From 107765f0e4b543dfc346851ee5b4605cc17eb1c6 Mon Sep 17 00:00:00 2001 From: rpolzer Date: Sat, 13 Sep 2008 18:28:57 +0000 Subject: [PATCH 1/1] initial git-svn-id: svn://svn.icculus.org/netradiant/trunk@1 61c419a2-8eb2-4b30-bcec-8cead039b335 --- CHANGES | 8216 ++++++ COMPILING | 72 + CONTRIBUTORS | 94 + DoxyConfig | 47 + Doxyfile | 1117 + Doxygen_files/Doxyfile | 159 + Doxygen_files/doxy_mainpage.h | 45 + Doxygen_files/doxygen_gtkradiant.css | 34 + Doxygen_files/doxygen_gtkradiant_foot.html | 49 + Doxygen_files/doxygen_gtkradiant_head.html | 38 + Doxygen_files/doxygen_index.html | 7 + Doxygen_files/doxygen_reference_foot.html | 46 + Doxygen_files/doxygen_reference_head.html | 38 + Doxygen_files/example/annotated.html | 103 + .../example/classIEpair-members.html | 110 + Doxygen_files/example/classIEpair.html | 414 + Doxygen_files/example/classes.html | 103 + Doxygen_files/example/doxygen.gif | Bin 0 -> 2378 bytes Doxygen_files/example/doxygen_gtkradiant.css | 35 + Doxygen_files/example/files.html | 102 + Doxygen_files/example/functions.html | 110 + Doxygen_files/example/graph_legend.dot | 16 + Doxygen_files/example/graph_legend.gif | Bin 0 -> 9344 bytes Doxygen_files/example/graph_legend.html | 141 + Doxygen_files/example/index.html | 102 + Doxygen_files/example/pages.html | 104 + Doxygen_files/example/test_8c-source.html | 140 + Doxygen_files/example/test_8c.html | 107 + Doxygen_files/example/todo.html | 105 + Doxygen_files/genDoxyfile | 159 + Doxygen_files/gendoxfunctions | 421 + Doxygen_files/images/body-left-tile.gif | Bin 0 -> 94 bytes Doxygen_files/images/body-lower-left.gif | Bin 0 -> 366 bytes Doxygen_files/images/body-lower-right.gif | Bin 0 -> 352 bytes Doxygen_files/images/body-lower-tile.gif | Bin 0 -> 67 bytes Doxygen_files/images/body-right-tile.gif | Bin 0 -> 94 bytes Doxygen_files/images/body-upper-left.gif | Bin 0 -> 358 bytes Doxygen_files/images/body-upper-right.gif | Bin 0 -> 362 bytes Doxygen_files/images/body-upper-tile.gif | Bin 0 -> 92 bytes Doxygen_files/images/gtkr_splash.jpg | Bin 0 -> 25053 bytes Doxygen_files/images/gtkr_splash_sm.jpg | Bin 0 -> 7599 bytes Doxygen_files/images/history_id_logo.gif | Bin 0 -> 1427 bytes Doxygen_files/images/top-right.gif | Bin 0 -> 606 bytes Doxygen_files/images/top-tile.gif | Bin 0 -> 122 bytes Doxygen_files/images/top-title.gif | Bin 0 -> 5356 bytes Doxygen_files/reference1.html | 333 + GPL | 340 + GtkRadiant.prj | 690 + GtkRadiant.sln | 272 + LGPL | 458 + LICENSE | 36 + README.doxygen | 51 + SConscript | 633 + SConstruct | 338 + TODO | 148 + contrib/bkgrnd2d/bitmaps/bkgrnd2d_conf.bmp | Bin 0 -> 216 bytes .../bkgrnd2d/bitmaps/bkgrnd2d_xy_toggle.bmp | Bin 0 -> 220 bytes .../bkgrnd2d/bitmaps/bkgrnd2d_xz_toggle.bmp | Bin 0 -> 220 bytes .../bkgrnd2d/bitmaps/bkgrnd2d_yz_toggle.bmp | Bin 0 -> 220 bytes contrib/bkgrnd2d/bkgrnd2d.cpp | 318 + contrib/bkgrnd2d/bkgrnd2d.def | 8 + contrib/bkgrnd2d/bkgrnd2d.h | 82 + contrib/bkgrnd2d/bkgrnd2d.vcproj | 171 + contrib/bkgrnd2d/dialog.cpp | 364 + contrib/bkgrnd2d/dialog.h | 35 + contrib/bkgrnd2d/plugin.cpp | 319 + contrib/bkgrnd2d/plugin.h | 79 + contrib/bkgrnd2d/readme_bkgrnd2d-b0.25.txt | 131 + contrib/bobtoolz/CPortals.h | 63 + contrib/bobtoolz/DBobView.cpp | 277 + contrib/bobtoolz/DBobView.h | 106 + contrib/bobtoolz/DBrush.cpp | 861 + contrib/bobtoolz/DBrush.h | 115 + contrib/bobtoolz/DEPair.cpp | 48 + contrib/bobtoolz/DEPair.h | 47 + contrib/bobtoolz/DEntity.cpp | 716 + contrib/bobtoolz/DEntity.h | 125 + contrib/bobtoolz/DMap.cpp | 178 + contrib/bobtoolz/DMap.h | 58 + contrib/bobtoolz/DPatch.cpp | 463 + contrib/bobtoolz/DPatch.h | 76 + contrib/bobtoolz/DPlane.cpp | 268 + contrib/bobtoolz/DPlane.h | 74 + contrib/bobtoolz/DPoint.cpp | 52 + contrib/bobtoolz/DPoint.h | 47 + contrib/bobtoolz/DShape.cpp | 469 + contrib/bobtoolz/DShape.h | 62 + contrib/bobtoolz/DTrainDrawer.cpp | 339 + contrib/bobtoolz/DTrainDrawer.h | 88 + contrib/bobtoolz/DTreePlanter.cpp | 291 + contrib/bobtoolz/DTreePlanter.h | 211 + contrib/bobtoolz/DVisDrawer.cpp | 144 + contrib/bobtoolz/DVisDrawer.h | 62 + contrib/bobtoolz/DWinding.cpp | 485 + contrib/bobtoolz/DWinding.h | 70 + contrib/bobtoolz/ScriptParser.cpp | 286 + contrib/bobtoolz/ScriptParser.h | 61 + contrib/bobtoolz/StdAfx.cpp | 25 + contrib/bobtoolz/StdAfx.h | 25 + contrib/bobtoolz/bitmaps/bobtoolz_caulk.bmp | Bin 0 -> 320 bytes contrib/bobtoolz/bitmaps/bobtoolz_cleanup.bmp | Bin 0 -> 316 bytes contrib/bobtoolz/bitmaps/bobtoolz_dropent.bmp | Bin 0 -> 312 bytes contrib/bobtoolz/bitmaps/bobtoolz_merge.bmp | Bin 0 -> 312 bytes contrib/bobtoolz/bitmaps/bobtoolz_poly.bmp | Bin 0 -> 320 bytes contrib/bobtoolz/bitmaps/bobtoolz_split.bmp | Bin 0 -> 312 bytes .../bitmaps/bobtoolz_trainpathplot.bmp | Bin 0 -> 324 bytes .../bobtoolz/bitmaps/bobtoolz_treeplanter.bmp | Bin 0 -> 356 bytes .../bobtoolz/bitmaps/bobtoolz_turnedge.bmp | Bin 0 -> 332 bytes contrib/bobtoolz/bobToolz-GTK.cpp | 328 + contrib/bobtoolz/bobToolz.def | 8 + contrib/bobtoolz/bobToolz.h | 64 + contrib/bobtoolz/bobToolz.rc | 533 + contrib/bobtoolz/bobToolz_gtk.vcproj | 328 + contrib/bobtoolz/bobtoolz-gtk.rc | 109 + contrib/bobtoolz/bobtoolz.vcproj | 481 + contrib/bobtoolz/bsploader.cpp | 263 + contrib/bobtoolz/bsploader.h | 137 + contrib/bobtoolz/bt/bt-el1.txt | 17 + contrib/bobtoolz/bt/bt-el2.txt | 0 contrib/bobtoolz/bt/ctf-blue.txt | 61 + contrib/bobtoolz/bt/ctf-red.txt | 61 + contrib/bobtoolz/bt/door-tex-trim.txt | 5 + contrib/bobtoolz/bt/door-tex.txt | 10 + contrib/bobtoolz/bt/tp_ent.txt | 14 + contrib/bobtoolz/cportals.cpp | 343 + contrib/bobtoolz/ctfToolz-GTK.cpp | 97 + contrib/bobtoolz/ctfresource_gtk.h | 34 + contrib/bobtoolz/ctfresource_gtk.rc | 109 + contrib/bobtoolz/ctftoolz.def | 12 + contrib/bobtoolz/dialogs/AboutDialog.cpp | 62 + contrib/bobtoolz/dialogs/AboutDialog.h | 64 + contrib/bobtoolz/dialogs/AutoCaulkDialog.cpp | 63 + contrib/bobtoolz/dialogs/AutoCaulkDialog.h | 66 + .../bobtoolz/dialogs/AutoCaulkStartDialog.cpp | 66 + .../bobtoolz/dialogs/AutoCaulkStartDialog.h | 71 + contrib/bobtoolz/dialogs/BrushCheckDialog.h | 65 + contrib/bobtoolz/dialogs/DoorDialog.cpp | 92 + contrib/bobtoolz/dialogs/DoorDialog.h | 74 + contrib/bobtoolz/dialogs/IntersectDialog.cpp | 65 + contrib/bobtoolz/dialogs/IntersectDialog.h | 70 + .../bobtoolz/dialogs/IntersectInfoDialog.cpp | 61 + .../bobtoolz/dialogs/IntersectInfoDialog.h | 65 + contrib/bobtoolz/dialogs/PolygonDialog.cpp | 116 + contrib/bobtoolz/dialogs/PolygonDialog.h | 74 + contrib/bobtoolz/dialogs/StairDialog.cpp | 105 + contrib/bobtoolz/dialogs/StairDialog.h | 74 + .../bobtoolz/dialogs/TextureResetDialog.cpp | 81 + contrib/bobtoolz/dialogs/TextureResetDialog.h | 73 + contrib/bobtoolz/dialogs/brushcheckdialog.cpp | 61 + contrib/bobtoolz/dialogs/dialogs-gtk.cpp | 1904 ++ contrib/bobtoolz/dialogs/dialogs-gtk.h | 107 + .../bobtoolz/dialogs/pathplotterdialog.cpp | 85 + contrib/bobtoolz/dialogs/pathplotterdialog.h | 70 + contrib/bobtoolz/funchandlers-GTK.cpp | 770 + contrib/bobtoolz/funchandlers-ctf-GTK.cpp | 214 + contrib/bobtoolz/funchandlers.cpp | 505 + contrib/bobtoolz/funchandlers.h | 72 + contrib/bobtoolz/lists.cpp | 86 + contrib/bobtoolz/lists.h | 25 + contrib/bobtoolz/misc.cpp | 406 + contrib/bobtoolz/misc.h | 57 + contrib/bobtoolz/res/plugin.rc2 | 13 + contrib/bobtoolz/resource-gtk.h | 15 + contrib/bobtoolz/resource.h | 115 + contrib/bobtoolz/shapes.cpp | 669 + contrib/bobtoolz/shapes.h | 56 + contrib/bobtoolz/txt/changelog.txt | 96 + contrib/bobtoolz/txt/readme.txt | 77 + contrib/bobtoolz/visfind.cpp | 247 + contrib/bobtoolz/visfind.h | 7 + contrib/brushexport/brushexport.def | 7 + contrib/brushexport/brushexport.vcproj | 257 + contrib/brushexport/callbacks.cpp | 148 + contrib/brushexport/callbacks.h | 12 + contrib/brushexport/export.cpp | 373 + contrib/brushexport/export.h | 15 + contrib/brushexport/interface.cpp | 241 + contrib/brushexport/plugin.cpp | 134 + contrib/brushexport/plugin.h | 25 + contrib/brushexport/support.cpp | 31 + contrib/brushexport/support.h | 24 + contrib/camera/bitmaps/camera_insp.bmp | Bin 0 -> 320 bytes contrib/camera/camera.cpp | 290 + contrib/camera/camera.def | 8 + contrib/camera/camera.h | 165 + contrib/camera/camera.vcproj | 190 + contrib/camera/dialogs.cpp | 1356 + contrib/camera/dialogs.h | 37 + contrib/camera/dialogs_common.cpp | 51 + contrib/camera/funchandlers.cpp | 271 + contrib/camera/funchandlers.h | 37 + contrib/camera/listener.cpp | 234 + contrib/camera/listener.h | 64 + contrib/camera/misc.cpp | 243 + contrib/camera/misc.h | 76 + contrib/camera/renderer.cpp | 183 + contrib/camera/renderer.h | 46 + contrib/gtkgensurf/.cvsignore | 4 + contrib/gtkgensurf/CHANGES | 73 + contrib/gtkgensurf/bitmap.cpp | 434 + contrib/gtkgensurf/dec.cpp | 1328 + contrib/gtkgensurf/face.cpp | 452 + contrib/gtkgensurf/font.cpp | 270 + contrib/gtkgensurf/gendlgs.cpp | 2364 ++ contrib/gtkgensurf/gendlgs.h | 151 + contrib/gtkgensurf/genmap.cpp | 2040 ++ contrib/gtkgensurf/gensurf.cpp | 467 + contrib/gtkgensurf/gensurf.def | 8 + contrib/gtkgensurf/gensurf.h | 432 + contrib/gtkgensurf/gtkgensurf.vcproj | 194 + contrib/gtkgensurf/heretic.cpp | 150 + contrib/gtkgensurf/plugin.cpp | 224 + contrib/gtkgensurf/triangle.c | 13236 +++++++++ contrib/gtkgensurf/triangle.h | 288 + contrib/gtkgensurf/view.cpp | 1287 + contrib/hydratoolz/hydratoolz.def | 8 + contrib/hydratoolz/hydratoolz.vcproj | 156 + contrib/hydratoolz/plugin.cpp | 542 + contrib/hydratoolz/plugin.h | 58 + contrib/prtview/.cvsignore | 8 + contrib/prtview/AboutDialog.cpp | 105 + contrib/prtview/AboutDialog.h | 25 + contrib/prtview/ConfigDialog.cpp | 526 + contrib/prtview/ConfigDialog.h | 25 + contrib/prtview/LoadPortalFileDialog.cpp | 202 + contrib/prtview/LoadPortalFileDialog.h | 26 + contrib/prtview/PrtView.aps | Bin 0 -> 21916 bytes contrib/prtview/PrtView.def | 7 + contrib/prtview/PrtView.txt | 12 + contrib/prtview/PrtView.vcproj | 333 + contrib/prtview/portals.cpp | 652 + contrib/prtview/portals.h | 157 + contrib/prtview/prtview.cpp | 328 + contrib/prtview/prtview.h | 35 + contrib/prtview/res/PrtView.rc2 | 13 + contrib/shaderplug/shaderplug.cpp | 263 + contrib/shaderplug/shaderplug.def | 7 + contrib/shaderplug/shaderplug.h | 27 + contrib/shaderplug/shaderplug.vcproj | 229 + contrib/sunplug/sunplug.cpp | 462 + contrib/sunplug/sunplug.def | 7 + contrib/sunplug/sunplug.h | 25 + contrib/sunplug/sunplug.vcproj | 238 + contrib/ufoaiplug/bitmaps/ufoai_actorclip.bmp | Bin 0 -> 1014 bytes contrib/ufoaiplug/bitmaps/ufoai_level1.bmp | Bin 0 -> 198 bytes contrib/ufoaiplug/bitmaps/ufoai_level2.bmp | Bin 0 -> 198 bytes contrib/ufoaiplug/bitmaps/ufoai_level3.bmp | Bin 0 -> 198 bytes contrib/ufoaiplug/bitmaps/ufoai_level4.bmp | Bin 0 -> 198 bytes contrib/ufoaiplug/bitmaps/ufoai_level5.bmp | Bin 0 -> 198 bytes contrib/ufoaiplug/bitmaps/ufoai_level6.bmp | Bin 0 -> 198 bytes contrib/ufoaiplug/bitmaps/ufoai_level7.bmp | Bin 0 -> 198 bytes contrib/ufoaiplug/bitmaps/ufoai_level8.bmp | Bin 0 -> 198 bytes contrib/ufoaiplug/bitmaps/ufoai_nodraw.bmp | Bin 0 -> 1014 bytes contrib/ufoaiplug/bitmaps/ufoai_stepon.bmp | Bin 0 -> 1014 bytes .../ufoaiplug/bitmaps/ufoai_weaponclip.bmp | Bin 0 -> 1014 bytes contrib/ufoaiplug/ufoai.cpp | 234 + contrib/ufoaiplug/ufoai.def | 7 + contrib/ufoaiplug/ufoai.h | 22 + contrib/ufoaiplug/ufoai.vcproj | 273 + contrib/ufoaiplug/ufoai_filters.cpp | 342 + contrib/ufoaiplug/ufoai_filters.h | 42 + contrib/ufoaiplug/ufoai_gtk.cpp | 207 + contrib/ufoaiplug/ufoai_gtk.h | 30 + contrib/ufoaiplug/ufoai_level.cpp | 293 + contrib/ufoaiplug/ufoai_level.h | 26 + games/NexuizPack/games/nexuiz.game | 28 + games/NexuizPack/gpl.txt | 340 + .../nexuiz.game/data/default_shaderlist.txt | 56 + .../NexuizPack/nexuiz.game/data/entities.def | 1044 + .../nexuiz.game/default_build_menu.xml | 59 + games/NexuizPack/nexuiz.game/game.xlink | 7 + gen.readme | 4 + gen.vcproj | 227 + gendox | 150 + generic_cpp.py | 40 + generic_h.py | 41 + generic_module.py | 59 + include/.cvsignore | 8 + include/aboutmsg.default | 1 + include/cullable.cpp | 23 + include/cullable.h | 71 + include/editable.cpp | 23 + include/editable.h | 60 + include/iarchive.cpp | 23 + include/iarchive.h | 162 + include/ibrush.cpp | 23 + include/ibrush.h | 127 + include/icamera.cpp | 23 + include/icamera.h | 67 + include/idatastream.cpp | 23 + include/idatastream.h | 83 + include/ieclass.cpp | 23 + include/ieclass.h | 118 + include/ientity.cpp | 23 + include/ientity.h | 154 + include/ifilesystem.cpp | 23 + include/ifilesystem.h | 133 + include/ifiletypes.cpp | 23 + include/ifiletypes.h | 76 + include/ifilter.cpp | 23 + include/ifilter.h | 88 + include/igl.cpp | 23 + include/igl.h | 2832 ++ include/iglrender.cpp | 3 + include/iglrender.h | 130 + include/igtkgl.cpp | 23 + include/igtkgl.h | 43 + include/iimage.cpp | 23 + include/iimage.h | 75 + include/imap.cpp | 23 + include/imap.h | 81 + include/imodel.cpp | 23 + include/imodel.h | 50 + include/include.vcproj | 500 + include/ipatch.cpp | 23 + include/ipatch.h | 292 + include/iplugin.cpp | 23 + include/iplugin.h | 53 + include/ireference.cpp | 23 + include/ireference.h | 78 + include/irender.cpp | 23 + include/irender.h | 177 + include/iscenegraph.cpp | 23 + include/iscenegraph.h | 221 + include/iscriplib.cpp | 23 + include/iscriplib.h | 85 + include/iselection.cpp | 23 + include/iselection.h | 145 + include/ishaders.cpp | 23 + include/ishaders.h | 195 + include/itexdef.cpp | 23 + include/itexdef.h | 42 + include/itextstream.cpp | 23 + include/itextstream.h | 114 + include/itextures.cpp | 23 + include/itextures.h | 92 + include/itoolbar.cpp | 23 + include/itoolbar.h | 66 + include/iundo.cpp | 23 + include/iundo.h | 110 + include/mapfile.cpp | 23 + include/mapfile.h | 76 + include/modelskin.cpp | 23 + include/modelskin.h | 92 + include/moduleobserver.cpp | 23 + include/moduleobserver.h | 32 + include/modulesystem.cpp | 23 + include/modulesystem.h | 258 + include/nameable.cpp | 23 + include/nameable.h | 41 + include/namespace.cpp | 23 + include/namespace.h | 63 + include/preferencesystem.cpp | 206 + include/preferencesystem.h | 56 + include/qerplugin.cpp | 23 + include/qerplugin.h | 178 + include/renderable.cpp | 23 + include/renderable.h | 76 + include/selectable.cpp | 23 + include/selectable.h | 305 + include/stream_version.h | 3 + include/version.default | 1 + include/warnings.h | 31 + include/windowobserver.cpp | 23 + include/windowobserver.h | 91 + install.py | 125 + libs/.cvsignore | 1 + libs/archivelib.cpp | 22 + libs/archivelib.h | 256 + libs/bytebool.cpp | 22 + libs/bytebool.h | 32 + libs/bytestreamutils.cpp | 22 + libs/bytestreamutils.h | 129 + libs/character.cpp | 23 + libs/character.h | 47 + libs/cmdlib.h | 103 + libs/cmdlib/.cvsignore | 1 + libs/cmdlib/.cvswrappers | 3 + libs/cmdlib/cmdlib.cpp | 129 + libs/cmdlib/cmdlib.vcproj | 173 + libs/container/array.cpp | 39 + libs/container/array.h | 197 + libs/container/cache.cpp | 22 + libs/container/cache.h | 206 + libs/container/container.cpp | 23 + libs/container/container.h | 393 + libs/container/hashfunc.cpp | 22 + libs/container/hashfunc.h | 434 + libs/container/hashtable.cpp | 65 + libs/container/hashtable.h | 474 + libs/container/stack.cpp | 23 + libs/container/stack.h | 239 + libs/convert.cpp | 23 + libs/convert.h | 305 + libs/ddslib.h | 250 + libs/ddslib/ddslib.c | 781 + libs/ddslib/ddslib.vcproj | 213 + libs/debugging/debugging.cpp | 28 + libs/debugging/debugging.h | 137 + libs/dragplanes.cpp | 23 + libs/dragplanes.h | 259 + libs/eclasslib.cpp | 22 + libs/eclasslib.h | 341 + libs/entitylib.cpp | 22 + libs/entitylib.h | 758 + libs/entityxml.cpp | 22 + libs/entityxml.h | 108 + libs/fs_filesystem.cpp | 22 + libs/fs_filesystem.h | 183 + libs/fs_path.cpp | 22 + libs/fs_path.h | 92 + libs/generic/arrayrange.cpp | 23 + libs/generic/arrayrange.h | 74 + libs/generic/bitfield.cpp | 23 + libs/generic/bitfield.h | 133 + libs/generic/callback.cpp | 285 + libs/generic/callback.h | 670 + libs/generic/callbackfwd.cpp | 3 + libs/generic/callbackfwd.h | 18 + libs/generic/constant.cpp | 40 + libs/generic/constant.h | 50 + libs/generic/enumeration.cpp | 23 + libs/generic/enumeration.h | 60 + libs/generic/functional.cpp | 3 + libs/generic/functional.h | 313 + libs/generic/object.cpp | 41 + libs/generic/object.h | 98 + libs/generic/reference.cpp | 22 + libs/generic/reference.h | 115 + libs/generic/referencecounted.cpp | 22 + libs/generic/referencecounted.h | 207 + libs/generic/static.cpp | 133 + libs/generic/static.h | 151 + libs/generic/vector.cpp | 3 + libs/generic/vector.h | 259 + libs/gtkutil/accelerator.cpp | 718 + libs/gtkutil/accelerator.h | 128 + libs/gtkutil/button.cpp | 137 + libs/gtkutil/button.h | 44 + libs/gtkutil/clipboard.cpp | 162 + libs/gtkutil/clipboard.h | 33 + libs/gtkutil/closure.cpp | 23 + libs/gtkutil/closure.h | 77 + libs/gtkutil/container.cpp | 23 + libs/gtkutil/container.h | 43 + libs/gtkutil/cursor.cpp | 97 + libs/gtkutil/cursor.h | 193 + libs/gtkutil/dialog.cpp | 303 + libs/gtkutil/dialog.h | 145 + libs/gtkutil/entry.cpp | 23 + libs/gtkutil/entry.h | 63 + libs/gtkutil/filechooser.cpp | 305 + libs/gtkutil/filechooser.h | 38 + libs/gtkutil/frame.cpp | 35 + libs/gtkutil/frame.h | 29 + libs/gtkutil/glfont.cpp | 141 + libs/gtkutil/glfont.h | 48 + libs/gtkutil/glwidget.cpp | 274 + libs/gtkutil/glwidget.h | 39 + libs/gtkutil/gtkutil.vcproj | 349 + libs/gtkutil/idledraw.cpp | 23 + libs/gtkutil/idledraw.h | 69 + libs/gtkutil/image.cpp | 96 + libs/gtkutil/image.h | 36 + libs/gtkutil/menu.cpp | 311 + libs/gtkutil/menu.h | 58 + libs/gtkutil/messagebox.cpp | 213 + libs/gtkutil/messagebox.h | 31 + libs/gtkutil/nonmodal.cpp | 23 + libs/gtkutil/nonmodal.h | 186 + libs/gtkutil/paned.cpp | 100 + libs/gtkutil/paned.h | 29 + libs/gtkutil/pointer.cpp | 23 + libs/gtkutil/pointer.h | 40 + libs/gtkutil/toolbar.cpp | 78 + libs/gtkutil/toolbar.h | 38 + libs/gtkutil/widget.cpp | 23 + libs/gtkutil/widget.h | 190 + libs/gtkutil/window.cpp | 170 + libs/gtkutil/window.h | 174 + libs/gtkutil/xorrectangle.cpp | 23 + libs/gtkutil/xorrectangle.h | 125 + libs/imagelib.cpp | 23 + libs/imagelib.h | 152 + libs/instancelib.cpp | 22 + libs/instancelib.h | 193 + libs/jpeg6/.cvsignore | 8 + libs/jpeg6/.cvswrappers | 3 + libs/jpeg6/README | 385 + libs/jpeg6/jchuff.h | 34 + libs/jpeg6/jcomapi.cpp | 94 + libs/jpeg6/jconfig.h | 41 + libs/jpeg6/jdapimin.cpp | 400 + libs/jpeg6/jdapistd.cpp | 275 + libs/jpeg6/jdatasrc.cpp | 189 + libs/jpeg6/jdcoefct.cpp | 725 + libs/jpeg6/jdcolor.cpp | 367 + libs/jpeg6/jdct.h | 176 + libs/jpeg6/jddctmgr.cpp | 270 + libs/jpeg6/jdhuff.cpp | 574 + libs/jpeg6/jdhuff.h | 202 + libs/jpeg6/jdinput.cpp | 381 + libs/jpeg6/jdmainct.cpp | 512 + libs/jpeg6/jdmarker.cpp | 1052 + libs/jpeg6/jdmaster.cpp | 558 + libs/jpeg6/jdpostct.cpp | 290 + libs/jpeg6/jdsample.cpp | 478 + libs/jpeg6/jdtrans.cpp | 122 + libs/jpeg6/jerror.cpp | 233 + libs/jpeg6/jerror.h | 278 + libs/jpeg6/jfdctflt.cpp | 168 + libs/jpeg6/jidctflt.cpp | 241 + libs/jpeg6/jinclude.h | 91 + libs/jpeg6/jmemmgr.cpp | 1115 + libs/jpeg6/jmemnobs.cpp | 103 + libs/jpeg6/jmemsys.h | 182 + libs/jpeg6/jmorecfg.h | 348 + libs/jpeg6/jpeg6.vcproj | 299 + libs/jpeg6/jpegint.h | 388 + libs/jpeg6/jpgload.cpp | 186 + libs/jpeg6/jutils.cpp | 175 + libs/jpeg6/jversion.h | 14 + libs/jpeglib.h | 1123 + libs/l_net/.cvsignore | 5 + libs/l_net/l_net.c | 626 + libs/l_net/l_net.h | 125 + libs/l_net/l_net.vcproj | 177 + libs/l_net/l_net_berkley.c | 766 + libs/l_net/l_net_wins.c | 789 + libs/l_net/l_net_wins.h | 52 + libs/libs.vcproj | 876 + libs/maplib.cpp | 22 + libs/maplib.h | 282 + libs/math/aabb.cpp | 23 + libs/math/aabb.h | 331 + libs/math/curve.cpp | 23 + libs/math/curve.h | 273 + libs/math/expression.cpp | 243 + libs/math/expression.h | 617 + libs/math/frustum.cpp | 23 + libs/math/frustum.h | 629 + libs/math/line.cpp | 23 + libs/math/line.h | 151 + libs/math/matrix.cpp | 23 + libs/math/matrix.h | 1322 + libs/math/pi.cpp | 23 + libs/math/pi.h | 45 + libs/math/plane.cpp | 23 + libs/math/plane.h | 153 + libs/math/quaternion.cpp | 23 + libs/math/quaternion.h | 327 + libs/math/vector.cpp | 23 + libs/math/vector.h | 800 + libs/mathlib.h | 422 + libs/mathlib/bbox.c | 462 + libs/mathlib/line.c | 41 + libs/mathlib/m4x4.c | 1877 ++ libs/mathlib/mathlib.c | 578 + libs/mathlib/mathlib.vcproj | 320 + libs/mathlib/ray.c | 140 + libs/md5lib.h | 91 + libs/md5lib/md5lib.c | 395 + libs/md5lib/md5lib.vcproj | 140 + libs/memory/allocator.cpp | 80 + libs/memory/allocator.h | 336 + libs/moduleobservers.cpp | 23 + libs/moduleobservers.h | 64 + libs/modulesystem/moduleregistry.cpp | 23 + libs/modulesystem/moduleregistry.h | 65 + libs/modulesystem/modulesmap.cpp | 22 + libs/modulesystem/modulesmap.h | 152 + libs/modulesystem/singletonmodule.cpp | 48 + libs/modulesystem/singletonmodule.h | 150 + libs/os/dir.cpp | 23 + libs/os/dir.h | 75 + libs/os/file.cpp | 23 + libs/os/file.h | 152 + libs/os/path.cpp | 22 + libs/os/path.h | 283 + libs/picomodel.h | 351 + libs/picomodel/lwo/clip.c | 278 + libs/picomodel/lwo/envelope.c | 600 + libs/picomodel/lwo/list.c | 101 + libs/picomodel/lwo/lwio.c | 442 + libs/picomodel/lwo/lwo2.c | 308 + libs/picomodel/lwo/lwo2.h | 651 + libs/picomodel/lwo/lwob.c | 723 + libs/picomodel/lwo/pntspols.c | 537 + libs/picomodel/lwo/surface.c | 1005 + libs/picomodel/lwo/vecmath.c | 37 + libs/picomodel/lwo/vmap.c | 243 + libs/picomodel/picointernal.c | 1356 + libs/picomodel/picointernal.h | 206 + libs/picomodel/picomodel.c | 2292 ++ libs/picomodel/picomodel.vcproj | 285 + libs/picomodel/picomodules.c | 94 + libs/picomodel/pm_3ds.c | 777 + libs/picomodel/pm_ase.c | 1193 + libs/picomodel/pm_fm.c | 667 + libs/picomodel/pm_fm.h | 367 + libs/picomodel/pm_lwo.c | 445 + libs/picomodel/pm_md2.c | 667 + libs/picomodel/pm_md3.c | 425 + libs/picomodel/pm_mdc.c | 750 + libs/picomodel/pm_ms3d.c | 494 + libs/picomodel/pm_obj.c | 898 + libs/picomodel/pm_terrain.c | 610 + libs/pivot.cpp | 23 + libs/pivot.h | 295 + libs/profile/file.cpp | 383 + libs/profile/file.h | 158 + libs/profile/profile.cpp | 297 + libs/profile/profile.h | 49 + libs/profile/profile.vcproj | 181 + libs/radiant_jpeglib.h | 1103 + libs/render.cpp | 22 + libs/render.h | 1372 + libs/scenelib.cpp | 22 + libs/scenelib.h | 1103 + libs/script/scripttokeniser.cpp | 23 + libs/script/scripttokeniser.h | 383 + libs/script/scripttokenwriter.cpp | 23 + libs/script/scripttokenwriter.h | 88 + libs/selectionlib.cpp | 22 + libs/selectionlib.h | 207 + libs/shaderlib.cpp | 22 + libs/shaderlib.h | 95 + libs/signal/isignal.cpp | 3 + libs/signal/isignal.h | 224 + libs/signal/signal.cpp | 105 + libs/signal/signal.h | 396 + libs/signal/signalfwd.cpp | 3 + libs/signal/signalfwd.h | 48 + libs/splines/.cvsignore | 1 + libs/splines/Splines.vcproj | 163 + libs/splines/math_angles.cpp | 150 + libs/splines/math_angles.h | 195 + libs/splines/math_matrix.cpp | 134 + libs/splines/math_matrix.h | 223 + libs/splines/math_quaternion.cpp | 78 + libs/splines/math_quaternion.h | 190 + libs/splines/math_vector.cpp | 143 + libs/splines/math_vector.h | 574 + libs/splines/q_parse.cpp | 535 + libs/splines/q_shared.cpp | 976 + libs/splines/q_shared.h | 796 + libs/splines/splines.cpp | 1421 + libs/splines/splines.h | 1120 + libs/splines/util_list.h | 346 + libs/splines/util_str.cpp | 628 + libs/splines/util_str.h | 817 + libs/str.cpp | 31 + libs/str.h | 513 + libs/stream/filestream.cpp | 22 + libs/stream/filestream.h | 194 + libs/stream/memstream.cpp | 22 + libs/stream/memstream.h | 81 + libs/stream/stringstream.cpp | 22 + libs/stream/stringstream.h | 177 + libs/stream/textfilestream.cpp | 22 + libs/stream/textfilestream.h | 87 + libs/stream/textstream.cpp | 22 + libs/stream/textstream.h | 533 + libs/string/pooledstring.cpp | 26 + libs/string/pooledstring.h | 108 + libs/string/string.cpp | 28 + libs/string/string.h | 610 + libs/string/stringfwd.cpp | 23 + libs/string/stringfwd.h | 35 + libs/stringio.cpp | 22 + libs/stringio.h | 553 + libs/texturelib.cpp | 22 + libs/texturelib.h | 44 + libs/transformlib.cpp | 22 + libs/transformlib.h | 193 + libs/traverselib.cpp | 22 + libs/traverselib.h | 405 + libs/typesystem.cpp | 23 + libs/typesystem.h | 149 + libs/undolib.cpp | 22 + libs/undolib.h | 161 + libs/uniquenames.cpp | 23 + libs/uniquenames.h | 335 + libs/versionlib.cpp | 23 + libs/versionlib.h | 91 + libs/xml/ixml.cpp | 22 + libs/xml/ixml.h | 60 + libs/xml/xmlelement.cpp | 22 + libs/xml/xmlelement.h | 105 + libs/xml/xmlparser.cpp | 22 + libs/xml/xmlparser.h | 242 + libs/xml/xmltextags.cpp | 597 + libs/xml/xmltextags.h | 106 + libs/xml/xmlwriter.cpp | 22 + libs/xml/xmlwriter.h | 194 + makeversion.py | 102 + plugins/archivepak/archive.cpp | 229 + plugins/archivepak/archive.h | 23 + plugins/archivepak/archivepak.def | 7 + plugins/archivepak/archivepak.vcproj | 285 + plugins/archivepak/pak.cpp | 23 + plugins/archivepak/pak.h | 40 + plugins/archivepak/plugin.cpp | 58 + plugins/archivepak/plugin.h | 25 + plugins/archivewad/archive.cpp | 230 + plugins/archivewad/archive.h | 23 + plugins/archivewad/archivewad.def | 7 + plugins/archivewad/archivewad.vcproj | 285 + plugins/archivewad/plugin.cpp | 58 + plugins/archivewad/plugin.h | 25 + plugins/archivewad/wad.cpp | 23 + plugins/archivewad/wad.h | 78 + plugins/archivezip/archive.cpp | 353 + plugins/archivezip/archive.h | 22 + plugins/archivezip/archivezip.def | 7 + plugins/archivezip/archivezip.vcproj | 298 + plugins/archivezip/pkzip.cpp | 23 + plugins/archivezip/pkzip.h | 266 + plugins/archivezip/plugin.cpp | 82 + plugins/archivezip/plugin.h | 25 + plugins/archivezip/zlibstream.cpp | 23 + plugins/archivezip/zlibstream.h | 75 + plugins/config.mk | 32 + plugins/entity/angle.cpp | 23 + plugins/entity/angle.h | 101 + plugins/entity/angles.cpp | 23 + plugins/entity/angles.h | 140 + plugins/entity/colour.cpp | 23 + plugins/entity/colour.h | 102 + plugins/entity/curve.cpp | 23 + plugins/entity/curve.h | 517 + plugins/entity/doom3group.cpp | 832 + plugins/entity/doom3group.h | 35 + plugins/entity/eclassmodel.cpp | 532 + plugins/entity/eclassmodel.h | 35 + plugins/entity/entity.cpp | 390 + plugins/entity/entity.h | 46 + plugins/entity/entityq3.def | 7 + plugins/entity/entityq3.vcproj | 456 + plugins/entity/filters.cpp | 72 + plugins/entity/filters.h | 82 + plugins/entity/generic.cpp | 499 + plugins/entity/generic.h | 27 + plugins/entity/group.cpp | 453 + plugins/entity/group.h | 27 + plugins/entity/keyobservers.cpp | 23 + plugins/entity/keyobservers.h | 53 + plugins/entity/light.cpp | 1912 ++ plugins/entity/light.h | 41 + plugins/entity/miscmodel.cpp | 476 + plugins/entity/miscmodel.h | 29 + plugins/entity/model.cpp | 23 + plugins/entity/model.h | 124 + plugins/entity/modelskinkey.cpp | 23 + plugins/entity/modelskinkey.h | 111 + plugins/entity/namedentity.cpp | 23 + plugins/entity/namedentity.h | 113 + plugins/entity/namekeys.cpp | 23 + plugins/entity/namekeys.h | 149 + plugins/entity/origin.cpp | 23 + plugins/entity/origin.h | 169 + plugins/entity/plugin.cpp | 160 + plugins/entity/plugin.h | 25 + plugins/entity/rotation.cpp | 23 + plugins/entity/rotation.h | 199 + plugins/entity/scale.cpp | 23 + plugins/entity/scale.h | 128 + plugins/entity/skincache.cpp | 348 + plugins/entity/skincache.h | 28 + plugins/entity/targetable.cpp | 37 + plugins/entity/targetable.h | 457 + plugins/image/.cvsignore | 8 + plugins/image/bmp.cpp | 207 + plugins/image/bmp.h | 31 + plugins/image/dds.cpp | 56 + plugins/image/dds.h | 30 + plugins/image/image.cpp | 159 + plugins/image/image.h | 25 + plugins/image/imageq3.def | 7 + plugins/image/imageq3.vcproj | 315 + plugins/image/jpeg.cpp | 406 + plugins/image/jpeg.h | 40 + plugins/image/pcx.cpp | 222 + plugins/image/pcx.h | 30 + plugins/image/tga.cpp | 420 + plugins/image/tga.h | 31 + plugins/imagehl/hlw.cpp | 157 + plugins/imagehl/hlw.h | 31 + plugins/imagehl/imagehl.cpp | 113 + plugins/imagehl/imagehl.def | 7 + plugins/imagehl/imagehl.h | 25 + plugins/imagehl/imagehl.txt | 30 + plugins/imagehl/imagehl.vcproj | 299 + plugins/imagehl/mip.cpp | 205 + plugins/imagehl/mip.h | 31 + plugins/imagehl/sprite.cpp | 229 + plugins/imagehl/sprite.h | 31 + plugins/imagepng/imagepng.def | 7 + plugins/imagepng/imagepng.vcproj | 273 + plugins/imagepng/plugin.cpp | 204 + plugins/imagepng/plugin.h | 25 + plugins/imageq2/imageq2.cpp | 114 + plugins/imageq2/imageq2.def | 7 + plugins/imageq2/imageq2.h | 25 + plugins/imageq2/imageq2.vcproj | 291 + plugins/imageq2/wal.cpp | 157 + plugins/imageq2/wal.h | 32 + plugins/imageq2/wal32.cpp | 92 + plugins/imageq2/wal32.h | 31 + plugins/mapq3/mapq3.def | 7 + plugins/mapq3/mapq3.vcproj | 288 + plugins/mapq3/parse.cpp | 151 + plugins/mapq3/parse.h | 48 + plugins/mapq3/plugin.cpp | 658 + plugins/mapq3/plugin.h | 25 + plugins/mapq3/write.cpp | 124 + plugins/mapq3/write.h | 29 + plugins/mapxml/mapxml.def | 7 + plugins/mapxml/mapxml.vcproj | 295 + plugins/mapxml/plugin.cpp | 107 + plugins/mapxml/plugin.h | 25 + plugins/mapxml/xmlparse.cpp | 277 + plugins/mapxml/xmlparse.h | 29 + plugins/mapxml/xmlwrite.cpp | 91 + plugins/mapxml/xmlwrite.h | 29 + plugins/md3model/doc/md3-design.txt | 172 + plugins/md3model/ident.h | 33 + plugins/md3model/md2.cpp | 309 + plugins/md3model/md2.h | 30 + plugins/md3model/md3.cpp | 340 + plugins/md3model/md3.h | 29 + plugins/md3model/md3normals.cpp | 23 + plugins/md3model/md3normals.h | 45 + plugins/md3model/md5.cpp | 454 + plugins/md3model/md5.h | 29 + plugins/md3model/mdc.cpp | 292 + plugins/md3model/mdc.h | 30 + plugins/md3model/mdl.cpp | 241 + plugins/md3model/mdl.h | 29 + plugins/md3model/mdlformat.cpp | 23 + plugins/md3model/mdlformat.h | 118 + plugins/md3model/mdlimage.cpp | 160 + plugins/md3model/mdlimage.h | 30 + plugins/md3model/mdlnormals.cpp | 23 + plugins/md3model/mdlnormals.h | 191 + plugins/md3model/model.cpp | 23 + plugins/md3model/model.h | 643 + plugins/md3model/modelmd3.def | 7 + plugins/md3model/modelmd3.vcproj | 367 + plugins/md3model/plugin.cpp | 247 + plugins/md3model/plugin.h | 25 + plugins/model/bitmaps/model_reload_entity.bmp | Bin 0 -> 308 bytes plugins/model/bitmaps/picomodel.bmp | Bin 0 -> 192660 bytes plugins/model/model.cpp | 1048 + plugins/model/model.h | 30 + plugins/model/modelpico.def | 7 + plugins/model/modelpico.vcproj | 286 + plugins/model/plugin.cpp | 189 + plugins/model/plugin.h | 25 + plugins/sample/sample.cpp | 95 + plugins/sample/sample.def | 7 + plugins/sample/sample.h | 25 + plugins/sample/sample.vcproj | 269 + plugins/shaders/plugin.cpp | 157 + plugins/shaders/plugin.h | 25 + plugins/shaders/shaders.cpp | 2074 ++ plugins/shaders/shaders.h | 55 + plugins/shaders/shaders.proj | Bin 0 -> 73728 bytes plugins/shaders/shadershl.def | 8 + plugins/shaders/shadersq3.def | 7 + plugins/shaders/shadersq3.vcproj | 285 + plugins/spritemodel/plugin.cpp | 221 + plugins/spritemodel/plugin.h | 47 + plugins/spritemodel/spritemodel.cpp | 177 + plugins/spritemodel/spritemodel.def | 8 + plugins/spritemodel/spritemodel.h | 59 + plugins/spritemodel/spritemodel.vcproj | 186 + plugins/textool/.cvsignore | 13 + plugins/textool/.cvswrappers | 2 + plugins/textool/2DView.cpp | 202 + plugins/textool/2DView.h | 70 + plugins/textool/ControlPointsManager.cpp | 332 + plugins/textool/ControlPointsManager.h | 133 + plugins/textool/Doc/.cvswrappers | 1 + plugins/textool/Doc/Image2.jpg | Bin 0 -> 50020 bytes plugins/textool/Doc/TexTool.html | 123 + plugins/textool/StdAfx.cpp | 28 + plugins/textool/StdAfx.h | 154 + plugins/textool/TexTool.cpp | 962 + plugins/textool/TexTool.def | 12 + plugins/textool/TexTool.rc | 136 + plugins/textool/TexTool.vcproj | 211 + plugins/textool/changelog.txt | 8 + plugins/textool/resource.h | 23 + plugins/vfspk3/.cvsignore | 11 + plugins/vfspk3/archive.cpp | 178 + plugins/vfspk3/archive.h | 29 + plugins/vfspk3/vfs.cpp | 683 + plugins/vfspk3/vfs.h | 39 + plugins/vfspk3/vfspk3.cpp | 85 + plugins/vfspk3/vfspk3.h | 25 + plugins/vfspk3/vfspk3.proj | Bin 0 -> 16384 bytes plugins/vfspk3/vfsq3.def | 7 + plugins/vfspk3/vfsq3.vcproj | 297 + q3map2build.sh | 152 + radiant/.cvsignore | 16 + radiant/GtkRadiant.vcproj | 961 + radiant/autosave.cpp | 224 + radiant/autosave.h | 35 + radiant/brush.cpp | 419 + radiant/brush.h | 4218 +++ radiant/brush_primit.cpp | 1466 + radiant/brush_primit.h | 140 + radiant/brushmanip.cpp | 1490 + radiant/brushmanip.h | 81 + radiant/brushmodule.cpp | 395 + radiant/brushmodule.h | 27 + radiant/brushnode.cpp | 23 + radiant/brushnode.h | 167 + radiant/brushtokens.cpp | 23 + radiant/brushtokens.h | 738 + radiant/brushxml.cpp | 23 + radiant/brushxml.h | 457 + radiant/build.cpp | 1158 + radiant/build.h | 44 + radiant/camwindow.cpp | 2091 ++ radiant/camwindow.h | 87 + radiant/clippertool.cpp | 23 + radiant/clippertool.h | 25 + radiant/commands.cpp | 424 + radiant/commands.h | 54 + radiant/console.cpp | 273 + radiant/console.h | 47 + radiant/csg.cpp | 692 + radiant/csg.h | 46 + radiant/dialog.cpp | 753 + radiant/dialog.h | 208 + radiant/eclass.cpp | 414 + radiant/eclass.h | 25 + radiant/eclass_def.cpp | 407 + radiant/eclass_def.h | 25 + radiant/eclass_doom3.cpp | 929 + radiant/eclass_doom3.h | 25 + radiant/eclass_fgd.cpp | 807 + radiant/eclass_fgd.h | 25 + radiant/eclass_xml.cpp | 610 + radiant/eclass_xml.h | 25 + radiant/entity.cpp | 482 + radiant/entity.h | 42 + radiant/entityinspector.cpp | 1761 ++ radiant/entityinspector.h | 31 + radiant/entitylist.cpp | 430 + radiant/entitylist.h | 33 + radiant/environment.cpp | 200 + radiant/environment.h | 29 + radiant/error.cpp | 138 + radiant/error.h | 27 + radiant/feedback.cpp | 372 + radiant/feedback.h | 186 + radiant/filetypes.cpp | 139 + radiant/filetypes.h | 29 + radiant/filters.cpp | 321 + radiant/filters.h | 28 + radiant/findtexturedialog.cpp | 301 + radiant/findtexturedialog.h | 34 + radiant/glwidget.cpp | 55 + radiant/glwidget.h | 25 + radiant/grid.cpp | 271 + radiant/grid.h | 41 + radiant/groupdialog.cpp | 232 + radiant/groupdialog.h | 48 + radiant/gtkdlgs.cpp | 1105 + radiant/gtkdlgs.h | 59 + radiant/gtkmisc.cpp | 164 + radiant/gtkmisc.h | 75 + radiant/help.cpp | 140 + radiant/help.h | 28 + radiant/image.cpp | 70 + radiant/image.h | 28 + radiant/main.cpp | 656 + radiant/main.h | 25 + radiant/mainframe.cpp | 3574 +++ radiant/mainframe.h | 286 + radiant/map.cpp | 2326 ++ radiant/map.h | 173 + radiant/mru.cpp | 253 + radiant/mru.h | 33 + radiant/multimon.cpp | 108 + radiant/multimon.h | 53 + radiant/nullmodel.cpp | 214 + radiant/nullmodel.h | 37 + radiant/parse.cpp | 51 + radiant/parse.h | 25 + radiant/patch.cpp | 2831 ++ radiant/patch.h | 2070 ++ radiant/patchdialog.cpp | 1218 + radiant/patchdialog.h | 43 + radiant/patchmanip.cpp | 1085 + radiant/patchmanip.h | 57 + radiant/patchmodule.cpp | 251 + radiant/patchmodule.h | 25 + radiant/plugin.cpp | 358 + radiant/plugin.h | 47 + radiant/pluginapi.cpp | 92 + radiant/pluginapi.h | 33 + radiant/pluginmanager.cpp | 253 + radiant/pluginmanager.h | 65 + radiant/pluginmenu.cpp | 196 + radiant/pluginmenu.h | 33 + radiant/plugintoolbar.cpp | 151 + radiant/plugintoolbar.h | 33 + radiant/points.cpp | 414 + radiant/points.h | 40 + radiant/preferencedictionary.cpp | 23 + radiant/preferencedictionary.h | 295 + radiant/preferences.cpp | 1055 + radiant/preferences.h | 434 + radiant/qe3.cpp | 394 + radiant/qe3.h | 66 + radiant/qgl.cpp | 1658 ++ radiant/qgl.h | 33 + radiant/radiant.ico | Bin 0 -> 2238 bytes radiant/radiant.rc | 72 + radiant/radiant_old.ico | Bin 0 -> 1078 bytes radiant/referencecache.cpp | 846 + radiant/referencecache.h | 42 + radiant/renderer.cpp | 23 + radiant/renderer.h | 199 + radiant/renderstate.cpp | 2693 ++ radiant/renderstate.h | 28 + radiant/resource.h | 18 + radiant/scenegraph.cpp | 312 + radiant/scenegraph.h | 25 + radiant/select.cpp | 1216 + radiant/select.h | 91 + radiant/selection.cpp | 4141 +++ radiant/selection.h | 54 + radiant/server.cpp | 287 + radiant/server.h | 31 + radiant/shaders.cpp | 73 + radiant/shaders.h | 27 + radiant/sockets.cpp | 47 + radiant/sockets.h | 14 + radiant/stacktrace.cpp | 318 + radiant/stacktrace.h | 28 + radiant/surfacedialog.cpp | 2450 ++ radiant/surfacedialog.h | 60 + radiant/texmanip.cpp | 388 + radiant/texmanip.h | 40 + radiant/textureentry.cpp | 23 + radiant/textureentry.h | 129 + radiant/textures.cpp | 899 + radiant/textures.h | 33 + radiant/texwindow.cpp | 2703 ++ radiant/texwindow.h | 62 + radiant/timer.cpp | 109 + radiant/timer.h | 103 + radiant/treemodel.cpp | 1551 + radiant/treemodel.h | 37 + radiant/undo.cpp | 590 + radiant/undo.h | 25 + radiant/url.cpp | 66 + radiant/url.h | 27 + radiant/view.cpp | 57 + radiant/view.h | 211 + radiant/watchbsp.cpp | 879 + radiant/watchbsp.h | 43 + radiant/winding.cpp | 346 + radiant/winding.h | 322 + radiant/windowobservers.cpp | 170 + radiant/windowobservers.h | 69 + radiant/xmlstuff.cpp | 32 + radiant/xmlstuff.h | 90 + radiant/xywindow.cpp | 2954 ++ radiant/xywindow.h | 315 + run_python.bat | 9 + setup/PluginSDK/BuildGtkSrc | 13 + setup/PluginSDK/BuildSDK | 53 + setup/PluginSDK/BuildZip | 6 + setup/PluginSDK/README.html | 171 + setup/PluginSDK/TODO | 33 + setup/changelog.txt | 1800 ++ setup/common/setup.pm | 139 + setup/credits.html | 220 + setup/data/tools/bitmaps/black.bmp | Bin 0 -> 248 bytes setup/data/tools/bitmaps/brush_flipx.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/brush_flipy.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/brush_flipz.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/brush_rotatex.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/brush_rotatey.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/brush_rotatez.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/cap_bevel.bmp | Bin 0 -> 154 bytes setup/data/tools/bitmaps/cap_cylinder.bmp | Bin 0 -> 156 bytes setup/data/tools/bitmaps/cap_endcap.bmp | Bin 0 -> 154 bytes setup/data/tools/bitmaps/cap_ibevel.bmp | Bin 0 -> 154 bytes setup/data/tools/bitmaps/cap_iendcap.bmp | Bin 0 -> 154 bytes setup/data/tools/bitmaps/console.bmp | Bin 0 -> 776 bytes setup/data/tools/bitmaps/curve_cap.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/dontselectcurve.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/dontselectmodel.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/ellipsis.bmp | Bin 0 -> 108 bytes setup/data/tools/bitmaps/entities.bmp | Bin 0 -> 776 bytes setup/data/tools/bitmaps/file_open.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/file_save.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/icon.bmp | Bin 0 -> 3126 bytes setup/data/tools/bitmaps/lightinspector.bmp | Bin 0 -> 776 bytes setup/data/tools/bitmaps/logo.bmp | Bin 0 -> 47936 bytes setup/data/tools/bitmaps/modify_edges.bmp | Bin 0 -> 316 bytes setup/data/tools/bitmaps/modify_faces.bmp | Bin 0 -> 316 bytes setup/data/tools/bitmaps/modify_vertices.bmp | Bin 0 -> 316 bytes setup/data/tools/bitmaps/noFalloff.bmp | Bin 0 -> 1592 bytes setup/data/tools/bitmaps/notex.bmp | Bin 0 -> 4312 bytes setup/data/tools/bitmaps/patch_bend.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/patch_drilldown.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/patch_insdel.bmp | Bin 0 -> 238 bytes .../tools/bitmaps/patch_showboundingbox.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/patch_weld.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/patch_wireframe.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/popup_selection.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/redo.bmp | Bin 0 -> 776 bytes setup/data/tools/bitmaps/refresh_models.bmp | Bin 0 -> 776 bytes setup/data/tools/bitmaps/scalelockx.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/scalelocky.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/scalelockz.bmp | Bin 0 -> 238 bytes .../data/tools/bitmaps/select_mouseresize.bmp | Bin 0 -> 200 bytes .../data/tools/bitmaps/select_mouserotate.bmp | Bin 0 -> 238 bytes .../data/tools/bitmaps/select_mousescale.bmp | Bin 0 -> 238 bytes .../tools/bitmaps/select_mousetranslate.bmp | Bin 0 -> 238 bytes .../data/tools/bitmaps/selection_csgmerge.bmp | Bin 0 -> 238 bytes .../tools/bitmaps/selection_csgsubtract.bmp | Bin 0 -> 238 bytes .../tools/bitmaps/selection_makehollow.bmp | Bin 0 -> 238 bytes .../bitmaps/selection_selectcompletetall.bmp | Bin 0 -> 238 bytes .../tools/bitmaps/selection_selectinside.bmp | Bin 0 -> 238 bytes .../bitmaps/selection_selectpartialtall.bmp | Bin 0 -> 238 bytes .../bitmaps/selection_selecttouching.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/shadernotex.bmp | Bin 0 -> 4308 bytes setup/data/tools/bitmaps/show_entities.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/splash.bmp | Bin 0 -> 299934 bytes setup/data/tools/bitmaps/texture_browser.bmp | Bin 0 -> 776 bytes setup/data/tools/bitmaps/texture_lock.bmp | Bin 0 -> 774 bytes setup/data/tools/bitmaps/textures_popup.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/undo.bmp | Bin 0 -> 776 bytes .../data/tools/bitmaps/view_cameratoggle.bmp | Bin 0 -> 238 bytes .../data/tools/bitmaps/view_cameraupdate.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/view_change.bmp | Bin 0 -> 126 bytes setup/data/tools/bitmaps/view_clipper.bmp | Bin 0 -> 238 bytes .../data/tools/bitmaps/view_cubicclipping.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/view_entity.bmp | Bin 0 -> 238 bytes setup/data/tools/bitmaps/white.bmp | Bin 0 -> 248 bytes setup/data/tools/bitmaps/window1.bmp | Bin 0 -> 572 bytes setup/data/tools/bitmaps/window2.bmp | Bin 0 -> 568 bytes setup/data/tools/bitmaps/window3.bmp | Bin 0 -> 572 bytes setup/data/tools/bitmaps/window4.bmp | Bin 0 -> 572 bytes .../data/tools/gl/lighting_DBS_XY_Z_arbfp1.cg | 92 + .../data/tools/gl/lighting_DBS_XY_Z_arbvp1.cg | 78 + setup/data/tools/gl/lighting_DBS_omni_fp.glp | 86 + setup/data/tools/gl/lighting_DBS_omni_fp.glsl | 73 + setup/data/tools/gl/lighting_DBS_omni_vp.glp | 410 + setup/data/tools/gl/lighting_DBS_omni_vp.glsl | 58 + setup/data/tools/gl/utils.cg | 36 + setup/data/tools/gl/zfill_arbfp1.cg | 47 + setup/data/tools/gl/zfill_arbvp1.cg | 49 + setup/data/tools/gl/zfill_fp.glp | 19 + setup/data/tools/gl/zfill_fp.glsl | 29 + setup/data/tools/gl/zfill_vp.glp | 384 + setup/data/tools/gl/zfill_vp.glsl | 35 + setup/data/tools/global.xlink | 11 + .../tools/plugins/bitmaps/bobtoolz_caulk.bmp | Bin 0 -> 320 bytes .../plugins/bitmaps/bobtoolz_cleanup.bmp | Bin 0 -> 316 bytes .../plugins/bitmaps/bobtoolz_dropent.bmp | Bin 0 -> 312 bytes .../tools/plugins/bitmaps/bobtoolz_merge.bmp | Bin 0 -> 312 bytes .../tools/plugins/bitmaps/bobtoolz_poly.bmp | Bin 0 -> 320 bytes .../tools/plugins/bitmaps/bobtoolz_split.bmp | Bin 0 -> 312 bytes .../bitmaps/bobtoolz_trainpathplot.bmp | Bin 0 -> 324 bytes .../plugins/bitmaps/bobtoolz_treeplanter.bmp | Bin 0 -> 356 bytes .../plugins/bitmaps/bobtoolz_turnedge.bmp | Bin 0 -> 332 bytes .../tools/plugins/bitmaps/ufoai_actorclip.bmp | Bin 0 -> 494 bytes .../tools/plugins/bitmaps/ufoai_level1.bmp | Bin 0 -> 198 bytes .../tools/plugins/bitmaps/ufoai_level2.bmp | Bin 0 -> 198 bytes .../tools/plugins/bitmaps/ufoai_level3.bmp | Bin 0 -> 198 bytes .../tools/plugins/bitmaps/ufoai_level4.bmp | Bin 0 -> 198 bytes .../tools/plugins/bitmaps/ufoai_level5.bmp | Bin 0 -> 198 bytes .../tools/plugins/bitmaps/ufoai_level6.bmp | Bin 0 -> 198 bytes .../tools/plugins/bitmaps/ufoai_level7.bmp | Bin 0 -> 198 bytes .../tools/plugins/bitmaps/ufoai_level8.bmp | Bin 0 -> 198 bytes .../tools/plugins/bitmaps/ufoai_nodraw.bmp | Bin 0 -> 498 bytes .../tools/plugins/bitmaps/ufoai_stepon.bmp | Bin 0 -> 458 bytes .../plugins/bitmaps/ufoai_weaponclip.bmp | Bin 0 -> 558 bytes setup/data/tools/plugins/bt/bt-el1.txt | 17 + setup/data/tools/plugins/bt/bt-el2.txt | 0 setup/data/tools/plugins/bt/door-tex-trim.txt | 5 + setup/data/tools/plugins/bt/door-tex.txt | 10 + setup/data/tools/plugins/bt/tp_ent.txt | 14 + setup/data/tools/q3data.qdt | 552 + setup/gpl.rtf | 126 + setup/gpl.txt | 340 + setup/license.rtf | 49 + setup/license.txt | 45 + setup/links.htm | 150 + setup/linux/Help/DocsArt/toolback.jpg | Bin 0 -> 364 bytes setup/linux/Help/Index.html | 34 + setup/linux/Help/Q3A_EULA.txt | 234 + setup/linux/README | 35 + setup/linux/all.cf | 10 + setup/linux/bspc | Bin 0 -> 417924 bytes setup/linux/bug750/loki_setup.patch | 606 + setup/linux/gtkradiant-1.5.0.spec | 206 + setup/linux/makeself/COPYING | 341 + setup/linux/makeself/README | 294 + setup/linux/makeself/TODO | 6 + setup/linux/makeself/makeself-header.sh | 359 + setup/linux/makeself/makeself.lsm | 16 + setup/linux/makeself/makeself.sh | 334 + setup/linux/makeself/update-readme | 7 + setup/linux/nightly.cf | 14 + setup/linux/q3.cf | 8 + setup/linux/rpm_build.sh | 1 + setup/linux/setup.sh | 124 + .../bin/Linux/x86/glibc-2.1/setup.gtk | Bin 0 -> 637280 bytes .../setup_image/setup.data/config.games.sh | 38 + .../linux/setup_image/setup.data/config.sh.in | 15 + .../setup_image/setup.data/postinstall.sh.in | 64 + .../linux/setup_image/setup.data/setup.glade | 2192 ++ .../linux/setup_image/setup.data/setup.xml.in | 139 + setup/linux/setup_image/setup.data/splash.xpm | 23719 ++++++++++++++++ setup/linux/setup_image/setup.sh.in | 276 + setup/linux/source_archive.sh | 22 + setup/linux/wolf.cf | 8 + setup/openurl.sh | 14 + setup/osx/build.sh | 13 + setup/osx/radiant.info.m4 | 32 + setup/quickstart.txt | 23 + setup/radiantgtkrc | 63 + setup/setup.bmp | Bin 0 -> 299934 bytes setup/setup.patch | 665 + setup/shortcuts.ini.sample | 95 + setup/win32/HOWTO | 32 + setup/win32/HOWTO_outdated | 195 + setup/win32/TODO | 74 + setup/win32/all.cf | 16 + setup/win32/bin/bspc.exe | Bin 0 -> 331776 bytes setup/win32/bin/msvcr70.dll | Bin 0 -> 344064 bytes setup/win32/build.py | 59 + setup/win32/classic.cf | 9 + setup/win32/components/archivepak.xml | 6 + setup/win32/components/archivewad.xml | 6 + setup/win32/components/archivezip.xml | 6 + setup/win32/components/atk.xml | 13 + setup/win32/components/bobtoolz.xml | 23 + setup/win32/components/brushexport.xml | 5 + setup/win32/components/bspc.xml | 4 + setup/win32/components/cairo.xml | 4 + setup/win32/components/darkplaces.xml | 14 + setup/win32/components/dbghelp.xml | 4 + setup/win32/components/doom3.xml | 9 + setup/win32/components/ef.xml | 16 + setup/win32/components/ef_data.xml | 414 + setup/win32/components/ef_docs.xml | 16 + setup/win32/components/ef_tools.xml | 7 + setup/win32/components/entityq3.xml | 6 + setup/win32/components/et.xml | 19 + setup/win32/components/et_data.xml | 387 + setup/win32/components/et_docs.xml | 96 + setup/win32/components/glib2.xml | 16 + setup/win32/components/gtk2.xml | 58 + setup/win32/components/gtkglext.xml | 5 + setup/win32/components/gtkradiant.xml | 6 + setup/win32/components/gtkradiant_data.xml | 83 + setup/win32/components/gtkradiant_docs.xml | 7 + setup/win32/components/her2.xml | 13 + setup/win32/components/her2_data.xml | 30 + setup/win32/components/hl.xml | 13 + setup/win32/components/hl_data.xml | 12 + setup/win32/components/hlcs_data.xml | 19 + setup/win32/components/icarus_manual.xml | 9 + setup/win32/components/iconv.xml | 4 + setup/win32/components/imagehl.xml | 6 + setup/win32/components/imagem8.xml | 6 + setup/win32/components/imagepng.xml | 6 + setup/win32/components/imageq2.xml | 6 + setup/win32/components/imageq3.xml | 6 + setup/win32/components/intl.xml | 4 + setup/win32/components/ja.xml | 17 + setup/win32/components/ja_data.xml | 2183 ++ setup/win32/components/ja_tools.xml | 38 + setup/win32/components/jk2.xml | 16 + setup/win32/components/jk2_data.xml | 417 + setup/win32/components/jk2_docs.xml | 12 + setup/win32/components/jk2_raven_docs.xml | 32 + setup/win32/components/jk2_tools.xml | 6 + setup/win32/components/libmhash.xml | 4 + setup/win32/components/libpng13.xml | 4 + setup/win32/components/libxml2.xml | 4 + setup/win32/components/mapq3.xml | 7 + setup/win32/components/mapxml.xml | 6 + setup/win32/components/modelmd3.xml | 6 + setup/win32/components/modelpico.xml | 6 + setup/win32/components/msvcr80.xml | 11 + setup/win32/components/nexuiz.xml | 14 + setup/win32/components/pango.xml | 23 + setup/win32/components/prey.xml | 9 + setup/win32/components/prtview.xml | 5 + setup/win32/components/q1.xml | 12 + setup/win32/components/q2.xml | 13 + setup/win32/components/q2_data.xml | 9 + setup/win32/components/q2map.xml | 4 + setup/win32/components/q3.xml | 18 + setup/win32/components/q3a_compile_manual.xml | 12 + setup/win32/components/q3a_data.xml | 156 + setup/win32/components/q3a_model_manual.xml | 10 + setup/win32/components/q3a_shader_manual.xml | 34 + setup/win32/components/q3a_terrain_manual.xml | 45 + setup/win32/components/q3map2.xml | 4 + setup/win32/components/q3map2_docs.xml | 4 + setup/win32/components/q3ta_data.xml | 57 + .../win32/components/q3ta_editing_manual.xml | 29 + setup/win32/components/q3ta_teams_manual.xml | 7 + setup/win32/components/q4.xml | 9 + setup/win32/components/qdata3.xml | 4 + setup/win32/components/radiant_manual.xml | 87 + setup/win32/components/raven.xml | 9 + setup/win32/components/shaderplug.xml | 5 + setup/win32/components/shadersq3.xml | 10 + setup/win32/components/sof2.xml | 17 + setup/win32/components/sof2_data.xml | 848 + setup/win32/components/sof2_docs.xml | 198 + setup/win32/components/sof2_raven_docs.xml | 32 + setup/win32/components/sof2_tools.xml | 7 + setup/win32/components/sunplug.xml | 5 + setup/win32/components/trem.xml | 22 + .../win32/components/trem_compile_manual.xml | 12 + setup/win32/components/trem_model_manual.xml | 10 + setup/win32/components/trem_shader_manual.xml | 34 + .../win32/components/trem_terrain_manual.xml | 45 + setup/win32/components/ufoai.xml | 13 + setup/win32/components/ufoaiplug.xml | 17 + setup/win32/components/vfsq3.xml | 6 + setup/win32/components/warsow.xml | 14 + setup/win32/components/warsow_data.xml | 7 + setup/win32/components/warsow_docs.xml | 21 + setup/win32/components/wolf.xml | 14 + setup/win32/components/wolf_data.xml | 887 + setup/win32/components/wolf_docs.xml | 12 + setup/win32/components/wolf_tools.xml | 10 + setup/win32/components/zhlt.xml | 31 + setup/win32/components/zlib.xml | 4 + setup/win32/ef_example_maps.xml | 8 + setup/win32/et.cf | 7 + setup/win32/et_example_maps.xml | 8 + setup/win32/gtkradiant-1.5.0.xml | 144 + setup/win32/halflife.cf | 7 + setup/win32/her2_example_maps.xml | 8 + setup/win32/heretic2.cf | 4 + setup/win32/hl_example_maps.xml | 8 + setup/win32/id-hl.cf | 11 + setup/win32/installer.py | 432 + setup/win32/ja.cf | 4 + setup/win32/ja_example_maps.xml | 8 + setup/win32/jk2.cf | 4 + setup/win32/msi.py | 66 + setup/win32/msi/msiquery.c | 474 + setup/win32/msi/msiquery.def | 2 + setup/win32/msi/msiquery.sln | 19 + setup/win32/msi/msiquery.vcproj | 244 + setup/win32/msiquery.dll | Bin 0 -> 6144 bytes setup/win32/msiquery.pyd | Bin 0 -> 9728 bytes setup/win32/nightly.cf | 14 + setup/win32/q1.cf | 4 + setup/win32/q2_example_maps.xml | 8 + setup/win32/q3.cf | 4 + setup/win32/q3a_example_maps.xml | 11 + setup/win32/q3q1.cf | 5 + setup/win32/q3wolf.cf | 5 + setup/win32/q3wolfet.cf | 6 + setup/win32/raven-hl.cf | 11 + setup/win32/raven.cf | 10 + setup/win32/setup.pl | 497 + setup/win32/sof2.cf | 4 + setup/win32/stvef.cf | 4 + setup/win32/template.msi | Bin 0 -> 299520 bytes .../Component Definitions/Default.cdf | 1477 + .../Component Definitions/Default.fgl | 336 + .../File Groups/1_2_1 Wolf Media Upgrade.fgl | 35 + .../template/File Groups/Compile Manual.fgl | 16 + setup/win32/template/File Groups/Default.fdf | 947 + .../File Groups/ET Executable Files.fgl | 117 + .../template/File Groups/ET Media Files.fgl | 463 + .../template/File Groups/Example Files.fgl | 4 + .../File Groups/Halflife Executable Files.fgl | 76 + .../File Groups/Halflife Media Files.fgl | 30 + .../File Groups/Heretic2 Executable Files.fgl | 18 + .../File Groups/Heretic2 Media Files.fgl | 66 + .../File Groups/JA Executable Files.fgl | 58 + .../template/File Groups/JA Media Files.fgl | 9 + .../File Groups/JKII Executable Files.fgl | 55 + .../template/File Groups/JKII Media Files.fgl | 518 + .../File Groups/Model Manual Files.fgl | 16 + .../File Groups/Plugins - BobToolz.fgl | 30 + .../File Groups/Plugins - Curry pk3 Wolf.fgl | 7 + .../File Groups/Plugins - Curry pk3.fgl | 7 + .../template/File Groups/Plugins - Curry.fgl | 7 + .../File Groups/Plugins - GTK GenSurf.fgl | 7 + .../template/File Groups/Plugins - Pk3Man.fgl | 20 + .../File Groups/Plugins - PrtView.fgl | 7 + .../File Groups/Plugins - TexTool.fgl | 7 + .../File Groups/Plugins - bkgrnd2d.fgl | 15 + .../File Groups/Program DLL Files.fgl | 119 + .../template/File Groups/Program DLLs.fgl | 4 + .../File Groups/Program Executable Files.fgl | 43 + .../File Groups/Program Misc Files.fgl | 76 + .../File Groups/Q2 Executable Files.fgl | 18 + .../template/File Groups/Q2 Media Files.fgl | 31 + .../File Groups/Q3 Default Project.fgl | 7 + .../Q3 Editor Images - SPoG pk3.fgl | 7 + .../File Groups/Q3 Executable Files.fgl | 17 + .../template/File Groups/Q3 Misc Files.fgl | 8 + .../template/File Groups/Q3 Sample Files.fgl | 184 + .../File Groups/Radiant Manual Files.fgl | 126 + .../File Groups/SOF2 Executable Files.fgl | 276 + .../template/File Groups/SOF2 Media Files.fgl | 1071 + .../File Groups/STVEF Executable Files.fgl | 41 + .../File Groups/STVEF Media Files.fgl | 471 + .../File Groups/Shader Manual Files.fgl | 56 + .../template/File Groups/TA Manual Files.fgl | 37 + .../template/File Groups/TA Sample Files.fgl | 87 + .../template/File Groups/TA Teams Manual.fgl | 11 + .../File Groups/Terrain Manual Files.fgl | 53 + .../template/File Groups/TexTool Help.fgl | 12 + .../File Groups/Wolf Exectuable Files.fgl | 31 + .../template/File Groups/Wolf Media Files.fgl | 1046 + .../template/File Groups/shaderlist-ta.fgl | 11 + .../win32/template/File Groups/shaderlist.fgl | 11 + setup/win32/template/GtkRadiant.ipr | 59 + .../template/Media/GtkRadiant/Default.mda | 66 + .../template/Registry Entries/Default.rge | 4 + setup/win32/template/Script Files/Setup.map | 556 + setup/win32/template/Script Files/Setup.rul | 1307 + .../OS Independent/_IsUser.dll | Bin 0 -> 45056 bytes .../OS Independent/infolist.txt | 35 + .../OS Independent/license.txt | 45 + .../win32/template/Shell Objects/Default.shl | 126 + .../String Tables/0009-English/value.shl | 16 + .../win32/template/String Tables/Default.shl | 47 + .../template/Text Substitutions/Build.tsb | 41 + .../template/Text Substitutions/Setup.tsb | 238 + setup/win32/warsow_mapping_files.xml | 8 + setup/win32/wolf.cf | 7 + setup/win32/wolf_example_maps.xml | 8 + svn-commit.tmp | 4 + svn.py | 16 + tools/quake2/common/bspfile.c | 789 + tools/quake2/common/bspfile.h | 128 + tools/quake2/common/cmdlib.c | 1221 + tools/quake2/common/cmdlib.h | 170 + tools/quake2/common/inout.c | 367 + tools/quake2/common/inout.h | 63 + tools/quake2/common/l3dslib.c | 300 + tools/quake2/common/l3dslib.h | 25 + tools/quake2/common/lbmlib.c | 837 + tools/quake2/common/lbmlib.h | 38 + tools/quake2/common/mathlib.c | 172 + tools/quake2/common/mathlib.h | 75 + tools/quake2/common/md4.c | 297 + tools/quake2/common/path_init.c | 404 + tools/quake2/common/polylib.c | 642 + tools/quake2/common/polylib.h | 54 + tools/quake2/common/q2_threads.h | 34 + tools/quake2/common/qfiles.h | 563 + tools/quake2/common/scriplib.c | 296 + tools/quake2/common/scriplib.h | 43 + tools/quake2/common/threads.c | 622 + tools/quake2/common/trilib.c | 186 + tools/quake2/common/trilib.h | 31 + tools/quake2/q2map/brushbsp.c | 1329 + tools/quake2/q2map/csg.c | 634 + tools/quake2/q2map/faces.c | 1076 + tools/quake2/q2map/flow.c | 787 + tools/quake2/q2map/gldraw.c | 231 + tools/quake2/q2map/glfile.c | 148 + tools/quake2/q2map/leakfile.c | 180 + tools/quake2/q2map/lightmap.c | 1315 + tools/quake2/q2map/main.c | 721 + tools/quake2/q2map/map.c | 1017 + tools/quake2/q2map/nodraw.c | 46 + tools/quake2/q2map/patches.c | 603 + tools/quake2/q2map/portals.c | 1110 + tools/quake2/q2map/prtfile.c | 286 + tools/quake2/q2map/q2map.h | 50 + tools/quake2/q2map/q2map.vcproj | 443 + tools/quake2/q2map/qbsp.c | 426 + tools/quake2/q2map/qbsp.h | 390 + tools/quake2/q2map/qrad.c | 647 + tools/quake2/q2map/qrad.h | 184 + tools/quake2/q2map/qvis.c | 581 + tools/quake2/q2map/qvis.h | 169 + tools/quake2/q2map/textures.c | 249 + tools/quake2/q2map/trace.c | 298 + tools/quake2/q2map/tree.c | 218 + tools/quake2/q2map/writebsp.c | 591 + tools/quake2/qdata/anorms.h | 162 + tools/quake2/qdata/images.c | 763 + tools/quake2/qdata/makefile | 81 + tools/quake2/qdata/models.c | 1152 + tools/quake2/qdata/qdata.c | 553 + tools/quake2/qdata/qdata.h | 93 + tools/quake2/qdata/qdata3.vcproj | 231 + tools/quake2/qdata/sprites.c | 228 + tools/quake2/qdata/tables.c | 171 + tools/quake2/qdata/video.c | 1259 + tools/quake2/qdata_heretic2/adpcm.h | 49 + tools/quake2/qdata_heretic2/animcomp.c | 351 + tools/quake2/qdata_heretic2/animcomp.h | 43 + tools/quake2/qdata_heretic2/anorms.h | 184 + tools/quake2/qdata_heretic2/book.c | 372 + tools/quake2/qdata_heretic2/common/bspfile.c | 793 + tools/quake2/qdata_heretic2/common/bspfile.h | 133 + tools/quake2/qdata_heretic2/common/cmdlib.c | 1238 + tools/quake2/qdata_heretic2/common/cmdlib.h | 177 + .../qdata_heretic2/common/her2_threads.h | 35 + tools/quake2/qdata_heretic2/common/inout.c | 367 + tools/quake2/qdata_heretic2/common/inout.h | 63 + tools/quake2/qdata_heretic2/common/l3dslib.c | 476 + tools/quake2/qdata_heretic2/common/l3dslib.h | 28 + tools/quake2/qdata_heretic2/common/lbmlib.c | 1052 + tools/quake2/qdata_heretic2/common/lbmlib.h | 41 + tools/quake2/qdata_heretic2/common/mathlib.c | 176 + tools/quake2/qdata_heretic2/common/mathlib.h | 76 + tools/quake2/qdata_heretic2/common/md4.c | 298 + .../quake2/qdata_heretic2/common/path_init.c | 405 + tools/quake2/qdata_heretic2/common/polylib.c | 656 + tools/quake2/qdata_heretic2/common/polylib.h | 55 + tools/quake2/qdata_heretic2/common/qfiles.c | 82 + tools/quake2/qdata_heretic2/common/qfiles.h | 619 + tools/quake2/qdata_heretic2/common/scriplib.c | 297 + tools/quake2/qdata_heretic2/common/scriplib.h | 44 + tools/quake2/qdata_heretic2/common/threads.c | 620 + tools/quake2/qdata_heretic2/common/token.c | 550 + tools/quake2/qdata_heretic2/common/token.h | 132 + tools/quake2/qdata_heretic2/common/trilib.c | 1077 + tools/quake2/qdata_heretic2/common/trilib.h | 56 + tools/quake2/qdata_heretic2/fmodels.c | 3404 +++ tools/quake2/qdata_heretic2/icon1.ico | Bin 0 -> 766 bytes tools/quake2/qdata_heretic2/images.c | 1397 + tools/quake2/qdata_heretic2/jointed.c | 572 + tools/quake2/qdata_heretic2/jointed.h | 35 + tools/quake2/qdata_heretic2/joints.h | 144 + tools/quake2/qdata_heretic2/models.c | 2050 ++ tools/quake2/qdata_heretic2/pics.c | 198 + tools/quake2/qdata_heretic2/qcommon/angles.h | 76 + .../qdata_heretic2/qcommon/arrayedlist.h | 71 + tools/quake2/qdata_heretic2/qcommon/flex.h | 33 + tools/quake2/qdata_heretic2/qcommon/fmodel.h | 202 + .../quake2/qdata_heretic2/qcommon/h2common.h | 26 + .../quake2/qdata_heretic2/qcommon/placement.h | 38 + .../quake2/qdata_heretic2/qcommon/q_typedef.h | 63 + tools/quake2/qdata_heretic2/qcommon/qfiles.h | 604 + .../quake2/qdata_heretic2/qcommon/reference.c | 124 + .../quake2/qdata_heretic2/qcommon/reference.h | 126 + .../qdata_heretic2/qcommon/resourcemanager.c | 159 + .../qdata_heretic2/qcommon/resourcemanager.h | 47 + .../quake2/qdata_heretic2/qcommon/skeletons.c | 232 + .../quake2/qdata_heretic2/qcommon/skeletons.h | 107 + tools/quake2/qdata_heretic2/qd_fmodel.h | 61 + tools/quake2/qdata_heretic2/qd_skeletons.c | 1291 + tools/quake2/qdata_heretic2/qd_skeletons.h | 84 + tools/quake2/qdata_heretic2/qdata.c | 736 + tools/quake2/qdata_heretic2/qdata.h | 166 + .../qdata_heretic2/qdata3_heretic2.vcproj | 329 + tools/quake2/qdata_heretic2/resource.h | 18 + tools/quake2/qdata_heretic2/script1.aps | Bin 0 -> 18732 bytes tools/quake2/qdata_heretic2/script1.rc | 115 + tools/quake2/qdata_heretic2/sprites.c | 349 + tools/quake2/qdata_heretic2/svdcmp.c | 490 + tools/quake2/qdata_heretic2/tables.c | 171 + tools/quake2/qdata_heretic2/tmix.c | 698 + tools/quake2/qdata_heretic2/video.c | 1149 + tools/quake3/common/aselib.c | 965 + tools/quake3/common/aselib.h | 31 + tools/quake3/common/bspfile.c | 706 + tools/quake3/common/bspfile.h | 121 + tools/quake3/common/cmdlib.c | 1153 + tools/quake3/common/cmdlib.h | 160 + tools/quake3/common/imagelib.c | 1230 + tools/quake3/common/imagelib.h | 44 + tools/quake3/common/inout.c | 375 + tools/quake3/common/inout.h | 62 + tools/quake3/common/l3dslib.c | 301 + tools/quake3/common/l3dslib.h | 26 + tools/quake3/common/md4.c | 298 + tools/quake3/common/mutex.c | 197 + tools/quake3/common/mutex.h | 28 + tools/quake3/common/polylib.c | 748 + tools/quake3/common/polylib.h | 57 + tools/quake3/common/polyset.h | 51 + tools/quake3/common/qfiles.h | 489 + tools/quake3/common/qthreads.h | 31 + tools/quake3/common/scriplib.c | 409 + tools/quake3/common/scriplib.h | 55 + tools/quake3/common/surfaceflags.h | 114 + tools/quake3/common/threads.c | 620 + tools/quake3/common/trilib.c | 235 + tools/quake3/common/trilib.h | 26 + tools/quake3/common/unzip.c | 4596 +++ tools/quake3/common/unzip.h | 321 + tools/quake3/common/vfs.c | 365 + tools/quake3/common/vfs.h | 41 + tools/quake3/q3data/.cvsignore | 12 + tools/quake3/q3data/.cvswrappers | 2 + tools/quake3/q3data/3dslib.c | 651 + tools/quake3/q3data/3dslib.h | 139 + tools/quake3/q3data/compress.c | 771 + tools/quake3/q3data/images.c | 486 + tools/quake3/q3data/md3lib.c | 214 + tools/quake3/q3data/md3lib.h | 28 + tools/quake3/q3data/models.c | 2155 ++ tools/quake3/q3data/oldstuff.c | 151 + tools/quake3/q3data/p3dlib.c | 345 + tools/quake3/q3data/p3dlib.h | 29 + tools/quake3/q3data/polyset.c | 273 + tools/quake3/q3data/q3data.c | 666 + tools/quake3/q3data/q3data.h | 99 + tools/quake3/q3data/q3data.vcproj | 207 + tools/quake3/q3data/stripper.c | 303 + tools/quake3/q3data/video.c | 1153 + tools/quake3/q3map2/.cvsignore | 12 + tools/quake3/q3map2/brush.c | 970 + tools/quake3/q3map2/brush_primit.c | 81 + tools/quake3/q3map2/bsp.c | 931 + tools/quake3/q3map2/bspfile_abstract.c | 853 + tools/quake3/q3map2/bspfile_ibsp.c | 593 + tools/quake3/q3map2/bspfile_rbsp.c | 340 + tools/quake3/q3map2/changelog.q3map1 | 371 + tools/quake3/q3map2/changelog.q3map2.txt | 709 + tools/quake3/q3map2/convert_ase.c | 375 + tools/quake3/q3map2/convert_map.c | 634 + tools/quake3/q3map2/decals.c | 905 + tools/quake3/q3map2/facebsp.c | 454 + tools/quake3/q3map2/fog.c | 803 + tools/quake3/q3map2/game_ef.h | 195 + tools/quake3/q3map2/game_etut.h | 265 + tools/quake3/q3map2/game_ja.h | 189 + tools/quake3/q3map2/game_jk2.h | 183 + tools/quake3/q3map2/game_nexuiz.h | 143 + tools/quake3/q3map2/game_qfusion.h | 196 + tools/quake3/q3map2/game_quake3.h | 192 + tools/quake3/q3map2/game_quakelive.h | 144 + tools/quake3/q3map2/game_sof2.h | 258 + tools/quake3/q3map2/game_t.h | 34 + tools/quake3/q3map2/game_tenebrae.h | 193 + tools/quake3/q3map2/game_tremulous.h | 161 + tools/quake3/q3map2/game_wolf.h | 239 + tools/quake3/q3map2/game_wolfet.h | 178 + tools/quake3/q3map2/image.c | 468 + tools/quake3/q3map2/leakfile.c | 127 + tools/quake3/q3map2/light.c | 2386 ++ tools/quake3/q3map2/light_bounce.c | 956 + tools/quake3/q3map2/light_shadows.c | 124 + tools/quake3/q3map2/light_trace.c | 1760 ++ tools/quake3/q3map2/light_ydnar.c | 4087 +++ tools/quake3/q3map2/lightmaps.c | 496 + tools/quake3/q3map2/lightmaps_ydnar.c | 3142 ++ tools/quake3/q3map2/listen.pl | 46 + tools/quake3/q3map2/main.c | 757 + tools/quake3/q3map2/map.c | 1690 ++ tools/quake3/q3map2/mesh.c | 826 + tools/quake3/q3map2/model.c | 758 + tools/quake3/q3map2/patch.c | 525 + tools/quake3/q3map2/path_init.c | 462 + tools/quake3/q3map2/portals.c | 971 + tools/quake3/q3map2/prtfile.c | 292 + tools/quake3/q3map2/q3map2.h | 2352 ++ tools/quake3/q3map2/q3map2.ico | Bin 0 -> 1078 bytes tools/quake3/q3map2/q3map2.rc | 1 + tools/quake3/q3map2/q3map2.vcproj | 480 + tools/quake3/q3map2/q3map2_VC9.vcproj | 479 + tools/quake3/q3map2/shaders.c | 2068 ++ tools/quake3/q3map2/surface.c | 3602 +++ tools/quake3/q3map2/surface_extra.c | 445 + tools/quake3/q3map2/surface_foliage.c | 328 + tools/quake3/q3map2/surface_fur.c | 129 + tools/quake3/q3map2/surface_meta.c | 1662 ++ tools/quake3/q3map2/tjunction.c | 728 + tools/quake3/q3map2/tree.c | 159 + tools/quake3/q3map2/vis.c | 1130 + tools/quake3/q3map2/visflow.c | 1710 ++ tools/quake3/q3map2/writebsp.c | 644 + touch.py | 1 + 1687 files changed, 438695 insertions(+) create mode 100644 CHANGES create mode 100644 COMPILING create mode 100644 CONTRIBUTORS create mode 100644 DoxyConfig create mode 100644 Doxyfile create mode 100644 Doxygen_files/Doxyfile create mode 100644 Doxygen_files/doxy_mainpage.h create mode 100644 Doxygen_files/doxygen_gtkradiant.css create mode 100644 Doxygen_files/doxygen_gtkradiant_foot.html create mode 100644 Doxygen_files/doxygen_gtkradiant_head.html create mode 100644 Doxygen_files/doxygen_index.html create mode 100644 Doxygen_files/doxygen_reference_foot.html create mode 100644 Doxygen_files/doxygen_reference_head.html create mode 100644 Doxygen_files/example/annotated.html create mode 100644 Doxygen_files/example/classIEpair-members.html create mode 100644 Doxygen_files/example/classIEpair.html create mode 100644 Doxygen_files/example/classes.html create mode 100644 Doxygen_files/example/doxygen.gif create mode 100644 Doxygen_files/example/doxygen_gtkradiant.css create mode 100644 Doxygen_files/example/files.html create mode 100644 Doxygen_files/example/functions.html create mode 100644 Doxygen_files/example/graph_legend.dot create mode 100644 Doxygen_files/example/graph_legend.gif create mode 100644 Doxygen_files/example/graph_legend.html create mode 100644 Doxygen_files/example/index.html create mode 100644 Doxygen_files/example/pages.html create mode 100644 Doxygen_files/example/test_8c-source.html create mode 100644 Doxygen_files/example/test_8c.html create mode 100644 Doxygen_files/example/todo.html create mode 100644 Doxygen_files/genDoxyfile create mode 100644 Doxygen_files/gendoxfunctions create mode 100644 Doxygen_files/images/body-left-tile.gif create mode 100644 Doxygen_files/images/body-lower-left.gif create mode 100644 Doxygen_files/images/body-lower-right.gif create mode 100644 Doxygen_files/images/body-lower-tile.gif create mode 100644 Doxygen_files/images/body-right-tile.gif create mode 100644 Doxygen_files/images/body-upper-left.gif create mode 100644 Doxygen_files/images/body-upper-right.gif create mode 100644 Doxygen_files/images/body-upper-tile.gif create mode 100644 Doxygen_files/images/gtkr_splash.jpg create mode 100644 Doxygen_files/images/gtkr_splash_sm.jpg create mode 100644 Doxygen_files/images/history_id_logo.gif create mode 100644 Doxygen_files/images/top-right.gif create mode 100644 Doxygen_files/images/top-tile.gif create mode 100644 Doxygen_files/images/top-title.gif create mode 100644 Doxygen_files/reference1.html create mode 100644 GPL create mode 100644 GtkRadiant.prj create mode 100644 GtkRadiant.sln create mode 100644 LGPL create mode 100644 LICENSE create mode 100644 README.doxygen create mode 100644 SConscript create mode 100644 SConstruct create mode 100644 TODO create mode 100644 contrib/bkgrnd2d/bitmaps/bkgrnd2d_conf.bmp create mode 100644 contrib/bkgrnd2d/bitmaps/bkgrnd2d_xy_toggle.bmp create mode 100644 contrib/bkgrnd2d/bitmaps/bkgrnd2d_xz_toggle.bmp create mode 100644 contrib/bkgrnd2d/bitmaps/bkgrnd2d_yz_toggle.bmp create mode 100644 contrib/bkgrnd2d/bkgrnd2d.cpp create mode 100644 contrib/bkgrnd2d/bkgrnd2d.def create mode 100644 contrib/bkgrnd2d/bkgrnd2d.h create mode 100644 contrib/bkgrnd2d/bkgrnd2d.vcproj create mode 100644 contrib/bkgrnd2d/dialog.cpp create mode 100644 contrib/bkgrnd2d/dialog.h create mode 100644 contrib/bkgrnd2d/plugin.cpp create mode 100644 contrib/bkgrnd2d/plugin.h create mode 100644 contrib/bkgrnd2d/readme_bkgrnd2d-b0.25.txt create mode 100644 contrib/bobtoolz/CPortals.h create mode 100644 contrib/bobtoolz/DBobView.cpp create mode 100644 contrib/bobtoolz/DBobView.h create mode 100644 contrib/bobtoolz/DBrush.cpp create mode 100644 contrib/bobtoolz/DBrush.h create mode 100644 contrib/bobtoolz/DEPair.cpp create mode 100644 contrib/bobtoolz/DEPair.h create mode 100644 contrib/bobtoolz/DEntity.cpp create mode 100644 contrib/bobtoolz/DEntity.h create mode 100644 contrib/bobtoolz/DMap.cpp create mode 100644 contrib/bobtoolz/DMap.h create mode 100644 contrib/bobtoolz/DPatch.cpp create mode 100644 contrib/bobtoolz/DPatch.h create mode 100644 contrib/bobtoolz/DPlane.cpp create mode 100644 contrib/bobtoolz/DPlane.h create mode 100644 contrib/bobtoolz/DPoint.cpp create mode 100644 contrib/bobtoolz/DPoint.h create mode 100644 contrib/bobtoolz/DShape.cpp create mode 100644 contrib/bobtoolz/DShape.h create mode 100644 contrib/bobtoolz/DTrainDrawer.cpp create mode 100644 contrib/bobtoolz/DTrainDrawer.h create mode 100644 contrib/bobtoolz/DTreePlanter.cpp create mode 100644 contrib/bobtoolz/DTreePlanter.h create mode 100644 contrib/bobtoolz/DVisDrawer.cpp create mode 100644 contrib/bobtoolz/DVisDrawer.h create mode 100644 contrib/bobtoolz/DWinding.cpp create mode 100644 contrib/bobtoolz/DWinding.h create mode 100644 contrib/bobtoolz/ScriptParser.cpp create mode 100644 contrib/bobtoolz/ScriptParser.h create mode 100644 contrib/bobtoolz/StdAfx.cpp create mode 100644 contrib/bobtoolz/StdAfx.h create mode 100644 contrib/bobtoolz/bitmaps/bobtoolz_caulk.bmp create mode 100644 contrib/bobtoolz/bitmaps/bobtoolz_cleanup.bmp create mode 100644 contrib/bobtoolz/bitmaps/bobtoolz_dropent.bmp create mode 100644 contrib/bobtoolz/bitmaps/bobtoolz_merge.bmp create mode 100644 contrib/bobtoolz/bitmaps/bobtoolz_poly.bmp create mode 100644 contrib/bobtoolz/bitmaps/bobtoolz_split.bmp create mode 100644 contrib/bobtoolz/bitmaps/bobtoolz_trainpathplot.bmp create mode 100644 contrib/bobtoolz/bitmaps/bobtoolz_treeplanter.bmp create mode 100644 contrib/bobtoolz/bitmaps/bobtoolz_turnedge.bmp create mode 100644 contrib/bobtoolz/bobToolz-GTK.cpp create mode 100644 contrib/bobtoolz/bobToolz.def create mode 100644 contrib/bobtoolz/bobToolz.h create mode 100644 contrib/bobtoolz/bobToolz.rc create mode 100644 contrib/bobtoolz/bobToolz_gtk.vcproj create mode 100644 contrib/bobtoolz/bobtoolz-gtk.rc create mode 100644 contrib/bobtoolz/bobtoolz.vcproj create mode 100644 contrib/bobtoolz/bsploader.cpp create mode 100644 contrib/bobtoolz/bsploader.h create mode 100644 contrib/bobtoolz/bt/bt-el1.txt create mode 100644 contrib/bobtoolz/bt/bt-el2.txt create mode 100644 contrib/bobtoolz/bt/ctf-blue.txt create mode 100644 contrib/bobtoolz/bt/ctf-red.txt create mode 100644 contrib/bobtoolz/bt/door-tex-trim.txt create mode 100644 contrib/bobtoolz/bt/door-tex.txt create mode 100644 contrib/bobtoolz/bt/tp_ent.txt create mode 100644 contrib/bobtoolz/cportals.cpp create mode 100644 contrib/bobtoolz/ctfToolz-GTK.cpp create mode 100644 contrib/bobtoolz/ctfresource_gtk.h create mode 100644 contrib/bobtoolz/ctfresource_gtk.rc create mode 100644 contrib/bobtoolz/ctftoolz.def create mode 100644 contrib/bobtoolz/dialogs/AboutDialog.cpp create mode 100644 contrib/bobtoolz/dialogs/AboutDialog.h create mode 100644 contrib/bobtoolz/dialogs/AutoCaulkDialog.cpp create mode 100644 contrib/bobtoolz/dialogs/AutoCaulkDialog.h create mode 100644 contrib/bobtoolz/dialogs/AutoCaulkStartDialog.cpp create mode 100644 contrib/bobtoolz/dialogs/AutoCaulkStartDialog.h create mode 100644 contrib/bobtoolz/dialogs/BrushCheckDialog.h create mode 100644 contrib/bobtoolz/dialogs/DoorDialog.cpp create mode 100644 contrib/bobtoolz/dialogs/DoorDialog.h create mode 100644 contrib/bobtoolz/dialogs/IntersectDialog.cpp create mode 100644 contrib/bobtoolz/dialogs/IntersectDialog.h create mode 100644 contrib/bobtoolz/dialogs/IntersectInfoDialog.cpp create mode 100644 contrib/bobtoolz/dialogs/IntersectInfoDialog.h create mode 100644 contrib/bobtoolz/dialogs/PolygonDialog.cpp create mode 100644 contrib/bobtoolz/dialogs/PolygonDialog.h create mode 100644 contrib/bobtoolz/dialogs/StairDialog.cpp create mode 100644 contrib/bobtoolz/dialogs/StairDialog.h create mode 100644 contrib/bobtoolz/dialogs/TextureResetDialog.cpp create mode 100644 contrib/bobtoolz/dialogs/TextureResetDialog.h create mode 100644 contrib/bobtoolz/dialogs/brushcheckdialog.cpp create mode 100644 contrib/bobtoolz/dialogs/dialogs-gtk.cpp create mode 100644 contrib/bobtoolz/dialogs/dialogs-gtk.h create mode 100644 contrib/bobtoolz/dialogs/pathplotterdialog.cpp create mode 100644 contrib/bobtoolz/dialogs/pathplotterdialog.h create mode 100644 contrib/bobtoolz/funchandlers-GTK.cpp create mode 100644 contrib/bobtoolz/funchandlers-ctf-GTK.cpp create mode 100644 contrib/bobtoolz/funchandlers.cpp create mode 100644 contrib/bobtoolz/funchandlers.h create mode 100644 contrib/bobtoolz/lists.cpp create mode 100644 contrib/bobtoolz/lists.h create mode 100644 contrib/bobtoolz/misc.cpp create mode 100644 contrib/bobtoolz/misc.h create mode 100644 contrib/bobtoolz/res/plugin.rc2 create mode 100644 contrib/bobtoolz/resource-gtk.h create mode 100644 contrib/bobtoolz/resource.h create mode 100644 contrib/bobtoolz/shapes.cpp create mode 100644 contrib/bobtoolz/shapes.h create mode 100644 contrib/bobtoolz/txt/changelog.txt create mode 100644 contrib/bobtoolz/txt/readme.txt create mode 100644 contrib/bobtoolz/visfind.cpp create mode 100644 contrib/bobtoolz/visfind.h create mode 100644 contrib/brushexport/brushexport.def create mode 100644 contrib/brushexport/brushexport.vcproj create mode 100644 contrib/brushexport/callbacks.cpp create mode 100644 contrib/brushexport/callbacks.h create mode 100644 contrib/brushexport/export.cpp create mode 100644 contrib/brushexport/export.h create mode 100644 contrib/brushexport/interface.cpp create mode 100644 contrib/brushexport/plugin.cpp create mode 100644 contrib/brushexport/plugin.h create mode 100644 contrib/brushexport/support.cpp create mode 100644 contrib/brushexport/support.h create mode 100644 contrib/camera/bitmaps/camera_insp.bmp create mode 100644 contrib/camera/camera.cpp create mode 100644 contrib/camera/camera.def create mode 100644 contrib/camera/camera.h create mode 100644 contrib/camera/camera.vcproj create mode 100644 contrib/camera/dialogs.cpp create mode 100644 contrib/camera/dialogs.h create mode 100644 contrib/camera/dialogs_common.cpp create mode 100644 contrib/camera/funchandlers.cpp create mode 100644 contrib/camera/funchandlers.h create mode 100644 contrib/camera/listener.cpp create mode 100644 contrib/camera/listener.h create mode 100644 contrib/camera/misc.cpp create mode 100644 contrib/camera/misc.h create mode 100644 contrib/camera/renderer.cpp create mode 100644 contrib/camera/renderer.h create mode 100644 contrib/gtkgensurf/.cvsignore create mode 100644 contrib/gtkgensurf/CHANGES create mode 100644 contrib/gtkgensurf/bitmap.cpp create mode 100644 contrib/gtkgensurf/dec.cpp create mode 100644 contrib/gtkgensurf/face.cpp create mode 100644 contrib/gtkgensurf/font.cpp create mode 100644 contrib/gtkgensurf/gendlgs.cpp create mode 100644 contrib/gtkgensurf/gendlgs.h create mode 100644 contrib/gtkgensurf/genmap.cpp create mode 100644 contrib/gtkgensurf/gensurf.cpp create mode 100644 contrib/gtkgensurf/gensurf.def create mode 100644 contrib/gtkgensurf/gensurf.h create mode 100644 contrib/gtkgensurf/gtkgensurf.vcproj create mode 100644 contrib/gtkgensurf/heretic.cpp create mode 100644 contrib/gtkgensurf/plugin.cpp create mode 100644 contrib/gtkgensurf/triangle.c create mode 100644 contrib/gtkgensurf/triangle.h create mode 100644 contrib/gtkgensurf/view.cpp create mode 100644 contrib/hydratoolz/hydratoolz.def create mode 100644 contrib/hydratoolz/hydratoolz.vcproj create mode 100644 contrib/hydratoolz/plugin.cpp create mode 100644 contrib/hydratoolz/plugin.h create mode 100644 contrib/prtview/.cvsignore create mode 100644 contrib/prtview/AboutDialog.cpp create mode 100644 contrib/prtview/AboutDialog.h create mode 100644 contrib/prtview/ConfigDialog.cpp create mode 100644 contrib/prtview/ConfigDialog.h create mode 100644 contrib/prtview/LoadPortalFileDialog.cpp create mode 100644 contrib/prtview/LoadPortalFileDialog.h create mode 100644 contrib/prtview/PrtView.aps create mode 100644 contrib/prtview/PrtView.def create mode 100644 contrib/prtview/PrtView.txt create mode 100644 contrib/prtview/PrtView.vcproj create mode 100644 contrib/prtview/portals.cpp create mode 100644 contrib/prtview/portals.h create mode 100644 contrib/prtview/prtview.cpp create mode 100644 contrib/prtview/prtview.h create mode 100644 contrib/prtview/res/PrtView.rc2 create mode 100644 contrib/shaderplug/shaderplug.cpp create mode 100644 contrib/shaderplug/shaderplug.def create mode 100644 contrib/shaderplug/shaderplug.h create mode 100644 contrib/shaderplug/shaderplug.vcproj create mode 100644 contrib/sunplug/sunplug.cpp create mode 100644 contrib/sunplug/sunplug.def create mode 100644 contrib/sunplug/sunplug.h create mode 100644 contrib/sunplug/sunplug.vcproj create mode 100644 contrib/ufoaiplug/bitmaps/ufoai_actorclip.bmp create mode 100644 contrib/ufoaiplug/bitmaps/ufoai_level1.bmp create mode 100644 contrib/ufoaiplug/bitmaps/ufoai_level2.bmp create mode 100644 contrib/ufoaiplug/bitmaps/ufoai_level3.bmp create mode 100644 contrib/ufoaiplug/bitmaps/ufoai_level4.bmp create mode 100644 contrib/ufoaiplug/bitmaps/ufoai_level5.bmp create mode 100644 contrib/ufoaiplug/bitmaps/ufoai_level6.bmp create mode 100644 contrib/ufoaiplug/bitmaps/ufoai_level7.bmp create mode 100644 contrib/ufoaiplug/bitmaps/ufoai_level8.bmp create mode 100644 contrib/ufoaiplug/bitmaps/ufoai_nodraw.bmp create mode 100644 contrib/ufoaiplug/bitmaps/ufoai_stepon.bmp create mode 100644 contrib/ufoaiplug/bitmaps/ufoai_weaponclip.bmp create mode 100644 contrib/ufoaiplug/ufoai.cpp create mode 100644 contrib/ufoaiplug/ufoai.def create mode 100644 contrib/ufoaiplug/ufoai.h create mode 100644 contrib/ufoaiplug/ufoai.vcproj create mode 100644 contrib/ufoaiplug/ufoai_filters.cpp create mode 100644 contrib/ufoaiplug/ufoai_filters.h create mode 100644 contrib/ufoaiplug/ufoai_gtk.cpp create mode 100644 contrib/ufoaiplug/ufoai_gtk.h create mode 100644 contrib/ufoaiplug/ufoai_level.cpp create mode 100644 contrib/ufoaiplug/ufoai_level.h create mode 100644 games/NexuizPack/games/nexuiz.game create mode 100755 games/NexuizPack/gpl.txt create mode 100644 games/NexuizPack/nexuiz.game/data/default_shaderlist.txt create mode 100644 games/NexuizPack/nexuiz.game/data/entities.def create mode 100644 games/NexuizPack/nexuiz.game/default_build_menu.xml create mode 100644 games/NexuizPack/nexuiz.game/game.xlink create mode 100644 gen.readme create mode 100644 gen.vcproj create mode 100644 gendox create mode 100644 generic_cpp.py create mode 100644 generic_h.py create mode 100644 generic_module.py create mode 100644 include/.cvsignore create mode 100644 include/aboutmsg.default create mode 100644 include/cullable.cpp create mode 100644 include/cullable.h create mode 100644 include/editable.cpp create mode 100644 include/editable.h create mode 100644 include/iarchive.cpp create mode 100644 include/iarchive.h create mode 100644 include/ibrush.cpp create mode 100644 include/ibrush.h create mode 100644 include/icamera.cpp create mode 100644 include/icamera.h create mode 100644 include/idatastream.cpp create mode 100644 include/idatastream.h create mode 100644 include/ieclass.cpp create mode 100644 include/ieclass.h create mode 100644 include/ientity.cpp create mode 100644 include/ientity.h create mode 100644 include/ifilesystem.cpp create mode 100644 include/ifilesystem.h create mode 100644 include/ifiletypes.cpp create mode 100644 include/ifiletypes.h create mode 100644 include/ifilter.cpp create mode 100644 include/ifilter.h create mode 100644 include/igl.cpp create mode 100644 include/igl.h create mode 100644 include/iglrender.cpp create mode 100644 include/iglrender.h create mode 100644 include/igtkgl.cpp create mode 100644 include/igtkgl.h create mode 100644 include/iimage.cpp create mode 100644 include/iimage.h create mode 100644 include/imap.cpp create mode 100644 include/imap.h create mode 100644 include/imodel.cpp create mode 100644 include/imodel.h create mode 100644 include/include.vcproj create mode 100644 include/ipatch.cpp create mode 100644 include/ipatch.h create mode 100644 include/iplugin.cpp create mode 100644 include/iplugin.h create mode 100644 include/ireference.cpp create mode 100644 include/ireference.h create mode 100644 include/irender.cpp create mode 100644 include/irender.h create mode 100644 include/iscenegraph.cpp create mode 100644 include/iscenegraph.h create mode 100644 include/iscriplib.cpp create mode 100644 include/iscriplib.h create mode 100644 include/iselection.cpp create mode 100644 include/iselection.h create mode 100644 include/ishaders.cpp create mode 100644 include/ishaders.h create mode 100644 include/itexdef.cpp create mode 100644 include/itexdef.h create mode 100644 include/itextstream.cpp create mode 100644 include/itextstream.h create mode 100644 include/itextures.cpp create mode 100644 include/itextures.h create mode 100644 include/itoolbar.cpp create mode 100644 include/itoolbar.h create mode 100644 include/iundo.cpp create mode 100644 include/iundo.h create mode 100644 include/mapfile.cpp create mode 100644 include/mapfile.h create mode 100644 include/modelskin.cpp create mode 100644 include/modelskin.h create mode 100644 include/moduleobserver.cpp create mode 100644 include/moduleobserver.h create mode 100644 include/modulesystem.cpp create mode 100644 include/modulesystem.h create mode 100644 include/nameable.cpp create mode 100644 include/nameable.h create mode 100644 include/namespace.cpp create mode 100644 include/namespace.h create mode 100644 include/preferencesystem.cpp create mode 100644 include/preferencesystem.h create mode 100644 include/qerplugin.cpp create mode 100644 include/qerplugin.h create mode 100644 include/renderable.cpp create mode 100644 include/renderable.h create mode 100644 include/selectable.cpp create mode 100644 include/selectable.h create mode 100644 include/stream_version.h create mode 100644 include/version.default create mode 100644 include/warnings.h create mode 100644 include/windowobserver.cpp create mode 100644 include/windowobserver.h create mode 100644 install.py create mode 100644 libs/.cvsignore create mode 100644 libs/archivelib.cpp create mode 100644 libs/archivelib.h create mode 100644 libs/bytebool.cpp create mode 100644 libs/bytebool.h create mode 100644 libs/bytestreamutils.cpp create mode 100644 libs/bytestreamutils.h create mode 100644 libs/character.cpp create mode 100644 libs/character.h create mode 100644 libs/cmdlib.h create mode 100644 libs/cmdlib/.cvsignore create mode 100644 libs/cmdlib/.cvswrappers create mode 100644 libs/cmdlib/cmdlib.cpp create mode 100644 libs/cmdlib/cmdlib.vcproj create mode 100644 libs/container/array.cpp create mode 100644 libs/container/array.h create mode 100644 libs/container/cache.cpp create mode 100644 libs/container/cache.h create mode 100644 libs/container/container.cpp create mode 100644 libs/container/container.h create mode 100644 libs/container/hashfunc.cpp create mode 100644 libs/container/hashfunc.h create mode 100644 libs/container/hashtable.cpp create mode 100644 libs/container/hashtable.h create mode 100644 libs/container/stack.cpp create mode 100644 libs/container/stack.h create mode 100644 libs/convert.cpp create mode 100644 libs/convert.h create mode 100644 libs/ddslib.h create mode 100644 libs/ddslib/ddslib.c create mode 100644 libs/ddslib/ddslib.vcproj create mode 100644 libs/debugging/debugging.cpp create mode 100644 libs/debugging/debugging.h create mode 100644 libs/dragplanes.cpp create mode 100644 libs/dragplanes.h create mode 100644 libs/eclasslib.cpp create mode 100644 libs/eclasslib.h create mode 100644 libs/entitylib.cpp create mode 100644 libs/entitylib.h create mode 100644 libs/entityxml.cpp create mode 100644 libs/entityxml.h create mode 100644 libs/fs_filesystem.cpp create mode 100644 libs/fs_filesystem.h create mode 100644 libs/fs_path.cpp create mode 100644 libs/fs_path.h create mode 100644 libs/generic/arrayrange.cpp create mode 100644 libs/generic/arrayrange.h create mode 100644 libs/generic/bitfield.cpp create mode 100644 libs/generic/bitfield.h create mode 100644 libs/generic/callback.cpp create mode 100644 libs/generic/callback.h create mode 100644 libs/generic/callbackfwd.cpp create mode 100644 libs/generic/callbackfwd.h create mode 100644 libs/generic/constant.cpp create mode 100644 libs/generic/constant.h create mode 100644 libs/generic/enumeration.cpp create mode 100644 libs/generic/enumeration.h create mode 100644 libs/generic/functional.cpp create mode 100644 libs/generic/functional.h create mode 100644 libs/generic/object.cpp create mode 100644 libs/generic/object.h create mode 100644 libs/generic/reference.cpp create mode 100644 libs/generic/reference.h create mode 100644 libs/generic/referencecounted.cpp create mode 100644 libs/generic/referencecounted.h create mode 100644 libs/generic/static.cpp create mode 100644 libs/generic/static.h create mode 100644 libs/generic/vector.cpp create mode 100644 libs/generic/vector.h create mode 100644 libs/gtkutil/accelerator.cpp create mode 100644 libs/gtkutil/accelerator.h create mode 100644 libs/gtkutil/button.cpp create mode 100644 libs/gtkutil/button.h create mode 100644 libs/gtkutil/clipboard.cpp create mode 100644 libs/gtkutil/clipboard.h create mode 100644 libs/gtkutil/closure.cpp create mode 100644 libs/gtkutil/closure.h create mode 100644 libs/gtkutil/container.cpp create mode 100644 libs/gtkutil/container.h create mode 100644 libs/gtkutil/cursor.cpp create mode 100644 libs/gtkutil/cursor.h create mode 100644 libs/gtkutil/dialog.cpp create mode 100644 libs/gtkutil/dialog.h create mode 100644 libs/gtkutil/entry.cpp create mode 100644 libs/gtkutil/entry.h create mode 100644 libs/gtkutil/filechooser.cpp create mode 100644 libs/gtkutil/filechooser.h create mode 100644 libs/gtkutil/frame.cpp create mode 100644 libs/gtkutil/frame.h create mode 100644 libs/gtkutil/glfont.cpp create mode 100644 libs/gtkutil/glfont.h create mode 100644 libs/gtkutil/glwidget.cpp create mode 100644 libs/gtkutil/glwidget.h create mode 100644 libs/gtkutil/gtkutil.vcproj create mode 100644 libs/gtkutil/idledraw.cpp create mode 100644 libs/gtkutil/idledraw.h create mode 100644 libs/gtkutil/image.cpp create mode 100644 libs/gtkutil/image.h create mode 100644 libs/gtkutil/menu.cpp create mode 100644 libs/gtkutil/menu.h create mode 100644 libs/gtkutil/messagebox.cpp create mode 100644 libs/gtkutil/messagebox.h create mode 100644 libs/gtkutil/nonmodal.cpp create mode 100644 libs/gtkutil/nonmodal.h create mode 100644 libs/gtkutil/paned.cpp create mode 100644 libs/gtkutil/paned.h create mode 100644 libs/gtkutil/pointer.cpp create mode 100644 libs/gtkutil/pointer.h create mode 100644 libs/gtkutil/toolbar.cpp create mode 100644 libs/gtkutil/toolbar.h create mode 100644 libs/gtkutil/widget.cpp create mode 100644 libs/gtkutil/widget.h create mode 100644 libs/gtkutil/window.cpp create mode 100644 libs/gtkutil/window.h create mode 100644 libs/gtkutil/xorrectangle.cpp create mode 100644 libs/gtkutil/xorrectangle.h create mode 100644 libs/imagelib.cpp create mode 100644 libs/imagelib.h create mode 100644 libs/instancelib.cpp create mode 100644 libs/instancelib.h create mode 100644 libs/jpeg6/.cvsignore create mode 100644 libs/jpeg6/.cvswrappers create mode 100644 libs/jpeg6/README create mode 100644 libs/jpeg6/jchuff.h create mode 100644 libs/jpeg6/jcomapi.cpp create mode 100644 libs/jpeg6/jconfig.h create mode 100644 libs/jpeg6/jdapimin.cpp create mode 100644 libs/jpeg6/jdapistd.cpp create mode 100644 libs/jpeg6/jdatasrc.cpp create mode 100644 libs/jpeg6/jdcoefct.cpp create mode 100644 libs/jpeg6/jdcolor.cpp create mode 100644 libs/jpeg6/jdct.h create mode 100644 libs/jpeg6/jddctmgr.cpp create mode 100644 libs/jpeg6/jdhuff.cpp create mode 100644 libs/jpeg6/jdhuff.h create mode 100644 libs/jpeg6/jdinput.cpp create mode 100644 libs/jpeg6/jdmainct.cpp create mode 100644 libs/jpeg6/jdmarker.cpp create mode 100644 libs/jpeg6/jdmaster.cpp create mode 100644 libs/jpeg6/jdpostct.cpp create mode 100644 libs/jpeg6/jdsample.cpp create mode 100644 libs/jpeg6/jdtrans.cpp create mode 100644 libs/jpeg6/jerror.cpp create mode 100644 libs/jpeg6/jerror.h create mode 100644 libs/jpeg6/jfdctflt.cpp create mode 100644 libs/jpeg6/jidctflt.cpp create mode 100644 libs/jpeg6/jinclude.h create mode 100644 libs/jpeg6/jmemmgr.cpp create mode 100644 libs/jpeg6/jmemnobs.cpp create mode 100644 libs/jpeg6/jmemsys.h create mode 100644 libs/jpeg6/jmorecfg.h create mode 100644 libs/jpeg6/jpeg6.vcproj create mode 100644 libs/jpeg6/jpegint.h create mode 100644 libs/jpeg6/jpgload.cpp create mode 100644 libs/jpeg6/jutils.cpp create mode 100644 libs/jpeg6/jversion.h create mode 100644 libs/jpeglib.h create mode 100644 libs/l_net/.cvsignore create mode 100644 libs/l_net/l_net.c create mode 100644 libs/l_net/l_net.h create mode 100644 libs/l_net/l_net.vcproj create mode 100644 libs/l_net/l_net_berkley.c create mode 100644 libs/l_net/l_net_wins.c create mode 100644 libs/l_net/l_net_wins.h create mode 100644 libs/libs.vcproj create mode 100644 libs/maplib.cpp create mode 100644 libs/maplib.h create mode 100644 libs/math/aabb.cpp create mode 100644 libs/math/aabb.h create mode 100644 libs/math/curve.cpp create mode 100644 libs/math/curve.h create mode 100644 libs/math/expression.cpp create mode 100644 libs/math/expression.h create mode 100644 libs/math/frustum.cpp create mode 100644 libs/math/frustum.h create mode 100644 libs/math/line.cpp create mode 100644 libs/math/line.h create mode 100644 libs/math/matrix.cpp create mode 100644 libs/math/matrix.h create mode 100644 libs/math/pi.cpp create mode 100644 libs/math/pi.h create mode 100644 libs/math/plane.cpp create mode 100644 libs/math/plane.h create mode 100644 libs/math/quaternion.cpp create mode 100644 libs/math/quaternion.h create mode 100644 libs/math/vector.cpp create mode 100644 libs/math/vector.h create mode 100644 libs/mathlib.h create mode 100644 libs/mathlib/bbox.c create mode 100644 libs/mathlib/line.c create mode 100644 libs/mathlib/m4x4.c create mode 100644 libs/mathlib/mathlib.c create mode 100644 libs/mathlib/mathlib.vcproj create mode 100644 libs/mathlib/ray.c create mode 100644 libs/md5lib.h create mode 100644 libs/md5lib/md5lib.c create mode 100644 libs/md5lib/md5lib.vcproj create mode 100644 libs/memory/allocator.cpp create mode 100644 libs/memory/allocator.h create mode 100644 libs/moduleobservers.cpp create mode 100644 libs/moduleobservers.h create mode 100644 libs/modulesystem/moduleregistry.cpp create mode 100644 libs/modulesystem/moduleregistry.h create mode 100644 libs/modulesystem/modulesmap.cpp create mode 100644 libs/modulesystem/modulesmap.h create mode 100644 libs/modulesystem/singletonmodule.cpp create mode 100644 libs/modulesystem/singletonmodule.h create mode 100644 libs/os/dir.cpp create mode 100644 libs/os/dir.h create mode 100644 libs/os/file.cpp create mode 100644 libs/os/file.h create mode 100644 libs/os/path.cpp create mode 100644 libs/os/path.h create mode 100644 libs/picomodel.h create mode 100644 libs/picomodel/lwo/clip.c create mode 100644 libs/picomodel/lwo/envelope.c create mode 100644 libs/picomodel/lwo/list.c create mode 100644 libs/picomodel/lwo/lwio.c create mode 100644 libs/picomodel/lwo/lwo2.c create mode 100644 libs/picomodel/lwo/lwo2.h create mode 100644 libs/picomodel/lwo/lwob.c create mode 100644 libs/picomodel/lwo/pntspols.c create mode 100644 libs/picomodel/lwo/surface.c create mode 100644 libs/picomodel/lwo/vecmath.c create mode 100644 libs/picomodel/lwo/vmap.c create mode 100644 libs/picomodel/picointernal.c create mode 100644 libs/picomodel/picointernal.h create mode 100644 libs/picomodel/picomodel.c create mode 100644 libs/picomodel/picomodel.vcproj create mode 100644 libs/picomodel/picomodules.c create mode 100644 libs/picomodel/pm_3ds.c create mode 100644 libs/picomodel/pm_ase.c create mode 100644 libs/picomodel/pm_fm.c create mode 100644 libs/picomodel/pm_fm.h create mode 100644 libs/picomodel/pm_lwo.c create mode 100644 libs/picomodel/pm_md2.c create mode 100644 libs/picomodel/pm_md3.c create mode 100644 libs/picomodel/pm_mdc.c create mode 100644 libs/picomodel/pm_ms3d.c create mode 100644 libs/picomodel/pm_obj.c create mode 100644 libs/picomodel/pm_terrain.c create mode 100644 libs/pivot.cpp create mode 100644 libs/pivot.h create mode 100644 libs/profile/file.cpp create mode 100644 libs/profile/file.h create mode 100644 libs/profile/profile.cpp create mode 100644 libs/profile/profile.h create mode 100644 libs/profile/profile.vcproj create mode 100644 libs/radiant_jpeglib.h create mode 100644 libs/render.cpp create mode 100644 libs/render.h create mode 100644 libs/scenelib.cpp create mode 100644 libs/scenelib.h create mode 100644 libs/script/scripttokeniser.cpp create mode 100644 libs/script/scripttokeniser.h create mode 100644 libs/script/scripttokenwriter.cpp create mode 100644 libs/script/scripttokenwriter.h create mode 100644 libs/selectionlib.cpp create mode 100644 libs/selectionlib.h create mode 100644 libs/shaderlib.cpp create mode 100644 libs/shaderlib.h create mode 100644 libs/signal/isignal.cpp create mode 100644 libs/signal/isignal.h create mode 100644 libs/signal/signal.cpp create mode 100644 libs/signal/signal.h create mode 100644 libs/signal/signalfwd.cpp create mode 100644 libs/signal/signalfwd.h create mode 100644 libs/splines/.cvsignore create mode 100644 libs/splines/Splines.vcproj create mode 100644 libs/splines/math_angles.cpp create mode 100644 libs/splines/math_angles.h create mode 100644 libs/splines/math_matrix.cpp create mode 100644 libs/splines/math_matrix.h create mode 100644 libs/splines/math_quaternion.cpp create mode 100644 libs/splines/math_quaternion.h create mode 100644 libs/splines/math_vector.cpp create mode 100644 libs/splines/math_vector.h create mode 100644 libs/splines/q_parse.cpp create mode 100644 libs/splines/q_shared.cpp create mode 100644 libs/splines/q_shared.h create mode 100644 libs/splines/splines.cpp create mode 100644 libs/splines/splines.h create mode 100644 libs/splines/util_list.h create mode 100644 libs/splines/util_str.cpp create mode 100644 libs/splines/util_str.h create mode 100644 libs/str.cpp create mode 100644 libs/str.h create mode 100644 libs/stream/filestream.cpp create mode 100644 libs/stream/filestream.h create mode 100644 libs/stream/memstream.cpp create mode 100644 libs/stream/memstream.h create mode 100644 libs/stream/stringstream.cpp create mode 100644 libs/stream/stringstream.h create mode 100644 libs/stream/textfilestream.cpp create mode 100644 libs/stream/textfilestream.h create mode 100644 libs/stream/textstream.cpp create mode 100644 libs/stream/textstream.h create mode 100644 libs/string/pooledstring.cpp create mode 100644 libs/string/pooledstring.h create mode 100644 libs/string/string.cpp create mode 100644 libs/string/string.h create mode 100644 libs/string/stringfwd.cpp create mode 100644 libs/string/stringfwd.h create mode 100644 libs/stringio.cpp create mode 100644 libs/stringio.h create mode 100644 libs/texturelib.cpp create mode 100644 libs/texturelib.h create mode 100644 libs/transformlib.cpp create mode 100644 libs/transformlib.h create mode 100644 libs/traverselib.cpp create mode 100644 libs/traverselib.h create mode 100644 libs/typesystem.cpp create mode 100644 libs/typesystem.h create mode 100644 libs/undolib.cpp create mode 100644 libs/undolib.h create mode 100644 libs/uniquenames.cpp create mode 100644 libs/uniquenames.h create mode 100644 libs/versionlib.cpp create mode 100644 libs/versionlib.h create mode 100644 libs/xml/ixml.cpp create mode 100644 libs/xml/ixml.h create mode 100644 libs/xml/xmlelement.cpp create mode 100644 libs/xml/xmlelement.h create mode 100644 libs/xml/xmlparser.cpp create mode 100644 libs/xml/xmlparser.h create mode 100644 libs/xml/xmltextags.cpp create mode 100644 libs/xml/xmltextags.h create mode 100644 libs/xml/xmlwriter.cpp create mode 100644 libs/xml/xmlwriter.h create mode 100644 makeversion.py create mode 100644 plugins/archivepak/archive.cpp create mode 100644 plugins/archivepak/archive.h create mode 100644 plugins/archivepak/archivepak.def create mode 100644 plugins/archivepak/archivepak.vcproj create mode 100644 plugins/archivepak/pak.cpp create mode 100644 plugins/archivepak/pak.h create mode 100644 plugins/archivepak/plugin.cpp create mode 100644 plugins/archivepak/plugin.h create mode 100644 plugins/archivewad/archive.cpp create mode 100644 plugins/archivewad/archive.h create mode 100644 plugins/archivewad/archivewad.def create mode 100644 plugins/archivewad/archivewad.vcproj create mode 100644 plugins/archivewad/plugin.cpp create mode 100644 plugins/archivewad/plugin.h create mode 100644 plugins/archivewad/wad.cpp create mode 100644 plugins/archivewad/wad.h create mode 100644 plugins/archivezip/archive.cpp create mode 100644 plugins/archivezip/archive.h create mode 100644 plugins/archivezip/archivezip.def create mode 100644 plugins/archivezip/archivezip.vcproj create mode 100644 plugins/archivezip/pkzip.cpp create mode 100644 plugins/archivezip/pkzip.h create mode 100644 plugins/archivezip/plugin.cpp create mode 100644 plugins/archivezip/plugin.h create mode 100644 plugins/archivezip/zlibstream.cpp create mode 100644 plugins/archivezip/zlibstream.h create mode 100644 plugins/config.mk create mode 100644 plugins/entity/angle.cpp create mode 100644 plugins/entity/angle.h create mode 100644 plugins/entity/angles.cpp create mode 100644 plugins/entity/angles.h create mode 100644 plugins/entity/colour.cpp create mode 100644 plugins/entity/colour.h create mode 100644 plugins/entity/curve.cpp create mode 100644 plugins/entity/curve.h create mode 100644 plugins/entity/doom3group.cpp create mode 100644 plugins/entity/doom3group.h create mode 100644 plugins/entity/eclassmodel.cpp create mode 100644 plugins/entity/eclassmodel.h create mode 100644 plugins/entity/entity.cpp create mode 100644 plugins/entity/entity.h create mode 100644 plugins/entity/entityq3.def create mode 100644 plugins/entity/entityq3.vcproj create mode 100644 plugins/entity/filters.cpp create mode 100644 plugins/entity/filters.h create mode 100644 plugins/entity/generic.cpp create mode 100644 plugins/entity/generic.h create mode 100644 plugins/entity/group.cpp create mode 100644 plugins/entity/group.h create mode 100644 plugins/entity/keyobservers.cpp create mode 100644 plugins/entity/keyobservers.h create mode 100644 plugins/entity/light.cpp create mode 100644 plugins/entity/light.h create mode 100644 plugins/entity/miscmodel.cpp create mode 100644 plugins/entity/miscmodel.h create mode 100644 plugins/entity/model.cpp create mode 100644 plugins/entity/model.h create mode 100644 plugins/entity/modelskinkey.cpp create mode 100644 plugins/entity/modelskinkey.h create mode 100644 plugins/entity/namedentity.cpp create mode 100644 plugins/entity/namedentity.h create mode 100644 plugins/entity/namekeys.cpp create mode 100644 plugins/entity/namekeys.h create mode 100644 plugins/entity/origin.cpp create mode 100644 plugins/entity/origin.h create mode 100644 plugins/entity/plugin.cpp create mode 100644 plugins/entity/plugin.h create mode 100644 plugins/entity/rotation.cpp create mode 100644 plugins/entity/rotation.h create mode 100644 plugins/entity/scale.cpp create mode 100644 plugins/entity/scale.h create mode 100644 plugins/entity/skincache.cpp create mode 100644 plugins/entity/skincache.h create mode 100644 plugins/entity/targetable.cpp create mode 100644 plugins/entity/targetable.h create mode 100644 plugins/image/.cvsignore create mode 100644 plugins/image/bmp.cpp create mode 100644 plugins/image/bmp.h create mode 100644 plugins/image/dds.cpp create mode 100644 plugins/image/dds.h create mode 100644 plugins/image/image.cpp create mode 100644 plugins/image/image.h create mode 100644 plugins/image/imageq3.def create mode 100644 plugins/image/imageq3.vcproj create mode 100644 plugins/image/jpeg.cpp create mode 100644 plugins/image/jpeg.h create mode 100644 plugins/image/pcx.cpp create mode 100644 plugins/image/pcx.h create mode 100644 plugins/image/tga.cpp create mode 100644 plugins/image/tga.h create mode 100644 plugins/imagehl/hlw.cpp create mode 100644 plugins/imagehl/hlw.h create mode 100644 plugins/imagehl/imagehl.cpp create mode 100644 plugins/imagehl/imagehl.def create mode 100644 plugins/imagehl/imagehl.h create mode 100644 plugins/imagehl/imagehl.txt create mode 100644 plugins/imagehl/imagehl.vcproj create mode 100644 plugins/imagehl/mip.cpp create mode 100644 plugins/imagehl/mip.h create mode 100644 plugins/imagehl/sprite.cpp create mode 100644 plugins/imagehl/sprite.h create mode 100644 plugins/imagepng/imagepng.def create mode 100644 plugins/imagepng/imagepng.vcproj create mode 100644 plugins/imagepng/plugin.cpp create mode 100644 plugins/imagepng/plugin.h create mode 100644 plugins/imageq2/imageq2.cpp create mode 100644 plugins/imageq2/imageq2.def create mode 100644 plugins/imageq2/imageq2.h create mode 100644 plugins/imageq2/imageq2.vcproj create mode 100644 plugins/imageq2/wal.cpp create mode 100644 plugins/imageq2/wal.h create mode 100644 plugins/imageq2/wal32.cpp create mode 100644 plugins/imageq2/wal32.h create mode 100644 plugins/mapq3/mapq3.def create mode 100644 plugins/mapq3/mapq3.vcproj create mode 100644 plugins/mapq3/parse.cpp create mode 100644 plugins/mapq3/parse.h create mode 100644 plugins/mapq3/plugin.cpp create mode 100644 plugins/mapq3/plugin.h create mode 100644 plugins/mapq3/write.cpp create mode 100644 plugins/mapq3/write.h create mode 100644 plugins/mapxml/mapxml.def create mode 100644 plugins/mapxml/mapxml.vcproj create mode 100644 plugins/mapxml/plugin.cpp create mode 100644 plugins/mapxml/plugin.h create mode 100644 plugins/mapxml/xmlparse.cpp create mode 100644 plugins/mapxml/xmlparse.h create mode 100644 plugins/mapxml/xmlwrite.cpp create mode 100644 plugins/mapxml/xmlwrite.h create mode 100644 plugins/md3model/doc/md3-design.txt create mode 100644 plugins/md3model/ident.h create mode 100644 plugins/md3model/md2.cpp create mode 100644 plugins/md3model/md2.h create mode 100644 plugins/md3model/md3.cpp create mode 100644 plugins/md3model/md3.h create mode 100644 plugins/md3model/md3normals.cpp create mode 100644 plugins/md3model/md3normals.h create mode 100644 plugins/md3model/md5.cpp create mode 100644 plugins/md3model/md5.h create mode 100644 plugins/md3model/mdc.cpp create mode 100644 plugins/md3model/mdc.h create mode 100644 plugins/md3model/mdl.cpp create mode 100644 plugins/md3model/mdl.h create mode 100644 plugins/md3model/mdlformat.cpp create mode 100644 plugins/md3model/mdlformat.h create mode 100644 plugins/md3model/mdlimage.cpp create mode 100644 plugins/md3model/mdlimage.h create mode 100644 plugins/md3model/mdlnormals.cpp create mode 100644 plugins/md3model/mdlnormals.h create mode 100644 plugins/md3model/model.cpp create mode 100644 plugins/md3model/model.h create mode 100644 plugins/md3model/modelmd3.def create mode 100644 plugins/md3model/modelmd3.vcproj create mode 100644 plugins/md3model/plugin.cpp create mode 100644 plugins/md3model/plugin.h create mode 100644 plugins/model/bitmaps/model_reload_entity.bmp create mode 100644 plugins/model/bitmaps/picomodel.bmp create mode 100644 plugins/model/model.cpp create mode 100644 plugins/model/model.h create mode 100644 plugins/model/modelpico.def create mode 100644 plugins/model/modelpico.vcproj create mode 100644 plugins/model/plugin.cpp create mode 100644 plugins/model/plugin.h create mode 100644 plugins/sample/sample.cpp create mode 100644 plugins/sample/sample.def create mode 100644 plugins/sample/sample.h create mode 100644 plugins/sample/sample.vcproj create mode 100644 plugins/shaders/plugin.cpp create mode 100644 plugins/shaders/plugin.h create mode 100644 plugins/shaders/shaders.cpp create mode 100644 plugins/shaders/shaders.h create mode 100644 plugins/shaders/shaders.proj create mode 100644 plugins/shaders/shadershl.def create mode 100644 plugins/shaders/shadersq3.def create mode 100644 plugins/shaders/shadersq3.vcproj create mode 100644 plugins/spritemodel/plugin.cpp create mode 100644 plugins/spritemodel/plugin.h create mode 100644 plugins/spritemodel/spritemodel.cpp create mode 100644 plugins/spritemodel/spritemodel.def create mode 100644 plugins/spritemodel/spritemodel.h create mode 100644 plugins/spritemodel/spritemodel.vcproj create mode 100644 plugins/textool/.cvsignore create mode 100644 plugins/textool/.cvswrappers create mode 100644 plugins/textool/2DView.cpp create mode 100644 plugins/textool/2DView.h create mode 100644 plugins/textool/ControlPointsManager.cpp create mode 100644 plugins/textool/ControlPointsManager.h create mode 100644 plugins/textool/Doc/.cvswrappers create mode 100644 plugins/textool/Doc/Image2.jpg create mode 100644 plugins/textool/Doc/TexTool.html create mode 100644 plugins/textool/StdAfx.cpp create mode 100644 plugins/textool/StdAfx.h create mode 100644 plugins/textool/TexTool.cpp create mode 100644 plugins/textool/TexTool.def create mode 100644 plugins/textool/TexTool.rc create mode 100644 plugins/textool/TexTool.vcproj create mode 100644 plugins/textool/changelog.txt create mode 100644 plugins/textool/resource.h create mode 100644 plugins/vfspk3/.cvsignore create mode 100644 plugins/vfspk3/archive.cpp create mode 100644 plugins/vfspk3/archive.h create mode 100644 plugins/vfspk3/vfs.cpp create mode 100644 plugins/vfspk3/vfs.h create mode 100644 plugins/vfspk3/vfspk3.cpp create mode 100644 plugins/vfspk3/vfspk3.h create mode 100644 plugins/vfspk3/vfspk3.proj create mode 100644 plugins/vfspk3/vfsq3.def create mode 100644 plugins/vfspk3/vfsq3.vcproj create mode 100644 q3map2build.sh create mode 100644 radiant/.cvsignore create mode 100644 radiant/GtkRadiant.vcproj create mode 100644 radiant/autosave.cpp create mode 100644 radiant/autosave.h create mode 100644 radiant/brush.cpp create mode 100644 radiant/brush.h create mode 100644 radiant/brush_primit.cpp create mode 100644 radiant/brush_primit.h create mode 100644 radiant/brushmanip.cpp create mode 100644 radiant/brushmanip.h create mode 100644 radiant/brushmodule.cpp create mode 100644 radiant/brushmodule.h create mode 100644 radiant/brushnode.cpp create mode 100644 radiant/brushnode.h create mode 100644 radiant/brushtokens.cpp create mode 100644 radiant/brushtokens.h create mode 100644 radiant/brushxml.cpp create mode 100644 radiant/brushxml.h create mode 100644 radiant/build.cpp create mode 100644 radiant/build.h create mode 100644 radiant/camwindow.cpp create mode 100644 radiant/camwindow.h create mode 100644 radiant/clippertool.cpp create mode 100644 radiant/clippertool.h create mode 100644 radiant/commands.cpp create mode 100644 radiant/commands.h create mode 100644 radiant/console.cpp create mode 100644 radiant/console.h create mode 100644 radiant/csg.cpp create mode 100644 radiant/csg.h create mode 100644 radiant/dialog.cpp create mode 100644 radiant/dialog.h create mode 100644 radiant/eclass.cpp create mode 100644 radiant/eclass.h create mode 100644 radiant/eclass_def.cpp create mode 100644 radiant/eclass_def.h create mode 100644 radiant/eclass_doom3.cpp create mode 100644 radiant/eclass_doom3.h create mode 100644 radiant/eclass_fgd.cpp create mode 100644 radiant/eclass_fgd.h create mode 100644 radiant/eclass_xml.cpp create mode 100644 radiant/eclass_xml.h create mode 100644 radiant/entity.cpp create mode 100644 radiant/entity.h create mode 100644 radiant/entityinspector.cpp create mode 100644 radiant/entityinspector.h create mode 100644 radiant/entitylist.cpp create mode 100644 radiant/entitylist.h create mode 100644 radiant/environment.cpp create mode 100644 radiant/environment.h create mode 100644 radiant/error.cpp create mode 100644 radiant/error.h create mode 100644 radiant/feedback.cpp create mode 100644 radiant/feedback.h create mode 100644 radiant/filetypes.cpp create mode 100644 radiant/filetypes.h create mode 100644 radiant/filters.cpp create mode 100644 radiant/filters.h create mode 100644 radiant/findtexturedialog.cpp create mode 100644 radiant/findtexturedialog.h create mode 100644 radiant/glwidget.cpp create mode 100644 radiant/glwidget.h create mode 100644 radiant/grid.cpp create mode 100644 radiant/grid.h create mode 100644 radiant/groupdialog.cpp create mode 100644 radiant/groupdialog.h create mode 100644 radiant/gtkdlgs.cpp create mode 100644 radiant/gtkdlgs.h create mode 100644 radiant/gtkmisc.cpp create mode 100644 radiant/gtkmisc.h create mode 100644 radiant/help.cpp create mode 100644 radiant/help.h create mode 100644 radiant/image.cpp create mode 100644 radiant/image.h create mode 100644 radiant/main.cpp create mode 100644 radiant/main.h create mode 100644 radiant/mainframe.cpp create mode 100644 radiant/mainframe.h create mode 100644 radiant/map.cpp create mode 100644 radiant/map.h create mode 100644 radiant/mru.cpp create mode 100644 radiant/mru.h create mode 100644 radiant/multimon.cpp create mode 100644 radiant/multimon.h create mode 100644 radiant/nullmodel.cpp create mode 100644 radiant/nullmodel.h create mode 100644 radiant/parse.cpp create mode 100644 radiant/parse.h create mode 100644 radiant/patch.cpp create mode 100644 radiant/patch.h create mode 100644 radiant/patchdialog.cpp create mode 100644 radiant/patchdialog.h create mode 100644 radiant/patchmanip.cpp create mode 100644 radiant/patchmanip.h create mode 100644 radiant/patchmodule.cpp create mode 100644 radiant/patchmodule.h create mode 100644 radiant/plugin.cpp create mode 100644 radiant/plugin.h create mode 100644 radiant/pluginapi.cpp create mode 100644 radiant/pluginapi.h create mode 100644 radiant/pluginmanager.cpp create mode 100644 radiant/pluginmanager.h create mode 100644 radiant/pluginmenu.cpp create mode 100644 radiant/pluginmenu.h create mode 100644 radiant/plugintoolbar.cpp create mode 100644 radiant/plugintoolbar.h create mode 100644 radiant/points.cpp create mode 100644 radiant/points.h create mode 100644 radiant/preferencedictionary.cpp create mode 100644 radiant/preferencedictionary.h create mode 100644 radiant/preferences.cpp create mode 100644 radiant/preferences.h create mode 100644 radiant/qe3.cpp create mode 100644 radiant/qe3.h create mode 100644 radiant/qgl.cpp create mode 100644 radiant/qgl.h create mode 100644 radiant/radiant.ico create mode 100644 radiant/radiant.rc create mode 100644 radiant/radiant_old.ico create mode 100644 radiant/referencecache.cpp create mode 100644 radiant/referencecache.h create mode 100644 radiant/renderer.cpp create mode 100644 radiant/renderer.h create mode 100644 radiant/renderstate.cpp create mode 100644 radiant/renderstate.h create mode 100644 radiant/resource.h create mode 100644 radiant/scenegraph.cpp create mode 100644 radiant/scenegraph.h create mode 100644 radiant/select.cpp create mode 100644 radiant/select.h create mode 100644 radiant/selection.cpp create mode 100644 radiant/selection.h create mode 100644 radiant/server.cpp create mode 100644 radiant/server.h create mode 100644 radiant/shaders.cpp create mode 100644 radiant/shaders.h create mode 100644 radiant/sockets.cpp create mode 100644 radiant/sockets.h create mode 100644 radiant/stacktrace.cpp create mode 100644 radiant/stacktrace.h create mode 100644 radiant/surfacedialog.cpp create mode 100644 radiant/surfacedialog.h create mode 100644 radiant/texmanip.cpp create mode 100644 radiant/texmanip.h create mode 100644 radiant/textureentry.cpp create mode 100644 radiant/textureentry.h create mode 100644 radiant/textures.cpp create mode 100644 radiant/textures.h create mode 100644 radiant/texwindow.cpp create mode 100644 radiant/texwindow.h create mode 100644 radiant/timer.cpp create mode 100644 radiant/timer.h create mode 100644 radiant/treemodel.cpp create mode 100644 radiant/treemodel.h create mode 100644 radiant/undo.cpp create mode 100644 radiant/undo.h create mode 100644 radiant/url.cpp create mode 100644 radiant/url.h create mode 100644 radiant/view.cpp create mode 100644 radiant/view.h create mode 100644 radiant/watchbsp.cpp create mode 100644 radiant/watchbsp.h create mode 100644 radiant/winding.cpp create mode 100644 radiant/winding.h create mode 100644 radiant/windowobservers.cpp create mode 100644 radiant/windowobservers.h create mode 100644 radiant/xmlstuff.cpp create mode 100644 radiant/xmlstuff.h create mode 100644 radiant/xywindow.cpp create mode 100644 radiant/xywindow.h create mode 100644 run_python.bat create mode 100644 setup/PluginSDK/BuildGtkSrc create mode 100644 setup/PluginSDK/BuildSDK create mode 100644 setup/PluginSDK/BuildZip create mode 100644 setup/PluginSDK/README.html create mode 100644 setup/PluginSDK/TODO create mode 100644 setup/changelog.txt create mode 100644 setup/common/setup.pm create mode 100644 setup/credits.html create mode 100644 setup/data/tools/bitmaps/black.bmp create mode 100644 setup/data/tools/bitmaps/brush_flipx.bmp create mode 100644 setup/data/tools/bitmaps/brush_flipy.bmp create mode 100644 setup/data/tools/bitmaps/brush_flipz.bmp create mode 100644 setup/data/tools/bitmaps/brush_rotatex.bmp create mode 100644 setup/data/tools/bitmaps/brush_rotatey.bmp create mode 100644 setup/data/tools/bitmaps/brush_rotatez.bmp create mode 100644 setup/data/tools/bitmaps/cap_bevel.bmp create mode 100644 setup/data/tools/bitmaps/cap_cylinder.bmp create mode 100644 setup/data/tools/bitmaps/cap_endcap.bmp create mode 100644 setup/data/tools/bitmaps/cap_ibevel.bmp create mode 100644 setup/data/tools/bitmaps/cap_iendcap.bmp create mode 100644 setup/data/tools/bitmaps/console.bmp create mode 100644 setup/data/tools/bitmaps/curve_cap.bmp create mode 100644 setup/data/tools/bitmaps/dontselectcurve.bmp create mode 100644 setup/data/tools/bitmaps/dontselectmodel.bmp create mode 100644 setup/data/tools/bitmaps/ellipsis.bmp create mode 100644 setup/data/tools/bitmaps/entities.bmp create mode 100644 setup/data/tools/bitmaps/file_open.bmp create mode 100644 setup/data/tools/bitmaps/file_save.bmp create mode 100644 setup/data/tools/bitmaps/icon.bmp create mode 100644 setup/data/tools/bitmaps/lightinspector.bmp create mode 100644 setup/data/tools/bitmaps/logo.bmp create mode 100644 setup/data/tools/bitmaps/modify_edges.bmp create mode 100644 setup/data/tools/bitmaps/modify_faces.bmp create mode 100644 setup/data/tools/bitmaps/modify_vertices.bmp create mode 100644 setup/data/tools/bitmaps/noFalloff.bmp create mode 100644 setup/data/tools/bitmaps/notex.bmp create mode 100644 setup/data/tools/bitmaps/patch_bend.bmp create mode 100644 setup/data/tools/bitmaps/patch_drilldown.bmp create mode 100644 setup/data/tools/bitmaps/patch_insdel.bmp create mode 100644 setup/data/tools/bitmaps/patch_showboundingbox.bmp create mode 100644 setup/data/tools/bitmaps/patch_weld.bmp create mode 100644 setup/data/tools/bitmaps/patch_wireframe.bmp create mode 100644 setup/data/tools/bitmaps/popup_selection.bmp create mode 100644 setup/data/tools/bitmaps/redo.bmp create mode 100644 setup/data/tools/bitmaps/refresh_models.bmp create mode 100644 setup/data/tools/bitmaps/scalelockx.bmp create mode 100644 setup/data/tools/bitmaps/scalelocky.bmp create mode 100644 setup/data/tools/bitmaps/scalelockz.bmp create mode 100644 setup/data/tools/bitmaps/select_mouseresize.bmp create mode 100644 setup/data/tools/bitmaps/select_mouserotate.bmp create mode 100644 setup/data/tools/bitmaps/select_mousescale.bmp create mode 100644 setup/data/tools/bitmaps/select_mousetranslate.bmp create mode 100644 setup/data/tools/bitmaps/selection_csgmerge.bmp create mode 100644 setup/data/tools/bitmaps/selection_csgsubtract.bmp create mode 100644 setup/data/tools/bitmaps/selection_makehollow.bmp create mode 100644 setup/data/tools/bitmaps/selection_selectcompletetall.bmp create mode 100644 setup/data/tools/bitmaps/selection_selectinside.bmp create mode 100644 setup/data/tools/bitmaps/selection_selectpartialtall.bmp create mode 100644 setup/data/tools/bitmaps/selection_selecttouching.bmp create mode 100644 setup/data/tools/bitmaps/shadernotex.bmp create mode 100644 setup/data/tools/bitmaps/show_entities.bmp create mode 100644 setup/data/tools/bitmaps/splash.bmp create mode 100644 setup/data/tools/bitmaps/texture_browser.bmp create mode 100644 setup/data/tools/bitmaps/texture_lock.bmp create mode 100644 setup/data/tools/bitmaps/textures_popup.bmp create mode 100644 setup/data/tools/bitmaps/undo.bmp create mode 100644 setup/data/tools/bitmaps/view_cameratoggle.bmp create mode 100644 setup/data/tools/bitmaps/view_cameraupdate.bmp create mode 100644 setup/data/tools/bitmaps/view_change.bmp create mode 100644 setup/data/tools/bitmaps/view_clipper.bmp create mode 100644 setup/data/tools/bitmaps/view_cubicclipping.bmp create mode 100644 setup/data/tools/bitmaps/view_entity.bmp create mode 100644 setup/data/tools/bitmaps/white.bmp create mode 100644 setup/data/tools/bitmaps/window1.bmp create mode 100644 setup/data/tools/bitmaps/window2.bmp create mode 100644 setup/data/tools/bitmaps/window3.bmp create mode 100644 setup/data/tools/bitmaps/window4.bmp create mode 100644 setup/data/tools/gl/lighting_DBS_XY_Z_arbfp1.cg create mode 100644 setup/data/tools/gl/lighting_DBS_XY_Z_arbvp1.cg create mode 100644 setup/data/tools/gl/lighting_DBS_omni_fp.glp create mode 100644 setup/data/tools/gl/lighting_DBS_omni_fp.glsl create mode 100644 setup/data/tools/gl/lighting_DBS_omni_vp.glp create mode 100644 setup/data/tools/gl/lighting_DBS_omni_vp.glsl create mode 100644 setup/data/tools/gl/utils.cg create mode 100644 setup/data/tools/gl/zfill_arbfp1.cg create mode 100644 setup/data/tools/gl/zfill_arbvp1.cg create mode 100644 setup/data/tools/gl/zfill_fp.glp create mode 100644 setup/data/tools/gl/zfill_fp.glsl create mode 100644 setup/data/tools/gl/zfill_vp.glp create mode 100644 setup/data/tools/gl/zfill_vp.glsl create mode 100644 setup/data/tools/global.xlink create mode 100644 setup/data/tools/plugins/bitmaps/bobtoolz_caulk.bmp create mode 100644 setup/data/tools/plugins/bitmaps/bobtoolz_cleanup.bmp create mode 100644 setup/data/tools/plugins/bitmaps/bobtoolz_dropent.bmp create mode 100644 setup/data/tools/plugins/bitmaps/bobtoolz_merge.bmp create mode 100644 setup/data/tools/plugins/bitmaps/bobtoolz_poly.bmp create mode 100644 setup/data/tools/plugins/bitmaps/bobtoolz_split.bmp create mode 100644 setup/data/tools/plugins/bitmaps/bobtoolz_trainpathplot.bmp create mode 100644 setup/data/tools/plugins/bitmaps/bobtoolz_treeplanter.bmp create mode 100644 setup/data/tools/plugins/bitmaps/bobtoolz_turnedge.bmp create mode 100644 setup/data/tools/plugins/bitmaps/ufoai_actorclip.bmp create mode 100644 setup/data/tools/plugins/bitmaps/ufoai_level1.bmp create mode 100644 setup/data/tools/plugins/bitmaps/ufoai_level2.bmp create mode 100644 setup/data/tools/plugins/bitmaps/ufoai_level3.bmp create mode 100644 setup/data/tools/plugins/bitmaps/ufoai_level4.bmp create mode 100644 setup/data/tools/plugins/bitmaps/ufoai_level5.bmp create mode 100644 setup/data/tools/plugins/bitmaps/ufoai_level6.bmp create mode 100644 setup/data/tools/plugins/bitmaps/ufoai_level7.bmp create mode 100644 setup/data/tools/plugins/bitmaps/ufoai_level8.bmp create mode 100644 setup/data/tools/plugins/bitmaps/ufoai_nodraw.bmp create mode 100644 setup/data/tools/plugins/bitmaps/ufoai_stepon.bmp create mode 100644 setup/data/tools/plugins/bitmaps/ufoai_weaponclip.bmp create mode 100644 setup/data/tools/plugins/bt/bt-el1.txt create mode 100644 setup/data/tools/plugins/bt/bt-el2.txt create mode 100644 setup/data/tools/plugins/bt/door-tex-trim.txt create mode 100644 setup/data/tools/plugins/bt/door-tex.txt create mode 100644 setup/data/tools/plugins/bt/tp_ent.txt create mode 100644 setup/data/tools/q3data.qdt create mode 100644 setup/gpl.rtf create mode 100644 setup/gpl.txt create mode 100644 setup/license.rtf create mode 100644 setup/license.txt create mode 100644 setup/links.htm create mode 100644 setup/linux/Help/DocsArt/toolback.jpg create mode 100644 setup/linux/Help/Index.html create mode 100644 setup/linux/Help/Q3A_EULA.txt create mode 100644 setup/linux/README create mode 100644 setup/linux/all.cf create mode 100644 setup/linux/bspc create mode 100644 setup/linux/bug750/loki_setup.patch create mode 100644 setup/linux/gtkradiant-1.5.0.spec create mode 100644 setup/linux/makeself/COPYING create mode 100644 setup/linux/makeself/README create mode 100644 setup/linux/makeself/TODO create mode 100644 setup/linux/makeself/makeself-header.sh create mode 100644 setup/linux/makeself/makeself.lsm create mode 100755 setup/linux/makeself/makeself.sh create mode 100755 setup/linux/makeself/update-readme create mode 100644 setup/linux/nightly.cf create mode 100644 setup/linux/q3.cf create mode 100644 setup/linux/rpm_build.sh create mode 100644 setup/linux/setup.sh create mode 100644 setup/linux/setup_image.Linux/setup.data/bin/Linux/x86/glibc-2.1/setup.gtk create mode 100644 setup/linux/setup_image/setup.data/config.games.sh create mode 100644 setup/linux/setup_image/setup.data/config.sh.in create mode 100644 setup/linux/setup_image/setup.data/postinstall.sh.in create mode 100644 setup/linux/setup_image/setup.data/setup.glade create mode 100644 setup/linux/setup_image/setup.data/setup.xml.in create mode 100644 setup/linux/setup_image/setup.data/splash.xpm create mode 100644 setup/linux/setup_image/setup.sh.in create mode 100644 setup/linux/source_archive.sh create mode 100644 setup/linux/wolf.cf create mode 100644 setup/openurl.sh create mode 100644 setup/osx/build.sh create mode 100644 setup/osx/radiant.info.m4 create mode 100644 setup/quickstart.txt create mode 100644 setup/radiantgtkrc create mode 100644 setup/setup.bmp create mode 100644 setup/setup.patch create mode 100644 setup/shortcuts.ini.sample create mode 100644 setup/win32/HOWTO create mode 100644 setup/win32/HOWTO_outdated create mode 100644 setup/win32/TODO create mode 100644 setup/win32/all.cf create mode 100644 setup/win32/bin/bspc.exe create mode 100644 setup/win32/bin/msvcr70.dll create mode 100644 setup/win32/build.py create mode 100644 setup/win32/classic.cf create mode 100644 setup/win32/components/archivepak.xml create mode 100644 setup/win32/components/archivewad.xml create mode 100644 setup/win32/components/archivezip.xml create mode 100644 setup/win32/components/atk.xml create mode 100644 setup/win32/components/bobtoolz.xml create mode 100644 setup/win32/components/brushexport.xml create mode 100644 setup/win32/components/bspc.xml create mode 100644 setup/win32/components/cairo.xml create mode 100644 setup/win32/components/darkplaces.xml create mode 100644 setup/win32/components/dbghelp.xml create mode 100644 setup/win32/components/doom3.xml create mode 100644 setup/win32/components/ef.xml create mode 100644 setup/win32/components/ef_data.xml create mode 100644 setup/win32/components/ef_docs.xml create mode 100644 setup/win32/components/ef_tools.xml create mode 100644 setup/win32/components/entityq3.xml create mode 100644 setup/win32/components/et.xml create mode 100644 setup/win32/components/et_data.xml create mode 100644 setup/win32/components/et_docs.xml create mode 100644 setup/win32/components/glib2.xml create mode 100644 setup/win32/components/gtk2.xml create mode 100644 setup/win32/components/gtkglext.xml create mode 100644 setup/win32/components/gtkradiant.xml create mode 100644 setup/win32/components/gtkradiant_data.xml create mode 100644 setup/win32/components/gtkradiant_docs.xml create mode 100644 setup/win32/components/her2.xml create mode 100644 setup/win32/components/her2_data.xml create mode 100644 setup/win32/components/hl.xml create mode 100644 setup/win32/components/hl_data.xml create mode 100644 setup/win32/components/hlcs_data.xml create mode 100644 setup/win32/components/icarus_manual.xml create mode 100644 setup/win32/components/iconv.xml create mode 100644 setup/win32/components/imagehl.xml create mode 100644 setup/win32/components/imagem8.xml create mode 100644 setup/win32/components/imagepng.xml create mode 100644 setup/win32/components/imageq2.xml create mode 100644 setup/win32/components/imageq3.xml create mode 100644 setup/win32/components/intl.xml create mode 100644 setup/win32/components/ja.xml create mode 100644 setup/win32/components/ja_data.xml create mode 100644 setup/win32/components/ja_tools.xml create mode 100644 setup/win32/components/jk2.xml create mode 100644 setup/win32/components/jk2_data.xml create mode 100644 setup/win32/components/jk2_docs.xml create mode 100644 setup/win32/components/jk2_raven_docs.xml create mode 100644 setup/win32/components/jk2_tools.xml create mode 100644 setup/win32/components/libmhash.xml create mode 100644 setup/win32/components/libpng13.xml create mode 100644 setup/win32/components/libxml2.xml create mode 100644 setup/win32/components/mapq3.xml create mode 100644 setup/win32/components/mapxml.xml create mode 100644 setup/win32/components/modelmd3.xml create mode 100644 setup/win32/components/modelpico.xml create mode 100644 setup/win32/components/msvcr80.xml create mode 100644 setup/win32/components/nexuiz.xml create mode 100644 setup/win32/components/pango.xml create mode 100644 setup/win32/components/prey.xml create mode 100644 setup/win32/components/prtview.xml create mode 100644 setup/win32/components/q1.xml create mode 100644 setup/win32/components/q2.xml create mode 100644 setup/win32/components/q2_data.xml create mode 100644 setup/win32/components/q2map.xml create mode 100644 setup/win32/components/q3.xml create mode 100644 setup/win32/components/q3a_compile_manual.xml create mode 100644 setup/win32/components/q3a_data.xml create mode 100644 setup/win32/components/q3a_model_manual.xml create mode 100644 setup/win32/components/q3a_shader_manual.xml create mode 100644 setup/win32/components/q3a_terrain_manual.xml create mode 100644 setup/win32/components/q3map2.xml create mode 100644 setup/win32/components/q3map2_docs.xml create mode 100644 setup/win32/components/q3ta_data.xml create mode 100644 setup/win32/components/q3ta_editing_manual.xml create mode 100644 setup/win32/components/q3ta_teams_manual.xml create mode 100644 setup/win32/components/q4.xml create mode 100644 setup/win32/components/qdata3.xml create mode 100644 setup/win32/components/radiant_manual.xml create mode 100644 setup/win32/components/raven.xml create mode 100644 setup/win32/components/shaderplug.xml create mode 100644 setup/win32/components/shadersq3.xml create mode 100644 setup/win32/components/sof2.xml create mode 100644 setup/win32/components/sof2_data.xml create mode 100644 setup/win32/components/sof2_docs.xml create mode 100644 setup/win32/components/sof2_raven_docs.xml create mode 100644 setup/win32/components/sof2_tools.xml create mode 100644 setup/win32/components/sunplug.xml create mode 100644 setup/win32/components/trem.xml create mode 100644 setup/win32/components/trem_compile_manual.xml create mode 100644 setup/win32/components/trem_model_manual.xml create mode 100644 setup/win32/components/trem_shader_manual.xml create mode 100644 setup/win32/components/trem_terrain_manual.xml create mode 100644 setup/win32/components/ufoai.xml create mode 100644 setup/win32/components/ufoaiplug.xml create mode 100644 setup/win32/components/vfsq3.xml create mode 100644 setup/win32/components/warsow.xml create mode 100644 setup/win32/components/warsow_data.xml create mode 100644 setup/win32/components/warsow_docs.xml create mode 100644 setup/win32/components/wolf.xml create mode 100644 setup/win32/components/wolf_data.xml create mode 100644 setup/win32/components/wolf_docs.xml create mode 100644 setup/win32/components/wolf_tools.xml create mode 100644 setup/win32/components/zhlt.xml create mode 100644 setup/win32/components/zlib.xml create mode 100644 setup/win32/ef_example_maps.xml create mode 100644 setup/win32/et.cf create mode 100644 setup/win32/et_example_maps.xml create mode 100644 setup/win32/gtkradiant-1.5.0.xml create mode 100644 setup/win32/halflife.cf create mode 100644 setup/win32/her2_example_maps.xml create mode 100644 setup/win32/heretic2.cf create mode 100644 setup/win32/hl_example_maps.xml create mode 100644 setup/win32/id-hl.cf create mode 100644 setup/win32/installer.py create mode 100644 setup/win32/ja.cf create mode 100644 setup/win32/ja_example_maps.xml create mode 100644 setup/win32/jk2.cf create mode 100644 setup/win32/msi.py create mode 100644 setup/win32/msi/msiquery.c create mode 100644 setup/win32/msi/msiquery.def create mode 100644 setup/win32/msi/msiquery.sln create mode 100644 setup/win32/msi/msiquery.vcproj create mode 100644 setup/win32/msiquery.dll create mode 100644 setup/win32/msiquery.pyd create mode 100644 setup/win32/nightly.cf create mode 100644 setup/win32/q1.cf create mode 100644 setup/win32/q2_example_maps.xml create mode 100644 setup/win32/q3.cf create mode 100644 setup/win32/q3a_example_maps.xml create mode 100644 setup/win32/q3q1.cf create mode 100644 setup/win32/q3wolf.cf create mode 100644 setup/win32/q3wolfet.cf create mode 100644 setup/win32/raven-hl.cf create mode 100644 setup/win32/raven.cf create mode 100644 setup/win32/setup.pl create mode 100644 setup/win32/sof2.cf create mode 100644 setup/win32/stvef.cf create mode 100644 setup/win32/template.msi create mode 100644 setup/win32/template/Component Definitions/Default.cdf create mode 100644 setup/win32/template/Component Definitions/Default.fgl create mode 100644 setup/win32/template/File Groups/1_2_1 Wolf Media Upgrade.fgl create mode 100644 setup/win32/template/File Groups/Compile Manual.fgl create mode 100644 setup/win32/template/File Groups/Default.fdf create mode 100644 setup/win32/template/File Groups/ET Executable Files.fgl create mode 100644 setup/win32/template/File Groups/ET Media Files.fgl create mode 100644 setup/win32/template/File Groups/Example Files.fgl create mode 100644 setup/win32/template/File Groups/Halflife Executable Files.fgl create mode 100644 setup/win32/template/File Groups/Halflife Media Files.fgl create mode 100644 setup/win32/template/File Groups/Heretic2 Executable Files.fgl create mode 100644 setup/win32/template/File Groups/Heretic2 Media Files.fgl create mode 100644 setup/win32/template/File Groups/JA Executable Files.fgl create mode 100644 setup/win32/template/File Groups/JA Media Files.fgl create mode 100644 setup/win32/template/File Groups/JKII Executable Files.fgl create mode 100644 setup/win32/template/File Groups/JKII Media Files.fgl create mode 100644 setup/win32/template/File Groups/Model Manual Files.fgl create mode 100644 setup/win32/template/File Groups/Plugins - BobToolz.fgl create mode 100644 setup/win32/template/File Groups/Plugins - Curry pk3 Wolf.fgl create mode 100644 setup/win32/template/File Groups/Plugins - Curry pk3.fgl create mode 100644 setup/win32/template/File Groups/Plugins - Curry.fgl create mode 100644 setup/win32/template/File Groups/Plugins - GTK GenSurf.fgl create mode 100644 setup/win32/template/File Groups/Plugins - Pk3Man.fgl create mode 100644 setup/win32/template/File Groups/Plugins - PrtView.fgl create mode 100644 setup/win32/template/File Groups/Plugins - TexTool.fgl create mode 100644 setup/win32/template/File Groups/Plugins - bkgrnd2d.fgl create mode 100644 setup/win32/template/File Groups/Program DLL Files.fgl create mode 100644 setup/win32/template/File Groups/Program DLLs.fgl create mode 100644 setup/win32/template/File Groups/Program Executable Files.fgl create mode 100644 setup/win32/template/File Groups/Program Misc Files.fgl create mode 100644 setup/win32/template/File Groups/Q2 Executable Files.fgl create mode 100644 setup/win32/template/File Groups/Q2 Media Files.fgl create mode 100644 setup/win32/template/File Groups/Q3 Default Project.fgl create mode 100644 setup/win32/template/File Groups/Q3 Editor Images - SPoG pk3.fgl create mode 100644 setup/win32/template/File Groups/Q3 Executable Files.fgl create mode 100644 setup/win32/template/File Groups/Q3 Misc Files.fgl create mode 100644 setup/win32/template/File Groups/Q3 Sample Files.fgl create mode 100644 setup/win32/template/File Groups/Radiant Manual Files.fgl create mode 100644 setup/win32/template/File Groups/SOF2 Executable Files.fgl create mode 100644 setup/win32/template/File Groups/SOF2 Media Files.fgl create mode 100644 setup/win32/template/File Groups/STVEF Executable Files.fgl create mode 100644 setup/win32/template/File Groups/STVEF Media Files.fgl create mode 100644 setup/win32/template/File Groups/Shader Manual Files.fgl create mode 100644 setup/win32/template/File Groups/TA Manual Files.fgl create mode 100644 setup/win32/template/File Groups/TA Sample Files.fgl create mode 100644 setup/win32/template/File Groups/TA Teams Manual.fgl create mode 100644 setup/win32/template/File Groups/Terrain Manual Files.fgl create mode 100644 setup/win32/template/File Groups/TexTool Help.fgl create mode 100644 setup/win32/template/File Groups/Wolf Exectuable Files.fgl create mode 100644 setup/win32/template/File Groups/Wolf Media Files.fgl create mode 100644 setup/win32/template/File Groups/shaderlist-ta.fgl create mode 100644 setup/win32/template/File Groups/shaderlist.fgl create mode 100644 setup/win32/template/GtkRadiant.ipr create mode 100644 setup/win32/template/Media/GtkRadiant/Default.mda create mode 100644 setup/win32/template/Registry Entries/Default.rge create mode 100644 setup/win32/template/Script Files/Setup.map create mode 100644 setup/win32/template/Script Files/Setup.rul create mode 100644 setup/win32/template/Setup Files/Compressed Files/Language Independent/OS Independent/_IsUser.dll create mode 100644 setup/win32/template/Setup Files/Compressed Files/Language Independent/OS Independent/infolist.txt create mode 100644 setup/win32/template/Setup Files/Compressed Files/Language Independent/OS Independent/license.txt create mode 100644 setup/win32/template/Shell Objects/Default.shl create mode 100644 setup/win32/template/String Tables/0009-English/value.shl create mode 100644 setup/win32/template/String Tables/Default.shl create mode 100644 setup/win32/template/Text Substitutions/Build.tsb create mode 100644 setup/win32/template/Text Substitutions/Setup.tsb create mode 100644 setup/win32/warsow_mapping_files.xml create mode 100644 setup/win32/wolf.cf create mode 100644 setup/win32/wolf_example_maps.xml create mode 100644 svn-commit.tmp create mode 100644 svn.py create mode 100644 tools/quake2/common/bspfile.c create mode 100644 tools/quake2/common/bspfile.h create mode 100644 tools/quake2/common/cmdlib.c create mode 100644 tools/quake2/common/cmdlib.h create mode 100644 tools/quake2/common/inout.c create mode 100644 tools/quake2/common/inout.h create mode 100644 tools/quake2/common/l3dslib.c create mode 100644 tools/quake2/common/l3dslib.h create mode 100644 tools/quake2/common/lbmlib.c create mode 100644 tools/quake2/common/lbmlib.h create mode 100644 tools/quake2/common/mathlib.c create mode 100644 tools/quake2/common/mathlib.h create mode 100644 tools/quake2/common/md4.c create mode 100644 tools/quake2/common/path_init.c create mode 100644 tools/quake2/common/polylib.c create mode 100644 tools/quake2/common/polylib.h create mode 100644 tools/quake2/common/q2_threads.h create mode 100644 tools/quake2/common/qfiles.h create mode 100644 tools/quake2/common/scriplib.c create mode 100644 tools/quake2/common/scriplib.h create mode 100644 tools/quake2/common/threads.c create mode 100644 tools/quake2/common/trilib.c create mode 100644 tools/quake2/common/trilib.h create mode 100644 tools/quake2/q2map/brushbsp.c create mode 100644 tools/quake2/q2map/csg.c create mode 100644 tools/quake2/q2map/faces.c create mode 100644 tools/quake2/q2map/flow.c create mode 100644 tools/quake2/q2map/gldraw.c create mode 100644 tools/quake2/q2map/glfile.c create mode 100644 tools/quake2/q2map/leakfile.c create mode 100644 tools/quake2/q2map/lightmap.c create mode 100644 tools/quake2/q2map/main.c create mode 100644 tools/quake2/q2map/map.c create mode 100644 tools/quake2/q2map/nodraw.c create mode 100644 tools/quake2/q2map/patches.c create mode 100644 tools/quake2/q2map/portals.c create mode 100644 tools/quake2/q2map/prtfile.c create mode 100644 tools/quake2/q2map/q2map.h create mode 100644 tools/quake2/q2map/q2map.vcproj create mode 100644 tools/quake2/q2map/qbsp.c create mode 100644 tools/quake2/q2map/qbsp.h create mode 100644 tools/quake2/q2map/qrad.c create mode 100644 tools/quake2/q2map/qrad.h create mode 100644 tools/quake2/q2map/qvis.c create mode 100644 tools/quake2/q2map/qvis.h create mode 100644 tools/quake2/q2map/textures.c create mode 100644 tools/quake2/q2map/trace.c create mode 100644 tools/quake2/q2map/tree.c create mode 100644 tools/quake2/q2map/writebsp.c create mode 100644 tools/quake2/qdata/anorms.h create mode 100644 tools/quake2/qdata/images.c create mode 100644 tools/quake2/qdata/makefile create mode 100644 tools/quake2/qdata/models.c create mode 100644 tools/quake2/qdata/qdata.c create mode 100644 tools/quake2/qdata/qdata.h create mode 100644 tools/quake2/qdata/qdata3.vcproj create mode 100644 tools/quake2/qdata/sprites.c create mode 100644 tools/quake2/qdata/tables.c create mode 100644 tools/quake2/qdata/video.c create mode 100644 tools/quake2/qdata_heretic2/adpcm.h create mode 100644 tools/quake2/qdata_heretic2/animcomp.c create mode 100644 tools/quake2/qdata_heretic2/animcomp.h create mode 100644 tools/quake2/qdata_heretic2/anorms.h create mode 100644 tools/quake2/qdata_heretic2/book.c create mode 100644 tools/quake2/qdata_heretic2/common/bspfile.c create mode 100644 tools/quake2/qdata_heretic2/common/bspfile.h create mode 100644 tools/quake2/qdata_heretic2/common/cmdlib.c create mode 100644 tools/quake2/qdata_heretic2/common/cmdlib.h create mode 100644 tools/quake2/qdata_heretic2/common/her2_threads.h create mode 100644 tools/quake2/qdata_heretic2/common/inout.c create mode 100644 tools/quake2/qdata_heretic2/common/inout.h create mode 100644 tools/quake2/qdata_heretic2/common/l3dslib.c create mode 100644 tools/quake2/qdata_heretic2/common/l3dslib.h create mode 100644 tools/quake2/qdata_heretic2/common/lbmlib.c create mode 100644 tools/quake2/qdata_heretic2/common/lbmlib.h create mode 100644 tools/quake2/qdata_heretic2/common/mathlib.c create mode 100644 tools/quake2/qdata_heretic2/common/mathlib.h create mode 100644 tools/quake2/qdata_heretic2/common/md4.c create mode 100644 tools/quake2/qdata_heretic2/common/path_init.c create mode 100644 tools/quake2/qdata_heretic2/common/polylib.c create mode 100644 tools/quake2/qdata_heretic2/common/polylib.h create mode 100644 tools/quake2/qdata_heretic2/common/qfiles.c create mode 100644 tools/quake2/qdata_heretic2/common/qfiles.h create mode 100644 tools/quake2/qdata_heretic2/common/scriplib.c create mode 100644 tools/quake2/qdata_heretic2/common/scriplib.h create mode 100644 tools/quake2/qdata_heretic2/common/threads.c create mode 100644 tools/quake2/qdata_heretic2/common/token.c create mode 100644 tools/quake2/qdata_heretic2/common/token.h create mode 100644 tools/quake2/qdata_heretic2/common/trilib.c create mode 100644 tools/quake2/qdata_heretic2/common/trilib.h create mode 100644 tools/quake2/qdata_heretic2/fmodels.c create mode 100644 tools/quake2/qdata_heretic2/icon1.ico create mode 100644 tools/quake2/qdata_heretic2/images.c create mode 100644 tools/quake2/qdata_heretic2/jointed.c create mode 100644 tools/quake2/qdata_heretic2/jointed.h create mode 100644 tools/quake2/qdata_heretic2/joints.h create mode 100644 tools/quake2/qdata_heretic2/models.c create mode 100644 tools/quake2/qdata_heretic2/pics.c create mode 100644 tools/quake2/qdata_heretic2/qcommon/angles.h create mode 100644 tools/quake2/qdata_heretic2/qcommon/arrayedlist.h create mode 100644 tools/quake2/qdata_heretic2/qcommon/flex.h create mode 100644 tools/quake2/qdata_heretic2/qcommon/fmodel.h create mode 100644 tools/quake2/qdata_heretic2/qcommon/h2common.h create mode 100644 tools/quake2/qdata_heretic2/qcommon/placement.h create mode 100644 tools/quake2/qdata_heretic2/qcommon/q_typedef.h create mode 100644 tools/quake2/qdata_heretic2/qcommon/qfiles.h create mode 100644 tools/quake2/qdata_heretic2/qcommon/reference.c create mode 100644 tools/quake2/qdata_heretic2/qcommon/reference.h create mode 100644 tools/quake2/qdata_heretic2/qcommon/resourcemanager.c create mode 100644 tools/quake2/qdata_heretic2/qcommon/resourcemanager.h create mode 100644 tools/quake2/qdata_heretic2/qcommon/skeletons.c create mode 100644 tools/quake2/qdata_heretic2/qcommon/skeletons.h create mode 100644 tools/quake2/qdata_heretic2/qd_fmodel.h create mode 100644 tools/quake2/qdata_heretic2/qd_skeletons.c create mode 100644 tools/quake2/qdata_heretic2/qd_skeletons.h create mode 100644 tools/quake2/qdata_heretic2/qdata.c create mode 100644 tools/quake2/qdata_heretic2/qdata.h create mode 100644 tools/quake2/qdata_heretic2/qdata3_heretic2.vcproj create mode 100644 tools/quake2/qdata_heretic2/resource.h create mode 100644 tools/quake2/qdata_heretic2/script1.aps create mode 100644 tools/quake2/qdata_heretic2/script1.rc create mode 100644 tools/quake2/qdata_heretic2/sprites.c create mode 100644 tools/quake2/qdata_heretic2/svdcmp.c create mode 100644 tools/quake2/qdata_heretic2/tables.c create mode 100644 tools/quake2/qdata_heretic2/tmix.c create mode 100644 tools/quake2/qdata_heretic2/video.c create mode 100644 tools/quake3/common/aselib.c create mode 100644 tools/quake3/common/aselib.h create mode 100644 tools/quake3/common/bspfile.c create mode 100644 tools/quake3/common/bspfile.h create mode 100644 tools/quake3/common/cmdlib.c create mode 100644 tools/quake3/common/cmdlib.h create mode 100644 tools/quake3/common/imagelib.c create mode 100644 tools/quake3/common/imagelib.h create mode 100644 tools/quake3/common/inout.c create mode 100644 tools/quake3/common/inout.h create mode 100644 tools/quake3/common/l3dslib.c create mode 100644 tools/quake3/common/l3dslib.h create mode 100644 tools/quake3/common/md4.c create mode 100644 tools/quake3/common/mutex.c create mode 100644 tools/quake3/common/mutex.h create mode 100644 tools/quake3/common/polylib.c create mode 100644 tools/quake3/common/polylib.h create mode 100644 tools/quake3/common/polyset.h create mode 100644 tools/quake3/common/qfiles.h create mode 100644 tools/quake3/common/qthreads.h create mode 100644 tools/quake3/common/scriplib.c create mode 100644 tools/quake3/common/scriplib.h create mode 100644 tools/quake3/common/surfaceflags.h create mode 100644 tools/quake3/common/threads.c create mode 100644 tools/quake3/common/trilib.c create mode 100644 tools/quake3/common/trilib.h create mode 100644 tools/quake3/common/unzip.c create mode 100644 tools/quake3/common/unzip.h create mode 100644 tools/quake3/common/vfs.c create mode 100644 tools/quake3/common/vfs.h create mode 100644 tools/quake3/q3data/.cvsignore create mode 100644 tools/quake3/q3data/.cvswrappers create mode 100644 tools/quake3/q3data/3dslib.c create mode 100644 tools/quake3/q3data/3dslib.h create mode 100644 tools/quake3/q3data/compress.c create mode 100644 tools/quake3/q3data/images.c create mode 100644 tools/quake3/q3data/md3lib.c create mode 100644 tools/quake3/q3data/md3lib.h create mode 100644 tools/quake3/q3data/models.c create mode 100644 tools/quake3/q3data/oldstuff.c create mode 100644 tools/quake3/q3data/p3dlib.c create mode 100644 tools/quake3/q3data/p3dlib.h create mode 100644 tools/quake3/q3data/polyset.c create mode 100644 tools/quake3/q3data/q3data.c create mode 100644 tools/quake3/q3data/q3data.h create mode 100644 tools/quake3/q3data/q3data.vcproj create mode 100644 tools/quake3/q3data/stripper.c create mode 100644 tools/quake3/q3data/video.c create mode 100644 tools/quake3/q3map2/.cvsignore create mode 100644 tools/quake3/q3map2/brush.c create mode 100644 tools/quake3/q3map2/brush_primit.c create mode 100644 tools/quake3/q3map2/bsp.c create mode 100644 tools/quake3/q3map2/bspfile_abstract.c create mode 100644 tools/quake3/q3map2/bspfile_ibsp.c create mode 100644 tools/quake3/q3map2/bspfile_rbsp.c create mode 100644 tools/quake3/q3map2/changelog.q3map1 create mode 100644 tools/quake3/q3map2/changelog.q3map2.txt create mode 100644 tools/quake3/q3map2/convert_ase.c create mode 100644 tools/quake3/q3map2/convert_map.c create mode 100644 tools/quake3/q3map2/decals.c create mode 100644 tools/quake3/q3map2/facebsp.c create mode 100644 tools/quake3/q3map2/fog.c create mode 100644 tools/quake3/q3map2/game_ef.h create mode 100644 tools/quake3/q3map2/game_etut.h create mode 100644 tools/quake3/q3map2/game_ja.h create mode 100644 tools/quake3/q3map2/game_jk2.h create mode 100644 tools/quake3/q3map2/game_nexuiz.h create mode 100644 tools/quake3/q3map2/game_qfusion.h create mode 100644 tools/quake3/q3map2/game_quake3.h create mode 100644 tools/quake3/q3map2/game_quakelive.h create mode 100644 tools/quake3/q3map2/game_sof2.h create mode 100644 tools/quake3/q3map2/game_t.h create mode 100644 tools/quake3/q3map2/game_tenebrae.h create mode 100644 tools/quake3/q3map2/game_tremulous.h create mode 100644 tools/quake3/q3map2/game_wolf.h create mode 100644 tools/quake3/q3map2/game_wolfet.h create mode 100644 tools/quake3/q3map2/image.c create mode 100644 tools/quake3/q3map2/leakfile.c create mode 100644 tools/quake3/q3map2/light.c create mode 100644 tools/quake3/q3map2/light_bounce.c create mode 100644 tools/quake3/q3map2/light_shadows.c create mode 100644 tools/quake3/q3map2/light_trace.c create mode 100644 tools/quake3/q3map2/light_ydnar.c create mode 100644 tools/quake3/q3map2/lightmaps.c create mode 100644 tools/quake3/q3map2/lightmaps_ydnar.c create mode 100644 tools/quake3/q3map2/listen.pl create mode 100644 tools/quake3/q3map2/main.c create mode 100644 tools/quake3/q3map2/map.c create mode 100644 tools/quake3/q3map2/mesh.c create mode 100644 tools/quake3/q3map2/model.c create mode 100644 tools/quake3/q3map2/patch.c create mode 100644 tools/quake3/q3map2/path_init.c create mode 100644 tools/quake3/q3map2/portals.c create mode 100644 tools/quake3/q3map2/prtfile.c create mode 100644 tools/quake3/q3map2/q3map2.h create mode 100644 tools/quake3/q3map2/q3map2.ico create mode 100644 tools/quake3/q3map2/q3map2.rc create mode 100644 tools/quake3/q3map2/q3map2.vcproj create mode 100644 tools/quake3/q3map2/q3map2_VC9.vcproj create mode 100644 tools/quake3/q3map2/shaders.c create mode 100644 tools/quake3/q3map2/surface.c create mode 100644 tools/quake3/q3map2/surface_extra.c create mode 100644 tools/quake3/q3map2/surface_foliage.c create mode 100644 tools/quake3/q3map2/surface_fur.c create mode 100644 tools/quake3/q3map2/surface_meta.c create mode 100644 tools/quake3/q3map2/tjunction.c create mode 100644 tools/quake3/q3map2/tree.c create mode 100644 tools/quake3/q3map2/vis.c create mode 100644 tools/quake3/q3map2/visflow.c create mode 100644 tools/quake3/q3map2/writebsp.c create mode 100644 touch.py diff --git a/CHANGES b/CHANGES new file mode 100644 index 00000000..47deae20 --- /dev/null +++ b/CHANGES @@ -0,0 +1,8216 @@ +This is the changelog for developers, != changelog for the end user +that we distribute with the binaries. (see changelog) + +28/06/2007 (dunkfordyce@gmail.com) +- fixed mnemonics for MRU list +- fixed find functionality +- added option to always use caulk texture for new brushes +- fixed(?) a small bug in install.py that was trying to copy msvc files on linux + +28/06/2007 +- Added material-support to brushexport-plugin (Shaderman) + +26/04/2007 +- Disabled auto-search for texbrowser and entityinspector even when the option + is turned on in the gtkrc + +26/04/2007 +- Fixed q3map2 lightbounce bug (by Unknown) + +26/04/2007 +- Fixed another texcompression bug + +25/04/2007 +- Fixed texcompression beeing disabled after each restart +- Fixed gtk-searchpopup in treeviews + +15/04/2007 +namespace +- Fixed brushexport2 output float-format (Shaderman) + +27/03/2007 +namespace +- Fix: Added missing xml-writer pop-calls +- Ported mapxml plugin to Doom3 +- mapxml now exports brushes with its vertices + +19/03/2007 +namespace +- Fix: Selection is now deleted when creating a patch out of it (aumüller / namespace) + +08/03/2007 +namespace +- Win32 compile fix (woekele) + +07/03/2007 +namespace +- Updated UFA:Plugin (mattn2) + +04/03/2007 +namespace +- Reverted Q3 translucency fix since it randomly made brushes + fully transparent in Doom3 mode. + +04/03/2007 +namespace +- Final update for installerfiles (Shaderman / namespace) +- Removed installer.py validation checks since they are incompatible with Vista +- Updated msiquery module to python 2.5 +- Added msiquery module for python 2.5 + +03/03/2007 +namespace +(THIS IS A TRANSITIONAL REVISION, DO NOT USE, WAIT FOR UPDATED DEPS) +- Fixed weird msvcrt bugs caused by dbghelp.dll +- Activated new radiant icon on Linux (Topsun) +- First update for installerfiles (Shaderman) +- New MODT for q3map2 :) +- tiny changes + +28/02/2007 +namespace +- Radiant is now Vista compatible (Aero must be disabled) +- Updated all radiant dependencies (gtk is now 2-2.10) + New dependency packages can be found here +http://zerowing.idsoftware.com/files/radiant/developer/1.5/gtkradiant-1.5-dependencies-1.0.zip + or here +http://www.codecreator.net/radiant/gtkradiant-1.5-dependencies-1.0.zip +- Removed Win32 filechooser + - Fixes crashes caused by utf-8 encoded filenames + - Fixes Vista "SaveAs"-Bug +- Converted all projects to Visual Studio 2005 +- Codefixes for vc2k5 compiler +- Removed old unused projectfiles for Visual Studio 6 +- Simplified dependency filestructure +- Simplified install.py +- Updated CONTRIBUTORS list + +26/01/2007 +namespace +- SnapPlane reenabled by namespace because of multiple reports of + q3map2-crashes which were triggered by this patch. + +24/01/2007 +namespace +- Added Undo/Redo-Toolbarbuttons (Shaderman) +- Fixed Bug #1078 (sogined) + +18/01/2007 +namespace +- Weird shiftvalues are now hidden to the user, shiftvalues will + stay in ]-shader_size;shader_size[ range for display. + For the sake of floatingpoint precision, it would be nice + to keep the internal values in that range too, but thats "nice to have". +- Fixed missing librarypaths in ufoai-plugin + +18/01/2007 +LordHavoc +- disabled SnapPlane because it slightly corrupts collision brushes from + embedded model triangles + +17/01/2007 +namespace +- Fixed "jumping" texcoords when switching to a shader with different size +- (Todo) Toolbar: add button for refresh-models. (Shaderman) +- Aniso fix (Shaderman) +- Translucency fix (Shaderman) + +05/01/2007 +namespace +- UFO:Alien Invasion Plugin (mattn2) +- Fix: Filtering models in ET doesn't filter misc_gamemodels (Shaderman) + +25/12/2006 +namespace + - Brushplugin Version 2.0, supports multiple collapse modes and a materialignore list (namespace) + - Camera movement speed changes. Increase speed = SHIFT+KP_PLUS, decrease speed = SHIFT+KP_MINUS. + New option to link the strafe speed to camera movement speed (default: linked). (Shaderman) + - Fixed bug in sample plugin (Shaderman) + - Merry Christmas + +17/12/2006 +namespace + - Radiant warzow-support (Topsun + Warzow-Mappers) + +15/12/2006 +namespace + - Inverted bevel can be used with end caps (Topsun) + - Removed needless .pdb files from win32 installer (Topsun) + - Fixed .ase picomodel loader to load shaders again (Shaderman) + - Removed translucent brushes fix because of wrong rendering behaviour on Linux + - New cool(!) radiant icon by eb + +13/12/2006 +namespace +- Added tremoulus-support to q3map2 + +08/12/2006 +namespace +- Entity names are now drawn for group entities in Doom3 and Quake 3 mode (namespace) +- Fixed translucent brushes becoming invisible when selected (Shaderman) + +06/11/2006 +namespace +- (TODO) Texture sizes sometimes vary wildly. New texture browser option: View -> Fixed Size. + Code parts taken from DarkRadiant. (Shaderman) +- Added new texture browser option: View -> Hide Image Missing. (Shaderman) +- Fixed new D3 lights don't have a light_radius key. (Shaderman) +- Fixed hotkey collision (paste to camera/view menu). New View shortcut = ALT+W. (Shaderman) +- Fixed .wad MMB 3d view texture selection bug. (Shaderman) + +04/11/2006 +namespace +- Added warzow-support to q3map2 + +12/10/2006 +namespace +- Updated help menu web links (removed map-center.com, added ETB documentation) (Shaderman) +- Added check for existing worldspawn if a new one should be added with the entity menu (Shaderman) +- Fixed Entity -> Ungroup (Topsun) +- Fixed hotkey collision ALT+M (filter botclip/Modify menu). New Modify shortcut = ALT+O (Shaderman) +- Updated Window Layout images (Shaderman) +- Fixed (TODO) XYWindow: save show-workzone option (Shaderman) +- Fixed (TODO) Toolbar: add shortcut to tooltips for toolbar buttons (Shaderman) +- Fixed (TODO) GUI: detachable submenus (Shaderman) + +09/10/2006 +namespace +- Added option to toggle the camera window stats on/off (Shaderman) + (view --> show --> show stats) + + +08/10/2006 +namespace +- Fix for bug 1106 - .wad files don't get listed in the textures menu (Shaderman) + (http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=1106) +- Added initial support for Prey (Shaderman) +- Fixed entity inspector sliders to always show (Shaderman) +- Added NOTES subsection to the entity inspector listing .def "editor_usage*" values (Shaderman) + +07/10/2006 +namespace +- Added "select inside" and "select touching" + Both functions now work with multiple selectionbrushes, allowing complex + selection operations. +- Added entries for the selectionfunctions in "Edit" and the main toolbar. + +06/10/2006 +namespace +- Changed ETB not to show any texture if a tag search doesn't match anything (Shaderman) +- Added View -> Filter -> "Invert filters/Reset filters" function (Shaderman) +- Fixed (TODO) Textures: remove "shaders only" option for doom3 (Shaderman) +- Fixed show shaders / shader only / shader info for doom3 and .wad file games (Shaderman) + +03/10/2006 +namespace +- Fix for Bug 1113 (Topsun) + (http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=1113) + +03/10/2006 +namespace +- Changed ETB tag toolbar to a notebook with tag/texture pages (Shaderman) +- Added a context menu (add/delete/rename tag) to the ETB tag tree view (Shaderman) +- Added new win32 installer HOWTO (Shaderman/Topsun) + +01/10/2006 +namespace +- Added missing xml files for win32 installer + +30/09/2006 +namespace +- Fixed Etb saving custom tags to global tagfile (Shaderman/namespace) +- Fixed postbuild step for brushexport, shaderplug and sunplug (Shaderman) +- Added installersupport for new files and plugins (Shaderman) + +30/09/2006 +namespace +- Added LocalLcPath to plugin interface +- Added enhanced Texturebrowser (ETB) by Shaderman +- Added shaderplug by Shaderman, accompanies the ETB +- Added xmltagging lib by Shaderman, accompanies the ETB +- Added new icons for ETB, Console, Entityinspector and Lighteditor +- Fixed minor warning in Sunplug Project +- Fixed PATH_MAX namecollision in ptrview plugin on Linux +- Final fix for 64 bit patch.h issue + + + +12/09/2006 +namespace +- Fixed 64 Bit issue in patch.h, see http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=1109 + for details + +11/09/2006 +namespace +- Fixed unnecessary warnings, see http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=1108 + for details + +11/09/2006 +namespace +- Added sunplug (Mapcoordinator-plugin for ET) by Topsun + ET only: setting mapcoordsmins and mapcoordsmaxs in the worldspawn + Tester: Shaderman +- Added brushexport Plugin by namespace + Exports selected brushes as wavefront object. + Tester: Shaderman + +11/09/2006 +namespace +- Fixed compile error on x86_64, see http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=1105 + for details. + +09/09/2996 +namespace +- Added strafing for the camerawindow while holding ctrl +- Additional forward movement can be enabled by pressing shift during strafe + +22/08/2006 +SPoG +- Added VFS support for locating the archive a file was loaded from. +- Changed Doom3 entity definition parser to gracefully handle parse errors. +- Fixed crash when loading entity definitions in quake4 1.3 point release. + +13/08/2006 +SPoG +- Disabled 'detail' content flag checkbox in quake2 Surface Inspector. + +22/07/2006 +SPoG +- Fixed doom3 func_static with model not appearing to move when dragged. +- Changed ASE model loader to parse material names the same way as DoomEdit. +- Fixed title of wait-dialog when loading a model. +- Fixed doom3 func_static with blank 'model' key being invisible. +- Changed doom3 func_static model creation to replace selected models. +- Added support for loading both .ent and .def files at the same time. + +09/07/2006 +Shaderman +- Updated win32 libxml2 package to 2.6.24. +namespace +- Ported win32 stack-backtrace to use new DebugHelp API. +- Added stack-backtrace functionality for Linux/OSX. + +11/06/2006 +SPoG +- Fixed arbitrary rotation X and Y axes appearing to be transposed. + +04/06/2006 +SPoG +- Fixed crash when deleting items from Build menu. + +03/06/2006 +SPoG +- Changed doom3 entity creation to add model key for brush-entities. +- Fixed crash in Save Region. +- Fixed detail brushes being turned structural when texture is set. + +29/05/2006 +SPoG +- Changed default doom3 light_radius to be taken from the entity-definition. +- Fixed error when entering and exiting camera freemove with LMB pressed. + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=1090 + +13/05/2006 +LordHavoc +- Added -fPIC for Linux builds to support x86_64. +- Removed static-linked libstdc++ hack for non-final builds. +- Removed gcc explicit linking hack for non-final builds. +- Added workaround for gcc static-member-pointer-as-template-argument bug. +- Fixed bobtoolz linux compile errors. +- Added Nexuiz and Darkplaces support. +SPoG +- Changed 'inconsistent vertex count' assert to be non-fatal. +- Changed 'failed to remove accelerator' assert to be non-fatal. +- Fixed crash due to broken module dependencies. +- Fixed default settings for Build Monitoring. +- Fixed nudge-selection when using the drag tool mode. +- Added support for patches in texture-painting tool. + +30/04/2006 +SPoG +- Fixed memory leak in signals library. +- Ported bobtoolz to use new module APIs. + +01/04/2006 +SPoG +- Added key-observer interface to entity module API. +- Rewrote callback library to add support for return-values. +- Added minimal API to support bobtoolz plugin. +- Refactored brush for-each functions to take Functor instead of Visitor. +- Added signals library. + +31/03/2006 +SPoG +- Changed doom3 light creation to use size of selected brushes. + +20/03/2006 +SPoG +- Fixed crash when resetting preferences after startup failure. +- Fixed crash on next-leak-spot with build-monitoring enabled. +- Fixed doom3/quake4 blended-decal rendering when toggling lighting mode. +- Fixed fit-texture with rotated texture transforms. + +20/03/2006 +SPoG +- Changed Copy/Paste to work on face textures if any faces are selected. +- Fixed GTK_WIDGET_REALIZED assert when selecting entities (linux). + +28/02/2006 +namespace, SPoG +- Added drag-resizing for doom3/quake4 light_radius boxes. + +25/02/2006 +SPoG +- Fixed objects sometimes dissappearing when at high zoom level in Ortho views. + +24/02/2006 +SPoG +- Added GPL text to win32 installer. +- Fixed gtk wimp theme not working after using gtkthemeselector. + +21/02/2006 +namespace +- Added command to toggle lighting/textured modes, shortcut F3. + +20/02/2006 +SPoG +- Fixed crash when disabling lighting for a second time. + +19/02/2006 +SPoG +- Fixed crash when loading invalid ASE models. + +11/02/2006 +SPoG +- Added install.py script. +- Updated COMPILING instructions. +- Fixed transparency rendering on quake3 shaders. +- Fixed hint/caulk filtering for Jedi Academy shaders. + +04/02/2006 +SPoG +- Added Radiant Manual shortcut to win32 installation. +- Added 'use alternative texture projection' option to prefs. +- Fixed caching bug causing import-map to not reload modified maps. +- Added blended-decal rendering for doom3 and quake4. + +30/01/2006 +SPoG +- Added expand-selection-to-entities with default shortcut ctrl+alt+e. +- Fixed crash in gtk file-chooser with some versions of GTK+. + +29/01/2006 +SPoG +- Changed create-group-entity to re-select brushes after creation. + +26/01/2006 +SPoG +- Changed far-clip-plane setting to increase exponentially with distance. +- Fixed selected entities moving when drag-resizing brushes. +- Changed patches to be selectable from the back in wireframe views. +- Speed improvements for transform-selection code. + +25/01/2006 +SPoG +- Added default-texture-scale option to preferences. +- Added default-grid-spacing option to preferences. + +23/01/2006 +Shamus +- Fixed editing of doom3/quake4 faces with negative texture scale factors. + +22/01/2006 +SPoG +- Fixed shortcut for toggle-far-clip. +- Fixed broken link in 1.5 user docs. + +10/01/2006 +SPoG +- Changed script parser to ignore comments that start in the middle of tokens. +- Added support for editor_int/editor_string keys in doom3 entity definitions. +- Changed doom3/quake4 .mtr and .guide searching to look in subfolders. +- Added experimental support for doom3/quake4 projected lights. + +20/12/2005 +SPoG +- Added support for quake4 .guide material templates. +- Fixed GCC4 compile errors with anonymous enums. +- Added light_radius and light_center GUI to doom3 entity inspector. + +10/12/2005 +SPoG +- Added support for loading and saving Quake4 map format. +- Added support for built-in shader images such as _noFalloff. +- Added experimental code to render projected lights. +- Added support for loading quake4 entity definitions. +- Fixed picomodel bug loading .lwo files with texture names >64 in length. + +22/10/2005 +SPoG +- Fixed minor memory leak in zip-archive reader. + +21/10/2005 +SPoG +- Fixed model rotation using free-rotation tool. +- Fixed behaviour of angle/rotation keys for Doom3 entities. +- Added entity angle-editing for entities that display a model. +- Stopped size-info being drawn if selection has zero size. +- Added support for 'angle' key on doom3 entities. + +20/10/2005 +SPoG +- Fixed minor one-time memory leak in build-shader-list. +- Added functional free-scale tool. +- Improved precision preservation for free-rotation and free-scale tools. +- Fixed misc_model rotation using free-rotation tool. + +09/10/2005 +SPoG +- Added entity angle-editing using rotate-tool or rotation toolbar buttons. +- Fixed start-on-primary-monitor (win32). +- Fixed camera-view freelook when window is on secondary monitor (win32). + +05/10/2005 +paxed +- Added texture-lock toolbar button. +SPoG +- Added stack-trace output to all debug popup messages (win32). +- Fixed vertex/edge selection behaviour on undoing brush edits. +- Changed doom3 light centre rendering to use a point instead of a box. +- Changed map and shader parsers to gracefully handle bad data. + +24/09/2005 +SPoG +- Fixed crash when importing a file after deleting the world entity. +- Changed copy/paste/import to select world brushes instead of world entity. +- Added win32-specific stack-trace logging support. + +31/08/2005 +SPoG +- Fixed clipper removing brushes on incorrect side of clip-plane when flipped. + +21/08/2005 +SPoG +- Optimised light-dragging. +- Changed connect-entities to not break existing connections where possible. + +24/07/2005 +SPoG +- Fixed q3 entity definition for item_health_small. + +02/07/2005 +SPoG +- Fixed build menu becoming empty if editing of default menu is cancelled. + +01/07/2005 +SPoG +- Fixed snap-planes-to-integer preference not being saved. + +26/06/2005 +SPoG +- Changed doom3 light_radius box to draw flat-shaded translucent in camera view. +- Fixed progress window not being shown when loading maps. +- Refactored shortcut handling to make focussed widgets override global shortcuts. + Allows using ctrl+c to copy console text, undo/redo without main-window focus. + +23/06/2005 +SPoG +- Fixed crash when handling assert/error before gtk_init. +- Changed Help menu to display only the help menu relevant to the current game. + +21/06/2005 +SPoG +- Fixed double-maps-directory when using non-native file open dialog on win32. +- Changed find/replace-textures window to update when selecting textures with MMB. +- Fixed handling of non-UTF-8 wad-names in textures menu. + +20/06/2005 +SPoG +- Changed home-path prefix to be optional on linux/osx for q1/q2. +- Changed OpenGL module to work correctly with GL_VERSION 2.0.0. +- Fixed spawnflags in Enemy Territory entity definitions. +- Added missing Enemy Territory entity types. + +19/06/2005 +SPoG +- Changed clipper-point selection/rendering to behave the same at all zoom levels. +- Changed clipper-point selection to always pick the closest point to the cursor. +- Fixed clipper colour not being updated until after restarting. +- Fixed QE tool not deselecting edges when left-clicking on a vertex without shift. +- Fixed grid rendering with grid settings other than 8-unit. +- Fixed textures menu always behaving as if show-shaderlist-only is enabled. + +18/06/2005 +SPoG +- Added saving of camera render mode preference. +- Changed default camera render mode to Textured for Doom3. +- Changed default texture render mode to Trilinear. + +17/06/2005 +SPoG +- Ported Team Arena entity definitions to xml format. +- Added xml q3 entity-definitions to win32 and linux setups. + +14/06/2005 +SPoG +- Fixed QE-tool component-drag selecting components of unselected brushes. + +13/06/2005 +SPoG +- Changed clipper mode and component-editing modes to be mutually exclusive. +- Changed camera A/Z/D/C keys to obey "discrete movement" checkbox. +- Added support for up/down movement in freelook mode. +- Fixed clipper-tool-uses-caulk option. +- Added prompt for engine path at startup if not found. + +12/06/2005 +SPoG +- Added entity-inspector GUI support for func_button/func_door direction attribute. +- Added angle/model/sound GUI to entity-inspector for old-style entity-definitions. +- Added clipper-plane rendering using stippled back-face-culled polygon. +- Fixed entity-inspector failing to handle non-ascii characters in entity keys. + +11/06/2005 +SPoG +- Fixed entity-inspector window position changing while hidden. + +09/06/2005 +SPoG +- Fixed region-set-selected for model entities. + +08/06/2005 +SPoG +- Added option to enable or disable snapping brush planes to integer on map load/save. +- Added 0.125 grid. +- Changed grid rendering to always show major/minor grid colours whatever the zoom level. +- Increased maximum zoom level for XY views. + +06/06/2005 +SPoG +- Added 'File > Refresh models' - reloads models that have changed outside Radiant. + +05/06/2005 +SPoG +- Fixed model files not being closed after being read. +- Fixed build menu dialog not saving changes to build commands. +- Changed build menu to save xml only if changed by user. +- Fixed CSG-merge. +- Reduced CSG-merge tolerance for misaligned brushes. +Michael Kluskens +- Updated STVEF default build menu to version 2 format. +- Updated STVEF game configuration file and removed synapse.config. + +02/04/2005 +SPoG +- Changed HashTable insert/remove operations to not invalidate iterators. +- Changed ReferenceCache realise/unrealise to take advantage of new HashTable behaviour. +- Refactored containers to use std::swap. +- Refactored bitfield.h. +Tr3B +- Fixed endianness for .lwo loading on linux. +- Extended q3map2 mathlib functionality. + +28/03/2005 +SPoG +- Specialised filters list for doom3 entity/material types. +- Added ASE-loader support for shader names specified as a bitmap absolute-path. +- Fixed monitored-compile debug-feedback display. +- Added TGA-loader support for vertical and horizontal flipping. +- Enabled auto-generation of smooth normals for all LWO models. + +27/03/2005 +Tr3B +- Fixed map-xml module. +- Added support for qer_trans keyword in doom3 materials. + +08/03/2005 +SPoG +- Added GUI updates during map load and engine-path/game-dir changes. +- Fixed main window being hidden behind another application on closing a floating window. + +07/03/2005 +SPoG +- Fixed crash when selecting the root node in the entity-list window. +- Fixed detail flag saving for quake3 map format. + +06/03/2005 +SPoG +- Optimised type-casts for scene-graph type system. +- Disabled substitution of 'unspecified' for entity key values that match the default. +- Refactored module-system api - added globalModuleServer(). +- Fixed rotation origin for doom3 func_static models. + +05/03/2005 +SPoG +- Added support for doom3 model-entity 'skin' key. +- Added noshadows/nospecular/nodiffuse/falloff doom3 light keys to entity-inspector. + +04/03/2005 +SPoG +- Added camera move up/down one floor. +- Added support for 'unspecified' contents/flags/value for Quake2 brush faces. + +26/02/2005 +SPoG +- Added auto-complete for shader attributes in entity inspector. +- Added support for doom3-material heightmap to normalmap conversion. + +22/02/2005 +SPoG +- Fixed missing prefix attribute in quake game configuration file. +- Added auto-completion for find/replace-textures texture-name entries. +- Removed redundant buttons and options in find/replace-textures window. + +21/02/2005 +SPoG +- Added auto-completion for surface-inspector texture-name entry. + +20/02/2005 +SPoG +- Fixed failure to load shaders containing '.' such as Heretic II model skins. +- Removed unused legacy cmdlib functions. +- Added support for fractional repeat values to brush-fit-texture. + +19/02/2005 +SPoG +- Changed entity-connect-selected to use the parent entity of a selected brush. +- Added q2map and bspc to installation packages. +Jamie Wilkinson +- Fixed path to q2map in quake2 build-menu. + +13/02/2005 +SPoG +- Fixed crash in 'Misc > Find Brush' when searching for a non-existent brush. + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=1059 +- Limited the maximum number of sides of a prefab brush-cone to 32. + +11/02/2005 +SPoG +- Changed display of angled box-entities to show un-rotated bounding boxes. + +10/02/2005 +SPoG +- Disabled rotation of light entities for games other than Doom 3. + +09/02/2005 +David Constanzo +- Fixed episode flag names on Quake item_sigil and func_episodegate entities. + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=1056 +- Added detailed description of Quake item_sigil and func_episodegate entities. +SPoG +- Fixed position of light-entity names. +- Fixed q3map2 feedback xml streams containing non-UTF-8 text. +- Optimised Locale<->UTF-8 text conversion. + +08/02/2005 +SPoG +- Fixed selecting a texture not resetting the current selected texture-projection. +- Added handling of OpenGL drivers that don't implement all reported extensions. + +07/02/2005 +SPoG +- Added support for multi-byte UTF-8 characters in xml content (not markup). +- Added UTF-8 support in entity-inspector, prefs dialog, recent-files menu, console. +- Fixed potentially misaligned objects. + +05/02/2005 +SPoG +- Added rendering of entity names for non-group entities. +- Removed context-dependent opengl calls before opengl context is created. + +04/02/2005 +SPoG +- Fixed crash when printing extended-ascii text in the console. +- Added conversion of extended-ascii text to utf8 for gtk text-buffer widgets. + +03/02/2005 +David Constanzo +- Fixed typo in Quake entity-definitions. +SPoG +- Fixed undo of brush-set-structural and brush-make-structural. +- Refactored image-loader module api. +- Added full support for Quake II and Heretic II surface-flags in surface-inspector. +- Fixed crash in patch-inspector for games other than doom3. + +01/02/2005 +SPoG +- Changed shader-list and entity-definition searching to also search base-dir. + +29/01/2005 +SPoG +- Fixed title of floating entities/console/textures window. +- Fixed destruction of floating windows on shutdown. +- Added shortcut handling when entity-inspector/texture-browser are active. +David Constanzo +- Fixed crash while auto-saving snapshots. + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=1049 + +28/01/2005 +SPoG +- Added rendering of fixed S/T tesselation on doom3 patches. +- Changed creation of doom3 func_static entities to prompt for a model. +- Fixed zero-sized primitives being rendered for culled entity-connection-lines. + +25/01/2005 +SPoG +- Added support for setting fixed S/T tesselation on doom3 patches. +- Changed patch inspector to be completely non-modal. + +24/01/2005 +SPoG +- Added support for rotating lights using the rotation tool. + +23/01/2005 +SPoG +- Fixed crash on startup with texture-subsets enabled. + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=1045 +- Added support for doom3 lights combined with brushes and patches. +- Added support for doom3 light_origin on lights. +- Added support for doom3 light_rotation and rotation key on lights. + +22/01/2005 +SPoG +- Fixed crash when accessing links from help menu. +- Fixed broken links in help menu. +- Added game-specific docs to win32 setup. +- Changed q3 connect-entities to preserve existing targetname key. +- Changed q3 connect-entities to generate targetname key based on target classname. + +21/01/2005 +SPoG +- Moved information in synapse.config into .game files. +- Added api versioning for module-system. + +20/01/2005 +SPoG +- Fixed keys/values for brush entities not being editable unless parent is selected. +- Fixed brush-entities not being deleted when the last child brush is deleted. +- Fixed scenarios where worldspawn is not the first entity in the map. + +19/01/2005 +SPoG +- Fixed numerical instability in brush winding-generation. + +16/01/2005 +SPoG +- Added Jedi Academy support to linux installer. +- Fixed main-window jumping to front during autosave. +- Fixed saving of empty brushes. +Jame Wilkinson +- Fixed build errors when compiling for big-endian target. + +15/01/2005 +SPoG +- Fixed launching q3map2 from build-menu on linux/osx. +- Added version checking of generated config files. +- Fixed key-up events being missed while holding ctrl/shift/alt. +- Fixed texture-compression support. +Jago +- Changed q1 trigger_relay and trigger_counter to be point entities. + +11/01/2005 +SPoG +- Changed brush-save to not write non-contributing faces. +- Fixed rendering artifacts on outlines of selected box-entities. + +10/01/2005 +SPoG +- Added patch-cap-cylinder. + +09/01/2005 +SPoG +- Fixed texture-browser scroll-to-selected-texture. +- Added brush face texture painting. + +08/01/2005 +SPoG +- Fixed ungroup-selected-entities. +- Added 'Entity' menu. +- Removed items unrelated to entity-creation from xy-window right-click menu. +- Fixed minor memory leaks in core. +- Removed code that ignores empty alpha-channel in tga loader. +- Fixed memory leak in model module. +- Fixed memory leaks in picomodel library: ase and lwo loaders. +- Changed default tool to 'QE' tool. + +04/01/2005 +SPoG +- Changed start-on-primary-screen default to 'true' with multiple screens. +- Added enter/escape as shortcuts for OK/Cancel buttons on modal dialogs. +- Disabled mouse-chase scrolling in XY window when drag-selecting. +- Fixed xlink I/O warnings on linux. +- Removed light-creation 'intensity' dialog in doom3 config. +- Changed the default vertical size of the shortcut-list window to 400. +- Fixed patch-creation with zero-sized workzone. +- Fixed crash when rendering doom3 light_radius box. + +03/01/2005 +SPoG +- Added arbfp1/arbvp1 vertex/fragment program lighting support. +- Fixed colour-selection dialog not going away after OK/Cancel. +- Fixed tangent/bitangent calculation on degenerate patches. +- Added resizing of patches with QE tool. + +02/01/2005 +SPoG +- Added stippled hidden-line rendering for rotate/translate manipulators. +- Refactored high-level camera/orthographic rendering functions. +- Fixed failure to load models with back-slashes in path. +- Improved peformance of doom3 lighting with large models. + +01/01/2005 +SPoG +- Changed entity renderering to draw connections for culled entities. +- Fixed runtime errors on shutdown after cycling between lighting/textured. +- Fixed 'Cancel' button in modal dialogs behaving like 'OK' button. +- Changed 'clip' brush filtering to use shader parameters instead of names. +- Added rendering of origin-point for selected model entities. +- Fixed doom3 light-interaction updates for model entities. + +31/12/2004 +SPoG +- Removed menu-splitting functionality. +- Refactored entity-class menu construction. +- Replaced const_reference with ConstReference. +- Renamed string_t to CopiedString. +- Changed code using Str::Format and Str::operator+= to use StringOutputStream. +- Refactored shortcut command parsing code. +- Changed light entity rendering to show light_radius box only in doom3 config. +- Added 2x overbrightening for doom3 lighting preview. + +16/12/2004 +SPoG +- Changed default doom3 light_radius to 300. +- Fixed initial value of doom3 light_center. +- Reduced frequency of gui updates during map load. + +13/12/2004 +SPoG +- Fixed crash in monitored-compile feedback dialog. + +12/12/2004 +SPoG +- Added visualisation of halflife texture-projection format. +- Added texture-lock for halflife texture-projection format. +- Changed brush-texture-lock default to enabled for doom3 and halflife. + +11/12/2004 +SPoG +- Refactored script-tokeniser. +- Fixed doom3 lighting on ATI cards. +- Added support for list attributes in entity definitions. +- Added utf8-normalisation on text printed to console. +- Fixed missing default_build_menu.xml in halflife game config. + +10/12/2004 +SPoG +- Fixed missing commas in entity-inspector comments. +- Added support for default values in entity-definitions. + +09/12/2004 +SPoG +- Enabled doom3 realtime lighting preview. + +05/12/2004 +SPoG +- Fixed gcc compile warnings in patch.cpp. +- Added q1 and q2 support to linux installer. + +02/12/2004 +SPoG +- Added highlighting of entity-list nodes that have selected descendants. + +29/11/2004 +SPoG +- Fixed xy-window failing to update after map-load. + +28/11/2004 +SPoG +- Fixed error on shutdown when game-select dialog is disabled. +- Added surface/light-interaction debugging mode. +- Added per-plane light-culling for brush faces. +- Added support for bump shaders using 'blend' layer keyword. +- Added support for light-shaders. +- Fixed edge/border clamping on light-attenuation textures. +- Added 'Lighting' camera render-mode for doom3 config. + +25/11/2004 +SPoG +- Fixed right-click-menu on linux. + +13/11/2004 +SPoG +- Changed light-entity to draw radii only when selected. + +07/11/2004 +SPoG +- Fixed shortcuts for creating n-sided brush-prisms. + +19/10/2003 +SPoG +- Fixed user-customisable shortcuts support. + +16/10/2003 +SPoG +- Added restore-main-window call when a modal-dialog dialog-box is shown. + +13/10/2003 +Jamie Wilkinson +- Fixed SConstruct cpu detection on Power Macintosh. +- Added more verbose warning settings for gcc builds. +SPoG +- Changed buffered-text-input-stream API to not use magic EOF value. + +06/10/2004 +Jamie Wilkinson +- Fixed debug-break on non-x86 platforms. + +05/10/2004 +SPoG +- Fixed new doom3 entities not being given unique names. + +02/10/2004 +SPoG +- Fixed autosave causing the main-window to jump to the front. +- Fixed window-title when changing view direction in floating-windows layout. +- Added status information to wait dialogs. +- Changed map-save to overwrite/refresh files modified/deleted outside radiant. +- Changed component selection in QE tool to treat near-equal points as one. +- Added texture browser window to View menu. +- Changed white overlay in camera-window to draw hidden lines in 75% grey. +- Reduced point-size for vertex/edge/face handles by 2/3. + +25/09/2004 +SPoG +- Added component-editing for curves on doom3 entities. +- Added normalisation of translation for texture-lock on doom3 brushes. +- Added update of surface inspector for texdef changes caused by texture-lock. +- Changed floating-windows layout to centre all ortho views on center-xy-views. +- Stopped autosave from saving unmodified maps after first-time load. +Shamus +- Textool improvements. + +21/09/2004 +Michael Schlueter +- Changed file-chooser dialogs to use GtkFileChooser. + +20/09/2004 +SPoG +- Added basic rendering for 'curve_CatmullRomSpline' on doom3 entities. + +19/09/2004 +SPoG +- Added basic rendering for 'curve_Nurbs' on doom3 entities. + +18/09/2004 +SPoG +- Added doom3 entity-definition editor_usage* key support. +- Added doom3 entity-definition editor_* key info to entity-inspector comments. +- Added specialised attribute-entry in entity-inspector for boolean attributes. +- Fixed crash in find-brush when entering the index of a fixed-size entity. +- Added support for rendering doom3 'targetN' -> 'name' entity connections. +- Changed connect-entities to use 'targetN' and 'name' keys on doom3 entities. +- Improved handling of brushes with near-duplicate planes. +- Fixed crash in snap-to-grid on brushes with non-contributing faces. + +17/09/2004 +Michael Schlueter +- Fixed gcc build errors. +SPoG +- Fixed stability problems with doom3 brush-entities. + +16/09/2004 +SPoG +- Added automatic renaming of entity target/name keys for paste and import-map. + +13/09/2004 +SPoG +- Fixed rotation/mirror toolbar buttons using wrong axes in certain situations. +- Fixed incorrect filter settings display in filters menu. +- Added automatic renaming of entity target/name keys when cloned. +- Fixed quake2 support. + +12/09/2004 +SPoG +- Added shortcut support for all menu commands and toolbar buttons. +- Fixed patch shader-name saving for doom3 patches created by user. + +10/09/2004 +SPoG +- Fixed patch-cap-endcap and patch-cap-reverse-endcap. + +09/09/2004 +SPoG +- Fixed status-bar labels showing 'Label' at startup. +- Fixed textures-menu grouping bug. +- Fixed mirroring on brushes with doom3 plane-defs. + +08/09/2004 +SPoG +- Changed patch-cap-selected to prompt for cap type only once. +- Fixed infinite-loop in patch-cap-selected. +- Changed autosave to only save if the map has changed since the last autosave. +- Changed "file exists, overwrite?" to go back to the file-chooser if 'no' is selected. + +07/09/2004 +Michael Schlueter +- Removed unused source files - qsysprintf, gtkfilesel. +- Changed linux exectuable-path resolution to use '/proc/self/exe'. +- Changed 'file exists, overwrite?' prompt to default to 'no'. + +06/09/2004 +SPoG +- Fixed crash when handling duplicate shader definitions. +- Fixed crash on startup after changing game-type (sp/mp). +- Changed compute-axis-base to be more stable in degenerate cases. +Michael Schlueter +- Cleaned up loki_path stuff. +Shamus +- Added textool rotation manipulator. + +01/09/2004 +Michael Schlueter +- Changed linux installer to copy *.game files from game-packs instead of generating them. + +29/08/2004 +SPoG +- Moved doom3 shader parsing into a separate function. +- Fixed saving of visibility of yz-side and xz-front in floating-windows layout. +- Added inheritance of doom3 entityDef 'editor_* *' keys. +- Added support for doom3 entityDef 'editor_* *' keys other than 'editor_var'. +- Changed entity-inspector attributes panel to be scrollable. +- Changed shaders module to load *.mtr for doom3 instead of using shaderlist. +- Improved doom3 material parsing. +- Fixed crash loading maps containing patches with large numbers of control points. +- Changed face-selection in camera view to use polygon test. +- Changed face-selection-mode rendering in camera view to cull back-faces. +- Changed shaders module to use diffusemap on doom3 shaders with no qer_editorimage. + +28/08/2004 +SPoG +- Fixed undefined-behaviour bug causing a crash with gcc optimisation. +- Fixed opengl extension support on linux. +- Added support for half-life .fgd entity-definitions. +- Fixed loading of half-life maps with texture-names containing '{'. + +26/08/2004 +SPoG +- Updated win32 install to use gtk+-2.4.7 and gtk-wimp-0.6.2. +- Removed 'unknown model type: ""' and 'Texture load failed: ""' errors. +- Changed pivot-point calculation to use entity-origin for fixed-size entities. +- Fixed errors drawing selection-rectangle in floating-windows layout. +- Fixed shortcuts becoming disabled in floating-windows layout. +- Removed snap-to-integer on entity angle/angles/scale keys. +- Fixed dragging of doom3 brush-entities. +Michael Schlueter +- Fixed gcc compiler error and warnings. +- linux installer fixes. + +25/08/2004 +Michael Schlueter +- Changed linux build system to copy '$GAME.game/*' for installer. +- Changed linux installer to use 1.5's module set. +- Removed optimisation from linux build to fix optimiser-related crash. +- Added Doom3 support to linux installer. +SPoG +- Changed texdef export to not snap shift/rotate to integer. + +17/08/2004 +SPoG +- Fixed texture-browser height update bug. +- Changed texture-browser to show only the texture-set selected from the menu. +- Fixed bug loading quake maps. + +16/08/2004 +SPoG +- Fixed shortcuts for shift/rotate/scale-texture on patches. +- Fixed patch-inspector rows/cols combos not being cleared between selections. +- Changed entity/brush counts to use counters instead of polling every second. +- Changed archivewad module to check lump types and ignore non-miptex files. +- Added support for worldcraft/hammer half-life map format. + +15/08/2004 +SPoG +- Added doom3 md5mesh loader. +- Added support for doom3 entityDef 'model' key. +- Changed textures-menu to show/load only shaders in the form 'textures//*'. +- Fixed texture-browser height calculation bug. +- Added support for doom3 entityDef 'editor_var *' key. +- Fixed support for patchDef3 subdivision settings. + +14/08/2004 +Chronos +- Added doom3 light_radius and light_center rendering. +SPoG +- Disabled default-shaders for doom3. +- Added one-click dragging of vertices/edges/faces on brushes and patches. +- Fixed detail flag being lost on brush-clone or save-as. + +12/08/2004 +SPoG +- Fixed workzone being invalidated when a brush is deleted. +- Fixed pivot updates when switching from translate-tool to rotate-tool. +- Fixed crash in misc_model. +- Changed file_dialog to show '*.*' when pattern is not specified. +- Added undo support for patch-inspector XYZUV tweaking. +- Removed 'Apply'/'Done' buttons from patch-inspector. + +11/08/2004 +redfella +- Converted RTCW entities.def to xml format. +SPoG +- Added support for "name" key for doom3 entities. + +10/08/2004 +SPoG +- Added support for loading text files from VFS archives. + +09/08/2004 +SPoG +- Changed .map writer to always use decimal notation for floats. +- Added filtering of monster_clip/full_clip/player_clip. +- Added support for inheritance in doom3 entity-definitions. +- Fixed doom3 patchDef2 material-name import/export. + +06/08/2004 +SPoG +- Doom3 support: + - Map load/save. + - Materials parsing. + - entityDef parsing. + - Fixed brush-primitives texturing bug. + - Added texture-lock for brush-primitives texturing on all transforms. + - Added support for model/rotation/origin keys on func_ entities. +- Added DDS image module. + +03/08/2004 +SPoG +- Fixed copy/paste copying all brushes of an entity when only one is selected. +- Fixed discrete-camera-movement shortcuts becoming disabled after using freemove-camera. + +27/07/2004 +Shallow +- Converted ET entities.def to xml format. + +26/07/2004 +SPoG +- Fixed SI when not in face-selection mode. +- Changed patch-create-prefab to use current workzone. +- Changed engine attribute to be optional in game-description. + +24/07/2004 +SPoG +- Changed module server to support wildcard modules. +- Cleaned up docs/developer, added README. +- Removed old TODO. + +23/07/2004 +Shamus +- Initial version of textool. +MindLink +- Fixed const bug for vs.net 2005 compiler. + +22/07/2004 +SPoG +- Fixed copy/paste and export-selected failing to export stuff. + +19/07/2004 +SPoG +- Updated win32 setup scripts for q1 entity definitions. + +18/07/2004 +SPoG +- Updated win32 setup scripts. +- Fixed bug loading 8-bit bmp with <256 palette entries. +- Added support for loading textures from arbitrary locations. +- Changed shaders module to load notex/shadernotex from bitmaps/ in .bmp format. +- Fixed bugs in brush-primitives texdef code. + +17/07/2004 +SPoG +- Added STV:EF support to win32 installer. +- Moved hardcoded game-specific configuration stuff into .game files. + +16/07/2004 +SPoG +- Fixed crash bug in brush edge/vertex/face selection. + +15/07/2004 +SPoG +- Moved .def files from //scripts/ to ///. +- Changed entity inspector UI to be driven by entities xml. + +14/07/2004 +SPoG +- Fixed map load/save in brusb-primitives mode. +- Added files required to run in brush-primitives mode. +- Added support for changing engine-path while a map is loaded. + + +11/07/2004 +SPoG +- Changed entity inspector to support custom UI for each entity-class. + +10/07/2004 +SPoG +- Added support for experimental xml version of .def format. +- Rewrote token parser. +- Rewrote .def parser. +- Fixed failure to read show-workzone preference. +- Refactored quake3 entities module. + +03/07/2004 +SPoG +- Changed QE-style Drag tool to move as well as resize. +- Fixed shortcuts becoming disabled. +- Fixed loading file types with upper-case extensions. + +30/06/2004 +AcidDeath +- Added support for plugin submenus. + +27/06/2004 +SPoG +- Added build-menu-command editing. +- Fixed texture-object reshuffle caused by changing gamma. +- Refactored vfs/shaders/renderstates/eclass/texturemenu unrealise/realise systems. + +26/06/2004 +SPoG +- Added printf-formatting support to output streams. +- Added error/warning printing to xml parser. +- Changed build-menu xml format to allow easier editing. +- Added sample plugin. + +25/06/2004 +SPoG +- Changed paste and import-map to select pasted/imported entities and world brushes. + +24/06/2004 +SPoG +- Changed create-entity API to use eclass instead of classname. +- Changed nodes with unknown entity types to use a group-entity if the node has children. + +18/06/2004 +SPoG +- Changed image-loaders interface to use a file object instead of VFS. +- Changed model-loader interface to support in-place model reloading. +- Changed picomodel-loader module to support configuration of suppported model types in synapse.config. +- PicoModel: added support for loading models from an abstract input stream. + +14/06/2004 +SPoG +- Refactored texture browser. +- Removed GLWindow. +- Fixed mousewheel movement in camera window. + +13/06/2004 +SPoG +- Added load-entire-wad from textures menu in Quake/HL mode. +- Fixed bug with writing negative integers to .map format. +- Fixed texture-browser hide-unused to show only in-use shaders. +- Fixed texture-browser scrollbar positioning and origin-clamping. +- Fixed prompting to save unchanged new maps. +- Changed unsaved-changes message box default to "Yes, save" instead of "OK, don't save". +- Changed all message boxes to appear centrally. +- Removed minimize/maximize buttons from all message boxes. + +12/06/2004 +SPoG +- Disabled shortcut keys during drags. +- Fixed window updates for edge/vertex multi-selection. +- Added apply/cancel on enter/escape for texture-browser filter entry. +- Changed undo system to use explicit start/finish pair to specify undoable operations. +- Changed undo system to print undoable command after command completion instead of before. +- Added printing of movement parameters for translate/rotate manipulator drags. +- Changed brush-subtract to not delete the selection after subtracting. +- Fixed texture browser to not display textures if only in-use by nodes in the undo-queue. +- Fixed another workzone-update performance issue causing area-selection to be slow. +- Added code to brush build-B-Rep to handle duplicate edges. + +10/06/2004 +SPoG +- Added lazy evaluation of local->world transforms and world-space AABBs. +- Added shortcut key for translate mode, default 'W'. +- Added toolbar buttons for edge/face/vertex modes. +- Changed Escape shortcut to deselect-components if components are selected. + +02/06/2004 +SPoG +- Fixed performance bug with workzone updates not being lazily evaluated. +- Fixed brush edge-graph bug by removing degenerate winding edges. + +01/06/2004 +SPoG +- Added mode information to status bar. +- Added console feedback describing undoable commands executed. +- Fixed assertion failures on updating degenerate edge/vertex selections. +- Fixed detail brushes turning structural when clipped. + +31/05/2004 +SPoG +- Added support for more than one edge/vertex selected per brush. + +29/05/2004 +SPoG +- Refactored groupdialog - decoupled notebook from console/textures/entity pages. +- Fixed crash in patch tesselator. +- Fixed floating windows startup crash. +- Fixed null texture name for patches. + +28/05/2004 +SPoG +- Hide arrows on translate manipulator when obscured by square part. +- Workaround for main window not being brought to front when a floating window is closed. +- Fixed rendering of selected components of degenerate brush faces. +- Fixed double typedef in picomodel. +- Fixed bug in allocator stuff. +- Fixed up clipper tool button state. +- Added workzone updates when selection bounds change. +- Fixed transient setting on floating inspector windows. + +27/05/2004 +Tr3b +- Fixed gcc build errors. +SPoG +- Fixed crash bug in game selection dialog. + +26/05/2004 +SPoG +- Cleaned up project settings. +- Changed autosave/snapshots to only save modified maps. +- Refactored build menu. + +23/05/2004 +SPoG +- Fixed minor picomodel ase loader smoothing-groups bug. + +19/05/2004 +SPoG +- Allocator system improvements, fixed warnings + +18/05/2004 +SPoG +- PicoModel: added support for per-vertex smoothing-group, used in generation of normals. +- PicoModel ASE loader: added support for per-face 'MESH_SMOOTHING' id. +- PicoModel: changed normal generation code to create smooth normals across texcoord/colour discontinuities. +- Fixed cosmetic menu bug. +- Fixed file handle leak in tga loader. +- Added experimental allocator system. + +15/05/2004 +Tr3b +- Fixed gcc build errors. + +11/05/2004 +SPoG +- Fixed version.h custom build step on win32 +- Refactored preferences dialog to eliminate dependencies on other modules. +- Redesigned preferences dialog ui layout. + +06/05/2004 +SPoG +- Refactored texture browser. + +05/05/2004 +SPoG +- Upgraded win32 gtk2 version to 2.4.1. +- Migrated from GtkCombo to GtkComboBox. +- Texture browser improvements. +- Fixed malloc/free mismatch bug. + +05/05/2004 +SPoG +- Refactored surface inspector and texture browser. + +02/05/2004 +Nurail +- Added imageq2 module with support for quake2 and heretic2 texture formats: wal, m8, m32. + +30/04/2004 +SPoG +- Changed texture window not to auto-scroll when selecting faces. +- Changed surface inspector to apply shader seperately from shift/scale/rotate. +- Changed selected face tracking to use separate selection list. + +26/04/2004 +SPoG +- Added per-file change tracking - only modified (unsaved) files are saved by SaveMap(). + +18/04/2004 +SPoG +- Fixed texture-increment-matches-grid. +- Changed selecting a texture to only change the shader on the selected brushes. +- Changed 'Axial' button in surface inspector to reset the texdef for selected brushes/faces. +- Removed MAX_TEXTUREDIRS limit. + +11/04/2004 +SPoG +- OS library module added to wrap calls to rename/unlink/access/stat. +- OpenGL-view selection tests constrained to current window. +- Cleaned up OpenURL. +- Added jk2 and sof2 support to win32 installer. + +09/04/2004 +TTimo +- update the OSX setup / .info generation code + +08/04/2004 +SPoG +- Added integer quantisation for brush edge/vertex drags to fix FP-drift on planes. +TTimo +- fix for scons 0.95 +- re-enable Python >= 2.1 version check +- OSX 10.3: remove obsolete dlsym_auto_underscore (bug #920) +- OSX: disable q2 tools build (broken thread code) + +07/04/2004 +SPoG +- Moved profile.cpp into a separate static library. + +06/04/2004 +SPoG +- Refactored preferences to be more modular. +- Fixed preferences being corrupted if a crash occurs during preference saving. +- Refactored window position saving. +- Fixed bug in radio button creation in preferences dialog. + +24/03/2004 +SPoG +- Changed surface inspector to unfocus after pressing escape or return, to enable shortcuts. +- Added refresh of surface inspector for shift/scale/rotate-texdef shortcuts. +- Fixed bug causing some operations to be applied twice. + +21/03/2004 +SPoG +- Added depth-test and normalisation for translate-manipulator rendering. +- Added basic clipper-preview rendering. + +18/03/2004 +SPoG +- Added maya-style arrow-heads to translate manipulator. +- Changed square part of translate manipulator to be viewplane-oriented. +- Changed square part of translate manipulator to take priority for selection. + +15/03/2004 +SPoG +- Added recalculation of pivot-point when grid size changes. +- Changed win32 SHGetFolderPath to link with shfolder.lib instead of shell32.lib. +- Fixed failure to recover brushes from an invalid state. + +14/03/2004 +SPoG +- Improved event handling to make manipulator-dragging smoother in complex scenes. +- Refactored camera window camera-movement handling. + +06/03/2004 +SPoG +- Fixed vertex/edge undo failing to update selected-vertex position. + +29/02/2004 +SPoG +- Re-enabled next-xy-view in floating windows mode. +- Reinstated light-radius rendering. +- Added Wolfenstein support to win32 setup. + +24/02/2004 +SPoG +- Changed create-brush-prism to use correct dimension for prism radius. +- Changed freelook selection to use window-centre position. + +22/02/2004 +SPoG +- Optimised undo for adding/removing scenegraph nodes. +- Changed entity-list view to sort the tree by name. + +18/02/2004 +SPoG +- Moved default shaderlist to ///default_shaderlist.txt +- Moved user shaderlist to ////shaderlist.txt + +11/02/2004 +SPoG +- Refactored clipboard copy/paste code to allow re-use. + +10/02/2004 +SPoG +- Fixed false-positive-intersection bug in CSG subtract algorithm. + +07/02/2004 +SPoG +- SceneGraph: Refactored per-instance functionality. +- Fixed intermittent culling bug caused by bad bounding boxes. +- Fixed missing ToggleCubicClip shortcut. +- Removed modal buttons from Surface Inspector. +- Changed Surface Inspector keys: + - Escape: revert not-yet-committed values to previous state. + - Enter: commit not-yet-committed values immediatly. +- Changed Surface Inspector to update when selection changes. + +31/01/2004 +SPoG +- Cleaned up functional.h template usage with typedefs. +- Fixed autosave path. +- Added q1 component to installer. +- Added radiant_manual component to installer. +- Added q3-example-maps package to installer. + +29/01/2004 +SPoG +- Added error reporting for module system initialisation failure. +- Added MSI package creation tools in setup/win32. + note: requires MSI SDK binaries in http://zerowing.idsoftware.com/radiant-files/msi/msitools.zip +- Added initial version of scripts to build basic win32 setup. + +26/01/2004 +SPoG +- Added storing EnginePath for each game in local.pref. +- Added EnginePath editing to prefs dialog. +- Added vfs shutdown/init when gamename changes. +- Added vfs shutdown/init when enginepath changes. + +25/01/2004 +SPoG +- Fixed filtering updates. +- Moved default bsp menu to //default_build_menu.xml +- Moved user bsp menu to ///build_menu.xml +- Moved synapse.config to //synapse.config +- Moved game-specific .xlink files to //*.xlink + +25/01/2004 +SPoG +- Redesigned filter system: + - Filtered status updated when filterable attributes change. + - Supports per-brush-face filtering. +- Removed long-dead project-settings stuff: + - Moved gamemode and gamename storage into local.pref temporarily. + - Moved bsp commands over to a new format, disabled gui editing for now. + +20/01/2004 +SPoG +- Changed win32 Application Data path lookup to use win32 API instead of getenv. + +14/01/2004 +SPoG +- Fixed main menu mnemonics. +- Fixed crash on exit after viewing pointfile. + +13/01/2004 +SPoG +- Fixed not always resetting +component-mode when selection is cleared. + +11/01/2004 +Tr3B +- Added parse/write support for BrushDef3 and PatchDef3. + Requires editing synapse.config to enable. +SPoG +- Rearranged menus: + File = new/open/import/export + Edit = manipulate scene hierarchy and selection + View = toggle entityinspector/entitylist/surfaceinspector/camera/xy + edit camera/xy parameters + filters/hideshow/region + Modify = transform scene elements + Build = bsp menu +- Fixed Brush-Primitives support: + Requires editing synapse.config to enable. + Supports load/save of BP mapfiles, and editing BP texdefs in surface inspector + (Not yet possible to convert between formats). + +15/12/2003 +SPoG +- Changed user-prefs to be stored in user-profile directory on win32. + +14/12/2003 +SPoG +- Fixed entitylist window visibility tracking. +- Implemented find/replace-shader for patches. +- Changed get-texture-from-selected to update find/replace-shader dialog. +- Implemented reparent-selected for more than one selected node. +- Added checks to stop assignment of invalid texture names. +- Fixed uninitialised texture-compression setting. +- Fixed bsp menu. + +------- spog_branch merged to trunk + +10/12/2003 +SPoG +- Ported imagehl module. +- Ported imagepng module. + +03/12/2003 +SPoG +- Changed brush winding generation to use use double precision planes. +- Fixed tracking of modifier key state. + +29/11/2003 +SPoG +- Replaced mathlib with c++ vector/matrix library. +- Added expression-templates for common vector/matrix operations. + +21/11/2003 +SPoG +- Refactored component-selection stuff. +- Ported m4x4_t library to c++ Matrix4. + +16/11/2003 +SPoG +- Changed shortcuts.ini to be written automatically. +- Moved more prefs into specific modules. +- Changed c++ NULL usage to 0 for maximum portability. + +11/11/2003 +SPoG +- #include cleanup. +- Added debug output/assert for module initialisation. + +09/11/2003 +SPoG +- Refactored status bar stuff. +- Refactored window updates to use closures. +- General cleanup and refactoring. + +08/11/2003 +SPoG +- Increased priority of window redraw events. +- Ported model module to new module system. + +07/11/2003 +SPoG +- Refactored window updates. + +05/11/2003 +SPoG +- Changed menubar/toolbars to be non-detachable. +- Fixed empty-undos created when left-clicking in XY window. +- Refactored camera modelview/projection matrix updates. +- Moved application-specific code out of mainframe.cpp. +- Reinstated brush/entity count in statusbar. + +04/11/2003 +SPoG +- Reinstated instant status-bar updates. + +30/10/2003 +SPoG +- Fixed crosshair cursor in clipper mode. + +29/10/2003 +SPoG +- Changed camera to maintain valid projection/modelview matrices. +- Moved manipulator-transform update from frustum class to selection system. +- Removed frame-rate dependency in 3d-window freelook. + +27/10/2003 +SPoG +- Smoothed out xy-window mouse-chasing. +- Changed mouse-chasing to occur 16 pixels inside the window edges. +- Changed xy-window to maintain valid projection/modelview matrices. +- Fixed invalid workzone generated when deselecting with no selection. + +26/10/2003 +SPoG +- Ported changes from trunk between rev 3818 and rev 4036. + +25/10/2003 +SPoG +- Refactored xy-window mouse event handling. + +22/10/2003 +SPoG +- Fixed freelook focus-out event. +- Changed selection mouse event handling to use modifiers on button-release. + +21/10/2003 +SPoG +- Refactored all mouse event handling for camera window. +- Changed menus/toolbars/accelerators to use anonymous closures. + +20/10/2003 +SPoG +- Fixed get-pointer-in-screen-coordinates on win32. +- Changed patch prefs to use preference-system module. +- Fixed selection of side-on faces. +- Changed brush rendering to avoid glPushClientAttrib - workaround for ATI driver issue. + +19/10/2003 +SPoG +- Changed VFS directory initialisation to occur after module initialisation. +- Changed load-preferences to occur after module initialisation. +- Added preference-system module. +- Changed undo prefs to use preference-system module. +- Restored splash screen with non-intrusive behaviour. +- Cleaned up main-toolbar creation. +- Fixed floating windows behaviour when main window is minimised. +- Changed shader-list parsing to use new script tokeniser. +- Changed deprecated code using GtkPixmap to use GtkImage. +- Removed bitmap-loading from core. +- Removed pc-speaker beep on map-save. + +17/10/2003 +SPoG +- Fixed crash when trying to undo on an empty undo queue. +- Refactored gtkdlgs, using helper functions to create common widgets. +- Changed Dialog class to use std::list for data. + +13/10/2003 +SPoG +- Refactored entity inspector. +- Removed miscellaneous unused functions. +- Refactored qe3 header. +- Moved texture-window preferences into texwindow.cpp. + +12/10/2003 +SPoG +- Moved layout-mode dependant code into main-frame creation. +- Replaced prefs system with new import/export system. +- Fixed gcc3.3 build errors. + +11/10/2003 +SPoG +- Moved unnecessary methods from MainFrame into free functions. +- Removed unsupported toolbar buttons and menu items. +- Changed misc->gamma to not require restart. +- Fixed unmaximised window-size when loading main window maximised. +- Fixed saving of rotate increment preference. + +10/10/2003 +SPoG +- Fixed keyboard shortcuts intercepting input to texture-subsets text-entry. + +09/10/2003 +SPoG +- Fixed gcc3 build errors, compiled and tested on linux. + +08/10/2003 +SPoG +- Fixed brush snap-to-grid creating huge coordinate values. +- Fixed minor bug in selection menu. + +07/10/2003 +SPoG +- Cleaned up MainFrame. +- Added quantisation of plane pts when dragging. +- Fixed create-empty-brush bug. + +06/10/2003 +SPoG +- Moved control of camera keyboard-accelerators into camera window module. + +05/10/2003 +SPoG +- Separated keyboard-accelerators from menu items, using functors. +- Completed factoring out HandleCommand. + +04/10/2003 +SPoG +- Changed 4-way-split mode to automatically reposition separators when window size changes. +- Fixed gtk error in floating-entity/textures/console window. +- Fixed position of console horizontal pane separator. + +03/10/2003 +SPoG +- Work-in-progress factoring out HandleCommand. + +02/10/2003 +SPoG +- Fixed pink icons on main window on win32. +- Replaced g_bIgnoreCommands with signal block/unblock. +- Refactored MainFrame startup. + +01/10/2003 +SPoG +- Fixed entity-window redraw on selection change. +- Moved control over VFS init/shutdown to module system. +- Fixed failure to release translucency render-states. +- Added support for realise/unrealise of all opengl texture objects. +- Refactored startup/shutdown logic. + Changed quit commands to do nothing but call gtk_main_quit(). + Added code after gtk_main() to destroy stuff. +- Changed 25ms timeout-handler to an idle-handler for window updates. + (reduces redraw-request response time on fast machines) +- Moved gl-shutdown control to glwidget. + Textures are realised/unrealised when the shared context is created/destroyed. +- Fixed lack of a valid gl context when unrealising gl textures. + +30/09/2003 +SPoG +- Added support for translucent entity rendering. + +29/09/2003 +SPoG +- Changed all global modules to use GlobalModule helper templates. +- Ported qgl.c to c++. +- Changed win32 project to dynamic-link with opengl32.lib. + +28/09/2003 +SPoG +- Added new experimental xml preference import/export system. +- Refactored dialog base class: + Moved common dialog-element-creation code into helper functions. + Changed UpdateData to use per-element callback functions. +- Refactored preferences dialog. + +26/09/2003 +SPoG +- Reversed winding order for circle primitives. + +25/09/2003 +SPoG +- Fixed slow selection response in large maps. + +21/09/2003 +SPoG +- Added sharing of global modules within a client. +- Added quake map module and wal image module. +- Ported mip image module and hlw image module. +- Cleaned up imagehl module. +- Fixed crash in pcx image module. +- Rewrote md3 model module. +- Ported mdl, md2 and mdc model modules. +- Added filtering of q1/q2 clip brushes. +- Added ati-crash-workaround rendering path. +- Upgraded to gtk+-2.2.4 on win32: + Fixed capslock affecting shortcut-keys. +- Upgraded to gtkglext-1.0.4 on win32. +- Fixed copy/paste. +- Added shortcut-key support to entity treeview. + +17/09/2003 +SPoG +- Fixed failure to call vfsFreeFile on an empty buffer. +- Fixed calling convention for dlls in new module system. +- Changed map.cpp to support maps without a worldspawn entity. +- Fixed crash on attempting to clone a map root. +- Added filter-update calls after loading or cloning stuff. +- Fixed reference cache failing to save a file. + +16/09/2003 +SPoG +- Refactored client-side module system code for direct module access. +- Changed shaders, vfs, model, brush, patch and entity module interfaces + to use abstract base class. +- Removed unused functions from shaders and vfs interfaces. + +15/09/2003 +SPoG +- Removed synapse dependencies for win32 makefiles. +- Added find-first-module-of-type helper function. + +14/09/2003 +SPoG +- Added new lightweight module system: + Initialises each module the first time it is requested. + Does not allow configurations with cyclic runtime dependencies. + Provides templates for simple type-safe client-side implementation. + Requires no client-side linkage. +- Split radiant API into multiple APIs: + core, textures, scenegraph, selection, renderstate, filters, filetypes. +- Ported radiant, core, mapq3, mapxml, md3model, entity, shaders, image, + vfspk3, archivewad, archivepak, archivezip to new module system. + +11/09/2003 +SPoG +- Removed edge and vertex integer-snapping during selection. + +10/09/2003 +SPoG +- Replaced map in referencecache.cpp with a hashtable. +- Changed ReferenceCache interface to take a path string, + which can be either absolute or relative. + +08/09/2003 +SPoG +- Cleaned up qertypes.h - split into multiple files. +- Moved texdef stuff into itexdef.h. +- Fixed shutdown destruction order bug. + +07/09/2003 +SPoG +- Refactored texwindow.cpp. + Moved texture management code into textures.cpp. + Added textures api. + Replaced globals from texwindow.h with accessor functions. +- Cleaned up shaders module. + Removed color-shader stuff. + Rewrote shader-activation logic to use textures API. + Removed appshaders API. +- Refactored shutdown logic. + Moved non-mainframe shutdown stuff into Radiant_Shutdown(). + Added deferred render-state realisation. +- Fixed failure to create gl contexts in 16-bit mode for latest nvidia drivers. +- Changed hashtable template to use traits parameter + for case-insensitive hasher and keyequal functions. +- Removed unused functions from qerplugin API. + +02/09/2003 +SPoG +- Refactored code to remove global-object accesses: + Passing global objects as parameters to functions. +- Refactored texwindow. +- Moved synapse server out of pluginmanager. + Changed pluginmanager to handle nothing but IPlugin stuff. + +01/09/2003 +SPoG +- Refactored scenelib.h, split into multiple files. +- Refactored #includes related to scenelib.h. +- Refactored preferences for xywindow and camwindow. +- Refactored references to g_pParentWnd in xywindow and camwindow. + +30/08/2003 +SPoG +- Ported changes from trunk since 17/08/2003. +- Ported all changes from bug800-spog_branch (vfs rewrite and q1 support). + +29/08/2003 +SPoG +- Refactored component-selection stuff. +- Fixed selection-tracking bug in patches. + +28/08/2003 +SPoG +- Added support for line-strips to Selectors. +- Fixed selection for entity treeview. +- Refactored external-resource loading. + +20/08/2003 +SPoG +- Fixed snap-to-grid in edge and vertex modes. +- Added a default model to use when a model load fails. + +19/08/2003 +SPoG +- Changed snap-to-grid to vastly reduce the possibility of creating an invalid plane. + +17/08/2003 +SPoG +- Ported changes from trunk since 01/08/2003. + +12/08/2003 +SPoG +- Changed entity key/value tracking to only track keys already added to the entity. +- Fixed bug causing undo system to track scene changes during a queue-flush operation. + +12/08/2003 +SPoG +- Fixed bug in project settings bsp-commands display. +- Fixed empty undos being created by rotation buttons. +- Fixed rotation pivot for rotation buttons. + +09/08/2003 +SPoG +- Added property svn:eol-style=native to all text files. +- Added property evn:eol-style=CRLF to all dsp and dsw files. +- Partially ported bobtoolz to use new spog_branch module APIs. +- Removed Sys_UpdateScene. +- Added Map_Name() for access to currentmap variable. +- Added initial drag-n-drop support for graph tree-model. + +01/08/2003 +SPoG +- Fixed uninitialised refcount in entity key class. +- Fixed memory leak in shader parsing. +- Added reference-counting holder for worldspawn node. +- Moved string class and utilities into a new header in libs dir. +- Fixed external-file-resource cache to use case-insensitive name compare on win32. +- Fixed shaders to use case-insensitive name compare. +- Fixed shader module to display SHADER_NOT_FOUND when appropriate. +- Changed the default texture name to "NULL" instead of SHADER_NOT_FOUND. +- Changed move-into-entity to perform parent-selection-to-last-selected-node. +- Added a new implementation of GtkTreeModel operating directly on the scenegraph. +- Fixed disable-screen-updates to queue update requests instead of ignoring them. +- Merged from trunk (tag head-cvs2svn-2). + +22/07/2003 +SPoG +- Changed scale/flip-selection to use the manipulator as a pivot point. +- Added OnShutdown callback for CSynapseClient, used in entity module. +- Fixed console updates when loading maps/models. + +20/07/2003 +SPoG +- Added renderer support for qer_alphatest and qer_cull. +- Merged 'picomodule' module into 'model' module. +- Added rendering of area-selection rectangle. + +19/07/2003 +SPoG +- Merged from trunk. + +18/07/2003 +SPoG +- Disabled deleting classname key in entity inspector window. +- Cleaned up some stuff in selection system. +- Added basic versions of std::mem_fun and std::bind1st to function-object library. + +10/07/2003 +SPoG +- Implemented connect-entities. + +09/07/2003 +SPoG +- Completed local-space rotation manipulator. +- Fixed cloning objects within a misc_model subgraph. +- Fixed crash bug in loading certain models. + +06/07/2003 +SPoG +- Added function-object library header, similar to boost::function. +- Changed undo system to improve performance. +- Refactored entity inspector implementation. + +22/06/2003 +SPoG +- Refactored selection observing stuff. +- Changed shader parsing to use new tokeniser. +- Changed implementation of entities to use generic container. +- Refactored entity wrapper system. +- Fixed undo on delete/reset entity keys. + +21/06/2003 +SPoG +- Added rendering of target/targetname connection lines. +- Moved entity and eclass stuff out of qertypes.h into ientity and ieclass. +- Refactored entity_t to hide it behind Entity interface. +- Removed global project settings entity. +- Cleaned up bsp-command editing in project settings dialog. + +17/06/2003 +SPoG +- Fixed back-face-culling for selections by working in screen-space. + +16/06/2003 +SPoG +- Added workaround for nvidia driver opengl vertex arrays bug. + +15/06/2003 +SPoG +- Refactored Patch implementation. +- Refactored scene graph system to allow multiple graph instances. + +13/06/2003 +SPoG +- Added snap-selected-components-to-grid for faces edges and vertices. + +11/06/2003 +SPoG +- Improved normal-quantisation code. + +10/06/2003 +SPoG +- Changed renderer vertex types to be struct instead of typedef'd array. +- Added experimental quantisation of normals. + +05/06/2003 +SPoG +- Fixed intermittent crash in face-fit-texture. +- Changed patch rendering to display components only in vertex mode. + +02/06/2003 +SPoG +- Added nameable interface for scene graph nodes. +- Fixed undo for set-detail and set-structural. +- Added rendering of arrow for angled entities. + +01/06/2003 +SPoG +- Fixed handling of invalid planes during brush b-rep build. +- Refactored brush copy-construction/assignment. +- Fixed lack of rebuild after removing empty faces. +- Added lightjunior entity display. +- Fixed display of spawnflags/info for selected entity. +- Fixed update of entity window on setting spawnflags. +- Fixed botclip filtering. +- Fixed uninitialised alpha channel on jpg images by ignoring alpha. + +31/05/2003 +SPoG +- Cleaned up undo calls in brush and patch implementation. +- Changed brush implementation to defer rebuilds until needed. +- Fixed undo for find/replace shader. +- Refactored brush implementation, removed empty-face list. +- Fixed crash on loading unrecognised entities containing brushes. +- Fixed crashes in renderer caused by state-stack underflow. +- Fixed handling of invalid and duplicate planes during brush editing. +- Fixed failure to save brush faces during save-as. +- Refactored brush data members. + +28/05/2003 +SPoG +- Fixed crash on selecting stuff from entity list window. + +27/05/2003 +SPoG +- Fixed window title after save-as. + +22/05/2003 +SPoG +- Refactored selection system interface slightly. +- Removed old brushwrapper stuff. +- Fixed clipper-split-selection to keep both parts selected. + +21/05/2003 +SPoG +- Added select-faces-by-shader. +- Fixed find/replace-shader. + +19/05/2003 +SPoG +- Fixed rotation on misc_model. + +13/05/2003 +SPoG +- Fixed lack of undo on create-n-sided-brush. + +29/04/2003 +SPoG +- Fixed per-face find/replace and per-face nudge rotation. + +25/04/2003 +SPoG +- Fixed find-brush. +- Changed rotation buttons to use perfect precision for 90-degree rotations. + +24/04/2003 +SPoG +- Added find-dir-for-relative-filename and find-dir-for-absolute-filename to vfs. +- Changed resource manager to handle per-mod resources using vfs. +- Changed resource manager to allow resources with absolute paths. +- Fixed selection-test behaving like an ellipse instead of a rectangle. + +23/04/2003 +SPoG +- Changed selection test to choose closest-to-cursor over closest-to-camera. + +22/04/2003 +SPoG +- Fixed reference counting on shaders loaded from a texture directory. +- Fixed show-in-use and show-all shaders. +- Fixed undo for patch-set-shader. +- Changed shaders module to use STL map container instead of CShaderArray. +- Fixed SelectAllOfType for entities and patches. +- Cleaned up unused functions in shaders API. +- Decoupled selecting a texture in texture window from applying a texture to selection. + +21/04/2003 +SPoG +- Fixed memory leaks in image loaders, texdef_t, csg subtract, picomodel module. +- Fixed uninitialised memory references in renderer, texdef_t, eclass loader, undo system. +- Refactored tga loader. +- Cleaned up image module, enabled pcx and bmp loaders. + +17/04/2003 +SPoG +- Fixed shift-texture on selected brush faces. +- Changed brush b-rep algorithm to have higher tolerance for similar planes. +- Changed brush b-rep algorithm to always produce a valid connectivity graph. +- Added setting/getting shader for patches. + +16/04/2003 +SPoG +- Fixed tracking of in-use shaders (though undo queue still keeps shader references). +- Changed brush faces to store state in undo-system cleanly. +- Fixed flush & reload shaders and sleep/wake. +- Ensured that shaders are never leaked. +- Fixed bool conversion warnings. + +15/04/2003 +SPoG +- Added clamping of planepts on export to nearest 65536th of 1 unit. +- Changed m4x4 rotation functions to use double-precision for angles in radians. +- Changed m4x4 quaternion rotation to use double-precision internally. +- Changed float printing to use %g instead of %f (strips trailing zeros). + +13/04/2003 +SPoG +- Fixed brush import failing to update bounding boxes. + +11/04/2003 +SPoG +- Refactored Brush class into Brush, BrushInstance and BrushNode. +- Fixed various flaws stopping brush b-rep algorithm from being 100% reliable. + +02/04/2003 +SPoG +- Changed manipulator to move to position of selected brush components. + +01/04/2003 +SPoG +- Fixed xml parser failing to resolve predefined entities. + +31/03/2003 +SPoG +- Fixed asserts in brush manipulation when creating invalid brushes. +- Removed automatic update of current texdef. +- Added Get Texture and Set Texture to textures menu. + +29/03/2003 +SPoG +- Fixed array template failing to free zero-sized allocations. +- Changed clipper mode to behave as a component-editing-mode. +- Removed right-click-to-drop-clip-point - use X and leftclick instead. + +28/03/2003 +SPoG +- Changed scene graph to sort objects in ascending order of creation time. + +27/03/2003 +SPoG +- Fixed bug in clone-selection causing only first selected brush to be cloned. +- Optimised select-all for brush/patch components. +- Changed edge/face/vertex toggles to toggle correctly. + +26/03/2003 +SPoG +- Removed win32/x dependencies from gl interface. +- Removed win32/glib dependencies from core interface. +- Replaced void* in gtk helper functions with forward-declared GtkWidget. +- Changed gtk helper functions to return enumerated values instead of win32 IDOK. +- Removed WINAPI macro for __stdcall from all interfaces. +- Removed unnecessary dependencies from core stdafx/qe3 header. + +25/03/2003 +SPoG +- Changed class-level render states to be static for Brush and Patch. +- Refactored brush implementation - breaking it up into smaller parts. +- Added refcounted ptr template to track refcounts. +- Fixed refcount bug introduced by new clone-selection. +- Fixed warnings in brush implementation. +- Cleaned up plugin interface header inclusion. + +24/03/2003 +SPoG +- Fixed crash on building unbounded brushes in release build. +- Changed assertion failures to be non-fatal in release build. +- Moved OS-specific gl headers into separate files. +- Moved app-shaders interface into a separate file. +- Cleaned up dependencies for brush implementation. + +23/03/2003 +SPoG +- Added per-instance selection of patch vertices. +- Fixed per-instance vertex selection rendering on multiple instances. +- Refactored patch/brush render code. +- Changed manipulator transforms to convert to local space before applying. +- Fixed selection test for picomodule meshes. +- Fixed selection test winding order for triangles. +- Fixed update of window title on new-map and load-map. +- Changed selection to work with exactly side-on faces. +- Refactored instance implementation to reduce code bloat. +- Fixed map export always exporting root. + +21/03/2003 +SPoG +- Fixed external resource tracking to preserve uppercase characters in names. +- Changed clone-selection to not use global copy/paste. + +20/03/2003 +SPoG +- Changed map-save to save all saveable files that the map references. + +16/03/2003 +SPoG +- Moved hashtable and hashfunc into libs. +- Cleaned up renderer interface and selection interface. +- Refactored frustum testing class. + +12/03/2003 +SPoG +- Moved winding-specific stuff from brush obj to winding obj. +- Refactored filter subsystem to remove dependency on entity/brush/patch. + +07/03/2003 +SPoG +- Fixed crash when clicking cancel from patch cap-dialog. +- Changed selection tests to ignore back-facing faces. + +05/03/2003 +SPoG +- Fixed failure to release cloned scene graph nodes. +- Fixed selection manager failing to release render states. +- Fixed eclass system failing to release render states. + +04/03/2003 +SPoG +- Fixed writing-past-end of vertex arrays in brush rendering. +- Fixed releasing data stored in undo system when deleting undoables. +- Fixed crash in misc_model when model fails to load. +- Fixed undo for clone/rotate/flip/nudge/set-texture operations. +- Cleaned up surface dialog. + +01/03/2003 +SPoG +- Changed clipper split-selection to keep result selected. +- Cleaned up and finalised picomodule, adding to cvs. + +28/02/2003 +SPoG +- Cleaned up entity list window. +- Fixed crash on freeing a map while entity list node is selected. +- Fixed crash on clone/copy of externally referenced brushes. +- Added multiple-selection support to entity list. +- Added updating entity list selection display on selection changes. + +26/02/2003 +SPoG +- Added stl_warnings include. +- Fixed tracking of map-modified-since-last-saved. + +24/02/2003 +SPoG +- Fixed missing map-release when map-load fails. +- Changed map-rename (save-as) to flush undo queue. +- Fixed bug in exporting selections. + +23/02/2003 +SPoG +- Fixed redo on changing misc_model "model" key. + +22/02/2003 +SPoG +- Changed undo interface to factor out global undo system. + +21/02/2003 +SPoG +- Started cleaning up Map subsystem. + +20/02/2003 +SPoG +- Added 'clone subgraph' feature to core, using direct xml import/export. +- Added 'save' feature to reference cache. +- Added scope timer object. +- Changed scene graph interface to allow setting scene root. +- Changed core to use reference cache to track current map. +- Removed mapmodel module. +- Fixed relative paths for loading maps as misc_model. + +19/02/2003 +SPoG +- Changed model/map modules to register their file types on load. +- Added map module manager to support unlimited map loader modules. + +18/02/2003 +SPoG +- Changed vertex buffer template to support resizing. +- Changed vertex buffer search algorithm to iterate instead of recurse. + +17/02/2003 +SPoG +- Fixed sorting bug with render-states. +- Fixed colour state when disabling gl_color_array. +- Fixed unterminated loop in scenegraph traversal. +- Fixed freeing project entity twice on shutdown (infinite loop). +- Fixed failing to destroy the scene graph on exit (oops). + +07/02/2003 +SPoG +- Added updating current texdef when a face is selected. +- Fixed crash when creating a brush with duplicate planes. + +06/02/2003 +SPoG +- Fixed various issues with 'save region'. +- Changed default current texdef to use game-specific scale. +- Changed brush creation to use current texdef. +- Changed clipper to use current texdef. +- Fixed bug in selection counting for patch control points. + +05/02/2003 +SPoG +- Fixed reversed solid-selection-outline preference. +- Finished factoring out face_t. +- Added script token writer interface for map export. +- Changed map export interface to use script token writer. + +04/02/2003 +SPoG +- Added depth-buffer-write enable/disable feature to renderer. +- Changed Pointfile class to use renderer. +- Continued factoring out face_t. + +03/02/2003 +SPoG +- Fixed temporary objects leaving dangling references in the undo system. +- Changed import-map to not modify existing selection. + +02/02/2003 +SPoG +- Fixed frustum culling for transformed nodes. +- Fixed hide/filter/region to work independently. +- Added highlighting of selected brush faces in component mode. +- Fixed bug detecting thin area-selection if dragged down or left. +- Renamed brush_type to Brush. +- Renamed face_type to Face. +- Started factoring out face_t. + +01/02/2003 +SPoG +- Improved edge and vertex manipulation. +- Removed unnecessary graph traversals for rendering/selection. + +31/01/2003 +SPoG +- Merged changes since last merge from merge-1_2_10-post to spog_branch. +- Merged changes since last merge from HEAD to spog_branch. +- Tagged HEAD after merging as 'merge-spog_branch-post'. +- Added walker to merge sibling worldspawns. +- Merged changes to Construct for PPC from trunk. +- Fixed over-enthusiastic worldspawn merging. +- Fixed misc_model "angle" key setting pitch instead of yaw. +- Fixed entity set-key-value changing scene-graph topology. + +30/01/2003 +SPoG +- Added subgraph traversal feature to scenegraph traversal. + +29/01/2003 +SPoG +- Changed scene graph implementation to allow nested traversals. + +28/01/2003 +SPoG +- Fixed angles key order for misc_model. + +27/01/2003 +SPoG +- Fixed typo causing set-texture-of-selection to behave incorrectly. + +26/01/2003 +SPoG +- Replaced all tabs with spaces in CHANGES. +- Fixed entity bounds updates for 'light'. +- Fixed code that doesn't conform to c++ standard as enforced by gcc. + +24/01/2003 +SPoG +- Added missing file: icharstream.h. +- Fixed bug with synchronisation of brush-face instance data. +- Added .map format import/export interfaces. +- Removed last remnants of IBrush. +- Removed IPatch. +- Moved walkers defined in in mainframe.cpp to appropriate places. + +23/01/2003 +SPoG +- Changed scenegraph traversals to use a compiled graph. + +20/01/2003 +SPoG +- Changed selection interface to hide per-instance data. +- Added streaming tokeniser for .map format. +- Changed mapq3 module to use streaming tokeniser. +- Added xml stream interface for input and output. +- Added xml stream parser based on libxml2 SAX. +- Changed mapxml module to use xml stream input. +- Changed brush and patch to use xml stream input. +- Added xml stream writer. +- Changed mapxml module to use stream output. +- Changed brush and patch to use xml stream output. + + +16/01/2003 +SPoG +- Disabled pivot updates while selection is being manipulated. +- Changed pivot calculation to use instanced world-bounding-box. +- Fixed crash bug in PositionView. + +14/01/2003 +SPoG +- Merged with branch merge-1_2_10-post at tag spog_merge_merge-1_2_10-post +- Removed 10k+ lines of unused #if 0 code. +- Removed unused declarations in qe3.h. +- Removed brush and face types from qertypes.h. +- Removed plugin API stuff dependant on brush type. +- Added per-instance selection for brush faces. + +08/01/2003 +SPoG +- Added selection counters for primitive/component modes. +- Added face-drag mode (default shortcut key = F). +- Added face-centre-point rendering/selection/editing. + +07/01/2003 +SPoG +- Changed selection to work per-instance. + +06/01/2003 +SPoG +- Changed selector interface to allow per-entity selection tests. + +06/12/2002 +SPoG +- Added selection-test and selector objects to clean up selection system. +- Unified key modifiers for selection in both primitive and component modes. +- Added mapmodel module to load .map and .xmap as misc_model. + +02/12/2002 +SPoG +- Added debug rendering of a selection test. +- Fixed bugs in triangle/line clipper, making selection work properly. +- Refactored selection system interface. +- Added undo for texture nudge. +- Fixed minor render-state bug. + +29/11/2002 +SPoG +- Optimised wireframe drawing of brushes with back-face culling. +- Optimised brush-winding generation. +- Changed winding generation to be more robust with large world extents. +- Fixed crashes with unbounded-face and degenerate-edge cases. + +20/11/2002 +SPoG +- Completed rotation manipulator. +- Added translation manipulator for the default editing mode. +- Changed XY mouse-chaser speed to depend on distance mouse moved outside window. + +15/11/2002 +SPoG +- Added initial version of maya-style manipulators - rotation manipulator. + +07/11/2002 +SPoG +- Fixed false asserts in Brush ConstructPrefab. +- Undo system optimisation - uses binary-sorted container to speed up finding already-added objects. +- Fixed crash when transforming objects which don't support edit_interface. +- Fixed clone command to nudge the selection after cloning. +- Fixed clipper to remove brushes that are completely behind the clip plane. + +06/11/2002 +SPoG +- Brushes: cleaned up per-face operations to use face visitor pattern. +- Fixed setting brush face texdef, with fully functioning undo on individual brush faces. + +04/11/2002 +SPoG +- Refactored renderer to two objects, camera and XY renderer - XY uses entity shaders. +- Experimental pivot object.. work-in-progress. +- Modified scene-graph instance-caching to maintain multiple instances within nodes. +- Fixed undo bug, deleting a void* doesn't call the destructor. +- Rewrote filters system to work with scene graph system. +- Cleaned up quake entity module to provide same functionality as before. +- Refactored render-state cache to use generic hashtable and reference-cache templates. + +19/10/2002 +SPoG - spog_branch - experimental - work-in-progress +- added scene graph library: defines interfaces, generic graph node types, + re-usable systems for traversals, traversal paths, node containers, + multiple-instance caching of world-space-transforms/bounding-volumes/visibility. +- added scene graph traversals to replace all traversals of 'active_brushes', + 'selected_brushes', 'filtered_brushes' and 'entities' linked-lists. +- added view module: view-volume-culling system acting on the scene graph, + uses scene heirarchy to minimise culling tests per frame. +- added opengl-state module: sorts opengl-state objects (shaders) to minimise + opengl state changes during rendering. +- added renderer module: culls objects outside the view volume, + gathers non-culled renderable objects from the scene graph, + keeps track of state during traversal, adds renderable objects to correct opengl-state. +- added selection module: sets view volume to the selection box/ray, culls + objects outside the view volume, gathers non-culled selectable objects + from the scene graph (entity/primitive/component), + selects gathered objects (select/toggle/cycle) or moves things if already-selected. +- added patch module: encapsulates a patch as a scene graph node, + optimised patch tesselation. +- added brush module: encapsulates a brush as a scene graph node, + adapts multiple-brush operations to use the scene graph. +- adapted entity module: encapsulates an entity as a scene graph node/subtree, + shares common code between different visualised entity types. +- adapted model module: encapsulates a model as a scene graph node/subtree. +- added undo module: completely new compact infinite-undo-system, + operates on undoable objects, uses minimal-state data to store objects, + uses refcounting to undo "deleted" objects. + +TODO: cleanup: many simple things are still broken or not functioning in the right way.. +see !\todo items in the code (not all are documented yet though). +Selection module probably needs refactoring some more - perhaps split off the move-already-selected stuff. +The dependencies are still pretty bad.. need to refactor stdafx.h/qertypes.h. +Some of the above modules are not really modules, they're static-linked to the core, but could easily be dynamic-linked. +The interfaces to brushes and entities still expose brush_t and entity_t for backwards-compatibility. +NOTE: some todos are #if 0 because their functionality is (or will be) replaced by the new systems. + +------ branch point - spog_branch + +30/08/2003 +SPoG +- Ported changes from trunk. + +17/08/2003 +SPoG +- Ported changes from trunk. +- Added quake pack to win32 setup scripts. +- Fixed crash in RunBsp. + +17/05/2003 +SPoG +- Renamed stream interfaces with C and I prefix. +- Shoehorned bytestream into idatastream. +- Moved member classes out of IArchive, replaced with member typedefs. +- Renamed filesystem, path, dynamic string, file input stream with C and I prefix. +- Documented filesystem, path, dynamic string, file input stream. +- Cleaned up game-specific encapsulation classes a little. + +16/05/2003 +TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=815 + found out about string_t that slept through my reviews + +Updated list of broken stuff on this branch: + +- include/bytestream.h: remove this file, extend idatastream.h header +from the code conventions: + include/ directory: + This directory is holding the API header files for the synapse modules (plus a few build control and version related headers) + All headers that describe synapse APIs should start with an i: ishader.h igl.h + + The few files in include/ that don't start with a lowercase i are specific files for build control and configuration. + Regular developement never creates non i-prefixed files in include/ + +the merge with IDataStream goes like this (note the corrected class names): +class IInputStream +class IOutputStream + +class IDataStream : public IInputStream, IOutputStream + +class ISeekableStream +class ISeekableInputStream : public IInputStream, public ISeekableStream +class ISeekableOutputStream : public IOutputStream, public ISeekableStream + +typedef unsigned int size_type; goes away, use size_t + +- include/iarchive.h + +renamed VisitorFunc to IArchiveVisitor +move it out of IArchive class (keep it in iarchive.h though) +from code conventions: + Don't declare classes inside classes. Makes the code harder to read and isn't useful to anything. + +- remove libs/bytestreamutils.h +put that functionality into the IStream stuff / idatastream.h stuff +anything you retrieve from an IStream should be endian-correct + +- libs/filestream.h +I'd rather see a header without embedded code, and a .cpp file for it + +- libs/fs_filesystem.h libs/fs_path.h +those totally lack documentation about what they are doing +same as above. way too much embedded code. makes things harder to maintain. implement in a .cpp +and as usual, name the classes correctly, C and I prefixes + +- gamespecific_t: rename correctly to CGameSpecific +I like the idea of gathering all hardocded game-specific behaviours inside one same class. +But is it really the topic of bug800? + +- gamespecific_executable_t: rename correctly to CGameSpecificExecutable +shouldn't this be merged with CGameSpecific? + +12/05/2003 +SPoG +- Fixed win32 build error (win32 has no ). +- Added dir_good() check to handle failure of dir_open() in posix DIR wrapper. +- Renamed dynamic_string_t and path_t to DynamicString and UnixPath respectively. +- Removed unnecessary use of 'inline' and 'virtual' keywords in class definitions (bad habit). +- Documented archive interface. + +09/05/2003 +TTimo +- some easy fixes to get it to startup on Linux +- tagging the current source as bug800-spog, preparing rollback + + +SPoG +- Added an implementation of IArchive to do OS filesystem access. +- Changed vfspk3 to use above implementation for OS filesystem access. +- Added const qualifiers to vfsGetFullPath and everything dependant on it. +- Changed vfsGetFileList to use "*" to indicate all-files instead of a NULL pointer. +- Extended IArchive interface to allow for extra filesystem functionality. + Added method to check if a file exists quickly. + Added method to traverse the filesystem with control on start-dir and depth of recursion. +- Defined implementation requirements for IArchive. + An archive contains a heirarchy of directories and files, and may contain empty directories. + An archive can be traversed in directory order. + Directories can be distinguished from files during a traversal. + Directory paths will always end with a separator. + File entries can be opened as an IArchiveFile. + IArchiveFile provides a simple non-seekable input stream which can only be read forwards. + Any number of files may be opened and read from an archive simultaneously. + An archive may be released while one or more files are still open, the files will remain valid until released. +- Changed archivepak, archivezip and archivewad to implement all IArchive requirements. +- Factored common code from archive implementations out into new lib/headers: + fs_filesystem.h: a templated filesystem container, with iterators and efficient traversal. + fs_path.h: a dynamic filesystem path object, with separator checking and efficient memory management. + ibytestream.h: abstract interfaces for input byte streams. + bytestream.h: utilities operating on abstract input byte streams. + filestream.h: a c++ wrapper for stdc FILE object, implementing a seekable input stream. +- Added a new pkzip-reader implementation, designed to be orthogonal to the compression library used. +- Added a zlib input stream object, which decompresses data from an abstract byte stream on the fly. +- Removed archivezip code copy/pasted/hacked from zlib and unzip.c, replaced by pkzip-reader and zlib input stream. +- Changed vfsLoadFile to return 0 for a zero-sized file, only returning -1 if it failed to load the file. +- Removed halflife-specific disabling of shader-loading, thereby allowing common-hydra.shader to work. +- Added archivezip, archivewad and archivepak to win32 setup scripts. +- Updated win32 setup scripts for halflife media. +- Updated cons scripts for archivezip, archivewad and archivepak. +- Added printing of warnings when vfs functions are given invalid input paths. + + +-------- branchpoint: "bug800-spog_branch" + + +30/08/2003 +SPoG +- Ported changes from trunk since branch point. + +11/08/2003 +SPoG +- Split qe3.h up into multiple headers. +- Cleaned up use of win32 symbols: + WINAPI macro for module functions removed. + GUID for module interfaces replaced by string. + boolean replaced by bool. + MB_OK etc gui defines replaced by enumerations. +- Removed plugin stuff deprecated by synapse from qerplugin.h. +- Cleaned up use of glib symbols: + guint32, gdouble, gboolean typedefs removed from non-glib-dependent code. + TRUE/FALSE replaced by c++ bool true/false. +- Removed all "extern" function declarations. +- Removed dependency on MainFrame from preferences.h. +- Moved synapse server and pluginmanager instances to pluginmanager.cpp. +- Split pluginmanager.cpp into three parts: + Radiant's synapse client stuff in plugin.cpp. + Implementation of brush/entity/patch-handles stuff in pluginapi.cpp. + Synapse server init/shutdown in pluginmanager.cpp. +- Added forward-declarations to avoid including other headers. +- Replaced CString usage with Str; +- Removed unused brush-scripts stuff. +- Cleaned up patch-vertex-area-selection logic. +- Removed very old code chunks that were commented or #if 0. +- Replaced project-entity with a dedicated key/value string map. + +---- branch point - spog-cleanup + +06/12/2003 +SCDS_ReyalP +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=930 + shortcuts to change texture window scale + +02/12/2003 +SCDS_ReyalP +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=913 + fix for single monitor window positioning save +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=924 + fixes to CenterXYView shortcut, correctly bound to Shift+Control+Tab now +TTimo +- add a 'q3map2' command line to win32_install.py, factorize and remove win32_install_q3map2.py + +-- linux 1.3.14 test build 1 + +29/11/2003 +TTimo +- heretic2 has no q2map. linux setup tries to install and breaks + removed faulty setup line +- cleaned more Linux setup fuckage caused by q2/her2 + a tip: rm -rf build install before building and testing a new setup + +28/11/2003 +ydnar +- full SCC purge of the vs.net project files (for real this time) +- added seperate Q3Map2 build targets +- added seperate Q3Map2 post-build Python script +- _skybox entity support +- _skybox and _decal in entities.def (Q3) + +-- win32 1.3.14 test build 1 + +28/11/2003 +djbob +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=899 + bobtoolz update (icon functionality is in menu too) +djbob & TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=842 + migration of the win32 build system to vs.net/VC7 + new libxml and libpng packages are required: + http://zerowing.idsoftware.com/libxml/ + http://zerowing.idsoftware.com/libpng/ + updated win32_install.py for new names and paths +TTimo +- assraped the vcproj with sed to remove Scc entries +SCDS_ReyalP +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=88 + 2D background image plugin +TTimo +- putting together win32 setup updates for 1.3.14 + msvcr70.dll goes in core directory + bkgrnd2d plugin content + changed file paths (libxml/libpng) + .xlink for new JA content + +27/11/2003 +TTimo +- using a central scons.signatures file for checksums +- version bump to 1.3.14 +- a libxml-related build bug in qe3.cpp on Debian sid +- it looks like Sid no longer has inflate_mask exported from /usr/lib/libz.so + switched the mask to be defined in our source + this may be a problem on other distros, and on holy box (Woody) +SCDS_ReyalP +- bug 921 and 922, Z floating window fixes +- bug 926, hullcaulk, hintskip, subtlehint +EvilTypeGuy +- bug 505 - select all faces with a given texture + +19/11/2003 +ydnar +- clipper tool plane points default to 1st selected patch mesh + +17/11/2003 +TTimo +- upgraded server to subversion 0.33 + +-- released 1.3.13 + +10/11/2003 +SCDS_reyalP +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=917 + floating windows startup crash + +09/11/2003 +TTimo +- fix M4_GAME_ET ( OSX setup ) +AstroCreep +- cleaned up JA shader scripts + +01/11/2003 +ydnar +- Merged ASE submaterial/subobject code from BirdDawg +- Made Q2/Heretic2 tools not use precompiled headers to eliminate Win32 compilation errors +- Added glColor4ubv() support to the GL function table +- Changed PicoModel rendering to use glColor4ubv() instead of 4 divides and pass-by-value glColor4f() +- Fixed bug 900 by setting alpha to 255 explicitly in image module, rather than 3 input components, + which was borking Q3Map2 jpeg loading, and thus compiles + +24/10/2003 +TTimo +- bump to 1.3.13 +Anders +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=826 +new osx patch, fixes strip bug in setup. merging setup patches to a single file +SCDS_reyalP +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=900 +Some jpegs get garbaged alpha channel + +22/10/2003 +-- merge https://zerowing.idsoftware.com:666/radiant/GtkRadiant/branches/Release-1.3.12/ + 19/10/2003 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=826 + scons BUILD=info to generate a tarball and it's .info + + 18/10/2003 + Spog + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=888 + patch for 16 bit RGBA support in glwidget + + -- released 1.3.12 Linux + + 14/10/2003 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=879 + fixed hellish dlclose issue only occuring with holy builds + + -- released 1.3.12 win32 +-- end merge +Arnout +- added epsilon testing to hashtable compares to eliminate almost-identical vertices +- pico surfaces now use the normals from LWO vertices + +21/10/2003 +Arnout +- added hashtable for faster vertex matching during LWO surface generation +- model rendering now uses DrawElements and will use vertex colours in wireframe/flats shade mode + +20/10/2003 +Arnout +- added LWO support to picomodel. + shader names are derived from surface name + only geometry from layer 0 is used +- added support for 'vertical flipped' TGAs + +19/10/2003 +Arnout +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=893 + fixed starton primary monitor + fixed mouse pointer setting on win32 to properly translate gdk's offset coordinate system in windows' one + +-- released 1.3.12 win32 + +11/10/2003 +Spog +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=865 + fix texture subsets +TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=889 + misc update, missing JA system textures +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=824 + fixed .pref file trashing +Nurail +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=881 + BSP monitoring disabled by default in Q2 + +09/10/2003 +TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=883 + more JA pack: shaders and mapextras.pk3 textures +- fix Q2 win32_install.py to put the tools at the right spot +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=878 + correctly support PNG images with an alpha channel +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=885 + fix console to refresh during a texture directory load +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=739 + fix weird Shift + Control + Z causing a Redo in non-floating window mode + +07/10/2003 +Nurail & TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=872 + Q2 tools, added -fs_basepath. Need corresponding setup and .proj updates +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=875 + fixed broken surface properties in Q2 surface plugin +TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=872 + more fixes, build paths in scons, take out INSTALL config on command line (not functional + not need) + added Q2 tools back to Linux setup +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=197 + using "*" as the shaders minor in surface plugin +- updated makeself copy to the latest from icculus.org cvs +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=574 + sprite plugins, tweak to make it functional for all games +- updated Q2 tools .dsp +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=876 + more Jedi Academy setup work and content + moving imagepng.dll module to the core, as now both Sof2 and JA need it + sample maps reorg, new siege_hoth_sample.map +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=874 + a lot more models - using a dynamic File Group in IS to cope with that +- Q2 IS setup fix, was not properly putting stuff in baseq2/ + IS setup: tweak to Q2 tools stuff + +06/10/2003 +TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=872 + Q2 setup needs to be cleaned up and unified between win32 and Linux + using INSTALL_Q2 and TOOLS_Q2 in SCons script to install the Q2 tools + fixed the setup build dependencies to reference the Q2 tools targets + moved the Q2 specific modules imagewad and vfspak to q2/modules + +05/10/2003 +TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=871 + updating the gtk2 version to 2.2.4 + adding an SVN module with the Gtk2 developer package: checkout gtk2-win32 + updating IS to the new files + sed'ing the .dsp to replace src-gtk2 by gtk2-win32 +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=609 + Q2 tools: comment out dupe strupr on win32 + update IS setup to missing Q2 stuff ( vfspak and tools ) +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=866 + Fixed Camera inspector window not refreshing. Was a missing top level gtk_widget_show call + +03/10/2003 +Nurail +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=609 + quake2 tools build scripts + Linux setup updates - Q2 game pack in Linux setup +TTimo +- scan through all URL links in game.xlink to update them +- added JA links (Raven and MapCenter forums) + +30/09/2003 +TTimo +- update all synapse.config, win32 .dsw and install_win32.py for new surface module +- Jedi Academy and Quake II game packs in IS setups +- hardcoded hacks in editor core for JA, copied over from JKII +- bug #867, disable sleep by default +- q3map2 bug fix + +Nurail +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=862 + Q2's 'no patch' stuff + +29/09/2003 +TTimo +- OSX: fixup setup.xml.in + +-- merge bug856 back into trunk +16/09/2003 +Nurail +- new patch + win32 stuff for surface module +TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=804 + refactored the XML synapse.config handling: + better detection of invalid XML file + less code, factorized to CSynapseClient::ConfigXML +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=828 + fixing bobtoolz 'shaders' major loading issues + added '*' minor. to be used with lots of caution. only if the given major will have a single API such as 'shaders' + also, map module was missing a VFS entry in non-HL configs. that's bad karma, using a minor "*" instead + NOTE: on a lot of modules we could be using a '*' entry instead of having lines in synapse.config +- took out obsolete md3model + +15/09/2003 +Nurail +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=856 + quake2 surface module + +07/09/2003 +Nurail & TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=856 + a surface inspector module to customize per-game API + removing DO_SURFACEPLUGIN define (enabled implicitely) + removing bSurfacePropertiesPlugin (true implicitely) + remove SI_SetActiveInRadiant, it's always on by default + why was USE_UNDOTABLE_DEFINE taken out? - put back in + removed DBG_PLUGIN define and related code, that stuff is from way back and no longer relevant + cleanup QERApp_FreeShaders in shader module from DO_SURFACEPLUGIN stuff + the WINAPI stuff in interfaces is not needed, that's an old remnant. Cleaned up +-- end merge bug856 back into trunk + +19/09/2003 +Justin Blur +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=785 + fix ~/.radiant permission bug +Nurail & TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=849 + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=851 + win32 updates for the new modules and install_win32.py + +16/09/2003 +Nurail & Hydra +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=848 + q2 map format minor + +08/09/2003 +Tr3B +- imagepng.so / PNG format support in Linux + NOTE: atm no official supported Linux game by GtkR uses this + +07/09/2003 +Nurail +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=851 + imagewal.so module / wal image format +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=840 + md2 support in picomodel + +06/09/2003 +Nurail & TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=848 + renaming mapq3.so to map.so + added hooks for Q2 map format load/save to single map module (minor mapq2) +Nurail +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=849 + vfspak port to synapse and updates + +04/09/2003 +SPoG +- Fixed crash in RunBsp caused by passing an invalid pointer to printf. +- Changed console to wrap long lines instead of using horizontal scrollbar. + +30/08/2003 +Anders & TTimo +- OSX setup, new patch to make scons SETUP=1 produce a .run +- don't put bspc Linux binary in the setup + +30/08/2003 +Anders & TTimo +- OSX setup, new patch to make scons SETUP=1 produce a .run +- don't put bspc Linux binary in the setup + +26/08/2003 +Anders +- more scons OSX, start on setup stuff + +25/08/2003 +TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=855 + make 'move into worldspawn' work again +Anders Gudmundson & TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=826 + OSX scons build system + +24/08/2003 +ydnar +- Removed "test.cpp" from radiant.dsp (merge artifact?) +- Added ddslib to radiant.dsw +- Correctly set lib deps for q3map2.dsp for ddslib +- [bug 852] Increased buffers from 260 bytes on Win32 to 4096 bytes + +TTimo +- bump to ver 1.3.12 +- EnsurePythonVersion broke in 0.91 (commented out) + +David Hogue +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=844 + q3data Linux. q3data can read .ase and turn them into .md3 + +23/08/2003 +ydnar +- Added ddslib to repository, based on nvidia sample code, cleaned up a bit + straight C, loads DXT1, DXT3 and DXT5 format DDS textures. + +22/08/2003 +TTimo +- https://zerowing.idsoftware.com:666/viewsvn/ +- Arnout's commit email script - try #2 + the commit script doesn't handle viewsvn root remaps (radiant instead of radiant.svn) + adding a prefix to the report + +04/08/2003 +TTimo +- conversion from CVS to Subversion: +repository convert completed (with revml) +module repositories glued together in a single one +hooking email commit scripts and backup scripts +- Python >= 2.1 required instead of 2.2 + +01/08/2003 +SPoG +- Changed gl widget to request maximum available depth buffer precision. +- Changed all uses of deprecated GtkCList and GtkCTree to use GtkTreeView/Model. +- Fixed directory handle leakage in synapse module search. +- Fixed dir_dialog always returning NULL for Textures -> Load Directory. + +23/07/2003 +SPoG +- Ported focus_out_event handlers in gensurf to gtk2 signals system. +- Fixed failure to load models for entities other than misc_model. +- Fixed crash in model module shutdown caused by mismatched resource capture/release. + +22/07/2003 +TTimo +- fix q3map2 .dsp for correct glib-2.0 includes (common/vfs.c) +- camera plugin installs to core now (RTCW and ET) +- fixups to the merged setup stuff +- patched cvsreport to provide explicit diff for some files #2 +- fix to work with scons 0.90 / added LIBPREFIX ('lib') where needed + https://sourceforge.net/tracker/?func=detail&atid=398971&aid=766975&group_id=30337 +SPoG +- Fixed crash in cmdlib ExtractFileBase when source filename is an empty string. + +20/07/2003 +TTimo +- SCons scripts for the ported plugins: bobtoolz, camera, prtview, gensurf +- ET Linux setup script + new plugins +- q3map2.x86 is installed and wrapped through a q3map2 script (libstdc++ LD_LIBRARY_PATH) +- update ChangeLog and credits +- put back the GTKRAD_DIR in .fgl + +19/07/2003 +SPoG +- Tagged trunk before merge as bug537-merge-3. +- Tagged branch port_gtk2_20030307 as gtk2-merge-final. +- Merged changes since tag bug537-merge-2 into trunk. +- Removed gtk dependency from plugin toolbar interface. +- Ported prtview, bobtoolz and gensurf to gtk2. + +18/07/2003 +Anders Gud +- OSX build fix + +16/07/2003 +TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=814 + merging ET support code into to trunk +- cvsreport 0.3.0 - http://www.nongnu.org/cvsreport/ + rolling out this ver since old cvsreport setup broke +- fixup to build on Linux (including fixing plugin builds) + +-- release-1_3_8-ET + +02/07/2003 +TTimo +- new setup build, with patches and updates from SD +- local fixing of bobtoolz dependency against libcmd, and itoolbar.h gtk header bustage + +19/06/2003 +TTimo +- missing plugins. add them to .dsw for default build, add them to IS setup: + camera, gensurf, bobtoolz, prtview +- fixup bobtoolz code for VC6. for(int i=0 causing duplicate definition errors +- removed pk3man from IS (we no longer distribute/maintain it) + TODO: cvs remove the IS files for it +- re-enabled plugins in build by default, disabled curry and textool + TODO: following error when bring up About box of bobtoolz: + BobToolz::ERROR->Failed To Load Exclusion List: C:\Program Files\GtkRadiant-ET-1.3\plugins\bobtoolz.dllbt\bt-el2.txt + +18/06/2003 +TTimo +- add ET game pack. from Arnout's full dump of editor source + game pack data + trunk tagged at ET-tag for this +- Dlg_SdAskCorePath: + szDir = "C:\\Program Files\\GtkRadiant-ET-1.<>"; + +09/06/2003 +ydnar +- Added Q3Map2 keys/entities to Quake 3 entities.def +- Removed obsolete vlight keys from Quake 3 entities.def +- Added MD5 functionality to mathlib, from: + http://sourceforge.net/projects/libmd5-rfc/ + +------- merged changes since tag bug537-merge-2 from branch port_gtk2_20030307 to trunk + +TTimo +- try checkin on branch see if cvsreport 0.3.0 will verbose it + +08/07/2003 +SPoG +- Fixed recent-files list for file names containing underscores. + +07/07/2003 +SPoG +- Fixed crash and file-type bugs in gtk file-dialog. +TTimo +- converted the setup code from perl to python +- added copy over of libgcc_s and libstdc++, and LD_LIBRARY_PATH in the wrapper script + +06/07/2003 +SPoG +- Changed console popup menu to include cut/copy/paste as well as clear. + +05/07/2003 +SPoG +- Fixed the way surface-inspector dialog responds to escape key. + +04/07/2003 +TTimo +- linux building / SCons + 0.90 is broken, use 0.14 for now. added version check + adding scons SETUP=1 option to spawn setup build + enable back vfswad in scons + TODO: grab Conscript-setup, convert it to python in build_setup function + +02/07/2003 +TTimo +- building a win32 setup, using -gtk2 suffix (game packs in Radiant-1.3-gtk2 and core in GtkRadiant-1.3-gtk2) +SPoG +- Ported vfswad to gtk2. +- Fixed memory leak in vfspk3 directory search. +- Added vfswad to win32_install.py. + +09/06/2003 +TTimo +- tagging setup/ as gtk2_setup_rollback + rolling back trunk setup code to the branch to build an experimental release +- merge trunk to branch: +-- tagged HEAD with bug537-merge-2 +-- merge HEAD between bug537 and bug537-merge-2 into the branch -- + 31/05/2003 + TTimo + - grab back vfswad code that I forgot in bug 800 rollback + - add prtview back to the project, fix it to build (#817) + + 27/05/2003 + djbob + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=817 + prtview fixes, upgrade to synapse + + -- release-1_3_8 +-- end merge HEAD between bug537 and bug537-merge-2 into the branch -- +- freshly merged in vfswad is broken +- freshly merged in prtview is broken +- q3radiant.dsp -> GtkRadiant.dsp, outputs GtkRadiant.exe +- removed vc5 dsps +SPoG +- Updated win32 setup stuff to use gtk2 dlls. +- Modified setup.pl to run correctly (tested on cygwin perl 5.6). + +08/06/2003 +TTimo +- Linux: + check gcc 3.x, better ldd check + add gcc version to about message + kill old cons stuff +- added q3map2.x86 scons build +- header conflict libs/cmdlib.h tools/quake3/common/cmdlib.h + grepped through q3map2 source to change #include "cmdlib.h" to common/cmdlib.h +- killed more cons files remnants +SPoG +- Improved error reporting for win32 setup system. +- Fixed errors reported when running setup scripts. +- Fixed scale of xor selection rectangle in XY window. + +07/06/2003 +SPoG +- Fixed X Window System error when entering freelook on *nix. + +06/06/2003 +SPoG +- Fixed copy/paste on *nix. +- Changed copy/paste on *nix to use GtkClipboard api. +- Changed copy/paste on win32 to be non-window-specific. +- Further cleaned up MainFrame::Create. +- Changed freelook to use gdk_window_get_origin instead of gdk_window_get_root_origin to place the cursor. + +05/06/2003 +SPoG +- Fixed grey statusbar in 4-way-split mode. +- Redirected gtk messages before creating main window. +- Removed unused XYFriend hack from camwindow. + +04/06/2003 +TTimo +- win32_install.py settings loaded/saved from site.conf + +02/06/2003 +TTimo +- fixed python running with no output. Make sure VC6 finds native Python before any cygwin Python + look at the Directories settings in Tools > Options to either kill the c:\cygwin\bin path, or have Python path first +- renamed dupe files to avoid header collision and general confusion between entity and model +- added win32_install.py to perform post-build install (need to load the configuration paths from a non-cvs stored site.conf file) + +01/06/2003 +TTimo +- bind gen.dsp to makeversion.py +- added a run_python.bat to check for python presence and execute + +27/05/2003 +TTimo +- write makeversion.py module - hook it up to SCons build - cleaner, easier to use +- comment out vfswad build lines. source is still not in tree (bug 800 aftermath I think) + +18/05/2003 +SPoG +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=537 + Changed Sys_FPrintf_VA to immediately process console events during map load. + Changed startup to create main window after QE_Init(). + Fixed loading last map on startup. + Fixed crash on exit. + Fixed colour dialog. +TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=537 + have to delay merging back to trunk. + have a behaviour problem between debug and release builds. + while debug is fine, release is screwed (see bug item) + +-- tagged HEAD with bug537 +-- merge HEAD between merge-gtk2-20030413 and bug537 into the branch ----- + 11/05/2003 + Dan Olofson & TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=812 + workaround for ATI drivers bug (polygon backfaces) + use Preferences > 2D Display/rendering > ATI cards with broken drivers + Riant + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=806 + updated synapse.config for SoF2 png + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=805 + dir_dialog is broken - is only used in prefab path prompt + + -- release-1_3_7 + + 14/04/2003 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=801 + moved "ignoring sprite for entity.." to be a _DEBUG only thing + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=802 + fixed models not drawing on win32. was a setup bug + - OSX setup build updates - added dependency against libpng3-shlibs + - added openurl.sh to open urls on *nix (with setup updates) + Riant + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=803 + RTCW - default_project.proj in setup + + 13/04/2003 + Michael Schlueter & EvilTypeGuy + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=684 + imagepng building under Linux + Riant & TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=797 + fixed texture compression support + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=799 + regen project file from template on version upgrade + updated all default_project.proj to have "version" "1" + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=798 + missing modules/bitmaps/model_reload_entity.bmp from Linux setup +-- end merge HEAD between merge-gtk2-20030413 and bug537 into the branch ----- + +17/05/2003 +TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=537 + http://www.qeradiant.com/wikifaq/index.php?Gtk2%20build%20notes + went through the new dll dependencies, listed required files preparing for a single zip package + updated all the project files to rely on src-gtk2/ + +13/04/2003 +SPoG +- Tagged HEAD with 'merge-gtk2-20030413' and merged HEAD --> port_gtk2_20030307. + +12/04/2003 +SPoG +- Added gtk-2.x libraries to win32 setup. +- Changed win32 setup to use 'dynamic' file-groups, making it possible to add files without + modifying installshield scripts. +- Modified win32/setup.pl to copy setup data to dynamic file-group directories. + +30/03/2003 +TTimo +- added scons scripts. the scons engine is included in the tree. you just need to have python + have ldd -r safe check on .so + TODO: + - make sure it's gcc3 + - check OSX + - add q3map2 build + +29/03/2003 +TTimo +- tracked and fixed the startup bomb on Debian sid: + `pkg-config gtk+-2.0 --libs` + -Wl,--export-dynamic -lgtk-x11-2.0 -lgdk-x11-2.0 -latk-1.0 -lgdk_pixbuf-2.0 -lm -lpangoxft-1.0 -lpangox-1.0 -lpango-1.0 -lgobject-2.0 -lgmodule-2.0 -ldl -lglib-2.0 + http://www.gnu.org/manual/ld-2.9.1/html_chapter/ld_2.html#SEC3 + --export-dynamic + When creating a dynamically linked executable, add all symbols to the dynamic symbol table. + The dynamic symbol table is the set of symbols which are visible from dynamic objects at run time. + If you do not use this option, the dynamic symbol table will normally contain only those symbols + which are referenced by some dynamic object mentioned in the link. If you use dlopen to load + a dynamic object which needs to refer back to the symbols defined by the program, rather than + some other dynamic object, then you will probably need to use this option when linking the program + itself. + this causes symbol confusion, shaders.so's g_ShaderTable suddenly resolves to the core's g_ShaderTable + one is a 'shaders' API, the other an 'appshaders' .. everything gets badly mixed up + added a check in the cons script, using `pkg-config gtk+-2.0 --libs-only-L` `pkg-config gtk+-2.0 --libs-only-l` + (same for gtkglext) + +28/03/2003 +TTimo +- propagate jpeg compile fix from bug750 branch +- use PKG_CONFIG_PATH when building radiant/ (alternate gtkglext-1.0) +- on OSX, you need gtk+2-dev package, and pkgconfig, atk1 + build gtkglext from source http://gtkglext.sourceforge.net + +17/03/2003 +TTimo +- updated the build system to glib2/gtk2/gtkglext + atm it compiles and starts on my dev box (Debian Sid) + but doesn't reach end of initialization, hangs on + q = (qtexture_t*)g_hash_table_lookup (g_ShadersTable.m_pfnQTexmap (), stdName); + in shaders.cpp QERApp_Try_Texture_ForName + need to have the gtk2 dev packages, and libgtkglext1-dev + +12/03/2003 +SPoG +- Replaced alpha-blended area-selection rect with XOR rect. +- Fixed YX/XZ/YZ toggle in floating windows layout. +- Cleaned up xor rectangle code. + +11/03/2003 +SPoG +- Fixed console scroll-to-last-text-inserted. +- Fixed console error/warning colours. +- Refactored or removed WIN32-specific gtk-related stuff. +- Removed win32 SetCapture/ReleaseCapture on GLWindow. +- Removed win32 gtk_main_iteration calls in glwindow mousemoved. +- Cleaned up start-on-primary-monitor stuff. +- Changed main window to use standard save/load window position/size. +- Replaced deprecated gtk_widget_set_uposition with gtk_window_move. +- Removed win32/X gl functions from igl. +- TODO: replace/remove deprecated gtk_widget_usize. + +10/03/2003 +SPoG +- Changed fonts in win32 rc file to 8pt tahoma. +- Fixed flat-grey gui in Regular layout mode. +- Changed main-window save/restore maximized to use gtk API. +- Fixed button_press_event handling on console/entity/entitylist windows. + +09/03/2003 +SPoG +- Fixed crash on shutdown after changing floating-z-window preference. +- Removed win32_realize_floating hack. +- Refactored MainFrame::Create to make it more readable. +- Fixed key_press_event handlers for entity/surface/patch dialogs. +- Fixed delete_event handlers for dialogs derived from Dialog class. + +08/03/2003 +SPoG +- Fixed viewport for entity window comment text. +- Fixed x-shrinking for entity window comment text. +- Fixed menu underscore shortcut hack in MRU list. +- Changed groupdialog to connect switch_page signal after creating all pages. +- Changed gl widget to use gtkglext/pango to create fonts. +- Cleaned up gtkglext glwidget implementation. +- Reduced border size on toolbar widgets. +- Replaced font with font_name in win32 rc file. +- Added viewports for all scrolled text boxes. +- Fixed entities/textures/console window title update when page is changed. +- Fixed floating windows not being transient to main window (don't want them on taskbar). + +07/03/2003 +SPoG +- Created a new branch for the port to gtk 2.x. +- Fixed menu underscore shortcuts to use gtk_label_new_with_mnemonic. +- Fixed global keyboard shortcuts by using mainframe_keypress. +- Fixed use of deprecated gtk_color_selection_get_color. +- Removed use of deprecated gtk_paned_set_gutter_size. +- Replaced deprecated gtk_widget_draw with gtk_widget_queue_draw. +- Replaced deprecated gtk_object_get/set_data with g_object_get/set_data. +- Replaced deprecated gdk functions with 2.x equivalents. + + +----- branch port_gtk2_20030307 ------ + + +13/04/2003 +Michael Schlueter & EvilTypeGuy +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=684 + imagepng building under Linux +Riant & TTimo +- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=797 + broken texture compression support + + + +31/01/2003 +SPoG +- bug #752 - Construct fix for ppc, patch applied. + +26/01/2003 +TTimo +- bug #750 - revamp of the setup stuff on Linux + (under way, see bug for progress - feel free to help!) +- reworked the cons scripts, setup is hooked in to cons now + also, added gcc version select on command line, using Cons_gcc.pm utility + +22/01/2003 +TTimo +- merged merge-1_2_10-post back to trunk + + +=============================================================== +-- merging release-1_2_9 -> merge-post-1_2_10 into trunk + trunk before merge is tagged pre-merge-1_2_10 +=============================================================== + +22/01/2003 +TTimo +- finished up the TODO items, turned into bug items or dropped them. branch is ready to move back in to trunk + +18/01/2003 +TTimo +- fixed ID_SELECTION_MERGE + +17/01/2003 +TTimo +- fixups + FlushReloadSelectedToolbarButton -> CFlushReloadSelected + incorrect naming: RadiantToolbarModuleManager -> CRadiantToolbarModuleManager + ToolbarButton -> IToolbarButton, and C* implementations + http://www.qeradiant.com/wikifaq/admin.php?Code%20Conventions + +14/01/2003 +ydnar +- Minor Cons fix for OS X (bug 729) + +13/01/2003 +ydnar +- GtkRadiant now builds on OS X, Linux, and Win32 out of the same tree +- OSX build uses gtkfileselect-linux now, as the Darwin version was broken/old + fixme: change this to use OS X open dialog box or something? +- Minor fixes to a few files to fix gcc warnings +- Model module now builds on OS X and Linux, using Synapse properly +- PicoModel change to invert T coordinate on ASE models +- Q3Map2 change to export ASE models with T coordinate flipped +- Misc Q3Map2 changes + +09/01/2003 +ydnar +- Updated Construct with Darwin/OS X ld flags for 4MB stack size +- Misc Q3Map2 updates (2.3.35-dev) + +05/01/2003 +ydnar +- "angles" key now properly ordered, to work with current mathlib + (also changed in Q3Map2) + +31/12/2002 +ydnar +- PicoModel: Minor fix to MDC loader (naming/define) +- Q3Map2: 2.3.34-pre-1 updates +- MapXML dsp unix->dos newlines + +29/12/2002 +SPoG +- Merged q3map2-texturing prefs key. +- Merged vfs check for gamemode project key. + +27/12/2002 +TTimo +- fix GetTickCount stuff +- added q3map2 cons script +- fixed Linux build + +23/12/2002 +SPoG +- Added model cache API, moved model cache implementation from entity module to core. +- Added file-type registry API, replaced core file-type manager with registry. +- Changed model module to register supported file types with core registry. +- Removed or #ifdef'd non-functional code from model module. +- Added support for misc_gamemodel and model_static to entity module. +- Cleaned up entity module's on-epair-changed API. +- Moved light-entity-specific code to a seperate file in entity module. +- Cleaned up file dialog interface - specify file-type-lists with a string. + +22/12/2002 +SPoG +- Ported camera plugin to synapse, adding support for camera and ui APIs. + +20/12/2002 +SPoG +- Fixed default prefs setting for selected-brushes-camera, gridmajor-alt and gridminor-alt. +- Merged CEntityEclassModel::Draw in entity module. +- Ported imagepng module to synapse. +- Fixed warning for CamDragMultiSelect preference bool used as int. + +19/12/2002 +SPoG +- Fixed white-textures bug caused by texture compression preferences. +- Ported light-radius rendering to 1.3 entity module. + +18/12/2002 +SPoG +- Merged win32 project files, with the exception of camera plugin. +- Ported model module to synapse API. +- Redesigned toolbar API to remove gtk-dependency from toolbar plugins. +- Refactored window-position preference save/load. + +17/12/2002 +TTimo +- kick doxygen generation for branch merge-1_2_10-post + +15/12/2002 +TTimo +- having the linux version compile and start again. took out numerous elements while merging, built a list of TODO stuff + the main thing to do being to bring the win32 build back up too, then to go through TODO list and fix stuff + until the win32 version runs too, I check this in to a seperate branch merge-1_2_10-post +- There is quite a massive update in mainframe.cpp switch case for all events. + Looks like it's just a reordering of stuff, but it looks bad in the diffs. +- added m_MapReg pattern + +- At some point, I'm thinking that forcing correct TAB/SPACE conversion on the server end would be a good thing to have. + Nazisticly forcing the formatting sounds like the only viable solution. + + 11/12/2002 + RR2DO2 + - #418, mdc load and display (RTCW) + - #597, CenterCamera shortcut + Use Ctrl+Shift+TAB to center the views onto the current camera location + - #714, bitmap loading fixes and speedups + - #715, fixed Alt+Shift cycle/drill select to work with brush-based entities + EvilTypeGuy + - #718, fix compilation warnings + Riant + - #707, fixed HM mode in STV:EF + + 10/12/2002 + EvilTypeGuy + - Fix gcc3 compilation warning + EvilTypeGuy and X-Man + - Fix OpenURL so browser launching works on XDarwin (Mac) systems. + + 8/12/2002 + RR2DO2 + - #710, AssignSound pattern + - #711, SoF2 model_static drawing + - #713, sync 2d and 3d rendering of models + - #238, apply 0..1 T range when Fitting a patch (instead of 0..-1 previously) + - #633, Add ability to change default color in 3D window Misc > Colors > Camera background + ydnar + - Q3Map 2.3.33 (see changes.q3map2.txt) + - Quake 3 + TA common.shader updates (q3map_terrain, hint) + + 3/12/2002 + TTimo + - merging Stable-1_2-Apple into Stable-1_2 + - why INSTALL.TXT? re-used INSTALL, updated to point to wiki + - why the -machinedump test against i386-redhat-linux? removed + - using $is_darwin flag instead of $gcc_machine tests in the build scripts: + gotta leave some room for a Linux ppc build, and darwin x86 + exporting it for use in sub scripts + - the addition of ccache support broke some Apple SConstruct patches to $ENV{PATH}, fixing + merged version is compiling fine on Debian Sid + checking in on a branch, need to validate win32 build and OSX build before applying in Stable-1_2 + + -- release-1_2_11 + + 30/11/2002 + TTimo + - added seaw0lf to credits + - ydnar's changelog.q3map2, added to global.xlink and Linux setup + - 1.2.11 version tag + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=699 + updated IS setup for q3map_terrain keyword + also fixed details in STVEF media + Arnout + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=569 + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=698 + fixed drill select, Ctrl bug in vertex mode, and updated changelog.txt + + 29/11/2002 + TTimo + - 1.2.11-rc1 + - update changelog credits links for release + - update linux setup, putting EULA and new README instructions + + 28/11/2002 + ydnar + - Removed redundant 'p' from "developers" + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=637 - fixed + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=694 - fixed + - Updated to Q3Map 2.3.32 + - Added epsilon to texture plane choose code to eliminate numerical + inconsistencies on brush faces slanted at 45 degree angles (bug 637) + - Fixed bug in lightmap export after lighting when map contains 0 BSP lightmaps + - Adjusted some light tracing constants to fix certain brush/patch seam shadows + - Tinkered with skylight code again + - Fixed bug where lightgrid would be black if level was compiled with -nogrid + - Fixed -approx code to work in floating-point space, using _minlight + - Fixed bug where vertex light code was using invalid pvs data to create + light list for surface, leading to incorrect vertex lighting + - Fixed related bug in anti-light-leak code that was causing brush faces to go + black (bug 694) + - New: _minlight sets _minvertexlight and (new) _mingridlight automatically + - New: _mingridlight key to set minimum grid lighting + + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=637 + added preference setting in Preferences > BSP monitoring + added an item on the wiki + + 27/11/2002 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=662 + picomodel-based model.dll module (new model.dll, removed md3module.dll) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=664 + media/setup updates for q3map2 support + added modified quakev3.qe4, bumped internal version to 4 for all games + SOF2 and JKII were forcing BSP monitoring off because of sof2map, now only printing a warning + added -rename to SOF2 BSP phase + Q3 & RTCW new templates are working + haven't tested the STVEF & SOF2 versions + updated IS setup scripts to make sure quakev3.qe4 is updated in nightly release + (not needed on Linux, we will be doing a full release) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=662 + updated win32 setup to provide right model.dll stuff + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=621 + typo was causing memory error + + RR2DO2 & TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=662 + more model fixes, fixed the Linux build to build model.so + added search path to modules/ for bitmaps + model reload, patch and bitmap + + 26/11/2002 + RR2DO2 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=569 + area select - Alt+Shift for area select (complete tall) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=621 + broken undo creating ghost undo entities (and trashes memory) + partly fixes the issue, it's a memory error still + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=659 + updates to RTCW camera plugin - works in 4 view mode + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=663 + more fixes to plugin API + + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=638 + libpng in the core DLLs instead of SoF2 (for q3map2 dependencies) + putting q3map2 binary with the Core Binaries + + 25/11/2002 + TTimo + - added ccache support to cons build system - http://ccache.samba.org/ + + 21/11/2002 + ydnar + - minor bugfix to PicoModel ASE material loader + - Q3Map2 updated to 2.3.31 (Splash Damage) + - Stitching the edges of lightmaps on patches that wrap around (cyls and cones) + so the seam is no longer visible + - The -patchmeta switch works better now, the patches are still stored in the + BSP for collision, but are pre-tesselated into nonplanar meta surfaces for + more efficient rendering + - Better, more uniform lightmap sample position finding on patch meshes + - Moved q3map_tcMod and q3map_alphaMod processing to the final phase + - New: q3map_skylight AMOUNT ITERATIONS to replace surfacelight on sky surfaces + for much faster and more uniform sky illumination + - Fixed bug in PicoModel ASE material parsing code + - Fixed a few seam/lightmap precision/projection errors + - Increased MAX_SHADER FILES to 1024 and fixed overrun error when more than that + number of shaders was listed in shaderlist.txt + - Increased a few compiler maximums for larger maps + - New: -np N switch on BSP phase, works like -shadeangle, in that it forces all + planar shaders to be nonplanar with the shading angle specified + - New: -nohint switch on BSP phase, omits hint brushes from compile for testing + - New: -debugaxis switch on light mode. Colors lightmaps based on their lightmap + axis (which direction the lightmap was projected on) + - New: -debugorigin switch on light mode. Colors lightmaps based on the luxel + origin relative to the raw lightmap's bounding box + - New: -debugcluster switch on light mode. Colors lightmaps based on the pvs + cluster the luxel falls into + - New: -convert switch to convert BSP to ASE file (experimental) + - New: q3map_lightmapmergable directive to allow terrain to be mapped onto a + single lightmap page for seamless terrain shadows + + 18/11/2002 + TTimo + - fixed pk3man build system to work with new cons layout + - fixing linux setup system to work with new cons layout + Linux 1.2.11 will be a full setup, much easier that way + - update makeself to the latest (and best) version + - add q3map2 to Linux setup. goes in core (g_strAppPath) + NOTE: has a dynamic dependency to libpng + - pk3man still has issues with the zlib code that's been thrown in it + unresolved which I don't have time to look at + since we plan to drop pk3man in 1.3, dropping it now is just as good + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=644 + detect GOTY install from registry and use it as default path + + 13/11/2002 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=639 + reworked to have the UI in game settings dialog + (this is strictly win32 thing, if that broke Linux build, then fix the typos) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=630 + mouse AngleSpeed setting was getting clobbered. fixed and upped the max values + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=623 + applied a fix, rolls back the values when the compression formats are not supported + fix ain't very clean, if we have to deal with extensions some more, we need to deal with the settings persistance better + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=624 + updated the setup code for town_*.shader (both in full setup and update) + updated files in WolfPack + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=672 + using a QE4_VERSION define, added a message if there's a project template with wrong version + + 12/11/2002 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=671 + guard junk.txt path between " " + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=663 + fix to CommitBrushHandleToEntity stuff + + RR2DO2 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=660 + previously you could select patch control points when patch selected + vertex edit (V) in 2D view. Works in camera view now + + 12/11/2002 + TTimo + - nudging zerowing to trigger Stable-1_2-Apple doxygen generation + http://zerowing.idsoftware.com/doxygen/ + + 11/11/2002 + TTimo + - http://ttimo.net/web/anjuta + modified the .prj to work with the cons patches (linked dirs) + still way experimental + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=651 + fixing linking for radiant.x86 + + 10/11/2002 + RR2DO2 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=669 + patch inspector bug - fixed + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=663 + fucked up change in the plugin API caused breakage of several plugins + still have to fix bobtoolz http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=665 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=661 + Undolevels not set properly + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=660 + drag selection to the camwindow for patches in controlpoint edit mode + (not sure about the actual shortcuts, Ctrl+Alt on my current Linux setup) + + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=657 + mark map modified on editing entity keys + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=651 + added the correct link and ldflags statements to have static linking everywhere + didn't check gcc 3 build, check correct static on Debian Sid and holy box + has a $staticstdcxx in Construct to toggle On/Off if needed + + 09/11/2002 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=651 + reworking the cons building. support for gcc2 and gcc3 + cons -- gcc= + read gcc version and configure accordingly + changed _NO_STLPORT to Q_NO_STLPORT + independant BASE_CFLAGS and BASE_CXXFLAGS + correcting usage of CC/CXX for c/cpp source and linking + fixed missing -lz in vfspk3.so + changed the way we build curry.so, works from the GtkRadiant tree now + (NOTE: gcc 3.2 build of curry.so spews quite a few warnings) + tweaked the way we do -fno-rtti -fno-exception + + 04/11/2002 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=638 + .qe4 version 4, udpated q3's .qe4 template, update setup to put q3map2 in the right place + bumped version to 1.2.11-test for test setups + + 03/11/2002 + ydnar + - fixed bug in jpeg loading code (4 components instead of 3 for RGB images, mh) + - updated PicoModel to 0.8.8 and Q3Map2 sundry fixes (2.3.29): + - Merged with latest CVS, fixed minor issues with matrix order + - Fixed minor Sys_FPrintf/Sys_Printf substitution typo in Q3Map2 + - Expanded debug colors to 12 for debugging surface meshes + - PicoModel: fixed ASE loader to support > 1 texture coordinate per-vertex, + so more models supported correctly, also loading vertex normals + - PicoModel: md3 shader names are now cleaned. Suffixes (such as .tga or .jpg) + are stripped, and \ path separators are changed to / + - New: Add :q3map to the end of any shader name, and it will be interpreted as + the named shader minus :q3map. Example: + textures/shaderlab/concrete:q3map -> textures/shaderlab/concrete + One potential use is the -approx feature to collapse lightmapped surfaces + into vertexlit surfaces, saving lightmap space/memory + - New: q3map_clipModel -- does what you think it does, sort of. This code ix + really experimental, and should *only* be used on large models such as terrain + (not small decorative models). This code will be evolving. Note: the shader's + surfaceparms are inherited by the magic clip brush, so if you have nonsolid + in your model's shader that uses q3map_clipModel, then the brush will also + be nonsolid + + 03/11/2002 + TTimo + - cleaning up some cons stuff, checking that the setup building process is still good on Linux + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=622 + updated Linux setup to put the bitmap + + 02/11/2002 + ydnar + - PicoModel: replaced stricmp with _pico_stricmp + + 02/11/2002 + ydnar + - PicoModel: added obj.c and ms3d.c, removed wfobj.c + + 02/11/2002 + ydnar - seaw0lf + - Updated Q3Map2 to 2.3.29 sources + 2.3.29 + - Merged with latest CVS, fixed minor issues with matrix order + 2.3.28 + - Bug 654 (http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=654): + Fixed problem where brush faces, drawsurfaces, and surfaceparms weren't living + together in perfect harmony (terrain surfaceparms now inherited by brushes) + - Nodraw fog works now, albeit when you're underneath, surfaces above don't get + fogged properly. Could be good for foggy water where you want the above-water + portions to only be occluded by the water surface + - Bug 656 (http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=656): + Number of lightgrid points displayed (byte size is currently out of proportion + due to internal storage format) when Q3Map is called with the -info switch + - Fixed wack surface merging bug where code would attempt to merge triangles not + adjacent to the current set, causing bad lightmap projections on nonplanar + surfaces + - Fixed tiny 1-character bug in 2d lightmap texture allocator where adjacent + luxels were being checked for occlusion rather than the actual source luxel + 2.3.27 + - Fixed minor bug in scriplib bugfix where the last character in a file wasn't + being read. + - Fixed bug where 0-area or bogus triangles were causing crash in MapRawLightmap + if they used a shader with a normalmap (thanks ShadowSpawn) + - Fixed bug where lightmaps were getting hosed levelwide on a prerelease version + of 2.3.27 + - Fixed bug where lightmaps were getting knackered on models and certain patches + - Merged latest PicoModel version from seaw0lf, adding support for ASE and WF OBJ + models (preliminary) + - Increased MAX_MAP_PLANES to 0x40000 (~256k) + 2.3.26 + - Now using GtkRadiant's libpng and zlib config (linked as DLLs) + - Fixed bug in script parser where repeat calls to GetToken() were causing + memory corruption + - Fixed SOF2 -rename bug + - When using -game sof2 or -game jk2, the -flares argument is implied + - Added -noflares argument to disable the above behavior + - Added support for flares on entities. Use one of the following keys: + "_flare" "1" -- use default flare (different for each game) + "_flareshader" "path/to/flareshader" -- use a specific flare shader + Note: This only matters in SOF2/JK2 now. Make a light targetted (a spotlight) + to get it to aim the correct direction, otherwise it defaults to pointing + downward. You cannot have omnidirectional flares + - Lightgrid size is automatically increased to accomodate large maps. The + MAX_MAP_LIGHTGRID error will never happen again + - Update PicoModel to 0.8.7 sources + - ASE support + - Alias|Wavefront OBJ support + - .remap shader remapping suport + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=655 + handle grayscale jpegs gracefully + - mathlib: fixed VectorClear(), got rid of braces + - scriplib: fixed double-free memory corruption bug + - radiant: added new color scheme to emulate Lightwave/Maya/3DS Max + + 02/11/2002 + TTimo + - too many issues with build system reading system's libjpeg.h instead of libs/libjpeg.h + renamed libs/libjpeg.h to libs/radiant_libjpeg.h, updated sources + + 29/10/2002 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=639 + running from a network share - saving prefs per-user + + 27/10/2002 + TTimo + - merged in some more m4x4 code for q3map2 + - fixed unresolved code in picomodel (strlwr / strnicmp) + - reworked the tools building to build both q3map and q3map2 without trouble + + 25/10/2002 + ydnar + - q3map2 and picomodel source, initial checkin to Stable-1_2 branch (does not compile yet, tweaking to be done) + + 23/10/2002 + TTimo + - camera.dll goes into $(RTCWRADIANTDIR)/plugins instead of $(RTCWRADIANTDIR)/modules + fixed up camera compile (exports) + added camera bitmap (plugin toolbar) + + 21/10/2002 + TTimo + - quickfix to the build (typo) + - changed dynamic linking on Linux to look for libGL.so.1 by default + fixes "all textures are blank" Linux bug with NVidia cards + (you still have to have a working NVidia GL installation though, xlibmesa-dev on Debian screws things up) + - checked in modified q3 .qe4 with q3map2 menu (see bug #638) + + 09/10/2002 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=622 + reworked the plugin toolbar to rely on interface instead of straight exports + cleaned up the botclip monsterclip Brush_Draw filtering, added proper selection filtering (Brush_Ray) + merged bug-622 back into Stable-1_2, bug-622 branch is dead now + + 06/09/2002 + James Monroe - RR2DO2 - TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=619 + light globes, applying patch by RR2DO2 built from the initial light globe code + - note to self: indent -kr -nut -st -ts2 -i2 + + RR2DO2 - TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=622 + massive patch update from SD's GtkRadiant + - camera plugin: new bitmap + fixed Linux install path for camera plugin to wolf/plugins + - fixed various warnings in camera build gcc / Linux + - fixed pref crash if plugin toolbar disabled + - fixed Gtk-WARNING on bad cast in AddPlugInToolbarItem + - renamed the new select to 'Use paint-select in camera view:' in prefs + (camera paint-select, should be our default name for this) + + NEW: you can 'paint select' in the camera view: 'camera paint-select' + press shift and move the mouse over the camera view to paint over brushes you want to select + configurable in prefs to enable / use Shift key, or use 'classic mode' ctrl+alt + + NEW: light radius drawing + Added in-editor light envelope drawing. Outer circle is max envelope, + inner fullbright radius. Optional classic mode emulates the similar drawing + from Rituals and Ravens tools (not q3map correct, easier for the level designer + to understand/legacy). + + NOTE: 'angles' is q3map2 only + NOTE: could manipulate angles directly from the views (2d and 3d with some handles) + + NOTE TO SELF: + hey guys .. just a quick question if you don't mind .. I'm trying to track a Gtk-WARNING .. is there a way to make those apps cause a break to track them easily ? + just run your app with --g-fatal-warnings + + TODO: add new bitmap to win32 & linux setups + TODO: don't use exports for the plugin toolbar, use entry functions + do something like CPlugIn::InitBSPFrontendPlugin + TODO: botclip is broken with the new Brush_Ray code + + initial ChangeLog for the patch: + + 28-09-2002 + Arnout + + Added 'angles' support for models (misc_model/misc_gamemodel). + + Prevented pivot drawing of model from scaling and rotating. + + Cleaned up the dropdown boxes in the preferences a bit (all use + tables now, so not multiline). + + Added 'Classic Key Setup' option to camera paint select configuration, this + drag-selects with ctrl+alt instead of shift. + + Changed XYWnd::PositionView to position on the center of the + selection, not on the mins. + + 27-09-2002 + Arnout + + Added in-editor light envelope drawing. Outer circle is max envelope, + inner fullbright radius. Optional classic mode emulates the similar drawing + from Rituals and Ravens tools (not q3map correct, easier for the level designer + to understand/legacy). + + 26-09-2002 + Arnout + + Upped MAX_TEXTUREDIRS to 256 (from 128). + + 25-09-2002 + Arnout + + Fixed patches not being drawn in XY window with colour of parent + entity. + + Made paste to camera snap destination spot snap to grid. + + 18-09-2002 + Arnout + + Changed Select_Reselect to be much faster. + + 12-09-2002 + Arnout + + Fixed curve point drag-selection area not showing properly in XY + views. + + Fixed size info breaking over 9999.9 units. + + Fixed AllocateSelectedPatchHandles not setting patchesmode to + ESelectedPatches. + + Changed the horizontal and vertical tc shift spin control to have a + limit of 8192. + + Moved SPoG's implementation of redisperse cols to a seperate function + and reinstated the old code. + + Added 'Paste to Camera', shortcut Alt+V, which pastes the contents of + the clipboard to the current camera origin. + + Added centerview functionality to 4 window mode. Ctrl+tab will focus + on the selection, or if non existant, on the camera. + + 11-09-2002 + Arnout + + Made sure settings set in savedinfo.bin get initialized to their + proper defaults. + + Added botclip filter (filters *botclip* and *monsterclip*). + + 10-09-2002 + Arnout + + Removed .reg from normal map saving, can only save as region + using 'Save region'. + + Added outline style cycling (j) cycle between z buffered outlines and + selected colour rendering. + + Added colour dialog to pick the colour of selected surfaces in the + camwindow. + + Third coordinate for clip points now gets set to the center of the + selection. + + Changed arbitrary rotation dialog to accept negative angles as well. + + Changed texture alignment dialog to accept values up to 2 decimal + points. + + Fixed entity inspector to say 'Textures:' in the window title. + NOTE: still broke in floating window mode + + Changed entity inspector so that tab doesn't clear the epair value + field anymore, so it retains the value while jumping to it. + + Disabling camera paint-select now returns selection behaviour for groups to + the old behaviour as well (shift+click selects whole group). + + Changed load_plugin_bitmap to load bitmaps from g_strAppPath if + g_strGameToolsPath fails. + + Added plugin toolbar and api. + + Fixed m_pfnCommitBrushHandleToEntity, wasn't creating brushes + properly (well, not at all really). + + Older changes: + Arnout + + Added misc_gamemodel drawing. + + Ported camera paint-select over from 1.3. + + Fixed statusbar display of text (removed a bunch of \n's). + + Added area selection in 3d view for patches. + + 30/09/2002 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=556 + quickfix crash bug + + 27/09/2002 + TTimo + - more CORERADIANTDIR cleanup (q3data) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=602 + added cascading to the entity submenu (doesn't cascade the main menu, only the sub ones, NPC_* for instance) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=556 + with clip and caulk filtered out, won't be selected anymore in camera view (i.e. selecting invisible faces) + added SF_CAMERA to the flags in the selection process + cleanup up various ugly syntaxes in the selection code: + don't ever do if (flags == SF_SINGLEFACE) on a bitmask and assert that the other flags will always be NULL + don't do arithmetic on bitmasks: + if ( (flags & SF_ENTITIES_FIRST) && t.brush == NULL) + return Test_Ray (origin, dir, flags - SF_ENTITIES_FIRST); + is WRONG + using flags & ~SF_ENTITIES_FIRST is the appropriate way + + 23/09/2002 + Riant + - new IS scripts to go with recent media updates + Riant & TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=615 + reworked the fix to use "caulk_shader" in .game + updated IS .rul script to generate special values for Sof2 and JKII + + 21/09/2002 + Riant + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=595 + more texture compression, dialog and settings + Michael Schlueter + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=592 + fixes to the Linux build system, exclusive q3 or wolf working now + + 22/08/2002 + EvilTypeGuy + - Fix @*$&)@)$$ memory leak of my own doing, yes it's really been in there this long. + This should help memory usage drastically, especially when flushing & reloading + the same sets of textures, GtkRadiant's memory usage no longer becomes heinous. + + 14/08/2002 + EvilTypeGuy + - Fix build on some linux boxen by including qertypes.h for proper boolean type declaration + + -- release-1_2_10 + + 16/08/2002 + TTimo + - STVEF media update finalized (some .def) + - 1_2 Core Update for shader manual update + - in JKII, typo with nar_shader? replaced by nar_shaddar, with proper support in update too + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=530 + Q3/TA media update with cleaned up shaders + - validated the update content by a diff between 1.2.9 + 1.2.10-update and 1.2.10 full + - added a DO_NIGHTLY_BOOL to setup.rul AND a warning during setup about update content for games that are not installed + - built 1.2.10-sof2, SoF2 full install + + 15/08/2002 + Michael Schlueter + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=590 + added the option to build a Linux setup with the debug binaries + + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=530 + cleaned up shaders, done nightly setup update on Linux + updated Linux nightly for 1.2 to use /usr/local/games/GtkRadiant-1.2 as default base + - using version 1.2.10-update. Full Sof2 setup will be 1.2.10-sof2 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=426 + don't straffe when using Ctrl+Shift(+Alt) + - camera.so RTCW plugin in Linux setup + - quickfix to non-initialized var in camera code + - awfull piece of work that had been completely left out, nightly elements for JK2 and STVEF + added JKII media update and STVEF media update (for the DIR_GAME elements) + JKII nightly is finalized + + 14/08/2002 + TTimo + - fixed a missing file + - Linux build quickfix + - fixed silly rendering bug + - added pref to force texture compression off (hey why would you do that??) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=466 + fixed MAX_POINTS_ON_WINDING overflow in q3map (and relevant code to handle in radiant) + + 13/08/2002 + TTimo + - cleared up notexture (dead code) + - cleaned up QERApp_LoadTextureRGBA gamma table init + - having a shot at 1.3 texture compression + sees the extension, binds the texture with the currect setting + but rendering is fucked .. someone explain? + + 08/7/2002 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=586 + search and destroy Q3Radiant -> Radiant + + 07/7/2002 + SCDS_reyalP + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=526 + wolf_entities.def update + + riant + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=548 + STV:EF updates + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=541 + SOF2 updates + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=584 + JKII updates + + 06/7/2002 + Riant + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=541 + Sof2 patches and IS setup + TTimo + - game pack prompt asking about STVEF, fixed + - mp_examples was leaked and non lighted, fixed + - imagepng.dll goes into Sof2 install / modules, and not in DIR_CORE + - libpng12.dll needs installed only with Sof2 pack (added 'SOF2 Pogram DLL') + default texture scale is 0.125 + + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=582 + nomipmap -> nomipmaps in shader manual + - removed libs/pak, this was still being linked in to Radiant, but not used at all + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=580 + .PK3 are recognized along .pk3 files (strcmp ->strcasecmp) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=386 + added the RTCW camera plugin to IS setup + + Michael Schlueter + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=581 + GL warning fix + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=567 + GL font display fixes (mostly Linux) + applied the changes with some tweaking + + 31/6/2002 + TTimo + - compiling the camera plugin on Linux: + move the GUID and other misc compatibility definitions to include/misc_def.h + GetTickCount being used in camera.so, this is from radiant/missing.cpp (unresolved) + -> use QGetTickCount instead (in main function table) + + 30/6/2002 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=530 + cleaned the .shader from 'light 1' statements + updated the IS script for the updated .shader + + 17/6/2002 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=541 + Sof2 support, PNG format + wrote imagepng module, dynamic dependencies to zlib and libpng + changes in the core: + some hardcoded to "sof2.game" for png interface loading and extensions + in GetTextureExtension, killed outdated support for texture plugins + if ! "sof2.game", png is not loaded, support disabled + http://zerowing.idsoftware.com/libpng/ + correctly configured for VC build (post build steps and dependencies) + is required on win32 to build imagepng + - added m_pfnGetGameFileName to the main function table (was needed for png stuff) + - cleaned up the QERApp_LoadTextureRGBA path + using (unsigned char* pPixels, int nWidth, int nHeight) + cleaning up internal access path + RR2DO2 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=386 + camera plugin for RTCW + TTimo: wrote the .dsp, post build steps etc. + IMPORTANT: you need to have RTCWRADIANTDIR env variable pointing to the RTCW Radiant files + (default C:\Program Files\Return To Castle Wolfenstein\Radiant) + + + 12/6/2002 + RR2DO2 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=299 + MP/SP pk3 filtering in VFS + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=386 + .camera support: splines library, camera plugin + TTimo: portability fixups, cons build, guarding pragma, __cdecl BOOL + virtual functions but non-virtual destructor + declaration with no type + int idCameraFOV::start - control reaches end of non-void, making it void + enumeration not handled in switch + no _MAX_PATH, the portable one is PATH_MAX + implicit declaration of int _fullpath + for(int i = 0; .. + struct _IO_FILE has no member named '_bufsiz' + stricmp -> Q_stricmp + attempt at implementation in .h file (InitIglToQgl) + camera stuff still vastly broken (particularly on Linux), need to check in because of new fixes incoming + djbob + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=17 + quick fix to spawnflags getting corrupted when multiple entities selected + (doesn't completely solve the problems we have with spawnflags yet though) + +=============================================================== +END -- merging release-1_2_9 -> merge-post-1_2_10 into trunk - END +=============================================================== + +12/12/2002 + Hydra + - #197, HL support update + +11/12/2002 + TTimo + - added cmdlib dependency to mapq3.so (fixes unresolved) + +25/10/2002 + Hydra + - vfsGetFullPath() can now (optionally) search PK3/WAD files + - Half-life map loading is now un-borked (my original patch worked + but some conditional code in the patch was incorrectly applied. + That, coupled with the missing vfsFileExists and vfsFindFile replacements) + - A patch to imagehl/lbmlib.cpp/LoadIDSP() was missed out, causing all sprite + models to be reverse-rendered (due to an inverted alphamask) + - Renamed HydraToolz to HydraToolz-HL as it's half-life specific + changed project files and renamed all appropriate files and directories + (for the merge, just delete contrib/hydratoolz and apply the diff) + + - Comments on previous notes: + + - TODO: need to rationalize where the modules are placed and identify HL specific modules + (this affects the build system / post build step too) + imagehl and spritemodel are halflife specific and can be placed in + either $coreradiantdir/modules or $hlradiantdir/modules + I've updated the .dsp files so that they are copied to $coreradiantdir/modules + spritemodel can actually be used for other engines, not just HL so it makes sense + to keep it in $coreradiantdir/modules + hydratoolz is a half-life specific plugin and must go in $hlradiantdir/modules as it + is NOT to be used for any other engines. I've also updated the "about text" to + reflect this. + +14/08/2002 + EvilTypeGuy + - fix build process for textool plugin on some Linux boxen by including qertypes.h + +11/06/2002 + TTimo + - spritemodels in build system + - applying HL setup patch (att 270, bug 197) + - the templating went one filename seperator too far, causing all *.fgl to be modified .. + fixed so that we only have the relevant changes + - modules added to main Executable Files, TODO for later will need to identify what is HL specific + - .game generation: don't want enginename yet, gamename is ok + - updated HL .game generation for eclass_singleload and no_patch + - update hydratoolz location in setup + - update maphl in synapse.config + +07/06/2002 + TTimo + - realized that \func doesn't work in doxygen, should be \fn (updated everywhere) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=197 + applying the HL patch (see original list of changes below) + - ugly eclass API changes + eclassfgd/plugin.cpp.rej, eclass API changes involved, a bunch of .rej + SupportsMultiple tries to send configuration information from the eclass format file module to the eclass manager + dropping it, using a proper configuration node instead (eclass_singleload) + - added "no_patch" prop to disable patch support + the patch toolbar prompts are only present in prefs if there is patch support + otherwise everything is force-disabled + - PFN_VFSFINDFILE PFN_VFSFILEEXISTS: + one of the problems we have is that the 'manager' code and file format code are in the same module + (i.e. vfspk3 / vfswad: two formats, but the manager part is pretty much the same) + vfsFindFile(relative filename): + this worked by searching through the list of loaded pk3/wad files for the file + then trying to search through the search directories + - wasn't properly documented about what it does / how is the search performed + - not consistent with existing code, duplicate of vfsGetFullPath for the most part + can't be added to the VFS API as-is, it would confuse the interface + - usage of vfsFindFile in the code doesn't justify the way it proceeds for search + foxing it, replacing by calls to vfsGetFullPath + vfsFileExists(relative filename): + returns wether a file exist, can be flagged to search in pk3/wad or straight filesystem + - this is a duplicate / particular case of vfsGetFileCount + foxing it too, we need to extend and update vfsGetFileCount instead + - mapq3: the changes completely fucked q3 map parsing + need reorganization. same module provides parsing for all .map based formats + we use wrappers around the actual calls and globals in the module to select formats + MAPVERSION_Q2 and MAPVERSION_Q1 don't need to be there yet, they are not supported + MAPVERSION_HL means WC >= 2.2 + (when introduced, MAPVERSION_Q2 would be Q2 or qer+hl plugin (same)) + MAPVERSION_HL uses "maphl" minor name (instead of mapq2) + cleaned up the Q3 read/write code that got broken + cleaned up various commenting/hack that deal with Q2!=HL format .. we'll see about Q2 when we actually do it + bad cut and paste from cmdlib code, using actual dependency to cmdlib instead (see below for some cmdlib updates) + - took out all SafeRead SafeWrite code from cmdlib, removed annoying cmdlib dependency to Error function + all file access go through VFS module, the cmdlib 07/06/2002 15:47file code was way old + - radiant/points.cpp pointfile code changes (that's used only for non-monitored compiling now) + - applied patch 267 (hydratoolz fixes) + - commented out some bworldcraft flagged stuff in mapq3/parse.cpp + - updated the .dsw .dsp to compile and copy HL stuff + - TODO: need to rationalize where the modules are placed and identify HL specific modules + - TODO: seems to be a synapse crash when unloading plugins (hydratools) + (looks like I didn't look at the plugin unload code yet actually) + - TODO: make sure HL setup puts eclass_singleload="1" and no_patch="1" + - TODO: WATCHBSP_KEY and TEXTURE_KEY hardcoded for HL need cleanup + - TODO: imagehl duplicates some image functionality + imagehl is supposed to be only for HL-specific image formats + it 'adds' the required formats to the stuff that image makes available for everyone already + - TODO: HL doesn't have a BSP menu! + - TODO: rename mapq3/ into map/, the map module handles all .map formats + - TODO: it's likely that we only need a vfs/ module instead of vfspk3/ and vfspak/ + think about it, see if we really act on this (or do we need to abstract the manager and some file format modules) + - TODO: HL synapse.config needs to use maphl + - TODO: wtf is enginename="quake2" in hl.game + - TODO: I don't have a sample HL map to play with, so I didn't test the changes against + +05/06/2002 + TTimo + - fixups to make 1.3 start (Q3 mode) + - turned off C++ exception support in the modules/plugins, as we don't use it + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 + exit properly if missing chunks in synapse.config, don't crash + + Hydra + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=558 + fix for version check in release build + + ====================================================================================== + -- http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=197 + HL support patch + ====================================================================================== + 04/6/2002 + Hydra + - Patched in some CVS changes and fixed a little issue with the + new entity file loader code. + + 28/5/2002 + Hydra + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=544 + Selected Entity Bounding Box obscured by brushes fix. + - Moved the "wad" keypair creation code from the Map module into + a new plugin called HydraToolz, this means that wad keypair is + done manually by the user, as in fact it should be. (as the + order of the wads is actually important). + - Fixed a problem with the wads in the wad list being re-ordered. + + 27/5/2002 + Hydra + - Created an inital implementation of a sprite model plugin. + According to the powers that be, it seems creating a model + plugin is hackish. + It works ok, but there is no way to attach models (sprites if you will) + to non-fixedsize entities (like func_bombtarget) + Also, I can't get the alpha map stuff right so I had to invert the alpha + mask in the spr loader so that 0xff = not drawn pixel. + + 17/5/2002 + Hydra + - "Wad" keypairs are now used when loading a map and speeds up map loading + significantly. This sorts out quite a few issues that could otherwise occur. + - Map loader now uses textures from wads listed in the the "wad" keypair first. + - Added a texture name mapping cache system to the .map loader + this significantly improves load times of maps that don't store texture + names along with paths (e.g. "mytexture" not "mytextures/mytexture".) + - Added vfsFileExists() to the vfs table (for above) and added it to + vfspk3 and vfswad + - Map loading and saving times are printed to the console. + - Wad file names from the "wad" key pair are logged to the console when + a map is loaded + - The user is informed if the textures loaded were not found in the + wad files in the "wad" keypair. + - The user is informed if the textures was not found in any wad file at all + (Q2/HL only, the shader module still gives you similar information for other + games when a shader activation fails) + + + 8/5/2002 + Hydra + - Added basic support in mapq3 for reading maps saved by Worldcraft 2.2+ + in .map format (It uses [ ]'s round some of the texture co-ordinates) + TODO: do we need to be able to save a map in this format too ? + - Added support for loading ZHLT style point files (*.lin) + - Added wad filename information when loading textures. + (This helps take the ambiguity out of which wad files textures come from, + so that we can correctly setup the worldspawn "wads" e-pair manually.) + Note: This will be removed when the "wads" worldspawn key is built by radiant. + - added vfsFindFile() to vfs table. + - VFSWAD: vfsLoadFile() no longer ignores paths when loading textures + (this was by design, but the design has changed for the better) + - When loading a Quake2 map file, vfsFindFile() is used to find the actual path of + the shader/texture being loaded. + This fixes all the weird issues that crop up when we were able to use non + wad-relative texture names () and wad-relative(/). + (such as having an image loaded twice in memory.) + We also now get the correct shader name in the suface inspector too. + Note: not sure if this code should stay in the map parser, or wether it should + be moved to where shaders are first initialised. + Note: maybe this needs to be when a halflife map is loaded, not specifically a + quake2 map file. + - added EClass_SupportsMultiple to the EClass loader API. + Note: this is poop. FGD files can be additive but radiant makes it so they can't be. + This function would not be needed if the eclass loader itself took care of the init, + rather then the manager taking care of the init. Also note that if the loader were + to take care of the init then FGD files *CAN* be additive, as it's not down to the + format of the FGD files. However, it'll do for the moment because all the supplied + FGD files that come with halflife and it's mods are meant to be used one at a time. + - removed support for having an additional (not external) eclass loader. + Just ifdef'd for now, grep for USEADDITIONALECLASSLOADER. + We never mix entity definition formats and synapse.config allows us to just have the + right one and also there is no mechanism for setting g_bHaveEClassExt anymore. + - Texture subset on by default for halflife. + - default texture scale is now set to 1 instead of 0.5 for halflife. + (needs to be 1 for q1/q2 too) + - patch toolbar disabled by default for halflife and it's also disabled + in the preferences so it can't be turned back on) + (needs to be 1 for q1/q2 too) + - bsp monitoring disabled by default for halflife + - When you drop a light entity the epair "_light" is used instead of "light" (halflife specific) + - removed -fs_game additions to the map compiler commands; ZHLT doesn't support it. + - saving of contents/flags/values in q2 format maps disabled (ZHLT doesn't like em !#?!) + TODO: re-enable for Q2 (but not halflife) format maps when we can + can figure out what game/engine combo we're using from within a module + - configured mapq3 to have dynamic VFS API too + - Added halflife shaderlist.txt parsing back in, it's actually useful + afterall (for editor shaders). + ====================================================================================== + -- end HL support patch + ====================================================================================== + +01/06/2002 + TTimo + - merging 1.2.7 -> 1.2.9 changes into 1.3, merge notes: + - the win32 .dsp are a bit different, using the $(CORERADIANTDIR) post build commands now + - merged in the JKII/STVEF hardcoded chunks, should probably check that everything is still fine on that end + was setting the "dir" epair in project files intead of "gamename" like all other games? + (which should really be "fs_game" anyway, I wonder who decided to call it "gamename") + - rebuilt a setup. we have a problem with RADIANT_MAJOR RADIANT_MINOR it seems + TODO: setup needs to use GtkRadiant-1. as basename in start menu, and base for installation + C:\Program Files\GtkRadiant-1.3 and C:\quake3\Radiant-1.3 etc. for the game packs + TODO: add HL setup chunks! + +=============================================================== +-- merging release-1_2_7 -> release-1_2_9 into 1.3 +=============================================================== +28/5/2002 + TTimo + - final fixes for Linux 1.2.9 setup + +27/5/2002 + TTimo + - bug 521, q3 entities.def trigger_hurt fix + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=542 + default texture scale is configured in .game + defaults to 0.5 (q3/wolf) if nothing specified, under the prop "default_scale" + removed the item from the prefs dialog too + updated the nightly setup to put the proper param in JKII .game + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=509 + changing texture window scale changes selected brushes texture + re-selecting the previous texture can be done, but is a bit tedious to write + made sure we deselect before re-init of the tex window view + - fixed linux setup code bug. won't be any update, only a full release on linux + +26/5/2002 + TTimo + - parallel cons working at last! was a problem with the targets list ('Default' command) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=515 + using the eclass extents for the box if model can't be found + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=435 + changed the submenu cascading params to avoid the overlap (we fit less stuff now obviously) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 + that sigchld handler is only used on Linux to report the run times + since we are rewriting the whole BSP code stuff, we can drop this for now + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=538 + removed that prompt and display + - we build radiant.x86 in cons scripts, updated the setup code + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=526 + updated the setup script to install new wolf_entities.def + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=302 + added q3map2 URL to global.xlink, updated Linux setup + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=465 + printing q3map version info through the net stream + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=539 + fixed various media, some related code, and Linux setup + - fixed watchbsp.cpp "jk2.game", was breaking game spawn for wolf (needed else if) + + SCDS_reyalP + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=504 + fixed bobtoolz vis viewer to work with RTCW (BSP version) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=526 + update of the Wolf entities file + +25/5/2002 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=164 + corruption on exit, tried to look some more. Cleaned up some source, need looking at Gtk code closer + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=536 + cleaned up SHADER_NOT_FOUND SHADER_NOTEX internals some more + added a clean error exit in case this happens, fixed a crash that would happen anyway (Patch_LODMatchAll) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=394 + cleanup/sanitize of the pattern filtering code, it was ugly. did some doxygen documentation + fixed part of the print XY code, more broken stuff showed up, dropping it + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=503 + region compiling was indeed fucked, spog b0rkage + fixed so that it works again + denying compile with camera out of the region + reworked SelectBrush to deal with regioning and select the right brushes + +24/5/2002 + TTimo + - Linux build fix + +23/5/2002 + Riant & TTimo + - STVEF patch and setup scripts + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=516 + moved error handling code to it's own file radiant/error.cpp + compiled with UNICODE define (that's why I had to isolate), and process the error string + so that Gtk can print it (that's only relevant to win32) + +22/5/2002 + TTimo + - quickfix, cleanup of the console verbosity + +11/5/2002 + TTimo + - final IS script updates for JKII game pack, version 1.2.8-jk2 + - fixed a bad karma #ifdef _DEBUG chunk in Texture_NextPos (causing crash of release build) + - added web url support in .xlink files (strstr on http://) + +10/5/2002 + TTimo + - cleaned the build step copy from $(QUAKE3RADIANTDIR) to $(CORERADIANTDIR) + - system shaders auto-load: display 'system' in the prefs + - force BSP monitoring off in jk2 mode + - cleaned up web update check, added HL (3) and Jedi Knight II (number 4) (on the web database too) + - updated IS setup script for mapextras.pk3 + Riant + - system shaders auto-load in prefs + Raven + - mapextras.pk3 as replacement for system.pk3 (system editor textures) + +8/5/2002 + Riant + - game pack and patches for JKII support + TTimo + - .game additions to specify .shader path (shaderlist and shader scripts) + NOTE: if we ever use q3map for JKII compiles, that would need to be propagated + - reworked the shaderlist to list all the included shaders + - built a system.pk3 pack for textures/system/ and textures/radiant/ (misses a few pieces still) + - MP / SP mapping mode toggle, SP ignores mp_*.def MP ignores sp_*.def + - fs_basepath does not get added during BSP command expansion for JKII mode + - more verbose on script location and junk.txt location when monitored compile is disabled + (the BSP compilation WANTS to be rewritten, it's getting VERY URGENT) + - JKII game back IS setup lands + +7/5/2002 + TTimo + - using radiant.x86 as Linux target (instead of radiant, didn't fit with the setup procedure) + - bumped version tag + - TODO: bug #453 code needs backported from 1.3 + +6/5/2002 + TTimo + - fixed typo in plugins/mapq3/write.cpp Map_Write + g_count_entities = 0; instead of g_count_brushes + - more fixes which showed up while merging this with 1.3 + +-- release-1_2_7 ----------- tagged and Stable-1_2 merging into trunk + +02/5/2002 + Gef + - added filtering on unselect for newly created brushes/entities (bugzilla: #374) + SPoG + - added undo for pasted/cloned brushes + +============================================================ +-- end release-1_2_7 -> release-1_2_9 merge +============================================================ + +15/05/2002 + TTimo + - rewrote the ref count code cleanly, added some elements to design and todo + - wrote the core shutdown code of synapse, 1.3 exits cleanly without crashing (well, in most cases it seems) + +10/05/2002 + TTimo + - began writing proper unloading and shutdown of synapse (see libs/synapse/docs/unload.txt) + design doc started, non active modules are unloaded after startup + need win32 implementation of ReleaseSO + - quickfix on win32 (ReleaseSO) + +07/05/2002 + SPoG + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=512 + - cleaned up strHomeMaps and strFSBasePath stuff + prompts for maps, models, sounds etc are fs_game-dependant + + =========================================================================== + -- merging Stable-1_2 between Stable-1_2-tag and release_1_2_7 into trunk + merge ChangeLog + ============================================================================= + 07/05/2002 + - using RADIANT_MAJOR_VERSION and RADIANT_MINOR_VERSION for the version info, this was conflicting with synapse + + - m_strHomeMaps + http://zerowing.idsoftware.com/viewcvs/viewcvs.cgi/GtkRadiant/include/qertypes.h.diff?r1=1.27&r2=1.28&only_with_tag=MAIN + http://zerowing.idsoftware.com/archives/gtkradiant/2002-February/002170.html + posted on the ML, bringing it back in from 1.2 + + setup scripts: + easily merged, as the 1.3 scripts have not been touched mostly + + qe3.cpp conflict: + 1.2 tweaks stuff in the QE_*Project* functions + 1.3 has them mostly commented out + applying manually where relevant + the project settings stuff is very different, and the changes can't be applied as is + given the fixes I had to do in 1.2, I'd expect the current 1.3 version to be fairly broken + a complete kill and rewrite of the prefs/project stuff might be our option anyway + http://zerowing.idsoftware.com/archives/gtkradiant/2002-May/003038.html + qe3.cpp QE_InitVFS conflict: + 1.3 has some changes when creating the files in a new userprefix (ex. ~/.q3a/baseq3/scripts /maps /maps/prefabs etc.) + merged by hand, probably needs to be checked + qe3.cpp OpenDialog SaveAsDialog conflict: + commented out in 1.3, getting rid of it completely + + preferences.cpp conflict: + 1.2 adds CUSTOMSHADEREDITOR_KEY pref + 1.3 uses a completely different syntax for prefs + preferences.cpp conflict: + prefab path pref changes conflict with 1.3 pref syntax + applying changes manually to 1.3 codebase + + pmesh.cpp conflict: + 1.2 adds pref to group / not group patch thickening + 1.3 changes the way we manipulate entities around that code + merged manually, would be worth checking that the thicken pref works + + pluginmanager.cpp conflicts: synapse completely changes that part + on relevant 1.2 thing is the removal of pfnRadiant_Free + + map.cpp Map_ImportEntities conflict + 1.3 has bug 453 map conversion promt that was not backported to 1.2 (caused merge to conflict a bit) + usin 1.3 code and checking 1.2 changes manually + + using radiant.x86 as Linux target (instead of radiant, didn't fit with the setup procedure) + + 06/05/2002 + not merging in .dsw .dsp + an eclass.cpp fixed moved to eclass_def.cpp + mainframe.cpp is always a bitch to merge, sent several mail comments to list about conflicts that arose + MainFrame::OnFileSaveas needed some updates that were not in the diff (correct default prompt) + (same for MainFrame::OnFileSaveregion) + MainFrame::OnFileNewproject conflicts a bit, changes have been made in 1.2 and 1.3 + changes in 1.2 seem more crucial, using the 1.2 version, and patched the 1.3 manually over it + (might need to be checked, bug #506) + + TODO: need to check for parasite g_free that I added back from the file dialog + + propagated ChangeLog from Stable-1_2 + ============================================================================= + 02/5/2002 + Gef + - added filtering on unselect for newly created brushes/entities (bugzilla: #374) + SPoG + - added undo for pasted/cloned brushes + TTimo + - shift+left click to open shader editor no longer selects the texture on the way + (this was unstable, pCurrentShader could become NULL somehow) + - editpad bindings were completely broken + attempts to make it work again failed + taking it out + changed the prefs, on win32 you select between internal shader editor or win32 .shader binding + we have lost the ability to jump to a given line, if someone has a good solution for line jumping, let me know + - one more fix to the MAJOR / MINOR safe checks stuff + - bug #500: oooogly, I removed a line which I should not have :) + + 01/5/2002 + TTimo + - "Save selected.." load/save in fs_game sensitive directory too + - removed a bunch of unused/broken project settings items + removed most of them actually .. project settings are .. ahem + - added an optional 'go to url' button in gtk_MessageBox + + 30/4/2002 + Gef + - fixed lod drawing of selected patches when patches are filtered + + 29/4/2002 + TTimo + - bugzilla #467 + make patch inspector deny space textures + make mapq3 write code drop space textures + - bugzilla #132 + removed remotebasepath and texturepath + rewrote the Textures > Load Directory (which was kinda relying on texturepath) + - bugzilla #355 + uploading editpad zip to qeradiant.com misc/ in files section, replacing the win32 message about editpad + added editpad quote in qer.com totd + - fixing the map load/save dialogs to work correctly with mod settings on win32 (was done on Linux and still broken on win32) + + + 26/4/2002 + Gef + - fixed patches losing their shader if outside region when calling flush/reload + (bugzilla: #492) + - blocked textures with spaces from loading in Texture_ShowDirectory with a warning + (bugzilla: #467) + - fixed a dud shader (liquids.shader -> textures/liquids/ripplewater2_back) didn't have + the textures/liquids prefix + + 25/4/2002 + Gef + - fixed a broken image link in the shader manual (bugzilla: #486) + - changed prtview to use ~/.radiant//prtview.ini instead of + ~/.q3a/radiant/prtview.ini on linux + - fixed prtview loading/saving config (bugzilla: #424) + TTimo + - removed QERApp_RadiantFree from the function table + we can malloc and free across modules configured correctly for the CRT (Common Runtime DLLs) + cleaned up related broken malloc / free strategy in the plugins (vfsLoadFile uglyness) + - added main build date and version to curry / pk3man / prtview + + 23/4/2002 + SmallPileOfGibs + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=229 + flush and reload was affecting texturing of selected brushes + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=489 + File > Check for update menu item, jumps to the website and checks for update + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431 + win32 part, RADIANT_MAJOR RADIANT_MINOR written out by setup + - more stuff on File > New Project and common mod setup issues (not finished yet) + + 22/4/2002 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431 + reworking a bit the installer stuff + wrote the version checking + needs testing on win32 (RADIANT_MAJOR and RADIANT_MINOR are required in the install now) + + 21/4/2002 + TTimo + - trying more seriously to get a new nightly out + updating the ChangeLog for current 1.2.7 from this file + cleanups, browsing through the bugs to close/update/fix + - http://zerowing.idsoftware.com/bugzilla/showattachment.cgi?attach_id=197 + I kinda fixed that myself already, going through the diff and applying the missing stuff + creating the prefabs/ dir in QE_InitVFS + - added a line about the games dialog / auto-select at startup in the dialog frame + + 15/4/2002 + TTimo + - cleaning some old commented out map load code + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=477 + on Linux: + - strHomeMaps was init without taking care of m_strFSGame + - SaveAsDialog was not using strHomeMaps + NOTE: should strHomeMaps be spcific to Linux, or we will do better if we unify + need similar checks on win32 + + 13/4/2002 + TTimo + - kicking the source to generate new doxygen on zerowing + + 09/4/2002 + Gef + - setting the sel_mode accordingly when (i)nverting selection, verts were being drawn when + they shouldn't have been + + 05/4/2002 + Gef + - fix File/New Project for mods so it doesn't fail if the dir exists (bugzilla: #459) + - add Linux-isms for New Projects & read/write permissions... + note: for a total conversion, basepath needs to be manually set + - prevent opening multiple internal shader editor dialogs + - added preference for using a custom shader editor + - set horizontal scrollbar to be automatic instead of never for entity keyval list (bugzilla: #4) + - added a call to Select_Reselect() in XYWnd->OnViewEntity() to make sure its modifying the + current selection (bugzilla: #436) + - fixed entity dialog passing events through to main window (bugzilla: #454) return values + were backwards + - patching in the .pfb extension adding stuff (bugzilla: #259) + - fixed thickened patches not being grouped (bugzilla: #226). this was supposed to be happening + anyway, the entity create code was called before the patches were selected + + 02/4/2002 + EvilTypeGuy + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=457 + add entity #X and brush #X comments back to saved .map files + + 24/3/2002 + Hydra & TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=444 + only show empty alpha channel warning if the tga texture is actually 32 bit + (24 bit would always have empty alpha, the warning was useless in this case) + + 19/3/2002 + Gef + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=217 + - Set show value to true for angle and movement velocity sliders in preferences + - Increased the maximum value of angle velocity from 6 to 100 + + 18/3/2002 + SPoG + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=419 + fixed File->Save with region active acts the same as File->SaveRegion for ents + + + end merge + ============================================================================= + +16/4/2002 + SPoG + - fixed MDC_XYZ_SCALE value + +12/4/2002 + SPoG + - fixed win32 compile error - vc6 being nitpicky + - fixed refcount init on CSynapseAPIManager + - cleaned up md3model win32 project file + +9/4/2002 + Gef + - added nudging for selected brush and patch vertices (bugzilla: #240) + - added selected brush vertex highlighting + - sorted all the ID_'s in HandleCommand alphabetically to make it easier to track things down + - setting the sel_mode accordingly when (i)nverting selection, verts were being drawn when + they shouldn't have been + +5/4/2002 + EvilTypeGuy & djbob + - patched in djbob's grid minor/major color settings for gridsize < 1 + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=24 + +1/4/2002 + TTimo + - configured md3model to have dynamic VFS API too + - cleaned up texwindow.cpp texture extension loop + - updated current HL media with hl's synapse.config + http://zerowing.idsoftware.com/stuff/HL-media-0401.zip + - checking in new synapse.config for Q3/RTCW + - fixed win32 project files, removed hltoggle.h + - bumped version to 1.3.3 + +31/3/2002 + TTimo + - XML runtime configuration of synapse + uses a synapse.config in the gametools path, we can add a line in the .game to specify the file later on + - various cleanups and removal of dead code + - Linux build system: sanitized CFLAGS, libxml, STLPort and glib include path all in the toplevel Construct file + - removed the 'cons -- halflife' option, the binaries are unified again + - cleaned up image loading + + +29/3/2002 + TTimo + - patching in Hydra's code for Half-Life, builds and runs on Linux, need to quickfix on win32 now + - build system on Linux: some things are still hardcoded into the core, you need a different core + for Q3/RTCW or HL for now. do ./cons -- halflife to build HL mode (build trees are seperate) + - there is no media / install procedure yet, but a zip with what you may need for install is available: + http://zerowing.idsoftware.com/stuff/HL-media-0329.zip + - cvs added all the new files + - patched various things from the patches, don't have a precise list + cleaned up the interface requests, isolated HL specific between TMP_HALFLIFE defines + removed 'tga' from imagehl, two modules providing the same API has unexpected results + + TODO: the image loading is the main problem right now. We should not have any place that scans the + extensions, this is done internally to the image load manager? texwindow.cpp does enumeration of the + minors too .. but that may be legal in this case. + + TODO: synapse config at runtime through XML (rather big piece) + + - fixing build on win32 + bad coding practices: 'for (GSList *choicelst = ..' + added a quick hack include/hltoggle.h for easy switch q3/rtcw or hl compile + (remaining hardcoded stuff is temporary) + + +28/3/2002 + Gef + - added linux pthreads support to tools + - removed old terrain.c and lightv.c from q3map2 Conscript + +27/3/2002 + Gef + - updated cons for q3map 2 + - minor q3map2 fixes for linux compile errors/warnings + - minor warning fix in map.cpp + SPoG + - changed entity_addtolist to add entities to end of list instead of beginning + - added eclass_forname to eclassmanager interface + - fixed setting eclass before model-update for entities loaded from map + - fixed setting bounding box for models after model-update + ydnar - q3map2 + - fixed crash on -connect and other gremlins related to argument processing + - removed flag that prevented Castle's maps from compiling + + +27/3/2002 + + Hydra + + Important Changes: + + - Added VFSWAD modules for extracting textures from WAD files. + - Added ImageHL for loading textures contained in WAD files + - Updated shaders source code so that you can produce ShadersHL.dll + (single minor using #ifdefs) + - MapQ3 source updated so that it can load and save q2 format maps + provides a multiple minors. + - Added EClassFGD for loading FGD definition files + (I documented this code quite well, if you're interested...) + - Added support for iconsprite() settings in the FGD loader, we set + eclass_t->skinpath with the name of the sprite + TODO: write a sprite model plugin. + + Fixes: + + - Fixed incorrect line numbers being reported when script files had // comments in them + - Removed EClass_Create from the EClass manager _EClassManagerTable + - Replaced all occurences of "textures/radiant/notex" in shaders.cpp with a define. + - Fixed a crash in shaders.cpp when there was no default texture. + - Fixed a possible issue with g_bCancel_Map_LoadFile + - Added a crash fix for uninitialised patchMesh_t->pSymbiot + + Core Changes Required for HalfLife Support: + + - Set MAX_FLAGS to 16 to support Halflife's extra spawnflags, adjusted + entity inspector to display the new spawnflags, updated FGD loader + to load them correctly (previously it only loaded the ones with values <8) + (Done without breaking the old Q2 code that was commented out) + - Added GetTokenExtra to the _ScripLibTable + - Kludged texwindow.cpp to allow loading extension other than "tga" and "jpg" + TODO: ttimo, we need something in synapse to help with this. + - Plugin manager requests different API's depending on .game file used. + TODO: this needs to be done on a PER GAME basis, not PER .GAME FILE. + - Shaderlist.txt is not parsed on startup if hl.game is used. + TODO: this needs to be done on a PER ENGINE basis, not PER GAME. + + Cosmetic Changes: + + - Changed MAPQ3's minor_name from "map" to "mapq3" (also adds "mapq2" as a minor) + - Changed XMAP's minor_name from "xmap" to "mapxml" + - Changed VFS's minor_name from "quake3" to "pk3", more inline with VFSWAD now. + - Changed file/Load to file/Import on the menus + - When a shader (Q3/HL) is not found a message is displayed in the console + (only once for each shader that is not found). This is so the user can + quickly get a list of missing textures/shaders. + +26/3/2002 + ydnar + - initial q3map 2.0 source import + new tools/quake3/q3map2 directory + common/qfiles.h and common/surfaceflags.h modified + affects q3map 1.x too, bumped MAX_MAP_BRUSHSIDES to 0x40000 + will need to write the build scripts and compile on Linux too + SPoG + - Re-added dialog prompting user to convert/change-mode/abort when map BP mode + conflicts with project settings + - large entity/models update + +++ include/ientity.h 25 Mar 2002 11:37:54 -0000 + entity module + - interface cleanup + - common #defines for easy transition + +++ include/igl.h 25 Mar 2002 11:37:55 -0000 + opengl module + - Vertex Arrays support + +++ include/imodel.h 25 Mar 2002 11:37:57 -0000 + model module + - interface cleanup + +++ libs/mathlib.h 25 Mar 2002 11:37:59 -0000 + vector macros - cleanup + m4x4 + - documentation of matrix layout + - interface for utility functions for axis-angle and quaternion rotations + - interface for new utilities for specifically transforming points/normals + aabb + - interface for faster aabb-ray test without finding intersection point + - interface for utility to calculate an aabb to contain a transformed aabb + +++ libs/mathlib/bbox.c 25 Mar 2002 11:38:01 -0000 + - cleanup of use of qboolean + - implementation of fast aabb-ray-test + - implementation of aabb-for-transformed-aabb + +++ libs/mathlib/m4x4.c 25 Mar 2002 11:38:02 -0000 + - implementation of utility for rotation matrix from axis-angle/quaternion + - cleanup of implementation of matrix multiplication functions (optimise for in-order array traversal) + - implementation of new utilities for specifically transforming points/normals + +++ libs/mathlib/ray.c 25 Mar 2002 11:38:02 -0000 + - replace use of m4x4_transform_vec3 with new point/normal specific utils + +++ plugins/mapq3/plugin.cpp 25 Mar 2002 11:38:06 -0000 + - rename g_EntityTable using #define in ientity.h + +++ plugins/mapq3/plugin.h 25 Mar 2002 11:38:06 -0000 + - rename g_EntityTable using #define in ientity.h + +++ plugins/mapxml/xmlparse.cpp 25 Mar 2002 11:38:06 -0000 + - buffer-safe dtd path construction (without using string class, in case of unknown bugs) + +++ plugins/md3model/Conscript 25 Mar 2002 11:38:06 -0000 + - remove entity-module files from md3model conscript + +++ plugins/md3model/md3model.cpp 25 Mar 2002 11:38:07 -0000 + - implementation of generic quake-style-model class CModel + - implementation of CModel-derived md3/mdc classes + +++ plugins/md3model/md3model.dsp 25 Mar 2002 11:38:08 -0000 + - remove entity-module files from md3model dsp + +++ plugins/md3model/md3model.h 25 Mar 2002 11:38:08 -0000 + - interface for generic quake-style-model class CModel + - interface for CModel-derived md3/mdc classes + +++ plugins/md3model/md3surface.cpp 25 Mar 2002 11:38:09 -0000 + - implementation of generic quake-style-model class CSurface + - implementation of CSurface-derived md3/md2/mdl/mdc classes + +++ plugins/md3model/md3surface.h 25 Mar 2002 11:38:09 -0000 + - interface for generic quake-style-model class CSurface + - interface for CSurface-derived md3/md2/mdl/mdc classes + +++ plugins/md3model/plugin.cpp 25 Mar 2002 11:38:10 -0000 + - provide support to synapse for loading md3/mdc/mdl/md2 models, and mdl images + +++ plugins/md3model/plugin.h 25 Mar 2002 11:38:10 -0000 + - interface for loading md3/mdc/mdl/md2 models, and mdl images + +++ radiant/brush.cpp 25 Mar 2002 11:38:18 -0000 + - #ifdef remove Group/Brush-Patch-Epair related stuff + - const correctness for ValueForKey interface const change + - add bounding-box update for models in Brush_Build + - remove old brush parsing/writing stuff + - remove old eclass-model loading/displaying stuff + - enable vertex arrays on light drawing + - moved brush is-selected? utility to brush.cpp + +++ radiant/brush.h 25 Mar 2002 11:38:18 -0000 + - comment out interface for old brush parse/write stuff + - comment out interface for brush epair stuff + +++ radiant/brush_primit.cpp 25 Mar 2002 11:38:20 -0000 + - remove old brush-primitives parsing/writing stuff + +++ radiant/camwindow.cpp 25 Mar 2002 11:38:23 -0000 + - moved brush-bbox update for models to brush.cpp:Brush_Build + - bugfix for material colour setting when drawing models + +++ radiant/eclass.cpp 25 Mar 2002 11:38:24 -0000 + - removed old eclass-model checking/loading stuff + - added const checking for Eclass_ForName interface + +++ radiant/entity.cpp 25 Mar 2002 11:38:26 -0000 + - REMOVE THIS FILE + +++ radiant/entity.h 25 Mar 2002 11:38:26 -0000 + - REMOVE THIS FILE + +++ radiant/groupdialog.cpp 25 Mar 2002 11:38:29 -0000 + - change entity creation to not use Entity_Create (function was removed) + - commented groups stuff + +++ radiant/gtkdlgs.cpp 25 Mar 2002 11:38:36 -0000 + - const correctness for ValueForKey + +++ radiant/gtkmisc.cpp 25 Mar 2002 11:38:39 -0000 + - added filetype patterns for mdc/mdl/md2 + +++ radiant/main.cpp 25 Mar 2002 11:38:41 -0000 + - const correctness fixes + +++ radiant/mainframe.cpp 25 Mar 2002 11:38:59 -0000 + - change selection -> merge entity and selection -> separate from entity to go through mainframe class + - implementation of mainframe functions for selection -> merge entity and selection -> separate from entity + - made entity grouping and detail/structural settings undoable + - commented out old groups stuff + +++ radiant/mainframe.h 25 Mar 2002 11:39:01 -0000 + - interface for mainframe functions for selection -> merge entity and selection -> separate from entity + +++ radiant/map.cpp 25 Mar 2002 11:39:04 -0000 + - const correctness fixes + +++ radiant/pluginmanager.cpp 25 Mar 2002 11:39:08 -0000 + - removed model table + - stopped requesting model table from synapse + - request undo table from synapse + - commented out support for IEpairs stuff - NOTE: to be integrated with entity module + - fill interface table for opengl vertex array support + - fill interface table for undo + +++ radiant/pmesh.cpp 25 Mar 2002 11:39:18 -0000 + - cleanup patch cap and patch thicken to create entities using entity module interface (without Entity_Create) + - removed old patch parse/write stuff + - #ifdef'd out patch epair/groups stuff + +++ radiant/qe3.cpp 25 Mar 2002 11:39:21 -0000 + - const fixes + - buffer-safe dtd path construction (without using string class, in case of unknown bugs) + +++ radiant/qe3.h 25 Mar 2002 11:39:24 -0000 + - don't include entity.h, include ientity.h and forward-declare entity-table instead + - include imodel.h but don't forward declare model-table + - include iundo.h and forward-declare undo table + - comment out interface to old patch parse/write stuff + - comment out interface to old brush parse/write stuff + - comment out iepairs header include.. NOTE: to be integrated with entity module/interface + - include eclass interface (we don't have an eclass.h) + - declare interface for CreateEntityFromName (generic useful func) + - declare target/targetname utils interface (we don't have a targetname.h) + +++ radiant/select.cpp 25 Mar 2002 11:39:27 -0000 + - cleanup implementation of entity selection-grouping/ungrouping utlities + +++ radiant/select.h 25 Mar 2002 11:39:27 -0000 + - cleanup interface for entity selection-grouping/ungrouping utlities + +++ radiant/undo.cpp 25 Mar 2002 11:39:29 -0000 + - removed workaround for wierd entity_clone behaviour (changed in entity module) + - avoid using Entity_FreeEpairs (not exposed by entity module) + +++ radiant/xywindow.cpp 25 Mar 2002 11:39:35 -0000 + - const fixes + - cleanup implementation of CreateEntityFromName to be a usful generic utiliy function + - enable vertex arrays in XY_Draw + - enable undo for right-click dropping entities in XY window + TTimo + - various fixes to make the above compile on Linux, checkin to cvs + - fixing some win32 build stuff + + Hydra + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=434 + fixed setSpecialLoad in .def code + +19/3/2002 + Gef + - Modified the fix for LoadImage to be more consistent with other code + - Applied Hydra's fix for empty alpha channel warnings & cleaned up indentations (tabs) in lbmlib.cpp + also added output of the tga type when a tga file fails to load + - Reverted my over complex fix (read; mess) for strtok_r to use strtok instead of manual tokenising + +17/3/2002 + Gef + - Fixed LoadImage API list not being incremented while trying to find image minors, result was + infinite loop when loading images that weren't of the first type (tga) + - Fixed cloning giving dtd errors. mapxml/xmlparse.cpp:ParseXMLStream() was using + g_FuncTable.m_pfnGetQERPath() as the dtds path... disabled validation until spog can check + that my fix is the right solution + - Added simple formatting to xmap file output so that each node has a new line for readability + +13/3/2002 + TTimo + - introduced API List managers + we deal with two types of APIManager now, the ones that matching all minors for a given major + and the ones that require a fixed list of minors + - converted the image loaders to go through a API list manager + - fixed various things in synapse (introduced more bugs?) + - fixed plugins, realized it was still broken + +12/3/2002 + Hydra & TTimo + - EClass_Create in the EClass manager _EClassManagerTable + - removed InitFromText from _EClassTable + +8/3/2002 + TTimo + - some commented out code cleanups + - added eclassfgd/ fgd.so module skeleton + loaded up in radiant core as an optional entity format + added eclass manager code to deal with the new format if present + this still loads .def, the actual .fgd code needs to be written now + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=433 + added a g_strDTDPath global + disabled DTD validation, broken on win32 + - Str copy constructor (const Str &) working correctly with a __strDup + +7/3/2002 + TTimo + - added verbosity in file accesses for CXMLPropertyBag + - more fixes to project lookup + - dropping dtds/ prefix, this is installation dependent + - removed ipluginentities.h, the plugin entities stuff was disabled long time ago already + - cleaned up some old commented out stuff + - proof of concept synapse builtin module: new class CSynapseClientBuiltin allows to have + modules builtin to the application (i.e. statically linked) + adding + include/ieclass.h (eclass loader API) + radiant/eclass_def.cpp (.def class loader, builtin) + radiant/eclass_def.h (.def loade, API public to the core) + + NOTE: radiant/eclass_def.cpp needs to be added to the win32 projects + - quickfix to project file loading ("/scripts/") + - introduced an EClass manager, hooked up the .def builtin module through it + (not yet possible to push new entity format modules, but .def reading is already fully synapsed) + +6/3/2002 + Gef + - Fixed a segfault when getting mUserPathPrefix in CGameDescription::CGameDescription() + - added preferences check for fixing target/name collisions + - fixed a logical error on my part, where setting g_qeglobals.m_strHomeGame in + CGameDescription constructor results in a value from the last file parsed. Moved it + to a more appropriate location, where it gets a value from the selected .game file. + SPoG + - changed g_strGameToolsPath to g_strAppPath in GetQERPath API + - fixed mapq3.dtd + - enabled DTD validation of xmap files + - added mapq3.dtd to setup scripts (not tested) + TTimo + - added OnActivated() to synapse clients, override to put some init code + - fixing default project path lookup and user project increment (again) + - fix to linux setup, no trailing slash in basegame items + (wolf.game and q3.game) + - removed old plugin/modules code, leaving only the synapse implementation + recoded image loading and Map_Import/Map_Export + still some temporary solutions and cleanup work to be done + removed plugin.cpp from the tree / build system + +5/3/2002 + SPoG + - TODO: add default project for wolf to WolfPack CVS module + - changed xml project file load to search for DTD "dtds/project.dtd" under radiant path + - fixed crash in mapq3 on trying to read uninitialised token ptr + - changed .map to be default map format for now + - changed runbsp to not hardcode -fs_basepath + - added -fs_basepath to quake3 default project + - added project.dtd to setup scripts and swapped quakev2.qe4 for default_project.proj (not tested) + +4/3/2002 + TTimo + - merged synapse2 branch back into trunk, checked Linux and win32 builds ok + - updated the .dsp to work with new libxml2 2.4.16 + - fixed broken enginepath guessing, and broken project path rotation / saving + +28/2/2002 + Gef + - Added extra checks for target/targetname collisions + - Find Brush dialog title correction (bugzilla #393) + +26/2/2002 + Gef + - Added Entity_Connect() to entity.cpp to avoid duplicating code + - Fixed target/targetname collisions - entities being cross-linked when copied + Bugzilla #385 : http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=385 + +16/2/2002 + Gef + - cleaned up the kyro gl_point workaround stuff + - taught ClipPoint's (clips & path points) how to draw themselves + +8/2/2002 + Gef + - Added mapxml.so to linux setup + - strip debug symbols option in setup + - removed g_qeglobals.m_strHomeMaps, writing it to project instead + fixes a bunch of re-broken path issues. Also uses fs_game now. + - Minor grid colour in QER Black & Green theme + - Save window's pos/size for all view types in MainFrame::OnDestroy + +--------------------------- on branch synapse2 +4/3/2002 + - modules don't show up in plugins menu, added a dump in console before entering interactive mode + - added compile time def for synapse verbosity + +3/3/2002 + - finished converting all the modules to synapse, disabled old ResolveInterface call + +24/2/2002 + - hooked TexTool into Radiant plugin menu through synapse + - added iplugin.h which I had forgotten earlier + - ported synapse code to compile and run on win32 + +19/2/2002 + - SYN_REQUIRE_ANY / multiple API manager code + can load multiple interfaces based on a matching pattern + converted TexTool to load that way + +18/2/2002 + - added iplugin.h with basic interface for plugins + +13/2/2002 + - synapse on modules currently disabled, + the basics of the code are working fine, need to look at multiple interfaces matches before going further + - started converting shaders, requires conversion of a lot more others + - image converted to synapse + - no longer using GUID in synapse, all done through *_MAJOR strings + HOWTO: convert a module to synapse: + configure it to link against synapse static lib + (+include path to STLPort required) + add #include "synapse.h" to the plugin header + declare the : public CSynapseClient in plugin header + implement it (listing provides and requires, implement the request code) + +12/2/2002 + - vfspk3 converted to synapse + +11/2/2002 + TODO: get rid of all WINAPI crap + - debugged the API dependencies solver to actually work + - added newer cons at the head of the tree + +10/2/2002 + TTimo + - added the basic code for solving API dependencies and requesting the various tables + - more diagnostic printing code fixes + - version checkings + - some more design work (libs/synapse/doc) + +9/2/2002 + TTimo + - some changes to the files layout, cleanup of the diagnostics printing + (stuff's mostly broken right now) + - reworked the complete Sys_Printf stuff to rely on va_list implementation + - include/isynapse.h declared useless and foxed without mercy + - added include/irefcount.h + +8/2/2002 + TTimo + - adding an experimental Anjuta project file to play around with + +7/2/2002 + TTimo + - various fixes to build on linux, listed a bunch of current issues + - fixing terrademo.map to remove broken mapobj~1 -> mapobjects + -- synapse2 branch -- + - propagate the code from old synapse branch to a new branch out of 1.3 tree + (builds and runs on linux, that's about it for now) + +--------------------------- end branch synapse2 + +7/2/2002 + djbob + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=354 + moved all the preferences code to XML + +6/2/2002 + SPoG + - checked all paths conform to: unix dir separators + trailing separator + - changed file dialogs for load/save of maps to default to "mapspath" + - removed OpenDialog and SaveAsDialog, use file_dialog instead + - removed FileSystem paths stored in QEGlobals_t.. replaced by CGameDescription + - removed dependence on EnginePath from preferences, replaced by CGameDescription + - removed hardcoded g_get_home_dir calls for file dialogs + - added validation of project settings entry paths before they are set + +5/2/2002 + EvilTypeGuy + - moved filters.cpp related function declarations to filters.h + and added #include "filters.h" to brush.cpp, csg.cpp, main.cpp, + mainframe.cpp, map.cpp, select.cpp as not all files include + qe3.h and qe3.h is a rather monolithic header this seems to be + a cleaner solution per SPoG's suggestion...Fixes compilation. + + Gef + - contrib/plugins BOOL cleanup (uses qboolean now) fixes X header conflict + - cons update for mapxml + - SaveAsDialog() changed to match path's used in OpenDialog() + - removed radiant/xy.h - moved contents to qe3.h (FilterBrush declaration) + +4/2/2002 + SPoG + - changed QE_LoadProject and QE_SaveProject to load/save xml project file format + - changed request dialog for project files to loop until a valid file is found + - fixed memleaks in CGameDescription constructor for xmlGetProp + - added converting gametoolspath unix format when parsed from game file + + - fixed m4x4 lib to use column-major order (more compatible with opengl) + - added divergence parameter to ray-point intersection test + (now easier to select distant points in perspective views) + - cleaned up modelview/projection matrix manipulations in 2d/3d view + - cleaned up map modules / interface source files a bit + + - added ability to specify map module version when importing/exporting map + - cleaned up file dialog code, returned filename is static and in unix format + - save-as dialogs force a file extension depending on filetype selected + - added filetype manager to support registering custom file types + - fixed loading and cleaning engine path from radiant.ini correctly + +3/2/2002 + EvilTypeGuy + - fixed win32 compilation (userpathprefix is Linux specific) + + ETG & Powzer + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=308 + added preference to allow 'paint drag-select' brushes/faces in 3d camera view + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=389 + added preference to strafe camera foward/back in 3d view while freelook is active + + ETG & RR2DO2 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=388 + patch adds ability to strafe up/down/left right while freelook is active + +1/2/2002 + TTimo + - bumped to 1.3.1-nightly + +-- 1.2 stable branch branched here + + Gef & TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=372 + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=376 + appplied the patch, corrected the mapspath expansion stuff + TTimo + - linux nightly setup code + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=384 + hacked a corrective action in the nightly setup + + SPoG + - fixed creating region brushes that fill the entire grid for Save Region + +31/1/2002 + + Micheal Schlueter + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=375 + syntax fix to q3map path_init.c + + Gef + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=279 + patch 185 == 186 + tweaking to config stuff in linux setup + patch 187 + Ensures the games directory exists before trying to create a file there + patch 177 + Adds *.cf files & uses them. I think I have all the files in the right places now... maybe + (some additional fixes on top by me) + + ETG + quick fix to shader prefs load + +29/1/2002 + + EvilTypeGuy + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=345 + more detachable menus fixes + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=334 + fixes 'load shaders at startup' preference + + SPoG + - fixed misc_model "modelscale" and "modelscale_vec" support + + TTimo + - upgraded setup scripts to support nightly build + - last minute fix to the modelscale and modelscale_vec code (md3 module) + + ydnar + q3map 1.2.4-y2 + + New features: + - -nopatchfix argument. This disables lightmap patch fixes and makes a map suitable for lighting with -vlight. + - Degenerate patches are treated like broken brushes. They are ignored, warned about, and selected in Radiant if you ran with the -connect option (or from the BSP menu). This was what was causing the "0 valued axis" error some people were experiencing. + + New entity keys: + - "_lightmapscale" key for brush entities (worldspawn, func_*). This lets a mapper scale the lightmap samplesize per-entity. For large constructions, 2.0 or 3.0 is a fine value, and keeps BSP size down and compile times low. For those areas you want to have high-detail shadows, make a func_group and use a value of 0.25 or so. It will scale the samplesize value for the surface's shader (default 16) or the -samplesize argument. + - "modelscale" and "modelscale_vec" keys for misc_models (1.0 = default). This was for proper RTCW support and is available for Quake 3 maps as well. Lets you scale up map models in the world, getting around the MD3 size limitation. The next build of GtkRadiant has SPoG's code to support this in-editor so you can see what effect a scale has. + - Flare surfaces are now supressed from the BSP. They serve no purpose other than add to the vert & surfacecount in a BSP. These surfaces were created silently when a shader has "light 1" or "q3map_flareshader X." Use the new -flares switch when BSPing your map to have them emitted. + + Changes: + - GtkRadiant 1.2.4-nightly version increment. + - Full WolfSDK style lighting enabled with -game wolf, including lightJuniors. This includes linear lights by default (no angle attenuation) and support for the additional RTCW "fade" and "angle" keys, and spawnflag changes, including q3map_nondynamic on light entities. This may require maps being constructed for RTCW with the current toolset to change their light entities. Sorry. :) Note, Wolf-style lighting only works with -light, and not -vlight. + - Vertex light stitching now uses a near-ambient light check for dark vertexes as opposed to lower-than-average fixups. This preserves some shadow detail better while getting the buried verts lit properly. Comments encouraged. + - Surfaces' samplesize are now stored in the BSP. This change makes BSPs generated from this version incompatible with all other q3maps. The upside is that -samplesize N is no longer necessary on the -light or -vlight stage. This feature is necessary to support the "_lightmapscale" key. + - Additional PVS optimizations in lighting. + + Fixes: + - Will compile for RTCW properly (1.2.1-y12 didn't). + - No more sparklies where fog meets brush faces. They're split properly now. + - Crash bug in vlight fixed. + - Vertex light fixups/stitching is considerably faster. + - Vertex light fixups ONLY stitch faces with lightmaps. For pointlight surfaces you're on your own. + - Better snapping logic when merging nearly-coincident vertexes on complex brush windings. + - Bug where the .prt file had some bogus or nearly-borked portals. They're cleaned up like everything else now. + - A few stupid bugs in path initialization. Should work better. Also includes TTimo's fixes to my code so it would work properly on Linux. + - RR2DO2's PCX loading patch for alphamaps. This bug was manifesting itself in the form of offset or incorrect samples being used on terrain entities. + - A ton of other minor little fixes here and there. + +28/1/2002 + + TTimo + - win32 fixes + - 1.2.4-nightly + + djbob + - EClass_ForName fix if malformed name + +---- 1.2.3 linux released + + TTimo + - fixed BSP version depending on game mode in q3map + (home dir guessing is still fucked, have to fix before release) + - fixed q3map init_path.c home path bug on init + + Gef + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=369 + more fixes to texture paths in gensurf + +27/1/2002 + TTimo + - switching to v3 project file, forcing reload of template if non-v3 + need to distribute quakev3.qe4 in setups now (done for linux setup, will have to in win32) + - renamed Main to main in q3map init paths + - fixes the ~/. inits and init order in q3map + - added m_pfnPathForPluginName to the main function table, returns the directory a plugin is running from + used in bobtoolz / curry / pk3man to find various files + see also todo: http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=368 + - some fixes to textures loading paths in curry + - linux setup copying correct content for curry (pk3 in wolf media) and bobtoolz (bt/ in plugins/) + - building 1.2.2 setups + + Gef + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=362 + gensurf fix + + djbob + - bobtoolz update + + Hydra + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=361 + fixes a bug with the texture menu loading, now we see the non-shaderlist directories too + +26/1/2002 + Gef - Michael Schlueter - TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=279 + applied several patches (to setup code and to the setup scripts) + modified makesdk.pl to update with more content + added an "enginepath" attribute to the game file, reworked the handling in editor + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=347 + took out the refresh command from the plugin menu + - more linux fixes: + bringing all the plugins to compile again on linux + polishing the setup code (all the right files in the right places) + - for linux release, bumping ver to 1.2.2 + win32 will have a 1.2.3-nightly after that + - added correct init of ~/.q3a or ~/.wolf + *nix systems have a 'prefix' attribute in the .game file to specify + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=359 + identified the mod compiling problems + applying back the old fs_basepath fs_game code to the BSP generation + + ydnar- TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=351 + cleaner path init code + it doesn't init for ~/.q3a and ~/.wolf paths yet + +25/1/2002 + SPoG + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=352 + using the wrong matrix stack for XY_Draw caused stack overflow error + - texture_mode was set to an invalid enum in wireframe/flatshade mode + - changed plugin API to expect gamedir-relative texturenames + - fixed gensurf to create faces/patches with gamedir-relative textures + +24/1/2002 + SPoG + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=322 + added modelscale key check to misc_model entity in md3/entity module + added angle key check to eclassmodel class in md3/entity module + fixed bugs in BP writing and reading in map module + TTimo (commited as SPoG) + - fixing permissions on cvsreport and doxygen stuff, upgraded dot + +23/1/2002 + SPoG + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=336 + plugin API bugfix - fixes textures on stuff created by plugins + +---- 1.2.1 was released here + +22/1/2002 + Gef + - linux build fixes + SCDS_reyalP + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=325 + wrong file packaged in setup + RR2DO2 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=327 + fix to PCX loading + SPoG + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=298 + cut & paste bugfix + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=332 + update origin key on entities + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=333 + made fixedsize entities not scalable + +21/1/2002 + TTimo + - scanning in g_strAppPath/modules/ and plugins/ prior to g_strGameToolsPath + using the main path to put general plugins and modules + - fixed bobtoolz bug, init of epairs table was relying on wrong params + - fixed curry to compile again on 1.2 + - fixed pk3man to compile again on 1.2 + - updated IS setup: + installing the plugins with the core + installing the common modules in the core + - Compiling manual, more IS stuff, .xlink etc. + + RR2DO2 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=315 + patches for improved multimonitor support (with some associated pref items) + + ydnar + - more q3map: http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=324 + - radiosity fixes (proper handling of ambient light) + - polygonoffset fixes + - lightmaps on patches work better (normal calcs adjusted, planar patches are + box projected like brush sides) + - double vfs init in bsp stage removed (this needs to be tested on Linux) + - lighting is faster again + - a couple crash bugs resolved + - other tasty nibbles + +20/1/2002 + EvilTypeGuy + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=313 + detachable menus set as preference (in layout) + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=255 + path prompt + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=307 + patch dialog names + ETG & RR2DO2 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=306 + fix 'Natural' texturing crash + ETG & TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=291 + found out the problem, Wolf SP spawn works now + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=144 + fixed more problems with model loading vfsExtractRelativePath + prolly broke the linux build, just a matter of putting a bunch of #idfdef + - fixed an additionnal .pid lock situation, cleaning the global prefs on game .pid lock + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=301 + fixed md3 tris test selection bug + djbob + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=219 + fixed bobtoolz for 1.2 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=318 + filter structural + RR2DO2 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=305 + filter in viewmenu for lightgrid brushes (ydnar's q3map) + fixes image lib loading bugs + Hydra + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=298 + copy/clone deselects the copied stuff + added a pref to deselect or not, and to nudge pasted stuff or not + +19/1/2002 + djbob + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=311 + IEpair wrapper to access project entity from plugins + RR2DO2 + - missing IncRef in CShaderArray::AddSingle + TTimo + - game.xlink files in gametools path, is scanned to build items in the Help menu + (and the associated code) + +18/1/2002 + Gef / Michael Schlueter / TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=279 + patches 133 and 138 applied, new setup code + +14/1/2002 + TTimo + - adding djbob write access for bobtoolz + +13/1/2002 + ydnar + - q3map code updates 1.2.1-y8 + new lightgrid surface flag feature + lightgrid shader and editor image for Wolf and Q3/TA: in the common .pk3 and in all common.shader + cvs remove setup/data/baseq3/common-q3r.pk3 (unused, we use common-spog.pk3) + TTimo + - updating setup to use mapq3 module instead of map (both Wolf and Q3 game packs) + (also checked the lightgrid option) + - fixed setup.pl bug for template gen from WorkDir/ + - correct spawning between SP and MP mapping mode + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297 + .pid check, console logging and prefs cleanup + Wolfen + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=294 + checked in updates to the manual links page and setup instructions + (i.e. new prefs dialog) + +12/1/2002 + Gef & Michael Schlueter + - bugs #295 and #279, new patches applied + EvilTypeGuy & djbob + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=220 + patch selection crash + EvilTypeGuy + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=260 + Dense and Very Dense Cylinders have wrong number of rows + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=292 + latching patch toolbar settings + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=144 + win32 long/short pathname bugs reappearing, switching back to short paths for project settings + - removed obsolete radiant/vfs.cpp radiant/vfs.h + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=291 + using new .qe4 for Wolf, fixed stuff editor side (long path names and engine spawn) + SCDS_reyalP + - http://zerowing.idsoftware.com/bugzilla/showattachment.cgi?attach_id=118 + realloc bug in terrain + +11/1/2002 + SPoG + - unpatched bug #239, unintended duplication of brushes, patch #96 + Note: bug #239 now unresolved + EvilTypeGuy + - bug #295 fixes for compile under Linux + +10/1/2002 + SPoG + - fixed CEntityEclassModel referencing eclass after eclass has been deleted + SPoG - map-module branch + - new map module, provides current functionality, using new map interface + - cleaned up merging/creating of entity array used by map module + - implemented MemStream::printf(const char,...) - can't print strings larger than 1024 currently + - changed copy/paste to use the map module, via abstraction of FileStream/MemStream as DataStream + - fixed Save Region and Save Selected + - new xml map module "mapxml" + - cleaned up map.cpp + +6/1/2002 + TTimo + - removed m_bPak from pref dialogs (it was dead code) + +5/1/2002 + RR2DO2 + - q3map terrain blending fix for >5 layers + EvilTypeGuy + - fs_homepath patch on linux + Gef + - bug #279, linux setup, patch #102 + - bug #239, unintended duplication of brushes, patch #96 + TTimo + - added Wolf specific project settings dialog: + correct fs_game selection and combo names + added multiplayer / single player mapping mode selection + +4/1/2002 + TTimo + - adding -game wolf switch to q3map (-game quake3 works too, but it's the default anyway) + using different bsp version and different fs_basegame on wolf + - updated the setup/win32/setup.pl script to generate from a config file instead of hardcoded + (added corresponding q3.cf wolf.cf and all.cf config files) + - added a default Start Menu shortcut name (RR2DO2 special) + - diffing against Id's internal SOS source and merging in new stuff: + - bumped MAX_SURFACE_INFO to 4096 in shaders.c + - new terrain code (Jim Dose) + ParseTerrain() addition in terrain.c + Creates a mapDrawSurface_t from the terrain text + - VL_SurfaceRadiosity and VL_SurfaceRadiosity + MrElusive's vlight radiosity code + - speedups to vis.c and visflow.c (MrElusive) + +3/1/2002 + TTimo + - merge gameselect branch back into trunk + the IS setup scripts have been updated for the new paths layout + developement environment needs to be updated to copy binaries to the right places for debug + it is recommended to run a 1.2.1 setup on win32 prior to compile and install debug bins + - updated the setup to be more templated for inclusion/non inclusion of game packs on demand + + - propagating recent fixes to Alpha into the trunk + based on diffing between Merge-1_1_1 and Merge-1_1_2: + ===================================================================== + 13/11/2001 + djbob + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=244 + reverted again the shader manual and tcMod docs + + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=214 + patched aselib.c, was calling strstr badly (relative path extraction) + + 12/11/2001 + djbob + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=241 + applied patch, will release in next nightly + + Spog + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=254 + patch for safe_malloc + + TTimo + - fixing STLPort config checks and XML config (CHAR -> xmlChar) + - added safe_malloc_info and safe_malloc in the common/ dir + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=179 + added XML stream version checking between Radiant and q3map + ======================================================================== + also, manual merge of docs/manual and setup media + this merge work is related to bug #280 too: + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=280 + + ydnar + - new q3map, radiosity and bug fixes, code merged in with the trunk version + (TODO: add more detailed changes log) + + EvilTypeGuy + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=274 + broken auto caulking fix + + EvilTypeGyu & LordHavoc + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=207 + (two new files, project settings updated) + +2/1/2002 + TTimo - branch gameselect + - copying over the linux setup binaries (setup, uninstall, setup.gtk) + from Alpha branch. Those have the ability to prompt destination path + per component. + Gef - branch gameselect + - patch 101 for bug 279 + .game files generation by the setup, makesdk.sh and postinstall.sh fixups + +1/1/2002 + Gef - branch gameselect + - linux source fix http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=279 + (we use PATH_MAX as the cross platform define instead of MAX_PATH which only works on win32) + + TTimo - branch gameselect + - cleanup and homogeneisation of the paths for prefs storage + m_global_rc_path: + win32: g_strAppPath + linux: ~/.radiant// + m_rc_path: + win32: g_strGameToolsPath + linux: ~/.radiant// + so that global.pref goes in ~/.radiant//global.pref + and radiant.ini ~/.radiant///radiant.ini + +27/12/2001 + TTimo - branch gameselect + - global prefs file (global.pref), XML based in core directory + stores game selection setting + stores autoload setting + - dropping 'gameid' from .game file, gonna use a 'gamefile' in global prefs instead + (and the .game file name .. thks Gef) + - TODO: radiant.log stuff in global prefs? + +21/12/2001 + TTimo - branch gameselect + - more Wolf setup tweaking for an experimental build release: + quakev2.qe4 project template file + using a 'gameid' attribute in the game file to select hardcoded features in the editor binary + 'basegame' node for lookup of the default project file + 'engine' node for engine path + - added experimental Wolf game pack to IS + - added setup/win32/HOWTO with extensive information about the procedure to + add new game packs + - reading the 'name' attribute in the game node for game selection dialog + - TODO: pid files to make safe startup? + The .pid stuff should be happening after game selection, since it covers game-specific + preference settings. + - TODO: console logging pref should be a global pref, goes with game autoload? + +17/12/2001 + TTimo - branch gameselect + - updating the setup script for experimental 1.2.0 setup: + + merged some of the docs back into trunk (Radiant manual, some TA docs) + will need to perform a complete diffing between Alpha and trunk about docs/manual at some point + merged bitmaps from Alpha too + + added the Q3 modules (image, map, md3model, shaders, vfspk3) to Q3 game pack in setup + + generating per-game config file q3.game in OnMoved (IS setup) + will need equivalent with linux setup of course + + various other generic fixes to the setup code + + - multiple games support, list of changes, and TODO: + NOTE: this is on a 'gameselect' branch for now + Doxygen documentation should be at http://zerowing.idsoftware.com/doxygen + for this branch too. + + The installation procedure has changed. The win32 installer is partly ready, linux installer + will need to be modified too. The editor binary and the Gtk DLLs are installed in a common + location, i.e. 'C:\Program Files\GtkRadiant' typically. The game specific binaries and modules + go in the same location as usual, for instance 'C:\Program Files\Quake III Arena\GtkRadiant\' + (and also 'C:\Program Files\Quake III Arena\GtkRadiant\modules' 'C:\<..>\plugins') + + The environment variables used by the build system (VC6 project files) have been adapted: + $(QUAKE3RADIANTDIR) is still used + $(CORERADIANTDIR) is used for the main editor location + + When editor starts, it looks for games/*.game under g_strAppPath and prompts the user for a game + Once game is selected, parameters are used for regular startup. + You need to write your own q3.game for now, it will be generated by the setup procedure + my C:\Program Files\GtkRadiant\games\q3.game looks like that: + + + + + + given that, the editor does a complete startup, and the basics are here for multiple games + + - precise changes: + + g_strToolsPath renamed to g_strGameToolsPath + most of former g_strAppPath uses g_strGameToolsPath + the name change was also meant for homogeneity with DIR_GAMETOOLS_* variables we use in the setups + g_strAppPath still used, points to the main installation path + + added the game selection code in CPrefsDlg::Init + using several classes and a dialog box, parsing XML files + + - TODO: + + the console 'Radiant.log' doesn't catch the game selection stuff as it is now + initialize it to the main install, without the game setting + (console logging is a debugging tool anyway, no reason it should go to the proper game folder + each time) + + the 'preferences reset/cleanup' code is probably broken, specially when used with the .pid checking + since we check for .pid even before we know where the GameTools path is + + on linux, we need to sanitize the ~/.q3a dir usage. Switch to ~/.radiant, use the version tag + to maintain things independant, and use the game name to isolate per-game settings? + ~/.radiant/1.2.0-nightly/quake3/radiant.ini (.pid, .log) + ~/.radiant/1.2.0-nightly/wolf/.. + + also, when looking for those files (.ini mostly), win32 stores them in a main installation, and + linux has them in ~/.radiant/.. (which is the read/write area). This should be homogenized? + Maybe by adding a 'Main' to the readonly path and a new variable with 'RW', pointing to 'Main' on + win32 and to ~/.radiant on linux + +11/12/2001 + TTimo + - replaced setup/win32/setup.sh by setup/win32/setup.pl + same functionality level + abiliy to generate back a template from a work version + - major rework on the IS scripts, basics of multiple games support installer + clean seperation between editor core and game pack + design doc and analysis of custom setup generation, setup script UI requierements + see setup/win32/TODO for more details + +10/12/2001 + TTimo + - new generation of InstallShield setup + using a template/ directory instead of a .zip file + requires rewrite of the processing script + allows easier maintenance of the IS script + +23/11/2001 + TTimo + - yet another update to cvsreport script, + catch the branch and forward the info to user commands too + able to build doxygen for several branches selectively now: + http://zerowing.idsoftware.com/doxygen + +22/11/2001 + TTimo + - new cvsreport script, should send explicit diff of the CHANGES file now + +03/12/2001 + TTimo - md3-module branch + - validated the fixes and the build on linux, ready to merge in trunk + SPoG - md3-module branch + - changed function naming conventions in mathlib for m4x4, ray, bbox, to be consistent + - fixed bug in m4x4_invert + +29/11/2001 + TTimo - md3-module branch + - flagged all new mathlib functions that need a name change or an argument order change + also added various \todo to point out inconsistencies + +28/11/2001 + TTimo - md3-module branch + - updated linux build + - fixed CEntityMiscModel and CEntityEclassModel destructors + (any destructor should be virtual) + +27/11/2001 + Spog - md3-module branch + - stopped texturewindow showing shaders without the "textures/" path + - made md3 module functionally identical to current radiant md3 code + +22/11/2001 + TTimo - md3-module branch + - fixes to the core for linux build + - model.so module builds on linux + - added plugins/md3model/doc/md3-design.txt + - several doxy-friendly \todo chunks about the module model + + Spog - md3-module branch + - fixed aabb_add_aabb() algorithm wasn't very reliable + - added VectorMid, VectorNegative and CrossProduct macros to mathlib + - added bbox_intersect_plane() + +21/11/2001 + Spog & TTimo - md3-module branch + - initial code from Spog following a preparatory design work + merging in as new 'md3-module' branch + geomlib code merged into mathlib + some reorganisation of the source layout and cleanup (more stuff in imodel.h, less in qertypes.h) + + Spog + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=261 + fix applied + +20/23/11/2001 + TTimo + - yet another update to cvsreport script, + catch the branch and forward the info to user commands too + able to build doxygen for several branches selectively now: + http://zerowing.idsoftware.com/doxygen + +22/11/2001 + TTimo + - new cvsreport script, should send explicit diff of the CHANGES file now + +11/2001 + Spog + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=253 + additional fixes + TTimo + - renamed tools/quake3/common/threads.h to qthreads.h + avoids a collision with system headers + +19/11/2001 + Spog + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=251 + Fixed "Move into worldspawn" deleting entities with only one brush + Fixed Brush_Move using texture lock on fixedsize entity brushes + Fixed Textures > Texture Lock > Rotations toggle checkbox + +16/11/2001 + Gef + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=247 + applied patch 77, using a notebook layout for preferences dialog + also patched in some preferences saving that had been forgotten + (such as invert mouse in freelook) + + Spog + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=253 + patch 84 + additional modifs, bug still open + +15/11/2001 + Spog + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=252 + rewritten rendering pipeline for cam window - fixes some hacks, improves speed, + makes rendering modes more consistent with each other + +07/11/2001 + TTimo + - more IMAP interface, adding a blind data void *pData to entity_t + more info about it and why it's done is in map.cpp, should be a small base for next additions to the editor + +31/10/2001 + TTimo + - using IDataStream in map module, moved back some of the module code into the trunk + +30/10/2001 + Gef + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=245 + applied patch 76 + + TTimo + - renaming istream.h to idatastream.h, this had nasty conflicts with OS includes + already had to IStream -> IDataStream some time ago anyway + +27/10/2001 + TTimo + - updated cvsreport, testing new ver + - added a static version of texdef (no memory alloc on the texture name) + unused for now, was just experimental + Gef + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=245 + applied patch 75 + +26/01/2001 + TTimo + - various updates: + new GtkSDK precompiled binaries + updated libxml2 package (to 2.4.3) + updated STLPort (to 4.5) + now compiling with STLPort and threading (since we are using threading throughout the app) + - exposing the data stream API to the modules, renamed some stuff on the way + need to update the map module to use it now + - cleanup on qtexture_t definition + guarding and disabling chunks of the surface plugin code behind DO_SURFACEPLUGIN + (see earlier patch on plugin entities) + +25/01/2001 + Hydra + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=202 + applied patch commenting out plugin entities code + might come back in 1.2 under another implementation + the code is still there, only commented out for now + +17/01/2001 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=93 + checking in doxygen content, setting up generation on zerowing + auto generation on zerowing upon a commit: + http://zerowing.idsoftware.com/doxygen + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=231 + checked in the patch + - switching to a new cvs commit script + +14/10/2001 + TTimo + - libs/mathlib library + unifies mathlib code squattered all over the tree + mainly a merge of tools math code and radiant/mathlib.cpp + C implementation, using an extern "C" construct for use from C++ + need to check on linux, win32 builds ok + extracted qboolean code into libs/bytebool.h on the way + +11/10/2001 + TTimo + merged TmpMerge-1_1_1 branch back in the trunk, the update process from Alpha 1.1.1 is done + + TTimo - branch TmpMerge-1_1_1 + looking through all remaining .rej files and applying the failed patches accordingly + bobtoolz is broken, but it's not due to the merge + the plugin API is different and some things need updated + (disabled bobtoolz build in contrib/Construct for now) + same for gensurf + same for prtview + same for textool + setup: replaced the existing stuff with 1.1.1 code + fixed various things for win32 build, checked correct CRT lib config + +10/10/2001 + TTimo - branch TmpMerge-1_1_1 + building modules, going through all the .rej + merging radiant/missing.h and modules/shaders/missing.h into a single one, moving to libs/ + exposing BuildShaderList PreloadShaders in _QERAppShadersTable + have to review all the remaining .rej to finalize the merge now + +04/10/2001 + TTimo - branch TmpMerge-1_1_1 + using this branch as temporary location for merge process + copied over new binary files. mostly .dsp (prolly broken) + and setup/linux/setup.data stuff + +25/08/2001 + TTimo + map module successfully loaded and saved q3dm1, the saved file was then loaded back into 1.1-TA without problems + rebuilt and checked on win32 + merged IMap back in trunk, fixed some memory conflicts on win32 + Took me a lot more time than I would have liked to, but there's a script tied to the CVS server now, which will post on this list a diff of the docs/developer/CHANGES file whenever it gets updated. This will probably be very handy for me since I'll only have to put update information in the CHANGES file instead of having to post on the list too. + The script is likely to be a bit laggy, or miss some features (for instance I'd like to extract the branch name .. anyone know how I can get the branch name (Alpha/IMap/HEAD) from the version number? + PS: I can email this script to anyone who would like to have a look + +22/08/2001 + TTimo + did more work on map module, one big chunk of work left: the core should broadcast interface requests to plugins + when it doesn't know how to do it by itself.. + Gef + new doxygen patch, generates output from core (libs/ include/ and radiant/) + +21/08/2001 + TTimo + removed Makefile, use cons damnit! + +18/08/2001 + Gef + automated documentation via doxygen, new scripts and content + +18/08/2001 + EvilTypeGuy + patch for CHAR to xmlChar conversion (xml2 consistency) + +09/08/2001 + TTimo + the map module starts to look like something, cleaned up the interface stuff + started moving the actual code out in the module and removing it from the core + lots of issues raised on the way, some structures to export, and the macro scheme to access API functions more easily + it compiles right now, but won't run because it's missing a lot of things .. the process simply happens to be "under way" + +04/08/2001 + TTimo + patched more path code, to look for stuff in "bitmaps/" and "modules/" instead of "tools/bitmaps" and "tools/modules" + modified the Construct files accordingly + merged in radiant/ishaders.cpp diff into plugins/shaders/shaders.cpp (PreloadShaders) + merged in radiant/lbmlib.cpp diff into plugins/images/lbmlib.cpp (Sys_FPrintf) + checked the .rej and patched a few remaining things + NOTE + the diffs are space/tab sensisitive, and we used the "beautify source" a bunch of times, so it's a bit fucked now + next time, generate the diffs not space sensitive.. + TODO + map loading is fucked, "textures/" prefix issue? + +27/07/2001 + TTimo + merging recent changes from Alpha branch into the Trunk + this could not be done with a regular cvs merge because we already did a cvs merge of Alpha into trunk some time ago + manually built a diff between the current Alpha (now tagged Merge-1_1-TA_1-nightly) + and the Alpha we had right after the former cvs merge: -r Alpha -D 2000-05-28 + binary files ignored in the diff, only going for source stuff + built with diff -Nru Reference/ Current/ + then patch -p1 < patchfile + next, started rebuilding: + big manual updates were in vfs.cpp and texwindow.cpp + cleaned up some VFS stuff .. it had an absurd QERAppFileSystem / QERPlugFileSystem scheme + TODO: + the ISSetup has not been copied over from Alpha + OK check the Construct files + OK radiant/ishaders.cpp no longer exists .. apply the patch on the shader module + OK radiant/lbmlib.cpp no longer exists .. in the image code? + OK look at the *.rej files + +03/10/2001 + TTimo + - adding a pref to select patches by BBox, fixes + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=212 + +02/10/2001 + TTimo + - reverting Spog patch 67 to bug #209, starting from scratch + applied again, with HasModel returning NULL safe checks + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=211 + fixed overlays drawing (XY and Cam) + +01/10/2001 + TTimo + - building and distributing q3data (.ase -> .md3 conversion utility) + updated q3data to show main GtkRadiant version information and build date + - generating a new GUID per-setup + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=206 + Gef + - updated credits.html and links.htm, look much better + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=200 + - update Z-checker view on camera up and down + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=199 + Spog + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=209 + Fixed QERApp_ReloadShaders.. PreloadShaders needs a BuildShaderList call + Fixed Flush & Reload Shaders for md3 models + +25/09/2001 + Gef / djbob + - several patches to the key handling code, for linux specific issues and sticky keys + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=191 + TTimo + - fixing q3map bug, not processing the argv correctly + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=192 + - fixed ToggleCubicClip shortcut Ctrl+\ (win32 Gtk source patch) + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=15 + +18/09/2001 + RR2DO2 + - discreet movement for camera (prefs setting) + fixes texture window bug + latching view layout changes until restart + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=23 + djbob + - added back "view > show > show angles" in view filters + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=188 + +15/09/2001 + G_Dewan + - fixed problems with q3map when not using -connect + SPoG + - fixed q3map texture projection for brushes belonging to entities with local origin + - added SafeOpenRead() check, terminating map->bsp stage if .map file cannot be read + +13/09/2001 + RR2DO2 + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=181 + fixing key handling bug (key pressed with repetition was not properly catched) + TTimo + - added new Radiant manual elements (GtkRad section) to the win32 full setup + - reverting version to nightly, going back to nightly / RC delayed + - patched linux setup, now prompting for component path only if at least + one of the options is checked. Still need to handle Cancel in dialog though. + +12/09/2001 + TTimo + - more fixes to linux script, copy plugins right now + +10/09/2001 + TTimo + - patched contrib plugins, using seperate build scheme + - fixed textool issues, compiles again + +09/09/2001 + TTimo + - sub-menu cascading + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=178 + - udpated the Q3Radiant manual with some new GtkRadiant stuff + - updated the FAQ with 1.1.1 known issues + Gef + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=174 + applied all the patches + +07/09/2001 + SPoG + - fixed qer_editorimages outside "textures/" being ignored + - stopped q3map_lightimage being used to set shader image dimensions + - changed bsp menu to remove "bsp_", changed menu text in default .qe4 + - fixed patch LOD update - now always occurs on both cam/xy draw + + djbob + - dynamic DEpair class strings in bobtoolz + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=171 + - decrease VESF verbosity + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=169 + + TTimo + - fixed Gtk keyboard bug Ctrl + [ and ] + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=15 + +06/09/2001 + TTimo + - merged FullSetup branch into the trunk, we have basic functionality + for a full linux setup (components prompting for path) + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=158 + + Gef + - CapDialog source cleanup patch (got rid of the namespace) + + RR2DO2 + - more camera fixes, wheel mouse and texture drag drop + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=23 + - additionnal patch to optimize camera refreshes + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=23 + + djbob + - remember last key/pair in entity dialog for easy "apply again" + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=18 + - re-enabled texture name edit on PI + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=18 + +04/09/2001 + djbob + - left pane on status bar + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=166 + + RR2DO2 + - cam window cursor fix + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=23 + + TTimo (FullSetup branch) + patched setupdb and setup to allow for path prompt in install + modified the setup script scheme to go towards a solution similar to what + we do under win32 (build a full and nightly build) + the binaries in setup.data/ (setup and setup.gtk) still need to be updated + with proper binaries built from setup and setupdb cvs source + +03/09/2001 + TTimo + - wheel mouse in texture window on win32 (with a pref setting for increment) + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=160 + - not saving prefs while exit on sleep + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=157 + - fixed select all of type (changed behaviour to something that makes more sense?) + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=79 + + Gef + - final tweaks to wheel mouse scrolling (locks texwin scrolling and scrollbar update) + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=160 + - prevent multiple color selection dialog for light entity + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=165 + + djbob & TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=137 + window positions + applied patch to store SI and PI positions + storing entity info and map info positions + reworked the overall position load/save scheme + added an enum for the view style, makes things more readable + +02/09/2001 + TTimo + - added/cleanup ToggleFreeMode to camwindow.cpp .. stopped working on cam stuff since RR2DO2 has another patch in preparation + Gef + - patched Conscript to accept 'cons -- release' on the command line to performa a release build + djbob + - added patch splitting to bobtoolz + - fix to patch control points bug in camera + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=159 + - handling of NWUV errors in q3map + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=147 + RR2DO2 + - new patch for camera control + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=23 + +01/09/2001 + SPoG + - Fixed .wal texture support, searches for .wal extension if .tga and .jpg fail + NOTE: requires a "pics/colormap.pcx" file to obtain a palette from + - Added variable default texture scale in preferences (ini key: TextureDefaultScale) + +01/09/2001 + djbob + - fixed surface inspector "fit" bug + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=161 + - single face deselection on a selected brush + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=153 + +31/08/2001 + TTimo + - Moved *.def files to scripts/ in win32 setup + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=116 + - Applied patch for background position on widgets (win32) + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=108 + - Checked C runtime lib configs + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=135 + - updating docs (add to CVS, update setups etc.) + added TA teams manual to the full setup + uploaded on web site + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=97 + - changed versioning to 1.1.1-nightly, next release will be 1.1.1 + (the -TA part was removed, since we now support ALL mods) + - removed AFX_MANAGE_STATE calls, this is old MFC related code for win32 + - moved texdef_t::name to private, added const char * GetName() + (doesn't fix explosion on exit for win32 debug builds though) + - fixed DoTextEdit / EditPad b0rkage (due to recent Q_Exec changes) + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=134 + - added targetShaderName documentation to shader manual + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=102 + - added "notta" and "notq3a" documentation to the TA Mapping manual + - fixed entities.def on shootable doors and buttons + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=101 + + djbob + - added MAX_POINT_ON_WINDING error handling + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=129 + - bobtoolz update + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=123 + + RR2DO2 + - noclip-type camera movement + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=23 + +30/08/2001 + TTimo + - Fixed CHANGES commit script bug + - Fixed -onlyents bug in q3map / origin brushes + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=92 + - added mouse wheel to the texture window + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=81 + SPoG + - Fixed texture rotation not updating correctly on patches + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=136 + - Fixed long delay on toggling cubic clip by removing call to Map_BuildBrushData() + - Added note in entities.def for default worldspawn _color value + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=105 + - Added IncRef and DecRef to Patch_FindReplaceTexture() + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=95 + - Fixed misc_model updating on changing model key or with invalid model + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=138 + +30/08/2001 + SPoG + - Added negative vertical scale on SET and FIT in patch/surface inspector + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=65 + +27/08/2001 + Gef + - running makeversion.sh from Conscript + - a bunch of patches to cleanup compile warnings on linux + - added VectorSnap on float grid + - IWindowListener modified to pass float values for X Y in click messages + TTimo + - fixed crash when adding a misc_model if Gtk dialog is on + djbob + - md3 filtering for misc_model dialog + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=76 + +26/08/2001 + Gef + fixed http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=152 + Kyro II GL drivers bug + fixed http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=150 + using a scrolling textbox for GL extensions in the about list + fixed http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=150 + func_group toggle in cap dialog + + TTimo + fixed running BSP commands on linux + fixed http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=133 + VFS init on linux + +around 15/08/2001 + TTimo + quakecon fixes: switched to long filenames in project and misc_model dialogs, + removed all occurences of win32 conversion to old 8.3 filenames + NOTE: this might raise some bugs and issues, but it's the way to go for the future, + already fixes more issues than it creates + +03/08/2001 + djbob + fixed Radiant hijacks win32 copy/paste + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=36 + +--- merged Alpha into Trunk, see Merge-1_1-TA-nightly tag + +25/07/2001 + TTimo + fixed project dialog to behave right + proper .def scanning + fixed shader loading with VFS and mod stuff + added a local to texwindow.cpp GSList *l_shaderfiles + holds the names of the active .shader files + modified q3map to read "fs_basepath" and "fs_game" + TODO: + .def files in the media need to move to /scripts/ + rename entities-TA.def to entities-ta.def + +24/07/2001 + TTimo + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=116 + updated cmdlib's Q_Exec to be more widely used through Radiant (during watchbsp.cpp cleanup) + patching in MarsMattel's code for mod support, and started fixing: + added m_strFSBasePath m_strFSMain m_strFSGame to g_qeglobals to match Q3's filesystem + reworked the project file dialog + changed the way we load and initialize eclass and shaders to work with mod code + updated VFS initialisation code, cleaner and better console output + the "game" key in the project file is no longer relevant, only "dir" is + (if "dir" is not present, then no mod support, vanilla Q3) + changed the loading of the .def files to scan in scripts/, you might need to move your entites.def to use + TODO: + cleanup .. (search where "basepath" is used for instance) + using fs_game when calling q3map + fixing project dialog to behave right + shader loading using VFS functions (seems to work again but I'm not sure) + win32 ver. might be slightly broken + .def scanning, don't scan ALL .def + +23/07/2001 + TTimo + added version and build info to the log file + current timestamp + +22/07/2001 + SPoG + fixed selection of misc_model when viewed as a bounding box + +20/07/2001 + TTimo + cons script for q3map building + added general GtkRadiant versioning (version.h) to q3map + nightly setup on linux: + using the right install path (with GtkRadiant's version name) + cleaned up options to only the stuff relevant to nightly + fixed Radiant and core binaries path in setup + added some template processing of setup.xml (similar to what is being done on win32) + +19/07/2001 + TTimo + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=112 + applied ^Fishman's path + inclusion of version.h and aboutmsg.h moved to qe3.h + changed base path location process (in most cases it will prompt) + fixed the path construction to initialize according to the new layout + +16/07/2001 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=115 + fixed wake up crash on linux + +12/07/2001 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=117 + fixed + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=120 + fixed installer bug on win98, was a problem with cygwin config + +11/07/2001 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=64 + cleanup and fixed + +06/07/2001 + TTimo + - http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=100 + can't locate the "textures: in use" problem, has been fixed already? + now selecting the right entity in the list, had to go around an inifinite recursion problem + (i.e. selection message in the entity class list causes UpdateSel recursion) + +04/07/2001 + TTimo + - added botclip to missionpack/common.shader + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=114 + +01/07/2001 + TTimo + - backported cons scripts to Alpha branch. Type 'cons' at the head to build + regular makefiles should soon be outdated.. + +30/06/2001 + TTimo + - updated the IS script (Gtk changes and and BACK problem) + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=68 + - bunch of fixed to make it build on win32 against new STLPort + http://zerowing.idsoftware.com/STLPort/ + using an STLPort configured locally in GtkRadiant (with stl_config.h, new file) + +29/06/2001 + TTimo + - fixed GDI leak affecting text widgets, specially the console and the entity inspector + +18/06/2001 + TTimo + - more win32 project file cleanup, removing libs/libxml2 from the tree + - importing back "gtkr_list.h" from 1.2, made Alpha branch STLport compliant too + http://www.qeradiant.com/faq/fom-serve/cache/174.html + +30/05/2001 + TTimo + - added libxml2 as an external dependency. libxml2 should be installed as a seperate + directory on win32. dynamic linking now instead of static previously. + - cleaned up q3map win32 project file, removed opengl dependency + (the drawflag command line is inoperant now .. I don't think it was used anyway?) + +26/07/2001 + TTimo + - checking in Gef's doxygen files + +14/06/2001 + TTimo + - added .dsp for map module on win32 + - fixed several issues with module loading, stability of the debug and release builds + http://www.qeradiant.com/faq/index.cgi?file=197 + +12/06/2001 + TTimo + - got rid of of SysMsg thing, using SysPrintf and SysFPrintf now + - got rid of InfoMsg functions (can't remember what it was) + - changed the m_pfnError in the plugin API to match Radiant's (void)(char *, ...) + - changed Warning to Sys_Warning, as #define to Sys_FPrintf(SYS_WRN, + +11/06/2001 + TTimo + - new map module, in plugins/map, required for execution (linux Makefiles written, win32 needs to be) + this is using the imap.h interface + - added Sys_ functions to the main table (Sys_BeginWait Sys_EndWait) + - got rid of AFX_MANAGE_STATE macros .. those are crappy MFC remnants + - reverted Spog's changes to the console logging switches to their original behaviour + - removed m_fVersion from the func table .. we use the size of the table to do the checks + XMLmap merge from 31/11/2000: + - started moving the map loading code into a module + +08/06/2001 + TTimo + - updates to the plugin loading code, verbose a bit more, and more interesting information + +05/06/2001 + TTimo + - some fixes to vfspk3 string code, using the proper str implementation + http://www.qeradiant.com/faq/index.cgi?file=175 + - added and tweaked various cons build files, Radiant 1.2 core and required Q3 modules are building now + - fixes to image module + +04/06/2001 + TTimo + - started using cons for the linux (*NIX) build system + see http://www.dsmit.com/cons/ + + SPoG + - Fixed drawing too much coordinate text in XY window + - Changed grid line drawing in XY window to be more consistent + - Fixed clipper-tool-uses-caulk to only apply common/caulk to solid opaque brushes + (shaders.dll now parses some new surfaceparms) + - Changed shader parsing to pass over layer information in shaders, + rather than parsing and ignoring it all + - Changed misc_model selection to ignore back-facing triangles + - Added axes to show the grid origin in XY window + - Changed misc_model rendering and selection to minimise the number of extra + transformation calculations + - Fixed texture directory listing to allocate and free memory correctly using vfs + - Added qglDeleteTextures() to plugin GL API - fixes crash + - Fixed image.dll to correctly allocate and free memory for jpgs + - Moved modules to /modules from /plugins, updated win32 project files. + (linux/mac makefiles will need to be updated) + - Changed console logging toggle in main() to automatically disable logging after any successful startup + - Changed console logging to only activate when a Release build finds a .pid file + - Changed "found .pid" and "logging console output" messageboxes to give a clearer message + - Added vfsFreeFile - which is kinda redundant if we use g_free and g_malloc for everything + +31/05/2001 + TTimo + - cleanup of the win32 project file and C++ options.\ + Turned off exception handling, changed some code generation options and fixed + some threaded/non-threaded linking problems + - STL in GtkRadiant or a plugin must now use STLPort + a custom configured version of STLPort is available at http://zerowing.idsoftware.com/STLPort + still need to write some guidelines about it + bascially, we are using STL: iostreams disabled, no namespace, no threading, no exceptions + +30/05/2001 + TTimo + - removed libxml2 from tree, use a seperate libxml2/ directory next to GtkRadiant/ for win32 + libxml2 will be distributed seperately as an archive based on official release (same as win32 Gtk SDK) + (check on zerowing for the latest archive) + libxml2 is now used as dynamic shared object on win32, makes sense since many module will rely on it + - cleaned q3map, removed GL dependencies + - updated Debug and Release builds on win32, it compiles and runs now + +28/05/2001 + Spog + - moved vfsExtractRelativePath and vfsGetFullPath to vfs.cpp in vfspk3, + added vfsExtractRelativePath and vfsGetFullPath to IFileSystem. Copied BuildShortPathName() from qe3.cpp to vfs.cpp as a Temp fix. + - Changed Error() calls in bmp.cpp as a Temp fix, they relied on definition of Error in qe3.cpp. Should probably use Error() from cmdlib instead. + - Fixed unresolved external in jpgload.obj - merged bufsize argument into jpeg_stdio_src from Alpha branch... assuming Alpha is the newer version. + - Changed GtkWidget* to void* in image.cpp.. this could be cleaned up more.. i only did enough to make it compile. + - Added jpeg.cpp to msvc project for image.dll.. changed declaraction of LoadJPG() in image.cpp to an extern... is this correct? + - TODO: update vfs.cpp, vfspak.cpp and vfs.h in plugins/vfspak + - fixed unresolved external load_pixmap() - merged load_pixmap declaration from Alpha branch into gtkmisc.cpp + - moved vfsBasePromptPath() to qe3.cpp as a Temp fix - not currently required in vfs module, but it will be in future. + - two calls to free() in texwindow.cpp freeing memory allocated by vfs module, causing debug assert errors - changed them to g_free() + - TODO: Delete vfs.cpp and vfs.h from /radiant + TTimo + - additional fixes after Spog's merge (linux version), removed messaging.cpp messaging.h (name changed to ui.h ui.cpp) + updated linux makefile accordingly + - merge of Alpha version into trunk (massive amount of changes and merges, not detailed) + +25/05/2001 + TTimo (Alpha branch) + - merged the recent MacOS branch back into Alpha + this makes a potential source codebase for a MacOS release + +24/05/2001 + TTimo (Alpha branch) + - patching Spog's recent changes to fix linux build + using DBL_MAX and FLT_MAX from for float and double max + +23/05/2001 + TTimo (Alpha branch) + - testing Spog's write access + + SPoG (Alpha branch) + - Added variable LOD for PatchMeshes based on curvature + - Added LOD-matching to eliminate gaps between patches with mismatched LOD + - Fixed texture shift/scale on LOD'd PatchMeshes + - Added opengl lighting (three infinite light sources) + - Added dynamically calculating vertex normals for PatchMeshes, for gl lighting + - Added decoding/transforming md3 vertex normals for gl lighting + - Changed camera drawing routine to minimise gl state changes + - Removed Patch_InsertDelete() - not functional + - Added CV lattice to selected patches + - Added Per-polygon patch selection + - Added Per-polygon misc_model selection + - Changed default "patch subdivisions" to 4 + - Rewrote camwindow drawing to only change opengl state within the camwnd's member functions + fixes all rendering modes to be more consistent, speeds up rendering + +--------- GtkRadiant 1.1-TA win32 and linux release ---------- + +13/05/2001 + + Spog (patched in TTimo) (Alpha branch) + - Fixed "Fix entity-target/targetname collisions" to use next available tN if tN, else use next available name_N + - Changed patch point selection to pick already-selected points in preference over non-selected + - Changed RemoveCols and RemoveRows to not extrapolate unless a col/row is selected + +11/05/2001 + TTimo (Alpha branch) + - final fix pass to the generated version and about message tags + - improved texture adjustment code (shift+arrows shortcuts) + + texture adjustment commands now affect the texture relatively to their current orientation + they will move along their texture axis, and not along world axis + the texture adjustment commands are now interpreted to be more intuitive: + Radiant will match the up/down/right/left translation messages to the face that is affected + depending on the way the camera is looking at the face, the right move commands will be used + + changes start in Select_ShiftTexture, using new ShiftTextureRelative_Camera + + ShiftTextureRelative_Camera uses several new functions: + + // get the two relative texture axes for the current texturing + BrushPrimit_GetRelativeAxes(f, vecS, vecT); + + MatchViewAxes does the matching between up/down/left/right commands and world directions: + // vec defines a direction in geometric space and P an origin point + // the user is interacting from the camera view + // (for example with texture adjustment shortcuts) + // and intuitively if he hits left / right / up / down + // what happens in geometric space should match the left/right/up/down move in camera space + // axis = 0: vec is along left/right + // axis = 1: vec is along up/down + // sgn = +1: same directions + // sgn = -1: opposite directions + // Implementation: + // typical use case is giving a face center and a normalized vector + // 1) compute start and endpoint, project them in camera view, get the direction + // depending on the situation, we might bump into precision issues with that + // 2) possible to compute the projected direction independently? + // this solution would be better but right now I don't see how to do it.. + void CamWnd::MatchViewAxes(const vec3_t P, const vec3_t vec, int &axis, float &sgn) + + // shift a texture (texture adjustments) along it's current texture axes + // x and y are geometric values, which we must compute as ST increments + // this depends on the texture size and the pixel/texel ratio + void ShiftTextureRelative_BrushPrimit( face_t *f, float x, float y) + + those functions are using various new utility functions: + + // GL matrix product + void GLMatMul(vec_t M[4][4], vec_t A[4], vec_t B[4]); + + // project a 3D point onto the camera space + // we use the GL viewing matrixes + // this is the implementation of a glu function (I realized that afterwards): gluProject + void CamWnd::ProjectCamera(const vec3_t A, vec_t B[2]) + + - UI abstraction layer (interfaces for Gtk MFC and Q3 UI) + +09/05/2001 + Maj (Alpha branch) + - new splash screen + + Spog (patched in by TTimo) (Alpha branch) + + patcing in changes: + - moving void VectorSnap(vec3_t point, int snap); to mathlib + - NOTE: STL dependency removed .. leaving this comment + this will rely on M$ implementation of STL on win32 and the libstdc++ for linux + it should work fine for basic stuff + but M$ implementation doesn't follow the standards when it comes to advanced stuff + it is probably better to leave the STL header in local files and not go towards including it directly from qe3.h + + Spog's Changelog: + + rushing this a bit.. make sure you check it doesn't remove anything you + changed. This only contains changes within /radiant .. i'm pretty sure I didn't + change anything else, but i'll check again. Patch below. + + Fixed ctrl+G SnapToGrid, now never creates degenerate face-planes + Fixed setting an origin for multiple brushes to use origin point of fixedsize + entities + Fixed mirroring and rotation of fixedsize entities including misc_model + Fixed undo/redo on multiple entities to link brushes to entities correctly + Fixed "view > entities as.." menu to display correct default setting + Fixed "view > entities as.." toolbar button to show menu + Changed selection-area of edge/vertex control handles to stay constant when + zoomed + Fixed undo on ctrl+G SnapToGrid + Fixed Selection Invert to set bSelected correctly on patches + Fixed XY-window Z selection origin to be g_MaxWorldCoord + Changed RotateIcon to draw same size at all zoom levels + + Fixed origin drift on saving misc_model with null md3Class + Fixed creation of cap for 'Bevel' type patches + Fixed inverted cap being created for 'Endcap' type patches + Fixed inverting patches on mirror operations + Added snap-selected-to-grid affects only the patch points selected + Cleaned up Select_ApplyMatrix and Select_SnapToGrid + Added drawing of brush planepts in debug build + Fixed texture quality slider adjustment + Removed redundant menu items curve > cap > inverted bevel/inverted endcap + Fixed texture scrolling not working when scrollbar is disabled + Fixed textures with odd dimensions being skewed with texture quality less than + max + Changed Patch Inspector Horizontal/Vertical increment to use pixel values + (default 8) + Changed Patch Inspector Horizontal increment to subtract from S values but not + T values + Changed Patch Inspector Stretch spinner to do something useful + Changed Patch Inspector Stretch default amount to 0.5 + Changed Arbitrary Rotation dialog to reset rotation spinner values to 0 on Apply + !! stops output in console window !! - Added sending q3map output + to /temp/junk.txt to bsp commands, in win32 only + Fixed Patch_Naturalize to calculate T values backwards, correcting texture + vertical flip + Changed patch row/column Insert/Remove to interpolate/extrapolate from existing + curves + Fixed point selection on patches when new points are added + Fixed redundant edge/vertex handles being created for patch brushes and + fixedsize brushes + Fixed refusal to activate brush vertex-drag mode if any patches are selected + Partly fixed Undo picking up patch point drags when no points are selected + Fixed behaviour of vertex selection on patches + Fixed patch point colours in textured mode in cam window + Changed patch point selection to update selection pool on each selection click + +06/05/2001 + TTimo (Alpha branch) + - more setup script changes, will rely on version information + various other fixes in the script file + +02/05/2001 + TTimo (Alpha branch) + - added makeversion.sh to the root, will generate version and date files before compilation + version.h and date.h + - cleanup and fixes to the linux setup scripts + + Spog (CVS add and config by TTimo) (Alpha branch) + - win32 setup script, run setup/setup.sh from cygwin to create a working directory for the setup + +01/05/2001 + TTimo (Alpha branch) + - fixes to linux version from previous set of patches + +19/04/2001 + Hydra (patched in TTimo) (Alpha branch) + http://fenris.lokigames.com/show_bug.cgi?id=3458 : + - *.pfb filter + + SpoG (patched in TTimo) (Alpha branch) + - updated setup data: entities.def common.shader(Q3) and common-spog.pk3 + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=25 : + - Patches: Curve > matrix > redisperse > rows/columns + I changed this function to treat a patch as multiple 3by3 sections when doing + redispersal of control points. The effect is that green patch points are never + moved. I also removed the call to Patch_Naturalize, so the texture coordinates + are not changed (user can hit ctrl+n to naturalize afterwards if desired). + - rewrote the patch_captexture function to be more reliable + - Fixed YZ view drawing and selection being mirrored on plane X=0 + - Fixed X and Z rotation direction to be clockwise as shown on the toolbar button + icons + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8 : + - Fixed: Removed orientation-switching hack to fix 2pt-clip orientation problems, + originally stemming from ass-backwards representation of grid axes. Fixed + clipper to generate 3rd clip point correctly for each axis instead. + - Fixed: Stopped clipper-caulker from NOT applying caulk if the first face of a + brush was "common/caulk". + + TTimo (Alpha branch) + - removed some unused code in the Gtk file dialog, hopefully stabilizing it + +02/04/2001 + TTimo (Alpha branch) + - changed the regular/BP conversion prompt + +01/04/2001 + Spog (patched in by TTimo) (Alpha branch) + - Fixed view > show coordinates now affects Z window + - Fixed minimum/maximum world coordinates now -65536/65536 + - Fixed view > show blocks now only draws vertical lines if not XY view + - Added variable blocksize (gtkr only displays 1024 < blocksize < 65536) + - Changed XY/Z window grid drawing to use floats internally + - Fixed broken XY window grid drawing for higher zoom levels + - Changed camera and Z-checker icons to stay the same size when zoom level changes + +24/03/2001 + Spog (patched in by TTimo) (Alpha branch) + - added filters.cpp to the VC6 Makefile. Linux build needs updating + - Changed Filter system to use brush-flags updated only on actions that affect filtering + - Changed Filter system to allow further extension and future customisation + - Changed show/hide to use brush filter flags + - Changed Find/Replace textures to only rebuild brushes that changed. + - Changed View > Filter menu shortcuts and behaviour + - Fixed various minor spelling errors + + TTimo (Alpha branch) + - Fixed the 'Clean' command in preferences, used to remove only Radiant.ini and not SavedInfo.bin + +06/03/2001 + TTimo (Alpha branch) + - better parse error information: added the line number of the error in (hopefully) all cases + +30/02/2001 + TTimo (Alpha branch) + - texture locking in BP mode for axis flipping and axis rotation (toolbar buttons) (Id bugfix request) + - fixed undo in BP mode + - MatchToken error message in q3map improved with the script name + +27/02/2001 + TTimo (Alpha branch) + - switched linux makefiles to xml2-config instead of xml-config + latest version of libxml2 is using xml2-config instead of xml-config now + +26/02/2001 + TTimo (Alpha branch) + - fixed a loki_initpath misbehaviour, was overriding the path to the binary with RADIANT_DATA if defined + +21/02/2001 + - Added setup data and a script to build linux setups to the cvs + TTimo (Alpha branch) + - various printf -> Sys_Printf + - added vslick/ directory for Visual Slickedit 6.0 on win32 <- THIS IDE RULES + +20/02/2001 + - Removed "Show Paths" option, already in the filter menu + +19/02/2001 + - Fixed the Enter key handling on the surface inspector (suggested by Spog) + - Fixed skewed textures with lower texture quality + - Changed the order the include directories are searched under Linux + +15/02/2001 + - Moved the Show Cluster Portals option to the Filter menu + - Fixed the rotate and scale toolbar buttons being incorrectly checked + - Fixed arbitrary rotation bug (#3073) + +14/02/2001 + - Fixed CreateFont memory leak on glwidget + +12/02/2001 + TTimo (Alpha branch) + - fixed save as prefab stuff (right dialog name and overwrite prompt) + +10/02/2001 + TTimo (Alpha branch) + - was crashing on win32 boxes where HOME env var was not defined (loading bookmarks) + +09/02/2001 + TTimo (Alpha branch) + - change some code in the file dialog to use g_malloc g_free instead of new + for win32 file dialog and malloc for Gtk file dialog. (all of this trying to get rid of + some of the crashes) + +07/02/2001 + - Fixed Load command initial path + - Fixed some toolbar buttons not being correctly initialized + +-----------? + +06/02/2001 + (Alpha branch) + - Fixed pk3man file dialog errors + - New RC uploaded + - Changed conflicting shortcuts: FilterModels = Shift+M, FilterTriggers = Ctrl+Shift+T + TTimo (Alpha branch) + - fix to hide/show, still selecting hidden brushes + +05/02/2001 + (Alpha branch) + - Fixed compile errors (use stat, not _stat) + - Removed --nofonts option + +04/02/2001 + TTimo (Alpha branch) + - fenris #2866, added a pref to turn on/off name conflicts resolution, rewrote the whole algorithm + - fenris #2823, fixed patch and brush dragging in 0.25 0.5 grids (it's an ugly hack btw) + - fixed the black squares at end of line in Gtk text boxes + RR2DO2 (Alpha branch) + - fix to the "entitypath" fixup in QE_CheckProject. might not compile on linux yet (use of _stat?) + +03/02/2001 + TTimo (Alpha branch) + - fenris #2867, limiting the amount of "spawnflags" "0" appearing in entities. This bug has work left to do, + there are some oddities in the entity inspector behavior described. + +02/02/2001 + (Alpha branch) + - Fixed sleep mode not restoring windows correctly + - Fixed some minimize/restore issues on floating views mode + TTimo (Alpha branch) + - fixed more gtkfilesel stuff. pattern filtering works for both Gtk dialogs + and win32 native. Also checked on linux that it compiles. Renamed gtkfilesel.h + to gtkfilesel-linux.h for consistency. + +31/01/2001 + (Alpha branch) + - New file selection widget with filtering, masks and all other shit we need + TTimo (Alpha branch) + - changed some gtkfilesel API and fixed some bugs on pattern filtering, need to reboot on linux and fix some more + +30/01/2001 + (Alpha branch) + - Added an option to keep the Z and XY views on the same window in floating views mode + - Did some cleanup + TTimo (Alpha branch) + - removed the 3 layers in terrain entities limitation, increase version tag to patchlevel 3 + sent for testing to AstroCreep, bug is still there + - fixed one more thing with RC file (get it in the right dir) + - worked on file dialog and pattern filtering, still issues left + +29/01/2001 + Jonas (patched in by TTimo) (Alpha branch) + - Using RC file to tweak the font size on win32, looks much nicer now! + - Fixed fenris #2773: esc key behaviour when group window has focus / dependant on view modes + +28/01/2001 + TTimo (Alpha branch) + - Finished fenris #2810 (Snap T to grid), it was a bitch + - Fixed fenris #2769: raise the brush max size (it was already big, now it's just not sane) + - Fixed fenris #2965: eclass (entities) loading code broken, needs to be checked on linux build before closing + +27/01/2001 + TTimo (Alpha branch) + - Fixed vertex edit prefs broken + - more fixes in Gtk libs + - Fixed clipping + brush primitives bug (#2644) + - Fixed clip caulk related issues (#2912) + - Added Snap T to grid back (#2810) <- still need to test and validate it, I'm too tired tonight + +25/01/2001 + TTimo (Alpha branch) + - New Gtk file selection dialog seems stabilized, built a Gtk SDK for the new Gtk libs. + +24/01/2001 + (Alpha branch) + - Fixed elapsed time displayed by q3map + - Fixed Radiant loading some TGA files upside down + TTimo (Alpha branch) + - more work done on the file selector. Added a win32 pref to select between regular win32 file dialog and Gtk one. + The advanced file selector is still not stabilized on win32. I am thinking about letting go and sticking to the regular one. + +23/01/2001 + (Alpha branch) + - Fixed q3map not finding md3 files under missionpack/ + - Prompt to save changes when choosing a recent file + - Fixed window title when choosing File/Load Map + - Don't show hidden brushes in the Z window + +22/01/2001 + (Alpha branch) + - More File Dialog fixes + +21/01/2001 + TTimo (Alpha branch) + - version first version of the advanced file selector on win32 + seems to work nicely, but crashes when you actually load something .. needs debugging! + + leo (Alpha branch) + - Set correct initial directory for the File/Load command + +17/01/2001 + (Alpha branch) + - Fixed q3map is crash if a .shader file is referenced in shaderlist and not found + + TTimo (Alpha branch) + - fixed some license headers on gtkfilesel.c, started looking into porting gtkfilesel.c to win32 + +16/01/2001 + raistlin + - the tree is opened whoooo! + + TTimo (Alpha branch) + - replaced the old Id header by the newer version (BIG update, to trunk and Alpha) + - switched str.h to BSP + - added LGPL license to the sample dll + +15/01/2001 + (Alpha branch) + - Added camera window toggle option to all views mode + + TTimo + for trunk and Alpha: + - renamed TOOL_SOURCE_EULA to LICENSE_ID + - renamed CONTRIBUTOR to CONTRIBUTOR_AGREEMENT + - added CONTRIBUTORS and LICENSE + +14/01/2001 + (Alpha branch) + - plugin SDK is back in setup/ moved the sample dll to be in plugins/, need to rewrite the SDK scripts + - built a lightweight plugin SDK with cygwin makefile for the sample plugin + +12/01/2001 + (Alpha branch) + - Set map modified flag when deleting a brush + - Minimize all windows when the main window is minimized in floating views mode + +11/01/2001 + (Alpha branch) + - Cleaned up the View/Show submenu + - Fixed texture menu splitting + - Fixed major grid lines on 128 and 256 grids + - Load only a single .def file if specified in the project settings + - Fixed q3map Makefile + + TTimo + (Alpha branch) + - Removed plugins/shaders from Alpha branch + - got rid of common2/ and code, moved qfiles.h and surfaceflags.h into common/ + - put the licensing headers in all source files (forgot some? byte me) + +10/01/2001 + leo (Alpha branch) + - Added new filter system based on FAKK2 Radiant + + TTimo + - fixed whatever could be fixed to make it compile with the new directory layout + (Alpha branch) + - project files update + +09/01/2001 + (Alpha branch) + - Added undo for patch redisperse rows and patch redisperse cols commands + - Fixed Show Z Outline menu item + +30/11/2000 + TTimo + - removed content flags and value from qtexture_t, these have moved to the IShader + (NOTE: qtexture_t != texdef_t, texdef_t still using flags value and content) + + - Rewrote the linux plugins Makefiles + +29/11/2000 + - Improved the way modules are loaded + - Added new parameter to QERPlug_RequestInterface + - finished VC6 project files conversion for new directory structure + - fixed shader blending on terrain maps bug + +08/01/2001 + - Updated Makefiles for the new directory structure + - Fixed View/Show/Entities menu not being checked correctly + - Fixed "Invert Selection" command selecting hidden brushes + - Fixed "Select All of Type" command not working correctly after the Enitity Window is closed + - Fixed grid being drawn even when it's the same color of the background + - Fixed "Toggle Size Paint" not turning off + +05/01/2001 + - Fixed autosave interval being calculated wrong + - Fixed autosave path under Linux + - Fixed q3map crash when trying to load missing pcx files + - Fixed q3map not finding .bmp files for the terrain alpha map + +04/01/2001 + - Fixed GL stack underflow when loading a misc_model + +03/01/2001 + - Fixed patches remaining half-selected after "Region set selected" (#2748) + - Fixed Surface Inspector spin buttons rate (#2776) + - Fixed some shortcuts not appearing on menu items (#2786) + + +================================================================================ + 1.1-TA beta +================================================================================ + +02/01/2001 + - Fixed memory problem if a file without extension is entered in the file save dialog. + - Fixed double slashes "//" on filenames when saving a map + - Fixed pk3man plugin not finding the toolbar bitmaps + - Fixed double clicks being considered 2 mouse clicks + + RR2DO2 (applied by TTimo) + - Clusterportal filtering ('View > Show > Show clusterportal' toggle) + +01/01/2001 + TTimo + - updated Web/ with new stuff, web site ready for release + - fixed a crash with multiple edge dragging on win32 (was caused by compiler optimizations, + this one was a major pain) + +31/12/2000 + - fixed the file open/save dialogs initial directory + + RR2DO2 (applied by TTimo) + - bug fix in the terrain loading speedup + + TTimo + - tried to lookup the Z window minimum width problem, added #define DBG_WINDOWPOS code to investigate + +30/12/2000 + RR2DO2 (applied by TTimo) + - fix to CSG Merge in the menu drop down (menu was there, command not hooked) + - some message formatting fixes + + TTimo + - rudimentary pattern matching in file selection, affects the plugin API too + - fixed silly bug in the "clipper uses caulk" code + - major speedup to the loading code on terrain entities + (in mpterra2, from 113s to 4s for the main terrain entity) + - patched back the file open/save dialogs initial directory to override in TA mode + - fixed Patch output crash in the plugin API + - fixed BP conversion in the brush output of the plugin API + + minkey (applied by TTimo) + - fix to the m_pfnLoadFile code to use VFS + +29/12/2000 + - Remember main window position in floating views mode + - Fixed wake up when running the engine in floating views mode under win32 + + TTimo + - various fixes and debug hooks for PJ bug reports + +28/12/2000 + - Fixed VFS initialization order + - Removed texture menu splitting option from preferences (now it's automatic) + + TTimo + - built an initial setup, updated the changelog file for 1.1-TA-beta + +27/12/2000 + - Fixed Q3Map output window being too wide on some errors + - Added VFS to q3map + + TTimo + - replace a printf in q3map by Sys_Printf (!), which is what should actually be used + - added a set of functions to vfs to help with file dialogs, building relative files etc. + - reworked the file dialogs so they default in the right location (open/save as/md3 loading/sound loading) + - fixed shader editor to work with the right path + +26/12/2000 + TTimo + - put the converted HTML manuals (Radiant, shaders and model) in the tree (and the win32 setup) + - added the new Terrain and Team Arena mapping manuals (added to the setups too) + + leo + - Copy and paste across different instances of Radiant + - Fixed wait cursor when copying + - Print engine command line to the console + - Fix glib warnings when running the engine + - Fixed shader files being loaded twice + - Texture menu now automatically breaks when it reaches the maximum screen height + +24/12/2000 + - Fixed q3map to compile with the new LoadJPGBuf parameter + + TTimo + - added TA paths to the BSP commands and running engine + - fixed a bug if running with monitoring disabled (generating the .bat was borked) + - changed the -moddir implementation to a global switch in q3map (same as -connect) + - moddirparam as a global variable in cmdlib, added a TA_HACK in there + - changed the SetQDirFromPath to stick to "baseq3/" when using -moddir + TODO: check standalone files + +23/12/2000 + TTimo + - fixed some sleep/wake code (crashes and wakeup problems on models) + - vfsInitDirectory for TA directory (needs to be checked on linux) + - changed my mind on entities.def, if TA is enabled, load entities-TA.def on top of regular entities.def + + mickey (applied by TTimo) + - some memory overrun fixes + +22/12/2000 + - Fixed plugin Makefiles to not use private/ + + RR2DO2 (applied by leo) + - Fixed SetTallBrush undo + - Added bug report link to help menu + +21/12/2000 + - Daily Linux compilation fixes + - Fixed q3map to read .pk3 files from the directory set by -moddir + - Fixed vfs not listing all files correctly + - Fixed libjpeg crashing on some jpeg files + - Load .def files depending on the current game + +20/12/2000 + - Applied Mickey's patch to fix win32 window position save/load. + - removed missing _msize call + + TTimo + - cleaned up more g_malloc g_free problems, cleaned a INPUT_BUF_SIZE problem in jpeglib + +19/12/2000 + - More manual updates + - Fixed bugs comparing file extensions + - Added VFS to the Alpha branch + + TTimo + - moved game selection to the project settings + - got leo's vfs fixes, started changing the memory allocation scheme to glib + - moved 'free' calls to g_free with a #define in cmdlib, Radiant seems to run nicely again + - removed calls to _msize .. those were causing heap debug assertion failures + - upped more stuff + +18/12/2000 + - Added popup menus with the list of active textures to the find texture dialog + - Fixed some menu checkbuttons + - Changed max number of shader files parsed by q3map to 128 + - Updated manual images + +17/12/2000 + - Fixed bug with the texture window scrollbar range + + Mickey (patched in by TTimo) + - fix to the floating windows mode, don't send windows to the desktop when raising something else (#2659) + + TTimo + - finalized the merge and move into worldspawn commands by adding the undo stuff + - fixed the clamping problems when flipping or mirroring patches + - added 0.5 and 0.25 grids + - added undo to Select_CompleteTall Select_PartialTall and Select_Inside + - added on-the-fly conversion between regular brush coordinates and brush primitives texturing in the plugin API + +15/12/2000 + - Fixed crash on Shift-A (Select all of type) + - Save the state of the toolbar buttons + - Remember the state of the Show Patch Bounding Box button + - Double clicking on an entity on the Entity View tree selects the entity + - Sort the list columns of the map info dialog + - Fixed a bug that would allow multiple Entity View dialogs + + Mickey (patched in by TTimo) + - saving position and size of the entity window between runs and during usage + + TTimo + - two new commands in the drop down menu: + "move into worldspawn" will move selected brushes to worldspawn and eventually delete entities which end up with no brushes + "merge brushes" will merge brushes into an entity (from worldspawn or from another entity) + - added cleaned HTML version of the editor manual in the tree + +14/12/2000 + - When pressing a letter key in the entity window list, scroll to the entity starting with the key pressed + - Fixed backspace not working on the texture subset entry + - Added version check when loading savedinfo.bin + + TTimo + - started implementing Select_Merge and Select_Seperate for workflow improvement on terrain maps + +13/12/2000 + - Finished GtkGenSurf + + RR2DO2 (merged in by TTimo) + - patch to q3map, added option -custinfoparams for custom surface flags (still need documentation) + + TTimo + - zoom out and grid drawing taylored to the world size + +12/12/2000 + TTimo + - quick win32 update to gensurf + - fixed #2610 (MAX_NETMESSAGE) .. needed a consistent rewrite of the way we parse the stream + +11/12/2000 + - Fixed linux compiler errors from recent changes + Not fixed today but I forgot to add those to the Alpha changelog + - Fixed multiple Map Info dialogs bug + - Fixed texture window not scrolling to the top when a new directory is loaded + - Fixed GL Windows grab pointer bug + - Fixed crash after map compilation if the map leaked + - Fixed q3map crash if MAX_SHADER_FILES is reached + +28/11/2000 + - Fixed sleep mode restoring hidden windows (win32) + - Fixed find/replace textures dialog layout and keep it always on top of the main window + - Replaced malloc/free calls with g_malloc/g_free to avoid the win32 limitation + +27/11/2000 + - Removed glu.h dependencies + - Added new file selection dialog + - Removed g_PrefsDlg.m_bDisableAlphaChannel (always FALSE) + - Added shortcuts for sleep and simple patch mesh + - Fixed crash after sleep mode (no GL context current) + +24/11/2000 + - Rewrote the jpeg functions of the image plugin + - Replaced some MFC classes with glib + - More shader plugin fixes + - Fixed bug with select all entities command + + TTimo + - fixed q3map to handle the new LoadJPGBuff length parameter + +22/11/2000 + - Fixed crash in Error() if there's no current GL context + - Fixes to the shaders plugin + + TTimo + - created VC6 project file for image module + - modified the m_pfnError in qerplugin.h to use (char *, ...) construct + +21/11/2000 + - Ensured that the plugins are loaded in the correct order + - Added Sys_FPrintf and Sys_Printf to the plugin interfaces + - Some VC++ fixes + + TTimo + - more fixes to the world size + - modified moduleentry_t so it compiles on win32. need to update the code in all modules probably + - other minor fixes and updates to get everything building on win32 + +20/11/2000 + - Moved image loading code to a plugin + - Fixed some bugs in the shader plugin + - Now using glGenTextures to set texture ids + +19/11/2000 + TTimo + - shader code removed from Radiant core, relies on shader module + - added ctrl-alt-LBUTTON = multiple brush select without selecting whole entities (from TA update) + - added an XML testing proggy in DevDocs/ + +18/11/2000 + TTimo + - shader module is compiling + - reworked the way we deal with required interfaces, + automated the interface request process and added code to check the required modules have been found + +17/11/2000 + - Q1 VFS plugin + - Changes to the VFS API to detect the format supported by a plugin + - Added checks to PluginManager to load the correct VFS plugin + + TTimo + - made a mess with XML MAX_NETMESSAGE error, still not fixed + - merged q3map 1.0r (TA update from Id) into the tree - important files modified: surfaceflags.h qfiles.h + +08/12/2000 + TTimo (shit I'm 24 now) + - added gtk gensurf, VC6 project files are up to date, linux Makefile not checked + - fix some WINAPI stuff on above code + - add idata.h for raw access to editor data + - new _QERAppShaderTable for shader module -> editor functions + - added new entries in various tables (GL, parser etc.) + - shader module is well under way + +16/11/2000 + - Added a color selection dialog function to the plugin API + - Added profile read/write functions to the plugin API + + TTimo + - MAX_NETMESSAGE bug: patched q3map so it sends in several messages if the problem occurs + still need to update Radiant to recognize XML nodes split into several messages (using an input buffer) + - added a test map for MAX_NETMESSAGE: sput.map + - project file for vfspk3and win32 patching + +15/11/2000 + - More plugin cleanup + - Added IsEqualGUID() to qerplugin.h + + TTimo + - merged Alpha back in (didn't try to merge this CHANGES file) + - backported some stuff from the trunk to here, the Sys_Printf, gtk_MessageBox and profile stuff + - created VC project file for gtk-based gensurf plugin + +14/11/2000 + - Fixed DumpUnreferencedShaders() + + TTimo + - added QE_CheckProjectEntity to check paths are following the right conventions + +13/11/2000 + - Fixed bugs in the vfs plugin + - Added support to vfs plugins in Radiant + + TTimo + - updated project file to libxml2-2.2.8, use libxml2 as the directory name for whatever version.. + NOTE: libxml2-2.2.8 needs some patching to compile right.. + +12/11/2000 + TTimo + - all Radiant functions that might be exported in interfaces need to use the WINAPI calling convention + modified the GTK functions code accordingly + - started writing the shaders module + +10/11/2000 + - Added new GTK functions to the plugin API + - Added 'parent' parameter to MessageBox, file_dialog and dir_dialog + - Fixed Help commands (Linux) + +09/11/2000 + - Fixed bug in the Z wnd code + - Fixed copy text from the console (win32) + + TTimo + - moved the libxml library out of the tree, updated the VC6 project files accordingly + +08/11/2000 + - ZWnd always on top (view #2, win32) + + TTimo + - added Escape key to hide the entity inspector + - S and Shift+S now act as toggles on the inspectors + +07/11/2000 + - Added ungroup command to right click menu + - Fixed message box accelerator bug + - Fixed GL error on win32 startup + + TTimo + - additions to the BSP interface + - fixed DestroyCursor error + - clipper caulks faces (and prefs checkbox) + +06/11/2000 + - Cleaned PrtView and TexTool plugins + - Fixed bug in texture menu names (#2506) + - Added splitters to Entity dialog + + TTimo + - started clipper caulk implementation + - fix to the pointfile not drawing in 2D views + - MAX_BUILD_SIDES in q3map debug stream + +05/11/2000 + - Merged Alpha branch with the trunk + + TTimo + - more plugin interface for Q3Build, and plugin SDK additions + - merged q3map Realloc back into Alpha branch + +04/11/2000 + - Fixed crash during startup if Zwnd was hidden in views #2 and #3 + - Fixed ToggleConsole command + - Fixed ToggleEntity and ToggleTexture commands in view #2 + - Fixed plugin Makefiles + - Removed -rdynamic from Radiant link options (crashes pk3man plugin) + +03/11/2000 + TTimo + - directory reorganisation for the plugin SDK, added an interface/ directory + +02/11/2000 + - Save ZWnd state in views #2 and #3 + - Entity dlg always on top (linux) + - Fixed shortcuts.ini parsing bug + - Fixed editpad crash if editpad not present (win32) + - Fixed bugs in the internal shader editor + - Fixed widget_show if window moved after gtk_widget_set_uposition + +================================================================================ + 1.1 beta +================================================================================ + +31/10/2000 + - Continue loading if glXGetProcAddressARB is not present (Utah-GLX fix) + - Fix BSP commands not working if a map is not in "mapspath" (linux) + +30/10/2000 + - fixed the Region commands, "Region > Set brush" is working + Region uses the camera as spawn point. + - Fixed view/show menu initialization + - Fixed warning when starting view #2 + - Fixed z wnd in view #3 + - Fixed win32 sleep mode crashes on views #2 and #3 + - Added "Restart" message when changing texture quality in the preferences + - Cleanup: removed radbsp.cpp (unused) and unzip.cpp (already in pak.a) + +29/10/2000 + - Fixed SIGCHLD handler + - Built 1.1b setups + +28/10/2000 + - Fixed q3map bug visbytes > MAX_MAP_VISIBILITY + - Fixed clipper display bug + + TTimo + - updated quakev2.qe4 with -vlight options + - added checks in q3map to prevent crashing on allocating a winding too big + will stop with an error now. + - added the corresponding editor support for debug messages if MAX_POINTS_ON_WINDING is exceeded + + G_Dewan + - improved q3map, reducing minimal memory footprint by about 45Mb + +27/10/2000 + - Fixed crash in BSP debug window + - Reorganized the preferences dialog + - Fixed q3map Makefile + - Fixed +/- bug in win32 (Gtk patch) + - Fixed Alt shortcuts bug in win32 (Gtk patch) + - Fixed q3map crash when visbytes > MAX_MAP_VISIBILITY + + TTimo + - improved snapshots behaviour, doesn't snapshot non-modified maps + +26/10/2000 + - Fixed patch inspector not showing after it has been closed + - Added 'Reset' button to entity dialog + +25/10/2000 + - Fixed more grid issues + - Fixed load window position bug (saved pos greater than screen resolution) + - Fixed selection nudge bug + - Improved entity windows layout + - Fixed GL font not being recreated when exiting sleep mode + + TTimo + - cleaned m_nTextureTweak and m_bSnapTToGrid + - improved the CycleCapTexturePatch command, now cycles across the 3 planes only + and works on multiple patches at once + +24/10/2000 + - Added an overwrite prompt when saving files + - Fixed 128 and 256 grid display + - Commented-out grouping code (not functional yet) + +23/10/2000 + - Fixed q3map to load jpgs under Linux + - Fixed wake-up crash when floating windows were closed (#2423) + +21/10/2000 + - More q3map and radiant Makefile fixes + - Remember size/position of the entities dialog + +20/10/2000 + - Redirect Gdk warnings + - Draw border around active XY wnd + - Moved some scripts to the Makefile + - Added shift+rclick to zoom in/out + - Removed minimize/maximize buttons for z wnd in floating mode under win32 + + TTimo + - Two new entries in View > Show: Show Outline and Show Axes + Show Outline turns on/off colored outline of the current view + Show Axes turns on/off display of a small axis base in the 2D view + - fix q3map Makefile to use external libxml2 source + + G_DEWAN + - Fix to bogus noshader error message in q3map + +19/10/2000 + - Added new console functions that support colors + - Revised linux makefile for debug/release builds + - Redirect Gtk warnings to the console + + TTimo + - reorganized the entity inspector window, layout depends on the number of flags to get + more space in the comment window. + +18/10/2000 + - Fixed add/remove bsp items in project settings dialog + - Did some cleanup (removed #define WIN32_CONSOLE) + - Fixed console not working in view #3 + - Fixed warning when exiting in views #2 #3 + + TTimo + - fix to entity inspector comment window for the eclass_t on win32 (removed the white squares) + +17/10/2000 + - Fixed texwindow not scrolling when last texture is large + - Added LOD for patches + - Fixed prefab path & user ini in preferences dialog + +13/10/2000 + TTimo (XML branch) + - basic architecture for XML feedback is functional + see radiant/feedback.cpp radiant/watchbsp.cpp q3tools/common2/inout.c + merging back in alpha + +04/10/2000 + TTimo (XML branch) + - sax interface is in Radiant, need to add a state machine and proper processing + +04/10/2000 + TTimo (XML branch) + - experimental use of SAX interface to parse the stream on the server side, see q3tools/q3map/NetTest + +03/10/2000 + TTimo (XML branch) + - adding libxml2 in the repository, based on libxml2-2.2.4 with project files and stuff to build on win32 + +31/09/2000 + TTimo (XML branch) + - new common2/ dir, output system rewritten through Sys_Printf + - experimental use of libxml + +28/09/2000 + TTimo (Inspector branch) + - shift+arrows matches the increments from the surface inspector + - button 'Match Grid' in the SI to set the increment according to current grid + +25/09/2000 + TTimo (Inspector branch) + - fixed crappy bug in SavedInfo.bin upgrade (when the struct sizes don't match) + - surface inspector has inc step dialog boxes (+saved in prefs) + - face selection is always on (was something weird from the prefs) + - undo works better with the surface inspector + +25/09/2000 + - Added ARB_Multitexture support + +21/09/2000 + - Fixed preferences dialog warning + - Added new grid sizes + +20/09/2000 + - Fixed small bugs reported from Fenris + +17/09/2000 + TTimo + - fixed a bug with template project loading / path to the engine safecheck (a weird hidden one) + G_Dewan & TTimo + - fixes to the process spawning (Q_Exec in cmdlib) + appropriate warning and error messages + Fishman + - antialiased drawing in 2D views + +14/09/2000 + TTimo + - radiant.log commandlist.txt and radiant.pid are create in g_strAppPath on win32 and g_strTempPath on linux + - moved the splash screen after the .pid code + - I suspect a bug in the .pid removal, added a check and message box + - help works again on win32, spawning Word with the Q3Rad_Manual.doc (temporary solution of course) + - surface inspector: removed all Q2 related stuff, fixed horizontal shift, reorganized the widgets layout + hooked the widgets to apply the changes on the fly (the inspectors need a good chunk of work) + - fixed a radiant.pid bug + - added icon to MSVC6 project (with some help) + + G_Dewan + - fix to BSP menu order getting mixed up + - fix to the file dialog + +11/09/2000 + - Added splash screen + +25/08/2000 TTimo + - launch sleep mode before running game + - fixed Map_Snapshot bug + - going to sleep works on view n2, raising is still screwed (contexts) + +24/08/2000 TTimo + - fixed some sleep mode stuff + - fixed map snapshot bug + +21/08/2000 + - fixed stuff to build on linux + - Merged in q3map 1.0p + +18/08/2000 + - Removed "High Color Textures" option (always on) + - Removed "Status Point Size" option + +17/08/2000 + - Fixed win32 console issues + +16/08/2000 + - added g_strTempPath + - restore maximized window state + - fixed logo.bmp + +15/08/2000 TTimo + - fixed keyboard shortcuts + - fixed engine path in prefs (must use the file dialog to change) + - fixed a bug related to engine path and project templates + +15/08/2000 + - Removed QE4 update model option (always on) + - Removed Buggy ICD option (always off) + - Reorganized the preferences dialog to take a bit less space + +14/08/2000 TTimo + - using profile.cpp code to read shortcut keys files + - moved DevDocs/changelog.txt to data/changelog.txt + (data/ should be used for user-side stuff and DevDocs/ for developpers) + - added data/quickstart.txt with a beginning of info about the main differences + between Q3Radiant 202 and GtkRadiant. to be used as a doc later. + +13/08/2000 TTimo + - added DevDocs/WIN32SETUP and DevDocs/changelog.txt + changelog.txt is end user changes + WIN32SETUP the TODO list for install specific stuff + - wrapped a first version of the win32 installers (full and patch) + +11/08/2000 TTimo + - quickfix to put undo/redo back in + - added DevDocs/WIN32SETUP, describes what I'm up to with the setup of win32 version + +10/08/2000 TTimo + - added back the window position saving code that was in earlier tree + NOTE: would have rather have it done in prefs than hooked in mainframe_delete and MainFrame::Create + (would have been cleaner IMO) + NOTE: IT'S STILL BROKEN .. I ADDED THE CODE BUT I MUST BE MISSING SOMETHING + NOTE: it doesn't remember the maximized state. It should. + +08/08/2000 TTimo + - fixed win32 build for GLWidget code, added WINAPI calling convention on all exported stuff + - fixed TexTool to compile under win32 + +07/08/2000 TTimo + - fixed some crash with the new jpeg lib + - fixed console logging behaviour (was always turned on at startup) + - added console logging checkbutton in prefs + +07/08/2000 + - Merged the GLWidget branch + - Merged 202 patches + - Fixed "clean" button in the preferences dialog + - Added pid startup detection + - Updated plugin interface with GLWidget functions + - Updated TexTool plugin + +04/08/2000 + - Added "errno" string to the Error() message box + - More 202 patches + +03/08/2000 + - Merged changes from MFC Radiant 202 + - Fixed the win32 GLWidget stuff + +02/08/2000 + - new OpenGL widget to keep all platform specific code in only one file + +01/08/2000 + - Added code to restore the windows when coming out of sleep mode + - Rewrote the TexTool plugin + +31/07/2000 TTimo + - added vc6 projects for PrtView + - tested PrtView and Radiant against latest binary release of Gtk (works great) + + Leo: + - Updated VC5 projects + - Fixed plugin loading under win32 + - Updated PrtView to compile under win32 + - Radiant is now iconified when going in sleep mode + +30/07/2000 TTimo + - prefs dialog for BSP monitoring + - Added data/ directory with entities.def and quakev2.qe4 + - stabilized syntax of v2 project file, same project file for both platforms + - added DevDocs/WIN32BETA with a list of stuff to do before going on public beta on win32 + - added DevDocs/d2u .. handy script to remove linefeeds from DOS files + - added radiant/radiant.proj, project file for source navigator (SN rules) + + Leo: + - Fixed the logfile crash when ~/.q3a/radiant doesn't exist (fenris #1953) + +28/07/2000 TTimo + - Fix to the win32 console to use window's default font + - Added File > Sleep for experimentation + NOTE: we need to keep Radiant minimized when going into sleep mode + +28/07/2000 + - Finished the win32 console replacement + - Fixed bug 1952 (map loading segfault) + - Added a Makefile to the libs dir + +26/07/2000 + - Added PrtView plugin + - Added qvm target to source/Makefile + - Another release candidate sent to QA + +18/07/2000 + - Fixed the slow updates issue in the win32 version + - Added 3 new variables to fix the paths issue + +17/07/2000 + - Fixed the plugin search directory (broken with the changes to g_strAppPath) + +14/07/2000 + - Increased the timer speed in MainFrame::RoutineProcessing + - Added code to release and recreate the contexts to the win32 version + - Fixed the mouse capture under win32 + +13/07/2000 + - Fixed the new path and bsp problems + - Sent new version to QA for testing/release + +12/07/2000 + - Added "tools/" back to g_strAppPath under linux + +11/07/2000 + - Added code to release and the recreate the GL contexts (linux) + +10/07/2000 + - Changed directory structure + +09/07/2000 + - Added CS_OWNDC for win32 with a GDK hack + +07/07/2000 + - Fixed "white textures" bug (gluBuild2DMipmaps bug) + +03/07/2000 +TTimo: - main.cpp l386, removed tools/ appending to g_strAppPath, g_strAppPath is expected to point to the app.. (hope it doesn't break anything) + +02/07/2000 + - Added precompiled headers for faster win32 builds + +01/07/2000 + - Finally got q3asm/lcc working + +26/06/2000 + - 201 patches + - Added screenshot option + - Added an error message if X is running in 8 bits + +23/06/2000 + - Updated with build 200 source + +13/06/2000 + - Remove --noshare option + - Added --nofonts option to workaround a bug using glXUseXFonts in XFree 4.0 + +04/06/2000 + - Fixed bug with the Ctrl-X accelerator for the File/Exit menu + +02/06/2000 + - Converting the TexTool plugin + +30/05/2000 + - Changes to the plugin loading code + +28/05/2000 + - Files with an underscore character are now correctly parsed in the MRU menu + +25/05/2000 + - Fixed _exit bugs + - Fixed bug in CMapStringToString::SetAt + - Fixed copy/paste/clone bug + +24/05/2000 + - Finished applying the 199 patches + - Fixed a bug in CShaderArray::SortShaders() that was calling the wrong version of InsertAt() + - Added numbers to the MRU menu items + +23/05/2000 + - Fixed the floating point bug in gluBuild2DMipmaps + - Fixed the time display after a bsp command is executed + - Applied several patches from the 199 version + - The console is now visible by default + +21/05/2000 + - Added a replacement for gluBuild2DMipmaps + +20/05/2000 + - Fixed the repeating textures bug when playing a map, "brush_primit" must be set to "1". + - Fixed the bug about no current GL context when exiting in computers with 3dfx cards. + - Textures in the directory pointed by "texturepath" are now loaded correctly in Radiant. + - Fixed bug in q3map where it would require a shaderlist.txt file in ~/.q3a/baseq3/scripts. + +19/05/2000 + - Fixed a bug in the multiple directories hack in libs/pakstuff.cc + - Finished the filter in the texture window + - The wait cursor is now correctly set in the XY window + - Added replacements for gluPerspective and gluLookAt + - Textures can now be stored in 2 places: + * The path pointed by "texturepath" (defaults to ~/.q3a/baseq3/textures, + but can be changed in the project settings) + * The base texture path (/baseq3/textures) + - Radiant and the q3map tool now looks for shaders in ~/.q3a/baseq3/shaderlist.txt + and /baseq3/scripts/shaderlist.txt + +18/05/2000 + - Plugin menu fixes + - Created a simple text editor to edit the shaders (instead of calling an external program) + - Copy and paste now work + - Fixed some bugs with the MRU menu + - Some menu items are now enabled/disabled correctly in MainFrame::RoutineProcessing () + - Added a new command line option (--cdpath) to set the CD-ROM path + - Fixed some bugs in the entity window, now it's possible to add/edit/remove properties + - New directory paths: + * maps now default to ~/.q3a/baseq3/maps + * autosave files are saved in ~/.q3a/baseq3/maps + * .pk3 files can be in ~/.q3a/baseq3, /baseq3 and in the CD-ROM + +17/05/2000 + - Fixed bug deselecting a brush after the surface dialog is open + - hide cursor when right-dragging XYWnd + - files saved to /tmp are now saved in ~/.q3a/radiant + - Disabled undo + - Fixed a bug in FillTextureMenu + - User can now correctly change the accelerators at run-time + - Accelerators are read from ~/.q3a/radiant/radiant.ini + +16/05/2000 + - Finished the patch inspector + - Finished the texture toolbar + - more small bug fixes + +11/05/2000 + - Finished the GroupDlg stuff + - Added support to read pak files from the Quake3 CD-ROM + - moved /tmp/paklog.txt to ~/.q3a/radiant/paklog + - added functions to replace GetKeyState and SetCursorPos + - fixed the command key handlers for the mainwindow diff --git a/COMPILING b/COMPILING new file mode 100644 index 00000000..7dd7bd14 --- /dev/null +++ b/COMPILING @@ -0,0 +1,72 @@ +developer documentation for GtkRadiant 1.5.0 +============================================ + +getting the source +================== + +The latest source is available from the Subversion repository. + https://zerowing.idsoftware.com/svn/radiant/GtkRadiant/trunk/ + +The subversion client can be obtained from the Subversion site. + http://subversion.tigris.org + +To get a copy of the source using the commandline Subversion client: + Change the current directory to the desired location for the source. + svn checkout https://zerowing.idsoftware.com/svn/radiant/GtkRadiant/branches/1.5/ ./GtkRadiant + svn checkout https://zerowing.idsoftware.com/svn/radiant.gamepacks/Q3Pack/trunk/ ./GtkRadiant/games/Q3Pack + svn checkout https://zerowing.idsoftware.com/svn/radiant.gamepacks/UFOAIPack/branches/1.5/ ./GtkRadiant/games/UFOAIPack + + + +Linux/OSX(using X-windows) +========================== + +environment: +- gcc >= version 3.1 (preferably) +- scons >= 0.96 (radiant is built with scons rather than make) +- python >= 2.3.0, (scons requires python, some build steps use python) +- svn >= 1.1 (some build steps use svn) + +dependencies: +- gtk+ >= 2.4.0 (requires glib, atk, pango, iconv, etc) +- gtkglext >= 1.0.0 (requires opengl) +- libxml2 >= 2.0.0 +- zlib >= 1.2.0 (for archivezip module) +- libpng >= 1.2.0 (for imagepng module) +- libmhash = 0.9.0 (for q3map2) + +build: +Execute 'scons SETUP=0' in the directory containing SConscript + +install: +run 'python ./GtkRadiant/install.py' +note - this script should be run after each time you update from svn + +run: +Execute './GtkRadiant/install/radiant.x86' (or './GtkRadiant/install/radiant.ppc' on osx) + + + +Win32 (2000, XP or Vista) +================== + +environment: +- visual studio 2005 +- python 2.3.0 or later (some build steps use python) +- subversion 1.1 or later (some build steps use svn) + +dependencies are prepackaged archives, extract them to the directory above GtkRadiant.sln: +- http://zerowing.idsoftware.com/files/radiant/developer/1.5/gtkradiant-1.5-dependencies-1.0.zip + +build: +Open GtkRadiant.sln. +In tools > options > projects > VC++ Directories > executables, add the paths to python.exe (e.g. c:\python23\) and svn.exe (e.g. c:\svn\) +Hit 'Build > Build Solution' (F7) + +install: +run 'python ./GtkRadiant/install.py' +note - this script should be run after each time you update from svn + +run: +set Project > Properties > Debugging > Command to "$(SolutionDir)install/$(TargetFileName)" +hit 'Debug > Start' (F5) diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 00000000..664e339d --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,94 @@ +GtkRadiant CONTRIBUTORS and CREDITS +last update: 28/02/2007 +======================= + +GtkRadiant 1.5 development: +------- +Thomas "namespace" Nitschke spam@codecreator.net +Stefan "Shaderman" Greven +"Topsun" +SmallPileofGibs spog@planetquake.com +"Tr3b" + +Small contributions and improvements for 1.5 +-------- +Eric "eb" Barth + +Loki +---- +Leonardo Zide leo@lokigames.com +Mike Phillips (Loki QA) +Bernd Kreimeier (overall coordination) + +QER.com +------- +TTimo timo@idsoftware.com +^Fishman (Pablo Zurita) fish@gamedesign.net +RR2DO2 rr2do2@q3f.com +SmallPileofGibs spog@planetquake.com + +Curry plugin +------------ +Mike "mickey" Jackman +Tim "Maj" Rennie + +PrtView plugin, various bug fixes and q3map guru +------------------------------------------------ +Geoffrey DeWan + +Gensurf plugin +-------------- +David Hyde + +PicoModel +--------- +seaw0lf with assist by ydnar + +Q3Map2 +------ +Randy 'ydnar' Reddig + +Updated shader files, textures, entities.def, keyboard shortcut list +overall testing and feedback +---------------------------- +Jean-Francois "Eutectic" Groleau + +Improvements and bug fixing +--------------------------- +Jan Paul "MrElusive" van Waveren +Robert Duffy +Forest "LordHavoc" Wroncy-Hale +Nurail +AcidDeath +Chronos +Michael Schlueter +Jamie Wilkinson +Robert "Tr3B" Beckebans + +Web +--- +Dave "Bargle" Koenig +Jason "Wolfen" Spencer +Shawn "EvilTypeGuy" Walker + +Thanks to John Hutton, AstroCreep and W2k for web help + +FAQ +--- +Equim and Wex + + +Testing/Feedback +--- +Black_Dog, d0nkey, Fjoggis, Jago, jetscreamer, gibbie, Godmil, Gom Jabbar, +Mindlink, mslaf, necros, Promit, Ravo, RPG, scampie, sock, sponge, thiste, +voodoochopsticks, Zwiffle + + +Misc +---- +Thanks to everyone on the beta mailing list and +irc.telefragged.com #qeradiant for testing and feedback. +Updated icons by AstroCreep! +Bitch-slapping by RaYGunn! +Last minute bugs by SPoG! (SPoG--) diff --git a/DoxyConfig b/DoxyConfig new file mode 100644 index 00000000..b9c0619d --- /dev/null +++ b/DoxyConfig @@ -0,0 +1,47 @@ +# Included Doxygen Config file +#--------------------------------------------------------------------------- +# Project name & version number +#--------------------------------------------------------------------------- +PROJECT_NAME = +PROJECT_NUMBER = + +#--------------------------------------------------------------------------- +# Where to put the output +# Note: The images dir should be next to the created html dir within the +# output dir. +# eg; +# [Current Dir] +# L__[OUTPUT_DIRECTORY] +# L__[html] +# L__[images] +#--------------------------------------------------------------------------- +OUTPUT_DIRECTORY = ../GtkRadiant-doxygen + +#--------------------------------------------------------------------------- +# Where to read the sources +# you can add more than one source... +# INPUT = radiant/ \ +# tools/quake3/q3map \ +# tools/quake3/q3data +# +# Recursive is set on, so setting it to ./ (current dir) would read source +# files recursively from the current dir down. (which would take a while) +# +# eg: To document just include, if the current directory is ../GtkRadiant/ +# then... +#--------------------------------------------------------------------------- +INPUT = radiant/ + +#--------------------------------------------------------------------------- +# Misc settings +# TAB_SIZE - sets the indenting for the inline source and the source +# browser +# INCLUDE_PATH - will include documentation for included files from other +# packages. You can specify more than one path the same as +# shown in the INPUT example Leave it blank if you don't want +# this. +# PERL_PATH - path to the perl executable +# +#--------------------------------------------------------------------------- +PERL_PATH = /usr/bin/perl + diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 00000000..b87c164f --- /dev/null +++ b/Doxyfile @@ -0,0 +1,1117 @@ +# Doxyfile 1.3.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = GtkRadiant + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 1.5.0 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doxygen-out + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, +# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en +# (Japanese with English messages), Korean, Korean-en, Norwegian, Polish, Portuguese, +# Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# This tag can be used to specify the encoding used in the generated output. +# The encoding is not always determined by the language that is chosen, +# but also whether or not the output is meant for Windows or non-Windows users. +# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES +# forces the Windows encoding (this is the default for the Windows binary), +# whereas setting the tag to NO uses a Unix-style encoding (the default for +# all platforms other than Windows). + +USE_WINDOWS_ENCODING = YES + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is used +# as the annotated text. Otherwise, the brief description is used as-is. If left +# blank, the following values are used ("$name" is automatically replaced with the +# name of the entity): "The $name class" "The $name widget" "The $name file" +# "is" "provides" "specifies" "contains" "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited +# members of a class in the documentation of that class as if those members were +# ordinary class members. Constructors, destructors and assignment operators of +# the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explicit @brief command for a brief description. + +JAVADOC_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources +# only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = NO + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = radiant/ plugins/ libs/ include/ + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp +# *.h++ *.idl *.odl *.cs *.php *.php3 *.inc + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or directories +# that are symbolic links (a Unix filesystem feature) are excluded from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = */.svn/* + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = radiant/ plugins/ libs/ include/ + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. + +INPUT_FILTER = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = YES + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = YES + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = doxygen-xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse the +# parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. Note that this +# option is superseded by the HAVE_DOT option below. This is only a fallback. It is +# recommended to install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes that +# lay further from the root node will be omitted. Note that setting this option to +# 1 or 2 may greatly reduce the computation time needed for large code bases. Also +# note that a graph may be further truncated if the graph's image dimensions are +# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). +# If 0 is used for the depth value (the default), the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/Doxygen_files/Doxyfile b/Doxygen_files/Doxyfile new file mode 100644 index 00000000..51bd498a --- /dev/null +++ b/Doxygen_files/Doxyfile @@ -0,0 +1,159 @@ +# Doxyfile 1.2.5-20010304 +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = +PROJECT_NUMBER = +OUTPUT_DIRECTORY = +OUTPUT_LANGUAGE = English +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_STATIC = YES +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ALWAYS_DETAILED_SEC = YES +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = $(PWD)/ +INTERNAL_DOCS = YES +CLASS_DIAGRAMS = YES +SOURCE_BROWSER = YES +INLINE_SOURCES = YES +STRIP_CODE_COMMENTS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +VERBATIM_HEADERS = YES +SHOW_INCLUDE_FILES = YES +JAVADOC_AUTOBRIEF = YES +INHERIT_DOCS = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +DISTRIBUTE_GROUP_DOC = NO +TAB_SIZE = 2 +ENABLED_SECTIONS = +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +ALIASES = +MAX_INITIALIZER_LINES = 30 +OPTIMIZE_OUTPUT_FOR_C = NO +SHOW_USED_FILES = YES +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = doxygen.log +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = +FILE_PATTERNS = *.h \ + *.cpp \ + *.c +RECURSIVE = YES +EXCLUDE = +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +IMAGE_PATH = +INPUT_FILTER = +FILTER_SOURCE_FILES = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 4 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = +HTML_HEADER = Doxygen_files/doxygen_gtkradiant_head.html +HTML_FOOTER = Doxygen_files/doxygen_gtkradiant_foot.html +HTML_STYLESHEET = Doxygen_files/doxygen_gtkradiant.css +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = +MAN_EXTENSION = .3 +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +GRAPHICAL_HIERARCHY = YES +DOT_PATH = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO +CGI_NAME = search.cgi +CGI_URL = +DOC_URL = +DOC_ABSPATH = +BIN_ABSPATH = /usr/local/bin/ +EXT_DOC_PATHS = + +#--------------------------------------------------------------------------- +# Include file, at the bottom to over-ride anything I missed. +#--------------------------------------------------------------------------- +@INCLUDE = genConf \ No newline at end of file diff --git a/Doxygen_files/doxy_mainpage.h b/Doxygen_files/doxy_mainpage.h new file mode 100644 index 00000000..34b43d9d --- /dev/null +++ b/Doxygen_files/doxy_mainpage.h @@ -0,0 +1,45 @@ +/* +** Doxygen index.html generation file +** +*/ + +/*! \mainpage +project+ Doxygen Index + + \section intro Introduction + + This documentation was generated from GtkRadiant source code using Doxygen.
+ Generated from source in: +target+ + + \section links Links + General Links
+ Doxygen Homepage
+ GtkRadiant Homepage
+ Zerowing - GtkRadiant Development
+ + Local Links
+ Doxygen Quick Reference (Local)
+ +

+ GtkRadiant FAQ Links
+ GtkRadiant FAQ
+ GtkRadiant FAQ: Open Tasks
+ GtkRadiant FAQ: Compiling instructions
+ GtkRadiant FAQ: Creating/Submitting patches
+ GtkRadiant FAQ: Coding Conventions & Guidelines
+

+ + Misc Links
+ idsoftware.com
+ +

+ + * Note: The content on this page was generated from this file. + It is moved into the path when the doxygen documentation is generated, and removed immediately + afterwards. + +

+ + This page generated: by +user+ on +machine+
+ On +date+ +
+*/ diff --git a/Doxygen_files/doxygen_gtkradiant.css b/Doxygen_files/doxygen_gtkradiant.css new file mode 100644 index 00000000..ad85caff --- /dev/null +++ b/Doxygen_files/doxygen_gtkradiant.css @@ -0,0 +1,34 @@ +body { background-color: black; } +IMG { border-color: #222222; border: 0; } +em { font-size: 11px; font-style: italic; font-weight: normal; color: #888888; } +H1 { text-align: center; font-size: 15px; color: #2222AA; font-family: Geneva, Verdana, Helvetica, Arial, sans-serif; } +H3 { text-align: center; font-size: 18px; color: #2222AA; font-family: Geneva, Verdana, Helvetica, Arial, sans-serif; } +A { text-decoration: none; color: #6666DD; } +A:HOVER { text-decoration: underline; color: #4444FF; } +A:VISITED { text-decoration: none; color: #8888AA; } +A.qindex { text-decoration: none; color: #6666DD; font-size: 11px; } +A.qindex:HOVER { text-decoration: underline; color: #4444FF; font-size: 11px; } +A.qindex:VISITED { text-decoration: none; color: #8888AA; font-size: 11px; } +A.qindexRef { font-size: 11px; } +A.el { text-decoration: none; font-weight: bold; } +A.elRef { font-weight: bold; } +A.code { text-decoration: none; font-weight: normal; color: #6666DD; } +A.code:HOVER { text-decoration: underline; font-weight: normal; color: #4444FF; } +A.code:VISITED { text-decoration: none; font-weight: normal; color: #8888AA; } +A.codeRef { text-decoration: none; font-weight: normal; color: #6666DD; } +A.codeRef:HOVER { text-decoration: underline; font-weight: normal; color: #4444FF; } +A.codeRef:VISITED { text-decoration: none; font-weight: normal; color: #8888AA; } +DL.el { margin-left: 2cm; width: 99%; } +DIV.fragment { background-color: #FFFFFF; width: 99%; } +DIV.ah { background-color: #AAAAAA; width: 99%; margin-bottom: 3; margin-top: 3; } +TD.md { cellpadding: 0; background-color: #DDDDDD; border: 0; width: 99%; color: #222222; } +DIV.groupHeader { margin-left: 16; margin-top: 12; margin-bottom: 6; font-weight: bold; color: #222222; } +DIV.groupText { margin-left: 16; font-style: italic; font-size: smaller; } +FONT.keyword { color: #0080A0; } +FONT.keywordtype { color: #604020; } +FONT.keywordflow { color: #E08000; } +FONT.comment { color: #800000; } +FONT.comment { color: #009900; text-decoration: italic; } +FONT.preprocessor { color: #806020; } +FONT.stringliteral{ color: #002080; } +FONT.charliteral { color: #008080; } diff --git a/Doxygen_files/doxygen_gtkradiant_foot.html b/Doxygen_files/doxygen_gtkradiant_foot.html new file mode 100644 index 00000000..affcb3c3 --- /dev/null +++ b/Doxygen_files/doxygen_gtkradiant_foot.html @@ -0,0 +1,49 @@ + + + + + +

+
+ + + + + +
+ Documentation generated by : Doxygen $doxygenversion + + + ttimo@idsoftware.com + +
+
+ +
+
+ +   + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Doxygen_files/doxygen_gtkradiant_head.html b/Doxygen_files/doxygen_gtkradiant_head.html new file mode 100644 index 00000000..cda29303 --- /dev/null +++ b/Doxygen_files/doxygen_gtkradiant_head.html @@ -0,0 +1,38 @@ + + + + $projectname $projectnumber Doxygen Documentation + + + + + +
+
+ + + + + + +
+ +
idsoftware
+
+ + + + + + + + + + + + +
+ + +
 
+

+
+ + + + + + + + +
+ \ No newline at end of file diff --git a/Doxygen_files/doxygen_index.html b/Doxygen_files/doxygen_index.html new file mode 100644 index 00000000..a9005b7f --- /dev/null +++ b/Doxygen_files/doxygen_index.html @@ -0,0 +1,7 @@ + + + + + + Redirecting to Doxygen index + diff --git a/Doxygen_files/doxygen_reference_foot.html b/Doxygen_files/doxygen_reference_foot.html new file mode 100644 index 00000000..1bc7d704 --- /dev/null +++ b/Doxygen_files/doxygen_reference_foot.html @@ -0,0 +1,46 @@ + +

+
+ + + + + +
+ + Doxygen is: Copyright © 1997-2001 by Dimitri van Heesch. + + + GtkRadiant Doxygen Maintainer: Gef +
+
+ +
+
+
 
+ + + + + +
+ +
+
+
+ +
+ + + diff --git a/Doxygen_files/doxygen_reference_head.html b/Doxygen_files/doxygen_reference_head.html new file mode 100644 index 00000000..4f12e035 --- /dev/null +++ b/Doxygen_files/doxygen_reference_head.html @@ -0,0 +1,38 @@ + + + + GtkRadiant - Doxygen Quick Reference + + + + + +
+ +
+ + + + + + +
+ + +
idsoftware
+
+ + + + + + + + + + + + +
+ + +
&nsbp;
+

+ \ No newline at end of file diff --git a/Doxygen_files/example/annotated.html b/Doxygen_files/example/annotated.html new file mode 100644 index 00000000..c827a83c --- /dev/null +++ b/Doxygen_files/example/annotated.html @@ -0,0 +1,103 @@ + + + + IEPairsClassDocumentationExample Doxygen Documentation + + + + + +
+ + + + + + + +
+ + +
idsoftware +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + +
 
+

+ +
+
+ + + + + +
+Main Page   Alphabetical List   Compound List   File List   Compound Members   Related Pages  
+

IEPairsClassDocumentationExample Compound List

Here are the classes, structs, unions and interfaces with brief descriptions:
    +
  • IEpair (Virtual class to allow plugin operations on entity pairs) +
+ + +
+
+ +

+
+ + + + + +
+ Documentation generated by : Doxygen 1.2.8.1 on 11 Aug 2001 + + + ttimo@idsoftware.com + +
+
+ +
+
+
 
+ + + + + +
+ +
+
+ +
+ + + diff --git a/Doxygen_files/example/classIEpair-members.html b/Doxygen_files/example/classIEpair-members.html new file mode 100644 index 00000000..b58775ce --- /dev/null +++ b/Doxygen_files/example/classIEpair-members.html @@ -0,0 +1,110 @@ + + + + IEPairsClassDocumentationExample Doxygen Documentation + + + + + +
+ + + + + + + +
+ + +
idsoftware +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + +
 
+

+ +
+
+ + + + + +
+Main Page   Alphabetical List   Compound List   File List   Compound Members   Related Pages  
+

IEpair Member List

This is the complete list of members for IEpair, including all inherited members. + + +
+
+ +

+
+ + + + + +
+ Documentation generated by : Doxygen 1.2.8.1 on 11 Aug 2001 + + + ttimo@idsoftware.com + +
+
+ +
+
+
 
+ + + + + +
+ + + + + + + +
+ + + diff --git a/Doxygen_files/example/classIEpair.html b/Doxygen_files/example/classIEpair.html new file mode 100644 index 00000000..a64a9e94 --- /dev/null +++ b/Doxygen_files/example/classIEpair.html @@ -0,0 +1,414 @@ + + + + IEPairsClassDocumentationExample Doxygen Documentation + + + + + +
+ + + + + + + +
+ + +
idsoftware +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + +
 
+

+ +
+
+ + + + + +
+Main Page   Alphabetical List   Compound List   File List   Compound Members   Related Pages  
+

IEpair Class Reference

Virtual class to allow plugin operations on entity pairs. +More... +

+List of all members. + + + + + + + + + + + + + + + + + +

Public Methods

virtual void IncRef ()=0
 Increment the number of references to this object. More...

virtual void DecRef ()=0
 Decrement the reference count. More...

virtual void GetVectorForKey (char *key, vec3_t vec)=0
 Get a vector from a key. More...

virtual float FloatForKey (char *key)=0
 Get a float from a key. More...

virtual char* ValueForKey (char *key)=0
 Get a string (char *) from a key. More...

virtual void SetKeyValue (char *key, char *value)=0
 Set a key value to char *value. More...

virtual void GetEntityOrigin (vec3_t vec)=0
 Get a vec3_t for the entities origin. More...

virtual void CalculateRotatedBounds (vec3_t mins, vec3_t maxs)=0
 Compute the rotated bounds of the BBox based on "angle" and "angles" keys. More...

+


Detailed Description

+Virtual class to allow plugin operations on entity pairs. +

+ +

+

+Todo:
+Write more complete documentation for this class so that it's use is clear
+

+An interface to entity keys and key pairs that allows plugins to; read and write entity keys and key values, get a key value as a vec3_t +

+ +

+Definition at line 10 of file iepairs.h.


Member Function Documentation

+

+ + + + +
+ + + + + + + + + + +
+void IEpair::CalculateRotatedBounds ( + +vec3_t mins, +
+vec3_t maxs ) [pure virtual] +
+
+ + + + + +
+   + + +

+Compute the rotated bounds of the BBox based on "angle" and "angles" keys. +

+

+

+ + + + +
+ + + + + + +
+void IEpair::DecRef ( + +) [pure virtual] +
+
+ + + + + +
+   + + +

+Decrement the reference count. +

+

+

+ + + + +
+ + + + + + +
+float IEpair::FloatForKey ( + +char * key ) [pure virtual] +
+
+ + + + + +
+   + + +

+Get a float from a key. +

+

+

+ + + + +
+ + + + + + +
+void IEpair::GetEntityOrigin ( + +vec3_t vec ) [pure virtual] +
+
+ + + + + +
+   + + +

+Get a vec3_t for the entities origin. +

+

+

+ + + + +
+ + + + + + + + + + +
+void IEpair::GetVectorForKey ( + +char * key, +
+vec3_t vec ) [pure virtual] +
+
+ + + + + +
+   + + +

+Get a vector from a key. +

+

+

+ + + + +
+ + + + + + +
+void IEpair::IncRef ( + +) [pure virtual] +
+
+ + + + + +
+   + + +

+Increment the number of references to this object. +

+

+

+ + + + +
+ + + + + + + + + + +
+void IEpair::SetKeyValue ( + +char * key, +
+char * value ) [pure virtual] +
+
+ + + + + +
+   + + +

+Set a key value to char *value. +

+

+Parameters:
+ + + +
key +The (char *) containing the keyname
value +The (char *) to set the key value to
+
+

+ + + + +
+ + + + + + +
+char * IEpair::ValueForKey ( + +char * key ) [pure virtual] +
+
+ + + + + +
+   + + +

+Get a string (char *) from a key. +

+

+


The documentation for this class was generated from the following file: + + +
+
+ +

+
+ + + + + +
+ Documentation generated by : Doxygen 1.2.8.1 on 11 Aug 2001 + + + ttimo@idsoftware.com + +
+
+ +
+
+
 
+ + + + + +
+ + + + + + + +
+ + + diff --git a/Doxygen_files/example/classes.html b/Doxygen_files/example/classes.html new file mode 100644 index 00000000..b79308f9 --- /dev/null +++ b/Doxygen_files/example/classes.html @@ -0,0 +1,103 @@ + + + + IEPairsClassDocumentationExample Doxygen Documentation + + + + + +
+ + + + + + + +
+ + +
idsoftware +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + +
 
+

+ +
+
+ + + + + +
+Main Page   Alphabetical List   Compound List   File List   Compound Members   Related Pages  
+

IEPairsClassDocumentationExample Compound Index

+ +
  I  
IEpair   
+ + +
+
+ +

+
+ + + + + +
+ Documentation generated by : Doxygen 1.2.8.1 on 11 Aug 2001 + + + ttimo@idsoftware.com + +
+
+ +
+
+
 
+ + + + + +
+ + + + + + + +
+ + + diff --git a/Doxygen_files/example/doxygen.gif b/Doxygen_files/example/doxygen.gif new file mode 100644 index 0000000000000000000000000000000000000000..192c83ce05078f6475c1e81290fdadded6d25467 GIT binary patch literal 2378 zcmV-Q3AOe|Nk%w1VQv650OtSz|Nr8jARx9SCIA26|Nq?I-^TyU%-^!I{`}Lwzp4NK z!AJl$=%to{Pn!Dk|EsHq~G7iKRhfS z91pmqSoh+E_1twJ9~uAt+rPi6_u+;4>Y>o9Ci2iy|NPV1&W%7nHQd#u_u_{7@UK2Q zC_q3tKtMO!(wV!kc>C|K*1%`_>!ID+u=e4G1Ox;A{M1}gCc3O{-`~UU#x(HCLb<7A z|Nh(_8x0>E5g#2C_1$(`Q6}BmuleerKRqwCoITdTXWY`6`s<+8yjR)DfImJmJ~}1- z_{iGMj36K!_vVw=#&tb69{caE{rSkgy`H408y_AP9~%w7zokGx zIN#j4{Pn%`)@AzYq1efR@XJEq-oPFh2KL-_zrU#1#&y=fX7}cj@ybHdvO2!LoQBU|vkZw|@5Fh4$iw4-E!FJ}bVvm)y~rJv0@)w~d&FKADM1?!`1GB^lw+ zgkWG&J~<&iI3ZwPPeepJ?#4A98VdH^cH7dJwVXcO(wK#985kE3(5)_$d?@b5G(0pF z-Py0>vxI<15b0p7VOf*ylGM zmLI?j=JM6&Sb2uIr(S>pw!{Zp1AH*R0}wXI)(37e!QcQ)L?|H>^(@HXT5K_4NXSa)9_?J1IWMk&ArPV+x2A*5hJ`3_9pmOL-8$fD8EQ1jLAnDWmYz6G0N0&Y^=Ub09#f zrBqo_fe00ta6$|mArZp~(TZ?FJdDM+T!|MGV&>46PA|-ed&|C`>Sc3M6#o z&JGh$K*0|6P@wS+Bp~r92`Y$8!7BA&v4hDd&_RJD9dks2B&SS}G0m%B!BHnZQ$R~7 z#mH}6d}F_8m!*E21V3xh%3P) zk_-Am)RV*O8XPjk6k;3?Jnt<<0>J_cAj1MUzyU7^5CZ?ZLp_!Nzy zXPBNRY=8&;8+ZW^I=};Gbl`_HNJc%XaD*e$paVxZf&-@Lhbr{pi7!|KH@Z=V7rfvI z8+bxJq%g)c2$2VC_zNnY&<9F9F^Dtd$P=8{0UG=;h-IipACCA8LP$Xhgm}RQI`D!# zI;0+jZ~zRZC=LgVVu}tRf(zI6VIArP_%gcG0&hC%iG@i_P4+du5gD-+~OMdxX4Yea+k~8<~sMeQwazFJ2OgsC;$Ke literal 0 HcmV?d00001 diff --git a/Doxygen_files/example/doxygen_gtkradiant.css b/Doxygen_files/example/doxygen_gtkradiant.css new file mode 100644 index 00000000..30bf2075 --- /dev/null +++ b/Doxygen_files/example/doxygen_gtkradiant.css @@ -0,0 +1,35 @@ +body { background-color: black; } +IMG { border-color: #222222; border: 1; } +em { font-size: 11px; font-style: italic; font-weight: normal; color: #888888; } +H1 { text-align: center; font-size: 15px; color: #2222AA; font-family: Geneva, Verdana, Helvetica, Arial, sans-serif; } +H3 { text-align: center; font-size: 18px; color: #2222AA; font-family: Geneva, Verdana, Helvetica, Arial, sans-serif; } +A { text-decoration: none; color: #6666DD; } +A:HOVER { text-decoration: underline; color: #4444FF; } +A:VISITED { text-decoration: none; color: #8888AA; } +A.qindex { text-decoration: none; color: #6666DD; font-size: 11px; } +A.qindex:HOVER { text-decoration: underline; color: #4444FF; font-size: 11px; } +A.qindex:VISITED { text-decoration: none; color: #8888AA; font-size: 11px; } +A.qindexRef { font-size: 11px; } +A.el { text-decoration: none; font-weight: bold; } +A.elRef { font-weight: bold; } +A.code { text-decoration: none; font-weight: normal; color: #6666DD; } +A.code:HOVER { text-decoration: underline; font-weight: normal; color: #4444FF; } +A.code:VISITED { text-decoration: none; font-weight: normal; color: #8888AA; } +A.codeRef { text-decoration: none; font-weight: normal; color: #6666DD; } +A.codeRef:HOVER { text-decoration: underline; font-weight: normal; color: #4444FF; } +A.codeRef:VISITED { text-decoration: none; font-weight: normal; color: #8888AA; } +DL.el { margin-left: 2cm; width: 99%; } +DIV.fragment { background-color: #FFFFFF; width: 99%; } +DIV.ah { background-color: #AAAAAA; width: 99%; margin-bottom: 3; margin-top: 3; } +TD.md { cellpadding: 2; background-color: #DDDDDD; border: 1; width: 99%; color: #222222; } +DIV.groupHeader { margin-left: 16; margin-top: 12; margin-bottom: 6; font-weight: bold; color: #222222; } +DIV.groupText { margin-left: 16; font-style: italic; font-size: smaller; } +FONT.keyword { color: #0080A0; } +FONT.keywordtype { color: #604020; } +FONT.keywordflow { color: #E08000; } +FONT.comment { color: #800000; } +FONT.comment { color: #009900; text-decoration: italic; } +FONT.preprocessor { color: #806020; } +FONT.stringliteral{ color: #002080; } +FONT.charliteral { color: #008080; } + diff --git a/Doxygen_files/example/files.html b/Doxygen_files/example/files.html new file mode 100644 index 00000000..9911650f --- /dev/null +++ b/Doxygen_files/example/files.html @@ -0,0 +1,102 @@ + + + + IEPairsClassDocumentationExample Doxygen Documentation + + + + + +
+ + + + + + + +
+ + +
idsoftware +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + +
 
+

+ +
+
+ + + + + +
+Main Page   Alphabetical List   Compound List   File List   Compound Members   Related Pages  
+

IEPairsClassDocumentationExample File List

Here is a list of all files with brief descriptions: + + +
+
+ +

+
+ + + + + +
+ Documentation generated by : Doxygen 1.2.8.1 on 11 Aug 2001 + + + ttimo@idsoftware.com + +
+
+ +
+
+
 
+ + + + + +
+ + + + + + + +
+ + + diff --git a/Doxygen_files/example/functions.html b/Doxygen_files/example/functions.html new file mode 100644 index 00000000..12914931 --- /dev/null +++ b/Doxygen_files/example/functions.html @@ -0,0 +1,110 @@ + + + + IEPairsClassDocumentationExample Doxygen Documentation + + + + + +
+ + + + + + + +
+ + +
idsoftware +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + +
 
+

+ +
+
+ + + + + +
+Main Page   Alphabetical List   Compound List   File List   Compound Members   Related Pages  
+

IEPairsClassDocumentationExample Compound Members

Here is a list of all class members with links to the class documentation for each member: + + +
+
+ +

+
+ + + + + +
+ Documentation generated by : Doxygen 1.2.8.1 on 11 Aug 2001 + + + ttimo@idsoftware.com + +
+
+ +
+
+
 
+ + + + + +
+ + + + + + + +
+ + + diff --git a/Doxygen_files/example/graph_legend.dot b/Doxygen_files/example/graph_legend.dot new file mode 100644 index 00000000..3d8a101e --- /dev/null +++ b/Doxygen_files/example/graph_legend.dot @@ -0,0 +1,16 @@ +digraph inheritance +{ + Node7 [shape="box",label="Inherited",fontsize=10,height=0.2,width=0.4,fontname="doxfont",color="black",style="filled" fontcolor="white"]; + Node8 -> Node7 [dir=back,color="midnightblue",fontsize=10,style="solid",fontname="doxfont"]; + Node8 [shape="box",label="PublicBase",fontsize=10,height=0.2,width=0.4,fontname="doxfont",color="black",URL="$class_publicbase.html"]; + Node9 -> Node8 [dir=back,color="midnightblue",fontsize=10,style="solid",fontname="doxfont"]; + Node9 [shape="box",label="Truncated",fontsize=10,height=0.2,width=0.4,fontname="doxfont",color="red",URL="$class_truncated.html"]; + Node11 -> Node7 [dir=back,color="darkgreen",fontsize=10,style="solid",fontname="doxfont"]; + Node11 [shape="box",label="ProtectedBase",fontsize=10,height=0.2,width=0.4,fontname="doxfont",color="black",URL="$class_protectedbase.html"]; + Node12 -> Node7 [dir=back,color="firebrick4",fontsize=10,style="solid",fontname="doxfont"]; + Node12 [shape="box",label="PrivateBase",fontsize=10,height=0.2,width=0.4,fontname="doxfont",color="black",URL="$class_privatebase.html"]; + Node13 -> Node7 [dir=back,color="midnightblue",fontsize=10,style="solid",fontname="doxfont"]; + Node13 [shape="box",label="Undocumented",fontsize=10,height=0.2,width=0.4,fontname="doxfont",color="grey75"]; + Node14 -> Node7 [dir=back,color="darkorchid3",fontsize=10,style="dashed",label="m_usedClass",fontname="doxfont"]; + Node14 [shape="box",label="Used",fontsize=10,height=0.2,width=0.4,fontname="doxfont",color="black",URL="$class_used.html"]; +} diff --git a/Doxygen_files/example/graph_legend.gif b/Doxygen_files/example/graph_legend.gif new file mode 100644 index 0000000000000000000000000000000000000000..f4f1a0053c470cf4d1cf5627049737676330ebac GIT binary patch literal 9344 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 n6;IE3c+clI)@^+JXlM7|HZ?HE49AVXpZsB3W8 + + + IEPairsClassDocumentationExample Doxygen Documentation + + + + + +
+ + + + + + + +
+ + +
idsoftware +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + +
 
+

+ +
+
+ + + + + +
+Main Page   Alphabetical List   Compound List   File List   Compound Members   Related Pages  
+

Graph Legend

This page explains how to interpret the graphs that are generated by doxygen. +

+ Consider the following example:

/*! Invisible class because of truncation */
+class Invisible { };
+
+/*! Truncated class, inheritance relation is hidden */
+class Truncated : public Invisible { };
+
+/* Class not documented with doxygen comments */
+class Undocumented { };
+
+/*! Class that is inherited using public inheritance */
+class PublicBase : public Truncated { };
+
+/*! Class that is inherited using protected inheritance */
+class ProtectedBase { };
+
+/*! Class that is inherited using private inheritance */
+class PrivateBase { };
+
+/*! Class that is used by the Inherited class */
+class Used { };
+
+/*! Super class that inherits a number of other classes */
+class Inherited : public PublicBase,
+                  protected ProtectedBase,
+                  private PrivateBase,
+                  public Undocumented
+{
+  private:
+    Used *m_usedClass;
+};
If the MAX_DOT_GRAPH_HEIGHT tag in the configuration file is set to 200 this will result in the following graph: +

+

+
+ +

+ The boxes in the above graph have the following meaning:

    +
  • A filled black box represents the struct or class for which the graph is generated.
  • A box with a black border denotes a documented struct or class.
  • A box with a grey border denotes an undocumented struct or class.
  • A box with a red border denotes a documented struct or class for which not all inheritance/containment relations are shown. A graph is truncated if it does not fit within the specified boundaries.
+ The arrows have the following meaning:
    +
  • A dark blue arrow is used to visualize a public inheritance relation between two classes.
  • A dark green arrow is used for protected inheritance.
  • A dark red arrow is used for private inheritance.
  • A purple dashed arrow is used if a class is contained or used by another class. The arrow is labeled with the variable(s) through which the pointed class or struct is accessible.
+ + +
+
+ +

+
+ + + + + +
+ Documentation generated by : Doxygen 1.2.8.1 on 11 Aug 2001 + + + ttimo@idsoftware.com + +
+
+ +
+
+
 
+ + + + + +
+ + + + + + + +
+ + + diff --git a/Doxygen_files/example/index.html b/Doxygen_files/example/index.html new file mode 100644 index 00000000..b8466f27 --- /dev/null +++ b/Doxygen_files/example/index.html @@ -0,0 +1,102 @@ + + + + IEPairs Class Example Doxygen Documentation + + + + + +
+ + + + + + + +
+ + +
idsoftware +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + +
 
+

+ +
+
+ + + + + +
+Main Page   Alphabetical List   Compound List   File List   Compound Members   Related Pages  
+

IEPairs Class Example Documentation

+

+ + +

+
+ +

+
+ + + + + +
+ Documentation generated by : Doxygen 1.2.8.1 on 11 Aug 2001 + + + ttimo@idsoftware.com + +
+
+ +
+
+
 
+ + + + + +
+ + + + + + + +
+ + + diff --git a/Doxygen_files/example/pages.html b/Doxygen_files/example/pages.html new file mode 100644 index 00000000..0f446d98 --- /dev/null +++ b/Doxygen_files/example/pages.html @@ -0,0 +1,104 @@ + + + + IEPairsClassDocumentationExample Doxygen Documentation + + + + + +
+ + + + + + + +
+ + +
idsoftware +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + +
 
+

+ +
+
+ + + + + +
+Main Page   Alphabetical List   Compound List   File List   Compound Members   Related Pages  
+

IEPairsClassDocumentationExample Related Pages

Here is a list of all related documentation pages: + + +
+
+ +

+
+ + + + + +
+ Documentation generated by : Doxygen 1.2.8.1 on 11 Aug 2001 + + + ttimo@idsoftware.com + +
+
+ +
+
+
 
+ + + + + +
+ + + + + + + +
+ + + diff --git a/Doxygen_files/example/test_8c-source.html b/Doxygen_files/example/test_8c-source.html new file mode 100644 index 00000000..c36f5a32 --- /dev/null +++ b/Doxygen_files/example/test_8c-source.html @@ -0,0 +1,140 @@ + + + + IEPairsClassDocumentationExample Doxygen Documentation + + + + + +
+ + + + + + + +
+ + +
idsoftware +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + +
 
+

+ +
+
+ + + + + +
+Main Page   Alphabetical List   Compound List   File List   Compound Members   Related Pages  
+

iepairs.h

Go to the documentation of this file.
00001 //! Virtual class to allow plugin operations on entity pairs
+00002 /*!
+00003   \todo Write more complete documentation for this class so that it's use
+00004   is clear
+00005                         
+00006   An interface to entity keys and key pairs that allows plugins to;
+00007   read and write entity keys and key values, get a key value as a
+00008   vec3_t
+00009 */
+00010 class IEpair
+00011 {
+00012   public:
+00013     //! Increment the number of references to this object
+00014     virtual void IncRef () = 0;
+00015                                 
+00016     //! Decrement the reference count
+00017     virtual void DecRef () = 0;
+00018                                 
+00019     //! Get a vector from a key
+00020     virtual void GetVectorForKey( char* key, vec3_t vec ) = 0;
+00021                                 
+00022     //! Get a float from a key
+00023     virtual float FloatForKey( char *key ) = 0;
+00024                                 
+00025     //! Get a string (char *) from a key
+00026     virtual char* ValueForKey( char *key ) = 0;
+00027                                 
+00028     //! Set a key value to char *value
+00029     /*!
+00030       \param key The (char *) containing the keyname
+00031       \param value The (char *) to set the key value to
+00032     */
+00033     virtual void SetKeyValue( char *key, char *value ) = 0;
+00034                                 
+00035     //! Get a vec3_t for the entities origin
+00036     virtual void GetEntityOrigin( vec3_t vec ) = 0;
+00037                                 
+00038     //! Compute the rotated bounds of the BBox based on "angle" and "angles" keys
+00039     virtual void CalculateRotatedBounds( vec3_t mins, vec3_t maxs ) = 0;
+00040 };
+
+ +
+
+ +

+
+ + + + + +
+ Documentation generated by : Doxygen 1.2.8.1 on 11 Aug 2001 + + + ttimo@idsoftware.com + +
+
+ +
+
+
 
+ + + + + +
+ + + + + + + +
+ + + diff --git a/Doxygen_files/example/test_8c.html b/Doxygen_files/example/test_8c.html new file mode 100644 index 00000000..206bda67 --- /dev/null +++ b/Doxygen_files/example/test_8c.html @@ -0,0 +1,107 @@ + + + + IEPairsClassDocumentationExample Doxygen Documentation + + + + + +
+ + + + + + + +
+ + +
idsoftware +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + +
 
+

+ +
+
+ + + + + +
+Main Page   Alphabetical List   Compound List   File List   Compound Members   Related Pages  
+

iepairs.h File Reference

+

+Go to the source code of this file. + + + +

Compounds

class  IEpair
 Virtual class to allow plugin operations on entity pairs. More...

+ + +

+
+ +

+
+ + + + + +
+ Documentation generated by : Doxygen 1.2.8.1 on 11 Aug 2001 + + + ttimo@idsoftware.com + +
+
+ +
+
+
 
+ + + + + +
+ + + + + + + +
+ + + diff --git a/Doxygen_files/example/todo.html b/Doxygen_files/example/todo.html new file mode 100644 index 00000000..769aac56 --- /dev/null +++ b/Doxygen_files/example/todo.html @@ -0,0 +1,105 @@ + + + + IEPairsClassDocumentationExample Doxygen Documentation + + + + + +
+ + + + + + + +
+ + +
idsoftware +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + +
 
+

+ +
+
+ + + + + +
+Main Page   Alphabetical List   Compound List   File List   Compound Members   Related Pages  
+

Todo List

+ +
+
Class IEpair
Write more complete documentation for this class so that it's use is clear +
+ + +
+
+ +

+
+ + + + + +
+ Documentation generated by : Doxygen 1.2.8.1 on 11 Aug 2001 + + + ttimo@idsoftware.com + +
+
+ +
+
+
 
+ + + + + +
+ + + + + + + +
+ + + diff --git a/Doxygen_files/genDoxyfile b/Doxygen_files/genDoxyfile new file mode 100644 index 00000000..330bb570 --- /dev/null +++ b/Doxygen_files/genDoxyfile @@ -0,0 +1,159 @@ +# Doxyfile 1.2.5-20010304 +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = +PROJECT_NUMBER = +OUTPUT_DIRECTORY = +OUTPUT_LANGUAGE = English +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_STATIC = YES +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ALWAYS_DETAILED_SEC = YES +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = $(PWD)/ +INTERNAL_DOCS = YES +CLASS_DIAGRAMS = YES +SOURCE_BROWSER = YES +INLINE_SOURCES = YES +STRIP_CODE_COMMENTS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +VERBATIM_HEADERS = YES +SHOW_INCLUDE_FILES = YES +JAVADOC_AUTOBRIEF = YES +INHERIT_DOCS = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +DISTRIBUTE_GROUP_DOC = NO +TAB_SIZE = 2 +ENABLED_SECTIONS = +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +ALIASES = +MAX_INITIALIZER_LINES = 30 +OPTIMIZE_OUTPUT_FOR_C = NO +SHOW_USED_FILES = YES +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = doxygen.log +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = +FILE_PATTERNS = *.h \ + *.cpp \ + *.c +RECURSIVE = YES +EXCLUDE = +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +IMAGE_PATH = +INPUT_FILTER = +FILTER_SOURCE_FILES = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 4 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = +HTML_HEADER = Doxygen_files/doxygen_gtkradiant_head.html +HTML_FOOTER = Doxygen_files/doxygen_gtkradiant_foot.html +HTML_STYLESHEET = Doxygen_files/doxygen_gtkradiant.css +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = +MAN_EXTENSION = .3 +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +GRAPHICAL_HIERARCHY = YES +DOT_PATH = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO +CGI_NAME = search.cgi +CGI_URL = +DOC_URL = +DOC_ABSPATH = +BIN_ABSPATH = /usr/local/bin/ +EXT_DOC_PATHS = + +#--------------------------------------------------------------------------- +# Include file, at the bottom to over-ride anything I missed. +#--------------------------------------------------------------------------- +@INCLUDE = ./Doxygen_files/genConf diff --git a/Doxygen_files/gendoxfunctions b/Doxygen_files/gendoxfunctions new file mode 100644 index 00000000..b8837be1 --- /dev/null +++ b/Doxygen_files/gendoxfunctions @@ -0,0 +1,421 @@ +#!/bin/bash +# Functions for the gendox script +# +# Gef, Aug 2001 + +#------------------------------------------------------------------------ +# Set the doxygen output language from the system locale +#------------------------------------------------------------------------ +get_language() { + if [ -n "$LANG" ] ; then + local LANG=`locale | grep LANG | cut -d= -f2`; + fi + case "$LANG" in + czech) + OUPUTLANGUAGE="Czech"; + ;; + german) + OUPUTLANGUAGE="German"; + ;; + spanish) + OUPUTLANGUAGE="Spanish"; + ;; + finnish) + OUPUTLANGUAGE="Finnish"; + ;; + french) + OUPUTLANGUAGE="French"; + ;; + italian) + OUPUTLANGUAGE="Italian"; + ;; + japanese*) + OUPUTLANGUAGE="Japanese"; + ;; + dutch) + OUPUTLANGUAGE="Dutch"; + ;; + swedish) + OUPUTLANGUAGE="Swedish"; + ;; + *) + OUPUTLANGUAGE="English"; + ;; + esac +} + +#------------------------------------------------------------------------ +# Output usage info & output help +#------------------------------------------------------------------------ +output_usage() { + echo -e "Usage: $0 [] [-o ]"; + return; +} + +output_help() { + output_usage; + echo -e "\nOptions:"; + echo -e " []"; + echo -e " This is an optional parameter that specifies the directory, or multiple"; + echo -e " directories from which to generate the documentation."; + echo -e ""; + echo -e " [-o ]"; + echo -e " An optional parameter that specifies the output directory in which"; + echo -e " to save the generated documentation."; + echo -e ""; + echo -e " -q or --quiet"; + echo -e " Prevents the output of status information" + echo -e "" + echo -e " --help, or -h"; + echo -e " Displays this information"; + echo -e "" + echo -e " -q or --quiet"; + echo -e " Prevents the output of status information" + echo -e "" + echo -e " -k or --kill"; + echo -e " kills running doxygen pids." + echo -e "" + echo -e "* Further information on using this script, can be found in README.doxygen"; + echo -e "* in the current directory."; + +} + +#------------------------------------------------------------------------ +# Set the target to what was passed on the command line +#------------------------------------------------------------------------ +parse_commandline() { + # todo: + # need to add the ability to check for an auto gen'd version + # used for automatically generating new documentation for each commit + # to the cvs server + + # funky bash shell array + declare -a OPTLIST[$#]; + + if [ $OPTCOUNT == 0 ] ; then + # No options on the command line so set the target list to the core + TARGETCOUNT=0; + OUTPUTDIR="../$(basename `pwd`)-doxygen"; + else + # put all the command line options into an array + for f in $COMLINE ; do + OPTLIST[$COUNTER]="$f"; + let COUNTER++; + done + + for (( COUNTER=0 ; $COUNTER < $OPTCOUNT; $[COUNTER++] )) ; do + if [ "${OPTLIST[$COUNTER]}" == "--help" ] ; then + # output usage information + output_help; + RETVAL=1; + return; + elif [ "${OPTLIST[$COUNTER]}" == "-h" ] ; then + # output usage information + output_help; + RETVAL=1; + return; + fi + + case ${OPTLIST[$COUNTER]} in + -q) + QUIETMODE=1; + ;; + --quiet) + QUIETMODE=1; + ;; + -k) + KILLON=1; + ;; + --kill) + KILLON=1; + ;; + -o) + # look for the -o switch, and get the next command line option as the output dir + if [ -z ${OPTLIST[$COUNTER + 1]} ] ; then + [ $QUIETMODE -gt 0 ] || echo -e " ** Output switch used, but no output dir passed..."; + [ $QUIETMODE -gt 0 ] || echo -e " ** Setting default output dir."; + else + let COUNTER++; + OUTPUTDIR=${OPTLIST[$COUNTER]}; + fi + break; + ;; + **) + # If the command line option is anything other that -o then assume it's a target + # Check to make sure the target exists first... + if [ -d ${OPTLIST[$COUNTER]} ] ; then + TARGETLIST[$COUNTER]=${OPTLIST[$COUNTER]}; + else + output_usage; + echo -e " ** Error: Non-existent directory specified as a target.\nExiting."; + RETVAL=1; + return; + fi + let TARGETCOUNT++; + ;; + esac + done + + fi # if [ $OPTCOUNT == 0 ] ; + + if [ $TARGETCOUNT == 0 ] ; then + TARGETCOUNT=4; + TARGETLIST[0]="include"; + TARGETLIST[1]="libs"; + TARGETLIST[2]="radiant"; + TARGETLIST[3]="plugins"; + # Gef: outputdir for default core when no targets are passed on the command line + # TTimo problem still there, if -o used on command line, don't override + if [ -z $OUTPUTDIR ] ; then + OUTPUTDIR="../$(basename `pwd`)-doxygen"; + fi + fi + + # Add trailing slash's to the lines that need them + TARGETSTRING=`echo ${TARGETLIST[*]} | sed -e 's/" "/", "/'` + [ $QUIETMODE -gt 0 ] || echo -ne " -> Set Input to: "; + for (( COUNTER=0; COUNTER < $TARGETCOUNT ; $[COUNTER++] )) ; do + if [ $COUNTER == $[TARGETCOUNT - 1] ] ; then + [ $QUIETMODE -gt 0 ] || echo -ne "${TARGETLIST[$COUNTER]}\n"; + TARGETLIST[$COUNTER]="${TARGETLIST[$COUNTER]}"; + else + [ $QUIETMODE -gt 0 ] || echo -ne "${TARGETLIST[$COUNTER]}, "; + TARGETLIST[$COUNTER]="${TARGETLIST[$COUNTER]} \\"; + fi + done + [ $QUIETMODE -gt 0 ] || echo -e " -> Set Output Dir to: $OUTPUTDIR"; + return; +} + +#------------------------------------------------------------------------ +# Try to extract the version number +# todo: find a better way to determine the version +#------------------------------------------------------------------------ +get_version() { + VERSION=`grep PROJECT_NUMBER $DOXYCONFIG | grep -v \# | cut -d= -f2`; + if [ -z $VERSION ] ; then + if [ -f "./include/version.default" ] ; then # checks that we are in the right dir + VERSION=`cat ./include/version.default`; + else + VERSION="(Unknown)"; + fi + fi + return; +} + +#------------------------------------------------------------------------ +# Create a projectname from the tree name +#------------------------------------------------------------------------ +get_projectname() { + PROJECTNAME=`grep PROJECT_NAME $DOXYCONFIG | grep -v \# | cut -d= -f2`; + if [ -z $PROJECTNAME ] ; then + # PROJECTNAME=`echo $TARGET | sed -e s/[^A-Za-z0-9]/!/ | cut -d! -f1`; + PROJECTNAME="$(basename `pwd`)"; + fi + return; +} + +#------------------------------------------------------------------------ +# function to determine the path to the perl executable +#------------------------------------------------------------------------ +get_perlpath() { + if [ -f "$DOXYCONFIG" ] ; then + PERLPATH=`grep PERL_PATH $DOXYCONFIG | grep = | cut -d= -f2` + fi + + if [ 'basename $PERLPATH &2>/dev/null' != "perl" ] ; then + PERLPATH=`which perl 2>/dev/null | sed -e 's/perl//'`; + elif [ 'basename $PERLPATH &2>/dev/null' != "perl" ] ; then + PERLPATH=""; + fi + return; +} + +#------------------------------------------------------------------------ +# Function to determine the path to the dot executable +#------------------------------------------------------------------------ +get_dotpath() { + if [ -f "$DOXYCONFIG" ] ; then + DOTPATH=`grep DOT_PATH $DOXYCONFIG | grep = | cut -d= -f2` + fi + + if [ -z $DOTPATH ] || [ `basename $DOTPATH 2>/dev/null` != "dot" ] ; then + DOTPATH=`which dot 2>/dev/null`; + fi + + if [ -z $DOTPATH ] || [ `basename $DOTPATH 2>/dev/null` != "dot" ] ; then + DOTPATH=""; + HAVEDOT="No"; + echo -e "** Warning: dot not found."; + [ $QUIETMODE -gt 0 ] || echo -e "** dot is part of the GraphVis package and is used to generate"; + [ $QUIETMODE -gt 0 ] || echo -e "** dependancy/inheritance/include (etc) diagrams."; + [ $QUIETMODE -gt 0 ] || echo -e "** It's suggested that you install the GraphVis package for those"; + [ $QUIETMODE -gt 0 ] || echo -e "** features."; + [ $QUIETMODE -gt 0 ] || echo -e "** GraphVis can be downloaded from www.graphvis.org"; + else + HAVEDOT="Yes"; + DOTPATH=`echo $DOTPATH | sed -e 's/dot//'`; + fi + + return; +} + +#------------------------------------------------------------------------ +# Function to move stuff around +#------------------------------------------------------------------------ +# eg: move the images into the output directory & the reference doc into the +# html directory. +# called after doxygen has finished generating documentation +move_stuff() { + [ $QUIETMODE -gt 0 ] || echo -ne " -> Move stuff.\n"; + if [ ! -d $OUTPUTDIR ] ; then + mkdir $OUTPUTDIR; + fi + + if [ ! -d "$EXTRAS_PATH/images/" ] ; then + [ $QUIETMODE -gt 0 ] || echo -e " - Looking for images."; + [ $QUIETMODE -gt 0 ] || sleep 2; + [ $QUIETMODE -gt 0 ] || echo -e " - I can't find the images..."; + [ $QUIETMODE -gt 0 ] || sleep 1; + [ $QUIETMODE -gt 0 ] || echo -e " - Where did you put the images!?"; + [ $QUIETMODE -gt 0 ] || sleep 2; + [ $QUIETMODE -gt 0 ] || echo -e " - They have to be here somewhere..."; + [ $QUIETMODE -gt 0 ] || sleep 1; + [ $QUIETMODE -gt 0 ] || echo -e " - Looking in /dev/null"; + [ $QUIETMODE -gt 0 ] || sleep 3; + [ $QUIETMODE -gt 0 ] || echo -e " - YOU FOOL, YOU DELETED THE IMAGES!!!"; + [ $QUIETMODE -gt 0 ] || sleep 1; + [ $QUIETMODE -gt 0 ] || echo -e " - I quit!"; + RETVAL=666; + else + if [ ! -d $OUTPUTDIR/images ] ; then + mkdir $OUTPUTDIR/images ; + fi + cp $EXTRAS_PATH/images/* $OUTPUTDIR/images/ ; + RETVAL=0; + fi + return; +} + +#------------------------------------------------------------------------ +# clean_up() removes old versions of the documentation +#------------------------------------------------------------------------ +clean_up() { + if [ -f $OUTPUTDIR/html/index.html ] ; then + [ $QUIETMODE -gt 0 ] || echo -e " -> Trashing old dox."; + rm -f $OUTPUTDIR/html/* + fi + return; +} + +#------------------------------------------------------------------------ +# Create a new genConf & Doxyfile +#------------------------------------------------------------------------ +gen_doxyconfig() { + [ $QUIETMODE -gt 0 ] || echo -e " -> Generating DoxyConfig."; + RETVAL=0; + # first need to make sure there is a Doxyfile here + if [ ! -f $DOXYFILE ] ; then + # what now? (could generate one with 'doxygen -e Doxyfile') but it would be screwed. + echo -e "No Doxyfile here..."; + RETVAL=3; + return; + else + # Create a new doxyfile with the @INCLUDE statement including the generated stuff + echo "`cat $DOXYFILE | grep -v @INCLUDE`" > $NEWDOXYFILE + echo "@INCLUDE = $CONFIG_OUTPUT" >> $NEWDOXYFILE + fi + + # remove the old config file + rm -f $CONFIG_OUTPUT + + # create a new one + touch $CONFIG_OUTPUT + echo "# Generated configuration - Do Not Edit." >> $CONFIG_OUTPUT; + echo "# If you want to modify options, edit DoxyConfig and re-run genconf." >> $CONFIG_OUTPUT; + echo -e "\n" >> $CONFIG_OUTPUT; + echo -e "PROJECT_NAME=$PROJECTNAME" >> $CONFIG_OUTPUT; + echo -e "PROJECT_NUMBER=$VERSION" >> $CONFIG_OUTPUT; + echo -e "PERL_PATH=$PERLPATH" >> $CONFIG_OUTPUT; + echo -e "HAVE_DOT=$HAVEDOT" >> $CONFIG_OUTPUT; + echo -e "DOT_PATH=$DOTPATH" >> $CONFIG_OUTPUT; + echo -e "OUTPUT_LANGUAGE=$OUTPUTLANGUAGE" >> $CONFIG_OUTPUT; + + echo -n "INPUT=" >> $CONFIG_OUTPUT; + for (( COUNTER=0 ; COUNTER < $TARGETCOUNT; $[COUNTER++] )) ; do + # echo -e "${TARGETLIST[$COUNTER]}"; + echo -e "${TARGETLIST[$COUNTER]}" >> $CONFIG_OUTPUT + done + # echo -e "INPUT=$TARGET" >> $CONFIG_OUTPUT; + + echo -e "OUTPUT_DIRECTORY=$OUTPUTDIR" >> $CONFIG_OUTPUT; + echo -e "\n" >> $CONFIG_OUTPUT; + return; +} + +#------------------------------------------------------------------------ +# Build the reference page & index +#------------------------------------------------------------------------ +build_extra_html() { + # file locations + REF_OUT="$OUTPUTDIR/reference/index.html" + INDEX_OUT="$OUTPUTDIR/index.html" + + # Make the output directory if it doesn't exist + if [ ! -d $OUTPUTDIR/reference/ ] ; then + [ $QUIETMODE -gt 0 ] || echo -e " -> Making reference directory"; + mkdir $OUTPUTDIR/reference + fi + + # cat the files together and output the result to each file + [ $QUIETMODE -gt 0 ] || echo -e " -> Building reference document"; + cat $EXTRAS_PATH/doxygen_reference_head.html $EXTRAS_PATH/reference1.html $EXTRAS_PATH/doxygen_reference_foot.html > $REF_OUT; + + if [ ! -d $OUTPUTDIR/example/ ] ; then + [ $QUIETMODE -gt 0 ] || echo -e " -> Making example dir"; + mkdir $OUTPUTDIR/example + fi + [ $QUIETMODE -gt 0 ] || echo -e " -> Moving example docs"; + cp $EXTRAS_PATH/example/* $OUTPUTDIR/example/ + cp $EXTRAS_PATH/doxygen_gtkradiant.css $OUTPUTDIR/example/ + + # Make a redirecting index.html + cat $EXTRAS_PATH/doxygen_index.html > $INDEX_OUT; + return; +} + +#------------------------------------------------------------------------ +# Execute doxygen +#------------------------------------------------------------------------ +run_doxygen() { + # copy doxy_mainpage.h to the target directory + # pipe it through sed to add generation time/date and username - $machine + TEMPLOCATION=`echo $TARGETSTRING | cut -d' ' -f1`; + if [ X"$USERNAME" == "X" ] ; then + USERNAME=`whoami`; + fi + MACHINE=`uname -n`; # `uname -n` or `hostname` ?? + cp $EXTRAS_PATH/doxy_mainpage.h temp.h + cat temp.h | + sed "s/+project+/$PROJECTNAME/" | + sed "s|+target+|$TARGETSTRING|" | + sed "s/+user+/$USERNAME/" | + sed "s/+machine+/$MACHINE/" | + sed "s/+date+/$(date '+%b %d %Y')/" > $TEMPLOCATION/doxy_mainpage.h ; + + rm -f temp.h + + # Start doxygen with the command "doxygen $DOXYFILE" + [ $QUIETMODE -gt 0 ] || echo -e " -> Executing doxygen."; + [ $QUIETMODE -gt 0 ] || echo -e "> doxygen $NEWDOXYFILE"; + doxygen $NEWDOXYFILE + RETVAL=$? + + # remove doxy_mainpage.h from the target directory + rm -f $TEMPLOCATION/doxy_mainpage.h + return; +} + +#------------------------------------------------------------------------ +# End. + diff --git a/Doxygen_files/images/body-left-tile.gif b/Doxygen_files/images/body-left-tile.gif new file mode 100644 index 0000000000000000000000000000000000000000..676dda0a025db73f2ef92c29e463e7fe5177e953 GIT binary patch literal 94 zcmZ?wbhEHb6lP##*vtR|YFfrTy!^50wUsS%-2CF&CM>nI_m!4Y>72YAERRAc{$v63 ZbwDJ@33pF^7Hj*bc}L-m%YNyTxNXE(%ME& zUQ%0bG&)8%JxM!3Pho6=B`Gl@Cocd1046Fk|NsA2VReL#q+4ZsL`z#ID>QF=lw@y* zcY>OMi=i?%Ln<#gVr_&%Nm)KcQ?9kc`}_QpoUen8qx$;$2?`5TU2*E{?|Fore21O} z2M9$>Ts%Thu(rfONLL;qC_hG34-piYpt3GAJ-NTj7#koE5*2@np!WCpw!F#I*xz7l zflgOxA^8LV00000EC2ui022TV000JyK#6cDu?RQ_1wfXYI48uR$-=oPBRI%2D(vVq z6%615$!xhSPGJ!PcszhHlxrv;0-^>0bLk9jToMZv0W%E@4hj_-0yTj)3NH)|4j~Ew zDF`5eGyn_$E(-`g4-Xp~ff5l80S_xB128-X2dOOpAQmG%Dg-+xB&iZ8KLZH_6v@fL MLKy}L%E>_hJK58G{r~^~ literal 0 HcmV?d00001 diff --git a/Doxygen_files/images/body-lower-right.gif b/Doxygen_files/images/body-lower-right.gif new file mode 100644 index 0000000000000000000000000000000000000000..b9d1f99264c06faeab1fc6ed6f53075bd194afdd GIT binary patch literal 352 zcmV-m0iXUyNk%w1VG{rh0M!5ha(YJmrij=74=ul z)7#Y8->9#?#mdxNW_W yj1Y7eBmo%)KN2~N3kY;LGc_n66Qm0kbP7C}u8blTv9+WYxVf$_5T>zBK>$0RQLJtN literal 0 HcmV?d00001 diff --git a/Doxygen_files/images/body-lower-tile.gif b/Doxygen_files/images/body-lower-tile.gif new file mode 100644 index 0000000000000000000000000000000000000000..f9bfad723e1b4eb35a032738a30930d7d410fd56 GIT binary patch literal 67 zcmZ?wbhEHbWMtrBSj51fre*y9|NoTy_W8?q965RU_wPS@51mUb=urI0!TlwBKG(A zeu+SIF z@AAjY)x^ou&C%NX`}~BCq#`CQUuuA1Y=d5Ces6n}xW3Bw`1#=D=;!I}>g?~y&e!?* z`j(%uA^8LV00000EC2ui022TV000JqK!k8eLS0N@(%Ofak$qd=(H6xWr`V<~YI3lAtFAXfq|Fa`xF0}%}nAs<&21}8EK z4-pU-VL~Jo0yiljGZG&S85CC?0to~Q0}U4q6t!0g4jeQODjFB5woeHi1(>}OwY5P2 EJFW+l00000 literal 0 HcmV?d00001 diff --git a/Doxygen_files/images/body-upper-right.gif b/Doxygen_files/images/body-upper-right.gif new file mode 100644 index 0000000000000000000000000000000000000000..af29319ff6a3a46436e0877e88e92e5c73944d43 GIT binary patch literal 362 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 E*-?T=jsO4v literal 0 HcmV?d00001 diff --git a/Doxygen_files/images/gtkr_splash_sm.jpg b/Doxygen_files/images/gtkr_splash_sm.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2f1f3cc5e5d1a8ee7f1516a01e520c30469d062c GIT binary patch literal 7599 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 LG^~E{d|Uc2vL2Ik literal 0 HcmV?d00001 diff --git a/Doxygen_files/images/history_id_logo.gif b/Doxygen_files/images/history_id_logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..117f9be1fcaf13da7a2f3be00f83e6cca1ce8d72 GIT binary patch literal 1427 zcmdUs`&Uv20Dv#Z<9c~`Bg9w2g&qo0A*G_W4MCAi7i!0{=>|TU?kqEBHaqixK!V~E zb*8Nn)RVKBO`Vl9qjE*oinL91t<==fDT~q{tQ=?CpRw;R_`dVS?vx0^6@1`B;3fb_ zLa7;2DgXda`;uq6CDT8MA9rkf+_t5zAS5>2H+%O+HbR{3m2sGGO&&KZfe{-<8>)+_ z&Sg$_ee}44Kd292)4{47f5~=o=B{A_*Gndy#UBcuaKTsD); zVNr&gq>@n2;l>y?gD_YTG}99|cS#}H4rj&tv+3?bHG-OhfwAF!Py17@wj^fDHVsvW zNF(TMggo3RsrxFdHa~c_do6bo&b24N~zXpkC*B68yy=tu+t8C0IaV91z0GT8%$F)(?}X&hxO6;H*U>9OfBX6mSI!aiy9yihhiHA(aI z^m;VmJ-IMu^P0ymnwn@{-ir>rr(2ezZAcOgH)%CTvA z)cLm45merkQNi>;7tjRlDott8I^Pmx57NR+MT<(Mds-i%MG&a9bEKF*qFhhD^WAT$ z5}FVFDqOdXmywwEO*y@?&(Ik(;9luUky1@B5sI3^z0gvH*5Nn4JeIy!(S=wU7%-2&25$BIcAE_+8hb4|ne$IZLVKAE+q`0VRU zSR3Qfss)RMO0|9!?zknKLFjrP=PBbpwrh4uSBst?Sfn)@&4nvlr;g z@sL`KqR7>1BzV*|hAs8y%`?Mc_!?U&3k6#`@gqRQDSj|WK)^LC8YSdXII3i;nk$OV>+(v=;0#-mFPAj zT^NNE=~HBf^%EsI8(^!r8mnVDEpBfXdR3d(Uqb4>ShQxMMk~3tAYsA^8LV00000EC2ui01Nw?S0T95b`mRh%)-x{(aqD?)y~V(*9FD=q??eg#R@#5Xv`t$bn z{qFtChmK#tbNdkb3kdKaJc9`zDip|&U_^Wn7dpf^vEjvm87XcA+0mlLj}=3DJjqfd z%8@CVZnP)>Awv|J9bQb} zF~LHE4LB~0FmYi(0u~9vJcuB}M2HtQm^A31gTV+H5nild!Q?>-2@x(hn$YCH0uCND swMbA{fPxx)3o{|`VToiW0xDZ>QsKb@C7?r#9!C>oFqYeT9JGjFfumAu6 literal 0 HcmV?d00001 diff --git a/Doxygen_files/images/top-tile.gif b/Doxygen_files/images/top-tile.gif new file mode 100644 index 0000000000000000000000000000000000000000..0c6d3b2f6713d9918c8e39cb65a6317dfe45bc3a GIT binary patch literal 122 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 literal 0 HcmV?d00001 diff --git a/Doxygen_files/reference1.html b/Doxygen_files/reference1.html new file mode 100644 index 00000000..bbcf308a --- /dev/null +++ b/Doxygen_files/reference1.html @@ -0,0 +1,333 @@ +
+ + +
+GtkRadiant Doxygen Documentation + + +

Doxygen Quick Reference

+
+

+ +

Index

+
    +
  1. Commenting styles
  2. +
  3. Qt Style C++ Class Example
  4. +
  5. JavaDoc Style C++ Class Example
  6. +
  7. Special Tags
  8. +
  9. Structural Tags
  10. +
+

+ +
+ +

1. Commenting Styles

+There are two different styles of commenting that doxygen recognises. +

+Qt Style:
+ +/*!
+ .... text ....
+*/
+
+
+Qt Style Single line
+ +//! .... one line of text ....
+
+

+ +

+JavaDoc Style:
+ +/**
+ * .... text ....
+ */
+
+
+JavaDoc Style Single line
+ +/// .... one line of text ....
+
+

+ +

+ Doxygen only allows one brief and one detailed description for each declaration/definition. + If there is a brief description before a declaration, and one before the a definition, only + the one before the declaration will be used. If the same situation occurs for a detailed + description the one before the definition is preferred and the one before the declaration will + be ignored.
+ A useful method is to have the brief documentation with the declaration in the header file, + and the detailed documentation with the definition in the source file. +

+ Note: Standard C/C++ comments are ignored by doxygen, but will be included in the code listing + for that file. +

+

+

top

+
+ + +

2. Qt Style C++ Class Example

+

+ Here is an example of a C++ class using Qt Style documentation.
+ The IEpair class from include/iepairs.h is used here. The html result of using these comments + can be found here.
+

+ Note: The resulting html was generated from a single file. If it were generated as part of + the whole documentation, many of the function names and variables would be hyperlinks to + their definitions.
+

+
+//! Virtual class to allow plugin operations on entity pairs
+/*!
+  \todo Write more complete documentation for this class so that it's use
+  is clear
+			
+  An interface to entity keys and key pairs that allows plugins to;
+  read and write entity keys and key values, get a key value as a
+  vec3_t
+*/
+class IEpair
+{
+  public:
+    //! Increment the number of references to this object
+    virtual void IncRef () = 0;
+				
+    //! Decrement the reference count
+    virtual void DecRef () = 0;
+				
+    //! Get a vector from a key
+    virtual void GetVectorForKey( char* key, vec3_t vec ) = 0;
+				
+    //! Get a float from a key
+    virtual float FloatForKey( char *key ) = 0;
+				
+    //! Get a string (char *) from a key
+    virtual char* ValueForKey( char *key ) = 0;
+				
+    //! Set a key value to char *value
+    /*!
+      \param key The (char *) containing the keyname
+      \param value The (char *) to set the key value to
+    */
+    virtual void SetKeyValue( char *key, char *value ) = 0;
+				
+    //! Get a vec3_t for the entities origin
+    virtual void GetEntityOrigin( vec3_t vec ) = 0;
+				
+    //! Compute the rotated bounds of the BBox based on "angle" and "angles" keys
+    virtual void CalculateRotatedBounds( vec3_t mins, vec3_t maxs ) = 0;
+};
+
+

+

+

top

+ +

3. JavaDoc Style C++ Class Example

+ + The same class documented using JavaDoc Style comments +
+/// Virtual class to allow plugin operations on entity pairs
+/**
+  * @todo Write more complete documentation for this class so that it's use
+  * is clear
+  *	
+  * An interface to entity keys and key pairs that allows plugins to;
+  * read and write entity keys and key values, get a key value as a
+  * vec3_t
+  */
+class IEpair
+{
+  public:
+    /// Increment the number of references to this object
+    virtual void IncRef () = 0;
+				
+    /// Decrement the reference count
+    virtual void DecRef () = 0;
+				
+    /// Get a vector from a key
+    virtual void GetVectorForKey( char* key, vec3_t vec ) = 0;
+				
+    /// Get a float from a key
+    virtual float FloatForKey( char *key ) = 0;
+				
+    /// Get a string (char *) from a key
+    virtual char* ValueForKey( char *key ) = 0;
+				
+    /** Set a key value to char *value
+      * @param key The (char *) containing the keyname
+      * @param value The (char *) to set the key value to
+      */
+    virtual void SetKeyValue( char *key, char *value ) = 0;
+				
+    //! Get a vec3_t for the entities origin
+    virtual void GetEntityOrigin( vec3_t vec ) = 0;
+				
+    //! Compute the rotated bounds of the BBox based on "angle" and "angles" keys
+    virtual void CalculateRotatedBounds( vec3_t mins, vec3_t maxs ) = 0;
+};
+
+

+

top

+
+ + +

4. Special Tags

+

+ Special tags using the Qt style begin with a " \ ", or using JavaDoc style a " @ " (the two should not be mixed).
+
+ Common special tags
+

+ + + +
+ author + + The author or a list of comma separated authors/contributers +
+ see + + A reference to another class, class member, function, etc... +
+ param + + A description of a specific function argument or parameter +
+ return + + A description of the value returned from a function/method +
+ bug + + Starts a paragraph where one or more bugs may be listed. +
+ note + + Starts a paragraph where a note may be entered. +
+ todo + + Starts a paragraph where a TODO item is described.
+ Note: All TODO items are collated into a separate todo list, each linking to each other +
+ version + + Starts a paragraph where one or more version strings may be entered. +
+ warning + + Starts a paragraph where one or more warning messages may be entered. +
+ brief + + A single line comment in place of the //! or /// comment. +
+
+
+

top

+
+ +

5. Structural Tags

+

+These are used to document a named object, and are not required to be located near that +object definition or declaration. This allows the documentation for an object to be located +anywhere in the doxygen input files. The exception to this rule however, is that these +documentation blocks cannot be within the body of a function or within C style comment blocks. +All structural commands are preceded by either a " \ " or a " @ ", depending on the +documentation style, and require one or more parameters to specify the name of the object +the description is referring to.
+

+

+An example of the \file structural tag: +

+/*! \file iepairs.h
+    \brief One line documentation for this file
+    \author Author(s)
+    Long description of this file
+*/
+
+

+ +Common Structural Tags

+
+ + + +
+ class + + Documents a class
+ eg:
+ /*! \class IEpair
+ \brief Short description of the IEpair class
+
+ Detailed description of the IEpair class
+ */
+
+
+
+ def + + Describes a #define
+ eg:
+ /*! \def MAX_VALUE The name of the define
+ \brief Description of MAX_VALUE
+ */
+
+
+
+ file + + Describes a file
+ eg:
+ /*! \file iepairs.h The name of the file
+ \brief Description of the file iepairs.h
+
+ Details
+ */
+
+
+
+ struct + + Documents a struct
+ eg:
+ /*! \struct BTListList_t the name of the struct
+ \brief Description of BTListList_t
+
+ Details
+ */
+
+
+
+ var + + Documents a typedef, variable or enum value
+ eg:
+ /*! \var typedef unsigned int UINT32
+ \brief Short description
+ */
+
+
+
+ fn + + Documents a function + eg:
+ /*! \fn virtual void IEpair::DecRef() = 0;
+ \brief Short description of this function
+
+ Detailed description of this function
+ */
+
+ +
+
+ +
+

top

+
+
+
diff --git a/GPL b/GPL new file mode 100644 index 00000000..3912109b --- /dev/null +++ b/GPL @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/GtkRadiant.prj b/GtkRadiant.prj new file mode 100644 index 00000000..957efee7 --- /dev/null +++ b/GtkRadiant.prj @@ -0,0 +1,690 @@ +# Anjuta Version 1.0.2 +Compatibility Level: 1 + + +level editor for Id technology games + + + + + + + + + + + + + + + + + + +props.file.type=project + +anjuta.version=1.0.2 +anjuta.compatibility.level=1 + +project.name=GtkRadiant +project.type=GENERIC +project.target.type=EXECUTABLE +project.version=changesallthetime +project.author=qeradiant.com dev team +project.source.target=install/radiant.x86 +project.has.gettext=0 +project.programming.language=C_C++ +project.excluded.modules= intl + +project.config.extra.modules.before= +project.config.extra.modules.after= +project.config.blocked=1 +project.config.disable.overwriting=1 1 1 1 1 1 1 1 1 + +project.menu.entry=GtkRadiant Version changesallthetime +project.menu.group=Application +project.menu.comment=GtkRadiant Version changesallthetime +project.menu.icon= +project.menu.need.terminal=0 + +project.configure.options= +anjuta.program.arguments= +preferences.indent.automatic=1 +preferences.use.tabs=0 +preferences.indent.opening=0 +preferences.indent.closing=0 +preferences.tabsize=2 +preferences.indent.size=2 +preferences.autoformat.style=Style of Kangleipak +preferences.autoformat.custom.style= -i8 -sc -bli0 -bl0 -cbi0 -ss +preferences.autoformat.disable=0 + +module.include.name=. +module.include.type= +module.include.expanded=1 +module.include.files=\ + Doxygen_files/doxy_mainpage.h\ + contrib/bobtoolz/CPortals.h\ + contrib/bobtoolz/DBobView.h\ + contrib/bobtoolz/DBrush.h\ + contrib/bobtoolz/DEPair.h\ + contrib/bobtoolz/DEntity.h\ + contrib/bobtoolz/DListener.h\ + contrib/bobtoolz/DMap.h\ + contrib/bobtoolz/DPatch.h\ + contrib/bobtoolz/DPlane.h\ + contrib/bobtoolz/DPoint.h\ + contrib/bobtoolz/DShape.h\ + contrib/bobtoolz/DVisDrawer.h\ + contrib/bobtoolz/DWinding.h\ + contrib/bobtoolz/StdAfx.h\ + contrib/bobtoolz/bobToolz.h\ + contrib/bobtoolz/bsploader.h\ + contrib/bobtoolz/ctfresource_gtk.h\ + contrib/bobtoolz/funchandlers.h\ + contrib/bobtoolz/lists.h\ + contrib/bobtoolz/misc.h\ + contrib/bobtoolz/resource-gtk.h\ + contrib/bobtoolz/resource.h\ + contrib/bobtoolz/shapes.h\ + contrib/bobtoolz/visfind.h\ + contrib/bobtoolz/dialogs/AboutDialog.h\ + contrib/bobtoolz/dialogs/AutoCaulkDialog.h\ + contrib/bobtoolz/dialogs/AutoCaulkStartDialog.h\ + contrib/bobtoolz/dialogs/BrushCheckDialog.h\ + contrib/bobtoolz/dialogs/DoorDialog.h\ + contrib/bobtoolz/dialogs/IntersectDialog.h\ + contrib/bobtoolz/dialogs/IntersectInfoDialog.h\ + contrib/bobtoolz/dialogs/PolygonDialog.h\ + contrib/bobtoolz/dialogs/StairDialog.h\ + contrib/bobtoolz/dialogs/TextureResetDialog.h\ + contrib/bobtoolz/dialogs/dialogs-gtk.h\ + contrib/bobtoolz/dialogs/pathplotterdialog.h\ + contrib/gtkgensurf/gendlgs.h\ + contrib/gtkgensurf/gensurf.h\ + contrib/gtkgensurf/triangle.h\ + contrib/patches/Gtk/fileselect/linux/gtkfilesel-01222001.h\ + contrib/prtview/AboutDialog.h\ + contrib/prtview/ConfigDialog.h\ + contrib/prtview/LoadPortalFileDialog.h\ + contrib/prtview/gtkdlgs.h\ + contrib/prtview/portals.h\ + contrib/prtview/prtview.h\ + contrib/prtview/resource.h\ + contrib/prtview/stdafx.h\ + docs/developer/XMLPush/StdAfx.h\ + include/gtkr_list.h\ + include/gtkr_vector.h\ + include/ibrush.h\ + include/ibspfrontend.h\ + include/idata.h\ + include/idatastream.h\ + include/ientity.h\ + include/iepairs.h\ + include/ifilesystem.h\ + include/igl.h\ + include/iimage.h\ + include/imap.h\ + include/imodel.h\ + include/ipatch.h\ + include/ipluginentities.h\ + include/irefcount.h\ + include/iscriplib.h\ + include/iselectedface.h\ + include/ishaders.h\ + include/ishadersmanager.h\ + include/isurfaceplugin.h\ + include/iui.h\ + include/iui_gtk.h\ + include/qerplugin.h\ + include/qertypes.h\ + include/qsysprintf.h\ + include/stl_check.h\ + include/stream_version.h\ + libs/bytebool.h\ + libs/cmdlib.h\ + libs/jpeglib.h\ + libs/mathlib.h\ + libs/missing.h\ + libs/multimon.h\ + libs/pakstuff.h\ + libs/str.h\ + libs/synapse.h\ + libs/jpeg6/jchuff.h\ + libs/jpeg6/jconfig.h\ + libs/jpeg6/jdct.h\ + libs/jpeg6/jdhuff.h\ + libs/jpeg6/jerror.h\ + libs/jpeg6/jinclude.h\ + libs/jpeg6/jmemsys.h\ + libs/jpeg6/jmorecfg.h\ + libs/jpeg6/jpegint.h\ + libs/jpeg6/jversion.h\ + libs/l_net/l_net.h\ + libs/l_net/l_net_wins.h\ + libs/pak/unzip.h\ + plugins/image/bmp.h\ + plugins/image/image.h\ + plugins/image/lbmlib.h\ + plugins/mapq3/plugin.h\ + plugins/mapxml/plugin.h\ + plugins/md3model/entitymodel.h\ + plugins/md3model/md3.h\ + plugins/md3model/md3model.h\ + plugins/md3model/md3surface.h\ + plugins/md3model/plugin.h\ + plugins/md3model/surface.h\ + plugins/sample/stdafx.h\ + plugins/sample/str.h\ + plugins/shaders/plugin.h\ + plugins/shaders/shaders.h\ + plugins/surface/plugtexdef.h\ + plugins/surface/surfdlg.h\ + plugins/surface/surfplug.h\ + plugins/textool/2DView.h\ + plugins/textool/ControlPointsManager.h\ + plugins/textool/StdAfx.h\ + plugins/textool/resource.h\ + plugins/vfspak/vfs.h\ + plugins/vfspak/vfspak.h\ + plugins/vfspk3/unzip-vfspk3.h\ + plugins/vfspk3/vfs.h\ + plugins/vfspk3/vfspk3.h\ + radiant/brush.h\ + radiant/camera.h\ + radiant/camwindow.h\ + radiant/dialog.h\ + radiant/entity.h\ + radiant/epairswrapper.h\ + radiant/feedback.h\ + radiant/file.h\ + radiant/filters.h\ + radiant/findtexturedialog.h\ + radiant/glwidget.h\ + radiant/glwindow.h\ + radiant/groupdialog.h\ + radiant/gtkfilesel-darwin.h\ + radiant/gtkfilesel-linux.h\ + radiant/gtkfilesel.h\ + radiant/gtkmisc.h\ + radiant/mainframe.h\ + radiant/map.h\ + radiant/parse.h\ + radiant/patchdialog.h\ + radiant/plugin.h\ + radiant/pluginmanager.h\ + radiant/points.h\ + radiant/preferences.h\ + radiant/qe3.h\ + radiant/qedefs.h\ + radiant/qfiles.h\ + radiant/qgl.h\ + radiant/resource.h\ + radiant/select.h\ + radiant/stdafx.h\ + radiant/surfacedialog.h\ + radiant/texmanip.h\ + radiant/textures.h\ + radiant/texwindow.h\ + radiant/ui.h\ + radiant/undo.h\ + radiant/watchbsp.h\ + radiant/winding.h\ + radiant/xmlstuff.h\ + radiant/xywindow.h\ + radiant/z.h\ + radiant/zwindow.h\ + tools/quake3/common/aselib.h\ + tools/quake3/common/bspfile.h\ + tools/quake3/common/cmdlib.h\ + tools/quake3/common/imagelib.h\ + tools/quake3/common/inout.h\ + tools/quake3/common/l3dslib.h\ + tools/quake3/common/mutex.h\ + tools/quake3/common/polylib.h\ + tools/quake3/common/polyset.h\ + tools/quake3/common/qfiles.h\ + tools/quake3/common/qthreads.h\ + tools/quake3/common/scriplib.h\ + tools/quake3/common/surfaceflags.h\ + tools/quake3/common/trilib.h\ + tools/quake3/common/unzip.h\ + tools/quake3/common/vfs.h\ + tools/quake3/q3data/3dslib.h\ + tools/quake3/q3data/md3lib.h\ + tools/quake3/q3data/p3dlib.h\ + tools/quake3/q3data/q3data.h\ + tools/quake3/q3map/Heapagnt.h\ + tools/quake3/q3map/game_t.h\ + tools/quake3/q3map/light.h\ + tools/quake3/q3map/mesh.h\ + tools/quake3/q3map/qbsp.h\ + tools/quake3/q3map/shaders.h\ + tools/quake3/q3map/vis.h\ + libs/function.h\ + include/iarchive.h\ + libs/filestream.h + +module.source.name=. +module.source.type= +module.source.expanded=1 +module.source.files=\ + contrib/bobtoolz/DBobView.cpp\ + contrib/bobtoolz/DBrush.cpp\ + contrib/bobtoolz/DEPair.cpp\ + contrib/bobtoolz/DEntity.cpp\ + contrib/bobtoolz/DListener.cpp\ + contrib/bobtoolz/DMap.cpp\ + contrib/bobtoolz/DPatch.cpp\ + contrib/bobtoolz/DPlane.cpp\ + contrib/bobtoolz/DPoint.cpp\ + contrib/bobtoolz/DShape.cpp\ + contrib/bobtoolz/DVisDrawer.cpp\ + contrib/bobtoolz/DWinding.cpp\ + contrib/bobtoolz/StdAfx.cpp\ + contrib/bobtoolz/bobToolz-GTK.cpp\ + contrib/bobtoolz/bobToolz.cpp\ + contrib/bobtoolz/bsploader.cpp\ + contrib/bobtoolz/cportals.cpp\ + contrib/bobtoolz/ctfToolz-GTK.cpp\ + contrib/bobtoolz/funchandlers-GTK.cpp\ + contrib/bobtoolz/funchandlers-ctf-GTK.cpp\ + contrib/bobtoolz/funchandlers.cpp\ + contrib/bobtoolz/lists.cpp\ + contrib/bobtoolz/misc.cpp\ + contrib/bobtoolz/shapes.cpp\ + contrib/bobtoolz/visfind.cpp\ + contrib/bobtoolz/dialogs/AboutDialog.cpp\ + contrib/bobtoolz/dialogs/AutoCaulkDialog.cpp\ + contrib/bobtoolz/dialogs/AutoCaulkStartDialog.cpp\ + contrib/bobtoolz/dialogs/DoorDialog.cpp\ + contrib/bobtoolz/dialogs/IntersectDialog.cpp\ + contrib/bobtoolz/dialogs/IntersectInfoDialog.cpp\ + contrib/bobtoolz/dialogs/PolygonDialog.cpp\ + contrib/bobtoolz/dialogs/StairDialog.cpp\ + contrib/bobtoolz/dialogs/TextureResetDialog.cpp\ + contrib/bobtoolz/dialogs/brushcheckdialog.cpp\ + contrib/bobtoolz/dialogs/dialogs-gtk.cpp\ + contrib/bobtoolz/dialogs/pathplotterdialog.cpp\ + contrib/gtkgensurf/bitmap.cpp\ + contrib/gtkgensurf/dec.cpp\ + contrib/gtkgensurf/face.cpp\ + contrib/gtkgensurf/font.cpp\ + contrib/gtkgensurf/gendlgs.cpp\ + contrib/gtkgensurf/genmap.cpp\ + contrib/gtkgensurf/gensurf.cpp\ + contrib/gtkgensurf/heretic.cpp\ + contrib/gtkgensurf/plugin.cpp\ + contrib/gtkgensurf/triangle.c\ + contrib/gtkgensurf/view.cpp\ + contrib/patches/Gtk/fileselect/linux/gtkfilesel-01222001.c\ + contrib/prtview/AboutDialog.cpp\ + contrib/prtview/ConfigDialog.cpp\ + contrib/prtview/LoadPortalFileDialog.cpp\ + contrib/prtview/gtkdlgs.cpp\ + contrib/prtview/portals.cpp\ + contrib/prtview/prtview.cpp\ + contrib/prtview/stdafx.cpp\ + docs/developer/XMLPush/StdAfx.cpp\ + docs/developer/XMLPush/XMLPush.cpp\ + docs/manual/quake3/Compile_Manual/cfgq3.c\ + libs/cmdlib/cmdlib.cpp\ + libs/jpeg6/jcomapi.cpp\ + libs/jpeg6/jdapimin.cpp\ + libs/jpeg6/jdapistd.cpp\ + libs/jpeg6/jdatasrc.cpp\ + libs/jpeg6/jdcoefct.cpp\ + libs/jpeg6/jdcolor.cpp\ + libs/jpeg6/jddctmgr.cpp\ + libs/jpeg6/jdhuff.cpp\ + libs/jpeg6/jdinput.cpp\ + libs/jpeg6/jdmainct.cpp\ + libs/jpeg6/jdmarker.cpp\ + libs/jpeg6/jdmaster.cpp\ + libs/jpeg6/jdpostct.cpp\ + libs/jpeg6/jdsample.cpp\ + libs/jpeg6/jdtrans.cpp\ + libs/jpeg6/jerror.cpp\ + libs/jpeg6/jfdctflt.cpp\ + libs/jpeg6/jidctflt.cpp\ + libs/jpeg6/jmemmgr.cpp\ + libs/jpeg6/jmemnobs.cpp\ + libs/jpeg6/jpgload.cpp\ + libs/jpeg6/jutils.cpp\ + libs/l_net/l_net.c\ + libs/l_net/l_net_berkley.c\ + libs/l_net/l_net_wins.c\ + libs/mathlib/bbox.c\ + libs/mathlib/linear.c\ + libs/mathlib/m4x4.c\ + libs/mathlib/mathlib.c\ + libs/mathlib/ray.c\ + libs/pak/pakstuff.cpp\ + libs/pak/unzip.cpp\ + libs/synapse/synapse.cpp\ + plugins/image/bmp.cpp\ + plugins/image/image.cpp\ + plugins/image/jpeg.cpp\ + plugins/image/lbmlib.cpp\ + plugins/mapq3/parse.cpp\ + plugins/mapq3/plugin.cpp\ + plugins/mapq3/write.cpp\ + plugins/mapxml/plugin.cpp\ + plugins/mapxml/xmlparse.cpp\ + plugins/mapxml/xmlwrite.cpp\ + plugins/md3model/eclassmodel.cpp\ + plugins/md3model/entitymodel.cpp\ + plugins/md3model/md3model.cpp\ + plugins/md3model/md3surface.cpp\ + plugins/md3model/miscmodel.cpp\ + plugins/md3model/plugin.cpp\ + plugins/sample/sample.cpp\ + plugins/sample/stdafx.cpp\ + plugins/shaders/plugin.cpp\ + plugins/shaders/shaders.cpp\ + plugins/surface/plugtexdef.cpp\ + plugins/surface/surfdlg.cpp\ + plugins/surface/surfplug.cpp\ + plugins/textool/2DView.cpp\ + plugins/textool/ControlPointsManager.cpp\ + plugins/textool/StdAfx.cpp\ + plugins/textool/TexTool.cpp\ + plugins/vfspak/vfs.cpp\ + plugins/vfspak/vfspak.cpp\ + plugins/vfspk3/unzip.cpp\ + plugins/vfspk3/vfs.cpp\ + plugins/vfspk3/vfspk3.cpp\ + radiant/bp_dlg.cpp\ + radiant/brush.cpp\ + radiant/brush_primit.cpp\ + radiant/brushscript.cpp\ + radiant/camwindow.cpp\ + radiant/csg.cpp\ + radiant/dialog.cpp\ + radiant/dialoginfo.cpp\ + radiant/drag.cpp\ + radiant/eclass.cpp\ + radiant/entity.cpp\ + radiant/feedback.cpp\ + radiant/file.cpp\ + radiant/filters.cpp\ + radiant/findtexturedialog.cpp\ + radiant/glinterface.cpp\ + radiant/glwidget.cpp\ + radiant/glwindow.cpp\ + radiant/groupdialog.cpp\ + radiant/gtkdlgs.cpp\ + radiant/gtkfilesel-darwin.c\ + radiant/gtkfilesel-linux.c\ + radiant/gtkfilesel.c\ + radiant/gtkmisc.cpp\ + radiant/iepairs.cpp\ + radiant/main.cpp\ + radiant/mainframe.cpp\ + radiant/map.cpp\ + radiant/missing.cpp\ + radiant/parse.cpp\ + radiant/patchdialog.cpp\ + radiant/plugin.cpp\ + radiant/pluginentities.cpp\ + radiant/pluginmanager.cpp\ + radiant/pmesh.cpp\ + radiant/points.cpp\ + radiant/preferences.cpp\ + radiant/profile.cpp\ + radiant/qe3.cpp\ + radiant/qgl-mac.c\ + radiant/qgl.c\ + radiant/queuedraw.cpp\ + radiant/select.cpp\ + radiant/selectedface.cpp\ + radiant/stdafx.cpp\ + radiant/surfacedialog.cpp\ + radiant/surfaceplugin.cpp\ + radiant/texmanip.cpp\ + radiant/texwindow.cpp\ + radiant/ui.cpp\ + radiant/undo.cpp\ + radiant/vertsel.cpp\ + radiant/watchbsp.cpp\ + radiant/winding.cpp\ + radiant/xywindow.cpp\ + radiant/z.cpp\ + radiant/zwindow.cpp\ + tools/quake3/common/aselib.c\ + tools/quake3/common/bspfile.c\ + tools/quake3/common/cmdlib.c\ + tools/quake3/common/imagelib.c\ + tools/quake3/common/inout.c\ + tools/quake3/common/l3dslib.c\ + tools/quake3/common/md4.c\ + tools/quake3/common/mutex.c\ + tools/quake3/common/polylib.c\ + tools/quake3/common/scriplib.c\ + tools/quake3/common/threads.c\ + tools/quake3/common/trilib.c\ + tools/quake3/common/unzip.c\ + tools/quake3/common/vfs.c\ + tools/quake3/q3data/3dslib.c\ + tools/quake3/q3data/compress.c\ + tools/quake3/q3data/images.c\ + tools/quake3/q3data/md3lib.c\ + tools/quake3/q3data/models.c\ + tools/quake3/q3data/oldstuff.c\ + tools/quake3/q3data/p3dlib.c\ + tools/quake3/q3data/polyset.c\ + tools/quake3/q3data/q3data.c\ + tools/quake3/q3data/stripper.c\ + tools/quake3/q3data/video.c\ + tools/quake3/q3map/NetConnect/main.c\ + tools/quake3/q3map/brush.c\ + tools/quake3/q3map/brush_primit.c\ + tools/quake3/q3map/bsp.c\ + tools/quake3/q3map/facebsp.c\ + tools/quake3/q3map/fog.c\ + tools/quake3/q3map/gldraw.c\ + tools/quake3/q3map/glfile.c\ + tools/quake3/q3map/leakfile.c\ + tools/quake3/q3map/light.c\ + tools/quake3/q3map/light_bounce.c\ + tools/quake3/q3map/light_trace.c\ + tools/quake3/q3map/lightmaps.c\ + tools/quake3/q3map/lightv.c\ + tools/quake3/q3map/map.c\ + tools/quake3/q3map/mesh.c\ + tools/quake3/q3map/misc_model.c\ + tools/quake3/q3map/nodraw.c\ + tools/quake3/q3map/patch.c\ + tools/quake3/q3map/path_init.c\ + tools/quake3/q3map/portals.c\ + tools/quake3/q3map/prtfile.c\ + tools/quake3/q3map/shaders.c\ + tools/quake3/q3map/surface.c\ + tools/quake3/q3map/terrain.c\ + tools/quake3/q3map/tjunction.c\ + tools/quake3/q3map/tree.c\ + tools/quake3/q3map/vis.c\ + tools/quake3/q3map/visflow.c\ + tools/quake3/q3map/writebsp.c\ + tools/quake3/q3map/NetTest/main.c\ + plugins/archivepak/archive.cpp\ + plugins/archivepak/plugin.cpp + +module.pixmap.name=. +module.pixmap.type= +module.pixmap.expanded=0 +module.pixmap.files=\ + Doxygen_files/example/doxygen.gif\ + Doxygen_files/example/graph_legend.gif\ + Doxygen_files/images/body-left-tile.gif\ + Doxygen_files/images/body-lower-left.gif\ + Doxygen_files/images/body-lower-right.gif\ + Doxygen_files/images/body-lower-tile.gif\ + Doxygen_files/images/body-right-tile.gif\ + Doxygen_files/images/body-upper-left.gif\ + Doxygen_files/images/body-upper-right.gif\ + Doxygen_files/images/body-upper-tile.gif\ + Doxygen_files/images/gtkr_splash.jpg\ + Doxygen_files/images/gtkr_splash_sm.jpg\ + Doxygen_files/images/history_id_logo.gif\ + Doxygen_files/images/top-right.gif\ + Doxygen_files/images/top-tile.gif\ + Doxygen_files/images/top-title.gif\ + contrib/patches/Gtk/fileselect/back.xpm\ + contrib/patches/Gtk/fileselect/forward.xpm\ + contrib/patches/Gtk/fileselect/refresh.xpm\ + contrib/patches/Gtk/fileselect/up.xpm\ + docs/developer/Inspector/classdiagram1.gif\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image002.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image003.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image004.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image006.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image008.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image010.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image012.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image014.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image016.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image018.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image020.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image022.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image024.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image026.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image028.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image030.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image032.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image034.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image035.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image038.png\ + docs/manual/Q3Rad_Manual/Q3Rad_Manual_files/image040.png\ + docs/manual/quake3/Q3AShader_Manual/q3ashader_manual_files/image002.jpg\ + docs/manual/quake3/Team_Arena_Mapping_Help/pics/CRUSADER.gif\ + docs/manual/quake3/Team_Arena_Mapping_Help/pics/INTRUDER.gif\ + docs/manual/quake3/Team_Arena_Mapping_Help/pics/MAINPOP.gif\ + docs/manual/quake3/Team_Arena_Mapping_Help/pics/MENUBACKgif.gif\ + docs/manual/quake3/Team_Arena_Mapping_Help/pics/PAGANs.gif\ + docs/manual/quake3/Team_Arena_Mapping_Help/pics/STROGGS.gif\ + docs/manual/quake3/Team_Arena_Mapping_Help/pics/THEFALLEN.gif\ + docs/manual/quake3/Team_Arena_Mapping_Help/pics/logo.gif\ + docs/manual/quake3/Terrain_Manual/pages/Image3.gif\ + docs/manual/quake3/Terrain_Manual/pages/Image4.gif\ + docs/manual/quake3/Terrain_Manual/pages/Image5.gif\ + docs/manual/quake3/Terrain_Manual/pages/Image6.gif\ + docs/manual/quake3/Terrain_Manual/pics/background.jpg\ + docs/manual/quake3/Terrain_Manual/pics/start.gif\ + docs/manual/quake3/Terrain_Manual/pics/terrain.jpg\ + plugins/textool/Doc/Image2.jpg\ + setup/linux/Help/DocsArt/toolback.jpg\ + setup/linux/radiant.xpm\ + setup/linux/setup.data/splash.xpm + +module.data.name=. +module.data.type= +module.data.expanded=0 +module.data.files= + +module.help.name=. +module.help.type= +module.help.expanded=0 +module.help.files= + +module.doc.name=. +module.doc.type= +module.doc.expanded=0 +module.doc.files=\ + INSTALL\ + README\ + Doxygen_files/example/annotated.html\ + Doxygen_files/example/classIEpair-members.html\ + Doxygen_files/example/classIEpair.html\ + Doxygen_files/example/classes.html\ + Doxygen_files/example/files.html\ + Doxygen_files/example/functions.html\ + Doxygen_files/example/graph_legend.html\ + Doxygen_files/example/index.html\ + Doxygen_files/example/pages.html\ + Doxygen_files/example/test_8c-source.html\ + Doxygen_files/example/test_8c.html\ + Doxygen_files/example/todo.html\ + Doxygen_files/doxygen_gtkradiant_foot.html\ + Doxygen_files/doxygen_gtkradiant_head.html\ + Doxygen_files/doxygen_index.html\ + Doxygen_files/doxygen_reference_foot.html\ + Doxygen_files/doxygen_reference_head.html\ + Doxygen_files/reference1.html\ + contrib/patches/Gtk/fileselect/README\ + docs/developer/TODO\ + docs/manual/quake3/Compile_Manual/index.html\ + docs/manual/quake3/Compile_Manual/q3map.html\ + docs/manual/quake3/New_Teams_For_Q3TA/index.html\ + docs/manual/quake3/Q3AShader_Manual/appendix/appA.html\ + docs/manual/quake3/Team_Arena_Mapping_Help/pages/design_tips.html\ + docs/manual/quake3/Team_Arena_Mapping_Help/pages/map_converters_checklist.html\ + docs/manual/quake3/Team_Arena_Mapping_Help/pages/preface.html\ + docs/manual/quake3/Team_Arena_Mapping_Help/pages/related_links.html\ + docs/manual/quake3/Team_Arena_Mapping_Help/pages/ta_game_types.html\ + docs/manual/quake3/Team_Arena_Mapping_Help/pages/team_arena_entity_definitions.html\ + docs/manual/quake3/Team_Arena_Mapping_Help/pages/team_arena_prefabs.html\ + docs/manual/quake3/Team_Arena_Mapping_Help/pages/team_powerup_bases.html\ + docs/manual/quake3/Team_Arena_Mapping_Help/pages/using_new_game_entities.html\ + docs/manual/quake3/Team_Arena_Mapping_Help/start.html\ + docs/manual/quake3/Terrain_Manual/pages/adding_bots.html\ + docs/manual/quake3/Terrain_Manual/pages/adding_buildings_to_terrain.html\ + docs/manual/quake3/Terrain_Manual/pages/art_tools.html\ + docs/manual/quake3/Terrain_Manual/pages/blocking_vis.html\ + docs/manual/quake3/Terrain_Manual/pages/boxing_in_the_world.html\ + docs/manual/quake3/Terrain_Manual/pages/clipping_the_terrain.html\ + docs/manual/quake3/Terrain_Manual/pages/creating_the_alphamap.html\ + docs/manual/quake3/Terrain_Manual/pages/creating_the_terrain.html\ + docs/manual/quake3/Terrain_Manual/pages/entity_keys_and_values.html\ + docs/manual/quake3/Terrain_Manual/pages/glossary.html\ + docs/manual/quake3/Terrain_Manual/pages/height_map_into_terrain_mesh.html\ + docs/manual/quake3/Terrain_Manual/pages/height_maps.html\ + docs/manual/quake3/Terrain_Manual/pages/introduction.html\ + docs/manual/quake3/Terrain_Manual/pages/key_changes.html\ + docs/manual/quake3/Terrain_Manual/pages/lighting_the_terrain.html\ + docs/manual/quake3/Terrain_Manual/pages/manipulating_the_terrain_mesh.html\ + docs/manual/quake3/Terrain_Manual/pages/mapping_the_textures.html\ + docs/manual/quake3/Terrain_Manual/pages/new_or_revised_q3map_shader_comm.html\ + docs/manual/quake3/Terrain_Manual/pages/other_possible_height_map_tools.html\ + docs/manual/quake3/Terrain_Manual/pages/related_links.html\ + docs/manual/quake3/Terrain_Manual/pages/suggested_gensurf_settings.html\ + docs/manual/quake3/Terrain_Manual/pages/table_of_contents.html\ + docs/manual/quake3/Terrain_Manual/pages/terrain_entity.html\ + docs/manual/quake3/Terrain_Manual/pages/terrain_mesh_into_terrain_entity.html\ + docs/manual/quake3/Terrain_Manual/pages/terrain_related_worldspawn_features.html\ + docs/manual/quake3/Terrain_Manual/pages/terrain_texture.html\ + docs/manual/quake3/Terrain_Manual/pages/the_meta_shader.html\ + docs/manual/quake3/Terrain_Manual/start.html\ + plugins/textool/Doc/TexTool.html\ + setup/PluginSDK/README.html\ + setup/PluginSDK/TODO\ + setup/data/tools/credits.html\ + setup/linux/Help/Index.html\ + setup/win32/TODO\ + www/coding.html\ + www/files.html\ + www/gtkradiant.html\ + www/hosted.html\ + www/index.html\ + www/reviews.html + +module.po.expanded=0 +module.po.files= + +compiler.options.supports= +compiler.options.include.paths=\ + .\ + .. +compiler.options.library.paths= +compiler.options.libraries= +compiler.options.libraries.selected= +compiler.options.defines=\ + HAVE_CONFIG_H +compiler.options.warning.buttons=0 0 1 1 0 1 0 0 0 0 0 0 0 1 0 0 +compiler.options.optimize.buttons=0 0 1 0 +compiler.options.other.buttons=1 0 +compiler.options.other.c.flags= +compiler.options.other.l.flags= +compiler.options.other.l.libs= +project.source.paths= + diff --git a/GtkRadiant.sln b/GtkRadiant.sln new file mode 100644 index 00000000..dfbfdb67 --- /dev/null +++ b/GtkRadiant.sln @@ -0,0 +1,272 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "entityq3", "plugins\entity\entityq3.vcproj", "{49C5823A-5E50-4029-ACEE-1627EBB79E47}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GtkRadiant", "radiant\GtkRadiant.vcproj", "{8E70385C-223A-4DD1-9B99-28FF2331A2B5}" + ProjectSection(ProjectDependencies) = postProject + {1C785349-866D-447D-8C55-8A51E5CA0E87} = {1C785349-866D-447D-8C55-8A51E5CA0E87} + {68E2C6B6-96CA-4BBD-A485-FEE6F2E65407} = {68E2C6B6-96CA-4BBD-A485-FEE6F2E65407} + {8845C5C1-4154-425F-8643-447FADC03449} = {8845C5C1-4154-425F-8643-447FADC03449} + {8845C5C1-4154-425F-8643-447FADC03449} = {8845C5C1-4154-425F-8643-447FADC03449} + {8845C5C1-4154-425F-8643-447FADC03449} = {8845C5C1-4154-425F-8643-447FADC03449} + {8845C5C1-4154-425F-8643-447FADC03449} = {8845C5C1-4154-425F-8643-447FADC03449} + {8845C5C1-4154-425F-8643-447FADC03449} = {8845C5C1-4154-425F-8643-447FADC03449} + {8845C5C1-4154-425F-8643-447FADC03449} = {8845C5C1-4154-425F-8643-447FADC03449} + {8845C5C1-4154-425F-8643-447FADC03449} = {8845C5C1-4154-425F-8643-447FADC03449} + {8845C5C1-4154-425F-8643-447FADC03449} = {8845C5C1-4154-425F-8643-447FADC03449} + {8845C5C1-4154-425F-8643-447FADC03449} = {8845C5C1-4154-425F-8643-447FADC03449} + {8845D5C1-4154-425F-8643-447FADC03449} = {8845D5C1-4154-425F-8643-447FADC03449} + {BED4E2E5-0368-4042-9898-4914B0372468} = {BED4E2E5-0368-4042-9898-4914B0372468} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gen", "gen.vcproj", "{BED4E2E5-0368-4042-9898-4914B0372468}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "l_net", "libs\l_net\l_net.vcproj", "{8845D5C1-4154-425F-8643-447FADC03449}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cmdlib", "libs\cmdlib\cmdlib.vcproj", "{8845C5C1-4154-425F-8643-447FADC03449}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "archivepak", "plugins\archivepak\archivepak.vcproj", "{75160E63-E642-4C71-9D4C-B733E152C418}" + ProjectSection(ProjectDependencies) = postProject + {8845C5C1-4154-425F-8643-447FADC03449} = {8845C5C1-4154-425F-8643-447FADC03449} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "archivewad", "plugins\archivewad\archivewad.vcproj", "{9DC47AF9-ACFC-40A5-A4A6-FF3E7F8EFFBE}" + ProjectSection(ProjectDependencies) = postProject + {8845C5C1-4154-425F-8643-447FADC03449} = {8845C5C1-4154-425F-8643-447FADC03449} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "archivezip", "plugins\archivezip\archivezip.vcproj", "{A7E0FE03-E9BB-4478-9752-250BBD406C2D}" + ProjectSection(ProjectDependencies) = postProject + {8845C5C1-4154-425F-8643-447FADC03449} = {8845C5C1-4154-425F-8643-447FADC03449} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "imageq3", "plugins\image\imageq3.vcproj", "{0A0D3519-2ADD-4B47-A890-746170B2CCD8}" + ProjectSection(ProjectDependencies) = postProject + {8576EC58-4E54-49C0-879A-F054C92B1D03} = {8576EC58-4E54-49C0-879A-F054C92B1D03} + {0501A08E-D4D7-42C1-9E2A-BA3F2F320741} = {0501A08E-D4D7-42C1-9E2A-BA3F2F320741} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "modelmd3", "plugins\md3model\modelmd3.vcproj", "{C2A5530D-C2DB-4503-A651-4B92AEC5FE74}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "modelpico", "plugins\model\modelpico.vcproj", "{386DBF35-2F76-4BB1-8B4B-1D69C34F8996}" + ProjectSection(ProjectDependencies) = postProject + {015EA9D3-85F2-4C4E-BFC3-430AC59093B9} = {015EA9D3-85F2-4C4E-BFC3-430AC59093B9} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vfsq3", "plugins\vfspk3\vfsq3.vcproj", "{0BB50F1C-E139-48A2-B9D8-1E781275777F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mapq3", "plugins\mapq3\mapq3.vcproj", "{D6130A5F-12DC-487B-BB9E-4BFDA60FBADF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mapxml", "plugins\mapxml\mapxml.vcproj", "{B43DBA9D-6EE0-421C-83D9-9776064B66B4}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shadersq3", "plugins\shaders\shadersq3.vcproj", "{F79DCF6D-72B1-45F6-A471-5209951C0BDD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "picomodel", "libs\picomodel\picomodel.vcproj", "{015EA9D3-85F2-4C4E-BFC3-430AC59093B9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jpeg6", "libs\jpeg6\jpeg6.vcproj", "{0501A08E-D4D7-42C1-9E2A-BA3F2F320741}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "imagehl", "plugins\imagehl\imagehl.vcproj", "{15DEA3EA-9386-49C7-80C6-5B090DE1D536}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "imagepng", "plugins\imagepng\imagepng.vcproj", "{15DEA4EA-9386-49C7-80C6-5B090DE1D536}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "q3map2", "tools\quake3\q3map2\q3map2.vcproj", "{8ED67991-58A6-44AA-9B3A-3217085EF187}" + ProjectSection(ProjectDependencies) = postProject + {BF0FF048-887F-4D43-A455-F8C04FB98F10} = {BF0FF048-887F-4D43-A455-F8C04FB98F10} + {8576EC58-4E54-49C0-879A-F054C92B1D03} = {8576EC58-4E54-49C0-879A-F054C92B1D03} + {0501A08E-D4D7-42C1-9E2A-BA3F2F320741} = {0501A08E-D4D7-42C1-9E2A-BA3F2F320741} + {8845C5C1-4154-425F-8643-447FADC03449} = {8845C5C1-4154-425F-8643-447FADC03449} + {8845D5C1-4154-425F-8643-447FADC03449} = {8845D5C1-4154-425F-8643-447FADC03449} + {015EA9D3-85F2-4C4E-BFC3-430AC59093B9} = {015EA9D3-85F2-4C4E-BFC3-430AC59093B9} + {BED4E2E5-0368-4042-9898-4914B0372468} = {BED4E2E5-0368-4042-9898-4914B0372468} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ddslib", "libs\ddslib\ddslib.vcproj", "{8576EC58-4E54-49C0-879A-F054C92B1D03}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mathlib", "libs\mathlib\mathlib.vcproj", "{BF0FF048-887F-4D43-A455-F8C04FB98F10}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libs", "libs\libs.vcproj", "{1C785349-866D-447D-8C55-8A51E5CA0E87}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "include", "include\include.vcproj", "{04A5D9EE-EC49-4CBC-BD05-D80BC287897C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "profile", "libs\profile\profile.vcproj", "{853632F4-6420-40C5-B80B-38B678E472B8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "imageq2", "plugins\imageq2\imageq2.vcproj", "{697E77F2-9E9E-4F12-973F-C1214494248C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sample", "plugins\sample\sample.vcproj", "{46B36F0C-5E17-458E-AE6F-AECE52F66EDE}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "q2map", "tools\quake2\q2map\q2map.vcproj", "{FB80DE6C-51C8-4D56-876D-C7878A4EB10B}" + ProjectSection(ProjectDependencies) = postProject + {8845D5C1-4154-425F-8643-447FADC03449} = {8845D5C1-4154-425F-8643-447FADC03449} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gtkutil", "libs\gtkutil\gtkutil.vcproj", "{68E2C6B6-96CA-4BBD-A485-FEE6F2E65407}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PrtView", "contrib\prtview\PrtView.vcproj", "{B20364D1-4329-4D4E-B9CE-C9767618FDD6}" + ProjectSection(ProjectDependencies) = postProject + {853632F4-6420-40C5-B80B-38B678E472B8} = {853632F4-6420-40C5-B80B-38B678E472B8} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bobtoolz", "contrib\bobtoolz\bobtoolz.vcproj", "{439FE12C-77F0-44CD-BC9B-803B3E92C197}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sunplug", "contrib\sunplug\sunplug.vcproj", "{46B36F0C-5E17-458E-AE6F-AECE52F66EDF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "brushexport", "contrib\brushexport\brushexport.vcproj", "{334D54AE-9AF7-43EA-BC64-2E31846B972E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shaderplug", "contrib\shaderplug\shaderplug.vcproj", "{0B09566E-63DB-4A28-A555-BBE2747769B6}" + ProjectSection(ProjectDependencies) = postProject + {1C785349-866D-447D-8C55-8A51E5CA0E87} = {1C785349-866D-447D-8C55-8A51E5CA0E87} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ufoai", "contrib\ufoaiplug\ufoai.vcproj", "{623CDF9E-ACC5-43E2-8E8D-B6266235A044}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {49C5823A-5E50-4029-ACEE-1627EBB79E47}.Debug|Win32.ActiveCfg = Debug|Win32 + {49C5823A-5E50-4029-ACEE-1627EBB79E47}.Debug|Win32.Build.0 = Debug|Win32 + {49C5823A-5E50-4029-ACEE-1627EBB79E47}.Release|Win32.ActiveCfg = Release|Win32 + {49C5823A-5E50-4029-ACEE-1627EBB79E47}.Release|Win32.Build.0 = Release|Win32 + {8E70385C-223A-4DD1-9B99-28FF2331A2B5}.Debug|Win32.ActiveCfg = Debug|Win32 + {8E70385C-223A-4DD1-9B99-28FF2331A2B5}.Debug|Win32.Build.0 = Debug|Win32 + {8E70385C-223A-4DD1-9B99-28FF2331A2B5}.Release|Win32.ActiveCfg = Release|Win32 + {8E70385C-223A-4DD1-9B99-28FF2331A2B5}.Release|Win32.Build.0 = Release|Win32 + {BED4E2E5-0368-4042-9898-4914B0372468}.Debug|Win32.ActiveCfg = Debug|Win32 + {BED4E2E5-0368-4042-9898-4914B0372468}.Debug|Win32.Build.0 = Debug|Win32 + {BED4E2E5-0368-4042-9898-4914B0372468}.Release|Win32.ActiveCfg = Release|Win32 + {BED4E2E5-0368-4042-9898-4914B0372468}.Release|Win32.Build.0 = Release|Win32 + {8845D5C1-4154-425F-8643-447FADC03449}.Debug|Win32.ActiveCfg = Debug|Win32 + {8845D5C1-4154-425F-8643-447FADC03449}.Debug|Win32.Build.0 = Debug|Win32 + {8845D5C1-4154-425F-8643-447FADC03449}.Release|Win32.ActiveCfg = Release|Win32 + {8845D5C1-4154-425F-8643-447FADC03449}.Release|Win32.Build.0 = Release|Win32 + {8845C5C1-4154-425F-8643-447FADC03449}.Debug|Win32.ActiveCfg = Debug|Win32 + {8845C5C1-4154-425F-8643-447FADC03449}.Debug|Win32.Build.0 = Debug|Win32 + {8845C5C1-4154-425F-8643-447FADC03449}.Release|Win32.ActiveCfg = Release|Win32 + {8845C5C1-4154-425F-8643-447FADC03449}.Release|Win32.Build.0 = Release|Win32 + {75160E63-E642-4C71-9D4C-B733E152C418}.Debug|Win32.ActiveCfg = Debug|Win32 + {75160E63-E642-4C71-9D4C-B733E152C418}.Debug|Win32.Build.0 = Debug|Win32 + {75160E63-E642-4C71-9D4C-B733E152C418}.Release|Win32.ActiveCfg = Release|Win32 + {75160E63-E642-4C71-9D4C-B733E152C418}.Release|Win32.Build.0 = Release|Win32 + {9DC47AF9-ACFC-40A5-A4A6-FF3E7F8EFFBE}.Debug|Win32.ActiveCfg = Debug|Win32 + {9DC47AF9-ACFC-40A5-A4A6-FF3E7F8EFFBE}.Debug|Win32.Build.0 = Debug|Win32 + {9DC47AF9-ACFC-40A5-A4A6-FF3E7F8EFFBE}.Release|Win32.ActiveCfg = Release|Win32 + {9DC47AF9-ACFC-40A5-A4A6-FF3E7F8EFFBE}.Release|Win32.Build.0 = Release|Win32 + {A7E0FE03-E9BB-4478-9752-250BBD406C2D}.Debug|Win32.ActiveCfg = Debug|Win32 + {A7E0FE03-E9BB-4478-9752-250BBD406C2D}.Debug|Win32.Build.0 = Debug|Win32 + {A7E0FE03-E9BB-4478-9752-250BBD406C2D}.Release|Win32.ActiveCfg = Release|Win32 + {A7E0FE03-E9BB-4478-9752-250BBD406C2D}.Release|Win32.Build.0 = Release|Win32 + {0A0D3519-2ADD-4B47-A890-746170B2CCD8}.Debug|Win32.ActiveCfg = Debug|Win32 + {0A0D3519-2ADD-4B47-A890-746170B2CCD8}.Debug|Win32.Build.0 = Debug|Win32 + {0A0D3519-2ADD-4B47-A890-746170B2CCD8}.Release|Win32.ActiveCfg = Release|Win32 + {0A0D3519-2ADD-4B47-A890-746170B2CCD8}.Release|Win32.Build.0 = Release|Win32 + {C2A5530D-C2DB-4503-A651-4B92AEC5FE74}.Debug|Win32.ActiveCfg = Debug|Win32 + {C2A5530D-C2DB-4503-A651-4B92AEC5FE74}.Debug|Win32.Build.0 = Debug|Win32 + {C2A5530D-C2DB-4503-A651-4B92AEC5FE74}.Release|Win32.ActiveCfg = Release|Win32 + {C2A5530D-C2DB-4503-A651-4B92AEC5FE74}.Release|Win32.Build.0 = Release|Win32 + {386DBF35-2F76-4BB1-8B4B-1D69C34F8996}.Debug|Win32.ActiveCfg = Debug|Win32 + {386DBF35-2F76-4BB1-8B4B-1D69C34F8996}.Debug|Win32.Build.0 = Debug|Win32 + {386DBF35-2F76-4BB1-8B4B-1D69C34F8996}.Release|Win32.ActiveCfg = Release|Win32 + {386DBF35-2F76-4BB1-8B4B-1D69C34F8996}.Release|Win32.Build.0 = Release|Win32 + {0BB50F1C-E139-48A2-B9D8-1E781275777F}.Debug|Win32.ActiveCfg = Debug|Win32 + {0BB50F1C-E139-48A2-B9D8-1E781275777F}.Debug|Win32.Build.0 = Debug|Win32 + {0BB50F1C-E139-48A2-B9D8-1E781275777F}.Release|Win32.ActiveCfg = Release|Win32 + {0BB50F1C-E139-48A2-B9D8-1E781275777F}.Release|Win32.Build.0 = Release|Win32 + {D6130A5F-12DC-487B-BB9E-4BFDA60FBADF}.Debug|Win32.ActiveCfg = Debug|Win32 + {D6130A5F-12DC-487B-BB9E-4BFDA60FBADF}.Debug|Win32.Build.0 = Debug|Win32 + {D6130A5F-12DC-487B-BB9E-4BFDA60FBADF}.Release|Win32.ActiveCfg = Release|Win32 + {D6130A5F-12DC-487B-BB9E-4BFDA60FBADF}.Release|Win32.Build.0 = Release|Win32 + {B43DBA9D-6EE0-421C-83D9-9776064B66B4}.Debug|Win32.ActiveCfg = Debug|Win32 + {B43DBA9D-6EE0-421C-83D9-9776064B66B4}.Debug|Win32.Build.0 = Debug|Win32 + {B43DBA9D-6EE0-421C-83D9-9776064B66B4}.Release|Win32.ActiveCfg = Release|Win32 + {B43DBA9D-6EE0-421C-83D9-9776064B66B4}.Release|Win32.Build.0 = Release|Win32 + {F79DCF6D-72B1-45F6-A471-5209951C0BDD}.Debug|Win32.ActiveCfg = Debug|Win32 + {F79DCF6D-72B1-45F6-A471-5209951C0BDD}.Debug|Win32.Build.0 = Debug|Win32 + {F79DCF6D-72B1-45F6-A471-5209951C0BDD}.Release|Win32.ActiveCfg = Release|Win32 + {F79DCF6D-72B1-45F6-A471-5209951C0BDD}.Release|Win32.Build.0 = Release|Win32 + {015EA9D3-85F2-4C4E-BFC3-430AC59093B9}.Debug|Win32.ActiveCfg = Debug|Win32 + {015EA9D3-85F2-4C4E-BFC3-430AC59093B9}.Debug|Win32.Build.0 = Debug|Win32 + {015EA9D3-85F2-4C4E-BFC3-430AC59093B9}.Release|Win32.ActiveCfg = Release|Win32 + {015EA9D3-85F2-4C4E-BFC3-430AC59093B9}.Release|Win32.Build.0 = Release|Win32 + {0501A08E-D4D7-42C1-9E2A-BA3F2F320741}.Debug|Win32.ActiveCfg = Debug|Win32 + {0501A08E-D4D7-42C1-9E2A-BA3F2F320741}.Debug|Win32.Build.0 = Debug|Win32 + {0501A08E-D4D7-42C1-9E2A-BA3F2F320741}.Release|Win32.ActiveCfg = Release|Win32 + {0501A08E-D4D7-42C1-9E2A-BA3F2F320741}.Release|Win32.Build.0 = Release|Win32 + {15DEA3EA-9386-49C7-80C6-5B090DE1D536}.Debug|Win32.ActiveCfg = Debug|Win32 + {15DEA3EA-9386-49C7-80C6-5B090DE1D536}.Debug|Win32.Build.0 = Debug|Win32 + {15DEA3EA-9386-49C7-80C6-5B090DE1D536}.Release|Win32.ActiveCfg = Release|Win32 + {15DEA3EA-9386-49C7-80C6-5B090DE1D536}.Release|Win32.Build.0 = Release|Win32 + {15DEA4EA-9386-49C7-80C6-5B090DE1D536}.Debug|Win32.ActiveCfg = Debug|Win32 + {15DEA4EA-9386-49C7-80C6-5B090DE1D536}.Debug|Win32.Build.0 = Debug|Win32 + {15DEA4EA-9386-49C7-80C6-5B090DE1D536}.Release|Win32.ActiveCfg = Release|Win32 + {15DEA4EA-9386-49C7-80C6-5B090DE1D536}.Release|Win32.Build.0 = Release|Win32 + {8ED67991-58A6-44AA-9B3A-3217085EF187}.Debug|Win32.ActiveCfg = Debug|Win32 + {8ED67991-58A6-44AA-9B3A-3217085EF187}.Debug|Win32.Build.0 = Debug|Win32 + {8ED67991-58A6-44AA-9B3A-3217085EF187}.Release|Win32.ActiveCfg = Release|Win32 + {8ED67991-58A6-44AA-9B3A-3217085EF187}.Release|Win32.Build.0 = Release|Win32 + {8576EC58-4E54-49C0-879A-F054C92B1D03}.Debug|Win32.ActiveCfg = Debug|Win32 + {8576EC58-4E54-49C0-879A-F054C92B1D03}.Debug|Win32.Build.0 = Debug|Win32 + {8576EC58-4E54-49C0-879A-F054C92B1D03}.Release|Win32.ActiveCfg = Release|Win32 + {8576EC58-4E54-49C0-879A-F054C92B1D03}.Release|Win32.Build.0 = Release|Win32 + {BF0FF048-887F-4D43-A455-F8C04FB98F10}.Debug|Win32.ActiveCfg = Debug|Win32 + {BF0FF048-887F-4D43-A455-F8C04FB98F10}.Debug|Win32.Build.0 = Debug|Win32 + {BF0FF048-887F-4D43-A455-F8C04FB98F10}.Release|Win32.ActiveCfg = Release|Win32 + {BF0FF048-887F-4D43-A455-F8C04FB98F10}.Release|Win32.Build.0 = Release|Win32 + {1C785349-866D-447D-8C55-8A51E5CA0E87}.Debug|Win32.ActiveCfg = Debug|Win32 + {1C785349-866D-447D-8C55-8A51E5CA0E87}.Debug|Win32.Build.0 = Debug|Win32 + {1C785349-866D-447D-8C55-8A51E5CA0E87}.Release|Win32.ActiveCfg = Release|Win32 + {1C785349-866D-447D-8C55-8A51E5CA0E87}.Release|Win32.Build.0 = Release|Win32 + {04A5D9EE-EC49-4CBC-BD05-D80BC287897C}.Debug|Win32.ActiveCfg = Debug|Win32 + {04A5D9EE-EC49-4CBC-BD05-D80BC287897C}.Debug|Win32.Build.0 = Debug|Win32 + {04A5D9EE-EC49-4CBC-BD05-D80BC287897C}.Release|Win32.ActiveCfg = Release|Win32 + {04A5D9EE-EC49-4CBC-BD05-D80BC287897C}.Release|Win32.Build.0 = Release|Win32 + {853632F4-6420-40C5-B80B-38B678E472B8}.Debug|Win32.ActiveCfg = Debug|Win32 + {853632F4-6420-40C5-B80B-38B678E472B8}.Debug|Win32.Build.0 = Debug|Win32 + {853632F4-6420-40C5-B80B-38B678E472B8}.Release|Win32.ActiveCfg = Release|Win32 + {853632F4-6420-40C5-B80B-38B678E472B8}.Release|Win32.Build.0 = Release|Win32 + {697E77F2-9E9E-4F12-973F-C1214494248C}.Debug|Win32.ActiveCfg = Debug|Win32 + {697E77F2-9E9E-4F12-973F-C1214494248C}.Debug|Win32.Build.0 = Debug|Win32 + {697E77F2-9E9E-4F12-973F-C1214494248C}.Release|Win32.ActiveCfg = Release|Win32 + {697E77F2-9E9E-4F12-973F-C1214494248C}.Release|Win32.Build.0 = Release|Win32 + {46B36F0C-5E17-458E-AE6F-AECE52F66EDE}.Debug|Win32.ActiveCfg = Debug|Win32 + {46B36F0C-5E17-458E-AE6F-AECE52F66EDE}.Debug|Win32.Build.0 = Debug|Win32 + {46B36F0C-5E17-458E-AE6F-AECE52F66EDE}.Release|Win32.ActiveCfg = Release|Win32 + {46B36F0C-5E17-458E-AE6F-AECE52F66EDE}.Release|Win32.Build.0 = Release|Win32 + {FB80DE6C-51C8-4D56-876D-C7878A4EB10B}.Debug|Win32.ActiveCfg = Debug|Win32 + {FB80DE6C-51C8-4D56-876D-C7878A4EB10B}.Debug|Win32.Build.0 = Debug|Win32 + {FB80DE6C-51C8-4D56-876D-C7878A4EB10B}.Release|Win32.ActiveCfg = Release|Win32 + {FB80DE6C-51C8-4D56-876D-C7878A4EB10B}.Release|Win32.Build.0 = Release|Win32 + {68E2C6B6-96CA-4BBD-A485-FEE6F2E65407}.Debug|Win32.ActiveCfg = Debug|Win32 + {68E2C6B6-96CA-4BBD-A485-FEE6F2E65407}.Debug|Win32.Build.0 = Debug|Win32 + {68E2C6B6-96CA-4BBD-A485-FEE6F2E65407}.Release|Win32.ActiveCfg = Release|Win32 + {68E2C6B6-96CA-4BBD-A485-FEE6F2E65407}.Release|Win32.Build.0 = Release|Win32 + {B20364D1-4329-4D4E-B9CE-C9767618FDD6}.Debug|Win32.ActiveCfg = Debug|Win32 + {B20364D1-4329-4D4E-B9CE-C9767618FDD6}.Debug|Win32.Build.0 = Debug|Win32 + {B20364D1-4329-4D4E-B9CE-C9767618FDD6}.Release|Win32.ActiveCfg = Release|Win32 + {B20364D1-4329-4D4E-B9CE-C9767618FDD6}.Release|Win32.Build.0 = Release|Win32 + {439FE12C-77F0-44CD-BC9B-803B3E92C197}.Debug|Win32.ActiveCfg = Debug|Win32 + {439FE12C-77F0-44CD-BC9B-803B3E92C197}.Debug|Win32.Build.0 = Debug|Win32 + {439FE12C-77F0-44CD-BC9B-803B3E92C197}.Release|Win32.ActiveCfg = Release|Win32 + {439FE12C-77F0-44CD-BC9B-803B3E92C197}.Release|Win32.Build.0 = Release|Win32 + {46B36F0C-5E17-458E-AE6F-AECE52F66EDF}.Debug|Win32.ActiveCfg = Debug|Win32 + {46B36F0C-5E17-458E-AE6F-AECE52F66EDF}.Debug|Win32.Build.0 = Debug|Win32 + {46B36F0C-5E17-458E-AE6F-AECE52F66EDF}.Release|Win32.ActiveCfg = Release|Win32 + {46B36F0C-5E17-458E-AE6F-AECE52F66EDF}.Release|Win32.Build.0 = Release|Win32 + {334D54AE-9AF7-43EA-BC64-2E31846B972E}.Debug|Win32.ActiveCfg = Debug|Win32 + {334D54AE-9AF7-43EA-BC64-2E31846B972E}.Debug|Win32.Build.0 = Debug|Win32 + {334D54AE-9AF7-43EA-BC64-2E31846B972E}.Release|Win32.ActiveCfg = Release|Win32 + {334D54AE-9AF7-43EA-BC64-2E31846B972E}.Release|Win32.Build.0 = Release|Win32 + {0B09566E-63DB-4A28-A555-BBE2747769B6}.Debug|Win32.ActiveCfg = Debug|Win32 + {0B09566E-63DB-4A28-A555-BBE2747769B6}.Debug|Win32.Build.0 = Debug|Win32 + {0B09566E-63DB-4A28-A555-BBE2747769B6}.Release|Win32.ActiveCfg = Release|Win32 + {0B09566E-63DB-4A28-A555-BBE2747769B6}.Release|Win32.Build.0 = Release|Win32 + {623CDF9E-ACC5-43E2-8E8D-B6266235A044}.Debug|Win32.ActiveCfg = Debug|Win32 + {623CDF9E-ACC5-43E2-8E8D-B6266235A044}.Debug|Win32.Build.0 = Debug|Win32 + {623CDF9E-ACC5-43E2-8E8D-B6266235A044}.Release|Win32.ActiveCfg = Release|Win32 + {623CDF9E-ACC5-43E2-8E8D-B6266235A044}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/LGPL b/LGPL new file mode 100644 index 00000000..3b204400 --- /dev/null +++ b/LGPL @@ -0,0 +1,458 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..962a9fb2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,36 @@ +LICENSE ( last update: Wed Feb 8 17:16:40 CST 2006 ) +----------------------------------------------------- + +There are 3 license types used throughout GtkRadiant source code. + +BSD - modified Berkeley Software Distribution license +( each BSD licensed source file starts with the appropriate header ) +LGPL - GNU Lesser General Public License v2.1 +( see LGPL at the root of the tree ) +GPL - GNU General Public License +( see GPL at the root of the tree ) + +How do I check which license applies to a given part of the source code? + +Each source file in the tree comes with a license header which explains what +license applies. To sum up shortly: + +GPL: ( except some files contributed by Loki Software under BSD license ) +GtkRadiant Core +GtkRadiant Modules +GtkRadiant Libraries +Quake III Tools +Quake II Tools +Background2D Plugin +HydraToolz Plugin + +BSD: +JPEG Library +MD5 Library +DDS Library +PicoModel Library +PrtView Plugin + +LGPL +BobToolz Plugin +GenSurf Plugin diff --git a/README.doxygen b/README.doxygen new file mode 100644 index 00000000..f3847f67 --- /dev/null +++ b/README.doxygen @@ -0,0 +1,51 @@ + Documentation for generating doxygen documentation +--------------------------------------------------------- + +1. Options for gendox +More up-to-date command line options are available via +the command ./gendox --help + +usage: "sh gendox [ ] [ -o ]" + or "./gendox [ ] [ -o ]" + + + The directory, or directories to generate the + documentation from. + +-o + Specifies the output directory which + should follow the -o switch + +-q --quiet + Stops the script from outputing status information, + other than errors. + +-k --kill + Kills other running doxygen pids. + +eg: ./gendox include/ -o ../Documentation + +* This will produce documentation for the include files, +and output to the directory specified one level above the +current directory. + +The target can be the current directory "./" in which case +doxygen will generate documentation for all subdirectories +of the current directory recursively. + +The default output directory is currently ... +> ../GtkRadiant-doxygen + +* If the script is called without any target directories +it will generate documentation for the core of radiant... +include/ libs/ radiant/ and plugins/ + +If there are specific options that you'd like to customise, +the DoxyConfig file is used to generate the file from which +doxygen gets its settings from. So any changes that need +to be made should be made to this file. + + +Gef :] +(gefdavis@dingoblue.net.au) +--------------------------------------------------------- diff --git a/SConscript b/SConscript new file mode 100644 index 00000000..a0509a90 --- /dev/null +++ b/SConscript @@ -0,0 +1,633 @@ +import os, sys, commands, string +from makeversion import get_version +# OS Detection: +OS = commands.getoutput('uname') + +Import('GLOBALS') +Import(GLOBALS) + +def build_list(s_prefix, s_string): + s_list = Split(s_string) + for i in range(len(s_list)): + s_list[i] = s_prefix + '/' + s_list[i] + return s_list + +# common code ------------------------------------------------------ + +cmdlib_lib = g_env.StaticLibrary(target='libs/cmdlib', source='libs/cmdlib/cmdlib.cpp') + +xml_env = g_env.Copy() +xml_env.Prepend(CPPPATH = 'include') +xml_env.Append(CXXFLAGS='`pkg-config glib-2.0 --cflags` `xml2-config --cflags`') +xml_src = 'ixml.cpp xmlparser.cpp xmlwriter.cpp xmlelement.cpp xmltextags.cpp' +xml_lib = xml_env.StaticLibrary(target='libs/xmllib', source=build_list('libs/xml', xml_src)) + +mathlib_src = 'mathlib.c bbox.c line.c m4x4.c ray.c' +mathlib_lib = g_env.StaticLibrary(target='libs/mathlib', source=build_list('libs/mathlib', mathlib_src)) + +md5lib_lib = g_env.StaticLibrary(target='libs/md5lib', source='libs/md5lib/md5lib.c') + +ddslib_lib = g_env.StaticLibrary(target='libs/ddslib', source='libs/ddslib/ddslib.c') + +jpeg_env = g_env.Copy() +jpeg_env.Prepend(CPPPATH = 'libs/jpeg6') +jpeg_src = 'jcomapi.cpp jdcoefct.cpp jdinput.cpp jdpostct.cpp jfdctflt.cpp jpgload.cpp jdapimin.cpp jdcolor.cpp jdmainct.cpp jdsample.cpp jidctflt.cpp jutils.cpp jdapistd.cpp jddctmgr.cpp jdmarker.cpp jdtrans.cpp jmemmgr.cpp jdatasrc.cpp jdhuff.cpp jdmaster.cpp jerror.cpp jmemnobs.cpp' +jpeg_lib = jpeg_env.StaticLibrary(target='libs/jpeg6', source=build_list('libs/jpeg6', jpeg_src)) + +l_net_lib = g_env.StaticLibrary(target='libs/l_net', source=['libs/l_net/l_net.c', 'libs/l_net/l_net_berkley.c']) + +picomodel_src = 'picointernal.c picomodel.c picomodules.c pm_3ds.c pm_ase.c pm_md3.c pm_obj.c\ + pm_ms3d.c pm_mdc.c pm_fm.c pm_md2.c pm_lwo.c pm_terrain.c lwo/clip.c lwo/envelope.c lwo/list.c lwo/lwio.c\ + lwo/lwo2.c lwo/lwob.c lwo/pntspols.c lwo/surface.c lwo/vecmath.c lwo/vmap.c' +picomodel_lib = g_env.StaticLibrary(target='libs/picomodel', source=build_list('libs/picomodel', picomodel_src)) + +#splines_env = g_env.Copy() +#splines_src = build_list('libs/splines', 'math_angles.cpp math_matrix.cpp math_quaternion.cpp math_vector.cpp q_parse.cpp q_shared.cpp splines.cpp util_str.cpp') +#splines_env['CPPPATH'].append('include') +#splines_lib = splines_env.StaticLibrary(target='libs/splines', source=splines_src) + +profile_env = g_env.Copy(); +profile_env['CPPPATH'].append('include') +profile_src = 'profile.cpp file.cpp' +profile_lib = profile_env.StaticLibrary(target='libs/profile', source=build_list('libs/profile', profile_src)) + +gtkutil_env = g_env.Copy(); +gtkutil_env['CPPPATH'].append('include') +gtkutil_env.useGlib2() +gtkutil_env.useGtk2() +gtkutil_env.useGtkGLExt() + +gtkutil_src = '\ + accelerator.cpp\ + button.cpp\ + clipboard.cpp\ + closure.cpp\ + container.cpp\ + cursor.cpp\ + dialog.cpp\ + entry.cpp\ + frame.cpp\ + filechooser.cpp\ + glfont.cpp\ + glwidget.cpp\ + image.cpp\ + idledraw.cpp\ + menu.cpp\ + messagebox.cpp\ + nonmodal.cpp\ + paned.cpp\ + pointer.cpp\ + toolbar.cpp\ + widget.cpp\ + window.cpp\ + xorrectangle.cpp\ +' + +gtkutil_lib = gtkutil_env.StaticLibrary(target='libs/gtkutil', source=build_list('libs/gtkutil', gtkutil_src)) + +# end static / common libraries --------------------------------------------------- + +# q3map --------------------------------------------------------------------------- + +q3map_env = g_env.Copy() +q3map_env['CPPPATH'].append('include') +q3map_env.useXML2() +q3map_env.useGlib2() +q3map_env.usePNG() +q3map_env.useMHash() +q3map_env.usePThread() +q3map_env.Prepend(CPPPATH='tools/quake3/common') + +q3map_common_src = [ + 'common/cmdlib.c', + 'common/imagelib.c', + 'common/inout.c', + 'common/mutex.c', + 'common/polylib.c', + 'common/scriplib.c', + 'common/threads.c', + 'common/unzip.c', + 'common/vfs.c' ] + +q3map_src = [ + 'q3map2/brush.c', + 'q3map2/brush_primit.c', + 'q3map2/bsp.c', + 'q3map2/facebsp.c', + 'q3map2/fog.c', + 'q3map2/leakfile.c', + 'q3map2/map.c', + 'q3map2/model.c', + 'q3map2/patch.c', + 'q3map2/portals.c', + 'q3map2/prtfile.c', + 'q3map2/surface.c', + 'q3map2/surface_fur.c', + 'q3map2/surface_meta.c', + 'q3map2/tjunction.c', + 'q3map2/tree.c', + 'q3map2/writebsp.c', + 'q3map2/image.c', + 'q3map2/light.c', + 'q3map2/light_bounce.c', + 'q3map2/light_trace.c', + 'q3map2/light_ydnar.c', + 'q3map2/lightmaps_ydnar.c', + 'q3map2/vis.c', + 'q3map2/visflow.c', + 'q3map2/bspfile_abstract.c', + 'q3map2/bspfile_ibsp.c', + 'q3map2/bspfile_rbsp.c', + 'q3map2/decals.c', + 'q3map2/main.c', + 'q3map2/mesh.c', + 'q3map2/path_init.c', + 'q3map2/shaders.c', + 'q3map2/surface_extra.c', + 'q3map2/surface_foliage.c', + 'q3map2/convert_ase.c', + 'q3map2/convert_map.c' ] + +q3map_full_src = [ ] +for i in q3map_common_src + q3map_src: + q3map_full_src.append('tools/quake3/' + i) + +q3map_libs = ['mathlib', 'l_net', 'jpeg6', 'picomodel', 'ddslib'] + +q3map_prog = q3map_env.Program(target='q3map2.' + g_cpu, source=q3map_full_src, LIBS=q3map_libs, LIBPATH='libs') +q3map_env.Depends(q3map_prog, mathlib_lib) +q3map_env.Depends(q3map_prog, l_net_lib) +q3map_env.Depends(q3map_prog, jpeg_lib) +q3map_env.Depends(q3map_prog, picomodel_lib) +q3map_env.Depends(q3map_prog, ddslib_lib) +q3map_env.Install(INSTALL, q3map_prog) + +# end q3map2 ---------------------------------------------------------------------- + +# q3data --------------------------------------------------------------------------- + +q3data_env = q3map_env.Copy() + +q3data_common_src = [ + 'common/aselib.c', + 'common/bspfile.c', + 'common/cmdlib.c', + 'common/imagelib.c', + 'common/inout.c', + 'common/md4.c', + 'common/scriplib.c', + 'common/trilib.c', + 'common/unzip.c', + 'common/vfs.c' + ] + +q3data_src = [ + 'q3data/3dslib.c', + 'q3data/compress.c', + 'q3data/images.c', + 'q3data/md3lib.c', + 'q3data/models.c', + 'q3data/p3dlib.c', + 'q3data/polyset.c', + 'q3data/q3data.c', + 'q3data/stripper.c', + 'q3data/video.c' ] + +q3data_full_src = [ ] +for i in q3data_common_src + q3data_src: + q3data_full_src.append('tools/quake3/' + i) + +q3data_prog = q3data_env.Program( target = 'q3data.' + g_cpu, source = q3data_full_src, LIBS=['mathlib', 'l_net'], LIBPATH='libs' ) +q3data_env.Depends(q3data_prog, mathlib_lib) +q3data_env.Depends(q3data_prog, l_net_lib) +q3data_env.Install( INSTALL, q3data_prog ) + +# end q3data ---------------------------------------------------------------------- + +# q2_tools --------------------------------------------------------------------------- + +q2_tools_env = g_env.Copy() +q2_tools_env['CPPPATH'].append('include') +q2_tools_env.useXML2() +q2_tools_env.usePThread() +q2_tools_env.Prepend(CPPPATH='tools/quake2/common') + +q2_tools_common_src = [ + 'common/bspfile.c', + 'common/cmdlib.c', + 'common/inout.c', + 'common/l3dslib.c', + 'common/lbmlib.c', + 'common/mathlib.c', + 'common/md4.c', + 'common/path_init.c', + 'common/polylib.c', + 'common/scriplib.c', + 'common/threads.c', + 'common/trilib.c' +] + + +q2_tools_q2map_src = [ + 'q2map/brushbsp.c', + 'q2map/csg.c', + 'q2map/faces.c', + 'q2map/flow.c', + 'q2map/glfile.c', + 'q2map/leakfile.c', + 'q2map/lightmap.c', + 'q2map/main.c', + 'q2map/map.c', + 'q2map/nodraw.c', + 'q2map/patches.c', + 'q2map/portals.c', + 'q2map/prtfile.c', + 'q2map/qbsp.c', + 'q2map/qrad.c', + 'q2map/qvis.c', + 'q2map/textures.c', + 'q2map/trace.c', + 'q2map/tree.c', + 'q2map/writebsp.c' +] + +q2_tools_qdata3_common_src = [ + 'common/bspfile.c', + 'common/cmdlib.c', + 'common/inout.c', + 'common/l3dslib.c', + 'common/lbmlib.c', + 'common/mathlib.c', + 'common/md4.c', + 'common/path_init.c', + 'common/scriplib.c', + 'common/threads.c', + 'common/trilib.c' +] + +q2_tools_qdata3_src = [ + 'qdata/images.c', + 'qdata/models.c', + 'qdata/qdata.c', + 'qdata/sprites.c', + 'qdata/tables.c', + 'qdata/video.c' +] + +q2_tools_q2map_full_src = [ ] +for i in q2_tools_common_src + q2_tools_q2map_src: + q2_tools_q2map_full_src.append('tools/quake2/' + i) + +q2_tools_qdata3_full_src = [ ] +for i in q2_tools_common_src + q2_tools_qdata3_src: + q2_tools_qdata3_full_src.append('tools/quake2/' + i) + +if ( OS != 'Darwin' ): + q2_tools_q2map_prog = q2_tools_env.Program(target='quake2_tools/q2map', source=q2_tools_q2map_full_src, LIBS='l_net', LIBPATH='libs') + q2_tools_env.Depends(q2_tools_q2map_prog, l_net_lib) + q2_tools_env.Install(INSTALL, q2_tools_q2map_prog ) + + q2_tools_qdata3_prog = q2_tools_env.Program(target='quake2_tools/qdata3', source=q2_tools_qdata3_full_src, LIBS='l_net', LIBPATH='libs') + q2_tools_env.Depends(q2_tools_qdata3_prog, l_net_lib) + q2_tools_env.Install(INSTALL, q2_tools_qdata3_prog ) + + +# end q2_tools ---------------------------------------------------------------------- + +# qdata3_heretic2 --------------------------------------------------------------------------- + +heretic2_tools_env = g_env.Copy() +heretic2_tools_env['CPPPATH'].append('include') +heretic2_tools_env.useXML2() +heretic2_tools_env.usePThread() +heretic2_tools_env.Prepend(CPPPATH='tools/quake2/qdata_heretic2') +heretic2_tools_env.Prepend(CPPPATH='tools/quake2/qdata_heretic2/qcommon') +heretic2_tools_env.Prepend(CPPPATH='tools/quake2/qdata_heretic2/common') + +heretic2_tools_qdata3_common_src = [ + 'qdata_heretic2/common/bspfile.c', + 'qdata_heretic2/common/cmdlib.c', + 'qdata_heretic2/common/inout.c', + 'qdata_heretic2/common/l3dslib.c', + 'qdata_heretic2/common/lbmlib.c', + 'qdata_heretic2/common/mathlib.c', + 'qdata_heretic2/common/md4.c', + 'qdata_heretic2/common/path_init.c', + 'qdata_heretic2/common/qfiles.c', + 'qdata_heretic2/common/scriplib.c', + 'qdata_heretic2/common/threads.c', + 'qdata_heretic2/common/token.c', + 'qdata_heretic2/common/trilib.c' +] + +heretic2_tools_qdata3_qcommon_src = [ + 'qdata_heretic2/qcommon/reference.c', + 'qdata_heretic2/qcommon/resourcemanager.c', + 'qdata_heretic2/qcommon/skeletons.c' +] + +heretic2_tools_qdata3_src = [ + 'qdata_heretic2/animcomp.c', + 'qdata_heretic2/book.c', + 'qdata_heretic2/fmodels.c', + 'qdata_heretic2/images.c', + 'qdata_heretic2/jointed.c', + 'qdata_heretic2/models.c', + 'qdata_heretic2/pics.c', + 'qdata_heretic2/qdata.c', + 'qdata_heretic2/qd_skeletons.c', + 'qdata_heretic2/sprites.c', + 'qdata_heretic2/svdcmp.c', + 'qdata_heretic2/tables.c', + 'qdata_heretic2/tmix.c', + 'qdata_heretic2/video.c' +] + +heretic2_tools_qdata3_full_src = [ ] +for i in heretic2_tools_qdata3_common_src + heretic2_tools_qdata3_qcommon_src + heretic2_tools_qdata3_src: + heretic2_tools_qdata3_full_src.append('tools/quake2/' + i) + + +heretic2_tools_env['CCFLAGS'] += '-D_LINUX ' + +if ( OS != 'Darwin' ): + heretic2_tools_prog = heretic2_tools_env.Program(target='h2data', source=heretic2_tools_qdata3_full_src, LIBS='l_net', LIBPATH='libs') + heretic2_tools_env.Depends(heretic2_tools_prog, l_net_lib) + heretic2_tools_env.Install(INSTALL + '/heretic2', heretic2_tools_prog ) + +# end heretic2_tools ---------------------------------------------------------------------- + + + +# radiant, modules and plugins ---------------------------------------------------- + +module_env = g_env.Copy() +module_env['CPPPATH'].append('include') +if ( OS == 'Darwin' ): + module_env['LINKFLAGS'] += '-dynamiclib -ldl ' +else: + module_env['LINKFLAGS'] += '-ldl ' +module_env['LIBPREFIX'] = '' + + +vfspk3_env = module_env.Copy() +vfspk3_lst = build_list('plugins/vfspk3', 'vfspk3.cpp vfs.cpp archive.cpp') +vfspk3_env.useGlib2() +vfspk3_lib = vfspk3_env.SharedLibrarySafe(target='vfspk3', source=vfspk3_lst) +vfspk3_env.Install(INSTALL + '/modules', vfspk3_lib) + +archivepak_env = module_env.Copy() +archivepak_lst = build_list('plugins/archivepak', 'plugin.cpp archive.cpp pak.cpp') +archivepak_lib = archivepak_env.SharedLibrarySafe(target='archivepak', source=archivepak_lst, LIBS='cmdlib', LIBPATH='libs') +archivepak_env.Depends(archivepak_lib, cmdlib_lib) +archivepak_env.Install(INSTALL + '/modules', archivepak_lib) + +archivewad_env = module_env.Copy() +archivewad_lst = build_list('plugins/archivewad', 'plugin.cpp archive.cpp wad.cpp') +archivewad_lib = archivewad_env.SharedLibrarySafe(target='archivewad', source=archivewad_lst, LIBS='cmdlib', LIBPATH='libs') +archivewad_env.Depends(archivewad_lib, cmdlib_lib) +archivewad_env.Install(INSTALL + '/modules', archivewad_lib) + +archivezip_env = module_env.Copy() +archivezip_lst = build_list('plugins/archivezip', 'plugin.cpp archive.cpp pkzip.cpp zlibstream.cpp') +archivezip_env.useZLib() +archivezip_lib = archivezip_env.SharedLibrarySafe(target='archivezip', source=archivezip_lst, LIBS='cmdlib', LIBPATH='libs') +archivezip_env.Depends(archivezip_lib, cmdlib_lib) +archivezip_env.Install(INSTALL + '/modules', archivezip_lib) + +shaders_env = module_env.Copy() +shaders_lst = build_list('plugins/shaders', 'shaders.cpp plugin.cpp') +shaders_env.useGlib2() +shaders_lib = shaders_env.SharedLibrarySafe(target='shaders', source=shaders_lst, LIBS='cmdlib', LIBPATH='libs') +shaders_env.Depends(shaders_lib, cmdlib_lib) +shaders_env.Install(INSTALL + '/modules', shaders_lib) + +image_env = module_env.Copy() +image_lst = build_list('plugins/image', 'bmp.cpp jpeg.cpp image.cpp pcx.cpp tga.cpp dds.cpp') +image_lib = image_env.SharedLibrarySafe(target='image', source=image_lst, LIBS=['jpeg6', 'ddslib'], LIBPATH='libs') +image_env.Depends(image_lib, jpeg_lib) +image_env.Depends(image_lib, ddslib_lib) +image_env.Install(INSTALL + '/modules', image_lib) + +imagehl_lst=build_list('plugins/imagehl', 'imagehl.cpp hlw.cpp mip.cpp sprite.cpp') +imagehl_lib = module_env.SharedLibrarySafe(target='imagehl', source=imagehl_lst) +module_env.Install(INSTALL + '/modules', imagehl_lib) + +imageq2_lst = build_list('plugins/imageq2', 'imageq2.cpp wal.cpp wal32.cpp') +imageq2_lib = module_env.SharedLibrarySafe(target='imageq2', source=imageq2_lst) +module_env.Install(INSTALL + '/modules', imageq2_lib) + +mapq3_env = module_env.Copy() +mapq3_lst=build_list('plugins/mapq3', 'plugin.cpp parse.cpp write.cpp') +mapq3_lib = mapq3_env.SharedLibrarySafe(target='mapq3', source=mapq3_lst, LIBS='cmdlib', LIBPATH='libs') +mapq3_env.Depends(mapq3_lib, cmdlib_lib) +mapq3_env.Install(INSTALL + '/modules', mapq3_lib) + +imagepng_env = module_env.Copy() +imagepng_lst = build_list('plugins/imagepng', 'plugin.cpp') +imagepng_env.usePNG() +imagepng_lib = imagepng_env.SharedLibrarySafe(target='imagepng', source=imagepng_lst) +imagepng_env.Install(INSTALL + '/modules', imagepng_lib) + +mapxml_env = module_env.Copy() +mapxml_lst = build_list('plugins/mapxml', 'plugin.cpp xmlparse.cpp xmlwrite.cpp') +mapxml_lib = mapxml_env.SharedLibrarySafe(target='mapxml', source=mapxml_lst) +mapxml_env.useXML2() +mapxml_env.useGlib2() +mapxml_env.Install(INSTALL + '/modules', mapxml_lib) + +model_env = module_env.Copy() +model_lst = build_list('plugins/model', 'plugin.cpp model.cpp') +model_lib = model_env.SharedLibrarySafe(target='model', source=model_lst, LIBS=['mathlib', 'picomodel'], LIBPATH='libs') +model_env.Depends(model_lib, mathlib_lib) +model_env.Depends(model_lib, picomodel_lib) +model_env.Install(INSTALL + '/modules', model_lib) + +md3model_lst=build_list('plugins/md3model', 'plugin.cpp mdl.cpp md3.cpp md2.cpp mdc.cpp mdlimage.cpp md5.cpp') +md3model_lib = module_env.SharedLibrarySafe(target='md3model', source=md3model_lst) +module_env.Install(INSTALL + '/modules', md3model_lib) + +entity_lst = build_list('plugins/entity', 'plugin.cpp entity.cpp eclassmodel.cpp generic.cpp group.cpp light.cpp miscmodel.cpp doom3group.cpp skincache.cpp angle.cpp angles.cpp colour.cpp filters.cpp model.cpp namedentity.cpp origin.cpp scale.cpp targetable.cpp rotation.cpp modelskinkey.cpp') +entity_lib = module_env.SharedLibrarySafe(target='entity', source=entity_lst) +module_env.Install(INSTALL + '/modules', entity_lib) + +bob_env = module_env.Copy() +bob_lst = build_list('contrib/bobtoolz/', +'dialogs/dialogs-gtk.cpp bobToolz-GTK.cpp bsploader.cpp cportals.cpp DBobView.cpp \ +DBrush.cpp DEntity.cpp DEPair.cpp DMap.cpp DPatch.cpp DPlane.cpp DPoint.cpp \ +DShape.cpp DTrainDrawer.cpp DTreePlanter.cpp DVisDrawer.cpp DWinding.cpp funchandlers-GTK.cpp \ +lists.cpp misc.cpp ScriptParser.cpp shapes.cpp visfind.cpp') +bob_lib = bob_env.SharedLibrarySafe(target='bobtoolz', source=bob_lst, LIBS=['mathlib', 'cmdlib', 'profile'], LIBPATH='libs') +bob_env.Depends(bob_lib, mathlib_lib) +bob_env.Depends(bob_lib, cmdlib_lib) +bob_env.Depends(bob_lib, profile_lib) +bob_env.useGlib2() +bob_env.useGtk2() +bob_env.Install(INSTALL + '/plugins', bob_lib) + +#camera_lst = build_list('contrib/camera', +#'camera.cpp dialogs.cpp dialogs_common.cpp funchandlers.cpp listener.cpp misc.cpp renderer.cpp') +#camera_lst.append('libs/libsplines.a') +#bob_env.SharedLibrarySafe(target='camera', source=camera_lst) +#bob_env.Install(INSTALL + '/plugins', 'camera.so') + +prtview_env = module_env.Copy() +prtview_lst = build_list('contrib/prtview', 'AboutDialog.cpp ConfigDialog.cpp LoadPortalFileDialog.cpp portals.cpp prtview.cpp') +prtview_env.useGlib2() +prtview_env.useGtk2() +prtview_lib = prtview_env.SharedLibrarySafe(target='prtview', source=prtview_lst, LIBS='profile', LIBPATH='libs') +prtview_env.Depends(prtview_lib, profile_lib) +prtview_env.Install(INSTALL + '/plugins', prtview_lib) + +brushexport2_env = module_env.Copy() +brushexport2_lst = build_list('contrib/brushexport', ['plugin.cpp','interface.cpp','callbacks.cpp', 'support.cpp', 'export.cpp']) +brushexport2_env.useGlib2() +brushexport2_env.useGtk2() +brushexport2_lib = brushexport2_env.SharedLibrarySafe(target='brushexport', source=brushexport2_lst, LIBPATH='libs') +brushexport2_env.Install(INSTALL + '/plugins', brushexport2_lib) + +sunplug_env = module_env.Copy() +sunplug_lst = build_list('contrib/sunplug', 'sunplug.cpp') +sunplug_env.useGlib2() +sunplug_env.useGtk2() +sunplug_lib = sunplug_env.SharedLibrarySafe(target='sunplug', source=sunplug_lst, LIBPATH='libs') +sunplug_env.Install(INSTALL + '/plugins', sunplug_lib) + +ufoai_env = module_env.Copy() +ufoai_lst = build_list('contrib/ufoaiplug', 'ufoai.cpp ufoai_filters.cpp ufoai_gtk.cpp ufoai_level.cpp') +ufoai_env.useGlib2() +ufoai_env.useGtk2() +ufoai_lib = ufoai_env.SharedLibrarySafe(target='ufoaiplug', source=ufoai_lst, LIBPATH='libs') +ufoai_env.Install(INSTALL + '/plugins', ufoai_lib) + +shaderplug_env = module_env.Copy() +shaderplug_lst = build_list('contrib/shaderplug', 'shaderplug.cpp') +shaderplug_env.useGlib2() +shaderplug_env.useGtk2() +shaderplug_env.useXML2() +shaderplug_lib = shaderplug_env.SharedLibrarySafe(target='shaderplug', source=shaderplug_lst, LIBS='xmllib', LIBPATH='libs') +shaderplug_env.Install(INSTALL + '/plugins', shaderplug_lib) + +#gensurf_lst = build_list('contrib/gtkgensurf', +#'bitmap.cpp dec.cpp face.cpp font.cpp gendlgs.cpp genmap.cpp gensurf.cpp heretic.cpp plugin.cpp view.cpp triangle.c') +#bob_env.SharedLibrarySafe(target='gensurf', source=gensurf_lst) +#bob_env.Install(INSTALL + '/plugins', 'gensurf.so') + +#bkgrnd2d_list = build_list( 'contrib/bkgrnd2d', 'bkgrnd2d.cpp plugin.cpp dialog.cpp' ) +#bkgrnd2d_list.append( 'libs/libsynapse.a' ) +#bkgrnd2d_env = module_env.Copy() +#bkgrnd2d_env.useGtk2() +#bkgrnd2d_env.SharedLibrarySafe( target='bkgrnd2d', source=bkgrnd2d_list ) +#bkgrnd2d_env.Install( INSTALL + '/plugins', 'bkgrnd2d.so' ) + +radiant_env = g_env.Copy() +radiant_env['CPPPATH'].append('include') +radiant_env['LINKFLAGS'] += '-ldl -lGL ' +if ( OS == 'Darwin' ): + radiant_env['CXXFLAGS'] += '-fno-common ' + radiant_env['CCFLAGS'] += '-fno-common ' + radiant_env['LINKFLAGS'] += '-lX11 -lGL -lGLU ' +radiant_env['LIBPREFIX'] = '' +radiant_env.useGlib2() +radiant_env.useXML2() +radiant_env.useGtk2() +radiant_env.useGtkGLExt() + +radiant_src = [ +'autosave.cpp', +'brush.cpp', +'brushmanip.cpp', +'brushmodule.cpp', +'brushnode.cpp', +'brushtokens.cpp', +'brushxml.cpp', +'brush_primit.cpp', +'build.cpp', +'camwindow.cpp', +'clippertool.cpp', +'commands.cpp', +'console.cpp', +'csg.cpp', +'dialog.cpp', +'eclass.cpp', +'eclass_def.cpp', +'eclass_doom3.cpp', +'eclass_fgd.cpp', +'eclass_xml.cpp', +'entity.cpp', +'entityinspector.cpp', +'entitylist.cpp', +'environment.cpp', +'error.cpp', +'feedback.cpp', +'filetypes.cpp', +'filters.cpp', +'findtexturedialog.cpp', +'glwidget.cpp', +'grid.cpp', +'groupdialog.cpp', +'gtkdlgs.cpp', +'gtkmisc.cpp', +'help.cpp', +'image.cpp', +'main.cpp', +'mainframe.cpp', +'map.cpp', +'mru.cpp', +'nullmodel.cpp', +'parse.cpp', +'patch.cpp', +'patchdialog.cpp', +'patchmanip.cpp', +'patchmodule.cpp', +'plugin.cpp', +'pluginapi.cpp', +'pluginmanager.cpp', +'pluginmenu.cpp', +'plugintoolbar.cpp', +'points.cpp', +'preferencedictionary.cpp', +'preferences.cpp', +'qe3.cpp', +'qgl.cpp', +'referencecache.cpp', +'renderer.cpp', +'renderstate.cpp', +'scenegraph.cpp', +'stacktrace.cpp', +'select.cpp', +'selection.cpp', +'server.cpp', +'shaders.cpp', +'sockets.cpp', +'surfacedialog.cpp', +'texmanip.cpp', +'textures.cpp', +'texwindow.cpp', +'timer.cpp', +'treemodel.cpp', +'undo.cpp', +'url.cpp', +'view.cpp', +'watchbsp.cpp', +'winding.cpp', +'windowobservers.cpp', +'xmlstuff.cpp', +'xywindow.cpp', +] + +for i in range(len(radiant_src)): + radiant_src[i] = 'radiant/' + radiant_src[i] + +radiant_libs = ['mathlib', 'cmdlib', 'l_net', 'profile', 'gtkutil', 'xmllib'] +radiant_prog = radiant_env.Program(target='radiant.' + g_cpu, source=radiant_src, LIBS=radiant_libs, LIBPATH='libs') +radiant_env.Depends(radiant_prog, mathlib_lib) +radiant_env.Depends(radiant_prog, cmdlib_lib) +radiant_env.Depends(radiant_prog, l_net_lib) +radiant_env.Depends(radiant_prog, profile_lib) +radiant_env.Depends(radiant_prog, gtkutil_lib) +radiant_env.Depends(radiant_prog, xml_lib) +radiant_env.Install(INSTALL, radiant_prog) + + +# end setup --------------------------------------------------------------------------------------- diff --git a/SConstruct b/SConstruct new file mode 100644 index 00000000..15d0e6a8 --- /dev/null +++ b/SConstruct @@ -0,0 +1,338 @@ +# scons build script +# http://scons.sourceforge.net + +import commands, re, sys, os, pickle, string, popen2 +from makeversion import radiant_makeversion, get_version + +# to access some internal stuff +import SCons + +conf_filename='site.conf' +# there is a default hardcoded value, you can override on command line, those are saved between runs +# we only handle strings +serialized=['CC', 'CXX', 'JOBS', 'BUILD'] + +# help ------------------------------------------- + +Help(""" +Usage: scons [OPTIONS] [TARGET] [CONFIG] + +[OPTIONS] and [TARGET] are covered in command line options, use scons -H + +[CONFIG]: KEY="VALUE" [...] +a number of configuration options saved between runs in the """ + conf_filename + """ file +erase """ + conf_filename + """ to start with default settings again + +CC +CXX + Specify C and C++ compilers (defaults gcc and g++) + ex: CC="gcc-3.2" + You can use ccache and distcc, for instance: + CC="ccache distcc gcc" CXX="ccache distcc g++" + +JOBS + Parallel build + ex: JOBS="4" is a good setting on SMP machines + +BUILD + Use debug/release to select build settings + ex: BUILD="debug" - default is debug +""" +) + +# end help --------------------------------------- + +# sanity ----------------------------------------- + +# get a recent python release +# that is broken in current version: +# http://sourceforge.net/tracker/index.php?func=detail&aid=794145&group_id=30337&atid=398971 +#EnsurePythonVersion(2,1) +# above 0.90 +EnsureSConsVersion( 0, 96 ) +print 'SCons ' + SCons.__version__ + +# end sanity ------------------------------------- + +# system detection ------------------------------- + +# TODO: detect Darwin / OSX + +# CPU type +g_cpu = commands.getoutput('uname -m') +exp = re.compile('.*i?86.*') +if (g_cpu == 'Power Macintosh' or g_cpu == 'ppc'): + g_cpu = 'ppc' +elif exp.match(g_cpu): + g_cpu = 'x86' +else: + g_cpu = 'cpu' + +# OS +OS = commands.getoutput('uname') +print "OS=\"" + OS + "\"" + +if (OS == 'Linux'): + # libc .. do the little magic! + libc = commands.getoutput('/lib/libc.so.6 |grep "GNU C "|grep version|awk -F "version " \'{ print $2 }\'|cut -b -3') + +# end system detection --------------------------- + +# default settings ------------------------------- + +CC='gcc' +CXX='g++' +JOBS='1' +BUILD='debug' +INSTALL='#install' +g_build_root = 'build' + +# end default settings --------------------------- + +# site settings ---------------------------------- + +site_dict = {} +if (os.path.exists(conf_filename)): + site_file = open(conf_filename, 'r') + p = pickle.Unpickler(site_file) + site_dict = p.load() + print 'Loading build configuration from ' + conf_filename + for k, v in site_dict.items(): + exec_cmd = k + '=\"' + v + '\"' + print exec_cmd + exec(exec_cmd) + +# end site settings ------------------------------ + +# command line settings -------------------------- + +for k in serialized: + if (ARGUMENTS.has_key(k)): + exec_cmd = k + '=\"' + ARGUMENTS[k] + '\"' + print 'Command line: ' + exec_cmd + exec(exec_cmd) + +# end command line settings ---------------------- + +# sanity check ----------------------------------- + + +def GetGCCVersion(name): + ret = commands.getstatusoutput('%s -dumpversion' % name) + if ( ret[0] != 0 ): + return None + vers = string.split(ret[1], '.') + if ( len(vers) == 2 ): + return [ vers[0], vers[1], 0 ] + elif ( len(vers) == 3 ): + return vers + return None + +ver_cc = GetGCCVersion(CC) +ver_cxx = GetGCCVersion(CXX) + +if ( ver_cc is None or ver_cxx is None or ver_cc[0] < '3' or ver_cxx[0] < '3' or ver_cc != ver_cxx ): + print 'Compiler version check failed - need gcc 3.x or later:' + print 'CC: %s %s\nCXX: %s %s' % ( CC, repr(ver_cc), CXX, repr(ver_cxx) ) + Exit(1) + +# end sanity check ------------------------------- + +# save site configuration ---------------------- + +for k in serialized: + exec_cmd = 'site_dict[\'' + k + '\'] = ' + k + exec(exec_cmd) + +site_file = open(conf_filename, 'w') +p = pickle.Pickler(site_file) +p.dump(site_dict) +site_file.close() + +# end save site configuration ------------------ + +# general configuration, target selection -------- + +SConsignFile( "scons.signatures" ) + +g_build = g_build_root + '/' + BUILD + +SetOption('num_jobs', JOBS) + +LINK = CXX +# common flags +warningFlags = '-W -Wall -Wcast-align -Wcast-qual -Wno-unused-parameter ' +warningFlagsCXX = '-Wno-non-virtual-dtor -Wreorder ' # -Wold-style-cast +# POSIX macro: platform supports posix IEEE Std 1003.1:2001 +# XWINDOWS macro: platform supports X-Windows API +CCFLAGS = '-DPOSIX -DXWINDOWS ' + warningFlags +CXXFLAGS = '-pipe -DPOSIX -DXWINDOWS ' + warningFlags + warningFlagsCXX +CPPPATH = [] +if (BUILD == 'debug'): + CXXFLAGS += '-g3 -D_DEBUG ' + CCFLAGS += '-g3 -D_DEBUG ' +elif (BUILD == 'release' or BUILD == 'final'): + CXXFLAGS += '-O2 ' + CCFLAGS += '-O2 ' +else: + print 'Unknown build configuration ' + BUILD + sys.exit( 0 ) + +LINKFLAGS = '' +if ( OS == 'Linux' ): + + if ( BUILD == 'final' ): + # static + # 2112833 /opt/gtkradiant/radiant.x86 + # 35282 /opt/gtkradiant/modules/archivezip.so + # 600099 /opt/gtkradiant/modules/entity.so + + # dynamic + # 2237060 /opt/gtkradiant/radiant.x86 + # 110605 /opt/gtkradiant/modules/archivezip.so + # 730222 /opt/gtkradiant/modules/entity.so + + # EVIL HACK - force static-linking for libstdc++ - create a symbolic link to the static libstdc++ in the root + os.system("ln -s `g++ -print-file-name=libstdc++.a`") + + #if not os.path.exists("./install"): + # os.mkdir("./install") + #os.system("cp `g++ -print-file-name=libstdc++.so` ./install") + + # -fPIC might be worth removing when building for 32-bit x86 + CCFLAGS += '-fPIC ' + CXXFLAGS += '-fPIC -fno-exceptions -fno-rtti ' + LINKFLAGS += '-fPIC -Wl,-fini,fini_stub -L. -static-libgcc ' + +if ( OS == 'Darwin' ): + CCFLAGS += '-force_cpusubtype_ALL -fPIC ' + CXXFLAGS += '-force_cpusubtype_ALL -fPIC -fno-exceptions -fno-rtti ' + CPPPATH.append('/sw/include') + CPPPATH.append('/usr/X11R6/include') + LINKFLAGS += '-L/sw/lib -L/usr/lib -L/usr/X11R6/lib ' + +CPPPATH.append('libs') + +# extend the standard Environment a bit +class idEnvironment(Environment): + + def __init__(self): + Environment.__init__(self, + ENV = os.environ, + CC = CC, + CXX = CXX, + LINK = LINK, + CCFLAGS = CCFLAGS, + CXXFLAGS = CXXFLAGS, + CPPPATH = CPPPATH, + LINKFLAGS = LINKFLAGS) + + def useGlib2(self): + self['CXXFLAGS'] += '`pkg-config glib-2.0 --cflags` ' + self['CCFLAGS'] += '`pkg-config glib-2.0 --cflags` ' + if BUILD == 'final': + self['LINKFLAGS'] += '-lglib-2.0 ' + else: + self['LINKFLAGS'] += '`pkg-config glib-2.0 --libs` ' + + + def useXML2(self): + self['CXXFLAGS'] += '`xml2-config --cflags` ' + self['CCFLAGS'] += '`xml2-config --cflags` ' + if BUILD == 'final': + self['LINKFLAGS'] += '-lxml2 ' + else: + self['LINKFLAGS'] += '`xml2-config --libs` ' + + def useGtk2(self): + self['CXXFLAGS'] += '`pkg-config gtk+-2.0 --cflags` ' + self['CCFLAGS'] += '`pkg-config gtk+-2.0 --cflags` ' + if BUILD == 'final': + self['LINKFLAGS'] += '-lgtk-x11-2.0 -lgdk-x11-2.0 -latk-1.0 -lpango-1.0 -lgdk_pixbuf-2.0 ' + else: + self['LINKFLAGS'] += '`pkg-config gtk+-2.0 --libs-only-L` `pkg-config gtk+-2.0 --libs-only-l` ' + + def useGtkGLExt(self): + self['CXXFLAGS'] += '`pkg-config gtkglext-1.0 --cflags` ' + self['CCFLAGS'] += '`pkg-config gtkglext-1.0 --cflags` ' + #if BUILD == 'final': + self['LINKFLAGS'] += '-lgtkglext-x11-1.0 -lgdkglext-x11-1.0 ' + # apparently pkg-config for gtkglext includes --export-dynamic, which b0rks everything. + #else: + # self['LINKFLAGS'] += 'pkg-config gtkglext-1.0 --libs-only-L` `pkg-config gtkglext-1.0 --libs-only-l` ' + + def usePNG(self): + self['CXXFLAGS'] += '`libpng-config --cflags` ' + self['CCFLAGS'] += '`libpng-config --cflags` ' + self['LINKFLAGS'] += '`libpng-config --ldflags` ' + + def useMHash(self): + self['LINKFLAGS'] += '-lmhash ' + + def useZLib(self): + self['LINKFLAGS'] += '-lz ' + + def usePThread(self): + if ( OS == 'Darwin' ): + self['LINKFLAGS'] += '-lpthread -Wl,-stack_size,0x400000 ' + else: + self['LINKFLAGS'] += '-lpthread ' + + def CheckLDD(self, target, source, env): + file = target[0] + if (not os.path.isfile(file.abspath)): + print('ERROR: CheckLDD: target %s not found\n' % target[0]) + Exit(1) + # not using os.popen3 as I want to check the return code + ldd = popen2.Popen3('`which ldd` -r %s' % target[0], 1) + stdout_lines = ldd.fromchild.readlines() + stderr_lines = ldd.childerr.readlines() + ldd_ret = ldd.wait() + del ldd + have_undef = 0 + if ( ldd_ret != 0 ): + print "ERROR: ldd command returned with exit code %d" % ldd_ret + os.system('rm %s' % target[0]) + Exit() + for i_line in stderr_lines: + print repr(i_line) + regex = re.compile('undefined symbol: (.*)\t\\((.*)\\)\n') + if ( regex.match(i_line) ): + symbol = regex.sub('\\1', i_line) + try: + env['ALLOWED_SYMBOLS'].index(symbol) + except: + have_undef = 1 + else: + print "ERROR: failed to parse ldd stderr line: %s" % i_line + os.system('rm %s' % target[0]) + Exit(1) + if ( have_undef ): + print "ERROR: undefined symbols" + os.system('rm %s' % target[0]) + Exit(1) + + def SharedLibrarySafe(self, target, source, LIBS=[], LIBPATH='.'): + result = self.SharedLibrary(target, source, LIBS=LIBS, LIBPATH=LIBPATH) + if (OS != 'Darwin'): + AddPostAction(target + '.so', self.CheckLDD) + return result + +g_env = idEnvironment() + +# export the globals +GLOBALS = 'g_env INSTALL g_cpu' + +radiant_makeversion('\\ngcc version: %s.%s.%s' % ( ver_cc[0], ver_cc[1], ver_cc[2] ) ) + +# end general configuration ---------------------- + +# targets ---------------------------------------- + +Default('.') + +Export('GLOBALS ' + GLOBALS) +BuildDir(g_build, '.', duplicate = 0) +SConscript(g_build + '/SConscript') + +# end targets ------------------------------------ diff --git a/TODO b/TODO new file mode 100644 index 00000000..c0b6aa51 --- /dev/null +++ b/TODO @@ -0,0 +1,148 @@ + + + +BUGS + +MSI: installer bug with new folders? : create custom dir, click New Folder icon, type "FOLDER\" - gets stuck +GTK2: gtk2 crashes when trying to use bitmap fonts such as MS Sans Serif http://bugzilla.gnome.org/show_bug.cgi?id=142579 +GTK2: alt+tab while mouse button is held down: see http://bugzilla.gnome.org/show_bug.cgi?id=145156 +UI: changing resolution in floating-windows mode can screw up window positions. +HalfLife: half-life maps saved in q1 map format are not supported - currently have to convert them to hammer map format using hammer editor. And vice versa. +Entity: creating a new entity with all the brushes of another entity selected results in the latter entity having no brushes. +SConscript: build fails if SETUP=1 +SConscript: svn.py fails if not using C locale - set LC_ALL? +GUI: can't use arrow keys to navigate in camera view when capslock is enabled +GUI: screensaver causes: gdkgc-win32.c: line 905 (gdk_win32_hdc_get): assertion failed: (win32_gc->hdc == NULL) + + +FEATURES + +- paint-select or equivalent (e.g. area-selection with occlusion) +- select-complete-tall or equivalent (e.g. subtract-from-selection modifier key) +- texture pane names are often illegible, becuase 1. they are long and overlap each other and 2. they overlap the outline rectangles around the images themselves. + + +Build: document build-menu xml format. +The build menu in GtkRadiant 1.5 is entirely customisable - you can make it run qbsp3/qvis3/arghrad or any tool you want. Use 'Build > Customize...' to edit the menu. + +Menu commands are the shell commands that Radiant will execute when you choose the menu item. You can add as many commands as you want to a single menu item, and they will be executed in sequence. The commands contain variables, specified using []. The values of variables will be substituted when the command is executed. + +For example: +
[q2map] -bsp "[MapFile]"
+becomes: +
"C:\Program Files\GtkRadiant 1.5.0\q2map" -fs_basepath "c:\quake2" -bsp "c:\quake2\baseq2\maps\blah.map"
+This uses the predefined variable 'MapFile' and the custom variable 'q2map'. 'q2map' is defined in the XML file, and 'MapFile' is the full path to your map. +The 'MapFile' variable is enclosed in quotes, because the path to your map may contain spaces. +At the moment you can only create custom variables by editing the XML file. A custom variable for arghrad would look something like this: +
"[RadiantPath]arghrad"
+This variable could then be used in a command like this: +
[arghrad] "[MapFile]"
+ +Entity: option to filter non-world entities (e.g. not func_group or func_static) +Rotate Tool: if more than one object is selected, with different local orientations, use parent-space rotation pivot instead of local-space +Brush: MMB+ctrl to paint texture on whole brush/patch. +Camera: add alternative highlighting styles (used to be J). +Doom3: filter func_splinemovers +Entity: draw arrowheads to show direction of connection-lines. +? MMB to select a texture should also apply that texture to all selected faces. +Mouse: support 2-button mouse. +Grid: background colour should be different when the smallest grid is invisible due to being zoomed out. +Brush: option to disable dots on selected faces when not in face mode. +Entity: draw direction arrow for func_door and func_button angle. +Build Menu: support for editing variables. +Shaders: handle doom3 materials with multiple bumpmaps stage - use first stage, ignore later stages. +Brush: warn when a brush is dragged into a configuration with <0 volume +Textures: add option to give new brushes a specific texture instead of the last selected. +? QE-tool: click anywhere on xy view to drag entity instead of requiring clicking directly on entity. +UserDocs: how to use multi-vertex selection - replaces vertex-edit-splits-faces option: +UserDocs: how to use parent-selection: + Parent-selection works like Maya: it allows you to 'reparent' brushes + onto other entities than the one they're currently part of. To use it, + select some brushes, select an entity, Edit -> Parent. +Textures: add anisotropic filtering. +Preferences: allow preference settings to be shared across games. +Preferences: add colour 'theme' files using prefs format. +Preferences: sensible default size for prefs window. +Doom3: add model browser. +Doom3: s_diversity light key. +HalfLife: enable HL-mode on linux/osx. +Renderer: doom3 'parallel' and 'spot' light support. +Entity: add mouse-editing for doom3 light_center key +Shaders: add support for texture transforms. +Shaders: add support for 'addnormals' keyword - e.g. models/mapobjects/healthgui/healthguidirty +TGA Loader: check that true-colour images with palettes are properly handled. +Module System: reinstate 'refresh' feature. +Surface Inspector: add button for 'axial' projection for doom3. +Build: fix hardcoded engine-launch commands - use similar system to build-menu command description. +Filters: use q2/heretic2 content flags to filter brushes. +? Surface Inspector: allow material names not relative to 'textures/' for doom3 +Module System: add versioning for module-system api. +svn: remove install/ dir, create it during build process on win32 +Editing: add option to choose the default startup tool mode. +Renderer: lighting for doom3 materials without bumpmaps (e.g. mcity/mchangar2) +Renderer: realtime doom3 materials preview +Renderer: realtime doom3 shadows preview +Linux: Provide .tar.gz of example-map data for et/wolf. +Textures Window: add inner dark outline to distinguish 'is-shader' outline from white textures. +HalfLife2: add HL2 map load/save. +Selection: add move-pivot mode to allow rotation/scale around a custom pivot-point. +Selection: add rotate increment for rotate manipulator. +Selection: visibly distinguish between entity and brush selections +Selection: need 'add to selection' and 'subtract from selection' modifiers +Selection: Finish scale manipulator. +FaceCopy/PasteTexture: Make face-copy/paste-texture shortcuts customisable. +Manual: add documentation about search paths for .ent/.def/.fgd, shaders etc for each game. +Halflife: add support for cstrike fgd. +HalfLife: disable patches +HalfLife: add HL .mdl model loader. +HalfLife: add HL .spr support. +HalfLife: support fgd 'flags' attributes. +Model: add support for doom3 md5anim format +Model: support doom3 ragdolls +VFS: add ability to browse VFS from file-open dialogs. +Installer: enable q3 brush-primitives map support. +Installer: add editor manual to linux installer +Map: add conversion between map formats +Map: add conversion between entity definition formats +Build: add build-menu dmap support (doom3) +Entity: optionally draw target connection lines thicker than one pixel. +Entity: add specialised attribute-entry in entity-inspector for integer/real/color attribute types. +Patch: add cap-texture, fit-texture and natural-texture toolbar buttons +Patch: draw patches in wireframe from the back, make patches selectable from the back +Patch: add option for convert-selection-to-new-brush/patch +Patch: fix bobtoolz merge-patches feature +Patch: fix insert/remove rows/cols indicated by current selected patch vertices. +Autosave/Snapshots: Add support for multi-file maps. +Quake2: Q2 hint transparency support +Shortcuts: make shortcut list editable within radiant. +Shortcuts: convert shortcuts.ini to xml. +Shortcuts: warn when duplicate shortcuts are registered +Shortcuts: rename commands in order to group shortcuts list better. +upgrade to new API for SymGetModuleInfo - required for compiling with Visual Studio 8.0 +Doom3: lights should stay in place while resizing + + +LOW priority features + +Selection: Add shear manipulator? +Textures Window: Improve texture-manipulation and texture-browsing tools. +Undo: make selections undoable? +Win32 Installer: Automatically upgrade existing installation. +General: refactor game-specific hacks to be parameterised by .game file +Patch: Overlays, Bend Mode, Thicken. +Brush: Add brush-specific plugin API. +Entity: Draw light style numbers. +... Entity: Show models with model2 key. +Entity: Interpret _remap* key (_MindLink_). +Entity: Support _origin _angles _scale on groups. +Selection: Add Primitive-mode shortcut key/button. +Selection: Customisable manipulator size - +/- to change the size of the translate/rotate tool. +Selection: Add optional screen-relative control for constrained rotations. +Clipper: Change selection/manipulation to be consistent with other component editing. +Filtering: Either deselect filtered nodes, or render filtered nodes that are selected. +Filtering: Add customisable filter presets to set/unset multiple filters at once. +Texdef: Make texdef formats abstract, add conversion between texdef formats (use generic affine-texture-matrix format for conversions). +Textures Window: Precise display of texture size when selecting. (tooltip, possibly) +Status: 'Size of brush' display on status bar. +Colours: maya scheme default? +Quake: add support for adjusting gamma on quake palette? diff --git a/contrib/bkgrnd2d/bitmaps/bkgrnd2d_conf.bmp b/contrib/bkgrnd2d/bitmaps/bkgrnd2d_conf.bmp new file mode 100644 index 0000000000000000000000000000000000000000..bc307e2615240b3b2c73eb6ec8b7fa5e7a94cc72 GIT binary patch literal 216 zcmYj|u?@o@6hx26l~V*VfrW(R(lSkuQl(Ex>scsVWCVKv@4*<}2H!VIl!xw455()e zqcU-NMl&}?p_g(sziL@%XC@dkKG%^U0wIQTn&~g47a5;Ch)?Ymg zD^*XHGdYkw*~pe&b=-**co#}Hr6idr#wd|F=Uj{(k!!KnNBX{((<m%UyqgRI`txz u6H)WeeXBitaiT|43EBixr;THd`6dMRoPS;upIH7TGL}73Qtexture_number); + g_free(m_tex); + m_tex = NULL; + } +} + +void CBackgroundImage::Render() +{ + if (!m_bActive || !Valid()) + return; + g_QglTable.m_pfn_qglPushAttrib(GL_ALL_ATTRIB_BITS); + + g_QglTable.m_pfn_qglEnable(GL_TEXTURE_2D); + g_QglTable.m_pfn_qglEnable(GL_BLEND); + g_QglTable.m_pfn_qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + g_QglTable.m_pfn_qglTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + g_QglTable.m_pfn_qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + g_QglTable.m_pfn_qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + + g_QglTable.m_pfn_qglPolygonMode(GL_FRONT,GL_FILL); + // TODO, just so we can tell if we end up going the wrong way + // g_QglTable.m_pfn_qglPolygonMode(GL_BACK,GL_LINE); + // TODO any other state we should not assume ? + + g_QglTable.m_pfn_qglBindTexture(GL_TEXTURE_2D, m_tex->texture_number); + g_QglTable.m_pfn_qglBegin(GL_QUADS); + + g_QglTable.m_pfn_qglColor4f(1.0,1.0,1.0,m_alpha); + g_QglTable.m_pfn_qglTexCoord2f(0.0,1.0); + g_QglTable.m_pfn_qglVertex2f(m_xmin,m_ymin); + + g_QglTable.m_pfn_qglTexCoord2f(1.0,1.0); + g_QglTable.m_pfn_qglVertex2f(m_xmax,m_ymin); + + g_QglTable.m_pfn_qglTexCoord2f(1.0,0.0); + g_QglTable.m_pfn_qglVertex2f(m_xmax,m_ymax); + + g_QglTable.m_pfn_qglTexCoord2f(0.0,0.0); + g_QglTable.m_pfn_qglVertex2f(m_xmin,m_ymax); + + g_QglTable.m_pfn_qglEnd(); + g_QglTable.m_pfn_qglBindTexture(GL_TEXTURE_2D, 0); + + g_QglTable.m_pfn_qglPopAttrib(); +} + +bool CBackgroundImage::Load(const char *filename) +{ + qtexture_t *newtex; + + unsigned char *image = NULL; // gets allocated with what ? g_malloc + int width = 0, height = 0; + + g_FuncTable.m_pfnLoadImage(filename,&image,&width,&height); + + if(!image) { + Syn_Printf(MSG_WARN "load %s failed\n",filename); + return false; + } + +// just in case we want to build for an old version +// http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=900 +#ifdef BKGRND2D_JPG_WORKAROUND + if ( strlen(filename) > 4 && !strcmp(".jpg",filename + strlen(filename) - 4)) { + Syn_Printf(MSG_PREFIX ".jpg workaround, clearing alpha channel\n"); + int size = width*height*4; + int i; + for (i = 3; i < size; i+=4) { + image[i] = 255; + } + } +#endif + + //TODO bug for stored texture size + //TODO whose gl context are we in, anyway ? + newtex = g_FuncTable.m_pfnLoadTextureRGBA(image,width,height); + + g_free(image); + + if(!newtex) { + Syn_Printf(MSG_WARN "image to texture failed\n"); + return false; + } + + Cleanup(); + m_tex = newtex; + + g_FuncTable.m_pfnSysUpdateWindows(W_XY); + + return true; +} + +bool CBackgroundImage::SetExtentsMM() +{ + entity_s *worldentity; + const char *val; + int xmin = 0, ymin = 0, xmax = 0, ymax = 0; + + worldentity = (entity_s *)g_FuncTable.m_pfnGetEntityHandle(0); + if(!worldentity) { + Syn_Printf(MSG_WARN "SetExtentsMM worldspawn not found\n"); + return false; + } + //TODO val is not NULL even if key does not exist + val = g_EntityTable.m_pfnValueForKey(worldentity,"mapcoordsmins"); + if(!val || !val[0]) { + Syn_Printf(MSG_WARN "SetExtentsMM mapcoordsmins not found\n"); + return false; + } +// we could be more robust +// note contortions due to splashs strange idea of min and max + if(sscanf(val, "%d %d",&xmin,&ymax) != 2) + { + Syn_Printf(MSG_WARN "SetExtentsMM mapcoordsmins malformed\n"); + return false; + } + + val = g_EntityTable.m_pfnValueForKey(worldentity,"mapcoordsmaxs"); + if(!val || !val[0]) { + Syn_Printf(MSG_WARN "SetExtentsMM mapcoordsmaxs not found\n"); + return false; + } + if(sscanf(val, "%d %d",&xmax,&ymin) != 2) + { + Syn_Printf(MSG_WARN "SetExtentsMM mapcoordsmaxs malformed\n"); + return false; + } + //might do sanity check before we commit + m_xmin = (float)xmin; + m_ymin = (float)ymin; + m_xmax = (float)xmax; + m_ymax = (float)ymax; + + g_FuncTable.m_pfnSysUpdateWindows(W_XY); + return true; +} + +// TODO, this should just be exported from core +// ripped directly from radiant/select.cpp:Select_GetBounds +// +static bool get_selection_bounds (vec3_t mins, vec3_t maxs) +{ + brush_t *b; + int i; + brush_t *selected_brushes = g_DataTable.m_pfnSelectedBrushes(); + //TODO should never happen + if(!selected_brushes) { + Sys_Printf (MSG_PREFIX "selected_brushes = NULL\n"); + return false; + } + // this should mean no selection + if(selected_brushes == selected_brushes->next) { + Sys_Printf (MSG_PREFIX "nothing selected\n"); + + return false; + } + + for (i=0 ; i<3 ; i++) + { + mins[i] = 99999; + maxs[i] = -99999; + } + + for (b=selected_brushes->next ; b != selected_brushes ; b=b->next) + { + if (b->owner->eclass->fixedsize) + { + for (i=0 ; i<3 ; i++) + { + if (b->owner->origin[i] < mins[i]) + mins[i] = b->owner->origin[i]; + if (b->owner->origin[i] > maxs[i]) + maxs[i] = b->owner->origin[i]; + } + } + else + { + for (i=0 ; i<3 ; i++) + { + if (b->mins[i] < mins[i]) + mins[i] = b->mins[i]; + if (b->maxs[i] > maxs[i]) + maxs[i] = b->maxs[i]; + } + } + } + return true; +} + +bool CBackgroundImage::SetExtentsSel() +{ + vec3_t mins,maxs; + + if(!get_selection_bounds(mins,maxs)) + return false; + + if(((int)mins[m_ix] == (int)maxs[m_ix]) || + ((int)mins[m_iy] == (int)maxs[m_iy])) { + Syn_Printf(MSG_PREFIX "tiny selection\n"); + return false; + } + + m_xmin = mins[m_ix]; + m_ymin = mins[m_iy]; + m_xmax = maxs[m_ix]; + m_ymax = maxs[m_iy]; + + g_FuncTable.m_pfnSysUpdateWindows(W_XY); + + return true; +} + diff --git a/contrib/bkgrnd2d/bkgrnd2d.def b/contrib/bkgrnd2d/bkgrnd2d.def new file mode 100644 index 00000000..36c4457f --- /dev/null +++ b/contrib/bkgrnd2d/bkgrnd2d.def @@ -0,0 +1,8 @@ +; bkgrnd2d.def : Declares the module parameters for the DLL. + +LIBRARY "BKGRND2D" +DESCRIPTION 'BKGRND2D Windows Dynamic Link Library' + +EXPORTS + ; Explicit exports can go here + Synapse_EnumerateInterfaces @1 diff --git a/contrib/bkgrnd2d/bkgrnd2d.h b/contrib/bkgrnd2d/bkgrnd2d.h new file mode 100644 index 00000000..3874cb19 --- /dev/null +++ b/contrib/bkgrnd2d/bkgrnd2d.h @@ -0,0 +1,82 @@ +/* +Copyright (C) 2003 Reed Mideke. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// bkgrnd2d Plugin +// +// Code by reyalP aka Reed Mideke +// +// Based on spritemodel source code by hydra +// + +#include "plugin.h" + +class CBackgroundImage { +private: + qtexture_t *m_tex; + VIEWTYPE m_vt; + +// which components of a vec3_t correspond to x and y in the image + unsigned m_ix,m_iy; + +public: + CBackgroundImage(VIEWTYPE vt); +// ~CBackgroundImage(); + + float m_alpha; // vertex alpha + bool m_bActive; + +// x and y axis are in relation to the screen, not world, making rendering +// the same for each view type. Whoever sets them is responsible for +// shuffling. +// units are world units. +// TODO should be private + float m_xmin,m_ymin,m_xmax,m_ymax; + +// load file, create new tex, cleanup old tex, set new tex + bool Load(const char *filename); + void Cleanup(); // free texture, free tex, set make tex NULL + bool SetExtentsMM(); // set extents by ET mapcoordsmaxs/mapcoordsmins + bool SetExtentsSel(); // set extents by selection + void Render(); + bool Valid() { return (m_tex && (m_xmin != m_xmax) && (m_ymin != m_ymax)); } +}; + +class CBackgroundRender : public IGL2DWindow { +public: + + CBackgroundRender(); + virtual ~CBackgroundRender(); + +protected: + int refCount; + +public: + + // IGL2DWindow IGL3DWindow interface + void IncRef() { refCount++; } + void DecRef() { refCount--; if (refCount <= 0) delete this; } + void Draw2D( VIEWTYPE vt ); + void Register(); +}; + +extern CBackgroundImage backgroundXY,backgroundXZ,backgroundYZ; +extern CBackgroundRender render; + diff --git a/contrib/bkgrnd2d/bkgrnd2d.vcproj b/contrib/bkgrnd2d/bkgrnd2d.vcproj new file mode 100644 index 00000000..f2a538a0 --- /dev/null +++ b/contrib/bkgrnd2d/bkgrnd2d.vcproj @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/bkgrnd2d/dialog.cpp b/contrib/bkgrnd2d/dialog.cpp new file mode 100644 index 00000000..8d9a8455 --- /dev/null +++ b/contrib/bkgrnd2d/dialog.cpp @@ -0,0 +1,364 @@ +/* +Copyright (C) 2003 Reed Mideke. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// bkgrnd2d Plugin dialog +// +// Code by reyalP aka Reed Mideke +// +// Based on various other plugins +// + +#include + +#include "bkgrnd2d.h" +#include "dialog.h" + +// spaces to make label nice and big +#define NO_FILE_MSG " (no file loaded) " + +static GtkWidget *pDialogWnd; +static GtkWidget *pNotebook; +static GtkTooltips *pTooltips; + +class CBackgroundDialogPage +{ +private: + GtkWidget *m_pWidget; + GtkWidget *m_pTabLabel; + GtkWidget *m_pFileLabel; + GtkWidget *m_pPosLabel; + VIEWTYPE m_vt; + bool m_bValidFile; + +public: + CBackgroundImage *m_pImage; + CBackgroundDialogPage( VIEWTYPE vt ); + void Append(GtkWidget *notebook); + void Browse(); + void Reload(); + void SetPosLabel(); +// ~BackgroundDialogPage(); +}; + + +// dialog page callbacks +static void browse_callback( GtkWidget *widget, gpointer data ) +{ + ((CBackgroundDialogPage *)data)->Browse(); +} + +static void reload_callback( GtkWidget *widget, gpointer data ) +{ + ((CBackgroundDialogPage *)data)->Reload(); +} + +static void size_sel_callback( GtkWidget *widget, gpointer data ) +{ + CBackgroundDialogPage *pPage = (CBackgroundDialogPage *)data; + if (pPage->m_pImage->SetExtentsSel()) + pPage->SetPosLabel(); +} + +static void size_mm_callback( GtkWidget *widget, gpointer data ) +{ + CBackgroundDialogPage *pPage = (CBackgroundDialogPage *)data; + if(pPage->m_pImage->SetExtentsMM()) + pPage->SetPosLabel(); +} + +static void alpha_adjust_callback( GtkWidget *widget, gpointer data ) +{ + CBackgroundDialogPage *pPage = (CBackgroundDialogPage *)data; + pPage->m_pImage->m_alpha = (float)gtk_range_get_value (GTK_RANGE(widget)); + g_FuncTable.m_pfnSysUpdateWindows(W_XY); +} + +void CBackgroundDialogPage::Reload() +{ + if(m_bValidFile) + m_pImage->Load(gtk_label_get_text(GTK_LABEL(m_pFileLabel))); +} + +void CBackgroundDialogPage::Browse() +{ + char browsedir[PATH_MAX]; + const char *ct; + const char *newfile; + char *t; + + //TODO GetMapName saves the map. eeep! + //also with no map, returns unnamed.map, otherwise returns full path +// Syn_Printf(MSG_PREFIX "GetMapName() %s\n", +// g_FuncTable.m_pfnGetMapName()); + + ct = g_FuncTable.m_pfnReadProjectKey("basepath"); + // TODO shouldn't need this stuff + if(!ct || !strlen(ct)) { + Syn_Printf(MSG_PREFIX "basepath = NULL or empty\n"); + return; + } + Syn_Printf(MSG_PREFIX "basepath: %s\n",ct); + if(strlen(ct) >= PATH_MAX) { + Syn_Printf(MSG_PREFIX "base game dir too long\n"); + return; + } + + strcpy(browsedir,ct); + // make sure we have a trailing / + if(browsedir[strlen(browsedir) - 1] != '/') + strcat(browsedir,"/"); + + //if we dont have a file yet, don't try to use it for default dir + if(m_bValidFile) { + // filename should always be a nice clean unix style relative path + ct = gtk_label_get_text(GTK_LABEL(m_pFileLabel)); + strcat(browsedir,ct); + Syn_Printf(MSG_PREFIX "full path: %s\n",browsedir); + + // lop off the file part + t = browsedir + strlen(browsedir) - 1; + while (t != browsedir && *t != '/') + t--; + *t = 0; + } + Syn_Printf(MSG_PREFIX "browse directory %s\n",browsedir); + +//does NOT need freeing contrary to include/qerplugin.h comments +//TODO bug/patch for comments +//TODO patern gets fucked up sometimes if empty +//http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=915 + newfile = g_FuncTable.m_pfnFileDialog(pDialogWnd,TRUE, + "Load Background Image",browsedir,FILETYPE_KEY); + if(!newfile) { + Syn_Printf(MSG_PREFIX "newfile = NULL\n"); + return; + } + Syn_Printf(MSG_PREFIX "newfile: %s\n",newfile); + newfile = g_FileSystemTable.m_pfnExtractRelativePath(newfile); + + if(!newfile) { + Syn_Printf(MSG_PREFIX "newfile = NULL\n"); + return; + } + Syn_Printf(MSG_PREFIX "newfile: %s\n",newfile); + + if(m_pImage->Load(newfile)) { + m_bValidFile = true; + gtk_label_set_text(GTK_LABEL(m_pFileLabel),newfile); + } +} + +void CBackgroundDialogPage::SetPosLabel() +{ + char s[64]; + // TODO no snprintf ? + sprintf(s, "Size/Position (%d,%d) (%d,%d)",(int)(m_pImage->m_xmin), + (int)(m_pImage->m_ymin),(int)(m_pImage->m_xmax),(int)(m_pImage->m_ymax)); + gtk_label_set_text(GTK_LABEL(m_pPosLabel),s); +} + +CBackgroundDialogPage::CBackgroundDialogPage(VIEWTYPE vt ) +{ + GtkWidget *frame; + GtkWidget *hbox; + GtkWidget *w; + + m_vt = vt; + + m_bValidFile = false; + + switch(m_vt) + { + case XY: + m_pTabLabel = gtk_label_new("X/Y"); + m_pImage = &backgroundXY; + break; + case XZ: + m_pTabLabel = gtk_label_new("X/Z"); + m_pImage = &backgroundXZ; + break; + case YZ: + m_pTabLabel = gtk_label_new("Y/Z"); + m_pImage = &backgroundYZ; + break; + } +// A vbox to hold everything + m_pWidget = gtk_vbox_new(FALSE,0); +// Frame for file row + frame = gtk_frame_new("File"); + gtk_box_pack_start (GTK_BOX (m_pWidget),frame, FALSE, FALSE, 2); + +// hbox for first row + hbox = gtk_hbox_new(FALSE,5); + gtk_container_set_border_width(GTK_CONTAINER (hbox),4); + gtk_container_add (GTK_CONTAINER (frame), hbox); + +// label to display filename + m_pFileLabel = gtk_label_new(NO_FILE_MSG); + gtk_label_set_selectable(GTK_LABEL(m_pFileLabel),TRUE); +//TODO set min size ? done with spaces right now + gtk_box_pack_start (GTK_BOX (hbox),m_pFileLabel, TRUE, TRUE, 5); + + gtk_widget_show (m_pFileLabel); + + w = gtk_button_new_with_label ("Browse..."); + g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK (browse_callback), + (gpointer)this); + gtk_box_pack_start (GTK_BOX (hbox),w, FALSE, FALSE, 5); + gtk_tooltips_set_tip (pTooltips, w, "Select a file", NULL); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Reload"); + g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK (reload_callback), + (gpointer)this); + // TODO disable until we have file + // gtk_widget_set_sensitive(w,FALSE); + gtk_tooltips_set_tip (pTooltips, w, "Reload current file", NULL); + gtk_box_pack_start (GTK_BOX (hbox),w, FALSE, FALSE, 5); + gtk_widget_show (w); + + gtk_widget_show (hbox); + gtk_widget_show (frame); + +// second row (rendering options) + frame = gtk_frame_new("Rendering"); + gtk_box_pack_start (GTK_BOX (m_pWidget),frame, FALSE, FALSE, 2); + + hbox = gtk_hbox_new(FALSE,5); + gtk_container_set_border_width(GTK_CONTAINER (hbox),4); + gtk_container_add (GTK_CONTAINER (frame), hbox); + + w = gtk_label_new("Vertex alpha:"); + gtk_box_pack_start (GTK_BOX (hbox),w, FALSE, FALSE, 5); + gtk_widget_show (w); + + w = gtk_hscale_new_with_range(0.0,1.0,0.01); + gtk_range_set_value(GTK_RANGE(w),0.5); + gtk_scale_set_value_pos(GTK_SCALE(w),GTK_POS_LEFT); + g_signal_connect (G_OBJECT (w), "value-changed", + G_CALLBACK (alpha_adjust_callback), (gpointer)this); + gtk_box_pack_start (GTK_BOX (hbox),w, TRUE, TRUE, 5); + gtk_tooltips_set_tip (pTooltips, w, "Set image transparancy", NULL); + gtk_widget_show (w); + + gtk_widget_show (hbox); + gtk_widget_show (frame); +// Third row (size and position) + frame = gtk_frame_new("Size/Position (undefined)"); + m_pPosLabel = gtk_frame_get_label_widget (GTK_FRAME(frame)); + gtk_box_pack_start ( GTK_BOX (m_pWidget), frame, FALSE, FALSE, 2); + + hbox = gtk_hbox_new(FALSE,5); + gtk_container_add (GTK_CONTAINER (frame), hbox); + gtk_container_set_border_width(GTK_CONTAINER (hbox),4); + + w = gtk_button_new_with_label ("from selection"); + gtk_box_pack_start (GTK_BOX (hbox),w, TRUE, FALSE, 5); + g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK (size_sel_callback), + (gpointer)this); + gtk_tooltips_set_tip (pTooltips, w, "Set the size of the image to the bounding rectangle of all selected brushes and entities", NULL); + gtk_widget_show (w); + + if(m_vt == XY) { + w = gtk_button_new_with_label ("from map mins/maxs"); + gtk_box_pack_start ( GTK_BOX (hbox),w, TRUE, FALSE, 2); + g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK (size_mm_callback), + (gpointer)this); + gtk_tooltips_set_tip (pTooltips, w, "Set the size of the image using the mapcoordsmins and mapcoordsmaxs keys of the worldspawn entity", NULL); + gtk_widget_show (w); + } + + gtk_widget_show (hbox); + gtk_widget_show (frame); + + gtk_widget_show ( m_pWidget ); +} + +void CBackgroundDialogPage::Append(GtkWidget *notebook) +{ + gtk_notebook_append_page( GTK_NOTEBOOK(notebook), m_pWidget, m_pTabLabel); +} + +// dialog global callbacks +/* +static gint expose_callback( GtkWidget *widget, gpointer data ) +{ + return FALSE; +} +*/ + +static void response_callback( GtkWidget *widget, gint response, gpointer data ) +{ + if( response == GTK_RESPONSE_CLOSE ) + gtk_widget_hide( pDialogWnd ); +} + +static gint close_callback( GtkWidget *widget, gpointer data ) +{ + gtk_widget_hide( pDialogWnd ); + return TRUE; +} + +void InitBackgroundDialog() +{ + CBackgroundDialogPage *pPage; + + pDialogWnd = gtk_dialog_new_with_buttons ("Background Images", + GTK_WINDOW(g_pMainWidget), + (GtkDialogFlags)(GTK_DIALOG_DESTROY_WITH_PARENT), + // TODO dialog with no buttons + // GTK_STOCK_CLOSE, + // GTK_RESPONSE_CLOSE, + NULL); + gtk_signal_connect( GTK_OBJECT (pDialogWnd), "delete_event", + GTK_SIGNAL_FUNC( close_callback ), NULL ); + gtk_signal_connect( GTK_OBJECT (pDialogWnd), "response", + GTK_SIGNAL_FUNC( response_callback ), NULL ); +// gtk_signal_connect( GTK_OBJECT (pDialogWnd), "expose_event", GTK_SIGNAL_FUNC( ci_expose ), NULL ); + + pTooltips = gtk_tooltips_new(); + + pNotebook = gtk_notebook_new(); + pPage = new CBackgroundDialogPage(XY); + pPage->Append(pNotebook); + pPage = new CBackgroundDialogPage(XZ); + pPage->Append(pNotebook); + pPage = new CBackgroundDialogPage(YZ); + pPage->Append(pNotebook); + + gtk_box_pack_start (GTK_BOX (GTK_DIALOG(pDialogWnd)->vbox), pNotebook, TRUE, TRUE, 0); + + gtk_widget_show ( pNotebook ); + + gtk_widget_realize( pDialogWnd ); +} + +void ShowBackgroundDialog() +{ + gtk_window_present( GTK_WINDOW(pDialogWnd) ); +} + +void ShowBackgroundDialogPG(int page) +{ + gtk_notebook_set_current_page(GTK_NOTEBOOK(pNotebook),page); + ShowBackgroundDialog(); +} + diff --git a/contrib/bkgrnd2d/dialog.h b/contrib/bkgrnd2d/dialog.h new file mode 100644 index 00000000..03af2078 --- /dev/null +++ b/contrib/bkgrnd2d/dialog.h @@ -0,0 +1,35 @@ +/* +Copyright (C) 2003 Reed Mideke. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// bkgrnd2d Plugin dialog box +// +// Code by reyalP aka Reed Mideke +// +// + +#ifndef _BKGRND2D_DIALOG_H_ +#define _BKGRND2D_DIALOG_H_ + +void InitBackgroundDialog(); +void ShowBackgroundDialog(); +void ShowBackgroundDialogPG(int page); + +#endif // _BKGRND2D_DIALOG_H_ diff --git a/contrib/bkgrnd2d/plugin.cpp b/contrib/bkgrnd2d/plugin.cpp new file mode 100644 index 00000000..09e9ed7c --- /dev/null +++ b/contrib/bkgrnd2d/plugin.cpp @@ -0,0 +1,319 @@ +/* +Copyright (C) 2003 Reed Mideke. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// 2d background Plugin +// +// Code by reyalP aka Reed Mideke +// +// Based on +// + +/* + Overview + ======== + This little plugin allows you to display an image in the background of the + gtkradiant XY window. + + Version History + =============== + + v0.1 + - Initial version. + v0.2 + - three views, dialog box, toolbar + v0.25 + - tooltips, follow gtkradiant coding conventions + + Why ? + ----- + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=88 + + + How ? + ----- + - textures 'n widgets 'n stuff. +*/ + +//#include "plugin.h" +//TODO we just poke the objects directly +#include "bkgrnd2d.h" +#include "dialog.h" + +#define CMD_SEP "-" +#define CMD_CONFIG "Configure..." +#define CMD_ABOUT "About..." +// ============================================================================= +// Globals + +// function tables +_QERFuncTable_1 g_FuncTable; +_QERQglTable g_QglTable; +_QERFileSystemTable g_FileSystemTable; +_QEREntityTable g_EntityTable; +_QERAppDataTable g_DataTable; + +// for the file load dialog +void *g_pMainWidget; + +// ============================================================================= +// plugin implementation + +static const char *PLUGIN_NAME = "2d window background plugin"; + +//backwards for some reason +static const char *PLUGIN_COMMANDS = CMD_ABOUT ";" + CMD_SEP ";" + CMD_CONFIG + ; + +static const char *PLUGIN_ABOUT = "2d window background v0.25\n\n" + "By reyalP (hellsownpuppy@yahoo.com)"; + + + + +void DoBkgrndToggleXY(); +void DoBkgrndToggleXZ(); +void DoBkgrndToggleYZ(); + +#define NUM_TOOLBAR_BUTTONS 4 +struct toolbar_button_info_s +{ + char *image; + char *text; + char *tip; + void (*func)(); + IToolbarButton::EType type; +}; + +struct toolbar_button_info_s toolbar_buttons[NUM_TOOLBAR_BUTTONS] = +{ + { + "bkgrnd2d_xy_toggle.bmp", + "xy background", + "Toggle xy background image", + DoBkgrndToggleXY, + IToolbarButton::eToggleButton + }, + { + "bkgrnd2d_xz_toggle.bmp", + "xz background", + "Toggle xz background image", + DoBkgrndToggleXZ, + IToolbarButton::eToggleButton + }, + { + "bkgrnd2d_yz_toggle.bmp", + "yz background", + "Toggle yz background image", + DoBkgrndToggleYZ, + IToolbarButton::eToggleButton + }, + { + "bkgrnd2d_conf.bmp", + "Configure", + "Configure background images", + ShowBackgroundDialog, + IToolbarButton::eButton + }, +}; + +class Bkgrnd2dButton : public IToolbarButton +{ +public: + const toolbar_button_info_s *bi; + virtual const char* getImage() const + { + return bi->image; + } + virtual const char* getText() const + { + return bi->text; + } + virtual const char* getTooltip() const + { + return bi->tip; + } + virtual void activate() const + { + bi->func(); + return ; + } + virtual EType getType() const + { + return bi->type; + } +}; + +Bkgrnd2dButton g_bkgrnd2dbuttons[NUM_TOOLBAR_BUTTONS]; + +unsigned int ToolbarButtonCount() +{ + return NUM_TOOLBAR_BUTTONS; +} + +const IToolbarButton* GetToolbarButton(unsigned int index) +{ + g_bkgrnd2dbuttons[index].bi = &toolbar_buttons[index]; + return &g_bkgrnd2dbuttons[index]; +} + +extern "C" const char* QERPlug_Init (void *hApp, void* pMainWidget) +{ + g_pMainWidget = pMainWidget; + + InitBackgroundDialog(); + render.Register(); + +//TODO is it right ? is it wrong ? it works +//TODO figure out supported image types + GetFileTypeRegistry()->addType(FILETYPE_KEY, filetype_t("all files", "*.*")); + GetFileTypeRegistry()->addType(FILETYPE_KEY, filetype_t("jpeg files", "*.jpg")); + GetFileTypeRegistry()->addType(FILETYPE_KEY, filetype_t("targa files", "*.tga")); + return (char *) PLUGIN_NAME; +} + +extern "C" const char* QERPlug_GetName () +{ + return (char *) PLUGIN_NAME; +} + +extern "C" const char* QERPlug_GetCommandList () +{ + return (char *) PLUGIN_COMMANDS; +} + +extern "C" void QERPlug_Dispatch (const char *p, vec3_t vMin, vec3_t vMax, bool bSingleBrush) +{ + Sys_Printf (MSG_PREFIX "Command \"%s\"\n",p); + if(!strcmp(p, CMD_ABOUT)) { + g_FuncTable.m_pfnMessageBox(NULL, PLUGIN_ABOUT, "About", MB_OK, NULL); + } + else if(!strcmp(p,CMD_CONFIG)) { + ShowBackgroundDialog(); + } +} + +//TODO these three suck +void DoBkgrndToggleXY() +{ + Sys_Printf (MSG_PREFIX "DoBkgrndToggleXY\n"); + // always toggle, since the buttons do + backgroundXY.m_bActive = (backgroundXY.m_bActive) ? false:true; + // if we don't have image or extents, and we activated, + // bring up the dialog with the corresponding page + // would be better to hide or grey out button, but we can't + if(backgroundXY.m_bActive && !backgroundXY.Valid()) + ShowBackgroundDialogPG(0); + else + g_FuncTable.m_pfnSysUpdateWindows(W_XY); +} + +void DoBkgrndToggleXZ() +{ + Sys_Printf (MSG_PREFIX "DoBkgrndToggleXZ\n"); + backgroundXZ.m_bActive = (backgroundXZ.m_bActive) ? false:true; + if(backgroundXZ.m_bActive && !backgroundXZ.Valid()) + ShowBackgroundDialogPG(1); + else + g_FuncTable.m_pfnSysUpdateWindows(W_XY); +} + +void DoBkgrndToggleYZ() +{ + Sys_Printf (MSG_PREFIX "DoBkgrndToggleYZ\n"); + backgroundYZ.m_bActive = (backgroundYZ.m_bActive) ? false:true; + if(backgroundYZ.m_bActive && !backgroundYZ.Valid()) + ShowBackgroundDialogPG(2); + else + g_FuncTable.m_pfnSysUpdateWindows(W_XY); +} + +// ============================================================================= +// SYNAPSE + +CSynapseServer* g_pSynapseServer = NULL; +CSynapseClientBkgrnd2d g_SynapseClient; + +extern "C" CSynapseClient* SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces (const char *version, CSynapseServer *pServer) +{ + if (strcmp(version, SYNAPSE_VERSION)) + { + Syn_Printf("ERROR: synapse API version mismatch: should be '" SYNAPSE_VERSION "', got '%s'\n", version); + return NULL; + } + g_pSynapseServer = pServer; + g_pSynapseServer->IncRef(); + Set_Syn_Printf(g_pSynapseServer->Get_Syn_Printf()); + + g_SynapseClient.AddAPI(TOOLBAR_MAJOR, BKGRND2D_MINOR, sizeof(_QERPlugToolbarTable)); + g_SynapseClient.AddAPI(PLUGIN_MAJOR, BKGRND2D_MINOR, sizeof( _QERPluginTable ) ); + + g_SynapseClient.AddAPI( RADIANT_MAJOR, NULL, sizeof( g_FuncTable ), SYN_REQUIRE, &g_FuncTable ); + g_SynapseClient.AddAPI( QGL_MAJOR, NULL, sizeof( g_QglTable ), SYN_REQUIRE, &g_QglTable ); +// TODO is this the right way to ask for 'whichever VFS we have loaded' ? Seems to work +// for misc filename functions + g_SynapseClient.AddAPI( VFS_MAJOR, "*", sizeof( g_FileSystemTable ), SYN_REQUIRE, &g_FileSystemTable ); +// get worldspawn + g_SynapseClient.AddAPI( ENTITY_MAJOR, NULL, sizeof( g_EntityTable ), SYN_REQUIRE, &g_EntityTable ); +// selected brushes + g_SynapseClient.AddAPI( DATA_MAJOR, NULL, sizeof( g_DataTable ), SYN_REQUIRE, &g_DataTable ); + + return &g_SynapseClient; +} + +bool CSynapseClientBkgrnd2d::RequestAPI(APIDescriptor_t *pAPI) +{ + if (!strcmp(pAPI->major_name, PLUGIN_MAJOR)) + { + _QERPluginTable* pTable= static_cast<_QERPluginTable*>(pAPI->mpTable); + + pTable->m_pfnQERPlug_Init = QERPlug_Init; + pTable->m_pfnQERPlug_GetName = QERPlug_GetName; + pTable->m_pfnQERPlug_GetCommandList = QERPlug_GetCommandList; + pTable->m_pfnQERPlug_Dispatch = QERPlug_Dispatch; + return true; + } + if (!strcmp(pAPI->major_name, TOOLBAR_MAJOR)) + { + _QERPlugToolbarTable* pTable= static_cast<_QERPlugToolbarTable*>(pAPI->mpTable); + + pTable->m_pfnToolbarButtonCount = &ToolbarButtonCount; + pTable->m_pfnGetToolbarButton = &GetToolbarButton; + return true; + } + + Syn_Printf("ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo()); + return false; +} + +#include "version.h" + +const char* CSynapseClientBkgrnd2d::GetInfo() +{ + return "2d Background plugin built " __DATE__ " " RADIANT_VERSION; +} + +const char* CSynapseClientBkgrnd2d::GetName() +{ + return "bkgrnd2d"; +} + diff --git a/contrib/bkgrnd2d/plugin.h b/contrib/bkgrnd2d/plugin.h new file mode 100644 index 00000000..30b0ca7d --- /dev/null +++ b/contrib/bkgrnd2d/plugin.h @@ -0,0 +1,79 @@ +/* +Copyright (C) 2003 Reed Mideke. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// bkgrnd2d Plugin +// +// Code by reyalP aka Reed Mideke +// +// Based on spritemodel source code by hydra +// + +#ifndef _PLUGIN_H_ +#define _PLUGIN_H_ + +/*! +\todo need general notice about lib purpose etc. +and the external dependencies (such as GLib, STL, mathlib etc.) +*/ + +#include +// for CPtrArray for idata.h +#include "missing.h" + +#include "synapse.h" +#include "iplugin.h" +#include "itoolbar.h" +#define USE_QERTABLE_DEFINE +#include "qerplugin.h" +#include "igl.h" +#include "ifilesystem.h" +#include "ientity.h" +#include "idata.h" + +// verbose messages +#define BKGRND2D_DEBUG + +extern _QERFuncTable_1 g_FuncTable; +extern _QERQglTable g_QglTable; +extern _QERFileSystemTable g_FileSystemTable; +extern _QEREntityTable g_EntityTable; +extern _QERAppDataTable g_DataTable; +extern void *g_pMainWidget; + +extern CSynapseServer* g_pSynapseServer; + +class CSynapseClientBkgrnd2d : public CSynapseClient +{ +public: + // CSynapseClient API + bool RequestAPI(APIDescriptor_t *pAPI); + const char* GetInfo(); + const char* GetName(); + + CSynapseClientBkgrnd2d() { } + virtual ~CSynapseClientBkgrnd2d() { } +}; +#define MSG_PREFIX "bkgrnd2d: " +#define MSG_WARN "bkgrnd2d WARNING: " +#define BKGRND2D_MINOR "bkgrnd2d" +#define FILETYPE_KEY "bkgrnd2d" + +#endif // _PLUGIN_H_ diff --git a/contrib/bkgrnd2d/readme_bkgrnd2d-b0.25.txt b/contrib/bkgrnd2d/readme_bkgrnd2d-b0.25.txt new file mode 100644 index 00000000..6184245e --- /dev/null +++ b/contrib/bkgrnd2d/readme_bkgrnd2d-b0.25.txt @@ -0,0 +1,131 @@ +November 25 2003 +bkgrnd2d v 0.25 beta for radiant 1.3.13 +by SCDS_reyalP (email hellsownpuppy@yahoo.com) + +WARNING: +This is an beta release. It is provided with absolutely NO WARRANTY. +If it turns your data to mush and melts your CPU, don't blame me. + +Overview: +This little plugin allows you to display an image in the the gtkradiant 2d +windows. This is useful for layout sketches, maps made from +existing plans, building geometry based on photgraphs, viewing terrain +alphamaps in relation to your terrain, and so on. + +Installation: +extract the .dll and bitmaps into your gtkradiant/plugins directory, and +restart radiant. Be sure to use directory names, to ensure the bitmaps go +in your plugins/bitmaps directory. + +Uninstallation: +Close radiant, delete the bkgrnd2d.dll from the plugins directory, and +delete the bkgrnd2*.bmp files from the plugins/bitmaps directory. + +User Interface: +- The plugin adds 4 buttons to the radiant plugin toolbar. The first 3 + toggle the display of a background image in the specified view. The fourth + brings up a configuration dialog. The configuration dialog can also be + opened from the plugins menu. + +- If an image has not been loaded, or it's display size and location have + not been set, pushing one of the toggle buttons will bring up the dialog + with the corresponding page selected. + +- The configuration dialog is non-modal, meaning that you can leave it open + while you work in radiant. If it gets lost behind another window, clicking + on the configuration button will bring it to the forground. + +Usage: +- bring up the configuration dialog. + +- Choose the "Browse..." button. This will prompt you for an image file. + The file *MUST* be inside your basegame directory (baseq3, main, etmain or + whatever your chosen game uses). The image must be in a format supported by + the game in use. For q3 based games this is truecolor .jpg, .tga and + sometimes .png. For q2 this is .wal + +- Use one of the following methods to set the size (in game units) that the + file is displayed. + 1) select 1 or more brushes or entities and choose "from selection" + This will use the total dimensions off all selected brushes and entities + to size the image. + 2) For the X/Y view only, choose 'Size to min/max keys' This will look in + the worldspawn entity for the keys mapcoordsmins and mapcoordsmaxs (also + used for ET tracemap generation and command map sizing) and use those + dimensions to size the image. + +- Use the toggle buttons to show or hide the loaded images. The buttons will + press or unpress whenever you click them, but an image will only be + displayed once you have successfully loaded a file and set its size/postion. + +- Set the opacity of the image using the slider in the configuration dialog. + +- If any of these commands do not produce the expected results, there may be + an information in the radiant console. Please include this when reporting + bugs. + + +Notes and limitations: +- This plugin is compiled for GtkRadiant 1.3.13. It may or may not work with + later versions. It will *NOT* work with version 1.3.12 and below. If you + build from source (see below) you can build it for other versions. + +- As mentioned above, the image *MUST* be inside your basegame directory, or + another directory in which radiant looks for game files. + +- To prevent the image from being distorted, you should size it to the + original images aspect ratio. mapcoordsmaxs/mapcoordsmins and command maps + should always be square. + +- If you want a specific pixel to world unit relationship, you must arrange + that yourself. + +- On load, the image is converted to a texture whose dimensions are powers + of 2. If the original image dimensions are not powers of 2, some detail will + be lost due to resampling. If it is too large to fit on a single texture, + resolution is reduced. + +- radiants gamma and mipmap options are applied to the image. + +- If the image has an alpha channel, it will be included in the blending + process. 0 is transparent, 255 is opaque. .tga images are recommended if + you want to have an alpha channel. + +- since the plugin will only use true color files, you cannot use a terrain + indexmap (aka alphamap) or heightmap directly. You can of course, save a + copy of your indexmap in a 32 bit format. + +- There is no unload command. + +- You put the plugin in a game specific plugin directory, rather than the + radiant plugin directory. + +- You cannot set the image size with sub-unit precision. + +- Only win32 binaries are included. The source is available from: + http://www.cyberonic.net/~gdevault/rfm/mapstuff/bkgrnd2d-b0.25-src.zip + If you want to use it on another platform you will need a buildable gtkradiant + source tree to build it. For any non-windows platform you will also have to + figure out the compile options. I suggest ripping those off from some other + plugin. + +TODO: +- make file selection paterns match supported filetypes +- large images without downsampling +- bitmap and pcx support for indexmaps +- automatic size from indexmapped entities +- render under the grid instead of blending +- mac/*nix support +- remember/save/restore settings +- texture options independant of radiant prefs +- clean up icky code + +Changes from 0.1 +- all 2d views supported +- new ui +- file selection patterns, default directory improved + +Changes from 0.2 +- tooltips in dialog +- various code cleanup + diff --git a/contrib/bobtoolz/CPortals.h b/contrib/bobtoolz/CPortals.h new file mode 100644 index 00000000..71f399b7 --- /dev/null +++ b/contrib/bobtoolz/CPortals.h @@ -0,0 +1,63 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +class CBspPoint { +public: + float p[3]; +}; + +class CBspPortal { +public: + CBspPortal(); + ~CBspPortal(); + + unsigned point_count; + CBspPoint *point; + bool Build(char *def, unsigned int pointCnt, bool bInverse); +}; + + +class CBspNode { +public: + CBspPortal *portal; + unsigned int portal_count; + + bool AddPortal(char* def, unsigned int pointCnt, bool bInverse); + unsigned int portal_next; + CBspNode(); + ~CBspNode(); +}; + + +#define NAME_MAX 255 + +class CPortals { +public: + + CPortals(); + ~CPortals(); + + void Load(); // use filename in fn + void Purge(); + + char fn[NAME_MAX]; + CBspNode *node; + + unsigned int node_count; +}; diff --git a/contrib/bobtoolz/DBobView.cpp b/contrib/bobtoolz/DBobView.cpp new file mode 100644 index 00000000..688dd7e4 --- /dev/null +++ b/contrib/bobtoolz/DBobView.cpp @@ -0,0 +1,277 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// BobView.cpp: implementation of the DBobView class. +// +////////////////////////////////////////////////////////////////////// + +#include "DBobView.h" +//#include "misc.h" +#include "funchandlers.h" + +#include + +#include "iglrender.h" +#include "qerplugin.h" +#include "str.h" +#include "math/matrix.h" + +#include "DEntity.h" +#include "DEPair.h" +#include "misc.h" +#include "dialogs/dialogs-gtk.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +DBobView::DBobView() +{ + nPathCount = 0; + + path = NULL; + + boundingShow = BOUNDS_APEX; + + constructShaders(); + GlobalShaderCache().attachRenderable(*this); +} + +DBobView::~DBobView() +{ + GlobalShaderCache().detachRenderable(*this); + destroyShaders(); + + if(path) + delete[] path; + + g_PathView = NULL; +} + +////////////////////////////////////////////////////////////////////// +// Implementation +////////////////////////////////////////////////////////////////////// + +void DBobView::render(RenderStateFlags state) const +{ + glBegin(GL_LINE_STRIP); + + for(int i = 0; i < nPathCount; i++) + glVertex3fv(path[i]); + + glEnd(); +} + +const char* DBobView_state_line = "$bobtoolz/bobview/line"; +const char* DBobView_state_box = "$bobtoolz/bobview/box"; + +void DBobView::constructShaders() +{ + OpenGLState state; + GlobalOpenGLStateLibrary().getDefaultState(state); + state.m_state = RENDER_COLOURWRITE|RENDER_DEPTHWRITE|RENDER_BLEND|RENDER_LINESMOOTH; + state.m_sort = OpenGLState::eSortOpaque; + state.m_linewidth = 1; + state.m_colour[0] = 1; + state.m_colour[1] = 0; + state.m_colour[2] = 0; + state.m_colour[3] = 1; + GlobalOpenGLStateLibrary().insert(DBobView_state_line, state); + + state.m_colour[0] = 0.25f; + state.m_colour[1] = 0.75f; + state.m_colour[2] = 0.75f; + state.m_colour[3] = 1; + GlobalOpenGLStateLibrary().insert(DBobView_state_box, state); + + m_shader_line = GlobalShaderCache().capture(DBobView_state_line); + m_shader_box = GlobalShaderCache().capture(DBobView_state_box); +} + +void DBobView::destroyShaders() +{ + GlobalOpenGLStateLibrary().erase(DBobView_state_line); + GlobalOpenGLStateLibrary().erase(DBobView_state_box); + GlobalShaderCache().release(DBobView_state_line); + GlobalShaderCache().release(DBobView_state_box); +} + +Matrix4 g_transform_box1 = matrix4_translation_for_vec3(Vector3(16.0f, 16.0f, 28.0f)); +Matrix4 g_transform_box2 = matrix4_translation_for_vec3(Vector3(-16.0f, 16.0f, 28.0f)); +Matrix4 g_transform_box3 = matrix4_translation_for_vec3(Vector3(16.0f, -16.0f, -28.0f)); +Matrix4 g_transform_box4 = matrix4_translation_for_vec3(Vector3(-16.0f, -16.0f, -28.0f)); + +void DBobView::renderSolid(Renderer& renderer, const VolumeTest& volume) const +{ + if(!path) + return; + + renderer.SetState(m_shader_line, Renderer::eWireframeOnly); + renderer.SetState(m_shader_line, Renderer::eFullMaterials); + renderer.addRenderable(*this, g_matrix4_identity); + + if(m_bShowExtra) + { + renderer.SetState(m_shader_box, Renderer::eWireframeOnly); + renderer.SetState(m_shader_box, Renderer::eFullMaterials); + renderer.addRenderable(*this, g_transform_box1); + renderer.addRenderable(*this, g_transform_box2); + renderer.addRenderable(*this, g_transform_box3); + renderer.addRenderable(*this, g_transform_box4); + } +} +void DBobView::renderWireframe(Renderer& renderer, const VolumeTest& volume) const +{ + renderSolid(renderer, volume); +} + +void DBobView::SetPath(vec3_t *pPath) +{ + if(path) + delete[] path; + + path = pPath; +} + +#define LOCAL_GRAVITY -800.0f + +bool DBobView::CalculateTrajectory(vec3_t start, vec3_t apex, float multiplier, int points, float varGravity) +{ + if(apex[2] <= start[2]) + { + SetPath(NULL); + return false; + } + // ----think q3a actually would allow these + //scrub that, coz the plugin wont :] + + vec3_t dist, speed; + VectorSubtract(apex, start, dist); + + vec_t speed_z = (float)sqrt(-2*LOCAL_GRAVITY*dist[2]); + float flight_time = -speed_z/LOCAL_GRAVITY; + + + VectorScale(dist, 1/flight_time, speed); + speed[2] = speed_z; + +// Sys_Printf("Speed: (%.4f %.4f %.4f)\n", speed[0], speed[1], speed[2]); + + vec3_t* pPath = new vec3_t[points]; + + float interval = multiplier*flight_time/points; + for(int i = 0; i < points; i++) + { + float ltime = interval*i; + + VectorScale(speed, ltime, pPath[i]); + VectorAdd(pPath[i], start, pPath[i]); + + // could do this all with vectors + // vGrav = {0, 0, -800.0f} + // VectorScale(vGrav, 0.5f*ltime*ltime, vAdd); + // VectorScale(speed, ltime, pPath[i]); + // _VectorAdd(pPath[i], start, pPath[i]) + // _VectorAdd(pPath[i], vAdd, pPath[i]) + + pPath[i][2] = start[2] + (speed_z*ltime) + (varGravity*0.5f*ltime*ltime); + } + + SetPath(pPath); + return true; +} + +void DBobView::Begin(const char* trigger, const char *target, float multiplier, int points, float varGravity, bool bNoUpdate, bool bShowExtra) +{ + strcpy(entTrigger, trigger); + strcpy(entTarget, target); + + fMultiplier = multiplier; + fVarGravity = varGravity; + nPathCount = points; + m_bShowExtra = bShowExtra; + + if(!UpdatePath()) + { + globalErrorStream() << "Initialization Failure in DBobView::Begin"; + delete this; + } +} + +bool DBobView::UpdatePath() +{ + vec3_t start, apex; + + if(GetEntityCentre(entTrigger, start)) + { + if(GetEntityCentre(entTarget, apex)) + { + CalculateTrajectory(start, apex, fMultiplier, nPathCount, fVarGravity); + return true; + } + } + return false; +} + +void DBobView_setEntity(Entity& entity, float multiplier, int points, float varGravity, bool bNoUpdate, bool bShowExtra) +{ + DEntity trigger; + trigger.LoadEPairList(&entity); + + DEPair* trigger_ep = trigger.FindEPairByKey("targetname"); + + if(trigger_ep) + { + if(!strcmp(trigger.m_Classname, "trigger_push")) + { + DEPair* target_ep = trigger.FindEPairByKey("target"); + if(target_ep) + { + const scene::Path* entTarget = FindEntityFromTargetname(target_ep->value); + if(entTarget) + { + if(g_PathView) + delete g_PathView; + g_PathView = new DBobView; + + Entity* target = Node_getEntity(entTarget->top()); + if(target != 0) + { + if(!bNoUpdate) + { + g_PathView->trigger = &entity; + entity.attach(*g_PathView); + g_PathView->target = target; + target->attach(*g_PathView); + } + g_PathView->Begin(trigger_ep->value, target_ep->value, multiplier, points, varGravity, bNoUpdate, bShowExtra); + } + } + else + DoMessageBox("trigger_push target could not be found.", "Error", eMB_OK); + } + else + DoMessageBox("trigger_push has no target.", "Error", eMB_OK); + } + else + DoMessageBox("You must select a 'trigger_push' entity.", "Error", eMB_OK); + } + else + DoMessageBox("Entity must have a targetname", "Error", eMB_OK); +} \ No newline at end of file diff --git a/contrib/bobtoolz/DBobView.h b/contrib/bobtoolz/DBobView.h new file mode 100644 index 00000000..6869e6bd --- /dev/null +++ b/contrib/bobtoolz/DBobView.h @@ -0,0 +1,106 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DBobView.h: interface for the DBobView class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_BOBVIEW_H__6E36062A_EF0B_11D4_ACF7_004095A18133__INCLUDED_) +#define AFX_BOBVIEW_H__6E36062A_EF0B_11D4_ACF7_004095A18133__INCLUDED_ + +#include "ientity.h" +#include "irender.h" +#include "renderable.h" +#include "mathlib.h" + +class DListener; +class Shader; + +#define BOUNDS_ALL 0 +#define BOUNDS_APEX 1 + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +class DBobView : public Renderable, public OpenGLRenderable, public Entity::Observer +{ + Shader* m_shader_line; + Shader* m_shader_box; +public: + DBobView(); + virtual ~DBobView(); + +protected: + vec3_t* path; +public: + bool m_bShowExtra; + int boundingShow; + DListener* eyes; + float fVarGravity; + float fMultiplier; + int nPathCount; + + Entity* trigger; + Entity* target; + + bool UpdatePath(); + char entTarget[256]; + char entTrigger[256]; + void Begin(const char*, const char*, float, int, float, bool, bool); + bool CalculateTrajectory(vec3_t, vec3_t, float, int, float); + + void SetPath(vec3_t* pPath); + + void render(RenderStateFlags state) const; + void renderSolid(Renderer& renderer, const VolumeTest& volume) const; + void renderWireframe(Renderer& renderer, const VolumeTest& volume) const; + + void constructShaders(); + void destroyShaders(); + + void valueChanged(const char* value) + { + UpdatePath(); + } + typedef MemberCaller1 ValueChangedCaller; + void insert(const char* key, EntityKeyValue& value) + { + value.attach(ValueChangedCaller(*this)); + } + void erase(const char* key, EntityKeyValue& value) + { + value.detach(ValueChangedCaller(*this)); + } + void clear() + { + if(trigger != 0) + { + trigger->detach(*this); + target->detach(*this); + trigger = 0; + target = 0; + } + } +}; + +class Entity; +void DBobView_setEntity(Entity& entity, float multiplier, int points, float varGravity, bool bNoUpdate, bool bShowExtra); + +#endif // !defined(AFX_BOBVIEW_H__6E36062A_EF0B_11D4_ACF7_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/DBrush.cpp b/contrib/bobtoolz/DBrush.cpp new file mode 100644 index 00000000..96c884d2 --- /dev/null +++ b/contrib/bobtoolz/DBrush.cpp @@ -0,0 +1,861 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DBrush.cpp: implementation of the DBrush class. +// +////////////////////////////////////////////////////////////////////// + +#include "DBrush.h" + +#ifdef WIN32 +#pragma warning(disable : 4786) +#endif + +#include +#include "str.h" + +#include "DPoint.h" +#include "DPlane.h" +#include "DEPair.h" +#include "DPatch.h" +#include "DEntity.h" +#include "DWinding.h" + +#include "dialogs/dialogs-gtk.h" + +#include "misc.h" + +#include "iundo.h" + +#include "generic/referencecounted.h" + +#include "scenelib.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +DBrush::DBrush(int ID) +{ + m_nBrushID = ID; + bBoundsBuilt = false; + QER_entity = NULL; + QER_brush = NULL; +} + +DBrush::~DBrush() +{ + ClearFaces(); + ClearPoints(); +} + +////////////////////////////////////////////////////////////////////// +// Implementation +////////////////////////////////////////////////////////////////////// + +DPlane* DBrush::AddFace(const vec3_t va, const vec3_t vb, const vec3_t vc, const _QERFaceData* texData) +{ +#ifdef _DEBUG +// Sys_Printf("(%f %f %f) (%f %f %f) (%f %f %f)\n", va[0], va[1], va[2], vb[0], vb[1], vb[2], vc[0], vc[1], vc[2]); +#endif + bBoundsBuilt = false; + DPlane* newFace = new DPlane(va, vb, vc, texData); + faceList.push_back(newFace); + + return newFace; +} + +int DBrush::BuildPoints() +{ + ClearPoints(); + + if(faceList.size() <= 3) // if less than 3 faces, there can be no points + return 0; // with only 3 faces u can't have a bounded soild + + for(std::list::const_iterator p1=faceList.begin(); p1!=faceList.end(); p1++) + { + std::list::const_iterator p2=p1; + for(p2++; p2!=faceList.end(); p2++) + { + std::list::const_iterator p3=p2; + for(p3++; p3!=faceList.end(); p3++) + { + vec3_t pnt; + if((*p1)->PlaneIntersection(*p2, *p3, pnt)) + { + int pos = PointPosition(pnt); + + if(pos == POINT_IN_BRUSH) + { // ???? shouldn't happen here + globalErrorStream() << "ERROR:: Build Brush Points: Point IN brush!!!\n"; + } + else if(pos == POINT_ON_BRUSH) + { // normal point + if(!HasPoint(pnt)) + AddPoint(pnt); +/* else + Sys_Printf("Duplicate Point Found, pyramids ahoy!!!!!\n");*/ + // point lies on more that 3 planes + } + + // otherwise point is removed due to another plane.. + + // Sys_Printf("(%f, %f, %f)\n", pnt[0], pnt[1], pnt[2]); + } + } + } + } + +#ifdef _DEBUG +// Sys_Printf("%i points on brush\n", pointList.size()); +#endif + + return static_cast(pointList.size()); +} + +void DBrush_addFace(DBrush& brush, const _QERFaceData& faceData) +{ + brush.AddFace(vector3_to_array(faceData.m_p0), vector3_to_array(faceData.m_p1), vector3_to_array(faceData.m_p2), 0); +} +typedef ReferenceCaller1 DBrushAddFaceCaller; + +void DBrush_addFaceTextured(DBrush& brush, const _QERFaceData& faceData) +{ + brush.AddFace(vector3_to_array(faceData.m_p0), vector3_to_array(faceData.m_p1), vector3_to_array(faceData.m_p2), &faceData); +} +typedef ReferenceCaller1 DBrushAddFaceTexturedCaller; + +void DBrush::LoadFromBrush(scene::Instance& brush, bool textured) +{ + ClearFaces(); + ClearPoints(); + + GlobalBrushCreator().Brush_forEachFace(brush.path().top(), textured ? BrushFaceDataCallback(DBrushAddFaceTexturedCaller(*this)) : BrushFaceDataCallback(DBrushAddFaceCaller(*this))); + + QER_entity = brush.path().parent().get_pointer(); + QER_brush = brush.path().top().get_pointer(); +} + +int DBrush::PointPosition(vec3_t pnt) +{ + int state = POINT_IN_BRUSH; // if nothing happens point is inside brush + + for(std::list::const_iterator chkPlane=faceList.begin(); chkPlane!=faceList.end(); chkPlane++) + { + float dist = (*chkPlane)->DistanceToPoint(pnt); + + if(dist > MAX_ROUND_ERROR) + return POINT_OUT_BRUSH; // if point is in front of plane, it CANT be in the brush + else if(fabs(dist) < MAX_ROUND_ERROR) + state = POINT_ON_BRUSH; // if point is ON plane point is either ON the brush + // or outside it, it can no longer be in it + } + + return state; +} + +void DBrush::ClearPoints() +{ + for(std::list::const_iterator deadPoint=pointList.begin(); deadPoint!=pointList.end(); deadPoint++) { + delete *deadPoint; + } + pointList.clear(); +} + +void DBrush::ClearFaces() +{ + bBoundsBuilt = false; + for(std::list::const_iterator deadPlane=faceList.begin(); deadPlane!=faceList.end(); deadPlane++) + { + delete *deadPlane; + } + faceList.clear(); +} + +void DBrush::AddPoint(vec3_t pnt) +{ + DPoint* newPoint = new DPoint; + VectorCopy(pnt, newPoint->_pnt); + pointList.push_back(newPoint); +} + +bool DBrush::HasPoint(vec3_t pnt) +{ + for(std::list::const_iterator chkPoint=pointList.begin(); chkPoint!=pointList.end(); chkPoint++) + { + if(**chkPoint == pnt) + return true; + } + + return false; +} + +int DBrush::RemoveRedundantPlanes() +{ + int cnt = 0; + std::list::iterator chkPlane; + + // find duplicate planes + std::list::iterator p1=faceList.begin(); + + while( p1!=faceList.end() ) + { + std::list::iterator p2 = p1; + + for(p2++; p2!=faceList.end(); p2++) + { + if(**p1 == **p2) + { + if(!strcmp((*p1)->m_shader.c_str(), "textures/common/caulk")) + { + delete *p1; + p1 = faceList.erase(p1); // duplicate plane + } + else + { + delete *p2; + p2 = faceList.erase(p2); // duplicate plane + } + + cnt++; + break; + } + } + + if( p2 == faceList.end() ) + p1++; + } + + //+djbob kill planes with bad normal, they are more of a nuisance than losing a brush + chkPlane=faceList.begin(); + while( chkPlane!=faceList.end() ) + { + if(VectorLength((*chkPlane)->normal) == 0) // plane has bad normal + { + delete *chkPlane; + chkPlane = faceList.erase(chkPlane); + cnt++; + } else { + chkPlane++; + } + } + //-djbob + + if(pointList.size() == 0) // if points may not have been built, build them +/* if(BuildPoints() == 0) // just let the planes die if they are all bad + return cnt;*/ + BuildPoints(); + + chkPlane=faceList.begin(); + while(chkPlane != faceList.end()) + { + if((*chkPlane)->IsRedundant(pointList)) // checks that plane "0wnz" :), 3 or more points + { + delete *chkPlane; + chkPlane = faceList.erase(chkPlane); + cnt++; + } + else + chkPlane++; + } + + return cnt; +} + +bool DBrush::GetBounds(vec3_t min, vec3_t max) +{ + BuildBounds(); + + if(!bBoundsBuilt) + return false; + + VectorCopy(bbox_min, min); + VectorCopy(bbox_max, max); + + return true; +} + +bool DBrush::BBoxCollision(DBrush* chkBrush) +{ + vec3_t min1, min2; + vec3_t max1, max2; + + GetBounds(min1, max1); + chkBrush->GetBounds(min2, max2); + + if(min1[0] >= max2[0]) + return false; + if(min1[1] >= max2[1]) + return false; + if(min1[2] >= max2[2]) + return false; + + if(max1[0] <= min2[0]) + return false; + if(max1[1] <= min2[1]) + return false; + if(max1[2] <= min2[2]) + return false; + + return true; +} + +DPlane* DBrush::HasPlane(DPlane* chkPlane) +{ + for(std::list::const_iterator brushPlane=faceList.begin(); brushPlane!=faceList.end(); brushPlane++) + { + if(**brushPlane == *chkPlane) + return *brushPlane; + } + return NULL; +} + +bool DBrush::IsCutByPlane(DPlane *cuttingPlane) +{ + bool isInFront; + + if(pointList.size() == 0) + if(BuildPoints() == 0) + return false; + + std::list::const_iterator chkPnt = pointList.begin(); + + if(chkPnt == pointList.end()) + return false; + + float dist = cuttingPlane->DistanceToPoint((*chkPnt)->_pnt); + + if(dist > MAX_ROUND_ERROR) + isInFront = false; + else if(dist < MAX_ROUND_ERROR) + isInFront = true; + else + return true; + + for(chkPnt++=pointList.begin(); chkPnt!=pointList.end(); chkPnt++) + { + dist = cuttingPlane->DistanceToPoint((*chkPnt)->_pnt); + + if(dist > MAX_ROUND_ERROR) + { + if(isInFront) + return true; + } + else if(dist < MAX_ROUND_ERROR) + { + if(!isInFront) + return true; + } + else + return true; + } + + return false; +} + + +scene::Node* DBrush::BuildInRadiant(bool allowDestruction, int* changeCnt, scene::Node* entity) +{ + if(allowDestruction) + { + bool kill = true; + + for(std::list::const_iterator chkPlane=faceList.begin(); chkPlane!=faceList.end(); chkPlane++) + { + if((*chkPlane)->m_bChkOk) + { + kill = false; + break; + } + } + if(kill) + return NULL; + } + + //+djbob: fixed bug when brush had no faces "phantom brush" in radiant. + if(faceList.size() < 4) + { + globalErrorStream() << "Possible Phantom Brush Found, will not rebuild\n"; + return NULL; + } + //-djbob + + NodeSmartReference node(GlobalBrushCreator().createBrush()); + + for(std::list::const_iterator buildPlane=faceList.begin(); buildPlane!=faceList.end(); buildPlane++) { + if((*buildPlane)->AddToBrush(node) && changeCnt) { + (*changeCnt)++; + } + } + + if(entity) { + Node_getTraversable(*entity)->insert(node); + } else { + Node_getTraversable(GlobalRadiant().getMapWorldEntity())->insert(node); + } + + QER_entity = entity; + QER_brush = node.get_pointer(); + + return node.get_pointer(); +} + +void DBrush::CutByPlane(DPlane *cutPlane, DBrush **newBrush1, DBrush **newBrush2) +{ + if(!IsCutByPlane(cutPlane)) + { + *newBrush1 = NULL; + *newBrush2 = NULL; + return; + } + + DBrush* b1 = new DBrush; + DBrush* b2 = new DBrush; + + for(std::list::const_iterator parsePlane=faceList.begin(); parsePlane!=faceList.end(); parsePlane++) + { + b1->AddFace((*parsePlane)->points[0], (*parsePlane)->points[1], (*parsePlane)->points[2], NULL); + b2->AddFace((*parsePlane)->points[0], (*parsePlane)->points[1], (*parsePlane)->points[2], NULL); + } + + b1->AddFace(cutPlane->points[0], cutPlane->points[1], cutPlane->points[2], NULL); + b2->AddFace(cutPlane->points[2], cutPlane->points[1], cutPlane->points[0], NULL); + + b1->RemoveRedundantPlanes(); + b2->RemoveRedundantPlanes(); + + *newBrush1 = b1; + *newBrush2 = b2; +} + +bool DBrush::IntersectsWith(DBrush *chkBrush) +{ + if(pointList.size() == 0) + if(BuildPoints() == 0) + return false; // invalid brush!!!! + + if(chkBrush->pointList.size() == 0) + if(chkBrush->BuildPoints() == 0) + return false; // invalid brush!!!! + + if(!BBoxCollision(chkBrush)) + return false; + + std::list::const_iterator iplPlane; + + for( iplPlane=faceList.begin(); iplPlane!=faceList.end(); iplPlane++) + { + + bool allInFront = true; + for(std::list::const_iterator iPoint=chkBrush->pointList.begin(); iPoint!=chkBrush->pointList.end(); iPoint++) + { + if((*iplPlane)->DistanceToPoint((*iPoint)->_pnt) < -MAX_ROUND_ERROR) + { + allInFront = false; + break; + } + } + if(allInFront) + return false; + } + + for( iplPlane=chkBrush->faceList.begin(); iplPlane!=chkBrush->faceList.end(); iplPlane++) + { + bool allInFront = true; + for(std::list::const_iterator iPoint=pointList.begin(); iPoint!=pointList.end(); iPoint++) + { + if((*iplPlane)->DistanceToPoint((*iPoint)->_pnt) < -MAX_ROUND_ERROR) + { + allInFront = false; + break; + } + } + if(allInFront) + return false; + } + + return true; +} + +bool DBrush::IntersectsWith(DPlane* p1, DPlane* p2, vec3_t v) { + vec3_t vDown = { 0, 0, -1 }; + + std::list::const_iterator iplPlane; + for( iplPlane = faceList.begin(); iplPlane != faceList.end(); iplPlane++) { + DPlane* p = (*iplPlane); + + vec_t d = DotProduct( p->normal, vDown ); + if( d >= 0 ) { + continue; + } + if(p->PlaneIntersection(p1, p2, v)) { + if(PointPosition( v ) != POINT_OUT_BRUSH) { + return true; + } + } + } + + return false; +} + +void DBrush::BuildBounds() +{ + if(!bBoundsBuilt) + { + if(pointList.size() == 0) // if points may not have been built, build them + if(BuildPoints() == 0) + return; + + std::list::const_iterator first = pointList.begin(); + VectorCopy((*first)->_pnt, bbox_min); + VectorCopy((*first)->_pnt, bbox_max); + + std::list::const_iterator point=pointList.begin(); + for( point++; point!=pointList.end(); point++) + { + if((*point)->_pnt[0] > bbox_max[0]) + bbox_max[0] = (*point)->_pnt[0]; + if((*point)->_pnt[1] > bbox_max[1]) + bbox_max[1] = (*point)->_pnt[1]; + if((*point)->_pnt[2] > bbox_max[2]) + bbox_max[2] = (*point)->_pnt[2]; + + if((*point)->_pnt[0] < bbox_min[0]) + bbox_min[0] = (*point)->_pnt[0]; + if((*point)->_pnt[1] < bbox_min[1]) + bbox_min[1] = (*point)->_pnt[1]; + if((*point)->_pnt[2] < bbox_min[2]) + bbox_min[2] = (*point)->_pnt[2]; + } + + bBoundsBuilt = true; + } +} + +bool DBrush::BBoxTouch(DBrush *chkBrush) +{ + vec3_t min1, min2; + vec3_t max1, max2; + + GetBounds(min1, max1); + chkBrush->GetBounds(min2, max2); + + if((min1[0] - max2[0]) > MAX_ROUND_ERROR) + return false; + if((min1[1] - max2[1]) > MAX_ROUND_ERROR) + return false; + if((min1[2] - max2[2]) > MAX_ROUND_ERROR) + return false; + + if((min2[0] - max1[0]) > MAX_ROUND_ERROR) + return false; + if((min2[1] - max1[1]) > MAX_ROUND_ERROR) + return false; + if((min2[2] - max1[2]) > MAX_ROUND_ERROR) + return false; + + int cnt = 0; + + if((min2[0] - max1[0]) == 0) + cnt++; + + if((min2[1] - max1[1]) == 0) + cnt++; + + if((min2[2] - max1[2]) == 0) + cnt++; + + if((min1[0] - max2[0]) == 0) + cnt++; + + if((min1[1] - max2[1]) == 0) + cnt++; + + if((min1[2] - max2[2]) == 0) + cnt++; + + if(cnt > 1) + return false; + + return true; +} + +void DBrush::ResetChecks(std::list* exclusionList) +{ + for(std::list::const_iterator resetPlane=faceList.begin(); resetPlane!=faceList.end(); resetPlane++) + { + bool set = false; + + if(exclusionList) + { + for(std::list::iterator eTexture = exclusionList->begin(); eTexture != exclusionList->end(); eTexture++) + { + if(strstr((*resetPlane)->m_shader.c_str(), eTexture->GetBuffer())) + { + set = true; + break; + } + } + } + + (*resetPlane)->m_bChkOk = set; + } +} + +DPlane* DBrush::HasPlaneInverted(DPlane *chkPlane) +{ + for(std::list::const_iterator brushPlane=faceList.begin(); brushPlane!=faceList.end(); brushPlane++) + { + if(**brushPlane != *chkPlane) + { + if(fabs((*brushPlane)->_d + chkPlane->_d) < 0.1) + return (*brushPlane); + } + } + return NULL; +} + +bool DBrush::HasTexture(const char *textureName) +{ + for(std::list::const_iterator chkPlane=faceList.begin(); chkPlane!=faceList.end(); chkPlane++) + { + if(strstr((*chkPlane)->m_shader.c_str(), textureName)) + return true; + + } + return false; +} + +bool DBrush::IsDetail() +{ + for(std::list::const_iterator chkPlane=faceList.begin(); chkPlane!=faceList.end(); chkPlane++) + { + if((*chkPlane)->texInfo.contents & FACE_DETAIL) + return true; + + } + return false; +} + +void DBrush::BuildFromWinding(DWinding *w) +{ + if(w->numpoints < 3) + { + globalErrorStream() << "Winding has invalid number of points"; + return; + } + + DPlane* wPlane = w->WindingPlane(); + + DWinding* w2; + w2 = w->CopyWinding(); + int i; + for(i = 0; i < w2->numpoints; i++) + VectorAdd(w2->p[i], wPlane->normal, w2->p[i]); + + AddFace(w2->p[0], w2->p[1], w2->p[2], NULL); + AddFace(w->p[2], w->p[1], w->p[0], NULL); + + for(i = 0; i < w->numpoints-1; i++) + AddFace(w2->p[i], w->p[i], w->p[i+1], NULL); + AddFace(w2->p[w->numpoints-1], w->p[w->numpoints-1], w->p[0], NULL); + + delete wPlane; + delete w2; +} + +void DBrush::SaveToFile(FILE *pFile) +{ + fprintf(pFile, "{\n"); + + for(std::list::const_iterator pp=faceList.begin(); pp!=faceList.end(); pp++) + { + char buffer[512]; + + sprintf(buffer, "( %.0f %.0f %.0f ) ( %.0f %.0f %.0f ) ( %.0f %.0f %.0f ) %s %.0f %.0f %f %f %.0f 0 0 0\n", + (*pp)->points[0][0], (*pp)->points[0][1], (*pp)->points[0][2], + (*pp)->points[1][0], (*pp)->points[1][1], (*pp)->points[1][2], + (*pp)->points[2][0], (*pp)->points[2][1], (*pp)->points[2][2], + (*pp)->m_shader.c_str(), + (*pp)->texInfo.m_texdef.shift[0], (*pp)->texInfo.m_texdef.shift[1], + (*pp)->texInfo.m_texdef.scale[0], (*pp)->texInfo.m_texdef.scale[0], + (*pp)->texInfo.m_texdef.rotate); + + fprintf(pFile, buffer); + } + + fprintf(pFile, "}\n"); +} + +void DBrush::Rotate(vec3_t vOrigin, vec3_t vRotation) +{ + for(std::list::const_iterator rotPlane=faceList.begin(); rotPlane!=faceList.end(); rotPlane++) + { + for(int i = 0; i < 3; i++) + VectorRotate((*rotPlane)->points[i], vRotation, vOrigin); + + (*rotPlane)->Rebuild(); + } +} + +void DBrush::RotateAboutCentre(vec3_t vRotation) +{ + vec3_t min, max, centre; + GetBounds(min, max); + VectorAdd(min, max, centre); + VectorScale(centre, 0.5f, centre); + + Rotate(centre, vRotation); +} + +bool DBrush::ResetTextures(const char* textureName, float fScale[2], float fShift[2], int rotation, const char* newTextureName, + int bResetTextureName, int bResetScale[2], int bResetShift[2], int bResetRotation) +{ + if(textureName) + { + bool changed = false; + for(std::list::const_iterator resetPlane=faceList.begin(); resetPlane!=faceList.end(); resetPlane++) + { + if(!strcmp((*resetPlane)->m_shader.c_str(), textureName)) + { + if(bResetTextureName) + (*resetPlane)->m_shader = newTextureName; + + if(bResetScale[0]) + (*resetPlane)->texInfo.m_texdef.scale[0] = fScale[0]; + if(bResetScale[1]) + (*resetPlane)->texInfo.m_texdef.scale[1] = fScale[1]; + + if(bResetShift[0]) + (*resetPlane)->texInfo.m_texdef.shift[0] = fShift[0]; + if(bResetShift[1]) + (*resetPlane)->texInfo.m_texdef.shift[1] = fShift[1]; + + if(bResetRotation) + (*resetPlane)->texInfo.m_texdef.rotate = (float)rotation; + + changed = true; + } + } + return changed; // no point rebuilding unless we need to, only slows things down + } + else + { + for(std::list::const_iterator resetPlane=faceList.begin(); resetPlane!=faceList.end(); resetPlane++) + { + if(bResetTextureName) + (*resetPlane)->m_shader = newTextureName; + + if(bResetScale[0]) + (*resetPlane)->texInfo.m_texdef.scale[0] = fScale[0]; + if(bResetScale[1]) + (*resetPlane)->texInfo.m_texdef.scale[1] = fScale[1]; + + if(bResetShift[0]) + (*resetPlane)->texInfo.m_texdef.shift[0] = fShift[0]; + if(bResetShift[1]) + (*resetPlane)->texInfo.m_texdef.shift[1] = fShift[1]; + + if(bResetRotation) + (*resetPlane)->texInfo.m_texdef.rotate = (float)rotation; + } + return true; + } +} + +bool DBrush::operator ==(DBrush* other) +{ + std::list::const_iterator chkPlane; + + for(chkPlane=faceList.begin(); chkPlane!=faceList.end(); chkPlane++) + { + if(!other->HasPlane((*chkPlane))) + return false; + } + + for(chkPlane=faceList.begin(); chkPlane!=faceList.end(); chkPlane++) + { + if(!HasPlane((*chkPlane))) + return false; + } + + return true; +} + +DPlane* DBrush::AddFace(const vec3_t va, const vec3_t vb, const vec3_t vc, const char *textureName, bool bDetail) +{ + bBoundsBuilt = false; + DPlane* newFace = new DPlane(va, vb, vc, textureName, bDetail); + faceList.push_back(newFace); + + return newFace; +} + +DPlane* DBrush::FindPlaneWithClosestNormal( vec_t* normal ) { + vec_t bestDot = -2; + DPlane* bestDotPlane = NULL; + std::list::const_iterator chkPlane; + for( chkPlane = faceList.begin(); chkPlane != faceList.end(); chkPlane++ ) { + DPlane* pPlane = (*chkPlane); + + vec_t dot = DotProduct( pPlane->normal, normal ); + if( dot > bestDot ) { + bestDot = dot; + bestDotPlane = pPlane; + } + } + + return bestDotPlane; +} + +int DBrush::FindPointsForPlane( DPlane* plane, DPoint** pnts, int maxpnts ) { + int numpnts = 0; + + if(!maxpnts) { + return 0; + } + + BuildPoints(); + + for( std::list::const_iterator points = pointList.begin(); points != pointList.end(); points++ ) { + DPoint* point = (*points); + + if( fabs(plane->DistanceToPoint( point->_pnt )) < MAX_ROUND_ERROR ) { + pnts[numpnts] = point; + numpnts++; + + if(numpnts >= maxpnts) { + return numpnts; + } + + } + } + + return numpnts; +} + +void DBrush::RemovePlane( DPlane* plane ) { + bBoundsBuilt = false; + for( std::list::const_iterator deadPlane = faceList.begin(); deadPlane != faceList.end(); deadPlane++ ) { + if(*deadPlane == plane) { + delete *deadPlane; + faceList.remove( plane ); + } + } +} diff --git a/contrib/bobtoolz/DBrush.h b/contrib/bobtoolz/DBrush.h new file mode 100644 index 00000000..112f2294 --- /dev/null +++ b/contrib/bobtoolz/DBrush.h @@ -0,0 +1,115 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DBrush.h: interface for the DBrush class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_DBRUSH_H__35B2C522_F0A7_11D4_ACF7_004095A18133__INCLUDED_) +#define AFX_DBRUSH_H__35B2C522_F0A7_11D4_ACF7_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include +#include +#include "mathlib.h" +#include "str.h" + +class DPlane; +class DWinding; +class DPoint; +class _QERFaceData; + +namespace scene +{ + class Node; + class Instance; +} + +#define POINT_IN_BRUSH 0 +#define POINT_ON_BRUSH 1 +#define POINT_OUT_BRUSH 2 + +class DBrush +{ +public: + DPlane* AddFace(const vec3_t va, const vec3_t vb, const vec3_t vc, const char* textureName, bool bDetail); + void SaveToFile(FILE* pFile); + + void Rotate(vec3_t vOrigin, vec3_t vRotation); + void RotateAboutCentre(vec3_t vRotation); + + DPlane* HasPlaneInverted(DPlane* chkPlane); + DPlane* HasPlane(DPlane* chkPlane); + DPlane* AddFace(const vec3_t va, const vec3_t vb, const vec3_t vc, const _QERFaceData* texData); + + bool ResetTextures(const char* textureName, float fScale[2], float fShift[2], int rotation, const char* newTextureName, int bResetTextureName, int bResetScale[2], int bResetShift[2], int bResetRotation); + bool IsDetail(); + bool HasTexture(const char* textureName); + bool IntersectsWith(DBrush *chkBrush); + bool IntersectsWith(DPlane* p1, DPlane* p2, vec3_t v); + bool IsCutByPlane(DPlane* cuttingPlane); + bool GetBounds(vec3_t min, vec3_t max); + bool HasPoint(vec3_t pnt); + bool BBoxCollision(DBrush* chkBrush); + bool BBoxTouch(DBrush* chkBrush); + + int BuildPoints(); + void BuildBounds(); + void BuildFromWinding(DWinding* w); + scene::Node* BuildInRadiant(bool allowDestruction, int* changeCnt, scene::Node* entity = NULL); + + void ResetChecks(std::list* exclusionList); + + void ClearFaces(); + void ClearPoints(); + + int RemoveRedundantPlanes( void ); + void RemovePlane( DPlane* plane ); + int PointPosition(vec3_t pnt); + + + void CutByPlane(DPlane* cutPlane, DBrush** newBrush1, DBrush** newBrush2); + + void LoadFromBrush(scene::Instance& brush, bool textured); + void AddPoint(vec3_t pnt); + + DPlane* FindPlaneWithClosestNormal( vec_t* normal ); + int FindPointsForPlane( DPlane* plane, DPoint** pnts, int maxpnts ); + + DBrush(int ID = -1); + virtual ~DBrush(); + + bool operator== (DBrush* other); + +// members + scene::Node* QER_entity; + scene::Node* QER_brush; + std::list faceList; + std::list pointList; + int m_nBrushID; + vec3_t bbox_min, bbox_max; + bool bBoundsBuilt; +}; + +//typedef CList DBrushList; + +#endif // !defined(AFX_DBRUSH_H__35B2C522_F0A7_11D4_ACF7_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/DEPair.cpp b/contrib/bobtoolz/DEPair.cpp new file mode 100644 index 00000000..72dda4b3 --- /dev/null +++ b/contrib/bobtoolz/DEPair.cpp @@ -0,0 +1,48 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DEPair.cpp: implementation of the DEPair class. +// +////////////////////////////////////////////////////////////////////// + +#include "DEPair.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +DEPair::DEPair() +{ + +} + +DEPair::~DEPair() +{ + +} + +////////////////////////////////////////////////////////////////////// +// Implementation +////////////////////////////////////////////////////////////////////// + +void DEPair::Build(const char *pKey, const char *pValue) +{ + key = pKey; + value = pValue; +} diff --git a/contrib/bobtoolz/DEPair.h b/contrib/bobtoolz/DEPair.h new file mode 100644 index 00000000..fac4c334 --- /dev/null +++ b/contrib/bobtoolz/DEPair.h @@ -0,0 +1,47 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DEPair.h: interface for the DEPair class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_DEPAIR_H__35B2C521_F0A7_11D4_ACF7_004095A18133__INCLUDED_) +#define AFX_DEPAIR_H__35B2C521_F0A7_11D4_ACF7_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "str.h" + +class DEPair +{ +public: + DEPair(); + virtual ~DEPair(); + + void Build(const char* pKey, const char* pValue); + + Str key; + Str value; +}; + +//typedef CList DEPairList; + +#endif // !defined(AFX_DEPAIR_H__35B2C521_F0A7_11D4_ACF7_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/DEntity.cpp b/contrib/bobtoolz/DEntity.cpp new file mode 100644 index 00000000..f03b489e --- /dev/null +++ b/contrib/bobtoolz/DEntity.cpp @@ -0,0 +1,716 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DEntity.cpp: implementation of the DEntity class. +// +////////////////////////////////////////////////////////////////////// + +#include "DEntity.h" + +#ifdef WIN32 +#pragma warning(disable : 4786) +#endif + +#include +#include "str.h" + +#include "DPoint.h" +#include "DPlane.h" +#include "DBrush.h" +#include "DEPair.h" +#include "DPatch.h" + +#include "dialogs/dialogs-gtk.h" +#include "misc.h" +#include "CPortals.h" + +#include "iundo.h" +#include "ientity.h" +#include "ieclass.h" + +#include "generic/referencecounted.h" + +#include +#include +#include +#include + +#include "scenelib.h" + + +const char* brushEntityList[] = { + "worldspawn", + "trigger_always", + "trigger_hurt", + "trigger_multiple", + "trigger_push", + "trigger_teleport", + "func_bobbing", + "func_button", + "func_door", + "func_group", + "func_pendulum", + "func_plat", + "func_rotating", + "func_static", + "func_timer", + "func_train", + 0 +}; + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +DEntity::DEntity(const char *classname, int ID) +{ + SetClassname(classname); + m_nID = ID; + QER_Entity = NULL; +} + +DEntity::~DEntity() +{ + ClearPatches(); + ClearBrushes(); + ClearEPairs(); +} + +////////////////////////////////////////////////////////////////////// +// Implementation +////////////////////////////////////////////////////////////////////// + +void DEntity::ClearBrushes() +{ + for(std::list::const_iterator deadBrush=brushList.begin(); deadBrush!=brushList.end(); deadBrush++) + { + delete *deadBrush; + } + brushList.clear(); +} + +void DEntity::ClearPatches() +{ + for(std::list::const_iterator deadPatch=patchList.begin(); deadPatch!=patchList.end(); deadPatch++) + { + delete *deadPatch; + } + patchList.clear(); +} + +DPatch* DEntity::NewPatch() +{ + DPatch* newPatch = new DPatch; + + patchList.push_back(newPatch); + + return newPatch; +} + +DBrush* DEntity::NewBrush(int ID) +{ + DBrush* newBrush = new DBrush(ID); + + brushList.push_back(newBrush); + + return newBrush; +} + +char* getNextBracket(char* s) +{ + char* p = s; + while(*p) + { + p++; + if(*p == '(') + break; + } + + return p; +} + +bool DEntity::LoadFromPrt(char *filename) +{ + CPortals portals; + strcpy(portals.fn, filename); + portals.Load(); + + if(portals.node_count == 0) + return false; + + ClearBrushes(); + ClearEPairs(); + + bool build = false; + for(unsigned int i = 0; i < portals.node_count; i++) + { + build = false; + DBrush* brush = NewBrush(); + + for(unsigned int j = 0; j < portals.node[i].portal_count; j++) + { + for(unsigned int k = 0; k < portals.node[i].portal[j].point_count-2; k++) + { + vec3_t v1, v2, normal, n; + VectorSubtract(portals.node[i].portal[j].point[k+2].p, portals.node[i].portal[j].point[k+1].p, v1); + VectorSubtract(portals.node[i].portal[j].point[k].p, portals.node[i].portal[j].point[k+1].p, v2); + CrossProduct(v1, v2, n); + VectorNormalize(n, v2); + + if(k == 0) + { + VectorCopy(v2, normal); + } + else + { + VectorSubtract(v2, normal, v1); + if(VectorLength(v1) > 0.01) + { + build = true; + break; + } + } + } + + if(!build) + brush->AddFace(portals.node[i].portal[j].point[2].p, portals.node[i].portal[j].point[1].p, portals.node[i].portal[j].point[0].p, "textures/common/caulk", false); + else + brush->AddFace(portals.node[i].portal[j].point[0].p, portals.node[i].portal[j].point[1].p, portals.node[i].portal[j].point[2].p, "textures/common/caulk", false); + } + if(build) + brush->BuildInRadiant(false, NULL); + } + + return true; +} + +DPlane* DEntity::AddFaceToBrush(vec3_t va, vec3_t vb, vec3_t vc, _QERFaceData* faceData, int ID) +{ + DBrush* buildBrush = GetBrushForID(ID); + return buildBrush->AddFace(va, vb, vc, faceData); + // slow, dont use much +} + +DBrush* DEntity::GetBrushForID(int ID) +{ + DBrush* buildBrush = NULL; + + for(std::list::const_iterator chkBrush=brushList.begin(); chkBrush!=brushList.end(); chkBrush++) + { + if((*chkBrush)->m_nBrushID == ID) + { + buildBrush = (*chkBrush); + break; + } + } + + if(!buildBrush) + buildBrush = NewBrush(ID); + + return buildBrush; +} + +template +class BrushSelectedVisitor : public SelectionSystem::Visitor +{ + const Functor& m_functor; +public: + BrushSelectedVisitor(const Functor& functor) : m_functor(functor) + { + } + void visit(scene::Instance& instance) const + { + if(Node_isBrush(instance.path().top())) + { + m_functor(instance); + } + } +}; + +template +inline const Functor& Scene_forEachSelectedBrush(const Functor& functor) +{ + GlobalSelectionSystem().foreachSelected(BrushSelectedVisitor(functor)); + return functor; +} + +void DEntity_loadBrush(DEntity& entity, scene::Instance& brush) +{ + DBrush* loadBrush = entity.NewBrush(static_cast(entity.brushList.size())); + loadBrush->LoadFromBrush(brush, true); +} +typedef ReferenceCaller1 DEntityLoadBrushCaller; + +void DEntity::LoadSelectedBrushes() +{ + ClearBrushes(); + ClearEPairs(); + + Scene_forEachSelectedBrush(DEntityLoadBrushCaller(*this)); +} + +template +class PatchSelectedVisitor : public SelectionSystem::Visitor +{ + const Functor& m_functor; +public: + PatchSelectedVisitor(const Functor& functor) : m_functor(functor) + { + } + void visit(scene::Instance& instance) const + { + if(Node_isPatch(instance.path().top())) + { + m_functor(instance); + } + } +}; + +template +inline const Functor& Scene_forEachSelectedPatch(const Functor& functor) +{ + GlobalSelectionSystem().foreachSelected(PatchSelectedVisitor(functor)); + return functor; +} + +void DEntity_loadPatch(DEntity& entity, scene::Instance& patch) +{ + DPatch* loadPatch = entity.NewPatch(); + loadPatch->LoadFromPatch(patch); +} +typedef ReferenceCaller1 DEntityLoadPatchCaller; + +void DEntity::LoadSelectedPatches() +{ + ClearPatches(); + ClearEPairs(); + + Scene_forEachSelectedPatch(DEntityLoadPatchCaller(*this)); +} + +bool* DEntity::BuildIntersectList() +{ + int max = GetIDMax(); + if(max == 0) + return NULL; + + bool* pbIntList = new bool[max]; + memset(pbIntList, 0, sizeof(bool)*(max)); + + for(std::list::const_iterator pB1=brushList.begin(); pB1!=brushList.end(); pB1++) + { + std::list::const_iterator pB2=pB1; + for(pB2++; pB2!=brushList.end(); pB2++) + { + if((*pB1)->IntersectsWith((*pB2))) + { + pbIntList[(*pB1)->m_nBrushID] = true; + pbIntList[(*pB2)->m_nBrushID] = true; + } + } + } + + return pbIntList; +} + +bool* DEntity::BuildDuplicateList() +{ + int max = GetIDMax(); + if(max == 0) + return NULL; + + bool* pbDupList = new bool[max]; + memset(pbDupList, 0, sizeof(bool)*(max)); + + for(std::list::const_iterator pB1=brushList.begin(); pB1!=brushList.end(); pB1++) + { + std::list::const_iterator pB2=pB1; + for(pB2++; pB2!=brushList.end(); pB2++) + { + if(**pB1 == *pB2) + { + pbDupList[(*pB1)->m_nBrushID] = true; + pbDupList[(*pB2)->m_nBrushID] = true; + } + } + } + + return pbDupList; +} + +void DEntity::SelectBrushes(bool *selectList) +{ + if(selectList == NULL) + return; + + GlobalSelectionSystem().setSelectedAll(false); + + scene::Path path(NodeReference(GlobalSceneGraph().root())); + path.push(NodeReference(*QER_Entity)); + + for(std::list::const_iterator pBrush=brushList.begin(); pBrush!=brushList.end(); pBrush++) + { + if(selectList[(*pBrush)->m_nBrushID]) + { + path.push(NodeReference(*(*pBrush)->QER_brush)); + Instance_getSelectable(*GlobalSceneGraph().find(path))->setSelected(true); + path.pop(); + } + } +} + +bool DEntity::LoadFromEntity(scene::Node& ent, bool bLoadPatches) { + ClearPatches(); + ClearBrushes(); + ClearEPairs(); + + QER_Entity = &ent; + + LoadEPairList(Node_getEntity(ent)); + + bool keep = false; + int i; + for(i = 0; brushEntityList[i]; i++) + { + if(string_equal_nocase(brushEntityList[i], m_Classname)) + { + keep = true; + break; + } + } + + if(!keep) + return false; + + if(Node_getTraversable(ent)) + { + class load_brushes_t : public scene::Traversable::Walker + { + DEntity* m_entity; + mutable int m_count; + public: + load_brushes_t(DEntity* entity) + : m_entity(entity), m_count(0) + { + } + bool pre(scene::Node& node) const + { + scene::Path path(NodeReference(GlobalSceneGraph().root())); + path.push(NodeReference(*m_entity->QER_Entity)); + path.push(NodeReference(node)); + scene::Instance* instance = GlobalSceneGraph().find(path); + ASSERT_MESSAGE(instance != 0, ""); + + if(Node_isPatch(node)) + { + DPatch* loadPatch = m_entity->NewPatch(); + loadPatch->LoadFromPatch(*instance); + } + else if(Node_isBrush(node)) + { + DBrush* loadBrush = m_entity->NewBrush(m_count++); + loadBrush->LoadFromBrush(*instance, true); + } + return false; + } + } load_brushes(this); + + Node_getTraversable(ent)->traverse(load_brushes); + } + + return true; +} + +void DEntity::RemoveNonCheckBrushes(std::list* exclusionList, bool useDetail) +{ + std::list::iterator chkBrush=brushList.begin(); + + while( chkBrush!=brushList.end() ) + { + if(!useDetail) + { + if((*chkBrush)->IsDetail()) + { + delete *chkBrush; + chkBrush = brushList.erase(chkBrush); + continue; + } + } + + std::list::iterator eTexture; + + for( eTexture=exclusionList->begin(); eTexture!=exclusionList->end(); eTexture++ ) + { + if((*chkBrush)->HasTexture((*eTexture).GetBuffer())) + { + delete *chkBrush; + chkBrush = brushList.erase(chkBrush); + break; + } + } + + if( eTexture == exclusionList->end() ) + chkBrush++; + } +} + +void DEntity::ResetChecks(std::list* exclusionList) +{ + for(std::list::const_iterator resetBrush=brushList.begin(); resetBrush!=brushList.end(); resetBrush++) + { + (*resetBrush)->ResetChecks(exclusionList); + } +} + +int DEntity::FixBrushes() +{ + int count = 0; + + for(std::list::const_iterator fixBrush=brushList.begin(); fixBrush!=brushList.end(); fixBrush++) + { + count += (*fixBrush)->RemoveRedundantPlanes(); + } + + return count; +} + +void DEntity::BuildInRadiant(bool allowDestruction) +{ + bool makeEntity = strcmp(m_Classname, "worldspawn") ? true : false; + + if(makeEntity) + { + NodeSmartReference node(GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert(m_Classname.GetBuffer(), !brushList.empty() || !patchList.empty()))); + + for(std::list::const_iterator buildEPair=epairList.begin(); buildEPair!=epairList.end(); buildEPair++) + { + Node_getEntity(node)->setKeyValue((*buildEPair)->key, (*buildEPair)->value); + } + + Node_getTraversable(GlobalSceneGraph().root())->insert(node); + + for(std::list::const_iterator buildBrush=brushList.begin(); buildBrush!=brushList.end(); buildBrush++) + (*buildBrush)->BuildInRadiant(allowDestruction, NULL, node.get_pointer()); + + for(std::list::const_iterator buildPatch=patchList.begin(); buildPatch!=patchList.end(); buildPatch++) + (*buildPatch)->BuildInRadiant(node.get_pointer()); + + QER_Entity = node.get_pointer(); + } + else + { + for(std::list::const_iterator buildBrush=brushList.begin(); buildBrush!=brushList.end(); buildBrush++) + (*buildBrush)->BuildInRadiant(allowDestruction, NULL); + + for(std::list::const_iterator buildPatch=patchList.begin(); buildPatch!=patchList.end(); buildPatch++) + (*buildPatch)->BuildInRadiant(); + } +} + + + +int DEntity::GetIDMax( void ) { + int max = -1; + for(std::list::const_iterator cntBrush=brushList.begin(); cntBrush!=brushList.end(); cntBrush++) { + if((*cntBrush)->m_nBrushID > max) + max = (*cntBrush)->m_nBrushID; + } + return max+1; +} + +void DEntity::SetClassname( const char *classname ) { + m_Classname = classname; +} + +void DEntity::SaveToFile(FILE *pFile) +{ + fprintf(pFile, "{\n"); + + fprintf(pFile, "\"classname\" \"%s\"\n", (const char *)m_Classname); + + for(std::list::const_iterator ep=epairList.begin(); ep!=epairList.end(); ep++) + { + fprintf(pFile, "\"%s\" \"%s\"\n", (const char *)(*ep)->key, (const char *)(*ep)->value); + } + + for(std::list::const_iterator bp=brushList.begin(); bp!=brushList.end(); bp++) + { + (*bp)->SaveToFile(pFile); + } + + fprintf(pFile, "}\n"); +} + +void DEntity::ClearEPairs() +{ + for(std::list::const_iterator deadEPair=epairList.begin(); deadEPair!=epairList.end(); deadEPair++) + { + delete (*deadEPair); + } + epairList.clear(); +} + +void DEntity::AddEPair(const char *key, const char *value) { + DEPair* newEPair; + newEPair = FindEPairByKey( key ); + if(!newEPair) { + newEPair = new DEPair; + newEPair->Build(key, value); + epairList.push_back(newEPair); + } else { + newEPair->Build(key, value); + } +} + +void DEntity::LoadEPairList(Entity *epl) +{ + class load_epairs_t : public Entity::Visitor + { + DEntity* m_entity; + public: + load_epairs_t(DEntity* entity) + : m_entity(entity) + { + } + void visit(const char* key, const char* value) + { + if(strcmp(key, "classname") == 0) + m_entity->SetClassname(value); + else + m_entity->AddEPair(key, value); + } + + } load_epairs(this); + + epl->forEachKeyValue(load_epairs); +} + +bool DEntity::ResetTextures(const char* textureName, float fScale[2], float fShift[2], int rotation, const char* newTextureName, + int bResetTextureName, int bResetScale[2], int bResetShift[2], int bResetRotation, bool rebuild) +{ + bool reset = false; + + for(std::list::const_iterator resetBrush=brushList.begin(); resetBrush!=brushList.end(); resetBrush++) + { + bool tmp = (*resetBrush)->ResetTextures(textureName, fScale, fShift, rotation, newTextureName, + bResetTextureName, bResetScale, bResetShift, bResetRotation); + + if(tmp) + { + reset = true; + if(rebuild) + { + Node_getTraversable(*(*resetBrush)->QER_entity)->erase(*(*resetBrush)->QER_brush); + (*resetBrush)->BuildInRadiant(false, NULL, (*resetBrush)->QER_entity); + } + } + } + + if(bResetTextureName) + { + for(std::list::const_iterator resetPatch=patchList.begin(); resetPatch!=patchList.end(); resetPatch++) + { + bool tmp = (*resetPatch)->ResetTextures(textureName, newTextureName); + + if(tmp) + { + reset = true; + if(rebuild) + { + Node_getTraversable(*(*resetPatch)->QER_entity)->erase(*(*resetPatch)->QER_brush); + (*resetPatch)->BuildInRadiant((*resetPatch)->QER_entity); + } + } + } + } + + return reset; +} + +DEPair* DEntity::FindEPairByKey(const char* keyname) +{ + for(std::list::const_iterator ep=epairList.begin(); ep!=epairList.end(); ep++) + { + char* c = (*ep)->key; + if(!strcmp(c, keyname)) + return *ep; + } + return NULL; +} + +void DEntity::RemoveFromRadiant() +{ + Node_getTraversable(GlobalSceneGraph().root())->erase(*QER_Entity); + + QER_Entity = NULL; +} + +void DEntity::SpawnString(const char* key, const char* defaultstring, const char** out) +{ + DEPair* pEP = FindEPairByKey(key); + if(pEP) { + *out = pEP->value; + } else { + *out = defaultstring; + } +} + +void DEntity::SpawnInt(const char* key, const char* defaultstring, int* out) +{ + DEPair* pEP = FindEPairByKey(key); + if(pEP) { + *out = atoi(pEP->value); + } else { + *out = atoi(defaultstring); + } +} + +void DEntity::SpawnFloat(const char* key, const char* defaultstring, float* out) +{ + DEPair* pEP = FindEPairByKey(key); + if(pEP) { + *out = static_cast(atof(pEP->value)); + } else { + *out = static_cast(atof(defaultstring)); + } +} + +void DEntity::SpawnVector(const char* key, const char* defaultstring, vec_t* out) +{ + DEPair* pEP = FindEPairByKey(key); + if(pEP) { + sscanf(pEP->value, "%f %f %f", &out[0], &out[1], &out[2]); + } else { + sscanf(defaultstring, "%f %f %f", &out[0], &out[1], &out[2]); + } +} + +int DEntity::GetBrushCount( void ) { + return static_cast(brushList.size()); +} + +DBrush* DEntity::FindBrushByPointer( scene::Node& brush ) { + for(std::list::const_iterator listBrush = brushList.begin(); listBrush != brushList.end(); listBrush++) { + DBrush* pBrush = (*listBrush); + if(pBrush->QER_brush == &brush) { + return pBrush; + } + } + return NULL; +} diff --git a/contrib/bobtoolz/DEntity.h b/contrib/bobtoolz/DEntity.h new file mode 100644 index 00000000..5baa3d3d --- /dev/null +++ b/contrib/bobtoolz/DEntity.h @@ -0,0 +1,125 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DEntity.h: interface for the DEntity class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_DENTITY_H__35B2C523_F0A7_11D4_ACF7_004095A18133__INCLUDED_) +#define AFX_DENTITY_H__35B2C523_F0A7_11D4_ACF7_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include +#include "str.h" +#include "mathlib.h" + +class DEPair; +class DBrush; +class DPlane; +class DPatch; +class Entity; + +namespace scene +{ + class Node; +} +class _QERFaceData; + +class DEntity +{ +public: + void RemoveFromRadiant(); + scene::Node* QER_Entity; + int m_nID; + +// Constrcution/Destruction + DEntity(const char* classname = "worldspawn", int ID = -1); // sets classname + virtual ~DEntity(); +// --------------------------------------------- + +// epair functions........ + void LoadEPairList(Entity* epl); + void AddEPair(const char* key, const char* value); + void ClearEPairs(); + DEPair* FindEPairByKey(const char* keyname); +// --------------------------------------------- + +// random functions........ + bool ResetTextures(const char* textureName, float fScale[2], float fShift[2], int rotation, const char* newTextureName, int bResetTextureName, int bResetScale[2], int bResetShift[2], int bResetRotation, bool rebuild); + void SaveToFile(FILE* pFile); + void SetClassname(const char* classname); + int GetIDMax(); + + void BuildInRadiant(bool allowDestruction); + void ResetChecks(std::list* exclusionList); + void RemoveNonCheckBrushes(std::list* exclusionList, bool useDetail); + + DPlane* AddFaceToBrush(vec3_t va, vec3_t vb, vec3_t vc, _QERFaceData* faceData, int ID); // slow, try not to use much + int GetBrushCount( void ); + DBrush* FindBrushByPointer( scene::Node& brush ); +// --------------------------------------------- + + +// bool list functions + void SelectBrushes(bool* selectList); + bool* BuildDuplicateList(); + bool* BuildIntersectList(); +// --------------------------------------------- + + +// brush operations + void ClearBrushes(); // clears brush list and frees memory for brushes + + DBrush* GetBrushForID(int ID); + DBrush* NewBrush(int ID = -1); +// --------------------------------------------- + +// patch operations + void ClearPatches(); + + DPatch* NewPatch(); +// --------------------------------------------- + +// vars + std::list epairList; + std::list brushList; + // new patches, wahey!!! + std::list patchList; + Str m_Classname; +// --------------------------------------------- + + + int FixBrushes(); + + bool LoadFromEntity(scene::Node& ent, bool bLoadPatches = false); + void LoadSelectedBrushes(); + void LoadSelectedPatches(); + + bool LoadFromPrt(char* filename); +// --------------------------------------------- + void SpawnString(const char* key, const char* defaultstring, const char** out); + void SpawnInt(const char* key, const char* defaultstring, int* out); + void SpawnFloat(const char* key, const char* defaultstring, float* out); + void SpawnVector(const char* key, const char* defaultstring, vec_t* out); +}; + +#endif // !defined(AFX_DENTITY_H__35B2C523_F0A7_11D4_ACF7_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/DMap.cpp b/contrib/bobtoolz/DMap.cpp new file mode 100644 index 00000000..18e43188 --- /dev/null +++ b/contrib/bobtoolz/DMap.cpp @@ -0,0 +1,178 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DMap.cpp: implementation of the DMap class. +// +////////////////////////////////////////////////////////////////////// + +#include "DMap.h" + +#include "str.h" +#include + +#include "DPoint.h" +#include "DPlane.h" +#include "DBrush.h" +#include "DEPair.h" +#include "DPatch.h" +#include "DEntity.h" + +#include "iundo.h" + +#include "generic/referencecounted.h" + +#include +#include +#include +#include + +#include "scenelib.h" + + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +DMap::DMap() +{ + m_nNextEntity = 1; + AddEntity("worldspawn", 0); +} + +DMap::~DMap() +{ + ClearEntities(); +} + +DEntity* DMap::AddEntity(char *classname, int ID) +{ + DEntity* newEntity; + if(ID == -1) + newEntity = new DEntity(classname, m_nNextEntity++); + else + newEntity = new DEntity(classname, ID); + + entityList.push_back(newEntity); + + return newEntity; +} + +void DMap::ClearEntities() +{ + m_nNextEntity = 1; + + for(std::list::const_iterator deadEntity=entityList.begin(); deadEntity!=entityList.end(); deadEntity++) + delete *deadEntity; + + entityList.clear(); +} + +DEntity* DMap::GetEntityForID(int ID) +{ + DEntity* findEntity = NULL; + + for(std::list::const_iterator chkEntity=entityList.begin(); chkEntity!=entityList.end(); chkEntity++) + { + if((*chkEntity)->m_nID == ID) + { + findEntity = (*chkEntity); + break; + } + } + + if(!findEntity) + findEntity = AddEntity("worldspawn", ID); + + return findEntity; +} + + +DEntity* DMap::GetWorldSpawn() +{ + return GetEntityForID(0); +} + +void DMap::BuildInRadiant(bool bAllowDestruction) +{ + for(std::list::const_iterator buildEntity=entityList.begin(); buildEntity!=entityList.end(); buildEntity++) + (*buildEntity)->BuildInRadiant(bAllowDestruction); +} + +void DMap::LoadAll(bool bLoadPatches) +{ + ClearEntities(); + + GlobalSelectionSystem().setSelectedAll(false); + + class load_entities_t : public scene::Traversable::Walker + { + DMap* m_map; + bool m_bLoadPatches; + public: + load_entities_t(DMap* map, bool bLoadPatches) + : m_map(map), m_bLoadPatches(bLoadPatches) + { + } + bool pre(scene::Node& node) const + { + if(Node_isEntity(node)) + { + DEntity* loadEntity = m_map->AddEntity("", 0); + loadEntity->LoadFromEntity(node, m_bLoadPatches); + } + return false; + } + } load_entities(this, bLoadPatches); + + Node_getTraversable(GlobalSceneGraph().root())->traverse(load_entities); +} + +int DMap::FixBrushes() +{ + int count = 0; + for(std::list::const_iterator fixEntity=entityList.begin(); fixEntity!=entityList.end(); fixEntity++) + { + count += (*fixEntity)->FixBrushes(); + } + + return count; +} + +void DMap::ResetTextures( const char* textureName, float fScale[2], float fShift[2], int rotation, const char* newTextureName, + int bResetTextureName, int bResetScale[2], int bResetShift[2], int bResetRotation) +{ + for(std::list::const_iterator texEntity=entityList.begin(); texEntity!=entityList.end(); texEntity++) + { + if(string_equal_nocase("worldspawn", (*texEntity)->m_Classname)) + (*texEntity)->ResetTextures(textureName, fScale, fShift, rotation, newTextureName, + bResetTextureName, bResetScale, bResetShift, bResetRotation, true); + else + { + if((*texEntity)->ResetTextures( textureName, fScale, fShift, rotation, newTextureName, + bResetTextureName, bResetScale, bResetShift, bResetRotation, false)) + RebuildEntity(*texEntity); + } + } +} + +void DMap::RebuildEntity(DEntity *ent) +{ + ent->RemoveFromRadiant(); + ent->BuildInRadiant(false); +} diff --git a/contrib/bobtoolz/DMap.h b/contrib/bobtoolz/DMap.h new file mode 100644 index 00000000..018dbc0f --- /dev/null +++ b/contrib/bobtoolz/DMap.h @@ -0,0 +1,58 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DMap.h: interface for the DMap class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_DMAP_H__ACAE597A_D26D_49AD_AA69_EDE743DB54FA__INCLUDED_) +#define AFX_DMAP_H__ACAE597A_D26D_49AD_AA69_EDE743DB54FA__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include + +class DEntity; + +class DMap +{ +public: + static void RebuildEntity(DEntity* ent); + + void ResetTextures( const char* textureName, float fScale[2], float fShift[2], int rotation, const char* newTextureName, int bResetTextureName, int bResetScale[2], int bResetShift[2], int bResetRotation); + void LoadAll(bool bLoadPatches = false); + void BuildInRadiant(bool bAllowDestruction); + int m_nNextEntity; + DEntity* GetWorldSpawn(); + void ClearEntities(); + + DEntity* GetEntityForID(int ID); + DEntity* AddEntity(char* classname = "worldspawn", int ID = -1); + + std::list entityList; + + DMap(); + virtual ~DMap(); + + int FixBrushes(); +}; + +#endif // !defined(AFX_DMAP_H__ACAE597A_D26D_49AD_AA69_EDE743DB54FA__INCLUDED_) diff --git a/contrib/bobtoolz/DPatch.cpp b/contrib/bobtoolz/DPatch.cpp new file mode 100644 index 00000000..30bb5c60 --- /dev/null +++ b/contrib/bobtoolz/DPatch.cpp @@ -0,0 +1,463 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DPatch.cpp: implementation of the DPatch class. +// +////////////////////////////////////////////////////////////////////// + +#include "DPatch.h" + +#include +#include "str.h" +#include "scenelib.h" + +#include "ipatch.h" + +#include "misc.h" +#include "./dialogs/dialogs-gtk.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +// Added patch merging, wahey! + +// +// problem is, you cant put patches into entities as yet :( +// + +DPatch::DPatch() +{ + width = MIN_PATCH_WIDTH; + height = MIN_PATCH_HEIGHT; + QER_entity = NULL; + QER_brush = NULL; +} + +DPatch::~DPatch() +{ + +} + +void DPatch::SetTexture(const char *textureName) +{ + strcpy(texture, textureName); +} + +void CopyDrawVert(const drawVert_t* in, drawVert_t* out) +{ + out->lightmap[0] = in->lightmap[0]; + out->lightmap[1] = in->lightmap[1]; + out->st[0] = in->st[0]; + out->st[1] = in->st[1]; + VectorCopy(in->normal, out->normal); + VectorCopy(in->xyz, out->xyz); +} + +void DPatch::BuildInRadiant(scene::Node* entity) +{ + NodeSmartReference patch(GlobalPatchCreator().createPatch()); + + scene::Node& parent = entity != 0 ? *entity : GlobalRadiant().getMapWorldEntity(); + Node_getTraversable(parent)->insert(patch); + + GlobalPatchCreator().Patch_setShader(patch, texture); + GlobalPatchCreator().Patch_resize(patch, height, width); + PatchControlMatrix matrix = GlobalPatchCreator().Patch_getControlPoints(patch); + for(int x = 0; x < height; x++) + { + for(int y = 0; y < width; y++) + { + PatchControl& p = matrix(y, x); + p.m_vertex[0] = points[x][y].xyz[0]; + p.m_vertex[1] = points[x][y].xyz[1]; + p.m_vertex[2] = points[x][y].xyz[2]; + p.m_texcoord[0] = points[x][y].st[0]; + p.m_texcoord[1] = points[x][y].st[1]; + } + } + GlobalPatchCreator().Patch_controlPointsChanged(patch); + + QER_entity = entity; + QER_brush = patch.get_pointer(); + + +#if 0 + int nIndex = g_FuncTable.m_pfnCreatePatchHandle(); + //$ FIXME: m_pfnGetPatchHandle + patchMesh_t* pm = g_FuncTable.m_pfnGetPatchData(nIndex); + + b->patchBrush = true; + b->pPatch = Patch_Alloc(); + b->pPatch->setDims(width,height); + + for(int x = 0; x < width; x++) + for(int y = 0; y < height; y++) + CopyDrawVert(&points[x][y], &pm->ctrl[x][y]); + +/* if(entity) + { +// strcpy(pm->d_texture->name, texture); + + brush_t* brush = (brush_t*)g_FuncTable.m_pfnCreateBrushHandle(); + brush->patchBrush = true; + brush->pPatch = pm; + + pm->pSymbiot = brush; + pm->bSelected = false; + pm->bOverlay = false; // bleh, f*cks up, just have to wait for a proper function + pm->bDirty = true; // or get my own patch out.... + pm->nListID = -1; + + g_FuncTable.m_pfnCommitBrushHandleToEntity(brush, entity); + } + else*/ // patch to entity just plain dont work atm + + if(entity) + g_FuncTable.m_pfnCommitPatchHandleToEntity(nIndex, pm, texture, entity); + else + g_FuncTable.m_pfnCommitPatchHandleToMap(nIndex, pm, texture); + + QER_brush = pm->pSymbiot; +#endif +} + +void DPatch::LoadFromPatch(scene::Instance& patch) +{ + QER_entity = patch.path().parent().get_pointer(); + QER_brush = patch.path().top().get_pointer(); + + PatchControlMatrix matrix = GlobalPatchCreator().Patch_getControlPoints(patch.path().top()); + + width = static_cast(matrix.x()); + height = static_cast(matrix.y()); + + for(int x = 0; x < height; x++) + { + for(int y = 0; y < width; y++) + { + PatchControl& p = matrix(y, x); + points[x][y].xyz[0] = p.m_vertex[0]; + points[x][y].xyz[1] = p.m_vertex[1]; + points[x][y].xyz[2] = p.m_vertex[2]; + points[x][y].st[0] = p.m_texcoord[0]; + points[x][y].st[1] = p.m_texcoord[1]; + } + } + SetTexture(GlobalPatchCreator().Patch_getShader(patch.path().top())); + +#if 0 + SetTexture(brush->pPatch->GetShader()); + + width = brush->pPatch->getWidth(); + height = brush->pPatch->getHeight(); + + for(int x = 0; x < height; x++) + { + for(int y = 0; y < width; y++) + { + float *p = brush->pPatch->ctrlAt(ROW,x,y); + p[0] = points[x][y].xyz[0]; + p[1] = points[x][y].xyz[1]; + p[2] = points[x][y].xyz[2]; + p[3] = points[x][y].st[0]; + p[4] = points[x][y].st[1]; + } + } +#endif +} + +bool DPatch::ResetTextures(const char *oldTextureName, const char *newTextureName) +{ + if( !oldTextureName || !strcmp(texture, oldTextureName)) + { + strcpy(texture, newTextureName); + return true; + } + + return false; +} + +void Build1dArray(vec3_t* array, drawVert_t points[MAX_PATCH_WIDTH][MAX_PATCH_HEIGHT], + int startX, int startY, int number, bool horizontal, bool inverse) +{ + int x = startX, y = startY, i, step; + + if(inverse) + step = -1; + else + step = 1; + + for(i = 0; i < number; i++) + { + VectorCopy(points[x][y].xyz, array[i]); + + if(horizontal) + x+=step; + else + y+=step; + } +} + +void Print1dArray(vec3_t* array, int size) +{ + for(int i = 0; i < size; i++) + globalOutputStream() << "(" << array[i][0] << " " << array[i][1] << " " << array[i][2] << ")\t"; + globalOutputStream() << "\n"; +} + +bool Compare1dArrays(vec3_t* a1, vec3_t* a2, int size) +{ + int i; + bool equal = true; + + for(i = 0; i < size; i++) + { + if(!VectorCompare(a1[i], a2[size-i-1])) + { + equal = false; + break; + } + } + return equal; +} + +patch_merge_t DPatch::IsMergable(DPatch *other) +{ + int i, j; + vec3_t p1Array[4][MAX_PATCH_HEIGHT]; + vec3_t p2Array[4][MAX_PATCH_HEIGHT]; + + int p1ArraySizes[4]; + int p2ArraySizes[4]; + + patch_merge_t merge_info; + + Build1dArray(p1Array[0], this->points, 0, 0, this->width, true, false); + Build1dArray(p1Array[1], this->points, this->width-1, 0, this->height, false, false); + Build1dArray(p1Array[2], this->points, this->width-1, this->height-1, this->width, true, true); + Build1dArray(p1Array[3], this->points, 0, this->height-1, this->height, false, true); + + Build1dArray(p2Array[0], other->points, 0, 0, other->width, true, false); + Build1dArray(p2Array[1], other->points, other->width-1, 0, other->height, false, false); + Build1dArray(p2Array[2], other->points, other->width-1, other->height-1, other->width, true, true); + Build1dArray(p2Array[3], other->points, 0, other->height-1, other->height, false, true); + + p1ArraySizes[0] = this->width; + p1ArraySizes[1] = this->height; + p1ArraySizes[2] = this->width; + p1ArraySizes[3] = this->height; + + p2ArraySizes[0] = other->width; + p2ArraySizes[1] = other->height; + p2ArraySizes[2] = other->width; + p2ArraySizes[3] = other->height; + + for(i = 0; i < 4; i++) + { + for(j = 0; j < 4; j++) + { + if(p1ArraySizes[i] == p2ArraySizes[j]) + { + if(Compare1dArrays(p1Array[i], p2Array[j], p1ArraySizes[i])) + { + merge_info.pos1 = i; + merge_info.pos2 = j; + merge_info.mergable = true; + return merge_info; + } + } + } + } + + merge_info.mergable = false; + return merge_info; +} + +DPatch* DPatch::MergePatches(patch_merge_t merge_info, DPatch *p1, DPatch *p2) +{ + while(merge_info.pos1 != 2) + { + p1->Transpose(); + merge_info.pos1--; + if(merge_info.pos1 < 0) + merge_info.pos1 += 4; + } + + while(merge_info.pos2 != 0) + { + p2->Transpose(); + merge_info.pos2--; + if(merge_info.pos2 < 0) + merge_info.pos2 += 3; + } + + int newHeight = p1->height + p2->height - 1; + if(newHeight > MAX_PATCH_HEIGHT) + return NULL; + + DPatch* newPatch = new DPatch(); + + newPatch->height = newHeight; + newPatch->width = p1->width; + newPatch->SetTexture(p1->texture); + + int y = 0; + int i; + for(i = 0; i < p1->height; i++, y++) + for(int x = 0; x < p1->width; x++) + newPatch->points[x][y] = p1->points[x][i]; + + for(i = 1; i < p2->height; i++, y++) + for(int x = 0; x < p2->width; x++) + newPatch->points[x][y] = p2->points[x][i]; + +// newPatch->Invert(); + + return newPatch; +} + +void DPatch::Invert() +{ + int i, j; + + for(i = 0 ; i < width ; i++ ) + { + for(j = 0; j < height / 2; j++) + { + std::swap(points[i][height - 1- j], points[i][j]); + } + } +} + +void DPatch::Transpose() +{ + int i, j, w; + + if ( width > height ) + { + for ( i = 0 ; i < height ; i++ ) + { + for ( j = i + 1 ; j < width ; j++ ) + { + if ( j < height ) + { + // swap the value + std::swap(points[j][i], points[i][j]); + } + else + { + // just copy + points[i][j] = points[j][i]; + } + } + } + } + else + { + for ( i = 0 ; i < width ; i++ ) + { + for ( j = i + 1 ; j < height ; j++ ) + { + if ( j < width ) + { + // swap the value + std::swap(points[i][j], points[j][i]); + } + else + { + // just copy + points[j][i] = points[i][j]; + } + } + } + } + + w = width; + width = height; + height = w; + + Invert(); +} + +std::list DPatch::Split(bool rows, bool cols) +{ + std::list patchList; + int i; + int x, y; + + if(rows && height >= 5) + { + for(i = 0; i < (height-1)/2; i++) + { + DPatch p; + + p.width = width; + p.height = 3; + p.SetTexture(texture); + + for(y = 0; y < 3; y++) + { + for(x = 0; x < p.width; x++) + { + p.points[x][y] = points[x][(i*2)+y]; + } + } + patchList.push_back(p); + } + + if(cols && width >= 5) + { + std::list patchList2; + + for(std::list::iterator patches = patchList.begin(); patches != patchList.end(); patches++) + { + std::list patchList3 = (*patches).Split(false, true); + + for(std::list::iterator patches2 = patchList3.begin(); patches2 != patchList3.end(); patches2++) + patchList2.push_front(*patches2); + } + + return patchList2; + } + } + else if(cols && width >= 5) + { + for(i = 0; i < (width-1)/2; i++) + { + DPatch p; + + p.height = height; + p.width = 3; + p.SetTexture(texture); + + for(x = 0; x < 3; x++) + { + for(y = 0; y < p.height; y++) + { + p.points[x][y] = points[(i*2)+x][y]; + } + } + + patchList.push_back(p); + } + } + + return patchList; +} diff --git a/contrib/bobtoolz/DPatch.h b/contrib/bobtoolz/DPatch.h new file mode 100644 index 00000000..32439189 --- /dev/null +++ b/contrib/bobtoolz/DPatch.h @@ -0,0 +1,76 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DPatch.h: interface for the DPatch class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_DPATCH_H__26C6B083_CE5B_420B_836B_1DDA733C04CE__INCLUDED_) +#define AFX_DPATCH_H__26C6B083_CE5B_420B_836B_1DDA733C04CE__INCLUDED_ + +#include + +typedef struct +{ + bool mergable; + int pos1; + int pos2; +} patch_merge_t; + +typedef struct { + float xyz[3]; + float st[2]; + float normal[3]; + float lightmap[2]; +} drawVert_t; + +namespace scene +{ + class Node; + class Instance; +} + +#define MAX_PATCH_WIDTH 16 +#define MAX_PATCH_HEIGHT 16 +#define MIN_PATCH_WIDTH 3 +#define MIN_PATCH_HEIGHT 3 + +class DPatch +{ +public: + std::list Split(bool rows, bool cols); + void Transpose(); + void Invert(); + DPatch* MergePatches(patch_merge_t merge_info, DPatch* p1, DPatch* p2); + patch_merge_t IsMergable(DPatch* other); + bool ResetTextures(const char *oldTextureName, const char *newTextureName); + scene::Node* QER_entity; + scene::Node* QER_brush; + void LoadFromPatch(scene::Instance& patch); + void BuildInRadiant(scene::Node* entity = NULL); + void SetTexture(const char* textureName); + char texture[256]; + int width, height; + drawVert_t points[MAX_PATCH_WIDTH][MAX_PATCH_HEIGHT]; + DPatch(); + virtual ~DPatch(); + +}; + +#endif // !defined(AFX_DPATCH_H__26C6B083_CE5B_420B_836B_1DDA733C04CE__INCLUDED_) diff --git a/contrib/bobtoolz/DPlane.cpp b/contrib/bobtoolz/DPlane.cpp new file mode 100644 index 00000000..c16db749 --- /dev/null +++ b/contrib/bobtoolz/DPlane.cpp @@ -0,0 +1,268 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DPlane.cpp: implementation of the DPlane class. +// +////////////////////////////////////////////////////////////////////// + +#include "DPlane.h" + +#include + +#include "DPoint.h" +#include "DWinding.h" + +#include "str.h" +#include "misc.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +DPlane::DPlane(const vec3_t va, const vec3_t vb, const vec3_t vc, const _QERFaceData* texData) +{ + MakeNormal( va, vb, vc, normal ); + if(VectorNormalize(normal, normal) == 0) // normalizes and returns length + globalErrorStream() << "DPlane::DPlane: Bad Normal.\n"; + + _d = (normal[0]*va[0]) + (normal[1]*va[1]) + (normal[2]*va[2]); + + VectorCopy(va, points[0]); + VectorCopy(vb, points[1]); + VectorCopy(vc, points[2]); + + m_bChkOk = true; + + if(texData) + memcpy(&texInfo, texData, sizeof(_QERFaceData)); + else + FillDefaultTexture(&texInfo, points[0], points[1], points[2], "textures/common/caulk"); +} + +DPlane::~DPlane() +{ + +} + +////////////////////////////////////////////////////////////////////// +// Implementation +////////////////////////////////////////////////////////////////////// + +vec_t DPlane::DistanceToPoint(vec3_t pnt) +{ + vec3_t tmp; + VectorSubtract(pnt, points[0], tmp); + return DotProduct(tmp, normal); +} + +bool DPlane::PlaneIntersection(DPlane *pl1, DPlane *pl2, vec3_t out) +{ + float a1, a2, a3; + float b1, b2, b3; + float c1, c2, c3; + + a1 = normal[0]; a2 = normal[1]; a3 = normal[2]; + b1 = pl1->normal[0]; b2 = pl1->normal[1]; b3 = pl1->normal[2]; + c1 = pl2->normal[0]; c2 = pl2->normal[1]; c3 = pl2->normal[2]; + + float d = Determinant3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3); + + if(d == 0) + return false; + + float v1 = _d; + float v2 = pl1->_d; + float v3 = pl2->_d; + + float d1 = Determinant3x3(v1, a2, a3, v2, b2, b3, v3, c2, c3); + float d2 = Determinant3x3(a1, v1, a3, b1, v2, b3, c1, v3, c3); + float d3 = Determinant3x3(a1, a2, v1, b1, b2, v2, c1, c2, v3); + + out[0] = d1/d; + out[1] = d2/d; + out[2] = d3/d; + + return true; +} + +bool DPlane::IsRedundant(std::list& pointList) +{ + int cnt = 0; + + //std::list::const_iterator point=pointList.begin(); + for(std::list::const_iterator point=pointList.begin(); point!=pointList.end(); point++) + { + if(fabs(DistanceToPoint((*point)->_pnt)) < MAX_ROUND_ERROR) + cnt++; + + if(cnt == 3) + return false; + } + return true; +} + +bool DPlane::operator == (DPlane& other) +{ + vec3_t chk; + VectorSubtract(other.normal, normal, chk); + if(fabs(VectorLength(chk)) > MAX_ROUND_ERROR) + return false; + + if(fabs(other._d - _d) > MAX_ROUND_ERROR) + return false; + + return true; +} + +bool DPlane::operator != (DPlane& other) +{ + vec3_t chk; + VectorAdd(other.normal, normal, chk); + if(fabs(VectorLength(chk)) > MAX_ROUND_ERROR) + return false; + + return true; +} + +DWinding* DPlane::BaseWindingForPlane() +{ + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + +// find the major axis + + max = -131072; + x = -1; + for (i=0 ; i<3; i++) + { + v = (float)fabs(normal[i]); + if (v > max) + { + x = i; + max = v; + } + } + if (x==-1) + globalOutputStream() << "BaseWindingForPlane: no axis found"; + + VectorCopy (vec3_origin, vup); + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct (vup, normal); + VectorMA (vup, -v, normal, vup); + VectorNormalize (vup, vup); + + VectorScale (normal, _d, org); + + CrossProduct (vup, normal, vright); + + VectorScale (vup, 131072, vup); + VectorScale (vright, 131072, vright); + +// project a really big axis aligned box onto the plane + DWinding* w = new DWinding; + w->AllocWinding(4); + + VectorSubtract (org, vright, w->p[0]); + VectorAdd (w->p[0], vup, w->p[0]); + + VectorAdd (org, vright, w->p[1]); + VectorAdd (w->p[1], vup, w->p[1]); + + VectorAdd (org, vright, w->p[2]); + VectorSubtract (w->p[2], vup, w->p[2]); + + VectorSubtract (org, vright, w->p[3]); + VectorSubtract (w->p[3], vup, w->p[3]); + + return w; +} + +void DPlane::Rebuild() +{ + vec3_t v1, v2; + VectorSubtract(points[0], points[1], v1); + VectorSubtract(points[2], points[1], v2); + CrossProduct(v1, v2, normal); + + if(VectorNormalize(normal, normal) == 0) // normalizes and returns length + globalErrorStream() << "DPlane::Rebuild: Bad Normal.\n"; + + _d = (normal[0]*points[0][0]) + (normal[1]*points[0][1]) + (normal[2]*points[0][2]); + + VectorCopy(points[0], texInfo.m_p0); + VectorCopy(points[1], texInfo.m_p1); + VectorCopy(points[2], texInfo.m_p2); +} + +bool DPlane::AddToBrush(scene::Node& brush) +{ + bool changed = false; + if(!(m_bChkOk || !strcmp(m_shader.c_str(), "textures/common/caulk"))) + { + m_shader = "textures/common/caulk"; + changed = true; + } + + _QERFaceData faceData; + faceData.m_p0 = vector3_from_array(points[0]); + faceData.m_p1 = vector3_from_array(points[1]); + faceData.m_p2 = vector3_from_array(points[2]); + faceData.m_texdef = texInfo.m_texdef; + faceData.m_shader = m_shader.c_str(); + GlobalBrushCreator().Brush_addFace(brush, faceData); + + return changed; +} + +void DPlane::ScaleTexture() +{ } + +DPlane::DPlane(const vec3_t va, const vec3_t vb, const vec3_t vc, const char* textureName, bool bDetail) +{ + vec3_t v1, v2; + VectorSubtract(va, vb, v1); + VectorSubtract(vc, vb, v2); + CrossProduct(v1, v2, normal); + + if(VectorNormalize(normal, normal) == 0) // normalizes and returns length + globalErrorStream() << "DPlane::DPlane: Bad Normal.\n"; + + _d = (normal[0]*va[0]) + (normal[1]*va[1]) + (normal[2]*va[2]); + + VectorCopy(va, points[0]); + VectorCopy(vb, points[1]); + VectorCopy(vc, points[2]); + + m_bChkOk = true; + + FillDefaultTexture(&texInfo, points[0], points[1], points[2], textureName); + if(bDetail) + texInfo.contents |= FACE_DETAIL; +} diff --git a/contrib/bobtoolz/DPlane.h b/contrib/bobtoolz/DPlane.h new file mode 100644 index 00000000..68db6ed9 --- /dev/null +++ b/contrib/bobtoolz/DPlane.h @@ -0,0 +1,74 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DPlane.h: interface for the DPlane class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_DPLANE_H__FC37C021_F0A1_11D4_ACF7_004095A18133__INCLUDED_) +#define AFX_DPLANE_H__FC37C021_F0A1_11D4_ACF7_004095A18133__INCLUDED_ + +#include +#include "ibrush.h" +#include "string/string.h" +#include "mathlib.h" + +class Brush; +class DPoint; + +#define FACE_DETAIL 0x8000000 + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +class DWinding; + +class DPlane +{ +public: + DPlane(const vec3_t va, const vec3_t vb, const vec3_t vc, const char* textureName, bool bDetail); + void ScaleTexture(); + DWinding* BaseWindingForPlane(); + + void Rebuild(); + + bool AddToBrush(scene::Node& brush); + bool operator != (DPlane& other); + bool operator == (DPlane& other); + + bool IsRedundant(std::list& pointList); + bool PlaneIntersection(DPlane* pl1, DPlane* pl2, vec3_t out);; + + vec_t DistanceToPoint(vec3_t pnt); + + DPlane(const vec3_t va, const vec3_t vb, const vec3_t vc, const _QERFaceData* texData); + DPlane() { } + virtual ~DPlane(); + + bool m_bChkOk; + _QERFaceData texInfo; + CopiedString m_shader; + vec3_t points[3]; // djbob:do we really need these any more? + vec3_t normal; + float _d; +}; + +//typedef CList DPlaneList; +#endif // !defined(AFX_DPLANE_H__FC37C021_F0A1_11D4_ACF7_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/DPoint.cpp b/contrib/bobtoolz/DPoint.cpp new file mode 100644 index 00000000..b0b60153 --- /dev/null +++ b/contrib/bobtoolz/DPoint.cpp @@ -0,0 +1,52 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DPoint.cpp: implementation of the DPoint class. +// +////////////////////////////////////////////////////////////////////// + +#include "DPoint.h" +#include "misc.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +DPoint::DPoint() +{ + +} + +DPoint::~DPoint() +{ + +} + +////////////////////////////////////////////////////////////////////// +// Implementation +////////////////////////////////////////////////////////////////////// + +bool DPoint::operator ==(vec3_t other) +{ + vec3_t test; + VectorSubtract(other, _pnt, test); + if(fabs(VectorLength(test)) > MAX_ROUND_ERROR) + return false; + return true; +} diff --git a/contrib/bobtoolz/DPoint.h b/contrib/bobtoolz/DPoint.h new file mode 100644 index 00000000..12137341 --- /dev/null +++ b/contrib/bobtoolz/DPoint.h @@ -0,0 +1,47 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DPoint.h: interface for the DPoint class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_DPOINT_H__FC37C022_F0A1_11D4_ACF7_004095A18133__INCLUDED_) +#define AFX_DPOINT_H__FC37C022_F0A1_11D4_ACF7_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "mathlib.h" + +class DPoint +{ +public: + DPoint(); + virtual ~DPoint(); + + bool operator ==(vec3_t other); + + vec3_t _pnt; + unsigned char m_uData; +}; + +//typedef CList DPointList; + +#endif // !defined(AFX_DPOINT_H__FC37C022_F0A1_11D4_ACF7_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/DShape.cpp b/contrib/bobtoolz/DShape.cpp new file mode 100644 index 00000000..9201d29c --- /dev/null +++ b/contrib/bobtoolz/DShape.cpp @@ -0,0 +1,469 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DShape.cpp: implementation of the DShape class. +// +////////////////////////////////////////////////////////////////////// + +#include "DShape.h" + +#include +#include +#include "str.h" + +#include "DPoint.h" +#include "DPlane.h" +#include "DBrush.h" +#include "DEPair.h" +#include "DPatch.h" +#include "DEntity.h" + +//#include "dialogs-gtk.h" + +#include "misc.h" +#include "shapes.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +bool bFacesAll[6] = {true, true, true, true, true, true}; + +DShape::DShape() +{ + m_nNextBrush = 0; +} + +DShape::~DShape() +{ + +} + +void DShape::BuildRegularPrism(vec3_t min, vec3_t max, int nSides, bool bAlignTop) +{ + vec3_t vc[MAX_POLYGON_FACES+2], vd[MAX_POLYGON_FACES+2]; + + vec3_t radius; + vec3_t origin; + + VectorSubtract(max, min, radius); + VectorScale(radius, 0.5f, radius); + // calc 3d radius and origin + VectorAdd(max, min, origin); + VectorScale(origin, 0.5f, origin); + + float phase = 0.0f; + + if(bAlignTop) + { + phase = -(Q_PI/nSides); + VectorScale(radius, static_cast(1.0 / cos(phase)), radius); + } + + //----- Build Polygon Vertices ----- + + int i; + for(i = 0; i < nSides; i++) + { + VectorCopy(origin, vc[i]); + VectorCopy(origin, vd[i]); + + vc[i][2] = min[2]; + vd[i][2] = max[2]; + + vc[i][0] += radius[0] * sinf( ( 2 * Q_PI * i / nSides ) + phase ); + vc[i][1] += radius[1] * cosf( ( 2 * Q_PI * i / nSides ) + phase ); + + vd[i][0] = vc[i][0]; + vd[i][1] = vc[i][1]; + } + + VectorCopy(vc[0], vc[nSides]); + VectorCopy(vd[0], vd[nSides]); + VectorCopy(vc[1], vc[nSides+1]); + VectorCopy(vd[1], vd[nSides+1]); + + //---------------------------------- + + DBrush* pB = m_Container.GetWorldSpawn()->NewBrush(m_nNextBrush++); + + for(i = 1; i <= nSides; i++) + pB->AddFace(vc[i-1], vc[i], vd[i], GetCurrentTexture(), false); + + pB->AddFace(vc[2], vc[1], vc[0], "textures/common/caulk", false); + pB->AddFace(vd[0], vd[1], vd[2], "textures/common/caulk", false); +} + +void DShape::Commit() +{ + m_Container.GetWorldSpawn()->FixBrushes(); + m_Container.BuildInRadiant(true); +} + +void DShape::BuildInversePrism(vec3_t min, vec3_t max, int nSides, bool bAlignTop) +{ + vec3_t va[MAX_POLYGON_FACES+1], vb[MAX_POLYGON_FACES+1]; + vec3_t radius; + vec3_t origin; + + VectorSubtract(max, min, radius); + VectorScale(radius, 0.5f, radius); + // calc 3d radius and origin + VectorAdd(max, min, origin); + VectorScale(origin, 0.5f, origin); + + float phase = 0.0f; + + if(bAlignTop) + { + phase = -(Q_PI/nSides); + VectorScale(radius, static_cast(1.0 / cos(phase)), radius); + } + + //----- Build Polygon Vertices ----- + + int i; + for(i = 0; i < nSides; i++) + { + VectorCopy(origin, va[i]); + VectorCopy(origin, vb[i]); + + va[i][2] = min[2]; + vb[i][2] = max[2]; + + va[i][0] += radius[0] * sinf( ( 2 * Q_PI * i / nSides ) + phase ); + va[i][1] += radius[1] * cosf( ( 2 * Q_PI * i / nSides ) + phase ); + + vb[i][0] = va[i][0]; + vb[i][1] = va[i][1]; + } + + VectorCopy(va[0], va[nSides]); + VectorCopy(vb[0], vb[nSides]); + + //---------------------------------- + + for(i = 1; i <= nSides; i++) + { + DBrush* pB = GetBoundingCube(min, max, "textures/common/caulk"); + + vec3_t top, bottom; + VectorCopy(va[i-1], top); + VectorCopy(va[i], bottom); + + if(va[i-1][1] > va[i][1]) + { + top[0] += 5; + bottom[0] += 5; + } + else // flip direction of plane on crossover + { + top[0] -= 5; + bottom[0] -= 5; + } + + if(top[1] != bottom[1]) // internal line is flat already if true + { + pB->AddFace(va[i-1], top, vb[i-1], "textures/common/caulk", false); + pB->AddFace(va[i], vb[i], bottom, "textures/common/caulk", false); + } // add cut-off planes + + pB->AddFace(va[i-1], vb[i-1], vb[i], GetCurrentTexture(), false); + // add internal polygon plane + } +} + +void DShape::BuildBorderedPrism(vec3_t min, vec3_t max, int nSides, int nBorder, bool bAlignTop) +{ + vec3_t va[MAX_POLYGON_FACES+2], vb[MAX_POLYGON_FACES+2]; + vec3_t vc[MAX_POLYGON_FACES+2], vd[MAX_POLYGON_FACES+2]; + + vec3_t radius; + vec3_t origin; + + VectorSubtract(max, min, radius); + VectorScale(radius, 0.5f, radius); + // calc 3d radius and origin + VectorAdd(max, min, origin); + VectorScale(origin, 0.5f, origin); + + if(nBorder >= Min(radius[0], radius[1])) + { +// DoMessageBox("Border is too large", "Error", MB_OK); + return; + } + + float phase = 0.0f; + + if(bAlignTop) + { + phase = -(Q_PI/nSides); + VectorScale(radius, static_cast(1.0 / cos(phase)), radius); + } + + //----- Build Polygon Vertices ----- + + int i; + for(i = 0; i < nSides; i++) + { + VectorCopy(origin, va[i]); + VectorCopy(origin, vb[i]); + VectorCopy(origin, vc[i]); + VectorCopy(origin, vd[i]); + + va[i][2] = min[2]; + vb[i][2] = max[2]; + + va[i][0] += (radius[0] - nBorder) * sinf( ( 2 * Q_PI * i / nSides ) + phase ); + va[i][1] += (radius[1] - nBorder) * cosf( ( 2 * Q_PI * i / nSides ) + phase ); + + vb[i][0] = va[i][0]; + vb[i][1] = va[i][1]; + + + + vc[i][2] = min[2]; + vd[i][2] = max[2]; + + vc[i][0] += radius[0] * sinf( ( 2 * Q_PI * i / nSides ) + phase ); + vc[i][1] += radius[1] * cosf( ( 2 * Q_PI * i / nSides ) + phase ); + + vd[i][0] = vc[i][0]; + vd[i][1] = vc[i][1]; + } + + VectorCopy(va[0], va[nSides]); + VectorCopy(vb[0], vb[nSides]); + VectorCopy(va[1], va[nSides+1]); + VectorCopy(vb[1], vb[nSides+1]); + + VectorCopy(vc[0], vc[nSides]); + VectorCopy(vd[0], vd[nSides]); + VectorCopy(vc[1], vc[nSides+1]); + VectorCopy(vd[1], vd[nSides+1]); + + //---------------------------------- + + for(i = 1; i <= nSides; i++) + { + DBrush* pB = GetBoundingCube(min, max, "textures/common/caulk"); + + pB->AddFace(origin, vc[i-1], vd[i-1], "textures/common/caulk", false); + pB->AddFace(origin, vd[i], vc[i], "textures/common/caulk", false); + + pB->AddFace(vc[i-1], vc[i], vd[i], GetCurrentTexture(), false); + pB->AddFace(vb[i], va[i], va[i-1], GetCurrentTexture(), false); + } +} + +DBrush* DShape::GetBoundingCube_Ext(vec3_t min, vec3_t max, const char *textureName, bool* bUseFaces, bool detail) +{ + DBrush* pB = new DBrush; + //----- Build Outer Bounds --------- + + vec3_t v1, v2, v3, v5, v6, v7; + VectorCopy(min, v1); + VectorCopy(min, v2); + VectorCopy(min, v3); + VectorCopy(max, v5); + VectorCopy(max, v6); + VectorCopy(max, v7); + + v2[0] = max[0]; + v3[1] = max[1]; + + v6[0] = min[0]; + v7[1] = min[1]; + + //---------------------------------- + + //----- Add Six Cube Faces --------- + + if(bUseFaces[0]) + pB->AddFace(v1, v2, v3, textureName, detail); + if(bUseFaces[1]) + pB->AddFace(v1, v3, v6, textureName, detail); + if(bUseFaces[2]) + pB->AddFace(v1, v7, v2, textureName, detail); + + if(bUseFaces[3]) + pB->AddFace(v5, v6, v3, textureName, detail); + if(bUseFaces[4]) + pB->AddFace(v5, v2, v7, textureName, detail); + if(bUseFaces[5]) + pB->AddFace(v5, v7, v6, textureName, detail); + + //---------------------------------- + + return pB; +} + +DBrush* DShape::GetBoundingCube(vec3_t min, vec3_t max, const char *textureName, DEntity* ent, bool* bUseFaces) +{ + DBrush* pB; + if(ent == NULL) + pB = m_Container.GetWorldSpawn()->NewBrush(m_nNextBrush++); + else + pB = ent->NewBrush(m_nNextBrush++); + + //----- Build Outer Bounds --------- + + vec3_t v1, v2, v3, v5, v6, v7; + VectorCopy(min, v1); + VectorCopy(min, v2); + VectorCopy(min, v3); + VectorCopy(max, v5); + VectorCopy(max, v6); + VectorCopy(max, v7); + + v2[0] = max[0]; + v3[1] = max[1]; + + v6[0] = min[0]; + v7[1] = min[1]; + + //---------------------------------- + + //----- Add Six Cube Faces --------- + + if(bUseFaces[0]) + pB->AddFace(v1, v2, v3, textureName, false); + if(bUseFaces[1]) + pB->AddFace(v1, v3, v6, textureName, false); + if(bUseFaces[2]) + pB->AddFace(v1, v7, v2, textureName, false); + + if(bUseFaces[3]) + pB->AddFace(v5, v6, v3, textureName, false); + if(bUseFaces[4]) + pB->AddFace(v5, v2, v7, textureName, false); + if(bUseFaces[5]) + pB->AddFace(v5, v7, v6, textureName, false); + + //---------------------------------- + + return pB; +} + +bool DShape::BuildPit(vec3_t min, vec3_t max) +{ + if((max[2] - min[2]) < 196) + return false; + + srand(time(NULL)); + + vec3_t centre; + VectorAdd(min, max, centre); + VectorScale(centre, 0.5f, centre); + + char buffer[256]; + + int team = (rand()%10000)+5000; + +// ************* SPEAKER *************** + sprintf(buffer, "t%i_1", team); + +// trigger for speaker + vec3_t triggerVoiceBtm; + VectorCopy(min, triggerVoiceBtm); + triggerVoiceBtm[2] = max[2] - 16; + + DEntity* triggerVoice = m_Container.AddEntity("trigger_multiple"); + GetBoundingCube(triggerVoiceBtm, max, "textures/common/trigger", triggerVoice); + triggerVoice->AddEPair("target", buffer); +//-------------------- + +// target for speaker + vec3_t voiceOrigin; + VectorCopy(centre, voiceOrigin); + voiceOrigin[2] = max[2]+16; + + + DEntity* targetVoice = m_Container.AddEntity("target_speaker"); + targetVoice->AddEPair("targetname", buffer); + + sprintf(buffer, "%f %f %f", voiceOrigin[0], voiceOrigin[1], voiceOrigin[2]); + targetVoice->AddEPair("origin", buffer); + targetVoice->AddEPair("spawnflags", "8"); + targetVoice->AddEPair("noise", "*falling1.wav"); +//-------------------- + +// *********** END SPEAKER ************* + +// ********* POWERUP REMOVAL *********** + sprintf(buffer, "t%i_2", team); + +// trigger for powerup removal + vec3_t triggerPwrRmvTop, triggerPwrRmvBtm; + VectorCopy(min, triggerPwrRmvBtm); + VectorCopy(max, triggerPwrRmvTop); + + triggerPwrRmvTop[2] = triggerVoiceBtm[2] - 64; + triggerPwrRmvBtm[2] = triggerPwrRmvTop[2] - 16; + + DEntity* triggerPwrRmv = m_Container.AddEntity("trigger_multiple"); + GetBoundingCube(triggerPwrRmvBtm, triggerPwrRmvTop, "textures/common/trigger", triggerPwrRmv); + triggerPwrRmv->AddEPair("target", buffer); +//-------------------- + +// target for powerup removal + vec3_t pwrRmvOrigin; + VectorCopy(centre, pwrRmvOrigin); + pwrRmvOrigin[2] = triggerPwrRmvTop[2]+16; + + DEntity* targetPwrRmv = m_Container.AddEntity("target_remove_powerups"); + targetPwrRmv->AddEPair("targetname", buffer); + + sprintf(buffer, "%f %f %f", pwrRmvOrigin[0], pwrRmvOrigin[1], pwrRmvOrigin[2]); + targetPwrRmv->AddEPair("origin", buffer); +//-------------------- + +// ****** END POWERUP REMOVAL ******** + +// ********* DAMAGE *********** + +// trigger for damage + vec3_t triggerDmgTop, triggerDmgBtm; + VectorCopy(min, triggerDmgBtm); + VectorCopy(max, triggerDmgTop); + + triggerDmgBtm[2] = min[2] + 64; + triggerDmgTop[2] = triggerDmgBtm[2] + 16; + + DEntity* triggerDmg = m_Container.AddEntity("trigger_hurt"); + GetBoundingCube(triggerDmgBtm, triggerDmgTop, "textures/common/trigger", triggerDmg); + triggerDmg->AddEPair("dmg", "9999"); + triggerDmg->AddEPair("spawnflags", "12"); +//-------------------- + +// ****** END DAMAGE ******** + +// ********* NODROP *********** + + vec3_t nodropTop; + VectorCopy(max, nodropTop); + + nodropTop[2] = min[2] + 64; + + GetBoundingCube(min, nodropTop, "textures/common/nodrop"); + +// ****** END NODROP ******** + + return true; +} diff --git a/contrib/bobtoolz/DShape.h b/contrib/bobtoolz/DShape.h new file mode 100644 index 00000000..3c4ed75e --- /dev/null +++ b/contrib/bobtoolz/DShape.h @@ -0,0 +1,62 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DShape.h: interface for the DShape class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_DSHAPE_H__0B30B302_9D21_4C2D_836A_61F3C8D4244D__INCLUDED_) +#define AFX_DSHAPE_H__0B30B302_9D21_4C2D_836A_61F3C8D4244D__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "mathlib.h" +#include "DMap.h" +class DBrush; +class DEntity; + +// defines for polygon stuff +#define MAX_POLYGON_FACES 128 + +extern bool bFacesAll[]; + +class DShape +{ +public: + bool BuildPit(vec3_t min, vec3_t max); + void BuildBorderedPrism(vec3_t min, vec3_t max, int nSides, int nBorder, bool bAlignTop); + void BuildInversePrism(vec3_t min, vec3_t max, int nSides, bool bAlignTop); + void BuildRegularPrism(vec3_t min, vec3_t max, int nSides, bool bAlignTop); + + int m_nNextBrush; + static DBrush* GetBoundingCube_Ext(vec3_t min, vec3_t max, const char* textureName, bool* bUseFaces = bFacesAll, bool detail = false); + + DShape(); + virtual ~DShape(); + + void Commit(); +private: + DBrush* GetBoundingCube(vec3_t min, vec3_t max, const char* textureName, DEntity* ent = NULL, bool* bUseFaces = bFacesAll); + + DMap m_Container; +}; + +#endif // !defined(AFX_DSHAPE_H__0B30B302_9D21_4C2D_836A_61F3C8D4244D__INCLUDED_) diff --git a/contrib/bobtoolz/DTrainDrawer.cpp b/contrib/bobtoolz/DTrainDrawer.cpp new file mode 100644 index 00000000..92b2333a --- /dev/null +++ b/contrib/bobtoolz/DTrainDrawer.cpp @@ -0,0 +1,339 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "DTrainDrawer.h" + +#include +#include "str.h" + +#include "DPoint.h" +#include "DPlane.h" +#include "DBrush.h" +#include "DEPair.h" +#include "DPatch.h" +#include "DEntity.h" + +#include "misc.h" +#include "funchandlers.h" + +#include "iglrender.h" +#include "ientity.h" +#include "math/matrix.h" + +#include "dialogs/dialogs-gtk.h" + +DTrainDrawer::DTrainDrawer() { + m_bDisplay = false; + + BuildPaths(); + constructShaders(); + GlobalShaderCache().attachRenderable(*this); +} + +DTrainDrawer::~DTrainDrawer(void) { + GlobalShaderCache().detachRenderable(*this); + destroyShaders(); + + ClearPoints(); + ClearSplines(); +} + +void DTrainDrawer::ClearSplines() { + for(std::list::const_iterator deadSpline = m_splineList.begin(); deadSpline != m_splineList.end(); deadSpline++) { + (*deadSpline)->m_pointList.clear(); + (*deadSpline)->m_vertexList.clear(); + delete (*deadSpline); + } + + m_splineList.clear(); +} + +void DTrainDrawer::ClearPoints() { + for(std::list::const_iterator deadPoint = m_pointList.begin(); deadPoint != m_pointList.end(); deadPoint++) { + delete *deadPoint; + } + + m_pointList.clear(); +} + +void CalculateSpline_r(vec3_t* v, int count, vec3_t out, float tension) { + vec3_t dist; + + if(count < 2) { + return; + } + + if(count == 2) { + VectorSubtract( v[1], v[0], dist ); + VectorMA(v[0], tension, dist, out); + return; + } + + vec3_t* v2 = new vec3_t[count-1]; + + for( int i = 0; i < count-1; i++ ) { + VectorSubtract( v[i+1], v[i], dist ); + VectorMA(v[i], tension, dist, v2[i]); + } + + CalculateSpline_r( v2, count-1, out, tension); + + delete[] v2; +} + +void DTrainDrawer::render(RenderStateFlags state) const +{ + for(std::list::const_iterator sp = m_splineList.begin(); sp != m_splineList.end(); sp++) { + splinePoint_t* pSP = (*sp); + + glBegin(GL_LINE_STRIP); + for(std::list::const_iterator v = pSP->m_vertexList.begin(); v != pSP->m_vertexList.end(); v++) { + glVertex3fv((*v)._pnt); + } + glEnd(); + + } +} + +const char* DTrainDrawer_state_wireframe = "$bobtoolz/traindrawer/wireframe"; +const char* DTrainDrawer_state_solid = "$bobtoolz/traindrawer/solid"; + +void DTrainDrawer::constructShaders() +{ + OpenGLState state; + GlobalOpenGLStateLibrary().getDefaultState(state); + state.m_state = RENDER_COLOURWRITE|RENDER_DEPTHWRITE|RENDER_BLEND; + state.m_sort = OpenGLState::eSortOverlayFirst; + state.m_linewidth = 1; + state.m_colour[0] = 1; + state.m_colour[1] = 0; + state.m_colour[2] = 0; + state.m_colour[3] = 1; + state.m_linewidth = 1; + GlobalOpenGLStateLibrary().insert(DTrainDrawer_state_wireframe, state); + + state.m_colour[0] = 1; + state.m_colour[1] = 1; + state.m_colour[2] = 1; + state.m_colour[3] = 1; + state.m_linewidth = 2; + GlobalOpenGLStateLibrary().insert(DTrainDrawer_state_solid, state); + + m_shader_wireframe = GlobalShaderCache().capture(DTrainDrawer_state_wireframe); + m_shader_solid = GlobalShaderCache().capture(DTrainDrawer_state_solid); +} + +void DTrainDrawer::destroyShaders() +{ + GlobalOpenGLStateLibrary().erase(DTrainDrawer_state_wireframe); + GlobalOpenGLStateLibrary().erase(DTrainDrawer_state_solid); + GlobalShaderCache().release(DTrainDrawer_state_wireframe); + GlobalShaderCache().release(DTrainDrawer_state_solid); +} + + +void DTrainDrawer::renderSolid(Renderer& renderer, const VolumeTest& volume) const +{ + if(!m_bDisplay) { + return; + } + + renderer.SetState(m_shader_wireframe, Renderer::eWireframeOnly); + renderer.SetState(m_shader_solid, Renderer::eFullMaterials); + renderer.addRenderable(*this, g_matrix4_identity); +} +void DTrainDrawer::renderWireframe(Renderer& renderer, const VolumeTest& volume) const +{ + renderSolid(renderer, volume); +} + +void AddSplineControl(const char* control, splinePoint_t* pSP) { + controlPoint_t cp; + strncpy(cp.strName, control, 64); + + pSP->m_pointList.push_front(cp); +} + +class EntityBuildPaths +{ + mutable DEntity e; + DTrainDrawer& drawer; +public: + EntityBuildPaths(DTrainDrawer& drawer) : drawer(drawer) + { + } + void operator()(scene::Instance& instance) const + { + e.ClearEPairs(); + e.LoadEPairList(Node_getEntity(instance.path().top())); + + const char* classname = e.m_Classname.GetBuffer(); + const char* target; + const char* control; + const char* targetname; + vec3_t vOrigin; + + e.SpawnString("targetname", NULL, &targetname); + e.SpawnVector("origin", "0 0 0", vOrigin); + + if(!strcmp(classname, "info_train_spline_main")) { + if(!targetname) { + globalOutputStream() << "info_train_spline_main with no targetname"; + return; + } + + e.SpawnString("target", NULL, &target); + + if(!target) { + drawer.AddControlPoint( targetname, vOrigin ); + } else { + splinePoint_t* pSP = drawer.AddSplinePoint( targetname, target, vOrigin ); + + e.SpawnString("control", NULL, &control); + + if(control) { + AddSplineControl( control, pSP ); + + for(int j = 2;; j++) { + char buffer[16]; + sprintf(buffer, "control%i", j); + + e.SpawnString(buffer, NULL, &control); + if(!control) { + break; + } + + AddSplineControl( control, pSP ); + } + } + } + } else if(!strcmp(classname, "info_train_spline_control")) { + if(!targetname) { + globalOutputStream() << "info_train_spline_control with no targetname"; + return; + } + + drawer.AddControlPoint( targetname, vOrigin ); + } + } +}; + +void DTrainDrawer::BuildPaths() { + Scene_forEachEntity(EntityBuildPaths(*this)); + + std::list::const_iterator sp; + for(sp = m_splineList.begin(); sp != m_splineList.end(); sp++) { + splinePoint_t* pSP = (*sp); + + controlPoint_t* pTarget = FindControlPoint( pSP->strTarget ); + + if(!pTarget) { + globalOutputStream() << "couldn't find target " << pSP->strTarget; + return; +// continue; + } + + pSP->pTarget = pTarget; + + + for(std::list::iterator cp = pSP->m_pointList.begin(); cp != pSP->m_pointList.end(); cp++) { + controlPoint_t* pControl = FindControlPoint( (*cp).strName ); + if(!pControl) { + globalOutputStream() << "couldn't find control " << (*cp).strName; + return; + } + + VectorCopy(pControl->vOrigin, (*cp).vOrigin); + } + } + + m_bDisplay = true; + + for(sp = m_splineList.begin(); sp != m_splineList.end(); sp++) { + splinePoint_t* pSP = (*sp); + DPoint out; + + if(!pSP->pTarget) { + continue; + } + + std::size_t count = pSP->m_pointList.size() + 2; + vec3_t* v = new vec3_t[count]; + + VectorCopy(pSP->point.vOrigin, v[0]); + + int i = 1; + for(std::list::reverse_iterator cp = pSP->m_pointList.rbegin(); cp != pSP->m_pointList.rend(); cp++) { + VectorCopy((*cp).vOrigin, v[i]); + i++; + } + VectorCopy(pSP->pTarget->vOrigin, v[i]); + + for (float tension = 0.0f; tension <= 1.f; tension += 0.01f) { + CalculateSpline_r(v, static_cast(count), out._pnt, tension); + pSP->m_vertexList.push_front(out); + } + + delete[] v; + + VectorCopy(pSP->pTarget->vOrigin, out._pnt); + pSP->m_vertexList.push_front(out); + } + + SceneChangeNotify(); +} + +void DTrainDrawer::AddControlPoint(const char* name, vec_t* origin) +{ + controlPoint_t* pCP = new controlPoint_t; + + strncpy(pCP->strName, name, 64); + VectorCopy( origin, pCP->vOrigin ); + + m_pointList.push_back( pCP ); +} + +splinePoint_t* DTrainDrawer::AddSplinePoint(const char* name, const char* target, vec_t* origin) +{ + splinePoint_t* pSP = new splinePoint_t; + + strncpy(pSP->point.strName, name, 64); + strncpy(pSP->strTarget, target, 64); + VectorCopy( origin, pSP->point.vOrigin ); + m_splineList.push_back( pSP ); + + return pSP; +} + +controlPoint_t* DTrainDrawer::FindControlPoint(const char* name) +{ + for(std::list::const_iterator cp = m_pointList.begin(); cp != m_pointList.end(); cp++) { + if(!strcmp(name, (*cp)->strName)) { + return (*cp); + } + } + + for(std::list::const_iterator sp = m_splineList.begin(); sp != m_splineList.end(); sp++) { + if(!strcmp(name, (*sp)->point.strName)) { + return &((*sp)->point); + } + } + + return NULL; +} diff --git a/contrib/bobtoolz/DTrainDrawer.h b/contrib/bobtoolz/DTrainDrawer.h new file mode 100644 index 00000000..9d8903ee --- /dev/null +++ b/contrib/bobtoolz/DTrainDrawer.h @@ -0,0 +1,88 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DTrainDrawer.h: interface for the DTrainDrawer class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_TRAINDRAWER_H__6E36062A_EF0B_11D4_ACF7_004095A18133__INCLUDED_) +#define AFX_TRAINDRAWER_H__6E36062A_EF0B_11D4_ACF7_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 + +#pragma once +#endif // _MSC_VER > 1000 + +#include +#include "mathlib.h" + +#include "irender.h" +#include "renderable.h" + +class DPoint; +class Shader; + +typedef struct { + char strName[64]; + + vec3_t vOrigin; +} controlPoint_t; + +typedef struct { + controlPoint_t point; + + char strControl[64]; + char strTarget[64]; + + std::list m_pointList; + std::list m_vertexList; + + controlPoint_t* pTarget; +} splinePoint_t; + +class DTrainDrawer : public Renderable, public OpenGLRenderable +{ +private: + std::list m_splineList; + std::list m_pointList; + + bool m_bDisplay; + Shader* m_shader_wireframe; + Shader* m_shader_solid; +public: + + DTrainDrawer(); + virtual ~DTrainDrawer(void); + + void render(RenderStateFlags state) const; + void renderSolid(Renderer& renderer, const VolumeTest& volume) const; + void renderWireframe(Renderer& renderer, const VolumeTest& volume) const; + + void constructShaders(); + void destroyShaders(); + + void ClearSplines(); + void ClearPoints(); + void BuildPaths(); + void AddControlPoint(const char* name, vec_t* origin); + splinePoint_t* AddSplinePoint(const char* name, const char* target, vec_t* origin); + controlPoint_t* FindControlPoint(const char* name); +}; + +#endif // !defined(AFX_TRAINDRAWER_H__6E36062A_EF0B_11D4_ACF7_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/DTreePlanter.cpp b/contrib/bobtoolz/DTreePlanter.cpp new file mode 100644 index 00000000..958bcf8b --- /dev/null +++ b/contrib/bobtoolz/DTreePlanter.cpp @@ -0,0 +1,291 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "DTreePlanter.h" + +#include +#include "str.h" + +#include "DPoint.h" +#include "DPlane.h" +#include "DBrush.h" +#include "DEPair.h" +#include "DPatch.h" +#include "DEntity.h" + +#include "ScriptParser.h" +#include "misc.h" +#include "scenelib.h" + + + +#include "funchandlers.h" + +SignalHandlerResult DTreePlanter::mouseDown(const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers) +{ + if(button != c_buttonLeft) + { + return SIGNAL_CONTINUE_EMISSION; + } + VIEWTYPE vt = GlobalRadiant().XYWindow_getViewType(); + + switch(vt) { + case XY: + break; + case YZ: + case XZ: + default: + return SIGNAL_CONTINUE_EMISSION; + } + + Vector3 pt, vhit; + + pt = vector3_snapped(GlobalRadiant().XYWindow_windowToWorld(position), GlobalRadiant().getGridSize()); + + if(FindDropPoint(vector3_to_array(pt), vector3_to_array(vhit))) { + vhit[2] += m_offset; + + char buffer[128]; + DEntity e(m_entType); + + sprintf(buffer, "%i %i %i", (int)vhit[0], (int)vhit[1], (int)vhit[2]); + e.AddEPair("origin", buffer); + + if(m_autoLink) { + + const scene::Path* pLastEntity = NULL; + const scene::Path* pThisEntity = NULL; + + int entpos; + for(int i = 0; i < 256; i++) { + sprintf(buffer, m_linkName, i); + pThisEntity = FindEntityFromTargetname( buffer ); + + if(pThisEntity) { + entpos = i; + pLastEntity = pThisEntity; + } + } + + if(!pLastEntity) { + sprintf(buffer, m_linkName, 0); + } else { + sprintf(buffer, m_linkName, entpos + 1); + } + + e.AddEPair( "targetname", buffer ); + + if(pLastEntity) { + DEntity e2; + e2.LoadFromEntity(pLastEntity->top(), true); + e2.AddEPair("target", buffer); + e2.RemoveFromRadiant(); + e2.BuildInRadiant(false); + } + + } + + if(m_setAngles) { + int angleYaw = (rand() % (m_maxYaw - m_minYaw + 1)) + m_minYaw; + int anglePitch = (rand() % (m_maxPitch - m_minPitch + 1)) + m_minPitch; + + sprintf(buffer, "%i %i 0", anglePitch, angleYaw); + e.AddEPair("angles", buffer); + } + + if(m_numModels) { + int treetype = rand() % m_numModels; + e.AddEPair("model", m_trees[treetype].name); + } + + if(m_useScale) { + float scale = (((rand()%1000)*0.001f) * (m_maxScale - m_minScale)) + m_minScale; + + sprintf(buffer, "%f", scale ); + e.AddEPair("modelscale", buffer); + } + + e.BuildInRadiant( false ); + } + + if(m_autoLink) { + DoTrainPathPlot(); + } + + return SIGNAL_STOP_EMISSION; +} + +bool DTreePlanter::FindDropPoint(vec3_t in, vec3_t out) { + DPlane p1; + DPlane p2; + + vec3_t vUp = { 0, 0, 1 }; + vec3_t vForward = { 0, 1, 0 }; + vec3_t vLeft = { 1, 0, 0 }; + + in[2] = 65535; + + VectorCopy(in, p1.points[0]); + VectorCopy(in, p1.points[1]); + VectorCopy(in, p1.points[2]); + VectorMA(p1.points[1], 20, vUp, p1.points[1]); + VectorMA(p1.points[1], 20, vLeft, p1.points[2]); + + VectorCopy(in, p2.points[0]); + VectorCopy(in, p2.points[1]); + VectorCopy(in, p2.points[2]); + VectorMA(p1.points[1], 20, vUp, p2.points[1]); + VectorMA(p1.points[1], 20, vForward, p2.points[2]); + + p1.Rebuild(); + p2.Rebuild(); + + bool found = false; + vec3_t temp; + vec_t dist; + int cnt = m_world.GetIDMax(); + for(int i = 0; i < cnt; i++) { + DBrush* pBrush = m_world.GetBrushForID( i ); + + if(pBrush->IntersectsWith( &p1, &p2, temp )) { + vec3_t diff; + vec_t tempdist; + VectorSubtract(in, temp, diff); + tempdist = VectorLength( diff ); + if(!found || (tempdist < dist)) { + dist = tempdist; + VectorCopy( temp, out ); + found = true; + } + } + } + + return found; +} + +class TreePlanterDropEntityIfSelected +{ + mutable DEntity ent; + DTreePlanter& planter; +public: + TreePlanterDropEntityIfSelected(DTreePlanter& planter) : planter(planter) + { + } + void operator()(scene::Instance& instance) const + { + if(!instance.isSelected()) + { + return; + } + ent.LoadFromEntity(instance.path().top()); + + DEPair* pEpair = ent.FindEPairByKey("origin"); + if(!pEpair) { + return; + } + + vec3_t vec, out; + sscanf( pEpair->value.GetBuffer(), "%f %f %f", &vec[0], &vec[1], &vec[2]); + + planter.FindDropPoint( vec, out ); + + char buffer[256]; + sprintf( buffer, "%f %f %f", out[0], out[1], out[2] ); + ent.AddEPair( "origin", buffer ); + ent.RemoveFromRadiant(); + ent.BuildInRadiant(false); + } +}; + +void DTreePlanter::DropEntsToGround( void ) { + Scene_forEachEntity(TreePlanterDropEntityIfSelected(*this)); +} + +void DTreePlanter::MakeChain( void ) { + char buffer[256]; + int i; + + for(i = 0; i < m_linkNum; i++) { + DEntity e("info_train_spline_main"); + + sprintf( buffer, "%s_pt%i", m_linkName, i ); + e.AddEPair( "targetname", buffer ); + + sprintf( buffer, "0 %i 0", i * 64 ); + e.AddEPair( "origin", buffer ); + + if(i != m_linkNum-1) { + sprintf( buffer, "%s_pt%i", m_linkName, i+1 ); + e.AddEPair( "target", buffer ); + + sprintf( buffer, "%s_ctl%i", m_linkName, i ); + e.AddEPair( "control", buffer ); + } + + e.BuildInRadiant( false ); + } + + for(i = 0; i < m_linkNum-1; i++) { + DEntity e("info_train_spline_control"); + + sprintf( buffer, "%s_ctl%i", m_linkName, i ); + e.AddEPair( "targetname", buffer ); + + sprintf( buffer, "0 %i 0", (i * 64) + 32); + e.AddEPair( "origin", buffer ); + + e.BuildInRadiant( false ); + } +} + +void DTreePlanter::SelectChain( void ) { +/* char buffer[256]; + + for(int i = 0; i < m_linkNum; i++) { + DEntity e("info_train_spline_main"); + + sprintf( buffer, "%s_pt%i", m_linkName, i ); + e.AddEPair( "targetname", buffer ); + + sprintf( buffer, "0 %i 0", i * 64 ); + e.AddEPair( "origin", buffer ); + + if(i != m_linkNum-1) { + sprintf( buffer, "%s_pt%i", m_linkName, i+1 ); + e.AddEPair( "target", buffer ); + + sprintf( buffer, "%s_ctl%i", m_linkName, i ); + e.AddEPair( "control", buffer ); + } + + e.BuildInRadiant( false ); + } + + for(int i = 0; i < m_linkNum-1; i++) { + DEntity e("info_train_spline_control"); + + sprintf( buffer, "%s_ctl%i", m_linkName, i ); + e.AddEPair( "targetname", buffer ); + + sprintf( buffer, "0 %i 0", (i * 64) + 32); + e.AddEPair( "origin", buffer ); + + e.BuildInRadiant( false ); + }*/ +} diff --git a/contrib/bobtoolz/DTreePlanter.h b/contrib/bobtoolz/DTreePlanter.h new file mode 100644 index 00000000..10421660 --- /dev/null +++ b/contrib/bobtoolz/DTreePlanter.h @@ -0,0 +1,211 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __DTREE_H__ +#define __DTREE_H__ + +#include "qerplugin.h" +#include "signal/isignal.h" +#include "string/string.h" + +#include "DEntity.h" +#include "ScriptParser.h" +#include "mathlib.h" +#include "misc.h" + +#define MAX_QPATH 64 + +typedef struct treeModel_s { + char name[MAX_QPATH]; +} treeModel_t; + +#define MAX_TP_MODELS 256 + +class DTreePlanter { + MouseEventHandlerId m_mouseDown; + SignalHandlerId m_destroyed; +public: + SignalHandlerResult mouseDown(const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers); + typedef Member3 MouseDownCaller; + void destroyed() + { + m_mouseDown = MouseEventHandlerId(); + m_destroyed = SignalHandlerId(); + } + typedef Member DestroyedCaller; + + DTreePlanter() { + m_numModels = 0; + m_offset = 0; + m_maxPitch = 0; + m_minPitch = 0; + m_maxYaw = 0; + m_minYaw = 0; + m_setAngles = false; + m_useScale = false; + m_autoLink = false; + m_linkNum = 0; + + m_world.LoadSelectedBrushes(); + + char buffer[256]; + GetFilename( buffer, "bt/tp_ent.txt" ); + + FILE* file = fopen( buffer, "rb" ); + if(file) { + fseek( file, 0, SEEK_END ); + int len = ftell( file ); + fseek( file, 0, SEEK_SET ); + + if(len) { + char* buf = new char[len+1]; + buf[len] = '\0'; + // parser will do the cleanup, dont delete. + + fread( buf, len, 1, file ); + + CScriptParser parser; + parser.SetScript( buf ); + + ReadConfig( &parser ); + } + + fclose( file ); + } + + m_mouseDown = GlobalRadiant().XYWindowMouseDown_connect(makeSignalHandler3(MouseDownCaller(), *this)); + m_destroyed = GlobalRadiant().XYWindowDestroyed_connect(makeSignalHandler(DestroyedCaller(), *this)); + } + + virtual ~DTreePlanter() + { + if(!m_mouseDown.isNull()) + { + GlobalRadiant().XYWindowMouseDown_disconnect(m_mouseDown); + } + if(!m_destroyed.isNull()) + { + GlobalRadiant().XYWindowDestroyed_disconnect(m_destroyed); + } + } + +#define MT(t) string_equal_nocase( pToken, t ) +#define GT pToken = pScriptParser->GetToken( true ) +#define CT if(!*pToken) { return; } + + void ReadConfig( CScriptParser* pScriptParser ) { + const char* GT; + CT; + + do { + GT; + if(*pToken == '}') { + break; + } + + if(MT("model")) { + if(m_numModels >= MAX_TP_MODELS) { + return; + } + + GT; CT; + + strncpy( m_trees[m_numModels++].name, pToken, MAX_QPATH ); + } else if(MT("link")) { + GT; CT; + + strncpy( m_linkName, pToken, MAX_QPATH ); + + m_autoLink = true; + } else if(MT("entity")) { + GT; CT; + + strncpy( m_entType, pToken, MAX_QPATH ); + } else if(MT("offset")) { + GT; CT; + + m_offset = atoi(pToken); + } else if(MT("pitch")) { + GT; CT; + + m_minPitch = atoi(pToken); + + GT; CT; + + m_maxPitch = atoi(pToken); + + m_setAngles = true; + } else if(MT("yaw")) { + GT; CT; + + m_minYaw = atoi(pToken); + + GT; CT; + + m_maxYaw = atoi(pToken); + + m_setAngles = true; + } else if(MT("scale")) { + GT; CT; + + m_minScale = static_cast(atof(pToken)); + + GT; CT; + + m_maxScale = static_cast(atof(pToken)); + + m_useScale = true; + } else if(MT("numlinks")) { + GT; CT; + + m_linkNum = atoi( pToken ); + } + } while( true ); + } + + bool FindDropPoint(vec3_t in, vec3_t out); + void DropEntsToGround( void ); + void MakeChain( void ); + void SelectChain( void ); + +private: + DEntity m_world; + + treeModel_t m_trees[MAX_TP_MODELS]; + + int m_numModels; + int m_offset; + int m_maxPitch; + int m_minPitch; + int m_maxYaw; + int m_minYaw; + + char m_entType[MAX_QPATH]; + char m_linkName[MAX_QPATH]; + int m_linkNum; + + float m_minScale; + float m_maxScale; + + bool m_useScale; + bool m_setAngles; + bool m_autoLink; +}; + +#endif diff --git a/contrib/bobtoolz/DVisDrawer.cpp b/contrib/bobtoolz/DVisDrawer.cpp new file mode 100644 index 00000000..0a936941 --- /dev/null +++ b/contrib/bobtoolz/DVisDrawer.cpp @@ -0,0 +1,144 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// BobView.cpp: implementation of the DVisDrawer class. +// +////////////////////////////////////////////////////////////////////// + +#include "DVisDrawer.h" + +#include "iglrender.h" +#include "math/matrix.h" + +#include +#include "str.h" + +#include "DPoint.h" +#include "DWinding.h" + +#include "misc.h" +#include "funchandlers.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +DVisDrawer::DVisDrawer() +{ + m_list = NULL; + + constructShaders(); + GlobalShaderCache().attachRenderable(*this); +} + +DVisDrawer::~DVisDrawer() +{ + GlobalShaderCache().detachRenderable(*this); + destroyShaders(); + + g_VisView = NULL; +} + +////////////////////////////////////////////////////////////////////// +// Implementation +////////////////////////////////////////////////////////////////////// +const char* g_state_solid = "$bobtoolz/visdrawer/solid"; +const char* g_state_wireframe = "$bobtoolz/visdrawer/wireframe"; + +void DVisDrawer::constructShaders() +{ + OpenGLState state; + GlobalOpenGLStateLibrary().getDefaultState(state); + state.m_state = RENDER_COLOURWRITE|RENDER_DEPTHWRITE|RENDER_COLOURCHANGE; + state.m_linewidth = 1; + + GlobalOpenGLStateLibrary().insert(g_state_wireframe, state); + + GlobalOpenGLStateLibrary().getDefaultState(state); + state.m_state = RENDER_FILL|RENDER_BLEND|RENDER_COLOURWRITE|RENDER_COLOURCHANGE|RENDER_SMOOTH|RENDER_DEPTHWRITE; + + GlobalOpenGLStateLibrary().insert(g_state_solid, state); + + m_shader_solid = GlobalShaderCache().capture(g_state_solid); + m_shader_wireframe = GlobalShaderCache().capture(g_state_wireframe); +} + +void DVisDrawer::destroyShaders() +{ + GlobalShaderCache().release(g_state_solid); + GlobalShaderCache().release(g_state_wireframe); + GlobalOpenGLStateLibrary().erase(g_state_solid); + GlobalOpenGLStateLibrary().erase(g_state_wireframe); +} + +void DVisDrawer::render(RenderStateFlags state) const +{ + //bleh + std::list::const_iterator l=m_list->begin(); + + for(; l != m_list->end(); l++) + { + DWinding* w = *l; + + glColor4f(w->clr[0], w->clr[1], w->clr[2], 0.5f); + + glBegin(GL_POLYGON); + for(int i = 0; i < w->numpoints; i++) { + glVertex3f((w->p[i])[0], (w->p[i])[1], (w->p[i])[2]); + } + glEnd(); + } +} + +void DVisDrawer::renderWireframe(Renderer& renderer, const VolumeTest& volume) const +{ + if(!m_list) + return; + + renderer.SetState(m_shader_wireframe, Renderer::eWireframeOnly); + + renderer.addRenderable(*this, g_matrix4_identity); +} + +void DVisDrawer::renderSolid(Renderer& renderer, const VolumeTest& volume) const +{ + if(!m_list) + return; + + renderer.SetState(m_shader_solid, Renderer::eWireframeOnly); + renderer.SetState(m_shader_solid, Renderer::eFullMaterials); + + renderer.addRenderable(*this, g_matrix4_identity); +} + +void DVisDrawer::SetList(std::list *pointList) +{ + if(m_list) + ClearPoints(); + + m_list = pointList; +} + +void DVisDrawer::ClearPoints() +{ + std::list::const_iterator deadPoint=m_list->begin(); + for(; deadPoint!=m_list->end(); deadPoint++) + delete *deadPoint; + m_list->clear(); +} diff --git a/contrib/bobtoolz/DVisDrawer.h b/contrib/bobtoolz/DVisDrawer.h new file mode 100644 index 00000000..83a80cb1 --- /dev/null +++ b/contrib/bobtoolz/DVisDrawer.h @@ -0,0 +1,62 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DBobView.h: interface for the DBobView class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_VISDRAWER_H__6E36062A_EF0B_11D4_ACF7_004095A18133__INCLUDED_) +#define AFX_VISDRAWER_H__6E36062A_EF0B_11D4_ACF7_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 + +#pragma once +#endif // _MSC_VER > 1000 + +#include +#include "renderable.h" +#include "irender.h" + +#include "DWinding.h" + +class DVisDrawer : public Renderable, public OpenGLRenderable +{ + Shader* m_shader_solid; + Shader* m_shader_wireframe; +public: + DVisDrawer(); + virtual ~DVisDrawer(); + +protected: + std::list* m_list; + int refCount; +public: + void ClearPoints(); + void SetList(std::list* pointList); + + void render(RenderStateFlags state) const; + void renderSolid(Renderer& renderer, const VolumeTest& volume) const; + void renderWireframe(Renderer& renderer, const VolumeTest& volume) const; + + void constructShaders(); + void destroyShaders(); + +}; + +#endif // !defined(AFX_VISDRAWER_H__6E36062A_EF0B_11D4_ACF7_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/DWinding.cpp b/contrib/bobtoolz/DWinding.cpp new file mode 100644 index 00000000..2068f3f8 --- /dev/null +++ b/contrib/bobtoolz/DWinding.cpp @@ -0,0 +1,485 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DWinding.cpp: implementation of the DWinding class. +// +////////////////////////////////////////////////////////////////////// + +#include "DWinding.h" + +#include + +#include "DPoint.h" +#include "DPlane.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +DWinding::DWinding() +{ + numpoints = 0; + p = NULL; +} + +DWinding::~DWinding() +{ + if(p) + delete[] p; +} + +////////////////////////////////////////////////////////////////////// +// Implementation +////////////////////////////////////////////////////////////////////// + +#define BOGUS_RANGE 4096 + +void DWinding::AllocWinding(int points) +{ + numpoints = points; + if(p) + delete[] p; + p = new vec3_t[points]; +} + +vec_t DWinding::WindingArea() +{ + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for (int i = 2; i < numpoints ; i++) + { + VectorSubtract (p[i-1], p[0], d1); + VectorSubtract (p[i], p[0], d2); + + CrossProduct (d1, d2, cross); + + total += 0.5f * VectorLength ( cross ); + } + + return total; +} + +void DWinding::RemoveColinearPoints() +{ + vec3_t p2[MAX_POINTS_ON_WINDING]; + + int nump = 0; + for (int i = 0; i < numpoints; i++) + { + int j = (i+1)%numpoints; + int k = (i+numpoints-1)%numpoints; + + vec3_t v1, v2; + VectorSubtract (p[j], p[i], v1); + VectorSubtract (p[i], p[k], v2); + VectorNormalize(v1, v1); + VectorNormalize(v2, v2); + + if (DotProduct(v1, v2) < 0.999) + { + VectorCopy (p[i], p2[nump]); + nump++; + } + } + + if (nump == numpoints) + return; + + AllocWinding(nump); + memcpy (p, p2, nump*sizeof(vec3_t)); +} + +DPlane* DWinding::WindingPlane() +{ + DPlane* newPlane = new DPlane(p[0], p[1], p[2], NULL); + return newPlane; +} + +void DWinding::WindingBounds(vec3_t mins, vec3_t maxs) +{ + if(numpoints == 0) + return; + + VectorCopy(mins, p[0]); + VectorCopy(maxs, p[0]); + + for (int i = 1; i < numpoints ;i++) + { + for (int j = 0; j < 3; j++) + { + vec_t v = p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } +} + +void DWinding::WindingCentre(vec3_t centre) +{ + VectorCopy (vec3_origin, centre); + for (int i = 0; i < numpoints; i++) + VectorAdd (p[i], centre, centre); + + float scale = 1.0f/numpoints; + VectorScale (centre, scale, centre); +} + + +DWinding* DWinding::CopyWinding() +{ + DWinding* c = new DWinding; + c->AllocWinding(numpoints); + memcpy (c->p, p, numpoints*sizeof(vec3_t)); + return c; +} + + +int DWinding::WindingOnPlaneSide(vec3_t normal, vec_t dist) +{ + bool front = false; + bool back = false; + + for (int i = 0; i < numpoints; i++) + { + vec_t d = DotProduct (p[i], normal) - dist; + if (d < -ON_EPSILON) + { + if (front) + return SIDE_CROSS; + back = true; + continue; + } + if (d > ON_EPSILON) + { + if (back) + return SIDE_CROSS; + front = true; + continue; + } + } + + if (back) + return SIDE_BACK; + if (front) + return SIDE_FRONT; + return SIDE_ON; +} + +void DWinding::CheckWinding() +{ + vec_t *p1, *p2; + vec_t edgedist; + vec3_t dir, edgenormal; + + if (numpoints < 3) + globalOutputStream() << "CheckWinding: " << numpoints << " points\n"; + + vec_t area = WindingArea(); + if (area < 1) + globalOutputStream() << "CheckWinding: " << area << " area\n"; + + DPlane* wPlane = WindingPlane (); + int i; + for (i = 0; i < numpoints; i++) + { + p1 = p[i]; + + int j; + for (j = 0; j < 3; j++) + if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) + globalOutputStream() << "CheckFace: BOGUS_RANGE: " << p1[j] << "\n"; + + j = i + 1 == numpoints ? 0 : i + 1; + + // check the point is on the face plane + vec_t d = DotProduct (p1, wPlane->normal) - wPlane->_d; + if (d < -ON_EPSILON || d > ON_EPSILON) + globalOutputStream() << "CheckWinding: point off plane\n"; + + // check the edge isnt degenerate + p2 = p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + globalOutputStream() << "CheckWinding: degenerate edge\n"; + + CrossProduct (wPlane->normal, dir, edgenormal); + VectorNormalize (edgenormal, edgenormal); + edgedist = DotProduct (p1, edgenormal); + + // all other points must be on front side + for (j = 0 ; j < numpoints ; j++) + { + if (j == i) + continue; + + d = DotProduct (p[j], edgenormal); + if (d > (edgedist + ON_EPSILON)) + globalOutputStream() << "CheckWinding: non-convex\n"; + } + } + + delete wPlane; +} + +DWinding* DWinding::ReverseWinding() +{ + DWinding* c = new DWinding; + c->AllocWinding(numpoints); + + for (int i = 0; i < numpoints ; i++) + VectorCopy (p[numpoints-1-i], c->p[i]); + + return c; +} + +bool DWinding::ChopWindingInPlace(DPlane* chopPlane, vec_t epsilon) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t *p1, *p2; + vec3_t mid; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + int i; + for (i = 0; i < numpoints; i++) + { + vec_t dot = DotProduct (p[i], chopPlane->normal); + dot -= chopPlane->_d; + dists[i] = dot; + + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + sides[i] = SIDE_ON; + + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + { + delete this; + return false; + } + + if (!counts[1]) + return true; + + int maxpts = numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + DWinding* f = new DWinding; + f->AllocWinding(maxpts); + f->numpoints = 0; + + for (i = 0; i < numpoints; i++) + { + p1 = p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = p[(i+1)%numpoints]; + + vec_t dot = dists[i] / (dists[i]-dists[i+1]); + for (int j = 0; j < 3; j++) + { + if (chopPlane->normal[j] == 1) + mid[j] = chopPlane->_d; + else if (chopPlane->normal[j] == -1) + mid[j] = -chopPlane->_d; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + } + + if (f->numpoints > maxpts) + globalOutputStream() << "ClipWinding: points exceeded estimate\n"; + if (f->numpoints > MAX_POINTS_ON_WINDING) + globalOutputStream() << "ClipWinding: MAX_POINTS_ON_WINDING\n"; + + delete[] p; + p = f->p; + f->p = NULL; + delete f; + return true; +} + +void DWinding::ClipWindingEpsilon(DPlane* chopPlane, vec_t epsilon, DWinding **front, DWinding **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t *p1, *p2; + vec3_t mid; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + int i; + for (i = 0; i < numpoints; i++) + { + vec_t dot = -chopPlane->DistanceToPoint(p[i]); + dists[i] = dot; + + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + sides[i] = SIDE_ON; + + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = CopyWinding(); + return; + } + if (!counts[1]) + { + *front = CopyWinding(); + return; + } + + int maxpts = numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + DWinding* f = new DWinding; + DWinding* b = new DWinding; + + f->AllocWinding(maxpts); + f->numpoints = 0; + + b->AllocWinding(maxpts); + b->numpoints = 0; + + *front = f; + *back = b; + + for (i = 0; i < numpoints ; i++) + { + p1 = p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = p[(i+1)%numpoints]; + + vec_t dot = dists[i] / (dists[i]-dists[i+1]); + for (int j = 0; j < 3; j++) + { + if (chopPlane->normal[j] == 1) + mid[j] = chopPlane->_d; + else if (chopPlane->normal[j] == -1) + mid[j] = -chopPlane->_d; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + globalOutputStream() << "ClipWinding: points exceeded estimate\n"; + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + globalOutputStream() << "ClipWinding: MAX_POINTS_ON_WINDING\n"; +} + +bool DWinding::ChopWinding(DPlane* chopPlane) +{ + DWinding *f, *b; + + ClipWindingEpsilon (chopPlane, (float)ON_EPSILON, &f, &b); + + if (b) + delete (b); + + + if(!f) + { + delete this; + return false; + } + + delete[] p; + p = f->p; + f->p = NULL; + numpoints = f->numpoints; + delete f; + + return true; +} diff --git a/contrib/bobtoolz/DWinding.h b/contrib/bobtoolz/DWinding.h new file mode 100644 index 00000000..52846aab --- /dev/null +++ b/contrib/bobtoolz/DWinding.h @@ -0,0 +1,70 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DWinding.h: interface for the DWinding class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_DWINDING_H__35B2C524_F0A7_11D4_ACF7_004095A18133__INCLUDED_) +#define AFX_DWINDING_H__35B2C524_F0A7_11D4_ACF7_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "mathlib.h" + +class DPlane; + +class DWinding +{ +public: + DWinding(); + virtual ~DWinding(); + + void AllocWinding(int points); + + bool ChopWinding(DPlane* chopPlane); + bool ChopWindingInPlace(DPlane* chopPlane, vec_t ON_EPSILON); + void ClipWindingEpsilon(DPlane* chopPlane, vec_t epsilon, DWinding** front, DWinding** back); + + void CheckWinding(); + void WindingCentre(vec3_t centre); + void WindingBounds(vec3_t mins, vec3_t maxs); + void RemoveColinearPoints(); + + DWinding* ReverseWinding(); + DWinding* CopyWinding(); + DPlane* WindingPlane(); + + int WindingOnPlaneSide(vec3_t normal, vec_t dist); + + vec_t WindingArea(); + +// members + int numpoints; + vec3_t* p; + vec3_t clr; +}; + +#define MAX_POINTS_ON_WINDING 64 + +#define ON_EPSILON 0.01 + +#endif // !defined(AFX_DWINDING_H__35B2C524_F0A7_11D4_ACF7_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/ScriptParser.cpp b/contrib/bobtoolz/ScriptParser.cpp new file mode 100644 index 00000000..826613e4 --- /dev/null +++ b/contrib/bobtoolz/ScriptParser.cpp @@ -0,0 +1,286 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "ScriptParser.h" +#include + +CScriptParser::CScriptParser(void): + m_pScript(NULL), + m_pScriptSection(NULL), + m_pLastScriptSection(NULL), + m_pToken(NULL) { + ClearBuffer(); +} + +CScriptParser::~CScriptParser(void) { + ClearBuffer(); +} + +void CScriptParser::ClearBuffer(void) { + if(m_pScript) { + delete[] m_pScript; + m_pScript = NULL; + } + if(m_pToken) { + delete[] m_pToken; + m_pToken = NULL; + } + m_pScriptSection = NULL; + m_pLastScriptSection = NULL; + memset(m_breakChars, 0, sizeof(m_breakChars)); +} + +const char* CScriptParser::MakeToken(const char* pToken) { + if(m_pToken) { + delete[] m_pToken; + m_pToken = NULL; + } + + if(!pToken) { + pToken = ""; + } + + int len = static_cast(strlen(pToken)); + + m_pToken = new char[len + 1]; + m_pToken[len] = '\0'; + strcpy(m_pToken, pToken); + + return m_pToken; +} + +#define MAX_TOKEN_STRING 1024 +// Should NEVER return NULL +const char* CScriptParser::GetToken(bool bAllowLinebreaks) { + int c = 0, len; + char token[MAX_TOKEN_STRING]; + bool bNewLines = false; + + m_pLastScriptSection = m_pScriptSection; + + len = 0; + *token = '\0'; + + if(!m_pScript || !m_pScriptSection) { + return MakeToken(token); + } + + while ( true ) { + SkipWhitespace( &bNewLines ); + if ( !*m_pScriptSection ) { + return MakeToken(token); + } + if ( bNewLines && !bAllowLinebreaks ) { + return MakeToken(token); + } + + c = *m_pScriptSection; + + if ( c == '/' && m_pScriptSection[1] == '/' ) { // C style comments + m_pScriptSection += 2; + while (*m_pScriptSection && *m_pScriptSection != '\n') { + m_pScriptSection++; + } + } else if ( c=='/' && m_pScriptSection[1] == '*' ) { // C++ style comments + m_pScriptSection += 2; + while ( *m_pScriptSection && ( *m_pScriptSection != '*' || m_pScriptSection[1] != '/' ) ) { + m_pScriptSection++; + } + if ( *m_pScriptSection ) { + m_pScriptSection += 2; + } + } else { + break; + } + } + + if (c == '\"') { + m_pScriptSection++; + while ( true ) { + c = *m_pScriptSection++; + if (c=='\"' || !c) { + token[len] = 0; + return MakeToken(token); + } + if (len < MAX_TOKEN_STRING) { + token[len] = c; + len++; + } + } + } + + do { + if(len > 0 && IsBreakChar(*m_pScriptSection)) { + break; + } + + if (len < MAX_TOKEN_STRING) { + token[len] = c; + len++; + } + m_pScriptSection++; + + if(IsBreakChar(c)) { + break; + } + + c = *m_pScriptSection; + } while (c > 32); + + if (len == MAX_TOKEN_STRING) { + len = 0; + } + token[len] = 0; + + return MakeToken(token); +} + +void CScriptParser::SkipWhitespace(bool* pbNewLines) { + int c; + + if(!m_pScript || !m_pScriptSection) { + return; + } + + while( (c = *m_pScriptSection) <= ' ') { + if( !c ) { + return; + } + if( c == '\n' ) { + *pbNewLines = true; + } + m_pScriptSection++; + } +} + +void CScriptParser::SkipBracedSection(void) { + const char *token; + int depth; + + depth = 0; + do { + token = GetToken( true ); + if( token[1] == 0 ) { + if( *token == '{' ) { + depth++; + } else if( *token == '}' ) { + depth--; + } + } + } while( depth && *m_pScriptSection ); +} + +void CScriptParser::SkipRestOfLine(void) { + char *p; + int c; + + p = m_pScriptSection; + while ( (c = *p++) != 0 ) { + if ( c == '\n' ) { + break; + } + } + m_pScriptSection = p; +} + +void CScriptParser::UndoGetToken(void) { + if(!m_pLastScriptSection) { + return; + } + m_pScriptSection = m_pLastScriptSection; + m_pLastScriptSection = NULL; +} + +void CScriptParser::ResetParseSession(void) { + if(!m_pScript) { + return; + } + + m_pScriptSection = m_pScript; + m_pLastScriptSection = NULL; +} + +char* CScriptParser::GetBufferCopy(void) { + if(!m_pScript) { + return NULL; + } + + int len = static_cast(strlen(m_pScript)); + char* pBuffer = new char[len + 1]; + strcpy(pBuffer, m_pScript); + return pBuffer; +} + +int CScriptParser::GetTokenOffset(void) { + if(!m_pScript || !m_pScriptSection) { + return 0; + } + + return static_cast(m_pScriptSection - m_pScript); +} + +void CScriptParser::LoadScript(const char* pScript) { + ClearBuffer(); + + int len = static_cast(strlen(pScript)); + if(len <= 0) { + return; + } + + m_pScript = new char[len + 1]; + m_pScript[len] = '\0'; + + strcpy(m_pScript, pScript); + m_pScriptSection = m_pScript; +} + +void CScriptParser::AddBreakChar(char c) { + for(int i = 0; i < SP_MAX_BREAKCHARS; i++) { + if(!m_breakChars[i]) { + m_breakChars[i] = c; + return; + } + } + + // TODO: Error: max break chars hit +} + +bool CScriptParser::IsBreakChar(char c) { + for(int i = 0; i < SP_MAX_BREAKCHARS; i++) { + if(!m_breakChars[i]) { + return false; + } + if(m_breakChars[i] == c) { + return true; + } + } + return false; +} + +void CScriptParser::SetScript(char* pScript) { + ClearBuffer(); + + int len = static_cast(strlen(pScript)); + if(len <= 0) { + return; + } + + m_pScript = pScript; + m_pScriptSection = m_pScript; +} diff --git a/contrib/bobtoolz/ScriptParser.h b/contrib/bobtoolz/ScriptParser.h new file mode 100644 index 00000000..27eb6df7 --- /dev/null +++ b/contrib/bobtoolz/ScriptParser.h @@ -0,0 +1,61 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#ifndef _SCRIPTPARSER_H_ +#define _SCRIPTPARSER_H_ + +//#include "interfaces/IScriptParser.h" + +#define SP_MAX_BREAKCHARS 16 + +class CScriptParser //: public IScriptParser +{ +public: + CScriptParser(void); + ~CScriptParser(void); +private: + char m_breakChars[SP_MAX_BREAKCHARS]; + char* m_pScript; + char* m_pScriptSection; + char* m_pLastScriptSection; + char* m_pToken; + + void SkipWhitespace(bool* pbNewLines); + void ClearBuffer(void); + const char* MakeToken(const char* pToken); + bool IsBreakChar(char c); +public: + const char* GetToken(bool bAllowLinebreaks); + void SkipBracedSection(void); + void SkipRestOfLine(void); + void UndoGetToken(void); + void ResetParseSession(void); + + char* GetBufferCopy(void); + int GetTokenOffset(void); + + void LoadScript(const char* pScript); + void SetScript(char* pScript); + + void AddBreakChar(char c); +private: +}; + +#endif diff --git a/contrib/bobtoolz/StdAfx.cpp b/contrib/bobtoolz/StdAfx.cpp new file mode 100644 index 00000000..1a02f66b --- /dev/null +++ b/contrib/bobtoolz/StdAfx.cpp @@ -0,0 +1,25 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// stdafx.cpp : source file that includes just the standard includes +// plugin.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "StdAfx.h" + diff --git a/contrib/bobtoolz/StdAfx.h b/contrib/bobtoolz/StdAfx.h new file mode 100644 index 00000000..b8897f39 --- /dev/null +++ b/contrib/bobtoolz/StdAfx.h @@ -0,0 +1,25 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __STDAFX_BOBTOOLZ__ +#define __STDAFX_BOBTOOLZ__ + + + +#endif diff --git a/contrib/bobtoolz/bitmaps/bobtoolz_caulk.bmp b/contrib/bobtoolz/bitmaps/bobtoolz_caulk.bmp new file mode 100644 index 0000000000000000000000000000000000000000..522395034ccf7b609f78b287d027a125bac211a9 GIT binary patch literal 320 zcmZusK?;B%5S&CK=ukwD=o3M7uLt%PpZtHaK$x*uBu#mTymBRP-_I@W}# z2ZSx9P!MB;td+~p9^ncwek?|Ah$&k-ib0))637(kd6$OYm5K=^pAGz04teun@5|3g(_ zQedN*KmaVq$OsZZ=A!dK%9ubZfHXukSOkp^QGk#qjt@2v?jnc%a^)Uk83x?I_Vp=P%~v1>A*v+ zQ)*zAUT!&XQ*F7o7{TgkAP O)&Ebeo`0--ib0))637(kc}$OYm5K==m;e*&Q$5Q4z}|Np@> z7J>yuj0FVXA`muQ1TF(*ASncEp_mUffDvX9&}JqE1{P){gMmT_9>`Wy9^C051^~OX B3%~#X literal 0 HcmV?d00001 diff --git a/contrib/bobtoolz/bitmaps/bobtoolz_split.bmp b/contrib/bobtoolz/bitmaps/bobtoolz_split.bmp new file mode 100644 index 0000000000000000000000000000000000000000..fbb1571ce1c870ddc44f5aa9eaa6106ba261274d GIT binary patch literal 312 zcmaKmF%p0v3!zxzy&rTA=|MU!gl(Fny8U7^z^_* z%|lYhyL5BO11D9?0q$ySoO>(g0()P$CAL)JRyYF0ml+UNw!nsY{p9&u%P~={57a0z MPcva~&g%H%2bECipJOMNtNMH`363}GgS?TM zOt`QDAM$<;p1Btr*Sc<~wW5@QoYld+HP^AsGqfP3Xe0c54 z5eK4R&knqaV-t`rEIe3*YcO72;!u^(jQO?0Gy&rX3`2+0<<5wVf2(b`vYFLdQ{l>* xmHUd#5D|IZuZS_@lm2`{0gCR^ljLrayy2m{puJmsuY1Vrk5Y)4{pU-X^9x&$8s7i_ literal 0 HcmV?d00001 diff --git a/contrib/bobtoolz/bitmaps/bobtoolz_turnedge.bmp b/contrib/bobtoolz/bitmaps/bobtoolz_turnedge.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b72cb2aae6326e7c82b8be43f02ff38910d8eaf2 GIT binary patch literal 332 zcmZvW!3{zo5JZR1hiE)#j6LYZ9?U-&588m0dD7;rzy@$;7e0w`$t?R|Sa!Ny11#>$ z2WmsDselDD@S(OV*OIkVxL)Ty@IC?0BXHT-Deyokh4r5pLXUy>AvRM^nt>YaBq!C9 rIf#wr5O$7+opZ7t^}Wp2qq3XnaCVf#xFXw+#`?%3iht*Cm-GGw>kkck literal 0 HcmV?d00001 diff --git a/contrib/bobtoolz/bobToolz-GTK.cpp b/contrib/bobtoolz/bobToolz-GTK.cpp new file mode 100644 index 00000000..be4f7804 --- /dev/null +++ b/contrib/bobtoolz/bobToolz-GTK.cpp @@ -0,0 +1,328 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + + +#include "str.h" +#include "qerplugin.h" +#include "mathlib.h" +#include "string/string.h" +#include "itoolbar.h" + +#include "funchandlers.h" +#include "DBobView.h" +#include "DVisDrawer.h" +#include "DTrainDrawer.h" +#include "DTreePlanter.h" + +#include "dialogs/dialogs-gtk.h" +#include "../../libs/cmdlib.h" + +void BobToolz_construct() +{ +} + +void BobToolz_destroy() +{ + if(g_PathView) { + delete g_PathView; + g_PathView = NULL; + } + if(g_VisView) { + delete g_VisView; + g_VisView = NULL; + } + if(g_TrainView) { + delete g_TrainView; + g_TrainView = NULL; + } + if(g_TreePlanter) { + delete g_TreePlanter; + g_TreePlanter = NULL; + } +} + +// plugin name +char* PLUGIN_NAME = "bobToolz"; + +// commands in the menu +static char* PLUGIN_COMMANDS = "About...,-,Reset Textures...,PitOMatic,-,Vis Viewer,Brush Cleanup,Polygon Builder,Caulk Selection,-,Tree Planter,Drop Entity,Plot Splines,-,Merge Patches,Split patches,Turn edge"; + +// globals +GtkWidget *g_pRadiantWnd = NULL; + +static const char *PLUGIN_ABOUT = "bobToolz for SDRadiant\n" + "by digibob (digibob@splashdamage.com)\n" + "http://www.splashdamage.com\n\n" + "Additional Contributors:\n" + "MarsMattel, RR2DO2\n"; + +extern "C" const char* QERPlug_Init( void* hApp, void* pMainWidget ) { + g_pRadiantWnd = (GtkWidget*)pMainWidget; + + return "bobToolz for GTKradiant"; +} + +extern "C" const char* QERPlug_GetName() { + return PLUGIN_NAME; +} + +extern "C" const char* QERPlug_GetCommandList() { + return PLUGIN_COMMANDS; +} + +extern "C" void QERPlug_Dispatch (const char *p, vec3_t vMin, vec3_t vMax, bool bSingleBrush) { + LoadLists(); + + if( string_equal_nocase( p, "brush cleanup" ) ) { + DoFixBrushes(); + } else if( string_equal_nocase( p, "polygon builder" ) ) { + DoPolygonsTB(); + } else if( string_equal_nocase( p, "caulk selection" ) ) { + DoCaulkSelection(); + } else if( string_equal_nocase( p, "tree planter" ) ) { + DoTreePlanter(); + } else if( string_equal_nocase( p, "plot splines" ) ) { + DoTrainPathPlot(); + } else if( string_equal_nocase( p, "drop entity" ) ) { + DoDropEnts(); + } else if( string_equal_nocase( p, "merge patches" ) ) { + DoMergePatches(); + } else if( string_equal_nocase( p, "split patches" ) ) { + DoSplitPatch(); + } else if( string_equal_nocase( p, "turn edge" ) ) { + DoFlipTerrain(); + } else if( string_equal_nocase(p, "reset textures...") ) { + DoResetTextures(); + } else if( string_equal_nocase(p, "pitomatic") ) { + DoPitBuilder(); + } else if( string_equal_nocase(p, "vis viewer") ) { + DoVisAnalyse(); + } else if( string_equal_nocase(p, "about...") ) { + DoMessageBox(PLUGIN_ABOUT, "About", eMB_OK); + } +} + +const char* QERPlug_GetCommandTitleList() +{ + return ""; +} + + +#define NUM_TOOLBARBUTTONS 9 + +std::size_t ToolbarButtonCount( void ) { + return NUM_TOOLBARBUTTONS; +} + +class CBobtoolzToolbarButton : public IToolbarButton +{ +public: + virtual const char* getImage() const + { + switch( mIndex ) { + case 0: return "bobtoolz_cleanup.bmp"; + case 1: return "bobtoolz_poly.bmp"; + case 2: return "bobtoolz_caulk.bmp"; + case 3: return "bobtoolz_treeplanter.bmp"; + case 4: return "bobtoolz_trainpathplot.bmp"; + case 5: return "bobtoolz_dropent.bmp"; + case 6: return "bobtoolz_merge.bmp"; + case 7: return "bobtoolz_split.bmp"; + case 8: return "bobtoolz_turnedge.bmp"; + } + return NULL; + } + virtual EType getType() const + { + switch( mIndex ) { + case 3: return eToggleButton; + default: return eButton; + } + } + virtual const char* getText() const + { + switch( mIndex ) { + case 0: return "Cleanup"; + case 1: return "Polygons"; + case 2: return "Caulk"; + case 3: return "Tree Planter"; + case 4: return "Plot Splines"; + case 5: return "Drop Entity"; + case 6: return "Merge Patches"; + case 7: return "Split Patches"; + case 8: return "Flip Terrain"; + } + return NULL; + } + virtual const char* getTooltip() const + { + switch( mIndex ) { + case 0: return "Brush Cleanup"; + case 1: return "Polygons"; + case 2: return "Caulk selection"; + case 3: return "Tree Planter"; + case 4: return "Plot Splines"; + case 5: return "Drop Entity"; + case 6: return "Merge Patches"; + case 7: return "Split Patches"; + case 8: return "Flip Terrain"; + } + return NULL; + } + + virtual void activate() const + { + LoadLists(); + + switch( mIndex ) { + case 0: DoFixBrushes(); break; + case 1: DoPolygonsTB(); break; + case 2: DoCaulkSelection(); break; + case 3: DoTreePlanter(); break; + case 4: DoTrainPathPlot(); break; + case 5: DoDropEnts(); break; + case 6: DoMergePatches(); break; + case 7: DoSplitPatch(); break; + case 8: DoFlipTerrain(); break; + } + } + + std::size_t mIndex; +}; + +CBobtoolzToolbarButton g_bobtoolzToolbarButtons[NUM_TOOLBARBUTTONS]; + +const IToolbarButton* GetToolbarButton(std::size_t index) +{ + g_bobtoolzToolbarButtons[index].mIndex = index; + return &g_bobtoolzToolbarButtons[index]; +} + + +#include "modulesystem/singletonmodule.h" + +#include "iscenegraph.h" +#include "irender.h" +#include "iundo.h" +#include "ishaders.h" +#include "ipatch.h" +#include "ibrush.h" +#include "ientity.h" +#include "ieclass.h" +#include "iglrender.h" +#include "iplugin.h" + +class BobToolzPluginDependencies : + public GlobalRadiantModuleRef, + public GlobalUndoModuleRef, + public GlobalSceneGraphModuleRef, + public GlobalSelectionModuleRef, + public GlobalEntityModuleRef, + public GlobalEntityClassManagerModuleRef, + public GlobalShadersModuleRef, + public GlobalShaderCacheModuleRef, + public GlobalBrushModuleRef, + public GlobalPatchModuleRef, + public GlobalOpenGLModuleRef, + public GlobalOpenGLStateLibraryModuleRef +{ +public: + BobToolzPluginDependencies() : + GlobalEntityModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("entities")), + GlobalShadersModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("shaders")), + GlobalBrushModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("brushtypes")), + GlobalPatchModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("patchtypes")), + GlobalEntityClassManagerModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("entityclass")) + { + } +}; + +class BobToolzPluginModule : public TypeSystemRef +{ + _QERPluginTable m_plugin; +public: + typedef _QERPluginTable Type; + STRING_CONSTANT(Name, "bobtoolz"); + + BobToolzPluginModule() + { + m_plugin.m_pfnQERPlug_Init = QERPlug_Init; + m_plugin.m_pfnQERPlug_GetName = QERPlug_GetName; + m_plugin.m_pfnQERPlug_GetCommandList = QERPlug_GetCommandList; + m_plugin.m_pfnQERPlug_GetCommandTitleList = QERPlug_GetCommandTitleList; + m_plugin.m_pfnQERPlug_Dispatch = QERPlug_Dispatch; + + BobToolz_construct(); + } + ~BobToolzPluginModule() + { + BobToolz_destroy(); + } + _QERPluginTable* getTable() + { + return &m_plugin; + } +}; + +typedef SingletonModule SingletonBobToolzPluginModule; + +SingletonBobToolzPluginModule g_BobToolzPluginModule; + + +class BobToolzToolbarDependencies : + public ModuleRef<_QERPluginTable> +{ +public: + BobToolzToolbarDependencies() : + ModuleRef<_QERPluginTable>("bobtoolz") + { + } +}; + +class BobToolzToolbarModule : public TypeSystemRef +{ + _QERPlugToolbarTable m_table; +public: + typedef _QERPlugToolbarTable Type; + STRING_CONSTANT(Name, "bobtoolz"); + + BobToolzToolbarModule() + { + m_table.m_pfnToolbarButtonCount = ToolbarButtonCount; + m_table.m_pfnGetToolbarButton = GetToolbarButton; + } + _QERPlugToolbarTable* getTable() + { + return &m_table; + } +}; + +typedef SingletonModule SingletonBobToolzToolbarModule; + +SingletonBobToolzToolbarModule g_BobToolzToolbarModule; + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer& server) +{ + initialiseModule(server); + + g_BobToolzPluginModule.selfRegister(); + g_BobToolzToolbarModule.selfRegister(); +} diff --git a/contrib/bobtoolz/bobToolz.def b/contrib/bobtoolz/bobToolz.def new file mode 100644 index 00000000..74f9189b --- /dev/null +++ b/contrib/bobtoolz/bobToolz.def @@ -0,0 +1,8 @@ +; plugin.def : Declares the module parameters for the DLL. + +LIBRARY "bobToolz" +; DESCRIPTION 'bobToolz Windows Dynamic Link Library' + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/contrib/bobtoolz/bobToolz.h b/contrib/bobtoolz/bobToolz.h new file mode 100644 index 00000000..6101e877 --- /dev/null +++ b/contrib/bobtoolz/bobToolz.h @@ -0,0 +1,64 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// plugin.h : main header file for the PLUGIN DLL +// + +#if !defined(AFX_PLUGIN_H__3BA55F6A_1D27_11D3_BC7B_F7EFD9765E37__INCLUDED_) +#define AFX_PLUGIN_H__3BA55F6A_1D27_11D3_BC7B_F7EFD9765E37__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef __AFXWIN_H__ + #error include 'StdAfx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CPluginApp +// See plugin.cpp for the implementation of this class +// + +class CPluginApp : public CWinApp +{ +public: + CPluginApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CPluginApp) + //}}AFX_VIRTUAL + + //{{AFX_MSG(CPluginApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Developer Studio will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_PLUGIN_H__3BA55F6A_1D27_11D3_BC7B_F7EFD9765E37__INCLUDED_) diff --git a/contrib/bobtoolz/bobToolz.rc b/contrib/bobtoolz/bobToolz.rc new file mode 100644 index 00000000..4abed936 --- /dev/null +++ b/contrib/bobtoolz/bobToolz.rc @@ -0,0 +1,533 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "resource.h" +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,1,1,0 + PRODUCTVERSION 2,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080904b0" + BEGIN + VALUE "Comments", "by djbob :P and MarsMattel ~:]\0" + VALUE "CompanyName", "bobCo\0" + VALUE "FileDescription", "All your plugins are belong to us\0" + VALUE "FileVersion", "1, 1, 1, 0\0" + VALUE "InternalName", "bobToolz\0" + VALUE "LegalCopyright", "Copyright (C) y2k\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "bobToolz.DLL\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "\0" + VALUE "ProductVersion", "2, 0, 0, 0\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x809, 1200 + END +END + +#endif // !_MAC + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// Neutral (Sys. Default) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEUSD) +#ifdef _WIN32 +LANGUAGE LANG_NEUTRAL, SUBLANG_SYS_DEFAULT +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUT DIALOG DISCARDABLE 0, 0, 361, 118 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About" +FONT 8, "Arial" +BEGIN + DEFPUSHBUTTON "OK",IDOK,305,100,50,14 + CTEXT "BobToolz - Q3radiant version by djbob &&&& Mars Mattel", + IDC_STATIC,5,5,350,14,SS_CENTERIMAGE + CTEXT "Latest Version Can Be Found At http://djbob.fortefide.com", + IDC_STATIC,5,20,350,15,SS_CENTERIMAGE + LTEXT "Random bobToolz comments:",IDC_STATIC,5,35,355,10 + CTEXT "Texture Alignments Are Irrelevant, You Will Be Caulked\n-QPSiren\nCaulk Me Baby One More Time\n-TTimo\nWe're Up The Creek Without A Penguin\n-Mars Mattel", + IDC_STATIC,5,45,355,50 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_ABOUT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 354 + TOPMARGIN, 7 + BOTTOMMARGIN, 111 + END +END +#endif // APSTUDIO_INVOKED + +#endif // Neutral (Sys. Default) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English (U.K.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_STAIR_DIALOG DIALOG DISCARDABLE 0, 0, 246, 118 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Stair Designer" +FONT 8, "Arial" +BEGIN + DEFPUSHBUTTON "OK",IDOK,189,83,50,12 + PUSHBUTTON "Cancel",IDCANCEL,189,99,50,12 + EDITTEXT IDC_EDIT1,5,5,86,12,ES_AUTOHSCROLL + LTEXT "Stair Height",IDC_STATIC,95,5,42,15,SS_CENTERIMAGE + GROUPBOX "Direction",IDC_STATIC,7,26,85,64 + CONTROL "North",IDC_DIR_N_RADIO,"Button",BS_AUTORADIOBUTTON | + WS_GROUP,14,38,33,10 + CONTROL "South",IDC_DIR_S_RADIO,"Button",BS_AUTORADIOBUTTON,14, + 50,35,10 + CONTROL "East",IDC_DIR_E_RADIO,"Button",BS_AUTORADIOBUTTON,14,62, + 30,10 + CONTROL "West",IDC_DIR_W_RADIO,"Button",BS_AUTORADIOBUTTON,14,74, + 33,10 + GROUPBOX "Style",IDC_STATIC,97,26,82,49 + CONTROL "Original",IDC_STYLE_ORIG_RADIO,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,105,35,39,10 + CONTROL "Bob's Style",IDC_STYLE_BOB_RADIO,"Button", + BS_AUTORADIOBUTTON,105,47,51,10 + CONTROL "Corner",IDC_STYLE_CORNER_RADIO,"Button", + BS_AUTORADIOBUTTON | WS_DISABLED,105,59,51,10 + EDITTEXT IDC_RISER_EDIT,7,99,85,12,ES_AUTOHSCROLL + LTEXT "Riser Texture",IDC_STATIC,95,100,60,11,SS_CENTERIMAGE + CONTROL "Use Detail Brushes",IDC_DETAIL_CHK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,100,80,75,10 +END + +IDD_POLYGON_DIALOG DIALOG DISCARDABLE 0, 0, 271, 68 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Polygon Builder" +FONT 8, "Arial" +BEGIN + PUSHBUTTON "OK",IDOK,150,50,50,14 + PUSHBUTTON "Cancel",IDCANCEL,215,50,50,14 + EDITTEXT IDC_EDIT1,7,7,76,13,ES_AUTOHSCROLL + LTEXT "Number Of Sides",IDC_STATIC,85,7,60,13,SS_CENTERIMAGE + CONTROL "Inverse Polygon",IDC_INVERSE_CHK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,149,21,75,10 + CONTROL "Use Border",IDC_BORDER_CHK,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,149,7,66,10 + EDITTEXT IDC_BORDER_EDIT,7,28,76,13,ES_AUTOHSCROLL + LTEXT "Border Size",IDC_STATIC,86,28,60,13,SS_CENTERIMAGE + CONTROL "Align Top Edge",IDC_ALIGN_CHK,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,149,35,75,10 +END + +IDD_DOOR_DIALOG DIALOG DISCARDABLE 0, 0, 327, 119 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Door Builder" +FONT 8, "Arial" +BEGIN + DEFPUSHBUTTON "OK",IDOK,270,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,270,24,50,14 + EDITTEXT IDC_FBTEXTURE_EDIT,7,7,125,14,ES_AUTOHSCROLL + LTEXT "Door Front/Back Texture",IDC_STATIC,137,7,86,14, + SS_CENTERIMAGE + EDITTEXT IDC_TRIMTEXTURE_EDIT,7,26,125,14,ES_AUTOHSCROLL + LTEXT "Door Trim Texture",IDC_STATIC,137,26,86,14, + SS_CENTERIMAGE + CONTROL "Scale Main Texture Horizontally",IDC_TEXSCALE1_CHECK, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,46,115,10 + CONTROL "Scale Main Texture Vertically",IDC_TEXSCALE2_CHECK, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,60,107,10 + CONTROL "Scale Trim Texture Horizontally",IDC_TEXSCALE3_CHECK, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,136,46,113,10 + CONTROL "Scale Trim Texture Vertically",IDC_TEXSCALE4_CHECK, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,136,60,105,10 + COMBOBOX IDC_MAINTEX_COMBO,7,76,126,30,CBS_DROPDOWN | CBS_SORT | + WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_TRIMTEX_COMBO,7,99,126,30,CBS_DROPDOWN | CBS_SORT | + WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Set As Main Texture",IDC_SET_MAINTEX_BTN,139,76,82,14 + PUSHBUTTON "Set As Trim Texture",IDC_SET_TRIMTEX_BTN,139,98,82,14 + GROUPBOX "Orientation",IDC_DIR_GROUP,225,72,95,40 + CONTROL "North - South",IDC_DIR_NS_RADIO,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,230,82,85,10 + CONTROL "East - West",IDC_DIR_EW_RADIO,"Button", + BS_AUTORADIOBUTTON,230,97,85,10 +END + +IDD_INTERSECT_DIALOG DIALOG DISCARDABLE 0, 0, 245, 58 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Intersecting Brush Finder" +FONT 8, "Arial" +BEGIN + DEFPUSHBUTTON "OK",IDOK,130,40,50,14 + PUSHBUTTON "Cancel",IDCANCEL,185,40,50,14 + CONTROL "Include Detail Brushes",IDC_DETAIL_INCLUDE_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,130,10,111,10 + GROUPBOX "Brush Options",IDC_STATIC,10,5,111,45 + CONTROL "Use Whole Map",IDC_WHOLEMAP_RADIO,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,15,15,95,10 + CONTROL "Use Selected Brushes",IDC_SELECTED_RADIO,"Button", + BS_AUTORADIOBUTTON,15,30,95,10 + CONTROL "Only Select Duplicate Brushes",IDC_DUPLICATEONLY_CHECK, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,130,25,113,10 +END + +IDD_INTERSECT_INFO_DIALOG DIALOG DISCARDABLE 0, 0, 187, 33 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Progress1",IDC_PROGRESS1,"msctls_progress32",PBS_SMOOTH, + 5,15,175,10 + CTEXT "Brush Loading",IDC_STATIC,5,5,175,10,SS_CENTERIMAGE +END + +IDD_BRUSHCHECKER_DIALOG DIALOG DISCARDABLE 0, 0, 182, 38 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Progress1",IDC_PROGRESS1,"msctls_progress32",PBS_SMOOTH, + 5,21,172,10 + CTEXT "Checking Brushes",IDC_STATIC,5,5,170,15,SS_CENTERIMAGE +END + +IDD_AUTOCAULK_DIALOG DIALOG DISCARDABLE 0, 0, 187, 52 +STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "Progress1",IDC_PROGRESS1,"msctls_progress32",PBS_SMOOTH, + 5,15,175,10 + CTEXT "Loading Portal Data",IDC_STATIC,5,5,175,10, + SS_CENTERIMAGE + CONTROL "Progress1",IDC_PROGRESS2,"msctls_progress32",PBS_SMOOTH, + 5,35,175,10 + CTEXT "Auto Caulking",IDC_STATIC,5,25,175,10,SS_CENTERIMAGE +END + +IDD_AUTOCAULKSTART_DIALOG DIALOG DISCARDABLE 0, 0, 237, 83 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Auto Caulk" +FONT 8, "Arial" +BEGIN + DEFPUSHBUTTON "OK",IDOK,125,10,50,14 + PUSHBUTTON "Cancel",IDCANCEL,180,10,50,14 + CONTROL "Destroy Invisible Brushes",IDC_KILLBRUSHES_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,125,50,95,10 + LTEXT "",IDC_WARNING1_STATIC,5,65,223,11,NOT WS_GROUP + GROUPBOX "Static",IDC_STATIC,5,5,115,57 + CONTROL "Autocaulk",IDC_AC_NORMAL_RADIO,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,10,15,48,10 + CONTROL "Build Mini Prt",IDC_AC_BUILD_MINI_PRT_RADIO,"Button", + BS_AUTORADIOBUTTON,10,30,56,10 + CONTROL "Autocaulk+ (Detail Brushes)",IDC_AC_SUPER_RADIO,"Button", + BS_AUTORADIOBUTTON,10,45,106,10 +END + +IDD_TEXTURE_RESET_DIALOG DIALOG DISCARDABLE 0, 0, 272, 107 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Texture Reset" +FONT 8, "Arial" +BEGIN + DEFPUSHBUTTON "OK",IDOK,160,85,50,14 + PUSHBUTTON "Cancel",IDCANCEL,215,85,50,14 + EDITTEXT IDC_RESET_TEXTURE_EDIT,5,5,118,14,ES_AUTOHSCROLL + EDITTEXT IDC_SCL_VERT_EDIT,85,45,40,14,ES_AUTOHSCROLL + EDITTEXT IDC_SCL_HOR_EDIT,85,65,40,14,ES_AUTOHSCROLL + EDITTEXT IDC_ROTATION_EDIT,85,85,40,14,ES_AUTOHSCROLL + EDITTEXT IDC_SHFT_VER_EDIT,225,45,40,14,ES_AUTOHSCROLL + EDITTEXT IDC_SHFT_HOR_EDIT,225,65,40,14,ES_AUTOHSCROLL + RTEXT "Scale::Vertical",IDC_STATIC,5,45,75,15,SS_CENTERIMAGE + RTEXT "Scale::Horizontal",IDC_STATIC,5,65,75,15,SS_CENTERIMAGE + RTEXT "Rotation",IDC_STATIC,5,85,75,15,SS_CENTERIMAGE + RTEXT "Shift::Horizontal",IDC_STATIC,160,65,60,15, + SS_CENTERIMAGE + RTEXT "Shift::Vertical",IDC_STATIC,160,45,60,15,SS_CENTERIMAGE + LTEXT "Texture To Reset",IDC_STATIC,128,5,62,15,SS_CENTERIMAGE + CONTROL "Reset All Textures",IDC_ALLTEXTURES_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,195,5,75,15 + EDITTEXT IDC_RESET_NEW_TEXTURE_EDIT,5,25,118,14,ES_AUTOHSCROLL + LTEXT "New Texture Name",IDC_STATIC,128,25,62,15, + SS_CENTERIMAGE + CONTROL "Reset Texture Only",IDC_ONLYTEXTURE_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,195,25,75,15 +END + +IDD_PATHPLOTTER_DIALOG DIALOG DISCARDABLE 0, 0, 172, 113 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Path Plotter" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Enable",IDYES,5,92,50,14 + PUSHBUTTON "Cancel",IDCANCEL,115,92,50,14 + PUSHBUTTON "Disable",IDNO,60,92,50,14 + EDITTEXT IDC_POINTCOUNT_EDIT,5,5,80,14,ES_AUTOHSCROLL + EDITTEXT IDC_MULTIPLIER_EDIT,5,25,80,14,ES_AUTOHSCROLL + EDITTEXT IDC_GRAVITY_EDIT,5,45,80,14,ES_AUTOHSCROLL + LTEXT "Number Of Points",IDC_STATIC,90,5,60,15,SS_CENTERIMAGE + CONTROL "No Dynamic Update",IDC_NOUPDATE_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,7,65,80,10 + LTEXT "Multiplier",IDC_STATIC,90,25,60,15,SS_CENTERIMAGE + LTEXT "Gravity",IDC_STATIC,90,45,60,15,SS_CENTERIMAGE + CONTROL "Show Bounding Lines",IDC_SHOWEXTRA_CHECK,"Button", + BS_AUTOCHECKBOX | WS_TABSTOP,7,79,85,10 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_STAIR_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 239 + TOPMARGIN, 7 + BOTTOMMARGIN, 111 + END + + IDD_POLYGON_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 264 + TOPMARGIN, 7 + BOTTOMMARGIN, 61 + END + + IDD_DOOR_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 320 + TOPMARGIN, 7 + BOTTOMMARGIN, 112 + END + + IDD_INTERSECT_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 238 + TOPMARGIN, 7 + BOTTOMMARGIN, 51 + END + + IDD_INTERSECT_INFO_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 180 + TOPMARGIN, 7 + BOTTOMMARGIN, 25 + END + + IDD_BRUSHCHECKER_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 175 + TOPMARGIN, 7 + BOTTOMMARGIN, 31 + END + + IDD_AUTOCAULK_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 180 + TOPMARGIN, 7 + BOTTOMMARGIN, 45 + END + + IDD_AUTOCAULKSTART_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 230 + TOPMARGIN, 7 + BOTTOMMARGIN, 76 + END + + IDD_TEXTURE_RESET_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 265 + TOPMARGIN, 7 + BOTTOMMARGIN, 100 + END + + IDD_PATHPLOTTER_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 165 + TOPMARGIN, 7 + BOTTOMMARGIN, 106 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog Info +// + +IDD_DOOR_DIALOG DLGINIT +BEGIN + IDC_MAINTEX_COMBO, 0x403, 25, 0 +0x6162, 0x6573, 0x645f, 0x6f6f, 0x2f72, 0x6873, 0x6e69, 0x6d79, 0x7465, +0x6c61, 0x6f64, 0x726f, "\000" + IDC_MAINTEX_COMBO, 0x403, 33, 0 +0x6162, 0x6573, 0x645f, 0x6f6f, 0x2f72, 0x6873, 0x6e69, 0x6d79, 0x7465, +0x6c61, 0x6f64, 0x726f, 0x6f5f, 0x7475, 0x6973, 0x6564, "\000" + IDC_MAINTEX_COMBO, 0x403, 36, 0 +0x6162, 0x6573, 0x645f, 0x6f6f, 0x2f72, 0x6873, 0x6e69, 0x6d79, 0x7465, +0x6c61, 0x6f64, 0x726f, 0x6f5f, 0x7475, 0x6973, 0x6564, 0x6133, 0x0032, + + IDC_MAINTEX_COMBO, 0x403, 24, 0 +0x6f67, 0x6874, 0x6369, 0x645f, 0x6f6f, 0x2f72, 0x6f64, 0x726f, 0x3230, +0x625f, 0x6572, 0x0064, + IDC_MAINTEX_COMBO, 0x403, 31, 0 +0x6f67, 0x6874, 0x6369, 0x645f, 0x6f6f, 0x2f72, 0x6f64, 0x726f, 0x3230, +0x625f, 0x6572, 0x3264, 0x735f, 0x6968, 0x796e, "\000" + IDC_MAINTEX_COMBO, 0x403, 32, 0 +0x6f67, 0x6874, 0x6369, 0x645f, 0x6f6f, 0x2f72, 0x6f64, 0x726f, 0x3230, +0x655f, 0x6c62, 0x6575, 0x5f32, 0x6873, 0x6e69, 0x0079, + IDC_MAINTEX_COMBO, 0x403, 33, 0 +0x6f67, 0x6874, 0x6369, 0x645f, 0x6f6f, 0x2f72, 0x6f64, 0x726f, 0x3230, +0x695f, 0x6f5f, 0x6e72, 0x7461, 0x3565, 0x665f, 0x6e69, "\000" + IDC_MAINTEX_COMBO, 0x403, 21, 0 +0x6f67, 0x6874, 0x6369, 0x645f, 0x6f6f, 0x2f72, 0x6f64, 0x726f, 0x3230, +0x6a5f, "\000" + IDC_MAINTEX_COMBO, 0x403, 22, 0 +0x6f67, 0x6874, 0x6369, 0x645f, 0x6f6f, 0x2f72, 0x6f64, 0x726f, 0x3230, +0x6a5f, 0x0033, + IDC_MAINTEX_COMBO, 0x403, 22, 0 +0x6f67, 0x6874, 0x6369, 0x645f, 0x6f6f, 0x2f72, 0x6f64, 0x726f, 0x3230, +0x6a5f, 0x0034, + IDC_MAINTEX_COMBO, 0x403, 23, 0 +0x6f67, 0x6874, 0x6369, 0x645f, 0x6f6f, 0x2f72, 0x6f64, 0x726f, 0x3230, +0x6b5f, 0x6232, "\000" + IDC_TRIMTEX_COMBO, 0x403, 26, 0 +0x6162, 0x6573, 0x735f, 0x7075, 0x6f70, 0x7472, 0x732f, 0x7075, 0x6f70, +0x7472, 0x7231, 0x7375, 0x0074, + IDC_TRIMTEX_COMBO, 0x403, 27, 0 +0x6162, 0x6573, 0x735f, 0x7075, 0x6f70, 0x7472, 0x732f, 0x7075, 0x6f70, +0x7472, 0x7331, 0x6968, 0x796e, "\000" + IDC_TRIMTEX_COMBO, 0x403, 26, 0 +0x6162, 0x6573, 0x735f, 0x7075, 0x6f70, 0x7472, 0x732f, 0x7075, 0x6f70, +0x7472, 0x7232, 0x7375, 0x0074, + IDC_TRIMTEX_COMBO, 0x403, 22, 0 +0x6162, 0x6573, 0x735f, 0x7075, 0x6f70, 0x7472, 0x772f, 0x6c70, 0x7461, +0x5f31, 0x0031, + IDC_TRIMTEX_COMBO, 0x403, 22, 0 +0x6162, 0x6573, 0x735f, 0x7075, 0x6f70, 0x7472, 0x702f, 0x616c, 0x6574, +0x5f32, 0x0035, + 0 +END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""resource.h""\r\n" + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (U.K.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/contrib/bobtoolz/bobToolz_gtk.vcproj b/contrib/bobtoolz/bobToolz_gtk.vcproj new file mode 100644 index 00000000..0cb32ece --- /dev/null +++ b/contrib/bobtoolz/bobToolz_gtk.vcproj @@ -0,0 +1,328 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/bobtoolz/bobtoolz-gtk.rc b/contrib/bobtoolz/bobtoolz-gtk.rc new file mode 100644 index 00000000..1d401e10 --- /dev/null +++ b/contrib/bobtoolz/bobtoolz-gtk.rc @@ -0,0 +1,109 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource-gtk.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.K.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource-gtk.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,1,1,0 + PRODUCTVERSION 2,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080904b0" + BEGIN + VALUE "Comments", "by djbob :P and MarsMattel ~:)\0" + VALUE "CompanyName", "bobCo\0" + VALUE "FileDescription", "All your plugins are belong to us\0" + VALUE "FileVersion", "1, 1, 1, 0\0" + VALUE "InternalName", "bobToolz GTK\0" + VALUE "LegalCopyright", "Copyright © 2001\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "bobToolz.dll\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "bobCo bobToolz\0" + VALUE "ProductVersion", "2, 0, 0, 0\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x809, 1200 + END +END + +#endif // !_MAC + +#endif // English (U.K.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/contrib/bobtoolz/bobtoolz.vcproj b/contrib/bobtoolz/bobtoolz.vcproj new file mode 100644 index 00000000..eec566c3 --- /dev/null +++ b/contrib/bobtoolz/bobtoolz.vcproj @@ -0,0 +1,481 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/bobtoolz/bsploader.cpp b/contrib/bobtoolz/bsploader.cpp new file mode 100644 index 00000000..287c5fff --- /dev/null +++ b/contrib/bobtoolz/bsploader.cpp @@ -0,0 +1,263 @@ +#include "bsploader.h" +#include "dialogs/dialogs-gtk.h" +#include "cmdlib.h" + +int numnodes; +int numplanes; +int numleafs; +int numleafsurfaces; +int numVisBytes; +int numDrawVerts; +int numDrawSurfaces; +int numbrushes; +int numbrushsides; +int numleafbrushes; + +byte *visBytes = NULL; +dnode_t *dnodes = NULL; +dplane_t *dplanes = NULL; +dleaf_t *dleafs = NULL; +qdrawVert_t *drawVerts = NULL; +dsurface_t *drawSurfaces = NULL; +int *dleafsurfaces = NULL; +dbrush_t *dbrushes = NULL; +dbrushside_t *dbrushsides = NULL; +int *dleafbrushes = NULL; + +#define BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'I') +#define Q3_BSP_VERSION 46 +#define WOLF_BSP_VERSION 47 + +/* +================ +FileLength +================ +*/ +int FileLength (FILE *f) +{ + int pos; + int end; + + pos = ftell (f); + fseek (f, 0, SEEK_END); + end = ftell (f); + fseek (f, pos, SEEK_SET); + + return end; +} + +/* +============== +LoadFile +============== +*/ +bool LoadFile( const char *filename, byte **bufferptr) +{ + FILE *f; + int length; + byte *buffer; + + f = fopen(filename, "rb"); + if(!f) + return false; + + length = FileLength (f); + buffer = new byte[length+1]; + buffer[length] = 0; + fread(buffer, 1, length, f); + fclose (f); + + *bufferptr = buffer; + return true; +} + +int LittleLong (int l) +{ +#if defined(__BIG_ENDIAN__) + std::reverse(reinterpret_cast(&l), reinterpret_cast(&l) + sizeof(int)); +#endif + return l; +} + +float LittleFloat (float l) +{ +#if defined(__BIG_ENDIAN__) + std::reverse(reinterpret_cast(&l), reinterpret_cast(&l) + sizeof(float)); +#endif + return l; +} + +/* +============= +SwapBlock + +If all values are 32 bits, this can be used to swap everything +============= +*/ +void SwapBlock( int *block, int sizeOfBlock ) { + int i; + + sizeOfBlock >>= 2; + for ( i = 0 ; i < sizeOfBlock ; i++ ) { + block[i] = LittleLong( block[i] ); + } +} + +/* +============= +SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void SwapBSPFile( void ) { + int i; + + // models +// SwapBlock( (int *)dmodels, nummodels * sizeof( dmodels[0] ) ); + + // shaders (don't swap the name) +// for ( i = 0 ; i < numShaders ; i++ ) { +// dshaders[i].contentFlags = LittleLong( dshaders[i].contentFlags ); +// dshaders[i].surfaceFlags = LittleLong( dshaders[i].surfaceFlags ); +// } + + // planes + SwapBlock( (int *)dplanes, numplanes * sizeof( dplanes[0] ) ); + + // nodes + SwapBlock( (int *)dnodes, numnodes * sizeof( dnodes[0] ) ); + + // leafs + SwapBlock( (int *)dleafs, numleafs * sizeof( dleafs[0] ) ); + + // leaffaces + SwapBlock( (int *)dleafsurfaces, numleafsurfaces * sizeof( dleafsurfaces[0] ) ); + + // leafbrushes + SwapBlock( (int *)dleafbrushes, numleafbrushes * sizeof( dleafbrushes[0] ) ); + + // brushes + SwapBlock( (int *)dbrushes, numbrushes * sizeof( dbrushes[0] ) ); + + // brushsides + SwapBlock( (int *)dbrushsides, numbrushsides * sizeof( dbrushsides[0] ) ); + + // vis + ((int *)&visBytes)[0] = LittleLong( ((int *)&visBytes)[0] ); + ((int *)&visBytes)[1] = LittleLong( ((int *)&visBytes)[1] ); + + // drawverts (don't swap colors ) + for ( i = 0 ; i < numDrawVerts ; i++ ) { + drawVerts[i].lightmap[0] = LittleFloat( drawVerts[i].lightmap[0] ); + drawVerts[i].lightmap[1] = LittleFloat( drawVerts[i].lightmap[1] ); + drawVerts[i].st[0] = LittleFloat( drawVerts[i].st[0] ); + drawVerts[i].st[1] = LittleFloat( drawVerts[i].st[1] ); + drawVerts[i].xyz[0] = LittleFloat( drawVerts[i].xyz[0] ); + drawVerts[i].xyz[1] = LittleFloat( drawVerts[i].xyz[1] ); + drawVerts[i].xyz[2] = LittleFloat( drawVerts[i].xyz[2] ); + drawVerts[i].normal[0] = LittleFloat( drawVerts[i].normal[0] ); + drawVerts[i].normal[1] = LittleFloat( drawVerts[i].normal[1] ); + drawVerts[i].normal[2] = LittleFloat( drawVerts[i].normal[2] ); + } + + // drawindexes +// SwapBlock( (int *)drawIndexes, numDrawIndexes * sizeof( drawIndexes[0] ) ); + + // drawsurfs + SwapBlock( (int *)drawSurfaces, numDrawSurfaces * sizeof( drawSurfaces[0] ) ); + + // fogs +// for ( i = 0 ; i < numFogs ; i++ ) { +// dfogs[i].brushNum = LittleLong( dfogs[i].brushNum ); +// dfogs[i].visibleSide = LittleLong( dfogs[i].visibleSide ); +// } +} + +/* +============= +CopyLump +============= +*/ +int CopyLump( dheader_t *header, int lump, void **dest, int size ) { + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if(length == 0) + return 0; + + *dest = new byte[length]; + memcpy( *dest, (byte *)header + ofs, length ); + + return length / size; +} + +/* +============= +LoadBSPFile +============= +*/ +bool LoadBSPFile( const char *filename ) { + dheader_t *header; + + // load the file header + if(!LoadFile (filename, (byte **)&header)) + return false; + + // swap the header + SwapBlock( (int *)header, sizeof(*header) ); + + if ( header->ident != BSP_IDENT ) { + DoMessageBox( "Cant find a valid IBSP file", "Error", eMB_OK); + return false; + } + if ( (header->version != Q3_BSP_VERSION) && + (header->version != WOLF_BSP_VERSION) ) { + DoMessageBox( "File is incorrect version", "Error", eMB_OK); + return false; + } + + numbrushsides = CopyLump( header, LUMP_BRUSHES, (void**)&dbrushsides, sizeof(dbrushside_t) ); + numbrushes = CopyLump( header, LUMP_BRUSHES, (void**)&dbrushes, sizeof(dbrush_t) ); + numplanes = CopyLump( header, LUMP_PLANES, (void**)&dplanes, sizeof(dplane_t) ); + numleafs = CopyLump( header, LUMP_LEAFS, (void**)&dleafs, sizeof(dleaf_t) ); + numnodes = CopyLump( header, LUMP_NODES, (void**)&dnodes, sizeof(dnode_t) ); + numDrawVerts = CopyLump( header, LUMP_DRAWVERTS, (void**)&drawVerts, sizeof(qdrawVert_t) ); + numDrawSurfaces = CopyLump( header, LUMP_SURFACES, (void**)&drawSurfaces, sizeof(dsurface_t) ); + numleafsurfaces = CopyLump( header, LUMP_LEAFSURFACES, (void**)&dleafsurfaces, sizeof(int) ); + numVisBytes = CopyLump( header, LUMP_VISIBILITY, (void**)&visBytes, 1 ); + numleafbrushes = CopyLump( header, LUMP_LEAFBRUSHES, (void**)&dleafbrushes, sizeof(int) ); + + delete header; // everything has been copied out + + // swap everything + SwapBSPFile(); + + return true; +} + +void FreeBSPData() +{ + if(visBytes) + delete visBytes; + if(dnodes) + delete dnodes; + if(dplanes) + delete dplanes; + if(dleafs) + delete dleafs; + if(drawVerts) + delete drawVerts; + if(drawSurfaces) + delete drawSurfaces; + if(dleafsurfaces) + delete dleafsurfaces; + if(dleafbrushes) + delete dleafbrushes; + if(dbrushes) + delete dbrushes; + if(dbrushsides) + delete dbrushsides; +} diff --git a/contrib/bobtoolz/bsploader.h b/contrib/bobtoolz/bsploader.h new file mode 100644 index 00000000..3cea233b --- /dev/null +++ b/contrib/bobtoolz/bsploader.h @@ -0,0 +1,137 @@ + +#include "mathlib.h" + +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define HEADER_LUMPS 17 + +typedef struct { + int fileofs, filelen; +} lump_t; + +typedef struct { + int ident; + int version; + + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct { + float normal[3]; + float dist; +} dplane_t; + +typedef struct { + int planeNum; + int children[2]; // negative numbers are -(leafs+1), not nodes + int mins[3]; // for frustom culling + int maxs[3]; +} dnode_t; + +typedef struct { + int cluster; // -1 = opaque cluster (do I still store these?) + int area; + + int mins[3]; // for frustum culling + int maxs[3]; + + int firstLeafSurface; + int numLeafSurfaces; + + int firstLeafBrush; + int numLeafBrushes; +} dleaf_t; + +typedef struct { + vec3_t xyz; + float st[2]; + float lightmap[2]; + vec3_t normal; + byte color[4]; +} qdrawVert_t; + +typedef struct { + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + int lightmapNum; + int lightmapX, lightmapY; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds + + int patchWidth; + int patchHeight; +} dsurface_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + int shaderNum; +} dbrushside_t; + +typedef struct { + int firstSide; + int numSides; + int shaderNum; // the shader that determines the contents flags +} dbrush_t; + +typedef enum { + MST_BAD, + MST_PLANAR, + MST_PATCH, + MST_TRIANGLE_SOUP, + MST_FLARE +} mapSurfaceType_t; + +#define MAX_MAP_VISIBILITY 0x200000 +#define MAX_MAP_NODES 0x20000 +#define MAX_MAP_PLANES 0x20000 +#define MAX_MAP_LEAFS 0x20000 + +extern int numVisBytes; +extern int numleafs; +extern int numplanes; +extern int numnodes; +extern int numDrawVerts; +extern int numDrawSurfaces; +extern int numleafsurfaces; +extern int numbrushes; +extern int numbrushsides; +extern int numleafbrushes; + +extern dnode_t *dnodes; +extern dplane_t *dplanes; +extern dleaf_t *dleafs; +extern byte *visBytes; +extern qdrawVert_t *drawVerts; +extern dsurface_t *drawSurfaces; +extern int *dleafsurfaces; +extern dbrush_t *dbrushes; +extern dbrushside_t *dbrushsides; +extern int *dleafbrushes; + +bool LoadBSPFile( const char *filename ); +void FreeBSPData(); diff --git a/contrib/bobtoolz/bt/bt-el1.txt b/contrib/bobtoolz/bt/bt-el1.txt new file mode 100644 index 00000000..f6485f94 --- /dev/null +++ b/contrib/bobtoolz/bt/bt-el1.txt @@ -0,0 +1,17 @@ +common/areaportal +common/clip +common/clusterportal +common/cushion +common/donotenter +common/full_clip +common/hint +common/missileclip +common/nodraw +common/nodrawnonsolid +common/nodrop +common/noimpact +common/origin +common/trigger +common/weapclip +liquid +fog \ No newline at end of file diff --git a/contrib/bobtoolz/bt/bt-el2.txt b/contrib/bobtoolz/bt/bt-el2.txt new file mode 100644 index 00000000..e69de29b diff --git a/contrib/bobtoolz/bt/ctf-blue.txt b/contrib/bobtoolz/bt/ctf-blue.txt new file mode 100644 index 00000000..5a5f6ca3 --- /dev/null +++ b/contrib/bobtoolz/bt/ctf-blue.txt @@ -0,0 +1,61 @@ +base_light/light1blue_2000 +base_light/light1blue_5000 +ctf/blue_telep +ctf/ctf_blueflag +ctf/ctf_tower_bluefin_shiny +gothic_door/door02_eblue2_shiny +gothic_light/ironcrossltblue_10000 +gothic_light/ironcrossltblue_2000 +gothic_light/ironcrossltblue_20000 +gothic_light/ironcrossltblue_3000 +gothic_light/ironcrossltblue_30000 +gothic_light/ironcrossltblue_4000 +gothic_light/ironcrossltblue_5000 +sfx/beam_blue +sfx/flameanim_blue +sfx/flameanim_blue_nolight +sfx/flameanim_blue_pj +sfx/mkc_fog_ctfblue +sfx/xbluefog +base_wall2/blue_metal +base_wall2/jumppad_blue_kc +base_wall2/blue_line +base_wall2/double_line_blue +base_wall2/blue_arrow_small +base_wall2/blue_circle +base_wall2/bluearrows +base_wall2/blue_solid +ctf2/blueteam01 +ctf2/blueteam02 +ctf2/xblueteam01 +ctf2/blue_banner02 +proto2/blueflag +proto2/blueob +proto2/marbledoor_blue +proto2/concrete_bluenfx +proto2/bluelight_on +proto2/bsbluelight_on +proto2/rsbluelight_off +proto2/bsbluelight_off +proto2/rsbluelight_on +proto2/bluetrim01 +proto2/blue_zot +proto2/blue_zot2 +proto2/bluea_dcl +proto2/concrete_blue +proto2/teamwerkz_blue1 +proto2/blueflare2 +proto2/blueflare +sfx2/flameanim_blue_lowlite +sfx2/blue_jumpad05 +sfx2/blue_launchpad +sfx2/blue_jumpad +sfx2/blue_jumpad2 +sfx2/blue_jumpad3 +sfx2/bluegoal2 +tim/blue_flagbase +team_icon/the fallen_blue +team_icon/intruders_blue +team_icon/crusaders_blue +team_icon/pagans_blue +team_icon/stroggs_blue \ No newline at end of file diff --git a/contrib/bobtoolz/bt/ctf-red.txt b/contrib/bobtoolz/bt/ctf-red.txt new file mode 100644 index 00000000..8dec85e7 --- /dev/null +++ b/contrib/bobtoolz/bt/ctf-red.txt @@ -0,0 +1,61 @@ +base_light/light1red_2000 +base_light/light1red_5000 +ctf/red_telep +ctf/ctf_redflag +ctf/ctf_tower_redfin_shiny +gothic_door/door02_bred2_shiny +gothic_light/ironcrossltred_10000 +gothic_light/ironcrossltred_2000 +gothic_light/ironcrossltred_20000 +gothic_light/ironcrossltred_3000 +gothic_light/ironcrossltred_30000 +gothic_light/ironcrossltred_4000 +gothic_light/ironcrossltred_5000 +sfx/beam_red +sfx/flameanim_red +sfx/flameanim_red_nolight +sfx/flameanim_red_pj +sfx/mkc_fog_ctfred +sfx/xredfog +base_wall2/red_metal +base_wall2/jumppad_red_kc +base_wall2/red_line +base_wall2/double_line_red +base_wall2/red_arrow_small +base_wall2/red_circle +base_wall2/redarrows +base_wall2/red_solid +ctf2/redteam01 +ctf2/redteam02 +ctf2/xredteam01x +ctf2/red_banner02 +proto2/redflag +proto2/redob +proto2/marbledoor_red +proto2/concrete_rednfx +proto2/redlight_on +proto2/bsredlight_on +proto2/rsredlight_off +proto2/bsredlight_off +proto2/rsredlight_on +proto2/redtrim01 +proto2/red_zot +proto2/red_zot2 +proto2/reda_dcl +proto2/concrete_red +proto2/teamwerkz_red1 +proto2/redflare2 +proto2/redflare +sfx2/flameanim_red_lowlite +sfx2/red_jumpad05 +sfx2/red_launchpad +sfx2/red_jumpad +sfx2/red_jumpad2 +sfx2/red_jumpad3 +sfx2/redgoal2 +tim/red_flagbase +team_icon/the fallen_red +team_icon/intruders_red +team_icon/crusaders_red +team_icon/pagans_red +team_icon/stroggs_red \ No newline at end of file diff --git a/contrib/bobtoolz/bt/door-tex-trim.txt b/contrib/bobtoolz/bt/door-tex-trim.txt new file mode 100644 index 00000000..d52ef76f --- /dev/null +++ b/contrib/bobtoolz/bt/door-tex-trim.txt @@ -0,0 +1,5 @@ +base_support/support1rust +base_support/support1shiny +base_support/support2rust +base_support/wplat1_1 +base_support/plate2_5 \ No newline at end of file diff --git a/contrib/bobtoolz/bt/door-tex.txt b/contrib/bobtoolz/bt/door-tex.txt new file mode 100644 index 00000000..69629989 --- /dev/null +++ b/contrib/bobtoolz/bt/door-tex.txt @@ -0,0 +1,10 @@ +base_door/shinymetaldoor +base_door/shinymetaldoor_outside +gothic_door/door02_bred +gothic_door/door02_bred2_shiny +gothic_door/door02_eblue2_shiny +gothic_door/door02_i_ornate5_fin +gothic_door/door02_j +gothic_door/door02_j3 +gothic_door/door02_j4 +gothic_door/door02_k2b \ No newline at end of file diff --git a/contrib/bobtoolz/bt/tp_ent.txt b/contrib/bobtoolz/bt/tp_ent.txt new file mode 100644 index 00000000..f0645b34 --- /dev/null +++ b/contrib/bobtoolz/bt/tp_ent.txt @@ -0,0 +1,14 @@ +{ + "entity" "misc_model" + + "offset" "-16" + + "model" "models/mapobjects/trees_sd/tree_a.md3" + "model" "models/mapobjects/trees_sd/tree_b.md3" + "model" "models/mapobjects/trees_sd/tree_c.md3" + "model" "models/mapobjects/trees_sd/tree_d.md3" + + "pitch" "-5" "5" + "yaw" "0" "360" + "scale" "1" "1.3" +} \ No newline at end of file diff --git a/contrib/bobtoolz/cportals.cpp b/contrib/bobtoolz/cportals.cpp new file mode 100644 index 00000000..5048eef0 --- /dev/null +++ b/contrib/bobtoolz/cportals.cpp @@ -0,0 +1,343 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "CPortals.h" + +#include +#include + +#include "misc.h" + +#define LINE_BUF 1000 +#define MSG_PREFIX "bobToolz plugin: " + +// these classes are far less of a mess than my code was, +// thanq to G.DeWan 4 the prtview source on which it was based + +CBspPortal::CBspPortal() +{ + memset(this, 0, sizeof(CBspPortal)); +} + +CBspPortal::~CBspPortal() +{ + delete[] point; +} + +void ClampFloat(float* p) +{ + double i; + double frac = modf(*p, &i); + + if(!frac) + return; + + if(fabs(*p - ceil(*p)) < MAX_ROUND_ERROR) + *p = static_cast(ceil(*p)); + + if(fabs(*p - floor(*p)) < MAX_ROUND_ERROR) + *p = static_cast(floor(*p)); +} + +bool CBspPortal::Build(char *def, unsigned int pointCnt, bool bInverse) +{ + char *c = def; + unsigned int n; + + point_count = pointCnt; + + if(point_count < 3) + return false; + + point = new CBspPoint[point_count]; + + for(n = 0; n < point_count; n++) + { + for(; *c != 0 && *c != '('; c++); + + if(*c == 0) + return false; + + c++; + + int x; + if(bInverse) + x = point_count - n - 1; + else + x = n; + + sscanf(c, "%f %f %f", &point[x].p[0], &point[x].p[1], &point[x].p[2]); + + ClampFloat(&point[x].p[0]); + ClampFloat(&point[x].p[1]); + ClampFloat(&point[x].p[2]); + } + + return true; +} + +CPortals::CPortals() +{ + memset(this, 0, sizeof(CPortals)); +} + +CPortals::~CPortals() +{ + Purge(); +} + +void CPortals::Purge() +{ + if(node) + delete[] node; + node = NULL; + node_count = 0; +} + +void CPortals::Load() +{ + char buf[LINE_BUF+1]; + + memset(buf, 0, LINE_BUF + 1); + + Purge(); + + globalOutputStream() << MSG_PREFIX "Loading portal file " << fn << ".\n"; + + FILE *in; + + in = fopen(fn, "rt"); + + if(in == NULL) + { + globalOutputStream() << " ERROR - could not open file.\n"; + + return; + } + + if(!fgets(buf, LINE_BUF, in)) + { + fclose(in); + + globalOutputStream() << " ERROR - File ended prematurely.\n"; + + return; + } + + if(strncmp("PRT1", buf, 4) != 0) + { + fclose(in); + + globalOutputStream() << " ERROR - File header indicates wrong file type (should be \"PRT1\").\n"; + + return; + } + + if(!fgets(buf, LINE_BUF, in)) + { + fclose(in); + + globalOutputStream() << " ERROR - File ended prematurely.\n"; + + return; + } + + sscanf(buf, "%u", &node_count); + + if(node_count > 0xFFFF) + { + fclose(in); + + node_count = 0; + + globalOutputStream() << " ERROR - Extreme number of nodes, aborting.\n"; + + return; + } + + if(!fgets(buf, LINE_BUF, in)) + { + fclose(in); + + node_count = 0; + + globalOutputStream() << " ERROR - File ended prematurely.\n"; + + return; + } + + unsigned int p_count; + sscanf(buf, "%u", &p_count); + + if(!fgets(buf, LINE_BUF, in)) + { + fclose(in); + + node_count = 0; + + globalOutputStream() << " ERROR - File ended prematurely.\n"; + + return; + } + + unsigned int p_count2; + sscanf(buf, "%u", &p_count2); + + node = new CBspNode[node_count]; + + unsigned int i; + for(i = 0; i < p_count; i++) + { + if(!fgets(buf, LINE_BUF, in)) + { + fclose(in); + + node_count = 0; + + globalOutputStream() << " ERROR - File ended prematurely.\n"; + + return; + } + + unsigned int dummy, node1, node2; + sscanf(buf, "%u %u %u", &dummy, &node1, &node2); + + node[node1].portal_count++; + node[node2].portal_count++; + } + + for(i = 0; i < p_count2; i++) + { + if(!fgets(buf, LINE_BUF, in)) + { + fclose(in); + + node_count = 0; + + globalOutputStream() << " ERROR - File ended prematurely.\n"; + + return; + } + + unsigned int dummy, node1; + sscanf(buf, "%u %u", &dummy, &node1); + + node[node1].portal_count++; + } + + for(i = 0; i < node_count; i++) + node[i].portal = new CBspPortal[node[i].portal_count]; + + fclose(in); + + in = fopen(fn, "rt"); + + fgets(buf, LINE_BUF, in); + fgets(buf, LINE_BUF, in); + fgets(buf, LINE_BUF, in); + fgets(buf, LINE_BUF, in); + + unsigned int n; + for(n = 0; n < p_count; n++) + { + if(!fgets(buf, LINE_BUF, in)) + { + fclose(in); + + Purge(); + + globalOutputStream() << " ERROR - Could not find information for portal number " << n + 1 << " of " << p_count << ".\n"; + + return; + } + + unsigned int pCount, node1, node2; + sscanf(buf, "%u %u %u", &pCount, &node1, &node2); + + if(!node[node1].AddPortal(buf, pCount, false)) + { + fclose(in); + + Purge(); + + globalOutputStream() << " ERROR - Information for portal number " << n + 1 << " of " << p_count << " is not formatted correctly.\n"; + + return; + } + + if(!node[node2].AddPortal(buf, pCount, true)) + { + fclose(in); + + Purge(); + + globalOutputStream() << " ERROR - Information for portal number " << n + 1 << " of " << p_count << " is not formatted correctly.\n"; + + return; + } + } + + for(n = 0; n < p_count2; n++) + { + if(!fgets(buf, LINE_BUF, in)) + { + fclose(in); + + Purge(); + + globalOutputStream() << " ERROR - Could not find information for portal number " << n + 1 << " of " << p_count << ".\n"; + + return; + } + + unsigned int pCount, node1; + sscanf(buf, "%u %u", &pCount, &node1); + + if(!node[node1].AddPortal(buf, pCount, false)) + { + fclose(in); + + Purge(); + + globalOutputStream() << " ERROR - Information for portal number " << n + 1 << " of " << p_count << " is not formatted correctly.\n"; + + return; + } + } + + fclose(in); +} + +CBspNode::CBspNode() +{ + portal = NULL; + portal_count = 0; + portal_next = 0; +} + +CBspNode::~CBspNode() +{ + if(portal != NULL) + delete[] portal; +} + +bool CBspNode::AddPortal(char *def, unsigned int pointCnt, bool bInverse) +{ + return portal[portal_next++].Build(def, pointCnt, bInverse); +} diff --git a/contrib/bobtoolz/ctfToolz-GTK.cpp b/contrib/bobtoolz/ctfToolz-GTK.cpp new file mode 100644 index 00000000..271931ab --- /dev/null +++ b/contrib/bobtoolz/ctfToolz-GTK.cpp @@ -0,0 +1,97 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "StdAfx.h" + +#include "funchandlers.h" +#include "misc.h" + +#include "dialogs/dialogs-gtk.h" + +// Radiant function table +_QERFuncTable_1 g_FuncTable; +_QERAppBSPFrontendTable g_BSPTable; // for map name + +BOOL g_bBSPInitDone = FALSE; + +// plugin name +static const char *PLUGIN_NAME = "ctfToolz"; + +// commands in the menu +static const char *PLUGIN_COMMANDS = "About...,Colour Changer...,Swap Light Colours,Change Angles 180,Swap Spawn Points"; + +// globals +GtkWidget *g_pRadiantWnd=NULL; + +static const char *PLUGIN_ABOUT = "ctfToolz for GtkRadiant\n" + "by djbob\n" + "http://www.planetquake.com/toolz\n\n"; + +extern "C" LPVOID WINAPI QERPlug_GetFuncTable() +{ + return &g_FuncTable; +} + +extern "C" LPCSTR WINAPI QERPlug_Init(HMODULE hApp, GtkWidget* pMainWidget) +{ + g_pRadiantWnd = pMainWidget; + memset(&g_FuncTable, 0, sizeof(_QERFuncTable_1)); + g_FuncTable.m_fVersion = QER_PLUG_VERSION; + g_FuncTable.m_nSize = sizeof(_QERFuncTable_1); + + return "ctfToolz for GTKradiant"; +} + +extern "C" LPCSTR WINAPI QERPlug_GetName() +{ + return (char*)PLUGIN_NAME; +} + +extern "C" LPCSTR WINAPI QERPlug_GetCommandList() +{ + return (char*)PLUGIN_COMMANDS; +} + +extern "C" void WINAPI QERPlug_Dispatch (LPCSTR p, vec3_t vMin, vec3_t vMax, bool bSingleBrush) +{ + LoadLists(); + + if (!g_bBSPInitDone) + { + g_BSPTable.m_nSize = sizeof(_QERAppBSPFrontendTable); + if ( g_FuncTable.m_pfnRequestInterface( QERAppBSPFrontendTable_GUID, static_cast(&g_BSPTable) ) ) + g_bBSPInitDone = TRUE; + else + { + Sys_ERROR("_QERAppBSPFrontendTable interface request failed\n"); + return; + } + } + + if(!strcmp(p, "About...")) + DoMessageBox(PLUGIN_ABOUT, "About", IDOK); + else if(!strcmp(p, "Colour Changer...")) + DoCTFColourChanger(); + else if(!strcmp(p, "Swap Light Colours")) + DoSwapLights(); + else if(!strcmp(p, "Change Angles 180")) + DoChangeAngles(); + else if(!strcmp(p, "Swap Spawn Points")) + DoSwapSpawns(); +} diff --git a/contrib/bobtoolz/ctfresource_gtk.h b/contrib/bobtoolz/ctfresource_gtk.h new file mode 100644 index 00000000..3f706a04 --- /dev/null +++ b/contrib/bobtoolz/ctfresource_gtk.h @@ -0,0 +1,34 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by ctfresource_gtk.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/contrib/bobtoolz/ctfresource_gtk.rc b/contrib/bobtoolz/ctfresource_gtk.rc new file mode 100644 index 00000000..d3dc2f77 --- /dev/null +++ b/contrib/bobtoolz/ctfresource_gtk.rc @@ -0,0 +1,109 @@ +//Microsoft Developer Studio generated resource script. +// +#include "ctfresource_gtk.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.K.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "ctfresource_gtk.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080904b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "bobCo\0" + VALUE "FileDescription", "All your plugins are belong to us\0" + VALUE "FileVersion", "1, 0, 0, 0\0" + VALUE "InternalName", "ctftoolz\0" + VALUE "LegalCopyright", "Copyright © 2001\0" + VALUE "LegalTrademarks", "\0" + VALUE "OriginalFilename", "ctftoolz.dll\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "bobCo ctftoolz\0" + VALUE "ProductVersion", "1, 0, 0, 0\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x809, 1200 + END +END + +#endif // !_MAC + +#endif // English (U.K.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/contrib/bobtoolz/ctftoolz.def b/contrib/bobtoolz/ctftoolz.def new file mode 100644 index 00000000..2c063edd --- /dev/null +++ b/contrib/bobtoolz/ctftoolz.def @@ -0,0 +1,12 @@ +; plugin.def : Declares the module parameters for the DLL. + +LIBRARY "ctfToolz" +DESCRIPTION 'ctfToolz Windows Dynamic Link Library' + +EXPORTS + ; Explicit exports can go here + QERPlug_Init @1 + QERPlug_GetName @2 + QERPlug_GetCommandList @3 + QERPlug_Dispatch @4 + QERPlug_GetFuncTable @5 diff --git a/contrib/bobtoolz/dialogs/AboutDialog.cpp b/contrib/bobtoolz/dialogs/AboutDialog.cpp new file mode 100644 index 00000000..e5d37038 --- /dev/null +++ b/contrib/bobtoolz/dialogs/AboutDialog.cpp @@ -0,0 +1,62 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// AboutDialog.cpp : implementation file +// + +#include "../StdAfx.h" +#include "AboutDialog.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +//static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CAboutDialog dialog + + +CAboutDialog::CAboutDialog(CWnd* pParent /*=NULL*/) + : CDialog(CAboutDialog::IDD, pParent) +{ + //{{AFX_DATA_INIT(CAboutDialog) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void CAboutDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CAboutDialog) + // NOTE: the ClassWizard will add DDX and DDV calls here + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CAboutDialog, CDialog) + //{{AFX_MSG_MAP(CAboutDialog) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CAboutDialog message handlers + diff --git a/contrib/bobtoolz/dialogs/AboutDialog.h b/contrib/bobtoolz/dialogs/AboutDialog.h new file mode 100644 index 00000000..54cfd017 --- /dev/null +++ b/contrib/bobtoolz/dialogs/AboutDialog.h @@ -0,0 +1,64 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(AFX_ABOUTDIALOG_H__3BA55F71_1D27_11D3_BC7B_F7EFD9765E37__INCLUDED_) +#define AFX_ABOUTDIALOG_H__3BA55F71_1D27_11D3_BC7B_F7EFD9765E37__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 +// AboutDialog.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CAboutDialog dialog + +class CAboutDialog : public CDialog +{ +// Construction +public: + CAboutDialog(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CAboutDialog) + enum { IDD = IDD_ABOUT }; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CAboutDialog) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CAboutDialog) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Developer Studio will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_ABOUTDIALOG_H__3BA55F71_1D27_11D3_BC7B_F7EFD9765E37__INCLUDED_) diff --git a/contrib/bobtoolz/dialogs/AutoCaulkDialog.cpp b/contrib/bobtoolz/dialogs/AutoCaulkDialog.cpp new file mode 100644 index 00000000..fd46b7fb --- /dev/null +++ b/contrib/bobtoolz/dialogs/AutoCaulkDialog.cpp @@ -0,0 +1,63 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// AutoCaulkDialog.cpp : implementation file +// + +#include "../StdAfx.h" +#include "../bobtoolz.h" +#include "AutoCaulkDialog.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CAutoCaulkDialog dialog + + +CAutoCaulkDialog::CAutoCaulkDialog(CWnd* pParent /*=NULL*/) + : CDialog(CAutoCaulkDialog::IDD, pParent) +{ + //{{AFX_DATA_INIT(CAutoCaulkDialog) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT +} + + +void CAutoCaulkDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CAutoCaulkDialog) + DDX_Control(pDX, IDC_PROGRESS2, m_prog2); + DDX_Control(pDX, IDC_PROGRESS1, m_prog1); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CAutoCaulkDialog, CDialog) + //{{AFX_MSG_MAP(CAutoCaulkDialog) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CAutoCaulkDialog message handlers diff --git a/contrib/bobtoolz/dialogs/AutoCaulkDialog.h b/contrib/bobtoolz/dialogs/AutoCaulkDialog.h new file mode 100644 index 00000000..a8740275 --- /dev/null +++ b/contrib/bobtoolz/dialogs/AutoCaulkDialog.h @@ -0,0 +1,66 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(AFX_AUTOCAULKDIALOG_H__C2783D61_DDEB_11D4_ACF6_004095A18133__INCLUDED_) +#define AFX_AUTOCAULKDIALOG_H__C2783D61_DDEB_11D4_ACF6_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// AutoCaulkDialog.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CAutoCaulkDialog dialog + +class CAutoCaulkDialog : public CDialog +{ +// Construction +public: + CAutoCaulkDialog(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CAutoCaulkDialog) + enum { IDD = IDD_AUTOCAULK_DIALOG }; + CProgressCtrl m_prog2; + CProgressCtrl m_prog1; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CAutoCaulkDialog) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CAutoCaulkDialog) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_AUTOCAULKDIALOG_H__C2783D61_DDEB_11D4_ACF6_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/dialogs/AutoCaulkStartDialog.cpp b/contrib/bobtoolz/dialogs/AutoCaulkStartDialog.cpp new file mode 100644 index 00000000..857df71a --- /dev/null +++ b/contrib/bobtoolz/dialogs/AutoCaulkStartDialog.cpp @@ -0,0 +1,66 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// AutoCaulkStartDialog.cpp : implementation file +// + +#include "../StdAfx.h" +#include "../bobtoolz.h" +#include "AutoCaulkStartDialog.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CAutoCaulkStartDialog dialog + + +CAutoCaulkStartDialog::CAutoCaulkStartDialog(CWnd* pParent /*=NULL*/) + : CDialog(CAutoCaulkStartDialog::IDD, pParent) +{ + //{{AFX_DATA_INIT(CAutoCaulkStartDialog) + m_bAllowDestruction = FALSE; + m_Warning1 = _T(""); + m_nMode = 0; + //}}AFX_DATA_INIT +} + + +void CAutoCaulkStartDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CAutoCaulkStartDialog) + DDX_Check(pDX, IDC_KILLBRUSHES_CHECK, m_bAllowDestruction); + DDX_Text(pDX, IDC_WARNING1_STATIC, m_Warning1); + DDX_Radio(pDX, IDC_AC_NORMAL_RADIO, m_nMode); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CAutoCaulkStartDialog, CDialog) + //{{AFX_MSG_MAP(CAutoCaulkStartDialog) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CAutoCaulkStartDialog message handlers diff --git a/contrib/bobtoolz/dialogs/AutoCaulkStartDialog.h b/contrib/bobtoolz/dialogs/AutoCaulkStartDialog.h new file mode 100644 index 00000000..9311c51c --- /dev/null +++ b/contrib/bobtoolz/dialogs/AutoCaulkStartDialog.h @@ -0,0 +1,71 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(AFX_AUTOCAULKSTARTDIALOG_H__F3DE2E81_E73E_11D4_ACF7_004095A18133__INCLUDED_) +#define AFX_AUTOCAULKSTARTDIALOG_H__F3DE2E81_E73E_11D4_ACF7_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// AutoCaulkStartDialog.h : header file +// + +#define MODE_AC_NORMAL 0 +#define MODE_AC_BUILD_MINI_PRT 1 +#define MODE_AC_SUPER 2 + +///////////////////////////////////////////////////////////////////////////// +// CAutoCaulkStartDialog dialog + +class CAutoCaulkStartDialog : public CDialog +{ +// Construction +public: + CAutoCaulkStartDialog(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CAutoCaulkStartDialog) + enum { IDD = IDD_AUTOCAULKSTART_DIALOG }; + BOOL m_bAllowDestruction; + CString m_Warning1; + int m_nMode; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CAutoCaulkStartDialog) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CAutoCaulkStartDialog) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_AUTOCAULKSTARTDIALOG_H__F3DE2E81_E73E_11D4_ACF7_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/dialogs/BrushCheckDialog.h b/contrib/bobtoolz/dialogs/BrushCheckDialog.h new file mode 100644 index 00000000..2ccf8e8f --- /dev/null +++ b/contrib/bobtoolz/dialogs/BrushCheckDialog.h @@ -0,0 +1,65 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(AFX_BRUSHCHECKDIALOG_H__4BF2C701_D9EF_11D4_ACF6_004095A18133__INCLUDED_) +#define AFX_BRUSHCHECKDIALOG_H__4BF2C701_D9EF_11D4_ACF6_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// BrushCheckDialog.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CBrushCheckDialog dialog + +class CBrushCheckDialog : public CDialog +{ +// Construction +public: + CBrushCheckDialog(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CBrushCheckDialog) + enum { IDD = IDD_BRUSHCHECKER_DIALOG }; + CProgressCtrl m_prog1; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CBrushCheckDialog) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CBrushCheckDialog) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_BRUSHCHECKDIALOG_H__4BF2C701_D9EF_11D4_ACF6_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/dialogs/DoorDialog.cpp b/contrib/bobtoolz/dialogs/DoorDialog.cpp new file mode 100644 index 00000000..2a1829d5 --- /dev/null +++ b/contrib/bobtoolz/dialogs/DoorDialog.cpp @@ -0,0 +1,92 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// DoorDialog.cpp : implementation file +// + +#include "../StdAfx.h" +#include "DoorDialog.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CDoorDialog dialog + + +CDoorDialog::CDoorDialog(CWnd* pParent /*=NULL*/) + : CDialog(CDoorDialog::IDD, pParent) +{ + //{{AFX_DATA_INIT(CDoorDialog) + m_fbTextureName = _T(""); + m_bSclMainHor = TRUE; + m_bSclMainVert = TRUE; + m_bSclTrimHor = TRUE; + m_bSclTrimVert = FALSE; + m_trimTextureName = _T(""); + m_trimTexSetBox = _T(""); + m_mainTexSetBox = _T(""); + m_doorDirection = -1; + //}}AFX_DATA_INIT +} + + +void CDoorDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CDoorDialog) + DDX_Text(pDX, IDC_FBTEXTURE_EDIT, m_fbTextureName); + DDX_Check(pDX, IDC_TEXSCALE1_CHECK, m_bSclMainHor); + DDX_Check(pDX, IDC_TEXSCALE2_CHECK, m_bSclMainVert); + DDX_Check(pDX, IDC_TEXSCALE3_CHECK, m_bSclTrimHor); + DDX_Check(pDX, IDC_TEXSCALE4_CHECK, m_bSclTrimVert); + DDX_Text(pDX, IDC_TRIMTEXTURE_EDIT, m_trimTextureName); + DDX_CBString(pDX, IDC_TRIMTEX_COMBO, m_trimTexSetBox); + DDX_CBString(pDX, IDC_MAINTEX_COMBO, m_mainTexSetBox); + DDX_Radio(pDX, IDC_DIR_NS_RADIO, m_doorDirection); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CDoorDialog, CDialog) + //{{AFX_MSG_MAP(CDoorDialog) + ON_BN_CLICKED(IDC_SET_MAINTEX_BTN, OnSetMaintexBtn) + ON_BN_CLICKED(IDC_SET_TRIMTEX_BTN, OnSetTrimtexBtn) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CDoorDialog message handlers + +void CDoorDialog::OnSetMaintexBtn() +{ + UpdateData(TRUE); + m_fbTextureName = m_mainTexSetBox; + UpdateData(FALSE); +} + +void CDoorDialog::OnSetTrimtexBtn() +{ + UpdateData(TRUE); + m_trimTextureName = m_trimTexSetBox; + UpdateData(FALSE); +} diff --git a/contrib/bobtoolz/dialogs/DoorDialog.h b/contrib/bobtoolz/dialogs/DoorDialog.h new file mode 100644 index 00000000..4d312cb9 --- /dev/null +++ b/contrib/bobtoolz/dialogs/DoorDialog.h @@ -0,0 +1,74 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(AFX_DOORDIALOG_H__F36CBE01_D2C4_11D4_AE97_004095A18133__INCLUDED_) +#define AFX_DOORDIALOG_H__F36CBE01_D2C4_11D4_AE97_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// DoorDialog.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CDoorDialog dialog + +class CDoorDialog : public CDialog +{ +// Construction +public: + CDoorDialog(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CDoorDialog) + enum { IDD = IDD_DOOR_DIALOG }; + CString m_fbTextureName; + BOOL m_bSclMainHor; + BOOL m_bSclMainVert; + BOOL m_bSclTrimHor; + BOOL m_bSclTrimVert; + CString m_trimTextureName; + CString m_trimTexSetBox; + CString m_mainTexSetBox; + int m_doorDirection; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CDoorDialog) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CDoorDialog) + afx_msg void OnSetMaintexBtn(); + afx_msg void OnSetTrimtexBtn(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_DOORDIALOG_H__F36CBE01_D2C4_11D4_AE97_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/dialogs/IntersectDialog.cpp b/contrib/bobtoolz/dialogs/IntersectDialog.cpp new file mode 100644 index 00000000..63a42d02 --- /dev/null +++ b/contrib/bobtoolz/dialogs/IntersectDialog.cpp @@ -0,0 +1,65 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// IntersectDialog.cpp : implementation file +// + +#include "../StdAfx.h" +#include "IntersectDialog.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CIntersectDialog dialog + + +CIntersectDialog::CIntersectDialog(CWnd* pParent /*=NULL*/) + : CDialog(CIntersectDialog::IDD, pParent) +{ + //{{AFX_DATA_INIT(CIntersectDialog) + m_nBrushOptions = 1; + m_bUseDetail = FALSE; + m_bDuplicateOnly = FALSE; + //}}AFX_DATA_INIT +} + + +void CIntersectDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CIntersectDialog) + DDX_Radio(pDX, IDC_WHOLEMAP_RADIO, m_nBrushOptions); + DDX_Check(pDX, IDC_DETAIL_INCLUDE_CHECK, m_bUseDetail); + DDX_Check(pDX, IDC_DUPLICATEONLY_CHECK, m_bDuplicateOnly); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CIntersectDialog, CDialog) + //{{AFX_MSG_MAP(CIntersectDialog) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CIntersectDialog message handlers diff --git a/contrib/bobtoolz/dialogs/IntersectDialog.h b/contrib/bobtoolz/dialogs/IntersectDialog.h new file mode 100644 index 00000000..d1190a6f --- /dev/null +++ b/contrib/bobtoolz/dialogs/IntersectDialog.h @@ -0,0 +1,70 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(AFX_INTERSECTDIALOG_H__03507C01_D3B3_11D4_AE97_004095A18133__INCLUDED_) +#define AFX_INTERSECTDIALOG_H__03507C01_D3B3_11D4_AE97_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// IntersectDialog.h : header file +// + +#define BRUSH_OPT_WHOLE_MAP 0 +#define BRUSH_OPT_SELECTED 1 + +///////////////////////////////////////////////////////////////////////////// +// CIntersectDialog dialog + +class CIntersectDialog : public CDialog +{ +// Construction +public: + CIntersectDialog(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CIntersectDialog) + enum { IDD = IDD_INTERSECT_DIALOG }; + int m_nBrushOptions; + BOOL m_bUseDetail; + BOOL m_bDuplicateOnly; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CIntersectDialog) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CIntersectDialog) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_INTERSECTDIALOG_H__03507C01_D3B3_11D4_AE97_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/dialogs/IntersectInfoDialog.cpp b/contrib/bobtoolz/dialogs/IntersectInfoDialog.cpp new file mode 100644 index 00000000..f7684a9e --- /dev/null +++ b/contrib/bobtoolz/dialogs/IntersectInfoDialog.cpp @@ -0,0 +1,61 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// IntersectInfoDialog.cpp : implementation file +// + +#include "../StdAfx.h" +#include "../bobtoolz.h" +#include "IntersectInfoDialog.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CIntersectInfoDialog dialog + + +CIntersectInfoDialog::CIntersectInfoDialog(CWnd* pParent /*=NULL*/) + : CDialog(CIntersectInfoDialog::IDD, pParent) +{ + //{{AFX_DATA_INIT(CIntersectInfoDialog) + //}}AFX_DATA_INIT +} + + +void CIntersectInfoDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CIntersectInfoDialog) + DDX_Control(pDX, IDC_PROGRESS1, m_prog1); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CIntersectInfoDialog, CDialog) + //{{AFX_MSG_MAP(CIntersectInfoDialog) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CIntersectInfoDialog message handlers diff --git a/contrib/bobtoolz/dialogs/IntersectInfoDialog.h b/contrib/bobtoolz/dialogs/IntersectInfoDialog.h new file mode 100644 index 00000000..51a4bf75 --- /dev/null +++ b/contrib/bobtoolz/dialogs/IntersectInfoDialog.h @@ -0,0 +1,65 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(AFX_INTERSECTINFODIALOG_H__62CDC8CD_D9D2_11D4_ACF6_004095A18133__INCLUDED_) +#define AFX_INTERSECTINFODIALOG_H__62CDC8CD_D9D2_11D4_ACF6_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// IntersectInfoDialog.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CIntersectInfoDialog dialog + +class CIntersectInfoDialog : public CDialog +{ +// Construction +public: + CIntersectInfoDialog(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CIntersectInfoDialog) + enum { IDD = IDD_INTERSECT_INFO_DIALOG }; + CProgressCtrl m_prog1; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CIntersectInfoDialog) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CIntersectInfoDialog) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_INTERSECTINFODIALOG_H__62CDC8CD_D9D2_11D4_ACF6_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/dialogs/PolygonDialog.cpp b/contrib/bobtoolz/dialogs/PolygonDialog.cpp new file mode 100644 index 00000000..f0a38d6e --- /dev/null +++ b/contrib/bobtoolz/dialogs/PolygonDialog.cpp @@ -0,0 +1,116 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// PolygonDialog.cpp : implementation file +// + +#include "../StdAfx.h" +#include "PolygonDialog.h" +#include "../shapes.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CPolygonDialog dialog + + +CPolygonDialog::CPolygonDialog(CWnd* pParent /*=NULL*/) + : CDialog(CPolygonDialog::IDD, pParent) +{ + //{{AFX_DATA_INIT(CPolygonDialog) + m_nSideCount = 3; + m_bInverse = FALSE; + m_bBorder = FALSE; + m_nBorderSize = 8; + m_bAlignTop = FALSE; + //}}AFX_DATA_INIT +} + +void CPolygonDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CPolygonDialog) + DDX_Text(pDX, IDC_EDIT1, m_nSideCount); + DDV_MinMaxUInt(pDX, m_nSideCount, 3, MAX_POLYGON_FACES); + DDX_Check(pDX, IDC_INVERSE_CHK, m_bInverse); + DDX_Check(pDX, IDC_BORDER_CHK, m_bBorder); + DDX_Text(pDX, IDC_BORDER_EDIT, m_nBorderSize); + DDV_MinMaxUInt(pDX, m_nBorderSize, 1, 1024); + DDX_Check(pDX, IDC_ALIGN_CHK, m_bAlignTop); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CPolygonDialog, CDialog) + //{{AFX_MSG_MAP(CPolygonDialog) + ON_BN_CLICKED(IDC_BORDER_CHK, OnBorderChkClicked) + ON_BN_CLICKED(IDC_INVERSE_CHK, OnInverseChkClickrd) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CPolygonDialog message handlers + +BOOL CPolygonDialog::OnInitDialog() +{ + CDialog::OnInitDialog(); + + EnableBordered(!GetChkBool(IDC_INVERSE_CHK)); + EnableBorderEdit(!GetChkBool(IDC_INVERSE_CHK) && GetChkBool(IDC_BORDER_CHK)); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +void CPolygonDialog::EnableBordered(BOOL bEnable) +{ + CWnd* dtlChk = GetDlgItem(IDC_BORDER_CHK); + if(dtlChk) + dtlChk->EnableWindow(bEnable); +} + +void CPolygonDialog::EnableBorderEdit(BOOL bEnable) +{ + CWnd* dtlChk = GetDlgItem(IDC_BORDER_EDIT); + if(dtlChk) + dtlChk->EnableWindow(bEnable); +} + +void CPolygonDialog::OnBorderChkClicked() +{ + EnableBorderEdit(!GetChkBool(IDC_INVERSE_CHK) && GetChkBool(IDC_BORDER_CHK)); +} + +void CPolygonDialog::OnInverseChkClickrd() +{ + EnableBordered(!GetChkBool(IDC_INVERSE_CHK)); + EnableBorderEdit(!GetChkBool(IDC_INVERSE_CHK) && GetChkBool(IDC_BORDER_CHK)); +} + +BOOL CPolygonDialog::GetChkBool(int nID) +{ + CButton* btn = (CButton*)GetDlgItem(nID); + if(btn) + return btn->GetCheck(); + return FALSE; +} diff --git a/contrib/bobtoolz/dialogs/PolygonDialog.h b/contrib/bobtoolz/dialogs/PolygonDialog.h new file mode 100644 index 00000000..afe5cb87 --- /dev/null +++ b/contrib/bobtoolz/dialogs/PolygonDialog.h @@ -0,0 +1,74 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(AFX_POLYGONDIALOG_H__EF7FE400_628A_11D1_B66D_004095A18133__INCLUDED_) +#define AFX_POLYGONDIALOG_H__EF7FE400_628A_11D1_B66D_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// PolygonDialog.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CPolygonDialog dialog + +class CPolygonDialog : public CDialog +{ +// Construction +public: + BOOL GetChkBool(int nID); + void EnableBorderEdit(BOOL bEnable); + void EnableBordered(BOOL bEnable); + CPolygonDialog(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CPolygonDialog) + enum { IDD = IDD_POLYGON_DIALOG }; + UINT m_nSideCount; + BOOL m_bInverse; + BOOL m_bBorder; + UINT m_nBorderSize; + BOOL m_bAlignTop; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CPolygonDialog) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CPolygonDialog) + virtual BOOL OnInitDialog(); + afx_msg void OnBorderChkClicked(); + afx_msg void OnInverseChkClickrd(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_POLYGONDIALOG_H__EF7FE400_628A_11D1_B66D_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/dialogs/StairDialog.cpp b/contrib/bobtoolz/dialogs/StairDialog.cpp new file mode 100644 index 00000000..4e7feba3 --- /dev/null +++ b/contrib/bobtoolz/dialogs/StairDialog.cpp @@ -0,0 +1,105 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// StairDialog.cpp : implementation file +// + +#include "../StdAfx.h" +#include "StairDialog.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CStairDialog dialog + + +CStairDialog::CStairDialog(CWnd* pParent /*=NULL*/) + : CDialog(CStairDialog::IDD, pParent) +{ + //{{AFX_DATA_INIT(CStairDialog) + m_nStairHeight = 8; + m_StairDir = 0; + m_StairStyle = 0; + m_riserTexture = _T(""); + m_bDetail = TRUE; + //}}AFX_DATA_INIT +} + +void CStairDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CStairDialog) + DDX_Text(pDX, IDC_EDIT1, m_nStairHeight); + DDV_MinMaxUInt(pDX, m_nStairHeight, 1, 256); + DDX_Radio(pDX, IDC_DIR_N_RADIO, m_StairDir); + DDX_Radio(pDX, IDC_STYLE_ORIG_RADIO, m_StairStyle); + DDX_Text(pDX, IDC_RISER_EDIT, m_riserTexture); + DDV_MaxChars(pDX, m_riserTexture, 256); + DDX_Check(pDX, IDC_DETAIL_CHK, m_bDetail); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CStairDialog, CDialog) + //{{AFX_MSG_MAP(CStairDialog) + ON_BN_CLICKED(IDC_STYLE_BOB_RADIO, OnStyleBobClicked) + ON_BN_CLICKED(IDC_STYLE_ORIG_RADIO, OnStyleOrigClicked) + ON_BN_CLICKED(IDC_STYLE_CORNER_RADIO, OnStyleCornerClicked) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CStairDialog message handlers + +void CStairDialog::OnStyleBobClicked() +{ + EnableDetail(TRUE); +} + +void CStairDialog::OnStyleOrigClicked() +{ + EnableDetail(FALSE); +} + +void CStairDialog::EnableDetail(BOOL bEnable) +{ + CWnd* dtlChk = GetDlgItem(IDC_DETAIL_CHK); + if(dtlChk) + dtlChk->EnableWindow(bEnable); +} + + +BOOL CStairDialog::OnInitDialog() +{ + CDialog::OnInitDialog(); + + EnableDetail(m_StairStyle == 1); + + return TRUE; // return TRUE unless you set the focus to a control + // EXCEPTION: OCX Property Pages should return FALSE +} + +void CStairDialog::OnStyleCornerClicked() +{ + EnableDetail(FALSE); +} diff --git a/contrib/bobtoolz/dialogs/StairDialog.h b/contrib/bobtoolz/dialogs/StairDialog.h new file mode 100644 index 00000000..0bcdc0ec --- /dev/null +++ b/contrib/bobtoolz/dialogs/StairDialog.h @@ -0,0 +1,74 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(AFX_STAIRDIALOG_H__942FFF20_5F9E_11D1_B66D_004095A18133__INCLUDED_) +#define AFX_STAIRDIALOG_H__942FFF20_5F9E_11D1_B66D_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// StairDialog.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CStairDialog dialog + +class CStairDialog : public CDialog +{ +// Construction +public: + CStairDialog(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CStairDialog) + enum { IDD = IDD_STAIR_DIALOG }; + UINT m_nStairHeight; + int m_StairDir; + int m_StairStyle; + CString m_riserTexture; + BOOL m_bDetail; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CStairDialog) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CStairDialog) + afx_msg void OnStyleBobClicked(); + afx_msg void OnStyleOrigClicked(); + virtual BOOL OnInitDialog(); + afx_msg void OnStyleCornerClicked(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +private: + void EnableDetail(BOOL bEnable); +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STAIRDIALOG_H__942FFF20_5F9E_11D1_B66D_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/dialogs/TextureResetDialog.cpp b/contrib/bobtoolz/dialogs/TextureResetDialog.cpp new file mode 100644 index 00000000..77ebc766 --- /dev/null +++ b/contrib/bobtoolz/dialogs/TextureResetDialog.cpp @@ -0,0 +1,81 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// TextureResetDialog.cpp : implementation file +// + +#include "../StdAfx.h" +#include "../bobtoolz.h" +#include "TextureResetDialog.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CTextureResetDialog dialog + + +CTextureResetDialog::CTextureResetDialog(CWnd* pParent /*=NULL*/) + : CDialog(CTextureResetDialog::IDD, pParent) +{ + //{{AFX_DATA_INIT(CTextureResetDialog) + m_bAllTextures = FALSE; + m_TextureName = _T(""); + m_nRotation = 0; + m_fScaleHorizontal = 0.5f; + m_fScaleVertical = 0.5f; + m_nShiftHorizontal = 0; + m_nShiftVertical = 0; + m_bOnlyTexture = FALSE; + m_NewTextureName = _T(""); + //}}AFX_DATA_INIT +} + + +void CTextureResetDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CTextureResetDialog) + DDX_Check(pDX, IDC_ALLTEXTURES_CHECK, m_bAllTextures); + DDX_Text(pDX, IDC_RESET_TEXTURE_EDIT, m_TextureName); + DDV_MaxChars(pDX, m_TextureName, 256); + DDX_Text(pDX, IDC_ROTATION_EDIT, m_nRotation); + DDV_MinMaxInt(pDX, m_nRotation, 0, 360); + DDX_Text(pDX, IDC_SCL_HOR_EDIT, m_fScaleHorizontal); + DDX_Text(pDX, IDC_SCL_VERT_EDIT, m_fScaleVertical); + DDX_Text(pDX, IDC_SHFT_HOR_EDIT, m_nShiftHorizontal); + DDX_Text(pDX, IDC_SHFT_VER_EDIT, m_nShiftVertical); + DDX_Check(pDX, IDC_ONLYTEXTURE_CHECK, m_bOnlyTexture); + DDX_Text(pDX, IDC_RESET_NEW_TEXTURE_EDIT, m_NewTextureName); + DDV_MaxChars(pDX, m_NewTextureName, 256); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CTextureResetDialog, CDialog) + //{{AFX_MSG_MAP(CTextureResetDialog) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CTextureResetDialog message handlers diff --git a/contrib/bobtoolz/dialogs/TextureResetDialog.h b/contrib/bobtoolz/dialogs/TextureResetDialog.h new file mode 100644 index 00000000..6f041611 --- /dev/null +++ b/contrib/bobtoolz/dialogs/TextureResetDialog.h @@ -0,0 +1,73 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(AFX_TEXTURERESETDIALOG_H__42D665C1_ED84_11D4_ACF7_004095A18133__INCLUDED_) +#define AFX_TEXTURERESETDIALOG_H__42D665C1_ED84_11D4_ACF7_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// TextureResetDialog.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CTextureResetDialog dialog + +class CTextureResetDialog : public CDialog +{ +// Construction +public: + CTextureResetDialog(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CTextureResetDialog) + enum { IDD = IDD_TEXTURE_RESET_DIALOG }; + BOOL m_bAllTextures; + CString m_TextureName; + int m_nRotation; + float m_fScaleHorizontal; + float m_fScaleVertical; + int m_nShiftHorizontal; + int m_nShiftVertical; + BOOL m_bOnlyTexture; + CString m_NewTextureName; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CTextureResetDialog) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CTextureResetDialog) + // NOTE: the ClassWizard will add member functions here + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_TEXTURERESETDIALOG_H__42D665C1_ED84_11D4_ACF7_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/dialogs/brushcheckdialog.cpp b/contrib/bobtoolz/dialogs/brushcheckdialog.cpp new file mode 100644 index 00000000..06fe6c9b --- /dev/null +++ b/contrib/bobtoolz/dialogs/brushcheckdialog.cpp @@ -0,0 +1,61 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// BrushCheckDialog.cpp : implementation file +// + +#include "../StdAfx.h" +#include "../bobtoolz.h" +#include "BrushCheckDialog.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CBrushCheckDialog dialog + + +CBrushCheckDialog::CBrushCheckDialog(CWnd* pParent /*=NULL*/) + : CDialog(CBrushCheckDialog::IDD, pParent) +{ + //{{AFX_DATA_INIT(CBrushCheckDialog) + //}}AFX_DATA_INIT +} + + +void CBrushCheckDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CBrushCheckDialog) + DDX_Control(pDX, IDC_PROGRESS1, m_prog1); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CBrushCheckDialog, CDialog) + //{{AFX_MSG_MAP(CBrushCheckDialog) + // NOTE: the ClassWizard will add message map macros here + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CBrushCheckDialog message handlers diff --git a/contrib/bobtoolz/dialogs/dialogs-gtk.cpp b/contrib/bobtoolz/dialogs/dialogs-gtk.cpp new file mode 100644 index 00000000..15d9db67 --- /dev/null +++ b/contrib/bobtoolz/dialogs/dialogs-gtk.cpp @@ -0,0 +1,1904 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "dialogs-gtk.h" +#include "../funchandlers.h" + +#include "str.h" +#include +#include +#include "gtkutil/pointer.h" + +#include "../lists.h" +#include "../misc.h" + + +/*-------------------------------- + Callback Functions +---------------------------------*/ + +typedef struct { + GtkWidget *cbTexChange; + GtkWidget *editTexOld, *editTexNew; + + GtkWidget *cbScaleHor, *cbScaleVert; + GtkWidget *editScaleHor, *editScaleVert; + + GtkWidget *cbShiftHor, *cbShiftVert; + GtkWidget *editShiftHor, *editShiftVert; + + GtkWidget *cbRotation; + GtkWidget *editRotation; +}dlg_texReset_t; + +dlg_texReset_t dlgTexReset; + +void Update_TextureReseter(); + +static void dialog_button_callback_texreset_update (GtkWidget *widget, gpointer data) +{ + Update_TextureReseter(); +} + +void Update_TextureReseter() +{ + gboolean check; + + check = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( dlgTexReset.cbTexChange )); + gtk_entry_set_editable (GTK_ENTRY (dlgTexReset.editTexNew), check); + gtk_entry_set_editable (GTK_ENTRY (dlgTexReset.editTexOld), check); + + check = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( dlgTexReset.cbScaleHor )); + gtk_entry_set_editable (GTK_ENTRY (dlgTexReset.editScaleHor), check); + + check = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( dlgTexReset.cbScaleVert )); + gtk_entry_set_editable (GTK_ENTRY (dlgTexReset.editScaleVert), check); + + check = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( dlgTexReset.cbShiftHor )); + gtk_entry_set_editable (GTK_ENTRY (dlgTexReset.editShiftHor), check); + + check = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( dlgTexReset.cbShiftVert )); + gtk_entry_set_editable (GTK_ENTRY (dlgTexReset.editShiftVert), check); + + check = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( dlgTexReset.cbRotation )); + gtk_entry_set_editable (GTK_ENTRY (dlgTexReset.editRotation), check); +} + +static void dialog_button_callback (GtkWidget *widget, gpointer data) +{ + GtkWidget *parent; + int *loop; + EMessageBoxReturn *ret; + + parent = gtk_widget_get_toplevel (widget); + loop = (int*)g_object_get_data (G_OBJECT (parent), "loop"); + ret = (EMessageBoxReturn*)g_object_get_data (G_OBJECT (parent), "ret"); + + *loop = 0; + *ret = (EMessageBoxReturn)gpointer_to_int(data); +} + +static gint dialog_delete_callback (GtkWidget *widget, GdkEvent* event, gpointer data) +{ + int *loop; + + gtk_widget_hide (widget); + loop = (int*)g_object_get_data (G_OBJECT (widget), "loop"); + *loop = 0; + + return TRUE; +} + +static void dialog_button_callback_settex (GtkWidget *widget, gpointer data) +{ + TwinWidget* tw = (TwinWidget*)data; + + GtkEntry* entry = GTK_ENTRY( tw->one ); + GtkCombo* combo = GTK_COMBO( tw->two ); + + const gchar* tex = gtk_entry_get_text(GTK_ENTRY( combo->entry )); + gtk_entry_set_text( entry, tex); +} + +/*-------------------------------- + Data validation Routines +---------------------------------*/ + +bool ValidateTextFloat(const char* pData, char* error_title, float* value) +{ + if(pData) + { + float testNum = (float)atof(pData); + + if((testNum == 0.0f) && strcmp(pData, "0")) + { + DoMessageBox("Please Enter A Floating Point Number", error_title, eMB_OK); + return FALSE; + } + else + { + *value = testNum; + return TRUE; + } + } + + DoMessageBox("Please Enter A Floating Point Number", error_title, eMB_OK); + return FALSE; +} + +bool ValidateTextFloatRange(const char* pData, float min, float max, char* error_title, float* value) +{ + char error_buffer[256]; + sprintf(error_buffer, "Please Enter A Floating Point Number Between %.3f and %.3f", min, max); + + if(pData) + { + float testNum = (float)atof(pData); + + if((testNum < min) || (testNum > max)) + { + DoMessageBox(error_buffer, error_title, eMB_OK); + return FALSE; + } + else + { + *value = testNum; + return TRUE; + } + } + + DoMessageBox(error_buffer, error_title, eMB_OK); + return FALSE; +} + +bool ValidateTextIntRange(const char* pData, int min, int max, char* error_title, int* value) +{ + char error_buffer[256]; + sprintf(error_buffer, "Please Enter An Integer Between %i and %i", min, max); + + if(pData) + { + int testNum = atoi(pData); + + if((testNum < min) || (testNum > max)) + { + DoMessageBox(error_buffer, error_title, eMB_OK); + return FALSE; + } + else + { + *value = testNum; + return TRUE; + } + } + + DoMessageBox(error_buffer, error_title, eMB_OK); + return FALSE; +} + +bool ValidateTextInt(const char* pData, char* error_title, int* value) +{ + if(pData) + { + int testNum = atoi(pData); + + if((testNum == 0) && strcmp(pData, "0")) + { + DoMessageBox("Please Enter An Integer", error_title, eMB_OK); + return FALSE; + } + else + { + *value = testNum; + return TRUE; + } + } + + DoMessageBox("Please Enter An Integer", error_title, eMB_OK); + return FALSE; +} + +/*-------------------------------- + Modal Dialog Boxes +---------------------------------*/ + +/* + + Major clean up of variable names etc required, excluding Mars's ones, + which are nicely done :) + +*/ + +EMessageBoxReturn DoMessageBox (const char* lpText, const char* lpCaption, EMessageBoxType type) +{ + GtkWidget *window, *w, *vbox, *hbox; + EMessageBoxReturn ret; + int loop = 1; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + gtk_window_set_title (GTK_WINDOW (window), lpCaption); + gtk_container_border_width (GTK_CONTAINER (window), 10); + g_object_set_data (G_OBJECT (window), "loop", &loop); + g_object_set_data (G_OBJECT (window), "ret", &ret); + gtk_widget_realize (window); + + vbox = gtk_vbox_new (FALSE, 10); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show (vbox); + + w = gtk_label_new (lpText); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT); + gtk_widget_show (w); + + w = gtk_hseparator_new (); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2); + gtk_widget_show (w); + + hbox = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); + gtk_widget_show (hbox); + + if (type == eMB_OK) + { + w = gtk_button_new_with_label ("Ok"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDOK)); + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + ret = eIDOK; + } + else if (type == eMB_OKCANCEL) + { + w = gtk_button_new_with_label ("Ok"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDOK)); + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDCANCEL)); + gtk_widget_show (w); + ret = eIDCANCEL; + } + else if (type == eMB_YESNOCANCEL) + { + w = gtk_button_new_with_label ("Yes"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDYES)); + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("No"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDNO)); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDCANCEL)); + gtk_widget_show (w); + ret = eIDCANCEL; + } + else /* if (mode == MB_YESNO) */ + { + w = gtk_button_new_with_label ("Yes"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDYES)); + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("No"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDNO)); + gtk_widget_show (w); + ret = eIDNO; + } + + gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_widget_show (window); + gtk_grab_add (window); + + while (loop) + gtk_main_iteration (); + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return ret; +} + +EMessageBoxReturn DoIntersectBox (IntersectRS* rs) +{ + GtkWidget *window, *w, *vbox, *hbox; + GtkWidget *radio1, *radio2; + GtkWidget *check1, *check2; + EMessageBoxReturn ret; + int loop = 1; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + + gtk_window_set_title (GTK_WINDOW (window), "Intersect"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + g_object_set_data (G_OBJECT (window), "loop", &loop); + g_object_set_data (G_OBJECT (window), "ret", &ret); + + gtk_widget_realize (window); + + + + vbox = gtk_vbox_new (FALSE, 10); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show (vbox); + + // ---- vbox ---- + + radio1 = gtk_radio_button_new_with_label(NULL, "Use Whole Map"); + gtk_box_pack_start (GTK_BOX (vbox), radio1, FALSE, FALSE, 2); + gtk_widget_show (radio1); + + radio2 = gtk_radio_button_new_with_label(((GtkRadioButton*)radio1)->group, "Use Selected Brushes"); + gtk_box_pack_start (GTK_BOX (vbox), radio2, FALSE, FALSE, 2); + gtk_widget_show (radio2); + + w = gtk_hseparator_new (); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2); + gtk_widget_show (w); + + check1 = gtk_check_button_new_with_label("Include Detail Brushes"); + gtk_box_pack_start (GTK_BOX (vbox), check1, FALSE, FALSE, 0); + gtk_widget_show (check1); + + check2 = gtk_check_button_new_with_label("Select Duplicate Brushes Only"); + gtk_box_pack_start (GTK_BOX (vbox), check2, FALSE, FALSE, 0); + gtk_widget_show (check2); + + hbox = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); + gtk_widget_show (hbox); + + // ---- hbox ---- ok/cancel buttons + + w = gtk_button_new_with_label ("Ok"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDOK)); + + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDCANCEL)); + gtk_widget_show (w); + ret = eIDCANCEL; + + // ---- /hbox ---- + + // ---- /vbox ---- + + gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_widget_show (window); + gtk_grab_add (window); + + while (loop) + gtk_main_iteration (); + + if(gtk_toggle_button_get_active((GtkToggleButton*)radio1)) + rs->nBrushOptions = BRUSH_OPT_WHOLE_MAP; + else if(gtk_toggle_button_get_active((GtkToggleButton*)radio2)) + rs->nBrushOptions = BRUSH_OPT_SELECTED; + + rs->bUseDetail = gtk_toggle_button_get_active((GtkToggleButton*)check1) ? true : false; + rs->bDuplicateOnly = gtk_toggle_button_get_active((GtkToggleButton*)check2) ? true : false; + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return ret; +} + +EMessageBoxReturn DoPolygonBox (PolygonRS* rs) +{ + GtkWidget *window, *w, *vbox, *hbox, *vbox2, *hbox2; + + GtkWidget *check1, *check2, *check3; + GtkWidget *text1, *text2; + + EMessageBoxReturn ret; + int loop = 1; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + + gtk_window_set_title (GTK_WINDOW (window), "Polygon Builder"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + g_object_set_data (G_OBJECT (window), "loop", &loop); + g_object_set_data (G_OBJECT (window), "ret", &ret); + + gtk_widget_realize (window); + + + + vbox = gtk_vbox_new (FALSE, 10); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show (vbox); + + // ---- vbox ---- + + hbox = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); + gtk_widget_show (hbox); + + // ---- hbox ---- + + + vbox2 = gtk_vbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 2); + gtk_widget_show (vbox2); + + // ---- vbox2 ---- + + hbox2 = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 2); + gtk_widget_show (hbox2); + + // ---- hbox2 ---- + + text1 = gtk_entry_new_with_max_length(256); + gtk_entry_set_text((GtkEntry*)text1, "3"); + gtk_box_pack_start (GTK_BOX (hbox2), text1, FALSE, FALSE, 2); + gtk_widget_show (text1); + + w = gtk_label_new ("Number Of Sides"); + gtk_box_pack_start (GTK_BOX (hbox2), w, FALSE, FALSE, 2); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT); + gtk_widget_show (w); + + // ---- /hbox2 ---- + + hbox2 = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 2); + gtk_widget_show (hbox2); + + // ---- hbox2 ---- + + text2 = gtk_entry_new_with_max_length(256); + gtk_entry_set_text((GtkEntry*)text2, "8"); + gtk_box_pack_start (GTK_BOX (hbox2), text2, FALSE, FALSE, 2); + gtk_widget_show (text2); + + w = gtk_label_new ("Border Width"); + gtk_box_pack_start (GTK_BOX (hbox2), w, FALSE, FALSE, 2); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT); + gtk_widget_show (w); + + // ---- /hbox2 ---- + + // ---- /vbox2 ---- + + + + vbox2 = gtk_vbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (hbox), vbox2, FALSE, FALSE, 2); + gtk_widget_show (vbox2); + + // ---- vbox2 ---- + + check1 = gtk_check_button_new_with_label("Use Border"); + gtk_box_pack_start (GTK_BOX (vbox2), check1, FALSE, FALSE, 0); + gtk_widget_show (check1); + + + check2 = gtk_check_button_new_with_label("Inverse Polygon"); + gtk_box_pack_start (GTK_BOX (vbox2), check2, FALSE, FALSE, 0); + gtk_widget_show (check2); + + + check3 = gtk_check_button_new_with_label("Align Top Edge"); + gtk_box_pack_start (GTK_BOX (vbox2), check3, FALSE, FALSE, 0); + gtk_widget_show (check3); + + // ---- /vbox2 ---- + + // ---- /hbox ---- + + hbox = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); + gtk_widget_show (hbox); + + // ---- hbox ---- + + w = gtk_button_new_with_label ("Ok"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDOK)); + + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDCANCEL)); + gtk_widget_show (w); + ret = eIDCANCEL; + + // ---- /hbox ---- + + // ---- /vbox ---- + + gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_widget_show (window); + gtk_grab_add (window); + + bool dialogError = TRUE; + while (dialogError) + { + loop = 1; + while (loop) + gtk_main_iteration (); + + dialogError = FALSE; + + if(ret == eIDOK) + { + rs->bUseBorder = gtk_toggle_button_get_active((GtkToggleButton*)check1) ? true : false; + rs->bInverse = gtk_toggle_button_get_active((GtkToggleButton*)check2) ? true : false; + rs->bAlignTop = gtk_toggle_button_get_active((GtkToggleButton*)check3) ? true : false; + + if(!ValidateTextIntRange(gtk_entry_get_text((GtkEntry*)text1), 3, 32, "Number Of Sides", &rs->nSides)) + dialogError = TRUE; + + if(rs->bUseBorder) + { + if(!ValidateTextIntRange(gtk_entry_get_text((GtkEntry*)text2), 8, 256, "Border Width", &rs->nBorderWidth)) + dialogError = TRUE; + } + } + } + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return ret; +} + +// mars +// for stair builder stuck as close as i could to the MFC version +// obviously feel free to change it at will :) +EMessageBoxReturn DoBuildStairsBox(BuildStairsRS* rs) +{ + // i made widgets for just about everything ... i think that's what i need to do dunno tho + GtkWidget *window, *w, *vbox, *hbox; + GtkWidget *textStairHeight, *textRiserTex, *textMainTex; + GtkWidget *radioNorth, *radioSouth, *radioEast, *radioWest; // i'm guessing we can't just abuse 'w' for these if we're getting a value + GtkWidget *radioOldStyle, *radioBobStyle, *radioCornerStyle; + GtkWidget *checkUseDetail; + GSList *radioDirection, *radioStyle; + EMessageBoxReturn ret; + int loop = 1; + + char *text = "Please set a value in the boxes below and press 'OK' to build the stairs"; + + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + + gtk_window_set_title( GTK_WINDOW( window ), "Stair Builder" ); + + gtk_container_border_width( GTK_CONTAINER( window ), 10 ); + + g_object_set_data (G_OBJECT (window), "loop", &loop); + g_object_set_data (G_OBJECT (window), "ret", &ret); + + gtk_widget_realize (window); + + // new vbox + vbox = gtk_vbox_new( FALSE, 10 ); + gtk_container_add( GTK_CONTAINER( window ), vbox ); + gtk_widget_show( vbox ); + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_container_add( GTK_CONTAINER( vbox ), hbox ); + gtk_widget_show( hbox ); + + // dunno if you want this text or not ... + w = gtk_label_new( text ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); // not entirely sure on all the parameters / what they do ... + gtk_widget_show( w ); + + w = gtk_hseparator_new(); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + // ------------------------- // indenting == good way of keeping track of lines :) + + // new hbox + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + textStairHeight = gtk_entry_new_with_max_length( 256 ); + gtk_box_pack_start( GTK_BOX( hbox ), textStairHeight, FALSE, FALSE, 1 ); + gtk_widget_show( textStairHeight ); + + w = gtk_label_new( "Stair Height" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 1 ); + gtk_widget_show( w ); + + // ------------------------- // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_label_new( "Direction:" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 5 ); + gtk_widget_show( w ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + // radio buttons confuse me ... + // but this _looks_ right + + // djbob: actually it looks very nice :), slightly better than the way i did it + // edit: actually it doesn't work :P, you must pass the last radio item each time, ugh + + radioNorth = gtk_radio_button_new_with_label( NULL, "North" ); + gtk_box_pack_start( GTK_BOX( hbox ), radioNorth, FALSE, FALSE, 3 ); + gtk_widget_show( radioNorth ); + + radioDirection = gtk_radio_button_group( GTK_RADIO_BUTTON( radioNorth ) ); + + radioSouth = gtk_radio_button_new_with_label( radioDirection, "South" ); + gtk_box_pack_start( GTK_BOX( hbox ), radioSouth, FALSE, FALSE, 2 ); + gtk_widget_show( radioSouth ); + + radioDirection = gtk_radio_button_group( GTK_RADIO_BUTTON( radioSouth ) ); + + radioEast = gtk_radio_button_new_with_label( radioDirection, "East" ); + gtk_box_pack_start( GTK_BOX( hbox ), radioEast, FALSE, FALSE, 1 ); + gtk_widget_show( radioEast ); + + radioDirection = gtk_radio_button_group( GTK_RADIO_BUTTON( radioEast ) ); + + radioWest = gtk_radio_button_new_with_label( radioDirection, "West" ); + gtk_box_pack_start( GTK_BOX( hbox ), radioWest, FALSE, FALSE, 0 ); + gtk_widget_show( radioWest ); + + // --------------------------- // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_label_new( "Style:" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 5 ); + gtk_widget_show( w ); + + // --------------------------- // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + radioOldStyle = gtk_radio_button_new_with_label( NULL, "Original" ); + gtk_box_pack_start( GTK_BOX( hbox ), radioOldStyle, FALSE, FALSE, 0 ); + gtk_widget_show( radioOldStyle ); + + radioStyle = gtk_radio_button_group( GTK_RADIO_BUTTON( radioOldStyle ) ); + + radioBobStyle = gtk_radio_button_new_with_label( radioStyle, "Bob's Style" ); + gtk_box_pack_start( GTK_BOX( hbox ), radioBobStyle, FALSE, FALSE, 0 ); + gtk_widget_show( radioBobStyle ); + + radioStyle = gtk_radio_button_group( GTK_RADIO_BUTTON( radioBobStyle ) ); + + radioCornerStyle = gtk_radio_button_new_with_label( radioStyle, "Corner Style" ); + gtk_box_pack_start( GTK_BOX( hbox ), radioCornerStyle, FALSE, FALSE, 0 ); + gtk_widget_show( radioCornerStyle ); + + // err, the q3r has an if or something so you need bob style checked before this + // is "ungreyed out" but you'll need to do that, as i suck :) + + // djbob: er.... yeah um, im not at all sure how i'm gonna sort this + // djbob: think we need some button callback functions or smuffin + // FIXME: actually get around to doing what i suggested!!!! + + checkUseDetail = gtk_check_button_new_with_label( "Use Detail Brushes" ); + gtk_box_pack_start( GTK_BOX( hbox ), checkUseDetail, FALSE, FALSE, 0 ); + gtk_widget_show( checkUseDetail ); + + // --------------------------- // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + textMainTex = gtk_entry_new_with_max_length( 512 ); + gtk_entry_set_text(GTK_ENTRY(textMainTex), rs->mainTexture); + gtk_box_pack_start( GTK_BOX( hbox ), textMainTex, FALSE, FALSE, 0 ); + gtk_widget_show( textMainTex ); + + w = gtk_label_new( "Main Texture" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 1 ); + gtk_widget_show( w ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + textRiserTex = gtk_entry_new_with_max_length( 512 ); + gtk_box_pack_start( GTK_BOX( hbox ), textRiserTex, FALSE, FALSE, 0 ); + gtk_widget_show( textRiserTex ); + + w = gtk_label_new( "Riser Texture" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 1 ); + gtk_widget_show( w ); + + // -------------------------- // + w = gtk_hseparator_new(); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_button_new_with_label( "OK" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( eIDOK ) ); + GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT ); + gtk_widget_grab_default( w ); + gtk_widget_show( w ); + + w = gtk_button_new_with_label( "Cancel" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 ); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( eIDCANCEL ) ); + gtk_widget_show( w ); + + ret = eIDCANCEL; + +// +djbob: need our "little" modal loop mars :P + gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_widget_show (window); + gtk_grab_add (window); + + bool dialogError = TRUE; + while (dialogError) + { + loop = 1; + while (loop) + gtk_main_iteration (); + + dialogError = FALSE; + + if(ret == eIDOK) + { + rs->bUseDetail = gtk_toggle_button_get_active((GtkToggleButton*)checkUseDetail) ? true : false; + + strcpy(rs->riserTexture, gtk_entry_get_text((GtkEntry*)textRiserTex)); + strcpy(rs->mainTexture, gtk_entry_get_text((GtkEntry*)textMainTex)); + + if(gtk_toggle_button_get_active((GtkToggleButton*)radioNorth)) + rs->direction = MOVE_NORTH; + else if(gtk_toggle_button_get_active((GtkToggleButton*)radioSouth)) + rs->direction = MOVE_SOUTH; + else if(gtk_toggle_button_get_active((GtkToggleButton*)radioEast)) + rs->direction = MOVE_EAST; + else if(gtk_toggle_button_get_active((GtkToggleButton*)radioWest)) + rs->direction = MOVE_WEST; + + if(!ValidateTextInt(gtk_entry_get_text((GtkEntry*)textStairHeight), "Stair Height", &rs->stairHeight)) + dialogError = TRUE; + + if(gtk_toggle_button_get_active((GtkToggleButton*)radioOldStyle)) + rs->style = STYLE_ORIGINAL; + else if(gtk_toggle_button_get_active((GtkToggleButton*)radioBobStyle)) + rs->style = STYLE_BOB; + else if(gtk_toggle_button_get_active((GtkToggleButton*)radioCornerStyle)) + rs->style = STYLE_CORNER; + } + } + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return ret; +// -djbob + + // there we go, all done ... on my end at least, not bad for a night's work +} + +EMessageBoxReturn DoDoorsBox(DoorRS* rs) +{ + GtkWidget *window, *hbox, *vbox, *w; + GtkWidget *textFrontBackTex, *textTrimTex; + GtkWidget *checkScaleMainH, *checkScaleMainV, *checkScaleTrimH, *checkScaleTrimV; + GtkWidget *comboMain, *comboTrim; + GtkWidget *buttonSetMain, *buttonSetTrim; + GtkWidget *radioNS, *radioEW; + GSList *radioOrientation; + TwinWidget tw1, tw2; + EMessageBoxReturn ret; + int loop = 1; + + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + + gtk_window_set_title( GTK_WINDOW( window ), "Door Builder" ); + + gtk_container_border_width( GTK_CONTAINER( window ), 10 ); + + g_object_set_data( G_OBJECT( window ), "loop", &loop ); + g_object_set_data( G_OBJECT( window ), "ret", &ret ); + + gtk_widget_realize (window); + + char buffer[256]; + GList *listMainTextures = NULL; + GList *listTrimTextures = NULL; + LoadGList(GetFilename(buffer, "plugins/bt/door-tex.txt"), &listMainTextures); + LoadGList(GetFilename(buffer, "plugins/bt/door-tex-trim.txt"), &listTrimTextures); + + vbox = gtk_vbox_new( FALSE, 10 ); + gtk_container_add( GTK_CONTAINER( window ), vbox ); + gtk_widget_show( vbox ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + textFrontBackTex = gtk_entry_new_with_max_length( 512 ); + gtk_entry_set_text( GTK_ENTRY( textFrontBackTex ), rs->mainTexture); + gtk_box_pack_start( GTK_BOX( hbox ), textFrontBackTex, FALSE, FALSE, 0 ); + gtk_widget_show( textFrontBackTex ); + + w = gtk_label_new( "Door Front/Back Texture" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + // ------------------------ // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + textTrimTex = gtk_entry_new_with_max_length( 512 ); + gtk_box_pack_start( GTK_BOX( hbox ), textTrimTex, FALSE, FALSE, 0 ); + gtk_widget_show( textTrimTex ); + + w = gtk_label_new( "Door Trim Texture" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + // ----------------------- // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + // sp: horizontally ???? + // djbob: yes mars, u can spell :] + checkScaleMainH = gtk_check_button_new_with_label( "Scale Main Texture Horizontally" ); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( checkScaleMainH ), TRUE); + gtk_box_pack_start( GTK_BOX( hbox ), checkScaleMainH, FALSE, FALSE, 0 ); + gtk_widget_show( checkScaleMainH ); + + checkScaleTrimH = gtk_check_button_new_with_label( "Scale Trim Texture Horizontally" ); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( checkScaleTrimH ), TRUE); + gtk_box_pack_start( GTK_BOX( hbox ), checkScaleTrimH, FALSE, FALSE, 0 ); + gtk_widget_show( checkScaleTrimH ); + + // ---------------------- // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + checkScaleMainV = gtk_check_button_new_with_label( "Scale Main Texture Vertically" ); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( checkScaleMainV ), TRUE); + gtk_box_pack_start( GTK_BOX( hbox ), checkScaleMainV, FALSE, FALSE, 0 ); + gtk_widget_show( checkScaleMainV ); + + checkScaleTrimV = gtk_check_button_new_with_label( "Scale Trim Texture Vertically" ); + gtk_box_pack_start( GTK_BOX( hbox ), checkScaleTrimV, FALSE, FALSE, 0 ); + gtk_widget_show( checkScaleTrimV ); + + // --------------------- // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + // djbob: lists added + + comboMain = gtk_combo_new(); + gtk_box_pack_start( GTK_BOX( hbox ), comboMain, FALSE, FALSE, 0 ); + gtk_combo_set_popdown_strings( GTK_COMBO( comboMain ), listMainTextures ); + gtk_combo_set_use_arrows( GTK_COMBO( comboMain ), 1 ); + gtk_widget_show( comboMain ); + + tw1.one = textFrontBackTex; + tw1.two = comboMain; + + buttonSetMain = gtk_button_new_with_label( "Set As Main Texture" ); + gtk_signal_connect( GTK_OBJECT( buttonSetMain ), "clicked", GTK_SIGNAL_FUNC( dialog_button_callback_settex ), &tw1 ); + gtk_box_pack_start( GTK_BOX( hbox ), buttonSetMain, FALSE, FALSE, 0 ); + gtk_widget_show( buttonSetMain ); + + // ------------------- // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + comboTrim = gtk_combo_new(); + gtk_box_pack_start( GTK_BOX( hbox ), comboTrim, FALSE, FALSE, 0 ); + gtk_combo_set_popdown_strings( GTK_COMBO( comboTrim ), listTrimTextures ); + gtk_combo_set_use_arrows( GTK_COMBO( comboMain ), 1 ); + gtk_widget_show( comboTrim ); + + tw2.one = textTrimTex; + tw2.two = comboTrim; + + buttonSetTrim = gtk_button_new_with_label( "Set As Trim Texture" ); + gtk_signal_connect( GTK_OBJECT( buttonSetTrim ), "clicked", GTK_SIGNAL_FUNC( dialog_button_callback_settex ), &tw2 ); + gtk_box_pack_start( GTK_BOX( hbox ), buttonSetTrim, FALSE, FALSE, 0 ); + gtk_widget_show( buttonSetTrim ); + + // ------------------ // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_label_new( "Orientation" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + // argh more radio buttons! + radioNS = gtk_radio_button_new_with_label( NULL, "North - South" ); + gtk_box_pack_start( GTK_BOX( hbox ), radioNS, FALSE, FALSE, 0 ); + gtk_widget_show( radioNS ); + + radioOrientation = gtk_radio_button_group( GTK_RADIO_BUTTON( radioNS ) ); + + radioEW = gtk_radio_button_new_with_label( radioOrientation, "East - West" ); + gtk_box_pack_start( GTK_BOX( hbox ), radioEW, FALSE, FALSE, 0 ); + gtk_widget_show( radioEW ); + + // ----------------- // + + w = gtk_hseparator_new(); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + // ----------------- // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_button_new_with_label( "OK" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( eIDOK ) ); + GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT ); + gtk_widget_grab_default( w ); + gtk_widget_show( w ); + + w = gtk_button_new_with_label( "Cancel" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 ); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( eIDCANCEL ) ); + gtk_widget_show( w ); + ret = eIDCANCEL; + + // ----------------- // + +//+djbob + gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_widget_show (window); + gtk_grab_add (window); + + while (loop) + gtk_main_iteration (); + + strcpy(rs->mainTexture, gtk_entry_get_text( GTK_ENTRY( textFrontBackTex ) )); + strcpy(rs->trimTexture, gtk_entry_get_text( GTK_ENTRY( textTrimTex ) )); + + rs->bScaleMainH = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkScaleMainH)) ? true : false; + rs->bScaleMainV = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkScaleMainV)) ? true : false; + rs->bScaleTrimH = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkScaleTrimH)) ? true : false; + rs->bScaleTrimV = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkScaleTrimV)) ? true : false; + + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radioNS))) + rs->nOrientation = DIRECTION_NS; + else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radioEW))) + rs->nOrientation = DIRECTION_EW; + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return ret; +//-djbob +} + +EMessageBoxReturn DoPathPlotterBox(PathPlotterRS* rs) +{ + GtkWidget *window, *w, *vbox, *hbox; + + GtkWidget *text1, *text2, *text3; + GtkWidget *check1, *check2; + + EMessageBoxReturn ret; + int loop = 1; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + + gtk_window_set_title (GTK_WINDOW (window), "Texture Reset"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + g_object_set_data (G_OBJECT (window), "loop", &loop); + g_object_set_data (G_OBJECT (window), "ret", &ret); + + gtk_widget_realize (window); + + + + vbox = gtk_vbox_new (FALSE, 10); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show (vbox); + + // ---- vbox ---- + + hbox = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); + gtk_widget_show (hbox); + + // ---- hbox ---- + + text1 = gtk_entry_new_with_max_length(256); + gtk_entry_set_text((GtkEntry*)text1, "25"); + gtk_box_pack_start (GTK_BOX (hbox), text1, FALSE, FALSE, 2); + gtk_widget_show (text1); + + w = gtk_label_new ("Number Of Points"); + gtk_box_pack_start (GTK_BOX (hbox), w, FALSE, FALSE, 2); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT); + gtk_widget_show (w); + + // ---- /hbox ---- + + hbox = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); + gtk_widget_show (hbox); + + // ---- hbox ---- + + text2 = gtk_entry_new_with_max_length(256); + gtk_entry_set_text((GtkEntry*)text2, "3"); + gtk_box_pack_start (GTK_BOX (hbox), text2, FALSE, FALSE, 2); + gtk_widget_show (text2); + + w = gtk_label_new ("Multipler"); + gtk_box_pack_start (GTK_BOX (hbox), w, FALSE, FALSE, 2); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT); + gtk_widget_show (w); + + // ---- /hbox ---- + + w = gtk_label_new ("Path Distance = dist(start -> apex) * multiplier"); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 0); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT); + gtk_widget_show (w); + + hbox = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); + gtk_widget_show (hbox); + + // ---- hbox ---- + + text3 = gtk_entry_new_with_max_length(256); + gtk_entry_set_text((GtkEntry*)text3, "-800"); + gtk_box_pack_start (GTK_BOX (hbox), text3, FALSE, FALSE, 2); + gtk_widget_show (text3); + + w = gtk_label_new ("Gravity"); + gtk_box_pack_start (GTK_BOX (hbox), w, FALSE, FALSE, 2); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT); + gtk_widget_show (w); + + // ---- /hbox ---- + + w = gtk_hseparator_new(); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + check1 = gtk_check_button_new_with_label( "No Dynamic Update" ); + gtk_box_pack_start( GTK_BOX( vbox ), check1, FALSE, FALSE, 0 ); + gtk_widget_show( check1 ); + + check2 = gtk_check_button_new_with_label( "Show Bounding Lines" ); + gtk_box_pack_start( GTK_BOX( vbox ), check2, FALSE, FALSE, 0 ); + gtk_widget_show( check2 ); + + // ---- /vbox ---- + + + // ----------------- // + + w = gtk_hseparator_new(); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + // ----------------- // + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_button_new_with_label( "Enable" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( eIDYES ) ); + gtk_widget_show( w ); + + GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT ); + gtk_widget_grab_default( w ); + + w = gtk_button_new_with_label( "Disable" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( eIDNO ) ); + gtk_widget_show( w ); + + w = gtk_button_new_with_label( "Cancel" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 ); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( dialog_button_callback ), GINT_TO_POINTER( eIDCANCEL ) ); + gtk_widget_show( w ); + + ret = eIDCANCEL; + + // ----------------- // + + gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_widget_show (window); + gtk_grab_add (window); + + bool dialogError = TRUE; + while (dialogError) + { + loop = 1; + while (loop) + gtk_main_iteration (); + + dialogError = FALSE; + + if(ret == eIDYES) + { + if(!ValidateTextIntRange(gtk_entry_get_text(GTK_ENTRY(text1)), 1, 200, "Number Of Points", &rs->nPoints)) + dialogError = TRUE; + + if(!ValidateTextFloatRange(gtk_entry_get_text(GTK_ENTRY(text2)), 1.0f, 10.0f, "Multiplier", &rs->fMultiplier)) + dialogError = TRUE; + + if(!ValidateTextFloatRange(gtk_entry_get_text(GTK_ENTRY(text3)), -10000.0f, -1.0f, "Gravity", &rs->fGravity)) + dialogError = TRUE; + + rs->bNoUpdate = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check1)) ? true : false; + rs->bShowExtra = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(check2)) ? true : false; + } + } + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return ret; +} + +EMessageBoxReturn DoCTFColourChangeBox () +{ + GtkWidget *window, *w, *vbox, *hbox; + EMessageBoxReturn ret; + int loop = 1; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + + gtk_window_set_title (GTK_WINDOW (window), "CTF Colour Changer"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + g_object_set_data (G_OBJECT (window), "loop", &loop); + g_object_set_data (G_OBJECT (window), "ret", &ret); + + gtk_widget_realize (window); + + + + vbox = gtk_vbox_new (FALSE, 10); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show (vbox); + + // ---- vbox ---- + + hbox = gtk_hbox_new( FALSE, 10 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, TRUE, TRUE, 0 ); + gtk_widget_show( hbox ); + + // ---- hbox ---- ok/cancel buttons + + w = gtk_button_new_with_label ("Red->Blue"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDOK)); + + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Blue->Red"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDYES)); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDCANCEL)); + gtk_widget_show (w); + ret = eIDCANCEL; + + // ---- /hbox ---- + + // ---- /vbox ---- + + gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_widget_show (window); + gtk_grab_add (window); + + while (loop) + gtk_main_iteration (); + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return ret; +} + +EMessageBoxReturn DoResetTextureBox (ResetTextureRS* rs) +{ + Str texSelected; + + GtkWidget *window, *w, *vbox, *hbox, *frame, *table; + + EMessageBoxReturn ret; + int loop = 1; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + + gtk_window_set_title (GTK_WINDOW (window), "Texture Reset"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + g_object_set_data (G_OBJECT (window), "loop", &loop); + g_object_set_data (G_OBJECT (window), "ret", &ret); + + gtk_widget_realize (window); + + vbox = gtk_vbox_new (FALSE, 10); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show (vbox); + + // ---- vbox ---- + + hbox = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); + gtk_widget_show (hbox); + + // ---- hbox ---- + + texSelected = "Currently Selected Texture: "; + texSelected += GetCurrentTexture(); + + w = gtk_label_new (texSelected); + gtk_box_pack_start (GTK_BOX (hbox), w, FALSE, FALSE, 2); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT); + gtk_widget_show (w); + + // ---- /hbox ---- + + frame = gtk_frame_new ("Reset Texture Names"); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0); + + table = gtk_table_new (2, 3, TRUE); + gtk_widget_show (table); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + + // ---- frame ---- + + dlgTexReset.cbTexChange = gtk_check_button_new_with_label("Enabled"); + gtk_signal_connect (GTK_OBJECT (dlgTexReset.cbTexChange), "toggled", GTK_SIGNAL_FUNC (dialog_button_callback_texreset_update), NULL); + gtk_widget_show (dlgTexReset.cbTexChange); + gtk_table_attach (GTK_TABLE (table), dlgTexReset.cbTexChange, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + w = gtk_label_new ("Old Name: "); + gtk_table_attach (GTK_TABLE (table), w, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + dlgTexReset.editTexOld = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(dlgTexReset.editTexOld), rs->textureName); + gtk_table_attach (GTK_TABLE (table), dlgTexReset.editTexOld, 2, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (dlgTexReset.editTexOld); + + w = gtk_label_new ("New Name: "); + gtk_table_attach (GTK_TABLE (table), w, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + dlgTexReset.editTexNew = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(dlgTexReset.editTexNew), rs->textureName); + gtk_table_attach (GTK_TABLE (table), dlgTexReset.editTexNew, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (dlgTexReset.editTexNew); + + // ---- /frame ---- + + frame = gtk_frame_new ("Reset Scales"); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0); + + table = gtk_table_new (2, 3, TRUE); + gtk_widget_show (table); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + + // ---- frame ---- + + dlgTexReset.cbScaleHor = gtk_check_button_new_with_label("Enabled"); + gtk_signal_connect (GTK_OBJECT (dlgTexReset.cbScaleHor), "toggled", GTK_SIGNAL_FUNC (dialog_button_callback_texreset_update), NULL); + gtk_widget_show (dlgTexReset.cbScaleHor); + gtk_table_attach (GTK_TABLE (table), dlgTexReset.cbScaleHor, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + w = gtk_label_new ("New Horizontal Scale: "); + gtk_table_attach (GTK_TABLE (table), w, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + dlgTexReset.editScaleHor = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(dlgTexReset.editScaleHor), "0.5"); + gtk_table_attach (GTK_TABLE (table), dlgTexReset.editScaleHor, 2, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (dlgTexReset.editScaleHor); + + + dlgTexReset.cbScaleVert = gtk_check_button_new_with_label("Enabled"); + gtk_signal_connect (GTK_OBJECT (dlgTexReset.cbScaleVert), "toggled", GTK_SIGNAL_FUNC (dialog_button_callback_texreset_update), NULL); + gtk_widget_show (dlgTexReset.cbScaleVert); + gtk_table_attach (GTK_TABLE (table), dlgTexReset.cbScaleVert, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + w = gtk_label_new ("New Vertical Scale: "); + gtk_table_attach (GTK_TABLE (table), w, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + dlgTexReset.editScaleVert = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(dlgTexReset.editScaleVert), "0.5"); + gtk_table_attach (GTK_TABLE (table), dlgTexReset.editScaleVert, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (dlgTexReset.editScaleVert); + + // ---- /frame ---- + + frame = gtk_frame_new ("Reset Shift"); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0); + + table = gtk_table_new (2, 3, TRUE); + gtk_widget_show (table); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + + // ---- frame ---- + + dlgTexReset.cbShiftHor = gtk_check_button_new_with_label("Enabled"); + gtk_signal_connect (GTK_OBJECT (dlgTexReset.cbShiftHor), "toggled", GTK_SIGNAL_FUNC (dialog_button_callback_texreset_update), NULL); + gtk_widget_show (dlgTexReset.cbShiftHor); + gtk_table_attach (GTK_TABLE (table), dlgTexReset.cbShiftHor, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + w = gtk_label_new ("New Horizontal Shift: "); + gtk_table_attach (GTK_TABLE (table), w, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + dlgTexReset.editShiftHor = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(dlgTexReset.editShiftHor), "0"); + gtk_table_attach (GTK_TABLE (table), dlgTexReset.editShiftHor, 2, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (dlgTexReset.editShiftHor); + + + dlgTexReset.cbShiftVert = gtk_check_button_new_with_label("Enabled"); + gtk_signal_connect (GTK_OBJECT (dlgTexReset.cbShiftVert), "toggled", GTK_SIGNAL_FUNC (dialog_button_callback_texreset_update), NULL); + gtk_widget_show (dlgTexReset.cbShiftVert); + gtk_table_attach (GTK_TABLE (table), dlgTexReset.cbShiftVert, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + w = gtk_label_new ("New Vertical Shift: "); + gtk_table_attach (GTK_TABLE (table), w, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + dlgTexReset.editShiftVert = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(dlgTexReset.editShiftVert), "0"); + gtk_table_attach (GTK_TABLE (table), dlgTexReset.editShiftVert, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (dlgTexReset.editShiftVert); + + // ---- /frame ---- + + frame = gtk_frame_new ("Reset Rotation"); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0); + + table = gtk_table_new (1, 3, TRUE); + gtk_widget_show (table); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + + // ---- frame ---- + + dlgTexReset.cbRotation = gtk_check_button_new_with_label("Enabled"); + gtk_widget_show (dlgTexReset.cbRotation); + gtk_table_attach (GTK_TABLE (table), dlgTexReset.cbRotation, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + w = gtk_label_new ("New Rotation Value: "); + gtk_table_attach (GTK_TABLE (table), w, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + dlgTexReset.editRotation = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(dlgTexReset.editRotation), "0"); + gtk_table_attach (GTK_TABLE (table), dlgTexReset.editRotation, 2, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (dlgTexReset.editRotation); + + // ---- /frame ---- + + hbox = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); + gtk_widget_show (hbox); + + // ---- hbox ---- + + w = gtk_button_new_with_label ("Use Selected Brushes"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDOK)); + + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Use All Brushes"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDYES)); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDCANCEL)); + gtk_widget_show (w); + ret = eIDCANCEL; + + // ---- /hbox ---- + + // ---- /vbox ---- + + gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_widget_show (window); + gtk_grab_add (window); + + Update_TextureReseter(); + + bool dialogError = TRUE; + while (dialogError) + { + loop = 1; + while (loop) + gtk_main_iteration (); + + dialogError = FALSE; + + if(ret != eIDCANCEL) + { + rs->bResetRotation = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( dlgTexReset.cbRotation )); + if(rs->bResetRotation) + if(!ValidateTextInt(gtk_entry_get_text(GTK_ENTRY(dlgTexReset.editRotation)), "Rotation", &rs->rotation)) + dialogError = TRUE; + + rs->bResetScale[0] = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( dlgTexReset.cbScaleHor )); + if(rs->bResetScale[0]) + if(!ValidateTextFloat(gtk_entry_get_text(GTK_ENTRY(dlgTexReset.editScaleHor)), "Horizontal Scale", &rs->fScale[0])) + dialogError = TRUE; + + rs->bResetScale[1] = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( dlgTexReset.cbScaleVert )); + if(rs->bResetScale[1]) + if(!ValidateTextFloat(gtk_entry_get_text(GTK_ENTRY(dlgTexReset.editScaleVert)), "Vertical Scale", &rs->fScale[1])) + dialogError = TRUE; + + rs->bResetShift[0] = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( dlgTexReset.cbShiftHor )); + if(rs->bResetShift[0]) + if(!ValidateTextFloat(gtk_entry_get_text(GTK_ENTRY(dlgTexReset.editShiftHor)), "Horizontal Shift", &rs->fShift[0])) + dialogError = TRUE; + + rs->bResetShift[1] = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( dlgTexReset.cbShiftVert )); + if(rs->bResetShift[1]) + if(!ValidateTextFloat(gtk_entry_get_text(GTK_ENTRY(dlgTexReset.editShiftVert)), "Vertical Shift", &rs->fShift[1])) + dialogError = TRUE; + + rs->bResetTextureName = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( dlgTexReset.cbTexChange )); + if(rs->bResetTextureName) + { + strcpy(rs->textureName, gtk_entry_get_text(GTK_ENTRY( dlgTexReset.editTexOld ))); + strcpy(rs->newTextureName, gtk_entry_get_text(GTK_ENTRY( dlgTexReset.editTexNew ))); + } + } + } + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return ret; +} + +EMessageBoxReturn DoTrainThingBox (TrainThingRS* rs) +{ + Str texSelected; + + GtkWidget *window, *w, *vbox, *hbox, *frame, *table; + + GtkWidget *radiusX, *radiusY; + GtkWidget *angleStart, *angleEnd; + GtkWidget *heightStart, *heightEnd; + GtkWidget *numPoints; + + EMessageBoxReturn ret; + int loop = 1; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + + gtk_window_set_title (GTK_WINDOW (window), "Train Thing"); + gtk_container_border_width (GTK_CONTAINER (window), 10); + + gtk_object_set_data (GTK_OBJECT (window), "loop", &loop); + gtk_object_set_data (GTK_OBJECT (window), "ret", &ret); + + gtk_widget_realize (window); + + vbox = gtk_vbox_new (FALSE, 10); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show (vbox); + + // ---- vbox ---- + + hbox = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); + gtk_widget_show (hbox); + + // ---- /hbox ---- + + frame = gtk_frame_new ("Radii"); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0); + + table = gtk_table_new (2, 3, TRUE); + gtk_widget_show (table); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + + // ---- frame ---- + + w = gtk_label_new ("X: "); + gtk_table_attach (GTK_TABLE (table), w, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + radiusX = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(radiusX), "100"); + gtk_table_attach (GTK_TABLE (table), radiusX, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (radiusX); + + + + w = gtk_label_new ("Y: "); + gtk_table_attach (GTK_TABLE (table), w, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + radiusY = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(radiusY), "100"); + gtk_table_attach (GTK_TABLE (table), radiusY, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (radiusY); + + + + frame = gtk_frame_new ("Angles"); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0); + + table = gtk_table_new (2, 3, TRUE); + gtk_widget_show (table); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + + // ---- frame ---- + + w = gtk_label_new ("Start: "); + gtk_table_attach (GTK_TABLE (table), w, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + angleStart = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(angleStart), "0"); + gtk_table_attach (GTK_TABLE (table), angleStart, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (angleStart); + + + + w = gtk_label_new ("End: "); + gtk_table_attach (GTK_TABLE (table), w, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + angleEnd = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(angleEnd), "90"); + gtk_table_attach (GTK_TABLE (table), angleEnd, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (angleEnd); + + + frame = gtk_frame_new ("Height"); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0); + + table = gtk_table_new (2, 3, TRUE); + gtk_widget_show (table); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + + // ---- frame ---- + + w = gtk_label_new ("Start: "); + gtk_table_attach (GTK_TABLE (table), w, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + heightStart = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(heightStart), "0"); + gtk_table_attach (GTK_TABLE (table), heightStart, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (heightStart); + + + + w = gtk_label_new ("End: "); + gtk_table_attach (GTK_TABLE (table), w, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + heightEnd = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(heightEnd), "0"); + gtk_table_attach (GTK_TABLE (table), heightEnd, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (heightEnd); + + + + frame = gtk_frame_new ("Points"); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0); + + table = gtk_table_new (2, 3, TRUE); + gtk_widget_show (table); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + + // ---- frame ---- + + w = gtk_label_new ("Number: "); + gtk_table_attach (GTK_TABLE (table), w, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (w); + + numPoints = gtk_entry_new_with_max_length(256); + gtk_entry_set_text(GTK_ENTRY(numPoints), "0"); + gtk_table_attach (GTK_TABLE (table), numPoints, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (numPoints); + + + hbox = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); + gtk_widget_show (hbox); + + // ---- hbox ---- + + w = gtk_button_new_with_label ("Ok"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDOK)); + + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDCANCEL)); + gtk_widget_show (w); + ret = eIDCANCEL; + + // ---- /hbox ---- + + + + gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_widget_show (window); + gtk_grab_add (window); + + bool dialogError = TRUE; + while (dialogError) + { + loop = 1; + while (loop) + gtk_main_iteration (); + + dialogError = FALSE; + + if(ret != eIDCANCEL) + { + if(!ValidateTextFloat(gtk_entry_get_text(GTK_ENTRY(radiusX)), "Radius (X)", &rs->fRadiusX)) + dialogError = TRUE; + + if(!ValidateTextFloat(gtk_entry_get_text(GTK_ENTRY(radiusY)), "Radius (Y)", &rs->fRadiusY)) + dialogError = TRUE; + + if(!ValidateTextFloat(gtk_entry_get_text(GTK_ENTRY(angleStart)), "Angle (Start)", &rs->fStartAngle)) + dialogError = TRUE; + + if(!ValidateTextFloat(gtk_entry_get_text(GTK_ENTRY(angleEnd)), "Angle (End)", &rs->fEndAngle)) + dialogError = TRUE; + + if(!ValidateTextFloat(gtk_entry_get_text(GTK_ENTRY(heightStart)), "Height (Start)", &rs->fStartHeight)) + dialogError = TRUE; + + if(!ValidateTextFloat(gtk_entry_get_text(GTK_ENTRY(heightEnd)), "Height (End)", &rs->fEndHeight)) + dialogError = TRUE; + + if(!ValidateTextInt(gtk_entry_get_text(GTK_ENTRY(numPoints)), "Num Points", &rs->iNumPoints)) + dialogError = TRUE; + } + } + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return ret; +} diff --git a/contrib/bobtoolz/dialogs/dialogs-gtk.h b/contrib/bobtoolz/dialogs/dialogs-gtk.h new file mode 100644 index 00000000..bcafdae8 --- /dev/null +++ b/contrib/bobtoolz/dialogs/dialogs-gtk.h @@ -0,0 +1,107 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(INCLUDED_DIALOGS_GTK_H) +#define INCLUDED_DIALOGS_GTK_H + +#include "qerplugin.h" + +struct BuildStairsRS{ + char mainTexture[256]; + char riserTexture[256]; + int direction; + int style; + int stairHeight; + bool bUseDetail; +}; + +struct ResetTextureRS { + int bResetTextureName; + char textureName[256]; + char newTextureName[256]; + + int bResetScale[2]; + float fScale[2]; + + int bResetShift[2]; + float fShift[2]; + + int bResetRotation; + int rotation; +}; + +struct TrainThingRS { + float fRadiusX, fRadiusY; + float fStartAngle, fEndAngle; + int iNumPoints; + float fStartHeight, fEndHeight; +}; + +struct IntersectRS{ + int nBrushOptions; + bool bUseDetail; + bool bDuplicateOnly; +}; + +struct PolygonRS{ + bool bUseBorder; + bool bInverse; + bool bAlignTop; + int nSides; + int nBorderWidth; +}; + +struct DoorRS{ + char mainTexture[256]; + char trimTexture[256]; + bool bScaleMainH; + bool bScaleMainV; + bool bScaleTrimH; + bool bScaleTrimV; + int nOrientation; +}; + +struct PathPlotterRS{ + int nPoints; + float fMultiplier; + float fGravity; + bool bNoUpdate; + bool bShowExtra; +}; + +typedef struct _GtkWidget GtkWidget; + +struct TwinWidget{ + GtkWidget* one; + GtkWidget* two; +}; + +EMessageBoxReturn DoMessageBox(const char* lpText, const char* lpCaption, EMessageBoxType type); +EMessageBoxReturn DoIntersectBox(IntersectRS* rs); +EMessageBoxReturn DoPolygonBox(PolygonRS* rs); +EMessageBoxReturn DoResetTextureBox (ResetTextureRS* rs); +EMessageBoxReturn DoBuildStairsBox(BuildStairsRS* rs); +EMessageBoxReturn DoDoorsBox(DoorRS* rs); +EMessageBoxReturn DoPathPlotterBox(PathPlotterRS* rs); +EMessageBoxReturn DoCTFColourChangeBox(); +EMessageBoxReturn DoTrainThingBox (TrainThingRS* rs); + +//GtkWidget* GetProgressWindow(char* title, GtkProgressBar* feedback); + +#endif diff --git a/contrib/bobtoolz/dialogs/pathplotterdialog.cpp b/contrib/bobtoolz/dialogs/pathplotterdialog.cpp new file mode 100644 index 00000000..ae638529 --- /dev/null +++ b/contrib/bobtoolz/dialogs/pathplotterdialog.cpp @@ -0,0 +1,85 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// PathPlotterDialog.cpp : implementation file +// + +#include "../StdAfx.h" +#include "../bobtoolz.h" +#include "PathPlotterDialog.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CPathPlotterDialog dialog + + +CPathPlotterDialog::CPathPlotterDialog(CWnd* pParent /*=NULL*/) + : CDialog(CPathPlotterDialog::IDD, pParent) +{ + //{{AFX_DATA_INIT(CPathPlotterDialog) + m_fGravity = -800.0f; + m_fMultiplier = 3.0f; + m_bNoUpdate = FALSE; + m_nPoints = 25; + m_bShowExtra = FALSE; + //}}AFX_DATA_INIT +} + + +void CPathPlotterDialog::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CPathPlotterDialog) + DDX_Text(pDX, IDC_GRAVITY_EDIT, m_fGravity); + DDV_MinMaxFloat(pDX, m_fGravity, -10000.f, -1.f); + DDX_Text(pDX, IDC_MULTIPLIER_EDIT, m_fMultiplier); + DDV_MinMaxFloat(pDX, m_fMultiplier, 1.f, 10.f); + DDX_Check(pDX, IDC_NOUPDATE_CHECK, m_bNoUpdate); + DDX_Text(pDX, IDC_POINTCOUNT_EDIT, m_nPoints); + DDV_MinMaxInt(pDX, m_nPoints, 1, 1000); + DDX_Check(pDX, IDC_SHOWEXTRA_CHECK, m_bShowExtra); + //}}AFX_DATA_MAP +} + + +BEGIN_MESSAGE_MAP(CPathPlotterDialog, CDialog) + //{{AFX_MSG_MAP(CPathPlotterDialog) + ON_BN_CLICKED(IDYES, OnYes) + ON_BN_CLICKED(IDNO, OnNo) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CPathPlotterDialog message handlers + +void CPathPlotterDialog::OnYes() +{ + if(UpdateData()) + EndModalLoop(IDYES); +} + +void CPathPlotterDialog::OnNo() +{ + EndModalLoop(IDNO); +} diff --git a/contrib/bobtoolz/dialogs/pathplotterdialog.h b/contrib/bobtoolz/dialogs/pathplotterdialog.h new file mode 100644 index 00000000..6edd8987 --- /dev/null +++ b/contrib/bobtoolz/dialogs/pathplotterdialog.h @@ -0,0 +1,70 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(AFX_PATHPLOTTERDIALOG_H__A0516221_F19B_11D4_ACF7_004095A18133__INCLUDED_) +#define AFX_PATHPLOTTERDIALOG_H__A0516221_F19B_11D4_ACF7_004095A18133__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 +// PathPlotterDialog.h : header file +// + +///////////////////////////////////////////////////////////////////////////// +// CPathPlotterDialog dialog + +class CPathPlotterDialog : public CDialog +{ +// Construction +public: + CPathPlotterDialog(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CPathPlotterDialog) + enum { IDD = IDD_PATHPLOTTER_DIALOG }; + float m_fGravity; + float m_fMultiplier; + BOOL m_bNoUpdate; + int m_nPoints; + BOOL m_bShowExtra; + //}}AFX_DATA + + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CPathPlotterDialog) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + + // Generated message map functions + //{{AFX_MSG(CPathPlotterDialog) + afx_msg void OnYes(); + afx_msg void OnNo(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_PATHPLOTTERDIALOG_H__A0516221_F19B_11D4_ACF7_004095A18133__INCLUDED_) diff --git a/contrib/bobtoolz/funchandlers-GTK.cpp b/contrib/bobtoolz/funchandlers-GTK.cpp new file mode 100644 index 00000000..5d1f16a9 --- /dev/null +++ b/contrib/bobtoolz/funchandlers-GTK.cpp @@ -0,0 +1,770 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "funchandlers.h" + +#ifdef WIN32 +#pragma warning(disable : 4786) +#endif + +#include "dialogs/dialogs-gtk.h" + +#include +#include "str.h" + +#include "DPoint.h" +#include "DPlane.h" +#include "DBrush.h" +#include "DEPair.h" +#include "DPatch.h" +#include "DEntity.h" +#include "DShape.h" +#include "DBobView.h" +#include "DVisDrawer.h" +#include "DTrainDrawer.h" + +#include "misc.h" +#include "ScriptParser.h" +#include "DTreePlanter.h" + +#include "shapes.h" +#include "lists.h" +#include "visfind.h" + +#include "iundo.h" + +#include +#include +#include +#include + +#include "scenelib.h" + +// for autocaulk +std::list exclusionList; // whole brush exclusion +std::list exclusionList_Face; // single face exclusion + +bool el1Loaded = false; +bool el2Loaded = false; +bool clrLst1Loaded = false; +bool clrLst2Loaded = false; + +DBobView* g_PathView = NULL; +DVisDrawer* g_VisView = NULL; +DTrainDrawer* g_TrainView = NULL; +DTreePlanter* g_TreePlanter = NULL; +// ------------- + +//========================// +// Helper Functions // +//========================// + +void LoadLists() +{ + char buffer[256]; + + if(!el1Loaded) + el1Loaded = LoadExclusionList(GetFilename(buffer, "bt/bt-el1.txt"), &exclusionList); + if(!el2Loaded) + el2Loaded = LoadExclusionList(GetFilename(buffer, "bt/bt-el2.txt"), &exclusionList_Face); +} + + +//========================// +// Main Functions // +//========================// + +void DoIntersect() +{ + UndoableCommand undo("bobToolz.intersect"); + IntersectRS rs; + + if(DoIntersectBox(&rs) == eIDCANCEL) + return; + + if(rs.nBrushOptions == BRUSH_OPT_SELECTED) + { + if( GlobalSelectionSystem().countSelected() < 2 ) + { + DoMessageBox("Invalid number of brushes selected, choose at least 2", "Error", eMB_OK); + return; + } + } + + DEntity world; + + switch(rs.nBrushOptions) + { + case BRUSH_OPT_SELECTED: + { + world.LoadSelectedBrushes(); + break; + } + case BRUSH_OPT_WHOLE_MAP: + { + world.LoadFromEntity(GlobalRadiant().getMapWorldEntity(), false); + break; + } + } + + world.RemoveNonCheckBrushes(&exclusionList, rs.bUseDetail); + + bool* pbSelectList; + if(rs.bDuplicateOnly) + pbSelectList = world.BuildDuplicateList(); + else + pbSelectList = world.BuildIntersectList(); + + world.SelectBrushes(pbSelectList); + + delete[] pbSelectList; +} + +void DoPolygonsTB() +{ + DoPolygons(); +} + +void DoPolygons() +{ + UndoableCommand undo("bobToolz.polygons"); + // ensure we have something selected + if( GlobalSelectionSystem().countSelected() != 1 ) + { + DoMessageBox("Invalid number of brushes selected, choose 1 only", "Error", eMB_OK); + return; + } + + PolygonRS rs; + + // ask user for type, size, etc.... + if(DoPolygonBox(&rs) == eIDOK) + { + DShape poly; + + vec3_t vMin, vMax; + + { + scene::Instance& instance = GlobalSelectionSystem().ultimateSelected(); + VectorSubtract(instance.worldAABB().origin, instance.worldAABB().extents, vMin); + VectorAdd(instance.worldAABB().origin, instance.worldAABB().extents, vMax); + + Path_deleteTop(instance.path()); + } + + if(rs.bInverse) + poly.BuildInversePrism(vMin, vMax, rs.nSides, rs.bAlignTop); + else + { + if(rs.bUseBorder) + poly.BuildBorderedPrism(vMin, vMax, rs.nSides, rs.nBorderWidth, rs.bAlignTop); + else + poly.BuildRegularPrism(vMin, vMax, rs.nSides, rs.bAlignTop); + + } + + poly.Commit(); + } +} + +void DoFixBrushes() +{ + UndoableCommand undo("bobToolz.fixBrushes"); + DMap world; + world.LoadAll(); + + int count = world.FixBrushes(); + + globalOutputStream() << count << " invalid/duplicate planes removed\n"; +} + +void DoResetTextures() +{ + UndoableCommand undo("bobToolz.resetTextures"); + static ResetTextureRS rs; + + const char* texName; + if(1/*g_SelectedFaceTable.m_pfnGetSelectedFaceCount() != 1*/) + { + texName = NULL; + } + else + { + texName = GetCurrentTexture(); + strcpy(rs.textureName, GetCurrentTexture()); + } + + EMessageBoxReturn ret; + if((ret = DoResetTextureBox(&rs)) == eIDCANCEL) + return; + + if(rs.bResetTextureName) + texName = rs.textureName; + + if(ret == eIDOK) + { + DEntity world; + world.LoadSelectedBrushes(); + world.ResetTextures(texName, rs.fScale, rs.fShift, rs.rotation, rs.newTextureName, + rs.bResetTextureName, rs.bResetScale, rs.bResetShift, rs.bResetRotation, true); + } + else + { + DMap world; + world.LoadAll(true); + world.ResetTextures(texName, rs.fScale, rs.fShift, rs.rotation, rs.newTextureName, + rs.bResetTextureName, rs.bResetScale, rs.bResetShift, rs.bResetRotation); + } +} + +void DoBuildStairs() +{ + UndoableCommand undo("bobToolz.buildStairs"); + BuildStairsRS rs; + + strcpy(rs.mainTexture, GetCurrentTexture()); + + // ensure we have something selected + if( GlobalSelectionSystem().countSelected() != 1 ) + { + DoMessageBox("Invalid number of brushes selected, choose 1 only", "Error", eMB_OK); + return; + } + + // ask user for type, size, etc.... + if(DoBuildStairsBox(&rs) == eIDOK) + { + vec3_t vMin, vMax; + + { + scene::Instance& instance = GlobalSelectionSystem().ultimateSelected(); + VectorSubtract(instance.worldAABB().origin, instance.worldAABB().extents, vMin); + VectorAdd(instance.worldAABB().origin, instance.worldAABB().extents, vMax); + } + + // calc brush size + vec3_t size; + VectorSubtract(vMax, vMin, size); + + if(((int)size[2] % rs.stairHeight) != 0) + { + // stairs must fit evenly into brush + DoMessageBox("Invalid stair height\nHeight of block must be divisable by stair height", "Error", eMB_OK); + } + else + { + { + scene::Instance& instance = GlobalSelectionSystem().ultimateSelected(); + Path_deleteTop(instance.path()); + } + + // Get Step Count + int numSteps = (int)size[2] / rs.stairHeight; + + if(rs.style == STYLE_CORNER) + { + BuildCornerStairs(vMin, vMax, numSteps, rs.mainTexture, rs.riserTexture); + } + else + { + + // Get Step Dimensions + float stairHeight = (float)rs.stairHeight; + float stairWidth; + if((rs.direction == MOVE_EAST) || (rs.direction == MOVE_WEST)) + stairWidth = (size[0])/numSteps; + else + stairWidth = (size[1])/numSteps; + + + // Build Base For Stair (bob's style) + if(rs.style == STYLE_BOB) + Build_Wedge(rs.direction, vMin, vMax, true); + + + // Set First Step Starting Position + vMax[2] = vMin[2] + stairHeight; + SetInitialStairPos(rs.direction, vMin, vMax, stairWidth); + + + // Build The Steps + for(int i = 0; i < numSteps; i++) + { + if(rs.style == STYLE_BOB) + Build_StairStep_Wedge(rs.direction, vMin, vMax, rs.mainTexture, rs.riserTexture, rs.bUseDetail); + else if(rs.style == STYLE_ORIGINAL) + Build_StairStep(vMin, vMax, rs.mainTexture, rs.riserTexture, rs.direction); + + // get step into next position + MoveBlock(rs.direction, vMin, vMax, stairWidth); + vMax[2] += stairHeight; + if(rs.style == STYLE_BOB) + vMin[2] += stairHeight; // wedge bottom must be raised + } + } + } + } +} + +void DoBuildDoors() +{ + UndoableCommand undo("bobToolz.buildDoors"); + // ensure we have something selected + if( GlobalSelectionSystem().countSelected() != 1 ) + { + DoMessageBox("Invalid number of brushes selected, choose 1 only", "Error", eMB_OK); + return; + } + + DoorRS rs; + strcpy(rs.mainTexture, GetCurrentTexture()); + + if(DoDoorsBox(&rs) == eIDOK) + { + vec3_t vMin, vMax; + + { + scene::Instance& instance = GlobalSelectionSystem().ultimateSelected(); + VectorSubtract(instance.worldAABB().origin, instance.worldAABB().extents, vMin); + VectorAdd(instance.worldAABB().origin, instance.worldAABB().extents, vMax); + Path_deleteTop(instance.path()); + } + + BuildDoorsX2(vMin, vMax, + rs.bScaleMainH, rs.bScaleMainV, + rs.bScaleTrimH, rs.bScaleTrimV, + rs.mainTexture, rs.trimTexture, + rs.nOrientation); // shapes.cpp + } +} + +void DoPathPlotter() +{ + UndoableCommand undo("bobToolz.pathPlotter"); + PathPlotterRS rs; + EMessageBoxReturn ret = DoPathPlotterBox(&rs); + if(ret == eIDCANCEL) + return; + if(ret == eIDNO) + { + if(g_PathView) + delete g_PathView; + return; + } + + // ensure we have something selected + if( GlobalSelectionSystem().countSelected() != 1 ) + { + DoMessageBox("Invalid number of brushes selected, choose 1 only", "Error", eMB_OK); + return; + } + + Entity* entity = Node_getEntity(GlobalSelectionSystem().ultimateSelected().path().top()); + if(entity != 0) + { + DBobView_setEntity(*entity, rs.fMultiplier, rs.nPoints, rs.fGravity, rs.bNoUpdate, rs.bShowExtra); + } +} + +void DoPitBuilder() +{ + UndoableCommand undo("bobToolz.pitBuilder"); + // ensure we have something selected + if( GlobalSelectionSystem().countSelected() != 1 ) + { + DoMessageBox("Invalid number of brushes selected, choose 1 only", "Error", eMB_OK); + return; + } + + vec3_t vMin, vMax; + + scene::Instance& instance = GlobalSelectionSystem().ultimateSelected(); + VectorSubtract(instance.worldAABB().origin, instance.worldAABB().extents, vMin); + VectorAdd(instance.worldAABB().origin, instance.worldAABB().extents, vMax); + + DShape pit; + + if(pit.BuildPit(vMin, vMax)) + { + pit.Commit(); + + Path_deleteTop(instance.path()); + } + else + DoMessageBox("Failed To Make Pit\nTry Making The Brush Bigger", "Error", eMB_OK); +} + +void DoMergePatches() +{ + UndoableCommand undo("bobToolz.mergePatch"); + patch_merge_t merge_info; + DPatch mrgPatches[2]; + int i; + + // ensure we have something selected + if( GlobalSelectionSystem().countSelected() != 2 ) + { + DoMessageBox("Invalid number of patches selected, choose 2 only", "Error", eMB_OK); + return; + } + + scene::Instance* patches[2]; + patches[0] = &GlobalSelectionSystem().ultimateSelected(); + patches[1] = &GlobalSelectionSystem().penultimateSelected(); + + for (i = 0; i < 2; i++) + { + if (!Node_isPatch(patches[i]->path().top())) + { + DoMessageBox("You must select ONLY patches", "Error", eMB_OK); + return; + } + + mrgPatches[i].LoadFromPatch(*patches[i]); + } + + /* mrgPatches[0].Transpose(); + mrgPatches[0].RemoveFromRadiant(); + mrgPatches[0].BuildInRadiant();*/ + + merge_info = mrgPatches[0].IsMergable(&mrgPatches[1]); + + if (merge_info.mergable) + { + globalOutputStream() << merge_info.pos1 << " " << merge_info.pos2; + + globalOutputStream() << "Patches Mergable\n"; + DPatch* newPatch = mrgPatches[0].MergePatches(merge_info, &mrgPatches[0], &mrgPatches[1]); + + /* mrgPatches[0].RemoveFromRadiant(); + mrgPatches[0].BuildInRadiant(); + + mrgPatches[1].RemoveFromRadiant(); + mrgPatches[1].BuildInRadiant(); + + + delete newPatch;*/ + + if (!newPatch) + { + } else + { + Path_deleteTop(patches[0]->path()); + Path_deleteTop(patches[1]->path()); + + newPatch->BuildInRadiant(); + delete newPatch; + } + } + else + { + globalOutputStream() << "bobToolz.mergePatch: the selected patches are not mergable\n"; + } +} + +void DoSplitPatch() { + UndoableCommand undo("bobToolz.splitPatch"); + + DPatch patch; + + // ensure we have something selected + if( GlobalSelectionSystem().countSelected() != 1 ) + { + DoMessageBox("Invalid number of patches selected, choose 1 only", "Error", eMB_OK); + return; + } + + scene::Instance& instance = GlobalSelectionSystem().ultimateSelected(); + + if( !Node_isPatch(instance.path().top()) ) { + DoMessageBox("You must select ONLY patches", "Error", eMB_OK); + return; + } + + patch.LoadFromPatch(instance); + + std::list patchList = patch.Split( true, true ); + for(std::list::iterator patches = patchList.begin(); patches != patchList.end(); patches++) { + (*patches).BuildInRadiant(); + } + + Path_deleteTop(instance.path()); +} + +void DoVisAnalyse() +{ + char filename[1024]; + + if( GlobalSelectionSystem().countSelected() == 0 ) + { + if(g_VisView) + { + delete g_VisView; + return; + } + } + + // ensure we have something selected + if( GlobalSelectionSystem().countSelected() != 1 ) + { + DoMessageBox("Invalid number of objects selected, choose 1 only", "Error", eMB_OK); + return; + } + + scene::Instance& brush = GlobalSelectionSystem().ultimateSelected(); + + DBrush orgBrush; + orgBrush.LoadFromBrush(brush, false); + + orgBrush.BuildBounds(); + vec3_t origin; + origin[0] = (orgBrush.bbox_max[0] + orgBrush.bbox_min[0])/2.f; + origin[1] = (orgBrush.bbox_max[1] + orgBrush.bbox_min[1])/2.f; + origin[2] = (orgBrush.bbox_max[2] + orgBrush.bbox_min[2])/2.f; + + + const char* rad_filename = GlobalRadiant().getMapName(); + if(!rad_filename) + { + DoMessageBox("An Error Occurred While Trying\n To Get The Map Filename", "Error", eMB_OK); + return; + } + + strcpy(filename, rad_filename); + + char* ext = strrchr(filename, '.')+1; + strcpy(ext, "bsp");// rename the extension + + std::list *pointList = BuildTrace(filename, origin); + + if(!g_VisView) + { + g_VisView = new DVisDrawer; + } + + g_VisView->SetList(pointList); +} + +void DoTrainPathPlot() { + if(g_TrainView) { + delete g_TrainView; + g_TrainView = NULL; + } + + g_TrainView = new DTrainDrawer(); +} + +void DoCaulkSelection() { + UndoableCommand undo("bobToolz.caulkSelection"); + DEntity world; + + float fScale[2] = { 0.5f, 0.5f }; + float fShift[2] = { 0.0f, 0.0f }; + + int bResetScale[2] = { false, false }; + int bResetShift[2] = { false, false }; + + world.LoadSelectedBrushes(); + world.LoadSelectedPatches(); + world.ResetTextures( NULL, fScale, fShift, 0, "textures/common/caulk", true, bResetScale, bResetShift, false, true ); +} + +void DoTreePlanter() { + UndoableCommand undo("bobToolz.treePlanter"); + if(g_TreePlanter) { + delete g_TreePlanter; + g_TreePlanter = NULL; + return; + } + + g_TreePlanter = new DTreePlanter(); +} + +void DoDropEnts() { + UndoableCommand undo("bobToolz.dropEntities"); + if(g_TreePlanter) { + g_TreePlanter->DropEntsToGround(); + } +} + +void DoMakeChain() { + UndoableCommand undo("bobToolz.makeChain"); + DTreePlanter pl; + pl.MakeChain(); +} + +typedef DPoint* pntTripple[3]; + +bool bFacesNoTop[6] = {true, true, true, true, true, false}; + +void DoFlipTerrain() { + UndoableCommand undo("bobToolz.flipTerrain"); + vec3_t vUp = { 0.f, 0.f, 1.f }; + int i; + + // ensure we have something selected + if( GlobalSelectionSystem().countSelected() != 2 ) + { + DoMessageBox("Invalid number of objects selected, choose 2 only", "Error", eMB_OK); + return; + } + + scene::Instance* brushes[2]; + brushes[0] = &GlobalSelectionSystem().ultimateSelected(); + brushes[1] = &GlobalSelectionSystem().penultimateSelected(); + + DBrush Brushes[2]; + DPlane* Planes[2]; + pntTripple Points[2]; + for( i = 0; i < 2; i++ ) { + Brushes[i].LoadFromBrush( *brushes[i], false ); + if(!(Planes[i] = Brushes[i].FindPlaneWithClosestNormal( vUp )) || Brushes[i].FindPointsForPlane( Planes[i], Points[i], 3 ) != 3) { + DoMessageBox("Error", "Error", eMB_OK); + return; + } + } + + vec3_t mins1, mins2, maxs1, maxs2; + Brushes[0].GetBounds( mins1, maxs1 ); + Brushes[1].GetBounds( mins2, maxs2 ); + + + + int dontmatch[2] = { -1, -1 }; + bool found = false; + for( i = 0; i < 3; i++ ) { + for( int j = 0; j < 3 && !found; j++ ) { + if(VectorCompare( (Points[0])[i]->_pnt, (Points[1])[j]->_pnt )) { + found = true; + break; + } + } + if(!found) { + dontmatch[0] = i; + break; + } + found = false; + } + if(dontmatch[0] == -1) { + DoMessageBox("Error", "Error", eMB_OK); + return; + } + + for( i = 0; i < 3; i++ ) { + for( int j = 0; j < 3 && !found; j++ ) { + if(VectorCompare( (Points[1])[i]->_pnt, (Points[0])[j]->_pnt )) { + found = true; + break; + } + } + if(!found) { + dontmatch[1] = i; + break; + } + found = false; + } + if(dontmatch[1] == -1) { + DoMessageBox("Error", "Error", eMB_OK); + return; + } + + vec3_t plnpnts1[3]; + vec3_t plnpnts2[3]; + vec3_t plnpntsshr[3]; + + VectorCopy( (Points[0])[dontmatch[0]]->_pnt, plnpnts1[0] ); + for( i = 0; i < 3; i++ ) { + if( dontmatch[0] != i ) { + VectorCopy( (Points[0])[i]->_pnt, plnpnts1[1] ); + break; + } + } + VectorCopy( (Points[1])[dontmatch[1]]->_pnt, plnpnts1[2] ); + + VectorCopy( (Points[1])[dontmatch[1]]->_pnt, plnpnts2[0] ); + for( i = 0; i < 3; i++ ) { + if( dontmatch[1] != i && !VectorCompare( (Points[1])[i]->_pnt, plnpnts1[1] )) { + VectorCopy( (Points[1])[i]->_pnt, plnpnts2[1] ); + break; + } + } + VectorCopy( (Points[0])[dontmatch[0]]->_pnt, plnpnts2[2] ); + + VectorCopy( (Points[0])[dontmatch[0]]->_pnt, plnpntsshr[0] ); + VectorCopy( (Points[1])[dontmatch[1]]->_pnt, plnpntsshr[1] ); + if( (Points[1])[dontmatch[1]]->_pnt[2] < (Points[0])[dontmatch[0]]->_pnt[2] ) { + VectorCopy( (Points[1])[dontmatch[1]]->_pnt, plnpntsshr[2] ); + } else { + VectorCopy( (Points[0])[dontmatch[0]]->_pnt, plnpntsshr[2] ); + } + plnpntsshr[2][2] -= 16; + + for( i = 0; i < 3; i++ ) { + if( mins2[i] < mins1[i] ) { + mins1[i] = mins2[i]; + } + if( maxs2[i] > maxs1[i] ) { + maxs1[i] = maxs2[i]; + } + } + + DBrush* newBrushes[2]; + newBrushes[0] = DShape::GetBoundingCube_Ext( mins1, maxs1, "textures/common/caulk", bFacesAll, true); + newBrushes[1] = DShape::GetBoundingCube_Ext( mins1, maxs1, "textures/common/caulk", bFacesAll, true); + + vec3_t normal; + MakeNormal( plnpnts1[0], plnpnts1[1], plnpnts1[2], normal ); + if( normal[2] >= 0 ) { + newBrushes[0]->AddFace( plnpnts1[0], plnpnts1[1], plnpnts1[2], "textures/common/terrain", true ); + } else { + newBrushes[0]->AddFace( plnpnts1[2], plnpnts1[1], plnpnts1[0], "textures/common/terrain", true ); + } + + MakeNormal( plnpnts2[0], plnpnts2[1], plnpnts2[2], normal ); + if( normal[2] >= 0 ) { + newBrushes[1]->AddFace( plnpnts2[0], plnpnts2[1], plnpnts2[2], "textures/common/terrain", true ); + } else { + newBrushes[1]->AddFace( plnpnts2[2], plnpnts2[1], plnpnts2[0], "textures/common/terrain", true ); + } + + vec3_t vec; + MakeNormal( plnpntsshr[0], plnpntsshr[1], plnpntsshr[2], normal ); + + VectorSubtract( plnpnts1[2], plnpnts1[1], vec ); + if( DotProduct( vec, normal ) >= 0 ) { + newBrushes[0]->AddFace( plnpntsshr[0], plnpntsshr[1], plnpntsshr[2], "textures/common/caulk", true ); + } else { + newBrushes[0]->AddFace( plnpntsshr[2], plnpntsshr[1], plnpntsshr[0], "textures/common/caulk", true ); + } + + VectorSubtract( plnpnts2[2], plnpnts2[1], vec ); + if( DotProduct( vec, normal ) >= 0 ) { + newBrushes[1]->AddFace( plnpntsshr[0], plnpntsshr[1], plnpntsshr[2], "textures/common/caulk", true ); + } else { + newBrushes[1]->AddFace( plnpntsshr[2], plnpntsshr[1], plnpntsshr[0], "textures/common/caulk", true ); + } + + for( i = 0; i < 2; i++ ) { + newBrushes[i]->RemoveRedundantPlanes(); + newBrushes[i]->BuildInRadiant( false, NULL, brushes[i]->path().parent().get_pointer() ); + Path_deleteTop(brushes[i]->path()); + delete newBrushes[i]; + } + +} diff --git a/contrib/bobtoolz/funchandlers-ctf-GTK.cpp b/contrib/bobtoolz/funchandlers-ctf-GTK.cpp new file mode 100644 index 00000000..b01fc7cb --- /dev/null +++ b/contrib/bobtoolz/funchandlers-ctf-GTK.cpp @@ -0,0 +1,214 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "StdAfx.h" + +#include "dialogs/dialogs-gtk.h" + +#include "DEntity.h" +#include "DMap.h" + +#include "misc.h" +#include "lists.h" +#include "funchandlers.h" + +// for ctf texture changer +list clrList_Blue; +list clrList_Red; + +BOOL clrLst1Loaded = FALSE; +BOOL clrLst2Loaded = FALSE; + +// ------------- + +//========================// +// Helper Functions // +//========================// + +void LoadLists() +{ + char buffer[256]; + + if(!clrLst1Loaded) + { + clrLst1Loaded = LoadExclusionList(GetFilename(buffer, "plugins/bt/ctf-blue.txt"), &clrList_Blue); + LoadExclusionList(GetFilename(buffer, "plugins/bt/blue.txt"), &clrList_Blue); + } + if(!clrLst2Loaded) + { + clrLst2Loaded = LoadExclusionList(GetFilename(buffer, "plugins/bt/ctf-red.txt"), &clrList_Red); + LoadExclusionList(GetFilename(buffer, "plugins/bt/red.txt"), &clrList_Red); + } +} + + +//========================// +// Main Functions // +//========================// + +void DoCTFColourChanger() +{ + if(!clrLst1Loaded || !clrLst2Loaded) + { + DoMessageBox("CTF texture lists not found, this function will terminate.", "Error", MB_OK); + return; + } + + int ret = DoCTFColourChangeBox(); + if(ret == IDCANCEL) + return; + + int cnt = Min(clrList_Blue.size(), clrList_Red.size()); + + list::const_iterator Texture_change; + list::const_iterator Texture_new; + + float fDummy[2]; + + int eCnt = g_FuncTable.m_pfnGetEntityCount(); + + DMap world; + world.LoadAll(TRUE); + + if(ret == IDYES) + { + Texture_change = clrList_Blue.begin(); + Texture_new = clrList_Red.begin(); + } + else + { + Texture_change = clrList_Red.begin(); + Texture_new = clrList_Blue.begin(); + } + + for(int i = 0; i < cnt; i++) + { + world.ResetTextures((*Texture_change).c_str(), fDummy, fDummy, 0, (*Texture_new).c_str(), TRUE); + + Texture_change++; + Texture_new++; + } +} + +void DoSwapLights() +{ +/* DMap world; + world.LoadAll(); + + for(list::const_iterator loopEnt = world.entityList.begin(); loopEnt != world.entityList.end(); loopEnt++) + { + DEntity* e = (*loopEnt); + DEPair* epLightColour = e->FindEPairByKey("_color"); + if(epLightColour) + { + float r, g, b; + sscanf(epLightColour->value, "%f %f %f", &r, &g, &b); + sprintf(epLightColour->value, "%f %f %f", b, g, r); + DMap::RebuildEntity(e); + } + }*/ + + int cnt = g_FuncTable.m_pfnGetEntityCount(); + + for(int i = 0; i < cnt; i++) + { + void* ent = g_FuncTable.m_pfnGetEntityHandle(i); + + for(epair_t* epList = *g_FuncTable.m_pfnGetEntityKeyValList(ent); epList; epList= epList->next) + { + if(!stricmp("_color", epList->key)) + { + float r, g, b; + sscanf(epList->value, "%f %f %f", &r, &g, &b); + sprintf(epList->value, "%f %f %f", b, g, r); + } + } + } +} + +void DoChangeAngles() +{ + int cnt = g_FuncTable.m_pfnGetEntityCount(); + + for(int i = 0; i < cnt; i++) + { + void* ent = g_FuncTable.m_pfnGetEntityHandle(i); + + for(epair_t* epList = *g_FuncTable.m_pfnGetEntityKeyValList(ent); epList; epList= epList->next) + { + if(!stricmp("angle", epList->key)) + { + float angle; + sscanf(epList->value, "%f", &angle); + angle += 180; + while(angle > 360) + angle -= 360; + + sprintf(epList->value, "%f", angle); + } + } + } +} + +void DoSwapSpawns() +{ + int cnt = g_FuncTable.m_pfnGetEntityCount(); + + for(int i = 0; i < cnt; i++) + { + void* ent = g_FuncTable.m_pfnGetEntityHandle(i); + + for(epair_t* epList = *g_FuncTable.m_pfnGetEntityKeyValList(ent); epList; epList= epList->next) + { + if(!stricmp("classname", epList->key)) + { + if(!strcmp(epList->value, "team_CTF_redplayer")) + sprintf(epList->value, "team_CTF_blueplayer"); + else if(!strcmp(epList->value, "team_CTF_blueplayer")) + sprintf(epList->value, "team_CTF_redplayer"); + + if(!strcmp(epList->value, "team_CTF_redspawn")) + sprintf(epList->value, "team_CTF_bluespawn"); + else if(!strcmp(epList->value, "team_CTF_bluespawn")) + sprintf(epList->value, "team_CTF_redspawn"); + + if(!strcmp(epList->value, "team_CTF_redflag")) + sprintf(epList->value, "team_CTF_blueflag"); + else if(!strcmp(epList->value, "team_CTF_blueflag")) + sprintf(epList->value, "team_CTF_redflag") + ; + if(!strcmp(epList->value, "team_redobelisk")) + sprintf(epList->value, "team_blueobelisk"); + else if(!strcmp(epList->value, "team_blueobelisk")) + sprintf(epList->value, "team_redobelisk"); + } + } + } +} + +/*void test() +{ + DMap world; + world.LoadAll(); + + for(list::const_iterator ents = world.entityList.begin(); ents != world.entityList.end(); ents++) + { + (*ents)->RemoveFromRadiant(); + } +}*/ diff --git a/contrib/bobtoolz/funchandlers.cpp b/contrib/bobtoolz/funchandlers.cpp new file mode 100644 index 00000000..a429b78d --- /dev/null +++ b/contrib/bobtoolz/funchandlers.cpp @@ -0,0 +1,505 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "StdAfx.h" + +#include "funchandlers.h" + +#include "IntersectDialog.h" +#include "PolygonDialog.h" +#include "StairDialog.h" +#include "DoorDialog.h" +#include "IntersectInfoDialog.h" +#include "BrushCheckDialog.h" +#include "AutoCaulkDialog.h" +#include "AutoCaulkStartDialog.h" +#include "TextureResetDialog.h" +#include "pathplotterdialog.h" + +#include "DEntity.h" +#include "shapes.h" +#include "lists.h" +#include "misc.h" +#include "DShape.h" + +// for autocaulk +std::list exclusionList; // whole brush exclusion +std::list exclusionList_Face; // single face exclusion + +BOOL el1Loaded; +BOOL el2Loaded; + +DBobView* g_PathView = NULL; +// ------------- + +/************************ + Global Variables +************************/ + +CPolygonDialog polygonDlg; +CIntersectDialog intrDlg; +CStairDialog stairDlg; +CDoorDialog doorDlg; +CAutoCaulkStartDialog autocaulkDlg; +CTextureResetDialog texRstDlg; +CPathPlotterDialog ppDlg; + +/************************ + --Main Functions-- +************************/ + +void LoadLists() +{ + char buffer[256]; + + if(!el1Loaded) + el1Loaded = LoadExclusionList(GetFilename(buffer, "bt\\bt-el1.txt"), &exclusionList); + if(!el2Loaded) + el2Loaded = LoadExclusionList(GetFilename(buffer, "bt\\bt-el2.txt"), &exclusionList); +} + +void PolygonBuilder(vec3_t vMin, vec3_t vMax) +{ + // ensure we have something selected + if( g_FuncTable.m_pfnSelectedBrushCount() != 1 ) + { + MessageBox(NULL, "Invalid number of brushes selected, chose 1 only", "Error", MB_OK); + return; + } + + // tell Radiant we want to access the selected brushes + g_FuncTable.m_pfnAllocateSelectedBrushHandles(); + + // get handle to size definition brush + brush_t *brush = (brush_t*)g_FuncTable.m_pfnGetSelectedBrushHandle(0); + // cant release until we delete the brush, if we do... + + // ask user for type, size, etc.... + if(polygonDlg.DoModal() == IDOK) + { + DShape poly; + + g_FuncTable.m_pfnDeleteBrushHandle(brush); + + if(polygonDlg.m_bInverse) + poly.BuildInversePrism(vMin, vMax, polygonDlg.m_nSideCount, polygonDlg.m_bAlignTop); + else + { + if(polygonDlg.m_bBorder) + poly.BuildBorderedPrism(vMin, vMax, polygonDlg.m_nSideCount, polygonDlg.m_nBorderSize, polygonDlg.m_bAlignTop); + else + poly.BuildRegularPrism(vMin, vMax, polygonDlg.m_nSideCount, polygonDlg.m_bAlignTop); + } + + poly.Commit(); + } + + + g_FuncTable.m_pfnReleaseSelectedBrushHandles(); +} + + +void IntersectionFinder() +{ + if(intrDlg.DoModal() == IDCANCEL) + return; + + if(intrDlg.m_nBrushOptions == BRUSH_OPT_SELECTED) + { + // ensure we have enough brushes selected + if( g_FuncTable.m_pfnSelectedBrushCount() < 2 ) + { + MessageBox(NULL, "Invalid number of brushes selected, choose at least 2", "Error", MB_OK); + return; + } + } + + CIntersectInfoDialog* intrInfoDlg = new CIntersectInfoDialog(); + intrInfoDlg->Create(IDD_INTERSECT_INFO_DIALOG); + + DEntity world; + + switch(intrDlg.m_nBrushOptions) + { + case BRUSH_OPT_SELECTED: + { + world.LoadSelectedBrushes(&intrInfoDlg->m_prog1); + break; + } + case BRUSH_OPT_WHOLE_MAP: + { + world.LoadFromEntity(0, &intrInfoDlg->m_prog1); + break; + } + } + + world.RemoveNonCheckBrushes(&exclusionList, intrDlg.m_bUseDetail); + BOOL* pbSelectList; + if(intrDlg.m_bDuplicateOnly) + pbSelectList = world.BuildDuplicateList(); + else + pbSelectList = world.BuildIntersectList(); + + world.SelectBrushes(pbSelectList); + + intrInfoDlg->DestroyWindow(); + delete[] pbSelectList; +} + +void StairBuilder(vec3_t vMin, vec3_t vMax) +{ + // ensure we have something selected + if( g_FuncTable.m_pfnSelectedBrushCount() != 1 ) + { + MessageBox(NULL, "Invalid number of brushes selected, chose 1 only", "Error", MB_OK); + return; + } + + // tell Radiant we want to access the selected brushes + g_FuncTable.m_pfnAllocateSelectedBrushHandles(); + + // get handle to size definition brush + brush_t *brush = (brush_t*)g_FuncTable.m_pfnGetSelectedBrushHandle(0); + // cant release until we delete the brush, if we do... + + + // ask user for type, size, etc.... + if(stairDlg.DoModal() == IDOK) + { + + // calc brush size + vec3_t size; + _VectorSubtract(vMax, vMin, size); + + + if(((int)size[2] % stairDlg.m_nStairHeight) != 0) + { + // stairs must fit evenly into brush + MessageBox(NULL, "Invalid stair height\nHeight of block must be divisable by stair height", "Error", MB_OK); + } + else + { + + // Remove Size Brush + g_FuncTable.m_pfnDeleteBrushHandle(brush); + + + // Get Step Count, Direction of Stairs, Stair Style + int numSteps = (int)size[2] / stairDlg.m_nStairHeight; + int direction = stairDlg.m_StairDir; + int style = stairDlg.m_StairStyle; + + if(stairDlg.m_StairStyle == STYLE_CORNER) + { + BuildCornerStairs(vMin, vMax, numSteps, "textures/common/caulk", (LPCTSTR)stairDlg.m_riserTexture); + } + else + { + // Get Step Dimensions + float stairHeight = (float)stairDlg.m_nStairHeight; + float stairWidth; + if((direction == MOVE_EAST) || (direction == MOVE_WEST)) + stairWidth = (size[0])/numSteps; + else + stairWidth = (size[1])/numSteps; + + + // Build Base For Stair (bob's style) + if(style == STYLE_BOB) + Build_Wedge(direction, vMin, vMax, TRUE); + + + // Set First Step Starting Position + vMax[2] = vMin[2] + stairHeight; + SetInitialStairPos(direction, vMin, vMax, stairWidth); + + + // Build The Steps + for(int i = 0; i < numSteps; i++) + { + if(style == STYLE_BOB) + Build_StairStep_Wedge(direction, vMin, vMax, "textures/common/caulk", (LPCTSTR)stairDlg.m_riserTexture, stairDlg.m_bDetail); + else if(style == STYLE_ORIGINAL) + Build_StairStep(vMin, vMax, "textures/common/caulk", (LPCTSTR)stairDlg.m_riserTexture, direction); + + // get step into next position + MoveBlock(direction, vMin, vMax, stairWidth); + vMax[2] += stairHeight; + if(style == STYLE_BOB) + vMin[2] += stairHeight; // wedge bottom must be raised + } + } + } + } + + g_FuncTable.m_pfnReleaseSelectedBrushHandles(); +} + +void DoorBuilder(vec3_t vMin, vec3_t vMax) +{ + // ensure we have something selected + if( g_FuncTable.m_pfnSelectedBrushCount() != 1 ) + { + MessageBox(NULL, "Invalid number of brushes selected, chose 1 only", "Error", MB_OK); + return; + } + + // tell Radiant we want to access the selected brushes + g_FuncTable.m_pfnAllocateSelectedBrushHandles(); + + // get handle to size definition brush + brush_t *brush = (brush_t*)g_FuncTable.m_pfnGetSelectedBrushHandle(0); + // cant release until we delete the brush, if we do... + + + + strcpy(doorDlg.m_fbTextureName.GetBuffer(256), g_FuncTable.m_pfnGetCurrentTexture()); + + if(doorDlg.DoModal() == IDOK) + { + g_FuncTable.m_pfnDeleteBrushHandle(brush); + + BuildDoorsX2(vMin, vMax, + doorDlg.m_bSclMainHor, doorDlg.m_bSclMainVert, + doorDlg.m_bSclTrimHor, doorDlg.m_bSclTrimVert, + (LPCTSTR)doorDlg.m_fbTextureName, + (LPCTSTR)doorDlg.m_trimTextureName, + doorDlg.m_doorDirection); + } + + g_FuncTable.m_pfnReleaseSelectedBrushHandles(); +} + +void FixBrushes() +{ + DEntity world; + + CIntersectInfoDialog* intrInfoDlg = new CIntersectInfoDialog(); + intrInfoDlg->Create(IDD_INTERSECT_INFO_DIALOG); + + world.LoadFromEntity(0, &intrInfoDlg->m_prog1); + + intrInfoDlg->DestroyWindow(); + + CBrushCheckDialog* chkDlg = new CBrushCheckDialog(); + chkDlg->Create(IDD_BRUSHCHECKER_DIALOG); + + int count = world.FixBrushes(TRUE, &chkDlg->m_prog1); + + chkDlg->DestroyWindow(); + + Sys_Printf("%i invalid/duplicate planes removed\n", count); +} + +void AutoCaulk() +{ + UndoableCommand undo("bobToolz.autoCaulk"); + + if(!el1Loaded) + autocaulkDlg.m_Warning1 = "WARNING: Brush exclusion list not found\n, ALL BRUSHES WILL BE USED"; + + if(autocaulkDlg.DoModal() == IDCANCEL) + return; + + if(autocaulkDlg.m_nMode == MODE_AC_BUILD_MINI_PRT) + { + BuildMiniPrt(&exclusionList); + return; + } + + CAutoCaulkDialog* acDlg = new CAutoCaulkDialog; + acDlg->Create(IDD_AUTOCAULK_DIALOG); + + char filename[1204]; + + if(autocaulkDlg.m_nMode == MODE_AC_NORMAL) + { + char* rad_filename = g_BSPTable.m_pfnGetMapName(); + if(!rad_filename) + { + MessageBox(NULL, "An Error Occurred While Trying To Get The Map Filename", "Error", MB_OK); + acDlg->DestroyWindow(); + return; + } + + strcpy(filename, rad_filename); + + char* ext = strrchr(filename, '.')+1; + strcpy(ext, "prt");// rename the extension + } + else + { + IEpair* pEp = g_EpairTable.m_pfnIEpairForProjectKeys(); + char *pn = pEp->ValueForKey("mapspath"); + pEp->DecRef(); + + strcpy( filename, pn ); + strcat( filename, "/ac_prt.prt" ); + } + + DEntity portals; + if(!portals.LoadFromPrt(filename, &acDlg->m_prog1)) + { + MessageBox(NULL, "Failed To Load Portal File", "Error", MB_OK); + acDlg->DestroyWindow(); + return; + } + // load portal file + + CIntersectInfoDialog* intrInfoDlg = new CIntersectInfoDialog(); + intrInfoDlg->Create(IDD_INTERSECT_INFO_DIALOG); + + DEntity world; + + world.LoadFromEntity(0, &intrInfoDlg->m_prog1); + intrInfoDlg->DestroyWindow(); + + if(autocaulkDlg.m_nMode == MODE_AC_NORMAL) + world.RemoveNonCheckBrushes(&exclusionList, FALSE); + else + world.RemoveNonCheckBrushes(&exclusionList, TRUE); + + world.ResetChecks(&exclusionList_Face); + + int caulkedCount = 0; + int killCnt = world.AutoCaulk(&portals, autocaulkDlg.m_bAllowDestruction, &caulkedCount, &acDlg->m_prog2); + + if(autocaulkDlg.m_bAllowDestruction) + Sys_Printf("%i unrequired brush(es) killed\n", killCnt); + Sys_Printf("%i face(s) caulked\n", caulkedCount); + + acDlg->DestroyWindow(); +} + +void ResetTextures() +{ + texRstDlg.m_TextureName = GetCurrentTexture(); + texRstDlg.m_NewTextureName = GetCurrentTexture(); + + if(texRstDlg.DoModal() == IDCANCEL) + return; + + float fScale[2]; + float fShift[2]; + fScale[1] = texRstDlg.m_fScaleVertical; + fScale[0] = texRstDlg.m_fScaleHorizontal; + + fShift[1] = (float)texRstDlg.m_nShiftVertical; + fShift[0] = (float)texRstDlg.m_nShiftHorizontal; + + DEntity world; + world.LoadFromEntity(0, NULL); + + if(texRstDlg.m_bAllTextures) + world.ResetTextures(NULL, fScale, fShift, texRstDlg.m_nRotation, texRstDlg.m_NewTextureName, texRstDlg.m_bOnlyTexture); + else + world.ResetTextures(texRstDlg.m_TextureName, fScale, fShift, texRstDlg.m_nRotation, texRstDlg.m_NewTextureName, texRstDlg.m_bOnlyTexture); +} + +void PathPlotter() +{ + int ret = ppDlg.DoModal(); + if(ret == IDCANCEL) + return; + if(ret == IDNO) + { + if(g_PathView) + delete g_PathView; + + return; + } + + if( g_FuncTable.m_pfnSelectedBrushCount() != 1) + { + MessageBox(NULL, "Invalid number of brushes selected, chose 1 only", "Error", MB_OK); + return; + } + + // tell Radiant we want to access the selected brushes + g_FuncTable.m_pfnAllocateSelectedBrushHandles(); + + // get handle to size definition brush + brush_t *brush = (brush_t*)g_FuncTable.m_pfnGetSelectedBrushHandle(0); + // cant release until we delete the brush, if we do... + + DEntity world; + world.LoadEPairList(*g_FuncTable.m_pfnGetEntityKeyValList(brush->owner)); + + DEPair* trigger_ep = world.FindEPairByKey("targetname"); + + if(trigger_ep) + { + if(!strcmp(world.m_Classname, "trigger_push")) + { + DEPair* target_ep = world.FindEPairByKey("target"); + if(target_ep) + { + entity_s* entTarget = FindEntityFromTargetname(target_ep->value); + if(entTarget) + { + if(g_PathView) + delete g_PathView; + g_PathView = new DBobView; + + g_PathView->Begin(trigger_ep->value, target_ep->value, ppDlg.m_fMultiplier, ppDlg.m_nPoints, ppDlg.m_fGravity, ppDlg.m_bNoUpdate, ppDlg.m_bShowExtra); + } + else + MessageBox(NULL, "trigger_push target could not be found.", "Error", MB_OK); + } + else + MessageBox(NULL, "trigger_push has no target.", "Error", MB_OK); + } + else + MessageBox(NULL, "You must select a 'trigger_push' entity.", "Error", MB_OK); + } + else + MessageBox(NULL, "Entity must have a targetname", "Error", MB_OK); + + g_FuncTable.m_pfnReleaseSelectedBrushHandles(); +} + +void PitBuilder(vec3_t vMin, vec3_t vMax) +{ + // ensure we have something selected + if( g_FuncTable.m_pfnSelectedBrushCount() != 1 ) + { + MessageBox(NULL, "Invalid number of brushes selected, chose 1 only", "Error", MB_OK); + return; + } + + // tell Radiant we want to access the selected brushes + g_FuncTable.m_pfnAllocateSelectedBrushHandles(); + + // get handle to size definition brush + brush_t *brush = (brush_t*)g_FuncTable.m_pfnGetSelectedBrushHandle(0); + // cant release until we delete the brush, if we do... + + DShape pit; + + if(pit.BuildPit(vMin, vMax)) + { + pit.Commit(); + + g_FuncTable.m_pfnDeleteBrushHandle(brush); + } + else + MessageBox(NULL, "Failed To Make Pit\nTry Making The Brush Bigger", "Error", MB_OK); + + g_FuncTable.m_pfnReleaseSelectedBrushHandles(); +} diff --git a/contrib/bobtoolz/funchandlers.h b/contrib/bobtoolz/funchandlers.h new file mode 100644 index 00000000..8be283fd --- /dev/null +++ b/contrib/bobtoolz/funchandlers.h @@ -0,0 +1,72 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +class DBobView; +class DVisDrawer; +class DTrainDrawer; +class DTreePlanter; + +extern DBobView* g_PathView; +extern DVisDrawer* g_VisView; +extern DTrainDrawer* g_TrainView; +extern DTreePlanter* g_TreePlanter; + +// intersect stuff +#define BRUSH_OPT_WHOLE_MAP 0 +#define BRUSH_OPT_SELECTED 1 + +// defines for stairs +#define MOVE_NORTH 0 +#define MOVE_SOUTH 1 +#define MOVE_EAST 2 +#define MOVE_WEST 3 + +#define STYLE_ORIGINAL 0 +#define STYLE_BOB 1 +#define STYLE_CORNER 2 + +// defines for doors +#define DIRECTION_NS 0 +#define DIRECTION_EW 1 + +// help +void LoadLists(); + + +// djbob +void DoIntersect(); +void DoPolygonsTB(); +void DoPolygons(); +void DoFixBrushes(); +void DoResetTextures(); +void DoBuildStairs(); +void DoBuildDoors(); +void DoPathPlotter(); +void DoPitBuilder(); +void DoCTFColourChanger(); +void DoMergePatches(); +void DoSplitPatch(); +void DoVisAnalyse(); +void DoTrainThing(); +void DoTrainPathPlot(); +void DoCaulkSelection(); +void DoTreePlanter(); +void DoDropEnts(); +void DoMakeChain(); +void DoFlipTerrain(); diff --git a/contrib/bobtoolz/lists.cpp b/contrib/bobtoolz/lists.cpp new file mode 100644 index 00000000..9823b051 --- /dev/null +++ b/contrib/bobtoolz/lists.cpp @@ -0,0 +1,86 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "lists.h" + +#ifdef WIN32 +#pragma warning(disable : 4786) +#endif + +#include + +#include "misc.h" + +bool LoadExclusionList(char* filename, std::list* exclusionList) +{ + FILE* eFile = fopen(filename, "r"); + if(eFile) + { + char buffer[256]; + int cnt = 0; + while(!feof(eFile)) + { + memset(buffer, 0, 256); + fscanf(eFile, "%s\n", buffer); + + if(strlen(buffer) > 0) + exclusionList->push_back(buffer); + else + cnt++; + } + + fclose(eFile); + + return TRUE; + } + + globalErrorStream() << "Failed To Load Exclusion List: " << filename << "\n"; + return FALSE; +} + +bool LoadGList(char* filename, GList** loadlist) +{ + FILE* eFile = fopen(filename, "r"); + if(eFile) + { + char buffer[256]; + int cnt = 0; + while(!feof(eFile)) + { + memset(buffer, 0, 256); + fscanf(eFile, "%s\n", buffer); + + if(strlen(buffer) > 0) + { + char* buffer2 = new char[strlen(buffer) + 1]; + strcpy(buffer2, buffer); + *loadlist = g_list_append(*loadlist, buffer2); + } + else + cnt++; + } + + fclose(eFile); + + return TRUE; + } + + globalErrorStream() << "Failed To Load GList: " << filename << "\n"; + return FALSE; +} diff --git a/contrib/bobtoolz/lists.h b/contrib/bobtoolz/lists.h new file mode 100644 index 00000000..c4ccf77e --- /dev/null +++ b/contrib/bobtoolz/lists.h @@ -0,0 +1,25 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include "str.h" +typedef struct _GList GList; + +bool LoadExclusionList(char* filename, std::list* exclusionList); +bool LoadGList(char* filename, GList** loadlist); diff --git a/contrib/bobtoolz/misc.cpp b/contrib/bobtoolz/misc.cpp new file mode 100644 index 00000000..5d926986 --- /dev/null +++ b/contrib/bobtoolz/misc.cpp @@ -0,0 +1,406 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "misc.h" + +#include +#include "str.h" + +#include "DPoint.h" +#include "DPlane.h" +#include "DBrush.h" +#include "DEPair.h" +#include "DPatch.h" +#include "DEntity.h" + +#include "funchandlers.h" + +#ifdef __linux__ +#include +#include +#endif + +#include "iundo.h" +#include "ientity.h" +#include "iscenegraph.h" +#include "qerplugin.h" + +#include +#include +#include +#include + +#include "scenelib.h" + +/*========================== + Global Vars +==========================*/ + +//HANDLE bsp_process; +char g_CurrentTexture[256] = ""; + +//============================================================= +//============================================================= + +void ReadCurrentTexture() +{ + const char* textureName = GlobalRadiant().TextureBrowser_getSelectedShader(); + strcpy(g_CurrentTexture, textureName); +} + +const char* GetCurrentTexture() +{ + ReadCurrentTexture(); + return g_CurrentTexture; +} + +void MoveBlock(int dir, vec3_t min, vec3_t max, float dist) +{ + switch(dir) + { + case MOVE_EAST: + { + min[0]+=dist; + max[0]+=dist; + break; + } + case MOVE_WEST: + { + min[0]-=dist; + max[0]-=dist; + break; + } + case MOVE_NORTH: + { + min[1]+=dist; + max[1]+=dist; + break; + } + case MOVE_SOUTH: + { + min[1]-=dist; + max[1]-=dist; + break; + } + } +} + +void SetInitialStairPos(int dir, vec3_t min, vec3_t max, float width) +{ + switch(dir) + { + case MOVE_EAST: + { + max[0] = min[0] + width; + break; + } + case MOVE_WEST: + { + min[0] = max[0] - width; + break; + } + case MOVE_NORTH: + { + max[1] = min[1] + width; + break; + } + case MOVE_SOUTH: + { + min[1] = max[1] - width; + break; + } + } +} + +char* TranslateString (char *buf) +{ + static char buf2[32768]; + + std::size_t l = strlen(buf); + char* out = buf2; + for (int i=0 ; i + +bool Q_Exec( const char *pCmd, bool bCreateConsole ) +{ + // G_DeWan: Don't know if this is needed for linux version + + PROCESS_INFORMATION pi; + STARTUPINFO si = {0}; // Initialize all members to zero + si.cb = sizeof(STARTUPINFO); // Set byte count + DWORD dwCreationFlags; + + if (bCreateConsole) + dwCreationFlags = CREATE_NEW_CONSOLE | NORMAL_PRIORITY_CLASS; + else + dwCreationFlags = DETACHED_PROCESS | NORMAL_PRIORITY_CLASS; + + for(; *pCmd == ' '; pCmd++); + + if(!CreateProcess(NULL, (char *)pCmd, NULL, NULL, false, dwCreationFlags, NULL, NULL, &si, &pi)) + return false; + + return true; +} +#endif + +void StartBSP() +{ + char exename[256]; + GetFilename(exename, "q3map"); + UnixToDosPath(exename); // do we want this done in linux version? + + char mapname[256]; + const char *pn = GlobalRadiant().getMapsPath(); + + strcpy( mapname, pn ); + strcat( mapname, "/ac_prt.map" ); + UnixToDosPath(mapname); + + char command[1024]; + sprintf(command, "%s -nowater -fulldetail %s", exename, mapname); + + Q_Exec( command, true ); +} + +class EntityWriteMiniPrt +{ + mutable DEntity world; + FILE* pFile; + std::list* exclusionList; +public: + EntityWriteMiniPrt(FILE* pFile, std::list* exclusionList) + : pFile(pFile), exclusionList(exclusionList) + { + } + void operator()(scene::Instance& instance) const + { + const char* classname = Node_getEntity(instance.path().top())->getKeyValue("classname"); + + if(!strcmp(classname, "worldspawn")) + { + world.LoadFromEntity(instance.path().top(), false); + world.RemoveNonCheckBrushes(exclusionList, true); + world.SaveToFile(pFile); + } + else if(strstr(classname, "info_")) + { + world.ClearBrushes(); + world.ClearEPairs(); + world.LoadEPairList(Node_getEntity(instance.path().top())); + world.SaveToFile(pFile); + } + } +}; + +void BuildMiniPrt(std::list* exclusionList) +{ + // yes, we could just use -fulldetail option, but, as SPOG said + // it'd be faster without all the hint, donotenter etc textures and + // doors, etc + + + + char buffer[128]; + const char *pn = GlobalRadiant().getMapsPath(); + + strcpy( buffer, pn ); + strcat( buffer, "/ac_prt.map" ); + FILE* pFile = fopen(buffer, "w"); + + // ahem, thx rr2 + if(!pFile) + return; + + Scene_forEachEntity(EntityWriteMiniPrt(pFile, exclusionList)); + + fclose(pFile); + + StartBSP(); +} + +class EntityFindByTargetName +{ + const char* targetname; +public: + mutable const scene::Path* result; + EntityFindByTargetName(const char* targetname) + : targetname(targetname), result(0) + { + } + void operator()(scene::Instance& instance) const + { + if(result == 0) + { + const char* value = Node_getEntity(instance.path().top())->getKeyValue("targetname"); + + if(!strcmp(value, targetname)) + { + result = &instance.path(); + } + } + } +}; + +const scene::Path* FindEntityFromTargetname(const char* targetname) +{ + return Scene_forEachEntity(EntityFindByTargetName(targetname)).result; +} + +void FillDefaultTexture(_QERFaceData* faceData, vec3_t va, vec3_t vb, vec3_t vc, const char* texture) +{ + faceData->m_texdef.rotate = 0; + faceData->m_texdef.scale[0] = 0.5; + faceData->m_texdef.scale[1] = 0.5; + faceData->m_texdef.shift[0] = 0; + faceData->m_texdef.shift[1] = 0; + faceData->contents = 0; + faceData->flags = 0; + faceData->value = 0; + if(*texture) + faceData->m_shader = texture; + else + faceData->m_shader = "textures/common/caulk"; + VectorCopy(va, faceData->m_p0); + VectorCopy(vb, faceData->m_p1); + VectorCopy(vc, faceData->m_p2); +} + +float Determinant3x3(float a1, float a2, float a3, + float b1, float b2, float b3, + float c1, float c2, float c3) +{ + return a1*(b2*c3-b3*c2) - a2*(b1*c3-b3*c1) + a3*(b1*c2-b2*c1); +} + +bool GetEntityCentre(const char* entity, vec3_t centre) +{ + const scene::Path* ent = FindEntityFromTargetname(entity); + if(!ent) + return false; + + scene::Instance& instance = *GlobalSceneGraph().find(*ent); + VectorCopy(instance.worldAABB().origin, centre); + + return true; +} + +vec_t Min(vec_t a, vec_t b) +{ + if(a < b) + return a; + return b; +} + +void MakeNormal( const vec_t* va, const vec_t* vb, const vec_t* vc, vec_t* out ) { + vec3_t v1, v2; + VectorSubtract(va, vb, v1); + VectorSubtract(vc, vb, v2); + CrossProduct(v1, v2, out); +} + +char* GetFilename(char* buffer, const char* filename) { + strcpy(buffer, GlobalRadiant().getAppPath()); + strcat(buffer, "plugins/"); + strcat(buffer, filename); + return buffer; +} + diff --git a/contrib/bobtoolz/misc.h b/contrib/bobtoolz/misc.h new file mode 100644 index 00000000..61faee5d --- /dev/null +++ b/contrib/bobtoolz/misc.h @@ -0,0 +1,57 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(INCLUDED_MISC_H) +#define INCLUDED_MISC_H + +#include "mathlib.h" +#include +#include "str.h" +#include "iscenegraph.h" + +#define MAX_ROUND_ERROR 0.05 + +vec_t Min(vec_t a, vec_t b); + +// reads current texture into global, returns pointer to it +const char* GetCurrentTexture(); + +class _QERFaceData; +void FillDefaultTexture(_QERFaceData* faceData, vec3_t va, vec3_t vb, vec3_t vc, const char* texture); + +void BuildMiniPrt(std::list* exclusionList); + +void MoveBlock(int dir, vec3_t min, vec3_t max, float dist); +void SetInitialStairPos(int dir, vec3_t min, vec3_t max, float width); + +const scene::Path* FindEntityFromTargetname(const char* targetname); + +char* UnixToDosPath(char* path); + +char* GetFilename(char* buffer, const char* filename); +char* GetGameFilename(char* buffer, const char* filename); + +float Determinant3x3(float a1, float a2, float a3, + float b1, float b2, float b3, + float c1, float c2, float c3); + +bool GetEntityCentre(const char* entity, vec3_t centre); +void MakeNormal( const vec_t* va, const vec_t* vb, const vec_t* vc, vec_t* out ); + +#endif diff --git a/contrib/bobtoolz/res/plugin.rc2 b/contrib/bobtoolz/res/plugin.rc2 new file mode 100644 index 00000000..2704dbfa --- /dev/null +++ b/contrib/bobtoolz/res/plugin.rc2 @@ -0,0 +1,13 @@ +// +// SOUNDTEST.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/contrib/bobtoolz/resource-gtk.h b/contrib/bobtoolz/resource-gtk.h new file mode 100644 index 00000000..9297877f --- /dev/null +++ b/contrib/bobtoolz/resource-gtk.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by bobtoolz-gtk.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/contrib/bobtoolz/resource.h b/contrib/bobtoolz/resource.h new file mode 100644 index 00000000..2c7f97ed --- /dev/null +++ b/contrib/bobtoolz/resource.h @@ -0,0 +1,115 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by bobToolz.rc +// +#define IDD_PATHPLOTTER_DIALOG 101 +#define IDD_ABOUT 129 +#define IDD_STAIR_DIALOG 130 +#define IDD_POLYGON_DIALOG 131 +#define IDB_BT_BITMAP 137 +#define IDI_ICON1 139 +#define IDD_POLYGON_BRD_DIALOG 141 +#define IDD_DOOR_DIALOG 141 +#define IDD_INTERSECT_DIALOG 143 +#define IDD_INTERSECT_INFO_DIALOG 144 +#define IDD_BRUSHCHECKER_DIALOG 146 +#define IDD_AUTOCAULK_DIALOG 147 +#define IDD_AUTOCAULKSTART_DIALOG 148 +#define IDD_TEXTURE_RESET_DIALOG 149 +#define IDC_EDIT1 1000 +#define IDC_DIR_N_RADIO 1001 +#define IDC_TRIMTEXTURE_EDIT 1001 +#define IDC_SCL_VERT_EDIT 1001 +#define IDC_DIR_S_RADIO 1002 +#define IDC_SCL_HOR_EDIT 1002 +#define IDC_POINTCOUNT_EDIT 1002 +#define IDC_DIR_E_RADIO 1003 +#define IDC_ROTATION_EDIT 1003 +#define IDC_MULTIPLIER_EDIT 1003 +#define IDC_DIR_W_RADIO 1004 +#define IDC_SHFT_VER_EDIT 1004 +#define IDC_GRAVITY_EDIT 1004 +#define IDC_STYLE_ORIG_RADIO 1005 +#define IDC_SHFT_HOR_EDIT 1005 +#define IDC_NOUPDATE_CHECK 1005 +#define IDC_STYLE_BOB_RADIO 1006 +#define IDC_SHOWEXTRA_CHECK 1006 +#define IDC_STYLE_CORNER_RADIO 1007 +#define IDC_RISER_EDIT 1011 +#define IDC_FLAT_EDIT 1012 +#define IDC_MAX_WALL_WIDTH 1013 +#define IDC_MIN_WALL_WIDTH 1014 +#define IDC_DETAIL_CHK 1014 +#define IDC_MAX_CLIFF_HEIGHT 1015 +#define IDC_INVERSE_CHK 1015 +#define IDC_MIN_CLIFF_HEIGHT 1016 +#define IDC_BORDER_CHK 1016 +#define IDC_ALIGN_CHK 1017 +#define IDC_BORDER_EDIT 1018 +#define IDC_MAX_CNR_SIZE 1019 +#define IDC_MIN_CNR_SIZE 1020 +#define IDC_FBTEXTURE_EDIT 1020 +#define IDC_GRID_SNAP 1021 +#define IDC_TEXSCALE1_CHECK 1021 +#define IDC_MAX_WALL_BREADTH 1022 +#define IDC_TEXSCALE2_CHECK 1022 +#define IDC_MIN_WALL_BREADTH 1023 +#define IDC_MAINTEX_COMBO 1023 +#define IDC_TEXSCALE3_CHECK 1024 +#define IDC_TEXSCALE4_CHECK 1025 +#define IDC_TRIMTEX_COMBO 1026 +#define IDC_SET_MAINTEX_BTN 1027 +#define IDC_SET_TRIMTEX_BTN 1028 +#define IDC_WHOLEMAP_CHECK 1030 +#define IDC_DETAIL_INCLUDE_CHECK 1031 +#define IDC_SKIPBOUNDS_CHECK 1032 +#define IDC_SKIPSIMPLE_CHECK 1033 +#define IDC_SKIPACCURATE_CHECK 1034 +#define IDC_PROGRESS1 1035 +#define IDC_INTR_PROG1 1035 +#define IDC_DIR_NS_RADIO 1036 +#define IDC_PROGRESS2 1036 +#define IDC_DIR_EW_RADIO 1037 +#define IDC_DIR_GROUP 1038 +#define IDC_WHOLEMAP_RADIO 1040 +#define IDC_SELECTED_RADIO 1041 +#define IDC_KILLBRUSHES_CHECK 1041 +#define IDC_WARNING1_STATIC 1042 +#define IDC_AC_NORMAL_RADIO 1043 +#define IDC_AC_BUILD_MINI_PRT_RADIO 1044 +#define IDC_AC_SUPER_RADIO 1045 +#define IDC_RESET_TEXTURE_EDIT 1046 +#define IDC_RESET_NEW_TEXTURE_EDIT 1047 +#define IDC_ONLYTEXTURE_CHECK 1048 +#define IDC_ALLTEXTURES_CHECK 1049 +#define IDC_DUPLICATEONLY_CHECK 1050 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1007 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/contrib/bobtoolz/shapes.cpp b/contrib/bobtoolz/shapes.cpp new file mode 100644 index 00000000..4713ff5f --- /dev/null +++ b/contrib/bobtoolz/shapes.cpp @@ -0,0 +1,669 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "shapes.h" + +#include + +#include "DPoint.h" +#include "DPlane.h" + +#include "str.h" +#include "misc.h" +#include "funchandlers.h" + +#include "iundo.h" +#include "ishaders.h" +#include "ientity.h" +#include "ieclass.h" +#include "ipatch.h" +#include "qerplugin.h" + +#include +#include +#include +#include +#include + +#include "scenelib.h" +#include "texturelib.h" + +//#include "dialogs-gtk.h" + +/************************ + Cube Diagram +************************/ + +/* + + 7 ----- 5 + /| /| + / | / | + / | / | + 4 ----- 6 | + | 2|_|___|8 + | / | / + | / | / ----> WEST, definitely + |/ | / + 1|_____|/3 + +*/ + +/************************ + Global Variables +************************/ + +vec3_t g_Origin = {0.0f, 0.0f, 0.0f}; + +extern bool bFacesAll[]; + +/************************ + Helper Functions +************************/ + +float Deg2Rad(float angle) +{ + return (float)(angle*Q_PI/180); +} + +void AddFaceWithTexture(scene::Node& brush, vec3_t va, vec3_t vb, vec3_t vc, const char* texture, bool detail) +{ + _QERFaceData faceData; + FillDefaultTexture(&faceData, va, vb, vc, texture); + if(detail) + faceData.contents |= FACE_DETAIL; + GlobalBrushCreator().Brush_addFace(brush, faceData); +} + +void AddFaceWithTextureScaled(scene::Node& brush, vec3_t va, vec3_t vb, vec3_t vc, + const char* texture, bool bVertScale, bool bHorScale, + float minX, float minY, float maxX, float maxY) +{ + qtexture_t* pqtTexInfo; + + // TTimo: there used to be a call to pfnHasShader here + // this was not necessary. In Radiant everything is shader. + // If a texture doesn't have a shader script, a default shader object is used. + // The IShader object was leaking also + // collect texture info: sizes, etc + IShader* i = GlobalShaderSystem().getShaderForName(texture); + pqtTexInfo = i->getTexture(); // shader width/height doesn't come out properly + + if(pqtTexInfo) + { + float scale[2] = {0.5f, 0.5f}; + float shift[2] = {0, 0}; + + if(bHorScale) + { + float width = maxX - minX; + + scale[0] = width/pqtTexInfo->width; + shift[0] = -(float)((int)maxX%(int)width)/scale[0]; + } + + if(bVertScale) + { + float height = maxY - minY; + + scale[1] = height/pqtTexInfo->height; + shift[1] = (float)((int)minY%(int)height)/scale[1]; + } + + _QERFaceData addFace; + FillDefaultTexture(&addFace, va, vb, vc, texture); + addFace.m_texdef.scale[0] = scale[0]; + addFace.m_texdef.scale[1] = scale[1]; + addFace.m_texdef.shift[0] = shift[0]; + addFace.m_texdef.shift[1] = shift[1]; + + GlobalBrushCreator().Brush_addFace(brush, addFace); + } + else + { + // shouldn't even get here, as default missing texture should be returned if + // texture doesn't exist, but just in case + AddFaceWithTexture(brush, va, vb, vc, texture, false); + globalErrorStream() << "BobToolz::Invalid Texture Name-> " << texture; + } + // the IShader is not kept referenced, DecRef it + i->DecRef(); +} + +/************************ + --Main Functions-- +************************/ + +void Build_Wedge(int dir, vec3_t min, vec3_t max, bool bUp) +{ + NodeSmartReference newBrush(GlobalBrushCreator().createBrush()); + + vec3_t v1, v2, v3, v5, v6, v7, v8; + VectorCopy(min, v1); + VectorCopy(min, v2); + VectorCopy(min, v3); + VectorCopy(max, v5); + VectorCopy(max, v6); + VectorCopy(max, v7); + VectorCopy(max, v8); + + v2[0] = max[0]; + v3[1] = max[1]; + + v6[0] = min[0]; + v7[1] = min[1]; + v8[2] = min[2]; + + if(bUp) + { + + if(dir != MOVE_EAST) + AddFaceWithTexture(newBrush, v1, v3, v6, "textures/common/caulk", false); + + if(dir != MOVE_WEST) + AddFaceWithTexture(newBrush, v7, v5, v8, "textures/common/caulk", false); + + if(dir != MOVE_NORTH) + AddFaceWithTexture(newBrush, v1, v7, v2, "textures/common/caulk", false); + + if(dir != MOVE_SOUTH) + AddFaceWithTexture(newBrush, v3, v8, v6, "textures/common/caulk", false); + + AddFaceWithTexture(newBrush, v1, v2, v3, "textures/common/caulk", false); + + if(dir == MOVE_EAST) + AddFaceWithTexture(newBrush, v1, v3, v5, "textures/common/caulk", false); + + if(dir == MOVE_WEST) + AddFaceWithTexture(newBrush, v2, v6, v8, "textures/common/caulk", false); + + if(dir == MOVE_NORTH) + AddFaceWithTexture(newBrush, v1, v6, v5, "textures/common/caulk", false); + + if(dir == MOVE_SOUTH) + AddFaceWithTexture(newBrush, v7, v3, v8, "textures/common/caulk", false); + } + else + { + if(dir != MOVE_WEST) + AddFaceWithTexture(newBrush, v7, v5, v8, "textures/common/caulk", false); + + if(dir != MOVE_EAST) + AddFaceWithTexture(newBrush, v1, v3, v6, "textures/common/caulk", false); + + if(dir != MOVE_NORTH) + AddFaceWithTexture(newBrush, v3, v8, v6, "textures/common/caulk", false); + + if(dir != MOVE_SOUTH) + AddFaceWithTexture(newBrush, v1, v7, v2, "textures/common/caulk", false); + + + AddFaceWithTexture(newBrush, v6, v5, v7, "textures/common/caulk", false); + + if(dir == MOVE_WEST) + AddFaceWithTexture(newBrush, v1, v5, v3, "textures/common/caulk", false); + + if(dir == MOVE_EAST) + AddFaceWithTexture(newBrush, v2, v8, v6, "textures/common/caulk", false); + + if(dir == MOVE_NORTH) + AddFaceWithTexture(newBrush, v1, v5, v6, "textures/common/caulk", false); + + if(dir == MOVE_SOUTH) + AddFaceWithTexture(newBrush, v7, v8, v3, "textures/common/caulk", false); + } + + Node_getTraversable(GlobalRadiant().getMapWorldEntity())->insert(newBrush); +} + +//----------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------- + +void Build_StairStep_Wedge(int dir, vec3_t min, vec3_t max, const char* mainTexture, const char* riserTexture, bool detail) +{ + NodeSmartReference newBrush(GlobalBrushCreator().createBrush()); + + //----- Build Outer Bounds --------- + + vec3_t v1, v2, v3, v5, v6, v7, v8; + VectorCopy(min, v1); + VectorCopy(min, v2); + VectorCopy(min, v3); + VectorCopy(max, v5); + VectorCopy(max, v6); + VectorCopy(max, v7); + VectorCopy(max, v8); + + v2[0] = max[0]; + v3[1] = max[1]; + + v6[0] = min[0]; + v7[1] = min[1]; + + v8[2] = min[2]; + //v8 needed this time, becoz of sloping faces (2-4-6-8) + + //---------------------------------- + + AddFaceWithTexture(newBrush, v6, v5, v7, mainTexture, detail); + + if(dir != MOVE_EAST) + { + if(dir == MOVE_WEST) + AddFaceWithTexture(newBrush, v5, v2, v7, riserTexture, detail); + else + AddFaceWithTexture(newBrush, v5, v2, v7, "textures/common/caulk", detail); + } + + if(dir != MOVE_WEST) + { + if(dir == MOVE_EAST) + AddFaceWithTexture(newBrush, v1, v3, v6, riserTexture, detail); + else + AddFaceWithTexture(newBrush, v1, v3, v6, "textures/common/caulk", detail); + } + + if(dir != MOVE_NORTH) + { + if(dir == MOVE_SOUTH) + AddFaceWithTexture(newBrush, v3, v5, v6, riserTexture, detail); + else + AddFaceWithTexture(newBrush, v3, v5, v6, "textures/common/caulk", detail); + } + + if(dir != MOVE_SOUTH) + { + if(dir == MOVE_NORTH) + AddFaceWithTexture(newBrush, v1, v7, v2, riserTexture, detail); + else + AddFaceWithTexture(newBrush, v1, v7, v2, "textures/common/caulk", detail); + } + + + if(dir == MOVE_EAST) + AddFaceWithTexture(newBrush, v1, v5, v3, "textures/common/caulk", detail); + + if(dir == MOVE_WEST) + AddFaceWithTexture(newBrush, v2, v8, v6, "textures/common/caulk", detail); + + if(dir == MOVE_NORTH) + AddFaceWithTexture(newBrush, v1, v5, v6, "textures/common/caulk", detail); + + if(dir == MOVE_SOUTH) + AddFaceWithTexture(newBrush, v7, v8, v3, "textures/common/caulk", detail); + + Node_getTraversable(GlobalRadiant().getMapWorldEntity())->insert(newBrush); +} + +//----------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------- + +// internal use only, to get a box without finishing construction +scene::Node& Build_Get_BoundingCube_Selective(vec3_t min, vec3_t max, char* texture, bool* useFaces) +{ + NodeSmartReference newBrush(GlobalBrushCreator().createBrush()); + + //----- Build Outer Bounds --------- + + vec3_t v1, v2, v3, v5, v6, v7; + VectorCopy(min, v1); + VectorCopy(min, v2); + VectorCopy(min, v3); + VectorCopy(max, v5); + VectorCopy(max, v6); + VectorCopy(max, v7); + + v2[0] = max[0]; + v3[1] = max[1]; + + v6[0] = min[0]; + v7[1] = min[1]; + + //---------------------------------- + + //----- Add Six Cube Faces --------- + + if(useFaces[0]) + AddFaceWithTexture(newBrush, v1, v2, v3, texture, false); + if(useFaces[1]) + AddFaceWithTexture(newBrush, v1, v3, v6, texture, false); + if(useFaces[2]) + AddFaceWithTexture(newBrush, v1, v7, v2, texture, false); + + if(useFaces[3]) + AddFaceWithTexture(newBrush, v5, v6, v3, texture, false); + if(useFaces[4]) + AddFaceWithTexture(newBrush, v5, v2, v7, texture, false); + if(useFaces[5]) + AddFaceWithTexture(newBrush, v5, v7, v6, texture, false); + + //---------------------------------- + + return newBrush; +} + +scene::Node& Build_Get_BoundingCube(vec3_t min, vec3_t max, char* texture) +{ + return Build_Get_BoundingCube_Selective(min, max, texture, bFacesAll); +} + +//----------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------- + +void Build_StairStep(vec3_t min, vec3_t max, const char* mainTexture, const char* riserTexture, int direction) +{ + NodeSmartReference newBrush(GlobalBrushCreator().createBrush()); + + //----- Build Outer Bounds --------- + + vec3_t v1, v2, v3, v5, v6, v7; + VectorCopy(min, v1); + VectorCopy(min, v2); + VectorCopy(min, v3); + VectorCopy(max, v5); + VectorCopy(max, v6); + VectorCopy(max, v7); + + v2[0] = max[0]; + v3[1] = max[1]; + + v6[0] = min[0]; + v7[1] = min[1]; + + //---------------------------------- + + AddFaceWithTexture(newBrush, v6, v5, v7, mainTexture, false); + // top gets current texture + + + if(direction == MOVE_EAST) + AddFaceWithTexture(newBrush, v1, v3, v6, riserTexture, false); + else + AddFaceWithTexture(newBrush, v1, v3, v6, "textures/common/caulk", false); + // west facing side, etc... + + + if(direction == MOVE_NORTH) + AddFaceWithTexture(newBrush, v1, v7, v2, riserTexture, false); + else + AddFaceWithTexture(newBrush, v1, v7, v2, "textures/common/caulk", false); + + if(direction == MOVE_SOUTH) + AddFaceWithTexture(newBrush, v3, v5, v6, riserTexture, false); + else + AddFaceWithTexture(newBrush, v3, v5, v6, "textures/common/caulk", false); + + if(direction == MOVE_WEST) + AddFaceWithTexture(newBrush, v7, v5, v2, riserTexture, false); + else + AddFaceWithTexture(newBrush, v7, v5, v2, "textures/common/caulk", false); + + + AddFaceWithTexture(newBrush, v1, v2, v3, "textures/common/caulk", false); + // base is caulked + + Node_getTraversable(GlobalRadiant().getMapWorldEntity())->insert(newBrush); + // finish brush +} + +//----------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------- + +void BuildDoorsX2(vec3_t min, vec3_t max, + bool bSclMainHor, bool bSclMainVert, + bool bSclTrimHor, bool bSclTrimVert, + const char* mainTexture, const char* trimTexture, + int direction) +{ + int xy; + if(direction == 0) + xy = 0; + else + xy = 1; + + //----- Build Outer Bounds --------- + + vec3_t v1, v2, v3, v5, v6, v7, ve_1, ve_2, ve_3; + VectorCopy(min, v1); + VectorCopy(min, v2); + VectorCopy(min, v3); + VectorCopy(max, v5); + VectorCopy(max, v6); + VectorCopy(max, v7); + + v2[0] = max[0]; + v3[1] = max[1]; + + v6[0] = min[0]; + v7[1] = min[1]; + + float width = (max[xy] - min[xy])/2; + + if(direction == 0) + { + VectorCopy(v1, ve_1); + VectorCopy(v3, ve_2); + VectorCopy(v6, ve_3); + } + else + { + VectorCopy(v7, ve_1); + VectorCopy(v1, ve_2); + VectorCopy(v2, ve_3); + } + + ve_1[xy] += width; + ve_2[xy] += width; + ve_3[xy] += width; + + //---------------------------------- + + NodeSmartReference newBrush1(GlobalBrushCreator().createBrush()); + NodeSmartReference newBrush2(GlobalBrushCreator().createBrush()); + + AddFaceWithTexture(newBrush1, v1, v2, v3, "textures/common/caulk", false); + AddFaceWithTexture(newBrush1, v5, v7, v6, "textures/common/caulk", false); + + AddFaceWithTexture(newBrush2, v1, v2, v3, "textures/common/caulk", false); + AddFaceWithTexture(newBrush2, v5, v7, v6, "textures/common/caulk", false); + + if(direction == 0) + { + AddFaceWithTexture(newBrush1, v1, v3, v6, "textures/common/caulk", false); + AddFaceWithTexture(newBrush2, v5, v2, v7, "textures/common/caulk", false); + } + else + { + AddFaceWithTexture(newBrush1, v1, v7, v2, "textures/common/caulk", false); + AddFaceWithTexture(newBrush2, v5, v6, v3, "textures/common/caulk", false); + } + + if(direction == 0) + { + AddFaceWithTextureScaled(newBrush1, v1, v7, v2, mainTexture, bSclMainVert, bSclMainHor, + min[0], min[2], max[0], max[2]); + AddFaceWithTextureScaled(newBrush1, v5, v6, v3, mainTexture, bSclMainVert, bSclMainHor, + max[0], min[2], min[0], max[2]); + + + AddFaceWithTextureScaled(newBrush2, v1, v7, v2, mainTexture, bSclMainVert, bSclMainHor, + min[0], min[2], max[0], max[2]); + AddFaceWithTextureScaled(newBrush2, v5, v6, v3, mainTexture, bSclMainVert, bSclMainHor, + max[0], min[2], min[0], max[2]); // flip max/min to reverse tex dir + + + + AddFaceWithTextureScaled(newBrush1, ve_3, ve_2, ve_1, trimTexture, bSclTrimVert, bSclTrimHor, + min[1], min[2], max[1], max[2]); + + AddFaceWithTextureScaled(newBrush2, ve_1, ve_2, ve_3, trimTexture, bSclTrimVert, bSclTrimHor, + max[1], min[2], min[1], max[2]); + } + else + { + AddFaceWithTextureScaled(newBrush1, v1, v3, v6, mainTexture, bSclMainVert, bSclMainHor, + min[1], min[2], max[1], max[2]); + AddFaceWithTextureScaled(newBrush1, v5, v2, v7, mainTexture, bSclMainVert, bSclMainHor, + max[1], min[2], min[1], max[2]); + + + AddFaceWithTextureScaled(newBrush2, v1, v3, v6, mainTexture, bSclMainVert, bSclMainHor, + min[1], min[2], max[1], max[2]); + AddFaceWithTextureScaled(newBrush2, v5, v2, v7, mainTexture, bSclMainVert, bSclMainHor, + max[1], min[2], min[1], max[2]); // flip max/min to reverse tex dir + + + AddFaceWithTextureScaled(newBrush1, ve_1, ve_2, ve_3, trimTexture, bSclTrimVert, bSclTrimHor, + min[0], min[2], max[0], max[2]); + + AddFaceWithTextureScaled(newBrush2, ve_3, ve_2, ve_1, trimTexture, bSclTrimVert, bSclTrimHor, + max[0], min[2], min[0], max[2]); + } + + //---------------------------------- + + + EntityClass* doorClass = GlobalEntityClassManager().findOrInsert("func_door", true); + NodeSmartReference pEDoor1(GlobalEntityCreator().createEntity(doorClass)); + NodeSmartReference pEDoor2(GlobalEntityCreator().createEntity(doorClass)); + + if(direction == 0) + { + Node_getEntity(pEDoor1)->setKeyValue("angle", "180"); + Node_getEntity(pEDoor2)->setKeyValue("angle", "360"); + } + else + { + Node_getEntity(pEDoor1)->setKeyValue("angle", "270"); + Node_getEntity(pEDoor2)->setKeyValue("angle", "90"); + } + + srand((unsigned)time(NULL)); + + char teamname[256]; + sprintf(teamname, "t%i", rand()); + Node_getEntity(pEDoor1)->setKeyValue("team", teamname); + Node_getEntity(pEDoor2)->setKeyValue("team", teamname); + + Node_getTraversable(pEDoor1)->insert(newBrush1); + Node_getTraversable(pEDoor2)->insert(newBrush2); + + Node_getTraversable(GlobalSceneGraph().root())->insert(pEDoor1); + Node_getTraversable(GlobalSceneGraph().root())->insert(pEDoor2); + +// ResetCurrentTexture(); +} + +//----------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------- + +void MakeBevel(vec3_t vMin, vec3_t vMax) +{ + NodeSmartReference patch(GlobalPatchCreator().createPatch()); + PatchControlMatrix matrix = GlobalPatchCreator().Patch_getControlPoints(patch); + + GlobalPatchCreator().Patch_setShader(patch, "textures/common/caulk"); + GlobalPatchCreator().Patch_resize(patch, 3, 3); + + vec3_t x_3, y_3, z_3; + x_3[0] = vMin[0]; x_3[1] = vMin[0]; x_3[2] = vMax[0]; + y_3[0] = vMin[1]; y_3[1] = vMax[1]; y_3[2] = vMax[1]; + z_3[0] = vMin[2]; z_3[1] = (vMax[2] + vMin[2])/2; z_3[2] = vMax[2]; + +/* x_3[0] = 0; x_3[1] = 0; x_3[2] = 64; + y_3[0] = 0; y_3[1] = 64; y_3[2] = 64; + z_3[0] = 0; z_3[1] = 32; z_3[2] = 64;*/ + + for(int i = 0; i < 3; i++) + { + for(int j = 0; j < 3; j++) + { + matrix(i, j).m_vertex[0] = x_3[i]; + matrix(i, j).m_vertex[1] = y_3[i]; + matrix(i, j).m_vertex[2] = z_3[j]; + } + } + + Node_getTraversable(GlobalRadiant().getMapWorldEntity())->insert(patch); +} + +void BuildCornerStairs(vec3_t vMin, vec3_t vMax, int nSteps, const char* mainTexture, const char* riserTex) +{ + vec3_t* topPoints = new vec3_t[nSteps+1]; + vec3_t* botPoints = new vec3_t[nSteps+1]; + + bool bFacesUse[6] = {true, true, false, true, false, false}; + + vec3_t centre; + VectorCopy(vMin, centre); + centre[0] = vMax[0]; + + int height = (int)(vMax[2] - vMin[2]) / nSteps; + + vec3_t vTop, vBot; + VectorCopy(vMax, vTop); + VectorCopy(vMin, vBot); + vTop[2] = vMin[2] + height; + + int i; + for(i = 0; i <= nSteps; i++) + { + VectorCopy(centre, topPoints[i]); + VectorCopy(centre, botPoints[i]); + + topPoints[i][2] = vMax[2]; + botPoints[i][2] = vMin[2]; + + topPoints[i][0] -= 10 * sinf( Q_PI * i / ( 2 * nSteps ) ); + topPoints[i][1] += 10 * cosf( Q_PI * i / ( 2 * nSteps ) ); + + botPoints[i][0] = topPoints[i][0]; + botPoints[i][1] = topPoints[i][1]; + } + + vec3_t tp[3]; + for(int j = 0; j < 3; j++) + VectorCopy(topPoints[j], tp[j]); + + for(i = 0; i < nSteps; i++) + { + scene::Node& brush = Build_Get_BoundingCube_Selective(vBot, vTop, "textures/common/caulk", bFacesUse); + + for(int j = 0; j < 3; j++) + tp[j][2] = vTop[2]; + + AddFaceWithTexture(brush, tp[2], tp[1], tp[0], mainTexture, false); + + AddFaceWithTexture(brush, centre, botPoints[i+1], topPoints[i+1], "textures/common/caulk", false); + AddFaceWithTexture(brush, centre, topPoints[i], botPoints[i], riserTex, false); + + Node_getTraversable(GlobalRadiant().getMapWorldEntity())->insert(brush); + + vTop[2] += height; + vBot[2] += height; + } + + delete[] topPoints; + delete[] botPoints; + + vMin[2] += height; + vMax[2] += height; + MakeBevel(vMin, vMax); +} diff --git a/contrib/bobtoolz/shapes.h b/contrib/bobtoolz/shapes.h new file mode 100644 index 00000000..0fa92659 --- /dev/null +++ b/contrib/bobtoolz/shapes.h @@ -0,0 +1,56 @@ +/* +BobToolz plugin for GtkRadiant +Copyright (C) 2001 Gordon Biggans + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// TODO: implement all this stuff via DBrush class. started with DShape +// TODO: Auto Face Scaling, no need to pass parms, calculated via brush. + +// Q3MAP stuff +#define FACE_DETAIL 0x8000000 + +// defines for polygon stuff +#define MAX_POLYGON_FACES 128 + +namespace scene +{ + class Node; +} + +#include "mathlib.h" + +// generic (detail added 12/01/01, for AC+) +void AddFaceWithTexture(scene::Node& brush, vec3_t va, vec3_t vb, vec3_t vc, const char* texture, bool detail); + +// ------------- +// ---caulked--- +// ------------- +void Build_Wedge(int dir, vec3_t min, vec3_t max, bool bUp); + +// -------------- +// ---textured--- +// -------------- +void BuildDoorsX2(vec3_t min, vec3_t max, bool bSclMainHor, bool bSclMainVert, bool bSclTrimHor, bool bSclTrimVert, const char* mainTexture, const char* trimTexture, int direction); +void Build_StairStep(vec3_t min, vec3_t max, const char* mainTexture, const char* riserTexture, int direction); +void Build_StairStep_Wedge(int dir, vec3_t min, vec3_t max, const char* mainTexture, const char* riserTexture, bool detail); +void BuildCornerStairs(vec3_t vMin, vec3_t vMax, int nSteps, const char* mainTexture, const char* riserTex); +// stairs stuff. + +//void Build_Prism_Border(vec3_t min, vec3_t max, int nSides, int nBorder, bool bAlignTop = FALSE); //moved to DShape +//void Build_Prism_Ordinary(vec3_t min, vec3_t max, int nSides, bool bAlignTop = FALSE); //moved to DShape +//void Build_Prism_Efficient(vec3_t min, vec3_t max, int nSides, bool bAlignTop = FALSE); //moved to DShape +// polygon stuff. diff --git a/contrib/bobtoolz/txt/changelog.txt b/contrib/bobtoolz/txt/changelog.txt new file mode 100644 index 00000000..777dc775 --- /dev/null +++ b/contrib/bobtoolz/txt/changelog.txt @@ -0,0 +1,96 @@ +BobToolz Changelog: +[=================] + +Changes in v1110 (GTK): +[=====================] + +djbob + +Added: +(16.05.01) + Impemented a little feature that highligths the q3map bug that is causing problems for autocaulk, (see readme) +(01.04.01) + Added DPatch class to implement patches, however radiant does not (currently) support adding patches to an entity via a plugin, so, its actually redundant atm, i was adding this to fix the patches being removed from entities probelm with dealing with entities outside of the worldspawn. + +Changes: +(02.04.01) + EPair keys and values can now be 128 characters long, not 64, happy now hydra? + Texture reseter works on patches now too. + +Removed: +(29.03.01) + Removed CTF colour changer, it is now in the ctftoolz. + +Fixed: +(02.04.01) + Worldspawn brushes are rebuilt per brush, rather than per entity in texture reseter. +(03.04.01) + Worldspawn brushes are rebuilt per brush, rather than per entity in brush cleanup. + +Changes in v1100 (GTK): +[=====================] + +djbob + +Added: +(24.03.01) + Added CTF colour changer for worldspawn+func_group. + +Changes: +(25.03.01) + Brought some functions over to using DMap class, allowing them to run on multiple entities, patches are still a no-no atm. + This includes: + + a) brush cleanup, will now fix problems on brushes that are not in the worldspawn, and rebuild the entitiy afterwards. + b) texture reseter will change textures on all brushes now, not just those in the worldspawn. + c) CTF colour changer will also work on brushes outside of the worldspawn entity. + +This introduces one prolem, patches in entities will no longer be part of the new entity, sorry for any trouble this causes, i will fix it soon hopefully. + +Fixes: +(25.03.01) + Fixed bug in DMap class that prevented it destroying brushes... thus enabling the class to work!!!! + +Changes in v1090 (GTK): +[=====================] + +djbob + +Added: +(22.03.01) + Added PitOMatic Function. + +Changes in v1080b (GTK): +[======================] + +djbob + +Fixes: + Removed some previously unnoticed porting introduced bugs. + +Added: + Polygon stuff now passes through my internal validation routines, no more phantom brushes will be made, or squiffy planes. + +Changes in v1080 (GTK): +[=====================] + +djbob + +Fixes: +(05.03.01) + Fixed line removed by rr2 in autocaulk. + Fixed Autocaulk not working with maps larger than the old grid. + +Added: +(04.03.01) + Added Changelog. + Added ability to align polygons so that top edge will be flat. (Request By Casey) +(05.03.01) + Added ability to change main texture for stairs. + +rr2do2 + +Changes: +(??.??.01) + Removed all traces of MFC from GTK version, the evil that it is ;] + diff --git a/contrib/bobtoolz/txt/readme.txt b/contrib/bobtoolz/txt/readme.txt new file mode 100644 index 00000000..5c0feddb --- /dev/null +++ b/contrib/bobtoolz/txt/readme.txt @@ -0,0 +1,77 @@ +BobToolz GTK: v1100 +[=================] + +Date: +[===] +16.05.01 + +The multipurpose Quake3 Mappers Tool +[==================================] + +Author: djbob +Other work: q3terra, ctftoolz, EECA mod + +eMail: gbiggans@uglab.eee.strath.ac.uk (NO SPAM thank u very much :P) + +www: www.planetquake.com/toolz + www.planetquake.com/eeca + +IRC: #freepq irc.fdf.net + #qeradiant irc.telefragged.com + +Files Contained In This Package: +[==============================] + +Readme.txt --- This file. +Changelog.txt --- Version information. +bobToolz.dll --- The plugin itsself. +bt/*.txt --- A few text files required by the plugin. + + +What's a boy to do? +[=================] + +Put The plugin in your GTKRadiant plugins folder: +e.g. mine: c:\gamez\quake3\GTKRadiant\plugins + +Run Radiant, and select the toolz from the dropdown plugin menu. + +Help is available in the form of a manual on my site. + + + +For the new q3map bug highlighting feature, run autocaulk-build mini prt, then run autocaulk. +identify an area which has been caulked incorrectly, then reopen the map, select the q3map bug highlight option, and wait. +once the selected brushes appear, u have 2 choices, + +a) press i and remove all the original brushes, or +b) keep going with both sets (slower, but more useful) + +navigate to the problem region, you should find that the set of inverse brushes that has been built is missing a brush +at the problem area, i suppose the next problem is finding out why :] + +this function is mainly provided for other coders to show the problem, and is probably of little use to anyone else, +but u never know. + + +Coming Soon: +[==========] +Region area saving. perhaps, if i get the time to finish it. + + +Testing, Feedback & Ideas: +[========================] + + Akuma, Casey, Cartman2K, maverik, rayden, nephilim_goth and god knows who else. + Thx to you all. + +Thanx: +[====] + + ttimo, da man :] + spog, for his often baffling advice.... and questions.... + Thx to rr2do2, for his linux conversion, and removing the "evil" (his words :]) MFC code. + Thx to RKone, for improving my q3w sig. + Azr for giving me ops in #qeradiant, k3wl :] + Everyone at the Quake3World Forums, I think of you all as my little worshippers :P + id Software, of course. \ No newline at end of file diff --git a/contrib/bobtoolz/visfind.cpp b/contrib/bobtoolz/visfind.cpp new file mode 100644 index 00000000..e5b890b1 --- /dev/null +++ b/contrib/bobtoolz/visfind.cpp @@ -0,0 +1,247 @@ +// Requries parts of the q3 tools source to compile +// Date: Oct 5, 2001 +// Written by: Brad Whitehead (whiteheb@gamerstv.net) + +#include "visfind.h" +#include "dialogs/dialogs-gtk.h" +#include "DWinding.h" +#include "bsploader.h" + +#include + +typedef struct { + int portalclusters; + int leafbytes; //leafbytes = ((portalclusters+63)&~63)>>3; +} vis_header; + +// added because int shift = 32; i = 0xFFFFFFFF >> shift; +// then i = 0xFFFFFFFF, when it should = 0 +const unsigned long bitmasks[33] = +{ + 0x00000000, + 0x00000001, 0x00000003, 0x00000007, 0x0000000F, + 0x0000001F, 0x0000003F, 0x0000007F, 0x000000FF, + 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF, + 0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF, + 0x0001FFFF, 0x0003FFFF, 0x0007FFFF, 0x000FFFFF, + 0x001FFFFF, 0x003FFFFF, 0x007FFFFF, 0x00FFFFFF, + 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, 0x0FFFFFFF, + 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF +}; + +int bsp_leafnumfororigin(vec3_t origin) +{ + dnode_t *node; + dplane_t *plane; + float d; + + // TODO: check if origin is in the map?? + + node = dnodes; + while (true) + { + plane = &dplanes[node->planeNum]; + d = DotProduct (origin, plane->normal) - plane->dist; + if ( d >= 0 ) + if ( node->children[0] < 0 ) + return -(node->children[0]+1); + else + node = &dnodes[node->children[0]]; + else + if ( node->children[1] < 0 ) + return -(node->children[1]+1); + else + node = &dnodes[node->children[1]]; + } + return 0; +} + +int bsp_leafnumforcluster(int cluster) +{ + dleaf_t *l; + int i; + + for ( i = 0, l = dleafs; i < numleafs; i++, l++ ) + if ( l->cluster == cluster ) return(i); + return(0); +} + +// leaf1 = origin leaf +// leaf2 = leaf to test for +/*int bsp_InPVS(int cluster1, int cluster2) +{ + vis_header *vheader; + byte *visdata; + + vheader = (vis_header *) visBytes; + visdata = visBytes + VIS_HEADER_SIZE; + + return( *( visdata + ( cluster1 * vheader->leafbytes ) + (cluster2 / 8) ) & ( 1 << ( cluster2 % 8 ) ) ); +}*/ + +void bsp_setbitvectorlength( byte *v, int length_bits, int length_vector ) +{ + int i; + + i = length_bits/8; + + *(v+i) = (byte) bitmasks[length_bits % 8]; + + memset((v+i+1), 0, length_vector-i-1); +} + + +void bsp_bitvectorsubtract(byte *first, byte *second, byte *out, int length) +{ + + int i; + + for ( i = 0; i < length; i++ ) + *(out+i) = *(first+i) & ~(*(second+i)); +} + +int bsp_countclusters(byte *bitvector, int length) +{ + int i, j, c; + + c = 0; + for ( i = 0; i < length; i++ ) + for ( j = 0; j < 8; j++ ) + if ( (*(bitvector+i) & (1 << j)) ) c++; + return(c); +} + +int bsp_countclusters_mask(byte *bitvector, byte *maskvector, int length) +{ + int i, j, c; + + c = 0; + for ( i = 0; i < length; i++ ) + for ( j = 0; j < 8; j++ ) + if ( (*(bitvector+i) & (1 << j)) && (*(maskvector+i) & (1 << j)) ) c++; + return(c); +} + +void AddCluster(std::list *pointlist, dleaf_t *cl, bool* repeatlist, vec3_t clr) +{ + DWinding* w; + + int* leafsurf = &dleafsurfaces[cl->firstLeafSurface]; + for(int k = 0; k < cl->numLeafSurfaces; k++, leafsurf++) + { + if(repeatlist[*leafsurf]) + continue; + + dsurface_t* surf = &drawSurfaces[*leafsurf]; + if(surf->surfaceType != MST_PLANAR) + continue; + + qdrawVert_t* vert = &drawVerts[surf->firstVert]; + if(surf->firstVert + surf->numVerts > numDrawVerts) + DoMessageBox("Warning", "Warning", eMB_OK); + + w = new DWinding(); + w->AllocWinding(surf->numVerts); + + for (int l = 0; l < surf->numVerts; l++, vert++) + { + (w->p[l])[0] = vert->xyz[0]; + (w->p[l])[1] = vert->xyz[1]; + (w->p[l])[2] = vert->xyz[2]; + + w->clr[0] = clr[0]; + w->clr[1] = clr[1]; + w->clr[2] = clr[2]; + } + pointlist->push_back(w); + + repeatlist[*leafsurf] = true; + } +} + +/* +============= +CreateTrace +============= +*/ +std::list *CreateTrace( dleaf_t *leaf, int c, vis_header *header, byte *visdata, byte *seen ) +{ + byte *vis; + int i, j, clusterNum; + std::list *pointlist = new std::list; + bool* repeatlist = new bool[numDrawSurfaces]; + dleaf_t *cl; + + vec3_t clrRnd[5] = { + {0.f, 0.f, 1.f}, + {0.f, 1.f, 1.f}, + {1.f, 0.f, 0.f}, + {1.f, 0.f, 1.f}, + {1.f, 1.f, 0.f}, + }; + + vec3_t clrGreen = {0.f, 1.f, 0.f}; + + memset(repeatlist, 0, sizeof(bool)*numDrawSurfaces); + + vis = visdata + ( c * header->leafbytes ); + + clusterNum = 0; + + AddCluster(pointlist, &(dleafs[bsp_leafnumforcluster( c )]), repeatlist, clrGreen); + + for ( i = 0; i < header->leafbytes; i++ ) + { + for ( j = 0; j < 8; j++ ) + { + cl = &(dleafs[bsp_leafnumforcluster( clusterNum )]); + + if ( ( *(vis + i) & (1 << j) ) && (*(seen+i) & (1 << j)) && (leaf->area == cl->area)) + AddCluster(pointlist, cl, repeatlist, clrRnd[rand()%5]); + clusterNum++; + } + } + + delete repeatlist; + + return pointlist; +} + +/* +============= +TraceCluster + +setup for CreateTrace +============= +*/ +std::list *TraceCluster (int leafnum) +{ + byte seen[(MAX_MAP_LEAFS/8) + 1]; + vis_header *vheader; + byte *visdata; + dleaf_t *leaf; + + vheader = (vis_header *) visBytes; + visdata = visBytes + sizeof(vis_header); + + memset(seen, 0xFF, sizeof(seen)); + bsp_setbitvectorlength(seen, vheader->portalclusters, sizeof(seen)); + + leaf = &(dleafs[leafnum]); + + return CreateTrace(leaf, leaf->cluster, vheader, visdata, seen); +} + +std::list* BuildTrace(char* filename, vec3_t v_origin) +{ + if(!LoadBSPFile(filename)) + return NULL; + + int leafnum = bsp_leafnumfororigin(v_origin); + + std::list *pointlist = TraceCluster(leafnum); + + FreeBSPData(); + + return pointlist; +} diff --git a/contrib/bobtoolz/visfind.h b/contrib/bobtoolz/visfind.h new file mode 100644 index 00000000..3476d864 --- /dev/null +++ b/contrib/bobtoolz/visfind.h @@ -0,0 +1,7 @@ + +#include +#include "mathlib.h" + +class DWinding; + +std::list *BuildTrace(char* filename, vec3_t v_origin); diff --git a/contrib/brushexport/brushexport.def b/contrib/brushexport/brushexport.def new file mode 100644 index 00000000..44d97f3f --- /dev/null +++ b/contrib/brushexport/brushexport.def @@ -0,0 +1,7 @@ +; brushexport.def : Declares the module parameters for the DLL. + +LIBRARY "BRUSHEXPORT" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/contrib/brushexport/brushexport.vcproj b/contrib/brushexport/brushexport.vcproj new file mode 100644 index 00000000..23e77444 --- /dev/null +++ b/contrib/brushexport/brushexport.vcproj @@ -0,0 +1,257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/brushexport/callbacks.cpp b/contrib/brushexport/callbacks.cpp new file mode 100644 index 00000000..ab6396ee --- /dev/null +++ b/contrib/brushexport/callbacks.cpp @@ -0,0 +1,148 @@ +#include +#include +#include + +#include "qerplugin.h" +#include "debugging/debugging.h" +#include "support.h" +#include "export.h" + +// stuff from interface.cpp +void DestroyWindow(); + + +namespace callbacks { + +void OnDestroy(GtkWidget* w, gpointer data) +{ + DestroyWindow(); +} + +void OnExportClicked(GtkButton* button, gpointer user_data) +{ + GtkWidget* window = lookup_widget(GTK_WIDGET(button), "w_plugplug2"); + ASSERT_NOTNULL(window); + const char* cpath = GlobalRadiant().m_pfnFileDialog(window, false, "Save as Obj", 0, 0); + if(!cpath) + return; + + std::string path(cpath); + + // get ignore list from ui + std::set ignore; + + GtkTreeView* view = GTK_TREE_VIEW(lookup_widget(GTK_WIDGET(button), "t_materialist")); + GtkListStore* list = GTK_LIST_STORE(gtk_tree_view_get_model(view)); + + GtkTreeIter iter; + gboolean valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(list), &iter); + while(valid) + { + gchar* data; + gtk_tree_model_get(GTK_TREE_MODEL(list), &iter, 0, &data, -1); + globalOutputStream() << data << "\n"; + ignore.insert(std::string(data)); + g_free(data); + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL(list), &iter); + } + + for(std::set::iterator it(ignore.begin()); it != ignore.end(); ++it) + globalOutputStream() << it->c_str() << "\n"; + + // collapse mode + collapsemode mode = COLLAPSE_NONE; + + GtkWidget* radio = lookup_widget(GTK_WIDGET(button), "r_collapse"); + ASSERT_NOTNULL(radio); + + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio))) + mode = COLLAPSE_ALL; + else + { + radio = lookup_widget(GTK_WIDGET(button), "r_collapsebymaterial"); + ASSERT_NOTNULL(radio); + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio))) + mode = COLLAPSE_BY_MATERIAL; + else + { + radio = lookup_widget(GTK_WIDGET(button), "r_nocollapse"); + ASSERT_NOTNULL(radio); + ASSERT_NOTNULL(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio))); + mode = COLLAPSE_NONE; + } + } + + // export materials? + GtkWidget* toggle = lookup_widget(GTK_WIDGET(button), "t_exportmaterials"); + ASSERT_NOTNULL(toggle); + + bool exportmat = FALSE; + + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle))) + exportmat = TRUE; + + // limit material names? + toggle = lookup_widget(GTK_WIDGET(button), "t_limitmatnames"); + ASSERT_NOTNULL(toggle); + + bool limitMatNames = FALSE; + + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle)) && exportmat) + limitMatNames = TRUE; + + // create objects instead of groups? + toggle = lookup_widget(GTK_WIDGET(button), "t_objects"); + ASSERT_NOTNULL(toggle); + + bool objects = FALSE; + + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle)) && exportmat) + objects = TRUE; + + // export + ExportSelection(ignore, mode, exportmat, path, limitMatNames, objects); +} + +void OnAddMaterial(GtkButton* button, gpointer user_data) +{ + GtkEntry* edit = GTK_ENTRY(lookup_widget(GTK_WIDGET(button), "ed_materialname")); + ASSERT_NOTNULL(edit); + + const gchar* name = gtk_entry_get_text(edit); + if(g_utf8_strlen(name, -1) > 0) + { + GtkListStore* list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(lookup_widget(GTK_WIDGET(button), "t_materialist")))); + GtkTreeIter iter; + gtk_list_store_append(list, &iter); + gtk_list_store_set(list, &iter, 0, name, -1); + gtk_entry_set_text(edit, ""); + } +} + +void OnRemoveMaterial(GtkButton* button, gpointer user_data) +{ + GtkTreeView* view = GTK_TREE_VIEW(lookup_widget(GTK_WIDGET(button), "t_materialist")); + GtkListStore* list = GTK_LIST_STORE(gtk_tree_view_get_model(view)); + GtkTreeSelection* sel = gtk_tree_view_get_selection(view); + + GtkTreeIter iter; + if(gtk_tree_selection_get_selected(sel, 0, &iter)) + gtk_list_store_remove(list, &iter); +} + +void OnExportMatClicked(GtkButton* button, gpointer user_data) +{ + GtkWidget* toggleLimit = lookup_widget(GTK_WIDGET(button), "t_limitmatnames"); + GtkWidget* toggleObject = lookup_widget(GTK_WIDGET(button), "t_objects"); + + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) + { + gtk_widget_set_sensitive(GTK_WIDGET(toggleLimit), TRUE); + gtk_widget_set_sensitive(GTK_WIDGET(toggleObject), TRUE); + } else { + gtk_widget_set_sensitive(GTK_WIDGET(toggleLimit), FALSE); + gtk_widget_set_sensitive(GTK_WIDGET(toggleObject), FALSE); + } +} + +}// callbacks diff --git a/contrib/brushexport/callbacks.h b/contrib/brushexport/callbacks.h new file mode 100644 index 00000000..9bf95684 --- /dev/null +++ b/contrib/brushexport/callbacks.h @@ -0,0 +1,12 @@ +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkButton GtkButton; + +namespace callbacks { + +void OnDestroy(GtkWidget*, gpointer); +void OnExportClicked(GtkButton*, gpointer); +void OnAddMaterial(GtkButton*, gpointer); +void OnRemoveMaterial(GtkButton*, gpointer); +void OnExportMatClicked(GtkButton* button, gpointer); + +}// callbacks diff --git a/contrib/brushexport/export.cpp b/contrib/brushexport/export.cpp new file mode 100644 index 00000000..a0964fbb --- /dev/null +++ b/contrib/brushexport/export.cpp @@ -0,0 +1,373 @@ +#include "export.h" +#include "debugging/debugging.h" +#include "ibrush.h" +#include "iscenegraph.h" +#include "iselection.h" +#include "stream/stringstream.h" +#include "stream/textfilestream.h" + +#include + +// this is very evil, but right now there is no better way +#include "../../radiant/brush.h" + +/* + Abstract baseclass for modelexporters + the class collects all the data which then gets + exported through the WriteToFile method. +*/ +class ExportData +{ +public: + ExportData(const std::set& ignorelist, collapsemode mode, bool limNames, bool objs); + virtual ~ExportData(void); + + virtual void BeginBrush(Brush& b); + virtual void AddBrushFace(Face& f); + virtual void EndBrush(void); + + virtual bool WriteToFile(const std::string& path, collapsemode mode) const = 0; + +protected: + + // a group of faces + class group + { + public: + std::string name; + std::list faces; + }; + + std::list groups; + +private: + + // "textures/common/caulk" -> "caulk" + void GetShaderNameFromShaderPath(const char* path, std::string& name); + + group* current; + collapsemode mode; + const std::set& ignorelist; +}; + +ExportData::ExportData(const std::set& _ignorelist, collapsemode _mode, bool _limNames, bool _objs) + : mode(_mode), + ignorelist(_ignorelist) +{ + current = 0; + + // in this mode, we need just one group + if(mode == COLLAPSE_ALL) + { + groups.push_back(group()); + current = &groups.back(); + current->name = "all"; + } +} + +ExportData::~ExportData(void) +{ + +} + +void ExportData::BeginBrush(Brush& b) +{ + // create a new group for each brush + if(mode == COLLAPSE_NONE) + { + groups.push_back(group()); + current = &groups.back(); + + StringOutputStream str(256); + str << "Brush" << (const unsigned int)groups.size(); + current->name = str.c_str(); + } +} + +void ExportData::EndBrush(void) +{ + // all faces of this brush were on the ignorelist, discard the emptygroup + if(mode == COLLAPSE_NONE) + { + ASSERT_NOTNULL(current); + if(current->faces.empty()) + { + groups.pop_back(); + current = 0; + } + } +} + +void ExportData::AddBrushFace(Face& f) +{ + std::string shadername; + GetShaderNameFromShaderPath(f.GetShader(), shadername); + + // ignore faces from ignore list + if(ignorelist.find(shadername) != ignorelist.end()) + return; + + if(mode == COLLAPSE_BY_MATERIAL) + { + // find a group for this material + current = 0; + const std::list::iterator end(groups.end()); + for(std::list::iterator it(groups.begin()); it != end; ++it) + { + if(it->name == shadername) + current = &(*it); + } + + // no group found, create one + if(!current) + { + groups.push_back(group()); + current = &groups.back(); + current->name = shadername; + } + } + + ASSERT_NOTNULL(current); + + // add face to current group + current->faces.push_back(&f); + +#ifdef _DEBUG + globalOutputStream() << "Added Face to group " << current->name.c_str() << "\n"; +#endif +} + +void ExportData::GetShaderNameFromShaderPath(const char* path, std::string& name) +{ + std::string tmp(path); + + size_t last_slash = tmp.find_last_of("/"); + + if(last_slash != std::string::npos && last_slash == (tmp.length() - 1)) + name = path; + else + name = tmp.substr(last_slash + 1, tmp.length() - last_slash); + +#ifdef _DEBUG + globalOutputStream() << "Last: " << last_slash << " " << "length: " << (const unsigned int)tmp.length() << "Name: " << name.c_str() << "\n"; +#endif +} + +/* + Exporter writing facedata as wavefront object +*/ +class ExportDataAsWavefront : public ExportData +{ +private: + bool expmat; + bool limNames; + bool objs; + +public: + ExportDataAsWavefront(const std::set& _ignorelist, collapsemode _mode, bool _expmat, bool _limNames, bool _objs) + : ExportData(_ignorelist, _mode, _limNames, _objs) + { + expmat = _expmat; + limNames = _limNames; + objs = _objs; + } + + bool WriteToFile(const std::string& path, collapsemode mode) const; +}; + +bool ExportDataAsWavefront::WriteToFile(const std::string& path, collapsemode mode) const +{ + std::string objFile = path.substr(0, path.length() -4) + ".obj"; + std::string mtlFile = path.substr(0, path.length() -4) + ".mtl"; + + std::set materials; + + TextFileOutputStream out(objFile.c_str()); + + if(out.failed()) + { + globalErrorStream() << "Unable to open file\n"; + return false; + } + + out << "# Wavefront Objectfile exported with radiants brushexport plugin 3.0 by Thomas 'namespace' Nitschke, spam@codecreator.net\n\n"; + + if(expmat) + { + size_t last = mtlFile.find_last_of("//"); + std::string mtllib = mtlFile.substr(last + 1, mtlFile.size() - last).c_str(); + out << "mtllib " << mtllib.c_str() << "\n"; + } + + unsigned int vertex_count = 0; + + const std::list::const_iterator gend(groups.end()); + for(std::list::const_iterator git(groups.begin()); git != gend; ++git) + { + typedef std::multimap bm; + bm brushMaterials; + typedef std::pair String_Pair; + + const std::list::const_iterator end(git->faces.end()); + + // submesh starts here + if(objs) + { + out << "\no "; + } else { + out << "\ng "; + } + out << git->name.c_str() << "\n"; + + // material + if(expmat && mode == COLLAPSE_ALL) + { + out << "usemtl material" << "\n\n"; + materials.insert("material"); + } + + for(std::list::const_iterator it(git->faces.begin()); it != end; ++it) + { + const Winding& w((*it)->getWinding()); + + // vertices + for(size_t i = 0; i < w.numpoints; ++i) + out << "v " << FloatFormat(w[i].vertex.x(), 1, 6) << " " << FloatFormat(w[i].vertex.z(), 1, 6) << " " << FloatFormat(w[i].vertex.y(), 1, 6) << "\n"; + } + out << "\n"; + + for(std::list::const_iterator it(git->faces.begin()); it != end; ++it) + { + const Winding& w((*it)->getWinding()); + + // texcoords + for(size_t i = 0; i < w.numpoints; ++i) + out << "vt " << FloatFormat(w[i].texcoord.x(), 1, 6) << " " << FloatFormat(w[i].texcoord.y(), 1, 6) << "\n"; + } + + for(std::list::const_iterator it(git->faces.begin()); it != end; ++it) + { + const Winding& w((*it)->getWinding()); + + // faces + StringOutputStream faceLine(256); + faceLine << "\nf"; + for(size_t i = 0; i < w.numpoints; ++i, ++vertex_count) + { + faceLine << " " << vertex_count+1 << "/" << vertex_count+1; + } + + if(mode != COLLAPSE_ALL) + { + materials.insert((*it)->getShader().getShader()); + brushMaterials.insert(String_Pair((*it)->getShader().getShader(), faceLine.c_str())); + } else { + out << faceLine.c_str(); + } + } + + if(mode != COLLAPSE_ALL) + { + std::string lastMat; + std::string mat; + std::string faces; + + for(bm::iterator iter = brushMaterials.begin(); iter != brushMaterials.end(); iter++) + { + mat = (*iter).first.c_str(); + faces = (*iter).second.c_str(); + + if(mat != lastMat) + { + if(limNames && mat.size() > 20) + { + out << "\nusemtl " << mat.substr(mat.size() - 20, mat.size()).c_str(); + } else { + out << "\nusemtl " << mat.c_str(); + } + } + + out << faces.c_str(); + lastMat = mat; + } + } + + out << "\n"; + } + + if(expmat) + { + TextFileOutputStream outMtl(mtlFile.c_str()); + if(outMtl.failed()) + { + globalErrorStream() << "Unable to open material file\n"; + return false; + } + + outMtl << "# Wavefront material file exported with GtkRadiants brushexport plugin.\n"; + outMtl << "# Material Count: " << (const Unsigned)materials.size() << "\n\n"; + for(std::set::const_iterator it(materials.begin()); it != materials.end(); ++it) + { + if(limNames && it->size() > 20) + { + outMtl << "newmtl " << it->substr(it->size() - 20, it->size()).c_str() << "\n"; + } else { + outMtl << "newmtl " << it->c_str() << "\n"; + } + } + } + + return true; +} + + +class ForEachFace : public BrushVisitor +{ +public: + ForEachFace(ExportData& _exporter) + : exporter(_exporter) + {} + + void visit(Face& face) const + { + exporter.AddBrushFace(face); + } + +private: + ExportData& exporter; +}; + +class ForEachSelected : public SelectionSystem::Visitor +{ +public: + ForEachSelected(ExportData& _exporter) + : exporter(_exporter) + {} + + void visit(scene::Instance& instance) const + { + BrushInstance* bptr = InstanceTypeCast::cast(instance); + if(bptr) + { + Brush& brush(bptr->getBrush()); + + exporter.BeginBrush(brush); + ForEachFace face_vis(exporter); + brush.forEachFace(face_vis); + exporter.EndBrush(); + } + } + +private: + ExportData& exporter; +}; + +bool ExportSelection(const std::set& ignorelist, collapsemode m, bool exmat, const std::string& path, bool limNames, bool objs) +{ + ExportDataAsWavefront exporter(ignorelist, m, exmat, limNames, objs); + + ForEachSelected vis(exporter); + GlobalSelectionSystem().foreachSelected(vis); + + return exporter.WriteToFile(path, m); +} diff --git a/contrib/brushexport/export.h b/contrib/brushexport/export.h new file mode 100644 index 00000000..3482bbed --- /dev/null +++ b/contrib/brushexport/export.h @@ -0,0 +1,15 @@ +#ifndef EXPORT_H +#define EXPORT_H +#include +#include + +enum collapsemode +{ + COLLAPSE_ALL, + COLLAPSE_BY_MATERIAL, + COLLAPSE_NONE +}; + +bool ExportSelection(const std::set& ignorelist, collapsemode m, bool exmat, const std::string& path, bool limitMatNames, bool objects); + +#endif diff --git a/contrib/brushexport/interface.cpp b/contrib/brushexport/interface.cpp new file mode 100644 index 00000000..6525e201 --- /dev/null +++ b/contrib/brushexport/interface.cpp @@ -0,0 +1,241 @@ +#include +#include + +#include "debugging/debugging.h" +#include "callbacks.h" +#include "support.h" + +#define GLADE_HOOKUP_OBJECT(component,widget,name) \ + g_object_set_data_full (G_OBJECT (component), name, \ + gtk_widget_ref (widget), (GDestroyNotify) gtk_widget_unref) + +#define GLADE_HOOKUP_OBJECT_NO_REF(component,widget,name) \ + g_object_set_data (G_OBJECT (component), name, widget) + +// created by glade +GtkWidget* +create_w_plugplug2 (void) +{ + GtkWidget *w_plugplug2; + GtkWidget *vbox1; + GtkWidget *hbox2; + GtkWidget *vbox4; + GtkWidget *r_collapse; + GSList *r_collapse_group = NULL; + GtkWidget *r_collapsebymaterial; + GtkWidget *r_nocollapse; + GtkWidget *vbox3; + GtkWidget *b_export; + GtkWidget *b_close; + GtkWidget *vbox2; + GtkWidget *label1; + GtkWidget *scrolledwindow1; + GtkWidget *t_materialist; + GtkWidget *ed_materialname; + GtkWidget *hbox1; + GtkWidget *b_addmaterial; + GtkWidget *b_removematerial; + GtkWidget *t_exportmaterials; + GtkWidget *t_limitmatnames; + GtkWidget *t_objects; + GtkTooltips *tooltips; + + tooltips = gtk_tooltips_new(); + + w_plugplug2 = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_set_name (w_plugplug2, "w_plugplug2"); + gtk_window_set_title (GTK_WINDOW (w_plugplug2), "BrushExport-Plugin 3.0 by namespace"); + gtk_window_set_position (GTK_WINDOW (w_plugplug2), GTK_WIN_POS_CENTER); + gtk_window_set_destroy_with_parent (GTK_WINDOW (w_plugplug2), TRUE); + + vbox1 = gtk_vbox_new (FALSE, 0); + gtk_widget_set_name (vbox1, "vbox1"); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (w_plugplug2), vbox1); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), 5); + + hbox2 = gtk_hbox_new (TRUE, 5); + gtk_widget_set_name (hbox2, "hbox2"); + gtk_widget_show (hbox2); + gtk_box_pack_start (GTK_BOX (vbox1), hbox2, FALSE, FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (hbox2), 5); + + vbox4 = gtk_vbox_new (TRUE, 0); + gtk_widget_set_name (vbox4, "vbox4"); + gtk_widget_show (vbox4); + gtk_box_pack_start (GTK_BOX (hbox2), vbox4, TRUE, FALSE, 0); + + r_collapse = gtk_radio_button_new_with_mnemonic (NULL, "Collapse mesh"); + gtk_widget_set_name (r_collapse, "r_collapse"); + gtk_tooltips_set_tip (GTK_TOOLTIPS(tooltips), r_collapse, "Collapse all brushes into a single group", "Collapse all brushes into a single group"); + gtk_widget_show (r_collapse); + gtk_box_pack_start (GTK_BOX (vbox4), r_collapse, FALSE, FALSE, 0); + gtk_radio_button_set_group (GTK_RADIO_BUTTON (r_collapse), r_collapse_group); + r_collapse_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (r_collapse)); + + r_collapsebymaterial = gtk_radio_button_new_with_mnemonic (NULL, "Collapse by material"); + gtk_widget_set_name (r_collapsebymaterial, "r_collapsebymaterial"); + gtk_tooltips_set_tip (GTK_TOOLTIPS(tooltips), r_collapsebymaterial, "Collapse into groups by material", "Collapse into groups by material"); + gtk_widget_show (r_collapsebymaterial); + gtk_box_pack_start (GTK_BOX (vbox4), r_collapsebymaterial, FALSE, FALSE, 0); + gtk_radio_button_set_group (GTK_RADIO_BUTTON (r_collapsebymaterial), r_collapse_group); + r_collapse_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (r_collapsebymaterial)); + + r_nocollapse = gtk_radio_button_new_with_mnemonic (NULL, "Don't collapse"); + gtk_widget_set_name (r_nocollapse, "r_nocollapse"); + gtk_tooltips_set_tip (GTK_TOOLTIPS(tooltips), r_nocollapse, "Every brush is stored in its own group", "Every brush is stored in its own group"); + gtk_widget_show (r_nocollapse); + gtk_box_pack_start (GTK_BOX (vbox4), r_nocollapse, FALSE, FALSE, 0); + gtk_radio_button_set_group (GTK_RADIO_BUTTON (r_nocollapse), r_collapse_group); + r_collapse_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (r_nocollapse)); + + vbox3 = gtk_vbox_new (FALSE, 0); + gtk_widget_set_name (vbox3, "vbox3"); + gtk_widget_show (vbox3); + gtk_box_pack_start (GTK_BOX (hbox2), vbox3, FALSE, FALSE, 0); + + b_export = gtk_button_new_from_stock ("gtk-save"); + gtk_widget_set_name (b_export, "b_export"); + gtk_widget_show (b_export); + gtk_box_pack_start (GTK_BOX (vbox3), b_export, TRUE, FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (b_export), 5); + + b_close = gtk_button_new_from_stock ("gtk-cancel"); + gtk_widget_set_name (b_close, "b_close"); + gtk_widget_show (b_close); + gtk_box_pack_start (GTK_BOX (vbox3), b_close, TRUE, FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (b_close), 5); + + vbox2 = gtk_vbox_new (FALSE, 5); + gtk_widget_set_name (vbox2, "vbox2"); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (vbox1), vbox2, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 2); + + label1 = gtk_label_new ("Ignored materials:"); + gtk_widget_set_name (label1, "label1"); + gtk_widget_show (label1); + gtk_box_pack_start (GTK_BOX (vbox2), label1, FALSE, FALSE, 0); + + scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_name (scrolledwindow1, "scrolledwindow1"); + gtk_widget_show (scrolledwindow1); + gtk_box_pack_start (GTK_BOX (vbox2), scrolledwindow1, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow1), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow1), GTK_SHADOW_IN); + + t_materialist = gtk_tree_view_new (); + gtk_widget_set_name (t_materialist, "t_materialist"); + gtk_widget_show (t_materialist); + gtk_container_add (GTK_CONTAINER (scrolledwindow1), t_materialist); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (t_materialist), FALSE); + gtk_tree_view_set_enable_search (GTK_TREE_VIEW (t_materialist), FALSE); + + ed_materialname = gtk_entry_new (); + gtk_widget_set_name (ed_materialname, "ed_materialname"); + gtk_widget_show (ed_materialname); + gtk_box_pack_start (GTK_BOX (vbox2), ed_materialname, FALSE, FALSE, 0); + + hbox1 = gtk_hbox_new (TRUE, 0); + gtk_widget_set_name (hbox1, "hbox1"); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox2), hbox1, FALSE, FALSE, 0); + + b_addmaterial = gtk_button_new_from_stock ("gtk-add"); + gtk_widget_set_name (b_addmaterial, "b_addmaterial"); + gtk_widget_show (b_addmaterial); + gtk_box_pack_start (GTK_BOX (hbox1), b_addmaterial, FALSE, FALSE, 0); + + b_removematerial = gtk_button_new_from_stock ("gtk-remove"); + gtk_widget_set_name (b_removematerial, "b_removematerial"); + gtk_widget_show (b_removematerial); + gtk_box_pack_start (GTK_BOX (hbox1), b_removematerial, FALSE, FALSE, 0); + + t_limitmatnames = gtk_check_button_new_with_mnemonic ("Use short material names (max. 20 chars)"); + gtk_widget_set_name (t_limitmatnames, "t_limitmatnames"); + gtk_widget_show (t_limitmatnames); + gtk_box_pack_end (GTK_BOX (vbox2), t_limitmatnames, FALSE, FALSE, 0); + + t_objects = gtk_check_button_new_with_mnemonic ("Create (o)bjects instead of (g)roups"); + gtk_widget_set_name (t_objects, "t_objects"); + gtk_widget_show (t_objects); + gtk_box_pack_end (GTK_BOX (vbox2), t_objects, FALSE, FALSE, 0); + + t_exportmaterials = gtk_check_button_new_with_mnemonic ("Create material information (.mtl file)"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(t_exportmaterials), true); + gtk_widget_set_name (t_exportmaterials, "t_exportmaterials"); + gtk_widget_show (t_exportmaterials); + gtk_box_pack_end (GTK_BOX (vbox2), t_exportmaterials, FALSE, FALSE, 10); + + using namespace callbacks; + g_signal_connect(G_OBJECT(w_plugplug2), "destroy", G_CALLBACK(OnDestroy), NULL); + g_signal_connect_swapped(G_OBJECT(b_close), "clicked", G_CALLBACK (OnDestroy), NULL); + + g_signal_connect ((gpointer) b_export, "clicked", G_CALLBACK (OnExportClicked), NULL); + g_signal_connect ((gpointer) b_addmaterial, "clicked", G_CALLBACK (OnAddMaterial), NULL); + g_signal_connect ((gpointer) b_removematerial, "clicked", G_CALLBACK (OnRemoveMaterial), NULL); + g_signal_connect ((gpointer) t_exportmaterials, "clicked", G_CALLBACK (OnExportMatClicked), NULL); + + /* Store pointers to all widgets, for use by lookup_widget(). */ + GLADE_HOOKUP_OBJECT_NO_REF (w_plugplug2, w_plugplug2, "w_plugplug2"); + GLADE_HOOKUP_OBJECT (w_plugplug2, vbox1, "vbox1"); + GLADE_HOOKUP_OBJECT (w_plugplug2, hbox2, "hbox2"); + GLADE_HOOKUP_OBJECT (w_plugplug2, vbox4, "vbox4"); + GLADE_HOOKUP_OBJECT (w_plugplug2, r_collapse, "r_collapse"); + GLADE_HOOKUP_OBJECT (w_plugplug2, r_collapsebymaterial, "r_collapsebymaterial"); + GLADE_HOOKUP_OBJECT (w_plugplug2, r_nocollapse, "r_nocollapse"); + GLADE_HOOKUP_OBJECT (w_plugplug2, vbox3, "vbox3"); + GLADE_HOOKUP_OBJECT (w_plugplug2, b_export, "b_export"); + GLADE_HOOKUP_OBJECT (w_plugplug2, b_close, "b_close"); + GLADE_HOOKUP_OBJECT (w_plugplug2, vbox2, "vbox2"); + GLADE_HOOKUP_OBJECT (w_plugplug2, label1, "label1"); + GLADE_HOOKUP_OBJECT (w_plugplug2, scrolledwindow1, "scrolledwindow1"); + GLADE_HOOKUP_OBJECT (w_plugplug2, t_materialist, "t_materialist"); + GLADE_HOOKUP_OBJECT (w_plugplug2, ed_materialname, "ed_materialname"); + GLADE_HOOKUP_OBJECT (w_plugplug2, hbox1, "hbox1"); + GLADE_HOOKUP_OBJECT (w_plugplug2, b_addmaterial, "b_addmaterial"); + GLADE_HOOKUP_OBJECT (w_plugplug2, b_removematerial, "b_removematerial"); + GLADE_HOOKUP_OBJECT (w_plugplug2, t_exportmaterials, "t_exportmaterials"); + GLADE_HOOKUP_OBJECT (w_plugplug2, t_limitmatnames, "t_limitmatnames"); + GLADE_HOOKUP_OBJECT (w_plugplug2, t_objects, "t_objects"); + + return w_plugplug2; +} + +// global main window, is 0 when not created +GtkWidget* g_brushexp_window = 0; + +// spawn plugin window (and make sure it got destroyed first or never created) +void CreateWindow(void) +{ + ASSERT_NOTNULL(!g_brushexp_window); + + GtkWidget* wnd = create_w_plugplug2(); + + // column & renderer + GtkTreeViewColumn* col = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(col, "materials"); + gtk_tree_view_append_column(GTK_TREE_VIEW(lookup_widget(wnd, "t_materialist")), col); + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(lookup_widget(wnd, "t_materialist")), -1, "", renderer, "text", 0, NULL); + + // list store + GtkListStore* ignorelist = gtk_list_store_new(1, G_TYPE_STRING); + gtk_tree_view_set_model(GTK_TREE_VIEW(lookup_widget(wnd, "t_materialist")), GTK_TREE_MODEL(ignorelist)); + g_object_unref(ignorelist); + + gtk_widget_show_all(wnd); + g_brushexp_window = wnd; +} + +void DestroyWindow(void) +{ + ASSERT_NOTNULL(g_brushexp_window); + gtk_widget_destroy(g_brushexp_window); + g_brushexp_window = 0; +} + +bool IsWindowOpen(void) +{ + return g_brushexp_window != 0; +} diff --git a/contrib/brushexport/plugin.cpp b/contrib/brushexport/plugin.cpp new file mode 100644 index 00000000..3b3fab09 --- /dev/null +++ b/contrib/brushexport/plugin.cpp @@ -0,0 +1,134 @@ +/* +Copyright (C) 2006, Thomas Nitschke. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "plugin.h" + +#include "iplugin.h" +#include "qerplugin.h" + +#include +#include + +#include "debugging/debugging.h" +#include "string/string.h" +#include "modulesystem/singletonmodule.h" +#include "stream/textfilestream.h" +#include "stream/stringstream.h" +#include "gtkutil/messagebox.h" +#include "gtkutil/filechooser.h" + +#include "ibrush.h" +#include "iscenegraph.h" +#include "iselection.h" +#include "ifilesystem.h" +#include "ifiletypes.h" + +#include "support.h" + +#include "typesystem.h" + +void CreateWindow (void); +void DestroyWindow(void); +bool IsWindowOpen(void); + +namespace BrushExport +{ + GtkWindow* g_mainwnd; + + const char* init(void* hApp, void* pMainWidget) + { + g_mainwnd = (GtkWindow*)pMainWidget; + ASSERT_NOTNULL(g_mainwnd); + return ""; + } + const char* getName() + { + return "Brush export Plugin"; + } + const char* getCommandList() + { + return "Export selected as Wavefront Object;About"; + } + const char* getCommandTitleList() + { + return ""; + } + + void dispatch(const char* command, float* vMin, float* vMax, bool bSingleBrush) + { + if(string_equal(command, "About")) + { + GlobalRadiant().m_pfnMessageBox(GTK_WIDGET(g_mainwnd), "Brushexport plugin v 2.0 by namespace (www.codecreator.net)\n" + "Enjoy!\n\nSend feedback to spam@codecreator.net", "About me...", + eMB_OK, + eMB_ICONDEFAULT); + } + else if(string_equal(command, "Export selected as Wavefront Object")) + { + if(IsWindowOpen()) + DestroyWindow(); + CreateWindow(); + } + } +} + +class BrushExportDependencies : + public GlobalRadiantModuleRef, + public GlobalFiletypesModuleRef, + public GlobalBrushModuleRef, + public GlobalFileSystemModuleRef, + public GlobalSceneGraphModuleRef, + public GlobalSelectionModuleRef +{ +public: + BrushExportDependencies(void) + : GlobalBrushModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("brushtypes")) + {} +}; + +class BrushExportModule : public TypeSystemRef +{ + _QERPluginTable m_plugin; +public: + typedef _QERPluginTable Type; + STRING_CONSTANT(Name, "brushexport2"); + + BrushExportModule() + { + m_plugin.m_pfnQERPlug_Init = &BrushExport::init; + m_plugin.m_pfnQERPlug_GetName = &BrushExport::getName; + m_plugin.m_pfnQERPlug_GetCommandList = &BrushExport::getCommandList; + m_plugin.m_pfnQERPlug_GetCommandTitleList = &BrushExport::getCommandTitleList; + m_plugin.m_pfnQERPlug_Dispatch = &BrushExport::dispatch; + } + _QERPluginTable* getTable() + { + return &m_plugin; + } +}; + +typedef SingletonModule SingletonBrushExportModule; +SingletonBrushExportModule g_BrushExportModule; + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer& server) +{ + initialiseModule(server); + g_BrushExportModule.selfRegister(); +} diff --git a/contrib/brushexport/plugin.h b/contrib/brushexport/plugin.h new file mode 100644 index 00000000..99396819 --- /dev/null +++ b/contrib/brushexport/plugin.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2006, Thomas Nitschke. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_BRUSH_EXPORT_H) +#define INCLUDED_BRUSH_EXPORT_H + +#endif diff --git a/contrib/brushexport/support.cpp b/contrib/brushexport/support.cpp new file mode 100644 index 00000000..2835b112 --- /dev/null +++ b/contrib/brushexport/support.cpp @@ -0,0 +1,31 @@ +#include + +#include "support.h" + +GtkWidget* +lookup_widget (GtkWidget *widget, + const gchar *widget_name) +{ + GtkWidget *parent, *found_widget; + + for (;;) + { + if (GTK_IS_MENU (widget)) + parent = gtk_menu_get_attach_widget (GTK_MENU (widget)); + else + parent = widget->parent; + if (!parent) + parent = (GtkWidget*) g_object_get_data (G_OBJECT (widget), "GladeParentKey"); + if (parent == NULL) + break; + widget = parent; + } + + found_widget = (GtkWidget*) g_object_get_data (G_OBJECT (widget), + widget_name); + if (!found_widget) + g_warning ("Widget not found: %s", widget_name); + return found_widget; +} + + diff --git a/contrib/brushexport/support.h b/contrib/brushexport/support.h new file mode 100644 index 00000000..628885a5 --- /dev/null +++ b/contrib/brushexport/support.h @@ -0,0 +1,24 @@ +/* + * DO NOT EDIT THIS FILE - it is generated by Glade. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +/* + * Public Functions. + */ + +/* + * This function returns a widget in a component created by Glade. + * Call it with the toplevel widget in the component (i.e. a window/dialog), + * or alternatively any widget in the component, and the name of the widget + * you want returned. + */ +GtkWidget* lookup_widget (GtkWidget *widget, + const gchar *widget_name); + + diff --git a/contrib/camera/bitmaps/camera_insp.bmp b/contrib/camera/bitmaps/camera_insp.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1286716ed0224332b47a73b0f14f4edccf1af436 GIT binary patch literal 320 zcmYjNTMmFA3>$1QCVprzTYcwi^#eI%vl_!gZfXJRal&*nkyCG9yRqiwH4+ z5(VpJsbGKogkuq<7LmkMe>{_0tBQadXxw1VQBz1EHle%lq3~oxBW@^M%rv|8;6925 awT?ZzU$oc$F*Rl**U#wOn>uTc9Kr{8e+#1k literal 0 HcmV?d00001 diff --git a/contrib/camera/camera.cpp b/contrib/camera/camera.cpp new file mode 100644 index 00000000..48041dc0 --- /dev/null +++ b/contrib/camera/camera.cpp @@ -0,0 +1,290 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Camera plugin for GtkRadiant +Copyright (C) 2002 Splash Damage Ltd. +*/ + +#include "camera.h" + +// Render view +CRenderer *Renderer = NULL; + +// Interaction +CListener *Listener = NULL; + +// plugin name +static const char *PLUGIN_NAME = "Camera"; + +// commands in the menu +static const char *PLUGIN_COMMANDS = "About...,-,Load Camera...,-,Preview Camera,-,Camera Inspector...,-,New Spline Camera,New Interpolated Camera,New Fixed Camera"; + +// globals +GtkWidget *g_pRadiantWnd = NULL; +GtkWidget *g_pCameraInspectorWnd = NULL; +CCamera *firstCam = NULL; // double linked list +CCamera *firstFreeCam = NULL; // single linked list +CCamera *currentCam = NULL; // single item +bool g_bEditOn = false; +int g_iEditMode = 0; // 0: editting points 1: adding points +int g_iActiveTarget = -1; +int g_iPreviewRunning = 0; // 0: no preview 1: start preview 2: preview in progress + +static const char *PLUGIN_ABOUT = "Camera v1.0 for GtkRadiant\n" + "by Arnout van Meer (rr2do2@splashdamage.com)\n\n" + "This product contains software technology\n" + "from id Software, Inc. ('id Technology').\n" + "id Technology (c) 2001, 2002 id Software, Inc."; + + +#include "iplugin.h" + +const char* QERPlug_Init(void* hApp, void* pMainWidget) +{ + g_pRadiantWnd = (GtkWidget*)pMainWidget; + + // initialize cams + for( int i = 0; i < MAX_CAMERAS; i++ ) { + if( i == 0 ) { + firstFreeCam = new CCamera( i ); + firstCam = firstFreeCam; + } else { + firstCam->SetNext( new CCamera( i ) ); + firstCam = firstCam->GetNext(); + } + } + firstCam = NULL; + + if( !Renderer ) + { + Renderer = new CRenderer; + } + + if( g_pCameraInspectorWnd == NULL ) + g_pCameraInspectorWnd = CreateCameraInspectorDialog(); + + GetFileTypeRegistry()->addType("camera", "", filetype_t("Camera file", "*.camera")); + + return "Camera for GtkRadiant"; +} + +const char* QERPlug_GetName() +{ + return PLUGIN_NAME; +} + +const char* QERPlug_GetCommandList() +{ + return PLUGIN_COMMANDS; +} + +void QERPlug_Dispatch (const char* p, float* vMin, float* vMax, bool bSingleBrush) +{ + if( !strcmp( p, "New Fixed Camera" ) ) + DoNewFixedCamera(); + else if( !strcmp( p, "New Interpolated Camera" ) ) + DoNewInterpolatedCamera(); + else if( !strcmp( p, "New Spline Camera" ) ) + DoNewSplineCamera(); + else if( !strcmp( p, "Camera Inspector..." ) ) + DoCameraInspector(); + else if( !strcmp( p, "Preview Camera" ) ) + DoPreviewCamera(); + else if( !strcmp( p, "Load Camera..." ) ) + DoLoadCamera(); + else if( !strcmp( p, "About..." ) ) + g_FuncTable.m_pfnMessageBox( (GtkWidget *)g_pRadiantWnd, PLUGIN_ABOUT, "About", eMB_OK ); +} + + +// toolbar + +#include "itoolbar.h" + +unsigned int ToolbarButtonCount() +{ + return 1; +} + +class CameraInspectorButton : public IToolbarButton +{ +public: + virtual const char* getImage() const + { + return "camera_insp.bmp"; + } + virtual const char* getText() const + { + return "Inspector"; + } + virtual const char* getTooltip() const + { + return "Camera Inspector"; + } + virtual void activate() const + { + DoCameraInspector(); + } + virtual EType getType() const + { + return eButton; + } +}; + +CameraInspectorButton g_camerainspectorbutton; + +const IToolbarButton* GetToolbarButton(unsigned int index) +{ + return &g_camerainspectorbutton; +} + + +_QERFuncTable_1 g_FuncTable; +_QERQglTable g_QglTable; +_QERUITable g_UITable; +_QERCameraTable g_CameraTable; + +// ============================================================================= +// SYNAPSE + +#include "synapse.h" + +class CameraSynapseClient : public CSynapseClient +{ +public: + // CSynapseClient API + bool RequestAPI(APIDescriptor_t *pAPI); + const char* GetInfo(); + + CameraSynapseClient() { } + virtual ~CameraSynapseClient() { } +}; + +CSynapseServer* g_pSynapseServer = NULL; +CameraSynapseClient g_SynapseClient; + +extern "C" CSynapseClient* SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces (const char *version, CSynapseServer *pServer) +{ + if (strcmp(version, SYNAPSE_VERSION)) + { + Syn_Printf("ERROR: synapse API version mismatch: should be '" SYNAPSE_VERSION "', got '%s'\n", version); + return NULL; + } + g_pSynapseServer = pServer; + g_pSynapseServer->IncRef(); + Set_Syn_Printf(g_pSynapseServer->Get_Syn_Printf()); + + g_SynapseClient.AddAPI(TOOLBAR_MAJOR, "camera", sizeof(_QERPlugToolbarTable)); + g_SynapseClient.AddAPI(PLUGIN_MAJOR, "camera", sizeof(_QERPluginTable)); + + g_SynapseClient.AddAPI(RADIANT_MAJOR, NULL, sizeof(_QERFuncTable_1), SYN_REQUIRE, &g_FuncTable); + g_SynapseClient.AddAPI(UI_MAJOR, NULL, sizeof(_QERUITable), SYN_REQUIRE, &g_UITable); + g_SynapseClient.AddAPI(QGL_MAJOR, NULL, sizeof(_QERQglTable), SYN_REQUIRE, &g_QglTable); + g_SynapseClient.AddAPI(CAMERA_MAJOR, NULL, sizeof(_QERCameraTable), SYN_REQUIRE, &g_CameraTable); + + return &g_SynapseClient; +} + +bool CameraSynapseClient::RequestAPI(APIDescriptor_t *pAPI) +{ + if (!strcmp(pAPI->major_name, TOOLBAR_MAJOR)) + { + _QERPlugToolbarTable* pTable= static_cast<_QERPlugToolbarTable*>(pAPI->mpTable); + + pTable->m_pfnToolbarButtonCount = &ToolbarButtonCount; + pTable->m_pfnGetToolbarButton = &GetToolbarButton; + return true; + } + else if (!strcmp(pAPI->major_name, PLUGIN_MAJOR)) + { + _QERPluginTable* pTable= static_cast<_QERPluginTable*>(pAPI->mpTable); + + pTable->m_pfnQERPlug_Init = QERPlug_Init; + pTable->m_pfnQERPlug_GetName = QERPlug_GetName; + pTable->m_pfnQERPlug_GetCommandList = QERPlug_GetCommandList; + pTable->m_pfnQERPlug_Dispatch = QERPlug_Dispatch; + return true; + } + + Syn_Printf("ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo()); + return false; +} + +#include "version.h" + +const char* CameraSynapseClient::GetInfo() +{ + return "Camera plugin v1.0 - Arnout van Meer - built " __DATE__ " " RADIANT_VERSION; +} + + + +// +// CCamera +// +CCamera *AllocCam() { + if( !firstFreeCam ) + return( NULL ); + + CCamera *cam = firstFreeCam; + firstFreeCam = firstFreeCam->GetNext(); + cam->Init(); + if( firstCam ) { + cam->SetNext( firstCam ); + firstCam->SetPrev( cam ); + } + firstCam = cam; + + return( cam ); +} + +void FreeCam( CCamera *cam ) { + if( cam->GetPrev() ) { + if( cam->GetNext() ) { + cam->GetPrev()->SetNext( cam->GetNext() ); + cam->GetNext()->SetPrev( cam->GetPrev() ); + } else { + cam->GetPrev()->SetNext( NULL ); + } + } else if( cam->GetNext() ) { + cam->GetNext()->SetPrev( NULL ); + firstCam = cam->GetNext(); + } else { + firstCam = NULL; + } + + cam->GetCam()->clear(); + cam->Init(); + + if( firstFreeCam ) { + cam->SetNext( firstFreeCam ); + } + firstFreeCam = cam; +} + +void SetCurrentCam( CCamera *cam ) { + currentCam = cam; +} + +CCamera *GetCurrentCam() { + return( currentCam ); +} diff --git a/contrib/camera/camera.def b/contrib/camera/camera.def new file mode 100644 index 00000000..6a597438 --- /dev/null +++ b/contrib/camera/camera.def @@ -0,0 +1,8 @@ +; camera.def : Declares the module parameters for the DLL. + +LIBRARY "CAMERA" +DESCRIPTION 'CAMERA Windows Dynamic Link Library' + +EXPORTS + ; Explicit exports can go here + Synapse_EnumerateInterfaces @1 diff --git a/contrib/camera/camera.h b/contrib/camera/camera.h new file mode 100644 index 00000000..4416f796 --- /dev/null +++ b/contrib/camera/camera.h @@ -0,0 +1,165 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Camera plugin for GtkRadiant +Copyright (C) 2002 Splash Damage Ltd. +*/ + +#ifndef _CAMERA_H_ +#define _CAMERA_H_ + +typedef unsigned char byte; + +#include "mathlib.h" +#include +#include "qertypes.h" +#include + +#define USE_QERTABLE_DEFINE +#include "iscenegraph.h" +#include "qerplugin.h" + +#define USE_QGLTABLE_DEFINE +#include "igl.h" +extern _QERQglTable __QGLTABLENAME; + +#include "iui.h" +#include "icamera.h" + +#include "bytebool.h" + +class CCamera; +#include + +#include "str.h" + + +#include "misc.h" +#include "dialogs.h" +#include "funchandlers.h" +#include "renderer.h" +#include "listener.h" + +extern _QERFuncTable_1 g_FuncTable; +extern _QERQglTable g_QglTable; +extern _QERUITable g_UITable; +extern _QERCameraTable g_CameraTable; + +extern CRenderer *Renderer; +extern CListener *Listener; + +// splinelib +#define CAMERA_PLUGIN +#define DotProduct(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) + +#include "splines/splines.h" + +// this needs to match splines.cpp +#define MAX_CAMERAS 64 +extern idCameraDef camera[MAX_CAMERAS]; + +extern "C" qboolean loadCamera(int camNum, const char *name); + +#define PATH_MAX 260 + +// +// CCamera +// + +class CCamera { +public: + CCamera( int i ) { + cam = &camera[i]; + camnum = i; + Init(); + } + ~CCamera(); + + void Init() { + next = prev = NULL; + fileName[0] = '\0'; + hasbeensaved = 0; + } + + idCameraDef *GetCam() { + return( cam ); + } + int GetCamNum() { + return( camnum ); + } + + char *GetFileName() { + return( fileName ); + } + void SetFileName( const char *name, bool save ) { + strcpy( fileName, name ); + if( save ) + hasbeensaved = 1; + } + + CCamera *GetNext() { + return( next ); + } + + CCamera *GetPrev() { + return( prev ); + } + + void SetNext( CCamera *camera ) { + next = camera; + } + void SetPrev( CCamera *camera ) { + prev = camera; + } + + int HasBeenSaved() { + return( hasbeensaved ); + } + void HasBeenModified() { + if( hasbeensaved ) + hasbeensaved = 2; + } + +protected: + idCameraDef *cam; + int camnum; + CCamera *next, *prev; + char fileName[PATH_MAX]; + int hasbeensaved; // 0:never saved 1:saved 2:saved, but modified +}; + +CCamera *AllocCam(); +void FreeCam( CCamera *cam ); +void SetCurrentCam( CCamera *cam ); +CCamera *GetCurrentCam(); + +// globals +extern GtkWidget *g_pRadiantWnd; +extern GtkWidget *g_pCameraInspectorWnd; +extern CCamera *firstCam; +extern bool g_bEditOn; +extern int g_iEditMode; +extern int g_iActiveTarget; +extern int g_iPreviewRunning; +extern CCamera *g_pCurrentEditCam; + +#endif // _CAMERA_H_ diff --git a/contrib/camera/camera.vcproj b/contrib/camera/camera.vcproj new file mode 100644 index 00000000..6b039254 --- /dev/null +++ b/contrib/camera/camera.vcproj @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/camera/dialogs.cpp b/contrib/camera/dialogs.cpp new file mode 100644 index 00000000..40df284e --- /dev/null +++ b/contrib/camera/dialogs.cpp @@ -0,0 +1,1356 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Camera plugin for GtkRadiant +Copyright (C) 2002 Splash Damage Ltd. +*/ + +#include "camera.h" + +static GSList *g_pEditTypeRadio = NULL; +static GtkWidget *g_pEditModeEditRadioButton = NULL; +GtkWidget *g_pEditModeAddRadioButton = NULL; +static GtkWidget *g_pSecondsEntry = NULL; +static GtkWidget *g_pEventsList = NULL; +static GtkLabel *g_pCurrentTime = NULL; +static GtkLabel *g_pTotalTime = NULL; +static GtkAdjustment *g_pTimeLine = NULL; +static GtkWidget *g_pTrackCamera = NULL; +static GtkWidget *g_pCamName = NULL; +static char *g_cNull = '\0'; + +static gint ci_editmode_edit( GtkWidget *widget, gpointer data ) +{ + g_iEditMode = 0; + + return TRUE; +} + +static gint ci_editmode_add( GtkWidget *widget, gpointer data ) +{ + g_iEditMode = 1; + + return TRUE; +} + +/*static gint ci_delete_selected( GtkWidget *widget, gpointer data ) +{ + return TRUE; +} + +static gint ci_select_all( GtkWidget *widget, gpointer data ) +{ + return TRUE; +}*/ + +static gint ci_new( GtkWidget *widget, gpointer data ) +{ + GtkWidget *window, *w, *vbox, *vbox2, *hbox, *frame; //, *name; + GtkWidget *fixed, *interpolated, *spline; + EMessageBoxReturn ret; + int loop = 1; + GSList *targetTypeRadio = NULL; +// char buf[128]; + + // create the window + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + gtk_window_set_title( GTK_WINDOW (window), "New Camera" ); + gtk_signal_connect( GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC( dialog_delete_callback ), NULL ); + gtk_signal_connect( GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC( gtk_widget_destroy ), NULL ); + gtk_window_set_transient_for( GTK_WINDOW( window ), GTK_WINDOW( g_pCameraInspectorWnd ) ); + + g_object_set_data (G_OBJECT (window), "loop", &loop); + g_object_set_data (G_OBJECT (window), "ret", &ret); + + gtk_widget_realize (window); + + // fill the window + vbox = gtk_vbox_new( FALSE, 5 ); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show( vbox ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + frame = gtk_frame_new( "Type" ); + gtk_box_pack_start( GTK_BOX( hbox ), frame, TRUE, TRUE, 0 ); + gtk_widget_show( frame ); + + vbox2 = gtk_vbox_new( FALSE, 5 ); + gtk_container_add( GTK_CONTAINER( frame ), vbox2 ); + gtk_container_set_border_width( GTK_CONTAINER (vbox2), 5 ); + gtk_widget_show( vbox2 ); + + // -------------------------- // + + fixed = gtk_radio_button_new_with_label( targetTypeRadio, "Fixed" ); + gtk_box_pack_start( GTK_BOX( vbox2 ), fixed, FALSE, FALSE, 3 ); + gtk_widget_show( fixed ); + targetTypeRadio = gtk_radio_button_group( GTK_RADIO_BUTTON( fixed ) ); + + interpolated = gtk_radio_button_new_with_label( targetTypeRadio, "Interpolated" ); + gtk_box_pack_start( GTK_BOX( vbox2 ), interpolated, FALSE, FALSE, 3 ); + gtk_widget_show( interpolated ); + targetTypeRadio = gtk_radio_button_group( GTK_RADIO_BUTTON( interpolated ) ); + + spline = gtk_radio_button_new_with_label( targetTypeRadio, "Spline" ); + gtk_box_pack_start( GTK_BOX( vbox2 ), spline, FALSE, FALSE, 3 ); + gtk_widget_show( spline ); + targetTypeRadio = gtk_radio_button_group( GTK_RADIO_BUTTON( spline ) ); + + // -------------------------- // + + w = gtk_hseparator_new (); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2); + gtk_widget_show (w); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_button_new_with_label ("Ok"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDOK)); + gtk_widget_show (w); + + GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT ); + gtk_widget_grab_default( w ); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDCANCEL)); + gtk_widget_show (w); + ret = eIDCANCEL; + + // -------------------------- // + + gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_widget_show (window); + gtk_grab_add (window); + + bool dialogError = TRUE; + while (dialogError) { + loop = 1; + while (loop) + gtk_main_iteration (); + + dialogError = FALSE; + + if( ret == eIDOK ) { + if( gtk_toggle_button_get_active( (GtkToggleButton*)fixed ) ) + DoNewFixedCamera(); + else if( gtk_toggle_button_get_active( (GtkToggleButton*)interpolated ) ) + DoNewInterpolatedCamera(); + else if( gtk_toggle_button_get_active( (GtkToggleButton*)spline ) ) + DoNewSplineCamera(); + } + } + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return TRUE; +} + +static gint ci_load( GtkWidget *widget, gpointer data ) +{ + DoLoadCamera(); + + return TRUE; +} + +static gint ci_save( GtkWidget *widget, gpointer data ) +{ + DoSaveCamera(); + + return TRUE; +} + +static gint ci_unload( GtkWidget *widget, gpointer data ) +{ + DoUnloadCamera(); + + return TRUE; +} + +static gint ci_apply( GtkWidget *widget, gpointer data ) +{ + if( GetCurrentCam() ) { + const char *str; + char buf[128]; + bool build = false; + + str = gtk_entry_get_text( GTK_ENTRY(g_pCamName) ); + + if( str ) { + GetCurrentCam()->GetCam()->setName( str ); + build = true; + } + + str = gtk_entry_get_text( GTK_ENTRY(g_pSecondsEntry) ); + + if( str ) { + GetCurrentCam()->GetCam()->setBaseTime( atof( str ) ); + build = true; + } + + if( build ) { + GetCurrentCam()->GetCam()->buildCamera(); + } + + sprintf( buf, "%.2f", GetCurrentCam()->GetCam()->getBaseTime() ); + gtk_entry_set_text( GTK_ENTRY(g_pSecondsEntry), buf ); + + sprintf( buf, "%.2f", GetCurrentCam()->GetCam()->getTotalTime() ); + gtk_label_set_text( g_pCurrentTime, "0.00" ); + gtk_label_set_text( g_pTotalTime, buf ); + + gtk_adjustment_set_value( g_pTimeLine, 0.f ); + g_pTimeLine->upper = GetCurrentCam()->GetCam()->getTotalTime() * 1000; + + GetCurrentCam()->HasBeenModified(); + } + + return TRUE; +} + +static gint ci_preview( GtkWidget *widget, gpointer data ) +{ + if( GetCurrentCam() ) { + g_iPreviewRunning = 1; + g_FuncTable.m_pfnSysUpdateWindows( W_XY_OVERLAY | W_CAMERA ); + } + + return TRUE; +} + +static gint ci_expose( GtkWidget *widget, gpointer data ) +{ + // start edit mode + DoStartEdit( GetCurrentCam() ); + + return FALSE; +} + +static gint ci_close( GtkWidget *widget, gpointer data ) +{ + gtk_widget_hide( g_pCameraInspectorWnd ); + + // exit edit mode + DoStopEdit(); + + return TRUE; +} + +static GtkWidget *g_pPathListCombo = NULL; +static GtkLabel *g_pPathType = NULL; + +static void RefreshPathListCombo( void ) +{ + if( !g_pPathListCombo ) + return; + + GList *combo_list = (GList*)NULL; + + if( GetCurrentCam() ) { + combo_list = g_list_append( combo_list, (void *)GetCurrentCam()->GetCam()->getPositionObj()->getName() ); + for( int i = 0; i < GetCurrentCam()->GetCam()->numTargets(); i++ ) { + combo_list = g_list_append( combo_list, (void *)GetCurrentCam()->GetCam()->getActiveTarget( i )->getName() ); + } + } else { + // add one empty string make gtk be quiet + combo_list = g_list_append( combo_list, (gpointer)g_cNull ); + } + + gtk_combo_set_popdown_strings( GTK_COMBO( g_pPathListCombo ), combo_list ); + g_list_free( combo_list ); +} + +static gint ci_pathlist_changed( GtkWidget *widget, gpointer data ) +{ + const char *str = gtk_entry_get_text( GTK_ENTRY(widget) ); + + if( !str || !GetCurrentCam() ) + return TRUE; + + int i; + for( i = 0; i < GetCurrentCam()->GetCam()->numTargets(); i++ ) { + if( !strcmp( str, GetCurrentCam()->GetCam()->getActiveTarget( i )->getName() ) ) + break; + } + + if( i >= 0 && i < GetCurrentCam()->GetCam()->numTargets() ) { + GetCurrentCam()->GetCam()->setActiveTarget( i ); + + g_iActiveTarget = i; + if( g_pPathType ) + gtk_label_set_text( g_pPathType, GetCurrentCam()->GetCam()->getActiveTarget(g_iActiveTarget)->typeStr() ); + } else { + g_iActiveTarget = -1; + if( g_pPathType ) + gtk_label_set_text( g_pPathType, GetCurrentCam()->GetCam()->getPositionObj()->typeStr() ); + } + + // start edit mode + if( g_pCameraInspectorWnd && GTK_WIDGET_VISIBLE( g_pCameraInspectorWnd ) ) + DoStartEdit( GetCurrentCam() ); + + return TRUE; +} + +static void RefreshEventList( void ) +{ + int i; + char buf[128]; + + // Clear events list + gtk_clist_freeze( GTK_CLIST(g_pEventsList) ); + gtk_clist_clear( GTK_CLIST(g_pEventsList) ); + + if( GetCurrentCam() ) { + // Fill events list + for( i = 0; i < GetCurrentCam()->GetCam()->numEvents(); i++ ) { + char rowbuf[3][128], *row[3]; + // FIXME: sort by time? + sprintf( rowbuf[0], "%li", GetCurrentCam()->GetCam()->getEvent(i)->getTime() ); row[0] = rowbuf[0]; + strncpy( rowbuf[1], GetCurrentCam()->GetCam()->getEvent(i)->typeStr(), sizeof(rowbuf[0]) ); row[1] = rowbuf[1]; + strncpy( rowbuf[2], GetCurrentCam()->GetCam()->getEvent(i)->getParam(), sizeof(rowbuf[1]) ); row[2] = rowbuf[2]; + gtk_clist_append( GTK_CLIST(g_pEventsList), row ); + } + + // Total duration might have changed + sprintf( buf, "%.2f", GetCurrentCam()->GetCam()->getTotalTime() ); + gtk_label_set_text( g_pCurrentTime, "0.00" ); + gtk_label_set_text( g_pTotalTime, buf ); + + gtk_adjustment_set_value( g_pTimeLine, 0.f ); + g_pTimeLine->upper = ( GetCurrentCam()->GetCam()->getTotalTime() * 1000 ); + } + + gtk_clist_thaw( GTK_CLIST(g_pEventsList) ); +} + +static gint ci_rename( GtkWidget *widget, gpointer data ) +{ + GtkWidget *window, *w, *vbox, *hbox, *name; + EMessageBoxReturn ret; + int loop = 1; + + if( !GetCurrentCam() ) + return TRUE; + + // create the window + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + gtk_window_set_title( GTK_WINDOW (window), "Rename Path" ); + gtk_signal_connect( GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC( dialog_delete_callback ), NULL ); + gtk_signal_connect( GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC( gtk_widget_destroy ), NULL ); + gtk_window_set_transient_for( GTK_WINDOW( window ), GTK_WINDOW( g_pCameraInspectorWnd ) ); + + g_object_set_data (G_OBJECT (window), "loop", &loop); + g_object_set_data (G_OBJECT (window), "ret", &ret); + + gtk_widget_realize ( window ); + + // fill the window + vbox = gtk_vbox_new( FALSE, 5 ); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show( vbox ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_label_new( "Name:" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + name = gtk_entry_new(); + gtk_box_pack_start( GTK_BOX( hbox ), name, FALSE, FALSE, 0 ); + gtk_widget_show( name ); + + if( g_iActiveTarget < 0 ) + gtk_entry_set_text( GTK_ENTRY(name), GetCurrentCam()->GetCam()->getPositionObj()->getName() ); + else + gtk_entry_set_text( GTK_ENTRY(name), GetCurrentCam()->GetCam()->getActiveTarget(g_iActiveTarget)->getName() ); + + // -------------------------- // + + w = gtk_hseparator_new (); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2); + gtk_widget_show (w); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_button_new_with_label ("Ok"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDOK)); + gtk_widget_show (w); + + GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT ); + gtk_widget_grab_default( w ); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDCANCEL)); + gtk_widget_show (w); + ret = eIDCANCEL; + + // -------------------------- // + + gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_widget_show (window); + gtk_grab_add (window); + + bool dialogError = TRUE; + while (dialogError) { + loop = 1; + while (loop) + gtk_main_iteration (); + + dialogError = FALSE; + + if( ret == eIDOK ) { + const char *str = gtk_entry_get_text( GTK_ENTRY(name) ); + + if( str && str[0] ) { + // Update the path + if( g_iActiveTarget < 0 ) + GetCurrentCam()->GetCam()->getPositionObj()->setName( str ); + else + GetCurrentCam()->GetCam()->getActiveTarget(g_iActiveTarget)->setName( str ); + + GetCurrentCam()->GetCam()->buildCamera(); + + // Rebuild the listbox + RefreshPathListCombo(); + } else { + dialogError = TRUE; + } + } + } + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return TRUE; +} + +static gint ci_add_target( GtkWidget *widget, gpointer data ) +{ + GtkWidget *window, *w, *vbox, *vbox2, *hbox, *frame, *name; + GtkWidget *fixed, *interpolated, *spline; + EMessageBoxReturn ret; + int loop = 1; + GSList *targetTypeRadio = NULL; + char buf[128]; + + if( !GetCurrentCam() ) + return TRUE; + + // create the window + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + gtk_window_set_title( GTK_WINDOW (window), "Add Target" ); + gtk_signal_connect( GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC( dialog_delete_callback ), NULL ); + gtk_signal_connect( GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC( gtk_widget_destroy ), NULL ); + gtk_window_set_transient_for( GTK_WINDOW( window ), GTK_WINDOW( g_pCameraInspectorWnd ) ); + + g_object_set_data (G_OBJECT (window), "loop", &loop); + g_object_set_data (G_OBJECT (window), "ret", &ret); + + gtk_widget_realize (window); + + // fill the window + vbox = gtk_vbox_new( FALSE, 5 ); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show( vbox ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_label_new( "Name:" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + name = gtk_entry_new(); + gtk_box_pack_start( GTK_BOX( hbox ), name, TRUE, TRUE, 0 ); + gtk_widget_show( name ); + + sprintf( buf, "target%i", GetCurrentCam()->GetCam()->numTargets() + 1 ); + gtk_entry_set_text( GTK_ENTRY(name), buf ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + frame = gtk_frame_new( "Type" ); + gtk_box_pack_start( GTK_BOX( hbox ), frame, TRUE, TRUE, 0 ); + gtk_widget_show( frame ); + + vbox2 = gtk_vbox_new( FALSE, 5 ); + gtk_container_add( GTK_CONTAINER( frame ), vbox2 ); + gtk_container_set_border_width( GTK_CONTAINER (vbox2), 5 ); + gtk_widget_show( vbox2 ); + + // -------------------------- // + + fixed = gtk_radio_button_new_with_label( targetTypeRadio, "Fixed" ); + gtk_box_pack_start( GTK_BOX( vbox2 ), fixed, FALSE, FALSE, 3 ); + gtk_widget_show( fixed ); + targetTypeRadio = gtk_radio_button_group( GTK_RADIO_BUTTON( fixed ) ); + + interpolated = gtk_radio_button_new_with_label( targetTypeRadio, "Interpolated" ); + gtk_box_pack_start( GTK_BOX( vbox2 ), interpolated, FALSE, FALSE, 3 ); + gtk_widget_show( interpolated ); + targetTypeRadio = gtk_radio_button_group( GTK_RADIO_BUTTON( interpolated ) ); + + spline = gtk_radio_button_new_with_label( targetTypeRadio, "Spline" ); + gtk_box_pack_start( GTK_BOX( vbox2 ), spline, FALSE, FALSE, 3 ); + gtk_widget_show( spline ); + targetTypeRadio = gtk_radio_button_group( GTK_RADIO_BUTTON( spline ) ); + + // -------------------------- // + + w = gtk_hseparator_new (); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2); + gtk_widget_show (w); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_button_new_with_label ("Ok"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDOK)); + gtk_widget_show (w); + + GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT ); + gtk_widget_grab_default( w ); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDCANCEL)); + gtk_widget_show (w); + ret = eIDCANCEL; + + // -------------------------- // + + gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_widget_show (window); + gtk_grab_add (window); + + bool dialogError = TRUE; + while (dialogError) { + loop = 1; + while (loop) + gtk_main_iteration (); + + dialogError = FALSE; + + if( ret == eIDOK ) { + const char *str = gtk_entry_get_text( GTK_ENTRY(name) ); + + if( str && str[0] ) { + int type; + GList *li; + + if( gtk_toggle_button_get_active( (GtkToggleButton*)fixed ) ) + type = 0; + else if( gtk_toggle_button_get_active( (GtkToggleButton*)interpolated ) ) + type = 1; + else if( gtk_toggle_button_get_active( (GtkToggleButton*)spline ) ) + type = 2; + + // Add the target + GetCurrentCam()->GetCam()->addTarget( str, static_cast(type) ); + + // Rebuild the listbox + RefreshPathListCombo(); + + // Select the last item in the listbox + li = g_list_last( GTK_LIST(GTK_COMBO(g_pPathListCombo)->list)->children ); + gtk_list_select_child( GTK_LIST(GTK_COMBO(g_pPathListCombo)->list), GTK_WIDGET (li->data) ); + + // If this was the first one, refresh the event list + if( GetCurrentCam()->GetCam()->numTargets() == 1 ) { + RefreshEventList(); + } + + // Go to editmode Add + gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(g_pEditModeAddRadioButton), TRUE ); + + } else { + dialogError = TRUE; + } + } + } + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return TRUE; +} + +static GtkWidget *g_pCamListCombo = NULL; +static GtkLabel *g_pCamType = NULL; + +void RefreshCamListCombo( void ) +{ + if( !g_pCamListCombo ) + return; + + GList *combo_list = (GList*)NULL; + CCamera *combo_cam = firstCam; + if( combo_cam ) { + while( combo_cam ) { + //combo_list = g_list_append( combo_list, (void *)combo_cam->GetCam()->getName() ); + //if( combo_cam->HasBeenSaved() ) { + combo_list = g_list_append( combo_list, (void *)combo_cam->GetFileName() ); + /*} else { + char buf[128]; + sprintf( buf, "Unsaved Camera %i", combo_cam->GetCamNum() ); + combo_list = g_list_append( combo_list, (void *)buf ); + + //combo_list = g_list_append( combo_list, (void *)combo_cam->GetCam()->getName() ); // FIXME: this requires camera.dll to create unique names for new cams + }*/ + combo_cam = combo_cam->GetNext(); + } + }else { + // add one empty string make gtk be quiet + combo_list = g_list_append( combo_list, (gpointer)g_cNull ); + } + gtk_combo_set_popdown_strings( GTK_COMBO( g_pCamListCombo ), combo_list ); + g_list_free( combo_list ); + + // select our current entry in the list + if( GetCurrentCam() ) { + // stop editing on the current cam + //GetCurrentCam()->GetCam()->stopEdit(); // FIXME: this crashed on creating new cameras, why is it here? + + GList *li = GTK_LIST( GTK_COMBO(g_pCamListCombo)->list)->children; + combo_cam = firstCam; + while( li && combo_cam ) { + if( combo_cam == GetCurrentCam() ) { + gtk_list_select_child( GTK_LIST( GTK_COMBO(g_pCamListCombo)->list ), GTK_WIDGET( li->data ) ); + break; + } + li = li->next; + combo_cam = combo_cam->GetNext(); + } + } + + RefreshPathListCombo(); +} + +static gint ci_camlist_changed( GtkWidget *widget, gpointer data ) +{ + const char *str = gtk_entry_get_text( GTK_ENTRY(widget) ); + + CCamera *combo_cam = firstCam; + while( str && combo_cam ) { + //if( !strcmp( str, combo_cam->GetCam()->getName() ) ) + //if( combo_cam->HasBeenSaved() ) { + if( !strcmp( str, combo_cam->GetFileName() ) ) + break; + /*} else { + char buf[128]; + sprintf( buf, "Unsaved Camera %i", combo_cam->GetCamNum() ); + if( !strcmp( str, buf ) ) + //if( !strcmp( str, combo_cam->GetCam()->getName() ) ) + break; + }*/ + + combo_cam = combo_cam->GetNext(); + } + + SetCurrentCam( combo_cam ); + + if( g_pCamType ) { + if( GetCurrentCam() ) { + // Fill in our widgets fields for this camera + char buf[128]; + + // Set Name + gtk_entry_set_text( GTK_ENTRY(g_pCamName), GetCurrentCam()->GetCam()->getName() ); + + // Set type + gtk_label_set_text( g_pCamType, GetCurrentCam()->GetCam()->getPositionObj()->typeStr() ); + + // Set duration + sprintf( buf, "%.2f", GetCurrentCam()->GetCam()->getBaseTime() ); + gtk_entry_set_text( GTK_ENTRY(g_pSecondsEntry), buf ); + + sprintf( buf, "%.2f", GetCurrentCam()->GetCam()->getTotalTime() ); + gtk_label_set_text( g_pCurrentTime, "0.00" ); + gtk_label_set_text( g_pTotalTime, buf ); + + gtk_adjustment_set_value( g_pTimeLine, 0.f ); + g_pTimeLine->upper = GetCurrentCam()->GetCam()->getTotalTime() * 1000; + } else { + // Set Name + gtk_entry_set_text( GTK_ENTRY(g_pCamName), "" ); + + // Set type + gtk_label_set_text( g_pCamType, "" ); + + // Set duration + gtk_entry_set_text( GTK_ENTRY(g_pSecondsEntry), "30.00" ); + + gtk_label_set_text( g_pCurrentTime, "0.00" ); + gtk_label_set_text( g_pTotalTime, "30.00" ); + + gtk_adjustment_set_value( g_pTimeLine, 0.f ); + g_pTimeLine->upper = 30000; + } + + // Refresh event list + RefreshEventList(); + } + + RefreshPathListCombo(); + + // start edit mode + g_iActiveTarget = -1; + if( g_pCameraInspectorWnd && GTK_WIDGET_VISIBLE( g_pCameraInspectorWnd ) ) + DoStartEdit( GetCurrentCam() ); + + return TRUE; +} + +enum camEventType { + EVENT_NA = 0x00, + EVENT_WAIT, // + EVENT_TARGETWAIT, // + EVENT_SPEED, // + EVENT_TARGET, // char(name) + EVENT_SNAPTARGET, // + EVENT_FOV, // int(time), int(targetfov) + EVENT_CMD, // + EVENT_TRIGGER, // + EVENT_STOP, // + EVENT_CAMERA, // + EVENT_FADEOUT, // int(time) + EVENT_FADEIN, // int(time) + EVENT_FEATHER, // + EVENT_COUNT +}; + +// { requires parameters, enabled } +const bool camEventFlags[][2] = { + { false, false }, + { false, true }, + { false, false }, + { false, false }, + { true, true }, + { false, false }, + { true, true }, + { false, false }, + { false, false }, + { false, true }, + { true, true }, + { true, true }, + { true, true }, + { false, true }, +}; + +const char *camEventStr[] = { + "n/a", + "Wait", + "Target wait", + "Speed", + "Change Target ", + "Snap Target", + "FOV ", + "Run Script", + "Trigger", + "Stop", + "Change to Camera (or ", + "Fade Out ", + "Fade In ", + "Feather" +}; + +static gint ci_add( GtkWidget *widget, gpointer data ) +{ + GtkWidget *window, *w, *vbox, *vbox2, *hbox, *frame, *parameters; + GtkWidget *eventWidget[EVENT_COUNT]; + EMessageBoxReturn ret; + int i, loop = 1; + GSList *eventTypeRadio = NULL; +// char buf[128]; + + if( !GetCurrentCam() ) + return TRUE; + + // create the window + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + gtk_window_set_title( GTK_WINDOW (window), "Add Event" ); + gtk_signal_connect( GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC( dialog_delete_callback ), NULL ); + gtk_signal_connect( GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC( gtk_widget_destroy ), NULL ); + gtk_window_set_transient_for( GTK_WINDOW( window ), GTK_WINDOW( g_pCameraInspectorWnd ) ); + + g_object_set_data (G_OBJECT (window), "loop", &loop); + g_object_set_data (G_OBJECT (window), "ret", &ret); + + gtk_widget_realize (window); + + // fill the window + vbox = gtk_vbox_new( FALSE, 5 ); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show( vbox ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + frame = gtk_frame_new( "Type" ); + gtk_box_pack_start( GTK_BOX( hbox ), frame, TRUE, TRUE, 0 ); + gtk_widget_show( frame ); + + vbox2 = gtk_vbox_new( FALSE, 5 ); + gtk_container_add( GTK_CONTAINER( frame ), vbox2 ); + gtk_container_set_border_width( GTK_CONTAINER (vbox2), 5 ); + gtk_widget_show( vbox2 ); + + // -------------------------- // + + for( i = 1; i < EVENT_COUNT; i++ ) { + eventWidget[i] = gtk_radio_button_new_with_label( eventTypeRadio, camEventStr[i] ); + gtk_box_pack_start( GTK_BOX( vbox2 ), eventWidget[i], FALSE, FALSE, 3 ); + gtk_widget_show( eventWidget[i] ); + eventTypeRadio = gtk_radio_button_group( GTK_RADIO_BUTTON( eventWidget[i] ) ); + if( camEventFlags[i][1] == false ) + gtk_widget_set_sensitive (eventWidget[i], FALSE); + } + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_label_new( "Parameters:" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + parameters = gtk_entry_new(); + gtk_box_pack_start( GTK_BOX( hbox ), parameters, TRUE, TRUE, 0 ); + gtk_widget_show( parameters ); + + // -------------------------- // + + w = gtk_hseparator_new (); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2); + gtk_widget_show (w); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_button_new_with_label ("Ok"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDOK)); + gtk_widget_show (w); + + GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT ); + gtk_widget_grab_default( w ); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (eIDCANCEL)); + gtk_widget_show (w); + ret = eIDCANCEL; + + // -------------------------- // + + gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER); + gtk_widget_show (window); + gtk_grab_add (window); + + bool dialogError = TRUE; + while (dialogError) { + loop = 1; + while (loop) + gtk_main_iteration (); + + dialogError = FALSE; + + if( ret == eIDOK ) { + const char *str = gtk_entry_get_text( GTK_ENTRY(parameters) ); + + if( !camEventFlags[i][0] || ( str && str[0] ) ) { + int type = 0; +// GList *li; + + for( type = 1; type < EVENT_COUNT; type++ ) { + if( gtk_toggle_button_get_active( (GtkToggleButton*)eventWidget[type] ) ) + break; + } + + // Add the event + GetCurrentCam()->GetCam()->addEvent( static_cast(type), str, (long)(g_pTimeLine->value) ); + + // Refresh event list + RefreshEventList(); + } else { + dialogError = TRUE; + } + } + } + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return TRUE; +} + +static gint ci_del( GtkWidget *widget, gpointer data ) +{ + // TODO: add support to splines lib + if( GetCurrentCam() && GTK_CLIST(g_pEventsList)->focus_row >= 0 ) { + GetCurrentCam()->GetCam()->removeEvent( GTK_CLIST(g_pEventsList)->focus_row ); + // Refresh event list + RefreshEventList(); + } + + return TRUE; +} + +static gint ci_timeline_changed( GtkAdjustment *adjustment ) +{ + char buf[128]; + + sprintf( buf, "%.2f", adjustment->value / 1000.f ); + gtk_label_set_text( g_pCurrentTime, buf ); + + // FIXME: this will never work completely perfect. Startcamera calls buildcamera, which sets all events to 'nottriggered'. + // So if you have a wait at the end of the path, this will go to nontriggered immediately when you go over it and the camera + // will have no idea where on the track it should be. + if( GetCurrentCam() && gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(g_pTrackCamera) ) ) { + float fov; + vec3_t origin = { 0.0f, 0.0f, 0.0f }, dir = { 0.0f, 0.0f, 0.0f}, angles; + + GetCurrentCam()->GetCam()->startCamera( 0 ); + + GetCurrentCam()->GetCam()->getCameraInfo( (long)(adjustment->value), &origin[0], &dir[0], &fov ); + VectorSet( angles, asin (dir[2])*180/3.14159, atan2 (dir[1], dir[0])*180/3.14159, 0 ); + g_CameraTable.m_pfnSetCamera( origin, angles ); + } + + return TRUE; +} + +GtkWidget *CreateCameraInspectorDialog( void ) +{ + GtkWidget *window, *w, *vbox, *hbox, *table, *frame; + + // create the window + window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); + gtk_window_set_title( GTK_WINDOW (window), "Camera Inspector" ); + gtk_signal_connect( GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC( ci_close ), NULL ); + gtk_signal_connect( GTK_OBJECT (window), "expose_event", GTK_SIGNAL_FUNC( ci_expose ), NULL ); + // gtk_signal_connect( GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC( gtk_widget_destroy ), NULL ); + gtk_window_set_transient_for( GTK_WINDOW( window ), GTK_WINDOW( g_pRadiantWnd ) ); + + // don't use show, as you don't want to have it displayed on startup ;-) + gtk_widget_realize( window ); + + // fill the window + + // the table + // -------------------------- // + + table = gtk_table_new( 3, 2, FALSE ); + gtk_widget_show( table ); + gtk_container_add( GTK_CONTAINER( window ), table ); + gtk_container_set_border_width( GTK_CONTAINER( table ), 5 ); + gtk_table_set_row_spacings( GTK_TABLE( table ), 5 ); + gtk_table_set_col_spacings( GTK_TABLE( table ), 5 ); + + // the properties column + // -------------------------- // + + vbox = gtk_vbox_new( FALSE, 5 ); + gtk_widget_show( vbox ); + gtk_table_attach( GTK_TABLE( table ), vbox, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0 ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_label_new( "File:" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + g_pCamListCombo = gtk_combo_new(); + gtk_box_pack_start (GTK_BOX( hbox ), g_pCamListCombo, TRUE, TRUE, 0); + gtk_widget_show( g_pCamListCombo ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_label_new( "Name:" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + g_pCamName = gtk_entry_new(); + gtk_box_pack_start( GTK_BOX( hbox ), g_pCamName, FALSE, FALSE, 0 ); + gtk_widget_show( g_pCamName ); + + w = gtk_label_new( "Type: " ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + w = gtk_label_new( "" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + g_pCamType = GTK_LABEL( w ); + + RefreshCamListCombo(); + + gtk_entry_set_editable( GTK_ENTRY( GTK_COMBO(g_pCamListCombo)->entry ), FALSE ); + gtk_signal_connect( GTK_OBJECT(GTK_COMBO(g_pCamListCombo)->entry), "changed", GTK_SIGNAL_FUNC( ci_camlist_changed ), NULL ); + + // -------------------------- // + + frame = gtk_frame_new( "Path and Target editing" ); + gtk_widget_show( frame ); + gtk_table_attach( GTK_TABLE( table ), frame, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0 ); + + vbox = gtk_vbox_new( FALSE, 5 ); + gtk_container_add( GTK_CONTAINER( frame ), vbox ); + gtk_container_set_border_width( GTK_CONTAINER (vbox), 5 ); + gtk_widget_show( vbox ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_label_new( "Edit:" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + g_pPathListCombo = gtk_combo_new(); + gtk_box_pack_start (GTK_BOX( hbox ), g_pPathListCombo, TRUE, TRUE, 0); + gtk_widget_show( g_pPathListCombo ); + + RefreshPathListCombo(); + + gtk_entry_set_editable( GTK_ENTRY( GTK_COMBO(g_pPathListCombo)->entry ), FALSE ); + gtk_signal_connect( GTK_OBJECT(GTK_COMBO(g_pPathListCombo)->entry), "changed", GTK_SIGNAL_FUNC( ci_pathlist_changed ), NULL ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + g_pEditModeEditRadioButton = gtk_radio_button_new_with_label( g_pEditTypeRadio, "Edit Points" ); + gtk_box_pack_start( GTK_BOX( hbox ), g_pEditModeEditRadioButton, FALSE, FALSE, 3 ); + gtk_widget_show( g_pEditModeEditRadioButton ); + g_pEditTypeRadio = gtk_radio_button_group( GTK_RADIO_BUTTON( g_pEditModeEditRadioButton ) ); + + gtk_signal_connect( GTK_OBJECT( g_pEditModeEditRadioButton ), "clicked", GTK_SIGNAL_FUNC( ci_editmode_edit ), NULL ); + + g_pEditModeAddRadioButton = gtk_radio_button_new_with_label( g_pEditTypeRadio, "Add Points" ); + gtk_box_pack_start( GTK_BOX( hbox ), g_pEditModeAddRadioButton, FALSE, FALSE, 3 ); + gtk_widget_show( g_pEditModeAddRadioButton ); + g_pEditTypeRadio = gtk_radio_button_group( GTK_RADIO_BUTTON( g_pEditModeAddRadioButton ) ); + + gtk_signal_connect( GTK_OBJECT( g_pEditModeAddRadioButton ), "clicked", GTK_SIGNAL_FUNC( ci_editmode_add ), NULL ); + + // see if we should use a different default + if( g_iEditMode == 1 ) { + // Go to editmode Add + gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(g_pEditModeAddRadioButton), TRUE ); + } + + w = gtk_label_new( "Type: " ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + w = gtk_label_new( "" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + g_pPathType = GTK_LABEL( w ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_button_new_with_label( "Rename..." ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, TRUE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( ci_rename ), NULL ); + gtk_widget_show( w ); + + w = gtk_button_new_with_label( "Add Target..." ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, TRUE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( ci_add_target ), NULL ); + gtk_widget_show( w ); + + // not available in splines library + /*w = gtk_button_new_with_label( "Delete Selected" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, TRUE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( ci_delete_selected ), NULL ); + gtk_widget_show( w ); + + w = gtk_button_new_with_label( "Select All" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, TRUE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( ci_select_all ), NULL ); + gtk_widget_show( w );*/ + + // -------------------------- // + + frame = gtk_frame_new( "Time" ); + gtk_widget_show( frame ); + gtk_table_attach( GTK_TABLE( table ), frame, 0, 1, 2, 3, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0 ); + + vbox = gtk_vbox_new( FALSE, 5 ); + gtk_container_add( GTK_CONTAINER( frame ), vbox ); + gtk_container_set_border_width( GTK_CONTAINER (vbox), 5 ); + gtk_widget_show( vbox ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_label_new( "Length (seconds):" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + g_pSecondsEntry = gtk_entry_new(); + gtk_box_pack_start( GTK_BOX( hbox ), g_pSecondsEntry, FALSE, FALSE, 0 ); + gtk_widget_show( g_pSecondsEntry ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_label_new( "Current Time: " ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + w = gtk_label_new( "0.00" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + g_pCurrentTime = GTK_LABEL( w ); + + w = gtk_label_new( " of " ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + w = gtk_label_new( "0.00" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + g_pTotalTime = GTK_LABEL( w ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + g_pTimeLine = GTK_ADJUSTMENT( gtk_adjustment_new( 0, 0, 30000, 100, 250, 0 ) ); + gtk_signal_connect( GTK_OBJECT(g_pTimeLine), "value_changed", GTK_SIGNAL_FUNC( ci_timeline_changed ), NULL ); + w = gtk_hscale_new( g_pTimeLine ); + gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 ); + gtk_widget_show( w ); + gtk_scale_set_draw_value( GTK_SCALE( w ), FALSE ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + g_pTrackCamera = gtk_check_button_new_with_label( "Track Camera" ); + gtk_box_pack_start( GTK_BOX( hbox ), g_pTrackCamera, FALSE, FALSE, 0 ); + gtk_widget_show( g_pTrackCamera ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_label_new( "Events:" ); + gtk_box_pack_start( GTK_BOX( hbox ), w, FALSE, FALSE, 0 ); + gtk_widget_show( w ); + + // -------------------------- // + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_scrolled_window_new( NULL, NULL ); + gtk_widget_set_usize( w, 0, 150 ); + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC ); + gtk_box_pack_start( GTK_BOX( hbox ), w, TRUE, TRUE, 0 ); + gtk_widget_show( w ); + + g_pEventsList = gtk_clist_new( 3 ); + gtk_container_add( GTK_CONTAINER(w), g_pEventsList); + //gtk_signal_connect( GTK_OBJECT(g_pEventsList), "select_row", GTK_SIGNAL_FUNC (proplist_select_row), NULL); + gtk_clist_set_selection_mode( GTK_CLIST(g_pEventsList), GTK_SELECTION_BROWSE ); + gtk_clist_column_titles_hide( GTK_CLIST(g_pEventsList) ); + gtk_clist_set_column_auto_resize( GTK_CLIST(g_pEventsList), 0, TRUE ); + gtk_clist_set_column_auto_resize( GTK_CLIST(g_pEventsList), 1, TRUE ); + gtk_clist_set_column_auto_resize( GTK_CLIST(g_pEventsList), 2, TRUE ); + gtk_widget_show( g_pEventsList ); + + vbox = gtk_vbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( hbox ), vbox, FALSE, FALSE, 0 ); + gtk_widget_show( vbox ); + + w = gtk_button_new_with_label( "Add..." ); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( ci_add ), NULL ); + gtk_widget_show( w ); + + w = gtk_button_new_with_label( "Del" ); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( ci_del ), NULL ); + gtk_widget_show( w ); + + // -------------------------- // + + /*/ + | + | + | + */ + + // the buttons column + // -------------------------- // + + vbox = gtk_vbox_new( FALSE, 5 ); + gtk_widget_show( vbox ); + gtk_table_attach( GTK_TABLE( table ), vbox, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0 ); + + w = gtk_button_new_with_label( "New..." ); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( ci_new ), NULL ); + gtk_widget_show( w ); + + w = gtk_button_new_with_label( "Load..." ); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( ci_load ), NULL ); + gtk_widget_show( w ); + + // -------------------------- // + + vbox = gtk_vbox_new( FALSE, 5 ); + gtk_widget_show( vbox ); + gtk_table_attach( GTK_TABLE( table ), vbox, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0 ); + + w = gtk_button_new_with_label( "Save..." ); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( ci_save ), NULL ); + gtk_widget_show( w ); + + w = gtk_button_new_with_label( "Unload" ); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( ci_unload ), NULL ); + gtk_widget_show( w ); + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, TRUE, TRUE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_button_new_with_label( "Apply" ); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( ci_apply ), NULL ); + gtk_widget_show( w ); + + w = gtk_button_new_with_label( "Preview" ); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( ci_preview ), NULL ); + gtk_widget_show( w ); + + // -------------------------- // + + vbox = gtk_vbox_new( FALSE, 5 ); + gtk_widget_show( vbox ); + gtk_table_attach( GTK_TABLE( table ), vbox, 1, 2, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0 ); + + hbox = gtk_hbox_new( FALSE, 5 ); + gtk_box_pack_start( GTK_BOX( vbox ), hbox, TRUE, TRUE, 0 ); + gtk_widget_show( hbox ); + + w = gtk_button_new_with_label( "Close" ); + gtk_box_pack_start( GTK_BOX( vbox ), w, FALSE, FALSE, 0); + gtk_signal_connect( GTK_OBJECT( w ), "clicked", GTK_SIGNAL_FUNC( ci_close ), NULL ); + GTK_WIDGET_SET_FLAGS( w, GTK_CAN_DEFAULT ); + gtk_widget_grab_default( w ); + gtk_widget_show( w ); + + // -------------------------- // + + return window; +} diff --git a/contrib/camera/dialogs.h b/contrib/camera/dialogs.h new file mode 100644 index 00000000..4df27eae --- /dev/null +++ b/contrib/camera/dialogs.h @@ -0,0 +1,37 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Camera plugin for GtkRadiant +Copyright (C) 2002 Splash Damage Ltd. +*/ + +struct TwinWidget { + GtkWidget* one; + GtkWidget* two; +}; + +void dialog_button_callback (GtkWidget *widget, gpointer data); +gint dialog_delete_callback (GtkWidget *widget, GdkEvent* event, gpointer data); +//void dialog_button_callback_settex (GtkWidget *widget, gpointer data); + +void RefreshCamListCombo( void ); +GtkWidget *CreateCameraInspectorDialog( void ); diff --git a/contrib/camera/dialogs_common.cpp b/contrib/camera/dialogs_common.cpp new file mode 100644 index 00000000..43a4e70e --- /dev/null +++ b/contrib/camera/dialogs_common.cpp @@ -0,0 +1,51 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Camera plugin for GtkRadiant +Copyright (C) 2002 Splash Damage Ltd. +*/ + +#include "camera.h" + +void dialog_button_callback (GtkWidget *widget, gpointer data) +{ + GtkWidget *parent; + int *loop, *ret; + + parent = gtk_widget_get_toplevel (widget); + loop = (int*)g_object_get_data (G_OBJECT (parent), "loop"); + ret = (int*)g_object_get_data (G_OBJECT (parent), "ret"); + + *loop = 0; + *ret = gpointer_to_int (data); +} + +gint dialog_delete_callback (GtkWidget *widget, GdkEvent* event, gpointer data) +{ + int *loop; + + gtk_widget_hide (widget); + loop = (int*)g_object_get_data (G_OBJECT (widget), "loop"); + *loop = 0; + + return TRUE; +} diff --git a/contrib/camera/funchandlers.cpp b/contrib/camera/funchandlers.cpp new file mode 100644 index 00000000..f958ad96 --- /dev/null +++ b/contrib/camera/funchandlers.cpp @@ -0,0 +1,271 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Camera plugin for GtkRadiant +Copyright (C) 2002 Splash Damage Ltd. +*/ + +#include "camera.h" + +extern GtkWidget *g_pEditModeAddRadioButton; + +char* Q_realpath(const char *path, char *resolved_path, size_t size) +{ +#if defined(POSIX) + return realpath(path, resolved_path); +#elif defined(WIN32) + return _fullpath(resolved_path, path, size); +#else +#error "unsupported platform" +#endif +} + +static void DoNewCamera( idCameraPosition::positionType type ) +{ + CCamera *cam = AllocCam(); + + if( cam ) { + char buf[128]; + sprintf( buf, "camera%i", cam->GetCamNum() ); + + cam->GetCam()->startNewCamera( type ); + cam->GetCam()->setName( buf ); + cam->GetCam()->buildCamera(); + + sprintf( buf, "Unsaved Camera %i", cam->GetCamNum() ); + cam->SetFileName( buf, false ); + + SetCurrentCam( cam ); + RefreshCamListCombo(); + + // Go to editmode Add + gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(g_pEditModeAddRadioButton), TRUE ); + + // Show the camera inspector + DoCameraInspector(); + + // Start edit mode (if not initiated by DoCameraInspector) + if( !g_bEditOn ) + DoStartEdit( GetCurrentCam() ); + } else { + g_FuncTable.m_pfnMessageBox( (GtkWidget *)g_pRadiantWnd, "No free cameras available.", "Create Camera Error", eMB_OK ); + } +} + +void DoNewFixedCamera() +{ + DoNewCamera( idCameraPosition::FIXED ); +} + +void DoNewInterpolatedCamera() +{ + DoNewCamera( idCameraPosition::INTERPOLATED ); +} + +void DoNewSplineCamera() +{ + DoNewCamera( idCameraPosition::SPLINE ); +} + +void DoCameraInspector() +{ + gtk_widget_show( g_pCameraInspectorWnd ); +} + +void DoPreviewCamera() +{ + if( GetCurrentCam() ) { + g_iPreviewRunning = 1; + g_FuncTable.m_pfnSysUpdateWindows( W_XY_OVERLAY | W_CAMERA ); + } +} + +void DoLoadCamera() +{ + char basepath[PATH_MAX]; + + if( firstCam && firstCam->HasBeenSaved() ) + ExtractFilePath( firstCam->GetFileName(), basepath ); + else + strcpy( basepath, g_FuncTable.m_pfnGetGamePath() ); + + const gchar *filename = g_FuncTable.m_pfnFileDialog( (GtkWidget *)g_pRadiantWnd, TRUE, "Open Camera File", basepath, "camera"); + + if( filename ) + { + CCamera *cam = AllocCam(); + char fullpathtofile[PATH_MAX]; + + if( cam ) { + Q_realpath(filename, fullpathtofile, PATH_MAX); + + // see if this camera file was already loaded + CCamera *checkCam = firstCam->GetNext(); // not the first one as we just allocated it + while( checkCam ) { + if( !strcmp( fullpathtofile, checkCam->GetFileName() ) ) { + char error[PATH_MAX+64]; + FreeCam( cam ); + sprintf( error, "Camera file \'%s\' is already loaded", fullpathtofile ); + g_FuncTable.m_pfnMessageBox( (GtkWidget *)g_pRadiantWnd, error, "Load error", eMB_OK ); + //g_free( filename ); + return; + } + checkCam = checkCam->GetNext(); + } + + if( loadCamera( cam->GetCamNum(), fullpathtofile ) ) { + cam->GetCam()->buildCamera(); + cam->SetFileName( filename, true ); + SetCurrentCam( cam ); + RefreshCamListCombo(); + g_FuncTable.m_pfnSysUpdateWindows( W_XY_OVERLAY | W_CAMERA ); + } else { + char error[PATH_MAX+64]; + FreeCam( cam ); + sprintf( error, "An error occured during the loading of \'%s\'", fullpathtofile ); + g_FuncTable.m_pfnMessageBox( (GtkWidget *)g_pRadiantWnd, error, "Load error", eMB_OK ); + } + + //g_free( filename ); + } else { + g_FuncTable.m_pfnMessageBox( (GtkWidget *)g_pRadiantWnd, "No free camera slots available", "Load error", eMB_OK ); + } + } +} + +void DoSaveCamera() { + char basepath[PATH_MAX]; + + if( !GetCurrentCam() ) + return; + + if( GetCurrentCam()->GetFileName()[0] ) + ExtractFilePath( GetCurrentCam()->GetFileName(), basepath ); + else + strcpy( basepath, g_FuncTable.m_pfnGetGamePath() ); + + const gchar *filename = g_FuncTable.m_pfnFileDialog( g_pRadiantWnd, FALSE, "Save Camera File", basepath, "camera"); + + if( filename ) { + char fullpathtofile[PATH_MAX + 8]; + + Q_realpath(filename, fullpathtofile, PATH_MAX); + + // File dialog from windows (and maybe the gtk one from radiant) doesn't handle default extensions properly. + // Add extension and check again if file exists + if( strcmp( fullpathtofile + (strlen(fullpathtofile) - 7), ".camera" ) ) { + strcat( fullpathtofile, ".camera" ); + + if( FileExists( fullpathtofile ) ) { + if( g_FuncTable.m_pfnMessageBox( (GtkWidget *)g_pRadiantWnd, "File already exists.\nOverwrite?", "Save Camera File", eMB_YESNO ) == eIDNO ) + return; + } + } + + // see if this camera file was already loaded + CCamera *checkCam = firstCam; + while( checkCam ) { + if( checkCam == GetCurrentCam() ) { + checkCam = checkCam->GetNext(); + if( !checkCam ) // we only have one camera file opened so no need to check further + break; + } else if( !strcmp( fullpathtofile, checkCam->GetFileName() ) ) { + char error[PATH_MAX+64]; + sprintf( error, "Camera file \'%s\' is currently loaded by GtkRadiant.\nPlease select a different filename.", fullpathtofile ); + g_FuncTable.m_pfnMessageBox( (GtkWidget *)g_pRadiantWnd, error, "Save error", eMB_OK ); + return; + } + checkCam = checkCam->GetNext(); + } + + // FIXME: check for existing directory + + GetCurrentCam()->GetCam()->save( fullpathtofile ); + GetCurrentCam()->SetFileName( fullpathtofile, true ); + RefreshCamListCombo(); + } +} + +void DoUnloadCamera() { + if( !GetCurrentCam() ) + return; + + if( !GetCurrentCam()->HasBeenSaved() ) { + char buf[PATH_MAX+64]; + sprintf( buf, "Do you want to save the changes for camera '%s'?", GetCurrentCam()->GetCam()->getName() ); + if( g_FuncTable.m_pfnMessageBox( (GtkWidget *)g_pRadiantWnd, buf, "Warning", eMB_YESNO ) == eIDYES ) + DoSaveCamera(); + } else if( GetCurrentCam()->HasBeenSaved() == 2 ) { + char buf[PATH_MAX+64]; + sprintf( buf, "Do you want to save the changes made to camera file '%s'?", GetCurrentCam()->GetFileName() ); + if( g_FuncTable.m_pfnMessageBox( (GtkWidget *)g_pRadiantWnd, buf, "Warning", eMB_YESNO ) == eIDYES ) + DoSaveCamera(); + } + + if( g_pCurrentEditCam ) { + DoStopEdit(); + g_pCurrentEditCam = NULL; + } + + FreeCam( GetCurrentCam() ); + SetCurrentCam( NULL ); + RefreshCamListCombo(); +} + +CCamera *g_pCurrentEditCam = NULL; + +void DoStartEdit( CCamera *cam ) { + if( g_pCurrentEditCam ) { + DoStopEdit(); + g_pCurrentEditCam = NULL; + } + + if( cam ) { + g_bEditOn = true; + + if( !Listener ) + Listener = new CListener; + + cam->GetCam()->startEdit( g_iActiveTarget < 0 ? true : false ); + + g_pCurrentEditCam = cam; + + g_FuncTable.m_pfnSysUpdateWindows( W_XY_OVERLAY | W_CAMERA ); + } +} + +void DoStopEdit( void ) { + g_bEditOn = false; + + if( Listener ) { + delete Listener; + Listener = NULL; + } + + if( g_pCurrentEditCam ) { + // stop editing on the current cam + g_pCurrentEditCam->GetCam()->stopEdit(); + g_pCurrentEditCam = NULL; + + g_FuncTable.m_pfnSysUpdateWindows( W_XY_OVERLAY | W_CAMERA ); + } +} diff --git a/contrib/camera/funchandlers.h b/contrib/camera/funchandlers.h new file mode 100644 index 00000000..0c97f610 --- /dev/null +++ b/contrib/camera/funchandlers.h @@ -0,0 +1,37 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Camera plugin for GtkRadiant +Copyright (C) 2002 Splash Damage Ltd. +*/ + +void DoNewFixedCamera(); +void DoNewInterpolatedCamera(); +void DoNewSplineCamera(); +void DoCameraInspector(); +void DoPreviewCamera(); +void DoLoadCamera(); +void DoSaveCamera(); +void DoUnloadCamera(); +void DoStartEdit( CCamera *cam ); +void DoStopEdit( void ); + diff --git a/contrib/camera/listener.cpp b/contrib/camera/listener.cpp new file mode 100644 index 00000000..f3169b8d --- /dev/null +++ b/contrib/camera/listener.cpp @@ -0,0 +1,234 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Camera plugin for GtkRadiant +Copyright (C) 2002 Splash Damage Ltd. +*/ + +#include "camera.h" + +CListener::CListener() +{ + refCount = 1; + + m_bHooked = FALSE; + + m_bLeftMBPressed = m_bRightMBPressed = m_bMiddleMBPressed = false; + + oldValid = false; + + Register(); +} + +CListener::~CListener() +{ + UnRegister(); +} + +void CListener::Register() +{ + g_UITable.m_pfnHookWindow( this ); + g_pXYWndWrapper = g_UITable.m_pfnGetXYWndWrapper(); + m_bHooked = TRUE; +} + +void CListener::UnRegister() +{ + if(m_bHooked) + { + g_UITable.m_pfnUnHookWindow( this ); + g_pXYWndWrapper= NULL; + m_bHooked = FALSE; + } +} + +bool CListener::OnMouseMove( unsigned int nFlags, double x, double y ) +{ + SetViewType( g_pXYWndWrapper->GetViewType() ); + + if( m_bLeftMBPressed && oldValid && g_iEditMode == 0 ) { + vec3_t click, delta; + + g_pXYWndWrapper->SnapToGrid( (int)x, (int)y, click ); + + switch( m_vt ) { + case XY: + VectorSet( delta, click[0] - old_x, click[1] - old_y, 0 ); + old_x = click[0]; old_y = click[1]; + break; + case XZ: + VectorSet( delta, click[0] - old_x, 0, click[2] - old_y ); + old_x = click[0]; old_y = click[2]; + break; + case YZ: + VectorSet( delta, 0, click[1] - old_x, click[2] - old_y ); + old_x = click[1]; old_y = click[2]; + break; + } + + if( g_iActiveTarget < 0 ) + GetCurrentCam()->GetCam()->getPositionObj()->updateSelection( delta[0], delta[1], delta[2] ); + else + GetCurrentCam()->GetCam()->getActiveTarget(g_iActiveTarget)->updateSelection( delta[0], delta[1], delta[2] ); + + GetCurrentCam()->HasBeenModified(); + + g_FuncTable.m_pfnSysUpdateWindows( W_XY_OVERLAY | W_CAMERA ); + + return true; + } + + return false; +} + +bool CListener::OnLButtonDown( unsigned int nFlags, double x, double y ) +{ + SetViewType( g_pXYWndWrapper->GetViewType() ); + + m_bLeftMBPressed = true; + oldValid = true; + + vec3_t org, delta; + + g_pXYWndWrapper->SnapToGrid( (int)x, (int)y, org ); + + switch( m_vt ) { + case XY: + old_x = org[0]; old_y = org[1]; org[2] = 64*1024; + VectorSet( delta, 0, 0, -1 ); + break; + case XZ: + old_x = org[0]; old_y = org[2]; org[1] = 64*1024; + VectorSet( delta, 0, -1, 0 ); + break; + case YZ: + old_x = org[1]; old_y = org[2]; org[0] = 64*1024; + VectorSet( delta, -1, 0, 0 ); + break; + } + + if( g_iEditMode == 0 ) { + if( g_iActiveTarget < 0 ) + GetCurrentCam()->GetCam()->getPositionObj()->selectPointByRay( org[0], org[1], org[2], delta[0], delta[1], delta[2], true ); + else + GetCurrentCam()->GetCam()->getActiveTarget(g_iActiveTarget)->selectPointByRay( org[0], org[1], org[2], delta[0], delta[1], delta[2], true ); + } else if( g_iEditMode == 1 ) { + idVec3 *lastcoord; + idCameraPosition *camera; + + if( g_iActiveTarget < 0 ) { + camera = GetCurrentCam()->GetCam()->getPositionObj(); + } else { + camera = GetCurrentCam()->GetCam()->getActiveTarget(g_iActiveTarget); + } + + if( camera->numPoints() ) { + lastcoord = camera->getPoint( camera->numPoints() -1 ); + switch( m_vt ) { + case XY: + camera->addPoint( org[0], org[1], lastcoord->z ); + break; + case XZ: + camera->addPoint( org[0], lastcoord->y, org[2] ); + break; + case YZ: + camera->addPoint( lastcoord->x, org[1], org[2] ); + break; + } + } else { + switch( m_vt ) { + case XY: + camera->addPoint( org[0], org[1], 0 ); + break; + case XZ: + camera->addPoint( org[0], 0, org[2] ); + break; + case YZ: + camera->addPoint( 0, org[1], org[2] ); + break; + } + } + + GetCurrentCam()->HasBeenModified(); + } + + g_FuncTable.m_pfnSysUpdateWindows( W_XY_OVERLAY | W_CAMERA ); + + return true; + + //return false; +} + +bool CListener::OnLButtonUp( unsigned int nFlags, double x, double y ) +{ + SetViewType( g_pXYWndWrapper->GetViewType() ); + + m_bLeftMBPressed = false; + oldValid = false; + + if( g_iEditMode == 0 ) { + if( g_iActiveTarget < 0 ) + GetCurrentCam()->GetCam()->getPositionObj()->deselectAll(); + else + GetCurrentCam()->GetCam()->getActiveTarget(g_iActiveTarget)->deselectAll(); + + g_FuncTable.m_pfnSysUpdateWindows( W_XY_OVERLAY | W_CAMERA ); + } + + return false; +} + +bool CListener::OnRButtonDown( unsigned int nFlags, double x, double y ) +{ + SetViewType( g_pXYWndWrapper->GetViewType() ); + + m_bRightMBPressed = true; + + return false; +} + +bool CListener::OnRButtonUp( unsigned int nFlags, double x, double y ) +{ + SetViewType( g_pXYWndWrapper->GetViewType() ); + + m_bRightMBPressed = false; + + return false; +} + +bool CListener::OnMButtonDown( unsigned int nFlags, double x, double y ) +{ + SetViewType( g_pXYWndWrapper->GetViewType() ); + + m_bMiddleMBPressed = true; + + return false; +} + +bool CListener::OnMButtonUp( unsigned int nFlags, double x, double y ) +{ + SetViewType( g_pXYWndWrapper->GetViewType() ); + + m_bMiddleMBPressed = false; + + return false; +} diff --git a/contrib/camera/listener.h b/contrib/camera/listener.h new file mode 100644 index 00000000..72bf1fdb --- /dev/null +++ b/contrib/camera/listener.h @@ -0,0 +1,64 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Camera plugin for GtkRadiant +Copyright (C) 2002 Splash Damage Ltd. +*/ + +class CListener : public IWindowListener +{ +public: + bool OnMouseMove(guint32 nFlags, gdouble x, gdouble y); + bool OnLButtonDown(guint32 nFlags, gdouble x, gdouble y); + bool OnMButtonDown(guint32 nFlags, gdouble x, gdouble y); + bool OnRButtonDown(guint32 nFlags, gdouble x, gdouble y); + bool OnLButtonUp(guint32 nFlags, gdouble x, gdouble y); + bool OnMButtonUp(guint32 nFlags, gdouble x, gdouble y); + bool OnRButtonUp(guint32 nFlags, gdouble x, gdouble y); + bool OnKeyPressed(char *s) { return false; } + bool Paint() { return true; } + void Close() { } + + void UnRegister(); + void Register(); + CListener(); + virtual ~CListener(); + + void IncRef() { refCount++; } + void DecRef() { refCount--; if (refCount <= 0) delete this; } + + void SetViewType( VIEWTYPE vt ) { if( m_vt != vt ) oldValid = false; m_vt = vt; } + +private: + IXYWndWrapper *g_pXYWndWrapper; + + bool m_bHooked; + int refCount; + VIEWTYPE m_vt; + + // mouse button status + bool m_bLeftMBPressed, m_bRightMBPressed, m_bMiddleMBPressed; + + // old mouse coordinates + bool oldValid; + gdouble old_x, old_y; +}; diff --git a/contrib/camera/misc.cpp b/contrib/camera/misc.cpp new file mode 100644 index 00000000..49148248 --- /dev/null +++ b/contrib/camera/misc.cpp @@ -0,0 +1,243 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Camera plugin for GtkRadiant +Copyright (C) 2002 Splash Damage Ltd. +*/ + +#include "camera.h" + +void Sys_ERROR( char* text, ... ) +{ + va_list argptr; + char buf[32768]; + + va_start (argptr,text); + vsprintf (buf, text,argptr); + va_end (argptr); + + Sys_Printf("Camera::ERROR->%s", buf); +} + +char* UnixToDosPath( char* path ) +{ +#ifndef WIN32 + return path; +#else + for(char* p = path; *p; p++) + { + if(*p == '/') + *p = '\\'; + } + return path; +#endif +} + +void ExtractFilePath( const char *path, char *dest ) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && *(src-1) != '/' && *(src-1) != '\\') + src--; + + memcpy (dest, path, src-path); + dest[src-path] = 0; +} + +const char* ExtractFilename( const char* path ) +{ + char* p = strrchr(path, '/'); + if(!p) + { + p = strrchr(path, '\\'); + + if(!p) + return path; + } + return ++p; +} + +int Q_stricmp (const char *s1, const char *s2) { + return string_equal_nocase( s1, s2 ); +} + +/* +============== +FileExists +============== +*/ +bool FileExists (const char *filename) +{ + FILE *f; + + f = fopen( filename, "r" ); + if( !f ) + return false; + fclose( f ); + return true; +} + +// +// command buffer +// empty wrappers, don't really use them here +// +void Cbuf_AddText( const char *text ) {}; +void Cbuf_Execute (void) {}; + +// +// Common +// + +void CDECL Com_Error( int level, const char *error, ... ) +{ + va_list argptr; + char buf[32768]; + + va_start (argptr,error); + vsprintf (buf, error,argptr); + va_end (argptr); + + Sys_Printf("Camera::ERROR->%s", buf); +} + +void CDECL Com_Printf( const char* msg, ... ) +{ + va_list argptr; + char buf[32768]; + + va_start (argptr,msg); + vsprintf (buf, msg,argptr); + va_end (argptr); + + Sys_Printf("Camera::%s", buf); +} + +void CDECL Com_DPrintf( const char* msg, ... ) +{ +#ifdef _DEBUG + va_list argptr; + char buf[32768]; + + va_start (argptr,msg); + vsprintf (buf, msg,argptr); + va_end (argptr); + + Sys_Printf("Camera::%s", buf); +#endif +} + +void *Com_Allocate( int bytes ) { + return( malloc( bytes ) ); +} + +void Com_Dealloc( void *ptr ) { + free( ptr ); +} + +// +// Filesystem +// + +#ifdef WIN32 + #pragma warning(disable : 4311) + #pragma warning(disable : 4312) +#endif + +int FS_Read( void *buffer, int len, fileHandle_t f ) { + return fread( buffer, len, 1, (FILE *)f ); +} + +int FS_Write( const void *buffer, int len, fileHandle_t h ) { + return fwrite( buffer, len, 1, (FILE *)h ); +} + +int FS_ReadFile( const char *qpath, void **buffer ) { + fileHandle_t h; + byte* buf; + int len; + + buf = NULL; + + len = FS_FOpenFileRead( qpath, &h, qfalse ); + + if( h == 0 ) { + if ( buffer ) { + *buffer = NULL; + } + + return -1; + } + + buf = (byte *)Com_Allocate( len + 1 ); + + *buffer = buf; + + FS_Read (buf, len, h); + + buf[len] = 0; + FS_FCloseFile( h ); + + return len; +} + +void FS_FreeFile( void *buffer ) { + Com_Dealloc( buffer ); +} + +int FS_FOpenFileRead( const char *filename, fileHandle_t *file, bool uniqueFILE ) { + FILE *fh; + long len; + + fh = fopen( filename, "rb" ); + *file = *(fileHandle_t *)&fh; + + if( file ) + { + fseek (fh, 0, SEEK_END); + len = ftell (fh); + rewind (fh); + return len; + } + else + return -1; +} + +fileHandle_t FS_FOpenFileWrite( const char *filename ) { + FILE *fh; + fileHandle_t f; + + memset( &f, 0, sizeof(f) ); + + fh = fopen( filename, "wb" ); + + f = (fileHandle_t)fh; + return f; +} + +void FS_FCloseFile( fileHandle_t f ) { + fclose( (FILE *)f ); +} diff --git a/contrib/camera/misc.h b/contrib/camera/misc.h new file mode 100644 index 00000000..913b7c4a --- /dev/null +++ b/contrib/camera/misc.h @@ -0,0 +1,76 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Camera plugin for GtkRadiant +Copyright (C) 2002 Splash Damage Ltd. +*/ + +void Sys_ERROR( char* text, ... ); +char* UnixToDosPath( char* path ); +void ExtractFilePath( const char *path, char *dest ); +const char* ExtractFilename( const char* path ); +bool FileExists (const char *filename); +int Q_stricmp (const char *s1, const char *s2); + +typedef int fileHandle_t; + +extern "C" { +// command buffer +void Cbuf_AddText( const char *text ); +void Cbuf_Execute (void); + +// common +#ifndef CDECL +#ifdef WIN32 + #define CDECL __cdecl +#else + #define CDECL +#endif +#endif + +void CDECL Com_Error( int level, const char *error, ... ); +void CDECL Com_Printf( const char *msg, ... ); +void CDECL Com_DPrintf( const char *msg, ... ); +void *Com_Allocate( int bytes ); +void Com_Dealloc( void *ptr ); + +// filesystem +int FS_Read( void *buffer, int len, fileHandle_t f ); +int FS_Write( const void *buffer, int len, fileHandle_t h ); +int FS_ReadFile( const char *qpath, void **buffer ); +void FS_FreeFile( void *buffer ); +int FS_FOpenFileRead( const char *filename, fileHandle_t *file, bool uniqueFILE ); +fileHandle_t FS_FOpenFileWrite( const char *filename ); +void FS_FCloseFile( fileHandle_t f ); +} + +// vectors +#define DotProduct4(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]+(x)[3]*(y)[3]) +#define VectorSubtract4(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2],(c)[3]=(a)[3]-(b)[3]) +#define VectorAdd4(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2],(c)[3]=(a)[3]+(b)[3]) +#define VectorCopy4(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) +#define VectorScale4(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s),(o)[3]=(v)[3]*(s)) +#define VectorMA4(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s),(o)[3]=(v)[3]+(b)[3]*(s)) + +#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) + +#define SnapVector(v) {v[0]=(int)v[0];v[1]=(int)v[1];v[2]=(int)v[2];} diff --git a/contrib/camera/renderer.cpp b/contrib/camera/renderer.cpp new file mode 100644 index 00000000..08e6664e --- /dev/null +++ b/contrib/camera/renderer.cpp @@ -0,0 +1,183 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Camera plugin for GtkRadiant +Copyright (C) 2002 Splash Damage Ltd. +*/ + +#include "camera.h" + +CRenderer::CRenderer() { + + refCount = 1; + + m_bHooked = FALSE; + + Register(); + Initialize(); +} + +CRenderer::~CRenderer() { + if( m_bHooked ) + UnRegister(); +} + +void CRenderer::Register() { + g_QglTable.m_pfnHookGL2DWindow( this ); + g_QglTable.m_pfnHookGL3DWindow( this ); + m_bHooked = TRUE; +} + +void CRenderer::UnRegister() { + if( g_QglTable.m_nSize ) { + g_QglTable.m_pfnUnHookGL2DWindow( this ); + g_QglTable.m_pfnUnHookGL3DWindow( this ); + } + m_bHooked = FALSE; +} + +void CRenderer::Initialize() { + +} + +void CRenderer::Draw2D( VIEWTYPE vt ) { + + g_QglTable.m_pfn_qglPushAttrib(GL_ALL_ATTRIB_BITS); + g_QglTable.m_pfn_qglPushMatrix(); + + switch(vt) + { + case XY: + break; + case XZ: + g_QglTable.m_pfn_qglRotatef(270.0f, 1.0f, 0.0f, 0.0f); + break; + case YZ: + g_QglTable.m_pfn_qglRotatef(270.0f, 1.0f, 0.0f, 0.0f); + g_QglTable.m_pfn_qglRotatef(270.0f, 0.0f, 0.0f, 1.0f); + break; + } + + CCamera *cam = firstCam; + while( cam ) { + cam->GetCam()->draw( ((Listener && cam == g_pCurrentEditCam) ? true : false) ); + cam = cam->GetNext(); + } + + g_QglTable.m_pfn_qglPopMatrix(); + g_QglTable.m_pfn_qglPopAttrib(); +} + +void CRenderer::Draw3D() { + // FIXME: really need a mainloop callback from the editor core + static long start; + static float cycle; + static long msecs; + static long current; + + if( g_iPreviewRunning ) { + if( g_iPreviewRunning == 1 ) { + start = Q_QGetTickCount(); + GetCurrentCam()->GetCam()->startCamera( start ); + cycle = GetCurrentCam()->GetCam()->getTotalTime(); + msecs = (long)(cycle * 1000); + current = start; + g_iPreviewRunning = 2; + } + + if( current < start + msecs ) { + float fov; + vec3_t origin = {0.0f, 0.0f, 0.0f}, dir = {0.0f, 0.0f, 0.0f}, angles; + + GetCurrentCam()->GetCam()->getCameraInfo( current, &origin[0], &dir[0], &fov ); + VectorSet( angles, asin (dir[2])*180/3.14159, atan2 (dir[1], dir[0])*180/3.14159, 0 ); + g_CameraTable.m_pfnSetCamera( origin, angles ); + current = Q_QGetTickCount(); + } else { + g_iPreviewRunning = 0; + GetCurrentCam()->GetCam()->setRunning( false ); + g_FuncTable.m_pfnSysUpdateWindows( W_XY_OVERLAY | W_CAMERA ); + } + } + + g_QglTable.m_pfn_qglPushAttrib(GL_ALL_ATTRIB_BITS); + + CCamera *cam = firstCam; + while( cam ) { + cam->GetCam()->draw( ((Listener && cam == g_pCurrentEditCam) ? true : false) ); + cam = cam->GetNext(); + } + + if( g_iPreviewRunning ) { + int x, y, width, height, i; + float degInRad; + + g_CameraTable.m_pfnGetCamWindowExtents( &x, &y, &width, &height ); + + // setup orthographic projection mode + g_QglTable.m_pfn_qglMatrixMode(GL_PROJECTION); + g_QglTable.m_pfn_qglLoadIdentity(); + g_QglTable.m_pfn_qglDisable( GL_DEPTH_TEST ); + g_QglTable.m_pfn_qglOrtho( 0, (float)width, 0, (float)height, -100, 100 ); + g_QglTable.m_pfn_qglMatrixMode( GL_MODELVIEW ); + + g_QglTable.m_pfn_qglLoadIdentity(); + g_QglTable.m_pfn_qglColor3f( 1.f, 1.f, 1.f ); + g_QglTable.m_pfn_qglBegin( GL_LINE_LOOP ); + g_QglTable.m_pfn_qglVertex2f( 10, 10 ); + g_QglTable.m_pfn_qglVertex2f( 40, 10 ); + g_QglTable.m_pfn_qglVertex2f( 40, 25 ); + g_QglTable.m_pfn_qglVertex2f( 10, 25 ); + g_QglTable.m_pfn_qglEnd(); + + g_QglTable.m_pfn_qglBegin( GL_LINE_LOOP ); + for( i = 0; i < 360; i += 60 ) { + degInRad = i * (3.14159265358979323846/180.f); + g_QglTable.m_pfn_qglVertex2f( 18 + cos(degInRad) * 5, 18 + sin(degInRad) * 5 ); + } + g_QglTable.m_pfn_qglEnd(); + + degInRad = (360-((current - start) % 360)) * (3.14159265358979323846/180.f); + g_QglTable.m_pfn_qglBegin( GL_LINES ); + g_QglTable.m_pfn_qglVertex2f( 18, 18 ); + g_QglTable.m_pfn_qglVertex2f( 18 + cos(degInRad) * 5, 18 + sin(degInRad) * 5 ); + g_QglTable.m_pfn_qglVertex2f( 32, 18 ); + g_QglTable.m_pfn_qglVertex2f( 32 + cos(degInRad) * 5, 18 + sin(degInRad) * 5 ); + g_QglTable.m_pfn_qglEnd(); + + g_QglTable.m_pfn_qglBegin( GL_LINE_LOOP ); + for( i = 0; i < 360; i += 60 ) { + degInRad = i * (3.14159265358979323846/180.f); + g_QglTable.m_pfn_qglVertex2f( 32 + cos(degInRad) * 5, 18 + sin(degInRad) * 5 ); + } + g_QglTable.m_pfn_qglEnd(); + + g_QglTable.m_pfn_qglBegin( GL_LINES ); + g_QglTable.m_pfn_qglVertex2f( 40, 22 ); + g_QglTable.m_pfn_qglVertex2f( 52, 31 ); + g_QglTable.m_pfn_qglVertex2f( 40, 13 ); + g_QglTable.m_pfn_qglVertex2f( 52, 4 ); + g_QglTable.m_pfn_qglEnd(); + } + + g_QglTable.m_pfn_qglPopAttrib(); +} diff --git a/contrib/camera/renderer.h b/contrib/camera/renderer.h new file mode 100644 index 00000000..c73d84d4 --- /dev/null +++ b/contrib/camera/renderer.h @@ -0,0 +1,46 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Camera plugin for GtkRadiant +Copyright (C) 2002 Splash Damage Ltd. +*/ + +class CRenderer : public IGL2DWindow, public IGL3DWindow { +public: + CRenderer(); + virtual ~CRenderer(); + +protected: + int refCount; + +public: + void Register(); + void UnRegister(); + void Initialize(); + void Draw2D( VIEWTYPE vt ); + void Draw3D(); + + void IncRef() { refCount++; } + void DecRef() { refCount--; if (refCount <= 0) delete this; } + + bool m_bHooked; +}; diff --git a/contrib/gtkgensurf/.cvsignore b/contrib/gtkgensurf/.cvsignore new file mode 100644 index 00000000..7320b210 --- /dev/null +++ b/contrib/gtkgensurf/.cvsignore @@ -0,0 +1,4 @@ +Debug +*.plg +*.BAK +*.d diff --git a/contrib/gtkgensurf/CHANGES b/contrib/gtkgensurf/CHANGES new file mode 100644 index 00000000..51510b6c --- /dev/null +++ b/contrib/gtkgensurf/CHANGES @@ -0,0 +1,73 @@ +05/14/2001 + ^Fishman +=============== +bitmap.cpp +Added Hydra's snap to grid code. +=============== +dec.cpp +Modified to support the new max size for the maps(Q3 1.27). +=============== +face.cpp +Modified to support the new max size for the maps(Q3 1.27). +=============== +font.cpp +Changed the fonts color to green, part of the new theme. +=============== +gendlgs.cpp +Added a label and textbox for the snap to grid feature. +Added a checkbox for adding terrain key to func_group. +Added a checkbox for antialiased lines in the preview window. +Modified a textbox to support the new max size for the maps(Q3 1.27). +Modified the About dialog. +=============== +genmap.cpp +Modified to support the new max size for the maps(Q3 1.27). +Added code for adding terrain key to func_group. +=============== +gensurf.cpp +Added code to save the settings of the antialiasing checkbox status and the terrain key checkbox status. +Modified version number. +=============== +view.cpp +Modified code for the new Green/Black theme. +Added code to antialiase lines in the preview window + +12/18/2000 + MrHyde +=============== +bitmap.cpp +Corrected a substitution error that would prevent code from reading a selected bitmap on the first pass, and possibly leave the bitmap file open. +Checks to ensure that main window has been created before updating the preview (which might be done if a bitmap filename was saved to ini file). Previous failure to do this resulted in Radiant console error messages. +=============== +gendlgs.cpp +One tooltip was goofed up ("preview" instead of "main_preview"), resulting in "invalid cast from (NULL) pointer to GtkObject" in Radiant console. +Tooltips - oops. Use wave_radios[] rather than string constants. +Moved check for game type from SetDlgValues to GenSurfInit in gensurf.cpp. Since the game type presumably won't change while GenSurf is active this only needs to be checked once (and if it IS possible to change the game while GenSurf is active then quite a bit more needs to be changed). +Worked around strange bug in SetDlgValues. In release dll (not debug), the 2nd pass through the set_sensitive loop for game_radios crashed. Since the state of those radio buttons won't ever change during a single session, the state is only set once now. Sure would like to know what's going on there, though. +=============== +view.cpp +Elevation and azimuth labels are right-justified. + +TODO: +Check out Fix Points on Voodoo2 again. Previous attempt caused entire preview to be redrawn when moving mouse (GL_SCISSOR_TEST failed?) + +12/17/2000 + MrHyde +- tooltips +- reformatted the source to use spaces instead of tabs +- fixes to update mechanism in bitmap tab + +12/13/2000 + MrHyde +=============== +gendlgs.cpp +In ReadDlgValues, reads wavelength, amplitude, and roughness text boxes (previously ignored). +In create_main_dialog, set a "focus_out_event" for the above and added a "value_changed" for RandomSeed. +In main_go, added call to WriteIni +=============== +gensurf.cpp +Set gszIni to "plugins/gensurf.ini" to get past assert error +ported WriteIni +=============== +view.cpp +In DrawPreview, changed 2 occurrences of GL_LINE_LOOP to GL_LINE_STRIP for patches diff --git a/contrib/gtkgensurf/bitmap.cpp b/contrib/gtkgensurf/bitmap.cpp new file mode 100644 index 00000000..044f43c7 --- /dev/null +++ b/contrib/gtkgensurf/bitmap.cpp @@ -0,0 +1,434 @@ +/* +GenSurf plugin for GtkRadiant +Copyright (C) 2001 David Hyde, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include "gensurf.h" + +void GenerateBitmapMapping () +{ + double value; + double C0, C1; + double x, y; + int i, j; + int O00,O01,O10,O11; + int r0, r1, c0, c1; + int color; + unsigned char *colors; + + if (!gbmp.colors) + return; + + colors = gbmp.colors; + + for (j=0; j<=NV; j++) + { + y = (double)(j*(gbmp.height-1))/(double)NV; + r0 = (int)floor(y); + r1 = (int)ceil(y); + for (i=0; i<=NH; i++) + { + x = (double)(i*(gbmp.width-1))/(double)NH; + c0 = (int)floor(x); + c1 = (int)ceil(x); + O00 = r0*gbmp.width + c0; + O01 = r0*gbmp.width + c1; + O10 = r1*gbmp.width + c0; + O11 = r1*gbmp.width + c1; + C0 = (double)colors[O00] + (double)(colors[O01]-colors[O00])*(x-(double)c0); + C1 = (double)colors[O10] + (double)(colors[O11]-colors[O10])*(x-(double)c0); + color = (int)(C0 + (C1-C0)*(y-r0)); + + value = CalculateSnapValue(gbmp.black_value + color*((gbmp.white_value-gbmp.black_value)/255.)); + + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + xyz[i][j].p[1] = value; + break; + case PLANE_YZ0: + case PLANE_YZ1: + xyz[i][j].p[0] = value; + break; + default: + xyz[i][j].p[2] = value; + } + } + } +} + +static unsigned char* OpenBitmapFile () +{ + int bmWidth; + int bmHeight; + unsigned char bmPlanes; + unsigned char bmBitsPixel; + unsigned char m1,m2; + unsigned long sizeimage; + short res1,res2; + long filesize, pixoff; + long bmisize, compression; + long xscale, yscale; + long colors, impcol; + unsigned long m_bytesRead = 0; + unsigned char *image; + FILE *fp; + + fp = fopen (gbmp.name, "rb"); + if (fp == NULL) + return NULL; + + long rc; + rc = fread(&m1, 1, 1, fp); + m_bytesRead++; + if (rc == -1) + { + fclose(fp); + return NULL; + } + + rc = fread(&m2, 1, 1, fp); + m_bytesRead++; + if ((m1 != 'B') || (m2 != 'M')) + { + fclose(fp); + return NULL; + } + + rc = fread((long*)&(filesize),4,1,fp); m_bytesRead+=4; + if (rc != 1) { fclose(fp); return NULL; } + + rc = fread((int*)&(res1),2,1,fp); m_bytesRead+=2; + if (rc != 1) { fclose(fp); return NULL; } + + rc = fread((int*)&(res2),2,1,fp); m_bytesRead+=2; + if (rc != 1) { fclose(fp); return NULL; } + + rc = fread((long*)&(pixoff),4,1,fp); m_bytesRead+=4; + if (rc != 1) { fclose(fp); return NULL; } + + rc = fread((long*)&(bmisize),4,1,fp); m_bytesRead+=4; + if (rc != 1) { fclose(fp); return NULL; } + + rc = fread((long *)&(bmWidth),4,1,fp); m_bytesRead+=4; + if (rc != 1) { fclose(fp); return NULL; } + + rc = fread((long*)&(bmHeight),4,1,fp); m_bytesRead+=4; + if (rc != 1) { fclose(fp); return NULL; } + + rc = fread((int*)&(bmPlanes),2,1,fp); m_bytesRead+=2; + if (rc != 1) { fclose(fp); return NULL; } + + rc = fread((int*)&(bmBitsPixel),2,1,fp); m_bytesRead+=2; + if (rc != 1) { fclose(fp); return NULL; } + + rc = fread((long*)&(compression),4,1,fp); m_bytesRead+=4; + if (rc != 1) { fclose(fp); return NULL; } + + rc = fread((long*)&(sizeimage),4,1,fp); m_bytesRead+=4; + if (rc != 1) {fclose(fp); return NULL; } + + rc = fread((long*)&(xscale),4,1,fp); m_bytesRead+=4; + if (rc != 1) { fclose(fp); return NULL; } + + rc = fread((long*)&(yscale),4,1,fp); m_bytesRead+=4; + if (rc != 1) { fclose(fp); return NULL; } + + rc = fread((long*)&(colors),4,1,fp); m_bytesRead+=4; + if (rc != 1) { fclose(fp); return NULL; } + + rc = fread((long*)&(impcol),4,1,fp); m_bytesRead+=4; + if (rc != 1) { fclose(fp); return NULL; } + + if (bmBitsPixel != 8) + { + g_FuncTable.m_pfnMessageBox (g_pWnd, "This is not an 8-bit image. GenSurf can't use it.", + "Bitmap", eMB_OK, eMB_ICONWARNING); + fclose(fp); + return NULL; + } + + if (colors == 0) + colors = 1 << bmBitsPixel; + + if (bmBitsPixel != 24) + { + int i; + for (i = 0; i < colors; i++) + { + unsigned char r ,g, b, dummy; + + rc = fread(&b, 1, 1, fp); + m_bytesRead++; + if (rc!=1) + { + fclose(fp); + return NULL; + } + + rc = fread(&g, 1, 1, fp); + m_bytesRead++; + if (rc!=1) + { + fclose(fp); + return NULL; + } + + rc = fread(&r, 1, 1, fp); + m_bytesRead++; + if (rc != 1) + { + fclose(fp); + return NULL; + } + + rc = fread(&dummy, 1, 1, fp); + m_bytesRead++; + if (rc != 1) + { + fclose(fp); + return NULL; + } + } + } + + if ((long)m_bytesRead > pixoff) + { + fclose(fp); + return NULL; + } + + while ((long)m_bytesRead < pixoff) + { + char dummy; + fread(&dummy,1,1,fp); + m_bytesRead++; + } + + int w = bmWidth; + int h = bmHeight; + + // set the output params + image = (unsigned char*)malloc(w*h); + + if (image != NULL) + { + gbmp.width = w; + gbmp.height = h; + unsigned char* outbuf = image; + long row = 0; + long rowOffset = 0; + + if (compression == 0) // BI_RGB + { + for (row = 0; row < bmHeight; row++) + { + // which row are we working on? + rowOffset = (long unsigned)row*w; + + { + // pixels are packed as 1 , 4 or 8 bit vals. need to unpack them + int bit_count = 0; + unsigned long mask = (1 << bmBitsPixel) - 1; + unsigned char inbyte = 0; + + for (int col=0;col> bit_count) & mask; + + // lookup the color from the colormap - stuff it in our buffer + // swap red and blue + *(outbuf + rowOffset + col) = pix; + } + + // read DWORD padding + while ((m_bytesRead-pixoff)&3) + { + char dummy; + if (fread(&dummy,1,1,fp)!=1) + { + free(image); + fclose(fp); + return NULL; + } + m_bytesRead++; + } + } + } + } + else // compression != 0 + { + int i, x = 0; + unsigned char c, c1 = 0, *pp; + row = 0; + pp = outbuf; + + if (bmBitsPixel == 8) + { + while (row < bmHeight) + { + c = getc(fp); + + if (c) + { + // encoded mode + c1 = getc(fp); + for (i = 0; i < c; x++, i++) + { + *pp = c1; pp++; + } + } + else + { + // c==0x00, escape codes + c = getc(fp); + + if (c == 0x00) // end of line + { + row++; + x = 0; + pp = outbuf + row*bmWidth; + } + else if (c == 0x01) + break; // end of pic + else if (c == 0x02) // delta + { + c = getc(fp); + x += c; + c = getc(fp); + row += c; + pp = outbuf + x + row*bmWidth; + } + else // absolute mode + { + for (i = 0; i < c; x++, i++) + { + c1 = getc(fp); + *pp = c1; pp++; + } + + if (c & 1) + getc(fp); // odd length run: read an extra pad byte + } + } + } + } + else if (bmBitsPixel == 4) + { + while (row < bmHeight) + { + c = getc(fp); + + if (c) + { + // encoded mode + c1 = getc(fp); + for (i = 0; i < c; x++, i++) + { + *pp = (i&1) ? (c1 & 0x0f) : ((c1>>4)&0x0f); pp++; + } + } + else + { + // c==0x00, escape codes + c = getc(fp); + + if (c == 0x00) // end of line + { + row++; + x = 0; + pp = outbuf + bmHeight*bmWidth; + } + else if (c == 0x01) + break; // end of pic + else if (c == 0x02) // delta + { + c = getc(fp); + x += c; + c = getc(fp); + row += c; + pp = outbuf + x + row*bmWidth; + } + else // absolute mode + { + for (i = 0; i < c; x++, i++) + { + if ((i&1) == 0) + c1 = getc(fp); + *pp = (i&1) ? (c1 & 0x0f) : ((c1>>4)&0x0f); pp++; + } + + if (((c&3) == 1) || ((c&3) == 2)) + getc(fp); // odd length run: read an extra pad byte + } + } + } + } + } + } + fclose(fp); + return image; +} + +bool OpenBitmap () +{ + if (gbmp.colors) + free (gbmp.colors); + + gbmp.colors = OpenBitmapFile (); + + if (!gbmp.colors) + { + char Text[256]; + + sprintf (Text, "Error opening %s", gbmp.name); + g_FuncTable.m_pfnMessageBox (g_pWnd, Text, "Bitmap", eMB_OK, eMB_ICONWARNING); + strcpy (gbmp.name, ""); + } + + if (g_pWnd) + { + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "bmp_file")), gbmp.name); + gtk_widget_set_sensitive (GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "bmp_reload")), + strlen (gbmp.name) ? TRUE : FALSE); + + UpdatePreview (true); + } + + return (gbmp.colors != NULL); +} diff --git a/contrib/gtkgensurf/dec.cpp b/contrib/gtkgensurf/dec.cpp new file mode 100644 index 00000000..5055c033 --- /dev/null +++ b/contrib/gtkgensurf/dec.cpp @@ -0,0 +1,1328 @@ +/* +GenSurf plugin for GtkRadiant +Copyright (C) 2001 David Hyde, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#define SINGLE +#ifdef SINGLE +#define REAL float +#else /* not SINGLE */ +#define REAL double +#endif /* not SINGLE */ + +#include +#include +#include +#include "gensurf.h" +#include "triangle.h" + +typedef struct +{ + float error; + int node; +} TRITABLE; + +double dh, dv; +int NVP1; + +#define Absolute(a) ((a) >= 0.0 ? (a) : -(a)) + +void MakeDecimatedMap(int *NumNodes, int *NumTris, NODE **pNode, TRI **pTri) +{ + int compare(TRITABLE *, TRITABLE *); + int Bisect(NODE *, int, int, int); + void CalcAngles(NODE *, int *, float *); + void EdgeOnSide(int *, int *, int *); + int tricall(int, NODE *, int *, TRI **, TRI **, char *); + int CheckBorders(int *,int,NODE *,int *,TRI **); + + float biggesterror; + int i, j, N; + int j0, j1, j2; + int NumNodesToSave; + int NumNodesUsed; + NODE *Node; + TRI *Tri; + TRITABLE *TriTable; + + if(Decimate <= 0) return; + /* + ghCursorCurrent = LoadCursor(NULL,IDC_WAIT); + SetCursor(ghCursorCurrent); + */ + dh = (Hur-Hll)/NH; + dv = (Vur-Vll)/NV; + NVP1 = NV+1; + + NumNodes[0] = (NH+1)*(NVP1); + *pNode = (NODE *) malloc(NumNodes[0] * sizeof(NODE)); + Node = *pNode; + memset(Node,0,NumNodes[0]*sizeof(NODE)); + + // Copy [NH][NV] vertex array to our working node array + for(i=0,N=0; i<=NH; i++) + { + for(j=0; j<=NV; j++, N++) + { + Node[N].p[0] = (float)xyz[i][j].p[0]; + Node[N].p[1] = (float)xyz[i][j].p[1]; + Node[N].p[2] = (float)xyz[i][j].p[2]; + Node[N].fixed = xyz[i][j].fixed; + } + } + // Start things off with the corner values + Node[ 0].used = 1; + Node[NV].used = 1; + Node[NH*NVP1].used = 1; + Node[NH*NVP1+NV].used = 1; + NumNodesUsed = 4; + tricall(NumNodes[0], Node, NumTris, NULL, pTri, "cnzBNPY"); + Tri = *pTri; + + // Which coordinates are we triangulating on? + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + j0 = 1; + j1 = 0; + j2 = 2; + break; + case PLANE_YZ0: + case PLANE_YZ1: + j0 = 0; + j1 = 1; + j2 = 2; + break; + default: + j0 = 2; + j1 = 0; + j2 = 1; + } + + // TriTable stores the largest error in a triangle and the node where that + // error occurs + TriTable = (TRITABLE *) malloc(NH*NV*2 * sizeof(TRITABLE)); + NumNodesToSave = min(NumNodes[0], (int)(0.01*(100-Decimate)*(NumNodes[0]-NumNodesUsed)+NumNodesUsed)); + + while(NumNodesUsed < NumNodesToSave) + { + for(i=0; i TriTable[Node[i].tri].error) + { + TriTable[Node[i].tri].error = (float)(Absolute(Node[i].error)); + TriTable[Node[i].tri].node = i; + } + } + qsort( (void *)TriTable, (size_t)(NumTris[0]), sizeof(TRITABLE), (int (*)(const void *, const void *))compare ); + for(i=0; i 0.5*biggesterror; i++) + { + if(Node[TriTable[i].node].used) continue; // shouldn't happen + NumNodesUsed++; + Node[TriTable[i].node].used++; + } + free(Tri); + tricall(NumNodes[0], Node, NumTris, NULL, pTri, "cnzBNPY"); + Tri = *pTri; + // Sliver-check along borders. Since borders are often linear, the errors + // along borders will often be zero, so no new points will be added. This + // tends to produce long, thin brushes. For all border triangles, check + // that minimum angle isn't less than SLIVER_ANGLE. If it is, add another + // vertex. + while(CheckBorders(&NumNodesUsed,NumNodes[0],Node,NumTris,pTri) > 0) + { + } + Tri = *pTri; + } + } + free(TriTable); + // One last time (because we're pessimistic), check border triangles +// CheckBorders(&NumNodesUsed,NumNodes[0],Node,NumTris,pTri); +// Tri = *pTri; + + // Check that all fixed points are exact. If not, add them to the mix. + // First check to see if we have any fixed points that aren't already used. + for(i=0, N=0; i 0.5) + { + NumNodesUsed++; + Node[i].used++; + free(Tri); + tricall(NumNodes[0], Node, NumTris, NULL, pTri, "cnzBNPY"); + Tri = *pTri; + } + } + } + + // Swap node orders for surfaces facing down, north or west so that + // they are counterclockwise when facing the surface + + if((Plane == PLANE_XY1) || (Plane == PLANE_XZ0) || (Plane == PLANE_YZ1) ) + { + for(i=0; i= R) && (v[1] >= R) ) + { + edge[0] = 0; + border[0] = 1; + } + if( (v[1] >= R) && (v[2] >= R) ) + { + edge[0] = 1; + border[0] = 1; + } + if( (v[2] >= R) && (v[0] >= R) ) + { + edge[0] = 2; + border[0] = 1; + } + + if(border[0] >= 0) + { + k0 = edge[0]; + k1 = (k0+1) % 3; + N = Absolute(v[k0] - v[k1]); + Ndv = (float)(N*dv); + } + if( ((v[0] % NVP1) == 0) && ((v[1] % NVP1) == 0) ) + { + if(border[0] >= 0) + if( Ndv > (Absolute(v[0] - v[1])*dh)) return; + edge[0] = 0; + border[0] = 2; + return; + } + if( ((v[1] % NVP1) == 0) && ((v[2] % NVP1) == 0) ) + { + if(border[0] >= 0) + if( Ndv > (Absolute(v[1] - v[2])*dh)) return; + edge[0] = 1; + border[0] = 2; + return; + } + if( ((v[2] % NVP1) == 0) && ((v[0] % NVP1) == 0) ) + { + if(border[0] >= 0) + if( Ndv > (Absolute(v[2] - v[0])*dh)) return; + edge[0] = 2; + border[0] = 2; + return; + } + + if( ((v[0] % NVP1) == NV) && ((v[1] % NVP1) == NV) ) + { + if(border[0] >= 0) + if( Ndv > (Absolute(v[0] - v[1])*dh)) return; + edge[0] = 0; + border[0] = 3; + return; + } + if( ((v[1] % NVP1) == NV) && ((v[2] % NVP1) == NV) ) + { + if(border[0] >= 0) + if( Ndv > (Absolute(v[1] - v[2])*dh)) return; + edge[0] = 1; + border[0] = 3; + return; + } + if( ((v[2] % NVP1) == NV) && ((v[0] % NVP1) == NV) ) + { + if(border[0] >= 0) + if( Ndv > (Absolute(v[2] - v[0])*dh)) return; + edge[0] = 2; + border[0] = 3; + return; + } + return; +} + +void CalcAngles(NODE *node, int *v, float *angle) +{ + int i, j, k; + vec l; + vec x0, x1, x2, y0, y1, y2; + vec2 vv[3]; + vec dot; + + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + i = 0; + j = 2; + break; + case PLANE_YZ0: + case PLANE_YZ1: + i = 1; + j = 2; + break; + default: + i = 0; + j = 1; + } + x0 = node[v[0]].p[i]; + x1 = node[v[1]].p[i]; + x2 = node[v[2]].p[i]; + y0 = node[v[0]].p[j]; + y1 = node[v[1]].p[j]; + y2 = node[v[2]].p[j]; + + vv[0][0] = x1-x0; + vv[0][1] = y1-y0; + vv[1][0] = x2-x1; + vv[1][1] = y2-y1; + vv[2][0] = x0-x2; + vv[2][1] = y0-y2; + + for(k=0; k<3; k++) + { + l = (vec)(sqrt( vv[k][0]*vv[k][0] + vv[k][1]*vv[k][1] )); + if(l > 0.) + { + vv[k][0] /= l; + vv[k][1] /= l; + } + } + + dot = -(vv[0][0]*vv[2][0] + vv[0][1]*vv[2][1]); + angle[0] = (float)(acos(dot)); + dot = -(vv[1][0]*vv[0][0] + vv[1][1]*vv[0][1]); + angle[1] = (float)(acos(dot)); + dot = -(vv[2][0]*vv[1][0] + vv[2][1]*vv[1][1]); + angle[2] = (float)(acos(dot)); +} +//================================================================= +int Bisect(NODE *node, int border, int j0, int j1) +{ + int k; + + switch(border) + { + case 0: + k = (j0+j1)/2; + break; + case 1: + k = (j0+j1)/2; + break; + case 2: + k = (int)((j0+j1)/(2*NVP1)) * NVP1; + break; + case 3: + k = (int)((j0+j1+2)/(2*NVP1)) * NVP1 - 1; + break; + } + return( ((k != j0) && (k != j1)) ? k : 0 ); +} +//================================================================= +int compare(TRITABLE *t1, TRITABLE *t2) +{ + if(t1->error > t2->error) return -1; + if(t1->error < t2->error) return 1; + return 0; +} + +void MakeBrushes(int NumTris, NODE *Node, TRI *Tri,bool surf, + int offset,char *texture0, char *texture1, char *texture2) +{ + extern double backface; + BRUSH brush; + int contents; + int i, j; + float Steep; + vec3_t PlaneNormal,SurfNormal; + bool CheckAngle; + vec3_t t[2]; + + // if texture2 is identical to texture0, there's no need to + // check surface angle + if(!g_strcasecmp(texture0,texture2) || !strlen(texture2)) + CheckAngle = FALSE; + else + { + CheckAngle = TRUE; + Steep = (float)cos((double)SlantAngle/57.2957795); + switch(Plane) + { + case PLANE_XY0: PlaneNormal[0]= 0.;PlaneNormal[1]= 0.;PlaneNormal[2]= 1.;break; + case PLANE_XY1: PlaneNormal[0]= 0.;PlaneNormal[1]= 0.;PlaneNormal[2]=-1.;break; + case PLANE_XZ0: PlaneNormal[0]= 0.;PlaneNormal[1]= 1.;PlaneNormal[2]= 1.;break; + case PLANE_XZ1: PlaneNormal[0]= 0.;PlaneNormal[1]=-1.;PlaneNormal[2]= 1.;break; + case PLANE_YZ0: PlaneNormal[0]= 1.;PlaneNormal[1]= 0.;PlaneNormal[2]= 1.;break; + case PLANE_YZ1: PlaneNormal[0]=-1.;PlaneNormal[1]= 0.;PlaneNormal[2]= 1.;break; + } + } + + contents = 0; + if(surf) + { + if(UseDetail) contents += CONTENTS_DETAIL; + if(UseLadder) contents += CONTENTS_LADDER; + } + + OpenFuncGroup(); + for(i=0; i= max(Node[q[0]].p[j1],Node[q[2]].p[j1])) continue; + if(Tri[k].min[j2] >= max(Node[q[0]].p[j2],Node[q[2]].p[j2])) continue; + if(Tri[k].max[j1] <= min(Node[q[0]].p[j1],Node[q[2]].p[j1])) continue; + if(Tri[k].max[j2] <= min(Node[q[0]].p[j2],Node[q[2]].p[j2])) continue; + + for(h0=0; h0<4 && OK; h0++) + { + h1 = (h0+1)%4; + for(t=0; t<3 && OK; t++) + { + s[t] = side(Node[q[h0]].p[j1],Node[q[h0]].p[j2], + Node[q[h1]].p[j1],Node[q[h1]].p[j2], + Node[Tri[k].v[t]].p[j1],Node[Tri[k].v[t]].p[j2]); + } + if((s[1] > 0 || s[2] > 0) && s[0] < 0) OK=0; + if((s[2] > 0 || s[0] > 0) && s[1] < 0) OK=0; + if((s[0] > 0 || s[1] > 0) && s[2] < 0) OK=0; + } + } + if(!OK) continue; + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + // front + brush.face[0].v[0][0] = Node[q[2]].p[0]; + brush.face[0].v[0][1] = (float)front; + brush.face[0].v[0][2] = Node[q[2]].p[2]; + + brush.face[0].v[1][0] = Node[q[1]].p[0]; + brush.face[0].v[1][1] = (float)front; + brush.face[0].v[1][2] = Node[q[1]].p[2]; + + brush.face[0].v[2][0] = Node[q[0]].p[0]; + brush.face[0].v[2][1] = (float)front; + brush.face[0].v[2][2] = Node[q[0]].p[2]; + + // back + brush.face[1].v[0][0] = Node[q[0]].p[0]; + brush.face[1].v[0][1] = (float)backface; + brush.face[1].v[0][2] = Node[q[0]].p[2]; + + brush.face[1].v[1][0] = Node[q[1]].p[0]; + brush.face[1].v[1][1] = (float)backface; + brush.face[1].v[1][2] = Node[q[1]].p[2]; + + brush.face[1].v[2][0] = Node[q[2]].p[0]; + brush.face[1].v[2][1] = (float)backface; + brush.face[1].v[2][2] = Node[q[2]].p[2]; + + for(k0=0; k0= 0) + { + if(!Node[j].used) // Shouldn't be used, but... + { + NumNodesUsed[0]++; + Node[j].used++; + } + } + } + } + if(NumNodesUsed[0] > N) + { + free(*pTri); + tricall(NumNodes, Node, NumTris, NULL, pTri, "cnzBNPY"); + Tri = *pTri; + } + return (NumNodesUsed[0] - N); +} diff --git a/contrib/gtkgensurf/face.cpp b/contrib/gtkgensurf/face.cpp new file mode 100644 index 00000000..b1e6c6e8 --- /dev/null +++ b/contrib/gtkgensurf/face.cpp @@ -0,0 +1,452 @@ +/* +GenSurf plugin for GtkRadiant +Copyright (C) 2001 David Hyde, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include "gensurf.h" + +#define MAX_FACES 128 // Maximum number of faces on a brush +#define MAX_POINTS_ON_WINDING 64 +#define SIDE_FRONT 0 +#define SIDE_ON 2 +#define SIDE_BACK 1 +#define SIDE_CROSS -2 + +//vec3 vec3_origin = {0,0,0}; + +void PlaneFromPoints (float *p0, float *p1, float *p2, PLANE *plane) +{ + vec3 t1, t2; + vec length; + + VectorSubtract (p0, p1, t1); + VectorSubtract (p2, p1, t2); + plane->normal[0] = t1[1]*t2[2] - t1[2]*t2[1]; + plane->normal[1] = t1[2]*t2[0] - t1[0]*t2[2]; + plane->normal[2] = t1[0]*t2[1] - t1[1]*t2[0]; + + length = (vec)(sqrt(plane->normal[0]*plane->normal[0] + + plane->normal[1]*plane->normal[1] + + plane->normal[2]*plane->normal[2] )); + if (length == 0) + { + VectorClear(plane->normal); + } + else + { + plane->normal[0] /= length; + plane->normal[1] /= length; + plane->normal[2] /= length; + } + plane->dist = DotProduct (p0, plane->normal); +} +/* +void VectorMA (vec3 va, vec scale, vec3 vb, vec3 vc) +{ + vc[0] = va[0] + scale*vb[0]; + vc[1] = va[1] + scale*vb[1]; + vc[2] = va[2] + scale*vb[2]; +} + +void CrossProduct (vec3 v1, vec3 v2, vec3 cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} +*/ +/* +============= +AllocWinding +============= +*/ +MY_WINDING *AllocWinding (int points) +{ + MY_WINDING *w; + int s; + + s = sizeof(vec)*3*points + sizeof(int); + w = (MY_WINDING*)malloc (s); + memset (w, 0, s); + return w; +} +/* +vec VectorNormalize (vec3 in, vec3 out) +{ + vec length, ilength; + + length = (vec)(sqrt (in[0]*in[0] + in[1]*in[1] + in[2]*in[2])); + if (length == 0) + { + VectorClear (out); + return 0; + } + + ilength = (vec)1.0/length; + out[0] = in[0]*ilength; + out[1] = in[1]*ilength; + out[2] = in[2]*ilength; + + return length; +} +*/ + +/* +================= +BaseWindingForPlane +================= +*/ +MY_WINDING *BaseWindingForPlane (vec3 normal, vec dist) +{ + int i, x; + vec max, v; + vec3 org, vright, vup; + MY_WINDING *w; + +// find the major axis + + max = -BOGUS_RANGE; + x = -1; + for (i=0 ; i<3; i++) + { + v = (vec)(fabs(normal[i])); + if (v > max) + { + x = i; + max = v; + } + } + if (x==-1) x = 2; + + VectorCopy(vec3_origin,vup); + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct (vup, normal); + VectorMA (vup, -v, normal, vup); + VectorNormalize (vup, vup); + + VectorScale (normal, dist, org); + + CrossProduct (vup, normal, vright); + + VectorScale (vup, 65536, vup); + VectorScale (vright, 65536, vright); + +// project a really big axis aligned box onto the plane + w = AllocWinding (4); + + VectorSubtract (org, vright, w->p[0]); + VectorAdd (w->p[0], vup, w->p[0]); + + VectorAdd (org, vright, w->p[1]); + VectorAdd (w->p[1], vup, w->p[1]); + + VectorAdd (org, vright, w->p[2]); + VectorSubtract (w->p[2], vup, w->p[2]); + + VectorSubtract (org, vright, w->p[3]); + VectorSubtract (w->p[3], vup, w->p[3]); + + w->numpoints = 4; + + return w; +} + +void FreeWinding (MY_WINDING *w) +{ + if (*(unsigned *)w == 0xdeaddead) +// Error ("FreeWinding: freed a freed winding"); + return; + *(unsigned *)w = 0xdeaddead; + + free (w); +} + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace (MY_WINDING **inout, vec3 normal, vec dist, vec epsilon) +{ + MY_WINDING *in; + vec dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + static vec dot; // VC 4.2 optimizer bug if not static + int i, j; + vec *p1, *p2; + vec3 mid; + MY_WINDING *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + { + FreeWinding(in); + *inout = NULL; + return; + } + if (!counts[1]) + return; // inout stays the same + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + } + +// if (f->numpoints > maxpts) +// Error ("ClipWinding: points exceeded estimate"); +// if (f->numpoints > MAX_POINTS_ON_WINDING) +// Error ("ClipWinding: MAX_POINTS_ON_WINDING"); + + FreeWinding(in); + *inout = f; +} + +void UseFaceBounds() +{ + LPVOID vp; + float Dot, BestDot; + float planepts[3][3]; + int BestFace; + int i, j; + int NumFaces; + vec3 SurfNormal; + vec3 vmin,vmax; + PLANE plane[MAX_FACES*2]; + PLANE pface; + MY_WINDING *w; + + switch(Plane) + { + case PLANE_XY1: + SurfNormal[0] = 0.0; + SurfNormal[1] = 0.0; + SurfNormal[2] =-1.0; + break; + case PLANE_XZ0: + SurfNormal[0] = 0.0; + SurfNormal[1] = 1.0; + SurfNormal[2] = 0.0; + break; + case PLANE_XZ1: + SurfNormal[0] = 0.0; + SurfNormal[1] =-1.0; + SurfNormal[2] = 0.0; + break; + case PLANE_YZ0: + SurfNormal[0] = 1.0; + SurfNormal[1] = 0.0; + SurfNormal[2] = 0.0; + break; + case PLANE_YZ1: + SurfNormal[0] =-1.0; + SurfNormal[1] = 0.0; + SurfNormal[2] = 0.0; + break; + default: + SurfNormal[0] = 0.0; + SurfNormal[1] = 0.0; + SurfNormal[2] = 1.0; + } + +#if 0 + i = g_FuncTable.m_pfnAllocateSelectedBrushHandles(); + vp = g_FuncTable.m_pfnGetSelectedBrushHandle(0); + NumFaces = g_FuncTable.m_pfnGetFaceCount(vp); + + BestFace = -1; + BestDot = 0.0; + + for(i=0; im_v1[0]; + planepts[0][1] = QERFaceData->m_v1[1]; + planepts[0][2] = QERFaceData->m_v1[2]; + planepts[1][0] = QERFaceData->m_v2[0]; + planepts[1][1] = QERFaceData->m_v2[1]; + planepts[1][2] = QERFaceData->m_v2[2]; + planepts[2][0] = QERFaceData->m_v3[0]; + planepts[2][1] = QERFaceData->m_v3[1]; + planepts[2][2] = QERFaceData->m_v3[2]; + + PlaneFromPoints (planepts[0], planepts[1], planepts[2], &plane[2*i]); + VectorSubtract (vec3_origin, plane[2*i].normal, plane[2*i+1].normal); + plane[2*i+1].dist = -plane[2*i].dist; + + Dot = DotProduct(plane[2*i].normal,SurfNormal); + if(Dot > BestDot) + { + BestDot = Dot; + BestFace = i; + if(strlen(QERFaceData->m_TextureName)) + strcpy(Texture[Game][0],QERFaceData->m_TextureName); + } + } + for(i=0; im_TextureName)) + { + if(strcmp(Texture[Game][0],QERFaceData->m_TextureName)) + strcpy(Texture[Game][1],QERFaceData->m_TextureName); + } + } + + + g_FuncTable.m_pfnReleaseSelectedBrushHandles(); + + w = BaseWindingForPlane (plane[BestFace*2].normal, plane[BestFace*2].dist); + + for (i=0 ; ip[0][0]; + vmin[1] = vmax[1] = w->p[0][1]; + vmin[2] = vmax[2] = w->p[0][2]; + for(j=1; jnumpoints; j++) + { + vmin[0] = min(vmin[0],w->p[j][0]); + vmin[1] = min(vmin[1],w->p[j][1]); + vmin[2] = min(vmin[2],w->p[j][2]); + vmax[0] = max(vmax[0],w->p[j][0]); + vmax[1] = max(vmax[1],w->p[j][1]); + vmax[2] = max(vmax[2],w->p[j][2]); + } + + FreeWinding(w); + + VectorCopy(plane[BestFace*2].normal,pface.normal); + pface.dist = plane[BestFace*2].dist; + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + if(pface.normal[1] == 0.) return; + Hll = vmin[0]; + Hur = vmax[0]; + Vll = vmin[2]; + Vur = vmax[2]; + Z00 = (pface.dist - pface.normal[0]*Hll - pface.normal[2]*Vll)/pface.normal[1]; + Z01 = (pface.dist - pface.normal[0]*Hll - pface.normal[2]*Vur)/pface.normal[1]; + Z10 = (pface.dist - pface.normal[0]*Hur - pface.normal[2]*Vll)/pface.normal[1]; + Z11 = (pface.dist - pface.normal[0]*Hur - pface.normal[2]*Vur)/pface.normal[1]; + break; + case PLANE_YZ0: + case PLANE_YZ1: + if(pface.normal[0] == 0.) return; + Hll = vmin[1]; + Hur = vmax[1]; + Vll = vmin[2]; + Vur = vmax[2]; + Z00 = (pface.dist - pface.normal[1]*Hll - pface.normal[2]*Vll)/pface.normal[0]; + Z01 = (pface.dist - pface.normal[1]*Hll - pface.normal[2]*Vur)/pface.normal[0]; + Z10 = (pface.dist - pface.normal[1]*Hur - pface.normal[2]*Vll)/pface.normal[0]; + Z11 = (pface.dist - pface.normal[1]*Hur - pface.normal[2]*Vur)/pface.normal[0]; + break; + default: + if(pface.normal[2] == 0.) return; + Hll = vmin[0]; + Hur = vmax[0]; + Vll = vmin[1]; + Vur = vmax[1]; + Z00 = (pface.dist - pface.normal[0]*Hll - pface.normal[1]*Vll)/pface.normal[2]; + Z01 = (pface.dist - pface.normal[0]*Hll - pface.normal[1]*Vur)/pface.normal[2]; + Z10 = (pface.dist - pface.normal[0]*Hur - pface.normal[1]*Vll)/pface.normal[2]; + Z11 = (pface.dist - pface.normal[0]*Hur - pface.normal[1]*Vur)/pface.normal[2]; + } +#endif +} diff --git a/contrib/gtkgensurf/font.cpp b/contrib/gtkgensurf/font.cpp new file mode 100644 index 00000000..dabb72d7 --- /dev/null +++ b/contrib/gtkgensurf/font.cpp @@ -0,0 +1,270 @@ +/* +GenSurf plugin for GtkRadiant +Copyright (C) 2001 David Hyde, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// +// Texture Font +// +// Taken from LeoCAD (www.leocad.org) and used in GtkGenSurf +// with permission from the author. +// +// Leonardo Zide (leo@lokigames.com) +// + +#include +#include "gensurf.h" + +static const unsigned char data[2048] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 207, 255, 255, 159, 255, 31, 255, 231, 159, 153, 63, 255, 255, 255, 255, + 255, 207, 255, 255, 159, 255, 207, 255, 231, 159, 153, 63, 255, 255, 255, 255, + 255, 207, 255, 255, 159, 255, 207, 255, 231, 255, 159, 63, 255, 255, 255, 255, + 7, 78, 252, 240, 145, 135, 3, 71, 38, 158, 153, 51, 19, 227, 196, 255, + 243, 140, 121, 230, 140, 51, 207, 51, 198, 156, 153, 57, 99, 204, 152, 255, + 255, 204, 51, 111, 158, 121, 206, 121, 230, 153, 153, 60, 115, 206, 60, 255, + 31, 204, 51, 127, 158, 121, 206, 121, 230, 153, 25, 62, 115, 206, 60, 255, + 199, 204, 51, 127, 158, 1, 206, 121, 230, 153, 25, 62, 115, 206, 60, 255, + 243, 204, 51, 127, 158, 249, 207, 121, 230, 153, 153, 60, 115, 206, 60, 255, + 243, 204, 51, 111, 158, 249, 207, 121, 230, 153, 153, 57, 115, 206, 60, 255, + 243, 140, 121, 230, 140, 115, 206, 51, 230, 153, 153, 51, 115, 206, 60, 255, + 7, 73, 252, 240, 145, 7, 207, 71, 230, 153, 153, 39, 115, 206, 60, 255, + 255, 255, 255, 255, 255, 255, 255, 127, 254, 255, 249, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 127, 254, 255, 249, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 57, 255, 255, 249, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 131, 255, 255, 252, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 249, 255, 255, 255, 255, 255, 255, 255, 227, 255, + 255, 255, 255, 255, 255, 255, 249, 255, 255, 255, 255, 255, 255, 255, 201, 255, + 255, 255, 255, 255, 255, 255, 249, 255, 255, 255, 255, 255, 255, 255, 156, 255, + 15, 79, 252, 200, 196, 96, 32, 79, 62, 252, 15, 15, 159, 192, 156, 255, + 103, 142, 121, 198, 112, 206, 57, 79, 62, 60, 15, 15, 159, 207, 156, 255, + 243, 204, 51, 207, 120, 254, 57, 207, 156, 57, 103, 102, 206, 231, 156, 255, + 243, 204, 51, 207, 124, 252, 57, 207, 156, 25, 230, 112, 206, 231, 156, 255, + 243, 204, 51, 207, 252, 224, 57, 207, 156, 25, 230, 121, 206, 243, 156, 255, + 243, 204, 51, 207, 252, 199, 57, 207, 201, 211, 242, 240, 228, 249, 156, 255, + 243, 204, 51, 207, 252, 207, 57, 207, 201, 195, 112, 230, 228, 249, 156, 255, + 103, 142, 121, 198, 124, 206, 121, 198, 227, 231, 57, 207, 241, 252, 201, 255, + 15, 79, 252, 200, 252, 224, 227, 200, 227, 231, 57, 207, 241, 192, 227, 255, + 255, 207, 255, 207, 255, 255, 255, 255, 255, 255, 255, 255, 249, 255, 255, 255, + 255, 207, 255, 207, 255, 255, 255, 255, 255, 255, 255, 255, 249, 255, 255, 255, + 255, 207, 255, 207, 255, 255, 255, 255, 255, 255, 255, 255, 252, 255, 255, 255, + 255, 207, 255, 207, 255, 255, 255, 255, 255, 255, 255, 127, 254, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 159, 15, 30, 252, 57, 224, 225, 128, 131, 7, 255, 254, 128, 127, 240, 255, + 135, 231, 204, 249, 57, 255, 252, 159, 57, 115, 126, 252, 60, 63, 231, 255, + 159, 247, 236, 249, 56, 127, 254, 159, 57, 115, 126, 252, 124, 158, 207, 255, + 159, 255, 252, 249, 56, 127, 254, 207, 57, 115, 62, 249, 124, 158, 207, 255, + 159, 255, 252, 121, 56, 112, 254, 207, 57, 115, 62, 249, 60, 207, 255, 255, + 159, 127, 62, 60, 57, 103, 224, 231, 131, 115, 158, 243, 128, 207, 255, 255, + 159, 63, 255, 57, 249, 103, 206, 231, 57, 7, 158, 243, 60, 207, 255, 255, + 159, 159, 255, 153, 249, 103, 206, 231, 57, 127, 206, 231, 124, 206, 255, 255, + 159, 207, 255, 25, 240, 103, 206, 243, 57, 127, 14, 224, 124, 158, 207, 255, + 159, 231, 239, 249, 185, 103, 206, 243, 57, 127, 230, 207, 124, 158, 207, 255, + 159, 231, 207, 249, 57, 103, 206, 243, 57, 63, 231, 207, 60, 63, 231, 255, + 159, 7, 28, 252, 121, 240, 224, 243, 131, 135, 231, 207, 128, 127, 240, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 7, 126, 128, 3, 124, 240, 249, 156, 63, 231, 57, 255, 252, 57, 159, 255, + 231, 124, 254, 243, 63, 231, 249, 156, 63, 231, 60, 255, 252, 57, 159, 255, + 231, 121, 254, 243, 159, 207, 249, 156, 63, 103, 62, 255, 248, 56, 158, 255, + 231, 121, 254, 243, 159, 207, 249, 156, 63, 39, 63, 255, 248, 56, 156, 255, + 231, 115, 254, 243, 207, 255, 249, 156, 63, 135, 63, 255, 112, 56, 152, 255, + 231, 115, 192, 3, 206, 255, 1, 156, 63, 199, 63, 255, 112, 56, 153, 255, + 231, 115, 254, 243, 207, 193, 249, 156, 63, 135, 63, 255, 36, 57, 147, 255, + 231, 115, 254, 243, 207, 207, 249, 156, 63, 39, 63, 255, 36, 57, 131, 255, + 231, 121, 254, 243, 159, 207, 249, 156, 57, 103, 62, 255, 140, 57, 135, 255, + 231, 121, 254, 243, 159, 199, 249, 156, 57, 231, 60, 255, 140, 57, 143, 255, + 231, 124, 254, 243, 63, 199, 249, 156, 147, 231, 57, 255, 220, 57, 159, 255, + 7, 126, 128, 243, 127, 208, 249, 156, 199, 231, 51, 192, 220, 57, 159, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 63, 252, 128, 63, 252, 128, 63, 28, 128, 249, 204, 79, 254, 39, 63, 255, + 159, 249, 60, 159, 249, 60, 159, 249, 249, 249, 204, 79, 158, 39, 63, 255, + 207, 243, 124, 206, 243, 124, 206, 243, 249, 249, 156, 103, 158, 103, 158, 255, + 207, 243, 124, 206, 243, 124, 206, 255, 249, 249, 156, 231, 156, 243, 204, 255, + 231, 231, 124, 230, 231, 124, 158, 255, 249, 249, 156, 231, 12, 243, 225, 255, + 231, 231, 60, 231, 231, 60, 63, 252, 249, 249, 60, 243, 12, 243, 243, 255, + 231, 231, 128, 231, 231, 128, 255, 249, 249, 249, 60, 243, 105, 249, 243, 255, + 231, 231, 252, 103, 230, 60, 255, 243, 249, 249, 124, 251, 97, 248, 225, 255, + 207, 243, 252, 207, 240, 124, 254, 243, 249, 249, 124, 248, 97, 248, 204, 255, + 207, 243, 252, 207, 241, 124, 206, 243, 249, 249, 124, 248, 243, 124, 158, 255, + 159, 249, 252, 159, 241, 124, 158, 249, 249, 115, 254, 252, 243, 60, 63, 255, + 63, 252, 252, 63, 228, 252, 60, 252, 249, 7, 255, 252, 243, 60, 63, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 243, 19, 192, 255, 252, 255, 255, 127, 14, 127, 248, 15, 252, 247, 227, 231, + 243, 243, 207, 255, 252, 255, 153, 127, 102, 62, 243, 227, 241, 193, 201, 243, + 231, 249, 231, 255, 252, 255, 60, 127, 242, 156, 231, 249, 231, 148, 201, 249, + 207, 252, 243, 255, 204, 124, 126, 126, 254, 156, 231, 57, 230, 148, 201, 252, + 207, 252, 243, 255, 204, 60, 255, 60, 255, 156, 231, 156, 204, 244, 99, 254, + 31, 254, 249, 255, 252, 159, 255, 57, 127, 158, 231, 204, 204, 244, 63, 255, + 31, 254, 252, 255, 252, 207, 255, 51, 63, 159, 231, 204, 204, 193, 159, 255, + 63, 127, 254, 255, 252, 159, 255, 57, 159, 207, 207, 204, 204, 151, 207, 248, + 63, 127, 254, 255, 252, 63, 255, 156, 159, 159, 231, 204, 228, 151, 103, 242, + 63, 63, 255, 255, 255, 127, 126, 158, 255, 159, 231, 25, 241, 148, 115, 242, + 63, 159, 127, 230, 204, 252, 60, 159, 159, 159, 231, 249, 255, 148, 121, 242, + 63, 31, 64, 230, 204, 252, 153, 159, 159, 159, 231, 227, 255, 193, 252, 248, + 255, 255, 63, 255, 231, 255, 255, 255, 255, 159, 231, 15, 252, 247, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 63, 243, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 248, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 63, 254, 255, 195, 255, 255, 255, 255, 3, 252, 147, 255, 255, 255, 255, 255, + 159, 252, 255, 153, 255, 255, 255, 255, 243, 252, 147, 255, 255, 255, 255, 255, + 159, 252, 255, 153, 255, 243, 255, 255, 243, 252, 147, 255, 255, 255, 255, 255, + 159, 252, 204, 60, 255, 243, 255, 255, 243, 252, 0, 255, 255, 255, 255, 255, + 63, 254, 204, 60, 255, 243, 128, 255, 243, 252, 201, 255, 255, 255, 255, 255, + 63, 254, 225, 60, 255, 243, 255, 255, 243, 252, 201, 255, 255, 255, 255, 255, + 31, 126, 128, 60, 127, 128, 255, 255, 243, 252, 201, 255, 255, 255, 255, 255, + 159, 228, 225, 60, 193, 243, 128, 255, 243, 252, 201, 255, 255, 255, 255, 255, + 207, 240, 204, 60, 255, 243, 255, 255, 243, 124, 128, 255, 255, 255, 255, 255, + 207, 249, 204, 60, 255, 243, 255, 255, 243, 252, 228, 255, 255, 255, 255, 255, + 207, 240, 255, 60, 255, 243, 255, 255, 243, 252, 228, 255, 255, 255, 255, 255, + 31, 242, 255, 60, 255, 255, 255, 255, 243, 252, 228, 255, 255, 255, 255, 255, + 255, 255, 255, 60, 255, 255, 255, 255, 243, 252, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 153, 255, 255, 255, 0, 242, 252, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 153, 255, 255, 255, 255, 243, 252, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 195, 255, 255, 255, 255, 3, 252, 255, 255, 255, 255, 255, 255 +}; + +typedef struct +{ + unsigned char width; + float left, right, top, bottom; +} LC_TXFVERT; + +static LC_TXFVERT glyphs[93]; +static GLuint texture; + +void texfont_init () +{ + if (texture != 0) + return; + + int i, j, x, y; + float inv = 1.0f/128; + char *charlines[16] = { + "abcdefghijklmn", "opqrstuvwxyz0", "123456789ABC", "DEFGHIJKLMN", + "OPQRSTUVWX", "YZ,.!;:<>/?{}@$%", "&*()-+=_[] #" }; + unsigned char lefts[7][17] = { + { 1, 11, 21, 30, 40, 50, 56, 66, 76, 80, 84, 93, 97, 111, 121 }, + { 1, 11, 21, 31, 38, 47, 53, 63, 72, 86, 94, 103, 111, 120 }, + { 1, 10, 19, 28, 37, 46, 55, 64, 73, 82, 94, 106, 118, }, + { 1, 13, 24, 34, 47, 59, 64, 73, 84, 94, 108, 120 }, + { 1, 14, 25, 38, 50, 61, 71, 83, 94, 109, 120 }, + { 1, 12, 22, 26, 30, 35, 39, 43, 52, 61, 65, 75, 81, 87, 103, 112, 125 }, + { 3, 14, 23, 28, 33, 38, 47, 56, 65, 70, 75, 79, 88 } }; + // tops = 1 20 39 58 77 96 112 (+16) + memset(glyphs, 0, sizeof(glyphs)); + + // ASCII 32-125 + for (i = 32; i < 126; i++) + for (x = 0; x < 7; x++) + for (y = 0; charlines[x][y]; y++) + if (charlines[x][y] == i) + { + glyphs[i-32].width = lefts[x][y+1] - lefts[x][y]; + glyphs[i-32].left = (float)lefts[x][y]*inv; + glyphs[i-32].right = (float)(lefts[x][y+1])*inv; + + if (x != 6) + glyphs[i-32].top = (float)(1 + 19*x); + else + glyphs[i-32].top = 112; + glyphs[i-32].bottom = glyphs[i-32].top + 16; + glyphs[i-32].top *= inv; + glyphs[i-32].bottom *= inv; + } + + g_GLTable.m_pfn_qglGenTextures (1, &texture); + g_GLTable.m_pfn_qglBindTexture (GL_TEXTURE_2D, texture); + g_GLTable.m_pfn_qglDisable (GL_TEXTURE_GEN_S); + g_GLTable.m_pfn_qglDisable (GL_TEXTURE_GEN_T); + g_GLTable.m_pfn_qglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + g_GLTable.m_pfn_qglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + g_GLTable.m_pfn_qglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + g_GLTable.m_pfn_qglTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + // g_GLTable.m_pfn_qglPixelStorei (GL_UNPACK_ALIGNMENT, 1); + + unsigned char *buf = (unsigned char*)malloc (128*128); + memset (buf, 255, 128*128); + + for (i = 0; i < 2048; i++) + for (j = 0; j < 8; j++) + if ((data[i] & (1 << j)) != 0) + buf[i*8+j] = 0; + + g_GLTable.m_pfn_qglTexImage2D (GL_TEXTURE_2D, 0, GL_INTENSITY4, 128, 128, 0, + GL_LUMINANCE, GL_UNSIGNED_BYTE, buf); + free (buf); +} + +void texfont_write (const char *text, int l, int t) +{ + if (texture == 0) + return; + + g_GLTable.m_pfn_qglColor3f (0, 1, 0); + g_GLTable.m_pfn_qglBindTexture (GL_TEXTURE_2D, texture); + g_GLTable.m_pfn_qglEnable (GL_TEXTURE_2D); + // g_GLTable.m_pfn_qglTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + g_GLTable.m_pfn_qglAlphaFunc (GL_GREATER, 0.0625); + g_GLTable.m_pfn_qglEnable (GL_ALPHA_TEST); + + g_GLTable.m_pfn_qglBegin (GL_QUADS); + for (const char* p = text; *p; p++) + { + if (*p < 32 || *p > 125) + continue; + if (glyphs[*p-32].width == 0) + continue; + + g_GLTable.m_pfn_qglTexCoord2f (glyphs[*p-32].left, glyphs[*p-32].top); + g_GLTable.m_pfn_qglVertex2i (l, t); + g_GLTable.m_pfn_qglTexCoord2f (glyphs[*p-32].left, glyphs[*p-32].bottom); + g_GLTable.m_pfn_qglVertex2i (l, t-16); + g_GLTable.m_pfn_qglTexCoord2f (glyphs[*p-32].right, glyphs[*p-32].bottom); + g_GLTable.m_pfn_qglVertex2i (l + glyphs[*p-32].width, t-16); + g_GLTable.m_pfn_qglTexCoord2f (glyphs[*p-32].right, glyphs[*p-32].top); + g_GLTable.m_pfn_qglVertex2i (l + glyphs[*p-32].width, t); + l += glyphs[*p-32].width; + } + g_GLTable.m_pfn_qglEnd (); + + g_GLTable.m_pfn_qglDisable (GL_ALPHA_TEST); + g_GLTable.m_pfn_qglDisable (GL_TEXTURE_2D); + g_GLTable.m_pfn_qglBindTexture (GL_TEXTURE_2D, 0); +} diff --git a/contrib/gtkgensurf/gendlgs.cpp b/contrib/gtkgensurf/gendlgs.cpp new file mode 100644 index 00000000..f2df9ea6 --- /dev/null +++ b/contrib/gtkgensurf/gendlgs.cpp @@ -0,0 +1,2364 @@ +/* +GenSurf plugin for GtkRadiant +Copyright (C) 2001 David Hyde, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include "gensurf.h" + +#define GENERAL_TAB 0 +#define EXTENTS_TAB 1 +#define BITMAP_TAB 2 +#define FIXPOINTS_TAB 3 +#define TEXTURE_TAB 4 +//#define BUFF_SIZE 32768 + +#define ENABLE_WIDGET(name,enable) \ + gtk_widget_set_sensitive (GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), (name))), (enable)) +#define CHECK_WIDGET(name,check) \ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (g_object_get_data (G_OBJECT (g_pWnd), name)), check) + +static GtkWidget *game_radios[NUMGAMES]; +static GtkWidget *wave_radios[5]; +static GtkWidget *plane_radios[6]; +static guint current_tab; +static int OldPreview; +static int WasDetail; +static GtkTooltips *tooltips; +static int FirstPassComplete = 0; + +void About (GtkWidget *parent) +{ +/* + char *icon_xpm[] = { +"32 32 4 1", +" c None", +". c #000000", +"+ c #FFFFFF", +"@ c #838183", +"................................", +"................................", +"................................", +"................................", +"................................", +"................................", +"................................", +"...............++...............", +".............++++++.............", +"............++@+++@+............", +"..........+++..++..+++..........", +"........++.+.++..++.+.@+........", +".......+..+..+.++.+..+..++......", +".....++..++.+..++..+.++..++.....", +"...++..++...+.+..+.++..++..++...", +"..++.+.++....++..++....++.+..+..", +".+.+..+..++....++....++..++.+.+.", +"..+++....+.++++++++++.+....+++..", +"....++.@@+++++.++.++++@++.++....", +"......+++++++......++@+++++.....", +".......+++.+.++..++.+..++.......", +".........++..+.++.+..++.........", +"...........++..++..++...........", +".............++..+.+............", +"..............+..+@.............", +"...............@@...............", +"................................", +"................................", +"................................", +"................................", +"................................", +"................................" +}; +*/ + // leo: I'm too lazy to create a nice about box + // ^Fishman - I am lazy too :P. + g_FuncTable.m_pfnMessageBox (parent, "GtkGenSurf 1.05\n\n" + "Original version\n" + "David Hyde (rascal@vicksburg.com)\n\n" + "Porting\n" + "Leonardo Zide (leo@lokigames.com)\n\n" + "Enhancements\n" + "Pablo Zurita (pablo@qeradiant.com)\n" + "Hydra (hydra@hydras-world.com)", + "About GtkGenSurf", eMB_OK); +} + +// ============================================================================= +// main dialog + +static void SetupControls () +{ + switch (current_tab) + { + case GENERAL_TAB: + break; + + case EXTENTS_TAB: + if (Game != QUAKE3) + { + gtk_widget_hide (GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "use_patches"))); + ENABLE_WIDGET ("use_patches", FALSE); + } + else + { + gtk_widget_show (GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "use_patches"))); + ENABLE_WIDGET ("use_patches", TRUE); + } + + if (Game == QUAKE3 && UsePatches != 0) + { + ENABLE_WIDGET ("decimate", FALSE); + } + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "snap_text")), "Snap to grid:"); // ^Fishman - Snap to grid. + break; + + case BITMAP_TAB: + if (WaveType != WAVE_BITMAP) + { + ENABLE_WIDGET ("bmp_file", FALSE); + ENABLE_WIDGET ("bmp_file_browse", FALSE); + ENABLE_WIDGET ("bmp_black", FALSE); + ENABLE_WIDGET ("bmp_white", FALSE); + ENABLE_WIDGET ("bmp_text1", FALSE); + ENABLE_WIDGET ("bmp_text2", FALSE); + ENABLE_WIDGET ("bmp_text3", FALSE); + ENABLE_WIDGET ("bmp_reload", FALSE); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "bmp_note")), + "These options are disabled unless \"From Bitmap\"\n" + "is selected as the Waveform on the General tab."); + } + else + { + ENABLE_WIDGET ("bmp_file", TRUE); + ENABLE_WIDGET ("bmp_file_browse", TRUE); + ENABLE_WIDGET ("bmp_black", TRUE); + ENABLE_WIDGET ("bmp_white", TRUE); + ENABLE_WIDGET ("bmp_text1", TRUE); + ENABLE_WIDGET ("bmp_text2", TRUE); + ENABLE_WIDGET ("bmp_text3", TRUE); + ENABLE_WIDGET ("bmp_reload", strlen(gbmp.name) != 0); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "bmp_note")), + "GenSurf works only with 8-bit bitmaps. Color indices are\n" + "mapped to values for each vertex. Generally, gray scale\n" + "images are stored with black as color 0, white as color 255."); + } + break; + + case FIXPOINTS_TAB: + ENABLE_WIDGET ("fix_value", (NumVerticesSelected != 0)); + ENABLE_WIDGET ("fix_value_text", (NumVerticesSelected != 0)); + ENABLE_WIDGET ("fix_free", (NumVerticesSelected != 0)); + ENABLE_WIDGET ("fix_range", ((NumVerticesSelected != 0) && (WaveType != WAVE_ROUGH_ONLY))); + ENABLE_WIDGET ("fix_range_text", ((NumVerticesSelected != 0) && (WaveType != WAVE_ROUGH_ONLY))); + ENABLE_WIDGET ("fix_rate", ((NumVerticesSelected != 0) && (WaveType != WAVE_ROUGH_ONLY))); + ENABLE_WIDGET ("fix_rate_text", ((NumVerticesSelected != 0) && (WaveType != WAVE_ROUGH_ONLY))); + break; + + case TEXTURE_TAB: + ENABLE_WIDGET ("texture2", (UsePatches == 0)); + ENABLE_WIDGET ("texture3", (UsePatches == 0)); + ENABLE_WIDGET ("tex_slant", (UsePatches == 0)); + ENABLE_WIDGET ("detail", (UsePatches == 0)); + if (Game != QUAKE3 ) + { + ENABLE_WIDGET ("terrain_ent", FALSE); // ^Fishman - Adds terrain key to func_group. + ENABLE_WIDGET ("hint", (UsePatches == 0)); + } + break; + } + + switch (WaveType) + { + case WAVE_HCYLINDER: + case WAVE_VCYLINDER: + ENABLE_WIDGET ("amplitude", TRUE); + ENABLE_WIDGET ("wavelength", TRUE); + ENABLE_WIDGET ("z00", TRUE); + ENABLE_WIDGET ("z01", TRUE); + ENABLE_WIDGET ("z10", TRUE); + ENABLE_WIDGET ("z11", TRUE); + ENABLE_WIDGET ("linearborder", TRUE); + ENABLE_WIDGET ("go", TRUE); + break; + case WAVE_BITMAP: + ENABLE_WIDGET ("amplitude", FALSE); + ENABLE_WIDGET ("wavelength", FALSE); + ENABLE_WIDGET ("z00", FALSE); + ENABLE_WIDGET ("z01", FALSE); + ENABLE_WIDGET ("z10", FALSE); + ENABLE_WIDGET ("z11", FALSE); + ENABLE_WIDGET ("linearborder", FALSE); + ENABLE_WIDGET ("go", (gbmp.colors != NULL ? TRUE : FALSE)); + break; + case WAVE_ROUGH_ONLY: + ENABLE_WIDGET ("amplitude", FALSE); + ENABLE_WIDGET ("wavelength", FALSE); + ENABLE_WIDGET ("z00", TRUE); + ENABLE_WIDGET ("z01", TRUE); + ENABLE_WIDGET ("z10", TRUE); + ENABLE_WIDGET ("z11", TRUE); + ENABLE_WIDGET ("linearborder", TRUE); + ENABLE_WIDGET ("go", TRUE); + break; + default: + ENABLE_WIDGET ("amplitude", TRUE); + ENABLE_WIDGET ("wavelength", TRUE); + ENABLE_WIDGET ("z00", TRUE); + ENABLE_WIDGET ("z01", TRUE); + ENABLE_WIDGET ("z10", TRUE); + ENABLE_WIDGET ("z11", TRUE); + ENABLE_WIDGET ("linearborder", TRUE); + ENABLE_WIDGET ("go", TRUE); + } + + switch (Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "hmin_text")), "X:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "hmax_text")), "X:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "vmin_text")), "Z:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "vmax_text")), "Z:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "nh_text")), "X:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "nv_text")), "Z:"); + break; + case PLANE_YZ0: + case PLANE_YZ1: + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "hmin_text")), "Y:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "hmax_text")), "Y:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "vmin_text")), "Z:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "vmax_text")), "Z:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "nh_text")), "Y:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "nv_text")), "Z:"); + break; + default: + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "hmin_text")), "X:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "hmax_text")), "X:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "vmin_text")), "Y:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "vmax_text")), "Y:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "nh_text")), "X:"); + gtk_label_set_text (GTK_LABEL (g_object_get_data (G_OBJECT (g_pWnd), "nv_text")), "Y:"); + break; + } +} + +// SetDlgValues fills in text boxes and initializes other input controls +static void SetDlgValues (int tab) +{ + char Text[256]; + char RForm[16] = "%.5g"; + int i; + + switch (tab) + { + case GENERAL_TAB: + // Hell if I know why, but in the release build the 2nd pass thru the + // set_sensitive loop for game_radios crashes. No need to do this more + // than once anyhow. + if (!FirstPassComplete) + { + for (i = 0; i < NUMGAMES; i++) + gtk_widget_set_sensitive (game_radios[i], (i == Game ? TRUE : FALSE)); + for (i = 0; i < 6; i++) + gtk_widget_set_sensitive (plane_radios[i], (i == Plane ? TRUE : FALSE)); + } + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (game_radios[Game]), TRUE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (plane_radios[Plane]), TRUE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wave_radios[WaveType]), TRUE); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (g_object_get_data (G_OBJECT (g_pWnd), "random")), + RandomSeed); + sprintf (Text, RForm, WaveLength); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "wavelength")), Text); + sprintf (Text, RForm, Amplitude); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "amplitude")), Text); + sprintf (Text, RForm, Roughness); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "roughness")), Text); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (g_object_get_data + (G_OBJECT (g_pWnd), "main_antialiasing")), Antialiasing); + break; + + case EXTENTS_TAB: + sprintf (Text,RForm,Hll); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "hmin")), Text); + sprintf (Text,RForm,Vll); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "vmin")), Text); + sprintf (Text,RForm,Hur); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "hmax")), Text); + sprintf (Text,RForm,Vur); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "vmax")), Text); + sprintf (Text,RForm,Z00); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "z00")), Text); + sprintf (Text,RForm,Z01); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "z01")), Text); + sprintf (Text,RForm,Z10); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "z10")), Text); + sprintf (Text,RForm,Z11); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "z11")), Text); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (g_object_get_data (G_OBJECT (g_pWnd), "nh")), NH); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (g_object_get_data (G_OBJECT (g_pWnd), "nv")), NV); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (g_object_get_data (G_OBJECT (g_pWnd), "sp")), SP); // ^Fishman - Snap to grid. + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (g_object_get_data + (G_OBJECT (g_pWnd), "linearborder")), FixBorders); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (g_object_get_data + (G_OBJECT (g_pWnd), "use_patches")), UsePatches); + gtk_adjustment_set_value (GTK_ADJUSTMENT (g_object_get_data (G_OBJECT (g_pWnd), "decimate_adj")), + Decimate); + + if (Game == QUAKE3 && UsePatches) + { + gtk_widget_set_sensitive (GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "decimate")), FALSE); + + if (NH % 2) + { + NH++; + if (NH > MAX_ROWS) NH -= 2; + SetDlgValues (current_tab); + } + + if (NV % 2) + { + NV++; + if (NV > MAX_ROWS) NV -= 2; + SetDlgValues (current_tab); + } + if (NH % 2 ) NH++; + if (NH < 2 ) NH = 2; + if (NH > MAX_ROWS) NH = MAX_ROWS; + if (NV % 2 ) NV++; + if (NV < 2 ) NV = 2; + if (NV > MAX_ROWS) NV = MAX_ROWS; + + gpointer spin = g_object_get_data (G_OBJECT (g_pWnd), "nh"); + GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spin)); + adj->lower = 2; + gtk_adjustment_changed (adj); + spin = g_object_get_data (G_OBJECT (g_pWnd), "nv"); + adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spin)); + adj->lower = 2; + gtk_adjustment_changed (adj); + } + else + { + gtk_widget_set_sensitive (GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "decimate")), TRUE); + + gpointer spin = g_object_get_data (G_OBJECT (g_pWnd), "nh"); + GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spin)); + adj->lower = 1; + gtk_adjustment_changed (adj); + spin = g_object_get_data (G_OBJECT (g_pWnd), "nv"); + adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spin)); + adj->lower = 1; + gtk_adjustment_changed (adj); + } + + gtk_spin_button_set_value (GTK_SPIN_BUTTON (g_object_get_data (G_OBJECT (g_pWnd), "nh")), NH); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (g_object_get_data (G_OBJECT (g_pWnd), "nv")), NV); + + break; + + case BITMAP_TAB: + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "bmp_file")), gbmp.name); + sprintf(Text,"%g",gbmp.black_value); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "bmp_black")), Text); + sprintf(Text,"%g",gbmp.white_value); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "bmp_white")), Text); + break; + + case FIXPOINTS_TAB: + break; + + case TEXTURE_TAB: + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "texture1")), Texture[Game][0]); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "texture2")), Texture[Game][1]); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "texture3")), Texture[Game][2]); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (g_object_get_data (G_OBJECT (g_pWnd), "tex_slant")), + SlantAngle); + sprintf(Text,RForm,TexOffset[0]); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "texoffsetx")), Text); + sprintf(Text,RForm,TexOffset[1]); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "texoffsety")), Text); + sprintf(Text,RForm,TexScale[0]); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "texscalex")), Text); + sprintf(Text,RForm,TexScale[1]); + gtk_entry_set_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "texscaley")), Text); + CHECK_WIDGET ("detail", UseDetail); + + if (Game==QUAKE3) + { + ENABLE_WIDGET ("hint", FALSE); + AddHints=0; + } + else + ENABLE_WIDGET ("hint", TRUE); + CHECK_WIDGET ("hint", AddHints); + + /* + if (Game==SIN) + { + // ArghRad doesn't currently support SiN + EnableWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2), 0); + EnableWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_SPIN), 0); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_TEXT), SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2), SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_SPIN), SW_HIDE); + SetDlgItemText(hwndDisplay,DLG_TEX_USEPAK,"Use sin file"); + SetDlgItemText(hwndDisplay,DLG_TEX_PAK_TEXT,"Sin:"); + } + */ + + if(Game==QUAKE3) + { + /* + // ArghRad sun is inapplicable (so far) + EnableWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2), 0); + EnableWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_SPIN), 0); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_TEXT), SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2), SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_SPIN), SW_HIDE); + // No ladders in Q3 + EnableWindow(GetDlgItem(hwndDisplay,DLG_LADDER), 0); + ShowWindow(GetDlgItem(hwndDisplay,DLG_LADDER), SW_HIDE); + SetDlgItemText(hwndDisplay,DLG_TEX_USEPAK,"Use pk3 file"); + SetDlgItemText(hwndDisplay,DLG_TEX_PAK_TEXT,"PK3:"); + */ + } + +/*trix if(Game==HERETIC2) + { + // ArghRad doesn't currently support Heretic2 + EnableWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2), 0); + EnableWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_SPIN), 0); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_TEXT), SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2), SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_SPIN), SW_HIDE); + + SetDlgItemText(hwndDisplay,DLG_TEX_USEPAK,"Use pak file"); + SetDlgItemText(hwndDisplay,DLG_TEX_PAK_TEXT,"Pak:"); + } */ + /* + if(Game==HALFLIFE) + { + // A bunch of controls aren't applicable to HL + EnableWindow(GetDlgItem(hwndDisplay,DLG_TEXTURE_BROWSE), 0); + EnableWindow(GetDlgItem(hwndDisplay,DLG_TEXTURE2_BROWSE),0); + EnableWindow(GetDlgItem(hwndDisplay,DLG_TEXTURE3_BROWSE),0); + EnableWindow(GetDlgItem(hwndDisplay,DLG_DETAIL), 0); + EnableWindow(GetDlgItem(hwndDisplay,DLG_LADDER), 0); + EnableWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2), 0); + EnableWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_SPIN), 0); + ShowWindow(GetDlgItem(hwndDisplay,DLG_TEXTURE_BROWSE), SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_TEXTURE2_BROWSE),SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_TEXTURE3_BROWSE),SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_DETAIL), SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_LADDER), SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_TEXT), SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2), SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_SPIN), SW_HIDE); + + SetDlgItemText(hwndDisplay,DLG_TEX_USEPAK,"Use wad file"); + SetDlgItemText(hwndDisplay,DLG_TEX_PAK_TEXT,"Wad:"); + SetDlgItemText(hwndDisplay,DLG_HINT,"Hint brushes"); + } + + if(Game==GENESIS3D) + { + // No Q2-type compilers support Genesis3D (including ArghRad) + EnableWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2), 0); + EnableWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_SPIN), 0); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_TEXT), SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2), SW_HIDE); + ShowWindow(GetDlgItem(hwndDisplay,DLG_ARGHRAD2_SPIN), SW_HIDE); + + SetDlgItemText(hwndDisplay,DLG_TEX_USEPAK,"Use sin file"); + SetDlgItemText(hwndDisplay,DLG_TEX_PAK_TEXT,"Sin:"); + } + */ + break; + } + SetupControls (); +} + +static void ReadDlgValues (int tab) +{ + // char Text[256]; + // int i; + + switch (tab) + { + case GENERAL_TAB: + gpointer spin; + Roughness = atof ( gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "roughness")))); + WaveLength = atof ( gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "wavelength")))); + Amplitude = atof ( gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "amplitude")))); + spin = g_object_get_data (G_OBJECT (g_pWnd), "random"); + RandomSeed = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (spin)); + break; + + case EXTENTS_TAB: + SP = atoi (gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "sp")))); + NH = atoi (gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "nh")))); + NV = atoi (gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "nv")))); + + if (Game == QUAKE3 && UsePatches != 0) + { + if (NH % 2 ) NH++; + if (NH < 2 ) NH = 2; + if (NH > MAX_ROWS) NH = MAX_ROWS; + if (NV % 2 ) NV++; + if (NV < 2 ) NV = 2; + if (NV > MAX_ROWS) NV = MAX_ROWS; + } + break; + +#if 0 + case BITMAP_TAB: + + if (WaveType == WAVE_BITMAP) + { + GetDlgItemText(hwnd,DLG_BMP_FILE,gbmp.name,sizeof(gbmp.name)); + CheckValidDIB(hwnd); + GetDlgItemText(hwnd,DLG_BMP_BLACK,Text,sizeof(Text)); + gbmp.black_value = atof(Text); + GetDlgItemText(hwnd,DLG_BMP_WHITE,Text,sizeof(Text)); + gbmp.white_value = atof(Text); + UpdatePreview(TRUE); + } + break; + + case FIXPOINTS_TAB: + GetDlgItemText(hwnd,DLG_FIX_VALUE,Text,sizeof(Text)); + temp.fixed_value = atoi(Text); + GetDlgItemText(hwnd,DLG_FIX_RANGE,Text,sizeof(Text)); + temp.range = atoi(Text); + GetDlgItemText(hwnd,DLG_FIX_RATE, Text,sizeof(Text)); + temp.rate = atof(Text); + for(k=0; k MAX_ROWS) + { + sprintf (Text, "The number of divisions must be > 0 and no greater than %d.", MAX_ROWS); + g_FuncTable.m_pfnMessageBox (g_pWnd, Text, "GenSurf", eMB_OK, eMB_ICONWARNING); + gtk_notebook_set_page (GTK_NOTEBOOK (notebook), EXTENTS_TAB); + return; + } + + if (NV < 1 || NV > MAX_ROWS) + { + sprintf (Text, "The number of divisions must be > 0 and no greater than %d.", MAX_ROWS); + g_FuncTable.m_pfnMessageBox (g_pWnd, Text, "GenSurf", eMB_OK, eMB_ICONWARNING); + gtk_notebook_set_page (GTK_NOTEBOOK (notebook), EXTENTS_TAB); + return; + } + + if (Hll >= Hur) + { + g_FuncTable.m_pfnMessageBox (g_pWnd, "The \"lower-left\" values must be less than " + "the corresponding \"upper-right\" values in " + "the \"Extent\" box.","GenSurf", eMB_OK, eMB_ICONWARNING); + gtk_notebook_set_page (GTK_NOTEBOOK (notebook), EXTENTS_TAB); + return; + } + + if (Vll >= Vur) + { + g_FuncTable.m_pfnMessageBox (g_pWnd,"The \"lower-left\" values must be less than " + "the corresponding \"upper-right\" values in " + "the \"Extent\" box.","GenSurf", eMB_OK, eMB_ICONWARNING); + gtk_notebook_set_page (GTK_NOTEBOOK (notebook), EXTENTS_TAB); + return; + } + + if (!strlen (Texture[Game][0])) + { + g_FuncTable.m_pfnMessageBox (g_pWnd, "You must supply a texture name.", "GenSurf", eMB_OK, eMB_ICONWARNING); + gtk_notebook_set_page (GTK_NOTEBOOK (notebook), EXTENTS_TAB); + return; + } + +/* if (Decimate>0 && GimpHints!=0) + { + MessageBox(hwnd,"You've elected to use a decimated grid and gimp's non-detail hint brushes. " + "This combination usually produces bizarre visual errors in the game, " + "so GenSurf has turned off the hint brush option.", + "GenSurf",eMB_ICONWARNING); + GimpHints = 0; + } */ + + gtk_widget_hide (g_pWnd); + if (g_pWndPreview) + gtk_widget_hide (g_pWndPreview); + + GenerateMap(); + WriteIniFile(gszIni); +} + +// ============================================================================= +// general tab callbacks + +static void general_game (GtkToggleButton *widget, gpointer data) +{ + if (gtk_toggle_button_get_active (widget)) + { + Game = GPOINTER_TO_INT (data); + UpdatePreview (TRUE); + } +} + +static void general_plane (GtkToggleButton *widget, gpointer data) +{ + if (gtk_toggle_button_get_active (widget)) + { + Plane = GPOINTER_TO_INT (data); + SetupControls (); + UpdatePreview (TRUE); + } +} + +static void general_wave (GtkToggleButton *widget, gpointer data) +{ + if (gtk_toggle_button_get_active (widget)) + { + WaveType = GPOINTER_TO_INT (data); + SetupControls (); + UpdatePreview (TRUE); + } +} + +static void general_random (GtkAdjustment *adj, gpointer data) +{ + int nPos = (int)adj->value; + + if (RandomSeed != nPos) + { + RandomSeed = nPos; + UpdatePreview (true); + } +} + +// ============================================================================= +// extents tab callbacks + +static void extents_linearborder (GtkToggleButton *check, gpointer data) +{ + FixBorders = gtk_toggle_button_get_active (check); + UpdatePreview (true); +} + +static void extents_use_patches (GtkToggleButton *check, gpointer data) +{ + if (Game != QUAKE3) + return; + + UsePatches = gtk_toggle_button_get_active (check); + SetDlgValues (current_tab); + SetupControls (); + UpdatePreview (true); +} + +static void extents_nhnv_spin (GtkAdjustment *adj, int *data) +{ + int nPos = (int)adj->value; + + if (*data != nPos) + { + if (Game==QUAKE3 && UsePatches && (nPos % 2)) + { + if (*data < nPos) + *data += 2; + else + *data -= 2; + gtk_adjustment_set_value (adj, *data); + } + else + *data = nPos; + UpdatePreview (true); + } +} + +static void extents_decimate (GtkAdjustment *adj, gpointer data) +{ + int nPos = (int)adj->value; + + Decimate = nPos; + UpdatePreview (true); +} + +// Hydra : snap to grid begin +/*static void extents_snaptogrid (GtkAdjustment *adj, gpointer data) +{ + int nPos = (int)adj->value; + + SnapToGrid = nPos; + UpdatePreview (true); +}*/ + +// ^Fishman - Modified version of Hydra's snap to grid code. +static void extents_snaptogrid_spin (GtkAdjustment *adj, int *data) +{ + int nPos = (int)adj->value; + SnapToGrid = nPos; + UpdatePreview (true); +} + +// ============================================================================= +// bitmap tab callbacks + +static gint bitmap_file_entryfocusout(GtkWidget* widget, GdkEventFocus* event, gpointer data) +{ + char filename[NAME_MAX]; + + strcpy (filename, gtk_entry_get_text (GTK_ENTRY(widget))); + if(strcmp (filename,gbmp.name)) + { + if (gbmp.colors) + { + free(gbmp.colors); + gbmp.colors=NULL; + } + strcpy (gbmp.name,filename); + if (strlen(gbmp.name) ) + OpenBitmap (); + ENABLE_WIDGET ("go", (gbmp.colors != NULL ? TRUE : FALSE)); + } + return FALSE; +} + +static void bitmap_browse (GtkWidget *widget, gpointer data) +{ + const char *filename; + char *ptr; + + filename = g_FuncTable.m_pfnFileDialog (g_pWnd, TRUE, "Bitmap File", gbmp.defpath); + + if (filename != NULL) + { + strcpy (gbmp.name, filename); + + ptr = strrchr (filename, G_DIR_SEPARATOR); + if (ptr != NULL) + { + *(ptr+1) = '\0'; + strcpy (gbmp.defpath, filename); + } + + OpenBitmap (); + ENABLE_WIDGET ("go", (gbmp.colors != NULL ? TRUE : FALSE)); + } +} + +static void bitmap_reload (GtkWidget *widget, gpointer data) +{ + strcpy (gbmp.name, gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (g_pWnd), "bmp_file")))); + if(strlen (gbmp.name) ) + { + OpenBitmap (); + ENABLE_WIDGET ("go", (gbmp.colors != NULL ? TRUE : FALSE)); + } + else + ENABLE_WIDGET ("go", FALSE ); +} + +// ============================================================================= +// fix points tab callbacks + +static gint fix_value_entryfocusout (GtkWidget* widget, GdkEventFocus *event, gpointer data) +{ + int i = atoi (gtk_entry_get_text (GTK_ENTRY(widget))), k; + char Text[32]; + + if (i < -65536 || i > 65536) + { + gdk_beep (); + g_FuncTable.m_pfnMessageBox (g_pWnd, "The value must be between -65536 and 65536, inclusive.", + "GenSurf", eMB_OK, eMB_ICONWARNING); + sprintf (Text, "%d", (int)xyz[Vertex[0].i][Vertex[0].j].fixed_value); + gtk_entry_set_text (GTK_ENTRY(widget), Text); + gtk_window_set_focus (GTK_WINDOW (gtk_widget_get_toplevel (widget)), widget); + } + else if (i != xyz[Vertex[0].i][Vertex[0].j].fixed_value) + { + for(k=0; kvalue; + + if (xyz[Vertex[0].i][Vertex[0].j].fixed_value != i) + { + for(k=0; k(data)); + return FALSE; +} + +// ============================================================================= +// create tooltips + +void create_tooltips () +{ + tooltips = gtk_tooltips_new (); + + // Main + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "go")), + "Accept all input and generate a surface in Q3Radiant", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "open")), + "Open a previously saved GenSurf settings file.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "save")), + "Save all settings to a file.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "defaults")), + "Restore default values from DEFAULTS.SRF. If this file does not exist, GenSurf " + "initializes all input parameters to reasonable values. You can create your own " + "default surface by setting all parameters to your liking, then saving a settings " + "file as DEFAULTS.SRF with the Save As button.", + ""); + + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "main_preview")), + "View a wire-frame representation of the surface", + ""); + + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "main_antialiasing")), + "The lines in the preview window are antialiased for better quality", + ""); + + // General tab + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (wave_radios[0]), + "Builds a surface with alternating hills and valleys. Uses the general form Z=cos(X) " + "x sin(Y)", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (wave_radios[1]), + "Builds a surface with ridges parallel to the vertical axis.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (wave_radios[2]), + "Builds a surface with ridges parallel to the horizontal axis.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (wave_radios[3]), + "Builds a map from a bitmap image representing a contour plot. Click the \"Bitmap\" " + "tab to select the image. GenSurf only supports 256-color (8 bit) " + "bitmaps. GenSurf will work with any 256-color bitmap, but gray scale bitmaps are a bit " + "more intuitive.", + "" ); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (wave_radios[4]), + "Builds a random surface using the Plasma Cloud technique. Variance is controlled " + "by the Roughness input. To build a surface with completely random values not " + "dependent on neighboring vertices, use one of the other waveforms with 0 amplitude.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "wavelength")), + "Enter the wavelength (distance between crests). NOTE: Wavelengths equal to the grid " + "size or 2 times the grid size will result in 0 amplitudes. For best results, the " + "wavelength value should be at least 4 times the grid size (extents divided by the " + "number of divisions", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "amplitude")), + "Enter the height of hills/ridges.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "roughness")), + "Enter the roughness value (noise) for the surface. For fractal surfaces, this value " + "is used as a variance in the fractal calculations.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "random")), + "Seed value for the pseudo-random number generator.", + ""); + // Extents tab + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "hmin")), + "Minimum horizontal coordinate of the surface, i.e. X for a surface parallel to " + "the XY or XZ planes, Y for a surface parallel to the YZ plane. For best results, " + "the extents (maximum-minimum values) in a given direction should be evenly " + "divisible by the number of divisions in that direction.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "hmax")), + "Maximum horizontal coordinate of the surface, i.e. X for a surface parallel to " + "the XY or XZ planes, Y for a surface parallel to the YZ plane. For best results, " + "the extents (maximum-minimum values) in a given direction should be evenly " + "divisible by the number of divisions in that direction.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "vmin")), + "Minimum vertical coordinate of the surface, i.e. Y for a surface parallel to " + "the XY plane, Z for a surface parallel to the XZ or YZ planes. For best results, " + "the extents (maximum-minimum values) in a given direction should be evenly " + "divisible by the number of divisions in that direction.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "vmax")), + "Maximum vertical coordinate of the surface, i.e. Y for a surface parallel to " + "the XY plane, Z for a surface parallel to the XZ or YZ planes. For best results, " + "the extents (maximum-minimum values) in a given direction should be evenly " + "divisible by the number of divisions in that direction.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "nh")), + "Number of divisions in the horizontal direction. For best results, the extents " + "in a given direction should be evenly divisible by the number of divisions in " + "that direction.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "nv")), + "Number of divisions in the vertical direction. For best results, the extents " + "in a given direction should be evenly divisible by the number of divisions in " + "that direction.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "use_patches")), + "Produce one or more curved patches in the shape of your selected surface rather " + "than producing solid brushes. Depending on the size of your surface (and the " + "user's graphic detail settings, which you cannot control), curved surfaces will " + "be represented in the game by a very large number of polygons. Read the warnings " + "concerning curved surfaces on the GenSurf web page before using this feature.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "decimate")), + "Use the slider to control the number of vertices discarded by GenSurf. For many " + "surfaces, you can produce roughly the same shape surface with a high decimation " + "value. This will generally result in a map with lower polygon counts (and better " + "in-game performance). However, this feature should NOT be used for large terrain " + "surfaces in Q3", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "z00")), + "Enter the height of the surface at the lower left corner. This value will likely " + "be modified unless \"Linear Borders\" is checked.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "z01")), + "Enter the height of the surface at the upper left corner. This value will likely " + "be modified unless \"Linear Borders\" is checked.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "z10")), + "Enter the height of the surface at the lower right corner. This value will likely " + "be modified unless \"Linear Borders\" is checked.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "z11")), + "Enter the height of the surface at the upper right corner. This value will likely " + "be modified unless \"Linear Borders\" is checked.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "linearborder")), + "Restrict the edges of the surface to a straight line. This will help match up " + "brush edges if you drop this surface into another map.", + ""); + // Bitmap tab + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "bmp_file")), + "Type the name of an 8-bit bitmap image file, or click Browse to select an image " + "from a list of those available on your system.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "bmp_file_browse")), + "Select a bitmap image file from a list of those available on your system.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "bmp_reload")), + "Reload the selected bitmap file after making changes in an external image editor.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "bmp_black")), + "Enter the value corresponding to color index 0 in the bitmap file. For gray scale " + "images, color 0 is normally black.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "bmp_white")), + "Enter the value corresponding to color index 255 in the bitmap file. For gray scale " + "images, color 255 is normally white.", + ""); + // Fixpoints tab + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "fix_value")), + "Enter a value for the selected vertex. This value will not be adjusted when applying " + "a waveform or roughness to the surface. Unlock this vertex (so that it will be " + "adjusted normally) by clicking \"Free\". This vertex will influence vertices within " + "the \"Range affected\" of this vertex.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "fix_range")), + "Enter the range away from the selected vertex that other vertices will be affected. " + "Use 0 if you don't want other vertices to be influenced by the currently selected " + "one. Note: this box is disabled if you've chosen the fractal generator, as it uses " + "a completely different method for determining values.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "fix_rate")), + "Enter a rate of change for the surface affected by the fixed value. 0 gives a smooth " + "sinusoidal curve, values less than 0 give progressively sharper spikes, and values " + "greater than 0 take on a square shape. Values less than -30 or greater than 30 are " + "set to -30 and 30, respectively. Note that this entry will have no effect unless " + "you also specify a \"range affected\".", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "fix_free")), + "Click this to free (unlock the value of) the currently selected vertex.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "fix_freeall")), + "Click this to free (unlock the values of) all vertices.", + ""); + // Texture tab + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "texture1")), + "Enter the name of the texture or shader used for the surface faces.", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "texture2")), + "Enter the name of the texture or shader used for faces other than the surface. Under " + "normal circumstances this should be \"common/caulk\"", + ""); + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "texture3")), + "Enter the name of the texture or shader used for \"steep\" surface faces, where \"steep\" " + "is the angle specified below. If this entry is left blank or if the \"steep\" angle is 0, " + "all surface faces will use the texture specified by \"Surface\".", + ""); + + gtk_tooltips_set_tip(GTK_TOOLTIPS (tooltips), + GTK_WIDGET (g_object_get_data (G_OBJECT (g_pWnd), "detail")), + "Check this box to use the detail content property on the generated brushes. Compile " + "times will be considerably shorter if the detail property is used, though the surface " + "will not block visibility at all. If you use the detail property, you should make sure " + "that \"common/caulk\" is used for the non-surface faces, or the polygon count will be " + "much higher than necessary.", + ""); +} + +// ============================================================================= +// create main dialog + +GtkWidget* create_main_dialog () +{ + GtkWidget *dlg, *vbox, *hbox, *hbox2, *button, *notebook, *frame, *table, *table2; + GtkWidget *check, *spin, *radio, *label, *entry, *scale; + GtkObject *adj; + GSList *group; + int i; + char *games[] = { "Quake 2", "Half-Life", "SiN", "Heretic 2", "Kingpin", "Genesis3D", "Quake 3 Arena" }; + char *waveforms[] = { "Alternating hill/valley", "Cylindrical left-to-right", "Cylindrical top-to-bottom", + "From bitmap", "Fractal" }; + char *orientations[] = { "Ground surface", "Ceiling", "Wall facing 0", "Wall facing 90", + "Wall facing 180","Wall facing 270" }; + + g_pWnd = dlg = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (dlg), gszCaption); + g_signal_connect (G_OBJECT (dlg), "delete_event", G_CALLBACK (main_close), NULL); + // g_signal_connect (G_OBJECT (dlg), "destroy", G_CALLBACK (gtk_widget_destroy), NULL); + gtk_window_set_transient_for (GTK_WINDOW (dlg), GTK_WINDOW (g_pRadiantWnd)); + + hbox = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox); + gtk_container_add (GTK_CONTAINER (dlg), hbox); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 5); + + notebook = gtk_notebook_new (); + gtk_widget_show (notebook); + gtk_box_pack_start (GTK_BOX (hbox), notebook, TRUE, TRUE, 0); + g_signal_connect (G_OBJECT (notebook), "switch_page", + G_CALLBACK (switch_page), NULL); + gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP); + g_object_set_data (G_OBJECT (dlg), "notebook", notebook); + + table = gtk_table_new (2, 2, FALSE); + gtk_widget_show (table); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + label = gtk_label_new ("General"); + gtk_widget_show (label); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), table, label); + + frame = gtk_frame_new ("Game"); + gtk_widget_show (frame); + gtk_table_attach (GTK_TABLE (table), frame, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + + vbox = gtk_vbox_new (TRUE, 5); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + for (i = 0, group = NULL; i < NUMGAMES; i++) + { + radio = gtk_radio_button_new_with_label (group, games[i]); + gtk_widget_show (radio); + gtk_box_pack_start (GTK_BOX (vbox), radio, TRUE, TRUE, 0); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (radio)); + game_radios[i] = radio; + g_signal_connect (G_OBJECT (radio), "toggled", G_CALLBACK (general_game), GINT_TO_POINTER (i)); + } + + frame = gtk_frame_new ("Waveform"); + gtk_widget_show (frame); + gtk_table_attach (GTK_TABLE (table), frame, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + + vbox = gtk_vbox_new (TRUE, 5); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + for (i = 0, group = NULL; i < 5; i++) + { + radio = gtk_radio_button_new_with_label (group, waveforms[i]); + gtk_widget_show (radio); + gtk_box_pack_start (GTK_BOX (vbox), radio, TRUE, TRUE, 0); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (radio)); + wave_radios[i] = radio; + g_signal_connect (G_OBJECT (radio), "toggled", G_CALLBACK (general_wave), GINT_TO_POINTER (i)); + } + + frame = gtk_frame_new ("Orientation"); + gtk_widget_show (frame); + gtk_table_attach (GTK_TABLE (table), frame, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + + vbox = gtk_vbox_new (TRUE, 5); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + for (i = 0, group = NULL; i < 6; i++) + { + radio = gtk_radio_button_new_with_label (group, orientations[i]); + gtk_widget_show (radio); + gtk_box_pack_start (GTK_BOX (vbox), radio, TRUE, TRUE, 0); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (radio)); + plane_radios[i] = radio; + g_signal_connect (G_OBJECT (radio), "toggled", G_CALLBACK (general_plane), GINT_TO_POINTER (i)); + } + + table2 = gtk_table_new (4, 2, FALSE); + gtk_widget_show (table2); + gtk_table_set_row_spacings (GTK_TABLE (table2), 5); + gtk_table_set_col_spacings (GTK_TABLE (table2), 5); + gtk_table_attach (GTK_TABLE (table), table2, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + + label = gtk_label_new ("Wavelength:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table2), label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_RIGHT); + + label = gtk_label_new ("Max. amplitude:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table2), label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_RIGHT); + + label = gtk_label_new ("Roughness:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table2), label, 0, 1, 2, 3, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_RIGHT); + + label = gtk_label_new ("Random seed:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table2), label, 0, 1, 3, 4, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_RIGHT); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table2), entry, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 50, -2); + g_object_set_data (G_OBJECT (dlg), "wavelength", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (doublevariable_entryfocusout), &WaveLength); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table2), entry, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 50, -2); + g_object_set_data (G_OBJECT (dlg), "amplitude", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (doublevariable_entryfocusout), &Amplitude); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table2), entry, 1, 2, 2, 3, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 50, -2); + g_object_set_data (G_OBJECT (dlg), "roughness", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (doublevariable_entryfocusout), &Roughness); + + adj = gtk_adjustment_new (1, 1, 32767, 1, 10, 10); + g_signal_connect (G_OBJECT (adj), "value_changed", G_CALLBACK (general_random), NULL); + spin = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1, 0); + gtk_widget_show (spin); + gtk_table_attach (GTK_TABLE (table2), spin, 1, 2, 3, 4, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (spin, 60, -2); + g_object_set_data (G_OBJECT (dlg), "random", spin); + + vbox = gtk_vbox_new (FALSE, 5); + gtk_widget_show (vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + label = gtk_label_new ("Extents"); + gtk_widget_show (label); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); + + hbox2 = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox2); + gtk_box_pack_start (GTK_BOX (vbox), hbox2, FALSE, TRUE, 0); + + frame = gtk_frame_new ("Extents"); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (hbox2), frame, TRUE, TRUE, 0); + + table = gtk_table_new (3, 4, FALSE); + gtk_widget_show (table); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + label = gtk_label_new ("X:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + g_object_set_data (G_OBJECT (dlg), "hmin_text", label); + + label = gtk_label_new ("X:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + g_object_set_data (G_OBJECT (dlg), "hmax_text", label); + + label = gtk_label_new ("Y:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + g_object_set_data (G_OBJECT (dlg), "vmin_text", label); + + label = gtk_label_new ("Y:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + g_object_set_data (G_OBJECT (dlg), "vmax_text", label); + + label = gtk_label_new ("Lower-left"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + label = gtk_label_new ("Upper-right"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 3, 4, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 50, -2); + g_object_set_data (G_OBJECT (dlg), "hmin", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (doublevariable_entryfocusout), &Hll); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 3, 4, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 50, -2); + g_object_set_data (G_OBJECT (dlg), "hmax", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (doublevariable_entryfocusout), &Hur); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 2, 3, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 50, -2); + g_object_set_data (G_OBJECT (dlg), "vmin", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (doublevariable_entryfocusout), &Vll); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 3, 4, 2, 3, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 50, -2); + g_object_set_data (G_OBJECT (dlg), "vmax", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (doublevariable_entryfocusout), &Vur); + + frame = gtk_frame_new ("Divisions"); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (hbox2), frame, TRUE, TRUE, 0); + + table = gtk_table_new (2, 2, FALSE); + gtk_widget_show (table); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + label = gtk_label_new ("X:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + g_object_set_data (G_OBJECT (dlg), "nh_text", label); + + label = gtk_label_new ("Y:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + g_object_set_data (G_OBJECT (dlg), "nv_text", label); + + adj = gtk_adjustment_new (8, 1, MAX_ROWS, 1, 10, 10); + g_signal_connect (G_OBJECT (adj), "value_changed", G_CALLBACK (extents_nhnv_spin), &NH); + spin = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1, 0); + gtk_widget_show (spin); + gtk_table_attach (GTK_TABLE (table), spin, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (spin, 60, -2); + g_object_set_data (G_OBJECT (dlg), "nh", spin); + + adj = gtk_adjustment_new (8, 1, MAX_ROWS, 1, 10, 10); + g_signal_connect (G_OBJECT (adj), "value_changed", G_CALLBACK (extents_nhnv_spin), &NV); + spin = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1, 0); + gtk_widget_show (spin); + gtk_table_attach (GTK_TABLE (table), spin, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (spin, 60, -2); + g_object_set_data (G_OBJECT (dlg), "nv", spin); + + check = gtk_check_button_new_with_label ("Use Bezier patches"); + gtk_widget_show (check); + gtk_box_pack_start (GTK_BOX (vbox), check, FALSE, TRUE, 0); + g_object_set_data (G_OBJECT (dlg), "use_patches", check); + g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (extents_use_patches), NULL); + + // ^Fishman - Snap to grid, replaced scroll bar with a texbox. + label = gtk_label_new ("Snap to grid:"); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0); + gtk_object_set_data (GTK_OBJECT (dlg), "snap_text", label); + + adj = gtk_adjustment_new (8, 0, 256, 1, 10, 10); + g_signal_connect (G_OBJECT (adj), "value_changed", G_CALLBACK (extents_snaptogrid_spin), &SP); + spin = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1, 0); + gtk_widget_show (spin); + gtk_box_pack_start (GTK_BOX (vbox), spin, FALSE, TRUE, 0); + gtk_widget_set_usize (spin, 60, -2); + g_object_set_data (G_OBJECT (dlg), "sp", spin); + // ^Fishman - End of Snap to grid code. + + hbox2 = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox2); + gtk_box_pack_start (GTK_BOX (vbox), hbox2, FALSE, TRUE, 10); + + label = gtk_label_new ("Decimate:"); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox2), label, FALSE, TRUE, 0); + + adj = gtk_adjustment_new (0, 0, 110, 1, 10, 10); + g_signal_connect (G_OBJECT (adj), "value_changed", G_CALLBACK (extents_decimate), NULL); + g_object_set_data (G_OBJECT (dlg), "decimate_adj", adj); + scale = gtk_hscale_new (GTK_ADJUSTMENT (adj)); + gtk_widget_show (scale); + gtk_box_pack_start (GTK_BOX (hbox2), scale, TRUE, TRUE, 0); + gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_RIGHT); + gtk_scale_set_digits (GTK_SCALE (scale), 0); + g_object_set_data (G_OBJECT (dlg), "decimate", scale); + + frame = gtk_frame_new ("Corner values"); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0); + + table = gtk_table_new (3, 4, FALSE); + gtk_widget_show (table); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + label = gtk_label_new ("Upper-left:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + label = gtk_label_new ("Lower-left:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + label = gtk_label_new ("Upper-right:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + label = gtk_label_new ("Lower-right:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 50, -2); + g_object_set_data (G_OBJECT (dlg), "z01", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (doublevariable_entryfocusout), &Z01); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 50, -2); + g_object_set_data (G_OBJECT (dlg), "z00", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (doublevariable_entryfocusout), &Z00); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 3, 4, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 50, -2); + g_object_set_data (G_OBJECT (dlg), "z11", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (doublevariable_entryfocusout), &Z11); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 3, 4, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 50, -2); + g_object_set_data (G_OBJECT (dlg), "z10", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (doublevariable_entryfocusout), &Z10); + + check = gtk_check_button_new_with_label ("Linear borders"); + gtk_widget_show (check); + gtk_table_attach (GTK_TABLE (table), check, 0, 4, 2, 3, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + g_object_set_data (G_OBJECT (dlg), "linearborder", check); + g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (extents_linearborder), NULL); + + vbox = gtk_vbox_new (FALSE, 10); + gtk_widget_show (vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + label = gtk_label_new ("Bitmap"); + gtk_widget_show (label); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); + + label = gtk_label_new (""); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0); + g_object_set_data (G_OBJECT (dlg), "bmp_note", label); + + table = gtk_table_new (2, 2, FALSE); + gtk_widget_show (table); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, TRUE, 0); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + label = gtk_label_new ("Filename:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + g_object_set_data (G_OBJECT (dlg), "bmp_text1", label); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + g_object_set_data (G_OBJECT (dlg), "bmp_file", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (bitmap_file_entryfocusout), NULL); + + hbox2 = gtk_hbox_new (TRUE, 5); + gtk_widget_show (hbox2); + gtk_table_attach (GTK_TABLE (table), hbox2, 1, 2, 1, 2, + (GtkAttachOptions) (0), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + + button = gtk_button_new_with_label ("Browse..."); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (hbox2), button, FALSE, FALSE, 0); + gtk_widget_set_usize (button, 60, -2); + g_object_set_data (G_OBJECT (dlg), "bmp_file_browse", button); + g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (bitmap_browse), NULL); + + button = gtk_button_new_with_label ("Reload"); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (hbox2), button, FALSE, FALSE, 0); + gtk_widget_set_usize (button, 60, -2); + g_object_set_data (G_OBJECT (dlg), "bmp_reload", button); + g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (bitmap_reload), NULL); + + table = gtk_table_new (2, 2, TRUE); + gtk_widget_show (table); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, TRUE, 0); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + label = gtk_label_new ("Map color 0 to:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL|GTK_EXPAND), + (GtkAttachOptions) (GTK_FILL), 0, 0); + g_object_set_data (G_OBJECT (dlg), "bmp_text2", label); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_RIGHT); + + label = gtk_label_new ("Map color 255 to:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL|GTK_EXPAND), + (GtkAttachOptions) (GTK_FILL), 0, 0); + g_object_set_data (G_OBJECT (dlg), "bmp_text3", label); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_RIGHT); + + hbox2 = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox2); + gtk_table_attach (GTK_TABLE (table), hbox2, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_box_pack_start (GTK_BOX (hbox2), entry, FALSE, FALSE, 0); + gtk_widget_set_usize (entry, 50, -2); + g_object_set_data (G_OBJECT (dlg), "bmp_black", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (doublevariable_entryfocusout), &gbmp.black_value); + + hbox2 = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox2); + gtk_table_attach (GTK_TABLE (table), hbox2, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), 0, 0); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_box_pack_start (GTK_BOX (hbox2), entry, FALSE, FALSE, 0); + gtk_widget_set_usize (entry, 50, -2); + g_object_set_data (G_OBJECT (dlg), "bmp_white", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (doublevariable_entryfocusout), &gbmp.white_value); + + vbox = gtk_vbox_new (FALSE, 10); + gtk_widget_show (vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + label = gtk_label_new ("Fix Points"); + gtk_widget_show (label); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); + + label = gtk_label_new ("Click on a vertex in the lower half of the preview window,\n" + "then use the arrow keys or text box to assign a value.\n" + "Use Ctrl+Click to select multiple vertices/toggle a\n" + "selection. Use Shift+Click to select a range of vertices.\n\n" + "Click \"Free\" to unlock a vertex. Vertices within \"Range\n" + "affected\" will be influenced by this vertex."); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0); + + table = gtk_table_new (3, 3, FALSE); + gtk_widget_show (table); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, TRUE, 0); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + label = gtk_label_new ("Value:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + g_object_set_data (G_OBJECT (dlg), "fix_value_text", label); + + label = gtk_label_new ("Range affected:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + g_object_set_data (G_OBJECT (dlg), "fix_range_text", label); + + label = gtk_label_new ("Rate of change:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + g_object_set_data (G_OBJECT (dlg), "fix_rate_text", label); + + adj = gtk_adjustment_new (0, -65536, 65536, 1, 16, 16); + g_signal_connect (G_OBJECT (adj), "value_changed", G_CALLBACK (fix_value_changed), NULL); + spin = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1, 0); + gtk_widget_show (spin); + gtk_table_attach (GTK_TABLE (table), spin, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND), + (GtkAttachOptions) (GTK_EXPAND), 0, 0); + gtk_widget_set_usize (spin, 60, -2); + g_object_set_data (G_OBJECT (dlg), "fix_value", spin); + g_signal_connect (G_OBJECT (spin), "focus_out_event", G_CALLBACK (fix_value_entryfocusout), NULL); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 60, -2); + g_object_set_data (G_OBJECT (dlg), "fix_range", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (fix_range_entryfocusout), NULL); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 2, 3, + (GtkAttachOptions) (GTK_EXPAND), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 60, -2); + g_object_set_data (G_OBJECT (dlg), "fix_rate", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK (fix_rate_entryfocusout), NULL); + + button = gtk_button_new_with_label ("Free"); + gtk_widget_show (button); + gtk_table_attach (GTK_TABLE (table), button, 2, 3, 0, 1, + (GtkAttachOptions) (GTK_EXPAND), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_widget_set_usize (button, 60, -2); + g_object_set_data (G_OBJECT (dlg), "fix_free", button); + g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (fix_free), NULL); + + button = gtk_button_new_with_label ("Free All"); + gtk_widget_show (button); + gtk_table_attach (GTK_TABLE (table), button, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_EXPAND), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_widget_set_usize (button, 60, -2); + g_object_set_data (G_OBJECT (dlg), "fix_freeall", button); + g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (fix_freeall), NULL); + + vbox = gtk_vbox_new (FALSE, 10); + gtk_widget_show (vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + label = gtk_label_new ("Texture"); + gtk_widget_show (label); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox, label); + + // ^Fishman - Modified to add more labels and textboxes. + table = gtk_table_new (5, 2, FALSE); + gtk_widget_show (table); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, TRUE, 0); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + label = gtk_label_new ("Surface:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + + label = gtk_label_new ("Other:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + + label = gtk_label_new ("Steep:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 60, -2); + g_object_set_data (G_OBJECT (dlg), "texture1", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK(texture_entryfocusout), GINT_TO_POINTER (0)); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 60, -2); + g_object_set_data (G_OBJECT (dlg), "texture2", entry); + g_signal_connect (G_OBJECT (entry), "focus_out_event", G_CALLBACK(texture_entryfocusout), GINT_TO_POINTER (1)); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 2, 3, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 60, -2); + g_object_set_data (G_OBJECT (dlg), "texture3", entry); + + hbox2 = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox2); + gtk_box_pack_start (GTK_BOX (vbox), hbox2, FALSE, TRUE, 0); + + label = gtk_label_new ("\"Steep\" angle:"); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox2), label, FALSE, TRUE, 0); + + adj = gtk_adjustment_new (60, 0, 90, 1, 10, 10); + spin = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1, 0); + gtk_widget_show (spin); + gtk_box_pack_start (GTK_BOX (hbox2), spin, FALSE, TRUE, 0); + g_object_set_data (G_OBJECT (dlg), "tex_slant", spin); + + table = gtk_table_new (2, 4, TRUE); + gtk_widget_show (table); + gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, TRUE, 0); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + label = gtk_label_new ("Offset "); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + label = gtk_label_new ("Scale "); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 2, 4, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 60, -2); + g_object_set_data (G_OBJECT (dlg), "texoffsetx", entry); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 60, -2); + g_object_set_data (G_OBJECT (dlg), "texoffsety", entry); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 60, -2); + g_object_set_data (G_OBJECT (dlg), "texscalex", entry); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 3, 4, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_widget_set_usize (entry, 60, -2); + g_object_set_data (G_OBJECT (dlg), "texscaley", entry); + + + + check = gtk_check_button_new_with_label ("Use detail brushes"); + gtk_widget_show (check); + gtk_box_pack_start (GTK_BOX (vbox), check, FALSE, TRUE, 0); + g_object_set_data (G_OBJECT (dlg), "detail", check); + g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (texture_detail), NULL); + + check = gtk_check_button_new_with_label ("Detail hint brushes"); + gtk_widget_show (check); + gtk_box_pack_start (GTK_BOX (vbox), check, FALSE, TRUE, 0); + g_object_set_data (G_OBJECT (dlg), "hint", check); + g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (texture_hint), NULL); + + // ^Fishman - Add terrain key to func_group. + check = gtk_check_button_new_with_label ("Add terrain key"); + gtk_widget_show (check); + gtk_box_pack_start (GTK_BOX (vbox), check, FALSE, TRUE, 0); + g_object_set_data (G_OBJECT (dlg), "terrain_ent", check); + g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (texture_terrainent), NULL); + + vbox = gtk_vbox_new (FALSE, 5); + gtk_widget_show (vbox); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, TRUE, 0); + + button = gtk_button_new_with_label ("OK"); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, TRUE, 0); + gtk_widget_set_usize (button, 60, -2); + g_object_set_data (G_OBJECT (dlg), "go", button); + g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (main_go), NULL); + + label = gtk_label_new ("Settings:"); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0); + + button = gtk_button_new_with_label ("Open..."); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, TRUE, 0); + g_object_set_data (G_OBJECT (dlg), "open", button); + g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (main_open), NULL); + + button = gtk_button_new_with_label ("Save as..."); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, TRUE, 0); + g_object_set_data (G_OBJECT (dlg), "save", button); + g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (main_save), NULL); + + button = gtk_button_new_with_label ("Defaults"); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, TRUE, 0); + g_object_set_data (G_OBJECT (dlg), "defaults", button); + g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (main_defaults), NULL); + + button = gtk_button_new_with_label ("About..."); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (main_about), NULL); + + check = gtk_check_button_new_with_label ("Preview"); + gtk_widget_show (check); + gtk_box_pack_start (GTK_BOX (vbox), check, FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (main_preview), NULL); + g_object_set_data (G_OBJECT (dlg), "main_preview", check); + + // ^Fishman - Antializing for the preview window. + check = gtk_check_button_new_with_label ("Antialised lines"); + gtk_widget_show (check); + gtk_box_pack_start (GTK_BOX (vbox), check, FALSE, TRUE, 0); + g_object_set_data (G_OBJECT (dlg), "main_antialiasing", check); + g_signal_connect (G_OBJECT (check), "toggled", G_CALLBACK (main_antialiasing), NULL); + + for (i = 0; i < 5; i++) + SetDlgValues (i); + + CreateViewWindow (); + + create_tooltips(); + + FirstPassComplete = 1; + + return dlg; +} + + +#if 0 + +HWND hwndDisplay = (HWND)NULL; +HWND ghwndTab = (HWND)NULL; +int iTab=0; +Rect rcTab; +FILE *ftex; + +char GenSurfURL[40] = {"http://tarot.telefragged.com/gensurf"}; +char GenSurfBoard[40]={"http://tarot.telefragged.com/board"}; + +/* +* AboutDlgProc - processes messages for the about dialog. +*/ + +qboolean CALLBACK AboutDlgProc( HWND hwnd, unsigned msg, UINT wparam, LONG lparam ) +{ + char szText[256]; + DRAWITEMSTRUCT *dis; + HDC hdc; + HPEN hpen; + HWND hwndURL; + Rect rc; + SIZE size; + + lparam = lparam; /* turn off warning */ + + switch( msg ) { + case WM_INITDIALOG: + strcpy(szText,"About " ); + strcat(szText,gszCaption); + SetWindowText(hwnd,gszCaption); + SetDlgItemText(hwnd,DLG_ABOUT_APP,szText); + /* Application icon: */ + SendDlgItemMessage( hwnd, DLG_ABOUT_ICON, + STM_SETICON, (WPARAM)(HICON)LoadIcon(ghInst,"GENSURF"), + (LPARAM) NULL); + + hwndURL = GetDlgItem(hwnd,DLG_ABOUT_URL); + hdc = GetDC(hwndURL); + GetTextExtentPoint(hdc,GenSurfURL,strlen(GenSurfURL),&size); + ReleaseDC(hwndURL,hdc); + GetWindowRect(hwndURL,&rc); + SetWindowPos(hwndURL,(HWND)NULL,0,0,size.cx,size.cy+2, + SWP_NOMOVE | SWP_NOZORDER); + + hwndURL = GetDlgItem(hwnd,DLG_ABOUT_BOARD); + hdc = GetDC(hwndURL); + GetTextExtentPoint(hdc,GenSurfBoard,strlen(GenSurfBoard),&size); + ReleaseDC(hwndURL,hdc); + GetWindowRect(hwndURL,&rc); + SetWindowPos(hwndURL,(HWND)NULL,0,0,size.cx,size.cy+2, + SWP_NOMOVE | SWP_NOZORDER); + + return TRUE; + + case WM_COMMAND: + switch(LOWORD(wparam)) + { + case DLG_ABOUT_URL: + HTTP(GenSurfURL); + break; + case DLG_ABOUT_BOARD: + HTTP(GenSurfBoard); + break; + case IDOK: + EndDialog(hwnd,1); + return TRUE; + } + break; + + case WM_DRAWITEM: + if(wparam == DLG_ABOUT_URL) + { + dis = (LPDRAWITEMSTRUCT)lparam; + SetTextColor(dis->hDC,RGB(0,0,255)); + TextOut(dis->hDC,0,0,GenSurfURL,strlen(GenSurfURL)); + GetWindowRect(dis->hwndItem,&rc); + GetTextExtentPoint(dis->hDC,GenSurfURL,strlen(GenSurfURL),&size); + hpen = CreatePen(PS_SOLID,0,RGB(0,0,255)); + SelectObject(dis->hDC,hpen); + MoveToEx(dis->hDC,0,size.cy,NULL); + LineTo(dis->hDC,size.cx,size.cy); + SelectObject(dis->hDC,GetStockObject(BLACK_PEN)); + DeleteObject(hpen); + } + else if(wparam==DLG_ABOUT_BOARD) + { + dis = (LPDRAWITEMSTRUCT)lparam; + SetTextColor(dis->hDC,RGB(0,0,255)); + TextOut(dis->hDC,0,0,GenSurfBoard,strlen(GenSurfBoard)); + GetWindowRect(dis->hwndItem,&rc); + GetTextExtentPoint(dis->hDC,GenSurfBoard,strlen(GenSurfBoard),&size); + hpen = CreatePen(PS_SOLID,0,RGB(0,0,255)); + SelectObject(dis->hDC,hpen); + MoveToEx(dis->hDC,0,size.cy,NULL); + LineTo(dis->hDC,size.cx,size.cy); + SelectObject(dis->hDC,GetStockObject(BLACK_PEN)); + DeleteObject(hpen); + } + break; + + case WM_CLOSE: + EndDialog(hwnd,1); + return TRUE; + + default: + return FALSE; + } + return FALSE; + +} /* AboutDlgProc */ + +void About() +{ + if( DialogBox( ghInst,"About", ghwnd_main, (DLGPROC)AboutDlgProc ) < 0) + { + char Text[256]; + sprintf(Text,"In About(), GetLastError()=0x%08x",GetLastError()); + MessageBox(ghwnd_main,Text,"GenSurf",eMB_ICONWARNING); + } +} + +#endif diff --git a/contrib/gtkgensurf/gendlgs.h b/contrib/gtkgensurf/gendlgs.h new file mode 100644 index 00000000..dd954678 --- /dev/null +++ b/contrib/gtkgensurf/gendlgs.h @@ -0,0 +1,151 @@ +/* +GenSurf plugin for GtkRadiant +Copyright (C) 2001 David Hyde, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#define DLG_PLANE_XY0 100 +#define DLG_PLANE_XY1 101 +#define DLG_PLANE_YZ0 102 +#define DLG_PLANE_XZ0 103 +#define DLG_PLANE_YZ1 104 +#define DLG_PLANE_XZ1 105 +#define DLG_WAVE_01 106 +#define DLG_WAVE_02 107 +#define DLG_WAVE_03 108 +#define DLG_WAVE_04 109 +#define DLG_WAVE_05 110 +#define DLG_WAVE_06 111 +#define DLG_LAMBDA 112 +#define DLG_LAMBDA_TEXT 113 +#define DLG_AMP 114 +#define DLG_AMP_TEXT 115 +#define DLG_ROUGH 116 +#define DLG_ROUGH_TEXT 117 +#define DLG_LINEARBORDER 118 +#define DLG_FILE 119 +#define DLG_FILE_BROWSE 120 +#define DLG_PREVIEW 121 +#define DLG_GO 122 +#define DLG_ABOUT 123 +#define DLG_NH_TEXT 124 +#define DLG_NH 125 +#define DLG_NH_SPIN 126 +#define DLG_NV_TEXT 127 +#define DLG_NV 128 +#define DLG_NV_SPIN 129 +#define DLG_HMIN_TEXT 130 +#define DLG_HMIN 131 +#define DLG_HMAX_TEXT 132 +#define DLG_HMAX 133 +#define DLG_VMIN_TEXT 134 +#define DLG_VMIN 135 +#define DLG_VMAX_TEXT 136 +#define DLG_VMAX 137 +#define DLG_Z00_TEXT 138 +#define DLG_Z00 139 +#define DLG_Z01_TEXT 140 +#define DLG_Z01 141 +#define DLG_Z10_TEXT 142 +#define DLG_Z10 143 +#define DLG_Z11_TEXT 144 +#define DLG_Z11 145 +#define DLG_TEXTURE 146 +#define DLG_SKYBOX 147 +#define DLG_AUTOOVERWRITE 148 +#define DLG_DETAIL 149 +#define DLG_ARGHRAD2 150 +#define DLG_ARGHRAD2_SPIN 151 +#define DLG_APPEND 152 +#define DLG_REFRESH 153 +#define DLG_TEXOFFSETX 154 +#define DLG_TEXOFFSETY 155 +#define DLG_TEXSCALEX 156 +#define DLG_TEXSCALEY 157 +#define DLG_FIXPOINTS 158 +#define DLG_TEXTURE_BROWSE 159 +#define DLG_AZIMUTH 162 +#define DLG_AZIMUTH_SPIN 163 +#define DLG_ELEVATION 164 +#define DLG_ELEVATION_SPIN 165 +#define DLG_RANDOMSEED 166 +#define DLG_RANDOMSEED_SPIN 167 +#define DLG_BITMAP 168 +#define DLG_SAVE 169 +#define DLG_OPEN 170 +#define DLG_TAB 171 +#define DLG_TEXTURE2 172 +#define DLG_TEXTURE2_BROWSE 173 +#define DLG_LADDER 174 +#define DLG_ARGHRAD2_TEXT 175 +#define DLG_FILE_TEXT 176 +#define DLG_DECIMATE 177 +#define DLG_DECIMATE_TEXT 178 +#define DLG_HIDEBACKFACES 179 +#define DLG_DEFAULTS 180 +#define DLG_ABOUT_APP 200 +#define DLG_ABOUT_ICON 201 +#define DLG_BMP_FILE 202 +#define DLG_BMP_FILE_BROWSE 203 +#define DLG_BMP_BLACK 204 +#define DLG_BMP_WHITE 205 +#define DLG_BMP_TEXT1 206 +#define DLG_BMP_TEXT2 207 +#define DLG_BMP_TEXT3 208 +#define DLG_BMP_NOTE 209 +#define DLG_BMP_RELOAD 210 +#define DLG_ABOUT_URL 211 +#define DLG_ABOUT_BOARD 212 +#define DLG_FIX_FREE 300 +#define DLG_FIX_FREEALL 301 +#define DLG_FIX_VALUE_TEXT 302 +#define DLG_FIX_VALUE 303 +#define DLG_FIX_VALUE_SPIN 304 +#define DLG_FIX_DONE 305 +#define DLG_FIX_RANGE_TEXT 306 +#define DLG_FIX_RANGE 307 +#define DLG_FIX_NOTE 308 +#define DLG_FIX_RATE_TEXT 309 +#define DLG_FIX_RATE 310 +#define DLG_USE_PATCHES 311 +#define DLG_DECIMATE_LABEL 312 +#define DLG_HINT 350 +#define DLG_GAME_00 400 +#define DLG_GAME_01 401 +#define DLG_GAME_02 402 +#define DLG_GAME_03 403 +#define DLG_GAME_04 404 +#define DLG_GAME_05 405 +#define DLG_GAME_06 406 +#define DLG_GAME_07 407 +#define DLG_GAME_08 408 +#define DLG_GAME_09 409 +#define DLG_TEX_USEPAK 420 +#define DLG_TEX_PAK_TEXT 421 +#define DLG_TEX_PAKFILE 422 +#define DLG_TEX_PAK_BROWSE 423 +#define DLG_TEX_LIST1 424 +#define DLG_TEX_LIST2 425 +#define DLG_TEX_LIST3 426 +#define DLG_TEXTURE3 427 +#define DLG_TEXTURE3_BROWSE 428 +#define DLG_TEX_SLANT_TEXT 429 +#define DLG_TEX_SLANT 430 +#define DLG_TEX_SLANT_SPIN 431 +#define DLG_EXCEL_FUNC 500 +#define DLG_EXCEL_FUNC_TEXT 501 +#define DLG_PREVIEW_ANTIALIASING 502 // ^Fishman - Antializing for the preview window. +#define DLG_SNAP_TO_GRID 503 // Hydra : snap to grid diff --git a/contrib/gtkgensurf/genmap.cpp b/contrib/gtkgensurf/genmap.cpp new file mode 100644 index 00000000..6e9c95dc --- /dev/null +++ b/contrib/gtkgensurf/genmap.cpp @@ -0,0 +1,2040 @@ +/* +GenSurf plugin for GtkRadiant +Copyright (C) 2001 David Hyde, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include "gensurf.h" + +double xmin,xmax,ymin,ymax,zmin,zmax; +double backface; +extern double dh, dv; +FILE *fmap; +XYZ xyz[MAX_ROWS+1][MAX_ROWS+1]; +int contents; +int surface[3]; + +#include "iundo.h" + +#include "refcounted_ptr.h" + +#include +#include +#include +#include + +#include "scenelib.h" + +scene::Node* h_func_group; +scene::Node* h_worldspawn; + + +//============================================================= +// Hydra : snap-to-grid begin +double CalculateSnapValue(double value) +{ + long snapvalue; + + // simple uncomplicated snapping, rounding both UP and DOWN to the nearest + // grid unit. + if (SnapToGrid >0) + { + snapvalue = (int)value / SnapToGrid; + if ((long)value % SnapToGrid < (SnapToGrid / 2)) // Snap Downwards if less than halfway between to grid units + value = snapvalue * SnapToGrid; + else // Snap Upwards if more than halfway between to grid units + value = (snapvalue+1) * SnapToGrid; + } + return value; +} +// Hydra : snap-to-grid end + +//============================================================= +bool ValidSurface() +{ + if(WaveType == WAVE_BITMAP && !gbmp.colors) return FALSE; + if(NH < 1) return FALSE; + if(NH > MAX_ROWS) return FALSE; + if(NV < 1) return FALSE; + if(NV > MAX_ROWS) return FALSE; + if(Hll >= Hur) return FALSE; + if(Vll >= Vur) return FALSE; + return TRUE; +} + +//============================================================= +int MapPatches() +{ + int NH_remain; + int NV_remain; + int NH_patch; + int NV_patch; + int BrushNum = 0; + int i, j, k1, k2, k3; + int i0, j0, ii; + char szOops[128]; + + dh = (Hur-Hll)/NH; + dv = (Vur-Vll)/NV; + + // Generate control points in pp array to give desired values currently + // in p array. + switch(Plane) + { + case PLANE_XY0: + case PLANE_XY1: + k1 = 0; + k2 = 1; + k3 = 2; + break; + case PLANE_XZ0: + case PLANE_XZ1: + k1 = 0; + k2 = 2; + k3 = 1; + break; + case PLANE_YZ0: + case PLANE_YZ1: + k1 = 1; + k2 = 2; + k3 = 0; + break; + } + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + xyz[i][j].pp[k1] = xyz[i][j].p[k1]; + xyz[i][j].pp[k2] = xyz[i][j].p[k2]; + } + } + for(i=0; i<=NH; i+=2) + { + for(j=0; j<=NV; j+=2) + xyz[i][j].pp[k3] = xyz[i][j].p[k3]; + } + for(i=1; i 1) + { + if(( (NH_remain-1) % 14) == 0) + NH_patch = 15; + else if(( (NH_remain-1) % 12) == 0) + NH_patch = 13; + else if(( (NH_remain-1) % 10) == 0) + NH_patch = 11; + else if(( (NH_remain-1) % 8) == 0) + NH_patch = 9; + else if(( (NH_remain-1) % 6) == 0) + NH_patch = 7; + else if(( (NH_remain-1) % 4) == 0) + NH_patch = 5; + else if(( (NH_remain-1) % 2) == 0) + NH_patch = 3; + else if(NH_remain > 16) + NH_patch = 7; + else if(NH_remain > 4) + NH_patch = 5; + else + NH_patch = 3; + while( NH_patch > 3 && (NH_patch-1)*dh > 512 ) + NH_patch -= 2; + NH_remain -= (NH_patch-1); + if(NH_remain < 0) + { + sprintf(szOops,"Oops... screwed up with NH=%d",NH); + g_FuncTable.m_pfnMessageBox(NULL,szOops,"Uh oh"); + } + NV_remain = NV+1; + j0 = 0; + while(NV_remain > 1) + { + if(( (NV_remain-1) % 14) == 0) + NV_patch = 15; + else if(( (NV_remain-1) % 12) == 0) + NV_patch = 13; + else if(( (NV_remain-1) % 10) == 0) + NV_patch = 11; + else if(( (NV_remain-1) % 8) == 0) + NV_patch = 9; + else if(( (NV_remain-1) % 6) == 0) + NV_patch = 7; + else if(( (NV_remain-1) % 4) == 0) + NV_patch = 5; + else if(( (NV_remain-1) % 2) == 0) + NV_patch = 3; + else if(NV_remain > 16) + NV_patch = 7; + else if(NV_remain > 4) + NV_patch = 5; + else + NV_patch = 3; + while( NV_patch > 3 && (NV_patch-1)*dh > 512 ) + NV_patch -= 2; + NV_remain -= (NV_patch-1); + if(NV_remain < 0) + { + sprintf(szOops,"Oops... screwed up with NV=%d",NV); + g_FuncTable.m_pfnMessageBox(NULL,szOops,"Uh oh"); + } + + scene::Node* patch = MakePatch(); +#if 0 + b->pPatch->setDims(NH_patch, NV_patch); + for(i=0; ipPatch->ctrlAt(COL,i,j)[0] = (float)xyz[ii][j0+j].pp[0]; + b->pPatch->ctrlAt(COL,i,j)[1] = (float)xyz[ii][j0+j].pp[1]; + b->pPatch->ctrlAt(COL,i,j)[2] = (float)xyz[ii][j0+j].pp[2]; + b->pPatch->ctrlAt(COL,i,j)[3] = (float)i; + b->pPatch->ctrlAt(COL,i,j)[4] = (float)j; + } + } + b->pPatch->UpdateCachedData(); +#endif + BrushNum++; + j0 += NV_patch-1; + } + i0 += NH_patch-1; + } + return BrushNum; +} + +//============================================================= +void MapBrushes() +{ + char hint[128]; + char skip[128]; + char sidetext[64]; + char surftext[64]; + char surftext2[64]; + char surft[64]; + float Steep; + vec3_t PlaneNormal,SurfNormal; + vec3_t t[2]; + int i, j, k; + int surf; + bool CheckAngle; + BRUSH brush; + XYZ v[8]; + + strcpy(surftext,Texture[Game][0]); + strcpy(sidetext,(strlen(Texture[Game][1]) ? Texture[Game][1] : Texture[Game][0])); + strcpy(surftext2,(strlen(Texture[Game][2]) ? Texture[Game][2] : Texture[Game][0])); + + // if surftext2 is identical to surftext, there's no need to + // check surface angle + if(!g_strcasecmp(surftext,surftext2)) + CheckAngle = FALSE; + else + { + CheckAngle = TRUE; + Steep = (float)cos((double)SlantAngle/57.2957795); + switch(Plane) + { + case PLANE_XY0: PlaneNormal[0]= 0.;PlaneNormal[1]= 0.;PlaneNormal[2]= 1.;break; + case PLANE_XY1: PlaneNormal[0]= 0.;PlaneNormal[1]= 0.;PlaneNormal[2]=-1.;break; + case PLANE_XZ0: PlaneNormal[0]= 0.;PlaneNormal[1]= 1.;PlaneNormal[2]= 1.;break; + case PLANE_XZ1: PlaneNormal[0]= 0.;PlaneNormal[1]=-1.;PlaneNormal[2]= 1.;break; + case PLANE_YZ0: PlaneNormal[0]= 1.;PlaneNormal[1]= 0.;PlaneNormal[2]= 1.;break; + case PLANE_YZ1: PlaneNormal[0]=-1.;PlaneNormal[1]= 0.;PlaneNormal[2]= 1.;break; + } + } + + OpenFuncGroup(); + + for(i=0; i 0 && (Game != QUAKE3 || UsePatches==0) ) + { + MapOut(gNumNodes,gNumTris,gNode,gTri); + /* + ghCursorCurrent = ghCursorDefault; + SetCursor(ghCursorCurrent); + */ + return; + } + + contents = 0; + // HL doesn't have detail property + if((Game != HALFLIFE) && UseDetail) contents += CONTENTS_DETAIL; + // HL and Q3 don't have ladder property + if((Game != HALFLIFE && Game != QUAKE3) && UseLadder) contents += CONTENTS_LADDER; + // Genesis requires solid property to be set explicitly + if(Game == GENESIS3D) contents |= CONTENTS_SOLID; + // Heretic 2 uses different sounds (in surface props) for different texture types + if(Game == HERETIC2) + { + surface[0] = GetDefSurfaceProps(Texture[Game][0]); + surface[1] = GetDefSurfaceProps(Texture[Game][1]); + surface[2] = GetDefSurfaceProps(Texture[Game][2]); + } + else + { + surface[0] = 0; + surface[1] = 0; + surface[2] = 0; + } + if(Game!=QUAKE3 || UsePatches == 0) + MapBrushes(); + + /* + ghCursorCurrent = ghCursorDefault; + SetCursor(ghCursorCurrent); + */ +} + +//============================================================= +void GenerateXYZ() +{ + extern void MakeDecimatedMap(int *, int *, NODE **, TRI **); + double zl, zu; + double wh, wv; + int NHalfcycles; + double a,v,h,ha,va; + double delta, dr, rate; + double range, maxrange; + double r; + int i, j, k, N; + int i0, i1, j0, j1; + int ii, jj; + +// FILE *f; +// char CSV[64]; + + if(!ValidSurface()) return; + + srand(1); + srand(RandomSeed); + + dh = (Hur-Hll)/NH; + dv = (Vur-Vll)/NV; + + // H & V + for(i=0; i<=NH; i++) + { + for(j=0;j<=NV;j++) + { + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + xyz[i][j].p[0] = Hll + i*dh; + xyz[i][j].p[2] = Vll + j*dv; + break; + case PLANE_YZ0: + case PLANE_YZ1: + xyz[i][j].p[1] = Hll + i*dh; + xyz[i][j].p[2] = Vll + j*dv; + break; + default: + xyz[i][j].p[0] = Hll + i*dh; + xyz[i][j].p[1] = Vll + j*dv; + } + } + } + + if(WaveType == WAVE_BITMAP) + GenerateBitmapMapping(); + /* + else if(WaveType == WAVE_FORMULA) + DoFormula(); + */ + else + { + // Initialize Z values using bilinear interpolation + for(i=0; i<=NH; i++) + { + zl = Z00 + i*(Z10 - Z00)/NH; + zu = Z01 + i*(Z11 - Z01)/NH; + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + for(j=0; j<=NV; j++) + xyz[i][j].p[1] = zl + j*(zu-zl)/NV; + break; + case PLANE_YZ0: + case PLANE_YZ1: + for(j=0; j<=NV; j++) + xyz[i][j].p[0] = zl + j*(zu-zl)/NV; + break; + default: + for(j=0; j<=NV; j++) + xyz[i][j].p[2] = zl + j*(zu-zl)/NV; + } + } + } + + switch(WaveType) + { + case WAVE_COS_SIN: + if(FixBorders) + { + NHalfcycles = (int)((Hur-Hll)/(WaveLength/2.)); + NHalfcycles = max(NHalfcycles,1); + wh = 2.*(Hur-Hll)/NHalfcycles; + NHalfcycles = (int)((Vur-Vll)/(WaveLength/2.)); + wv = 2.*(Vur-Vll)/NHalfcycles; + NHalfcycles = max(NHalfcycles,1); + i0 = 1; + i1 = NH-1; + j0 = 1; + j1 = NV-1; + } + else + { + wh = WaveLength; + wv = WaveLength; + i0 = 0; + i1 = NH; + j0 = 0; + j1 = NV; + } + + for(i=i0; i<=i1; i++) + { + h = Hll + i*dh; + ha = ((h-Hll)/wh)*2.*PI - PI/2.; + for(j=j0; j<=j1; j++) + { + v = Vll + j*dv; + va = ((v-Vll)/wv)*2.*PI; + a = Amplitude * cos( ha ) * sin( va ); + switch(Plane) + { + case PLANE_XY1: + xyz[i][j].p[2] -= a; + break; + case PLANE_XZ0: + xyz[i][j].p[1] += a; + break; + case PLANE_XZ1: + xyz[i][j].p[1] -= a; + break; + case PLANE_YZ0: + xyz[i][j].p[0] += a; + break; + case PLANE_YZ1: + xyz[i][j].p[0] -= a; + break; + default: + xyz[i][j].p[2] += a; + } + } + } + break; + case WAVE_HCYLINDER: + for(i=0; i<=NH; i++) + { + h = Hll + i*dh; + ha = ((h-Hll)/WaveLength)*2.*PI - PI/2.; + for(j=0; j<=NV; j++) + { + a = Amplitude * cos( ha ); + switch(Plane) + { + case PLANE_XY1: + xyz[i][j].p[2] -= a; + break; + case PLANE_XZ0: + xyz[i][j].p[1] += a; + break; + case PLANE_XZ1: + xyz[i][j].p[1] -= a; + break; + case PLANE_YZ0: + xyz[i][j].p[0] += a; + break; + case PLANE_YZ1: + xyz[i][j].p[0] -= a; + break; + default: + xyz[i][j].p[2] += a; + } + } + } + break; + case WAVE_VCYLINDER: + for(i=0; i<=NH; i++) + { + h = Hll + i*dh; + for(j=0; j<=NV; j++) + { + v = Vll + j*dv; + va = ((v-Vll)/WaveLength)*2.*PI; + a = Amplitude * sin( va ); + switch(Plane) + { + case PLANE_XY1: + xyz[i][j].p[2] -= a; + break; + case PLANE_XZ0: + xyz[i][j].p[1] += a; + break; + case PLANE_XZ1: + xyz[i][j].p[1] -= a; + break; + case PLANE_YZ0: + xyz[i][j].p[0] += a; + break; + case PLANE_YZ1: + xyz[i][j].p[0] -= a; + break; + default: + xyz[i][j].p[2] += a; + } + } + } + break; + case WAVE_ROUGH_ONLY: + PlasmaCloud(); + break; + } + + if(WaveType != WAVE_ROUGH_ONLY) + { + // Fixed values + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + if(xyz[i][j].fixed) + { + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + xyz[i][j].p[1] = xyz[i][j].fixed_value; + break; + case PLANE_YZ0: + case PLANE_YZ1: + xyz[i][j].p[0] = xyz[i][j].fixed_value; + break; + default: + xyz[i][j].p[2] = xyz[i][j].fixed_value; + } + + if(xyz[i][j].range > 0) + { + maxrange = pow(xyz[i][j].range,2); // so we don't have to do sqrt's + i0 = i - (int)( floor(xyz[i][j].range/dh - 0.5) + 1 ); + i1 = i + i - i0; + j0 = j - (int)( floor(xyz[i][j].range/dv - 0.5) + 1 ); + j1 = j + j - j0; + if(FixBorders) + { + i0 = max(i0,1); + i1 = min(i1,NH-1); + j0 = max(j0,1); + j1 = min(j1,NV-1); + } + else + { + i0 = max(i0,0); + i1 = min(i1,NH); + j0 = max(j0,0); + j1 = min(j1,NV); + } + for(ii=i0; ii<=i1; ii++) + { + for(jj=j0; jj<=j1; jj++) + { + if(ii==i && jj==j) continue; + range = pow( dh*(i-ii), 2) + pow( dv*(j-jj), 2); + if(range > maxrange) continue; + dr = sqrt(range/maxrange); + rate = max(-30.,min(xyz[i][j].rate,30.)); + if(rate < -1.) + { + delta = pow((1.-dr),-rate+1.); + } + else if(rate < 0.) + { + delta = (1+rate)*0.5*(cos(dr*PI)+1.0) - + rate*pow((1.-dr),2); + } + else if(rate == 0.) + { + delta = 0.5*(cos(dr*PI)+1.0); + } + else if(rate <= 1.) + { + delta = (1.-rate)*0.5*(cos(dr*PI)+1.0) + + rate*(1.-pow(dr,2)); + } + else + { + delta = 1.-pow(dr,rate+1); + } + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + xyz[ii][jj].p[1] += (xyz[i][j].p[1] - xyz[ii][jj].p[1])*delta; + break; + case PLANE_YZ0: + case PLANE_YZ1: + xyz[ii][jj].p[0] += (xyz[i][j].p[0] - xyz[ii][jj].p[0])*delta; + break; + default: + xyz[ii][jj].p[2] += (xyz[i][j].p[2] - xyz[ii][jj].p[2])*delta; + } + } + } + } + } + } + } + } + + if((Roughness > 0.) && (WaveType != WAVE_ROUGH_ONLY) ) + { + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + if(CanEdit(i,j) && !xyz[i][j].fixed) + { + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + xyz[i][j].p[1] += -Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX); + break; + case PLANE_YZ0: + case PLANE_YZ1: + xyz[i][j].p[0] += -Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX); + break; + default: + xyz[i][j].p[2] += -Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX); + } + } + else + r = rand(); // We still get a random number, so that fixing points + // doesn't change the sequence. + + } + } + } + + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + for(k=0; k<3; k++) + { + xyz[i][j].p[k] = Nearest(xyz[i][j].p[k],2.0); + } + } + } + + // Find minima and maxima + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + xmin = Hll; + xmax = Hur; + zmin = Vll; + zmax = Vur; + ymin = xyz[0][0].p[1]; + ymax = ymin; + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + ymin = min(ymin,xyz[i][j].p[1]); + ymax = max(ymax,xyz[i][j].p[1]); + } + } + break; + case PLANE_YZ0: + case PLANE_YZ1: + ymin = Hll; + ymax = Hur; + zmin = Vll; + zmax = Vur; + xmin = xyz[0][0].p[0]; + xmax = ymin; + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + xmin = min(xmin,xyz[i][j].p[0]); + xmax = max(xmax,xyz[i][j].p[0]); + } + } + break; + break; + default: + xmin = Hll; + xmax = Hur; + ymin = Vll; + ymax = Vur; + zmin = xyz[0][0].p[2]; + zmax = zmin; + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + zmin = min(zmin,xyz[i][j].p[2]); + zmax = max(zmax,xyz[i][j].p[2]); + } + } + } + + xmin = Nearest(xmin,2.); + xmax = Nearest(xmax,2.); + ymin = Nearest(ymin,2.); + ymax = Nearest(ymax,2.); + zmin = Nearest(zmin,2.); + zmax = Nearest(zmax,2.); + + switch(Plane) + { + case PLANE_XY1: + backface = AtLeast(zmax+32.,32.); + break; + case PLANE_XZ0: + backface = NoMoreThan(ymin-32.,32.); + break; + case PLANE_XZ1: + backface = AtLeast(ymax+32.,32.); + break; + case PLANE_YZ0: + backface = NoMoreThan(xmin-32.,32.); + break; + case PLANE_YZ1: + backface = AtLeast(xmax+32.,32.); + break; + default: + backface = NoMoreThan(zmin-32.,32.); + } + + if(gNode) + { + free(gNode); + free(gTri); + gNode = (NODE *)NULL; + gTri = (TRI *)NULL; + } + if(Decimate > 0 && (Game != QUAKE3 || UsePatches==0) ) + { + MakeDecimatedMap(&gNumNodes,&gNumTris,&gNode,&gTri); + } + else + { + gNumNodes = (NH+1)*(NV+1); + gNumTris = NH*NV*2; + gNode = (NODE *) malloc(gNumNodes * sizeof(NODE)); + gTri = (TRI *) malloc(gNumTris * sizeof(TRI)); + + for(i=0,N=0; i<=NH; i++) + { + for(j=0; j<=NV; j++, N++) + { + gNode[N].used = 1; + gNode[N].p[0] = (float)xyz[i][j].p[0]; + gNode[N].p[1] = (float)xyz[i][j].p[1]; + gNode[N].p[2] = (float)xyz[i][j].p[2]; + } + } + + for(i=0; i 0) + { + for(i=0; i x) xx -= dx; + return xx; +} +//============================================================= +double AtLeast(double x, double dx) +{ + double xx; + + xx = (double)(floor(x/dx - 0.5)+1.)*dx; + if(xx < x) xx += dx; + return xx; +} +//============================================================= +double LessThan(double x,double dx) +{ + double xx; + + xx = (double)(floor(x/dx - 0.5)+1.)*dx; + if(xx >= x) xx -= dx; + return xx; +} +//============================================================= +double MoreThan(double x,double dx) +{ + double xx; + + xx = (double)(floor(x/dx - 0.5)+1.)*dx; + while(xx <= x) + xx += dx; + return xx; +} +//============================================================= +void SubdividePlasma(int i0,int j0,int i1,int j1) +{ + int i, j; + double z1, z2; + double r; // NOTE: This is used to keep the random number sequence the same + // when we fix a point. If we did NOT do this, then simply + // fixing a point at its current value would change the entire + // surface. + + i = (i0+i1)/2; + j = (j0+j1)/2; + if(i1 > i0+1) + { + if(!xyz[i][j0].done) + { + xyz[i][j0].pp[2] = xyz[i0][j0].pp[2] + + (xyz[i1][j0].pp[2] - xyz[i0][j0].pp[2])*(double)(i-i0)/(double)(i1-i0) + + ((double)(i-i0))*(-Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX)); + xyz[i][j0].done = 1; + } + else + r = rand(); + if((j1 > j0) && (!xyz[i][j1].done) ) + { + xyz[i][j1].pp[2] = xyz[i0][j1].pp[2] + + (xyz[i1][j1].pp[2] - xyz[i0][j1].pp[2])*(double)(i-i0)/(double)(i1-i0) + + ((double)(i-i0))*(-Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX)); + xyz[i][j1].done = 1; + } + else + r = rand(); + } + if(j1 > j0 + 1) + { + if(!xyz[i0][j].done) + { + xyz[i0][j].pp[2] = xyz[i0][j0].pp[2] + + (xyz[i0][j1].pp[2] - xyz[i0][j0].pp[2])*(double)(j-j0)/(double)(j1-j0) + + ((double)(j-j0))*(-Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX)); + xyz[i0][j].done = 1; + } + else + r = rand(); + if((i1 > i0) && (!xyz[i1][j].done)) + { + xyz[i1][j].pp[2] = xyz[i1][j0].pp[2] + + (xyz[i1][j1].pp[2] - xyz[i1][j0].pp[2])*(double)(j-j0)/(double)(j1-j0) + + ((double)(j-j0))*(-Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX)); + xyz[i1][j].done = 1; + } + else + r = rand(); + } + if((i1 > i0+1) && (j1 > j0+1)) + { + if(!xyz[i][j].done) + { + z1 = xyz[i0][j].pp[2] + + (xyz[i1][j].pp[2] - xyz[i0][j].pp[2])*(double)(i-i0)/(double)(i1-i0); + z2 = xyz[i][j0].pp[2] + + (xyz[i][j1].pp[2] - xyz[i][j0].pp[2])*(double)(j-j0)/(double)(j1-j0); + xyz[i][j].pp[2] = (z1+z2)/2. + + ((double)(i-i0))*(-Roughness/2. + Roughness*((double)rand()/(double)RAND_MAX)); + xyz[i][j].done = 1; + } + else + r = rand(); + } + if(i > i0+1 || j > j0+1) + SubdividePlasma(i0,j0,i,j); + if(i1 > i+1 || j > j0+1) + SubdividePlasma(i,j0,i1,j); + if(i > i0+1 || j1 > j0+1) + SubdividePlasma(i0,j,i,j1); + if(i1 > i+1 || j1 > j0+1) + SubdividePlasma(i,j,i1,j1); +} +//================================================================================== +void PlasmaCloud() +{ + int i, j; + /* use pp[2] values until done to avoid messing with a bunch of + switch statements */ + + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + if(FixedPoint(i,j)) + xyz[i][j].done = 1; + else + xyz[i][j].done = 0; + } + } + + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + if(xyz[i][j].fixed) + xyz[i][j].pp[2] = xyz[i][j].fixed_value; + else + xyz[i][j].pp[2] = xyz[i][j].p[1]; + } + } + break; + case PLANE_YZ0: + case PLANE_YZ1: + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + if(xyz[i][j].fixed) + xyz[i][j].pp[2] = xyz[i][j].fixed_value; + else + xyz[i][j].pp[2] = xyz[i][j].p[0]; + } + } + break; + default: + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + if(xyz[i][j].fixed) + xyz[i][j].pp[2] = xyz[i][j].fixed_value; + else + xyz[i][j].pp[2] = xyz[i][j].p[2]; + } + } + break; + } + SubdividePlasma(0,0,NH,NV); + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + xyz[i][j].p[1] = xyz[i][j].pp[2]; + } + } + break; + case PLANE_YZ0: + case PLANE_YZ1: + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + xyz[i][j].p[0] = xyz[i][j].pp[2]; + } + } + break; + default: + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + xyz[i][j].p[2] = xyz[i][j].pp[2]; + } + } + break; + } +} +//=========================================================================== +bool FixedPoint(int i, int j) +{ + if(xyz[i][j].fixed) return TRUE; + return !CanEdit(i,j); +} +//=========================================================================== +bool CanEdit(int i, int j) +{ + if(FixBorders && ( (WaveType==WAVE_COS_SIN) || (WaveType==WAVE_ROUGH_ONLY) ) ) + { + if(i== 0) return FALSE; + if(i==NH) return FALSE; + if(j== 0) return FALSE; + if(j==NV) return FALSE; + } + if(i== 0 && j== 0) return FALSE; + if(i==NH && j== 0) return FALSE; + if(i== 0 && j==NV) return FALSE; + if(i==NH && j==NV) return FALSE; + return TRUE; +} +/*============================================================================ + TriangleFromPoint + Determines which triangle in the gTri array bounds the input point. Doesn't + do anything special with border points. +*/ +int TriangleFromPoint(double x, double y) +{ + int j, tri; + + if(!gTri) return -1; + + for(j=0, tri=-1; jp[0],2.); + v[0][1] = (vec)Nearest(xyz->p[1],2.); + v[0][2] = (vec)Nearest(xyz->p[2],2.); +} + +//============================================================= +scene::Node* MakePatch(void) +{ + scene::Node* patch = Patch_AllocNode(); +#if 0 + patch->m_patch->SetShader(Texture[Game][0]); +#endif + Node_getTraversable(h_worldspawn)->insert(patch); + return patch; +} + +//============================================================= +void MakeBrush(BRUSH *brush) +{ + NodePtr node(Brush_AllocNode()); + +#if 0 + for(int i=0; iNumFaces; i++) + { + _QERFaceData QERFaceData; + if(!strncmp(brush->face[i].texture, "textures/", 9)) + strcpy(QERFaceData.m_TextureName,brush->face[i].texture); + else + { + strcpy(QERFaceData.m_TextureName,"textures/"); + strcpy(QERFaceData.m_TextureName+9,brush->face[i].texture); + } + QERFaceData.m_nContents = brush->face[i].Contents; + QERFaceData.m_nFlags = brush->face[i].Surface; + QERFaceData.m_nValue = brush->face[i].Value; + QERFaceData.m_fShift[0] = brush->face[i].Shift[0]; + QERFaceData.m_fShift[1] = brush->face[i].Shift[1]; + QERFaceData.m_fRotate = brush->face[i].Rotate; + QERFaceData.m_fScale[0] = brush->face[i].Scale[0]; + QERFaceData.m_fScale[1] = brush->face[i].Scale[1]; + QERFaceData.m_v1[0] = brush->face[i].v[0][0]; + QERFaceData.m_v1[1] = brush->face[i].v[0][1]; + QERFaceData.m_v1[2] = brush->face[i].v[0][2]; + QERFaceData.m_v2[0] = brush->face[i].v[1][0]; + QERFaceData.m_v2[1] = brush->face[i].v[1][1]; + QERFaceData.m_v2[2] = brush->face[i].v[1][2]; + QERFaceData.m_v3[0] = brush->face[i].v[2][0]; + QERFaceData.m_v3[1] = brush->face[i].v[2][1]; + QERFaceData.m_v3[2] = brush->face[i].v[2][2]; + QERFaceData.m_bBPrimit = false; + (g_FuncTable.m_pfnAddFaceData)(vp,&QERFaceData); + } +#endif + + Node_getTraversable(h_func_group)->insert(node); +} +//============================================================= +void OpenFuncGroup() +{ + h_func_group = GlobalEntityCreator().createEntity("func_group"); + h_func_group->IncRef(); + if(AddTerrainKey) + h_func_group->m_entity->setkeyvalue("terrain", "1"); +} +//============================================================= +void CloseFuncGroup() +{ + h_func_group->DecRef(); + if (g_FuncTable.m_pfnSysUpdateWindows != NULL) + g_FuncTable.m_pfnSysUpdateWindows (W_ALL); +} diff --git a/contrib/gtkgensurf/gensurf.cpp b/contrib/gtkgensurf/gensurf.cpp new file mode 100644 index 00000000..32b54b55 --- /dev/null +++ b/contrib/gtkgensurf/gensurf.cpp @@ -0,0 +1,467 @@ +/* +GenSurf plugin for GtkRadiant +Copyright (C) 2001 David Hyde, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +/* +#include +#include +#include +*/ +#include "gensurf.h" + +char gszAppDir[NAME_MAX]; +char gszCaption[64]; +char gszIni[NAME_MAX]; +char gszHelpFile[NAME_MAX]; +char gszMapFile[NAME_MAX]; +char gszVersion[64]; +double Amplitude; +double Roughness; +double TexOffset[2]; +double TexScale[2]; +double WaveLength; +double Hll, Hur, Vll, Vur; +double Z00, Z01, Z10, Z11; +ELEMENT Vertex[(MAX_ROWS+1)*(MAX_ROWS+1)]; +int AddHints; +int ArghRad2; +int AutoOverwrite; +int Decimate=0; +int SnapToGrid=0; // 0, or the grid size to snap to. // Hydra : snap to grid +int FileAppend=0; +int FixBorders; +int HideBackFaces=0; +int NH, NV; +int NumVerticesSelected; +int Plane; +int Preview; +int RandomSeed=1; +int Skybox; +int UseDetail; +int UseLadder; +int VertexMode=0; +int WaveType; +int gNumNodes=0; +int gNumTris=0; +int vid_x, vid_y; +int view_x, view_y; +int view_cx, view_cy; +int UsePatches; +int SlantAngle; +int GimpHints; +int Antialiasing; // ^Fishman - Antializing for the preview window. +int AddTerrainKey; // ^Fishman - Add terrain key to func_group. +int SP; // ^Fishman - Snap to grid. + +GtkWidget *g_pWnd; // ghwnd; +GtkWidget *g_pRadiantWnd; // ghwnd_main; +/*HWND ghwndAngles; +*/GtkWidget *g_pWndPreview; +GtkWidget *g_pPreviewWidget; +MYBITMAP gbmp; +NODE *gNode=(NODE *)NULL; +TRI *gTri=(TRI *)NULL; + +int Game; +bounding_box PlayerBox[NUMGAMES] = { {{-16., 16.}, {-16., 16.}, {-24., 32.}}, // Quake2 + {{-16., 16.}, {-16., 16.}, {-36., 36.}}, // Half-Life + {{-16., 16.}, {-16., 16.}, {-32., 32.}}, // SiN + {{-16., 16.}, {-16., 16.}, {-24., 32.}}, // Heretic2 (guess) + {{-16., 16.}, {-16., 16.}, {-24., 32.}}, // KingPin (guess) + {{-30., 30.}, {-30., 30.}, {-10.,160.}}, // Genesis3D (no idea) + {{-16., 16.}, {-16., 16.}, {-24., 32.}}}; // Quake3 (not sure) +//char gszOutputDir[NUMGAMES][NAME_MAX]; +//char gszTextureDir[NUMGAMES][NAME_MAX]; +char Texture[NUMGAMES][3][64]; +//char pakfile[NUMGAMES][NAME_MAX]; +//char lastpakfile[NUMGAMES][NAME_MAX]; +//int UsePak[NUMGAMES]; +//char GameDir[NUMGAMES][NAME_MAX]; + +char GameName[NUMGAMES][16] = {"Quake2", "Half-Life", "SiN", "Heretic2", "Kingpin", "Genesis3D", "Quake3" }; + + +bool GenSurfInit () +{ + strcpy (gszVersion, "1.05"); + strcpy (gszCaption, "GtkGenSurf"); + if (strlen (gszVersion)) + { + strcat (gszCaption, " v"); + strcat (gszCaption, gszVersion); + } + + strcpy (gszIni, g_FuncTable.m_pfnProfileGetDirectory ()); + strcat (gszIni, "gensurf.ini"); + +/*if (g_FuncTable.m_pfnReadProjectKey != NULL) + { + char *basepath; + + basepath = g_FuncTable.m_pfnReadProjectKey("basepath"); + if (basepath) + { + g_strdown (basepath); + if (strstr(basepath,"baseq3")) + Game = QUAKE3; + else if (strstr (basepath,"baseq2")) + Game = QUAKE2; + else // Gotta have a game, might as well be Quake3 + Game = QUAKE3; + } + else + Game = QUAKE3; + } + else */ + Game = QUAKE3; + + ReadIniFile (gszIni); + + if (g_pWnd == NULL) + g_pWnd = create_main_dialog (); + + return true; +} + +// Reads default values + +#define OPTS_SECTION "Options" + +void ReadIniFile (const char *file) +{ + char *Text; + float x1,x2,x3,x4; + int i; + + Text = g_FuncTable.m_pfnProfileLoadString (file, OPTS_SECTION, "Amplitude", ""); + if (strlen (Text)) + Amplitude = atof (Text); + else + Amplitude = 128; + + Text = g_FuncTable.m_pfnProfileLoadString (file, OPTS_SECTION, "Roughness", ""); + if (strlen (Text)) + Roughness = atof (Text); + else + Roughness = 16; + + Text = g_FuncTable.m_pfnProfileLoadString (file, OPTS_SECTION, "WaveLength", ""); + if (strlen (Text)) + WaveLength = atof (Text); + else + WaveLength = 1024; + + Text = g_FuncTable.m_pfnProfileLoadString (file, OPTS_SECTION, "Extents", ""); + if (strlen (Text)) + { + sscanf(Text,"%f,%f,%f,%f",&x1,&x2,&x3,&x4); + Hll = x1; + Vll = x2; + Hur = x3; + Vur = x4; + } + else + { + Hll = -512; + Vll = -512; + Hur = 512; + Vur = 512; + } + + Text = g_FuncTable.m_pfnProfileLoadString (file, OPTS_SECTION, "CornerValues", ""); + if (strlen (Text)) + { + sscanf(Text,"%f,%f,%f,%f",&x1,&x2,&x3,&x4); + Z00 = x1; + Z01 = x2; + Z10 = x3; + Z11 = x4; + } + else + { + Z00 = 0.; + Z01 = 0.; + Z10 = 0.; + Z11 = 0.; + } + + Text = g_FuncTable.m_pfnProfileLoadString (file, OPTS_SECTION, "TextureOffset", ""); + if (strlen (Text)) + { + sscanf(Text,"%f,%f",&x1,&x2); + TexOffset[0] = x1; + TexOffset[1] = x2; + } + else + { + TexOffset[0] = 0.; + TexOffset[1] = 0.; + } + + Text = g_FuncTable.m_pfnProfileLoadString (file, OPTS_SECTION,"TextureScale",""); + if (strlen (Text)) + { + sscanf(Text,"%f,%f",&x1,&x2); + TexScale[0] = x1; + TexScale[1] = x2; + if(TexScale[0] == 0.) TexScale[0] = 1.0; + if(TexScale[1] == 0.) TexScale[1] = 1.0; + } + else + { + TexScale[0] = 1.; + TexScale[1] = 1.; + } + + NH = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"NH",8); + NH = max(1,min(NH,MAX_ROWS)); + NV = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"NV",8); + NV = max(1,min(NV,MAX_ROWS)); + +// Decimate = GetPrivateProfileInt(OPTS_SECTION,"Decimate",0,file); +// Decimate = max(0,min(Decimate,100)); + + AddHints = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"AddHints",0); + ArghRad2 = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"ArghRad2",0); + AutoOverwrite = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"AutoOverwrite",0); + FixBorders = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"FixBorders",1); + HideBackFaces = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"HideBackFaces",0); + Plane = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"Plane",0); + Preview = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"Preview", 0); + Antialiasing = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"Antialiasing",0); // ^Fishman - Antializing for the preview window. + RandomSeed = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"RandomSeed",1); + Skybox = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"Skybox",0); + UseDetail = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"UseDetail",0); + AddTerrainKey = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"AddTerrainKey",0); // ^Fishman - Add terrain key to func_group. + UseLadder = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"UseLadder",0); + WaveType = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"WaveType",0); + vid_x = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"vid_x", 0); + vid_y = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"vid_y", 0); + view_x = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"view_x",0); + view_y = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"view_y",0); + view_cx = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"view_cx",0); + view_cy = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"view_cy",0); + + UsePatches = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"UsePatches",0); + + SlantAngle = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"SlantAngle",60); + GimpHints = g_FuncTable.m_pfnProfileLoadInt (file, OPTS_SECTION,"GimpHints",0); + + for(i=0; i +#include "qertypes.h" +#include + +#include "mathlib.h" +#include "iscenegraph.h" +#define USE_QERTABLE_DEFINE +#include "qerplugin.h" +extern _QERFuncTable_1 g_FuncTable; + +#include "irender.h" +#include "iselection.h" + +#define USE_ENTITYTABLE_DEFINE +#include "ientity.h" +extern _QEREntityTable __ENTITYTABLENAME; + +#define USE_PATCHTABLE_DEFINE +#include "ipatch.h" +extern _QERPatchTable __PATCHTABLENAME; + +#define USE_BRUSHTABLE_DEFINE +#include "ibrush.h" +extern _QERBrushTable __BRUSHTABLENAME; + +#include "igl.h" +#include "ientity.h" + +#include + +#include "iui_gtk.h" + +#include "gendlgs.h" + + +#define PLUGIN +#define Q3RADIANT + +//#if defined(__linux__) || defined(__APPLE__) +#if 1 +#include +#else +template +inline T min (T x, T y) { return (x < y) ? x : y; } +template +inline T max (T x, T y) { return (x > y) ? x : y; } +#endif + +typedef struct { long x, y; } Point; +typedef struct { long left, top, right, bottom; } Rect; + +#define NAME_MAX 255 + +typedef void* LPVOID; +typedef char* LPSTR; + +//#endif +inline bool PtInRect (Rect *rc, Point pt) +{ + if (pt.x < rc->left) return false; + if (pt.x > rc->right) return false; + if (pt.y < rc->bottom) return false; + if (pt.y > rc->top) return false; + return true; +} + +#define NUMGAMES 7 + +#define CONTENTS_SOLID 0x00000001 +#define CONTENTS_DETAIL 0x08000000 // brushes to be added after vis leafs +#define CONTENTS_LADDER 0x20000000 +#define SURF_HINT 0x100 // make a primary bsp splitter +#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes +#define HINT_OFFSET 96 + +#define PI 3.14159265358979224 +#define RadiansToDegrees(a) (floor(a*57.2957795 - 0.5)+1.) +#define DegreesToRadians(a) (a/57.2957795) + +#define BOGUS_RANGE 65536 +/* +#define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) +#define VectorAdd(a,b,c) {c[0]=a[0]+b[0];c[1]=a[1]+b[1];c[2]=a[2]+b[2];} +#define VectorClear(x) {x[0] = x[1] = x[2] = 0;} +#define VectorCopy(a,b) {b[0]=a[0];b[1]=a[1];b[2]=a[2];} +#define VectorScale(a,b,c) {c[0]=b*a[0];c[1]=b*a[1];c[2]=b*a[2];} +#define VectorSubtract(a,b,c) {c[0]=a[0]-b[0];c[1]=a[1]-b[1];c[2]=a[2]-b[2];} +*/ +#define XYZVectorSubtract(a,b,c) {c[0]=(float)a[0]-(float)b[0];c[1]=(float)a[1]-(float)b[1];c[2]=(float)a[2]-(float)b[2];} +#define side(u1,v1,u2,v2,u3,v3) (v3-v1)*(u2-u1) - (u3-u1)*(v2-v1) + +#define QUAKE2 0 +#define HALFLIFE 1 +#define SIN 2 +#define HERETIC2 3 +#define KINGPIN 4 +#define GENESIS3D 5 +#define QUAKE3 6 + +#define MAX_FACES_PER_BRUSH 6 +#define SLIVER_ANGLE DegreesToRadians(20) +#define MAX_NODES (MAX_ROWS+1)*(MAX_ROWS+1) +#define MAX_TRIS (MAX_ROWS)*(MAX_ROWS) + +typedef float vec; +typedef vec vec3[3]; +typedef vec vec2[2]; + +typedef struct +{ + vec3 v[3]; + char texture[64]; + float Shift[2]; + float Rotate; + float Scale[2]; + int Contents; + int Surface; + int Value; +} FACE; + +typedef struct +{ + vec3 normal; + vec dist; +} PLANE; + +typedef struct +{ + int numpoints; + vec3 p[4]; // variable sized +} MY_WINDING; + +typedef struct +{ + int Number; + int NumFaces; + FACE face[MAX_FACES_PER_BRUSH]; +} BRUSH; + +typedef struct tagXYZ +{ + int fixed; + int done; + double p[3]; + double pp[3]; // these used only for general 3D projection (not isometric) + double fixed_value; + double range; + double rate; +} XYZ; + +// Q2 PAK file structures +typedef struct +{ + char id[4]; // Should be 'PACK' + int dstart; // Offest in the file to the directory + int dsize; // Size in bytes of the directory, same as num_items*64 +} pak_header_t; + +typedef struct +{ + char name[56]; // The name of the item, normal C string + int start; // Offset in .pak file to start of item + int size; // Size of item in bytes +} pak_item_t; + +// SiN .SIN structures +#define SINPAKHEADER (('K'<<24)+('A'<<16)+('P'<<8)+'S') +#define MAX_PAK_FILENAME_LENGTH 120 + +typedef struct +{ + char name[MAX_PAK_FILENAME_LENGTH]; + int filepos, filelen; +} dpackfile_t; + +typedef struct +{ + int ident; // == IDPAKHEADER + int dirofs; + int dirlen; +} dpackheader_t; + +// Half-Life WAD file structures +typedef struct +{ + char identification[4]; // should be WAD2 or 2DAW + int numlumps; + int infotableofs; +} wadinfo_t; + +typedef struct +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[16]; // must be null terminated +} lumpinfo_t; + +typedef struct +{ + int signature; + short version; + short bitflag; + short compression_method; + short modfiletime; + short modfiledate; + int crc; + int compressed_size; + int uncompressed_size; + short filename_size; + short extra_size; +} zipheader_t; + +typedef struct +{ + double x[2]; + double y[2]; + double z[2]; +} bounding_box; + +typedef struct +{ + float p[3]; + int used; + int tri; + float error; + int fixed; +} NODE; + +typedef struct +{ + int v[3]; + int n[3]; // indices of neighboring triangles + PLANE plane; + int flag; + float min[3]; + float max[3]; +} TRI; + +//--------------- bitmap.c ----------------------------- +bool OpenBitmap (); +double CalculateSnapValue(double value); +void GenerateBitmapMapping (); +//--------------- face.c ------------------------------- +void PlaneFromPoints (float *, float *, float *, PLANE *); +//void CrossProduct (vec3 v1, vec3 v2, vec3 cross); +//vec VectorNormalize (vec3 in, vec3 out); +//--------------- gendlg.c ----------------------------- +GtkWidget* create_main_dialog (); +void About (GtkWidget *parent); +//--------------- genmap.c ----------------------------- +double AtLeast(double,double); +bool CanEdit(int, int); +void CloseFuncGroup(); +bool FixedPoint(int,int); +void GenerateMap(); +void GenerateXYZ(); +double LessThan(double,double); +void MakeBrush(BRUSH *); +double MoreThan(double,double); +double Nearest(double,double); +double NoMoreThan(double,double); +void OpenFuncGroup(); +void PlasmaCloud(); +int PlayerStartZ(double,double); +void SubdividePlasma(int,int,int,int); +bool ValidSurface(); +void XYZtoV(XYZ *, vec3 *); +scene::Node* MakePatch(void); + +//---------------- gensurf.c --------------------------- +bool GenSurfInit (); +void ReadIniFile (const char *); +void WriteIniFile (const char *); +void OpenSetup (GtkWidget*,int); +void SaveSetup (GtkWidget*); +//---------------- heretic.c --------------------------- +int GetDefSurfaceProps(char *); +//---------------- view.c ------------------------------ +void CreateViewWindow (); +void DrawGrid(Rect); +void DrawPreview(Rect); +void evaluate(); +void GetScaleFactor(Rect); +void project(XYZ *); +void Scale(Rect,XYZ,Point *); +void ShowPreview (); +void UpdatePreview (bool); + +//---------------- plugin.c ----------------------------- +void UseFaceBounds(); + +extern _QERFuncTable_1 g_FuncTable; +extern _QERQglTable g_GLTable; +extern _QERUIGtkTable g_UIGtkTable; +extern _QEREntityTable g_EntityTable; +//#define MAX_ROWS 64 +#define MAX_ROWS 128 + +#define PLANE_XY0 0 +#define PLANE_XY1 1 +#define PLANE_YZ0 2 +#define PLANE_XZ0 3 +#define PLANE_YZ1 4 +#define PLANE_XZ1 5 + +#define WAVE_COS_SIN 0 +#define WAVE_HCYLINDER 1 +#define WAVE_VCYLINDER 2 +#define WAVE_BITMAP 3 +#define WAVE_ROUGH_ONLY 4 +#define WAVE_FORMULA 5 +#define WAVE_FIRST WAVE_COS_SIN +#define WAVE_LAST WAVE_FORMULA +#define DLG_WAVE_LAST DLG_WAVE_01+WAVE_LAST-WAVE_FIRST + +#define MSG_VERTEX_SELECTED WM_USER+1 + +typedef struct tagMYBITMAP +{ + char name[NAME_MAX]; + char defpath[NAME_MAX]; + double black_value; + double white_value; + int width, height; + unsigned char* colors; +} MYBITMAP; + +typedef struct tagELEMENT { + int i; + int j; +} ELEMENT; + +extern char gszAppDir[NAME_MAX]; +extern char gszCaption[64]; +extern char gszHelpFile[NAME_MAX]; +extern char gszIni[NAME_MAX]; +extern char gszMapFile[NAME_MAX]; +extern char gszVersion[64]; +extern double Amplitude; +extern double Roughness; +extern double TexOffset[2]; +extern double TexScale[2]; +extern double WaveLength; +extern double Hll, Hur, Vll, Vur; +extern double Z00, Z01, Z10, Z11; +extern double yaw, pitch, roll; +extern ELEMENT Vertex[(MAX_ROWS+1)*(MAX_ROWS+1)]; +extern int AddHints; +extern int ArghRad2; +extern int AutoOverwrite; +extern int Decimate; +extern int FileAppend; +extern int FixBorders; +extern int HideBackFaces; +extern int NH, NV; +extern int NumVerticesSelected; +extern int Plane; +extern int Preview; +extern int RandomSeed; +extern int Skybox; +extern int UseDetail; +extern int UseLadder; +extern int VertexMode; +extern int vid_x, vid_y; +extern int WaveType; +extern int gNumNodes; +extern int gNumTris; +extern int view_x, view_y; +extern int view_cx, view_cy; +extern int UsePatches; +extern int SlantAngle; +extern int GimpHints; +extern int Antialiasing; // ^Fishman - Antializing for the preview window. +extern int AddTerrainKey; // ^Fishman - Add terrain key to func_group. +extern int SnapToGrid; // Hydra : snap to grid +extern int SP; // ^Fishman - Snap to grid. + + +/*extern HCURSOR ghCursorCurrent; +extern HCURSOR ghCursorDefault; +extern HCURSOR ghCursorVertex; +extern HINSTANCE ghInst;*/ +extern GtkWidget *g_pRadiantWnd; +extern GtkWidget *g_pWnd; +/*extern HWND ghwndAngles; +extern HWND ghwndFix; +*/extern GtkWidget *g_pWndPreview; +extern GtkWidget *g_pPreviewWidget; +extern MYBITMAP gbmp; +extern NODE *gNode; +extern TRI *gTri; +extern XYZ xyz[MAX_ROWS+1][MAX_ROWS+1]; + +extern int Game; +extern bounding_box PlayerBox[NUMGAMES]; +//extern char gszOutputDir[NUMGAMES][NAME_MAX]; +extern char Texture[NUMGAMES][3][64]; +//extern char gszTextureDir[NUMGAMES][NAME_MAX]; +extern char GameName[NUMGAMES][16]; +//extern char pakfile[NUMGAMES][NAME_MAX]; +//extern char lastpakfile[NUMGAMES][NAME_MAX]; +//extern int UsePak[NUMGAMES]; +//extern char GameDir[NUMGAMES][NAME_MAX]; +//extern char ExcelFunc[1024]; + +#endif // _GENSURF_H_ diff --git a/contrib/gtkgensurf/gtkgensurf.vcproj b/contrib/gtkgensurf/gtkgensurf.vcproj new file mode 100644 index 00000000..bd20e46e --- /dev/null +++ b/contrib/gtkgensurf/gtkgensurf.vcproj @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/gtkgensurf/heretic.cpp b/contrib/gtkgensurf/heretic.cpp new file mode 100644 index 00000000..4fb19a23 --- /dev/null +++ b/contrib/gtkgensurf/heretic.cpp @@ -0,0 +1,150 @@ +/* +GenSurf plugin for GtkRadiant +Copyright (C) 2001 David Hyde, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include "gensurf.h" + +// Heretic 2 - specific routines + +typedef struct palette_s +{ + guint8 r,g,b; +} palette_t; + +#define MIP_VERSION 2 +#define PAL_SIZE 256 +#define MIPLEVELS 16 + +typedef struct miptex_s +{ + int version; + char name[32]; + unsigned width[MIPLEVELS], height[MIPLEVELS]; + unsigned offsets[MIPLEVELS]; // four mip maps stored + char animname[32]; // next frame in animation chain + palette_t palette[PAL_SIZE]; + int flags; + int contents; + int value; +} miptex_t; + +//============================================================= +int GetDefSurfaceProps(char *Tex) +{ + return 0; // leo: only used for Heretic 2, fix later + /* + char path[NAME_MAX]; + char *p; + int flags; + miptex_t *mt; + FILE *f; + int length; + int pos; + + if(Game != HERETIC2) return 0; + if(!strlen(Tex)) return 0; + + mt = NULL; + flags = 0; + if(UsePak[Game]) + { + FILE *fpak; + pak_header_t pakheader; + pak_item_t pakitem; + int i; + int num; + int numitems; + + if (NULL != (fpak = fopen(pakfile[Game], "rb"))) + { + sprintf(path,"textures/%s.m8",Tex); + g_strdown(path); + num=fread(&pakheader,1,sizeof(pak_header_t),fpak); + if((size_t)num < sizeof(pak_header_t)) + { + fclose(fpak); + return 0; + } + if(strncmp(pakheader.id,"PACK",4)) + { + fclose(fpak); + return 0; + } + numitems = pakheader.dsize/sizeof(pak_item_t); + fseek(fpak,pakheader.dstart,SEEK_SET); + for(i=0; iflags; + free(mt); + } + } + } + fclose(fpak); + } + } + else + { + // Assume .map will be output to gamedir/maps, then back up + // to the gamedir and append /textures. Ugly but it should work + strcpy(path,gszMapFile); + g_strdown(path); + p = strstr(path,"maps"); + if(!p) return 0; + p[0] = '\0'; + strcat(path,"textures/"); + strcat(path,Tex); + strcat(path,".m8"); + f = fopen (path, "rb"); + if (!f) + flags = 0; + else + { + pos = ftell (f); + fseek (f, 0, SEEK_END); + length = ftell (f); + fseek (f, pos, SEEK_SET); + if((mt = (miptex_t*)malloc(length+1))==NULL) + flags = 0; + else + { + ((char *)mt)[length] = 0; + fread(mt, 1, length, f); + fclose (f); + flags = mt->flags; + free(mt); + } + } + } + return flags; + */ +} diff --git a/contrib/gtkgensurf/plugin.cpp b/contrib/gtkgensurf/plugin.cpp new file mode 100644 index 00000000..18e12091 --- /dev/null +++ b/contrib/gtkgensurf/plugin.cpp @@ -0,0 +1,224 @@ +/* +GenSurf plugin for GtkRadiant +Copyright (C) 2001 David Hyde, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "gensurf.h" + +// Global plugin FuncTable +_QERFuncTable_1 g_FuncTable; +_QERQglTable g_GLTable; +_QERUIGtkTable g_UIGtkTable; +_QEREntityTable __ENTITYTABLENAME; +_QERBrushTable __BRUSHTABLENAME; +_QERPatchTable __PATCHTABLENAME; +bool SingleBrushSelected; +bool g_bInitDone; + +#include "iplugin.h" + +const char* QERPlug_Init(void* hApp, void* pMainWidget) +{ + g_pRadiantWnd = (GtkWidget*)pMainWidget; + + return "GenSurf for Q3Radiant"; +} + +const char* QERPlug_GetName () +{ + return "GtkGenSurf"; +} + +const char* QERPlug_GetCommandList () +{ + return "Wall facing 270...;Wall facing 180...;Wall facing 90...;Wall facing 0...;" + "Ceiling...;Ground surface...;-;About..."; +} + +// vMin/vMax provide the bounds of the selection, they are zero if there is no selection +// if there is a selection, bSingleBrush will be true if a single brush is selected +// if so, typical plugin behaviour (such as primitive creation) would use the bounds as +// a rule to create the primitive, then delete the selection +void QERPlug_Dispatch (const char *p, vec3_t vMin, vec3_t vMax, bool bSingleBrush) +{ + bool Generate = false; + + if (!g_bInitDone) + { + if (GenSurfInit ()) + g_bInitDone = true; + } + + if (!strcmp (p, "Ground surface...")) + { + SingleBrushSelected = bSingleBrush; + Plane = PLANE_XY0; + if (SingleBrushSelected) + { + Hll = vMin[0]; + Vll = vMin[1]; + Hur = vMax[0]; + Vur = vMax[1]; + Z00 = Z01 = Z10 = Z11 = vMax[2]; + } + Generate = true; + } + else if (!strcmp (p, "Ceiling...")) + { + SingleBrushSelected = bSingleBrush; + Plane = PLANE_XY1; + if(SingleBrushSelected) + { + Hll = vMin[0]; + Vll = vMin[1]; + Hur = vMax[0]; + Vur = vMax[1]; + Z00 = Z01 = Z10 = Z11 = vMin[2]; + } + Generate = true; + } + else if (!strcmp (p, "Wall facing 0...")) + { + SingleBrushSelected = bSingleBrush; + Plane = PLANE_YZ0; + if (SingleBrushSelected) + { + Hll = vMin[1]; + Vll = vMin[2]; + Hur = vMax[1]; + Vur = vMax[2]; + Z00 = Z01 = Z10 = Z11 = vMax[0]; + } + Generate = true; + } + else if (!strcmp (p, "Wall facing 90...")) + { + SingleBrushSelected = bSingleBrush; + Plane = PLANE_XZ0; + if (SingleBrushSelected) + { + Hll = vMin[0]; + Vll = vMin[2]; + Hur = vMax[0]; + Vur = vMax[2]; + Z00 = Z01 = Z10 = Z11 = vMax[1]; + } + Generate = true; + } + else if (!strcmp (p, "Wall facing 180...")) + { + SingleBrushSelected = bSingleBrush; + Plane = PLANE_YZ1; + if (SingleBrushSelected) + { + Hll = vMin[1]; + Vll = vMin[2]; + Hur = vMax[1]; + Vur = vMax[2]; + Z00 = Z01 = Z10 = Z11 = vMin[0]; + } + Generate = true; + } + else if (!strcmp (p, "Wall facing 270...")) + { + SingleBrushSelected = bSingleBrush; + Plane = PLANE_XZ1; + if (SingleBrushSelected) + { + Hll = vMin[0]; + Vll = vMin[2]; + Hur = vMax[0]; + Vur = vMax[2]; + Z00 = Z01 = Z10 = Z11 = vMin[1]; + } + Generate = true; + } + else if (!strcmp(p,"About...")) + About (g_pRadiantWnd); + + if (Generate) + { + if (SingleBrushSelected) + UseFaceBounds (); + + gtk_widget_show (g_pWnd); + } +} + +// ============================================================================= +// SYNAPSE + +#include "synapse.h" + +class GenSurfSynapseClient : public CSynapseClient +{ +public: + // CSynapseClient API + bool RequestAPI(APIDescriptor_t *pAPI); + const char* GetInfo(); + + GenSurfSynapseClient() { } + virtual ~GenSurfSynapseClient() { } +}; + +CSynapseServer* g_pSynapseServer = NULL; +GenSurfSynapseClient g_SynapseClient; + +extern "C" CSynapseClient* SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces (const char *version, CSynapseServer *pServer) +{ + if (strcmp(version, SYNAPSE_VERSION)) + { + Syn_Printf("ERROR: synapse API version mismatch: should be '" SYNAPSE_VERSION "', got '%s'\n", version); + return NULL; + } + g_pSynapseServer = pServer; + g_pSynapseServer->IncRef(); + Set_Syn_Printf(g_pSynapseServer->Get_Syn_Printf()); + + g_SynapseClient.AddAPI(PLUGIN_MAJOR, "gtkgensurf", sizeof(_QERPluginTable)); + + g_SynapseClient.AddAPI(RADIANT_MAJOR, NULL, sizeof(_QERFuncTable_1), SYN_REQUIRE, &g_FuncTable); + g_SynapseClient.AddAPI(UIGTK_MAJOR, NULL, sizeof(_QERUIGtkTable), SYN_REQUIRE, &g_UIGtkTable); + g_SynapseClient.AddAPI(QGL_MAJOR, NULL, sizeof(_QERQglTable), SYN_REQUIRE, &g_GLTable); + g_SynapseClient.AddAPI(ENTITY_MAJOR, NULL, sizeof(_QEREntityTable), SYN_REQUIRE, &g_EntityTable); + + return &g_SynapseClient; +} + +bool GenSurfSynapseClient::RequestAPI(APIDescriptor_t *pAPI) +{ + if (!strcmp(pAPI->major_name, PLUGIN_MAJOR)) + { + _QERPluginTable* pTable= static_cast<_QERPluginTable*>(pAPI->mpTable); + + pTable->m_pfnQERPlug_Init = QERPlug_Init; + pTable->m_pfnQERPlug_GetName = QERPlug_GetName; + pTable->m_pfnQERPlug_GetCommandList = QERPlug_GetCommandList; + pTable->m_pfnQERPlug_Dispatch = QERPlug_Dispatch; + return true; + } + + Syn_Printf("ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo()); + return false; +} + +#include "version.h" + +const char* GenSurfSynapseClient::GetInfo() +{ + return "GtkGenSurf - built " __DATE__ " " RADIANT_VERSION; +} diff --git a/contrib/gtkgensurf/triangle.c b/contrib/gtkgensurf/triangle.c new file mode 100644 index 00000000..1e155cac --- /dev/null +++ b/contrib/gtkgensurf/triangle.c @@ -0,0 +1,13236 @@ +#define ANSI_DECLARATORS +/*****************************************************************************/ +/* */ +/* 888888888 ,o, / 888 */ +/* 888 88o88o " o8888o 88o8888o o88888o 888 o88888o */ +/* 888 888 888 88b 888 888 888 888 888 d888 88b */ +/* 888 888 888 o88^o888 888 888 "88888" 888 8888oo888 */ +/* 888 888 888 C888 888 888 888 / 888 q888 */ +/* 888 888 888 "88o^888 888 888 Cb 888 "88oooo" */ +/* "8oo8D */ +/* */ +/* A Two-Dimensional Quality Mesh Generator and Delaunay Triangulator. */ +/* (triangle.c) */ +/* */ +/* Version 1.3 */ +/* July 19, 1996 */ +/* */ +/* Copyright 1996 */ +/* Jonathan Richard Shewchuk */ +/* School of Computer Science */ +/* Carnegie Mellon University */ +/* 5000 Forbes Avenue */ +/* Pittsburgh, Pennsylvania 15213-3891 */ +/* jrs@cs.cmu.edu */ +/* */ +/* This program may be freely redistributed under the condition that the */ +/* copyright notices (including this entire header and the copyright */ +/* notice printed when the `-h' switch is selected) are not removed, and */ +/* no compensation is received. Private, research, and institutional */ +/* use is free. You may distribute modified versions of this code UNDER */ +/* THE CONDITION THAT THIS CODE AND ANY MODIFICATIONS MADE TO IT IN THE */ +/* SAME FILE REMAIN UNDER COPYRIGHT OF THE ORIGINAL AUTHOR, BOTH SOURCE */ +/* AND OBJECT CODE ARE MADE FREELY AVAILABLE WITHOUT CHARGE, AND CLEAR */ +/* NOTICE IS GIVEN OF THE MODIFICATIONS. Distribution of this code as */ +/* part of a commercial system is permissible ONLY BY DIRECT ARRANGEMENT */ +/* WITH THE AUTHOR. (If you are not directly supplying this code to a */ +/* customer, and you are instead telling them how they can obtain it for */ +/* free, then you are not required to make any arrangement with me.) */ +/* */ +/* Hypertext instructions for Triangle are available on the Web at */ +/* */ +/* http://www.cs.cmu.edu/~quake/triangle.html */ +/* */ +/* Some of the references listed below are marked [*]. These are available */ +/* for downloading from the Web page */ +/* */ +/* http://www.cs.cmu.edu/~quake/triangle.research.html */ +/* */ +/* A paper discussing some aspects of Triangle is available. See Jonathan */ +/* Richard Shewchuk, "Triangle: Engineering a 2D Quality Mesh Generator */ +/* and Delaunay Triangulator," First Workshop on Applied Computational */ +/* Geometry, ACM, May 1996. [*] */ +/* */ +/* Triangle was created as part of the Archimedes project in the School of */ +/* Computer Science at Carnegie Mellon University. Archimedes is a */ +/* system for compiling parallel finite element solvers. For further */ +/* information, see Anja Feldmann, Omar Ghattas, John R. Gilbert, Gary L. */ +/* Miller, David R. O'Hallaron, Eric J. Schwabe, Jonathan R. Shewchuk, */ +/* and Shang-Hua Teng, "Automated Parallel Solution of Unstructured PDE */ +/* Problems." To appear in Communications of the ACM, we hope. */ +/* */ +/* The quality mesh generation algorithm is due to Jim Ruppert, "A */ +/* Delaunay Refinement Algorithm for Quality 2-Dimensional Mesh */ +/* Generation," Journal of Algorithms 18(3):548-585, May 1995. [*] */ +/* */ +/* My implementation of the divide-and-conquer and incremental Delaunay */ +/* triangulation algorithms follows closely the presentation of Guibas */ +/* and Stolfi, even though I use a triangle-based data structure instead */ +/* of their quad-edge data structure. (In fact, I originally implemented */ +/* Triangle using the quad-edge data structure, but switching to a */ +/* triangle-based data structure sped Triangle by a factor of two.) The */ +/* mesh manipulation primitives and the two aforementioned Delaunay */ +/* triangulation algorithms are described by Leonidas J. Guibas and Jorge */ +/* Stolfi, "Primitives for the Manipulation of General Subdivisions and */ +/* the Computation of Voronoi Diagrams," ACM Transactions on Graphics */ +/* 4(2):74-123, April 1985. */ +/* */ +/* Their O(n log n) divide-and-conquer algorithm is adapted from Der-Tsai */ +/* Lee and Bruce J. Schachter, "Two Algorithms for Constructing the */ +/* Delaunay Triangulation," International Journal of Computer and */ +/* Information Science 9(3):219-242, 1980. The idea to improve the */ +/* divide-and-conquer algorithm by alternating between vertical and */ +/* horizontal cuts was introduced by Rex A. Dwyer, "A Faster Divide-and- */ +/* Conquer Algorithm for Constructing Delaunay Triangulations," */ +/* Algorithmica 2(2):137-151, 1987. */ +/* */ +/* The incremental insertion algorithm was first proposed by C. L. Lawson, */ +/* "Software for C1 Surface Interpolation," in Mathematical Software III, */ +/* John R. Rice, editor, Academic Press, New York, pp. 161-194, 1977. */ +/* For point location, I use the algorithm of Ernst P. Mucke, Isaac */ +/* Saias, and Binhai Zhu, "Fast Randomized Point Location Without */ +/* Preprocessing in Two- and Three-dimensional Delaunay Triangulations," */ +/* Proceedings of the Twelfth Annual Symposium on Computational Geometry, */ +/* ACM, May 1996. [*] If I were to randomize the order of point */ +/* insertion (I currently don't bother), their result combined with the */ +/* result of Leonidas J. Guibas, Donald E. Knuth, and Micha Sharir, */ +/* "Randomized Incremental Construction of Delaunay and Voronoi */ +/* Diagrams," Algorithmica 7(4):381-413, 1992, would yield an expected */ +/* O(n^{4/3}) bound on running time. */ +/* */ +/* The O(n log n) sweepline Delaunay triangulation algorithm is taken from */ +/* Steven Fortune, "A Sweepline Algorithm for Voronoi Diagrams", */ +/* Algorithmica 2(2):153-174, 1987. A random sample of edges on the */ +/* boundary of the triangulation are maintained in a splay tree for the */ +/* purpose of point location. Splay trees are described by Daniel */ +/* Dominic Sleator and Robert Endre Tarjan, "Self-Adjusting Binary Search */ +/* Trees," Journal of the ACM 32(3):652-686, July 1985. */ +/* */ +/* The algorithms for exact computation of the signs of determinants are */ +/* described in Jonathan Richard Shewchuk, "Adaptive Precision Floating- */ +/* Point Arithmetic and Fast Robust Geometric Predicates," Technical */ +/* Report CMU-CS-96-140, School of Computer Science, Carnegie Mellon */ +/* University, Pittsburgh, Pennsylvania, May 1996. [*] (Submitted to */ +/* Discrete & Computational Geometry.) An abbreviated version appears as */ +/* Jonathan Richard Shewchuk, "Robust Adaptive Floating-Point Geometric */ +/* Predicates," Proceedings of the Twelfth Annual Symposium on Computa- */ +/* tional Geometry, ACM, May 1996. [*] Many of the ideas for my exact */ +/* arithmetic routines originate with Douglas M. Priest, "Algorithms for */ +/* Arbitrary Precision Floating Point Arithmetic," Tenth Symposium on */ +/* Computer Arithmetic, 132-143, IEEE Computer Society Press, 1991. [*] */ +/* Many of the ideas for the correct evaluation of the signs of */ +/* determinants are taken from Steven Fortune and Christopher J. Van Wyk, */ +/* "Efficient Exact Arithmetic for Computational Geometry," Proceedings */ +/* of the Ninth Annual Symposium on Computational Geometry, ACM, */ +/* pp. 163-172, May 1993, and from Steven Fortune, "Numerical Stability */ +/* of Algorithms for 2D Delaunay Triangulations," International Journal */ +/* of Computational Geometry & Applications 5(1-2):193-213, March-June */ +/* 1995. */ +/* */ +/* For definitions of and results involving Delaunay triangulations, */ +/* constrained and conforming versions thereof, and other aspects of */ +/* triangular mesh generation, see the excellent survey by Marshall Bern */ +/* and David Eppstein, "Mesh Generation and Optimal Triangulation," in */ +/* Computing and Euclidean Geometry, Ding-Zhu Du and Frank Hwang, */ +/* editors, World Scientific, Singapore, pp. 23-90, 1992. */ +/* */ +/* The time for incrementally adding PSLG (planar straight line graph) */ +/* segments to create a constrained Delaunay triangulation is probably */ +/* O(n^2) per segment in the worst case and O(n) per edge in the common */ +/* case, where n is the number of triangles that intersect the segment */ +/* before it is inserted. This doesn't count point location, which can */ +/* be much more expensive. (This note does not apply to conforming */ +/* Delaunay triangulations, for which a different method is used to */ +/* insert segments.) */ +/* */ +/* The time for adding segments to a conforming Delaunay triangulation is */ +/* not clear, but does not depend upon n alone. In some cases, very */ +/* small features (like a point lying next to a segment) can cause a */ +/* single segment to be split an arbitrary number of times. Of course, */ +/* floating-point precision is a practical barrier to how much this can */ +/* happen. */ +/* */ +/* The time for deleting a point from a Delaunay triangulation is O(n^2) in */ +/* the worst case and O(n) in the common case, where n is the degree of */ +/* the point being deleted. I could improve this to expected O(n) time */ +/* by "inserting" the neighboring vertices in random order, but n is */ +/* usually quite small, so it's not worth the bother. (The O(n) time */ +/* for random insertion follows from L. Paul Chew, "Building Voronoi */ +/* Diagrams for Convex Polygons in Linear Expected Time," Technical */ +/* Report PCS-TR90-147, Department of Mathematics and Computer Science, */ +/* Dartmouth College, 1990. */ +/* */ +/* Ruppert's Delaunay refinement algorithm typically generates triangles */ +/* at a linear rate (constant time per triangle) after the initial */ +/* triangulation is formed. There may be pathological cases where more */ +/* time is required, but these never arise in practice. */ +/* */ +/* The segment intersection formulae are straightforward. If you want to */ +/* see them derived, see Franklin Antonio. "Faster Line Segment */ +/* Intersection." In Graphics Gems III (David Kirk, editor), pp. 199- */ +/* 202. Academic Press, Boston, 1992. */ +/* */ +/* If you make any improvements to this code, please please please let me */ +/* know, so that I may obtain the improvements. Even if you don't change */ +/* the code, I'd still love to hear what it's being used for. */ +/* */ +/* Disclaimer: Neither I nor Carnegie Mellon warrant this code in any way */ +/* whatsoever. This code is provided "as-is". Use at your own risk. */ +/* */ +/*****************************************************************************/ + +/* For single precision (which will save some memory and reduce paging), */ +/* define the symbol SINGLE by using the -DSINGLE compiler switch or by */ +/* writing "#define SINGLE" below. */ +/* */ +/* For double precision (which will allow you to refine meshes to a smaller */ +/* edge length), leave SINGLE undefined. */ +/* */ +/* Double precision uses more memory, but improves the resolution of the */ +/* meshes you can generate with Triangle. It also reduces the likelihood */ +/* of a floating exception due to overflow. Finally, it is much faster */ +/* than single precision on 64-bit architectures like the DEC Alpha. I */ +/* recommend double precision unless you want to generate a mesh for which */ +/* you do not have enough memory. */ + +#define SINGLE + +#ifdef SINGLE +#define REAL float +#else /* not SINGLE */ +#define REAL double +#endif /* not SINGLE */ + +/* If yours is not a Unix system, define the NO_TIMER compiler switch to */ +/* remove the Unix-specific timing code. */ + +#define NO_TIMER + +/* To insert lots of self-checks for internal errors, define the SELF_CHECK */ +/* symbol. This will slow down the program significantly. It is best to */ +/* define the symbol using the -DSELF_CHECK compiler switch, but you could */ +/* write "#define SELF_CHECK" below. If you are modifying this code, I */ +/* recommend you turn self-checks on. */ + +/* #define SELF_CHECK */ + +/* To compile Triangle as a callable object library (triangle.o), define the */ +/* TRILIBRARY symbol. Read the file triangle.h for details on how to call */ +/* the procedure triangulate() that results. */ + +#define TRILIBRARY + +/* It is possible to generate a smaller version of Triangle using one or */ +/* both of the following symbols. Define the REDUCED symbol to eliminate */ +/* all features that are primarily of research interest; specifically, the */ +/* -i, -F, -s, and -C switches. Define the CDT_ONLY symbol to eliminate */ +/* all meshing algorithms above and beyond constrained Delaunay */ +/* triangulation; specifically, the -r, -q, -a, -S, and -s switches. */ +/* These reductions are most likely to be useful when generating an object */ +/* library (triangle.o) by defining the TRILIBRARY symbol. */ + +#define REDUCED +#define CDT_ONLY + +/* On some machines, the exact arithmetic routines might be defeated by the */ +/* use of internal extended precision floating-point registers. Sometimes */ +/* this problem can be fixed by defining certain values to be volatile, */ +/* thus forcing them to be stored to memory and rounded off. This isn't */ +/* a great solution, though, as it slows Triangle down. */ +/* */ +/* To try this out, write "#define INEXACT volatile" below. Normally, */ +/* however, INEXACT should be defined to be nothing. ("#define INEXACT".) */ + +#define INEXACT /* Nothing */ +/* #define INEXACT volatile */ + +/* Maximum number of characters in a file name (including the null). */ + +#define FILENAMESIZE 512 + +/* Maximum number of characters in a line read from a file (including the */ +/* null). */ + +#define INPUTLINESIZE 512 + +/* For efficiency, a variety of data structures are allocated in bulk. The */ +/* following constants determine how many of each structure is allocated */ +/* at once. */ + +#define TRIPERBLOCK 4092 /* Number of triangles allocated at once. */ +#define SHELLEPERBLOCK 508 /* Number of shell edges allocated at once. */ +#define POINTPERBLOCK 4092 /* Number of points allocated at once. */ +#define VIRUSPERBLOCK 1020 /* Number of virus triangles allocated at once. */ +/* Number of encroached segments allocated at once. */ +#define BADSEGMENTPERBLOCK 252 +/* Number of skinny triangles allocated at once. */ +#define BADTRIPERBLOCK 4092 +/* Number of splay tree nodes allocated at once. */ +#define SPLAYNODEPERBLOCK 508 + +/* The point marker DEADPOINT is an arbitrary number chosen large enough to */ +/* (hopefully) not conflict with user boundary markers. Make sure that it */ +/* is small enough to fit into your machine's integer size. */ + +#define DEADPOINT -1073741824 + +/* The next line is used to outsmart some very stupid compilers. If your */ +/* compiler is smarter, feel free to replace the "int" with "void". */ +/* Not that it matters. */ + +#define VOID int + +/* Two constants for algorithms based on random sampling. Both constants */ +/* have been chosen empirically to optimize their respective algorithms. */ + +/* Used for the point location scheme of Mucke, Saias, and Zhu, to decide */ +/* how large a random sample of triangles to inspect. */ +#define SAMPLEFACTOR 11 +/* Used in Fortune's sweepline Delaunay algorithm to determine what fraction */ +/* of boundary edges should be maintained in the splay tree for point */ +/* location on the front. */ +#define SAMPLERATE 10 + +/* A number that speaks for itself, every kissable digit. */ + +#define PI 3.141592653589793238462643383279502884197169399375105820974944592308 + +/* Another fave. */ + +#define SQUAREROOTTWO 1.4142135623730950488016887242096980785696718753769480732 + +/* And here's one for those of you who are intimidated by math. */ + +#define ONETHIRD 0.333333333333333333333333333333333333333333333333333333333333 + +#include +#include +#include +#ifndef NO_TIMER +#include +#endif /* NO_TIMER */ +#ifdef TRILIBRARY +#include "triangle.h" +#endif /* TRILIBRARY */ + +/* The following obscenity seems to be necessary to ensure that this program */ +/* will port to Dec Alphas running OSF/1, because their stdio.h file commits */ +/* the unpardonable sin of including stdlib.h. Hence, malloc(), free(), and */ +/* exit() may or may not already be defined at this point. I declare these */ +/* functions explicitly because some non-ANSI C compilers lack stdlib.h. */ + +#ifndef _STDLIB_H_ +extern void *malloc(); +extern void free(); +extern void exit(); +extern double strtod(); +extern long strtol(); +#endif /* _STDLIB_H_ */ + +/* A few forward declarations. */ + +void poolrestart(); +#ifndef TRILIBRARY +char *readline(); +char *findfield(); +#endif /* not TRILIBRARY */ + +/* Labels that signify whether a record consists primarily of pointers or of */ +/* floating-point words. Used to make decisions about data alignment. */ + +enum wordtype {POINTER, FLOATINGPOINT}; + +/* Labels that signify the result of point location. The result of a */ +/* search indicates that the point falls in the interior of a triangle, on */ +/* an edge, on a vertex, or outside the mesh. */ + +enum locateresult {INTRIANGLE, ONEDGE, ONVERTEX, OUTSIDE}; + +/* Labels that signify the result of site insertion. The result indicates */ +/* that the point was inserted with complete success, was inserted but */ +/* encroaches on a segment, was not inserted because it lies on a segment, */ +/* or was not inserted because another point occupies the same location. */ + +enum insertsiteresult {SUCCESSFULPOINT, ENCROACHINGPOINT, VIOLATINGPOINT, + DUPLICATEPOINT}; + +/* Labels that signify the result of direction finding. The result */ +/* indicates that a segment connecting the two query points falls within */ +/* the direction triangle, along the left edge of the direction triangle, */ +/* or along the right edge of the direction triangle. */ + +enum finddirectionresult {WITHIN, LEFTCOLLINEAR, RIGHTCOLLINEAR}; + +/* Labels that signify the result of the circumcenter computation routine. */ +/* The return value indicates which edge of the triangle is shortest. */ + +enum circumcenterresult {OPPOSITEORG, OPPOSITEDEST, OPPOSITEAPEX}; + +/*****************************************************************************/ +/* */ +/* The basic mesh data structures */ +/* */ +/* There are three: points, triangles, and shell edges (abbreviated */ +/* `shelle'). These three data structures, linked by pointers, comprise */ +/* the mesh. A point simply represents a point in space and its properties.*/ +/* A triangle is a triangle. A shell edge is a special data structure used */ +/* to represent impenetrable segments in the mesh (including the outer */ +/* boundary, boundaries of holes, and internal boundaries separating two */ +/* triangulated regions). Shell edges represent boundaries defined by the */ +/* user that triangles may not lie across. */ +/* */ +/* A triangle consists of a list of three vertices, a list of three */ +/* adjoining triangles, a list of three adjoining shell edges (when shell */ +/* edges are used), an arbitrary number of optional user-defined floating- */ +/* point attributes, and an optional area constraint. The latter is an */ +/* upper bound on the permissible area of each triangle in a region, used */ +/* for mesh refinement. */ +/* */ +/* For a triangle on a boundary of the mesh, some or all of the neighboring */ +/* triangles may not be present. For a triangle in the interior of the */ +/* mesh, often no neighboring shell edges are present. Such absent */ +/* triangles and shell edges are never represented by NULL pointers; they */ +/* are represented by two special records: `dummytri', the triangle that */ +/* fills "outer space", and `dummysh', the omnipresent shell edge. */ +/* `dummytri' and `dummysh' are used for several reasons; for instance, */ +/* they can be dereferenced and their contents examined without causing the */ +/* memory protection exception that would occur if NULL were dereferenced. */ +/* */ +/* However, it is important to understand that a triangle includes other */ +/* information as well. The pointers to adjoining vertices, triangles, and */ +/* shell edges are ordered in a way that indicates their geometric relation */ +/* to each other. Furthermore, each of these pointers contains orientation */ +/* information. Each pointer to an adjoining triangle indicates which face */ +/* of that triangle is contacted. Similarly, each pointer to an adjoining */ +/* shell edge indicates which side of that shell edge is contacted, and how */ +/* the shell edge is oriented relative to the triangle. */ +/* */ +/* Shell edges are found abutting edges of triangles; either sandwiched */ +/* between two triangles, or resting against one triangle on an exterior */ +/* boundary or hole boundary. */ +/* */ +/* A shell edge consists of a list of two vertices, a list of two */ +/* adjoining shell edges, and a list of two adjoining triangles. One of */ +/* the two adjoining triangles may not be present (though there should */ +/* always be one), and neighboring shell edges might not be present. */ +/* Shell edges also store a user-defined integer "boundary marker". */ +/* Typically, this integer is used to indicate what sort of boundary */ +/* conditions are to be applied at that location in a finite element */ +/* simulation. */ +/* */ +/* Like triangles, shell edges maintain information about the relative */ +/* orientation of neighboring objects. */ +/* */ +/* Points are relatively simple. A point is a list of floating point */ +/* numbers, starting with the x, and y coordinates, followed by an */ +/* arbitrary number of optional user-defined floating-point attributes, */ +/* followed by an integer boundary marker. During the segment insertion */ +/* phase, there is also a pointer from each point to a triangle that may */ +/* contain it. Each pointer is not always correct, but when one is, it */ +/* speeds up segment insertion. These pointers are assigned values once */ +/* at the beginning of the segment insertion phase, and are not used or */ +/* updated at any other time. Edge swapping during segment insertion will */ +/* render some of them incorrect. Hence, don't rely upon them for */ +/* anything. For the most part, points do not have any information about */ +/* what triangles or shell edges they are linked to. */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* Handles */ +/* */ +/* The oriented triangle (`triedge') and oriented shell edge (`edge') data */ +/* structures defined below do not themselves store any part of the mesh. */ +/* The mesh itself is made of `triangle's, `shelle's, and `point's. */ +/* */ +/* Oriented triangles and oriented shell edges will usually be referred to */ +/* as "handles". A handle is essentially a pointer into the mesh; it */ +/* allows you to "hold" one particular part of the mesh. Handles are used */ +/* to specify the regions in which one is traversing and modifying the mesh.*/ +/* A single `triangle' may be held by many handles, or none at all. (The */ +/* latter case is not a memory leak, because the triangle is still */ +/* connected to other triangles in the mesh.) */ +/* */ +/* A `triedge' is a handle that holds a triangle. It holds a specific side */ +/* of the triangle. An `edge' is a handle that holds a shell edge. It */ +/* holds either the left or right side of the edge. */ +/* */ +/* Navigation about the mesh is accomplished through a set of mesh */ +/* manipulation primitives, further below. Many of these primitives take */ +/* a handle and produce a new handle that holds the mesh near the first */ +/* handle. Other primitives take two handles and glue the corresponding */ +/* parts of the mesh together. The exact position of the handles is */ +/* important. For instance, when two triangles are glued together by the */ +/* bond() primitive, they are glued by the sides on which the handles lie. */ +/* */ +/* Because points have no information about which triangles they are */ +/* attached to, I commonly represent a point by use of a handle whose */ +/* origin is the point. A single handle can simultaneously represent a */ +/* triangle, an edge, and a point. */ +/* */ +/*****************************************************************************/ + +/* The triangle data structure. Each triangle contains three pointers to */ +/* adjoining triangles, plus three pointers to vertex points, plus three */ +/* pointers to shell edges (defined below; these pointers are usually */ +/* `dummysh'). It may or may not also contain user-defined attributes */ +/* and/or a floating-point "area constraint". It may also contain extra */ +/* pointers for nodes, when the user asks for high-order elements. */ +/* Because the size and structure of a `triangle' is not decided until */ +/* runtime, I haven't simply defined the type `triangle' to be a struct. */ + +typedef REAL **triangle; /* Really: typedef triangle *triangle */ + +/* An oriented triangle: includes a pointer to a triangle and orientation. */ +/* The orientation denotes an edge of the triangle. Hence, there are */ +/* three possible orientations. By convention, each edge is always */ +/* directed to point counterclockwise about the corresponding triangle. */ + +struct triedge { + triangle *tri; + int orient; /* Ranges from 0 to 2. */ +}; + +/* The shell data structure. Each shell edge contains two pointers to */ +/* adjoining shell edges, plus two pointers to vertex points, plus two */ +/* pointers to adjoining triangles, plus one shell marker. */ + +typedef REAL **shelle; /* Really: typedef shelle *shelle */ + +/* An oriented shell edge: includes a pointer to a shell edge and an */ +/* orientation. The orientation denotes a side of the edge. Hence, there */ +/* are two possible orientations. By convention, the edge is always */ +/* directed so that the "side" denoted is the right side of the edge. */ + +struct edge { + shelle *sh; + int shorient; /* Ranges from 0 to 1. */ +}; + +/* The point data structure. Each point is actually an array of REALs. */ +/* The number of REALs is unknown until runtime. An integer boundary */ +/* marker, and sometimes a pointer to a triangle, is appended after the */ +/* REALs. */ + +typedef REAL *point; + +/* A queue used to store encroached segments. Each segment's vertices are */ +/* stored so that one can check whether a segment is still the same. */ + +struct badsegment { + struct edge encsegment; /* An encroached segment. */ + point segorg, segdest; /* The two vertices. */ + struct badsegment *nextsegment; /* Pointer to next encroached segment. */ +}; + +/* A queue used to store bad triangles. The key is the square of the cosine */ +/* of the smallest angle of the triangle. Each triangle's vertices are */ +/* stored so that one can check whether a triangle is still the same. */ + +struct badface { + struct triedge badfacetri; /* A bad triangle. */ + REAL key; /* cos^2 of smallest (apical) angle. */ + point faceorg, facedest, faceapex; /* The three vertices. */ + struct badface *nextface; /* Pointer to next bad triangle. */ +}; + +/* A node in a heap used to store events for the sweepline Delaunay */ +/* algorithm. Nodes do not point directly to their parents or children in */ +/* the heap. Instead, each node knows its position in the heap, and can */ +/* look up its parent and children in a separate array. The `eventptr' */ +/* points either to a `point' or to a triangle (in encoded format, so that */ +/* an orientation is included). In the latter case, the origin of the */ +/* oriented triangle is the apex of a "circle event" of the sweepline */ +/* algorithm. To distinguish site events from circle events, all circle */ +/* events are given an invalid (smaller than `xmin') x-coordinate `xkey'. */ + +struct event { + REAL xkey, ykey; /* Coordinates of the event. */ + VOID *eventptr; /* Can be a point or the location of a circle event. */ + int heapposition; /* Marks this event's position in the heap. */ +}; + +/* A node in the splay tree. Each node holds an oriented ghost triangle */ +/* that represents a boundary edge of the growing triangulation. When a */ +/* circle event covers two boundary edges with a triangle, so that they */ +/* are no longer boundary edges, those edges are not immediately deleted */ +/* from the tree; rather, they are lazily deleted when they are next */ +/* encountered. (Since only a random sample of boundary edges are kept */ +/* in the tree, lazy deletion is faster.) `keydest' is used to verify */ +/* that a triangle is still the same as when it entered the splay tree; if */ +/* it has been rotated (due to a circle event), it no longer represents a */ +/* boundary edge and should be deleted. */ + +struct splaynode { + struct triedge keyedge; /* Lprev of an edge on the front. */ + point keydest; /* Used to verify that splay node is still live. */ + struct splaynode *lchild, *rchild; /* Children in splay tree. */ +}; + +/* A type used to allocate memory. firstblock is the first block of items. */ +/* nowblock is the block from which items are currently being allocated. */ +/* nextitem points to the next slab of free memory for an item. */ +/* deaditemstack is the head of a linked list (stack) of deallocated items */ +/* that can be recycled. unallocateditems is the number of items that */ +/* remain to be allocated from nowblock. */ +/* */ +/* Traversal is the process of walking through the entire list of items, and */ +/* is separate from allocation. Note that a traversal will visit items on */ +/* the "deaditemstack" stack as well as live items. pathblock points to */ +/* the block currently being traversed. pathitem points to the next item */ +/* to be traversed. pathitemsleft is the number of items that remain to */ +/* be traversed in pathblock. */ +/* */ +/* itemwordtype is set to POINTER or FLOATINGPOINT, and is used to suggest */ +/* what sort of word the record is primarily made up of. alignbytes */ +/* determines how new records should be aligned in memory. itembytes and */ +/* itemwords are the length of a record in bytes (after rounding up) and */ +/* words. itemsperblock is the number of items allocated at once in a */ +/* single block. items is the number of currently allocated items. */ +/* maxitems is the maximum number of items that have been allocated at */ +/* once; it is the current number of items plus the number of records kept */ +/* on deaditemstack. */ + +struct memorypool { + VOID **firstblock, **nowblock; + VOID *nextitem; + VOID *deaditemstack; + VOID **pathblock; + VOID *pathitem; + enum wordtype itemwordtype; + int alignbytes; + int itembytes, itemwords; + int itemsperblock; + long items, maxitems; + int unallocateditems; + int pathitemsleft; +}; + +/* Variables used to allocate memory for triangles, shell edges, points, */ +/* viri (triangles being eaten), bad (encroached) segments, bad (skinny */ +/* or too large) triangles, and splay tree nodes. */ + +static struct memorypool triangles; +static struct memorypool shelles; +static struct memorypool points; +static struct memorypool viri; +static struct memorypool badsegments; +static struct memorypool badtriangles; +static struct memorypool splaynodes; + +/* Variables that maintain the bad triangle queues. The tails are pointers */ +/* to the pointers that have to be filled in to enqueue an item. */ + +static struct badface *queuefront[64]; +static struct badface **queuetail[64]; + +static REAL xmin, xmax, ymin, ymax; /* x and y bounds. */ +static REAL xminextreme; /* Nonexistent x value used as a flag in sweepline. */ +static int inpoints; /* Number of input points. */ +static int inelements; /* Number of input triangles. */ +static int insegments; /* Number of input segments. */ +static int holes; /* Number of input holes. */ +static int regions; /* Number of input regions. */ +static long edges; /* Number of output edges. */ +static int mesh_dim; /* Dimension (ought to be 2). */ +static int nextras; /* Number of attributes per point. */ +static int eextras; /* Number of attributes per triangle. */ +static long hullsize; /* Number of edges of convex hull. */ +static int triwords; /* Total words per triangle. */ +static int shwords; /* Total words per shell edge. */ +static int pointmarkindex; /* Index to find boundary marker of a point. */ +static int point2triindex; /* Index to find a triangle adjacent to a point. */ +static int highorderindex; /* Index to find extra nodes for high-order elements. */ +static int elemattribindex; /* Index to find attributes of a triangle. */ +static int areaboundindex; /* Index to find area bound of a triangle. */ +static int checksegments; /* Are there segments in the triangulation yet? */ +static int readnodefile; /* Has a .node file been read? */ +static long samples; /* Number of random samples for point location. */ +static unsigned long randomseed; /* Current random number seed. */ + +static REAL splitter; /* Used to split REAL factors for exact multiplication. */ +static REAL epsilon; /* Floating-point machine epsilon. */ +static REAL resulterrbound; +static REAL ccwerrboundA, ccwerrboundB, ccwerrboundC; +static REAL iccerrboundA, iccerrboundB, iccerrboundC; + +static long incirclecount; /* Number of incircle tests performed. */ +static long counterclockcount; /* Number of counterclockwise tests performed. */ +static long hyperbolacount; /* Number of right-of-hyperbola tests performed. */ +static long circumcentercount; /* Number of circumcenter calculations performed. */ +static long circletopcount; /* Number of circle top calculations performed. */ + +/* Switches for the triangulator. */ +/* poly: -p switch. refine: -r switch. */ +/* quality: -q switch. */ +/* minangle: minimum angle bound, specified after -q switch. */ +/* goodangle: cosine squared of minangle. */ +/* vararea: -a switch without number. */ +/* fixedarea: -a switch with number. */ +/* maxarea: maximum area bound, specified after -a switch. */ +/* regionattrib: -A switch. convex: -c switch. */ +/* firstnumber: inverse of -z switch. All items are numbered starting */ +/* from firstnumber. */ +/* edgesout: -e switch. voronoi: -v switch. */ +/* neighbors: -n switch. geomview: -g switch. */ +/* nobound: -B switch. nopolywritten: -P switch. */ +/* nonodewritten: -N switch. noelewritten: -E switch. */ +/* noiterationnum: -I switch. noholes: -O switch. */ +/* noexact: -X switch. */ +/* order: element order, specified after -o switch. */ +/* nobisect: count of how often -Y switch is selected. */ +/* steiner: maximum number of Steiner points, specified after -S switch. */ +/* steinerleft: number of Steiner points not yet used. */ +/* incremental: -i switch. sweepline: -F switch. */ +/* dwyer: inverse of -l switch. */ +/* splitseg: -s switch. */ +/* docheck: -C switch. */ +/* quiet: -Q switch. verbose: count of how often -V switch is selected. */ +/* useshelles: -p, -r, -q, or -c switch; determines whether shell edges */ +/* are used at all. */ +/* */ +/* Read the instructions to find out the meaning of these switches. */ + +static int poly, refine, quality, vararea, fixedarea, regionattrib, convex; +static int firstnumber; +static int edgesout, voronoi, neighbors, geomview; +static int nobound, nopolywritten, nonodewritten, noelewritten, noiterationnum; +static int noholes, noexact; +static int incremental, sweepline, dwyer; +static int splitseg; +static int docheck; +static int quiet, verbose; +static int useshelles; +static int order; +static int nobisect; +static int steiner, steinerleft; +static REAL minangle, goodangle; +static REAL maxarea; + +/* Variables for file names. */ + +#ifndef TRILIBRARY +char innodefilename[FILENAMESIZE]; +char inelefilename[FILENAMESIZE]; +char inpolyfilename[FILENAMESIZE]; +char areafilename[FILENAMESIZE]; +char outnodefilename[FILENAMESIZE]; +char outelefilename[FILENAMESIZE]; +char outpolyfilename[FILENAMESIZE]; +char edgefilename[FILENAMESIZE]; +char vnodefilename[FILENAMESIZE]; +char vedgefilename[FILENAMESIZE]; +char neighborfilename[FILENAMESIZE]; +char offfilename[FILENAMESIZE]; +#endif /* not TRILIBRARY */ + +/* Triangular bounding box points. */ + +static point infpoint1, infpoint2, infpoint3; + +/* Pointer to the `triangle' that occupies all of "outer space". */ + +static triangle *dummytri; +static triangle *dummytribase; /* Keep base address so we can free() it later. */ + +/* Pointer to the omnipresent shell edge. Referenced by any triangle or */ +/* shell edge that isn't really connected to a shell edge at that */ +/* location. */ + +static shelle *dummysh; +static shelle *dummyshbase; /* Keep base address so we can free() it later. */ + +/* Pointer to a recently visited triangle. Improves point location if */ +/* proximate points are inserted sequentially. */ + +static struct triedge recenttri; + +/*****************************************************************************/ +/* */ +/* Mesh manipulation primitives. Each triangle contains three pointers to */ +/* other triangles, with orientations. Each pointer points not to the */ +/* first byte of a triangle, but to one of the first three bytes of a */ +/* triangle. It is necessary to extract both the triangle itself and the */ +/* orientation. To save memory, I keep both pieces of information in one */ +/* pointer. To make this possible, I assume that all triangles are aligned */ +/* to four-byte boundaries. The `decode' routine below decodes a pointer, */ +/* extracting an orientation (in the range 0 to 2) and a pointer to the */ +/* beginning of a triangle. The `encode' routine compresses a pointer to a */ +/* triangle and an orientation into a single pointer. My assumptions that */ +/* triangles are four-byte-aligned and that the `unsigned long' type is */ +/* long enough to hold a pointer are two of the few kludges in this program.*/ +/* */ +/* Shell edges are manipulated similarly. A pointer to a shell edge */ +/* carries both an address and an orientation in the range 0 to 1. */ +/* */ +/* The other primitives take an oriented triangle or oriented shell edge, */ +/* and return an oriented triangle or oriented shell edge or point; or they */ +/* change the connections in the data structure. */ +/* */ +/*****************************************************************************/ + +/********* Mesh manipulation primitives begin here *********/ +/** **/ +/** **/ + +/* Fast lookup arrays to speed some of the mesh manipulation primitives. */ + +int plus1mod3[3] = {1, 2, 0}; +int minus1mod3[3] = {2, 0, 1}; + +/********* Primitives for triangles *********/ +/* */ +/* */ + +/* decode() converts a pointer to an oriented triangle. The orientation is */ +/* extracted from the two least significant bits of the pointer. */ + +#define decode(ptr, triedge) \ + (triedge).orient = (int) ((unsigned long) (ptr) & (unsigned long) 3l); \ + (triedge).tri = (triangle *) \ + ((unsigned long) (ptr) ^ (unsigned long) (triedge).orient) + +/* encode() compresses an oriented triangle into a single pointer. It */ +/* relies on the assumption that all triangles are aligned to four-byte */ +/* boundaries, so the two least significant bits of (triedge).tri are zero.*/ + +#define encode(triedge) \ + (triangle) ((unsigned long) (triedge).tri | (unsigned long) (triedge).orient) + +/* The following edge manipulation primitives are all described by Guibas */ +/* and Stolfi. However, they use an edge-based data structure, whereas I */ +/* am using a triangle-based data structure. */ + +/* sym() finds the abutting triangle, on the same edge. Note that the */ +/* edge direction is necessarily reversed, because triangle/edge handles */ +/* are always directed counterclockwise around the triangle. */ + +#define sym(triedge1, triedge2) \ + ptr = (triedge1).tri[(triedge1).orient]; \ + decode(ptr, triedge2); + +#define symself(triedge) \ + ptr = (triedge).tri[(triedge).orient]; \ + decode(ptr, triedge); + +/* lnext() finds the next edge (counterclockwise) of a triangle. */ + +#define lnext(triedge1, triedge2) \ + (triedge2).tri = (triedge1).tri; \ + (triedge2).orient = plus1mod3[(triedge1).orient] + +#define lnextself(triedge) \ + (triedge).orient = plus1mod3[(triedge).orient] + +/* lprev() finds the previous edge (clockwise) of a triangle. */ + +#define lprev(triedge1, triedge2) \ + (triedge2).tri = (triedge1).tri; \ + (triedge2).orient = minus1mod3[(triedge1).orient] + +#define lprevself(triedge) \ + (triedge).orient = minus1mod3[(triedge).orient] + +/* onext() spins counterclockwise around a point; that is, it finds the next */ +/* edge with the same origin in the counterclockwise direction. This edge */ +/* will be part of a different triangle. */ + +#define onext(triedge1, triedge2) \ + lprev(triedge1, triedge2); \ + symself(triedge2); + +#define onextself(triedge) \ + lprevself(triedge); \ + symself(triedge); + +/* oprev() spins clockwise around a point; that is, it finds the next edge */ +/* with the same origin in the clockwise direction. This edge will be */ +/* part of a different triangle. */ + +#define oprev(triedge1, triedge2) \ + sym(triedge1, triedge2); \ + lnextself(triedge2); + +#define oprevself(triedge) \ + symself(triedge); \ + lnextself(triedge); + +/* dnext() spins counterclockwise around a point; that is, it finds the next */ +/* edge with the same destination in the counterclockwise direction. This */ +/* edge will be part of a different triangle. */ + +#define dnext(triedge1, triedge2) \ + sym(triedge1, triedge2); \ + lprevself(triedge2); + +#define dnextself(triedge) \ + symself(triedge); \ + lprevself(triedge); + +/* dprev() spins clockwise around a point; that is, it finds the next edge */ +/* with the same destination in the clockwise direction. This edge will */ +/* be part of a different triangle. */ + +#define dprev(triedge1, triedge2) \ + lnext(triedge1, triedge2); \ + symself(triedge2); + +#define dprevself(triedge) \ + lnextself(triedge); \ + symself(triedge); + +/* rnext() moves one edge counterclockwise about the adjacent triangle. */ +/* (It's best understood by reading Guibas and Stolfi. It involves */ +/* changing triangles twice.) */ + +#define rnext(triedge1, triedge2) \ + sym(triedge1, triedge2); \ + lnextself(triedge2); \ + symself(triedge2); + +#define rnextself(triedge) \ + symself(triedge); \ + lnextself(triedge); \ + symself(triedge); + +/* rnext() moves one edge clockwise about the adjacent triangle. */ +/* (It's best understood by reading Guibas and Stolfi. It involves */ +/* changing triangles twice.) */ + +#define rprev(triedge1, triedge2) \ + sym(triedge1, triedge2); \ + lprevself(triedge2); \ + symself(triedge2); + +#define rprevself(triedge) \ + symself(triedge); \ + lprevself(triedge); \ + symself(triedge); + +/* These primitives determine or set the origin, destination, or apex of a */ +/* triangle. */ + +#define org(triedge, pointptr) \ + pointptr = (point) (triedge).tri[plus1mod3[(triedge).orient] + 3] + +#define dest(triedge, pointptr) \ + pointptr = (point) (triedge).tri[minus1mod3[(triedge).orient] + 3] + +#define apex(triedge, pointptr) \ + pointptr = (point) (triedge).tri[(triedge).orient + 3] + +#define setorg(triedge, pointptr) \ + (triedge).tri[plus1mod3[(triedge).orient] + 3] = (triangle) pointptr + +#define setdest(triedge, pointptr) \ + (triedge).tri[minus1mod3[(triedge).orient] + 3] = (triangle) pointptr + +#define setapex(triedge, pointptr) \ + (triedge).tri[(triedge).orient + 3] = (triangle) pointptr + +#define setvertices2null(triedge) \ + (triedge).tri[3] = (triangle) NULL; \ + (triedge).tri[4] = (triangle) NULL; \ + (triedge).tri[5] = (triangle) NULL; + +/* Bond two triangles together. */ + +#define bond(triedge1, triedge2) \ + (triedge1).tri[(triedge1).orient] = encode(triedge2); \ + (triedge2).tri[(triedge2).orient] = encode(triedge1) + +/* Dissolve a bond (from one side). Note that the other triangle will still */ +/* think it's connected to this triangle. Usually, however, the other */ +/* triangle is being deleted entirely, or bonded to another triangle, so */ +/* it doesn't matter. */ + +#define dissolve(triedge) \ + (triedge).tri[(triedge).orient] = (triangle) dummytri + +/* Copy a triangle/edge handle. */ + +#define triedgecopy(triedge1, triedge2) \ + (triedge2).tri = (triedge1).tri; \ + (triedge2).orient = (triedge1).orient + +/* Test for equality of triangle/edge handles. */ + +#define triedgeequal(triedge1, triedge2) \ + (((triedge1).tri == (triedge2).tri) && \ + ((triedge1).orient == (triedge2).orient)) + +/* Primitives to infect or cure a triangle with the virus. These rely on */ +/* the assumption that all shell edges are aligned to four-byte boundaries.*/ + +#define infect(triedge) \ + (triedge).tri[6] = (triangle) \ + ((unsigned long) (triedge).tri[6] | (unsigned long) 2l) + +#define uninfect(triedge) \ + (triedge).tri[6] = (triangle) \ + ((unsigned long) (triedge).tri[6] & ~ (unsigned long) 2l) + +/* Test a triangle for viral infection. */ + +#define infected(triedge) \ + (((unsigned long) (triedge).tri[6] & (unsigned long) 2l) != 0) + +/* Check or set a triangle's attributes. */ + +#define elemattribute(triedge, attnum) \ + ((REAL *) (triedge).tri)[elemattribindex + (attnum)] + +#define setelemattribute(triedge, attnum, value) \ + ((REAL *) (triedge).tri)[elemattribindex + (attnum)] = (REAL)value + +/* Check or set a triangle's maximum area bound. */ + +#define areabound(triedge) ((REAL *) (triedge).tri)[areaboundindex] + +#define setareabound(triedge, value) \ + ((REAL *) (triedge).tri)[areaboundindex] = (REAL)value + +/********* Primitives for shell edges *********/ +/* */ +/* */ + +/* sdecode() converts a pointer to an oriented shell edge. The orientation */ +/* is extracted from the least significant bit of the pointer. The two */ +/* least significant bits (one for orientation, one for viral infection) */ +/* are masked out to produce the real pointer. */ + +#define sdecode(sptr, edge) \ + (edge).shorient = (int) ((unsigned long) (sptr) & (unsigned long) 1l); \ + (edge).sh = (shelle *) \ + ((unsigned long) (sptr) & ~ (unsigned long) 3l) + +/* sencode() compresses an oriented shell edge into a single pointer. It */ +/* relies on the assumption that all shell edges are aligned to two-byte */ +/* boundaries, so the least significant bit of (edge).sh is zero. */ + +#define sencode(edge) \ + (shelle) ((unsigned long) (edge).sh | (unsigned long) (edge).shorient) + +/* ssym() toggles the orientation of a shell edge. */ + +#define ssym(edge1, edge2) \ + (edge2).sh = (edge1).sh; \ + (edge2).shorient = 1 - (edge1).shorient + +#define ssymself(edge) \ + (edge).shorient = 1 - (edge).shorient + +/* spivot() finds the other shell edge (from the same segment) that shares */ +/* the same origin. */ + +#define spivot(edge1, edge2) \ + sptr = (edge1).sh[(edge1).shorient]; \ + sdecode(sptr, edge2) + +#define spivotself(edge) \ + sptr = (edge).sh[(edge).shorient]; \ + sdecode(sptr, edge) + +/* snext() finds the next shell edge (from the same segment) in sequence; */ +/* one whose origin is the input shell edge's destination. */ + +#define snext(edge1, edge2) \ + sptr = (edge1).sh[1 - (edge1).shorient]; \ + sdecode(sptr, edge2) + +#define snextself(edge) \ + sptr = (edge).sh[1 - (edge).shorient]; \ + sdecode(sptr, edge) + +/* These primitives determine or set the origin or destination of a shell */ +/* edge. */ + +#define sorg(edge, pointptr) \ + pointptr = (point) (edge).sh[2 + (edge).shorient] + +#define sdest(edge, pointptr) \ + pointptr = (point) (edge).sh[3 - (edge).shorient] + +#define setsorg(edge, pointptr) \ + (edge).sh[2 + (edge).shorient] = (shelle) pointptr + +#define setsdest(edge, pointptr) \ + (edge).sh[3 - (edge).shorient] = (shelle) pointptr + +/* These primitives read or set a shell marker. Shell markers are used to */ +/* hold user boundary information. */ + +#define mark(edge) (* (int *) ((edge).sh + 6)) + +#define setmark(edge, value) \ + * (int *) ((edge).sh + 6) = value + +/* Bond two shell edges together. */ + +#define sbond(edge1, edge2) \ + (edge1).sh[(edge1).shorient] = sencode(edge2); \ + (edge2).sh[(edge2).shorient] = sencode(edge1) + +/* Dissolve a shell edge bond (from one side). Note that the other shell */ +/* edge will still think it's connected to this shell edge. */ + +#define sdissolve(edge) \ + (edge).sh[(edge).shorient] = (shelle) dummysh + +/* Copy a shell edge. */ + +#define shellecopy(edge1, edge2) \ + (edge2).sh = (edge1).sh; \ + (edge2).shorient = (edge1).shorient + +/* Test for equality of shell edges. */ + +#define shelleequal(edge1, edge2) \ + (((edge1).sh == (edge2).sh) && \ + ((edge1).shorient == (edge2).shorient)) + +/********* Primitives for interacting triangles and shell edges *********/ +/* */ +/* */ + +/* tspivot() finds a shell edge abutting a triangle. */ + +#define tspivot(triedge, edge) \ + sptr = (shelle) (triedge).tri[6 + (triedge).orient]; \ + sdecode(sptr, edge) + +/* stpivot() finds a triangle abutting a shell edge. It requires that the */ +/* variable `ptr' of type `triangle' be defined. */ + +#define stpivot(edge, triedge) \ + ptr = (triangle) (edge).sh[4 + (edge).shorient]; \ + decode(ptr, triedge) + +/* Bond a triangle to a shell edge. */ + +#define tsbond(triedge, edge) \ + (triedge).tri[6 + (triedge).orient] = (triangle) sencode(edge); \ + (edge).sh[4 + (edge).shorient] = (shelle) encode(triedge) + +/* Dissolve a bond (from the triangle side). */ + +#define tsdissolve(triedge) \ + (triedge).tri[6 + (triedge).orient] = (triangle) dummysh + +/* Dissolve a bond (from the shell edge side). */ + +#define stdissolve(edge) \ + (edge).sh[4 + (edge).shorient] = (shelle) dummytri + +/********* Primitives for points *********/ +/* */ +/* */ + +#define pointmark(pt) ((int *) (pt))[pointmarkindex] + +#define setpointmark(pt, value) \ + ((int *) (pt))[pointmarkindex] = value + +#define point2tri(pt) ((triangle *) (pt))[point2triindex] + +#define setpoint2tri(pt, value) \ + ((triangle *) (pt))[point2triindex] = value + +/** **/ +/** **/ +/********* Mesh manipulation primitives end here *********/ + +/********* User interaction routines begin here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* syntax() Print list of command line switches. */ +/* */ +/*****************************************************************************/ + +#ifndef TRILIBRARY + +void syntax() +{ +#ifdef CDT_ONLY +#ifdef REDUCED + printf("triangle [-pAcevngBPNEIOXzo_lQVh] input_file\n"); +#else /* not REDUCED */ + printf("triangle [-pAcevngBPNEIOXzo_iFlCQVh] input_file\n"); +#endif /* not REDUCED */ +#else /* not CDT_ONLY */ +#ifdef REDUCED + printf("triangle [-prq__a__AcevngBPNEIOXzo_YS__lQVh] input_file\n"); +#else /* not REDUCED */ + printf("triangle [-prq__a__AcevngBPNEIOXzo_YS__iFlsCQVh] input_file\n"); +#endif /* not REDUCED */ +#endif /* not CDT_ONLY */ + + printf(" -p Triangulates a Planar Straight Line Graph (.poly file).\n"); +#ifndef CDT_ONLY + printf(" -r Refines a previously generated mesh.\n"); + printf( + " -q Quality mesh generation. A minimum angle may be specified.\n"); + printf(" -a Applies a maximum triangle area constraint.\n"); +#endif /* not CDT_ONLY */ + printf( + " -A Applies attributes to identify elements in certain regions.\n"); + printf(" -c Encloses the convex hull with segments.\n"); + printf(" -e Generates an edge list.\n"); + printf(" -v Generates a Voronoi diagram.\n"); + printf(" -n Generates a list of triangle neighbors.\n"); + printf(" -g Generates an .off file for Geomview.\n"); + printf(" -B Suppresses output of boundary information.\n"); + printf(" -P Suppresses output of .poly file.\n"); + printf(" -N Suppresses output of .node file.\n"); + printf(" -E Suppresses output of .ele file.\n"); + printf(" -I Suppresses mesh iteration numbers.\n"); + printf(" -O Ignores holes in .poly file.\n"); + printf(" -X Suppresses use of exact arithmetic.\n"); + printf(" -z Numbers all items starting from zero (rather than one).\n"); + printf(" -o2 Generates second-order subparametric elements.\n"); +#ifndef CDT_ONLY + printf(" -Y Suppresses boundary segment splitting.\n"); + printf(" -S Specifies maximum number of added Steiner points.\n"); +#endif /* not CDT_ONLY */ +#ifndef REDUCED + printf(" -i Uses incremental method, rather than divide-and-conquer.\n"); + printf(" -F Uses Fortune's sweepline algorithm, rather than d-and-c.\n"); +#endif /* not REDUCED */ + printf(" -l Uses vertical cuts only, rather than alternating cuts.\n"); +#ifndef REDUCED +#ifndef CDT_ONLY + printf( + " -s Force segments into mesh by splitting (instead of using CDT).\n"); +#endif /* not CDT_ONLY */ + printf(" -C Check consistency of final mesh.\n"); +#endif /* not REDUCED */ + printf(" -Q Quiet: No terminal output except errors.\n"); + printf(" -V Verbose: Detailed information on what I'm doing.\n"); + printf(" -h Help: Detailed instructions for Triangle.\n"); + exit(0); +} + +#endif /* not TRILIBRARY */ + +/*****************************************************************************/ +/* */ +/* info() Print out complete instructions. */ +/* */ +/*****************************************************************************/ + +#ifndef TRILIBRARY + +void info() +{ + printf("Triangle\n"); + printf( +"A Two-Dimensional Quality Mesh Generator and Delaunay Triangulator.\n"); + printf("Version 1.3\n\n"); + printf( +"Copyright 1996 Jonathan Richard Shewchuk (bugs/comments to jrs@cs.cmu.edu)\n" +); + printf("School of Computer Science / Carnegie Mellon University\n"); + printf("5000 Forbes Avenue / Pittsburgh, Pennsylvania 15213-3891\n"); + printf( +"Created as part of the Archimedes project (tools for parallel FEM).\n"); + printf( +"Supported in part by NSF Grant CMS-9318163 and an NSERC 1967 Scholarship.\n"); + printf("There is no warranty whatsoever. Use at your own risk.\n"); +#ifdef SINGLE + printf("This executable is compiled for single precision arithmetic.\n\n\n"); +#else /* not SINGLE */ + printf("This executable is compiled for double precision arithmetic.\n\n\n"); +#endif /* not SINGLE */ + printf( +"Triangle generates exact Delaunay triangulations, constrained Delaunay\n"); + printf( +"triangulations, and quality conforming Delaunay triangulations. The latter\n" +); + printf( +"can be generated with no small angles, and are thus suitable for finite\n"); + printf( +"element analysis. If no command line switches are specified, your .node\n"); + printf( +"input file will be read, and the Delaunay triangulation will be returned in\n" +); + printf(".node and .ele output files. The command syntax is:\n\n"); +#ifdef CDT_ONLY +#ifdef REDUCED + printf("triangle [-pAcevngBPNEIOXzo_lQVh] input_file\n\n"); +#else /* not REDUCED */ + printf("triangle [-pAcevngBPNEIOXzo_iFlCQVh] input_file\n\n"); +#endif /* not REDUCED */ +#else /* not CDT_ONLY */ +#ifdef REDUCED + printf("triangle [-prq__a__AcevngBPNEIOXzo_YS__lQVh] input_file\n\n"); +#else /* not REDUCED */ + printf("triangle [-prq__a__AcevngBPNEIOXzo_YS__iFlsCQVh] input_file\n\n"); +#endif /* not REDUCED */ +#endif /* not CDT_ONLY */ + printf( +"Underscores indicate that numbers may optionally follow certain switches;\n"); + printf( +"do not leave any space between a switch and its numeric parameter.\n"); + printf( +"input_file must be a file with extension .node, or extension .poly if the\n"); + printf( +"-p switch is used. If -r is used, you must supply .node and .ele files,\n"); + printf( +"and possibly a .poly file and .area file as well. The formats of these\n"); + printf("files are described below.\n\n"); + printf("Command Line Switches:\n\n"); + printf( +" -p Reads a Planar Straight Line Graph (.poly file), which can specify\n" +); + printf( +" points, segments, holes, and regional attributes and area\n"); + printf( +" constraints. Will generate a constrained Delaunay triangulation\n"); + printf( +" fitting the input; or, if -s, -q, or -a is used, a conforming\n"); + printf( +" Delaunay triangulation. If -p is not used, Triangle reads a .node\n" +); + printf(" file by default.\n"); + printf( +" -r Refines a previously generated mesh. The mesh is read from a .node\n" +); + printf( +" file and an .ele file. If -p is also used, a .poly file is read\n"); + printf( +" and used to constrain edges in the mesh. Further details on\n"); + printf(" refinement are given below.\n"); + printf( +" -q Quality mesh generation by Jim Ruppert's Delaunay refinement\n"); + printf( +" algorithm. Adds points to the mesh to ensure that no angles\n"); + printf( +" smaller than 20 degrees occur. An alternative minimum angle may be\n" +); + printf( +" specified after the `q'. If the minimum angle is 20.7 degrees or\n"); + printf( +" smaller, the triangulation algorithm is theoretically guaranteed to\n" +); + printf( +" terminate (assuming infinite precision arithmetic - Triangle may\n"); + printf( +" fail to terminate if you run out of precision). In practice, the\n"); + printf( +" algorithm often succeeds for minimum angles up to 33.8 degrees.\n"); + printf( +" For highly refined meshes, however, it may be necessary to reduce\n"); + printf( +" the minimum angle to well below 20 to avoid problems associated\n"); + printf( +" with insufficient floating-point precision. The specified angle\n"); + printf(" may include a decimal point.\n"); + printf( +" -a Imposes a maximum triangle area. If a number follows the `a', no\n"); + printf( +" triangle will be generated whose area is larger than that number.\n"); + printf( +" If no number is specified, an .area file (if -r is used) or .poly\n"); + printf( +" file (if -r is not used) specifies a number of maximum area\n"); + printf( +" constraints. An .area file contains a separate area constraint for\n" +); + printf( +" each triangle, and is useful for refining a finite element mesh\n"); + printf( +" based on a posteriori error estimates. A .poly file can optionally\n" +); + printf( +" contain an area constraint for each segment-bounded region, thereby\n" +); + printf( +" enforcing triangle densities in a first triangulation. You can\n"); + printf( +" impose both a fixed area constraint and a varying area constraint\n"); + printf( +" by invoking the -a switch twice, once with and once without a\n"); + printf( +" number following. Each area specified may include a decimal point.\n" +); + printf( +" -A Assigns an additional attribute to each triangle that identifies\n"); + printf( +" what segment-bounded region each triangle belongs to. Attributes\n"); + printf( +" are assigned to regions by the .poly file. If a region is not\n"); + printf( +" explicitly marked by the .poly file, triangles in that region are\n"); + printf( +" assigned an attribute of zero. The -A switch has an effect only\n"); + printf(" when the -p switch is used and the -r switch is not.\n"); + printf( +" -c Creates segments on the convex hull of the triangulation. If you\n"); + printf( +" are triangulating a point set, this switch causes a .poly file to\n"); + printf( +" be written, containing all edges in the convex hull. (By default,\n" +); + printf( +" a .poly file is written only if a .poly file is read.) If you are\n" +); + printf( +" triangulating a PSLG, this switch specifies that the interior of\n"); + printf( +" the convex hull of the PSLG should be triangulated. If you do not\n" +); + printf( +" use this switch when triangulating a PSLG, it is assumed that you\n"); + printf( +" have identified the region to be triangulated by surrounding it\n"); + printf( +" with segments of the input PSLG. Beware: if you are not careful,\n" +); + printf( +" this switch can cause the introduction of an extremely thin angle\n"); + printf( +" between a PSLG segment and a convex hull segment, which can cause\n"); + printf( +" overrefinement or failure if Triangle runs out of precision. If\n"); + printf( +" you are refining a mesh, the -c switch works differently; it\n"); + printf( +" generates the set of boundary edges of the mesh, rather than the\n"); + printf(" convex hull.\n"); + printf( +" -e Outputs (to an .edge file) a list of edges of the triangulation.\n"); + printf( +" -v Outputs the Voronoi diagram associated with the triangulation.\n"); + printf(" Does not attempt to detect degeneracies.\n"); + printf( +" -n Outputs (to a .neigh file) a list of triangles neighboring each\n"); + printf(" triangle.\n"); + printf( +" -g Outputs the mesh to an Object File Format (.off) file, suitable for\n" +); + printf(" viewing with the Geometry Center's Geomview package.\n"); + printf( +" -B No boundary markers in the output .node, .poly, and .edge output\n"); + printf( +" files. See the detailed discussion of boundary markers below.\n"); + printf( +" -P No output .poly file. Saves disk space, but you lose the ability\n"); + printf( +" to impose segment constraints on later refinements of the mesh.\n"); + printf(" -N No output .node file.\n"); + printf(" -E No output .ele file.\n"); + printf( +" -I No iteration numbers. Suppresses the output of .node and .poly\n"); + printf( +" files, so your input files won't be overwritten. (If your input is\n" +); + printf( +" a .poly file only, a .node file will be written.) Cannot be used\n"); + printf( +" with the -r switch, because that would overwrite your input .ele\n"); + printf( +" file. Shouldn't be used with the -s, -q, or -a switch if you are\n"); + printf( +" using a .node file for input, because no .node file will be\n"); + printf(" written, so there will be no record of any added points.\n"); + printf(" -O No holes. Ignores the holes in the .poly file.\n"); + printf( +" -X No exact arithmetic. Normally, Triangle uses exact floating-point\n" +); + printf( +" arithmetic for certain tests if it thinks the inexact tests are not\n" +); + printf( +" accurate enough. Exact arithmetic ensures the robustness of the\n"); + printf( +" triangulation algorithms, despite floating-point roundoff error.\n"); + printf( +" Disabling exact arithmetic with the -X switch will cause a small\n"); + printf( +" improvement in speed and create the possibility (albeit small) that\n" +); + printf( +" Triangle will fail to produce a valid mesh. Not recommended.\n"); + printf( +" -z Numbers all items starting from zero (rather than one). Note that\n" +); + printf( +" this switch is normally overrided by the value used to number the\n"); + printf( +" first point of the input .node or .poly file. However, this switch\n" +); + printf(" is useful when calling Triangle from another program.\n"); + printf( +" -o2 Generates second-order subparametric elements with six nodes each.\n" +); + printf( +" -Y No new points on the boundary. This switch is useful when the mesh\n" +); + printf( +" boundary must be preserved so that it conforms to some adjacent\n"); + printf( +" mesh. Be forewarned that you will probably sacrifice some of the\n"); + printf( +" quality of the mesh; Triangle will try, but the resulting mesh may\n" +); + printf( +" contain triangles of poor aspect ratio. Works well if all the\n"); + printf( +" boundary points are closely spaced. Specify this switch twice\n"); + printf( +" (`-YY') to prevent all segment splitting, including internal\n"); + printf(" boundaries.\n"); + printf( +" -S Specifies the maximum number of Steiner points (points that are not\n" +); + printf( +" in the input, but are added to meet the constraints of minimum\n"); + printf( +" angle and maximum area). The default is to allow an unlimited\n"); + printf( +" number. If you specify this switch with no number after it,\n"); + printf( +" the limit is set to zero. Triangle always adds points at segment\n"); + printf( +" intersections, even if it needs to use more points than the limit\n"); + printf( +" you set. When Triangle inserts segments by splitting (-s), it\n"); + printf( +" always adds enough points to ensure that all the segments appear in\n" +); + printf( +" the triangulation, again ignoring the limit. Be forewarned that\n"); + printf( +" the -S switch may result in a conforming triangulation that is not\n" +); + printf( +" truly Delaunay, because Triangle may be forced to stop adding\n"); + printf( +" points when the mesh is in a state where a segment is non-Delaunay\n" +); + printf( +" and needs to be split. If so, Triangle will print a warning.\n"); + printf( +" -i Uses an incremental rather than divide-and-conquer algorithm to\n"); + printf( +" form a Delaunay triangulation. Try it if the divide-and-conquer\n"); + printf(" algorithm fails.\n"); + printf( +" -F Uses Steven Fortune's sweepline algorithm to form a Delaunay\n"); + printf( +" triangulation. Warning: does not use exact arithmetic for all\n"); + printf(" calculations. An exact result is not guaranteed.\n"); + printf( +" -l Uses only vertical cuts in the divide-and-conquer algorithm. By\n"); + printf( +" default, Triangle uses alternating vertical and horizontal cuts,\n"); + printf( +" which usually improve the speed except with point sets that are\n"); + printf( +" small or short and wide. This switch is primarily of theoretical\n"); + printf(" interest.\n"); + printf( +" -s Specifies that segments should be forced into the triangulation by\n" +); + printf( +" recursively splitting them at their midpoints, rather than by\n"); + printf( +" generating a constrained Delaunay triangulation. Segment splitting\n" +); + printf( +" is true to Ruppert's original algorithm, but can create needlessly\n" +); + printf(" small triangles near external small features.\n"); + printf( +" -C Check the consistency of the final mesh. Uses exact arithmetic for\n" +); + printf( +" checking, even if the -X switch is used. Useful if you suspect\n"); + printf(" Triangle is buggy.\n"); + printf( +" -Q Quiet: Suppresses all explanation of what Triangle is doing, unless\n" +); + printf(" an error occurs.\n"); + printf( +" -V Verbose: Gives detailed information about what Triangle is doing.\n"); + printf( +" Add more `V's for increasing amount of detail. `-V' gives\n"); + printf( +" information on algorithmic progress and more detailed statistics.\n"); + printf( +" `-VV' gives point-by-point details, and will print so much that\n"); + printf( +" Triangle will run much more slowly. `-VVV' gives information only\n" +); + printf(" a debugger could love.\n"); + printf(" -h Help: Displays these instructions.\n"); + printf("\n"); + printf("Definitions:\n"); + printf("\n"); + printf( +" A Delaunay triangulation of a point set is a triangulation whose vertices\n" +); + printf( +" are the point set, having the property that no point in the point set\n"); + printf( +" falls in the interior of the circumcircle (circle that passes through all\n" +); + printf(" three vertices) of any triangle in the triangulation.\n\n"); + printf( +" A Voronoi diagram of a point set is a subdivision of the plane into\n"); + printf( +" polygonal regions (some of which may be infinite), where each region is\n"); + printf( +" the set of points in the plane that are closer to some input point than\n"); + printf( +" to any other input point. (The Voronoi diagram is the geometric dual of\n" +); + printf(" the Delaunay triangulation.)\n\n"); + printf( +" A Planar Straight Line Graph (PSLG) is a collection of points and\n"); + printf( +" segments. Segments are simply edges, whose endpoints are points in the\n"); + printf( +" PSLG. The file format for PSLGs (.poly files) is described below.\n"); + printf("\n"); + printf( +" A constrained Delaunay triangulation of a PSLG is similar to a Delaunay\n"); + printf( +" triangulation, but each PSLG segment is present as a single edge in the\n"); + printf( +" triangulation. (A constrained Delaunay triangulation is not truly a\n"); + printf(" Delaunay triangulation.)\n\n"); + printf( +" A conforming Delaunay triangulation of a PSLG is a true Delaunay\n"); + printf( +" triangulation in which each PSLG segment may have been subdivided into\n"); + printf( +" several edges by the insertion of additional points. These inserted\n"); + printf( +" points are necessary to allow the segments to exist in the mesh while\n"); + printf(" maintaining the Delaunay property.\n\n"); + printf("File Formats:\n\n"); + printf( +" All files may contain comments prefixed by the character '#'. Points,\n"); + printf( +" triangles, edges, holes, and maximum area constraints must be numbered\n"); + printf( +" consecutively, starting from either 1 or 0. Whichever you choose, all\n"); + printf( +" input files must be consistent; if the nodes are numbered from 1, so must\n" +); + printf( +" be all other objects. Triangle automatically detects your choice while\n"); + printf( +" reading the .node (or .poly) file. (When calling Triangle from another\n"); + printf( +" program, use the -z switch if you wish to number objects from zero.)\n"); + printf(" Examples of these file formats are given below.\n\n"); + printf(" .node files:\n"); + printf( +" First line: <# of points> <# of attributes>\n"); + printf( +" <# of boundary markers (0 or 1)>\n" +); + printf( +" Remaining lines: [attributes] [boundary marker]\n"); + printf("\n"); + printf( +" The attributes, which are typically floating-point values of physical\n"); + printf( +" quantities (such as mass or conductivity) associated with the nodes of\n" +); + printf( +" a finite element mesh, are copied unchanged to the output mesh. If -s,\n" +); + printf( +" -q, or -a is selected, each new Steiner point added to the mesh will\n"); + printf(" have attributes assigned to it by linear interpolation.\n\n"); + printf( +" If the fourth entry of the first line is `1', the last column of the\n"); + printf( +" remainder of the file is assumed to contain boundary markers. Boundary\n" +); + printf( +" markers are used to identify boundary points and points resting on PSLG\n" +); + printf( +" segments; a complete description appears in a section below. The .node\n" +); + printf( +" file produced by Triangle will contain boundary markers in the last\n"); + printf(" column unless they are suppressed by the -B switch.\n\n"); + printf(" .ele files:\n"); + printf( +" First line: <# of triangles> <# of attributes>\n"); + printf( +" Remaining lines: ... [attributes]\n" +); + printf("\n"); + printf( +" Points are indices into the corresponding .node file. The first three\n" +); + printf( +" points are the corners, and are listed in counterclockwise order around\n" +); + printf( +" each triangle. (The remaining points, if any, depend on the type of\n"); + printf( +" finite element used.) The attributes are just like those of .node\n"); + printf( +" files. Because there is no simple mapping from input to output\n"); + printf( +" triangles, an attempt is made to interpolate attributes, which may\n"); + printf( +" result in a good deal of diffusion of attributes among nearby triangles\n" +); + printf( +" as the triangulation is refined. Diffusion does not occur across\n"); + printf( +" segments, so attributes used to identify segment-bounded regions remain\n" +); + printf( +" intact. In output .ele files, all triangles have three points each\n"); + printf( +" unless the -o2 switch is used, in which case they have six, and the\n"); + printf( +" fourth, fifth, and sixth points lie on the midpoints of the edges\n"); + printf(" opposite the first, second, and third corners.\n\n"); + printf(" .poly files:\n"); + printf( +" First line: <# of points> <# of attributes>\n"); + printf( +" <# of boundary markers (0 or 1)>\n" +); + printf( +" Following lines: [attributes] [boundary marker]\n"); + printf(" One line: <# of segments> <# of boundary markers (0 or 1)>\n"); + printf( +" Following lines: [boundary marker]\n"); + printf(" One line: <# of holes>\n"); + printf(" Following lines: \n"); + printf( +" Optional line: <# of regional attributes and/or area constraints>\n"); + printf( +" Optional following lines: \n"); + printf("\n"); + printf( +" A .poly file represents a PSLG, as well as some additional information.\n" +); + printf( +" The first section lists all the points, and is identical to the format\n" +); + printf( +" of .node files. <# of points> may be set to zero to indicate that the\n" +); + printf( +" points are listed in a separate .node file; .poly files produced by\n"); + printf( +" Triangle always have this format. This has the advantage that a point\n" +); + printf( +" set may easily be triangulated with or without segments. (The same\n"); + printf( +" effect can be achieved, albeit using more disk space, by making a copy\n" +); + printf( +" of the .poly file with the extension .node; all sections of the file\n"); + printf(" but the first are ignored.)\n\n"); + printf( +" The second section lists the segments. Segments are edges whose\n"); + printf( +" presence in the triangulation is enforced. Each segment is specified\n"); + printf( +" by listing the indices of its two endpoints. This means that you must\n" +); + printf( +" include its endpoints in the point list. If -s, -q, and -a are not\n"); + printf( +" selected, Triangle will produce a constrained Delaunay triangulation,\n"); + printf( +" in which each segment appears as a single edge in the triangulation.\n"); + printf( +" If -q or -a is selected, Triangle will produce a conforming Delaunay\n"); + printf( +" triangulation, in which segments may be subdivided into smaller edges.\n" +); + printf(" Each segment, like each point, may have a boundary marker.\n\n"); + printf( +" The third section lists holes (and concavities, if -c is selected) in\n"); + printf( +" the triangulation. Holes are specified by identifying a point inside\n"); + printf( +" each hole. After the triangulation is formed, Triangle creates holes\n"); + printf( +" by eating triangles, spreading out from each hole point until its\n"); + printf( +" progress is blocked by PSLG segments; you must be careful to enclose\n"); + printf( +" each hole in segments, or your whole triangulation may be eaten away.\n"); + printf( +" If the two triangles abutting a segment are eaten, the segment itself\n"); + printf( +" is also eaten. Do not place a hole directly on a segment; if you do,\n"); + printf(" Triangle will choose one side of the segment arbitrarily.\n\n"); + printf( +" The optional fourth section lists regional attributes (to be assigned\n"); + printf( +" to all triangles in a region) and regional constraints on the maximum\n"); + printf( +" triangle area. Triangle will read this section only if the -A switch\n"); + printf( +" is used or the -a switch is used without a number following it, and the\n" +); + printf( +" -r switch is not used. Regional attributes and area constraints are\n"); + printf( +" propagated in the same manner as holes; you specify a point for each\n"); + printf( +" attribute and/or constraint, and the attribute and/or constraint will\n"); + printf( +" affect the whole region (bounded by segments) containing the point. If\n" +); + printf( +" two values are written on a line after the x and y coordinate, the\n"); + printf( +" former is assumed to be a regional attribute (but will only be applied\n" +); + printf( +" if the -A switch is selected), and the latter is assumed to be a\n"); + printf( +" regional area constraint (but will only be applied if the -a switch is\n" +); + printf( +" selected). You may also specify just one value after the coordinates,\n" +); + printf( +" which can serve as both an attribute and an area constraint, depending\n" +); + printf( +" on the choice of switches. If you are using the -A and -a switches\n"); + printf( +" simultaneously and wish to assign an attribute to some region without\n"); + printf(" imposing an area constraint, use a negative maximum area.\n\n"); + printf( +" When a triangulation is created from a .poly file, you must either\n"); + printf( +" enclose the entire region to be triangulated in PSLG segments, or\n"); + printf( +" use the -c switch, which encloses the convex hull of the input point\n"); + printf( +" set. If you do not use the -c switch, Triangle will eat all triangles\n" +); + printf( +" on the outer boundary that are not protected by segments; if you are\n"); + printf( +" not careful, your whole triangulation may be eaten away. If you do\n"); + printf( +" use the -c switch, you can still produce concavities by appropriate\n"); + printf(" placement of holes just inside the convex hull.\n\n"); + printf( +" An ideal PSLG has no intersecting segments, nor any points that lie\n"); + printf( +" upon segments (except, of course, the endpoints of each segment.) You\n" +); + printf( +" aren't required to make your .poly files ideal, but you should be aware\n" +); + printf( +" of what can go wrong. Segment intersections are relatively safe -\n"); + printf( +" Triangle will calculate the intersection points for you and add them to\n" +); + printf( +" the triangulation - as long as your machine's floating-point precision\n" +); + printf( +" doesn't become a problem. You are tempting the fates if you have three\n" +); + printf( +" segments that cross at the same location, and expect Triangle to figure\n" +); + printf( +" out where the intersection point is. Thanks to floating-point roundoff\n" +); + printf( +" error, Triangle will probably decide that the three segments intersect\n" +); + printf( +" at three different points, and you will find a minuscule triangle in\n"); + printf( +" your output - unless Triangle tries to refine the tiny triangle, uses\n"); + printf( +" up the last bit of machine precision, and fails to terminate at all.\n"); + printf( +" You're better off putting the intersection point in the input files,\n"); + printf( +" and manually breaking up each segment into two. Similarly, if you\n"); + printf( +" place a point at the middle of a segment, and hope that Triangle will\n"); + printf( +" break up the segment at that point, you might get lucky. On the other\n" +); + printf( +" hand, Triangle might decide that the point doesn't lie precisely on the\n" +); + printf( +" line, and you'll have a needle-sharp triangle in your output - or a lot\n" +); + printf(" of tiny triangles if you're generating a quality mesh.\n\n"); + printf( +" When Triangle reads a .poly file, it also writes a .poly file, which\n"); + printf( +" includes all edges that are part of input segments. If the -c switch\n"); + printf( +" is used, the output .poly file will also include all of the edges on\n"); + printf( +" the convex hull. Hence, the output .poly file is useful for finding\n"); + printf( +" edges associated with input segments and setting boundary conditions in\n" +); + printf( +" finite element simulations. More importantly, you will need it if you\n" +); + printf( +" plan to refine the output mesh, and don't want segments to be missing\n"); + printf(" in later triangulations.\n\n"); + printf(" .area files:\n"); + printf(" First line: <# of triangles>\n"); + printf(" Following lines: \n\n"); + printf( +" An .area file associates with each triangle a maximum area that is used\n" +); + printf( +" for mesh refinement. As with other file formats, every triangle must\n"); + printf( +" be represented, and they must be numbered consecutively. A triangle\n"); + printf( +" may be left unconstrained by assigning it a negative maximum area.\n"); + printf("\n"); + printf(" .edge files:\n"); + printf(" First line: <# of edges> <# of boundary markers (0 or 1)>\n"); + printf( +" Following lines: [boundary marker]\n"); + printf("\n"); + printf( +" Endpoints are indices into the corresponding .node file. Triangle can\n" +); + printf( +" produce .edge files (use the -e switch), but cannot read them. The\n"); + printf( +" optional column of boundary markers is suppressed by the -B switch.\n"); + printf("\n"); + printf( +" In Voronoi diagrams, one also finds a special kind of edge that is an\n"); + printf( +" infinite ray with only one endpoint. For these edges, a different\n"); + printf(" format is used:\n\n"); + printf(" -1 \n\n"); + printf( +" The `direction' is a floating-point vector that indicates the direction\n" +); + printf(" of the infinite ray.\n\n"); + printf(" .neigh files:\n"); + printf( +" First line: <# of triangles> <# of neighbors per triangle (always 3)>\n" +); + printf( +" Following lines: \n"); + printf("\n"); + printf( +" Neighbors are indices into the corresponding .ele file. An index of -1\n" +); + printf( +" indicates a mesh boundary, and therefore no neighbor. Triangle can\n"); + printf( +" produce .neigh files (use the -n switch), but cannot read them.\n"); + printf("\n"); + printf( +" The first neighbor of triangle i is opposite the first corner of\n"); + printf(" triangle i, and so on.\n\n"); + printf("Boundary Markers:\n\n"); + printf( +" Boundary markers are tags used mainly to identify which output points and\n" +); + printf( +" edges are associated with which PSLG segment, and to identify which\n"); + printf( +" points and edges occur on a boundary of the triangulation. A common use\n" +); + printf( +" is to determine where boundary conditions should be applied to a finite\n"); + printf( +" element mesh. You can prevent boundary markers from being written into\n"); + printf(" files produced by Triangle by using the -B switch.\n\n"); + printf( +" The boundary marker associated with each segment in an output .poly file\n" +); + printf(" or edge in an output .edge file is chosen as follows:\n"); + printf( +" - If an output edge is part or all of a PSLG segment with a nonzero\n"); + printf( +" boundary marker, then the edge is assigned the same marker.\n"); + printf( +" - Otherwise, if the edge occurs on a boundary of the triangulation\n"); + printf( +" (including boundaries of holes), then the edge is assigned the marker\n" +); + printf(" one (1).\n"); + printf(" - Otherwise, the edge is assigned the marker zero (0).\n"); + printf( +" The boundary marker associated with each point in an output .node file is\n" +); + printf(" chosen as follows:\n"); + printf( +" - If a point is assigned a nonzero boundary marker in the input file,\n"); + printf( +" then it is assigned the same marker in the output .node file.\n"); + printf( +" - Otherwise, if the point lies on a PSLG segment (including the\n"); + printf( +" segment's endpoints) with a nonzero boundary marker, then the point\n"); + printf( +" is assigned the same marker. If the point lies on several such\n"); + printf(" segments, one of the markers is chosen arbitrarily.\n"); + printf( +" - Otherwise, if the point occurs on a boundary of the triangulation,\n"); + printf(" then the point is assigned the marker one (1).\n"); + printf(" - Otherwise, the point is assigned the marker zero (0).\n"); + printf("\n"); + printf( +" If you want Triangle to determine for you which points and edges are on\n"); + printf( +" the boundary, assign them the boundary marker zero (or use no markers at\n" +); + printf( +" all) in your input files. Alternatively, you can mark some of them and\n"); + printf(" leave others marked zero, allowing Triangle to label them.\n\n"); + printf("Triangulation Iteration Numbers:\n\n"); + printf( +" Because Triangle can read and refine its own triangulations, input\n"); + printf( +" and output files have iteration numbers. For instance, Triangle might\n"); + printf( +" read the files mesh.3.node, mesh.3.ele, and mesh.3.poly, refine the\n"); + printf( +" triangulation, and output the files mesh.4.node, mesh.4.ele, and\n"); + printf(" mesh.4.poly. Files with no iteration number are treated as if\n"); + printf( +" their iteration number is zero; hence, Triangle might read the file\n"); + printf( +" points.node, triangulate it, and produce the files points.1.node and\n"); + printf(" points.1.ele.\n\n"); + printf( +" Iteration numbers allow you to create a sequence of successively finer\n"); + printf( +" meshes suitable for multigrid methods. They also allow you to produce a\n" +); + printf( +" sequence of meshes using error estimate-driven mesh refinement.\n"); + printf("\n"); + printf( +" If you're not using refinement or quality meshing, and you don't like\n"); + printf( +" iteration numbers, use the -I switch to disable them. This switch will\n"); + printf( +" also disable output of .node and .poly files to prevent your input files\n" +); + printf( +" from being overwritten. (If the input is a .poly file that contains its\n" +); + printf(" own points, a .node file will be written.)\n\n"); + printf("Examples of How to Use Triangle:\n\n"); + printf( +" `triangle dots' will read points from dots.node, and write their Delaunay\n" +); + printf( +" triangulation to dots.1.node and dots.1.ele. (dots.1.node will be\n"); + printf( +" identical to dots.node.) `triangle -I dots' writes the triangulation to\n" +); + printf( +" dots.ele instead. (No additional .node file is needed, so none is\n"); + printf(" written.)\n\n"); + printf( +" `triangle -pe object.1' will read a PSLG from object.1.poly (and possibly\n" +); + printf( +" object.1.node, if the points are omitted from object.1.poly) and write\n"); + printf(" their constrained Delaunay triangulation to object.2.node and\n"); + printf( +" object.2.ele. The segments will be copied to object.2.poly, and all\n"); + printf(" edges will be written to object.2.edge.\n\n"); + printf( +" `triangle -pq31.5a.1 object' will read a PSLG from object.poly (and\n"); + printf( +" possibly object.node), generate a mesh whose angles are all greater than\n" +); + printf( +" 31.5 degrees and whose triangles all have area smaller than 0.1, and\n"); + printf( +" write the mesh to object.1.node and object.1.ele. Each segment may have\n" +); + printf( +" been broken up into multiple edges; the resulting constrained edges are\n"); + printf(" written to object.1.poly.\n\n"); + printf( +" Here is a sample file `box.poly' describing a square with a square hole:\n" +); + printf("\n"); + printf( +" # A box with eight points in 2D, no attributes, one boundary marker.\n"); + printf(" 8 2 0 1\n"); + printf(" # Outer box has these vertices:\n"); + printf(" 1 0 0 0\n"); + printf(" 2 0 3 0\n"); + printf(" 3 3 0 0\n"); + printf(" 4 3 3 33 # A special marker for this point.\n"); + printf(" # Inner square has these vertices:\n"); + printf(" 5 1 1 0\n"); + printf(" 6 1 2 0\n"); + printf(" 7 2 1 0\n"); + printf(" 8 2 2 0\n"); + printf(" # Five segments with boundary markers.\n"); + printf(" 5 1\n"); + printf(" 1 1 2 5 # Left side of outer box.\n"); + printf(" 2 5 7 0 # Segments 2 through 5 enclose the hole.\n"); + printf(" 3 7 8 0\n"); + printf(" 4 8 6 10\n"); + printf(" 5 6 5 0\n"); + printf(" # One hole in the middle of the inner square.\n"); + printf(" 1\n"); + printf(" 1 1.5 1.5\n\n"); + printf( +" Note that some segments are missing from the outer square, so one must\n"); + printf( +" use the `-c' switch. After `triangle -pqc box.poly', here is the output\n" +); + printf( +" file `box.1.node', with twelve points. The last four points were added\n"); + printf( +" to meet the angle constraint. Points 1, 2, and 9 have markers from\n"); + printf( +" segment 1. Points 6 and 8 have markers from segment 4. All the other\n"); + printf( +" points but 4 have been marked to indicate that they lie on a boundary.\n"); + printf("\n"); + printf(" 12 2 0 1\n"); + printf(" 1 0 0 5\n"); + printf(" 2 0 3 5\n"); + printf(" 3 3 0 1\n"); + printf(" 4 3 3 33\n"); + printf(" 5 1 1 1\n"); + printf(" 6 1 2 10\n"); + printf(" 7 2 1 1\n"); + printf(" 8 2 2 10\n"); + printf(" 9 0 1.5 5\n"); + printf(" 10 1.5 0 1\n"); + printf(" 11 3 1.5 1\n"); + printf(" 12 1.5 3 1\n"); + printf(" # Generated by triangle -pqc box.poly\n\n"); + printf(" Here is the output file `box.1.ele', with twelve triangles.\n\n"); + printf(" 12 3 0\n"); + printf(" 1 5 6 9\n"); + printf(" 2 10 3 7\n"); + printf(" 3 6 8 12\n"); + printf(" 4 9 1 5\n"); + printf(" 5 6 2 9\n"); + printf(" 6 7 3 11\n"); + printf(" 7 11 4 8\n"); + printf(" 8 7 5 10\n"); + printf(" 9 12 2 6\n"); + printf(" 10 8 7 11\n"); + printf(" 11 5 1 10\n"); + printf(" 12 8 4 12\n"); + printf(" # Generated by triangle -pqc box.poly\n\n"); + printf( +" Here is the output file `box.1.poly'. Note that segments have been added\n" +); + printf( +" to represent the convex hull, and some segments have been split by newly\n" +); + printf( +" added points. Note also that <# of points> is set to zero to indicate\n"); + printf(" that the points should be read from the .node file.\n\n"); + printf(" 0 2 0 1\n"); + printf(" 12 1\n"); + printf(" 1 1 9 5\n"); + printf(" 2 5 7 1\n"); + printf(" 3 8 7 1\n"); + printf(" 4 6 8 10\n"); + printf(" 5 5 6 1\n"); + printf(" 6 3 10 1\n"); + printf(" 7 4 11 1\n"); + printf(" 8 2 12 1\n"); + printf(" 9 9 2 5\n"); + printf(" 10 10 1 1\n"); + printf(" 11 11 3 1\n"); + printf(" 12 12 4 1\n"); + printf(" 1\n"); + printf(" 1 1.5 1.5\n"); + printf(" # Generated by triangle -pqc box.poly\n\n"); + printf("Refinement and Area Constraints:\n\n"); + printf( +" The -r switch causes a mesh (.node and .ele files) to be read and\n"); + printf( +" refined. If the -p switch is also used, a .poly file is read and used to\n" +); + printf( +" specify edges that are constrained and cannot be eliminated (although\n"); + printf( +" they can be divided into smaller edges) by the refinement process.\n"); + printf("\n"); + printf( +" When you refine a mesh, you generally want to impose tighter quality\n"); + printf( +" constraints. One way to accomplish this is to use -q with a larger\n"); + printf( +" angle, or -a followed by a smaller area than you used to generate the\n"); + printf( +" mesh you are refining. Another way to do this is to create an .area\n"); + printf( +" file, which specifies a maximum area for each triangle, and use the -a\n"); + printf( +" switch (without a number following). Each triangle's area constraint is\n" +); + printf( +" applied to that triangle. Area constraints tend to diffuse as the mesh\n"); + printf( +" is refined, so if there are large variations in area constraint between\n"); + printf(" adjacent triangles, you may not get the results you want.\n\n"); + printf( +" If you are refining a mesh composed of linear (three-node) elements, the\n" +); + printf( +" output mesh will contain all the nodes present in the input mesh, in the\n" +); + printf( +" same order, with new nodes added at the end of the .node file. However,\n" +); + printf( +" there is no guarantee that each output element is contained in a single\n"); + printf( +" input element. Often, output elements will overlap two input elements,\n"); + printf( +" and input edges are not present in the output mesh. Hence, a sequence of\n" +); + printf( +" refined meshes will form a hierarchy of nodes, but not a hierarchy of\n"); + printf( +" elements. If you a refining a mesh of higher-order elements, the\n"); + printf( +" hierarchical property applies only to the nodes at the corners of an\n"); + printf(" element; other nodes may not be present in the refined mesh.\n\n"); + printf( +" It is important to understand that maximum area constraints in .poly\n"); + printf( +" files are handled differently from those in .area files. A maximum area\n" +); + printf( +" in a .poly file applies to the whole (segment-bounded) region in which a\n" +); + printf( +" point falls, whereas a maximum area in an .area file applies to only one\n" +); + printf( +" triangle. Area constraints in .poly files are used only when a mesh is\n"); + printf( +" first generated, whereas area constraints in .area files are used only to\n" +); + printf( +" refine an existing mesh, and are typically based on a posteriori error\n"); + printf( +" estimates resulting from a finite element simulation on that mesh.\n"); + printf("\n"); + printf( +" `triangle -rq25 object.1' will read object.1.node and object.1.ele, then\n" +); + printf( +" refine the triangulation to enforce a 25 degree minimum angle, and then\n"); + printf( +" write the refined triangulation to object.2.node and object.2.ele.\n"); + printf("\n"); + printf( +" `triangle -rpaa6.2 z.3' will read z.3.node, z.3.ele, z.3.poly, and\n"); + printf( +" z.3.area. After reconstructing the mesh and its segments, Triangle will\n" +); + printf( +" refine the mesh so that no triangle has area greater than 6.2, and\n"); + printf( +" furthermore the triangles satisfy the maximum area constraints in\n"); + printf( +" z.3.area. The output is written to z.4.node, z.4.ele, and z.4.poly.\n"); + printf("\n"); + printf( +" The sequence `triangle -qa1 x', `triangle -rqa.3 x.1', `triangle -rqa.1\n"); + printf( +" x.2' creates a sequence of successively finer meshes x.1, x.2, and x.3,\n"); + printf(" suitable for multigrid.\n\n"); + printf("Convex Hulls and Mesh Boundaries:\n\n"); + printf( +" If the input is a point set (rather than a PSLG), Triangle produces its\n"); + printf( +" convex hull as a by-product in the output .poly file if you use the -c\n"); + printf( +" switch. There are faster algorithms for finding a two-dimensional convex\n" +); + printf( +" hull than triangulation, of course, but this one comes for free. If the\n" +); + printf( +" input is an unconstrained mesh (you are using the -r switch but not the\n"); + printf( +" -p switch), Triangle produces a list of its boundary edges (including\n"); + printf(" hole boundaries) as a by-product if you use the -c switch.\n\n"); + printf("Voronoi Diagrams:\n\n"); + printf( +" The -v switch produces a Voronoi diagram, in files suffixed .v.node and\n"); + printf( +" .v.edge. For example, `triangle -v points' will read points.node,\n"); + printf( +" produce its Delaunay triangulation in points.1.node and points.1.ele,\n"); + printf( +" and produce its Voronoi diagram in points.1.v.node and points.1.v.edge.\n"); + printf( +" The .v.node file contains a list of all Voronoi vertices, and the .v.edge\n" +); + printf( +" file contains a list of all Voronoi edges, some of which may be infinite\n" +); + printf( +" rays. (The choice of filenames makes it easy to run the set of Voronoi\n"); + printf(" vertices through Triangle, if so desired.)\n\n"); + printf( +" This implementation does not use exact arithmetic to compute the Voronoi\n" +); + printf( +" vertices, and does not check whether neighboring vertices are identical.\n" +); + printf( +" Be forewarned that if the Delaunay triangulation is degenerate or\n"); + printf( +" near-degenerate, the Voronoi diagram may have duplicate points, crossing\n" +); + printf( +" edges, or infinite rays whose direction vector is zero. Also, if you\n"); + printf( +" generate a constrained (as opposed to conforming) Delaunay triangulation,\n" +); + printf( +" or if the triangulation has holes, the corresponding Voronoi diagram is\n"); + printf(" likely to have crossing edges and unlikely to make sense.\n\n"); + printf("Mesh Topology:\n\n"); + printf( +" You may wish to know which triangles are adjacent to a certain Delaunay\n"); + printf( +" edge in an .edge file, which Voronoi regions are adjacent to a certain\n"); + printf( +" Voronoi edge in a .v.edge file, or which Voronoi regions are adjacent to\n" +); + printf( +" each other. All of this information can be found by cross-referencing\n"); + printf( +" output files with the recollection that the Delaunay triangulation and\n"); + printf(" the Voronoi diagrams are planar duals.\n\n"); + printf( +" Specifically, edge i of an .edge file is the dual of Voronoi edge i of\n"); + printf( +" the corresponding .v.edge file, and is rotated 90 degrees counterclock-\n"); + printf( +" wise from the Voronoi edge. Triangle j of an .ele file is the dual of\n"); + printf( +" vertex j of the corresponding .v.node file; and Voronoi region k is the\n"); + printf(" dual of point k of the corresponding .node file.\n\n"); + printf( +" Hence, to find the triangles adjacent to a Delaunay edge, look at the\n"); + printf( +" vertices of the corresponding Voronoi edge; their dual triangles are on\n"); + printf( +" the left and right of the Delaunay edge, respectively. To find the\n"); + printf( +" Voronoi regions adjacent to a Voronoi edge, look at the endpoints of the\n" +); + printf( +" corresponding Delaunay edge; their dual regions are on the right and left\n" +); + printf( +" of the Voronoi edge, respectively. To find which Voronoi regions are\n"); + printf(" adjacent to each other, just read the list of Delaunay edges.\n"); + printf("\n"); + printf("Statistics:\n"); + printf("\n"); + printf( +" After generating a mesh, Triangle prints a count of the number of points,\n" +); + printf( +" triangles, edges, boundary edges, and segments in the output mesh. If\n"); + printf( +" you've forgotten the statistics for an existing mesh, the -rNEP switches\n" +); + printf( +" (or -rpNEP if you've got a .poly file for the existing mesh) will\n"); + printf(" regenerate these statistics without writing any output.\n\n"); + printf( +" The -V switch produces extended statistics, including a rough estimate\n"); + printf( +" of memory use and a histogram of triangle aspect ratios and angles in the\n" +); + printf(" mesh.\n\n"); + printf("Exact Arithmetic:\n\n"); + printf( +" Triangle uses adaptive exact arithmetic to perform what computational\n"); + printf( +" geometers call the `orientation' and `incircle' tests. If the floating-\n" +); + printf( +" point arithmetic of your machine conforms to the IEEE 754 standard (as\n"); + printf( +" most workstations do), and does not use extended precision internal\n"); + printf( +" registers, then your output is guaranteed to be an absolutely true\n"); + printf(" Delaunay or conforming Delaunay triangulation, roundoff error\n"); + printf( +" notwithstanding. The word `adaptive' implies that these arithmetic\n"); + printf( +" routines compute the result only to the precision necessary to guarantee\n" +); + printf( +" correctness, so they are usually nearly as fast as their approximate\n"); + printf( +" counterparts. The exact tests can be disabled with the -X switch. On\n"); + printf( +" most inputs, this switch will reduce the computation time by about eight\n" +); + printf( +" percent - it's not worth the risk. There are rare difficult inputs\n"); + printf( +" (having many collinear and cocircular points), however, for which the\n"); + printf( +" difference could be a factor of two. These are precisely the inputs most\n" +); + printf(" likely to cause errors if you use the -X switch.\n\n"); + printf( +" Unfortunately, these routines don't solve every numerical problem. Exact\n" +); + printf( +" arithmetic is not used to compute the positions of points, because the\n"); + printf( +" bit complexity of point coordinates would grow without bound. Hence,\n"); + printf( +" segment intersections aren't computed exactly; in very unusual cases,\n"); + printf( +" roundoff error in computing an intersection point might actually lead to\n" +); + printf( +" an inverted triangle and an invalid triangulation. (This is one reason\n"); + printf( +" to compute your own intersection points in your .poly files.) Similarly,\n" +); + printf( +" exact arithmetic is not used to compute the vertices of the Voronoi\n"); + printf(" diagram.\n\n"); + printf( +" Underflow and overflow can also cause difficulties; the exact arithmetic\n" +); + printf( +" routines do not ameliorate out-of-bounds exponents, which can arise\n"); + printf( +" during the orientation and incircle tests. As a rule of thumb, you\n"); + printf( +" should ensure that your input values are within a range such that their\n"); + printf( +" third powers can be taken without underflow or overflow. Underflow can\n"); + printf( +" silently prevent the tests from being performed exactly, while overflow\n"); + printf(" will typically cause a floating exception.\n\n"); + printf("Calling Triangle from Another Program:\n\n"); + printf(" Read the file triangle.h for details.\n\n"); + printf("Troubleshooting:\n\n"); + printf(" Please read this section before mailing me bugs.\n\n"); + printf(" `My output mesh has no triangles!'\n\n"); + printf( +" If you're using a PSLG, you've probably failed to specify a proper set\n" +); + printf( +" of bounding segments, or forgotten to use the -c switch. Or you may\n"); + printf( +" have placed a hole badly. To test these possibilities, try again with\n" +); + printf( +" the -c and -O switches. Alternatively, all your input points may be\n"); + printf( +" collinear, in which case you can hardly expect to triangulate them.\n"); + printf("\n"); + printf(" `Triangle doesn't terminate, or just crashes.'\n"); + printf("\n"); + printf( +" Bad things can happen when triangles get so small that the distance\n"); + printf( +" between their vertices isn't much larger than the precision of your\n"); + printf( +" machine's arithmetic. If you've compiled Triangle for single-precision\n" +); + printf( +" arithmetic, you might do better by recompiling it for double-precision.\n" +); + printf( +" Then again, you might just have to settle for more lenient constraints\n" +); + printf( +" on the minimum angle and the maximum area than you had planned.\n"); + printf("\n"); + printf( +" You can minimize precision problems by ensuring that the origin lies\n"); + printf( +" inside your point set, or even inside the densest part of your\n"); + printf( +" mesh. On the other hand, if you're triangulating an object whose x\n"); + printf( +" coordinates all fall between 6247133 and 6247134, you're not leaving\n"); + printf(" much floating-point precision for Triangle to work with.\n\n"); + printf( +" Precision problems can occur covertly if the input PSLG contains two\n"); + printf( +" segments that meet (or intersect) at a very small angle, or if such an\n" +); + printf( +" angle is introduced by the -c switch, which may occur if a point lies\n"); + printf( +" ever-so-slightly inside the convex hull, and is connected by a PSLG\n"); + printf( +" segment to a point on the convex hull. If you don't realize that a\n"); + printf( +" small angle is being formed, you might never discover why Triangle is\n"); + printf( +" crashing. To check for this possibility, use the -S switch (with an\n"); + printf( +" appropriate limit on the number of Steiner points, found by trial-and-\n" +); + printf( +" error) to stop Triangle early, and view the output .poly file with\n"); + printf( +" Show Me (described below). Look carefully for small angles between\n"); + printf( +" segments; zoom in closely, as such segments might look like a single\n"); + printf(" segment from a distance.\n\n"); + printf( +" If some of the input values are too large, Triangle may suffer a\n"); + printf( +" floating exception due to overflow when attempting to perform an\n"); + printf( +" orientation or incircle test. (Read the section on exact arithmetic\n"); + printf( +" above.) Again, I recommend compiling Triangle for double (rather\n"); + printf(" than single) precision arithmetic.\n\n"); + printf( +" `The numbering of the output points doesn't match the input points.'\n"); + printf("\n"); + printf( +" You may have eaten some of your input points with a hole, or by placing\n" +); + printf(" them outside the area enclosed by segments.\n\n"); + printf( +" `Triangle executes without incident, but when I look at the resulting\n"); + printf( +" mesh, it has overlapping triangles or other geometric inconsistencies.'\n"); + printf("\n"); + printf( +" If you select the -X switch, Triangle's divide-and-conquer Delaunay\n"); + printf( +" triangulation algorithm occasionally makes mistakes due to floating-\n"); + printf( +" point roundoff error. Although these errors are rare, don't use the -X\n" +); + printf(" switch. If you still have problems, please report the bug.\n"); + printf("\n"); + printf( +" Strange things can happen if you've taken liberties with your PSLG. Do\n"); + printf( +" you have a point lying in the middle of a segment? Triangle sometimes\n"); + printf( +" copes poorly with that sort of thing. Do you want to lay out a collinear\n" +); + printf( +" row of evenly spaced, segment-connected points? Have you simply defined\n" +); + printf( +" one long segment connecting the leftmost point to the rightmost point,\n"); + printf( +" and a bunch of points lying along it? This method occasionally works,\n"); + printf( +" especially with horizontal and vertical lines, but often it doesn't, and\n" +); + printf( +" you'll have to connect each adjacent pair of points with a separate\n"); + printf(" segment. If you don't like it, tough.\n\n"); + printf( +" Furthermore, if you have segments that intersect other than at their\n"); + printf( +" endpoints, try not to let the intersections fall extremely close to PSLG\n" +); + printf(" points or each other.\n\n"); + printf( +" If you have problems refining a triangulation not produced by Triangle:\n"); + printf( +" Are you sure the triangulation is geometrically valid? Is it formatted\n"); + printf( +" correctly for Triangle? Are the triangles all listed so the first three\n" +); + printf(" points are their corners in counterclockwise order?\n\n"); + printf("Show Me:\n\n"); + printf( +" Triangle comes with a separate program named `Show Me', whose primary\n"); + printf( +" purpose is to draw meshes on your screen or in PostScript. Its secondary\n" +); + printf( +" purpose is to check the validity of your input files, and do so more\n"); + printf( +" thoroughly than Triangle does. Show Me requires that you have the X\n"); + printf( +" Windows system. If you didn't receive Show Me with Triangle, complain to\n" +); + printf(" whomever you obtained Triangle from, then send me mail.\n\n"); + printf("Triangle on the Web:\n\n"); + printf( +" To see an illustrated, updated version of these instructions, check out\n"); + printf("\n"); + printf(" http://www.cs.cmu.edu/~quake/triangle.html\n"); + printf("\n"); + printf("A Brief Plea:\n"); + printf("\n"); + printf( +" If you use Triangle, and especially if you use it to accomplish real\n"); + printf( +" work, I would like very much to hear from you. A short letter or email\n"); + printf( +" (to jrs@cs.cmu.edu) describing how you use Triangle will mean a lot to\n"); + printf( +" me. The more people I know are using this program, the more easily I can\n" +); + printf( +" justify spending time on improvements and on the three-dimensional\n"); + printf( +" successor to Triangle, which in turn will benefit you. Also, I can put\n"); + printf( +" you on a list to receive email whenever a new version of Triangle is\n"); + printf(" available.\n\n"); + printf( +" If you use a mesh generated by Triangle in a publication, please include\n" +); + printf(" an acknowledgment as well.\n\n"); + printf("Research credit:\n\n"); + printf( +" Of course, I can take credit for only a fraction of the ideas that made\n"); + printf( +" this mesh generator possible. Triangle owes its existence to the efforts\n" +); + printf( +" of many fine computational geometers and other researchers, including\n"); + printf( +" Marshall Bern, L. Paul Chew, Boris Delaunay, Rex A. Dwyer, David\n"); + printf( +" Eppstein, Steven Fortune, Leonidas J. Guibas, Donald E. Knuth, C. L.\n"); + printf( +" Lawson, Der-Tsai Lee, Ernst P. Mucke, Douglas M. Priest, Jim Ruppert,\n"); + printf( +" Isaac Saias, Bruce J. Schachter, Micha Sharir, Jorge Stolfi, Christopher\n" +); + printf( +" J. Van Wyk, David F. Watson, and Binhai Zhu. See the comments at the\n"); + printf(" beginning of the source code for references.\n\n"); + exit(0); +} + +#endif /* not TRILIBRARY */ + +/*****************************************************************************/ +/* */ +/* internalerror() Ask the user to send me the defective product. Exit. */ +/* */ +/*****************************************************************************/ + +void internalerror() +{ + printf(" Please report this bug to jrs@cs.cmu.edu\n"); + printf(" Include the message above, your input data set, and the exact\n"); + printf(" command line you used to run Triangle.\n"); + exit(1); +} + +/*****************************************************************************/ +/* */ +/* parsecommandline() Read the command line, identify switches, and set */ +/* up options and file names. */ +/* */ +/* The effects of this routine are felt entirely through global variables. */ +/* */ +/*****************************************************************************/ + +void parsecommandline(argc, argv) +int argc; +char **argv; +{ +#ifdef TRILIBRARY +#define STARTINDEX 0 +#else /* not TRILIBRARY */ +#define STARTINDEX 1 + int increment; + int meshnumber; +#endif /* not TRILIBRARY */ + int i, j; +#ifndef CDT_ONLY + int k; + char workstring[FILENAMESIZE]; +#endif + + poly = refine = quality = vararea = fixedarea = regionattrib = convex = 0; + firstnumber = 1; + edgesout = voronoi = neighbors = geomview = 0; + nobound = nopolywritten = nonodewritten = noelewritten = noiterationnum = 0; + noholes = noexact = 0; + incremental = sweepline = 0; + dwyer = 1; + splitseg = 0; + docheck = 0; + nobisect = 0; + steiner = -1; + order = 1; + minangle = 0.0; + maxarea = -1.0; + quiet = verbose = 0; +#ifndef TRILIBRARY + innodefilename[0] = '\0'; +#endif /* not TRILIBRARY */ + + for (i = STARTINDEX; i < argc; i++) { +#ifndef TRILIBRARY + if (argv[i][0] == '-') { +#endif /* not TRILIBRARY */ + for (j = STARTINDEX; argv[i][j] != '\0'; j++) { + if (argv[i][j] == 'p') { + poly = 1; + } +#ifndef CDT_ONLY + if (argv[i][j] == 'r') { + refine = 1; + } + if (argv[i][j] == 'q') { + quality = 1; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + minangle = (REAL) strtod(workstring, (char **) NULL); + } else { + minangle = 20.0; + } + } + if (argv[i][j] == 'a') { + quality = 1; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + fixedarea = 1; + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + maxarea = (REAL) strtod(workstring, (char **) NULL); + if (maxarea <= 0.0) { + printf("Error: Maximum area must be greater than zero.\n"); + exit(1); + } + } else { + vararea = 1; + } + } +#endif /* not CDT_ONLY */ + if (argv[i][j] == 'A') { + regionattrib = 1; + } + if (argv[i][j] == 'c') { + convex = 1; + } + if (argv[i][j] == 'z') { + firstnumber = 0; + } + if (argv[i][j] == 'e') { + edgesout = 1; + } + if (argv[i][j] == 'v') { + voronoi = 1; + } + if (argv[i][j] == 'n') { + neighbors = 1; + } + if (argv[i][j] == 'g') { + geomview = 1; + } + if (argv[i][j] == 'B') { + nobound = 1; + } + if (argv[i][j] == 'P') { + nopolywritten = 1; + } + if (argv[i][j] == 'N') { + nonodewritten = 1; + } + if (argv[i][j] == 'E') { + noelewritten = 1; + } +#ifndef TRILIBRARY + if (argv[i][j] == 'I') { + noiterationnum = 1; + } +#endif /* not TRILIBRARY */ + if (argv[i][j] == 'O') { + noholes = 1; + } + if (argv[i][j] == 'X') { + noexact = 1; + } + if (argv[i][j] == 'o') { + if (argv[i][j + 1] == '2') { + j++; + order = 2; + } + } +#ifndef CDT_ONLY + if (argv[i][j] == 'Y') { + nobisect++; + } + if (argv[i][j] == 'S') { + steiner = 0; + while ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + j++; + steiner = steiner * 10 + (int) (argv[i][j] - '0'); + } + } +#endif /* not CDT_ONLY */ +#ifndef REDUCED + if (argv[i][j] == 'i') { + incremental = 1; + } + if (argv[i][j] == 'F') { + sweepline = 1; + } +#endif /* not REDUCED */ + if (argv[i][j] == 'l') { + dwyer = 0; + } +#ifndef REDUCED +#ifndef CDT_ONLY + if (argv[i][j] == 's') { + splitseg = 1; + } +#endif /* not CDT_ONLY */ + if (argv[i][j] == 'C') { + docheck = 1; + } +#endif /* not REDUCED */ + if (argv[i][j] == 'Q') { + quiet = 1; + } + if (argv[i][j] == 'V') { + verbose++; + } +#ifndef TRILIBRARY + if ((argv[i][j] == 'h') || (argv[i][j] == 'H') || + (argv[i][j] == '?')) { + info(); + } +#endif /* not TRILIBRARY */ + } +#ifndef TRILIBRARY + } else { + strncpy(innodefilename, argv[i], FILENAMESIZE - 1); + innodefilename[FILENAMESIZE - 1] = '\0'; + } +#endif /* not TRILIBRARY */ + } +#ifndef TRILIBRARY + if (innodefilename[0] == '\0') { + syntax(); + } + if (!strcmp(&innodefilename[strlen(innodefilename) - 5], ".node")) { + innodefilename[strlen(innodefilename) - 5] = '\0'; + } + if (!strcmp(&innodefilename[strlen(innodefilename) - 5], ".poly")) { + innodefilename[strlen(innodefilename) - 5] = '\0'; + poly = 1; + } +#ifndef CDT_ONLY + if (!strcmp(&innodefilename[strlen(innodefilename) - 4], ".ele")) { + innodefilename[strlen(innodefilename) - 4] = '\0'; + refine = 1; + } + if (!strcmp(&innodefilename[strlen(innodefilename) - 5], ".area")) { + innodefilename[strlen(innodefilename) - 5] = '\0'; + refine = 1; + quality = 1; + vararea = 1; + } +#endif /* not CDT_ONLY */ +#endif /* not TRILIBRARY */ + steinerleft = steiner; + useshelles = poly || refine || quality || convex; + goodangle = (REAL)cos(minangle * PI / 180.0); + goodangle *= goodangle; + if (refine && noiterationnum) { + printf( + "Error: You cannot use the -I switch when refining a triangulation.\n"); + exit(1); + } + /* Be careful not to allocate space for element area constraints that */ + /* will never be assigned any value (other than the default -1.0). */ + if (!refine && !poly) { + vararea = 0; + } + /* Be careful not to add an extra attribute to each element unless the */ + /* input supports it (PSLG in, but not refining a preexisting mesh). */ + if (refine || !poly) { + regionattrib = 0; + } + +#ifndef TRILIBRARY + strcpy(inpolyfilename, innodefilename); + strcpy(inelefilename, innodefilename); + strcpy(areafilename, innodefilename); + increment = 0; + strcpy(workstring, innodefilename); + j = 1; + while (workstring[j] != '\0') { + if ((workstring[j] == '.') && (workstring[j + 1] != '\0')) { + increment = j + 1; + } + j++; + } + meshnumber = 0; + if (increment > 0) { + j = increment; + do { + if ((workstring[j] >= '0') && (workstring[j] <= '9')) { + meshnumber = meshnumber * 10 + (int) (workstring[j] - '0'); + } else { + increment = 0; + } + j++; + } while (workstring[j] != '\0'); + } + if (noiterationnum) { + strcpy(outnodefilename, innodefilename); + strcpy(outelefilename, innodefilename); + strcpy(edgefilename, innodefilename); + strcpy(vnodefilename, innodefilename); + strcpy(vedgefilename, innodefilename); + strcpy(neighborfilename, innodefilename); + strcpy(offfilename, innodefilename); + strcat(outnodefilename, ".node"); + strcat(outelefilename, ".ele"); + strcat(edgefilename, ".edge"); + strcat(vnodefilename, ".v.node"); + strcat(vedgefilename, ".v.edge"); + strcat(neighborfilename, ".neigh"); + strcat(offfilename, ".off"); + } else if (increment == 0) { + strcpy(outnodefilename, innodefilename); + strcpy(outpolyfilename, innodefilename); + strcpy(outelefilename, innodefilename); + strcpy(edgefilename, innodefilename); + strcpy(vnodefilename, innodefilename); + strcpy(vedgefilename, innodefilename); + strcpy(neighborfilename, innodefilename); + strcpy(offfilename, innodefilename); + strcat(outnodefilename, ".1.node"); + strcat(outpolyfilename, ".1.poly"); + strcat(outelefilename, ".1.ele"); + strcat(edgefilename, ".1.edge"); + strcat(vnodefilename, ".1.v.node"); + strcat(vedgefilename, ".1.v.edge"); + strcat(neighborfilename, ".1.neigh"); + strcat(offfilename, ".1.off"); + } else { + workstring[increment] = '%'; + workstring[increment + 1] = 'd'; + workstring[increment + 2] = '\0'; + sprintf(outnodefilename, workstring, meshnumber + 1); + strcpy(outpolyfilename, outnodefilename); + strcpy(outelefilename, outnodefilename); + strcpy(edgefilename, outnodefilename); + strcpy(vnodefilename, outnodefilename); + strcpy(vedgefilename, outnodefilename); + strcpy(neighborfilename, outnodefilename); + strcpy(offfilename, outnodefilename); + strcat(outnodefilename, ".node"); + strcat(outpolyfilename, ".poly"); + strcat(outelefilename, ".ele"); + strcat(edgefilename, ".edge"); + strcat(vnodefilename, ".v.node"); + strcat(vedgefilename, ".v.edge"); + strcat(neighborfilename, ".neigh"); + strcat(offfilename, ".off"); + } + strcat(innodefilename, ".node"); + strcat(inpolyfilename, ".poly"); + strcat(inelefilename, ".ele"); + strcat(areafilename, ".area"); +#endif /* not TRILIBRARY */ +} + +/** **/ +/** **/ +/********* User interaction routines begin here *********/ + +/********* Debugging routines begin here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* printtriangle() Print out the details of a triangle/edge handle. */ +/* */ +/* I originally wrote this procedure to simplify debugging; it can be */ +/* called directly from the debugger, and presents information about a */ +/* triangle/edge handle in digestible form. It's also used when the */ +/* highest level of verbosity (`-VVV') is specified. */ +/* */ +/*****************************************************************************/ + +void printtriangle(t) +struct triedge *t; +{ + struct triedge printtri; + struct edge printsh; + point printpoint; + + printf("triangle x%lx with orientation %d:\n", (unsigned long) t->tri, + t->orient); + decode(t->tri[0], printtri); + if (printtri.tri == dummytri) { + printf(" [0] = Outer space\n"); + } else { + printf(" [0] = x%lx %d\n", (unsigned long) printtri.tri, + printtri.orient); + } + decode(t->tri[1], printtri); + if (printtri.tri == dummytri) { + printf(" [1] = Outer space\n"); + } else { + printf(" [1] = x%lx %d\n", (unsigned long) printtri.tri, + printtri.orient); + } + decode(t->tri[2], printtri); + if (printtri.tri == dummytri) { + printf(" [2] = Outer space\n"); + } else { + printf(" [2] = x%lx %d\n", (unsigned long) printtri.tri, + printtri.orient); + } + org(*t, printpoint); + if (printpoint == (point) NULL) + printf(" Origin[%d] = NULL\n", (t->orient + 1) % 3 + 3); + else + printf(" Origin[%d] = x%lx (%.12g, %.12g)\n", + (t->orient + 1) % 3 + 3, (unsigned long) printpoint, + printpoint[0], printpoint[1]); + dest(*t, printpoint); + if (printpoint == (point) NULL) + printf(" Dest [%d] = NULL\n", (t->orient + 2) % 3 + 3); + else + printf(" Dest [%d] = x%lx (%.12g, %.12g)\n", + (t->orient + 2) % 3 + 3, (unsigned long) printpoint, + printpoint[0], printpoint[1]); + apex(*t, printpoint); + if (printpoint == (point) NULL) + printf(" Apex [%d] = NULL\n", t->orient + 3); + else + printf(" Apex [%d] = x%lx (%.12g, %.12g)\n", + t->orient + 3, (unsigned long) printpoint, + printpoint[0], printpoint[1]); + if (useshelles) { + sdecode(t->tri[6], printsh); + if (printsh.sh != dummysh) { + printf(" [6] = x%lx %d\n", (unsigned long) printsh.sh, + printsh.shorient); + } + sdecode(t->tri[7], printsh); + if (printsh.sh != dummysh) { + printf(" [7] = x%lx %d\n", (unsigned long) printsh.sh, + printsh.shorient); + } + sdecode(t->tri[8], printsh); + if (printsh.sh != dummysh) { + printf(" [8] = x%lx %d\n", (unsigned long) printsh.sh, + printsh.shorient); + } + } + if (vararea) { + printf(" Area constraint: %.4g\n", areabound(*t)); + } +} + +/*****************************************************************************/ +/* */ +/* printshelle() Print out the details of a shell edge handle. */ +/* */ +/* I originally wrote this procedure to simplify debugging; it can be */ +/* called directly from the debugger, and presents information about a */ +/* shell edge handle in digestible form. It's also used when the highest */ +/* level of verbosity (`-VVV') is specified. */ +/* */ +/*****************************************************************************/ + +void printshelle(s) +struct edge *s; +{ + struct edge printsh; + struct triedge printtri; + point printpoint; + + printf("shell edge x%lx with orientation %d and mark %d:\n", + (unsigned long) s->sh, s->shorient, mark(*s)); + sdecode(s->sh[0], printsh); + if (printsh.sh == dummysh) { + printf(" [0] = No shell\n"); + } else { + printf(" [0] = x%lx %d\n", (unsigned long) printsh.sh, + printsh.shorient); + } + sdecode(s->sh[1], printsh); + if (printsh.sh == dummysh) { + printf(" [1] = No shell\n"); + } else { + printf(" [1] = x%lx %d\n", (unsigned long) printsh.sh, + printsh.shorient); + } + sorg(*s, printpoint); + if (printpoint == (point) NULL) + printf(" Origin[%d] = NULL\n", 2 + s->shorient); + else + printf(" Origin[%d] = x%lx (%.12g, %.12g)\n", + 2 + s->shorient, (unsigned long) printpoint, + printpoint[0], printpoint[1]); + sdest(*s, printpoint); + if (printpoint == (point) NULL) + printf(" Dest [%d] = NULL\n", 3 - s->shorient); + else + printf(" Dest [%d] = x%lx (%.12g, %.12g)\n", + 3 - s->shorient, (unsigned long) printpoint, + printpoint[0], printpoint[1]); + decode(s->sh[4], printtri); + if (printtri.tri == dummytri) { + printf(" [4] = Outer space\n"); + } else { + printf(" [4] = x%lx %d\n", (unsigned long) printtri.tri, + printtri.orient); + } + decode(s->sh[5], printtri); + if (printtri.tri == dummytri) { + printf(" [5] = Outer space\n"); + } else { + printf(" [5] = x%lx %d\n", (unsigned long) printtri.tri, + printtri.orient); + } +} + +/** **/ +/** **/ +/********* Debugging routines end here *********/ + +/********* Memory management routines begin here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* poolinit() Initialize a pool of memory for allocation of items. */ +/* */ +/* This routine initializes the machinery for allocating items. A `pool' */ +/* is created whose records have size at least `bytecount'. Items will be */ +/* allocated in `itemcount'-item blocks. Each item is assumed to be a */ +/* collection of words, and either pointers or floating-point values are */ +/* assumed to be the "primary" word type. (The "primary" word type is used */ +/* to determine alignment of items.) If `alignment' isn't zero, all items */ +/* will be `alignment'-byte aligned in memory. `alignment' must be either */ +/* a multiple or a factor of the primary word size; powers of two are safe. */ +/* `alignment' is normally used to create a few unused bits at the bottom */ +/* of each item's pointer, in which information may be stored. */ +/* */ +/* Don't change this routine unless you understand it. */ +/* */ +/*****************************************************************************/ + +void poolinit(pool, bytecount, itemcount, wtype, alignment) +struct memorypool *pool; +int bytecount; +int itemcount; +enum wordtype wtype; +int alignment; +{ + int wordsize; + + /* Initialize values in the pool. */ + pool->itemwordtype = wtype; + wordsize = (pool->itemwordtype == POINTER) ? sizeof(VOID *) : sizeof(REAL); + /* Find the proper alignment, which must be at least as large as: */ + /* - The parameter `alignment'. */ + /* - The primary word type, to avoid unaligned accesses. */ + /* - sizeof(VOID *), so the stack of dead items can be maintained */ + /* without unaligned accesses. */ + if (alignment > wordsize) { + pool->alignbytes = alignment; + } else { + pool->alignbytes = wordsize; + } + if (sizeof(VOID *) > pool->alignbytes) { + pool->alignbytes = sizeof(VOID *); + } + pool->itemwords = ((bytecount + pool->alignbytes - 1) / pool->alignbytes) + * (pool->alignbytes / wordsize); + pool->itembytes = pool->itemwords * wordsize; + pool->itemsperblock = itemcount; + + /* Allocate a block of items. Space for `itemsperblock' items and one */ + /* pointer (to point to the next block) are allocated, as well as space */ + /* to ensure alignment of the items. */ + pool->firstblock = (VOID **) malloc(pool->itemsperblock * pool->itembytes + + sizeof(VOID *) + pool->alignbytes); + if (pool->firstblock == (VOID **) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + /* Set the next block pointer to NULL. */ + *(pool->firstblock) = (VOID *) NULL; + poolrestart(pool); +} + +/*****************************************************************************/ +/* */ +/* poolrestart() Deallocate all items in a pool. */ +/* */ +/* The pool is returned to its starting state, except that no memory is */ +/* freed to the operating system. Rather, the previously allocated blocks */ +/* are ready to be reused. */ +/* */ +/*****************************************************************************/ + +void poolrestart(pool) +struct memorypool *pool; +{ + unsigned long alignptr; + + pool->items = 0; + pool->maxitems = 0; + + /* Set the currently active block. */ + pool->nowblock = pool->firstblock; + /* Find the first item in the pool. Increment by the size of (VOID *). */ + alignptr = (unsigned long) (pool->nowblock + 1); + /* Align the item on an `alignbytes'-byte boundary. */ + pool->nextitem = (VOID *) + (alignptr + (unsigned long) pool->alignbytes + - (alignptr % (unsigned long) pool->alignbytes)); + /* There are lots of unallocated items left in this block. */ + pool->unallocateditems = pool->itemsperblock; + /* The stack of deallocated items is empty. */ + pool->deaditemstack = (VOID *) NULL; +} + +/*****************************************************************************/ +/* */ +/* pooldeinit() Free to the operating system all memory taken by a pool. */ +/* */ +/*****************************************************************************/ + +void pooldeinit(pool) +struct memorypool *pool; +{ + while (pool->firstblock != (VOID **) NULL) { + pool->nowblock = (VOID **) *(pool->firstblock); + free(pool->firstblock); + pool->firstblock = pool->nowblock; + } +} + +/*****************************************************************************/ +/* */ +/* poolalloc() Allocate space for an item. */ +/* */ +/*****************************************************************************/ + +VOID *poolalloc(pool) +struct memorypool *pool; +{ + VOID *newitem; + VOID **newblock; + unsigned long alignptr; + + /* First check the linked list of dead items. If the list is not */ + /* empty, allocate an item from the list rather than a fresh one. */ + if (pool->deaditemstack != (VOID *) NULL) { + newitem = pool->deaditemstack; /* Take first item in list. */ + pool->deaditemstack = * (VOID **) pool->deaditemstack; + } else { + /* Check if there are any free items left in the current block. */ + if (pool->unallocateditems == 0) { + /* Check if another block must be allocated. */ + if (*(pool->nowblock) == (VOID *) NULL) { + /* Allocate a new block of items, pointed to by the previous block. */ + newblock = (VOID **) malloc(pool->itemsperblock * pool->itembytes + + sizeof(VOID *) + pool->alignbytes); + if (newblock == (VOID **) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + *(pool->nowblock) = (VOID *) newblock; + /* The next block pointer is NULL. */ + *newblock = (VOID *) NULL; + } + /* Move to the new block. */ + pool->nowblock = (VOID **) *(pool->nowblock); + /* Find the first item in the block. */ + /* Increment by the size of (VOID *). */ + alignptr = (unsigned long) (pool->nowblock + 1); + /* Align the item on an `alignbytes'-byte boundary. */ + pool->nextitem = (VOID *) + (alignptr + (unsigned long) pool->alignbytes + - (alignptr % (unsigned long) pool->alignbytes)); + /* There are lots of unallocated items left in this block. */ + pool->unallocateditems = pool->itemsperblock; + } + /* Allocate a new item. */ + newitem = pool->nextitem; + /* Advance `nextitem' pointer to next free item in block. */ + if (pool->itemwordtype == POINTER) { + pool->nextitem = (VOID *) ((VOID **) pool->nextitem + pool->itemwords); + } else { + pool->nextitem = (VOID *) ((REAL *) pool->nextitem + pool->itemwords); + } + pool->unallocateditems--; + pool->maxitems++; + } + pool->items++; + return newitem; +} + +/*****************************************************************************/ +/* */ +/* pooldealloc() Deallocate space for an item. */ +/* */ +/* The deallocated space is stored in a queue for later reuse. */ +/* */ +/*****************************************************************************/ + +void pooldealloc(pool, dyingitem) +struct memorypool *pool; +VOID *dyingitem; +{ + /* Push freshly killed item onto stack. */ + *((VOID **) dyingitem) = pool->deaditemstack; + pool->deaditemstack = dyingitem; + pool->items--; +} + +/*****************************************************************************/ +/* */ +/* traversalinit() Prepare to traverse the entire list of items. */ +/* */ +/* This routine is used in conjunction with traverse(). */ +/* */ +/*****************************************************************************/ + +void traversalinit(pool) +struct memorypool *pool; +{ + unsigned long alignptr; + + /* Begin the traversal in the first block. */ + pool->pathblock = pool->firstblock; + /* Find the first item in the block. Increment by the size of (VOID *). */ + alignptr = (unsigned long) (pool->pathblock + 1); + /* Align with item on an `alignbytes'-byte boundary. */ + pool->pathitem = (VOID *) + (alignptr + (unsigned long) pool->alignbytes + - (alignptr % (unsigned long) pool->alignbytes)); + /* Set the number of items left in the current block. */ + pool->pathitemsleft = pool->itemsperblock; +} + +/*****************************************************************************/ +/* */ +/* traverse() Find the next item in the list. */ +/* */ +/* This routine is used in conjunction with traversalinit(). Be forewarned */ +/* that this routine successively returns all items in the list, including */ +/* deallocated ones on the deaditemqueue. It's up to you to figure out */ +/* which ones are actually dead. Why? I don't want to allocate extra */ +/* space just to demarcate dead items. It can usually be done more */ +/* space-efficiently by a routine that knows something about the structure */ +/* of the item. */ +/* */ +/*****************************************************************************/ + +VOID *traverse(pool) +struct memorypool *pool; +{ + VOID *newitem; + unsigned long alignptr; + + /* Stop upon exhausting the list of items. */ + if (pool->pathitem == pool->nextitem) { + return (VOID *) NULL; + } + /* Check whether any untraversed items remain in the current block. */ + if (pool->pathitemsleft == 0) { + /* Find the next block. */ + pool->pathblock = (VOID **) *(pool->pathblock); + /* Find the first item in the block. Increment by the size of (VOID *). */ + alignptr = (unsigned long) (pool->pathblock + 1); + /* Align with item on an `alignbytes'-byte boundary. */ + pool->pathitem = (VOID *) + (alignptr + (unsigned long) pool->alignbytes + - (alignptr % (unsigned long) pool->alignbytes)); + /* Set the number of items left in the current block. */ + pool->pathitemsleft = pool->itemsperblock; + } + newitem = pool->pathitem; + /* Find the next item in the block. */ + if (pool->itemwordtype == POINTER) { + pool->pathitem = (VOID *) ((VOID **) pool->pathitem + pool->itemwords); + } else { + pool->pathitem = (VOID *) ((REAL *) pool->pathitem + pool->itemwords); + } + pool->pathitemsleft--; + return newitem; +} + +/*****************************************************************************/ +/* */ +/* dummyinit() Initialize the triangle that fills "outer space" and the */ +/* omnipresent shell edge. */ +/* */ +/* The triangle that fills "outer space", called `dummytri', is pointed to */ +/* by every triangle and shell edge on a boundary (be it outer or inner) of */ +/* the triangulation. Also, `dummytri' points to one of the triangles on */ +/* the convex hull (until the holes and concavities are carved), making it */ +/* possible to find a starting triangle for point location. */ +/* */ +/* The omnipresent shell edge, `dummysh', is pointed to by every triangle */ +/* or shell edge that doesn't have a full complement of real shell edges */ +/* to point to. */ +/* */ +/*****************************************************************************/ + +void dummyinit(trianglewords, shellewords) +int trianglewords; +int shellewords; +{ + unsigned long alignptr; + + /* `triwords' and `shwords' are used by the mesh manipulation primitives */ + /* to extract orientations of triangles and shell edges from pointers. */ + triwords = trianglewords; /* Initialize `triwords' once and for all. */ + shwords = shellewords; /* Initialize `shwords' once and for all. */ + + /* Set up `dummytri', the `triangle' that occupies "outer space". */ + dummytribase = (triangle *) malloc(triwords * sizeof(triangle) + + triangles.alignbytes); + if (dummytribase == (triangle *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + /* Align `dummytri' on a `triangles.alignbytes'-byte boundary. */ + alignptr = (unsigned long) dummytribase; + dummytri = (triangle *) + (alignptr + (unsigned long) triangles.alignbytes + - (alignptr % (unsigned long) triangles.alignbytes)); + /* Initialize the three adjoining triangles to be "outer space". These */ + /* will eventually be changed by various bonding operations, but their */ + /* values don't really matter, as long as they can legally be */ + /* dereferenced. */ + dummytri[0] = (triangle) dummytri; + dummytri[1] = (triangle) dummytri; + dummytri[2] = (triangle) dummytri; + /* Three NULL vertex points. */ + dummytri[3] = (triangle) NULL; + dummytri[4] = (triangle) NULL; + dummytri[5] = (triangle) NULL; + + if (useshelles) { + /* Set up `dummysh', the omnipresent "shell edge" pointed to by any */ + /* triangle side or shell edge end that isn't attached to a real shell */ + /* edge. */ + dummyshbase = (shelle *) malloc(shwords * sizeof(shelle) + + shelles.alignbytes); + if (dummyshbase == (shelle *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + /* Align `dummysh' on a `shelles.alignbytes'-byte boundary. */ + alignptr = (unsigned long) dummyshbase; + dummysh = (shelle *) + (alignptr + (unsigned long) shelles.alignbytes + - (alignptr % (unsigned long) shelles.alignbytes)); + /* Initialize the two adjoining shell edges to be the omnipresent shell */ + /* edge. These will eventually be changed by various bonding */ + /* operations, but their values don't really matter, as long as they */ + /* can legally be dereferenced. */ + dummysh[0] = (shelle) dummysh; + dummysh[1] = (shelle) dummysh; + /* Two NULL vertex points. */ + dummysh[2] = (shelle) NULL; + dummysh[3] = (shelle) NULL; + /* Initialize the two adjoining triangles to be "outer space". */ + dummysh[4] = (shelle) dummytri; + dummysh[5] = (shelle) dummytri; + /* Set the boundary marker to zero. */ + * (int *) (dummysh + 6) = 0; + + /* Initialize the three adjoining shell edges of `dummytri' to be */ + /* the omnipresent shell edge. */ + dummytri[6] = (triangle) dummysh; + dummytri[7] = (triangle) dummysh; + dummytri[8] = (triangle) dummysh; + } +} + +/*****************************************************************************/ +/* */ +/* initializepointpool() Calculate the size of the point data structure */ +/* and initialize its memory pool. */ +/* */ +/* This routine also computes the `pointmarkindex' and `point2triindex' */ +/* indices used to find values within each point. */ +/* */ +/*****************************************************************************/ + +void initializepointpool() +{ + int pointsize; + + /* The index within each point at which the boundary marker is found. */ + /* Ensure the point marker is aligned to a sizeof(int)-byte address. */ + pointmarkindex = ((mesh_dim + nextras) * sizeof(REAL) + sizeof(int) - 1) + / sizeof(int); + pointsize = (pointmarkindex + 1) * sizeof(int); + if (poly) { + /* The index within each point at which a triangle pointer is found. */ + /* Ensure the pointer is aligned to a sizeof(triangle)-byte address. */ + point2triindex = (pointsize + sizeof(triangle) - 1) / sizeof(triangle); + pointsize = (point2triindex + 1) * sizeof(triangle); + } + /* Initialize the pool of points. */ + poolinit(&points, pointsize, POINTPERBLOCK, + (sizeof(REAL) >= sizeof(triangle)) ? FLOATINGPOINT : POINTER, 0); +} + +/*****************************************************************************/ +/* */ +/* initializetrisegpools() Calculate the sizes of the triangle and shell */ +/* edge data structures and initialize their */ +/* memory pools. */ +/* */ +/* This routine also computes the `highorderindex', `elemattribindex', and */ +/* `areaboundindex' indices used to find values within each triangle. */ +/* */ +/*****************************************************************************/ + +void initializetrisegpools() +{ + int trisize; + + /* The index within each triangle at which the extra nodes (above three) */ + /* associated with high order elements are found. There are three */ + /* pointers to other triangles, three pointers to corners, and possibly */ + /* three pointers to shell edges before the extra nodes. */ + highorderindex = 6 + (useshelles * 3); + /* The number of bytes occupied by a triangle. */ + trisize = ((order + 1) * (order + 2) / 2 + (highorderindex - 3)) * + sizeof(triangle); + /* The index within each triangle at which its attributes are found, */ + /* where the index is measured in REALs. */ + elemattribindex = (trisize + sizeof(REAL) - 1) / sizeof(REAL); + /* The index within each triangle at which the maximum area constraint */ + /* is found, where the index is measured in REALs. Note that if the */ + /* `regionattrib' flag is set, an additional attribute will be added. */ + areaboundindex = elemattribindex + eextras + regionattrib; + /* If triangle attributes or an area bound are needed, increase the number */ + /* of bytes occupied by a triangle. */ + if (vararea) { + trisize = (areaboundindex + 1) * sizeof(REAL); + } else if (eextras + regionattrib > 0) { + trisize = areaboundindex * sizeof(REAL); + } + /* If a Voronoi diagram or triangle neighbor graph is requested, make */ + /* sure there's room to store an integer index in each triangle. This */ + /* integer index can occupy the same space as the shell edges or */ + /* attributes or area constraint or extra nodes. */ + if ((voronoi || neighbors) && + (trisize < 6 * sizeof(triangle) + sizeof(int))) { + trisize = 6 * sizeof(triangle) + sizeof(int); + } + /* Having determined the memory size of a triangle, initialize the pool. */ + poolinit(&triangles, trisize, TRIPERBLOCK, POINTER, 4); + + if (useshelles) { + /* Initialize the pool of shell edges. */ + poolinit(&shelles, 6 * sizeof(triangle) + sizeof(int), SHELLEPERBLOCK, + POINTER, 4); + + /* Initialize the "outer space" triangle and omnipresent shell edge. */ + dummyinit(triangles.itemwords, shelles.itemwords); + } else { + /* Initialize the "outer space" triangle. */ + dummyinit(triangles.itemwords, 0); + } +} + +/*****************************************************************************/ +/* */ +/* triangledealloc() Deallocate space for a triangle, marking it dead. */ +/* */ +/*****************************************************************************/ + +void triangledealloc(dyingtriangle) +triangle *dyingtriangle; +{ + /* Set triangle's vertices to NULL. This makes it possible to */ + /* detect dead triangles when traversing the list of all triangles. */ + dyingtriangle[3] = (triangle) NULL; + dyingtriangle[4] = (triangle) NULL; + dyingtriangle[5] = (triangle) NULL; + pooldealloc(&triangles, (VOID *) dyingtriangle); +} + +/*****************************************************************************/ +/* */ +/* triangletraverse() Traverse the triangles, skipping dead ones. */ +/* */ +/*****************************************************************************/ + +triangle *triangletraverse() +{ + triangle *newtriangle; + + do { + newtriangle = (triangle *) traverse(&triangles); + if (newtriangle == (triangle *) NULL) { + return (triangle *) NULL; + } + } while (newtriangle[3] == (triangle) NULL); /* Skip dead ones. */ + return newtriangle; +} + +/*****************************************************************************/ +/* */ +/* shelledealloc() Deallocate space for a shell edge, marking it dead. */ +/* */ +/*****************************************************************************/ + +void shelledealloc(dyingshelle) +shelle *dyingshelle; +{ + /* Set shell edge's vertices to NULL. This makes it possible to */ + /* detect dead shells when traversing the list of all shells. */ + dyingshelle[2] = (shelle) NULL; + dyingshelle[3] = (shelle) NULL; + pooldealloc(&shelles, (VOID *) dyingshelle); +} + +/*****************************************************************************/ +/* */ +/* shelletraverse() Traverse the shell edges, skipping dead ones. */ +/* */ +/*****************************************************************************/ + +shelle *shelletraverse() +{ + shelle *newshelle; + + do { + newshelle = (shelle *) traverse(&shelles); + if (newshelle == (shelle *) NULL) { + return (shelle *) NULL; + } + } while (newshelle[2] == (shelle) NULL); /* Skip dead ones. */ + return newshelle; +} + +/*****************************************************************************/ +/* */ +/* pointdealloc() Deallocate space for a point, marking it dead. */ +/* */ +/*****************************************************************************/ + +void pointdealloc(dyingpoint) +point dyingpoint; +{ + /* Mark the point as dead. This makes it possible to detect dead points */ + /* when traversing the list of all points. */ + setpointmark(dyingpoint, DEADPOINT); + pooldealloc(&points, (VOID *) dyingpoint); +} + +/*****************************************************************************/ +/* */ +/* pointtraverse() Traverse the points, skipping dead ones. */ +/* */ +/*****************************************************************************/ + +point pointtraverse() +{ + point newpoint; + + do { + newpoint = (point) traverse(&points); + if (newpoint == (point) NULL) { + return (point) NULL; + } + } while (pointmark(newpoint) == DEADPOINT); /* Skip dead ones. */ + return newpoint; +} + +/*****************************************************************************/ +/* */ +/* badsegmentdealloc() Deallocate space for a bad segment, marking it */ +/* dead. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +void badsegmentdealloc(dyingseg) +struct edge *dyingseg; +{ + /* Set segment's orientation to -1. This makes it possible to */ + /* detect dead segments when traversing the list of all segments. */ + dyingseg->shorient = -1; + pooldealloc(&badsegments, (VOID *) dyingseg); +} + +#endif /* not CDT_ONLY */ + +/*****************************************************************************/ +/* */ +/* badsegmenttraverse() Traverse the bad segments, skipping dead ones. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +struct edge *badsegmenttraverse() +{ + struct edge *newseg; + + do { + newseg = (struct edge *) traverse(&badsegments); + if (newseg == (struct edge *) NULL) { + return (struct edge *) NULL; + } + } while (newseg->shorient == -1); /* Skip dead ones. */ + return newseg; +} + +#endif /* not CDT_ONLY */ + +/*****************************************************************************/ +/* */ +/* getpoint() Get a specific point, by number, from the list. */ +/* */ +/* The first point is number 'firstnumber'. */ +/* */ +/* Note that this takes O(n) time (with a small constant, if POINTPERBLOCK */ +/* is large). I don't care to take the trouble to make it work in constant */ +/* time. */ +/* */ +/*****************************************************************************/ + +point getpoint(number) +int number; +{ + VOID **getblock; + point foundpoint; + unsigned long alignptr; + int current; + + getblock = points.firstblock; + current = firstnumber; + /* Find the right block. */ + while (current + points.itemsperblock <= number) { + getblock = (VOID **) *getblock; + current += points.itemsperblock; + } + /* Now find the right point. */ + alignptr = (unsigned long) (getblock + 1); + foundpoint = (point) (alignptr + (unsigned long) points.alignbytes + - (alignptr % (unsigned long) points.alignbytes)); + while (current < number) { + foundpoint += points.itemwords; + current++; + } + return foundpoint; +} + +/*****************************************************************************/ +/* */ +/* triangledeinit() Free all remaining allocated memory. */ +/* */ +/*****************************************************************************/ + +void triangledeinit() +{ + pooldeinit(&triangles); + free(dummytribase); + if (useshelles) { + pooldeinit(&shelles); + free(dummyshbase); + } + pooldeinit(&points); +#ifndef CDT_ONLY + if (quality) { + pooldeinit(&badsegments); + if ((minangle > 0.0) || vararea || fixedarea) { + pooldeinit(&badtriangles); + } + } +#endif /* not CDT_ONLY */ +} + +/** **/ +/** **/ +/********* Memory management routines end here *********/ + +/********* Constructors begin here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* maketriangle() Create a new triangle with orientation zero. */ +/* */ +/*****************************************************************************/ + +void maketriangle(newtriedge) +struct triedge *newtriedge; +{ + int i; + + newtriedge->tri = (triangle *) poolalloc(&triangles); + /* Initialize the three adjoining triangles to be "outer space". */ + newtriedge->tri[0] = (triangle) dummytri; + newtriedge->tri[1] = (triangle) dummytri; + newtriedge->tri[2] = (triangle) dummytri; + /* Three NULL vertex points. */ + newtriedge->tri[3] = (triangle) NULL; + newtriedge->tri[4] = (triangle) NULL; + newtriedge->tri[5] = (triangle) NULL; + /* Initialize the three adjoining shell edges to be the omnipresent */ + /* shell edge. */ + if (useshelles) { + newtriedge->tri[6] = (triangle) dummysh; + newtriedge->tri[7] = (triangle) dummysh; + newtriedge->tri[8] = (triangle) dummysh; + } + for (i = 0; i < eextras; i++) { + setelemattribute(*newtriedge, i, 0.0); + } + if (vararea) { + setareabound(*newtriedge, -1.0); + } + + newtriedge->orient = 0; +} + +/*****************************************************************************/ +/* */ +/* makeshelle() Create a new shell edge with orientation zero. */ +/* */ +/*****************************************************************************/ + +void makeshelle(newedge) +struct edge *newedge; +{ + newedge->sh = (shelle *) poolalloc(&shelles); + /* Initialize the two adjoining shell edges to be the omnipresent */ + /* shell edge. */ + newedge->sh[0] = (shelle) dummysh; + newedge->sh[1] = (shelle) dummysh; + /* Two NULL vertex points. */ + newedge->sh[2] = (shelle) NULL; + newedge->sh[3] = (shelle) NULL; + /* Initialize the two adjoining triangles to be "outer space". */ + newedge->sh[4] = (shelle) dummytri; + newedge->sh[5] = (shelle) dummytri; + /* Set the boundary marker to zero. */ + setmark(*newedge, 0); + + newedge->shorient = 0; +} + +/** **/ +/** **/ +/********* Constructors end here *********/ + +/********* Determinant evaluation routines begin here *********/ +/** **/ +/** **/ + +/* The adaptive exact arithmetic geometric predicates implemented herein are */ +/* described in detail in my Technical Report CMU-CS-96-140. The complete */ +/* reference is given in the header. */ + +/* Which of the following two methods of finding the absolute values is */ +/* fastest is compiler-dependent. A few compilers can inline and optimize */ +/* the fabs() call; but most will incur the overhead of a function call, */ +/* which is disastrously slow. A faster way on IEEE machines might be to */ +/* mask the appropriate bit, but that's difficult to do in C. */ + +#define Absolute(a) ((a) >= 0.0 ? (a) : -(a)) +/* #define Absolute(a) fabs(a) */ + +/* Many of the operations are broken up into two pieces, a main part that */ +/* performs an approximate operation, and a "tail" that computes the */ +/* roundoff error of that operation. */ +/* */ +/* The operations Fast_Two_Sum(), Fast_Two_Diff(), Two_Sum(), Two_Diff(), */ +/* Split(), and Two_Product() are all implemented as described in the */ +/* reference. Each of these macros requires certain variables to be */ +/* defined in the calling routine. The variables `bvirt', `c', `abig', */ +/* `_i', `_j', `_k', `_l', `_m', and `_n' are declared `INEXACT' because */ +/* they store the result of an operation that may incur roundoff error. */ +/* The input parameter `x' (or the highest numbered `x_' parameter) must */ +/* also be declared `INEXACT'. */ + +#define Fast_Two_Sum_Tail(a, b, x, y) \ + bvirt = x - a; \ + y = b - bvirt + +#define Fast_Two_Sum(a, b, x, y) \ + x = (REAL) (a + b); \ + Fast_Two_Sum_Tail(a, b, x, y) + +#define Two_Sum_Tail(a, b, x, y) \ + bvirt = (REAL) (x - a); \ + avirt = x - bvirt; \ + bround = b - bvirt; \ + around = a - avirt; \ + y = around + bround + +#define Two_Sum(a, b, x, y) \ + x = (REAL) (a + b); \ + Two_Sum_Tail(a, b, x, y) + +#define Two_Diff_Tail(a, b, x, y) \ + bvirt = (REAL) (a - x); \ + avirt = x + bvirt; \ + bround = bvirt - b; \ + around = a - avirt; \ + y = around + bround + +#define Two_Diff(a, b, x, y) \ + x = (REAL) (a - b); \ + Two_Diff_Tail(a, b, x, y) + +#define Split(a, ahi, alo) \ + c = (REAL) (splitter * a); \ + abig = (REAL) (c - a); \ + ahi = (REAL)(c - abig); \ + alo = (REAL)(a - ahi) + +#define Two_Product_Tail(a, b, x, y) \ + Split(a, ahi, alo); \ + Split(b, bhi, blo); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +#define Two_Product(a, b, x, y) \ + x = (REAL) (a * b); \ + Two_Product_Tail(a, b, x, y) + +/* Two_Product_Presplit() is Two_Product() where one of the inputs has */ +/* already been split. Avoids redundant splitting. */ + +#define Two_Product_Presplit(a, b, bhi, blo, x, y) \ + x = (REAL) (a * b); \ + Split(a, ahi, alo); \ + err1 = x - (ahi * bhi); \ + err2 = err1 - (alo * bhi); \ + err3 = err2 - (ahi * blo); \ + y = (alo * blo) - err3 + +/* Square() can be done more quickly than Two_Product(). */ + +#define Square_Tail(a, x, y) \ + Split(a, ahi, alo); \ + err1 = x - (ahi * ahi); \ + err3 = err1 - ((ahi + ahi) * alo); \ + y = (alo * alo) - err3 + +#define Square(a, x, y) \ + x = (REAL) (a * a); \ + Square_Tail(a, x, y) + +/* Macros for summing expansions of various fixed lengths. These are all */ +/* unrolled versions of Expansion_Sum(). */ + +#define Two_One_Sum(a1, a0, b, x2, x1, x0) \ + Two_Sum(a0, b , _i, x0); \ + Two_Sum(a1, _i, x2, x1) + +#define Two_One_Diff(a1, a0, b, x2, x1, x0) \ + Two_Diff(a0, b , _i, x0); \ + Two_Sum( a1, _i, x2, x1) + +#define Two_Two_Sum(a1, a0, b1, b0, x3, x2, x1, x0) \ + Two_One_Sum(a1, a0, b0, _j, _0, x0); \ + Two_One_Sum(_j, _0, b1, x3, x2, x1) + +#define Two_Two_Diff(a1, a0, b1, b0, x3, x2, x1, x0) \ + Two_One_Diff(a1, a0, b0, _j, _0, x0); \ + Two_One_Diff(_j, _0, b1, x3, x2, x1) + +/*****************************************************************************/ +/* */ +/* exactinit() Initialize the variables used for exact arithmetic. */ +/* */ +/* `epsilon' is the largest power of two such that 1.0 + epsilon = 1.0 in */ +/* floating-point arithmetic. `epsilon' bounds the relative roundoff */ +/* error. It is used for floating-point error analysis. */ +/* */ +/* `splitter' is used to split floating-point numbers into two half- */ +/* length significands for exact multiplication. */ +/* */ +/* I imagine that a highly optimizing compiler might be too smart for its */ +/* own good, and somehow cause this routine to fail, if it pretends that */ +/* floating-point arithmetic is too much like real arithmetic. */ +/* */ +/* Don't change this routine unless you fully understand it. */ +/* */ +/*****************************************************************************/ + +void exactinit() +{ + REAL half; + REAL check, lastcheck; + int every_other; + + every_other = 1; + half = 0.5; + epsilon = 1.0; + splitter = 1.0; + check = 1.0; + /* Repeatedly divide `epsilon' by two until it is too small to add to */ + /* one without causing roundoff. (Also check if the sum is equal to */ + /* the previous sum, for machines that round up instead of using exact */ + /* rounding. Not that these routines will work on such machines anyway. */ + do { + lastcheck = check; + epsilon *= half; + if (every_other) { + splitter *= 2.0; + } + every_other = !every_other; + check = (REAL)(1.0 + epsilon); + } while ((check != 1.0) && (check != lastcheck)); + splitter += 1.0; + if (verbose > 1) { + printf("Floating point roundoff is of magnitude %.17g\n", epsilon); + printf("Floating point splitter is %.17g\n", splitter); + } + /* Error bounds for orientation and incircle tests. */ + resulterrbound = (REAL)((3.0 + 8.0 * epsilon) * epsilon); + ccwerrboundA = (REAL)((3.0 + 16.0 * epsilon) * epsilon); + ccwerrboundB = (REAL)((2.0 + 12.0 * epsilon) * epsilon); + ccwerrboundC = (REAL)((9.0 + 64.0 * epsilon) * epsilon * epsilon); + iccerrboundA = (REAL)((10.0 + 96.0 * epsilon) * epsilon); + iccerrboundB = (REAL)((4.0 + 48.0 * epsilon) * epsilon); + iccerrboundC = (REAL)((44.0 + 576.0 * epsilon) * epsilon * epsilon); +} + +/*****************************************************************************/ +/* */ +/* fast_expansion_sum_zeroelim() Sum two expansions, eliminating zero */ +/* components from the output expansion. */ +/* */ +/* Sets h = e + f. See my Robust Predicates paper for details. */ +/* */ +/* If round-to-even is used (as with IEEE 754), maintains the strongly */ +/* nonoverlapping property. (That is, if e is strongly nonoverlapping, h */ +/* will be also.) Does NOT maintain the nonoverlapping or nonadjacent */ +/* properties. */ +/* */ +/*****************************************************************************/ + +int fast_expansion_sum_zeroelim(elen, e, flen, f, h) /* h cannot be e or f. */ +int elen; +REAL *e; +int flen; +REAL *f; +REAL *h; +{ + REAL Q; + INEXACT REAL Qnew; + INEXACT REAL hh; + INEXACT REAL bvirt; + REAL avirt, bround, around; + int eindex, findex, hindex; + REAL enow, fnow; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) { + Q = enow; + enow = e[++eindex]; + } else { + Q = fnow; + fnow = f[++findex]; + } + hindex = 0; + if ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Fast_Two_Sum(enow, Q, Qnew, hh); + enow = e[++eindex]; + } else { + Fast_Two_Sum(fnow, Q, Qnew, hh); + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + while ((eindex < elen) && (findex < flen)) { + if ((fnow > enow) == (fnow > -enow)) { + Two_Sum(Q, enow, Qnew, hh); + enow = e[++eindex]; + } else { + Two_Sum(Q, fnow, Qnew, hh); + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + } + while (eindex < elen) { + Two_Sum(Q, enow, Qnew, hh); + enow = e[++eindex]; + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + while (findex < flen) { + Two_Sum(Q, fnow, Qnew, hh); + fnow = f[++findex]; + Q = Qnew; + if (hh != 0.0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* scale_expansion_zeroelim() Multiply an expansion by a scalar, */ +/* eliminating zero components from the */ +/* output expansion. */ +/* */ +/* Sets h = be. See my Robust Predicates paper for details. */ +/* */ +/* Maintains the nonoverlapping property. If round-to-even is used (as */ +/* with IEEE 754), maintains the strongly nonoverlapping and nonadjacent */ +/* properties as well. (That is, if e has one of these properties, so */ +/* will h.) */ +/* */ +/*****************************************************************************/ + +int scale_expansion_zeroelim(elen, e, b, h) /* e and h cannot be the same. */ +int elen; +REAL *e; +REAL b; +REAL *h; +{ + INEXACT REAL Q, sum; + REAL hh; + INEXACT REAL product1; + REAL product0; + int eindex, hindex; + REAL enow; + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + + Split(b, bhi, blo); + Two_Product_Presplit(e[0], b, bhi, blo, Q, hh); + hindex = 0; + if (hh != 0) { + h[hindex++] = hh; + } + for (eindex = 1; eindex < elen; eindex++) { + enow = e[eindex]; + Two_Product_Presplit(enow, b, bhi, blo, product1, product0); + Two_Sum(Q, product0, sum, hh); + if (hh != 0) { + h[hindex++] = hh; + } + Fast_Two_Sum(product1, sum, Q, hh); + if (hh != 0) { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) { + h[hindex++] = Q; + } + return hindex; +} + +/*****************************************************************************/ +/* */ +/* estimate() Produce a one-word estimate of an expansion's value. */ +/* */ +/* See my Robust Predicates paper for details. */ +/* */ +/*****************************************************************************/ + +REAL estimate(elen, e) +int elen; +REAL *e; +{ + REAL Q; + int eindex; + + Q = e[0]; + for (eindex = 1; eindex < elen; eindex++) { + Q += e[eindex]; + } + return Q; +} + +/*****************************************************************************/ +/* */ +/* counterclockwise() Return a positive value if the points pa, pb, and */ +/* pc occur in counterclockwise order; a negative */ +/* value if they occur in clockwise order; and zero */ +/* if they are collinear. The result is also a rough */ +/* approximation of twice the signed area of the */ +/* triangle defined by the three points. */ +/* */ +/* Uses exact arithmetic if necessary to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. This determinant is */ +/* computed adaptively, in the sense that exact arithmetic is used only to */ +/* the degree it is needed to ensure that the returned value has the */ +/* correct sign. Hence, this function is usually quite fast, but will run */ +/* more slowly when the input points are collinear or nearly so. */ +/* */ +/* See my Robust Predicates paper for details. */ +/* */ +/*****************************************************************************/ + +REAL counterclockwiseadapt(pa, pb, pc, detsum) +point pa; +point pb; +point pc; +REAL detsum; +{ + INEXACT REAL acx, acy, bcx, bcy; + REAL acxtail, acytail, bcxtail, bcytail; + INEXACT REAL detleft, detright; + REAL detlefttail, detrighttail; + REAL det, errbound; + REAL B[4], C1[8], C2[12], D[16]; + INEXACT REAL B3; + int C1length, C2length, Dlength; + REAL u[4]; + INEXACT REAL u3; + INEXACT REAL s1, t1; + REAL s0, t0; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + acx = (REAL) (pa[0] - pc[0]); + bcx = (REAL) (pb[0] - pc[0]); + acy = (REAL) (pa[1] - pc[1]); + bcy = (REAL) (pb[1] - pc[1]); + + Two_Product(acx, bcy, detleft, detlefttail); + Two_Product(acy, bcx, detright, detrighttail); + + Two_Two_Diff(detleft, detlefttail, detright, detrighttail, + B3, B[2], B[1], B[0]); + B[3] = B3; + + det = estimate(4, B); + errbound = (REAL)(ccwerrboundB * detsum); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pc[0], acx, acxtail); + Two_Diff_Tail(pb[0], pc[0], bcx, bcxtail); + Two_Diff_Tail(pa[1], pc[1], acy, acytail); + Two_Diff_Tail(pb[1], pc[1], bcy, bcytail); + + if ((acxtail == 0.0) && (acytail == 0.0) + && (bcxtail == 0.0) && (bcytail == 0.0)) { + return det; + } + + errbound = (REAL)(ccwerrboundC * detsum + resulterrbound * Absolute(det)); + det += (acx * bcytail + bcy * acxtail) + - (acy * bcxtail + bcx * acytail); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Product(acxtail, bcy, s1, s0); + Two_Product(acytail, bcx, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + C1length = fast_expansion_sum_zeroelim(4, B, 4, u, C1); + + Two_Product(acx, bcytail, s1, s0); + Two_Product(acy, bcxtail, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + C2length = fast_expansion_sum_zeroelim(C1length, C1, 4, u, C2); + + Two_Product(acxtail, bcytail, s1, s0); + Two_Product(acytail, bcxtail, t1, t0); + Two_Two_Diff(s1, s0, t1, t0, u3, u[2], u[1], u[0]); + u[3] = u3; + Dlength = fast_expansion_sum_zeroelim(C2length, C2, 4, u, D); + + return(D[Dlength - 1]); +} + +REAL counterclockwise(pa, pb, pc) +point pa; +point pb; +point pc; +{ + REAL detleft, detright, det; + REAL detsum, errbound; + + counterclockcount++; + + detleft = (pa[0] - pc[0]) * (pb[1] - pc[1]); + detright = (pa[1] - pc[1]) * (pb[0] - pc[0]); + det = detleft - detright; + + if (noexact) { + return det; + } + + if (detleft > 0.0) { + if (detright <= 0.0) { + return det; + } else { + detsum = detleft + detright; + } + } else if (detleft < 0.0) { + if (detright >= 0.0) { + return det; + } else { + detsum = -detleft - detright; + } + } else { + return det; + } + + errbound = ccwerrboundA * detsum; + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + return counterclockwiseadapt(pa, pb, pc, detsum); +} + +/*****************************************************************************/ +/* */ +/* incircle() Return a positive value if the point pd lies inside the */ +/* circle passing through pa, pb, and pc; a negative value if */ +/* it lies outside; and zero if the four points are cocircular.*/ +/* The points pa, pb, and pc must be in counterclockwise */ +/* order, or the sign of the result will be reversed. */ +/* */ +/* Uses exact arithmetic if necessary to ensure a correct answer. The */ +/* result returned is the determinant of a matrix. This determinant is */ +/* computed adaptively, in the sense that exact arithmetic is used only to */ +/* the degree it is needed to ensure that the returned value has the */ +/* correct sign. Hence, this function is usually quite fast, but will run */ +/* more slowly when the input points are cocircular or nearly so. */ +/* */ +/* See my Robust Predicates paper for details. */ +/* */ +/*****************************************************************************/ + +REAL incircleadapt(pa, pb, pc, pd, permanent) +point pa; +point pb; +point pc; +point pd; +REAL permanent; +{ + INEXACT REAL adx, bdx, cdx, ady, bdy, cdy; + REAL det, errbound; + + INEXACT REAL bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; + REAL bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; + REAL bc[4], ca[4], ab[4]; + INEXACT REAL bc3, ca3, ab3; + REAL axbc[8], axxbc[16], aybc[8], ayybc[16], adet[32]; + int axbclen, axxbclen, aybclen, ayybclen, alen; + REAL bxca[8], bxxca[16], byca[8], byyca[16], bdet[32]; + int bxcalen, bxxcalen, bycalen, byycalen, blen; + REAL cxab[8], cxxab[16], cyab[8], cyyab[16], cdet[32]; + int cxablen, cxxablen, cyablen, cyyablen, clen; + REAL abdet[64]; + int ablen; + REAL fin1[1152], fin2[1152]; + REAL *finnow, *finother, *finswap; + int finlength; + + REAL adxtail, bdxtail, cdxtail, adytail, bdytail, cdytail; + INEXACT REAL adxadx1, adyady1, bdxbdx1, bdybdy1, cdxcdx1, cdycdy1; + REAL adxadx0, adyady0, bdxbdx0, bdybdy0, cdxcdx0, cdycdy0; + REAL aa[4], bb[4], cc[4]; + INEXACT REAL aa3, bb3, cc3; + INEXACT REAL ti1, tj1; + REAL ti0, tj0; + REAL u[4], v[4]; + INEXACT REAL u3, v3; + REAL temp8[8], temp16a[16], temp16b[16], temp16c[16]; + REAL temp32a[32], temp32b[32], temp48[48], temp64[64]; + int temp8len, temp16alen, temp16blen, temp16clen; + int temp32alen, temp32blen, temp48len, temp64len; + REAL axtbb[8], axtcc[8], aytbb[8], aytcc[8]; + int axtbblen, axtcclen, aytbblen, aytcclen; + REAL bxtaa[8], bxtcc[8], bytaa[8], bytcc[8]; + int bxtaalen, bxtcclen, bytaalen, bytcclen; + REAL cxtaa[8], cxtbb[8], cytaa[8], cytbb[8]; + int cxtaalen, cxtbblen, cytaalen, cytbblen; + REAL axtbc[8], aytbc[8], bxtca[8], bytca[8], cxtab[8], cytab[8]; + int axtbclen, aytbclen, bxtcalen, bytcalen, cxtablen, cytablen; + REAL axtbct[16], aytbct[16], bxtcat[16], bytcat[16], cxtabt[16], cytabt[16]; + int axtbctlen, aytbctlen, bxtcatlen, bytcatlen, cxtabtlen, cytabtlen; + REAL axtbctt[8], aytbctt[8], bxtcatt[8]; + REAL bytcatt[8], cxtabtt[8], cytabtt[8]; + int axtbcttlen, aytbcttlen, bxtcattlen, bytcattlen, cxtabttlen, cytabttlen; + REAL abt[8], bct[8], cat[8]; + int abtlen, bctlen, catlen; + REAL abtt[4], bctt[4], catt[4]; + int abttlen, bcttlen, cattlen; + INEXACT REAL abtt3, bctt3, catt3; + REAL negate; + + INEXACT REAL bvirt; + REAL avirt, bround, around; + INEXACT REAL c; + INEXACT REAL abig; + REAL ahi, alo, bhi, blo; + REAL err1, err2, err3; + INEXACT REAL _i, _j; + REAL _0; + + adx = (REAL) (pa[0] - pd[0]); + bdx = (REAL) (pb[0] - pd[0]); + cdx = (REAL) (pc[0] - pd[0]); + ady = (REAL) (pa[1] - pd[1]); + bdy = (REAL) (pb[1] - pd[1]); + cdy = (REAL) (pc[1] - pd[1]); + + Two_Product(bdx, cdy, bdxcdy1, bdxcdy0); + Two_Product(cdx, bdy, cdxbdy1, cdxbdy0); + Two_Two_Diff(bdxcdy1, bdxcdy0, cdxbdy1, cdxbdy0, bc3, bc[2], bc[1], bc[0]); + bc[3] = bc3; + axbclen = scale_expansion_zeroelim(4, bc, adx, axbc); + axxbclen = scale_expansion_zeroelim(axbclen, axbc, adx, axxbc); + aybclen = scale_expansion_zeroelim(4, bc, ady, aybc); + ayybclen = scale_expansion_zeroelim(aybclen, aybc, ady, ayybc); + alen = fast_expansion_sum_zeroelim(axxbclen, axxbc, ayybclen, ayybc, adet); + + Two_Product(cdx, ady, cdxady1, cdxady0); + Two_Product(adx, cdy, adxcdy1, adxcdy0); + Two_Two_Diff(cdxady1, cdxady0, adxcdy1, adxcdy0, ca3, ca[2], ca[1], ca[0]); + ca[3] = ca3; + bxcalen = scale_expansion_zeroelim(4, ca, bdx, bxca); + bxxcalen = scale_expansion_zeroelim(bxcalen, bxca, bdx, bxxca); + bycalen = scale_expansion_zeroelim(4, ca, bdy, byca); + byycalen = scale_expansion_zeroelim(bycalen, byca, bdy, byyca); + blen = fast_expansion_sum_zeroelim(bxxcalen, bxxca, byycalen, byyca, bdet); + + Two_Product(adx, bdy, adxbdy1, adxbdy0); + Two_Product(bdx, ady, bdxady1, bdxady0); + Two_Two_Diff(adxbdy1, adxbdy0, bdxady1, bdxady0, ab3, ab[2], ab[1], ab[0]); + ab[3] = ab3; + cxablen = scale_expansion_zeroelim(4, ab, cdx, cxab); + cxxablen = scale_expansion_zeroelim(cxablen, cxab, cdx, cxxab); + cyablen = scale_expansion_zeroelim(4, ab, cdy, cyab); + cyyablen = scale_expansion_zeroelim(cyablen, cyab, cdy, cyyab); + clen = fast_expansion_sum_zeroelim(cxxablen, cxxab, cyyablen, cyyab, cdet); + + ablen = fast_expansion_sum_zeroelim(alen, adet, blen, bdet, abdet); + finlength = fast_expansion_sum_zeroelim(ablen, abdet, clen, cdet, fin1); + + det = estimate(finlength, fin1); + errbound = (REAL)(iccerrboundB * permanent); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + Two_Diff_Tail(pa[0], pd[0], adx, adxtail); + Two_Diff_Tail(pa[1], pd[1], ady, adytail); + Two_Diff_Tail(pb[0], pd[0], bdx, bdxtail); + Two_Diff_Tail(pb[1], pd[1], bdy, bdytail); + Two_Diff_Tail(pc[0], pd[0], cdx, cdxtail); + Two_Diff_Tail(pc[1], pd[1], cdy, cdytail); + if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) + && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0)) { + return det; + } + + errbound = (REAL)(iccerrboundC * permanent + resulterrbound * Absolute(det)); + det += (REAL)(((adx * adx + ady * ady) * ((bdx * cdytail + cdy * bdxtail) + - (bdy * cdxtail + cdx * bdytail)) + + 2.0 * (adx * adxtail + ady * adytail) * (bdx * cdy - bdy * cdx)) + + ((bdx * bdx + bdy * bdy) * ((cdx * adytail + ady * cdxtail) + - (cdy * adxtail + adx * cdytail)) + + 2.0 * (bdx * bdxtail + bdy * bdytail) * (cdx * ady - cdy * adx)) + + ((cdx * cdx + cdy * cdy) * ((adx * bdytail + bdy * adxtail) + - (ady * bdxtail + bdx * adytail)) + + 2.0 * (cdx * cdxtail + cdy * cdytail) * (adx * bdy - ady * bdx))); + if ((det >= errbound) || (-det >= errbound)) { + return det; + } + + finnow = fin1; + finother = fin2; + + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) { + Square(adx, adxadx1, adxadx0); + Square(ady, adyady1, adyady0); + Two_Two_Sum(adxadx1, adxadx0, adyady1, adyady0, aa3, aa[2], aa[1], aa[0]); + aa[3] = aa3; + } + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) { + Square(bdx, bdxbdx1, bdxbdx0); + Square(bdy, bdybdy1, bdybdy0); + Two_Two_Sum(bdxbdx1, bdxbdx0, bdybdy1, bdybdy0, bb3, bb[2], bb[1], bb[0]); + bb[3] = bb3; + } + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) { + Square(cdx, cdxcdx1, cdxcdx0); + Square(cdy, cdycdy1, cdycdy0); + Two_Two_Sum(cdxcdx1, cdxcdx0, cdycdy1, cdycdy0, cc3, cc[2], cc[1], cc[0]); + cc[3] = cc3; + } + + if (adxtail != 0.0) { + axtbclen = scale_expansion_zeroelim(4, bc, adxtail, axtbc); + temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, 2.0 * adx, + temp16a); + + axtcclen = scale_expansion_zeroelim(4, cc, adxtail, axtcc); + temp16blen = scale_expansion_zeroelim(axtcclen, axtcc, bdy, temp16b); + + axtbblen = scale_expansion_zeroelim(4, bb, adxtail, axtbb); + temp16clen = scale_expansion_zeroelim(axtbblen, axtbb, -cdy, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + aytbclen = scale_expansion_zeroelim(4, bc, adytail, aytbc); + temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, 2.0 * ady, + temp16a); + + aytbblen = scale_expansion_zeroelim(4, bb, adytail, aytbb); + temp16blen = scale_expansion_zeroelim(aytbblen, aytbb, cdx, temp16b); + + aytcclen = scale_expansion_zeroelim(4, cc, adytail, aytcc); + temp16clen = scale_expansion_zeroelim(aytcclen, aytcc, -bdx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdxtail != 0.0) { + bxtcalen = scale_expansion_zeroelim(4, ca, bdxtail, bxtca); + temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, 2.0 * bdx, + temp16a); + + bxtaalen = scale_expansion_zeroelim(4, aa, bdxtail, bxtaa); + temp16blen = scale_expansion_zeroelim(bxtaalen, bxtaa, cdy, temp16b); + + bxtcclen = scale_expansion_zeroelim(4, cc, bdxtail, bxtcc); + temp16clen = scale_expansion_zeroelim(bxtcclen, bxtcc, -ady, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + bytcalen = scale_expansion_zeroelim(4, ca, bdytail, bytca); + temp16alen = scale_expansion_zeroelim(bytcalen, bytca, 2.0 * bdy, + temp16a); + + bytcclen = scale_expansion_zeroelim(4, cc, bdytail, bytcc); + temp16blen = scale_expansion_zeroelim(bytcclen, bytcc, adx, temp16b); + + bytaalen = scale_expansion_zeroelim(4, aa, bdytail, bytaa); + temp16clen = scale_expansion_zeroelim(bytaalen, bytaa, -cdx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdxtail != 0.0) { + cxtablen = scale_expansion_zeroelim(4, ab, cdxtail, cxtab); + temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, 2.0 * cdx, + temp16a); + + cxtbblen = scale_expansion_zeroelim(4, bb, cdxtail, cxtbb); + temp16blen = scale_expansion_zeroelim(cxtbblen, cxtbb, ady, temp16b); + + cxtaalen = scale_expansion_zeroelim(4, aa, cdxtail, cxtaa); + temp16clen = scale_expansion_zeroelim(cxtaalen, cxtaa, -bdy, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + cytablen = scale_expansion_zeroelim(4, ab, cdytail, cytab); + temp16alen = scale_expansion_zeroelim(cytablen, cytab, 2.0 * cdy, + temp16a); + + cytaalen = scale_expansion_zeroelim(4, aa, cdytail, cytaa); + temp16blen = scale_expansion_zeroelim(cytaalen, cytaa, bdx, temp16b); + + cytbblen = scale_expansion_zeroelim(4, bb, cdytail, cytbb); + temp16clen = scale_expansion_zeroelim(cytbblen, cytbb, -adx, temp16c); + + temp32alen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16clen, temp16c, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + if ((adxtail != 0.0) || (adytail != 0.0)) { + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) { + Two_Product(bdxtail, cdy, ti1, ti0); + Two_Product(bdx, cdytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -bdy; + Two_Product(cdxtail, negate, ti1, ti0); + negate = -bdytail; + Two_Product(cdx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + bctlen = fast_expansion_sum_zeroelim(4, u, 4, v, bct); + + Two_Product(bdxtail, cdytail, ti1, ti0); + Two_Product(cdxtail, bdytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, bctt3, bctt[2], bctt[1], bctt[0]); + bctt[3] = bctt3; + bcttlen = 4; + } else { + bct[0] = 0.0; + bctlen = 1; + bctt[0] = 0.0; + bcttlen = 1; + } + + if (adxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(axtbclen, axtbc, adxtail, temp16a); + axtbctlen = scale_expansion_zeroelim(bctlen, bct, adxtail, axtbct); + temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, 2.0 * adx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, cc, adxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, bb, -adxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(axtbctlen, axtbct, adxtail, + temp32a); + axtbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adxtail, axtbctt); + temp16alen = scale_expansion_zeroelim(axtbcttlen, axtbctt, 2.0 * adx, + temp16a); + temp16blen = scale_expansion_zeroelim(axtbcttlen, axtbctt, adxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + temp16alen = scale_expansion_zeroelim(aytbclen, aytbc, adytail, temp16a); + aytbctlen = scale_expansion_zeroelim(bctlen, bct, adytail, aytbct); + temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, 2.0 * ady, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(aytbctlen, aytbct, adytail, + temp32a); + aytbcttlen = scale_expansion_zeroelim(bcttlen, bctt, adytail, aytbctt); + temp16alen = scale_expansion_zeroelim(aytbcttlen, aytbctt, 2.0 * ady, + temp16a); + temp16blen = scale_expansion_zeroelim(aytbcttlen, aytbctt, adytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((bdxtail != 0.0) || (bdytail != 0.0)) { + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) { + Two_Product(cdxtail, ady, ti1, ti0); + Two_Product(cdx, adytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -cdy; + Two_Product(adxtail, negate, ti1, ti0); + negate = -cdytail; + Two_Product(adx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + catlen = fast_expansion_sum_zeroelim(4, u, 4, v, cat); + + Two_Product(cdxtail, adytail, ti1, ti0); + Two_Product(adxtail, cdytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, catt3, catt[2], catt[1], catt[0]); + catt[3] = catt3; + cattlen = 4; + } else { + cat[0] = 0.0; + catlen = 1; + catt[0] = 0.0; + cattlen = 1; + } + + if (bdxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(bxtcalen, bxtca, bdxtail, temp16a); + bxtcatlen = scale_expansion_zeroelim(catlen, cat, bdxtail, bxtcat); + temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, 2.0 * bdx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, aa, bdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, cdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, cc, -bdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(bxtcatlen, bxtcat, bdxtail, + temp32a); + bxtcattlen = scale_expansion_zeroelim(cattlen, catt, bdxtail, bxtcatt); + temp16alen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, 2.0 * bdx, + temp16a); + temp16blen = scale_expansion_zeroelim(bxtcattlen, bxtcatt, bdxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + temp16alen = scale_expansion_zeroelim(bytcalen, bytca, bdytail, temp16a); + bytcatlen = scale_expansion_zeroelim(catlen, cat, bdytail, bytcat); + temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, 2.0 * bdy, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(bytcatlen, bytcat, bdytail, + temp32a); + bytcattlen = scale_expansion_zeroelim(cattlen, catt, bdytail, bytcatt); + temp16alen = scale_expansion_zeroelim(bytcattlen, bytcatt, 2.0 * bdy, + temp16a); + temp16blen = scale_expansion_zeroelim(bytcattlen, bytcatt, bdytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((cdxtail != 0.0) || (cdytail != 0.0)) { + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) { + Two_Product(adxtail, bdy, ti1, ti0); + Two_Product(adx, bdytail, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, u3, u[2], u[1], u[0]); + u[3] = u3; + negate = -ady; + Two_Product(bdxtail, negate, ti1, ti0); + negate = -adytail; + Two_Product(bdx, negate, tj1, tj0); + Two_Two_Sum(ti1, ti0, tj1, tj0, v3, v[2], v[1], v[0]); + v[3] = v3; + abtlen = fast_expansion_sum_zeroelim(4, u, 4, v, abt); + + Two_Product(adxtail, bdytail, ti1, ti0); + Two_Product(bdxtail, adytail, tj1, tj0); + Two_Two_Diff(ti1, ti0, tj1, tj0, abtt3, abtt[2], abtt[1], abtt[0]); + abtt[3] = abtt3; + abttlen = 4; + } else { + abt[0] = 0.0; + abtlen = 1; + abtt[0] = 0.0; + abttlen = 1; + } + + if (cdxtail != 0.0) { + temp16alen = scale_expansion_zeroelim(cxtablen, cxtab, cdxtail, temp16a); + cxtabtlen = scale_expansion_zeroelim(abtlen, abt, cdxtail, cxtabt); + temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, 2.0 * cdx, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, bb, cdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, adytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) { + temp8len = scale_expansion_zeroelim(4, aa, -cdxtail, temp8); + temp16alen = scale_expansion_zeroelim(temp8len, temp8, bdytail, + temp16a); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp16alen, + temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = scale_expansion_zeroelim(cxtabtlen, cxtabt, cdxtail, + temp32a); + cxtabttlen = scale_expansion_zeroelim(abttlen, abtt, cdxtail, cxtabtt); + temp16alen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, 2.0 * cdx, + temp16a); + temp16blen = scale_expansion_zeroelim(cxtabttlen, cxtabtt, cdxtail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) { + temp16alen = scale_expansion_zeroelim(cytablen, cytab, cdytail, temp16a); + cytabtlen = scale_expansion_zeroelim(abtlen, abt, cdytail, cytabt); + temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, 2.0 * cdy, + temp32a); + temp48len = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp32alen, temp32a, temp48); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp48len, + temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = scale_expansion_zeroelim(cytabtlen, cytabt, cdytail, + temp32a); + cytabttlen = scale_expansion_zeroelim(abttlen, abtt, cdytail, cytabtt); + temp16alen = scale_expansion_zeroelim(cytabttlen, cytabtt, 2.0 * cdy, + temp16a); + temp16blen = scale_expansion_zeroelim(cytabttlen, cytabtt, cdytail, + temp16b); + temp32blen = fast_expansion_sum_zeroelim(temp16alen, temp16a, + temp16blen, temp16b, temp32b); + temp64len = fast_expansion_sum_zeroelim(temp32alen, temp32a, + temp32blen, temp32b, temp64); + finlength = fast_expansion_sum_zeroelim(finlength, finnow, temp64len, + temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + + return finnow[finlength - 1]; +} + +REAL incircle(pa, pb, pc, pd) +point pa; +point pb; +point pc; +point pd; +{ + REAL adx, bdx, cdx, ady, bdy, cdy; + REAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + REAL alift, blift, clift; + REAL det; + REAL permanent, errbound; + + incirclecount++; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + alift = adx * adx + ady * ady; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + blift = bdx * bdx + bdy * bdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + clift = cdx * cdx + cdy * cdy; + + det = alift * (bdxcdy - cdxbdy) + + blift * (cdxady - adxcdy) + + clift * (adxbdy - bdxady); + + if (noexact) { + return det; + } + + permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * alift + + (Absolute(cdxady) + Absolute(adxcdy)) * blift + + (Absolute(adxbdy) + Absolute(bdxady)) * clift; + errbound = iccerrboundA * permanent; + if ((det > errbound) || (-det > errbound)) { + return det; + } + + return incircleadapt(pa, pb, pc, pd, permanent); +} + +/** **/ +/** **/ +/********* Determinant evaluation routines end here *********/ + +/*****************************************************************************/ +/* */ +/* triangleinit() Initialize some variables. */ +/* */ +/*****************************************************************************/ + +void triangleinit() +{ + points.maxitems = triangles.maxitems = shelles.maxitems = viri.maxitems = + badsegments.maxitems = badtriangles.maxitems = splaynodes.maxitems = 0l; + points.itembytes = triangles.itembytes = shelles.itembytes = viri.itembytes = + badsegments.itembytes = badtriangles.itembytes = splaynodes.itembytes = 0; + recenttri.tri = (triangle *) NULL; /* No triangle has been visited yet. */ + samples = 1; /* Point location should take at least one sample. */ + checksegments = 0; /* There are no segments in the triangulation yet. */ + incirclecount = counterclockcount = hyperbolacount = 0; + circumcentercount = circletopcount = 0; + randomseed = 1; + + exactinit(); /* Initialize exact arithmetic constants. */ +} + +/*****************************************************************************/ +/* */ +/* randomnation() Generate a random number between 0 and `choices' - 1. */ +/* */ +/* This is a simple linear congruential random number generator. Hence, it */ +/* is a bad random number generator, but good enough for most randomized */ +/* geometric algorithms. */ +/* */ +/*****************************************************************************/ + +unsigned long randomnation(choices) +unsigned int choices; +{ + randomseed = (randomseed * 1366l + 150889l) % 714025l; + return randomseed / (714025l / choices + 1); +} + +/********* Mesh quality testing routines begin here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* checkmesh() Test the mesh for topological consistency. */ +/* */ +/*****************************************************************************/ + +#ifndef REDUCED + +void checkmesh() +{ + struct triedge triangleloop; + struct triedge oppotri, oppooppotri; + point triorg, tridest, triapex; + point oppoorg, oppodest; + int horrors; + int saveexact; + triangle ptr; /* Temporary variable used by sym(). */ + + /* Temporarily turn on exact arithmetic if it's off. */ + saveexact = noexact; + noexact = 0; + if (!quiet) { + printf(" Checking consistency of mesh...\n"); + } + horrors = 0; + /* Run through the list of triangles, checking each one. */ + traversalinit(&triangles); + triangleloop.tri = triangletraverse(); + while (triangleloop.tri != (triangle *) NULL) { + /* Check all three edges of the triangle. */ + for (triangleloop.orient = 0; triangleloop.orient < 3; + triangleloop.orient++) { + org(triangleloop, triorg); + dest(triangleloop, tridest); + if (triangleloop.orient == 0) { /* Only test for inversion once. */ + /* Test if the triangle is flat or inverted. */ + apex(triangleloop, triapex); + if (counterclockwise(triorg, tridest, triapex) <= 0.0) { + printf(" !! !! Inverted "); + printtriangle(&triangleloop); + horrors++; + } + } + /* Find the neighboring triangle on this edge. */ + sym(triangleloop, oppotri); + if (oppotri.tri != dummytri) { + /* Check that the triangle's neighbor knows it's a neighbor. */ + sym(oppotri, oppooppotri); + if ((triangleloop.tri != oppooppotri.tri) + || (triangleloop.orient != oppooppotri.orient)) { + printf(" !! !! Asymmetric triangle-triangle bond:\n"); + if (triangleloop.tri == oppooppotri.tri) { + printf(" (Right triangle, wrong orientation)\n"); + } + printf(" First "); + printtriangle(&triangleloop); + printf(" Second (nonreciprocating) "); + printtriangle(&oppotri); + horrors++; + } + /* Check that both triangles agree on the identities */ + /* of their shared vertices. */ + org(oppotri, oppoorg); + dest(oppotri, oppodest); + if ((triorg != oppodest) || (tridest != oppoorg)) { + printf(" !! !! Mismatched edge coordinates between two triangles:\n" + ); + printf(" First mismatched "); + printtriangle(&triangleloop); + printf(" Second mismatched "); + printtriangle(&oppotri); + horrors++; + } + } + } + triangleloop.tri = triangletraverse(); + } + if (horrors == 0) { + if (!quiet) { + printf(" In my studied opinion, the mesh appears to be consistent.\n"); + } + } else if (horrors == 1) { + printf(" !! !! !! !! Precisely one festering wound discovered.\n"); + } else { + printf(" !! !! !! !! %d abominations witnessed.\n", horrors); + } + /* Restore the status of exact arithmetic. */ + noexact = saveexact; +} + +#endif /* not REDUCED */ + +/*****************************************************************************/ +/* */ +/* checkdelaunay() Ensure that the mesh is (constrained) Delaunay. */ +/* */ +/*****************************************************************************/ + +#ifndef REDUCED + +void checkdelaunay() +{ + struct triedge triangleloop; + struct triedge oppotri; + struct edge opposhelle; + point triorg, tridest, triapex; + point oppoapex; + int shouldbedelaunay; + int horrors; + int saveexact; + triangle ptr; /* Temporary variable used by sym(). */ + shelle sptr; /* Temporary variable used by tspivot(). */ + + /* Temporarily turn on exact arithmetic if it's off. */ + saveexact = noexact; + noexact = 0; + if (!quiet) { + printf(" Checking Delaunay property of mesh...\n"); + } + horrors = 0; + /* Run through the list of triangles, checking each one. */ + traversalinit(&triangles); + triangleloop.tri = triangletraverse(); + while (triangleloop.tri != (triangle *) NULL) { + /* Check all three edges of the triangle. */ + for (triangleloop.orient = 0; triangleloop.orient < 3; + triangleloop.orient++) { + org(triangleloop, triorg); + dest(triangleloop, tridest); + apex(triangleloop, triapex); + sym(triangleloop, oppotri); + apex(oppotri, oppoapex); + /* Only test that the edge is locally Delaunay if there is an */ + /* adjoining triangle whose pointer is larger (to ensure that */ + /* each pair isn't tested twice). */ + shouldbedelaunay = (oppotri.tri != dummytri) + && (triapex != (point) NULL) && (oppoapex != (point) NULL) + && (triangleloop.tri < oppotri.tri); + if (checksegments && shouldbedelaunay) { + /* If a shell edge separates the triangles, then the edge is */ + /* constrained, so no local Delaunay test should be done. */ + tspivot(triangleloop, opposhelle); + if (opposhelle.sh != dummysh){ + shouldbedelaunay = 0; + } + } + if (shouldbedelaunay) { + if (incircle(triorg, tridest, triapex, oppoapex) > 0.0) { + printf(" !! !! Non-Delaunay pair of triangles:\n"); + printf(" First non-Delaunay "); + printtriangle(&triangleloop); + printf(" Second non-Delaunay "); + printtriangle(&oppotri); + horrors++; + } + } + } + triangleloop.tri = triangletraverse(); + } + if (horrors == 0) { + if (!quiet) { + printf( + " By virtue of my perceptive intelligence, I declare the mesh Delaunay.\n"); + } + } else if (horrors == 1) { + printf( + " !! !! !! !! Precisely one terrifying transgression identified.\n"); + } else { + printf(" !! !! !! !! %d obscenities viewed with horror.\n", horrors); + } + /* Restore the status of exact arithmetic. */ + noexact = saveexact; +} + +#endif /* not REDUCED */ + +/*****************************************************************************/ +/* */ +/* enqueuebadtri() Add a bad triangle to the end of a queue. */ +/* */ +/* The queue is actually a set of 64 queues. I use multiple queues to give */ +/* priority to smaller angles. I originally implemented a heap, but the */ +/* queues are (to my surprise) much faster. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +void enqueuebadtri(instri, angle, insapex, insorg, insdest) +struct triedge *instri; +REAL angle; +point insapex; +point insorg; +point insdest; +{ + struct badface *newface; + int queuenumber; + + if (verbose > 2) { + printf(" Queueing bad triangle:\n"); + printf(" (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n", insorg[0], + insorg[1], insdest[0], insdest[1], insapex[0], insapex[1]); + } + /* Allocate space for the bad triangle. */ + newface = (struct badface *) poolalloc(&badtriangles); + triedgecopy(*instri, newface->badfacetri); + newface->key = angle; + newface->faceapex = insapex; + newface->faceorg = insorg; + newface->facedest = insdest; + newface->nextface = (struct badface *) NULL; + /* Determine the appropriate queue to put the bad triangle into. */ + if (angle > 0.6) { + queuenumber = (int) (160.0 * (angle - 0.6)); + if (queuenumber > 63) { + queuenumber = 63; + } + } else { + /* It's not a bad angle; put the triangle in the lowest-priority queue. */ + queuenumber = 0; + } + /* Add the triangle to the end of a queue. */ + *queuetail[queuenumber] = newface; + /* Maintain a pointer to the NULL pointer at the end of the queue. */ + queuetail[queuenumber] = &newface->nextface; +} + +#endif /* not CDT_ONLY */ + +/*****************************************************************************/ +/* */ +/* dequeuebadtri() Remove a triangle from the front of the queue. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +struct badface *dequeuebadtri() +{ + struct badface *result; + int queuenumber; + + /* Look for a nonempty queue. */ + for (queuenumber = 63; queuenumber >= 0; queuenumber--) { + result = queuefront[queuenumber]; + if (result != (struct badface *) NULL) { + /* Remove the triangle from the queue. */ + queuefront[queuenumber] = result->nextface; + /* Maintain a pointer to the NULL pointer at the end of the queue. */ + if (queuefront[queuenumber] == (struct badface *) NULL) { + queuetail[queuenumber] = &queuefront[queuenumber]; + } + return result; + } + } + return (struct badface *) NULL; +} + +#endif /* not CDT_ONLY */ + +/*****************************************************************************/ +/* */ +/* checkedge4encroach() Check a segment to see if it is encroached; add */ +/* it to the list if it is. */ +/* */ +/* An encroached segment is an unflippable edge that has a point in its */ +/* diametral circle (that is, it faces an angle greater than 90 degrees). */ +/* This definition is due to Ruppert. */ +/* */ +/* Returns a nonzero value if the edge is encroached. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +int checkedge4encroach(testedge) +struct edge *testedge; +{ + struct triedge neighbortri; + struct edge testsym; + struct edge *badedge; + int addtolist; + int sides; + point eorg, edest, eapex; + triangle ptr; /* Temporary variable used by stpivot(). */ + + addtolist = 0; + sides = 0; + + sorg(*testedge, eorg); + sdest(*testedge, edest); + /* Check one neighbor of the shell edge. */ + stpivot(*testedge, neighbortri); + /* Does the neighbor exist, or is this a boundary edge? */ + if (neighbortri.tri != dummytri) { + sides++; + /* Find a vertex opposite this edge. */ + apex(neighbortri, eapex); + /* Check whether the vertex is inside the diametral circle of the */ + /* shell edge. Pythagoras' Theorem is used to check whether the */ + /* angle at the vertex is greater than 90 degrees. */ + if (eapex[0] * (eorg[0] + edest[0]) + eapex[1] * (eorg[1] + edest[1]) > + eapex[0] * eapex[0] + eorg[0] * edest[0] + + eapex[1] * eapex[1] + eorg[1] * edest[1]) { + addtolist = 1; + } + } + /* Check the other neighbor of the shell edge. */ + ssym(*testedge, testsym); + stpivot(testsym, neighbortri); + /* Does the neighbor exist, or is this a boundary edge? */ + if (neighbortri.tri != dummytri) { + sides++; + /* Find the other vertex opposite this edge. */ + apex(neighbortri, eapex); + /* Check whether the vertex is inside the diametral circle of the */ + /* shell edge. Pythagoras' Theorem is used to check whether the */ + /* angle at the vertex is greater than 90 degrees. */ + if (eapex[0] * (eorg[0] + edest[0]) + + eapex[1] * (eorg[1] + edest[1]) > + eapex[0] * eapex[0] + eorg[0] * edest[0] + + eapex[1] * eapex[1] + eorg[1] * edest[1]) { + addtolist += 2; + } + } + + if (addtolist && (!nobisect || ((nobisect == 1) && (sides == 2)))) { + if (verbose > 2) { + printf(" Queueing encroached segment (%.12g, %.12g) (%.12g, %.12g).\n", + eorg[0], eorg[1], edest[0], edest[1]); + } + /* Add the shell edge to the list of encroached segments. */ + /* Be sure to get the orientation right. */ + badedge = (struct edge *) poolalloc(&badsegments); + if (addtolist == 1) { + shellecopy(*testedge, *badedge); + } else { + shellecopy(testsym, *badedge); + } + } + return addtolist; +} + +#endif /* not CDT_ONLY */ + +/*****************************************************************************/ +/* */ +/* testtriangle() Test a face for quality measures. */ +/* */ +/* Tests a triangle to see if it satisfies the minimum angle condition and */ +/* the maximum area condition. Triangles that aren't up to spec are added */ +/* to the bad triangle queue. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +void testtriangle(testtri) +struct triedge *testtri; +{ + struct triedge sametesttri; + struct edge edge1, edge2; + point torg, tdest, tapex; + point anglevertex; + REAL dxod, dyod, dxda, dyda, dxao, dyao; + REAL dxod2, dyod2, dxda2, dyda2, dxao2, dyao2; + REAL apexlen, orglen, destlen; + REAL angle; + REAL area; + shelle sptr; /* Temporary variable used by tspivot(). */ + + org(*testtri, torg); + dest(*testtri, tdest); + apex(*testtri, tapex); + dxod = torg[0] - tdest[0]; + dyod = torg[1] - tdest[1]; + dxda = tdest[0] - tapex[0]; + dyda = tdest[1] - tapex[1]; + dxao = tapex[0] - torg[0]; + dyao = tapex[1] - torg[1]; + dxod2 = dxod * dxod; + dyod2 = dyod * dyod; + dxda2 = dxda * dxda; + dyda2 = dyda * dyda; + dxao2 = dxao * dxao; + dyao2 = dyao * dyao; + /* Find the lengths of the triangle's three edges. */ + apexlen = dxod2 + dyod2; + orglen = dxda2 + dyda2; + destlen = dxao2 + dyao2; + if ((apexlen < orglen) && (apexlen < destlen)) { + /* The edge opposite the apex is shortest. */ + /* Find the square of the cosine of the angle at the apex. */ + angle = dxda * dxao + dyda * dyao; + angle = angle * angle / (orglen * destlen); + anglevertex = tapex; + lnext(*testtri, sametesttri); + tspivot(sametesttri, edge1); + lnextself(sametesttri); + tspivot(sametesttri, edge2); + } else if (orglen < destlen) { + /* The edge opposite the origin is shortest. */ + /* Find the square of the cosine of the angle at the origin. */ + angle = dxod * dxao + dyod * dyao; + angle = angle * angle / (apexlen * destlen); + anglevertex = torg; + tspivot(*testtri, edge1); + lprev(*testtri, sametesttri); + tspivot(sametesttri, edge2); + } else { + /* The edge opposite the destination is shortest. */ + /* Find the square of the cosine of the angle at the destination. */ + angle = dxod * dxda + dyod * dyda; + angle = angle * angle / (apexlen * orglen); + anglevertex = tdest; + tspivot(*testtri, edge1); + lnext(*testtri, sametesttri); + tspivot(sametesttri, edge2); + } + /* Check if both edges that form the angle are segments. */ + if ((edge1.sh != dummysh) && (edge2.sh != dummysh)) { + /* The angle is a segment intersection. */ + if ((angle > 0.9924) && !quiet) { /* Roughly 5 degrees. */ + if (angle > 1.0) { + /* Beware of a floating exception in acos(). */ + angle = 1.0; + } + /* Find the actual angle in degrees, for printing. */ + angle = acos(sqrt(angle)) * (180.0 / PI); + printf( + "Warning: Small angle (%.4g degrees) between segments at point\n", + angle); + printf(" (%.12g, %.12g)\n", anglevertex[0], anglevertex[1]); + } + /* Don't add this bad triangle to the list; there's nothing that */ + /* can be done about a small angle between two segments. */ + angle = 0.0; + } + /* Check whether the angle is smaller than permitted. */ + if (angle > goodangle) { + /* Add this triangle to the list of bad triangles. */ + enqueuebadtri(testtri, angle, tapex, torg, tdest); + return; + } + if (vararea || fixedarea) { + /* Check whether the area is larger than permitted. */ + area = 0.5 * (dxod * dyda - dyod * dxda); + if (fixedarea && (area > maxarea)) { + /* Add this triangle to the list of bad triangles. */ + enqueuebadtri(testtri, angle, tapex, torg, tdest); + } else if (vararea) { + /* Nonpositive area constraints are treated as unconstrained. */ + if ((area > areabound(*testtri)) && (areabound(*testtri) > 0.0)) { + /* Add this triangle to the list of bad triangles. */ + enqueuebadtri(testtri, angle, tapex, torg, tdest); + } + } + } +} + +#endif /* not CDT_ONLY */ + +/** **/ +/** **/ +/********* Mesh quality testing routines end here *********/ + +/********* Point location routines begin here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* makepointmap() Construct a mapping from points to triangles to improve */ +/* the speed of point location for segment insertion. */ +/* */ +/* Traverses all the triangles, and provides each corner of each triangle */ +/* with a pointer to that triangle. Of course, pointers will be */ +/* overwritten by other pointers because (almost) each point is a corner */ +/* of several triangles, but in the end every point will point to some */ +/* triangle that contains it. */ +/* */ +/*****************************************************************************/ + +void makepointmap() +{ + struct triedge triangleloop; + point triorg; + + if (verbose) { + printf(" Constructing mapping from points to triangles.\n"); + } + traversalinit(&triangles); + triangleloop.tri = triangletraverse(); + while (triangleloop.tri != (triangle *) NULL) { + /* Check all three points of the triangle. */ + for (triangleloop.orient = 0; triangleloop.orient < 3; + triangleloop.orient++) { + org(triangleloop, triorg); + setpoint2tri(triorg, encode(triangleloop)); + } + triangleloop.tri = triangletraverse(); + } +} + +/*****************************************************************************/ +/* */ +/* preciselocate() Find a triangle or edge containing a given point. */ +/* */ +/* Begins its search from `searchtri'. It is important that `searchtri' */ +/* be a handle with the property that `searchpoint' is strictly to the left */ +/* of the edge denoted by `searchtri', or is collinear with that edge and */ +/* does not intersect that edge. (In particular, `searchpoint' should not */ +/* be the origin or destination of that edge.) */ +/* */ +/* These conditions are imposed because preciselocate() is normally used in */ +/* one of two situations: */ +/* */ +/* (1) To try to find the location to insert a new point. Normally, we */ +/* know an edge that the point is strictly to the left of. In the */ +/* incremental Delaunay algorithm, that edge is a bounding box edge. */ +/* In Ruppert's Delaunay refinement algorithm for quality meshing, */ +/* that edge is the shortest edge of the triangle whose circumcenter */ +/* is being inserted. */ +/* */ +/* (2) To try to find an existing point. In this case, any edge on the */ +/* convex hull is a good starting edge. The possibility that the */ +/* vertex one seeks is an endpoint of the starting edge must be */ +/* screened out before preciselocate() is called. */ +/* */ +/* On completion, `searchtri' is a triangle that contains `searchpoint'. */ +/* */ +/* This implementation differs from that given by Guibas and Stolfi. It */ +/* walks from triangle to triangle, crossing an edge only if `searchpoint' */ +/* is on the other side of the line containing that edge. After entering */ +/* a triangle, there are two edges by which one can leave that triangle. */ +/* If both edges are valid (`searchpoint' is on the other side of both */ +/* edges), one of the two is chosen by drawing a line perpendicular to */ +/* the entry edge (whose endpoints are `forg' and `fdest') passing through */ +/* `fapex'. Depending on which side of this perpendicular `searchpoint' */ +/* falls on, an exit edge is chosen. */ +/* */ +/* This implementation is empirically faster than the Guibas and Stolfi */ +/* point location routine (which I originally used), which tends to spiral */ +/* in toward its target. */ +/* */ +/* Returns ONVERTEX if the point lies on an existing vertex. `searchtri' */ +/* is a handle whose origin is the existing vertex. */ +/* */ +/* Returns ONEDGE if the point lies on a mesh edge. `searchtri' is a */ +/* handle whose primary edge is the edge on which the point lies. */ +/* */ +/* Returns INTRIANGLE if the point lies strictly within a triangle. */ +/* `searchtri' is a handle on the triangle that contains the point. */ +/* */ +/* Returns OUTSIDE if the point lies outside the mesh. `searchtri' is a */ +/* handle whose primary edge the point is to the right of. This might */ +/* occur when the circumcenter of a triangle falls just slightly outside */ +/* the mesh due to floating-point roundoff error. It also occurs when */ +/* seeking a hole or region point that a foolish user has placed outside */ +/* the mesh. */ +/* */ +/* WARNING: This routine is designed for convex triangulations, and will */ +/* not generally work after the holes and concavities have been carved. */ +/* However, it can still be used to find the circumcenter of a triangle, as */ +/* long as the search is begun from the triangle in question. */ +/* */ +/*****************************************************************************/ + +enum locateresult preciselocate(searchpoint, searchtri) +point searchpoint; +struct triedge *searchtri; +{ + struct triedge backtracktri; + point forg, fdest, fapex; + point swappoint; + REAL orgorient, destorient; + int moveleft; + triangle ptr; /* Temporary variable used by sym(). */ + + if (verbose > 2) { + printf(" Searching for point (%.12g, %.12g).\n", + searchpoint[0], searchpoint[1]); + } + /* Where are we? */ + org(*searchtri, forg); + dest(*searchtri, fdest); + apex(*searchtri, fapex); + while (1) { + if (verbose > 2) { + printf(" At (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n", + forg[0], forg[1], fdest[0], fdest[1], fapex[0], fapex[1]); + } + /* Check whether the apex is the point we seek. */ + if ((fapex[0] == searchpoint[0]) && (fapex[1] == searchpoint[1])) { + lprevself(*searchtri); + return ONVERTEX; + } + /* Does the point lie on the other side of the line defined by the */ + /* triangle edge opposite the triangle's destination? */ + destorient = counterclockwise(forg, fapex, searchpoint); + /* Does the point lie on the other side of the line defined by the */ + /* triangle edge opposite the triangle's origin? */ + orgorient = counterclockwise(fapex, fdest, searchpoint); + if (destorient > 0.0) { + if (orgorient > 0.0) { + /* Move left if the inner product of (fapex - searchpoint) and */ + /* (fdest - forg) is positive. This is equivalent to drawing */ + /* a line perpendicular to the line (forg, fdest) passing */ + /* through `fapex', and determining which side of this line */ + /* `searchpoint' falls on. */ + moveleft = (fapex[0] - searchpoint[0]) * (fdest[0] - forg[0]) + + (fapex[1] - searchpoint[1]) * (fdest[1] - forg[1]) > 0.0; + } else { + moveleft = 1; + } + } else { + if (orgorient > 0.0) { + moveleft = 0; + } else { + /* The point we seek must be on the boundary of or inside this */ + /* triangle. */ + if (destorient == 0.0) { + lprevself(*searchtri); + return ONEDGE; + } + if (orgorient == 0.0) { + lnextself(*searchtri); + return ONEDGE; + } + return INTRIANGLE; + } + } + + /* Move to another triangle. Leave a trace `backtracktri' in case */ + /* floating-point roundoff or some such bogey causes us to walk */ + /* off a boundary of the triangulation. We can just bounce off */ + /* the boundary as if it were an elastic band. */ + if (moveleft) { + lprev(*searchtri, backtracktri); + fdest = fapex; + } else { + lnext(*searchtri, backtracktri); + forg = fapex; + } + sym(backtracktri, *searchtri); + + /* Check for walking off the edge. */ + if (searchtri->tri == dummytri) { + /* Turn around. */ + triedgecopy(backtracktri, *searchtri); + swappoint = forg; + forg = fdest; + fdest = swappoint; + apex(*searchtri, fapex); + /* Check if the point really is beyond the triangulation boundary. */ + destorient = counterclockwise(forg, fapex, searchpoint); + orgorient = counterclockwise(fapex, fdest, searchpoint); + if ((orgorient < 0.0) && (destorient < 0.0)) { + return OUTSIDE; + } + } else { + apex(*searchtri, fapex); + } + } +} + +/*****************************************************************************/ +/* */ +/* locate() Find a triangle or edge containing a given point. */ +/* */ +/* Searching begins from one of: the input `searchtri', a recently */ +/* encountered triangle `recenttri', or from a triangle chosen from a */ +/* random sample. The choice is made by determining which triangle's */ +/* origin is closest to the point we are searcing for. Normally, */ +/* `searchtri' should be a handle on the convex hull of the triangulation. */ +/* */ +/* Details on the random sampling method can be found in the Mucke, Saias, */ +/* and Zhu paper cited in the header of this code. */ +/* */ +/* On completion, `searchtri' is a triangle that contains `searchpoint'. */ +/* */ +/* Returns ONVERTEX if the point lies on an existing vertex. `searchtri' */ +/* is a handle whose origin is the existing vertex. */ +/* */ +/* Returns ONEDGE if the point lies on a mesh edge. `searchtri' is a */ +/* handle whose primary edge is the edge on which the point lies. */ +/* */ +/* Returns INTRIANGLE if the point lies strictly within a triangle. */ +/* `searchtri' is a handle on the triangle that contains the point. */ +/* */ +/* Returns OUTSIDE if the point lies outside the mesh. `searchtri' is a */ +/* handle whose primary edge the point is to the right of. This might */ +/* occur when the circumcenter of a triangle falls just slightly outside */ +/* the mesh due to floating-point roundoff error. It also occurs when */ +/* seeking a hole or region point that a foolish user has placed outside */ +/* the mesh. */ +/* */ +/* WARNING: This routine is designed for convex triangulations, and will */ +/* not generally work after the holes and concavities have been carved. */ +/* */ +/*****************************************************************************/ + +enum locateresult locate(searchpoint, searchtri) +point searchpoint; +struct triedge *searchtri; +{ + VOID **sampleblock; + triangle *firsttri; + struct triedge sampletri; + point torg, tdest; + unsigned long alignptr; + REAL searchdist, dist; + REAL ahead; + long sampleblocks, samplesperblock, samplenum; + long triblocks; + long i, j; + triangle ptr; /* Temporary variable used by sym(). */ + + if (verbose > 2) { + printf(" Randomly sampling for a triangle near point (%.12g, %.12g).\n", + searchpoint[0], searchpoint[1]); + } + /* Record the distance from the suggested starting triangle to the */ + /* point we seek. */ + org(*searchtri, torg); + searchdist = (searchpoint[0] - torg[0]) * (searchpoint[0] - torg[0]) + + (searchpoint[1] - torg[1]) * (searchpoint[1] - torg[1]); + if (verbose > 2) { + printf(" Boundary triangle has origin (%.12g, %.12g).\n", + torg[0], torg[1]); + } + + /* If a recently encountered triangle has been recorded and has not been */ + /* deallocated, test it as a good starting point. */ + if (recenttri.tri != (triangle *) NULL) { + if (recenttri.tri[3] != (triangle) NULL) { + org(recenttri, torg); + if ((torg[0] == searchpoint[0]) && (torg[1] == searchpoint[1])) { + triedgecopy(recenttri, *searchtri); + return ONVERTEX; + } + dist = (searchpoint[0] - torg[0]) * (searchpoint[0] - torg[0]) + + (searchpoint[1] - torg[1]) * (searchpoint[1] - torg[1]); + if (dist < searchdist) { + triedgecopy(recenttri, *searchtri); + searchdist = dist; + if (verbose > 2) { + printf(" Choosing recent triangle with origin (%.12g, %.12g).\n", + torg[0], torg[1]); + } + } + } + } + + /* The number of random samples taken is proportional to the cube root of */ + /* the number of triangles in the mesh. The next bit of code assumes */ + /* that the number of triangles increases monotonically. */ + while (SAMPLEFACTOR * samples * samples * samples < triangles.items) { + samples++; + } + triblocks = (triangles.maxitems + TRIPERBLOCK - 1) / TRIPERBLOCK; + samplesperblock = 1 + (samples / triblocks); + sampleblocks = samples / samplesperblock; + sampleblock = triangles.firstblock; + sampletri.orient = 0; + for (i = 0; i < sampleblocks; i++) { + alignptr = (unsigned long) (sampleblock + 1); + firsttri = (triangle *) (alignptr + (unsigned long) triangles.alignbytes + - (alignptr % (unsigned long) triangles.alignbytes)); + for (j = 0; j < samplesperblock; j++) { + if (i == triblocks - 1) { + samplenum = randomnation((int) + (triangles.maxitems - (i * TRIPERBLOCK))); + } else { + samplenum = randomnation(TRIPERBLOCK); + } + sampletri.tri = (triangle *) + (firsttri + (samplenum * triangles.itemwords)); + if (sampletri.tri[3] != (triangle) NULL) { + org(sampletri, torg); + dist = (searchpoint[0] - torg[0]) * (searchpoint[0] - torg[0]) + + (searchpoint[1] - torg[1]) * (searchpoint[1] - torg[1]); + if (dist < searchdist) { + triedgecopy(sampletri, *searchtri); + searchdist = dist; + if (verbose > 2) { + printf(" Choosing triangle with origin (%.12g, %.12g).\n", + torg[0], torg[1]); + } + } + } + } + sampleblock = (VOID **) *sampleblock; + } + /* Where are we? */ + org(*searchtri, torg); + dest(*searchtri, tdest); + /* Check the starting triangle's vertices. */ + if ((torg[0] == searchpoint[0]) && (torg[1] == searchpoint[1])) { + return ONVERTEX; + } + if ((tdest[0] == searchpoint[0]) && (tdest[1] == searchpoint[1])) { + lnextself(*searchtri); + return ONVERTEX; + } + /* Orient `searchtri' to fit the preconditions of calling preciselocate(). */ + ahead = counterclockwise(torg, tdest, searchpoint); + if (ahead < 0.0) { + /* Turn around so that `searchpoint' is to the left of the */ + /* edge specified by `searchtri'. */ + symself(*searchtri); + } else if (ahead == 0.0) { + /* Check if `searchpoint' is between `torg' and `tdest'. */ + if (((torg[0] < searchpoint[0]) == (searchpoint[0] < tdest[0])) + && ((torg[1] < searchpoint[1]) == (searchpoint[1] < tdest[1]))) { + return ONEDGE; + } + } + return preciselocate(searchpoint, searchtri); +} + +/** **/ +/** **/ +/********* Point location routines end here *********/ + +/********* Mesh transformation routines begin here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* insertshelle() Create a new shell edge and insert it between two */ +/* triangles. */ +/* */ +/* The new shell edge is inserted at the edge described by the handle */ +/* `tri'. Its vertices are properly initialized. The marker `shellemark' */ +/* is applied to the shell edge and, if appropriate, its vertices. */ +/* */ +/*****************************************************************************/ + +void insertshelle(tri, shellemark) +struct triedge *tri; /* Edge at which to insert the new shell edge. */ +int shellemark; /* Marker for the new shell edge. */ +{ + struct triedge oppotri; + struct edge newshelle; + point triorg, tridest; + triangle ptr; /* Temporary variable used by sym(). */ + shelle sptr; /* Temporary variable used by tspivot(). */ + + /* Mark points if possible. */ + org(*tri, triorg); + dest(*tri, tridest); + if (pointmark(triorg) == 0) { + setpointmark(triorg, shellemark); + } + if (pointmark(tridest) == 0) { + setpointmark(tridest, shellemark); + } + /* Check if there's already a shell edge here. */ + tspivot(*tri, newshelle); + if (newshelle.sh == dummysh) { + /* Make new shell edge and initialize its vertices. */ + makeshelle(&newshelle); + setsorg(newshelle, tridest); + setsdest(newshelle, triorg); + /* Bond new shell edge to the two triangles it is sandwiched between. */ + /* Note that the facing triangle `oppotri' might be equal to */ + /* `dummytri' (outer space), but the new shell edge is bonded to it */ + /* all the same. */ + tsbond(*tri, newshelle); + sym(*tri, oppotri); + ssymself(newshelle); + tsbond(oppotri, newshelle); + setmark(newshelle, shellemark); + if (verbose > 2) { + printf(" Inserting new "); + printshelle(&newshelle); + } + } else { + if (mark(newshelle) == 0) { + setmark(newshelle, shellemark); + } + } +} + +/*****************************************************************************/ +/* */ +/* Terminology */ +/* */ +/* A "local transformation" replaces a small set of triangles with another */ +/* set of triangles. This may or may not involve inserting or deleting a */ +/* point. */ +/* */ +/* The term "casing" is used to describe the set of triangles that are */ +/* attached to the triangles being transformed, but are not transformed */ +/* themselves. Think of the casing as a fixed hollow structure inside */ +/* which all the action happens. A "casing" is only defined relative to */ +/* a single transformation; each occurrence of a transformation will */ +/* involve a different casing. */ +/* */ +/* A "shell" is similar to a "casing". The term "shell" describes the set */ +/* of shell edges (if any) that are attached to the triangles being */ +/* transformed. However, I sometimes use "shell" to refer to a single */ +/* shell edge, so don't get confused. */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* flip() Transform two triangles to two different triangles by flipping */ +/* an edge within a quadrilateral. */ +/* */ +/* Imagine the original triangles, abc and bad, oriented so that the */ +/* shared edge ab lies in a horizontal plane, with the point b on the left */ +/* and the point a on the right. The point c lies below the edge, and the */ +/* point d lies above the edge. The `flipedge' handle holds the edge ab */ +/* of triangle abc, and is directed left, from vertex a to vertex b. */ +/* */ +/* The triangles abc and bad are deleted and replaced by the triangles cdb */ +/* and dca. The triangles that represent abc and bad are NOT deallocated; */ +/* they are reused for dca and cdb, respectively. Hence, any handles that */ +/* may have held the original triangles are still valid, although not */ +/* directed as they were before. */ +/* */ +/* Upon completion of this routine, the `flipedge' handle holds the edge */ +/* dc of triangle dca, and is directed down, from vertex d to vertex c. */ +/* (Hence, the two triangles have rotated counterclockwise.) */ +/* */ +/* WARNING: This transformation is geometrically valid only if the */ +/* quadrilateral adbc is convex. Furthermore, this transformation is */ +/* valid only if there is not a shell edge between the triangles abc and */ +/* bad. This routine does not check either of these preconditions, and */ +/* it is the responsibility of the calling routine to ensure that they are */ +/* met. If they are not, the streets shall be filled with wailing and */ +/* gnashing of teeth. */ +/* */ +/*****************************************************************************/ + +void flip(flipedge) +struct triedge *flipedge; /* Handle for the triangle abc. */ +{ + struct triedge botleft, botright; + struct triedge topleft, topright; + struct triedge top; + struct triedge botlcasing, botrcasing; + struct triedge toplcasing, toprcasing; + struct edge botlshelle, botrshelle; + struct edge toplshelle, toprshelle; + point leftpoint, rightpoint, botpoint; + point farpoint; + triangle ptr; /* Temporary variable used by sym(). */ + shelle sptr; /* Temporary variable used by tspivot(). */ + + /* Identify the vertices of the quadrilateral. */ + org(*flipedge, rightpoint); + dest(*flipedge, leftpoint); + apex(*flipedge, botpoint); + sym(*flipedge, top); +#ifdef SELF_CHECK + if (top.tri == dummytri) { + printf("Internal error in flip(): Attempt to flip on boundary.\n"); + lnextself(*flipedge); + return; + } + if (checksegments) { + tspivot(*flipedge, toplshelle); + if (toplshelle.sh != dummysh) { + printf("Internal error in flip(): Attempt to flip a segment.\n"); + lnextself(*flipedge); + return; + } + } +#endif /* SELF_CHECK */ + apex(top, farpoint); + + /* Identify the casing of the quadrilateral. */ + lprev(top, topleft); + sym(topleft, toplcasing); + lnext(top, topright); + sym(topright, toprcasing); + lnext(*flipedge, botleft); + sym(botleft, botlcasing); + lprev(*flipedge, botright); + sym(botright, botrcasing); + /* Rotate the quadrilateral one-quarter turn counterclockwise. */ + bond(topleft, botlcasing); + bond(botleft, botrcasing); + bond(botright, toprcasing); + bond(topright, toplcasing); + + if (checksegments) { + /* Check for shell edges and rebond them to the quadrilateral. */ + tspivot(topleft, toplshelle); + tspivot(botleft, botlshelle); + tspivot(botright, botrshelle); + tspivot(topright, toprshelle); + if (toplshelle.sh == dummysh) { + tsdissolve(topright); + } else { + tsbond(topright, toplshelle); + } + if (botlshelle.sh == dummysh) { + tsdissolve(topleft); + } else { + tsbond(topleft, botlshelle); + } + if (botrshelle.sh == dummysh) { + tsdissolve(botleft); + } else { + tsbond(botleft, botrshelle); + } + if (toprshelle.sh == dummysh) { + tsdissolve(botright); + } else { + tsbond(botright, toprshelle); + } + } + + /* New point assignments for the rotated quadrilateral. */ + setorg(*flipedge, farpoint); + setdest(*flipedge, botpoint); + setapex(*flipedge, rightpoint); + setorg(top, botpoint); + setdest(top, farpoint); + setapex(top, leftpoint); + if (verbose > 2) { + printf(" Edge flip results in left "); + lnextself(topleft); + printtriangle(&topleft); + printf(" and right "); + printtriangle(flipedge); + } +} + +/*****************************************************************************/ +/* */ +/* insertsite() Insert a vertex into a Delaunay triangulation, */ +/* performing flips as necessary to maintain the Delaunay */ +/* property. */ +/* */ +/* The point `insertpoint' is located. If `searchtri.tri' is not NULL, */ +/* the search for the containing triangle begins from `searchtri'. If */ +/* `searchtri.tri' is NULL, a full point location procedure is called. */ +/* If `insertpoint' is found inside a triangle, the triangle is split into */ +/* three; if `insertpoint' lies on an edge, the edge is split in two, */ +/* thereby splitting the two adjacent triangles into four. Edge flips are */ +/* used to restore the Delaunay property. If `insertpoint' lies on an */ +/* existing vertex, no action is taken, and the value DUPLICATEPOINT is */ +/* returned. On return, `searchtri' is set to a handle whose origin is the */ +/* existing vertex. */ +/* */ +/* Normally, the parameter `splitedge' is set to NULL, implying that no */ +/* segment should be split. In this case, if `insertpoint' is found to */ +/* lie on a segment, no action is taken, and the value VIOLATINGPOINT is */ +/* returned. On return, `searchtri' is set to a handle whose primary edge */ +/* is the violated segment. */ +/* */ +/* If the calling routine wishes to split a segment by inserting a point in */ +/* it, the parameter `splitedge' should be that segment. In this case, */ +/* `searchtri' MUST be the triangle handle reached by pivoting from that */ +/* segment; no point location is done. */ +/* */ +/* `segmentflaws' and `triflaws' are flags that indicate whether or not */ +/* there should be checks for the creation of encroached segments or bad */ +/* quality faces. If a newly inserted point encroaches upon segments, */ +/* these segments are added to the list of segments to be split if */ +/* `segmentflaws' is set. If bad triangles are created, these are added */ +/* to the queue if `triflaws' is set. */ +/* */ +/* If a duplicate point or violated segment does not prevent the point */ +/* from being inserted, the return value will be ENCROACHINGPOINT if the */ +/* point encroaches upon a segment (and checking is enabled), or */ +/* SUCCESSFULPOINT otherwise. In either case, `searchtri' is set to a */ +/* handle whose origin is the newly inserted vertex. */ +/* */ +/* insertsite() does not use flip() for reasons of speed; some */ +/* information can be reused from edge flip to edge flip, like the */ +/* locations of shell edges. */ +/* */ +/*****************************************************************************/ + +enum insertsiteresult insertsite(insertpoint, searchtri, splitedge, + segmentflaws, triflaws) +point insertpoint; +struct triedge *searchtri; +struct edge *splitedge; +int segmentflaws; +int triflaws; +{ + struct triedge horiz; + struct triedge top; + struct triedge botleft, botright; + struct triedge topleft, topright; + struct triedge newbotleft, newbotright; + struct triedge newtopright; + struct triedge botlcasing, botrcasing; + struct triedge toplcasing, toprcasing; + struct triedge testtri; + struct edge botlshelle, botrshelle; + struct edge toplshelle, toprshelle; + struct edge brokenshelle; + struct edge checkshelle; + struct edge rightedge; + struct edge newedge; + struct edge *encroached; + point first; + point leftpoint, rightpoint, botpoint, toppoint, farpoint; + REAL attrib; + REAL area; + enum insertsiteresult success; + enum locateresult intersect; + int doflip; + int mirrorflag; + int i; + triangle ptr; /* Temporary variable used by sym(). */ + shelle sptr; /* Temporary variable used by spivot() and tspivot(). */ + + if (verbose > 1) { + printf(" Inserting (%.12g, %.12g).\n", insertpoint[0], insertpoint[1]); + } + if (splitedge == (struct edge *) NULL) { + /* Find the location of the point to be inserted. Check if a good */ + /* starting triangle has already been provided by the caller. */ + if (searchtri->tri == (triangle *) NULL) { + /* Find a boundary triangle. */ + horiz.tri = dummytri; + horiz.orient = 0; + symself(horiz); + /* Search for a triangle containing `insertpoint'. */ + intersect = locate(insertpoint, &horiz); + } else { + /* Start searching from the triangle provided by the caller. */ + triedgecopy(*searchtri, horiz); + intersect = preciselocate(insertpoint, &horiz); + } + } else { + /* The calling routine provides the edge in which the point is inserted. */ + triedgecopy(*searchtri, horiz); + intersect = ONEDGE; + } + if (intersect == ONVERTEX) { + /* There's already a vertex there. Return in `searchtri' a triangle */ + /* whose origin is the existing vertex. */ + triedgecopy(horiz, *searchtri); + triedgecopy(horiz, recenttri); + return DUPLICATEPOINT; + } + if ((intersect == ONEDGE) || (intersect == OUTSIDE)) { + /* The vertex falls on an edge or boundary. */ + if (checksegments && (splitedge == (struct edge *) NULL)) { + /* Check whether the vertex falls on a shell edge. */ + tspivot(horiz, brokenshelle); + if (brokenshelle.sh != dummysh) { + /* The vertex falls on a shell edge. */ + if (segmentflaws) { + if (nobisect == 0) { + /* Add the shell edge to the list of encroached segments. */ + encroached = (struct edge *) poolalloc(&badsegments); + shellecopy(brokenshelle, *encroached); + } else if ((nobisect == 1) && (intersect == ONEDGE)) { + /* This segment may be split only if it is an internal boundary. */ + sym(horiz, testtri); + if (testtri.tri != dummytri) { + /* Add the shell edge to the list of encroached segments. */ + encroached = (struct edge *) poolalloc(&badsegments); + shellecopy(brokenshelle, *encroached); + } + } + } + /* Return a handle whose primary edge contains the point, */ + /* which has not been inserted. */ + triedgecopy(horiz, *searchtri); + triedgecopy(horiz, recenttri); + return VIOLATINGPOINT; + } + } + /* Insert the point on an edge, dividing one triangle into two (if */ + /* the edge lies on a boundary) or two triangles into four. */ + lprev(horiz, botright); + sym(botright, botrcasing); + sym(horiz, topright); + /* Is there a second triangle? (Or does this edge lie on a boundary?) */ + mirrorflag = topright.tri != dummytri; + if (mirrorflag) { + lnextself(topright); + sym(topright, toprcasing); + maketriangle(&newtopright); + } else { + /* Splitting the boundary edge increases the number of boundary edges. */ + hullsize++; + } + maketriangle(&newbotright); + + /* Set the vertices of changed and new triangles. */ + org(horiz, rightpoint); + dest(horiz, leftpoint); + apex(horiz, botpoint); + setorg(newbotright, botpoint); + setdest(newbotright, rightpoint); + setapex(newbotright, insertpoint); + setorg(horiz, insertpoint); + for (i = 0; i < eextras; i++) { + /* Set the element attributes of a new triangle. */ + setelemattribute(newbotright, i, elemattribute(botright, i)); + } + if (vararea) { + /* Set the area constraint of a new triangle. */ + setareabound(newbotright, areabound(botright)); + } + if (mirrorflag) { + dest(topright, toppoint); + setorg(newtopright, rightpoint); + setdest(newtopright, toppoint); + setapex(newtopright, insertpoint); + setorg(topright, insertpoint); + for (i = 0; i < eextras; i++) { + /* Set the element attributes of another new triangle. */ + setelemattribute(newtopright, i, elemattribute(topright, i)); + } + if (vararea) { + /* Set the area constraint of another new triangle. */ + setareabound(newtopright, areabound(topright)); + } + } + + /* There may be shell edges that need to be bonded */ + /* to the new triangle(s). */ + if (checksegments) { + tspivot(botright, botrshelle); + if (botrshelle.sh != dummysh) { + tsdissolve(botright); + tsbond(newbotright, botrshelle); + } + if (mirrorflag) { + tspivot(topright, toprshelle); + if (toprshelle.sh != dummysh) { + tsdissolve(topright); + tsbond(newtopright, toprshelle); + } + } + } + + /* Bond the new triangle(s) to the surrounding triangles. */ + bond(newbotright, botrcasing); + lprevself(newbotright); + bond(newbotright, botright); + lprevself(newbotright); + if (mirrorflag) { + bond(newtopright, toprcasing); + lnextself(newtopright); + bond(newtopright, topright); + lnextself(newtopright); + bond(newtopright, newbotright); + } + + if (splitedge != (struct edge *) NULL) { + /* Split the shell edge into two. */ + setsdest(*splitedge, insertpoint); + ssymself(*splitedge); + spivot(*splitedge, rightedge); + insertshelle(&newbotright, mark(*splitedge)); + tspivot(newbotright, newedge); + sbond(*splitedge, newedge); + ssymself(newedge); + sbond(newedge, rightedge); + ssymself(*splitedge); + } + +#ifdef SELF_CHECK + if (counterclockwise(rightpoint, leftpoint, botpoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf(" Clockwise triangle prior to edge point insertion (bottom).\n"); + } + if (mirrorflag) { + if (counterclockwise(leftpoint, rightpoint, toppoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf(" Clockwise triangle prior to edge point insertion (top).\n"); + } + if (counterclockwise(rightpoint, toppoint, insertpoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf(" Clockwise triangle after edge point insertion (top right).\n" + ); + } + if (counterclockwise(toppoint, leftpoint, insertpoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf(" Clockwise triangle after edge point insertion (top left).\n" + ); + } + } + if (counterclockwise(leftpoint, botpoint, insertpoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf(" Clockwise triangle after edge point insertion (bottom left).\n" + ); + } + if (counterclockwise(botpoint, rightpoint, insertpoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf( + " Clockwise triangle after edge point insertion (bottom right).\n"); + } +#endif /* SELF_CHECK */ + if (verbose > 2) { + printf(" Updating bottom left "); + printtriangle(&botright); + if (mirrorflag) { + printf(" Updating top left "); + printtriangle(&topright); + printf(" Creating top right "); + printtriangle(&newtopright); + } + printf(" Creating bottom right "); + printtriangle(&newbotright); + } + + /* Position `horiz' on the first edge to check for */ + /* the Delaunay property. */ + lnextself(horiz); + } else { + /* Insert the point in a triangle, splitting it into three. */ + lnext(horiz, botleft); + lprev(horiz, botright); + sym(botleft, botlcasing); + sym(botright, botrcasing); + maketriangle(&newbotleft); + maketriangle(&newbotright); + + /* Set the vertices of changed and new triangles. */ + org(horiz, rightpoint); + dest(horiz, leftpoint); + apex(horiz, botpoint); + setorg(newbotleft, leftpoint); + setdest(newbotleft, botpoint); + setapex(newbotleft, insertpoint); + setorg(newbotright, botpoint); + setdest(newbotright, rightpoint); + setapex(newbotright, insertpoint); + setapex(horiz, insertpoint); + for (i = 0; i < eextras; i++) { + /* Set the element attributes of the new triangles. */ + attrib = elemattribute(horiz, i); + setelemattribute(newbotleft, i, attrib); + setelemattribute(newbotright, i, attrib); + } + if (vararea) { + /* Set the area constraint of the new triangles. */ + area = areabound(horiz); + setareabound(newbotleft, area); + setareabound(newbotright, area); + } + + /* There may be shell edges that need to be bonded */ + /* to the new triangles. */ + if (checksegments) { + tspivot(botleft, botlshelle); + if (botlshelle.sh != dummysh) { + tsdissolve(botleft); + tsbond(newbotleft, botlshelle); + } + tspivot(botright, botrshelle); + if (botrshelle.sh != dummysh) { + tsdissolve(botright); + tsbond(newbotright, botrshelle); + } + } + + /* Bond the new triangles to the surrounding triangles. */ + bond(newbotleft, botlcasing); + bond(newbotright, botrcasing); + lnextself(newbotleft); + lprevself(newbotright); + bond(newbotleft, newbotright); + lnextself(newbotleft); + bond(botleft, newbotleft); + lprevself(newbotright); + bond(botright, newbotright); + +#ifdef SELF_CHECK + if (counterclockwise(rightpoint, leftpoint, botpoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf(" Clockwise triangle prior to point insertion.\n"); + } + if (counterclockwise(rightpoint, leftpoint, insertpoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf(" Clockwise triangle after point insertion (top).\n"); + } + if (counterclockwise(leftpoint, botpoint, insertpoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf(" Clockwise triangle after point insertion (left).\n"); + } + if (counterclockwise(botpoint, rightpoint, insertpoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf(" Clockwise triangle after point insertion (right).\n"); + } +#endif /* SELF_CHECK */ + if (verbose > 2) { + printf(" Updating top "); + printtriangle(&horiz); + printf(" Creating left "); + printtriangle(&newbotleft); + printf(" Creating right "); + printtriangle(&newbotright); + } + } + + /* The insertion is successful by default, unless an encroached */ + /* edge is found. */ + success = SUCCESSFULPOINT; + /* Circle around the newly inserted vertex, checking each edge opposite */ + /* it for the Delaunay property. Non-Delaunay edges are flipped. */ + /* `horiz' is always the edge being checked. `first' marks where to */ + /* stop circling. */ + org(horiz, first); + rightpoint = first; + dest(horiz, leftpoint); + /* Circle until finished. */ + while (1) { + /* By default, the edge will be flipped. */ + doflip = 1; + if (checksegments) { + /* Check for a segment, which cannot be flipped. */ + tspivot(horiz, checkshelle); + if (checkshelle.sh != dummysh) { + /* The edge is a segment and cannot be flipped. */ + doflip = 0; +#ifndef CDT_ONLY + if (segmentflaws) { + /* Does the new point encroach upon this segment? */ + if (checkedge4encroach(&checkshelle)) { + success = ENCROACHINGPOINT; + } + } +#endif /* not CDT_ONLY */ + } + } + if (doflip) { + /* Check if the edge is a boundary edge. */ + sym(horiz, top); + if (top.tri == dummytri) { + /* The edge is a boundary edge and cannot be flipped. */ + doflip = 0; + } else { + /* Find the point on the other side of the edge. */ + apex(top, farpoint); + /* In the incremental Delaunay triangulation algorithm, any of */ + /* `leftpoint', `rightpoint', and `farpoint' could be vertices */ + /* of the triangular bounding box. These vertices must be */ + /* treated as if they are infinitely distant, even though their */ + /* "coordinates" are not. */ + if ((leftpoint == infpoint1) || (leftpoint == infpoint2) + || (leftpoint == infpoint3)) { + /* `leftpoint' is infinitely distant. Check the convexity of */ + /* the boundary of the triangulation. 'farpoint' might be */ + /* infinite as well, but trust me, this same condition */ + /* should be applied. */ + doflip = counterclockwise(insertpoint, rightpoint, farpoint) > 0.0; + } else if ((rightpoint == infpoint1) || (rightpoint == infpoint2) + || (rightpoint == infpoint3)) { + /* `rightpoint' is infinitely distant. Check the convexity of */ + /* the boundary of the triangulation. 'farpoint' might be */ + /* infinite as well, but trust me, this same condition */ + /* should be applied. */ + doflip = counterclockwise(farpoint, leftpoint, insertpoint) > 0.0; + } else if ((farpoint == infpoint1) || (farpoint == infpoint2) + || (farpoint == infpoint3)) { + /* `farpoint' is infinitely distant and cannot be inside */ + /* the circumcircle of the triangle `horiz'. */ + doflip = 0; + } else { + /* Test whether the edge is locally Delaunay. */ + doflip = incircle(leftpoint, insertpoint, rightpoint, farpoint) + > 0.0; + } + if (doflip) { + /* We made it! Flip the edge `horiz' by rotating its containing */ + /* quadrilateral (the two triangles adjacent to `horiz'). */ + /* Identify the casing of the quadrilateral. */ + lprev(top, topleft); + sym(topleft, toplcasing); + lnext(top, topright); + sym(topright, toprcasing); + lnext(horiz, botleft); + sym(botleft, botlcasing); + lprev(horiz, botright); + sym(botright, botrcasing); + /* Rotate the quadrilateral one-quarter turn counterclockwise. */ + bond(topleft, botlcasing); + bond(botleft, botrcasing); + bond(botright, toprcasing); + bond(topright, toplcasing); + if (checksegments) { + /* Check for shell edges and rebond them to the quadrilateral. */ + tspivot(topleft, toplshelle); + tspivot(botleft, botlshelle); + tspivot(botright, botrshelle); + tspivot(topright, toprshelle); + if (toplshelle.sh == dummysh) { + tsdissolve(topright); + } else { + tsbond(topright, toplshelle); + } + if (botlshelle.sh == dummysh) { + tsdissolve(topleft); + } else { + tsbond(topleft, botlshelle); + } + if (botrshelle.sh == dummysh) { + tsdissolve(botleft); + } else { + tsbond(botleft, botrshelle); + } + if (toprshelle.sh == dummysh) { + tsdissolve(botright); + } else { + tsbond(botright, toprshelle); + } + } + /* New point assignments for the rotated quadrilateral. */ + setorg(horiz, farpoint); + setdest(horiz, insertpoint); + setapex(horiz, rightpoint); + setorg(top, insertpoint); + setdest(top, farpoint); + setapex(top, leftpoint); + for (i = 0; i < eextras; i++) { + /* Take the average of the two triangles' attributes. */ + attrib = (REAL)(0.5 * (elemattribute(top, i) + elemattribute(horiz, i))); + setelemattribute(top, i, attrib); + setelemattribute(horiz, i, attrib); + } + if (vararea) { + if ((areabound(top) <= 0.0) || (areabound(horiz) <= 0.0)) { + area = -1.0; + } else { + /* Take the average of the two triangles' area constraints. */ + /* This prevents small area constraints from migrating a */ + /* long, long way from their original location due to flips. */ + area = (REAL)(0.5 * (areabound(top) + areabound(horiz))); + } + setareabound(top, area); + setareabound(horiz, area); + } +#ifdef SELF_CHECK + if (insertpoint != (point) NULL) { + if (counterclockwise(leftpoint, insertpoint, rightpoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf(" Clockwise triangle prior to edge flip (bottom).\n"); + } + /* The following test has been removed because constrainededge() */ + /* sometimes generates inverted triangles that insertsite() */ + /* removes. */ +/* + if (counterclockwise(rightpoint, farpoint, leftpoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf(" Clockwise triangle prior to edge flip (top).\n"); + } +*/ + if (counterclockwise(farpoint, leftpoint, insertpoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf(" Clockwise triangle after edge flip (left).\n"); + } + if (counterclockwise(insertpoint, rightpoint, farpoint) < 0.0) { + printf("Internal error in insertsite():\n"); + printf(" Clockwise triangle after edge flip (right).\n"); + } + } +#endif /* SELF_CHECK */ + if (verbose > 2) { + printf(" Edge flip results in left "); + lnextself(topleft); + printtriangle(&topleft); + printf(" and right "); + printtriangle(&horiz); + } + /* On the next iterations, consider the two edges that were */ + /* exposed (this is, are now visible to the newly inserted */ + /* point) by the edge flip. */ + lprevself(horiz); + leftpoint = farpoint; + } + } + } + if (!doflip) { + /* The handle `horiz' is accepted as locally Delaunay. */ +#ifndef CDT_ONLY + if (triflaws) { + /* Check the triangle `horiz' for quality. */ + testtriangle(&horiz); + } +#endif /* not CDT_ONLY */ + /* Look for the next edge around the newly inserted point. */ + lnextself(horiz); + sym(horiz, testtri); + /* Check for finishing a complete revolution about the new point, or */ + /* falling off the edge of the triangulation. The latter will */ + /* happen when a point is inserted at a boundary. */ + if ((leftpoint == first) || (testtri.tri == dummytri)) { + /* We're done. Return a triangle whose origin is the new point. */ + lnext(horiz, *searchtri); + lnext(horiz, recenttri); + return success; + } + /* Finish finding the next edge around the newly inserted point. */ + lnext(testtri, horiz); + rightpoint = leftpoint; + dest(horiz, leftpoint); + } + } +} + +/*****************************************************************************/ +/* */ +/* triangulatepolygon() Find the Delaunay triangulation of a polygon that */ +/* has a certain "nice" shape. This includes the */ +/* polygons that result from deletion of a point or */ +/* insertion of a segment. */ +/* */ +/* This is a conceptually difficult routine. The starting assumption is */ +/* that we have a polygon with n sides. n - 1 of these sides are currently */ +/* represented as edges in the mesh. One side, called the "base", need not */ +/* be. */ +/* */ +/* Inside the polygon is a structure I call a "fan", consisting of n - 1 */ +/* triangles that share a common origin. For each of these triangles, the */ +/* edge opposite the origin is one of the sides of the polygon. The */ +/* primary edge of each triangle is the edge directed from the origin to */ +/* the destination; note that this is not the same edge that is a side of */ +/* the polygon. `firstedge' is the primary edge of the first triangle. */ +/* From there, the triangles follow in counterclockwise order about the */ +/* polygon, until `lastedge', the primary edge of the last triangle. */ +/* `firstedge' and `lastedge' are probably connected to other triangles */ +/* beyond the extremes of the fan, but their identity is not important, as */ +/* long as the fan remains connected to them. */ +/* */ +/* Imagine the polygon oriented so that its base is at the bottom. This */ +/* puts `firstedge' on the far right, and `lastedge' on the far left. */ +/* The right vertex of the base is the destination of `firstedge', and the */ +/* left vertex of the base is the apex of `lastedge'. */ +/* */ +/* The challenge now is to find the right sequence of edge flips to */ +/* transform the fan into a Delaunay triangulation of the polygon. Each */ +/* edge flip effectively removes one triangle from the fan, committing it */ +/* to the polygon. The resulting polygon has one fewer edge. If `doflip' */ +/* is set, the final flip will be performed, resulting in a fan of one */ +/* (useless?) triangle. If `doflip' is not set, the final flip is not */ +/* performed, resulting in a fan of two triangles, and an unfinished */ +/* triangular polygon that is not yet filled out with a single triangle. */ +/* On completion of the routine, `lastedge' is the last remaining triangle, */ +/* or the leftmost of the last two. */ +/* */ +/* Although the flips are performed in the order described above, the */ +/* decisions about what flips to perform are made in precisely the reverse */ +/* order. The recursive triangulatepolygon() procedure makes a decision, */ +/* uses up to two recursive calls to triangulate the "subproblems" */ +/* (polygons with fewer edges), and then performs an edge flip. */ +/* */ +/* The "decision" it makes is which vertex of the polygon should be */ +/* connected to the base. This decision is made by testing every possible */ +/* vertex. Once the best vertex is found, the two edges that connect this */ +/* vertex to the base become the bases for two smaller polygons. These */ +/* are triangulated recursively. Unfortunately, this approach can take */ +/* O(n^2) time not only in the worst case, but in many common cases. It's */ +/* rarely a big deal for point deletion, where n is rarely larger than ten, */ +/* but it could be a big deal for segment insertion, especially if there's */ +/* a lot of long segments that each cut many triangles. I ought to code */ +/* a faster algorithm some time. */ +/* */ +/* The `edgecount' parameter is the number of sides of the polygon, */ +/* including its base. `triflaws' is a flag that determines whether the */ +/* new triangles should be tested for quality, and enqueued if they are */ +/* bad. */ +/* */ +/*****************************************************************************/ + +void triangulatepolygon(firstedge, lastedge, edgecount, doflip, triflaws) +struct triedge *firstedge; +struct triedge *lastedge; +int edgecount; +int doflip; +int triflaws; +{ + struct triedge testtri; + struct triedge besttri; + struct triedge tempedge; + point leftbasepoint, rightbasepoint; + point testpoint; + point bestpoint; + int bestnumber; + int i; + triangle ptr; /* Temporary variable used by sym(), onext(), and oprev(). */ + + /* Identify the base vertices. */ + apex(*lastedge, leftbasepoint); + dest(*firstedge, rightbasepoint); + if (verbose > 2) { + printf(" Triangulating interior polygon at edge\n"); + printf(" (%.12g, %.12g) (%.12g, %.12g)\n", leftbasepoint[0], + leftbasepoint[1], rightbasepoint[0], rightbasepoint[1]); + } + /* Find the best vertex to connect the base to. */ + onext(*firstedge, besttri); + dest(besttri, bestpoint); + triedgecopy(besttri, testtri); + bestnumber = 1; + for (i = 2; i <= edgecount - 2; i++) { + onextself(testtri); + dest(testtri, testpoint); + /* Is this a better vertex? */ + if (incircle(leftbasepoint, rightbasepoint, bestpoint, testpoint) > 0.0) { + triedgecopy(testtri, besttri); + bestpoint = testpoint; + bestnumber = i; + } + } + if (verbose > 2) { + printf(" Connecting edge to (%.12g, %.12g)\n", bestpoint[0], + bestpoint[1]); + } + if (bestnumber > 1) { + /* Recursively triangulate the smaller polygon on the right. */ + oprev(besttri, tempedge); + triangulatepolygon(firstedge, &tempedge, bestnumber + 1, 1, triflaws); + } + if (bestnumber < edgecount - 2) { + /* Recursively triangulate the smaller polygon on the left. */ + sym(besttri, tempedge); + triangulatepolygon(&besttri, lastedge, edgecount - bestnumber, 1, + triflaws); + /* Find `besttri' again; it may have been lost to edge flips. */ + sym(tempedge, besttri); + } + if (doflip) { + /* Do one final edge flip. */ + flip(&besttri); +#ifndef CDT_ONLY + if (triflaws) { + /* Check the quality of the newly committed triangle. */ + sym(besttri, testtri); + testtriangle(&testtri); + } +#endif /* not CDT_ONLY */ + } + /* Return the base triangle. */ + triedgecopy(besttri, *lastedge); +} + +/*****************************************************************************/ +/* */ +/* deletesite() Delete a vertex from a Delaunay triangulation, ensuring */ +/* that the triangulation remains Delaunay. */ +/* */ +/* The origin of `deltri' is deleted. The union of the triangles adjacent */ +/* to this point is a polygon, for which the Delaunay triangulation is */ +/* found. Two triangles are removed from the mesh. */ +/* */ +/* Only interior points that do not lie on segments (shell edges) or */ +/* boundaries may be deleted. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +void deletesite(deltri) +struct triedge *deltri; +{ + struct triedge countingtri; + struct triedge firstedge, lastedge; + struct triedge deltriright; + struct triedge lefttri, righttri; + struct triedge leftcasing, rightcasing; + struct edge leftshelle, rightshelle; + point delpoint; + point neworg; + int edgecount; + triangle ptr; /* Temporary variable used by sym(), onext(), and oprev(). */ + shelle sptr; /* Temporary variable used by tspivot(). */ + + org(*deltri, delpoint); + if (verbose > 1) { + printf(" Deleting (%.12g, %.12g).\n", delpoint[0], delpoint[1]); + } + pointdealloc(delpoint); + + /* Count the degree of the point being deleted. */ + onext(*deltri, countingtri); + edgecount = 1; + while (!triedgeequal(*deltri, countingtri)) { +#ifdef SELF_CHECK + if (countingtri.tri == dummytri) { + printf("Internal error in deletesite():\n"); + printf(" Attempt to delete boundary point.\n"); + internalerror(); + } +#endif /* SELF_CHECK */ + edgecount++; + onextself(countingtri); + } + +#ifdef SELF_CHECK + if (edgecount < 3) { + printf("Internal error in deletesite():\n Point has degree %d.\n", + edgecount); + internalerror(); + } +#endif /* SELF_CHECK */ + if (edgecount > 3) { + /* Triangulate the polygon defined by the union of all triangles */ + /* adjacent to the point being deleted. Check the quality of */ + /* the resulting triangles. */ + onext(*deltri, firstedge); + oprev(*deltri, lastedge); + triangulatepolygon(&firstedge, &lastedge, edgecount, 0, !nobisect); + } + /* Splice out two triangles. */ + lprev(*deltri, deltriright); + dnext(*deltri, lefttri); + sym(lefttri, leftcasing); + oprev(deltriright, righttri); + sym(righttri, rightcasing); + bond(*deltri, leftcasing); + bond(deltriright, rightcasing); + tspivot(lefttri, leftshelle); + if (leftshelle.sh != dummysh) { + tsbond(*deltri, leftshelle); + } + tspivot(righttri, rightshelle); + if (rightshelle.sh != dummysh) { + tsbond(deltriright, rightshelle); + } + + /* Set the new origin of `deltri' and check its quality. */ + org(lefttri, neworg); + setorg(*deltri, neworg); + if (!nobisect) { + testtriangle(deltri); + } + + /* Delete the two spliced-out triangles. */ + triangledealloc(lefttri.tri); + triangledealloc(righttri.tri); +} + +#endif /* not CDT_ONLY */ + +/** **/ +/** **/ +/********* Mesh transformation routines end here *********/ + +/********* Divide-and-conquer Delaunay triangulation begins here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* The divide-and-conquer bounding box */ +/* */ +/* I originally implemented the divide-and-conquer and incremental Delaunay */ +/* triangulations using the edge-based data structure presented by Guibas */ +/* and Stolfi. Switching to a triangle-based data structure doubled the */ +/* speed. However, I had to think of a few extra tricks to maintain the */ +/* elegance of the original algorithms. */ +/* */ +/* The "bounding box" used by my variant of the divide-and-conquer */ +/* algorithm uses one triangle for each edge of the convex hull of the */ +/* triangulation. These bounding triangles all share a common apical */ +/* vertex, which is represented by NULL and which represents nothing. */ +/* The bounding triangles are linked in a circular fan about this NULL */ +/* vertex, and the edges on the convex hull of the triangulation appear */ +/* opposite the NULL vertex. You might find it easiest to imagine that */ +/* the NULL vertex is a point in 3D space behind the center of the */ +/* triangulation, and that the bounding triangles form a sort of cone. */ +/* */ +/* This bounding box makes it easy to represent degenerate cases. For */ +/* instance, the triangulation of two vertices is a single edge. This edge */ +/* is represented by two bounding box triangles, one on each "side" of the */ +/* edge. These triangles are also linked together in a fan about the NULL */ +/* vertex. */ +/* */ +/* The bounding box also makes it easy to traverse the convex hull, as the */ +/* divide-and-conquer algorithm needs to do. */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* pointsort() Sort an array of points by x-coordinate, using the */ +/* y-coordinate as a secondary key. */ +/* */ +/* Uses quicksort. Randomized O(n log n) time. No, I did not make any of */ +/* the usual quicksort mistakes. */ +/* */ +/*****************************************************************************/ + +void pointsort(sortarray, arraysize) +point *sortarray; +int arraysize; +{ + int left, right; + int pivot; + REAL pivotx, pivoty; + point temp; + + if (arraysize == 2) { + /* Recursive base case. */ + if ((sortarray[0][0] > sortarray[1][0]) || + ((sortarray[0][0] == sortarray[1][0]) && + (sortarray[0][1] > sortarray[1][1]))) { + temp = sortarray[1]; + sortarray[1] = sortarray[0]; + sortarray[0] = temp; + } + return; + } + /* Choose a random pivot to split the array. */ + pivot = (int) randomnation(arraysize); + pivotx = sortarray[pivot][0]; + pivoty = sortarray[pivot][1]; + /* Split the array. */ + left = -1; + right = arraysize; + while (left < right) { + /* Search for a point whose x-coordinate is too large for the left. */ + do { + left++; + } while ((left <= right) && ((sortarray[left][0] < pivotx) || + ((sortarray[left][0] == pivotx) && + (sortarray[left][1] < pivoty)))); + /* Search for a point whose x-coordinate is too small for the right. */ + do { + right--; + } while ((left <= right) && ((sortarray[right][0] > pivotx) || + ((sortarray[right][0] == pivotx) && + (sortarray[right][1] > pivoty)))); + if (left < right) { + /* Swap the left and right points. */ + temp = sortarray[left]; + sortarray[left] = sortarray[right]; + sortarray[right] = temp; + } + } + if (left > 1) { + /* Recursively sort the left subset. */ + pointsort(sortarray, left); + } + if (right < arraysize - 2) { + /* Recursively sort the right subset. */ + pointsort(&sortarray[right + 1], arraysize - right - 1); + } +} + +/*****************************************************************************/ +/* */ +/* pointmedian() An order statistic algorithm, almost. Shuffles an array */ +/* of points so that the first `median' points occur */ +/* lexicographically before the remaining points. */ +/* */ +/* Uses the x-coordinate as the primary key if axis == 0; the y-coordinate */ +/* if axis == 1. Very similar to the pointsort() procedure, but runs in */ +/* randomized linear time. */ +/* */ +/*****************************************************************************/ + +void pointmedian(sortarray, arraysize, median, axis) +point *sortarray; +int arraysize; +int median; +int axis; +{ + int left, right; + int pivot; + REAL pivot1, pivot2; + point temp; + + if (arraysize == 2) { + /* Recursive base case. */ + if ((sortarray[0][axis] > sortarray[1][axis]) || + ((sortarray[0][axis] == sortarray[1][axis]) && + (sortarray[0][1 - axis] > sortarray[1][1 - axis]))) { + temp = sortarray[1]; + sortarray[1] = sortarray[0]; + sortarray[0] = temp; + } + return; + } + /* Choose a random pivot to split the array. */ + pivot = (int) randomnation(arraysize); + pivot1 = sortarray[pivot][axis]; + pivot2 = sortarray[pivot][1 - axis]; + /* Split the array. */ + left = -1; + right = arraysize; + while (left < right) { + /* Search for a point whose x-coordinate is too large for the left. */ + do { + left++; + } while ((left <= right) && ((sortarray[left][axis] < pivot1) || + ((sortarray[left][axis] == pivot1) && + (sortarray[left][1 - axis] < pivot2)))); + /* Search for a point whose x-coordinate is too small for the right. */ + do { + right--; + } while ((left <= right) && ((sortarray[right][axis] > pivot1) || + ((sortarray[right][axis] == pivot1) && + (sortarray[right][1 - axis] > pivot2)))); + if (left < right) { + /* Swap the left and right points. */ + temp = sortarray[left]; + sortarray[left] = sortarray[right]; + sortarray[right] = temp; + } + } + /* Unlike in pointsort(), at most one of the following */ + /* conditionals is true. */ + if (left > median) { + /* Recursively shuffle the left subset. */ + pointmedian(sortarray, left, median, axis); + } + if (right < median - 1) { + /* Recursively shuffle the right subset. */ + pointmedian(&sortarray[right + 1], arraysize - right - 1, + median - right - 1, axis); + } +} + +/*****************************************************************************/ +/* */ +/* alternateaxes() Sorts the points as appropriate for the divide-and- */ +/* conquer algorithm with alternating cuts. */ +/* */ +/* Partitions by x-coordinate if axis == 0; by y-coordinate if axis == 1. */ +/* For the base case, subsets containing only two or three points are */ +/* always sorted by x-coordinate. */ +/* */ +/*****************************************************************************/ + +void alternateaxes(sortarray, arraysize, axis) +point *sortarray; +int arraysize; +int axis; +{ + int divider; + + divider = arraysize >> 1; + if (arraysize <= 3) { + /* Recursive base case: subsets of two or three points will be */ + /* handled specially, and should always be sorted by x-coordinate. */ + axis = 0; + } + /* Partition with a horizontal or vertical cut. */ + pointmedian(sortarray, arraysize, divider, axis); + /* Recursively partition the subsets with a cross cut. */ + if (arraysize - divider >= 2) { + if (divider >= 2) { + alternateaxes(sortarray, divider, 1 - axis); + } + alternateaxes(&sortarray[divider], arraysize - divider, 1 - axis); + } +} + +/*****************************************************************************/ +/* */ +/* mergehulls() Merge two adjacent Delaunay triangulations into a */ +/* single Delaunay triangulation. */ +/* */ +/* This is similar to the algorithm given by Guibas and Stolfi, but uses */ +/* a triangle-based, rather than edge-based, data structure. */ +/* */ +/* The algorithm walks up the gap between the two triangulations, knitting */ +/* them together. As they are merged, some of their bounding triangles */ +/* are converted into real triangles of the triangulation. The procedure */ +/* pulls each hull's bounding triangles apart, then knits them together */ +/* like the teeth of two gears. The Delaunay property determines, at each */ +/* step, whether the next "tooth" is a bounding triangle of the left hull */ +/* or the right. When a bounding triangle becomes real, its apex is */ +/* changed from NULL to a real point. */ +/* */ +/* Only two new triangles need to be allocated. These become new bounding */ +/* triangles at the top and bottom of the seam. They are used to connect */ +/* the remaining bounding triangles (those that have not been converted */ +/* into real triangles) into a single fan. */ +/* */ +/* On entry, `farleft' and `innerleft' are bounding triangles of the left */ +/* triangulation. The origin of `farleft' is the leftmost vertex, and */ +/* the destination of `innerleft' is the rightmost vertex of the */ +/* triangulation. Similarly, `innerright' and `farright' are bounding */ +/* triangles of the right triangulation. The origin of `innerright' and */ +/* destination of `farright' are the leftmost and rightmost vertices. */ +/* */ +/* On completion, the origin of `farleft' is the leftmost vertex of the */ +/* merged triangulation, and the destination of `farright' is the rightmost */ +/* vertex. */ +/* */ +/*****************************************************************************/ + +void mergehulls(farleft, innerleft, innerright, farright, axis) +struct triedge *farleft; +struct triedge *innerleft; +struct triedge *innerright; +struct triedge *farright; +int axis; +{ + struct triedge leftcand, rightcand; + struct triedge baseedge; + struct triedge nextedge; + struct triedge sidecasing, topcasing, outercasing; + struct triedge checkedge; + point innerleftdest; + point innerrightorg; + point innerleftapex, innerrightapex; + point farleftpt, farrightpt; + point farleftapex, farrightapex; + point lowerleft, lowerright; + point upperleft, upperright; + point nextapex; + point checkvertex; + int changemade; + int badedge; + int leftfinished, rightfinished; + triangle ptr; /* Temporary variable used by sym(). */ + + dest(*innerleft, innerleftdest); + apex(*innerleft, innerleftapex); + org(*innerright, innerrightorg); + apex(*innerright, innerrightapex); + /* Special treatment for horizontal cuts. */ + if (dwyer && (axis == 1)) { + org(*farleft, farleftpt); + apex(*farleft, farleftapex); + dest(*farright, farrightpt); + apex(*farright, farrightapex); + /* The pointers to the extremal points are shifted to point to the */ + /* topmost and bottommost point of each hull, rather than the */ + /* leftmost and rightmost points. */ + while (farleftapex[1] < farleftpt[1]) { + lnextself(*farleft); + symself(*farleft); + farleftpt = farleftapex; + apex(*farleft, farleftapex); + } + sym(*innerleft, checkedge); + apex(checkedge, checkvertex); + while (checkvertex[1] > innerleftdest[1]) { + lnext(checkedge, *innerleft); + innerleftapex = innerleftdest; + innerleftdest = checkvertex; + sym(*innerleft, checkedge); + apex(checkedge, checkvertex); + } + while (innerrightapex[1] < innerrightorg[1]) { + lnextself(*innerright); + symself(*innerright); + innerrightorg = innerrightapex; + apex(*innerright, innerrightapex); + } + sym(*farright, checkedge); + apex(checkedge, checkvertex); + while (checkvertex[1] > farrightpt[1]) { + lnext(checkedge, *farright); + farrightapex = farrightpt; + farrightpt = checkvertex; + sym(*farright, checkedge); + apex(checkedge, checkvertex); + } + } + /* Find a line tangent to and below both hulls. */ + do { + changemade = 0; + /* Make innerleftdest the "bottommost" point of the left hull. */ + if (counterclockwise(innerleftdest, innerleftapex, innerrightorg) > 0.0) { + lprevself(*innerleft); + symself(*innerleft); + innerleftdest = innerleftapex; + apex(*innerleft, innerleftapex); + changemade = 1; + } + /* Make innerrightorg the "bottommost" point of the right hull. */ + if (counterclockwise(innerrightapex, innerrightorg, innerleftdest) > 0.0) { + lnextself(*innerright); + symself(*innerright); + innerrightorg = innerrightapex; + apex(*innerright, innerrightapex); + changemade = 1; + } + } while (changemade); + /* Find the two candidates to be the next "gear tooth". */ + sym(*innerleft, leftcand); + sym(*innerright, rightcand); + /* Create the bottom new bounding triangle. */ + maketriangle(&baseedge); + /* Connect it to the bounding boxes of the left and right triangulations. */ + bond(baseedge, *innerleft); + lnextself(baseedge); + bond(baseedge, *innerright); + lnextself(baseedge); + setorg(baseedge, innerrightorg); + setdest(baseedge, innerleftdest); + /* Apex is intentionally left NULL. */ + if (verbose > 2) { + printf(" Creating base bounding "); + printtriangle(&baseedge); + } + /* Fix the extreme triangles if necessary. */ + org(*farleft, farleftpt); + if (innerleftdest == farleftpt) { + lnext(baseedge, *farleft); + } + dest(*farright, farrightpt); + if (innerrightorg == farrightpt) { + lprev(baseedge, *farright); + } + /* The vertices of the current knitting edge. */ + lowerleft = innerleftdest; + lowerright = innerrightorg; + /* The candidate vertices for knitting. */ + apex(leftcand, upperleft); + apex(rightcand, upperright); + /* Walk up the gap between the two triangulations, knitting them together. */ + while (1) { + /* Have we reached the top? (This isn't quite the right question, */ + /* because even though the left triangulation might seem finished now, */ + /* moving up on the right triangulation might reveal a new point of */ + /* the left triangulation. And vice-versa.) */ + leftfinished = counterclockwise(upperleft, lowerleft, lowerright) <= 0.0; + rightfinished = counterclockwise(upperright, lowerleft, lowerright) <= 0.0; + if (leftfinished && rightfinished) { + /* Create the top new bounding triangle. */ + maketriangle(&nextedge); + setorg(nextedge, lowerleft); + setdest(nextedge, lowerright); + /* Apex is intentionally left NULL. */ + /* Connect it to the bounding boxes of the two triangulations. */ + bond(nextedge, baseedge); + lnextself(nextedge); + bond(nextedge, rightcand); + lnextself(nextedge); + bond(nextedge, leftcand); + if (verbose > 2) { + printf(" Creating top bounding "); + printtriangle(&baseedge); + } + /* Special treatment for horizontal cuts. */ + if (dwyer && (axis == 1)) { + org(*farleft, farleftpt); + apex(*farleft, farleftapex); + dest(*farright, farrightpt); + apex(*farright, farrightapex); + sym(*farleft, checkedge); + apex(checkedge, checkvertex); + /* The pointers to the extremal points are restored to the leftmost */ + /* and rightmost points (rather than topmost and bottommost). */ + while (checkvertex[0] < farleftpt[0]) { + lprev(checkedge, *farleft); + farleftapex = farleftpt; + farleftpt = checkvertex; + sym(*farleft, checkedge); + apex(checkedge, checkvertex); + } + while (farrightapex[0] > farrightpt[0]) { + lprevself(*farright); + symself(*farright); + farrightpt = farrightapex; + apex(*farright, farrightapex); + } + } + return; + } + /* Consider eliminating edges from the left triangulation. */ + if (!leftfinished) { + /* What vertex would be exposed if an edge were deleted? */ + lprev(leftcand, nextedge); + symself(nextedge); + apex(nextedge, nextapex); + /* If nextapex is NULL, then no vertex would be exposed; the */ + /* triangulation would have been eaten right through. */ + if (nextapex != (point) NULL) { + /* Check whether the edge is Delaunay. */ + badedge = incircle(lowerleft, lowerright, upperleft, nextapex) > 0.0; + while (badedge) { + /* Eliminate the edge with an edge flip. As a result, the */ + /* left triangulation will have one more boundary triangle. */ + lnextself(nextedge); + sym(nextedge, topcasing); + lnextself(nextedge); + sym(nextedge, sidecasing); + bond(nextedge, topcasing); + bond(leftcand, sidecasing); + lnextself(leftcand); + sym(leftcand, outercasing); + lprevself(nextedge); + bond(nextedge, outercasing); + /* Correct the vertices to reflect the edge flip. */ + setorg(leftcand, lowerleft); + setdest(leftcand, NULL); + setapex(leftcand, nextapex); + setorg(nextedge, NULL); + setdest(nextedge, upperleft); + setapex(nextedge, nextapex); + /* Consider the newly exposed vertex. */ + upperleft = nextapex; + /* What vertex would be exposed if another edge were deleted? */ + triedgecopy(sidecasing, nextedge); + apex(nextedge, nextapex); + if (nextapex != (point) NULL) { + /* Check whether the edge is Delaunay. */ + badedge = incircle(lowerleft, lowerright, upperleft, nextapex) + > 0.0; + } else { + /* Avoid eating right through the triangulation. */ + badedge = 0; + } + } + } + } + /* Consider eliminating edges from the right triangulation. */ + if (!rightfinished) { + /* What vertex would be exposed if an edge were deleted? */ + lnext(rightcand, nextedge); + symself(nextedge); + apex(nextedge, nextapex); + /* If nextapex is NULL, then no vertex would be exposed; the */ + /* triangulation would have been eaten right through. */ + if (nextapex != (point) NULL) { + /* Check whether the edge is Delaunay. */ + badedge = incircle(lowerleft, lowerright, upperright, nextapex) > 0.0; + while (badedge) { + /* Eliminate the edge with an edge flip. As a result, the */ + /* right triangulation will have one more boundary triangle. */ + lprevself(nextedge); + sym(nextedge, topcasing); + lprevself(nextedge); + sym(nextedge, sidecasing); + bond(nextedge, topcasing); + bond(rightcand, sidecasing); + lprevself(rightcand); + sym(rightcand, outercasing); + lnextself(nextedge); + bond(nextedge, outercasing); + /* Correct the vertices to reflect the edge flip. */ + setorg(rightcand, NULL); + setdest(rightcand, lowerright); + setapex(rightcand, nextapex); + setorg(nextedge, upperright); + setdest(nextedge, NULL); + setapex(nextedge, nextapex); + /* Consider the newly exposed vertex. */ + upperright = nextapex; + /* What vertex would be exposed if another edge were deleted? */ + triedgecopy(sidecasing, nextedge); + apex(nextedge, nextapex); + if (nextapex != (point) NULL) { + /* Check whether the edge is Delaunay. */ + badedge = incircle(lowerleft, lowerright, upperright, nextapex) + > 0.0; + } else { + /* Avoid eating right through the triangulation. */ + badedge = 0; + } + } + } + } + if (leftfinished || (!rightfinished && + (incircle(upperleft, lowerleft, lowerright, upperright) > 0.0))) { + /* Knit the triangulations, adding an edge from `lowerleft' */ + /* to `upperright'. */ + bond(baseedge, rightcand); + lprev(rightcand, baseedge); + setdest(baseedge, lowerleft); + lowerright = upperright; + sym(baseedge, rightcand); + apex(rightcand, upperright); + } else { + /* Knit the triangulations, adding an edge from `upperleft' */ + /* to `lowerright'. */ + bond(baseedge, leftcand); + lnext(leftcand, baseedge); + setorg(baseedge, lowerright); + lowerleft = upperleft; + sym(baseedge, leftcand); + apex(leftcand, upperleft); + } + if (verbose > 2) { + printf(" Connecting "); + printtriangle(&baseedge); + } + } +} + +/*****************************************************************************/ +/* */ +/* divconqrecurse() Recursively form a Delaunay triangulation by the */ +/* divide-and-conquer method. */ +/* */ +/* Recursively breaks down the problem into smaller pieces, which are */ +/* knitted together by mergehulls(). The base cases (problems of two or */ +/* three points) are handled specially here. */ +/* */ +/* On completion, `farleft' and `farright' are bounding triangles such that */ +/* the origin of `farleft' is the leftmost vertex (breaking ties by */ +/* choosing the highest leftmost vertex), and the destination of */ +/* `farright' is the rightmost vertex (breaking ties by choosing the */ +/* lowest rightmost vertex). */ +/* */ +/*****************************************************************************/ + +void divconqrecurse(sortarray, vertices, axis, farleft, farright) +point *sortarray; +int vertices; +int axis; +struct triedge *farleft; +struct triedge *farright; +{ + struct triedge midtri, tri1, tri2, tri3; + struct triedge innerleft, innerright; + REAL area; + int divider; + + if (verbose > 2) { + printf(" Triangulating %d points.\n", vertices); + } + if (vertices == 2) { + /* The triangulation of two vertices is an edge. An edge is */ + /* represented by two bounding triangles. */ + maketriangle(farleft); + setorg(*farleft, sortarray[0]); + setdest(*farleft, sortarray[1]); + /* The apex is intentionally left NULL. */ + maketriangle(farright); + setorg(*farright, sortarray[1]); + setdest(*farright, sortarray[0]); + /* The apex is intentionally left NULL. */ + bond(*farleft, *farright); + lprevself(*farleft); + lnextself(*farright); + bond(*farleft, *farright); + lprevself(*farleft); + lnextself(*farright); + bond(*farleft, *farright); + if (verbose > 2) { + printf(" Creating "); + printtriangle(farleft); + printf(" Creating "); + printtriangle(farright); + } + /* Ensure that the origin of `farleft' is sortarray[0]. */ + lprev(*farright, *farleft); + return; + } else if (vertices == 3) { + /* The triangulation of three vertices is either a triangle (with */ + /* three bounding triangles) or two edges (with four bounding */ + /* triangles). In either case, four triangles are created. */ + maketriangle(&midtri); + maketriangle(&tri1); + maketriangle(&tri2); + maketriangle(&tri3); + area = counterclockwise(sortarray[0], sortarray[1], sortarray[2]); + if (area == 0.0) { + /* Three collinear points; the triangulation is two edges. */ + setorg(midtri, sortarray[0]); + setdest(midtri, sortarray[1]); + setorg(tri1, sortarray[1]); + setdest(tri1, sortarray[0]); + setorg(tri2, sortarray[2]); + setdest(tri2, sortarray[1]); + setorg(tri3, sortarray[1]); + setdest(tri3, sortarray[2]); + /* All apices are intentionally left NULL. */ + bond(midtri, tri1); + bond(tri2, tri3); + lnextself(midtri); + lprevself(tri1); + lnextself(tri2); + lprevself(tri3); + bond(midtri, tri3); + bond(tri1, tri2); + lnextself(midtri); + lprevself(tri1); + lnextself(tri2); + lprevself(tri3); + bond(midtri, tri1); + bond(tri2, tri3); + /* Ensure that the origin of `farleft' is sortarray[0]. */ + triedgecopy(tri1, *farleft); + /* Ensure that the destination of `farright' is sortarray[2]. */ + triedgecopy(tri2, *farright); + } else { + /* The three points are not collinear; the triangulation is one */ + /* triangle, namely `midtri'. */ + setorg(midtri, sortarray[0]); + setdest(tri1, sortarray[0]); + setorg(tri3, sortarray[0]); + /* Apices of tri1, tri2, and tri3 are left NULL. */ + if (area > 0.0) { + /* The vertices are in counterclockwise order. */ + setdest(midtri, sortarray[1]); + setorg(tri1, sortarray[1]); + setdest(tri2, sortarray[1]); + setapex(midtri, sortarray[2]); + setorg(tri2, sortarray[2]); + setdest(tri3, sortarray[2]); + } else { + /* The vertices are in clockwise order. */ + setdest(midtri, sortarray[2]); + setorg(tri1, sortarray[2]); + setdest(tri2, sortarray[2]); + setapex(midtri, sortarray[1]); + setorg(tri2, sortarray[1]); + setdest(tri3, sortarray[1]); + } + /* The topology does not depend on how the vertices are ordered. */ + bond(midtri, tri1); + lnextself(midtri); + bond(midtri, tri2); + lnextself(midtri); + bond(midtri, tri3); + lprevself(tri1); + lnextself(tri2); + bond(tri1, tri2); + lprevself(tri1); + lprevself(tri3); + bond(tri1, tri3); + lnextself(tri2); + lprevself(tri3); + bond(tri2, tri3); + /* Ensure that the origin of `farleft' is sortarray[0]. */ + triedgecopy(tri1, *farleft); + /* Ensure that the destination of `farright' is sortarray[2]. */ + if (area > 0.0) { + triedgecopy(tri2, *farright); + } else { + lnext(*farleft, *farright); + } + } + if (verbose > 2) { + printf(" Creating "); + printtriangle(&midtri); + printf(" Creating "); + printtriangle(&tri1); + printf(" Creating "); + printtriangle(&tri2); + printf(" Creating "); + printtriangle(&tri3); + } + return; + } else { + /* Split the vertices in half. */ + divider = vertices >> 1; + /* Recursively triangulate each half. */ + divconqrecurse(sortarray, divider, 1 - axis, farleft, &innerleft); + divconqrecurse(&sortarray[divider], vertices - divider, 1 - axis, + &innerright, farright); + if (verbose > 1) { + printf(" Joining triangulations with %d and %d vertices.\n", divider, + vertices - divider); + } + /* Merge the two triangulations into one. */ + mergehulls(farleft, &innerleft, &innerright, farright, axis); + } +} + +long removeghosts(startghost) +struct triedge *startghost; +{ + struct triedge searchedge; + struct triedge dissolveedge; + struct triedge deadtri; + point markorg; + long hullsize; + triangle ptr; /* Temporary variable used by sym(). */ + + if (verbose) { + printf(" Removing ghost triangles.\n"); + } + /* Find an edge on the convex hull to start point location from. */ + lprev(*startghost, searchedge); + symself(searchedge); + dummytri[0] = encode(searchedge); + /* Remove the bounding box and count the convex hull edges. */ + triedgecopy(*startghost, dissolveedge); + hullsize = 0; + do { + hullsize++; + lnext(dissolveedge, deadtri); + lprevself(dissolveedge); + symself(dissolveedge); + /* If no PSLG is involved, set the boundary markers of all the points */ + /* on the convex hull. If a PSLG is used, this step is done later. */ + if (!poly) { + /* Watch out for the case where all the input points are collinear. */ + if (dissolveedge.tri != dummytri) { + org(dissolveedge, markorg); + if (pointmark(markorg) == 0) { + setpointmark(markorg, 1); + } + } + } + /* Remove a bounding triangle from a convex hull triangle. */ + dissolve(dissolveedge); + /* Find the next bounding triangle. */ + sym(deadtri, dissolveedge); + /* Delete the bounding triangle. */ + triangledealloc(deadtri.tri); + } while (!triedgeequal(dissolveedge, *startghost)); + return hullsize; +} + +/*****************************************************************************/ +/* */ +/* divconqdelaunay() Form a Delaunay triangulation by the divide-and- */ +/* conquer method. */ +/* */ +/* Sorts the points, calls a recursive procedure to triangulate them, and */ +/* removes the bounding box, setting boundary markers as appropriate. */ +/* */ +/*****************************************************************************/ + +long divconqdelaunay() +{ + point *sortarray; + struct triedge hullleft, hullright; + int divider; + int i, j; + + /* Allocate an array of pointers to points for sorting. */ + sortarray = (point *) malloc(inpoints * sizeof(point)); + if (sortarray == (point *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + traversalinit(&points); + for (i = 0; i < inpoints; i++) { + sortarray[i] = pointtraverse(); + } + if (verbose) { + printf(" Sorting points.\n"); + } + /* Sort the points. */ + pointsort(sortarray, inpoints); + /* Discard duplicate points, which can really mess up the algorithm. */ + i = 0; + for (j = 1; j < inpoints; j++) { + if ((sortarray[i][0] == sortarray[j][0]) + && (sortarray[i][1] == sortarray[j][1])) { + if (!quiet) { + printf( +"Warning: A duplicate point at (%.12g, %.12g) appeared and was ignored.\n", + sortarray[j][0], sortarray[j][1]); + } +/* Commented out - would eliminate point from output .node file, but causes + a failure if some segment has this point as an endpoint. + setpointmark(sortarray[j], DEADPOINT); +*/ + } else { + i++; + sortarray[i] = sortarray[j]; + } + } + i++; + if (dwyer) { + /* Re-sort the array of points to accommodate alternating cuts. */ + divider = i >> 1; + if (i - divider >= 2) { + if (divider >= 2) { + alternateaxes(sortarray, divider, 1); + } + alternateaxes(&sortarray[divider], i - divider, 1); + } + } + if (verbose) { + printf(" Forming triangulation.\n"); + } + /* Form the Delaunay triangulation. */ + divconqrecurse(sortarray, i, 0, &hullleft, &hullright); + free(sortarray); + + return removeghosts(&hullleft); +} + +/** **/ +/** **/ +/********* Divide-and-conquer Delaunay triangulation ends here *********/ + +/********* Incremental Delaunay triangulation begins here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* boundingbox() Form an "infinite" bounding triangle to insert points */ +/* into. */ +/* */ +/* The points at "infinity" are assigned finite coordinates, which are used */ +/* by the point location routines, but (mostly) ignored by the Delaunay */ +/* edge flip routines. */ +/* */ +/*****************************************************************************/ + +#ifndef REDUCED + +void boundingbox() +{ + struct triedge inftri; /* Handle for the triangular bounding box. */ + REAL width; + + if (verbose) { + printf(" Creating triangular bounding box.\n"); + } + /* Find the width (or height, whichever is larger) of the triangulation. */ + width = xmax - xmin; + if (ymax - ymin > width) { + width = ymax - ymin; + } + if (width == 0.0) { + width = 1.0; + } + /* Create the vertices of the bounding box. */ + infpoint1 = (point) malloc(points.itembytes); + infpoint2 = (point) malloc(points.itembytes); + infpoint3 = (point) malloc(points.itembytes); + if ((infpoint1 == (point) NULL) || (infpoint2 == (point) NULL) + || (infpoint3 == (point) NULL)) { + printf("Error: Out of memory.\n"); + exit(1); + } + infpoint1[0] = xmin - 50.0 * width; + infpoint1[1] = ymin - 40.0 * width; + infpoint2[0] = xmax + 50.0 * width; + infpoint2[1] = ymin - 40.0 * width; + infpoint3[0] = 0.5 * (xmin + xmax); + infpoint3[1] = ymax + 60.0 * width; + + /* Create the bounding box. */ + maketriangle(&inftri); + setorg(inftri, infpoint1); + setdest(inftri, infpoint2); + setapex(inftri, infpoint3); + /* Link dummytri to the bounding box so we can always find an */ + /* edge to begin searching (point location) from. */ + dummytri[0] = (triangle) inftri.tri; + if (verbose > 2) { + printf(" Creating "); + printtriangle(&inftri); + } +} + +#endif /* not REDUCED */ + +/*****************************************************************************/ +/* */ +/* removebox() Remove the "infinite" bounding triangle, setting boundary */ +/* markers as appropriate. */ +/* */ +/* The triangular bounding box has three boundary triangles (one for each */ +/* side of the bounding box), and a bunch of triangles fanning out from */ +/* the three bounding box vertices (one triangle for each edge of the */ +/* convex hull of the inner mesh). This routine removes these triangles. */ +/* */ +/*****************************************************************************/ + +#ifndef REDUCED + +long removebox() +{ + struct triedge deadtri; + struct triedge searchedge; + struct triedge checkedge; + struct triedge nextedge, finaledge, dissolveedge; + point markorg; + long hullsize; + triangle ptr; /* Temporary variable used by sym(). */ + + if (verbose) { + printf(" Removing triangular bounding box.\n"); + } + /* Find a boundary triangle. */ + nextedge.tri = dummytri; + nextedge.orient = 0; + symself(nextedge); + /* Mark a place to stop. */ + lprev(nextedge, finaledge); + lnextself(nextedge); + symself(nextedge); + /* Find a triangle (on the boundary of the point set) that isn't */ + /* a bounding box triangle. */ + lprev(nextedge, searchedge); + symself(searchedge); + /* Check whether nextedge is another boundary triangle */ + /* adjacent to the first one. */ + lnext(nextedge, checkedge); + symself(checkedge); + if (checkedge.tri == dummytri) { + /* Go on to the next triangle. There are only three boundary */ + /* triangles, and this next triangle cannot be the third one, */ + /* so it's safe to stop here. */ + lprevself(searchedge); + symself(searchedge); + } + /* Find a new boundary edge to search from, as the current search */ + /* edge lies on a bounding box triangle and will be deleted. */ + dummytri[0] = encode(searchedge); + hullsize = -2l; + while (!triedgeequal(nextedge, finaledge)) { + hullsize++; + lprev(nextedge, dissolveedge); + symself(dissolveedge); + /* If not using a PSLG, the vertices should be marked now. */ + /* (If using a PSLG, markhull() will do the job.) */ + if (!poly) { + /* Be careful! One must check for the case where all the input */ + /* points are collinear, and thus all the triangles are part of */ + /* the bounding box. Otherwise, the setpointmark() call below */ + /* will cause a bad pointer reference. */ + if (dissolveedge.tri != dummytri) { + org(dissolveedge, markorg); + if (pointmark(markorg) == 0) { + setpointmark(markorg, 1); + } + } + } + /* Disconnect the bounding box triangle from the mesh triangle. */ + dissolve(dissolveedge); + lnext(nextedge, deadtri); + sym(deadtri, nextedge); + /* Get rid of the bounding box triangle. */ + triangledealloc(deadtri.tri); + /* Do we need to turn the corner? */ + if (nextedge.tri == dummytri) { + /* Turn the corner. */ + triedgecopy(dissolveedge, nextedge); + } + } + triangledealloc(finaledge.tri); + + free(infpoint1); /* Deallocate the bounding box vertices. */ + free(infpoint2); + free(infpoint3); + + return hullsize; +} + +#endif /* not REDUCED */ + +/*****************************************************************************/ +/* */ +/* incrementaldelaunay() Form a Delaunay triangulation by incrementally */ +/* adding vertices. */ +/* */ +/*****************************************************************************/ + +#ifndef REDUCED + +long incrementaldelaunay() +{ + struct triedge starttri; + point pointloop; + int i; + + /* Create a triangular bounding box. */ + boundingbox(); + if (verbose) { + printf(" Incrementally inserting points.\n"); + } + traversalinit(&points); + pointloop = pointtraverse(); + i = 1; + while (pointloop != (point) NULL) { + /* Find a boundary triangle to search from. */ + starttri.tri = (triangle *) NULL; + if (insertsite(pointloop, &starttri, (struct edge *) NULL, 0, 0) == + DUPLICATEPOINT) { + if (!quiet) { + printf( +"Warning: A duplicate point at (%.12g, %.12g) appeared and was ignored.\n", + pointloop[0], pointloop[1]); + } +/* Commented out - would eliminate point from output .node file. + setpointmark(pointloop, DEADPOINT); +*/ + } + pointloop = pointtraverse(); + i++; + } + /* Remove the bounding box. */ + return removebox(); +} + +#endif /* not REDUCED */ + +/** **/ +/** **/ +/********* Incremental Delaunay triangulation ends here *********/ + +/********* Sweepline Delaunay triangulation begins here *********/ +/** **/ +/** **/ + +#ifndef REDUCED + +void eventheapinsert(heap, heapsize, newevent) +struct event **heap; +int heapsize; +struct event *newevent; +{ + REAL eventx, eventy; + int eventnum; + int parent; + int notdone; + + eventx = newevent->xkey; + eventy = newevent->ykey; + eventnum = heapsize; + notdone = eventnum > 0; + while (notdone) { + parent = (eventnum - 1) >> 1; + if ((heap[parent]->ykey < eventy) || + ((heap[parent]->ykey == eventy) + && (heap[parent]->xkey <= eventx))) { + notdone = 0; + } else { + heap[eventnum] = heap[parent]; + heap[eventnum]->heapposition = eventnum; + + eventnum = parent; + notdone = eventnum > 0; + } + } + heap[eventnum] = newevent; + newevent->heapposition = eventnum; +} + +#endif /* not REDUCED */ + +#ifndef REDUCED + +void eventheapify(heap, heapsize, eventnum) +struct event **heap; +int heapsize; +int eventnum; +{ + struct event *thisevent; + REAL eventx, eventy; + int leftchild, rightchild; + int smallest; + int notdone; + + thisevent = heap[eventnum]; + eventx = thisevent->xkey; + eventy = thisevent->ykey; + leftchild = 2 * eventnum + 1; + notdone = leftchild < heapsize; + while (notdone) { + if ((heap[leftchild]->ykey < eventy) || + ((heap[leftchild]->ykey == eventy) + && (heap[leftchild]->xkey < eventx))) { + smallest = leftchild; + } else { + smallest = eventnum; + } + rightchild = leftchild + 1; + if (rightchild < heapsize) { + if ((heap[rightchild]->ykey < heap[smallest]->ykey) || + ((heap[rightchild]->ykey == heap[smallest]->ykey) + && (heap[rightchild]->xkey < heap[smallest]->xkey))) { + smallest = rightchild; + } + } + if (smallest == eventnum) { + notdone = 0; + } else { + heap[eventnum] = heap[smallest]; + heap[eventnum]->heapposition = eventnum; + heap[smallest] = thisevent; + thisevent->heapposition = smallest; + + eventnum = smallest; + leftchild = 2 * eventnum + 1; + notdone = leftchild < heapsize; + } + } +} + +#endif /* not REDUCED */ + +#ifndef REDUCED + +void eventheapdelete(heap, heapsize, eventnum) +struct event **heap; +int heapsize; +int eventnum; +{ + struct event *moveevent; + REAL eventx, eventy; + int parent; + int notdone; + + moveevent = heap[heapsize - 1]; + if (eventnum > 0) { + eventx = moveevent->xkey; + eventy = moveevent->ykey; + do { + parent = (eventnum - 1) >> 1; + if ((heap[parent]->ykey < eventy) || + ((heap[parent]->ykey == eventy) + && (heap[parent]->xkey <= eventx))) { + notdone = 0; + } else { + heap[eventnum] = heap[parent]; + heap[eventnum]->heapposition = eventnum; + + eventnum = parent; + notdone = eventnum > 0; + } + } while (notdone); + } + heap[eventnum] = moveevent; + moveevent->heapposition = eventnum; + eventheapify(heap, heapsize - 1, eventnum); +} + +#endif /* not REDUCED */ + +#ifndef REDUCED + +void createeventheap(eventheap, events, freeevents) +struct event ***eventheap; +struct event **events; +struct event **freeevents; +{ + point thispoint; + int maxevents; + int i; + + maxevents = (3 * inpoints) / 2; + *eventheap = (struct event **) malloc(maxevents * sizeof(struct event *)); + if (*eventheap == (struct event **) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + *events = (struct event *) malloc(maxevents * sizeof(struct event)); + if (*events == (struct event *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + traversalinit(&points); + for (i = 0; i < inpoints; i++) { + thispoint = pointtraverse(); + (*events)[i].eventptr = (VOID *) thispoint; + (*events)[i].xkey = thispoint[0]; + (*events)[i].ykey = thispoint[1]; + eventheapinsert(*eventheap, i, *events + i); + } + *freeevents = (struct event *) NULL; + for (i = maxevents - 1; i >= inpoints; i--) { + (*events)[i].eventptr = (VOID *) *freeevents; + *freeevents = *events + i; + } +} + +#endif /* not REDUCED */ + +#ifndef REDUCED + +int rightofhyperbola(fronttri, newsite) +struct triedge *fronttri; +point newsite; +{ + point leftpoint, rightpoint; + REAL dxa, dya, dxb, dyb; + + hyperbolacount++; + + dest(*fronttri, leftpoint); + apex(*fronttri, rightpoint); + if ((leftpoint[1] < rightpoint[1]) + || ((leftpoint[1] == rightpoint[1]) && (leftpoint[0] < rightpoint[0]))) { + if (newsite[0] >= rightpoint[0]) { + return 1; + } + } else { + if (newsite[0] <= leftpoint[0]) { + return 0; + } + } + dxa = leftpoint[0] - newsite[0]; + dya = leftpoint[1] - newsite[1]; + dxb = rightpoint[0] - newsite[0]; + dyb = rightpoint[1] - newsite[1]; + return dya * (dxb * dxb + dyb * dyb) > dyb * (dxa * dxa + dya * dya); +} + +#endif /* not REDUCED */ + +#ifndef REDUCED + +REAL circletop(pa, pb, pc, ccwabc) +point pa; +point pb; +point pc; +REAL ccwabc; +{ + REAL xac, yac, xbc, ybc, xab, yab; + REAL aclen2, bclen2, ablen2; + + circletopcount++; + + xac = pa[0] - pc[0]; + yac = pa[1] - pc[1]; + xbc = pb[0] - pc[0]; + ybc = pb[1] - pc[1]; + xab = pa[0] - pb[0]; + yab = pa[1] - pb[1]; + aclen2 = xac * xac + yac * yac; + bclen2 = xbc * xbc + ybc * ybc; + ablen2 = xab * xab + yab * yab; + return pc[1] + (xac * bclen2 - xbc * aclen2 + sqrt(aclen2 * bclen2 * ablen2)) + / (2.0 * ccwabc); +} + +#endif /* not REDUCED */ + +#ifndef REDUCED + +void check4deadevent(checktri, freeevents, eventheap, heapsize) +struct triedge *checktri; +struct event **freeevents; +struct event **eventheap; +int *heapsize; +{ + struct event *deadevent; + point eventpoint; + int eventnum; + + org(*checktri, eventpoint); + if (eventpoint != (point) NULL) { + deadevent = (struct event *) eventpoint; + eventnum = deadevent->heapposition; + deadevent->eventptr = (VOID *) *freeevents; + *freeevents = deadevent; + eventheapdelete(eventheap, *heapsize, eventnum); + (*heapsize)--; + setorg(*checktri, NULL); + } +} + +#endif /* not REDUCED */ + +#ifndef REDUCED + +struct splaynode *splay(splaytree, searchpoint, searchtri) +struct splaynode *splaytree; +point searchpoint; +struct triedge *searchtri; +{ + struct splaynode *child, *grandchild; + struct splaynode *lefttree, *righttree; + struct splaynode *leftright; + point checkpoint; + int rightofroot, rightofchild; + + if (splaytree == (struct splaynode *) NULL) { + return (struct splaynode *) NULL; + } + dest(splaytree->keyedge, checkpoint); + if (checkpoint == splaytree->keydest) { + rightofroot = rightofhyperbola(&splaytree->keyedge, searchpoint); + if (rightofroot) { + triedgecopy(splaytree->keyedge, *searchtri); + child = splaytree->rchild; + } else { + child = splaytree->lchild; + } + if (child == (struct splaynode *) NULL) { + return splaytree; + } + dest(child->keyedge, checkpoint); + if (checkpoint != child->keydest) { + child = splay(child, searchpoint, searchtri); + if (child == (struct splaynode *) NULL) { + if (rightofroot) { + splaytree->rchild = (struct splaynode *) NULL; + } else { + splaytree->lchild = (struct splaynode *) NULL; + } + return splaytree; + } + } + rightofchild = rightofhyperbola(&child->keyedge, searchpoint); + if (rightofchild) { + triedgecopy(child->keyedge, *searchtri); + grandchild = splay(child->rchild, searchpoint, searchtri); + child->rchild = grandchild; + } else { + grandchild = splay(child->lchild, searchpoint, searchtri); + child->lchild = grandchild; + } + if (grandchild == (struct splaynode *) NULL) { + if (rightofroot) { + splaytree->rchild = child->lchild; + child->lchild = splaytree; + } else { + splaytree->lchild = child->rchild; + child->rchild = splaytree; + } + return child; + } + if (rightofchild) { + if (rightofroot) { + splaytree->rchild = child->lchild; + child->lchild = splaytree; + } else { + splaytree->lchild = grandchild->rchild; + grandchild->rchild = splaytree; + } + child->rchild = grandchild->lchild; + grandchild->lchild = child; + } else { + if (rightofroot) { + splaytree->rchild = grandchild->lchild; + grandchild->lchild = splaytree; + } else { + splaytree->lchild = child->rchild; + child->rchild = splaytree; + } + child->lchild = grandchild->rchild; + grandchild->rchild = child; + } + return grandchild; + } else { + lefttree = splay(splaytree->lchild, searchpoint, searchtri); + righttree = splay(splaytree->rchild, searchpoint, searchtri); + + pooldealloc(&splaynodes, (VOID *) splaytree); + if (lefttree == (struct splaynode *) NULL) { + return righttree; + } else if (righttree == (struct splaynode *) NULL) { + return lefttree; + } else if (lefttree->rchild == (struct splaynode *) NULL) { + lefttree->rchild = righttree->lchild; + righttree->lchild = lefttree; + return righttree; + } else if (righttree->lchild == (struct splaynode *) NULL) { + righttree->lchild = lefttree->rchild; + lefttree->rchild = righttree; + return lefttree; + } else { +/* printf("Holy Toledo!!!\n"); */ + leftright = lefttree->rchild; + while (leftright->rchild != (struct splaynode *) NULL) { + leftright = leftright->rchild; + } + leftright->rchild = righttree; + return lefttree; + } + } +} + +#endif /* not REDUCED */ + +#ifndef REDUCED + +struct splaynode *splayinsert(splayroot, newkey, searchpoint) +struct splaynode *splayroot; +struct triedge *newkey; +point searchpoint; +{ + struct splaynode *newsplaynode; + + newsplaynode = (struct splaynode *) poolalloc(&splaynodes); + triedgecopy(*newkey, newsplaynode->keyedge); + dest(*newkey, newsplaynode->keydest); + if (splayroot == (struct splaynode *) NULL) { + newsplaynode->lchild = (struct splaynode *) NULL; + newsplaynode->rchild = (struct splaynode *) NULL; + } else if (rightofhyperbola(&splayroot->keyedge, searchpoint)) { + newsplaynode->lchild = splayroot; + newsplaynode->rchild = splayroot->rchild; + splayroot->rchild = (struct splaynode *) NULL; + } else { + newsplaynode->lchild = splayroot->lchild; + newsplaynode->rchild = splayroot; + splayroot->lchild = (struct splaynode *) NULL; + } + return newsplaynode; +} + +#endif /* not REDUCED */ + +#ifndef REDUCED + +struct splaynode *circletopinsert(splayroot, newkey, pa, pb, pc, topy) +struct splaynode *splayroot; +struct triedge *newkey; +point pa; +point pb; +point pc; +REAL topy; +{ + REAL ccwabc; + REAL xac, yac, xbc, ybc; + REAL aclen2, bclen2; + REAL searchpoint[2]; + struct triedge dummytri; + + ccwabc = counterclockwise(pa, pb, pc); + xac = pa[0] - pc[0]; + yac = pa[1] - pc[1]; + xbc = pb[0] - pc[0]; + ybc = pb[1] - pc[1]; + aclen2 = xac * xac + yac * yac; + bclen2 = xbc * xbc + ybc * ybc; + searchpoint[0] = pc[0] - (yac * bclen2 - ybc * aclen2) / (2.0 * ccwabc); + searchpoint[1] = topy; + return splayinsert(splay(splayroot, (point) searchpoint, &dummytri), newkey, + (point) searchpoint); +} + +#endif /* not REDUCED */ + +#ifndef REDUCED + +struct splaynode *frontlocate(splayroot, bottommost, searchpoint, searchtri, + farright) +struct splaynode *splayroot; +struct triedge *bottommost; +point searchpoint; +struct triedge *searchtri; +int *farright; +{ + int farrightflag; + triangle ptr; /* Temporary variable used by onext(). */ + + triedgecopy(*bottommost, *searchtri); + splayroot = splay(splayroot, searchpoint, searchtri); + + farrightflag = 0; + while (!farrightflag && rightofhyperbola(searchtri, searchpoint)) { + onextself(*searchtri); + farrightflag = triedgeequal(*searchtri, *bottommost); + } + *farright = farrightflag; + return splayroot; +} + +#endif /* not REDUCED */ + +#ifndef REDUCED + +long sweeplinedelaunay() +{ + struct event **eventheap; + struct event *events; + struct event *freeevents; + struct event *nextevent; + struct event *newevent; + struct splaynode *splayroot; + struct triedge bottommost; + struct triedge searchtri; + struct triedge fliptri; + struct triedge lefttri, righttri, farlefttri, farrighttri; + struct triedge inserttri; + point firstpoint, secondpoint; + point nextpoint, lastpoint; + point connectpoint; + point leftpoint, midpoint, rightpoint; + REAL lefttest, righttest; + int heapsize; + int check4events, farrightflag; + triangle ptr; /* Temporary variable used by sym(), onext(), and oprev(). */ + + poolinit(&splaynodes, sizeof(struct splaynode), SPLAYNODEPERBLOCK, POINTER, + 0); + splayroot = (struct splaynode *) NULL; + + if (verbose) { + printf(" Placing points in event heap.\n"); + } + createeventheap(&eventheap, &events, &freeevents); + heapsize = inpoints; + + if (verbose) { + printf(" Forming triangulation.\n"); + } + maketriangle(&lefttri); + maketriangle(&righttri); + bond(lefttri, righttri); + lnextself(lefttri); + lprevself(righttri); + bond(lefttri, righttri); + lnextself(lefttri); + lprevself(righttri); + bond(lefttri, righttri); + firstpoint = (point) eventheap[0]->eventptr; + eventheap[0]->eventptr = (VOID *) freeevents; + freeevents = eventheap[0]; + eventheapdelete(eventheap, heapsize, 0); + heapsize--; + do { + if (heapsize == 0) { + printf("Error: Input points are all identical.\n"); + exit(1); + } + secondpoint = (point) eventheap[0]->eventptr; + eventheap[0]->eventptr = (VOID *) freeevents; + freeevents = eventheap[0]; + eventheapdelete(eventheap, heapsize, 0); + heapsize--; + if ((firstpoint[0] == secondpoint[0]) + && (firstpoint[1] == secondpoint[1])) { + printf( +"Warning: A duplicate point at (%.12g, %.12g) appeared and was ignored.\n", + secondpoint[0], secondpoint[1]); +/* Commented out - would eliminate point from output .node file. + setpointmark(secondpoint, DEADPOINT); +*/ + } + } while ((firstpoint[0] == secondpoint[0]) + && (firstpoint[1] == secondpoint[1])); + setorg(lefttri, firstpoint); + setdest(lefttri, secondpoint); + setorg(righttri, secondpoint); + setdest(righttri, firstpoint); + lprev(lefttri, bottommost); + lastpoint = secondpoint; + while (heapsize > 0) { + nextevent = eventheap[0]; + eventheapdelete(eventheap, heapsize, 0); + heapsize--; + check4events = 1; + if (nextevent->xkey < xmin) { + decode(nextevent->eventptr, fliptri); + oprev(fliptri, farlefttri); + check4deadevent(&farlefttri, &freeevents, eventheap, &heapsize); + onext(fliptri, farrighttri); + check4deadevent(&farrighttri, &freeevents, eventheap, &heapsize); + + if (triedgeequal(farlefttri, bottommost)) { + lprev(fliptri, bottommost); + } + flip(&fliptri); + setapex(fliptri, NULL); + lprev(fliptri, lefttri); + lnext(fliptri, righttri); + sym(lefttri, farlefttri); + + if (randomnation(SAMPLERATE) == 0) { + symself(fliptri); + dest(fliptri, leftpoint); + apex(fliptri, midpoint); + org(fliptri, rightpoint); + splayroot = circletopinsert(splayroot, &lefttri, leftpoint, midpoint, + rightpoint, nextevent->ykey); + } + } else { + nextpoint = (point) nextevent->eventptr; + if ((nextpoint[0] == lastpoint[0]) && (nextpoint[1] == lastpoint[1])) { + printf( +"Warning: A duplicate point at (%.12g, %.12g) appeared and was ignored.\n", + nextpoint[0], nextpoint[1]); +/* Commented out - would eliminate point from output .node file. + setpointmark(nextpoint, DEADPOINT); +*/ + check4events = 0; + } else { + lastpoint = nextpoint; + + splayroot = frontlocate(splayroot, &bottommost, nextpoint, &searchtri, + &farrightflag); +/* + triedgecopy(bottommost, searchtri); + farrightflag = 0; + while (!farrightflag && rightofhyperbola(&searchtri, nextpoint)) { + onextself(searchtri); + farrightflag = triedgeequal(searchtri, bottommost); + } +*/ + + check4deadevent(&searchtri, &freeevents, eventheap, &heapsize); + + triedgecopy(searchtri, farrighttri); + sym(searchtri, farlefttri); + maketriangle(&lefttri); + maketriangle(&righttri); + dest(farrighttri, connectpoint); + setorg(lefttri, connectpoint); + setdest(lefttri, nextpoint); + setorg(righttri, nextpoint); + setdest(righttri, connectpoint); + bond(lefttri, righttri); + lnextself(lefttri); + lprevself(righttri); + bond(lefttri, righttri); + lnextself(lefttri); + lprevself(righttri); + bond(lefttri, farlefttri); + bond(righttri, farrighttri); + if (!farrightflag && triedgeequal(farrighttri, bottommost)) { + triedgecopy(lefttri, bottommost); + } + + if (randomnation(SAMPLERATE) == 0) { + splayroot = splayinsert(splayroot, &lefttri, nextpoint); + } else if (randomnation(SAMPLERATE) == 0) { + lnext(righttri, inserttri); + splayroot = splayinsert(splayroot, &inserttri, nextpoint); + } + } + } + nextevent->eventptr = (VOID *) freeevents; + freeevents = nextevent; + + if (check4events) { + apex(farlefttri, leftpoint); + dest(lefttri, midpoint); + apex(lefttri, rightpoint); + lefttest = counterclockwise(leftpoint, midpoint, rightpoint); + if (lefttest > 0.0) { + newevent = freeevents; + freeevents = (struct event *) freeevents->eventptr; + newevent->xkey = xminextreme; + newevent->ykey = circletop(leftpoint, midpoint, rightpoint, + lefttest); + newevent->eventptr = (VOID *) encode(lefttri); + eventheapinsert(eventheap, heapsize, newevent); + heapsize++; + setorg(lefttri, newevent); + } + apex(righttri, leftpoint); + org(righttri, midpoint); + apex(farrighttri, rightpoint); + righttest = counterclockwise(leftpoint, midpoint, rightpoint); + if (righttest > 0.0) { + newevent = freeevents; + freeevents = (struct event *) freeevents->eventptr; + newevent->xkey = xminextreme; + newevent->ykey = circletop(leftpoint, midpoint, rightpoint, + righttest); + newevent->eventptr = (VOID *) encode(farrighttri); + eventheapinsert(eventheap, heapsize, newevent); + heapsize++; + setorg(farrighttri, newevent); + } + } + } + + pooldeinit(&splaynodes); + lprevself(bottommost); + return removeghosts(&bottommost); +} + +#endif /* not REDUCED */ + +/** **/ +/** **/ +/********* Sweepline Delaunay triangulation ends here *********/ + +/********* General mesh construction routines begin here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* delaunay() Form a Delaunay triangulation. */ +/* */ +/*****************************************************************************/ + +long delaunay() +{ + eextras = 0; + initializetrisegpools(); + +#ifdef REDUCED + if (!quiet) { + printf( + "Constructing Delaunay triangulation by divide-and-conquer method.\n"); + } + return divconqdelaunay(); +#else /* not REDUCED */ + if (!quiet) { + printf("Constructing Delaunay triangulation "); + if (incremental) { + printf("by incremental method.\n"); + } else if (sweepline) { + printf("by sweepline method.\n"); + } else { + printf("by divide-and-conquer method.\n"); + } + } + if (incremental) { + return incrementaldelaunay(); + } else if (sweepline) { + return sweeplinedelaunay(); + } else { + return divconqdelaunay(); + } +#endif /* not REDUCED */ +} + +/*****************************************************************************/ +/* */ +/* reconstruct() Reconstruct a triangulation from its .ele (and possibly */ +/* .poly) file. Used when the -r switch is used. */ +/* */ +/* Reads an .ele file and reconstructs the original mesh. If the -p switch */ +/* is used, this procedure will also read a .poly file and reconstruct the */ +/* shell edges of the original mesh. If the -a switch is used, this */ +/* procedure will also read an .area file and set a maximum area constraint */ +/* on each triangle. */ +/* */ +/* Points that are not corners of triangles, such as nodes on edges of */ +/* subparametric elements, are discarded. */ +/* */ +/* This routine finds the adjacencies between triangles (and shell edges) */ +/* by forming one stack of triangles for each vertex. Each triangle is on */ +/* three different stacks simultaneously. Each triangle's shell edge */ +/* pointers are used to link the items in each stack. This memory-saving */ +/* feature makes the code harder to read. The most important thing to keep */ +/* in mind is that each triangle is removed from a stack precisely when */ +/* the corresponding pointer is adjusted to refer to a shell edge rather */ +/* than the next triangle of the stack. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +#ifdef TRILIBRARY + +int reconstruct(trianglelist, triangleattriblist, trianglearealist, elements, + corners, attribs, segmentlist, segmentmarkerlist, + numberofsegments) +int *trianglelist; +REAL *triangleattriblist; +REAL *trianglearealist; +int elements; +int corners; +int attribs; +int *segmentlist; +int *segmentmarkerlist; +int numberofsegments; + +#else /* not TRILIBRARY */ + +long reconstruct(elefilename, areafilename, polyfilename, polyfile) +char *elefilename; +char *areafilename; +char *polyfilename; +FILE *polyfile; + +#endif /* not TRILIBRARY */ + +{ +#ifdef TRILIBRARY + int pointindex; + int attribindex; +#else /* not TRILIBRARY */ + FILE *elefile; + FILE *areafile; + char inputline[INPUTLINESIZE]; + char *stringptr; + int areaelements; +#endif /* not TRILIBRARY */ + struct triedge triangleloop; + struct triedge triangleleft; + struct triedge checktri; + struct triedge checkleft; + struct triedge checkneighbor; + struct edge shelleloop; + triangle *vertexarray; + triangle *prevlink; + triangle nexttri; + point tdest, tapex; + point checkdest, checkapex; + point shorg; + point killpoint; + REAL area; + int corner[3]; + int end[2]; + int killpointindex; + int incorners; + int segmentmarkers; + int boundmarker; + int aroundpoint; + long hullsize; + int notfound; + int elementnumber, segmentnumber; + int i, j; + triangle ptr; /* Temporary variable used by sym(). */ + +#ifdef TRILIBRARY + inelements = elements; + incorners = corners; + if (incorners < 3) { + printf("Error: Triangles must have at least 3 points.\n"); + exit(1); + } + eextras = attribs; +#else /* not TRILIBRARY */ + /* Read the triangles from an .ele file. */ + if (!quiet) { + printf("Opening %s.\n", elefilename); + } + elefile = fopen(elefilename, "r"); + if (elefile == (FILE *) NULL) { + printf(" Error: Cannot access file %s.\n", elefilename); + exit(1); + } + /* Read number of triangles, number of points per triangle, and */ + /* number of triangle attributes from .ele file. */ + stringptr = readline(inputline, elefile, elefilename); + inelements = (int) strtol (stringptr, &stringptr, 0); + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + incorners = 3; + } else { + incorners = (int) strtol (stringptr, &stringptr, 0); + if (incorners < 3) { + printf("Error: Triangles in %s must have at least 3 points.\n", + elefilename); + exit(1); + } + } + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + eextras = 0; + } else { + eextras = (int) strtol (stringptr, &stringptr, 0); + } +#endif /* not TRILIBRARY */ + + initializetrisegpools(); + + /* Create the triangles. */ + for (elementnumber = 1; elementnumber <= inelements; elementnumber++) { + maketriangle(&triangleloop); + /* Mark the triangle as living. */ + triangleloop.tri[3] = (triangle) triangleloop.tri; + } + + if (poly) { +#ifdef TRILIBRARY + insegments = numberofsegments; + segmentmarkers = segmentmarkerlist != (int *) NULL; +#else /* not TRILIBRARY */ + /* Read number of segments and number of segment */ + /* boundary markers from .poly file. */ + stringptr = readline(inputline, polyfile, inpolyfilename); + insegments = (int) strtol (stringptr, &stringptr, 0); + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + segmentmarkers = 0; + } else { + segmentmarkers = (int) strtol (stringptr, &stringptr, 0); + } +#endif /* not TRILIBRARY */ + + /* Create the shell edges. */ + for (segmentnumber = 1; segmentnumber <= insegments; segmentnumber++) { + makeshelle(&shelleloop); + /* Mark the shell edge as living. */ + shelleloop.sh[2] = (shelle) shelleloop.sh; + } + } + +#ifdef TRILIBRARY + pointindex = 0; + attribindex = 0; +#else /* not TRILIBRARY */ + if (vararea) { + /* Open an .area file, check for consistency with the .ele file. */ + if (!quiet) { + printf("Opening %s.\n", areafilename); + } + areafile = fopen(areafilename, "r"); + if (areafile == (FILE *) NULL) { + printf(" Error: Cannot access file %s.\n", areafilename); + exit(1); + } + stringptr = readline(inputline, areafile, areafilename); + areaelements = (int) strtol (stringptr, &stringptr, 0); + if (areaelements != inelements) { + printf("Error: %s and %s disagree on number of triangles.\n", + elefilename, areafilename); + exit(1); + } + } +#endif /* not TRILIBRARY */ + + if (!quiet) { + printf("Reconstructing mesh.\n"); + } + /* Allocate a temporary array that maps each point to some adjacent */ + /* triangle. I took care to allocate all the permanent memory for */ + /* triangles and shell edges first. */ + vertexarray = (triangle *) malloc(points.items * sizeof(triangle)); + if (vertexarray == (triangle *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + /* Each point is initially unrepresented. */ + for (i = 0; i < points.items; i++) { + vertexarray[i] = (triangle) dummytri; + } + + if (verbose) { + printf(" Assembling triangles.\n"); + } + /* Read the triangles from the .ele file, and link */ + /* together those that share an edge. */ + traversalinit(&triangles); + triangleloop.tri = triangletraverse(); + elementnumber = firstnumber; + while (triangleloop.tri != (triangle *) NULL) { +#ifdef TRILIBRARY + /* Copy the triangle's three corners. */ + for (j = 0; j < 3; j++) { + corner[j] = trianglelist[pointindex++]; + if ((corner[j] < firstnumber) || (corner[j] >= firstnumber + inpoints)) { + printf("Error: Triangle %d has an invalid vertex index.\n", + elementnumber); + exit(1); + } + } +#else /* not TRILIBRARY */ + /* Read triangle number and the triangle's three corners. */ + stringptr = readline(inputline, elefile, elefilename); + for (j = 0; j < 3; j++) { + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + printf("Error: Triangle %d is missing point %d in %s.\n", + elementnumber, j + 1, elefilename); + exit(1); + } else { + corner[j] = (int) strtol (stringptr, &stringptr, 0); + if ((corner[j] < firstnumber) || + (corner[j] >= firstnumber + inpoints)) { + printf("Error: Triangle %d has an invalid vertex index.\n", + elementnumber); + exit(1); + } + } + } +#endif /* not TRILIBRARY */ + + /* Find out about (and throw away) extra nodes. */ + for (j = 3; j < incorners; j++) { +#ifdef TRILIBRARY + killpointindex = trianglelist[pointindex++]; +#else /* not TRILIBRARY */ + stringptr = findfield(stringptr); + if (*stringptr != '\0') { + killpointindex = (int) strtol (stringptr, &stringptr, 0); +#endif /* not TRILIBRARY */ + if ((killpointindex >= firstnumber) && + (killpointindex < firstnumber + inpoints)) { + /* Delete the non-corner point if it's not already deleted. */ + killpoint = getpoint(killpointindex); + if (pointmark(killpoint) != DEADPOINT) { + pointdealloc(killpoint); + } + } +#ifndef TRILIBRARY + } +#endif /* not TRILIBRARY */ + } + + /* Read the triangle's attributes. */ + for (j = 0; j < eextras; j++) { +#ifdef TRILIBRARY + setelemattribute(triangleloop, j, triangleattriblist[attribindex++]); +#else /* not TRILIBRARY */ + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + setelemattribute(triangleloop, j, 0); + } else { + setelemattribute(triangleloop, j, + (REAL) strtod (stringptr, &stringptr)); + } +#endif /* not TRILIBRARY */ + } + + if (vararea) { +#ifdef TRILIBRARY + area = trianglearealist[elementnumber - firstnumber]; +#else /* not TRILIBRARY */ + /* Read an area constraint from the .area file. */ + stringptr = readline(inputline, areafile, areafilename); + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + area = -1.0; /* No constraint on this triangle. */ + } else { + area = (REAL) strtod(stringptr, &stringptr); + } +#endif /* not TRILIBRARY */ + setareabound(triangleloop, area); + } + + /* Set the triangle's vertices. */ + triangleloop.orient = 0; + setorg(triangleloop, getpoint(corner[0])); + setdest(triangleloop, getpoint(corner[1])); + setapex(triangleloop, getpoint(corner[2])); + /* Try linking the triangle to others that share these vertices. */ + for (triangleloop.orient = 0; triangleloop.orient < 3; + triangleloop.orient++) { + /* Take the number for the origin of triangleloop. */ + aroundpoint = corner[triangleloop.orient]; + /* Look for other triangles having this vertex. */ + nexttri = vertexarray[aroundpoint - firstnumber]; + /* Link the current triangle to the next one in the stack. */ + triangleloop.tri[6 + triangleloop.orient] = nexttri; + /* Push the current triangle onto the stack. */ + vertexarray[aroundpoint - firstnumber] = encode(triangleloop); + decode(nexttri, checktri); + if (checktri.tri != dummytri) { + dest(triangleloop, tdest); + apex(triangleloop, tapex); + /* Look for other triangles that share an edge. */ + do { + dest(checktri, checkdest); + apex(checktri, checkapex); + if (tapex == checkdest) { + /* The two triangles share an edge; bond them together. */ + lprev(triangleloop, triangleleft); + bond(triangleleft, checktri); + } + if (tdest == checkapex) { + /* The two triangles share an edge; bond them together. */ + lprev(checktri, checkleft); + bond(triangleloop, checkleft); + } + /* Find the next triangle in the stack. */ + nexttri = checktri.tri[6 + checktri.orient]; + decode(nexttri, checktri); + } while (checktri.tri != dummytri); + } + } + triangleloop.tri = triangletraverse(); + elementnumber++; + } + +#ifdef TRILIBRARY + pointindex = 0; +#else /* not TRILIBRARY */ + fclose(elefile); + if (vararea) { + fclose(areafile); + } +#endif /* not TRILIBRARY */ + + hullsize = 0; /* Prepare to count the boundary edges. */ + if (poly) { + if (verbose) { + printf(" Marking segments in triangulation.\n"); + } + /* Read the segments from the .poly file, and link them */ + /* to their neighboring triangles. */ + boundmarker = 0; + traversalinit(&shelles); + shelleloop.sh = shelletraverse(); + segmentnumber = firstnumber; + while (shelleloop.sh != (shelle *) NULL) { +#ifdef TRILIBRARY + end[0] = segmentlist[pointindex++]; + end[1] = segmentlist[pointindex++]; + if (segmentmarkers) { + boundmarker = segmentmarkerlist[segmentnumber - firstnumber]; + } +#else /* not TRILIBRARY */ + /* Read the endpoints of each segment, and possibly a boundary marker. */ + stringptr = readline(inputline, polyfile, inpolyfilename); + /* Skip the first (segment number) field. */ + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + printf("Error: Segment %d has no endpoints in %s.\n", segmentnumber, + polyfilename); + exit(1); + } else { + end[0] = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + printf("Error: Segment %d is missing its second endpoint in %s.\n", + segmentnumber, polyfilename); + exit(1); + } else { + end[1] = (int) strtol (stringptr, &stringptr, 0); + } + if (segmentmarkers) { + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + boundmarker = 0; + } else { + boundmarker = (int) strtol (stringptr, &stringptr, 0); + } + } +#endif /* not TRILIBRARY */ + for (j = 0; j < 2; j++) { + if ((end[j] < firstnumber) || (end[j] >= firstnumber + inpoints)) { + printf("Error: Segment %d has an invalid vertex index.\n", + segmentnumber); + exit(1); + } + } + + /* set the shell edge's vertices. */ + shelleloop.shorient = 0; + setsorg(shelleloop, getpoint(end[0])); + setsdest(shelleloop, getpoint(end[1])); + setmark(shelleloop, boundmarker); + /* Try linking the shell edge to triangles that share these vertices. */ + for (shelleloop.shorient = 0; shelleloop.shorient < 2; + shelleloop.shorient++) { + /* Take the number for the destination of shelleloop. */ + aroundpoint = end[1 - shelleloop.shorient]; + /* Look for triangles having this vertex. */ + prevlink = &vertexarray[aroundpoint - firstnumber]; + nexttri = vertexarray[aroundpoint - firstnumber]; + decode(nexttri, checktri); + sorg(shelleloop, shorg); + notfound = 1; + /* Look for triangles having this edge. Note that I'm only */ + /* comparing each triangle's destination with the shell edge; */ + /* each triangle's apex is handled through a different vertex. */ + /* Because each triangle appears on three vertices' lists, each */ + /* occurrence of a triangle on a list can (and does) represent */ + /* an edge. In this way, most edges are represented twice, and */ + /* every triangle-segment bond is represented once. */ + while (notfound && (checktri.tri != dummytri)) { + dest(checktri, checkdest); + if (shorg == checkdest) { + /* We have a match. Remove this triangle from the list. */ + *prevlink = checktri.tri[6 + checktri.orient]; + /* Bond the shell edge to the triangle. */ + tsbond(checktri, shelleloop); + /* Check if this is a boundary edge. */ + sym(checktri, checkneighbor); + if (checkneighbor.tri == dummytri) { + /* The next line doesn't insert a shell edge (because there's */ + /* already one there), but it sets the boundary markers of */ + /* the existing shell edge and its vertices. */ + insertshelle(&checktri, 1); + hullsize++; + } + notfound = 0; + } + /* Find the next triangle in the stack. */ + prevlink = &checktri.tri[6 + checktri.orient]; + nexttri = checktri.tri[6 + checktri.orient]; + decode(nexttri, checktri); + } + } + shelleloop.sh = shelletraverse(); + segmentnumber++; + } + } + + /* Mark the remaining edges as not being attached to any shell edge. */ + /* Also, count the (yet uncounted) boundary edges. */ + for (i = 0; i < points.items; i++) { + /* Search the stack of triangles adjacent to a point. */ + nexttri = vertexarray[i]; + decode(nexttri, checktri); + while (checktri.tri != dummytri) { + /* Find the next triangle in the stack before this */ + /* information gets overwritten. */ + nexttri = checktri.tri[6 + checktri.orient]; + /* No adjacent shell edge. (This overwrites the stack info.) */ + tsdissolve(checktri); + sym(checktri, checkneighbor); + if (checkneighbor.tri == dummytri) { + insertshelle(&checktri, 1); + hullsize++; + } + decode(nexttri, checktri); + } + } + + free(vertexarray); + return hullsize; +} + +#endif /* not CDT_ONLY */ + +/** **/ +/** **/ +/********* General mesh construction routines end here *********/ + +/********* Segment (shell edge) insertion begins here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* finddirection() Find the first triangle on the path from one point */ +/* to another. */ +/* */ +/* Finds the triangle that intersects a line segment drawn from the */ +/* origin of `searchtri' to the point `endpoint', and returns the result */ +/* in `searchtri'. The origin of `searchtri' does not change, even though */ +/* the triangle returned may differ from the one passed in. This routine */ +/* is used to find the direction to move in to get from one point to */ +/* another. */ +/* */ +/* The return value notes whether the destination or apex of the found */ +/* triangle is collinear with the two points in question. */ +/* */ +/*****************************************************************************/ + +enum finddirectionresult finddirection(searchtri, endpoint) +struct triedge *searchtri; +point endpoint; +{ + struct triedge checktri; + point startpoint; + point leftpoint, rightpoint; + REAL leftccw, rightccw; + int leftflag, rightflag; + triangle ptr; /* Temporary variable used by onext() and oprev(). */ + + org(*searchtri, startpoint); + dest(*searchtri, rightpoint); + apex(*searchtri, leftpoint); + /* Is `endpoint' to the left? */ + leftccw = counterclockwise(endpoint, startpoint, leftpoint); + leftflag = leftccw > 0.0; + /* Is `endpoint' to the right? */ + rightccw = counterclockwise(startpoint, endpoint, rightpoint); + rightflag = rightccw > 0.0; + if (leftflag && rightflag) { + /* `searchtri' faces directly away from `endpoint'. We could go */ + /* left or right. Ask whether it's a triangle or a boundary */ + /* on the left. */ + onext(*searchtri, checktri); + if (checktri.tri == dummytri) { + leftflag = 0; + } else { + rightflag = 0; + } + } + while (leftflag) { + /* Turn left until satisfied. */ + onextself(*searchtri); + if (searchtri->tri == dummytri) { + printf("Internal error in finddirection(): Unable to find a\n"); + printf(" triangle leading from (%.12g, %.12g) to", startpoint[0], + startpoint[1]); + printf(" (%.12g, %.12g).\n", endpoint[0], endpoint[1]); + internalerror(); + } + apex(*searchtri, leftpoint); + rightccw = leftccw; + leftccw = counterclockwise(endpoint, startpoint, leftpoint); + leftflag = leftccw > 0.0; + } + while (rightflag) { + /* Turn right until satisfied. */ + oprevself(*searchtri); + if (searchtri->tri == dummytri) { + printf("Internal error in finddirection(): Unable to find a\n"); + printf(" triangle leading from (%.12g, %.12g) to", startpoint[0], + startpoint[1]); + printf(" (%.12g, %.12g).\n", endpoint[0], endpoint[1]); + internalerror(); + } + dest(*searchtri, rightpoint); + leftccw = rightccw; + rightccw = counterclockwise(startpoint, endpoint, rightpoint); + rightflag = rightccw > 0.0; + } + if (leftccw == 0.0) { + return LEFTCOLLINEAR; + } else if (rightccw == 0.0) { + return RIGHTCOLLINEAR; + } else { + return WITHIN; + } +} + +/*****************************************************************************/ +/* */ +/* segmentintersection() Find the intersection of an existing segment */ +/* and a segment that is being inserted. Insert */ +/* a point at the intersection, splitting an */ +/* existing shell edge. */ +/* */ +/* The segment being inserted connects the apex of splittri to endpoint2. */ +/* splitshelle is the shell edge being split, and MUST be opposite */ +/* splittri. Hence, the edge being split connects the origin and */ +/* destination of splittri. */ +/* */ +/* On completion, splittri is a handle having the newly inserted */ +/* intersection point as its origin, and endpoint1 as its destination. */ +/* */ +/*****************************************************************************/ + +void segmentintersection(splittri, splitshelle, endpoint2) +struct triedge *splittri; +struct edge *splitshelle; +point endpoint2; +{ + point endpoint1; + point torg, tdest; + point leftpoint, rightpoint; + point newpoint; + enum insertsiteresult success; + enum finddirectionresult collinear; + REAL ex, ey; + REAL tx, ty; + REAL etx, ety; + REAL split, denom; + int i; + triangle ptr; /* Temporary variable used by onext(). */ + + /* Find the other three segment endpoints. */ + apex(*splittri, endpoint1); + org(*splittri, torg); + dest(*splittri, tdest); + /* Segment intersection formulae; see the Antonio reference. */ + tx = tdest[0] - torg[0]; + ty = tdest[1] - torg[1]; + ex = endpoint2[0] - endpoint1[0]; + ey = endpoint2[1] - endpoint1[1]; + etx = torg[0] - endpoint2[0]; + ety = torg[1] - endpoint2[1]; + denom = ty * ex - tx * ey; + if (denom == 0.0) { + printf("Internal error in segmentintersection():"); + printf(" Attempt to find intersection of parallel segments.\n"); + internalerror(); + } + split = (ey * etx - ex * ety) / denom; + /* Create the new point. */ + newpoint = (point) poolalloc(&points); + /* Interpolate its coordinate and attributes. */ + for (i = 0; i < 2 + nextras; i++) { + newpoint[i] = torg[i] + split * (tdest[i] - torg[i]); + } + setpointmark(newpoint, mark(*splitshelle)); + if (verbose > 1) { + printf( + " Splitting edge (%.12g, %.12g) (%.12g, %.12g) at (%.12g, %.12g).\n", + torg[0], torg[1], tdest[0], tdest[1], newpoint[0], newpoint[1]); + } + /* Insert the intersection point. This should always succeed. */ + success = insertsite(newpoint, splittri, splitshelle, 0, 0); + if (success != SUCCESSFULPOINT) { + printf("Internal error in segmentintersection():\n"); + printf(" Failure to split a segment.\n"); + internalerror(); + } + if (steinerleft > 0) { + steinerleft--; + } + /* Inserting the point may have caused edge flips. We wish to rediscover */ + /* the edge connecting endpoint1 to the new intersection point. */ + collinear = finddirection(splittri, endpoint1); + dest(*splittri, rightpoint); + apex(*splittri, leftpoint); + if ((leftpoint[0] == endpoint1[0]) && (leftpoint[1] == endpoint1[1])) { + onextself(*splittri); + } else if ((rightpoint[0] != endpoint1[0]) || + (rightpoint[1] != endpoint1[1])) { + printf("Internal error in segmentintersection():\n"); + printf(" Topological inconsistency after splitting a segment.\n"); + internalerror(); + } + /* `splittri' should have destination endpoint1. */ +} + +/*****************************************************************************/ +/* */ +/* scoutsegment() Scout the first triangle on the path from one endpoint */ +/* to another, and check for completion (reaching the */ +/* second endpoint), a collinear point, and the */ +/* intersection of two segments. */ +/* */ +/* Returns one if the entire segment is successfully inserted, and zero if */ +/* the job must be finished by conformingedge() or constrainededge(). */ +/* */ +/* If the first triangle on the path has the second endpoint as its */ +/* destination or apex, a shell edge is inserted and the job is done. */ +/* */ +/* If the first triangle on the path has a destination or apex that lies on */ +/* the segment, a shell edge is inserted connecting the first endpoint to */ +/* the collinear point, and the search is continued from the collinear */ +/* point. */ +/* */ +/* If the first triangle on the path has a shell edge opposite its origin, */ +/* then there is a segment that intersects the segment being inserted. */ +/* Their intersection point is inserted, splitting the shell edge. */ +/* */ +/* Otherwise, return zero. */ +/* */ +/*****************************************************************************/ + +int scoutsegment(searchtri, endpoint2, newmark) +struct triedge *searchtri; +point endpoint2; +int newmark; +{ + struct triedge crosstri; + struct edge crossedge; + point leftpoint, rightpoint; + point endpoint1; + enum finddirectionresult collinear; + shelle sptr; /* Temporary variable used by tspivot(). */ + + collinear = finddirection(searchtri, endpoint2); + dest(*searchtri, rightpoint); + apex(*searchtri, leftpoint); + if (((leftpoint[0] == endpoint2[0]) && (leftpoint[1] == endpoint2[1])) || + ((rightpoint[0] == endpoint2[0]) && (rightpoint[1] == endpoint2[1]))) { + /* The segment is already an edge in the mesh. */ + if ((leftpoint[0] == endpoint2[0]) && (leftpoint[1] == endpoint2[1])) { + lprevself(*searchtri); + } + /* Insert a shell edge, if there isn't already one there. */ + insertshelle(searchtri, newmark); + return 1; + } else if (collinear == LEFTCOLLINEAR) { + /* We've collided with a point between the segment's endpoints. */ + /* Make the collinear point be the triangle's origin. */ + lprevself(*searchtri); + insertshelle(searchtri, newmark); + /* Insert the remainder of the segment. */ + return scoutsegment(searchtri, endpoint2, newmark); + } else if (collinear == RIGHTCOLLINEAR) { + /* We've collided with a point between the segment's endpoints. */ + insertshelle(searchtri, newmark); + /* Make the collinear point be the triangle's origin. */ + lnextself(*searchtri); + /* Insert the remainder of the segment. */ + return scoutsegment(searchtri, endpoint2, newmark); + } else { + lnext(*searchtri, crosstri); + tspivot(crosstri, crossedge); + /* Check for a crossing segment. */ + if (crossedge.sh == dummysh) { + return 0; + } else { + org(*searchtri, endpoint1); + /* Insert a point at the intersection. */ + segmentintersection(&crosstri, &crossedge, endpoint2); + triedgecopy(crosstri, *searchtri); + insertshelle(searchtri, newmark); + /* Insert the remainder of the segment. */ + return scoutsegment(searchtri, endpoint2, newmark); + } + } +} + +/*****************************************************************************/ +/* */ +/* conformingedge() Force a segment into a conforming Delaunay */ +/* triangulation by inserting a point at its midpoint, */ +/* and recursively forcing in the two half-segments if */ +/* necessary. */ +/* */ +/* Generates a sequence of edges connecting `endpoint1' to `endpoint2'. */ +/* `newmark' is the boundary marker of the segment, assigned to each new */ +/* splitting point and shell edge. */ +/* */ +/* Note that conformingedge() does not always maintain the conforming */ +/* Delaunay property. Once inserted, segments are locked into place; */ +/* points inserted later (to force other segments in) may render these */ +/* fixed segments non-Delaunay. The conforming Delaunay property will be */ +/* restored by enforcequality() by splitting encroached segments. */ +/* */ +/*****************************************************************************/ + +#ifndef REDUCED +#ifndef CDT_ONLY + +void conformingedge(endpoint1, endpoint2, newmark) +point endpoint1; +point endpoint2; +int newmark; +{ + struct triedge searchtri1, searchtri2; + struct edge brokenshelle; + point newpoint; + point midpoint1, midpoint2; + enum insertsiteresult success; + int result1, result2; + int i; + shelle sptr; /* Temporary variable used by tspivot(). */ + + if (verbose > 2) { + printf("Forcing segment into triangulation by recursive splitting:\n"); + printf(" (%.12g, %.12g) (%.12g, %.12g)\n", endpoint1[0], endpoint1[1], + endpoint2[0], endpoint2[1]); + } + /* Create a new point to insert in the middle of the segment. */ + newpoint = (point) poolalloc(&points); + /* Interpolate coordinates and attributes. */ + for (i = 0; i < 2 + nextras; i++) { + newpoint[i] = 0.5 * (endpoint1[i] + endpoint2[i]); + } + setpointmark(newpoint, newmark); + /* Find a boundary triangle to search from. */ + searchtri1.tri = (triangle *) NULL; + /* Attempt to insert the new point. */ + success = insertsite(newpoint, &searchtri1, (struct edge *) NULL, 0, 0); + if (success == DUPLICATEPOINT) { + if (verbose > 2) { + printf(" Segment intersects existing point (%.12g, %.12g).\n", + newpoint[0], newpoint[1]); + } + /* Use the point that's already there. */ + pointdealloc(newpoint); + org(searchtri1, newpoint); + } else { + if (success == VIOLATINGPOINT) { + if (verbose > 2) { + printf(" Two segments intersect at (%.12g, %.12g).\n", + newpoint[0], newpoint[1]); + } + /* By fluke, we've landed right on another segment. Split it. */ + tspivot(searchtri1, brokenshelle); + success = insertsite(newpoint, &searchtri1, &brokenshelle, 0, 0); + if (success != SUCCESSFULPOINT) { + printf("Internal error in conformingedge():\n"); + printf(" Failure to split a segment.\n"); + internalerror(); + } + } + /* The point has been inserted successfully. */ + if (steinerleft > 0) { + steinerleft--; + } + } + triedgecopy(searchtri1, searchtri2); + result1 = scoutsegment(&searchtri1, endpoint1, newmark); + result2 = scoutsegment(&searchtri2, endpoint2, newmark); + if (!result1) { + /* The origin of searchtri1 may have changed if a collision with an */ + /* intervening vertex on the segment occurred. */ + org(searchtri1, midpoint1); + conformingedge(midpoint1, endpoint1, newmark); + } + if (!result2) { + /* The origin of searchtri2 may have changed if a collision with an */ + /* intervening vertex on the segment occurred. */ + org(searchtri2, midpoint2); + conformingedge(midpoint2, endpoint2, newmark); + } +} + +#endif /* not CDT_ONLY */ +#endif /* not REDUCED */ + +/*****************************************************************************/ +/* */ +/* delaunayfixup() Enforce the Delaunay condition at an edge, fanning out */ +/* recursively from an existing point. Pay special */ +/* attention to stacking inverted triangles. */ +/* */ +/* This is a support routine for inserting segments into a constrained */ +/* Delaunay triangulation. */ +/* */ +/* The origin of fixuptri is treated as if it has just been inserted, and */ +/* the local Delaunay condition needs to be enforced. It is only enforced */ +/* in one sector, however, that being the angular range defined by */ +/* fixuptri. */ +/* */ +/* This routine also needs to make decisions regarding the "stacking" of */ +/* triangles. (Read the description of constrainededge() below before */ +/* reading on here, so you understand the algorithm.) If the position of */ +/* the new point (the origin of fixuptri) indicates that the vertex before */ +/* it on the polygon is a reflex vertex, then "stack" the triangle by */ +/* doing nothing. (fixuptri is an inverted triangle, which is how stacked */ +/* triangles are identified.) */ +/* */ +/* Otherwise, check whether the vertex before that was a reflex vertex. */ +/* If so, perform an edge flip, thereby eliminating an inverted triangle */ +/* (popping it off the stack). The edge flip may result in the creation */ +/* of a new inverted triangle, depending on whether or not the new vertex */ +/* is visible to the vertex three edges behind on the polygon. */ +/* */ +/* If neither of the two vertices behind the new vertex are reflex */ +/* vertices, fixuptri and fartri, the triangle opposite it, are not */ +/* inverted; hence, ensure that the edge between them is locally Delaunay. */ +/* */ +/* `leftside' indicates whether or not fixuptri is to the left of the */ +/* segment being inserted. (Imagine that the segment is pointing up from */ +/* endpoint1 to endpoint2.) */ +/* */ +/*****************************************************************************/ + +void delaunayfixup(fixuptri, leftside) +struct triedge *fixuptri; +int leftside; +{ + struct triedge neartri; + struct triedge fartri; + struct edge faredge; + point nearpoint, leftpoint, rightpoint, farpoint; + triangle ptr; /* Temporary variable used by sym(). */ + shelle sptr; /* Temporary variable used by tspivot(). */ + + lnext(*fixuptri, neartri); + sym(neartri, fartri); + /* Check if the edge opposite the origin of fixuptri can be flipped. */ + if (fartri.tri == dummytri) { + return; + } + tspivot(neartri, faredge); + if (faredge.sh != dummysh) { + return; + } + /* Find all the relevant vertices. */ + apex(neartri, nearpoint); + org(neartri, leftpoint); + dest(neartri, rightpoint); + apex(fartri, farpoint); + /* Check whether the previous polygon vertex is a reflex vertex. */ + if (leftside) { + if (counterclockwise(nearpoint, leftpoint, farpoint) <= 0.0) { + /* leftpoint is a reflex vertex too. Nothing can */ + /* be done until a convex section is found. */ + return; + } + } else { + if (counterclockwise(farpoint, rightpoint, nearpoint) <= 0.0) { + /* rightpoint is a reflex vertex too. Nothing can */ + /* be done until a convex section is found. */ + return; + } + } + if (counterclockwise(rightpoint, leftpoint, farpoint) > 0.0) { + /* fartri is not an inverted triangle, and farpoint is not a reflex */ + /* vertex. As there are no reflex vertices, fixuptri isn't an */ + /* inverted triangle, either. Hence, test the edge between the */ + /* triangles to ensure it is locally Delaunay. */ + if (incircle(leftpoint, farpoint, rightpoint, nearpoint) <= 0.0) { + return; + } + /* Not locally Delaunay; go on to an edge flip. */ + } /* else fartri is inverted; remove it from the stack by flipping. */ + flip(&neartri); + lprevself(*fixuptri); /* Restore the origin of fixuptri after the flip. */ + /* Recursively process the two triangles that result from the flip. */ + delaunayfixup(fixuptri, leftside); + delaunayfixup(&fartri, leftside); +} + +/*****************************************************************************/ +/* */ +/* constrainededge() Force a segment into a constrained Delaunay */ +/* triangulation by deleting the triangles it */ +/* intersects, and triangulating the polygons that */ +/* form on each side of it. */ +/* */ +/* Generates a single edge connecting `endpoint1' to `endpoint2'. The */ +/* triangle `starttri' has `endpoint1' as its origin. `newmark' is the */ +/* boundary marker of the segment. */ +/* */ +/* To insert a segment, every triangle whose interior intersects the */ +/* segment is deleted. The union of these deleted triangles is a polygon */ +/* (which is not necessarily monotone, but is close enough), which is */ +/* divided into two polygons by the new segment. This routine's task is */ +/* to generate the Delaunay triangulation of these two polygons. */ +/* */ +/* You might think of this routine's behavior as a two-step process. The */ +/* first step is to walk from endpoint1 to endpoint2, flipping each edge */ +/* encountered. This step creates a fan of edges connected to endpoint1, */ +/* including the desired edge to endpoint2. The second step enforces the */ +/* Delaunay condition on each side of the segment in an incremental manner: */ +/* proceeding along the polygon from endpoint1 to endpoint2 (this is done */ +/* independently on each side of the segment), each vertex is "enforced" */ +/* as if it had just been inserted, but affecting only the previous */ +/* vertices. The result is the same as if the vertices had been inserted */ +/* in the order they appear on the polygon, so the result is Delaunay. */ +/* */ +/* In truth, constrainededge() interleaves these two steps. The procedure */ +/* walks from endpoint1 to endpoint2, and each time an edge is encountered */ +/* and flipped, the newly exposed vertex (at the far end of the flipped */ +/* edge) is "enforced" upon the previously flipped edges, usually affecting */ +/* only one side of the polygon (depending upon which side of the segment */ +/* the vertex falls on). */ +/* */ +/* The algorithm is complicated by the need to handle polygons that are not */ +/* convex. Although the polygon is not necessarily monotone, it can be */ +/* triangulated in a manner similar to the stack-based algorithms for */ +/* monotone polygons. For each reflex vertex (local concavity) of the */ +/* polygon, there will be an inverted triangle formed by one of the edge */ +/* flips. (An inverted triangle is one with negative area - that is, its */ +/* vertices are arranged in clockwise order - and is best thought of as a */ +/* wrinkle in the fabric of the mesh.) Each inverted triangle can be */ +/* thought of as a reflex vertex pushed on the stack, waiting to be fixed */ +/* later. */ +/* */ +/* A reflex vertex is popped from the stack when a vertex is inserted that */ +/* is visible to the reflex vertex. (However, if the vertex behind the */ +/* reflex vertex is not visible to the reflex vertex, a new inverted */ +/* triangle will take its place on the stack.) These details are handled */ +/* by the delaunayfixup() routine above. */ +/* */ +/*****************************************************************************/ + +void constrainededge(starttri, endpoint2, newmark) +struct triedge *starttri; +point endpoint2; +int newmark; +{ + struct triedge fixuptri, fixuptri2; + struct edge fixupedge; + point endpoint1; + point farpoint; + REAL area; + int collision; + int done; + triangle ptr; /* Temporary variable used by sym() and oprev(). */ + shelle sptr; /* Temporary variable used by tspivot(). */ + + org(*starttri, endpoint1); + lnext(*starttri, fixuptri); + flip(&fixuptri); + /* `collision' indicates whether we have found a point directly */ + /* between endpoint1 and endpoint2. */ + collision = 0; + done = 0; + do { + org(fixuptri, farpoint); + /* `farpoint' is the extreme point of the polygon we are "digging" */ + /* to get from endpoint1 to endpoint2. */ + if ((farpoint[0] == endpoint2[0]) && (farpoint[1] == endpoint2[1])) { + oprev(fixuptri, fixuptri2); + /* Enforce the Delaunay condition around endpoint2. */ + delaunayfixup(&fixuptri, 0); + delaunayfixup(&fixuptri2, 1); + done = 1; + } else { + /* Check whether farpoint is to the left or right of the segment */ + /* being inserted, to decide which edge of fixuptri to dig */ + /* through next. */ + area = counterclockwise(endpoint1, endpoint2, farpoint); + if (area == 0.0) { + /* We've collided with a point between endpoint1 and endpoint2. */ + collision = 1; + oprev(fixuptri, fixuptri2); + /* Enforce the Delaunay condition around farpoint. */ + delaunayfixup(&fixuptri, 0); + delaunayfixup(&fixuptri2, 1); + done = 1; + } else { + if (area > 0.0) { /* farpoint is to the left of the segment. */ + oprev(fixuptri, fixuptri2); + /* Enforce the Delaunay condition around farpoint, on the */ + /* left side of the segment only. */ + delaunayfixup(&fixuptri2, 1); + /* Flip the edge that crosses the segment. After the edge is */ + /* flipped, one of its endpoints is the fan vertex, and the */ + /* destination of fixuptri is the fan vertex. */ + lprevself(fixuptri); + } else { /* farpoint is to the right of the segment. */ + delaunayfixup(&fixuptri, 0); + /* Flip the edge that crosses the segment. After the edge is */ + /* flipped, one of its endpoints is the fan vertex, and the */ + /* destination of fixuptri is the fan vertex. */ + oprevself(fixuptri); + } + /* Check for two intersecting segments. */ + tspivot(fixuptri, fixupedge); + if (fixupedge.sh == dummysh) { + flip(&fixuptri); /* May create an inverted triangle on the left. */ + } else { + /* We've collided with a segment between endpoint1 and endpoint2. */ + collision = 1; + /* Insert a point at the intersection. */ + segmentintersection(&fixuptri, &fixupedge, endpoint2); + done = 1; + } + } + } + } while (!done); + /* Insert a shell edge to make the segment permanent. */ + insertshelle(&fixuptri, newmark); + /* If there was a collision with an interceding vertex, install another */ + /* segment connecting that vertex with endpoint2. */ + if (collision) { + /* Insert the remainder of the segment. */ + if (!scoutsegment(&fixuptri, endpoint2, newmark)) { + constrainededge(&fixuptri, endpoint2, newmark); + } + } +} + +/*****************************************************************************/ +/* */ +/* insertsegment() Insert a PSLG segment into a triangulation. */ +/* */ +/*****************************************************************************/ + +void insertsegment(endpoint1, endpoint2, newmark) +point endpoint1; +point endpoint2; +int newmark; +{ + struct triedge searchtri1, searchtri2; + triangle encodedtri; + point checkpoint; + triangle ptr; /* Temporary variable used by sym(). */ + + if (verbose > 1) { + printf(" Connecting (%.12g, %.12g) to (%.12g, %.12g).\n", + endpoint1[0], endpoint1[1], endpoint2[0], endpoint2[1]); + } + + /* Find a triangle whose origin is the segment's first endpoint. */ + checkpoint = (point) NULL; + encodedtri = point2tri(endpoint1); + if (encodedtri != (triangle) NULL) { + decode(encodedtri, searchtri1); + org(searchtri1, checkpoint); + } + if (checkpoint != endpoint1) { + /* Find a boundary triangle to search from. */ + searchtri1.tri = dummytri; + searchtri1.orient = 0; + symself(searchtri1); + /* Search for the segment's first endpoint by point location. */ + if (locate(endpoint1, &searchtri1) != ONVERTEX) { + printf( + "Internal error in insertsegment(): Unable to locate PSLG point\n"); + printf(" (%.12g, %.12g) in triangulation.\n", + endpoint1[0], endpoint1[1]); + internalerror(); + } + } + /* Remember this triangle to improve subsequent point location. */ + triedgecopy(searchtri1, recenttri); + /* Scout the beginnings of a path from the first endpoint */ + /* toward the second. */ + if (scoutsegment(&searchtri1, endpoint2, newmark)) { + /* The segment was easily inserted. */ + return; + } + /* The first endpoint may have changed if a collision with an intervening */ + /* vertex on the segment occurred. */ + org(searchtri1, endpoint1); + + /* Find a triangle whose origin is the segment's second endpoint. */ + checkpoint = (point) NULL; + encodedtri = point2tri(endpoint2); + if (encodedtri != (triangle) NULL) { + decode(encodedtri, searchtri2); + org(searchtri2, checkpoint); + } + if (checkpoint != endpoint2) { + /* Find a boundary triangle to search from. */ + searchtri2.tri = dummytri; + searchtri2.orient = 0; + symself(searchtri2); + /* Search for the segment's second endpoint by point location. */ + if (locate(endpoint2, &searchtri2) != ONVERTEX) { + printf( + "Internal error in insertsegment(): Unable to locate PSLG point\n"); + printf(" (%.12g, %.12g) in triangulation.\n", + endpoint2[0], endpoint2[1]); + internalerror(); + } + } + /* Remember this triangle to improve subsequent point location. */ + triedgecopy(searchtri2, recenttri); + /* Scout the beginnings of a path from the second endpoint */ + /* toward the first. */ + if (scoutsegment(&searchtri2, endpoint1, newmark)) { + /* The segment was easily inserted. */ + return; + } + /* The second endpoint may have changed if a collision with an intervening */ + /* vertex on the segment occurred. */ + org(searchtri2, endpoint2); + +#ifndef REDUCED +#ifndef CDT_ONLY + if (splitseg) { + /* Insert vertices to force the segment into the triangulation. */ + conformingedge(endpoint1, endpoint2, newmark); + } else { +#endif /* not CDT_ONLY */ +#endif /* not REDUCED */ + /* Insert the segment directly into the triangulation. */ + constrainededge(&searchtri1, endpoint2, newmark); +#ifndef REDUCED +#ifndef CDT_ONLY + } +#endif /* not CDT_ONLY */ +#endif /* not REDUCED */ +} + +/*****************************************************************************/ +/* */ +/* markhull() Cover the convex hull of a triangulation with shell edges. */ +/* */ +/*****************************************************************************/ + +void markhull() +{ + struct triedge hulltri; + struct triedge nexttri; + struct triedge starttri; + triangle ptr; /* Temporary variable used by sym() and oprev(). */ + + /* Find a triangle handle on the hull. */ + hulltri.tri = dummytri; + hulltri.orient = 0; + symself(hulltri); + /* Remember where we started so we know when to stop. */ + triedgecopy(hulltri, starttri); + /* Go once counterclockwise around the convex hull. */ + do { + /* Create a shell edge if there isn't already one here. */ + insertshelle(&hulltri, 1); + /* To find the next hull edge, go clockwise around the next vertex. */ + lnextself(hulltri); + oprev(hulltri, nexttri); + while (nexttri.tri != dummytri) { + triedgecopy(nexttri, hulltri); + oprev(hulltri, nexttri); + } + } while (!triedgeequal(hulltri, starttri)); +} + +/*****************************************************************************/ +/* */ +/* formskeleton() Create the shell edges of a triangulation, including */ +/* PSLG edges and edges on the convex hull. */ +/* */ +/* The PSLG edges are read from a .poly file. The return value is the */ +/* number of segments in the file. */ +/* */ +/*****************************************************************************/ + +#ifdef TRILIBRARY + +int formskeleton(segmentlist, segmentmarkerlist, numberofsegments) +int *segmentlist; +int *segmentmarkerlist; +int numberofsegments; + +#else /* not TRILIBRARY */ + +int formskeleton(polyfile, polyfilename) +FILE *polyfile; +char *polyfilename; + +#endif /* not TRILIBRARY */ + +{ +#ifdef TRILIBRARY + char polyfilename[6]; + int index; +#else /* not TRILIBRARY */ + char inputline[INPUTLINESIZE]; + char *stringptr; +#endif /* not TRILIBRARY */ + point endpoint1, endpoint2; + int segments; + int segmentmarkers; + int end1, end2; + int boundmarker; + int i; + + if (poly) { + if (!quiet) { + printf("Inserting segments into Delaunay triangulation.\n"); + } +#ifdef TRILIBRARY + strcpy(polyfilename, "input"); + segments = numberofsegments; + segmentmarkers = segmentmarkerlist != (int *) NULL; + index = 0; +#else /* not TRILIBRARY */ + /* Read the segments from a .poly file. */ + /* Read number of segments and number of boundary markers. */ + stringptr = readline(inputline, polyfile, polyfilename); + segments = (int) strtol (stringptr, &stringptr, 0); + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + segmentmarkers = 0; + } else { + segmentmarkers = (int) strtol (stringptr, &stringptr, 0); + } +#endif /* not TRILIBRARY */ + /* If segments are to be inserted, compute a mapping */ + /* from points to triangles. */ + if (segments > 0) { + if (verbose) { + printf(" Inserting PSLG segments.\n"); + } + makepointmap(); + } + + boundmarker = 0; + /* Read and insert the segments. */ + for (i = 1; i <= segments; i++) { +#ifdef TRILIBRARY + end1 = segmentlist[index++]; + end2 = segmentlist[index++]; + if (segmentmarkers) { + boundmarker = segmentmarkerlist[i - 1]; + } +#else /* not TRILIBRARY */ + stringptr = readline(inputline, polyfile, inpolyfilename); + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + printf("Error: Segment %d has no endpoints in %s.\n", i, + polyfilename); + exit(1); + } else { + end1 = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + printf("Error: Segment %d is missing its second endpoint in %s.\n", i, + polyfilename); + exit(1); + } else { + end2 = (int) strtol (stringptr, &stringptr, 0); + } + if (segmentmarkers) { + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + boundmarker = 0; + } else { + boundmarker = (int) strtol (stringptr, &stringptr, 0); + } + } +#endif /* not TRILIBRARY */ + if ((end1 < firstnumber) || (end1 >= firstnumber + inpoints)) { + if (!quiet) { + printf("Warning: Invalid first endpoint of segment %d in %s.\n", i, + polyfilename); + } + } else if ((end2 < firstnumber) || (end2 >= firstnumber + inpoints)) { + if (!quiet) { + printf("Warning: Invalid second endpoint of segment %d in %s.\n", i, + polyfilename); + } + } else { + endpoint1 = getpoint(end1); + endpoint2 = getpoint(end2); + if ((endpoint1[0] == endpoint2[0]) && (endpoint1[1] == endpoint2[1])) { + if (!quiet) { + printf("Warning: Endpoints of segment %d are coincident in %s.\n", + i, polyfilename); + } + } else { + insertsegment(endpoint1, endpoint2, boundmarker); + } + } + } + } else { + segments = 0; + } + if (convex || !poly) { + /* Enclose the convex hull with shell edges. */ + if (verbose) { + printf(" Enclosing convex hull with segments.\n"); + } + markhull(); + } + return segments; +} + +/** **/ +/** **/ +/********* Segment (shell edge) insertion ends here *********/ + +/********* Carving out holes and concavities begins here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* infecthull() Virally infect all of the triangles of the convex hull */ +/* that are not protected by shell edges. Where there are */ +/* shell edges, set boundary markers as appropriate. */ +/* */ +/*****************************************************************************/ + +void infecthull() +{ + struct triedge hulltri; + struct triedge nexttri; + struct triedge starttri; + struct edge hulledge; + triangle **deadtri; + point horg, hdest; + triangle ptr; /* Temporary variable used by sym(). */ + shelle sptr; /* Temporary variable used by tspivot(). */ + + if (verbose) { + printf(" Marking concavities (external triangles) for elimination.\n"); + } + /* Find a triangle handle on the hull. */ + hulltri.tri = dummytri; + hulltri.orient = 0; + symself(hulltri); + /* Remember where we started so we know when to stop. */ + triedgecopy(hulltri, starttri); + /* Go once counterclockwise around the convex hull. */ + do { + /* Ignore triangles that are already infected. */ + if (!infected(hulltri)) { + /* Is the triangle protected by a shell edge? */ + tspivot(hulltri, hulledge); + if (hulledge.sh == dummysh) { + /* The triangle is not protected; infect it. */ + infect(hulltri); + deadtri = (triangle **) poolalloc(&viri); + *deadtri = hulltri.tri; + } else { + /* The triangle is protected; set boundary markers if appropriate. */ + if (mark(hulledge) == 0) { + setmark(hulledge, 1); + org(hulltri, horg); + dest(hulltri, hdest); + if (pointmark(horg) == 0) { + setpointmark(horg, 1); + } + if (pointmark(hdest) == 0) { + setpointmark(hdest, 1); + } + } + } + } + /* To find the next hull edge, go clockwise around the next vertex. */ + lnextself(hulltri); + oprev(hulltri, nexttri); + while (nexttri.tri != dummytri) { + triedgecopy(nexttri, hulltri); + oprev(hulltri, nexttri); + } + } while (!triedgeequal(hulltri, starttri)); +} + +/*****************************************************************************/ +/* */ +/* plague() Spread the virus from all infected triangles to any neighbors */ +/* not protected by shell edges. Delete all infected triangles. */ +/* */ +/* This is the procedure that actually creates holes and concavities. */ +/* */ +/* This procedure operates in two phases. The first phase identifies all */ +/* the triangles that will die, and marks them as infected. They are */ +/* marked to ensure that each triangle is added to the virus pool only */ +/* once, so the procedure will terminate. */ +/* */ +/* The second phase actually eliminates the infected triangles. It also */ +/* eliminates orphaned points. */ +/* */ +/*****************************************************************************/ + +void plague() +{ + struct triedge testtri; + struct triedge neighbor; + triangle **virusloop; + triangle **deadtri; + struct edge neighborshelle; + point testpoint; + point norg, ndest; + point deadorg, deaddest, deadapex; + int killorg; + triangle ptr; /* Temporary variable used by sym() and onext(). */ + shelle sptr; /* Temporary variable used by tspivot(). */ + + if (verbose) { + printf(" Marking neighbors of marked triangles.\n"); + } + /* Loop through all the infected triangles, spreading the virus to */ + /* their neighbors, then to their neighbors' neighbors. */ + traversalinit(&viri); + virusloop = (triangle **) traverse(&viri); + while (virusloop != (triangle **) NULL) { + testtri.tri = *virusloop; + /* A triangle is marked as infected by messing with one of its shell */ + /* edges, setting it to an illegal value. Hence, we have to */ + /* temporarily uninfect this triangle so that we can examine its */ + /* adjacent shell edges. */ + uninfect(testtri); + if (verbose > 2) { + /* Assign the triangle an orientation for convenience in */ + /* checking its points. */ + testtri.orient = 0; + org(testtri, deadorg); + dest(testtri, deaddest); + apex(testtri, deadapex); + printf(" Checking (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n", + deadorg[0], deadorg[1], deaddest[0], deaddest[1], + deadapex[0], deadapex[1]); + } + /* Check each of the triangle's three neighbors. */ + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) { + /* Find the neighbor. */ + sym(testtri, neighbor); + /* Check for a shell between the triangle and its neighbor. */ + tspivot(testtri, neighborshelle); + /* Check if the neighbor is nonexistent or already infected. */ + if ((neighbor.tri == dummytri) || infected(neighbor)) { + if (neighborshelle.sh != dummysh) { + /* There is a shell edge separating the triangle from its */ + /* neighbor, but both triangles are dying, so the shell */ + /* edge dies too. */ + shelledealloc(neighborshelle.sh); + if (neighbor.tri != dummytri) { + /* Make sure the shell edge doesn't get deallocated again */ + /* later when the infected neighbor is visited. */ + uninfect(neighbor); + tsdissolve(neighbor); + infect(neighbor); + } + } + } else { /* The neighbor exists and is not infected. */ + if (neighborshelle.sh == dummysh) { + /* There is no shell edge protecting the neighbor, so */ + /* the neighbor becomes infected. */ + if (verbose > 2) { + org(neighbor, deadorg); + dest(neighbor, deaddest); + apex(neighbor, deadapex); + printf( + " Marking (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n", + deadorg[0], deadorg[1], deaddest[0], deaddest[1], + deadapex[0], deadapex[1]); + } + infect(neighbor); + /* Ensure that the neighbor's neighbors will be infected. */ + deadtri = (triangle **) poolalloc(&viri); + *deadtri = neighbor.tri; + } else { /* The neighbor is protected by a shell edge. */ + /* Remove this triangle from the shell edge. */ + stdissolve(neighborshelle); + /* The shell edge becomes a boundary. Set markers accordingly. */ + if (mark(neighborshelle) == 0) { + setmark(neighborshelle, 1); + } + org(neighbor, norg); + dest(neighbor, ndest); + if (pointmark(norg) == 0) { + setpointmark(norg, 1); + } + if (pointmark(ndest) == 0) { + setpointmark(ndest, 1); + } + } + } + } + /* Remark the triangle as infected, so it doesn't get added to the */ + /* virus pool again. */ + infect(testtri); + virusloop = (triangle **) traverse(&viri); + } + + if (verbose) { + printf(" Deleting marked triangles.\n"); + } + traversalinit(&viri); + virusloop = (triangle **) traverse(&viri); + while (virusloop != (triangle **) NULL) { + testtri.tri = *virusloop; + + /* Check each of the three corners of the triangle for elimination. */ + /* This is done by walking around each point, checking if it is */ + /* still connected to at least one live triangle. */ + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) { + org(testtri, testpoint); + /* Check if the point has already been tested. */ + if (testpoint != (point) NULL) { + killorg = 1; + /* Mark the corner of the triangle as having been tested. */ + setorg(testtri, NULL); + /* Walk counterclockwise about the point. */ + onext(testtri, neighbor); + /* Stop upon reaching a boundary or the starting triangle. */ + while ((neighbor.tri != dummytri) + && (!triedgeequal(neighbor, testtri))) { + if (infected(neighbor)) { + /* Mark the corner of this triangle as having been tested. */ + setorg(neighbor, NULL); + } else { + /* A live triangle. The point survives. */ + killorg = 0; + } + /* Walk counterclockwise about the point. */ + onextself(neighbor); + } + /* If we reached a boundary, we must walk clockwise as well. */ + if (neighbor.tri == dummytri) { + /* Walk clockwise about the point. */ + oprev(testtri, neighbor); + /* Stop upon reaching a boundary. */ + while (neighbor.tri != dummytri) { + if (infected(neighbor)) { + /* Mark the corner of this triangle as having been tested. */ + setorg(neighbor, NULL); + } else { + /* A live triangle. The point survives. */ + killorg = 0; + } + /* Walk clockwise about the point. */ + oprevself(neighbor); + } + } + if (killorg) { + if (verbose > 1) { + printf(" Deleting point (%.12g, %.12g)\n", + testpoint[0], testpoint[1]); + } + pointdealloc(testpoint); + } + } + } + + /* Record changes in the number of boundary edges, and disconnect */ + /* dead triangles from their neighbors. */ + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) { + sym(testtri, neighbor); + if (neighbor.tri == dummytri) { + /* There is no neighboring triangle on this edge, so this edge */ + /* is a boundary edge. This triangle is being deleted, so this */ + /* boundary edge is deleted. */ + hullsize--; + } else { + /* Disconnect the triangle from its neighbor. */ + dissolve(neighbor); + /* There is a neighboring triangle on this edge, so this edge */ + /* becomes a boundary edge when this triangle is deleted. */ + hullsize++; + } + } + /* Return the dead triangle to the pool of triangles. */ + triangledealloc(testtri.tri); + virusloop = (triangle **) traverse(&viri); + } + /* Empty the virus pool. */ + poolrestart(&viri); +} + +/*****************************************************************************/ +/* */ +/* regionplague() Spread regional attributes and/or area constraints */ +/* (from a .poly file) throughout the mesh. */ +/* */ +/* This procedure operates in two phases. The first phase spreads an */ +/* attribute and/or an area constraint through a (segment-bounded) region. */ +/* The triangles are marked to ensure that each triangle is added to the */ +/* virus pool only once, so the procedure will terminate. */ +/* */ +/* The second phase uninfects all infected triangles, returning them to */ +/* normal. */ +/* */ +/*****************************************************************************/ + +void regionplague(attribute, area) +REAL attribute; +REAL area; +{ + struct triedge testtri; + struct triedge neighbor; + triangle **virusloop; + triangle **regiontri; + struct edge neighborshelle; + point regionorg, regiondest, regionapex; + triangle ptr; /* Temporary variable used by sym() and onext(). */ + shelle sptr; /* Temporary variable used by tspivot(). */ + + if (verbose > 1) { + printf(" Marking neighbors of marked triangles.\n"); + } + /* Loop through all the infected triangles, spreading the attribute */ + /* and/or area constraint to their neighbors, then to their neighbors' */ + /* neighbors. */ + traversalinit(&viri); + virusloop = (triangle **) traverse(&viri); + while (virusloop != (triangle **) NULL) { + testtri.tri = *virusloop; + /* A triangle is marked as infected by messing with one of its shell */ + /* edges, setting it to an illegal value. Hence, we have to */ + /* temporarily uninfect this triangle so that we can examine its */ + /* adjacent shell edges. */ + uninfect(testtri); + if (regionattrib) { + /* Set an attribute. */ + setelemattribute(testtri, eextras, attribute); + } + if (vararea) { + /* Set an area constraint. */ + setareabound(testtri, area); + } + if (verbose > 2) { + /* Assign the triangle an orientation for convenience in */ + /* checking its points. */ + testtri.orient = 0; + org(testtri, regionorg); + dest(testtri, regiondest); + apex(testtri, regionapex); + printf(" Checking (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n", + regionorg[0], regionorg[1], regiondest[0], regiondest[1], + regionapex[0], regionapex[1]); + } + /* Check each of the triangle's three neighbors. */ + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) { + /* Find the neighbor. */ + sym(testtri, neighbor); + /* Check for a shell between the triangle and its neighbor. */ + tspivot(testtri, neighborshelle); + /* Make sure the neighbor exists, is not already infected, and */ + /* isn't protected by a shell edge. */ + if ((neighbor.tri != dummytri) && !infected(neighbor) + && (neighborshelle.sh == dummysh)) { + if (verbose > 2) { + org(neighbor, regionorg); + dest(neighbor, regiondest); + apex(neighbor, regionapex); + printf(" Marking (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n", + regionorg[0], regionorg[1], regiondest[0], regiondest[1], + regionapex[0], regionapex[1]); + } + /* Infect the neighbor. */ + infect(neighbor); + /* Ensure that the neighbor's neighbors will be infected. */ + regiontri = (triangle **) poolalloc(&viri); + *regiontri = neighbor.tri; + } + } + /* Remark the triangle as infected, so it doesn't get added to the */ + /* virus pool again. */ + infect(testtri); + virusloop = (triangle **) traverse(&viri); + } + + /* Uninfect all triangles. */ + if (verbose > 1) { + printf(" Unmarking marked triangles.\n"); + } + traversalinit(&viri); + virusloop = (triangle **) traverse(&viri); + while (virusloop != (triangle **) NULL) { + testtri.tri = *virusloop; + uninfect(testtri); + virusloop = (triangle **) traverse(&viri); + } + /* Empty the virus pool. */ + poolrestart(&viri); +} + +/*****************************************************************************/ +/* */ +/* carveholes() Find the holes and infect them. Find the area */ +/* constraints and infect them. Infect the convex hull. */ +/* Spread the infection and kill triangles. Spread the */ +/* area constraints. */ +/* */ +/* This routine mainly calls other routines to carry out all these */ +/* functions. */ +/* */ +/*****************************************************************************/ + +void carveholes(holelist, holes, regionlist, regions) +REAL *holelist; +int holes; +REAL *regionlist; +int regions; +{ + struct triedge searchtri; + struct triedge triangleloop; + struct triedge *regiontris; + triangle **holetri; + triangle **regiontri; + point searchorg, searchdest; + enum locateresult intersect; + int i; + triangle ptr; /* Temporary variable used by sym(). */ + + if (!(quiet || (noholes && convex))) { + printf("Removing unwanted triangles.\n"); + if (verbose && (holes > 0)) { + printf(" Marking holes for elimination.\n"); + } + } + + if (regions > 0) { + /* Allocate storage for the triangles in which region points fall. */ + regiontris = (struct triedge *) malloc(regions * sizeof(struct triedge)); + if (regiontris == (struct triedge *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + + if (((holes > 0) && !noholes) || !convex || (regions > 0)) { + /* Initialize a pool of viri to be used for holes, concavities, */ + /* regional attributes, and/or regional area constraints. */ + poolinit(&viri, sizeof(triangle *), VIRUSPERBLOCK, POINTER, 0); + } + + if (!convex) { + /* Mark as infected any unprotected triangles on the boundary. */ + /* This is one way by which concavities are created. */ + infecthull(); + } + + if ((holes > 0) && !noholes) { + /* Infect each triangle in which a hole lies. */ + for (i = 0; i < 2 * holes; i += 2) { + /* Ignore holes that aren't within the bounds of the mesh. */ + if ((holelist[i] >= xmin) && (holelist[i] <= xmax) + && (holelist[i + 1] >= ymin) && (holelist[i + 1] <= ymax)) { + /* Start searching from some triangle on the outer boundary. */ + searchtri.tri = dummytri; + searchtri.orient = 0; + symself(searchtri); + /* Ensure that the hole is to the left of this boundary edge; */ + /* otherwise, locate() will falsely report that the hole */ + /* falls within the starting triangle. */ + org(searchtri, searchorg); + dest(searchtri, searchdest); + if (counterclockwise(searchorg, searchdest, &holelist[i]) > 0.0) { + /* Find a triangle that contains the hole. */ + intersect = locate(&holelist[i], &searchtri); + if ((intersect != OUTSIDE) && (!infected(searchtri))) { + /* Infect the triangle. This is done by marking the triangle */ + /* as infect and including the triangle in the virus pool. */ + infect(searchtri); + holetri = (triangle **) poolalloc(&viri); + *holetri = searchtri.tri; + } + } + } + } + } + + /* Now, we have to find all the regions BEFORE we carve the holes, because */ + /* locate() won't work when the triangulation is no longer convex. */ + /* (Incidentally, this is the reason why regional attributes and area */ + /* constraints can't be used when refining a preexisting mesh, which */ + /* might not be convex; they can only be used with a freshly */ + /* triangulated PSLG.) */ + if (regions > 0) { + /* Find the starting triangle for each region. */ + for (i = 0; i < regions; i++) { + regiontris[i].tri = dummytri; + /* Ignore region points that aren't within the bounds of the mesh. */ + if ((regionlist[4 * i] >= xmin) && (regionlist[4 * i] <= xmax) && + (regionlist[4 * i + 1] >= ymin) && (regionlist[4 * i + 1] <= ymax)) { + /* Start searching from some triangle on the outer boundary. */ + searchtri.tri = dummytri; + searchtri.orient = 0; + symself(searchtri); + /* Ensure that the region point is to the left of this boundary */ + /* edge; otherwise, locate() will falsely report that the */ + /* region point falls within the starting triangle. */ + org(searchtri, searchorg); + dest(searchtri, searchdest); + if (counterclockwise(searchorg, searchdest, ®ionlist[4 * i]) > + 0.0) { + /* Find a triangle that contains the region point. */ + intersect = locate(®ionlist[4 * i], &searchtri); + if ((intersect != OUTSIDE) && (!infected(searchtri))) { + /* Record the triangle for processing after the */ + /* holes have been carved. */ + triedgecopy(searchtri, regiontris[i]); + } + } + } + } + } + + if (viri.items > 0) { + /* Carve the holes and concavities. */ + plague(); + } + /* The virus pool should be empty now. */ + + if (regions > 0) { + if (!quiet) { + if (regionattrib) { + if (vararea) { + printf("Spreading regional attributes and area constraints.\n"); + } else { + printf("Spreading regional attributes.\n"); + } + } else { + printf("Spreading regional area constraints.\n"); + } + } + if (regionattrib && !refine) { + /* Assign every triangle a regional attribute of zero. */ + traversalinit(&triangles); + triangleloop.orient = 0; + triangleloop.tri = triangletraverse(); + while (triangleloop.tri != (triangle *) NULL) { + setelemattribute(triangleloop, eextras, 0.0); + triangleloop.tri = triangletraverse(); + } + } + for (i = 0; i < regions; i++) { + if (regiontris[i].tri != dummytri) { + /* Make sure the triangle under consideration still exists. */ + /* It may have been eaten by the virus. */ + if (regiontris[i].tri[3] != (triangle) NULL) { + /* Put one triangle in the virus pool. */ + infect(regiontris[i]); + regiontri = (triangle **) poolalloc(&viri); + *regiontri = regiontris[i].tri; + /* Apply one region's attribute and/or area constraint. */ + regionplague(regionlist[4 * i + 2], regionlist[4 * i + 3]); + /* The virus pool should be empty now. */ + } + } + } + if (regionattrib && !refine) { + /* Note the fact that each triangle has an additional attribute. */ + eextras++; + } + } + + /* Free up memory. */ + if (((holes > 0) && !noholes) || !convex || (regions > 0)) { + pooldeinit(&viri); + } + if (regions > 0) { + free(regiontris); + } +} + +/** **/ +/** **/ +/********* Carving out holes and concavities ends here *********/ + +/********* Mesh quality maintenance begins here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* tallyencs() Traverse the entire list of shell edges, check each edge */ +/* to see if it is encroached. If so, add it to the list. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +void tallyencs() +{ + struct edge edgeloop; + int dummy; + + traversalinit(&shelles); + edgeloop.shorient = 0; + edgeloop.sh = shelletraverse(); + while (edgeloop.sh != (shelle *) NULL) { + /* If the segment is encroached, add it to the list. */ + dummy = checkedge4encroach(&edgeloop); + edgeloop.sh = shelletraverse(); + } +} + +#endif /* not CDT_ONLY */ + +/*****************************************************************************/ +/* */ +/* precisionerror() Print an error message for precision problems. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +void precisionerror() +{ + printf("Try increasing the area criterion and/or reducing the minimum\n"); + printf(" allowable angle so that tiny triangles are not created.\n"); +#ifdef SINGLE + printf("Alternatively, try recompiling me with double precision\n"); + printf(" arithmetic (by removing \"#define SINGLE\" from the\n"); + printf(" source file or \"-DSINGLE\" from the makefile).\n"); +#endif /* SINGLE */ +} + +#endif /* not CDT_ONLY */ + +/*****************************************************************************/ +/* */ +/* repairencs() Find and repair all the encroached segments. */ +/* */ +/* Encroached segments are repaired by splitting them by inserting a point */ +/* at or near their centers. */ +/* */ +/* `flaws' is a flag that specifies whether one should take note of new */ +/* encroached segments and bad triangles that result from inserting points */ +/* to repair existing encroached segments. */ +/* */ +/* When a segment is split, the two resulting subsegments are always */ +/* tested to see if they are encroached upon, regardless of the value */ +/* of `flaws'. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +void repairencs(flaws) +int flaws; +{ + struct triedge enctri; + struct triedge testtri; + struct edge *encloop; + struct edge testsh; + point eorg, edest; + point newpoint; + enum insertsiteresult success; + REAL segmentlength, nearestpoweroftwo; + REAL split; + int acuteorg, acutedest; + int dummy; + int i; + triangle ptr; /* Temporary variable used by stpivot(). */ + shelle sptr; /* Temporary variable used by snext(). */ + + while ((badsegments.items > 0) && (steinerleft != 0)) { + traversalinit(&badsegments); + encloop = badsegmenttraverse(); + while ((encloop != (struct edge *) NULL) && (steinerleft != 0)) { + /* To decide where to split a segment, we need to know if the */ + /* segment shares an endpoint with an adjacent segment. */ + /* The concern is that, if we simply split every encroached */ + /* segment in its center, two adjacent segments with a small */ + /* angle between them might lead to an infinite loop; each */ + /* point added to split one segment will encroach upon the */ + /* other segment, which must then be split with a point that */ + /* will encroach upon the first segment, and so on forever. */ + /* To avoid this, imagine a set of concentric circles, whose */ + /* radii are powers of two, about each segment endpoint. */ + /* These concentric circles determine where the segment is */ + /* split. (If both endpoints are shared with adjacent */ + /* segments, split the segment in the middle, and apply the */ + /* concentric shells for later splittings.) */ + + /* Is the origin shared with another segment? */ + stpivot(*encloop, enctri); + lnext(enctri, testtri); + tspivot(testtri, testsh); + acuteorg = testsh.sh != dummysh; + /* Is the destination shared with another segment? */ + lnextself(testtri); + tspivot(testtri, testsh); + acutedest = testsh.sh != dummysh; + /* Now, check the other side of the segment, if there's a triangle */ + /* there. */ + sym(enctri, testtri); + if (testtri.tri != dummytri) { + /* Is the destination shared with another segment? */ + lnextself(testtri); + tspivot(testtri, testsh); + acutedest = acutedest || (testsh.sh != dummysh); + /* Is the origin shared with another segment? */ + lnextself(testtri); + tspivot(testtri, testsh); + acuteorg = acuteorg || (testsh.sh != dummysh); + } + + sorg(*encloop, eorg); + sdest(*encloop, edest); + /* Use the concentric circles if exactly one endpoint is shared */ + /* with another adjacent segment. */ + if (acuteorg ^ acutedest) { + segmentlength = sqrt((edest[0] - eorg[0]) * (edest[0] - eorg[0]) + + (edest[1] - eorg[1]) * (edest[1] - eorg[1])); + /* Find the power of two nearest the segment's length. */ + nearestpoweroftwo = 1.0; + while (segmentlength > SQUAREROOTTWO * nearestpoweroftwo) { + nearestpoweroftwo *= 2.0; + } + while (segmentlength < (0.5 * SQUAREROOTTWO) * nearestpoweroftwo) { + nearestpoweroftwo *= 0.5; + } + /* Where do we split the segment? */ + split = 0.5 * nearestpoweroftwo / segmentlength; + if (acutedest) { + split = 1.0 - split; + } + } else { + /* If we're not worried about adjacent segments, split */ + /* this segment in the middle. */ + split = 0.5; + } + + /* Create the new point. */ + newpoint = (point) poolalloc(&points); + /* Interpolate its coordinate and attributes. */ + for (i = 0; i < 2 + nextras; i++) { + newpoint[i] = (1.0 - split) * eorg[i] + split * edest[i]; + } + setpointmark(newpoint, mark(*encloop)); + if (verbose > 1) { + printf( + " Splitting edge (%.12g, %.12g) (%.12g, %.12g) at (%.12g, %.12g).\n", + eorg[0], eorg[1], edest[0], edest[1], newpoint[0], newpoint[1]); + } + /* Check whether the new point lies on an endpoint. */ + if (((newpoint[0] == eorg[0]) && (newpoint[1] == eorg[1])) + || ((newpoint[0] == edest[0]) && (newpoint[1] == edest[1]))) { + printf("Error: Ran out of precision at (%.12g, %.12g).\n", + newpoint[0], newpoint[1]); + printf("I attempted to split a segment to a smaller size than can\n"); + printf(" be accommodated by the finite precision of floating point\n" + ); + printf(" arithmetic.\n"); + precisionerror(); + exit(1); + } + /* Insert the splitting point. This should always succeed. */ + success = insertsite(newpoint, &enctri, encloop, flaws, flaws); + if ((success != SUCCESSFULPOINT) && (success != ENCROACHINGPOINT)) { + printf("Internal error in repairencs():\n"); + printf(" Failure to split a segment.\n"); + internalerror(); + } + if (steinerleft > 0) { + steinerleft--; + } + /* Check the two new subsegments to see if they're encroached. */ + dummy = checkedge4encroach(encloop); + snextself(*encloop); + dummy = checkedge4encroach(encloop); + + badsegmentdealloc(encloop); + encloop = badsegmenttraverse(); + } + } +} + +#endif /* not CDT_ONLY */ + +/*****************************************************************************/ +/* */ +/* tallyfaces() Test every triangle in the mesh for quality measures. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +void tallyfaces() +{ + struct triedge triangleloop; + + if (verbose) { + printf(" Making a list of bad triangles.\n"); + } + traversalinit(&triangles); + triangleloop.orient = 0; + triangleloop.tri = triangletraverse(); + while (triangleloop.tri != (triangle *) NULL) { + /* If the triangle is bad, enqueue it. */ + testtriangle(&triangleloop); + triangleloop.tri = triangletraverse(); + } +} + +#endif /* not CDT_ONLY */ + +/*****************************************************************************/ +/* */ +/* findcircumcenter() Find the circumcenter of a triangle. */ +/* */ +/* The result is returned both in terms of x-y coordinates and xi-eta */ +/* coordinates. The xi-eta coordinate system is defined in terms of the */ +/* triangle: the origin of the triangle is the origin of the coordinate */ +/* system; the destination of the triangle is one unit along the xi axis; */ +/* and the apex of the triangle is one unit along the eta axis. */ +/* */ +/* The return value indicates which edge of the triangle is shortest. */ +/* */ +/*****************************************************************************/ + +enum circumcenterresult findcircumcenter(torg, tdest, tapex, circumcenter, + xi, eta) +point torg; +point tdest; +point tapex; +point circumcenter; +REAL *xi; +REAL *eta; +{ + REAL xdo, ydo, xao, yao, xad, yad; + REAL dodist, aodist, addist; + REAL denominator; + REAL dx, dy; + + circumcentercount++; + + /* Compute the circumcenter of the triangle. */ + xdo = tdest[0] - torg[0]; + ydo = tdest[1] - torg[1]; + xao = tapex[0] - torg[0]; + yao = tapex[1] - torg[1]; + dodist = xdo * xdo + ydo * ydo; + aodist = xao * xao + yao * yao; + if (noexact) { + denominator = (REAL)(0.5 / (xdo * yao - xao * ydo)); + } else { + /* Use the counterclockwise() routine to ensure a positive (and */ + /* reasonably accurate) result, avoiding any possibility of */ + /* division by zero. */ + denominator = (REAL)(0.5 / counterclockwise(tdest, tapex, torg)); + /* Don't count the above as an orientation test. */ + counterclockcount--; + } + circumcenter[0] = torg[0] - (ydo * aodist - yao * dodist) * denominator; + circumcenter[1] = torg[1] + (xdo * aodist - xao * dodist) * denominator; + + /* To interpolate point attributes for the new point inserted at */ + /* the circumcenter, define a coordinate system with a xi-axis, */ + /* directed from the triangle's origin to its destination, and */ + /* an eta-axis, directed from its origin to its apex. */ + /* Calculate the xi and eta coordinates of the circumcenter. */ + dx = circumcenter[0] - torg[0]; + dy = circumcenter[1] - torg[1]; + *xi = (REAL)((dx * yao - xao * dy) * (2.0 * denominator)); + *eta = (REAL)((xdo * dy - dx * ydo) * (2.0 * denominator)); + + xad = tapex[0] - tdest[0]; + yad = tapex[1] - tdest[1]; + addist = xad * xad + yad * yad; + if ((addist < dodist) && (addist < aodist)) { + return OPPOSITEORG; + } else if (dodist < aodist) { + return OPPOSITEAPEX; + } else { + return OPPOSITEDEST; + } +} + +/*****************************************************************************/ +/* */ +/* splittriangle() Inserts a point at the circumcenter of a triangle. */ +/* Deletes the newly inserted point if it encroaches upon */ +/* a segment. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +void splittriangle(badtri) +struct badface *badtri; +{ + point borg, bdest, bapex; + point newpoint; + REAL xi, eta; + enum insertsiteresult success; + enum circumcenterresult shortedge; + int errorflag; + int i; + + org(badtri->badfacetri, borg); + dest(badtri->badfacetri, bdest); + apex(badtri->badfacetri, bapex); + /* Make sure that this triangle is still the same triangle it was */ + /* when it was tested and determined to be of bad quality. */ + /* Subsequent transformations may have made it a different triangle. */ + if ((borg == badtri->faceorg) && (bdest == badtri->facedest) && + (bapex == badtri->faceapex)) { + if (verbose > 1) { + printf(" Splitting this triangle at its circumcenter:\n"); + printf(" (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n", borg[0], + borg[1], bdest[0], bdest[1], bapex[0], bapex[1]); + } + errorflag = 0; + /* Create a new point at the triangle's circumcenter. */ + newpoint = (point) poolalloc(&points); + shortedge = findcircumcenter(borg, bdest, bapex, newpoint, &xi, &eta); + /* Check whether the new point lies on a triangle vertex. */ + if (((newpoint[0] == borg[0]) && (newpoint[1] == borg[1])) + || ((newpoint[0] == bdest[0]) && (newpoint[1] == bdest[1])) + || ((newpoint[0] == bapex[0]) && (newpoint[1] == bapex[1]))) { + if (!quiet) { + printf("Warning: New point (%.12g, %.12g) falls on existing vertex.\n" + , newpoint[0], newpoint[1]); + errorflag = 1; + } + pointdealloc(newpoint); + } else { + for (i = 2; i < 2 + nextras; i++) { + /* Interpolate the point attributes at the circumcenter. */ + newpoint[i] = borg[i] + xi * (bdest[i] - borg[i]) + + eta * (bapex[i] - borg[i]); + } + /* The new point must be in the interior, and have a marker of zero. */ + setpointmark(newpoint, 0); + /* Ensure that the handle `badtri->badfacetri' represents the shortest */ + /* edge of the triangle. This ensures that the circumcenter must */ + /* fall to the left of this edge, so point location will work. */ + if (shortedge == OPPOSITEORG) { + lnextself(badtri->badfacetri); + } else if (shortedge == OPPOSITEDEST) { + lprevself(badtri->badfacetri); + } + /* Insert the circumcenter, searching from the edge of the triangle, */ + /* and maintain the Delaunay property of the triangulation. */ + success = insertsite(newpoint, &(badtri->badfacetri), + (struct edge *) NULL, 1, 1); + if (success == SUCCESSFULPOINT) { + if (steinerleft > 0) { + steinerleft--; + } + } else if (success == ENCROACHINGPOINT) { + /* If the newly inserted point encroaches upon a segment, delete it. */ + deletesite(&(badtri->badfacetri)); + } else if (success == VIOLATINGPOINT) { + /* Failed to insert the new point, but some segment was */ + /* marked as being encroached. */ + pointdealloc(newpoint); + } else { /* success == DUPLICATEPOINT */ + /* Failed to insert the new point because a vertex is already there. */ + if (!quiet) { + printf( + "Warning: New point (%.12g, %.12g) falls on existing vertex.\n" + , newpoint[0], newpoint[1]); + errorflag = 1; + } + pointdealloc(newpoint); + } + } + if (errorflag) { + if (verbose) { + printf(" The new point is at the circumcenter of triangle\n"); + printf(" (%.12g, %.12g) (%.12g, %.12g) (%.12g, %.12g)\n", + borg[0], borg[1], bdest[0], bdest[1], bapex[0], bapex[1]); + } + printf("This probably means that I am trying to refine triangles\n"); + printf(" to a smaller size than can be accommodated by the finite\n"); + printf(" precision of floating point arithmetic. (You can be\n"); + printf(" sure of this if I fail to terminate.)\n"); + precisionerror(); + } + } + /* Return the bad triangle to the pool. */ + pooldealloc(&badtriangles, (VOID *) badtri); +} + +#endif /* not CDT_ONLY */ + +/*****************************************************************************/ +/* */ +/* enforcequality() Remove all the encroached edges and bad triangles */ +/* from the triangulation. */ +/* */ +/*****************************************************************************/ + +#ifndef CDT_ONLY + +void enforcequality() +{ + int i; + + if (!quiet) { + printf("Adding Steiner points to enforce quality.\n"); + } + /* Initialize the pool of encroached segments. */ + poolinit(&badsegments, sizeof(struct edge), BADSEGMENTPERBLOCK, POINTER, 0); + if (verbose) { + printf(" Looking for encroached segments.\n"); + } + /* Test all segments to see if they're encroached. */ + tallyencs(); + if (verbose && (badsegments.items > 0)) { + printf(" Splitting encroached segments.\n"); + } + /* Note that steinerleft == -1 if an unlimited number */ + /* of Steiner points is allowed. */ + while ((badsegments.items > 0) && (steinerleft != 0)) { + /* Fix the segments without noting newly encroached segments or */ + /* bad triangles. The reason we don't want to note newly */ + /* encroached segments is because some encroached segments are */ + /* likely to be noted multiple times, and would then be blindly */ + /* split multiple times. I should fix that some time. */ + repairencs(0); + /* Now, find all the segments that became encroached while adding */ + /* points to split encroached segments. */ + tallyencs(); + } + /* At this point, if we haven't run out of Steiner points, the */ + /* triangulation should be (conforming) Delaunay. */ + + /* Next, we worry about enforcing triangle quality. */ + if ((minangle > 0.0) || vararea || fixedarea) { + /* Initialize the pool of bad triangles. */ + poolinit(&badtriangles, sizeof(struct badface), BADTRIPERBLOCK, POINTER, + 0); + /* Initialize the queues of bad triangles. */ + for (i = 0; i < 64; i++) { + queuefront[i] = (struct badface *) NULL; + queuetail[i] = &queuefront[i]; + } + /* Test all triangles to see if they're bad. */ + tallyfaces(); + if (verbose) { + printf(" Splitting bad triangles.\n"); + } + while ((badtriangles.items > 0) && (steinerleft != 0)) { + /* Fix one bad triangle by inserting a point at its circumcenter. */ + splittriangle(dequeuebadtri()); + /* Fix any encroached segments that may have resulted. Record */ + /* any new bad triangles or encroached segments that result. */ + if (badsegments.items > 0) { + repairencs(1); + } + } + } + /* At this point, if we haven't run out of Steiner points, the */ + /* triangulation should be (conforming) Delaunay and have no */ + /* low-quality triangles. */ + + /* Might we have run out of Steiner points too soon? */ + if (!quiet && (badsegments.items > 0) && (steinerleft == 0)) { + printf("\nWarning: I ran out of Steiner points, but the mesh has\n"); + if (badsegments.items == 1) { + printf(" an encroached segment, and therefore might not be truly\n"); + } else { + printf(" %ld encroached segments, and therefore might not be truly\n", + badsegments.items); + } + printf(" Delaunay. If the Delaunay property is important to you,\n"); + printf(" try increasing the number of Steiner points (controlled by\n"); + printf(" the -S switch) slightly and try again.\n\n"); + } +} + +#endif /* not CDT_ONLY */ + +/** **/ +/** **/ +/********* Mesh quality maintenance ends here *********/ + +/*****************************************************************************/ +/* */ +/* highorder() Create extra nodes for quadratic subparametric elements. */ +/* */ +/*****************************************************************************/ + +void highorder() +{ + struct triedge triangleloop, trisym; + struct edge checkmark; + point newpoint; + point torg, tdest; + int i; + triangle ptr; /* Temporary variable used by sym(). */ + shelle sptr; /* Temporary variable used by tspivot(). */ + + if (!quiet) { + printf("Adding vertices for second-order triangles.\n"); + } + /* The following line ensures that dead items in the pool of nodes */ + /* cannot be allocated for the extra nodes associated with high */ + /* order elements. This ensures that the primary nodes (at the */ + /* corners of elements) will occur earlier in the output files, and */ + /* have lower indices, than the extra nodes. */ + points.deaditemstack = (VOID *) NULL; + + traversalinit(&triangles); + triangleloop.tri = triangletraverse(); + /* To loop over the set of edges, loop over all triangles, and look at */ + /* the three edges of each triangle. If there isn't another triangle */ + /* adjacent to the edge, operate on the edge. If there is another */ + /* adjacent triangle, operate on the edge only if the current triangle */ + /* has a smaller pointer than its neighbor. This way, each edge is */ + /* considered only once. */ + while (triangleloop.tri != (triangle *) NULL) { + for (triangleloop.orient = 0; triangleloop.orient < 3; + triangleloop.orient++) { + sym(triangleloop, trisym); + if ((triangleloop.tri < trisym.tri) || (trisym.tri == dummytri)) { + org(triangleloop, torg); + dest(triangleloop, tdest); + /* Create a new node in the middle of the edge. Interpolate */ + /* its attributes. */ + newpoint = (point) poolalloc(&points); + for (i = 0; i < 2 + nextras; i++) { + newpoint[i] = (REAL)(0.5 * (torg[i] + tdest[i])); + } + /* Set the new node's marker to zero or one, depending on */ + /* whether it lies on a boundary. */ + setpointmark(newpoint, trisym.tri == dummytri); + if (useshelles) { + tspivot(triangleloop, checkmark); + /* If this edge is a segment, transfer the marker to the new node. */ + if (checkmark.sh != dummysh) { + setpointmark(newpoint, mark(checkmark)); + } + } + if (verbose > 1) { + printf(" Creating (%.12g, %.12g).\n", newpoint[0], newpoint[1]); + } + /* Record the new node in the (one or two) adjacent elements. */ + triangleloop.tri[highorderindex + triangleloop.orient] = + (triangle) newpoint; + if (trisym.tri != dummytri) { + trisym.tri[highorderindex + trisym.orient] = (triangle) newpoint; + } + } + } + triangleloop.tri = triangletraverse(); + } +} + +/********* File I/O routines begin here *********/ +/** **/ +/** **/ + +/*****************************************************************************/ +/* */ +/* readline() Read a nonempty line from a file. */ +/* */ +/* A line is considered "nonempty" if it contains something that looks like */ +/* a number. */ +/* */ +/*****************************************************************************/ + +#ifndef TRILIBRARY + +char *readline(string, infile, infilename) +char *string; +FILE *infile; +char *infilename; +{ + char *result; + + /* Search for something that looks like a number. */ + do { + result = fgets(string, INPUTLINESIZE, infile); + if (result == (char *) NULL) { + printf(" Error: Unexpected end of file in %s.\n", infilename); + exit(1); + } + /* Skip anything that doesn't look like a number, a comment, */ + /* or the end of a line. */ + while ((*result != '\0') && (*result != '#') + && (*result != '.') && (*result != '+') && (*result != '-') + && ((*result < '0') || (*result > '9'))) { + result++; + } + /* If it's a comment or end of line, read another line and try again. */ + } while ((*result == '#') || (*result == '\0')); + return result; +} + +#endif /* not TRILIBRARY */ + +/*****************************************************************************/ +/* */ +/* findfield() Find the next field of a string. */ +/* */ +/* Jumps past the current field by searching for whitespace, then jumps */ +/* past the whitespace to find the next field. */ +/* */ +/*****************************************************************************/ + +#ifndef TRILIBRARY + +char *findfield(string) +char *string; +{ + char *result; + + result = string; + /* Skip the current field. Stop upon reaching whitespace. */ + while ((*result != '\0') && (*result != '#') + && (*result != ' ') && (*result != '\t')) { + result++; + } + /* Now skip the whitespace and anything else that doesn't look like a */ + /* number, a comment, or the end of a line. */ + while ((*result != '\0') && (*result != '#') + && (*result != '.') && (*result != '+') && (*result != '-') + && ((*result < '0') || (*result > '9'))) { + result++; + } + /* Check for a comment (prefixed with `#'). */ + if (*result == '#') { + *result = '\0'; + } + return result; +} + +#endif /* not TRILIBRARY */ + +/*****************************************************************************/ +/* */ +/* readnodes() Read the points from a file, which may be a .node or .poly */ +/* file. */ +/* */ +/*****************************************************************************/ + +#ifndef TRILIBRARY + +void readnodes(nodefilename, polyfilename, polyfile) +char *nodefilename; +char *polyfilename; +FILE **polyfile; +{ + FILE *infile; + point pointloop; + char inputline[INPUTLINESIZE]; + char *stringptr; + char *infilename; + REAL x, y; + int firstnode; + int nodemarkers; + int currentmarker; + int i, j; + + if (poly) { + /* Read the points from a .poly file. */ + if (!quiet) { + printf("Opening %s.\n", polyfilename); + } + *polyfile = fopen(polyfilename, "r"); + if (*polyfile == (FILE *) NULL) { + printf(" Error: Cannot access file %s.\n", polyfilename); + exit(1); + } + /* Read number of points, number of dimensions, number of point */ + /* attributes, and number of boundary markers. */ + stringptr = readline(inputline, *polyfile, polyfilename); + inpoints = (int) strtol (stringptr, &stringptr, 0); + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + mesh_dim = 2; + } else { + mesh_dim = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + nextras = 0; + } else { + nextras = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + nodemarkers = 0; + } else { + nodemarkers = (int) strtol (stringptr, &stringptr, 0); + } + if (inpoints > 0) { + infile = *polyfile; + infilename = polyfilename; + readnodefile = 0; + } else { + /* If the .poly file claims there are zero points, that means that */ + /* the points should be read from a separate .node file. */ + readnodefile = 1; + infilename = innodefilename; + } + } else { + readnodefile = 1; + infilename = innodefilename; + *polyfile = (FILE *) NULL; + } + + if (readnodefile) { + /* Read the points from a .node file. */ + if (!quiet) { + printf("Opening %s.\n", innodefilename); + } + infile = fopen(innodefilename, "r"); + if (infile == (FILE *) NULL) { + printf(" Error: Cannot access file %s.\n", innodefilename); + exit(1); + } + /* Read number of points, number of dimensions, number of point */ + /* attributes, and number of boundary markers. */ + stringptr = readline(inputline, infile, innodefilename); + inpoints = (int) strtol (stringptr, &stringptr, 0); + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + mesh_dim = 2; + } else { + mesh_dim = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + nextras = 0; + } else { + nextras = (int) strtol (stringptr, &stringptr, 0); + } + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + nodemarkers = 0; + } else { + nodemarkers = (int) strtol (stringptr, &stringptr, 0); + } + } + + if (inpoints < 3) { + printf("Error: Input must have at least three input points.\n"); + exit(1); + } + if (mesh_dim != 2) { + printf("Error: Triangle only works with two-dimensional meshes.\n"); + exit(1); + } + + initializepointpool(); + + /* Read the points. */ + for (i = 0; i < inpoints; i++) { + pointloop = (point) poolalloc(&points); + stringptr = readline(inputline, infile, infilename); + if (i == 0) { + firstnode = (int) strtol (stringptr, &stringptr, 0); + if ((firstnode == 0) || (firstnode == 1)) { + firstnumber = firstnode; + } + } + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + printf("Error: Point %d has no x coordinate.\n", firstnumber + i); + exit(1); + } + x = (REAL) strtod(stringptr, &stringptr); + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + printf("Error: Point %d has no y coordinate.\n", firstnumber + i); + exit(1); + } + y = (REAL) strtod(stringptr, &stringptr); + pointloop[0] = x; + pointloop[1] = y; + /* Read the point attributes. */ + for (j = 2; j < 2 + nextras; j++) { + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + pointloop[j] = 0.0; + } else { + pointloop[j] = (REAL) strtod(stringptr, &stringptr); + } + } + if (nodemarkers) { + /* Read a point marker. */ + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + setpointmark(pointloop, 0); + } else { + currentmarker = (int) strtol (stringptr, &stringptr, 0); + setpointmark(pointloop, currentmarker); + } + } else { + /* If no markers are specified in the file, they default to zero. */ + setpointmark(pointloop, 0); + } + /* Determine the smallest and largest x and y coordinates. */ + if (i == 0) { + xmin = xmax = x; + ymin = ymax = y; + } else { + xmin = (x < xmin) ? x : xmin; + xmax = (x > xmax) ? x : xmax; + ymin = (y < ymin) ? y : ymin; + ymax = (y > ymax) ? y : ymax; + } + } + if (readnodefile) { + fclose(infile); + } + + /* Nonexistent x value used as a flag to mark circle events in sweepline */ + /* Delaunay algorithm. */ + xminextreme = 10 * xmin - 9 * xmax; +} + +#endif /* not TRILIBRARY */ + +/*****************************************************************************/ +/* */ +/* transfernodes() Read the points from memory. */ +/* */ +/*****************************************************************************/ + +#ifdef TRILIBRARY + +void transfernodes(pointlist, pointattriblist, pointmarkerlist, numberofpoints, + numberofpointattribs) +REAL *pointlist; +REAL *pointattriblist; +int *pointmarkerlist; +int numberofpoints; +int numberofpointattribs; +{ + point pointloop; + REAL x, y; + int i, j; + int coordindex; + int attribindex; + + inpoints = numberofpoints; + mesh_dim = 2; + nextras = numberofpointattribs; + readnodefile = 0; + if (inpoints < 3) { + printf("Error: Input must have at least three input points.\n"); + exit(1); + } + + initializepointpool(); + + /* Read the points. */ + coordindex = 0; + attribindex = 0; + for (i = 0; i < inpoints; i++) { + pointloop = (point) poolalloc(&points); + /* Read the point coordinates. */ + x = pointloop[0] = pointlist[coordindex++]; + y = pointloop[1] = pointlist[coordindex++]; + /* Read the point attributes. */ + for (j = 0; j < numberofpointattribs; j++) { + pointloop[2 + j] = pointattriblist[attribindex++]; + } + if (pointmarkerlist != (int *) NULL) { + /* Read a point marker. */ + setpointmark(pointloop, pointmarkerlist[i]); + } else { + /* If no markers are specified, they default to zero. */ + setpointmark(pointloop, 0); + } + x = pointloop[0]; + y = pointloop[1]; + /* Determine the smallest and largest x and y coordinates. */ + if (i == 0) { + xmin = xmax = x; + ymin = ymax = y; + } else { + xmin = (x < xmin) ? x : xmin; + xmax = (x > xmax) ? x : xmax; + ymin = (y < ymin) ? y : ymin; + ymax = (y > ymax) ? y : ymax; + } + } + + /* Nonexistent x value used as a flag to mark circle events in sweepline */ + /* Delaunay algorithm. */ + xminextreme = 10 * xmin - 9 * xmax; +} + +#endif /* TRILIBRARY */ + +/*****************************************************************************/ +/* */ +/* readholes() Read the holes, and possibly regional attributes and area */ +/* constraints, from a .poly file. */ +/* */ +/*****************************************************************************/ + +#ifndef TRILIBRARY + +void readholes(polyfile, polyfilename, hlist, holes, rlist, regions) +FILE *polyfile; +char *polyfilename; +REAL **hlist; +int *holes; +REAL **rlist; +int *regions; +{ + REAL *holelist; + REAL *regionlist; + char inputline[INPUTLINESIZE]; + char *stringptr; + int index; + int i; + + /* Read the holes. */ + stringptr = readline(inputline, polyfile, polyfilename); + *holes = (int) strtol (stringptr, &stringptr, 0); + if (*holes > 0) { + holelist = (REAL *) malloc(2 * *holes * sizeof(REAL)); + *hlist = holelist; + if (holelist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + for (i = 0; i < 2 * *holes; i += 2) { + stringptr = readline(inputline, polyfile, polyfilename); + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + printf("Error: Hole %d has no x coordinate.\n", + firstnumber + (i >> 1)); + exit(1); + } else { + holelist[i] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + printf("Error: Hole %d has no y coordinate.\n", + firstnumber + (i >> 1)); + exit(1); + } else { + holelist[i + 1] = (REAL) strtod(stringptr, &stringptr); + } + } + } else { + *hlist = (REAL *) NULL; + } + +#ifndef CDT_ONLY + if ((regionattrib || vararea) && !refine) { + /* Read the area constraints. */ + stringptr = readline(inputline, polyfile, polyfilename); + *regions = (int) strtol (stringptr, &stringptr, 0); + if (*regions > 0) { + regionlist = (REAL *) malloc(4 * *regions * sizeof(REAL)); + *rlist = regionlist; + if (regionlist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + index = 0; + for (i = 0; i < *regions; i++) { + stringptr = readline(inputline, polyfile, polyfilename); + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + printf("Error: Region %d has no x coordinate.\n", + firstnumber + i); + exit(1); + } else { + regionlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + printf("Error: Region %d has no y coordinate.\n", + firstnumber + i); + exit(1); + } else { + regionlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + printf( + "Error: Region %d has no region attribute or area constraint.\n", + firstnumber + i); + exit(1); + } else { + regionlist[index++] = (REAL) strtod(stringptr, &stringptr); + } + stringptr = findfield(stringptr); + if (*stringptr == '\0') { + regionlist[index] = regionlist[index - 1]; + } else { + regionlist[index] = (REAL) strtod(stringptr, &stringptr); + } + index++; + } + } + } else { + /* Set `*regions' to zero to avoid an accidental free() later. */ + *regions = 0; + *rlist = (REAL *) NULL; + } +#endif /* not CDT_ONLY */ + + fclose(polyfile); +} + +#endif /* not TRILIBRARY */ + +/*****************************************************************************/ +/* */ +/* finishfile() Write the command line to the output file so the user */ +/* can remember how the file was generated. Close the file. */ +/* */ +/*****************************************************************************/ + +#ifndef TRILIBRARY + +void finishfile(outfile, argc, argv) +FILE *outfile; +int argc; +char **argv; +{ + int i; + + fprintf(outfile, "# Generated by"); + for (i = 0; i < argc; i++) { + fprintf(outfile, " "); + fputs(argv[i], outfile); + } + fprintf(outfile, "\n"); + fclose(outfile); +} + +#endif /* not TRILIBRARY */ + +/*****************************************************************************/ +/* */ +/* writenodes() Number the points and write them to a .node file. */ +/* */ +/* To save memory, the point numbers are written over the shell markers */ +/* after the points are written to a file. */ +/* */ +/*****************************************************************************/ + +#ifdef TRILIBRARY + +void writenodes(pointlist, pointattriblist, pointmarkerlist) +REAL **pointlist; +REAL **pointattriblist; +int **pointmarkerlist; + +#else /* not TRILIBRARY */ + +void writenodes(nodefilename, argc, argv) +char *nodefilename; +int argc; +char **argv; + +#endif /* not TRILIBRARY */ + +{ +#ifdef TRILIBRARY + REAL *plist; + REAL *palist; + int *pmlist; + int coordindex; + int attribindex; +#else /* not TRILIBRARY */ + FILE *outfile; +#endif /* not TRILIBRARY */ + point pointloop; + int pointnumber; + int i; + +#ifdef TRILIBRARY + if (!quiet) { + printf("Writing points.\n"); + } + /* Allocate memory for output points if necessary. */ + if (*pointlist == (REAL *) NULL) { + *pointlist = (REAL *) malloc(points.items * 2 * sizeof(REAL)); + if (*pointlist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + /* Allocate memory for output point attributes if necessary. */ + if ((nextras > 0) && (*pointattriblist == (REAL *) NULL)) { + *pointattriblist = (REAL *) malloc(points.items * nextras * sizeof(REAL)); + if (*pointattriblist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + /* Allocate memory for output point markers if necessary. */ + if (!nobound && (*pointmarkerlist == (int *) NULL)) { + *pointmarkerlist = (int *) malloc(points.items * sizeof(int)); + if (*pointmarkerlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + plist = *pointlist; + palist = *pointattriblist; + pmlist = *pointmarkerlist; + coordindex = 0; + attribindex = 0; +#else /* not TRILIBRARY */ + if (!quiet) { + printf("Writing %s.\n", nodefilename); + } + outfile = fopen(nodefilename, "w"); + if (outfile == (FILE *) NULL) { + printf(" Error: Cannot create file %s.\n", nodefilename); + exit(1); + } + /* Number of points, number of dimensions, number of point attributes, */ + /* and number of boundary markers (zero or one). */ + fprintf(outfile, "%ld %d %d %d\n", points.items, mesh_dim, nextras, + 1 - nobound); +#endif /* not TRILIBRARY */ + + traversalinit(&points); + pointloop = pointtraverse(); + pointnumber = firstnumber; + while (pointloop != (point) NULL) { +#ifdef TRILIBRARY + /* X and y coordinates. */ + plist[coordindex++] = pointloop[0]; + plist[coordindex++] = pointloop[1]; + /* Point attributes. */ + for (i = 0; i < nextras; i++) { + palist[attribindex++] = pointloop[2 + i]; + } + if (!nobound) { + /* Copy the boundary marker. */ + pmlist[pointnumber - firstnumber] = pointmark(pointloop); + } +#else /* not TRILIBRARY */ + /* Point number, x and y coordinates. */ + fprintf(outfile, "%4d %.17g %.17g", pointnumber, pointloop[0], + pointloop[1]); + for (i = 0; i < nextras; i++) { + /* Write an attribute. */ + fprintf(outfile, " %.17g", pointloop[i + 2]); + } + if (nobound) { + fprintf(outfile, "\n"); + } else { + /* Write the boundary marker. */ + fprintf(outfile, " %d\n", pointmark(pointloop)); + } +#endif /* not TRILIBRARY */ + + setpointmark(pointloop, pointnumber); + pointloop = pointtraverse(); + pointnumber++; + } + +#ifndef TRILIBRARY + finishfile(outfile, argc, argv); +#endif /* not TRILIBRARY */ +} + +/*****************************************************************************/ +/* */ +/* numbernodes() Number the points. */ +/* */ +/* Each point is assigned a marker equal to its number. */ +/* */ +/* Used when writenodes() is not called because no .node file is written. */ +/* */ +/*****************************************************************************/ + +void numbernodes() +{ + point pointloop; + int pointnumber; + + traversalinit(&points); + pointloop = pointtraverse(); + pointnumber = firstnumber; + while (pointloop != (point) NULL) { + setpointmark(pointloop, pointnumber); + pointloop = pointtraverse(); + pointnumber++; + } +} + +/*****************************************************************************/ +/* */ +/* writeelements() Write the triangles to an .ele file. */ +/* */ +/*****************************************************************************/ + +#ifdef TRILIBRARY + +void writeelements(trianglelist, triangleattriblist) +int **trianglelist; +REAL **triangleattriblist; + +#else /* not TRILIBRARY */ + +void writeelements(elefilename, argc, argv) +char *elefilename; +int argc; +char **argv; + +#endif /* not TRILIBRARY */ + +{ +#ifdef TRILIBRARY + int *tlist; + REAL *talist; + int pointindex; + int attribindex; +#else /* not TRILIBRARY */ + FILE *outfile; +#endif /* not TRILIBRARY */ + struct triedge triangleloop; + point p1, p2, p3; + point mid1, mid2, mid3; + int elementnumber; + int i; + +#ifdef TRILIBRARY + if (!quiet) { + printf("Writing triangles.\n"); + } + /* Allocate memory for output triangles if necessary. */ + if (*trianglelist == (int *) NULL) { + *trianglelist = (int *) malloc(triangles.items * + ((order + 1) * (order + 2) / 2) * sizeof(int)); + if (*trianglelist == (int *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + /* Allocate memory for output triangle attributes if necessary. */ + if ((eextras > 0) && (*triangleattriblist == (REAL *) NULL)) { + *triangleattriblist = (REAL *) malloc(triangles.items * eextras * + sizeof(REAL)); + if (*triangleattriblist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + tlist = *trianglelist; + talist = *triangleattriblist; + pointindex = 0; + attribindex = 0; +#else /* not TRILIBRARY */ + if (!quiet) { + printf("Writing %s.\n", elefilename); + } + outfile = fopen(elefilename, "w"); + if (outfile == (FILE *) NULL) { + printf(" Error: Cannot create file %s.\n", elefilename); + exit(1); + } + /* Number of triangles, points per triangle, attributes per triangle. */ + fprintf(outfile, "%ld %d %d\n", triangles.items, + (order + 1) * (order + 2) / 2, eextras); +#endif /* not TRILIBRARY */ + + traversalinit(&triangles); + triangleloop.tri = triangletraverse(); + triangleloop.orient = 0; + elementnumber = firstnumber; + while (triangleloop.tri != (triangle *) NULL) { + org(triangleloop, p1); + dest(triangleloop, p2); + apex(triangleloop, p3); + if (order == 1) { +#ifdef TRILIBRARY + tlist[pointindex++] = pointmark(p1); + tlist[pointindex++] = pointmark(p2); + tlist[pointindex++] = pointmark(p3); +#else /* not TRILIBRARY */ + /* Triangle number, indices for three points. */ + fprintf(outfile, "%4d %4d %4d %4d", elementnumber, + pointmark(p1), pointmark(p2), pointmark(p3)); +#endif /* not TRILIBRARY */ + } else { + mid1 = (point) triangleloop.tri[highorderindex + 1]; + mid2 = (point) triangleloop.tri[highorderindex + 2]; + mid3 = (point) triangleloop.tri[highorderindex]; +#ifdef TRILIBRARY + tlist[pointindex++] = pointmark(p1); + tlist[pointindex++] = pointmark(p2); + tlist[pointindex++] = pointmark(p3); + tlist[pointindex++] = pointmark(mid1); + tlist[pointindex++] = pointmark(mid2); + tlist[pointindex++] = pointmark(mid3); +#else /* not TRILIBRARY */ + /* Triangle number, indices for six points. */ + fprintf(outfile, "%4d %4d %4d %4d %4d %4d %4d", elementnumber, + pointmark(p1), pointmark(p2), pointmark(p3), pointmark(mid1), + pointmark(mid2), pointmark(mid3)); +#endif /* not TRILIBRARY */ + } + +#ifdef TRILIBRARY + for (i = 0; i < eextras; i++) { + talist[attribindex++] = elemattribute(triangleloop, i); + } +#else /* not TRILIBRARY */ + for (i = 0; i < eextras; i++) { + fprintf(outfile, " %.17g", elemattribute(triangleloop, i)); + } + fprintf(outfile, "\n"); +#endif /* not TRILIBRARY */ + + triangleloop.tri = triangletraverse(); + elementnumber++; + } + +#ifndef TRILIBRARY + finishfile(outfile, argc, argv); +#endif /* not TRILIBRARY */ +} + +/*****************************************************************************/ +/* */ +/* writepoly() Write the segments and holes to a .poly file. */ +/* */ +/*****************************************************************************/ + +#ifdef TRILIBRARY + +void writepoly(segmentlist, segmentmarkerlist) +int **segmentlist; +int **segmentmarkerlist; + +#else /* not TRILIBRARY */ + +void writepoly(polyfilename, holelist, holes, regionlist, regions, argc, argv) +char *polyfilename; +REAL *holelist; +int holes; +REAL *regionlist; +int regions; +int argc; +char **argv; + +#endif /* not TRILIBRARY */ + +{ +#ifdef TRILIBRARY + int *slist; + int *smlist; + int index; +#else /* not TRILIBRARY */ + FILE *outfile; + int i; +#endif /* not TRILIBRARY */ + struct edge shelleloop; + point endpoint1, endpoint2; + int shellenumber; + +#ifdef TRILIBRARY + if (!quiet) { + printf("Writing segments.\n"); + } + /* Allocate memory for output segments if necessary. */ + if (*segmentlist == (int *) NULL) { + *segmentlist = (int *) malloc(shelles.items * 2 * sizeof(int)); + if (*segmentlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + /* Allocate memory for output segment markers if necessary. */ + if (!nobound && (*segmentmarkerlist == (int *) NULL)) { + *segmentmarkerlist = (int *) malloc(shelles.items * sizeof(int)); + if (*segmentmarkerlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + slist = *segmentlist; + smlist = *segmentmarkerlist; + index = 0; +#else /* not TRILIBRARY */ + if (!quiet) { + printf("Writing %s.\n", polyfilename); + } + outfile = fopen(polyfilename, "w"); + if (outfile == (FILE *) NULL) { + printf(" Error: Cannot create file %s.\n", polyfilename); + exit(1); + } + /* The zero indicates that the points are in a separate .node file. */ + /* Followed by number of dimensions, number of point attributes, */ + /* and number of boundary markers (zero or one). */ + fprintf(outfile, "%d %d %d %d\n", 0, mesh_dim, nextras, 1 - nobound); + /* Number of segments, number of boundary markers (zero or one). */ + fprintf(outfile, "%ld %d\n", shelles.items, 1 - nobound); +#endif /* not TRILIBRARY */ + + traversalinit(&shelles); + shelleloop.sh = shelletraverse(); + shelleloop.shorient = 0; + shellenumber = firstnumber; + while (shelleloop.sh != (shelle *) NULL) { + sorg(shelleloop, endpoint1); + sdest(shelleloop, endpoint2); +#ifdef TRILIBRARY + /* Copy indices of the segment's two endpoints. */ + slist[index++] = pointmark(endpoint1); + slist[index++] = pointmark(endpoint2); + if (!nobound) { + /* Copy the boundary marker. */ + smlist[shellenumber - firstnumber] = mark(shelleloop); + } +#else /* not TRILIBRARY */ + /* Segment number, indices of its two endpoints, and possibly a marker. */ + if (nobound) { + fprintf(outfile, "%4d %4d %4d\n", shellenumber, + pointmark(endpoint1), pointmark(endpoint2)); + } else { + fprintf(outfile, "%4d %4d %4d %4d\n", shellenumber, + pointmark(endpoint1), pointmark(endpoint2), mark(shelleloop)); + } +#endif /* not TRILIBRARY */ + + shelleloop.sh = shelletraverse(); + shellenumber++; + } + +#ifndef TRILIBRARY +#ifndef CDT_ONLY + fprintf(outfile, "%d\n", holes); + if (holes > 0) { + for (i = 0; i < holes; i++) { + /* Hole number, x and y coordinates. */ + fprintf(outfile, "%4d %.17g %.17g\n", firstnumber + i, + holelist[2 * i], holelist[2 * i + 1]); + } + } + if (regions > 0) { + fprintf(outfile, "%d\n", regions); + for (i = 0; i < regions; i++) { + /* Region number, x and y coordinates, attribute, maximum area. */ + fprintf(outfile, "%4d %.17g %.17g %.17g %.17g\n", firstnumber + i, + regionlist[4 * i], regionlist[4 * i + 1], + regionlist[4 * i + 2], regionlist[4 * i + 3]); + } + } +#endif /* not CDT_ONLY */ + + finishfile(outfile, argc, argv); +#endif /* not TRILIBRARY */ +} + +/*****************************************************************************/ +/* */ +/* writeedges() Write the edges to a .edge file. */ +/* */ +/*****************************************************************************/ + +#ifdef TRILIBRARY + +void writeedges(edgelist, edgemarkerlist) +int **edgelist; +int **edgemarkerlist; + +#else /* not TRILIBRARY */ + +void writeedges(edgefilename, argc, argv) +char *edgefilename; +int argc; +char **argv; + +#endif /* not TRILIBRARY */ + +{ +#ifdef TRILIBRARY + int *elist; + int *emlist; + int index; +#else /* not TRILIBRARY */ + FILE *outfile; +#endif /* not TRILIBRARY */ + struct triedge triangleloop, trisym; + struct edge checkmark; + point p1, p2; + int edgenumber; + triangle ptr; /* Temporary variable used by sym(). */ + shelle sptr; /* Temporary variable used by tspivot(). */ + +#ifdef TRILIBRARY + if (!quiet) { + printf("Writing edges.\n"); + } + /* Allocate memory for edges if necessary. */ + if (*edgelist == (int *) NULL) { + *edgelist = (int *) malloc(edges * 2 * sizeof(int)); + if (*edgelist == (int *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + /* Allocate memory for edge markers if necessary. */ + if (!nobound && (*edgemarkerlist == (int *) NULL)) { + *edgemarkerlist = (int *) malloc(edges * sizeof(int)); + if (*edgemarkerlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + elist = *edgelist; + emlist = *edgemarkerlist; + index = 0; +#else /* not TRILIBRARY */ + if (!quiet) { + printf("Writing %s.\n", edgefilename); + } + outfile = fopen(edgefilename, "w"); + if (outfile == (FILE *) NULL) { + printf(" Error: Cannot create file %s.\n", edgefilename); + exit(1); + } + /* Number of edges, number of boundary markers (zero or one). */ + fprintf(outfile, "%ld %d\n", edges, 1 - nobound); +#endif /* not TRILIBRARY */ + + traversalinit(&triangles); + triangleloop.tri = triangletraverse(); + edgenumber = firstnumber; + /* To loop over the set of edges, loop over all triangles, and look at */ + /* the three edges of each triangle. If there isn't another triangle */ + /* adjacent to the edge, operate on the edge. If there is another */ + /* adjacent triangle, operate on the edge only if the current triangle */ + /* has a smaller pointer than its neighbor. This way, each edge is */ + /* considered only once. */ + while (triangleloop.tri != (triangle *) NULL) { + for (triangleloop.orient = 0; triangleloop.orient < 3; + triangleloop.orient++) { + sym(triangleloop, trisym); + if ((triangleloop.tri < trisym.tri) || (trisym.tri == dummytri)) { + org(triangleloop, p1); + dest(triangleloop, p2); +#ifdef TRILIBRARY + elist[index++] = pointmark(p1); + elist[index++] = pointmark(p2); +#endif /* TRILIBRARY */ + if (nobound) { +#ifndef TRILIBRARY + /* Edge number, indices of two endpoints. */ + fprintf(outfile, "%4d %d %d\n", edgenumber, + pointmark(p1), pointmark(p2)); +#endif /* not TRILIBRARY */ + } else { + /* Edge number, indices of two endpoints, and a boundary marker. */ + /* If there's no shell edge, the boundary marker is zero. */ + if (useshelles) { + tspivot(triangleloop, checkmark); + if (checkmark.sh == dummysh) { +#ifdef TRILIBRARY + emlist[edgenumber - firstnumber] = 0; +#else /* not TRILIBRARY */ + fprintf(outfile, "%4d %d %d %d\n", edgenumber, + pointmark(p1), pointmark(p2), 0); +#endif /* not TRILIBRARY */ + } else { +#ifdef TRILIBRARY + emlist[edgenumber - firstnumber] = mark(checkmark); +#else /* not TRILIBRARY */ + fprintf(outfile, "%4d %d %d %d\n", edgenumber, + pointmark(p1), pointmark(p2), mark(checkmark)); +#endif /* not TRILIBRARY */ + } + } else { +#ifdef TRILIBRARY + emlist[edgenumber - firstnumber] = trisym.tri == dummytri; +#else /* not TRILIBRARY */ + fprintf(outfile, "%4d %d %d %d\n", edgenumber, + pointmark(p1), pointmark(p2), trisym.tri == dummytri); +#endif /* not TRILIBRARY */ + } + } + edgenumber++; + } + } + triangleloop.tri = triangletraverse(); + } + +#ifndef TRILIBRARY + finishfile(outfile, argc, argv); +#endif /* not TRILIBRARY */ +} + +/*****************************************************************************/ +/* */ +/* writevoronoi() Write the Voronoi diagram to a .v.node and .v.edge */ +/* file. */ +/* */ +/* The Voronoi diagram is the geometric dual of the Delaunay triangulation. */ +/* Hence, the Voronoi vertices are listed by traversing the Delaunay */ +/* triangles, and the Voronoi edges are listed by traversing the Delaunay */ +/* edges. */ +/* */ +/* WARNING: In order to assign numbers to the Voronoi vertices, this */ +/* procedure messes up the shell edges or the extra nodes of every */ +/* element. Hence, you should call this procedure last. */ +/* */ +/*****************************************************************************/ + +#ifdef TRILIBRARY + +void writevoronoi(vpointlist, vpointattriblist, vpointmarkerlist, vedgelist, + vedgemarkerlist, vnormlist) +REAL **vpointlist; +REAL **vpointattriblist; +int **vpointmarkerlist; +int **vedgelist; +int **vedgemarkerlist; +REAL **vnormlist; + +#else /* not TRILIBRARY */ + +void writevoronoi(vnodefilename, vedgefilename, argc, argv) +char *vnodefilename; +char *vedgefilename; +int argc; +char **argv; + +#endif /* not TRILIBRARY */ + +{ +#ifdef TRILIBRARY + REAL *plist; + REAL *palist; + int *elist; + REAL *normlist; + int coordindex; + int attribindex; +#else /* not TRILIBRARY */ + FILE *outfile; +#endif /* not TRILIBRARY */ + struct triedge triangleloop, trisym; + point torg, tdest, tapex; + REAL circumcenter[2]; + REAL xi, eta; + int vnodenumber, vedgenumber; + int p1, p2; + int i; + triangle ptr; /* Temporary variable used by sym(). */ + +#ifdef TRILIBRARY + if (!quiet) { + printf("Writing Voronoi vertices.\n"); + } + /* Allocate memory for Voronoi vertices if necessary. */ + if (*vpointlist == (REAL *) NULL) { + *vpointlist = (REAL *) malloc(triangles.items * 2 * sizeof(REAL)); + if (*vpointlist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + /* Allocate memory for Voronoi vertex attributes if necessary. */ + if (*vpointattriblist == (REAL *) NULL) { + *vpointattriblist = (REAL *) malloc(triangles.items * nextras * + sizeof(REAL)); + if (*vpointattriblist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + *vpointmarkerlist = (int *) NULL; + plist = *vpointlist; + palist = *vpointattriblist; + coordindex = 0; + attribindex = 0; +#else /* not TRILIBRARY */ + if (!quiet) { + printf("Writing %s.\n", vnodefilename); + } + outfile = fopen(vnodefilename, "w"); + if (outfile == (FILE *) NULL) { + printf(" Error: Cannot create file %s.\n", vnodefilename); + exit(1); + } + /* Number of triangles, two dimensions, number of point attributes, */ + /* zero markers. */ + fprintf(outfile, "%ld %d %d %d\n", triangles.items, 2, nextras, 0); +#endif /* not TRILIBRARY */ + + traversalinit(&triangles); + triangleloop.tri = triangletraverse(); + triangleloop.orient = 0; + vnodenumber = firstnumber; + while (triangleloop.tri != (triangle *) NULL) { + org(triangleloop, torg); + dest(triangleloop, tdest); + apex(triangleloop, tapex); + findcircumcenter(torg, tdest, tapex, circumcenter, &xi, &eta); +#ifdef TRILIBRARY + /* X and y coordinates. */ + plist[coordindex++] = circumcenter[0]; + plist[coordindex++] = circumcenter[1]; + for (i = 2; i < 2 + nextras; i++) { + /* Interpolate the point attributes at the circumcenter. */ + palist[attribindex++] = torg[i] + xi * (tdest[i] - torg[i]) + + eta * (tapex[i] - torg[i]); + } +#else /* not TRILIBRARY */ + /* Voronoi vertex number, x and y coordinates. */ + fprintf(outfile, "%4d %.17g %.17g", vnodenumber, circumcenter[0], + circumcenter[1]); + for (i = 2; i < 2 + nextras; i++) { + /* Interpolate the point attributes at the circumcenter. */ + fprintf(outfile, " %.17g", torg[i] + xi * (tdest[i] - torg[i]) + + eta * (tapex[i] - torg[i])); + } + fprintf(outfile, "\n"); +#endif /* not TRILIBRARY */ + + * (int *) (triangleloop.tri + 6) = vnodenumber; + triangleloop.tri = triangletraverse(); + vnodenumber++; + } + +#ifndef TRILIBRARY + finishfile(outfile, argc, argv); +#endif /* not TRILIBRARY */ + +#ifdef TRILIBRARY + if (!quiet) { + printf("Writing Voronoi edges.\n"); + } + /* Allocate memory for output Voronoi edges if necessary. */ + if (*vedgelist == (int *) NULL) { + *vedgelist = (int *) malloc(edges * 2 * sizeof(int)); + if (*vedgelist == (int *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + *vedgemarkerlist = (int *) NULL; + /* Allocate memory for output Voronoi norms if necessary. */ + if (*vnormlist == (REAL *) NULL) { + *vnormlist = (REAL *) malloc(edges * 2 * sizeof(REAL)); + if (*vnormlist == (REAL *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + elist = *vedgelist; + normlist = *vnormlist; + coordindex = 0; +#else /* not TRILIBRARY */ + if (!quiet) { + printf("Writing %s.\n", vedgefilename); + } + outfile = fopen(vedgefilename, "w"); + if (outfile == (FILE *) NULL) { + printf(" Error: Cannot create file %s.\n", vedgefilename); + exit(1); + } + /* Number of edges, zero boundary markers. */ + fprintf(outfile, "%ld %d\n", edges, 0); +#endif /* not TRILIBRARY */ + + traversalinit(&triangles); + triangleloop.tri = triangletraverse(); + vedgenumber = firstnumber; + /* To loop over the set of edges, loop over all triangles, and look at */ + /* the three edges of each triangle. If there isn't another triangle */ + /* adjacent to the edge, operate on the edge. If there is another */ + /* adjacent triangle, operate on the edge only if the current triangle */ + /* has a smaller pointer than its neighbor. This way, each edge is */ + /* considered only once. */ + while (triangleloop.tri != (triangle *) NULL) { + for (triangleloop.orient = 0; triangleloop.orient < 3; + triangleloop.orient++) { + sym(triangleloop, trisym); + if ((triangleloop.tri < trisym.tri) || (trisym.tri == dummytri)) { + /* Find the number of this triangle (and Voronoi vertex). */ + p1 = * (int *) (triangleloop.tri + 6); + if (trisym.tri == dummytri) { + org(triangleloop, torg); + dest(triangleloop, tdest); +#ifdef TRILIBRARY + /* Copy an infinite ray. Index of one endpoint, and -1. */ + elist[coordindex] = p1; + normlist[coordindex++] = tdest[1] - torg[1]; + elist[coordindex] = -1; + normlist[coordindex++] = torg[0] - tdest[0]; +#else /* not TRILIBRARY */ + /* Write an infinite ray. Edge number, index of one endpoint, -1, */ + /* and x and y coordinates of a vector representing the */ + /* direction of the ray. */ + fprintf(outfile, "%4d %d %d %.17g %.17g\n", vedgenumber, + p1, -1, tdest[1] - torg[1], torg[0] - tdest[0]); +#endif /* not TRILIBRARY */ + } else { + /* Find the number of the adjacent triangle (and Voronoi vertex). */ + p2 = * (int *) (trisym.tri + 6); + /* Finite edge. Write indices of two endpoints. */ +#ifdef TRILIBRARY + elist[coordindex] = p1; + normlist[coordindex++] = 0.0; + elist[coordindex] = p2; + normlist[coordindex++] = 0.0; +#else /* not TRILIBRARY */ + fprintf(outfile, "%4d %d %d\n", vedgenumber, p1, p2); +#endif /* not TRILIBRARY */ + } + vedgenumber++; + } + } + triangleloop.tri = triangletraverse(); + } + +#ifndef TRILIBRARY + finishfile(outfile, argc, argv); +#endif /* not TRILIBRARY */ +} + +#ifdef TRILIBRARY + +void writeneighbors(neighborlist) +int **neighborlist; + +#else /* not TRILIBRARY */ + +void writeneighbors(neighborfilename, argc, argv) +char *neighborfilename; +int argc; +char **argv; + +#endif /* not TRILIBRARY */ + +{ +#ifdef TRILIBRARY + int *nlist; + int index; +#else /* not TRILIBRARY */ + FILE *outfile; +#endif /* not TRILIBRARY */ + struct triedge triangleloop, trisym; + int elementnumber; + int neighbor1, neighbor2, neighbor3; + triangle ptr; /* Temporary variable used by sym(). */ + +#ifdef TRILIBRARY + if (!quiet) { + printf("Writing neighbors.\n"); + } + /* Allocate memory for neighbors if necessary. */ + if (*neighborlist == (int *) NULL) { + *neighborlist = (int *) malloc(triangles.items * 3 * sizeof(int)); + if (*neighborlist == (int *) NULL) { + printf("Error: Out of memory.\n"); + exit(1); + } + } + nlist = *neighborlist; + index = 0; +#else /* not TRILIBRARY */ + if (!quiet) { + printf("Writing %s.\n", neighborfilename); + } + outfile = fopen(neighborfilename, "w"); + if (outfile == (FILE *) NULL) { + printf(" Error: Cannot create file %s.\n", neighborfilename); + exit(1); + } + /* Number of triangles, three edges per triangle. */ + fprintf(outfile, "%ld %d\n", triangles.items, 3); +#endif /* not TRILIBRARY */ + + traversalinit(&triangles); + triangleloop.tri = triangletraverse(); + triangleloop.orient = 0; + elementnumber = firstnumber; + while (triangleloop.tri != (triangle *) NULL) { + * (int *) (triangleloop.tri + 6) = elementnumber; + triangleloop.tri = triangletraverse(); + elementnumber++; + } + * (int *) (dummytri + 6) = -1; + + traversalinit(&triangles); + triangleloop.tri = triangletraverse(); + elementnumber = firstnumber; + while (triangleloop.tri != (triangle *) NULL) { + triangleloop.orient = 1; + sym(triangleloop, trisym); + neighbor1 = * (int *) (trisym.tri + 6); + triangleloop.orient = 2; + sym(triangleloop, trisym); + neighbor2 = * (int *) (trisym.tri + 6); + triangleloop.orient = 0; + sym(triangleloop, trisym); + neighbor3 = * (int *) (trisym.tri + 6); +#ifdef TRILIBRARY + nlist[index++] = neighbor1; + nlist[index++] = neighbor2; + nlist[index++] = neighbor3; +#else /* not TRILIBRARY */ + /* Triangle number, neighboring triangle numbers. */ + fprintf(outfile, "%4d %d %d %d\n", elementnumber, + neighbor1, neighbor2, neighbor3); +#endif /* not TRILIBRARY */ + + triangleloop.tri = triangletraverse(); + elementnumber++; + } + +#ifndef TRILIBRARY + finishfile(outfile, argc, argv); +#endif /* TRILIBRARY */ +} + +/*****************************************************************************/ +/* */ +/* writeoff() Write the triangulation to an .off file. */ +/* */ +/* OFF stands for the Object File Format, a format used by the Geometry */ +/* Center's Geomview package. */ +/* */ +/*****************************************************************************/ + +#ifndef TRILIBRARY + +void writeoff(offfilename, argc, argv) +char *offfilename; +int argc; +char **argv; +{ + FILE *outfile; + struct triedge triangleloop; + point pointloop; + point p1, p2, p3; + + if (!quiet) { + printf("Writing %s.\n", offfilename); + } + outfile = fopen(offfilename, "w"); + if (outfile == (FILE *) NULL) { + printf(" Error: Cannot create file %s.\n", offfilename); + exit(1); + } + /* Number of points, triangles, and edges. */ + fprintf(outfile, "OFF\n%ld %ld %ld\n", points.items, triangles.items, + edges); + + /* Write the points. */ + traversalinit(&points); + pointloop = pointtraverse(); + while (pointloop != (point) NULL) { + /* The "0.0" is here because the OFF format uses 3D coordinates. */ + fprintf(outfile, " %.17g %.17g %.17g\n", pointloop[0], + pointloop[1], 0.0); + pointloop = pointtraverse(); + } + + /* Write the triangles. */ + traversalinit(&triangles); + triangleloop.tri = triangletraverse(); + triangleloop.orient = 0; + while (triangleloop.tri != (triangle *) NULL) { + org(triangleloop, p1); + dest(triangleloop, p2); + apex(triangleloop, p3); + /* The "3" means a three-vertex polygon. */ + fprintf(outfile, " 3 %4d %4d %4d\n", pointmark(p1) - 1, + pointmark(p2) - 1, pointmark(p3) - 1); + triangleloop.tri = triangletraverse(); + } + finishfile(outfile, argc, argv); +} + +#endif /* not TRILIBRARY */ + +/** **/ +/** **/ +/********* File I/O routines end here *********/ + +/*****************************************************************************/ +/* */ +/* quality_statistics() Print statistics about the quality of the mesh. */ +/* */ +/*****************************************************************************/ + +void quality_statistics() +{ + struct triedge triangleloop; + point p[3]; + REAL cossquaretable[8]; + REAL ratiotable[16]; + REAL dx[3], dy[3]; + REAL edgelength[3]; + REAL dotproduct; + REAL cossquare; + REAL triarea; + REAL shortest, longest; + REAL trilongest2; + REAL smallestarea, biggestarea; + REAL triminaltitude2; + REAL minaltitude; + REAL triaspect2; + REAL worstaspect; + REAL smallestangle, biggestangle; + REAL radconst, degconst; + int angletable[18]; + int aspecttable[16]; + int aspectindex; + int tendegree; + int acutebiggest; + int i, ii, j, k; + + printf("Mesh quality statistics:\n\n"); + radconst = (REAL)(PI / 18.0); + degconst = (REAL)(180.0 / PI); + for (i = 0; i < 8; i++) { + cossquaretable[i] = (REAL)(cos(radconst * (REAL) (i + 1))); + cossquaretable[i] = cossquaretable[i] * cossquaretable[i]; + } + for (i = 0; i < 18; i++) { + angletable[i] = 0; + } + + ratiotable[0] = 1.5; ratiotable[1] = 2.0; + ratiotable[2] = 2.5; ratiotable[3] = 3.0; + ratiotable[4] = 4.0; ratiotable[5] = 6.0; + ratiotable[6] = 10.0; ratiotable[7] = 15.0; + ratiotable[8] = 25.0; ratiotable[9] = 50.0; + ratiotable[10] = 100.0; ratiotable[11] = 300.0; + ratiotable[12] = 1000.0; ratiotable[13] = 10000.0; + ratiotable[14] = 100000.0; ratiotable[15] = 0.0; + for (i = 0; i < 16; i++) { + aspecttable[i] = 0; + } + + worstaspect = 0.0; + minaltitude = xmax - xmin + ymax - ymin; + minaltitude = minaltitude * minaltitude; + shortest = minaltitude; + longest = 0.0; + smallestarea = minaltitude; + biggestarea = 0.0; + worstaspect = 0.0; + smallestangle = 0.0; + biggestangle = 2.0; + acutebiggest = 1; + + traversalinit(&triangles); + triangleloop.tri = triangletraverse(); + triangleloop.orient = 0; + while (triangleloop.tri != (triangle *) NULL) { + org(triangleloop, p[0]); + dest(triangleloop, p[1]); + apex(triangleloop, p[2]); + trilongest2 = 0.0; + + for (i = 0; i < 3; i++) { + j = plus1mod3[i]; + k = minus1mod3[i]; + dx[i] = p[j][0] - p[k][0]; + dy[i] = p[j][1] - p[k][1]; + edgelength[i] = dx[i] * dx[i] + dy[i] * dy[i]; + if (edgelength[i] > trilongest2) { + trilongest2 = edgelength[i]; + } + if (edgelength[i] > longest) { + longest = edgelength[i]; + } + if (edgelength[i] < shortest) { + shortest = edgelength[i]; + } + } + + triarea = counterclockwise(p[0], p[1], p[2]); + if (triarea < smallestarea) { + smallestarea = triarea; + } + if (triarea > biggestarea) { + biggestarea = triarea; + } + triminaltitude2 = triarea * triarea / trilongest2; + if (triminaltitude2 < minaltitude) { + minaltitude = triminaltitude2; + } + triaspect2 = trilongest2 / triminaltitude2; + if (triaspect2 > worstaspect) { + worstaspect = triaspect2; + } + aspectindex = 0; + while ((triaspect2 > ratiotable[aspectindex] * ratiotable[aspectindex]) + && (aspectindex < 15)) { + aspectindex++; + } + aspecttable[aspectindex]++; + + for (i = 0; i < 3; i++) { + j = plus1mod3[i]; + k = minus1mod3[i]; + dotproduct = dx[j] * dx[k] + dy[j] * dy[k]; + cossquare = dotproduct * dotproduct / (edgelength[j] * edgelength[k]); + tendegree = 8; + for (ii = 7; ii >= 0; ii--) { + if (cossquare > cossquaretable[ii]) { + tendegree = ii; + } + } + if (dotproduct <= 0.0) { + angletable[tendegree]++; + if (cossquare > smallestangle) { + smallestangle = cossquare; + } + if (acutebiggest && (cossquare < biggestangle)) { + biggestangle = cossquare; + } + } else { + angletable[17 - tendegree]++; + if (acutebiggest || (cossquare > biggestangle)) { + biggestangle = cossquare; + acutebiggest = 0; + } + } + } + triangleloop.tri = triangletraverse(); + } + + shortest = (REAL)sqrt(shortest); + longest = (REAL)sqrt(longest); + minaltitude = (REAL)sqrt(minaltitude); + worstaspect = (REAL)sqrt(worstaspect); + smallestarea *= 2.0; + biggestarea *= 2.0; + if (smallestangle >= 1.0) { + smallestangle = 0.0; + } else { + smallestangle = (REAL)(degconst * acos(sqrt(smallestangle))); + } + if (biggestangle >= 1.0) { + biggestangle = 180.0; + } else { + if (acutebiggest) { + biggestangle = (REAL)(degconst * acos(sqrt(biggestangle))); + } else { + biggestangle = (REAL)(180.0 - degconst * acos(sqrt(biggestangle))); + } + } + + printf(" Smallest area: %16.5g | Largest area: %16.5g\n", + smallestarea, biggestarea); + printf(" Shortest edge: %16.5g | Longest edge: %16.5g\n", + shortest, longest); + printf(" Shortest altitude: %12.5g | Largest aspect ratio: %8.5g\n\n", + minaltitude, worstaspect); + printf(" Aspect ratio histogram:\n"); + printf(" 1.1547 - %-6.6g : %8d | %6.6g - %-6.6g : %8d\n", + ratiotable[0], aspecttable[0], ratiotable[7], ratiotable[8], + aspecttable[8]); + for (i = 1; i < 7; i++) { + printf(" %6.6g - %-6.6g : %8d | %6.6g - %-6.6g : %8d\n", + ratiotable[i - 1], ratiotable[i], aspecttable[i], + ratiotable[i + 7], ratiotable[i + 8], aspecttable[i + 8]); + } + printf(" %6.6g - %-6.6g : %8d | %6.6g - : %8d\n", + ratiotable[6], ratiotable[7], aspecttable[7], ratiotable[14], + aspecttable[15]); + printf( +" (Triangle aspect ratio is longest edge divided by shortest altitude)\n\n"); + printf(" Smallest angle: %15.5g | Largest angle: %15.5g\n\n", + smallestangle, biggestangle); + printf(" Angle histogram:\n"); + for (i = 0; i < 9; i++) { + printf(" %3d - %3d degrees: %8d | %3d - %3d degrees: %8d\n", + i * 10, i * 10 + 10, angletable[i], + i * 10 + 90, i * 10 + 100, angletable[i + 9]); + } + printf("\n"); +} + +/*****************************************************************************/ +/* */ +/* statistics() Print all sorts of cool facts. */ +/* */ +/*****************************************************************************/ + +void statistics() +{ + printf("\nStatistics:\n\n"); + printf(" Input points: %d\n", inpoints); + if (refine) { + printf(" Input triangles: %d\n", inelements); + } + if (poly) { + printf(" Input segments: %d\n", insegments); + if (!refine) { + printf(" Input holes: %d\n", holes); + } + } + + printf("\n Mesh points: %ld\n", points.items); + printf(" Mesh triangles: %ld\n", triangles.items); + printf(" Mesh edges: %ld\n", edges); + if (poly || refine) { + printf(" Mesh boundary edges: %ld\n", hullsize); + printf(" Mesh segments: %ld\n\n", shelles.items); + } else { + printf(" Mesh convex hull edges: %ld\n\n", hullsize); + } + if (verbose) { + quality_statistics(); + printf("Memory allocation statistics:\n\n"); + printf(" Maximum number of points: %ld\n", points.maxitems); + printf(" Maximum number of triangles: %ld\n", triangles.maxitems); + if (shelles.maxitems > 0) { + printf(" Maximum number of segments: %ld\n", shelles.maxitems); + } + if (viri.maxitems > 0) { + printf(" Maximum number of viri: %ld\n", viri.maxitems); + } + if (badsegments.maxitems > 0) { + printf(" Maximum number of encroached segments: %ld\n", + badsegments.maxitems); + } + if (badtriangles.maxitems > 0) { + printf(" Maximum number of bad triangles: %ld\n", + badtriangles.maxitems); + } + if (splaynodes.maxitems > 0) { + printf(" Maximum number of splay tree nodes: %ld\n", + splaynodes.maxitems); + } + printf(" Approximate heap memory use (bytes): %ld\n\n", + points.maxitems * points.itembytes + + triangles.maxitems * triangles.itembytes + + shelles.maxitems * shelles.itembytes + + viri.maxitems * viri.itembytes + + badsegments.maxitems * badsegments.itembytes + + badtriangles.maxitems * badtriangles.itembytes + + splaynodes.maxitems * splaynodes.itembytes); + + printf("Algorithmic statistics:\n\n"); + printf(" Number of incircle tests: %ld\n", incirclecount); + printf(" Number of orientation tests: %ld\n", counterclockcount); + if (hyperbolacount > 0) { + printf(" Number of right-of-hyperbola tests: %ld\n", + hyperbolacount); + } + if (circumcentercount > 0) { + printf(" Number of circumcenter computations: %ld\n", + circumcentercount); + } + if (circletopcount > 0) { + printf(" Number of circle top computations: %ld\n", + circletopcount); + } + printf("\n"); + } +} + +/*****************************************************************************/ +/* */ +/* main() or triangulate() Gosh, do everything. */ +/* */ +/* The sequence is roughly as follows. Many of these steps can be skipped, */ +/* depending on the command line switches. */ +/* */ +/* - Initialize constants and parse the command line. */ +/* - Read the points from a file and either */ +/* - triangulate them (no -r), or */ +/* - read an old mesh from files and reconstruct it (-r). */ +/* - Insert the PSLG segments (-p), and possibly segments on the convex */ +/* hull (-c). */ +/* - Read the holes (-p), regional attributes (-pA), and regional area */ +/* constraints (-pa). Carve the holes and concavities, and spread the */ +/* regional attributes and area constraints. */ +/* - Enforce the constraints on minimum angle (-q) and maximum area (-a). */ +/* Also enforce the conforming Delaunay property (-q and -a). */ +/* - Compute the number of edges in the resulting mesh. */ +/* - Promote the mesh's linear triangles to higher order elements (-o). */ +/* - Write the output files and print the statistics. */ +/* - Check the consistency and Delaunay property of the mesh (-C). */ +/* */ +/*****************************************************************************/ + +#ifdef TRILIBRARY + +void triangulate(triswitches, in, out, vorout) +char *triswitches; +struct triangulateio *in; +struct triangulateio *out; +struct triangulateio *vorout; + +#else /* not TRILIBRARY */ + +int main(argc, argv) +int argc; +char **argv; + +#endif /* not TRILIBRARY */ + +{ + REAL *holearray; /* Array of holes. */ + REAL *regionarray; /* Array of regional attributes and area constraints. */ +#ifndef TRILIBRARY + FILE *polyfile; +#endif /* not TRILIBRARY */ +#ifndef NO_TIMER + /* Variables for timing the performance of Triangle. The types are */ + /* defined in sys/time.h. */ + struct timeval tv0, tv1, tv2, tv3, tv4, tv5, tv6; + struct timezone tz; +#endif /* NO_TIMER */ + +#ifndef NO_TIMER + gettimeofday(&tv0, &tz); +#endif /* NO_TIMER */ + + triangleinit(); +#ifdef TRILIBRARY + parsecommandline(1, &triswitches); +#else /* not TRILIBRARY */ + parsecommandline(argc, argv); +#endif /* not TRILIBRARY */ + +#ifdef TRILIBRARY + transfernodes(in->pointlist, in->pointattributelist, in->pointmarkerlist, + in->numberofpoints, in->numberofpointattributes); +#else /* not TRILIBRARY */ + readnodes(innodefilename, inpolyfilename, &polyfile); +#endif /* not TRILIBRARY */ + +#ifndef NO_TIMER + if (!quiet) { + gettimeofday(&tv1, &tz); + } +#endif /* NO_TIMER */ + +#ifdef CDT_ONLY + hullsize = delaunay(); /* Triangulate the points. */ +#else /* not CDT_ONLY */ + if (refine) { + /* Read and reconstruct a mesh. */ +#ifdef TRILIBRARY + hullsize = reconstruct(in->trianglelist, in->triangleattributelist, + in->trianglearealist, in->numberoftriangles, + in->numberofcorners, in->numberoftriangleattributes, + in->segmentlist, in->segmentmarkerlist, + in->numberofsegments); +#else /* not TRILIBRARY */ + hullsize = reconstruct(inelefilename, areafilename, inpolyfilename, + polyfile); +#endif /* not TRILIBRARY */ + } else { + hullsize = delaunay(); /* Triangulate the points. */ + } +#endif /* not CDT_ONLY */ + +#ifndef NO_TIMER + if (!quiet) { + gettimeofday(&tv2, &tz); + if (refine) { + printf("Mesh reconstruction"); + } else { + printf("Delaunay"); + } + printf(" milliseconds: %ld\n", 1000l * (tv2.tv_sec - tv1.tv_sec) + + (tv2.tv_usec - tv1.tv_usec) / 1000l); + } +#endif /* NO_TIMER */ + + /* Ensure that no point can be mistaken for a triangular bounding */ + /* box point in insertsite(). */ + infpoint1 = (point) NULL; + infpoint2 = (point) NULL; + infpoint3 = (point) NULL; + + if (useshelles) { + checksegments = 1; /* Segments will be introduced next. */ + if (!refine) { + /* Insert PSLG segments and/or convex hull segments. */ +#ifdef TRILIBRARY + insegments = formskeleton(in->segmentlist, in->segmentmarkerlist, + in->numberofsegments); +#else /* not TRILIBRARY */ + insegments = formskeleton(polyfile, inpolyfilename); +#endif /* not TRILIBRARY */ + } + } + +#ifndef NO_TIMER + if (!quiet) { + gettimeofday(&tv3, &tz); + if (useshelles && !refine) { + printf("Segment milliseconds: %ld\n", + 1000l * (tv3.tv_sec - tv2.tv_sec) + + (tv3.tv_usec - tv2.tv_usec) / 1000l); + } + } +#endif /* NO_TIMER */ + + if (poly) { +#ifdef TRILIBRARY + holearray = in->holelist; + holes = in->numberofholes; + regionarray = in->regionlist; + regions = in->numberofregions; +#else /* not TRILIBRARY */ + readholes(polyfile, inpolyfilename, &holearray, &holes, + ®ionarray, ®ions); +#endif /* not TRILIBRARY */ + if (!refine) { + /* Carve out holes and concavities. */ + carveholes(holearray, holes, regionarray, regions); + } + } else { + /* Without a PSLG, there can be no holes or regional attributes */ + /* or area constraints. The following are set to zero to avoid */ + /* an accidental free() later. */ + holes = 0; + regions = 0; + } + +#ifndef NO_TIMER + if (!quiet) { + gettimeofday(&tv4, &tz); + if (poly && !refine) { + printf("Hole milliseconds: %ld\n", 1000l * (tv4.tv_sec - tv3.tv_sec) + + (tv4.tv_usec - tv3.tv_usec) / 1000l); + } + } +#endif /* NO_TIMER */ + +#ifndef CDT_ONLY + if (quality) { + enforcequality(); /* Enforce angle and area constraints. */ + } +#endif /* not CDT_ONLY */ + +#ifndef NO_TIMER + if (!quiet) { + gettimeofday(&tv5, &tz); +#ifndef CDT_ONLY + if (quality) { + printf("Quality milliseconds: %ld\n", + 1000l * (tv5.tv_sec - tv4.tv_sec) + + (tv5.tv_usec - tv4.tv_usec) / 1000l); + } +#endif /* not CDT_ONLY */ + } +#endif /* NO_TIMER */ + + /* Compute the number of edges. */ + edges = (3l * triangles.items + hullsize) / 2l; + + if (order > 1) { + highorder(); /* Promote elements to higher polynomial order. */ + } + if (!quiet) { + printf("\n"); + } + +#ifdef TRILIBRARY + out->numberofpoints = points.items; + out->numberofpointattributes = nextras; + out->numberoftriangles = triangles.items; + out->numberofcorners = (order + 1) * (order + 2) / 2; + out->numberoftriangleattributes = eextras; + out->numberofedges = edges; + if (useshelles) { + out->numberofsegments = shelles.items; + } else { + out->numberofsegments = hullsize; + } + if (vorout != (struct triangulateio *) NULL) { + vorout->numberofpoints = triangles.items; + vorout->numberofpointattributes = nextras; + vorout->numberofedges = edges; + } +#endif /* TRILIBRARY */ + /* If not using iteration numbers, don't write a .node file if one was */ + /* read, because the original one would be overwritten! */ + if (nonodewritten || (noiterationnum && readnodefile)) { + if (!quiet) { +#ifdef TRILIBRARY + printf("NOT writing points.\n"); +#else /* not TRILIBRARY */ + printf("NOT writing a .node file.\n"); +#endif /* not TRILIBRARY */ + } + numbernodes(); /* We must remember to number the points. */ + } else { +#ifdef TRILIBRARY + writenodes(&out->pointlist, &out->pointattributelist, + &out->pointmarkerlist); +#else /* not TRILIBRARY */ + writenodes(outnodefilename, argc, argv); /* Numbers the points too. */ +#endif /* TRILIBRARY */ + } + if (noelewritten) { + if (!quiet) { +#ifdef TRILIBRARY + printf("NOT writing triangles.\n"); +#else /* not TRILIBRARY */ + printf("NOT writing an .ele file.\n"); +#endif /* not TRILIBRARY */ + } + } else { +#ifdef TRILIBRARY + writeelements(&out->trianglelist, &out->triangleattributelist); +#else /* not TRILIBRARY */ + writeelements(outelefilename, argc, argv); +#endif /* not TRILIBRARY */ + } + /* The -c switch (convex switch) causes a PSLG to be written */ + /* even if none was read. */ + if (poly || convex) { + /* If not using iteration numbers, don't overwrite the .poly file. */ + if (nopolywritten || noiterationnum) { + if (!quiet) { +#ifdef TRILIBRARY + printf("NOT writing segments.\n"); +#else /* not TRILIBRARY */ + printf("NOT writing a .poly file.\n"); +#endif /* not TRILIBRARY */ + } + } else { +#ifdef TRILIBRARY + writepoly(&out->segmentlist, &out->segmentmarkerlist); + out->numberofholes = holes; + out->numberofregions = regions; + if (poly) { + out->holelist = in->holelist; + out->regionlist = in->regionlist; + } else { + out->holelist = (REAL *) NULL; + out->regionlist = (REAL *) NULL; + } +#else /* not TRILIBRARY */ + writepoly(outpolyfilename, holearray, holes, regionarray, regions, + argc, argv); +#endif /* not TRILIBRARY */ + } + } +#ifndef TRILIBRARY +#ifndef CDT_ONLY + if (regions > 0) { + free(regionarray); + } +#endif /* not CDT_ONLY */ + if (holes > 0) { + free(holearray); + } + if (geomview) { + writeoff(offfilename, argc, argv); + } +#endif /* not TRILIBRARY */ + if (edgesout) { +#ifdef TRILIBRARY + writeedges(&out->edgelist, &out->edgemarkerlist); +#else /* not TRILIBRARY */ + writeedges(edgefilename, argc, argv); +#endif /* not TRILIBRARY */ + } + if (voronoi) { +#ifdef TRILIBRARY + writevoronoi(&vorout->pointlist, &vorout->pointattributelist, + &vorout->pointmarkerlist, &vorout->edgelist, + &vorout->edgemarkerlist, &vorout->normlist); +#else /* not TRILIBRARY */ + writevoronoi(vnodefilename, vedgefilename, argc, argv); +#endif /* not TRILIBRARY */ + } + if (neighbors) { +#ifdef TRILIBRARY + writeneighbors(&out->neighborlist); +#else /* not TRILIBRARY */ + writeneighbors(neighborfilename, argc, argv); +#endif /* not TRILIBRARY */ + } + + if (!quiet) { +#ifndef NO_TIMER + gettimeofday(&tv6, &tz); + printf("\nOutput milliseconds: %ld\n", + 1000l * (tv6.tv_sec - tv5.tv_sec) + + (tv6.tv_usec - tv5.tv_usec) / 1000l); + printf("Total running milliseconds: %ld\n", + 1000l * (tv6.tv_sec - tv0.tv_sec) + + (tv6.tv_usec - tv0.tv_usec) / 1000l); +#endif /* NO_TIMER */ + + statistics(); + } + +#ifndef REDUCED + if (docheck) { + checkmesh(); + checkdelaunay(); + } +#endif /* not REDUCED */ + + triangledeinit(); +#ifndef TRILIBRARY + return 0; +#endif /* not TRILIBRARY */ +} diff --git a/contrib/gtkgensurf/triangle.h b/contrib/gtkgensurf/triangle.h new file mode 100644 index 00000000..e7d87d01 --- /dev/null +++ b/contrib/gtkgensurf/triangle.h @@ -0,0 +1,288 @@ +/*****************************************************************************/ +/* */ +/* (triangle.h) */ +/* */ +/* Include file for programs that call Triangle. */ +/* */ +/* Accompanies Triangle Version 1.3 */ +/* July 19, 1996 */ +/* */ +/* Copyright 1996 */ +/* Jonathan Richard Shewchuk */ +/* School of Computer Science */ +/* Carnegie Mellon University */ +/* 5000 Forbes Avenue */ +/* Pittsburgh, Pennsylvania 15213-3891 */ +/* jrs@cs.cmu.edu */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* How to call Triangle from another program */ +/* */ +/* */ +/* If you haven't read Triangle's instructions (run "triangle -h" to read */ +/* them), you won't understand what follows. */ +/* */ +/* Triangle must be compiled into an object file (triangle.o) with the */ +/* TRILIBRARY symbol defined (preferably by using the -DTRILIBRARY compiler */ +/* switch). The makefile included with Triangle will do this for you if */ +/* you run "make trilibrary". The resulting object file can be called via */ +/* the procedure triangulate(). */ +/* */ +/* If the size of the object file is important to you, you may wish to */ +/* generate a reduced version of triangle.o. The REDUCED symbol gets rid */ +/* of all features that are primarily of research interest. Specifically, */ +/* the -DREDUCED switch eliminates Triangle's -i, -F, -s, and -C switches. */ +/* The CDT_ONLY symbol gets rid of all meshing algorithms above and beyond */ +/* constrained Delaunay triangulation. Specifically, the -DCDT_ONLY switch */ +/* eliminates Triangle's -r, -q, -a, -S, and -s switches. */ +/* */ +/* IMPORTANT: These definitions (TRILIBRARY, REDUCED, CDT_ONLY) must be */ +/* made in the makefile or in triangle.c itself. Putting these definitions */ +/* in this file will not create the desired effect. */ +/* */ +/* */ +/* The calling convention for triangulate() follows. */ +/* */ +/* void triangulate(triswitches, in, out, vorout) */ +/* char *triswitches; */ +/* struct triangulateio *in; */ +/* struct triangulateio *out; */ +/* struct triangulateio *vorout; */ +/* */ +/* `triswitches' is a string containing the command line switches you wish */ +/* to invoke. No initial dash is required. Some suggestions: */ +/* */ +/* - You'll probably find it convenient to use the `z' switch so that */ +/* points (and other items) are numbered from zero. This simplifies */ +/* indexing, because the first item of any type always starts at index */ +/* [0] of the corresponding array, whether that item's number is zero or */ +/* one. */ +/* - You'll probably want to use the `Q' (quiet) switch in your final code, */ +/* but you can take advantage of Triangle's printed output (including the */ +/* `V' switch) while debugging. */ +/* - If you are not using the `q' or `a' switches, then the output points */ +/* will be identical to the input points, except possibly for the */ +/* boundary markers. If you don't need the boundary markers, you should */ +/* use the `N' (no nodes output) switch to save memory. (If you do need */ +/* boundary markers, but need to save memory, a good nasty trick is to */ +/* set out->pointlist equal to in->pointlist before calling triangulate(),*/ +/* so that Triangle overwrites the input points with identical copies.) */ +/* - The `I' (no iteration numbers) and `g' (.off file output) switches */ +/* have no effect when Triangle is compiled with TRILIBRARY defined. */ +/* */ +/* `in', `out', and `vorout' are descriptions of the input, the output, */ +/* and the Voronoi output. If the `v' (Voronoi output) switch is not used, */ +/* `vorout' may be NULL. `in' and `out' may never be NULL. */ +/* */ +/* Certain fields of the input and output structures must be initialized, */ +/* as described below. */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* The `triangulateio' structure. */ +/* */ +/* Used to pass data into and out of the triangulate() procedure. */ +/* */ +/* */ +/* Arrays are used to store points, triangles, markers, and so forth. In */ +/* all cases, the first item in any array is stored starting at index [0]. */ +/* However, that item is item number `1' unless the `z' switch is used, in */ +/* which case it is item number `0'. Hence, you may find it easier to */ +/* index points (and triangles in the neighbor list) if you use the `z' */ +/* switch. Unless, of course, you're calling Triangle from a Fortran */ +/* program. */ +/* */ +/* Description of fields (except the `numberof' fields, which are obvious): */ +/* */ +/* `pointlist': An array of point coordinates. The first point's x */ +/* coordinate is at index [0] and its y coordinate at index [1], followed */ +/* by the coordinates of the remaining points. Each point occupies two */ +/* REALs. */ +/* `pointattributelist': An array of point attributes. Each point's */ +/* attributes occupy `numberofpointattributes' REALs. */ +/* `pointmarkerlist': An array of point markers; one int per point. */ +/* */ +/* `trianglelist': An array of triangle corners. The first triangle's */ +/* first corner is at index [0], followed by its other two corners in */ +/* counterclockwise order, followed by any other nodes if the triangle */ +/* represents a nonlinear element. Each triangle occupies */ +/* `numberofcorners' ints. */ +/* `triangleattributelist': An array of triangle attributes. Each */ +/* triangle's attributes occupy `numberoftriangleattributes' REALs. */ +/* `trianglearealist': An array of triangle area constraints; one REAL per */ +/* triangle. Input only. */ +/* `neighborlist': An array of triangle neighbors; three ints per */ +/* triangle. Output only. */ +/* */ +/* `segmentlist': An array of segment endpoints. The first segment's */ +/* endpoints are at indices [0] and [1], followed by the remaining */ +/* segments. Two ints per segment. */ +/* `segmentmarkerlist': An array of segment markers; one int per segment. */ +/* */ +/* `holelist': An array of holes. The first hole's x and y coordinates */ +/* are at indices [0] and [1], followed by the remaining holes. Two */ +/* REALs per hole. Input only, although the pointer is copied to the */ +/* output structure for your convenience. */ +/* */ +/* `regionlist': An array of regional attributes and area constraints. */ +/* The first constraint's x and y coordinates are at indices [0] and [1], */ +/* followed by the regional attribute and index [2], followed by the */ +/* maximum area at index [3], followed by the remaining area constraints. */ +/* Four REALs per area constraint. Note that each regional attribute is */ +/* used only if you select the `A' switch, and each area constraint is */ +/* used only if you select the `a' switch (with no number following), but */ +/* omitting one of these switches does not change the memory layout. */ +/* Input only, although the pointer is copied to the output structure for */ +/* your convenience. */ +/* */ +/* `edgelist': An array of edge endpoints. The first edge's endpoints are */ +/* at indices [0] and [1], followed by the remaining edges. Two ints per */ +/* edge. Output only. */ +/* `edgemarkerlist': An array of edge markers; one int per edge. Output */ +/* only. */ +/* `normlist': An array of normal vectors, used for infinite rays in */ +/* Voronoi diagrams. The first normal vector's x and y magnitudes are */ +/* at indices [0] and [1], followed by the remaining vectors. For each */ +/* finite edge in a Voronoi diagram, the normal vector written is the */ +/* zero vector. Two REALs per edge. Output only. */ +/* */ +/* */ +/* Any input fields that Triangle will examine must be initialized. */ +/* Furthermore, for each output array that Triangle will write to, you */ +/* must either provide space by setting the appropriate pointer to point */ +/* to the space you want the data written to, or you must initialize the */ +/* pointer to NULL, which tells Triangle to allocate space for the results. */ +/* The latter option is preferable, because Triangle always knows exactly */ +/* how much space to allocate. The former option is provided mainly for */ +/* people who need to call Triangle from Fortran code, though it also makes */ +/* possible some nasty space-saving tricks, like writing the output to the */ +/* same arrays as the input. */ +/* */ +/* Triangle will not free() any input or output arrays, including those it */ +/* allocates itself; that's up to you. */ +/* */ +/* Here's a guide to help you decide which fields you must initialize */ +/* before you call triangulate(). */ +/* */ +/* `in': */ +/* */ +/* - `pointlist' must always point to a list of points; `numberofpoints' */ +/* and `numberofpointattributes' must be properly set. */ +/* `pointmarkerlist' must either be set to NULL (in which case all */ +/* markers default to zero), or must point to a list of markers. If */ +/* `numberofpointattributes' is not zero, `pointattributelist' must */ +/* point to a list of point attributes. */ +/* - If the `r' switch is used, `trianglelist' must point to a list of */ +/* triangles, and `numberoftriangles', `numberofcorners', and */ +/* `numberoftriangleattributes' must be properly set. If */ +/* `numberoftriangleattributes' is not zero, `triangleattributelist' */ +/* must point to a list of triangle attributes. If the `a' switch is */ +/* used (with no number following), `trianglearealist' must point to a */ +/* list of triangle area constraints. `neighborlist' may be ignored. */ +/* - If the `p' switch is used, `segmentlist' must point to a list of */ +/* segments, `numberofsegments' must be properly set, and */ +/* `segmentmarkerlist' must either be set to NULL (in which case all */ +/* markers default to zero), or must point to a list of markers. */ +/* - If the `p' switch is used without the `r' switch, then */ +/* `numberofholes' and `numberofregions' must be properly set. If */ +/* `numberofholes' is not zero, `holelist' must point to a list of */ +/* holes. If `numberofregions' is not zero, `regionlist' must point to */ +/* a list of region constraints. */ +/* - If the `p' switch is used, `holelist', `numberofholes', */ +/* `regionlist', and `numberofregions' is copied to `out'. (You can */ +/* nonetheless get away with not initializing them if the `r' switch is */ +/* used.) */ +/* - `edgelist', `edgemarkerlist', `normlist', and `numberofedges' may be */ +/* ignored. */ +/* */ +/* `out': */ +/* */ +/* - `pointlist' must be initialized (NULL or pointing to memory) unless */ +/* the `N' switch is used. `pointmarkerlist' must be initialized */ +/* unless the `N' or `B' switch is used. If `N' is not used and */ +/* `in->numberofpointattributes' is not zero, `pointattributelist' must */ +/* be initialized. */ +/* - `trianglelist' must be initialized unless the `E' switch is used. */ +/* `neighborlist' must be initialized if the `n' switch is used. If */ +/* the `E' switch is not used and (`in->numberofelementattributes' is */ +/* not zero or the `A' switch is used), `elementattributelist' must be */ +/* initialized. `trianglearealist' may be ignored. */ +/* - `segmentlist' must be initialized if the `p' or `c' switch is used, */ +/* and the `P' switch is not used. `segmentmarkerlist' must also be */ +/* initialized under these circumstances unless the `B' switch is used. */ +/* - `edgelist' must be initialized if the `e' switch is used. */ +/* `edgemarkerlist' must be initialized if the `e' switch is used and */ +/* the `B' switch is not. */ +/* - `holelist', `regionlist', `normlist', and all scalars may be ignored.*/ +/* */ +/* `vorout' (only needed if `v' switch is used): */ +/* */ +/* - `pointlist' must be initialized. If `in->numberofpointattributes' */ +/* is not zero, `pointattributelist' must be initialized. */ +/* `pointmarkerlist' may be ignored. */ +/* - `edgelist' and `normlist' must both be initialized. */ +/* `edgemarkerlist' may be ignored. */ +/* - Everything else may be ignored. */ +/* */ +/* After a call to triangulate(), the valid fields of `out' and `vorout' */ +/* will depend, in an obvious way, on the choice of switches used. Note */ +/* that when the `p' switch is used, the pointers `holelist' and */ +/* `regionlist' are copied from `in' to `out', but no new space is */ +/* allocated; be careful that you don't free() the same array twice. On */ +/* the other hand, Triangle will never copy the `pointlist' pointer (or any */ +/* others); new space is allocated for `out->pointlist', or if the `N' */ +/* switch is used, `out->pointlist' remains uninitialized. */ +/* */ +/* All of the meaningful `numberof' fields will be properly set; for */ +/* instance, `numberofedges' will represent the number of edges in the */ +/* triangulation whether or not the edges were written. If segments are */ +/* not used, `numberofsegments' will indicate the number of boundary edges. */ +/* */ +/*****************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +struct triangulateio { + REAL *pointlist; /* In / out */ + REAL *pointattributelist; /* In / out */ + int *pointmarkerlist; /* In / out */ + int numberofpoints; /* In / out */ + int numberofpointattributes; /* In / out */ + + int *trianglelist; /* In / out */ + REAL *triangleattributelist; /* In / out */ + REAL *trianglearealist; /* In only */ + int *neighborlist; /* Out only */ + int numberoftriangles; /* In / out */ + int numberofcorners; /* In / out */ + int numberoftriangleattributes; /* In / out */ + + int *segmentlist; /* In / out */ + int *segmentmarkerlist; /* In / out */ + int numberofsegments; /* In / out */ + + REAL *holelist; /* In / pointer to array copied out */ + int numberofholes; /* In / copied out */ + + REAL *regionlist; /* In / pointer to array copied out */ + int numberofregions; /* In / copied out */ + + int *edgelist; /* Out only */ + int *edgemarkerlist; /* Not used with Voronoi diagram; out only */ + REAL *normlist; /* Used only with Voronoi diagram; out only */ + int numberofedges; /* Out only */ +}; + +void triangulate(char *, struct triangulateio *, struct triangulateio *, + struct triangulateio *); + +#ifdef __cplusplus +} +#endif diff --git a/contrib/gtkgensurf/view.cpp b/contrib/gtkgensurf/view.cpp new file mode 100644 index 00000000..bf4cbe23 --- /dev/null +++ b/contrib/gtkgensurf/view.cpp @@ -0,0 +1,1287 @@ +/* +GenSurf plugin for GtkRadiant +Copyright (C) 2001 David Hyde, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include "gensurf.h" + +#undef ISOMETRIC + +extern double backface; +extern double dh, dv; +extern double xmin,xmax,ymin,ymax,zmin,zmax; + +double SF, SFG; // Graphics scale factors +double XLo, XHi, YLo, YHi, ZLo, ZHi; +double yaw,roll; +double elevation,azimuth; +int cxChar = 10, cyChar = 16; +int X0, Y0; +int X0G, Y0G; + +static Rect rcCoord; // where X= Y= is drawn +static Rect rcGrid; // rectangle within rcLower that forms the border of the grid, plus + // a 3 pixel slop. +static Rect rcLower; // lower half of window, where plan view is drawn +static Rect rcUpper; // upper half or entire window, where isometric projection is drawn + +void vertex_selected (); +void texfont_init (); +void texfont_write (const char *text, int l, int t); + +#define PEN_GRID { \ + g_GLTable.m_pfn_qglLineWidth (1); \ + g_GLTable.m_pfn_qglColor3f (0, 1, 0); \ + g_GLTable.m_pfn_qglDisable (GL_LINE_STIPPLE); } + +#define PEN_RED { \ + g_GLTable.m_pfn_qglLineWidth (2); \ + g_GLTable.m_pfn_qglColor3f (1, 0, 0); \ + g_GLTable.m_pfn_qglDisable (GL_LINE_STIPPLE); } + +#define PEN_DASH { \ + g_GLTable.m_pfn_qglLineWidth (1); \ + g_GLTable.m_pfn_qglColor3f (0, 1, 0); \ + g_GLTable.m_pfn_qglLineStipple (1, 0xF0F0); \ + g_GLTable.m_pfn_qglEnable (GL_LINE_STIPPLE); } + +#define DRAW_QUAD(rc,r,g,b) { \ + g_GLTable.m_pfn_qglBegin (GL_QUADS); \ + g_GLTable.m_pfn_qglColor3f (0,1,0); \ + g_GLTable.m_pfn_qglVertex2i (rc.left-1, rc.bottom); \ + g_GLTable.m_pfn_qglVertex2i (rc.right, rc.bottom); \ + g_GLTable.m_pfn_qglVertex2i (rc.right, rc.top+1); \ + g_GLTable.m_pfn_qglVertex2i (rc.left-1, rc.top+1); \ + g_GLTable.m_pfn_qglColor3f (r,g,b); \ + g_GLTable.m_pfn_qglVertex2i (rc.left, rc.bottom+1); \ + g_GLTable.m_pfn_qglVertex2i (rc.right-1, rc.bottom+1); \ + g_GLTable.m_pfn_qglVertex2i (rc.right-1, rc.top); \ + g_GLTable.m_pfn_qglVertex2i (rc.left, rc.top); \ + g_GLTable.m_pfn_qglEnd (); } + + +#ifndef ISOMETRIC +double D=65536.; +double ct[3],st[3]; +double Hhi, Hlo, Vhi, Vlo; +#endif + +#define SUBDIVS 6 + + +void ShowPreview () +{ + if (Preview) + { + if (g_pWndPreview == NULL) + CreateViewWindow (); + gtk_widget_show (g_pWndPreview); + + UpdatePreview (true); + } + else + gtk_widget_hide (g_pWndPreview); +} + +static void draw_preview () +{ + int width = g_pPreviewWidget->allocation.width, height = g_pPreviewWidget->allocation.height; + + g_GLTable.m_pfn_qglClearColor (0, 0, 0, 1); + g_GLTable.m_pfn_qglViewport (0, 0, width, height); + g_GLTable.m_pfn_qglMatrixMode (GL_PROJECTION); + g_GLTable.m_pfn_qglLoadIdentity (); + g_GLTable.m_pfn_qglOrtho (0, width, 0, height, -1, 1); + g_GLTable.m_pfn_qglClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); + + // ^Fishman - Antializing for the preview window. + if (Antialiasing) + { + g_GLTable.m_pfn_qglEnable(GL_BLEND); + g_GLTable.m_pfn_qglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + g_GLTable.m_pfn_qglEnable(GL_LINE_SMOOTH); + } + else + { + g_GLTable.m_pfn_qglDisable(GL_BLEND); + g_GLTable.m_pfn_qglDisable(GL_LINE_SMOOTH); + } + + texfont_init (); + + if (!ValidSurface ()) + return; + + rcUpper.left = 0; + rcUpper.right = width; + rcUpper.bottom = 0; + rcUpper.top = height; + rcLower.left = 0; + rcLower.right = width; + rcLower.bottom = 0; + rcLower.top = height; + + if (VertexMode) + { + rcUpper.bottom = rcUpper.top/2; + DrawPreview (rcUpper); + g_GLTable.m_pfn_qglBegin (GL_LINES); + g_GLTable.m_pfn_qglVertex2i (rcUpper.left, rcUpper.bottom); + g_GLTable.m_pfn_qglVertex2i (rcUpper.right, rcUpper.bottom); + g_GLTable.m_pfn_qglEnd (); + rcLower.top = rcUpper.bottom-1; + DrawGrid (rcLower); + rcCoord.left = rcLower.left; + rcCoord.right = rcLower.right; + rcCoord.bottom = rcLower.bottom; + rcCoord.top = rcLower.top; + rcCoord.top = rcCoord.bottom+cyChar; + rcCoord.right = rcCoord.left + 15*cxChar; + rcGrid.left = X0G - 3; + rcGrid.bottom = Y0G - 3; + rcGrid.right = X0G + (int)(SFG*(Hur-Hll)) + 3; + rcGrid.top = Y0G + (int)(SFG*(Vur-Vll)) + 3; + } + else + DrawPreview (rcUpper); +} + +static gint expose (GtkWidget *widget, GdkEventExpose *event, gpointer data) +{ + if (event->count > 0) + return TRUE; + + if (!g_UIGtkTable.m_pfn_glwidget_make_current (g_pPreviewWidget)) + { + g_FuncTable.m_pfnSysPrintf ("GtkGenSurf: glMakeCurrent failed\n"); + return TRUE; + } + + draw_preview (); + + g_UIGtkTable.m_pfn_glwidget_swap_buffers (g_pPreviewWidget); + g_GLTable.m_pfn_QE_CheckOpenGLForErrors (); + + return TRUE; +} + +static void button_press (GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + Point pt = { (long)event->x, widget->allocation.height - (long)event->y }; + bool Selected; + double x,y; + int i, j, k, ks; + int i0, i1, j0, j1; + + if ((!VertexMode) || (event->button != 1)) + return; + + if (!PtInRect (&rcGrid,pt)) + { + gdk_beep (); + return; + } + + x = Hll + (pt.x-X0G)/SFG; + y = Vur - (pt.y-Y0G)/SFG; + i = (int)(floor( (x-Hll)/dh - 0.5) + 1); + j = (int)(floor( (y-Vll)/dv - 0.5) + 1); + if (i < 0 || i > NH || j < 0 || j > NV) + { + gdk_beep (); + return; + } + + if(!CanEdit(i,j)) + { + gdk_beep (); + return; + } + + // Control key pressed - add this point, or remove it if already selected + if ((event->state & GDK_CONTROL_MASK) != 0) + { + Selected = FALSE; + if (NumVerticesSelected) + { + for (k=0; kstate & GDK_SHIFT_MASK) != 0) + { + if (NumVerticesSelected) + { + NumVerticesSelected = 1; + i0 = min(Vertex[0].i, i); + i1 = max(Vertex[0].i, i); + j0 = min(Vertex[0].j, j); + j1 = max(Vertex[0].j, j); + for(i=i0; i<=i1; i++) + { + for(j=j0; j<=j1; j++) + { + if(i==0 && j==0 ) continue; + if(i==NH && j==0 ) continue; + if(i==0 && j==NV) continue; + if(i==NH && j==NV) continue; + if(i != Vertex[0].i || j != Vertex[0].j) + { + Vertex[NumVerticesSelected].i = i; + Vertex[NumVerticesSelected].j = j; + NumVerticesSelected++; + } + } + } + } + else + { + Vertex[0].i = i; + Vertex[0].j = j; + NumVerticesSelected = 1; + } + } + else + { + Vertex[0].i = i; + Vertex[0].j = j; + NumVerticesSelected = 1; + } + + vertex_selected (); +} + +static void motion (GtkWidget *widget, GdkEventMotion *event, gpointer data) +{ + Point pt = { (long)event->x, widget->allocation.height - (long)event->y }; + + if (!VertexMode) + return; + + if (!g_UIGtkTable.m_pfn_glwidget_make_current (g_pPreviewWidget)) + { + g_FuncTable.m_pfnSysPrintf ("GtkGenSurf: glMakeCurrent failed\n"); + return; + } + + g_GLTable.m_pfn_qglEnable (GL_SCISSOR_TEST); + g_GLTable.m_pfn_qglScissor (rcCoord.left, rcCoord.bottom, rcCoord.right-rcCoord.left, + rcCoord.top-rcCoord.bottom); + g_GLTable.m_pfn_qglClear (GL_COLOR_BUFFER_BIT); + + if (PtInRect(&rcGrid,pt)) + { + GdkCursor *cursor = gdk_cursor_new (GDK_CROSS); + gdk_window_set_cursor (g_pWndPreview->window, cursor); + gdk_cursor_unref (cursor); + + char Text[32]; + int x, y; + + x = (int)(Hll + (pt.x-X0G)/SFG); + y = (int)(Vur - (pt.y-Y0G)/SFG); + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + sprintf(Text," x=%d, z=%d ",(int)(floor(x-0.5)+1.) ,(int)(floor(y-0.5)+1.) ); + break; + case PLANE_YZ0: + case PLANE_YZ1: + sprintf(Text," y=%d, z=%d ",(int)(floor(x-0.5)+1.) ,(int)(floor(y-0.5)+1.) ); + break; + default: + sprintf(Text," x=%d, y=%d ",(int)(floor(x-0.5)+1.) ,(int)(floor(y-0.5)+1.) ); + } + + texfont_write (Text, rcCoord.left, rcCoord.top); + } + else + { + gdk_window_set_cursor (g_pWndPreview->window, NULL); + } + + g_UIGtkTable.m_pfn_glwidget_swap_buffers (g_pPreviewWidget); + g_GLTable.m_pfn_QE_CheckOpenGLForErrors (); + g_GLTable.m_pfn_qglDisable (GL_SCISSOR_TEST); +} + +static gint preview_close (GtkWidget *widget, gpointer data) +{ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (g_object_get_data (G_OBJECT (g_pWnd), "main_preview")), FALSE); + return TRUE; +} + +static void preview_focusout (GtkSpinButton *spin, GdkEventFocus *event, double *data) +{ + *data = DegreesToRadians ((double)(gtk_spin_button_get_value_as_int (spin) % 360)); + UpdatePreview (false); +} + +static gint doublevariable_spinfocusout(GtkWidget* widget, GdkEventFocus* event, gpointer data) +{ + preview_focusout(GTK_SPIN_BUTTON(widget), event, reinterpret_cast(data)); + return FALSE; +} + +static void preview_spin (GtkAdjustment *adj, double *data) +{ + *data = DegreesToRadians (adj->value); + UpdatePreview (false); +} + +void CreateViewWindow () +{ + GtkWidget *dlg, *vbox, *hbox, *label, *spin, *frame; + GtkObject *adj; + +#ifndef ISOMETRIC + elevation = PI/6.; + azimuth = PI/6.; +#endif + + g_pWndPreview = dlg = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (dlg), "GtkGenSurf Preview"); + gtk_signal_connect (GTK_OBJECT (dlg), "delete_event", GTK_SIGNAL_FUNC (preview_close), NULL); + gtk_signal_connect (GTK_OBJECT (dlg), "destroy", GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + gtk_window_set_transient_for (GTK_WINDOW (dlg), GTK_WINDOW (g_pWnd)); + gtk_window_set_default_size (GTK_WINDOW (dlg), 300, 400); + + vbox = gtk_vbox_new (FALSE, 5); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (dlg), vbox); + +#ifndef ISOMETRIC + hbox = gtk_hbox_new (TRUE, 5); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 3); + + label = gtk_label_new ("Elevation"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0); + + adj = gtk_adjustment_new (30, -90, 90, 1, 10, 10); + gtk_signal_connect (adj, "value_changed", GTK_SIGNAL_FUNC (preview_spin), &elevation); + spin = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1, 0); + gtk_widget_show (spin); + gtk_box_pack_start (GTK_BOX (hbox), spin, FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (spin), "focus_out_event", G_CALLBACK (doublevariable_spinfocusout), &elevation); + + adj = gtk_adjustment_new (30, 0, 359, 1, 10, 10); + gtk_signal_connect (adj, "value_changed", GTK_SIGNAL_FUNC (preview_spin), &azimuth); + spin = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1, 0); + gtk_widget_show (spin); + gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spin), TRUE); + gtk_box_pack_end (GTK_BOX (hbox), spin, FALSE, TRUE, 0); + + label = gtk_label_new ("Azimuth"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 1, 0.5); + gtk_box_pack_end (GTK_BOX (hbox), label, FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (spin), "focus_out_event", G_CALLBACK (doublevariable_spinfocusout), &azimuth); +#endif + + frame = gtk_frame_new (NULL); + gtk_widget_show (frame); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0); + + g_pPreviewWidget = g_UIGtkTable.m_pfn_glwidget_new (FALSE, NULL); + + gtk_widget_set_events (g_pPreviewWidget, GDK_EXPOSURE_MASK|GDK_BUTTON_PRESS_MASK|GDK_POINTER_MOTION_MASK); + gtk_signal_connect (GTK_OBJECT (g_pPreviewWidget), "expose_event", GTK_SIGNAL_FUNC (expose), NULL); + gtk_signal_connect (GTK_OBJECT (g_pPreviewWidget), "motion_notify_event", GTK_SIGNAL_FUNC (motion), NULL); + gtk_signal_connect (GTK_OBJECT (g_pPreviewWidget), "button_press_event", + GTK_SIGNAL_FUNC (button_press), NULL); + + gtk_widget_show (g_pPreviewWidget); + gtk_container_add (GTK_CONTAINER (frame), g_pPreviewWidget); + + if (Preview) + gtk_widget_show (g_pWndPreview); + + UpdatePreview (true); +} + +//============================================================= +/* DrawPreview */ +void DrawPreview (Rect rc) +{ +#define COSXA 0.8660254037844 +#define SINXA 0.5 +#define COSYA 0.8660254037844 +#define SINYA 0.5 + + double L; + double x,y; + int i, j; + Point pt[8]; + XYZ v[8]; + char axis[3][2] = {"X","Y","Z"}; + +#ifndef ISOMETRIC + evaluate(); +#endif + + XLo = xmin; + XHi = xmax; + YLo = ymin; + YHi = ymax; + ZLo = zmin; + ZHi = zmax; + switch (Plane) + { + case PLANE_XY1: + ZHi = backface; + break; + case PLANE_XZ0: + YLo = backface; + break; + case PLANE_XZ1: + YHi = backface; + break; + case PLANE_YZ0: + XLo = backface; + break; + case PLANE_YZ1: + XHi = backface; + break; + default: + ZLo = backface; + } + + + + GetScaleFactor(rc); + //PEN_GRID + g_GLTable.m_pfn_qglLineWidth (1); + g_GLTable.m_pfn_qglColor3f (0, 1, 0); + g_GLTable.m_pfn_qglDisable (GL_LINE_STIPPLE); + + if (Decimate > 0 && (Game != QUAKE3 || UsePatches==0) ) + { + XYZ *vv; + + vv = (XYZ *) malloc(gNumNodes * sizeof(XYZ)); + for(i=0; i 2) + x = Hll + dh * (int)(NH/2 + 1); + else + x = Hll + dh * (int)(NH/2); + if(NV > 2) + y = Vll + dv * (int)(NV/2 + 1); + else + y = Vll + dv * (int)(NV/2); + } + else + { + if(NH > 1) + x = Hll + dh * (int)(NH/2); + else + x = Hll + dh/2; + if(NV > 1) + y = Vll + dv * (int)(NV/2); + else + y = Vll + dv/2; + } +// x = (Hll+Hur)/2.; +// y = (Vll+Vur)/2.; + v[0].p[0] = x + PlayerBox[Game].x[0]; + v[0].p[1] = y + PlayerBox[Game].y[0]; + v[0].p[2] = PlayerStartZ(x,y) + PlayerBox[Game].z[0] + 8; // add 8 cuz I'm a pessimist + } + v[1].p[0] = v[0].p[0] + PlayerBox[Game].x[1] - PlayerBox[Game].x[0]; + v[1].p[1] = v[0].p[1]; + v[1].p[2] = v[0].p[2]; + v[2].p[0] = v[1].p[0]; + v[2].p[1] = v[1].p[1] + PlayerBox[Game].y[1] - PlayerBox[Game].y[0]; + v[2].p[2] = v[0].p[2]; + v[3].p[0] = v[0].p[0]; + v[3].p[1] = v[2].p[1]; + v[3].p[2] = v[0].p[2]; + VectorCopy(v[0].p,v[4].p); + VectorCopy(v[1].p,v[5].p); + VectorCopy(v[2].p,v[6].p); + VectorCopy(v[3].p,v[7].p); + v[4].p[2] += PlayerBox[Game].z[1] - PlayerBox[Game].z[0]; + v[5].p[2] += PlayerBox[Game].z[1] - PlayerBox[Game].z[0]; + v[6].p[2] += PlayerBox[Game].z[1] - PlayerBox[Game].z[0]; + v[7].p[2] += PlayerBox[Game].z[1] - PlayerBox[Game].z[0]; + for(i=0; i<=7; i++) + { +#ifndef ISOMETRIC + project(&v[i]); +#endif + Scale(rc,v[i],&pt[i]); + } + g_GLTable.m_pfn_qglBegin (GL_LINE_STRIP); + g_GLTable.m_pfn_qglVertex2i (pt[3].x, pt[3].y); + for(i=0; i<=3; i++) + g_GLTable.m_pfn_qglVertex2i (pt[i].x, pt[i].y); + g_GLTable.m_pfn_qglEnd (); + g_GLTable.m_pfn_qglBegin (GL_LINE_STRIP); + g_GLTable.m_pfn_qglVertex2i (pt[7].x, pt[7].y); + for(i=4; i<=7; i++) + g_GLTable.m_pfn_qglVertex2i (pt[i].x, pt[i].y); + g_GLTable.m_pfn_qglEnd (); + g_GLTable.m_pfn_qglBegin (GL_LINES); + for(i=0; i<=3; i++) + { + g_GLTable.m_pfn_qglVertex2i (pt[i].x,pt[i].y); + g_GLTable.m_pfn_qglVertex2i (pt[i+4].x,pt[i+4].y); + } + g_GLTable.m_pfn_qglEnd (); + + g_GLTable.m_pfn_qglLineWidth (1); + g_GLTable.m_pfn_qglColor3f (0, 1, 0); + g_GLTable.m_pfn_qglDisable (GL_LINE_STIPPLE); +} +//============================================================= +void DrawGrid(Rect rc) +{ + int i, j, k; + double h,w,x,y; + Point pt[2]; + Rect rcBox; + + w = (double)(rc.right-rc.left+1) - cxChar; + h = (double)(rc.top-rc.bottom+1) - cxChar - cyChar; + + SFG = w/(Hur-Hll); + SFG = min(SFG, h/(Vur-Vll)); + + // Center drawing + X0G = (int)(rc.left + rc.right - (int)(SFG*(Hur-Hll)))/2; + Y0G = (int)(rc.top + rc.bottom + cyChar - (int)(SFG*(Vur-Vll)))/2; + + g_GLTable.m_pfn_qglLineWidth (2); + g_GLTable.m_pfn_qglColor3f (0, 1, 0); + g_GLTable.m_pfn_qglDisable (GL_LINE_STIPPLE); + + pt[0].y = Y0G; + pt[1].y = Y0G + (int)(SFG*(Vur-Vll)); + g_GLTable.m_pfn_qglBegin (GL_LINES); + for(i=0; i<=NH; i++) + { + x = Hll + i * dh; + pt[0].x = X0G + (int)(SFG*(x-Hll)); + g_GLTable.m_pfn_qglVertex2i(pt[0].x, pt[0].y); + g_GLTable.m_pfn_qglVertex2i(pt[0].x, pt[1].y); + } + g_GLTable.m_pfn_qglEnd (); + pt[0].x = X0G; + pt[1].x = X0G + (int)(SFG*(Hur-Hll)); + g_GLTable.m_pfn_qglBegin (GL_LINES); + for(i=0; i<=NV; i++) + { + y = Vll + i * dv; + pt[0].y = Y0G + (int)(SFG*(Vur-y)); + g_GLTable.m_pfn_qglVertex2i (pt[0].x,pt[0].y); + g_GLTable.m_pfn_qglVertex2i (pt[1].x,pt[0].y); + } + g_GLTable.m_pfn_qglEnd (); + + g_GLTable.m_pfn_qglLineWidth (1); + + // Draw axes + pt[0].x = rc.right - cyChar - cxChar - cyChar/2; + pt[0].y = rc.bottom + cyChar/2; + pt[1].x = pt[0].x + cyChar; + pt[1].y = pt[0].y; + g_GLTable.m_pfn_qglBegin (GL_LINES); + g_GLTable.m_pfn_qglVertex2i (pt[0].x,pt[0].y); + g_GLTable.m_pfn_qglVertex2i (pt[1].x,pt[1].y); + g_GLTable.m_pfn_qglEnd (); + switch(Plane) + { + case PLANE_YZ0: + case PLANE_YZ1: + texfont_write ("Y", pt[1].x, pt[1].y+cyChar/2); + break; + default: + texfont_write ("X", pt[1].x, pt[1].y+cyChar/2); + } + pt[1].x = pt[0].x; + pt[1].y = pt[0].y + cyChar; + g_GLTable.m_pfn_qglBegin (GL_LINES); + g_GLTable.m_pfn_qglVertex2i (pt[0].x,pt[0].y); + g_GLTable.m_pfn_qglVertex2i (pt[1].x,pt[1].y); + g_GLTable.m_pfn_qglEnd (); + switch(Plane) + { + case PLANE_XY0: + case PLANE_XY1: + texfont_write ("Y", pt[1].x-cyChar/2, pt[1].y+cyChar); + break; + default: + texfont_write ("Z", pt[1].x-cyChar/2, pt[1].y+cyChar); + } + + // Denote fixed points with a 5x5 red rectangle + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + if(xyz[i][j].fixed) + { + x = Hll + i*dh; + y = Vll + j*dv; + rcBox.left = X0G + (int)(SFG*(x-Hll)) - 2; + rcBox.top = Y0G + (int)(SFG*(Vur-y)) + 2; + rcBox.right = rcBox.left + 5; + rcBox.bottom = rcBox.top - 5; + + DRAW_QUAD (rcBox, 1,0,0); + } + } + } + + // Denote currently selected point with a 5x5 green rectangle + if (NumVerticesSelected) + { + for(k=0; kp[0]; + y = v->p[1]; + z = v->p[2]; + + // yaw + xa = ct[0]*x - st[0]*z; + za = st[0]*x + ct[0]*z; + + // roll + x = ct[1]*xa + st[1]*y; + ya = ct[1]*y - st[1]*xa; + + // azimuth + z = ct[2]*za - st[2]*ya; + y = ct[2]*ya + st[2]*za; + + // horizontal and vertical projections: +// v->pp[0] = D*x/z; +// v->pp[1] = D*y/z; + v->pp[0] = -y; + v->pp[1] = x; + v->pp[2] = z; + + // NOTE: if perspective transformation is desired, + // set "persp" to the range from the surface, + // then: + // v->projected_h = -v->projected_h * persp/(v->projected_z-persp); + // v->projected_v = -v->projected_v * persp/(v->projected_z-persp); +} +/*=======================================================================*/ +void evaluate() +{ + int i, j; + XYZ v[4]; + + if(elevation > PI) elevation -= 2.*PI; + roll = elevation * sin(azimuth); + yaw = 1.5*PI + elevation*cos(azimuth); + + // Find angles from midpoint to viewpoint: + st[0] = sin(yaw); + st[1] = sin(roll); + st[2] = sin(azimuth); + ct[0] = cos(yaw); + ct[1] = cos(roll); + ct[2] = cos(azimuth); + + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + project(&xyz[i][j]); + } + } + + Hhi = xyz[0][0].pp[0]; + Hlo = Hhi; + Vhi = xyz[0][0].pp[1]; + Vlo = Vhi; + for(i=0; i<=NH; i++) + { + for(j=0; j<=NV; j++) + { + Hlo = min(Hlo,xyz[i][j].pp[0]); + Hhi = max(Hhi,xyz[i][j].pp[0]); + Vlo = min(Vlo,xyz[i][j].pp[1]); + Vhi = max(Vhi,xyz[i][j].pp[1]); + } + } + + // Include backface in min-max + VectorCopy(xyz[ 0][ 0].p,v[0].p); + VectorCopy(xyz[NH][ 0].p,v[1].p); + VectorCopy(xyz[NH][NV].p,v[2].p); + VectorCopy(xyz[ 0][NV].p,v[3].p); + switch(Plane) + { + case PLANE_XZ0: + case PLANE_XZ1: + v[0].p[1] = backface; + v[1].p[1] = v[0].p[1]; + v[2].p[1] = v[0].p[1]; + v[3].p[1] = v[0].p[1]; + break; + case PLANE_YZ0: + case PLANE_YZ1: + v[0].p[0] = backface; + v[1].p[0] = v[0].p[0]; + v[2].p[0] = v[0].p[0]; + v[3].p[0] = v[0].p[0]; + break; + default: + v[0].p[2] = backface; + v[1].p[2] = v[0].p[2]; + v[2].p[2] = v[0].p[2]; + v[3].p[2] = v[0].p[2]; + } + for(i=0; i<=3; i++) + { + project(&v[i]); + Hlo = min(Hlo,v[i].pp[0]); + Hhi = max(Hhi,v[i].pp[0]); + Vlo = min(Vlo,v[i].pp[1]); + Vhi = max(Vhi,v[i].pp[1]); + } + +} +#endif diff --git a/contrib/hydratoolz/hydratoolz.def b/contrib/hydratoolz/hydratoolz.def new file mode 100644 index 00000000..c48e519a --- /dev/null +++ b/contrib/hydratoolz/hydratoolz.def @@ -0,0 +1,8 @@ +; fgd.def : Declares the module parameters for the DLL. + +LIBRARY "HydraToolz" +DESCRIPTION 'HydraToolz Windows Dynamic Link Library' + +EXPORTS + ; Explicit exports can go here + Synapse_EnumerateInterfaces @1 diff --git a/contrib/hydratoolz/hydratoolz.vcproj b/contrib/hydratoolz/hydratoolz.vcproj new file mode 100644 index 00000000..53d20a95 --- /dev/null +++ b/contrib/hydratoolz/hydratoolz.vcproj @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/hydratoolz/plugin.cpp b/contrib/hydratoolz/plugin.cpp new file mode 100644 index 00000000..abc57fc3 --- /dev/null +++ b/contrib/hydratoolz/plugin.cpp @@ -0,0 +1,542 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "plugin.h" +#include "version.h" + +/*! \file plugin.cpp + \brief HydraToolz! + + HydraToolz by Dominic Clifton - Hydra (Hydra@Hydras-World.com) + + Overview + ======== + + + Version History + =============== + + v0.1 - 28/May/2002 + - Initial version. + + + ToDo + ==== + + * Code it ? :) + +*/ + +// ============================================================================= +// Globals + +_QERFuncTable_1 g_FuncTable; +_QERFileSystemTable g_FileSystemTable; +_QEREntityTable g_EntityTable; + +// cast to GtkWidget* +void *g_pMainWnd; + +// ============================================================================= +// Ripped from TexTool.cpp + +static void dialog_button_callback (GtkWidget *widget, gpointer data) +{ + GtkWidget *parent; + int *loop, *ret; + + parent = gtk_widget_get_toplevel (widget); + loop = (int*)gtk_object_get_data (GTK_OBJECT (parent), "loop"); + ret = (int*)gtk_object_get_data (GTK_OBJECT (parent), "ret"); + + *loop = 0; + *ret = gpointer_to_int (data); +} + +static gint dialog_delete_callback (GtkWidget *widget, GdkEvent* event, gpointer data) +{ + int *loop; + + gtk_widget_hide (widget); + loop = (int*)gtk_object_get_data (GTK_OBJECT (widget), "loop"); + *loop = 0; + + return TRUE; +} + +int DoMessageBox (const char* lpText, const char* lpCaption, guint32 uType) +{ + GtkWidget *window, *w, *vbox, *hbox; + int mode = (uType & MB_TYPEMASK), ret, loop = 1; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + gtk_window_set_title (GTK_WINDOW (window), lpCaption); + gtk_container_border_width (GTK_CONTAINER (window), 10); + gtk_object_set_data (GTK_OBJECT (window), "loop", &loop); + gtk_object_set_data (GTK_OBJECT (window), "ret", &ret); + gtk_widget_realize (window); + + vbox = gtk_vbox_new (FALSE, 10); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show (vbox); + + w = gtk_label_new (lpText); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT); + gtk_widget_show (w); + + w = gtk_hseparator_new (); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2); + gtk_widget_show (w); + + hbox = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); + gtk_widget_show (hbox); + + if (mode == MB_OK) + { + w = gtk_button_new_with_label ("Ok"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK)); + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + ret = IDOK; + } + else if (mode == MB_OKCANCEL) + { + w = gtk_button_new_with_label ("Ok"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK)); + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDCANCEL)); + gtk_widget_show (w); + ret = IDCANCEL; + } + else if (mode == MB_YESNOCANCEL) + { + w = gtk_button_new_with_label ("Yes"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDYES)); + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("No"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDNO)); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDCANCEL)); + gtk_widget_show (w); + ret = IDCANCEL; + } + else /* if (mode == MB_YESNO) */ + { + w = gtk_button_new_with_label ("Yes"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDYES)); + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("No"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDNO)); + gtk_widget_show (w); + ret = IDNO; + } + + gtk_widget_show (window); + gtk_grab_add (window); + + while (loop) + gtk_main_iteration (); + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return ret; +} + +// End of rip from TexTool.cpp + +// ============================================================================= +// Ripped from cmdlib.cpp + +/* +==================== +Extract file parts +==================== +*/ +void ExtractFilePath (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && *(src-1) != '/' && *(src-1) != '\\') + src--; + + memcpy (dest, path, src-path); + dest[src-path] = 0; +} + +void ExtractFileName (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && *(src-1) != '/' + && *(src-1) != '\\' ) + src--; + + while (*src) + { + *dest++ = *src++; + } + *dest = 0; +} + +void ConvertDOSToUnixName( char *dst, const char *src ) +{ + while ( *src ) + { + if ( *src == '\\' ) + *dst = '/'; + else + *dst = *src; + dst++; src++; + } + *dst = 0; +} + +// End of rip from cmdlib.cpp + +// ============================================================================= +// Actual Plugin Code + +// get the wad name from the shader name (or an actual wadname) and add to a list of wad names making +// sure we don't add duplicates. + +GSList *AddToWadList(GSList *wadlist, const char *shadername, const char *wad) +{ + char tmpstr[QER_MAX_NAMELEN]; + char *wadname; + if (!shadername && !wad) return wadlist; + + if (shadername) + { + if (strcmp(shadername,"color") == 0) + return wadlist; + ExtractFilePath(shadername,tmpstr); + // Sys_Printf("checking: %s\n",shadername); + + int l = strlen(tmpstr) - 1; + + if (tmpstr[l] == '/' || tmpstr[l] == '\\') + tmpstr[l] = 0; + else + { + Sys_Printf("WARNING: Unknown wad file for shader %s\n",shadername); + return wadlist; + } + + ExtractFileName(tmpstr,tmpstr); + + wadname = (char *)malloc(strlen(tmpstr) + 5); + sprintf(wadname,"%s.wad",tmpstr); + } + else + { + wadname=strdup(wad); + } + + for (GSList *l = wadlist; l != NULL ; l = l->next) + { + if (string_equal_nocase((char *)l->data,wadname)) + { + free( wadname ); + return wadlist; + } + } + + Sys_Printf("Adding Wad File to WAD list: %s (reason: ",wadname); + if (shadername) + Sys_Printf("see shader \"%s\")\n", shadername); + else + Sys_Printf("already in WAD key. )\n"); + return ( g_slist_append (wadlist, wadname ) ); +} + +void UpdateWadKeyPair( void ) +{ + int i,nb; + + char wads[2048]; // change to CString usage ? + wads[0] = 0; + char *p1,*p2; + entity_t *pEntity; + epair_t *pEpair; + GSList *wadlist = NULL; + face_t *f; + brush_t *b; + char cleanwadname[QER_MAX_NAMELEN]; + const char *actualwad; + + + pEntity = (entity_t *)g_FuncTable.m_pfnGetEntityHandle(0); // get the worldspawn ent + + Sys_Printf("Searching for in-use wad files...\n"); + for(pEpair = pEntity->epairs; pEpair != NULL; pEpair = pEpair->next) + { + if (string_equal_nocase(pEpair->key,"wad")) + { + strcpy(wads,pEpair->value); + ConvertDOSToUnixName(wads,wads); + + // ok, we got the list of ; delimited wads, now split it into a GSList that contains + // just the wad names themselves. + + p1 = wads; + + do + { + p2 = strchr(p1,';'); + if (p2) + *p2 = 0; // swap the ; with a null terminator + + if (strchr(p1,'/') || strchr(p1,'\\')) + { + ExtractFileName(p1,cleanwadname); + wadlist = AddToWadList (wadlist, NULL, cleanwadname); + } + else + { + wadlist = AddToWadList (wadlist, NULL, p1); + } + if (p2) + p1 = p2+1; // point back to the remainder of the string + else + p1 = NULL; // make it so we exit the loop. + + } while (p1); + + // ok, now we have a list of wads in GSList. + // now we need to add any new wadfiles (with their paths) to this list + // so scan all brushes and see what wads are in use + // FIXME: scan brushes only in the region ? + + break; // we don't need to process any more key/pairs. + } + } + + nb = g_FuncTable.m_pfnAllocateActiveBrushHandles(); + for( i = 0; i < nb; i++ ) + { + b = (brush_t *)g_FuncTable.m_pfnGetActiveBrushHandle(i); + if (b->patchBrush) // patches in halflife ? + { + wadlist = AddToWadList(wadlist, b->pPatch->pShader->getName(),NULL); + } else + { + for (f=b->brush_faces ; f ; f=f->next) + { + wadlist = AddToWadList(wadlist, f->pShader->getName(),NULL); + } + } + } + g_FuncTable.m_pfnReleaseActiveBrushHandles(); + + nb = g_FuncTable.m_pfnAllocateSelectedBrushHandles(); + for( i = 0; i < nb; i++ ) + { + b = (brush_t *)g_FuncTable.m_pfnGetSelectedBrushHandle(i); + if (b->patchBrush) // patches in halflife ? + { + wadlist = AddToWadList(wadlist, b->pPatch->pShader->getName(),NULL); + } else + { + for (f=b->brush_faces ; f ; f=f->next) + { + wadlist = AddToWadList(wadlist, f->pShader->getName(),NULL); + } + } + } + g_FuncTable.m_pfnReleaseSelectedBrushHandles(); + + // Now we have a complete list of wadnames (without paths) so we just have to turn this + // back to a ; delimited list. + + wads[0] = 0; + while (wadlist) + { + if (string_equal_nocase((char *)wadlist->data,"common-hydra.wad")) + { + Sys_Printf("Skipping radiant-supplied wad file %s\n",(char *)wadlist->data); + } + else + { + if (wads[0]) + strcat(wads,";"); + + actualwad = vfsGetFullPath((char *)wadlist->data); + + if (actualwad) + { + strcat(wads, actualwad); + } + else + { + Sys_Printf("WARNING: could not locate wad file %s\n",(char *)wadlist->data); + strcat(wads, (char *)wadlist->data); + } + } + + free (wadlist->data); + wadlist = g_slist_remove (wadlist, wadlist->data); + } + + // store the wad list back in the worldspawn. + if (wads[0]) + { + //free(pEpair->value); + //pEpair->value = strdup(wads); + SetKeyValue(pEntity, "wad", wads); + } + +} + +// ============================================================================= +// PLUGIN INTERFACE STUFF + +// plugin name +const char *PLUGIN_NAME = "Q3 Texture Tools"; + +// commands in the menu +const char *PLUGIN_COMMANDS = "About...;Create/Update WAD keypair"; + +const char *PLUGIN_ABOUT = "HydraToolz for GTKRadiant\n\n" + "By Hydra!"; + +extern "C" void* WINAPI QERPlug_GetFuncTable () +{ + return &g_FuncTable; +} + +const char* QERPlug_Init (void* hApp, void *pWidget) +{ + GtkWidget* pMainWidget = static_cast(pWidget); + + g_pMainWnd = pMainWidget; + memset(&g_FuncTable, 0, sizeof(_QERFuncTable_1)); + g_FuncTable.m_nSize = sizeof(_QERFuncTable_1); + return "HydraToolz for GTKRadiant"; // do we need this ? hmmm +} + +const char* QERPlug_GetName() +{ + return (char*)PLUGIN_NAME; +} + +const char* QERPlug_GetCommandList() +{ + return PLUGIN_COMMANDS; +} + +extern "C" void QERPlug_Dispatch(const char* p, vec3_t vMin, vec3_t vMax, bool bSingleBrush) +{ + if(!strcmp(p, "Create/Update WAD keypair")) + UpdateWadKeyPair(); + else if(!strcmp(p, "About...")) + g_FuncTable.m_pfnMessageBox((GtkWidget*)NULL, PLUGIN_ABOUT, "About", eMB_OK); +} + +// ============================================================================= +// SYNAPSE + +CSynapseServer* g_pSynapseServer = NULL; +CSynapseClientHydraToolz g_SynapseClient; + +extern "C" CSynapseClient* SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces (const char *version, CSynapseServer *pServer) +{ + if (strcmp(version, SYNAPSE_VERSION)) + { + Syn_Printf("ERROR: synapse API version mismatch: should be '" SYNAPSE_VERSION "', got '%s'\n", version); + return NULL; + } + g_pSynapseServer = pServer; + g_pSynapseServer->IncRef(); + Set_Syn_Printf(g_pSynapseServer->Get_Syn_Printf()); + + g_SynapseClient.AddAPI(PLUGIN_MAJOR, "HydraToolz", sizeof(_QERPluginTable)); + g_SynapseClient.AddAPI(RADIANT_MAJOR, NULL, sizeof(g_FuncTable), SYN_REQUIRE, &g_FuncTable); + g_SynapseClient.AddAPI(VFS_MAJOR, "wad", sizeof(g_FileSystemTable), SYN_REQUIRE, &g_FileSystemTable); + g_SynapseClient.AddAPI(ENTITY_MAJOR, NULL, sizeof(g_EntityTable), SYN_REQUIRE, &g_EntityTable); + return &g_SynapseClient; +} + +bool CSynapseClientHydraToolz::RequestAPI(APIDescriptor_t *pAPI) +{ + if (!strcmp(pAPI->major_name, PLUGIN_MAJOR)) + { + _QERPluginTable *pTable = static_cast<_QERPluginTable*>(pAPI->mpTable); + pTable->m_pfnQERPlug_Init = QERPlug_Init; + pTable->m_pfnQERPlug_GetName = QERPlug_GetName; + pTable->m_pfnQERPlug_GetCommandList = QERPlug_GetCommandList; + pTable->m_pfnQERPlug_Dispatch = QERPlug_Dispatch; + return true; + } + + Syn_Printf("ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo()); + return false; +} + +const char* CSynapseClientHydraToolz::GetInfo() +{ + return "HydraToolz plugin built " __DATE__ " " RADIANT_VERSION; +} diff --git a/contrib/hydratoolz/plugin.h b/contrib/hydratoolz/plugin.h new file mode 100644 index 00000000..70e3ea9b --- /dev/null +++ b/contrib/hydratoolz/plugin.h @@ -0,0 +1,58 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PLUGIN_H_ +#define _PLUGIN_H_ + +#include "mathlib.h" +#include +#include "qertypes.h" +#include + +#define USE_QERTABLE_DEFINE +#include "iscenegraph.h" +#include "qerplugin.h" + +#include "ifilesystem.h" +#define USE_ENTITYTABLE_DEFINE +#include "ientity.h" + +#include +#include +#include + +#include "iplugin.h" + +#include +#include "synapse.h" + +class CSynapseClientHydraToolz : public CSynapseClient +{ +public: + // CSynapseClient API + bool RequestAPI(APIDescriptor_t *pAPI); + const char* GetInfo(); + + CSynapseClientHydraToolz() { } + virtual ~CSynapseClientHydraToolz() { } +}; + +#endif // _PLUGIN_H_ diff --git a/contrib/prtview/.cvsignore b/contrib/prtview/.cvsignore new file mode 100644 index 00000000..66d25b10 --- /dev/null +++ b/contrib/prtview/.cvsignore @@ -0,0 +1,8 @@ +Debug +Release +*.d +*.plg +*.BAK +*.mak +*.ncb +*.opt diff --git a/contrib/prtview/AboutDialog.cpp b/contrib/prtview/AboutDialog.cpp new file mode 100644 index 00000000..1c6b735f --- /dev/null +++ b/contrib/prtview/AboutDialog.cpp @@ -0,0 +1,105 @@ +/* +PrtView plugin for GtkRadiant +Copyright (C) 2001 Geoffrey Dewan, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "AboutDialog.h" +#include +#include +#include "version.h" +#include "gtkutil/pointer.h" + +#include "prtview.h" +#include "ConfigDialog.h" + +static void dialog_button_callback (GtkWidget *widget, gpointer data) +{ + GtkWidget *parent; + int *loop, *ret; + + parent = gtk_widget_get_toplevel (widget); + loop = (int*)g_object_get_data (G_OBJECT (parent), "loop"); + ret = (int*)g_object_get_data (G_OBJECT (parent), "ret"); + + *loop = 0; + *ret = gpointer_to_int(data); +} + +static gint dialog_delete_callback (GtkWidget *widget, GdkEvent* event, gpointer data) +{ + int *loop; + + gtk_widget_hide (widget); + loop = (int*)g_object_get_data (G_OBJECT (widget), "loop"); + *loop = 0; + + return TRUE; +} + +void DoAboutDlg () +{ + GtkWidget *dlg, *hbox, *vbox, *button, *label; + int loop = 1, ret = IDCANCEL; + + dlg = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (dlg), "About Portal Viewer"); + gtk_signal_connect (GTK_OBJECT (dlg), "delete_event", + GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (dlg), "destroy", + GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + g_object_set_data (G_OBJECT (dlg), "loop", &loop); + g_object_set_data (G_OBJECT (dlg), "ret", &ret); + + hbox = gtk_hbox_new (FALSE, 10); + gtk_widget_show (hbox); + gtk_container_add (GTK_CONTAINER (dlg), hbox); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 10); + + label = gtk_label_new ("Version 1.000\n\n" + "Gtk port by Leonardo Zide\nleo@lokigames.com\n\n" + "Written by Geoffrey DeWan\ngdewan@prairienet.org\n\n" + "Built against GtkRadiant " RADIANT_VERSION "\n" + __DATE__ + ); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + + button = gtk_button_new_with_label ("OK"); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK)); + gtk_widget_set_usize (button, 60, -2); + + gtk_grab_add (dlg); + gtk_widget_show (dlg); + + while (loop) + gtk_main_iteration (); + + gtk_grab_remove (dlg); + gtk_widget_destroy (dlg); +} + + +///////////////////////////////////////////////////////////////////////////// +// CAboutDialog message handlers diff --git a/contrib/prtview/AboutDialog.h b/contrib/prtview/AboutDialog.h new file mode 100644 index 00000000..bf72885f --- /dev/null +++ b/contrib/prtview/AboutDialog.h @@ -0,0 +1,25 @@ +/* +PrtView plugin for GtkRadiant +Copyright (C) 2001 Geoffrey Dewan, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(INCLUDED_ABOUTDIALOG_H) +#define INCLUDED_ABOUTDIALOG_H + +void DoAboutDlg (); + +#endif diff --git a/contrib/prtview/ConfigDialog.cpp b/contrib/prtview/ConfigDialog.cpp new file mode 100644 index 00000000..8acf47f7 --- /dev/null +++ b/contrib/prtview/ConfigDialog.cpp @@ -0,0 +1,526 @@ +/* +PrtView plugin for GtkRadiant +Copyright (C) 2001 Geoffrey Dewan, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "ConfigDialog.h" +#include +#include +#include "gtkutil/pointer.h" + +#include "iscenegraph.h" + +#include "prtview.h" +#include "portals.h" + +static void dialog_button_callback (GtkWidget *widget, gpointer data) +{ + GtkWidget *parent; + int *loop, *ret; + + parent = gtk_widget_get_toplevel (widget); + loop = (int*)g_object_get_data (G_OBJECT (parent), "loop"); + ret = (int*)g_object_get_data (G_OBJECT (parent), "ret"); + + *loop = 0; + *ret = gpointer_to_int(data); +} + +static gint dialog_delete_callback (GtkWidget *widget, GdkEvent* event, gpointer data) +{ + int *loop; + + gtk_widget_hide (widget); + loop = (int*)g_object_get_data (G_OBJECT (widget), "loop"); + *loop = 0; + + return TRUE; +} + +// ============================================================================= +// Color selection dialog + +static int DoColor (PackedColour *c) +{ + GtkWidget* dlg; + double clr[4]; + int loop = 1, ret = IDCANCEL; + + clr[0] = ((double)GetRValue (*c)) / 255.0; + clr[1] = ((double)GetGValue (*c)) / 255.0; + clr[2] = ((double)GetBValue (*c)) / 255.0; + + dlg = gtk_color_selection_dialog_new ("Choose Color"); + gtk_color_selection_set_color (GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG (dlg)->colorsel), clr); + gtk_signal_connect (GTK_OBJECT (dlg), "delete_event", + GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (dlg), "destroy", + GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + gtk_signal_connect (GTK_OBJECT (GTK_COLOR_SELECTION_DIALOG (dlg)->ok_button), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK)); + gtk_signal_connect (GTK_OBJECT (GTK_COLOR_SELECTION_DIALOG (dlg)->cancel_button), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDCANCEL)); + g_object_set_data (G_OBJECT (dlg), "loop", &loop); + g_object_set_data (G_OBJECT (dlg), "ret", &ret); + + gtk_widget_show(dlg); + gtk_grab_add(dlg); + + while (loop) + gtk_main_iteration (); + + gtk_color_selection_get_color (GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG (dlg)->colorsel), clr); + + gtk_grab_remove (dlg); + gtk_widget_destroy (dlg); + + if (ret == IDOK) + { + *c = RGB (clr[0]*255, clr[1]*255, clr[2]*255); + } + + return ret; +} + +static void Set2DText (GtkWidget* label) +{ + char s[40]; + + sprintf(s, "Line Width = %6.3f", portals.width_2d * 0.5f); + + gtk_label_set_text (GTK_LABEL (label), s); +} + +static void Set3DText (GtkWidget* label) +{ + char s[40]; + + sprintf(s, "Line Width = %6.3f", portals.width_3d * 0.5f); + + gtk_label_set_text (GTK_LABEL (label), s); +} + +static void Set3DTransText (GtkWidget* label) +{ + char s[40]; + + sprintf(s, "Polygon transparency = %d%%", (int)portals.trans_3d); + + gtk_label_set_text (GTK_LABEL (label), s); +} + +static void SetClipText (GtkWidget* label) +{ + char s[40]; + + sprintf(s, "Cubic clip range = %d", (int)portals.clip_range * 64); + + gtk_label_set_text (GTK_LABEL (label), s); +} + +static void OnScroll2d (GtkAdjustment *adj, gpointer data) +{ + portals.width_2d = static_cast(adj->value); + Set2DText (GTK_WIDGET (data)); + + Portals_shadersChanged(); + SceneChangeNotify(); +} + +static void OnScroll3d (GtkAdjustment *adj, gpointer data) +{ + portals.width_3d = static_cast(adj->value); + Set3DText (GTK_WIDGET (data)); + + SceneChangeNotify(); +} + +static void OnScrollTrans (GtkAdjustment *adj, gpointer data) +{ + portals.trans_3d = static_cast(adj->value); + Set3DTransText (GTK_WIDGET (data)); + + SceneChangeNotify(); +} + +static void OnScrollClip (GtkAdjustment *adj, gpointer data) +{ + portals.clip_range = static_cast(adj->value); + SetClipText (GTK_WIDGET (data)); + + SceneChangeNotify(); +} + +static void OnAntiAlias2d (GtkWidget *widget, gpointer data) +{ + portals.aa_2d = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)) ? true : false; + + Portals_shadersChanged(); + + SceneChangeNotify(); +} + +static void OnConfig2d (GtkWidget *widget, gpointer data) +{ + portals.show_2d = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)) ? true : false; + + SceneChangeNotify(); +} + +static void OnColor2d (GtkWidget *widget, gpointer data) +{ + if (DoColor (&portals.color_2d) == IDOK) + { + Portals_shadersChanged(); + + SceneChangeNotify(); + } +} + +static void OnConfig3d (GtkWidget *widget, gpointer data) +{ + portals.show_3d = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)) ? true : false; + + SceneChangeNotify(); +} + + +static void OnAntiAlias3d (GtkWidget *widget, gpointer data) +{ + portals.aa_3d = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)) ? true : false; + + Portals_shadersChanged(); + SceneChangeNotify(); +} + +static void OnColor3d (GtkWidget *widget, gpointer data) +{ + if (DoColor (&portals.color_3d) == IDOK) + { + Portals_shadersChanged(); + + SceneChangeNotify(); + } +} + +static void OnColorFog (GtkWidget *widget, gpointer data) +{ + if (DoColor (&portals.color_fog) == IDOK) + { + Portals_shadersChanged(); + + SceneChangeNotify(); + } +} + +static void OnFog (GtkWidget *widget, gpointer data) +{ + portals.fog = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)) ? true : false; + + Portals_shadersChanged(); + SceneChangeNotify(); +} + +static void OnSelchangeZbuffer (GtkWidget *widget, gpointer data) +{ + portals.zbuffer = gpointer_to_int(data); + + Portals_shadersChanged(); + SceneChangeNotify(); +} + +static void OnPoly (GtkWidget *widget, gpointer data) +{ + portals.polygons = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + SceneChangeNotify(); +} + +static void OnLines (GtkWidget *widget, gpointer data) +{ + portals.lines = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + SceneChangeNotify(); +} + +static void OnClip (GtkWidget *widget, gpointer data) +{ + portals.clip = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)) ? true : false; + + SceneChangeNotify(); +} + +void DoConfigDialog () +{ + GtkWidget *dlg, *hbox, *vbox, *vbox2, *button, *table, *frame; + GtkWidget *lw3slider, *lw3label, *lw2slider, *lw2label, *zlist, *menu, *item; + GtkWidget *aa2check, *aa3check, *depthcheck, *linescheck, *polyscheck; + GtkWidget *transslider, *translabel, *clipslider, *cliplabel; + GtkWidget *show2check, *show3check, *portalcheck; + int loop = 1, ret = IDCANCEL; + GtkObject *adj; + + dlg = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (dlg), "Portal Viewer Configuration"); + gtk_signal_connect (GTK_OBJECT (dlg), "delete_event", + GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (dlg), "destroy", + GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + g_object_set_data (G_OBJECT (dlg), "loop", &loop); + g_object_set_data (G_OBJECT (dlg), "ret", &ret); + + vbox = gtk_vbox_new (FALSE, 5); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (dlg), vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + frame = gtk_frame_new ("3D View"); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0); + + vbox2 = gtk_vbox_new (FALSE, 5); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (frame), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 5); + + hbox = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox2), hbox, TRUE, TRUE, 0); + + adj = gtk_adjustment_new (portals.width_3d, 2, 40, 1, 1, 1); + lw3slider = gtk_hscale_new (GTK_ADJUSTMENT (adj)); + gtk_widget_show (lw3slider); + gtk_box_pack_start (GTK_BOX (hbox), lw3slider, TRUE, TRUE, 0); + gtk_scale_set_draw_value (GTK_SCALE (lw3slider), FALSE); + + lw3label = gtk_label_new (""); + gtk_widget_show (lw3label); + gtk_box_pack_start (GTK_BOX (hbox), lw3label, FALSE, TRUE, 0); + gtk_signal_connect (adj, "value_changed", GTK_SIGNAL_FUNC (OnScroll3d), lw3label); + + table = gtk_table_new (2, 4, FALSE); + gtk_widget_show (table); + gtk_box_pack_start (GTK_BOX (vbox2), table, TRUE, TRUE, 0); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + button = gtk_button_new_with_label ("Color"); + gtk_widget_show (button); + gtk_table_attach (GTK_TABLE (table), button, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (OnColor3d), NULL); + + button = gtk_button_new_with_label ("Depth Color"); + gtk_widget_show (button); + gtk_table_attach (GTK_TABLE (table), button, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (OnColorFog), NULL); + + aa3check = gtk_check_button_new_with_label ("Anti-Alias (May not work on some video cards)"); + gtk_widget_show (aa3check); + gtk_table_attach (GTK_TABLE (table), aa3check, 1, 4, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_signal_connect (GTK_OBJECT (aa3check), "toggled", GTK_SIGNAL_FUNC (OnAntiAlias3d), NULL); + + depthcheck = gtk_check_button_new_with_label ("Depth Cue"); + gtk_widget_show (depthcheck); + gtk_table_attach (GTK_TABLE (table), depthcheck, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_signal_connect (GTK_OBJECT (depthcheck), "toggled", GTK_SIGNAL_FUNC (OnFog), NULL); + + linescheck = gtk_check_button_new_with_label ("Lines"); + gtk_widget_show (linescheck); + gtk_table_attach (GTK_TABLE (table), linescheck, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_signal_connect (GTK_OBJECT (linescheck), "toggled", GTK_SIGNAL_FUNC (OnLines), NULL); + + polyscheck = gtk_check_button_new_with_label ("Polygons"); + gtk_widget_show (polyscheck); + gtk_table_attach (GTK_TABLE (table), polyscheck, 3, 4, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_signal_connect (GTK_OBJECT (polyscheck), "toggled", GTK_SIGNAL_FUNC (OnPoly), NULL); + + zlist = gtk_option_menu_new (); + gtk_widget_show (zlist); + gtk_box_pack_start (GTK_BOX (vbox2), zlist, TRUE, FALSE, 0); + + menu = gtk_menu_new (); + gtk_widget_show (menu); + gtk_option_menu_set_menu (GTK_OPTION_MENU (zlist), menu); + + item = gtk_menu_item_new_with_label ("Z-Buffer Test and Write (recommended for solid or no polygons)"); + gtk_widget_show (item); + gtk_signal_connect (GTK_OBJECT (item), "activate", + GTK_SIGNAL_FUNC (OnSelchangeZbuffer), GINT_TO_POINTER (0)); + gtk_menu_append (GTK_MENU (menu), item); + + item = gtk_menu_item_new_with_label ("Z-Buffer Test Only (recommended for transparent polygons)"); + gtk_widget_show (item); + gtk_signal_connect (GTK_OBJECT (item), "activate", + GTK_SIGNAL_FUNC (OnSelchangeZbuffer), GINT_TO_POINTER (1)); + gtk_menu_append (GTK_MENU (menu), item); + + item = gtk_menu_item_new_with_label ("Z-Buffer Off"); + gtk_widget_show (item); + gtk_signal_connect (GTK_OBJECT (item), "activate", + GTK_SIGNAL_FUNC (OnSelchangeZbuffer), GINT_TO_POINTER (2)); + gtk_menu_append (GTK_MENU (menu), item); + + table = gtk_table_new (2, 2, FALSE); + gtk_widget_show (table); + gtk_box_pack_start (GTK_BOX (vbox2), table, TRUE, TRUE, 0); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + adj = gtk_adjustment_new (portals.trans_3d, 0, 100, 1, 1, 1); + transslider = gtk_hscale_new (GTK_ADJUSTMENT (adj)); + gtk_widget_show (transslider); + gtk_table_attach (GTK_TABLE (table), transslider, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_scale_set_draw_value (GTK_SCALE (transslider), FALSE); + + translabel = gtk_label_new (""); + gtk_widget_show (translabel); + gtk_table_attach (GTK_TABLE (table), translabel, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (translabel), 0.0, 0.0); + gtk_signal_connect (adj, "value_changed", GTK_SIGNAL_FUNC (OnScrollTrans), translabel); + + adj = gtk_adjustment_new (portals.clip_range, 1, 128, 1, 1, 1); + clipslider = gtk_hscale_new (GTK_ADJUSTMENT (adj)); + gtk_widget_show (clipslider); + gtk_table_attach (GTK_TABLE (table), clipslider, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_scale_set_draw_value (GTK_SCALE (clipslider), FALSE); + + cliplabel = gtk_label_new (""); + gtk_widget_show (cliplabel); + gtk_table_attach (GTK_TABLE (table), cliplabel, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (cliplabel), 0.0, 0.0); + gtk_signal_connect (adj, "value_changed", GTK_SIGNAL_FUNC (OnScrollClip), cliplabel); + + hbox = gtk_hbox_new (TRUE, 5); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox2), hbox, TRUE, FALSE, 0); + + show3check = gtk_check_button_new_with_label ("Show"); + gtk_widget_show (show3check); + gtk_box_pack_start (GTK_BOX (hbox), show3check, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (show3check), "toggled", GTK_SIGNAL_FUNC (OnConfig3d), NULL); + + portalcheck = gtk_check_button_new_with_label ("Portal cubic clipper"); + gtk_widget_show (portalcheck); + gtk_box_pack_start (GTK_BOX (hbox), portalcheck, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (portalcheck), "toggled", GTK_SIGNAL_FUNC (OnClip), NULL); + + frame = gtk_frame_new ("2D View"); + gtk_widget_show (frame); + gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0); + + vbox2 = gtk_vbox_new (FALSE, 5); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (frame), vbox2); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 5); + + hbox = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox2), hbox, TRUE, FALSE, 0); + + adj = gtk_adjustment_new (portals.width_2d, 2, 40, 1, 1, 1); + lw2slider = gtk_hscale_new (GTK_ADJUSTMENT (adj)); + gtk_widget_show (lw2slider); + gtk_box_pack_start (GTK_BOX (hbox), lw2slider, TRUE, TRUE, 0); + gtk_scale_set_draw_value (GTK_SCALE (lw2slider), FALSE); + + lw2label = gtk_label_new (""); + gtk_widget_show (lw2label); + gtk_box_pack_start (GTK_BOX (hbox), lw2label, FALSE, TRUE, 0); + gtk_signal_connect (adj, "value_changed", GTK_SIGNAL_FUNC (OnScroll2d), lw2label); + + hbox = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox2), hbox, TRUE, FALSE, 0); + + button = gtk_button_new_with_label ("Color"); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (OnColor2d), NULL); + gtk_widget_set_usize (button, 60, -2); + + aa2check = gtk_check_button_new_with_label ("Anti-Alias (May not work on some video cards)"); + gtk_widget_show (aa2check); + gtk_box_pack_start (GTK_BOX (hbox), aa2check, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (aa2check), "toggled", GTK_SIGNAL_FUNC (OnAntiAlias2d), NULL); + + hbox = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox2), hbox, TRUE, FALSE, 0); + + show2check = gtk_check_button_new_with_label ("Show"); + gtk_widget_show (show2check); + gtk_box_pack_start (GTK_BOX (hbox), show2check, FALSE, FALSE, 0); + gtk_signal_connect (GTK_OBJECT (show2check), "toggled", GTK_SIGNAL_FUNC (OnConfig2d), NULL); + + hbox = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + button = gtk_button_new_with_label ("OK"); + gtk_widget_show (button); + gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK)); + gtk_widget_set_usize (button, 60, -2); + + // initialize dialog + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (show2check), portals.show_2d); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (aa2check), portals.aa_2d); + Set2DText (lw2label); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (show3check), portals.show_3d); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (depthcheck), portals.fog); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (polyscheck), portals.polygons); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (linescheck), portals.lines); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (aa3check), portals.aa_3d); + gtk_option_menu_set_history (GTK_OPTION_MENU (zlist), portals.zbuffer); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (portalcheck), portals.clip); + + Set3DText (lw3label); + Set3DTransText (translabel); + SetClipText (cliplabel); + + gtk_grab_add (dlg); + gtk_widget_show (dlg); + + while (loop) + gtk_main_iteration (); + + gtk_grab_remove (dlg); + gtk_widget_destroy (dlg); +} + diff --git a/contrib/prtview/ConfigDialog.h b/contrib/prtview/ConfigDialog.h new file mode 100644 index 00000000..ef35db06 --- /dev/null +++ b/contrib/prtview/ConfigDialog.h @@ -0,0 +1,25 @@ +/* +PrtView plugin for GtkRadiant +Copyright (C) 2001 Geoffrey Dewan, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(INCLUDED_CONFIGDIALOG_H) +#define INCLUDED_CONFIGDIALOG_H + +void DoConfigDialog (); + +#endif diff --git a/contrib/prtview/LoadPortalFileDialog.cpp b/contrib/prtview/LoadPortalFileDialog.cpp new file mode 100644 index 00000000..d9984a0b --- /dev/null +++ b/contrib/prtview/LoadPortalFileDialog.cpp @@ -0,0 +1,202 @@ +/* +PrtView plugin for GtkRadiant +Copyright (C) 2001 Geoffrey Dewan, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +// LoadPortalFileDialog.cpp : implementation file +// + +#include "LoadPortalFileDialog.h" + +#include +#include +#include "stream/stringstream.h" +#include "convert.h" +#include "gtkutil/pointer.h" + +#include "qerplugin.h" + +#include "prtview.h" +#include "portals.h" + +static void dialog_button_callback (GtkWidget *widget, gpointer data) +{ + GtkWidget *parent; + int *loop, *ret; + + parent = gtk_widget_get_toplevel (widget); + loop = (int*)g_object_get_data (G_OBJECT (parent), "loop"); + ret = (int*)g_object_get_data (G_OBJECT (parent), "ret"); + + *loop = 0; + *ret = gpointer_to_int(data); +} + +static gint dialog_delete_callback (GtkWidget *widget, GdkEvent* event, gpointer data) +{ + int *loop; + + gtk_widget_hide (widget); + loop = (int*)g_object_get_data (G_OBJECT (widget), "loop"); + *loop = 0; + + return TRUE; +} + +static void file_sel_callback (GtkWidget *widget, gpointer data) +{ + GtkWidget *parent; + int *loop; + char **filename; + + parent = gtk_widget_get_toplevel (widget); + loop = (int*)g_object_get_data (G_OBJECT (parent), "loop"); + filename = (char**)g_object_get_data (G_OBJECT (parent), "filename"); + + *loop = 0; + if (gpointer_to_int(data) == IDOK) + *filename = g_strdup (gtk_file_selection_get_filename (GTK_FILE_SELECTION (parent))); +} + +static void change_clicked (GtkWidget *widget, gpointer data) +{ + GtkWidget* file_sel; + char* filename = NULL; + int loop = 1; + + file_sel = gtk_file_selection_new ("Locate portal (.prt) file"); + gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (file_sel)->ok_button), "clicked", + GTK_SIGNAL_FUNC (file_sel_callback), GINT_TO_POINTER (IDOK)); + gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (file_sel)->cancel_button), "clicked", + GTK_SIGNAL_FUNC (file_sel_callback), GINT_TO_POINTER (IDCANCEL)); + gtk_signal_connect (GTK_OBJECT (file_sel), "delete_event", + GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_file_selection_hide_fileop_buttons (GTK_FILE_SELECTION (file_sel)); + + g_object_set_data (G_OBJECT (file_sel), "loop", &loop); + g_object_set_data (G_OBJECT (file_sel), "filename", &filename); + gtk_file_selection_set_filename (GTK_FILE_SELECTION (file_sel), portals.fn); + + gtk_grab_add (file_sel); + gtk_widget_show (file_sel); + + while (loop) + gtk_main_iteration (); + + gtk_grab_remove (file_sel); + gtk_widget_destroy (file_sel); + + if (filename != NULL) + { + strcpy (portals.fn, filename); + gtk_entry_set_text (GTK_ENTRY (data), filename); + g_free (filename); + } +} + +int DoLoadPortalFileDialog () +{ + GtkWidget *dlg, *vbox, *hbox, *button, *entry, *check2d, *check3d; + int loop = 1, ret = IDCANCEL; + + dlg = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (dlg), "Load .prt"); + gtk_signal_connect (GTK_OBJECT (dlg), "delete_event", + GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (dlg), "destroy", + GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + g_object_set_data (G_OBJECT (dlg), "loop", &loop); + g_object_set_data (G_OBJECT (dlg), "ret", &ret); + + vbox = gtk_vbox_new (FALSE, 5); + gtk_widget_show (vbox); + gtk_container_add (GTK_CONTAINER (dlg), vbox); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + entry = gtk_entry_new (); + gtk_widget_show (entry); + gtk_entry_set_editable (GTK_ENTRY (entry), FALSE); + gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, FALSE, 0); + + hbox = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + check3d = gtk_check_button_new_with_label ("Show 3D"); + gtk_widget_show (check3d); + gtk_box_pack_start (GTK_BOX (hbox), check3d, FALSE, FALSE, 0); + + check2d = gtk_check_button_new_with_label ("Show 2D"); + gtk_widget_show (check2d); + gtk_box_pack_start (GTK_BOX (hbox), check2d, FALSE, FALSE, 0); + + button = gtk_button_new_with_label ("Change"); + gtk_widget_show (button); + gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (change_clicked), entry); + gtk_widget_set_usize (button, 60, -2); + + hbox = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + button = gtk_button_new_with_label ("Cancel"); + gtk_widget_show (button); + gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDCANCEL)); + gtk_widget_set_usize (button, 60, -2); + + button = gtk_button_new_with_label ("OK"); + gtk_widget_show (button); + gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK)); + gtk_widget_set_usize (button, 60, -2); + + strcpy (portals.fn, GlobalRadiant().getMapName()); + char* fn = strrchr (portals.fn, '.'); + if (fn != NULL) + { + strcpy(fn, ".prt"); + } + + StringOutputStream value(256); + value << ConvertLocaleToUTF8(portals.fn); + gtk_entry_set_text (GTK_ENTRY (entry), value.c_str()); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check2d), portals.show_2d); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check3d), portals.show_3d); + + gtk_grab_add (dlg); + gtk_widget_show (dlg); + + while (loop) + gtk_main_iteration (); + + if (ret == IDOK) + { + portals.Purge(); + + portals.show_3d = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check3d)) ? true : false; + portals.show_2d = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check2d)) ? true : false; + } + + gtk_grab_remove (dlg); + gtk_widget_destroy (dlg); + + return ret; +} diff --git a/contrib/prtview/LoadPortalFileDialog.h b/contrib/prtview/LoadPortalFileDialog.h new file mode 100644 index 00000000..a4a40c6c --- /dev/null +++ b/contrib/prtview/LoadPortalFileDialog.h @@ -0,0 +1,26 @@ +/* +PrtView plugin for GtkRadiant +Copyright (C) 2001 Geoffrey Dewan, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(INCLUDED_LOADPORTALFILEDIALOG_H) +#define INCLUDED_LOADPORTALFILEDIALOG_H + +int DoLoadPortalFileDialog (); + +#endif + diff --git a/contrib/prtview/PrtView.aps b/contrib/prtview/PrtView.aps new file mode 100644 index 0000000000000000000000000000000000000000..d86f060791a3bb3c138bcadf57243ffbdec5f624 GIT binary patch literal 21916 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 literal 0 HcmV?d00001 diff --git a/contrib/prtview/PrtView.def b/contrib/prtview/PrtView.def new file mode 100644 index 00000000..7c18cf88 --- /dev/null +++ b/contrib/prtview/PrtView.def @@ -0,0 +1,7 @@ +; PrtView.def : Declares the module parameters for the DLL. + +LIBRARY "PrtView" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/contrib/prtview/PrtView.txt b/contrib/prtview/PrtView.txt new file mode 100644 index 00000000..50228e02 --- /dev/null +++ b/contrib/prtview/PrtView.txt @@ -0,0 +1,12 @@ +Put PrtView.dll in the Q3Radiant plugins directory. + +This program is pretty self explanitary, but point needs to +be mentioned. In the configuration menu for 3D view options, +the lines and polygons flags are tri-state. In the third state, +the lines or polygons will only be drawn if the have the +hint flag set. Older version of q3map will not set this flag +and the hint shader may have to be modified to set it. As of +this writing, I do not know all the details. + +Geoffrey DeWan +gdewan@prairienet.org \ No newline at end of file diff --git a/contrib/prtview/PrtView.vcproj b/contrib/prtview/PrtView.vcproj new file mode 100644 index 00000000..0c896bc9 --- /dev/null +++ b/contrib/prtview/PrtView.vcproj @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/prtview/portals.cpp b/contrib/prtview/portals.cpp new file mode 100644 index 00000000..d3bef1b6 --- /dev/null +++ b/contrib/prtview/portals.cpp @@ -0,0 +1,652 @@ +/* +PrtView plugin for GtkRadiant +Copyright (C) 2001 Geoffrey Dewan, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "portals.h" +#include +#include +#ifndef __APPLE__ +#include +#endif +#include + +#include "iglrender.h" +#include "cullable.h" + +#include "prtview.h" + +#define LINE_BUF 1000 + +CPortals portals; +CPortalsRender render; + +int compare( const void *arg1, const void *arg2 ) +{ + + if(portals.portal[*((int *)arg1)].dist > portals.portal[*((int *)arg2)].dist) + return -1; + else if(portals.portal[*((int *)arg1)].dist < portals.portal[*((int *)arg2)].dist) + return 1; + + return 0; +} + + +CBspPortal::CBspPortal() +{ + memset(this, 0, sizeof(CBspPortal)); +} + +CBspPortal::~CBspPortal() +{ + delete[] point; + delete[] inner_point; +} + +bool CBspPortal::Build(char *def) +{ + char *c = def; + unsigned int n; + int dummy1, dummy2; + int res_cnt, i; + + if(portals.hint_flags) + { + res_cnt = sscanf(def, "%u %d %d %d", &point_count, &dummy1, &dummy2, (int *)&hint); + } + else + { + sscanf(def, "%u", &point_count); + hint = false; + } + + if(point_count < 3 || (portals.hint_flags && res_cnt < 4)) + return false; + + point = new CBspPoint[point_count]; + inner_point = new CBspPoint[point_count]; + + for(n = 0; n < point_count; n++) + { + for(; *c != 0 && *c != '('; c++); + + if(*c == 0) + return false; + + c++; + + sscanf(c, "%f %f %f", point[n].p, point[n].p+1, point[n].p+2); + + center.p[0] += point[n].p[0]; + center.p[1] += point[n].p[1]; + center.p[2] += point[n].p[2]; + + if(n == 0) + { + for(i = 0; i < 3; i++) + { + min[i] = point[n].p[i]; + max[i] = point[n].p[i]; + } + } + else + { + for(i = 0; i < 3; i++) + { + if(min[i] > point[n].p[i]) + min[i] = point[n].p[i]; + if(max[i] < point[n].p[i]) + max[i] = point[n].p[i]; + } + } + } + + center.p[0] /= (float)point_count; + center.p[1] /= (float)point_count; + center.p[2] /= (float)point_count; + + for(n = 0; n < point_count; n++) + { + inner_point[n].p[0] = (0.01f * center.p[0]) + (0.99f * point[n].p[0]); + inner_point[n].p[1] = (0.01f * center.p[1]) + (0.99f * point[n].p[1]); + inner_point[n].p[2] = (0.01f * center.p[2]) + (0.99f * point[n].p[2]); + } + + fp_color_random[0] = (float)(rand() & 0xff) / 255.0f; + fp_color_random[1] = (float)(rand() & 0xff) / 255.0f; + fp_color_random[2] = (float)(rand() & 0xff) / 255.0f; + fp_color_random[3] = 1.0f; + + return true; +} + +CPortals::CPortals() +{ + memset(this, 0, sizeof(CPortals)); +} + +CPortals::~CPortals() +{ + Purge(); +} + +void CPortals::Purge() +{ + delete[] portal; + delete[] portal_sort; + portal = NULL; + portal_sort = NULL; + portal_count = 0; + + /* + delete[] node; + node = NULL; + node_count = 0; + */ +} + +void CPortals::Load() +{ + char buf[LINE_BUF+1]; + + memset(buf, 0, LINE_BUF + 1); + + Purge(); + + globalOutputStream() << MSG_PREFIX "Loading portal file " << fn << ".\n"; + + FILE *in; + + in = fopen(fn, "rt"); + + if(in == NULL) + { + globalOutputStream() << " ERROR - could not open file.\n"; + + return; + } + + if(!fgets(buf, LINE_BUF, in)) + { + fclose(in); + + globalOutputStream() << " ERROR - File ended prematurely.\n"; + + return; + } + + if(strncmp("PRT1", buf, 4) != 0) + { + fclose(in); + + globalOutputStream() << " ERROR - File header indicates wrong file type (should be \"PRT1\").\n"; + + return; + } + + if(!fgets(buf, LINE_BUF, in)) + { + fclose(in); + + globalOutputStream() << " ERROR - File ended prematurely.\n"; + + return; + } + + sscanf(buf, "%u", &node_count); +/* + if(node_count > 0xFFFF) + { + fclose(in); + + node_count = 0; + + globalOutputStream() << " ERROR - Extreme number of nodes, aborting.\n"; + + return; + } + */ + + if(!fgets(buf, LINE_BUF, in)) + { + fclose(in); + + node_count = 0; + + globalOutputStream() << " ERROR - File ended prematurely.\n"; + + return; + } + + sscanf(buf, "%u", &portal_count); + + if(portal_count > 0xFFFF) + { + fclose(in); + + portal_count = 0; + node_count = 0; + + globalOutputStream() << " ERROR - Extreme number of portals, aborting.\n"; + + return; + } + + if(portal_count == 0) + { + fclose(in); + + portal_count = 0; + node_count = 0; + + globalOutputStream() << " ERROR - number of portals equals 0, aborting.\n"; + + return; + } + +// node = new CBspNode[node_count]; + portal = new CBspPortal[portal_count]; + portal_sort = new int[portal_count]; + + unsigned int n; + bool first = true; + unsigned test_vals_1, test_vals_2; + + hint_flags = false; + + for(n = 0; n < portal_count; ) + { + if(!fgets(buf, LINE_BUF, in)) + { + fclose(in); + + Purge(); + + globalOutputStream() << " ERROR - Could not find information for portal number " << n + 1 << " of " << portal_count << ".\n"; + + return; + } + + if(!portal[n].Build(buf)) + { + if(first && sscanf(buf, "%d %d", &test_vals_1, &test_vals_2) == 1) // skip additional counts of later data, not needed + { + // We can count on hint flags being in the file + hint_flags = true; + continue; + } + + first = false; + + fclose(in); + + Purge(); + + globalOutputStream() << " ERROR - Information for portal number " << n + 1 << " of " << portal_count << " is not formatted correctly.\n"; + + return; + } + + n++; + } + + fclose(in); + + globalOutputStream() << " " << node_count << " portals read in.\n"; +} + +#include "math/matrix.h" + +const char* g_state_solid = "$plugins/prtview/solid"; +const char* g_state_solid_outline = "$plugins/prtview/solid_outline"; +const char* g_state_wireframe = "$plugins/prtview/wireframe"; +Shader* g_shader_solid = 0; +Shader* g_shader_solid_outline = 0; +Shader* g_shader_wireframe = 0; + +void Portals_constructShaders() +{ + OpenGLState state; + GlobalOpenGLStateLibrary().getDefaultState(state); + state.m_state = RENDER_COLOURWRITE|RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortOverlayFirst; + state.m_linewidth = portals.width_2d * 0.5f; + state.m_colour[0] = portals.fp_color_2d[0]; + state.m_colour[1] = portals.fp_color_2d[1]; + state.m_colour[2] = portals.fp_color_2d[2]; + state.m_colour[3] = portals.fp_color_2d[3]; + if(portals.aa_2d) + { + state.m_state |= RENDER_BLEND|RENDER_LINESMOOTH; + } + GlobalOpenGLStateLibrary().insert(g_state_wireframe, state); + + GlobalOpenGLStateLibrary().getDefaultState(state); + state.m_state = RENDER_FILL|RENDER_BLEND|RENDER_COLOURWRITE|RENDER_COLOURCHANGE|RENDER_SMOOTH; + + if(portals.aa_3d) + { + state.m_state |= RENDER_POLYGONSMOOTH; + } + + switch(portals.zbuffer) + { + case 1: + state.m_state |= RENDER_DEPTHTEST; + break; + case 2: + break; + default: + state.m_state |= RENDER_DEPTHTEST; + state.m_state |= RENDER_DEPTHWRITE; + } + + if(portals.fog) + { + state.m_state |= RENDER_FOG; + + state.m_fog.mode = GL_EXP; + state.m_fog.density = 0.001f; + state.m_fog.start = 10.0f; + state.m_fog.end = 10000.0f; + state.m_fog.index = 0; + state.m_fog.colour[0] = portals.fp_color_fog[0]; + state.m_fog.colour[1] = portals.fp_color_fog[1]; + state.m_fog.colour[2] = portals.fp_color_fog[2]; + state.m_fog.colour[3] = portals.fp_color_fog[3]; + } + + GlobalOpenGLStateLibrary().insert(g_state_solid, state); + + GlobalOpenGLStateLibrary().getDefaultState(state); + state.m_state = RENDER_COLOURWRITE|RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortOverlayFirst; + state.m_linewidth = portals.width_3d * 0.5f; + state.m_colour[0] = portals.fp_color_3d[0]; + state.m_colour[1] = portals.fp_color_3d[1]; + state.m_colour[2] = portals.fp_color_3d[2]; + state.m_colour[3] = portals.fp_color_3d[3]; + + if(portals.aa_3d) + { + state.m_state |= RENDER_LINESMOOTH; + } + + switch(portals.zbuffer) + { + case 1: + state.m_state |= RENDER_DEPTHTEST; + break; + case 2: + break; + default: + state.m_state |= RENDER_DEPTHTEST; + state.m_state |= RENDER_DEPTHWRITE; + } + + if(portals.fog) + { + state.m_state |= RENDER_FOG; + + state.m_fog.mode = GL_EXP; + state.m_fog.density = 0.001f; + state.m_fog.start = 10.0f; + state.m_fog.end = 10000.0f; + state.m_fog.index = 0; + state.m_fog.colour[0] = portals.fp_color_fog[0]; + state.m_fog.colour[1] = portals.fp_color_fog[1]; + state.m_fog.colour[2] = portals.fp_color_fog[2]; + state.m_fog.colour[3] = portals.fp_color_fog[3]; + } + + GlobalOpenGLStateLibrary().insert(g_state_solid_outline, state); + + g_shader_solid = GlobalShaderCache().capture(g_state_solid); + g_shader_solid_outline = GlobalShaderCache().capture(g_state_solid_outline); + g_shader_wireframe = GlobalShaderCache().capture(g_state_wireframe); +} + +void Portals_destroyShaders() +{ + GlobalShaderCache().release(g_state_solid); + GlobalShaderCache().release(g_state_solid_outline); + GlobalShaderCache().release(g_state_wireframe); + GlobalOpenGLStateLibrary().erase(g_state_solid); + GlobalOpenGLStateLibrary().erase(g_state_solid_outline); + GlobalOpenGLStateLibrary().erase(g_state_wireframe); +} + +void Portals_shadersChanged() +{ + Portals_destroyShaders(); + portals.FixColors(); + Portals_constructShaders(); +} + +void CPortals::FixColors() +{ + fp_color_2d[0] = (float)GetRValue(color_2d) / 255.0f; + fp_color_2d[1] = (float)GetGValue(color_2d) / 255.0f; + fp_color_2d[2] = (float)GetBValue(color_2d) / 255.0f; + fp_color_2d[3] = 1.0f; + + fp_color_3d[0] = (float)GetRValue(color_3d) / 255.0f; + fp_color_3d[1] = (float)GetGValue(color_3d) / 255.0f; + fp_color_3d[2] = (float)GetBValue(color_3d) / 255.0f; + fp_color_3d[3] = 1.0f; + + fp_color_fog[0] = 0.0f;//(float)GetRValue(color_fog) / 255.0f; + fp_color_fog[1] = 0.0f;//(float)GetGValue(color_fog) / 255.0f; + fp_color_fog[2] = 0.0f;//(float)GetBValue(color_fog) / 255.0f; + fp_color_fog[3] = 1.0f; +} + +void CPortalsRender::renderWireframe(Renderer& renderer, const VolumeTest& volume) const +{ + if(!portals.show_2d || portals.portal_count < 1) + return; + + renderer.SetState(g_shader_wireframe, Renderer::eWireframeOnly); + + renderer.addRenderable(m_drawWireframe, g_matrix4_identity); +} + +void CPortalsDrawWireframe::render(RenderStateFlags state) const +{ + unsigned int n, p; + + for(n = 0; n < portals.portal_count; n++) + { + glBegin(GL_LINE_LOOP); + + for(p = 0; p < portals.portal[n].point_count; p++) + glVertex3fv(portals.portal[n].point[p].p); + + glEnd(); + } +} + +CubicClipVolume calculateCubicClipVolume(const Matrix4& viewproj) +{ + CubicClipVolume clip; + clip.cam = vector4_projected( + matrix4_transformed_vector4( + matrix4_full_inverse(viewproj), + Vector4(0, 0, -1, 1) + ) + ); + clip.min[0] = clip.cam[0] + (portals.clip_range * 64.0f); + clip.min[1] = clip.cam[1] + (portals.clip_range * 64.0f); + clip.min[2] = clip.cam[2] + (portals.clip_range * 64.0f); + clip.max[0] = clip.cam[0] - (portals.clip_range * 64.0f); + clip.max[1] = clip.cam[1] - (portals.clip_range * 64.0f); + clip.max[2] = clip.cam[2] - (portals.clip_range * 64.0f); + return clip; +} + +void CPortalsRender::renderSolid(Renderer& renderer, const VolumeTest& volume) const +{ + if(!portals.show_3d || portals.portal_count < 1) + return; + + CubicClipVolume clip = calculateCubicClipVolume(matrix4_multiplied_by_matrix4(volume.GetProjection(), volume.GetModelview())); + + if(portals.polygons) + { + renderer.SetState(g_shader_solid, Renderer::eWireframeOnly); + renderer.SetState(g_shader_solid, Renderer::eFullMaterials); + + m_drawSolid.clip = clip; + renderer.addRenderable(m_drawSolid, g_matrix4_identity); + } + + if(portals.lines) + { + renderer.SetState(g_shader_solid_outline, Renderer::eWireframeOnly); + renderer.SetState(g_shader_solid_outline, Renderer::eFullMaterials); + + m_drawSolidOutline.clip = clip; + renderer.addRenderable(m_drawSolidOutline, g_matrix4_identity); + } +} + +void CPortalsDrawSolid::render(RenderStateFlags state) const +{ + float trans = (100.0f - portals.trans_3d) / 100.0f; + + unsigned int n, p; + + if(portals.zbuffer != 0) + { + float d; + + for(n = 0; n < portals.portal_count; n++) + { + d = (float)clip.cam[0] - portals.portal[n].center.p[0]; + portals.portal[n].dist = d * d; + + d = (float)clip.cam[1] - portals.portal[n].center.p[1]; + portals.portal[n].dist += d * d; + + d = (float)clip.cam[2] - portals.portal[n].center.p[2]; + portals.portal[n].dist += d * d; + + portals.portal_sort[n] = n; + } + + qsort(portals.portal_sort, portals.portal_count, 4, compare); + + for(n = 0; n < portals.portal_count; n++) + { + if(portals.polygons == 2 && !portals.portal[portals.portal_sort[n]].hint) + continue; + + if(portals.clip) + { + if(clip.min[0] < portals.portal[portals.portal_sort[n]].min[0]) + continue; + else if(clip.min[1] < portals.portal[portals.portal_sort[n]].min[1]) + continue; + else if(clip.min[2] < portals.portal[portals.portal_sort[n]].min[2]) + continue; + else if(clip.max[0] > portals.portal[portals.portal_sort[n]].max[0]) + continue; + else if(clip.max[1] > portals.portal[portals.portal_sort[n]].max[1]) + continue; + else if(clip.max[2] > portals.portal[portals.portal_sort[n]].max[2]) + continue; + } + + glColor4f(portals.portal[portals.portal_sort[n]].fp_color_random[0], portals.portal[portals.portal_sort[n]].fp_color_random[1], + portals.portal[portals.portal_sort[n]].fp_color_random[2], trans); + + glBegin(GL_POLYGON); + + for(p = 0; p < portals.portal[portals.portal_sort[n]].point_count; p++) + glVertex3fv(portals.portal[portals.portal_sort[n]].point[p].p); + + glEnd(); + } + } + else + { + for(n = 0; n < portals.portal_count; n++) + { + if(portals.polygons == 2 && !portals.portal[n].hint) + continue; + + if(portals.clip) + { + if(clip.min[0] < portals.portal[n].min[0]) + continue; + else if(clip.min[1] < portals.portal[n].min[1]) + continue; + else if(clip.min[2] < portals.portal[n].min[2]) + continue; + else if(clip.max[0] > portals.portal[n].max[0]) + continue; + else if(clip.max[1] > portals.portal[n].max[1]) + continue; + else if(clip.max[2] > portals.portal[n].max[2]) + continue; + } + + glColor4f(portals.portal[n].fp_color_random[0], portals.portal[n].fp_color_random[1], + portals.portal[n].fp_color_random[2], trans); + + glBegin(GL_POLYGON); + + for(p = 0; p < portals.portal[n].point_count; p++) + glVertex3fv(portals.portal[n].point[p].p); + + glEnd(); + } + } +} + +void CPortalsDrawSolidOutline::render(RenderStateFlags state) const +{ + for(int n = 0; n < portals.portal_count; n++) + { + if(portals.lines == 2 && !portals.portal[n].hint) + continue; + + if(portals.clip) + { + if(clip.min[0] < portals.portal[n].min[0]) + continue; + else if(clip.min[1] < portals.portal[n].min[1]) + continue; + else if(clip.min[2] < portals.portal[n].min[2]) + continue; + else if(clip.max[0] > portals.portal[n].max[0]) + continue; + else if(clip.max[1] > portals.portal[n].max[1]) + continue; + else if(clip.max[2] > portals.portal[n].max[2]) + continue; + } + + glBegin(GL_LINE_LOOP); + + for(int p = 0; p < portals.portal[n].point_count; p++) + glVertex3fv(portals.portal[n].inner_point[p].p); + + glEnd(); + } +} diff --git a/contrib/prtview/portals.h b/contrib/prtview/portals.h new file mode 100644 index 00000000..06f4af4b --- /dev/null +++ b/contrib/prtview/portals.h @@ -0,0 +1,157 @@ +/* +PrtView plugin for GtkRadiant +Copyright (C) 2001 Geoffrey Dewan, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _PORTALS_H_ +#define _PORTALS_H_ + +#include +#include "irender.h" +#include "renderable.h" +#include "math/vector.h" + + +class CBspPoint { +public: + float p[3]; +}; + +class CBspPortal { +public: + CBspPortal(); + ~CBspPortal(); + +protected: + +public: + CBspPoint center; + unsigned point_count; + CBspPoint *point; + CBspPoint *inner_point; + float fp_color_random[4]; + float min[3]; + float max[3]; + float dist; + bool hint; + + bool Build(char *def); +}; + +#define PRTVIEW_PATH_MAX 260 +typedef guint32 PackedColour; +#define RGB(r, g, b) ((guint32)(((guint8) (r) | ((guint16) (g) << 8))|(((guint32) (guint8) (b)) << 16))) +#define GetRValue(rgb) ((guint8)(rgb)) +#define GetGValue(rgb) ((guint8)(((guint16)(rgb)) >> 8)) +#define GetBValue(rgb) ((guint8)((rgb)>>16)) + + +class CPortals { +public: + + CPortals(); + ~CPortals(); + +protected: + + +public: + + void Load(); // use filename in fn + void Purge(); + + void FixColors(); + + char fn[PRTVIEW_PATH_MAX]; + + int zbuffer; + int polygons; + int lines; + bool show_3d; + bool aa_3d; + bool fog; + PackedColour color_3d; + float width_3d; // in 8'ths + float fp_color_3d[4]; + PackedColour color_fog; + float fp_color_fog[4]; + float trans_3d; + float clip_range; + bool clip; + + bool show_2d; + bool aa_2d; + PackedColour color_2d; + float width_2d; // in 8'ths + float fp_color_2d[4]; + + CBspPortal *portal; + int *portal_sort; + bool hint_flags; +// CBspNode *node; + + unsigned int node_count; + unsigned int portal_count; +}; + +class CubicClipVolume +{ +public: + Vector3 cam, min, max; +}; + +class CPortalsDrawSolid : public OpenGLRenderable +{ +public: + mutable CubicClipVolume clip; + void render(RenderStateFlags state) const; +}; + +class CPortalsDrawSolidOutline : public OpenGLRenderable +{ +public: + mutable CubicClipVolume clip; + void render(RenderStateFlags state) const; +}; + +class CPortalsDrawWireframe : public OpenGLRenderable +{ +public: + void render(RenderStateFlags state) const; +}; + +class CPortalsRender : public Renderable +{ +public: + CPortalsDrawSolid m_drawSolid; + CPortalsDrawSolidOutline m_drawSolidOutline; + CPortalsDrawWireframe m_drawWireframe; + + void renderSolid(Renderer& renderer, const VolumeTest& volume) const; + void renderWireframe(Renderer& renderer, const VolumeTest& volume) const; +}; + +extern CPortals portals; +extern CPortalsRender render; + +void Portals_constructShaders(); +void Portals_destroyShaders(); + +void Portals_shadersChanged(); + + +#endif // _PORTALS_H_ diff --git a/contrib/prtview/prtview.cpp b/contrib/prtview/prtview.cpp new file mode 100644 index 00000000..c5ed86c9 --- /dev/null +++ b/contrib/prtview/prtview.cpp @@ -0,0 +1,328 @@ +/* +PrtView plugin for GtkRadiant +Copyright (C) 2001 Geoffrey Dewan, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "prtview.h" +#include +#include + +#include "profile/profile.h" + +#include "qerplugin.h" +#include "iscenegraph.h" +#include "iglrender.h" +#include "iplugin.h" +#include "stream/stringstream.h" + +#include "portals.h" +#include "AboutDialog.h" +#include "ConfigDialog.h" +#include "LoadPortalFileDialog.h" + +#define Q3R_CMD_SPLITTER "-" +#define Q3R_CMD_ABOUT "About Portal Viewer" +#define Q3R_CMD_LOAD "Load .prt file" +#define Q3R_CMD_RELEASE "Unload .prt file" +#define Q3R_CMD_SHOW_3D "Toggle portals (3D)" +#define Q3R_CMD_SHOW_2D "Toggle portals (2D)" +#define Q3R_CMD_OPTIONS "Configure Portal Viewer" + +CopiedString INIfn; + +///////////////////////////////////////////////////////////////////////////// +// CPrtViewApp construction + +#define RENDER_2D "Render2D" +#define WIDTH_2D "Width2D" +#define AA_2D "AntiAlias2D" +#define COLOR_2D "Color2D" + +#define RENDER_3D "Render3D" +#define WIDTH_3D "Width3D" +#define AA_3D "AntiAlias3D" +#define COLOR_3D "Color3D" +#define COLOR_FOG "ColorFog" +#define FOG "Fog" +#define ZBUFFER "ZBuffer" +#define POLYGON "Polygons" +#define LINE "Lines" +#define TRANS_3D "Transparency" +#define CLIP_RANGE "ClipRange" +#define CLIP "Clip" + + +void PrtView_construct() +{ + StringOutputStream tmp(64); + tmp << GlobalRadiant().getSettingsPath() << "prtview.ini"; + INIfn = tmp.c_str(); + + portals.show_2d = INIGetInt(RENDER_2D, FALSE) ? true : false; + portals.aa_2d = INIGetInt(AA_2D, FALSE) ? true : false; + portals.width_2d = (float)INIGetInt(WIDTH_2D, 10); + portals.color_2d = (PackedColour)INIGetInt(COLOR_2D, RGB(0, 0, 255)) & 0xFFFFFF; + + if (portals.width_2d > 40.0f) + portals.width_2d = 40.0f; + else if (portals.width_2d < 2.0f) + portals.width_2d = 2.0f; + + portals.show_3d = INIGetInt(RENDER_3D, TRUE) ? true : false; + + portals.zbuffer = INIGetInt(ZBUFFER, 1); + portals.fog = INIGetInt(FOG, FALSE) ? true : false; + portals.polygons = INIGetInt(POLYGON, TRUE); + portals.lines = INIGetInt(LINE, TRUE); + portals.aa_3d = INIGetInt(AA_3D, FALSE) ? true : false; + portals.width_3d = (float)INIGetInt(WIDTH_3D, 4); + portals.color_3d = (PackedColour)INIGetInt(COLOR_3D, RGB(255, 255, 0)) & 0xFFFFFF; + portals.color_fog = (PackedColour)INIGetInt(COLOR_FOG, RGB(127, 127, 127)) & 0xFFFFFF; + portals.trans_3d = (float)INIGetInt(TRANS_3D, 50); + portals.clip = INIGetInt(CLIP, FALSE) ? true : false; + portals.clip_range = (float)INIGetInt(CLIP_RANGE, 16); + + if (portals.clip_range < 1) + portals.clip_range = 1; + else if (portals.clip_range > 128) + portals.clip_range = 128; + + if (portals.zbuffer < 0) + portals.zbuffer = 0; + else if (portals.zbuffer > 2) + portals.zbuffer = 0; + + if (portals.width_3d > 40.0f) + portals.width_3d = 40.0f; + else if (portals.width_3d < 2.0f) + portals.width_3d = 2.0f; + + if (portals.trans_3d > 100.0f) + portals.trans_3d = 100.0f; + else if (portals.trans_3d < 0.0f) + portals.trans_3d = 0.0f; + + SaveConfig(); + + portals.FixColors(); + + Portals_constructShaders(); + GlobalShaderCache().attachRenderable(render); +} + +void PrtView_destroy() +{ + GlobalShaderCache().detachRenderable(render); + Portals_destroyShaders(); +} + +void SaveConfig () +{ + INISetInt(RENDER_2D, portals.show_2d, "Draw in 2D windows"); + INISetInt(WIDTH_2D, (int)portals.width_2d, "Width of lines in 2D windows (in units of 1/2)"); + INISetInt(COLOR_2D, (int)portals.color_2d, "Color of lines in 2D windows"); + INISetInt(AA_2D, portals.aa_2d, "Draw lines in 2D window anti-aliased"); + + INISetInt(ZBUFFER, portals.zbuffer, "ZBuffer level in 3D window"); + INISetInt(FOG, portals.fog, "Use depth cueing in 3D window"); + INISetInt(POLYGON, portals.polygons, "Render using polygons polygons in 3D window"); + INISetInt(LINE, portals.polygons, "Render using lines in 3D window"); + INISetInt(RENDER_3D, portals.show_3d, "Draw in 3D windows"); + INISetInt(WIDTH_3D, (int)portals.width_3d, "Width of lines in 3D window (in units of 1/2)"); + INISetInt(COLOR_3D, (int)portals.color_3d, "Color of lines/polygons in 3D window"); + INISetInt(COLOR_FOG, (int)portals.color_fog, "Color of distant lines/polygons in 3D window"); + INISetInt(AA_3D, portals.aa_3d, "Draw lines in 3D window anti-aliased"); + INISetInt(TRANS_3D, (int)portals.trans_3d, "Transparency in 3d view (0 = solid, 100 = invisible)"); + INISetInt(CLIP, portals.clip, "Cubic clipper active for portal viewer"); + INISetInt(CLIP_RANGE, (int)portals.clip_range, "Portal viewer cubic clip distance (in units of 64)"); +} + + +#define CONFIG_SECTION "Configuration" + +int INIGetInt(char *key, int def) +{ + char value[1024]; + + if (read_var (INIfn.c_str(), CONFIG_SECTION, key, value)) + return atoi (value); + else + return def; +} + +void INISetInt(char *key, int val, char *comment /* = NULL */) +{ + char s[1000]; + + if(comment) + sprintf(s, "%d ; %s", val, comment); + else + sprintf(s, "%d", val); + save_var (INIfn.c_str(), CONFIG_SECTION, key, s); +} + + +// plugin name +static const char *PLUGIN_NAME = "Portal Viewer"; +// commands in the menu +static const char *PLUGIN_COMMANDS = + Q3R_CMD_ABOUT ";" + Q3R_CMD_SPLITTER ";" + Q3R_CMD_OPTIONS ";" + Q3R_CMD_SPLITTER ";" + Q3R_CMD_SHOW_2D ";" + Q3R_CMD_SHOW_3D ";" + Q3R_CMD_SPLITTER ";" + Q3R_CMD_RELEASE ";" + Q3R_CMD_LOAD; + + + +const char* QERPlug_Init (void *hApp, void* pMainWidget) +{ + return "Portal Viewer for Q3Radiant"; +} + +const char* QERPlug_GetName() +{ + return PLUGIN_NAME; +} + +const char* QERPlug_GetCommandList() +{ + return PLUGIN_COMMANDS; +} + + +const char* QERPlug_GetCommandTitleList() +{ + return ""; +} + + +void QERPlug_Dispatch(const char* p, float* vMin, float* vMax, bool bSingleBrush) +{ + globalOutputStream() << MSG_PREFIX "Command \"" << p << "\"\n"; + + if (!strcmp(p,Q3R_CMD_ABOUT)) + { + DoAboutDlg (); + } + else if (!strcmp(p,Q3R_CMD_LOAD)) + { + if (DoLoadPortalFileDialog () == IDOK) + { + portals.Load(); + SceneChangeNotify(); + } + else + { + globalOutputStream() << MSG_PREFIX "Portal file load aborted.\n"; + } + } + else if (!strcmp(p,Q3R_CMD_RELEASE)) + { + portals.Purge(); + + SceneChangeNotify(); + + globalOutputStream() << MSG_PREFIX "Portals unloaded.\n"; + } + else if (!strcmp(p,Q3R_CMD_SHOW_2D)) + { + portals.show_2d = !portals.show_2d; + + SceneChangeNotify(); + SaveConfig(); + + if(portals.show_2d) + globalOutputStream() << MSG_PREFIX "Portals will be rendered in 2D view.\n"; + else + globalOutputStream() << MSG_PREFIX "Portals will NOT be rendered in 2D view.\n"; + } + else if (!strcmp(p,Q3R_CMD_SHOW_3D)) + { + portals.show_3d = !portals.show_3d; + SaveConfig(); + + SceneChangeNotify(); + + if (portals.show_3d) + globalOutputStream() << MSG_PREFIX "Portals will be rendered in 3D view.\n"; + else + globalOutputStream() << MSG_PREFIX "Portals will NOT be rendered in 3D view.\n"; + } + else if (!strcmp(p,Q3R_CMD_OPTIONS)) + { + DoConfigDialog (); + SaveConfig(); + + SceneChangeNotify(); + } +} + + +#include "modulesystem/singletonmodule.h" + +class PrtViewPluginDependencies : + public GlobalSceneGraphModuleRef, + public GlobalRadiantModuleRef, + public GlobalShaderCacheModuleRef, + public GlobalOpenGLModuleRef, + public GlobalOpenGLStateLibraryModuleRef +{ +}; + +class PrtViewPluginModule +{ + _QERPluginTable m_plugin; +public: + typedef _QERPluginTable Type; + STRING_CONSTANT(Name, "prtview"); + + PrtViewPluginModule() + { + m_plugin.m_pfnQERPlug_Init = QERPlug_Init; + m_plugin.m_pfnQERPlug_GetName = QERPlug_GetName; + m_plugin.m_pfnQERPlug_GetCommandList = QERPlug_GetCommandList; + m_plugin.m_pfnQERPlug_GetCommandTitleList = QERPlug_GetCommandTitleList; + m_plugin.m_pfnQERPlug_Dispatch = QERPlug_Dispatch; + + PrtView_construct(); + } + ~PrtViewPluginModule() + { + PrtView_destroy(); + } + _QERPluginTable* getTable() + { + return &m_plugin; + } +}; + +typedef SingletonModule SingletonPrtViewPluginModule; + +SingletonPrtViewPluginModule g_PrtViewPluginModule; + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer& server) +{ + initialiseModule(server); + + g_PrtViewPluginModule.selfRegister(); +} diff --git a/contrib/prtview/prtview.h b/contrib/prtview/prtview.h new file mode 100644 index 00000000..472b028a --- /dev/null +++ b/contrib/prtview/prtview.h @@ -0,0 +1,35 @@ +/* +PrtView plugin for GtkRadiant +Copyright (C) 2001 Geoffrey Dewan, Loki software and qeradiant.com + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#if !defined(INCLUDED_PRTVIEW_H) +#define INCLUDED_PRTVIEW_H + +#define MSG_PREFIX "Portal Viewer plugin: " + +void InitInstance (); +void SaveConfig (); + +int INIGetInt(char *key, int def); +void INISetInt(char *key, int val, char *comment = 0); + +#define IDOK 1 +#define IDCANCEL 2 + + +#endif diff --git a/contrib/prtview/res/PrtView.rc2 b/contrib/prtview/res/PrtView.rc2 new file mode 100644 index 00000000..a534b6a2 --- /dev/null +++ b/contrib/prtview/res/PrtView.rc2 @@ -0,0 +1,13 @@ +// +// PRTVIEW.RC2 - resources Microsoft Visual C++ does not edit directly +// + +#ifdef APSTUDIO_INVOKED + #error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// Add manually edited resources here... + +///////////////////////////////////////////////////////////////////////////// diff --git a/contrib/shaderplug/shaderplug.cpp b/contrib/shaderplug/shaderplug.cpp new file mode 100644 index 00000000..facb3822 --- /dev/null +++ b/contrib/shaderplug/shaderplug.cpp @@ -0,0 +1,263 @@ +/* +Copyright (C) 2006, Stefan Greven. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "shaderplug.h" + +#include "debugging/debugging.h" + +#include +#include +#include "string/string.h" +#include "modulesystem/singletonmodule.h" +#include "stream/stringstream.h" +#include "os/file.h" + +#include + +#include "iplugin.h" +#include "qerplugin.h" +#include "ifilesystem.h" +#include "iarchive.h" +#include "ishaders.h" +#include "iscriplib.h" + +#include "generic/callback.h" + +namespace { +const char SHADERTAG_FILE[] = "shadertags.xml"; +} + +class ShaderPlugPluginDependencies : public GlobalRadiantModuleRef, + public GlobalFileSystemModuleRef, + public GlobalShadersModuleRef +{ +public: + ShaderPlugPluginDependencies() : + GlobalShadersModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("shaders")) + { + } +}; + +namespace Shaderplug +{ + GtkWindow* g_window; + + std::vector archives; + std::set shaders; + std::set textures; + + XmlTagBuilder TagBuilder; + void CreateTagFile(); + + const char* init(void* hApp, void* pMainWidget) + { + g_window = GTK_WINDOW(pMainWidget); + return ""; + } + const char* getName() + { + return "ShaderPlug"; + } + const char* getCommandList() + { + return "About;Create tag file"; + } + const char* getCommandTitleList() + { + return ""; + } + void dispatch(const char* command, float* vMin, float* vMax, bool bSingleBrush) + { + if(string_equal(command, "About")) + { + GlobalRadiant().m_pfnMessageBox(GTK_WIDGET(g_window), "Shaderplug (1.0)\n\n" + "by Shaderman (shaderman@gmx.net)", + "About", + eMB_OK, + eMB_ICONDEFAULT); + } + if(string_equal(command, "Create tag file")) + { + CreateTagFile(); + } + } + + void loadArchiveFile(const char* filename) + { + archives.push_back(filename); + } + + typedef FreeCaller1 LoadArchiveFileCaller; + + void LoadTextureFile(const char* filename) + { + std::string s_filename = filename; + + char buffer[256]; + strcpy(buffer, "textures/"); + + // append filename without trailing file extension (.tga or .jpg for example) + strncat(buffer, filename, s_filename.length() - 4); + + std::set::iterator iter; + iter = shaders.find(buffer); + + // a shader with this name already exists + if(iter == shaders.end()) + { + textures.insert(buffer); + } + } + + typedef FreeCaller1 LoadTextureFileCaller; + + void GetTextures(char* extension) + { + GlobalFileSystem().forEachFile("textures/", extension, LoadTextureFileCaller(), 0); + } + + void LoadShaderList(const char* filename) + { + if(string_equal_prefix(filename, "textures/")) + { + shaders.insert(filename); + } + } + + typedef FreeCaller1 LoadShaderListCaller; + + void GetAllShaders() + { + GlobalShaderSystem().foreachShaderName(LoadShaderListCaller()); + } + + void GetArchiveList() + { + GlobalFileSystem().forEachArchive(LoadArchiveFileCaller()); + globalOutputStream() << "Shaderplug: " << (const Unsigned)Shaderplug::archives.size() << " archives found.\n"; + } + + void CreateTagFile() + { + const char* shader_type = GlobalRadiant().getGameDescriptionKeyValue("shaders"); + + GetAllShaders(); + globalOutputStream() << "Shaderplug: " << (const Unsigned)shaders.size() << " shaders found.\n"; + + if(string_equal(shader_type, "quake3")) + { + GetTextures("jpg"); + GetTextures("tga"); + GetTextures("png"); + + globalOutputStream() << "Shaderplug: " << (const Unsigned)textures.size() << " textures found.\n"; + } + + if(shaders.size() || textures.size() != 0) + { + globalOutputStream() << "Shaderplug: Creating XML tag file.\n"; + + TagBuilder.CreateXmlDocument(); + + std::set::reverse_iterator r_iter; + + for(r_iter = textures.rbegin(); r_iter != textures.rend(); ++r_iter) + { + TagBuilder.AddShaderNode(const_cast((*r_iter).c_str()), STOCK, TEXTURE); + } + + for(r_iter = shaders.rbegin(); r_iter != shaders.rend(); ++r_iter) + { + TagBuilder.AddShaderNode(const_cast((*r_iter).c_str()), STOCK, SHADER); + } + + // Get the tag file + StringOutputStream tagFileStream(256); + tagFileStream << GlobalRadiant().getLocalRcPath() << SHADERTAG_FILE; + char* tagFile = tagFileStream.c_str(); + + char message[256]; + strcpy(message, "Tag file saved to\n"); + strcat(message, tagFile); + strcat(message, "\nPlease restart Radiant now.\n"); + + if(file_exists(tagFile)) + { + EMessageBoxReturn result = GlobalRadiant().m_pfnMessageBox(GTK_WIDGET(g_window), + "WARNING! A tag file already exists! Overwrite it?", "Overwrite tag file?", + eMB_NOYES, + eMB_ICONWARNING); + + if(result == eIDYES) + { + TagBuilder.SaveXmlDoc(tagFile); + GlobalRadiant().m_pfnMessageBox(GTK_WIDGET(g_window), message, "INFO", eMB_OK, eMB_ICONASTERISK); + } + } else { + TagBuilder.SaveXmlDoc(tagFile); + GlobalRadiant().m_pfnMessageBox(GTK_WIDGET(g_window), message, "INFO", eMB_OK, eMB_ICONASTERISK); + } + } else { + GlobalRadiant().m_pfnMessageBox(GTK_WIDGET(g_window), + "No shaders or textures found. No XML tag file created!\n" + "", + "ERROR", + eMB_OK, + eMB_ICONERROR); + } + } +} // namespace + +class ShaderPluginModule +{ + _QERPluginTable m_plugin; +public: + typedef _QERPluginTable Type; + STRING_CONSTANT(Name, "ShaderPlug"); + + ShaderPluginModule() + { + m_plugin.m_pfnQERPlug_Init = &Shaderplug::init; + m_plugin.m_pfnQERPlug_GetName = &Shaderplug::getName; + m_plugin.m_pfnQERPlug_GetCommandList = &Shaderplug::getCommandList; + m_plugin.m_pfnQERPlug_GetCommandTitleList = &Shaderplug::getCommandTitleList; + m_plugin.m_pfnQERPlug_Dispatch = &Shaderplug::dispatch; + } + _QERPluginTable* getTable() + { + return &m_plugin; + } +}; + +typedef SingletonModule SingletonShaderPluginModule; + +SingletonShaderPluginModule g_ShaderPluginModule; + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer& server) +{ + initialiseModule(server); + + g_ShaderPluginModule.selfRegister(); +} + + + + diff --git a/contrib/shaderplug/shaderplug.def b/contrib/shaderplug/shaderplug.def new file mode 100644 index 00000000..be71ca58 --- /dev/null +++ b/contrib/shaderplug/shaderplug.def @@ -0,0 +1,7 @@ +; shaderplug.def : Declares the module parameters for the DLL. + +LIBRARY "SHADERPLUG" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/contrib/shaderplug/shaderplug.h b/contrib/shaderplug/shaderplug.h new file mode 100644 index 00000000..4842cc22 --- /dev/null +++ b/contrib/shaderplug/shaderplug.h @@ -0,0 +1,27 @@ +/* +Copyright (C) 2006, Stefan Greven. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_SHADERPLUG_H) +#define INCLUDED_SHADERPLUG_H + +#include "xml/xmltextags.h" + +#endif diff --git a/contrib/shaderplug/shaderplug.vcproj b/contrib/shaderplug/shaderplug.vcproj new file mode 100644 index 00000000..7c727b77 --- /dev/null +++ b/contrib/shaderplug/shaderplug.vcproj @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/sunplug/sunplug.cpp b/contrib/sunplug/sunplug.cpp new file mode 100644 index 00000000..67103018 --- /dev/null +++ b/contrib/sunplug/sunplug.cpp @@ -0,0 +1,462 @@ +/* +Sunplug plugin for GtkRadiant +Copyright (C) 2004 Topsun +Thanks to SPoG for help! + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "sunplug.h" + +#include "debugging/debugging.h" + +#include "iplugin.h" + +#include "string/string.h" +#include "modulesystem/singletonmodule.h" + +#include "iundo.h" // declaration of undo system +#include "ientity.h" // declaration of entity system +#include "iscenegraph.h" // declaration of datastructure of the map + +#include "scenelib.h" // declaration of datastructure of the map +#include "qerplugin.h" // declaration to use other interfaces as a plugin + +#include // to display something with gtk (windows, buttons etc.), the whole package might not be necessary + +void about_plugin_window(); +void MapCoordinator(); + +#ifdef __linux__ +// linux itoa implementation +char* itoa( int value, char* result, int base ) +{ + // check that the base if valid + if (base < 2 || base > 16) + { + *result = 0; + return result; + } + + char* out = result; + int quotient = value; + + do + { + *out = "0123456789abcdef"[abs(quotient % base)]; + ++out; + + quotient /= base; + } while (quotient); + + // Only apply negative sign for base 10 + if( value < 0 && base == 10) + *out++ = '-'; + + std::reverse(result, out); + + *out = 0; + return result; +} +#endif + +typedef struct _mapcoord_setting_packet { + GtkSpinButton *spinner1, *spinner2, *spinner3, *spinner4; + Entity* worldspawn; +} mapcoord_setting_packet; + +static int map_minX, map_maxX, map_minY, map_maxY; +static int minX, maxX, minY, maxY; +mapcoord_setting_packet msp; + +// ************************** +// ** find entities by class ** from radiant/map.cpp +// ************************** +class EntityFindByClassname : public scene::Graph::Walker +{ + const char* m_name; + Entity*& m_entity; +public: + EntityFindByClassname(const char* name, Entity*& entity) : m_name(name), m_entity(entity) + { + m_entity = 0; + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(m_entity == 0) + { + Entity* entity = Node_getEntity(path.top()); + if(entity != 0 + && string_equal(m_name, entity->getKeyValue("classname"))) + { + m_entity = entity; + } + } + return true; + } +}; + +Entity* Scene_FindEntityByClass(const char* name) +{ + Entity* entity; + GlobalSceneGraph().traverse(EntityFindByClassname(name, entity)); + return entity; +} + +// ************************** +// ** GTK callback functions ** +// ************************** + +static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) +{ + /* If you return FALSE in the "delete_event" signal handler, + * GTK will emit the "destroy" signal. Returning TRUE means + * you don't want the window to be destroyed. + * This is useful for popping up 'are you sure you want to quit?' + * type dialogs. */ + + return FALSE; +} + +// destroy widget if destroy signal is passed to widget +static void destroy(GtkWidget *widget, gpointer data) +{ + gtk_widget_destroy(widget); +} + +// function for close button to destroy the toplevel widget +static void close_window(GtkWidget *widget, gpointer data) +{ + gtk_widget_destroy(gtk_widget_get_toplevel(widget)); +} + +// callback function to assign the optimal mapcoords to the spinboxes +static void input_optimal(GtkWidget *widget, gpointer data) +{ + gtk_spin_button_set_value(msp.spinner1, minX); + gtk_spin_button_set_value(msp.spinner2, maxY); + gtk_spin_button_set_value(msp.spinner3, maxX); + gtk_spin_button_set_value(msp.spinner4, minY); +} + +// Spinner return value function +gint grab_int_value(GtkSpinButton *a_spinner, gpointer user_data) { + return gtk_spin_button_get_value_as_int(a_spinner); +} + +// write the values of the Spinner-Boxes to the worldspawn +static void set_coordinates(GtkWidget *widget, gpointer data) +{ + //Str str_min, str_max; + char buffer[10], str_min[20], str_max[20]; + + itoa(gtk_spin_button_get_value_as_int(msp.spinner1), str_min, 10); + itoa(gtk_spin_button_get_value_as_int(msp.spinner2), buffer, 10); + strcat(str_min, " "); + strcat(str_min, buffer); + msp.worldspawn->setKeyValue("mapcoordsmins", str_min); + + itoa(gtk_spin_button_get_value_as_int(msp.spinner3), str_max, 10); + itoa(gtk_spin_button_get_value_as_int(msp.spinner4), buffer, 10); + strcat(str_max, " "); + strcat(str_max, buffer); + UndoableCommand undo("SunPlug.entitySetMapcoords"); + msp.worldspawn->setKeyValue("mapcoordsmaxs", str_max); + + close_window(widget, NULL); +} + +class SunPlugPluginDependencies : + public GlobalRadiantModuleRef, // basic class for all other module refs + public GlobalUndoModuleRef, // used to say radiant that something has changed and to undo that + public GlobalSceneGraphModuleRef, // necessary to handle data in the mapfile (change, retrieve data) + public GlobalEntityModuleRef // to access and modify the entities +{ +public: + SunPlugPluginDependencies() : + GlobalEntityModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("entities"))//, + { + } +}; + +// ************************* +// ** standard plugin stuff ** +// ************************* +namespace SunPlug +{ + GtkWindow* main_window; + char MenuList[100] = ""; + + const char* init(void* hApp, void* pMainWidget) + { + main_window = GTK_WINDOW(pMainWidget); + return "Initializing SunPlug for GTKRadiant"; + } + const char* getName() + { + return "SunPlug"; // name that is shown in the menue + } + const char* getCommandList() + { + const char about[] = "About..."; + const char etMapCoordinator[] = ";ET-MapCoordinator"; + + strcat(MenuList, about); + if (strncmp(GlobalRadiant().getGameName(), "etmain", 6) == 0) strcat(MenuList, etMapCoordinator); + return (const char*)MenuList; + } + const char* getCommandTitleList() + { + return ""; + } + void dispatch(const char* command, float* vMin, float* vMax, bool bSingleBrush) // message processing + { + if(string_equal(command, "About...")) + { + about_plugin_window(); + } + if(string_equal(command, "ET-MapCoordinator")) + { + MapCoordinator(); + } + } +} // namespace + +class SunPlugModule : public TypeSystemRef +{ + _QERPluginTable m_plugin; +public: + typedef _QERPluginTable Type; + STRING_CONSTANT(Name, "SunPlug"); + + SunPlugModule() + { + m_plugin.m_pfnQERPlug_Init = &SunPlug::init; + m_plugin.m_pfnQERPlug_GetName = &SunPlug::getName; + m_plugin.m_pfnQERPlug_GetCommandList = &SunPlug::getCommandList; + m_plugin.m_pfnQERPlug_GetCommandTitleList = &SunPlug::getCommandTitleList; + m_plugin.m_pfnQERPlug_Dispatch = &SunPlug::dispatch; + } + _QERPluginTable* getTable() + { + return &m_plugin; + } +}; + +typedef SingletonModule SingletonSunPlugModule; + +SingletonSunPlugModule g_SunPlugModule; + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer& server) +{ + initialiseModule(server); + + g_SunPlugModule.selfRegister(); +} + +// ************ +// ** my stuff ** +// ************ + +// About dialog +void about_plugin_window() +{ + GtkWidget *window, *vbox, *label, *button; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); // create a window + gtk_window_set_transient_for(GTK_WINDOW(window), SunPlug::main_window); // make the window to stay in front of the main window + g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_event), NULL); // connect the delete event + g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL); // connect the destroy event for the window + gtk_window_set_title(GTK_WINDOW(window), "About SunPlug"); // set the title of the window for the window + gtk_window_set_resizable(GTK_WINDOW(window), FALSE); // don't let the user resize the window + gtk_window_set_modal(GTK_WINDOW(window), TRUE); // force the user not to do something with the other windows + gtk_container_set_border_width(GTK_CONTAINER(window), 10); // set the border of the window + + vbox = gtk_vbox_new(FALSE, 10); // create a box to arrange new objects vertically + gtk_container_add(GTK_CONTAINER(window), vbox); // add the box to the window + + label = gtk_label_new("SunPlug v1.0 for GtkRadiant 1.5\nby Topsun"); // create a label + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // text align left + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 2); // insert the label in the box + + button = gtk_button_new_with_label("OK"); // create a button with text + g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK (gtk_widget_destroy), window); // connect the click event to close the window + gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 2); // insert the button in the box + + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); // center the window on screen + + gtk_widget_show_all(window); // show the window and all subelements +} + +// get the current bounding box and return the optimal coordinates +void GetOptimalCoordinates(AABB *levelBoundingBox) +{ + int half_width, half_heigth, center_x, center_y; + + half_width = levelBoundingBox->extents.x(); + half_heigth = levelBoundingBox->extents.y(); + center_x = levelBoundingBox->origin.x(); + center_y = levelBoundingBox->origin.y(); + + if (half_width > 175 || half_heigth > 175) // the square must be at least 350x350 units + { + // the wider side is the indicator for the square + if (half_width >= half_heigth) + { + minX = center_x - half_width; + maxX = center_x + half_width; + minY = center_y - half_width; + maxY = center_y + half_width; + } else { + minX = center_x - half_heigth; + maxX = center_x + half_heigth; + minY = center_y - half_heigth; + maxY = center_y + half_heigth; + } + } else { + minX = center_x - 175; + maxX = center_x + 175; + minY = center_y - 175; + maxY = center_y + 175; + } +} + +// MapCoordinator dialog window +void MapCoordinator() +{ + GtkWidget *window, *vbox, *table, *label, *spinnerMinX, *spinnerMinY, *spinnerMaxX, *spinnerMaxY, *button; + GtkAdjustment *spinner_adj_MinX, *spinner_adj_MinY, *spinner_adj_MaxX, *spinner_adj_MaxY; + Entity *theWorldspawn = NULL; + const char *buffer; + char line[20]; + + // in any case we need a window to show the user what to do + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); // create the window + gtk_window_set_transient_for(GTK_WINDOW(window), SunPlug::main_window); // make the window to stay in front of the main window + g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_event), NULL); // connect the delete event for the window + g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL); // connect the destroy event for the window + gtk_window_set_title(GTK_WINDOW(window), "ET-MapCoordinator"); // set the title of the window for the window + gtk_window_set_resizable(GTK_WINDOW(window), FALSE); // don't let the user resize the window + gtk_window_set_modal(GTK_WINDOW(window), TRUE); // force the user not to do something with the other windows + gtk_container_set_border_width(GTK_CONTAINER(window), 10); // set the border of the window + + vbox = gtk_vbox_new(FALSE, 10); // create a box to arrange new objects vertically + gtk_container_add(GTK_CONTAINER(window), vbox); // add the box to the window + + scene::Path path = makeReference(GlobalSceneGraph().root()); // get the path to the root element of the graph + scene::Instance* instance = GlobalSceneGraph().find(path); // find the instance to the given path + AABB levelBoundingBox = instance->worldAABB(); // get the bounding box of the level + + theWorldspawn = Scene_FindEntityByClass("worldspawn"); // find the entity worldspawn + if (theWorldspawn != 0) { // need to have a worldspawn otherwise setting a value crashes the radiant + // next two if's: get the current values of the mapcoords + buffer = theWorldspawn->getKeyValue("mapcoordsmins"); // upper left corner + if (strlen(buffer) > 0) { + strncpy(line, buffer, 19); + map_minX = atoi(strtok(line, " ")); // minimum of x value + map_minY = atoi(strtok(NULL, " ")); // maximum of y value + } else { + map_minX = 0; + map_minY = 0; + } + buffer = theWorldspawn->getKeyValue("mapcoordsmaxs"); // lower right corner + if (strlen(buffer) > 0) { + strncpy(line, buffer, 19); + map_maxX = atoi(strtok(line, " ")); // maximum of x value + map_maxY = atoi(strtok(NULL, " ")); // minimum of y value + } else { + map_maxX = 0; + map_maxY = 0; + } + + globalOutputStream() << "SunPlug: calculating optimal coordinates\n"; // write to console that we are calculating the coordinates + GetOptimalCoordinates(&levelBoundingBox); // calculate optimal mapcoords with the dimensions of the level bounding box + globalOutputStream() << "SunPlug: adviced mapcoordsmins=" << minX << " " << maxY << "\n"; // console info about mapcoordsmins + globalOutputStream() << "SunPlug: adviced mapcoordsmaxs=" << maxX << " " << minY << "\n"; // console info about mapcoordsmaxs + + spinner_adj_MinX = (GtkAdjustment *)gtk_adjustment_new(map_minX, -65536.0, 65536.0, 1.0, 5.0, 5.0); // create adjustment for value and range of minimum x value + spinner_adj_MinY = (GtkAdjustment *)gtk_adjustment_new(map_minY, -65536.0, 65536.0, 1.0, 5.0, 5.0); // create adjustment for value and range of minimum y value + spinner_adj_MaxX = (GtkAdjustment *)gtk_adjustment_new(map_maxX, -65536.0, 65536.0, 1.0, 5.0, 5.0); // create adjustment for value and range of maximum x value + spinner_adj_MaxY = (GtkAdjustment *)gtk_adjustment_new(map_maxY, -65536.0, 65536.0, 1.0, 5.0, 5.0); // create adjustment for value and range of maximum y value + + button = gtk_button_new_with_label("Get optimal mapcoords"); // create button with text + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(input_optimal), NULL); // connect button with callback function + gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 2); // insert button into vbox + + gtk_box_pack_start(GTK_BOX(vbox), gtk_hseparator_new(), FALSE, FALSE, 2); // insert separator into vbox + + table = gtk_table_new(4, 3, TRUE); // create table + gtk_table_set_row_spacings(GTK_TABLE(table), 8); // set row spacings + gtk_table_set_col_spacings(GTK_TABLE(table), 8); // set column spacings + gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 2); // insert table into vbox + + label = gtk_label_new("x"); // create label + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // align text to the left side + gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 0, 1); // insert label into table + + label = gtk_label_new("y"); // create label + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // align text to the left side + gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, 0, 1); // insert label into table + + label = gtk_label_new("mapcoordsmins"); // create label + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // align text to the left side + gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2); // insert label into table + + spinnerMinX = gtk_spin_button_new(spinner_adj_MinX, 1.0, 0); // create textbox wiht value spin, value and value range + gtk_table_attach_defaults(GTK_TABLE(table), spinnerMinX, 1, 2, 1, 2); // insert spinbox into table + + spinnerMinY = gtk_spin_button_new(spinner_adj_MinY, 1.0, 0); // create textbox wiht value spin, value and value range + gtk_table_attach_defaults(GTK_TABLE(table), spinnerMinY, 2, 3, 1, 2); // insert spinbox into table + + label = gtk_label_new("mapcoordsmaxs"); // create label + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // align text to the left side + gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3); // insert label into table + + spinnerMaxX = gtk_spin_button_new(spinner_adj_MaxX, 1.0, 0); // create textbox wiht value spin, value and value range + gtk_table_attach_defaults(GTK_TABLE(table), spinnerMaxX, 1, 2, 2, 3); // insert spinbox into table + + spinnerMaxY = gtk_spin_button_new(spinner_adj_MaxY, 1.0, 0); // create textbox wiht value spin, value and value range + gtk_table_attach_defaults(GTK_TABLE(table), spinnerMaxY, 2, 3, 2, 3); // insert spinbox into table + + // put the references to the spinboxes and the worldspawn into the global exchange + msp.spinner1 = GTK_SPIN_BUTTON(spinnerMinX); + msp.spinner2 = GTK_SPIN_BUTTON(spinnerMinY); + msp.spinner3 = GTK_SPIN_BUTTON(spinnerMaxX); + msp.spinner4 = GTK_SPIN_BUTTON(spinnerMaxY); + msp.worldspawn = theWorldspawn; + + button = gtk_button_new_with_label("Set"); // create button with text + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(set_coordinates), NULL); // connect button with callback function + gtk_table_attach_defaults(GTK_TABLE(table), button, 1, 2, 3, 4); // insert button into table + + button = gtk_button_new_with_label("Cancel"); // create button with text + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(close_window), NULL); // connect button with callback function + gtk_table_attach_defaults(GTK_TABLE(table), button, 2, 3, 3, 4); // insert button into table + } else { + globalOutputStream() << "SunPlug: no worldspawn found!\n"; // output error to console + + label = gtk_label_new("ERROR: No worldspawn was found in the map!\nIn order to use this tool the map must have at least one brush in the worldspawn. "); // create a label + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // text align left + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 2); // insert the label in the box + + button = gtk_button_new_with_label("OK"); // create a button with text + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(close_window), NULL); // connect the click event to close the window + gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 2); // insert the button in the box + } + + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); // center the window + gtk_widget_show_all(window); // show the window and all subelements +} \ No newline at end of file diff --git a/contrib/sunplug/sunplug.def b/contrib/sunplug/sunplug.def new file mode 100644 index 00000000..6d260776 --- /dev/null +++ b/contrib/sunplug/sunplug.def @@ -0,0 +1,7 @@ +; sunplug.def : Declares the module parameters for the DLL. + +LIBRARY "SUNPLUG" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/contrib/sunplug/sunplug.h b/contrib/sunplug/sunplug.h new file mode 100644 index 00000000..9649a743 --- /dev/null +++ b/contrib/sunplug/sunplug.h @@ -0,0 +1,25 @@ +/* +Sunplug plugin for GtkRadiant +Copyright (C) 2004 Topsun +Thanks to SPoG for help! + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef _PLUGIN_SUNPLUG_ + #define _PLUGIN_SUNPLUG_ + + +#endif // _PLUGIN_SUNPLUG_ diff --git a/contrib/sunplug/sunplug.vcproj b/contrib/sunplug/sunplug.vcproj new file mode 100644 index 00000000..516a2d74 --- /dev/null +++ b/contrib/sunplug/sunplug.vcproj @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/ufoaiplug/bitmaps/ufoai_actorclip.bmp b/contrib/ufoaiplug/bitmaps/ufoai_actorclip.bmp new file mode 100644 index 0000000000000000000000000000000000000000..43bdf933ef5cc94894002cf993526f9e78802b68 GIT binary patch literal 1014 zcmZ?r{l?4y24+A~1BeBHm>-B485Dpd1H%EJybw18g9X6E|NjgOxN$?n|NrOk~XAnpR<79gGr#Hjj{l8*dOPCoj-wDc9&{?yb{|NZ=C1Jzvu;ypmT35Y>< suLNRH7@_KixdWYssl!F1t4HVK(gzbqSC7tzsl!F1t4HVK(gza<0AuYzS^xk5 literal 0 HcmV?d00001 diff --git a/contrib/ufoaiplug/bitmaps/ufoai_level1.bmp b/contrib/ufoaiplug/bitmaps/ufoai_level1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..80a4b2996e9dbcf76cec18e761c746dfb15c3fb2 GIT binary patch literal 198 zcmZ?rJ;ne5en3hChy{R{ABY(lSb!u0Lj@2EaYHa0kPX5h@c;jR1`xCb!Z#vs82--s e3y}bljDlbwfS`eH1V76Jf4u@TV# literal 0 HcmV?d00001 diff --git a/contrib/ufoaiplug/bitmaps/ufoai_level2.bmp b/contrib/ufoaiplug/bitmaps/ufoai_level2.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e84e254434fe221653da07189d16146fa36aa9ba GIT binary patch literal 198 zcmYj~!3}^g3&148an7_`^KR!az*Z6{=t-svwbbt&=GG@gU+PRw`2?mC&&Q z7c~!|o^MHEW>9X-vJ}+2MuRm?9{Ho2{CkVc;Dq`zL>ZzCC(Dn%DGO~*`|aQhD#j6R literal 0 HcmV?d00001 diff --git a/contrib/ufoaiplug/bitmaps/ufoai_level3.bmp b/contrib/ufoaiplug/bitmaps/ufoai_level3.bmp new file mode 100644 index 0000000000000000000000000000000000000000..141c32457872bce1579e51417c3b8afa73c0b01f GIT binary patch literal 198 zcmY*Su?>JQ409#K!a##Kf+twm;T~?`Ag<{PRd5n2gd@kbqomt@BXCd_GLs{j(6Iw2 zISs*{Z=*p~VYRZ#Tu`qX1NJ12{3)-B&?jq=HVF}a literal 0 HcmV?d00001 diff --git a/contrib/ufoaiplug/bitmaps/ufoai_level6.bmp b/contrib/ufoaiplug/bitmaps/ufoai_level6.bmp new file mode 100644 index 0000000000000000000000000000000000000000..418fdd280dc849add3cc4d3d307e245e0ef77906 GIT binary patch literal 198 zcmZ?rJ;ne5en3hChy{R{ABY(lSb!u0Lj@2EaYHa0kPX5h@c;jR1`xCb!Z#vs82--s y3y}bljDlbwfJ8Ge2nquE0*qin5XfTy@iAzaJWM^vAcz)_W}q&JIS}(<76Je$4-s1c literal 0 HcmV?d00001 diff --git a/contrib/ufoaiplug/bitmaps/ufoai_level7.bmp b/contrib/ufoaiplug/bitmaps/ufoai_level7.bmp new file mode 100644 index 0000000000000000000000000000000000000000..d82dab61c387a5f974bad9a52dde617aaf298b81 GIT binary patch literal 198 zcmZ?rJ;ne5en3hChy{R{ABY(lSb!u0Lj@2EaYHa0kPX5h@c;jR1`xCb!Z#vs82--s y3y}bljDlbwfJ8F@ML>Lz9EgTvpafVQoMwdZfr4Oh7%j*E;)AS!h=a_7SqK0^mJzT3 literal 0 HcmV?d00001 diff --git a/contrib/ufoaiplug/bitmaps/ufoai_level8.bmp b/contrib/ufoaiplug/bitmaps/ufoai_level8.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c3809538b01aa2cc6b430fa75a63c18c842e401c GIT binary patch literal 198 zcmZ?rJ;ne5en3hChy{R{ABY(lSb!u0Lj@2EaYHa0kPX5h@c;jR1`xCb!Z#vs82--s r3y}bljDlbwfS`e-B485Dpd1H%EJybw18g9U)Ze}?-1cyYu2|NrsfVWR&I zL*D=O4B`I|G9>)p$q@7ZAw$9c!wiZ4_r~{u@v6}J|Ch7o{h#ES`~OeHbd_J0*hcE4*6eg8}q-_Dfxerq38cosnap^gVe5L2>X8k zXvTJi=>Kbh?6(YM{|_fl`hPfS(*Lc|?f=(?H~xQGyc*1h$BD!I|Nkk$SmJhjOy~b^ zH9P*#@hkm*nj!W7R)(nmPZ$cZrxPst8yph;&+sYyUuGQv3=8}JUm2?YcQLpUt$$~1 h_y23z3;u)XvkYlq`=2os5v?B{HXyeRlk|zp9{{z5lzjjI literal 0 HcmV?d00001 diff --git a/contrib/ufoaiplug/bitmaps/ufoai_stepon.bmp b/contrib/ufoaiplug/bitmaps/ufoai_stepon.bmp new file mode 100644 index 0000000000000000000000000000000000000000..828449efa53df7b96b9fd81427b686e52a89f5b7 GIT binary patch literal 1014 zcmZ?r{l?4y24+A~1BeBHm>-B485Dpd1H%EJybw18g9SiD!h-+!aNV9~|M6jbdO>pd z^n&C@y#9c;{|qjL{}~*z{xf*hK(I;pe+JL0{|v50|NTn)|Cg=5{y(T{>i?L|6=2#m zz3zW_)BOJ+S~0TzzhO}3f4kZh|Mjycfw6*n(trDkdH?lOJOAsXcK?@mjsFi8>pcXf z#Oc-M}`?*Qiy0PGP{y8r+H literal 0 HcmV?d00001 diff --git a/contrib/ufoaiplug/bitmaps/ufoai_weaponclip.bmp b/contrib/ufoaiplug/bitmaps/ufoai_weaponclip.bmp new file mode 100644 index 0000000000000000000000000000000000000000..fe6b3485852ca68332315bc63597fea01b897579 GIT binary patch literal 1014 zcmZ?r{l?4y24+A~1BeBHm>-B485Dpd1H%EJybw18g9Si@{r~^?aKrrn|M6jbdO>pd z^n&CDoqppB{~5G5{%0_~@t;Ay?tf13v;TWWPyAms{WFGtFx;;)`9Fi@i~kI^ z-~KZwG=SAR1T_3NbI$p1hu0H=%y&tUZ$$i4{-pX(Umfu7zl;s-6gqpL^f4|e?k)Tv>r literal 0 HcmV?d00001 diff --git a/contrib/ufoaiplug/ufoai.cpp b/contrib/ufoaiplug/ufoai.cpp new file mode 100644 index 00000000..e371e833 --- /dev/null +++ b/contrib/ufoaiplug/ufoai.cpp @@ -0,0 +1,234 @@ +/* +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ufoai.h" +#include "ufoai_level.h" +#include "ufoai_gtk.h" +#include "ufoai_filters.h" + +#include "debugging/debugging.h" + +#include "iplugin.h" + +#include "version.h" + +#include "string/string.h" +#include "modulesystem/singletonmodule.h" + +#include + +#define PLUGIN_VERSION "0.4" + +#include "ifilter.h" +#include "ibrush.h" +#include "iundo.h" // declaration of undo system +#include "ientity.h" // declaration of entity system +#include "iscenegraph.h" // declaration of datastructure of the map +#include "scenelib.h" // declaration of datastructure of the map +#include "qerplugin.h" // declaration to use other interfaces as a plugin +#include "ieclass.h" + +class UFOAIPluginDependencies : + public GlobalRadiantModuleRef, // basic class for all other module refs + public GlobalUndoModuleRef, // used to say radiant that something has changed and to undo that + public GlobalSceneGraphModuleRef, // necessary to handle data in the mapfile (change, retrieve data) + public GlobalEntityModuleRef, // to access and modify the entities + public GlobalEntityClassManagerModuleRef +{ +public: + UFOAIPluginDependencies(void) : + GlobalEntityModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("entities")), + GlobalEntityClassManagerModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("entityclass")) + { + } +}; + +namespace UFOAI +{ + GtkWindow* g_mainwnd; + + const char* init(void* hApp, void* pMainWidget) + { + g_mainwnd = GTK_WINDOW(pMainWidget); + return "Initializing GTKRadiant UFOAI plugin"; + } + const char* getName() + { + return "UFO:AI"; + } + const char* getCommandList() + { + /*GlobalRadiant().getGameName()*/ + return "About;-;Worldspawn reset;Worldspawn;Perform check;-;Level 1;Level 2;Level 3;Level 4;Level 5;Level 6;Level 7;Level 8;-;StepOn;ActorClip;WeaponClip;Nodraw"; + } + const char* getCommandTitleList() + { + return ""; + } + void dispatch(const char* command, float* vMin, float* vMax, bool bSingleBrush) + { + char *message = NULL; + if(string_equal(command, "About")) + { + GlobalRadiant().m_pfnMessageBox(GTK_WIDGET(g_mainwnd), + "UFO:AI Plugin (http://ufoai.sf.net)\nBuild: " __DATE__ "\nRadiant version: " RADIANT_VERSION "\nPlugin version: " PLUGIN_VERSION "\nAuthor: Martin Gerhardy (tlh2000/mattn)\n", "About", + eMB_OK, eMB_ICONDEFAULT); + } + else if(string_equal(command, "Level 1")) + { + filter_level(CONTENTS_LEVEL1); + } + else if(string_equal(command, "Level 2")) + { + filter_level(CONTENTS_LEVEL2); + } + else if(string_equal(command, "Level 3")) + { + filter_level(CONTENTS_LEVEL3); + } + else if(string_equal(command, "Worldspawn")) + { + assign_default_values_to_worldspawn(false, &message); + } + else if(string_equal(command, "Worldspawn reset")) + { + assign_default_values_to_worldspawn(true, &message); + } + else if(string_equal(command, "Perform check")) + { + check_map_values(&message); + } + else if(string_equal(command, "Level 4")) + { + filter_level(CONTENTS_LEVEL4); + } + else if(string_equal(command, "Level 5")) + { + filter_level(CONTENTS_LEVEL5); + } + else if(string_equal(command, "Level 6")) + { + filter_level(CONTENTS_LEVEL6); + } + else if(string_equal(command, "Level 7")) + { + filter_level(CONTENTS_LEVEL7); + } + else if(string_equal(command, "Level 8")) + { + filter_level(CONTENTS_LEVEL8); + } + else if(string_equal(command, "StepOn")) + { + filter_stepon(); + } + else if(string_equal(command, "ActorClip")) + { + filter_actorclip(); + } + else if(string_equal(command, "WeaponClip")) + { + filter_weaponclip(); + } + else if(string_equal(command, "NoDraw")) + { + filter_nodraw(); + } + + if (message != NULL) + { + GlobalRadiant().m_pfnMessageBox(GTK_WIDGET(g_mainwnd), + message, "Note", + eMB_OK, eMB_ICONDEFAULT); + } + SceneChangeNotify(); + } +} // namespace + + +class UFOAIModule : public TypeSystemRef +{ + _QERPluginTable m_plugin; +public: + typedef _QERPluginTable Type; + STRING_CONSTANT(Name, "UFO:AI"); + + UFOAIModule() + { + m_plugin.m_pfnQERPlug_Init = &UFOAI::init; + m_plugin.m_pfnQERPlug_GetName = &UFOAI::getName; + m_plugin.m_pfnQERPlug_GetCommandList = &UFOAI::getCommandList; + m_plugin.m_pfnQERPlug_GetCommandTitleList = &UFOAI::getCommandTitleList; + m_plugin.m_pfnQERPlug_Dispatch = &UFOAI::dispatch; + } + _QERPluginTable* getTable() + { + return &m_plugin; + } +}; + +typedef SingletonModule SingletonUFOAIModule; + +SingletonUFOAIModule g_UFOAIModule; + + +class UFOAIToolbarDependencies : public ModuleRef<_QERPluginTable> +{ +public: + UFOAIToolbarDependencies() : ModuleRef<_QERPluginTable>("UFO:AI") + { + } +}; + +class UFOAIToolbarModule : public TypeSystemRef +{ + _QERPlugToolbarTable m_table; +public: + typedef _QERPlugToolbarTable Type; + STRING_CONSTANT(Name, "UFO:AI"); + + UFOAIToolbarModule() + { + if (!strcmp(GlobalRadiant().getGameDescriptionKeyValue("name"), "UFO:Alien Invasion")) { + m_table.m_pfnToolbarButtonCount = ToolbarButtonCount; + m_table.m_pfnGetToolbarButton = GetToolbarButton; + } + else + { + m_table.m_pfnToolbarButtonCount = ToolbarNoButtons; + m_table.m_pfnGetToolbarButton = GetToolbarNoButton; + } + } + _QERPlugToolbarTable* getTable() + { + return &m_table; + } +}; + +typedef SingletonModule SingletonUFOAIToolbarModule; + +SingletonUFOAIToolbarModule g_UFOAIToolbarModule; + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer& server) +{ + initialiseModule(server); + + g_UFOAIModule.selfRegister(); + g_UFOAIToolbarModule.selfRegister(); +} diff --git a/contrib/ufoaiplug/ufoai.def b/contrib/ufoaiplug/ufoai.def new file mode 100644 index 00000000..03e07507 --- /dev/null +++ b/contrib/ufoaiplug/ufoai.def @@ -0,0 +1,7 @@ +; sample.def : Declares the module parameters for the DLL. + +LIBRARY "UFOAI" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/contrib/ufoaiplug/ufoai.h b/contrib/ufoaiplug/ufoai.h new file mode 100644 index 00000000..7193fa0c --- /dev/null +++ b/contrib/ufoaiplug/ufoai.h @@ -0,0 +1,22 @@ +/* +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_UFOAI_H) +#define INCLUDED_UFOAI_H + +#endif diff --git a/contrib/ufoaiplug/ufoai.vcproj b/contrib/ufoaiplug/ufoai.vcproj new file mode 100644 index 00000000..7f63b261 --- /dev/null +++ b/contrib/ufoaiplug/ufoai.vcproj @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/ufoaiplug/ufoai_filters.cpp b/contrib/ufoaiplug/ufoai_filters.cpp new file mode 100644 index 00000000..869c4eb8 --- /dev/null +++ b/contrib/ufoaiplug/ufoai_filters.cpp @@ -0,0 +1,342 @@ +/* +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ufoai_filters.h" + +#include "ibrush.h" +#include "ientity.h" +#include "iscenegraph.h" + +// believe me, i'm sorry +#include "../../radiant/brush.h" + +#include "generic/callback.h" + +#include + +bool actorclip_active = false; +bool stepon_active = false; +bool nodraw_active = false; +bool weaponclip_active = false; +int level_active = 0; + +// TODO: This should be added to ibrush.h +// like already done for Node_getEntity in ientity.h +// FIXME: Doesn't belong here +inline Brush* Node_getBrush(scene::Node& node) +{ + return NodeTypeCast::cast(node); +} + +void hide_node(scene::Node& node, bool hide) +{ + hide + ? node.enable(scene::Node::eHidden) + : node.disable(scene::Node::eHidden); +} + +typedef std::list entitylist_t; + +class EntityFindByName : public scene::Graph::Walker +{ + const char* m_name; + entitylist_t& m_entitylist; + /* this starts at 1 << level */ + int m_flag; + int m_hide; +public: + EntityFindByName(const char* name, entitylist_t& entitylist, int flag, bool hide) + : m_name(name), m_entitylist(entitylist), m_flag(flag), m_hide(hide) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + int spawnflagsInt; + Entity* entity = Node_getEntity(path.top()); + if (entity != 0) + { + if (string_equal(m_name, entity->getKeyValue("classname"))) + { + const char *spawnflags = entity->getKeyValue("spawnflags"); + globalOutputStream() << "spawnflags for " << m_name << ": " << spawnflags << ".\n"; + + if (!string_empty(spawnflags)) { + spawnflagsInt = atoi(spawnflags); + if (!(spawnflagsInt & m_flag)) + { + hide_node(path.top(), m_hide); // hide/unhide + m_entitylist.push_back(entity); + } + } + else + { + globalOutputStream() << "UFO:AI: Warning: no spawnflags for " << m_name << ".\n"; + } + } + } + return true; + } +}; + +class ForEachFace : public BrushVisitor +{ + Brush &m_brush; +public: + mutable int m_contentFlagsVis; + mutable int m_surfaceFlagsVis; + + ForEachFace(Brush& brush) + : m_brush(brush) + { + m_contentFlagsVis = -1; + m_surfaceFlagsVis = -1; + } + + void visit(Face& face) const + { +#if _DEBUG + if (m_surfaceFlagsVis < 0) + m_surfaceFlagsVis = face.getShader().m_flags.m_surfaceFlags; + else if (m_surfaceFlagsVis >= 0 && m_surfaceFlagsVis != face.getShader().m_flags.m_surfaceFlags) + globalOutputStream() << "Faces with different surfaceflags at brush\n"; + if (m_contentFlagsVis < 0) + m_contentFlagsVis = face.getShader().m_flags.m_contentFlags; + else if (m_contentFlagsVis >= 0 && m_contentFlagsVis != face.getShader().m_flags.m_contentFlags) + globalOutputStream() << "Faces with different contentflags at brush\n"; +#else + m_surfaceFlagsVis = face.getShader().m_flags.m_surfaceFlags; + m_contentFlagsVis = face.getShader().m_flags.m_contentFlags; +#endif + } +}; + +typedef std::list brushlist_t; + +class BrushGetLevel : public scene::Graph::Walker +{ + brushlist_t& m_brushlist; + int m_flag; + bool m_content; // if true - use m_contentFlags - otherwise m_surfaceFlags + mutable bool m_notset; + mutable bool m_hide; +public: + BrushGetLevel(brushlist_t& brushlist, int flag, bool content, bool notset, bool hide) + : m_brushlist(brushlist), m_flag(flag), m_content(content), m_notset(notset), m_hide(hide) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Brush* brush = Node_getBrush(path.top()); + if (brush != 0) + { + ForEachFace faces(*brush); + brush->forEachFace(faces); + // contentflags? + if (m_content) + { + // are any flags set? + if (faces.m_contentFlagsVis > 0) + { + // flag should not be set + if (m_notset && (!(faces.m_contentFlagsVis & m_flag))) + { + hide_node(path.top(), m_hide); + m_brushlist.push_back(brush); + } + // check whether flag is set + else if (!m_notset && ((faces.m_contentFlagsVis & m_flag))) + { + hide_node(path.top(), m_hide); + m_brushlist.push_back(brush); + } + } + } + // surfaceflags? + else + { + // are any flags set? + if (faces.m_surfaceFlagsVis > 0) + { + // flag should not be set + if (m_notset && (!(faces.m_surfaceFlagsVis & m_flag))) + { + hide_node(path.top(), m_hide); + m_brushlist.push_back(brush); + } + // check whether flag is set + else if (!m_notset && ((faces.m_surfaceFlagsVis & m_flag))) + { + hide_node(path.top(), m_hide); + m_brushlist.push_back(brush); + } + } + } + + } + return true; + } +}; + +/** + * @brief Activates the level filter for the given level + * @param[in] level Which level to show? + * @todo Entities + */ +void filter_level(int flag) +{ + int level; + brushlist_t brushes; + entitylist_t entities; + + level = (flag >> 8); + + if (level_active) + { + GlobalSceneGraph().traverse(BrushGetLevel(brushes, (level_active << 8), true, true, false)); + GlobalSceneGraph().traverse(EntityFindByName("func_door", entities, level_active, false)); + GlobalSceneGraph().traverse(EntityFindByName("func_breakable", entities, level_active, false)); + GlobalSceneGraph().traverse(EntityFindByName("misc_model", entities, level_active, false)); + GlobalSceneGraph().traverse(EntityFindByName("misc_particle", entities, level_active, false)); + entities.erase(entities.begin(), entities.end()); + brushes.erase(brushes.begin(), brushes.end()); + if (level_active == level) + { + level_active = 0; + // just disabĺe level filter + return; + } + } + level_active = level; + globalOutputStream() << "UFO:AI: level_active: " << level_active << ", flag: " << flag << ".\n"; + + // first all brushes + GlobalSceneGraph().traverse(BrushGetLevel(brushes, flag, true, true, true)); + + // now all entities + GlobalSceneGraph().traverse(EntityFindByName("func_door", entities, level, true)); + GlobalSceneGraph().traverse(EntityFindByName("func_breakable", entities, level, true)); + GlobalSceneGraph().traverse(EntityFindByName("misc_model", entities, level, true)); + GlobalSceneGraph().traverse(EntityFindByName("misc_particle", entities, level, true)); + +#ifdef _DEBUG + if (brushes.empty()) + { + globalOutputStream() << "UFO:AI: No brushes.\n"; + } + else + { + globalOutputStream() << "UFO:AI: Found " << Unsigned(brushes.size()) << " brushes.\n"; + } + + // now let's filter all entities like misc_model, func_breakable and func_door that have the spawnflags set + if (entities.empty()) + { + globalOutputStream() << "UFO:AI: No entities.\n"; + } + else + { + globalOutputStream() << "UFO:AI: Found " << Unsigned(entities.size()) << " entities.\n"; + } +#endif +} + +void filter_stepon (void) +{ + if (stepon_active) { + stepon_active = false; + } else { + stepon_active = true; + } + brushlist_t brushes; + GlobalSceneGraph().traverse(BrushGetLevel(brushes, CONTENTS_STEPON, true, false, stepon_active)); + + if (brushes.empty()) + { + globalOutputStream() << "UFO:AI: No brushes.\n"; + } + else + { + globalOutputStream() << "UFO:AI: Hiding " << Unsigned(brushes.size()) << " stepon brushes.\n"; + } +} + +void filter_nodraw (void) +{ + if (nodraw_active) { + nodraw_active = false; + } else { + nodraw_active = true; + } + brushlist_t brushes; + GlobalSceneGraph().traverse(BrushGetLevel(brushes, SURF_NODRAW, false, false, nodraw_active)); + +#ifdef _DEBUG + if (brushes.empty()) + { + globalOutputStream() << "UFO:AI: No brushes.\n"; + } + else + { + globalOutputStream() << "UFO:AI: Hiding " << Unsigned(brushes.size()) << " nodraw brushes.\n"; + } +#endif +} + +void filter_actorclip (void) +{ + if (actorclip_active) { + actorclip_active = false; + } else { + actorclip_active = true; + } + brushlist_t brushes; + GlobalSceneGraph().traverse(BrushGetLevel(brushes, CONTENTS_ACTORCLIP, true, false, actorclip_active)); + +#ifdef _DEBUG + if (brushes.empty()) + { + globalOutputStream() << "UFO:AI: No brushes.\n"; + } + else + { + globalOutputStream() << "UFO:AI: Hiding " << Unsigned(brushes.size()) << " actorclip brushes.\n"; + } +#endif +} + +void filter_weaponclip (void) +{ + if (weaponclip_active) { + weaponclip_active = false; + } else { + weaponclip_active = true; + } + brushlist_t brushes; + GlobalSceneGraph().traverse(BrushGetLevel(brushes, CONTENTS_WEAPONCLIP, true, false, weaponclip_active)); + +#ifdef _DEBUG + if (brushes.empty()) + { + globalOutputStream() << "UFO:AI: No brushes.\n"; + } + else + { + globalOutputStream() << "UFO:AI: Hiding " << Unsigned(brushes.size()) << " weaponclip brushes.\n"; + } +#endif +} diff --git a/contrib/ufoaiplug/ufoai_filters.h b/contrib/ufoaiplug/ufoai_filters.h new file mode 100644 index 00000000..7d581140 --- /dev/null +++ b/contrib/ufoaiplug/ufoai_filters.h @@ -0,0 +1,42 @@ +/* +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_FILTERS_H) +#define INCLUDED_FILTERS_H + +void filter_level(int flag); +void filter_stepon(void); +void filter_actorclip(void); +void filter_weaponclip(void); +void filter_nodraw(void); + +#define SURF_NODRAW 0x80 + +#define CONTENTS_LEVEL8 0x8000 +#define CONTENTS_LEVEL7 0x4000 +#define CONTENTS_LEVEL6 0x2000 +#define CONTENTS_LEVEL5 0x1000 +#define CONTENTS_LEVEL4 0x0800 +#define CONTENTS_LEVEL3 0x0400 +#define CONTENTS_LEVEL2 0x0200 +#define CONTENTS_LEVEL1 0x0100 +#define CONTENTS_ACTORCLIP 0x10000 +#define CONTENTS_WEAPONCLIP 0x2000000 +#define CONTENTS_STEPON 0x40000000 + +#endif diff --git a/contrib/ufoaiplug/ufoai_gtk.cpp b/contrib/ufoaiplug/ufoai_gtk.cpp new file mode 100644 index 00000000..afadc7ad --- /dev/null +++ b/contrib/ufoaiplug/ufoai_gtk.cpp @@ -0,0 +1,207 @@ +/* +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ufoai_gtk.h" +#include "ufoai_filters.h" + +#include "itoolbar.h" +#include "iscenegraph.h" + +#include + +/** + * GTK callback functions + */ + +class UFOAIGtk +{ + GtkWindow* m_gtk_window; +public: + UFOAIGtk(void* gtk_window) : m_gtk_window((GtkWindow*)gtk_window) + { + } +}; + +/** + * @brief If you return FALSE in the "delete_event" signal handler, + * GTK will emit the "destroy" signal. Returning TRUE means + * you don't want the window to be destroyed. + * This is useful for popping up 'are you sure you want to quit?' + * type dialogs. + */ +static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) +{ + return FALSE; +} + +/** + * @brief destroy widget if destroy signal is passed to widget + */ +static void destroy(GtkWidget *widget, gpointer data) +{ + gtk_widget_destroy(widget); +} + +/** + * @brief function for close button to destroy the toplevel widget + */ +static void close_window(GtkWidget *widget, gpointer data) +{ + gtk_widget_destroy(gtk_widget_get_toplevel(widget)); +} + +/* =============================== */ + +#define NUM_TOOLBARBUTTONS 12 + +/** + * @brief + */ +std::size_t ToolbarButtonCount(void) +{ + return NUM_TOOLBARBUTTONS; +} + +/** + * @brief Used if the ufo plugin should not be visible (at least the toolbar stuff) + */ +std::size_t ToolbarNoButtons (void) +{ + return 0; +} + +/** + * @brief + */ +class CUFOAIToolbarButton : public IToolbarButton +{ +public: + virtual const char* getImage() const + { + switch( mIndex ) + { + case 0: return "ufoai_level1.bmp"; + case 1: return "ufoai_level2.bmp"; + case 2: return "ufoai_level3.bmp"; + case 3: return "ufoai_level4.bmp"; + case 4: return "ufoai_level5.bmp"; + case 5: return "ufoai_level6.bmp"; + case 6: return "ufoai_level7.bmp"; + case 7: return "ufoai_level8.bmp"; + case 8: return "ufoai_stepon.bmp"; + case 9: return "ufoai_actorclip.bmp"; + case 10: return "ufoai_weaponclip.bmp"; + case 11: return "ufoai_nodraw.bmp"; + } + return NULL; + } + virtual EType getType() const + { + switch( mIndex ) + { +/* case 3: return eButton;*/ + case 8: return eToggleButton; + case 9: return eToggleButton; + case 10: return eToggleButton; + case 11: return eToggleButton; + default: return eButton; + } + } + virtual const char* getText() const + { + switch( mIndex ) + { + case 0: return "Level 1"; + case 1: return "Level 2"; + case 2: return "Level 3"; + case 3: return "Level 4"; + case 4: return "Level 5"; + case 5: return "Level 6"; + case 6: return "Level 7"; + case 7: return "Level 8"; + case 8: return "Stepon"; + case 9: return "Actorclip"; + case 10: return "Weaponclip"; + case 11: return "Nodraw"; + } + return NULL; + } + virtual const char* getTooltip() const + { + switch( mIndex ) + { + case 0: return "Show only level 1"; + case 1: return "Show only level 2"; + case 2: return "Show only level 3"; + case 3: return "Show only level 4"; + case 4: return "Show only level 5"; + case 5: return "Show only level 6"; + case 6: return "Show only level 7"; + case 7: return "Show only level 8"; + case 8: return "Hide stepon brushes"; + case 9: return "Hide actorclip brushes"; + case 10: return "Hide weaponclip brushes"; + case 11: return "Hide nodraw brushes"; + } + return NULL; + } + + virtual void activate() const + { + switch( mIndex ) + { + case 0: filter_level(CONTENTS_LEVEL1); break; + case 1: filter_level(CONTENTS_LEVEL2); break; + case 2: filter_level(CONTENTS_LEVEL3); break; + case 3: filter_level(CONTENTS_LEVEL4); break; + case 4: filter_level(CONTENTS_LEVEL5); break; + case 5: filter_level(CONTENTS_LEVEL6); break; + case 6: filter_level(CONTENTS_LEVEL7); break; + case 7: filter_level(CONTENTS_LEVEL8); break; + case 8: filter_stepon(); break; + case 9: filter_actorclip(); break; + case 10: filter_weaponclip(); break; + case 11: filter_nodraw(); break; + } + SceneChangeNotify(); + } + + std::size_t mIndex; +}; + +/** + * @brief + */ +CUFOAIToolbarButton g_ufoaiToolbarButtons[NUM_TOOLBARBUTTONS]; + +/** + * @brief + */ +const IToolbarButton* GetToolbarButton(std::size_t index) +{ + g_ufoaiToolbarButtons[index].mIndex = index; + return &g_ufoaiToolbarButtons[index]; +} + +/** + * @brief + */ +const IToolbarButton* GetToolbarNoButton(std::size_t index) +{ + return NULL; +} diff --git a/contrib/ufoaiplug/ufoai_gtk.h b/contrib/ufoaiplug/ufoai_gtk.h new file mode 100644 index 00000000..ef2ae3c3 --- /dev/null +++ b/contrib/ufoaiplug/ufoai_gtk.h @@ -0,0 +1,30 @@ +/* +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_UFOAI_GTK_H) +#define INCLUDED_UFOAI_GTK_H + +#include "itoolbar.h" +#include + +const IToolbarButton* GetToolbarButton(std::size_t index); +const IToolbarButton* GetToolbarNoButton(std::size_t index); +std::size_t ToolbarButtonCount(void); +std::size_t ToolbarNoButtons(void); + +#endif diff --git a/contrib/ufoaiplug/ufoai_level.cpp b/contrib/ufoaiplug/ufoai_level.cpp new file mode 100644 index 00000000..3a36b258 --- /dev/null +++ b/contrib/ufoaiplug/ufoai_level.cpp @@ -0,0 +1,293 @@ +/* +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ufoai_level.h" +#include "ufoai_filters.h" + +#include "ibrush.h" +#include "ientity.h" +#include "iscenegraph.h" + +#include "string/string.h" +#include + +class Level; + +/** + * @brief find entities by class + * @note from radiant/map.cpp + */ +class EntityFindByClassname : public scene::Graph::Walker +{ + const char* m_name; + Entity*& m_entity; +public: + EntityFindByClassname(const char* name, Entity*& entity) : m_name(name), m_entity(entity) + { + m_entity = 0; + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(m_entity == 0) + { + Entity* entity = Node_getEntity(path.top()); + if(entity != 0 && string_equal(m_name, entity->getKeyValue("classname"))) + { + m_entity = entity; + } + } + return true; + } +}; + +/** + * @brief + */ +Entity* Scene_FindEntityByClass(const char* name) +{ + Entity* entity = NULL; + GlobalSceneGraph().traverse(EntityFindByClassname(name, entity)); + return entity; +} + +/** + * @brief finds start positions + */ +class EntityFindFlags : public scene::Graph::Walker +{ + const char *m_classname; + const char *m_flag; + int *m_count; + + public: + EntityFindFlags(const char *classname, const char *flag, int *count) : m_classname(classname), m_flag(flag), m_count(count) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + const char *str; + Entity* entity = Node_getEntity(path.top()); + if(entity != 0 && string_equal(m_classname, entity->getKeyValue("classname"))) + { + str = entity->getKeyValue(m_flag); + if (string_empty(str)) + { + (*m_count)++; + } + } + return true; + } +}; + + +/** + * @brief finds start positions + */ +class EntityFindTeams : public scene::Graph::Walker +{ + const char *m_classname; + int *m_count; + int *m_team; + +public: + EntityFindTeams(const char *classname, int *count, int *team) : m_classname(classname), m_count(count), m_team(team) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + const char *str; + Entity* entity = Node_getEntity(path.top()); + if(entity != 0 && string_equal(m_classname, entity->getKeyValue("classname"))) + { + if (m_count) + (*m_count)++; + // now get the highest teamnum + if (m_team) + { + str = entity->getKeyValue("team"); + if (!string_empty(str)) + { + if (atoi(str) > *m_team) + (*m_team) = atoi(str); + } + } + } + return true; + } +}; + +/** + * @brief + */ +void get_team_count (const char *classname, int *count, int *team) +{ + GlobalSceneGraph().traverse(EntityFindTeams(classname, count, team)); + globalOutputStream() << "UFO:AI: classname: " << classname << ": #" << (*count) << "\n"; +} + +/** + * @brief Some default values to worldspawn like maxlevel and so on + */ +void assign_default_values_to_worldspawn (bool override, char **returnMsg) +{ + static char message[1024]; + Entity* worldspawn; + int teams = 0; + int count = 0; + char str[64]; + + worldspawn = Scene_FindEntityByClass("worldspawn"); + if (!worldspawn) + { + globalOutputStream() << "UFO:AI: Could not find worldspawn.\n"; + *returnMsg = "Could not find worldspawn"; + return; + } + + *message = '\0'; + *str = '\0'; + + if (override || string_empty(worldspawn->getKeyValue("maxlevel"))) + { + // TODO: Get highest brush - a level has 64 units + worldspawn->setKeyValue("maxlevel", "5"); + snprintf(&message[strlen(message)], sizeof(message) - 1 - strlen(message), "Set maxlevel to: %s", worldspawn->getKeyValue("maxlevel")); + } + + if (override || string_empty(worldspawn->getKeyValue("maxteams"))) + { + get_team_count("info_player_start", &count, &teams); + if (teams) + { + snprintf(str, sizeof(str) - 1, "%i", teams); + worldspawn->setKeyValue("maxteams", str); + snprintf(&message[strlen(message)], sizeof(message) - 1 - strlen(message), "Set maxteams to: %s", worldspawn->getKeyValue("maxteams")); + } + if (count < 16) + { + snprintf(&message[strlen(message)], sizeof(message) - 1 - strlen(message), "You should at least place 16 info_player_start"); + } + } + + // no errors - no warnings + if (!strlen(message)) + return; + + *returnMsg = message; +} + +/** + * @brief + */ +int check_entity_flags (const char *classname, const char *flag) +{ + int count; + + /* init this with 0 every time we browse the tree */ + count = 0; + + GlobalSceneGraph().traverse(EntityFindFlags(classname, flag, &count)); + return count; +} + +/** + * @brief Will check e.g. the map entities for valid values + * @todo: check for maxlevel + */ +void check_map_values (char **returnMsg) +{ + static char message[1024]; + int count = 0; + int teams = 0; + int ent_flags; + Entity* worldspawn; + char str[64]; + + worldspawn = Scene_FindEntityByClass("worldspawn"); + if (!worldspawn) + { + globalOutputStream() << "UFO:AI: Could not find worldspawn.\n"; + *returnMsg = "Could not find worldspawn"; + return; + } + + *message = '\0'; + *str = '\0'; + + // multiplayer start positions + get_team_count("info_player_start", &count, &teams); + if (!count) + strncat(message, "No multiplayer start positions (info_player_start)\n", sizeof(message) - 1); + + // singleplayer map? + count = 0; + get_team_count("info_human_start", &count, NULL); + if (!count) + strncat(message, "No singleplayer start positions (info_human_start)\n", sizeof(message) - 1); + + // singleplayer map? + count = 0; + get_team_count("info_2x2_start", &count, NULL); + if (!count) + strncat(message, "No singleplayer start positions for 2x2 units (info_2x2_start)\n", sizeof(message) - 1); + + // search for civilians + count = 0; + get_team_count("info_civilian_start", &count, NULL); + if (!count) + strncat(message, "No civilian start positions (info_civilian_start)\n", sizeof(message) - 1); + + // check maxlevel + if (string_empty(worldspawn->getKeyValue("maxlevel"))) + strncat(message, "Worldspawn: No maxlevel defined\n", sizeof(message) - 1); + else if (atoi(worldspawn->getKeyValue("maxlevel")) > 8) + { + strncat(message, "Worldspawn: Highest maxlevel is 8\n", sizeof(message) - 1); + worldspawn->setKeyValue("maxlevel", "8"); + } + + ent_flags = check_entity_flags("func_door", "spawnflags"); + if (ent_flags) + snprintf(&message[strlen(message)], sizeof(message) - 1 - strlen(message), "Found %i func_door with no spawnflags\n", ent_flags); + ent_flags = check_entity_flags("func_breakable", "spawnflags"); + if (ent_flags) + snprintf(&message[strlen(message)], sizeof(message) - 1 - strlen(message), "Found %i func_breakable with no spawnflags\n", ent_flags); + ent_flags = check_entity_flags("misc_sound", "spawnflags"); + if (ent_flags) + snprintf(&message[strlen(message)], sizeof(message) - 1 - strlen(message), "Found %i misc_sound with no spawnflags\n", ent_flags); + ent_flags = check_entity_flags("misc_model", "spawnflags"); + if (ent_flags) + snprintf(&message[strlen(message)], sizeof(message) - 1 - strlen(message), "Found %i misc_model with no spawnflags\n", ent_flags); + ent_flags = check_entity_flags("misc_particle", "spawnflags"); + if (ent_flags) + snprintf(&message[strlen(message)], sizeof(message) - 1 - strlen(message), "Found %i misc_particle with no spawnflags\n", ent_flags); + ent_flags = check_entity_flags("info_player_start", "team"); + if (ent_flags) + snprintf(&message[strlen(message)], sizeof(message) - 1 - strlen(message), "Found %i info_player_start with no team assigned\n!!Teamcount may change after you've fixed this\n", ent_flags); + ent_flags = check_entity_flags("light", "color"); + ent_flags = check_entity_flags("light", "_color"); + if (ent_flags) + snprintf(&message[strlen(message)], sizeof(message) - 1 - strlen(message), "Found %i lights with no color value\n", ent_flags); + + // no errors found + if (!strlen(message)) { + snprintf(message, sizeof(message) - 1, "No errors found - you are ready to compile the map now\n"); + } + + *returnMsg = message; +} diff --git a/contrib/ufoaiplug/ufoai_level.h b/contrib/ufoaiplug/ufoai_level.h new file mode 100644 index 00000000..ba75bd45 --- /dev/null +++ b/contrib/ufoaiplug/ufoai_level.h @@ -0,0 +1,26 @@ +/* +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_UFOAI_LEVEL_H) +#define INCLUDED_UFOAI_LEVEL_H + +void assign_default_values_to_worldspawn (bool override, char **returnMsg); +void check_map_values (char **returnMsg); +void get_team_count (const char *classname, int *count, int *team); + +#endif diff --git a/games/NexuizPack/games/nexuiz.game b/games/NexuizPack/games/nexuiz.game new file mode 100644 index 00000000..2e2bfcf1 --- /dev/null +++ b/games/NexuizPack/games/nexuiz.game @@ -0,0 +1,28 @@ + + + diff --git a/games/NexuizPack/gpl.txt b/games/NexuizPack/gpl.txt new file mode 100755 index 00000000..5b6e7c66 --- /dev/null +++ b/games/NexuizPack/gpl.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/games/NexuizPack/nexuiz.game/data/default_shaderlist.txt b/games/NexuizPack/nexuiz.game/data/default_shaderlist.txt new file mode 100644 index 00000000..4e5dea8b --- /dev/null +++ b/games/NexuizPack/nexuiz.game/data/default_shaderlist.txt @@ -0,0 +1,56 @@ +//**********************************************************************// +// shaderlist.txt modified for Q3Radiant // +// by Eutectic - 13 May 2000 // +// // +// Rev history: // +// 08/11/2000 +// TTimo - changed liquid to liquids and sky to skies for better // +// consistency with the directories and actual shader names // +// // +// Added the names of the new shader files created as a result // +// of the cleanup of the old shader files so Q3Radiant will read // +// and display all the shaders in the texture window. // +// // +// SHADER FILE NAMES ADDED: // +// base_door // +// gothic_button // +// gothic_door // +//**********************************************************************// + +// this file lists all the separate shader files + +common +dsi +e7 +el3dm1 +evil3 +evil4_techtrims +evil5 +evil6 +evil6_floors +evil6_lights +evil6_support +evil6_trims +evil6_walls +evil8_base +gfx +harlequin_sky +hell +kaznexctf2 +liquids +models +museum +official_cmp1 +organics +reaper +sav-graysky1 +sav-liquids +savdm6ish +sfx +skies +skin +stralenex1 +terrademoQ3 +test +tznex01 +tznex03 \ No newline at end of file diff --git a/games/NexuizPack/nexuiz.game/data/entities.def b/games/NexuizPack/nexuiz.game/data/entities.def new file mode 100644 index 00000000..12224204 --- /dev/null +++ b/games/NexuizPack/nexuiz.game/data/entities.def @@ -0,0 +1,1044 @@ +/*QUAKED _skybox (0.77 0.88 1.0) (-4 -4 -4) (4 4 4) +Compiler-only entity that specifies a the origin of a sky box (a wholly contained, separate area of the map), similar to some games' portal skies. When compiled with Q3Map2, the sky box surfaces will be visible from any place where sky is normally visible. It will cast shadows on the normal parts of the map, and can be used with cloud layers and other effects. As it is compiler-only, it can't "scale up" entities in its box. +To use this, carve a small box in some larger structure on your map, place this entity inside that box hole, and make a small version on what should be seen in the sky there. +-------- KEYS -------- +angle: rotation angle of the sky surfaces. +angles: Individual control of PITCH, YAW, and ROLL (default 0 0 0). +_scale: scaling factor (default 64), good values are between 50 and 300, depending on the map. +*/ + +/*QUAKED dom_controlpoint (.3 .3 1) (-16 -16 -16) (16 16 16) +Domination control point +In order to get Domination working well in your map, you need to place dom_team and dom_controlpoint entities. You *must* have at least 3 dom_team entities - 2 minimum teams and one blank one (empty netname and no team). You can have up to 4 teams (5 dom_team entities). +-------- KEYS -------- +message: message to be displayed to all players when this point is captured, preceded by the team's name. This defaults to " has captured a control point". You can specify different names for each point, for example " has captured the Lava Room". +wait: How often this point gives its controlling team frags. +frags: How many frags this point gives each wait cycle. +*/ + +/*QUAKED dom_team (.3 .3 1) (-16 -16 -16) (16 16 16) +Domination team. +In order to get Domination working well in your map, you need to place dom_team and dom_controlpoint entities. You *must* have at least 3 dom_team entities - 2 minimum teams and one blank one (empty netname and no team). You can have up to 4 teams (5 dom_team entities). +-------- KEYS -------- +netname: name of team (Red Team). Set to "" or don't define for the required blank team. +cnt: color of the team. See the "Helpful Extras" section for info. +model: When this team captures control points, the points turn to this model. If this is the neutral team, points start out as this model. +noise: Sound to be played on the control point when it's captured. Only players nearby will hear it. +noise1: Sound to be played to all players when the control point is captured. Also good for an announcer voice ("Red Team has captured a control point") +*/ + +/*QUAKED func_assault_destructible (.5 0 .5) (-8 -8 -8) (8 8 8) +This is a brush model which can be damaged. Once triggered it's active and will happily receive damage players inflict upon it. Once all health is consumed it'll disappear and trigger the targeted entity/entities. As damage is received the brush model will be tinted in an increasingly visible flavor of red to give visible feedback. +-------- KEYS -------- +health: The damage this trigger can take +target: The entity/entities to be triggered once this entity gets invisible +targetname: The name other entities can use to target this entity +*/ + +/*QUAKED func_assault_wall (.5 0 .5) (-8 -8 -8) (8 8 8) +Brush model that will disappear once the targeted target_objective is fulfilled. This can be used to restrict access to parts of the map until a certain objective has been conquered. +-------- KEYS -------- +target: targetname of a target_objective +*/ + +/*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS +Solid entity that oscillates back and forth in a linear motion. By default, it will have an amount of displacement in either direction equal to the dimension of the brush in the axis in which it's bobbing. Entity bobs on the Z axis (up-down) by default. It can also emit sound if the "noise" key is set. Will crush the player when blocked. +-------- KEYS -------- +speed: amount of time in seconds for one complete oscillation cycle (default 4). +height: sets the amount of travel of the oscillation movement (default 32). +phase: sets the start offset of the oscillation cycle. Values must be 0 < phase < 1. Any integer phase value is the same as no offset (default 0). +noise: path/name of .wav or .ogg file to play. Use looping sounds only (e.g. sound/world/drone6.wav - See Notes). +dmg: damage a player who gets crushed by it receives +dmgtime: interval to apply dmg to a player who is s in the way +message: death message when a player gets crushed +-------- SPAWNFLAGS -------- +X_AXIS: entity will bob along the X axis. +Y_AXIS: entity will bob along the Y axis. +*/ + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched by a player, it moves in the direction set by the "angle" key, triggers all its targets, stays pressed by an amount of time set by the "wait" key, then returns to it's original position where it can be operated again. +-------- KEYS -------- +angle: determines the direction in which the button will move (up = -1, down = -2). +target: all entities with a matching targetname will be triggered. +speed: speed of button's displacement (default 40). +wait: number of seconds button stays pressed (default 1, -1 = return immediately). +lip: lip remaining at end of move (default 4 units). +health: (default 0) if set to any non-zero value, the button must take damage (any amount) to activate. +*/ + +/*QUAKED func_door (0 .5 .8) ? START_OPEN - DOOR_DONT_LINK - - TOGGLE +Normal sliding door entity. By default, the door will activate when player walks close to it or when damage is inflicted to it. +If DOOR_DONT_LINK is not set, the door will be linked with all doors it touches. +-------- KEYS -------- +message: is printed when the door is touched if it is a trigger door and it hasn't been fired yet +angle: determines the opening direction +targetname: if set, no touch field will be spawned and a remote button or trigger field activates the door. +health: if set, door must be shot open +speed: movement speed (100 default) +wait: wait before returning (3 default, -1 = never return) +lip: lip remaining at end of move (8 default) +dmg: damage to inflict when blocked (when triggered and someone is in the way) +sounds: when 1, use default door sounds +noise1: sound when the door opens +noise2: sound when the door closes +-------- SPAWNFLAGS -------- +START_OPEN: causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or damage triggered doors). +DOOR_DONT_LINK: the door won't link with another door it touches +TOGGLE: causes the door to wait in both the start and end states for a trigger event. +*/ + +/*QUAKED func_door_secret (0 .5 .8) ? OPEN_ONCE 1ST_LEFT 1ST_DOWN NO_SHOOT ALWAYS_SHOOT +Basic secret door. Slides back, then to the side. Angle determines direction. Opens when targeted or when shot; does not create its own trigger field like func_door does. +-------- KEYS -------- +wait: # of seconds before coming back +key1: first entity key with one-line description +key2: second entity key with one-line description +t_width: override WIDTH to move back (or height if going down) +t_length: override LENGTH to move sideways +dmg: damage to inflict when blocked (2 default) +message: text to display when activating the door +noise1: sound when opening backwards or closing +noise2: sound when opening sideways +noise3: sound when stopping +-------- SPAWNFLAGS -------- +OPEN_ONCE: only work once, then stay open +1ST_LEFT: 1st move is left of arrow +1ST_DOWN: 1st move is down from arrow +NO_SHOOT: never respond to shots +ALWAYS_SHOOT: even if targetname is set, respond to shots +*/ + +/*QUAKED func_group (0 .5 .8) ? +This is not an entity as such. It is strictly an editor utility to group world brushes and patches together for convenience (selecting, moving, copying, etc). You cannot group entities with this. +-------- Q3MAP2 KEYS -------- +_lightmapscale: light map resolution factor +_castshadows: Allows per-entity control over shadow casting. Defaults to 0 on entities, 1 on world. 0 = no shadow casting. 1 = cast shadows on world. > 1 = cast shadows on entities with _rs (or _receiveshadows) with the corresponding value, AND world. Negative values imply same, but DO NOT cast shadows on world. +_receiveshadows: Allows per-entity control over shadow reception. Defaults to 1 on everything (world shadows). 0 = receives NO shadows. > 1 = receive shadows only from corresponding keyed entities (see above) and world. < 1 = receive shadows ONLY from corresponding keyed entities. +_celshader: Sets the cel shader used for this geometry. Note: omit the "textures/" prefix. +-------- KEYS -------- +_indexmap: Path/name for the TGA file used to guide the mapping of textures on the terrain surface. +_layers: number of unique root shaders that will be use on the terrain. +_shader: Path to the metashader used to assign textures to the terrain entity. Note: Omit the "textures/" prefix. +_offsets: space separated list of height offsets for the index map +*/ + +/*QUAKED func_ladder (0 .5 .8) ? +a ladder, need i say no more +grab a trigger brush and put it in front of the part that you want the player to climb +*/ + +/*QUAKED func_plat (0 .5 .8) ? - - CRUSH +Rising platform the player can ride to reach higher places. Plats must always be drawn in the raised position, so they will operate and be lighted correctly but they spawn in the lowered position. The plat will stay in the raised position until the player steps off. +-------- KEYS -------- +speed: determines how fast the plat moves (default 150). +lip: lip remaining at end of move (default 16). Has no effect if "height" is set. +height: if set, this will determine the total amount of vertical travel of the plat. +dmg: damage to inflict on player when he blocks operation of plat. Plat will reverse direction when blocked. +targetname: if set, the trigger that points to this will lower the plat each time it fires. The plat lowers and lifts someone up later. +sounds: 2 for alternate sound set, -1 for silence, or use the fields below +sound1: platform starts moving sound +sound2: platform stop sound +message: kill message, when someone gets killed by this plat +-------- SPAWNFLAGS -------- +CRUSH: crush players hit by the platform instantly +-------- NOTES -------- +By default, the total amount of vertical travel of a platform is implicitly determined by the overall vertical size of the brushes of which it's made minus the lip value. But if the "height" key is used, then the total amount of vertical travel of the plat will be exactly that value regardless of the shape and size of the plat and regardless of the value of the "lip" key. Using the "height" key is the best method for any kind of platforms and the only possible one for thin plats which need to travel vertical distances many times their own thickness. Setting the origin key is simply an alternate method to using an origin brush. When using the model2 key, the origin point of the model will correspond to the origin point defined by either the origin brush or the origin coordinate value. +*/ + +/*QUAKED func_rain (0 .5 .8) ? +This is an invisible area like a trigger, which rain falls inside of. +-------- KEYS -------- +velocity: falling direction (should be something like '0 0 -700', use the X and Y velocity for wind) +cnt: sets color of rain in the Quake palette (default 12 - white) +count: adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000 +*/ + +/*QUAKED func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS +Brush entity that spins in place on one axis (default Z). Use an origin brush to specify define the rotation axis. +To rotate around another axis, make a func_wall with an explicit avelocity given. +-------- KEYS -------- +speed: speed to rotate (in degrees per second) +noise: path/name of looping .wav file to play. +dmg: Do this much dmg every .dmgtime interval when blocked +dmgtime: See above. (0.25s default) +message: kill message when crushed by this +-------- SPAWNFLAGS -------- +X_AXIS: rotate around the X axis +Y_AXIS: rotate around the Y axis +*/ + +/*QUAKED func_snow (0 .5 .8) ? +This is an invisible area like a trigger, which snow falls inside of. +-------- KEYS -------- +velocity: falling direction (should be something like '0 0 -300', use the X and Y velocity for wind) +cnt: sets color of snow in the Quake palette (default 12 - white) +count: adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000 +*/ + +/*QUAKED func_stardust (.5 .5 .5) (-8 -8 -8) (8 8 8) +Point entity with EF_STARDUST applied. This will spawn a particle cloud with mostly golden particles. Used as eye-candy. +*/ + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving solids that follow a cycle of path_corner entities. Origin brushes are NOT supported; they use the "mins" corner as reference (that is, lowest x, y, and z coordinates). +At each node, the train's mins corner hits exactly the path_corner. +Trains always start on in the game. +Trains do not damage the played when blocked. +Trains cannot emit sound. +Trains are not trigger-able or toggle-able. +Trains cannot be block-stopped just by getting in their way, the player must be wedged between the train and another obstacle to block it. +-------- KEYS -------- +speed: default/initial speed of train (default 100 or overridden by speed value of targeted path_corner) +target: targetname of first path_corner to move to at the default speed; ideally, this path_corner shall be exactly where the train starts +*/ + +/*QUAKED func_wall (0 .5 .8) ? +A solid brush entity. +Behaves just like solid brushes, except that it is a separate entity (a submodel) and can be targeted. +-------- KEYS -------- +targetname: when invoking it by a button etc., it changes the color to the initiator of the action (e.g. the one pressing a button). In Onslaught, this can be used to color control points for team who owns them. In other game types, this can be used as a fun feature. +*/ + +/*QUAKED info_location (1 1 0) (-8 -8 -8) (8 8 8) +Location for use by the %l escape in "say" messages. +The closest "visible" info_location entity is chosen to find the right location name for a point. +-------- KEYS -------- +message: name of location, possibly with color codes +*/ + +/*QUAKED info_notnull (0 .5 0) (-8 -8 -8) (8 8 8) +Entity that does nothing, but may be targeted (e.g. to use its position) +-------- KEYS -------- +targetname: must match the target key of entity that uses this for pointing. +*/ + +/*QUAKED info_null (0 .5 0) (-8 -8 -8) (8 8 8) +Aiming target for q3map2-internal entities like _decal or light. Removes itself when loaded, so it can NOT be used for in-game stuff! +-------- KEYS -------- +targetname: the entity that requires an aiming direction points to this. +*/ + +/*QUAKED info_player_attacker (1 0.5 0) (-16 -16 -24) (16 16 45) +Attacking team's player spawning location in Assault. Should touch the floor, but not the walls, and should point where the player should look when he spawns there. +-------- KEYS -------- +target: this should point to a target_objective to decide when this spawning point is active. +cnt: weight of spawn point for random selection. Set to a lower value if you have many spawn points close together. Default value is 1. +*/ + +/*QUAKED info_player_deathmatch (0 1 0) (-16 -16 -24) (16 16 45) +Normal player spawning location in game types without team spawns. Should touch the floor, but not the walls, and should point where the player should look when he spawns there. +-------- KEYS -------- +cnt: weight of spawn point for random selection. Set to a lower value if you have many spawn points close together. Default value is 1. +targetname: when targeted by a func_button, pressing the button will assign the spawn point to the team of the activator as an additional spawn point, or reassign it if it was already assigned. Also used to assign spawn points to Onslaught control points. +*/ + +/*QUAKED info_player_defender (.5 .5 .5) (-16 -16 -24) (16 16 45) +Defending team's player spawning location in Assault. Should touch the floor, but not the walls, and should point where the player should look when he spawns there. +-------- KEYS -------- +target: this should point to a target_objective to decide when this spawning point is active. +cnt: weight of spawn point for random selection. Set to a lower value if you have many spawn points close together. Default value is 1. +*/ + +/*QUAKED info_player_team1 (1 0 0) (-16 -16 -24) (16 16 45) +Red team's player spawning location in e.g. CTF and Onslaught. Should touch the floor, but not the walls, and should point where the player should look when he spawns there. +-------- KEYS -------- +cnt: weight of spawn point for random selection. Set to a lower value if you have many spawn points close together. Default value is 1. +targetname: when targeted by a func_button, pressing the button will reassign the spawn point to the team of the activator. If a team has no more spawn point left, it immediately loses. +*/ + +/*QUAKED info_player_team2 (0 0 1) (-16 -16 -24) (16 16 45) +Blue team's player spawning location in e.g. CTF and Onslaught. Should touch the floor, but not the walls, and should point where the player should look when he spawns there. +-------- KEYS -------- +cnt: weight of spawn point for random selection. Set to a lower value if you have many spawn points close together. Default value is 1. +targetname: when targeted by a func_button, pressing the button will reassign the spawn point to the team of the activator. If a team has no more spawn point left, it immediately loses. +*/ + +/*QUAKED info_player_team3 (1 1 0) (-16 -16 -24) (16 16 45) +Yellow team's player spawning location, but there is no game mode to use this yet. Anyway, should touch the floor, but not the walls, and should point where the player should look when he spawns there. +-------- KEYS -------- +cnt: weight of spawn point for random selection. Set to a lower value if you have many spawn points close together. Default value is 1. +targetname: when targeted by a func_button, pressing the button will reassign the spawn point to the team of the activator. If a team has no more spawn point left, it immediately loses. +*/ + +/*QUAKED info_player_team4 (1 0 1) (-16 -16 -24) (16 16 45) +Pink team's player spawning location, but there is no game mode to use this yet. Anyway, should touch the floor, but not the walls, and should point where the player should look when he spawns there. +-------- KEYS -------- +cnt: weight of spawn point for random selection. Set to a lower value if you have many spawn points close together. Default value is 1. +targetname: when targeted by a func_button, pressing the button will reassign the spawn point to the team of the activator. If a team has no more spawn point left, it immediately loses. +*/ + +/*QUAKED item_armor_large (.4 .8 .4) (-30 -30 0) (30 30 32) FLOATING +Large Armor (default 100 armor points) +-------- KEYS -------- +respawntime: time till it respawns (default: 30) +armorvalue: amount of armor it gives (default: 100 (g_pickup_armorlarge)) +max_armorvalue: max of armor it increases to (default: 999 (g_pickup_armorlarge_max)) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/g_a25.md3" +*/ + +/*QUAKED item_armor_medium (.4 .8 .4) (-30 -30 0) (30 30 32) FLOATING +Medium Armor (default 25 armor points) +-------- KEYS -------- +respawntime: time till it respawns (default: 20) +armorvalue: amount of armor it gives (default: 25 (g_pickup_armormedium)) +max_armorvalue: max of armor it increases to (default: 999 (g_pickup_armormedium_max)) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/g_armormedium.md3" +*/ + +/*QUAKED item_armor_small (.4 .8 .4) (-30 -30 0) (30 30 32) FLOATING +Small Armor (default 5 armor points) +-------- KEYS -------- +respawntime: time till it respawns (default: 15) +armorvalue: amount of armor it gives (default: 5 (g_pickup_armorsmall)) +max_armorvalue: max of armor it increases to (default: 999 (g_pickup_armorsmall_max)) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/g_a1.md3" +*/ + +/*QUAKED item_bullets (.3 .3 1) (-30 -30 0) (30 30 32) FLOATING +Machine Gun ammo +-------- KEYS -------- +ammo_nails: bullets gained by this item (if unset, g_pickup_nails is used) +respawntime: time till it respawns (default: 15) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/a_bullets.mdl" +*/ + +/*QUAKED item_cells (.3 .3 1) (-30 -30 0) (30 30 32) FLOATING +Nex, Electro and Crylink ammo +-------- KEYS -------- +ammo_cells: cells gained by this item (if unset, g_pickup_cells is used) +respawntime: time till it respawns (default: 15) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/a_cells.md3" +*/ + +/*QUAKED item_flag_team1 (1 0 0) (-48 -48 -37) (48 48 37) +CTF flag for team one (Red). Use more than one if you really insist. +-------- KEYS -------- +model: model to use +scale: scaling factor (DO set this when using your own model!) +noise: sound played when flag is picked up +noise1: sound played when flag is returned +noise2: sound played when flag is captured +noise3: sound played when flag is lost in the field and respawns itself +*/ + +/*QUAKED item_flag_team2 (0 0 1) (-48 -48 -37) (48 48 37) +CTF flag for team two (Blue). Use more than one if you really insist. +-------- KEYS -------- +model: model to use +scale: scaling factor (DO set this when using your own model!) +noise: sound played when flag is picked up +noise1: sound played when flag is returned +noise2: sound played when flag is captured +noise3: sound played when flag is lost in the field and respawns itself +*/ + +/*QUAKED item_health_large (.9 .3 .3) (-30 -30 0) (30 30 48) FLOATING +Large Health (default 50 health points) +-------- KEYS -------- +respawntime: time till it respawns (default: 20) +health: amount of health it gives (default: 50 (g_pickup_healthlarge)) +max_health: max of health it increases to (default: 999 (g_pickup_healthlarge_max)) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/g_h50.md3" +*/ + +/*QUAKED item_health_medium (.9 .3 .3) (-30 -30 0) (30 30 48) FLOATING +Medium Health (default 25 health points) +-------- KEYS -------- +respawntime: time till it respawns (default: 15) +health: amount of health it gives (default: 25 (g_pickup_healthmedium)) +max_health: max of health it increases to (default: 999 (g_pickup_healthmedium_max)) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/g_h25.md3" +*/ + +/*QUAKED item_health_mega (.9 .3 .3) (-30 -30 0) (30 30 48) FLOATING +Mega Health (default 100 health points) +In Minstagib, this randomly turns into either an invisibility, an extra lives or a speed power-up with a default respawn time of 120. +-------- KEYS -------- +respawntime: time till it respawns (default: 30) +health: amount of health it gives (default: 100 (g_pickup_healthmega)) +max_health: max of health it increases to (default: 999 (g_pickup_healthmega_max)) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/g_h100.md3" +*/ + +/*QUAKED item_health_small (.9 .3 .3) (-30 -30 0) (30 30 48) FLOATING +Small Health (default 5 health points) +-------- KEYS -------- +respawntime: time till it respawns (default: 15) +health: amount of health it gives (default: 5 (g_pickup_healthsmall)) +max_health: max of health it increases to (default: 5 (g_pickup_healthsmall_max)) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/g_h1.md3" +*/ + +/*QUAKED item_invincible (.3 .3 1) (-30 -30 0) (30 30 48) FLOATING +Strong Shield +In Minstagib, this randomly turns into either an invisibility, an extra lives or a speed power-up with a default respawn time of 120. +-------- KEYS -------- +respawntime: time till it respawns (default: 120) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/g_invincible.md3" +*/ + +/*QUAKED item_minst_cells (.3 .3 1) (-30 -30 0) (30 30 32) FLOATING +Minstagib ammo. +Always contains 5 (g_minstagib_ammo_drop) shots. +It only appears when playing Minstagib and prevents auto-replacement of weapon_nex & weapon_rocketlauncher when used. +-------- KEYS -------- +respawntime: time till it respawns (default: 45) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/a_cells.md3" +*/ + +/*QUAKED item_rockets (.3 .3 1) (-30 -30 0) (30 30 32) FLOATING +Rocket Launcher, Hagar and Mortar ammo +-------- KEYS -------- +ammo_rockets: rockets gained by this item (if unset, g_pickup_rockets is used) +respawntime: time till it respawns (default: 15) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/a_rockets.md3" +*/ + +/*QUAKED item_shells (.3 .3 1) (-30 -30 0) (30 30 32) FLOATING +Shotgun ammo +-------- KEYS -------- +ammo_shells: shells gained by this item (if unset, g_pickup_shells is used) +respawntime: time till it respawns (default: 15) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/a_shells.md3" +*/ + +/*QUAKED item_strength (.3 .3 1) (-30 -30 0) (30 30 48) FLOATING +Strength aka Quad damage +In Minstagib, this randomly turns into either an invisibility, an extra lives or a speed power-up with a default respawn time of 120. +-------- KEYS -------- +respawntime: time till it respawns (default: 120) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/items/g_strength.md3" +*/ + +/*QUAKED light (.65 .65 1) (-8 -8 -8) (8 8 8) LINEAR NOANGLE - - NOGRIDLIGHT +Non-displayed point light source. The -pointscale and -scale arguments to Q3Map2 affect the brightness of these lights. The -skyscale argument affects brightness of entity sun lights. +Maximum intensity (in a radius 16 sphere around the light): regular lights have light/256, linear lights have light/8000-16*fade. +Falloff radius to a negligible light amount: regular lights have light have 16*sqrt(light), linear lights have light/(fade*8000). +By this you see that you HAVE to specify fade for a linear light... use values below 0.01 there. +-------- KEYS -------- +light: intensity factor (default: 300). A linear +_color: weighted RGB value of light color (default white - 1.0 1.0 1.0). +target: Lights pointed at a target will be spotlights. +radius: radius of a spotlight at the target point (default: 64) +_anglescale: scales angle attenuation +fade: Fade factor of light attenuation of linear lights. Linear lights completely vanish at distance light/(fade*8000), so if you want the light to vanish at distance X, specify light/(8000*X) here. +_filterradius: filter radius for this light, similar to -light -filter +_sun: if 1, this light is an infinite sun light +_samples: number of samples to use to get soft shadows from a light +_deviance: position deviance of the samples of a regular light (distributes the light samples in a cube of side length 2*_deviance around the origin), or angle deviance of the sun light samples in radians +-------- SPAWNFLAGS -------- +LINEAR: Use a linear falloff. Default is inverse distance squared (more realistic). +NOANGLE: Ignore angle attenuation. +NOGRIDLIGHT: Do not affect the light grid (dynamic entity lighting). +*/ + +/*QUAKED lightJunior (.65 .65 1) (-8 -8 -8) (8 8 8) LINEAR NOANGLE +Non-displayed point light source that JUST APPLIES TO THE LIGHT GRID. No idea what this is good for. The -pointscale and -scale arguments to Q3Map2 affect the brightness of these lights. The -skyscale argument affects brightness of entity sun lights. +Maximum intensity (in a radius 16 sphere around the light): regular lights have light/256, linear lights have light/8000-16*fade. +Falloff radius to a negligible light amount: regular lights have light have 16*sqrt(light), linear lights have light/(fade*8000). +By this you see that you HAVE to specify fade for a linear light... use values below 0.01 there. +-------- KEYS -------- +light: intensity factor (default: 300). A linear +_color: weighted RGB value of light color (default white - 1.0 1.0 1.0). +target: Lights pointed at a target will be spotlights. +radius: radius of a spotlight at the target point (default: 64) +_anglescale: scales angle attenuation +fade: Fade factor of light attenuation of linear lights. Linear lights completely vanish at distance light/(fade*8000), so if you want the light to vanish at distance X, specify light/(8000*X) here. +_filterradius: filter radius for this light, similar to -light -filter +_sun: if 1, this light is an infinite sun light +_samples: number of samples to use to get soft shadows from a light +_deviance: position deviance of the samples of a regular light (distributes the light samples in a cube of side length 2*_deviance around the origin), or angle deviance of the sun light samples in radians +-------- SPAWNFLAGS -------- +LINEAR: Use a linear falloff. Default is inverse distance squared (more realistic). +NOANGLE: Ignore angle attenuation. +*/ + +/*QUAKED misc_laser (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON FINITE +Laser beam emitter +-------- KEYS -------- +target: target_position the laser targets (may be another entity, preferably target_position, possibly controlled by misc_follow) +mdl: name of particle effect for the beam end point (see effectinfo.txt; default is laser_deadly if dmg is set, and none if not) +colormod: color of the laser beam (default: red, that is, 1 0 0) +dmg: damage inflicted by the beam per second, or -1 for an instant-death ray +targetname: name to target this (then its state is toggled) +alpha: when set, makes a dark laser of the given strength; may be combined with colormod +-------- SPAWNFLAGS -------- +START_ON: when targeted, the laser will start switched on +FINITE: the laser does not extend over its target like light would do, but stops there (takes more bandwidth) +-------- NOTES -------- +Use trigger_monoflop if you want the laser to turn off for a while, then turn back on. +When the laser's target has itself target set, its targets are triggered when someone enters or leaves the laser. +*/ + +/*QUAKED misc_model (1 .5 .25) (-16 -16 -16) (16 16 16) - SOLID - EXTRUDE_NORMALS EXTRUDE_TERRAIN +Generic placeholder for inserting MD3 models in game. Requires compilation of map geometry to be added to level. If the map is compiled with Q3Map2, then ASE, 3DS, OBJ and other model formats are supported. +-------- Q3MAP2 KEYS -------- +model: file name of model to include +_frame: frame of model to include +_remap: string of the form from;to specifying which texture name of the model to replace by which shader; * is allowed. Any key starting with this prefix will work, so if you need more remappings, create _remap2, etc. +angle: view direction of the model +angles: view direction of the model in PITCH YAW ROLL +modelscale: scaling factor +modelscale_vec: scaling vector for non-uniform scaling +_castshadows: Allows per-entity control over shadow casting. Defaults to 0 on entities, 1 on world. 0 = no shadow casting. 1 = cast shadows on world. > 1 = cast shadows on entities with _rs (or _receiveshadows) with the corresponding value, AND world. Negative values imply same, but DO NOT cast shadows on world. +_receiveshadows: Allows per-entity control over shadow reception. Defaults to 1 on everything (world shadows). 0 = receives NO shadows. > 1 = receive shadows only from corresponding keyed entities (see above) and world. < 1 = receive shadows ONLY from corresponding keyed entities. +_lightmapscale: light map resolution factor +_celshader: the cel shader for this +-------- SPAWNFLAGS -------- +SOLID: make the model solid +EXTRUDE_NORMALS: for converting triangles to clip brushes, extrude along the model normals (by default, extrusion happens in a coordinate axis direction that is decided per triangle) +EXTRUDE_TERRAIN: always extrude downwards (for terrain) +*/ + +/*QUAKED misc_models (0 .5 .8) ? +A non-solid brush entity, or a way to load models from a map (e.g. self-animated zym models). +Behaves just like non-solid brushes otherwise. +The keys below actually apply to most brush entities as they are engine features; however, they are described here as they aren't overridden by game code in misc_models. Its q3map2 keys below will work on any brush entity! +_receiveshadows only works if this is a brush model (that is, if "model" is not defined). +-------- KEYS -------- +model: when used as a point entity, file name of model to load; when used as a brush entity, do not specify that +frame: animation frame to play (for self-animated zym models) +skin: number of skin to load (when model is used) +movetype: way in which it moves: one of 0 = NONE, 1 = ANGLENOCLIP, 2 = ANGLECLIP, 3 = WALK, 4 = STEP, 5 = FLY, 6 = TOSS, 7 = PUSH, 8 = NOCLIP, 9 = FLYMISSILE, 10 = BOUNCE, 11 = BOUNCEMISSILE +solid: solidity: one of 0 = NOT, 1 = TRIGGER, 2 = BBOX, 3 = SLIDEBOX, 4 = BSP, 5 = CORPSE +avelocity: vector giving its angular velocity (useful for spinning models) +scale: scale factor of the model (range: 0.0625 to 15.9375) +colormap: 1024 + 16 * pantscolor + shirtcolor +velocity: when movetype isn't 0, initial velocity vector +angles: initial looking direction +effects: sum of 1 = BRIGHTFIELD, 4 = BRIGHTLIGHT, 8 = DIMLIGHT, 32 = ADDITIVE, 64 = BLUE, 128 = RED, 512 = FULLBRIGHT, 1024 = FLAME, 2048 = STARDUST, 4096 = NOSHADOW, 8192 = NODEPTHTEST, 32768 = DOUBLESIDED, 8388608 = NOMODELFLAGS (ignores the following coming from a model file), 16777216 = ROCKET, 33554432 = GRENADE, 67108864 = GIB, 134217728 = ROTATE, 268435456 = TRACER, 536870912 = ZOMGIB, 1073741824 = TRACER2, -2147483648 = TRACER3 +-------- Q3MAP2 KEYS -------- +_frame: frame of model to include (set equal to frame if _castshadows is set) +modelscale: scaling factor (set equal to scale if _castshadows is set) +_castshadows: Allows per-entity control over shadow casting. Defaults to 0 on entities, 1 on world. 0 = no shadow casting. 1 = cast shadows on world. > 1 = cast shadows on entities with _rs (or _receiveshadows) with the corresponding value, AND world. Negative values imply same, but DO NOT cast shadows on world. +_receiveshadows: Allows per-entity control over shadow reception. Defaults to 1 on everything (world shadows). 0 = receives NO shadows. > 1 = receive shadows only from corresponding keyed entities (see above) and world. < 1 = receive shadows ONLY from corresponding keyed entities. +_clone: copies brushes from entity with identical _clonename. Still needs a single brush that will get replaced. +_clonename: template name so one can clone from it +min: override automatically found minimum coordinate bounds +max: override automatically found maximum coordinate bounds +targetname: if targeted by a misc_model, its brushes get inserted into this +_celshader: Sets the cel shader used for this geometry. Note: omit the "textures/" prefix. +*/ + +/*QUAKED misc_teleporter_dest (1 .5 .25) (-16 -16 -24) (16 16 45) +Teleport destination location point for trigger_teleport entities. Do not let it touch the floor, but place it slightly higher (like, 16 units above) for better flow when jumping through it. +-------- KEYS -------- +targetname: make the trigger_teleporter point to this. +target: target to activate when a teleporter targeting this is used +angle: direction in which player will look when teleported, OR use +angles: pitch and yaw when coming out of the teleporter (also specifies the direction the player will aim when coming out) +*/ + +/*QUAKED onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128) +Control point. Be sure to give this enough clearance so that the shootable part has room to exist +This should link to an onslaught_controlpoint entity or onslaught_generator entity. +-------- KEYS -------- +targetname: name that onslaught_link entities will use to target this. +target: target any entities that are tied to this control point, such as vehicles and buildable structure entities. +message: name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc) +*/ + +/*QUAKED onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64) +Base generator. + +onslaught_link entities can target this. +-------- KEYS -------- +team: team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET. +targetname: name that onslaught_link entities will use to target this. +*/ + +/*QUAKED onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16) +Link between control points. + +This entity targets two different onslaught_controlpoint or onslaught_generator entities, and suppresses shielding on both if they are owned by different teams. +-------- KEYS -------- +target: first control point. +target2: second control point. +*/ + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) +Path corner entity that func_train will follow. +All path_corner entities of a train have to connect in a circular manner, while the func_train shall point to one of the path_corners (ideally the one at the train's starting point) +-------- KEYS -------- +target: point to next path_corner in the path. +targetname: the train following the path or the previous path_corner in the path points to this. +speed: speed of func_train while moving to this path corner. If unset, the value from the func_train will be used. +wait: number of seconds func_train will pause on this path corner before moving to next path corner (default: 0.1; to not wait, set this to -1 +*/ + +/*QUAKED runematch_spawn_point (.3 .3 1) (-16 -16 -16) (16 16 16) +Spawn point for runes in a runematch. +*/ + +/*QUAKED target_assault_roundend (1 0 0) (-8 -8 -8) (8 8 8) +This entity ends the current assault round if triggered or if the timelimit is reached. +Ending a round means swapping attacker/defender teams, resetting objectives and rewarding the winning team. +Every assault map needs this entity. There should only be one per map. +-------- KEYS -------- +health: Time in seconds how long a round in assault shall last. Default is 300 seconds. +targetname: Name to target this entity +*/ + +/*QUAKED target_assault_roundstart (.5 0 .5) (-8 -8 -8) (8 8 8) +This entity triggers its targets whenever a new assault round is started. This can be used to e.g. activate the first objective. +-------- KEYS -------- +target: targetname of the entity/entities to be triggered +*/ + +/*QUAKED target_objective (.5 0 .5) (-8 -8 -8) (8 8 8) +target_objective controls an objective. Once triggered the objective is active and has 100 "health" points. If this "health" falls below zero it is assumed this objective has been fulfilled and entities targeted will be triggered (e.g. to activate the next objective or to end this round). Use target_objective_decrease to decrease the objective health. +-------- KEYS -------- +target: targetname of the entity to be triggered once this objective is fulfilled. +targetname: targetname for this entity so it can be triggered by other entities. +*/ + +/*QUAKED target_objective_decrease (0 1 0) (-8 -8 -8) (8 8 8) +When triggered decreases health of the targeted target_objective by the amount specified in dmg. Remember, target_objective has 100 health points and is considered conquered if health falls below zero. +If you want e.g. two events to happen to conquer an objective you'd need two target_objective_decrease, each with a value for cnt of e.g. 51. To show attackers and defenders where to go, target_objective_decrease will show a fitting sprite ("Defend" to defenders, "Destroy"/"Push" to attackers) which can be seen through walls. +-------- KEYS -------- +target: The targetname of the target_objective you want to manipulate. +targetname: Name for other entities to target this entity. +dmg: The amount of "health"-points you want to subtract from the objective health. Defaults to 101. +*/ + +/*QUAKED target_position (0 .5 0) (-8 -8 -8) (8 8 8) +Aiming target for entities like light and trigger_push. +-------- KEYS -------- +targetname: the entity that requires an aiming direction points to this. +target: target to activate when a jumppad targeting this is used +*/ + +/*QUAKED target_speaker (0 .7 .7) (-8 -8 -8) (8 8 8) +Sound generating entity that plays sound files. +If targeted, it plays the sound file every time when triggered. +If not targeted, it loops the sound file as an ambient noise. +-------- KEYS -------- +noise: path/name of .wav/.ogg file to play +targetname: the activating button or trigger points to this. +atten: distance attenuation of the sound (a value from 0.1 to 3.9), default is 0.5 if targeted, 3 otherwise; set to -1 for no attenuation (global sound) +volume: volume of the sound +*/ + +/*QUAKED trigger_counter (.5 .5 .5) ? NOMESSAGE +Acts as an intermediary for an action that takes multiple inputs. +After the counter has been triggered "count" times, it will fire all of its targets and remove itself. +-------- KEYS -------- +count: how many times this needs to be triggered to activate its targets +target: trigger all entities with this targetname when triggered +targetname: name that identifies this entity so it can be triggered +delay: delay the triggering by the given time +message: print this message to the player who activated the trigger +killtarget: remove all entities with this targetname when triggered +-------- SPAWNFLAGS -------- +NOMESSAGE: don't print a "2 more to go..."-like message when triggered +*/ + +/*QUAKED trigger_delay (.5 .5 .5) (-8 -8 -8) (8 8 8) +Trigger that delays triggering by a given amount of time. Only one action can be waited for; if triggered again before the "wait" time expires, the timer will restart (as opposed to trigger_relay). +-------- KEYS -------- +wait: delay the triggering by the given time +target: trigger all entities with this targetname when triggered +targetname: name that identifies this entity so it can be triggered +message: print this message to the player who activated the trigger +killtarget: remove all entities with this targetname when triggered +*/ + +/*QUAKED trigger_hurt (.5 .5 .5) ? +Any object touching this will be hurt. +Has the useful effect of automatically returning flags, keys and runes when they touch it. +-------- KEYS -------- +dmg: amount of damage to deal (default: 1000) +message: kill message when someone gets killed by this (default: "was in the wrong place.") +*/ + +/*QUAKED trigger_impulse (.5 .5 .5) ? +An accelerator/dampener/wind field. +Can be used in two ways: +"dampener field": just set strength to a value from 0 to 1. Entering the field will slow down to this factor. +"accelerator field": just set strength to a value from 1 to infinity. Entering the field will accelerate by this factor. +"wind field": set strength to the amount of velocity to add per second, and target a target_position. The field will apply force in the direction from its own origin to the target (use an origin brush to specify its own origin, or this will fail) when touched. +"gravity field": set strength to the amount of velocity to add per second at the center, and set radius to the radius of the field. Set falloff to the desired falloff characteristics. +-------- KEYS -------- +target: "wind field": points to the target_position to which the player will get pushed. +strength: "wind field", "gravity field": amount of force per second to apply. "dampener/accelerator field": slowdown/speedup factor. +falloff: "gravity field": 0 means no falloff, 1 means linear falloff (zero at the outside), 2 means inverted linear falloff (zero at the inside) +*/ + +/*QUAKED trigger_multiple (.5 .5 .5) ? NOTOUCH ALLENTS +Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time. +-------- KEYS -------- +health: amount of damage that has to be dealt to the trigger to activate (it then won't respond to merely touching it) +wait: prevent triggering again for this amount of time +sounds: 1 to play misc/secret.wav, 2 to play misc/talk.wav, 3 to play misc/trigger1.wav +noise: path to sound file, if you want to play something else +target: trigger all entities with this targetname when triggered +targetname: name that identifies this entity so it can be triggered +delay: delay the triggering by the given time +message: print this message to the player who activated the trigger +killtarget: remove all entities with this targetname when triggered +-------- SPAWNFLAGS -------- +NOTOUCH: the trigger can only be triggered by other entities, not by touching or firing (you should probably use trigger_relay or trigger_delay instead) +ALLENTS: the trigger responds to all entities, not just players (useful for targetting trigger_items) +*/ + +/*QUAKED trigger_once (.5 .5 .5) ? NOTOUCH +Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time. +Basically, it's a use-once trigger_multiple. +-------- KEYS -------- +health: amount of damage that has to be dealt to the trigger to activate (it then won't respond to merely touching it) +sounds: 1 to play misc/secret.wav, 2 to play misc/talk.wav, 3 to play misc/trigger1.wav +noise: path to sound file, if you want to play something else +target: trigger all entities with this targetname when triggered +targetname: name that identifies this entity so it can be triggered +delay: delay the triggering by the given time +message: print this message to the player who activated the trigger +killtarget: remove all entities with this targetname when triggered +-------- SPAWNFLAGS -------- +NOTOUCH: the trigger can only be triggered by other entities, not by touching or firing (you should probably use trigger_relay or trigger_delay instead) +*/ + +/*QUAKED trigger_push (1 .5 0) ? +Jump pad. What else? +Can be used in three ways: +Nexuiz "target/height" way: put the target_position where the player should land, and tune height to get a nice jump path. A good starting value for height is 100. +Q3A "target" way: put the target_position at the apex of the jump, and hope the player will land at the right spot. Good luck. +Quake "movedir/speed" way: player will get velocity movedir * speed * 10 when using the jump pad +-------- KEYS -------- +target: point the player will fly to when using the jump pad (use a target_position here) +height: if height is 0, the (player's origin at the) apex of the jump will be at the target; otherwise, the apex will be abs(height) above the higher point of player's origin and the target; if positive, the apex will be reached in the jump from initial origin to target +movedir: when target is not set, direction vector to push to +speed: speed of jump pad (default: 1000) +noise: sound to play when jump pad is used; default is misc/jumppad.wav; you can set it to "" to make the pad silent +*/ + +/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) +This fixed size trigger cannot be touched, it can only be fired by other events. It can, like any other trigger, contain killtargets, targets, delays, and messages. +One possible use is to trigger entities with more than one targetname on an action, e.g. a button. For this, set target of your button to foobar. Create two trigger_relay with targetname=foobar. Give one of the trigger_relay entities target=ent1, and give the other target=ent2. +More than one "trigger event" can be delayed at once, as opposed to trigger_delay. +-------- KEYS -------- +target: trigger all entities with this targetname when triggered +targetname: name that identifies this entity so it can be triggered +delay: delay the triggering by the given time +message: print this message to the player who activated the trigger +killtarget: remove all entities with this targetname when triggered +*/ + +/*QUAKED trigger_swamp (.5 .5 .5) ? +Players getting into the swamp will get slowed down and damaged +-------- KEYS -------- +dmg: damage per interval to deal (default is 5) +swamp_interval: interval of damage when in swamp (default is 1) +swamp_slowdown: amount of slowdown caused by the swamp (default is 0.5) +*/ + +/*QUAKED trigger_teleport (.5 .5 .5) ? +Touching this will teleport players to the location of the targeted misc_teleporter_dest entity. +Note that in Nexuiz, teleporters preserve momentum of the player using them. +-------- KEYS -------- +target: this must point to a misc_teleporter_dest entity. +*/ + +/*QUAKED weapon_crylink (1 0 .5) (-30 -30 0) (30 30 32) FLOATING +the Crylink +-------- KEYS -------- +ammo_cells: initial cells of the weapon (if unset, g_pickup_cells is used) +respawntime: time till it respawns (default: 15) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons/g_crylink.md3" +*/ + +/*QUAKED weapon_electro (1 0 .5) (-30 -30 0) (30 30 32) FLOATING +the Electro +-------- KEYS -------- +ammo_cells: initial cells of the weapon (if unset, g_pickup_cells is used) +respawntime: time till it respawns (default: 15) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons/g_electro.md3" +*/ + +/*QUAKED weapon_grenadelauncher (1 0 .5) (-30 -30 0) (30 30 32) FLOATING +the Mortar +-------- KEYS -------- +ammo_rockets: initial rockets of the weapon (if unset, g_pickup_rockets is used) +respawntime: time till it respawns (default: 15) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons/g_gl.md3" +*/ + +/*QUAKED weapon_hagar (1 0 .5) (-30 -30 0) (30 30 32) FLOATING +the Hagar +-------- KEYS -------- +ammo_rockets: initial rockets of the weapon (if unset, g_pickup_rockets is used) +respawntime: time till it respawns (default: 15) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons/g_hagar.md3" +*/ + +/*QUAKED weapon_nex (1 0 .5) (-30 -30 0) (30 30 32) FLOATING +the Nex +In Minstagib, this turns into an item_minst_cells if no explicit item_minst_cells have been placed. +-------- KEYS -------- +ammo_cells: initial cells of the weapon (if unset, g_pickup_cells is used) +respawntime: time till it respawns (default: 15 * g_balance_nex_respawntime_modifier) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons/g_nex.md3" +*/ + +/*QUAKED weapon_rocketlauncher (1 0 .5) (-30 -30 0) (30 30 32) FLOATING +the Rocket Launcher +In Minstagib, this turns into an item_minst_cells if no explicit item_minst_cells have been placed. +-------- KEYS -------- +ammo_rockets: initial rockets of the weapon (if unset, g_pickup_rockets is used) +respawntime: time till it respawns (default: 15) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons/g_rl.md3" +*/ + +/*QUAKED weapon_shotgun (1 0 .5) (-30 -30 0) (30 30 32) FLOATING +the Shotgun +-------- KEYS -------- +ammo_shells: initial shells of the weapon (if unset, g_pickup_shells is used) +respawntime: time till it respawns (default: 15) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons/g_shotgun.md3" +*/ + +/*QUAKED weapon_uzi (1 0 .5) (-30 -30 0) (30 30 32) FLOATING +the Machine Gun +-------- KEYS -------- +ammo_nails: initial bullets of the weapon (if unset, g_pickup_nails is used) +respawntime: time till it respawns (default: 15) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons/g_uzi.md3" +*/ + +/*QUAKED worldspawn (0 0 0) ? +The world. +If you see this, the currently selected brush is not of an entity. +-------- KEYS -------- +fog: fog parameters of the map (density red green blue alpha mindist maxdist); works just like the "fog" console command +author: name of the author of the map, if not specified in "message". Will get copied to the "author" entry of the mapinfo file when none is present. +message: text to print at user logon. Used for name of level. Ideally: "NAMEOFLEVEL by AUTHOR". Will get copied to the "title" and "author" entries of the mapinfo file when none is present. +_description: one-line description of the map. Will get copied to the "description" entry of the mapinfo file when none is present. +-------- Q3MAP2 KEYS -------- +_blocksize: vector specifying the automatic vis subdivision block size (default: 1024 1024 1024) +gridsize: lightgrid granularity vector (default: 64 64 128) +_color: color of the global light parameters +_ambient: light added to all surfaces +_mingridlight: minimum grid light +_minlight: minimum light value on both surfaces and lightgrid +_keepLights: do not remove light entities from the map (useful e.g. for realtime lighting) +_floodlight: flood light parameters (red green blue distance intensity), start with 240 240 255 1024 128 +_farplanedist: range after which everything is completely invisible by fog (when fog is being used) +_noshadersun: turn off sun defined by shaders (useful if you defined your own light entity to be a sun) +_ignoreleaks: ignore leaks when compiling +_lightmapscale: light map resolution factor +_castshadows: Allows per-entity control over shadow casting. Defaults to 0 on entities, 1 on world. 0 = no shadow casting.1 = cast shadows on world. > 1 = cast shadows on entities with _rs (or _receiveshadows) with the corresponding value, AND world. Negative values imply same, but DO NOT cast shadows on world. +_receiveshadows: Allows per-entity control over shadow reception. Defaults to 1 on everything (world shadows). 0 = receives NO shadows. > 1 = receive shadows only from corresponding keyed entities (see above) and world. < 1 = receive shadows ONLY from corresponding keyed entities. +_celshader: Sets the cel shader used for this geometry. Note: omit the "textures/" prefix. +*/ + +/*QUAKED trigger_race_checkpoint (0 1 0) ? NOTOUCH STRICTTRIGGER CRUSH +A checkpoint, for the race game mode. Be sure to make them quite long, so they actually catch a player reliably! +-------- KEYS -------- +cnt: Number of the checkpoint. 0 for finish line, and at least two other checkpoints have to exist. They MUST be touched in sequential order! +message: Death message, when touching checkpoints in the wrong order. +targetname: Name of the checkpoint. info_player_race can target this to assign a spawn to a checkpoint. Also used for triggering a checkpoint by an event. +target: when the checkpoint is passed, these entities are triggered. Useful for forcing items in certain areas using trigger_items +-------- SPAWNFLAGS -------- +NOTOUCH: the checkpoint will not become active when touched, it HAS to be targeted +STRICTTRIGGER: only trigger the targets when the checkpoint actually was reached in a valid way (that is, not when going back) +CRUSH: the checkpoint kills when used at the wrong time +*/ + +/*QUAKED info_player_race (1 0.5 0) (-16 -16 -24) (16 16 45) +Race spawn point. +NOTE for race_place: when the race starts after the qualifying, the player with the fastest map ends up at the info_player_race with race_place 1, and so on. If there are too many players, or if someone comes in later, he will spawn at an info_player_race with race_place not set. So for each trigger_race_checkpoint, there must be at least one corresponding info_player_race with race_place NOT set. +-------- KEYS -------- +target: this should point to a trigger_race_checkpoint to decide when this spawning point is active. The checkpoint has to be AFTER this spawn. +cnt: weight of spawn point for random selection. Set to a lower value if you have many spawn points close together. Default value is 1. +race_place: if target points to the trigger_race_checkpoint with cnt 0 (finish line), this sets which place the spawn corresponds to +*/ + +/*QUAKED func_pointparticles (.5 .5 .5) ? START_ON +A brush that emits particles. +-------- KEYS -------- +mdl: particle effect name from effectinfo.txt +impulse: when positive, number of particles to emit per second; when negative; number of particles to emit per second and 64^3 block +velocity: particle direction and speed +waterlevel: extra velocity jitter amount +count: particle count multiplier (per spawned particle) +movedir: when set, trace direction (particles will then be emitted from the surface the trace hits); the length of the vector is used as strength of taking the normal of the trace into account +glow_color: particle palette color +noise: sound to play when the particle is emitted +targetname: name to target this (then its state is toggled) +-------- SPAWNFLAGS -------- +START_ON: when targeted, the particle emitter will start switched on +-------- NOTES -------- +Use trigger_monoflop if you want the particles to turn off for a while, then turn back on +*/ + +/*QUAKED trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON +"Flip-flop" trigger gate... lets only every second trigger event through +-------- KEYS -------- +target: trigger all entities with this targetname when triggered +targetname: name that identifies this entity so it can be triggered +-------- SPAWNFLAGS -------- +START_ON: assume it is already turned on (so the first event is NOT passed through) +*/ + +/*QUAKED trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8) FIXED +"Mono-flop" trigger gate... turns trigger events into pairs of events +-------- KEYS -------- +target: trigger all entities with this targetname when triggered +targetname: name that identifies this entity so it can be triggered +wait: time to wait until the "off" event is fired +-------- SPAWNFLAGS -------- +FIXED: do pulses of fixed length (so the "off" delay is NOT extended as usual and new events are just ignored) +*/ + +/*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON +"Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off. +-------- KEYS -------- +target: trigger all entities with this targetname when it goes off +targetname: name that identifies this entity so it can be triggered +phase: phase of the multivibrator (it is added to the time) +wait: "on" cycle time (default: 1) +respawntime: "off" cycle time (default: same as wait) +-------- SPAWNFLAGS -------- +START_ON: assume it is already turned on (when targeted) +*/ + +/*QUAKED misc_follow (.5 .5 .5) (-8 -8 -8) (8 8 8) +Makes one entity follow another. Will not work with all entities. +-------- KEYS -------- +target: points to the entity to move (e.g. something that won't move by itself) +killtarget: points to the entity that is to be used as the source (e.g. a func_plat) +*/ + +/*QUAKED weapon_minstanex (1 0 .5) (-30 -30 0) (30 30 32) FLOATING +the MinstaGib Nex. Always kills with one shot. +-------- KEYS -------- +ammo_cells: initial cells of the weapon (if unset, g_pickup_cells is used) +respawntime: time till it respawns (default: 15) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons/g_minstanex.md3" +*/ + +/*QUAKED weapon_porto (1 0 .5) (-30 -30 0) (30 30 32) FLOATING +the Port-O-Launch. Only can be shot once. +Portals cannot be made on noimpact surfaces, and the portal missile will bounce on slick surfaces. +-------- KEYS -------- +respawntime: time till it respawns (default: 120) +team: out of items with the same value here, only one (random one) will spawn. Useful to put multiple items on one spot. +-------- SPAWNFLAGS -------- +FLOATING: the item will float in air, instead of aligning to the floor by falling +-------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- +model="models/weapons/g_porto.md3" +*/ + +/*QUAKED target_items (0 0 1) ? AND OR ANDNOT +Sets the items of any player who triggers this. +For the number fields, not specifying a value means not changing it. To clear armor, you need to explicitly set "armor" to "-1". +You may want to target this by a race checkpoint, a teleporter, or a trigger_multiple with ALLENTS set (so it removes weapons thrown through the field to avoid getting a weapon through it). +-------- KEYS -------- +targetname: used to trigger this +netname: space separated list of items (either weapon short names (like in g_start_weapon_* cvars), or item short names "unlimited_ammo", "invincible" and "strength" +message: message to print +ammo_shells: amount of shells +ammo_nails: amount of bullets +ammo_rockets: amount of rockets +ammo_cells: amount of cells +health: amount of health +armorvalue: amount of armor +strength_finished: if "strength" is specified, the time in seconds for which the strength will hold +invincible_finished: if "invincible" is specified, the time in seconds for which the invincibility will hold +-------- SPAWNFLAGS -------- +AND: any items not listed will get removed, and none will get added +OR: the player may keep items not listed +ANDNOT: the items listed will get removed from the player +*/ diff --git a/games/NexuizPack/nexuiz.game/default_build_menu.xml b/games/NexuizPack/nexuiz.game/default_build_menu.xml new file mode 100644 index 00000000..0b6b0f23 --- /dev/null +++ b/games/NexuizPack/nexuiz.game/default_build_menu.xml @@ -0,0 +1,59 @@ + + + +"[RadiantPath]q3map2.[ExecutableType]" -v -connect [MonitorAddress] -game quake3 -fs_basepath "[EnginePath]" -fs_game [GameName] + +[q3map2] -meta "[MapFile]" + + +[q3map2] -vis "[MapFile]" + + +[q3map2] -vis -fast "[MapFile]" + + +[q3map2] -light -deluxe -faster "[MapFile]" + + +[q3map2] -light -deluxe -fast "[MapFile]" + + +[q3map2] -light -deluxe -fast -super 2 "[MapFile]" + + +[q3map2] -light -deluxe -fast -super 2 -filter "[MapFile]" + + +[q3map2] -light -deluxe -fast -super 2 -filter -bounce 8 "[MapFile]" + + +[q3map2] -meta "[MapFile]" +[q3map2] -vis -saveprt "[MapFile]" +[q3map2] -light -deluxe -fast -filter "[MapFile]" + + +[q3map2] -meta "[MapFile]" +[q3map2] -vis -saveprt -fast "[MapFile]" +[q3map2] -light -deluxe -fast -super 2 -filter "[MapFile]" + + +[q3map2] -meta "[MapFile]" +[q3map2] -vis -saveprt "[MapFile]" +[q3map2] -light -deluxe -fast -filter -super 2 "[MapFile]" + + +[q3map2] -meta "[MapFile]" +[q3map2] -vis -saveprt "[MapFile]" +[q3map2] -light -deluxe -fast -super 2 -filter -bounce 8 "[MapFile]" + + +[q3map2] -meta "[MapFile]" +[q3map2] -vis -saveprt "[MapFile]" +[q3map2] -light -deluxe -super 2 "[MapFile]" + + + diff --git a/games/NexuizPack/nexuiz.game/game.xlink b/games/NexuizPack/nexuiz.game/game.xlink new file mode 100644 index 00000000..94e3f1d8 --- /dev/null +++ b/games/NexuizPack/nexuiz.game/game.xlink @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gen.readme b/gen.readme new file mode 100644 index 00000000..4b735df4 --- /dev/null +++ b/gen.readme @@ -0,0 +1,4 @@ +Dummy file for gen.dsp + +gen.dsp is used with MSVC to generate include/version.h and include/aboutmsg.h +you need python installed and configured to make this work diff --git a/gen.vcproj b/gen.vcproj new file mode 100644 index 00000000..75af13f4 --- /dev/null +++ b/gen.vcproj @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gendox b/gendox new file mode 100644 index 00000000..c25663fb --- /dev/null +++ b/gendox @@ -0,0 +1,150 @@ +#!/bin/bash +# +# Shell script to make doxygen documentation by specifying a target directory +# on the command line +# +# Gef (gefdavis@dingoblue.net.au) -- August 2001 + +# TODO: +# - Dynamic ChangeLog (page gets updated with each commit) +# - Have the ability to specify server dox or local dox, which will then +# change the content on the main page +# - Incorporate a scaled gtkradiant splash image into the pages + +#------------------------------------------------------------------------ +# Set some variables +#------------------------------------------------------------------------ +# WORKINGDIR=`pwd`; +RETVAL=0; +TARGETSTRING=''; +EXTRAS_PATH="./Doxygen_files"; +CONFIG_OUTPUT="$EXTRAS_PATH/genConf"; +DOXYCONFIG="./DoxyConfig"; +DOXYFILE="$EXTRAS_PATH/Doxyfile"; +NEWDOXYFILE="$EXTRAS_PATH/genDoxyfile"; +declare -a TARGETLIST[$#]; +COUNTER=0; +TARGETCOUNT=0; +QUIETMODE=0; +# added -k command line option to kill running doxygen procs +KILLON=0 + +#------------------------------------------------------------------------ +# load the functions +#------------------------------------------------------------------------ +if [ -f "$EXTRAS_PATH/gendoxfunctions" ] ; then + . $EXTRAS_PATH/gendoxfunctions +else + echo -e "Missing critical files...\n"; + exit 1; +fi + +#------------------------------------------------------------------------ +# parse the command line options +#------------------------------------------------------------------------ +COMLINE="$*"; +OPTCOUNT="$#"; +parse_commandline; +if [ $RETVAL -gt 0 ] ; then + echo -e "Exiting."; + exit $RETVAL; +fi + + +if [ $KILLON -gt 0 ] ; then + PIDOF_DOXYGEN=`pidof -x doxygen` + MYPID=$$ + + if [ -z "$PIDOF_DOXYGEN" ] ; then + [ $QUIETMODE -gt 0 ] || echo -e " * Killing other doxygen pids"; + killall -q -9 doxygen + else + [ $QUIETMODE -gt 0 ] || echo -e " * Killing other doxygen pids"; + kill -9 $PIDOF_DOXYGEN &> /dev/null + fi + + [ $QUIETMODE -gt 0 ] || echo -e " * Cleaning up gendox pids"; + killall -q -9 `pidof -x gendox | sed -e s/$MYPID//` &> /dev/null + +fi + +# If the output dir hasn't been set yet... +#if [ -z "$OUTPUTDIR" ] ; then +# OUTPUTDIR="../$(basename `pwd`)-doxygen"; +#fi + +#------------------------------------------------------------------------ +# execute some functions to determine stuff(c) +# Get the perl path (either from the config file, or find it) +#------------------------------------------------------------------------ +get_perlpath; +if [ X"$PERLPATH" == "X" ] ; then + echo -e "\nError: A working install of perl is needed to use doxygen"; + exit 2; +fi +[ $QUIETMODE -gt 0 ] || echo -e " -> Set PERL_PATH to: $PERLPATH"; + +get_dotpath; +[ $QUIETMODE -gt 0 ] || echo -e " -> Set HAVE_DOT to: $HAVEDOT"; +if [ X"$HAVEDOT" == "XYes" ] ; then + [ $QUIETMODE -gt 0 ] || echo -e " -> Set DOT_PATH to: $DOTPATH"; +fi + +get_language; +[ $QUIETMODE -gt 0 ] || echo -e " -> Set OUTPUT_LANGUAGE to: $OUPUTLANGUAGE"; + +get_projectname; +[ $QUIETMODE -gt 0 ] || echo -e " -> Set PROJECT_NAME to: $PROJECTNAME"; + +get_version; +[ $QUIETMODE -gt 0 ] || echo -e " -> Set PROJECT_NUMBER to: $VERSION"; +#------------------------------------------------------------------------ +# Got everything we need, now write the DoxyConfig file and run doxygen +#------------------------------------------------------------------------ + +# Clean up first +clean_up; + +# Put the images & reference pages in the right place +move_stuff; +if [ $RETVAL -ge 666 ] ; then + exit 666; +fi + +# Generate the config file +gen_doxyconfig; +if [ $RETVAL -gt 0 ] ; then + echo -e "Error: You are missing critical files." + exit RETVAL; +fi + +# build the reference page and the index +build_extra_html; + +# Generate documentation +RETVAL=0; +run_doxygen; +if [ $RETVAL -gt 0 ] ; then + echo -e "Doxygen error: returned $RETVAL"; + echo -e " Check doxygen.log for details"; +elif [ $RETVAL -lt 0 ] ; then + echo -e "Doxygen error: Doxygen returned $RETVAL"; +fi + +# if the log file is empty, remove it +if [ ! -s ./doxygen.log ] ; then + rm -f ./doxygen.log +fi + +#------------------------------------------------------------------------ +# Done. +#------------------------------------------------------------------------ +[ $QUIETMODE -gt 0 ] || echo -e "Finished..."; +[ $QUIETMODE -gt 0 ] || echo -e "Duration: $SECONDS seconds\n"; + +# echo -e "** Removing output while in debug mode **"; +# echo -e "** Output dir: $OUTPUTDIR **\n"; +# rm -rf $OUTPUTDIR + +exit 0; + diff --git a/generic_cpp.py b/generic_cpp.py new file mode 100644 index 00000000..44c78d31 --- /dev/null +++ b/generic_cpp.py @@ -0,0 +1,40 @@ +# Copyright (C) 2001-2006 William Joseph. +# For a list of contributors, see the accompanying CONTRIBUTORS file. +# +# This file is part of GtkRadiant. +# +# GtkRadiant is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GtkRadiant is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GtkRadiant; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +import os +import sys + +def svnAddText(filename): + os.system("svn add " + filename); + os.system("svn ps svn:eol-style native " + filename); + +def createCPPTemplate(filename, name): + file = open(filename, "wt") + file.write("\n") + file.write("#include \"" + name + ".h\"\n") + file.write("\n") + file.close() + +if __name__ == "__main__": + name = sys.argv[1] + location = sys.argv[2] + filename = os.path.join(location, name + ".cpp") + createCPPTemplate(filename, name) + svnAddText(filename) diff --git a/generic_h.py b/generic_h.py new file mode 100644 index 00000000..337f3b03 --- /dev/null +++ b/generic_h.py @@ -0,0 +1,41 @@ +# Copyright (C) 2001-2006 William Joseph. +# For a list of contributors, see the accompanying CONTRIBUTORS file. +# +# This file is part of GtkRadiant. +# +# GtkRadiant is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GtkRadiant is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GtkRadiant; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +import os +import sys + +def svnAddText(filename): + os.system("svn add " + filename); + os.system("svn ps svn:eol-style native " + filename); + +def createHeaderTemplate(filename, name): + file = open(filename, "wt") + file.write("\n") + file.write("#if !defined(_INCLUDED_" + name.upper() + "_H_)\n") + file.write("#define _INCLUDED_" + name.upper() + "_H_\n") + file.write("\n") + file.write("#endif\n") + +if __name__ == "__main__": + name = sys.argv[1] + location = sys.argv[2] + filename = os.path.join(location, name + ".h") + createHeaderTemplate(filename, name) + svnAddText(filename) diff --git a/generic_module.py b/generic_module.py new file mode 100644 index 00000000..e055e8fa --- /dev/null +++ b/generic_module.py @@ -0,0 +1,59 @@ +# Copyright (C) 2001-2006 William Joseph. +# For a list of contributors, see the accompanying CONTRIBUTORS file. +# +# This file is part of GtkRadiant. +# +# GtkRadiant is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GtkRadiant is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GtkRadiant; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +import os +import sys + +def svnAddText(filename): + os.system("svn add " + filename); + os.system("svn ps svn:eol-style native " + filename); + +def createHeaderTemplate(filename, name): + file = open(filename, "wt") + file.write("\n") + file.write("#if !defined(INCLUDED_" + name.upper() + "_H)\n") + file.write("#define INCLUDED_" + name.upper() + "_H\n") + file.write("\n") + file.write("#endif\n") + +def createCPPTemplate(filename, name): + file = open(filename, "wt") + file.write("\n") + file.write("#include \"" + name + ".h\"\n") + file.write("\n") + file.close() + +if __name__ == "__main__": + if not len(sys.argv) == 2: + print "usage: " + sys.argv[0] + " " + + path = sys.argv[1] + name = os.path.split(path)[1]; + + h = os.path.normpath(path + ".h") + print "writing " + h; + createHeaderTemplate(h, name) + svnAddText(h) + + cpp = os.path.normpath(path + ".cpp") + print "writing " + cpp; + createCPPTemplate(cpp, name) + svnAddText(cpp) + diff --git a/include/.cvsignore b/include/.cvsignore new file mode 100644 index 00000000..36594088 --- /dev/null +++ b/include/.cvsignore @@ -0,0 +1,8 @@ +version +version.h +version.new +aboutmsg +aboutmsg.h +aboutmsg.new +RADIANT_MAJOR +RADIANT_MINOR diff --git a/include/aboutmsg.default b/include/aboutmsg.default new file mode 100644 index 00000000..0f536453 --- /dev/null +++ b/include/aboutmsg.default @@ -0,0 +1 @@ +Custom build based on trunk \ No newline at end of file diff --git a/include/cullable.cpp b/include/cullable.cpp new file mode 100644 index 00000000..352ea5d6 --- /dev/null +++ b/include/cullable.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "cullable.h" + diff --git a/include/cullable.h b/include/cullable.h new file mode 100644 index 00000000..8e3c71bc --- /dev/null +++ b/include/cullable.h @@ -0,0 +1,71 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_CULLABLE_H) +#define INCLUDED_CULLABLE_H + +#include "generic/constant.h" + +template class BasicVector3; +typedef BasicVector3 Vector3; +class Plane3; +class Matrix4; +class AABB; +class Segment; + +template class EnumeratedValue; +struct VolumeIntersection; +typedef EnumeratedValue VolumeIntersectionValue; + +class VolumeTest +{ +public: + + /// \brief Returns true if \p point intersects volume. + virtual bool TestPoint(const Vector3& point) const = 0; + /// \brief Returns true if \p segment intersects volume. + virtual bool TestLine(const Segment& segment) const = 0; + /// \brief Returns true if \p plane faces towards volume. + virtual bool TestPlane(const Plane3& plane) const = 0; + /// \brief Returns true if \p plane transformed by \p localToWorld faces the viewer. + virtual bool TestPlane(const Plane3& plane, const Matrix4& localToWorld) const = 0; + /// \brief Returns the intersection of \p aabb and volume. + virtual VolumeIntersectionValue TestAABB(const AABB& aabb) const = 0; + /// \brief Returns the intersection of \p aabb transformed by \p localToWorld and volume. + virtual VolumeIntersectionValue TestAABB(const AABB& aabb, const Matrix4& localToWorld) const = 0; + + virtual bool fill() const = 0; + + virtual const Matrix4& GetViewport() const = 0; + virtual const Matrix4& GetProjection() const = 0; + virtual const Matrix4& GetModelview() const = 0; +}; + +class Cullable +{ +public: + STRING_CONSTANT(Name, "Cullable"); + + virtual VolumeIntersectionValue intersectVolume(const VolumeTest& test, const Matrix4& localToWorld) const = 0; +}; + + +#endif diff --git a/include/editable.cpp b/include/editable.cpp new file mode 100644 index 00000000..bcd04d8a --- /dev/null +++ b/include/editable.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "editable.h" + diff --git a/include/editable.h b/include/editable.h new file mode 100644 index 00000000..9e9bac04 --- /dev/null +++ b/include/editable.h @@ -0,0 +1,60 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_EDITABLE_H) +#define INCLUDED_EDITABLE_H + +template class BasicVector3; +typedef BasicVector3 Vector3; +template class BasicVector4; +typedef BasicVector4 Vector4; +class Matrix4; +typedef Vector4 Quaternion; + +#include "scenelib.h" + +class Editable +{ +public: + STRING_CONSTANT(Name, "Editable"); + + virtual const Matrix4& getLocalPivot() const = 0; +}; + +inline Editable* Node_getEditable(scene::Node& node) +{ + return NodeTypeCast::cast(node); +} + +class Snappable +{ +public: + STRING_CONSTANT(Name, "Snappable"); + + virtual void snapto(float snap) = 0; +}; + +inline Snappable* Node_getSnappable(scene::Node& node) +{ + return NodeTypeCast::cast(node); +} + +#endif diff --git a/include/iarchive.cpp b/include/iarchive.cpp new file mode 100644 index 00000000..b975bf05 --- /dev/null +++ b/include/iarchive.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "iarchive.h" + diff --git a/include/iarchive.h b/include/iarchive.h new file mode 100644 index 00000000..b3f21016 --- /dev/null +++ b/include/iarchive.h @@ -0,0 +1,162 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IARCHIVE_H) +#define INCLUDED_IARCHIVE_H + +#include +#include "generic/constant.h" + +class InputStream; + +/// \brief A file opened in binary mode. +class ArchiveFile +{ +public: + /// \brief Destroys the file object. + virtual void release() = 0; + /// \brief Returns the size of the file data in bytes. + virtual std::size_t size() const = 0; + /// \brief Returns the path to this file (relative to the filesystem root) + virtual const char* getName() const = 0; + /// \brief Returns the stream associated with this file. + /// Subsequent calls return the same stream. + /// The stream may be read forwards until it is exhausted. + /// The stream remains valid for the lifetime of the file. + virtual InputStream& getInputStream() = 0; +}; + +class TextInputStream; + +/// \brief A file opened in text mode. +class ArchiveTextFile +{ +public: + /// \brief Destroys the file object. + virtual void release() = 0; + /// \brief Returns the stream associated with this file. + /// Subsequent calls return the same stream. + /// The stream may be read forwards until it is exhausted. + /// The stream remains valid for the lifetime of the file. + virtual TextInputStream& getInputStream() = 0; +}; + +class ScopedArchiveFile +{ + ArchiveFile& m_file; +public: + ScopedArchiveFile(ArchiveFile& file) : m_file(file) + { + } + ~ScopedArchiveFile() + { + m_file.release(); + } +}; + +class CustomArchiveVisitor; + +class Archive +{ +public: + + class Visitor + { + public: + virtual void visit(const char* name) = 0; + }; + + typedef CustomArchiveVisitor VisitorFunc; + + enum EMode + { + eFiles = 0x01, + eDirectories = 0x02, + eFilesAndDirectories = 0x03, + }; + + /// \brief Destroys the archive object. + /// Any unreleased file object associated with the archive remains valid. */ + virtual void release() = 0; + /// \brief Returns a new object associated with the file identified by \p name, or 0 if the file cannot be opened. + /// Name comparisons are case-insensitive. + virtual ArchiveFile* openFile(const char* name) = 0; + /// \brief Returns a new object associated with the file identified by \p name, or 0 if the file cannot be opened. + /// Name comparisons are case-insensitive. + virtual ArchiveTextFile* openTextFile(const char* name) = 0; + /// Returns true if the file identified by \p name can be opened. + /// Name comparisons are case-insensitive. + virtual bool containsFile(const char* name) = 0; + /// \brief Performs a depth-first traversal of the archive tree starting at \p root. + /// Traverses the entire tree if \p root is "". + /// When a file is encountered, calls \c visitor.file passing the file name. + /// When a directory is encountered, calls \c visitor.directory passing the directory name. + /// Skips the directory if \c visitor.directory returned true. + /// Root comparisons are case-insensitive. + /// Names are mixed-case. + virtual void forEachFile(VisitorFunc visitor, const char* root) = 0; +}; + +class CustomArchiveVisitor +{ + Archive::Visitor* m_visitor; + Archive::EMode m_mode; + std::size_t m_depth; +public: + CustomArchiveVisitor(Archive::Visitor& visitor, Archive::EMode mode, std::size_t depth) + : m_visitor(&visitor), m_mode(mode), m_depth(depth) + { + } + void file(const char* name) + { + if((m_mode & Archive::eFiles) != 0) + m_visitor->visit(name); + } + bool directory(const char* name, std::size_t depth) + { + if((m_mode & Archive::eDirectories) != 0) + m_visitor->visit(name); + if(depth == m_depth) + return true; + return false; + } +}; + +typedef Archive* (*PFN_OPENARCHIVE) (const char* name); + +class _QERArchiveTable +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "archive"); + + PFN_OPENARCHIVE m_pfnOpenArchive; +}; + +template +class Modules; +typedef Modules<_QERArchiveTable> ArchiveModules; + +template +class ModulesRef; +typedef ModulesRef<_QERArchiveTable> ArchiveModulesRef; + +#endif diff --git a/include/ibrush.cpp b/include/ibrush.cpp new file mode 100644 index 00000000..05dc91ef --- /dev/null +++ b/include/ibrush.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ibrush.h" + diff --git a/include/ibrush.h b/include/ibrush.h new file mode 100644 index 00000000..ddc82559 --- /dev/null +++ b/include/ibrush.h @@ -0,0 +1,127 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IBRUSH_H) +#define INCLUDED_IBRUSH_H + +#include "generic/constant.h" +#include "generic/callback.h" +#include "generic/vector.h" +#include "itexdef.h" + +namespace scene +{ + class Node; +} + +#if 0 +class IBrushFace +{ +public: + virtual const char* GetShader() const = 0; + virtual void SetShader(const char* name) = 0; + virtual const TextureProjection& GetTexdef() const = 0; + virtual void GetTexdef(TextureProjection& projection) const = 0; + virtual void SetTexdef(const TextureProjection& projection) = 0; + virtual void GetFlags(ContentsFlagsValue& flags) const = 0; + virtual void SetFlags(const ContentsFlagsValue& flags) = 0; + virtual void ShiftTexdef(float s, float t) = 0; + virtual void ScaleTexdef(float s, float t) = 0; + virtual void RotateTexdef(float angle) = 0; + virtual void FitTexture(float s_repeat, float t_repeat) = 0; + virtual bool isDetail() const = 0; + virtual void setDetail(bool detail) = 0; +}; + +class IBrush +{ +public: + STRING_CONSTANT(Name, "IBrush"); + virtual void reserve(std::size_t count) = 0; + virtual void clear() = 0; + virtual void copy(const IBrush& other) = 0; + virtual IBrushFace* addPlane(const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, const TextureProjection& projection) = 0; + virtual const AABB& localAABB() const = 0; + virtual void removeEmptyFaces() = 0; +}; + +class IBrushFaceInstance +{ +public: + virtual IBrushFace& getFace() = 0; + virtual const IBrushFace& getFace() const = 0; + virtual bool isSelected() const = 0; + virtual void setSelected(SelectionSystem::EComponentMode mode, bool select) const = 0; +}; + +class IBrushInstance +{ +public: + STRING_CONSTANT(Name, "IBrushInstance"); + virtual void forEachFaceInstance(const BrushInstanceVisitor& visitor) = 0; +}; +#endif + +class _QERFaceData +{ +public: + _QERFaceData() : m_shader(""), contents(0), flags(0), value(0) + { + } + Vector3 m_p0; + Vector3 m_p1; + Vector3 m_p2; + texdef_t m_texdef; + const char* m_shader; + int contents; + int flags; + int value; +}; + +typedef Callback1 BrushFaceDataCallback; + +class BrushCreator +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "brush"); + virtual scene::Node& createBrush() = 0; + virtual bool useAlternativeTextureProjection() const = 0; + virtual void Brush_forEachFace(scene::Node& brush, const BrushFaceDataCallback& callback) = 0; + virtual bool Brush_addFace(scene::Node& brush, const _QERFaceData& faceData) = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalBrushModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalBrushModuleRef; + +inline BrushCreator& GlobalBrushCreator() +{ + return GlobalBrushModule::getTable(); +} + +#endif diff --git a/include/icamera.cpp b/include/icamera.cpp new file mode 100644 index 00000000..436e93a9 --- /dev/null +++ b/include/icamera.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "icamera.h" + diff --git a/include/icamera.h b/include/icamera.h new file mode 100644 index 00000000..c98fdc06 --- /dev/null +++ b/include/icamera.h @@ -0,0 +1,67 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//----------------------------------------------------------------------------- +// +// DESCRIPTION: +// camera interface +// + +#if !defined(INCLUDED_ICAMERA_H) +#define INCLUDED_ICAMERA_H + +#include "generic/constant.h" +#include "generic/callbackfwd.h" + +class Matrix4; + +class CameraView +{ +public: + virtual void setModelview(const Matrix4& modelview) = 0; + virtual void setFieldOfView(float fieldOfView) = 0; +}; + +class CameraModel +{ +public: + STRING_CONSTANT(Name, "CameraModel"); + virtual void setCameraView(CameraView* view, const Callback& disconnect) = 0; +}; + +template class BasicVector3; +typedef BasicVector3 Vector3; + +typedef void (* PFN_GETCAMERA) ( Vector3& origin, Vector3& angles ); +typedef void (* PFN_SETCAMERA) ( const Vector3& origin, const Vector3& angles ); +typedef void (* PFN_GETCAMWINDOWEXTENTS) ( int *x, int *y, int *width, int *height ); + +struct _QERCameraTable +{ + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "camera"); + + PFN_GETCAMERA m_pfnGetCamera; + PFN_SETCAMERA m_pfnSetCamera; + PFN_GETCAMWINDOWEXTENTS m_pfnGetCamWindowExtents; +}; + +#endif diff --git a/include/idatastream.cpp b/include/idatastream.cpp new file mode 100644 index 00000000..fff02b57 --- /dev/null +++ b/include/idatastream.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "idatastream.h" + diff --git a/include/idatastream.h b/include/idatastream.h new file mode 100644 index 00000000..c5a28a25 --- /dev/null +++ b/include/idatastream.h @@ -0,0 +1,83 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IDATASTREAM_H) +#define INCLUDED_IDATASTREAM_H + +#include + +class StreamBase +{ +public: + typedef std::size_t size_type; + typedef unsigned char byte_type; +}; + +/// \brief A read-only byte-stream. +class InputStream : public StreamBase +{ +public: + /// \brief Attempts to read the next \p length bytes from the stream to \p buffer. + /// Returns the number of bytes actually stored in \p buffer. + virtual size_type read(byte_type* buffer, size_type length) = 0; +}; + +/// \brief A write-only byte-stream. +class OutputStream : public StreamBase +{ +public: + /// \brief Attempts to write \p length bytes to the stream from \p buffer. + /// Returns the number of bytes actually read from \p buffer. + virtual size_type write(const byte_type* buffer, size_type length) = 0; +}; + +class SeekableStream +{ +public: + typedef int offset_type; + typedef std::size_t position_type; + + enum seekdir + { + beg, + cur, + end, + }; + + /// \brief Sets the current \p position of the stream relative to the start. + virtual position_type seek(position_type position) = 0; + /// \brief Sets the current \p position of the stream relative to either the start, end or current position. + virtual position_type seek(offset_type offset, seekdir direction) = 0; + /// \brief Returns the current position of the stream. + virtual position_type tell() const = 0; +}; + +/// \brief A seekable read-only byte-stream. +class SeekableInputStream : public InputStream, public SeekableStream +{ +}; + +/// \brief A seekable write-only byte-stream. +class SeekableOutputStream : public OutputStream, public SeekableStream +{ +}; + +#endif diff --git a/include/ieclass.cpp b/include/ieclass.cpp new file mode 100644 index 00000000..461edd53 --- /dev/null +++ b/include/ieclass.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ieclass.h" + diff --git a/include/ieclass.h b/include/ieclass.h new file mode 100644 index 00000000..89b86ca9 --- /dev/null +++ b/include/ieclass.h @@ -0,0 +1,118 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/// \file ieclass.h +/// \brief Entity Class definition loader API. + + +#if !defined(INCLUDED_IECLASS_H) +#define INCLUDED_IECLASS_H + +#include "generic/constant.h" + +#define MAX_FLAGS 16 + +// eclass show flags + +#define ECLASS_LIGHT 0x00000001 +#define ECLASS_ANGLE 0x00000002 +#define ECLASS_PATH 0x00000004 +#define ECLASS_MISCMODEL 0x00000008 + +class Shader; + +class EntityClass; +class ListAttributeType; + +class EntityClassCollector +{ +public: + virtual void insert(EntityClass* eclass) = 0; + virtual void insert(const char* name, const ListAttributeType& list) + { + } +}; + +struct EntityClassScanner +{ + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "eclass"); + + void (*scanFile)(EntityClassCollector& collector, const char* filename); + const char* (*getExtension)(); +}; + +#include "modulesystem.h" + +template +class ModuleRef; +typedef ModuleRef EClassModuleRef; + +template +class Modules; +typedef Modules EClassModules; + +template +class ModulesRef; +typedef ModulesRef EClassModulesRef; + + + + + + +class EntityClassVisitor +{ +public: + virtual void visit(EntityClass* eclass) = 0; +}; + +class ModuleObserver; + + +struct EntityClassManager +{ + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "eclassmanager"); + + EntityClass* (*findOrInsert)(const char* name, bool has_brushes); + const ListAttributeType* (*findListType)(const char* name); + void (*forEach)(EntityClassVisitor& visitor); + void (*attach)(ModuleObserver& observer); + void (*detach)(ModuleObserver& observer); + void (*realise)(); + void (*unrealise)(); +}; + +template +class GlobalModule; +typedef GlobalModule GlobalEntityClassManagerModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalEntityClassManagerModuleRef; + +inline EntityClassManager& GlobalEntityClassManager() +{ + return GlobalEntityClassManagerModule::getTable(); +} + +#endif diff --git a/include/ientity.cpp b/include/ientity.cpp new file mode 100644 index 00000000..0a2eef32 --- /dev/null +++ b/include/ientity.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ientity.h" + diff --git a/include/ientity.h b/include/ientity.h new file mode 100644 index 00000000..444e377d --- /dev/null +++ b/include/ientity.h @@ -0,0 +1,154 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IENTITY_H) +#define INCLUDED_IENTITY_H + +#include "generic/constant.h" + +#include "string/string.h" +#include "scenelib.h" + +class EntityClass; + +typedef Callback1 KeyObserver; + +class EntityKeyValue +{ +public: + virtual const char* c_str() const = 0; + virtual void assign(const char* other) = 0; + virtual void attach(const KeyObserver& observer) = 0; + virtual void detach(const KeyObserver& observer) = 0; +}; + +class Entity +{ +public: + STRING_CONSTANT(Name, "Entity"); + + class Observer + { + public: + virtual void insert(const char* key, EntityKeyValue& value) = 0; + virtual void erase(const char* key, EntityKeyValue& value) = 0; + virtual void clear() { }; + }; + + class Visitor + { + public: + virtual void visit(const char* key, const char* value) = 0; + }; + + virtual const EntityClass& getEntityClass() const = 0; + virtual void forEachKeyValue(Visitor& visitor) const = 0; + virtual void setKeyValue(const char* key, const char* value) = 0; + virtual const char* getKeyValue(const char* key) const = 0; + virtual bool isContainer() const = 0; + virtual void attach(Observer& observer) = 0; + virtual void detach(Observer& observer) = 0; +}; + +class EntityCopyingVisitor : public Entity::Visitor +{ + Entity& m_entity; +public: + EntityCopyingVisitor(Entity& entity) + : m_entity(entity) + { + } + + void visit(const char* key, const char* value) + { + if(!string_equal(key, "classname")) + { + m_entity.setKeyValue(key, value); + } + } +}; + +inline Entity* Node_getEntity(scene::Node& node) +{ + return NodeTypeCast::cast(node); +} + + +template +class Stack; +template +class Reference; + +namespace scene +{ + class Node; +} + +typedef Reference NodeReference; + +namespace scene +{ + typedef Stack Path; +} + +class Counter; + +class EntityCreator +{ +public: + INTEGER_CONSTANT(Version, 2); + STRING_CONSTANT(Name, "entity"); + + virtual scene::Node& createEntity(EntityClass* eclass) = 0; + + typedef void (*KeyValueChangedFunc)(); + virtual void setKeyValueChangedFunc(KeyValueChangedFunc func) = 0; + + virtual void setCounter(Counter* counter) = 0; + + virtual void connectEntities(const scene::Path& e1, const scene::Path& e2) = 0; + + virtual void setLightRadii(bool lightRadii) = 0; + virtual bool getLightRadii() = 0; + virtual void setShowNames(bool showNames) = 0; + virtual bool getShowNames() = 0; + virtual void setShowAngles(bool showAngles) = 0; + virtual bool getShowAngles() = 0; + + virtual void printStatistics() const = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalEntityModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalEntityModuleRef; + +inline EntityCreator& GlobalEntityCreator() +{ + return GlobalEntityModule::getTable(); +} + +#endif diff --git a/include/ifilesystem.cpp b/include/ifilesystem.cpp new file mode 100644 index 00000000..d941113c --- /dev/null +++ b/include/ifilesystem.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ifilesystem.h" + diff --git a/include/ifilesystem.h b/include/ifilesystem.h new file mode 100644 index 00000000..339c82f1 --- /dev/null +++ b/include/ifilesystem.h @@ -0,0 +1,133 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IFILESYSTEM_H) +#define INCLUDED_IFILESYSTEM_H + +#include +#include "generic/constant.h" +#include "generic/callbackfwd.h" + +typedef Callback1 ArchiveNameCallback; +typedef Callback1 FileNameCallback; + +class ArchiveFile; +class ArchiveTextFile; +class Archive; + +class ModuleObserver; + +typedef struct _GSList GSList; + +/// The Virtual File System. +class VirtualFileSystem +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "VFS"); + + /// \brief Adds a root search \p path. + /// Called before \c initialise. + virtual void initDirectory(const char *path) = 0; + /// \brief Initialises the filesystem. + /// Called after all root search paths have been added. + virtual void initialise() = 0; + /// \brief Shuts down the filesystem. + virtual void shutdown() = 0; + + /// \brief Returns the file identified by \p filename opened in binary mode, or 0 if not found. + /// The caller must \c release() the file returned if it is not 0. + virtual ArchiveFile* openFile(const char* filename) = 0; + /// \brief Returns the file identified by \p filename opened in text mode, or 0 if not found. + /// The caller must \c release() the file returned if it is not 0. + virtual ArchiveTextFile* openTextFile(const char* filename) = 0; + + /// \brief Opens the file identified by \p filename and reads it into \p buffer, or sets *\p buffer to 0 if not found. + /// Returns the size of the buffer allocated, or undefined value if *\p buffer is 0; + /// The caller must free the allocated buffer by calling \c freeFile + /// \deprecated Deprecated - use \c openFile. + virtual std::size_t loadFile(const char *filename, void **buffer) = 0; + /// \brief Frees the buffer returned by \c loadFile. + /// \deprecated Deprecated. + virtual void freeFile(void *p) = 0; + + /// \brief Calls \p callback for each directory under \p basedir. + virtual void forEachDirectory(const char* basedir, const FileNameCallback& callback, std::size_t depth = 1) = 0; + /// \brief Calls \p callback for each file under \p basedir matching \p extension. + /// Use "*" as \p extension to match all file extensions. + virtual void forEachFile(const char* basedir, const char* extension, const FileNameCallback& callback, std::size_t depth = 1) = 0; + + /// \brief Returns a list containing the relative names of all the directories under \p basedir. + /// The caller must free the returned list by calling \c clearFileDirList; + /// \deprecated Deprecated - use \c forEachDirectory. + virtual GSList* getDirList(const char *basedir) = 0; + /// \brief Returns a list containing the relative names of the files under \p basedir (\p extension can be "*" for all files). + /// The caller must free the returned list by calling \c clearFileDirList. + /// \deprecated Deprecated - use \c forEachFile. + virtual GSList* getFileList(const char *basedir, const char *extension) = 0; + /// \brief Frees the \p list returned from \c getDirList or \c getFileList. + /// \deprecated Deprecated. + virtual void clearFileDirList(GSList **list) = 0; + + /// \brief Returns the absolute filename for a relative \p name, or "" if not found. + virtual const char* findFile(const char* name) = 0; + /// \brief Returns the filesystem root for an absolute \p name, or "" if not found. + /// This can be used to convert an absolute name to a relative name. + virtual const char* findRoot(const char* name) = 0; + + /// \brief Attach an \p observer whose realise() and unrealise() methods will be called when the filesystem is initialised or shut down. + virtual void attach(ModuleObserver& observer) = 0; + /// \brief Detach an \p observer previously-attached by calling \c attach. + virtual void detach(ModuleObserver& observer) = 0; + + virtual Archive* getArchive(const char* archiveName) = 0; + virtual void forEachArchive(const ArchiveNameCallback& callback) = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalFileSystemModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalFileSystemModuleRef; + +inline VirtualFileSystem& GlobalFileSystem() +{ + return GlobalFileSystemModule::getTable(); +} + + +/// \deprecated Use \c openFile. +inline int vfsLoadFile(const char* filename, void** buffer, int index = 0) +{ + return static_cast(GlobalFileSystem().loadFile(filename, buffer)); +}; + +/// \deprecated Deprecated. +inline void vfsFreeFile(void* p) +{ + GlobalFileSystem().freeFile(p); +} + +#endif diff --git a/include/ifiletypes.cpp b/include/ifiletypes.cpp new file mode 100644 index 00000000..1d151586 --- /dev/null +++ b/include/ifiletypes.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ifiletypes.h" + diff --git a/include/ifiletypes.h b/include/ifiletypes.h new file mode 100644 index 00000000..e2851be2 --- /dev/null +++ b/include/ifiletypes.h @@ -0,0 +1,76 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IFILETYPES_H) +#define INCLUDED_IFILETYPES_H + +#include "generic/constant.h" + +class filetype_t +{ +public: + filetype_t() + : name(""), pattern("") + { + } + filetype_t(const char* _name, const char* _pattern) + : name(_name), pattern(_pattern) + { + } + const char* name; + const char* pattern; +}; + + +class IFileTypeList +{ +public: + virtual void addType(const char* moduleName, filetype_t type) = 0; +}; + +class IFileTypeRegistry +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "filetypes"); + + virtual void addType(const char* moduleType, const char* moduleName, filetype_t type) = 0; + virtual void getTypeList(const char* moduleType, IFileTypeList* typelist) = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalFiletypesModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalFiletypesModuleRef; + +inline IFileTypeRegistry& GlobalFiletypes() +{ + return GlobalFiletypesModule::getTable(); +} + + + +#endif diff --git a/include/ifilter.cpp b/include/ifilter.cpp new file mode 100644 index 00000000..1472a935 --- /dev/null +++ b/include/ifilter.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ifilter.h" + diff --git a/include/ifilter.h b/include/ifilter.h new file mode 100644 index 00000000..90619673 --- /dev/null +++ b/include/ifilter.h @@ -0,0 +1,88 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IFILTER_H) +#define INCLUDED_IFILTER_H + +#include "generic/constant.h" + +enum +{ + EXCLUDE_WORLD = 0x00000001, + EXCLUDE_ENT = 0x00000002, + EXCLUDE_CURVES = 0x00000004, + EXCLUDE_TRANSLUCENT = 0x00000008, + EXCLUDE_LIQUIDS = 0x00000010, + EXCLUDE_CAULK = 0x00000020, + EXCLUDE_CLIP = 0x00000040, + EXCLUDE_PATHS = 0x00000080, + EXCLUDE_LIGHTS = 0x00000100, + EXCLUDE_DETAILS = 0x00000200, + EXCLUDE_HINTSSKIPS = 0x00000400, + EXCLUDE_MODELS = 0x00000800, + EXCLUDE_AREAPORTALS = 0x00001000, + EXCLUDE_TRIGGERS = 0x00002000, + EXCLUDE_CLUSTERPORTALS = 0x00004000, + EXCLUDE_TERRAIN = 0x00008000, + EXCLUDE_LIGHTGRID = 0x00010000, + EXCLUDE_STRUCTURAL = 0x00020000, + EXCLUDE_BOTCLIP = 0x00040000, + EXCLUDE_VISPORTALS = 0x00080000, +}; + +class Filter +{ +public: + virtual void setActive(bool active) = 0; +}; + +class Filterable +{ +public: + virtual void updateFiltered() = 0; +}; + +class FilterSystem +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "filters"); + virtual void addFilter(Filter& filter, int mask) = 0; + virtual void registerFilterable(Filterable& filterable) = 0; + virtual void unregisterFilterable(Filterable& filterable) = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalFilterModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalFilterModuleRef; + +inline FilterSystem& GlobalFilterSystem() +{ + return GlobalFilterModule::getTable(); +} + +#endif diff --git a/include/igl.cpp b/include/igl.cpp new file mode 100644 index 00000000..170022ee --- /dev/null +++ b/include/igl.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "igl.h" + diff --git a/include/igl.h b/include/igl.h new file mode 100644 index 00000000..8cd81f86 --- /dev/null +++ b/include/igl.h @@ -0,0 +1,2832 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IGL_H) +#define INCLUDED_IGL_H + +#include +#include +#include "generic/constant.h" + +#if defined(WIN32) +#define QGL_DLLEXPORT __stdcall +#else +#define QGL_DLLEXPORT +#endif + +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef signed char GLbyte; +typedef short GLshort; +typedef int GLint; +typedef int GLsizei; +typedef unsigned char GLubyte; +typedef unsigned short GLushort; +typedef unsigned int GLuint; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef void GLvoid; + +#if !defined(GL_VERSION_1_1) +#define GL_VERSION_1_1 1 + +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_TRUE 1 +#define GL_FALSE 0 +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 +#define GL_ACCUM 0x0100 +#define GL_LOAD 0x0101 +#define GL_RETURN 0x0102 +#define GL_MULT 0x0103 +#define GL_ADD 0x0104 +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 +#define GL_DOUBLE 0x140A +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 +#define GL_2D 0x0600 +#define GL_3D 0x0601 +#define GL_3D_COLOR 0x0602 +#define GL_3D_COLOR_TEXTURE 0x0603 +#define GL_4D_COLOR_TEXTURE 0x0604 +#define GL_PASS_THROUGH_TOKEN 0x0700 +#define GL_POINT_TOKEN 0x0701 +#define GL_LINE_TOKEN 0x0702 +#define GL_POLYGON_TOKEN 0x0703 +#define GL_BITMAP_TOKEN 0x0704 +#define GL_DRAW_PIXEL_TOKEN 0x0705 +#define GL_COPY_PIXEL_TOKEN 0x0706 +#define GL_LINE_RESET_TOKEN 0x0707 +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_COEFF 0x0A00 +#define GL_ORDER 0x0A01 +#define GL_DOMAIN 0x0A02 +#define GL_CURRENT_COLOR 0x0B00 +#define GL_CURRENT_INDEX 0x0B01 +#define GL_CURRENT_NORMAL 0x0B02 +#define GL_CURRENT_TEXTURE_COORDS 0x0B03 +#define GL_CURRENT_RASTER_COLOR 0x0B04 +#define GL_CURRENT_RASTER_INDEX 0x0B05 +#define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 +#define GL_CURRENT_RASTER_POSITION 0x0B07 +#define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 +#define GL_CURRENT_RASTER_DISTANCE 0x0B09 +#define GL_POINT_SMOOTH 0x0B10 +#define GL_POINT_SIZE 0x0B11 +#define GL_POINT_SIZE_RANGE 0x0B12 +#define GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_LINE_WIDTH 0x0B21 +#define GL_LINE_WIDTH_RANGE 0x0B22 +#define GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_LINE_STIPPLE 0x0B24 +#define GL_LINE_STIPPLE_PATTERN 0x0B25 +#define GL_LINE_STIPPLE_REPEAT 0x0B26 +#define GL_LIST_MODE 0x0B30 +#define GL_MAX_LIST_NESTING 0x0B31 +#define GL_LIST_BASE 0x0B32 +#define GL_LIST_INDEX 0x0B33 +#define GL_POLYGON_MODE 0x0B40 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_POLYGON_STIPPLE 0x0B42 +#define GL_EDGE_FLAG 0x0B43 +#define GL_CULL_FACE 0x0B44 +#define GL_CULL_FACE_MODE 0x0B45 +#define GL_FRONT_FACE 0x0B46 +#define GL_LIGHTING 0x0B50 +#define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 +#define GL_LIGHT_MODEL_TWO_SIDE 0x0B52 +#define GL_LIGHT_MODEL_AMBIENT 0x0B53 +#define GL_SHADE_MODEL 0x0B54 +#define GL_COLOR_MATERIAL_FACE 0x0B55 +#define GL_COLOR_MATERIAL_PARAMETER 0x0B56 +#define GL_COLOR_MATERIAL 0x0B57 +#define GL_FOG 0x0B60 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_COLOR 0x0B66 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_ACCUM_CLEAR_VALUE 0x0B80 +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_MATRIX_MODE 0x0BA0 +#define GL_NORMALIZE 0x0BA1 +#define GL_VIEWPORT 0x0BA2 +#define GL_MODELVIEW_STACK_DEPTH 0x0BA3 +#define GL_PROJECTION_STACK_DEPTH 0x0BA4 +#define GL_TEXTURE_STACK_DEPTH 0x0BA5 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_TEXTURE_MATRIX 0x0BA8 +#define GL_ATTRIB_STACK_DEPTH 0x0BB0 +#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 +#define GL_ALPHA_TEST 0x0BC0 +#define GL_ALPHA_TEST_FUNC 0x0BC1 +#define GL_ALPHA_TEST_REF 0x0BC2 +#define GL_DITHER 0x0BD0 +#define GL_BLEND_DST 0x0BE0 +#define GL_BLEND_SRC 0x0BE1 +#define GL_BLEND 0x0BE2 +#define GL_LOGIC_OP_MODE 0x0BF0 +#define GL_INDEX_LOGIC_OP 0x0BF1 +#define GL_COLOR_LOGIC_OP 0x0BF2 +#define GL_AUX_BUFFERS 0x0C00 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_READ_BUFFER 0x0C02 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_INDEX_CLEAR_VALUE 0x0C20 +#define GL_INDEX_WRITEMASK 0x0C21 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_INDEX_MODE 0x0C30 +#define GL_RGBA_MODE 0x0C31 +#define GL_DOUBLEBUFFER 0x0C32 +#define GL_STEREO 0x0C33 +#define GL_RENDER_MODE 0x0C40 +#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 +#define GL_FOG_HINT 0x0C54 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 +#define GL_PIXEL_MAP_I_TO_I 0x0C70 +#define GL_PIXEL_MAP_S_TO_S 0x0C71 +#define GL_PIXEL_MAP_I_TO_R 0x0C72 +#define GL_PIXEL_MAP_I_TO_G 0x0C73 +#define GL_PIXEL_MAP_I_TO_B 0x0C74 +#define GL_PIXEL_MAP_I_TO_A 0x0C75 +#define GL_PIXEL_MAP_R_TO_R 0x0C76 +#define GL_PIXEL_MAP_G_TO_G 0x0C77 +#define GL_PIXEL_MAP_B_TO_B 0x0C78 +#define GL_PIXEL_MAP_A_TO_A 0x0C79 +#define GL_PIXEL_MAP_I_TO_I_SIZE 0x0CB0 +#define GL_PIXEL_MAP_S_TO_S_SIZE 0x0CB1 +#define GL_PIXEL_MAP_I_TO_R_SIZE 0x0CB2 +#define GL_PIXEL_MAP_I_TO_G_SIZE 0x0CB3 +#define GL_PIXEL_MAP_I_TO_B_SIZE 0x0CB4 +#define GL_PIXEL_MAP_I_TO_A_SIZE 0x0CB5 +#define GL_PIXEL_MAP_R_TO_R_SIZE 0x0CB6 +#define GL_PIXEL_MAP_G_TO_G_SIZE 0x0CB7 +#define GL_PIXEL_MAP_B_TO_B_SIZE 0x0CB8 +#define GL_PIXEL_MAP_A_TO_A_SIZE 0x0CB9 +#define GL_UNPACK_SWAP_BYTES 0x0CF0 +#define GL_UNPACK_LSB_FIRST 0x0CF1 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_SKIP_ROWS 0x0CF3 +#define GL_UNPACK_SKIP_PIXELS 0x0CF4 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_SWAP_BYTES 0x0D00 +#define GL_PACK_LSB_FIRST 0x0D01 +#define GL_PACK_ROW_LENGTH 0x0D02 +#define GL_PACK_SKIP_ROWS 0x0D03 +#define GL_PACK_SKIP_PIXELS 0x0D04 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_MAP_COLOR 0x0D10 +#define GL_MAP_STENCIL 0x0D11 +#define GL_INDEX_SHIFT 0x0D12 +#define GL_INDEX_OFFSET 0x0D13 +#define GL_RED_SCALE 0x0D14 +#define GL_RED_BIAS 0x0D15 +#define GL_ZOOM_X 0x0D16 +#define GL_ZOOM_Y 0x0D17 +#define GL_GREEN_SCALE 0x0D18 +#define GL_GREEN_BIAS 0x0D19 +#define GL_BLUE_SCALE 0x0D1A +#define GL_BLUE_BIAS 0x0D1B +#define GL_ALPHA_SCALE 0x0D1C +#define GL_ALPHA_BIAS 0x0D1D +#define GL_DEPTH_SCALE 0x0D1E +#define GL_DEPTH_BIAS 0x0D1F +#define GL_MAX_EVAL_ORDER 0x0D30 +#define GL_MAX_LIGHTS 0x0D31 +#define GL_MAX_CLIP_PLANES 0x0D32 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_PIXEL_MAP_TABLE 0x0D34 +#define GL_MAX_ATTRIB_STACK_DEPTH 0x0D35 +#define GL_MAX_MODELVIEW_STACK_DEPTH 0x0D36 +#define GL_MAX_NAME_STACK_DEPTH 0x0D37 +#define GL_MAX_PROJECTION_STACK_DEPTH 0x0D38 +#define GL_MAX_TEXTURE_STACK_DEPTH 0x0D39 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_MAX_CLIENT_ATTRIB_STACK_DEPTH 0x0D3B +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_INDEX_BITS 0x0D51 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_ALPHA_BITS 0x0D55 +#define GL_DEPTH_BITS 0x0D56 +#define GL_STENCIL_BITS 0x0D57 +#define GL_ACCUM_RED_BITS 0x0D58 +#define GL_ACCUM_GREEN_BITS 0x0D59 +#define GL_ACCUM_BLUE_BITS 0x0D5A +#define GL_ACCUM_ALPHA_BITS 0x0D5B +#define GL_NAME_STACK_DEPTH 0x0D70 +#define GL_AUTO_NORMAL 0x0D80 +#define GL_MAP1_COLOR_4 0x0D90 +#define GL_MAP1_INDEX 0x0D91 +#define GL_MAP1_NORMAL 0x0D92 +#define GL_MAP1_TEXTURE_COORD_1 0x0D93 +#define GL_MAP1_TEXTURE_COORD_2 0x0D94 +#define GL_MAP1_TEXTURE_COORD_3 0x0D95 +#define GL_MAP1_TEXTURE_COORD_4 0x0D96 +#define GL_MAP1_VERTEX_3 0x0D97 +#define GL_MAP1_VERTEX_4 0x0D98 +#define GL_MAP2_COLOR_4 0x0DB0 +#define GL_MAP2_INDEX 0x0DB1 +#define GL_MAP2_NORMAL 0x0DB2 +#define GL_MAP2_TEXTURE_COORD_1 0x0DB3 +#define GL_MAP2_TEXTURE_COORD_2 0x0DB4 +#define GL_MAP2_TEXTURE_COORD_3 0x0DB5 +#define GL_MAP2_TEXTURE_COORD_4 0x0DB6 +#define GL_MAP2_VERTEX_3 0x0DB7 +#define GL_MAP2_VERTEX_4 0x0DB8 +#define GL_MAP1_GRID_DOMAIN 0x0DD0 +#define GL_MAP1_GRID_SEGMENTS 0x0DD1 +#define GL_MAP2_GRID_DOMAIN 0x0DD2 +#define GL_MAP2_GRID_SEGMENTS 0x0DD3 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_FEEDBACK_BUFFER_POINTER 0x0DF0 +#define GL_FEEDBACK_BUFFER_SIZE 0x0DF1 +#define GL_FEEDBACK_BUFFER_TYPE 0x0DF2 +#define GL_SELECTION_BUFFER_POINTER 0x0DF3 +#define GL_SELECTION_BUFFER_SIZE 0x0DF4 +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_BORDER 0x1005 +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 +#define GL_LIGHT0 0x4000 +#define GL_LIGHT1 0x4001 +#define GL_LIGHT2 0x4002 +#define GL_LIGHT3 0x4003 +#define GL_LIGHT4 0x4004 +#define GL_LIGHT5 0x4005 +#define GL_LIGHT6 0x4006 +#define GL_LIGHT7 0x4007 +#define GL_AMBIENT 0x1200 +#define GL_DIFFUSE 0x1201 +#define GL_SPECULAR 0x1202 +#define GL_POSITION 0x1203 +#define GL_SPOT_DIRECTION 0x1204 +#define GL_SPOT_EXPONENT 0x1205 +#define GL_SPOT_CUTOFF 0x1206 +#define GL_CONSTANT_ATTENUATION 0x1207 +#define GL_LINEAR_ATTENUATION 0x1208 +#define GL_QUADRATIC_ATTENUATION 0x1209 +#define GL_COMPILE 0x1300 +#define GL_COMPILE_AND_EXECUTE 0x1301 +#define GL_CLEAR 0x1500 +#define GL_AND 0x1501 +#define GL_AND_REVERSE 0x1502 +#define GL_COPY 0x1503 +#define GL_AND_INVERTED 0x1504 +#define GL_NOOP 0x1505 +#define GL_XOR 0x1506 +#define GL_OR 0x1507 +#define GL_NOR 0x1508 +#define GL_EQUIV 0x1509 +#define GL_INVERT 0x150A +#define GL_OR_REVERSE 0x150B +#define GL_COPY_INVERTED 0x150C +#define GL_OR_INVERTED 0x150D +#define GL_NAND 0x150E +#define GL_SET 0x150F +#define GL_EMISSION 0x1600 +#define GL_SHININESS 0x1601 +#define GL_AMBIENT_AND_DIFFUSE 0x1602 +#define GL_COLOR_INDEXES 0x1603 +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 +#define GL_COLOR 0x1800 +#define GL_DEPTH 0x1801 +#define GL_STENCIL 0x1802 +#define GL_COLOR_INDEX 0x1900 +#define GL_STENCIL_INDEX 0x1901 +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_BITMAP 0x1A00 +#define GL_POINT 0x1B00 +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 +#define GL_RENDER 0x1C00 +#define GL_FEEDBACK 0x1C01 +#define GL_SELECT 0x1C02 +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 +#define GL_MODULATE 0x2100 +#define GL_DECAL 0x2101 +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_ENV_COLOR 0x2201 +#define GL_TEXTURE_ENV 0x2300 +#define GL_EYE_LINEAR 0x2400 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_SPHERE_MAP 0x2402 +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_PLANE 0x2502 +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_CLAMP 0x2900 +#define GL_REPEAT 0x2901 +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 +#define GL_CURRENT_BIT 0x00000001 +#define GL_POINT_BIT 0x00000002 +#define GL_LINE_BIT 0x00000004 +#define GL_POLYGON_BIT 0x00000008 +#define GL_POLYGON_STIPPLE_BIT 0x00000010 +#define GL_PIXEL_MODE_BIT 0x00000020 +#define GL_LIGHTING_BIT 0x00000040 +#define GL_FOG_BIT 0x00000080 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_VIEWPORT_BIT 0x00000800 +#define GL_TRANSFORM_BIT 0x00001000 +#define GL_ENABLE_BIT 0x00002000 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_HINT_BIT 0x00008000 +#define GL_EVAL_BIT 0x00010000 +#define GL_LIST_BIT 0x00020000 +#define GL_TEXTURE_BIT 0x00040000 +#define GL_SCISSOR_BIT 0x00080000 +#define GL_ALL_ATTRIB_BITS 0x000fffff +#define GL_CLIENT_PIXEL_STORE_BIT 0x00000001 +#define GL_CLIENT_VERTEX_ARRAY_BIT 0x00000002 +#define GL_CLIENT_ALL_ATTRIB_BITS 0xffffffff +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F +#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A +#define GL_INTENSITY8 0x804B +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_TEXTURE_PRIORITY 0x8066 +#define GL_TEXTURE_RESIDENT 0x8067 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +#define GL_EDGE_FLAG_ARRAY 0x8079 +#define GL_VERTEX_ARRAY_SIZE 0x807A +#define GL_VERTEX_ARRAY_TYPE 0x807B +#define GL_VERTEX_ARRAY_STRIDE 0x807C +#define GL_NORMAL_ARRAY_TYPE 0x807E +#define GL_NORMAL_ARRAY_STRIDE 0x807F +#define GL_COLOR_ARRAY_SIZE 0x8081 +#define GL_COLOR_ARRAY_TYPE 0x8082 +#define GL_COLOR_ARRAY_STRIDE 0x8083 +#define GL_INDEX_ARRAY_TYPE 0x8085 +#define GL_INDEX_ARRAY_STRIDE 0x8086 +#define GL_TEXTURE_COORD_ARRAY_SIZE 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A +#define GL_EDGE_FLAG_ARRAY_STRIDE 0x808C +#define GL_VERTEX_ARRAY_POINTER 0x808E +#define GL_NORMAL_ARRAY_POINTER 0x808F +#define GL_COLOR_ARRAY_POINTER 0x8090 +#define GL_INDEX_ARRAY_POINTER 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER 0x8093 +#define GL_V2F 0x2A20 +#define GL_V3F 0x2A21 +#define GL_C4UB_V2F 0x2A22 +#define GL_C4UB_V3F 0x2A23 +#define GL_C3F_V3F 0x2A24 +#define GL_N3F_V3F 0x2A25 +#define GL_C4F_N3F_V3F 0x2A26 +#define GL_T2F_V3F 0x2A27 +#define GL_T4F_V4F 0x2A28 +#define GL_T2F_C4UB_V3F 0x2A29 +#define GL_T2F_C3F_V3F 0x2A2A +#define GL_T2F_N3F_V3F 0x2A2B +#define GL_T2F_C4F_N3F_V3F 0x2A2C +#define GL_T4F_C4F_N3F_V4F 0x2A2D + +#define glAccum GlobalOpenGL().m_glAccum +#define glAlphaFunc GlobalOpenGL().m_glAlphaFunc +#define glAreTexturesResident GlobalOpenGL().m_glAreTexturesResident +#define glArrayElement GlobalOpenGL().m_glArrayElement +#define glBegin GlobalOpenGL().m_glBegin +#define glBindTexture GlobalOpenGL().m_glBindTexture +#define glBitmap GlobalOpenGL().m_glBitmap +#define glBlendFunc GlobalOpenGL().m_glBlendFunc +#define glCallList GlobalOpenGL().m_glCallList +#define glCallLists GlobalOpenGL().m_glCallLists +#define glClear GlobalOpenGL().m_glClear +#define glClearAccum GlobalOpenGL().m_glClearAccum +#define glClearColor GlobalOpenGL().m_glClearColor +#define glClearDepth GlobalOpenGL().m_glClearDepth +#define glClearIndex GlobalOpenGL().m_glClearIndex +#define glClearStencil GlobalOpenGL().m_glClearStencil +#define glClipPlane GlobalOpenGL().m_glClipPlane +#define glColor3b GlobalOpenGL().m_glColor3b +#define glColor3bv GlobalOpenGL().m_glColor3bv +#define glColor3d GlobalOpenGL().m_glColor3d +#define glColor3dv GlobalOpenGL().m_glColor3dv +#define glColor3f GlobalOpenGL().m_glColor3f +#define glColor3fv GlobalOpenGL().m_glColor3fv +#define glColor3i GlobalOpenGL().m_glColor3i +#define glColor3iv GlobalOpenGL().m_glColor3iv +#define glColor3s GlobalOpenGL().m_glColor3s +#define glColor3sv GlobalOpenGL().m_glColor3sv +#define glColor3ub GlobalOpenGL().m_glColor3ub +#define glColor3ubv GlobalOpenGL().m_glColor3ubv +#define glColor3ui GlobalOpenGL().m_glColor3ui +#define glColor3uiv GlobalOpenGL().m_glColor3uiv +#define glColor3us GlobalOpenGL().m_glColor3us +#define glColor3usv GlobalOpenGL().m_glColor3usv +#define glColor4b GlobalOpenGL().m_glColor4b +#define glColor4bv GlobalOpenGL().m_glColor4bv +#define glColor4d GlobalOpenGL().m_glColor4d +#define glColor4dv GlobalOpenGL().m_glColor4dv +#define glColor4f GlobalOpenGL().m_glColor4f +#define glColor4fv GlobalOpenGL().m_glColor4fv +#define glColor4i GlobalOpenGL().m_glColor4i +#define glColor4iv GlobalOpenGL().m_glColor4iv +#define glColor4s GlobalOpenGL().m_glColor4s +#define glColor4sv GlobalOpenGL().m_glColor4sv +#define glColor4ub GlobalOpenGL().m_glColor4ub +#define glColor4ubv GlobalOpenGL().m_glColor4ubv +#define glColor4ui GlobalOpenGL().m_glColor4ui +#define glColor4uiv GlobalOpenGL().m_glColor4uiv +#define glColor4us GlobalOpenGL().m_glColor4us +#define glColor4usv GlobalOpenGL().m_glColor4usv +#define glColorMask GlobalOpenGL().m_glColorMask +#define glColorMaterial GlobalOpenGL().m_glColorMaterial +#define glColorPointer GlobalOpenGL().m_glColorPointer +#define glCopyPixels GlobalOpenGL().m_glCopyPixels +#define glCopyTexImage1D GlobalOpenGL().m_glCopyTexImage1D +#define glCopyTexImage2D GlobalOpenGL().m_glCopyTexImage2D +#define glCopyTexSubImage1D GlobalOpenGL().m_glCopyTexSubImage1D +#define glCopyTexSubImage2D GlobalOpenGL().m_glCopyTexSubImage2D +#define glCullFace GlobalOpenGL().m_glCullFace +#define glDeleteLists GlobalOpenGL().m_glDeleteLists +#define glDeleteTextures GlobalOpenGL().m_glDeleteTextures +#define glDepthFunc GlobalOpenGL().m_glDepthFunc +#define glDepthMask GlobalOpenGL().m_glDepthMask +#define glDepthRange GlobalOpenGL().m_glDepthRange +#define glDisable GlobalOpenGL().m_glDisable +#define glDisableClientState GlobalOpenGL().m_glDisableClientState +#define glDrawArrays GlobalOpenGL().m_glDrawArrays +#define glDrawBuffer GlobalOpenGL().m_glDrawBuffer +#define glDrawElements GlobalOpenGL().m_glDrawElements +#define glDrawPixels GlobalOpenGL().m_glDrawPixels +#define glEdgeFlag GlobalOpenGL().m_glEdgeFlag +#define glEdgeFlagPointer GlobalOpenGL().m_glEdgeFlagPointer +#define glEdgeFlagv GlobalOpenGL().m_glEdgeFlagv +#define glEnable GlobalOpenGL().m_glEnable +#define glEnableClientState GlobalOpenGL().m_glEnableClientState +#define glEnd GlobalOpenGL().m_glEnd +#define glEndList GlobalOpenGL().m_glEndList +#define glEvalCoord1d GlobalOpenGL().m_glEvalCoord1d +#define glEvalCoord1dv GlobalOpenGL().m_glEvalCoord1dv +#define glEvalCoord1f GlobalOpenGL().m_glEvalCoord1f +#define glEvalCoord1fv GlobalOpenGL().m_glEvalCoord1fv +#define glEvalCoord2d GlobalOpenGL().m_glEvalCoord2d +#define glEvalCoord2dv GlobalOpenGL().m_glEvalCoord2dv +#define glEvalCoord2f GlobalOpenGL().m_glEvalCoord2f +#define glEvalCoord2fv GlobalOpenGL().m_glEvalCoord2fv +#define glEvalMesh1 GlobalOpenGL().m_glEvalMesh1 +#define glEvalMesh2 GlobalOpenGL().m_glEvalMesh2 +#define glEvalPoint1 GlobalOpenGL().m_glEvalPoint1 +#define glEvalPoint2 GlobalOpenGL().m_glEvalPoint2 +#define glFeedbackBuffer GlobalOpenGL().m_glFeedbackBuffer +#define glFinish GlobalOpenGL().m_glFinish +#define glFlush GlobalOpenGL().m_glFlush +#define glFogf GlobalOpenGL().m_glFogf +#define glFogfv GlobalOpenGL().m_glFogfv +#define glFogi GlobalOpenGL().m_glFogi +#define glFogiv GlobalOpenGL().m_glFogiv +#define glFrontFace GlobalOpenGL().m_glFrontFace +#define glFrustum GlobalOpenGL().m_glFrustum +#define glGenLists GlobalOpenGL().m_glGenLists +#define glGenTextures GlobalOpenGL().m_glGenTextures +#define glGetBooleanv GlobalOpenGL().m_glGetBooleanv +#define glGetClipPlane GlobalOpenGL().m_glGetClipPlane +#define glGetDoublev GlobalOpenGL().m_glGetDoublev +#define glGetError GlobalOpenGL().m_glGetError +#define glGetFloatv GlobalOpenGL().m_glGetFloatv +#define glGetIntegerv GlobalOpenGL().m_glGetIntegerv +#define glGetLightfv GlobalOpenGL().m_glGetLightfv +#define glGetLightiv GlobalOpenGL().m_glGetLightiv +#define glGetMapdv GlobalOpenGL().m_glGetMapdv +#define glGetMapfv GlobalOpenGL().m_glGetMapfv +#define glGetMapiv GlobalOpenGL().m_glGetMapiv +#define glGetMaterialfv GlobalOpenGL().m_glGetMaterialfv +#define glGetMaterialiv GlobalOpenGL().m_glGetMaterialiv +#define glGetPixelMapfv GlobalOpenGL().m_glGetPixelMapfv +#define glGetPixelMapuiv GlobalOpenGL().m_glGetPixelMapuiv +#define glGetPixelMapusv GlobalOpenGL().m_glGetPixelMapusv +#define glGetPointerv GlobalOpenGL().m_glGetPointerv +#define glGetPolygonStipple GlobalOpenGL().m_glGetPolygonStipple +#define glGetString GlobalOpenGL().m_glGetString +#define glGetTexEnvfv GlobalOpenGL().m_glGetTexEnvfv +#define glGetTexEnviv GlobalOpenGL().m_glGetTexEnviv +#define glGetTexGendv GlobalOpenGL().m_glGetTexGendv +#define glGetTexGenfv GlobalOpenGL().m_glGetTexGenfv +#define glGetTexGeniv GlobalOpenGL().m_glGetTexGeniv +#define glGetTexImage GlobalOpenGL().m_glGetTexImage +#define glGetTexLevelParameterfv GlobalOpenGL().m_glGetTexLevelParameter +#define glGetTexLevelParameteriv GlobalOpenGL().m_glGetTexLevelParameteriv +#define glGetTexParameterfv GlobalOpenGL().m_glGetTexParameterfv +#define glGetTexParameteriv GlobalOpenGL().m_glGetTexParameteriv +#define glHint GlobalOpenGL().m_glHint +#define glIndexMask GlobalOpenGL().m_glIndexMask +#define glIndexPointer GlobalOpenGL().m_glIndexPointer +#define glIndexd GlobalOpenGL().m_glIndexd +#define glIndexdv GlobalOpenGL().m_glIndexdv +#define glIndexf GlobalOpenGL().m_glIndexf +#define glIndexfv GlobalOpenGL().m_glIndexfv +#define glIndexi GlobalOpenGL().m_glIndexi +#define glIndexiv GlobalOpenGL().m_glIndexiv +#define glIndexs GlobalOpenGL().m_glIndexs +#define glIndexsv GlobalOpenGL().m_glIndexsv +#define glIndexub GlobalOpenGL().m_glIndexub +#define glIndexubv GlobalOpenGL().m_glIndexubv +#define glInitNames GlobalOpenGL().m_glInitNames +#define glInterleavedArrays GlobalOpenGL().m_glInterleavedArrays +#define glIsEnabled GlobalOpenGL().m_glIsEnabled +#define glIsList GlobalOpenGL().m_glIsList +#define glIsTexture GlobalOpenGL().m_glIsTexture +#define glLightModelf GlobalOpenGL().m_glLightModelf +#define glLightModelfv GlobalOpenGL().m_glLightModelfv +#define glLightModeli GlobalOpenGL().m_glLightModeli +#define glLightModeliv GlobalOpenGL().m_glLightModeliv +#define glLightf GlobalOpenGL().m_glLightf +#define glLightfv GlobalOpenGL().m_glLightfv +#define glLighti GlobalOpenGL().m_glLighti +#define glLightiv GlobalOpenGL().m_glLightiv +#define glLineStipple GlobalOpenGL().m_glLineStipple +#define glLineWidth GlobalOpenGL().m_glLineWidth +#define glListBase GlobalOpenGL().m_glListBase +#define glLoadIdentity GlobalOpenGL().m_glLoadIdentity +#define glLoadMatrixd GlobalOpenGL().m_glLoadMatrixd +#define glLoadMatrixf GlobalOpenGL().m_glLoadMatrixf +#define glLoadName GlobalOpenGL().m_glLoadName +#define glLogicOp GlobalOpenGL().m_glLogicOp +#define glMap1d GlobalOpenGL().m_glMap1d +#define glMap1f GlobalOpenGL().m_glMap1f +#define glMap2d GlobalOpenGL().m_glMap2d +#define glMap2f GlobalOpenGL().m_glMap2f +#define glMapGrid1d GlobalOpenGL().m_glMapGrid1d +#define glMapGrid1f GlobalOpenGL().m_glMapGrid1f +#define glMapGrid2d GlobalOpenGL().m_glMapGrid2d +#define glMapGrid2f GlobalOpenGL().m_glMapGrid2f +#define glMaterialf GlobalOpenGL().m_glMaterialf +#define glMaterialfv GlobalOpenGL().m_glMaterialfv +#define glMateriali GlobalOpenGL().m_glMateriali +#define glMaterialiv GlobalOpenGL().m_glMaterialiv +#define glMatrixMode GlobalOpenGL().m_glMatrixMode +#define glMultMatrixd GlobalOpenGL().m_glMultMatrixd +#define glMultMatrixf GlobalOpenGL().m_glMultMatrixf +#define glNewList GlobalOpenGL().m_glNewList +#define glNormal3b GlobalOpenGL().m_glNormal3b +#define glNormal3bv GlobalOpenGL().m_glNormal3bv +#define glNormal3d GlobalOpenGL().m_glNormal3d +#define glNormal3dv GlobalOpenGL().m_glNormal3dv +#define glNormal3f GlobalOpenGL().m_glNormal3f +#define glNormal3fv GlobalOpenGL().m_glNormal3fv +#define glNormal3i GlobalOpenGL().m_glNormal3i +#define glNormal3iv GlobalOpenGL().m_glNormal3iv +#define glNormal3s GlobalOpenGL().m_glNormal3s +#define glNormal3sv GlobalOpenGL().m_glNormal3sv +#define glNormalPointer GlobalOpenGL().m_glNormalPointer +#define glOrtho GlobalOpenGL().m_glOrtho +#define glPassThrough GlobalOpenGL().m_glPassThrough +#define glPixelMapfv GlobalOpenGL().m_glPixelMapfv +#define glPixelMapuiv GlobalOpenGL().m_glPixelMapuiv +#define glPixelMapusv GlobalOpenGL().m_glPixelMapusv +#define glPixelStoref GlobalOpenGL().m_glPixelStoref +#define glPixelStorei GlobalOpenGL().m_glPixelStorei +#define glPixelTransferf GlobalOpenGL().m_glPixelTransferf +#define glPixelTransferi GlobalOpenGL().m_glPixelTransferi +#define glPixelZoom GlobalOpenGL().m_glPixelZoom +#define glPointSize GlobalOpenGL().m_glPointSize +#define glPolygonMode GlobalOpenGL().m_glPolygonMode +#define glPolygonOffset GlobalOpenGL().m_glPolygonOffset +#define glPolygonStipple GlobalOpenGL().m_glPolygonStipple +#define glPopAttrib GlobalOpenGL().m_glPopAttrib +#define glPopClientAttrib GlobalOpenGL().m_glPopClientAttrib +#define glPopMatrix GlobalOpenGL().m_glPopMatrix +#define glPopName GlobalOpenGL().m_glPopName +#define glPrioritizeTextures GlobalOpenGL().m_glPrioritizeTextures +#define glPushAttrib GlobalOpenGL().m_glPushAttrib +#define glPushClientAttrib GlobalOpenGL().m_glPushClientAttrib +#define glPushMatrix GlobalOpenGL().m_glPushMatrix +#define glPushName GlobalOpenGL().m_glPushName +#define glRasterPos2d GlobalOpenGL().m_glRasterPos2d +#define glRasterPos2dv GlobalOpenGL().m_glRasterPos2dv +#define glRasterPos2f GlobalOpenGL().m_glRasterPos2f +#define glRasterPos2fv GlobalOpenGL().m_glRasterPos2fv +#define glRasterPos2i GlobalOpenGL().m_glRasterPos2i +#define glRasterPos2iv GlobalOpenGL().m_glRasterPos2iv +#define glRasterPos2s GlobalOpenGL().m_glRasterPos2s +#define glRasterPos2sv GlobalOpenGL().m_glRasterPos2sv +#define glRasterPos3d GlobalOpenGL().m_glRasterPos3d +#define glRasterPos3dv GlobalOpenGL().m_glRasterPos3dv +#define glRasterPos3f GlobalOpenGL().m_glRasterPos3f +#define glRasterPos3fv GlobalOpenGL().m_glRasterPos3fv +#define glRasterPos3i GlobalOpenGL().m_glRasterPos3i +#define glRasterPos3iv GlobalOpenGL().m_glRasterPos3iv +#define glRasterPos3s GlobalOpenGL().m_glRasterPos3s +#define glRasterPos3sv GlobalOpenGL().m_glRasterPos3sv +#define glRasterPos4d GlobalOpenGL().m_glRasterPos4d +#define glRasterPos4dv GlobalOpenGL().m_glRasterPos4dv +#define glRasterPos4f GlobalOpenGL().m_glRasterPos4f +#define glRasterPos4fv GlobalOpenGL().m_glRasterPos4fv +#define glRasterPos4i GlobalOpenGL().m_glRasterPos4i +#define glRasterPos4iv GlobalOpenGL().m_glRasterPos4iv +#define glRasterPos4s GlobalOpenGL().m_glRasterPos4s +#define glRasterPos4sv GlobalOpenGL().m_glRasterPos4sv +#define glReadBuffer GlobalOpenGL().m_glReadBuffer +#define glReadPixels GlobalOpenGL().m_glReadPixels +#define glRectd GlobalOpenGL().m_glRectd +#define glRectdv GlobalOpenGL().m_glRectdv +#define glRectf GlobalOpenGL().m_glRectf +#define glRectfv GlobalOpenGL().m_glRectfv +#define glRecti GlobalOpenGL().m_glRecti +#define glRectiv GlobalOpenGL().m_glRectiv +#define glRects GlobalOpenGL().m_glRects +#define glRectsv GlobalOpenGL().m_glRectsv +#define glRenderMode GlobalOpenGL().m_glRenderMode +#define glRotated GlobalOpenGL().m_glRotated +#define glRotatef GlobalOpenGL().m_glRotatef +#define glScaled GlobalOpenGL().m_glScaled +#define glScalef GlobalOpenGL().m_glScalef +#define glScissor GlobalOpenGL().m_glScissor +#define glSelectBuffer GlobalOpenGL().m_glSelectBuffer +#define glShadeModel GlobalOpenGL().m_glShadeModel +#define glStencilFunc GlobalOpenGL().m_glStencilFunc +#define glStencilMask GlobalOpenGL().m_glStencilMask +#define glStencilOp GlobalOpenGL().m_glStencilOp +#define glTexCoord1d GlobalOpenGL().m_glTexCoord1d +#define glTexCoord1dv GlobalOpenGL().m_glTexCoord1dv +#define glTexCoord1f GlobalOpenGL().m_glTexCoord1f +#define glTexCoord1fv GlobalOpenGL().m_glTexCoord1fv +#define glTexCoord1i GlobalOpenGL().m_glTexCoord1i +#define glTexCoord1iv GlobalOpenGL().m_glTexCoord1iv +#define glTexCoord1s GlobalOpenGL().m_glTexCoord1s +#define glTexCoord1sv GlobalOpenGL().m_glTexCoord1sv +#define glTexCoord2d GlobalOpenGL().m_glTexCoord2d +#define glTexCoord2dv GlobalOpenGL().m_glTexCoord2dv +#define glTexCoord2f GlobalOpenGL().m_glTexCoord2f +#define glTexCoord2fv GlobalOpenGL().m_glTexCoord2fv +#define glTexCoord2i GlobalOpenGL().m_glTexCoord2i +#define glTexCoord2iv GlobalOpenGL().m_glTexCoord2iv +#define glTexCoord2s GlobalOpenGL().m_glTexCoord2s +#define glTexCoord2sv GlobalOpenGL().m_glTexCoord2sv +#define glTexCoord3d GlobalOpenGL().m_glTexCoord3d +#define glTexCoord3dv GlobalOpenGL().m_glTexCoord3dv +#define glTexCoord3f GlobalOpenGL().m_glTexCoord3f +#define glTexCoord3fv GlobalOpenGL().m_glTexCoord3fv +#define glTexCoord3i GlobalOpenGL().m_glTexCoord3i +#define glTexCoord3iv GlobalOpenGL().m_glTexCoord3iv +#define glTexCoord3s GlobalOpenGL().m_glTexCoord3s +#define glTexCoord3sv GlobalOpenGL().m_glTexCoord3sv +#define glTexCoord4d GlobalOpenGL().m_glTexCoord4d +#define glTexCoord4dv GlobalOpenGL().m_glTexCoord4dv +#define glTexCoord4f GlobalOpenGL().m_glTexCoord4f +#define glTexCoord4fv GlobalOpenGL().m_glTexCoord4fv +#define glTexCoord4i GlobalOpenGL().m_glTexCoord4i +#define glTexCoord4iv GlobalOpenGL().m_glTexCoord4iv +#define glTexCoord4s GlobalOpenGL().m_glTexCoord4s +#define glTexCoord4sv GlobalOpenGL().m_glTexCoord4sv +#define glTexCoordPointer GlobalOpenGL().m_glTexCoordPointer +#define glTexEnvf GlobalOpenGL().m_glTexEnvf +#define glTexEnvfv GlobalOpenGL().m_glTexEnvfv +#define glTexEnvi GlobalOpenGL().m_glTexEnvi +#define glTexEnviv GlobalOpenGL().m_glTexEnviv +#define glTexGend GlobalOpenGL().m_glTexGend +#define glTexGendv GlobalOpenGL().m_glTexGendv +#define glTexGenf GlobalOpenGL().m_glTexGenf +#define glTexGenfv GlobalOpenGL().m_glTexGenfv +#define glTexGeni GlobalOpenGL().m_glTexGeni +#define glTexGeniv GlobalOpenGL().m_glTexGeniv +#define glTexImage1D GlobalOpenGL().m_glTexImage1D +#define glTexImage2D GlobalOpenGL().m_glTexImage2D +#define glTexParameterf GlobalOpenGL().m_glTexParameterf +#define glTexParameterfv GlobalOpenGL().m_glTexParameterfv +#define glTexParameteri GlobalOpenGL().m_glTexParameteri +#define glTexParameteriv GlobalOpenGL().m_glTexParameteriv +#define glTexSubImage1D GlobalOpenGL().m_glTexSubImage1D +#define glTexSubImage2D GlobalOpenGL().m_glTexSubImage2D +#define glTranslated GlobalOpenGL().m_glTranslated +#define glTranslatef GlobalOpenGL().m_glTranslatef +#define glVertex2d GlobalOpenGL().m_glVertex2d +#define glVertex2dv GlobalOpenGL().m_glVertex2dv +#define glVertex2f GlobalOpenGL().m_glVertex2f +#define glVertex2fv GlobalOpenGL().m_glVertex2fv +#define glVertex2i GlobalOpenGL().m_glVertex2i +#define glVertex2iv GlobalOpenGL().m_glVertex2iv +#define glVertex2s GlobalOpenGL().m_glVertex2s +#define glVertex2sv GlobalOpenGL().m_glVertex2sv +#define glVertex3d GlobalOpenGL().m_glVertex3d +#define glVertex3dv GlobalOpenGL().m_glVertex3dv +#define glVertex3f GlobalOpenGL().m_glVertex3f +#define glVertex3fv GlobalOpenGL().m_glVertex3fv +#define glVertex3i GlobalOpenGL().m_glVertex3i +#define glVertex3iv GlobalOpenGL().m_glVertex3iv +#define glVertex3s GlobalOpenGL().m_glVertex3s +#define glVertex3sv GlobalOpenGL().m_glVertex3sv +#define glVertex4d GlobalOpenGL().m_glVertex4d +#define glVertex4dv GlobalOpenGL().m_glVertex4dv +#define glVertex4f GlobalOpenGL().m_glVertex4f +#define glVertex4fv GlobalOpenGL().m_glVertex4fv +#define glVertex4i GlobalOpenGL().m_glVertex4i +#define glVertex4iv GlobalOpenGL().m_glVertex4iv +#define glVertex4s GlobalOpenGL().m_glVertex4s +#define glVertex4sv GlobalOpenGL().m_glVertex4sv +#define glVertexPointer GlobalOpenGL().m_glVertexPointer +#define glViewport GlobalOpenGL().m_glViewport + +#endif + + +#if !defined(GL_EXT_vertex_array) +#define GL_EXT_vertex_array 1 + +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +#define GL_DOUBLE_EXT GL_DOUBLE + +#endif + + +#if !defined(GL_EXT_bgra) +#define GL_EXT_bgra 1 + +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 + +#endif + + +#if !defined(GL_EXT_paletted_texture) +#define GL_EXT_paletted_texture 1 + +#define GL_COLOR_TABLE_FORMAT_EXT 0x80D8 +#define GL_COLOR_TABLE_WIDTH_EXT 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_EXT 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_EXT 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_EXT 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_EXT 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_EXT 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_EXT 0x80DF + +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 + +#define GL_LOGIC_OP GL_INDEX_LOGIC_OP +#define GL_TEXTURE_COMPONENTS GL_TEXTURE_INTERNAL_FORMAT + +#endif + +#if !defined(GL_ARB_multitexture) +#define GL_ARB_multitexture 1 + +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 + +#define glActiveTextureARB GlobalOpenGL().m_glActiveTextureARB +#define glClientActiveTextureARB GlobalOpenGL().m_glClientActiveTextureARB +#define glMultiTexCoord1dARB GlobalOpenGL().m_glMultiTexCoord1dARB +#define glMultiTexCoord1dvARB GlobalOpenGL().m_glMultiTexCoord1dvARB +#define glMultiTexCoord1fARB GlobalOpenGL().m_glMultiTexCoord1fARB +#define glMultiTexCoord1fvARB GlobalOpenGL().m_glMultiTexCoord1fvARB +#define glMultiTexCoord1iARB GlobalOpenGL().m_glMultiTexCoord1iARB +#define glMultiTexCoord1ivARB GlobalOpenGL().m_glMultiTexCoord1ivARB +#define glMultiTexCoord1sARB GlobalOpenGL().m_glMultiTexCoord1sARB +#define glMultiTexCoord1svARB GlobalOpenGL().m_glMultiTexCoord1svARB +#define glMultiTexCoord2dARB GlobalOpenGL().m_glMultiTexCoord2dARB +#define glMultiTexCoord2dvARB GlobalOpenGL().m_glMultiTexCoord2dvARB +#define glMultiTexCoord2fARB GlobalOpenGL().m_glMultiTexCoord2fARB +#define glMultiTexCoord2fvARB GlobalOpenGL().m_glMultiTexCoord2fvARB +#define glMultiTexCoord2iARB GlobalOpenGL().m_glMultiTexCoord2iARB +#define glMultiTexCoord2ivARB GlobalOpenGL().m_glMultiTexCoord2ivARB +#define glMultiTexCoord2sARB GlobalOpenGL().m_glMultiTexCoord2sARB +#define glMultiTexCoord2svARB GlobalOpenGL().m_glMultiTexCoord2svARB +#define glMultiTexCoord3dARB GlobalOpenGL().m_glMultiTexCoord3dARB +#define glMultiTexCoord3dvARB GlobalOpenGL().m_glMultiTexCoord3dvARB +#define glMultiTexCoord3fARB GlobalOpenGL().m_glMultiTexCoord3fARB +#define glMultiTexCoord3fvARB GlobalOpenGL().m_glMultiTexCoord3fvARB +#define glMultiTexCoord3iARB GlobalOpenGL().m_glMultiTexCoord3iARB +#define glMultiTexCoord3ivARB GlobalOpenGL().m_glMultiTexCoord3ivARB +#define glMultiTexCoord3sARB GlobalOpenGL().m_glMultiTexCoord3sARB +#define glMultiTexCoord3svARB GlobalOpenGL().m_glMultiTexCoord3svARB +#define glMultiTexCoord4dARB GlobalOpenGL().m_glMultiTexCoord4dARB +#define glMultiTexCoord4dvARB GlobalOpenGL().m_glMultiTexCoord4dvARB +#define glMultiTexCoord4fARB GlobalOpenGL().m_glMultiTexCoord4fARB +#define glMultiTexCoord4fvARB GlobalOpenGL().m_glMultiTexCoord4fvARB +#define glMultiTexCoord4iARB GlobalOpenGL().m_glMultiTexCoord4iARB +#define glMultiTexCoord4ivARB GlobalOpenGL().m_glMultiTexCoord4ivARB +#define glMultiTexCoord4sARB GlobalOpenGL().m_glMultiTexCoord4sARB +#define glMultiTexCoord4svARB GlobalOpenGL().m_glMultiTexCoord4svARB + +#endif + + +// EXT_texture_compression_s3tc +#if !defined(GL_EXT_texture_compression_s3tc) +#define GL_EXT_texture_compression_s3tc 1 + +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + +#endif + + +// ARB_texture_compression +#if !defined(GL_ARB_texture_compression) +#define GL_ARB_texture_compression 1 + +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 + +#define glCompressedTexImage3DARB GlobalOpenGL().m_glCompressedTexImage3DARB +#define glCompressedTexImage2DARB GlobalOpenGL().m_glCompressedTexImage2DARB +#define glCompressedTexImage1DARB GlobalOpenGL().m_glCompressedTexImage1DARB +#define glCompressedTexSubImage3DARB GlobalOpenGL().m_glCompressedTexSubImage3DARB +#define glCompressedTexSubImage2DARB GlobalOpenGL().m_glCompressedTexSubImage2DARB +#define glCompressedTexSubImage1DARB GlobalOpenGL().m_glCompressedTexSubImage1DARB +#define glGetCompressedTexImageARB GlobalOpenGL().m_glGetCompressedTexImageARB + +#endif + + +// GL 1.2 + +#if !defined(GL_VERSION_1_2) + +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_RESCALE_NORMAL 0x803A +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E + +#define glCopyTexSubImage3D GlobalOpenGL().m_glCopyTexSubImage3D +#define glDrawRangeElements GlobalOpenGL().m_glDrawRangeElements +#define glTexImage3D GlobalOpenGL().m_glTexImage3D +#define glTexSubImage3D GlobalOpenGL().m_glTexSubImage3D + +#endif + + +// GL 1.3 + +#if !defined(GL_VERSION_1_3) +#define GL_VERSION_1_3 1 + +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +#define GL_SUBTRACT 0x84E7 +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +#define GL_MULTISAMPLE_BIT 0x20000000 + +#define glActiveTexture GlobalOpenGL().m_glActiveTexture +#define glClientActiveTexture GlobalOpenGL().m_glClientActiveTexture +#define glCompressedTexImage1D GlobalOpenGL().m_glCompressedTexImage1D +#define glCompressedTexImage2D GlobalOpenGL().m_glCompressedTexImage2D +#define glCompressedTexImage3D GlobalOpenGL().m_glCompressedTexImage3D +#define glCompressedTexSubImage1D GlobalOpenGL().m_glCompressedTexSubImage1D +#define glCompressedTexSubImage2D GlobalOpenGL().m_glCompressedTexSubImage2D +#define glCompressedTexSubImage3D GlobalOpenGL().m_glCompressedTexSubImage3D +#define glGetCompressedTexImage GlobalOpenGL().m_glGetCompressedTexImage +#define glLoadTransposeMatrixd GlobalOpenGL().m_glLoadTransposeMatrixd +#define glLoadTransposeMatrixf GlobalOpenGL().m_glLoadTransposeMatrixf +#define glMultTransposeMatrixd GlobalOpenGL().m_glMultTransposeMatrixd +#define glMultTransposeMatrixf GlobalOpenGL().m_glMultTransposeMatrixf +#define glMultiTexCoord1d GlobalOpenGL().m_glMultiTexCoord1d +#define glMultiTexCoord1dv GlobalOpenGL().m_glMultiTexCoord1dv +#define glMultiTexCoord1f GlobalOpenGL().m_glMultiTexCoord1f +#define glMultiTexCoord1fv GlobalOpenGL().m_glMultiTexCoord1fv +#define glMultiTexCoord1i GlobalOpenGL().m_glMultiTexCoord1i +#define glMultiTexCoord1iv GlobalOpenGL().m_glMultiTexCoord1iv +#define glMultiTexCoord1s GlobalOpenGL().m_glMultiTexCoord1s +#define glMultiTexCoord1sv GlobalOpenGL().m_glMultiTexCoord1sv +#define glMultiTexCoord2d GlobalOpenGL().m_glMultiTexCoord2d +#define glMultiTexCoord2dv GlobalOpenGL().m_glMultiTexCoord2dv +#define glMultiTexCoord2f GlobalOpenGL().m_glMultiTexCoord2f +#define glMultiTexCoord2fv GlobalOpenGL().m_glMultiTexCoord2fv +#define glMultiTexCoord2i GlobalOpenGL().m_glMultiTexCoord2i +#define glMultiTexCoord2iv GlobalOpenGL().m_glMultiTexCoord2iv +#define glMultiTexCoord2s GlobalOpenGL().m_glMultiTexCoord2s +#define glMultiTexCoord2sv GlobalOpenGL().m_glMultiTexCoord2sv +#define glMultiTexCoord3d GlobalOpenGL().m_glMultiTexCoord3d +#define glMultiTexCoord3dv GlobalOpenGL().m_glMultiTexCoord3dv +#define glMultiTexCoord3f GlobalOpenGL().m_glMultiTexCoord3f +#define glMultiTexCoord3fv GlobalOpenGL().m_glMultiTexCoord3fv +#define glMultiTexCoord3i GlobalOpenGL().m_glMultiTexCoord3i +#define glMultiTexCoord3iv GlobalOpenGL().m_glMultiTexCoord3iv +#define glMultiTexCoord3s GlobalOpenGL().m_glMultiTexCoord3s +#define glMultiTexCoord3sv GlobalOpenGL().m_glMultiTexCoord3sv +#define glMultiTexCoord4d GlobalOpenGL().m_glMultiTexCoord4d +#define glMultiTexCoord4dv GlobalOpenGL().m_glMultiTexCoord4dv +#define glMultiTexCoord4f GlobalOpenGL().m_glMultiTexCoord4f +#define glMultiTexCoord4fv GlobalOpenGL().m_glMultiTexCoord4fv +#define glMultiTexCoord4i GlobalOpenGL().m_glMultiTexCoord4i +#define glMultiTexCoord4iv GlobalOpenGL().m_glMultiTexCoord4iv +#define glMultiTexCoord4s GlobalOpenGL().m_glMultiTexCoord4s +#define glMultiTexCoord4sv GlobalOpenGL().m_glMultiTexCoord4sv +#define glSampleCoverage GlobalOpenGL().m_glSampleCoverage + +#endif + + +// GL 1.4 +#if !defined(GL_VERSION_1_4) +#define GL_VERSION_1_4 1 + +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_SIZE_MIN 0x8126 +#define GL_POINT_SIZE_MAX 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION 0x8129 +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_FOG_COORDINATE_SOURCE 0x8450 +#define GL_FOG_COORDINATE 0x8451 +#define GL_FRAGMENT_DEPTH 0x8452 +#define GL_CURRENT_FOG_COORDINATE 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 +#define GL_FOG_COORDINATE_ARRAY 0x8457 +#define GL_COLOR_SUM 0x8458 +#define GL_CURRENT_SECONDARY_COLOR 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D +#define GL_SECONDARY_COLOR_ARRAY 0x845E +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#define GL_TEXTURE_FILTER_CONTROL 0x8500 +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_DEPTH_TEXTURE_MODE 0x884B +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_COMPARE_R_TO_TEXTURE 0x884E + +#define glBlendColor GlobalOpenGL().m_glBlendColor +#define glBlendEquation GlobalOpenGL().m_glBlendEquation +#define glBlendFuncSeparate GlobalOpenGL().m_glBlendFuncSeparate +#define glFogCoordPointer GlobalOpenGL().m_glFogCoordPointer +#define glFogCoordd GlobalOpenGL().m_glFogCoordd +#define glFogCoorddv GlobalOpenGL().m_glFogCoorddv +#define glFogCoordf GlobalOpenGL().m_glFogCoordf +#define glFogCoordfv GlobalOpenGL().m_glFogCoordfv +#define glMultiDrawArrays GlobalOpenGL().m_glMultiDrawArrays +#define glMultiDrawElements GlobalOpenGL().m_glMultiDrawElements +#define glPointParameterf GlobalOpenGL().m_glPointParameterf +#define glPointParameterfv GlobalOpenGL().m_glPointParameterfv +#define glSecondaryColor3b GlobalOpenGL().m_glSecondaryColor3b +#define glSecondaryColor3bv GlobalOpenGL().m_glSecondaryColor3bv +#define glSecondaryColor3d GlobalOpenGL().m_glSecondaryColor3d +#define glSecondaryColor3dv GlobalOpenGL().m_glSecondaryColor3dv +#define glSecondaryColor3f GlobalOpenGL().m_glSecondaryColor3f +#define glSecondaryColor3fv GlobalOpenGL().m_glSecondaryColor3fv +#define glSecondaryColor3i GlobalOpenGL().m_glSecondaryColor3i +#define glSecondaryColor3iv GlobalOpenGL().m_glSecondaryColor3iv +#define glSecondaryColor3s GlobalOpenGL().m_glSecondaryColor3s +#define glSecondaryColor3sv GlobalOpenGL().m_glSecondaryColor3sv +#define glSecondaryColor3ub GlobalOpenGL().m_glSecondaryColor3ub +#define glSecondaryColor3ubv GlobalOpenGL().m_glSecondaryColor3ubv +#define glSecondaryColor3ui GlobalOpenGL().m_glSecondaryColor3ui +#define glSecondaryColor3uiv GlobalOpenGL().m_glSecondaryColor3uiv +#define glSecondaryColor3us GlobalOpenGL().m_glSecondaryColor3us +#define glSecondaryColor3usv GlobalOpenGL().m_glSecondaryColor3usv +#define glSecondaryColorPointer GlobalOpenGL().m_glSecondaryColorPointer +#define glWindowPos2d GlobalOpenGL().m_glWindowPos2d +#define glWindowPos2dv GlobalOpenGL().m_glWindowPos2dv +#define glWindowPos2f GlobalOpenGL().m_glWindowPos2f +#define glWindowPos2fv GlobalOpenGL().m_glWindowPos2fv +#define glWindowPos2i GlobalOpenGL().m_glWindowPos2i +#define glWindowPos2iv GlobalOpenGL().m_glWindowPos2iv +#define glWindowPos2s GlobalOpenGL().m_glWindowPos2s +#define glWindowPos2sv GlobalOpenGL().m_glWindowPos2sv +#define glWindowPos3d GlobalOpenGL().m_glWindowPos3d +#define glWindowPos3dv GlobalOpenGL().m_glWindowPos3dv +#define glWindowPos3f GlobalOpenGL().m_glWindowPos3f +#define glWindowPos3fv GlobalOpenGL().m_glWindowPos3fv +#define glWindowPos3i GlobalOpenGL().m_glWindowPos3i +#define glWindowPos3iv GlobalOpenGL().m_glWindowPos3iv +#define glWindowPos3s GlobalOpenGL().m_glWindowPos3s +#define glWindowPos3sv GlobalOpenGL().m_glWindowPos3sv + +#endif + + +// GL 1.5 +#if !defined(GL_VERSION_1_5) +#define GL_VERSION_1_5 1 + +#define GL_FOG_COORD GL_FOG_COORDINATE +#define GL_FOG_COORD_ARRAY GL_FOG_COORDINATE_ARRAY +#define GL_SRC0_RGB GL_SOURCE0_RGB +#define GL_FOG_COORD_ARRAY_POINTER GL_FOG_COORDINATE_ARRAY_POINTER +#define GL_FOG_COORD_SOURCE GL_FOG_COORDINATE_SOURCE +#define GL_FOG_COORD_ARRAY_TYPE GL_FOG_COORDINATE_ARRAY_TYPE +#define GL_SRC1_ALPHA GL_SOURCE1_ALPHA +#define GL_CURRENT_FOG_COORD GL_CURRENT_FOG_COORDINATE +#define GL_FOG_COORD_ARRAY_STRIDE GL_FOG_COORDINATE_ARRAY_STRIDE +#define GL_SRC0_ALPHA GL_SOURCE0_ALPHA +#define GL_SRC1_RGB GL_SOURCE1_RGB +#define GL_FOG_COORD_ARRAY_BUFFER_BINDING GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING +#define GL_SRC2_ALPHA GL_SOURCE2_ALPHA +#define GL_SRC2_RGB GL_SOURCE2_RGB +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 + +typedef std::ptrdiff_t GLsizeiptr; +typedef std::ptrdiff_t GLintptr; + +#define glBeginQuery GlobalOpenGL().m_glBeginQuery +#define glBindBuffer GlobalOpenGL().m_glBindBuffer +#define glBufferData GlobalOpenGL().m_glBufferData +#define glBufferSubData GlobalOpenGL().m_glBufferSubData +#define glDeleteBuffers GlobalOpenGL().m_glDeleteBuffers +#define glDeleteQueries GlobalOpenGL().m_glDeleteQueries +#define glEndQuery GlobalOpenGL().m_glEndQuery +#define glGenBuffers GlobalOpenGL().m_glGenBuffers +#define glGenQueries GlobalOpenGL().m_glGenQueries +#define glGetBufferParameteriv GlobalOpenGL().m_glGetBufferParameteriv +#define glGetBufferPointerv GlobalOpenGL().m_glGetBufferPointerv +#define glGetBufferSubData GlobalOpenGL().m_glGetBufferSubData +#define glGetQueryObjectiv GlobalOpenGL().m_glGetQueryObjectiv +#define glGetQueryObjectuiv GlobalOpenGL().m_glGetQueryObjectuiv +#define glGetQueryiv GlobalOpenGL().m_glGetQueryiv +#define glIsBuffer GlobalOpenGL().m_glIsBuffer +#define glIsQuery GlobalOpenGL().m_glIsQuery +#define glMapBuffer GlobalOpenGL().m_glMapBuffer +#define glUnmapBuffer GlobalOpenGL().m_glUnmapBuffer + +#endif + + +// GL_ARB_vertex_program +#if !defined(GL_ARB_vertex_program) +#define GL_ARB_vertex_program + +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF + +#define glVertexAttrib1sARB GlobalOpenGL().m_glVertexAttrib1sARB +#define glVertexAttrib1fARB GlobalOpenGL().m_glVertexAttrib1fARB +#define glVertexAttrib1dARB GlobalOpenGL().m_glVertexAttrib1dARB +#define glVertexAttrib2sARB GlobalOpenGL().m_glVertexAttrib2sARB +#define glVertexAttrib2fARB GlobalOpenGL().m_glVertexAttrib2fARB +#define glVertexAttrib2dARB GlobalOpenGL().m_glVertexAttrib2dARB +#define glVertexAttrib3sARB GlobalOpenGL().m_glVertexAttrib3sARB +#define glVertexAttrib3fARB GlobalOpenGL().m_glVertexAttrib3fARB +#define glVertexAttrib3dARB GlobalOpenGL().m_glVertexAttrib3dARB +#define glVertexAttrib4sARB GlobalOpenGL().m_glVertexAttrib4sARB +#define glVertexAttrib4fARB GlobalOpenGL().m_glVertexAttrib4fARB +#define glVertexAttrib4dARB GlobalOpenGL().m_glVertexAttrib4dARB +#define glVertexAttrib4NubARB GlobalOpenGL().m_glVertexAttrib4NubARB +#define glVertexAttrib1svARB GlobalOpenGL().m_glVertexAttrib1svARB +#define glVertexAttrib1fvARB GlobalOpenGL().m_glVertexAttrib1fvARB +#define glVertexAttrib1dvARB GlobalOpenGL().m_glVertexAttrib1dvARB +#define glVertexAttrib2svARB GlobalOpenGL().m_glVertexAttrib2svARB +#define glVertexAttrib2fvARB GlobalOpenGL().m_glVertexAttrib2fvARB +#define glVertexAttrib2dvARB GlobalOpenGL().m_glVertexAttrib2dvARB +#define glVertexAttrib3svARB GlobalOpenGL().m_glVertexAttrib3svARB +#define glVertexAttrib3fvARB GlobalOpenGL().m_glVertexAttrib3fvARB +#define glVertexAttrib3dvARB GlobalOpenGL().m_glVertexAttrib3dvARB +#define glVertexAttrib4bvARB GlobalOpenGL().m_glVertexAttrib4bvARB +#define glVertexAttrib4svARB GlobalOpenGL().m_glVertexAttrib4svARB +#define glVertexAttrib4ivARB GlobalOpenGL().m_glVertexAttrib4ivARB +#define glVertexAttrib4ubvARB GlobalOpenGL().m_glVertexAttrib4ubvARB +#define glVertexAttrib4usvARB GlobalOpenGL().m_glVertexAttrib4usvARB +#define glVertexAttrib4uivARB GlobalOpenGL().m_glVertexAttrib4uivARB +#define glVertexAttrib4fvARB GlobalOpenGL().m_glVertexAttrib4fvARB +#define glVertexAttrib4dvARB GlobalOpenGL().m_glVertexAttrib4dvARB +#define glVertexAttrib4NbvARB GlobalOpenGL().m_glVertexAttrib4NbvARB +#define glVertexAttrib4NsvARB GlobalOpenGL().m_glVertexAttrib4NsvARB +#define glVertexAttrib4NivARB GlobalOpenGL().m_glVertexAttrib4NivARB +#define glVertexAttrib4NubvARB GlobalOpenGL().m_glVertexAttrib4NubvARB +#define glVertexAttrib4NusvARB GlobalOpenGL().m_glVertexAttrib4NusvARB +#define glVertexAttrib4NuivARB GlobalOpenGL().m_glVertexAttrib4NuivARB +#define glVertexAttribPointerARB GlobalOpenGL().m_glVertexAttribPointerARB +#define glEnableVertexAttribArrayARB GlobalOpenGL().m_glEnableVertexAttribArrayARB +#define glDisableVertexAttribArrayARB GlobalOpenGL().m_glDisableVertexAttribArrayARB +#define glProgramStringARB GlobalOpenGL().m_glProgramStringARB +#define glBindProgramARB GlobalOpenGL().m_glBindProgramARB +#define glDeleteProgramsARB GlobalOpenGL().m_glDeleteProgramsARB +#define glGenProgramsARB GlobalOpenGL().m_glGenProgramsARB +#define glProgramEnvParameter4dARB GlobalOpenGL().m_glProgramEnvParameter4dARB +#define glProgramEnvParameter4dvARB GlobalOpenGL().m_glProgramEnvParameter4dvARB +#define glProgramEnvParameter4fARB GlobalOpenGL().m_glProgramEnvParameter4fARB +#define glProgramEnvParameter4fvARB GlobalOpenGL().m_glProgramEnvParameter4fvARB +#define glProgramLocalParameter4dARB GlobalOpenGL().m_glProgramLocalParameter4dARB +#define glProgramLocalParameter4dvARB GlobalOpenGL().m_glProgramLocalParameter4dvARB +#define glProgramLocalParameter4fARB GlobalOpenGL().m_glProgramLocalParameter4fARB +#define glProgramLocalParameter4fvARB GlobalOpenGL().m_glProgramLocalParameter4fvARB +#define glGetProgramEnvParameterdvARB GlobalOpenGL().m_glGetProgramEnvParameterdvARB +#define glGetProgramEnvParameterfvARB GlobalOpenGL().m_glGetProgramEnvParameterfvARB +#define glGetProgramLocalParameterdvARB GlobalOpenGL().m_glGetProgramLocalParameterdvARB +#define glGetProgramLocalParameterfvARB GlobalOpenGL().m_glGetProgramLocalParameterfvARB +#define glGetProgramivARB GlobalOpenGL().m_glGetProgramivARB +#define glGetProgramStringARB GlobalOpenGL().m_glGetProgramStringARB +#define glGetVertexAttribdvARB GlobalOpenGL().m_glGetVertexAttribdvARB +#define glGetVertexAttribfvARB GlobalOpenGL().m_glGetVertexAttribfvARB +#define glGetVertexAttribivARB GlobalOpenGL().m_glGetVertexAttribivARB +#define glGetVertexAttribPointervARB GlobalOpenGL().m_glGetVertexAttribPointervARB +#define glIsProgramARB GlobalOpenGL().m_glIsProgramARB + +#endif + + +// GL_ARB_fragment_program +#if !defined(GL_ARB_fragment_program) +#define GL_ARB_fragment_program 1 + +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 + +#endif + + +// GL_ARB_shader_objects +#if !defined(GL_ARB_shader_objects) +#define GL_ARB_shader_objects 1 + +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_SAMPLER_1D_ARB 0x8B5D +#define GL_SAMPLER_2D_ARB 0x8B5E +#define GL_SAMPLER_3D_ARB 0x8B5F +#define GL_SAMPLER_CUBE_ARB 0x8B60 +#define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 +#define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 + +#define glDeleteObjectARB GlobalOpenGL().m_glDeleteObjectARB +#define glGetHandleARB GlobalOpenGL().m_glGetHandleARB +#define glDetachObjectARB GlobalOpenGL().m_glDetachObjectARB +#define glCreateShaderObjectARB GlobalOpenGL().m_glCreateShaderObjectARB +#define glShaderSourceARB GlobalOpenGL().m_glShaderSourceARB +#define glCompileShaderARB GlobalOpenGL().m_glCompileShaderARB +#define glCreateProgramObjectARB GlobalOpenGL().m_glCreateProgramObjectARB +#define glAttachObjectARB GlobalOpenGL().m_glAttachObjectARB +#define glLinkProgramARB GlobalOpenGL().m_glLinkProgramARB +#define glUseProgramObjectARB GlobalOpenGL().m_glUseProgramObjectARB +#define glValidateProgramARB GlobalOpenGL().m_glValidateProgramARB +#define glUniform1fARB GlobalOpenGL().m_glUniform1fARB +#define glUniform2fARB GlobalOpenGL().m_glUniform2fARB +#define glUniform3fARB GlobalOpenGL().m_glUniform3fARB +#define glUniform4fARB GlobalOpenGL().m_glUniform4fARB +#define glUniform1iARB GlobalOpenGL().m_glUniform1iARB +#define glUniform2iARB GlobalOpenGL().m_glUniform2iARB +#define glUniform3iARB GlobalOpenGL().m_glUniform3iARB +#define glUniform4iARB GlobalOpenGL().m_glUniform4iARB +#define glUniform1fvARB GlobalOpenGL().m_glUniform1fvARB +#define glUniform2fvARB GlobalOpenGL().m_glUniform2fvARB +#define glUniform3fvARB GlobalOpenGL().m_glUniform3fvARB +#define glUniform4fvARB GlobalOpenGL().m_glUniform4fvARB +#define glUniform1ivARB GlobalOpenGL().m_glUniform1ivARB +#define glUniform2ivARB GlobalOpenGL().m_glUniform2ivARB +#define glUniform3ivARB GlobalOpenGL().m_glUniform3ivARB +#define glUniform4ivARB GlobalOpenGL().m_glUniform4ivARB +#define glUniformMatrix2fvARB GlobalOpenGL().m_glUniformMatrix2fvARB +#define glUniformMatrix3fvARB GlobalOpenGL().m_glUniformMatrix3fvARB +#define glUniformMatrix4fvARB GlobalOpenGL().m_glUniformMatrix4fvARB +#define glGetObjectParameterfvARB GlobalOpenGL().m_glGetObjectParameterfvARB +#define glGetObjectParameterivARB GlobalOpenGL().m_glGetObjectParameterivARB +#define glGetInfoLogARB GlobalOpenGL().m_glGetInfoLogARB +#define glGetAttachedObjectsARB GlobalOpenGL().m_glGetAttachedObjectsARB +#define glGetUniformLocationARB GlobalOpenGL().m_glGetUniformLocationARB +#define glGetActiveUniformARB GlobalOpenGL().m_glGetActiveUniformARB +#define glGetUniformfvARB GlobalOpenGL().m_glGetUniformfvARB +#define glGetUniformivARB GlobalOpenGL().m_glGetUniformivARB +#define glGetShaderSourceARB GlobalOpenGL().m_glGetShaderSourceARB + +typedef char GLcharARB; +typedef unsigned int GLhandleARB; + +#endif + +// GL_ARB_vertex_shader +#if !defined(GL_ARB_vertex_shader) +#define GL_ARB_vertex_shader 1 + +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A + +#if 0 +#define glVertexAttrib1fARB GlobalOpenGL().m_glVertexAttrib1fARB +#define glVertexAttrib1sARB GlobalOpenGL().m_glVertexAttrib1sARB +#define glVertexAttrib1dARB GlobalOpenGL().m_glVertexAttrib1dARB +#define glVertexAttrib2fARB GlobalOpenGL().m_glVertexAttrib2fARB +#define glVertexAttrib2sARB GlobalOpenGL().m_glVertexAttrib2sARB +#define glVertexAttrib2dARB GlobalOpenGL().m_glVertexAttrib2dARB +#define glVertexAttrib3fARB GlobalOpenGL().m_glVertexAttrib3fARB +#define glVertexAttrib3sARB GlobalOpenGL().m_glVertexAttrib3sARB +#define glVertexAttrib3dARB GlobalOpenGL().m_glVertexAttrib3dARB +#define glVertexAttrib4fARB GlobalOpenGL().m_glVertexAttrib4fARB +#define glVertexAttrib4sARB GlobalOpenGL().m_glVertexAttrib4sARB +#define glVertexAttrib4dARB GlobalOpenGL().m_glVertexAttrib4dARB +#define glVertexAttrib4NubARB GlobalOpenGL().m_glVertexAttrib4NubARB +#define glVertexAttrib1fvARB GlobalOpenGL().m_glVertexAttrib1fvARB +#define glVertexAttrib1svARB GlobalOpenGL().m_glVertexAttrib1svARB +#define glVertexAttrib1dvARB GlobalOpenGL().m_glVertexAttrib1dvARB +#define glVertexAttrib2fvARB GlobalOpenGL().m_glVertexAttrib2fvARB +#define glVertexAttrib2svARB GlobalOpenGL().m_glVertexAttrib2svARB +#define glVertexAttrib2dvARB GlobalOpenGL().m_glVertexAttrib2dvARB +#define glVertexAttrib3fvARB GlobalOpenGL().m_glVertexAttrib3fvARB +#define glVertexAttrib3svARB GlobalOpenGL().m_glVertexAttrib3svARB +#define glVertexAttrib3dvARB GlobalOpenGL().m_glVertexAttrib3dvARB +#define glVertexAttrib4fvARB GlobalOpenGL().m_glVertexAttrib4fvARB +#define glVertexAttrib4svARB GlobalOpenGL().m_glVertexAttrib4svARB +#define glVertexAttrib4dvARB GlobalOpenGL().m_glVertexAttrib4dvARB +#define glVertexAttrib4ivARB GlobalOpenGL().m_glVertexAttrib4ivARB +#define glVertexAttrib4bvARB GlobalOpenGL().m_glVertexAttrib4bvARB +#define glVertexAttrib4ubvARB GlobalOpenGL().m_glVertexAttrib4ubvARB +#define glVertexAttrib4usvARB GlobalOpenGL().m_glVertexAttrib4usvARB +#define glVertexAttrib4uivARB GlobalOpenGL().m_glVertexAttrib4uivARB +#define glVertexAttrib4NbvARB GlobalOpenGL().m_glVertexAttrib4NbvARB +#define glVertexAttrib4NsvARB GlobalOpenGL().m_glVertexAttrib4NsvARB +#define glVertexAttrib4NivARB GlobalOpenGL().m_glVertexAttrib4NivARB +#define glVertexAttrib4NubvARB GlobalOpenGL().m_glVertexAttrib4NubvARB +#define glVertexAttrib4NusvARB GlobalOpenGL().m_glVertexAttrib4NusvARB +#define glVertexAttrib4NuivARB GlobalOpenGL().m_glVertexAttrib4NuivARB +#define glVertexAttribPointerARB GlobalOpenGL().m_glVertexAttribPointerARB +#define glEnableVertexAttribArrayARB GlobalOpenGL().m_glEnableVertexAttribArrayARB +#define glDisableVertexAttribArrayARB GlobalOpenGL().m_glDisableVertexAttribArrayARB +#endif +#define glBindAttribLocationARB GlobalOpenGL().m_glBindAttribLocationARB +#define glGetActiveAttribARB GlobalOpenGL().m_glGetActiveAttribARB +#define glGetAttribLocationARB GlobalOpenGL().m_glGetAttribLocationARB +#if 0 +#define glGetVertexAttribdvARB GlobalOpenGL().m_glGetVertexAttribdvARB +#define glGetVertexAttribfvARB GlobalOpenGL().m_glGetVertexAttribfvARB +#define glGetVertexAttribivARB GlobalOpenGL().m_glGetVertexAttribivARB +#define glGetVertexAttribPointervARB GlobalOpenGL().m_glGetVertexAttribPointervARB +#endif +#endif + + + +// GL_ARB_fragment_shader +#if !defined(GL_ARB_fragment_shader) +#define GL_ARB_fragment_shader 1 + +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B + +#endif + + +// GL_ARB_shading_language_100 +#if !defined(GL_ARB_shading_language_100) +#define GL_ARB_shading_language_100 1 + +#define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C + +#endif + + +// GL_NV_vertex_program2 +#if !defined(GL_NV_vertex_program) +#define GL_NV_vertex_program 1 + +#define GL_VERTEX_PROGRAM_NV 0x8620 +#define GL_VERTEX_STATE_PROGRAM_NV 0x8621 +#define GL_ATTRIB_ARRAY_SIZE_NV 0x8623 +#define GL_ATTRIB_ARRAY_STRIDE_NV 0x8624 +#define GL_ATTRIB_ARRAY_TYPE_NV 0x8625 +#define GL_CURRENT_ATTRIB_NV 0x8626 +#define GL_PROGRAM_LENGTH_NV 0x8627 +#define GL_PROGRAM_STRING_NV 0x8628 +#define GL_MODELVIEW_PROJECTION_NV 0x8629 +#define GL_IDENTITY_NV 0x862A +#define GL_INVERSE_NV 0x862B +#define GL_TRANSPOSE_NV 0x862C +#define GL_INVERSE_TRANSPOSE_NV 0x862D +#define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E +#define GL_MAX_TRACK_MATRICES_NV 0x862F +#define GL_MATRIX0_NV 0x8630 +#define GL_MATRIX1_NV 0x8631 +#define GL_MATRIX2_NV 0x8632 +#define GL_MATRIX3_NV 0x8633 +#define GL_MATRIX4_NV 0x8634 +#define GL_MATRIX5_NV 0x8635 +#define GL_MATRIX6_NV 0x8636 +#define GL_MATRIX7_NV 0x8637 +#define GL_CURRENT_MATRIX_STACK_DEPTH_NV 0x8640 +#define GL_CURRENT_MATRIX_NV 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_NV 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_NV 0x8643 +#define GL_PROGRAM_PARAMETER_NV 0x8644 +#define GL_ATTRIB_ARRAY_POINTER_NV 0x8645 +#define GL_PROGRAM_TARGET_NV 0x8646 +#define GL_PROGRAM_RESIDENT_NV 0x8647 +#define GL_TRACK_MATRIX_NV 0x8648 +#define GL_TRACK_MATRIX_TRANSFORM_NV 0x8649 +#define GL_VERTEX_PROGRAM_BINDING_NV 0x864A +#define GL_PROGRAM_ERROR_POSITION_NV 0x864B +#define GL_VERTEX_ATTRIB_ARRAY0_NV 0x8650 +#define GL_VERTEX_ATTRIB_ARRAY1_NV 0x8651 +#define GL_VERTEX_ATTRIB_ARRAY2_NV 0x8652 +#define GL_VERTEX_ATTRIB_ARRAY3_NV 0x8653 +#define GL_VERTEX_ATTRIB_ARRAY4_NV 0x8654 +#define GL_VERTEX_ATTRIB_ARRAY5_NV 0x8655 +#define GL_VERTEX_ATTRIB_ARRAY6_NV 0x8656 +#define GL_VERTEX_ATTRIB_ARRAY7_NV 0x8657 +#define GL_VERTEX_ATTRIB_ARRAY8_NV 0x8658 +#define GL_VERTEX_ATTRIB_ARRAY9_NV 0x8659 +#define GL_VERTEX_ATTRIB_ARRAY10_NV 0x865A +#define GL_VERTEX_ATTRIB_ARRAY11_NV 0x865B +#define GL_VERTEX_ATTRIB_ARRAY12_NV 0x865C +#define GL_VERTEX_ATTRIB_ARRAY13_NV 0x865D +#define GL_VERTEX_ATTRIB_ARRAY14_NV 0x865E +#define GL_VERTEX_ATTRIB_ARRAY15_NV 0x865F +#define GL_MAP1_VERTEX_ATTRIB0_4_NV 0x8660 +#define GL_MAP1_VERTEX_ATTRIB1_4_NV 0x8661 +#define GL_MAP1_VERTEX_ATTRIB2_4_NV 0x8662 +#define GL_MAP1_VERTEX_ATTRIB3_4_NV 0x8663 +#define GL_MAP1_VERTEX_ATTRIB4_4_NV 0x8664 +#define GL_MAP1_VERTEX_ATTRIB5_4_NV 0x8665 +#define GL_MAP1_VERTEX_ATTRIB6_4_NV 0x8666 +#define GL_MAP1_VERTEX_ATTRIB7_4_NV 0x8667 +#define GL_MAP1_VERTEX_ATTRIB8_4_NV 0x8668 +#define GL_MAP1_VERTEX_ATTRIB9_4_NV 0x8669 +#define GL_MAP1_VERTEX_ATTRIB10_4_NV 0x866A +#define GL_MAP1_VERTEX_ATTRIB11_4_NV 0x866B +#define GL_MAP1_VERTEX_ATTRIB12_4_NV 0x866C +#define GL_MAP1_VERTEX_ATTRIB13_4_NV 0x866D +#define GL_MAP1_VERTEX_ATTRIB14_4_NV 0x866E +#define GL_MAP1_VERTEX_ATTRIB15_4_NV 0x866F +#define GL_MAP2_VERTEX_ATTRIB0_4_NV 0x8670 +#define GL_MAP2_VERTEX_ATTRIB1_4_NV 0x8671 +#define GL_MAP2_VERTEX_ATTRIB2_4_NV 0x8672 +#define GL_MAP2_VERTEX_ATTRIB3_4_NV 0x8673 +#define GL_MAP2_VERTEX_ATTRIB4_4_NV 0x8674 +#define GL_MAP2_VERTEX_ATTRIB5_4_NV 0x8675 +#define GL_MAP2_VERTEX_ATTRIB6_4_NV 0x8676 +#define GL_MAP2_VERTEX_ATTRIB7_4_NV 0x8677 +#define GL_MAP2_VERTEX_ATTRIB8_4_NV 0x8678 +#define GL_MAP2_VERTEX_ATTRIB9_4_NV 0x8679 +#define GL_MAP2_VERTEX_ATTRIB10_4_NV 0x867A +#define GL_MAP2_VERTEX_ATTRIB11_4_NV 0x867B +#define GL_MAP2_VERTEX_ATTRIB12_4_NV 0x867C +#define GL_MAP2_VERTEX_ATTRIB13_4_NV 0x867D +#define GL_MAP2_VERTEX_ATTRIB14_4_NV 0x867E +#define GL_MAP2_VERTEX_ATTRIB15_4_NV 0x867F + +#define glAreProgramsResidentNV GlobalOpenGL().m_glAreProgramsResidentNV +#define glBindProgramNV GlobalOpenGL().m_glBindProgramNV +#define glDeleteProgramsNV GlobalOpenGL().m_glDeleteProgramsNV +#define glExecuteProgramNV GlobalOpenGL().m_glExecuteProgramNV +#define glGenProgramsNV GlobalOpenGL().m_glGenProgramsNV +#define glGetProgramParameterdvNV GlobalOpenGL().m_glGetProgramParameterdvNV +#define glGetProgramParameterfvNV GlobalOpenGL().m_glGetProgramParameterfvNV +#define glGetProgramivNV GlobalOpenGL().m_glGetProgramivNV +#define glGetProgramStringNV GlobalOpenGL().m_glGetProgramStringNV +#define glGetTrackMatrixivNV GlobalOpenGL().m_glGetTrackMatrixivNV +#define glGetVertexAttribdvNV GlobalOpenGL().m_glGetVertexAttribdvNV +#define glGetVertexAttribfvNV GlobalOpenGL().m_glGetVertexAttribfvNV +#define glGetVertexAttribivNV GlobalOpenGL().m_glGetVertexAttribivNV +#define glGetVertexAttribPointervNV GlobalOpenGL().m_glGetVertexAttribPointervNV +#define glIsProgramNV GlobalOpenGL().m_glIsProgramNV +#define glLoadProgramNV GlobalOpenGL().m_glLoadProgramNV +#define glProgramParameter4fNV GlobalOpenGL().m_glProgramParameter4fNV +#define glProgramParameter4fvNV GlobalOpenGL().m_glProgramParameter4fvNV +#define glProgramParameters4fvNV GlobalOpenGL().m_glProgramParameters4fvNV +#define glRequestResidentProgramsNV GlobalOpenGL().m_glRequestResidentProgramsNV +#define glTrackMatrixNV GlobalOpenGL().m_glTrackMatrixNV +#define glVertexAttribPointerNV GlobalOpenGL().m_glVertexAttribPointerNV +#define glVertexAttrib1fNV GlobalOpenGL().m_glVertexAttrib1fNV +#define glVertexAttrib1fvNV GlobalOpenGL().m_glVertexAttrib1fvNV +#define glVertexAttrib2fNV GlobalOpenGL().m_glVertexAttrib2fNV +#define glVertexAttrib2fvNV GlobalOpenGL().m_glVertexAttrib2fvNV +#define glVertexAttrib3fNV GlobalOpenGL().m_glVertexAttrib3fNV +#define glVertexAttrib3fvNV GlobalOpenGL().m_glVertexAttrib3fvNV +#define glVertexAttrib4fNV GlobalOpenGL().m_glVertexAttrib4fNV +#define glVertexAttrib4fvNV GlobalOpenGL().m_glVertexAttrib4fvNV +#define glVertexAttribs1fvNV GlobalOpenGL().m_glVertexAttribs1fvNV +#define glVertexAttribs2fvNV GlobalOpenGL().m_glVertexAttribs2fvNV +#define glVertexAttribs3fvNV GlobalOpenGL().m_glVertexAttribs3fvNV +#define glVertexAttribs4fvNV GlobalOpenGL().m_glVertexAttribs4fvNV + +#endif + + +// GL_NV_fragment_program +#if !defined(GL_NV_fragment_program) + +#define GL_NV_fragment_program 1 + +#define GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV 0x8868 +#define GL_FRAGMENT_PROGRAM_NV 0x8870 +#define GL_MAX_TEXTURE_COORDS_NV 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_NV 0x8872 +#define GL_FRAGMENT_PROGRAM_BINDING_NV 0x8873 +#define GL_PROGRAM_ERROR_STRING_NV 0x8874 + +#define glProgramNamedParameter4fNV GlobalOpenGL().m_glProgramNamedParameter4fNV +#define glProgramNamedParameter4fvNV GlobalOpenGL().m_glProgramNamedParameter4fvNV +#define glGetProgramNamedParameterfvNV GlobalOpenGL().m_glGetProgramNamedParameterfvNV + +#endif + + +/// \brief A module which wraps a runtime-binding of the standard OpenGL functions. +/// Provides convenience functions for querying availabiliy of extensions, rendering text and error-checking. +struct OpenGLBinding +{ + INTEGER_CONSTANT(Version, 2); + STRING_CONSTANT(Name, "qgl"); + + /// \brief OpenGL version, extracted from the GL_VERSION string. + int major_version, minor_version; + + /// \brief Is true if the global shared OpenGL context is valid. + bool contextValid; + + OpenGLBinding() : contextValid(false) + { + } + + /// \brief Asserts that there no OpenGL errors have occurred since the last call to glGetError. + void (*assertNoErrors)(); + + GLuint m_font; + int m_fontHeight; + + /// \brief Renders \p string at the current raster-position of the current context. + void drawString(const char* string) const + { + m_glListBase(m_font); + m_glCallLists(GLsizei(strlen(string)), GL_UNSIGNED_BYTE, reinterpret_cast(string)); + } + + /// \brief Renders \p character at the current raster-position of the current context. + void drawChar(char character) const + { + m_glListBase(m_font); + m_glCallLists(1, GL_UNSIGNED_BYTE, reinterpret_cast(&character)); + } + + + // GL 1.1 + void (QGL_DLLEXPORT *m_glAccum)(GLenum op, GLfloat value); + void (QGL_DLLEXPORT *m_glAlphaFunc)(GLenum func, GLclampf ref); + GLboolean (QGL_DLLEXPORT *m_glAreTexturesResident)(GLsizei n, const GLuint *textures, GLboolean *residences); + void (QGL_DLLEXPORT *m_glArrayElement)(GLint i); + void (QGL_DLLEXPORT *m_glBegin)(GLenum mode); + void (QGL_DLLEXPORT *m_glBindTexture)(GLenum target, GLuint texture); + void (QGL_DLLEXPORT *m_glBitmap)(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); + void (QGL_DLLEXPORT *m_glBlendFunc)(GLenum sfactor, GLenum dfactor); + void (QGL_DLLEXPORT *m_glCallList)(GLuint list); + void (QGL_DLLEXPORT *m_glCallLists)(GLsizei n, GLenum type, const GLvoid *lists); + void (QGL_DLLEXPORT *m_glClear)(GLbitfield mask); + void (QGL_DLLEXPORT *m_glClearAccum)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); + void (QGL_DLLEXPORT *m_glClearColor)(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); + void (QGL_DLLEXPORT *m_glClearDepth)(GLclampd depth); + void (QGL_DLLEXPORT *m_glClearIndex)(GLfloat c); + void (QGL_DLLEXPORT *m_glClearStencil)(GLint s); + void (QGL_DLLEXPORT *m_glClipPlane)(GLenum plane, const GLdouble *equation); + void (QGL_DLLEXPORT *m_glColor3b)(GLbyte red, GLbyte green, GLbyte blue); + void (QGL_DLLEXPORT *m_glColor3bv)(const GLbyte *v); + void (QGL_DLLEXPORT *m_glColor3d)(GLdouble red, GLdouble green, GLdouble blue); + void (QGL_DLLEXPORT *m_glColor3dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glColor3f)(GLfloat red, GLfloat green, GLfloat blue); + void (QGL_DLLEXPORT *m_glColor3fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glColor3i)(GLint red, GLint green, GLint blue); + void (QGL_DLLEXPORT *m_glColor3iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glColor3s)(GLshort red, GLshort green, GLshort blue); + void (QGL_DLLEXPORT *m_glColor3sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glColor3ub)(GLubyte red, GLubyte green, GLubyte blue); + void (QGL_DLLEXPORT *m_glColor3ubv)(const GLubyte *v); + void (QGL_DLLEXPORT *m_glColor3ui)(GLuint red, GLuint green, GLuint blue); + void (QGL_DLLEXPORT *m_glColor3uiv)(const GLuint *v); + void (QGL_DLLEXPORT *m_glColor3us)(GLushort red, GLushort green, GLushort blue); + void (QGL_DLLEXPORT *m_glColor3usv)(const GLushort *v); + void (QGL_DLLEXPORT *m_glColor4b)(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); + void (QGL_DLLEXPORT *m_glColor4bv)(const GLbyte *v); + void (QGL_DLLEXPORT *m_glColor4d)(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); + void (QGL_DLLEXPORT *m_glColor4dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glColor4f)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); + void (QGL_DLLEXPORT *m_glColor4fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glColor4i)(GLint red, GLint green, GLint blue, GLint alpha); + void (QGL_DLLEXPORT *m_glColor4iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glColor4s)(GLshort red, GLshort green, GLshort blue, GLshort alpha); + void (QGL_DLLEXPORT *m_glColor4sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glColor4ub)(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); + void (QGL_DLLEXPORT *m_glColor4ubv)(const GLubyte *v); + void (QGL_DLLEXPORT *m_glColor4ui)(GLuint red, GLuint green, GLuint blue, GLuint alpha); + void (QGL_DLLEXPORT *m_glColor4uiv)(const GLuint *v); + void (QGL_DLLEXPORT *m_glColor4us)(GLushort red, GLushort green, GLushort blue, GLushort alpha); + void (QGL_DLLEXPORT *m_glColor4usv)(const GLushort *v); + void (QGL_DLLEXPORT *m_glColorMask)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); + void (QGL_DLLEXPORT *m_glColorMaterial)(GLenum face, GLenum mode); + void (QGL_DLLEXPORT *m_glColorPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); + void (QGL_DLLEXPORT *m_glCopyPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); + void (QGL_DLLEXPORT *m_glCopyTexImage1D)(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); + void (QGL_DLLEXPORT *m_glCopyTexImage2D)(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); + void (QGL_DLLEXPORT *m_glCopyTexSubImage1D)(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); + void (QGL_DLLEXPORT *m_glCopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); + void (QGL_DLLEXPORT *m_glCullFace)(GLenum mode); + void (QGL_DLLEXPORT *m_glDeleteLists)(GLuint list, GLsizei range); + void (QGL_DLLEXPORT *m_glDeleteTextures)(GLsizei n, const GLuint *textures); + void (QGL_DLLEXPORT *m_glDepthFunc)(GLenum func); + void (QGL_DLLEXPORT *m_glDepthMask)(GLboolean flag); + void (QGL_DLLEXPORT *m_glDepthRange)(GLclampd zNear, GLclampd zFar); + void (QGL_DLLEXPORT *m_glDisable)(GLenum cap); + void (QGL_DLLEXPORT *m_glDisableClientState)(GLenum array); + void (QGL_DLLEXPORT *m_glDrawArrays)(GLenum mode, GLint first, GLsizei count); + void (QGL_DLLEXPORT *m_glDrawBuffer)(GLenum mode); + void (QGL_DLLEXPORT *m_glDrawElements)(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); + void (QGL_DLLEXPORT *m_glDrawPixels)(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); + void (QGL_DLLEXPORT *m_glEdgeFlag)(GLboolean flag); + void (QGL_DLLEXPORT *m_glEdgeFlagPointer)(GLsizei stride, const GLvoid *pointer); + void (QGL_DLLEXPORT *m_glEdgeFlagv)(const GLboolean *flag); + void (QGL_DLLEXPORT *m_glEnable)(GLenum cap); + void (QGL_DLLEXPORT *m_glEnableClientState)(GLenum array); + void (QGL_DLLEXPORT *m_glEnd)(void); + void (QGL_DLLEXPORT *m_glEndList)(void); + void (QGL_DLLEXPORT *m_glEvalCoord1d)(GLdouble u); + void (QGL_DLLEXPORT *m_glEvalCoord1dv)(const GLdouble *u); + void (QGL_DLLEXPORT *m_glEvalCoord1f)(GLfloat u); + void (QGL_DLLEXPORT *m_glEvalCoord1fv)(const GLfloat *u); + void (QGL_DLLEXPORT *m_glEvalCoord2d)(GLdouble u, GLdouble v); + void (QGL_DLLEXPORT *m_glEvalCoord2dv)(const GLdouble *u); + void (QGL_DLLEXPORT *m_glEvalCoord2f)(GLfloat u, GLfloat v); + void (QGL_DLLEXPORT *m_glEvalCoord2fv)(const GLfloat *u); + void (QGL_DLLEXPORT *m_glEvalMesh1)(GLenum mode, GLint i1, GLint i2); + void (QGL_DLLEXPORT *m_glEvalMesh2)(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); + void (QGL_DLLEXPORT *m_glEvalPoint1)(GLint i); + void (QGL_DLLEXPORT *m_glEvalPoint2)(GLint i, GLint j); + void (QGL_DLLEXPORT *m_glFeedbackBuffer)(GLsizei size, GLenum type, GLfloat *buffer); + void (QGL_DLLEXPORT *m_glFinish)(void); + void (QGL_DLLEXPORT *m_glFlush)(void); + void (QGL_DLLEXPORT *m_glFogf)(GLenum pname, GLfloat param); + void (QGL_DLLEXPORT *m_glFogfv)(GLenum pname, const GLfloat *params); + void (QGL_DLLEXPORT *m_glFogi)(GLenum pname, GLint param); + void (QGL_DLLEXPORT *m_glFogiv)(GLenum pname, const GLint *params); + void (QGL_DLLEXPORT *m_glFrontFace)(GLenum mode); + void (QGL_DLLEXPORT *m_glFrustum)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); + GLuint (QGL_DLLEXPORT *m_glGenLists)(GLsizei range); + void (QGL_DLLEXPORT *m_glGenTextures)(GLsizei n, GLuint *textures); + void (QGL_DLLEXPORT *m_glGetBooleanv)(GLenum pname, GLboolean *params); + void (QGL_DLLEXPORT *m_glGetClipPlane)(GLenum plane, GLdouble *equation); + void (QGL_DLLEXPORT *m_glGetDoublev)(GLenum pname, GLdouble *params); + GLenum (QGL_DLLEXPORT *m_glGetError)(void); + void (QGL_DLLEXPORT *m_glGetFloatv)(GLenum pname, GLfloat *params); + void (QGL_DLLEXPORT *m_glGetIntegerv)(GLenum pname, GLint *params); + void (QGL_DLLEXPORT *m_glGetLightfv)(GLenum light, GLenum pname, GLfloat *params); + void (QGL_DLLEXPORT *m_glGetLightiv)(GLenum light, GLenum pname, GLint *params); + void (QGL_DLLEXPORT *m_glGetMapdv)(GLenum target, GLenum query, GLdouble *v); + void (QGL_DLLEXPORT *m_glGetMapfv)(GLenum target, GLenum query, GLfloat *v); + void (QGL_DLLEXPORT *m_glGetMapiv)(GLenum target, GLenum query, GLint *v); + void (QGL_DLLEXPORT *m_glGetMaterialfv)(GLenum face, GLenum pname, GLfloat *params); + void (QGL_DLLEXPORT *m_glGetMaterialiv)(GLenum face, GLenum pname, GLint *params); + void (QGL_DLLEXPORT *m_glGetPixelMapfv)(GLenum map, GLfloat *values); + void (QGL_DLLEXPORT *m_glGetPixelMapuiv)(GLenum map, GLuint *values); + void (QGL_DLLEXPORT *m_glGetPixelMapusv)(GLenum map, GLushort *values); + void (QGL_DLLEXPORT *m_glGetPointerv)(GLenum pname, GLvoid* *params); + void (QGL_DLLEXPORT *m_glGetPolygonStipple)(GLubyte *mask); + const GLubyte * (QGL_DLLEXPORT *m_glGetString)(GLenum name); + void (QGL_DLLEXPORT *m_glGetTexEnvfv)(GLenum target, GLenum pname, GLfloat *params); + void (QGL_DLLEXPORT *m_glGetTexEnviv)(GLenum target, GLenum pname, GLint *params); + void (QGL_DLLEXPORT *m_glGetTexGendv)(GLenum coord, GLenum pname, GLdouble *params); + void (QGL_DLLEXPORT *m_glGetTexGenfv)(GLenum coord, GLenum pname, GLfloat *params); + void (QGL_DLLEXPORT *m_glGetTexGeniv)(GLenum coord, GLenum pname, GLint *params); + void (QGL_DLLEXPORT *m_glGetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); + void (QGL_DLLEXPORT *m_glGetTexLevelParameterfv)(GLenum target, GLint level, GLenum pname, GLfloat *params); + void (QGL_DLLEXPORT *m_glGetTexLevelParameteriv)(GLenum target, GLint level, GLenum pname, GLint *params); + void (QGL_DLLEXPORT *m_glGetTexParameterfv)(GLenum target, GLenum pname, GLfloat *params); + void (QGL_DLLEXPORT *m_glGetTexParameteriv)(GLenum target, GLenum pname, GLint *params); + void (QGL_DLLEXPORT *m_glHint)(GLenum target, GLenum mode); + void (QGL_DLLEXPORT *m_glIndexMask)(GLuint mask); + void (QGL_DLLEXPORT *m_glIndexPointer)(GLenum type, GLsizei stride, const GLvoid *pointer); + void (QGL_DLLEXPORT *m_glIndexd)(GLdouble c); + void (QGL_DLLEXPORT *m_glIndexdv)(const GLdouble *c); + void (QGL_DLLEXPORT *m_glIndexf)(GLfloat c); + void (QGL_DLLEXPORT *m_glIndexfv)(const GLfloat *c); + void (QGL_DLLEXPORT *m_glIndexi)(GLint c); + void (QGL_DLLEXPORT *m_glIndexiv)(const GLint *c); + void (QGL_DLLEXPORT *m_glIndexs)(GLshort c); + void (QGL_DLLEXPORT *m_glIndexsv)(const GLshort *c); + void (QGL_DLLEXPORT *m_glIndexub)(GLubyte c); + void (QGL_DLLEXPORT *m_glIndexubv)(const GLubyte *c); + void (QGL_DLLEXPORT *m_glInitNames)(void); + void (QGL_DLLEXPORT *m_glInterleavedArrays)(GLenum format, GLsizei stride, const GLvoid *pointer); + GLboolean (QGL_DLLEXPORT *m_glIsEnabled)(GLenum cap); + GLboolean (QGL_DLLEXPORT *m_glIsList)(GLuint list); + GLboolean (QGL_DLLEXPORT *m_glIsTexture)(GLuint texture); + void (QGL_DLLEXPORT *m_glLightModelf)(GLenum pname, GLfloat param); + void (QGL_DLLEXPORT *m_glLightModelfv)(GLenum pname, const GLfloat *params); + void (QGL_DLLEXPORT *m_glLightModeli)(GLenum pname, GLint param); + void (QGL_DLLEXPORT *m_glLightModeliv)(GLenum pname, const GLint *params); + void (QGL_DLLEXPORT *m_glLightf)(GLenum light, GLenum pname, GLfloat param); + void (QGL_DLLEXPORT *m_glLightfv)(GLenum light, GLenum pname, const GLfloat *params); + void (QGL_DLLEXPORT *m_glLighti)(GLenum light, GLenum pname, GLint param); + void (QGL_DLLEXPORT *m_glLightiv)(GLenum light, GLenum pname, const GLint *params); + void (QGL_DLLEXPORT *m_glLineStipple)(GLint factor, GLushort pattern); + void (QGL_DLLEXPORT *m_glLineWidth)(GLfloat width); + void (QGL_DLLEXPORT *m_glListBase)(GLuint base); + void (QGL_DLLEXPORT *m_glLoadIdentity)(void); + void (QGL_DLLEXPORT *m_glLoadMatrixd)(const GLdouble *m); + void (QGL_DLLEXPORT *m_glLoadMatrixf)(const GLfloat *m); + void (QGL_DLLEXPORT *m_glLoadName)(GLuint name); + void (QGL_DLLEXPORT *m_glLogicOp)(GLenum opcode); + void (QGL_DLLEXPORT *m_glMap1d)(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); + void (QGL_DLLEXPORT *m_glMap1f)(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); + void (QGL_DLLEXPORT *m_glMap2d)(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); + void (QGL_DLLEXPORT *m_glMap2f)(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); + void (QGL_DLLEXPORT *m_glMapGrid1d)(GLint un, GLdouble u1, GLdouble u2); + void (QGL_DLLEXPORT *m_glMapGrid1f)(GLint un, GLfloat u1, GLfloat u2); + void (QGL_DLLEXPORT *m_glMapGrid2d)(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); + void (QGL_DLLEXPORT *m_glMapGrid2f)(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); + void (QGL_DLLEXPORT *m_glMaterialf)(GLenum face, GLenum pname, GLfloat param); + void (QGL_DLLEXPORT *m_glMaterialfv)(GLenum face, GLenum pname, const GLfloat *params); + void (QGL_DLLEXPORT *m_glMateriali)(GLenum face, GLenum pname, GLint param); + void (QGL_DLLEXPORT *m_glMaterialiv)(GLenum face, GLenum pname, const GLint *params); + void (QGL_DLLEXPORT *m_glMatrixMode)(GLenum mode); + void (QGL_DLLEXPORT *m_glMultMatrixd)(const GLdouble *m); + void (QGL_DLLEXPORT *m_glMultMatrixf)(const GLfloat *m); + void (QGL_DLLEXPORT *m_glNewList)(GLuint list, GLenum mode); + void (QGL_DLLEXPORT *m_glNormal3b)(GLbyte nx, GLbyte ny, GLbyte nz); + void (QGL_DLLEXPORT *m_glNormal3bv)(const GLbyte *v); + void (QGL_DLLEXPORT *m_glNormal3d)(GLdouble nx, GLdouble ny, GLdouble nz); + void (QGL_DLLEXPORT *m_glNormal3dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glNormal3f)(GLfloat nx, GLfloat ny, GLfloat nz); + void (QGL_DLLEXPORT *m_glNormal3fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glNormal3i)(GLint nx, GLint ny, GLint nz); + void (QGL_DLLEXPORT *m_glNormal3iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glNormal3s)(GLshort nx, GLshort ny, GLshort nz); + void (QGL_DLLEXPORT *m_glNormal3sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glNormalPointer)(GLenum type, GLsizei stride, const GLvoid *pointer); + void (QGL_DLLEXPORT *m_glOrtho)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); + void (QGL_DLLEXPORT *m_glPassThrough)(GLfloat token); + void (QGL_DLLEXPORT *m_glPixelMapfv)(GLenum map, GLsizei mapsize, const GLfloat *values); + void (QGL_DLLEXPORT *m_glPixelMapuiv)(GLenum map, GLsizei mapsize, const GLuint *values); + void (QGL_DLLEXPORT *m_glPixelMapusv)(GLenum map, GLsizei mapsize, const GLushort *values); + void (QGL_DLLEXPORT *m_glPixelStoref)(GLenum pname, GLfloat param); + void (QGL_DLLEXPORT *m_glPixelStorei)(GLenum pname, GLint param); + void (QGL_DLLEXPORT *m_glPixelTransferf)(GLenum pname, GLfloat param); + void (QGL_DLLEXPORT *m_glPixelTransferi)(GLenum pname, GLint param); + void (QGL_DLLEXPORT *m_glPixelZoom)(GLfloat xfactor, GLfloat yfactor); + void (QGL_DLLEXPORT *m_glPointSize)(GLfloat size); + void (QGL_DLLEXPORT *m_glPolygonMode)(GLenum face, GLenum mode); + void (QGL_DLLEXPORT *m_glPolygonOffset)(GLfloat factor, GLfloat units); + void (QGL_DLLEXPORT *m_glPolygonStipple)(const GLubyte *mask); + void (QGL_DLLEXPORT *m_glPopAttrib)(void); + void (QGL_DLLEXPORT *m_glPopClientAttrib)(void); + void (QGL_DLLEXPORT *m_glPopMatrix)(void); + void (QGL_DLLEXPORT *m_glPopName)(void); + void (QGL_DLLEXPORT *m_glPrioritizeTextures)(GLsizei n, const GLuint *textures, const GLclampf *priorities); + void (QGL_DLLEXPORT *m_glPushAttrib)(GLbitfield mask); + void (QGL_DLLEXPORT *m_glPushClientAttrib)(GLbitfield mask); + void (QGL_DLLEXPORT *m_glPushMatrix)(void); + void (QGL_DLLEXPORT *m_glPushName)(GLuint name); + void (QGL_DLLEXPORT *m_glRasterPos2d)(GLdouble x, GLdouble y); + void (QGL_DLLEXPORT *m_glRasterPos2dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glRasterPos2f)(GLfloat x, GLfloat y); + void (QGL_DLLEXPORT *m_glRasterPos2fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glRasterPos2i)(GLint x, GLint y); + void (QGL_DLLEXPORT *m_glRasterPos2iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glRasterPos2s)(GLshort x, GLshort y); + void (QGL_DLLEXPORT *m_glRasterPos2sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glRasterPos3d)(GLdouble x, GLdouble y, GLdouble z); + void (QGL_DLLEXPORT *m_glRasterPos3dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glRasterPos3f)(GLfloat x, GLfloat y, GLfloat z); + void (QGL_DLLEXPORT *m_glRasterPos3fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glRasterPos3i)(GLint x, GLint y, GLint z); + void (QGL_DLLEXPORT *m_glRasterPos3iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glRasterPos3s)(GLshort x, GLshort y, GLshort z); + void (QGL_DLLEXPORT *m_glRasterPos3sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glRasterPos4d)(GLdouble x, GLdouble y, GLdouble z, GLdouble w); + void (QGL_DLLEXPORT *m_glRasterPos4dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glRasterPos4f)(GLfloat x, GLfloat y, GLfloat z, GLfloat w); + void (QGL_DLLEXPORT *m_glRasterPos4fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glRasterPos4i)(GLint x, GLint y, GLint z, GLint w); + void (QGL_DLLEXPORT *m_glRasterPos4iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glRasterPos4s)(GLshort x, GLshort y, GLshort z, GLshort w); + void (QGL_DLLEXPORT *m_glRasterPos4sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glReadBuffer)(GLenum mode); + void (QGL_DLLEXPORT *m_glReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); + void (QGL_DLLEXPORT *m_glRectd)(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); + void (QGL_DLLEXPORT *m_glRectdv)(const GLdouble *v1, const GLdouble *v2); + void (QGL_DLLEXPORT *m_glRectf)(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); + void (QGL_DLLEXPORT *m_glRectfv)(const GLfloat *v1, const GLfloat *v2); + void (QGL_DLLEXPORT *m_glRecti)(GLint x1, GLint y1, GLint x2, GLint y2); + void (QGL_DLLEXPORT *m_glRectiv)(const GLint *v1, const GLint *v2); + void (QGL_DLLEXPORT *m_glRects)(GLshort x1, GLshort y1, GLshort x2, GLshort y2); + void (QGL_DLLEXPORT *m_glRectsv)(const GLshort *v1, const GLshort *v2); + GLint (QGL_DLLEXPORT *m_glRenderMode)(GLenum mode); + void (QGL_DLLEXPORT *m_glRotated)(GLdouble anm_gle, GLdouble x, GLdouble y, GLdouble z); + void (QGL_DLLEXPORT *m_glRotatef)(GLfloat anm_gle, GLfloat x, GLfloat y, GLfloat z); + void (QGL_DLLEXPORT *m_glScaled)(GLdouble x, GLdouble y, GLdouble z); + void (QGL_DLLEXPORT *m_glScalef)(GLfloat x, GLfloat y, GLfloat z); + void (QGL_DLLEXPORT *m_glScissor)(GLint x, GLint y, GLsizei width, GLsizei height); + void (QGL_DLLEXPORT *m_glSelectBuffer)(GLsizei size, GLuint *buffer); + void (QGL_DLLEXPORT *m_glShadeModel)(GLenum mode); + void (QGL_DLLEXPORT *m_glStencilFunc)(GLenum func, GLint ref, GLuint mask); + void (QGL_DLLEXPORT *m_glStencilMask)(GLuint mask); + void (QGL_DLLEXPORT *m_glStencilOp)(GLenum fail, GLenum zfail, GLenum zpass); + void (QGL_DLLEXPORT *m_glTexCoord1d)(GLdouble s); + void (QGL_DLLEXPORT *m_glTexCoord1dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glTexCoord1f)(GLfloat s); + void (QGL_DLLEXPORT *m_glTexCoord1fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glTexCoord1i)(GLint s); + void (QGL_DLLEXPORT *m_glTexCoord1iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glTexCoord1s)(GLshort s); + void (QGL_DLLEXPORT *m_glTexCoord1sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glTexCoord2d)(GLdouble s, GLdouble t); + void (QGL_DLLEXPORT *m_glTexCoord2dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glTexCoord2f)(GLfloat s, GLfloat t); + void (QGL_DLLEXPORT *m_glTexCoord2fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glTexCoord2i)(GLint s, GLint t); + void (QGL_DLLEXPORT *m_glTexCoord2iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glTexCoord2s)(GLshort s, GLshort t); + void (QGL_DLLEXPORT *m_glTexCoord2sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glTexCoord3d)(GLdouble s, GLdouble t, GLdouble r); + void (QGL_DLLEXPORT *m_glTexCoord3dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glTexCoord3f)(GLfloat s, GLfloat t, GLfloat r); + void (QGL_DLLEXPORT *m_glTexCoord3fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glTexCoord3i)(GLint s, GLint t, GLint r); + void (QGL_DLLEXPORT *m_glTexCoord3iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glTexCoord3s)(GLshort s, GLshort t, GLshort r); + void (QGL_DLLEXPORT *m_glTexCoord3sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glTexCoord4d)(GLdouble s, GLdouble t, GLdouble r, GLdouble q); + void (QGL_DLLEXPORT *m_glTexCoord4dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glTexCoord4f)(GLfloat s, GLfloat t, GLfloat r, GLfloat q); + void (QGL_DLLEXPORT *m_glTexCoord4fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glTexCoord4i)(GLint s, GLint t, GLint r, GLint q); + void (QGL_DLLEXPORT *m_glTexCoord4iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glTexCoord4s)(GLshort s, GLshort t, GLshort r, GLshort q); + void (QGL_DLLEXPORT *m_glTexCoord4sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glTexCoordPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); + void (QGL_DLLEXPORT *m_glTexEnvf)(GLenum target, GLenum pname, GLfloat param); + void (QGL_DLLEXPORT *m_glTexEnvfv)(GLenum target, GLenum pname, const GLfloat *params); + void (QGL_DLLEXPORT *m_glTexEnvi)(GLenum target, GLenum pname, GLint param); + void (QGL_DLLEXPORT *m_glTexEnviv)(GLenum target, GLenum pname, const GLint *params); + void (QGL_DLLEXPORT *m_glTexGend)(GLenum coord, GLenum pname, GLdouble param); + void (QGL_DLLEXPORT *m_glTexGendv)(GLenum coord, GLenum pname, const GLdouble *params); + void (QGL_DLLEXPORT *m_glTexGenf)(GLenum coord, GLenum pname, GLfloat param); + void (QGL_DLLEXPORT *m_glTexGenfv)(GLenum coord, GLenum pname, const GLfloat *params); + void (QGL_DLLEXPORT *m_glTexGeni)(GLenum coord, GLenum pname, GLint param); + void (QGL_DLLEXPORT *m_glTexGeniv)(GLenum coord, GLenum pname, const GLint *params); + void (QGL_DLLEXPORT *m_glTexImage1D)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); + void (QGL_DLLEXPORT *m_glTexImage2D)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); + void (QGL_DLLEXPORT *m_glTexParameterf)(GLenum target, GLenum pname, GLfloat param); + void (QGL_DLLEXPORT *m_glTexParameterfv)(GLenum target, GLenum pname, const GLfloat *params); + void (QGL_DLLEXPORT *m_glTexParameteri)(GLenum target, GLenum pname, GLint param); + void (QGL_DLLEXPORT *m_glTexParameteriv)(GLenum target, GLenum pname, const GLint *params); + void (QGL_DLLEXPORT *m_glTexSubImage1D)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); + void (QGL_DLLEXPORT *m_glTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); + void (QGL_DLLEXPORT *m_glTranslated)(GLdouble x, GLdouble y, GLdouble z); + void (QGL_DLLEXPORT *m_glTranslatef)(GLfloat x, GLfloat y, GLfloat z); + void (QGL_DLLEXPORT *m_glVertex2d)(GLdouble x, GLdouble y); + void (QGL_DLLEXPORT *m_glVertex2dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glVertex2f)(GLfloat x, GLfloat y); + void (QGL_DLLEXPORT *m_glVertex2fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glVertex2i)(GLint x, GLint y); + void (QGL_DLLEXPORT *m_glVertex2iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glVertex2s)(GLshort x, GLshort y); + void (QGL_DLLEXPORT *m_glVertex2sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glVertex3d)(GLdouble x, GLdouble y, GLdouble z); + void (QGL_DLLEXPORT *m_glVertex3dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glVertex3f)(GLfloat x, GLfloat y, GLfloat z); + void (QGL_DLLEXPORT *m_glVertex3fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glVertex3i)(GLint x, GLint y, GLint z); + void (QGL_DLLEXPORT *m_glVertex3iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glVertex3s)(GLshort x, GLshort y, GLshort z); + void (QGL_DLLEXPORT *m_glVertex3sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glVertex4d)(GLdouble x, GLdouble y, GLdouble z, GLdouble w); + void (QGL_DLLEXPORT *m_glVertex4dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glVertex4f)(GLfloat x, GLfloat y, GLfloat z, GLfloat w); + void (QGL_DLLEXPORT *m_glVertex4fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glVertex4i)(GLint x, GLint y, GLint z, GLint w); + void (QGL_DLLEXPORT *m_glVertex4iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glVertex4s)(GLshort x, GLshort y, GLshort z, GLshort w); + void (QGL_DLLEXPORT *m_glVertex4sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glVertexPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); + void (QGL_DLLEXPORT *m_glViewport)(GLint x, GLint y, GLsizei width, GLsizei height); + + // GL_ARB_multitexture + bool support_ARB_multitexture; + bool ARB_multitexture() + { + return support_ARB_multitexture; + } + void (QGL_DLLEXPORT *m_glActiveTextureARB)(GLenum texture); + void (QGL_DLLEXPORT *m_glClientActiveTextureARB)(GLenum texture); + void (QGL_DLLEXPORT *m_glMultiTexCoord1dARB)(GLenum target, GLdouble s); + void (QGL_DLLEXPORT *m_glMultiTexCoord1dvARB)(GLenum target, const GLdouble *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord1fARB)(GLenum target, GLfloat s); + void (QGL_DLLEXPORT *m_glMultiTexCoord1fvARB)(GLenum target, const GLfloat *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord1iARB)(GLenum target, GLint s); + void (QGL_DLLEXPORT *m_glMultiTexCoord1ivARB)(GLenum target, const GLint *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord1sARB)(GLenum target, GLshort s); + void (QGL_DLLEXPORT *m_glMultiTexCoord1svARB)(GLenum target, const GLshort *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord2dARB)(GLenum target, GLdouble s); + void (QGL_DLLEXPORT *m_glMultiTexCoord2dvARB)(GLenum target, const GLdouble *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord2fARB)(GLenum target, GLfloat s); + void (QGL_DLLEXPORT *m_glMultiTexCoord2fvARB)(GLenum target, const GLfloat *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord2iARB)(GLenum target, GLint s); + void (QGL_DLLEXPORT *m_glMultiTexCoord2ivARB)(GLenum target, const GLint *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord2sARB)(GLenum target, GLshort s); + void (QGL_DLLEXPORT *m_glMultiTexCoord2svARB)(GLenum target, const GLshort *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord3dARB)(GLenum target, GLdouble s); + void (QGL_DLLEXPORT *m_glMultiTexCoord3dvARB)(GLenum target, const GLdouble *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord3fARB)(GLenum target, GLfloat s); + void (QGL_DLLEXPORT *m_glMultiTexCoord3fvARB)(GLenum target, const GLfloat *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord3iARB)(GLenum target, GLint s); + void (QGL_DLLEXPORT *m_glMultiTexCoord3ivARB)(GLenum target, const GLint *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord3sARB)(GLenum target, GLshort s); + void (QGL_DLLEXPORT *m_glMultiTexCoord3svARB)(GLenum target, const GLshort *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord4dARB)(GLenum target, GLdouble s); + void (QGL_DLLEXPORT *m_glMultiTexCoord4dvARB)(GLenum target, const GLdouble *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord4fARB)(GLenum target, GLfloat s); + void (QGL_DLLEXPORT *m_glMultiTexCoord4fvARB)(GLenum target, const GLfloat *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord4iARB)(GLenum target, GLint s); + void (QGL_DLLEXPORT *m_glMultiTexCoord4ivARB)(GLenum target, const GLint *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord4sARB)(GLenum target, GLshort s); + void (QGL_DLLEXPORT *m_glMultiTexCoord4svARB)(GLenum target, const GLshort *v); + + // ARB_texture_compression + bool support_ARB_texture_compression; + bool ARB_texture_compression() + { + return support_ARB_texture_compression; + } + void (QGL_DLLEXPORT *m_glCompressedTexImage3DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid* data); + void (QGL_DLLEXPORT *m_glCompressedTexImage2DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid* data); + void (QGL_DLLEXPORT *m_glCompressedTexImage1DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid* data); + void (QGL_DLLEXPORT *m_glCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid* data); + void (QGL_DLLEXPORT *m_glCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid* data); + void (QGL_DLLEXPORT *m_glCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid* data); + void (QGL_DLLEXPORT *m_glGetCompressedTexImageARB)(GLenum target, GLint lod, GLvoid* img); + + // EXT_texture_compression_s3tc + bool support_EXT_texture_compression_s3tc; + bool EXT_texture_compression_s3tc() + { + return support_EXT_texture_compression_s3tc; + } + + // GL 1.2 + bool support_GL_1_2; + bool GL_1_2() + { + return support_GL_1_2; + } + void (QGL_DLLEXPORT *m_glCopyTexSubImage3D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); + void (QGL_DLLEXPORT *m_glDrawRangeElements)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); + void (QGL_DLLEXPORT *m_glTexImage3D)(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); + void (QGL_DLLEXPORT *m_glTexSubImage3D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); + + // GL 1.3 + bool support_GL_1_3; + bool GL_1_3() + { + return support_GL_1_3; + } + void (QGL_DLLEXPORT *m_glActiveTexture)(GLenum texture); + void (QGL_DLLEXPORT *m_glClientActiveTexture)(GLenum texture); + void (QGL_DLLEXPORT *m_glCompressedTexImage1D)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); + void (QGL_DLLEXPORT *m_glCompressedTexImage2D)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); + void (QGL_DLLEXPORT *m_glCompressedTexImage3D)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); + void (QGL_DLLEXPORT *m_glCompressedTexSubImage1D)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); + void (QGL_DLLEXPORT *m_glCompressedTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); + void (QGL_DLLEXPORT *m_glCompressedTexSubImage3D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); + void (QGL_DLLEXPORT *m_glGetCompressedTexImage)(GLenum target, GLint lod, GLvoid *img); + void (QGL_DLLEXPORT *m_glLoadTransposeMatrixd)(const GLdouble m[16]); + void (QGL_DLLEXPORT *m_glLoadTransposeMatrixf)(const GLfloat m[16]); + void (QGL_DLLEXPORT *m_glMultTransposeMatrixd)(const GLdouble m[16]); + void (QGL_DLLEXPORT *m_glMultTransposeMatrixf)(const GLfloat m[16]); + void (QGL_DLLEXPORT *m_glMultiTexCoord1d)(GLenum target, GLdouble s); + void (QGL_DLLEXPORT *m_glMultiTexCoord1dv)(GLenum target, const GLdouble *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord1f)(GLenum target, GLfloat s); + void (QGL_DLLEXPORT *m_glMultiTexCoord1fv)(GLenum target, const GLfloat *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord1i)(GLenum target, GLint s); + void (QGL_DLLEXPORT *m_glMultiTexCoord1iv)(GLenum target, const GLint *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord1s)(GLenum target, GLshort s); + void (QGL_DLLEXPORT *m_glMultiTexCoord1sv)(GLenum target, const GLshort *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord2d)(GLenum target, GLdouble s, GLdouble t); + void (QGL_DLLEXPORT *m_glMultiTexCoord2dv)(GLenum target, const GLdouble *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord2f)(GLenum target, GLfloat s, GLfloat t); + void (QGL_DLLEXPORT *m_glMultiTexCoord2fv)(GLenum target, const GLfloat *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord2i)(GLenum target, GLint s, GLint t); + void (QGL_DLLEXPORT *m_glMultiTexCoord2iv)(GLenum target, const GLint *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord2s)(GLenum target, GLshort s, GLshort t); + void (QGL_DLLEXPORT *m_glMultiTexCoord2sv)(GLenum target, const GLshort *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord3d)(GLenum target, GLdouble s, GLdouble t, GLdouble r); + void (QGL_DLLEXPORT *m_glMultiTexCoord3dv)(GLenum target, const GLdouble *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord3f)(GLenum target, GLfloat s, GLfloat t, GLfloat r); + void (QGL_DLLEXPORT *m_glMultiTexCoord3fv)(GLenum target, const GLfloat *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord3i)(GLenum target, GLint s, GLint t, GLint r); + void (QGL_DLLEXPORT *m_glMultiTexCoord3iv)(GLenum target, const GLint *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord3s)(GLenum target, GLshort s, GLshort t, GLshort r); + void (QGL_DLLEXPORT *m_glMultiTexCoord3sv)(GLenum target, const GLshort *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord4d)(GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); + void (QGL_DLLEXPORT *m_glMultiTexCoord4dv)(GLenum target, const GLdouble *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord4f)(GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); + void (QGL_DLLEXPORT *m_glMultiTexCoord4fv)(GLenum target, const GLfloat *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord4i)(GLenum target, GLint s, GLint t, GLint r, GLint q); + void (QGL_DLLEXPORT *m_glMultiTexCoord4iv)(GLenum target, const GLint *v); + void (QGL_DLLEXPORT *m_glMultiTexCoord4s)(GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); + void (QGL_DLLEXPORT *m_glMultiTexCoord4sv)(GLenum target, const GLshort *v); + void (QGL_DLLEXPORT *m_glSampleCoverage)(GLclampf value, GLboolean invert); + + // GL 1.4 + bool support_GL_1_4; + bool GL_1_4() + { + return support_GL_1_4; + } + void (QGL_DLLEXPORT *m_glBlendColor)(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); + void (QGL_DLLEXPORT *m_glBlendEquation)(GLenum mode); + void (QGL_DLLEXPORT *m_glBlendFuncSeparate)(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); + void (QGL_DLLEXPORT *m_glFogCoordPointer)(GLenum type, GLsizei stride, const GLvoid *pointer); + void (QGL_DLLEXPORT *m_glFogCoordd)(GLdouble coord); + void (QGL_DLLEXPORT *m_glFogCoorddv)(const GLdouble *coord); + void (QGL_DLLEXPORT *m_glFogCoordf)(GLfloat coord); + void (QGL_DLLEXPORT *m_glFogCoordfv)(const GLfloat *coord); + void (QGL_DLLEXPORT *m_glMultiDrawArrays)(GLenum mode, GLint *first, GLsizei *count, GLsizei primcount); + void (QGL_DLLEXPORT *m_glMultiDrawElements)(GLenum mode, GLsizei *count, GLenum type, const GLvoid **indices, GLsizei primcount); + void (QGL_DLLEXPORT *m_glPointParameterf)(GLenum pname, GLfloat param); + void (QGL_DLLEXPORT *m_glPointParameterfv)(GLenum pname, GLfloat *params); + void (QGL_DLLEXPORT *m_glSecondaryColor3b)(GLbyte red, GLbyte green, GLbyte blue); + void (QGL_DLLEXPORT *m_glSecondaryColor3bv)(const GLbyte *v); + void (QGL_DLLEXPORT *m_glSecondaryColor3d)(GLdouble red, GLdouble green, GLdouble blue); + void (QGL_DLLEXPORT *m_glSecondaryColor3dv)(const GLdouble *v); + void (QGL_DLLEXPORT *m_glSecondaryColor3f)(GLfloat red, GLfloat green, GLfloat blue); + void (QGL_DLLEXPORT *m_glSecondaryColor3fv)(const GLfloat *v); + void (QGL_DLLEXPORT *m_glSecondaryColor3i)(GLint red, GLint green, GLint blue); + void (QGL_DLLEXPORT *m_glSecondaryColor3iv)(const GLint *v); + void (QGL_DLLEXPORT *m_glSecondaryColor3s)(GLshort red, GLshort green, GLshort blue); + void (QGL_DLLEXPORT *m_glSecondaryColor3sv)(const GLshort *v); + void (QGL_DLLEXPORT *m_glSecondaryColor3ub)(GLubyte red, GLubyte green, GLubyte blue); + void (QGL_DLLEXPORT *m_glSecondaryColor3ubv)(const GLubyte *v); + void (QGL_DLLEXPORT *m_glSecondaryColor3ui)(GLuint red, GLuint green, GLuint blue); + void (QGL_DLLEXPORT *m_glSecondaryColor3uiv)(const GLuint *v); + void (QGL_DLLEXPORT *m_glSecondaryColor3us)(GLushort red, GLushort green, GLushort blue); + void (QGL_DLLEXPORT *m_glSecondaryColor3usv)(const GLushort *v); + void (QGL_DLLEXPORT *m_glSecondaryColorPointer)(GLint size, GLenum type, GLsizei stride, GLvoid *pointer); + void (QGL_DLLEXPORT *m_glWindowPos2d)(GLdouble x, GLdouble y); + void (QGL_DLLEXPORT *m_glWindowPos2dv)(const GLdouble *p); + void (QGL_DLLEXPORT *m_glWindowPos2f)(GLfloat x, GLfloat y); + void (QGL_DLLEXPORT *m_glWindowPos2fv)(const GLfloat *p); + void (QGL_DLLEXPORT *m_glWindowPos2i)(GLint x, GLint y); + void (QGL_DLLEXPORT *m_glWindowPos2iv)(const GLint *p); + void (QGL_DLLEXPORT *m_glWindowPos2s)(GLshort x, GLshort y); + void (QGL_DLLEXPORT *m_glWindowPos2sv)(const GLshort *p); + void (QGL_DLLEXPORT *m_glWindowPos3d)(GLdouble x, GLdouble y, GLdouble z); + void (QGL_DLLEXPORT *m_glWindowPos3dv)(const GLdouble *p); + void (QGL_DLLEXPORT *m_glWindowPos3f)(GLfloat x, GLfloat y, GLfloat z); + void (QGL_DLLEXPORT *m_glWindowPos3fv)(const GLfloat *p); + void (QGL_DLLEXPORT *m_glWindowPos3i)(GLint x, GLint y, GLint z); + void (QGL_DLLEXPORT *m_glWindowPos3iv)(const GLint *p); + void (QGL_DLLEXPORT *m_glWindowPos3s)(GLshort x, GLshort y, GLshort z); + void (QGL_DLLEXPORT *m_glWindowPos3sv)(const GLshort *p); + + // GL 1.5 + bool support_GL_1_5; + bool GL_1_5() + { + return support_GL_1_5; + } + void (QGL_DLLEXPORT *m_glBeginQuery)(GLenum target, GLuint id); + void (QGL_DLLEXPORT *m_glBindBuffer)(GLenum target, GLuint buffer); + void (QGL_DLLEXPORT *m_glBufferData)(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage); + void (QGL_DLLEXPORT *m_glBufferSubData)(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data); + void (QGL_DLLEXPORT *m_glDeleteBuffers)(GLsizei n, const GLuint* buffers); + void (QGL_DLLEXPORT *m_glDeleteQueries)(GLsizei n, const GLuint* ids); + void (QGL_DLLEXPORT *m_glEndQuery)(GLenum target); + void (QGL_DLLEXPORT *m_glGenBuffers)(GLsizei n, GLuint* buffers); + void (QGL_DLLEXPORT *m_glGenQueries)(GLsizei n, GLuint* ids); + void (QGL_DLLEXPORT *m_glGetBufferParameteriv)(GLenum target, GLenum pname, GLint* params); + void (QGL_DLLEXPORT *m_glGetBufferPointerv)(GLenum target, GLenum pname, GLvoid** params); + void (QGL_DLLEXPORT *m_glGetBufferSubData)(GLenum target, GLintptr offset, GLsizeiptr size, GLvoid* data); + void (QGL_DLLEXPORT *m_glGetQueryObjectiv)(GLuint id, GLenum pname, GLint* params); + void (QGL_DLLEXPORT *m_glGetQueryObjectuiv)(GLuint id, GLenum pname, GLuint* params); + void (QGL_DLLEXPORT *m_glGetQueryiv)(GLenum target, GLenum pname, GLint params); + GLboolean (QGL_DLLEXPORT *m_glIsBuffer)(GLuint buffer); + GLboolean (QGL_DLLEXPORT *m_glIsQuery)(GLuint id); + GLvoid* (QGL_DLLEXPORT *m_glMapBuffer)(GLenum target, GLenum access); + GLboolean (QGL_DLLEXPORT *m_glUnmapBuffer)(GLenum target); + + // GL_ARB_vertex_program + bool support_ARB_vertex_program; + bool ARB_vertex_program() + { + return support_ARB_vertex_program; + } + void (QGL_DLLEXPORT *m_glVertexAttrib1sARB)(GLuint index, GLshort x); + void (QGL_DLLEXPORT *m_glVertexAttrib1fARB)(GLuint index, GLfloat x); + void (QGL_DLLEXPORT *m_glVertexAttrib1dARB)(GLuint index, GLdouble x); + void (QGL_DLLEXPORT *m_glVertexAttrib2sARB)(GLuint index, GLshort x, GLshort y); + void (QGL_DLLEXPORT *m_glVertexAttrib2fARB)(GLuint index, GLfloat x, GLfloat y); + void (QGL_DLLEXPORT *m_glVertexAttrib2dARB)(GLuint index, GLdouble x, GLdouble y); + void (QGL_DLLEXPORT *m_glVertexAttrib3sARB)(GLuint index, GLshort x, GLshort y, GLshort z); + void (QGL_DLLEXPORT *m_glVertexAttrib3fARB)(GLuint index, GLfloat x, GLfloat y, GLfloat z); + void (QGL_DLLEXPORT *m_glVertexAttrib3dARB)(GLuint index, GLdouble x, GLdouble y, GLdouble z); + void (QGL_DLLEXPORT *m_glVertexAttrib4sARB)(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); + void (QGL_DLLEXPORT *m_glVertexAttrib4fARB)(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); + void (QGL_DLLEXPORT *m_glVertexAttrib4dARB)(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); + void (QGL_DLLEXPORT *m_glVertexAttrib4NubARB)(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); + void (QGL_DLLEXPORT *m_glVertexAttrib1svARB)(GLuint index, const GLshort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib1fvARB)(GLuint index, const GLfloat *v); + void (QGL_DLLEXPORT *m_glVertexAttrib1dvARB)(GLuint index, const GLdouble *v); + void (QGL_DLLEXPORT *m_glVertexAttrib2svARB)(GLuint index, const GLshort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib2fvARB)(GLuint index, const GLfloat *v); + void (QGL_DLLEXPORT *m_glVertexAttrib2dvARB)(GLuint index, const GLdouble *v); + void (QGL_DLLEXPORT *m_glVertexAttrib3svARB)(GLuint index, const GLshort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib3fvARB)(GLuint index, const GLfloat *v); + void (QGL_DLLEXPORT *m_glVertexAttrib3dvARB)(GLuint index, const GLdouble *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4bvARB)(GLuint index, const GLbyte *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4svARB)(GLuint index, const GLshort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4ivARB)(GLuint index, const GLint *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4ubvARB)(GLuint index, const GLubyte *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4usvARB)(GLuint index, const GLushort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4uivARB)(GLuint index, const GLuint *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4fvARB)(GLuint index, const GLfloat *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4dvARB)(GLuint index, const GLdouble *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4NbvARB)(GLuint index, const GLbyte *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4NsvARB)(GLuint index, const GLshort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4NivARB)(GLuint index, const GLint *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4NubvARB)(GLuint index, const GLubyte *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4NusvARB)(GLuint index, const GLushort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4NuivARB)(GLuint index, const GLuint *v); + void (QGL_DLLEXPORT *m_glVertexAttribPointerARB)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); + void (QGL_DLLEXPORT *m_glEnableVertexAttribArrayARB)(GLuint index); + void (QGL_DLLEXPORT *m_glDisableVertexAttribArrayARB)(GLuint index); + void (QGL_DLLEXPORT *m_glProgramStringARB)(GLenum target, GLenum format, GLsizei len, const GLvoid *string); + void (QGL_DLLEXPORT *m_glBindProgramARB)(GLenum target, GLuint program); + void (QGL_DLLEXPORT *m_glDeleteProgramsARB)(GLsizei n, const GLuint *programs); + void (QGL_DLLEXPORT *m_glGenProgramsARB)(GLsizei n, GLuint *programs); + void (QGL_DLLEXPORT *m_glProgramEnvParameter4dARB)(GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); + void (QGL_DLLEXPORT *m_glProgramEnvParameter4dvARB)(GLenum target, GLuint index, const GLdouble *params); + void (QGL_DLLEXPORT *m_glProgramEnvParameter4fARB)(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); + void (QGL_DLLEXPORT *m_glProgramEnvParameter4fvARB)(GLenum target, GLuint index, const GLfloat *params); + void (QGL_DLLEXPORT *m_glProgramLocalParameter4dARB)(GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); + void (QGL_DLLEXPORT *m_glProgramLocalParameter4dvARB)(GLenum target, GLuint index, const GLdouble *params); + void (QGL_DLLEXPORT *m_glProgramLocalParameter4fARB)(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); + void (QGL_DLLEXPORT *m_glProgramLocalParameter4fvARB)(GLenum target, GLuint index, const GLfloat *params); + void (QGL_DLLEXPORT *m_glGetProgramEnvParameterdvARB)(GLenum target, GLuint index, GLdouble *params); + void (QGL_DLLEXPORT *m_glGetProgramEnvParameterfvARB)(GLenum target, GLuint index, GLfloat *params); + void (QGL_DLLEXPORT *m_glGetProgramLocalParameterdvARB)(GLenum target, GLuint index, GLdouble *params); + void (QGL_DLLEXPORT *m_glGetProgramLocalParameterfvARB)(GLenum target, GLuint index, GLfloat *params); + void (QGL_DLLEXPORT *m_glGetProgramivARB)(GLenum target, GLenum pname, GLint *params); + void (QGL_DLLEXPORT *m_glGetProgramStringARB)(GLenum target, GLenum pname, GLvoid *string); + void (QGL_DLLEXPORT *m_glGetVertexAttribdvARB)(GLuint index, GLenum pname, GLdouble *params); + void (QGL_DLLEXPORT *m_glGetVertexAttribfvARB)(GLuint index, GLenum pname, GLfloat *params); + void (QGL_DLLEXPORT *m_glGetVertexAttribivARB)(GLuint index, GLenum pname, GLint *params); + void (QGL_DLLEXPORT *m_glGetVertexAttribPointervARB)(GLuint index, GLenum pname, GLvoid **pointer); + GLboolean (QGL_DLLEXPORT *m_glIsProgramARB)(GLuint program); + + // GL_ARB_fragment_program + bool support_ARB_fragment_program; + bool ARB_fragment_program() + { + return support_ARB_fragment_program; + } + + // GL_ARB_shader_objects + bool support_ARB_shader_objects; + bool ARB_shader_objects() + { + return support_ARB_shader_objects; + } + void (QGL_DLLEXPORT *m_glDeleteObjectARB)(GLhandleARB obj); + GLhandleARB (QGL_DLLEXPORT *m_glGetHandleARB)(GLenum pname); + void (QGL_DLLEXPORT *m_glDetachObjectARB)(GLhandleARB containerObj, GLhandleARB attachedObj); + GLhandleARB (QGL_DLLEXPORT *m_glCreateShaderObjectARB)(GLenum shaderType); + void (QGL_DLLEXPORT *m_glShaderSourceARB)(GLhandleARB shaderObj, GLsizei count, const GLcharARB **string, const GLint *length); + void (QGL_DLLEXPORT *m_glCompileShaderARB)(GLhandleARB shaderObj); + GLhandleARB (QGL_DLLEXPORT *m_glCreateProgramObjectARB)(void); + void (QGL_DLLEXPORT *m_glAttachObjectARB)(GLhandleARB containerObj, GLhandleARB obj); + void (QGL_DLLEXPORT *m_glLinkProgramARB)(GLhandleARB programObj); + void (QGL_DLLEXPORT *m_glUseProgramObjectARB)(GLhandleARB programObj); + void (QGL_DLLEXPORT *m_glValidateProgramARB)(GLhandleARB programObj); + void (QGL_DLLEXPORT *m_glUniform1fARB)(GLint location, GLfloat v0); + void (QGL_DLLEXPORT *m_glUniform2fARB)(GLint location, GLfloat v0, GLfloat v1); + void (QGL_DLLEXPORT *m_glUniform3fARB)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); + void (QGL_DLLEXPORT *m_glUniform4fARB)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); + void (QGL_DLLEXPORT *m_glUniform1iARB)(GLint location, GLint v0); + void (QGL_DLLEXPORT *m_glUniform2iARB)(GLint location, GLint v0, GLint v1); + void (QGL_DLLEXPORT *m_glUniform3iARB)(GLint location, GLint v0, GLint v1, GLint v2); + void (QGL_DLLEXPORT *m_glUniform4iARB)(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); + void (QGL_DLLEXPORT *m_glUniform1fvARB)(GLint location, GLsizei count, const GLfloat *value); + void (QGL_DLLEXPORT *m_glUniform2fvARB)(GLint location, GLsizei count, const GLfloat *value); + void (QGL_DLLEXPORT *m_glUniform3fvARB)(GLint location, GLsizei count, const GLfloat *value); + void (QGL_DLLEXPORT *m_glUniform4fvARB)(GLint location, GLsizei count, const GLfloat *value); + void (QGL_DLLEXPORT *m_glUniform1ivARB)(GLint location, GLsizei count, const GLint *value); + void (QGL_DLLEXPORT *m_glUniform2ivARB)(GLint location, GLsizei count, const GLint *value); + void (QGL_DLLEXPORT *m_glUniform3ivARB)(GLint location, GLsizei count, const GLint *value); + void (QGL_DLLEXPORT *m_glUniform4ivARB)(GLint location, GLsizei count, const GLint *value); + void (QGL_DLLEXPORT *m_glUniformMatrix2fvARB)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); + void (QGL_DLLEXPORT *m_glUniformMatrix3fvARB)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); + void (QGL_DLLEXPORT *m_glUniformMatrix4fvARB)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); + void (QGL_DLLEXPORT *m_glGetObjectParameterfvARB)(GLhandleARB obj, GLenum pname, GLfloat *params); + void (QGL_DLLEXPORT *m_glGetObjectParameterivARB)(GLhandleARB obj, GLenum pname, GLint *params); + void (QGL_DLLEXPORT *m_glGetInfoLogARB)(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); + void (QGL_DLLEXPORT *m_glGetAttachedObjectsARB)(GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); + GLint (QGL_DLLEXPORT *m_glGetUniformLocationARB)(GLhandleARB programObj, const GLcharARB *name); + void (QGL_DLLEXPORT *m_glGetActiveUniformARB)(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); + void (QGL_DLLEXPORT *m_glGetUniformfvARB)(GLhandleARB programObj, GLint location, GLfloat *params); + void (QGL_DLLEXPORT *m_glGetUniformivARB)(GLhandleARB programObj, GLint location, GLint *params); + void (QGL_DLLEXPORT *m_glGetShaderSourceARB)(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); + + // GL_ARB_vertex_shader + bool support_ARB_vertex_shader; + bool ARB_vertex_shader() + { + return support_ARB_vertex_shader; + } +#if 0 + void (QGL_DLLEXPORT *m_glVertexAttrib1fARB)(GLuint index, GLfloat v0); + void (QGL_DLLEXPORT *m_glVertexAttrib1sARB)(GLuint index, GLshort v0); + void (QGL_DLLEXPORT *m_glVertexAttrib1dARB)(GLuint index, GLdouble v0); + void (QGL_DLLEXPORT *m_glVertexAttrib2fARB)(GLuint index, GLfloat v0, GLfloat v1); + void (QGL_DLLEXPORT *m_glVertexAttrib2sARB)(GLuint index, GLshort v0, GLshort v1); + void (QGL_DLLEXPORT *m_glVertexAttrib2dARB)(GLuint index, GLdouble v0, GLdouble v1); + void (QGL_DLLEXPORT *m_glVertexAttrib3fARB)(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2); + void (QGL_DLLEXPORT *m_glVertexAttrib3sARB)(GLuint index, GLshort v0, GLshort v1, GLshort v2); + void (QGL_DLLEXPORT *m_glVertexAttrib3dARB)(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2); + void (QGL_DLLEXPORT *m_glVertexAttrib4fARB)(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); + void (QGL_DLLEXPORT *m_glVertexAttrib4sARB)(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3); + void (QGL_DLLEXPORT *m_glVertexAttrib4dARB)(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); + void (QGL_DLLEXPORT *m_glVertexAttrib4NubARB)(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); + void (QGL_DLLEXPORT *m_glVertexAttrib1fvARB)(GLuint index, const GLfloat *v); + void (QGL_DLLEXPORT *m_glVertexAttrib1svARB)(GLuint index, const GLshort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib1dvARB)(GLuint index, const GLdouble *v); + void (QGL_DLLEXPORT *m_glVertexAttrib2fvARB)(GLuint index, const GLfloat *v); + void (QGL_DLLEXPORT *m_glVertexAttrib2svARB)(GLuint index, const GLshort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib2dvARB)(GLuint index, const GLdouble *v); + void (QGL_DLLEXPORT *m_glVertexAttrib3fvARB)(GLuint index, const GLfloat *v); + void (QGL_DLLEXPORT *m_glVertexAttrib3svARB)(GLuint index, const GLshort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib3dvARB)(GLuint index, const GLdouble *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4fvARB)(GLuint index, const GLfloat *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4svARB)(GLuint index, const GLshort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4dvARB)(GLuint index, const GLdouble *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4ivARB)(GLuint index, const GLint *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4bvARB)(GLuint index, const GLbyte *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4ubvARB)(GLuint index, const GLubyte *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4usvARB)(GLuint index, const GLushort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4uivARB)(GLuint index, const GLuint *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4NbvARB)(GLuint index, const GLbyte *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4NsvARB)(GLuint index, const GLshort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4NivARB)(GLuint index, const GLint *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4NubvARB)(GLuint index, const GLubyte *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4NusvARB)(GLuint index, const GLushort *v); + void (QGL_DLLEXPORT *m_glVertexAttrib4NuivARB)(GLuint index, const GLuint *v); + void (QGL_DLLEXPORT *m_glVertexAttribPointerARB)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); + void (QGL_DLLEXPORT *m_glEnableVertexAttribArrayARB)(GLuint index); + void (QGL_DLLEXPORT *m_glDisableVertexAttribArrayARB)(GLuint index); +#endif + void (QGL_DLLEXPORT *m_glBindAttribLocationARB)(GLhandleARB programObj, GLuint index, const GLcharARB *name); + void (QGL_DLLEXPORT *m_glGetActiveAttribARB)(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); + GLint (QGL_DLLEXPORT *m_glGetAttribLocationARB)(GLhandleARB programObj, const GLcharARB *name); +#if 0 + void (QGL_DLLEXPORT *m_glGetVertexAttribdvARB)(GLuint index, GLenum pname, GLdouble *params); + void (QGL_DLLEXPORT *m_glGetVertexAttribfvARB)(GLuint index, GLenum pname, GLfloat *params); + void (QGL_DLLEXPORT *m_glGetVertexAttribivARB)(GLuint index, GLenum pname, GLint *params); + void (QGL_DLLEXPORT *m_glGetVertexAttribPointervARB)(GLuint index, GLenum pname, GLvoid **pointer); +#endif + + // ARB_fragment_shader + bool support_ARB_fragment_shader; + bool ARB_fragment_shader() + { + return support_ARB_fragment_shader; + } + + // ARB_shading_language_100 + bool support_ARB_shading_language_100; + bool ARB_shading_language_100() + { + return support_ARB_shading_language_100; + } + + // GL_NV_vertex_program2 + bool support_NV_vertex_program2; + bool NV_vertex_program2() + { + return support_NV_vertex_program2; + } + GLboolean (QGL_DLLEXPORT* m_glAreProgramsResidentNV)(GLsizei, const GLuint *, GLboolean *); + void (QGL_DLLEXPORT* m_glBindProgramNV)(GLenum, GLuint); + void (QGL_DLLEXPORT* m_glDeleteProgramsNV)(GLsizei, const GLuint *); + void (QGL_DLLEXPORT* m_glExecuteProgramNV)(GLenum, GLuint, const GLfloat *); + void (QGL_DLLEXPORT* m_glGenProgramsNV)(GLsizei, GLuint *); + void (QGL_DLLEXPORT* m_glGetProgramParameterdvNV)(GLenum, GLuint, GLenum, GLdouble *); + void (QGL_DLLEXPORT* m_glGetProgramParameterfvNV)(GLenum, GLuint, GLenum, GLfloat *); + void (QGL_DLLEXPORT* m_glGetProgramivNV)(GLuint, GLenum, GLint *); + void (QGL_DLLEXPORT* m_glGetProgramStringNV)(GLuint, GLenum, GLubyte *); + void (QGL_DLLEXPORT* m_glGetTrackMatrixivNV)(GLenum, GLuint, GLenum, GLint *); + void (QGL_DLLEXPORT* m_glGetVertexAttribdvNV)(GLuint, GLenum, GLdouble *); + void (QGL_DLLEXPORT* m_glGetVertexAttribfvNV)(GLuint, GLenum, GLfloat *); + void (QGL_DLLEXPORT* m_glGetVertexAttribivNV)(GLuint, GLenum, GLint *); + void (QGL_DLLEXPORT* m_glGetVertexAttribPointervNV)(GLuint, GLenum, GLvoid* *); + GLboolean (QGL_DLLEXPORT* m_glIsProgramNV)(GLuint); + void (QGL_DLLEXPORT* m_glLoadProgramNV)(GLenum, GLuint, GLsizei, const GLubyte *); + void (QGL_DLLEXPORT* m_glProgramParameter4fNV)(GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); + void (QGL_DLLEXPORT* m_glProgramParameter4fvNV)(GLenum, GLuint, const GLfloat *); + void (QGL_DLLEXPORT* m_glProgramParameters4fvNV)(GLenum, GLuint, GLuint, const GLfloat *); + void (QGL_DLLEXPORT* m_glRequestResidentProgramsNV)(GLsizei, const GLuint *); + void (QGL_DLLEXPORT* m_glTrackMatrixNV)(GLenum, GLuint, GLenum, GLenum); + void (QGL_DLLEXPORT* m_glVertexAttribPointerNV)(GLuint, GLint, GLenum, GLsizei, const GLvoid *); + void (QGL_DLLEXPORT* m_glVertexAttrib1fNV)(GLuint, GLfloat); + void (QGL_DLLEXPORT* m_glVertexAttrib1fvNV)(GLuint, const GLfloat *); + void (QGL_DLLEXPORT* m_glVertexAttrib2fNV)(GLuint, GLfloat, GLfloat); + void (QGL_DLLEXPORT* m_glVertexAttrib2fvNV)(GLuint, const GLfloat *); + void (QGL_DLLEXPORT* m_glVertexAttrib3fNV)(GLuint, GLfloat, GLfloat, GLfloat); + void (QGL_DLLEXPORT* m_glVertexAttrib3fvNV)(GLuint, const GLfloat *); + void (QGL_DLLEXPORT* m_glVertexAttrib4fNV)(GLuint, GLfloat, GLfloat, GLfloat, GLfloat); + void (QGL_DLLEXPORT* m_glVertexAttrib4fvNV)(GLuint, const GLfloat *); + void (QGL_DLLEXPORT* m_glVertexAttribs1fvNV)(GLuint, GLsizei, const GLfloat *); + void (QGL_DLLEXPORT* m_glVertexAttribs2fvNV)(GLuint, GLsizei, const GLfloat *); + void (QGL_DLLEXPORT* m_glVertexAttribs3fvNV)(GLuint, GLsizei, const GLfloat *); + void (QGL_DLLEXPORT* m_glVertexAttribs4fvNV)(GLuint, GLsizei, const GLfloat *); + + // GL_NV_fragment_program + bool support_NV_fragment_program; + bool NV_fragment_program() + { + return support_NV_fragment_program; + } + void (QGL_DLLEXPORT* m_glProgramNamedParameter4fNV)(GLuint, GLsizei, const GLubyte *, GLfloat, GLfloat, GLfloat, GLfloat); + void (QGL_DLLEXPORT* m_glProgramNamedParameter4fvNV)(GLuint, GLsizei, const GLubyte *, const GLfloat *); + void (QGL_DLLEXPORT* m_glGetProgramNamedParameterfvNV)(GLuint, GLsizei, const GLubyte *, GLfloat *); +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalOpenGLModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalOpenGLModuleRef; + +inline OpenGLBinding& GlobalOpenGL() +{ + return GlobalOpenGLModule::getTable(); +} + +#if defined(_DEBUG) +inline void GlobalOpenGL_debugAssertNoErrors() +{ + GlobalOpenGL().assertNoErrors(); +} +#else +inline void GlobalOpenGL_debugAssertNoErrors() +{ +} +#endif + + +#endif diff --git a/include/iglrender.cpp b/include/iglrender.cpp new file mode 100644 index 00000000..26d66ae0 --- /dev/null +++ b/include/iglrender.cpp @@ -0,0 +1,3 @@ + +#include "iglrender.h" + diff --git a/include/iglrender.h b/include/iglrender.h new file mode 100644 index 00000000..76216026 --- /dev/null +++ b/include/iglrender.h @@ -0,0 +1,130 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IGLRENDER_H) +#define INCLUDED_IGLRENDER_H + +#include "igl.h" +#include "generic/vector.h" +class AABB; +class Matrix4; + + +class GLProgram +{ +public: + virtual void enable() = 0; + virtual void disable() = 0; + virtual void setParameters(const Vector3& viewer, const Matrix4& localToWorld, const Vector3& origin, const Vector3& colour, const Matrix4& world2light) = 0; +}; + +class OpenGLFogState +{ +public: + OpenGLFogState() : mode(GL_EXP), density(0), start(0), end(0), index(0), colour(1, 1, 1, 1) + { + } + GLenum mode; + GLfloat density; + GLfloat start; + GLfloat end; + GLint index; + Vector4 colour; +}; + +//! A collection of opengl state information. +class OpenGLState +{ +public: + enum ESort + { + eSortFirst = 0, + eSortOpaque = 1, + eSortMultiFirst = 2, + eSortMultiLast = 1023, + eSortOverbrighten = 1024, + eSortFullbright = 1025, + eSortHighlight = 1026, + eSortTranslucent = 1027, + eSortOverlayFirst = 1028, + eSortOverlayLast = 2047, + eSortControlFirst = 2048, + eSortControlLast = 3071, + eSortGUI0 = 3072, + eSortGUI1 = 3073, + eSortLast = 4096, + }; + + unsigned int m_state; + std::size_t m_sort; + GLint m_texture; + GLint m_texture1; + GLint m_texture2; + GLint m_texture3; + GLint m_texture4; + GLint m_texture5; + GLint m_texture6; + GLint m_texture7; + Vector4 m_colour; + GLenum m_blend_src, m_blend_dst; + GLenum m_depthfunc; + GLenum m_alphafunc; + GLfloat m_alpharef; + GLfloat m_linewidth; + GLfloat m_pointsize; + GLint m_linestipple_factor; + GLushort m_linestipple_pattern; + OpenGLFogState m_fog; + GLProgram* m_program; + + OpenGLState() : m_program(0) + { + } +}; + +class OpenGLStateLibrary +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "openglshaderlibrary"); + + virtual void getDefaultState(OpenGLState& state) const = 0; + + virtual void insert(const char* name, const OpenGLState& state) = 0; + virtual void erase(const char* name) = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalOpenGLStateLibraryModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalOpenGLStateLibraryModuleRef; + +inline OpenGLStateLibrary& GlobalOpenGLStateLibrary() +{ + return GlobalOpenGLStateLibraryModule::getTable(); +} + +#endif diff --git a/include/igtkgl.cpp b/include/igtkgl.cpp new file mode 100644 index 00000000..1180d547 --- /dev/null +++ b/include/igtkgl.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "igtkgl.h" + diff --git a/include/igtkgl.h b/include/igtkgl.h new file mode 100644 index 00000000..6a0a631f --- /dev/null +++ b/include/igtkgl.h @@ -0,0 +1,43 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IGTKGL_H) +#define INCLUDED_IGTKGL_H + +#include "generic/constant.h" + +typedef struct _GtkWidget GtkWidget; +typedef int gint; +typedef gint gboolean; + +struct _QERGtkGLTable +{ + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "gtkgl"); + + GtkWidget* (*glwidget_new)(gboolean zbufffer); + void (*glwidget_swap_buffers)(GtkWidget* widget); + gboolean(*glwidget_make_current)(GtkWidget* widget); + void (*glwidget_destroy_context)(GtkWidget* widget); + void (*glwidget_create_context)(GtkWidget* widget); +}; + +#endif diff --git a/include/iimage.cpp b/include/iimage.cpp new file mode 100644 index 00000000..a95ab6d3 --- /dev/null +++ b/include/iimage.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "iimage.h" + diff --git a/include/iimage.h b/include/iimage.h new file mode 100644 index 00000000..f6020bd8 --- /dev/null +++ b/include/iimage.h @@ -0,0 +1,75 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IIMAGE_H) +#define INCLUDED_IIMAGE_H + +#include "generic/constant.h" + +typedef unsigned char byte; + +class Image +{ +public: + virtual void release() = 0; + virtual byte* getRGBAPixels() const = 0; + virtual unsigned int getWidth() const = 0; + virtual unsigned int getHeight() const = 0; + + virtual int getSurfaceFlags() const + { + return 0; + } + virtual int getContentFlags() const + { + return 0; + } + virtual int getValue() const + { + return 0; + } +}; + +class ArchiveFile; + +struct _QERPlugImageTable +{ + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "image"); + + /// Read an image from the file. + /// Returns 0 if the image could not be read. + Image* (*loadImage)(ArchiveFile& file); +}; + +template +class ModuleRef; +typedef ModuleRef<_QERPlugImageTable> ImageModuleRef; + +template +class Modules; +typedef Modules<_QERPlugImageTable> ImageModules; + +template +class ModulesRef; +typedef ModulesRef<_QERPlugImageTable> ImageModulesRef; + +#endif // _IIMAGE_H diff --git a/include/imap.cpp b/include/imap.cpp new file mode 100644 index 00000000..0b363b5e --- /dev/null +++ b/include/imap.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "imap.h" + diff --git a/include/imap.h b/include/imap.h new file mode 100644 index 00000000..53c804e4 --- /dev/null +++ b/include/imap.h @@ -0,0 +1,81 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IMAP_H) +#define INCLUDED_IMAP_H + +#include "generic/constant.h" + +class Tokeniser; +class TokenWriter; + +/// \brief A node whose state can be imported from a token stream. +class MapImporter +{ +public: + STRING_CONSTANT(Name, "MapImporter"); + + virtual bool importTokens(Tokeniser& tokeniser) = 0; +}; + +/// \brief A node whose state can be exported to a token stream. +class MapExporter +{ +public: + STRING_CONSTANT(Name, "MapExporter"); + + virtual void exportTokens(TokenWriter& writer) const = 0; +}; + +#include "iscenegraph.h" + +class EntityCreator; + +class TextInputStream; +class TextOutputStream; + + +typedef void(*GraphTraversalFunc)(scene::Node& root, const scene::Traversable::Walker& walker); + +/// \brief A module that reads and writes a map in a specific format. +class MapFormat +{ +public: + INTEGER_CONSTANT(Version, 2); + STRING_CONSTANT(Name, "map"); + + /// \brief Read a map graph into \p root from \p outputStream, using \p entityTable to create entities. + virtual void readGraph(scene::Node& root, TextInputStream& inputStream, EntityCreator& entityTable) const = 0; + /// \brief Write the map graph obtained by applying \p traverse to \p root into \p outputStream. + virtual void writeGraph(scene::Node& root, GraphTraversalFunc traverse, TextOutputStream& outputStream) const = 0; +}; + + +template +class Modules; +typedef Modules MapModules; + +template +class ModulesRef; +typedef ModulesRef MapModulesRef; + + +#endif diff --git a/include/imodel.cpp b/include/imodel.cpp new file mode 100644 index 00000000..081dccac --- /dev/null +++ b/include/imodel.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "imodel.h" + diff --git a/include/imodel.h b/include/imodel.h new file mode 100644 index 00000000..aa040d63 --- /dev/null +++ b/include/imodel.h @@ -0,0 +1,50 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IMODEL_H) +#define INCLUDED_IMODEL_H + +#include "generic/constant.h" + +namespace scene +{ + class Node; +} + +class ArchiveFile; + +class ModelLoader +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "model"); + virtual scene::Node& loadModel(ArchiveFile& file) = 0; +}; + +template +class Modules; +typedef Modules ModelModules; + +template +class ModulesRef; +typedef ModulesRef ModelModulesRef; + +#endif /* _IMODEL_H_ */ diff --git a/include/include.vcproj b/include/include.vcproj new file mode 100644 index 00000000..a374cc2c --- /dev/null +++ b/include/include.vcproj @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/include/ipatch.cpp b/include/ipatch.cpp new file mode 100644 index 00000000..940a1bed --- /dev/null +++ b/include/ipatch.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ipatch.h" + diff --git a/include/ipatch.h b/include/ipatch.h new file mode 100644 index 00000000..48ce39b5 --- /dev/null +++ b/include/ipatch.h @@ -0,0 +1,292 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IPATCH_H) +#define INCLUDED_IPATCH_H + +#include "generic/constant.h" +#include "generic/vector.h" + +namespace scene +{ + class Node; +} + +template +class ArrayReference +{ + std::size_t m_size; + Element* m_data; +public: + typedef Element value_type; + typedef value_type* iterator; + typedef const value_type* const_iterator; + + ArrayReference() + : m_size(0), m_data(0) + { + } + ArrayReference(std::size_t size, Element* data) + : m_size(size), m_data(data) + { + } + + iterator begin() + { + return m_data; + } + const_iterator begin() const + { + return m_data; + } + iterator end() + { + return m_data + m_size; + } + const_iterator end() const + { + return m_data + m_size; + } + + value_type& operator[](std::size_t index) + { +#if defined(_DEBUG) + ASSERT_MESSAGE(index < size(), "array index out of bounds"); +#endif + return m_data[index]; + } + const value_type& operator[](std::size_t index) const + { +#if defined(_DEBUG) + ASSERT_MESSAGE(index < size(), "array index out of bounds"); +#endif + return m_data[index]; + } + value_type* data() + { + return m_data; + } + const value_type* data() const + { + return m_data; + } + std::size_t size() const + { + return m_size; + } + bool empty() const + { + return m_size == 0; + } +}; + +#if 0 +template +class MatrixIterator +{ + Element* m_position; + + void increment() + { + ++m_position; + } + +public: + typedef std::bidirectional_iterator_tag iterator_category; + typedef std::ptrdiff_t difference_type; + typedef difference_type distance_type; + typedef KeyValue value_type; + typedef value_type* pointer; + typedef value_type& reference; + + MatrixIterator(Element* position) : m_position(position) + { + } + + Element* position() + { + return m_position; + } + + bool operator==(const MatrixIterator& other) const + { + return m_position == other.m_position; + } + bool operator!=(const MatrixIterator& other) const + { + return !operator==(other); + } + MatrixIterator& operator++() + { + increment(); + return *this; + } + MatrixIterator operator++(int) + { + MatrixIterator tmp = *this; + increment(); + return tmp; + } + value_type& operator*() const + { + return m_position->m_value; + } + value_type* operator->() const + { + return &(operator*()); + } +}; +#endif + +template +class Matrix +{ + std::size_t m_x, m_y; + Element* m_data; +public: + typedef Element value_type; + typedef value_type* iterator; + typedef const value_type* const_iterator; + + Matrix() + : m_x(0), m_y(0), m_data(0) + { + } + Matrix(std::size_t x, std::size_t y, Element* data) + : m_x(x), m_y(y), m_data(data) + { + } + + iterator begin() + { + return m_data; + } + const_iterator begin() const + { + return m_data; + } + iterator end() + { + return m_data + size(); + } + const_iterator end() const + { + return m_data + size(); + } + + value_type& operator[](std::size_t index) + { +#if defined(_DEBUG) + ASSERT_MESSAGE(index < size(), "array index out of bounds"); +#endif + return m_data[index]; + } + const value_type& operator[](std::size_t index) const + { +#if defined(_DEBUG) + ASSERT_MESSAGE(index < size(), "array index out of bounds"); +#endif + return m_data[index]; + } + value_type& operator()(std::size_t x, std::size_t y) + { +#if defined(_DEBUG) + ASSERT_MESSAGE(x < m_x && y < m_y, "array index out of bounds"); +#endif + return m_data[x * m_y + y]; + } + const value_type& operator()(std::size_t x, std::size_t y) const + { +#if defined(_DEBUG) + ASSERT_MESSAGE(x < m_x && y < m_y, "array index out of bounds"); +#endif + return m_data[x * m_y + y]; + } + value_type* data() + { + return m_data; + } + const value_type* data() const + { + return m_data; + } + std::size_t x() const + { + return m_x; + } + std::size_t y() const + { + return m_y; + } + std::size_t size() const + { + return m_x * m_y; + } + bool empty() const + { + return m_x == 0; + } +}; + +class PatchControl +{ +public: + Vector3 m_vertex; + Vector2 m_texcoord; +}; + +typedef Matrix PatchControlMatrix; + + +class PatchCreator +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "patch"); + virtual scene::Node& createPatch() = 0; + virtual void Patch_undoSave(scene::Node& patch) const = 0; + virtual void Patch_resize(scene::Node& patch, std::size_t width, std::size_t height) const = 0; + virtual PatchControlMatrix Patch_getControlPoints(scene::Node& patch) const = 0; + virtual void Patch_controlPointsChanged(scene::Node& patch) const = 0; + virtual const char* Patch_getShader(scene::Node& patch) const = 0; + virtual void Patch_setShader(scene::Node& patch, const char* shader) const = 0; +}; + +#include "modulesystem.h" + +template +class ModuleRef; +typedef ModuleRef PatchModuleRef; + +template +class GlobalModule; +typedef GlobalModule GlobalPatchModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalPatchModuleRef; + +inline PatchCreator& GlobalPatchCreator() +{ + return GlobalPatchModule::getTable(); +} + +#endif diff --git a/include/iplugin.cpp b/include/iplugin.cpp new file mode 100644 index 00000000..ebfc591f --- /dev/null +++ b/include/iplugin.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "iplugin.h" + diff --git a/include/iplugin.h b/include/iplugin.h new file mode 100644 index 00000000..af713a7d --- /dev/null +++ b/include/iplugin.h @@ -0,0 +1,53 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IPLUGIN_H) +#define INCLUDED_IPLUGIN_H + +#include "generic/constant.h" + +typedef const char* (* PFN_QERPLUG_INIT) (void* hApp, void* pMainWidget); +typedef const char* (* PFN_QERPLUG_GETNAME)(); +typedef const char* (* PFN_QERPLUG_GETCOMMANDLIST)(); +typedef const char* (* PFN_QERPLUG_GETCOMMANDTITLELIST)(); +typedef void (* PFN_QERPLUG_DISPATCH) (const char* p, float* vMin, float* vMax, bool bSingleBrush); + +struct _QERPluginTable +{ + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "plugin"); + + PFN_QERPLUG_INIT m_pfnQERPlug_Init; + PFN_QERPLUG_GETNAME m_pfnQERPlug_GetName; + PFN_QERPLUG_GETCOMMANDLIST m_pfnQERPlug_GetCommandList; + PFN_QERPLUG_GETCOMMANDTITLELIST m_pfnQERPlug_GetCommandTitleList; + PFN_QERPLUG_DISPATCH m_pfnQERPlug_Dispatch; +}; + +template +class Modules; +typedef Modules<_QERPluginTable> PluginModules; + +template +class ModulesRef; +typedef ModulesRef<_QERPluginTable> PluginModulesRef; + +#endif diff --git a/include/ireference.cpp b/include/ireference.cpp new file mode 100644 index 00000000..844e95be --- /dev/null +++ b/include/ireference.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ireference.h" + diff --git a/include/ireference.h b/include/ireference.h new file mode 100644 index 00000000..e97df794 --- /dev/null +++ b/include/ireference.h @@ -0,0 +1,78 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IREFERENCE_H) +#define INCLUDED_IREFERENCE_H + +#include "generic/constant.h" + +namespace scene +{ + class Node; +} + +class ModuleObserver; + +class Resource +{ +public: + virtual bool load() = 0; + virtual bool save() = 0; + virtual void flush() = 0; + virtual void refresh() = 0; + virtual scene::Node* getNode() = 0; + virtual void setNode(scene::Node* node) = 0; + virtual void attach(ModuleObserver& observer) = 0; + virtual void detach(ModuleObserver& observer) = 0; + virtual void realise() = 0; + virtual void unrealise() = 0; +}; + +class EntityCreator; + +class ReferenceCache +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "reference"); + + virtual Resource* capture(const char* path) = 0; + virtual void release(const char* path) = 0; + + virtual void setEntityCreator(EntityCreator& entityCreator) = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalReferenceModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalReferenceModuleRef; + +inline ReferenceCache& GlobalReferenceCache() +{ + return GlobalReferenceModule::getTable(); +} + +#endif diff --git a/include/irender.cpp b/include/irender.cpp new file mode 100644 index 00000000..c0fec667 --- /dev/null +++ b/include/irender.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "irender.h" + diff --git a/include/irender.h b/include/irender.h new file mode 100644 index 00000000..d9c40b6f --- /dev/null +++ b/include/irender.h @@ -0,0 +1,177 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IRENDER_H) +#define INCLUDED_IRENDER_H + +#include "generic/constant.h" +#include "generic/callbackfwd.h" + + +// Rendering states to sort by. +// Higher bits have the most effect - slowest state changes should be highest. + +const unsigned int RENDER_DEFAULT = 0; +const unsigned int RENDER_LINESTIPPLE = 1 << 0; // glEnable(GL_LINE_STIPPLE) +const unsigned int RENDER_LINESMOOTH = 1 << 1; // glEnable(GL_LINE_SMOOTH) +const unsigned int RENDER_POLYGONSTIPPLE = 1 << 2; // glEnable(GL_POLYGON_STIPPLE) +const unsigned int RENDER_POLYGONSMOOTH = 1 << 3; // glEnable(GL_POLYGON_SMOOTH) +const unsigned int RENDER_ALPHATEST = 1 << 4; // glEnable(GL_ALPHA_TEST) +const unsigned int RENDER_DEPTHTEST = 1 << 5; // glEnable(GL_DEPTH_TEST) +const unsigned int RENDER_DEPTHWRITE = 1 << 6; // glDepthMask(GL_TRUE) +const unsigned int RENDER_COLOURWRITE = 1 << 7; // glColorMask(GL_TRUE; GL_TRUE; GL_TRUE; GL_TRUE) +const unsigned int RENDER_CULLFACE = 1 << 8; // glglEnable(GL_CULL_FACE) +const unsigned int RENDER_SCALED = 1 << 9; // glEnable(GL_NORMALIZE) +const unsigned int RENDER_SMOOTH = 1 << 10; // glShadeModel +const unsigned int RENDER_FOG = 1 << 11; // glEnable(GL_FOG) +const unsigned int RENDER_LIGHTING = 1 << 12; // glEnable(GL_LIGHTING) +const unsigned int RENDER_BLEND = 1 << 13; // glEnable(GL_BLEND) +const unsigned int RENDER_OFFSETLINE = 1 << 14; // glEnable(GL_POLYGON_OFFSET_LINE) +const unsigned int RENDER_FILL = 1 << 15; // glPolygonMode +const unsigned int RENDER_COLOURARRAY = 1 << 16; // glEnableClientState(GL_COLOR_ARRAY) +const unsigned int RENDER_COLOURCHANGE = 1 << 17; // render() is allowed to call glColor*() +const unsigned int RENDER_TEXTURE = 1 << 18; // glEnable(GL_TEXTURE_2D) +const unsigned int RENDER_BUMP = 1 << 19; +const unsigned int RENDER_PROGRAM = 1 << 20; +const unsigned int RENDER_SCREEN = 1 << 21; +const unsigned int RENDER_OVERRIDE = 1 << 22; +typedef unsigned int RenderStateFlags; + + +class AABB; +class Matrix4; + +template class BasicVector3; +typedef BasicVector3 Vector3; + +class Shader; + +class RendererLight +{ +public: + virtual Shader* getShader() const = 0; + virtual const AABB& aabb() const = 0; + virtual bool testAABB(const AABB& other) const = 0; + virtual const Matrix4& rotation() const = 0; + virtual const Vector3& offset() const = 0; + virtual const Vector3& colour() const = 0; + virtual bool isProjected() const = 0; + virtual const Matrix4& projection() const = 0; +}; + +class LightCullable +{ +public: + virtual bool testLight(const RendererLight& light) const = 0; + virtual void insertLight(const RendererLight& light) + { + } + virtual void clearLights() + { + } +}; + +class Renderable; +typedef Callback1 RenderableCallback; + +typedef Callback1 RendererLightCallback; + +class LightList +{ +public: + virtual void evaluateLights() const = 0; + virtual void lightsChanged() const = 0; + virtual void forEachLight(const RendererLightCallback& callback) const = 0; +}; + +const int c_attr_TexCoord0 = 1; +const int c_attr_Tangent = 3; +const int c_attr_Binormal = 4; + +class OpenGLRenderable +{ +public: + virtual void render(RenderStateFlags state) const = 0; +}; + +class Matrix4; +struct qtexture_t; +class ModuleObserver; + +#include "generic/vector.h" + +class Shader +{ +public: + virtual void addRenderable(const OpenGLRenderable& renderable, const Matrix4& modelview, const LightList* lights = 0) = 0; + virtual void incrementUsed() = 0; + virtual void decrementUsed() = 0; + virtual void attach(ModuleObserver& observer) = 0; + virtual void detach(ModuleObserver& observer) = 0; + virtual qtexture_t& getTexture() const = 0; + virtual unsigned int getFlags() const = 0; +}; + +class ShaderCache +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "renderstate"); + + virtual Shader* capture(const char* name) = 0; + virtual void release(const char* name) = 0; + /*! Render all Shader objects. */ + virtual void render(RenderStateFlags globalstate, const Matrix4& modelview, const Matrix4& projection, const Vector3& viewer = Vector3(0, 0, 0)) = 0; + + virtual void realise() = 0; + virtual void unrealise() = 0; + + virtual bool lightingSupported() const = 0; + virtual bool useShaderLanguage() const = 0; + + virtual const LightList& attach(LightCullable& cullable) = 0; + virtual void detach(LightCullable& cullable) = 0; + virtual void changed(LightCullable& cullable) = 0; + virtual void attach(RendererLight& light) = 0; + virtual void detach(RendererLight& light) = 0; + virtual void changed(RendererLight& light) = 0; + + virtual void attachRenderable(const Renderable& renderable) = 0; + virtual void detachRenderable(const Renderable& renderable) = 0; + virtual void forEachRenderable(const RenderableCallback& callback) const = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalShaderCacheModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalShaderCacheModuleRef; + +inline ShaderCache& GlobalShaderCache() +{ + return GlobalShaderCacheModule::getTable(); +} + +#endif diff --git a/include/iscenegraph.cpp b/include/iscenegraph.cpp new file mode 100644 index 00000000..6b7b450e --- /dev/null +++ b/include/iscenegraph.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "iscenegraph.h" + diff --git a/include/iscenegraph.h b/include/iscenegraph.h new file mode 100644 index 00000000..18e50f77 --- /dev/null +++ b/include/iscenegraph.h @@ -0,0 +1,221 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_ISCENEGRAPH_H) +#define INCLUDED_ISCENEGRAPH_H + +#include +#include "generic/constant.h" +#include "signal/signalfwd.h" + +template +class Stack; +template +class Reference; + +namespace scene +{ + class Instance; + const Instance* const nullInstancePointer = 0; + inline const Instance& nullInstance() + { + return *nullInstancePointer; + } + + class Node; + const Node* const nullNodePointer = 0; + inline const Node& nullNode() + { + return *nullNodePointer; + } +} + +typedef Reference NodeReference; + +typedef std::size_t TypeId; + +const TypeId NODETYPEID_MAX = 64; +const TypeId NODETYPEID_NONE = NODETYPEID_MAX; + +const TypeId INSTANCETYPEID_MAX = 64; +const TypeId INSTANCETYPEID_NONE = INSTANCETYPEID_MAX; + +namespace scene +{ + /// \brief A unique key to an instance of a node in the scene-graph. + typedef Stack Path; + + /// \brief A scene-graph - a Directed Acyclic Graph (DAG). + /// + /// - Each node may refer to zero or more 'child' nodes (directed). + /// - A node may never have itself as one of its ancestors (acyclic). + /// - Each node may have more than one 'parent', thus having more than one 'instance' in the graph. + /// - Each instance is uniquely identified by the list of its ancestors plus itself, known as a 'path'. + class Graph + { + public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "scenegraph"); + + class Walker + { + public: + /// \brief Called before traversing the first child-instance of 'instance'. If the return value is false, the children of the current instance are not traversed. + virtual bool pre(const Path& path, Instance& instance) const = 0; + /// \brief Called after traversing the last child-instance of 'instance'. + virtual void post(const Path& path, Instance& instance) const + { + } + }; + + /// \brief Returns the root-node of the graph. + virtual Node& root() = 0; + /// \brief Sets the root-node of the graph to be 'node'. + virtual void insert_root(Node& root) = 0; + /// \brief Clears the root-node of the graph. + virtual void erase_root() = 0; + /// \brief Traverses all nodes in the graph depth-first, starting from the root node. + virtual void traverse(const Walker& walker) = 0; + /// \brief Traverses all nodes in the graph depth-first, starting from 'start'. + virtual void traverse_subgraph(const Walker& walker, const Path& start) = 0; + /// \brief Returns the instance at the location identified by 'path', or 0 if it does not exist. + virtual scene::Instance* find(const Path& path) = 0; + + /// \brief Invokes all scene-changed callbacks. Called when any part of the scene changes the way it will appear when the scene is rendered. + /// \todo Move to a separate class. + virtual void sceneChanged() = 0; + /// \brief Add a \p callback to be invoked when the scene changes. + /// \todo Move to a separate class. + virtual void addSceneChangedCallback(const SignalHandler& handler) = 0; + + /// \brief Invokes all bounds-changed callbacks. Called when the bounds of any instance in the scene change. + /// \todo Move to a separate class. + virtual void boundsChanged() = 0; + /// \brief Add a \p callback to be invoked when the bounds of any instance in the scene change. + virtual SignalHandlerId addBoundsChangedCallback(const SignalHandler& boundsChanged) = 0; + /// \brief Remove a \p callback to be invoked when the bounds of any instance in the scene change. + virtual void removeBoundsChangedCallback(SignalHandlerId id) = 0; + + virtual TypeId getNodeTypeId(const char* name) = 0; + virtual TypeId getInstanceTypeId(const char* name) = 0; + }; + + class Traversable + { + public: + STRING_CONSTANT(Name, "scene::Traversable"); + + class Observer + { + public: + /// \brief Called when a node is added to the container. + virtual void insert(Node& node) = 0; + /// \brief Called when a node is removed from the container. + virtual void erase(Node& node) = 0; + }; + + class Walker + { + public: + /// \brief Called before traversing the first child-node of 'node'. If the return value is false, the children of the current node are not traversed. + virtual bool pre(Node& node) const = 0; + /// \brief Called after traversing the last child-node of 'node'. + virtual void post(Node& node) const + { + } + }; + /// \brief Adds a node to the container. + virtual void insert(Node& node) = 0; + /// \brief Removes a node from the container. + virtual void erase(Node& node) = 0; + /// \brief Traverses the subgraphs rooted at each node in the container, depth-first. + virtual void traverse(const Walker& walker) = 0; + /// \brief Returns true if the container contains no nodes. + virtual bool empty() const = 0; + }; + + class Instantiable + { + public: + STRING_CONSTANT(Name, "scene::Instantiable"); + + class Observer + { + public: + /// \brief Called when an instance is added to the container. + virtual void insert(scene::Instance* instance) = 0; + /// \brief Called when an instance is removed from the container. + virtual void erase(scene::Instance* instance) = 0; + }; + + class Visitor + { + public: + virtual void visit(Instance& instance) const = 0; + }; + + /// \brief Returns a new instance uniquely identified by 'path'. + virtual scene::Instance* create(const scene::Path& path, scene::Instance* parent) = 0; + /// \brief Calls Visitor::visit(instance) for each instance in the container. + virtual void forEachInstance(const Visitor& visitor) = 0; + /// \brief Adds an instance to the container. + virtual void insert(Observer* observer, const Path& path, scene::Instance* instance) = 0; + /// \brief Returns an instance removed from the container. + virtual scene::Instance* erase(Observer* observer, const Path& path) = 0; + }; + + class Cloneable + { + public: + STRING_CONSTANT(Name, "scene::Cloneable"); + + /// \brief Returns a copy of itself. + virtual scene::Node& clone() const = 0; + }; +} + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalSceneGraphModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalSceneGraphModuleRef; + +inline scene::Graph& GlobalSceneGraph() +{ + return GlobalSceneGraphModule::getTable(); +} + +inline void AddSceneChangeCallback(const SignalHandler& handler) +{ + GlobalSceneGraph().addSceneChangedCallback(handler); +} +inline void SceneChangeNotify() +{ + GlobalSceneGraph().sceneChanged(); +} + + + +#endif diff --git a/include/iscriplib.cpp b/include/iscriplib.cpp new file mode 100644 index 00000000..4de7c3f9 --- /dev/null +++ b/include/iscriplib.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "iscriplib.h" + diff --git a/include/iscriplib.h b/include/iscriplib.h new file mode 100644 index 00000000..98d85c27 --- /dev/null +++ b/include/iscriplib.h @@ -0,0 +1,85 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ISCRIPLIB_H) +#define INCLUDED_ISCRIPLIB_H + +/// \file iscriplib.h +/// \brief Token input/output stream module. + +#include +#include "generic/constant.h" + +#define MAXTOKEN 1024 + +class Tokeniser +{ +public: + virtual void release() = 0; + virtual void nextLine() = 0; + virtual const char* getToken() = 0; + virtual void ungetToken() = 0; + virtual std::size_t getLine() const = 0; + virtual std::size_t getColumn() const = 0; +}; + +class TextInputStream; + +class TokenWriter +{ +public: + virtual void release() = 0; + virtual void nextLine() = 0; + virtual void writeToken(const char* token) = 0; + virtual void writeString(const char* string) = 0; + virtual void writeInteger(int i) = 0; + virtual void writeUnsigned(std::size_t i) = 0; + virtual void writeFloat(double f) = 0; +}; + +class TextOutputStream; + +struct _QERScripLibTable +{ + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "scriptlib"); + + Tokeniser& (* m_pfnNewScriptTokeniser)(TextInputStream& istream); + Tokeniser& (* m_pfnNewSimpleTokeniser)(TextInputStream& istream); + TokenWriter& (* m_pfnNewSimpleTokenWriter)(TextOutputStream& ostream); +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule<_QERScripLibTable> GlobalScripLibModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef<_QERScripLibTable> GlobalScripLibModuleRef; + +inline _QERScripLibTable& GlobalScriptLibrary() +{ + return GlobalScripLibModule::getTable(); +} + +#endif diff --git a/include/iselection.cpp b/include/iselection.cpp new file mode 100644 index 00000000..2438d313 --- /dev/null +++ b/include/iselection.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "iselection.h" + diff --git a/include/iselection.h b/include/iselection.h new file mode 100644 index 00000000..ec32561e --- /dev/null +++ b/include/iselection.h @@ -0,0 +1,145 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ISELECTION_H) +#define INCLUDED_ISELECTION_H + +#include +#include "generic/constant.h" +#include "generic/callbackfwd.h" +#include "signal/signalfwd.h" + +class Renderer; +class View; + +class Selectable +{ +public: + STRING_CONSTANT(Name, "Selectable"); + + virtual void setSelected(bool select) = 0; + virtual bool isSelected() const = 0; +}; + +namespace scene +{ + class Instance; +}; + +class InstanceSelectionObserver +{ +public: + virtual void onSelectedChanged(scene::Instance& instance) = 0; +}; + +template class BasicVector3; +typedef BasicVector3 Vector3; +template class BasicVector4; +typedef BasicVector4 Vector4; +class Matrix4; +typedef Vector4 Quaternion; + +typedef Callback1 SelectionChangeCallback; +typedef SignalHandler1 SelectionChangeHandler; + +class SelectionSystem +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "selection"); + + enum EMode + { + eEntity, + ePrimitive, + eComponent, + }; + + enum EComponentMode + { + eDefault, + eVertex, + eEdge, + eFace, + }; + + enum EManipulatorMode + { + eTranslate, + eRotate, + eScale, + eDrag, + eClip, + }; + + virtual void SetMode(EMode mode) = 0; + virtual EMode Mode() const = 0; + virtual void SetComponentMode(EComponentMode mode) = 0; + virtual EComponentMode ComponentMode() const = 0; + virtual void SetManipulatorMode(EManipulatorMode mode) = 0; + virtual EManipulatorMode ManipulatorMode() const = 0; + + virtual SelectionChangeCallback getObserver(EMode mode) = 0; + virtual std::size_t countSelected() const = 0; + virtual std::size_t countSelectedComponents() const = 0; + virtual void onSelectedChanged(scene::Instance& instance, const Selectable& selectable) = 0; + virtual void onComponentSelection(scene::Instance& instance, const Selectable& selectable) = 0; + virtual scene::Instance& ultimateSelected() const = 0; + virtual scene::Instance& penultimateSelected() const = 0; + virtual void setSelectedAll(bool selected) = 0; + virtual void setSelectedAllComponents(bool selected) = 0; + + class Visitor + { + public: + virtual void visit(scene::Instance& instance) const = 0; + }; + virtual void foreachSelected(const Visitor& visitor) const = 0; + virtual void foreachSelectedComponent(const Visitor& visitor) const = 0; + + virtual void addSelectionChangeCallback(const SelectionChangeHandler& handler) = 0; + + virtual void NudgeManipulator(const Vector3& nudge, const Vector3& view) = 0; + + virtual void translateSelected(const Vector3& translation) = 0; + virtual void rotateSelected(const Quaternion& rotation) = 0; + virtual void scaleSelected(const Vector3& scaling) = 0; + + virtual void pivotChanged() const = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalSelectionModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalSelectionModuleRef; + +inline SelectionSystem& GlobalSelectionSystem() +{ + return GlobalSelectionModule::getTable(); +} + + +#endif diff --git a/include/ishaders.cpp b/include/ishaders.cpp new file mode 100644 index 00000000..bf380198 --- /dev/null +++ b/include/ishaders.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "ishaders.h" + diff --git a/include/ishaders.h b/include/ishaders.h new file mode 100644 index 00000000..928dd1de --- /dev/null +++ b/include/ishaders.h @@ -0,0 +1,195 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ISHADERS_H) +#define INCLUDED_ISHADERS_H + +#include "generic/constant.h" +#include "generic/callbackfwd.h" + +enum +{ + QER_TRANS = 1 << 0, + QER_NOCARVE = 1 << 1, + QER_NODRAW = 1 << 2, + QER_NONSOLID = 1 << 3, + QER_WATER = 1 << 4, + QER_LAVA = 1 << 5, + QER_FOG = 1 << 6, + QER_ALPHATEST = 1 << 7, + QER_CULL = 1 << 8, + QER_AREAPORTAL = 1 << 9, + QER_CLIP = 1 << 10, + QER_BOTCLIP = 1 << 11, +}; + +struct qtexture_t; + +template class BasicVector3; +typedef BasicVector3 Vector3; +typedef Vector3 Colour3; + +typedef unsigned char BlendFactor; +const BlendFactor BLEND_ZERO = 0; +const BlendFactor BLEND_ONE = 1; +const BlendFactor BLEND_SRC_COLOUR = 2; +const BlendFactor BLEND_ONE_MINUS_SRC_COLOUR = 3; +const BlendFactor BLEND_SRC_ALPHA = 4; +const BlendFactor BLEND_ONE_MINUS_SRC_ALPHA = 5; +const BlendFactor BLEND_DST_COLOUR = 6; +const BlendFactor BLEND_ONE_MINUS_DST_COLOUR = 7; +const BlendFactor BLEND_DST_ALPHA = 8; +const BlendFactor BLEND_ONE_MINUS_DST_ALPHA = 9; +const BlendFactor BLEND_SRC_ALPHA_SATURATE = 10; + +class BlendFunc +{ +public: + BlendFunc(BlendFactor src, BlendFactor dst) : m_src(src), m_dst(dst) + { + } + BlendFactor m_src; + BlendFactor m_dst; +}; + +class ShaderLayer +{ +public: + virtual qtexture_t* texture() const = 0; + virtual BlendFunc blendFunc() const = 0; + virtual bool clampToBorder() const = 0; + virtual float alphaTest() const = 0; +}; + +typedef Callback1 ShaderLayerCallback; + + +class IShader +{ +public: + enum EAlphaFunc + { + eAlways, + eEqual, + eLess, + eGreater, + eLEqual, + eGEqual, + }; + enum ECull + { + eCullNone, + eCullBack, + }; + // Increment the number of references to this object + virtual void IncRef() = 0; + // Decrement the reference count + virtual void DecRef() = 0; + // get/set the qtexture_t* Radiant uses to represent this shader object + virtual qtexture_t* getTexture() const = 0; + virtual qtexture_t* getDiffuse() const = 0; + virtual qtexture_t* getBump() const = 0; + virtual qtexture_t* getSpecular() const = 0; + // get shader name + virtual const char* getName() const = 0; + virtual bool IsInUse() const = 0; + virtual void SetInUse(bool bInUse) = 0; + // get the editor flags (QER_NOCARVE QER_TRANS) + virtual int getFlags() const = 0; + // get the transparency value + virtual float getTrans() const = 0; + // test if it's a true shader, or a default shader created to wrap around a texture + virtual bool IsDefault() const = 0; + // get the alphaFunc + virtual void getAlphaFunc(EAlphaFunc *func, float *ref) = 0; + virtual BlendFunc getBlendFunc() const = 0; + // get the cull type + virtual ECull getCull() = 0; + // get shader file name (ie the file where this one is defined) + virtual const char* getShaderFileName() const = 0; + + virtual const ShaderLayer* firstLayer() const = 0; + virtual void forEachLayer(const ShaderLayerCallback& layer) const = 0; + + virtual qtexture_t* lightFalloffImage() const = 0; +}; + +typedef struct _GSList GSList; +typedef Callback1 ShaderNameCallback; + +class ModuleObserver; + +class ShaderSystem +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "shaders"); + // NOTE: shader and texture names used must be full path. + // Shaders usable as textures have prefix equal to getTexturePrefix() + + virtual void realise() = 0; + virtual void unrealise() = 0; + virtual void refresh() = 0; + // activate the shader for a given name and return it + // will return the default shader if name is not found + virtual IShader* getShaderForName(const char* name) = 0; + + virtual void foreachShaderName(const ShaderNameCallback& callback) = 0; + + // iterate over the list of active shaders + virtual void beginActiveShadersIterator() = 0; + virtual bool endActiveShadersIterator() = 0; + virtual IShader* dereferenceActiveShadersIterator() = 0; + virtual void incrementActiveShadersIterator() = 0; + + virtual void setActiveShadersChangedNotify(const Callback& notify) = 0; + + virtual void attach(ModuleObserver& observer) = 0; + virtual void detach(ModuleObserver& observer) = 0; + + virtual void setLightingEnabled(bool enabled) = 0; + + virtual const char* getTexturePrefix() const = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalShadersModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalShadersModuleRef; + +inline ShaderSystem& GlobalShaderSystem() +{ + return GlobalShadersModule::getTable(); +} + + +#define QERApp_Shader_ForName GlobalShaderSystem().getShaderForName +#define QERApp_ActiveShaders_IteratorBegin GlobalShaderSystem().beginActiveShadersIterator +#define QERApp_ActiveShaders_IteratorAtEnd GlobalShaderSystem().endActiveShadersIterator +#define QERApp_ActiveShaders_IteratorCurrent GlobalShaderSystem().dereferenceActiveShadersIterator +#define QERApp_ActiveShaders_IteratorIncrement GlobalShaderSystem().incrementActiveShadersIterator + +#endif diff --git a/include/itexdef.cpp b/include/itexdef.cpp new file mode 100644 index 00000000..1973c960 --- /dev/null +++ b/include/itexdef.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "itexdef.h" + diff --git a/include/itexdef.h b/include/itexdef.h new file mode 100644 index 00000000..a299843b --- /dev/null +++ b/include/itexdef.h @@ -0,0 +1,42 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_ITEXDEF_H) +#define INCLUDED_ITEXDEF_H + +class texdef_t +{ +public: + float shift[2]; + float rotate; + float scale[2]; + + texdef_t() + { + shift[0] = 0; + shift[1] = 0; + rotate = 0; + scale[0] = 1; + scale[1] = 1; + } +}; + +#endif diff --git a/include/itextstream.cpp b/include/itextstream.cpp new file mode 100644 index 00000000..c93ae35a --- /dev/null +++ b/include/itextstream.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "itextstream.h" + diff --git a/include/itextstream.h b/include/itextstream.h new file mode 100644 index 00000000..891336e7 --- /dev/null +++ b/include/itextstream.h @@ -0,0 +1,114 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ITEXTSTREAM_H) +#define INCLUDED_ITEXTSTREAM_H + +/// \file +/// \brief Text-stream interfaces. + +#include +#include "generic/static.h" + +/// \brief A read-only character-stream. +class TextInputStream +{ +public: + /// \brief Attempts to read the next \p length characters from the stream to \p buffer. + /// Returns the number of characters actually stored in \p buffer. + virtual std::size_t read(char* buffer, std::size_t length) = 0; +}; + +/// \brief A write-only character-stream. +class TextOutputStream +{ +public: + /// \brief Attempts to write \p length characters to the stream from \p buffer. + /// Returns the number of characters actually read from \p buffer. + virtual std::size_t write(const char* buffer, std::size_t length) = 0; +}; + +/// \brief Calls the overloaded function ostream_write() to perform text formatting specific to the type being written. +/*! Note that ostream_write() is not globally defined - it must be defined once for each type supported.\n +To support writing a custom type MyClass to any kind of text-output-stream with operator<<: +\code +template +TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const MyClass& myClass) +{ + return ostream << myClass.getName() << ' ' << myClass.getText(); +} +\endcode +Expressing this as a template allows it to be used directly with any concrete text-output-stream type, not just the abstract TextOutputStream\n +\n +This overload writes a single character to any text-output-stream - ostream_write(TextOutputStreamType& ostream, char c). +*/ +template +inline TextOutputStream& operator<<(TextOutputStream& ostream, const T& t) +{ + return ostream_write(ostream, t); +} + +class NullOutputStream : public TextOutputStream +{ +public: + std::size_t write(const char*, std::size_t length) + { + return length; + } +}; + +class OutputStreamHolder +{ + NullOutputStream m_nullOutputStream; + TextOutputStream* m_outputStream; +public: + OutputStreamHolder() + : m_outputStream(&m_nullOutputStream) + { + } + void setOutputStream(TextOutputStream& outputStream) + { + m_outputStream = &outputStream; + } + TextOutputStream& getOutputStream() + { + return *m_outputStream; + } +}; + +typedef Static GlobalOutputStream; + +/// \brief Returns the global output stream. Used to display messages to the user. +inline TextOutputStream& globalOutputStream() +{ + return GlobalOutputStream::instance().getOutputStream(); +} + +class ErrorStreamHolder : public OutputStreamHolder {}; +typedef Static GlobalErrorStream; + +/// \brief Returns the global error stream. Used to display error messages to the user. +inline TextOutputStream& globalErrorStream() +{ + return GlobalErrorStream::instance().getOutputStream(); +} + +#endif diff --git a/include/itextures.cpp b/include/itextures.cpp new file mode 100644 index 00000000..26e2bf6d --- /dev/null +++ b/include/itextures.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "itextures.h" + diff --git a/include/itextures.h b/include/itextures.h new file mode 100644 index 00000000..368e7b76 --- /dev/null +++ b/include/itextures.h @@ -0,0 +1,92 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ITEXTURES_H) +#define INCLUDED_ITEXTURES_H + +#include "iimage.h" +#include "generic/constant.h" + +struct qtexture_t; + +class LoadImageCallback +{ + typedef Image* (*LoadFunc)(void* environment, const char* name); +public: + void* m_environment; + LoadFunc m_func; + + LoadImageCallback(void* environment, LoadFunc func) : m_environment(environment), m_func(func) + { + } + Image* loadImage(const char* name) const + { + return m_func(m_environment, name); + } +}; + +inline bool operator==(const LoadImageCallback& self, const LoadImageCallback& other) +{ + return self.m_environment == other.m_environment && self.m_func == other.m_func; +} +inline bool operator<(const LoadImageCallback& self, const LoadImageCallback& other) +{ + return self.m_environment < other.m_environment || + (!(other.m_environment < self.m_environment) && self.m_func < other.m_func); +} + +class TexturesCacheObserver +{ +public: + virtual void unrealise() = 0; + virtual void realise() = 0; +}; + +class TexturesCache +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "textures"); + virtual LoadImageCallback defaultLoader() const = 0; + virtual Image* loadImage(const char* name) = 0; + virtual qtexture_t* capture(const char* name) = 0; + virtual qtexture_t* capture(const LoadImageCallback& load, const char* name) = 0; + virtual void release(qtexture_t* texture) = 0; + virtual void attach(TexturesCacheObserver& observer) = 0; + virtual void detach(TexturesCacheObserver& observer) = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalTexturesModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalTexturesModuleRef; + +inline TexturesCache& GlobalTexturesCache() +{ + return GlobalTexturesModule::getTable(); +} + +#endif diff --git a/include/itoolbar.cpp b/include/itoolbar.cpp new file mode 100644 index 00000000..2c989b6a --- /dev/null +++ b/include/itoolbar.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "itoolbar.h" + diff --git a/include/itoolbar.h b/include/itoolbar.h new file mode 100644 index 00000000..350e5a5f --- /dev/null +++ b/include/itoolbar.h @@ -0,0 +1,66 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IPLUGTOOLBAR_H) +#define INCLUDED_IPLUGTOOLBAR_H + +#include +#include "generic/constant.h" + +class IToolbarButton +{ +public: + enum EType + { + eSpace, + eButton, + eToggleButton, + eRadioButton, + }; + + virtual const char* getImage() const = 0; + virtual const char* getText() const = 0; + virtual const char* getTooltip() const = 0; + virtual EType getType() const = 0; + virtual void activate() const = 0; +}; + +typedef std::size_t (* PFN_TOOLBARBUTTONCOUNT)(); +typedef const IToolbarButton* (* PFN_GETTOOLBARBUTTON)(std::size_t index); + +struct _QERPlugToolbarTable +{ + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "toolbar"); + + PFN_TOOLBARBUTTONCOUNT m_pfnToolbarButtonCount; + PFN_GETTOOLBARBUTTON m_pfnGetToolbarButton; +}; + +template +class Modules; +typedef Modules<_QERPlugToolbarTable> ToolbarModules; + +template +class ModulesRef; +typedef ModulesRef<_QERPlugToolbarTable> ToolbarModulesRef; + +#endif diff --git a/include/iundo.cpp b/include/iundo.cpp new file mode 100644 index 00000000..49b96b03 --- /dev/null +++ b/include/iundo.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "iundo.h" + diff --git a/include/iundo.h b/include/iundo.h new file mode 100644 index 00000000..4dcbad85 --- /dev/null +++ b/include/iundo.h @@ -0,0 +1,110 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IUNDO_H) +#define INCLUDED_IUNDO_H + +/// \file +/// \brief The undo-system interface. Uses the 'memento' pattern. + +#include +#include "generic/constant.h" +#include "generic/callbackfwd.h" + +class UndoMemento +{ +public: + virtual void release() = 0; +}; + +class Undoable +{ +public: + virtual UndoMemento* exportState() const = 0; + virtual void importState(const UndoMemento* state) = 0; +}; + +class UndoObserver +{ +public: + virtual void save(Undoable* undoable) = 0; +}; + +class UndoTracker +{ +public: + virtual void clear() = 0; + virtual void begin() = 0; + virtual void undo() = 0; + virtual void redo() = 0; +}; + +class UndoSystem +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "undo"); + + virtual UndoObserver* observer(Undoable* undoable) = 0; + virtual void release(Undoable* undoable) = 0; + + virtual std::size_t size() const = 0; + virtual void start() = 0; + virtual void finish(const char* command) = 0; + virtual void undo() = 0; + virtual void redo() = 0; + virtual void clear() = 0; + + virtual void trackerAttach(UndoTracker& tracker) = 0; + virtual void trackerDetach(UndoTracker& tracker) = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalUndoModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalUndoModuleRef; + +inline UndoSystem& GlobalUndoSystem() +{ + return GlobalUndoModule::getTable(); +} + +class UndoableCommand +{ + const char* m_command; +public: + UndoableCommand(const char* command) : m_command(command) + { + GlobalUndoSystem().start(); + } + ~UndoableCommand() + { + GlobalUndoSystem().finish(m_command); + } +}; + + +#endif diff --git a/include/mapfile.cpp b/include/mapfile.cpp new file mode 100644 index 00000000..1065f591 --- /dev/null +++ b/include/mapfile.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "mapfile.h" + diff --git a/include/mapfile.h b/include/mapfile.h new file mode 100644 index 00000000..e53d79c1 --- /dev/null +++ b/include/mapfile.h @@ -0,0 +1,76 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MAPFILE_H) +#define INCLUDED_MAPFILE_H + +#include + +#include "iscenegraph.h" +#include "generic/callbackfwd.h" + +const std::size_t MAPFILE_MAX_CHANGES = std::numeric_limits::max(); + +class MapFile +{ +public: + STRING_CONSTANT(Name, "MapFile"); + + virtual void save() = 0; + virtual bool saved() const = 0; + virtual void changed() = 0; + virtual void setChangedCallback(const Callback& changed) = 0; + virtual std::size_t changes() const = 0; +}; + +#include "scenelib.h" + +inline MapFile* Node_getMapFile(scene::Node& node) +{ + return NodeTypeCast::cast(node); +} + +template +inline MapFile* path_find_mapfile(Iterator first, Iterator last) +{ + Iterator i = last; + for(;;) + { + --i; + + MapFile* map = Node_getMapFile(*i); + if(map != 0) + { + return map; + } + + if(i == first) + { + break; + } + } + ERROR_MESSAGE("failed to find parent mapfile for path"); + return 0; +} + + + +#endif diff --git a/include/modelskin.cpp b/include/modelskin.cpp new file mode 100644 index 00000000..6d34f272 --- /dev/null +++ b/include/modelskin.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "modelskin.h" + diff --git a/include/modelskin.h b/include/modelskin.h new file mode 100644 index 00000000..c01b5f30 --- /dev/null +++ b/include/modelskin.h @@ -0,0 +1,92 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MODELSKIN_H) +#define INCLUDED_MODELSKIN_H + +#include "generic/constant.h" +#include "generic/callbackfwd.h" + +class SkinRemap +{ +public: + const char* m_from; + const char* m_to; + SkinRemap(const char* from, const char* to) : m_from(from), m_to(to) + { + } +}; + +typedef Callback1 SkinRemapCallback; +class ModuleObserver; + +class ModelSkin +{ +public: + STRING_CONSTANT(Name, "ModelSkin"); + /// \brief Attach an \p observer whose realise() and unrealise() methods will be called when the skin is loaded or unloaded. + virtual void attach(ModuleObserver& observer) = 0; + /// \brief Detach an \p observer previously-attached by calling \c attach. + virtual void detach(ModuleObserver& observer) = 0; + /// \brief Returns true if this skin is currently loaded. + virtual bool realised() const = 0; + /// \brief Returns the shader identifier that \p name remaps to, or "" if not found or not realised. + virtual const char* getRemap(const char* name) const = 0; + /// \brief Calls \p callback for each remap pair. Has no effect if not realised. + virtual void forEachRemap(const SkinRemapCallback& callback) const = 0; +}; + +class SkinnedModel +{ +public: + STRING_CONSTANT(Name, "SkinnedModel"); + /// \brief Instructs the skinned model to update its skin. + virtual void skinChanged() = 0; +}; + +class ModelSkinCache +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "modelskin"); + /// \brief Increments the reference count of and returns a reference to the skin uniquely identified by 'name'. + virtual ModelSkin& capture(const char* name) = 0; + /// \brief Decrements the reference-count of the skin uniquely identified by 'name'. + virtual void release(const char* name) = 0; +}; + + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalModelSkinCacheModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalModelSkinCacheModuleRef; + +inline ModelSkinCache& GlobalModelSkinCache() +{ + return GlobalModelSkinCacheModule::getTable(); +} + +#endif diff --git a/include/moduleobserver.cpp b/include/moduleobserver.cpp new file mode 100644 index 00000000..fa26f8d9 --- /dev/null +++ b/include/moduleobserver.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "moduleobserver.h" + diff --git a/include/moduleobserver.h b/include/moduleobserver.h new file mode 100644 index 00000000..e218b4aa --- /dev/null +++ b/include/moduleobserver.h @@ -0,0 +1,32 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MODULEOBSERVER_H) +#define INCLUDED_MODULEOBSERVER_H + +class ModuleObserver +{ +public: + virtual void unrealise() = 0; + virtual void realise() = 0; +}; + +#endif diff --git a/include/modulesystem.cpp b/include/modulesystem.cpp new file mode 100644 index 00000000..6b7a27c9 --- /dev/null +++ b/include/modulesystem.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "modulesystem.h" + diff --git a/include/modulesystem.h b/include/modulesystem.h new file mode 100644 index 00000000..dd7c59ad --- /dev/null +++ b/include/modulesystem.h @@ -0,0 +1,258 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MODULESYSTEM_H) +#define INCLUDED_MODULESYSTEM_H + +#include "generic/static.h" +#include "debugging/debugging.h" + +#if defined(WIN32) +#define RADIANT_DLLEXPORT __stdcall +#else +#define RADIANT_DLLEXPORT +#endif + + +class Module +{ +public: + virtual void capture() = 0; + virtual void release() = 0; + virtual void* getTable() = 0; +}; + +inline void* Module_getTable(Module& module) +{ + return module.getTable(); +} + +class TextOutputStream; +class DebugMessageHandler; + +class ModuleServer +{ +public: + class Visitor + { + public: + virtual void visit(const char* name, Module& module) const = 0; + }; + + virtual void setError(bool error) = 0; + virtual bool getError() const = 0; + + virtual TextOutputStream& getOutputStream() = 0; + virtual TextOutputStream& getErrorStream() = 0; + virtual DebugMessageHandler& getDebugMessageHandler() = 0; + + virtual void registerModule(const char* type, int version, const char* name, Module& module) = 0; + virtual Module* findModule(const char* type, int version, const char* name) const = 0; + virtual void foreachModule(const char* type, int version, const Visitor& visitor) = 0; +}; + +class ModuleServerHolder +{ + ModuleServer* m_server; +public: + ModuleServerHolder() + : m_server(0) + { + } + void set(ModuleServer& server) + { + m_server = &server; + } + ModuleServer& get() + { + return *m_server; + } +}; + +typedef Static GlobalModuleServer; + +inline ModuleServer& globalModuleServer() +{ + return GlobalModuleServer::instance().get(); +} + + +inline void initialiseModule(ModuleServer& server) +{ + GlobalErrorStream::instance().setOutputStream(server.getErrorStream()); + GlobalOutputStream::instance().setOutputStream(server.getOutputStream()); + GlobalDebugMessageHandler::instance().setHandler(server.getDebugMessageHandler()); + GlobalModuleServer::instance().set(server); +} + + + +template +class Modules +{ +public: + class Visitor + { + public: + virtual void visit(const char* name, const Type& table) const = 0; + }; + + virtual Type* findModule(const char* name) = 0; + virtual void foreachModule(const Visitor& visitor) = 0; +}; + +#include "debugging/debugging.h" + +template +class ModuleRef +{ + Module* m_module; + Type* m_table; +public: + ModuleRef(const char* name) : m_table(0) + { + if(!globalModuleServer().getError()) + { + m_module = globalModuleServer().findModule(typename Type::Name(), typename Type::Version(), name); + if(m_module == 0) + { + globalModuleServer().setError(true); + globalErrorStream() << "ModuleRef::initialise: type=" << makeQuoted(typename Type::Name()) << " version=" << makeQuoted(typename Type::Version()) << " name=" << makeQuoted(name) << " - not found\n"; + } + else + { + m_module->capture(); + if(!globalModuleServer().getError()) + { + m_table = static_cast(m_module->getTable()); + } + } + } + } + ~ModuleRef() + { + if(m_module != 0) + { + m_module->release(); + } + } + Type* getTable() + { +#if defined(_DEBUG) + ASSERT_MESSAGE(m_table != 0, "ModuleRef::getTable: type=" << makeQuoted(typename Type::Name()) << " version=" << makeQuoted(typename Type::Version()) << " - module-reference used without being initialised"); +#endif + return m_table; + } +}; + +template +class SingletonModuleRef +{ + Module* m_module; + Type* m_table; +public: + + SingletonModuleRef() + : m_module(0), m_table(0) + { + } + + bool initialised() const + { + return m_module != 0; + } + + void initialise(const char* name) + { + m_module = globalModuleServer().findModule(typename Type::Name(), typename Type::Version(), name); + if(m_module == 0) + { + globalModuleServer().setError(true); + globalErrorStream() << "SingletonModuleRef::initialise: type=" << makeQuoted(typename Type::Name()) << " version=" << makeQuoted(typename Type::Version()) << " name=" << makeQuoted(name) << " - not found\n"; + } + } + + Type* getTable() + { +#if defined(_DEBUG) + ASSERT_MESSAGE(m_table != 0, "SingletonModuleRef::getTable: type=" << makeQuoted(typename Type::Name()) << " version=" << makeQuoted(typename Type::Version()) << " - module-reference used without being initialised"); +#endif + return m_table; + } + void capture() + { + if(initialised()) + { + m_module->capture(); + m_table = static_cast(m_module->getTable()); + } + } + void release() + { + if(initialised()) + { + m_module->release(); + } + } +}; + +template +class GlobalModule +{ + static SingletonModuleRef m_instance; +public: + static SingletonModuleRef& instance() + { + return m_instance; + } + static Type& getTable() + { + return *m_instance.getTable(); + } +}; + +template +SingletonModuleRef GlobalModule::m_instance; + + +template +class GlobalModuleRef +{ +public: + GlobalModuleRef(const char* name = "*") + { + if(!globalModuleServer().getError()) + { + GlobalModule::instance().initialise(name); + } + GlobalModule::instance().capture(); + } + ~GlobalModuleRef() + { + GlobalModule::instance().release(); + } + Type& getTable() + { + return GlobalModule::getTable(); + } +}; + +#endif diff --git a/include/nameable.cpp b/include/nameable.cpp new file mode 100644 index 00000000..f401626c --- /dev/null +++ b/include/nameable.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "nameable.h" + diff --git a/include/nameable.h b/include/nameable.h new file mode 100644 index 00000000..8e1357a4 --- /dev/null +++ b/include/nameable.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_NAMEABLE_H) +#define INCLUDED_NAMEABLE_H + +#include "generic/constant.h" +#include "generic/callbackfwd.h" + +typedef Callback1 NameCallback; + +class Nameable +{ +public: + STRING_CONSTANT(Name, "Nameable"); + + virtual const char* name() const = 0; + virtual void attach(const NameCallback& callback) = 0; + virtual void detach(const NameCallback& callback) = 0; +}; + + +#endif diff --git a/include/namespace.cpp b/include/namespace.cpp new file mode 100644 index 00000000..2f322fda --- /dev/null +++ b/include/namespace.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "namespace.h" + diff --git a/include/namespace.h b/include/namespace.h new file mode 100644 index 00000000..30367bfd --- /dev/null +++ b/include/namespace.h @@ -0,0 +1,63 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_NAMESPACE_H) +#define INCLUDED_NAMESPACE_H + +#include "generic/constant.h" +#include "generic/callbackfwd.h" + +typedef Callback1 NameCallback; +typedef Callback1 NameCallbackCallback; + +class Namespace +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "namespace"); + virtual void attach(const NameCallback& setName, const NameCallbackCallback& attachObserver) = 0; + virtual void detach(const NameCallback& setName, const NameCallbackCallback& detachObserver) = 0; + virtual void makeUnique(const char* name, const NameCallback& setName) const = 0; +}; + +class Namespaced +{ +public: + STRING_CONSTANT(Name, "Namespaced"); + + virtual void setNamespace(Namespace& space) = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalNamespaceModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalNamespaceModuleRef; + +inline Namespace& GlobalNamespace() +{ + return GlobalNamespaceModule::getTable(); +} +#endif diff --git a/include/preferencesystem.cpp b/include/preferencesystem.cpp new file mode 100644 index 00000000..a5fecd4d --- /dev/null +++ b/include/preferencesystem.cpp @@ -0,0 +1,206 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if 0 + +#include "preferencesystem.h" +#include "preferencedictionary.h" + +#include "xml/xmlparser.h" +#include "xml/xmlwriter.h" + + +void LoadPrefs(PreferenceDictionary& preferences, const char* filename) +{ + TextFileInputStream file(filename); + if(!file.failed()) + { + XMLStreamParser parser(file); + XMLPreferenceDictionaryImporter importer(preferences); + parser.exportXML(importer); + } + else + { + // error + } +} + +void SavePrefs(PreferenceDictionary& preferences, const char* filename) +{ + TextFileOutputStream file(filename); + if(!file.failed()) + { + XMLStreamWriter writer(file); + XMLPreferenceDictionaryExporter exporter(preferences, "1"); + exporter.exportXML(writer); + } + else + { + // error + } +} + + +class StringPreference +{ +public: + class Observer + { + public: + virtual void onChanged() = 0; + }; + +private: + CopiedString m_string; + Observer& m_observer; +public: + StringPreference(Observer& observer) + : m_observer(observer) + { + } + void importString(const char* value) + { + m_string = value; + m_observer.onChanged(); + } + typedef MemberCaller1 ImportStringCaller; + void exportString(StringImportCallback& importer) + { + importer(m_string.c_str()); + } + typedef MemberCaller1 ExportStringCaller; +}; + +inline void int_export(int i, StringImportCallback& importer) +{ + char buffer[16]; + sprintf(buffer, "%d", i); + importer(buffer); +} + +inline int int_import(const char* value) +{ + return atoi(value); +} + +class IntPreference +{ +public: + class Observer + { + public: + virtual void onChanged() = 0; + }; + +private: + int m_int; + Observer& m_observer; +public: + + IntPreference(Observer& observer) + : m_observer(observer) + { + } + void importString(const char* value) + { + m_int = int_import(value); + m_observer.onChanged(); + } + typedef MemberCaller1 ImportStringCaller; + void exportString(StringImportCallback& importer) + { + int_export(m_int, importer); + } + typedef MemberCaller1 ExportStringCaller; +}; + +class IntPreferenceImporter +{ + int& m_i; +public: + + IntPreferenceImporter(int& i) + : m_i(i) + { + } + void importString(const char* value) + { + m_i = int_import(value); + } +}; + + +class TestPrefs +{ +public: + TestPrefs() + { + PreferenceDictionary preferences; + + class StringObserver : public StringPreference::Observer + { + public: + void onChanged() + { + int bleh = 0; + } + } string_observer; + StringPreference string1(string_observer); + string1.importString("twenty-three"); + + class IntObserver : public IntPreference::Observer + { + public: + void onChanged() + { + int bleh = 0; + } + + } int_observer; + IntPreference int1(int_observer); + int1.importString("23"); + + preferences.registerPreference("string1", StringPreference::ImportStringCaller(string1), StringPreference::ExportStringCaller(string1)); + preferences.registerPreference("int1", IntPreference::ImportStringCaller(int1), IntPreference::ExportStringCaller(int1)); + + LoadPrefs(preferences, "test.pref"); + SavePrefs(preferences, "test.pref"); + + } +}; + +#if 0 +TestPrefs g_TestPrefs; +#endif + +void readpref(PreferenceDictionary& preferences, int& int_variable) +{ + PreferenceDictionary::iterator i = preferences.find("int_variable"); + IntPreferenceImporter importer(int_variable); + (*i).second.exporter().exportString(importer); +} + +void writepref(PreferenceDictionary& preferences, int& int_variable) +{ + PreferenceDictionary::iterator i = preferences.find("int_variable"); + int_export(int_variable, (*i).second.importer()); +} +#endif diff --git a/include/preferencesystem.h b/include/preferencesystem.h new file mode 100644 index 00000000..f5ebae1d --- /dev/null +++ b/include/preferencesystem.h @@ -0,0 +1,56 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_PREFERENCESYSTEM_H) +#define INCLUDED_PREFERENCESYSTEM_H + +#include "generic/constant.h" +#include "generic/callbackfwd.h" + +typedef Callback1 StringImportCallback; +typedef Callback1 StringExportCallback; + +class PreferenceSystem +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "preferences"); + + virtual void registerPreference(const char* name, const StringImportCallback& importer, const StringExportCallback& exporter) = 0; +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule GlobalPreferenceSystemModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef GlobalPreferenceSystemModuleRef; + +inline PreferenceSystem& GlobalPreferenceSystem() +{ + return GlobalPreferenceSystemModule::getTable(); +} + + +#endif diff --git a/include/qerplugin.cpp b/include/qerplugin.cpp new file mode 100644 index 00000000..dd209319 --- /dev/null +++ b/include/qerplugin.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qerplugin.h" + diff --git a/include/qerplugin.h b/include/qerplugin.h new file mode 100644 index 00000000..ea7be9e4 --- /dev/null +++ b/include/qerplugin.h @@ -0,0 +1,178 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// QERadiant PlugIns +// +// + +#ifndef __QERPLUGIN_H__ +#define __QERPLUGIN_H__ + +#include "generic/constant.h" + + +// ======================================== +// GTK+ helper functions + +// NOTE: parent can be 0 in all functions but it's best to set them + +// this API does not depend on gtk+ or glib +typedef struct _GtkWidget GtkWidget; + +enum EMessageBoxType +{ + eMB_OK, + eMB_OKCANCEL, + eMB_YESNO, + eMB_YESNOCANCEL, + eMB_NOYES, +}; + +enum EMessageBoxIcon +{ + eMB_ICONDEFAULT, + eMB_ICONERROR, + eMB_ICONWARNING, + eMB_ICONQUESTION, + eMB_ICONASTERISK, +}; + +enum EMessageBoxReturn +{ + eIDOK, + eIDCANCEL, + eIDYES, + eIDNO, +}; + +// simple Message Box, see above for the 'type' flags + +typedef EMessageBoxReturn (* PFN_QERAPP_MESSAGEBOX) (GtkWidget *parent, const char* text, const char* caption/* = "GtkRadiant"*/, EMessageBoxType type/* = eMB_OK*/, EMessageBoxIcon icon/* = eMB_ICONDEFAULT*/); + +// file and directory selection functions return null if the user hits cancel +// - 'title' is the dialog title (can be null) +// - 'path' is used to set the initial directory (can be null) +// - 'pattern': the first pattern is for the win32 mode, then comes the Gtk pattern list, see Radiant source for samples +typedef const char* (* PFN_QERAPP_FILEDIALOG) (GtkWidget *parent, bool open, const char* title, const char* path/* = 0*/, const char* pattern/* = 0*/); + +// returns a gchar* string that must be g_free'd by the user +typedef char* (* PFN_QERAPP_DIRDIALOG) (GtkWidget *parent, const char* title/* = "Choose Directory"*/, const char* path/* = 0*/); + +// return true if the user closed the dialog with 'Ok' +// 'color' is used to set the initial value and store the selected value +template class BasicVector3; +typedef BasicVector3 Vector3; +typedef bool (* PFN_QERAPP_COLORDIALOG) (GtkWidget *parent, Vector3& color, + const char* title/* = "Choose Color"*/); + +// load a .bmp file and create a GtkImage widget from it +// NOTE: 'filename' is relative to /plugins/bitmaps/ +typedef struct _GtkImage GtkImage; +typedef GtkImage* (* PFN_QERAPP_NEWIMAGE) (const char* filename); + +// ======================================== + +namespace scene +{ + class Node; +} + +class ModuleObserver; + +#include "signal/signalfwd.h" +#include "windowobserver.h" +#include "generic/vector.h" + +typedef SignalHandler3 MouseEventHandler; +typedef SignalFwd::handler_id_type MouseEventHandlerId; + +enum VIEWTYPE +{ + YZ = 0, + XZ = 1, + XY = 2 +}; + +// the radiant core API +struct _QERFuncTable_1 +{ + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, "radiant"); + + const char* (*getEnginePath)(); + const char* (*getLocalRcPath)(); + const char* (*getGameToolsPath)(); + const char* (*getAppPath)(); + const char* (*getSettingsPath)(); + const char* (*getMapsPath)(); + + const char* (*getGameName)(); + const char* (*getGameMode)(); + + const char* (*getMapName)(); + scene::Node& (*getMapWorldEntity)(); + float (*getGridSize)(); + + const char* (*getGameDescriptionKeyValue)(const char* key); + const char* (*getRequiredGameDescriptionKeyValue)(const char* key); + + void (*attachGameToolsPathObserver)(ModuleObserver& observer); + void (*detachGameToolsPathObserver)(ModuleObserver& observer); + void (*attachEnginePathObserver)(ModuleObserver& observer); + void (*detachEnginePathObserver)(ModuleObserver& observer); + void (*attachGameNameObserver)(ModuleObserver& observer); + void (*detachGameNameObserver)(ModuleObserver& observer); + void (*attachGameModeObserver)(ModuleObserver& observer); + void (*detachGameModeObserver)(ModuleObserver& observer); + + SignalHandlerId (*XYWindowDestroyed_connect)(const SignalHandler& handler); + void (*XYWindowDestroyed_disconnect)(SignalHandlerId id); + MouseEventHandlerId (*XYWindowMouseDown_connect)(const MouseEventHandler& handler); + void (*XYWindowMouseDown_disconnect)(MouseEventHandlerId id); + VIEWTYPE (*XYWindow_getViewType)(); + Vector3 (*XYWindow_windowToWorld)(const WindowVector& position); + const char* (*TextureBrowser_getSelectedShader)(); + + // GTK+ functions + PFN_QERAPP_MESSAGEBOX m_pfnMessageBox; + PFN_QERAPP_FILEDIALOG m_pfnFileDialog; + PFN_QERAPP_DIRDIALOG m_pfnDirDialog; + PFN_QERAPP_COLORDIALOG m_pfnColorDialog; + PFN_QERAPP_NEWIMAGE m_pfnNewImage; + +}; + +#include "modulesystem.h" + +template +class GlobalModule; +typedef GlobalModule<_QERFuncTable_1> GlobalRadiantModule; + +template +class GlobalModuleRef; +typedef GlobalModuleRef<_QERFuncTable_1> GlobalRadiantModuleRef; + +inline _QERFuncTable_1& GlobalRadiant() +{ + return GlobalRadiantModule::getTable(); +} + +#endif diff --git a/include/renderable.cpp b/include/renderable.cpp new file mode 100644 index 00000000..9ca3673f --- /dev/null +++ b/include/renderable.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "renderable.h" + diff --git a/include/renderable.h b/include/renderable.h new file mode 100644 index 00000000..de573910 --- /dev/null +++ b/include/renderable.h @@ -0,0 +1,76 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_RENDERABLE_H) +#define INCLUDED_RENDERABLE_H + +#include "generic/constant.h" + +class Shader; +class OpenGLRenderable; +class LightList; +class Matrix4; + +class Renderer +{ +public: + enum EHighlightMode + { + eFace = 1 << 0, + /*! Full highlighting. */ + ePrimitive = 1 << 1, + }; + + enum EStyle + { + eWireframeOnly, + eFullMaterials, + }; + + virtual void PushState() = 0; + virtual void PopState() = 0; + virtual void SetState(Shader* state, EStyle mode) = 0; + virtual const EStyle getStyle() const = 0; + virtual void Highlight(EHighlightMode mode, bool bEnable = true) = 0; + virtual void setLights(const LightList& lights) + { + } + virtual void addRenderable(const OpenGLRenderable& renderable, const Matrix4& world) = 0; +}; + +class VolumeTest; + +class Renderable +{ +public: + STRING_CONSTANT(Name, "Renderable"); + + virtual void renderSolid(Renderer& renderer, const VolumeTest& volume) const = 0; + virtual void renderWireframe(Renderer& renderer, const VolumeTest& volume) const = 0; + virtual void renderComponents(Renderer&, const VolumeTest&) const + { + } + virtual void viewChanged() const + { + } +}; + +#endif diff --git a/include/selectable.cpp b/include/selectable.cpp new file mode 100644 index 00000000..206bebe4 --- /dev/null +++ b/include/selectable.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "selectable.h" + diff --git a/include/selectable.h b/include/selectable.h new file mode 100644 index 00000000..fffcfe29 --- /dev/null +++ b/include/selectable.h @@ -0,0 +1,305 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_SELECTABLE_H) +#define INCLUDED_SELECTABLE_H + +#include + +#include "generic/vector.h" +#include "scenelib.h" +#include "generic/callbackfwd.h" + +class SelectionIntersection +{ + float m_depth; + float m_distance; +public: + SelectionIntersection() : m_depth(1), m_distance(2) + { + } + SelectionIntersection(float depth, float distance) : m_depth(depth), m_distance(distance) + { + } + bool operator<(const SelectionIntersection& other) const + { + if(m_distance != other.m_distance) + { + return m_distance < other.m_distance; + } + if(m_depth != other.m_depth) + { + return m_depth < other.m_depth; + } + return false; + } + bool equalEpsilon(const SelectionIntersection& other, float distanceEpsilon, float depthEpsilon) const + { + return float_equal_epsilon(m_distance, other.m_distance, distanceEpsilon) + && float_equal_epsilon(m_depth, other.m_depth, depthEpsilon); + } + float depth() const + { + return m_depth; + } + bool valid() const + { + return depth() < 1; + } +}; + +// returns true if self is closer than other +inline bool SelectionIntersection_closer(const SelectionIntersection& self, const SelectionIntersection& other) +{ + return self < other; +} + +// assigns other to best if other is closer than best +inline void assign_if_closer(SelectionIntersection& best, const SelectionIntersection& other) +{ + if(SelectionIntersection_closer(other, best)) + { + best = other; + } +} + + + + +class VertexPointer +{ + typedef const unsigned char* byte_pointer; +public: + typedef float elem_type; + typedef const elem_type* pointer; + typedef const elem_type& reference; + + class iterator + { + public: + iterator() {} + iterator(byte_pointer vertices, std::size_t stride) + : m_iter(vertices), m_stride(stride) {} + + bool operator==(const iterator& other) const + { + return m_iter == other.m_iter; + } + bool operator!=(const iterator& other) const + { + return !operator==(other); + } + + iterator operator+(std::size_t i) + { + return iterator(m_iter + i * m_stride, m_stride); + } + iterator operator+=(std::size_t i) + { + m_iter += i * m_stride; + return *this; + } + iterator& operator++() + { + m_iter += m_stride; + return *this; + } + iterator operator++(int) + { + iterator tmp = *this; + m_iter += m_stride; + return tmp; + } + reference operator*() const + { + return *reinterpret_cast(m_iter); + } + private: + byte_pointer m_iter; + std::size_t m_stride; + }; + + VertexPointer(pointer vertices, std::size_t stride) + : m_vertices(reinterpret_cast(vertices)), m_stride(stride) {} + + iterator begin() const + { + return iterator(m_vertices, m_stride); + } + + reference operator[](std::size_t i) const + { + return *reinterpret_cast(m_vertices + m_stride*i); + } + +private: + byte_pointer m_vertices; + std::size_t m_stride; +}; + +class IndexPointer +{ +public: + typedef unsigned int index_type; + typedef const index_type* pointer; + + class iterator + { + public: + iterator(pointer iter) : m_iter(iter) {} + + bool operator==(const iterator& other) const + { + return m_iter == other.m_iter; + } + bool operator!=(const iterator& other) const + { + return !operator==(other); + } + + iterator operator+(std::size_t i) + { + return m_iter + i; + } + iterator operator+=(std::size_t i) + { + return m_iter += i; + } + iterator operator++() + { + return ++m_iter; + } + iterator operator++(int) + { + return m_iter++; + } + const index_type& operator*() const + { + return *m_iter; + } + private: + void increment() + { + ++m_iter; + } + pointer m_iter; + }; + + IndexPointer(pointer indices, std::size_t count) + : m_indices(indices), m_finish(indices + count) {} + + iterator begin() const + { + return m_indices; + } + iterator end() const + { + return m_finish; + } + +private: + pointer m_indices; + pointer m_finish; +}; + +template class BasicVector3; +typedef BasicVector3 Vector3; +class Matrix4; +class VolumeTest; + +class SelectionTest +{ +public: + virtual void BeginMesh(const Matrix4& localToWorld, bool twoSided = false) = 0; + virtual const VolumeTest& getVolume() const = 0; + virtual const Vector3& getNear() const = 0; + virtual const Vector3& getFar() const = 0; + virtual void TestPoint(const Vector3& point, SelectionIntersection& best) = 0; + virtual void TestPolygon(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) = 0; + virtual void TestLineLoop(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) = 0; + virtual void TestLineStrip(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) = 0; + virtual void TestLines(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) = 0; + virtual void TestTriangles(const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best) = 0; + virtual void TestQuads(const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best) = 0; + virtual void TestQuadStrip(const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best) = 0; +}; + +class Selectable; + +class Selector +{ +public: + virtual void pushSelectable(Selectable& selectable) = 0; + virtual void popSelectable() = 0; + virtual void addIntersection(const SelectionIntersection& intersection) = 0; +}; + +inline void Selector_add(Selector& selector, Selectable& selectable) +{ + selector.pushSelectable(selectable); + selector.addIntersection(SelectionIntersection(0, 0)); + selector.popSelectable(); +} + +inline void Selector_add(Selector& selector, Selectable& selectable, const SelectionIntersection& intersection) +{ + selector.pushSelectable(selectable); + selector.addIntersection(intersection); + selector.popSelectable(); +} + + +class VolumeTest; +class SelectionTestable +{ +public: + STRING_CONSTANT(Name, "SelectionTestable"); + + virtual void testSelect(Selector& selector, SelectionTest& test) = 0; +}; + +inline SelectionTestable* Instance_getSelectionTestable(scene::Instance& instance) +{ + return InstanceTypeCast::cast(instance); +} + + +class Plane3; +typedef Callback1 PlaneCallback; + +class SelectedPlanes +{ +public: + virtual bool contains(const Plane3& plane) const = 0; +}; + +class PlaneSelectable +{ +public: + STRING_CONSTANT(Name, "PlaneSelectable"); + + virtual void selectPlanes(Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback) = 0; + virtual void selectReversedPlanes(Selector& selector, const SelectedPlanes& selectedPlanes) = 0; +}; + + + +#endif diff --git a/include/stream_version.h b/include/stream_version.h new file mode 100644 index 00000000..17ce6339 --- /dev/null +++ b/include/stream_version.h @@ -0,0 +1,3 @@ +// version defines for q3map stream +#define Q3MAP_STREAM_VERSION "1" + diff --git a/include/version.default b/include/version.default new file mode 100644 index 00000000..bc80560f --- /dev/null +++ b/include/version.default @@ -0,0 +1 @@ +1.5.0 diff --git a/include/warnings.h b/include/warnings.h new file mode 100644 index 00000000..66415614 --- /dev/null +++ b/include/warnings.h @@ -0,0 +1,31 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_WARNINGS_H) +#define INCLUDED_WARNINGS_H + +#if _MSC_VER > 1000 && defined(WIN32) +#pragma warning(disable:4355) // 'this' : used in base member initializer list +#pragma warning(disable:4503) // '[symbol]' : decorated name length exceeded, name was truncated +#endif + +#endif + diff --git a/include/windowobserver.cpp b/include/windowobserver.cpp new file mode 100644 index 00000000..2e59b8c0 --- /dev/null +++ b/include/windowobserver.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "windowobserver.h" + diff --git a/include/windowobserver.h b/include/windowobserver.h new file mode 100644 index 00000000..99fc03da --- /dev/null +++ b/include/windowobserver.h @@ -0,0 +1,91 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_WINDOWOBSERVER_H) +#define INCLUDED_WINDOWOBSERVER_H + +template class BitFieldValue; +struct ModifierEnumeration; +typedef BitFieldValue ModifierFlags; + +template class EnumeratedValue; +struct ButtonEnumeration; +typedef EnumeratedValue ButtonIdentifier; + + +#include "generic/bitfield.h" + +struct ModifierEnumeration +{ + enum Value + { + SHIFT = 0, + CONTROL = 1, + ALT = 2 + }; +}; + +typedef BitFieldValue ModifierFlags; + +const ModifierFlags c_modifierNone; +const ModifierFlags c_modifierShift(ModifierEnumeration::SHIFT); +const ModifierFlags c_modifierControl(ModifierEnumeration::CONTROL); +const ModifierFlags c_modifierAlt(ModifierEnumeration::ALT); + +#include "generic/enumeration.h" + +struct ButtonEnumeration +{ + enum Value + { + INVALID = 0, + LEFT = 1, + MIDDLE = 3, + RIGHT = 2 + }; +}; + +typedef EnumeratedValue ButtonIdentifier; + +const ButtonIdentifier c_buttonInvalid(ButtonEnumeration::INVALID); +const ButtonIdentifier c_buttonLeft(ButtonEnumeration::LEFT); +const ButtonIdentifier c_buttonMiddle(ButtonEnumeration::MIDDLE); +const ButtonIdentifier c_buttonRight(ButtonEnumeration::RIGHT); + + +template +class BasicVector2; +typedef BasicVector2 Vector2; +typedef Vector2 WindowVector; + +class WindowObserver +{ +public: + virtual void release() = 0; + virtual void onSizeChanged(int width, int height) = 0; + virtual void onMouseDown(const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers) = 0; + virtual void onMouseUp(const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers) = 0; + virtual void onMouseMotion(const WindowVector& position, ModifierFlags modifiers) = 0; + virtual void onModifierDown(ModifierFlags modifier) = 0; + virtual void onModifierUp(ModifierFlags modifier) = 0; +}; + +#endif diff --git a/install.py b/install.py new file mode 100644 index 00000000..0d44ceb9 --- /dev/null +++ b/install.py @@ -0,0 +1,125 @@ +# Copyright (C) 2001-2006 William Joseph. +# +# This file is part of GtkRadiant. +# +# GtkRadiant is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GtkRadiant is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GtkRadiant; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +""" +Builds the ./install directory. + +Copies files from various locations: +./setup/data/tools/ +./games// +./include/version.default is used to generate RADIANT_MAJOR and RADIANT_MINOR +""" + +import os +import sys +import shutil + +def assertMessage(condition, message): + if not condition: + raise Exception(message) + +def copyFile(source, target): + assertMessage(os.path.isfile(source), "failed to find file: " + source) + print source, "->", target + shutil.copy2(source, target) + +def copyFileIfExists(source, target): + if os.path.exists(source): + copyFile(source, target) + +def copySvn(source, target): + assertMessage(os.path.isdir(source), "failed to find directory: " + source) + if not os.path.exists(target): + os.mkdir(target) + for name in os.listdir(source): + absolute = os.path.join(source, name) + absTarget = os.path.join(target, name) + if os.path.isdir(absolute): + if name != ".svn": + copySvn(absolute, absTarget) + else: + copyFile(absolute, absTarget) + +def copyGame(source, game, target): + assertMessage(os.path.isdir(source), "failed to find directory: " + source) + assertMessage(os.path.isdir(target), "failed to find directory: " + target) + root = os.path.join(source, os.path.normpath(game[0])) + if os.path.exists(root): + gamename = game[1] + ".game" + copySvn(os.path.join(root, gamename), os.path.join(target, gamename)) + gamesDir = os.path.join(target, "games") + if not os.path.exists(gamesDir): + os.mkdir(gamesDir) + copyFile(os.path.join(root, "games", gamename), os.path.join(gamesDir, gamename)) + +thisDir = os.path.dirname(__file__) +gamesRoot = os.path.join(thisDir, "games") +installRoot = os.path.join(thisDir, "install") + +if not os.path.exists(installRoot): + os.mkdir(installRoot) + +# copy generic data +copySvn(os.path.join(thisDir, os.path.normpath("setup/data/tools")), installRoot) + +# root, gamename +games = [ + ("Doom3Pack/tools", "doom3"), + ("ETPack", "et"), + ("HalfLifePack", "hl"), + ("Her2Pack", "heretic2"), + ("JAPack/Tools", "ja"), + ("JK2Pack", "jk2"), + ("Q1Pack", "q1"), + ("Q2Pack", "q2"), + ("Q3Pack/tools", "q3"), + ("Q4Pack/tools", "q4"), + ("Sof2Pack", "sof2"), + ("STVEFPack", "stvef"), + ("WolfPack/bin", "wolf"), + ("NexuizPack", "nexuiz"), + ("DarkPlacesPack", "darkplaces"), + ("WarsowPack/tools", "warsow"), + ("TremulousPack/tools", "trem") +] + +# copy games +for game in games: + copyGame(gamesRoot, game, installRoot) + +# copy win32 dlls +gtk2Root = os.path.normpath(os.path.join(thisDir, "../gtk2-2.10/install")) +if os.path.exists(gtk2Root): + copySvn(gtk2Root, installRoot) + +libxml2 = os.path.normpath(os.path.join(thisDir, "../libxml2-2.6/bin/libxml2.dll")) +copyFileIfExists(libxml2, installRoot) + +libmhash = os.path.normpath(os.path.join(thisDir, "../mhash-0.9/win32/libmhash/Release/libmhash.dll")) +copyFileIfExists(libmhash, installRoot) + +if sys.platform[:3] == "win" : + copySvn("../msvc_redist", installRoot) + dbghelp = os.path.normpath(os.path.join(thisDir, "../msvc_redist/dbghelp.dll")) + copyFileIfExists(dbghelp, installRoot) + +# create version files +version = open(os.path.join(thisDir, "include/version.default"), "rt").readline().split(".") +assertMessage(len(version) == 3, "failed to parse include/version.default") +open(os.path.join(thisDir, "install/RADIANT_MAJOR"), "wt").write(str(version[1])) +open(os.path.join(thisDir, "install/RADIANT_MINOR"), "wt").write(str(version[2])) diff --git a/libs/.cvsignore b/libs/.cvsignore new file mode 100644 index 00000000..62555de1 --- /dev/null +++ b/libs/.cvsignore @@ -0,0 +1 @@ +.consign diff --git a/libs/archivelib.cpp b/libs/archivelib.cpp new file mode 100644 index 00000000..795f7c88 --- /dev/null +++ b/libs/archivelib.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "archivelib.h" diff --git a/libs/archivelib.h b/libs/archivelib.h new file mode 100644 index 00000000..710d2e8a --- /dev/null +++ b/libs/archivelib.h @@ -0,0 +1,256 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_ARCHIVELIB_H) +#define INCLUDED_ARCHIVELIB_H + +#include "debugging/debugging.h" +#include "iarchive.h" +#include "stream/filestream.h" +#include "stream/textfilestream.h" +#include "memory/allocator.h" +#include "string/string.h" + +/// \brief A single-byte-reader wrapper around an InputStream. +/// Optimised for reading one byte at a time. +/// Uses a buffer to reduce the number of times the wrapped stream must be read. +template +class SingleByteInputStream +{ + typedef typename InputStreamType::byte_type byte_type; + + InputStreamType& m_inputStream; + byte_type m_buffer[SIZE]; + byte_type* m_cur; + byte_type* m_end; + +public: + + SingleByteInputStream(InputStreamType& inputStream) : m_inputStream(inputStream), m_cur(m_buffer + SIZE), m_end(m_cur) + { + } + bool readByte(byte_type& b) + { + if(m_cur == m_end) + { + if(m_end != m_buffer + SIZE) + { + return false; + } + + m_end = m_buffer + m_inputStream.read(m_buffer, SIZE); + m_cur = m_buffer; + + if(m_end == m_buffer) + { + return false; + } + } + + b = *m_cur++; + + return true; + } +}; + +/// \brief A binary-to-text wrapper around an InputStream. +/// Converts CRLF or LFCR line-endings to LF line-endings. +template +class BinaryToTextInputStream : public TextInputStream +{ + SingleByteInputStream m_inputStream; +public: + BinaryToTextInputStream(BinaryInputStreamType& inputStream) : m_inputStream(inputStream) + { + } + std::size_t read(char* buffer, std::size_t length) + { + char* p = buffer; + for(;;) + { + if(length != 0 && m_inputStream.readByte(*reinterpret_cast(p))) + { + if(*p != '\r') + { + ++p; + --length; + } + } + else + { + return p - buffer; + } + } + } +}; + +/// \brief An ArchiveFile which is stored uncompressed as part of a larger archive file. +class StoredArchiveFile : public ArchiveFile +{ + CopiedString m_name; + FileInputStream m_filestream; + SubFileInputStream m_substream; + FileInputStream::size_type m_size; +public: + typedef FileInputStream::size_type size_type; + typedef FileInputStream::position_type position_type; + + StoredArchiveFile(const char* name, const char* archiveName, position_type position, size_type stream_size, size_type file_size) + : m_name(name), m_filestream(archiveName), m_substream(m_filestream, position, stream_size), m_size(file_size) + { + } + + static StoredArchiveFile* create(const char* name, const char* archiveName, position_type position, size_type stream_size, size_type file_size) + { + return New().scalar(name, archiveName, position, stream_size, file_size); + } + + void release() + { + Delete().scalar(this); + } + size_type size() const + { + return m_size; + } + const char* getName() const + { + return m_name.c_str(); + } + InputStream& getInputStream() + { + return m_substream; + } +}; + +/// \brief An ArchiveTextFile which is stored uncompressed as part of a larger archive file. +class StoredArchiveTextFile : public ArchiveTextFile +{ + CopiedString m_name; + FileInputStream m_filestream; + SubFileInputStream m_substream; + BinaryToTextInputStream m_textStream; +public: + typedef FileInputStream::size_type size_type; + typedef FileInputStream::position_type position_type; + + StoredArchiveTextFile(const char* name, const char* archiveName, position_type position, size_type stream_size) + : m_name(name), m_filestream(archiveName), m_substream(m_filestream, position, stream_size), m_textStream(m_substream) + { + } + + static StoredArchiveTextFile* create(const char* name, const char* archiveName, position_type position, size_type stream_size) + { + return New().scalar(name, archiveName, position, stream_size); + } + + void release() + { + Delete().scalar(this); + } + const char* getName() const + { + return m_name.c_str(); + } + TextInputStream& getInputStream() + { + return m_textStream; + } +}; + +/// \brief An ArchiveFile which is stored as a single file on disk. +class DirectoryArchiveFile : public ArchiveFile +{ + CopiedString m_name; + FileInputStream m_istream; + FileInputStream::size_type m_size; +public: + typedef FileInputStream::size_type size_type; + + DirectoryArchiveFile(const char* name, const char* filename) + : m_name(name), m_istream(filename) + { + if(!failed()) + { + m_istream.seek(0, FileInputStream::end); + m_size = m_istream.tell(); + m_istream.seek(0); + } + else + { + m_size = 0; + } + } + bool failed() const + { + return m_istream.failed(); + } + + void release() + { + delete this; + } + size_type size() const + { + return m_size; + } + const char* getName() const + { + return m_name.c_str(); + } + InputStream& getInputStream() + { + return m_istream; + } +}; + +/// \brief An ArchiveTextFile which is stored as a single file on disk. +class DirectoryArchiveTextFile : public ArchiveTextFile +{ + CopiedString m_name; + TextFileInputStream m_inputStream; +public: + + DirectoryArchiveTextFile(const char* name, const char* filename) + : m_name(name), m_inputStream(filename) + { + } + bool failed() const + { + return m_inputStream.failed(); + } + + void release() + { + delete this; + } + const char* getName() const + { + return m_name.c_str(); + } + TextInputStream& getInputStream() + { + return m_inputStream; + } +}; + + +#endif diff --git a/libs/bytebool.cpp b/libs/bytebool.cpp new file mode 100644 index 00000000..036061fe --- /dev/null +++ b/libs/bytebool.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "bytebool.h" diff --git a/libs/bytebool.h b/libs/bytebool.h new file mode 100644 index 00000000..4009c2dd --- /dev/null +++ b/libs/bytebool.h @@ -0,0 +1,32 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __BYTEBOOL__ +#define __BYTEBOOL__ + +// defines boolean and byte types usable in both c and c++ code +// this header is not really meant for direct inclusion, +// it is used by mathlib and cmdlib + +typedef enum { qfalse, qtrue } qboolean; +typedef unsigned char byte; + +#endif diff --git a/libs/bytestreamutils.cpp b/libs/bytestreamutils.cpp new file mode 100644 index 00000000..f9b8ddca --- /dev/null +++ b/libs/bytestreamutils.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "bytestreamutils.h" diff --git a/libs/bytestreamutils.h b/libs/bytestreamutils.h new file mode 100644 index 00000000..aad7d7e4 --- /dev/null +++ b/libs/bytestreamutils.h @@ -0,0 +1,129 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_BYTESTREAMUTILS_H) +#define INCLUDED_BYTESTREAMUTILS_H + +#if defined(__GNUC__) + +#define _ISOC9X_SOURCE 1 +#define _ISOC99_SOURCE 1 + +#define __USE_ISOC9X 1 +#define __USE_ISOC99 1 + +#include + +#endif + +#include + +// if C99 is unavailable, fall back to the types most likely to be the right sizes +#if !defined(int16_t) +typedef signed short int16_t; +#endif +#if !defined(uint16_t) +typedef unsigned short uint16_t; +#endif +#if !defined(int32_t) +typedef signed int int32_t; +#endif +#if !defined(uint32_t) +typedef unsigned int uint32_t; +#endif + + + + +template +inline void istream_read_little_endian(InputStreamType& istream, Type& value) +{ + istream.read(reinterpret_cast(&value), sizeof(Type)); +#if defined(__BIG_ENDIAN__) + std::reverse(reinterpret_cast(&value), reinterpret_cast(&value) + sizeof(Type)); +#endif +} + +template +inline void istream_read_big_endian(InputStreamType& istream, Type& value) +{ + istream.read(reinterpret_cast(&value), sizeof(Type)); +#if !defined(__BIG_ENDIAN__) + std::reverse(reinterpret_cast(&value), reinterpret_cast(&value) + sizeof(Type)); +#endif +} + +template +inline void istream_read_byte(InputStreamType& istream, typename InputStreamType::byte_type& b) +{ + istream.read(&b, 1); +} + + +template +inline int16_t istream_read_int16_le(InputStreamType& istream) +{ + int16_t value; + istream_read_little_endian(istream, value); + return value; +} + +template +inline uint16_t istream_read_uint16_le(InputStreamType& istream) +{ + uint16_t value; + istream_read_little_endian(istream, value); + return value; +} + +template +inline int32_t istream_read_int32_le(InputStreamType& istream) +{ + int32_t value; + istream_read_little_endian(istream, value); + return value; +} + +template +inline uint32_t istream_read_uint32_le(InputStreamType& istream) +{ + uint32_t value; + istream_read_little_endian(istream, value); + return value; +} + +template +inline float istream_read_float32_le(InputStreamType& istream) +{ + float value; + istream_read_little_endian(istream, value); + return value; +} + +template +inline typename InputStreamType::byte_type istream_read_byte(InputStreamType& istream) +{ + typename InputStreamType::byte_type b; + istream.read(&b, sizeof(typename InputStreamType::byte_type)); + return b; +} + +#endif diff --git a/libs/character.cpp b/libs/character.cpp new file mode 100644 index 00000000..ab8969b6 --- /dev/null +++ b/libs/character.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "character.h" + diff --git a/libs/character.h b/libs/character.h new file mode 100644 index 00000000..1e6d3b16 --- /dev/null +++ b/libs/character.h @@ -0,0 +1,47 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_CHARACTER_H) +#define INCLUDED_CHARACTER_H + +/// \file +/// \brief Character encoding. + +/// \brief Returns true if \p c is an ASCII character that can be represented with 7 bits. +inline bool char_is_ascii(char c) +{ + return (c & 0x80) == 0; +} + +/// \brief Returns true if \p string consists entirely of ASCII characters. +inline bool string_is_ascii(const char* string) +{ + while(*string != '\0') + { + if(!char_is_ascii(*string++)) + { + return false; + } + } + return true; +} + +#endif diff --git a/libs/cmdlib.h b/libs/cmdlib.h new file mode 100644 index 00000000..ffeb4271 --- /dev/null +++ b/libs/cmdlib.h @@ -0,0 +1,103 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// start of shared cmdlib stuff +// + +#ifndef __CMDLIB__ +#define __CMDLIB__ + +#include + + +// TTimo started adding portability code: +// return true if spawning was successful, false otherwise +// on win32 we have a bCreateConsole flag to create a new console or run inside the current one +//boolean Q_Exec(const char* pCmd, boolean bCreateConsole); +// execute a system command: +// cmd: the command to run +// cmdline: the command line +// NOTE TTimo following are win32 specific: +// execdir: the directory to execute in +// bCreateConsole: spawn a new console or not +// return values; +// if the spawn was fine +// TODO TTimo add functionality to track the process until it dies + +bool Q_Exec(const char *cmd, char *cmdline, const char *execdir, bool bCreateConsole); + +// some easy portability crap + + +#define access_owner_read 0400 +#define access_owner_write 0200 +#define access_owner_execute 0100 +#define access_owner_rw_ 0600 +#define access_owner_r_x 0500 +#define access_owner__wx 0300 +#define access_owner_rwx 0700 + +#define access_group_read 0040 +#define access_group_write 0020 +#define access_group_execute 0010 +#define access_group_rw_ 0060 +#define access_group_r_x 0050 +#define access_group__wx 0030 +#define access_group_rwx 0070 + +#define access_others_read 0004 +#define access_others_write 0002 +#define access_others_execute 0001 +#define access_others_rw_ 0006 +#define access_others_r_x 0005 +#define access_others__wx 0003 +#define access_others_rwx 0007 + + +#define access_rwxrwxr_x (access_owner_rwx | access_group_rwx | access_others_r_x) +#define access_rwxrwxrwx (access_owner_rwx | access_group_rwx | access_others_rwx) + +// Q_mkdir +// returns true if succeeded in creating directory +#ifdef WIN32 +#include +inline bool Q_mkdir(const char* name) +{ + return _mkdir(name) != -1; +} +#else +#include +inline bool Q_mkdir(const char* name) +{ + return mkdir(name, access_rwxrwxr_x) != -1; +} +#endif + + +inline double Sys_DoubleTime(void) +{ + return clock()/ 1000.0; +} + + + +#endif diff --git a/libs/cmdlib/.cvsignore b/libs/cmdlib/.cvsignore new file mode 100644 index 00000000..b5ee5ae1 --- /dev/null +++ b/libs/cmdlib/.cvsignore @@ -0,0 +1 @@ +Debug Release *.ncb *.opt *.plg *.001 *.BAK \ No newline at end of file diff --git a/libs/cmdlib/.cvswrappers b/libs/cmdlib/.cvswrappers new file mode 100644 index 00000000..cdfd6d4a --- /dev/null +++ b/libs/cmdlib/.cvswrappers @@ -0,0 +1,3 @@ +*.dsp -m 'COPY' -k 'b' +*.dsw -m 'COPY' -k 'b' +*.scc -m 'COPY' -k 'b' diff --git a/libs/cmdlib/cmdlib.cpp b/libs/cmdlib/cmdlib.cpp new file mode 100644 index 00000000..a09ed9ea --- /dev/null +++ b/libs/cmdlib/cmdlib.cpp @@ -0,0 +1,129 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// start of shared cmdlib stuff +// + +#include "cmdlib.h" + +#include +#include + +#include "string/string.h" +#include "os/path.h" +#include "container/array.h" + + +#if defined (POSIX) + +#include + +bool Q_Exec(const char *cmd, char *cmdline, const char *, bool) +{ + char fullcmd[2048]; + char *pCmd; +#ifdef _DEBUG + printf("Q_Exec damnit\n"); +#endif + switch (fork()) + { + case -1: + return true; + break; + case 0: + // always concat the command on linux + if (cmd) + { + strcpy(fullcmd, cmd); + } + else + fullcmd[0] = '\0'; + if (cmdline) + { + strcat(fullcmd, " "); + strcat(fullcmd, cmdline); + } + pCmd = fullcmd; + while (*pCmd == ' ') + pCmd++; +#ifdef _DEBUG + printf("Running system...\n"); + printf("Command: %s\n", pCmd); +#endif + system( pCmd ); +#ifdef _DEBUG + printf ("system() returned\n"); +#endif + _exit (0); + break; + } + return true; +} + +#elif defined(WIN32) + +#include + +// NOTE TTimo windows is VERY nitpicky about the syntax in CreateProcess +bool Q_Exec(const char *cmd, char *cmdline, const char *execdir, bool bCreateConsole) +{ + PROCESS_INFORMATION ProcessInformation; + STARTUPINFO startupinfo = {0}; + DWORD dwCreationFlags; + GetStartupInfo (&startupinfo); + if (bCreateConsole) + dwCreationFlags = CREATE_NEW_CONSOLE | NORMAL_PRIORITY_CLASS; + else + dwCreationFlags = DETACHED_PROCESS | NORMAL_PRIORITY_CLASS; + const char *pCmd; + char *pCmdline; + pCmd = cmd; + if (pCmd) + { + while (*pCmd == ' ') + pCmd++; + } + pCmdline = cmdline; + if (pCmdline) + { + while (*pCmdline == ' ') + pCmdline++; + } + + if (CreateProcess( + pCmd, + pCmdline, + NULL, + NULL, + FALSE, + dwCreationFlags, + NULL, + execdir, + &startupinfo, + &ProcessInformation + )) + return true; + return false; +} + +#endif + diff --git a/libs/cmdlib/cmdlib.vcproj b/libs/cmdlib/cmdlib.vcproj new file mode 100644 index 00000000..97f34755 --- /dev/null +++ b/libs/cmdlib/cmdlib.vcproj @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/container/array.cpp b/libs/container/array.cpp new file mode 100644 index 00000000..378a2e76 --- /dev/null +++ b/libs/container/array.cpp @@ -0,0 +1,39 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "array.h" + +namespace +{ + class Bleh + { + Array m_array; + public: + Bleh() : m_array(16) + { + } + }; + + void testAutoArray() + { + Array array(32); + } +} \ No newline at end of file diff --git a/libs/container/array.h b/libs/container/array.h new file mode 100644 index 00000000..de444fd8 --- /dev/null +++ b/libs/container/array.h @@ -0,0 +1,197 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_CONTAINER_ARRAY_H) +#define INCLUDED_CONTAINER_ARRAY_H + +#include +#include + +#include "memory/allocator.h" + +/// \brief An array whose size is variable at run-time. +/// +/// - Resizing the array destroys all the existing elements and invalidates all iterators. +/// - Default-Constructible, Copyable, Assignable. +/// - Compatible with the containers and algorithms in the Standard Template Library (STL) - http://www.sgi.com/tech/stl/ +/// +/// \param Element The type to be stored in the array. Must provide a default-constructor and a copy-constructor. +/// \param Allocator A custom memory-allocator, conforming to the std::allocator interface. +template > +class Array : public Allocator +{ + std::size_t m_size; + Element* m_data; + + Element* construct(std::size_t size) + { +#if 1 + return New(*this).vector(size); +#else + return new Element[size]; +#endif + } + template + Element* construct(std::size_t size, const T1& value) + { + return New(*this).vector(size, value); + } + void destroy(Element* data, std::size_t size) + { +#if 1 + Delete(*this).vector(data, size); +#else + delete[] data; +#endif + } + +public: + typedef Element value_type; + typedef value_type* iterator; + typedef const value_type* const_iterator; + + Array() + : m_size(0), m_data(0) + { + } + Array(std::size_t size) + : m_size(size), m_data(construct(size)) + { + } + template + Array(std::size_t size, const T1& value) + : m_size(size), m_data(construct(size, value)) + { + } + Array(const Array& other) + : Allocator(other), m_size(other.size()), m_data(construct(m_size)) + { + std::copy(other.begin(), other.end(), begin()); + } + template + Array(Iterator start, Iterator finish) + : m_size(std::distance(start, finish)), m_data(construct(m_size)) + { + std::copy(start, finish, begin()); + } + ~Array() + { + destroy(m_data, m_size); + } + + Array& operator=(const Array& other) + { + if(other.size() == size()) + { + std::copy(other.begin(), other.end(), begin()); + } + else + { + Array temp(other); + temp.swap(*this); + } + return *this; + } + + void swap(Array& other) + { + std::swap(m_size, other.m_size); + std::swap(m_data, other.m_data); + } + + iterator begin() + { + return m_data; + } + const_iterator begin() const + { + return m_data; + } + iterator end() + { + return m_data + m_size; + } + const_iterator end() const + { + return m_data + m_size; + } + + value_type& operator[](std::size_t index) + { +#if defined(_DEBUG) + ASSERT_MESSAGE(index < size(), "array index out of bounds"); +#endif + return m_data[index]; + } + const value_type& operator[](std::size_t index) const + { +#if defined(_DEBUG) + ASSERT_MESSAGE(index < size(), "array index out of bounds"); +#endif + return m_data[index]; + } + value_type* data() + { + return m_data; + } + const value_type* data() const + { + return m_data; + } + std::size_t size() const + { + return m_size; + } + bool empty() const + { + return m_size == 0; + } + + void resize(std::size_t count) + { + if(count != size()) + { + Array temp(count); + temp.swap(*this); + } + } + void resize(std::size_t count, const value_type& value) + { + if(count != size()) + { + Array temp(count, value); + temp.swap(*this); + } + } +}; + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap. + template + inline void swap(Array& self, Array& other) + { + self.swap(other); + } +} + +#endif diff --git a/libs/container/cache.cpp b/libs/container/cache.cpp new file mode 100644 index 00000000..a704287e --- /dev/null +++ b/libs/container/cache.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "cache.h" diff --git a/libs/container/cache.h b/libs/container/cache.h new file mode 100644 index 00000000..8c594b88 --- /dev/null +++ b/libs/container/cache.h @@ -0,0 +1,206 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_CONTAINER_CACHE_H) +#define INCLUDED_CONTAINER_CACHE_H + +#include +#include "container/hashtable.h" +#include "memory/allocator.h" + +template +class DefaultCreationPolicy +{ +public: + Type* construct(const Parameter& parameter) + { + return New().scalar(parameter); + } + void destroy(Type* p) + { + Delete().scalar(p); + } +}; + +template +class SharedValue +{ + typedef Type value_type; + typedef value_type* pointer; + typedef value_type& reference; + + std::size_t m_count; + pointer m_value; + +public: + SharedValue() + : m_count(0), m_value(0) + { + } + ~SharedValue() + { + ASSERT_MESSAGE(m_count == 0 , "destroying a referenced object\n"); + } + void set(pointer value) + { + m_value = value; + } + pointer get() + { + return m_value; + } + std::size_t increment() + { + return ++m_count; + } + std::size_t decrement() + { + ASSERT_MESSAGE(!empty(), "destroying a non-existent object\n"); + return --m_count; + } + std::size_t count() + { + return m_count; + } + bool empty() + { + return m_count == 0; + } + reference operator*() const + { + ASSERT_NOTNULL(m_value); + return *m_value; + } + pointer operator->() const + { + return &(operator*()); + } +}; + + + +/// \brief Caches values that are uniquely identified by a key. +/// +/// - Automatically removes objects that are no longer referenced. +/// +/// \param Key Uniquely identifies each element. +/// \param Cached The type to be cached. Must define a constructor that accepts \c Key. +/// \param CreationPolicy Must define 'Cached* construct(const Key&)' and 'void destroy(Cached*)'. The lifetime of the \c Key passed to 'construct' is guaranteed to be longer than the subsequent matched call to 'destroy'. +template, typename CreationPolicy = DefaultCreationPolicy > +class HashedCache : public CreationPolicy +{ + typedef SharedValue Element; + typedef HashTable map_type; + + map_type m_map; + +public: + explicit HashedCache(const CreationPolicy& creation = CreationPolicy()) + : CreationPolicy(creation), m_map(256) + { + } + ~HashedCache() + { + ASSERT_MESSAGE(empty(), "HashedCache::~HashedCache: not empty"); + } + + typedef typename map_type::iterator iterator; + typedef typename map_type::value_type value_type; + + iterator begin() + { + return m_map.begin(); + } + iterator end() + { + return m_map.end(); + } + + bool empty() const + { + return m_map.empty(); + } + + iterator find(const Key& key) + { + return m_map.find(key); + } + + void capture(iterator i) + { + (*i).value.increment(); + } + void release(iterator i) + { + if((*i).value.decrement() == 0) + { + CreationPolicy::destroy((*i).value.get()); + m_map.erase(i); + } + } + +#if 1 + Element& capture(const Key& key) + { +#if 0 + Element& elem = m_map[key]; + if(elem.increment() == 1) + { + elem.set(CreationPolicy::construct(key)); + } + return elem; +#else + iterator i = m_map.insert(key, Element()); + if((*i).value.increment() == 1) + { + (*i).value.set(CreationPolicy::construct((*i).key)); + } + return (*i).value; +#endif + } +#else + value_type& capture(const Key& key) + { + iterator i = m_map.find(key); + if(i == m_map.end()) + { + i = m_map.insert(key, Element()); + (*i).value.set(CreationPolicy::construct((*i).key)); + } + (*i).value.increment(); + return (*i); + } +#endif + void release(const Key& key) + { + iterator i = m_map.find(key); + ASSERT_MESSAGE(i != m_map.end(), "releasing a non-existent object\n"); + release(i); + } + + void clear() + { + m_map.clear(); + } +}; + + +#endif diff --git a/libs/container/container.cpp b/libs/container/container.cpp new file mode 100644 index 00000000..627e0c88 --- /dev/null +++ b/libs/container/container.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "container.h" + diff --git a/libs/container/container.h b/libs/container/container.h new file mode 100644 index 00000000..b6d75841 --- /dev/null +++ b/libs/container/container.h @@ -0,0 +1,393 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_CONTAINER_CONTAINER_H) +#define INCLUDED_CONTAINER_CONTAINER_H + +#include +#include + +#include "generic/static.h" + +/// \brief A single-value container, which can either be empty or full. +template +class Single +{ + Type* m_value; +public: + Single() : m_value(0) + { + } + bool empty() + { + return m_value == 0; + } + Type* insert(const Type& other) + { + m_value = new Type(other); + return m_value; + } + void clear() + { + delete m_value; + m_value = 0; + } + Type& get() + { + //ASSERT_MESSAGE(!empty(), "Single: must be initialised before being accessed"); + return *m_value; + } + const Type& get() const + { + //ASSERT_MESSAGE(!empty(), "Single: must be initialised before being accessed"); + return *m_value; + } +}; + + +/// \brief An adaptor to make std::list into a Unique Sequence - which cannot contain the same value more than once. +/// \param Value Uniquely identifies itself. Must provide a copy-constructor and an equality operator. +template +class UnsortedSet +{ + typedef typename std::list Values; + Values m_values; +public: + typedef typename Values::iterator iterator; + typedef typename Values::const_iterator const_iterator; + typedef typename Values::reverse_iterator reverse_iterator; + typedef typename Values::const_reverse_iterator const_reverse_iterator; + + iterator begin() + { + return m_values.begin(); + } + const_iterator begin() const + { + return m_values.begin(); + } + iterator end() + { + return m_values.end(); + } + const_iterator end() const + { + return m_values.end(); + } + reverse_iterator rbegin() + { + return m_values.rbegin(); + } + const_reverse_iterator rbegin() const + { + return m_values.rbegin(); + } + reverse_iterator rend() + { + return m_values.rend(); + } + const_reverse_iterator rend() const + { + return m_values.rend(); + } + + bool empty() const + { + return m_values.empty(); + } + std::size_t size() const + { + return m_values.size(); + } + void clear() + { + m_values.clear(); + } + + void swap(UnsortedSet& other) + { + std::swap(m_values, other.m_values); + } + iterator insert(const Value& value) + { + ASSERT_MESSAGE(find(value) == end(), "UnsortedSet::insert: already added"); + m_values.push_back(value); + return --end(); + } + void erase(const Value& value) + { + iterator i = find(value); + ASSERT_MESSAGE(i != end(), "UnsortedSet::erase: not found"); + m_values.erase(i); + } + iterator find(const Value& value) + { + return std::find(begin(), end(), value); + } +}; + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap. + template + inline void swap(UnsortedSet& self, UnsortedSet& other) + { + self.swap(other); + } +} + +/// An adaptor to make std::list into a Unique Associative Sequence - which cannot contain the same value more than once. +/// Key: Uniquely identifies a value. Must provide a copy-constructor and an equality operator. +/// Value: Must provide a copy-constructor. +template +class UnsortedMap +{ + typedef typename std::list< std::pair > Values; + Values m_values; +public: + typedef typename Values::value_type value_type; + typedef typename Values::iterator iterator; + typedef typename Values::const_iterator const_iterator; + + iterator begin() + { + return m_values.begin(); + } + const_iterator begin() const + { + return m_values.begin(); + } + iterator end() + { + return m_values.end(); + } + const_iterator end() const + { + return m_values.end(); + } + + bool empty() const + { + return m_values.empty(); + } + std::size_t size() const + { + return m_values.size(); + } + void clear() + { + m_values.clear(); + } + + iterator insert(const value_type& value) + { + ASSERT_MESSAGE(find(value.first) == end(), "UnsortedMap::insert: already added"); + m_values.push_back(value); + return --m_values.end(); + } + void erase(const Key& key) + { + iterator i = find(key); + ASSERT_MESSAGE(i != end(), "UnsortedMap::erase: not found"); + erase(i); + } + void erase(iterator i) + { + m_values.erase(i); + } + iterator find(const Key& key) + { + for(iterator i = m_values.begin(); i != m_values.end(); ++i) + { + if((*i).first == key) + { + return i; + } + } + return m_values.end(); + } + const_iterator find(const Key& key) const + { + for(const_iterator i = m_values.begin(); i != m_values.end(); ++i) + { + if((*i).first == key) + { + return i; + } + } + return m_values.end(); + } + + Value& operator[](const Key& key) + { + iterator i = find(key); + if(i != end()) + { + return (*i).second; + } + + m_values.push_back(Values::value_type(key, Value())); + return m_values.back().second; + } +}; + +/// An adaptor to assert when duplicate values are added, or non-existent values removed from a std::set. +template +class UniqueSet +{ + typedef std::set Values; + Values m_values; +public: + typedef typename Values::iterator iterator; + typedef typename Values::const_iterator const_iterator; + typedef typename Values::reverse_iterator reverse_iterator; + typedef typename Values::const_reverse_iterator const_reverse_iterator; + + + iterator begin() + { + return m_values.begin(); + } + const_iterator begin() const + { + return m_values.begin(); + } + iterator end() + { + return m_values.end(); + } + const_iterator end() const + { + return m_values.end(); + } + reverse_iterator rbegin() + { + return m_values.rbegin(); + } + const_reverse_iterator rbegin() const + { + return m_values.rbegin(); + } + reverse_iterator rend() + { + return m_values.rend(); + } + const_reverse_iterator rend() const + { + return m_values.rend(); + } + + bool empty() const + { + return m_values.empty(); + } + std::size_t size() const + { + return m_values.size(); + } + void clear() + { + m_values.clear(); + } + + void swap(UniqueSet& other) + { + std::swap(m_values, other.m_values); + } + iterator insert(const Value& value) + { + std::pair result = m_values.insert(value); + ASSERT_MESSAGE(result.second, "UniqueSet::insert: already added"); + return result.first; + } + void erase(const Value& value) + { + iterator i = find(value); + ASSERT_MESSAGE(i != end(), "UniqueSet::erase: not found"); + m_values.erase(i); + } + iterator find(const Value& value) + { + return std::find(begin(), end(), value); + } +}; + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap. + template + inline void swap(UniqueSet& self, UniqueSet& other) + { + self.swap(other); + } +} + +template +class ReferencePair +{ + Type* m_first; + Type* m_second; +public: + ReferencePair() : m_first(0), m_second(0) + { + } + void attach(Type& t) + { + ASSERT_MESSAGE(m_first == 0 || m_second == 0, "ReferencePair::insert: pointer already exists"); + if(m_first == 0) + { + m_first = &t; + } + else if(m_second == 0) + { + m_second = &t; + } + } + void detach(Type& t) + { + ASSERT_MESSAGE(m_first == &t || m_second == &t, "ReferencePair::erase: pointer not found"); + if(m_first == &t) + { + m_first = 0; + } + else if(m_second == &t) + { + m_second = 0; + } + } + template + void forEach(const Functor& functor) + { + if(m_second != 0) + { + functor(*m_second); + } + if(m_first != 0) + { + functor(*m_first); + } + } +}; + + +#endif diff --git a/libs/container/hashfunc.cpp b/libs/container/hashfunc.cpp new file mode 100644 index 00000000..20569cd1 --- /dev/null +++ b/libs/container/hashfunc.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "hashfunc.h" diff --git a/libs/container/hashfunc.h b/libs/container/hashfunc.h new file mode 100644 index 00000000..a4ec5fee --- /dev/null +++ b/libs/container/hashfunc.h @@ -0,0 +1,434 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_CONTAINER_HASHFUNC_H) +#define INCLUDED_CONTAINER_HASHFUNC_H + +#include +#include "string/string.h" +#include "container/array.h" +typedef unsigned long int ub4; /* unsigned 4-byte quantities */ +typedef unsigned char ub1; + +inline ub1 ub1_as_ub1_nocase(ub1 byte) +{ + return std::tolower(byte); +} + +inline ub4 ub1x4_as_ub4_nocase(const ub1 bytes[4]) +{ + ub4 result; + reinterpret_cast(&result)[0] = ub1_as_ub1_nocase(bytes[0]); + reinterpret_cast(&result)[1] = ub1_as_ub1_nocase(bytes[1]); + reinterpret_cast(&result)[2] = ub1_as_ub1_nocase(bytes[2]); + reinterpret_cast(&result)[3] = ub1_as_ub1_nocase(bytes[3]); + return result; +} + +class ub1_default_traits +{ +public: + static ub1 as_ub1(ub1 byte) + { + return byte; + } +}; + +class ub1_nocase_traits +{ +public: + static ub1 as_ub1(ub1 byte) + { + return ub1_as_ub1_nocase(byte); + } +}; + +class ub1x4_default_traits +{ +public: + static ub4 as_ub4(const ub1 bytes[4]) + { + return *reinterpret_cast(bytes); + } +}; + +class ub1x4_nocase_traits +{ +public: + static ub4 as_ub4(const ub1 bytes[4]) + { + return ub1x4_as_ub4_nocase(bytes); + } +}; + +class ub4_default_traits +{ +public: + static ub4 as_ub4(ub4 i) + { + return i; + } +}; + +class ub4_nocase_traits +{ +public: + static ub4 as_ub4(ub4 i) + { + return ub1x4_as_ub4_nocase(reinterpret_cast(&i)); + } +}; + +// lookup2.c +// By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net. You may use this +// code any way you wish, private, educational, or commercial. It's free. + +#define hashsize(n) ((ub4)1<<(n)) +#define hashmask(n) (hashsize(n)-1) + +/* +-------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. +For every delta with one or two bit set, and the deltas of all three + high bits or all three low bits, whether the original value of a,b,c + is almost all zero or is uniformly distributed, +* If mix() is run forward or backward, at least 32 bits in a,b,c + have at least 1/4 probability of changing. +* If mix() is run forward, every bit of c will change between 1/3 and + 2/3 of the time. (Well, 22/100 and 78/100 for some 2-bit deltas.) +mix() was built out of 36 single-cycle latency instructions in a + structure that could supported 2x parallelism, like so: + a -= b; + a -= c; x = (c>>13); + b -= c; a ^= x; + b -= a; x = (a<<8); + c -= a; b ^= x; + c -= b; x = (b>>13); + ... + Unfortunately, superscalar Pentiums and Sparcs can't take advantage + of that parallelism. They've also turned some of those single-cycle + latency instructions into multi-cycle latency instructions. Still, + this is the fastest good hash I could find. There were about 2^^68 + to choose from. I only looked at a billion or so. +-------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<<8); \ + c -= a; c -= b; c ^= (b>>13); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<16); \ + c -= a; c -= b; c ^= (b>>5); \ + a -= b; a -= c; a ^= (c>>3); \ + b -= c; b -= a; b ^= (a<<10); \ + c -= a; c -= b; c ^= (b>>15); \ +} + +/* same, but slower, works on systems that might have 8 byte ub4's */ +#define mix2(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<< 8); \ + c -= a; c -= b; c ^= ((b&0xffffffff)>>13); \ + a -= b; a -= c; a ^= ((c&0xffffffff)>>12); \ + b -= c; b -= a; b = (b ^ (a<<16)) & 0xffffffff; \ + c -= a; c -= b; c = (c ^ (b>> 5)) & 0xffffffff; \ + a -= b; a -= c; a = (a ^ (c>> 3)) & 0xffffffff; \ + b -= c; b -= a; b = (b ^ (a<<10)) & 0xffffffff; \ + c -= a; c -= b; c = (c ^ (b>>15)) & 0xffffffff; \ +} + +/* +-------------------------------------------------------------------- +hash() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + len : the length of the key, counting by bytes + level : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Every 1-bit and 2-bit delta achieves avalanche. +About 36+6len instructions. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (ub1 **)k, do it like this: + for (i=0, h=0; i +inline ub4 hash( +const ub1 *k, /* the key */ +ub4 length, /* the length of the key */ +ub4 initval, /* the previous hash, or an arbitrary value */ +const UB1Traits& ub1traits, +const UB4x1Traits& ub4x1traits +) +{ + register ub4 a,b,c,len; + + /* Set up the internal state */ + len = length; + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ + c = initval; /* the previous hash value */ + + /*---------------------------------------- handle most of the key */ + while (len >= 12) + { + a += (k[0] +((ub4)UB1Traits::as_ub1(k[1])<<8) +((ub4)UB1Traits::as_ub1(k[2])<<16) +((ub4)UB1Traits::as_ub1(k[3])<<24)); + b += (k[4] +((ub4)UB1Traits::as_ub1(k[5])<<8) +((ub4)UB1Traits::as_ub1(k[6])<<16) +((ub4)UB1Traits::as_ub1(k[7])<<24)); + c += (k[8] +((ub4)UB1Traits::as_ub1(k[9])<<8) +((ub4)UB1Traits::as_ub1(k[10])<<16)+((ub4)UB1Traits::as_ub1(k[11])<<24)); + mix(a,b,c); + k += 12; len -= 12; + } + + /*------------------------------------- handle the last 11 bytes */ + c += length; + switch(len) /* all the case statements fall through */ + { + case 11: c += ((ub4)UB1Traits::as_ub1(k[10]) << 24); + case 10: c += ((ub4)UB1Traits::as_ub1(k[9]) << 16); + case 9 : c += ((ub4)UB1Traits::as_ub1(k[8]) << 8); + /* the first byte of c is reserved for the length */ + case 8 : b += ((ub4)UB1Traits::as_ub1(k[7]) << 24); + case 7 : b += ((ub4)UB1Traits::as_ub1(k[6]) << 16); + case 6 : b += ((ub4)UB1Traits::as_ub1(k[5]) << 8); + case 5 : b += UB1Traits::as_ub1(k[4]); + case 4 : a += ((ub4)UB1Traits::as_ub1(k[3]) << 24); + case 3 : a += ((ub4)UB1Traits::as_ub1(k[2]) << 16); + case 2 : a += ((ub4)UB1Traits::as_ub1(k[1]) << 8); + case 1 : a += UB1Traits::as_ub1(k[0]); + /* case 0: nothing left to add */ + } + mix(a,b,c); + /*-------------------------------------------- report the result */ + return c; +} + +/* +-------------------------------------------------------------------- + This works on all machines. hash2() is identical to hash() on + little-endian machines, except that the length has to be measured + in ub4s instead of bytes. It is much faster than hash(). It + requires + -- that the key be an array of ub4's, and + -- that all your machines have the same endianness, and + -- that the length be the number of ub4's in the key +-------------------------------------------------------------------- +*/ +template +inline ub4 hash2( +const ub4 *k, /* the key */ +ub4 length, /* the length of the key, in ub4s */ +ub4 initval, /* the previous hash, or an arbitrary value */ +const UB4Traits& ub4traits +) +{ + register ub4 a,b,c,len; + + /* Set up the internal state */ + len = length; + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ + c = initval; /* the previous hash value */ + + /*---------------------------------------- handle most of the key */ + while (len >= 3) + { + a += UB4Traits::as_ub4(k[0]); + b += UB4Traits::as_ub4(k[1]); + c += UB4Traits::as_ub4(k[2]); + mix(a,b,c); + k += 3; len -= 3; + } + + /*-------------------------------------- handle the last 2 ub4's */ + c += length; + switch(len) /* all the case statements fall through */ + { + /* c is reserved for the length */ + case 2 : b += UB4Traits::as_ub4(k[1]); + case 1 : a += UB4Traits::as_ub4(k[0]); + /* case 0: nothing left to add */ + } + mix(a,b,c); + /*-------------------------------------------- report the result */ + return c; +} + +typedef ub4 hash_t; + +inline hash_t hash_ub1(const ub1* key, std::size_t len, hash_t previous = 0) +{ + return hash(key, ub4(len), previous, ub1_default_traits(), ub1x4_default_traits()); +} + +inline hash_t hash_ub1_nocase(const ub1* key, std::size_t len, hash_t previous = 0) +{ + return hash(key, ub4(len), previous, ub1_nocase_traits(), ub1x4_nocase_traits()); +} + +template +inline hash_t hash_ub4(const ub4* key, std::size_t len, const UB4Traits& traits, hash_t previous = 0) +{ + return hash2(key,ub4(len), previous, traits); +} + +inline ub4 hash_combine(ub4 left, ub4 right) +{ + return hash_ub1(reinterpret_cast(&left), 4, right); +} + +template +inline hash_t pod_hash(const POD& pod) +{ + return hash_ub1(reinterpret_cast(&pod), sizeof(POD)); +} + +inline hash_t string_hash(const char* string, hash_t previous = 0) +{ + return hash_ub1(reinterpret_cast(string), string_length(string), previous); +} + +inline hash_t string_hash_nocase(const char* string, hash_t previous = 0) +{ + return hash_ub1_nocase(reinterpret_cast(string), string_length(string), previous); +} + +struct RawStringHash +{ + typedef hash_t hash_type; + hash_type operator()(const char* string) const + { + return string_hash(string); + } +}; + +struct HashString +{ + typedef hash_t hash_type; + hash_type operator()(const CopiedString& string) const + { + return string_hash(string.c_str()); + } +}; + +struct HashStringNoCase +{ + typedef hash_t hash_type; + hash_type operator()(const CopiedString& string) const + { + return string_hash_nocase(string.c_str()); + } +}; + +/// \brief Length of a string in ub4. +/// "wibble" (6) gives 2, +/// "and" (3) gives 1, +/// "bleh" (4) gives 2 +inline std::size_t string_length_ub4(const char* string) +{ + return ((string_length(string)>>2)+1)<<2; +} + +/// \brief Hashable key type that stores a string as an array of ub4 - making hashing faster. +/// Also caches the 32-bit result of the hash to speed up comparison of keys. +template +class HashKey +{ + Array m_key; + hash_t m_hash; + + void copy(const HashKey& other) + { + std::copy(other.m_key.begin(), other.m_key.end(), m_key.begin()); + m_hash = other.m_hash; + } + void copy(const char* string) + { + strncpy(reinterpret_cast(m_key.data()), string, m_key.size()); + for(Array::iterator i = m_key.begin(); i != m_key.end(); ++i) + { + *i = UB4Traits::as_ub4(*i); + } + m_hash = hash_ub4(m_key.data(), m_key.size(), ub4_default_traits()); + } + bool equal(const HashKey& other) const + { + return m_hash == other.m_hash && m_key.size() == other.m_key.size() + && std::equal(m_key.begin(), m_key.end(), other.m_key.begin()); + } + +public: + HashKey(const HashKey& other) : m_key(other.m_key.size()) + { + copy(other); + } + HashKey(const char* string) : m_key(string_length_ub4(string)) + { + copy(string); + } + HashKey& operator=(const char* string) + { + m_key.resize(string_length_ub4(string)); + copy(string); + return *this; + } + bool operator==(const HashKey& other) const + { + return equal(other); + } + bool operator!=(const HashKey& other) const + { + return !equal(other); + } + hash_t hash() const + { + return m_hash; + } +#if 0 + const char* c_str() const + { + return reinterpret_cast(m_key.data()); + } +#endif +}; + +/// \brief Hash function to use with HashKey. +struct HashKeyHasher +{ + typedef hash_t hash_type; + hash_type operator()(const HashKey& key) const + { + return key.hash(); + } +}; + + + +#endif diff --git a/libs/container/hashtable.cpp b/libs/container/hashtable.cpp new file mode 100644 index 00000000..e4d02c93 --- /dev/null +++ b/libs/container/hashtable.cpp @@ -0,0 +1,65 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "hashtable.h" + +#if defined(_DEBUG) || defined(DOXYGEN) + +#include "hashfunc.h" + +namespace ExampleHashTable +{ + void testStuff() + { + // HashTable example + typedef HashTable MyHashTable; + MyHashTable hashtable; + hashtable["bleh"] = 5; + hashtable.insert("blah", 17); + hashtable["foo"] = 99; + hashtable.insert("bar", 23); + + int bleh = (*hashtable.find("bleh")).value; // 5 + int blah = hashtable["blah"]; // 17 + hashtable.erase("foo"); + MyHashTable::iterator barIter = hashtable.find("bar"); + hashtable.erase(barIter); + + for(MyHashTable::iterator i = hashtable.begin(); i != hashtable.end(); ++i) + { + if((*i).key != "bleh") + { + ++hashtable["count"]; // insertion does not invalidate iterators + } + } + // end example + } + + struct Always + { + Always() + { + testStuff(); + } + } always; +} + +#endif diff --git a/libs/container/hashtable.h b/libs/container/hashtable.h new file mode 100644 index 00000000..88d672cf --- /dev/null +++ b/libs/container/hashtable.h @@ -0,0 +1,474 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_CONTAINER_HASHTABLE_H) +#define INCLUDED_CONTAINER_HASHTABLE_H + +#include +#include +#include +#include "debugging/debugging.h" + + +namespace HashTableDetail +{ + inline std::size_t next_power_of_two(std::size_t size) + { + std::size_t result = 1; + while(result < size) + { + result <<= 1; + } + return result; + } + + struct BucketNodeBase + { + BucketNodeBase* next; + BucketNodeBase* prev; + }; + + inline void list_initialise(BucketNodeBase& self) + { + self.next = self.prev = &self; + } + + inline void list_swap(BucketNodeBase& self, BucketNodeBase& other) + { + BucketNodeBase tmp(self); + if(other.next == &other) + { + list_initialise(self); + } + else + { + self = other; + self.next->prev = self.prev->next = &self; + } + if(tmp.next == &self) + { + list_initialise(other); + } + else + { + other = tmp; + other.next->prev = other.prev->next = &other; + } + } + + inline void node_link(BucketNodeBase* node, BucketNodeBase* next) + { + node->next = next; + node->prev = next->prev; + next->prev = node; + node->prev->next = node; + } + inline void node_unlink(BucketNodeBase* node) + { + node->prev->next = node->next; + node->next->prev = node->prev; + } + + template + struct KeyValue + { + const Key key; + Value value; + + KeyValue(const Key& key_, const Value& value_) + : key(key_), value(value_) + { + } + }; + + template + struct BucketNode : public BucketNodeBase + { + Hash m_hash; + KeyValue m_value; + + BucketNode(Hash hash, const Key& key, const Value& value) + : m_hash(hash), m_value(key, value) + { + } + BucketNode* getNext() const + { + return static_cast(next); + } + BucketNode* getPrev() const + { + return static_cast(prev); + } + }; + + template + class BucketIterator + { + typedef BucketNode Node; + Node* m_node; + + void increment() + { + m_node = m_node->getNext(); + } + + public: + typedef std::forward_iterator_tag iterator_category; + typedef std::ptrdiff_t difference_type; + typedef difference_type distance_type; + typedef KeyValue value_type; + typedef value_type* pointer; + typedef value_type& reference; + + BucketIterator(Node* node) : m_node(node) + { + } + + Node* node() + { + return m_node; + } + + bool operator==(const BucketIterator& other) const + { + return m_node == other.m_node; + } + bool operator!=(const BucketIterator& other) const + { + return !operator==(other); + } + BucketIterator& operator++() + { + increment(); + return *this; + } + BucketIterator operator++(int) + { + BucketIterator tmp = *this; + increment(); + return tmp; + } + value_type& operator*() const + { + return m_node->m_value; + } + value_type* operator->() const + { + return &(operator*()); + } + }; +} + + +/// A hash-table container which maps keys to values. +/// +/// - Inserting or removing elements does not invalidate iterators. +/// - Inserting or retrieving an element for a given key takes O(1) time on average. +/// - Elements are stored in no particular order. +/// +/// \param Key Uniquely identifies a value. Must provide a copy-constructor. +/// \param Value The value to be stored . Must provide a default-constructor and a copy-constructor. +/// \param Hasher Must provide 'std::size_t operator()(const Key&) const' which always returns the same result if the same argument is given. +/// \param KeyEqual Must provide 'bool operator==(const Key&, const Key&) const' which returns true only if both arguments are equal. +/// +/// \dontinclude container/hashtable.cpp +/// \skipline HashTable example +/// \until end example +template > +class HashTable : private KeyEqual, private Hasher +{ + typedef typename Hasher::hash_type hash_type; + typedef HashTableDetail::KeyValue KeyValue; + typedef HashTableDetail::BucketNode BucketNode; + + inline BucketNode* node_create(hash_type hash, const Key& key, const Value& value) + { + return new BucketNode(hash, key, value); + } + inline void node_destroy(BucketNode* node) + { + delete node; + } + + typedef BucketNode* Bucket; + + static Bucket* buckets_new(std::size_t count) + { + Bucket* buckets = new Bucket[count]; + std::uninitialized_fill(buckets, buckets + count, Bucket(0)); + return buckets; + } + static void buckets_delete(Bucket* buckets) + { + delete[] buckets; + } + + std::size_t m_bucketCount; + Bucket* m_buckets; + std::size_t m_size; + HashTableDetail::BucketNodeBase m_list; + + BucketNode* getFirst() + { + return static_cast(m_list.next); + } + BucketNode* getLast() + { + return static_cast(&m_list); + } + +public: + + typedef KeyValue value_type; + typedef HashTableDetail::BucketIterator iterator; + +private: + + void initialise() + { + list_initialise(m_list); + } + hash_type hashKey(const Key& key) + { + return Hasher::operator()(key); + } + + std::size_t getBucketId(hash_type hash) const + { + return hash & (m_bucketCount - 1); + } + Bucket& getBucket(hash_type hash) + { + return m_buckets[getBucketId(hash)]; + } + BucketNode* bucket_find(Bucket bucket, hash_type hash, const Key& key) + { + std::size_t bucketId = getBucketId(hash); + for(iterator i(bucket); i != end(); ++i) + { + hash_type nodeHash = i.node()->m_hash; + + if(getBucketId(nodeHash) != bucketId) + { + return 0; + } + + if(nodeHash == hash && KeyEqual::operator()((*i).key, key)) + { + return i.node(); + } + } + return 0; + } + BucketNode* bucket_insert(Bucket& bucket, BucketNode* node) + { + // link node into list + node_link(node, bucket_next(bucket)); + bucket = node; + return node; + } + BucketNode* bucket_next(Bucket& bucket) + { + Bucket* end = m_buckets + m_bucketCount; + for(Bucket* i = &bucket; i != end; ++i) + { + if(*i != 0) + { + return *i; + } + } + return getLast(); + } + + void buckets_resize(std::size_t count) + { + BucketNode* first = getFirst(); + BucketNode* last = getLast(); + + buckets_delete(m_buckets); + + m_bucketCount = count; + + m_buckets = buckets_new(m_bucketCount); + initialise(); + + for(BucketNode* i = first; i != last;) + { + BucketNode* node = i; + i = i->getNext(); + bucket_insert(getBucket((*node).m_hash), node); + } + } + void size_increment() + { + if(m_size == m_bucketCount) + { + buckets_resize(m_bucketCount == 0 ? 8 : m_bucketCount << 1); + } + ++m_size; + } + void size_decrement() + { + --m_size; + } + + HashTable(const HashTable& other); + HashTable& operator=(const HashTable& other); +public: + HashTable() : m_bucketCount(0), m_buckets(0), m_size(0) + { + initialise(); + } + HashTable(std::size_t bucketCount) : m_bucketCount(HashTableDetail::next_power_of_two(bucketCount)), m_buckets(buckets_new(m_bucketCount)), m_size(0) + { + initialise(); + } + ~HashTable() + { + for(BucketNode* i = getFirst(); i != getLast();) + { + BucketNode* node = i; + i = i->getNext(); + node_destroy(node); + } + buckets_delete(m_buckets); + } + + iterator begin() + { + return iterator(getFirst()); + } + iterator end() + { + return iterator(getLast()); + } + + bool empty() const + { + return m_size == 0; + } + std::size_t size() const + { + return m_size; + } + + /// \brief Returns an iterator pointing to the value associated with \p key if it is contained by the hash-table, else \c end(). + iterator find(const Key& key) + { + hash_type hash = hashKey(key); + if(m_bucketCount != 0) + { + Bucket bucket = getBucket(hash); + if(bucket != 0) + { + BucketNode* node = bucket_find(bucket, hash, key); + if(node != 0) + { + return iterator(node); + } + } + } + + return end(); + } + /// \brief Adds \p value to the hash-table associated with \p key if it does not exist. + iterator insert(const Key& key, const Value& value) + { + hash_type hash = hashKey(key); + if(m_bucketCount != 0) + { + Bucket& bucket = getBucket(hash); + if(bucket != 0) + { + BucketNode* node = bucket_find(bucket, hash, key); + if(node != 0) + { + return iterator(node); + } + } + } + + size_increment(); + return iterator(bucket_insert(getBucket(hash), node_create(hash, key, value))); + } + + /// \brief Removes the value pointed to by \p i from the hash-table. + /// + /// \p i must be a deferenceable iterator into the hash-table. + void erase(iterator i) + { + Bucket& bucket = getBucket(i.node()->m_hash); + BucketNode* node = i.node(); + + // if this was the last node in the bucket + if(bucket == node) + { + bucket = (node->getNext() == getLast() || &getBucket(node->getNext()->m_hash) != &bucket) ? 0 : node->getNext(); + } + + node_unlink(node); + ASSERT_MESSAGE(node != 0, "tried to erase a non-existent key/value"); + node_destroy(node); + + size_decrement(); + } + + /// \brief Returns the value identified by \p key if it is contained by the hash-table, else inserts and returns a new default-constructed value associated with \p key. + Value& operator[](const Key& key) + { + hash_type hash = hashKey(key); + if(m_bucketCount != 0) + { + Bucket& bucket = getBucket(hash); + if(bucket != 0) + { + BucketNode* node = bucket_find(bucket, hash, key); + if(node != 0) + { + return node->m_value.value; + } + } + } + size_increment(); + return bucket_insert(getBucket(hash), node_create(hash, key, Value()))->m_value.value; + } + /// \brief Removes the value associated with \p key from the hash-table. + void erase(const Key& key) + { + erase(find(key)); + } + /// \brief Swaps the contents of the hash-table with \p other. + void swap(HashTable& other) + { + std::swap(m_buckets, other.m_buckets); + std::swap(m_bucketCount, other.m_bucketCount); + std::swap(m_size, other.m_size); + HashTableDetail::list_swap(m_list, other.m_list); + } + /// \brief Removes all values from the hash-table. + void clear() + { + HashTable tmp; + tmp.swap(*this); + } +}; + +#endif diff --git a/libs/container/stack.cpp b/libs/container/stack.cpp new file mode 100644 index 00000000..3a7c7ce2 --- /dev/null +++ b/libs/container/stack.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "stack.h" + diff --git a/libs/container/stack.h b/libs/container/stack.h new file mode 100644 index 00000000..d83674c3 --- /dev/null +++ b/libs/container/stack.h @@ -0,0 +1,239 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_CONTAINER_STACK_H) +#define INCLUDED_CONTAINER_STACK_H + +#include "memory/allocator.h" +#include + +/// \brief A stack whose storage capacity is variable at run-time. Similar to std::vector. +/// +/// - Pushing or popping elements is a constant-time operation (on average). +/// - The storage capacity of the stack will grow when a new element is added beyond the current capacity. Iterators are invalidated when the storage capacity grows. +/// - DefaultConstructible, Copyable, Assignable. +/// - Compatible with the containers and algorithms in the Standard Template Library (STL) - http://www.sgi.com/tech/stl/ +/// +/// \param Type: The type to be stored in the stack. Must provide a copy-constructor. +template +class Stack : public DefaultAllocator +{ + typedef DefaultAllocator Allocator; + + enum + { + DEFAULT_CAPACITY = 4, + }; + + typedef Type* pointer; + typedef const Type* const_pointer; + +public: + typedef const_pointer const_iterator; +private: + + pointer m_data; + pointer m_end; + std::size_t m_capacity; + + + void insert(const Type& value) + { + Allocator::construct(m_end++, value); + } + void insert_overflow(const Type& value) + { + const std::size_t new_capacity = (m_capacity) ? m_capacity + m_capacity : std::size_t(DEFAULT_CAPACITY); + const pointer new_data = Allocator::allocate(new_capacity); + const pointer new_end = std::copy(m_data, m_end, new_data); + + destroy(); + Allocator::deallocate(m_data, m_capacity); + + m_capacity = new_capacity; + m_data = new_data; + m_end = new_end; + insert(value); + } + void destroy() + { + for(pointer p = m_data; p != m_end; ++p) + { + Allocator::destroy(p); + } + } + void construct(const Stack& other) + { + pointer p = m_data; + for(const_iterator i = other.begin(); i != other.end(); ++i) + { + Allocator::construct(p++, *i); + } + } + +public: + + Stack() : + m_data(0), + m_end(0), + m_capacity(0) + { + } + Stack(const Type& value) : + m_data(0), + m_end(0), + m_capacity(0) + { + push(value); + } + Stack(const Stack& other) : + DefaultAllocator(other) + { + m_capacity = other.m_capacity; + m_data = Allocator::allocate(m_capacity); + construct(other); + m_end = m_data + other.size(); + } + ~Stack() + { + destroy(); + Allocator::deallocate(m_data, m_capacity); + } + + const_iterator begin() const + { + return m_data; + } + const_iterator end() const + { + return m_end; + } + + bool empty() const + { + return end() == begin(); + } + void clear() + { + destroy(); + m_end = m_data; + } + + std::size_t size() const + { + return m_end - m_data; + } + Type operator[](const std::size_t i) const + { + return m_data[i]; + } + /// \brief Pushes \p value onto the stack at the top element. If reserved storage is insufficient for the new element, this will invalidate all iterators. + void push(const Type& value) + { + if(size() == m_capacity) + { + insert_overflow(value); + } + else + { + insert(value); + } + } + /// \brief Removes the top element of the stack. + void pop() + { + Allocator::destroy(--m_end); + } + /// \brief Returns the top element of the mutable stack. + Type& top() + { + return *(m_end-1); + } + /// \brief Returns the top element of the non-mutable stack. + const Type& top() const + { + return *(m_end-1); + } + /// \brief Returns the element below the top element of the mutable stack. + Type& parent() + { + return *(m_end-2); + } + /// \brief Returns the element below the top element of the non-mutable stack. + const Type& parent() const + { + return *(m_end-2); + } + /// \brief Swaps the values of this stack and \p other. + void swap(Stack& other) + { + std::swap(m_data, other.m_data); + std::swap(m_end, other.m_end); + std::swap(m_capacity, other.m_capacity); + } +#if 1 // use copy-swap technique + Stack& operator=(const Stack& other) + { + Stack temp(other); + temp.swap(*this); + return *this; + } +#else // avoids memory allocation if capacity is already sufficient. + Stack& operator=(const Stack& other) + { + if(&other != this) + { + destroy(); + + if(other.size() > m_capacity) + { + Allocator::deallocate(m_data, m_capacity); + m_capacity = other.m_capacity; + m_data = Allocator::allocate(m_capacity); + } + m_end = m_data + other.size(); + + construct(other); + } + return *this; + } +#endif +}; + +/// \brief Returns true if \p self is lexicographically less than \p other. +template +inline bool operator<(const Stack& self, const Stack& other) +{ + return std::lexicographical_compare(self.begin(), self.end(), other.begin(), other.end()); +} + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap(). + template + inline void swap(Stack& self, Stack& other) + { + self.swap(other); + } +} + +#endif diff --git a/libs/convert.cpp b/libs/convert.cpp new file mode 100644 index 00000000..0e73aef7 --- /dev/null +++ b/libs/convert.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "convert.h" + diff --git a/libs/convert.h b/libs/convert.h new file mode 100644 index 00000000..f7f4f992 --- /dev/null +++ b/libs/convert.h @@ -0,0 +1,305 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_CONVERT_H) +#define INCLUDED_CONVERT_H + +/// \file +/// \brief Character encoding conversion. + +#include "debugging/debugging.h" +#include +#include +#include + +#include "character.h" + +/// \brief Returns the number of bytes required to represent \p character in UTF-8 encoding. +inline std::size_t utf8_character_length(const char* character) +{ + if((*character & 0xE0) == 0xC0) // 110xxxxx + { + return 2; + } + else if((*character & 0xF0) == 0xE0) // 1110xxxx + { + return 3; + } + else if((*character & 0xF8) == 0xF0) // 11110xxx + { + return 4; + } + else if((*character & 0xFC) == 0xF8) // 111110xx + { + return 5; + } + else if((*character & 0xFE) == 0xFC) // 1111110x + { + return 6; + } + ERROR_MESSAGE(""); + return 0; +} + +struct UTF8Character +{ + const char* buffer; + std::size_t length; + UTF8Character() : buffer(0), length(0) + { + } + UTF8Character(const char* bytes) : buffer(bytes), length(utf8_character_length(bytes)) + { + } +}; + +inline bool operator<(const UTF8Character& self, const UTF8Character& other) +{ + return std::lexicographical_compare(self.buffer, self.buffer + self.length, other.buffer, other.buffer + other.length); +} + +/// \brief Writes \p c to \p ostream in Hex form. Useful for debugging. +template +inline TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const UTF8Character& c) +{ + for(const char* p = c.buffer; p != c.buffer + c.length; ++p) + { + ostream << HexChar(*p); + } + return ostream; +} + + + +/// \brief The character-set encoding for the current C locale. +/// +/// Obtain the global instance with globalCharacterSet(). +class CharacterSet +{ + const char* m_charSet; +public: + CharacterSet() + { + if(g_get_charset(&m_charSet) != FALSE) + { + m_charSet = 0; + } + } + bool isUTF8() const + { + return m_charSet == 0; + } + const char* get() const + { + return m_charSet; + } +}; + +typedef LazyStatic GlobalCharacterSet; + +/// \brief Returns the global instance of CharacterSet. +inline CharacterSet& globalCharacterSet() +{ + return GlobalCharacterSet::instance(); +} + + +class UTF8CharacterToExtendedASCII +{ +public: + UTF8Character m_utf8; + char m_c; + UTF8CharacterToExtendedASCII() : m_c('\0') + { + } + UTF8CharacterToExtendedASCII(const UTF8Character& utf8, char c) : m_utf8(utf8), m_c(c) + { + } +}; + +inline bool operator<(const UTF8CharacterToExtendedASCII& self, const UTF8CharacterToExtendedASCII& other) +{ + return self.m_utf8 < other.m_utf8; +} + +inline std::size_t extended_ascii_to_index(char c) +{ + return static_cast(c & 0x7F); +} + +inline char extended_ascii_for_index(std::size_t i) +{ + return static_cast(i | 0x80); +} + +/// \brief The active extended-ascii character set encoding. +/// Performs UTF-8 encoding and decoding of extended-ascii characters. +/// +/// Obtain the global instance with globalExtendedASCIICharacterSet(). +class ExtendedASCIICharacterSet +{ + typedef char UTF8CharBuffer[6]; + UTF8CharBuffer m_converted[128]; + UTF8Character m_decodeMap[128]; + UTF8CharacterToExtendedASCII m_encodeMap[128]; +public: + ExtendedASCIICharacterSet() + { + if(!globalCharacterSet().isUTF8()) + { + GIConv descriptor = g_iconv_open("UTF-8", globalCharacterSet().get()); + for(std::size_t i = 1; i < 128; ++i) + { + char c = extended_ascii_for_index(i); + char* inbuf = &c; + std::size_t inbytesleft = 1; + char* outbuf = m_converted[i]; + std::size_t outbytesleft = 6; + if(g_iconv(descriptor, &inbuf, &inbytesleft, &outbuf, &outbytesleft) != (size_t)(-1)) + { + UTF8Character utf8(m_converted[i]); + m_decodeMap[i] = utf8; + m_encodeMap[i] = UTF8CharacterToExtendedASCII(utf8, c); + } + } + g_iconv_close(descriptor); + std::sort(m_encodeMap, m_encodeMap + 128); + } + } + /// \brief Prints the (up to) 128 characters in the current extended-ascii character set. + /// Useful for debugging. + void print() const + { + globalOutputStream() << "UTF-8 conversion required from charset: " << globalCharacterSet().get() << "\n"; + for(std::size_t i = 1; i < 128; ++i) + { + if(m_decodeMap[i].buffer != 0) + { + globalOutputStream() << extended_ascii_for_index(i) << " = " << m_decodeMap[i] << "\n"; + } + } + } + /// \brief Returns \p c decoded from extended-ascii to UTF-8. + /// \p c must be an extended-ascii character. + const UTF8Character& decode(char c) const + { + ASSERT_MESSAGE(!globalCharacterSet().isUTF8(), "locale is utf8, no conversion required"); + ASSERT_MESSAGE(!char_is_ascii(c), "decode: ascii character"); + ASSERT_MESSAGE(m_decodeMap[extended_ascii_to_index(c)].buffer != 0, "decode: invalid character: " << HexChar(c)); + return m_decodeMap[extended_ascii_to_index(c)]; + } + /// \brief Returns \p c encoded to extended-ascii from UTF-8. + /// \p c must map to an extended-ascii character. + char encode(const UTF8Character& c) const + { + ASSERT_MESSAGE(!globalCharacterSet().isUTF8(), "locale is utf8, no conversion required"); + ASSERT_MESSAGE(!char_is_ascii(*c.buffer), "encode: ascii character"); + std::pair range + = std::equal_range(m_encodeMap, m_encodeMap + 128, UTF8CharacterToExtendedASCII(c, 0)); + ASSERT_MESSAGE(range.first != range.second, "encode: invalid character: " << c); + return (*range.first).m_c; + } +}; + +typedef LazyStatic GlobalExtendedASCIICharacterSet; + +/// \brief Returns the global instance of ExtendedASCIICharacterSet. +inline ExtendedASCIICharacterSet& globalExtendedASCIICharacterSet() +{ + return GlobalExtendedASCIICharacterSet::instance(); +} + +class ConvertUTF8ToLocale +{ +public: + StringRange m_range; + ConvertUTF8ToLocale(const char* string) : m_range(StringRange(string, string + strlen(string))) + { + } + ConvertUTF8ToLocale(const StringRange& range) : m_range(range) + { + } +}; + +/// \brief Writes \p convert to \p ostream after encoding each character to extended-ascii from UTF-8. +template +inline TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const ConvertUTF8ToLocale& convert) +{ + if(globalCharacterSet().isUTF8()) + { + return ostream << convert.m_range; + } + + for(const char* p = convert.m_range.first; p != convert.m_range.last;) + { + if(!char_is_ascii(*p)) + { + UTF8Character c(p); + ostream << globalExtendedASCIICharacterSet().encode(c); + p += c.length; + } + else + { + ostream << *p++; + } + } + return ostream; +} + + +class ConvertLocaleToUTF8 +{ +public: + StringRange m_range; + ConvertLocaleToUTF8(const char* string) : m_range(StringRange(string, string + strlen(string))) + { + } + ConvertLocaleToUTF8(const StringRange& range) : m_range(range) + { + } +}; + +/// \brief Writes \p convert to \p ostream after decoding each character from extended-ascii to UTF-8. +template +inline TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const ConvertLocaleToUTF8& convert) +{ + if(globalCharacterSet().isUTF8()) + { + return ostream << convert.m_range; + } + + for(const char* p = convert.m_range.first; p != convert.m_range.last; ++p) + { + if(!char_is_ascii(*p)) + { + UTF8Character c(globalExtendedASCIICharacterSet().decode(*p)); + ostream.write(c.buffer, c.length); + } + else + { + ostream << *p; + } + } + return ostream; +} + + +#endif diff --git a/libs/ddslib.h b/libs/ddslib.h new file mode 100644 index 00000000..3f5f124c --- /dev/null +++ b/libs/ddslib.h @@ -0,0 +1,250 @@ +/* ----------------------------------------------------------------------------- + +DDS Library + +Based on code from Nvidia's DDS example: +http://www.nvidia.com/object/dxtc_decompression_code.html + +Copyright (c) 2003 Randy Reddig +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef DDSLIB_H +#define DDSLIB_H + + + +/* dependencies */ +#include +#include + + + +/* c++ marker */ +#ifdef __cplusplus +extern "C" +{ +#endif + + + +/* dds definition */ +typedef enum +{ + DDS_PF_ARGB8888, + DDS_PF_DXT1, + DDS_PF_DXT2, + DDS_PF_DXT3, + DDS_PF_DXT4, + DDS_PF_DXT5, + DDS_PF_UNKNOWN +} +ddsPF_t; + + +/* 16bpp stuff */ +#define DDS_LOW_5 0x001F; +#define DDS_MID_6 0x07E0; +#define DDS_HIGH_5 0xF800; +#define DDS_MID_555 0x03E0; +#define DDS_HI_555 0x7C00; + + +/* structures */ +typedef struct ddsColorKey_s +{ + unsigned int colorSpaceLowValue; + unsigned int colorSpaceHighValue; +} +ddsColorKey_t; + + +typedef struct ddsCaps_s +{ + unsigned int caps1; + unsigned int caps2; + unsigned int caps3; + unsigned int caps4; +} +ddsCaps_t; + + +typedef struct ddsMultiSampleCaps_s +{ + unsigned short flipMSTypes; + unsigned short bltMSTypes; +} +ddsMultiSampleCaps_t; + + +typedef struct ddsPixelFormat_s +{ + unsigned int size; + unsigned int flags; + unsigned int fourCC; + union + { + unsigned int rgbBitCount; + unsigned int yuvBitCount; + unsigned int zBufferBitDepth; + unsigned int alphaBitDepth; + unsigned int luminanceBitCount; + unsigned int bumpBitCount; + unsigned int privateFormatBitCount; + }; + union + { + unsigned int rBitMask; + unsigned int yBitMask; + unsigned int stencilBitDepth; + unsigned int luminanceBitMask; + unsigned int bumpDuBitMask; + unsigned int operations; + }; + union + { + unsigned int gBitMask; + unsigned int uBitMask; + unsigned int zBitMask; + unsigned int bumpDvBitMask; + ddsMultiSampleCaps_t multiSampleCaps; + }; + union + { + unsigned int bBitMask; + unsigned int vBitMask; + unsigned int stencilBitMask; + unsigned int bumpLuminanceBitMask; + }; + union + { + unsigned int rgbAlphaBitMask; + unsigned int yuvAlphaBitMask; + unsigned int luminanceAlphaBitMask; + unsigned int rgbZBitMask; + unsigned int yuvZBitMask; + }; +} +ddsPixelFormat_t; + + +typedef struct ddsBuffer_s +{ + /* magic: 'dds ' */ + char magic[ 4 ]; + + /* directdraw surface */ + unsigned int size; + unsigned int flags; + unsigned int height; + unsigned int width; + union + { + int pitch; + unsigned int linearSize; + }; + unsigned int backBufferCount; + union + { + unsigned int mipMapCount; + unsigned int refreshRate; + unsigned int srcVBHandle; + }; + unsigned int alphaBitDepth; + unsigned int reserved; + void *surface; + union + { + ddsColorKey_t ckDestOverlay; + unsigned int emptyFaceColor; + }; + ddsColorKey_t ckDestBlt; + ddsColorKey_t ckSrcOverlay; + ddsColorKey_t ckSrcBlt; + union + { + ddsPixelFormat_t pixelFormat; + unsigned int fvf; + }; + ddsCaps_t ddsCaps; + unsigned int textureStage; + + /* data (Varying size) */ + unsigned char data[ 4 ]; +} +ddsBuffer_t; + + +typedef struct ddsColorBlock_s +{ + unsigned short colors[ 2 ]; + unsigned char row[ 4 ]; +} +ddsColorBlock_t; + + +typedef struct ddsAlphaBlockExplicit_s +{ + unsigned short row[ 4 ]; +} +ddsAlphaBlockExplicit_t; + + +typedef struct ddsAlphaBlock3BitLinear_s +{ + unsigned char alpha0; + unsigned char alpha1; + unsigned char stuff[ 6 ]; +} +ddsAlphaBlock3BitLinear_t; + + +typedef struct ddsColor_s +{ + unsigned char r, g, b, a; +} +ddsColor_t; + + + +/* public functions */ +int DDSGetInfo( ddsBuffer_t *dds, int *width, int *height, ddsPF_t *pf ); +int DDSDecompress( ddsBuffer_t *dds, unsigned char *pixels ); + + + +/* end marker */ +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libs/ddslib/ddslib.c b/libs/ddslib/ddslib.c new file mode 100644 index 00000000..bf1a9f15 --- /dev/null +++ b/libs/ddslib/ddslib.c @@ -0,0 +1,781 @@ +/* ----------------------------------------------------------------------------- + +DDS Library + +Based on code from Nvidia's DDS example: +http://www.nvidia.com/object/dxtc_decompression_code.html + +Copyright (c) 2003 Randy Reddig +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define DDSLIB_C + + + +/* dependencies */ +#include "ddslib.h" + + + +/* endian tomfoolery */ +typedef union +{ + float f; + char c[ 4 ]; +} +floatSwapUnion; + + +#ifndef __BIG_ENDIAN__ + #ifdef _SGI_SOURCE + #define __BIG_ENDIAN__ + #endif +#endif + + +#ifdef __BIG_ENDIAN__ + + int DDSBigLong( int src ) { return src; } + short DDSBigShort( short src ) { return src; } + float DDSBigFloat( float src ) { return src; } + + int DDSLittleLong( int src ) + { + return ((src & 0xFF000000) >> 24) | + ((src & 0x00FF0000) >> 8) | + ((src & 0x0000FF00) << 8) | + ((src & 0x000000FF) << 24); + } + + short DDSLittleShort( short src ) + { + return ((src & 0xFF00) >> 8) | + ((src & 0x00FF) << 8); + } + + float DDSLittleFloat( float src ) + { + floatSwapUnion in,out; + in.f = src; + out.c[ 0 ] = in.c[ 3 ]; + out.c[ 1 ] = in.c[ 2 ]; + out.c[ 2 ] = in.c[ 1 ]; + out.c[ 3 ] = in.c[ 0 ]; + return out.f; + } + +#else /*__BIG_ENDIAN__*/ + + int DDSLittleLong( int src ) { return src; } + short DDSLittleShort( short src ) { return src; } + float DDSLittleFloat( float src ) { return src; } + + int DDSBigLong( int src ) + { + return ((src & 0xFF000000) >> 24) | + ((src & 0x00FF0000) >> 8) | + ((src & 0x0000FF00) << 8) | + ((src & 0x000000FF) << 24); + } + + short DDSBigShort( short src ) + { + return ((src & 0xFF00) >> 8) | + ((src & 0x00FF) << 8); + } + + float DDSBigFloat( float src ) + { + floatSwapUnion in,out; + in.f = src; + out.c[ 0 ] = in.c[ 3 ]; + out.c[ 1 ] = in.c[ 2 ]; + out.c[ 2 ] = in.c[ 1 ]; + out.c[ 3 ] = in.c[ 0 ]; + return out.f; + } + +#endif /*__BIG_ENDIAN__*/ + + + +/* +DDSDecodePixelFormat() +determines which pixel format the dds texture is in +*/ + +static void DDSDecodePixelFormat( ddsBuffer_t *dds, ddsPF_t *pf ) +{ + unsigned int fourCC; + + + /* dummy check */ + if( dds == NULL || pf == NULL ) + return; + + /* extract fourCC */ + fourCC = dds->pixelFormat.fourCC; + + /* test it */ + if( fourCC == 0 ) + *pf = DDS_PF_ARGB8888; + else if( fourCC == *((unsigned int*) "DXT1") ) + *pf = DDS_PF_DXT1; + else if( fourCC == *((unsigned int*) "DXT2") ) + *pf = DDS_PF_DXT2; + else if( fourCC == *((unsigned int*) "DXT3") ) + *pf = DDS_PF_DXT3; + else if( fourCC == *((unsigned int*) "DXT4") ) + *pf = DDS_PF_DXT4; + else if( fourCC == *((unsigned int*) "DXT5") ) + *pf = DDS_PF_DXT5; + else + *pf = DDS_PF_UNKNOWN; +} + + + +/* +DDSGetInfo() +extracts relevant info from a dds texture, returns 0 on success +*/ + +int DDSGetInfo( ddsBuffer_t *dds, int *width, int *height, ddsPF_t *pf ) +{ + /* dummy test */ + if( dds == NULL ) + return -1; + + /* test dds header */ + if( *((int*) dds->magic) != *((int*) "DDS ") ) + return -1; + if( DDSLittleLong( dds->size ) != 124 ) + return -1; + + /* extract width and height */ + if( width != NULL ) + *width = DDSLittleLong( dds->width ); + if( height != NULL ) + *height = DDSLittleLong( dds->height ); + + /* get pixel format */ + DDSDecodePixelFormat( dds, pf ); + + /* return ok */ + return 0; +} + + + +/* +DDSGetColorBlockColors() +extracts colors from a dds color block +*/ + +static void DDSGetColorBlockColors( ddsColorBlock_t *block, ddsColor_t colors[ 4 ] ) +{ + unsigned short word; + + + /* color 0 */ + word = DDSLittleShort( block->colors[ 0 ] ); + colors[ 0 ].a = 0xff; + + /* extract rgb bits */ + colors[ 0 ].b = (unsigned char) word; + colors[ 0 ].b <<= 3; + colors[ 0 ].b |= (colors[ 0 ].b >> 5); + word >>= 5; + colors[ 0 ].g = (unsigned char) word; + colors[ 0 ].g <<= 2; + colors[ 0 ].g |= (colors[ 0 ].g >> 5); + word >>= 6; + colors[ 0 ].r = (unsigned char) word; + colors[ 0 ].r <<= 3; + colors[ 0 ].r |= (colors[ 0 ].r >> 5); + + /* same for color 1 */ + word = DDSLittleShort( block->colors[ 1 ] ); + colors[ 1 ].a = 0xff; + + /* extract rgb bits */ + colors[ 1 ].b = (unsigned char) word; + colors[ 1 ].b <<= 3; + colors[ 1 ].b |= (colors[ 1 ].b >> 5); + word >>= 5; + colors[ 1 ].g = (unsigned char) word; + colors[ 1 ].g <<= 2; + colors[ 1 ].g |= (colors[ 1 ].g >> 5); + word >>= 6; + colors[ 1 ].r = (unsigned char) word; + colors[ 1 ].r <<= 3; + colors[ 1 ].r |= (colors[ 1 ].r >> 5); + + /* use this for all but the super-freak math method */ + if( block->colors[ 0 ] > block->colors[ 1 ] ) + { + /* four-color block: derive the other two colors. + 00 = color 0, 01 = color 1, 10 = color 2, 11 = color 3 + these two bit codes correspond to the 2-bit fields + stored in the 64-bit block. */ + + word = ((unsigned short) colors[ 0 ].r * 2 + (unsigned short) colors[ 1 ].r ) / 3; + /* no +1 for rounding */ + /* as bits have been shifted to 888 */ + colors[ 2 ].r = (unsigned char) word; + word = ((unsigned short) colors[ 0 ].g * 2 + (unsigned short) colors[ 1 ].g) / 3; + colors[ 2 ].g = (unsigned char) word; + word = ((unsigned short) colors[ 0 ].b * 2 + (unsigned short) colors[ 1 ].b) / 3; + colors[ 2 ].b = (unsigned char) word; + colors[ 2 ].a = 0xff; + + word = ((unsigned short) colors[ 0 ].r + (unsigned short) colors[ 1 ].r * 2) / 3; + colors[ 3 ].r = (unsigned char) word; + word = ((unsigned short) colors[ 0 ].g + (unsigned short) colors[ 1 ].g * 2) / 3; + colors[ 3 ].g = (unsigned char) word; + word = ((unsigned short) colors[ 0 ].b + (unsigned short) colors[ 1 ].b * 2) / 3; + colors[ 3 ].b = (unsigned char) word; + colors[ 3 ].a = 0xff; + } + else + { + /* three-color block: derive the other color. + 00 = color 0, 01 = color 1, 10 = color 2, + 11 = transparent. + These two bit codes correspond to the 2-bit fields + stored in the 64-bit block */ + + word = ((unsigned short) colors[ 0 ].r + (unsigned short) colors[ 1 ].r) / 2; + colors[ 2 ].r = (unsigned char) word; + word = ((unsigned short) colors[ 0 ].g + (unsigned short) colors[ 1 ].g) / 2; + colors[ 2 ].g = (unsigned char) word; + word = ((unsigned short) colors[ 0 ].b + (unsigned short) colors[ 1 ].b) / 2; + colors[ 2 ].b = (unsigned char) word; + colors[ 2 ].a = 0xff; + + /* random color to indicate alpha */ + colors[ 3 ].r = 0x00; + colors[ 3 ].g = 0xff; + colors[ 3 ].b = 0xff; + colors[ 3 ].a = 0x00; + } +} + + + +/* +DDSDecodeColorBlock() +decodes a dds color block +fixme: make endian-safe +*/ + +static void DDSDecodeColorBlock( unsigned int *pixel, ddsColorBlock_t *block, int width, unsigned int colors[ 4 ] ) +{ + int r, n; + unsigned int bits; + unsigned int masks[] = { 3, 12, 3 << 4, 3 << 6 }; /* bit masks = 00000011, 00001100, 00110000, 11000000 */ + int shift[] = { 0, 2, 4, 6 }; + + + /* r steps through lines in y */ + for( r = 0; r < 4; r++, pixel += (width - 4) ) /* no width * 4 as unsigned int ptr inc will * 4 */ + { + /* width * 4 bytes per pixel per line, each j dxtc row is 4 lines of pixels */ + + /* n steps through pixels */ + for( n = 0; n < 4; n++ ) + { + bits = block->row[ r ] & masks[ n ]; + bits >>= shift[ n ]; + + switch( bits ) + { + case 0: + *pixel = colors[ 0 ]; + pixel++; + break; + + case 1: + *pixel = colors[ 1 ]; + pixel++; + break; + + case 2: + *pixel = colors[ 2 ]; + pixel++; + break; + + case 3: + *pixel = colors[ 3 ]; + pixel++; + break; + + default: + /* invalid */ + pixel++; + break; + } + } + } +} + + + +/* +DDSDecodeAlphaExplicit() +decodes a dds explicit alpha block +*/ + +static void DDSDecodeAlphaExplicit( unsigned int *pixel, ddsAlphaBlockExplicit_t *alphaBlock, int width, unsigned int alphaZero ) +{ + int row, pix; + unsigned short word; + ddsColor_t color; + + + /* clear color */ + color.r = 0; + color.g = 0; + color.b = 0; + + /* walk rows */ + for( row = 0; row < 4; row++, pixel += (width - 4) ) + { + word = DDSLittleShort( alphaBlock->row[ row ] ); + + /* walk pixels */ + for( pix = 0; pix < 4; pix++ ) + { + /* zero the alpha bits of image pixel */ + *pixel &= alphaZero; + color.a = word & 0x000F; + color.a = color.a | (color.a << 4); + *pixel |= *((unsigned int*) &color); + word >>= 4; /* move next bits to lowest 4 */ + pixel++; /* move to next pixel in the row */ + + } + } +} + + + +/* +DDSDecodeAlpha3BitLinear() +decodes interpolated alpha block +*/ + +static void DDSDecodeAlpha3BitLinear( unsigned int *pixel, ddsAlphaBlock3BitLinear_t *alphaBlock, int width, unsigned int alphaZero ) +{ + + int row, pix; + unsigned int stuff; + unsigned char bits[ 4 ][ 4 ]; + unsigned short alphas[ 8 ]; + ddsColor_t aColors[ 4 ][ 4 ]; + + + /* get initial alphas */ + alphas[ 0 ] = alphaBlock->alpha0; + alphas[ 1 ] = alphaBlock->alpha1; + + /* 8-alpha block */ + if( alphas[ 0 ] > alphas[ 1 ] ) + { + /* 000 = alpha_0, 001 = alpha_1, others are interpolated */ + alphas[ 2 ] = ( 6 * alphas[ 0 ] + alphas[ 1 ]) / 7; /* bit code 010 */ + alphas[ 3 ] = ( 5 * alphas[ 0 ] + 2 * alphas[ 1 ]) / 7; /* bit code 011 */ + alphas[ 4 ] = ( 4 * alphas[ 0 ] + 3 * alphas[ 1 ]) / 7; /* bit code 100 */ + alphas[ 5 ] = ( 3 * alphas[ 0 ] + 4 * alphas[ 1 ]) / 7; /* bit code 101 */ + alphas[ 6 ] = ( 2 * alphas[ 0 ] + 5 * alphas[ 1 ]) / 7; /* bit code 110 */ + alphas[ 7 ] = ( alphas[ 0 ] + 6 * alphas[ 1 ]) / 7; /* bit code 111 */ + } + + /* 6-alpha block */ + else + { + /* 000 = alpha_0, 001 = alpha_1, others are interpolated */ + alphas[ 2 ] = (4 * alphas[ 0 ] + alphas[ 1 ]) / 5; /* bit code 010 */ + alphas[ 3 ] = (3 * alphas[ 0 ] + 2 * alphas[ 1 ]) / 5; /* bit code 011 */ + alphas[ 4 ] = (2 * alphas[ 0 ] + 3 * alphas[ 1 ]) / 5; /* bit code 100 */ + alphas[ 5 ] = ( alphas[ 0 ] + 4 * alphas[ 1 ]) / 5; /* bit code 101 */ + alphas[ 6 ] = 0; /* bit code 110 */ + alphas[ 7 ] = 255; /* bit code 111 */ + } + + /* decode 3-bit fields into array of 16 bytes with same value */ + + /* first two rows of 4 pixels each */ + stuff = *((unsigned int*) &(alphaBlock->stuff[ 0 ])); + + bits[ 0 ][ 0 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 0 ][ 1 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 0 ][ 2 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 0 ][ 3 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 1 ][ 0 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 1 ][ 1 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 1 ][ 2 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 1 ][ 3 ] = (unsigned char) (stuff & 0x00000007); + + /* last two rows */ + stuff = *((unsigned int*) &(alphaBlock->stuff[ 3 ])); /* last 3 bytes */ + + bits[ 2 ][ 0 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 2 ][ 1 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 2 ][ 2 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 2 ][ 3 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 3 ][ 0 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 3 ][ 1 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 3 ][ 2 ] = (unsigned char) (stuff & 0x00000007); + stuff >>= 3; + bits[ 3 ][ 3 ] = (unsigned char) (stuff & 0x00000007); + + /* decode the codes into alpha values */ + for( row = 0; row < 4; row++ ) + { + for( pix=0; pix < 4; pix++ ) + { + aColors[ row ][ pix ].r = 0; + aColors[ row ][ pix ].g = 0; + aColors[ row ][ pix ].b = 0; + aColors[ row ][ pix ].a = (unsigned char) alphas[ bits[ row ][ pix ] ]; + } + } + + /* write out alpha values to the image bits */ + for( row = 0; row < 4; row++, pixel += width-4 ) + { + for( pix = 0; pix < 4; pix++ ) + { + /* zero the alpha bits of image pixel */ + *pixel &= alphaZero; + + /* or the bits into the prev. nulled alpha */ + *pixel |= *((unsigned int*) &(aColors[ row ][ pix ])); + pixel++; + } + } +} + + + +/* +DDSDecompressDXT1() +decompresses a dxt1 format texture +*/ + +static int DDSDecompressDXT1( ddsBuffer_t *dds, int width, int height, unsigned char *pixels ) +{ + int x, y, xBlocks, yBlocks; + unsigned int *pixel; + ddsColorBlock_t *block; + ddsColor_t colors[ 4 ]; + + + /* setup */ + xBlocks = width / 4; + yBlocks = height / 4; + + /* walk y */ + for( y = 0; y < yBlocks; y++ ) + { + /* 8 bytes per block */ + block = (ddsColorBlock_t*) ((unsigned int) dds->data + y * xBlocks * 8); + + /* walk x */ + for( x = 0; x < xBlocks; x++, block++ ) + { + DDSGetColorBlockColors( block, colors ); + pixel = (unsigned int*) (pixels + x * 16 + (y * 4) * width * 4); + DDSDecodeColorBlock( pixel, block, width, (unsigned int*) colors ); + } + } + + /* return ok */ + return 0; +} + + + +/* +DDSDecompressDXT3() +decompresses a dxt3 format texture +*/ + +static int DDSDecompressDXT3( ddsBuffer_t *dds, int width, int height, unsigned char *pixels ) +{ + int x, y, xBlocks, yBlocks; + unsigned int *pixel, alphaZero; + ddsColorBlock_t *block; + ddsAlphaBlockExplicit_t *alphaBlock; + ddsColor_t colors[ 4 ]; + + + /* setup */ + xBlocks = width / 4; + yBlocks = height / 4; + + /* create zero alpha */ + colors[ 0 ].a = 0; + colors[ 0 ].r = 0xFF; + colors[ 0 ].g = 0xFF; + colors[ 0 ].b = 0xFF; + alphaZero = *((unsigned int*) &colors[ 0 ]); + + /* walk y */ + for( y = 0; y < yBlocks; y++ ) + { + /* 8 bytes per block, 1 block for alpha, 1 block for color */ + block = (ddsColorBlock_t*) ((unsigned int) dds->data + y * xBlocks * 16); + + /* walk x */ + for( x = 0; x < xBlocks; x++, block++ ) + { + /* get alpha block */ + alphaBlock = (ddsAlphaBlockExplicit_t*) block; + + /* get color block */ + block++; + DDSGetColorBlockColors( block, colors ); + + /* decode color block */ + pixel = (unsigned int*) (pixels + x * 16 + (y * 4) * width * 4); + DDSDecodeColorBlock( pixel, block, width, (unsigned int*) colors ); + + /* overwrite alpha bits with alpha block */ + DDSDecodeAlphaExplicit( pixel, alphaBlock, width, alphaZero ); + } + } + + /* return ok */ + return 0; +} + + + +/* +DDSDecompressDXT5() +decompresses a dxt5 format texture +*/ + +static int DDSDecompressDXT5( ddsBuffer_t *dds, int width, int height, unsigned char *pixels ) +{ + int x, y, xBlocks, yBlocks; + unsigned int *pixel, alphaZero; + ddsColorBlock_t *block; + ddsAlphaBlock3BitLinear_t *alphaBlock; + ddsColor_t colors[ 4 ]; + + + /* setup */ + xBlocks = width / 4; + yBlocks = height / 4; + + /* create zero alpha */ + colors[ 0 ].a = 0; + colors[ 0 ].r = 0xFF; + colors[ 0 ].g = 0xFF; + colors[ 0 ].b = 0xFF; + alphaZero = *((unsigned int*) &colors[ 0 ]); + + /* walk y */ + for( y = 0; y < yBlocks; y++ ) + { + /* 8 bytes per block, 1 block for alpha, 1 block for color */ + block = (ddsColorBlock_t*) ((unsigned int) dds->data + y * xBlocks * 16); + + /* walk x */ + for( x = 0; x < xBlocks; x++, block++ ) + { + /* get alpha block */ + alphaBlock = (ddsAlphaBlock3BitLinear_t*) block; + + /* get color block */ + block++; + DDSGetColorBlockColors( block, colors ); + + /* decode color block */ + pixel = (unsigned int*) (pixels + x * 16 + (y * 4) * width * 4); + DDSDecodeColorBlock( pixel, block, width, (unsigned int*) colors ); + + /* overwrite alpha bits with alpha block */ + DDSDecodeAlpha3BitLinear( pixel, alphaBlock, width, alphaZero ); + } + } + + /* return ok */ + return 0; +} + + + +/* +DDSDecompressDXT2() +decompresses a dxt2 format texture (fixme: un-premultiply alpha) +*/ + +static int DDSDecompressDXT2( ddsBuffer_t *dds, int width, int height, unsigned char *pixels ) +{ + int r; + + + /* decompress dxt3 first */ + r = DDSDecompressDXT3( dds, width, height, pixels ); + + /* return to sender */ + return r; +} + + + +/* +DDSDecompressDXT4() +decompresses a dxt4 format texture (fixme: un-premultiply alpha) +*/ + +static int DDSDecompressDXT4( ddsBuffer_t *dds, int width, int height, unsigned char *pixels ) +{ + int r; + + + /* decompress dxt5 first */ + r = DDSDecompressDXT5( dds, width, height, pixels ); + + /* return to sender */ + return r; +} + + + +/* +DDSDecompressARGB8888() +decompresses an argb 8888 format texture +*/ + +static int DDSDecompressARGB8888( ddsBuffer_t *dds, int width, int height, unsigned char *pixels ) +{ + int x, y; + unsigned char *in, *out; + + + /* setup */ + in = dds->data; + out = pixels; + + /* walk y */ + for( y = 0; y < height; y++ ) + { + /* walk x */ + for( x = 0; x < width; x++ ) + { + *out++ = *in++; + *out++ = *in++; + *out++ = *in++; + *out++ = *in++; + } + } + + /* return ok */ + return 0; +} + + + +/* +DDSDecompress() +decompresses a dds texture into an rgba image buffer, returns 0 on success +*/ + +int DDSDecompress( ddsBuffer_t *dds, unsigned char *pixels ) +{ + int width, height, r; + ddsPF_t pf; + + + /* get dds info */ + r = DDSGetInfo( dds, &width, &height, &pf ); + if( r ) + return r; + + /* decompress */ + switch( pf ) + { + case DDS_PF_ARGB8888: + /* fixme: support other [a]rgb formats */ + r = DDSDecompressARGB8888( dds, width, height, pixels ); + break; + + case DDS_PF_DXT1: + r = DDSDecompressDXT1( dds, width, height, pixels ); + break; + + case DDS_PF_DXT2: + r = DDSDecompressDXT2( dds, width, height, pixels ); + break; + + case DDS_PF_DXT3: + r = DDSDecompressDXT3( dds, width, height, pixels ); + break; + + case DDS_PF_DXT4: + r = DDSDecompressDXT4( dds, width, height, pixels ); + break; + + case DDS_PF_DXT5: + r = DDSDecompressDXT5( dds, width, height, pixels ); + break; + + default: + case DDS_PF_UNKNOWN: + memset( pixels, 0xFF, width * height * 4 ); + r = -1; + break; + } + + /* return to sender */ + return r; +} + diff --git a/libs/ddslib/ddslib.vcproj b/libs/ddslib/ddslib.vcproj new file mode 100644 index 00000000..c692367a --- /dev/null +++ b/libs/ddslib/ddslib.vcproj @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/debugging/debugging.cpp b/libs/debugging/debugging.cpp new file mode 100644 index 00000000..23784950 --- /dev/null +++ b/libs/debugging/debugging.cpp @@ -0,0 +1,28 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "debugging.h" + +void TEST_ASSERT() +{ + ERROR_MESSAGE("test"); + ASSERT_NOTNULL(0); +} diff --git a/libs/debugging/debugging.h b/libs/debugging/debugging.h new file mode 100644 index 00000000..4c03e9d7 --- /dev/null +++ b/libs/debugging/debugging.h @@ -0,0 +1,137 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_DEBUGGING_DEBUGGING_H) +#define INCLUDED_DEBUGGING_DEBUGGING_H + +/// \file +/// \brief Debugging macros for fatal error/assert messages. + +#include "stream/textstream.h" +#include "warnings.h" +#include "generic/static.h" + +#if defined(_MSC_VER) && defined(_M_IX86) +#define DEBUGGER_BREAKPOINT() __asm { int 3 } +#elif defined (__i386__) && defined (__GNUC__) && __GNUC__ >= 2 +#define DEBUGGER_BREAKPOINT() __asm__ __volatile__ ("int $03") +#else +#include + +#define DEBUGGER_BREAKPOINT() raise(SIGTRAP); +#endif + +#define STR(x) #x +#define STR2(x) STR(x) +#define FILE_LINE __FILE__ ":" STR2(__LINE__) + +#if defined(_DEBUG) || 1 +#define DEBUG_ASSERTS +#endif + +class DebugMessageHandler +{ +public: + virtual TextOutputStream& getOutputStream() = 0; + virtual bool handleMessage() = 0; +}; + +class NullDebugMessageHandler : public NullOutputStream, public DebugMessageHandler +{ +public: + virtual TextOutputStream& getOutputStream() + { + return *this; + } + virtual bool handleMessage() + { + return false; + } +}; + +class DefaultDebugMessageHandler : public DebugMessageHandler +{ +public: + virtual TextOutputStream& getOutputStream() + { + return globalErrorStream(); + } + virtual bool handleMessage() + { +#if defined(_DEBUG) + return false; // send debug-break +#else + return true; +#endif + } +}; + +class DebugMessageHandlerRef : public DefaultDebugMessageHandler +{ + DebugMessageHandler* m_handler; +public: + DebugMessageHandlerRef() + : m_handler(this) + { + } + void setHandler(DebugMessageHandler& handler) + { + m_handler = &handler; + } + DebugMessageHandler& getHandler() + { + return *m_handler; + } +}; + +typedef Static GlobalDebugMessageHandler; + +inline DebugMessageHandler& globalDebugMessageHandler() +{ + return GlobalDebugMessageHandler::instance().getHandler(); +} + +#if defined(DEBUG_ASSERTS) + +/// \brief Sends a \p message to the current debug-message-handler text-output-stream if \p condition evaluates to false. +#define ASSERT_MESSAGE(condition, message) do{\ +if(!(condition))\ +{\ + globalDebugMessageHandler().getOutputStream() << FILE_LINE "\nassertion failure: " << message << "\n";\ + if(!globalDebugMessageHandler().handleMessage()) { DEBUGGER_BREAKPOINT(); }\ +}} while(0) + +/// \brief Sends a \p message to the current debug-message-handler text-output-stream. +#define ERROR_MESSAGE(message) do{\ +globalDebugMessageHandler().getOutputStream() << FILE_LINE "\nruntime error: " << message << "\n";\ +if(!globalDebugMessageHandler().handleMessage()) { DEBUGGER_BREAKPOINT(); }} while(0) + +#define ASSERT_NOTNULL(ptr) ASSERT_MESSAGE(ptr != 0, "pointer \"" #ptr "\" is null") + +#else + +#define ASSERT_MESSAGE(condition, message) +#define ERROR_MESSAGE(message) +#define ASSERT_NOTNULL(ptr) + +#endif + +#endif diff --git a/libs/dragplanes.cpp b/libs/dragplanes.cpp new file mode 100644 index 00000000..9c7e18f8 --- /dev/null +++ b/libs/dragplanes.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "dragplanes.h" + diff --git a/libs/dragplanes.h b/libs/dragplanes.h new file mode 100644 index 00000000..a8b54dc1 --- /dev/null +++ b/libs/dragplanes.h @@ -0,0 +1,259 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_DRAGPLANES_H) +#define INCLUDED_DRAGPLANES_H + +#include "selectable.h" +#include "selectionlib.h" +#include "math/aabb.h" +#include "math/line.h" + +// local must be a pure rotation +inline Vector3 translation_to_local(const Vector3& translation, const Matrix4& local) +{ + return matrix4_get_translation_vec3( + matrix4_multiplied_by_matrix4( + matrix4_translated_by_vec3(matrix4_transposed(local), translation), + local + ) + ); +} + +// local must be a pure rotation +inline Vector3 translation_from_local(const Vector3& translation, const Matrix4& local) +{ + return matrix4_get_translation_vec3( + matrix4_multiplied_by_matrix4( + matrix4_translated_by_vec3(local, translation), + matrix4_transposed(local) + ) + ); +} + +class DragPlanes +{ +public: + ObservedSelectable m_selectable_right; // +x + ObservedSelectable m_selectable_left; // -x + ObservedSelectable m_selectable_front; // +y + ObservedSelectable m_selectable_back; // -y + ObservedSelectable m_selectable_top; // +z + ObservedSelectable m_selectable_bottom; // -z + AABB m_bounds; + + DragPlanes(const SelectionChangeCallback& onchanged) : + m_selectable_right(onchanged), + m_selectable_left(onchanged), + m_selectable_front(onchanged), + m_selectable_back(onchanged), + m_selectable_top(onchanged), + m_selectable_bottom(onchanged) + { + } + bool isSelected() const + { + return m_selectable_right.isSelected() + || m_selectable_left.isSelected() + || m_selectable_front.isSelected() + || m_selectable_back.isSelected() + || m_selectable_top.isSelected() + || m_selectable_bottom.isSelected(); + } + void setSelected(bool selected) + { + m_selectable_right.setSelected(selected); + m_selectable_left.setSelected(selected); + m_selectable_front.setSelected(selected); + m_selectable_back.setSelected(selected); + m_selectable_top.setSelected(selected); + m_selectable_bottom.setSelected(selected); + } + void selectPlanes(const AABB& aabb, Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback, const Matrix4& rotation = g_matrix4_identity) + { + Line line(test.getNear(), test.getFar()); + Vector3 corners[8]; + aabb_corners_oriented(aabb, rotation, corners); + + Plane3 planes[6]; + aabb_planes_oriented(aabb, rotation, planes); + + for(Vector3* i = corners; i != corners + 8; ++i) + { + *i = vector3_subtracted(line_closest_point(line, *i), *i); + } + + if(vector3_dot(planes[0].normal(), corners[1]) > 0 + && vector3_dot(planes[0].normal(), corners[2]) > 0 + && vector3_dot(planes[0].normal(), corners[5]) > 0 + && vector3_dot(planes[0].normal(), corners[6]) > 0) + { + Selector_add(selector, m_selectable_right); + selectedPlaneCallback(planes[0]); + //globalOutputStream() << "right\n"; + } + if(vector3_dot(planes[1].normal(), corners[0]) > 0 + && vector3_dot(planes[1].normal(), corners[3]) > 0 + && vector3_dot(planes[1].normal(), corners[4]) > 0 + && vector3_dot(planes[1].normal(), corners[7]) > 0) + { + Selector_add(selector, m_selectable_left); + selectedPlaneCallback(planes[1]); + //globalOutputStream() << "left\n"; + } + if(vector3_dot(planes[2].normal(), corners[0]) > 0 + && vector3_dot(planes[2].normal(), corners[1]) > 0 + && vector3_dot(planes[2].normal(), corners[4]) > 0 + && vector3_dot(planes[2].normal(), corners[5]) > 0) + { + Selector_add(selector, m_selectable_front); + selectedPlaneCallback(planes[2]); + //globalOutputStream() << "front\n"; + } + if(vector3_dot(planes[3].normal(), corners[2]) > 0 + && vector3_dot(planes[3].normal(), corners[3]) > 0 + && vector3_dot(planes[3].normal(), corners[6]) > 0 + && vector3_dot(planes[3].normal(), corners[7]) > 0) + { + Selector_add(selector, m_selectable_back); + selectedPlaneCallback(planes[3]); + //globalOutputStream() << "back\n"; + } + if(vector3_dot(planes[4].normal(), corners[0]) > 0 + && vector3_dot(planes[4].normal(), corners[1]) > 0 + && vector3_dot(planes[4].normal(), corners[2]) > 0 + && vector3_dot(planes[4].normal(), corners[3]) > 0) + { + Selector_add(selector, m_selectable_top); + selectedPlaneCallback(planes[4]); + //globalOutputStream() << "top\n"; + } + if(vector3_dot(planes[5].normal(), corners[4]) > 0 + && vector3_dot(planes[5].normal(), corners[5]) > 0 + && vector3_dot(planes[5].normal(), corners[6]) > 0 + && vector3_dot(planes[5].normal(), corners[7]) > 0) + { + Selector_add(selector, m_selectable_bottom); + selectedPlaneCallback(planes[5]); + //globalOutputStream() << "bottom\n"; + } + + m_bounds = aabb; + } + void selectReversedPlanes(const AABB& aabb, Selector& selector, const SelectedPlanes& selectedPlanes, const Matrix4& rotation = g_matrix4_identity) + { + Plane3 planes[6]; + aabb_planes_oriented(aabb, rotation, planes); + + if(selectedPlanes.contains(plane3_flipped(planes[0]))) + { + Selector_add(selector, m_selectable_right); + } + if(selectedPlanes.contains(plane3_flipped(planes[1]))) + { + Selector_add(selector, m_selectable_left); + } + if(selectedPlanes.contains(plane3_flipped(planes[2]))) + { + Selector_add(selector, m_selectable_front); + } + if(selectedPlanes.contains(plane3_flipped(planes[3]))) + { + Selector_add(selector, m_selectable_back); + } + if(selectedPlanes.contains(plane3_flipped(planes[4]))) + { + Selector_add(selector, m_selectable_top); + } + if(selectedPlanes.contains(plane3_flipped(planes[5]))) + { + Selector_add(selector, m_selectable_bottom); + } + } + AABB evaluateResize(const Vector3& translation) const + { + Vector3 min = m_bounds.origin - m_bounds.extents; + Vector3 max = m_bounds.origin + m_bounds.extents; + if(m_bounds.extents[0] != 0) + { + if(m_selectable_right.isSelected()) + { + max[0] += translation[0]; + //globalOutputStream() << "moving right\n"; + } + if(m_selectable_left.isSelected()) + { + min[0] += translation[0]; + //globalOutputStream() << "moving left\n"; + } + } + if(m_bounds.extents[1] != 0) + { + if(m_selectable_front.isSelected()) + { + max[1] += translation[1]; + //globalOutputStream() << "moving front\n"; + } + if(m_selectable_back.isSelected()) + { + min[1] += translation[1]; + //globalOutputStream() << "moving back\n"; + } + } + if(m_bounds.extents[2] != 0) + { + if(m_selectable_top.isSelected()) + { + max[2] += translation[2]; + //globalOutputStream() << "moving top\n"; + } + if(m_selectable_bottom.isSelected()) + { + min[2] += translation[2]; + //globalOutputStream() << "moving bottom\n"; + } + } + + return AABB(vector3_mid(min, max), vector3_scaled(vector3_subtracted(max, min), 0.5)); + } + AABB evaluateResize(const Vector3& translation, const Matrix4& rotation) const + { + AABB aabb(evaluateResize(translation_to_local(translation, rotation))); + aabb.origin = m_bounds.origin + translation_from_local(aabb.origin - m_bounds.origin, rotation); + return aabb; + } + Matrix4 evaluateTransform(const Vector3& translation) const + { + AABB aabb(evaluateResize(translation)); + Vector3 scale( + m_bounds.extents[0] != 0 ? aabb.extents[0] / m_bounds.extents[0] : 1, + m_bounds.extents[1] != 0 ? aabb.extents[1] / m_bounds.extents[1] : 1, + m_bounds.extents[2] != 0 ? aabb.extents[2] / m_bounds.extents[2] : 1 + ); + + Matrix4 matrix(matrix4_translation_for_vec3(aabb.origin - m_bounds.origin)); + matrix4_pivoted_scale_by_vec3(matrix, scale, m_bounds.origin); + + return matrix; + } +}; + +#endif diff --git a/libs/eclasslib.cpp b/libs/eclasslib.cpp new file mode 100644 index 00000000..a320bc26 --- /dev/null +++ b/libs/eclasslib.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "eclasslib.h" diff --git a/libs/eclasslib.h b/libs/eclasslib.h new file mode 100644 index 00000000..7bee547b --- /dev/null +++ b/libs/eclasslib.h @@ -0,0 +1,341 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_ECLASSLIB_H) +#define INCLUDED_ECLASSLIB_H + +#include +#include +#include +#include +#include + +#include "ieclass.h" +#include "irender.h" + +#include "math/vector.h" +#include "string/string.h" + +typedef Vector3 Colour3; + +class ListAttributeType +{ + typedef std::pair ListItem; + typedef std::vector ListItems; + ListItems m_items; +public: + + typedef ListItems::const_iterator const_iterator; + const_iterator begin() const + { + return m_items.begin(); + } + const_iterator end() const + { + return m_items.end(); + } + + const ListItem& operator[](std::size_t i) const + { + return m_items[i]; + } + const_iterator findValue(const char* value) const + { + for(ListItems::const_iterator i = m_items.begin(); i != m_items.end(); ++i) + { + if(string_equal(value, (*i).second.c_str())) + { + return i; + } + } + return m_items.end(); + } + + void push_back(const char* name, const char* value) + { + m_items.push_back(ListItems::value_type(name, value)); + } +}; + +class EntityClassAttribute +{ +public: + CopiedString m_type; + CopiedString m_name; + CopiedString m_value; + CopiedString m_description; + EntityClassAttribute() + { + } + EntityClassAttribute(const char* type, const char* name, const char* value = "", const char* description = "") : m_type(type), m_name(name), m_value(value), m_description(description) + { + } +}; + +typedef std::pair EntityClassAttributePair; +typedef std::list EntityClassAttributes; +typedef std::list StringList; + +inline const char* EntityClassAttributePair_getName(const EntityClassAttributePair& attributePair) +{ + if(!string_empty(attributePair.second.m_name.c_str())) + { + return attributePair.second.m_name.c_str(); + } + return attributePair.first.c_str(); +} + +inline const char* EntityClassAttributePair_getDescription(const EntityClassAttributePair& attributePair) +{ + if(!string_empty(attributePair.second.m_description.c_str())) + { + return attributePair.second.m_description.c_str(); + } + return EntityClassAttributePair_getName(attributePair); +} + +class EntityClass +{ +public: + CopiedString m_name; + StringList m_parent; + bool fixedsize; + bool unknown; // wasn't found in source + Vector3 mins; + Vector3 maxs; + + Colour3 color; + Shader* m_state_fill; + Shader* m_state_wire; + Shader* m_state_blend; + + CopiedString m_comments; + char flagnames[MAX_FLAGS][32]; + + CopiedString m_modelpath; + CopiedString m_skin; + + void (*free)(EntityClass*); + + EntityClassAttributes m_attributes; + + bool inheritanceResolved; + bool sizeSpecified; + bool colorSpecified; + + const char* name() const + { + return m_name.c_str(); + } + const char* comments() const + { + return m_comments.c_str(); + } + const char* modelpath() const + { + return m_modelpath.c_str(); + } + const char* skin() const + { + return m_skin.c_str(); + } +}; + +inline const char* EntityClass_valueForKey(const EntityClass& entityClass, const char* key) +{ + for(EntityClassAttributes::const_iterator i = entityClass.m_attributes.begin(); i != entityClass.m_attributes.end(); ++i) + { + if(string_equal(key, (*i).first.c_str())) + { + return (*i).second.m_value.c_str(); + } + } + return ""; +} + +inline EntityClassAttributePair& EntityClass_insertAttribute(EntityClass& entityClass, const char* key, const EntityClassAttribute& attribute = EntityClassAttribute()) +{ + entityClass.m_attributes.push_back(EntityClassAttributePair(key, attribute)); + return entityClass.m_attributes.back(); +} + + +inline void buffer_write_colour_fill(char buffer[128], const Colour3& colour) +{ + sprintf(buffer, "(%g %g %g)", colour[0], colour[1], colour[2]); +} + +inline void buffer_write_colour_wire(char buffer[128], const Colour3& colour) +{ + sprintf(buffer, "<%g %g %g>", colour[0], colour[1], colour[2]); +} + +inline void buffer_write_colour_blend(char buffer[128], const Colour3& colour) +{ + sprintf(buffer, "[%g %g %g]", colour[0], colour[1], colour[2]); +} + +inline Shader* colour_capture_state_fill(const Colour3& colour) +{ + char buffer[128]; + buffer_write_colour_fill(buffer, colour); + return GlobalShaderCache().capture(buffer); +} + +inline void colour_release_state_fill(const Colour3& colour) +{ + char buffer[128]; + buffer_write_colour_fill(buffer, colour); + GlobalShaderCache().release(buffer); +} + +inline Shader* colour_capture_state_wire(const Colour3& colour) +{ + char buffer[128]; + buffer_write_colour_wire(buffer, colour); + return GlobalShaderCache().capture(buffer); +} + +inline void colour_release_state_wire(const Colour3& colour) +{ + char buffer[128]; + buffer_write_colour_wire(buffer, colour); + GlobalShaderCache().release(buffer); +} + +inline Shader* colour_capture_state_blend(const Colour3& colour) +{ + char buffer[128]; + buffer_write_colour_blend(buffer, colour); + return GlobalShaderCache().capture(buffer); +} + +inline void colour_release_state_blend(const Colour3& colour) +{ + char buffer[128]; + buffer_write_colour_blend(buffer, colour); + GlobalShaderCache().release(buffer); +} + +inline void eclass_capture_state(EntityClass* eclass) +{ + eclass->m_state_fill = colour_capture_state_fill(eclass->color); + eclass->m_state_wire = colour_capture_state_wire(eclass->color); + eclass->m_state_blend = colour_capture_state_blend(eclass->color); +} + +inline void eclass_release_state(EntityClass* eclass) +{ + colour_release_state_fill(eclass->color); + colour_release_state_wire(eclass->color); + colour_release_state_blend(eclass->color); +} + +// eclass constructor +inline EntityClass* Eclass_Alloc() +{ + EntityClass* e = new EntityClass; + + e->fixedsize = false; + e->unknown = false; + memset(e->flagnames, 0, MAX_FLAGS*32); + + e->maxs = Vector3(-1,-1,-1); + e->mins = Vector3(1, 1, 1); + + e->free = 0; + + e->inheritanceResolved = true; + e->sizeSpecified = false; + e->colorSpecified = false; + + return e; +} + +// eclass destructor +inline void Eclass_Free(EntityClass* e) +{ + eclass_release_state(e); + + delete e; +} + +inline bool classname_equal(const char* classname, const char* other) +{ + return string_equal(classname, other); +} + +inline EntityClass* EClass_Create(const char* name, const Vector3& colour, const char* comments) +{ + EntityClass *e = Eclass_Alloc(); + e->free = &Eclass_Free; + + e->m_name = name; + + e->color = colour; + eclass_capture_state(e); + + if (comments) + e->m_comments = comments; + + return e; +} + +inline EntityClass* EClass_Create_FixedSize(const char* name, const Vector3& colour, const Vector3& mins, const Vector3& maxs, const char* comments) +{ + EntityClass *e = Eclass_Alloc(); + e->free = &Eclass_Free; + + e->m_name = name; + + e->color = colour; + eclass_capture_state(e); + + e->fixedsize = true; + + e->mins = mins; + e->maxs = maxs; + + if (comments) + e->m_comments = comments; + + return e; +} + +const Vector3 smallbox[2] = { + Vector3(-8,-8,-8), + Vector3( 8, 8, 8), +}; + +inline EntityClass *EntityClass_Create_Default(const char *name, bool has_brushes) +{ + // create a new class for it + if (has_brushes) + { + return EClass_Create(name, Vector3(0.0f, 0.5f, 0.0f), "Not found in source."); + } + else + { + return EClass_Create_FixedSize(name, Vector3(0.0f, 0.5f, 0.0f), smallbox[0], smallbox[1], "Not found in source."); + } +} + +#endif diff --git a/libs/entitylib.cpp b/libs/entitylib.cpp new file mode 100644 index 00000000..385d0f2e --- /dev/null +++ b/libs/entitylib.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "entitylib.h" diff --git a/libs/entitylib.h b/libs/entitylib.h new file mode 100644 index 00000000..4a34f0a5 --- /dev/null +++ b/libs/entitylib.h @@ -0,0 +1,758 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_ENTITYLIB_H) +#define INCLUDED_ENTITYLIB_H + +#include "ireference.h" +#include "debugging/debugging.h" + +#include "ientity.h" +#include "irender.h" +#include "igl.h" +#include "selectable.h" + +#include "generic/callback.h" +#include "math/vector.h" +#include "math/aabb.h" +#include "undolib.h" +#include "string/pooledstring.h" +#include "generic/referencecounted.h" +#include "scenelib.h" +#include "container/container.h" +#include "eclasslib.h" + +#include +#include + +inline void arrow_draw(const Vector3& origin, const Vector3& direction) +{ + Vector3 up(0, 0, 1); + Vector3 left(-direction[1], direction[0], 0); + + Vector3 endpoint(vector3_added(origin, vector3_scaled(direction, 32.0))); + + Vector3 tip1(vector3_added(vector3_added(endpoint, vector3_scaled(direction, -8.0)), vector3_scaled(up, -4.0))); + Vector3 tip2(vector3_added(tip1, vector3_scaled(up, 8.0))); + Vector3 tip3(vector3_added(vector3_added(endpoint, vector3_scaled(direction, -8.0)), vector3_scaled(left, -4.0))); + Vector3 tip4(vector3_added(tip3, vector3_scaled(left, 8.0))); + + glBegin (GL_LINES); + + glVertex3fv(vector3_to_array(origin)); + glVertex3fv(vector3_to_array(endpoint)); + + glVertex3fv(vector3_to_array(endpoint)); + glVertex3fv(vector3_to_array(tip1)); + + glVertex3fv(vector3_to_array(endpoint)); + glVertex3fv(vector3_to_array(tip2)); + + glVertex3fv(vector3_to_array(endpoint)); + glVertex3fv(vector3_to_array(tip3)); + + glVertex3fv(vector3_to_array(endpoint)); + glVertex3fv(vector3_to_array(tip4)); + + glVertex3fv(vector3_to_array(tip1)); + glVertex3fv(vector3_to_array(tip3)); + + glVertex3fv(vector3_to_array(tip3)); + glVertex3fv(vector3_to_array(tip2)); + + glVertex3fv(vector3_to_array(tip2)); + glVertex3fv(vector3_to_array(tip4)); + + glVertex3fv(vector3_to_array(tip4)); + glVertex3fv(vector3_to_array(tip1)); + + glEnd(); +} + +class SelectionIntersection; + +inline void aabb_testselect(const AABB& aabb, SelectionTest& test, SelectionIntersection& best) +{ + const IndexPointer::index_type indices[24] = { + 2, 1, 5, 6, + 1, 0, 4, 5, + 0, 1, 2, 3, + 3, 7, 4, 0, + 3, 2, 6, 7, + 7, 6, 5, 4, + }; + + Vector3 points[8]; + aabb_corners(aabb, points); + test.TestQuads(VertexPointer(reinterpret_cast(points), sizeof(Vector3)), IndexPointer(indices, 24), best); +} + +inline void aabb_draw_wire(const Vector3 points[8]) +{ + typedef std::size_t index_t; + index_t indices[24] = { + 0, 1, 1, 2, 2, 3, 3, 0, + 4, 5, 5, 6, 6, 7, 7, 4, + 0, 4, 1, 5, 2, 6, 3, 7, + }; +#if 1 + glVertexPointer(3, GL_FLOAT, 0, points); + glDrawElements(GL_LINES, sizeof(indices)/sizeof(index_t), GL_UNSIGNED_INT, indices); +#else + glBegin(GL_LINES); + for(std::size_t i = 0; i < sizeof(indices)/sizeof(index_t); ++i) + { + glVertex3fv(points[indices[i]]); + } + glEnd(); +#endif +} + +inline void aabb_draw_flatshade(const Vector3 points[8]) +{ + glBegin(GL_QUADS); + + glNormal3fv(vector3_to_array(aabb_normals[0])); + glVertex3fv(vector3_to_array(points[2])); + glVertex3fv(vector3_to_array(points[1])); + glVertex3fv(vector3_to_array(points[5])); + glVertex3fv(vector3_to_array(points[6])); + + glNormal3fv(vector3_to_array(aabb_normals[1])); + glVertex3fv(vector3_to_array(points[1])); + glVertex3fv(vector3_to_array(points[0])); + glVertex3fv(vector3_to_array(points[4])); + glVertex3fv(vector3_to_array(points[5])); + + glNormal3fv(vector3_to_array(aabb_normals[2])); + glVertex3fv(vector3_to_array(points[0])); + glVertex3fv(vector3_to_array(points[1])); + glVertex3fv(vector3_to_array(points[2])); + glVertex3fv(vector3_to_array(points[3])); + + glNormal3fv(vector3_to_array(aabb_normals[3])); + glVertex3fv(vector3_to_array(points[0])); + glVertex3fv(vector3_to_array(points[3])); + glVertex3fv(vector3_to_array(points[7])); + glVertex3fv(vector3_to_array(points[4])); + + glNormal3fv(vector3_to_array(aabb_normals[4])); + glVertex3fv(vector3_to_array(points[3])); + glVertex3fv(vector3_to_array(points[2])); + glVertex3fv(vector3_to_array(points[6])); + glVertex3fv(vector3_to_array(points[7])); + + glNormal3fv(vector3_to_array(aabb_normals[5])); + glVertex3fv(vector3_to_array(points[7])); + glVertex3fv(vector3_to_array(points[6])); + glVertex3fv(vector3_to_array(points[5])); + glVertex3fv(vector3_to_array(points[4])); + + glEnd(); +} + +inline void aabb_draw_wire(const AABB& aabb) +{ + Vector3 points[8]; + aabb_corners(aabb, points); + aabb_draw_wire(points); +} + +inline void aabb_draw_flatshade(const AABB& aabb) +{ + Vector3 points[8]; + aabb_corners(aabb, points); + aabb_draw_flatshade(points); +} + +inline void aabb_draw_textured(const AABB& aabb) +{ + Vector3 points[8]; + aabb_corners(aabb, points); + + glBegin(GL_QUADS); + + glNormal3fv(vector3_to_array(aabb_normals[0])); + glTexCoord2fv(aabb_texcoord_topleft); + glVertex3fv(vector3_to_array(points[2])); + glTexCoord2fv(aabb_texcoord_topright); + glVertex3fv(vector3_to_array(points[1])); + glTexCoord2fv(aabb_texcoord_botright); + glVertex3fv(vector3_to_array(points[5])); + glTexCoord2fv(aabb_texcoord_botleft); + glVertex3fv(vector3_to_array(points[6])); + + glNormal3fv(vector3_to_array(aabb_normals[1])); + glTexCoord2fv(aabb_texcoord_topleft); + glVertex3fv(vector3_to_array(points[1])); + glTexCoord2fv(aabb_texcoord_topright); + glVertex3fv(vector3_to_array(points[0])); + glTexCoord2fv(aabb_texcoord_botright); + glVertex3fv(vector3_to_array(points[4])); + glTexCoord2fv(aabb_texcoord_botleft); + glVertex3fv(vector3_to_array(points[5])); + + glNormal3fv(vector3_to_array(aabb_normals[2])); + glTexCoord2fv(aabb_texcoord_topleft); + glVertex3fv(vector3_to_array(points[0])); + glTexCoord2fv(aabb_texcoord_topright); + glVertex3fv(vector3_to_array(points[1])); + glTexCoord2fv(aabb_texcoord_botright); + glVertex3fv(vector3_to_array(points[2])); + glTexCoord2fv(aabb_texcoord_botleft); + glVertex3fv(vector3_to_array(points[3])); + + glNormal3fv(vector3_to_array(aabb_normals[3])); + glTexCoord2fv(aabb_texcoord_topleft); + glVertex3fv(vector3_to_array(points[0])); + glTexCoord2fv(aabb_texcoord_topright); + glVertex3fv(vector3_to_array(points[3])); + glTexCoord2fv(aabb_texcoord_botright); + glVertex3fv(vector3_to_array(points[7])); + glTexCoord2fv(aabb_texcoord_botleft); + glVertex3fv(vector3_to_array(points[4])); + + glNormal3fv(vector3_to_array(aabb_normals[4])); + glTexCoord2fv(aabb_texcoord_topleft); + glVertex3fv(vector3_to_array(points[3])); + glTexCoord2fv(aabb_texcoord_topright); + glVertex3fv(vector3_to_array(points[2])); + glTexCoord2fv(aabb_texcoord_botright); + glVertex3fv(vector3_to_array(points[6])); + glTexCoord2fv(aabb_texcoord_botleft); + glVertex3fv(vector3_to_array(points[7])); + + glNormal3fv(vector3_to_array(aabb_normals[5])); + glTexCoord2fv(aabb_texcoord_topleft); + glVertex3fv(vector3_to_array(points[7])); + glTexCoord2fv(aabb_texcoord_topright); + glVertex3fv(vector3_to_array(points[6])); + glTexCoord2fv(aabb_texcoord_botright); + glVertex3fv(vector3_to_array(points[5])); + glTexCoord2fv(aabb_texcoord_botleft); + glVertex3fv(vector3_to_array(points[4])); + + glEnd(); +} + +inline void aabb_draw_solid(const AABB& aabb, RenderStateFlags state) +{ + if(state & RENDER_TEXTURE) + { + aabb_draw_textured(aabb); + } + else + { + aabb_draw_flatshade(aabb); + } +} + +inline void aabb_draw(const AABB& aabb, RenderStateFlags state) +{ + if(state & RENDER_FILL) + { + aabb_draw_solid(aabb, state); + } + else + { + aabb_draw_wire(aabb); + } +} + +class RenderableSolidAABB : public OpenGLRenderable +{ + const AABB& m_aabb; +public: + RenderableSolidAABB(const AABB& aabb) : m_aabb(aabb) + { + } + void render(RenderStateFlags state) const + { + aabb_draw_solid(m_aabb, state); + } +}; + +class RenderableWireframeAABB : public OpenGLRenderable +{ + const AABB& m_aabb; +public: + RenderableWireframeAABB(const AABB& aabb) : m_aabb(aabb) + { + } + void render(RenderStateFlags state) const + { + aabb_draw_wire(m_aabb); + } +}; + + +/// \brief A key/value pair of strings. +/// +/// - Notifies observers when value changes - value changes to "" on destruction. +/// - Provides undo support through the global undo system. +class KeyValue : public EntityKeyValue +{ + typedef UnsortedSet KeyObservers; + + std::size_t m_refcount; + KeyObservers m_observers; + CopiedString m_string; + const char* m_empty; + ObservedUndoableObject m_undo; + static EntityCreator::KeyValueChangedFunc m_entityKeyValueChanged; +public: + + KeyValue(const char* string, const char* empty) + : m_refcount(0), m_string(string), m_empty(empty), m_undo(m_string, UndoImportCaller(*this)) + { + notify(); + } + ~KeyValue() + { + ASSERT_MESSAGE(m_observers.empty(), "KeyValue::~KeyValue: observers still attached"); + } + + static void setKeyValueChangedFunc(EntityCreator::KeyValueChangedFunc func) + { + m_entityKeyValueChanged = func; + } + + void IncRef() + { + ++m_refcount; + } + void DecRef() + { + if(--m_refcount == 0) + { + delete this; + } + } + + void instanceAttach(MapFile* map) + { + m_undo.instanceAttach(map); + } + void instanceDetach(MapFile* map) + { + m_undo.instanceDetach(map); + } + + void attach(const KeyObserver& observer) + { + (*m_observers.insert(observer))(c_str()); + } + void detach(const KeyObserver& observer) + { + observer(m_empty); + m_observers.erase(observer); + } + const char* c_str() const + { + if(string_empty(m_string.c_str())) + { + return m_empty; + } + return m_string.c_str(); + } + void assign(const char* other) + { + if(!string_equal(m_string.c_str(), other)) + { + m_undo.save(); + m_string = other; + notify(); + } + } + + void notify() + { + m_entityKeyValueChanged(); + KeyObservers::reverse_iterator i = m_observers.rbegin(); + while(i != m_observers.rend()) + { + (*i++)(c_str()); + } + } + + void importState(const CopiedString& string) + { + m_string = string; + + notify(); + } + typedef MemberCaller1 UndoImportCaller; +}; + +/// \brief An unsorted list of key/value pairs. +/// +/// - Notifies observers when a pair is inserted or removed. +/// - Provides undo support through the global undo system. +/// - New keys are appended to the end of the list. +class EntityKeyValues : public Entity +{ +public: + typedef KeyValue Value; + + static StringPool& getPool() + { + return Static::instance(); + } +private: + static EntityCreator::KeyValueChangedFunc m_entityKeyValueChanged; + static Counter* m_counter; + + EntityClass* m_eclass; + + class KeyContext{}; + typedef Static KeyPool; + typedef PooledString Key; + typedef SmartPointer KeyValuePtr; + typedef UnsortedMap KeyValues; + KeyValues m_keyValues; + + typedef UnsortedSet Observers; + Observers m_observers; + + ObservedUndoableObject m_undo; + bool m_instanced; + + bool m_observerMutex; + + void notifyInsert(const char* key, Value& value) + { + m_observerMutex = true; + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->insert(key, value); + } + m_observerMutex = false; + } + void notifyErase(const char* key, Value& value) + { + m_observerMutex = true; + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->erase(key, value); + } + m_observerMutex = false; + } + void forEachKeyValue_notifyInsert() + { + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + notifyInsert((*i).first.c_str(), *(*i).second); + } + } + void forEachKeyValue_notifyErase() + { + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + notifyErase((*i).first.c_str(), *(*i).second); + } + } + + void insert(const char* key, const KeyValuePtr& keyValue) + { + KeyValues::iterator i = m_keyValues.insert(KeyValues::value_type(key, keyValue)); + notifyInsert(key, *(*i).second); + + if(m_instanced) + { + (*i).second->instanceAttach(m_undo.map()); + } + } + + void insert(const char* key, const char* value) + { + KeyValues::iterator i = m_keyValues.find(key); + if(i != m_keyValues.end()) + { + (*i).second->assign(value); + } + else + { + m_undo.save(); + insert(key, KeyValuePtr(new KeyValue(value, EntityClass_valueForKey(*m_eclass, key)))); + } + } + + void erase(KeyValues::iterator i) + { + if(m_instanced) + { + (*i).second->instanceDetach(m_undo.map()); + } + + Key key((*i).first); + KeyValuePtr value((*i).second); + m_keyValues.erase(i); + notifyErase(key.c_str(), *value); + } + + void erase(const char* key) + { + KeyValues::iterator i = m_keyValues.find(key); + if(i != m_keyValues.end()) + { + m_undo.save(); + erase(i); + } + } + +public: + bool m_isContainer; + + EntityKeyValues(EntityClass* eclass) : + m_eclass(eclass), + m_undo(m_keyValues, UndoImportCaller(*this)), + m_instanced(false), + m_observerMutex(false), + m_isContainer(!eclass->fixedsize) + { + } + EntityKeyValues(const EntityKeyValues& other) : + Entity(other), + m_eclass(&other.getEntityClass()), + m_undo(m_keyValues, UndoImportCaller(*this)), + m_instanced(false), + m_observerMutex(false), + m_isContainer(other.m_isContainer) + { + for(KeyValues::const_iterator i = other.m_keyValues.begin(); i != other.m_keyValues.end(); ++i) + { + insert((*i).first.c_str(), (*i).second->c_str()); + } + } + ~EntityKeyValues() + { + for(Observers::iterator i = m_observers.begin(); i != m_observers.end();) + { + // post-increment to allow current element to be removed safely + (*i++)->clear(); + } + ASSERT_MESSAGE(m_observers.empty(), "EntityKeyValues::~EntityKeyValues: observers still attached"); + } + + static void setKeyValueChangedFunc(EntityCreator::KeyValueChangedFunc func) + { + m_entityKeyValueChanged = func; + KeyValue::setKeyValueChangedFunc(func); + } + static void setCounter(Counter* counter) + { + m_counter = counter; + } + + void importState(const KeyValues& keyValues) + { + for(KeyValues::iterator i = m_keyValues.begin(); i != m_keyValues.end();) + { + erase(i++); + } + + for(KeyValues::const_iterator i = keyValues.begin(); i != keyValues.end(); ++i) + { + insert((*i).first.c_str(), (*i).second); + } + + m_entityKeyValueChanged(); + } + typedef MemberCaller1 UndoImportCaller; + + void attach(Observer& observer) + { + ASSERT_MESSAGE(!m_observerMutex, "observer cannot be attached during iteration"); + m_observers.insert(&observer); + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + observer.insert((*i).first.c_str(), *(*i).second); + } + } + void detach(Observer& observer) + { + ASSERT_MESSAGE(!m_observerMutex, "observer cannot be detached during iteration"); + m_observers.erase(&observer); + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + observer.erase((*i).first.c_str(), *(*i).second); + } + } + + void forEachKeyValue_instanceAttach(MapFile* map) + { + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + (*i).second->instanceAttach(map); + } + } + void forEachKeyValue_instanceDetach(MapFile* map) + { + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + (*i).second->instanceDetach(map); + } + } + + void instanceAttach(MapFile* map) + { + if(m_counter != 0) + { + m_counter->increment(); + } + + m_instanced = true; + forEachKeyValue_instanceAttach(map); + m_undo.instanceAttach(map); + } + void instanceDetach(MapFile* map) + { + if(m_counter != 0) + { + m_counter->decrement(); + } + + m_undo.instanceDetach(map); + forEachKeyValue_instanceDetach(map); + m_instanced = false; + } + + // entity + EntityClass& getEntityClass() const + { + return *m_eclass; + } + void forEachKeyValue(Visitor& visitor) const + { + for(KeyValues::const_iterator i = m_keyValues.begin(); i != m_keyValues.end(); ++i) + { + visitor.visit((*i).first.c_str(), (*i).second->c_str()); + } + } + void setKeyValue(const char* key, const char* value) + { + if(value[0] == '\0' + /*|| string_equal(EntityClass_valueForKey(*m_eclass, key), value)*/) // don't delete values equal to default + { + erase(key); + } + else + { + insert(key, value); + } + m_entityKeyValueChanged(); + } + const char* getKeyValue(const char* key) const + { + KeyValues::const_iterator i = m_keyValues.find(key); + if(i != m_keyValues.end()) + { + return (*i).second->c_str(); + } + + return EntityClass_valueForKey(*m_eclass, key); + } + + bool isContainer() const + { + return m_isContainer; + } +}; + +/// \brief A Resource reference with a controlled lifetime. +/// \brief The resource is released when the ResourceReference is destroyed. +class ResourceReference +{ + CopiedString m_name; + Resource* m_resource; +public: + ResourceReference(const char* name) + : m_name(name) + { + capture(); + } + ResourceReference(const ResourceReference& other) + : m_name(other.m_name) + { + capture(); + } + ResourceReference& operator=(const ResourceReference& other) + { + ResourceReference tmp(other); + tmp.swap(*this); + return *this; + } + ~ResourceReference() + { + release(); + } + + void capture() + { + m_resource = GlobalReferenceCache().capture(m_name.c_str()); + } + void release() + { + GlobalReferenceCache().release(m_name.c_str()); + } + + const char* getName() const + { + return m_name.c_str(); + } + void setName(const char* name) + { + ResourceReference tmp(name); + tmp.swap(*this); + } + + void swap(ResourceReference& other) + { + std::swap(m_resource, other.m_resource); + std::swap(m_name, other.m_name); + } + + void attach(ModuleObserver& observer) + { + m_resource->attach(observer); + } + void detach(ModuleObserver& observer) + { + m_resource->detach(observer); + } + + Resource* get() + { + return m_resource; + } +}; + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap. + inline void swap(ResourceReference& self, ResourceReference& other) + { + self.swap(other); + } +} + +#endif diff --git a/libs/entityxml.cpp b/libs/entityxml.cpp new file mode 100644 index 00000000..175c4818 --- /dev/null +++ b/libs/entityxml.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "entityxml.h" diff --git a/libs/entityxml.h b/libs/entityxml.h new file mode 100644 index 00000000..50df739b --- /dev/null +++ b/libs/entityxml.h @@ -0,0 +1,108 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ENTITYXML_H) +#define INCLUDED_ENTITYXML_H + +#include "ientity.h" +#include "xml/ixml.h" +#include "xml/xmlelement.h" + +class entity_import : public XMLImporter +{ + Entity& m_entity; +public: + entity_import(Entity& entity) + : m_entity(entity) + { + } + void pushElement(const XMLElement& element) + { + if(strcmp(element.name(), "epair") == 0) + m_entity.setKeyValue(element.attribute("key"), element.attribute("value")); + } + void popElement(const char* name) + { + } + std::size_t write(const char* data, std::size_t length) + { + return length; + } +}; + +class entity_export : public XMLExporter +{ + class ExportXMLVisitor : public Entity::Visitor + { + XMLImporter& m_importer; + public: + ExportXMLVisitor(XMLImporter& importer) : m_importer(importer) + { + } + void visit(const char* key, const char* value) + { + StaticElement element("epair"); + element.insertAttribute("key", key); + element.insertAttribute("value", value); + m_importer.pushElement(element); + m_importer.popElement(element.name()); + } + }; + + const Entity& m_entity; + +public: + entity_export(const Entity& entity) : m_entity(entity) + { + } + void exportXML(XMLImporter& observer) + { + ExportXMLVisitor visitor(observer); + + m_entity.forEachKeyValue(visitor); + } +}; + +inline void entity_copy(Entity& entity, const Entity& other) +{ + entity_export exporter(other); + entity_import importer(entity); + exporter.exportXML(importer); +} + +template +class EntityConstruction +{ +public: + typedef EntityClass* type; + static type get(const EntityType& entity) + { + return &entity.getEntity().getEntityClass(); + } + static void copy(EntityType& entity, const EntityType& other) + { + entity_copy(entity.getEntity(), other.getEntity()); + } +}; + + + +#endif diff --git a/libs/fs_filesystem.cpp b/libs/fs_filesystem.cpp new file mode 100644 index 00000000..f0820982 --- /dev/null +++ b/libs/fs_filesystem.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "fs_filesystem.h" diff --git a/libs/fs_filesystem.h b/libs/fs_filesystem.h new file mode 100644 index 00000000..0c22a4b3 --- /dev/null +++ b/libs/fs_filesystem.h @@ -0,0 +1,183 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_FS_FILESYSTEM_H) +#define INCLUDED_FS_FILESYSTEM_H + +#include "string/string.h" +#include "os/path.h" + +#include + +inline unsigned int path_get_depth(const char* path) +{ + unsigned int depth = 0; + while(path != 0 && path[0] != '\0') + { + path = strchr(path, '/'); + if(path != 0) + { + ++path; + } + ++depth; + } + return depth; +} + +/// \brief A generic unix-style file-system which maps paths to files and directories. +/// Provides average O(log n) find and insert methods. +/// \param file_type The data type which represents a file. +template +class GenericFileSystem +{ + class Path + { + CopiedString m_path; + unsigned int m_depth; + public: + Path(const char* path) + : m_path(path), m_depth(path_get_depth(c_str())) + { + } + Path(StringRange range) + : m_path(range), m_depth(path_get_depth(c_str())) + { + } + bool operator<(const Path& other) const + { + return string_less_nocase(c_str(), other.c_str()); + } + unsigned int depth() const + { + return m_depth; + } + const char* c_str() const + { + return m_path.c_str(); + } + }; + + class Entry + { + file_type* m_file; + public: + Entry() : m_file(0) + { + } + Entry(file_type* file) : m_file(file) + { + } + file_type* file() const + { + return m_file; + } + bool is_directory() const + { + return file() == 0; + } + }; + + typedef std::map Entries; + Entries m_entries; + +public: + typedef typename Entries::iterator iterator; + typedef typename Entries::value_type value_type; + typedef Entry entry_type; + + iterator begin() + { + return m_entries.begin(); + } + iterator end() + { + return m_entries.end(); + } + + /// \brief Returns the file at \p path. + /// Creates all directories below \p path if they do not exist. + /// O(log n) on average. + entry_type& operator[](const Path& path) + { + { + const char* end = path_remove_directory(path.c_str()); + while(end[0] != '\0') + { + Path dir(StringRange(path.c_str(), end)); + m_entries.insert(value_type(dir, Entry(0))); + end = path_remove_directory(end); + } + } + + return m_entries[path]; + } + + /// \brief Returns the file at \p path or end() if not found. + iterator find(const Path& path) + { + return m_entries.find(path); + } + + iterator begin(const char* root) + { + if(root[0] == '\0') + { + return m_entries.begin(); + } + iterator i = m_entries.find(root); + if(i == m_entries.end()) + { + return i; + } + return ++i; + } + + /// \brief Performs a depth-first traversal of the file-system subtree rooted at \p root. + /// Traverses the entire tree if \p root is "". + /// Calls \p visitor.file() with the path to each file relative to the filesystem root. + /// Calls \p visitor.directory() with the path to each directory relative to the filesystem root. + template + void traverse(visitor_type visitor, const char* root) + { + unsigned int start_depth = path_get_depth(root); + unsigned int skip_depth = 0; + for(iterator i = begin(root); i != end() && i->first.depth() > start_depth; ++i) + { + if(i->first.depth() == skip_depth) + { + skip_depth = 0; + } + if(skip_depth == 0) + { + if(!i->second.is_directory()) + { + visitor.file(i->first.c_str()); + } + else if(visitor.directory(i->first.c_str(), i->first.depth() - start_depth)) + { + skip_depth = i->first.depth(); + } + } + } + } +}; + +#endif diff --git a/libs/fs_path.cpp b/libs/fs_path.cpp new file mode 100644 index 00000000..5cd36406 --- /dev/null +++ b/libs/fs_path.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "fs_path.h" diff --git a/libs/fs_path.h b/libs/fs_path.h new file mode 100644 index 00000000..df409ef0 --- /dev/null +++ b/libs/fs_path.h @@ -0,0 +1,92 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_FS_PATH_H) +#define INCLUDED_FS_PATH_H + +#include "stream/stringstream.h" + +/// \brief A unix-style path string which can be modified at runtime. +/// +/// - Maintains a path ending in a path-separator. +/// - Provides a limited STL-style interface to push and pop file or directory names at the end of the path. +class UnixPath +{ + StringBuffer m_string; + + void check_separator() + { + if(!empty() && m_string.back() != '/') + { + m_string.push_back('/'); + } + } + +public: + /// \brief Constructs with the directory \p root. + UnixPath(const char* root) + : m_string(root) + { + check_separator(); + } + + bool empty() const + { + return m_string.empty(); + } + + const char* c_str() const + { + return m_string.c_str(); + } + + /// \brief Appends the directory \p name. + void push(const char* name) + { + m_string.push_string(name); + check_separator(); + } + /// \brief Appends the directory [\p first, \p last). + void push(const char* first, const char* last) + { + m_string.push_range(first, last); + check_separator(); + } + /// \brief Appends the filename \p name. + void push_filename(const char* name) + { + m_string.push_string(name); + } + /// \brief Removes the last directory or filename appended. + void pop() + { + if(m_string.back() == '/') + { + m_string.pop_back(); + } + while(!empty() && m_string.back() != '/') + { + m_string.pop_back(); + } + } +}; + +#endif diff --git a/libs/generic/arrayrange.cpp b/libs/generic/arrayrange.cpp new file mode 100644 index 00000000..b04ba068 --- /dev/null +++ b/libs/generic/arrayrange.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "arrayrange.h" + diff --git a/libs/generic/arrayrange.h b/libs/generic/arrayrange.h new file mode 100644 index 00000000..a05489a1 --- /dev/null +++ b/libs/generic/arrayrange.h @@ -0,0 +1,74 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GENERIC_ARRAYRANGE_H) +#define INCLUDED_GENERIC_ARRAYRANGE_H + +/// \file +/// \brief Macros for automatically converting a compile-time-sized array to a range. + +template +struct ArrayRange +{ + typedef Element* Iterator; + ArrayRange(Iterator first, Iterator last) + : first(first), last(last) + { + } + Iterator first; + Iterator last; +}; + +template +inline ArrayRange makeArrayRange(Element* first, Element* last) +{ + return ArrayRange(first, last); +} + +template +struct ArrayConstRange +{ + typedef const Element* Iterator; + ArrayConstRange(Iterator first, Iterator last) + : first(first), last(last) + { + } + Iterator first; + Iterator last; +}; + +template +inline ArrayConstRange makeArrayRange(const Element* first, const Element* last) +{ + return ArrayConstRange(first, last); +} + +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(*array)) +#define ARRAY_END(array) (array + ARRAY_SIZE(array)) +#define ARRAY_RANGE(array) (makeArrayRange(array, ARRAY_END(array))) + + +typedef ArrayConstRange StringArrayRange; +#define STRING_ARRAY_RANGE(array) (StringArrayRange(array, ARRAY_END(array))) + +typedef ArrayRange StringRange; + +#endif diff --git a/libs/generic/bitfield.cpp b/libs/generic/bitfield.cpp new file mode 100644 index 00000000..96d15c9c --- /dev/null +++ b/libs/generic/bitfield.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "bitfield.h" + diff --git a/libs/generic/bitfield.h b/libs/generic/bitfield.h new file mode 100644 index 00000000..652f0539 --- /dev/null +++ b/libs/generic/bitfield.h @@ -0,0 +1,133 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GENERIC_BITFIELD_H) +#define INCLUDED_GENERIC_BITFIELD_H + +/// \file +/// \brief Type safe bitfield. + +/// \brief A bit-field value. +/// +/// - Can be forward-declared when the definition of Enumeration is unknown. +/// - Can only be constructed from valid enumerated values. +/// - Can only be compared and combined with others of the same type. +/// +/// \param Enumeration A type that contains an enum \c Value of the bits that can be set in this field. +template +class BitFieldValue : public Enumeration +{ + unsigned m_value; +protected: + explicit BitFieldValue(unsigned value) : m_value(value) + { + } +public: + BitFieldValue() : m_value(0) + { + } + explicit BitFieldValue(typename Enumeration::Value value) : m_value(1 << value) + { + } + unsigned get() const + { + return m_value; + } +}; + +template +class BitFieldValueUnsafe : public BitFieldValue +{ +public: + explicit BitFieldValueUnsafe(unsigned value) : BitFieldValue(value) + { + } +}; + +template +inline bool operator==(BitFieldValue self, BitFieldValue other) +{ + return self.get() == other.get(); +} +template +inline bool operator!=(BitFieldValue self, BitFieldValue other) +{ + return !operator==(self, other); +} + +template +inline BitFieldValue operator|(BitFieldValue self, BitFieldValue other) +{ + return BitFieldValueUnsafe(self.get() | other.get()); +} +template +inline BitFieldValue& operator|=(BitFieldValue& self, BitFieldValue other) +{ + return self = self | other; +} +template +inline BitFieldValue operator&(BitFieldValue self, BitFieldValue other) +{ + return BitFieldValueUnsafe(self.get() & other.get()); +} +template +inline BitFieldValue& operator&=(BitFieldValue& self, BitFieldValue other) +{ + return self = self & other; +} +template +inline BitFieldValue operator~(BitFieldValue self) +{ + return BitFieldValueUnsafe(~self.get()); +} + + + +inline unsigned int bitfield_enable(unsigned int bitfield, unsigned int mask) +{ + return bitfield | mask; +} +inline unsigned int bitfield_disable(unsigned int bitfield, unsigned int mask) +{ + return bitfield & ~mask; +} +inline bool bitfield_enabled(unsigned int bitfield, unsigned int mask) +{ + return (bitfield & mask) != 0; +} + +template +inline BitFieldValue bitfield_enable(BitFieldValue bitfield, BitFieldValue mask) +{ + return bitfield | mask; +} +template +inline BitFieldValue bitfield_disable(BitFieldValue bitfield, BitFieldValue mask) +{ + return bitfield & ~mask; +} +template +inline bool bitfield_enabled(BitFieldValue bitfield, BitFieldValue mask) +{ + return (bitfield & mask).get() != 0; +} + +#endif diff --git a/libs/generic/callback.cpp b/libs/generic/callback.cpp new file mode 100644 index 00000000..2344830a --- /dev/null +++ b/libs/generic/callback.cpp @@ -0,0 +1,285 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "callback.h" + +#if defined(_DEBUG) || defined(DOXYGEN) + +namespace ExampleMemberCaller +{ + // MemberCaller example + class Integer + { + public: + int value; + + void printValue() const + { + // print this->value here; + } + + void setValue() + { + value = 3; + } + // a typedef to make things more readable + typedef MemberCaller SetValueCaller; + }; + + void example() + { + Integer foo = { 0 }; + + { + Callback bar = ConstMemberCaller(foo); + + // invoke the callback + bar(); // foo.printValue() + } + + + { + // use the typedef to improve readability + Callback bar = Integer::SetValueCaller(foo); + + // invoke the callback + bar(); // foo.setValue() + } + } + // end example +} + +namespace ExampleReferenceCaller +{ + // ReferenceCaller example + void Int_printValue(const int& value) + { + // print value here; + } + + void Int_setValue(int& value) + { + value = 3; + } + + // a typedef to make things more readable + typedef ReferenceCaller IntSetValueCaller; + + void example() + { + int foo = 0; + + { + Callback bar = ConstReferenceCaller(foo); + + // invoke the callback + bar(); // Int_printValue(foo) + } + + + { + // use the typedef to improve readability + Callback bar = IntSetValueCaller(foo); + + // invoke the callback + bar(); // Int_setValue(foo) + } + } + // end example +} + +#endif + +namespace +{ + class A1 + { + }; + class A2 + { + }; + class A3 + { + }; + class A4 + { + }; + + class Test + { + public: + void test0() + { + } + typedef Member Test0; + typedef MemberCaller Test0Caller; + void test0const() const + { + } + typedef ConstMember Test0Const; + typedef ConstMemberCaller Test0ConstCaller; + void test1(A1) + { + } + typedef Member1 Test1; + typedef MemberCaller1 Test1Caller; + void test1const(A1) const + { + } + typedef ConstMember1 Test1Const; + typedef ConstMemberCaller1 Test1ConstCaller; + void test2(A1, A2) + { + } + typedef Member2 Test2; + void test2const(A1, A2) const + { + } + typedef ConstMember2 Test2Const; + void test3(A1, A2, A3) + { + } + typedef Member3 Test3; + void test3const(A1, A2, A3) const + { + } + typedef ConstMember3 Test3Const; + }; + + void test0free() + { + } + typedef FreeCaller<&test0free> Test0FreeCaller; + void test1free(A1) + { + } + typedef FreeCaller1 Test1FreeCaller; + void test2free(A1, A2) + { + } + typedef Function2 Test2Free; + void test3free(A1, A2, A3) + { + } + typedef Function3 Test3Free; + + + void test0(Test& test) + { + } + typedef ReferenceCaller Test0Caller; + + void test0const(const Test& test) + { + } + typedef ConstReferenceCaller Test0ConstCaller; + + void test0p(Test* test) + { + } + typedef PointerCaller Test0PCaller; + + void test0constp(const Test* test) + { + } + typedef ConstPointerCaller Test0ConstPCaller; + + void test1(Test& test, A1) + { + } + typedef ReferenceCaller1 Test1Caller; + + void test1const(const Test& test, A1) + { + } + typedef ConstReferenceCaller1 Test1ConstCaller; + + void test1p(Test* test, A1) + { + } + typedef PointerCaller1 Test1PCaller; + + void test1constp(const Test* test, A1) + { + } + typedef ConstPointerCaller1 Test1ConstPCaller; + + void test2(Test& test, A1, A2) + { + } + typedef Function3 Test2; + + void test3(Test& test, A1, A2, A3) + { + } + typedef Function4 Test3; + + void instantiate() + { + Test test; + const Test& testconst = test; + { + Callback a = Test0FreeCaller(); + Callback b = Test::Test0Caller(test); + b = makeCallback0(Test::Test0(), test); + Callback c = Test::Test0ConstCaller(testconst); + c = makeCallback0(Test::Test0Const(), test); + Callback d = Test0Caller(test); + Callback e = Test0ConstCaller(testconst); + Callback f = Test0PCaller(&test); + Callback g = Test0ConstPCaller(&testconst); + a(); + bool u = a != b; + } + { + typedef Callback1 TestCallback1; + TestCallback1 a = Test1FreeCaller(); + TestCallback1 b = Test::Test1Caller(test); + b = makeCallback1(Test::Test1(), test); + TestCallback1 c = Test::Test1ConstCaller(testconst); + c = makeCallback1(Test::Test1Const(), test); + TestCallback1 d = Test1Caller(test); + TestCallback1 e = Test1ConstCaller(testconst); + TestCallback1 f = Test1PCaller(&test); + TestCallback1 g = Test1ConstPCaller(&testconst); + a(A1()); + bool u = a != b; + } + { + typedef Callback2 TestCallback2; + TestCallback2 a = makeStatelessCallback2(Test2Free()); + TestCallback2 b = makeCallback2(Test2(), test); + TestCallback2 c = makeCallback2(Test::Test2(), test); + TestCallback2 d = makeCallback2(Test::Test2Const(), test); + a(A1(), A2()); + bool u = a != b; + } + { + typedef Callback3 TestCallback3; + TestCallback3 a = makeStatelessCallback3(Test3Free()); + TestCallback3 b = makeCallback3(Test3(), test); + TestCallback3 c = makeCallback3(Test::Test3(), test); + TestCallback3 d = makeCallback3(Test::Test3Const(), test); + a(A1(), A2(), A3()); + bool u = a != b; + } + } +} diff --git a/libs/generic/callback.h b/libs/generic/callback.h new file mode 100644 index 00000000..7141da4a --- /dev/null +++ b/libs/generic/callback.h @@ -0,0 +1,670 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GENERIC_CLOSURE_H) +#define INCLUDED_GENERIC_CLOSURE_H + +/// \file +/// \brief Type-safe techniques for binding the first argument of an opaque callback. + +#include +#include "functional.h" +#include "callbackfwd.h" + +template +inline void* convertToOpaque(Type* t) +{ + return t; +} +template +inline void* convertToOpaque(const Type* t) +{ + return const_cast(t); +} +template +inline void* convertToOpaque(Type& t) +{ + return &t; +} +template +inline void* convertToOpaque(const Type& t) +{ + return const_cast(&t); +} + + +template +class ConvertFromOpaque +{ +}; + +template +class ConvertFromOpaque +{ +public: + static Type& apply(void* p) + { + return *static_cast(p); + } +}; + +template +class ConvertFromOpaque +{ +public: + static const Type& apply(void* p) + { + return *static_cast(p); + } +}; + + +template +class ConvertFromOpaque +{ +public: + static Type* apply(void* p) + { + return static_cast(p); + } +}; + +template +class ConvertFromOpaque +{ +public: + static const Type* apply(void* p) + { + return static_cast(p); + } +}; + +template +class BindFirstOpaque +{ + typedef typename Caller::first_argument_type FirstBound; + FirstBound firstBound; +public: + typedef typename Caller::result_type result_type; + explicit BindFirstOpaque(FirstBound firstBound) : firstBound(firstBound) + { + } + result_type operator()() const + { + return Caller::call(firstBound); + } + FirstBound getBound() const + { + return firstBound; + } + static result_type thunk(void* environment) + { + return Caller::call(ConvertFromOpaque::apply(environment)); + } + void* getEnvironment() const + { + return convertToOpaque(firstBound); + } +}; + +template +class BindFirstOpaque1 +{ + typedef typename Caller::first_argument_type FirstBound; + FirstBound firstBound; +public: + typedef typename Caller::second_argument_type first_argument_type; + typedef typename Caller::result_type result_type; + explicit BindFirstOpaque1(FirstBound firstBound) : firstBound(firstBound) + { + } + result_type operator()(first_argument_type a1) const + { + return Caller::call(firstBound, a1); + } + FirstBound getBound() const + { + return firstBound; + } + static result_type thunk(void* environment, first_argument_type a1) + { + return Caller::call(ConvertFromOpaque::apply(environment), a1); + } + void* getEnvironment() const + { + return convertToOpaque(firstBound); + } +}; + +template +class BindFirstOpaque2 +{ + typedef typename Caller::first_argument_type FirstBound; + FirstBound firstBound; +public: + typedef typename Caller::second_argument_type first_argument_type; + typedef typename Caller::third_argument_type second_argument_type; + typedef typename Caller::result_type result_type; + explicit BindFirstOpaque2(FirstBound firstBound) : firstBound(firstBound) + { + } + result_type operator()(first_argument_type a1, second_argument_type a2) const + { + return Caller::call(firstBound, a1, a2); + } + FirstBound getBound() const + { + return firstBound; + } + static result_type thunk(void* environment, first_argument_type a1, second_argument_type a2) + { + return Caller::call(ConvertFromOpaque::apply(environment), a1, a2); + } + void* getEnvironment() const + { + return convertToOpaque(firstBound); + } +}; + +template +class BindFirstOpaque3 +{ + typedef typename Caller::first_argument_type FirstBound; + FirstBound firstBound; +public: + typedef typename Caller::second_argument_type first_argument_type; + typedef typename Caller::third_argument_type second_argument_type; + typedef typename Caller::fourth_argument_type third_argument_type; + typedef typename Caller::result_type result_type; + explicit BindFirstOpaque3(FirstBound firstBound) : firstBound(firstBound) + { + } + result_type operator()(first_argument_type a1, second_argument_type a2, third_argument_type a3) const + { + return Caller::call(firstBound, a1, a2, a3); + } + FirstBound getBound() const + { + return firstBound; + } + static result_type thunk(void* environment, first_argument_type a1, second_argument_type a2, third_argument_type a3) + { + return Caller::call(ConvertFromOpaque::apply(environment), a1, a2, a3); + } + void* getEnvironment() const + { + return convertToOpaque(firstBound); + } +}; + +template +class CallbackBase +{ + void* m_environment; + Thunk_ m_thunk; +public: + typedef Thunk_ Thunk; + CallbackBase(void* environment, Thunk function) : m_environment(environment), m_thunk(function) + { + } + void* getEnvironment() const + { + return m_environment; + } + Thunk getThunk() const + { + return m_thunk; + } +}; + +template +inline bool operator==(const CallbackBase& self, const CallbackBase& other) +{ + return self.getEnvironment() == other.getEnvironment() && self.getThunk() == other.getThunk(); +} +template +inline bool operator!=(const CallbackBase& self, const CallbackBase& other) +{ + return !(self == other); +} +template +inline bool operator<(const CallbackBase& self, const CallbackBase& other) +{ + return self.getEnvironment() < other.getEnvironment() || + (!(other.getEnvironment() < self.getEnvironment()) && self.getThunk() < other.getThunk()); +} + + +/// \brief Combines a void pointer with a pointer to a function which operates on a void pointer. +/// +/// Use with the callback constructors MemberCaller, ConstMemberCaller, ReferenceCaller, ConstReferenceCaller, PointerCaller, ConstPointerCaller and FreeCaller. +template +class Callback0 : public CallbackBase +{ + typedef CallbackBase Base; + static Result nullThunk(void*) + { + } + +public: + typedef Result result_type; + + Callback0() : Base(0, nullThunk) + { + } + template + Callback0(const BindFirstOpaque& caller) : Base(caller.getEnvironment(), BindFirstOpaque::thunk) + { + } + Callback0(void* environment, typename Base::Thunk function) : Base(environment, function) + { + } + result_type operator()() const + { + return Base::getThunk()(Base::getEnvironment()); + } +}; + +template +inline Callback0 makeCallback0(const Caller& caller, typename Caller::first_argument_type callee) +{ + return Callback0(BindFirstOpaque(callee)); +} +template +inline Callback0 makeStatelessCallback0(const Caller& caller) +{ + return makeCallback0(Caller0To1(), 0); +} + +typedef Callback0 Callback; + + + +/// \brief Combines a void pointer with a pointer to a function which operates on a void pointer and one other argument. +/// +/// Use with the callback constructors MemberCaller1, ConstMemberCaller1, ReferenceCaller1, ConstReferenceCaller1, PointerCaller1, ConstPointerCaller1 and FreeCaller1. +template +class Callback1 : public CallbackBase +{ + typedef CallbackBase Base; + static Result nullThunk(void*, FirstArgument) + { + } + +public: + typedef FirstArgument first_argument_type; + typedef Result result_type; + + Callback1() : Base(0, nullThunk) + { + } + template + Callback1(const BindFirstOpaque1& caller) : Base(caller.getEnvironment(), BindFirstOpaque1::thunk) + { + } + Callback1(void* environment, typename Base::Thunk function) : Base(environment, function) + { + } + result_type operator()(FirstArgument firstArgument) const + { + return Base::getThunk()(Base::getEnvironment(), firstArgument); + } +}; + +template +inline Callback1 makeCallback1(const Caller& caller, typename Caller::first_argument_type callee) +{ + return Callback1(BindFirstOpaque1(callee)); +} +template +inline Callback1 makeStatelessCallback1(const Caller& caller) +{ + return makeCallback1(Caller1To2(), 0); +} + + +/// \brief Combines a void pointer with a pointer to a function which operates on a void pointer and two other arguments. +/// +template +class Callback2 : public CallbackBase +{ + typedef CallbackBase Base; + static Result nullThunk(void*, FirstArgument, SecondArgument) + { + } + +public: + typedef FirstArgument first_argument_type; + typedef SecondArgument second_argument_type; + typedef Result result_type; + + Callback2() : Base(0, nullThunk) + { + } + template + Callback2(const BindFirstOpaque2& caller) : Base(caller.getEnvironment(), BindFirstOpaque2::thunk) + { + } + Callback2(void* environment, typename Base::Thunk function) : Base(environment, function) + { + } + result_type operator()(FirstArgument firstArgument, SecondArgument secondArgument) const + { + return Base::getThunk()(Base::getEnvironment(), firstArgument, secondArgument); + } +}; + +template +inline Callback2< + typename Caller::second_argument_type, + typename Caller::third_argument_type, + typename Caller::result_type +> makeCallback2(const Caller& caller, typename Caller::first_argument_type callee) +{ + return Callback2< + typename Caller::second_argument_type, + typename Caller::third_argument_type, + typename Caller::result_type + >(BindFirstOpaque2(callee)); +} +template +inline Callback2< + typename Caller::first_argument_type, + typename Caller::second_argument_type, + typename Caller::result_type +> makeStatelessCallback2(const Caller& caller) +{ + return makeCallback2(Caller2To3(), 0); +} + + +/// \brief Combines a void pointer with a pointer to a function which operates on a void pointer and three other arguments. +/// +template +class Callback3 : public CallbackBase +{ + typedef CallbackBase Base; + static Result nullThunk(void*, FirstArgument, SecondArgument, ThirdArgument) + { + } + +public: + typedef FirstArgument first_argument_type; + typedef SecondArgument second_argument_type; + typedef ThirdArgument third_argument_type; + typedef Result result_type; + + Callback3() : Base(0, nullThunk) + { + } + template + Callback3(const BindFirstOpaque3& caller) : Base(caller.getEnvironment(), BindFirstOpaque3::thunk) + { + } + Callback3(void* environment, typename Base::Thunk function) : Base(environment, function) + { + } + result_type operator()(FirstArgument firstArgument, SecondArgument secondArgument, ThirdArgument thirdArgument) const + { + return Base::getThunk()(Base::getEnvironment(), firstArgument, secondArgument, thirdArgument); + } +}; + +template +inline Callback3< + typename Caller::second_argument_type, + typename Caller::third_argument_type, + typename Caller::fourth_argument_type, + typename Caller::result_type +> makeCallback3(const Caller& caller, typename Caller::first_argument_type callee) +{ + return Callback3< + typename Caller::second_argument_type, + typename Caller::third_argument_type, + typename Caller::fourth_argument_type, + typename Caller::result_type + >(BindFirstOpaque3(callee)); +} +template +inline Callback3< + typename Caller::first_argument_type, + typename Caller::second_argument_type, + typename Caller::third_argument_type, + typename Caller::result_type +> makeStatelessCallback3(const Caller& caller) +{ + return makeCallback3(Caller3To4(), 0); +} + + +/// \brief Forms a Callback from a non-const Environment reference and a non-const Environment member-function. +/// +/// \dontinclude generic/callback.cpp +/// \skipline MemberCaller example +/// \until end example +template +class MemberCaller : public BindFirstOpaque< Member > +{ +public: + MemberCaller(Environment& environment) : BindFirstOpaque< Member >(environment) + { + } +}; + +/// \brief Forms a Callback from a const Environment reference and a const Environment member-function. +/// +/// \dontinclude generic/callback.cpp +/// \skipline MemberCaller example +/// \until end example +template +class ConstMemberCaller : public BindFirstOpaque< ConstMember > +{ +public: + ConstMemberCaller(const Environment& environment) : BindFirstOpaque< ConstMember >(environment) + { + } +}; + +/// \brief Forms a Callback from a non-const Environment reference and a const Environment member-function which takes one argument. +template +class MemberCaller1 : public BindFirstOpaque1< Member1 > +{ +public: + MemberCaller1(Environment& environment) : BindFirstOpaque1< Member1 >(environment) + { + } +}; + +/// \brief Forms a Callback from a const Environment reference and a const Environment member-function which takes one argument. +template +class ConstMemberCaller1 : public BindFirstOpaque1< ConstMember1 > +{ +public: + ConstMemberCaller1(const Environment& environment) : BindFirstOpaque1< ConstMember1 >(environment) + { + } +}; + +/// \brief Forms a Callback from a non-const Environment reference and a free function which operates on a non-const Environment reference. +/// +/// \dontinclude generic/callback.cpp +/// \skipline ReferenceCaller example +/// \until end example +template +class ReferenceCaller : public BindFirstOpaque< Function1 > +{ +public: + ReferenceCaller(Environment& environment) : BindFirstOpaque< Function1 >(environment) + { + } +}; + +/// \brief Forms a Callback from a const Environment reference and a free function which operates on a const Environment reference. +/// +/// \dontinclude generic/callback.cpp +/// \skipline ReferenceCaller example +/// \until end example +template +class ConstReferenceCaller : public BindFirstOpaque< Function1 > +{ +public: + ConstReferenceCaller(const Environment& environment) : BindFirstOpaque< Function1 >(environment) + { + } +}; + +/// \brief Forms a Callback from a non-const Environment reference and a free function which operates on a non-const Environment reference and one other argument. +template +class ReferenceCaller1 : public BindFirstOpaque1< Function2 > +{ +public: + ReferenceCaller1(Environment& environment) : BindFirstOpaque1< Function2 >(environment) + { + } +}; + +/// \brief Forms a Callback from a const Environment reference and a free function which operates on a const Environment reference and one other argument. +template +class ConstReferenceCaller1 : public BindFirstOpaque1< Function2 > +{ +public: + ConstReferenceCaller1(const Environment& environment) : BindFirstOpaque1< Function2 >(environment) + { + } +}; + +/// \brief Forms a Callback from a non-const Environment pointer and a free function which operates on a non-const Environment pointer. +template +class PointerCaller : public BindFirstOpaque< Function1 > +{ +public: + PointerCaller(Environment* environment) : BindFirstOpaque< Function1 >(environment) + { + } +}; + +/// \brief Forms a Callback from a const Environment pointer and a free function which operates on a const Environment pointer. +template +class ConstPointerCaller : public BindFirstOpaque< Function1 > +{ +public: + ConstPointerCaller(const Environment* environment) : BindFirstOpaque< Function1 >(environment) + { + } +}; + +/// \brief Forms a Callback from a non-const Environment pointer and a free function which operates on a non-const Environment pointer and one other argument. +template +class PointerCaller1 : public BindFirstOpaque1< Function2 > +{ +public: + PointerCaller1(Environment* environment) : BindFirstOpaque1< Function2 >(environment) + { + } +}; + +/// \brief Forms a Callback from a const Environment pointer and a free function which operates on a const Environment pointer and one other argument. +template +class ConstPointerCaller1 : public BindFirstOpaque1< Function2 > +{ +public: + ConstPointerCaller1(const Environment* environment) : BindFirstOpaque1< Function2 >(environment) + { + } +}; + +/// \brief Forms a Callback from a free function which takes no arguments. +template +class FreeCaller : public BindFirstOpaque< Caller0To1< Function0 > > +{ +public: + FreeCaller() : BindFirstOpaque< Caller0To1< Function0 > >(0) + { + } +}; + +/// \brief Forms a Callback from a free function which takes a single argument. +template +class FreeCaller1 : public BindFirstOpaque1< Caller1To2< Function1 > > +{ +public: + FreeCaller1() : BindFirstOpaque1< Caller1To2< Function1 > >(0) + { + } +}; + + +/// \brief Constructs a Callback from a non-const \p functor with zero arguments. +/// +/// \param Functor Must define \c operator()(). +template +inline Callback makeCallback(Functor& functor) +{ + return Callback(MemberCaller(functor)); +} + +/// \brief Constructs a Callback from a const \p functor with zero arguments. +/// +/// \param Functor Must define const \c operator()(). +template +inline Callback makeCallback(const Functor& functor) +{ + return Callback(ConstMemberCaller(functor)); +} + +/// \brief Constructs a Callback1 from a non-const \p functor with one argument. +/// +/// \param Functor Must define \c first_argument_type and \c operator()(first_argument_type). +template +inline Callback1 makeCallback1(Functor& functor) +{ + typedef typename Functor::first_argument_type FirstArgument; + return Callback1(MemberCaller1(functor)); +} + +/// \brief Constructs a Callback1 from a const \p functor with one argument. +/// +/// \param Functor Must define \c first_argument_type and const \c operator()(first_argument_type). +template +inline Callback1 makeCallback1(const Functor& functor) +{ + typedef typename Functor::first_argument_type FirstArgument; + return Callback1(ConstMemberCaller1(functor)); +} + + +typedef Callback1 BoolImportCallback; +typedef Callback1 BoolExportCallback; + +typedef Callback1 IntImportCallback; +typedef Callback1 IntExportCallback; + +typedef Callback1 FloatImportCallback; +typedef Callback1 FloatExportCallback; + +typedef Callback1 StringImportCallback; +typedef Callback1 StringExportCallback; + +typedef Callback1 SizeImportCallback; +typedef Callback1 SizeExportCallback; + + +#endif diff --git a/libs/generic/callbackfwd.cpp b/libs/generic/callbackfwd.cpp new file mode 100644 index 00000000..a2faa664 --- /dev/null +++ b/libs/generic/callbackfwd.cpp @@ -0,0 +1,3 @@ + +#include "callbackfwd.h" + diff --git a/libs/generic/callbackfwd.h b/libs/generic/callbackfwd.h new file mode 100644 index 00000000..f89f9090 --- /dev/null +++ b/libs/generic/callbackfwd.h @@ -0,0 +1,18 @@ + +#if !defined(INCLUDED_CALLBACKFWD_H) +#define INCLUDED_CALLBACKFWD_H + +template +class Callback0; +typedef Callback0 Callback; + +template +class Callback1; + +template +class Callback2; + +template +class Callback3; + +#endif diff --git a/libs/generic/constant.cpp b/libs/generic/constant.cpp new file mode 100644 index 00000000..ff54c9cc --- /dev/null +++ b/libs/generic/constant.cpp @@ -0,0 +1,40 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "constant.h" + +#if defined(_DEBUG) || defined(DOXYGEN) + +namespace ExampleConstant +{ + class Bleh + { + public: + + STRING_CONSTANT(Name, "Bleh"); + INTEGER_CONSTANT(Version, 1); + }; + + int version = Bleh::Version(); + const char* name = Bleh::Name(); +} + +#endif diff --git a/libs/generic/constant.h b/libs/generic/constant.h new file mode 100644 index 00000000..42b2fa5d --- /dev/null +++ b/libs/generic/constant.h @@ -0,0 +1,50 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GENERIC_CONSTANT_H) +#define INCLUDED_GENERIC_CONSTANT_H + +/// \file +/// \brief Language extensions for constants that are guaranteed to be evaluated at compile-time. + +/// \brief A compile-time-constant as a type. +template +struct ConstantWrapper +{ + typedef typename Type::Value Value; + operator Value() const + { + return Type::evaluate(); + } +}; +template +inline TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const ConstantWrapper& c) +{ + return ostream_write(ostream, typename Type::Value(c)); +} + +#define TYPE_CONSTANT(name, value, type) struct name##_CONSTANT_ { typedef type Value; static Value evaluate() { return value; } }; typedef ConstantWrapper name +#define STRING_CONSTANT(name, value) TYPE_CONSTANT(name, value, const char*) +#define INTEGER_CONSTANT(name, value) TYPE_CONSTANT(name, value, int) + +STRING_CONSTANT(EmptyString, ""); + +#endif diff --git a/libs/generic/enumeration.cpp b/libs/generic/enumeration.cpp new file mode 100644 index 00000000..4591fda9 --- /dev/null +++ b/libs/generic/enumeration.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "enumeration.h" + diff --git a/libs/generic/enumeration.h b/libs/generic/enumeration.h new file mode 100644 index 00000000..31e43052 --- /dev/null +++ b/libs/generic/enumeration.h @@ -0,0 +1,60 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GENERIC_ENUMERATION_H) +#define INCLUDED_GENERIC_ENUMERATION_H + +/// \file +/// \brief Type safe enumeration. + +/// \brief An enumerated value. +/// +/// - Can be forward-declared when the definition of Enumeration is unknown. +/// - Can only be constructed from valid enumerated values. +/// - Can only be compared with others of the same type. +/// +/// \param Enumeration A type that contains an enum \c Value of the allowed values of the enumeration. +template +class EnumeratedValue : public Enumeration +{ + typename Enumeration::Value m_value; +public: + explicit EnumeratedValue(typename Enumeration::Value value) : m_value(value) + { + } + typename Enumeration::Value get() const + { + return m_value; + } +}; + +template +inline bool operator==(EnumeratedValue self, EnumeratedValue other) +{ + return self.get() == other.get(); +} +template +inline bool operator!=(EnumeratedValue self, EnumeratedValue other) +{ + return !operator==(self, other); +} + +#endif diff --git a/libs/generic/functional.cpp b/libs/generic/functional.cpp new file mode 100644 index 00000000..4717c8e2 --- /dev/null +++ b/libs/generic/functional.cpp @@ -0,0 +1,3 @@ + +#include "functional.h" + diff --git a/libs/generic/functional.h b/libs/generic/functional.h new file mode 100644 index 00000000..bd253486 --- /dev/null +++ b/libs/generic/functional.h @@ -0,0 +1,313 @@ + +#if !defined(INCLUDED_FUNCTIONAL_H) +#define INCLUDED_FUNCTIONAL_H + +template +class Member +{ +public: + typedef Object& first_argument_type; + typedef R result_type; + static result_type call(first_argument_type object) + { + return (object.*member)(); + } +}; + +template +class ConstMember +{ +public: + typedef const Object& first_argument_type; + typedef R result_type; + static result_type call(first_argument_type object) + { + return (object.*member)(); + } +}; + +template +class Member1 +{ +public: + typedef Object& first_argument_type; + typedef A1 second_argument_type; + typedef R result_type; + static result_type call(first_argument_type object, second_argument_type a1) + { + return (object.*member)(a1); + } +}; + +template +class ConstMember1 +{ +public: + typedef const Object& first_argument_type; + typedef A1 second_argument_type; + typedef R result_type; + static result_type call(first_argument_type object, second_argument_type a1) + { + return (object.*member)(a1); + } +}; + +template +class Member2 +{ +public: + typedef Object& first_argument_type; + typedef A2 second_argument_type; + typedef A3 third_argument_type; + typedef R result_type; + static result_type call(first_argument_type object, second_argument_type a2, third_argument_type a3) + { + return (object.*member)(a2, a3); + } +}; + +template +class ConstMember2 +{ +public: + typedef const Object& first_argument_type; + typedef A2 second_argument_type; + typedef A3 third_argument_type; + typedef R result_type; + static result_type call(first_argument_type object, second_argument_type a2, third_argument_type a3) + { + return (object.*member)(a2, a3); + } +}; + +template +class Member3 +{ +public: + typedef Object& first_argument_type; + typedef A2 second_argument_type; + typedef A3 third_argument_type; + typedef A4 fourth_argument_type; + typedef R result_type; + static result_type call(first_argument_type object, second_argument_type a2, third_argument_type a3, fourth_argument_type a4) + { + return (object.*member)(a2, a3, a4); + } +}; + +template +class ConstMember3 +{ +public: + typedef const Object& first_argument_type; + typedef A2 second_argument_type; + typedef A3 third_argument_type; + typedef A4 fourth_argument_type; + typedef R result_type; + static result_type call(first_argument_type object, second_argument_type a2, third_argument_type a3, fourth_argument_type a4) + { + return (object.*member)(a2, a3, a4); + } +}; + +template +class Function0 +{ +public: + typedef R result_type; + static result_type call() + { + return (func)(); + } +}; + +template +class Function1 +{ +public: + typedef A1 first_argument_type; + typedef R result_type; + static result_type call(first_argument_type a1) + { + return (func)(a1); + } +}; + +template +class Function2 +{ +public: + typedef A1 first_argument_type; + typedef A2 second_argument_type; + typedef R result_type; + static result_type call(first_argument_type a1, second_argument_type a2) + { + return (func)(a1, a2); + } +}; + +template +class Function3 +{ +public: + typedef A1 first_argument_type; + typedef A2 second_argument_type; + typedef A3 third_argument_type; + typedef R result_type; + static result_type call(first_argument_type a1, second_argument_type a2, third_argument_type a3) + { + return (func)(a1, a2, a3); + } +}; + +template +class Function4 +{ +public: + typedef A1 first_argument_type; + typedef A2 second_argument_type; + typedef A3 third_argument_type; + typedef A4 fourth_argument_type; + typedef R result_type; + static result_type call(first_argument_type a1, second_argument_type a2, third_argument_type a3, fourth_argument_type a4) + { + return (func)(a1, a2, a3, a4); + } +}; + +template +class Caller0To1 +{ +public: + typedef FirstArgument first_argument_type; + typedef typename Caller::result_type result_type; + static result_type call(first_argument_type) + { + return Caller::call(); + } +}; + +template +class Caller1To2 +{ +public: + typedef FirstArgument first_argument_type; + typedef typename Caller::first_argument_type second_argument_type; + typedef typename Caller::result_type result_type; + static result_type call(first_argument_type, second_argument_type a2) + { + return Caller::call(a2); + } +}; + +template +class Caller2To3 +{ +public: + typedef FirstArgument first_argument_type; + typedef typename Caller::first_argument_type second_argument_type; + typedef typename Caller::second_argument_type third_argument_type; + typedef typename Caller::result_type result_type; + static result_type call(first_argument_type, second_argument_type a2, third_argument_type a3) + { + return Caller::call(a2, a3); + } +}; + +template +class Caller3To4 +{ +public: + typedef FirstArgument first_argument_type; + typedef typename Caller::first_argument_type second_argument_type; + typedef typename Caller::second_argument_type third_argument_type; + typedef typename Caller::third_argument_type fourth_argument_type; + typedef typename Caller::result_type result_type; + static result_type call(first_argument_type, second_argument_type a2, third_argument_type a3, fourth_argument_type a4) + { + return Caller::call(a2, a3, a4); + } +}; + +template +class FunctorInvoke +{ +public: + typedef typename Functor::result_type result_type; + inline result_type operator()(Functor functor) + { + return functor(); + } +}; + +template +class Functor1Invoke +{ + typename Functor::first_argument_type a1; +public: + typedef typename Functor::first_argument_type first_argument_type; + typedef typename Functor::result_type result_type; + Functor1Invoke(first_argument_type a1) : a1(a1) + { + } + inline result_type operator()(Functor functor) + { + return functor(a1); + } +}; + +template +class Functor2Invoke +{ + typename Functor::first_argument_type a1; + typename Functor::second_argument_type a2; +public: + typedef typename Functor::first_argument_type first_argument_type; + typedef typename Functor::second_argument_type second_argument_type; + typedef typename Functor::result_type result_type; + Functor2Invoke(first_argument_type a1, second_argument_type a2) + : a1(a1), a2(a2) + { + } + inline result_type operator()(Functor functor) + { + return functor(a1, a2); + } +}; + +template +class Functor3Invoke +{ + typename Functor::first_argument_type a1; + typename Functor::second_argument_type a2; + typename Functor::third_argument_type a3; +public: + typedef typename Functor::first_argument_type first_argument_type; + typedef typename Functor::second_argument_type second_argument_type; + typedef typename Functor::third_argument_type third_argument_type; + typedef typename Functor::result_type result_type; + Functor3Invoke(first_argument_type a1, second_argument_type a2, third_argument_type a3) + : a1(a1), a2(a2), a3(a3) + { + } + inline result_type operator()(Functor functor) + { + return functor(a1, a2, a3); + } +}; + +template +class TypeEqual +{ +public: + typedef False type; +}; +template +class TypeEqual +{ +public: + typedef True type; +}; + + +#endif diff --git a/libs/generic/object.cpp b/libs/generic/object.cpp new file mode 100644 index 00000000..a6ed31c0 --- /dev/null +++ b/libs/generic/object.cpp @@ -0,0 +1,41 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "object.h" + +namespace +{ + class Blah + { + int i; + public: + Blah() + { + i = 3; + } + }; + + void Test() + { + char storage[sizeof(Blah)]; + constructor(*reinterpret_cast(storage)); + } +} \ No newline at end of file diff --git a/libs/generic/object.h b/libs/generic/object.h new file mode 100644 index 00000000..ec8e7262 --- /dev/null +++ b/libs/generic/object.h @@ -0,0 +1,98 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GENERIC_OBJECT_H) +#define INCLUDED_GENERIC_OBJECT_H + +/// \file +/// \brief Convenience functions (syntactic sugar) to wrap explicit constructor (aka in-place 'new') and destructor calls. +/// +/// Use makeReference() to wrap non-const-reference constructor parameters. + +#if _MSC_VER > 1000 && defined(WIN32) +#pragma warning(disable:4345) // behavior change: an object of POD type constructed with an initializer of the form () will be default-initialized +#endif + +#include + +template +inline void constructor(Type& object) +{ + new(&object) Type(); +} + +template +inline void constructor(Type& object, const T1& t1) +{ + new(&object) Type(t1); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2) +{ + new(&object) Type(t1, t2); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2, const T3& t3) +{ + new(&object) Type(t1, t2, t3); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2, const T3& t3, const T4& t4) +{ + new(&object) Type(t1, t2, t3, t4); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5) +{ + new(&object) Type(t1, t2, t3, t4, t5); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6) +{ + new(&object) Type(t1, t2, t3, t4, t5, t6); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6, const T7& t7) +{ + new(&object) Type(t1, t2, t3, t4, t5, t6, t7); +} + +template +inline void constructor(Type& object, const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6, const T7& t7, const T8& t8) +{ + new(&object) Type(t1, t2, t3, t4, t5, t6, t7, t8); +} + +template +inline void destructor(Type& object) +{ + object.~Type(); +} + + + +#endif diff --git a/libs/generic/reference.cpp b/libs/generic/reference.cpp new file mode 100644 index 00000000..c09cad82 --- /dev/null +++ b/libs/generic/reference.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "reference.h" diff --git a/libs/generic/reference.h b/libs/generic/reference.h new file mode 100644 index 00000000..62e2c238 --- /dev/null +++ b/libs/generic/reference.h @@ -0,0 +1,115 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GENERIC_REFERENCE_H) +#define INCLUDED_GENERIC_REFERENCE_H + +/// \file +/// \brief Wrappers to allow storing objects in templated containers using 'reference' semantics. + +/// \brief A reference to a mutable object. +/// Has 'reference' semantics, except for \c 'operator==' and \c 'operator.'. +/// \param Type The type of the referenced object. +template +class Reference +{ + Type* m_contained; +public: + explicit Reference(Type& contained) : m_contained(&contained) + { + } + operator Type&() const + { + return *m_contained; + } + Type& get() const + { + return *m_contained; + } + Type* get_pointer() const + { + return m_contained; + } +}; + +template +bool operator<(const Reference& self, const Reference& other) +{ + return self.get() < other.get(); +} +template +bool operator==(const Reference& self, const Reference& other) +{ + return self.get() == other.get(); +} + +/// \brief construct a reference to a mutable object. +template +inline Reference makeReference(Type& value) +{ + return Reference(value); +} + +/// \brief A reference to a non-mutable object. +/// Has 'reference' semantics, except for \c 'operator==' and \c 'operator.'. +/// \param Type The type of the referenced object. +template +class ConstReference +{ + const Type* m_contained; +public: + explicit ConstReference(const Type& contained) : m_contained(&contained) + { + } + operator const Type&() const + { + return *m_contained; + } + const Type& get() const + { + return *m_contained; + } + const Type* get_pointer() const + { + return m_contained; + } +}; + +template +bool operator<(const ConstReference& self, const ConstReference& other) +{ + return self.get() < other.get(); +} +template +bool operator==(const ConstReference& self, const ConstReference& other) +{ + return self.get() == other.get(); +} + +/// \brief construct a reference to a non-mutable object. +template +inline ConstReference makeReference(const Type& value) +{ + return ConstReference(value); +} + + +#endif diff --git a/libs/generic/referencecounted.cpp b/libs/generic/referencecounted.cpp new file mode 100644 index 00000000..757e08d8 --- /dev/null +++ b/libs/generic/referencecounted.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "referencecounted.h" diff --git a/libs/generic/referencecounted.h b/libs/generic/referencecounted.h new file mode 100644 index 00000000..e761cccc --- /dev/null +++ b/libs/generic/referencecounted.h @@ -0,0 +1,207 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GENERIC_REFERENCECOUNTED_H) +#define INCLUDED_GENERIC_REFERENCECOUNTED_H + +/// \file +/// \brief 'smart' pointers and references. + +#include + +template +class IncRefDecRefCounter +{ +public: + void increment(Type& value) + { + value.IncRef(); + } + void decrement(Type& value) + { + value.DecRef(); + } +}; + +/// \brief A smart-pointer that uses a counter stored in the object pointed-to. +template > +class SmartPointer : public Counter +{ + Type* m_value; +public: + + SmartPointer(const SmartPointer& other) + : m_value(other.m_value) + { + Counter::increment(*m_value); + } + explicit SmartPointer(Type* value) + : m_value(value) + { + Counter::increment(*m_value); + } + ~SmartPointer() + { + Counter::decrement(*m_value); + } + SmartPointer& operator=(const SmartPointer& other) + { + SmartPointer temp(other); + temp.swap(*this); + return *this; + } + SmartPointer& operator=(Type* value) + { + SmartPointer temp(value); + temp.swap(*this); + return *this; + } + void swap(SmartPointer& other) + { + std::swap(m_value, other.m_value); + } + + operator Type*() const + { + return m_value; + } + Type& operator*() const + { + return *m_value; + } + Type* operator->() const + { + return m_value; + } + Type* get() const + { + return m_value; + } +}; + +template +inline bool operator<(const SmartPointer& self, const SmartPointer& other) +{ + return self.get() < other.get(); +} +template +inline bool operator==(const SmartPointer& self, const SmartPointer& other) +{ + return self.get() == other.get(); +} +template +inline bool operator!=(const SmartPointer& self, const SmartPointer& other) +{ + return !::operator==(self, other); +} + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap(). + template + inline void swap(SmartPointer& self, SmartPointer& other) + { + self.swap(other); + } +} + + +/// \brief A smart-reference that uses a counter stored in the object pointed-to. +template > +class SmartReference : public Counter +{ + Type* m_value; +public: + + SmartReference(const SmartReference& other) + : m_value(other.m_value) + { + Counter::increment(*m_value); + } + explicit SmartReference(Type& value) + : m_value(&value) + { + Counter::increment(*m_value); + } + ~SmartReference() + { + Counter::decrement(*m_value); + } + SmartReference& operator=(const SmartReference& other) + { + SmartReference temp(other); + temp.swap(*this); + return *this; + } + SmartReference& operator=(Type& value) + { + SmartReference temp(value); + temp.swap(*this); + return *this; + } + void swap(SmartReference& other) + { + std::swap(m_value, other.m_value); + } + + operator Type&() const + { + return *m_value; + } + Type& get() const + { + return *m_value; + } + Type* get_pointer() const + { + return m_value; + } +}; + +template +inline bool operator<(const SmartReference& self, const SmartReference& other) +{ + return self.get() < other.get(); +} +template +inline bool operator==(const SmartReference& self, const SmartReference& other) +{ + return self.get() == other.get(); +} +template +inline bool operator!=(const SmartReference& self, const SmartReference& other) +{ + return !::operator==(self, other); +} + +namespace std +{ + /// \brief Swaps the values of \p self and \p other. + /// Overloads std::swap(). + template + inline void swap(SmartReference& self, SmartReference& other) + { + self.swap(other); + } +} + +#endif diff --git a/libs/generic/static.cpp b/libs/generic/static.cpp new file mode 100644 index 00000000..ecd539f3 --- /dev/null +++ b/libs/generic/static.cpp @@ -0,0 +1,133 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "static.h" + +#if defined(_DEBUG) || defined(DOXYGEN) + +namespace ExampleStatic +{ + // Static example + // ---- myclass.h + class MyClass + { + public: + int value; + MyClass() : value(3) + { + } + }; + + typedef Static StaticMyClass; + + // ---- main.cpp + class DynamicInitialisation + { + public: + DynamicInitialisation() + { + // StaticMyClass::instance() may be invalid here because construction order is undefined + } + }; + + DynamicInitialisation g_dynamicInitialisation; + + void duringMain() + { + int bar = StaticMyClass::instance().value; + } + // end example +} + +namespace ExampleLazyStatic +{ + // LazyStatic example + // ---- myclass.h + class MyClass + { + public: + int value; + MyClass() : value(3) + { + } + // destructor will never be called + }; + + typedef LazyStatic StaticMyClass; + + // ---- main.cpp + class DynamicInitialisation + { + public: + DynamicInitialisation() + { + int bar = StaticMyClass::instance().value; + } + }; + + DynamicInitialisation g_dynamicInitialisation; + + void duringMain() + { + int bar = StaticMyClass::instance().value; + } + // end example +} + +namespace ExampleSmartStatic +{ + // SmartStatic example + // ---- myclass.h + class MyClass + { + public: + int value; + MyClass() : value(3) + { + } + }; + + typedef CountedStatic StaticMyClass; + + // ---- main.cpp + class DynamicInitialisation + { + public: + DynamicInitialisation() + { + // StaticMyClass::instance() is invalid before the ref is constructed + SmartStatic ref; + int bar = ref.instance().value; + + SmartStatic ref2; // any number of instances are allowed. + } + }; + + DynamicInitialisation g_dynamicInitialisation; + + void duringMain() + { + int bar = SmartStatic().instance().value; // an instance can be a temporary + } + // end example +} + +#endif diff --git a/libs/generic/static.h b/libs/generic/static.h new file mode 100644 index 00000000..81691827 --- /dev/null +++ b/libs/generic/static.h @@ -0,0 +1,151 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GENERIC_STATIC_H) +#define INCLUDED_GENERIC_STATIC_H + +/// \file +/// \brief Template techniques for instantiating singletons. + +#include + +class Null +{ +}; + +/// \brief A singleton which is statically initialised. +/// +/// \param Type The singleton object type. +/// \param Type The type distinguishing this instance from others of the same type. +/// +/// \dontinclude generic/static.cpp +/// \skipline Static example +/// \until end example +template +class Static +{ + static Type m_instance; +public: + static Type& instance() + { + return m_instance; + } +}; + +template +Type Static::m_instance; + + +/// \brief A singleton which is lazily initialised. +/// The instance is constructed the first time it is referenced, and is never destroyed. +/// +/// \param Type The singleton object type. +/// \param Type The type distinguishing this instance from others of the same type. +/// +/// \dontinclude generic/static.cpp +/// \skipline LazyStatic example +/// \until end example +template +class LazyStatic +{ + static Type* m_instance; // this will be initialised to 0 by the CRT, according to the c++ standard +public: + static Type& instance() + { + if(m_instance == 0) + { + m_instance = new Type; // allocate using 'new' to get the correct alignment + } + return *m_instance; + } +}; + +template +Type* LazyStatic::m_instance; + + +/// \brief A singleton which keeps a count of the number of times it is referenced. +/// +/// The instance is constructed when its reference count changes from 0 to 1 and destroyed when its reference count changes from 1 to 0. +/// Use with SmartStatic. +/// +/// \param Type The singleton object type. +/// \param Type The type distinguishing this instance from others of the same type. +template +class CountedStatic +{ + static std::size_t m_refcount; // this will be initialised to 0 by the CRT, according to the c++ standard + static Type* m_instance; +public: + static Type& instance() + { + return *m_instance; + } + static void capture() + { + if(++m_refcount == 1) + { + m_instance = new Type; // allocate using 'new' to get the correct alignment + } + } + static void release() + { + if(--m_refcount == 0) + { + delete m_instance; + } + } +}; + +template +std::size_t CountedStatic::m_refcount; // this will be initialised to 0 by the CRT, according to the c++ standard +template +Type* CountedStatic::m_instance; + +/// \brief A reference to a CountedStatic. +/// Guarantees that CountedStatic will be constructed for the lifetime of this object. +/// +/// \param Type The type parameter of the CountedStatic to reference. +/// \param Type The type distinguishing this instance from others of the same type. +/// +/// \dontinclude generic/static.cpp +/// \skipline SmartStatic example +/// \until end example +template +class SmartStatic +{ +public: + SmartStatic() + { + CountedStatic::capture(); + } + ~SmartStatic() + { + CountedStatic::release(); + } + Type& instance() + { + return CountedStatic::instance(); + } +}; + + +#endif diff --git a/libs/generic/vector.cpp b/libs/generic/vector.cpp new file mode 100644 index 00000000..83765dcb --- /dev/null +++ b/libs/generic/vector.cpp @@ -0,0 +1,3 @@ + +#include "vector.h" + diff --git a/libs/generic/vector.h b/libs/generic/vector.h new file mode 100644 index 00000000..6e6f74e2 --- /dev/null +++ b/libs/generic/vector.h @@ -0,0 +1,259 @@ + +#if !defined(INCLUDED_VECTOR_H) +#define INCLUDED_VECTOR_H + +#include + +template +class BasicVector2 +{ + Element m_elements[2]; +public: + BasicVector2() + { + } + BasicVector2(const Element& x_, const Element& y_) + { + x() = x_; + y() = y_; + } + + Element& x() + { + return m_elements[0]; + } + const Element& x() const + { + return m_elements[0]; + } + Element& y() + { + return m_elements[1]; + } + const Element& y() const + { + return m_elements[1]; + } + + const Element& operator[](std::size_t i) const + { + return m_elements[i]; + } + Element& operator[](std::size_t i) + { + return m_elements[i]; + } + + Element* data() + { + return m_elements; + } + const Element* data() const + { + return m_elements; + } +}; + +/// \brief A 3-element vector. +template +class BasicVector3 +{ + Element m_elements[3]; +public: + + BasicVector3() + { + } + template + BasicVector3(const BasicVector3& other) + { + x() = static_cast(other.x()); + y() = static_cast(other.y()); + z() = static_cast(other.z()); + } + BasicVector3(const Element& x_, const Element& y_, const Element& z_) + { + x() = x_; + y() = y_; + z() = z_; + } + + Element& x() + { + return m_elements[0]; + } + const Element& x() const + { + return m_elements[0]; + } + Element& y() + { + return m_elements[1]; + } + const Element& y() const + { + return m_elements[1]; + } + Element& z() + { + return m_elements[2]; + } + const Element& z() const + { + return m_elements[2]; + } + + const Element& operator[](std::size_t i) const + { + return m_elements[i]; + } + Element& operator[](std::size_t i) + { + return m_elements[i]; + } + + Element* data() + { + return m_elements; + } + const Element* data() const + { + return m_elements; + } +}; + +/// \brief A 4-element vector. +template +class BasicVector4 +{ + Element m_elements[4]; +public: + + BasicVector4() + { + } + BasicVector4(Element x_, Element y_, Element z_, Element w_) + { + x() = x_; + y() = y_; + z() = z_; + w() = w_; + } + BasicVector4(const BasicVector3& self, Element w_) + { + x() = self.x(); + y() = self.y(); + z() = self.z(); + w() = w_; + } + + Element& x() + { + return m_elements[0]; + } + const Element& x() const + { + return m_elements[0]; + } + Element& y() + { + return m_elements[1]; + } + const Element& y() const + { + return m_elements[1]; + } + Element& z() + { + return m_elements[2]; + } + const Element& z() const + { + return m_elements[2]; + } + Element& w() + { + return m_elements[3]; + } + const Element& w() const + { + return m_elements[3]; + } + + Element index(std::size_t i) const + { + return m_elements[i]; + } + Element& index(std::size_t i) + { + return m_elements[i]; + } + Element operator[](std::size_t i) const + { + return m_elements[i]; + } + Element& operator[](std::size_t i) + { + return m_elements[i]; + } + + Element* data() + { + return m_elements; + } + const Element* data() const + { + return m_elements; + } +}; + +template +inline BasicVector3 vector3_from_array(const Element* array) +{ + return BasicVector3(array[0], array[1], array[2]); +} + +template +inline Element* vector3_to_array(BasicVector3& self) +{ + return self.data(); +} +template +inline const Element* vector3_to_array(const BasicVector3& self) +{ + return self.data(); +} + +template +inline Element* vector4_to_array(BasicVector4& self) +{ + return self.data(); +} +template +inline const Element* vector4_to_array(const BasicVector4& self) +{ + return self.data(); +} + +template +inline BasicVector3& vector4_to_vector3(BasicVector4& self) +{ + return *reinterpret_cast*>(vector4_to_array(self)); +} +template +inline const BasicVector3& vector4_to_vector3(const BasicVector4& self) +{ + return *reinterpret_cast*>(vector4_to_array(self)); +} + +/// \brief A 2-element vector stored in single-precision floating-point. +typedef BasicVector2 Vector2; + +/// \brief A 3-element vector stored in single-precision floating-point. +typedef BasicVector3 Vector3; + +/// \brief A 4-element vector stored in single-precision floating-point. +typedef BasicVector4 Vector4; + + +#endif diff --git a/libs/gtkutil/accelerator.cpp b/libs/gtkutil/accelerator.cpp new file mode 100644 index 00000000..af3eec17 --- /dev/null +++ b/libs/gtkutil/accelerator.cpp @@ -0,0 +1,718 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "accelerator.h" + +#include "debugging/debugging.h" + +#include +#include +#include +#include + +#include "generic/callback.h" +#include "generic/bitfield.h" +#include "string/string.h" + +#include "pointer.h" +#include "closure.h" + +#include + + + +struct SKeyInfo +{ + const char* m_strName; + unsigned int m_nVKKey; +}; + +SKeyInfo g_Keys[] = +{ + {"Space", GDK_space}, + {"Backspace", GDK_BackSpace}, + {"Escape", GDK_Escape}, + {"End", GDK_End}, + {"Insert", GDK_Insert}, + {"Delete", GDK_Delete}, + {"PageUp", GDK_Prior}, + {"PageDown", GDK_Next}, + {"Up", GDK_Up}, + {"Down", GDK_Down}, + {"Left", GDK_Left}, + {"Right", GDK_Right}, + {"F1", GDK_F1}, + {"F2", GDK_F2}, + {"F3", GDK_F3}, + {"F4", GDK_F4}, + {"F5", GDK_F5}, + {"F6", GDK_F6}, + {"F7", GDK_F7}, + {"F8", GDK_F8}, + {"F9", GDK_F9}, + {"F10", GDK_F10}, + {"F11", GDK_F11}, + {"F12", GDK_F12}, + {"Tab", GDK_Tab}, + {"Return", GDK_Return}, + {"Comma", GDK_comma}, + {"Period", GDK_period}, + {"Plus", GDK_KP_Add}, + {"Multiply", GDK_multiply}, + {"Minus", GDK_KP_Subtract}, + {"NumPad0", GDK_KP_0}, + {"NumPad1", GDK_KP_1}, + {"NumPad2", GDK_KP_2}, + {"NumPad3", GDK_KP_3}, + {"NumPad4", GDK_KP_4}, + {"NumPad5", GDK_KP_5}, + {"NumPad6", GDK_KP_6}, + {"NumPad7", GDK_KP_7}, + {"NumPad8", GDK_KP_8}, + {"NumPad9", GDK_KP_9}, + {"[", 219}, + {"]", 221}, + {"\\", 220}, + {"Home", GDK_Home} +}; + +int g_nKeyCount = sizeof(g_Keys) / sizeof(SKeyInfo); + +const char* global_keys_find(unsigned int key) +{ + for(int i = 0; i < g_nKeyCount; ++i) + { + if(g_Keys[i].m_nVKKey == key) + { + return g_Keys[i].m_strName; + } + } + return ""; +} + +unsigned int global_keys_find(const char* name) +{ + for(int i = 0; i < g_nKeyCount; ++i) + { + if(string_equal_nocase(g_Keys[i].m_strName, name)) + { + return g_Keys[i].m_nVKKey; + } + } + return 0; +} + +void accelerator_write(const Accelerator& accelerator, TextOutputStream& ostream) +{ + if(accelerator.modifiers & GDK_SHIFT_MASK) + { + ostream << "Shift + "; + } + if(accelerator.modifiers & GDK_MOD1_MASK) + { + ostream << "Alt + "; + } + if(accelerator.modifiers & GDK_CONTROL_MASK) + { + ostream << "Control + "; + } + + const char* keyName = global_keys_find(accelerator.key); + if(!string_empty(keyName)) + { + ostream << keyName; + } + else + { + ostream << static_cast(accelerator.key); + } +} + +typedef std::map AcceleratorMap; +typedef std::set AcceleratorSet; + +bool accelerator_map_insert(AcceleratorMap& acceleratorMap, Accelerator accelerator, const Callback& callback) +{ + if(accelerator.key != 0) + { + return acceleratorMap.insert(AcceleratorMap::value_type(accelerator, callback)).second; + } + return true; +} + +bool accelerator_map_erase(AcceleratorMap& acceleratorMap, Accelerator accelerator) +{ + if(accelerator.key != 0) + { + AcceleratorMap::iterator i = acceleratorMap.find(accelerator); + if(i == acceleratorMap.end()) + { + return false; + } + acceleratorMap.erase(i); + } + return true; +} + +Accelerator accelerator_for_event_key(guint keyval, guint state) +{ + keyval = gdk_keyval_to_upper(keyval); + if(keyval == GDK_ISO_Left_Tab) + keyval = GDK_Tab; + return Accelerator(keyval, (GdkModifierType)(state & gtk_accelerator_get_default_mod_mask())); +} + +bool AcceleratorMap_activate(const AcceleratorMap& acceleratorMap, const Accelerator& accelerator) +{ + AcceleratorMap::const_iterator i = acceleratorMap.find(accelerator); + if(i != acceleratorMap.end()) + { + (*i).second(); + return true; + } + + return false; +} + +static gboolean accelerator_key_event(GtkWindow* window, GdkEventKey* event, AcceleratorMap* acceleratorMap) +{ + return AcceleratorMap_activate(*acceleratorMap, accelerator_for_event_key(event->keyval, event->state)); +} + + +AcceleratorMap g_special_accelerators; + + +namespace MouseButton +{ + enum + { + Left = 1 << 0, + Right = 1 << 1, + Middle = 1 << 2, + }; +} + +typedef unsigned int ButtonMask; + +void print_buttons(ButtonMask mask) +{ + globalOutputStream() << "button state: "; + if((mask & MouseButton::Left) != 0) + { + globalOutputStream() << "Left "; + } + if((mask & MouseButton::Right) != 0) + { + globalOutputStream() << "Right "; + } + if((mask & MouseButton::Middle) != 0) + { + globalOutputStream() << "Middle "; + } + globalOutputStream() << "\n"; +} + +ButtonMask ButtonMask_for_event_button(guint button) +{ + switch(button) + { + case 1: + return MouseButton::Left; + case 2: + return MouseButton::Middle; + case 3: + return MouseButton::Right; + } + return 0; +} + +bool window_has_accel(GtkWindow* toplevel) +{ + return g_slist_length(gtk_accel_groups_from_object(G_OBJECT(toplevel))) != 0; +} + +namespace +{ + bool g_accel_enabled = true; +} + +bool global_accel_enabled() +{ + return g_accel_enabled; +} + + +GClosure* accel_group_add_accelerator(GtkAccelGroup* group, Accelerator accelerator, const Callback& callback); +void accel_group_remove_accelerator(GtkAccelGroup* group, Accelerator accelerator); + +AcceleratorMap g_queuedAcceleratorsAdd; +AcceleratorSet g_queuedAcceleratorsRemove; + +void globalQueuedAccelerators_add(Accelerator accelerator, const Callback& callback) +{ + if(!g_queuedAcceleratorsAdd.insert(AcceleratorMap::value_type(accelerator, callback)).second) + { + globalErrorStream() << "globalQueuedAccelerators_add: accelerator already queued: " << accelerator << "\n"; + } +} + +void globalQueuedAccelerators_remove(Accelerator accelerator) +{ + if(g_queuedAcceleratorsAdd.erase(accelerator) == 0) + { + if(!g_queuedAcceleratorsRemove.insert(accelerator).second) + { + globalErrorStream() << "globalQueuedAccelerators_remove: accelerator already queued: " << accelerator << "\n"; + } + } +} + +void globalQueuedAccelerators_commit() +{ + for(AcceleratorSet::const_iterator i = g_queuedAcceleratorsRemove.begin(); i != g_queuedAcceleratorsRemove.end(); ++i) + { + //globalOutputStream() << "removing: " << (*i).first << "\n"; + accel_group_remove_accelerator(global_accel, *i); + } + g_queuedAcceleratorsRemove.clear(); + for(AcceleratorMap::const_iterator i = g_queuedAcceleratorsAdd.begin(); i != g_queuedAcceleratorsAdd.end(); ++i) + { + //globalOutputStream() << "adding: " << (*i).first << "\n"; + accel_group_add_accelerator(global_accel, (*i).first, (*i).second); + } + g_queuedAcceleratorsAdd.clear(); +} + +void accel_group_test(GtkWindow* toplevel, GtkAccelGroup* accel) +{ + guint n_entries; + gtk_accel_group_query(accel, '4', (GdkModifierType)0, &n_entries); + globalOutputStream() << "grid4: " << n_entries << "\n"; + globalOutputStream() << "toplevel accelgroups: " << g_slist_length(gtk_accel_groups_from_object(G_OBJECT(toplevel))) << "\n"; +} + +typedef std::set WindowSet; +WindowSet g_accel_windows; + +bool Buttons_press(ButtonMask& buttons, guint button, guint state) +{ + if(buttons == 0 && bitfield_enable(buttons, ButtonMask_for_event_button(button)) != 0) + { + ASSERT_MESSAGE(g_accel_enabled, "Buttons_press: accelerators not enabled"); + g_accel_enabled = false; + for(WindowSet::iterator i = g_accel_windows.begin(); i != g_accel_windows.end(); ++i) + { + GtkWindow* toplevel = *i; + ASSERT_MESSAGE(window_has_accel(toplevel), "ERROR"); + ASSERT_MESSAGE(GTK_WIDGET_TOPLEVEL(toplevel), "disabling accel for non-toplevel window"); + gtk_window_remove_accel_group(toplevel, global_accel); +#if 0 + globalOutputStream() << reinterpret_cast(toplevel) << ": disabled global accelerators\n"; +#endif +#if 0 + accel_group_test(toplevel, global_accel); +#endif + } + } + buttons = bitfield_enable(buttons, ButtonMask_for_event_button(button)); +#if 0 + globalOutputStream() << "Buttons_press: "; + print_buttons(buttons); +#endif + return false; +} + +bool Buttons_release(ButtonMask& buttons, guint button, guint state) +{ + if(buttons != 0 && bitfield_disable(buttons, ButtonMask_for_event_button(button)) == 0) + { + ASSERT_MESSAGE(!g_accel_enabled, "Buttons_release: accelerators are enabled"); + g_accel_enabled = true; + for(WindowSet::iterator i = g_accel_windows.begin(); i != g_accel_windows.end(); ++i) + { + GtkWindow* toplevel = *i; + ASSERT_MESSAGE(!window_has_accel(toplevel), "ERROR"); + ASSERT_MESSAGE(GTK_WIDGET_TOPLEVEL(toplevel), "enabling accel for non-toplevel window"); + gtk_window_add_accel_group(toplevel, global_accel); +#if 0 + globalOutputStream() << reinterpret_cast(toplevel) << ": enabled global accelerators\n"; +#endif +#if 0 + accel_group_test(toplevel, global_accel); +#endif + } + globalQueuedAccelerators_commit(); + } + buttons = bitfield_disable(buttons, ButtonMask_for_event_button(button)); +#if 0 + globalOutputStream() << "Buttons_release: "; + print_buttons(buttons); +#endif + return false; +} + +bool Buttons_releaseAll(ButtonMask& buttons) +{ + Buttons_release(buttons, MouseButton::Left | MouseButton::Middle | MouseButton::Right, 0); + return false; +} + +struct PressedButtons +{ + ButtonMask buttons; + + PressedButtons() : buttons(0) + { + } +}; + +gboolean PressedButtons_button_press(GtkWidget* widget, GdkEventButton* event, PressedButtons* pressed) +{ + if(event->type == GDK_BUTTON_PRESS) + { + return Buttons_press(pressed->buttons, event->button, event->state); + } + return FALSE; +} + +gboolean PressedButtons_button_release(GtkWidget* widget, GdkEventButton* event, PressedButtons* pressed) +{ + if(event->type == GDK_BUTTON_RELEASE) + { + return Buttons_release(pressed->buttons, event->button, event->state); + } + return FALSE; +} + +gboolean PressedButtons_focus_out(GtkWidget* widget, GdkEventFocus* event, PressedButtons* pressed) +{ + Buttons_releaseAll(pressed->buttons); + return FALSE; +} + +void PressedButtons_connect(PressedButtons& pressedButtons, GtkWidget* widget) +{ + g_signal_connect(G_OBJECT(widget), "button_press_event", G_CALLBACK(PressedButtons_button_press), &pressedButtons); + g_signal_connect(G_OBJECT(widget), "button_release_event", G_CALLBACK(PressedButtons_button_release), &pressedButtons); + g_signal_connect(G_OBJECT(widget), "focus_out_event", G_CALLBACK(PressedButtons_focus_out), &pressedButtons); +} + +PressedButtons g_pressedButtons; + + +#include + +struct PressedKeys +{ + typedef std::set Keys; + Keys keys; + std::size_t refcount; + + PressedKeys() : refcount(0) + { + } +}; + +AcceleratorMap g_keydown_accelerators; +AcceleratorMap g_keyup_accelerators; + +bool Keys_press(PressedKeys::Keys& keys, guint keyval) +{ + if(keys.insert(keyval).second) + { + return AcceleratorMap_activate(g_keydown_accelerators, accelerator_for_event_key(keyval, 0)); + } + return g_keydown_accelerators.find(accelerator_for_event_key(keyval, 0)) != g_keydown_accelerators.end(); +} + +bool Keys_release(PressedKeys::Keys& keys, guint keyval) +{ + if(keys.erase(keyval) != 0) + { + return AcceleratorMap_activate(g_keyup_accelerators, accelerator_for_event_key(keyval, 0)); + } + return g_keyup_accelerators.find(accelerator_for_event_key(keyval, 0)) != g_keyup_accelerators.end(); +} + +void Keys_releaseAll(PressedKeys::Keys& keys, guint state) +{ + for(PressedKeys::Keys::iterator i = keys.begin(); i != keys.end(); ++i) + { + AcceleratorMap_activate(g_keyup_accelerators, accelerator_for_event_key(*i, state)); + } + keys.clear(); +} + +gboolean PressedKeys_key_press(GtkWidget* widget, GdkEventKey* event, PressedKeys* pressedKeys) +{ + //globalOutputStream() << "pressed: " << event->keyval << "\n"; + return event->state == 0 && Keys_press(pressedKeys->keys, event->keyval); +} + +gboolean PressedKeys_key_release(GtkWidget* widget, GdkEventKey* event, PressedKeys* pressedKeys) +{ + //globalOutputStream() << "released: " << event->keyval << "\n"; + return Keys_release(pressedKeys->keys, event->keyval); +} + +gboolean PressedKeys_focus_in(GtkWidget* widget, GdkEventFocus* event, PressedKeys* pressedKeys) +{ + ++pressedKeys->refcount; + return FALSE; +} + +gboolean PressedKeys_focus_out(GtkWidget* widget, GdkEventFocus* event, PressedKeys* pressedKeys) +{ + if(--pressedKeys->refcount == 0) + { + Keys_releaseAll(pressedKeys->keys, 0); + } + return FALSE; +} + +PressedKeys g_pressedKeys; + +void GlobalPressedKeys_releaseAll() +{ + Keys_releaseAll(g_pressedKeys.keys, 0); +} + +void GlobalPressedKeys_connect(GtkWindow* window) +{ + unsigned int key_press_handler = g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(PressedKeys_key_press), &g_pressedKeys); + unsigned int key_release_handler = g_signal_connect(G_OBJECT(window), "key_release_event", G_CALLBACK(PressedKeys_key_release), &g_pressedKeys); + g_object_set_data(G_OBJECT(window), "key_press_handler", gint_to_pointer(key_press_handler)); + g_object_set_data(G_OBJECT(window), "key_release_handler", gint_to_pointer(key_release_handler)); + unsigned int focus_in_handler = g_signal_connect(G_OBJECT(window), "focus_in_event", G_CALLBACK(PressedKeys_focus_in), &g_pressedKeys); + unsigned int focus_out_handler = g_signal_connect(G_OBJECT(window), "focus_out_event", G_CALLBACK(PressedKeys_focus_out), &g_pressedKeys); + g_object_set_data(G_OBJECT(window), "focus_in_handler", gint_to_pointer(focus_in_handler)); + g_object_set_data(G_OBJECT(window), "focus_out_handler", gint_to_pointer(focus_out_handler)); +} + +void GlobalPressedKeys_disconnect(GtkWindow* window) +{ + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_press_handler"))); + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_release_handler"))); + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "focus_in_handler"))); + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "focus_out_handler"))); +} + + + +void special_accelerators_add(Accelerator accelerator, const Callback& callback) +{ + //globalOutputStream() << "special_accelerators_add: " << makeQuoted(accelerator) << "\n"; + if(!accelerator_map_insert(g_special_accelerators, accelerator, callback)) + { + globalErrorStream() << "special_accelerators_add: already exists: " << makeQuoted(accelerator) << "\n"; + } +} +void special_accelerators_remove(Accelerator accelerator) +{ + //globalOutputStream() << "special_accelerators_remove: " << makeQuoted(accelerator) << "\n"; + if(!accelerator_map_erase(g_special_accelerators, accelerator)) + { + globalErrorStream() << "special_accelerators_remove: not found: " << makeQuoted(accelerator) << "\n"; + } +} + +void keydown_accelerators_add(Accelerator accelerator, const Callback& callback) +{ + //globalOutputStream() << "keydown_accelerators_add: " << makeQuoted(accelerator) << "\n"; + if(!accelerator_map_insert(g_keydown_accelerators, accelerator, callback)) + { + globalErrorStream() << "keydown_accelerators_add: already exists: " << makeQuoted(accelerator) << "\n"; + } +} +void keydown_accelerators_remove(Accelerator accelerator) +{ + //globalOutputStream() << "keydown_accelerators_remove: " << makeQuoted(accelerator) << "\n"; + if(!accelerator_map_erase(g_keydown_accelerators, accelerator)) + { + globalErrorStream() << "keydown_accelerators_remove: not found: " << makeQuoted(accelerator) << "\n"; + } +} + +void keyup_accelerators_add(Accelerator accelerator, const Callback& callback) +{ + //globalOutputStream() << "keyup_accelerators_add: " << makeQuoted(accelerator) << "\n"; + if(!accelerator_map_insert(g_keyup_accelerators, accelerator, callback)) + { + globalErrorStream() << "keyup_accelerators_add: already exists: " << makeQuoted(accelerator) << "\n"; + } +} +void keyup_accelerators_remove(Accelerator accelerator) +{ + //globalOutputStream() << "keyup_accelerators_remove: " << makeQuoted(accelerator) << "\n"; + if(!accelerator_map_erase(g_keyup_accelerators, accelerator)) + { + globalErrorStream() << "keyup_accelerators_remove: not found: " << makeQuoted(accelerator) << "\n"; + } +} + + +gboolean accel_closure_callback(GtkAccelGroup* group, GtkWidget* widget, guint key, GdkModifierType modifiers, gpointer data) +{ + (*reinterpret_cast(data))(); + return TRUE; +} + +GClosure* accel_group_add_accelerator(GtkAccelGroup* group, Accelerator accelerator, const Callback& callback) +{ + if(accelerator.key != 0 && gtk_accelerator_valid(accelerator.key, accelerator.modifiers)) + { + //globalOutputStream() << "global_accel_connect: " << makeQuoted(accelerator) << "\n"; + GClosure* closure = create_cclosure(G_CALLBACK(accel_closure_callback), callback); + gtk_accel_group_connect(group, accelerator.key, accelerator.modifiers, GTK_ACCEL_VISIBLE, closure); + return closure; + } + else + { + special_accelerators_add(accelerator, callback); + return 0; + } +} + +void accel_group_remove_accelerator(GtkAccelGroup* group, Accelerator accelerator) +{ + if(accelerator.key != 0 && gtk_accelerator_valid(accelerator.key, accelerator.modifiers)) + { + //globalOutputStream() << "global_accel_disconnect: " << makeQuoted(accelerator) << "\n"; + gtk_accel_group_disconnect_key(group, accelerator.key, accelerator.modifiers); + } + else + { + special_accelerators_remove(accelerator); + } +} + +GtkAccelGroup* global_accel = 0; + +void global_accel_init() +{ + global_accel = gtk_accel_group_new(); +} + +void global_accel_destroy() +{ + g_object_unref(global_accel); +} + +GClosure* global_accel_group_add_accelerator(Accelerator accelerator, const Callback& callback) +{ + if(!global_accel_enabled()) + { + // workaround: cannot add to GtkAccelGroup while it is disabled + //globalOutputStream() << "queued for add: " << accelerator << "\n"; + globalQueuedAccelerators_add(accelerator, callback); + return 0; + } + return accel_group_add_accelerator(global_accel, accelerator, callback); +} +void global_accel_group_remove_accelerator(Accelerator accelerator) +{ + if(!global_accel_enabled()) + { + //globalOutputStream() << "queued for remove: " << accelerator << "\n"; + globalQueuedAccelerators_remove(accelerator); + return; + } + accel_group_remove_accelerator(global_accel, accelerator); +} + +/// \brief Propagates key events to the focus-widget, overriding global accelerators. +static gboolean override_global_accelerators(GtkWindow* window, GdkEventKey* event, gpointer data) +{ + gboolean b = gtk_window_propagate_key_event(window, event); + return b; +} + +void global_accel_connect_window(GtkWindow* window) +{ +#if 1 + unsigned int override_handler = g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(override_global_accelerators), 0); + g_object_set_data(G_OBJECT(window), "override_handler", gint_to_pointer(override_handler)); + + unsigned int special_key_press_handler = g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(accelerator_key_event), &g_special_accelerators); + g_object_set_data(G_OBJECT(window), "special_key_press_handler", gint_to_pointer(special_key_press_handler)); + + GlobalPressedKeys_connect(window); +#else + unsigned int key_press_handler = g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(accelerator_key_event), &g_keydown_accelerators); + unsigned int key_release_handler = g_signal_connect(G_OBJECT(window), "key_release_event", G_CALLBACK(accelerator_key_event), &g_keyup_accelerators); + g_object_set_data(G_OBJECT(window), "key_press_handler", gint_to_pointer(key_press_handler)); + g_object_set_data(G_OBJECT(window), "key_release_handler", gint_to_pointer(key_release_handler)); +#endif + g_accel_windows.insert(window); + gtk_window_add_accel_group(window, global_accel); +} +void global_accel_disconnect_window(GtkWindow* window) +{ +#if 1 + GlobalPressedKeys_disconnect(window); + + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "override_handler"))); + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "special_key_press_handler"))); +#else + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_press_handler"))); + g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_release_handler"))); +#endif + gtk_window_remove_accel_group(window, global_accel); + std::size_t count = g_accel_windows.erase(window); + ASSERT_MESSAGE(count == 1, "failed to remove accel group\n"); +} + + +GClosure* global_accel_group_find(Accelerator accelerator) +{ + guint numEntries = 0; + GtkAccelGroupEntry* entry = gtk_accel_group_query(global_accel, accelerator.key, accelerator.modifiers, &numEntries); + if(numEntries != 0) + { + if(numEntries != 1) + { + char* name = gtk_accelerator_name(accelerator.key, accelerator.modifiers); + globalErrorStream() << "accelerator already in-use: " << name << "\n"; + g_free(name); + } + return entry->closure; + } + return 0; +} + +void global_accel_group_connect(const Accelerator& accelerator, const Callback& callback) +{ + if(accelerator.key != 0) + { + global_accel_group_add_accelerator(accelerator, callback); + } +} + +void global_accel_group_disconnect(const Accelerator& accelerator, const Callback& callback) +{ + if(accelerator.key != 0) + { + global_accel_group_remove_accelerator(accelerator); + } +} + + diff --git a/libs/gtkutil/accelerator.h b/libs/gtkutil/accelerator.h new file mode 100644 index 00000000..4f7085a7 --- /dev/null +++ b/libs/gtkutil/accelerator.h @@ -0,0 +1,128 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_ACCELERATOR_H) +#define INCLUDED_GTKUTIL_ACCELERATOR_H + +#include +#include + +#include "generic/callback.h" + +struct Accelerator +{ + Accelerator(guint _key) + : key(_key), modifiers((GdkModifierType)0) + { + } + Accelerator(guint _key, GdkModifierType _modifiers) + : key(_key), modifiers(_modifiers) + { + } + bool operator<(const Accelerator& other) const + { + return key < other.key || (!(other.key < key) && modifiers < other.modifiers); + } + guint key; + GdkModifierType modifiers; +}; + +inline Accelerator accelerator_null() +{ + return Accelerator(0, (GdkModifierType)0); +} + +const char* global_keys_find(unsigned int key); +unsigned int global_keys_find(const char* name); + +class TextOutputStream; +void accelerator_write(const Accelerator& accelerator, TextOutputStream& ostream); + +template +TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const Accelerator& accelerator) +{ + accelerator_write(accelerator, ostream); + return ostream; +} + +void keydown_accelerators_add(Accelerator accelerator, const Callback& callback); +void keydown_accelerators_remove(Accelerator accelerator); +void keyup_accelerators_add(Accelerator accelerator, const Callback& callback); +void keyup_accelerators_remove(Accelerator accelerator); + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkWindow GtkWindow; +void global_accel_connect_window(GtkWindow* window); +void global_accel_disconnect_window(GtkWindow* window); + +void GlobalPressedKeys_releaseAll(); + +typedef struct _GtkAccelGroup GtkAccelGroup; +extern GtkAccelGroup* global_accel; +void global_accel_init(); +void global_accel_destroy(); + +GClosure* global_accel_group_find(Accelerator accelerator); + +void global_accel_group_connect(const Accelerator& accelerator, const Callback& callback); +void global_accel_group_disconnect(const Accelerator& accelerator, const Callback& callback); + + +class Command +{ +public: + Callback m_callback; + const Accelerator& m_accelerator; + Command(const Callback& callback, const Accelerator& accelerator) : m_callback(callback), m_accelerator(accelerator) + { + } +}; + +class Toggle +{ +public: + Command m_command; + BoolExportCallback m_exportCallback; + Toggle(const Callback& callback, const Accelerator& accelerator, const BoolExportCallback& exportCallback) : m_command(callback, accelerator), m_exportCallback(exportCallback) + { + } +}; + +class KeyEvent +{ +public: + const Accelerator& m_accelerator; + Callback m_keyDown; + Callback m_keyUp; + KeyEvent(const Accelerator& accelerator, const Callback& keyDown, const Callback& keyUp) : m_accelerator(accelerator), m_keyDown(keyDown), m_keyUp(keyUp) + { + } +}; + + + +struct PressedButtons; +typedef struct _GtkWidget GtkWidget; +void PressedButtons_connect(PressedButtons& pressedButtons, GtkWidget* widget); + +extern PressedButtons g_pressedButtons; + +#endif diff --git a/libs/gtkutil/button.cpp b/libs/gtkutil/button.cpp new file mode 100644 index 00000000..0356ba39 --- /dev/null +++ b/libs/gtkutil/button.cpp @@ -0,0 +1,137 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "button.h" + +#include + +#include "stream/textstream.h" +#include "stream/stringstream.h" +#include "generic/callback.h" + +#include "image.h" +#include "pointer.h" + +void clicked_closure_callback(GtkWidget* widget, gpointer data) +{ + (*reinterpret_cast(data))(); +} + +void button_connect_callback(GtkButton* button, const Callback& callback) +{ +#if 1 + g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(callback.getThunk()), callback.getEnvironment()); +#else + g_signal_connect_closure(G_OBJECT(button), "clicked", create_cclosure(G_CALLBACK(clicked_closure_callback), callback), FALSE); +#endif +} + +guint toggle_button_connect_callback(GtkToggleButton* button, const Callback& callback) +{ +#if 1 + guint handler = g_signal_connect_swapped(G_OBJECT(button), "toggled", G_CALLBACK(callback.getThunk()), callback.getEnvironment()); +#else + guint handler = g_signal_connect_closure(G_OBJECT(button), "toggled", create_cclosure(G_CALLBACK(clicked_closure_callback), callback), TRUE); +#endif + g_object_set_data(G_OBJECT(button), "handler", gint_to_pointer(handler)); + return handler; +} + +void button_set_icon(GtkButton* button, const char* icon) +{ + GtkImage* image = new_local_image(icon); + gtk_widget_show(GTK_WIDGET(image)); + gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image)); +} + +void toggle_button_set_active_no_signal(GtkToggleButton* button, gboolean active) +{ + //globalOutputStream() << "set active: " << active << "\n"; + guint handler_id = gpointer_to_int(g_object_get_data(G_OBJECT(button), "handler")); + //guint signal_id = g_signal_lookup("toggled", G_OBJECT_TYPE (button)); + //globalOutputStream() << "signal_id: " << signal_id << "\n"; + //guint found = g_signal_handler_find(G_OBJECT(button), G_SIGNAL_MATCH_ID, signal_id, 0, 0, 0, 0); + //globalOutputStream() << " handler found: " << found << "\n"; + g_signal_handler_block(G_OBJECT(button), handler_id); + gtk_toggle_button_set_active(button, active); + g_signal_handler_unblock(G_OBJECT(button), handler_id); +} + + +void radio_button_print_state(GtkRadioButton* button) +{ + globalOutputStream() << "toggle button: "; + for(GSList* radio = gtk_radio_button_group(button); radio != 0; radio = g_slist_next(radio)) + { + globalOutputStream() << gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (radio->data)); + } + globalOutputStream() << "\n"; +} + +GtkToggleButton* radio_button_get_nth(GtkRadioButton* radio, int index) +{ + GSList *group = gtk_radio_button_group(radio); + return GTK_TOGGLE_BUTTON(g_slist_nth_data(group, g_slist_length(group) - index - 1)); +} + +void radio_button_set_active(GtkRadioButton* radio, int index) +{ + //radio_button_print_state(radio); + gtk_toggle_button_set_active(radio_button_get_nth(radio, index), TRUE); + //radio_button_print_state(radio); +} + +void radio_button_set_active_no_signal(GtkRadioButton* radio, int index) +{ + { + for(GSList* l = gtk_radio_button_get_group(radio); l != 0; l = g_slist_next(l)) + { + g_signal_handler_block(G_OBJECT(l->data), gpointer_to_int(g_object_get_data(G_OBJECT(l->data), "handler"))); + } + } + radio_button_set_active(radio, index); + { + for(GSList* l = gtk_radio_button_get_group(radio); l != 0; l = g_slist_next(l)) + { + g_signal_handler_unblock(G_OBJECT(l->data), gpointer_to_int(g_object_get_data(G_OBJECT(l->data), "handler"))); + } + } +} + +int radio_button_get_active(GtkRadioButton* radio) +{ + //radio_button_print_state(radio); + GSList *group = gtk_radio_button_group(radio); + int index = g_slist_length(group) - 1; + for (; group != 0; group = g_slist_next(group)) + { + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(group->data))) + { + break; + } + else + { + index--; + } + } + return index; +} + diff --git a/libs/gtkutil/button.h b/libs/gtkutil/button.h new file mode 100644 index 00000000..69ddac4b --- /dev/null +++ b/libs/gtkutil/button.h @@ -0,0 +1,44 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_BUTTON_H) +#define INCLUDED_GTKUTIL_BUTTON_H + +#include "generic/callbackfwd.h" + +typedef struct _GtkButton GtkButton; +typedef struct _GtkToggleButton GtkToggleButton; +typedef struct _GtkRadioButton GtkRadioButton; +typedef int gint; +typedef gint gboolean; +typedef unsigned int guint; + +void button_connect_callback(GtkButton* button, const Callback& callback); +guint toggle_button_connect_callback(GtkToggleButton* button, const Callback& callback); + +void button_set_icon(GtkButton* button, const char* icon); +void toggle_button_set_active_no_signal(GtkToggleButton* item, gboolean active); + +void radio_button_set_active(GtkRadioButton* radio, int index); +void radio_button_set_active_no_signal(GtkRadioButton* radio, int index); +int radio_button_get_active(GtkRadioButton* radio); + +#endif diff --git a/libs/gtkutil/clipboard.cpp b/libs/gtkutil/clipboard.cpp new file mode 100644 index 00000000..f7b9466a --- /dev/null +++ b/libs/gtkutil/clipboard.cpp @@ -0,0 +1,162 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "clipboard.h" + +#include "stream/memstream.h" +#include "stream/textstream.h" + + +/// \file +/// \brief Platform-independent GTK clipboard support. +/// \todo Using GDK_SELECTION_CLIPBOARD fails on win32, so we use the win32 API directly for now. +#if defined(WIN32) + +const char* c_clipboard_format = "RadiantClippings"; + +#include + +void clipboard_copy(ClipboardCopyFunc copy) +{ + BufferOutputStream ostream; + copy(ostream); + + bool bClipped = false; + UINT nClipboard = ::RegisterClipboardFormat(c_clipboard_format); + if (nClipboard > 0) + { + if (::OpenClipboard(0)) + { + EmptyClipboard(); + std::size_t length = ostream.size(); + HANDLE h = ::GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, length + sizeof(std::size_t)); + if (h != 0) + { + char *buffer = reinterpret_cast(::GlobalLock(h)); + *reinterpret_cast(buffer) = length; + buffer += sizeof(std::size_t); + memcpy(buffer, ostream.data(), length); + ::GlobalUnlock(h); + ::SetClipboardData(nClipboard, h); + ::CloseClipboard(); + bClipped = true; + } + } + } + + if (!bClipped) + { + globalOutputStream() << "Unable to register Windows clipboard formats, copy/paste between editors will not be possible\n"; + } +} + +void clipboard_paste(ClipboardPasteFunc paste) +{ + UINT nClipboard = ::RegisterClipboardFormat(c_clipboard_format); + if (nClipboard > 0 && ::OpenClipboard(0)) + { + if(IsClipboardFormatAvailable(nClipboard)) + { + HANDLE h = ::GetClipboardData(nClipboard); + if(h) + { + const char *buffer = reinterpret_cast(::GlobalLock(h)); + std::size_t length = *reinterpret_cast(buffer); + buffer += sizeof(std::size_t); + BufferInputStream istream(buffer, length); + paste(istream); + ::GlobalUnlock(h); + } + } + ::CloseClipboard(); + } +} + +#else + +#include + +enum +{ + RADIANT_CLIPPINGS = 23, +}; + +static const GtkTargetEntry clipboard_targets[] = { + { "RADIANT_CLIPPINGS", 0, RADIANT_CLIPPINGS, }, +}; + +static void clipboard_get (GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info, gpointer data) +{ + std::size_t len = *reinterpret_cast(data); + const char* buffer = (len != 0) ? reinterpret_cast(data) + sizeof(std::size_t) : 0; + + GdkAtom type = GDK_NONE; + if(info == clipboard_targets[0].info) + { + type = gdk_atom_intern(clipboard_targets[0].target, FALSE); + } + + gtk_selection_data_set (selection_data, type, 8, reinterpret_cast(buffer), static_cast(len)); +} + +static void clipboard_clear (GtkClipboard *clipboard, gpointer data) +{ + delete [] reinterpret_cast(data); +} + +static void clipboard_received (GtkClipboard *clipboard, GtkSelectionData *data, gpointer user_data) +{ + if (data->length < 0) + { + globalErrorStream() << "Error retrieving selection\n"; + } + else if(strcmp(gdk_atom_name(data->type), clipboard_targets[0].target) == 0) + { + BufferInputStream istream(reinterpret_cast(data->data), data->length); + (*reinterpret_cast(user_data))(istream); + } +} + +void clipboard_copy(ClipboardCopyFunc copy) +{ + GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + + BufferOutputStream ostream; + copy(ostream); + std::size_t length = ostream.size(); + char* data = new char[length + sizeof(std::size_t)]; + *reinterpret_cast(data) = length; + memcpy(data + sizeof(std::size_t), ostream.data(), length); + + gtk_clipboard_set_with_data (clipboard, clipboard_targets, 1, clipboard_get, clipboard_clear, data); +} + +ClipboardPasteFunc g_clipboardPasteFunc = 0; +void clipboard_paste(ClipboardPasteFunc paste) +{ + GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + + g_clipboardPasteFunc = paste; + gtk_clipboard_request_contents (clipboard, gdk_atom_intern(clipboard_targets[0].target, FALSE), clipboard_received, &g_clipboardPasteFunc); +} + + +#endif diff --git a/libs/gtkutil/clipboard.h b/libs/gtkutil/clipboard.h new file mode 100644 index 00000000..1589a66a --- /dev/null +++ b/libs/gtkutil/clipboard.h @@ -0,0 +1,33 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_CLIPBOARD_H) +#define INCLUDED_GTKUTIL_CLIPBOARD_H + +class TextOutputStream; +typedef void(*ClipboardCopyFunc)(TextOutputStream&); +void clipboard_copy(ClipboardCopyFunc copy); + +class TextInputStream; +typedef void(*ClipboardPasteFunc)(TextInputStream&); +void clipboard_paste(ClipboardPasteFunc paste); + +#endif diff --git a/libs/gtkutil/closure.cpp b/libs/gtkutil/closure.cpp new file mode 100644 index 00000000..4926d994 --- /dev/null +++ b/libs/gtkutil/closure.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "closure.h" + diff --git a/libs/gtkutil/closure.h b/libs/gtkutil/closure.h new file mode 100644 index 00000000..9a74b95e --- /dev/null +++ b/libs/gtkutil/closure.h @@ -0,0 +1,77 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_CLOSURE_H) +#define INCLUDED_GTKUTIL_CLOSURE_H + +#include +#include "generic/callback.h" + +inline void closure_destroy(gpointer data, GClosure* closure) +{ + delete reinterpret_cast(data); +} + +inline GClosure* create_cclosure(GCallback func, const Callback& callback) +{ + return g_cclosure_new(func, new Callback(callback), closure_destroy); +} + +inline GValue GValue_default() +{ + GValue value; + value.g_type = 0; + return value; +} + +inline gint object_get_int_property(GObject* object, const char* property) +{ + GValue gvalue = GValue_default(); + g_value_init(&gvalue, G_TYPE_INT); + g_object_get_property(object, property, &gvalue); + return g_value_get_int(&gvalue); +} + +inline void object_set_int_property(GObject* object, const char* property, gint value) +{ + GValue gvalue = GValue_default(); + g_value_init(&gvalue, G_TYPE_INT); + g_value_set_int(&gvalue, value); + g_object_set_property(object, property, &gvalue); +} + +inline gboolean object_get_boolean_property(GObject* object, const char* property) +{ + GValue gvalue = GValue_default(); + g_value_init(&gvalue, G_TYPE_BOOLEAN); + g_object_get_property(object, property, &gvalue); + return g_value_get_boolean(&gvalue); +} + +inline void object_set_boolean_property(GObject* object, const char* property, gboolean value) +{ + GValue gvalue = GValue_default(); + g_value_init(&gvalue, G_TYPE_BOOLEAN); + g_value_set_boolean(&gvalue, value); + g_object_set_property(object, property, &gvalue); +} + +#endif diff --git a/libs/gtkutil/container.cpp b/libs/gtkutil/container.cpp new file mode 100644 index 00000000..627e0c88 --- /dev/null +++ b/libs/gtkutil/container.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "container.h" + diff --git a/libs/gtkutil/container.h b/libs/gtkutil/container.h new file mode 100644 index 00000000..c41478ba --- /dev/null +++ b/libs/gtkutil/container.h @@ -0,0 +1,43 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_CONTAINER_H) +#define INCLUDED_GTKUTIL_CONTAINER_H + +#include + +inline GtkWidget* container_add_widget(GtkContainer* container, GtkWidget* widget) +{ + gtk_container_add(container, widget); + return widget; +} + +inline void container_remove(GtkWidget* item, gpointer data) +{ + gtk_container_remove(GTK_CONTAINER(data), item); +} + +inline void container_remove_all(GtkContainer* container) +{ + gtk_container_foreach(container, container_remove, container); +} + +#endif diff --git a/libs/gtkutil/cursor.cpp b/libs/gtkutil/cursor.cpp new file mode 100644 index 00000000..2c4225a2 --- /dev/null +++ b/libs/gtkutil/cursor.cpp @@ -0,0 +1,97 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "cursor.h" + +#include "stream/textstream.h" + +#include +#include +#include + + +GdkCursor* create_blank_cursor() +{ + GdkPixmap *pixmap; + GdkBitmap *mask; + char buffer [(32 * 32)/8]; + memset (buffer, 0, (32 * 32)/8); + GdkColor white = {0, 0xffff, 0xffff, 0xffff}; + GdkColor black = {0, 0x0000, 0x0000, 0x0000}; + pixmap = gdk_bitmap_create_from_data(0, buffer, 32, 32); + mask = gdk_bitmap_create_from_data(0, buffer, 32, 32); + GdkCursor *cursor = gdk_cursor_new_from_pixmap(pixmap, mask, &white, &black, 1, 1); + gdk_drawable_unref(pixmap); + gdk_drawable_unref(mask); + + return cursor; +} + +void blank_cursor(GtkWidget* widget) +{ + GdkCursor* cursor = create_blank_cursor(); + gdk_window_set_cursor (widget->window, cursor); + gdk_cursor_unref(cursor); +} + +void default_cursor(GtkWidget* widget) +{ + gdk_window_set_cursor(widget->window, 0); +} + + +#if defined(WIN32) + +#include + +void Sys_GetCursorPos(GtkWindow* window, int *x, int *y) +{ + POINT pos; + GetCursorPos(&pos); + ScreenToClient((HWND)GDK_WINDOW_HWND(GTK_WIDGET(window)->window), &pos); + *x = pos.x; + *y = pos.y; +} + +void Sys_SetCursorPos(GtkWindow* window, int x, int y) +{ + POINT pos; + pos.x = x; + pos.y = y; + ClientToScreen((HWND)GDK_WINDOW_HWND(GTK_WIDGET(window)->window), &pos); + SetCursorPos(pos.x, pos.y); +} + +#else + +#include + +void Sys_GetCursorPos(GtkWindow* window, int *x, int *y) +{ + gdk_display_get_pointer(gdk_display_get_default(), 0, x, y, 0); +} + +void Sys_SetCursorPos(GtkWindow* window, int x, int y) +{ + XWarpPointer(GDK_DISPLAY(), None, GDK_ROOT_WINDOW(), 0, 0, 0, 0, x, y); +} + +#endif diff --git a/libs/gtkutil/cursor.h b/libs/gtkutil/cursor.h new file mode 100644 index 00000000..eadae085 --- /dev/null +++ b/libs/gtkutil/cursor.h @@ -0,0 +1,193 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_CURSOR_H) +#define INCLUDED_GTKUTIL_CURSOR_H + +#include +#include +#include +#include + +#include "debugging/debugging.h" + +typedef struct _GdkCursor GdkCursor; +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkWindow GtkWindow; + +GdkCursor* create_blank_cursor(); +void blank_cursor(GtkWidget* widget); +void default_cursor(GtkWidget* widget); +void Sys_GetCursorPos(GtkWindow* window, int *x, int *y); +void Sys_SetCursorPos(GtkWindow* window, int x, int y); + + + +class DeferredMotion +{ + guint m_handler; + typedef void(*MotionFunction)(gdouble x, gdouble y, guint state, void* data); + MotionFunction m_function; + void* m_data; + gdouble m_x; + gdouble m_y; + guint m_state; + + static gboolean deferred(DeferredMotion* self) + { + self->m_handler = 0; + self->m_function(self->m_x, self->m_y, self->m_state, self->m_data); + return FALSE; + } +public: + DeferredMotion(MotionFunction function, void* data) : m_handler(0), m_function(function), m_data(data) + { + } + void motion(gdouble x, gdouble y, guint state) + { + m_x = x; + m_y = y; + m_state = state; + if(m_handler == 0) + { + m_handler = g_idle_add((GSourceFunc)deferred, this); + } + } + static gboolean gtk_motion(GtkWidget *widget, GdkEventMotion *event, DeferredMotion* self) + { + self->motion(event->x, event->y, event->state); + return FALSE; + } +}; + +class DeferredMotionDelta +{ + int m_delta_x; + int m_delta_y; + guint m_motion_handler; + typedef void (*MotionDeltaFunction)(int x, int y, void* data); + MotionDeltaFunction m_function; + void* m_data; + + static gboolean deferred_motion(gpointer data) + { + reinterpret_cast(data)->m_function( + reinterpret_cast(data)->m_delta_x, + reinterpret_cast(data)->m_delta_y, + reinterpret_cast(data)->m_data + ); + reinterpret_cast(data)->m_motion_handler = 0; + reinterpret_cast(data)->m_delta_x = 0; + reinterpret_cast(data)->m_delta_y = 0; + return FALSE; + } +public: + DeferredMotionDelta(MotionDeltaFunction function, void* data) : m_delta_x(0), m_delta_y(0), m_motion_handler(0), m_function(function), m_data(data) + { + } + void flush() + { + if(m_motion_handler != 0) + { + g_source_remove(m_motion_handler); + deferred_motion(this); + } + } + void motion_delta(int x, int y, unsigned int state) + { + m_delta_x += x; + m_delta_y += y; + if(m_motion_handler == 0) + { + m_motion_handler = g_idle_add(deferred_motion, this); + } + } +}; + +class FreezePointer +{ + unsigned int handle_motion; + int recorded_x, recorded_y; + typedef void (*MotionDeltaFunction)(int x, int y, unsigned int state, void* data); + MotionDeltaFunction m_function; + void* m_data; +public: + FreezePointer() : handle_motion(0), m_function(0), m_data(0) + { + } + static gboolean motion_delta(GtkWidget *widget, GdkEventMotion *event, FreezePointer* self) + { + int current_x, current_y; + Sys_GetCursorPos(GTK_WINDOW(widget), ¤t_x, ¤t_y); + int dx = current_x - self->recorded_x; + int dy = current_y - self->recorded_y; + if(dx != 0 || dy != 0) + { + //globalOutputStream() << "motion x: " << dx << ", y: " << dy << "\n"; + Sys_SetCursorPos(GTK_WINDOW(widget), self->recorded_x, self->recorded_y); + self->m_function(dx, dy, event->state, self->m_data); + } + return FALSE; + } + + void freeze_pointer(GtkWindow* window, MotionDeltaFunction function, void* data) + { + ASSERT_MESSAGE(m_function == 0, "can't freeze pointer"); + + const GdkEventMask mask = static_cast(GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK + | GDK_BUTTON_MOTION_MASK + | GDK_BUTTON1_MOTION_MASK + | GDK_BUTTON2_MOTION_MASK + | GDK_BUTTON3_MOTION_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_VISIBILITY_NOTIFY_MASK); + + GdkCursor* cursor = create_blank_cursor(); + //GdkGrabStatus status = + gdk_pointer_grab(GTK_WIDGET(window)->window, TRUE, mask, 0, cursor, GDK_CURRENT_TIME); + gdk_cursor_unref(cursor); + + Sys_GetCursorPos(window, &recorded_x, &recorded_y); + + Sys_SetCursorPos(window, recorded_x, recorded_y); + + m_function = function; + m_data = data; + + handle_motion = g_signal_connect(G_OBJECT(window), "motion_notify_event", G_CALLBACK(motion_delta), this); + } + + void unfreeze_pointer(GtkWindow* window) + { + g_signal_handler_disconnect(G_OBJECT(window), handle_motion); + + m_function = 0; + m_data = 0; + + Sys_SetCursorPos(window, recorded_x, recorded_y); + + gdk_pointer_ungrab(GDK_CURRENT_TIME); + } +}; + +#endif diff --git a/libs/gtkutil/dialog.cpp b/libs/gtkutil/dialog.cpp new file mode 100644 index 00000000..0f95e9ae --- /dev/null +++ b/libs/gtkutil/dialog.cpp @@ -0,0 +1,303 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "dialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "button.h" +#include "window.h" + +GtkVBox* create_dialog_vbox(int spacing, int border) +{ + GtkVBox* vbox = GTK_VBOX(gtk_vbox_new(FALSE, spacing)); + gtk_widget_show(GTK_WIDGET(vbox)); + gtk_container_set_border_width(GTK_CONTAINER(vbox), border); + return vbox; +} + +GtkHBox* create_dialog_hbox(int spacing, int border) +{ + GtkHBox* hbox = GTK_HBOX(gtk_hbox_new(FALSE, spacing)); + gtk_widget_show(GTK_WIDGET(hbox)); + gtk_container_set_border_width(GTK_CONTAINER(hbox), border); + return hbox; +} + +GtkFrame* create_dialog_frame(const char* label, GtkShadowType shadow) +{ + GtkFrame* frame = GTK_FRAME(gtk_frame_new(label)); + gtk_widget_show(GTK_WIDGET(frame)); + gtk_frame_set_shadow_type(frame, shadow); + return frame; +} + +GtkTable* create_dialog_table(unsigned int rows, unsigned int columns, unsigned int row_spacing, unsigned int col_spacing, int border) +{ + GtkTable* table = GTK_TABLE(gtk_table_new(rows, columns, FALSE)); + gtk_widget_show(GTK_WIDGET(table)); + gtk_table_set_row_spacings(table, row_spacing); + gtk_table_set_col_spacings(table, col_spacing); + gtk_container_set_border_width(GTK_CONTAINER(table), border); + return table; +} + +GtkButton* create_dialog_button(const char* label, GCallback func, gpointer data) +{ + GtkButton* button = GTK_BUTTON(gtk_button_new_with_label(label)); + gtk_widget_set_size_request(GTK_WIDGET(button), 64, -1); + gtk_widget_show(GTK_WIDGET(button)); + g_signal_connect(G_OBJECT(button), "clicked", func, data); + return button; +} + +GtkWindow* create_dialog_window(GtkWindow* parent, const char* title, GCallback func, gpointer data, int default_w, int default_h) +{ + GtkWindow* window = create_floating_window(title, parent); + gtk_window_set_default_size(window, default_w, default_h); + gtk_window_set_position(window, GTK_WIN_POS_CENTER_ON_PARENT); + g_signal_connect(G_OBJECT(window), "delete_event", func, data); + + return window; +} + +gboolean modal_dialog_button_clicked(GtkWidget *widget, ModalDialogButton* button) +{ + button->m_dialog.loop = false; + button->m_dialog.ret = button->m_value; + return TRUE; +} + +gboolean modal_dialog_delete(GtkWidget *widget, GdkEvent* event, ModalDialog* dialog) +{ + dialog->loop = 0; + dialog->ret = eIDCANCEL; + return TRUE; +} + +EMessageBoxReturn modal_dialog_show(GtkWindow* window, ModalDialog& dialog) +{ + gtk_grab_add(GTK_WIDGET(window)); + gtk_widget_show(GTK_WIDGET(window)); + + dialog.loop = true; + while(dialog.loop) + { + gtk_main_iteration(); + } + + gtk_widget_hide(GTK_WIDGET(window)); + gtk_grab_remove(GTK_WIDGET(window)); + + return dialog.ret; +} + +GtkButton* create_modal_dialog_button(const char* label, ModalDialogButton& button) +{ + return create_dialog_button(label, G_CALLBACK(modal_dialog_button_clicked), &button); +} + +GtkWindow* create_modal_dialog_window(GtkWindow* parent, const char* title, ModalDialog& dialog, int default_w, int default_h) +{ + return create_dialog_window(parent, title, G_CALLBACK(modal_dialog_delete), &dialog, default_w, default_h); +} + +GtkWindow* create_fixedsize_modal_dialog_window(GtkWindow* parent, const char* title, ModalDialog& dialog, int width, int height) +{ + GtkWindow* window = create_modal_dialog_window(parent, title, dialog, width, height); + + gtk_window_set_resizable(window, FALSE); + gtk_window_set_modal(window, TRUE); + gtk_window_set_position(window, GTK_WIN_POS_CENTER); + + window_remove_minmax(window); + + //gtk_widget_set_size_request(GTK_WIDGET(window), width, height); + //gtk_window_set_default_size(window, width, height); + //gtk_window_resize(window, width, height); + //GdkGeometry geometry = { width, height, -1, -1, width, height, -1, -1, -1, -1, GDK_GRAVITY_STATIC, }; + //gtk_window_set_geometry_hints(window, GTK_WIDGET(window), &geometry, (GdkWindowHints)(GDK_HINT_POS|GDK_HINT_MIN_SIZE|GDK_HINT_BASE_SIZE)); + + return window; +} + +gboolean dialog_button_ok(GtkWidget *widget, ModalDialog* data) +{ + data->loop = false; + data->ret = eIDOK; + return TRUE; +} + +gboolean dialog_button_cancel(GtkWidget *widget, ModalDialog* data) +{ + data->loop = false; + data->ret = eIDCANCEL; + return TRUE; +} + +gboolean dialog_button_yes(GtkWidget *widget, ModalDialog* data) +{ + data->loop = false; + data->ret = eIDYES; + return TRUE; +} + +gboolean dialog_button_no(GtkWidget *widget, ModalDialog* data) +{ + data->loop = false; + data->ret = eIDNO; + return TRUE; +} + +gboolean dialog_delete_callback(GtkWidget *widget, GdkEventAny* event, ModalDialog* data) +{ + gtk_widget_hide(widget); + data->loop = false; + return TRUE; +} + +GtkWindow* create_simple_modal_dialog_window(const char* title, ModalDialog& dialog, GtkWidget* contents) +{ + GtkWindow* window = create_fixedsize_modal_dialog_window(0, title, dialog); + + GtkVBox* vbox1 = create_dialog_vbox(8, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox1)); + + gtk_container_add(GTK_CONTAINER(vbox1), contents); + + GtkAlignment* alignment = GTK_ALIGNMENT(gtk_alignment_new(0.5, 0.0, 0.0, 0.0)); + gtk_widget_show(GTK_WIDGET(alignment)); + gtk_box_pack_start(GTK_BOX(vbox1), GTK_WIDGET(alignment), FALSE, FALSE, 0); + + GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog); + gtk_container_add(GTK_CONTAINER(alignment), GTK_WIDGET(button)); + + return window; +} + +RadioHBox RadioHBox_new(StringArrayRange names) +{ + GtkHBox* hbox = GTK_HBOX(gtk_hbox_new(TRUE, 4)); + gtk_widget_show(GTK_WIDGET(hbox)); + + GSList* group = 0; + GtkRadioButton* radio = 0; + for(StringArrayRange::Iterator i = names.first; i != names.last; ++i) + { + radio = GTK_RADIO_BUTTON(gtk_radio_button_new_with_label(group, *i)); + gtk_widget_show(GTK_WIDGET(radio)); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(radio), FALSE, FALSE, 0); + + group = gtk_radio_button_get_group(radio); + } + + return RadioHBox(hbox, radio); +} + + +PathEntry PathEntry_new() +{ + GtkFrame* frame = GTK_FRAME(gtk_frame_new(NULL)); + gtk_widget_show(GTK_WIDGET(frame)); + gtk_frame_set_shadow_type(frame, GTK_SHADOW_IN); + + // path entry + GtkHBox* hbox = GTK_HBOX(gtk_hbox_new(FALSE, 0)); + gtk_widget_show(GTK_WIDGET(hbox)); + + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_entry_set_has_frame(entry, FALSE); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(entry), TRUE, TRUE, 0); + + // browse button + GtkButton* button = GTK_BUTTON(gtk_button_new()); + button_set_icon(button, "ellipsis.bmp"); + gtk_widget_show(GTK_WIDGET(button)); + gtk_box_pack_end(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0); + + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(hbox)); + + return PathEntry(frame, entry, button); +} + +void PathEntry_setPath(PathEntry& self, const char* path) +{ + gtk_entry_set_text(self.m_entry, path); +} +typedef ReferenceCaller1 PathEntrySetPathCaller; + +void BrowsedPathEntry_clicked(GtkWidget* widget, BrowsedPathEntry* self) +{ + self->m_browse(PathEntrySetPathCaller(self->m_entry)); +} + +BrowsedPathEntry::BrowsedPathEntry(const BrowseCallback& browse) : + m_entry(PathEntry_new()), + m_browse(browse) +{ + g_signal_connect(G_OBJECT(m_entry.m_button), "clicked", G_CALLBACK(BrowsedPathEntry_clicked), this); +} + + +GtkLabel* DialogLabel_new(const char* name) +{ + GtkLabel* label = GTK_LABEL(gtk_label_new(name)); + gtk_widget_show(GTK_WIDGET(label)); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + gtk_label_set_justify(label, GTK_JUSTIFY_LEFT); + + return label; +} + +GtkTable* DialogRow_new(const char* name, GtkWidget* widget) +{ + GtkTable* table = GTK_TABLE(gtk_table_new(1, 3, TRUE)); + gtk_widget_show(GTK_WIDGET(table)); + + gtk_table_set_col_spacings(table, 4); + gtk_table_set_row_spacings(table, 0); + + gtk_table_attach(table, GTK_WIDGET(DialogLabel_new(name)), 0, 1, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + gtk_table_attach(table, widget, 1, 3, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + return table; +} + +void DialogVBox_packRow(GtkVBox* vbox, GtkWidget* row) +{ + gtk_box_pack_start(GTK_BOX(vbox), row, FALSE, FALSE, 0); +} + diff --git a/libs/gtkutil/dialog.h b/libs/gtkutil/dialog.h new file mode 100644 index 00000000..150404a2 --- /dev/null +++ b/libs/gtkutil/dialog.h @@ -0,0 +1,145 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_DIALOG_H) +#define INCLUDED_GTKUTIL_DIALOG_H + +#include "generic/callback.h" +#include "generic/arrayrange.h" +#include "qerplugin.h" +#include + +typedef int gint; +typedef gint gboolean; +typedef struct _GdkEventAny GdkEventAny; +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkHBox GtkHBox; +typedef struct _GtkVBox GtkVBox; +typedef struct _GtkRadioButton GtkRadioButton; +typedef struct _GtkFrame GtkFrame; +typedef struct _GtkEntry GtkEntry; +typedef struct _GtkButton GtkButton; +typedef struct _GtkLabel GtkLabel; +typedef struct _GtkTable GtkTable; + + +struct ModalDialog +{ + ModalDialog() + : loop(true), ret(eIDCANCEL) + { + } + bool loop; + EMessageBoxReturn ret; +}; + +struct ModalDialogButton +{ + ModalDialogButton(ModalDialog& dialog, EMessageBoxReturn value) + : m_dialog(dialog), m_value(value) + { + } + ModalDialog& m_dialog; + EMessageBoxReturn m_value; +}; + +typedef void (*GCallback)(void); +typedef void* gpointer; +typedef struct _GtkWindow GtkWindow; +typedef struct _GtkTable GtkTable; +typedef struct _GtkButton GtkButton; +typedef struct _GtkVBox GtkVBox; +typedef struct _GtkHBox GtkHBox; +typedef struct _GtkFrame GtkFrame; + +GtkWindow* create_fixedsize_modal_window(GtkWindow* parent, const char* title, int width, int height); + +GtkWindow* create_dialog_window(GtkWindow* parent, const char* title, GCallback func, gpointer data, int default_w = -1, int default_h = -1); +GtkTable* create_dialog_table(unsigned int rows, unsigned int columns, unsigned int row_spacing, unsigned int col_spacing, int border = 0); +GtkButton* create_dialog_button(const char* label, GCallback func, gpointer data); +GtkVBox* create_dialog_vbox(int spacing, int border = 0); +GtkHBox* create_dialog_hbox(int spacing, int border = 0); +GtkFrame* create_dialog_frame(const char* label, GtkShadowType shadow = GTK_SHADOW_ETCHED_IN); + +GtkButton* create_modal_dialog_button(const char* label, ModalDialogButton& button); +GtkWindow* create_modal_dialog_window(GtkWindow* parent, const char* title, ModalDialog& dialog, int default_w = -1, int default_h = -1); +GtkWindow* create_fixedsize_modal_dialog_window(GtkWindow* parent, const char* title, ModalDialog& dialog, int width = -1, int height = -1); +EMessageBoxReturn modal_dialog_show(GtkWindow* window, ModalDialog& dialog); + + +gboolean dialog_button_ok(GtkWidget *widget, ModalDialog* data); +gboolean dialog_button_cancel(GtkWidget *widget, ModalDialog* data); +gboolean dialog_button_yes(GtkWidget *widget, ModalDialog* data); +gboolean dialog_button_no(GtkWidget *widget, ModalDialog* data); +gboolean dialog_delete_callback(GtkWidget *widget, GdkEventAny* event, ModalDialog* data); + +GtkWindow* create_simple_modal_dialog_window(const char* title, ModalDialog& dialog, GtkWidget* contents); + +class RadioHBox +{ +public: + GtkHBox* m_hbox; + GtkRadioButton* m_radio; + RadioHBox(GtkHBox* hbox, GtkRadioButton* radio) : + m_hbox(hbox), + m_radio(radio) + { + } +}; + +RadioHBox RadioHBox_new(StringArrayRange names); + + +class PathEntry +{ +public: + GtkFrame* m_frame; + GtkEntry* m_entry; + GtkButton* m_button; + PathEntry(GtkFrame* frame, GtkEntry* entry, GtkButton* button) : + m_frame(frame), + m_entry(entry), + m_button(button) + { + } +}; + +PathEntry PathEntry_new(); + +class BrowsedPathEntry +{ +public: + typedef Callback1 SetPathCallback; + typedef Callback1 BrowseCallback; + + PathEntry m_entry; + BrowseCallback m_browse; + + BrowsedPathEntry(const BrowseCallback& browse); +}; + +GtkLabel* DialogLabel_new(const char* name); +GtkTable* DialogRow_new(const char* name, GtkWidget* widget); +typedef struct _GtkVBox GtkVBox; +void DialogVBox_packRow(GtkVBox* vbox, GtkWidget* row); + + +#endif diff --git a/libs/gtkutil/entry.cpp b/libs/gtkutil/entry.cpp new file mode 100644 index 00000000..9c3c1553 --- /dev/null +++ b/libs/gtkutil/entry.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "entry.h" + diff --git a/libs/gtkutil/entry.h b/libs/gtkutil/entry.h new file mode 100644 index 00000000..82957d29 --- /dev/null +++ b/libs/gtkutil/entry.h @@ -0,0 +1,63 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_ENTRY_H) +#define INCLUDED_GTKUTIL_ENTRY_H + +#include +#include +#include + +inline void entry_set_string(GtkEntry* entry, const char* string) +{ + gtk_entry_set_text(entry, string); +} + +inline void entry_set_int(GtkEntry* entry, int i) +{ + char buf[32]; + sprintf(buf, "%d", i); + entry_set_string(entry, buf); +} + +inline void entry_set_float(GtkEntry* entry, float f) +{ + char buf[32]; + sprintf(buf, "%g", f); + entry_set_string(entry, buf); +} + +inline const char* entry_get_string(GtkEntry* entry) +{ + return gtk_entry_get_text(entry); +} + +inline int entry_get_int(GtkEntry* entry) +{ + return atoi(entry_get_string(entry)); +} + +inline double entry_get_float(GtkEntry* entry) +{ + return atof(entry_get_string(entry)); +} + +#endif diff --git a/libs/gtkutil/filechooser.cpp b/libs/gtkutil/filechooser.cpp new file mode 100644 index 00000000..f164d220 --- /dev/null +++ b/libs/gtkutil/filechooser.cpp @@ -0,0 +1,305 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "filechooser.h" + +#include "ifiletypes.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "string/string.h" +#include "stream/stringstream.h" +#include "container/array.h" +#include "os/path.h" +#include "os/file.h" + +#include "messagebox.h" + + +struct filetype_pair_t +{ + filetype_pair_t() + : m_moduleName("") + { + } + filetype_pair_t(const char* moduleName, filetype_t type) + : m_moduleName(moduleName), m_type(type) + { + } + const char* m_moduleName; + filetype_t m_type; +}; + +class FileTypeList : public IFileTypeList +{ + struct filetype_copy_t + { + filetype_copy_t(const filetype_pair_t& other) + : m_moduleName(other.m_moduleName), m_name(other.m_type.name), m_pattern(other.m_type.pattern) + { + } + CopiedString m_moduleName; + CopiedString m_name; + CopiedString m_pattern; + }; + + typedef std::list Types; + Types m_types; +public: + + typedef Types::const_iterator const_iterator; + const_iterator begin() const + { + return m_types.begin(); + } + const_iterator end() const + { + return m_types.end(); + } + + std::size_t size() const + { + return m_types.size(); + } + + void addType(const char* moduleName, filetype_t type) + { + m_types.push_back(filetype_pair_t(moduleName, type)); + } +}; + + +class GTKMasks +{ + const FileTypeList& m_types; +public: + std::vector m_filters; + std::vector m_masks; + + GTKMasks(const FileTypeList& types) : m_types(types) + { + m_masks.reserve(m_types.size()); + for(FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i) + { + std::size_t len = strlen((*i).m_name.c_str()) + strlen((*i).m_pattern.c_str()) + 3; + StringOutputStream buffer(len + 1); // length + null char + + buffer << (*i).m_name.c_str() << " <" << (*i).m_pattern.c_str() << ">"; + + m_masks.push_back(buffer.c_str()); + } + + m_filters.reserve(m_types.size()); + for(FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i) + { + m_filters.push_back((*i).m_pattern); + } + } + + filetype_pair_t GetTypeForGTKMask(const char *mask) const + { + std::vector::const_iterator j = m_masks.begin(); + for(FileTypeList::const_iterator i = m_types.begin(); i != m_types.end(); ++i, ++j) + { + if(string_equal((*j).c_str(), mask)) + { + return filetype_pair_t((*i).m_moduleName.c_str(), filetype_t((*i).m_name.c_str(), (*i).m_pattern.c_str())); + } + } + return filetype_pair_t(); + } + +}; + +static char g_file_dialog_file[1024]; + +const char* file_dialog_show(GtkWidget* parent, bool open, const char* title, const char* path, const char* pattern) +{ + filetype_t type; + + if(pattern == 0) + { + pattern = "*"; + } + + FileTypeList typelist; + GlobalFiletypes().getTypeList(pattern, &typelist); + + GTKMasks masks(typelist); + + if (title == 0) + title = open ? "Open File" : "Save File"; + + GtkWidget* dialog; + if (open) + { + dialog = gtk_file_chooser_dialog_new(title, + GTK_WINDOW(parent), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + } + else + { + dialog = gtk_file_chooser_dialog_new(title, + GTK_WINDOW(parent), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "unnamed"); + } + + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT); + + // we expect an actual path below, if the path is 0 we might crash + if (path != 0 && !string_empty(path)) + { + ASSERT_MESSAGE(path_is_absolute(path), "file_dialog_show: path not absolute: " << makeQuoted(path)); + + Array new_path(strlen(path)+1); + + // copy path, replacing dir separators as appropriate + Array::iterator w = new_path.begin(); + for(const char* r = path; *r != '\0'; ++r) + { + *w++ = (*r == '/') ? G_DIR_SEPARATOR : *r; + } + // remove separator from end of path if required + if(*(w-1) == G_DIR_SEPARATOR) + { + --w; + } + // terminate string + *w = '\0'; + + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), new_path.data()); + } + + // we should add all important paths as shortcut folder... + // gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog), "/tmp/", NULL); + + + for(std::size_t i = 0; i < masks.m_filters.size(); ++i) + { + GtkFileFilter* filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, masks.m_filters[i].c_str()); + gtk_file_filter_set_name(filter, masks.m_masks[i].c_str()); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); + } + + if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) + { + strcpy(g_file_dialog_file, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog))); + + if(!string_equal(pattern, "*")) + { + GtkFileFilter* filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog)); + if(filter != 0) // no filter set? some file-chooser implementations may allow the user to set no filter, which we treat as 'all files' + { + type = masks.GetTypeForGTKMask(gtk_file_filter_get_name(filter)).m_type; + // last ext separator + const char* extension = path_get_extension(g_file_dialog_file); + // no extension + if(string_empty(extension)) + { + strcat(g_file_dialog_file, type.pattern+1); + } + else + { + strcpy(g_file_dialog_file + (extension - g_file_dialog_file), type.pattern+2); + } + } + } + + // convert back to unix format + for(char* w = g_file_dialog_file; *w!='\0'; w++) + { + if(*w=='\\') + { + *w = '/'; + } + } + } + else + { + g_file_dialog_file[0] = '\0'; + } + + gtk_widget_destroy(dialog); + + // don't return an empty filename + if(g_file_dialog_file[0] == '\0') return NULL; + + return g_file_dialog_file; +} + +char* dir_dialog(GtkWidget* parent, const char* title, const char* path) +{ + GtkWidget* dialog = gtk_file_chooser_dialog_new(title, + GTK_WINDOW(parent), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT); + + if(!string_empty(path)) + { + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), path); + } + + char* filename = 0; + if(gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) + { + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + } + + gtk_widget_destroy(dialog); + + return filename; +} + +const char* file_dialog(GtkWidget* parent, bool open, const char* title, const char* path, const char* pattern) +{ + for(;;) + { + const char* file = file_dialog_show(parent, open, title, path, pattern); + + if(open + || file == 0 + || !file_exists(file) + || gtk_MessageBox(parent, "The file specified already exists.\nDo you want to replace it?", title, eMB_NOYES, eMB_ICONQUESTION) == eIDYES) + { + return file; + } + } +} diff --git a/libs/gtkutil/filechooser.h b/libs/gtkutil/filechooser.h new file mode 100644 index 00000000..d23beb5d --- /dev/null +++ b/libs/gtkutil/filechooser.h @@ -0,0 +1,38 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_FILECHOOSER_H) +#define INCLUDED_GTKUTIL_FILECHOOSER_H + +/// \file +/// GTK+ file-chooser dialogs. + +typedef struct _GtkWidget GtkWidget; +const char* file_dialog(GtkWidget *parent, bool open, const char* title, const char* path = 0, const char* pattern = 0); + + +/// \brief Prompts the user to browse for a directory. +/// The prompt window will be transient to \p parent. +/// The directory will initially default to \p path, which must be an absolute path. +/// The returned string is allocated with \c g_malloc and must be freed with \c g_free. +char* dir_dialog(GtkWidget *parent, const char* title = "Choose Directory", const char* path = ""); + +#endif diff --git a/libs/gtkutil/frame.cpp b/libs/gtkutil/frame.cpp new file mode 100644 index 00000000..026de650 --- /dev/null +++ b/libs/gtkutil/frame.cpp @@ -0,0 +1,35 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "frame.h" + +#include + +GtkFrame* create_framed_widget(GtkWidget* widget) +{ + GtkFrame* frame = GTK_FRAME(gtk_frame_new(0)); + gtk_widget_show(GTK_WIDGET(frame)); + gtk_frame_set_shadow_type(frame, GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER(frame), widget); + gtk_widget_show(GTK_WIDGET(widget)); + return frame; +} + diff --git a/libs/gtkutil/frame.h b/libs/gtkutil/frame.h new file mode 100644 index 00000000..12305db2 --- /dev/null +++ b/libs/gtkutil/frame.h @@ -0,0 +1,29 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_FRAME_H) +#define INCLUDED_GTKUTIL_FRAME_H + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkFrame GtkFrame; +GtkFrame* create_framed_widget(GtkWidget* widget); + +#endif diff --git a/libs/gtkutil/glfont.cpp b/libs/gtkutil/glfont.cpp new file mode 100644 index 00000000..e46236d3 --- /dev/null +++ b/libs/gtkutil/glfont.cpp @@ -0,0 +1,141 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "glfont.h" +#ifdef _WIN32 + #include +#endif +#include +#include "debugging/debugging.h" + +// LordHavoc: this is code for direct Xlib bitmap character fetching, as an +// alternative to requiring gtkglarea, it was created due to a lack of this +// package on SuSE 9.x but this package is now commonly shipping in Linux +// distributions so this code may be unnecessary, feel free however to enable +// it when building packages for distros that do not ship with that package, +// or if you just prefer less dependencies... +#if 0 + +#include +#include +#include + +GLFont glfont_create(const char* font_string) +{ + GLuint font_list_base; + XFontStruct *fontInfo; + Display *dpy = GDK_DISPLAY (); + unsigned int i, first, last, firstrow, lastrow; + int maxchars; + int firstbitmap; + + fontInfo = XLoadQueryFont (dpy, "-*-fixed-*-*-*-*-8-*-*-*-*-*-*-*"); + if (fontInfo == NULL) + { + // try to load other fonts + fontInfo = XLoadQueryFont (dpy, "-*-fixed-*-*-*-*-*-*-*-*-*-*-*-*"); + + // any font will do ! + if (fontInfo == NULL) + fontInfo = XLoadQueryFont(dpy, "-*-*-*-*-*-*-*-*-*-*-*-*-*-*"); + + if (fontInfo == NULL) + ERROR_MESSAGE("couldn't create font"); + } + + first = (int)fontInfo->min_char_or_byte2; + last = (int)fontInfo->max_char_or_byte2; + firstrow = (int)fontInfo->min_byte1; + lastrow = (int)fontInfo->max_byte1; + /* + * How many chars in the charset + */ + maxchars = 256 * lastrow + last; + font_list_base = glGenLists(maxchars+1); + if (font_list_base == 0) + { + ERROR_MESSAGE( "couldn't create font" ); + } + + /* + * Get offset to first char in the charset + */ + firstbitmap = 256 * firstrow + first; + /* + * for each row of chars, call glXUseXFont to build the bitmaps. + */ + + for(i=firstrow; i<=lastrow; i++) + { + glXUseXFont(fontInfo->fid, firstbitmap, last-first+1, font_list_base+firstbitmap); + firstbitmap += 256; + } + +/* *height = fontInfo->ascent + fontInfo->descent; + *width = fontInfo->max_bounds.width; */ + return GLFont(font_list_base, fontInfo->ascent + fontInfo->descent); +} + +void glfont_release(GLFont& font) +{ + glDeleteLists(font.getDisplayList(), 256); + font = GLFont(0, 0); +} + +#else + +#include + +GLFont glfont_create(const char* font_string) +{ + GLuint font_list_base = glGenLists (256); + gint font_height = 0; + + PangoFontDescription* font_desc = pango_font_description_from_string (font_string); + + PangoFont* font = gdk_gl_font_use_pango_font (font_desc, 0, 256, font_list_base); + + if(font != 0) + { + PangoFontMetrics* font_metrics = pango_font_get_metrics (font, 0); + + font_height = pango_font_metrics_get_ascent (font_metrics) + + pango_font_metrics_get_descent (font_metrics); + font_height = PANGO_PIXELS (font_height); + + pango_font_metrics_unref (font_metrics); + } + + pango_font_description_free (font_desc); + + // fix for pango/gtkglext metrix bug + if(font_height > 16) + font_height = 16; + + return GLFont(font_list_base, font_height); +} + +void glfont_release(GLFont& font) +{ + glDeleteLists(font.getDisplayList(), 256); + font = GLFont(0, 0); +} +#endif diff --git a/libs/gtkutil/glfont.h b/libs/gtkutil/glfont.h new file mode 100644 index 00000000..8c18afad --- /dev/null +++ b/libs/gtkutil/glfont.h @@ -0,0 +1,48 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_GLFONT_H) +#define INCLUDED_GTKUTIL_GLFONT_H + +typedef unsigned int GLuint; + +class GLFont +{ + GLuint m_displayList; + int m_pixelHeight; +public: + GLFont(GLuint displayList, int pixelHeight) : m_displayList(displayList), m_pixelHeight(pixelHeight) + { + } + GLuint getDisplayList() const + { + return m_displayList; + } + int getPixelHeight() const + { + return m_pixelHeight; + } +}; + +GLFont glfont_create(const char* font_string); +void glfont_release(GLFont& font); + +#endif diff --git a/libs/gtkutil/glwidget.cpp b/libs/gtkutil/glwidget.cpp new file mode 100644 index 00000000..cda5366a --- /dev/null +++ b/libs/gtkutil/glwidget.cpp @@ -0,0 +1,274 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// OpenGL widget based on GtkGLExt + +#include "glwidget.h" + +#include "debugging/debugging.h" + +#include "igl.h" + +#include +#include + +#include "pointer.h" + +void (*GLWidget_sharedContextCreated)() = 0; +void (*GLWidget_sharedContextDestroyed)() = 0; + + +typedef int* attribs_t; +struct config_t +{ + const char* name; + attribs_t attribs; +}; +typedef const config_t* configs_iterator; + +int config_rgba32[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 24, + GDK_GL_ATTRIB_LIST_NONE, +}; + +int config_rgba[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 16, + GDK_GL_ATTRIB_LIST_NONE, +}; + +const config_t configs[] = { + { + "colour-buffer = 32bpp, depth-buffer = none", + config_rgba32, + }, + { + "colour-buffer = 16bpp, depth-buffer = none", + config_rgba, + } +}; + +GdkGLConfig* glconfig_new() +{ + GdkGLConfig* glconfig = 0; + + for(configs_iterator i = configs, end = configs + 2; i != end; ++i) + { + glconfig = gdk_gl_config_new((*i).attribs); + if(glconfig != 0) + { + globalOutputStream() << "OpenGL window configuration: " << (*i).name << "\n"; + return glconfig; + } + } + + globalOutputStream() << "OpenGL window configuration: colour-buffer = auto, depth-buffer = none\n"; + return gdk_gl_config_new_by_mode((GdkGLConfigMode)(GDK_GL_MODE_RGBA | GDK_GL_MODE_DOUBLE)); +} + +int config_rgba32_depth32[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 24, + GDK_GL_DEPTH_SIZE, 32, + GDK_GL_ATTRIB_LIST_NONE, +}; + +int config_rgba32_depth24[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 24, + GDK_GL_DEPTH_SIZE, 24, + GDK_GL_ATTRIB_LIST_NONE, +}; + +int config_rgba32_depth16[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 24, + GDK_GL_DEPTH_SIZE, 16, + GDK_GL_ATTRIB_LIST_NONE, +}; + +int config_rgba32_depth[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 24, + GDK_GL_DEPTH_SIZE, 1, + GDK_GL_ATTRIB_LIST_NONE, +}; + +int config_rgba_depth16[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 16, + GDK_GL_DEPTH_SIZE, 16, + GDK_GL_ATTRIB_LIST_NONE, +}; + +int config_rgba_depth[] = { + GDK_GL_RGBA, + GDK_GL_DOUBLEBUFFER, + GDK_GL_BUFFER_SIZE, 16, + GDK_GL_DEPTH_SIZE, 1, + GDK_GL_ATTRIB_LIST_NONE, +}; + +const config_t configs_with_depth[] = +{ + { + "colour-buffer = 32bpp, depth-buffer = 32bpp", + config_rgba32_depth32, + }, + { + "colour-buffer = 32bpp, depth-buffer = 24bpp", + config_rgba32_depth24, + }, + { + "colour-buffer = 32bpp, depth-buffer = 16bpp", + config_rgba32_depth16, + }, + { + "colour-buffer = 32bpp, depth-buffer = auto", + config_rgba32_depth, + }, + { + "colour-buffer = 16bpp, depth-buffer = 16bpp", + config_rgba_depth16, + }, + { + "colour-buffer = auto, depth-buffer = auto", + config_rgba_depth, + }, +}; + +GdkGLConfig* glconfig_new_with_depth() +{ + GdkGLConfig* glconfig = 0; + + for(configs_iterator i = configs_with_depth, end = configs_with_depth + 6; i != end; ++i) + { + glconfig = gdk_gl_config_new((*i).attribs); + if(glconfig != 0) + { + globalOutputStream() << "OpenGL window configuration: " << (*i).name << "\n"; + return glconfig; + } + } + + globalOutputStream() << "OpenGL window configuration: colour-buffer = auto, depth-buffer = auto (fallback)\n"; + return gdk_gl_config_new_by_mode((GdkGLConfigMode)(GDK_GL_MODE_RGBA | GDK_GL_MODE_DOUBLE | GDK_GL_MODE_DEPTH)); +} + +unsigned int g_context_count = 0; + +namespace +{ + GtkWidget* g_shared = 0; +} + +gint glwidget_context_created(GtkWidget* widget, gpointer data) +{ + if(++g_context_count == 1) + { + g_shared = widget; + gtk_widget_ref(g_shared); + + glwidget_make_current(g_shared); + GlobalOpenGL().contextValid = true; + + GLWidget_sharedContextCreated(); + } + return FALSE; +} + +gint glwidget_context_destroyed(GtkWidget* widget, gpointer data) +{ + if(--g_context_count == 0) + { + GlobalOpenGL().contextValid = false; + + GLWidget_sharedContextDestroyed(); + + gtk_widget_unref(g_shared); + g_shared = 0; + } + return FALSE; +} + +gboolean glwidget_enable_gl(GtkWidget* widget, GtkWidget* widget2, gpointer data) +{ + if(widget2 == 0 && !gtk_widget_is_gl_capable(widget)) + { + GdkGLConfig* glconfig = (g_object_get_data(G_OBJECT(widget), "zbuffer")) ? glconfig_new_with_depth() : glconfig_new(); + ASSERT_MESSAGE(glconfig != 0, "failed to create OpenGL config"); + + gtk_widget_set_gl_capability(widget, glconfig, g_shared != 0 ? gtk_widget_get_gl_context(g_shared) : 0, TRUE, GDK_GL_RGBA_TYPE); + + gtk_widget_realize(widget); + if(g_shared == 0) + { + g_shared = widget; + } + + // free glconfig? + } + return FALSE; +} + +GtkWidget* glwidget_new(gboolean zbuffer) +{ + GtkWidget* widget = gtk_drawing_area_new(); + + g_object_set_data(G_OBJECT(widget), "zbuffer", gint_to_pointer(zbuffer)); + + g_signal_connect(G_OBJECT(widget), "hierarchy-changed", G_CALLBACK(glwidget_enable_gl), 0); + + g_signal_connect(G_OBJECT(widget), "realize", G_CALLBACK(glwidget_context_created), 0); + g_signal_connect(G_OBJECT(widget), "unrealize", G_CALLBACK(glwidget_context_destroyed), 0); + + return widget; +} + +void glwidget_destroy_context (GtkWidget *widget) +{ +} + +void glwidget_create_context (GtkWidget *widget) +{ +} + +void glwidget_swap_buffers (GtkWidget *widget) +{ + GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget); + gdk_gl_drawable_swap_buffers (gldrawable); +} + +gboolean glwidget_make_current (GtkWidget *widget) +{ + GdkGLContext *glcontext = gtk_widget_get_gl_context (widget); + GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget); + return gdk_gl_drawable_gl_begin (gldrawable, glcontext); +} + diff --git a/libs/gtkutil/glwidget.h b/libs/gtkutil/glwidget.h new file mode 100644 index 00000000..235b0908 --- /dev/null +++ b/libs/gtkutil/glwidget.h @@ -0,0 +1,39 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_GLWIDGET_H) +#define INCLUDED_GTKUTIL_GLWIDGET_H + +typedef struct _GtkWidget GtkWidget; +typedef int gint; +typedef gint gboolean; + +GtkWidget* glwidget_new(gboolean zbuffer); +void glwidget_swap_buffers(GtkWidget* widget); +gboolean glwidget_make_current(GtkWidget* widget); +void glwidget_destroy_context(GtkWidget* widget); +void glwidget_create_context(GtkWidget* widget); + +extern void (*GLWidget_sharedContextCreated)(); +extern void (*GLWidget_sharedContextDestroyed)(); + + +#endif diff --git a/libs/gtkutil/gtkutil.vcproj b/libs/gtkutil/gtkutil.vcproj new file mode 100644 index 00000000..9197cd15 --- /dev/null +++ b/libs/gtkutil/gtkutil.vcproj @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/gtkutil/idledraw.cpp b/libs/gtkutil/idledraw.cpp new file mode 100644 index 00000000..47d432ca --- /dev/null +++ b/libs/gtkutil/idledraw.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "idledraw.h" + diff --git a/libs/gtkutil/idledraw.h b/libs/gtkutil/idledraw.h new file mode 100644 index 00000000..48081237 --- /dev/null +++ b/libs/gtkutil/idledraw.h @@ -0,0 +1,69 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_IDLEDRAW_H) +#define INCLUDED_GTKUTIL_IDLEDRAW_H + +#include + +#include "generic/callback.h" + +class IdleDraw +{ + Callback m_draw; + unsigned int m_handler; + static gboolean draw(gpointer data) + { + reinterpret_cast(data)->m_draw(); + reinterpret_cast(data)->m_handler = 0; + return FALSE; + } +public: + IdleDraw(const Callback& draw) : m_draw(draw), m_handler(0) + { + } + ~IdleDraw() + { + if(m_handler != 0) + { + g_source_remove(m_handler); + } + } + void queueDraw() + { + if(m_handler == 0) + { + m_handler = g_idle_add(&draw, this); + } + } + typedef MemberCaller QueueDrawCaller; + + void flush() + { + if(m_handler != 0) + { + draw(this); + } + } +}; + + +#endif diff --git a/libs/gtkutil/image.cpp b/libs/gtkutil/image.cpp new file mode 100644 index 00000000..66e61d1e --- /dev/null +++ b/libs/gtkutil/image.cpp @@ -0,0 +1,96 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "image.h" + +#include +#include + +#include "string/string.h" +#include "stream/stringstream.h" +#include "stream/textstream.h" + + +namespace +{ + CopiedString g_bitmapsPath; +} + +void BitmapsPath_set(const char* path) +{ + g_bitmapsPath = path; +} + +GdkPixbuf* pixbuf_new_from_file_with_mask(const char* filename) +{ + GdkPixbuf* rgb = gdk_pixbuf_new_from_file(filename, 0); + if(rgb == 0) + { + return 0; + } + else + { + GdkPixbuf* rgba = gdk_pixbuf_add_alpha(rgb, TRUE, 255, 0, 255); + gdk_pixbuf_unref(rgb); + return rgba; + } +} + +GtkImage* image_new_from_file_with_mask(const char* filename) +{ + GdkPixbuf* rgba = pixbuf_new_from_file_with_mask(filename); + if(rgba == 0) + { + return 0; + } + else + { + GtkImage* image = GTK_IMAGE(gtk_image_new_from_pixbuf(rgba)); + gdk_pixbuf_unref(rgba); + return image; + } +} + +GtkImage* image_new_missing() +{ + return GTK_IMAGE(gtk_image_new_from_stock(GTK_STOCK_MISSING_IMAGE, GTK_ICON_SIZE_SMALL_TOOLBAR)); +} + +GtkImage* new_image(const char* filename) +{ + { + GtkImage* image = image_new_from_file_with_mask(filename); + if(image != 0) + { + return image; + } + } + + return image_new_missing(); +} + +GtkImage* new_local_image(const char* filename) +{ + StringOutputStream fullPath(256); + fullPath << g_bitmapsPath.c_str() << filename; + return new_image(fullPath.c_str()); +} + diff --git a/libs/gtkutil/image.h b/libs/gtkutil/image.h new file mode 100644 index 00000000..bd865142 --- /dev/null +++ b/libs/gtkutil/image.h @@ -0,0 +1,36 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_IMAGE_H) +#define INCLUDED_GTKUTIL_IMAGE_H + +void BitmapsPath_set(const char* path); + +typedef struct _GtkImage GtkImage; +typedef struct _GdkPixbuf GdkPixbuf; + +GdkPixbuf* pixbuf_new_from_file_with_mask(const char* filename); +GtkImage* image_new_from_file_with_mask(const char* filename); +GtkImage* image_new_missing(); +GtkImage* new_image(const char* filename); // filename is full path to image file +GtkImage* new_local_image(const char* filename); // filename is relative to local bitmaps path + +#endif diff --git a/libs/gtkutil/menu.cpp b/libs/gtkutil/menu.cpp new file mode 100644 index 00000000..86be24e7 --- /dev/null +++ b/libs/gtkutil/menu.cpp @@ -0,0 +1,311 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "menu.h" + +#include +#include +#include +#include +#include +#include + +#include "generic/callback.h" + +#include "accelerator.h" +#include "closure.h" +#include "container.h" +#include "pointer.h" + +void menu_add_item(GtkMenu* menu, GtkMenuItem* item) +{ + gtk_container_add(GTK_CONTAINER(menu), GTK_WIDGET(item)); +} + +GtkMenuItem* menu_separator(GtkMenu* menu) +{ + GtkMenuItem* menu_item = GTK_MENU_ITEM(gtk_menu_item_new()); + container_add_widget(GTK_CONTAINER(menu), GTK_WIDGET(menu_item)); + gtk_widget_set_sensitive(GTK_WIDGET(menu_item), FALSE); + gtk_widget_show(GTK_WIDGET(menu_item)); + return menu_item; +} + +GtkTearoffMenuItem* menu_tearoff(GtkMenu* menu) +{ + GtkTearoffMenuItem* menu_item = GTK_TEAROFF_MENU_ITEM(gtk_tearoff_menu_item_new()); + container_add_widget(GTK_CONTAINER(menu), GTK_WIDGET(menu_item)); +// gtk_widget_set_sensitive(GTK_WIDGET(menu_item), FALSE); -- controls whether menu is detachable + gtk_widget_show(GTK_WIDGET(menu_item)); + return menu_item; +} + +GtkMenuItem* new_sub_menu_item_with_mnemonic(const char* mnemonic) +{ + GtkMenuItem* item = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(mnemonic)); + gtk_widget_show(GTK_WIDGET(item)); + + GtkWidget* sub_menu = gtk_menu_new(); + gtk_menu_item_set_submenu(item, sub_menu); + + return item; +} + +GtkMenu* create_sub_menu_with_mnemonic(GtkMenuShell* parent, const char* mnemonic) +{ + GtkMenuItem* item = new_sub_menu_item_with_mnemonic(mnemonic); + container_add_widget(GTK_CONTAINER(parent), GTK_WIDGET(item)); + return GTK_MENU(gtk_menu_item_get_submenu(item)); +} + +GtkMenu* create_sub_menu_with_mnemonic(GtkMenuBar* bar, const char* mnemonic) +{ + return create_sub_menu_with_mnemonic(GTK_MENU_SHELL(bar), mnemonic); +} + +GtkMenu* create_sub_menu_with_mnemonic(GtkMenu* parent, const char* mnemonic) +{ + return create_sub_menu_with_mnemonic(GTK_MENU_SHELL(parent), mnemonic); +} + +void activate_closure_callback(GtkWidget* widget, gpointer data) +{ + (*reinterpret_cast(data))(); +} + +guint menu_item_connect_callback(GtkMenuItem* item, const Callback& callback) +{ +#if 1 + return g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(callback.getThunk()), callback.getEnvironment()); +#else + return g_signal_connect_closure(G_OBJECT(item), "activate", create_cclosure(G_CALLBACK(activate_closure_callback), callback), FALSE); +#endif +} + +guint check_menu_item_connect_callback(GtkCheckMenuItem* item, const Callback& callback) +{ +#if 1 + guint handler = g_signal_connect_swapped(G_OBJECT(item), "toggled", G_CALLBACK(callback.getThunk()), callback.getEnvironment()); +#else + guint handler = g_signal_connect_closure(G_OBJECT(item), "toggled", create_cclosure(G_CALLBACK(activate_closure_callback), callback), TRUE); +#endif + g_object_set_data(G_OBJECT(item), "handler", gint_to_pointer(handler)); + return handler; +} + +GtkMenuItem* new_menu_item_with_mnemonic(const char *mnemonic, const Callback& callback) +{ + GtkMenuItem* item = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(mnemonic)); + gtk_widget_show(GTK_WIDGET(item)); + menu_item_connect_callback(item, callback); + return item; +} + +GtkMenuItem* create_menu_item_with_mnemonic(GtkMenu* menu, const char *mnemonic, const Callback& callback) +{ + GtkMenuItem* item = new_menu_item_with_mnemonic(mnemonic, callback); + container_add_widget(GTK_CONTAINER(menu), GTK_WIDGET(item)); + return item; +} + +GtkCheckMenuItem* new_check_menu_item_with_mnemonic(const char* mnemonic, const Callback& callback) +{ + GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(gtk_check_menu_item_new_with_mnemonic(mnemonic)); + gtk_widget_show(GTK_WIDGET(item)); + check_menu_item_connect_callback(item, callback); + return item; +} + +GtkCheckMenuItem* create_check_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Callback& callback) +{ + GtkCheckMenuItem* item = new_check_menu_item_with_mnemonic(mnemonic, callback); + container_add_widget(GTK_CONTAINER(menu), GTK_WIDGET(item)); + return item; +} + +GtkRadioMenuItem* new_radio_menu_item_with_mnemonic(GSList** group, const char* mnemonic, const Callback& callback) +{ + GtkRadioMenuItem* item = GTK_RADIO_MENU_ITEM(gtk_radio_menu_item_new_with_mnemonic(*group, mnemonic)); + if(*group == 0) + { + gtk_check_menu_item_set_state(GTK_CHECK_MENU_ITEM(item), TRUE); + } + *group = gtk_radio_menu_item_group(item); + gtk_widget_show(GTK_WIDGET(item)); + check_menu_item_connect_callback(GTK_CHECK_MENU_ITEM(item), callback); + return item; +} + +GtkRadioMenuItem* create_radio_menu_item_with_mnemonic(GtkMenu* menu, GSList** group, const char* mnemonic, const Callback& callback) +{ + GtkRadioMenuItem* item = new_radio_menu_item_with_mnemonic(group, mnemonic, callback); + container_add_widget(GTK_CONTAINER(menu), GTK_WIDGET(item)); + return item; +} + +void check_menu_item_set_active_no_signal(GtkCheckMenuItem* item, gboolean active) +{ + guint handler_id = gpointer_to_int(g_object_get_data(G_OBJECT(item), "handler")); + g_signal_handler_block(G_OBJECT(item), handler_id); + gtk_check_menu_item_set_active(item, active); + g_signal_handler_unblock(G_OBJECT(item), handler_id); +} + + + +void radio_menu_item_set_active_no_signal(GtkRadioMenuItem* item, gboolean active) +{ + { + for(GSList* l = gtk_radio_menu_item_get_group(item); l != 0; l = g_slist_next(l)) + { + g_signal_handler_block(G_OBJECT(l->data), gpointer_to_int(g_object_get_data(G_OBJECT(l->data), "handler"))); + } + } + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), active); + { + for(GSList* l = gtk_radio_menu_item_get_group(item); l != 0; l = g_slist_next(l)) + { + g_signal_handler_unblock(G_OBJECT(l->data), gpointer_to_int(g_object_get_data(G_OBJECT(l->data), "handler"))); + } + } +} + + +void menu_item_set_accelerator(GtkMenuItem* item, GClosure* closure) +{ + GtkAccelLabel* accel_label = GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(item))); + gtk_accel_label_set_accel_closure(accel_label, closure); +} + +void accelerator_name(const Accelerator& accelerator, GString* gstring) +{ + gboolean had_mod = FALSE; + if (accelerator.modifiers & GDK_SHIFT_MASK) + { + g_string_append (gstring, "Shift"); + had_mod = TRUE; + } + if (accelerator.modifiers & GDK_CONTROL_MASK) + { + if (had_mod) + g_string_append (gstring, "+"); + g_string_append (gstring, "Ctrl"); + had_mod = TRUE; + } + if (accelerator.modifiers & GDK_MOD1_MASK) + { + if (had_mod) + g_string_append (gstring, "+"); + g_string_append (gstring, "Alt"); + had_mod = TRUE; + } + + if (had_mod) + g_string_append (gstring, "+"); + if (accelerator.key < 0x80 || (accelerator.key > 0x80 && accelerator.key <= 0xff)) + { + switch (accelerator.key) + { + case ' ': + g_string_append (gstring, "Space"); + break; + case '\\': + g_string_append (gstring, "Backslash"); + break; + default: + g_string_append_c (gstring, gchar(toupper(accelerator.key))); + break; + } + } + else + { + gchar *tmp; + + tmp = gtk_accelerator_name (accelerator.key, (GdkModifierType)0); + if (tmp[0] != 0 && tmp[1] == 0) + tmp[0] = gchar(toupper(tmp[0])); + g_string_append (gstring, tmp); + g_free (tmp); + } +} + +void menu_item_set_accelerator(GtkMenuItem* item, Accelerator accelerator) +{ + GtkAccelLabel* accel_label = GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(item))); + + g_free (accel_label->accel_string); + accel_label->accel_string = 0; + + GString* gstring = g_string_new (accel_label->accel_string); + g_string_append (gstring, " "); + + accelerator_name(accelerator, gstring); + + g_free (accel_label->accel_string); + accel_label->accel_string = gstring->str; + g_string_free (gstring, FALSE); + + if (!accel_label->accel_string) + accel_label->accel_string = g_strdup (""); + + gtk_widget_queue_resize (GTK_WIDGET (accel_label)); +} + +void menu_item_add_accelerator(GtkMenuItem* item, Accelerator accelerator) +{ + if(accelerator.key != 0) + { + GClosure* closure = global_accel_group_find(accelerator); + if(closure != 0) + { + menu_item_set_accelerator(item, closure); + } + else + { + menu_item_set_accelerator(item, accelerator); + } + } +} + +GtkMenuItem* create_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Command& command) +{ + GtkMenuItem* item = create_menu_item_with_mnemonic(menu, mnemonic, command.m_callback); + menu_item_add_accelerator(item, command.m_accelerator); + return item; +} + +void check_menu_item_set_active_callback(GtkCheckMenuItem& item, bool enabled) +{ + check_menu_item_set_active_no_signal(&item, enabled); +} +typedef ReferenceCaller1 CheckMenuItemSetActiveCaller; + +GtkCheckMenuItem* create_check_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Toggle& toggle) +{ + GtkCheckMenuItem* item = create_check_menu_item_with_mnemonic(menu, mnemonic, toggle.m_command.m_callback); + menu_item_add_accelerator(GTK_MENU_ITEM(item), toggle.m_command.m_accelerator); + toggle.m_exportCallback(CheckMenuItemSetActiveCaller(*item)); + return item; +} + + + + diff --git a/libs/gtkutil/menu.h b/libs/gtkutil/menu.h new file mode 100644 index 00000000..a394d823 --- /dev/null +++ b/libs/gtkutil/menu.h @@ -0,0 +1,58 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_MENU_H) +#define INCLUDED_GTKUTIL_MENU_H + +#include "generic/callbackfwd.h" + +typedef int gint; +typedef gint gboolean; +typedef struct _GSList GSList; +typedef struct _GtkMenu GtkMenu; +typedef struct _GtkMenuBar GtkMenuBar; +typedef struct _GtkMenuItem GtkMenuItem; +typedef struct _GtkCheckMenuItem GtkCheckMenuItem; +typedef struct _GtkRadioMenuItem GtkRadioMenuItem; +typedef struct _GtkTearoffMenuItem GtkTearoffMenuItem; + +void menu_add_item(GtkMenu* menu, GtkMenuItem* item); +GtkMenuItem* menu_separator(GtkMenu* menu); +GtkTearoffMenuItem* menu_tearoff(GtkMenu* menu); +GtkMenuItem* new_sub_menu_item_with_mnemonic(const char* mnemonic); +GtkMenu* create_sub_menu_with_mnemonic(GtkMenuBar* bar, const char* mnemonic); +GtkMenu* create_sub_menu_with_mnemonic(GtkMenu* parent, const char* mnemonic); +GtkMenuItem* create_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Callback& callback); +GtkCheckMenuItem* create_check_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Callback& callback); +GtkRadioMenuItem* create_radio_menu_item_with_mnemonic(GtkMenu* menu, GSList** group, const char* mnemonic, const Callback& callback); + +class Command; +GtkMenuItem* create_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Command& command); +class Toggle; +GtkCheckMenuItem* create_check_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const Toggle& toggle); + + +typedef struct _GtkCheckMenuItem GtkCheckMenuItem; +void check_menu_item_set_active_no_signal(GtkCheckMenuItem* item, gboolean active); +typedef struct _GtkRadioMenuItem GtkRadioMenuItem; +void radio_menu_item_set_active_no_signal(GtkRadioMenuItem* item, gboolean active); + +#endif diff --git a/libs/gtkutil/messagebox.cpp b/libs/gtkutil/messagebox.cpp new file mode 100644 index 00000000..4c430ed3 --- /dev/null +++ b/libs/gtkutil/messagebox.cpp @@ -0,0 +1,213 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "messagebox.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dialog.h" +#include "widget.h" + +GtkWidget* create_padding(int width, int height) +{ + GtkWidget* widget = gtk_alignment_new(0.0, 0.0, 0.0, 0.0); + gtk_widget_show(widget); + gtk_widget_set_size_request(widget, width, height); + return widget; +} + +const char* messagebox_stock_icon(EMessageBoxIcon type) +{ + switch(type) + { + default: + case eMB_ICONDEFAULT: + return GTK_STOCK_DIALOG_INFO; + case eMB_ICONERROR: + return GTK_STOCK_DIALOG_ERROR; + case eMB_ICONWARNING: + return GTK_STOCK_DIALOG_WARNING; + case eMB_ICONQUESTION: + return GTK_STOCK_DIALOG_QUESTION; + case eMB_ICONASTERISK: + return GTK_STOCK_DIALOG_INFO; + } +} + +EMessageBoxReturn gtk_MessageBox (GtkWidget *parent, const char* text, const char* title, EMessageBoxType type, EMessageBoxIcon icon) +{ + ModalDialog dialog; + ModalDialogButton ok_button(dialog, eIDOK); + ModalDialogButton cancel_button(dialog, eIDCANCEL); + ModalDialogButton yes_button(dialog, eIDYES); + ModalDialogButton no_button(dialog, eIDNO); + + GtkWindow* parentWindow = parent != 0 ? GTK_WINDOW(parent) : 0; + + GtkWindow* window = create_fixedsize_modal_dialog_window(parentWindow, title, dialog, 400, 100); + + if(parentWindow != 0) + { + //g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(floating_window_delete_present), parent); + gtk_window_deiconify(parentWindow); + } + + GtkAccelGroup* accel = gtk_accel_group_new(); + gtk_window_add_accel_group(window, accel); + + GtkVBox* vbox = create_dialog_vbox(8, 8); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox)); + + + GtkHBox* hboxDummy = create_dialog_hbox(0, 0); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hboxDummy), FALSE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(hboxDummy), create_padding(0, 50), FALSE, FALSE, 0); // HACK to force minimum height + + GtkHBox* iconBox = create_dialog_hbox(16, 0); + gtk_box_pack_start(GTK_BOX(hboxDummy), GTK_WIDGET(iconBox), FALSE, FALSE, 0); + + GtkImage* image = GTK_IMAGE(gtk_image_new_from_stock(messagebox_stock_icon(icon), GTK_ICON_SIZE_DIALOG)); + gtk_widget_show(GTK_WIDGET(image)); + gtk_box_pack_start(GTK_BOX(iconBox), GTK_WIDGET(image), FALSE, FALSE, 0); + + GtkLabel* label = GTK_LABEL(gtk_label_new(text)); + gtk_widget_show(GTK_WIDGET(label)); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + gtk_label_set_justify(label, GTK_JUSTIFY_LEFT); + gtk_label_set_line_wrap(label, TRUE); + gtk_box_pack_start(GTK_BOX(iconBox), GTK_WIDGET(label), TRUE, TRUE, 0); + + + GtkVBox* vboxDummy = create_dialog_vbox(0, 0); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(vboxDummy), FALSE, FALSE, 0); + + GtkAlignment* alignment = GTK_ALIGNMENT(gtk_alignment_new(0.5, 0.0, 0.0, 0.0)); + gtk_widget_show(GTK_WIDGET(alignment)); + gtk_box_pack_start(GTK_BOX(vboxDummy), GTK_WIDGET(alignment), FALSE, FALSE, 0); + + GtkHBox* hbox = create_dialog_hbox(8, 0); + gtk_container_add(GTK_CONTAINER(alignment), GTK_WIDGET(hbox)); + + gtk_box_pack_start(GTK_BOX(vboxDummy), create_padding(400, 0), FALSE, FALSE, 0); // HACK to force minimum width + + + if (type == eMB_OK) + { + GtkButton* button = create_modal_dialog_button("OK", ok_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_show(GTK_WIDGET(button)); + + dialog.ret = eIDOK; + } + else if (type == eMB_OKCANCEL) + { + { + GtkButton* button = create_modal_dialog_button("OK", ok_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_show(GTK_WIDGET(button)); + } + + { + GtkButton* button = create_modal_dialog_button("OK", cancel_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0); + gtk_widget_show(GTK_WIDGET(button)); + } + + dialog.ret = eIDCANCEL; + } + else if (type == eMB_YESNOCANCEL) + { + { + GtkButton* button = create_modal_dialog_button("Yes", yes_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_show(GTK_WIDGET(button)); + } + + { + GtkButton* button = create_modal_dialog_button("No", no_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_show(GTK_WIDGET(button)); + } + { + GtkButton* button = create_modal_dialog_button("Cancel", cancel_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_show(GTK_WIDGET(button)); + } + + dialog.ret = eIDCANCEL; + } + else if (type == eMB_NOYES) + { + { + GtkButton* button = create_modal_dialog_button("No", no_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_show(GTK_WIDGET(button)); + } + { + GtkButton* button = create_modal_dialog_button("Yes", yes_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_show(GTK_WIDGET(button)); + } + + dialog.ret = eIDNO; + } + else /* if (type == eMB_YESNO) */ + { + { + GtkButton* button = create_modal_dialog_button("Yes", yes_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_show(GTK_WIDGET(button)); + } + + { + GtkButton* button = create_modal_dialog_button("No", no_button); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), TRUE, FALSE, 0); + gtk_widget_show(GTK_WIDGET(button)); + } + dialog.ret = eIDNO; + } + + modal_dialog_show(window, dialog); + + gtk_widget_destroy(GTK_WIDGET(window)); + + return dialog.ret; +} + diff --git a/libs/gtkutil/messagebox.h b/libs/gtkutil/messagebox.h new file mode 100644 index 00000000..c374aafe --- /dev/null +++ b/libs/gtkutil/messagebox.h @@ -0,0 +1,31 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_MESSAGEBOX_H) +#define INCLUDED_GTKUTIL_MESSAGEBOX_H + +#include "qerplugin.h" + +typedef struct _GtkWidget GtkWidget; +/// \brief Shows a modal message-box. +EMessageBoxReturn gtk_MessageBox(GtkWidget *parent, const char* text, const char* title = "GtkRadiant", EMessageBoxType type = eMB_OK, EMessageBoxIcon icon = eMB_ICONDEFAULT); + +#endif diff --git a/libs/gtkutil/nonmodal.cpp b/libs/gtkutil/nonmodal.cpp new file mode 100644 index 00000000..6252e2dd --- /dev/null +++ b/libs/gtkutil/nonmodal.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "nonmodal.h" + diff --git a/libs/gtkutil/nonmodal.h b/libs/gtkutil/nonmodal.h new file mode 100644 index 00000000..eed4be37 --- /dev/null +++ b/libs/gtkutil/nonmodal.h @@ -0,0 +1,186 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_NONMODAL_H) +#define INCLUDED_GTKUTIL_NONMODAL_H + +#include +#include +#include +#include + +#include "generic/callback.h" + +#include "pointer.h" +#include "button.h" + +typedef struct _GtkEntry GtkEntry; + + +inline gboolean escape_clear_focus_widget(GtkWidget* widget, GdkEventKey* event, gpointer data) +{ + if(event->keyval == GDK_Escape) + { + gtk_window_set_focus(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(widget))), NULL); + return TRUE; + } + return FALSE; +} + +inline void widget_connect_escape_clear_focus_widget(GtkWidget* widget) +{ + g_signal_connect(G_OBJECT(widget), "key_press_event", G_CALLBACK(escape_clear_focus_widget), 0); +} + + +class NonModalEntry +{ + bool m_editing; + Callback m_apply; + Callback m_cancel; + + static gboolean focus_in(GtkEntry* entry, GdkEventFocus *event, NonModalEntry* self) + { + self->m_editing = false; + return FALSE; + } + + static gboolean focus_out(GtkEntry* entry, GdkEventFocus *event, NonModalEntry* self) + { + if(self->m_editing && GTK_WIDGET_VISIBLE(entry)) + { + self->m_apply(); + } + self->m_editing = false; + return FALSE; + } + + static gboolean changed(GtkEntry* entry, NonModalEntry* self) + { + self->m_editing = true; + return FALSE; + } + + static gboolean enter(GtkEntry* entry, GdkEventKey* event, NonModalEntry* self) + { + if(event->keyval == GDK_Return) + { + self->m_apply(); + self->m_editing = false; + gtk_window_set_focus(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(entry))), NULL); + return TRUE; + } + return FALSE; + } + + static gboolean escape(GtkEntry* entry, GdkEventKey* event, NonModalEntry* self) + { + if(event->keyval == GDK_Escape) + { + self->m_cancel(); + self->m_editing = false; + gtk_window_set_focus(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(entry))), NULL); + return TRUE; + } + return FALSE; + } + +public: + NonModalEntry(const Callback& apply, const Callback& cancel) : m_editing(false), m_apply(apply), m_cancel(cancel) + { + } + void connect(GtkEntry* entry) + { + g_signal_connect(G_OBJECT(entry), "focus_in_event", G_CALLBACK(focus_in), this); + g_signal_connect(G_OBJECT(entry), "focus_out_event", G_CALLBACK(focus_out), this); + g_signal_connect(G_OBJECT(entry), "key_press_event", G_CALLBACK(enter), this); + g_signal_connect(G_OBJECT(entry), "key_press_event", G_CALLBACK(escape), this); + g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(changed), this); + } +}; + + +class NonModalSpinner +{ + Callback m_apply; + Callback m_cancel; + + static gboolean changed(GtkSpinButton* spin, NonModalSpinner* self) + { + self->m_apply(); + return FALSE; + } + + static gboolean enter(GtkSpinButton* spin, GdkEventKey* event, NonModalSpinner* self) + { + if(event->keyval == GDK_Return) + { + gtk_window_set_focus(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(spin))), NULL); + return TRUE; + } + return FALSE; + } + + static gboolean escape(GtkSpinButton* spin, GdkEventKey* event, NonModalSpinner* self) + { + if(event->keyval == GDK_Escape) + { + self->m_cancel(); + gtk_window_set_focus(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(spin))), NULL); + return TRUE; + } + return FALSE; + } + +public: + NonModalSpinner(const Callback& apply, const Callback& cancel) : m_apply(apply), m_cancel(cancel) + { + } + void connect(GtkSpinButton* spin) + { + guint handler = g_signal_connect(G_OBJECT(gtk_spin_button_get_adjustment(spin)), "value_changed", G_CALLBACK(changed), this); + g_object_set_data(G_OBJECT(spin), "handler", gint_to_pointer(handler)); + g_signal_connect(G_OBJECT(spin), "key_press_event", G_CALLBACK(enter), this); + g_signal_connect(G_OBJECT(spin), "key_press_event", G_CALLBACK(escape), this); + } +}; + + +class NonModalRadio +{ + Callback m_changed; + +public: + NonModalRadio(const Callback& changed) : m_changed(changed) + { + } + void connect(GtkRadioButton* radio) + { + GSList* group = gtk_radio_button_group(radio); + for(; group != 0; group = g_slist_next(group)) + { + toggle_button_connect_callback(GTK_TOGGLE_BUTTON(group->data), m_changed); + } + } +}; + + +#endif diff --git a/libs/gtkutil/paned.cpp b/libs/gtkutil/paned.cpp new file mode 100644 index 00000000..db0c308b --- /dev/null +++ b/libs/gtkutil/paned.cpp @@ -0,0 +1,100 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "paned.h" + +#include +#include + +#include "frame.h" + + +class PanedState +{ +public: + float position; + int size; +}; + +gboolean hpaned_allocate(GtkWidget* widget, GtkAllocation* allocation, PanedState* paned) +{ + if(paned->size != allocation->width) + { + paned->size = allocation->width; + gtk_paned_set_position (GTK_PANED (widget), static_cast(paned->size * paned->position)); + } + return FALSE; +} + +gboolean vpaned_allocate(GtkWidget* widget, GtkAllocation* allocation, PanedState* paned) +{ + if(paned->size != allocation->height) + { + paned->size = allocation->height; + gtk_paned_set_position (GTK_PANED (widget), static_cast(paned->size * paned->position)); + } + return FALSE; +} + +gboolean paned_position(GtkWidget* widget, gpointer dummy, PanedState* paned) +{ + if(paned->size != -1) + paned->position = gtk_paned_get_position (GTK_PANED (widget)) / static_cast(paned->size); + return FALSE; +} + +PanedState g_hpaned = { 0.5f, -1, }; +PanedState g_vpaned1 = { 0.5f, -1, }; +PanedState g_vpaned2 = { 0.5f, -1, }; + +GtkHPaned* create_split_views(GtkWidget* topleft, GtkWidget* topright, GtkWidget* botleft, GtkWidget* botright) +{ + GtkHPaned* hsplit = GTK_HPANED(gtk_hpaned_new()); + gtk_widget_show(GTK_WIDGET(hsplit)); + + g_signal_connect(G_OBJECT(hsplit), "size_allocate", G_CALLBACK(hpaned_allocate), &g_hpaned); + g_signal_connect(G_OBJECT(hsplit), "notify::position", G_CALLBACK(paned_position), &g_hpaned); + + { + GtkVPaned* vsplit = GTK_VPANED(gtk_vpaned_new()); + gtk_paned_add1(GTK_PANED(hsplit), GTK_WIDGET(vsplit)); + gtk_widget_show(GTK_WIDGET(vsplit)); + + g_signal_connect(G_OBJECT(vsplit), "size_allocate", G_CALLBACK(vpaned_allocate), &g_vpaned1); + g_signal_connect(G_OBJECT(vsplit), "notify::position", G_CALLBACK(paned_position), &g_vpaned1); + + gtk_paned_add1(GTK_PANED(vsplit), GTK_WIDGET(create_framed_widget(topleft))); + gtk_paned_add2(GTK_PANED(vsplit), GTK_WIDGET(create_framed_widget(topright))); + } + { + GtkVPaned* vsplit = GTK_VPANED(gtk_vpaned_new()); + gtk_paned_add2(GTK_PANED(hsplit), GTK_WIDGET(vsplit)); + gtk_widget_show(GTK_WIDGET(vsplit)); + + g_signal_connect(G_OBJECT(vsplit), "size_allocate", G_CALLBACK(vpaned_allocate), &g_vpaned2); + g_signal_connect(G_OBJECT(vsplit), "notify::position", G_CALLBACK(paned_position), &g_vpaned2); + + gtk_paned_add1(GTK_PANED(vsplit), GTK_WIDGET(create_framed_widget(botleft))); + gtk_paned_add2(GTK_PANED(vsplit), GTK_WIDGET(create_framed_widget(botright))); + } + return hsplit; +} + diff --git a/libs/gtkutil/paned.h b/libs/gtkutil/paned.h new file mode 100644 index 00000000..578a3c5d --- /dev/null +++ b/libs/gtkutil/paned.h @@ -0,0 +1,29 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_PANED_H) +#define INCLUDED_GTKUTIL_PANED_H + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkHPaned GtkHPaned; +GtkHPaned* create_split_views(GtkWidget* topleft, GtkWidget* topright, GtkWidget* botleft, GtkWidget* botright); + +#endif diff --git a/libs/gtkutil/pointer.cpp b/libs/gtkutil/pointer.cpp new file mode 100644 index 00000000..2c36d2d7 --- /dev/null +++ b/libs/gtkutil/pointer.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "pointer.h" + diff --git a/libs/gtkutil/pointer.h b/libs/gtkutil/pointer.h new file mode 100644 index 00000000..c8294467 --- /dev/null +++ b/libs/gtkutil/pointer.h @@ -0,0 +1,40 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_POINTER_H) +#define INCLUDED_GTKUTIL_POINTER_H + +typedef int gint; +typedef void* gpointer; + +#include + +inline gint gpointer_to_int(gpointer p) +{ + return gint(std::size_t(p)); +} + +inline gpointer gint_to_pointer(gint i) +{ + return gpointer(std::size_t(i)); +} + +#endif diff --git a/libs/gtkutil/toolbar.cpp b/libs/gtkutil/toolbar.cpp new file mode 100644 index 00000000..194aff2d --- /dev/null +++ b/libs/gtkutil/toolbar.cpp @@ -0,0 +1,78 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "toolbar.h" + +#include +#include + +#include "generic/callback.h" + +#include "accelerator.h" +#include "button.h" +#include "closure.h" +#include "pointer.h" + + +void toolbar_append(GtkToolbar* toolbar, GtkButton* button, const char* description) +{ + gtk_widget_show(GTK_WIDGET(button)); + gtk_button_set_relief(button, GTK_RELIEF_NONE); + GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(button), GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(button), GTK_CAN_DEFAULT); + gtk_toolbar_append_element(toolbar, GTK_TOOLBAR_CHILD_WIDGET, GTK_WIDGET(button), "", description, "", 0, 0, 0); +} + +GtkButton* toolbar_append_button(GtkToolbar* toolbar, const char* description, const char* icon, const Callback& callback) +{ + GtkButton* button = GTK_BUTTON(gtk_button_new()); + button_set_icon(button, icon); + button_connect_callback(button, callback); + toolbar_append(toolbar, button, description); + return button; +} + +GtkToggleButton* toolbar_append_toggle_button(GtkToolbar* toolbar, const char* description, const char* icon, const Callback& callback) +{ + GtkToggleButton* button = GTK_TOGGLE_BUTTON(gtk_toggle_button_new()); + button_set_icon(GTK_BUTTON(button), icon); + toggle_button_connect_callback(button, callback); + toolbar_append(toolbar, GTK_BUTTON(button), description); + return button; +} + +GtkButton* toolbar_append_button(GtkToolbar* toolbar, const char* description, const char* icon, const Command& command) +{ + return toolbar_append_button(toolbar, description, icon, command.m_callback); +} + +void toggle_button_set_active_callback(GtkToggleButton& button, bool active) +{ + toggle_button_set_active_no_signal(&button, active); +} +typedef ReferenceCaller1 ToggleButtonSetActiveCaller; + +GtkToggleButton* toolbar_append_toggle_button(GtkToolbar* toolbar, const char* description, const char* icon, const Toggle& toggle) +{ + GtkToggleButton* button = toolbar_append_toggle_button(toolbar, description, icon, toggle.m_command.m_callback); + toggle.m_exportCallback(ToggleButtonSetActiveCaller(*button)); + return button; +} diff --git a/libs/gtkutil/toolbar.h b/libs/gtkutil/toolbar.h new file mode 100644 index 00000000..9bf847d2 --- /dev/null +++ b/libs/gtkutil/toolbar.h @@ -0,0 +1,38 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_TOOLBAR_H) +#define INCLUDED_GTKUTIL_TOOLBAR_H + +#include "generic/callbackfwd.h" + +typedef struct _GtkButton GtkButton; +typedef struct _GtkToggleButton GtkToggleButton; +typedef struct _GtkToolbar GtkToolbar; +class Command; +class Toggle; + +GtkButton* toolbar_append_button(GtkToolbar* toolbar, const char* description, const char* icon, const Callback& callback); +GtkButton* toolbar_append_button(GtkToolbar* toolbar, const char* description, const char* icon, const Command& command); +GtkToggleButton* toolbar_append_toggle_button(GtkToolbar* toolbar, const char* description, const char* icon, const Callback& callback); +GtkToggleButton* toolbar_append_toggle_button(GtkToolbar* toolbar, const char* description, const char* icon, const Toggle& toggle); + +#endif diff --git a/libs/gtkutil/widget.cpp b/libs/gtkutil/widget.cpp new file mode 100644 index 00000000..62a1b6ca --- /dev/null +++ b/libs/gtkutil/widget.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "widget.h" + diff --git a/libs/gtkutil/widget.h b/libs/gtkutil/widget.h new file mode 100644 index 00000000..225fccf4 --- /dev/null +++ b/libs/gtkutil/widget.h @@ -0,0 +1,190 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_WIDGET_H) +#define INCLUDED_GTKUTIL_WIDGET_H + +#include +#include +#include "generic/callback.h" +#include "warnings.h" +#include "debugging/debugging.h" + +inline void widget_set_visible(GtkWidget* widget, bool shown) +{ + if(shown) + { + gtk_widget_show(widget); + } + else + { + gtk_widget_hide(widget); + } +} + +inline bool widget_is_visible(GtkWidget* widget) +{ + return GTK_WIDGET_VISIBLE(widget) != FALSE; +} + +inline void widget_toggle_visible(GtkWidget* widget) +{ + widget_set_visible(widget, !widget_is_visible(widget)); +} + +class ToggleItem +{ + BoolExportCallback m_exportCallback; + typedef std::list ImportCallbacks; + ImportCallbacks m_importCallbacks; +public: + ToggleItem(const BoolExportCallback& exportCallback) : m_exportCallback(exportCallback) + { + } + + void update() + { + for(ImportCallbacks::iterator i = m_importCallbacks.begin(); i != m_importCallbacks.end(); ++i) + { + m_exportCallback(*i); + } + } + + void addCallback(const BoolImportCallback& callback) + { + m_importCallbacks.push_back(callback); + m_exportCallback(callback); + } + typedef MemberCaller1 AddCallbackCaller; +}; + +class ToggleShown +{ + bool m_shownDeferred; + + ToggleShown(const ToggleShown& other); // NOT COPYABLE + ToggleShown& operator=(const ToggleShown& other); // NOT ASSIGNABLE + + static gboolean notify_visible(GtkWidget* widget, gpointer dummy, ToggleShown* self) + { + self->update(); + return FALSE; + } + static gboolean destroy(GtkWidget* widget, ToggleShown* self) + { + self->m_shownDeferred = GTK_WIDGET_VISIBLE(self->m_widget) != FALSE; + self->m_widget = 0; + return FALSE; + } +public: + GtkWidget* m_widget; + ToggleItem m_item; + + ToggleShown(bool shown) + : m_shownDeferred(shown), m_widget(0), m_item(ActiveCaller(*this)) + { + } + void update() + { + m_item.update(); + } + bool active() const + { + if(m_widget == 0) + { + return m_shownDeferred; + } + else + { + return GTK_WIDGET_VISIBLE(m_widget) != FALSE; + } + } + void exportActive(const BoolImportCallback& importCallback) + { + importCallback(active()); + } + typedef MemberCaller1 ActiveCaller; + void set(bool shown) + { + if(m_widget == 0) + { + m_shownDeferred = shown; + } + else + { + widget_set_visible(m_widget, shown); + } + } + void toggle() + { + widget_toggle_visible(m_widget); + } + typedef MemberCaller ToggleCaller; + void connect(GtkWidget* widget) + { + m_widget = widget; + widget_set_visible(m_widget, m_shownDeferred); + g_signal_connect(G_OBJECT(m_widget), "notify::visible", G_CALLBACK(notify_visible), this); + g_signal_connect(G_OBJECT(m_widget), "destroy", G_CALLBACK(destroy), this); + update(); + } +}; + + +inline void widget_queue_draw(GtkWidget& widget) +{ + gtk_widget_queue_draw(&widget); +} +typedef ReferenceCaller WidgetQueueDrawCaller; + + +inline void widget_make_default(GtkWidget* widget) +{ + GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_DEFAULT); + gtk_widget_grab_default(widget); +} + +class WidgetFocusPrinter +{ + const char* m_name; + + static gboolean focus_in(GtkWidget *widget, GdkEventFocus *event, WidgetFocusPrinter* self) + { + globalOutputStream() << self->m_name << " takes focus\n"; + return FALSE; + } + static gboolean focus_out(GtkWidget *widget, GdkEventFocus *event, WidgetFocusPrinter* self) + { + globalOutputStream() << self->m_name << " loses focus\n"; + return FALSE; + } +public: + WidgetFocusPrinter(const char* name) : m_name(name) + { + } + void connect(GtkWidget* widget) + { + g_signal_connect(G_OBJECT(widget), "focus_in_event", G_CALLBACK(focus_in), this); + g_signal_connect(G_OBJECT(widget), "focus_out_event", G_CALLBACK(focus_out), this); + } +}; + +#endif diff --git a/libs/gtkutil/window.cpp b/libs/gtkutil/window.cpp new file mode 100644 index 00000000..5d465531 --- /dev/null +++ b/libs/gtkutil/window.cpp @@ -0,0 +1,170 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "window.h" + +#include + +#include "pointer.h" +#include "accelerator.h" + +inline void CHECK_RESTORE(GtkWidget* w) +{ + if(gpointer_to_int(g_object_get_data(G_OBJECT(w), "was_mapped")) != 0) + { + gtk_widget_show(w); + } +} + +inline void CHECK_MINIMIZE(GtkWidget* w) +{ + g_object_set_data(G_OBJECT(w), "was_mapped", gint_to_pointer(GTK_WIDGET_VISIBLE(w))); + gtk_widget_hide(w); +} + +static gboolean main_window_iconified(GtkWidget* widget, GdkEventWindowState* event, gpointer data) +{ + if((event->changed_mask & (GDK_WINDOW_STATE_ICONIFIED|GDK_WINDOW_STATE_WITHDRAWN)) != 0) + { + if((event->new_window_state & (GDK_WINDOW_STATE_ICONIFIED|GDK_WINDOW_STATE_WITHDRAWN)) != 0) + { + CHECK_MINIMIZE(GTK_WIDGET(data)); + } + else + { + CHECK_RESTORE(GTK_WIDGET(data)); + } + } + return FALSE; +} + +unsigned int connect_floating(GtkWindow* main_window, GtkWindow* floating) +{ + return g_signal_connect(G_OBJECT(main_window), "window_state_event", G_CALLBACK(main_window_iconified), floating); +} + +gboolean destroy_disconnect_floating(GtkWindow* widget, gpointer data) +{ + g_signal_handler_disconnect(G_OBJECT(data), gpointer_to_int(g_object_get_data(G_OBJECT(widget), "floating_handler"))); + return FALSE; +} + +gboolean floating_window_delete_present(GtkWindow* floating, GdkEventFocus *event, GtkWindow* main_window) +{ + if(gtk_window_is_active(floating) || gtk_window_is_active(main_window)) + { + gtk_window_present(main_window); + } + return FALSE; +} + +guint connect_floating_window_delete_present(GtkWindow* floating, GtkWindow* main_window) +{ + return g_signal_connect(G_OBJECT(floating), "delete_event", G_CALLBACK(floating_window_delete_present), main_window); +} + +gboolean floating_window_destroy_present(GtkWindow* floating, GtkWindow* main_window) +{ + if(gtk_window_is_active(floating) || gtk_window_is_active(main_window)) + { + gtk_window_present(main_window); + } + return FALSE; +} + +guint connect_floating_window_destroy_present(GtkWindow* floating, GtkWindow* main_window) +{ + return g_signal_connect(G_OBJECT(floating), "destroy", G_CALLBACK(floating_window_destroy_present), main_window); +} + +GtkWindow* create_floating_window(const char* title, GtkWindow* parent) +{ + GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + gtk_window_set_title(window, title); + + if(parent != 0) + { + gtk_window_set_transient_for(window, parent); + connect_floating_window_destroy_present(window, parent); + g_object_set_data(G_OBJECT(window), "floating_handler", gint_to_pointer(connect_floating(parent, window))); + g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy_disconnect_floating), parent); + } + + return window; +} + +void destroy_floating_window(GtkWindow* window) +{ + gtk_widget_destroy(GTK_WIDGET(window)); +} + +gint window_realize_remove_sysmenu(GtkWidget* widget, gpointer data) +{ + gdk_window_set_decorations(widget->window, (GdkWMDecoration)(GDK_DECOR_ALL|GDK_DECOR_MENU)); + return FALSE; +} + +gboolean persistent_floating_window_delete(GtkWindow* floating, GdkEvent *event, GtkWindow* main_window) +{ + gtk_widget_hide(GTK_WIDGET(floating)); + return TRUE; +} + +GtkWindow* create_persistent_floating_window(const char* title, GtkWindow* main_window) +{ + GtkWindow* window = GTK_WINDOW(create_floating_window(title, main_window)); + + gtk_widget_set_events(GTK_WIDGET(window), GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK); + + connect_floating_window_delete_present(window, main_window); + g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(persistent_floating_window_delete), 0); + +#if 0 + if(g_multimon_globals.m_bStartOnPrimMon && g_multimon_globals.m_bNoSysMenuPopups) + g_signal_connect(G_OBJECT(window), "realize", G_CALLBACK(window_realize_remove_sysmenu), 0); +#endif + + return window; +} + +gint window_realize_remove_minmax(GtkWidget* widget, gpointer data) +{ + gdk_window_set_decorations(widget->window, (GdkWMDecoration)(GDK_DECOR_ALL|GDK_DECOR_MINIMIZE|GDK_DECOR_MAXIMIZE)); + return FALSE; +} + +void window_remove_minmax(GtkWindow* window) +{ + g_signal_connect(G_OBJECT(window), "realize", G_CALLBACK(window_realize_remove_minmax), 0); +} + + +GtkScrolledWindow* create_scrolled_window(GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy, int border) +{ + GtkScrolledWindow* scr = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(0, 0)); + gtk_widget_show(GTK_WIDGET(scr)); + gtk_scrolled_window_set_policy(scr, hscrollbar_policy, vscrollbar_policy); + gtk_scrolled_window_set_shadow_type(scr, GTK_SHADOW_IN); + gtk_container_set_border_width(GTK_CONTAINER(scr), border); + return scr; +} + + diff --git a/libs/gtkutil/window.h b/libs/gtkutil/window.h new file mode 100644 index 00000000..5ab1e500 --- /dev/null +++ b/libs/gtkutil/window.h @@ -0,0 +1,174 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GTKUTIL_WINDOW_H) +#define INCLUDED_GTKUTIL_WINDOW_H + +#include + +#include "debugging/debugging.h" +#include "generic/callback.h" +#include "widget.h" + +inline gboolean window_focus_in_clear_focus_widget(GtkWidget* widget, GdkEventKey* event, gpointer data) +{ + gtk_window_set_focus(GTK_WINDOW(widget), NULL); + return FALSE; +} + +inline guint window_connect_focus_in_clear_focus_widget(GtkWindow* window) +{ + return g_signal_connect(G_OBJECT(window), "focus_in_event", G_CALLBACK(window_focus_in_clear_focus_widget), NULL); +} + + +unsigned int connect_floating(GtkWindow* main_window, GtkWindow* floating); +GtkWindow* create_floating_window(const char* title, GtkWindow* parent); +void destroy_floating_window(GtkWindow* window); + +GtkWindow* create_persistent_floating_window(const char* title, GtkWindow* main_window); +gboolean persistent_floating_window_delete(GtkWindow* floating, GdkEvent *event, GtkWindow* main_window); + +void window_remove_minmax(GtkWindow* window); + +typedef struct _GtkScrolledWindow GtkScrolledWindow; +GtkScrolledWindow* create_scrolled_window(GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy, int border = 0); + + +struct WindowPosition +{ + int x, y, w, h; + + WindowPosition() + { + } + WindowPosition(int _x, int _y, int _w, int _h) + : x(_x), y(_y), w(_w), h(_h) + { + } +}; + +const WindowPosition c_default_window_pos(50, 25, 400, 300); + +inline void window_get_position(GtkWindow* window, WindowPosition& position) +{ + ASSERT_MESSAGE(window != 0, "error saving window position"); + + gtk_window_get_position(window, &position.x, &position.y); + gtk_window_get_size(window, &position.w, &position.h); +} + +inline void window_set_position(GtkWindow* window, const WindowPosition& position) +{ + gtk_window_set_gravity(window, GDK_GRAVITY_STATIC); + + GdkScreen* screen = gdk_screen_get_default(); + if(position.x < 0 + || position.y < 0 + || position.x > gdk_screen_get_width(screen) + || position.y > gdk_screen_get_height(screen)) + { + gtk_window_set_position(window, GTK_WIN_POS_CENTER_ON_PARENT); + } + else + { + gtk_window_move(window, position.x, position.y); + } + + gtk_window_set_default_size(window, position.w, position.h); +} + +inline void WindowPosition_Parse(WindowPosition& position, const char* value) +{ + if(sscanf(value, "%d %d %d %d", &position.x, &position.y, &position.w, &position.h) != 4) + { + position = WindowPosition(c_default_window_pos); // ensure sane default value for window position + } +} +typedef ReferenceCaller1 WindowPositionImportStringCaller; + +inline void WindowPosition_Write(const WindowPosition& position, const StringImportCallback& importCallback) +{ + char buffer[64]; + sprintf(buffer, "%d %d %d %d", position.x, position.y, position.w, position.h); + importCallback(buffer); +} +typedef ConstReferenceCaller1 WindowPositionExportStringCaller; + + + +class WindowPositionTracker +{ + WindowPosition m_position; + + static gboolean configure(GtkWidget* widget, GdkEventConfigure *event, WindowPositionTracker* self) + { + self->m_position = WindowPosition(event->x, event->y, event->width, event->height); + return FALSE; + } + +public: + WindowPositionTracker() + : m_position(c_default_window_pos) + { + } + + void sync(GtkWindow* window) + { + window_set_position(window, m_position); + } + + void connect(GtkWindow* window) + { + sync(window); + g_signal_connect(G_OBJECT(window), "configure_event", G_CALLBACK(configure), this); + } + + const WindowPosition& getPosition() const + { + return m_position; + } + + //hack + void setPosition(const WindowPosition& position) + { + m_position = position; + } +}; + + +inline void WindowPositionTracker_importString(WindowPositionTracker& self, const char* value) +{ + WindowPosition position; + WindowPosition_Parse(position, value); + self.setPosition(position); +} +typedef ReferenceCaller1 WindowPositionTrackerImportStringCaller; + +inline void WindowPositionTracker_exportString(const WindowPositionTracker& self, const StringImportCallback& importer) +{ + WindowPosition_Write(self.getPosition(), importer); +} +typedef ConstReferenceCaller1 WindowPositionTrackerExportStringCaller; + + + +#endif diff --git a/libs/gtkutil/xorrectangle.cpp b/libs/gtkutil/xorrectangle.cpp new file mode 100644 index 00000000..d968337d --- /dev/null +++ b/libs/gtkutil/xorrectangle.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "xorrectangle.h" + diff --git a/libs/gtkutil/xorrectangle.h b/libs/gtkutil/xorrectangle.h new file mode 100644 index 00000000..0d89203e --- /dev/null +++ b/libs/gtkutil/xorrectangle.h @@ -0,0 +1,125 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_XORRECTANGLE_H) +#define INCLUDED_XORRECTANGLE_H + +#include +#include "math/vector.h" + +class rectangle_t +{ +public: + rectangle_t() + : x(0), y(0), w(0), h(0) + {} + rectangle_t(float _x, float _y, float _w, float _h) + : x(_x), y(_y), w(_w), h(_h) + {} + float x; + float y; + float w; + float h; +}; + +struct Coord2D +{ + float x, y; + Coord2D(float _x, float _y) + : x(_x), y(_y) + { + } +}; + +inline Coord2D coord2d_device2screen(const Coord2D& coord, unsigned int width, unsigned int height) +{ + return Coord2D(((coord.x + 1.0f) * 0.5f) * width, ((coord.y + 1.0f) * 0.5f) * height); +} + +inline rectangle_t rectangle_from_area(const float min[2], const float max[2], unsigned int width, unsigned int height) +{ + Coord2D botleft(coord2d_device2screen(Coord2D(min[0], min[1]), width, height)); + Coord2D topright(coord2d_device2screen(Coord2D(max[0], max[1]), width, height)); + return rectangle_t(botleft.x, botleft.y, topright.x - botleft.x, topright.y - botleft.y); +} + +class XORRectangle +{ + + rectangle_t m_rectangle; + + GtkWidget* m_widget; + GdkGC* m_gc; + + bool initialised() const + { + return m_gc != 0; + } + void lazy_init() + { + if(!initialised()) + { + m_gc = gdk_gc_new(m_widget->window); + + GdkColor color = { 0, 0xffff, 0xffff, 0xffff, }; + GdkColormap* colormap = gdk_window_get_colormap(m_widget->window); + gdk_colormap_alloc_color (colormap, &color, FALSE, TRUE); + gdk_gc_copy(m_gc, m_widget->style->white_gc); + gdk_gc_set_foreground(m_gc, &color); + gdk_gc_set_background(m_gc, &color); + + gdk_gc_set_function(m_gc, GDK_INVERT); + } + } + void draw() const + { + const int x = float_to_integer(m_rectangle.x); + const int y = float_to_integer(m_rectangle.y); + const int w = float_to_integer(m_rectangle.w); + const int h = float_to_integer(m_rectangle.h); + gdk_draw_rectangle(m_widget->window, m_gc, FALSE, x, -(h) - (y - m_widget->allocation.height), w, h); + } + +public: + XORRectangle(GtkWidget* widget) : m_widget(widget), m_gc(0) + { + } + ~XORRectangle() + { + if(initialised()) + { + gdk_gc_unref(m_gc); + } + } + void set(rectangle_t rectangle) + { + if(GTK_WIDGET_REALIZED(m_widget)) + { + lazy_init(); + draw(); + m_rectangle = rectangle; + draw(); + } + } +}; + + +#endif diff --git a/libs/imagelib.cpp b/libs/imagelib.cpp new file mode 100644 index 00000000..5c205f63 --- /dev/null +++ b/libs/imagelib.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "imagelib.h" + diff --git a/libs/imagelib.h b/libs/imagelib.h new file mode 100644 index 00000000..b5f95b7d --- /dev/null +++ b/libs/imagelib.h @@ -0,0 +1,152 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_IMAGELIB_H) +#define INCLUDED_IMAGELIB_H + +#include "iimage.h" +#include "iarchive.h" +#include "idatastream.h" +#include + +struct RGBAPixel +{ + unsigned char red, green, blue, alpha; +}; + +class RGBAImage : public Image +{ + RGBAImage(const RGBAImage& other); + RGBAImage& operator=(const RGBAImage& other); +public: + RGBAPixel* pixels; + unsigned int width, height; + + RGBAImage(unsigned int _width, unsigned int _height) + : pixels(new RGBAPixel[_width * _height]), width(_width), height(_height) + { + } + ~RGBAImage() + { + delete pixels; + } + + void release() + { + delete this; + } + byte* getRGBAPixels() const + { + return reinterpret_cast(pixels); + } + unsigned int getWidth() const + { + return width; + } + unsigned int getHeight() const + { + return height; + } +}; + +class RGBAImageFlags : public RGBAImage +{ +public: + int m_surfaceFlags; + int m_contentFlags; + int m_value; + RGBAImageFlags(unsigned short _width, unsigned short _height, int surfaceFlags, int contentFlags, int value) : + RGBAImage(_width, _height), m_surfaceFlags(surfaceFlags), m_contentFlags(contentFlags), m_value(value) + { + } + + int getSurfaceFlags() const + { + return m_surfaceFlags; + } + int getContentFlags() const + { + return m_contentFlags; + } + int getValue() const + { + return m_value; + } +}; + + +inline InputStream::byte_type* ArchiveFile_loadBuffer(ArchiveFile& file, std::size_t& length) +{ + InputStream::byte_type* buffer = (InputStream::byte_type*)malloc(file.size() + 1); + length = file.getInputStream().read(buffer, file.size()); + buffer[file.size()] = 0; + return buffer; +} + +inline void ArchiveFile_freeBuffer(InputStream::byte_type* buffer) +{ + free(buffer); +} + +class ScopedArchiveBuffer +{ +public: + std::size_t length; + InputStream::byte_type* buffer; + + ScopedArchiveBuffer(ArchiveFile& file) + { + buffer = ArchiveFile_loadBuffer(file, length); + } + ~ScopedArchiveBuffer() + { + ArchiveFile_freeBuffer(buffer); + } +}; + +class PointerInputStream : public InputStream +{ + const byte* m_read; +public: + PointerInputStream(const byte* pointer) + : m_read(pointer) + { + } + std::size_t read(byte* buffer, std::size_t length) + { + const byte* end = m_read + length; + while(m_read != end) + { + *buffer++ = *m_read++; + } + return length; + } + void seek(std::size_t offset) + { + m_read += offset; + } + const byte* get() + { + return m_read; + } +}; + +#endif diff --git a/libs/instancelib.cpp b/libs/instancelib.cpp new file mode 100644 index 00000000..053ac73b --- /dev/null +++ b/libs/instancelib.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "instancelib.h" diff --git a/libs/instancelib.h b/libs/instancelib.h new file mode 100644 index 00000000..9ff32e6d --- /dev/null +++ b/libs/instancelib.h @@ -0,0 +1,193 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_INSTANCELIB_H) +#define INCLUDED_INSTANCELIB_H + +#include "debugging/debugging.h" + +#include "iscenegraph.h" + +#include "scenelib.h" +#include "generic/reference.h" +#include "generic/callback.h" +#include + +class InstanceSubgraphWalker : public scene::Traversable::Walker +{ + scene::Instantiable::Observer* m_observer; + mutable scene::Path m_path; + mutable Stack m_parent; +public: + InstanceSubgraphWalker(scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* parent) + : m_observer(observer), m_path(path), m_parent(parent) + { + } + bool pre(scene::Node& node) const + { + m_path.push(makeReference(node)); + scene::Instance* instance = Node_getInstantiable(node)->create(m_path, m_parent.top()); + m_observer->insert(instance); + Node_getInstantiable(node)->insert(m_observer, m_path, instance); + m_parent.push(instance); + return true; + } + void post(scene::Node& node) const + { + m_path.pop(); + m_parent.pop(); + } +}; + +class UninstanceSubgraphWalker : public scene::Traversable::Walker +{ + scene::Instantiable::Observer* m_observer; + mutable scene::Path m_path; +public: + UninstanceSubgraphWalker(scene::Instantiable::Observer* observer, const scene::Path& parent) + : m_observer(observer), m_path(parent) + { + } + bool pre(scene::Node& node) const + { + m_path.push(makeReference(node)); + return true; + } + void post(scene::Node& node) const + { + scene::Instance* instance = Node_getInstantiable(node)->erase(m_observer, m_path); + m_observer->erase(instance); + delete instance; + m_path.pop(); + } +}; + +class InstanceSet : public scene::Traversable::Observer +{ + typedef std::pair CachePath; + + typedef CachePath key_type; + + typedef std::map InstanceMap; + InstanceMap m_instances; +public: + + typedef InstanceMap::iterator iterator; + + iterator begin() + { + return m_instances.begin(); + } + iterator end() + { + return m_instances.end(); + } + + // traverse observer + void insert(scene::Node& child) + { + for(iterator i = begin(); i != end(); ++i) + { + Node_traverseSubgraph(child, InstanceSubgraphWalker((*i).first.first, (*i).first.second, (*i).second)); + (*i).second->boundsChanged(); + } + } + void erase(scene::Node& child) + { + for(iterator i = begin(); i != end(); ++i) + { + Node_traverseSubgraph(child, UninstanceSubgraphWalker((*i).first.first, (*i).first.second)); + (*i).second->boundsChanged(); + } + } + + // instance + void forEachInstance(const scene::Instantiable::Visitor& visitor) + { + for(iterator i = begin(); i != end(); ++i) + { + visitor.visit(*(*i).second); + } + } + + void insert(scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance) + { + ASSERT_MESSAGE(m_instances.find(key_type(observer, PathConstReference(instance->path()))) == m_instances.end(), "InstanceSet::insert - element already exists"); + m_instances.insert(InstanceMap::value_type(key_type(observer, PathConstReference(instance->path())), instance)); + } + scene::Instance* erase(scene::Instantiable::Observer* observer, const scene::Path& path) + { + ASSERT_MESSAGE(m_instances.find(key_type(observer, PathConstReference(path))) != m_instances.end(), "InstanceSet::erase - failed to find element"); + InstanceMap::iterator i = m_instances.find(key_type(observer, PathConstReference(path))); + scene::Instance* instance = i->second; + m_instances.erase(i); + return instance; + } + + void transformChanged() + { + for(InstanceMap::iterator i = m_instances.begin(); i != m_instances.end(); ++i) + { + (*i).second->transformChanged(); + } + } + typedef MemberCaller TransformChangedCaller; + void boundsChanged() + { + for(InstanceMap::iterator i = m_instances.begin(); i != m_instances.end(); ++i) + { + (*i).second->boundsChanged(); + } + } + typedef MemberCaller BoundsChangedCaller; +}; + +template +inline void InstanceSet_forEach(InstanceSet& instances, const Functor& functor) +{ + for(InstanceSet::iterator i = instances.begin(), end = instances.end(); i != end; ++i) + { + functor(*(*i).second); + } +} + +template +class InstanceEvaluateTransform +{ +public: + inline void operator()(scene::Instance& instance) const + { + InstanceTypeCast::cast(instance)->evaluateTransform(); + } +}; + +template +class InstanceSetEvaluateTransform +{ +public: + static void apply(InstanceSet& instances) + { + InstanceSet_forEach(instances, InstanceEvaluateTransform()); + } + typedef ReferenceCaller::apply> Caller; +}; + +#endif diff --git a/libs/jpeg6/.cvsignore b/libs/jpeg6/.cvsignore new file mode 100644 index 00000000..face6ad3 --- /dev/null +++ b/libs/jpeg6/.cvsignore @@ -0,0 +1,8 @@ +Debug +Release +*.ncb +*.opt +*.plg +*.001 +*.BAK +.consign diff --git a/libs/jpeg6/.cvswrappers b/libs/jpeg6/.cvswrappers new file mode 100644 index 00000000..cdfd6d4a --- /dev/null +++ b/libs/jpeg6/.cvswrappers @@ -0,0 +1,3 @@ +*.dsp -m 'COPY' -k 'b' +*.dsw -m 'COPY' -k 'b' +*.scc -m 'COPY' -k 'b' diff --git a/libs/jpeg6/README b/libs/jpeg6/README new file mode 100644 index 00000000..86cc2066 --- /dev/null +++ b/libs/jpeg6/README @@ -0,0 +1,385 @@ +The Independent JPEG Group's JPEG software +========================================== + +README for release 6b of 27-Mar-1998 +==================================== + +This distribution contains the sixth public release of the Independent JPEG +Group's free JPEG software. You are welcome to redistribute this software and +to use it for any purpose, subject to the conditions under LEGAL ISSUES, below. + +Serious users of this software (particularly those incorporating it into +larger programs) should contact IJG at jpeg-info@uunet.uu.net to be added to +our electronic mailing list. Mailing list members are notified of updates +and have a chance to participate in technical discussions, etc. + +This software is the work of Tom Lane, Philip Gladstone, Jim Boucher, +Lee Crocker, Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi, +Guido Vollbeding, Ge' Weijers, and other members of the Independent JPEG +Group. + +IJG is not affiliated with the official ISO JPEG standards committee. + + +DOCUMENTATION ROADMAP +===================== + +This file contains the following sections: + +OVERVIEW General description of JPEG and the IJG software. +LEGAL ISSUES Copyright, lack of warranty, terms of distribution. +REFERENCES Where to learn more about JPEG. +ARCHIVE LOCATIONS Where to find newer versions of this software. +RELATED SOFTWARE Other stuff you should get. +FILE FORMAT WARS Software *not* to get. +TO DO Plans for future IJG releases. + +Other documentation files in the distribution are: + +User documentation: + install.doc How to configure and install the IJG software. + usage.doc Usage instructions for cjpeg, djpeg, jpegtran, + rdjpgcom, and wrjpgcom. + *.1 Unix-style man pages for programs (same info as usage.doc). + wizard.doc Advanced usage instructions for JPEG wizards only. + change.log Version-to-version change highlights. +Programmer and internal documentation: + libjpeg.doc How to use the JPEG library in your own programs. + example.c Sample code for calling the JPEG library. + structure.doc Overview of the JPEG library's internal structure. + filelist.doc Road map of IJG files. + coderules.doc Coding style rules --- please read if you contribute code. + +Please read at least the files install.doc and usage.doc. Useful information +can also be found in the JPEG FAQ (Frequently Asked Questions) article. See +ARCHIVE LOCATIONS below to find out where to obtain the FAQ article. + +If you want to understand how the JPEG code works, we suggest reading one or +more of the REFERENCES, then looking at the documentation files (in roughly +the order listed) before diving into the code. + + +OVERVIEW +======== + +This package contains C software to implement JPEG image compression and +decompression. JPEG (pronounced "jay-peg") is a standardized compression +method for full-color and gray-scale images. JPEG is intended for compressing +"real-world" scenes; line drawings, cartoons and other non-realistic images +are not its strong suit. JPEG is lossy, meaning that the output image is not +exactly identical to the input image. Hence you must not use JPEG if you +have to have identical output bits. However, on typical photographic images, +very good compression levels can be obtained with no visible change, and +remarkably high compression levels are possible if you can tolerate a +low-quality image. For more details, see the references, or just experiment +with various compression settings. + +This software implements JPEG baseline, extended-sequential, and progressive +compression processes. Provision is made for supporting all variants of these +processes, although some uncommon parameter settings aren't implemented yet. +For legal reasons, we are not distributing code for the arithmetic-coding +variants of JPEG; see LEGAL ISSUES. We have made no provision for supporting +the hierarchical or lossless processes defined in the standard. + +We provide a set of library routines for reading and writing JPEG image files, +plus two sample applications "cjpeg" and "djpeg", which use the library to +perform conversion between JPEG and some other popular image file formats. +The library is intended to be reused in other applications. + +In order to support file conversion and viewing software, we have included +considerable functionality beyond the bare JPEG coding/decoding capability; +for example, the color quantization modules are not strictly part of JPEG +decoding, but they are essential for output to colormapped file formats or +colormapped displays. These extra functions can be compiled out of the +library if not required for a particular application. We have also included +"jpegtran", a utility for lossless transcoding between different JPEG +processes, and "rdjpgcom" and "wrjpgcom", two simple applications for +inserting and extracting textual comments in JFIF files. + +The emphasis in designing this software has been on achieving portability and +flexibility, while also making it fast enough to be useful. In particular, +the software is not intended to be read as a tutorial on JPEG. (See the +REFERENCES section for introductory material.) Rather, it is intended to +be reliable, portable, industrial-strength code. We do not claim to have +achieved that goal in every aspect of the software, but we strive for it. + +We welcome the use of this software as a component of commercial products. +No royalty is required, but we do ask for an acknowledgement in product +documentation, as described under LEGAL ISSUES. + + +LEGAL ISSUES +============ + +In plain English: + +1. We don't promise that this software works. (But if you find any bugs, + please let us know!) +2. You can use this software for whatever you want. You don't have to pay us. +3. You may not pretend that you wrote this software. If you use it in a + program, you must acknowledge somewhere in your documentation that + you've used the IJG code. + +In legalese: + +The authors make NO WARRANTY or representation, either express or implied, +with respect to this software, its quality, accuracy, merchantability, or +fitness for a particular purpose. This software is provided "AS IS", and you, +its user, assume the entire risk as to its quality and accuracy. + +This software is copyright (C) 1991-1998, Thomas G. Lane. +All Rights Reserved except as specified below. + +Permission is hereby granted to use, copy, modify, and distribute this +software (or portions thereof) for any purpose, without fee, subject to these +conditions: +(1) If any part of the source code for this software is distributed, then this +README file must be included, with this copyright and no-warranty notice +unaltered; and any additions, deletions, or changes to the original files +must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying +documentation must state that "this software is based in part on the work of +the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts +full responsibility for any undesirable consequences; the authors accept +NO LIABILITY for damages of any kind. + +These conditions apply to any software derived from or based on the IJG code, +not just to the unmodified library. If you use our work, you ought to +acknowledge us. + +Permission is NOT granted for the use of any IJG author's name or company name +in advertising or publicity relating to this software or products derived from +it. This software may be referred to only as "the Independent JPEG Group's +software". + +We specifically permit and encourage the use of this software as the basis of +commercial products, provided that all warranty or liability claims are +assumed by the product vendor. + + +ansi2knr.c is included in this distribution by permission of L. Peter Deutsch, +sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA. +ansi2knr.c is NOT covered by the above copyright and conditions, but instead +by the usual distribution terms of the Free Software Foundation; principally, +that you must include source code if you redistribute it. (See the file +ansi2knr.c for full details.) However, since ansi2knr.c is not needed as part +of any program generated from the IJG code, this does not limit you more than +the foregoing paragraphs do. + +The Unix configuration script "configure" was produced with GNU Autoconf. +It is copyright by the Free Software Foundation but is freely distributable. +The same holds for its supporting scripts (config.guess, config.sub, +ltconfig, ltmain.sh). Another support script, install-sh, is copyright +by M.I.T. but is also freely distributable. + +It appears that the arithmetic coding option of the JPEG spec is covered by +patents owned by IBM, AT&T, and Mitsubishi. Hence arithmetic coding cannot +legally be used without obtaining one or more licenses. For this reason, +support for arithmetic coding has been removed from the free JPEG software. +(Since arithmetic coding provides only a marginal gain over the unpatented +Huffman mode, it is unlikely that very many implementations will support it.) +So far as we are aware, there are no patent restrictions on the remaining +code. + +The IJG distribution formerly included code to read and write GIF files. +To avoid entanglement with the Unisys LZW patent, GIF reading support has +been removed altogether, and the GIF writer has been simplified to produce +"uncompressed GIFs". This technique does not use the LZW algorithm; the +resulting GIF files are larger than usual, but are readable by all standard +GIF decoders. + +We are required to state that + "The Graphics Interchange Format(c) is the Copyright property of + CompuServe Incorporated. GIF(sm) is a Service Mark property of + CompuServe Incorporated." + + +REFERENCES +========== + +We highly recommend reading one or more of these references before trying to +understand the innards of the JPEG software. + +The best short technical introduction to the JPEG compression algorithm is + Wallace, Gregory K. "The JPEG Still Picture Compression Standard", + Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44. +(Adjacent articles in that issue discuss MPEG motion picture compression, +applications of JPEG, and related topics.) If you don't have the CACM issue +handy, a PostScript file containing a revised version of Wallace's article is +available at ftp://ftp.uu.net/graphics/jpeg/wallace.ps.gz. The file (actually +a preprint for an article that appeared in IEEE Trans. Consumer Electronics) +omits the sample images that appeared in CACM, but it includes corrections +and some added material. Note: the Wallace article is copyright ACM and IEEE, +and it may not be used for commercial purposes. + +A somewhat less technical, more leisurely introduction to JPEG can be found in +"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by +M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1. This book provides +good explanations and example C code for a multitude of compression methods +including JPEG. It is an excellent source if you are comfortable reading C +code but don't know much about data compression in general. The book's JPEG +sample code is far from industrial-strength, but when you are ready to look +at a full implementation, you've got one here... + +The best full description of JPEG is the textbook "JPEG Still Image Data +Compression Standard" by William B. Pennebaker and Joan L. Mitchell, published +by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1. Price US$59.95, 638 pp. +The book includes the complete text of the ISO JPEG standards (DIS 10918-1 +and draft DIS 10918-2). This is by far the most complete exposition of JPEG +in existence, and we highly recommend it. + +The JPEG standard itself is not available electronically; you must order a +paper copy through ISO or ITU. (Unless you feel a need to own a certified +official copy, we recommend buying the Pennebaker and Mitchell book instead; +it's much cheaper and includes a great deal of useful explanatory material.) +In the USA, copies of the standard may be ordered from ANSI Sales at (212) +642-4900, or from Global Engineering Documents at (800) 854-7179. (ANSI +doesn't take credit card orders, but Global does.) It's not cheap: as of +1992, ANSI was charging $95 for Part 1 and $47 for Part 2, plus 7% +shipping/handling. The standard is divided into two parts, Part 1 being the +actual specification, while Part 2 covers compliance testing methods. Part 1 +is titled "Digital Compression and Coding of Continuous-tone Still Images, +Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS +10918-1, ITU-T T.81. Part 2 is titled "Digital Compression and Coding of +Continuous-tone Still Images, Part 2: Compliance testing" and has document +numbers ISO/IEC IS 10918-2, ITU-T T.83. + +Some extensions to the original JPEG standard are defined in JPEG Part 3, +a newer ISO standard numbered ISO/IEC IS 10918-3 and ITU-T T.84. IJG +currently does not support any Part 3 extensions. + +The JPEG standard does not specify all details of an interchangeable file +format. For the omitted details we follow the "JFIF" conventions, revision +1.02. A copy of the JFIF spec is available from: + Literature Department + C-Cube Microsystems, Inc. + 1778 McCarthy Blvd. + Milpitas, CA 95035 + phone (408) 944-6300, fax (408) 944-6314 +A PostScript version of this document is available by FTP at +ftp://ftp.uu.net/graphics/jpeg/jfif.ps.gz. There is also a plain text +version at ftp://ftp.uu.net/graphics/jpeg/jfif.txt.gz, but it is missing +the figures. + +The TIFF 6.0 file format specification can be obtained by FTP from +ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz. The JPEG incorporation scheme +found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems. +IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6). +Instead, we recommend the JPEG design proposed by TIFF Technical Note #2 +(Compression tag 7). Copies of this Note can be obtained from ftp.sgi.com or +from ftp://ftp.uu.net/graphics/jpeg/. It is expected that the next revision +of the TIFF spec will replace the 6.0 JPEG design with the Note's design. +Although IJG's own code does not support TIFF/JPEG, the free libtiff library +uses our library to implement TIFF/JPEG per the Note. libtiff is available +from ftp://ftp.sgi.com/graphics/tiff/. + + +ARCHIVE LOCATIONS +================= + +The "official" archive site for this software is ftp.uu.net (Internet +address 192.48.96.9). The most recent released version can always be found +there in directory graphics/jpeg. This particular version will be archived +as ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz. If you don't have +direct Internet access, UUNET's archives are also available via UUCP; contact +help@uunet.uu.net for information on retrieving files that way. + +Numerous Internet sites maintain copies of the UUNET files. However, only +ftp.uu.net is guaranteed to have the latest official version. + +You can also obtain this software in DOS-compatible "zip" archive format from +the SimTel archives (ftp://ftp.simtel.net/pub/simtelnet/msdos/graphics/), or +on CompuServe in the Graphics Support forum (GO CIS:GRAPHSUP), library 12 +"JPEG Tools". Again, these versions may sometimes lag behind the ftp.uu.net +release. + +The JPEG FAQ (Frequently Asked Questions) article is a useful source of +general information about JPEG. It is updated constantly and therefore is +not included in this distribution. The FAQ is posted every two weeks to +Usenet newsgroups comp.graphics.misc, news.answers, and other groups. +It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/ +and other news.answers archive sites, including the official news.answers +archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/. +If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu +with body + send usenet/news.answers/jpeg-faq/part1 + send usenet/news.answers/jpeg-faq/part2 + + +RELATED SOFTWARE +================ + +Numerous viewing and image manipulation programs now support JPEG. (Quite a +few of them use this library to do so.) The JPEG FAQ described above lists +some of the more popular free and shareware viewers, and tells where to +obtain them on Internet. + +If you are on a Unix machine, we highly recommend Jef Poskanzer's free +PBMPLUS software, which provides many useful operations on PPM-format image +files. In particular, it can convert PPM images to and from a wide range of +other formats, thus making cjpeg/djpeg considerably more useful. The latest +version is distributed by the NetPBM group, and is available from numerous +sites, notably ftp://wuarchive.wustl.edu/graphics/graphics/packages/NetPBM/. +Unfortunately PBMPLUS/NETPBM is not nearly as portable as the IJG software is; +you are likely to have difficulty making it work on any non-Unix machine. + +A different free JPEG implementation, written by the PVRG group at Stanford, +is available from ftp://havefun.stanford.edu/pub/jpeg/. This program +is designed for research and experimentation rather than production use; +it is slower, harder to use, and less portable than the IJG code, but it +is easier to read and modify. Also, the PVRG code supports lossless JPEG, +which we do not. (On the other hand, it doesn't do progressive JPEG.) + + +FILE FORMAT WARS +================ + +Some JPEG programs produce files that are not compatible with our library. +The root of the problem is that the ISO JPEG committee failed to specify a +concrete file format. Some vendors "filled in the blanks" on their own, +creating proprietary formats that no one else could read. (For example, none +of the early commercial JPEG implementations for the Macintosh were able to +exchange compressed files.) + +The file format we have adopted is called JFIF (see REFERENCES). This format +has been agreed to by a number of major commercial JPEG vendors, and it has +become the de facto standard. JFIF is a minimal or "low end" representation. +We recommend the use of TIFF/JPEG (TIFF revision 6.0 as modified by TIFF +Technical Note #2) for "high end" applications that need to record a lot of +additional data about an image. TIFF/JPEG is fairly new and not yet widely +supported, unfortunately. + +The upcoming JPEG Part 3 standard defines a file format called SPIFF. +SPIFF is interoperable with JFIF, in the sense that most JFIF decoders should +be able to read the most common variant of SPIFF. SPIFF has some technical +advantages over JFIF, but its major claim to fame is simply that it is an +official standard rather than an informal one. At this point it is unclear +whether SPIFF will supersede JFIF or whether JFIF will remain the de-facto +standard. IJG intends to support SPIFF once the standard is frozen, but we +have not decided whether it should become our default output format or not. +(In any case, our decoder will remain capable of reading JFIF indefinitely.) + +Various proprietary file formats incorporating JPEG compression also exist. +We have little or no sympathy for the existence of these formats. Indeed, +one of the original reasons for developing this free software was to help +force convergence on common, open format standards for JPEG files. Don't +use a proprietary file format! + + +TO DO +===== + +The major thrust for v7 will probably be improvement of visual quality. +The current method for scaling the quantization tables is known not to be +very good at low Q values. We also intend to investigate block boundary +smoothing, "poor man's variable quantization", and other means of improving +quality-vs-file-size performance without sacrificing compatibility. + +In future versions, we are considering supporting some of the upcoming JPEG +Part 3 extensions --- principally, variable quantization and the SPIFF file +format. + +As always, speeding things up is of great interest. + +Please send bug reports, offers of help, etc. to jpeg-info@uunet.uu.net. diff --git a/libs/jpeg6/jchuff.h b/libs/jpeg6/jchuff.h new file mode 100644 index 00000000..f43d571d --- /dev/null +++ b/libs/jpeg6/jchuff.h @@ -0,0 +1,34 @@ +/* + * jchuff.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy encoding routines + * that are shared between the sequential encoder (jchuff.c) and the + * progressive encoder (jcphuff.c). No other modules need to see these. + */ + +/* Derived data constructed for each Huffman table */ + +typedef struct { + unsigned int ehufco[256]; /* code for each symbol */ + char ehufsi[256]; /* length of code for each symbol */ + /* If no code has been allocated for a symbol S, ehufsi[S] contains 0 */ +} c_derived_tbl; + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_c_derived_tbl jMkCDerived +#define jpeg_gen_optimal_table jGenOptTbl +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Expand a Huffman table definition into the derived format */ +EXTERN void jpeg_make_c_derived_tbl JPP((j_compress_ptr cinfo, + JHUFF_TBL * htbl, c_derived_tbl ** pdtbl)); + +/* Generate an optimal table definition given the specified counts */ +EXTERN void jpeg_gen_optimal_table JPP((j_compress_ptr cinfo, + JHUFF_TBL * htbl, long freq[])); diff --git a/libs/jpeg6/jcomapi.cpp b/libs/jpeg6/jcomapi.cpp new file mode 100644 index 00000000..df3302cb --- /dev/null +++ b/libs/jpeg6/jcomapi.cpp @@ -0,0 +1,94 @@ +/* + * jcomapi.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface routines that are used for both + * compression and decompression. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +/* + * Abort processing of a JPEG compression or decompression operation, + * but don't destroy the object itself. + * + * For this, we merely clean up all the nonpermanent memory pools. + * Note that temp files (virtual arrays) are not allowed to belong to + * the permanent pool, so we will be able to close all temp files here. + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL void +jpeg_abort (j_common_ptr cinfo) +{ + int pool; + + /* Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for (pool = JPOOL_NUMPOOLS-1; pool > JPOOL_PERMANENT; pool--) { + (*cinfo->mem->free_pool) (cinfo, pool); + } + + /* Reset overall state for possible reuse of object */ + cinfo->global_state = (cinfo->is_decompressor ? DSTATE_START : CSTATE_START); +} + + +/* + * Destruction of a JPEG object. + * + * Everything gets deallocated except the master jpeg_compress_struct itself + * and the error manager struct. Both of these are supplied by the application + * and must be freed, if necessary, by the application. (Often they are on + * the stack and so don't need to be freed anyway.) + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL void +jpeg_destroy (j_common_ptr cinfo) +{ + /* We need only tell the memory manager to release everything. */ + /* NB: mem pointer is NULL if memory mgr failed to initialize. */ + if (cinfo->mem != NULL) + (*cinfo->mem->self_destruct) (cinfo); + cinfo->mem = NULL; /* be safe if jpeg_destroy is called twice */ + cinfo->global_state = 0; /* mark it destroyed */ +} + + +/* + * Convenience routines for allocating quantization and Huffman tables. + * (Would jutils.c be a more reasonable place to put these?) + */ + +GLOBAL JQUANT_TBL * +jpeg_alloc_quant_table (j_common_ptr cinfo) +{ + JQUANT_TBL *tbl; + + tbl = (JQUANT_TBL *) + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JQUANT_TBL)); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} + + +GLOBAL JHUFF_TBL * +jpeg_alloc_huff_table (j_common_ptr cinfo) +{ + JHUFF_TBL *tbl; + + tbl = (JHUFF_TBL *) + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JHUFF_TBL)); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} diff --git a/libs/jpeg6/jconfig.h b/libs/jpeg6/jconfig.h new file mode 100644 index 00000000..7d2f733b --- /dev/null +++ b/libs/jpeg6/jconfig.h @@ -0,0 +1,41 @@ +/* jconfig.wat --- jconfig.h for Watcom C/C++ on MS-DOS or OS/2. */ +/* see jconfig.doc for explanations */ + +#define HAVE_PROTOTYPES +#define HAVE_UNSIGNED_CHAR +#define HAVE_UNSIGNED_SHORT +/* #define void char */ +/* #define const */ +#define CHAR_IS_UNSIGNED +#define HAVE_STDDEF_H +#define HAVE_STDLIB_H +#undef NEED_BSD_STRINGS +#undef NEED_SYS_TYPES_H +#undef NEED_FAR_POINTERS /* Watcom uses flat 32-bit addressing */ +#undef NEED_SHORT_EXTERNAL_NAMES +#undef INCOMPLETE_TYPES_BROKEN + +#define JDCT_DEFAULT JDCT_FLOAT +#define JDCT_FASTEST JDCT_FLOAT + +#ifdef JPEG_INTERNALS + +#undef RIGHT_SHIFT_IS_UNSIGNED + +#endif /* JPEG_INTERNALS */ + +#ifdef JPEG_CJPEG_DJPEG + +#define BMP_SUPPORTED /* BMP image file format */ +#define GIF_SUPPORTED /* GIF image file format */ +#define PPM_SUPPORTED /* PBMPLUS PPM/PGM image file format */ +#undef RLE_SUPPORTED /* Utah RLE image file format */ +#define TARGA_SUPPORTED /* Targa image file format */ + +#undef TWO_FILE_COMMANDLINE /* optional */ +#define USE_SETMODE /* Needed to make one-file style work in Watcom */ +#undef NEED_SIGNAL_CATCHER /* Define this if you use jmemname.c */ +#undef DONT_USE_B_MODE +#undef PROGRESS_REPORT /* optional */ + +#endif /* JPEG_CJPEG_DJPEG */ diff --git a/libs/jpeg6/jdapimin.cpp b/libs/jpeg6/jdapimin.cpp new file mode 100644 index 00000000..c5c3be32 --- /dev/null +++ b/libs/jpeg6/jdapimin.cpp @@ -0,0 +1,400 @@ +/* + * jdapimin.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-decompression case or the + * transcoding-only case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jdapistd.c. But also see jcomapi.c for routines + * shared by compression and decompression, and jdtrans.c for the transcoding + * case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +/* + * Initialization of a JPEG decompression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL void +jpeg_create_decompress (j_decompress_ptr cinfo) +{ + int i; + + /* For debugging purposes, zero the whole master structure. + * But error manager pointer is already there, so save and restore it. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + i = sizeof(struct jpeg_decompress_struct); + i = SIZEOF(struct jpeg_decompress_struct); + MEMZERO(cinfo, SIZEOF(struct jpeg_decompress_struct)); + cinfo->err = err; + } + cinfo->is_decompressor = TRUE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr((j_common_ptr) cinfo); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->src = NULL; + + for (i = 0; i < NUM_QUANT_TBLS; i++) + cinfo->quant_tbl_ptrs[i] = NULL; + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + /* Initialize marker processor so application can override methods + * for COM, APPn markers before calling jpeg_read_header. + */ + jinit_marker_reader(cinfo); + + /* And initialize the overall input controller. */ + jinit_input_controller(cinfo); + + /* OK, I'm ready */ + cinfo->global_state = DSTATE_START; +} + + +/* + * Destruction of a JPEG decompression object + */ + +GLOBAL void +jpeg_destroy_decompress (j_decompress_ptr cinfo) +{ + jpeg_destroy((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Abort processing of a JPEG decompression operation, + * but don't destroy the object itself. + */ + +GLOBAL void +jpeg_abort_decompress (j_decompress_ptr cinfo) +{ + jpeg_abort((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Install a special processing method for COM or APPn markers. + */ + +GLOBAL void +jpeg_set_marker_processor (j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine) +{ + if (marker_code == JPEG_COM) + cinfo->marker->process_COM = routine; + else if (marker_code >= JPEG_APP0 && marker_code <= JPEG_APP0+15) + cinfo->marker->process_APPn[marker_code-JPEG_APP0] = routine; + else + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, marker_code); +} + + +/* + * Set default decompression parameters. + */ + +LOCAL void +default_decompress_parms (j_decompress_ptr cinfo) +{ + /* Guess the input colorspace, and set output colorspace accordingly. */ + /* (Wish JPEG committee had provided a real way to specify this...) */ + /* Note application may override our guesses. */ + switch (cinfo->num_components) { + case 1: + cinfo->jpeg_color_space = JCS_GRAYSCALE; + cinfo->out_color_space = JCS_GRAYSCALE; + break; + + case 3: + if (cinfo->saw_JFIF_marker) { + cinfo->jpeg_color_space = JCS_YCbCr; /* JFIF implies YCbCr */ + } else if (cinfo->saw_Adobe_marker) { + switch (cinfo->Adobe_transform) { + case 0: + cinfo->jpeg_color_space = JCS_RGB; + break; + case 1: + cinfo->jpeg_color_space = JCS_YCbCr; + break; + default: + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + break; + } + } else { + /* Saw no special markers, try to guess from the component IDs */ + int cid0 = cinfo->comp_info[0].component_id; + int cid1 = cinfo->comp_info[1].component_id; + int cid2 = cinfo->comp_info[2].component_id; + + if (cid0 == 1 && cid1 == 2 && cid2 == 3) + cinfo->jpeg_color_space = JCS_YCbCr; /* assume JFIF w/out marker */ + else if (cid0 == 82 && cid1 == 71 && cid2 == 66) + cinfo->jpeg_color_space = JCS_RGB; /* ASCII 'R', 'G', 'B' */ + else { + TRACEMS3(cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + } + } + /* Always guess RGB is proper output colorspace. */ + cinfo->out_color_space = JCS_RGB; + break; + + case 4: + if (cinfo->saw_Adobe_marker) { + switch (cinfo->Adobe_transform) { + case 0: + cinfo->jpeg_color_space = JCS_CMYK; + break; + case 2: + cinfo->jpeg_color_space = JCS_YCCK; + break; + default: + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCCK; /* assume it's YCCK */ + break; + } + } else { + /* No special markers, assume straight CMYK. */ + cinfo->jpeg_color_space = JCS_CMYK; + } + cinfo->out_color_space = JCS_CMYK; + break; + + default: + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->out_color_space = JCS_UNKNOWN; + break; + } + + /* Set defaults for other decompression parameters. */ + cinfo->scale_num = 1; /* 1:1 scaling */ + cinfo->scale_denom = 1; + cinfo->output_gamma = 1.0; + cinfo->buffered_image = FALSE; + cinfo->raw_data_out = FALSE; + cinfo->dct_method = JDCT_DEFAULT; + cinfo->do_fancy_upsampling = TRUE; + cinfo->do_block_smoothing = TRUE; + cinfo->quantize_colors = FALSE; + /* We set these in case application only sets quantize_colors. */ + cinfo->dither_mode = JDITHER_FS; +#ifdef QUANT_2PASS_SUPPORTED + cinfo->two_pass_quantize = TRUE; +#else + cinfo->two_pass_quantize = FALSE; +#endif + cinfo->desired_number_of_colors = 256; + cinfo->colormap = NULL; + /* Initialize for no mode change in buffered-image mode. */ + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; +} + + +/* + * Decompression startup: read start of JPEG datastream to see what's there. + * Need only initialize JPEG object and supply a data source before calling. + * + * This routine will read as far as the first SOS marker (ie, actual start of + * compressed data), and will save all tables and parameters in the JPEG + * object. It will also initialize the decompression parameters to default + * values, and finally return JPEG_HEADER_OK. On return, the application may + * adjust the decompression parameters and then call jpeg_start_decompress. + * (Or, if the application only wanted to determine the image parameters, + * the data need not be decompressed. In that case, call jpeg_abort or + * jpeg_destroy to release any temporary space.) + * If an abbreviated (tables only) datastream is presented, the routine will + * return JPEG_HEADER_TABLES_ONLY upon reaching EOI. The application may then + * re-use the JPEG object to read the abbreviated image datastream(s). + * It is unnecessary (but OK) to call jpeg_abort in this case. + * The JPEG_SUSPENDED return code only occurs if the data source module + * requests suspension of the decompressor. In this case the application + * should load more source data and then re-call jpeg_read_header to resume + * processing. + * If a non-suspending data source is used and require_image is TRUE, then the + * return code need not be inspected since only JPEG_HEADER_OK is possible. + * + * This routine is now just a front end to jpeg_consume_input, with some + * extra error checking. + */ + +GLOBAL int +jpeg_read_header (j_decompress_ptr cinfo, boolean require_image) +{ + int retcode; + + if (cinfo->global_state != DSTATE_START && + cinfo->global_state != DSTATE_INHEADER) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + retcode = jpeg_consume_input(cinfo); + + switch (retcode) { + case JPEG_REACHED_SOS: + retcode = JPEG_HEADER_OK; + break; + case JPEG_REACHED_EOI: + if (require_image) /* Complain if application wanted an image */ + ERREXIT(cinfo, JERR_NO_IMAGE); + /* Reset to start state; it would be safer to require the application to + * call jpeg_abort, but we can't change it now for compatibility reasons. + * A side effect is to free any temporary memory (there shouldn't be any). + */ + jpeg_abort((j_common_ptr) cinfo); /* sets state = DSTATE_START */ + retcode = JPEG_HEADER_TABLES_ONLY; + break; + case JPEG_SUSPENDED: + /* no work */ + break; + } + + return retcode; +} + + +/* + * Consume data in advance of what the decompressor requires. + * This can be called at any time once the decompressor object has + * been created and a data source has been set up. + * + * This routine is essentially a state machine that handles a couple + * of critical state-transition actions, namely initial setup and + * transition from header scanning to ready-for-start_decompress. + * All the actual input is done via the input controller's consume_input + * method. + */ + +GLOBAL int +jpeg_consume_input (j_decompress_ptr cinfo) +{ + int retcode = JPEG_SUSPENDED; + + /* NB: every possible DSTATE value should be listed in this switch */ + switch (cinfo->global_state) { + case DSTATE_START: + /* Start-of-datastream actions: reset appropriate modules */ + (*cinfo->inputctl->reset_input_controller) (cinfo); + /* Initialize application's data source module */ + (*cinfo->src->init_source) (cinfo); + cinfo->global_state = DSTATE_INHEADER; + /*FALLTHROUGH*/ + case DSTATE_INHEADER: + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_REACHED_SOS) { /* Found SOS, prepare to decompress */ + /* Set up default parameters based on header data */ + default_decompress_parms(cinfo); + /* Set global state: ready for start_decompress */ + cinfo->global_state = DSTATE_READY; + } + break; + case DSTATE_READY: + /* Can't advance past first SOS until start_decompress is called */ + retcode = JPEG_REACHED_SOS; + break; + case DSTATE_PRELOAD: + case DSTATE_PRESCAN: + case DSTATE_SCANNING: + case DSTATE_RAW_OK: + case DSTATE_BUFIMAGE: + case DSTATE_BUFPOST: + case DSTATE_STOPPING: + retcode = (*cinfo->inputctl->consume_input) (cinfo); + break; + default: + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + return retcode; +} + + +/* + * Have we finished reading the input file? + */ + +GLOBAL boolean +jpeg_input_complete (j_decompress_ptr cinfo) +{ + /* Check for valid jpeg object */ + if (cinfo->global_state < DSTATE_START || + cinfo->global_state > DSTATE_STOPPING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return cinfo->inputctl->eoi_reached; +} + + +/* + * Is there more than one scan? + */ + +GLOBAL boolean +jpeg_has_multiple_scans (j_decompress_ptr cinfo) +{ + /* Only valid after jpeg_read_header completes */ + if (cinfo->global_state < DSTATE_READY || + cinfo->global_state > DSTATE_STOPPING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return cinfo->inputctl->has_multiple_scans; +} + + +/* + * Finish JPEG decompression. + * + * This will normally just verify the file trailer and release temp storage. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_finish_decompress (j_decompress_ptr cinfo) +{ + if ((cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK) && ! cinfo->buffered_image) { + /* Terminate final pass of non-buffered mode */ + if (cinfo->output_scanline < cinfo->output_height) + ERREXIT(cinfo, JERR_TOO_LITTLE_DATA); + (*cinfo->master->finish_output_pass) (cinfo); + cinfo->global_state = DSTATE_STOPPING; + } else if (cinfo->global_state == DSTATE_BUFIMAGE) { + /* Finishing after a buffered-image operation */ + cinfo->global_state = DSTATE_STOPPING; + } else if (cinfo->global_state != DSTATE_STOPPING) { + /* STOPPING = repeat call after a suspension, anything else is error */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + /* Read until EOI */ + while (! cinfo->inputctl->eoi_reached) { + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + return FALSE; /* Suspend, come back later */ + } + /* Do final cleanup */ + (*cinfo->src->term_source) (cinfo); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort((j_common_ptr) cinfo); + return TRUE; +} diff --git a/libs/jpeg6/jdapistd.cpp b/libs/jpeg6/jdapistd.cpp new file mode 100644 index 00000000..22da01b3 --- /dev/null +++ b/libs/jpeg6/jdapistd.cpp @@ -0,0 +1,275 @@ +/* + * jdapistd.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "standard" API routines that are + * used in the normal full-decompression case. They are not used by a + * transcoding-only application. Note that if an application links in + * jpeg_start_decompress, it will end up linking in the entire decompressor. + * We thus must separate this file from jdapimin.c to avoid linking the + * whole decompression library into a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +/* Forward declarations */ +LOCAL boolean output_pass_setup JPP((j_decompress_ptr cinfo)); + + +/* + * Decompression initialization. + * jpeg_read_header must be completed before calling this. + * + * If a multipass operating mode was selected, this will do all but the + * last pass, and thus may take a great deal of time. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_start_decompress (j_decompress_ptr cinfo) +{ + if (cinfo->global_state == DSTATE_READY) { + /* First call: initialize master control, select active modules */ + jinit_master_decompress(cinfo); + if (cinfo->buffered_image) { + /* No more work here; expecting jpeg_start_output next */ + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; + } + cinfo->global_state = DSTATE_PRELOAD; + } + if (cinfo->global_state == DSTATE_PRELOAD) { + /* If file has multiple scans, absorb them all into the coef buffer */ + if (cinfo->inputctl->has_multiple_scans) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + for (;;) { + int retcode; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + /* Absorb some more input */ + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_SUSPENDED) + return FALSE; + if (retcode == JPEG_REACHED_EOI) + break; + /* Advance progress counter if appropriate */ + if (cinfo->progress != NULL && + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + /* jdmaster underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + } + cinfo->output_scan_number = cinfo->input_scan_number; + } else if (cinfo->global_state != DSTATE_PRESCAN) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Perform any dummy output passes, and set up for the final pass */ + return output_pass_setup(cinfo); +} + + +/* + * Set up for an output pass, and perform any dummy pass(es) needed. + * Common subroutine for jpeg_start_decompress and jpeg_start_output. + * Entry: global_state = DSTATE_PRESCAN only if previously suspended. + * Exit: If done, returns TRUE and sets global_state for proper output mode. + * If suspended, returns FALSE and sets global_state = DSTATE_PRESCAN. + */ + +LOCAL boolean +output_pass_setup (j_decompress_ptr cinfo) +{ + if (cinfo->global_state != DSTATE_PRESCAN) { + /* First call: do pass setup */ + (*cinfo->master->prepare_for_output_pass) (cinfo); + cinfo->output_scanline = 0; + cinfo->global_state = DSTATE_PRESCAN; + } + /* Loop over any required dummy passes */ + while (cinfo->master->is_dummy_pass) { +#ifdef QUANT_2PASS_SUPPORTED + /* Crank through the dummy pass */ + while (cinfo->output_scanline < cinfo->output_height) { + JDIMENSION last_scanline; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + /* Process some data */ + last_scanline = cinfo->output_scanline; + (*cinfo->main->process_data) (cinfo, (JSAMPARRAY) NULL, + &cinfo->output_scanline, (JDIMENSION) 0); + if (cinfo->output_scanline == last_scanline) + return FALSE; /* No progress made, must suspend */ + } + /* Finish up dummy pass, and set up for another one */ + (*cinfo->master->finish_output_pass) (cinfo); + (*cinfo->master->prepare_for_output_pass) (cinfo); + cinfo->output_scanline = 0; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* QUANT_2PASS_SUPPORTED */ + } + /* Ready for application to drive output pass through + * jpeg_read_scanlines or jpeg_read_raw_data. + */ + cinfo->global_state = cinfo->raw_data_out ? DSTATE_RAW_OK : DSTATE_SCANNING; + return TRUE; +} + + +/* + * Read some scanlines of data from the JPEG decompressor. + * + * The return value will be the number of lines actually read. + * This may be less than the number requested in several cases, + * including bottom of image, data source suspension, and operating + * modes that emit multiple scanlines at a time. + * + * Note: we warn about excess calls to jpeg_read_scanlines() since + * this likely signals an application programmer error. However, + * an oversize buffer (max_lines > scanlines remaining) is not an error. + */ + +GLOBAL JDIMENSION +jpeg_read_scanlines (j_decompress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION max_lines) +{ + JDIMENSION row_ctr; + + if (cinfo->global_state != DSTATE_SCANNING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->output_scanline >= cinfo->output_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Process some data */ + row_ctr = 0; + (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines); + cinfo->output_scanline += row_ctr; + return row_ctr; +} + + +/* + * Alternate entry point to read raw data. + * Processes exactly one iMCU row per call, unless suspended. + */ + +GLOBAL JDIMENSION +jpeg_read_raw_data (j_decompress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION max_lines) +{ + JDIMENSION lines_per_iMCU_row; + + if (cinfo->global_state != DSTATE_RAW_OK) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->output_scanline >= cinfo->output_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Verify that at least one iMCU row can be returned. */ + lines_per_iMCU_row = cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size; + if (max_lines < lines_per_iMCU_row) + ERREXIT(cinfo, JERR_BUFFER_SIZE); + + /* Decompress directly into user's buffer. */ + if (! (*cinfo->coef->decompress_data) (cinfo, data)) + return 0; /* suspension forced, can do nothing more */ + + /* OK, we processed one iMCU row. */ + cinfo->output_scanline += lines_per_iMCU_row; + return lines_per_iMCU_row; +} + + +/* Additional entry points for buffered-image mode. */ + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Initialize for an output pass in buffered-image mode. + */ + +GLOBAL boolean +jpeg_start_output (j_decompress_ptr cinfo, int scan_number) +{ + if (cinfo->global_state != DSTATE_BUFIMAGE && + cinfo->global_state != DSTATE_PRESCAN) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Limit scan number to valid range */ + if (scan_number <= 0) + scan_number = 1; + if (cinfo->inputctl->eoi_reached && + scan_number > cinfo->input_scan_number) + scan_number = cinfo->input_scan_number; + cinfo->output_scan_number = scan_number; + /* Perform any dummy output passes, and set up for the real pass */ + return output_pass_setup(cinfo); +} + + +/* + * Finish up after an output pass in buffered-image mode. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_finish_output (j_decompress_ptr cinfo) +{ + if ((cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK) && cinfo->buffered_image) { + /* Terminate this pass. */ + /* We do not require the whole pass to have been completed. */ + (*cinfo->master->finish_output_pass) (cinfo); + cinfo->global_state = DSTATE_BUFPOST; + } else if (cinfo->global_state != DSTATE_BUFPOST) { + /* BUFPOST = repeat call after a suspension, anything else is error */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + /* Read markers looking for SOS or EOI */ + while (cinfo->input_scan_number <= cinfo->output_scan_number && + ! cinfo->inputctl->eoi_reached) { + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + return FALSE; /* Suspend, come back later */ + } + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ diff --git a/libs/jpeg6/jdatasrc.cpp b/libs/jpeg6/jdatasrc.cpp new file mode 100644 index 00000000..9bb30dbe --- /dev/null +++ b/libs/jpeg6/jdatasrc.cpp @@ -0,0 +1,189 @@ +/* + * jdatasrc.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains decompression data source routines for the case of + * reading JPEG data from a file (or any stdio stream). While these routines + * are sufficient for most applications, some will want to use a different + * source manager. + * IMPORTANT: we assume that fread() will correctly transcribe an array of + * JOCTETs from 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "radiant_jpeglib.h" +#include "jerror.h" +//extern int leo_buf_size; // FIXME ? merged in from Alpha - replaced by my_source_mgr->src_size +/* Expanded data source object for stdio input */ +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + int src_size; // FIXME ? merged from Alpha + unsigned char *infile; /* source stream */ + JOCTET * buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ +} my_source_mgr; +typedef my_source_mgr * my_src_ptr; +#define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */ + +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ +METHODDEF void +init_source (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + /* We reset the empty-input-file flag for each image, + * but we don't clear the input buffer. + * This is correct behavior for reading a series of images from one source. + */ + src->start_of_file = TRUE; +} + +/* + * Fill the input buffer --- called whenever buffer is emptied. + * + * In typical applications, this should read fresh data into the buffer + * (ignoring the current state of next_input_byte & bytes_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been reloaded. It is not necessary to + * fill the buffer entirely, only to obtain at least one more byte. + * + * There is no such thing as an EOF return. If the end of the file has been + * reached, the routine has a choice of ERREXIT() or inserting fake data into + * the buffer. In most cases, generating a warning message and inserting a + * fake EOI marker is the best course of action --- this will allow the + * decompressor to output however much of the image is there. However, + * the resulting error message is misleading if the real problem is an empty + * input file, so we handle that case specially. + * + * In applications that need to be able to suspend compression due to input + * not being available yet, a FALSE return indicates that no more data can be + * obtained right now, but more may be forthcoming later. In this situation, + * the decompressor will return to its caller (with an indication of the + * number of scanlines it has read, if any). The application should resume + * decompression after it has loaded more data into the input buffer. Note + * that there are substantial restrictions on the use of suspension --- see + * the documentation. + * + * When suspending, the decompressor will back up to a convenient restart point + * (typically the start of the current MCU). next_input_byte & bytes_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point must be rescanned after resumption, so move it to + * the front of the buffer rather than discarding it. + */ +METHODDEF boolean +// FIXME ? merged in from Alpha +fill_input_buffer (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + size_t nbytes; + + if (src->src_size > INPUT_BUF_SIZE) + nbytes = INPUT_BUF_SIZE; + else + nbytes = src->src_size; + memcpy (src->buffer, src->infile, nbytes); + src->infile += nbytes; + src->src_size -= nbytes; + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = nbytes; + src->start_of_file = FALSE; + return TRUE; +} + +/* + * Skip data --- used to skip over a potentially large amount of + * uninteresting data (such as an APPn marker). + * + * Writers of suspendable-input applications must note that skip_input_data + * is not granted the right to give a suspension return. If the skip extends + * beyond the data currently in the buffer, the buffer can be marked empty so + * that the next read will cause a fill_input_buffer call that can suspend. + * Arranging for additional bytes to be discarded before reloading the input + * buffer is the application writer's problem. + */ +METHODDEF void +skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if (num_bytes > 0) { + while (num_bytes > (long) src->pub.bytes_in_buffer) { + num_bytes -= (long) src->pub.bytes_in_buffer; + (void) fill_input_buffer(cinfo); + /* note we assume that fill_input_buffer will never return FALSE, + * so suspension need not be handled. + */ + } + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; + } +} + +/* + * An additional method that can be provided by data source modules is the + * resync_to_restart method for error recovery in the presence of RST markers. + * For the moment, this source module just uses the default resync method + * provided by the JPEG library. That method assumes that no backtracking + * is possible. + */ + +/* + * Terminate source --- called by jpeg_finish_decompress + * after all data has been read. Often a no-op. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ +METHODDEF void +term_source (j_decompress_ptr cinfo) +{ + /* no work necessary here */ +} + +/* + * Prepare for input from a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing decompression. + */ +GLOBAL void +jpeg_stdio_src (j_decompress_ptr cinfo, unsigned char *infile, int bufsize) +{ + my_src_ptr src; + /* The source object and input buffer are made permanent so that a series + * of JPEG images can be read from the same file by calling jpeg_stdio_src + * only before the first one. (If we discarded the buffer at the end of + * one image, we'd likely lose the start of the next one.) + * This makes it unsafe to use this manager and a different source + * manager serially with the same JPEG object. Caveat programmer. + */ + if (cinfo->src == NULL) { /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_source_mgr)); + src = (my_src_ptr) cinfo->src; + src->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + INPUT_BUF_SIZE * SIZEOF(JOCTET)); + } + src = (my_src_ptr) cinfo->src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.term_source = term_source; + src->infile = infile; + src->src_size = bufsize; // FIXME ? merged from Alpha + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ +} diff --git a/libs/jpeg6/jdcoefct.cpp b/libs/jpeg6/jdcoefct.cpp new file mode 100644 index 00000000..4b371313 --- /dev/null +++ b/libs/jpeg6/jdcoefct.cpp @@ -0,0 +1,725 @@ +/* + * jdcoefct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for decompression. + * This controller is the top level of the JPEG decompressor proper. + * The coefficient buffer lies between entropy decoding and inverse-DCT steps. + * + * In buffered-image mode, this controller is the interface between + * input-oriented processing and output-oriented processing. + * Also, the input side (only) is used when reading a file for transcoding. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + +/* Block smoothing is only applicable for progressive JPEG, so: */ +#ifndef D_PROGRESSIVE_SUPPORTED +#undef BLOCK_SMOOTHING_SUPPORTED +#endif + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_coef_controller pub; /* public fields */ + + /* These variables keep track of the current location of the input side. */ + /* cinfo->input_iMCU_row is also used for this. */ + JDIMENSION MCU_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* The output side's location is represented by cinfo->output_iMCU_row. */ + + /* In single-pass modes, it's sufficient to buffer just one MCU. + * We allocate a workspace of D_MAX_BLOCKS_IN_MCU coefficient blocks, + * and let the entropy decoder write into that workspace each time. + * (On 80x86, the workspace is FAR even though it's not really very big; + * this is to keep the module interfaces unchanged when a large coefficient + * buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays; it is used only by the input side. + */ + JBLOCKROW MCU_buffer[D_MAX_BLOCKS_IN_MCU]; + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +#endif + +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* When doing block smoothing, we latch coefficient Al values here */ + int * coef_bits_latch; +#define SAVED_COEFS 6 /* we save coef_bits[0..5] */ +#endif +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + +/* Forward declarations */ +METHODDEF int decompress_onepass + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#ifdef D_MULTISCAN_FILES_SUPPORTED +METHODDEF int decompress_data + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#endif +#ifdef BLOCK_SMOOTHING_SUPPORTED +LOCAL boolean smoothing_ok JPP((j_decompress_ptr cinfo)); +METHODDEF int decompress_smooth_data + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#endif + + +LOCAL void +start_iMCU_row (j_decompress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row (input side) */ +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (cinfo->input_iMCU_row < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->MCU_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for an input processing pass. + */ + +METHODDEF void +start_input_pass (j_decompress_ptr cinfo) +{ + cinfo->input_iMCU_row = 0; + start_iMCU_row(cinfo); +} + + +/* + * Initialize for an output processing pass. + */ + +METHODDEF void +start_output_pass (j_decompress_ptr cinfo) +{ +#ifdef BLOCK_SMOOTHING_SUPPORTED + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* If multipass, check to see whether to use block smoothing on this pass */ + if (coef->pub.coef_arrays != NULL) { + if (cinfo->do_block_smoothing && smoothing_ok(cinfo)) + coef->pub.decompress_data = decompress_smooth_data; + else + coef->pub.decompress_data = decompress_data; + } +#endif + cinfo->output_iMCU_row = 0; +} + + +/* + * Decompress and return some data in the single-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Input and output must run in lockstep since we have only a one-MCU buffer. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image. + * For single pass, this is the same as the components in the scan. + */ + +METHODDEF int +decompress_onepass (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, useful_width; + JSAMPARRAY output_ptr; + JDIMENSION start_col, output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Loop to process as much as one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->MCU_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++) { + /* Try to fetch an MCU. Entropy decoder expects buffer to be zeroed. */ + jzero_far((void FAR *) coef->MCU_buffer[0], + (size_t) (cinfo->blocks_in_MCU * SIZEOF(JBLOCK))); + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + /* Determine where data should go in output_buf and do the IDCT thing. + * We skip dummy blocks at the right and bottom edges (but blkn gets + * incremented past them!). Note the inner loop relies on having + * allocated the MCU_buffer[] blocks sequentially. + */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) { + blkn += compptr->MCU_blocks; + continue; + } + inverse_DCT = cinfo->idct->inverse_DCT[compptr->component_index]; + useful_width = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + output_ptr = output_buf[ci] + yoffset * compptr->DCT_scaled_size; + start_col = MCU_col_num * compptr->MCU_sample_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (cinfo->input_iMCU_row < last_iMCU_row || + yoffset+yindex < compptr->last_row_height) { + output_col = start_col; + for (xindex = 0; xindex < useful_width; xindex++) { + (*inverse_DCT) (cinfo, compptr, + (JCOEFPTR) coef->MCU_buffer[blkn+xindex], + output_ptr, output_col); + output_col += compptr->DCT_scaled_size; + } + } + blkn += compptr->MCU_width; + output_ptr += compptr->DCT_scaled_size; + } + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + cinfo->output_iMCU_row++; + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + start_iMCU_row(cinfo); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + (*cinfo->inputctl->finish_input_pass) (cinfo); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Dummy consume-input routine for single-pass operation. + */ + +METHODDEF int +dummy_consume_data (j_decompress_ptr cinfo) +{ + return JPEG_SUSPENDED; /* Always indicate nothing was done */ +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Consume input data and store it in the full-image coefficient buffer. + * We read as much as one fully interleaved MCU row ("iMCU" row) per call, + * ie, v_samp_factor block rows for each component in the scan. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + */ + +METHODDEF int +consume_data (j_decompress_ptr cinfo) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + cinfo->input_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE); + /* Note: entropy decoder expects buffer to be zeroed, + * but this is handled automatically by the memory manager + * because we requested a pre-zeroed array. + */ + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->MCU_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < compptr->MCU_width; xindex++) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to fetch the MCU. */ + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + start_iMCU_row(cinfo); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + (*cinfo->inputctl->finish_input_pass) (cinfo); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Decompress and return some data in the multi-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image. + */ + +METHODDEF int +decompress_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num; + int ci, block_row, block_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Force some input to be done if we are getting ahead of the input. */ + while (cinfo->input_scan_number < cinfo->output_scan_number || + (cinfo->input_scan_number == cinfo->output_scan_number && + cinfo->input_iMCU_row <= cinfo->output_iMCU_row)) { + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + return JPEG_SUSPENDED; + } + + /* OK, output from the virtual arrays. */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) + continue; + /* Align the virtual buffer for this component. */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + cinfo->output_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + /* Count non-dummy DCT block rows in this iMCU row. */ + if (cinfo->output_iMCU_row < last_iMCU_row) + block_rows = compptr->v_samp_factor; + else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + } + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for (block_row = 0; block_row < block_rows; block_row++) { + buffer_ptr = buffer[block_row]; + output_col = 0; + for (block_num = 0; block_num < compptr->width_in_blocks; block_num++) { + (*inverse_DCT) (cinfo, compptr, (JCOEFPTR) buffer_ptr, + output_ptr, output_col); + buffer_ptr++; + output_col += compptr->DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + return JPEG_ROW_COMPLETED; + return JPEG_SCAN_COMPLETED; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +#ifdef BLOCK_SMOOTHING_SUPPORTED + +/* + * This code applies interblock smoothing as described by section K.8 + * of the JPEG standard: the first 5 AC coefficients are estimated from + * the DC values of a DCT block and its 8 neighboring blocks. + * We apply smoothing only for progressive JPEG decoding, and only if + * the coefficients it can estimate are not yet known to full precision. + */ + +/* + * Determine whether block smoothing is applicable and safe. + * We also latch the current states of the coef_bits[] entries for the + * AC coefficients; otherwise, if the input side of the decompressor + * advances into a new scan, we might think the coefficients are known + * more accurately than they really are. + */ + +LOCAL boolean +smoothing_ok (j_decompress_ptr cinfo) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + boolean smoothing_useful = FALSE; + int ci, coefi; + jpeg_component_info *compptr; + JQUANT_TBL * qtable; + int * coef_bits; + int * coef_bits_latch; + + if (! cinfo->progressive_mode || cinfo->coef_bits == NULL) + return FALSE; + + /* Allocate latch area if not already done */ + if (coef->coef_bits_latch == NULL) + coef->coef_bits_latch = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * + (SAVED_COEFS * SIZEOF(int))); + coef_bits_latch = coef->coef_bits_latch; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* All components' quantization values must already be latched. */ + if ((qtable = compptr->quant_table) == NULL) + return FALSE; + /* Verify DC & first 5 AC quantizers are nonzero to avoid zero-divide. */ + for (coefi = 0; coefi <= 5; coefi++) { + if (qtable->quantval[coefi] == 0) + return FALSE; + } + /* DC values must be at least partly known for all components. */ + coef_bits = cinfo->coef_bits[ci]; + if (coef_bits[0] < 0) + return FALSE; + /* Block smoothing is helpful if some AC coefficients remain inaccurate. */ + for (coefi = 1; coefi <= 5; coefi++) { + coef_bits_latch[coefi] = coef_bits[coefi]; + if (coef_bits[coefi] != 0) + smoothing_useful = TRUE; + } + coef_bits_latch += SAVED_COEFS; + } + + return smoothing_useful; +} + + +/* + * Variant of decompress_data for use when doing block smoothing. + */ + +METHODDEF int +decompress_smooth_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num, last_block_column; + int ci, block_row, block_rows, access_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr, prev_block_row, next_block_row; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + boolean first_row, last_row; + JBLOCK workspace; + int *coef_bits; + JQUANT_TBL *quanttbl; + INT32 Q00,Q01,Q02,Q10,Q11,Q20, num; + int DC1,DC2,DC3,DC4,DC5,DC6,DC7,DC8,DC9; + int Al, pred; + + /* Force some input to be done if we are getting ahead of the input. */ + while (cinfo->input_scan_number <= cinfo->output_scan_number && + ! cinfo->inputctl->eoi_reached) { + if (cinfo->input_scan_number == cinfo->output_scan_number) { + /* If input is working on current scan, we ordinarily want it to + * have completed the current row. But if input scan is DC, + * we want it to keep one row ahead so that next block row's DC + * values are up to date. + */ + JDIMENSION delta = (cinfo->Ss == 0) ? 1 : 0; + if (cinfo->input_iMCU_row > cinfo->output_iMCU_row+delta) + break; + } + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + return JPEG_SUSPENDED; + } + + /* OK, output from the virtual arrays. */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) + continue; + /* Count non-dummy DCT block rows in this iMCU row. */ + if (cinfo->output_iMCU_row < last_iMCU_row) { + block_rows = compptr->v_samp_factor; + access_rows = block_rows * 2; /* this and next iMCU row */ + last_row = FALSE; + } else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + access_rows = block_rows; /* this iMCU row only */ + last_row = TRUE; + } + /* Align the virtual buffer for this component. */ + if (cinfo->output_iMCU_row > 0) { + access_rows += compptr->v_samp_factor; /* prior iMCU row too */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + (cinfo->output_iMCU_row - 1) * compptr->v_samp_factor, + (JDIMENSION) access_rows, FALSE); + buffer += compptr->v_samp_factor; /* point to current iMCU row */ + first_row = FALSE; + } else { + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + (JDIMENSION) 0, (JDIMENSION) access_rows, FALSE); + first_row = TRUE; + } + /* Fetch component-dependent info */ + coef_bits = coef->coef_bits_latch + (ci * SAVED_COEFS); + quanttbl = compptr->quant_table; + Q00 = quanttbl->quantval[0]; + Q01 = quanttbl->quantval[1]; + Q10 = quanttbl->quantval[2]; + Q20 = quanttbl->quantval[3]; + Q11 = quanttbl->quantval[4]; + Q02 = quanttbl->quantval[5]; + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for (block_row = 0; block_row < block_rows; block_row++) { + buffer_ptr = buffer[block_row]; + if (first_row && block_row == 0) + prev_block_row = buffer_ptr; + else + prev_block_row = buffer[block_row-1]; + if (last_row && block_row == block_rows-1) + next_block_row = buffer_ptr; + else + next_block_row = buffer[block_row+1]; + /* We fetch the surrounding DC values using a sliding-register approach. + * Initialize all nine here so as to do the right thing on narrow pics. + */ + DC1 = DC2 = DC3 = (int) prev_block_row[0][0]; + DC4 = DC5 = DC6 = (int) buffer_ptr[0][0]; + DC7 = DC8 = DC9 = (int) next_block_row[0][0]; + output_col = 0; + last_block_column = compptr->width_in_blocks - 1; + for (block_num = 0; block_num <= last_block_column; block_num++) { + /* Fetch current DCT block into workspace so we can modify it. */ + jcopy_block_row(buffer_ptr, (JBLOCKROW) workspace, (JDIMENSION) 1); + /* Update DC values */ + if (block_num < last_block_column) { + DC3 = (int) prev_block_row[1][0]; + DC6 = (int) buffer_ptr[1][0]; + DC9 = (int) next_block_row[1][0]; + } + /* Compute coefficient estimates per K.8. + * An estimate is applied only if coefficient is still zero, + * and is not known to be fully accurate. + */ + /* AC01 */ + if ((Al=coef_bits[1]) != 0 && workspace[1] == 0) { + num = 36 * Q00 * (DC4 - DC6); + if (num >= 0) { + pred = (int) (((Q01<<7) + num) / (Q01<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q10<<7) + num) / (Q10<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q20<<7) + num) / (Q20<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q11<<7) + num) / (Q11<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q02<<7) + num) / (Q02<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + return JPEG_ROW_COMPLETED; + return JPEG_SCAN_COMPLETED; +} + +#endif /* BLOCK_SMOOTHING_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL void +jinit_d_coef_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_coef_ptr coef; + + coef = (my_coef_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller)); + cinfo->coef = (struct jpeg_d_coef_controller *) coef; + coef->pub.start_input_pass = start_input_pass; + coef->pub.start_output_pass = start_output_pass; +#ifdef BLOCK_SMOOTHING_SUPPORTED + coef->coef_bits_latch = NULL; +#endif + + /* Create the coefficient buffer. */ + if (need_full_buffer) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + /* Note we ask for a pre-zeroed array. */ + int ci, access_rows; + jpeg_component_info *compptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + access_rows = compptr->v_samp_factor; +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* If block smoothing could be used, need a bigger window */ + if (cinfo->progressive_mode) + access_rows *= 3; +#endif + coef->whole_image[ci] = (*cinfo->mem->request_virt_barray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, TRUE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) access_rows); + } + coef->pub.consume_data = consume_data; + coef->pub.decompress_data = decompress_data; + coef->pub.coef_arrays = coef->whole_image; /* link to virtual arrays */ +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + D_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < D_MAX_BLOCKS_IN_MCU; i++) { + coef->MCU_buffer[i] = buffer + i; + } + coef->pub.consume_data = dummy_consume_data; + coef->pub.decompress_data = decompress_onepass; + coef->pub.coef_arrays = NULL; /* flag for no virtual arrays */ + } +} diff --git a/libs/jpeg6/jdcolor.cpp b/libs/jpeg6/jdcolor.cpp new file mode 100644 index 00000000..50e66f26 --- /dev/null +++ b/libs/jpeg6/jdcolor.cpp @@ -0,0 +1,367 @@ +/* + * jdcolor.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains output colorspace conversion routines. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_deconverter pub; /* public fields */ + + /* Private state for YCC->RGB conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ +} my_color_deconverter; + +typedef my_color_deconverter * my_cconvert_ptr; + + +/**************** YCbCr -> RGB conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * R = Y + 1.40200 * Cr + * G = Y - 0.34414 * Cb - 0.71414 * Cr + * B = Y + 1.77200 * Cb + * where Cb and Cr represent the incoming values less CENTERJSAMPLE. + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * Notice that Y, being an integral input, does not contribute any fraction + * so it need not participate in the rounding. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times Cb and Cr for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The Cr=>R and Cb=>B values can be rounded to integers in advance; the + * values for the G calculation are left scaled up, since we must add them + * together before rounding. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L<RGB colorspace conversion. + */ + +LOCAL void +build_ycc_rgb_table (j_decompress_ptr cinfo) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + int i; + INT32 x; + SHIFT_TEMPS + + cconvert->Cr_r_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + cconvert->Cb_b_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + cconvert->Cr_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + cconvert->Cb_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + + for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + cconvert->Cr_r_tab[i] = (int) + RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS); + /* Cb=>B value is nearest int to 1.77200 * x */ + cconvert->Cb_b_tab[i] = (int) + RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS); + /* Cr=>G value is scaled-up -0.71414 * x */ + cconvert->Cr_g_tab[i] = (- FIX(0.71414)) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + cconvert->Cb_g_tab[i] = (- FIX(0.34414)) * x + ONE_HALF; + } +} + + +/* + * Convert some rows of samples to the output colorspace. + * + * Note that we change from noninterleaved, one-plane-per-component format + * to interleaved-pixel format. The output buffer is therefore three times + * as wide as the input buffer. + * A starting row offset is provided only for the input buffer. The caller + * can easily adjust the passed output_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF void +ycc_rgb_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while (--num_rows >= 0) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + input_row++; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + y = GETJSAMPLE(inptr0[col]); + cb = GETJSAMPLE(inptr1[col]); + cr = GETJSAMPLE(inptr2[col]); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[RGB_RED] = range_limit[y + Crrtab[cr]]; + outptr[RGB_GREEN] = range_limit[y + + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS))]; + outptr[RGB_BLUE] = range_limit[y + Cbbtab[cb]]; + outptr += RGB_PIXELSIZE; + } + } +} + + +/**************** Cases other than YCbCr -> RGB **************/ + + +/* + * Color conversion for no colorspace change: just copy the data, + * converting from separate-planes to interleaved representation. + */ + +METHODDEF void +null_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + register JSAMPROW inptr, outptr; + register JDIMENSION count; + register int num_components = cinfo->num_components; + JDIMENSION num_cols = cinfo->output_width; + int ci; + + while (--num_rows >= 0) { + for (ci = 0; ci < num_components; ci++) { + inptr = input_buf[ci][input_row]; + outptr = output_buf[0] + ci; + for (count = num_cols; count > 0; count--) { + *outptr = *inptr++; /* needn't bother with GETJSAMPLE() here */ + outptr += num_components; + } + } + input_row++; + output_buf++; + } +} + + +/* + * Color conversion for grayscale: just copy the data. + * This also works for YCbCr -> grayscale conversion, in which + * we just copy the Y (luminance) component and ignore chrominance. + */ + +METHODDEF void +grayscale_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + jcopy_sample_rows(input_buf[0], (int) input_row, output_buf, 0, + num_rows, cinfo->output_width); +} + + +/* + * Adobe-style YCCK->CMYK conversion. + * We convert YCbCr to R=1-C, G=1-M, and B=1-Y using the same + * conversion as above, while passing K (black) unchanged. + * We assume build_ycc_rgb_table has been called. + */ + +METHODDEF void +ycck_cmyk_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2, inptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while (--num_rows >= 0) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + inptr3 = input_buf[3][input_row]; + input_row++; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + y = GETJSAMPLE(inptr0[col]); + cb = GETJSAMPLE(inptr1[col]); + cr = GETJSAMPLE(inptr2[col]); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[0] = range_limit[MAXJSAMPLE - (y + Crrtab[cr])]; /* red */ + outptr[1] = range_limit[MAXJSAMPLE - (y + /* green */ + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS)))]; + outptr[2] = range_limit[MAXJSAMPLE - (y + Cbbtab[cb])]; /* blue */ + /* K passes through unchanged */ + outptr[3] = inptr3[col]; /* don't need GETJSAMPLE here */ + outptr += 4; + } + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF void +start_pass_dcolor (j_decompress_ptr cinfo) +{ + /* no work needed */ +} + + +/* + * Module initialization routine for output colorspace conversion. + */ + +GLOBAL void +jinit_color_deconverter (j_decompress_ptr cinfo) +{ + my_cconvert_ptr cconvert; + int ci; + + cconvert = (my_cconvert_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_color_deconverter)); + cinfo->cconvert = (struct jpeg_color_deconverter *) cconvert; + cconvert->pub.start_pass = start_pass_dcolor; + + /* Make sure num_components agrees with jpeg_color_space */ + switch (cinfo->jpeg_color_space) { + case JCS_GRAYSCALE: + if (cinfo->num_components != 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + case JCS_RGB: + case JCS_YCbCr: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + case JCS_CMYK: + case JCS_YCCK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + default: /* JCS_UNKNOWN can be anything */ + if (cinfo->num_components < 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + } + + /* Set out_color_components and conversion method based on requested space. + * Also clear the component_needed flags for any unused components, + * so that earlier pipeline stages can avoid useless computation. + */ + + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + if (cinfo->jpeg_color_space == JCS_GRAYSCALE || + cinfo->jpeg_color_space == JCS_YCbCr) { + cconvert->pub.color_convert = grayscale_convert; + /* For color->grayscale conversion, only the Y (0) component is needed */ + for (ci = 1; ci < cinfo->num_components; ci++) + cinfo->comp_info[ci].component_needed = FALSE; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_RGB: + cinfo->out_color_components = RGB_PIXELSIZE; + if (cinfo->jpeg_color_space == JCS_YCbCr) { + cconvert->pub.color_convert = ycc_rgb_convert; + build_ycc_rgb_table(cinfo); + } else if (cinfo->jpeg_color_space == JCS_RGB && RGB_PIXELSIZE == 3) { + cconvert->pub.color_convert = null_convert; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_CMYK: + cinfo->out_color_components = 4; + if (cinfo->jpeg_color_space == JCS_YCCK) { + cconvert->pub.color_convert = ycck_cmyk_convert; + build_ycc_rgb_table(cinfo); + } else if (cinfo->jpeg_color_space == JCS_CMYK) { + cconvert->pub.color_convert = null_convert; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + default: + /* Permit null conversion to same output space */ + if (cinfo->out_color_space == cinfo->jpeg_color_space) { + cinfo->out_color_components = cinfo->num_components; + cconvert->pub.color_convert = null_convert; + } else /* unsupported non-null conversion */ + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + } + + if (cinfo->quantize_colors) + cinfo->output_components = 1; /* single colormapped output component */ + else + cinfo->output_components = cinfo->out_color_components; +} diff --git a/libs/jpeg6/jdct.h b/libs/jpeg6/jdct.h new file mode 100644 index 00000000..3ce790bc --- /dev/null +++ b/libs/jpeg6/jdct.h @@ -0,0 +1,176 @@ +/* + * jdct.h + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file contains common declarations for the forward and + * inverse DCT modules. These declarations are private to the DCT managers + * (jcdctmgr.c, jddctmgr.c) and the individual DCT algorithms. + * The individual DCT algorithms are kept in separate files to ease + * machine-dependent tuning (e.g., assembly coding). + */ + + +/* + * A forward DCT routine is given a pointer to a work area of type DCTELEM[]; + * the DCT is to be performed in-place in that buffer. Type DCTELEM is int + * for 8-bit samples, INT32 for 12-bit samples. (NOTE: Floating-point DCT + * implementations use an array of type FAST_FLOAT, instead.) + * The DCT inputs are expected to be signed (range +-CENTERJSAMPLE). + * The DCT outputs are returned scaled up by a factor of 8; they therefore + * have a range of +-8K for 8-bit data, +-128K for 12-bit data. This + * convention improves accuracy in integer implementations and saves some + * work in floating-point ones. + * Quantization of the output coefficients is done by jcdctmgr.c. + */ + +#if BITS_IN_JSAMPLE == 8 +typedef int DCTELEM; /* 16 or 32 bits is fine */ +#else +typedef INT32 DCTELEM; /* must have 32 bits */ +#endif + +typedef JMETHOD(void, forward_DCT_method_ptr, (DCTELEM * data)); +typedef JMETHOD(void, float_DCT_method_ptr, (FAST_FLOAT * data)); + + +/* + * An inverse DCT routine is given a pointer to the input JBLOCK and a pointer + * to an output sample array. The routine must dequantize the input data as + * well as perform the IDCT; for dequantization, it uses the multiplier table + * pointed to by compptr->dct_table. The output data is to be placed into the + * sample array starting at a specified column. (Any row offset needed will + * be applied to the array pointer before it is passed to the IDCT code.) + * Note that the number of samples emitted by the IDCT routine is + * DCT_scaled_size * DCT_scaled_size. + */ + +/* typedef inverse_DCT_method_ptr is declared in jpegint.h */ + +/* + * Each IDCT routine has its own ideas about the best dct_table element type. + */ + +typedef MULTIPLIER ISLOW_MULT_TYPE; /* short or int, whichever is faster */ +#if BITS_IN_JSAMPLE == 8 +typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ +#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ +#else +typedef INT32 IFAST_MULT_TYPE; /* need 32 bits for scaled quantizers */ +#define IFAST_SCALE_BITS 13 /* fractional bits in scale factors */ +#endif +typedef FAST_FLOAT FLOAT_MULT_TYPE; /* preferred floating type */ + + +/* + * Each IDCT routine is responsible for range-limiting its results and + * converting them to unsigned form (0..MAXJSAMPLE). The raw outputs could + * be quite far out of range if the input data is corrupt, so a bulletproof + * range-limiting step is required. We use a mask-and-table-lookup method + * to do the combined operations quickly. See the comments with + * prepare_range_limit_table (in jdmaster.c) for more info. + */ + +#define IDCT_range_limit(cinfo) ((cinfo)->sample_range_limit + CENTERJSAMPLE) + +#define RANGE_MASK (MAXJSAMPLE * 4 + 3) /* 2 bits wider than legal samples */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_fdct_islow jFDislow +#define jpeg_fdct_ifast jFDifast +#define jpeg_fdct_float jFDfloat +#define jpeg_idct_islow jRDislow +#define jpeg_idct_ifast jRDifast +#define jpeg_idct_float jRDfloat +#define jpeg_idct_4x4 jRD4x4 +#define jpeg_idct_2x2 jRD2x2 +#define jpeg_idct_1x1 jRD1x1 +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Extern declarations for the forward and inverse DCT routines. */ + +EXTERN void jpeg_fdct_islow JPP((DCTELEM * data)); +EXTERN void jpeg_fdct_ifast JPP((DCTELEM * data)); +EXTERN void jpeg_fdct_float JPP((FAST_FLOAT * data)); + +EXTERN void jpeg_idct_islow + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_ifast + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_float + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_4x4 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_2x2 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_1x1 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + + +/* + * Macros for handling fixed-point arithmetic; these are used by many + * but not all of the DCT/IDCT modules. + * + * All values are expected to be of type INT32. + * Fractional constants are scaled left by CONST_BITS bits. + * CONST_BITS is defined within each module using these macros, + * and may differ from one module to the next. + */ + +#define ONE ((INT32) 1) +#define CONST_SCALE (ONE << CONST_BITS) + +/* Convert a positive real constant to an integer scaled by CONST_SCALE. + * Caution: some C compilers fail to reduce "FIX(constant)" at compile time, + * thus causing a lot of useless floating-point operations at run time. + */ + +#define FIX(x) ((INT32) ((x) * CONST_SCALE + 0.5)) + +/* Descale and correctly round an INT32 value that's scaled by N bits. + * We assume RIGHT_SHIFT rounds towards minus infinity, so adding + * the fudge factor is correct for either sign of X. + */ + +#define DESCALE(x,n) RIGHT_SHIFT((x) + (ONE << ((n)-1)), n) + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * This macro is used only when the two inputs will actually be no more than + * 16 bits wide, so that a 16x16->32 bit multiply can be used instead of a + * full 32x32 multiply. This provides a useful speedup on many machines. + * Unfortunately there is no way to specify a 16x16->32 multiply portably + * in C, but some C compilers will do the right thing if you provide the + * correct combination of casts. + */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT16) (const))) +#endif +#ifdef SHORTxLCONST_32 /* known to work with Microsoft C 6.0 */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT32) (const))) +#endif + +#ifndef MULTIPLY16C16 /* default definition */ +#define MULTIPLY16C16(var,const) ((var) * (const)) +#endif + +/* Same except both inputs are variables. */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16V16(var1,var2) (((INT16) (var1)) * ((INT16) (var2))) +#endif + +#ifndef MULTIPLY16V16 /* default definition */ +#define MULTIPLY16V16(var1,var2) ((var1) * (var2)) +#endif diff --git a/libs/jpeg6/jddctmgr.cpp b/libs/jpeg6/jddctmgr.cpp new file mode 100644 index 00000000..5267ade9 --- /dev/null +++ b/libs/jpeg6/jddctmgr.cpp @@ -0,0 +1,270 @@ +/* + * jddctmgr.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the inverse-DCT management logic. + * This code selects a particular IDCT implementation to be used, + * and it performs related housekeeping chores. No code in this file + * is executed per IDCT step, only during output pass setup. + * + * Note that the IDCT routines are responsible for performing coefficient + * dequantization as well as the IDCT proper. This module sets up the + * dequantization multiplier table needed by the IDCT routine. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* + * The decompressor input side (jdinput.c) saves away the appropriate + * quantization table for each component at the start of the first scan + * involving that component. (This is necessary in order to correctly + * decode files that reuse Q-table slots.) + * When we are ready to make an output pass, the saved Q-table is converted + * to a multiplier table that will actually be used by the IDCT routine. + * The multiplier table contents are IDCT-method-dependent. To support + * application changes in IDCT method between scans, we can remake the + * multiplier tables if necessary. + * In buffered-image mode, the first output pass may occur before any data + * has been seen for some components, and thus before their Q-tables have + * been saved away. To handle this case, multiplier tables are preset + * to zeroes; the result of the IDCT will be a neutral gray level. + */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_inverse_dct pub; /* public fields */ + + /* This array contains the IDCT method code that each multiplier table + * is currently set up for, or -1 if it's not yet set up. + * The actual multiplier tables are pointed to by dct_table in the + * per-component comp_info structures. + */ + int cur_method[MAX_COMPONENTS]; +} my_idct_controller; + +typedef my_idct_controller * my_idct_ptr; + + +/* Allocated multiplier tables: big enough for any supported variant */ + +typedef union { + ISLOW_MULT_TYPE islow_array[DCTSIZE2]; +#ifdef DCT_IFAST_SUPPORTED + IFAST_MULT_TYPE ifast_array[DCTSIZE2]; +#endif +#ifdef DCT_FLOAT_SUPPORTED + FLOAT_MULT_TYPE float_array[DCTSIZE2]; +#endif +} multiplier_table; + + +/* The current scaled-IDCT routines require ISLOW-style multiplier tables, + * so be sure to compile that code if either ISLOW or SCALING is requested. + */ +#ifdef DCT_ISLOW_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#else +#ifdef IDCT_SCALING_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#endif +#endif + + +/* + * Prepare for an output pass. + * Here we select the proper IDCT routine for each component and build + * a matching multiplier table. + */ + +METHODDEF void +start_pass (j_decompress_ptr cinfo) +{ + my_idct_ptr idct = (my_idct_ptr) cinfo->idct; + int ci, i; + jpeg_component_info *compptr; + int method = 0; + inverse_DCT_method_ptr method_ptr = NULL; + JQUANT_TBL * qtbl; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Select the proper IDCT routine for this component's scaling */ + switch (compptr->DCT_scaled_size) { +#ifdef IDCT_SCALING_SUPPORTED + case 1: + method_ptr = jpeg_idct_1x1; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; + case 2: + method_ptr = jpeg_idct_2x2; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; + case 4: + method_ptr = jpeg_idct_4x4; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; +#endif + case DCTSIZE: + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + method_ptr = jpeg_idct_islow; + method = JDCT_ISLOW; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + method_ptr = jpeg_idct_ifast; + method = JDCT_IFAST; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + method_ptr = jpeg_idct_float; + method = JDCT_FLOAT; + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + break; + default: + ERREXIT1(cinfo, JERR_BAD_DCTSIZE, compptr->DCT_scaled_size); + break; + } + idct->pub.inverse_DCT[ci] = method_ptr; + /* Create multiplier table from quant table. + * However, we can skip this if the component is uninteresting + * or if we already built the table. Also, if no quant table + * has yet been saved for the component, we leave the + * multiplier table all-zero; we'll be reading zeroes from the + * coefficient controller's buffer anyway. + */ + if (! compptr->component_needed || idct->cur_method[ci] == method) + continue; + qtbl = compptr->quant_table; + if (qtbl == NULL) /* happens if no data yet for component */ + continue; + idct->cur_method[ci] = method; + switch (method) { +#ifdef PROVIDE_ISLOW_TABLES + case JDCT_ISLOW: + { + /* For LL&M IDCT method, multipliers are equal to raw quantization + * coefficients, but are stored in natural order as ints. + */ + ISLOW_MULT_TYPE * ismtbl = (ISLOW_MULT_TYPE *) compptr->dct_table; + for (i = 0; i < DCTSIZE2; i++) { + ismtbl[i] = (ISLOW_MULT_TYPE) qtbl->quantval[jpeg_zigzag_order[i]]; + } + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * For integer operation, the multiplier table is to be scaled by + * IFAST_SCALE_BITS. The multipliers are stored in natural order. + */ + IFAST_MULT_TYPE * ifmtbl = (IFAST_MULT_TYPE *) compptr->dct_table; +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + for (i = 0; i < DCTSIZE2; i++) { + ifmtbl[i] = (IFAST_MULT_TYPE) + DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[jpeg_zigzag_order[i]], + (INT32) aanscales[i]), + CONST_BITS-IFAST_SCALE_BITS); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * The multipliers are stored in natural order. + */ + FLOAT_MULT_TYPE * fmtbl = (FLOAT_MULT_TYPE *) compptr->dct_table; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + i = 0; + for (row = 0; row < DCTSIZE; row++) { + for (col = 0; col < DCTSIZE; col++) { + fmtbl[i] = (FLOAT_MULT_TYPE) + ((double) qtbl->quantval[jpeg_zigzag_order[i]] * + aanscalefactor[row] * aanscalefactor[col]); + i++; + } + } + } + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + } +} + + +/* + * Initialize IDCT manager. + */ + +GLOBAL void +jinit_inverse_dct (j_decompress_ptr cinfo) +{ + my_idct_ptr idct; + int ci; + jpeg_component_info *compptr; + + idct = (my_idct_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_idct_controller)); + cinfo->idct = (struct jpeg_inverse_dct *) idct; + idct->pub.start_pass = start_pass; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Allocate and pre-zero a multiplier table for each component */ + compptr->dct_table = + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(multiplier_table)); + MEMZERO(compptr->dct_table, SIZEOF(multiplier_table)); + /* Mark multiplier table not yet set up for any method */ + idct->cur_method[ci] = -1; + } +} diff --git a/libs/jpeg6/jdhuff.cpp b/libs/jpeg6/jdhuff.cpp new file mode 100644 index 00000000..025d5187 --- /dev/null +++ b/libs/jpeg6/jdhuff.cpp @@ -0,0 +1,574 @@ +/* + * jdhuff.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy decoding routines. + * + * Much of the complexity here has to do with supporting input suspension. + * If the data source module demands suspension, we want to be able to back + * up to the start of the current MCU. To do this, we copy state variables + * into local working storage, and update them back to the permanent + * storage only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" +#include "jdhuff.h" /* Declarations shared with jdphuff.c */ + + +/* + * Expanded entropy decoder object for Huffman decoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_decoder pub; /* public fields */ + + /* These fields are loaded into local variables at start of each MCU. + * In case of suspension, we exit WITHOUT updating them. + */ + bitread_perm_state bitstate; /* Bit buffer at start of MCU */ + savable_state saved; /* Other state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + d_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; +} huff_entropy_decoder; + +typedef huff_entropy_decoder * huff_entropy_ptr; + + +/* + * Initialize for a Huffman-compressed scan. + */ + +METHODDEF void +start_pass_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + + /* Check that the scan parameters Ss, Se, Ah/Al are OK for sequential JPEG. + * This ought to be an error condition, but we make it a warning because + * there are some baseline files out there with all zeroes in these bytes. + */ + if (cinfo->Ss != 0 || cinfo->Se != DCTSIZE2-1 || + cinfo->Ah != 0 || cinfo->Al != 0) + WARNMS(cinfo, JWRN_NOT_SEQUENTIAL); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + /* Make sure requested tables are present */ + if (dctbl < 0 || dctbl >= NUM_HUFF_TBLS || + cinfo->dc_huff_tbl_ptrs[dctbl] == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, dctbl); + if (actbl < 0 || actbl >= NUM_HUFF_TBLS || + cinfo->ac_huff_tbl_ptrs[actbl] == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, actbl); + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_d_derived_tbl(cinfo, cinfo->dc_huff_tbl_ptrs[dctbl], + & entropy->dc_derived_tbls[dctbl]); + jpeg_make_d_derived_tbl(cinfo, cinfo->ac_huff_tbl_ptrs[actbl], + & entropy->ac_derived_tbls[actbl]); + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bitread state variables */ + entropy->bitstate.bits_left = 0; + entropy->bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */ + entropy->bitstate.printed_eod = FALSE; + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Compute the derived values for a Huffman table. + * Note this is also used by jdphuff.c. + */ + +GLOBAL void +jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, JHUFF_TBL * htbl, + d_derived_tbl ** pdtbl) +{ + d_derived_tbl *dtbl; + int p, i, l, si; + int lookbits, ctr; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Allocate a workspace if we haven't already done so. */ + if (*pdtbl == NULL) + *pdtbl = (d_derived_tbl *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(d_derived_tbl)); + dtbl = *pdtbl; + dtbl->pub = htbl; /* fill in back link */ + + /* Figure C.1: make table of Huffman code length for each symbol */ + /* Note that this is in code-length order. */ + + p = 0; + for (l = 1; l <= 16; l++) { + for (i = 1; i <= (int) htbl->bits[l]; i++) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + + /* Figure C.2: generate the codes themselves */ + /* Note that this is in code-length order. */ + + code = 0; + si = huffsize[0]; + p = 0; + while (huffsize[p]) { + while (((int) huffsize[p]) == si) { + huffcode[p++] = code; + code++; + } + code <<= 1; + si++; + } + + /* Figure F.15: generate decoding tables for bit-sequential decoding */ + + p = 0; + for (l = 1; l <= 16; l++) { + if (htbl->bits[l]) { + dtbl->valptr[l] = p; /* huffval[] index of 1st symbol of code length l */ + dtbl->mincode[l] = huffcode[p]; /* minimum code of length l */ + p += htbl->bits[l]; + dtbl->maxcode[l] = huffcode[p-1]; /* maximum code of length l */ + } else { + dtbl->maxcode[l] = -1; /* -1 if no codes of this length */ + } + } + dtbl->maxcode[17] = 0xFFFFFL; /* ensures jpeg_huff_decode terminates */ + + /* Compute lookahead tables to speed up decoding. + * First we set all the table entries to 0, indicating "too long"; + * then we iterate through the Huffman codes that are short enough and + * fill in all the entries that correspond to bit sequences starting + * with that code. + */ + + MEMZERO(dtbl->look_nbits, SIZEOF(dtbl->look_nbits)); + + p = 0; + for (l = 1; l <= HUFF_LOOKAHEAD; l++) { + for (i = 1; i <= (int) htbl->bits[l]; i++, p++) { + /* l = current code's length, p = its index in huffcode[] & huffval[]. */ + /* Generate left-justified code followed by all possible bit sequences */ + lookbits = huffcode[p] << (HUFF_LOOKAHEAD-l); + for (ctr = 1 << (HUFF_LOOKAHEAD-l); ctr > 0; ctr--) { + dtbl->look_nbits[lookbits] = l; + dtbl->look_sym[lookbits] = htbl->huffval[p]; + lookbits++; + } + } + } +} + + +/* + * Out-of-line code for bit fetching (shared with jdphuff.c). + * See jdhuff.h for info about usage. + * Note: current values of get_buffer and bits_left are passed as parameters, + * but are returned in the corresponding fields of the state struct. + * + * On most machines MIN_GET_BITS should be 25 to allow the full 32-bit width + * of get_buffer to be used. (On machines with wider words, an even larger + * buffer could be used.) However, on some machines 32-bit shifts are + * quite slow and take time proportional to the number of places shifted. + * (This is true with most PC compilers, for instance.) In this case it may + * be a win to set MIN_GET_BITS to the minimum value of 15. This reduces the + * average shift distance at the cost of more calls to jpeg_fill_bit_buffer. + */ + +#ifdef SLOW_SHIFT_32 +#define MIN_GET_BITS 15 /* minimum allowable value */ +#else +#define MIN_GET_BITS (BIT_BUF_SIZE-7) +#endif + + +GLOBAL boolean +jpeg_fill_bit_buffer (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits) +/* Load up the bit buffer to a depth of at least nbits */ +{ + /* Copy heavily used state fields into locals (hopefully registers) */ + register const JOCTET * next_input_byte = state->next_input_byte; + register size_t bytes_in_buffer = state->bytes_in_buffer; + register int c; + + /* Attempt to load at least MIN_GET_BITS bits into get_buffer. */ + /* (It is assumed that no request will be for more than that many bits.) */ + + while (bits_left < MIN_GET_BITS) { + /* Attempt to read a byte */ + if (state->unread_marker != 0) + goto no_more_data; /* can't advance past a marker */ + + if (bytes_in_buffer == 0) { + if (! (*state->cinfo->src->fill_input_buffer) (state->cinfo)) + return FALSE; + next_input_byte = state->cinfo->src->next_input_byte; + bytes_in_buffer = state->cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + + /* If it's 0xFF, check and discard stuffed zero byte */ + if (c == 0xFF) { + do { + if (bytes_in_buffer == 0) { + if (! (*state->cinfo->src->fill_input_buffer) (state->cinfo)) + return FALSE; + next_input_byte = state->cinfo->src->next_input_byte; + bytes_in_buffer = state->cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + } while (c == 0xFF); + + if (c == 0) { + /* Found FF/00, which represents an FF data byte */ + c = 0xFF; + } else { + /* Oops, it's actually a marker indicating end of compressed data. */ + /* Better put it back for use later */ + state->unread_marker = c; + + no_more_data: + /* There should be enough bits still left in the data segment; */ + /* if so, just break out of the outer while loop. */ + if (bits_left >= nbits) + break; + /* Uh-oh. Report corrupted data to user and stuff zeroes into + * the data stream, so that we can produce some kind of image. + * Note that this code will be repeated for each byte demanded + * for the rest of the segment. We use a nonvolatile flag to ensure + * that only one warning message appears. + */ + if (! *(state->printed_eod_ptr)) { + WARNMS(state->cinfo, JWRN_HIT_MARKER); + *(state->printed_eod_ptr) = TRUE; + } + c = 0; /* insert a zero byte into bit buffer */ + } + } + + /* OK, load c into get_buffer */ + get_buffer = (get_buffer << 8) | c; + bits_left += 8; + } + + /* Unload the local registers */ + state->next_input_byte = next_input_byte; + state->bytes_in_buffer = bytes_in_buffer; + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + return TRUE; +} + + +/* + * Out-of-line code for Huffman code decoding. + * See jdhuff.h for info about usage. + */ + +GLOBAL int +jpeg_huff_decode (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits) +{ + register int l = min_bits; + register INT32 code; + + /* HUFF_DECODE has determined that the code is at least min_bits */ + /* bits long, so fetch that many bits in one swoop. */ + + CHECK_BIT_BUFFER(*state, l, return -1); + code = GET_BITS(l); + + /* Collect the rest of the Huffman code one bit at a time. */ + /* This is per Figure F.16 in the JPEG spec. */ + + while (code > htbl->maxcode[l]) { + code <<= 1; + CHECK_BIT_BUFFER(*state, 1, return -1); + code |= GET_BITS(1); + l++; + } + + /* Unload the local registers */ + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + /* With garbage input we may reach the sentinel value l = 17. */ + + if (l > 16) { + WARNMS(state->cinfo, JWRN_HUFF_BAD_CODE); + return 0; /* fake a zero as the safest result */ + } + + return htbl->pub->huffval[ htbl->valptr[l] + + ((int) (code - htbl->mincode[l])) ]; +} + + +/* + * Figure F.12: extend sign bit. + * On some machines, a shift and add will be faster than a table lookup. + */ + +#ifdef AVOID_TABLES + +#define HUFF_EXTEND(x,s) ((x) < (1<<((s)-1)) ? (x) + (((-1)<<(s)) + 1) : (x)) + +#else + +#define HUFF_EXTEND(x,s) ((x) < extend_test[s] ? (x) + extend_offset[s] : (x)) + +static const int extend_test[16] = /* entry n is 2**(n-1) */ + { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, + 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; + +static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */ + { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1, + ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1, + ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1, + ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 }; + +#endif /* AVOID_TABLES */ + + +/* + * Check for a restart marker & resynchronize decoder. + * Returns FALSE if must suspend. + */ + +LOCAL boolean +process_restart (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci; + + /* Throw away any unused bits remaining in bit buffer; */ + /* include any full bytes in next_marker's count of discarded bytes */ + cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8; + entropy->bitstate.bits_left = 0; + + /* Advance past the RSTn marker */ + if (! (*cinfo->marker->read_restart_marker) (cinfo)) + return FALSE; + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; + + /* Next segment can get another out-of-data warning */ + entropy->bitstate.printed_eod = FALSE; + + return TRUE; +} + + +/* + * Decode and return one MCU's worth of Huffman-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA HAS BEEN ZEROED BY THE CALLER. + * (Wholesale zeroing is usually a little faster than retail...) + * + * Returns FALSE if data source requested suspension. In that case no + * changes have been made to permanent state. (Exception: some output + * coefficients may already have been assigned. This is harmless for + * this module, since we'll just re-assign them on the next call.) + */ + +METHODDEF boolean +decode_mcu (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + register int s, k, r; + int blkn, ci; + JBLOCKROW block; + BITREAD_STATE_VARS; + savable_state state; + d_derived_tbl * dctbl; + d_derived_tbl * actbl; + jpeg_component_info * compptr; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(state, entropy->saved); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + dctbl = entropy->dc_derived_tbls[compptr->dc_tbl_no]; + actbl = entropy->ac_derived_tbls[compptr->ac_tbl_no]; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + HUFF_DECODE(s, br_state, dctbl, return FALSE, label1); + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + } + + /* Shortcut if component's values are not interesting */ + if (! compptr->component_needed) + goto skip_ACs; + + /* Convert DC difference to actual value, update last_dc_val */ + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Output the DC coefficient (assumes jpeg_natural_order[0] = 0) */ + (*block)[0] = (JCOEF) s; + + /* Do we need to decode the AC coefficients for this component? */ + if (compptr->DCT_scaled_size > 1) { + + /* Section F.2.2.2: decode the AC coefficients */ + /* Since zeroes are skipped, output area must be cleared beforehand */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label2); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + /* Output coefficient in natural (dezigzagged) order. + * Note: the extra entries in jpeg_natural_order[] will save us + * if k >= DCTSIZE2, which could happen if the data is corrupted. + */ + (*block)[jpeg_natural_order[k]] = (JCOEF) s; + } else { + if (r != 15) + break; + k += 15; + } + } + + } else { +skip_ACs: + + /* Section F.2.2.2: decode the AC coefficients */ + /* In this path we just discard the values */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label3); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + DROP_BITS(s); + } else { + if (r != 15) + break; + k += 15; + } + } + + } + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(entropy->saved, state); + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * Module initialization routine for Huffman entropy decoding. + */ + +GLOBAL void +jinit_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(huff_entropy_decoder)); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass_huff_decoder; + entropy->pub.decode_mcu = decode_mcu; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; + } +} diff --git a/libs/jpeg6/jdhuff.h b/libs/jpeg6/jdhuff.h new file mode 100644 index 00000000..d375c781 --- /dev/null +++ b/libs/jpeg6/jdhuff.h @@ -0,0 +1,202 @@ +/* + * jdhuff.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy decoding routines + * that are shared between the sequential decoder (jdhuff.c) and the + * progressive decoder (jdphuff.c). No other modules need to see these. + */ + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_d_derived_tbl jMkDDerived +#define jpeg_fill_bit_buffer jFilBitBuf +#define jpeg_huff_decode jHufDecode +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Derived data constructed for each Huffman table */ + +#define HUFF_LOOKAHEAD 8 /* # of bits of lookahead */ + +typedef struct { + /* Basic tables: (element [0] of each array is unused) */ + INT32 mincode[17]; /* smallest code of length k */ + INT32 maxcode[18]; /* largest code of length k (-1 if none) */ + /* (maxcode[17] is a sentinel to ensure jpeg_huff_decode terminates) */ + int valptr[17]; /* huffval[] index of 1st symbol of length k */ + + /* Link to public Huffman table (needed only in jpeg_huff_decode) */ + JHUFF_TBL *pub; + + /* Lookahead tables: indexed by the next HUFF_LOOKAHEAD bits of + * the input data stream. If the next Huffman code is no more + * than HUFF_LOOKAHEAD bits long, we can obtain its length and + * the corresponding symbol directly from these tables. + */ + int look_nbits[1< 32 bits on your machine, and shifting/masking longs is + * reasonably fast, making bit_buf_type be long and setting BIT_BUF_SIZE + * appropriately should be a win. Unfortunately we can't do this with + * something like #define BIT_BUF_SIZE (sizeof(bit_buf_type)*8) + * because not all machines measure sizeof in 8-bit bytes. + */ + +typedef struct { /* Bitreading state saved across MCUs */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + boolean printed_eod; /* flag to suppress multiple warning msgs */ +} bitread_perm_state; + +typedef struct { /* Bitreading working state within an MCU */ + /* current data source state */ + const JOCTET * next_input_byte; /* => next byte to read from source */ + size_t bytes_in_buffer; /* # of bytes remaining in source buffer */ + int unread_marker; /* nonzero if we have hit a marker */ + /* bit input buffer --- note these values are kept in register variables, + * not in this struct, inside the inner loops. + */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + /* pointers needed by jpeg_fill_bit_buffer */ + j_decompress_ptr cinfo; /* back link to decompress master record */ + boolean * printed_eod_ptr; /* => flag in permanent state */ +} bitread_working_state; + +/* Macros to declare and load/save bitread local variables. */ +#define BITREAD_STATE_VARS \ + register bit_buf_type get_buffer; \ + register int bits_left; \ + bitread_working_state br_state + +#define BITREAD_LOAD_STATE(cinfop,permstate) \ + br_state.cinfo = cinfop; \ + br_state.next_input_byte = cinfop->src->next_input_byte; \ + br_state.bytes_in_buffer = cinfop->src->bytes_in_buffer; \ + br_state.unread_marker = cinfop->unread_marker; \ + get_buffer = permstate.get_buffer; \ + bits_left = permstate.bits_left; \ + br_state.printed_eod_ptr = & permstate.printed_eod + +#define BITREAD_SAVE_STATE(cinfop,permstate) \ + cinfop->src->next_input_byte = br_state.next_input_byte; \ + cinfop->src->bytes_in_buffer = br_state.bytes_in_buffer; \ + cinfop->unread_marker = br_state.unread_marker; \ + permstate.get_buffer = get_buffer; \ + permstate.bits_left = bits_left + +/* + * These macros provide the in-line portion of bit fetching. + * Use CHECK_BIT_BUFFER to ensure there are N bits in get_buffer + * before using GET_BITS, PEEK_BITS, or DROP_BITS. + * The variables get_buffer and bits_left are assumed to be locals, + * but the state struct might not be (jpeg_huff_decode needs this). + * CHECK_BIT_BUFFER(state,n,action); + * Ensure there are N bits in get_buffer; if suspend, take action. + * val = GET_BITS(n); + * Fetch next N bits. + * val = PEEK_BITS(n); + * Fetch next N bits without removing them from the buffer. + * DROP_BITS(n); + * Discard next N bits. + * The value N should be a simple variable, not an expression, because it + * is evaluated multiple times. + */ + +#define CHECK_BIT_BUFFER(state,nbits,action) \ + { if (bits_left < (nbits)) { \ + if (! jpeg_fill_bit_buffer(&(state),get_buffer,bits_left,nbits)) \ + { action; } \ + get_buffer = (state).get_buffer; bits_left = (state).bits_left; } } + +#define GET_BITS(nbits) \ + (((int) (get_buffer >> (bits_left -= (nbits)))) & ((1<<(nbits))-1)) + +#define PEEK_BITS(nbits) \ + (((int) (get_buffer >> (bits_left - (nbits)))) & ((1<<(nbits))-1)) + +#define DROP_BITS(nbits) \ + (bits_left -= (nbits)) + +/* Load up the bit buffer to a depth of at least nbits */ +EXTERN boolean jpeg_fill_bit_buffer JPP((bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits)); + + +/* + * Code for extracting next Huffman-coded symbol from input bit stream. + * Again, this is time-critical and we make the main paths be macros. + * + * We use a lookahead table to process codes of up to HUFF_LOOKAHEAD bits + * without looping. Usually, more than 95% of the Huffman codes will be 8 + * or fewer bits long. The few overlength codes are handled with a loop, + * which need not be inline code. + * + * Notes about the HUFF_DECODE macro: + * 1. Near the end of the data segment, we may fail to get enough bits + * for a lookahead. In that case, we do it the hard way. + * 2. If the lookahead table contains no entry, the next code must be + * more than HUFF_LOOKAHEAD bits long. + * 3. jpeg_huff_decode returns -1 if forced to suspend. + */ + +#define HUFF_DECODE(result,state,htbl,failaction,slowlabel) \ +{ register int nb, look; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + if (! jpeg_fill_bit_buffer(&state,get_buffer,bits_left, 0)) {failaction;} \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + nb = 1; goto slowlabel; \ + } \ + } \ + look = PEEK_BITS(HUFF_LOOKAHEAD); \ + if ((nb = htbl->look_nbits[look]) != 0) { \ + DROP_BITS(nb); \ + result = htbl->look_sym[look]; \ + } else { \ + nb = HUFF_LOOKAHEAD+1; \ +slowlabel: \ + if ((result=jpeg_huff_decode(&state,get_buffer,bits_left,htbl,nb)) < 0) \ + { failaction; } \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + } \ +} + +/* Out-of-line case for Huffman code fetching */ +EXTERN int jpeg_huff_decode JPP((bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits)); diff --git a/libs/jpeg6/jdinput.cpp b/libs/jpeg6/jdinput.cpp new file mode 100644 index 00000000..f48307d0 --- /dev/null +++ b/libs/jpeg6/jdinput.cpp @@ -0,0 +1,381 @@ +/* + * jdinput.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input control logic for the JPEG decompressor. + * These routines are concerned with controlling the decompressor's input + * processing (marker reading and coefficient decoding). The actual input + * reading is done in jdmarker.c, jdhuff.c, and jdphuff.c. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_input_controller pub; /* public fields */ + + boolean inheaders; /* TRUE until first SOS is reached */ +} my_input_controller; + +typedef my_input_controller * my_inputctl_ptr; + + +/* Forward declarations */ +METHODDEF int consume_markers JPP((j_decompress_ptr cinfo)); + + +/* + * Routines to calculate various quantities related to the size of the image. + */ + +LOCAL void +initial_setup (j_decompress_ptr cinfo) +/* Called once, when first SOS marker is reached */ +{ + int ci; + jpeg_component_info *compptr; + + /* Make sure image isn't bigger than I can handle */ + if ((long) cinfo->image_height > (long) JPEG_MAX_DIMENSION || + (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION); + + /* For now, precision must match compiled-in value... */ + if (cinfo->data_precision != BITS_IN_JSAMPLE) + ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); + + /* Check that number of components won't exceed internal array sizes */ + if (cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR || + compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) + ERREXIT(cinfo, JERR_BAD_SAMPLING); + cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor, + compptr->h_samp_factor); + cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor, + compptr->v_samp_factor); + } + + /* We initialize DCT_scaled_size and min_DCT_scaled_size to DCTSIZE. + * In the full decompressor, this will be overridden by jdmaster.c; + * but in the transcoder, jdmaster.c is not used, so we must do it here. + */ + cinfo->min_DCT_scaled_size = DCTSIZE; + + /* Compute dimensions of components */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->DCT_scaled_size = DCTSIZE; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + /* downsampled_width and downsampled_height will also be overridden by + * jdmaster.c if we are doing full decompression. The transcoder library + * doesn't use these values, but the calling application might. + */ + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor); + /* Mark component needed, until color conversion says otherwise */ + compptr->component_needed = TRUE; + /* Mark no quantization table yet saved for component */ + compptr->quant_table = NULL; + } + + /* Compute number of fully interleaved MCU rows. */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + /* Decide whether file contains multiple scans */ + if (cinfo->comps_in_scan < cinfo->num_components || cinfo->progressive_mode) + cinfo->inputctl->has_multiple_scans = TRUE; + else + cinfo->inputctl->has_multiple_scans = FALSE; +} + + +LOCAL void +per_scan_setup (j_decompress_ptr cinfo) +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] were set from SOS marker */ +{ + int ci, mcublks, tmp; + jpeg_component_info *compptr; + + if (cinfo->comps_in_scan == 1) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = compptr->DCT_scaled_size; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (tmp == 0) tmp = compptr->v_samp_factor; + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN); + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, + (long) (cinfo->max_h_samp_factor*DCTSIZE)); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + cinfo->blocks_in_MCU = 0; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * compptr->DCT_scaled_size; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) (compptr->width_in_blocks % compptr->MCU_width); + if (tmp == 0) tmp = compptr->MCU_width; + compptr->last_col_width = tmp; + tmp = (int) (compptr->height_in_blocks % compptr->MCU_height); + if (tmp == 0) tmp = compptr->MCU_height; + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if (cinfo->blocks_in_MCU + mcublks > D_MAX_BLOCKS_IN_MCU) + ERREXIT(cinfo, JERR_BAD_MCU_SIZE); + while (mcublks-- > 0) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } +} + + +/* + * Save away a copy of the Q-table referenced by each component present + * in the current scan, unless already saved during a prior scan. + * + * In a multiple-scan JPEG file, the encoder could assign different components + * the same Q-table slot number, but change table definitions between scans + * so that each component uses a different Q-table. (The IJG encoder is not + * currently capable of doing this, but other encoders might.) Since we want + * to be able to dequantize all the components at the end of the file, this + * means that we have to save away the table actually used for each component. + * We do this by copying the table at the start of the first scan containing + * the component. + * The JPEG spec prohibits the encoder from changing the contents of a Q-table + * slot between scans of a component using that slot. If the encoder does so + * anyway, this decoder will simply use the Q-table values that were current + * at the start of the first scan for the component. + * + * The decompressor output side looks only at the saved quant tables, + * not at the current Q-table slots. + */ + +LOCAL void +latch_quant_tables (j_decompress_ptr cinfo) +{ + int ci, qtblno; + jpeg_component_info *compptr; + JQUANT_TBL * qtbl; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* No work if we already saved Q-table for this component */ + if (compptr->quant_table != NULL) + continue; + /* Make sure specified quantization table is present */ + qtblno = compptr->quant_tbl_no; + if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + cinfo->quant_tbl_ptrs[qtblno] == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno); + /* OK, save away the quantization table */ + qtbl = (JQUANT_TBL *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(JQUANT_TBL)); + MEMCOPY(qtbl, cinfo->quant_tbl_ptrs[qtblno], SIZEOF(JQUANT_TBL)); + compptr->quant_table = qtbl; + } +} + + +/* + * Initialize the input modules to read a scan of compressed data. + * The first call to this is done by jdmaster.c after initializing + * the entire decompressor (during jpeg_start_decompress). + * Subsequent calls come from consume_markers, below. + */ + +METHODDEF void +start_input_pass (j_decompress_ptr cinfo) +{ + per_scan_setup(cinfo); + latch_quant_tables(cinfo); + (*cinfo->entropy->start_pass) (cinfo); + (*cinfo->coef->start_input_pass) (cinfo); + cinfo->inputctl->consume_input = cinfo->coef->consume_data; +} + + +/* + * Finish up after inputting a compressed-data scan. + * This is called by the coefficient controller after it's read all + * the expected data of the scan. + */ + +METHODDEF void +finish_input_pass (j_decompress_ptr cinfo) +{ + cinfo->inputctl->consume_input = consume_markers; +} + + +/* + * Read JPEG markers before, between, or after compressed-data scans. + * Change state as necessary when a new scan is reached. + * Return value is JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + * + * The consume_input method pointer points either here or to the + * coefficient controller's consume_data routine, depending on whether + * we are reading a compressed data segment or inter-segment markers. + */ + +METHODDEF int +consume_markers (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + int val; + + if (inputctl->pub.eoi_reached) /* After hitting EOI, read no further */ + return JPEG_REACHED_EOI; + + val = (*cinfo->marker->read_markers) (cinfo); + + switch (val) { + case JPEG_REACHED_SOS: /* Found SOS */ + if (inputctl->inheaders) { /* 1st SOS */ + initial_setup(cinfo); + inputctl->inheaders = FALSE; + /* Note: start_input_pass must be called by jdmaster.c + * before any more input can be consumed. jdapi.c is + * responsible for enforcing this sequencing. + */ + } else { /* 2nd or later SOS marker */ + if (! inputctl->pub.has_multiple_scans) + ERREXIT(cinfo, JERR_EOI_EXPECTED); /* Oops, I wasn't expecting this! */ + start_input_pass(cinfo); + } + break; + case JPEG_REACHED_EOI: /* Found EOI */ + inputctl->pub.eoi_reached = TRUE; + if (inputctl->inheaders) { /* Tables-only datastream, apparently */ + if (cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOF_NO_SOS); + } else { + /* Prevent infinite loop in coef ctlr's decompress_data routine + * if user set output_scan_number larger than number of scans. + */ + if (cinfo->output_scan_number > cinfo->input_scan_number) + cinfo->output_scan_number = cinfo->input_scan_number; + } + break; + case JPEG_SUSPENDED: + break; + } + + return val; +} + + +/* + * Reset state to begin a fresh datastream. + */ + +METHODDEF void +reset_input_controller (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + + inputctl->pub.consume_input = consume_markers; + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; + /* Reset other modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->marker->reset_marker_reader) (cinfo); + /* Reset progression state -- would be cleaner if entropy decoder did this */ + cinfo->coef_bits = NULL; +} + + +/* + * Initialize the input controller module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL void +jinit_input_controller (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl; + + /* Create subobject in permanent pool */ + inputctl = (my_inputctl_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_input_controller)); + cinfo->inputctl = (struct jpeg_input_controller *) inputctl; + /* Initialize method pointers */ + inputctl->pub.consume_input = consume_markers; + inputctl->pub.reset_input_controller = reset_input_controller; + inputctl->pub.start_input_pass = start_input_pass; + inputctl->pub.finish_input_pass = finish_input_pass; + /* Initialize state: can't use reset_input_controller since we don't + * want to try to reset other modules yet. + */ + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; +} diff --git a/libs/jpeg6/jdmainct.cpp b/libs/jpeg6/jdmainct.cpp new file mode 100644 index 00000000..d445649f --- /dev/null +++ b/libs/jpeg6/jdmainct.cpp @@ -0,0 +1,512 @@ +/* + * jdmainct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for decompression. + * The main buffer lies between the JPEG decompressor proper and the + * post-processor; it holds downsampled data in the JPEG colorspace. + * + * Note that this code is bypassed in raw-data mode, since the application + * supplies the equivalent of the main buffer in that case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +/* + * In the current system design, the main buffer need never be a full-image + * buffer; any full-height buffers will be found inside the coefficient or + * postprocessing controllers. Nonetheless, the main controller is not + * trivial. Its responsibility is to provide context rows for upsampling/ + * rescaling, and doing this in an efficient fashion is a bit tricky. + * + * Postprocessor input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. (We require DCT_scaled_size values to be + * chosen such that these numbers are integers. In practice DCT_scaled_size + * values will likely be powers of two, so we actually have the stronger + * condition that DCT_scaled_size / min_DCT_scaled_size is an integer.) + * Upsampling will typically produce max_v_samp_factor pixel rows from each + * row group (times any additional scale factor that the upsampler is + * applying). + * + * The coefficient controller will deliver data to us one iMCU row at a time; + * each iMCU row contains v_samp_factor * DCT_scaled_size sample rows, or + * exactly min_DCT_scaled_size row groups. (This amount of data corresponds + * to one row of MCUs when the image is fully interleaved.) Note that the + * number of sample rows varies across components, but the number of row + * groups does not. Some garbage sample rows may be included in the last iMCU + * row at the bottom of the image. + * + * Depending on the vertical scaling algorithm used, the upsampler may need + * access to the sample row(s) above and below its current input row group. + * The upsampler is required to set need_context_rows TRUE at global selection + * time if so. When need_context_rows is FALSE, this controller can simply + * obtain one iMCU row at a time from the coefficient controller and dole it + * out as row groups to the postprocessor. + * + * When need_context_rows is TRUE, this controller guarantees that the buffer + * passed to postprocessing contains at least one row group's worth of samples + * above and below the row group(s) being processed. Note that the context + * rows "above" the first passed row group appear at negative row offsets in + * the passed buffer. At the top and bottom of the image, the required + * context rows are manufactured by duplicating the first or last real sample + * row; this avoids having special cases in the upsampling inner loops. + * + * The amount of context is fixed at one row group just because that's a + * convenient number for this controller to work with. The existing + * upsamplers really only need one sample row of context. An upsampler + * supporting arbitrary output rescaling might wish for more than one row + * group of context when shrinking the image; tough, we don't handle that. + * (This is justified by the assumption that downsizing will be handled mostly + * by adjusting the DCT_scaled_size values, so that the actual scale factor at + * the upsample step needn't be much less than one.) + * + * To provide the desired context, we have to retain the last two row groups + * of one iMCU row while reading in the next iMCU row. (The last row group + * can't be processed until we have another row group for its below-context, + * and so we have to save the next-to-last group too for its above-context.) + * We could do this most simply by copying data around in our buffer, but + * that'd be very slow. We can avoid copying any data by creating a rather + * strange pointer structure. Here's how it works. We allocate a workspace + * consisting of M+2 row groups (where M = min_DCT_scaled_size is the number + * of row groups per iMCU row). We create two sets of redundant pointers to + * the workspace. Labeling the physical row groups 0 to M+1, the synthesized + * pointer lists look like this: + * M+1 M-1 + * master pointer --> 0 master pointer --> 0 + * 1 1 + * ... ... + * M-3 M-3 + * M-2 M + * M-1 M+1 + * M M-2 + * M+1 M-1 + * 0 0 + * We read alternate iMCU rows using each master pointer; thus the last two + * row groups of the previous iMCU row remain un-overwritten in the workspace. + * The pointer lists are set up so that the required context rows appear to + * be adjacent to the proper places when we pass the pointer lists to the + * upsampler. + * + * The above pictures describe the normal state of the pointer lists. + * At top and bottom of the image, we diddle the pointer lists to duplicate + * the first or last sample row as necessary (this is cheaper than copying + * sample rows around). + * + * This scheme breaks down if M < 2, ie, min_DCT_scaled_size is 1. In that + * situation each iMCU row provides only one row group so the buffering logic + * must be different (eg, we must read two iMCU rows before we can emit the + * first row group). For now, we simply do not support providing context + * rows when min_DCT_scaled_size is 1. That combination seems unlikely to + * be worth providing --- if someone wants a 1/8th-size preview, they probably + * want it quick and dirty, so a context-free upsampler is sufficient. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_main_controller pub; /* public fields */ + + /* Pointer to allocated workspace (M or M+2 row groups). */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + + boolean buffer_full; /* Have we gotten an iMCU row from decoder? */ + JDIMENSION rowgroup_ctr; /* counts row groups output to postprocessor */ + + /* Remaining fields are only used in the context case. */ + + /* These are the master pointers to the funny-order pointer lists. */ + JSAMPIMAGE xbuffer[2]; /* pointers to weird pointer lists */ + + int whichptr; /* indicates which pointer set is now in use */ + int context_state; /* process_data state machine status */ + JDIMENSION rowgroups_avail; /* row groups available to postprocessor */ + JDIMENSION iMCU_row_ctr; /* counts iMCU rows to detect image top/bot */ +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + +/* context_state values: */ +#define CTX_PREPARE_FOR_IMCU 0 /* need to prepare for MCU row */ +#define CTX_PROCESS_IMCU 1 /* feeding iMCU to postprocessor */ +#define CTX_POSTPONED_ROW 2 /* feeding postponed row group */ + + +/* Forward declarations */ +METHODDEF void process_data_simple_main + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +METHODDEF void process_data_context_main + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF void process_data_crank_post + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +#endif + + +LOCAL void +alloc_funny_pointers (j_decompress_ptr cinfo) +/* Allocate space for the funny pointer lists. + * This is done only once, not once per pass. + */ +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + /* Get top-level space for component array pointers. + * We alloc both arrays with one call to save a few cycles. + */ + main->xbuffer[0] = (JSAMPIMAGE) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * 2 * SIZEOF(JSAMPARRAY)); + main->xbuffer[1] = main->xbuffer[0] + cinfo->num_components; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + /* Get space for pointer lists --- M+4 row groups in each list. + * We alloc both pointer lists with one call to save a few cycles. + */ + xbuf = (JSAMPARRAY) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 2 * (rgroup * (M + 4)) * SIZEOF(JSAMPROW)); + xbuf += rgroup; /* want one row group at negative offsets */ + main->xbuffer[0][ci] = xbuf; + xbuf += rgroup * (M + 4); + main->xbuffer[1][ci] = xbuf; + } +} + + +LOCAL void +make_funny_pointers (j_decompress_ptr cinfo) +/* Create the funny pointer lists discussed in the comments above. + * The actual workspace is already allocated (in main->buffer), + * and the space for the pointer lists is allocated too. + * This routine just fills in the curiously ordered lists. + * This will be repeated at the beginning of each pass. + */ +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY buf, xbuf0, xbuf1; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = main->xbuffer[0][ci]; + xbuf1 = main->xbuffer[1][ci]; + /* First copy the workspace pointers as-is */ + buf = main->buffer[ci]; + for (i = 0; i < rgroup * (M + 2); i++) { + xbuf0[i] = xbuf1[i] = buf[i]; + } + /* In the second list, put the last four row groups in swapped order */ + for (i = 0; i < rgroup * 2; i++) { + xbuf1[rgroup*(M-2) + i] = buf[rgroup*M + i]; + xbuf1[rgroup*M + i] = buf[rgroup*(M-2) + i]; + } + /* The wraparound pointers at top and bottom will be filled later + * (see set_wraparound_pointers, below). Initially we want the "above" + * pointers to duplicate the first actual data line. This only needs + * to happen in xbuffer[0]. + */ + for (i = 0; i < rgroup; i++) { + xbuf0[i - rgroup] = xbuf0[0]; + } + } +} + + +LOCAL void +set_wraparound_pointers (j_decompress_ptr cinfo) +/* Set up the "wraparound" pointers at top and bottom of the pointer lists. + * This changes the pointer list state from top-of-image to the normal state. + */ +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf0, xbuf1; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = main->xbuffer[0][ci]; + xbuf1 = main->xbuffer[1][ci]; + for (i = 0; i < rgroup; i++) { + xbuf0[i - rgroup] = xbuf0[rgroup*(M+1) + i]; + xbuf1[i - rgroup] = xbuf1[rgroup*(M+1) + i]; + xbuf0[rgroup*(M+2) + i] = xbuf0[i]; + xbuf1[rgroup*(M+2) + i] = xbuf1[i]; + } + } +} + + +LOCAL void +set_bottom_pointers (j_decompress_ptr cinfo) +/* Change the pointer lists to duplicate the last sample row at the bottom + * of the image. whichptr indicates which xbuffer holds the final iMCU row. + * Also sets rowgroups_avail to indicate number of nondummy row groups in row. + */ +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, i, rgroup, iMCUheight, rows_left; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Count sample rows in one iMCU row and in one row group */ + iMCUheight = compptr->v_samp_factor * compptr->DCT_scaled_size; + rgroup = iMCUheight / cinfo->min_DCT_scaled_size; + /* Count nondummy sample rows remaining for this component */ + rows_left = (int) (compptr->downsampled_height % (JDIMENSION) iMCUheight); + if (rows_left == 0) rows_left = iMCUheight; + /* Count nondummy row groups. Should get same answer for each component, + * so we need only do it once. + */ + if (ci == 0) { + main->rowgroups_avail = (JDIMENSION) ((rows_left-1) / rgroup + 1); + } + /* Duplicate the last real sample row rgroup*2 times; this pads out the + * last partial rowgroup and ensures at least one full rowgroup of context. + */ + xbuf = main->xbuffer[main->whichptr][ci]; + for (i = 0; i < rgroup * 2; i++) { + xbuf[rows_left + i] = xbuf[rows_left-1]; + } + } +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_main (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (cinfo->upsample->need_context_rows) { + main->pub.process_data = process_data_context_main; + make_funny_pointers(cinfo); /* Create the xbuffer[] lists */ + main->whichptr = 0; /* Read first iMCU row into xbuffer[0] */ + main->context_state = CTX_PREPARE_FOR_IMCU; + main->iMCU_row_ctr = 0; + } else { + /* Simple case with no context needed */ + main->pub.process_data = process_data_simple_main; + } + main->buffer_full = FALSE; /* Mark buffer empty */ + main->rowgroup_ctr = 0; + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_CRANK_DEST: + /* For last pass of 2-pass quantization, just crank the postprocessor */ + main->pub.process_data = process_data_crank_post; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data. + * This handles the simple case where no context is required. + */ + +METHODDEF void +process_data_simple_main (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + JDIMENSION rowgroups_avail; + + /* Read input data if we haven't filled the main buffer yet */ + if (! main->buffer_full) { + if (! (*cinfo->coef->decompress_data) (cinfo, main->buffer)) + return; /* suspension forced, can do nothing more */ + main->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + } + + /* There are always min_DCT_scaled_size row groups in an iMCU row. */ + rowgroups_avail = (JDIMENSION) cinfo->min_DCT_scaled_size; + /* Note: at the bottom of the image, we may pass extra garbage row groups + * to the postprocessor. The postprocessor has to check for bottom + * of image anyway (at row resolution), so no point in us doing it too. + */ + + /* Feed the postprocessor */ + (*cinfo->post->post_process_data) (cinfo, main->buffer, + &main->rowgroup_ctr, rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + + /* Has postprocessor consumed all the data yet? If so, mark buffer empty */ + if (main->rowgroup_ctr >= rowgroups_avail) { + main->buffer_full = FALSE; + main->rowgroup_ctr = 0; + } +} + + +/* + * Process some data. + * This handles the case where context rows must be provided. + */ + +METHODDEF void +process_data_context_main (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + + /* Read input data if we haven't filled the main buffer yet */ + if (! main->buffer_full) { + if (! (*cinfo->coef->decompress_data) (cinfo, + main->xbuffer[main->whichptr])) + return; /* suspension forced, can do nothing more */ + main->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + main->iMCU_row_ctr++; /* count rows received */ + } + + /* Postprocessor typically will not swallow all the input data it is handed + * in one call (due to filling the output buffer first). Must be prepared + * to exit and restart. This switch lets us keep track of how far we got. + * Note that each case falls through to the next on successful completion. + */ + switch (main->context_state) { + case CTX_POSTPONED_ROW: + /* Call postprocessor using previously set pointers for postponed row */ + (*cinfo->post->post_process_data) (cinfo, main->xbuffer[main->whichptr], + &main->rowgroup_ctr, main->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (main->rowgroup_ctr < main->rowgroups_avail) + return; /* Need to suspend */ + main->context_state = CTX_PREPARE_FOR_IMCU; + if (*out_row_ctr >= out_rows_avail) + return; /* Postprocessor exactly filled output buf */ + /*FALLTHROUGH*/ + case CTX_PREPARE_FOR_IMCU: + /* Prepare to process first M-1 row groups of this iMCU row */ + main->rowgroup_ctr = 0; + main->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size - 1); + /* Check for bottom of image: if so, tweak pointers to "duplicate" + * the last sample row, and adjust rowgroups_avail to ignore padding rows. + */ + if (main->iMCU_row_ctr == cinfo->total_iMCU_rows) + set_bottom_pointers(cinfo); + main->context_state = CTX_PROCESS_IMCU; + /*FALLTHROUGH*/ + case CTX_PROCESS_IMCU: + /* Call postprocessor using previously set pointers */ + (*cinfo->post->post_process_data) (cinfo, main->xbuffer[main->whichptr], + &main->rowgroup_ctr, main->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (main->rowgroup_ctr < main->rowgroups_avail) + return; /* Need to suspend */ + /* After the first iMCU, change wraparound pointers to normal state */ + if (main->iMCU_row_ctr == 1) + set_wraparound_pointers(cinfo); + /* Prepare to load new iMCU row using other xbuffer list */ + main->whichptr ^= 1; /* 0=>1 or 1=>0 */ + main->buffer_full = FALSE; + /* Still need to process last row group of this iMCU row, */ + /* which is saved at index M+1 of the other xbuffer */ + main->rowgroup_ctr = (JDIMENSION) (cinfo->min_DCT_scaled_size + 1); + main->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size + 2); + main->context_state = CTX_POSTPONED_ROW; + } +} + + +/* + * Process some data. + * Final pass of two-pass quantization: just call the postprocessor. + * Source data will be the postprocessor controller's internal buffer. + */ + +#ifdef QUANT_2PASS_SUPPORTED + +METHODDEF void +process_data_crank_post (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + (*cinfo->post->post_process_data) (cinfo, (JSAMPIMAGE) NULL, + (JDIMENSION *) NULL, (JDIMENSION) 0, + output_buf, out_row_ctr, out_rows_avail); +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL void +jinit_d_main_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_main_ptr main; + int ci, rgroup, ngroups; + jpeg_component_info *compptr; + + main = (my_main_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_main_controller)); + cinfo->main = (struct jpeg_d_main_controller *) main; + main->pub.start_pass = start_pass_main; + + if (need_full_buffer) /* shouldn't happen */ + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + /* Allocate the workspace. + * ngroups is the number of row groups we need. + */ + if (cinfo->upsample->need_context_rows) { + if (cinfo->min_DCT_scaled_size < 2) /* unsupported, see comments above */ + ERREXIT(cinfo, JERR_NOTIMPL); + alloc_funny_pointers(cinfo); /* Alloc space for xbuffer[] lists */ + ngroups = cinfo->min_DCT_scaled_size + 2; + } else { + ngroups = cinfo->min_DCT_scaled_size; + } + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + main->buffer[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * compptr->DCT_scaled_size, + (JDIMENSION) (rgroup * ngroups)); + } +} diff --git a/libs/jpeg6/jdmarker.cpp b/libs/jpeg6/jdmarker.cpp new file mode 100644 index 00000000..b897b507 --- /dev/null +++ b/libs/jpeg6/jdmarker.cpp @@ -0,0 +1,1052 @@ +/* + * jdmarker.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to decode JPEG datastream markers. + * Most of the complexity arises from our desire to support input + * suspension: if not all of the data for a marker is available, + * we must exit back to the application. On resumption, we reprocess + * the marker. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* + * Macros for fetching data from the data source module. + * + * At all times, cinfo->src->next_input_byte and ->bytes_in_buffer reflect + * the current restart point; we update them only when we have reached a + * suitable place to restart if a suspension occurs. + */ + +/* Declare and initialize local copies of input pointer/count */ +#define INPUT_VARS(cinfo) \ + struct jpeg_source_mgr * datasrc = (cinfo)->src; \ + const JOCTET * next_input_byte = datasrc->next_input_byte; \ + size_t bytes_in_buffer = datasrc->bytes_in_buffer + +/* Unload the local copies --- do this only at a restart boundary */ +#define INPUT_SYNC(cinfo) \ + ( datasrc->next_input_byte = next_input_byte, \ + datasrc->bytes_in_buffer = bytes_in_buffer ) + +/* Reload the local copies --- seldom used except in MAKE_BYTE_AVAIL */ +#define INPUT_RELOAD(cinfo) \ + ( next_input_byte = datasrc->next_input_byte, \ + bytes_in_buffer = datasrc->bytes_in_buffer ) + +/* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available. + * Note we do *not* do INPUT_SYNC before calling fill_input_buffer, + * but we must reload the local copies after a successful fill. + */ +#define MAKE_BYTE_AVAIL(cinfo,action) \ + if (bytes_in_buffer == 0) { \ + if (! (*datasrc->fill_input_buffer) (cinfo)) \ + { action; } \ + INPUT_RELOAD(cinfo); \ + } \ + bytes_in_buffer-- + +/* Read a byte into variable V. + * If must suspend, take the specified action (typically "return FALSE"). + */ +#define INPUT_BYTE(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + V = GETJOCTET(*next_input_byte++); ) + +/* As above, but read two bytes interpreted as an unsigned 16-bit integer. + * V should be declared unsigned int or perhaps INT32. + */ +#define INPUT_2BYTES(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 8; \ + MAKE_BYTE_AVAIL(cinfo,action); \ + V += GETJOCTET(*next_input_byte++); ) + + +/* + * Routines to process JPEG markers. + * + * Entry condition: JPEG marker itself has been read and its code saved + * in cinfo->unread_marker; input restart point is just after the marker. + * + * Exit: if return TRUE, have read and processed any parameters, and have + * updated the restart point to point after the parameters. + * If return FALSE, was forced to suspend before reaching end of + * marker parameters; restart point has not been moved. Same routine + * will be called again after application supplies more input data. + * + * This approach to suspension assumes that all of a marker's parameters can + * fit into a single input bufferload. This should hold for "normal" + * markers. Some COM/APPn markers might have large parameter segments, + * but we use skip_input_data to get past those, and thereby put the problem + * on the source manager's shoulders. + * + * Note that we don't bother to avoid duplicate trace messages if a + * suspension occurs within marker parameters. Other side effects + * require more care. + */ + + +LOCAL boolean +get_soi (j_decompress_ptr cinfo) +/* Process an SOI marker */ +{ + int i; + + TRACEMS(cinfo, 1, JTRC_SOI); + + if (cinfo->marker->saw_SOI) + ERREXIT(cinfo, JERR_SOI_DUPLICATE); + + /* Reset all parameters that are defined to be reset by SOI */ + + for (i = 0; i < NUM_ARITH_TBLS; i++) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + cinfo->restart_interval = 0; + + /* Set initial assumptions for colorspace etc */ + + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->CCIR601_sampling = FALSE; /* Assume non-CCIR sampling??? */ + + cinfo->saw_JFIF_marker = FALSE; + cinfo->density_unit = 0; /* set default JFIF APP0 values */ + cinfo->X_density = 1; + cinfo->Y_density = 1; + cinfo->saw_Adobe_marker = FALSE; + cinfo->Adobe_transform = 0; + + cinfo->marker->saw_SOI = TRUE; + + return TRUE; +} + + +LOCAL boolean +get_sof (j_decompress_ptr cinfo, boolean is_prog, boolean is_arith) +/* Process a SOFn marker */ +{ + INT32 length; + int c, ci; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + cinfo->progressive_mode = is_prog; + cinfo->arith_code = is_arith; + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, cinfo->data_precision, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_height, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_width, return FALSE); + INPUT_BYTE(cinfo, cinfo->num_components, return FALSE); + + length -= 8; + + TRACEMS4(cinfo, 1, JTRC_SOF, cinfo->unread_marker, + (int) cinfo->image_width, (int) cinfo->image_height, + cinfo->num_components); + + if (cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOF_DUPLICATE); + + /* We don't support files in which the image height is initially specified */ + /* as 0 and is later redefined by DNL. As long as we have to check that, */ + /* might as well have a general sanity check. */ + if (cinfo->image_height <= 0 || cinfo->image_width <= 0 + || cinfo->num_components <= 0) + ERREXIT(cinfo, JERR_EMPTY_IMAGE); + + if (length != (cinfo->num_components * 3)) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + if (cinfo->comp_info == NULL) /* do only once, even if suspend */ + cinfo->comp_info = (jpeg_component_info *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * SIZEOF(jpeg_component_info)); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->component_index = ci; + INPUT_BYTE(cinfo, compptr->component_id, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + compptr->h_samp_factor = (c >> 4) & 15; + compptr->v_samp_factor = (c ) & 15; + INPUT_BYTE(cinfo, compptr->quant_tbl_no, return FALSE); + + TRACEMS4(cinfo, 1, JTRC_SOF_COMPONENT, + compptr->component_id, compptr->h_samp_factor, + compptr->v_samp_factor, compptr->quant_tbl_no); + } + + cinfo->marker->saw_SOF = TRUE; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_sos (j_decompress_ptr cinfo) +/* Process a SOS marker */ +{ + INT32 length; + int i, ci, n, c, cc; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + if (! cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOS_NO_SOF); + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, n, return FALSE); /* Number of components */ + + if (length != (n * 2 + 6) || n < 1 || n > MAX_COMPS_IN_SCAN) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + TRACEMS1(cinfo, 1, JTRC_SOS, n); + + cinfo->comps_in_scan = n; + + /* Collect the component-spec parameters */ + + for (i = 0; i < n; i++) { + INPUT_BYTE(cinfo, cc, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (cc == compptr->component_id) + goto id_found; + } + + ERREXIT1(cinfo, JERR_BAD_COMPONENT_ID, cc); + + id_found: + + cinfo->cur_comp_info[i] = compptr; + compptr->dc_tbl_no = (c >> 4) & 15; + compptr->ac_tbl_no = (c ) & 15; + + TRACEMS3(cinfo, 1, JTRC_SOS_COMPONENT, cc, + compptr->dc_tbl_no, compptr->ac_tbl_no); + } + + /* Collect the additional scan parameters Ss, Se, Ah/Al. */ + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ss = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Se = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ah = (c >> 4) & 15; + cinfo->Al = (c ) & 15; + + TRACEMS4(cinfo, 1, JTRC_SOS_PARAMS, cinfo->Ss, cinfo->Se, + cinfo->Ah, cinfo->Al); + + /* Prepare to scan data & restart markers */ + cinfo->marker->next_restart_num = 0; + + /* Count another SOS marker */ + cinfo->input_scan_number++; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +METHODDEF boolean +get_app0 (j_decompress_ptr cinfo) +/* Process an APP0 marker */ +{ +#define JFIF_LEN 14 + INT32 length; + UINT8 b[JFIF_LEN]; + int buffp; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + /* See if a JFIF APP0 marker is present */ + + if (length >= JFIF_LEN) { + for (buffp = 0; buffp < JFIF_LEN; buffp++) + INPUT_BYTE(cinfo, b[buffp], return FALSE); + length -= JFIF_LEN; + + if (b[0]==0x4A && b[1]==0x46 && b[2]==0x49 && b[3]==0x46 && b[4]==0) { + /* Found JFIF APP0 marker: check version */ + /* Major version must be 1, anything else signals an incompatible change. + * We used to treat this as an error, but now it's a nonfatal warning, + * because some bozo at Hijaak couldn't read the spec. + * Minor version should be 0..2, but process anyway if newer. + */ + if (b[5] != 1) + WARNMS2(cinfo, JWRN_JFIF_MAJOR, b[5], b[6]); + else if (b[6] > 2) + TRACEMS2(cinfo, 1, JTRC_JFIF_MINOR, b[5], b[6]); + /* Save info */ + cinfo->saw_JFIF_marker = TRUE; + cinfo->density_unit = b[7]; + cinfo->X_density = (b[8] << 8) + b[9]; + cinfo->Y_density = (b[10] << 8) + b[11]; + TRACEMS3(cinfo, 1, JTRC_JFIF, + cinfo->X_density, cinfo->Y_density, cinfo->density_unit); + if (b[12] | b[13]) + TRACEMS2(cinfo, 1, JTRC_JFIF_THUMBNAIL, b[12], b[13]); + if (length != ((INT32) b[12] * (INT32) b[13] * (INT32) 3)) + TRACEMS1(cinfo, 1, JTRC_JFIF_BADTHUMBNAILSIZE, (int) length); + } else { + /* Start of APP0 does not match "JFIF" */ + TRACEMS1(cinfo, 1, JTRC_APP0, (int) length + JFIF_LEN); + } + } else { + /* Too short to be JFIF marker */ + TRACEMS1(cinfo, 1, JTRC_APP0, (int) length); + } + + INPUT_SYNC(cinfo); + if (length > 0) /* skip any remaining data -- could be lots */ + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +METHODDEF boolean +get_app14 (j_decompress_ptr cinfo) +/* Process an APP14 marker */ +{ +#define ADOBE_LEN 12 + INT32 length; + UINT8 b[ADOBE_LEN]; + int buffp; + unsigned int version, flags0, flags1, transform; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + /* See if an Adobe APP14 marker is present */ + + if (length >= ADOBE_LEN) { + for (buffp = 0; buffp < ADOBE_LEN; buffp++) + INPUT_BYTE(cinfo, b[buffp], return FALSE); + length -= ADOBE_LEN; + + if (b[0]==0x41 && b[1]==0x64 && b[2]==0x6F && b[3]==0x62 && b[4]==0x65) { + /* Found Adobe APP14 marker */ + version = (b[5] << 8) + b[6]; + flags0 = (b[7] << 8) + b[8]; + flags1 = (b[9] << 8) + b[10]; + transform = b[11]; + TRACEMS4(cinfo, 1, JTRC_ADOBE, version, flags0, flags1, transform); + cinfo->saw_Adobe_marker = TRUE; + cinfo->Adobe_transform = (UINT8) transform; + } else { + /* Start of APP14 does not match "Adobe" */ + TRACEMS1(cinfo, 1, JTRC_APP14, (int) length + ADOBE_LEN); + } + } else { + /* Too short to be Adobe marker */ + TRACEMS1(cinfo, 1, JTRC_APP14, (int) length); + } + + INPUT_SYNC(cinfo); + if (length > 0) /* skip any remaining data -- could be lots */ + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +LOCAL boolean +get_dac (j_decompress_ptr cinfo) +/* Process a DAC marker */ +{ + INT32 length; + int index, val; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, index, return FALSE); + INPUT_BYTE(cinfo, val, return FALSE); + + length -= 2; + + TRACEMS2(cinfo, 1, JTRC_DAC, index, val); + + if (index < 0 || index >= (2*NUM_ARITH_TBLS)) + ERREXIT1(cinfo, JERR_DAC_INDEX, index); + + if (index >= NUM_ARITH_TBLS) { /* define AC table */ + cinfo->arith_ac_K[index-NUM_ARITH_TBLS] = (UINT8) val; + } else { /* define DC table */ + cinfo->arith_dc_L[index] = (UINT8) (val & 0x0F); + cinfo->arith_dc_U[index] = (UINT8) (val >> 4); + if (cinfo->arith_dc_L[index] > cinfo->arith_dc_U[index]) + ERREXIT1(cinfo, JERR_DAC_VALUE, val); + } + } + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_dht (j_decompress_ptr cinfo) +/* Process a DHT marker */ +{ + INT32 length; + UINT8 bits[17]; + UINT8 huffval[256]; + int i, index, count; + JHUFF_TBL **htblptr; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, index, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DHT, index); + + bits[0] = 0; + count = 0; + for (i = 1; i <= 16; i++) { + INPUT_BYTE(cinfo, bits[i], return FALSE); + count += bits[i]; + } + + length -= 1 + 16; + + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[1], bits[2], bits[3], bits[4], + bits[5], bits[6], bits[7], bits[8]); + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[9], bits[10], bits[11], bits[12], + bits[13], bits[14], bits[15], bits[16]); + + if (count > 256 || ((INT32) count) > length) + ERREXIT(cinfo, JERR_DHT_COUNTS); + + for (i = 0; i < count; i++) + INPUT_BYTE(cinfo, huffval[i], return FALSE); + + length -= count; + + if (index & 0x10) { /* AC table definition */ + index -= 0x10; + htblptr = &cinfo->ac_huff_tbl_ptrs[index]; + } else { /* DC table definition */ + htblptr = &cinfo->dc_huff_tbl_ptrs[index]; + } + + if (index < 0 || index >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_DHT_INDEX, index); + + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); + MEMCOPY((*htblptr)->huffval, huffval, SIZEOF((*htblptr)->huffval)); + } + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_dqt (j_decompress_ptr cinfo) +/* Process a DQT marker */ +{ + INT32 length; + int n, i, prec; + unsigned int tmp; + JQUANT_TBL *quant_ptr; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, n, return FALSE); + prec = n >> 4; + n &= 0x0F; + + TRACEMS2(cinfo, 1, JTRC_DQT, n, prec); + + if (n >= NUM_QUANT_TBLS) + ERREXIT1(cinfo, JERR_DQT_INDEX, n); + + if (cinfo->quant_tbl_ptrs[n] == NULL) + cinfo->quant_tbl_ptrs[n] = jpeg_alloc_quant_table((j_common_ptr) cinfo); + quant_ptr = cinfo->quant_tbl_ptrs[n]; + + for (i = 0; i < DCTSIZE2; i++) { + if (prec) + INPUT_2BYTES(cinfo, tmp, return FALSE); + else + INPUT_BYTE(cinfo, tmp, return FALSE); + quant_ptr->quantval[i] = (UINT16) tmp; + } + + for (i = 0; i < DCTSIZE2; i += 8) { + TRACEMS8(cinfo, 2, JTRC_QUANTVALS, + quant_ptr->quantval[i ], quant_ptr->quantval[i+1], + quant_ptr->quantval[i+2], quant_ptr->quantval[i+3], + quant_ptr->quantval[i+4], quant_ptr->quantval[i+5], + quant_ptr->quantval[i+6], quant_ptr->quantval[i+7]); + } + + length -= DCTSIZE2+1; + if (prec) length -= DCTSIZE2; + } + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_dri (j_decompress_ptr cinfo) +/* Process a DRI marker */ +{ + INT32 length; + unsigned int tmp; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + + if (length != 4) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_2BYTES(cinfo, tmp, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DRI, tmp); + + cinfo->restart_interval = tmp; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +METHODDEF boolean +skip_variable (j_decompress_ptr cinfo) +/* Skip over an unknown or uninteresting variable-length marker */ +{ + INT32 length; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + + TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, (int) length); + + INPUT_SYNC(cinfo); /* do before skip_input_data */ + (*cinfo->src->skip_input_data) (cinfo, (long) length - 2L); + + return TRUE; +} + + +/* + * Find the next JPEG marker, save it in cinfo->unread_marker. + * Returns FALSE if had to suspend before reaching a marker; + * in that case cinfo->unread_marker is unchanged. + * + * Note that the result might not be a valid marker code, + * but it will never be 0 or FF. + */ + +LOCAL boolean +next_marker (j_decompress_ptr cinfo) +{ + int c; + INPUT_VARS(cinfo); + + for (;;) { + INPUT_BYTE(cinfo, c, return FALSE); + /* Skip any non-FF bytes. + * This may look a bit inefficient, but it will not occur in a valid file. + * We sync after each discarded byte so that a suspending data source + * can discard the byte from its buffer. + */ + while (c != 0xFF) { + cinfo->marker->discarded_bytes++; + INPUT_SYNC(cinfo); + INPUT_BYTE(cinfo, c, return FALSE); + } + /* This loop swallows any duplicate FF bytes. Extra FFs are legal as + * pad bytes, so don't count them in discarded_bytes. We assume there + * will not be so many consecutive FF bytes as to overflow a suspending + * data source's input buffer. + */ + do { + INPUT_BYTE(cinfo, c, return FALSE); + } while (c == 0xFF); + if (c != 0) + break; /* found a valid marker, exit loop */ + /* Reach here if we found a stuffed-zero data sequence (FF/00). + * Discard it and loop back to try again. + */ + cinfo->marker->discarded_bytes += 2; + INPUT_SYNC(cinfo); + } + + if (cinfo->marker->discarded_bytes != 0) { + WARNMS2(cinfo, JWRN_EXTRANEOUS_DATA, cinfo->marker->discarded_bytes, c); + cinfo->marker->discarded_bytes = 0; + } + + cinfo->unread_marker = c; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +first_marker (j_decompress_ptr cinfo) +/* Like next_marker, but used to obtain the initial SOI marker. */ +/* For this marker, we do not allow preceding garbage or fill; otherwise, + * we might well scan an entire input file before realizing it ain't JPEG. + * If an application wants to process non-JFIF files, it must seek to the + * SOI before calling the JPEG library. + */ +{ + int c, c2; + INPUT_VARS(cinfo); + + INPUT_BYTE(cinfo, c, return FALSE); + INPUT_BYTE(cinfo, c2, return FALSE); + if (c != 0xFF || c2 != (int) M_SOI) + ERREXIT2(cinfo, JERR_NO_SOI, c, c2); + + cinfo->unread_marker = c2; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +/* + * Read markers until SOS or EOI. + * + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + +METHODDEF int +read_markers (j_decompress_ptr cinfo) +{ + /* Outer loop repeats once for each marker. */ + for (;;) { + /* Collect the marker proper, unless we already did. */ + /* NB: first_marker() enforces the requirement that SOI appear first. */ + if (cinfo->unread_marker == 0) { + if (! cinfo->marker->saw_SOI) { + if (! first_marker(cinfo)) + return JPEG_SUSPENDED; + } else { + if (! next_marker(cinfo)) + return JPEG_SUSPENDED; + } + } + /* At this point cinfo->unread_marker contains the marker code and the + * input point is just past the marker proper, but before any parameters. + * A suspension will cause us to return with this state still true. + */ + switch (cinfo->unread_marker) { + case M_SOI: + if (! get_soi(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_SOF0: /* Baseline */ + case M_SOF1: /* Extended sequential, Huffman */ + if (! get_sof(cinfo, FALSE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF2: /* Progressive, Huffman */ + if (! get_sof(cinfo, TRUE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF9: /* Extended sequential, arithmetic */ + if (! get_sof(cinfo, FALSE, TRUE)) + return JPEG_SUSPENDED; + break; + + case M_SOF10: /* Progressive, arithmetic */ + if (! get_sof(cinfo, TRUE, TRUE)) + return JPEG_SUSPENDED; + break; + + /* Currently unsupported SOFn types */ + case M_SOF3: /* Lossless, Huffman */ + case M_SOF5: /* Differential sequential, Huffman */ + case M_SOF6: /* Differential progressive, Huffman */ + case M_SOF7: /* Differential lossless, Huffman */ + case M_JPG: /* Reserved for JPEG extensions */ + case M_SOF11: /* Lossless, arithmetic */ + case M_SOF13: /* Differential sequential, arithmetic */ + case M_SOF14: /* Differential progressive, arithmetic */ + case M_SOF15: /* Differential lossless, arithmetic */ + ERREXIT1(cinfo, JERR_SOF_UNSUPPORTED, cinfo->unread_marker); + break; + + case M_SOS: + if (! get_sos(cinfo)) + return JPEG_SUSPENDED; + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_SOS; + + case M_EOI: + TRACEMS(cinfo, 1, JTRC_EOI); + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_EOI; + + case M_DAC: + if (! get_dac(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DHT: + if (! get_dht(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DQT: + if (! get_dqt(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DRI: + if (! get_dri(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_APP0: + case M_APP1: + case M_APP2: + case M_APP3: + case M_APP4: + case M_APP5: + case M_APP6: + case M_APP7: + case M_APP8: + case M_APP9: + case M_APP10: + case M_APP11: + case M_APP12: + case M_APP13: + case M_APP14: + case M_APP15: + if (! (*cinfo->marker->process_APPn[cinfo->unread_marker - (int) M_APP0]) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_COM: + if (! (*cinfo->marker->process_COM) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_RST0: /* these are all parameterless */ + case M_RST1: + case M_RST2: + case M_RST3: + case M_RST4: + case M_RST5: + case M_RST6: + case M_RST7: + case M_TEM: + TRACEMS1(cinfo, 1, JTRC_PARMLESS_MARKER, cinfo->unread_marker); + break; + + case M_DNL: /* Ignore DNL ... perhaps the wrong thing */ + if (! skip_variable(cinfo)) + return JPEG_SUSPENDED; + break; + + default: /* must be DHP, EXP, JPGn, or RESn */ + /* For now, we treat the reserved markers as fatal errors since they are + * likely to be used to signal incompatible JPEG Part 3 extensions. + * Once the JPEG 3 version-number marker is well defined, this code + * ought to change! + */ + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker); + break; + } + /* Successfully processed marker, so reset state variable */ + cinfo->unread_marker = 0; + } /* end loop */ +} + + +/* + * Read a restart marker, which is expected to appear next in the datastream; + * if the marker is not there, take appropriate recovery action. + * Returns FALSE if suspension is required. + * + * This is called by the entropy decoder after it has read an appropriate + * number of MCUs. cinfo->unread_marker may be nonzero if the entropy decoder + * has already read a marker from the data source. Under normal conditions + * cinfo->unread_marker will be reset to 0 before returning; if not reset, + * it holds a marker which the decoder will be unable to read past. + */ + +METHODDEF boolean +read_restart_marker (j_decompress_ptr cinfo) +{ + /* Obtain a marker unless we already did. */ + /* Note that next_marker will complain if it skips any data. */ + if (cinfo->unread_marker == 0) { + if (! next_marker(cinfo)) + return FALSE; + } + + if (cinfo->unread_marker == + ((int) M_RST0 + cinfo->marker->next_restart_num)) { + /* Normal case --- swallow the marker and let entropy decoder continue */ + TRACEMS1(cinfo, 2, JTRC_RST, cinfo->marker->next_restart_num); + cinfo->unread_marker = 0; + } else { + /* Uh-oh, the restart markers have been messed up. */ + /* Let the data source manager determine how to resync. */ + if (! (*cinfo->src->resync_to_restart) (cinfo, + cinfo->marker->next_restart_num)) + return FALSE; + } + + /* Update next-restart state */ + cinfo->marker->next_restart_num = (cinfo->marker->next_restart_num + 1) & 7; + + return TRUE; +} + + +/* + * This is the default resync_to_restart method for data source managers + * to use if they don't have any better approach. Some data source managers + * may be able to back up, or may have additional knowledge about the data + * which permits a more intelligent recovery strategy; such managers would + * presumably supply their own resync method. + * + * read_restart_marker calls resync_to_restart if it finds a marker other than + * the restart marker it was expecting. (This code is *not* used unless + * a nonzero restart interval has been declared.) cinfo->unread_marker is + * the marker code actually found (might be anything, except 0 or FF). + * The desired restart marker number (0..7) is passed as a parameter. + * This routine is supposed to apply whatever error recovery strategy seems + * appropriate in order to position the input stream to the next data segment. + * Note that cinfo->unread_marker is treated as a marker appearing before + * the current data-source input point; usually it should be reset to zero + * before returning. + * Returns FALSE if suspension is required. + * + * This implementation is substantially constrained by wanting to treat the + * input as a data stream; this means we can't back up. Therefore, we have + * only the following actions to work with: + * 1. Simply discard the marker and let the entropy decoder resume at next + * byte of file. + * 2. Read forward until we find another marker, discarding intervening + * data. (In theory we could look ahead within the current bufferload, + * without having to discard data if we don't find the desired marker. + * This idea is not implemented here, in part because it makes behavior + * dependent on buffer size and chance buffer-boundary positions.) + * 3. Leave the marker unread (by failing to zero cinfo->unread_marker). + * This will cause the entropy decoder to process an empty data segment, + * inserting dummy zeroes, and then we will reprocess the marker. + * + * #2 is appropriate if we think the desired marker lies ahead, while #3 is + * appropriate if the found marker is a future restart marker (indicating + * that we have missed the desired restart marker, probably because it got + * corrupted). + * We apply #2 or #3 if the found marker is a restart marker no more than + * two counts behind or ahead of the expected one. We also apply #2 if the + * found marker is not a legal JPEG marker code (it's certainly bogus data). + * If the found marker is a restart marker more than 2 counts away, we do #1 + * (too much risk that the marker is erroneous; with luck we will be able to + * resync at some future point). + * For any valid non-restart JPEG marker, we apply #3. This keeps us from + * overrunning the end of a scan. An implementation limited to single-scan + * files might find it better to apply #2 for markers other than EOI, since + * any other marker would have to be bogus data in that case. + */ + +GLOBAL boolean +jpeg_resync_to_restart (j_decompress_ptr cinfo, int desired) +{ + int marker = cinfo->unread_marker; + int action = 1; + + /* Always put up a warning. */ + WARNMS2(cinfo, JWRN_MUST_RESYNC, marker, desired); + + /* Outer loop handles repeated decision after scanning forward. */ + for (;;) { + if (marker < (int) M_SOF0) + action = 2; /* invalid marker */ + else if (marker < (int) M_RST0 || marker > (int) M_RST7) + action = 3; /* valid non-restart marker */ + else { + if (marker == ((int) M_RST0 + ((desired+1) & 7)) || + marker == ((int) M_RST0 + ((desired+2) & 7))) + action = 3; /* one of the next two expected restarts */ + else if (marker == ((int) M_RST0 + ((desired-1) & 7)) || + marker == ((int) M_RST0 + ((desired-2) & 7))) + action = 2; /* a prior restart, so advance */ + else + action = 1; /* desired restart or too far away */ + } + TRACEMS2(cinfo, 4, JTRC_RECOVERY_ACTION, marker, action); + switch (action) { + case 1: + /* Discard marker and let entropy decoder resume processing. */ + cinfo->unread_marker = 0; + return TRUE; + case 2: + /* Scan to the next marker, and repeat the decision loop. */ + if (! next_marker(cinfo)) + return FALSE; + marker = cinfo->unread_marker; + break; + case 3: + /* Return without advancing past this marker. */ + /* Entropy decoder will be forced to process an empty segment. */ + return TRUE; + } + } /* end loop */ +} + + +/* + * Reset marker processing state to begin a fresh datastream. + */ + +METHODDEF void +reset_marker_reader (j_decompress_ptr cinfo) +{ + cinfo->comp_info = NULL; /* until allocated by get_sof */ + cinfo->input_scan_number = 0; /* no SOS seen yet */ + cinfo->unread_marker = 0; /* no pending marker */ + cinfo->marker->saw_SOI = FALSE; /* set internal state too */ + cinfo->marker->saw_SOF = FALSE; + cinfo->marker->discarded_bytes = 0; +} + + +/* + * Initialize the marker reader module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL void +jinit_marker_reader (j_decompress_ptr cinfo) +{ + int i; + + /* Create subobject in permanent pool */ + cinfo->marker = (struct jpeg_marker_reader *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(struct jpeg_marker_reader)); + /* Initialize method pointers */ + cinfo->marker->reset_marker_reader = reset_marker_reader; + cinfo->marker->read_markers = read_markers; + cinfo->marker->read_restart_marker = read_restart_marker; + cinfo->marker->process_COM = skip_variable; + for (i = 0; i < 16; i++) + cinfo->marker->process_APPn[i] = skip_variable; + cinfo->marker->process_APPn[0] = get_app0; + cinfo->marker->process_APPn[14] = get_app14; + /* Reset marker processing state */ + reset_marker_reader(cinfo); +} diff --git a/libs/jpeg6/jdmaster.cpp b/libs/jpeg6/jdmaster.cpp new file mode 100644 index 00000000..6d38f604 --- /dev/null +++ b/libs/jpeg6/jdmaster.cpp @@ -0,0 +1,558 @@ +/* + * jdmaster.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG decompressor. + * These routines are concerned with selecting the modules to be executed + * and with determining the number of passes and the work to be done in each + * pass. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_decomp_master pub; /* public fields */ + + int pass_number; /* # of passes completed */ + + boolean using_merged_upsample; /* TRUE if using merged upsample/cconvert */ + + /* Saved references to initialized quantizer modules, + * in case we need to switch modes. + */ + struct jpeg_color_quantizer * quantizer_1pass; + struct jpeg_color_quantizer * quantizer_2pass; +} my_decomp_master; + +typedef my_decomp_master * my_master_ptr; + + +/* + * Determine whether merged upsample/color conversion should be used. + * CRUCIAL: this must match the actual capabilities of jdmerge.c! + */ + +LOCAL boolean +use_merged_upsample (j_decompress_ptr cinfo) +{ +#ifdef UPSAMPLE_MERGING_SUPPORTED + /* Merging is the equivalent of plain box-filter upsampling */ + if (cinfo->do_fancy_upsampling || cinfo->CCIR601_sampling) + return FALSE; + /* jdmerge.c only supports YCC=>RGB color conversion */ + if (cinfo->jpeg_color_space != JCS_YCbCr || cinfo->num_components != 3 || + cinfo->out_color_space != JCS_RGB || + cinfo->out_color_components != RGB_PIXELSIZE) + return FALSE; + /* and it only handles 2h1v or 2h2v sampling ratios */ + if (cinfo->comp_info[0].h_samp_factor != 2 || + cinfo->comp_info[1].h_samp_factor != 1 || + cinfo->comp_info[2].h_samp_factor != 1 || + cinfo->comp_info[0].v_samp_factor > 2 || + cinfo->comp_info[1].v_samp_factor != 1 || + cinfo->comp_info[2].v_samp_factor != 1) + return FALSE; + /* furthermore, it doesn't work if we've scaled the IDCTs differently */ + if (cinfo->comp_info[0].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[1].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[2].DCT_scaled_size != cinfo->min_DCT_scaled_size) + return FALSE; + /* ??? also need to test for upsample-time rescaling, when & if supported */ + return TRUE; /* by golly, it'll work... */ +#else + return FALSE; +#endif +} + + +/* + * Compute output image dimensions and related values. + * NOTE: this is exported for possible use by application. + * Hence it mustn't do anything that can't be done twice. + * Also note that it may be called before the master module is initialized! + */ + +GLOBAL void +jpeg_calc_output_dimensions (j_decompress_ptr cinfo) +/* Do computations that are needed before master selection phase */ +{ +#if 0 // JDC: commented out to remove warning + int ci; + jpeg_component_info *compptr; +#endif + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_READY) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + +#ifdef IDCT_SCALING_SUPPORTED + + /* Compute actual output image dimensions and DCT scaling choices. */ + if (cinfo->scale_num * 8 <= cinfo->scale_denom) { + /* Provide 1/8 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 8L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 8L); + cinfo->min_DCT_scaled_size = 1; + } else if (cinfo->scale_num * 4 <= cinfo->scale_denom) { + /* Provide 1/4 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 4L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 4L); + cinfo->min_DCT_scaled_size = 2; + } else if (cinfo->scale_num * 2 <= cinfo->scale_denom) { + /* Provide 1/2 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 2L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 2L); + cinfo->min_DCT_scaled_size = 4; + } else { + /* Provide 1/1 scaling */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + cinfo->min_DCT_scaled_size = DCTSIZE; + } + /* In selecting the actual DCT scaling for each component, we try to + * scale up the chroma components via IDCT scaling rather than upsampling. + * This saves time if the upsampler gets to use 1:1 scaling. + * Note this code assumes that the supported DCT scalings are powers of 2. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + int ssize = cinfo->min_DCT_scaled_size; + while (ssize < DCTSIZE && + (compptr->h_samp_factor * ssize * 2 <= + cinfo->max_h_samp_factor * cinfo->min_DCT_scaled_size) && + (compptr->v_samp_factor * ssize * 2 <= + cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size)) { + ssize = ssize * 2; + } + compptr->DCT_scaled_size = ssize; + } + + /* Recompute downsampled dimensions of components; + * application needs to know these if using raw downsampled data. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Size in samples, after IDCT scaling */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * + (long) (compptr->h_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * + (long) (compptr->v_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + } + +#else /* !IDCT_SCALING_SUPPORTED */ + + /* Hardwire it to "no scaling" */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + /* jdinput.c has already initialized DCT_scaled_size to DCTSIZE, + * and has computed unscaled downsampled_width and downsampled_height. + */ + +#endif /* IDCT_SCALING_SUPPORTED */ + + /* Report number of components in selected colorspace. */ + /* Probably this should be in the color conversion module... */ + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + break; + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + cinfo->out_color_components = RGB_PIXELSIZE; + break; +#endif /* else share code with YCbCr */ + case JCS_YCbCr: + cinfo->out_color_components = 3; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo->out_color_components = 4; + break; + default: /* else must be same colorspace as in file */ + cinfo->out_color_components = cinfo->num_components; + break; + } + cinfo->output_components = (cinfo->quantize_colors ? 1 : + cinfo->out_color_components); + + /* See if upsampler will want to emit more than one row at a time */ + if (use_merged_upsample(cinfo)) + cinfo->rec_outbuf_height = cinfo->max_v_samp_factor; + else + cinfo->rec_outbuf_height = 1; +} + + +/* + * Several decompression processes need to range-limit values to the range + * 0..MAXJSAMPLE; the input value may fall somewhat outside this range + * due to noise introduced by quantization, roundoff error, etc. These + * processes are inner loops and need to be as fast as possible. On most + * machines, particularly CPUs with pipelines or instruction prefetch, + * a (subscript-check-less) C table lookup + * x = sample_range_limit[x]; + * is faster than explicit tests + * if (x < 0) x = 0; + * else if (x > MAXJSAMPLE) x = MAXJSAMPLE; + * These processes all use a common table prepared by the routine below. + * + * For most steps we can mathematically guarantee that the initial value + * of x is within MAXJSAMPLE+1 of the legal range, so a table running from + * -(MAXJSAMPLE+1) to 2*MAXJSAMPLE+1 is sufficient. But for the initial + * limiting step (just after the IDCT), a wildly out-of-range value is + * possible if the input data is corrupt. To avoid any chance of indexing + * off the end of memory and getting a bad-pointer trap, we perform the + * post-IDCT limiting thus: + * x = range_limit[x & MASK]; + * where MASK is 2 bits wider than legal sample data, ie 10 bits for 8-bit + * samples. Under normal circumstances this is more than enough range and + * a correct output will be generated; with bogus input data the mask will + * cause wraparound, and we will safely generate a bogus-but-in-range output. + * For the post-IDCT step, we want to convert the data from signed to unsigned + * representation by adding CENTERJSAMPLE at the same time that we limit it. + * So the post-IDCT limiting table ends up looking like this: + * CENTERJSAMPLE,CENTERJSAMPLE+1,...,MAXJSAMPLE, + * MAXJSAMPLE (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0 (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0,1,...,CENTERJSAMPLE-1 + * Negative inputs select values from the upper half of the table after + * masking. + * + * We can save some space by overlapping the start of the post-IDCT table + * with the simpler range limiting table. The post-IDCT table begins at + * sample_range_limit + CENTERJSAMPLE. + * + * Note that the table is allocated in near data space on PCs; it's small + * enough and used often enough to justify this. + */ + +LOCAL void +prepare_range_limit_table (j_decompress_ptr cinfo) +/* Allocate and fill in the sample_range_limit table */ +{ + JSAMPLE * table; + int i; + + table = (JSAMPLE *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (5 * (MAXJSAMPLE+1) + CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + table += (MAXJSAMPLE+1); /* allow negative subscripts of simple table */ + cinfo->sample_range_limit = table; + /* First segment of "simple" table: limit[x] = 0 for x < 0 */ + MEMZERO(table - (MAXJSAMPLE+1), (MAXJSAMPLE+1) * SIZEOF(JSAMPLE)); + /* Main part of "simple" table: limit[x] = x */ + for (i = 0; i <= MAXJSAMPLE; i++) + table[i] = (JSAMPLE) i; + table += CENTERJSAMPLE; /* Point to where post-IDCT table starts */ + /* End of simple table, rest of first half of post-IDCT table */ + for (i = CENTERJSAMPLE; i < 2*(MAXJSAMPLE+1); i++) + table[i] = MAXJSAMPLE; + /* Second half of post-IDCT table */ + MEMZERO(table + (2 * (MAXJSAMPLE+1)), + (2 * (MAXJSAMPLE+1) - CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + MEMCOPY(table + (4 * (MAXJSAMPLE+1) - CENTERJSAMPLE), + cinfo->sample_range_limit, CENTERJSAMPLE * SIZEOF(JSAMPLE)); +} + + +/* + * Master selection of decompression modules. + * This is done once at jpeg_start_decompress time. We determine + * which modules will be used and give them appropriate initialization calls. + * We also initialize the decompressor input side to begin consuming data. + * + * Since jpeg_read_header has finished, we know what is in the SOF + * and (first) SOS markers. We also have all the application parameter + * settings. + */ + +LOCAL void +master_selection (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + boolean use_c_buffer; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Initialize dimensions and other stuff */ + jpeg_calc_output_dimensions(cinfo); + prepare_range_limit_table(cinfo); + + /* Width of an output scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->output_width * (long) cinfo->out_color_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ((long) jd_samplesperrow != samplesperrow) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + /* Initialize my private state */ + master->pass_number = 0; + master->using_merged_upsample = use_merged_upsample(cinfo); + + /* Color quantizer selection */ + master->quantizer_1pass = NULL; + master->quantizer_2pass = NULL; + /* No mode changes if not using buffered-image mode. */ + if (! cinfo->quantize_colors || ! cinfo->buffered_image) { + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + } + if (cinfo->quantize_colors) { + if (cinfo->raw_data_out) + ERREXIT(cinfo, JERR_NOTIMPL); + /* 2-pass quantizer only works in 3-component color space. */ + if (cinfo->out_color_components != 3) { + cinfo->enable_1pass_quant = TRUE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + cinfo->colormap = NULL; + } else if (cinfo->colormap != NULL) { + cinfo->enable_external_quant = TRUE; + } else if (cinfo->two_pass_quantize) { + cinfo->enable_2pass_quant = TRUE; + } else { + cinfo->enable_1pass_quant = TRUE; + } + + if (cinfo->enable_1pass_quant) { +#ifdef QUANT_1PASS_SUPPORTED + jinit_1pass_quantizer(cinfo); + master->quantizer_1pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + + /* We use the 2-pass code to map to external colormaps. */ + if (cinfo->enable_2pass_quant || cinfo->enable_external_quant) { +#ifdef QUANT_2PASS_SUPPORTED + jinit_2pass_quantizer(cinfo); + master->quantizer_2pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + /* If both quantizers are initialized, the 2-pass one is left active; + * this is necessary for starting with quantization to an external map. + */ + } + + /* Post-processing: in particular, color conversion first */ + if (! cinfo->raw_data_out) { + if (master->using_merged_upsample) { +#ifdef UPSAMPLE_MERGING_SUPPORTED + jinit_merged_upsampler(cinfo); /* does color conversion too */ +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + jinit_color_deconverter(cinfo); + jinit_upsampler(cinfo); + } + jinit_d_post_controller(cinfo, cinfo->enable_2pass_quant); + } + /* Inverse DCT */ + jinit_inverse_dct(cinfo); + /* Entropy decoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder(cinfo); +#else + ERREXIT(cinfo, JERR_NO_PROGRESSIVE); +#endif + } else + jinit_huff_decoder(cinfo); + } + + /* Initialize principal buffer controllers. */ + use_c_buffer = cinfo->inputctl->has_multiple_scans || cinfo->buffered_image; + jinit_d_coef_controller(cinfo, use_c_buffer); + + if (! cinfo->raw_data_out) + jinit_d_main_controller(cinfo, FALSE /* never need full buffer here */); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Initialize input side of decompressor to consume first scan. */ + (*cinfo->inputctl->start_input_pass) (cinfo); + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* If jpeg_start_decompress will read the whole file, initialize + * progress monitoring appropriately. The input step is counted + * as one pass. + */ + if (cinfo->progress != NULL && ! cinfo->buffered_image && + cinfo->inputctl->has_multiple_scans) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if (cinfo->progressive_mode) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = (cinfo->enable_2pass_quant ? 3 : 2); + /* Count the input pass as done */ + master->pass_number++; + } +#endif /* D_MULTISCAN_FILES_SUPPORTED */ +} + + +/* + * Per-pass setup. + * This is called at the beginning of each output pass. We determine which + * modules will be active during this pass and give them appropriate + * start_pass calls. We also set is_dummy_pass to indicate whether this + * is a "real" output pass or a dummy pass for color quantization. + * (In the latter case, jdapi.c will crank the pass to completion.) + */ + +METHODDEF void +prepare_for_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + if (master->pub.is_dummy_pass) { +#ifdef QUANT_2PASS_SUPPORTED + /* Final pass of 2-pass quantization */ + master->pub.is_dummy_pass = FALSE; + (*cinfo->cquantize->start_pass) (cinfo, FALSE); + (*cinfo->post->start_pass) (cinfo, JBUF_CRANK_DEST); + (*cinfo->main->start_pass) (cinfo, JBUF_CRANK_DEST); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + if (cinfo->quantize_colors && cinfo->colormap == NULL) { + /* Select new quantization method */ + if (cinfo->two_pass_quantize && cinfo->enable_2pass_quant) { + cinfo->cquantize = master->quantizer_2pass; + master->pub.is_dummy_pass = TRUE; + } else if (cinfo->enable_1pass_quant) { + cinfo->cquantize = master->quantizer_1pass; + } else { + ERREXIT(cinfo, JERR_MODE_CHANGE); + } + } + (*cinfo->idct->start_pass) (cinfo); + (*cinfo->coef->start_output_pass) (cinfo); + if (! cinfo->raw_data_out) { + if (! master->using_merged_upsample) + (*cinfo->cconvert->start_pass) (cinfo); + (*cinfo->upsample->start_pass) (cinfo); + if (cinfo->quantize_colors) + (*cinfo->cquantize->start_pass) (cinfo, master->pub.is_dummy_pass); + (*cinfo->post->start_pass) (cinfo, + (master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); + (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU); + } + } + + /* Set up progress monitor's pass info if present */ + if (cinfo->progress != NULL) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->pass_number + + (master->pub.is_dummy_pass ? 2 : 1); + /* In buffered-image mode, we assume one more output pass if EOI not + * yet reached, but no more passes if EOI has been reached. + */ + if (cinfo->buffered_image && ! cinfo->inputctl->eoi_reached) { + cinfo->progress->total_passes += (cinfo->enable_2pass_quant ? 2 : 1); + } + } +} + + +/* + * Finish up at end of an output pass. + */ + +METHODDEF void +finish_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + if (cinfo->quantize_colors) + (*cinfo->cquantize->finish_pass) (cinfo); + master->pass_number++; +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Switch to a new external colormap between output passes. + */ + +GLOBAL void +jpeg_new_colormap (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_BUFIMAGE) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (cinfo->quantize_colors && cinfo->enable_external_quant && + cinfo->colormap != NULL) { + /* Select 2-pass quantizer for external colormap use */ + cinfo->cquantize = master->quantizer_2pass; + /* Notify quantizer of colormap change */ + (*cinfo->cquantize->new_color_map) (cinfo); + master->pub.is_dummy_pass = FALSE; /* just in case */ + } else + ERREXIT(cinfo, JERR_MODE_CHANGE); +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +/* + * Initialize master decompression control and select active modules. + * This is performed at the start of jpeg_start_decompress. + */ + +GLOBAL void +jinit_master_decompress (j_decompress_ptr cinfo) +{ + my_master_ptr master; + + master = (my_master_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_decomp_master)); + cinfo->master = (struct jpeg_decomp_master *) master; + master->pub.prepare_for_output_pass = prepare_for_output_pass; + master->pub.finish_output_pass = finish_output_pass; + + master->pub.is_dummy_pass = FALSE; + + master_selection(cinfo); +} + diff --git a/libs/jpeg6/jdpostct.cpp b/libs/jpeg6/jdpostct.cpp new file mode 100644 index 00000000..e8b934cc --- /dev/null +++ b/libs/jpeg6/jdpostct.cpp @@ -0,0 +1,290 @@ +/* + * jdpostct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the decompression postprocessing controller. + * This controller manages the upsampling, color conversion, and color + * quantization/reduction steps; specifically, it controls the buffering + * between upsample/color conversion and color quantization/reduction. + * + * If no color quantization/reduction is required, then this module has no + * work to do, and it just hands off to the upsample/color conversion code. + * An integrated upsample/convert/quantize process would replace this module + * entirely. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_post_controller pub; /* public fields */ + + /* Color quantization source buffer: this holds output data from + * the upsample/color conversion step to be passed to the quantizer. + * For two-pass color quantization, we need a full-image buffer; + * for one-pass operation, a strip buffer is sufficient. + */ + jvirt_sarray_ptr whole_image; /* virtual array, or NULL if one-pass */ + JSAMPARRAY buffer; /* strip buffer, or current strip of virtual */ + JDIMENSION strip_height; /* buffer size in rows */ + /* for two-pass mode only: */ + JDIMENSION starting_row; /* row # of first row in current strip */ + JDIMENSION next_row; /* index of next row to fill/empty in strip */ +} my_post_controller; + +typedef my_post_controller * my_post_ptr; + + +/* Forward declarations */ +METHODDEF void post_process_1pass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF void post_process_prepass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +METHODDEF void post_process_2pass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_dpost (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (cinfo->quantize_colors) { + /* Single-pass processing with color quantization. */ + post->pub.post_process_data = post_process_1pass; + /* We could be doing buffered-image output before starting a 2-pass + * color quantization; in that case, jinit_d_post_controller did not + * allocate a strip buffer. Use the virtual-array buffer as workspace. + */ + if (post->buffer == NULL) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + (JDIMENSION) 0, post->strip_height, TRUE); + } + } else { + /* For single-pass processing without color quantization, + * I have no work to do; just call the upsampler directly. + */ + post->pub.post_process_data = cinfo->upsample->upsample; + } + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_SAVE_AND_PASS: + /* First pass of 2-pass quantization */ + if (post->whole_image == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + post->pub.post_process_data = post_process_prepass; + break; + case JBUF_CRANK_DEST: + /* Second pass of 2-pass quantization */ + if (post->whole_image == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + post->pub.post_process_data = post_process_2pass; + break; +#endif /* QUANT_2PASS_SUPPORTED */ + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } + post->starting_row = post->next_row = 0; +} + + +/* + * Process some data in the one-pass (strip buffer) case. + * This is used for color precision reduction as well as one-pass quantization. + */ + +METHODDEF void +post_process_1pass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Fill the buffer, but not more than what we can dump out in one go. */ + /* Note we rely on the upsampler to detect bottom of image. */ + max_rows = out_rows_avail - *out_row_ctr; + if (max_rows > post->strip_height) + max_rows = post->strip_height; + num_rows = 0; + (*cinfo->upsample->upsample) (cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &num_rows, max_rows); + /* Quantize and emit data. */ + (*cinfo->cquantize->color_quantize) (cinfo, + post->buffer, output_buf + *out_row_ctr, (int) num_rows); + *out_row_ctr += num_rows; +} + + +#ifdef QUANT_2PASS_SUPPORTED + +/* + * Process some data in the first pass of 2-pass quantization. + */ + +METHODDEF void +post_process_prepass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION old_next_row, num_rows; + + /* Reposition virtual buffer if at start of strip. */ + if (post->next_row == 0) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, TRUE); + } + + /* Upsample some data (up to a strip height's worth). */ + old_next_row = post->next_row; + (*cinfo->upsample->upsample) (cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &post->next_row, post->strip_height); + + /* Allow quantizer to scan new data. No data is emitted, */ + /* but we advance out_row_ctr so outer loop can tell when we're done. */ + if (post->next_row > old_next_row) { + num_rows = post->next_row - old_next_row; + (*cinfo->cquantize->color_quantize) (cinfo, post->buffer + old_next_row, + (JSAMPARRAY) NULL, (int) num_rows); + *out_row_ctr += num_rows; + } + + /* Advance if we filled the strip. */ + if (post->next_row >= post->strip_height) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + + +/* + * Process some data in the second pass of 2-pass quantization. + */ + +METHODDEF void +post_process_2pass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Reposition virtual buffer if at start of strip. */ + if (post->next_row == 0) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, FALSE); + } + + /* Determine number of rows to emit. */ + num_rows = post->strip_height - post->next_row; /* available in strip */ + max_rows = out_rows_avail - *out_row_ctr; /* available in output area */ + if (num_rows > max_rows) + num_rows = max_rows; + /* We have to check bottom of image here, can't depend on upsampler. */ + max_rows = cinfo->output_height - post->starting_row; + if (num_rows > max_rows) + num_rows = max_rows; + + /* Quantize and emit data. */ + (*cinfo->cquantize->color_quantize) (cinfo, + post->buffer + post->next_row, output_buf + *out_row_ctr, + (int) num_rows); + *out_row_ctr += num_rows; + + /* Advance if we filled the strip. */ + post->next_row += num_rows; + if (post->next_row >= post->strip_height) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize postprocessing controller. + */ + +GLOBAL void +jinit_d_post_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_post_ptr post; + + post = (my_post_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_post_controller)); + cinfo->post = (struct jpeg_d_post_controller *) post; + post->pub.start_pass = start_pass_dpost; + post->whole_image = NULL; /* flag for no virtual arrays */ + post->buffer = NULL; /* flag for no strip buffer */ + + /* Create the quantization buffer, if needed */ + if (cinfo->quantize_colors) { + /* The buffer strip height is max_v_samp_factor, which is typically + * an efficient number of rows for upsampling to return. + * (In the presence of output rescaling, we might want to be smarter?) + */ + post->strip_height = (JDIMENSION) cinfo->max_v_samp_factor; + if (need_full_buffer) { + /* Two-pass color quantization: need full-image storage. */ + /* We round up the number of rows to a multiple of the strip height. */ +#ifdef QUANT_2PASS_SUPPORTED + post->whole_image = (*cinfo->mem->request_virt_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + cinfo->output_width * cinfo->out_color_components, + (JDIMENSION) jround_up((long) cinfo->output_height, + (long) post->strip_height), + post->strip_height); +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + /* One-pass color quantization: just make a strip buffer. */ + post->buffer = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->output_width * cinfo->out_color_components, + post->strip_height); + } + } +} diff --git a/libs/jpeg6/jdsample.cpp b/libs/jpeg6/jdsample.cpp new file mode 100644 index 00000000..f53ceaf3 --- /dev/null +++ b/libs/jpeg6/jdsample.cpp @@ -0,0 +1,478 @@ +/* + * jdsample.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains upsampling routines. + * + * Upsampling input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. Upsampling will normally produce + * max_v_samp_factor pixel rows from each row group (but this could vary + * if the upsampler is applying a scale factor of its own). + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +/* Pointer to routine to upsample a single component */ +typedef JMETHOD(void, upsample1_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)); + +/* Private subobject */ + +typedef struct { + struct jpeg_upsampler pub; /* public fields */ + + /* Color conversion buffer. When using separate upsampling and color + * conversion steps, this buffer holds one upsampled row group until it + * has been color converted and output. + * Note: we do not allocate any storage for component(s) which are full-size, + * ie do not need rescaling. The corresponding entry of color_buf[] is + * simply set to point to the input data array, thereby avoiding copying. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + /* Per-component upsampling method pointers */ + upsample1_ptr methods[MAX_COMPONENTS]; + + int next_row_out; /* counts rows emitted from color_buf */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ + + /* Height of an input row group for each component. */ + int rowgroup_height[MAX_COMPONENTS]; + + /* These arrays save pixel expansion factors so that int_expand need not + * recompute them each time. They are unused for other upsampling methods. + */ + UINT8 h_expand[MAX_COMPONENTS]; + UINT8 v_expand[MAX_COMPONENTS]; +} my_upsampler; + +typedef my_upsampler * my_upsample_ptr; + + +/* + * Initialize for an upsampling pass. + */ + +METHODDEF void +start_pass_upsample (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Mark the conversion buffer empty */ + upsample->next_row_out = cinfo->max_v_samp_factor; + /* Initialize total-height counter for detecting bottom of image */ + upsample->rows_to_go = cinfo->output_height; +} + + +/* + * Control routine to do upsampling (and color conversion). + * + * In this version we upsample each component independently. + * We upsample one row group into the conversion buffer, then apply + * color conversion a row at a time. + */ + +METHODDEF void +sep_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + int ci; + jpeg_component_info * compptr; + JDIMENSION num_rows; + + /* Fill the conversion buffer, if it's empty */ + if (upsample->next_row_out >= cinfo->max_v_samp_factor) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Invoke per-component upsample method. Notice we pass a POINTER + * to color_buf[ci], so that fullsize_upsample can change it. + */ + (*upsample->methods[ci]) (cinfo, compptr, + input_buf[ci] + (*in_row_group_ctr * upsample->rowgroup_height[ci]), + upsample->color_buf + ci); + } + upsample->next_row_out = 0; + } + + /* Color-convert and emit rows */ + + /* How many we have in the buffer: */ + num_rows = (JDIMENSION) (cinfo->max_v_samp_factor - upsample->next_row_out); + /* Not more than the distance to the end of the image. Need this test + * in case the image height is not a multiple of max_v_samp_factor: + */ + if (num_rows > upsample->rows_to_go) + num_rows = upsample->rows_to_go; + /* And not more than what the client can accept: */ + out_rows_avail -= *out_row_ctr; + if (num_rows > out_rows_avail) + num_rows = out_rows_avail; + + (*cinfo->cconvert->color_convert) (cinfo, upsample->color_buf, + (JDIMENSION) upsample->next_row_out, + output_buf + *out_row_ctr, + (int) num_rows); + + /* Adjust counts */ + *out_row_ctr += num_rows; + upsample->rows_to_go -= num_rows; + upsample->next_row_out += num_rows; + /* When the buffer is emptied, declare this input row group consumed */ + if (upsample->next_row_out >= cinfo->max_v_samp_factor) + (*in_row_group_ctr)++; +} + + +/* + * These are the routines invoked by sep_upsample to upsample pixel values + * of a single component. One row group is processed per call. + */ + + +/* + * For full-size components, we just make color_buf[ci] point at the + * input buffer, and thus avoid copying any data. Note that this is + * safe only because sep_upsample doesn't declare the input row group + * "consumed" until we are done color converting and emitting it. + */ + +METHODDEF void +fullsize_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + *output_data_ptr = input_data; +} + + +/* + * This is a no-op version used for "uninteresting" components. + * These components will not be referenced by color conversion. + */ + +METHODDEF void +noop_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + *output_data_ptr = NULL; /* safety check */ +} + + +/* + * This version handles any integral sampling ratios. + * This is not used for typical JPEG files, so it need not be fast. + * Nor, for that matter, is it particularly accurate: the algorithm is + * simple replication of the input pixel onto the corresponding output + * pixels. The hi-falutin sampling literature refers to this as a + * "box filter". A box filter tends to introduce visible artifacts, + * so if you are actually going to use 3:1 or 4:1 sampling ratios + * you would be well advised to improve this code. + */ + +METHODDEF void +int_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + register int h; + JSAMPROW outend; + int h_expand, v_expand; + int inrow, outrow; + + h_expand = upsample->h_expand[compptr->component_index]; + v_expand = upsample->v_expand[compptr->component_index]; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + /* Generate one output row with proper horizontal expansion */ + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + for (h = h_expand; h > 0; h--) { + *outptr++ = invalue; + } + } + /* Generate any additional output rows by duplicating the first one */ + if (v_expand > 1) { + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + v_expand-1, cinfo->output_width); + } + inrow++; + outrow += v_expand; + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 1:1 vertical. + * It's still a box filter. + */ + +METHODDEF void +h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow; + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 2:1 vertical. + * It's still a box filter. + */ + +METHODDEF void +h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow, outrow; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + 1, cinfo->output_width); + inrow++; + outrow += 2; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 1:1 vertical. + * + * The upsampling algorithm is linear interpolation between pixel centers, + * also known as a "triangle filter". This is a good compromise between + * speed and visual quality. The centers of the output pixels are 1/4 and 3/4 + * of the way between input pixel centers. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF void +h2v1_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register int invalue; + register JDIMENSION colctr; + int inrow; + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + /* Special case for first column */ + invalue = GETJSAMPLE(*inptr++); + *outptr++ = (JSAMPLE) invalue; + *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(*inptr) + 2) >> 2); + + for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel */ + invalue = GETJSAMPLE(*inptr++) * 3; + *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(inptr[-2]) + 1) >> 2); + *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(*inptr) + 2) >> 2); + } + + /* Special case for last column */ + invalue = GETJSAMPLE(*inptr); + *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(inptr[-1]) + 1) >> 2); + *outptr++ = (JSAMPLE) invalue; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 2:1 vertical. + * Again a triangle filter; see comments for h2v1 case, above. + * + * It is OK for us to reference the adjacent input rows because we demanded + * context from the main buffer controller (see initialization code). + */ + +METHODDEF void +h2v2_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr0, inptr1, outptr; +#if BITS_IN_JSAMPLE == 8 + register int thiscolsum, lastcolsum, nextcolsum; +#else + register INT32 thiscolsum, lastcolsum, nextcolsum; +#endif + register JDIMENSION colctr; + int inrow, outrow, v; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + for (v = 0; v < 2; v++) { + /* inptr0 points to nearest input row, inptr1 points to next nearest */ + inptr0 = input_data[inrow]; + if (v == 0) /* next nearest is row above */ + inptr1 = input_data[inrow-1]; + else /* next nearest is row below */ + inptr1 = input_data[inrow+1]; + outptr = output_data[outrow++]; + + /* Special case for first column */ + thiscolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + + for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */ + /* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */ + nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + } + + /* Special case for last column */ + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 7) >> 4); + } + inrow++; + } +} + + +/* + * Module initialization routine for upsampling. + */ + +GLOBAL void +jinit_upsampler (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample; + int ci; + jpeg_component_info * compptr; + boolean need_buffer, do_fancy; + int h_in_group, v_in_group, h_out_group, v_out_group; + + upsample = (my_upsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_upsampler)); + cinfo->upsample = (struct jpeg_upsampler *) upsample; + upsample->pub.start_pass = start_pass_upsample; + upsample->pub.upsample = sep_upsample; + upsample->pub.need_context_rows = FALSE; /* until we find out differently */ + + if (cinfo->CCIR601_sampling) /* this isn't supported */ + ERREXIT(cinfo, JERR_CCIR601_NOTIMPL); + + /* jdmainct.c doesn't support context rows when min_DCT_scaled_size = 1, + * so don't ask for it. + */ + do_fancy = cinfo->do_fancy_upsampling && cinfo->min_DCT_scaled_size > 1; + + /* Verify we can handle the sampling factors, select per-component methods, + * and create storage as needed. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Compute size of an "input group" after IDCT scaling. This many samples + * are to be converted to max_h_samp_factor * max_v_samp_factor pixels. + */ + h_in_group = (compptr->h_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; + v_in_group = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; + h_out_group = cinfo->max_h_samp_factor; + v_out_group = cinfo->max_v_samp_factor; + upsample->rowgroup_height[ci] = v_in_group; /* save for use later */ + need_buffer = TRUE; + if (! compptr->component_needed) { + /* Don't bother to upsample an uninteresting component. */ + upsample->methods[ci] = noop_upsample; + need_buffer = FALSE; + } else if (h_in_group == h_out_group && v_in_group == v_out_group) { + /* Fullsize components can be processed without any work. */ + upsample->methods[ci] = fullsize_upsample; + need_buffer = FALSE; + } else if (h_in_group * 2 == h_out_group && + v_in_group == v_out_group) { + /* Special cases for 2h1v upsampling */ + if (do_fancy && compptr->downsampled_width > 2) + upsample->methods[ci] = h2v1_fancy_upsample; + else + upsample->methods[ci] = h2v1_upsample; + } else if (h_in_group * 2 == h_out_group && + v_in_group * 2 == v_out_group) { + /* Special cases for 2h2v upsampling */ + if (do_fancy && compptr->downsampled_width > 2) { + upsample->methods[ci] = h2v2_fancy_upsample; + upsample->pub.need_context_rows = TRUE; + } else + upsample->methods[ci] = h2v2_upsample; + } else if ((h_out_group % h_in_group) == 0 && + (v_out_group % v_in_group) == 0) { + /* Generic integral-factors upsampling method */ + upsample->methods[ci] = int_upsample; + upsample->h_expand[ci] = (UINT8) (h_out_group / h_in_group); + upsample->v_expand[ci] = (UINT8) (v_out_group / v_in_group); + } else + ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL); + if (need_buffer) { + upsample->color_buf[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) jround_up((long) cinfo->output_width, + (long) cinfo->max_h_samp_factor), + (JDIMENSION) cinfo->max_v_samp_factor); + } + } +} diff --git a/libs/jpeg6/jdtrans.cpp b/libs/jpeg6/jdtrans.cpp new file mode 100644 index 00000000..3f05ae37 --- /dev/null +++ b/libs/jpeg6/jdtrans.cpp @@ -0,0 +1,122 @@ +/* + * jdtrans.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding decompression, + * that is, reading raw DCT coefficient arrays from an input JPEG file. + * The routines in jdapimin.c will also be needed by a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +/* Forward declarations */ +LOCAL void transdecode_master_selection JPP((j_decompress_ptr cinfo)); + + +/* + * Read the coefficient arrays from a JPEG file. + * jpeg_read_header must be completed before calling this. + * + * The entire image is read into a set of virtual coefficient-block arrays, + * one per component. The return value is a pointer to the array of + * virtual-array descriptors. These can be manipulated directly via the + * JPEG memory manager, or handed off to jpeg_write_coefficients(). + * To release the memory occupied by the virtual arrays, call + * jpeg_finish_decompress() when done with the data. + * + * Returns NULL if suspended. This case need be checked only if + * a suspending data source is used. + */ + +GLOBAL jvirt_barray_ptr * +jpeg_read_coefficients (j_decompress_ptr cinfo) +{ + if (cinfo->global_state == DSTATE_READY) { + /* First call: initialize active modules */ + transdecode_master_selection(cinfo); + cinfo->global_state = DSTATE_RDCOEFS; + } else if (cinfo->global_state != DSTATE_RDCOEFS) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Absorb whole file into the coef buffer */ + for (;;) { + int retcode; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + /* Absorb some more input */ + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_SUSPENDED) + return NULL; + if (retcode == JPEG_REACHED_EOI) + break; + /* Advance progress counter if appropriate */ + if (cinfo->progress != NULL && + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + /* startup underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } + /* Set state so that jpeg_finish_decompress does the right thing */ + cinfo->global_state = DSTATE_STOPPING; + return cinfo->coef->coef_arrays; +} + + +/* + * Master selection of decompression modules for transcoding. + * This substitutes for jdmaster.c's initialization of the full decompressor. + */ + +LOCAL void +transdecode_master_selection (j_decompress_ptr cinfo) +{ + /* Entropy decoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_decoder(cinfo); + } + + /* Always get a full-image coefficient buffer. */ + jinit_d_coef_controller(cinfo, TRUE); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Initialize input side of decompressor to consume first scan. */ + (*cinfo->inputctl->start_input_pass) (cinfo); + + /* Initialize progress monitoring. */ + if (cinfo->progress != NULL) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if (cinfo->progressive_mode) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else if (cinfo->inputctl->has_multiple_scans) { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } else { + nscans = 1; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = 1; + } +} diff --git a/libs/jpeg6/jerror.cpp b/libs/jpeg6/jerror.cpp new file mode 100644 index 00000000..dda71a56 --- /dev/null +++ b/libs/jpeg6/jerror.cpp @@ -0,0 +1,233 @@ +/* + * jerror.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains simple error-reporting and trace-message routines. + * These are suitable for Unix-like systems and others where writing to + * stderr is the right thing to do. Many applications will want to replace + * some or all of these routines. + * + * These routines are used by both the compression and decompression code. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "radiant_jpeglib.h" +#include "jversion.h" +#include "jerror.h" + +#ifndef EXIT_FAILURE /* define exit() codes if not provided */ +#define EXIT_FAILURE 1 +#endif + + +/* + * Create the message string table. + * We do this from the master message list in jerror.h by re-reading + * jerror.h with a suitable definition for macro JMESSAGE. + * The message table is made an external symbol just in case any applications + * want to refer to it directly. + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_message_table jMsgTable +#endif + +#define JMESSAGE(code,string) string , + +const char * const jpeg_std_message_table[] = { +#include "jerror.h" + NULL +}; + +// Rad additions, longjmp out of the LoadJPGBuff +GLOBAL jmp_buf rad_loadfailed; +GLOBAL char rad_errormsg[JMSG_LENGTH_MAX]; + +/* + * Error exit handler: must not return to caller. + * + * Applications may override this if they want to get control back after + * an error. Typically one would longjmp somewhere instead of exiting. + * The setjmp buffer can be made a private field within an expanded error + * handler object. Note that the info needed to generate an error message + * is stored in the error object, so you can generate the message now or + * later, at your convenience. + * You should make sure that the JPEG object is cleaned up (with jpeg_abort + * or jpeg_destroy) at some point. + */ + +METHODDEF void +error_exit (j_common_ptr cinfo) +{ +// char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo,rad_errormsg); + + /* Let the memory manager delete any temp files before we die */ + jpeg_destroy(cinfo); + + longjmp( rad_loadfailed, -1 ); +} + + +/* + * Actual output of an error or trace message. + * Applications may override this method to send JPEG messages somewhere + * other than stderr. + */ + +METHODDEF void +output_message (j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + + /* Send it to stderr, adding a newline */ + printf("%s\n", buffer); +} + + +/* + * Decide whether to emit a trace or warning message. + * msg_level is one of: + * -1: recoverable corrupt-data warning, may want to abort. + * 0: important advisory messages (always display to user). + * 1: first level of tracing detail. + * 2,3,...: successively more detailed tracing messages. + * An application might override this method if it wanted to abort on warnings + * or change the policy about which messages to display. + */ + +METHODDEF void +emit_message (j_common_ptr cinfo, int msg_level) +{ + struct jpeg_error_mgr * err = cinfo->err; + + if (msg_level < 0) { + /* It's a warning message. Since corrupt files may generate many warnings, + * the policy implemented here is to show only the first warning, + * unless trace_level >= 3. + */ + if (err->num_warnings == 0 || err->trace_level >= 3) + (*err->output_message) (cinfo); + /* Always count warnings in num_warnings. */ + err->num_warnings++; + } else { + /* It's a trace message. Show it if trace_level >= msg_level. */ + if (err->trace_level >= msg_level) + (*err->output_message) (cinfo); + } +} + + +/* + * Format a message string for the most recent JPEG error or message. + * The message is stored into buffer, which should be at least JMSG_LENGTH_MAX + * characters. Note that no '\n' character is added to the string. + * Few applications should need to override this method. + */ + +METHODDEF void +format_message (j_common_ptr cinfo, char * buffer) +{ + struct jpeg_error_mgr * err = cinfo->err; + int msg_code = err->msg_code; + const char * msgtext = NULL; + const char * msgptr; + char ch; + boolean isstring; + + /* Look up message string in proper table */ + if (msg_code > 0 && msg_code <= err->last_jpeg_message) { + msgtext = err->jpeg_message_table[msg_code]; + } else if (err->addon_message_table != NULL && + msg_code >= err->first_addon_message && + msg_code <= err->last_addon_message) { + msgtext = err->addon_message_table[msg_code - err->first_addon_message]; + } + + /* Defend against bogus message number */ + if (msgtext == NULL) { + err->msg_parm.i[0] = msg_code; + msgtext = err->jpeg_message_table[0]; + } + + /* Check for string parameter, as indicated by %s in the message text */ + isstring = FALSE; + msgptr = msgtext; + while ((ch = *msgptr++) != '\0') { + if (ch == '%') { + if (*msgptr == 's') isstring = TRUE; + break; + } + } + + /* Format the message into the passed buffer */ + if (isstring) + sprintf(buffer, msgtext, err->msg_parm.s); + else + sprintf(buffer, msgtext, + err->msg_parm.i[0], err->msg_parm.i[1], + err->msg_parm.i[2], err->msg_parm.i[3], + err->msg_parm.i[4], err->msg_parm.i[5], + err->msg_parm.i[6], err->msg_parm.i[7]); +} + + +/* + * Reset error state variables at start of a new image. + * This is called during compression startup to reset trace/error + * processing to default state, without losing any application-specific + * method pointers. An application might possibly want to override + * this method if it has additional error processing state. + */ + +METHODDEF void +reset_error_mgr (j_common_ptr cinfo) +{ + cinfo->err->num_warnings = 0; + /* trace_level is not reset since it is an application-supplied parameter */ + cinfo->err->msg_code = 0; /* may be useful as a flag for "no error" */ +} + + +/* + * Fill in the standard error-handling methods in a jpeg_error_mgr object. + * Typical call is: + * struct jpeg_compress_struct cinfo; + * struct jpeg_error_mgr err; + * + * cinfo.err = jpeg_std_error(&err); + * after which the application may override some of the methods. + */ + +GLOBAL struct jpeg_error_mgr * +jpeg_std_error (struct jpeg_error_mgr * err) +{ + err->error_exit = error_exit; + err->emit_message = emit_message; + err->output_message = output_message; + err->format_message = format_message; + err->reset_error_mgr = reset_error_mgr; + + err->trace_level = 0; /* default = no tracing */ + err->num_warnings = 0; /* no warnings emitted yet */ + err->msg_code = 0; /* may be useful as a flag for "no error" */ + + /* Initialize message table pointers */ + err->jpeg_message_table = jpeg_std_message_table; + err->last_jpeg_message = (int) JMSG_LASTMSGCODE - 1; + + err->addon_message_table = NULL; + err->first_addon_message = 0; /* for safety */ + err->last_addon_message = 0; + + return err; +} diff --git a/libs/jpeg6/jerror.h b/libs/jpeg6/jerror.h new file mode 100644 index 00000000..86a36c70 --- /dev/null +++ b/libs/jpeg6/jerror.h @@ -0,0 +1,278 @@ +/* + * jerror.h + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the error and message codes for the JPEG library. + * Edit this file to add new codes, or to translate the message strings to + * some other language. + * A set of error-reporting macros are defined too. Some applications using + * the JPEG library may wish to include this file to get the error codes + * and/or the macros. + */ + +/* + * To define the enum list of message codes, include this file without + * defining macro JMESSAGE. To create a message string table, include it + * again with a suitable JMESSAGE definition (see jerror.c for an example). + */ +#ifndef JMESSAGE +#ifndef JERROR_H +/* First time through, define the enum list */ +#define JMAKE_ENUM_LIST +#else +/* Repeated inclusions of this file are no-ops unless JMESSAGE is defined */ +#define JMESSAGE(code,string) +#endif /* JERROR_H */ +#endif /* JMESSAGE */ + +#ifdef JMAKE_ENUM_LIST + +typedef enum { + +#define JMESSAGE(code,string) code , + +#endif /* JMAKE_ENUM_LIST */ + +JMESSAGE(JMSG_NOMESSAGE, "Bogus message code %d") /* Must be first entry! */ + +/* For maintenance convenience, list is alphabetical by message code name */ +JMESSAGE(JERR_ARITH_NOTIMPL, + "Sorry, there are legal restrictions on arithmetic coding") +JMESSAGE(JERR_BAD_ALIGN_TYPE, "ALIGN_TYPE is wrong, please fix") +JMESSAGE(JERR_BAD_ALLOC_CHUNK, "MAX_ALLOC_CHUNK is wrong, please fix") +JMESSAGE(JERR_BAD_BUFFER_MODE, "Bogus buffer control mode") +JMESSAGE(JERR_BAD_COMPONENT_ID, "Invalid component ID %d in SOS") +JMESSAGE(JERR_BAD_DCTSIZE, "IDCT output block size %d not supported") +JMESSAGE(JERR_BAD_IN_COLORSPACE, "Bogus input colorspace") +JMESSAGE(JERR_BAD_J_COLORSPACE, "Bogus JPEG colorspace") +JMESSAGE(JERR_BAD_LENGTH, "Bogus marker length") +JMESSAGE(JERR_BAD_MCU_SIZE, "Sampling factors too large for interleaved scan") +JMESSAGE(JERR_BAD_POOL_ID, "Invalid memory pool code %d") +JMESSAGE(JERR_BAD_PRECISION, "Unsupported JPEG data precision %d") +JMESSAGE(JERR_BAD_PROGRESSION, + "Invalid progressive parameters Ss=%d Se=%d Ah=%d Al=%d") +JMESSAGE(JERR_BAD_PROG_SCRIPT, + "Invalid progressive parameters at scan script entry %d") +JMESSAGE(JERR_BAD_SAMPLING, "Bogus sampling factors") +JMESSAGE(JERR_BAD_SCAN_SCRIPT, "Invalid scan script at entry %d") +JMESSAGE(JERR_BAD_STATE, "Improper call to JPEG library in state %d") +JMESSAGE(JERR_BAD_VIRTUAL_ACCESS, "Bogus virtual array access") +JMESSAGE(JERR_BUFFER_SIZE, "Buffer passed to JPEG library is too small") +JMESSAGE(JERR_CANT_SUSPEND, "Suspension not allowed here") +JMESSAGE(JERR_CCIR601_NOTIMPL, "CCIR601 sampling not implemented yet") +JMESSAGE(JERR_COMPONENT_COUNT, "Too many color components: %d, max %d") +JMESSAGE(JERR_CONVERSION_NOTIMPL, "Unsupported color conversion request") +JMESSAGE(JERR_DAC_INDEX, "Bogus DAC index %d") +JMESSAGE(JERR_DAC_VALUE, "Bogus DAC value 0x%x") +JMESSAGE(JERR_DHT_COUNTS, "Bogus DHT counts") +JMESSAGE(JERR_DHT_INDEX, "Bogus DHT index %d") +JMESSAGE(JERR_DQT_INDEX, "Bogus DQT index %d") +JMESSAGE(JERR_EMPTY_IMAGE, "Empty JPEG image (DNL not supported)") +JMESSAGE(JERR_EMS_READ, "Read from EMS failed") +JMESSAGE(JERR_EMS_WRITE, "Write to EMS failed") +JMESSAGE(JERR_EOI_EXPECTED, "Didn't expect more than one scan") +JMESSAGE(JERR_FILE_READ, "Input file read error") +JMESSAGE(JERR_FILE_WRITE, "Output file write error --- out of disk space?") +JMESSAGE(JERR_FRACT_SAMPLE_NOTIMPL, "Fractional sampling not implemented yet") +JMESSAGE(JERR_HUFF_CLEN_OVERFLOW, "Huffman code size table overflow") +JMESSAGE(JERR_HUFF_MISSING_CODE, "Missing Huffman code table entry") +JMESSAGE(JERR_IMAGE_TOO_BIG, "Maximum supported image dimension is %u pixels") +JMESSAGE(JERR_INPUT_EMPTY, "Empty input file") +JMESSAGE(JERR_INPUT_EOF, "Premature end of input file") +JMESSAGE(JERR_MISMATCHED_QUANT_TABLE, + "Cannot transcode due to multiple use of quantization table %d") +JMESSAGE(JERR_MISSING_DATA, "Scan script does not transmit all data") +JMESSAGE(JERR_MODE_CHANGE, "Invalid color quantization mode change") +JMESSAGE(JERR_NOTIMPL, "Not implemented yet") +JMESSAGE(JERR_NOT_COMPILED, "Requested feature was omitted at compile time") +JMESSAGE(JERR_NO_PROGRESSIVE, "Progressive JPEGs not supported, use regular JPEG instead") +JMESSAGE(JERR_NO_BACKING_STORE, "Backing store not supported") +JMESSAGE(JERR_NO_HUFF_TABLE, "Huffman table 0x%02x was not defined") +JMESSAGE(JERR_NO_IMAGE, "JPEG datastream contains no image") +JMESSAGE(JERR_NO_QUANT_TABLE, "Quantization table 0x%02x was not defined") +JMESSAGE(JERR_NO_SOI, "Not a JPEG file: starts with 0x%02x 0x%02x") +JMESSAGE(JERR_OUT_OF_MEMORY, "Insufficient memory (case %d)") +JMESSAGE(JERR_QUANT_COMPONENTS, + "Cannot quantize more than %d color components") +JMESSAGE(JERR_QUANT_FEW_COLORS, "Cannot quantize to fewer than %d colors") +JMESSAGE(JERR_QUANT_MANY_COLORS, "Cannot quantize to more than %d colors") +JMESSAGE(JERR_SOF_DUPLICATE, "Invalid JPEG file structure: two SOF markers") +JMESSAGE(JERR_SOF_NO_SOS, "Invalid JPEG file structure: missing SOS marker") +JMESSAGE(JERR_SOF_UNSUPPORTED, "Unsupported JPEG process: SOF type 0x%02x") +JMESSAGE(JERR_SOI_DUPLICATE, "Invalid JPEG file structure: two SOI markers") +JMESSAGE(JERR_SOS_NO_SOF, "Invalid JPEG file structure: SOS before SOF") +JMESSAGE(JERR_TFILE_CREATE, "Failed to create temporary file %s") +JMESSAGE(JERR_TFILE_READ, "Read failed on temporary file") +JMESSAGE(JERR_TFILE_SEEK, "Seek failed on temporary file") +JMESSAGE(JERR_TFILE_WRITE, + "Write failed on temporary file --- out of disk space?") +JMESSAGE(JERR_TOO_LITTLE_DATA, "Application transferred too few scanlines") +JMESSAGE(JERR_UNKNOWN_MARKER, "Unsupported marker type 0x%02x") +JMESSAGE(JERR_VIRTUAL_BUG, "Virtual array controller messed up") +JMESSAGE(JERR_WIDTH_OVERFLOW, "Image too wide for this implementation") +JMESSAGE(JERR_XMS_READ, "Read from XMS failed") +JMESSAGE(JERR_XMS_WRITE, "Write to XMS failed") +JMESSAGE(JMSG_COPYRIGHT, JCOPYRIGHT) +JMESSAGE(JMSG_VERSION, JVERSION) +JMESSAGE(JTRC_16BIT_TABLES, + "Caution: quantization tables are too coarse for baseline JPEG") +JMESSAGE(JTRC_ADOBE, + "Adobe APP14 marker: version %d, flags 0x%04x 0x%04x, transform %d") +JMESSAGE(JTRC_APP0, "Unknown APP0 marker (not JFIF), length %u") +JMESSAGE(JTRC_APP14, "Unknown APP14 marker (not Adobe), length %u") +JMESSAGE(JTRC_DAC, "Define Arithmetic Table 0x%02x: 0x%02x") +JMESSAGE(JTRC_DHT, "Define Huffman Table 0x%02x") +JMESSAGE(JTRC_DQT, "Define Quantization Table %d precision %d") +JMESSAGE(JTRC_DRI, "Define Restart Interval %u") +JMESSAGE(JTRC_EMS_CLOSE, "Freed EMS handle %u") +JMESSAGE(JTRC_EMS_OPEN, "Obtained EMS handle %u") +JMESSAGE(JTRC_EOI, "End Of Image") +JMESSAGE(JTRC_HUFFBITS, " %3d %3d %3d %3d %3d %3d %3d %3d") +JMESSAGE(JTRC_JFIF, "JFIF APP0 marker, density %dx%d %d") +JMESSAGE(JTRC_JFIF_BADTHUMBNAILSIZE, + "Warning: thumbnail image size does not match data length %u") +JMESSAGE(JTRC_JFIF_MINOR, "Unknown JFIF minor revision number %d.%02d") +JMESSAGE(JTRC_JFIF_THUMBNAIL, " with %d x %d thumbnail image") +JMESSAGE(JTRC_MISC_MARKER, "Skipping marker 0x%02x, length %u") +JMESSAGE(JTRC_PARMLESS_MARKER, "Unexpected marker 0x%02x") +JMESSAGE(JTRC_QUANTVALS, " %4u %4u %4u %4u %4u %4u %4u %4u") +JMESSAGE(JTRC_QUANT_3_NCOLORS, "Quantizing to %d = %d*%d*%d colors") +JMESSAGE(JTRC_QUANT_NCOLORS, "Quantizing to %d colors") +JMESSAGE(JTRC_QUANT_SELECTED, "Selected %d colors for quantization") +JMESSAGE(JTRC_RECOVERY_ACTION, "At marker 0x%02x, recovery action %d") +JMESSAGE(JTRC_RST, "RST%d") +JMESSAGE(JTRC_SMOOTH_NOTIMPL, + "Smoothing not supported with nonstandard sampling ratios") +JMESSAGE(JTRC_SOF, "Start Of Frame 0x%02x: width=%u, height=%u, components=%d") +JMESSAGE(JTRC_SOF_COMPONENT, " Component %d: %dhx%dv q=%d") +JMESSAGE(JTRC_SOI, "Start of Image") +JMESSAGE(JTRC_SOS, "Start Of Scan: %d components") +JMESSAGE(JTRC_SOS_COMPONENT, " Component %d: dc=%d ac=%d") +JMESSAGE(JTRC_SOS_PARAMS, " Ss=%d, Se=%d, Ah=%d, Al=%d") +JMESSAGE(JTRC_TFILE_CLOSE, "Closed temporary file %s") +JMESSAGE(JTRC_TFILE_OPEN, "Opened temporary file %s") +JMESSAGE(JTRC_UNKNOWN_IDS, + "Unrecognized component IDs %d %d %d, assuming YCbCr") +JMESSAGE(JTRC_XMS_CLOSE, "Freed XMS handle %u") +JMESSAGE(JTRC_XMS_OPEN, "Obtained XMS handle %u") +JMESSAGE(JWRN_ADOBE_XFORM, "Unknown Adobe color transform code %d") +JMESSAGE(JWRN_BOGUS_PROGRESSION, + "Inconsistent progression sequence for component %d coefficient %d") +JMESSAGE(JWRN_EXTRANEOUS_DATA, + "Corrupt JPEG data: %u extraneous bytes before marker 0x%02x") +JMESSAGE(JWRN_HIT_MARKER, "Corrupt JPEG data: premature end of data segment") +JMESSAGE(JWRN_HUFF_BAD_CODE, "Corrupt JPEG data: bad Huffman code") +JMESSAGE(JWRN_JFIF_MAJOR, "Warning: unknown JFIF revision number %d.%02d") +JMESSAGE(JWRN_JPEG_EOF, "Premature end of JPEG file") +JMESSAGE(JWRN_MUST_RESYNC, + "Corrupt JPEG data: found marker 0x%02x instead of RST%d") +JMESSAGE(JWRN_NOT_SEQUENTIAL, "Invalid SOS parameters for sequential JPEG") +JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") + +#ifdef JMAKE_ENUM_LIST + + JMSG_LASTMSGCODE +} J_MESSAGE_CODE; + +#undef JMAKE_ENUM_LIST +#endif /* JMAKE_ENUM_LIST */ + +/* Zap JMESSAGE macro so that future re-inclusions do nothing by default */ +#undef JMESSAGE + +#ifndef JERROR_H +#define JERROR_H + +// Rad additions, using longjmp to recover from errors +#include +EXTERN jmp_buf rad_loadfailed; +EXTERN char rad_errormsg[JMSG_LENGTH_MAX]; + +/* Macros to simplify using the error and trace message stuff */ +/* The first parameter is either type of cinfo pointer */ + +/* Fatal errors (print message and exit) */ +#define ERREXIT(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT3(cinfo,code,p1,p2,p3) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT4(cinfo,code,p1,p2,p3,p4) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (cinfo)->err->msg_parm.i[3] = (p4), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXITS(cinfo,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) + +#define MAKESTMT(stuff) do { stuff } while (0) + +/* Nonfatal errors (we can keep going, but the data is probably corrupt) */ +#define WARNMS(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) + +/* Informational/debugging messages */ +#define TRACEMS(cinfo,lvl,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS1(cinfo,lvl,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS2(cinfo,lvl,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS3(cinfo,lvl,code,p1,p2,p3) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS4(cinfo,lvl,code,p1,p2,p3,p4) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS8(cinfo,lvl,code,p1,p2,p3,p4,p5,p6,p7,p8) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); _mp[5] = (p6); _mp[6] = (p7); _mp[7] = (p8); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMSS(cinfo,lvl,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) + +#endif /* JERROR_H */ diff --git a/libs/jpeg6/jfdctflt.cpp b/libs/jpeg6/jfdctflt.cpp new file mode 100644 index 00000000..785e93ef --- /dev/null +++ b/libs/jpeg6/jfdctflt.cpp @@ -0,0 +1,168 @@ +/* + * jfdctflt.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * forward DCT (Discrete Cosine Transform). + * + * This implementation should be more accurate than either of the integer + * DCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL void +jpeg_fdct_float (FAST_FLOAT * data) +{ + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z1, z2, z3, z4, z5, z11, z13; + FAST_FLOAT *dataptr; + int ctr; + + /* Pass 1: process rows. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = tmp10 + tmp11; /* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + dataptr[2] = tmp13 + z1; /* phase 5 */ + dataptr[6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */ + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2; /* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE*0] = tmp10 + tmp11; /* phase 3 */ + dataptr[DCTSIZE*4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + dataptr[DCTSIZE*2] = tmp13 + z1; /* phase 5 */ + dataptr[DCTSIZE*6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */ + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE*5] = z13 + z2; /* phase 6 */ + dataptr[DCTSIZE*3] = z13 - z2; + dataptr[DCTSIZE*1] = z11 + z4; + dataptr[DCTSIZE*7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/libs/jpeg6/jidctflt.cpp b/libs/jpeg6/jidctflt.cpp new file mode 100644 index 00000000..82d1e61d --- /dev/null +++ b/libs/jpeg6/jidctflt.cpp @@ -0,0 +1,241 @@ +/* + * jidctflt.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * This implementation should be more accurate than either of the integer + * IDCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce a float result. + */ + +#define DEQUANTIZE(coef,quantval) (((FAST_FLOAT) (coef)) * (quantval)) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL void +jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z5, z10, z11, z12, z13; + JCOEFPTR inptr; + FLOAT_MULT_TYPE * quantptr; + FAST_FLOAT * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + FAST_FLOAT workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (FLOAT_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if ((inptr[DCTSIZE*1] | inptr[DCTSIZE*2] | inptr[DCTSIZE*3] | + inptr[DCTSIZE*4] | inptr[DCTSIZE*5] | inptr[DCTSIZE*6] | + inptr[DCTSIZE*7]) == 0) { + /* AC terms all zero */ + FAST_FLOAT dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp12 = (tmp1 - tmp3) * ((FAST_FLOAT) 1.414213562) - tmp13; /* 2*c4 */ + + tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp5 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp6 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp7 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); /* 2*c4 */ + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */ + tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + wsptr[DCTSIZE*0] = tmp0 + tmp7; + wsptr[DCTSIZE*7] = tmp0 - tmp7; + wsptr[DCTSIZE*1] = tmp1 + tmp6; + wsptr[DCTSIZE*6] = tmp1 - tmp6; + wsptr[DCTSIZE*2] = tmp2 + tmp5; + wsptr[DCTSIZE*5] = tmp2 - tmp5; + wsptr[DCTSIZE*4] = tmp3 + tmp4; + wsptr[DCTSIZE*3] = tmp3 - tmp4; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * And testing floats for zero is relatively expensive, so we don't bother. + */ + + /* Even part */ + + tmp10 = wsptr[0] + wsptr[4]; + tmp11 = wsptr[0] - wsptr[4]; + + tmp13 = wsptr[2] + wsptr[6]; + tmp12 = (wsptr[2] - wsptr[6]) * ((FAST_FLOAT) 1.414213562) - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = wsptr[5] + wsptr[3]; + z10 = wsptr[5] - wsptr[3]; + z11 = wsptr[1] + wsptr[7]; + z12 = wsptr[1] - wsptr[7]; + + tmp7 = z11 + z13; + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */ + tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + outptr[0] = range_limit[(int) DESCALE((INT32) (tmp0 + tmp7), 3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) DESCALE((INT32) (tmp0 - tmp7), 3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE((INT32) (tmp1 + tmp6), 3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) DESCALE((INT32) (tmp1 - tmp6), 3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE((INT32) (tmp2 + tmp5), 3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) DESCALE((INT32) (tmp2 - tmp5), 3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) DESCALE((INT32) (tmp3 + tmp4), 3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE((INT32) (tmp3 - tmp4), 3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/libs/jpeg6/jinclude.h b/libs/jpeg6/jinclude.h new file mode 100644 index 00000000..0a4f1514 --- /dev/null +++ b/libs/jpeg6/jinclude.h @@ -0,0 +1,91 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + + +/* Include auto-config file to find out which system include files we need. */ + +#include "jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) diff --git a/libs/jpeg6/jmemmgr.cpp b/libs/jpeg6/jmemmgr.cpp new file mode 100644 index 00000000..9862e2f8 --- /dev/null +++ b/libs/jpeg6/jmemmgr.cpp @@ -0,0 +1,1115 @@ +/* + * jmemmgr.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the JPEG system-independent memory management + * routines. This code is usable across a wide variety of machines; most + * of the system dependencies have been isolated in a separate file. + * The major functions provided here are: + * * pool-based allocation and freeing of memory; + * * policy decisions about how to divide available memory among the + * virtual arrays; + * * control logic for swapping virtual arrays between main memory and + * backing storage. + * The separate system-dependent file provides the actual backing-storage + * access code, and it contains the policy decision about how much total + * main memory to use. + * This file is system-dependent in the sense that some of its functions + * are unnecessary in some systems. For example, if there is enough virtual + * memory so that backing storage will never be used, much of the virtual + * array control logic could be removed. (Of course, if you have that much + * memory then you shouldn't care about a little bit of unused code...) + */ + +#define JPEG_INTERNALS +#define AM_MEMORY_MANAGER /* we define jvirt_Xarray_control structs */ +#include "jinclude.h" +#include "radiant_jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#ifndef NO_GETENV +#ifndef HAVE_STDLIB_H /* should declare getenv() */ +extern char * getenv JPP((const char * name)); +#endif +#endif + + +/* + * Some important notes: + * The allocation routines provided here must never return NULL. + * They should exit to error_exit if unsuccessful. + * + * It's not a good idea to try to merge the sarray and barray routines, + * even though they are textually almost the same, because samples are + * usually stored as bytes while coefficients are shorts or ints. Thus, + * in machines where byte pointers have a different representation from + * word pointers, the resulting machine code could not be the same. + */ + + +/* + * Many machines require storage alignment: longs must start on 4-byte + * boundaries, doubles on 8-byte boundaries, etc. On such machines, malloc() + * always returns pointers that are multiples of the worst-case alignment + * requirement, and we had better do so too. + * There isn't any really portable way to determine the worst-case alignment + * requirement. This module assumes that the alignment requirement is + * multiples of sizeof(ALIGN_TYPE). + * By default, we define ALIGN_TYPE as double. This is necessary on some + * workstations (where doubles really do need 8-byte alignment) and will work + * fine on nearly everything. If your machine has lesser alignment needs, + * you can save a few bytes by making ALIGN_TYPE smaller. + * The only place I know of where this will NOT work is certain Macintosh + * 680x0 compilers that define double as a 10-byte IEEE extended float. + * Doing 10-byte alignment is counterproductive because longwords won't be + * aligned well. Put "#define ALIGN_TYPE long" in jconfig.h if you have + * such a compiler. + */ + +#ifndef ALIGN_TYPE /* so can override from jconfig.h */ +#define ALIGN_TYPE double +#endif + + +/* + * We allocate objects from "pools", where each pool is gotten with a single + * request to jpeg_get_small() or jpeg_get_large(). There is no per-object + * overhead within a pool, except for alignment padding. Each pool has a + * header with a link to the next pool of the same class. + * Small and large pool headers are identical except that the latter's + * link pointer must be FAR on 80x86 machines. + * Notice that the "real" header fields are union'ed with a dummy ALIGN_TYPE + * field. This forces the compiler to make SIZEOF(small_pool_hdr) a multiple + * of the alignment requirement of ALIGN_TYPE. + */ + +typedef union small_pool_struct * small_pool_ptr; + +typedef union small_pool_struct { + struct { + small_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} small_pool_hdr; + +typedef union large_pool_struct FAR * large_pool_ptr; + +typedef union large_pool_struct { + struct { + large_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} large_pool_hdr; + + +/* + * Here is the full definition of a memory manager object. + */ + +typedef struct { + struct jpeg_memory_mgr pub; /* public fields */ + + /* Each pool identifier (lifetime class) names a linked list of pools. */ + small_pool_ptr small_list[JPOOL_NUMPOOLS]; + large_pool_ptr large_list[JPOOL_NUMPOOLS]; + + /* Since we only have one lifetime class of virtual arrays, only one + * linked list is necessary (for each datatype). Note that the virtual + * array control blocks being linked together are actually stored somewhere + * in the small-pool list. + */ + jvirt_sarray_ptr virt_sarray_list; + jvirt_barray_ptr virt_barray_list; + + /* This counts total space obtained from jpeg_get_small/large */ + long total_space_allocated; + + /* alloc_sarray and alloc_barray set this value for use by virtual + * array routines. + */ + JDIMENSION last_rowsperchunk; /* from most recent alloc_sarray/barray */ +} my_memory_mgr; + +typedef my_memory_mgr * my_mem_ptr; + + +/* + * The control blocks for virtual arrays. + * Note that these blocks are allocated in the "small" pool area. + * System-dependent info for the associated backing store (if any) is hidden + * inside the backing_store_info struct. + */ + +struct jvirt_sarray_control { + JSAMPARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION samplesperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_sarray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_sarray_ptr next; /* link to next virtual sarray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + +struct jvirt_barray_control { + JBLOCKARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION blocksperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_barray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_barray_ptr next; /* link to next virtual barray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + + +#ifdef MEM_STATS /* optional extra stuff for statistics */ + +LOCAL void +print_mem_stats (j_common_ptr cinfo, int pool_id) +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + + /* Since this is only a debugging stub, we can cheat a little by using + * fprintf directly rather than going through the trace message code. + * This is helpful because message parm array can't handle longs. + */ + fprintf(stderr, "Freeing pool %d, total space = %ld\n", + pool_id, mem->total_space_allocated); + + for (lhdr_ptr = mem->large_list[pool_id]; lhdr_ptr != NULL; + lhdr_ptr = lhdr_ptr->hdr.next) { + fprintf(stderr, " Large chunk used %ld\n", + (long) lhdr_ptr->hdr.bytes_used); + } + + for (shdr_ptr = mem->small_list[pool_id]; shdr_ptr != NULL; + shdr_ptr = shdr_ptr->hdr.next) { + fprintf(stderr, " Small chunk used %ld free %ld\n", + (long) shdr_ptr->hdr.bytes_used, + (long) shdr_ptr->hdr.bytes_left); + } +} + +#endif /* MEM_STATS */ + + +LOCAL void +out_of_memory (j_common_ptr cinfo, int which) +/* Report an out-of-memory error and stop execution */ +/* If we compiled MEM_STATS support, report alloc requests before dying */ +{ +#ifdef MEM_STATS + cinfo->err->trace_level = 2; /* force self_destruct to report stats */ +#endif + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, which); +} + + +/* + * Allocation of "small" objects. + * + * For these, we use pooled storage. When a new pool must be created, + * we try to get enough space for the current request plus a "slop" factor, + * where the slop will be the amount of leftover space in the new pool. + * The speed vs. space tradeoff is largely determined by the slop values. + * A different slop value is provided for each pool class (lifetime), + * and we also distinguish the first pool of a class from later ones. + * NOTE: the values given work fairly well on both 16- and 32-bit-int + * machines, but may be too small if longs are 64 bits or more. + */ + +static const size_t first_pool_slop[JPOOL_NUMPOOLS] = +{ + 1600, /* first PERMANENT pool */ + 16000 /* first IMAGE pool */ +}; + +static const size_t extra_pool_slop[JPOOL_NUMPOOLS] = +{ + 0, /* additional PERMANENT pools */ + 5000 /* additional IMAGE pools */ +}; + +#define MIN_SLOP 50 /* greater than 0 to avoid futile looping */ + + +METHODDEF void * +alloc_small (j_common_ptr cinfo, int pool_id, size_t sizeofobject) +/* Allocate a "small" object */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr hdr_ptr, prev_hdr_ptr; + char * data_ptr; + size_t odd_bytes, min_request, slop; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(small_pool_hdr))) + out_of_memory(cinfo, 1); /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + if (odd_bytes > 0) + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + /* See if space is available in any existing pool */ + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + prev_hdr_ptr = NULL; + hdr_ptr = mem->small_list[pool_id]; + while (hdr_ptr != NULL) { + if (hdr_ptr->hdr.bytes_left >= sizeofobject) + break; /* found pool with enough space */ + prev_hdr_ptr = hdr_ptr; + hdr_ptr = hdr_ptr->hdr.next; + } + + /* Time to make a new pool? */ + if (hdr_ptr == NULL) { + /* min_request is what we need now, slop is what will be leftover */ + min_request = sizeofobject + SIZEOF(small_pool_hdr); + if (prev_hdr_ptr == NULL) /* first pool in class? */ + slop = first_pool_slop[pool_id]; + else + slop = extra_pool_slop[pool_id]; + /* Don't ask for more than MAX_ALLOC_CHUNK */ + if (slop > (size_t) (MAX_ALLOC_CHUNK-min_request)) + slop = (size_t) (MAX_ALLOC_CHUNK-min_request); + /* Try to get space, if fail reduce slop and try again */ + for (;;) { + hdr_ptr = (small_pool_ptr) jpeg_get_small(cinfo, min_request + slop); + if (hdr_ptr != NULL) + break; + slop /= 2; + if (slop < MIN_SLOP) /* give up when it gets real small */ + out_of_memory(cinfo, 2); /* jpeg_get_small failed */ + } + mem->total_space_allocated += min_request + slop; + /* Success, initialize the new pool header and add to end of list */ + hdr_ptr->hdr.next = NULL; + hdr_ptr->hdr.bytes_used = 0; + hdr_ptr->hdr.bytes_left = sizeofobject + slop; + if (prev_hdr_ptr == NULL) /* first pool in class? */ + mem->small_list[pool_id] = hdr_ptr; + else + prev_hdr_ptr->hdr.next = hdr_ptr; + } + + /* OK, allocate the object from the current pool */ + data_ptr = (char *) (hdr_ptr + 1); /* point to first data byte in pool */ + data_ptr += hdr_ptr->hdr.bytes_used; /* point to place for object */ + hdr_ptr->hdr.bytes_used += sizeofobject; + hdr_ptr->hdr.bytes_left -= sizeofobject; + + return (void *) data_ptr; +} + + +/* + * Allocation of "large" objects. + * + * The external semantics of these are the same as "small" objects, + * except that FAR pointers are used on 80x86. However the pool + * management heuristics are quite different. We assume that each + * request is large enough that it may as well be passed directly to + * jpeg_get_large; the pool management just links everything together + * so that we can free it all on demand. + * Note: the major use of "large" objects is in JSAMPARRAY and JBLOCKARRAY + * structures. The routines that create these structures (see below) + * deliberately bunch rows together to ensure a large request size. + */ + +METHODDEF void FAR * +alloc_large (j_common_ptr cinfo, int pool_id, size_t sizeofobject) +/* Allocate a "large" object */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + large_pool_ptr hdr_ptr; + size_t odd_bytes; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr))) + out_of_memory(cinfo, 3); /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + if (odd_bytes > 0) + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + /* Always make a new pool */ + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + hdr_ptr = (large_pool_ptr) jpeg_get_large(cinfo, sizeofobject + + SIZEOF(large_pool_hdr)); + if (hdr_ptr == NULL) + out_of_memory(cinfo, 4); /* jpeg_get_large failed */ + mem->total_space_allocated += sizeofobject + SIZEOF(large_pool_hdr); + + /* Success, initialize the new pool header and add to list */ + hdr_ptr->hdr.next = mem->large_list[pool_id]; + /* We maintain space counts in each pool header for statistical purposes, + * even though they are not needed for allocation. + */ + hdr_ptr->hdr.bytes_used = sizeofobject; + hdr_ptr->hdr.bytes_left = 0; + mem->large_list[pool_id] = hdr_ptr; + + return (void FAR *) (hdr_ptr + 1); /* point to first data byte in pool */ +} + + +/* + * Creation of 2-D sample arrays. + * The pointers are in near heap, the samples themselves in FAR heap. + * + * To minimize allocation overhead and to allow I/O of large contiguous + * blocks, we allocate the sample rows in groups of as many rows as possible + * without exceeding MAX_ALLOC_CHUNK total bytes per allocation request. + * NB: the virtual array control routines, later in this file, know about + * this chunking of rows. The rowsperchunk value is left in the mem manager + * object so that it can be saved away if this sarray is the workspace for + * a virtual array. + */ + +METHODDEF JSAMPARRAY +alloc_sarray (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, JDIMENSION numrows) +/* Allocate a 2-D sample array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JSAMPARRAY result; + JSAMPROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + ((long) samplesperrow * SIZEOF(JSAMPLE)); + if (ltemp <= 0) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + if (ltemp < (long) numrows) + rowsperchunk = (JDIMENSION) ltemp; + else + rowsperchunk = numrows; + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JSAMPARRAY) alloc_small(cinfo, pool_id, + (size_t) (numrows * SIZEOF(JSAMPROW))); + + /* Get the rows themselves (large objects) */ + currow = 0; + while (currow < numrows) { + rowsperchunk = MIN(rowsperchunk, numrows - currow); + workspace = (JSAMPROW) alloc_large(cinfo, pool_id, + (size_t) ((size_t) rowsperchunk * (size_t) samplesperrow + * SIZEOF(JSAMPLE))); + for (i = rowsperchunk; i > 0; i--) { + result[currow++] = workspace; + workspace += samplesperrow; + } + } + + return result; +} + + +/* + * Creation of 2-D coefficient-block arrays. + * This is essentially the same as the code for sample arrays, above. + */ + +METHODDEF JBLOCKARRAY +alloc_barray (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, JDIMENSION numrows) +/* Allocate a 2-D coefficient-block array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JBLOCKARRAY result; + JBLOCKROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + ((long) blocksperrow * SIZEOF(JBLOCK)); + if (ltemp <= 0) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + if (ltemp < (long) numrows) + rowsperchunk = (JDIMENSION) ltemp; + else + rowsperchunk = numrows; + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JBLOCKARRAY) alloc_small(cinfo, pool_id, + (size_t) (numrows * SIZEOF(JBLOCKROW))); + + /* Get the rows themselves (large objects) */ + currow = 0; + while (currow < numrows) { + rowsperchunk = MIN(rowsperchunk, numrows - currow); + workspace = (JBLOCKROW) alloc_large(cinfo, pool_id, + (size_t) ((size_t) rowsperchunk * (size_t) blocksperrow + * SIZEOF(JBLOCK))); + for (i = rowsperchunk; i > 0; i--) { + result[currow++] = workspace; + workspace += blocksperrow; + } + } + + return result; +} + + +/* + * About virtual array management: + * + * The above "normal" array routines are only used to allocate strip buffers + * (as wide as the image, but just a few rows high). Full-image-sized buffers + * are handled as "virtual" arrays. The array is still accessed a strip at a + * time, but the memory manager must save the whole array for repeated + * accesses. The intended implementation is that there is a strip buffer in + * memory (as high as is possible given the desired memory limit), plus a + * backing file that holds the rest of the array. + * + * The request_virt_array routines are told the total size of the image and + * the maximum number of rows that will be accessed at once. The in-memory + * buffer must be at least as large as the maxaccess value. + * + * The request routines create control blocks but not the in-memory buffers. + * That is postponed until realize_virt_arrays is called. At that time the + * total amount of space needed is known (approximately, anyway), so free + * memory can be divided up fairly. + * + * The access_virt_array routines are responsible for making a specific strip + * area accessible (after reading or writing the backing file, if necessary). + * Note that the access routines are told whether the caller intends to modify + * the accessed strip; during a read-only pass this saves having to rewrite + * data to disk. The access routines are also responsible for pre-zeroing + * any newly accessed rows, if pre-zeroing was requested. + * + * In current usage, the access requests are usually for nonoverlapping + * strips; that is, successive access start_row numbers differ by exactly + * num_rows = maxaccess. This means we can get good performance with simple + * buffer dump/reload logic, by making the in-memory buffer be a multiple + * of the access height; then there will never be accesses across bufferload + * boundaries. The code will still work with overlapping access requests, + * but it doesn't handle bufferload overlaps very efficiently. + */ + + +METHODDEF jvirt_sarray_ptr +request_virt_sarray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION samplesperrow, JDIMENSION numrows, + JDIMENSION maxaccess) +/* Request a virtual 2-D sample array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_sarray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if (pool_id != JPOOL_IMAGE) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + /* get control block */ + result = (jvirt_sarray_ptr) alloc_small(cinfo, pool_id, + SIZEOF(struct jvirt_sarray_control)); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->samplesperrow = samplesperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_sarray_list; /* add to list of virtual arrays */ + mem->virt_sarray_list = result; + + return result; +} + + +METHODDEF jvirt_barray_ptr +request_virt_barray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION blocksperrow, JDIMENSION numrows, + JDIMENSION maxaccess) +/* Request a virtual 2-D coefficient-block array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_barray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if (pool_id != JPOOL_IMAGE) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + /* get control block */ + result = (jvirt_barray_ptr) alloc_small(cinfo, pool_id, + SIZEOF(struct jvirt_barray_control)); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->blocksperrow = blocksperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_barray_list; /* add to list of virtual arrays */ + mem->virt_barray_list = result; + + return result; +} + + +METHODDEF void +realize_virt_arrays (j_common_ptr cinfo) +/* Allocate the in-memory buffers for any unrealized virtual arrays */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + long space_per_minheight, maximum_space, avail_mem; + long minheights, max_minheights; + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + /* Compute the minimum space needed (maxaccess rows in each buffer) + * and the maximum space needed (full image height in each buffer). + * These may be of use to the system-dependent jpeg_mem_available routine. + */ + space_per_minheight = 0; + maximum_space = 0; + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + space_per_minheight += (long) sptr->maxaccess * + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + maximum_space += (long) sptr->rows_in_array * + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + } + } + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + space_per_minheight += (long) bptr->maxaccess * + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + maximum_space += (long) bptr->rows_in_array * + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + } + } + + if (space_per_minheight <= 0) + return; /* no unrealized arrays, no work */ + + /* Determine amount of memory to actually use; this is system-dependent. */ + avail_mem = jpeg_mem_available(cinfo, space_per_minheight, maximum_space, + mem->total_space_allocated); + + /* If the maximum space needed is available, make all the buffers full + * height; otherwise parcel it out with the same number of minheights + * in each buffer. + */ + if (avail_mem >= maximum_space) + max_minheights = 1000000000L; + else { + max_minheights = avail_mem / space_per_minheight; + /* If there doesn't seem to be enough space, try to get the minimum + * anyway. This allows a "stub" implementation of jpeg_mem_available(). + */ + if (max_minheights <= 0) + max_minheights = 1; + } + + /* Allocate the in-memory buffers and initialize backing store as needed. */ + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + minheights = ((long) sptr->rows_in_array - 1L) / sptr->maxaccess + 1L; + if (minheights <= max_minheights) { + /* This buffer fits in memory */ + sptr->rows_in_mem = sptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + sptr->rows_in_mem = (JDIMENSION) (max_minheights * sptr->maxaccess); + jpeg_open_backing_store(cinfo, & sptr->b_s_info, + (long) sptr->rows_in_array * + (long) sptr->samplesperrow * + (long) SIZEOF(JSAMPLE)); + sptr->b_s_open = TRUE; + } + sptr->mem_buffer = alloc_sarray(cinfo, JPOOL_IMAGE, + sptr->samplesperrow, sptr->rows_in_mem); + sptr->rowsperchunk = mem->last_rowsperchunk; + sptr->cur_start_row = 0; + sptr->first_undef_row = 0; + sptr->dirty = FALSE; + } + } + + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + minheights = ((long) bptr->rows_in_array - 1L) / bptr->maxaccess + 1L; + if (minheights <= max_minheights) { + /* This buffer fits in memory */ + bptr->rows_in_mem = bptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + bptr->rows_in_mem = (JDIMENSION) (max_minheights * bptr->maxaccess); + jpeg_open_backing_store(cinfo, & bptr->b_s_info, + (long) bptr->rows_in_array * + (long) bptr->blocksperrow * + (long) SIZEOF(JBLOCK)); + bptr->b_s_open = TRUE; + } + bptr->mem_buffer = alloc_barray(cinfo, JPOOL_IMAGE, + bptr->blocksperrow, bptr->rows_in_mem); + bptr->rowsperchunk = mem->last_rowsperchunk; + bptr->cur_start_row = 0; + bptr->first_undef_row = 0; + bptr->dirty = FALSE; + } + } +} + + +LOCAL void +do_sarray_io (j_common_ptr cinfo, jvirt_sarray_ptr ptr, boolean writing) +/* Do backing store read or write of a virtual sample array */ +{ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->samplesperrow * SIZEOF(JSAMPLE); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + /* Transfer no more than fits in file */ + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + if (rows <= 0) /* this chunk might be past end of file! */ + break; + byte_count = rows * bytesperrow; + if (writing) + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + else + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + file_offset += byte_count; + } +} + + +LOCAL void +do_barray_io (j_common_ptr cinfo, jvirt_barray_ptr ptr, boolean writing) +/* Do backing store read or write of a virtual coefficient-block array */ +{ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->blocksperrow * SIZEOF(JBLOCK); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + /* Transfer no more than fits in file */ + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + if (rows <= 0) /* this chunk might be past end of file! */ + break; + byte_count = rows * bytesperrow; + if (writing) + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + else + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + file_offset += byte_count; + } +} + + +METHODDEF JSAMPARRAY +access_virt_sarray (j_common_ptr cinfo, jvirt_sarray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) +/* Access the part of a virtual sample array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ +{ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL) + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + /* Make the desired part of the virtual array accessible */ + if (start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + if (! ptr->b_s_open) + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + /* Flush old buffer contents if necessary */ + if (ptr->dirty) { + do_sarray_io(cinfo, ptr, TRUE); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if (start_row > ptr->cur_start_row) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if (ltemp < 0) + ltemp = 0; /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_sarray_io(cinfo, ptr, FALSE); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if (ptr->first_undef_row < end_row) { + if (ptr->first_undef_row < start_row) { + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if (writable) + ptr->first_undef_row = end_row; + if (ptr->pre_zero) { + size_t bytesperrow = (size_t) ptr->samplesperrow * SIZEOF(JSAMPLE); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while (undef_row < end_row) { + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; + } + } else { + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + } + } + /* Flag the buffer dirty if caller will write in it */ + if (writable) + ptr->dirty = TRUE; + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + (start_row - ptr->cur_start_row); +} + + +METHODDEF JBLOCKARRAY +access_virt_barray (j_common_ptr cinfo, jvirt_barray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) +/* Access the part of a virtual block array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ +{ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL) + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + /* Make the desired part of the virtual array accessible */ + if (start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + if (! ptr->b_s_open) + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + /* Flush old buffer contents if necessary */ + if (ptr->dirty) { + do_barray_io(cinfo, ptr, TRUE); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if (start_row > ptr->cur_start_row) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if (ltemp < 0) + ltemp = 0; /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_barray_io(cinfo, ptr, FALSE); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if (ptr->first_undef_row < end_row) { + if (ptr->first_undef_row < start_row) { + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if (writable) + ptr->first_undef_row = end_row; + if (ptr->pre_zero) { + size_t bytesperrow = (size_t) ptr->blocksperrow * SIZEOF(JBLOCK); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while (undef_row < end_row) { + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; + } + } else { + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + } + } + /* Flag the buffer dirty if caller will write in it */ + if (writable) + ptr->dirty = TRUE; + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + (start_row - ptr->cur_start_row); +} + + +/* + * Release all objects belonging to a specified pool. + */ + +METHODDEF void +free_pool (j_common_ptr cinfo, int pool_id) +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + size_t space_freed; + + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + +#ifdef MEM_STATS + if (cinfo->err->trace_level > 1) + print_mem_stats(cinfo, pool_id); /* print pool's memory usage statistics */ +#endif + + /* If freeing IMAGE pool, close any virtual arrays first */ + if (pool_id == JPOOL_IMAGE) { + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->b_s_open) { /* there may be no backing store */ + sptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*sptr->b_s_info.close_backing_store) (cinfo, & sptr->b_s_info); + } + } + mem->virt_sarray_list = NULL; + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->b_s_open) { /* there may be no backing store */ + bptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*bptr->b_s_info.close_backing_store) (cinfo, & bptr->b_s_info); + } + } + mem->virt_barray_list = NULL; + } + + /* Release large objects */ + lhdr_ptr = mem->large_list[pool_id]; + mem->large_list[pool_id] = NULL; + + while (lhdr_ptr != NULL) { + large_pool_ptr next_lhdr_ptr = lhdr_ptr->hdr.next; + space_freed = lhdr_ptr->hdr.bytes_used + + lhdr_ptr->hdr.bytes_left + + SIZEOF(large_pool_hdr); + jpeg_free_large(cinfo, (void FAR *) lhdr_ptr, space_freed); + mem->total_space_allocated -= space_freed; + lhdr_ptr = next_lhdr_ptr; + } + + /* Release small objects */ + shdr_ptr = mem->small_list[pool_id]; + mem->small_list[pool_id] = NULL; + + while (shdr_ptr != NULL) { + small_pool_ptr next_shdr_ptr = shdr_ptr->hdr.next; + space_freed = shdr_ptr->hdr.bytes_used + + shdr_ptr->hdr.bytes_left + + SIZEOF(small_pool_hdr); + jpeg_free_small(cinfo, (void *) shdr_ptr, space_freed); + mem->total_space_allocated -= space_freed; + shdr_ptr = next_shdr_ptr; + } +} + + +/* + * Close up shop entirely. + * Note that this cannot be called unless cinfo->mem is non-NULL. + */ + +METHODDEF void +self_destruct (j_common_ptr cinfo) +{ + int pool; + + /* Close all backing store, release all memory. + * Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + free_pool(cinfo, pool); + } + + /* Release the memory manager control block too. */ + jpeg_free_small(cinfo, (void *) cinfo->mem, SIZEOF(my_memory_mgr)); + cinfo->mem = NULL; /* ensures I will be called only once */ + + jpeg_mem_term(cinfo); /* system-dependent cleanup */ +} + + +/* + * Memory manager initialization. + * When this is called, only the error manager pointer is valid in cinfo! + */ + +GLOBAL void +jinit_memory_mgr (j_common_ptr cinfo) +{ + my_mem_ptr mem; + long max_to_use; + int pool; + size_t test_mac; + + cinfo->mem = NULL; /* for safety if init fails */ + + /* Check for configuration errors. + * SIZEOF(ALIGN_TYPE) should be a power of 2; otherwise, it probably + * doesn't reflect any real hardware alignment requirement. + * The test is a little tricky: for X>0, X and X-1 have no one-bits + * in common if and only if X is a power of 2, ie has only one one-bit. + * Some compilers may give an "unreachable code" warning here; ignore it. + */ + if ((SIZEOF(ALIGN_TYPE) & (SIZEOF(ALIGN_TYPE)-1)) != 0) + ERREXIT(cinfo, JERR_BAD_ALIGN_TYPE); + /* MAX_ALLOC_CHUNK must be representable as type size_t, and must be + * a multiple of SIZEOF(ALIGN_TYPE). + * Again, an "unreachable code" warning may be ignored here. + * But a "constant too large" warning means you need to fix MAX_ALLOC_CHUNK. + */ + test_mac = (size_t) MAX_ALLOC_CHUNK; + if ((long) test_mac != MAX_ALLOC_CHUNK || + (MAX_ALLOC_CHUNK % SIZEOF(ALIGN_TYPE)) != 0) + ERREXIT(cinfo, JERR_BAD_ALLOC_CHUNK); + + max_to_use = jpeg_mem_init(cinfo); /* system-dependent initialization */ + + /* Attempt to allocate memory manager's control block */ + mem = (my_mem_ptr) jpeg_get_small(cinfo, SIZEOF(my_memory_mgr)); + + if (mem == NULL) { + jpeg_mem_term(cinfo); /* system-dependent cleanup */ + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 0); + } + + /* OK, fill in the method pointers */ + mem->pub.alloc_small = alloc_small; + mem->pub.alloc_large = alloc_large; + mem->pub.alloc_sarray = alloc_sarray; + mem->pub.alloc_barray = alloc_barray; + mem->pub.request_virt_sarray = request_virt_sarray; + mem->pub.request_virt_barray = request_virt_barray; + mem->pub.realize_virt_arrays = realize_virt_arrays; + mem->pub.access_virt_sarray = access_virt_sarray; + mem->pub.access_virt_barray = access_virt_barray; + mem->pub.free_pool = free_pool; + mem->pub.self_destruct = self_destruct; + + /* Initialize working state */ + mem->pub.max_memory_to_use = max_to_use; + + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + mem->small_list[pool] = NULL; + mem->large_list[pool] = NULL; + } + mem->virt_sarray_list = NULL; + mem->virt_barray_list = NULL; + + mem->total_space_allocated = SIZEOF(my_memory_mgr); + + /* Declare ourselves open for business */ + cinfo->mem = & mem->pub; + + /* Check for an environment variable JPEGMEM; if found, override the + * default max_memory setting from jpeg_mem_init. Note that the + * surrounding application may again override this value. + * If your system doesn't support getenv(), define NO_GETENV to disable + * this feature. + */ +#ifndef NO_GETENV + { char * memenv; + + if ((memenv = getenv("JPEGMEM")) != NULL) { + char ch = 'x'; + + if (sscanf(memenv, "%ld%c", &max_to_use, &ch) > 0) { + if (ch == 'm' || ch == 'M') + max_to_use *= 1000L; + mem->pub.max_memory_to_use = max_to_use * 1000L; + } + } + } +#endif + +} diff --git a/libs/jpeg6/jmemnobs.cpp b/libs/jpeg6/jmemnobs.cpp new file mode 100644 index 00000000..df0abb0c --- /dev/null +++ b/libs/jpeg6/jmemnobs.cpp @@ -0,0 +1,103 @@ +/* + * jmemnobs.c + * + * Copyright (C) 1992-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides a really simple implementation of the system- + * dependent portion of the JPEG memory manager. This implementation + * assumes that no backing-store files are needed: all required space + * can be obtained from ri.Malloc(). + * This is very portable in the sense that it'll compile on almost anything, + * but you'd better have lots of main memory (or virtual memory) if you want + * to process big images. + * Note that the max_memory_to_use option is ignored by this implementation. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +/* + * Memory allocation and ri.Freeing are controlled by the regular library + * routines ri.Malloc() and ri.Free(). + */ + +GLOBAL void * +jpeg_get_small (j_common_ptr cinfo, size_t sizeofobject) +{ + return (void *) malloc(sizeofobject); +} + +GLOBAL void +jpeg_free_small (j_common_ptr cinfo, void * object, size_t sizeofobject) +{ + free(object); +} + + +/* + * "Large" objects are treated the same as "small" ones. + * NB: although we include FAR keywords in the routine declarations, + * this file won't actually work in 80x86 small/medium model; at least, + * you probably won't be able to process useful-size images in only 64KB. + */ + +GLOBAL void FAR * +jpeg_get_large (j_common_ptr cinfo, size_t sizeofobject) +{ + return (void FAR *) malloc(sizeofobject); +} + +GLOBAL void +jpeg_free_large (j_common_ptr cinfo, void FAR * object, size_t sizeofobject) +{ + free(object); +} + + +/* + * This routine computes the total memory space available for allocation. + * Here we always say, "we got all you want bud!" + */ + +GLOBAL long +jpeg_mem_available (j_common_ptr cinfo, long min_bytes_needed, + long max_bytes_needed, long already_allocated) +{ + return max_bytes_needed; +} + + +/* + * Backing store (temporary file) management. + * Since jpeg_mem_available always promised the moon, + * this should never be called and we can just error out. + */ + +GLOBAL void +jpeg_open_backing_store (j_common_ptr cinfo, backing_store_ptr info, + long total_bytes_needed) +{ + ERREXIT(cinfo, JERR_NO_BACKING_STORE); +} + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. Here, there isn't any. + */ + +GLOBAL long +jpeg_mem_init (j_common_ptr cinfo) +{ + return 0; /* just set max_memory_to_use to 0 */ +} + +GLOBAL void +jpeg_mem_term (j_common_ptr cinfo) +{ + /* no work */ +} diff --git a/libs/jpeg6/jmemsys.h b/libs/jpeg6/jmemsys.h new file mode 100644 index 00000000..033d29a7 --- /dev/null +++ b/libs/jpeg6/jmemsys.h @@ -0,0 +1,182 @@ +/* + * jmemsys.h + * + * Copyright (C) 1992-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file defines the interface between the system-independent + * and system-dependent portions of the JPEG memory manager. No other + * modules need include it. (The system-independent portion is jmemmgr.c; + * there are several different versions of the system-dependent portion.) + * + * This file works as-is for the system-dependent memory managers supplied + * in the IJG distribution. You may need to modify it if you write a + * custom memory manager. If system-dependent changes are needed in + * this file, the best method is to #ifdef them based on a configuration + * symbol supplied in jconfig.h, as we have done with USE_MSDOS_MEMMGR. + */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_get_small jGetSmall +#define jpeg_free_small jFreeSmall +#define jpeg_get_large jGetLarge +#define jpeg_free_large jFreeLarge +#define jpeg_mem_available jMemAvail +#define jpeg_open_backing_store jOpenBackStore +#define jpeg_mem_init jMemInit +#define jpeg_mem_term jMemTerm +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * These two functions are used to allocate and release small chunks of + * memory. (Typically the total amount requested through jpeg_get_small is + * no more than 20K or so; this will be requested in chunks of a few K each.) + * Behavior should be the same as for the standard library functions malloc + * and free; in particular, jpeg_get_small must return NULL on failure. + * On most systems, these ARE malloc and free. jpeg_free_small is passed the + * size of the object being freed, just in case it's needed. + * On an 80x86 machine using small-data memory model, these manage near heap. + */ + +EXTERN void * jpeg_get_small JPP((j_common_ptr cinfo, size_t sizeofobject)); +EXTERN void jpeg_free_small JPP((j_common_ptr cinfo, void * object, + size_t sizeofobject)); + +/* + * These two functions are used to allocate and release large chunks of + * memory (up to the total free space designated by jpeg_mem_available). + * The interface is the same as above, except that on an 80x86 machine, + * far pointers are used. On most other machines these are identical to + * the jpeg_get/free_small routines; but we keep them separate anyway, + * in case a different allocation strategy is desirable for large chunks. + */ + +EXTERN void FAR * jpeg_get_large JPP((j_common_ptr cinfo,size_t sizeofobject)); +EXTERN void jpeg_free_large JPP((j_common_ptr cinfo, void FAR * object, + size_t sizeofobject)); + +/* + * The macro MAX_ALLOC_CHUNK designates the maximum number of bytes that may + * be requested in a single call to jpeg_get_large (and jpeg_get_small for that + * matter, but that case should never come into play). This macro is needed + * to model the 64Kb-segment-size limit of far addressing on 80x86 machines. + * On those machines, we expect that jconfig.h will provide a proper value. + * On machines with 32-bit flat address spaces, any large constant may be used. + * + * NB: jmemmgr.c expects that MAX_ALLOC_CHUNK will be representable as type + * size_t and will be a multiple of sizeof(align_type). + */ + +#ifndef MAX_ALLOC_CHUNK /* may be overridden in jconfig.h */ +#define MAX_ALLOC_CHUNK 1000000000L +#endif + +/* + * This routine computes the total space still available for allocation by + * jpeg_get_large. If more space than this is needed, backing store will be + * used. NOTE: any memory already allocated must not be counted. + * + * There is a minimum space requirement, corresponding to the minimum + * feasible buffer sizes; jmemmgr.c will request that much space even if + * jpeg_mem_available returns zero. The maximum space needed, enough to hold + * all working storage in memory, is also passed in case it is useful. + * Finally, the total space already allocated is passed. If no better + * method is available, cinfo->mem->max_memory_to_use - already_allocated + * is often a suitable calculation. + * + * It is OK for jpeg_mem_available to underestimate the space available + * (that'll just lead to more backing-store access than is really necessary). + * However, an overestimate will lead to failure. Hence it's wise to subtract + * a slop factor from the true available space. 5% should be enough. + * + * On machines with lots of virtual memory, any large constant may be returned. + * Conversely, zero may be returned to always use the minimum amount of memory. + */ + +EXTERN long jpeg_mem_available JPP((j_common_ptr cinfo, + long min_bytes_needed, + long max_bytes_needed, + long already_allocated)); + + +/* + * This structure holds whatever state is needed to access a single + * backing-store object. The read/write/close method pointers are called + * by jmemmgr.c to manipulate the backing-store object; all other fields + * are private to the system-dependent backing store routines. + */ + +#define TEMP_NAME_LENGTH 64 /* max length of a temporary file's name */ + +#ifdef USE_MSDOS_MEMMGR /* DOS-specific junk */ + +typedef unsigned short XMSH; /* type of extended-memory handles */ +typedef unsigned short EMSH; /* type of expanded-memory handles */ + +typedef union { + short file_handle; /* DOS file handle if it's a temp file */ + XMSH xms_handle; /* handle if it's a chunk of XMS */ + EMSH ems_handle; /* handle if it's a chunk of EMS */ +} handle_union; + +#endif /* USE_MSDOS_MEMMGR */ + +typedef struct backing_store_struct * backing_store_ptr; + +typedef struct backing_store_struct { + /* Methods for reading/writing/closing this backing-store object */ + JMETHOD(void, read_backing_store, (j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, write_backing_store, (j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, close_backing_store, (j_common_ptr cinfo, + backing_store_ptr info)); + + /* Private fields for system-dependent backing-store management */ +#ifdef USE_MSDOS_MEMMGR + /* For the MS-DOS manager (jmemdos.c), we need: */ + handle_union handle; /* reference to backing-store storage object */ + char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ +#else + /* For a typical implementation with temp files, we need: */ + FILE * temp_file; /* stdio reference to temp file */ + char temp_name[TEMP_NAME_LENGTH]; /* name of temp file */ +#endif +} backing_store_info; + +/* + * Initial opening of a backing-store object. This must fill in the + * read/write/close pointers in the object. The read/write routines + * may take an error exit if the specified maximum file size is exceeded. + * (If jpeg_mem_available always returns a large value, this routine can + * just take an error exit.) + */ + +EXTERN void jpeg_open_backing_store JPP((j_common_ptr cinfo, + backing_store_ptr info, + long total_bytes_needed)); + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. jpeg_mem_init will be called before anything is + * allocated (and, therefore, nothing in cinfo is of use except the error + * manager pointer). It should return a suitable default value for + * max_memory_to_use; this may subsequently be overridden by the surrounding + * application. (Note that max_memory_to_use is only important if + * jpeg_mem_available chooses to consult it ... no one else will.) + * jpeg_mem_term may assume that all requested memory has been freed and that + * all opened backing-store objects have been closed. + */ + +EXTERN long jpeg_mem_init JPP((j_common_ptr cinfo)); +EXTERN void jpeg_mem_term JPP((j_common_ptr cinfo)); diff --git a/libs/jpeg6/jmorecfg.h b/libs/jpeg6/jmorecfg.h new file mode 100644 index 00000000..e14f31bd --- /dev/null +++ b/libs/jpeg6/jmorecfg.h @@ -0,0 +1,348 @@ +/* + * jmorecfg.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains additional configuration options that customize the + * JPEG software for special applications or support machine-dependent + * optimizations. Most users will not need to touch this file. + */ + + +/* + * Define BITS_IN_JSAMPLE as either + * 8 for 8-bit sample values (the usual setting) + * 12 for 12-bit sample values + * Only 8 and 12 are legal data precisions for lossy JPEG according to the + * JPEG standard, and the IJG code does not support anything else! + * We do not support run-time selection of data precision, sorry. + */ + +#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ + + +/* + * Maximum number of components (color channels) allowed in JPEG image. + * To meet the letter of the JPEG spec, set this to 255. However, darn + * few applications need more than 4 channels (maybe 5 for CMYK + alpha + * mask). We recommend 10 as a reasonable compromise; use 4 if you are + * really short on memory. (Each allowed component costs a hundred or so + * bytes of storage, whether actually used in an image or not.) + */ + +#define MAX_COMPONENTS 10 /* maximum number of image components */ + + +/* + * Basic data types. + * You may need to change these if you have a machine with unusual data + * type sizes; for example, "char" not 8 bits, "short" not 16 bits, + * or "long" not 32 bits. We don't care whether "int" is 16 or 32 bits, + * but it had better be at least 16. + */ + +/* Representation of a single sample (pixel element value). + * We frequently allocate large arrays of these, so it's important to keep + * them small. But if you have memory to burn and access to char or short + * arrays is very slow on your hardware, you might want to change these. + */ + +#if BITS_IN_JSAMPLE == 8 +/* JSAMPLE should be the smallest type that will hold the values 0..255. + * You can use a signed char by having GETJSAMPLE mask it with 0xFF. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JSAMPLE; +#ifdef CHAR_IS_UNSIGNED +#define GETJSAMPLE(value) ((int) (value)) +#else +#define GETJSAMPLE(value) ((int) (value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + +#define MAXJSAMPLE 255 +#define CENTERJSAMPLE 128 + +#endif /* BITS_IN_JSAMPLE == 8 */ + + +#if BITS_IN_JSAMPLE == 12 +/* JSAMPLE should be the smallest type that will hold the values 0..4095. + * On nearly all machines "short" will do nicely. + */ + +typedef short JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#define MAXJSAMPLE 4095 +#define CENTERJSAMPLE 2048 + +#endif /* BITS_IN_JSAMPLE == 12 */ + + +/* Representation of a DCT frequency coefficient. + * This should be a signed value of at least 16 bits; "short" is usually OK. + * Again, we allocate large arrays of these, but you can change to int + * if you have memory to burn and "short" is really slow. + */ + +typedef short JCOEF; + + +/* Compressed datastreams are represented as arrays of JOCTET. + * These must be EXACTLY 8 bits wide, at least once they are written to + * external storage. Note that when using the stdio data source/destination + * managers, this is also the data type passed to fread/fwrite. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JOCTET; +#define GETJOCTET(value) (value) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JOCTET; +#ifdef CHAR_IS_UNSIGNED +#define GETJOCTET(value) (value) +#else +#define GETJOCTET(value) ((value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + + +/* These typedefs are used for various table entries and so forth. + * They must be at least as wide as specified; but making them too big + * won't cost a huge amount of memory, so we don't provide special + * extraction code like we did for JSAMPLE. (In other words, these + * typedefs live at a different point on the speed/space tradeoff curve.) + */ + +/* UINT8 must hold at least the values 0..255. */ + +#ifdef HAVE_UNSIGNED_CHAR +typedef unsigned char UINT8; +#else /* not HAVE_UNSIGNED_CHAR */ +#ifdef CHAR_IS_UNSIGNED +typedef char UINT8; +#else /* not CHAR_IS_UNSIGNED */ +typedef short UINT8; +#endif /* CHAR_IS_UNSIGNED */ +#endif /* HAVE_UNSIGNED_CHAR */ + +/* UINT16 must hold at least the values 0..65535. */ + +#ifdef HAVE_UNSIGNED_SHORT +typedef unsigned short UINT16; +#else /* not HAVE_UNSIGNED_SHORT */ +typedef unsigned int UINT16; +#endif /* HAVE_UNSIGNED_SHORT */ + +/* INT16 must hold at least the values -32768..32767. */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */ +typedef short INT16; +#endif + +/* INT32 must hold at least signed 32-bit values. */ + +//#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */ +//typedef long INT32; +//#endif + +/* Datatype used for image dimensions. The JPEG standard only supports + * images up to 64K*64K due to 16-bit fields in SOF markers. Therefore + * "unsigned int" is sufficient on all machines. However, if you need to + * handle larger images and you don't mind deviating from the spec, you + * can change this datatype. + */ + +typedef unsigned int JDIMENSION; + +#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */ + + +/* These defines are used in all function definitions and extern declarations. + * You could modify them if you need to change function linkage conventions. + * Another application is to make all functions global for use with debuggers + * or code profilers that require it. + */ + +#define METHODDEF static /* a function called through method pointers */ +#define LOCAL static /* a function used only in its module */ +#define GLOBAL /* a function referenced thru EXTERNs */ +#define EXTERN extern /* a reference to a GLOBAL function */ + + +/* Here is the pseudo-keyword for declaring pointers that must be "far" + * on 80x86 machines. Most of the specialized coding for 80x86 is handled + * by just saying "FAR *" where such a pointer is needed. In a few places + * explicit coding is needed; see uses of the NEED_FAR_POINTERS symbol. + */ + +#ifdef NEED_FAR_POINTERS +#undef FAR +#define FAR far +#else +#undef FAR +#define FAR +#endif + + +/* + * On a few systems, type boolean and/or its values FALSE, TRUE may appear + * in standard header files. Or you may have conflicts with application- + * specific header files that you want to include together with these files. + * Defining HAVE_BOOLEAN before including jpeglib.h should make it work. + */ + +//#ifndef HAVE_BOOLEAN +//typedef int boolean; +//#endif +#ifndef FALSE /* in case these macros already exist */ +#define FALSE 0 /* values of boolean */ +#endif +#ifndef TRUE +#define TRUE 1 +#endif + + +/* + * The remaining options affect code selection within the JPEG library, + * but they don't need to be visible to most applications using the library. + * To minimize application namespace pollution, the symbols won't be + * defined unless JPEG_INTERNALS or JPEG_INTERNAL_OPTIONS has been defined. + */ + +#ifdef JPEG_INTERNALS +#define JPEG_INTERNAL_OPTIONS +#endif + +#ifdef JPEG_INTERNAL_OPTIONS + + +/* + * These defines indicate whether to include various optional functions. + * Undefining some of these symbols will produce a smaller but less capable + * library. Note that you can leave certain source files out of the + * compilation/linking process if you've #undef'd the corresponding symbols. + * (You may HAVE to do that if your compiler doesn't like null source files.) + */ + +/* Arithmetic coding is unsupported for legal reasons. Complaints to IBM. */ + +/* Capability options common to encoder and decoder: */ + +#undef DCT_ISLOW_SUPPORTED /* slow but accurate integer algorithm */ +#undef DCT_IFAST_SUPPORTED /* faster, less accurate integer method */ +#define DCT_FLOAT_SUPPORTED /* floating-point: accurate, fast on fast HW */ + +/* Encoder capability options: */ + +#undef C_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#define C_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define C_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define ENTROPY_OPT_SUPPORTED /* Optimization of entropy coding parms? */ +/* Note: if you selected 12-bit data precision, it is dangerous to turn off + * ENTROPY_OPT_SUPPORTED. The standard Huffman tables are only good for 8-bit + * precision, so jchuff.c normally uses entropy optimization to compute + * usable tables for higher precision. If you don't want to do optimization, + * you'll have to supply different default Huffman tables. + * The exact same statements apply for progressive JPEG: the default tables + * don't work for progressive mode. (This may get fixed, however.) + */ +#define INPUT_SMOOTHING_SUPPORTED /* Input image smoothing option? */ + +/* Decoder capability options: */ + +#undef D_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#undef D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#undef D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#undef BLOCK_SMOOTHING_SUPPORTED /* Block smoothing? (Progressive only) */ +#undef IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */ +#undef UPSAMPLE_SCALING_SUPPORTED /* Output rescaling at upsample stage? */ +#undef UPSAMPLE_MERGING_SUPPORTED /* Fast path for sloppy upsampling? */ +#undef QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */ +#undef QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */ + +/* more capability options later, no doubt */ + + +/* + * Ordering of RGB data in scanlines passed to or from the application. + * If your application wants to deal with data in the order B,G,R, just + * change these macros. You can also deal with formats such as R,G,B,X + * (one extra byte per pixel) by changing RGB_PIXELSIZE. Note that changing + * the offsets will also change the order in which colormap data is organized. + * RESTRICTIONS: + * 1. The sample applications cjpeg,djpeg do NOT support modified RGB formats. + * 2. These macros only affect RGB<=>YCbCr color conversion, so they are not + * useful if you are using JPEG color spaces other than YCbCr or grayscale. + * 3. The color quantizer modules will not behave desirably if RGB_PIXELSIZE + * is not 3 (they don't understand about dummy color components!). So you + * can't use color quantization if you change that value. + */ + +#define RGB_RED 0 /* Offset of Red in an RGB scanline element */ +#define RGB_GREEN 1 /* Offset of Green */ +#define RGB_BLUE 2 /* Offset of Blue */ +// http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=900 +// ydnar: setting this fucks jpeg loading in q3map2, disabling "fix" (3) +#define RGB_PIXELSIZE 4 /* JSAMPLEs per RGB scanline element */ + + +/* Definitions for speed-related optimizations. */ + + +/* If your compiler supports inline functions, define INLINE + * as the inline keyword; otherwise define it as empty. + */ + +#ifndef INLINE +#ifdef __GNUC__ /* for instance, GNU C knows about inline */ +#define INLINE __inline__ +#endif +#ifndef INLINE +#define INLINE /* default is to define it as empty */ +#endif +#endif + + +/* On some machines (notably 68000 series) "int" is 32 bits, but multiplying + * two 16-bit shorts is faster than multiplying two ints. Define MULTIPLIER + * as short on such a machine. MULTIPLIER must be at least 16 bits wide. + */ + +#ifndef MULTIPLIER +#define MULTIPLIER int /* type for fastest integer multiply */ +#endif + + +/* FAST_FLOAT should be either float or double, whichever is done faster + * by your compiler. (Note that this type is only used in the floating point + * DCT routines, so it only matters if you've defined DCT_FLOAT_SUPPORTED.) + * Typically, float is faster in ANSI C compilers, while double is faster in + * pre-ANSI compilers (because they insist on converting to double anyway). + * The code below therefore chooses float if we have ANSI-style prototypes. + */ + +#ifndef FAST_FLOAT +#ifdef HAVE_PROTOTYPES +#define FAST_FLOAT float +#else +#define FAST_FLOAT double +#endif +#endif + +#endif /* JPEG_INTERNAL_OPTIONS */ diff --git a/libs/jpeg6/jpeg6.vcproj b/libs/jpeg6/jpeg6.vcproj new file mode 100644 index 00000000..9e002501 --- /dev/null +++ b/libs/jpeg6/jpeg6.vcproj @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/jpeg6/jpegint.h b/libs/jpeg6/jpegint.h new file mode 100644 index 00000000..ab5bee2c --- /dev/null +++ b/libs/jpeg6/jpegint.h @@ -0,0 +1,388 @@ +/* + * jpegint.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* perhaps this should be an array??? */ + JMETHOD(void, forward_DCT, (j_compress_ptr cinfo, + jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + /* write_any_marker is exported for use by applications */ + /* Probably only COM and APPn markers should be written */ + JMETHOD(void, write_any_marker, (j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen)); + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); +}; + + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + /* Application-overridable marker processing methods */ + jpeg_marker_parser_method process_COM; + jpeg_marker_parser_method process_APPn[16]; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_phuff_encoder jIPHEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_phuff_decoder jIPHDecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Compression module initialization routines */ +EXTERN void jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN void jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN void jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN void jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN void jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN void jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN void jinit_phuff_encoder JPP((j_compress_ptr cinfo)); +EXTERN void jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN void jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_phuff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN void jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN long jdiv_round_up JPP((long a, long b)); +EXTERN long jround_up JPP((long a, long b)); +EXTERN void jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN void jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +EXTERN void jzero_far JPP((void FAR * target, size_t bytestozero)); +/* Constant tables in jutils.c */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ diff --git a/libs/jpeg6/jpgload.cpp b/libs/jpeg6/jpgload.cpp new file mode 100644 index 00000000..323b073a --- /dev/null +++ b/libs/jpeg6/jpgload.cpp @@ -0,0 +1,186 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "radiant_jpeglib.h" +#include "jerror.h" +#include + +GLOBAL int LoadJPGBuff(unsigned char *fbuffer, int bufsize, unsigned char **pic, int *width, int *height ) +{ + + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + + struct jpeg_error_mgr jerr; + /* More stuff */ + JSAMPARRAY buffer; /* Output row buffer */ + int row_stride; /* physical row width in output buffer */ + unsigned char *out, *bbuf; + int nSize; + int jmpret; + + // Rad additions: initialize the longjmp buffer + jmpret = setjmp( rad_loadfailed ); + if (jmpret != 0) + { + *pic = (unsigned char *)rad_errormsg; + return -1; + } + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error(&jerr); + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress(&cinfo); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_stdio_src(&cinfo, fbuffer, bufsize); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header(&cinfo, TRUE); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* In this example, we don't need to change any of the defaults set by + * jpeg_read_header(), so we do nothing here. + */ + + /* Step 5: Start decompressor */ + + (void) jpeg_start_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* ydnar: radiant only handles RGB, non-progressive format jpegs */ + if( cinfo.output_components != 4 ) + { + *pic = const_cast(reinterpret_cast("Non-RGB JPEG encountered (unsupported)")); + return -1; + } + if( cinfo.progressive_mode ) + { + *pic = const_cast(reinterpret_cast("Progressive JPEG encountered (unsupported)")); + return -1; + } + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + + /* JSAMPLEs per row in output buffer */ + row_stride = cinfo.output_width * cinfo.output_components; + nSize = cinfo.output_width*cinfo.output_height*cinfo.output_components; + + out = reinterpret_cast( malloc( nSize+ 1 ) ); + memset( out, 255, nSize + 1 ); + + *pic = out; + *width = cinfo.output_width; + *height = cinfo.output_height; + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + while (cinfo.output_scanline < cinfo.output_height) + { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + bbuf = out + row_stride * cinfo.output_scanline; + buffer = &bbuf; + (void) jpeg_read_scanlines( &cinfo, buffer, 1 ); + } + + // clear all the alphas to 255 + { + int i, j; + unsigned char *buf; + + buf = *pic; + + j = cinfo.output_width * cinfo.output_height * 4; + for ( i = 3 ; i < j ; i+=4 ) { + buf[i] = 255; + } + } + + /* Step 7: Finish decompression */ + + (void) jpeg_finish_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* Step 8: Release JPEG decompression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_decompress(&cinfo); + + /* After finish_decompress, we can close the input file. + * Here we postpone it until after no more JPEG errors are possible, + * so as to simplify the setjmp error logic above. (Actually, I don't + * think that jpeg_destroy can do an error exit, but why assume anything...) + */ + //free (fbuffer); + + /* At this point you may want to check to see whether any corrupt-data + * warnings occurred (test whether jerr.pub.num_warnings is nonzero). + */ + + /* And we're done! */ + return 0; +} diff --git a/libs/jpeg6/jutils.cpp b/libs/jpeg6/jutils.cpp new file mode 100644 index 00000000..549648de --- /dev/null +++ b/libs/jpeg6/jutils.cpp @@ -0,0 +1,175 @@ +/* + * jutils.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains tables and miscellaneous utility routines needed + * for both compression and decompression. + * Note we prefix all global names with "j" to minimize conflicts with + * a surrounding application. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "radiant_jpeglib.h" + + +/* + * jpeg_zigzag_order[i] is the zigzag-order position of the i'th element + * of a DCT block read in natural order (left to right, top to bottom). + */ + +const int jpeg_zigzag_order[DCTSIZE2] = { + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63 +}; + +/* + * jpeg_natural_order[i] is the natural-order position of the i'th element + * of zigzag order. + * + * When reading corrupted data, the Huffman decoders could attempt + * to reference an entry beyond the end of this array (if the decoded + * zero run length reaches past the end of the block). To prevent + * wild stores without adding an inner-loop test, we put some extra + * "63"s after the real entries. This will cause the extra coefficient + * to be stored in location 63 of the block, not somewhere random. + * The worst case would be a run-length of 15, which means we need 16 + * fake entries. + */ + +const int jpeg_natural_order[DCTSIZE2+16] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + + +/* + * Arithmetic utilities + */ + +GLOBAL long +jdiv_round_up (long a, long b) +/* Compute a/b rounded up to next integer, ie, ceil(a/b) */ +/* Assumes a >= 0, b > 0 */ +{ + return (a + b - 1L) / b; +} + + +GLOBAL long +jround_up (long a, long b) +/* Compute a rounded up to next multiple of b, ie, ceil(a/b)*b */ +/* Assumes a >= 0, b > 0 */ +{ + a += b - 1L; + return a - (a % b); +} + + +/* On normal machines we can apply MEMCOPY() and MEMZERO() to sample arrays + * and coefficient-block arrays. This won't work on 80x86 because the arrays + * are FAR and we're assuming a small-pointer memory model. However, some + * DOS compilers provide far-pointer versions of memcpy() and memset() even + * in the small-model libraries. These will be used if USE_FMEM is defined. + * Otherwise, the routines below do it the hard way. (The performance cost + * is not all that great, because these routines aren't very heavily used.) + */ + +#ifndef NEED_FAR_POINTERS /* normal case, same as regular macros */ +#define FMEMCOPY(dest,src,size) MEMCOPY(dest,src,size) +#define FMEMZERO(target,size) MEMZERO(target,size) +#else /* 80x86 case, define if we can */ +#ifdef USE_FMEM +#define FMEMCOPY(dest,src,size) _fmemcpy((void FAR *)(dest), (const void FAR *)(src), (size_t)(size)) +#define FMEMZERO(target,size) _fmemset((void FAR *)(target), 0, (size_t)(size)) +#endif +#endif + + +GLOBAL void +jcopy_sample_rows (JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols) +/* Copy some rows of samples from one place to another. + * num_rows rows are copied from input_array[source_row++] + * to output_array[dest_row++]; these areas may overlap for duplication. + * The source and destination arrays must be at least as wide as num_cols. + */ +{ + register JSAMPROW inptr, outptr; +#ifdef FMEMCOPY + register size_t count = (size_t) (num_cols * SIZEOF(JSAMPLE)); +#else + register JDIMENSION count; +#endif + register int row; + + input_array += source_row; + output_array += dest_row; + + for (row = num_rows; row > 0; row--) { + inptr = *input_array++; + outptr = *output_array++; +#ifdef FMEMCOPY + FMEMCOPY(outptr, inptr, count); +#else + for (count = num_cols; count > 0; count--) + *outptr++ = *inptr++; /* needn't bother with GETJSAMPLE() here */ +#endif + } +} + + +GLOBAL void +jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks) +/* Copy a row of coefficient blocks from one place to another. */ +{ +#ifdef FMEMCOPY + FMEMCOPY(output_row, input_row, num_blocks * (DCTSIZE2 * SIZEOF(JCOEF))); +#else + register JCOEFPTR inptr, outptr; + register long count; + + inptr = (JCOEFPTR) input_row; + outptr = (JCOEFPTR) output_row; + for (count = (long) num_blocks * DCTSIZE2; count > 0; count--) { + *outptr++ = *inptr++; + } +#endif +} + + +GLOBAL void +jzero_far (void FAR * target, size_t bytestozero) +/* Zero out a chunk of FAR memory. */ +/* This might be sample-array data, block-array data, or alloc_large data. */ +{ +#ifdef FMEMZERO + FMEMZERO(target, bytestozero); +#else + register char FAR * ptr = (char FAR *) target; + register size_t count; + + for (count = bytestozero; count > 0; count--) { + *ptr++ = 0; + } +#endif +} diff --git a/libs/jpeg6/jversion.h b/libs/jpeg6/jversion.h new file mode 100644 index 00000000..f2f1b8da --- /dev/null +++ b/libs/jpeg6/jversion.h @@ -0,0 +1,14 @@ +/* + * jversion.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains software version identification. + */ + + +#define JVERSION "6 2-Aug-95" + +#define JCOPYRIGHT "Copyright (C) 1995, Thomas G. Lane" diff --git a/libs/jpeglib.h b/libs/jpeglib.h new file mode 100644 index 00000000..f5b59010 --- /dev/null +++ b/libs/jpeglib.h @@ -0,0 +1,1123 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + * jpeglib.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the application interface for the JPEG library. + * Most applications using the library need only include this file, + * and perhaps jerror.h if they want to know the exact error codes. + */ + +#ifndef JPEGLIB_H +#define JPEGLIB_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +// LZ: linux stuff +#if !defined (WIN32) + +#include +#include + +#ifndef boolean +#ifdef __cplusplus +#define boolean bool +#else +typedef int boolean; +#endif +#endif + +#endif + +#ifdef __MACOS__ + +// JDC: stuff to make mac version compile +#define boolean qboolean +#define register +#define INT32 int + +#endif + +// rad additions +// 11.29.99 + +//#include "cmdlib.h" +#ifdef _WIN32 +#include "windows.h" +#include "stdio.h" +#endif + +#ifndef INT32 +#define INT32 int +#endif + +// TTimo: if LoadJPGBuff returns -1, *pic is the error message +extern int LoadJPGBuff(unsigned char *fbuffer, int bufsize, unsigned char **pic, int *width, int *height ); +// rad end + + +/* + * First we include the configuration files that record how this + * installation of the JPEG library is set up. jconfig.h can be + * generated automatically for many systems. jmorecfg.h contains + * manual configuration options that most people need not worry about. + */ + +#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */ +#include "jpeg6/jconfig.h" /* widely used configuration options */ +#endif +#include "jpeg6/jmorecfg.h" /* seldom changed options */ + + +/* Version ID for the JPEG library. + * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60". + */ + +#define JPEG_LIB_VERSION 60 /* Version 6 */ + + +/* Various constants determining the sizes of things. + * All of these are specified by the JPEG standard, so don't change them + * if you want to be compatible. + */ + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ +#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ +#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ +#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ +#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ +#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ +/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; + * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. + * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU + * to handle it. We even let you do this from the jconfig.h file. However, + * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe + * sometimes emits noncompliant files doesn't mean you should too. + */ +#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */ +#ifndef D_MAX_BLOCKS_IN_MCU +#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */ +#endif + + +/* This macro is used to declare a "method", that is, a function pointer. + * We want to supply prototype parameters if the compiler can cope. + * Note that the arglist parameter must be parenthesized! + */ + +#ifdef HAVE_PROTOTYPES +#define JMETHOD(type,methodname,arglist) type (*methodname) arglist +#else +#define JMETHOD(type,methodname,arglist) type (*methodname) () +#endif + + +/* Data structures for images (arrays of samples and of DCT coefficients). + * On 80x86 machines, the image arrays are too big for near pointers, + * but the pointer arrays can fit in near memory. + */ + +typedef JSAMPLE FAR *JSAMPROW; /* ptr to one image row of pixel samples. */ +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ + +typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ +typedef JBLOCK FAR *JBLOCKROW; /* pointer to one row of coefficient blocks */ +typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ +typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ + +typedef JCOEF FAR *JCOEFPTR; /* useful in a couple of places */ + + +/* Types for JPEG compression parameters and working tables. */ + + +/* DCT coefficient quantization tables. */ + +typedef struct { + /* This field directly represents the contents of a JPEG DQT marker. + * Note: the values are always given in zigzag order. + */ + UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JQUANT_TBL; + + +/* Huffman coding tables. */ + +typedef struct { + /* These two fields directly represent the contents of a JPEG DHT marker */ + UINT8 bits[17]; /* bits[k] = # of symbols with codes of */ + /* length k bits; bits[0] is unused */ + UINT8 huffval[256]; /* The symbols, in order of incr code length */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JHUFF_TBL; + + +/* Basic info about one component (color channel). */ + +typedef struct { + /* These values are fixed over the whole image. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOF marker. */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ + /* These values may vary between scans. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOS marker. */ + /* The decompressor output side may not use these variables. */ + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + + /* Remaining fields should be treated as private by applications. */ + + /* These values are computed during compression or decompression startup: */ + /* Component's size in DCT blocks. + * Any dummy blocks added to complete an MCU are not counted; therefore + * these values do not depend on whether a scan is interleaved or not. + */ + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + /* Size of a DCT block in samples. Always DCTSIZE for compression. + * For decompression this is the size of the output from one DCT block, + * reflecting any scaling we choose to apply during the IDCT step. + * Values of 1,2,4,8 are likely to be supported. Note that different + * components may receive different IDCT scalings. + */ + int DCT_scaled_size; + /* The downsampled dimensions are the component's actual, unpadded number + * of samples at the main buffer (preprocessing/compression interface), thus + * downsampled_width = ceil(image_width * Hi/Hmax) + * and similarly for height. For decompression, IDCT scaling is included, so + * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE) + */ + JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_height; /* actual height in samples */ + /* This flag is used only for decompression. In cases where some of the + * components will be ignored (eg grayscale output from YCbCr image), + * we can skip most computations for the unused components. + */ + boolean component_needed; /* do we need the value of this component? */ + + /* These values are computed before starting a scan of the component. */ + /* The decompressor output side may not use these variables. */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ + + /* Saved quantization table for component; NULL if none yet saved. + * See jdinput.c comments about the need for this information. + * This field is not currently used by the compressor. + */ + JQUANT_TBL * quant_table; + + /* Private per-component storage for DCT or IDCT subsystem. */ + void * dct_table; +} jpeg_component_info; + + +/* The script for encoding a multiple-scan file is an array of these: */ + +typedef struct { + int comps_in_scan; /* number of components encoded in this scan */ + int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */ + int Ss, Se; /* progressive JPEG spectral selection parms */ + int Ah, Al; /* progressive JPEG successive approx. parms */ +} jpeg_scan_info; + + +/* Known color spaces. */ + +typedef enum { + JCS_UNKNOWN, /* error/unspecified */ + JCS_GRAYSCALE, /* monochrome */ + JCS_RGB, /* red/green/blue */ + JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ + JCS_CMYK, /* C/M/Y/K */ + JCS_YCCK /* Y/Cb/Cr/K */ +} J_COLOR_SPACE; + +/* DCT/IDCT algorithm options. */ + +typedef enum { + JDCT_ISLOW, /* slow but accurate integer algorithm */ + JDCT_IFAST, /* faster, less accurate integer method */ + JDCT_FLOAT /* floating-point: accurate, fast on fast HW */ +} J_DCT_METHOD; + +#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */ +#define JDCT_DEFAULT JDCT_ISLOW +#endif +#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */ +#define JDCT_FASTEST JDCT_IFAST +#endif + +/* Dithering options for decompression. */ + +typedef enum { + JDITHER_NONE, /* no dithering */ + JDITHER_ORDERED, /* simple ordered dither */ + JDITHER_FS /* Floyd-Steinberg error diffusion dither */ +} J_DITHER_MODE; + + +/* Common fields between JPEG compression and decompression master structs. */ + +#define jpeg_common_fields \ + struct jpeg_error_mgr * err; /* Error handler module */\ + struct jpeg_memory_mgr * mem; /* Memory manager module */\ + struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */\ + boolean is_decompressor; /* so common code can tell which is which */\ + int global_state /* for checking call sequence validity */ + +/* Routines that are to be used by both halves of the library are declared + * to receive a pointer to this structure. There are no actual instances of + * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct. + */ +struct jpeg_common_struct { + jpeg_common_fields; /* Fields common to both master struct types */ + /* Additional fields follow in an actual jpeg_compress_struct or + * jpeg_decompress_struct. All three structs must agree on these + * initial fields! (This would be a lot cleaner in C++.) + */ +}; + +typedef struct jpeg_common_struct * j_common_ptr; +typedef struct jpeg_compress_struct * j_compress_ptr; +typedef struct jpeg_decompress_struct * j_decompress_ptr; + + +/* Master record for a compression instance */ + +struct jpeg_compress_struct { + jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */ + + /* Destination for compressed data */ + struct jpeg_destination_mgr * dest; + + /* Description of source image --- these fields must be filled in by + * outer application before starting compression. in_color_space must + * be correct before you can even call jpeg_set_defaults(). + */ + + JDIMENSION image_width; /* input image width */ + JDIMENSION image_height; /* input image height */ + int input_components; /* # of color components in input image */ + J_COLOR_SPACE in_color_space; /* colorspace of input image */ + + double input_gamma; /* image gamma of input image */ + + /* Compression parameters --- these fields must be set before calling + * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to + * initialize everything to reasonable defaults, then changing anything + * the application specifically wants to change. That way you won't get + * burnt when new parameters are added. Also note that there are several + * helper routines to simplify changing parameters. + */ + + int data_precision; /* bits of precision in image data */ + + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + int num_scans; /* # of entries in scan_info array */ + const jpeg_scan_info * scan_info; /* script for multi-scan file, or NULL */ + /* The default value of scan_info is NULL, which causes a single-scan + * sequential JPEG file to be emitted. To create a multi-scan file, + * set num_scans and scan_info to point to an array of scan definitions. + */ + + boolean raw_data_in; /* TRUE=caller supplies downsampled data */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + boolean optimize_coding; /* TRUE=optimize entropy encoding parms */ + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + int smoothing_factor; /* 1..100, or 0 for no input smoothing */ + J_DCT_METHOD dct_method; /* DCT algorithm selector */ + + /* The restart interval can be specified in absolute MCUs by setting + * restart_interval, or in MCU rows by setting restart_in_rows + * (in which case the correct restart_interval will be figured + * for each scan). + */ + unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */ + int restart_in_rows; /* if > 0, MCU rows per restart interval */ + + /* Parameters controlling emission of special markers. */ + + boolean write_JFIF_header; /* should a JFIF marker be written? */ + /* These three values are not used by the JPEG code, merely copied */ + /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */ + /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */ + /* ratio is defined by X_density/Y_density even when density_unit=0. */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean write_Adobe_marker; /* should an Adobe marker be written? */ + + /* State variable: index of next scanline to be written to + * jpeg_write_scanlines(). Application may use this to control its + * processing loop, e.g., "while (next_scanline < image_height)". + */ + + JDIMENSION next_scanline; /* 0 .. image_height-1 */ + + /* Remaining fields are known throughout compressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during compression startup + */ + boolean progressive_mode; /* TRUE if scan script uses progressive mode */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */ + /* The coefficient controller receives data in units of MCU rows as defined + * for fully interleaved scans (whether the JPEG file is interleaved or not). + * There are v_samp_factor * DCTSIZE sample rows of each component in an + * "iMCU" (interleaved MCU) row. + */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* + * Links to compression subobjects (methods and private variables of modules) + */ + struct jpeg_comp_master * master; + struct jpeg_c_main_controller * main; + struct jpeg_c_prep_controller * prep; + struct jpeg_c_coef_controller * coef; + struct jpeg_marker_writer * marker; + struct jpeg_color_converter * cconvert; + struct jpeg_downsampler * downsample; + struct jpeg_forward_dct * fdct; + struct jpeg_entropy_encoder * entropy; +}; + + +/* Master record for a decompression instance */ + +struct jpeg_decompress_struct { + jpeg_common_fields; /* Fields shared with jpeg_compress_struct */ + + /* Source of compressed data */ + struct jpeg_source_mgr * src; + + /* Basic description of image --- filled in by jpeg_read_header(). */ + /* Application may inspect these values to decide how to process image. */ + + JDIMENSION image_width; /* nominal image width (from SOF marker) */ + JDIMENSION image_height; /* nominal image height */ + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + /* Decompression processing parameters --- these fields must be set before + * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes + * them to default values. + */ + + J_COLOR_SPACE out_color_space; /* colorspace for output */ + + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + double output_gamma; /* image gamma wanted in output */ + + boolean buffered_image; /* TRUE=multiple output passes */ + boolean raw_data_out; /* TRUE=downsampled data wanted */ + + J_DCT_METHOD dct_method; /* IDCT algorithm selector */ + boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */ + boolean do_block_smoothing; /* TRUE=apply interblock smoothing */ + + boolean quantize_colors; /* TRUE=colormapped output wanted */ + /* the following are ignored if not quantize_colors: */ + J_DITHER_MODE dither_mode; /* type of color dithering to use */ + boolean two_pass_quantize; /* TRUE=use two-pass color quantization */ + int desired_number_of_colors; /* max # colors to use in created colormap */ + /* these are significant only in buffered-image mode: */ + boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */ + boolean enable_external_quant;/* enable future use of external colormap */ + boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */ + + /* Description of actual output image that will be returned to application. + * These fields are computed by jpeg_start_decompress(). + * You can also use jpeg_calc_output_dimensions() to determine these values + * in advance of calling jpeg_start_decompress(). + */ + + JDIMENSION output_width; /* scaled image width */ + JDIMENSION output_height; /* scaled image height */ + int out_color_components; /* # of color components in out_color_space */ + int output_components; /* # of color components returned */ + /* output_components is 1 (a colormap index) when quantizing colors; + * otherwise it equals out_color_components. + */ + int rec_outbuf_height; /* min recommended height of scanline buffer */ + /* If the buffer passed to jpeg_read_scanlines() is less than this many rows + * high, space and time will be wasted due to unnecessary data copying. + * Usually rec_outbuf_height will be 1 or 2, at most 4. + */ + + /* When quantizing colors, the output colormap is described by these fields. + * The application can supply a colormap by setting colormap non-NULL before + * calling jpeg_start_decompress; otherwise a colormap is created during + * jpeg_start_decompress or jpeg_start_output. + * The map has out_color_components rows and actual_number_of_colors columns. + */ + int actual_number_of_colors; /* number of entries in use */ + JSAMPARRAY colormap; /* The color map as a 2-D pixel array */ + + /* State variables: these variables indicate the progress of decompression. + * The application may examine these but must not modify them. + */ + + /* Row index of next scanline to be read from jpeg_read_scanlines(). + * Application may use this to control its processing loop, e.g., + * "while (output_scanline < output_height)". + */ + JDIMENSION output_scanline; /* 0 .. output_height-1 */ + + /* Current input scan number and number of iMCU rows completed in scan. + * These indicate the progress of the decompressor input side. + */ + int input_scan_number; /* Number of SOS markers seen so far */ + JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */ + + /* The "output scan number" is the notional scan being displayed by the + * output side. The decompressor will not allow output scan/row number + * to get ahead of input scan/row, but it can fall arbitrarily far behind. + */ + int output_scan_number; /* Nominal scan number being displayed */ + JDIMENSION output_iMCU_row; /* Number of iMCU rows read */ + + /* Current progression status. coef_bits[c][i] indicates the precision + * with which component c's DCT coefficient i (in zigzag order) is known. + * It is -1 when no data has yet been received, otherwise it is the point + * transform (shift) value for the most recent scan of the coefficient + * (thus, 0 at completion of the progression). + * This pointer is NULL when reading a non-progressive file. + */ + int (*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */ + + /* Internal JPEG parameters --- the application usually need not look at + * these fields. Note that the decompressor output side may not use + * any parameters that can change between scans. + */ + + /* Quantization and Huffman tables are carried forward across input + * datastreams when processing abbreviated JPEG datastreams. + */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + /* These parameters are never carried across datastreams, since they + * are given in SOF/SOS markers or defined to be reset by SOI. + */ + + int data_precision; /* bits of precision in image data */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + unsigned int restart_interval; /* MCUs per restart interval, or 0 for no restart */ + + /* These fields record data obtained from optional markers recognized by + * the JPEG library. + */ + boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */ + /* Data copied from JFIF marker: */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */ + UINT8 Adobe_transform; /* Color transform code from Adobe marker */ + + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + + /* Remaining fields are known throughout decompressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during decompression startup + */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + int min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */ + /* The coefficient controller's input and output progress is measured in + * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows + * in fully interleaved JPEG scans, but are used whether the scan is + * interleaved or not. We define an iMCU row as v_samp_factor DCT block + * rows of each component. Therefore, the IDCT output contains + * v_samp_factor*DCT_scaled_size sample rows of a component per iMCU row. + */ + + JSAMPLE * sample_range_limit; /* table for fast range-limiting */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + * Note that the decompressor output side must not use these fields. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* This field is shared between entropy decoder and marker parser. + * It is either zero or the code of a JPEG marker that has been + * read from the data source, but has not yet been processed. + */ + int unread_marker; + + /* + * Links to decompression subobjects (methods, private variables of modules) + */ + struct jpeg_decomp_master * master; + struct jpeg_d_main_controller * main; + struct jpeg_d_coef_controller * coef; + struct jpeg_d_post_controller * post; + struct jpeg_input_controller * inputctl; + struct jpeg_marker_reader * marker; + struct jpeg_entropy_decoder * entropy; + struct jpeg_inverse_dct * idct; + struct jpeg_upsampler * upsample; + struct jpeg_color_deconverter * cconvert; + struct jpeg_color_quantizer * cquantize; +}; + + +/* "Object" declarations for JPEG modules that may be supplied or called + * directly by the surrounding application. + * As with all objects in the JPEG library, these structs only define the + * publicly visible methods and state variables of a module. Additional + * private fields may exist after the public ones. + */ + + +/* Error handler object */ + +struct jpeg_error_mgr { + /* Error exit handler: does not return to caller */ + JMETHOD(void, error_exit, (j_common_ptr cinfo)); + /* Conditionally emit a trace or warning message */ + JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level)); + /* Routine that actually outputs a trace or error message */ + JMETHOD(void, output_message, (j_common_ptr cinfo)); + /* Format a message string for the most recent JPEG error or message */ + JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer)); +#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */ + /* Reset error state variables at start of a new image */ + JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo)); + + /* The message ID code and any parameters are saved here. + * A message can have one string parameter or up to 8 int parameters. + */ + int msg_code; +#define JMSG_STR_PARM_MAX 80 + union { + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + + /* Standard state variables for error facility */ + + int trace_level; /* max msg_level that will be displayed */ + + /* For recoverable corrupt-data errors, we emit a warning message, + * but keep going unless emit_message chooses to abort. emit_message + * should count warnings in num_warnings. The surrounding application + * can check for bad data by seeing if num_warnings is nonzero at the + * end of processing. + */ + long num_warnings; /* number of corrupt-data warnings */ + + /* These fields point to the table(s) of error message strings. + * An application can change the table pointer to switch to a different + * message list (typically, to change the language in which errors are + * reported). Some applications may wish to add additional error codes + * that will be handled by the JPEG library error mechanism; the second + * table pointer is used for this purpose. + * + * First table includes all errors generated by JPEG library itself. + * Error code 0 is reserved for a "no such error string" message. + */ + const char * const * jpeg_message_table; /* Library errors */ + int last_jpeg_message; /* Table contains strings 0..last_jpeg_message */ + /* Second table can be added by application (see cjpeg/djpeg for example). + * It contains strings numbered first_addon_message..last_addon_message. + */ + const char * const * addon_message_table; /* Non-library errors */ + int first_addon_message; /* code for first string in addon table */ + int last_addon_message; /* code for last string in addon table */ +}; + + +/* Progress monitor object */ + +struct jpeg_progress_mgr { + JMETHOD(void, progress_monitor, (j_common_ptr cinfo)); + + long pass_counter; /* work units completed in this pass */ + long pass_limit; /* total number of work units in this pass */ + int completed_passes; /* passes completed so far */ + int total_passes; /* total number of passes expected */ +}; + + +/* Data destination object for compression */ + +struct jpeg_destination_mgr { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + + JMETHOD(void, init_destination, (j_compress_ptr cinfo)); + JMETHOD(boolean, empty_output_buffer, (j_compress_ptr cinfo)); + JMETHOD(void, term_destination, (j_compress_ptr cinfo)); +}; + + +/* Data source object for decompression */ + +struct jpeg_source_mgr { + const JOCTET * next_input_byte; /* => next byte to read from buffer */ + size_t bytes_in_buffer; /* # of bytes remaining in buffer */ + + JMETHOD(void, init_source, (j_decompress_ptr cinfo)); + JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo)); + JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes)); + JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired)); + JMETHOD(void, term_source, (j_decompress_ptr cinfo)); +}; + + +/* Memory manager object. + * Allocates "small" objects (a few K total), "large" objects (tens of K), + * and "really big" objects (virtual arrays with backing store if needed). + * The memory manager does not allow individual objects to be freed; rather, + * each created object is assigned to a pool, and whole pools can be freed + * at once. This is faster and more convenient than remembering exactly what + * to free, especially where malloc()/free() are not too speedy. + * NB: alloc routines never return NULL. They exit to error_exit if not + * successful. + */ + +#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */ +#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */ +#define JPOOL_NUMPOOLS 2 + +typedef struct jvirt_sarray_control * jvirt_sarray_ptr; +typedef struct jvirt_barray_control * jvirt_barray_ptr; + + +struct jpeg_memory_mgr { + /* Method pointers */ + JMETHOD(void *, alloc_small, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(void FAR *, alloc_large, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(JSAMPARRAY, alloc_sarray, (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, + JDIMENSION numrows)); + JMETHOD(JBLOCKARRAY, alloc_barray, (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, + JDIMENSION numrows)); + JMETHOD(jvirt_sarray_ptr, request_virt_sarray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION samplesperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(jvirt_barray_ptr, request_virt_barray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION blocksperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(void, realize_virt_arrays, (j_common_ptr cinfo)); + JMETHOD(JSAMPARRAY, access_virt_sarray, (j_common_ptr cinfo, + jvirt_sarray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(JBLOCKARRAY, access_virt_barray, (j_common_ptr cinfo, + jvirt_barray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(void, free_pool, (j_common_ptr cinfo, int pool_id)); + JMETHOD(void, self_destruct, (j_common_ptr cinfo)); + + /* Limit on memory allocation for this JPEG object. (Note that this is + * merely advisory, not a guaranteed maximum; it only affects the space + * used for virtual-array buffers.) May be changed by outer application + * after creating the JPEG object. + */ + long max_memory_to_use; +}; + + +/* Routine signature for application-supplied marker processing methods. + * Need not pass marker code since it is stored in cinfo->unread_marker. + */ +typedef JMETHOD(boolean, jpeg_marker_parser_method, (j_decompress_ptr cinfo)); + + +/* Declarations for routines called by application. + * The JPP macro hides prototype parameters from compilers that can't cope. + * Note JPP requires double parentheses. + */ + +#ifdef HAVE_PROTOTYPES +#define JPP(arglist) arglist +#else +#define JPP(arglist) () +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. + * We shorten external names to be unique in the first six letters, which + * is good enough for all known systems. + * (If your compiler itself needs names to be unique in less than 15 + * characters, you are out of luck. Get a better compiler.) + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_error jStdError +#define jpeg_create_compress jCreaCompress +#define jpeg_create_decompress jCreaDecompress +#define jpeg_destroy_compress jDestCompress +#define jpeg_destroy_decompress jDestDecompress +#define jpeg_stdio_dest jStdDest +#define jpeg_stdio_src jStdSrc +#define jpeg_set_defaults jSetDefaults +#define jpeg_set_colorspace jSetColorspace +#define jpeg_default_colorspace jDefColorspace +#define jpeg_set_quality jSetQuality +#define jpeg_set_linear_quality jSetLQuality +#define jpeg_add_quant_table jAddQuantTable +#define jpeg_quality_scaling jQualityScaling +#define jpeg_simple_progression jSimProgress +#define jpeg_suppress_tables jSuppressTables +#define jpeg_alloc_quant_table jAlcQTable +#define jpeg_alloc_huff_table jAlcHTable +#define jpeg_start_compress jStrtCompress +#define jpeg_write_scanlines jWrtScanlines +#define jpeg_finish_compress jFinCompress +#define jpeg_write_raw_data jWrtRawData +#define jpeg_write_marker jWrtMarker +#define jpeg_write_tables jWrtTables +#define jpeg_read_header jReadHeader +#define jpeg_start_decompress jStrtDecompress +#define jpeg_read_scanlines jReadScanlines +#define jpeg_finish_decompress jFinDecompress +#define jpeg_read_raw_data jReadRawData +#define jpeg_has_multiple_scans jHasMultScn +#define jpeg_start_output jStrtOutput +#define jpeg_finish_output jFinOutput +#define jpeg_input_complete jInComplete +#define jpeg_new_colormap jNewCMap +#define jpeg_consume_input jConsumeInput +#define jpeg_calc_output_dimensions jCalcDimensions +#define jpeg_set_marker_processor jSetMarker +#define jpeg_read_coefficients jReadCoefs +#define jpeg_write_coefficients jWrtCoefs +#define jpeg_copy_critical_parameters jCopyCrit +#define jpeg_abort_compress jAbrtCompress +#define jpeg_abort_decompress jAbrtDecompress +#define jpeg_abort jAbort +#define jpeg_destroy jDestroy +#define jpeg_resync_to_restart jResyncRestart +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Default error-management setup */ +EXTERN struct jpeg_error_mgr *jpeg_std_error JPP((struct jpeg_error_mgr *err)); + +/* Initialization and destruction of JPEG compression objects */ +/* NB: you must set up the error-manager BEFORE calling jpeg_create_xxx */ +EXTERN void jpeg_create_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_create_decompress JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_destroy_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_destroy_decompress JPP((j_decompress_ptr cinfo)); + +/* Standard data source and destination managers: stdio streams. */ +/* Caller is responsible for opening the file before and closing after. */ +EXTERN void jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile)); +EXTERN void jpeg_stdio_src JPP((j_decompress_ptr cinfo, unsigned char *infile, int bufsize)); + +/* Default parameter setup for compression */ +EXTERN void jpeg_set_defaults JPP((j_compress_ptr cinfo)); +/* Compression parameter setup aids */ +EXTERN void jpeg_set_colorspace JPP((j_compress_ptr cinfo, + J_COLOR_SPACE colorspace)); +EXTERN void jpeg_default_colorspace JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_set_quality JPP((j_compress_ptr cinfo, int quality, + boolean force_baseline)); +EXTERN void jpeg_set_linear_quality JPP((j_compress_ptr cinfo, + int scale_factor, + boolean force_baseline)); +EXTERN void jpeg_add_quant_table JPP((j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, + boolean force_baseline)); +EXTERN int jpeg_quality_scaling JPP((int quality)); +EXTERN void jpeg_simple_progression JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_suppress_tables JPP((j_compress_ptr cinfo, + boolean suppress)); +EXTERN JQUANT_TBL * jpeg_alloc_quant_table JPP((j_common_ptr cinfo)); +EXTERN JHUFF_TBL * jpeg_alloc_huff_table JPP((j_common_ptr cinfo)); + +/* Main entry points for compression */ +EXTERN void jpeg_start_compress JPP((j_compress_ptr cinfo, + boolean write_all_tables)); +EXTERN JDIMENSION jpeg_write_scanlines JPP((j_compress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION num_lines)); +EXTERN void jpeg_finish_compress JPP((j_compress_ptr cinfo)); + +/* Replaces jpeg_write_scanlines when writing raw downsampled data. */ +EXTERN JDIMENSION jpeg_write_raw_data JPP((j_compress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION num_lines)); + +/* Write a special marker. See libjpeg.doc concerning safe usage. */ +EXTERN void jpeg_write_marker JPP((j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen)); + +/* Alternate compression function: just write an abbreviated table file */ +EXTERN void jpeg_write_tables JPP((j_compress_ptr cinfo)); + +/* Decompression startup: read start of JPEG datastream to see what's there */ +EXTERN int jpeg_read_header JPP((j_decompress_ptr cinfo, + boolean require_image)); +/* Return value is one of: */ +#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */ +#define JPEG_HEADER_OK 1 /* Found valid image datastream */ +#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */ +/* If you pass require_image = TRUE (normal case), you need not check for + * a TABLES_ONLY return code; an abbreviated file will cause an error exit. + * JPEG_SUSPENDED is only possible if you use a data source module that can + * give a suspension return (the stdio source module doesn't). + */ + +/* Main entry points for decompression */ +EXTERN boolean jpeg_start_decompress JPP((j_decompress_ptr cinfo)); +EXTERN JDIMENSION jpeg_read_scanlines JPP((j_decompress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION max_lines)); +EXTERN boolean jpeg_finish_decompress JPP((j_decompress_ptr cinfo)); + +/* Replaces jpeg_read_scanlines when reading raw downsampled data. */ +EXTERN JDIMENSION jpeg_read_raw_data JPP((j_decompress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION max_lines)); + +/* Additional entry points for buffered-image mode. */ +EXTERN boolean jpeg_has_multiple_scans JPP((j_decompress_ptr cinfo)); +EXTERN boolean jpeg_start_output JPP((j_decompress_ptr cinfo, + int scan_number)); +EXTERN boolean jpeg_finish_output JPP((j_decompress_ptr cinfo)); +EXTERN boolean jpeg_input_complete JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_new_colormap JPP((j_decompress_ptr cinfo)); +EXTERN int jpeg_consume_input JPP((j_decompress_ptr cinfo)); +/* Return value is one of: */ +/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */ +#define JPEG_REACHED_SOS 1 /* Reached start of new scan */ +#define JPEG_REACHED_EOI 2 /* Reached end of image */ +#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */ +#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */ + +/* Precalculate output dimensions for current decompression parameters. */ +EXTERN void jpeg_calc_output_dimensions JPP((j_decompress_ptr cinfo)); + +/* Install a special processing method for COM or APPn markers. */ +EXTERN void jpeg_set_marker_processor JPP((j_decompress_ptr cinfo, + int marker_code, + jpeg_marker_parser_method routine)); + +/* Read or write raw DCT coefficients --- useful for lossless transcoding. */ +EXTERN jvirt_barray_ptr * jpeg_read_coefficients JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_write_coefficients JPP((j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays)); +EXTERN void jpeg_copy_critical_parameters JPP((j_decompress_ptr srcinfo, + j_compress_ptr dstinfo)); + +/* If you choose to abort compression or decompression before completing + * jpeg_finish_(de)compress, then you need to clean up to release memory, + * temporary files, etc. You can just call jpeg_destroy_(de)compress + * if you're done with the JPEG object, but if you want to clean it up and + * reuse it, call this: + */ +EXTERN void jpeg_abort_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_abort_decompress JPP((j_decompress_ptr cinfo)); + +/* Generic versions of jpeg_abort and jpeg_destroy that work on either + * flavor of JPEG object. These may be more convenient in some places. + */ +EXTERN void jpeg_abort JPP((j_common_ptr cinfo)); +EXTERN void jpeg_destroy JPP((j_common_ptr cinfo)); + +/* Default restart-marker-resync procedure for use by data source modules */ +EXTERN boolean jpeg_resync_to_restart JPP((j_decompress_ptr cinfo, + int desired)); + + +/* These marker codes are exported since applications and data source modules + * are likely to want to use them. + */ + +#define JPEG_RST0 0xD0 /* RST0 marker code */ +#define JPEG_EOI 0xD9 /* EOI marker code */ +#define JPEG_APP0 0xE0 /* APP0 marker code */ +#define JPEG_COM 0xFE /* COM marker code */ + + +/* If we have a brain-damaged compiler that emits warnings (or worse, errors) + * for structure definitions that are never filled in, keep it quiet by + * supplying dummy definitions for the various substructures. + */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +struct jpeg_comp_master { long dummy; }; +struct jpeg_c_main_controller { long dummy; }; +struct jpeg_c_prep_controller { long dummy; }; +struct jpeg_c_coef_controller { long dummy; }; +struct jpeg_marker_writer { long dummy; }; +struct jpeg_color_converter { long dummy; }; +struct jpeg_downsampler { long dummy; }; +struct jpeg_forward_dct { long dummy; }; +struct jpeg_entropy_encoder { long dummy; }; +struct jpeg_decomp_master { long dummy; }; +struct jpeg_d_main_controller { long dummy; }; +struct jpeg_d_coef_controller { long dummy; }; +struct jpeg_d_post_controller { long dummy; }; +struct jpeg_input_controller { long dummy; }; +struct jpeg_marker_reader { long dummy; }; +struct jpeg_entropy_decoder { long dummy; }; +struct jpeg_inverse_dct { long dummy; }; +struct jpeg_upsampler { long dummy; }; +struct jpeg_color_deconverter { long dummy; }; +struct jpeg_color_quantizer { long dummy; }; +#endif /* JPEG_INTERNALS */ +#endif /* INCOMPLETE_TYPES_BROKEN */ + + +/* + * The JPEG library modules define JPEG_INTERNALS before including this file. + * The internal structure declarations are read only when that is true. + * Applications using the library should not include jpegint.h, but may wish + * to include jerror.h. + */ + +#ifdef JPEG_INTERNALS +#include "jpegint.h" /* fetch private declarations */ +#include "jerror.h" /* fetch error codes too */ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* JPEGLIB_H */ diff --git a/libs/l_net/.cvsignore b/libs/l_net/.cvsignore new file mode 100644 index 00000000..877bc8c4 --- /dev/null +++ b/libs/l_net/.cvsignore @@ -0,0 +1,5 @@ +Debug +Release +*.plg +*.BAK +.consign diff --git a/libs/l_net/l_net.c b/libs/l_net/l_net.c new file mode 100644 index 00000000..558ec211 --- /dev/null +++ b/libs/l_net/l_net.c @@ -0,0 +1,626 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//==================================================================== +// +// Name: l_net.c +// Function: - +// Programmer: MrElusive +// Last update: - +// Tab size: 3 +// Notes: +//==================================================================== + +#include +#include +#include +#include +#include "l_net.h" +#include "l_net_wins.h" + +#define GetMemory malloc +#define FreeMemory free + +#define qtrue 1 +#define qfalse 0 + +#ifdef _DEBUG +void WinPrint(char *str, ...) +{ + va_list argptr; + char text[4096]; + + va_start (argptr,str); + vsprintf (text, str, argptr); + va_end (argptr); + + printf(text); +} +#else +void WinPrint(char *str, ...) +{ +} +#endif + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_SetAddressPort(address_t *address, int port) +{ + sockaddr_t addr; + + WINS_StringToAddr(address->ip, &addr); + WINS_SetSocketPort(&addr, port); + strcpy(address->ip, WINS_AddrToString(&addr)); +} //end of the function Net_SetAddressPort +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Net_AddressCompare(address_t *addr1, address_t *addr2) +{ +#ifdef WIN32 + return _stricmp(addr1->ip, addr2->ip); +#else + return strcasecmp(addr1->ip, addr2->ip); +#endif +} //end of the function Net_AddressCompare +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_SocketToAddress(socket_t *sock, address_t *address) +{ + strcpy(address->ip, WINS_AddrToString(&sock->addr)); +} //end of the function Net_SocketToAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Net_Send(socket_t *sock, netmessage_t *msg) +{ + int size; + + size = msg->size; + msg->size = 0; + NMSG_WriteLong(msg, size-4); + msg->size = size; + //WinPrint("Net_Send: message of size %d\n", sendmsg.size); + return WINS_Write(sock->socket, msg->data, msg->size, NULL); +} //end of the function Net_SendSocketReliable +//=========================================================================== +// returns the number of bytes recieved +// -1 on error +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Net_Receive(socket_t *sock, netmessage_t *msg) +{ + int curread; + + if (sock->remaining > 0) + { + curread = WINS_Read(sock->socket, &sock->msg.data[sock->msg.size], sock->remaining, NULL); + if (curread == -1) + { + WinPrint("Net_Receive: read error\n"); + return -1; + } //end if + sock->remaining -= curread; + sock->msg.size += curread; + if (sock->remaining <= 0) + { + sock->remaining = 0; + memcpy(msg, &sock->msg, sizeof(netmessage_t)); + sock->msg.size = 0; + return msg->size - 4; + } //end if + return 0; + } //end if + sock->msg.size = WINS_Read(sock->socket, sock->msg.data, 4, NULL); + if (sock->msg.size == 0) return 0; + if (sock->msg.size == -1) + { + WinPrint("Net_Receive: size header read error\n"); + return -1; + } //end if + //WinPrint("Net_Receive: message size header %d\n", msg->size); + sock->msg.read = 0; + sock->remaining = NMSG_ReadLong(&sock->msg); + if (sock->remaining == 0) return 0; + if (sock->remaining < 0 || sock->remaining > MAX_NETMESSAGE) + { + WinPrint("Net_Receive: invalid message size %d\n", sock->remaining); + return -1; + } //end if + //try to read the message + curread = WINS_Read(sock->socket, &sock->msg.data[sock->msg.size], sock->remaining, NULL); + if (curread == -1) + { + WinPrint("Net_Receive: read error\n"); + return -1; + } //end if + sock->remaining -= curread; + sock->msg.size += curread; + if (sock->remaining <= 0) + { + sock->remaining = 0; + memcpy(msg, &sock->msg, sizeof(netmessage_t)); + sock->msg.size = 0; + return msg->size - 4; + } //end if + //the message has not been completely read yet +#ifdef _DEBUG + printf("++timo TODO: debug the Net_Receive on big size messages\n"); +#endif + return 0; +} //end of the function Net_Receive +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +socket_t *Net_AllocSocket(void) +{ + socket_t *sock; + + sock = (socket_t *) GetMemory(sizeof(socket_t)); + memset(sock, 0, sizeof(socket_t)); + return sock; +} //end of the function Net_AllocSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_FreeSocket(socket_t *sock) +{ + FreeMemory(sock); +} //end of the function Net_FreeSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +socket_t *Net_Connect(address_t *address, int port) +{ + int newsock; + socket_t *sock; + sockaddr_t sendaddr; + + // see if we can resolve the host name + WINS_StringToAddr(address->ip, &sendaddr); + + newsock = WINS_OpenReliableSocket(port); + if (newsock == -1) return NULL; + + sock = Net_AllocSocket(); + if (sock == NULL) + { + WINS_CloseSocket(newsock); + return NULL; + } //end if + sock->socket = newsock; + + //connect to the host + if (WINS_Connect(newsock, &sendaddr) == -1) + { + Net_FreeSocket(sock); + WINS_CloseSocket(newsock); + WinPrint("Net_Connect: error connecting\n"); + return NULL; + } //end if + + memcpy(&sock->addr, &sendaddr, sizeof(sockaddr_t)); + //now we can send messages + // + return sock; +} //end of the function Net_Connect + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +socket_t *Net_ListenSocket(int port) +{ + int newsock; + socket_t *sock; + + newsock = WINS_OpenReliableSocket(port); + if (newsock == -1) return NULL; + + if (WINS_Listen(newsock) == -1) + { + WINS_CloseSocket(newsock); + return NULL; + } //end if + sock = Net_AllocSocket(); + if (sock == NULL) + { + WINS_CloseSocket(newsock); + return NULL; + } //end if + sock->socket = newsock; + WINS_GetSocketAddr(newsock, &sock->addr); + WinPrint("listen socket opened at %s\n", WINS_AddrToString(&sock->addr)); + // + return sock; +} //end of the function Net_ListenSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +socket_t *Net_Accept(socket_t *sock) +{ + int newsocket; + sockaddr_t sendaddr; + socket_t *newsock; + + newsocket = WINS_Accept(sock->socket, &sendaddr); + if (newsocket == -1) return NULL; + + newsock = Net_AllocSocket(); + if (newsock == NULL) + { + WINS_CloseSocket(newsocket); + return NULL; + } //end if + newsock->socket = newsocket; + memcpy(&newsock->addr, &sendaddr, sizeof(sockaddr_t)); + // + return newsock; +} //end of the function Net_Accept +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_Disconnect(socket_t *sock) +{ + WINS_CloseSocket(sock->socket); + Net_FreeSocket(sock); +} //end of the function Net_Disconnect +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_StringToAddress(char *string, address_t *address) +{ + strcpy(address->ip, string); +} //end of the function Net_StringToAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_MyAddress(address_t *address) +{ + strcpy(address->ip, WINS_MyAddress()); +} //end of the function Net_MyAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Net_Setup(void) +{ + WINS_Init(); + // + WinPrint("my address is %s\n", WINS_MyAddress()); + // + return qtrue; +} //end of the function Net_Setup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Net_Shutdown(void) +{ + WINS_Shutdown(); +} //end of the function Net_Shutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_Clear(netmessage_t *msg) +{ + msg->size = 4; +} //end of the function NMSG_Clear +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_WriteChar (netmessage_t *msg, int c) +{ + if (c < -128 || c > 127) + WinPrint("NMSG_WriteChar: range error\n"); + + if (msg->size >= MAX_NETMESSAGE) + { + WinPrint("NMSG_WriteChar: overflow\n"); + return; + } //end if + msg->data[msg->size] = c; + msg->size++; +} //end of the function NMSG_WriteChar +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_WriteByte(netmessage_t *msg, int c) +{ + if (c < -128 || c > 127) + WinPrint("NMSG_WriteByte: range error\n"); + + if (msg->size + 1 >= MAX_NETMESSAGE) + { + WinPrint("NMSG_WriteByte: overflow\n"); + return; + } //end if + msg->data[msg->size] = c; + msg->size++; +} //end of the function NMSG_WriteByte +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_WriteShort(netmessage_t *msg, int c) +{ + if (c < ((short)0x8000) || c > (short)0x7fff) + WinPrint("NMSG_WriteShort: range error"); + + if (msg->size + 2 >= MAX_NETMESSAGE) + { + WinPrint("NMSG_WriteShort: overflow\n"); + return; + } //end if + msg->data[msg->size] = c&0xff; + msg->data[msg->size+1] = c>>8; + msg->size += 2; +} //end of the function NMSG_WriteShort +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_WriteLong(netmessage_t *msg, int c) +{ + if (msg->size + 4 >= MAX_NETMESSAGE) + { + WinPrint("NMSG_WriteLong: overflow\n"); + return; + } //end if + msg->data[msg->size] = c&0xff; + msg->data[msg->size+1] = (c>>8)&0xff; + msg->data[msg->size+2] = (c>>16)&0xff; + msg->data[msg->size+3] = c>>24; + msg->size += 4; +} //end of the function NMSG_WriteLong +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_WriteFloat(netmessage_t *msg, float c) +{ + if (msg->size + 4 >= MAX_NETMESSAGE) + { + WinPrint("NMSG_WriteLong: overflow\n"); + return; + } //end if + msg->data[msg->size] = *((int *)&c)&0xff; + msg->data[msg->size+1] = (*((int *)&c)>>8)&0xff; + msg->data[msg->size+2] = (*((int *)&c)>>16)&0xff; + msg->data[msg->size+3] = *((int *)&c)>>24; + msg->size += 4; +} //end of the function NMSG_WriteFloat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_WriteString(netmessage_t *msg, char *string) +{ + if (msg->size + strlen(string) + 1 >= MAX_NETMESSAGE) + { + WinPrint("NMSG_WriteString: overflow\n"); + return; + } //end if + strcpy(&msg->data[msg->size], string); + msg->size += strlen(string) + 1; +} //end of the function NMSG_WriteString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void NMSG_ReadStart(netmessage_t *msg) +{ + msg->readoverflow = qfalse; + msg->read = 4; +} //end of the function NMSG_ReadStart +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int NMSG_ReadChar(netmessage_t *msg) +{ + if (msg->size + 1 > msg->size) + { + msg->readoverflow = qtrue; + WinPrint("NMSG_ReadChar: read overflow\n"); + return 0; + } //end if + msg->read++; + return msg->data[msg->read-1]; +} //end of the function NMSG_ReadChar +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int NMSG_ReadByte(netmessage_t *msg) +{ + if (msg->read + 1 > msg->size) + { + msg->readoverflow = qtrue; + WinPrint("NMSG_ReadByte: read overflow\n"); + return 0; + } //end if + msg->read++; + return msg->data[msg->read-1]; +} //end of the function NMSG_ReadByte +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int NMSG_ReadShort(netmessage_t *msg) +{ + int c; + + if (msg->read + 2 > msg->size) + { + msg->readoverflow = qtrue; + WinPrint("NMSG_ReadShort: read overflow\n"); + return 0; + } //end if + c = (short)(msg->data[msg->read] + (msg->data[msg->read+1]<<8)); + msg->read += 2; + return c; +} //end of the function NMSG_ReadShort +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int NMSG_ReadLong(netmessage_t *msg) +{ + int c; + + if (msg->read + 4 > msg->size) + { + msg->readoverflow = qtrue; + WinPrint("NMSG_ReadLong: read overflow\n"); + return 0; + } //end if + c = msg->data[msg->read] + + (msg->data[msg->read+1]<<8) + + (msg->data[msg->read+2]<<16) + + (msg->data[msg->read+3]<<24); + msg->read += 4; + return c; +} //end of the function NMSG_ReadLong +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float NMSG_ReadFloat(netmessage_t *msg) +{ + int c; + + if (msg->read + 4 > msg->size) + { + msg->readoverflow = qtrue; + WinPrint("NMSG_ReadLong: read overflow\n"); + return 0; + } //end if + c = msg->data[msg->read] + + (msg->data[msg->read+1]<<8) + + (msg->data[msg->read+2]<<16) + + (msg->data[msg->read+3]<<24); + msg->read += 4; + return *(float *)&c; +} //end of the function NMSG_ReadFloat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *NMSG_ReadString(netmessage_t *msg) +{ + static char string[2048]; + int l, c; + + l = 0; + do + { + if (msg->read + 1 > msg->size) + { + msg->readoverflow = qtrue; + WinPrint("NMSG_ReadString: read overflow\n"); + string[l] = 0; + return string; + } //end if + c = msg->data[msg->read]; + msg->read++; + if (c == 0) break; + string[l] = c; + l++; + } while (l < sizeof(string)-1); + string[l] = 0; + return string; +} //end of the function NMSG_ReadString diff --git a/libs/l_net/l_net.h b/libs/l_net/l_net.h new file mode 100644 index 00000000..00096013 --- /dev/null +++ b/libs/l_net/l_net.h @@ -0,0 +1,125 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//==================================================================== +// +// Name: l_net.h +// Function: - +// Programmer: MrElusive +// Last update: TTimo: cross-platform version, l_net library +// Tab size: 2 +// Notes: +//==================================================================== + +//++timo FIXME: the l_net code understands that as the max size for the netmessage_s structure +// we have defined unsigned char data[MAX_NETMESSAGE] in netmessage_s but actually it cannot be filled completely +// we need to introduce a new #define and adapt to data[MAX_NETBUFFER] +#define MAX_NETMESSAGE 1024 +#define MAX_NETADDRESS 32 + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __BYTEBOOL__ +#define __BYTEBOOL__ +typedef enum { qfalse, qtrue } qboolean; +typedef unsigned char byte; +#endif + +typedef struct address_s +{ + char ip[MAX_NETADDRESS]; +} address_t; + +typedef struct sockaddr_s +{ + short sa_family; + unsigned char sa_data[14]; +} sockaddr_t; + +typedef struct netmessage_s +{ + unsigned char data[MAX_NETMESSAGE]; + int size; + int read; + int readoverflow; +} netmessage_t; + +typedef struct socket_s +{ + int socket; //socket number + sockaddr_t addr; //socket address + netmessage_t msg; //current message being read + int remaining; //remaining bytes to read for the current message + struct socket_s *prev, *next; //prev and next socket in a list +} socket_t; + +//compare addresses +int Net_AddressCompare(address_t *addr1, address_t *addr2); +//gives the address of a socket +void Net_SocketToAddress(socket_t *sock, address_t *address); +//converts a string to an address +void Net_StringToAddress(char *string, address_t *address); +//set the address ip port +void Net_SetAddressPort(address_t *address, int port); +//send a message to the given socket +int Net_Send(socket_t *sock, netmessage_t *msg); +//recieve a message from the given socket +int Net_Receive(socket_t *sock, netmessage_t *msg); +//connect to a host +// NOTE: port is the localhost port, usually 0 +// ex: Net_Connect( "192.168.0.1:39000", 0 ) +socket_t *Net_Connect(address_t *address, int port); +//disconnect from a host +void Net_Disconnect(socket_t *sock); +//returns the local address +void Net_MyAddress(address_t *address); +//listen at the given port +socket_t *Net_ListenSocket(int port); +//accept new connections at the given socket +socket_t *Net_Accept(socket_t *sock); +//setup networking +int Net_Setup(void); +//shutdown networking +void Net_Shutdown(void); +//message handling +void NMSG_Clear(netmessage_t *msg); +void NMSG_WriteChar(netmessage_t *msg, int c); +void NMSG_WriteByte(netmessage_t *msg, int c); +void NMSG_WriteShort(netmessage_t *msg, int c); +void NMSG_WriteLong(netmessage_t *msg, int c); +void NMSG_WriteFloat(netmessage_t *msg, float c); +void NMSG_WriteString(netmessage_t *msg, char *string); +void NMSG_ReadStart(netmessage_t *msg); +int NMSG_ReadChar(netmessage_t *msg); +int NMSG_ReadByte(netmessage_t *msg); +int NMSG_ReadShort(netmessage_t *msg); +int NMSG_ReadLong(netmessage_t *msg); +float NMSG_ReadFloat(netmessage_t *msg); +char *NMSG_ReadString(netmessage_t *msg); + +//++timo FIXME: the WINS_ things are not necessary, they can be made portable arther easily +char *WINS_ErrorMessage(int error); + +#ifdef __cplusplus +} +#endif diff --git a/libs/l_net/l_net.vcproj b/libs/l_net/l_net.vcproj new file mode 100644 index 00000000..ed866197 --- /dev/null +++ b/libs/l_net/l_net.vcproj @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/l_net/l_net_berkley.c b/libs/l_net/l_net_berkley.c new file mode 100644 index 00000000..19d670b3 --- /dev/null +++ b/libs/l_net/l_net_berkley.c @@ -0,0 +1,766 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//=========================================================================== +// +// Name: l_net_wins.c +// Function: WinSock +// Programmer: MrElusive +// Last update: TTimo: cross-platform version, l_net library +// Tab Size: 2 +// Notes: +//=========================================================================== + +//#include +#include +#include +#include +#include "l_net.h" +#include "l_net_wins.h" + +#include +#include +#include +#include +#include +#include +#include +#define SOCKET_ERROR -1 +#define INVALID_SOCKET -1 + +#define WinError WinPrint + +#define qtrue 1 +#define qfalse 0 + +#define ioctlsocket ioctl +#define closesocket close + +int WSAGetLastError() +{ + return errno; +} + +/* +typedef struct tag_error_struct +{ + int errnum; + LPSTR errstr; +} ERROR_STRUCT; +*/ + +typedef struct tag_error_struct +{ + int errnum; + const char *errstr; +} ERROR_STRUCT; + +#define NET_NAMELEN 64 + +static char my_tcpip_address[NET_NAMELEN]; + +#define DEFAULTnet_hostport 26000 + +#define MAXHOSTNAMELEN 256 + +static int net_acceptsocket = -1; // socket for fielding new connections +static int net_controlsocket; +static int net_hostport; // udp port number for acceptsocket +static int net_broadcastsocket = 0; +//static qboolean ifbcastinit = qfalse; +//static struct sockaddr_s broadcastaddr; +static struct sockaddr_s broadcastaddr; + +static unsigned long myAddr; + +ERROR_STRUCT errlist[] = { + {EACCES,"EACCES - The address is protected, user is not root"}, + {EAGAIN,"EAGAIN - Operation on non-blocking socket that cannot return immediatly"}, + {EBADF, "EBADF - sockfd is not a valid descriptor"}, + {EFAULT, "EFAULT - The parameter is not in a writable part of the user address space"}, + {EINVAL,"EINVAL - The socket is already bound to an address"}, + {ENOBUFS,"ENOBUFS - not enough memory"}, + {ENOMEM, "ENOMEM - not enough memory"}, + {ENOTCONN, "ENOTCONN - not connected"}, + {ENOTSOCK,"ENOTSOCK - Argument is file descriptor not a socket"}, + {EOPNOTSUPP,"ENOTSUPP - The referenced socket is not of type SOCK_STREAM"}, + {EPERM, "EPERM - Firewall rules forbid connection"}, + {-1, NULL} +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WINS_ErrorMessage(int error) +{ + int search = 0; + + if (!error) return "No error occurred"; + + for (search = 0; errlist[search].errstr; search++) + { + if (error == errlist[search].errnum) + return (char *)errlist[search].errstr; + } //end for + + return "Unknown error"; +} //end of the function WINS_ErrorMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Init(void) +{ + int i; + struct hostent *local; + char buff[MAXHOSTNAMELEN]; + struct sockaddr_s addr; + char *p; + int r; +/* + linux doesn't have anything to initialize for the net + "Windows .. built for the internet .. the internet .. built with unix" + */ +#if 0 + WORD wVersionRequested; + + wVersionRequested = MAKEWORD(2, 2); + + r = WSAStartup (wVersionRequested, &winsockdata); + + if (r) + { + WinPrint("Winsock initialization failed.\n"); + return -1; + } +#endif + /* + i = COM_CheckParm ("-udpport"); + if (i == 0)*/ + net_hostport = DEFAULTnet_hostport; + /* + else if (i < com_argc-1) + net_hostport = Q_atoi (com_argv[i+1]); + else + Sys_Error ("WINS_Init: you must specify a number after -udpport"); + */ + + // determine my name & address + gethostname(buff, MAXHOSTNAMELEN); + local = gethostbyname(buff); + myAddr = *(int *)local->h_addr_list[0]; + + // if the quake hostname isn't set, set it to the machine name +// if (Q_strcmp(hostname.string, "UNNAMED") == 0) + { + // see if it's a text IP address (well, close enough) + for (p = buff; *p; p++) + if ((*p < '0' || *p > '9') && *p != '.') + break; + + // if it is a real name, strip off the domain; we only want the host + if (*p) + { + for (i = 0; i < 15; i++) + if (buff[i] == '.') + break; + buff[i] = 0; + } +// Cvar_Set ("hostname", buff); + } + + //++timo WTF is that net_controlsocket? it's sole purpose is to retrieve the local IP? + if ((net_controlsocket = WINS_OpenSocket (0)) == SOCKET_ERROR) + WinError("WINS_Init: Unable to open control socket\n"); + + ((struct sockaddr_in *)&broadcastaddr)->sin_family = AF_INET; + ((struct sockaddr_in *)&broadcastaddr)->sin_addr.s_addr = INADDR_BROADCAST; + ((struct sockaddr_in *)&broadcastaddr)->sin_port = htons((u_short)net_hostport); + + WINS_GetSocketAddr (net_controlsocket, &addr); + strcpy(my_tcpip_address, WINS_AddrToString (&addr)); + p = strrchr (my_tcpip_address, ':'); + if (p) *p = 0; + WinPrint("Winsock Initialized\n"); + + return net_controlsocket; +} //end of the function WINS_Init +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WINS_MyAddress(void) +{ + return my_tcpip_address; +} //end of the function WINS_MyAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WINS_Shutdown(void) +{ + //WINS_Listen(0); + WINS_CloseSocket(net_controlsocket); +// WSACleanup(); + // + //WinPrint("Winsock Shutdown\n"); +} //end of the function WINS_Shutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void WINS_Listen(int state) +{ + // enable listening + if (state) + { + if (net_acceptsocket != -1) + return; + if ((net_acceptsocket = WINS_OpenSocket (net_hostport)) == -1) + WinError ("WINS_Listen: Unable to open accept socket\n"); + return; + } + + // disable listening + if (net_acceptsocket == -1) + return; + WINS_CloseSocket (net_acceptsocket); + net_acceptsocket = -1; +} //end of the function WINS_Listen*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_OpenSocket(int port) +{ + int newsocket; + struct sockaddr_in address; + u_long _true = 1; + + if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == SOCKET_ERROR) + { + WinPrint("WINS_OpenSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + + if (ioctlsocket (newsocket, FIONBIO, &_true) == SOCKET_ERROR) + { + WinPrint("WINS_OpenSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + closesocket(newsocket); + return -1; + } //end if + + memset((char *) &address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons((u_short)port); + if( bind (newsocket, (void *)&address, sizeof(address)) == -1) + { + WinPrint("WINS_OpenSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + closesocket(newsocket); + return -1; + } //end if + + return newsocket; +} //end of the function WINS_OpenSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_OpenReliableSocket(int port) +{ + int newsocket; + struct sockaddr_in address; + qboolean _true = 0xFFFFFFFF; + + //IPPROTO_TCP + // + if ((newsocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + WinPrint("WINS_OpenReliableSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + + memset((char *) &address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_ANY); + address.sin_port = htons((u_short)port); + if (bind(newsocket, (void *)&address, sizeof(address)) == -1) + { + WinPrint("WINS_OpenReliableSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + closesocket(newsocket); + return -1; + } //end if + + // + if (setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (void *) &_true, sizeof(int)) == -1) + { + WinPrint("WINS_OpenReliableSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + WinPrint("setsockopt error\n"); + } //end if + + return newsocket; +} //end of the function WINS_OpenReliableSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Listen(int socket) +{ + u_long _true = 1; + + if (ioctlsocket(socket, FIONBIO, &_true) == -1) + { + WinPrint("WINS_Listen: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + if (listen(socket, SOMAXCONN) == SOCKET_ERROR) + { + WinPrint("WINS_Listen: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + return 0; +} //end of the function WINS_Listen +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Accept(int socket, struct sockaddr_s *addr) +{ + int addrlen = sizeof (struct sockaddr_s); + int newsocket; + qboolean _true = 1; + + newsocket = accept(socket, (struct sockaddr *)addr, &addrlen); + if (newsocket == INVALID_SOCKET) + { + if (errno == EAGAIN) return -1; + WinPrint("WINS_Accept: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + // + if (setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (void *) &_true, sizeof(int)) == SOCKET_ERROR) + { + WinPrint("WINS_Accept: %s\n", WINS_ErrorMessage(WSAGetLastError())); + WinPrint("setsockopt error\n"); + } //end if + return newsocket; +} //end of the function WINS_Accept +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_CloseSocket(int socket) +{ + /* + if (socket == net_broadcastsocket) + net_broadcastsocket = 0; + */ +// shutdown(socket, SD_SEND); + + if (closesocket(socket) == SOCKET_ERROR) + { + WinPrint("WINS_CloseSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return SOCKET_ERROR; + } //end if + return 0; +} //end of the function WINS_CloseSocket +//=========================================================================== +// this lets you type only as much of the net address as required, using +// the local network components to fill in the rest +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +static int PartialIPAddress (char *in, struct sockaddr_s *hostaddr) +{ + char buff[256]; + char *b; + int addr; + int num; + int mask; + + buff[0] = '.'; + b = buff; + strcpy(buff+1, in); + if (buff[1] == '.') b++; + + addr = 0; + mask=-1; + while (*b == '.') + { + num = 0; + if (*++b < '0' || *b > '9') return -1; + while (!( *b < '0' || *b > '9')) + num = num*10 + *(b++) - '0'; + mask<<=8; + addr = (addr<<8) + num; + } + + hostaddr->sa_family = AF_INET; + ((struct sockaddr_in *)hostaddr)->sin_port = htons((u_short)net_hostport); + ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr = (myAddr & htonl(mask)) | htonl(addr); + + return 0; +} //end of the function PartialIPAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Connect(int socket, struct sockaddr_s *addr) +{ + int ret; + u_long _true2 = 0xFFFFFFFF; + + ret = connect(socket, (struct sockaddr *)addr, sizeof(struct sockaddr_s)); + if (ret == SOCKET_ERROR) + { + WinPrint("WINS_Connect: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + if (ioctlsocket(socket, FIONBIO, &_true2) == -1) + { + WinPrint("WINS_Connect: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + return 0; +} //end of the function WINS_Connect +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_CheckNewConnections(void) +{ + char buf[4]; + + if (net_acceptsocket == -1) + return -1; + + if (recvfrom(net_acceptsocket, buf, 4, MSG_PEEK, NULL, NULL) > 0) + return net_acceptsocket; + return -1; +} //end of the function WINS_CheckNewConnections +//=========================================================================== +// returns the number of bytes read +// 0 if no bytes available +// -1 on failure +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Read(int socket, byte *buf, int len, struct sockaddr_s *addr) +{ + int addrlen = sizeof (struct sockaddr_s); + int ret; + + if (addr) + { + ret = recvfrom(socket, buf, len, 0, (struct sockaddr *)addr, &addrlen); + if (ret == -1) + { +// errno = WSAGetLastError(); + + if (errno == EAGAIN || errno == ENOTCONN) + return 0; + } //end if + } //end if + else + { + ret = recv(socket, buf, len, 0); + // if there's no data on the socket ret == -1 and errno == EAGAIN + // MSDN states that if ret == 0 the socket has been closed + // man recv doesn't say anything + if (ret == 0) + return -1; + if (ret == SOCKET_ERROR) + { +// errno = WSAGetLastError(); + + if (errno == EAGAIN || errno == ENOTCONN) + return 0; + } //end if + } //end else + if (ret == SOCKET_ERROR) + { + WinPrint("WINS_Read: %s\n", WINS_ErrorMessage(WSAGetLastError())); + } //end if + return ret; +} //end of the function WINS_Read +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_MakeSocketBroadcastCapable (int socket) +{ + int i = 1; + + // make this socket broadcast capable + if (setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) < 0) + return -1; + net_broadcastsocket = socket; + + return 0; +} //end of the function WINS_MakeSocketBroadcastCapable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Broadcast (int socket, byte *buf, int len) +{ + int ret; + + if (socket != net_broadcastsocket) + { + if (net_broadcastsocket != 0) + WinError("Attempted to use multiple broadcasts sockets\n"); + ret = WINS_MakeSocketBroadcastCapable (socket); + if (ret == -1) + { + WinPrint("Unable to make socket broadcast capable\n"); + return ret; + } + } + + return WINS_Write (socket, buf, len, &broadcastaddr); +} //end of the function WINS_Broadcast +//=========================================================================== +// returns qtrue on success or qfalse on failure +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Write(int socket, byte *buf, int len, struct sockaddr_s *addr) +{ + int ret, written; + + if (addr) + { + written = 0; + while(written < len) + { + ret = sendto (socket, &buf[written], len-written, 0, (struct sockaddr *)addr, sizeof(struct sockaddr_s)); + if (ret == SOCKET_ERROR) + { + if (WSAGetLastError() != EAGAIN) + return qfalse; + //++timo FIXME: what is this used for? +// Sleep(1000); + } //end if + else + { + written += ret; + } + } + } //end if + else + { + written = 0; + while(written < len) + { + ret = send(socket, buf, len, 0); + if (ret == SOCKET_ERROR) + { + if (WSAGetLastError() != EAGAIN) + return qfalse; + //++timo FIXME: what is this used for? +// Sleep(1000); + } //end if + else + { + written += ret; + } + } + } //end else + if (ret == SOCKET_ERROR) + { + WinPrint("WINS_Write: %s\n", WINS_ErrorMessage(WSAGetLastError())); + } //end if + return (ret == len); +} //end of the function WINS_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WINS_AddrToString (struct sockaddr_s *addr) +{ + static char buffer[22]; + int haddr; + + haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); + sprintf(buffer, "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, ntohs(((struct sockaddr_in *)addr)->sin_port)); + return buffer; +} //end of the function WINS_AddrToString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_StringToAddr(char *string, struct sockaddr_s *addr) +{ + int ha1, ha2, ha3, ha4, hp; + int ipaddr; + + sscanf(string, "%d.%d.%d.%d:%d", &ha1, &ha2, &ha3, &ha4, &hp); + ipaddr = (ha1 << 24) | (ha2 << 16) | (ha3 << 8) | ha4; + + addr->sa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_addr.s_addr = htonl(ipaddr); + ((struct sockaddr_in *)addr)->sin_port = htons((u_short)hp); + return 0; +} //end of the function WINS_StringToAddr +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetSocketAddr(int socket, struct sockaddr_s *addr) +{ + int addrlen = sizeof(struct sockaddr_s); + unsigned int a; + + memset(addr, 0, sizeof(struct sockaddr_s)); + getsockname(socket, (struct sockaddr *)addr, &addrlen); + a = ((struct sockaddr_in *)addr)->sin_addr.s_addr; + if (a == 0 || a == inet_addr("127.0.0.1")) + ((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddr; + + return 0; +} //end of the function WINS_GetSocketAddr +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetNameFromAddr (struct sockaddr_s *addr, char *name) +{ + struct hostent *hostentry; + + hostentry = gethostbyaddr ((char *)&((struct sockaddr_in *)addr)->sin_addr, sizeof(struct in_addr), AF_INET); + if (hostentry) + { + strncpy (name, (char *)hostentry->h_name, NET_NAMELEN - 1); + return 0; + } + + strcpy (name, WINS_AddrToString (addr)); + return 0; +} //end of the function WINS_GetNameFromAddr +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetAddrFromName(char *name, struct sockaddr_s *addr) +{ + struct hostent *hostentry; + + if (name[0] >= '0' && name[0] <= '9') + return PartialIPAddress (name, addr); + + hostentry = gethostbyname (name); + if (!hostentry) + return -1; + + addr->sa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_port = htons((u_short)net_hostport); + ((struct sockaddr_in *)addr)->sin_addr.s_addr = *(int *)hostentry->h_addr_list[0]; + + return 0; +} //end of the function WINS_GetAddrFromName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_AddrCompare (struct sockaddr_s *addr1, struct sockaddr_s *addr2) +{ + if (addr1->sa_family != addr2->sa_family) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != ((struct sockaddr_in *)addr2)->sin_addr.s_addr) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_port != ((struct sockaddr_in *)addr2)->sin_port) + return 1; + + return 0; +} //end of the function WINS_AddrCompare +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetSocketPort (struct sockaddr_s *addr) +{ + return ntohs(((struct sockaddr_in *)addr)->sin_port); +} //end of the function WINS_GetSocketPort +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_SetSocketPort (struct sockaddr_s *addr, int port) +{ + ((struct sockaddr_in *)addr)->sin_port = htons((u_short)port); + return 0; +} //end of the function WINS_SetSocketPort diff --git a/libs/l_net/l_net_wins.c b/libs/l_net/l_net_wins.c new file mode 100644 index 00000000..faf7d88c --- /dev/null +++ b/libs/l_net/l_net_wins.c @@ -0,0 +1,789 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//=========================================================================== +// +// Name: l_net_wins.c +// Function: WinSock +// Programmer: MrElusive +// Last update: - +// Tab Size: 3 +// Notes: +//=========================================================================== + +#include +#include +#include +#include +#include "l_net.h" +#include "l_net_wins.h" +//#include +//#include "mpdosock.h" + +#define WinError WinPrint + +#define qtrue 1 +#define qfalse 0 + +typedef struct tag_error_struct +{ + int errnum; + LPSTR errstr; +} ERROR_STRUCT; + +#define NET_NAMELEN 64 + +char my_tcpip_address[NET_NAMELEN]; + +#define DEFAULTnet_hostport 26000 + +#define MAXHOSTNAMELEN 256 + +static int net_acceptsocket = -1; // socket for fielding new connections +static int net_controlsocket; +static int net_hostport; // udp port number for acceptsocket +static int net_broadcastsocket = 0; +//static qboolean ifbcastinit = qfalse; +static struct sockaddr_s broadcastaddr; + +static unsigned long myAddr; + +WSADATA winsockdata; + +ERROR_STRUCT errlist[] = { + {WSAEINTR, "WSAEINTR - Interrupted"}, + {WSAEBADF, "WSAEBADF - Bad file number"}, + {WSAEFAULT, "WSAEFAULT - Bad address"}, + {WSAEINVAL, "WSAEINVAL - Invalid argument"}, + {WSAEMFILE, "WSAEMFILE - Too many open files"}, + +/* +* Windows Sockets definitions of regular Berkeley error constants +*/ + + {WSAEWOULDBLOCK, "WSAEWOULDBLOCK - Socket marked as non-blocking"}, + {WSAEINPROGRESS, "WSAEINPROGRESS - Blocking call in progress"}, + {WSAEALREADY, "WSAEALREADY - Command already completed"}, + {WSAENOTSOCK, "WSAENOTSOCK - Descriptor is not a socket"}, + {WSAEDESTADDRREQ, "WSAEDESTADDRREQ - Destination address required"}, + {WSAEMSGSIZE, "WSAEMSGSIZE - Data size too large"}, + {WSAEPROTOTYPE, "WSAEPROTOTYPE - Protocol is of wrong type for this socket"}, + {WSAENOPROTOOPT, "WSAENOPROTOOPT - Protocol option not supported for this socket type"}, + {WSAEPROTONOSUPPORT, "WSAEPROTONOSUPPORT - Protocol is not supported"}, + {WSAESOCKTNOSUPPORT, "WSAESOCKTNOSUPPORT - Socket type not supported by this address family"}, + {WSAEOPNOTSUPP, "WSAEOPNOTSUPP - Option not supported"}, + {WSAEPFNOSUPPORT, "WSAEPFNOSUPPORT - "}, + {WSAEAFNOSUPPORT, "WSAEAFNOSUPPORT - Address family not supported by this protocol"}, + {WSAEADDRINUSE, "WSAEADDRINUSE - Address is in use"}, + {WSAEADDRNOTAVAIL, "WSAEADDRNOTAVAIL - Address not available from local machine"}, + {WSAENETDOWN, "WSAENETDOWN - Network subsystem is down"}, + {WSAENETUNREACH, "WSAENETUNREACH - Network cannot be reached"}, + {WSAENETRESET, "WSAENETRESET - Connection has been dropped"}, + {WSAECONNABORTED, "WSAECONNABORTED - Connection aborted"}, + {WSAECONNRESET, "WSAECONNRESET - Connection reset"}, + {WSAENOBUFS, "WSAENOBUFS - No buffer space available"}, + {WSAEISCONN, "WSAEISCONN - Socket is already connected"}, + {WSAENOTCONN, "WSAENOTCONN - Socket is not connected"}, + {WSAESHUTDOWN, "WSAESHUTDOWN - Socket has been shut down"}, + {WSAETOOMANYREFS, "WSAETOOMANYREFS - Too many references"}, + {WSAETIMEDOUT, "WSAETIMEDOUT - Command timed out"}, + {WSAECONNREFUSED, "WSAECONNREFUSED - Connection refused"}, + {WSAELOOP, "WSAELOOP - "}, + {WSAENAMETOOLONG, "WSAENAMETOOLONG - "}, + {WSAEHOSTDOWN, "WSAEHOSTDOWN - Host is down"}, + {WSAEHOSTUNREACH, "WSAEHOSTUNREACH - "}, + {WSAENOTEMPTY, "WSAENOTEMPTY - "}, + {WSAEPROCLIM, "WSAEPROCLIM - "}, + {WSAEUSERS, "WSAEUSERS - "}, + {WSAEDQUOT, "WSAEDQUOT - "}, + {WSAESTALE, "WSAESTALE - "}, + {WSAEREMOTE, "WSAEREMOTE - "}, + +/* +* Extended Windows Sockets error constant definitions +*/ + + {WSASYSNOTREADY, "WSASYSNOTREADY - Network subsystem not ready"}, + {WSAVERNOTSUPPORTED, "WSAVERNOTSUPPORTED - Version not supported"}, + {WSANOTINITIALISED, "WSANOTINITIALISED - WSAStartup() has not been successfully called"}, + +/* +* Other error constants. +*/ + + {WSAHOST_NOT_FOUND, "WSAHOST_NOT_FOUND - Host not found"}, + {WSATRY_AGAIN, "WSATRY_AGAIN - Host not found or SERVERFAIL"}, + {WSANO_RECOVERY, "WSANO_RECOVERY - Non-recoverable error"}, + {WSANO_DATA, "WSANO_DATA - (or WSANO_ADDRESS) - No data record of requested type"}, + {-1, NULL} +}; + +#ifdef _DEBUG +void WinPrint(char *str, ...); +#else +void WinPrint(char *str, ...); +#endif + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WINS_ErrorMessage(int error) +{ + int search = 0; + + if (!error) return "No error occurred"; + + for (search = 0; errlist[search].errstr; search++) + { + if (error == errlist[search].errnum) + return errlist[search].errstr; + } //end for + + return "Unknown error"; +} //end of the function WINS_ErrorMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Init(void) +{ + int i; + struct hostent *local; + char buff[MAXHOSTNAMELEN]; + struct sockaddr_s addr; + char *p; + int r; + WORD wVersionRequested; + + wVersionRequested = MAKEWORD(1, 1); + + r = WSAStartup (wVersionRequested, &winsockdata); + + if (r) + { + WinPrint("Winsock initialization failed.\n"); + return -1; + } + + /* + i = COM_CheckParm ("-udpport"); + if (i == 0)*/ + net_hostport = DEFAULTnet_hostport; + /* + else if (i < com_argc-1) + net_hostport = Q_atoi (com_argv[i+1]); + else + Sys_Error ("WINS_Init: you must specify a number after -udpport"); + */ + + // determine my name & address + gethostname(buff, MAXHOSTNAMELEN); + local = gethostbyname(buff); + myAddr = *(int *)local->h_addr_list[0]; + + // if the quake hostname isn't set, set it to the machine name +// if (Q_strcmp(hostname.string, "UNNAMED") == 0) + { + // see if it's a text IP address (well, close enough) + for (p = buff; *p; p++) + if ((*p < '0' || *p > '9') && *p != '.') + break; + + // if it is a real name, strip off the domain; we only want the host + if (*p) + { + for (i = 0; i < 15; i++) + if (buff[i] == '.') + break; + buff[i] = 0; + } +// Cvar_Set ("hostname", buff); + } + + if ((net_controlsocket = WINS_OpenSocket (0)) == -1) + WinError("WINS_Init: Unable to open control socket\n"); + + ((struct sockaddr_in *)&broadcastaddr)->sin_family = AF_INET; + ((struct sockaddr_in *)&broadcastaddr)->sin_addr.s_addr = INADDR_BROADCAST; + ((struct sockaddr_in *)&broadcastaddr)->sin_port = htons((u_short)net_hostport); + + WINS_GetSocketAddr (net_controlsocket, &addr); + strcpy(my_tcpip_address, WINS_AddrToString (&addr)); + p = strrchr (my_tcpip_address, ':'); + if (p) *p = 0; + WinPrint("Winsock Initialized\n"); + + return net_controlsocket; +} //end of the function WINS_Init +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WINS_MyAddress(void) +{ + return my_tcpip_address; +} //end of the function WINS_MyAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void WINS_Shutdown(void) +{ + //WINS_Listen(0); + WINS_CloseSocket(net_controlsocket); + WSACleanup(); + // + //WinPrint("Winsock Shutdown\n"); +} //end of the function WINS_Shutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +void WINS_Listen(int state) +{ + // enable listening + if (state) + { + if (net_acceptsocket != -1) + return; + if ((net_acceptsocket = WINS_OpenSocket (net_hostport)) == -1) + WinError ("WINS_Listen: Unable to open accept socket\n"); + return; + } + + // disable listening + if (net_acceptsocket == -1) + return; + WINS_CloseSocket (net_acceptsocket); + net_acceptsocket = -1; +} //end of the function WINS_Listen*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_OpenSocket(int port) +{ + int newsocket; + struct sockaddr_in address; + u_long _true = 1; + + if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) + { + WinPrint("WINS_OpenSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + + if (ioctlsocket (newsocket, FIONBIO, &_true) == -1) + { + WinPrint("WINS_OpenSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + closesocket(newsocket); + return -1; + } //end if + + memset((char *) &address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons((u_short)port); + if( bind (newsocket, (void *)&address, sizeof(address)) == -1) + { + WinPrint("WINS_OpenSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + closesocket(newsocket); + return -1; + } //end if + + return newsocket; +} //end of the function WINS_OpenSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_OpenReliableSocket(int port) +{ + int newsocket; + struct sockaddr_in address; + BOOL _true = 0xFFFFFFFF; + + //IPPROTO_TCP + // + if ((newsocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + WinPrint("WINS_OpenReliableSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + + memset((char *) &address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_ANY); + address.sin_port = htons((u_short)port); + if (bind(newsocket, (void *)&address, sizeof(address)) == -1) + { + WinPrint("WINS_OpenReliableSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + closesocket(newsocket); + return -1; + } //end if + + // + if (setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (void *) &_true, sizeof(int)) == SOCKET_ERROR) + { + WinPrint("WINS_OpenReliableSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + WinPrint("setsockopt error\n"); + } //end if + + return newsocket; +} //end of the function WINS_OpenReliableSocket +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Listen(int socket) +{ + u_long _true = 1; + + if (ioctlsocket(socket, FIONBIO, &_true) == -1) + { + WinPrint("WINS_Listen: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + if (listen(socket, SOMAXCONN) == SOCKET_ERROR) + { + WinPrint("WINS_Listen: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + return 0; +} //end of the function WINS_Listen +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Accept(int socket, struct sockaddr_s *addr) +{ + int addrlen = sizeof (struct sockaddr_s); + int newsocket; + BOOL _true = 1; + + newsocket = accept(socket, (struct sockaddr *)addr, &addrlen); + if (newsocket == INVALID_SOCKET) + { + if (WSAGetLastError() == WSAEWOULDBLOCK) return -1; + WinPrint("WINS_Accept: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + // + if (setsockopt(newsocket, IPPROTO_TCP, TCP_NODELAY, (void *) &_true, sizeof(int)) == SOCKET_ERROR) + { + WinPrint("WINS_Accept: %s\n", WINS_ErrorMessage(WSAGetLastError())); + WinPrint("setsockopt error\n"); + } //end if + return newsocket; +} //end of the function WINS_Accept +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_CloseSocket(int socket) +{ + /* + if (socket == net_broadcastsocket) + net_broadcastsocket = 0; + */ +// shutdown(socket, SD_SEND); + + if (closesocket(socket) == SOCKET_ERROR) + { + WinPrint("WINS_CloseSocket: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return SOCKET_ERROR; + } //end if + return 0; +} //end of the function WINS_CloseSocket +//=========================================================================== +// this lets you type only as much of the net address as required, using +// the local network components to fill in the rest +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +static int PartialIPAddress (char *in, struct sockaddr_s *hostaddr) +{ + char buff[256]; + char *b; + int addr; + int num; + int mask; + + buff[0] = '.'; + b = buff; + strcpy(buff+1, in); + if (buff[1] == '.') b++; + + addr = 0; + mask=-1; + while (*b == '.') + { + num = 0; + if (*++b < '0' || *b > '9') return -1; + while (!( *b < '0' || *b > '9')) + num = num*10 + *(b++) - '0'; + mask<<=8; + addr = (addr<<8) + num; + } + + hostaddr->sa_family = AF_INET; + ((struct sockaddr_in *)hostaddr)->sin_port = htons((u_short)net_hostport); + ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr = (myAddr & htonl(mask)) | htonl(addr); + + return 0; +} //end of the function PartialIPAddress +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Connect(int socket, struct sockaddr_s *addr) +{ + int ret; + u_long _true2 = 0xFFFFFFFF; + + ret = connect(socket, (struct sockaddr *)addr, sizeof(struct sockaddr_s)); + if (ret == SOCKET_ERROR) + { + WinPrint("WINS_Connect: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + if (ioctlsocket(socket, FIONBIO, &_true2) == -1) + { + WinPrint("WINS_Connect: %s\n", WINS_ErrorMessage(WSAGetLastError())); + return -1; + } //end if + return 0; +} //end of the function WINS_Connect +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_CheckNewConnections(void) +{ + char buf[4]; + + if (net_acceptsocket == -1) + return -1; + + if (recvfrom(net_acceptsocket, buf, 4, MSG_PEEK, NULL, NULL) > 0) + return net_acceptsocket; + return -1; +} //end of the function WINS_CheckNewConnections +//=========================================================================== +// returns the number of bytes read +// 0 if no bytes available +// -1 on failure +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Read(int socket, byte *buf, int len, struct sockaddr_s *addr) +{ + int addrlen = sizeof (struct sockaddr_s); + int ret, errno; + + if (addr) + { + ret = recvfrom(socket, buf, len, 0, (struct sockaddr *)addr, &addrlen); + if (ret == -1) + { + errno = WSAGetLastError(); + + if (errno == WSAEWOULDBLOCK || errno == WSAECONNREFUSED) + return 0; + } //end if + } //end if + else + { + ret = recv(socket, buf, len, 0); + if (ret == SOCKET_ERROR) + { + errno = WSAGetLastError(); + + if (errno == WSAEWOULDBLOCK || errno == WSAECONNREFUSED) + return 0; + } //end if + } //end else + if (ret == SOCKET_ERROR) + { + WinPrint("WINS_Read: %s\n", WINS_ErrorMessage(WSAGetLastError())); + } //end if + return ret; +} //end of the function WINS_Read +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_MakeSocketBroadcastCapable (int socket) +{ + int i = 1; + + // make this socket broadcast capable + if (setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) < 0) + return -1; + net_broadcastsocket = socket; + + return 0; +} //end of the function WINS_MakeSocketBroadcastCapable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Broadcast (int socket, byte *buf, int len) +{ + int ret; + + if (socket != net_broadcastsocket) + { + if (net_broadcastsocket != 0) + WinError("Attempted to use multiple broadcasts sockets\n"); + ret = WINS_MakeSocketBroadcastCapable (socket); + if (ret == -1) + { + WinPrint("Unable to make socket broadcast capable\n"); + return ret; + } + } + + return WINS_Write (socket, buf, len, &broadcastaddr); +} //end of the function WINS_Broadcast +//=========================================================================== +// returns qtrue on success or qfalse on failure +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_Write(int socket, byte *buf, int len, struct sockaddr_s *addr) +{ + int ret, written; + + if (addr) + { + written = 0; + while(written < len) + { + ret = sendto (socket, &buf[written], len-written, 0, (struct sockaddr *)addr, sizeof(struct sockaddr_s)); + if (ret == SOCKET_ERROR) + { + if (WSAGetLastError() != WSAEWOULDBLOCK) + return qfalse; + Sleep(1000); + } //end if + else + { + written += ret; + } + } + } //end if + else + { + written = 0; + while(written < len) + { + ret = send(socket, buf, len, 0); + if (ret == SOCKET_ERROR) + { + if (WSAGetLastError() != WSAEWOULDBLOCK) + return qfalse; + Sleep(1000); + } //end if + else + { + written += ret; + } + } + } //end else + if (ret == SOCKET_ERROR) + { + WinPrint("WINS_Write: %s\n", WINS_ErrorMessage(WSAGetLastError())); + } //end if + return (ret == len); +} //end of the function WINS_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *WINS_AddrToString (struct sockaddr_s *addr) +{ + static char buffer[22]; + int haddr; + + haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); + sprintf(buffer, "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, ntohs(((struct sockaddr_in *)addr)->sin_port)); + return buffer; +} //end of the function WINS_AddrToString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_StringToAddr(char *string, struct sockaddr_s *addr) +{ + int ha1, ha2, ha3, ha4, hp; + int ipaddr; + + sscanf(string, "%d.%d.%d.%d:%d", &ha1, &ha2, &ha3, &ha4, &hp); + ipaddr = (ha1 << 24) | (ha2 << 16) | (ha3 << 8) | ha4; + + addr->sa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_addr.s_addr = htonl(ipaddr); + ((struct sockaddr_in *)addr)->sin_port = htons((u_short)hp); + return 0; +} //end of the function WINS_StringToAddr +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetSocketAddr(int socket, struct sockaddr_s *addr) +{ + int addrlen = sizeof(struct sockaddr_s); + unsigned int a; + + memset(addr, 0, sizeof(struct sockaddr_s)); + getsockname(socket, (struct sockaddr *)addr, &addrlen); + a = ((struct sockaddr_in *)addr)->sin_addr.s_addr; + if (a == 0 || a == inet_addr("127.0.0.1")) + ((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddr; + + return 0; +} //end of the function WINS_GetSocketAddr +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetNameFromAddr (struct sockaddr_s *addr, char *name) +{ + struct hostent *hostentry; + + hostentry = gethostbyaddr ((char *)&((struct sockaddr_in *)addr)->sin_addr, sizeof(struct in_addr), AF_INET); + if (hostentry) + { + strncpy (name, (char *)hostentry->h_name, NET_NAMELEN - 1); + return 0; + } + + strcpy (name, WINS_AddrToString (addr)); + return 0; +} //end of the function WINS_GetNameFromAddr +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetAddrFromName(char *name, struct sockaddr_s *addr) +{ + struct hostent *hostentry; + + if (name[0] >= '0' && name[0] <= '9') + return PartialIPAddress (name, addr); + + hostentry = gethostbyname (name); + if (!hostentry) + return -1; + + addr->sa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_port = htons((u_short)net_hostport); + ((struct sockaddr_in *)addr)->sin_addr.s_addr = *(int *)hostentry->h_addr_list[0]; + + return 0; +} //end of the function WINS_GetAddrFromName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_AddrCompare (struct sockaddr_s *addr1, struct sockaddr_s *addr2) +{ + if (addr1->sa_family != addr2->sa_family) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != ((struct sockaddr_in *)addr2)->sin_addr.s_addr) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_port != ((struct sockaddr_in *)addr2)->sin_port) + return 1; + + return 0; +} //end of the function WINS_AddrCompare +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_GetSocketPort (struct sockaddr_s *addr) +{ + return ntohs(((struct sockaddr_in *)addr)->sin_port); +} //end of the function WINS_GetSocketPort +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WINS_SetSocketPort (struct sockaddr_s *addr, int port) +{ + ((struct sockaddr_in *)addr)->sin_port = htons((u_short)port); + return 0; +} //end of the function WINS_SetSocketPort diff --git a/libs/l_net/l_net_wins.h b/libs/l_net/l_net_wins.h new file mode 100644 index 00000000..ad1b41fb --- /dev/null +++ b/libs/l_net/l_net_wins.h @@ -0,0 +1,52 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//=========================================================================== +// +// Name: l_net_wins.h +// Function: WinSock +// Programmer: MrElusive +// Last update: TTimo: cross-platform version, l_net library +// Tab Size: 3 +// Notes: +//=========================================================================== + +int WINS_Init(void); +void WINS_Shutdown(void); +char *WINS_MyAddress(void); +int WINS_Listen(int socket); +int WINS_Accept(int socket, struct sockaddr_s *addr); +int WINS_OpenSocket(int port); +int WINS_OpenReliableSocket(int port); +int WINS_CloseSocket(int socket); +int WINS_Connect (int socket, struct sockaddr_s *addr); +int WINS_CheckNewConnections(void); +int WINS_Read(int socket, byte *buf, int len, struct sockaddr_s *addr); +int WINS_Write(int socket, byte *buf, int len, struct sockaddr_s *addr); +int WINS_Broadcast (int socket, byte *buf, int len); +char *WINS_AddrToString (struct sockaddr_s *addr); +int WINS_StringToAddr (char *string, struct sockaddr_s *addr); +int WINS_GetSocketAddr (int socket, struct sockaddr_s *addr); +int WINS_GetNameFromAddr (struct sockaddr_s *addr, char *name); +int WINS_GetAddrFromName (char *name, struct sockaddr_s *addr); +int WINS_AddrCompare (struct sockaddr_s *addr1, struct sockaddr_s *addr2); +int WINS_GetSocketPort (struct sockaddr_s *addr); +int WINS_SetSocketPort (struct sockaddr_s *addr, int port); diff --git a/libs/libs.vcproj b/libs/libs.vcproj new file mode 100644 index 00000000..c644478d --- /dev/null +++ b/libs/libs.vcproj @@ -0,0 +1,876 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/maplib.cpp b/libs/maplib.cpp new file mode 100644 index 00000000..80504f1d --- /dev/null +++ b/libs/maplib.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "maplib.h" diff --git a/libs/maplib.h b/libs/maplib.h new file mode 100644 index 00000000..6a2d5cea --- /dev/null +++ b/libs/maplib.h @@ -0,0 +1,282 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_MAPLIB_H) +#define INCLUDED_MAPLIB_H + +#include "nameable.h" +#include "mapfile.h" + +#include "traverselib.h" +#include "transformlib.h" +#include "scenelib.h" +#include "string/string.h" +#include "instancelib.h" +#include "selectionlib.h" +#include "generic/callback.h" + + +class NameableString : public Nameable +{ + CopiedString m_name; +public: + NameableString(const char* name) + : m_name(name) + { + } + + const char* name() const + { + return m_name.c_str(); + } + void attach(const NameCallback& callback) + { + } + void detach(const NameCallback& callback) + { + } +}; + + +class UndoFileChangeTracker : public UndoTracker, public MapFile +{ + std::size_t m_size; + std::size_t m_saved; + typedef void (UndoFileChangeTracker::*Pending)(); + Pending m_pending; + Callback m_changed; + +public: + UndoFileChangeTracker() : m_size(0), m_saved(MAPFILE_MAX_CHANGES), m_pending(0) + { + } + void print() + { + globalOutputStream() << "saved: " << Unsigned(m_saved) << " size: " << Unsigned(m_size) << "\n"; + } + + void push() + { + ++m_size; + m_changed(); + //print(); + } + void pop() + { + --m_size; + m_changed(); + //print(); + } + void pushOperation() + { + if(m_size < m_saved) + { + // redo queue has been flushed.. it is now impossible to get back to the saved state via undo/redo + m_saved = MAPFILE_MAX_CHANGES; + } + push(); + } + void clear() + { + m_size = 0; + m_changed(); + //print(); + } + void begin() + { + m_pending = Pending(&UndoFileChangeTracker::pushOperation); + } + void undo() + { + m_pending = Pending(&UndoFileChangeTracker::pop); + } + void redo() + { + m_pending = Pending(&UndoFileChangeTracker::push); + } + + void changed() + { + if(m_pending != 0) + { + ((*this).*m_pending)(); + m_pending = 0; + } + } + + void save() + { + m_saved = m_size; + m_changed(); + } + bool saved() const + { + return m_saved == m_size; + } + + void setChangedCallback(const Callback& changed) + { + m_changed = changed; + m_changed(); + } + + std::size_t changes() const + { + return m_size; + } +}; + + +class MapRoot : public scene::Node::Symbiot, public scene::Instantiable, public scene::Traversable::Observer +{ + class TypeCasts + { + NodeTypeCastTable m_casts; + public: + TypeCasts() + { + NodeStaticCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + } + NodeTypeCastTable& get() + { + return m_casts; + } + }; + + scene::Node m_node; + IdentityTransform m_transform; + TraversableNodeSet m_traverse; + InstanceSet m_instances; + typedef SelectableInstance Instance; + NameableString m_name; + UndoFileChangeTracker m_changeTracker; +public: + typedef LazyStatic StaticTypeCasts; + + scene::Traversable& get(NullType) + { + return m_traverse; + } + TransformNode& get(NullType) + { + return m_transform; + } + Nameable& get(NullType) + { + return m_name; + } + MapFile& get(NullType) + { + return m_changeTracker; + } + + MapRoot(const char* name) : m_node(this, this, StaticTypeCasts::instance().get()), m_name(name) + { + m_node.m_isRoot = true; + + m_traverse.attach(this); + + GlobalUndoSystem().trackerAttach(m_changeTracker); + } + ~MapRoot() + { + } + void release() + { + GlobalUndoSystem().trackerDetach(m_changeTracker); + + m_traverse.detach(this); + delete this; + } + scene::Node& node() + { + return m_node; + } + + InstanceCounter m_instanceCounter; + void instanceAttach(const scene::Path& path) + { + if(++m_instanceCounter.m_count == 1) + { + m_traverse.instanceAttach(path_find_mapfile(path.begin(), path.end())); + } + } + void instanceDetach(const scene::Path& path) + { + if(--m_instanceCounter.m_count == 0) + { + m_traverse.instanceDetach(path_find_mapfile(path.begin(), path.end())); + } + } + + void insert(scene::Node& child) + { + m_instances.insert(child); + } + void erase(scene::Node& child) + { + m_instances.erase(child); + } + + scene::Node& clone() const + { + return (new MapRoot(*this))->node(); + } + + scene::Instance* create(const scene::Path& path, scene::Instance* parent) + { + return new Instance(path, parent); + } + void forEachInstance(const scene::Instantiable::Visitor& visitor) + { + m_instances.forEachInstance(visitor); + } + void insert(scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance) + { + m_instances.insert(observer, path, instance); + instanceAttach(path); + } + scene::Instance* erase(scene::Instantiable::Observer* observer, const scene::Path& path) + { + instanceDetach(path); + return m_instances.erase(observer, path); + } +}; + +inline void MapRoot_construct() +{ +} + +inline void MapRoot_destroy() +{ +} + +inline NodeSmartReference NewMapRoot(const char* name) +{ + return NodeSmartReference((new MapRoot(name))->node()); +} + + +#endif diff --git a/libs/math/aabb.cpp b/libs/math/aabb.cpp new file mode 100644 index 00000000..8bed2209 --- /dev/null +++ b/libs/math/aabb.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "aabb.h" + diff --git a/libs/math/aabb.h b/libs/math/aabb.h new file mode 100644 index 00000000..1341a340 --- /dev/null +++ b/libs/math/aabb.h @@ -0,0 +1,331 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MATH_AABB_H) +#define INCLUDED_MATH_AABB_H + +/// \file +/// \brief Axis-aligned bounding-box data types and related operations. + +#include "math/matrix.h" +#include "math/plane.h" + +class AABB +{ +public: + Vector3 origin, extents; + + AABB() : origin(0, 0, 0), extents(-1,-1,-1) + { + } + AABB(const Vector3& origin_, const Vector3& extents_) : + origin(origin_), extents(extents_) + { + } +}; + +const float c_aabb_max = FLT_MAX; + +inline bool extents_valid(float f) +{ + return f >= 0.0f && f <= c_aabb_max; +} + +inline bool origin_valid(float f) +{ + return f >= -c_aabb_max && f <= c_aabb_max; +} + +inline bool aabb_valid(const AABB& aabb) +{ + return origin_valid(aabb.origin[0]) + && origin_valid(aabb.origin[1]) + && origin_valid(aabb.origin[2]) + && extents_valid(aabb.extents[0]) + && extents_valid(aabb.extents[1]) + && extents_valid(aabb.extents[2]); +} + +inline AABB aabb_for_minmax(const Vector3& min, const Vector3& max) +{ + AABB aabb; + aabb.origin = vector3_mid(min, max); + aabb.extents = vector3_subtracted(max, aabb.origin); + return aabb; +} + +template +class AABBExtend +{ +public: + static void apply(AABB& aabb, const Vector3& point) + { + float displacement = point[Index::VALUE] - aabb.origin[Index::VALUE]; + float half_difference = static_cast(0.5 * (fabs(displacement) - aabb.extents[Index::VALUE])); + if(half_difference > 0.0f) + { + aabb.origin[Index::VALUE] += (displacement >= 0.0f) ? half_difference : -half_difference; + aabb.extents[Index::VALUE] += half_difference; + } + } + static void apply(AABB& aabb, const AABB& other) + { + float displacement = other.origin[Index::VALUE] - aabb.origin[Index::VALUE]; + float difference = other.extents[Index::VALUE] - aabb.extents[Index::VALUE]; + if(fabs(displacement) > fabs(difference)) + { + float half_difference = static_cast(0.5 * (fabs(displacement) + difference)); + if(half_difference > 0.0f) + { + aabb.origin[Index::VALUE] += (displacement >= 0.0f) ? half_difference : -half_difference; + aabb.extents[Index::VALUE] += half_difference; + } + } + else if(difference > 0.0f) + { + aabb.origin[Index::VALUE] = other.origin[Index::VALUE]; + aabb.extents[Index::VALUE] = other.extents[Index::VALUE]; + } + } +}; + +inline void aabb_extend_by_point(AABB& aabb, const Vector3& point) +{ + AABBExtend< IntegralConstant<0> >::apply(aabb, point); + AABBExtend< IntegralConstant<1> >::apply(aabb, point); + AABBExtend< IntegralConstant<2> >::apply(aabb, point); +} + +inline void aabb_extend_by_point_safe(AABB& aabb, const Vector3& point) +{ + if(aabb_valid(aabb)) + { + aabb_extend_by_point(aabb, point); + } + else + { + aabb.origin = point; + aabb.extents = Vector3(0, 0, 0); + } +} + +class AABBExtendByPoint +{ + AABB& m_aabb; +public: + AABBExtendByPoint(AABB& aabb) : m_aabb(aabb) + { + } + void operator()(const Vector3& point) const + { + aabb_extend_by_point_safe(m_aabb, point); + } +}; + +inline void aabb_extend_by_aabb(AABB& aabb, const AABB& other) +{ + AABBExtend< IntegralConstant<0> >::apply(aabb, other); + AABBExtend< IntegralConstant<1> >::apply(aabb, other); + AABBExtend< IntegralConstant<2> >::apply(aabb, other); +} + +inline void aabb_extend_by_aabb_safe(AABB& aabb, const AABB& other) +{ + if(aabb_valid(aabb) && aabb_valid(other)) + { + aabb_extend_by_aabb(aabb, other); + } + else if(aabb_valid(other)) + { + aabb = other; + } +} + +inline void aabb_extend_by_vec3(AABB& aabb, const Vector3& extension) +{ + vector3_add(aabb.extents, extension); +} + + + + +template +inline bool aabb_intersects_point_dimension(const AABB& aabb, const Vector3& point) +{ + return fabs(point[Index::VALUE] - aabb.origin[Index::VALUE]) < aabb.extents[Index::VALUE]; +} + +inline bool aabb_intersects_point(const AABB& aabb, const Vector3& point) +{ + return aabb_intersects_point_dimension< IntegralConstant<0> >(aabb, point) + && aabb_intersects_point_dimension< IntegralConstant<1> >(aabb, point) + && aabb_intersects_point_dimension< IntegralConstant<2> >(aabb, point); +} + +template +inline bool aabb_intersects_aabb_dimension(const AABB& aabb, const AABB& other) +{ + return fabs(other.origin[Index::VALUE] - aabb.origin[Index::VALUE]) < (aabb.extents[Index::VALUE] + other.extents[Index::VALUE]); +} + +inline bool aabb_intersects_aabb(const AABB& aabb, const AABB& other) +{ + return aabb_intersects_aabb_dimension< IntegralConstant<0> >(aabb, other) + && aabb_intersects_aabb_dimension< IntegralConstant<1> >(aabb, other) + && aabb_intersects_aabb_dimension< IntegralConstant<2> >(aabb, other); +} + +inline unsigned int aabb_classify_plane(const AABB& aabb, const Plane3& plane) +{ + double distance_origin = vector3_dot(plane.normal(), aabb.origin) + plane.dist(); + + if(fabs(distance_origin) < (fabs(plane.a * aabb.extents[0]) + + fabs(plane.b * aabb.extents[1]) + + fabs(plane.c * aabb.extents[2]))) + { + return 1; // partially inside + } + else if (distance_origin < 0) + { + return 2; // totally inside + } + return 0; // totally outside +} + +inline unsigned int aabb_oriented_classify_plane(const AABB& aabb, const Matrix4& transform, const Plane3& plane) +{ + double distance_origin = vector3_dot(plane.normal(), aabb.origin) + plane.dist(); + + if(fabs(distance_origin) < (fabs(aabb.extents[0] * vector3_dot(plane.normal(), vector4_to_vector3(transform.x()))) + + fabs(aabb.extents[1] * vector3_dot(plane.normal(), vector4_to_vector3(transform.y()))) + + fabs(aabb.extents[2] * vector3_dot(plane.normal(), vector4_to_vector3(transform.z()))))) + { + return 1; // partially inside + } + else if (distance_origin < 0) + { + return 2; // totally inside + } + return 0; // totally outside +} + +inline void aabb_corners(const AABB& aabb, Vector3 corners[8]) +{ + Vector3 min(vector3_subtracted(aabb.origin, aabb.extents)); + Vector3 max(vector3_added(aabb.origin, aabb.extents)); + corners[0] = Vector3(min[0], max[1], max[2]); + corners[1] = Vector3(max[0], max[1], max[2]); + corners[2] = Vector3(max[0], min[1], max[2]); + corners[3] = Vector3(min[0], min[1], max[2]); + corners[4] = Vector3(min[0], max[1], min[2]); + corners[5] = Vector3(max[0], max[1], min[2]); + corners[6] = Vector3(max[0], min[1], min[2]); + corners[7] = Vector3(min[0], min[1], min[2]); +} + +inline void aabb_corners_oriented(const AABB& aabb, const Matrix4& rotation, Vector3 corners[8]) +{ + Vector3 x = vector4_to_vector3(rotation.x()) * aabb.extents.x(); + Vector3 y = vector4_to_vector3(rotation.y()) * aabb.extents.y(); + Vector3 z = vector4_to_vector3(rotation.z()) * aabb.extents.z(); + + corners[0] = aabb.origin + -x + y + z; + corners[1] = aabb.origin + x + y + z; + corners[2] = aabb.origin + x + -y + z; + corners[3] = aabb.origin + -x + -y + z; + corners[4] = aabb.origin + -x + y + -z; + corners[5] = aabb.origin + x + y + -z; + corners[6] = aabb.origin + x + -y + -z; + corners[7] = aabb.origin + -x + -y + -z; +} + +inline void aabb_planes(const AABB& aabb, Plane3 planes[6]) +{ + planes[0] = Plane3(g_vector3_axes[0], aabb.origin[0] + aabb.extents[0]); + planes[1] = Plane3(vector3_negated(g_vector3_axes[0]), -(aabb.origin[0] - aabb.extents[0])); + planes[2] = Plane3(g_vector3_axes[1], aabb.origin[1] + aabb.extents[1]); + planes[3] = Plane3(vector3_negated(g_vector3_axes[1]), -(aabb.origin[1] - aabb.extents[1])); + planes[4] = Plane3(g_vector3_axes[2], aabb.origin[2] + aabb.extents[2]); + planes[5] = Plane3(vector3_negated(g_vector3_axes[2]), -(aabb.origin[2] - aabb.extents[2])); +} + +inline void aabb_planes_oriented(const AABB& aabb, const Matrix4& rotation, Plane3 planes[6]) +{ + double x = vector3_dot(vector4_to_vector3(rotation.x()), aabb.origin); + double y = vector3_dot(vector4_to_vector3(rotation.y()), aabb.origin); + double z = vector3_dot(vector4_to_vector3(rotation.z()), aabb.origin); + + planes[0] = Plane3(vector4_to_vector3(rotation.x()), x + aabb.extents[0]); + planes[1] = Plane3(-vector4_to_vector3(rotation.x()), -(x - aabb.extents[0])); + planes[2] = Plane3(vector4_to_vector3(rotation.y()), y + aabb.extents[1]); + planes[3] = Plane3(-vector4_to_vector3(rotation.y()), -(y - aabb.extents[1])); + planes[4] = Plane3(vector4_to_vector3(rotation.z()), z + aabb.extents[2]); + planes[5] = Plane3(-vector4_to_vector3(rotation.z()), -(z - aabb.extents[2])); +} + +const Vector3 aabb_normals[6] = { + Vector3( 1, 0, 0 ), + Vector3( 0, 1, 0 ), + Vector3( 0, 0, 1 ), + Vector3(-1, 0, 0 ), + Vector3( 0,-1, 0 ), + Vector3( 0, 0,-1 ), +}; + +const float aabb_texcoord_topleft[2] = { 0, 0 }; +const float aabb_texcoord_topright[2] = { 1, 0 }; +const float aabb_texcoord_botleft[2] = { 0, 1 }; +const float aabb_texcoord_botright[2] = { 1, 1 }; + + +inline AABB aabb_for_oriented_aabb(const AABB& aabb, const Matrix4& transform) +{ + return AABB( + matrix4_transformed_point(transform, aabb.origin), + Vector3( + static_cast(fabs(transform[0] * aabb.extents[0]) + + fabs(transform[4] * aabb.extents[1]) + + fabs(transform[8] * aabb.extents[2])), + static_cast(fabs(transform[1] * aabb.extents[0]) + + fabs(transform[5] * aabb.extents[1]) + + fabs(transform[9] * aabb.extents[2])), + static_cast(fabs(transform[2] * aabb.extents[0]) + + fabs(transform[6] * aabb.extents[1]) + + fabs(transform[10] * aabb.extents[2])) + ) + ); +} + +inline AABB aabb_for_oriented_aabb_safe(const AABB& aabb, const Matrix4& transform) +{ + if(aabb_valid(aabb)) + { + return aabb_for_oriented_aabb(aabb, transform); + } + return aabb; +} + +inline AABB aabb_infinite() +{ + return AABB(Vector3(0, 0, 0), Vector3(c_aabb_max, c_aabb_max, c_aabb_max)); +} + +#endif diff --git a/libs/math/curve.cpp b/libs/math/curve.cpp new file mode 100644 index 00000000..dd2bc0e3 --- /dev/null +++ b/libs/math/curve.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "curve.h" + diff --git a/libs/math/curve.h b/libs/math/curve.h new file mode 100644 index 00000000..9000734c --- /dev/null +++ b/libs/math/curve.h @@ -0,0 +1,273 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MATH_CURVE_H) +#define INCLUDED_MATH_CURVE_H + +/// \file +/// \brief Curve data types and related operations. + +#include "debugging/debugging.h" +#include "container/array.h" +#include + + +template +struct BernsteinPolynomial +{ + static double apply(double t) + { + return 1; // general case not implemented + } +}; + +typedef IntegralConstant<0> Zero; +typedef IntegralConstant<1> One; +typedef IntegralConstant<2> Two; +typedef IntegralConstant<3> Three; +typedef IntegralConstant<4> Four; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return 1; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return 1 - t; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return t; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return (1 - t) * (1 - t); + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return 2 * (1 - t) * t; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return t * t; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return (1 - t) * (1 - t) * (1 - t); + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return 3 * (1 - t) * (1 - t) * t; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return 3 * (1 - t) * t * t; + } +}; + +template<> +struct BernsteinPolynomial +{ + static double apply(double t) + { + return t * t * t; + } +}; + +typedef Array ControlPoints; + +inline Vector3 CubicBezier_evaluate(const Vector3* firstPoint, double t) +{ + Vector3 result(0, 0, 0); + double denominator = 0; + + { + double weight = BernsteinPolynomial::apply(t); + result += vector3_scaled(*firstPoint++, weight); + denominator += weight; + } + { + double weight = BernsteinPolynomial::apply(t); + result += vector3_scaled(*firstPoint++, weight); + denominator += weight; + } + { + double weight = BernsteinPolynomial::apply(t); + result += vector3_scaled(*firstPoint++, weight); + denominator += weight; + } + { + double weight = BernsteinPolynomial::apply(t); + result += vector3_scaled(*firstPoint++, weight); + denominator += weight; + } + + return result / denominator; +} + +inline Vector3 CubicBezier_evaluateMid(const Vector3* firstPoint) +{ + return vector3_scaled(firstPoint[0], 0.125) + + vector3_scaled(firstPoint[1], 0.375) + + vector3_scaled(firstPoint[2], 0.375) + + vector3_scaled(firstPoint[3], 0.125); +} + +inline Vector3 CatmullRom_evaluate(const ControlPoints& controlPoints, double t) +{ + // scale t to be segment-relative + t *= double(controlPoints.size() - 1); + + // subtract segment index; + std::size_t segment = 0; + for(std::size_t i = 0; i < controlPoints.size() - 1; ++i) + { + if(t <= double(i+1)) + { + segment = i; + break; + } + } + t -= segment; + + const double reciprocal_alpha = 1.0 / 3.0; + + Vector3 bezierPoints[4]; + bezierPoints[0] = controlPoints[segment]; + bezierPoints[1] = (segment > 0) + ? controlPoints[segment] + vector3_scaled(controlPoints[segment + 1] - controlPoints[segment - 1], reciprocal_alpha * 0.5) + : controlPoints[segment] + vector3_scaled(controlPoints[segment + 1] - controlPoints[segment], reciprocal_alpha); + bezierPoints[2] = (segment < controlPoints.size() - 2) + ? controlPoints[segment + 1] + vector3_scaled(controlPoints[segment] - controlPoints[segment + 2], reciprocal_alpha * 0.5) + : controlPoints[segment + 1] + vector3_scaled(controlPoints[segment] - controlPoints[segment + 1], reciprocal_alpha); + bezierPoints[3] = controlPoints[segment + 1]; + return CubicBezier_evaluate(bezierPoints, t); +} + +typedef Array Knots; + +inline double BSpline_basis(const Knots& knots, std::size_t i, std::size_t degree, double t) +{ + if(degree == 0) + { + if(knots[i] <= t + && t < knots[i + 1] + && knots[i] < knots[i + 1]) + { + return 1; + } + return 0; + } + double leftDenom = knots[i + degree] - knots[i]; + double left = (leftDenom == 0) ? 0 : ((t - knots[i]) / leftDenom) * BSpline_basis(knots, i, degree - 1, t); + double rightDenom = knots[i + degree + 1] - knots[i + 1]; + double right = (rightDenom == 0) ? 0 : ((knots[i + degree + 1] - t) / rightDenom) * BSpline_basis(knots, i + 1, degree - 1, t); + return left + right; +} + +inline Vector3 BSpline_evaluate(const ControlPoints& controlPoints, const Knots& knots, std::size_t degree, double t) +{ + Vector3 result(0, 0, 0); + for(std::size_t i = 0; i < controlPoints.size(); ++i) + { + result += vector3_scaled(controlPoints[i], BSpline_basis(knots, i, degree, t)); + } + return result; +} + +typedef Array NURBSWeights; + +inline Vector3 NURBS_evaluate(const ControlPoints& controlPoints, const NURBSWeights& weights, const Knots& knots, std::size_t degree, double t) +{ + Vector3 result(0, 0, 0); + double denominator = 0; + for(std::size_t i = 0; i < controlPoints.size(); ++i) + { + double weight = weights[i] * BSpline_basis(knots, i, degree, t); + result += vector3_scaled(controlPoints[i], weight); + denominator += weight; + } + return result / denominator; +} + +inline void KnotVector_openUniform(Knots& knots, std::size_t count, std::size_t degree) +{ + knots.resize(count + degree + 1); + + std::size_t equalKnots = 1; + + for(std::size_t i = 0; i < equalKnots; ++i) + { + knots[i] = 0; + knots[knots.size() - (i + 1)] = 1; + } + + std::size_t difference = knots.size() - 2 * (equalKnots); + for(std::size_t i = 0; i < difference; ++i) + { + knots[i + equalKnots] = Knots::value_type(double(i + 1) * 1.0 / double(difference + 1)); + } +} + +#endif diff --git a/libs/math/expression.cpp b/libs/math/expression.cpp new file mode 100644 index 00000000..5fc8d051 --- /dev/null +++ b/libs/math/expression.cpp @@ -0,0 +1,243 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "expression.h" + +Vector3 testAdded1(const Vector3& a, const Vector3& b) +{ + return vector3_added(a, vector3_added(a, b)); +} + +Vector3 testAdded2(const Vector3& a, const Vector3& b) +{ + return vector3_for_expression( vector_added( vector3_identity(a), vector_added( vector3_identity(a), vector3_identity(b) ) ) ); +} + +Vector3 testMultiplied1(const Vector3& a, const Vector3& b) +{ + return vector3_scaled(a, b); +} + +Vector3 testMultiplied2(const Vector3& a, const Vector3& b) +{ + return vector3_for_expression( vector_multiplied( vector3_identity(a), vector3_identity(b) ) ); +} + +Vector3 testCross1(const Vector3& a, const Vector3& b) +{ + return vector3_cross(a, b); +} + +Vector3 testCross2(const Vector3& a, const Vector3& b) +{ + return vector3_for_expression( vector_cross( vector3_identity(a), vector3_identity(b) ) ); +} + +double testDot1(const Vector3& a, const Vector3& b) +{ + return vector3_dot(a, b); +} + +double testDot2(const Vector3& a, const Vector3& b) +{ + return float_for_expression( vector_dot( vector3_identity(a), vector3_identity(b) ) ); +} + +double testLength1(const Vector3& a) +{ + return vector3_length(a); +} + +double testLength2(const Vector3& a) +{ + return float_for_expression( vector_length( vector3_identity(a) ) ); +} + +Vector3 testNormalised1(const Vector3& a) +{ + return vector3_normalised(a); +} + +Vector3 testNormalised2(const Vector3& a) +{ + return vector3_for_expression( vector_normalised( vector3_identity(a) ) ); +} + +Vector3 testNegated1(const Vector3& a) +{ + return vector3_negated(a); +} + +Vector3 testNegated2(const Vector3& a) +{ + return vector3_for_expression( vector_negated( vector3_identity(a) ) ); +} + +Vector3 testScaled1(const Vector3& a, const double& b) +{ + return vector3_scaled(a, b); +} + +Vector3 testScaled2(const Vector3& a, const double& b) +{ + return vector3_for_expression( vector_scaled( vector3_identity(a), float_literal(b) ) ); +} + +Vector3 testMatrixMultiplied1(const Vector3& a, const Matrix4& b) +{ + return matrix4_transformed_point(b, vector3_added(a, Vector3(1, 0, 0))); +} + +Vector3 testMatrixMultiplied2(const Vector3& a, const Matrix4& b) +{ + return vector3_for_expression( + point_multiplied( + vector_added( + vector3_identity(a), + vector3_literal(Vector3(1, 0, 0)) + ), + matrix4_identity(b) + ) + ); +} + +Matrix4 testMatrix4Multiplied1(const Matrix4& a, const Matrix4& b) +{ + return matrix4_multiplied_by_matrix4(a, matrix4_multiplied_by_matrix4(a, b)); +} + +Matrix4 testMatrix4Multiplied2(const Matrix4& a, const Matrix4& b) +{ + return matrix4_for_expression( + matrix4_multiplied( + matrix4_identity(a), + matrix4_identity(b) + ) + ); +} + +Matrix4 testMatrix4AffineMultiplied1(const Matrix4& a, const Matrix4& b) +{ + return matrix4_affine_multiplied_by_matrix4(a, b); +} + +Matrix4 testMatrix4AffineMultiplied2(const Matrix4& a, const Matrix4& b) +{ + return matrix4_affine_for_expression( + matrix4_multiplied( + matrix4_identity(a), + matrix4_identity(b) + ) + ); +} + +Matrix4 testMatrix4MultipliedConstant1(const Matrix4& a) +{ + return matrix4_multiplied_by_matrix4(a, g_matrix4_identity); +} + +Matrix4 testMatrix4MultipliedConstant2(const Matrix4& a) +{ + return matrix4_for_expression( + matrix4_multiplied( + matrix4_identity(a), + matrix4_identity(g_matrix4_identity) + ) + ); +} +Matrix4 testMatrix4Transposed1(const Matrix4& a) +{ + return matrix4_transposed(a); +} + +Matrix4 testMatrix4Transposed2(const Matrix4& a) +{ + return matrix4_for_expression( matrix_transposed( matrix4_identity(a) ) ); +} + +Vector3 testMulti1(const Matrix4& a, const Vector3& b, const Vector3& c) +{ + return vector3_added(matrix4_transformed_point(matrix4_transposed(a), b), c); +} + +Vector3 testMulti2(const Matrix4& a, const Vector3& b, const Vector3& c) +{ + return vector3_for_expression( + vector_added( + point_multiplied( + vector3_identity(b), + matrix_transposed(matrix4_identity(a)) + ), + vector3_identity(c) + ) + ); +} + +template +class TestBinaryFunction +{ + typedef Value(*Function)(const First&, const Second&); + Function m_function; +public: + + TestBinaryFunction(Function function) : m_function(function) + { + } + Value run(const First& first, const Second& second) const + { + return m_function(first, second); + } +}; + +template +class TestUnaryFunction +{ + typedef Value(*Function)(const First&); + Function m_function; +public: + + TestUnaryFunction(Function function) : m_function(function) + { + } + Value run(const First& first) const + { + return m_function(first); + } +}; + +class TestAll +{ +public: + TestAll() + { + Vector3 result1 = TestBinaryFunction(testAdded1).run(Vector3(0, 0, 0), Vector3(1, 1, 1)); + Vector3 result2 = TestBinaryFunction(testAdded2).run(Vector3(0, 0, 0), Vector3(1, 1, 1)); + Vector3 result3 = TestBinaryFunction(testMultiplied1).run(Vector3(1, 2, 3), Vector3(2, 1, 0.5f)); + Vector3 result4 = TestBinaryFunction(testMultiplied2).run(Vector3(1, 2, 3), Vector3(2, 1, 0.5f)); + Vector3 result5 = TestBinaryFunction(testScaled1).run(Vector3(1, 2, 3), 2.0); + Vector3 result6 = TestBinaryFunction(testScaled2).run(Vector3(1, 2, 3), 2.0); + Vector3 result7 = TestBinaryFunction(testMatrixMultiplied1).run(Vector3(1, 2, 3), matrix4_rotation_for_x_degrees(90)); + Vector3 result8 = TestBinaryFunction(testMatrixMultiplied2).run(Vector3(1, 2, 3), matrix4_rotation_for_x_degrees(90)); + Vector3 result9 = TestUnaryFunction(testNormalised1).run(Vector3(1, 2, 3)); + Vector3 result10 = TestUnaryFunction(testNormalised2).run(Vector3(1, 2, 3)); + } +} g_testAll; + diff --git a/libs/math/expression.h b/libs/math/expression.h new file mode 100644 index 00000000..c609bb16 --- /dev/null +++ b/libs/math/expression.h @@ -0,0 +1,617 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_EXPRESSION_H) +#define INCLUDED_EXPRESSION_H + +#include + +template +class Literal +{ + Value m_value; +public: + typedef Value value_type; + + Literal(const Value& value) + : m_value(value) + { + } + const value_type& eval() const + { + return m_value; + } +}; + +template +inline Literal float_literal(const Value& value) +{ + return Literal(value); +} + +template +inline float float_for_expression(const Expression& expression) +{ + return expression.eval(); +} + + +template +class ScalarDivided +{ + First first; + Second second; +public: + typedef typename First::value_type value_type; + + ScalarDivided(const First& first_, const Second& second_) : first(first_), second(second_) + { + } + value_type eval() const + { + return static_cast(first.eval() / second.eval()); + } +}; + +template +inline ScalarDivided float_divided(const First& first, const Second& second) +{ + return ScalarDivided(first, second); +} + +template +inline ScalarDivided, First> float_reciprocal(const First& first) +{ + typedef typename First::value_type first_value_type; + return ScalarDivided, First>(float_literal(first_value_type(1.0)), first); +} + +template +class SquareRoot +{ + First first; +public: + typedef typename First::value_type value_type; + + SquareRoot(const First& first_) : first(first_) + { + } + value_type eval() const + { + return static_cast(sqrt(first.eval())); + } +}; + +template +inline SquareRoot float_square_root(const First& first) +{ + return SquareRoot(first); +} + + +template +class BasicVector3Literal +{ + const BasicVector3 m_value; +public: + typedef Element value_type; + typedef IntegralConstant<3> dimension; + + BasicVector3Literal(const BasicVector3& value) + : m_value(value) + { + } + const value_type& eval(unsigned int i) const + { + return m_value[i]; + } +}; + +template +inline BasicVector3Literal vector3_literal(const BasicVector3& value) +{ + return BasicVector3Literal(value); +} + +typedef BasicVector3Literal Vector3Literal; + +template +class BasicVector3Identity +{ + const BasicVector3& m_value; +public: + typedef Element value_type; + typedef IntegralConstant<3> dimension; + + BasicVector3Identity(const BasicVector3& value) + : m_value(value) + { + } + const value_type& eval(unsigned int i) const + { + return m_value[i]; + } +}; + +template +inline BasicVector3Identity vector3_identity(const BasicVector3& value) +{ + return BasicVector3Identity(value); +} + +typedef BasicVector3Identity Vector3Identity; + +template +inline BasicVector3 vector3_for_expression(const Expression& expression) +{ + return Vector3(expression.eval(0), expression.eval(1), expression.eval(2)); +} + + +template +class VectorScalar +{ + First first; + Literal second; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + VectorScalar(const First& first_, const Second& second_) + : first(first_), second(second_.eval()) + { + } + value_type eval(unsigned int i) const + { + return Operation::apply( first.eval(i), second.eval() ); + } +}; + + + +template +class VectorVector +{ + First first; + Second second; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + VectorVector(const First& first_, const Second& second_) + : first(first_), second(second_) + { + } + value_type eval(unsigned int i) const + { + return Operation::apply(first.eval(i), second.eval(i)); + } +}; + +template +class Added +{ +public: + typedef First value_type; + + static value_type apply(const First& first, const Second& second) + { + return static_cast(first + second); + } +}; + +template +inline VectorVector, First, Second> +vector_added(const First& first, const Second& second) +{ + typedef typename First::value_type first_value_type; + typedef typename Second::value_type second_value_type; + return VectorVector, First, Second>(first, second); +} + +template +class Multiplied +{ +public: + typedef First value_type; + + static value_type apply(const First& first, const Second& second) + { + return static_cast(first * second); + } +}; + +template +inline VectorVector, First, Second> +vector_multiplied(const First& first, const Second& second) +{ + typedef typename First::value_type first_value_type; + typedef typename Second::value_type second_value_type; + return VectorVector, First, Second>(first, second); +} + +template +inline VectorScalar, First, Second> +vector_scaled(const First& first, const Second& second) +{ + typedef typename First::value_type first_value_type; + typedef typename Second::value_type second_value_type; + return VectorScalar, First, Second>(first, second); +} + +template +class Negated +{ +public: + typedef First value_type; + + static value_type apply(const First& first) + { + return -first; + } +}; + +template +class VectorUnary +{ + First first; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + VectorUnary(const First& first_) : first(first_) + { + } + value_type eval(unsigned int i) const + { + return Operation::apply(first.eval(i)); + } +}; + +template +inline VectorUnary > +vector_negated(const First& first) +{ + typedef typename First::value_type first_value_type; + return VectorUnary >(first); +} + +template +class VectorCross +{ + First first; + Second second; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + VectorCross(const First& first_, const Second& second_) + : first(first_), second(second_) + { + } + value_type eval(unsigned int i) const + { + return first.eval((i+1)%3) * second.eval((i+2)%3) - first.eval((i+2)%3) * second.eval((i+1)%3); + } +}; + +template +inline VectorCross +vector_cross(const First& first, const Second& second) +{ + return VectorCross(first, second); +} + + +template +class VectorDot +{ + First first; + Second second; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + VectorDot(const First& first_, const Second& second_) + : first(first_), second(second_) + { + } + + template + struct eval_dot + { + static value_type apply(const First& first, const Second& second) + { + return static_cast( + first.eval(Index::VALUE) * second.eval(Index::VALUE) + + eval_dot< IntegralConstant >::apply(first, second) + ); + } + }; + + template<> + struct eval_dot< IntegralConstant<0> > + { + static value_type apply(const First& first, const Second& second) + { + return first.eval(0) * second.eval(0); + } + }; + + value_type eval() const + { + return eval_dot< IntegralConstant >::apply(first, second); + } +}; + + +template +inline VectorDot vector_dot(const First& first, const Second& second) +{ + return VectorDot(first, second); +} + +template +class VectorLengthSquared +{ + First first; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + VectorLengthSquared(const First& first_) + : first(first_) + { + } + + static value_type squared(const value_type& value) + { + return value * value; + } + + template + struct eval_squared + { + static value_type apply(const First& first) + { + return static_cast( + squared(first.eval(Index::VALUE)) + + eval_squared< IntegralConstant >::apply(first) + ); + } + }; + + template<> + struct eval_squared< IntegralConstant<0> > + { + static value_type apply(const First& first) + { + return squared(first.eval(0)); + } + }; + + value_type eval() const + { + return eval_squared< IntegralConstant >::apply(first); + } +}; + +template +inline VectorLengthSquared vector_length_squared(const First& first) +{ + return VectorLengthSquared(first); +} + +template +inline SquareRoot< VectorLengthSquared > vector_length(const First& first) +{ + return float_square_root(vector_length_squared(first)); +} + +#if 1 +template +inline VectorScalar< + Multiplied, + First, + // multiple evaulations of subexpression + ScalarDivided< + Literal, + SquareRoot< + VectorLengthSquared + > + > +> vector_normalised(const First& first) +{ + typedef typename First::value_type first_value_type; + return vector_scaled(first, float_reciprocal(vector_length(first))); +} +#else +template +inline VectorScalar< + Multiplied, + First, + // single evaluation of subexpression + Literal +> +vector_normalised(const First& first) +{ + typedef typename First::value_type first_value_type; + return vector_scaled(first, float_literal(static_cast(first_value_type(1.0) / vector_length(first).eval()))); +} +#endif + + +class Matrix4Literal +{ + const Matrix4 m_value; +public: + typedef float value_type; + typedef IntegralConstant<4> dimension0; + typedef IntegralConstant<4> dimension1; + + Matrix4Literal(const Matrix4& value) + : m_value(value) + { + } + const value_type& eval(unsigned int r, unsigned int c) const + { + return m_value[r*4+c]; + } +}; + +inline Matrix4Literal matrix4_literal(const Matrix4& value) +{ + return Matrix4Literal(value); +} + +class Matrix4Identity +{ + const Matrix4& m_value; +public: + typedef float value_type; + typedef IntegralConstant<4> dimension0; + typedef IntegralConstant<4> dimension1; + + Matrix4Identity(const Matrix4& value) + : m_value(value) + { + } + const value_type& eval(unsigned int r, unsigned int c) const + { + return m_value[r*4+c]; + } +}; + +inline Matrix4Identity matrix4_identity(const Matrix4& value) +{ + return Matrix4Identity(value); +} + +template +inline Matrix4 matrix4_for_expression(const Expression& expression) +{ + return Matrix4( + expression.eval(0, 0), expression.eval(0, 1), expression.eval(0, 2), expression.eval(0, 3), + expression.eval(1, 0), expression.eval(1, 1), expression.eval(1, 2), expression.eval(1, 3), + expression.eval(2, 0), expression.eval(2, 1), expression.eval(2, 2), expression.eval(2, 3), + expression.eval(3, 0), expression.eval(3, 1), expression.eval(3, 2), expression.eval(3, 3) + ); +} + +template +inline Matrix4 matrix4_affine_for_expression(const Expression& expression) +{ + return Matrix4( + expression.eval(0, 0), expression.eval(0, 1), expression.eval(0, 2), 0, + expression.eval(1, 0), expression.eval(1, 1), expression.eval(1, 2), 0, + expression.eval(2, 0), expression.eval(2, 1), expression.eval(2, 2), 0, + expression.eval(3, 0), expression.eval(3, 1), expression.eval(3, 2), 1 + ); +} + + +template +class PointMultiplied +{ + const First& first; + const Second& second; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension dimension; + + PointMultiplied(const First& first_, const Second& second_) + : first(first_), second(second_) + { + } + value_type eval(unsigned int i) const + { + return static_cast(second.eval(0, i) * first.eval(0) + + second.eval(1, i) * first.eval(1) + + second.eval(2, i) * first.eval(2) + + second.eval(3, i)); + } +}; + +template +inline PointMultiplied point_multiplied(const First& point, const Second& matrix) +{ + return PointMultiplied(point, matrix); +} + +template +class Matrix4Multiplied +{ + const First& first; + const Second& second; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension0 dimension0; + typedef typename First::dimension1 dimension1; + + Matrix4Multiplied(const First& first_, const Second& second_) + : first(first_), second(second_) + { + } + + value_type eval(unsigned int r, unsigned int c) const + { + return static_cast( + second.eval(r, 0) * first.eval(0, c) + + second.eval(r, 1) * first.eval(1, c) + + second.eval(r, 2) * first.eval(2, c) + + second.eval(r, 3) * first.eval(3, c) + ); + } +}; + +template +inline Matrix4Multiplied matrix4_multiplied(const First& first, const Second& second) +{ + return Matrix4Multiplied(first, second); +} + +template +class MatrixTransposed +{ + const First& first; +public: + typedef typename First::value_type value_type; + typedef typename First::dimension0 dimension0; + typedef typename First::dimension1 dimension1; + + MatrixTransposed(const First& first_) + : first(first_) + { + } + + value_type eval(unsigned int r, unsigned int c) const + { + return first.eval(c, r); + } +}; + +template +inline MatrixTransposed matrix_transposed(const First& first) +{ + return MatrixTransposed(first); +} + +#endif diff --git a/libs/math/frustum.cpp b/libs/math/frustum.cpp new file mode 100644 index 00000000..84481e20 --- /dev/null +++ b/libs/math/frustum.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "frustum.h" + diff --git a/libs/math/frustum.h b/libs/math/frustum.h new file mode 100644 index 00000000..fe98db20 --- /dev/null +++ b/libs/math/frustum.h @@ -0,0 +1,629 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MATH_FRUSTUM_H) +#define INCLUDED_MATH_FRUSTUM_H + +/// \file +/// \brief View-frustum data types and related operations. + +#include "generic/enumeration.h" +#include "math/matrix.h" +#include "math/plane.h" +#include "math/aabb.h" +#include "math/line.h" + +inline Matrix4 matrix4_frustum(float left, float right, float bottom, float top, float nearval, float farval) +{ + return Matrix4( + static_cast( (2*nearval) / (right-left) ), + 0, + 0, + 0, + 0, + static_cast( (2*nearval) / (top-bottom) ), + 0, + 0, + static_cast( (right+left) / (right-left) ), + static_cast( (top+bottom) / (top-bottom) ), + static_cast( -(farval+nearval) / (farval-nearval) ), + -1, + 0, + 0, + static_cast( -(2*farval*nearval) / (farval-nearval) ), + 0 + ); +} + + + +typedef unsigned char ClipResult; +const ClipResult c_CLIP_PASS = 0x00; // 000000 +const ClipResult c_CLIP_LT_X = 0x01; // 000001 +const ClipResult c_CLIP_GT_X = 0x02; // 000010 +const ClipResult c_CLIP_LT_Y = 0x04; // 000100 +const ClipResult c_CLIP_GT_Y = 0x08; // 001000 +const ClipResult c_CLIP_LT_Z = 0x10; // 010000 +const ClipResult c_CLIP_GT_Z = 0x20; // 100000 +const ClipResult c_CLIP_FAIL = 0x3F; // 111111 + +template +class Vector4ClipLT +{ +public: + static bool compare(const Vector4& self) + { + return self[Index::VALUE] < self[3]; + } + static double scale(const Vector4& self, const Vector4& other) + { + return (self[Index::VALUE] - self[3]) / (other[3] - other[Index::VALUE]); + } +}; + +template +class Vector4ClipGT +{ +public: + static bool compare(const Vector4& self) + { + return self[Index::VALUE] > -self[3]; + } + static double scale(const Vector4& self, const Vector4& other) + { + return (self[Index::VALUE] + self[3]) / (-other[3] - other[Index::VALUE]); + } +}; + +template +class Vector4ClipPolygon +{ +public: + typedef Vector4* iterator; + typedef const Vector4* const_iterator; + + static std::size_t apply(const_iterator first, const_iterator last, iterator out) + { + const_iterator next = first, i = last - 1; + iterator tmp(out); + bool b0 = ClipPlane::compare(*i); + while(next != last) + { + bool b1 = ClipPlane::compare(*next); + if(b0 ^ b1) + { + *out = vector4_subtracted(*next, *i); + + double scale = ClipPlane::scale(*i, *out); + + (*out)[0] = static_cast((*i)[0] + scale*((*out)[0])); + (*out)[1] = static_cast((*i)[1] + scale*((*out)[1])); + (*out)[2] = static_cast((*i)[2] + scale*((*out)[2])); + (*out)[3] = static_cast((*i)[3] + scale*((*out)[3])); + + ++out; + } + + if(b1) + { + *out = *next; + ++out; + } + + i = next; + ++next; + b0 = b1; + } + + return out - tmp; + } +}; + +#define CLIP_X_LT_W(p) (Vector4ClipLT< IntegralConstant<0> >::compare(p)) +#define CLIP_X_GT_W(p) (Vector4ClipGT< IntegralConstant<0> >::compare(p)) +#define CLIP_Y_LT_W(p) (Vector4ClipLT< IntegralConstant<1> >::compare(p)) +#define CLIP_Y_GT_W(p) (Vector4ClipGT< IntegralConstant<1> >::compare(p)) +#define CLIP_Z_LT_W(p) (Vector4ClipLT< IntegralConstant<2> >::compare(p)) +#define CLIP_Z_GT_W(p) (Vector4ClipGT< IntegralConstant<2> >::compare(p)) + +inline ClipResult homogenous_clip_point(const Vector4& clipped) +{ + ClipResult result = c_CLIP_FAIL; + if(CLIP_X_LT_W(clipped)) result &= ~c_CLIP_LT_X; // X < W + if(CLIP_X_GT_W(clipped)) result &= ~c_CLIP_GT_X; // X > -W + if(CLIP_Y_LT_W(clipped)) result &= ~c_CLIP_LT_Y; // Y < W + if(CLIP_Y_GT_W(clipped)) result &= ~c_CLIP_GT_Y; // Y > -W + if(CLIP_Z_LT_W(clipped)) result &= ~c_CLIP_LT_Z; // Z < W + if(CLIP_Z_GT_W(clipped)) result &= ~c_CLIP_GT_Z; // Z > -W + return result; +} + +/// \brief Clips \p point by canonical matrix \p self. +/// Stores the result in \p clipped. +/// Returns a bitmask indicating which clip-planes the point was outside. +inline ClipResult matrix4_clip_point(const Matrix4& self, const Vector3& point, Vector4& clipped) +{ + clipped[0] = point[0]; + clipped[1] = point[1]; + clipped[2] = point[2]; + clipped[3] = 1; + matrix4_transform_vector4(self, clipped); + return homogenous_clip_point(clipped); +} + + +inline std::size_t homogenous_clip_triangle(Vector4 clipped[9]) +{ + Vector4 buffer[9]; + std::size_t count = 3; + count = Vector4ClipPolygon< Vector4ClipLT< IntegralConstant<0> > >::apply(clipped, clipped + count, buffer); + count = Vector4ClipPolygon< Vector4ClipGT< IntegralConstant<0> > >::apply(buffer, buffer + count, clipped); + count = Vector4ClipPolygon< Vector4ClipLT< IntegralConstant<1> > >::apply(clipped, clipped + count, buffer); + count = Vector4ClipPolygon< Vector4ClipGT< IntegralConstant<1> > >::apply(buffer, buffer + count, clipped); + count = Vector4ClipPolygon< Vector4ClipLT< IntegralConstant<2> > >::apply(clipped, clipped + count, buffer); + return Vector4ClipPolygon< Vector4ClipGT< IntegralConstant<2> > >::apply(buffer, buffer + count, clipped); +} + +/// \brief Transforms and clips the triangle formed by \p p0, \p p1, \p p2 by the canonical matrix \p self. +/// Stores the resulting polygon in \p clipped. +/// Returns the number of points in the resulting polygon. +inline std::size_t matrix4_clip_triangle(const Matrix4& self, const Vector3& p0, const Vector3& p1, const Vector3& p2, Vector4 clipped[9]) +{ + clipped[0][0] = p0[0]; + clipped[0][1] = p0[1]; + clipped[0][2] = p0[2]; + clipped[0][3] = 1; + clipped[1][0] = p1[0]; + clipped[1][1] = p1[1]; + clipped[1][2] = p1[2]; + clipped[1][3] = 1; + clipped[2][0] = p2[0]; + clipped[2][1] = p2[1]; + clipped[2][2] = p2[2]; + clipped[2][3] = 1; + + matrix4_transform_vector4(self, clipped[0]); + matrix4_transform_vector4(self, clipped[1]); + matrix4_transform_vector4(self, clipped[2]); + + return homogenous_clip_triangle(clipped); +} + +inline std::size_t homogenous_clip_line(Vector4 clipped[2]) +{ + const Vector4& p0 = clipped[0]; + const Vector4& p1 = clipped[1]; + + // early out + { + ClipResult mask0 = homogenous_clip_point(clipped[0]); + ClipResult mask1 = homogenous_clip_point(clipped[1]); + + if((mask0 | mask1) == c_CLIP_PASS) // both points passed all planes + return 2; + + if(mask0 & mask1) // both points failed any one plane + return 0; + } + + { + const bool index = CLIP_X_LT_W(p0); + if(index ^ CLIP_X_LT_W(p1)) + { + Vector4 clip(vector4_subtracted(p1, p0)); + + double scale = (p0[0] - p0[3]) / (clip[3] - clip[0]); + + clip[0] = static_cast(p0[0] + scale * clip[0]); + clip[1] = static_cast(p0[1] + scale * clip[1]); + clip[2] = static_cast(p0[2] + scale * clip[2]); + clip[3] = static_cast(p0[3] + scale * clip[3]); + + clipped[index] = clip; + } + else if(index == 0) + return 0; + } + + { + const bool index = CLIP_X_GT_W(p0); + if(index ^ CLIP_X_GT_W(p1)) + { + Vector4 clip(vector4_subtracted(p1, p0)); + + double scale = (p0[0] + p0[3]) / (-clip[3] - clip[0]); + + clip[0] = static_cast(p0[0] + scale * clip[0]); + clip[1] = static_cast(p0[1] + scale * clip[1]); + clip[2] = static_cast(p0[2] + scale * clip[2]); + clip[3] = static_cast(p0[3] + scale * clip[3]); + + clipped[index] = clip; + } + else if(index == 0) + return 0; + } + + { + const bool index = CLIP_Y_LT_W(p0); + if(index ^ CLIP_Y_LT_W(p1)) + { + Vector4 clip(vector4_subtracted(p1, p0)); + + double scale = (p0[1] - p0[3]) / (clip[3] - clip[1]); + + clip[0] = static_cast(p0[0] + scale * clip[0]); + clip[1] = static_cast(p0[1] + scale * clip[1]); + clip[2] = static_cast(p0[2] + scale * clip[2]); + clip[3] = static_cast(p0[3] + scale * clip[3]); + + clipped[index] = clip; + } + else if(index == 0) + return 0; + } + + { + const bool index = CLIP_Y_GT_W(p0); + if(index ^ CLIP_Y_GT_W(p1)) + { + Vector4 clip(vector4_subtracted(p1, p0)); + + double scale = (p0[1] + p0[3]) / (-clip[3] - clip[1]); + + clip[0] = static_cast(p0[0] + scale * clip[0]); + clip[1] = static_cast(p0[1] + scale * clip[1]); + clip[2] = static_cast(p0[2] + scale * clip[2]); + clip[3] = static_cast(p0[3] + scale * clip[3]); + + clipped[index] = clip; + } + else if(index == 0) + return 0; + } + + { + const bool index = CLIP_Z_LT_W(p0); + if(index ^ CLIP_Z_LT_W(p1)) + { + Vector4 clip(vector4_subtracted(p1, p0)); + + double scale = (p0[2] - p0[3]) / (clip[3] - clip[2]); + + clip[0] = static_cast(p0[0] + scale * clip[0]); + clip[1] = static_cast(p0[1] + scale * clip[1]); + clip[2] = static_cast(p0[2] + scale * clip[2]); + clip[3] = static_cast(p0[3] + scale * clip[3]); + + clipped[index] = clip; + } + else if(index == 0) + return 0; + } + + { + const bool index = CLIP_Z_GT_W(p0); + if(index ^ CLIP_Z_GT_W(p1)) + { + Vector4 clip(vector4_subtracted(p1, p0)); + + double scale = (p0[2] + p0[3]) / (-clip[3] - clip[2]); + + clip[0] = static_cast(p0[0] + scale * clip[0]); + clip[1] = static_cast(p0[1] + scale * clip[1]); + clip[2] = static_cast(p0[2] + scale * clip[2]); + clip[3] = static_cast(p0[3] + scale * clip[3]); + + clipped[index] = clip; + } + else if(index == 0) + return 0; + } + + return 2; +} + +/// \brief Transforms and clips the line formed by \p p0, \p p1 by the canonical matrix \p self. +/// Stores the resulting line in \p clipped. +/// Returns the number of points in the resulting line. +inline std::size_t matrix4_clip_line(const Matrix4& self, const Vector3& p0, const Vector3& p1, Vector4 clipped[2]) +{ + clipped[0][0] = p0[0]; + clipped[0][1] = p0[1]; + clipped[0][2] = p0[2]; + clipped[0][3] = 1; + clipped[1][0] = p1[0]; + clipped[1][1] = p1[1]; + clipped[1][2] = p1[2]; + clipped[1][3] = 1; + + matrix4_transform_vector4(self, clipped[0]); + matrix4_transform_vector4(self, clipped[1]); + + return homogenous_clip_line(clipped); +} + + + + +struct Frustum +{ + Plane3 right, left, bottom, top, back, front; + + Frustum() + { + } + Frustum(const Plane3& _right, + const Plane3& _left, + const Plane3& _bottom, + const Plane3& _top, + const Plane3& _back, + const Plane3& _front) + : right(_right), left(_left), bottom(_bottom), top(_top), back(_back), front(_front) + { + } +}; + +inline Frustum frustum_transformed(const Frustum& frustum, const Matrix4& transform) +{ + return Frustum( + plane3_transformed(frustum.right, transform), + plane3_transformed(frustum.left, transform), + plane3_transformed(frustum.bottom, transform), + plane3_transformed(frustum.top, transform), + plane3_transformed(frustum.back, transform), + plane3_transformed(frustum.front, transform) + ); +} + +inline Frustum frustum_inverse_transformed(const Frustum& frustum, const Matrix4& transform) +{ + return Frustum( + plane3_inverse_transformed(frustum.right, transform), + plane3_inverse_transformed(frustum.left, transform), + plane3_inverse_transformed(frustum.bottom, transform), + plane3_inverse_transformed(frustum.top, transform), + plane3_inverse_transformed(frustum.back, transform), + plane3_inverse_transformed(frustum.front, transform) + ); +} + +inline bool viewproj_test_point(const Matrix4& viewproj, const Vector3& point) +{ + Vector4 hpoint(matrix4_transformed_vector4(viewproj, Vector4(point, 1.0f))); + if(fabs(hpoint[0]) < fabs(hpoint[3]) + && fabs(hpoint[1]) < fabs(hpoint[3]) + && fabs(hpoint[2]) < fabs(hpoint[3])) + return true; + return false; +} + +inline bool viewproj_test_transformed_point(const Matrix4& viewproj, const Vector3& point, const Matrix4& localToWorld) +{ + return viewproj_test_point(viewproj, matrix4_transformed_point(localToWorld, point)); +} + +inline Frustum frustum_from_viewproj(const Matrix4& viewproj) +{ + return Frustum + ( + plane3_normalised(Plane3(viewproj[ 3] - viewproj[ 0], viewproj[ 7] - viewproj[ 4], viewproj[11] - viewproj[ 8], viewproj[15] - viewproj[12])), + plane3_normalised(Plane3(viewproj[ 3] + viewproj[ 0], viewproj[ 7] + viewproj[ 4], viewproj[11] + viewproj[ 8], viewproj[15] + viewproj[12])), + plane3_normalised(Plane3(viewproj[ 3] + viewproj[ 1], viewproj[ 7] + viewproj[ 5], viewproj[11] + viewproj[ 9], viewproj[15] + viewproj[13])), + plane3_normalised(Plane3(viewproj[ 3] - viewproj[ 1], viewproj[ 7] - viewproj[ 5], viewproj[11] - viewproj[ 9], viewproj[15] - viewproj[13])), + plane3_normalised(Plane3(viewproj[ 3] - viewproj[ 2], viewproj[ 7] - viewproj[ 6], viewproj[11] - viewproj[10], viewproj[15] - viewproj[14])), + plane3_normalised(Plane3(viewproj[ 3] + viewproj[ 2], viewproj[ 7] + viewproj[ 6], viewproj[11] + viewproj[10], viewproj[15] + viewproj[14])) + ); +} + +struct VolumeIntersection +{ + enum Value + { + OUTSIDE, + INSIDE, + PARTIAL + }; +}; + +typedef EnumeratedValue VolumeIntersectionValue; + +const VolumeIntersectionValue c_volumeOutside(VolumeIntersectionValue::OUTSIDE); +const VolumeIntersectionValue c_volumeInside(VolumeIntersectionValue::INSIDE); +const VolumeIntersectionValue c_volumePartial(VolumeIntersectionValue::PARTIAL); + +inline VolumeIntersectionValue frustum_test_aabb(const Frustum& frustum, const AABB& aabb) +{ + VolumeIntersectionValue result = c_volumeInside; + + switch(aabb_classify_plane(aabb, frustum.right)) + { + case 2: + return c_volumeOutside; + case 1: + result = c_volumePartial; + } + + switch(aabb_classify_plane(aabb, frustum.left)) + { + case 2: + return c_volumeOutside; + case 1: + result = c_volumePartial; + } + + switch(aabb_classify_plane(aabb, frustum.bottom)) + { + case 2: + return c_volumeOutside; + case 1: + result = c_volumePartial; + } + + switch(aabb_classify_plane(aabb, frustum.top)) + { + case 2: + return c_volumeOutside; + case 1: + result = c_volumePartial; + } + + switch(aabb_classify_plane(aabb, frustum.back)) + { + case 2: + return c_volumeOutside; + case 1: + result = c_volumePartial; + } + + switch(aabb_classify_plane(aabb, frustum.front)) + { + case 2: + return c_volumeOutside; + case 1: + result = c_volumePartial; + } + + return result; +} + +inline double plane_distance_to_point(const Plane3& plane, const Vector3& point) +{ + return vector3_dot(plane.normal(), point) + plane.d; +} + +inline double plane_distance_to_oriented_extents(const Plane3& plane, const Vector3& extents, const Matrix4& orientation) +{ + return fabs(extents[0] * vector3_dot(plane.normal(), vector4_to_vector3(orientation.x()))) + + fabs(extents[1] * vector3_dot(plane.normal(), vector4_to_vector3(orientation.y()))) + + fabs(extents[2] * vector3_dot(plane.normal(), vector4_to_vector3(orientation.z()))); +} + +/// \brief Return false if \p aabb with \p orientation is partially or completely outside \p plane. +inline bool plane_contains_oriented_aabb(const Plane3& plane, const AABB& aabb, const Matrix4& orientation) +{ + double dot = plane_distance_to_point(plane, aabb.origin); + return !(dot > 0 || -dot < plane_distance_to_oriented_extents(plane, aabb.extents, orientation)); +} + +inline VolumeIntersectionValue frustum_intersects_transformed_aabb(const Frustum& frustum, const AABB& aabb, const Matrix4& localToWorld) +{ + AABB aabb_world(aabb); + matrix4_transform_point(localToWorld, aabb_world.origin); + + if(plane_contains_oriented_aabb(frustum.right, aabb_world, localToWorld) + || plane_contains_oriented_aabb(frustum.left, aabb_world, localToWorld) + || plane_contains_oriented_aabb(frustum.bottom, aabb_world, localToWorld) + || plane_contains_oriented_aabb(frustum.top, aabb_world, localToWorld) + || plane_contains_oriented_aabb(frustum.back, aabb_world, localToWorld) + || plane_contains_oriented_aabb(frustum.front, aabb_world, localToWorld)) + return c_volumeOutside; + return c_volumeInside; +} + +inline bool plane3_test_point(const Plane3& plane, const Vector3& point) +{ + return vector3_dot(point, plane.normal()) + plane.dist() <= 0; +} + +inline bool plane3_test_line(const Plane3& plane, const Segment& segment) +{ + return segment_classify_plane(segment, plane) == 2; +} + +inline bool frustum_test_point(const Frustum& frustum, const Vector3& point) +{ + return !plane3_test_point(frustum.right, point) + && !plane3_test_point(frustum.left, point) + && !plane3_test_point(frustum.bottom, point) + && !plane3_test_point(frustum.top, point) + && !plane3_test_point(frustum.back, point) + && !plane3_test_point(frustum.front, point); +} + +inline bool frustum_test_line(const Frustum& frustum, const Segment& segment) +{ + return !plane3_test_line(frustum.right, segment) + && !plane3_test_line(frustum.left, segment) + && !plane3_test_line(frustum.bottom, segment) + && !plane3_test_line(frustum.top, segment) + && !plane3_test_line(frustum.back, segment) + && !plane3_test_line(frustum.front, segment); +} + +inline bool viewer_test_plane(const Vector4& viewer, const Plane3& plane) +{ + return ((plane.a * viewer[0]) + + (plane.b * viewer[1]) + + (plane.c * viewer[2]) + + (plane.d * viewer[3])) > 0; +} + +inline Vector3 triangle_cross(const Vector3& p0, const Vector3& p1, const Vector3& p2) +{ + return vector3_cross(vector3_subtracted(p1, p0), vector3_subtracted(p1, p2)); +} + +inline bool viewer_test_triangle(const Vector4& viewer, const Vector3& p0, const Vector3& p1, const Vector3& p2) +{ + Vector3 cross(triangle_cross(p0, p1, p2)); + return ((viewer[0] * cross[0]) + + (viewer[1] * cross[1]) + + (viewer[2] * cross[2]) + + (viewer[3] * 0)) > 0; +} + +inline Vector4 viewer_from_transformed_viewer(const Vector4& viewer, const Matrix4& transform) +{ + if(viewer[3] == 0) + { + return Vector4(matrix4_transformed_direction(transform, vector4_to_vector3(viewer)), 0); + } + else + { + return Vector4(matrix4_transformed_point(transform, vector4_to_vector3(viewer)), viewer[3]); + } +} + +inline bool viewer_test_transformed_plane(const Vector4& viewer, const Plane3& plane, const Matrix4& localToWorld) +{ +#if 0 + return viewer_test_plane(viewer_from_transformed_viewer(viewer, matrix4_affine_inverse(localToWorld)), plane); +#else + return viewer_test_plane(viewer, plane3_transformed(plane, localToWorld)); +#endif +} + +inline Vector4 viewer_from_viewproj(const Matrix4& viewproj) +{ + // get viewer pos in object coords + Vector4 viewer(matrix4_transformed_vector4(matrix4_full_inverse(viewproj), Vector4(0, 0, -1, 0))); + if(viewer[3] != 0) // non-affine matrix + { + viewer[0] /= viewer[3]; + viewer[1] /= viewer[3]; + viewer[2] /= viewer[3]; + viewer[3] /= viewer[3]; + } + return viewer; +} + +#endif diff --git a/libs/math/line.cpp b/libs/math/line.cpp new file mode 100644 index 00000000..a0bdea1a --- /dev/null +++ b/libs/math/line.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "line.h" + diff --git a/libs/math/line.h b/libs/math/line.h new file mode 100644 index 00000000..26d58369 --- /dev/null +++ b/libs/math/line.h @@ -0,0 +1,151 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MATH_LINE_H) +#define INCLUDED_MATH_LINE_H + +/// \file +/// \brief Line data types and related operations. + +#include "math/vector.h" +#include "math/plane.h" + +/// \brief A line segment defined by a start point and and end point. +class Line +{ +public: + Vector3 start, end; + + Line() + { + } + Line(const Vector3& start_, const Vector3& end_) : start(start_), end(end_) + { + } +}; + +inline Vector3 line_closest_point(const Line& line, const Vector3& point) +{ + Vector3 v = line.end - line.start; + Vector3 w = point - line.start; + + double c1 = vector3_dot(w,v); + if ( c1 <= 0 ) + return line.start; + + double c2 = vector3_dot(v,v); + if ( c2 <= c1 ) + return line.end; + + return Vector3(line.start + v * (c1 / c2)); +} + + +class Segment +{ +public: + Vector3 origin, extents; + + Segment() + { + } + Segment(const Vector3& origin_, const Vector3& extents_) : + origin(origin_), extents(extents_) + { + } +}; + + +inline Segment segment_for_startend(const Vector3& start, const Vector3& end) +{ + Segment segment; + segment.origin = vector3_mid(start, end); + segment.extents = vector3_subtracted(end, segment.origin); + return segment; +} + +inline unsigned int segment_classify_plane(const Segment& segment, const Plane3& plane) +{ + double distance_origin = vector3_dot(plane.normal(), segment.origin) + plane.dist(); + + if (fabs(distance_origin) < fabs(vector3_dot(plane.normal(), segment.extents))) + { + return 1; // partially inside + } + else if (distance_origin < 0) + { + return 2; // totally inside + } + return 0; // totally outside +} + + +class Ray +{ +public: + Vector3 origin, direction; + + Ray() + { + } + Ray(const Vector3& origin_, const Vector3& direction_) : + origin(origin_), direction(direction_) + { + } +}; + +inline Ray ray_for_points(const Vector3& origin, const Vector3& p2) +{ + return Ray(origin, vector3_normalised(vector3_subtracted(p2, origin))); +} + +inline void ray_transform(Ray& ray, const Matrix4& matrix) +{ + matrix4_transform_point(matrix, ray.origin); + matrix4_transform_direction(matrix, ray.direction); +} + +// closest-point-on-line +inline double ray_squared_distance_to_point(const Ray& ray, const Vector3& point) +{ + return vector3_length_squared( + vector3_subtracted( + point, + vector3_added( + ray.origin, + vector3_scaled( + ray.direction, + vector3_dot( + vector3_subtracted(point, ray.origin), + ray.direction + ) + ) + ) + ) + ); +} + +inline double ray_distance_to_plane(const Ray& ray, const Plane3& plane) +{ + return -(vector3_dot(plane.normal(), ray.origin) - plane.dist()) / vector3_dot(ray.direction, plane.normal()); +} + +#endif diff --git a/libs/math/matrix.cpp b/libs/math/matrix.cpp new file mode 100644 index 00000000..e3831647 --- /dev/null +++ b/libs/math/matrix.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "matrix.h" + diff --git a/libs/math/matrix.h b/libs/math/matrix.h new file mode 100644 index 00000000..b28c2688 --- /dev/null +++ b/libs/math/matrix.h @@ -0,0 +1,1322 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MATH_MATRIX_H) +#define INCLUDED_MATH_MATRIX_H + +/// \file +/// \brief Matrix data types and related operations. + +#include "math/vector.h" + +/// \brief A 4x4 matrix stored in single-precision floating-point. +class Matrix4 +{ + float m_elements[16]; +public: + + Matrix4() + { + } + Matrix4(float xx_, float xy_, float xz_, float xw_, + float yx_, float yy_, float yz_, float yw_, + float zx_, float zy_, float zz_, float zw_, + float tx_, float ty_, float tz_, float tw_) + { + xx() = xx_; + xy() = xy_; + xz() = xz_; + xw() = xw_; + yx() = yx_; + yy() = yy_; + yz() = yz_; + yw() = yw_; + zx() = zx_; + zy() = zy_; + zz() = zz_; + zw() = zw_; + tx() = tx_; + ty() = ty_; + tz() = tz_; + tw() = tw_; + } + + float& xx() + { + return m_elements[0]; + } + const float& xx() const + { + return m_elements[0]; + } + float& xy() + { + return m_elements[1]; + } + const float& xy() const + { + return m_elements[1]; + } + float& xz() + { + return m_elements[2]; + } + const float& xz() const + { + return m_elements[2]; + } + float& xw() + { + return m_elements[3]; + } + const float& xw() const + { + return m_elements[3]; + } + float& yx() + { + return m_elements[4]; + } + const float& yx() const + { + return m_elements[4]; + } + float& yy() + { + return m_elements[5]; + } + const float& yy() const + { + return m_elements[5]; + } + float& yz() + { + return m_elements[6]; + } + const float& yz() const + { + return m_elements[6]; + } + float& yw() + { + return m_elements[7]; + } + const float& yw() const + { + return m_elements[7]; + } + float& zx() + { + return m_elements[8]; + } + const float& zx() const + { + return m_elements[8]; + } + float& zy() + { + return m_elements[9]; + } + const float& zy() const + { + return m_elements[9]; + } + float& zz() + { + return m_elements[10]; + } + const float& zz() const + { + return m_elements[10]; + } + float& zw() + { + return m_elements[11]; + } + const float& zw() const + { + return m_elements[11]; + } + float& tx() + { + return m_elements[12]; + } + const float& tx() const + { + return m_elements[12]; + } + float& ty() + { + return m_elements[13]; + } + const float& ty() const + { + return m_elements[13]; + } + float& tz() + { + return m_elements[14]; + } + const float& tz() const + { + return m_elements[14]; + } + float& tw() + { + return m_elements[15]; + } + const float& tw() const + { + return m_elements[15]; + } + + Vector4& x() + { + return reinterpret_cast(xx()); + } + const Vector4& x() const + { + return reinterpret_cast(xx()); + } + Vector4& y() + { + return reinterpret_cast(yx()); + } + const Vector4& y() const + { + return reinterpret_cast(yx()); + } + Vector4& z() + { + return reinterpret_cast(zx()); + } + const Vector4& z() const + { + return reinterpret_cast(zx()); + } + Vector4& t() + { + return reinterpret_cast(tx()); + } + const Vector4& t() const + { + return reinterpret_cast(tx()); + } + + const float& index(std::size_t i) const + { + return m_elements[i]; + } + float& index(std::size_t i) + { + return m_elements[i]; + } + const float& operator[](std::size_t i) const + { + return m_elements[i]; + } + float& operator[](std::size_t i) + { + return m_elements[i]; + } + const float& index(std::size_t r, std::size_t c) const + { + return m_elements[(r << 2) + c]; + } + float& index(std::size_t r, std::size_t c) + { + return m_elements[(r << 2) + c]; + } +}; + +/// \brief The 4x4 identity matrix. +const Matrix4 g_matrix4_identity( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 +); + + +/// \brief Returns true if \p self and \p other are exactly element-wise equal. +inline bool operator==(const Matrix4& self, const Matrix4& other) +{ + return self.xx() == other.xx() && self.xy() == other.xy() && self.xz() == other.xz() && self.xw() == other.xw() + && self.yx() == other.yx() && self.yy() == other.yy() && self.yz() == other.yz() && self.yw() == other.yw() + && self.zx() == other.zx() && self.zy() == other.zy() && self.zz() == other.zz() && self.zw() == other.zw() + && self.tx() == other.tx() && self.ty() == other.ty() && self.tz() == other.tz() && self.tw() == other.tw(); +} + +/// \brief Returns true if \p self and \p other are exactly element-wise equal. +inline bool matrix4_equal(const Matrix4& self, const Matrix4& other) +{ + return self == other; +} + +/// \brief Returns true if \p self and \p other are element-wise equal within \p epsilon. +inline bool matrix4_equal_epsilon(const Matrix4& self, const Matrix4& other, float epsilon) +{ + return float_equal_epsilon(self.xx(), other.xx(), epsilon) + && float_equal_epsilon(self.xy(), other.xy(), epsilon) + && float_equal_epsilon(self.xz(), other.xz(), epsilon) + && float_equal_epsilon(self.xw(), other.xw(), epsilon) + && float_equal_epsilon(self.yx(), other.yx(), epsilon) + && float_equal_epsilon(self.yy(), other.yy(), epsilon) + && float_equal_epsilon(self.yz(), other.yz(), epsilon) + && float_equal_epsilon(self.yw(), other.yw(), epsilon) + && float_equal_epsilon(self.zx(), other.zx(), epsilon) + && float_equal_epsilon(self.zy(), other.zy(), epsilon) + && float_equal_epsilon(self.zz(), other.zz(), epsilon) + && float_equal_epsilon(self.zw(), other.zw(), epsilon) + && float_equal_epsilon(self.tx(), other.tx(), epsilon) + && float_equal_epsilon(self.ty(), other.ty(), epsilon) + && float_equal_epsilon(self.tz(), other.tz(), epsilon) + && float_equal_epsilon(self.tw(), other.tw(), epsilon); +} + +/// \brief Returns true if \p self and \p other are exactly element-wise equal. +/// \p self and \p other must be affine. +inline bool matrix4_affine_equal(const Matrix4& self, const Matrix4& other) +{ + return self[0] == other[0] + && self[1] == other[1] + && self[2] == other[2] + && self[4] == other[4] + && self[5] == other[5] + && self[6] == other[6] + && self[8] == other[8] + && self[9] == other[9] + && self[10] == other[10] + && self[12] == other[12] + && self[13] == other[13] + && self[14] == other[14]; +} + +enum Matrix4Handedness +{ + MATRIX4_RIGHTHANDED = 0, + MATRIX4_LEFTHANDED = 1, +}; + +/// \brief Returns MATRIX4_RIGHTHANDED if \p self is right-handed, else returns MATRIX4_LEFTHANDED. +inline Matrix4Handedness matrix4_handedness(const Matrix4& self) +{ + return ( + vector3_dot( + vector3_cross(vector4_to_vector3(self.x()), vector4_to_vector3(self.y())), + vector4_to_vector3(self.z()) + ) + < 0.0 + ) ? MATRIX4_LEFTHANDED : MATRIX4_RIGHTHANDED; +} + + + + + +/// \brief Returns \p self post-multiplied by \p other. +inline Matrix4 matrix4_multiplied_by_matrix4(const Matrix4& self, const Matrix4& other) +{ + return Matrix4( + other[0] * self[0] + other[1] * self[4] + other[2] * self[8] + other[3] * self[12], + other[0] * self[1] + other[1] * self[5] + other[2] * self[9] + other[3] * self[13], + other[0] * self[2] + other[1] * self[6] + other[2] * self[10]+ other[3] * self[14], + other[0] * self[3] + other[1] * self[7] + other[2] * self[11]+ other[3] * self[15], + other[4] * self[0] + other[5] * self[4] + other[6] * self[8] + other[7] * self[12], + other[4] * self[1] + other[5] * self[5] + other[6] * self[9] + other[7] * self[13], + other[4] * self[2] + other[5] * self[6] + other[6] * self[10]+ other[7] * self[14], + other[4] * self[3] + other[5] * self[7] + other[6] * self[11]+ other[7] * self[15], + other[8] * self[0] + other[9] * self[4] + other[10]* self[8] + other[11]* self[12], + other[8] * self[1] + other[9] * self[5] + other[10]* self[9] + other[11]* self[13], + other[8] * self[2] + other[9] * self[6] + other[10]* self[10]+ other[11]* self[14], + other[8] * self[3] + other[9] * self[7] + other[10]* self[11]+ other[11]* self[15], + other[12]* self[0] + other[13]* self[4] + other[14]* self[8] + other[15]* self[12], + other[12]* self[1] + other[13]* self[5] + other[14]* self[9] + other[15]* self[13], + other[12]* self[2] + other[13]* self[6] + other[14]* self[10]+ other[15]* self[14], + other[12]* self[3] + other[13]* self[7] + other[14]* self[11]+ other[15]* self[15] + ); +} + +/// \brief Post-multiplies \p self by \p other in-place. +inline void matrix4_multiply_by_matrix4(Matrix4& self, const Matrix4& other) +{ + self = matrix4_multiplied_by_matrix4(self, other); +} + + +/// \brief Returns \p self pre-multiplied by \p other. +inline Matrix4 matrix4_premultiplied_by_matrix4(const Matrix4& self, const Matrix4& other) +{ +#if 1 + return matrix4_multiplied_by_matrix4(other, self); +#else + return Matrix4( + self[0] * other[0] + self[1] * other[4] + self[2] * other[8] + self[3] * other[12], + self[0] * other[1] + self[1] * other[5] + self[2] * other[9] + self[3] * other[13], + self[0] * other[2] + self[1] * other[6] + self[2] * other[10]+ self[3] * other[14], + self[0] * other[3] + self[1] * other[7] + self[2] * other[11]+ self[3] * other[15], + self[4] * other[0] + self[5] * other[4] + self[6] * other[8] + self[7] * other[12], + self[4] * other[1] + self[5] * other[5] + self[6] * other[9] + self[7] * other[13], + self[4] * other[2] + self[5] * other[6] + self[6] * other[10]+ self[7] * other[14], + self[4] * other[3] + self[5] * other[7] + self[6] * other[11]+ self[7] * other[15], + self[8] * other[0] + self[9] * other[4] + self[10]* other[8] + self[11]* other[12], + self[8] * other[1] + self[9] * other[5] + self[10]* other[9] + self[11]* other[13], + self[8] * other[2] + self[9] * other[6] + self[10]* other[10]+ self[11]* other[14], + self[8] * other[3] + self[9] * other[7] + self[10]* other[11]+ self[11]* other[15], + self[12]* other[0] + self[13]* other[4] + self[14]* other[8] + self[15]* other[12], + self[12]* other[1] + self[13]* other[5] + self[14]* other[9] + self[15]* other[13], + self[12]* other[2] + self[13]* other[6] + self[14]* other[10]+ self[15]* other[14], + self[12]* other[3] + self[13]* other[7] + self[14]* other[11]+ self[15]* other[15] + ); +#endif +} + +/// \brief Pre-multiplies \p self by \p other in-place. +inline void matrix4_premultiply_by_matrix4(Matrix4& self, const Matrix4& other) +{ + self = matrix4_premultiplied_by_matrix4(self, other); +} + +/// \brief returns true if \p transform is affine. +inline bool matrix4_is_affine(const Matrix4& transform) +{ + return transform[3] == 0 && transform[7] == 0 && transform[11] == 0 && transform[15] == 1; +} + +/// \brief Returns \p self post-multiplied by \p other. +/// \p self and \p other must be affine. +inline Matrix4 matrix4_affine_multiplied_by_matrix4(const Matrix4& self, const Matrix4& other) +{ + return Matrix4( + other[0] * self[0] + other[1] * self[4] + other[2] * self[8], + other[0] * self[1] + other[1] * self[5] + other[2] * self[9], + other[0] * self[2] + other[1] * self[6] + other[2] * self[10], + 0, + other[4] * self[0] + other[5] * self[4] + other[6] * self[8], + other[4] * self[1] + other[5] * self[5] + other[6] * self[9], + other[4] * self[2] + other[5] * self[6] + other[6] * self[10], + 0, + other[8] * self[0] + other[9] * self[4] + other[10]* self[8], + other[8] * self[1] + other[9] * self[5] + other[10]* self[9], + other[8] * self[2] + other[9] * self[6] + other[10]* self[10], + 0, + other[12]* self[0] + other[13]* self[4] + other[14]* self[8] + self[12], + other[12]* self[1] + other[13]* self[5] + other[14]* self[9] + self[13], + other[12]* self[2] + other[13]* self[6] + other[14]* self[10]+ self[14], + 1 + ); +} + +/// \brief Post-multiplies \p self by \p other in-place. +/// \p self and \p other must be affine. +inline void matrix4_affine_multiply_by_matrix4(Matrix4& self, const Matrix4& other) +{ + self = matrix4_affine_multiplied_by_matrix4(self, other); +} + +/// \brief Returns \p self pre-multiplied by \p other. +/// \p self and \p other must be affine. +inline Matrix4 matrix4_affine_premultiplied_by_matrix4(const Matrix4& self, const Matrix4& other) +{ +#if 1 + return matrix4_affine_multiplied_by_matrix4(other, self); +#else + return Matrix4( + self[0] * other[0] + self[1] * other[4] + self[2] * other[8], + self[0] * other[1] + self[1] * other[5] + self[2] * other[9], + self[0] * other[2] + self[1] * other[6] + self[2] * other[10], + 0, + self[4] * other[0] + self[5] * other[4] + self[6] * other[8], + self[4] * other[1] + self[5] * other[5] + self[6] * other[9], + self[4] * other[2] + self[5] * other[6] + self[6] * other[10], + 0, + self[8] * other[0] + self[9] * other[4] + self[10]* other[8], + self[8] * other[1] + self[9] * other[5] + self[10]* other[9], + self[8] * other[2] + self[9] * other[6] + self[10]* other[10], + 0, + self[12]* other[0] + self[13]* other[4] + self[14]* other[8] + other[12], + self[12]* other[1] + self[13]* other[5] + self[14]* other[9] + other[13], + self[12]* other[2] + self[13]* other[6] + self[14]* other[10]+ other[14], + 1 + ) + ); +#endif +} + +/// \brief Pre-multiplies \p self by \p other in-place. +/// \p self and \p other must be affine. +inline void matrix4_affine_premultiply_by_matrix4(Matrix4& self, const Matrix4& other) +{ + self = matrix4_affine_premultiplied_by_matrix4(self, other); +} + +/// \brief Returns \p point transformed by \p self. +template +inline BasicVector3 matrix4_transformed_point(const Matrix4& self, const BasicVector3& point) +{ + return BasicVector3( + static_cast(self[0] * point[0] + self[4] * point[1] + self[8] * point[2] + self[12]), + static_cast(self[1] * point[0] + self[5] * point[1] + self[9] * point[2] + self[13]), + static_cast(self[2] * point[0] + self[6] * point[1] + self[10] * point[2] + self[14]) + ); +} + +/// \brief Transforms \p point by \p self in-place. +template +inline void matrix4_transform_point(const Matrix4& self, BasicVector3& point) +{ + point = matrix4_transformed_point(self, point); +} + +/// \brief Returns \p direction transformed by \p self. +template +inline BasicVector3 matrix4_transformed_direction(const Matrix4& self, const BasicVector3& direction) +{ + return BasicVector3( + static_cast(self[0] * direction[0] + self[4] * direction[1] + self[8] * direction[2]), + static_cast(self[1] * direction[0] + self[5] * direction[1] + self[9] * direction[2]), + static_cast(self[2] * direction[0] + self[6] * direction[1] + self[10] * direction[2]) + ); +} + +/// \brief Transforms \p direction by \p self in-place. +template +inline void matrix4_transform_direction(const Matrix4& self, BasicVector3& normal) +{ + normal = matrix4_transformed_direction(self, normal); +} + +/// \brief Returns \p vector4 transformed by \p self. +inline Vector4 matrix4_transformed_vector4(const Matrix4& self, const Vector4& vector4) +{ + return Vector4( + self[0] * vector4[0] + self[4] * vector4[1] + self[8] * vector4[2] + self[12] * vector4[3], + self[1] * vector4[0] + self[5] * vector4[1] + self[9] * vector4[2] + self[13] * vector4[3], + self[2] * vector4[0] + self[6] * vector4[1] + self[10] * vector4[2] + self[14] * vector4[3], + self[3] * vector4[0] + self[7] * vector4[1] + self[11] * vector4[2] + self[15] * vector4[3] + ); +} + +/// \brief Transforms \p vector4 by \p self in-place. +inline void matrix4_transform_vector4(const Matrix4& self, Vector4& vector4) +{ + vector4 = matrix4_transformed_vector4(self, vector4); +} + + +/// \brief Transposes \p self in-place. +inline void matrix4_transpose(Matrix4& self) +{ + std::swap(self.xy(), self.yx()); + std::swap(self.xz(), self.zx()); + std::swap(self.xw(), self.tx()); + std::swap(self.yz(), self.zy()); + std::swap(self.yw(), self.ty()); + std::swap(self.zw(), self.tz()); +} + +/// \brief Returns \p self transposed. +inline Matrix4 matrix4_transposed(const Matrix4& self) +{ + return Matrix4( + self.xx(), + self.yx(), + self.zx(), + self.tx(), + self.xy(), + self.yy(), + self.zy(), + self.ty(), + self.xz(), + self.yz(), + self.zz(), + self.tz(), + self.xw(), + self.yw(), + self.zw(), + self.tw() + ); +} + + +/// \brief Inverts an affine transform in-place. +/// Adapted from Graphics Gems 2. +inline Matrix4 matrix4_affine_inverse(const Matrix4& self) +{ + Matrix4 result; + + // determinant of rotation submatrix + double det + = self[0] * ( self[5]*self[10] - self[9]*self[6] ) + - self[1] * ( self[4]*self[10] - self[8]*self[6] ) + + self[2] * ( self[4]*self[9] - self[8]*self[5] ); + + // throw exception here if (det*det < 1e-25) + + // invert rotation submatrix + det = 1.0 / det; + + result[0] = static_cast( (self[5]*self[10]- self[6]*self[9] )*det); + result[1] = static_cast(- (self[1]*self[10]- self[2]*self[9] )*det); + result[2] = static_cast( (self[1]*self[6] - self[2]*self[5] )*det); + result[3] = 0; + result[4] = static_cast(- (self[4]*self[10]- self[6]*self[8] )*det); + result[5] = static_cast( (self[0]*self[10]- self[2]*self[8] )*det); + result[6] = static_cast(- (self[0]*self[6] - self[2]*self[4] )*det); + result[7] = 0; + result[8] = static_cast( (self[4]*self[9] - self[5]*self[8] )*det); + result[9] = static_cast(- (self[0]*self[9] - self[1]*self[8] )*det); + result[10]= static_cast( (self[0]*self[5] - self[1]*self[4] )*det); + result[11] = 0; + + // multiply translation part by rotation + result[12] = - (self[12] * result[0] + + self[13] * result[4] + + self[14] * result[8]); + result[13] = - (self[12] * result[1] + + self[13] * result[5] + + self[14] * result[9]); + result[14] = - (self[12] * result[2] + + self[13] * result[6] + + self[14] * result[10]); + result[15] = 1; + + return result; +} + +inline void matrix4_affine_invert(Matrix4& self) +{ + self = matrix4_affine_inverse(self); +} + +/// \brief A compile-time-constant integer. +template +struct IntegralConstant +{ + enum unnamed_{ VALUE = VALUE_ }; +}; + +/// \brief A compile-time-constant row/column index into a 4x4 matrix. +template +class Matrix4Index +{ +public: + typedef IntegralConstant r; + typedef IntegralConstant c; + typedef IntegralConstant<(r::VALUE * 4) + c::VALUE> i; +}; + +/// \brief A functor which returns the cofactor of a 3x3 submatrix obtained by ignoring a given row and column of a 4x4 matrix. +/// \param Row Defines the compile-time-constant integers x, y and z with values corresponding to the indices of the three rows to use. +/// \param Col Defines the compile-time-constant integers x, y and z with values corresponding to the indices of the three columns to use. +template +class Matrix4Cofactor +{ +public: + typedef typename Matrix4Index::i xx; + typedef typename Matrix4Index::i xy; + typedef typename Matrix4Index::i xz; + typedef typename Matrix4Index::i yx; + typedef typename Matrix4Index::i yy; + typedef typename Matrix4Index::i yz; + typedef typename Matrix4Index::i zx; + typedef typename Matrix4Index::i zy; + typedef typename Matrix4Index::i zz; + static double apply(const Matrix4& self) + { + return self[xx::VALUE] * ( self[yy::VALUE]*self[zz::VALUE] - self[zy::VALUE]*self[yz::VALUE] ) + - self[xy::VALUE] * ( self[yx::VALUE]*self[zz::VALUE] - self[zx::VALUE]*self[yz::VALUE] ) + + self[xz::VALUE] * ( self[yx::VALUE]*self[zy::VALUE] - self[zx::VALUE]*self[yy::VALUE] ); + } +}; + +/// \brief The cofactor element indices for a 4x4 matrix row or column. +/// \param Element The index of the element to ignore. +template +class Cofactor4 +{ +public: + typedef IntegralConstant<(Element <= 0) ? 1 : 0> x; + typedef IntegralConstant<(Element <= 1) ? 2 : 1> y; + typedef IntegralConstant<(Element <= 2) ? 3 : 2> z; +}; + +/// \brief Returns the determinant of \p self. +inline double matrix4_determinant(const Matrix4& self) +{ + return self.xx() * Matrix4Cofactor< Cofactor4<0>, Cofactor4<0> >::apply(self) + - self.xy() * Matrix4Cofactor< Cofactor4<0>, Cofactor4<1> >::apply(self) + + self.xz() * Matrix4Cofactor< Cofactor4<0>, Cofactor4<2> >::apply(self) + - self.xw() * Matrix4Cofactor< Cofactor4<0>, Cofactor4<3> >::apply(self); +} + +/// \brief Returns the inverse of \p self using the Adjoint method. +/// \todo Throw an exception if the determinant is zero. +inline Matrix4 matrix4_full_inverse(const Matrix4& self) +{ + double determinant = 1.0 / matrix4_determinant(self); + + return Matrix4( + static_cast( Matrix4Cofactor< Cofactor4<0>, Cofactor4<0> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<1>, Cofactor4<0> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<2>, Cofactor4<0> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<3>, Cofactor4<0> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<0>, Cofactor4<1> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<1>, Cofactor4<1> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<2>, Cofactor4<1> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<3>, Cofactor4<1> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<0>, Cofactor4<2> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<1>, Cofactor4<2> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<2>, Cofactor4<2> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<3>, Cofactor4<2> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<0>, Cofactor4<3> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<1>, Cofactor4<3> >::apply(self) * determinant), + static_cast(-Matrix4Cofactor< Cofactor4<2>, Cofactor4<3> >::apply(self) * determinant), + static_cast( Matrix4Cofactor< Cofactor4<3>, Cofactor4<3> >::apply(self) * determinant) + ); +} + +/// \brief Inverts \p self in-place using the Adjoint method. +inline void matrix4_full_invert(Matrix4& self) +{ + self = matrix4_full_inverse(self); +} + + +/// \brief Constructs a pure-translation matrix from \p translation. +inline Matrix4 matrix4_translation_for_vec3(const Vector3& translation) +{ + return Matrix4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + translation[0], translation[1], translation[2], 1 + ); +} + +/// \brief Returns the translation part of \p self. +inline Vector3 matrix4_get_translation_vec3(const Matrix4& self) +{ + return vector4_to_vector3(self.t()); +} + +/// \brief Concatenates \p self with \p translation. +/// The concatenated \p translation occurs before \p self. +inline void matrix4_translate_by_vec3(Matrix4& self, const Vector3& translation) +{ + matrix4_multiply_by_matrix4(self, matrix4_translation_for_vec3(translation)); +} + +/// \brief Returns \p self Concatenated with \p translation. +/// The concatenated translation occurs before \p self. +inline Matrix4 matrix4_translated_by_vec3(const Matrix4& self, const Vector3& translation) +{ + return matrix4_multiplied_by_matrix4(self, matrix4_translation_for_vec3(translation)); +} + + +#include "math/pi.h" + +/// \brief Returns \p angle modulated by the range [0, 360). +/// \p angle must be in the range [-360, 360). +inline float angle_modulate_degrees_range(float angle) +{ + return static_cast(float_mod_range(angle, 360.0)); +} + +/// \brief Returns \p euler angles converted from radians to degrees. +inline Vector3 euler_radians_to_degrees(const Vector3& euler) +{ + return Vector3( + static_cast(radians_to_degrees(euler.x())), + static_cast(radians_to_degrees(euler.y())), + static_cast(radians_to_degrees(euler.z())) + ); +} + +/// \brief Returns \p euler angles converted from degrees to radians. +inline Vector3 euler_degrees_to_radians(const Vector3& euler) +{ + return Vector3( + static_cast(degrees_to_radians(euler.x())), + static_cast(degrees_to_radians(euler.y())), + static_cast(degrees_to_radians(euler.z())) + ); +} + + + +/// \brief Constructs a pure-rotation matrix about the x axis from sin \p s and cosine \p c of an angle. +inline Matrix4 matrix4_rotation_for_sincos_x(float s, float c) +{ + return Matrix4( + 1, 0, 0, 0, + 0, c, s, 0, + 0,-s, c, 0, + 0, 0, 0, 1 + ); +} + +/// \brief Constructs a pure-rotation matrix about the x axis from an angle in radians. +inline Matrix4 matrix4_rotation_for_x(double x) +{ + return matrix4_rotation_for_sincos_x(static_cast(sin(x)), static_cast(cos(x))); +} + +/// \brief Constructs a pure-rotation matrix about the x axis from an angle in degrees. +inline Matrix4 matrix4_rotation_for_x_degrees(float x) +{ + return matrix4_rotation_for_x(degrees_to_radians(x)); +} + +/// \brief Constructs a pure-rotation matrix about the y axis from sin \p s and cosine \p c of an angle. +inline Matrix4 matrix4_rotation_for_sincos_y(float s, float c) +{ + return Matrix4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1 + ); +} + +/// \brief Constructs a pure-rotation matrix about the y axis from an angle in radians. +inline Matrix4 matrix4_rotation_for_y(double y) +{ + return matrix4_rotation_for_sincos_y(static_cast(sin(y)), static_cast(cos(y))); +} + +/// \brief Constructs a pure-rotation matrix about the y axis from an angle in degrees. +inline Matrix4 matrix4_rotation_for_y_degrees(float y) +{ + return matrix4_rotation_for_y(degrees_to_radians(y)); +} + +/// \brief Constructs a pure-rotation matrix about the z axis from sin \p s and cosine \p c of an angle. +inline Matrix4 matrix4_rotation_for_sincos_z(float s, float c) +{ + return Matrix4( + c, s, 0, 0, + -s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ); +} + +/// \brief Constructs a pure-rotation matrix about the z axis from an angle in radians. +inline Matrix4 matrix4_rotation_for_z(double z) +{ + return matrix4_rotation_for_sincos_z(static_cast(sin(z)), static_cast(cos(z))); +} + +/// \brief Constructs a pure-rotation matrix about the z axis from an angle in degrees. +inline Matrix4 matrix4_rotation_for_z_degrees(float z) +{ + return matrix4_rotation_for_z(degrees_to_radians(z)); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (radians) in the order (x, y, z). +/*! \verbatim +clockwise rotation around X, Y, Z, facing along axis + 1 0 0 cy 0 -sy cz sz 0 + 0 cx sx 0 1 0 -sz cz 0 + 0 -sx cx sy 0 cy 0 0 1 + +rows of Z by cols of Y + cy*cz -sy*cz+sz -sy*sz+cz +-sz*cy -sz*sy+cz + + .. or something like that.. + +final rotation is Z * Y * X + cy*cz -sx*-sy*cz+cx*sz cx*-sy*sz+sx*cz +-cy*sz sx*sy*sz+cx*cz -cx*-sy*sz+sx*cz + sy -sx*cy cx*cy + +transposed +cy.cz + 0.sz + sy.0 cy.-sz + 0 .cz + sy.0 cy.0 + 0 .0 + sy.1 | +sx.sy.cz + cx.sz + -sx.cy.0 sx.sy.-sz + cx.cz + -sx.cy.0 sx.sy.0 + cx.0 + -sx.cy.1 | +-cx.sy.cz + sx.sz + cx.cy.0 -cx.sy.-sz + sx.cz + cx.cy.0 -cx.sy.0 + 0 .0 + cx.cy.1 | +\endverbatim */ +inline Matrix4 matrix4_rotation_for_euler_xyz(const Vector3& euler) +{ +#if 1 + + double cx = cos(euler[0]); + double sx = sin(euler[0]); + double cy = cos(euler[1]); + double sy = sin(euler[1]); + double cz = cos(euler[2]); + double sz = sin(euler[2]); + + return Matrix4( + static_cast(cy*cz), + static_cast(cy*sz), + static_cast(-sy), + 0, + static_cast(sx*sy*cz + cx*-sz), + static_cast(sx*sy*sz + cx*cz), + static_cast(sx*cy), + 0, + static_cast(cx*sy*cz + sx*sz), + static_cast(cx*sy*sz + -sx*cz), + static_cast(cx*cy), + 0, + 0, + 0, + 0, + 1 + ); + +#else + + return matrix4_premultiply_by_matrix4( + matrix4_premultiply_by_matrix4( + matrix4_rotation_for_x(euler[0]), + matrix4_rotation_for_y(euler[1]) + ), + matrix4_rotation_for_z(euler[2]) + ); + +#endif +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (degrees) in the order (x, y, z). +inline Matrix4 matrix4_rotation_for_euler_xyz_degrees(const Vector3& euler) +{ + return matrix4_rotation_for_euler_xyz(euler_degrees_to_radians(euler)); +} + +/// \brief Concatenates \p self with the rotation transform produced by \p euler angles (degrees) in the order (x, y, z). +/// The concatenated rotation occurs before \p self. +inline void matrix4_rotate_by_euler_xyz_degrees(Matrix4& self, const Vector3& euler) +{ + matrix4_multiply_by_matrix4(self, matrix4_rotation_for_euler_xyz_degrees(euler)); +} + + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (radians) in the order (y, z, x). +inline Matrix4 matrix4_rotation_for_euler_yzx(const Vector3& euler) +{ + return matrix4_premultiplied_by_matrix4( + matrix4_premultiplied_by_matrix4( + matrix4_rotation_for_y(euler[1]), + matrix4_rotation_for_z(euler[2]) + ), + matrix4_rotation_for_x(euler[0]) + ); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (degrees) in the order (y, z, x). +inline Matrix4 matrix4_rotation_for_euler_yzx_degrees(const Vector3& euler) +{ + return matrix4_rotation_for_euler_yzx(euler_degrees_to_radians(euler)); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (radians) in the order (x, z, y). +inline Matrix4 matrix4_rotation_for_euler_xzy(const Vector3& euler) +{ + return matrix4_premultiplied_by_matrix4( + matrix4_premultiplied_by_matrix4( + matrix4_rotation_for_x(euler[0]), + matrix4_rotation_for_z(euler[2]) + ), + matrix4_rotation_for_y(euler[1]) + ); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (degrees) in the order (x, z, y). +inline Matrix4 matrix4_rotation_for_euler_xzy_degrees(const Vector3& euler) +{ + return matrix4_rotation_for_euler_xzy(euler_degrees_to_radians(euler)); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (radians) in the order (y, x, z). +/*! \verbatim +| cy.cz + sx.sy.-sz + -cx.sy.0 0.cz + cx.-sz + sx.0 sy.cz + -sx.cy.-sz + cx.cy.0 | +| cy.sz + sx.sy.cz + -cx.sy.0 0.sz + cx.cz + sx.0 sy.sz + -sx.cy.cz + cx.cy.0 | +| cy.0 + sx.sy.0 + -cx.sy.1 0.0 + cx.0 + sx.1 sy.0 + -sx.cy.0 + cx.cy.1 | +\endverbatim */ +inline Matrix4 matrix4_rotation_for_euler_yxz(const Vector3& euler) +{ +#if 1 + + double cx = cos(euler[0]); + double sx = sin(euler[0]); + double cy = cos(euler[1]); + double sy = sin(euler[1]); + double cz = cos(euler[2]); + double sz = sin(euler[2]); + + return Matrix4( + static_cast(cy*cz + sx*sy*-sz), + static_cast(cy*sz + sx*sy*cz), + static_cast(-cx*sy), + 0, + static_cast(cx*-sz), + static_cast(cx*cz), + static_cast(sx), + 0, + static_cast(sy*cz + -sx*cy*-sz), + static_cast(sy*sz + -sx*cy*cz), + static_cast(cx*cy), + 0, + 0, + 0, + 0, + 1 + ); + +#else + + return matrix4_premultiply_by_matrix4( + matrix4_premultiply_by_matrix4( + matrix4_rotation_for_y(euler[1]), + matrix4_rotation_for_x(euler[0]) + ), + matrix4_rotation_for_z(euler[2]) + ); + +#endif +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (degrees) in the order (y, x, z). +inline Matrix4 matrix4_rotation_for_euler_yxz_degrees(const Vector3& euler) +{ + return matrix4_rotation_for_euler_yxz(euler_degrees_to_radians(euler)); +} + +/// \brief Returns \p self concatenated with the rotation transform produced by \p euler angles (degrees) in the order (y, x, z). +/// The concatenated rotation occurs before \p self. +inline Matrix4 matrix4_rotated_by_euler_yxz_degrees(const Matrix4& self, const Vector3& euler) +{ + return matrix4_multiplied_by_matrix4(self, matrix4_rotation_for_euler_yxz_degrees(euler)); +} + +/// \brief Concatenates \p self with the rotation transform produced by \p euler angles (degrees) in the order (y, x, z). +/// The concatenated rotation occurs before \p self. +inline void matrix4_rotate_by_euler_yxz_degrees(Matrix4& self, const Vector3& euler) +{ + self = matrix4_rotated_by_euler_yxz_degrees(self, euler); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (radians) in the order (z, x, y). +inline Matrix4 matrix4_rotation_for_euler_zxy(const Vector3& euler) +{ +#if 1 + return matrix4_premultiplied_by_matrix4( + matrix4_premultiplied_by_matrix4( + matrix4_rotation_for_z(euler[2]), + matrix4_rotation_for_x(euler[0]) + ), + matrix4_rotation_for_y(euler[1]) + ); +#else + double cx = cos(euler[0]); + double sx = sin(euler[0]); + double cy = cos(euler[1]); + double sy = sin(euler[1]); + double cz = cos(euler[2]); + double sz = sin(euler[2]); + + return Matrix4( + static_cast(cz * cy + sz * sx * sy), + static_cast(sz * cx), + static_cast(cz * -sy + sz * sx * cy), + 0, + static_cast(-sz * cy + cz * sx * sy), + static_cast(cz * cx), + static_cast(-sz * -sy + cz * cx * cy), + 0, + static_cast(cx* sy), + static_cast(-sx), + static_cast(cx* cy), + 0, + 0, + 0, + 0, + 1 + ); +#endif +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (degres=es) in the order (z, x, y). +inline Matrix4 matrix4_rotation_for_euler_zxy_degrees(const Vector3& euler) +{ + return matrix4_rotation_for_euler_zxy(euler_degrees_to_radians(euler)); +} + +/// \brief Returns \p self concatenated with the rotation transform produced by \p euler angles (degrees) in the order (z, x, y). +/// The concatenated rotation occurs before \p self. +inline Matrix4 matrix4_rotated_by_euler_zxy_degrees(const Matrix4& self, const Vector3& euler) +{ + return matrix4_multiplied_by_matrix4(self, matrix4_rotation_for_euler_zxy_degrees(euler)); +} + +/// \brief Concatenates \p self with the rotation transform produced by \p euler angles (degrees) in the order (z, x, y). +/// The concatenated rotation occurs before \p self. +inline void matrix4_rotate_by_euler_zxy_degrees(Matrix4& self, const Vector3& euler) +{ + self = matrix4_rotated_by_euler_zxy_degrees(self, euler); +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (radians) in the order (z, y, x). +inline Matrix4 matrix4_rotation_for_euler_zyx(const Vector3& euler) +{ +#if 1 + + double cx = cos(euler[0]); + double sx = sin(euler[0]); + double cy = cos(euler[1]); + double sy = sin(euler[1]); + double cz = cos(euler[2]); + double sz = sin(euler[2]); + + return Matrix4( + static_cast(cy*cz), + static_cast(sx*sy*cz + cx*sz), + static_cast(cx*-sy*cz + sx*sz), + 0, + static_cast(cy*-sz), + static_cast(sx*sy*-sz + cx*cz), + static_cast(cx*-sy*-sz + sx*cz), + 0, + static_cast(sy), + static_cast(-sx*cy), + static_cast(cx*cy), + 0, + 0, + 0, + 0, + 1 + ); + +#else + + return matrix4_premultiply_by_matrix4( + matrix4_premultiply_by_matrix4( + matrix4_rotation_for_z(euler[2]), + matrix4_rotation_for_y(euler[1]) + ), + matrix4_rotation_for_x(euler[0]) + ); + +#endif +} + +/// \brief Constructs a pure-rotation matrix from a set of euler angles (degrees) in the order (z, y, x). +inline Matrix4 matrix4_rotation_for_euler_zyx_degrees(const Vector3& euler) +{ + return matrix4_rotation_for_euler_zyx(euler_degrees_to_radians(euler)); +} + + +/// \brief Calculates and returns a set of euler angles that produce the rotation component of \p self when applied in the order (x, y, z). +/// \p self must be affine and orthonormal (unscaled) to produce a meaningful result. +inline Vector3 matrix4_get_rotation_euler_xyz(const Matrix4& self) +{ + double a = asin(-self[2]); + double ca = cos(a); + + if (fabs(ca) > 0.005) // Gimbal lock? + { + return Vector3( + static_cast(atan2(self[6] / ca, self[10] / ca)), + static_cast(a), + static_cast(atan2(self[1] / ca, self[0]/ ca)) + ); + } + else // Gimbal lock has occurred + { + return Vector3( + static_cast(atan2(-self[9], self[5])), + static_cast(a), + 0 + ); + } +} + +/// \brief \copydoc matrix4_get_rotation_euler_xyz(const Matrix4&) +inline Vector3 matrix4_get_rotation_euler_xyz_degrees(const Matrix4& self) +{ + return euler_radians_to_degrees(matrix4_get_rotation_euler_xyz(self)); +} + +/// \brief Calculates and returns a set of euler angles that produce the rotation component of \p self when applied in the order (y, x, z). +/// \p self must be affine and orthonormal (unscaled) to produce a meaningful result. +inline Vector3 matrix4_get_rotation_euler_yxz(const Matrix4& self) +{ + double a = asin(self[6]); + double ca = cos(a); + + if (fabs(ca) > 0.005) // Gimbal lock? + { + return Vector3( + static_cast(a), + static_cast(atan2(-self[2] / ca, self[10]/ ca)), + static_cast(atan2(-self[4] / ca, self[5] / ca)) + ); + } + else // Gimbal lock has occurred + { + return Vector3( + static_cast(a), + static_cast(atan2(self[8], self[0])), + 0 + ); + } +} + +/// \brief \copydoc matrix4_get_rotation_euler_yxz(const Matrix4&) +inline Vector3 matrix4_get_rotation_euler_yxz_degrees(const Matrix4& self) +{ + return euler_radians_to_degrees(matrix4_get_rotation_euler_yxz(self)); +} + +/// \brief Calculates and returns a set of euler angles that produce the rotation component of \p self when applied in the order (z, x, y). +/// \p self must be affine and orthonormal (unscaled) to produce a meaningful result. +inline Vector3 matrix4_get_rotation_euler_zxy(const Matrix4& self) +{ + double a = asin(-self[9]); + double ca = cos(a); + + if (fabs(ca) > 0.005) // Gimbal lock? + { + return Vector3( + static_cast(a), + static_cast(atan2(self[8] / ca, self[10] / ca)), + static_cast(atan2(self[1] / ca, self[5]/ ca)) + ); + } + else // Gimbal lock has occurred + { + return Vector3( + static_cast(a), + 0, + static_cast(atan2(-self[4], self[0])) + ); + } +} + +/// \brief \copydoc matrix4_get_rotation_euler_zxy(const Matrix4&) +inline Vector3 matrix4_get_rotation_euler_zxy_degrees(const Matrix4& self) +{ + return euler_radians_to_degrees(matrix4_get_rotation_euler_zxy(self)); +} + +/// \brief Calculates and returns a set of euler angles that produce the rotation component of \p self when applied in the order (z, y, x). +/// \p self must be affine and orthonormal (unscaled) to produce a meaningful result. +inline Vector3 matrix4_get_rotation_euler_zyx(const Matrix4& self) +{ + double a = asin(self[8]); + double ca = cos(a); + + if (fabs(ca) > 0.005) // Gimbal lock? + { + return Vector3( + static_cast(atan2(-self[9] / ca, self[10]/ ca)), + static_cast(a), + static_cast(atan2(-self[4] / ca, self[0] / ca)) + ); + } + else // Gimbal lock has occurred + { + return Vector3( + 0, + static_cast(a), + static_cast(atan2(self[1], self[5])) + ); + } +} + +/// \brief \copydoc matrix4_get_rotation_euler_zyx(const Matrix4&) +inline Vector3 matrix4_get_rotation_euler_zyx_degrees(const Matrix4& self) +{ + return euler_radians_to_degrees(matrix4_get_rotation_euler_zyx(self)); +} + + +/// \brief Rotate \p self by \p euler angles (degrees) applied in the order (x, y, z), using \p pivotpoint. +inline void matrix4_pivoted_rotate_by_euler_xyz_degrees(Matrix4& self, const Vector3& euler, const Vector3& pivotpoint) +{ + matrix4_translate_by_vec3(self, pivotpoint); + matrix4_rotate_by_euler_xyz_degrees(self, euler); + matrix4_translate_by_vec3(self, vector3_negated(pivotpoint)); +} + + +/// \brief Constructs a pure-scale matrix from \p scale. +inline Matrix4 matrix4_scale_for_vec3(const Vector3& scale) +{ + return Matrix4( + scale[0], 0, 0, 0, + 0, scale[1], 0, 0, + 0, 0, scale[2], 0, + 0, 0, 0, 1 + ); +} + +/// \brief Calculates and returns the (x, y, z) scale values that produce the scale component of \p self. +/// \p self must be affine and orthogonal to produce a meaningful result. +inline Vector3 matrix4_get_scale_vec3(const Matrix4& self) +{ + return Vector3( + static_cast(vector3_length(vector4_to_vector3(self.x()))), + static_cast(vector3_length(vector4_to_vector3(self.y()))), + static_cast(vector3_length(vector4_to_vector3(self.z()))) + ); +} + +/// \brief Scales \p self by \p scale. +inline void matrix4_scale_by_vec3(Matrix4& self, const Vector3& scale) +{ + matrix4_multiply_by_matrix4(self, matrix4_scale_for_vec3(scale)); +} + +/// \brief Scales \p self by \p scale, using \p pivotpoint. +inline void matrix4_pivoted_scale_by_vec3(Matrix4& self, const Vector3& scale, const Vector3& pivotpoint) +{ + matrix4_translate_by_vec3(self, pivotpoint); + matrix4_scale_by_vec3(self, scale); + matrix4_translate_by_vec3(self, vector3_negated(pivotpoint)); +} + + +/// \brief Transforms \p self by \p translation, \p euler and \p scale. +/// The transforms are combined in the order: scale, rotate-z, rotate-y, rotate-x, translate. +inline void matrix4_transform_by_euler_xyz_degrees(Matrix4& self, const Vector3& translation, const Vector3& euler, const Vector3& scale) +{ + matrix4_translate_by_vec3(self, translation); + matrix4_rotate_by_euler_xyz_degrees(self, euler); + matrix4_scale_by_vec3(self, scale); +} + +/// \brief Transforms \p self by \p translation, \p euler and \p scale, using \p pivotpoint. +inline void matrix4_pivoted_transform_by_euler_xyz_degrees(Matrix4& self, const Vector3& translation, const Vector3& euler, const Vector3& scale, const Vector3& pivotpoint) +{ + matrix4_translate_by_vec3(self, pivotpoint + translation); + matrix4_rotate_by_euler_xyz_degrees(self, euler); + matrix4_scale_by_vec3(self, scale); + matrix4_translate_by_vec3(self, vector3_negated(pivotpoint)); +} + + +#endif diff --git a/libs/math/pi.cpp b/libs/math/pi.cpp new file mode 100644 index 00000000..34930192 --- /dev/null +++ b/libs/math/pi.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "pi.h" + diff --git a/libs/math/pi.h b/libs/math/pi.h new file mode 100644 index 00000000..9cc74c1b --- /dev/null +++ b/libs/math/pi.h @@ -0,0 +1,45 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MATH_PI_H) +#define INCLUDED_MATH_PI_H + +/// \file +/// \brief Pi constants and degrees/radians conversion. + +const double c_pi = 3.1415926535897932384626433832795; +const double c_half_pi = c_pi / 2; +const double c_2pi = 2 * c_pi; +const double c_inv_2pi = 1 / c_2pi; + +const double c_DEG2RADMULT = c_pi / 180.0; +const double c_RAD2DEGMULT = 180.0 / c_pi; + +inline double radians_to_degrees(double radians) +{ + return radians * c_RAD2DEGMULT; +} +inline double degrees_to_radians(double degrees) +{ + return degrees * c_DEG2RADMULT; +} + +#endif diff --git a/libs/math/plane.cpp b/libs/math/plane.cpp new file mode 100644 index 00000000..4182a175 --- /dev/null +++ b/libs/math/plane.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "plane.h" + diff --git a/libs/math/plane.h b/libs/math/plane.h new file mode 100644 index 00000000..af05db74 --- /dev/null +++ b/libs/math/plane.h @@ -0,0 +1,153 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MATH_PLANE_H) +#define INCLUDED_MATH_PLANE_H + +/// \file +/// \brief Plane data types and related operations. + +#include "math/matrix.h" + +/// \brief A plane equation stored in double-precision floating-point. +class Plane3 +{ +public: + double a, b, c, d; + + Plane3() + { + } + Plane3(double _a, double _b, double _c, double _d) + : a(_a), b(_b), c(_c), d(_d) + { + } + template + Plane3(const BasicVector3& normal, double dist) + : a(normal.x()), b(normal.y()), c(normal.z()), d(dist) + { + } + + BasicVector3& normal() + { + return reinterpret_cast&>(*this); + } + const BasicVector3& normal() const + { + return reinterpret_cast&>(*this); + } + double& dist() + { + return d; + } + const double& dist() const + { + return d; + } +}; + +inline Plane3 plane3_normalised(const Plane3& plane) +{ + double rmagnitude = 1.0 / sqrt(plane.a * plane.a + plane.b * plane.b + plane.c * plane.c); + return Plane3( + plane.a * rmagnitude, + plane.b * rmagnitude, + plane.c * rmagnitude, + plane.d * rmagnitude + ); +} + +inline Plane3 plane3_translated(const Plane3& plane, const Vector3& translation) +{ + Plane3 transformed; + transformed.a = plane.a; + transformed.b = plane.b; + transformed.c = plane.c; + transformed.d = -((-plane.d * transformed.a + translation.x()) * transformed.a + + (-plane.d * transformed.b + translation.y()) * transformed.b + + (-plane.d * transformed.c + translation.z()) * transformed.c); + return transformed; +} + +inline Plane3 plane3_transformed(const Plane3& plane, const Matrix4& transform) +{ + Plane3 transformed; + transformed.a = transform[0] * plane.a + transform[4] * plane.b + transform[8] * plane.c; + transformed.b = transform[1] * plane.a + transform[5] * plane.b + transform[9] * plane.c; + transformed.c = transform[2] * plane.a + transform[6] * plane.b + transform[10] * plane.c; + transformed.d = -((-plane.d * transformed.a + transform[12]) * transformed.a + + (-plane.d * transformed.b + transform[13]) * transformed.b + + (-plane.d * transformed.c + transform[14]) * transformed.c); + return transformed; +} + +inline Plane3 plane3_inverse_transformed(const Plane3& plane, const Matrix4& transform) +{ + return Plane3 + ( + transform[ 0] * plane.a + transform[ 1] * plane.b + transform[ 2] * plane.c + transform[ 3] * plane.d, + transform[ 4] * plane.a + transform[ 5] * plane.b + transform[ 6] * plane.c + transform[ 7] * plane.d, + transform[ 8] * plane.a + transform[ 9] * plane.b + transform[10] * plane.c + transform[11] * plane.d, + transform[12] * plane.a + transform[13] * plane.b + transform[14] * plane.c + transform[15] * plane.d + ); +} + +inline Plane3 plane3_flipped(const Plane3& plane) +{ + return Plane3(vector3_negated(plane.normal()), -plane.dist()); +} + +const double c_PLANE_NORMAL_EPSILON = 0.0001f; +const double c_PLANE_DIST_EPSILON = 0.02; + +inline bool plane3_equal(const Plane3& self, const Plane3& other) +{ + return vector3_equal_epsilon(self.normal(), other.normal(), c_PLANE_NORMAL_EPSILON) + && float_equal_epsilon(self.dist(), other.dist(), c_PLANE_DIST_EPSILON); +} + +inline bool plane3_opposing(const Plane3& self, const Plane3& other) +{ + return plane3_equal(self, plane3_flipped(other)); +} + +inline bool plane3_valid(const Plane3& self) +{ + return float_equal_epsilon(vector3_dot(self.normal(), self.normal()), 1.0, 0.01); +} + +template +inline Plane3 plane3_for_points(const BasicVector3& p0, const BasicVector3& p1, const BasicVector3& p2) +{ + Plane3 self; + self.normal() = vector3_normalised(vector3_cross(vector3_subtracted(p1, p0), vector3_subtracted(p2, p0))); + self.dist() = vector3_dot(p0, self.normal()); + return self; +} + +template +inline Plane3 plane3_for_points(const BasicVector3 planepts[3]) +{ + return plane3_for_points(planepts[2], planepts[1], planepts[0]); +} + + +#endif diff --git a/libs/math/quaternion.cpp b/libs/math/quaternion.cpp new file mode 100644 index 00000000..6688368e --- /dev/null +++ b/libs/math/quaternion.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "quaternion.h" + diff --git a/libs/math/quaternion.h b/libs/math/quaternion.h new file mode 100644 index 00000000..d91ad62a --- /dev/null +++ b/libs/math/quaternion.h @@ -0,0 +1,327 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MATH_QUATERNION_H) +#define INCLUDED_MATH_QUATERNION_H + +/// \file +/// \brief Quaternion data types and related operations. + +#include "math/matrix.h" + +/// \brief A quaternion stored in single-precision floating-point. +typedef Vector4 Quaternion; + +const Quaternion c_quaternion_identity(0, 0, 0, 1); + +inline Quaternion quaternion_multiplied_by_quaternion(const Quaternion& quaternion, const Quaternion& other) +{ + return Quaternion( + quaternion[3]*other[0] + quaternion[0]*other[3] + quaternion[1]*other[2] - quaternion[2]*other[1], + quaternion[3]*other[1] + quaternion[1]*other[3] + quaternion[2]*other[0] - quaternion[0]*other[2], + quaternion[3]*other[2] + quaternion[2]*other[3] + quaternion[0]*other[1] - quaternion[1]*other[0], + quaternion[3]*other[3] - quaternion[0]*other[0] - quaternion[1]*other[1] - quaternion[2]*other[2] + ); +} + +inline void quaternion_multiply_by_quaternion(Quaternion& quaternion, const Quaternion& other) +{ + quaternion = quaternion_multiplied_by_quaternion(quaternion, other); +} + +/// \brief Constructs a quaternion which rotates between two points on the unit-sphere, \p from and \p to. +inline Quaternion quaternion_for_unit_vectors(const Vector3& from, const Vector3& to) +{ + return Quaternion(vector3_cross(from, to), static_cast(vector3_dot(from, to))); +} + +inline Quaternion quaternion_for_axisangle(const Vector3& axis, double angle) +{ + angle *= 0.5; + float sa = static_cast(sin(angle)); + return Quaternion(axis[0] * sa, axis[1] * sa, axis[2] * sa, static_cast(cos(angle))); +} + +inline Quaternion quaternion_for_x(double angle) +{ + angle *= 0.5; + return Quaternion(static_cast(sin(angle)), 0, 0, static_cast(cos(angle))); +} + +inline Quaternion quaternion_for_y(double angle) +{ + angle *= 0.5; + return Quaternion(0, static_cast(sin(angle)), 0, static_cast(cos(angle))); +} + +inline Quaternion quaternion_for_z(double angle) +{ + angle *= 0.5; + return Quaternion(0, 0, static_cast(sin(angle)), static_cast(cos(angle))); +} + +inline Quaternion quaternion_inverse(const Quaternion& quaternion) +{ + return Quaternion(vector3_negated(vector4_to_vector3(quaternion)), quaternion[3]); +} + +inline void quaternion_conjugate(Quaternion& quaternion) +{ + quaternion = quaternion_inverse(quaternion); +} + +inline Quaternion quaternion_normalised(const Quaternion& quaternion) +{ + const double n = (1.0 / (quaternion[0] * quaternion[0] + quaternion[1] * quaternion[1] + quaternion[2] * quaternion[2] + quaternion[3] * quaternion[3])); + return Quaternion( + static_cast(quaternion[0] * n), + static_cast(quaternion[1] * n), + static_cast(quaternion[2] * n), + static_cast(quaternion[3] * n) + ); +} + +inline void quaternion_normalise(Quaternion& quaternion) +{ + quaternion = quaternion_normalised(quaternion); +} + +/// \brief Constructs a pure-rotation matrix from \p quaternion. +inline Matrix4 matrix4_rotation_for_quaternion(const Quaternion& quaternion) +{ +#if 0 + const double xx = quaternion[0] * quaternion[0]; + const double xy = quaternion[0] * quaternion[1]; + const double xz = quaternion[0] * quaternion[2]; + const double xw = quaternion[0] * quaternion[3]; + + const double yy = quaternion[1] * quaternion[1]; + const double yz = quaternion[1] * quaternion[2]; + const double yw = quaternion[1] * quaternion[3]; + + const double zz = quaternion[2] * quaternion[2]; + const double zw = quaternion[2] * quaternion[3]; + + return Matrix4( + static_cast( 1 - 2 * ( yy + zz ) ), + static_cast( 2 * ( xy + zw ) ), + static_cast( 2 * ( xz - yw ) ), + 0, + static_cast( 2 * ( xy - zw ) ), + static_cast( 1 - 2 * ( xx + zz ) ), + static_cast( 2 * ( yz + xw ) ), + 0, + static_cast( 2 * ( xz + yw ) ), + static_cast( 2 * ( yz - xw ) ), + static_cast( 1 - 2 * ( xx + yy ) ), + 0, + 0, + 0, + 0, + 1 + ); + +#else + const double x2 = quaternion[0] + quaternion[0]; + const double y2 = quaternion[1] + quaternion[1]; + const double z2 = quaternion[2] + quaternion[2]; + const double xx = quaternion[0] * x2; + const double xy = quaternion[0] * y2; + const double xz = quaternion[0] * z2; + const double yy = quaternion[1] * y2; + const double yz = quaternion[1] * z2; + const double zz = quaternion[2] * z2; + const double wx = quaternion[3] * x2; + const double wy = quaternion[3] * y2; + const double wz = quaternion[3] * z2; + + return Matrix4( + static_cast( 1.0 - (yy + zz) ), + static_cast(xy + wz), + static_cast(xz - wy), + 0, + static_cast(xy - wz), + static_cast( 1.0 - (xx + zz) ), + static_cast(yz + wx), + 0, + static_cast(xz + wy), + static_cast(yz - wx), + static_cast( 1.0 - (xx + yy) ), + 0, + 0, + 0, + 0, + 1 + ); + +#endif +} + +const double c_half_sqrt2 = 0.70710678118654752440084436210485; +const float c_half_sqrt2f = static_cast(c_half_sqrt2); + +inline bool quaternion_component_is_90(float component) +{ + return (fabs(component) - c_half_sqrt2) < 0.001; +} + +inline Matrix4 matrix4_rotation_for_quaternion_quantised(const Quaternion& quaternion) +{ + if(quaternion.y() == 0 + && quaternion.z() == 0 + && quaternion_component_is_90(quaternion.x()) + && quaternion_component_is_90(quaternion.w())) + { + return matrix4_rotation_for_sincos_x((quaternion.x() > 0) ? 1.f : -1.f, 0); + } + + if(quaternion.x() == 0 + && quaternion.z() == 0 + && quaternion_component_is_90(quaternion.y()) + && quaternion_component_is_90(quaternion.w())) + { + return matrix4_rotation_for_sincos_y((quaternion.y() > 0) ? 1.f : -1.f, 0); + } + + if(quaternion.x() == 0 + && quaternion.y() == 0 + && quaternion_component_is_90(quaternion.z()) + && quaternion_component_is_90(quaternion.w())) + { + return matrix4_rotation_for_sincos_z((quaternion.z() > 0) ? 1.f : -1.f, 0); + } + + return matrix4_rotation_for_quaternion(quaternion); +} + +inline Quaternion quaternion_for_matrix4_rotation(const Matrix4& matrix4) +{ + Matrix4 transposed = matrix4_transposed(matrix4); + + double trace = transposed[0] + transposed[5] + transposed[10] + 1.0; + + if(trace > 0.0001) + { + double S = 0.5 / sqrt(trace); + return Quaternion( + static_cast((transposed[9] - transposed[6]) * S), + static_cast((transposed[2] - transposed[8]) * S), + static_cast((transposed[4] - transposed[1]) * S), + static_cast(0.25 / S) + ); + } + + if(transposed[0] >= transposed[5] && transposed[0] >= transposed[10]) + { + double S = 2.0 * sqrt(1.0 + transposed[0] - transposed[5] - transposed[10]); + return Quaternion( + static_cast(0.25 / S), + static_cast((transposed[1] + transposed[4]) / S), + static_cast((transposed[2] + transposed[8]) / S), + static_cast((transposed[6] + transposed[9]) / S) + ); + } + + if(transposed[5] >= transposed[0] && transposed[5] >= transposed[10]) + { + double S = 2.0 * sqrt(1.0 + transposed[5] - transposed[0] - transposed[10]); + return Quaternion( + static_cast((transposed[1] + transposed[4]) / S), + static_cast(0.25 / S), + static_cast((transposed[6] + transposed[9]) / S), + static_cast((transposed[2] + transposed[8]) / S) + ); + } + + double S = 2.0 * sqrt(1.0 + transposed[10] - transposed[0] - transposed[5]); + return Quaternion( + static_cast((transposed[2] + transposed[8]) / S), + static_cast((transposed[6] + transposed[9]) / S), + static_cast(0.25 / S), + static_cast((transposed[1] + transposed[4]) / S) + ); +} + +/// \brief Returns \p self concatenated with the rotation transform produced by \p rotation. +/// The concatenated rotation occurs before \p self. +inline Matrix4 matrix4_rotated_by_quaternion(const Matrix4& self, const Quaternion& rotation) +{ + return matrix4_multiplied_by_matrix4(self, matrix4_rotation_for_quaternion(rotation)); +} + +/// \brief Concatenates \p self with the rotation transform produced by \p rotation. +/// The concatenated rotation occurs before \p self. +inline void matrix4_rotate_by_quaternion(Matrix4& self, const Quaternion& rotation) +{ + self = matrix4_rotated_by_quaternion(self, rotation); +} + +/// \brief Rotates \p self by \p rotation, using \p pivotpoint. +inline void matrix4_pivoted_rotate_by_quaternion(Matrix4& self, const Quaternion& rotation, const Vector3& pivotpoint) +{ + matrix4_translate_by_vec3(self, pivotpoint); + matrix4_rotate_by_quaternion(self, rotation); + matrix4_translate_by_vec3(self, vector3_negated(pivotpoint)); +} + +inline Vector3 quaternion_transformed_point(const Quaternion& quaternion, const Vector3& point) +{ + double xx = quaternion.x() * quaternion.x(); + double yy = quaternion.y() * quaternion.y(); + double zz = quaternion.z() * quaternion.z(); + double ww = quaternion.w() * quaternion.w(); + + double xy2 = quaternion.x() * quaternion.y() * 2; + double xz2 = quaternion.x() * quaternion.z() * 2; + double xw2 = quaternion.x() * quaternion.w() * 2; + double yz2 = quaternion.y() * quaternion.z() * 2; + double yw2 = quaternion.y() * quaternion.w() * 2; + double zw2 = quaternion.z() * quaternion.w() * 2; + + return Vector3( + static_cast(ww * point.x() + yw2 * point.z() - zw2 * point.y() + xx * point.x() + xy2 * point.y() + xz2 * point.z() - zz * point.x() - yy * point.x()), + static_cast(xy2 * point.x() + yy * point.y() + yz2 * point.z() + zw2 * point.x() - zz * point.y() + ww * point.y() - xw2 * point.z() - xx * point.y()), + static_cast(xz2 * point.x() + yz2 * point.y() + zz * point.z() - yw2 * point.x() - yy * point.z() + xw2 * point.y() - xx * point.z() + ww * point.z()) + ); +} + +/// \brief Constructs a pure-rotation transform from \p axis and \p angle (radians). +inline Matrix4 matrix4_rotation_for_axisangle(const Vector3& axis, double angle) +{ + return matrix4_rotation_for_quaternion(quaternion_for_axisangle(axis, angle)); +} + +/// \brief Rotates \p self about \p axis by \p angle. +inline void matrix4_rotate_by_axisangle(Matrix4& self, const Vector3& axis, double angle) +{ + matrix4_multiply_by_matrix4(self, matrix4_rotation_for_axisangle(axis, angle)); +} + +/// \brief Rotates \p self about \p axis by \p angle using \p pivotpoint. +inline void matrix4_pivoted_rotate_by_axisangle(Matrix4& self, const Vector3& axis, double angle, const Vector3& pivotpoint) +{ + matrix4_translate_by_vec3(self, pivotpoint); + matrix4_rotate_by_axisangle(self, axis, angle); + matrix4_translate_by_vec3(self, vector3_negated(pivotpoint)); +} + + +#endif diff --git a/libs/math/vector.cpp b/libs/math/vector.cpp new file mode 100644 index 00000000..d72bd9e4 --- /dev/null +++ b/libs/math/vector.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "vector.h" + diff --git a/libs/math/vector.h b/libs/math/vector.h new file mode 100644 index 00000000..19fdd755 --- /dev/null +++ b/libs/math/vector.h @@ -0,0 +1,800 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MATH_VECTOR_H) +#define INCLUDED_MATH_VECTOR_H + +/// \file +/// \brief Vector data types and related operations. + +#include "generic/vector.h" + +#if defined (_MSC_VER) + +inline int lrint (double flt) +{ + int i; + + _asm + { + fld flt + fistp i + }; + + return i; +} + +#elif defined(__FreeBSD__) + +inline int lrint(double f) +{ + return static_cast(f + 0.5); +} + +#elif defined(__GNUC__) + + // lrint is part of ISO C99 +#define _ISOC9X_SOURCE 1 +#define _ISOC99_SOURCE 1 + +#define __USE_ISOC9X 1 +#define __USE_ISOC99 1 + +#else +#error "unsupported platform" +#endif + +#include +#include +#include + + +//#include "debugging/debugging.h" + +/// \brief Returns true if \p self is equal to other \p other within \p epsilon. +template +inline bool float_equal_epsilon(const Element& self, const OtherElement& other, const Element& epsilon) +{ + return fabs(other - self) < epsilon; +} + +/// \brief Returns the value midway between \p self and \p other. +template +inline Element float_mid(const Element& self, const Element& other) +{ + return Element((self + other) * 0.5); +} + +/// \brief Returns \p f rounded to the nearest integer. Note that this is not the same behaviour as casting from float to int. +template +inline int float_to_integer(const Element& f) +{ + return lrint(f); +} + +/// \brief Returns \p f rounded to the nearest multiple of \p snap. +template +inline Element float_snapped(const Element& f, const OtherElement& snap) +{ + return Element(float_to_integer(f / snap) * snap); +} + +/// \brief Returns true if \p f has no decimal fraction part. +template +inline bool float_is_integer(const Element& f) +{ + return f == Element(float_to_integer(f)); +} + +/// \brief Returns \p self modulated by the range [0, \p modulus) +/// \p self must be in the range [\p -modulus, \p modulus) +template +inline Element float_mod_range(const Element& self, const ModulusElement& modulus) +{ + return Element((self < 0.0) ? self + modulus : self); +} + +/// \brief Returns \p self modulated by the range [0, \p modulus) +template +inline Element float_mod(const Element& self, const ModulusElement& modulus) +{ + return float_mod_range(Element(fmod(static_cast(self), static_cast(modulus))), modulus); +} + + +template +inline BasicVector2 vector2_added(const BasicVector2& self, const BasicVector2& other) +{ + return BasicVector2( + Element(self.x() + other.x()), + Element(self.y() + other.y()) + ); +} +template +inline BasicVector2 operator+(const BasicVector2& self, const BasicVector2& other) +{ + return vector2_added(self, other); +} +template +inline void vector2_add(BasicVector2& self, const BasicVector2& other) +{ + self.x() += Element(other.x()); + self.y() += Element(other.y()); +} +template +inline void operator+=(BasicVector2& self, const BasicVector2& other) +{ + vector2_add(self, other); +} + + +template +inline BasicVector2 vector2_subtracted(const BasicVector2& self, const BasicVector2& other) +{ + return BasicVector2( + Element(self.x() - other.x()), + Element(self.y() - other.y()) + ); +} +template +inline BasicVector2 operator-(const BasicVector2& self, const BasicVector2& other) +{ + return vector2_subtracted(self, other); +} +template +inline void vector2_subtract(BasicVector2& self, const BasicVector2& other) +{ + self.x() -= Element(other.x()); + self.y() -= lement(other.y()); +} +template +inline void operator-=(BasicVector2& self, const BasicVector2& other) +{ + vector2_subtract(self, other); +} + + +template +inline BasicVector2 vector2_scaled(const BasicVector2& self, OtherElement other) +{ + return BasicVector2( + Element(self.x() * other), + Element(self.y() * other) + ); +} +template +inline BasicVector2 operator*(const BasicVector2& self, OtherElement other) +{ + return vector2_scaled(self, other); +} +template +inline void vector2_scale(BasicVector2& self, OtherElement other) +{ + self.x() *= Element(other); + self.y() *= Element(other); +} +template +inline void operator*=(BasicVector2& self, OtherElement other) +{ + vector2_scale(self, other); +} + + +template +inline BasicVector2 vector2_scaled(const BasicVector2& self, const BasicVector2& other) +{ + return BasicVector2( + Element(self.x() * other.x()), + Element(self.y() * other.y()) + ); +} +template +inline BasicVector2 operator*(const BasicVector2& self, const BasicVector2& other) +{ + return vector2_scaled(self, other); +} +template +inline void vector2_scale(BasicVector2& self, const BasicVector2& other) +{ + self.x() *= Element(other.x()); + self.y() *= Element(other.y()); +} +template +inline void operator*=(BasicVector2& self, const BasicVector2& other) +{ + vector2_scale(self, other); +} + +template +inline BasicVector2 vector2_divided(const BasicVector2& self, const BasicVector2& other) +{ + return BasicVector2( + Element(self.x() / other.x()), + Element(self.y() / other.y()) + ); +} +template +inline BasicVector2 operator/(const BasicVector2& self, const BasicVector2& other) +{ + return vector2_divided(self, other); +} +template +inline void vector2_divide(BasicVector2& self, const BasicVector2& other) +{ + self.x() /= Element(other.x()); + self.y() /= Element(other.y()); +} +template +inline void operator/=(BasicVector2& self, const BasicVector2& other) +{ + vector2_divide(self, other); +} + + +template +inline BasicVector2 vector2_divided(const BasicVector2& self, OtherElement other) +{ + return BasicVector2( + Element(self.x() / other), + Element(self.y() / other) + ); +} +template +inline BasicVector2 operator/(const BasicVector2& self, OtherElement other) +{ + return vector2_divided(self, other); +} +template +inline void vector2_divide(BasicVector2& self, OtherElement other) +{ + self.x() /= Element(other); + self.y() /= Element(other); +} +template +inline void operator/=(BasicVector2& self, OtherElement other) +{ + vector2_divide(self, other); +} + +template +inline double vector2_dot(const BasicVector2& self, const BasicVector2& other) +{ + return self.x() * other.x() + self.y() * other.y(); +} + +template +inline double vector2_length_squared(const BasicVector2& self) +{ + return vector2_dot(self, self); +} + +template +inline double vector2_length(const BasicVector2& self) +{ + return sqrt(vector2_length_squared(self)); +} + +template +inline double vector2_cross(const BasicVector2& self, const BasicVector2& other) +{ + return self.x() * other.y() - self.y() * other.x(); +} + +const Vector3 g_vector3_identity(0, 0, 0); +const Vector3 g_vector3_max = Vector3(FLT_MAX, FLT_MAX, FLT_MAX); +const Vector3 g_vector3_axis_x(1, 0, 0); +const Vector3 g_vector3_axis_y(0, 1, 0); +const Vector3 g_vector3_axis_z(0, 0, 1); + +const Vector3 g_vector3_axes[3] = { g_vector3_axis_x, g_vector3_axis_y, g_vector3_axis_z }; + +template +inline void vector3_swap(BasicVector3& self, BasicVector3& other) +{ + std::swap(self.x(), other.x()); + std::swap(self.y(), other.y()); + std::swap(self.z(), other.z()); +} + +template +inline bool vector3_equal(const BasicVector3& self, const BasicVector3& other) +{ + return self.x() == other.x() && self.y() == other.y() && self.z() == other.z(); +} +template +inline bool operator==(const BasicVector3& self, const BasicVector3& other) +{ + return vector3_equal(self, other); +} +template +inline bool operator!=(const BasicVector3& self, const BasicVector3& other) +{ + return !vector3_equal(self, other); +} + + +template +inline bool vector3_equal_epsilon(const BasicVector3& self, const BasicVector3& other, Epsilon epsilon) +{ + return float_equal_epsilon(self.x(), other.x(), epsilon) + && float_equal_epsilon(self.y(), other.y(), epsilon) + && float_equal_epsilon(self.z(), other.z(), epsilon); +} + + + +template +inline BasicVector3 vector3_added(const BasicVector3& self, const BasicVector3& other) +{ + return BasicVector3( + Element(self.x() + other.x()), + Element(self.y() + other.y()), + Element(self.z() + other.z()) + ); +} +template +inline BasicVector3 operator+(const BasicVector3& self, const BasicVector3& other) +{ + return vector3_added(self, other); +} +template +inline void vector3_add(BasicVector3& self, const BasicVector3& other) +{ + self.x() += static_cast(other.x()); + self.y() += static_cast(other.y()); + self.z() += static_cast(other.z()); +} +template +inline void operator+=(BasicVector3& self, const BasicVector3& other) +{ + vector3_add(self, other); +} + +template +inline BasicVector3 vector3_subtracted(const BasicVector3& self, const BasicVector3& other) +{ + return BasicVector3( + Element(self.x() - other.x()), + Element(self.y() - other.y()), + Element(self.z() - other.z()) + ); +} +template +inline BasicVector3 operator-(const BasicVector3& self, const BasicVector3& other) +{ + return vector3_subtracted(self, other); +} +template +inline void vector3_subtract(BasicVector3& self, const BasicVector3& other) +{ + self.x() -= static_cast(other.x()); + self.y() -= static_cast(other.y()); + self.z() -= static_cast(other.z()); +} +template +inline void operator-=(BasicVector3& self, const BasicVector3& other) +{ + vector3_subtract(self, other); +} + +template +inline BasicVector3 vector3_scaled(const BasicVector3& self, const BasicVector3& other) +{ + return BasicVector3( + Element(self.x() * other.x()), + Element(self.y() * other.y()), + Element(self.z() * other.z()) + ); +} +template +inline BasicVector3 operator*(const BasicVector3& self, const BasicVector3& other) +{ + return vector3_scaled(self, other); +} +template +inline void vector3_scale(BasicVector3& self, const BasicVector3& other) +{ + self.x() *= static_cast(other.x()); + self.y() *= static_cast(other.y()); + self.z() *= static_cast(other.z()); +} +template +inline void operator*=(BasicVector3& self, const BasicVector3& other) +{ + vector3_scale(self, other); +} + +template +inline BasicVector3 vector3_scaled(const BasicVector3& self, const OtherElement& scale) +{ + return BasicVector3( + Element(self.x() * scale), + Element(self.y() * scale), + Element(self.z() * scale) + ); +} +template +inline BasicVector3 operator*(const BasicVector3& self, const OtherElement& scale) +{ + return vector3_scaled(self, scale); +} +template +inline void vector3_scale(BasicVector3& self, const OtherElement& scale) +{ + self.x() *= static_cast(scale); + self.y() *= static_cast(scale); + self.z() *= static_cast(scale); +} +template +inline void operator*=(BasicVector3& self, const OtherElement& scale) +{ + vector3_scale(self, scale); +} + +template +inline BasicVector3 vector3_divided(const BasicVector3& self, const BasicVector3& other) +{ + return BasicVector3( + Element(self.x() / other.x()), + Element(self.y() / other.y()), + Element(self.z() / other.z()) + ); +} +template +inline BasicVector3 operator/(const BasicVector3& self, const BasicVector3& other) +{ + return vector3_divided(self, other); +} +template +inline void vector3_divide(BasicVector3& self, const BasicVector3& other) +{ + self.x() /= static_cast(other.x()); + self.y() /= static_cast(other.y()); + self.z() /= static_cast(other.z()); +} +template +inline void operator/=(BasicVector3& self, const BasicVector3& other) +{ + vector3_divide(self, other); +} + +template +inline BasicVector3 vector3_divided(const BasicVector3& self, const OtherElement& divisor) +{ + return BasicVector3( + Element(self.x() / divisor), + Element(self.y() / divisor), + Element(self.z() / divisor) + ); +} +template +inline BasicVector3 operator/(const BasicVector3& self, const OtherElement& divisor) +{ + return vector3_divided(self, divisor); +} +template +inline void vector3_divide(BasicVector3& self, const OtherElement& divisor) +{ + self.x() /= static_cast(divisor); + self.y() /= static_cast(divisor); + self.z() /= static_cast(divisor); +} +template +inline void operator/=(BasicVector3& self, const OtherElement& divisor) +{ + vector3_divide(self, divisor); +} + +template +inline double vector3_dot(const BasicVector3& self, const BasicVector3& other) +{ + return self.x() * other.x() + self.y() * other.y() + self.z() * other.z(); +} + +template +inline BasicVector3 vector3_mid(const BasicVector3& begin, const BasicVector3& end) +{ + return vector3_scaled(vector3_added(begin, end), 0.5); +} + +template +inline BasicVector3 vector3_cross(const BasicVector3& self, const BasicVector3& other) +{ + return BasicVector3( + Element(self.y() * other.z() - self.z() * other.y()), + Element(self.z() * other.x() - self.x() * other.z()), + Element(self.x() * other.y() - self.y() * other.x()) + ); +} + +template +inline BasicVector3 vector3_negated(const BasicVector3& self) +{ + return BasicVector3(-self.x(), -self.y(), -self.z()); +} +template +inline BasicVector3 operator-(const BasicVector3& self) +{ + return vector3_negated(self); +} + +template +inline void vector3_negate(BasicVector3& self) +{ + self = vector3_negated(self); +} + +template +inline double vector3_length_squared(const BasicVector3& self) +{ + return vector3_dot(self, self); +} + +template +inline double vector3_length(const BasicVector3& self) +{ + return sqrt(vector3_length_squared(self)); +} + +template +inline Element float_divided(Element f, Element other) +{ + //ASSERT_MESSAGE(other != 0, "float_divided: invalid divisor"); + return f / other; +} + +template +inline BasicVector3 vector3_normalised(const BasicVector3& self) +{ + return vector3_scaled(self, float_divided(1.0, vector3_length(self))); +} + +template +inline void vector3_normalise(BasicVector3& self) +{ + self = vector3_normalised(self); +} + + +template +inline BasicVector3 vector3_snapped(const BasicVector3& self) +{ + return BasicVector3( + Element(float_to_integer(self.x())), + Element(float_to_integer(self.y())), + Element(float_to_integer(self.z())) + ); +} +template +inline void vector3_snap(BasicVector3& self) +{ + self = vector3_snapped(self); +} +template +inline BasicVector3 vector3_snapped(const BasicVector3& self, const OtherElement& snap) +{ + return BasicVector3( + Element(float_snapped(self.x(), snap)), + Element(float_snapped(self.y(), snap)), + Element(float_snapped(self.z(), snap)) + ); +} +template +inline void vector3_snap(BasicVector3& self, const OtherElement& snap) +{ + self = vector3_snapped(self, snap); +} + +inline Vector3 vector3_for_spherical(double theta, double phi) +{ + return Vector3( + static_cast(cos(theta) * cos(phi)), + static_cast(sin(theta) * cos(phi)), + static_cast(sin(phi)) + ); +} + + + + +template +inline bool vector4_equal(const BasicVector4& self, const BasicVector4& other) +{ + return self.x() == other.x() && self.y() == other.y() && self.z() == other.z() && self.w() == other.w(); +} +template +inline bool operator==(const BasicVector4& self, const BasicVector4& other) +{ + return vector4_equal(self, other); +} +template +inline bool operator!=(const BasicVector4& self, const BasicVector4& other) +{ + return !vector4_equal(self, other); +} + +template +inline bool vector4_equal_epsilon(const BasicVector4& self, const BasicVector4& other, Element epsilon) +{ + return float_equal_epsilon(self.x(), other.x(), epsilon) + && float_equal_epsilon(self.y(), other.y(), epsilon) + && float_equal_epsilon(self.z(), other.z(), epsilon) + && float_equal_epsilon(self.w(), other.w(), epsilon); +} + +template +inline BasicVector4 vector4_added(const BasicVector4& self, const BasicVector4& other) +{ + return BasicVector4( + float(self.x() + other.x()), + float(self.y() + other.y()), + float(self.z() + other.z()), + float(self.w() + other.w()) + ); +} +template +inline BasicVector4 operator+(const BasicVector4& self, const BasicVector4& other) +{ + return vector4_added(self, other); +} +template +inline void vector4_add(BasicVector4& self, const BasicVector4& other) +{ + self.x() += static_cast(other.x()); + self.y() += static_cast(other.y()); + self.z() += static_cast(other.z()); + self.w() += static_cast(other.w()); +} +template +inline void operator+=(BasicVector4& self, const BasicVector4& other) +{ + vector4_add(self, other); +} + +template +inline BasicVector4 vector4_subtracted(const BasicVector4& self, const BasicVector4& other) +{ + return BasicVector4( + float(self.x() - other.x()), + float(self.y() - other.y()), + float(self.z() - other.z()), + float(self.w() - other.w()) + ); +} +template +inline BasicVector4 operator-(const BasicVector4& self, const BasicVector4& other) +{ + return vector4_subtracted(self, other); +} +template +inline void vector4_subtract(BasicVector4& self, const BasicVector4& other) +{ + self.x() -= static_cast(other.x()); + self.y() -= static_cast(other.y()); + self.z() -= static_cast(other.z()); + self.w() -= static_cast(other.w()); +} +template +inline void operator-=(BasicVector4& self, const BasicVector4& other) +{ + vector4_subtract(self, other); +} + +template +inline BasicVector4 vector4_scaled(const BasicVector4& self, const BasicVector4& other) +{ + return BasicVector4( + float(self.x() * other.x()), + float(self.y() * other.y()), + float(self.z() * other.z()), + float(self.w() * other.w()) + ); +} +template +inline BasicVector4 operator*(const BasicVector4& self, const BasicVector4& other) +{ + return vector4_scaled(self, other); +} +template +inline void vector4_scale(BasicVector4& self, const BasicVector4& other) +{ + self.x() *= static_cast(other.x()); + self.y() *= static_cast(other.y()); + self.z() *= static_cast(other.z()); + self.w() *= static_cast(other.w()); +} +template +inline void operator*=(BasicVector4& self, const BasicVector4& other) +{ + vector4_scale(self, other); +} + +template +inline BasicVector4 vector4_scaled(const BasicVector4& self, OtherElement scale) +{ + return BasicVector4( + float(self.x() * scale), + float(self.y() * scale), + float(self.z() * scale), + float(self.w() * scale) + ); +} +template +inline BasicVector4 operator*(const BasicVector4& self, OtherElement scale) +{ + return vector4_scaled(self, scale); +} +template +inline void vector4_scale(BasicVector4& self, OtherElement scale) +{ + self.x() *= static_cast(scale); + self.y() *= static_cast(scale); + self.z() *= static_cast(scale); + self.w() *= static_cast(scale); +} +template +inline void operator*=(BasicVector4& self, OtherElement scale) +{ + vector4_scale(self, scale); +} + +template +inline BasicVector4 vector4_divided(const BasicVector4& self, OtherElement divisor) +{ + return BasicVector4( + float(self.x() / divisor), + float(self.y() / divisor), + float(self.z() / divisor), + float(self.w() / divisor) + ); +} +template +inline BasicVector4 operator/(const BasicVector4& self, OtherElement divisor) +{ + return vector4_divided(self, divisor); +} +template +inline void vector4_divide(BasicVector4& self, OtherElement divisor) +{ + self.x() /= divisor; + self.y() /= divisor; + self.z() /= divisor; + self.w() /= divisor; +} +template +inline void operator/=(BasicVector4& self, OtherElement divisor) +{ + vector4_divide(self, divisor); +} + +template +inline double vector4_dot(const BasicVector4& self, const BasicVector4& other) +{ + return self.x() * other.x() + self.y() * other.y() + self.z() * other.z() + self.w() * other.w(); +} + +template +inline BasicVector3 vector4_projected(const BasicVector4& self) +{ + return vector3_scaled(vector4_to_vector3(self), 1.0 / self[3]); +} + +#endif diff --git a/libs/mathlib.h b/libs/mathlib.h new file mode 100644 index 00000000..04fbc0f0 --- /dev/null +++ b/libs/mathlib.h @@ -0,0 +1,422 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __MATHLIB__ +#define __MATHLIB__ + +// mathlib.h +#include + +#ifdef __cplusplus + +// start declarations of functions defined in C library. +extern "C" +{ + +#endif + +#include "bytebool.h" + +typedef float vec_t; +typedef vec_t vec3_t[3]; +typedef vec_t vec5_t[5]; +typedef vec_t vec4_t[4]; + +#define SIDE_FRONT 0 +#define SIDE_ON 2 +#define SIDE_BACK 1 +#define SIDE_CROSS -2 + +// plane types are used to speed some tests +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 +#define PLANE_NON_AXIAL 3 + +#define Q_PI 3.14159265358979323846f + +extern const vec3_t vec3_origin; + +extern const vec3_t g_vec3_axis_x; +extern const vec3_t g_vec3_axis_y; +extern const vec3_t g_vec3_axis_z; + +#define EQUAL_EPSILON 0.001 + +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) +#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) +#define VectorIncrement(a,b) ((b)[0]+=(a)[0],(b)[1]+=(a)[1],(b)[2]+=(a)[2]) +#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) +#define VectorSet(v, a, b, c) ((v)[0]=(a),(v)[1]=(b),(v)[2]=(c)) +#define VectorScale(a,b,c) ((c)[0]=(b)*(a)[0],(c)[1]=(b)*(a)[1],(c)[2]=(b)*(a)[2]) +#define VectorMid(a,b,c) ((c)[0]=((a)[0]+(b)[0])*0.5f,(c)[1]=((a)[1]+(b)[1])*0.5f,(c)[2]=((a)[2]+(b)[2])*0.5f) +#define VectorNegate(a,b) ((b)[0]=-(a)[0],(b)[1]=-(a)[1],(b)[2]=-(a)[2]) +#define CrossProduct(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0]) +#define VectorClear(x) ((x)[0]=(x)[1]=(x)[2]=0) + +#define FLOAT_SNAP(f,snap) ( (float)( floor( (f) / (snap) + 0.5 ) * (snap) ) ) +#define FLOAT_TO_INTEGER(f) ( (float)( floor( (f) + 0.5 ) ) ) + +#define Q_rint(in) ((vec_t)floor(in+0.5)) + +qboolean VectorCompare (const vec3_t v1, const vec3_t v2); + +vec_t VectorLength(const vec3_t v); + +void VectorMA( const vec3_t va, vec_t scale, const vec3_t vb, vec3_t vc ); + +void _CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +vec_t VectorNormalize (const vec3_t in, vec3_t out); +vec_t ColorNormalize( const vec3_t in, vec3_t out ); +void VectorInverse (vec3_t v); +void VectorPolar(vec3_t v, float radius, float theta, float phi); + +// default snapping, to 1 +void VectorSnap(vec3_t v); + +// integer snapping +void VectorISnap(vec3_t point, int snap); + +// Gef: added snap to float for sub-integer grid sizes +// TTimo: we still use the int version of VectorSnap when possible +// to avoid potential rounding issues +// TTimo: renaming to VectorFSnap for C implementation +void VectorFSnap(vec3_t point, float snap); + +// NOTE: added these from Ritual's Q3Radiant +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); + + +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +void VectorToAngles( vec3_t vec, vec3_t angles ); + +#define ZERO_EPSILON 1.0E-6 +#define RAD2DEGMULT 57.29577951308232f +#define DEG2RADMULT 0.01745329251994329f +#define RAD2DEG( a ) ( (a) * RAD2DEGMULT ) +#define DEG2RAD( a ) ( (a) * DEG2RADMULT ) + +void VectorRotate (vec3_t vIn, vec3_t vRotation, vec3_t out); +void VectorRotateOrigin (vec3_t vIn, vec3_t vRotation, vec3_t vOrigin, vec3_t out); + +// some function merged from tools mathlib code + +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ); +void NormalToLatLong( const vec3_t normal, byte bytes[2] ); +int PlaneTypeForNormal (vec3_t normal); +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ); + + +/*! +\todo +FIXME test calls such as intersect tests should be named test_ +*/ + +typedef vec_t m3x3_t[9]; +/*!NOTE +m4x4 looks like this.. + + x y z +x axis ( 0 1 2) +y axis ( 4 5 6) +z axis ( 8 9 10) +translation (12 13 14) +scale ( 0 5 10) +*/ +typedef vec_t m4x4_t[16]; + +#define M4X4_INDEX(m,row,col) (m[(col<<2)+row]) + +typedef enum { eXYZ, eYZX, eZXY, eXZY, eYXZ, eZYX } eulerOrder_t; + +#define CLIP_PASS 0x00 // 000000 +#define CLIP_LT_X 0x01 // 000001 +#define CLIP_GT_X 0x02 // 000010 +#define CLIP_LT_Y 0x04 // 000100 +#define CLIP_GT_Y 0x08 // 001000 +#define CLIP_LT_Z 0x10 // 010000 +#define CLIP_GT_Z 0x20 // 100000 +#define CLIP_FAIL 0x3F // 111111 +typedef unsigned char clipmask_t; + +extern const m4x4_t g_m4x4_identity; + +#define M4X4_COPY(dst,src) (\ +(dst)[0]=(src)[0],\ +(dst)[1]=(src)[1],\ +(dst)[2]=(src)[2],\ +(dst)[3]=(src)[3],\ +(dst)[4]=(src)[4],\ +(dst)[5]=(src)[5],\ +(dst)[6]=(src)[6],\ +(dst)[7]=(src)[7],\ +(dst)[8]=(src)[8],\ +(dst)[9]=(src)[9],\ +(dst)[10]=(src)[10],\ +(dst)[11]=(src)[11],\ +(dst)[12]=(src)[12],\ +(dst)[13]=(src)[13],\ +(dst)[14]=(src)[14],\ +(dst)[15]=(src)[15]) + +typedef enum +{ + eRightHanded = 0, + eLeftHanded = 1, +} +m4x4Handedness_t; + +m4x4Handedness_t m4x4_handedness(const m4x4_t matrix); + +/*! assign other m4x4 to this m4x4 */ +void m4x4_assign(m4x4_t matrix, const m4x4_t other); + +// constructors +/*! create m4x4 as identity matrix */ +void m4x4_identity(m4x4_t matrix); +/*! create m4x4 as a translation matrix, for a translation vec3 */ +void m4x4_translation_for_vec3(m4x4_t matrix, const vec3_t translation); +/*! create m4x4 as a rotation matrix, for an euler angles (degrees) vec3 */ +void m4x4_rotation_for_vec3(m4x4_t matrix, const vec3_t euler, eulerOrder_t order); +/*! create m4x4 as a scaling matrix, for a scale vec3 */ +void m4x4_scale_for_vec3(m4x4_t matrix, const vec3_t scale); +/*! create m4x4 as a rotation matrix, for a quaternion vec4 */ +void m4x4_rotation_for_quat(m4x4_t matrix, const vec4_t rotation); +/*! create m4x4 as a rotation matrix, for an axis vec3 and an angle (radians) */ +void m4x4_rotation_for_axisangle(m4x4_t matrix, const vec3_t axis, double angle); +/*! generate a perspective matrix by specifying the view frustum */ +void m4x4_frustum(m4x4_t matrix, vec_t left, vec_t right, vec_t bottom, vec_t top, vec_t nearval, vec_t farval); + +// a valid m4x4 to access is always first argument +/*! extract translation vec3 from matrix */ +void m4x4_get_translation_vec3(const m4x4_t matrix, vec3_t translation); +/*! extract euler rotation angles from a rotation-only matrix */ +void m4x4_get_rotation_vec3(const m4x4_t matrix, vec3_t euler, eulerOrder_t order); +/*! extract scale vec3 from matrix */ +void m4x4_get_scale_vec3(const m4x4_t matrix, vec3_t scale); +/*! extract translation/euler/scale from an orthogonal matrix. NOTE: requires right-handed axis-base */ +void m4x4_get_transform_vec3(const m4x4_t matrix, vec3_t translation, vec3_t euler, eulerOrder_t order, vec3_t scale); + +// a valid m4x4 to be modified is always first argument +/*! translate m4x4 by a translation vec3 */ +void m4x4_translate_by_vec3(m4x4_t matrix, const vec3_t translation); +/*! rotate m4x4 by a euler (degrees) vec3 */ +void m4x4_rotate_by_vec3(m4x4_t matrix, const vec3_t euler, eulerOrder_t order); +/*! scale m4x4 by a scaling vec3 */ +void m4x4_scale_by_vec3(m4x4_t matrix, const vec3_t scale); +/*! rotate m4x4 by a quaternion vec4 */ +void m4x4_rotate_by_quat(m4x4_t matrix, const vec4_t rotation); +/*! rotate m4x4 by an axis vec3 and an angle (radians) */ +void m4x4_rotate_by_axisangle(m4x4_t matrix, const vec3_t axis, double angle); +/*! transform m4x4 by translation/eulerZYX/scaling vec3 (transform = scale * eulerZ * eulerY * eulerX * translation) */ +void m4x4_transform_by_vec3(m4x4_t matrix, const vec3_t translation, const vec3_t euler, eulerOrder_t order, const vec3_t scale); +/*! rotate m4x4 around a pivot point by eulerZYX vec3 */ +void m4x4_pivoted_rotate_by_vec3(m4x4_t matrix, const vec3_t euler, eulerOrder_t order, const vec3_t pivotpoint); +/*! scale m4x4 around a pivot point by scaling vec3 */ +void m4x4_pivoted_scale_by_vec3(m4x4_t matrix, const vec3_t scale, const vec3_t pivotpoint); +/*! transform m4x4 around a pivot point by translation/eulerZYX/scaling vec3 */ +void m4x4_pivoted_transform_by_vec3(m4x4_t matrix, const vec3_t translation, const vec3_t euler, eulerOrder_t order, const vec3_t scale, const vec3_t pivotpoint); +/*! transform m4x4 around a pivot point by translation/rotation/scaling vec3 */ +void m4x4_pivoted_transform_by_rotation(m4x4_t matrix, const vec3_t translation, const m4x4_t rotation, const vec3_t scale, const vec3_t pivotpoint); +/*! rotate m4x4 around a pivot point by quaternion vec4 */ +void m4x4_pivoted_rotate_by_quat(m4x4_t matrix, const vec4_t quat, const vec3_t pivotpoint); +/*! rotate m4x4 around a pivot point by axis vec3 and angle (radians) */ +void m4x4_pivoted_rotate_by_axisangle(m4x4_t matrix, const vec3_t axis, double angle, const vec3_t pivotpoint); + +/*! postmultiply m4x4 by another m4x4 */ +void m4x4_multiply_by_m4x4(m4x4_t matrix, const m4x4_t matrix_src); +/*! premultiply m4x4 by another m4x4 */ +void m4x4_premultiply_by_m4x4(m4x4_t matrix, const m4x4_t matrix_src); +/*! postmultiply orthogonal m4x4 by another orthogonal m4x4 */ +void m4x4_orthogonal_multiply_by_m4x4(m4x4_t matrix, const m4x4_t matrix_src); +/*! premultiply orthogonal m4x4 by another orthogonal m4x4 */ +void m4x4_orthogonal_premultiply_by_m4x4(m4x4_t matrix, const m4x4_t matrix_src); + +/*! multiply a point (x,y,z,1) by matrix */ +void m4x4_transform_point(const m4x4_t matrix, vec3_t point); +/*! multiply a normal (x,y,z,0) by matrix */ +void m4x4_transform_normal(const m4x4_t matrix, vec3_t normal); +/*! multiply a vec4 (x,y,z,w) by matrix */ +void m4x4_transform_vec4(const m4x4_t matrix, vec4_t vector); + +/*! multiply a point (x,y,z,1) by matrix */ +void m4x4_transform_point(const m4x4_t matrix, vec3_t point); +/*! multiply a normal (x,y,z,0) by matrix */ +void m4x4_transform_normal(const m4x4_t matrix, vec3_t normal); + +/*! transpose a m4x4 */ +void m4x4_transpose(m4x4_t matrix); +/*! invert an orthogonal 4x3 subset of a 4x4 matrix */ +int m4x4_orthogonal_invert(m4x4_t matrix); +/*! invert any m4x4 using Kramer's rule.. return 1 if matrix is singular, else return 0 */ +int m4x4_invert(m4x4_t matrix); + +/*! clip a point (x,y,z,1) by canonical matrix */ +clipmask_t m4x4_clip_point(const m4x4_t matrix, const vec3_t point, vec4_t clipped); +/*! device-space polygon for clipped triangle */ +unsigned int m4x4_clip_triangle(const m4x4_t matrix, const vec3_t p0, const vec3_t p1, const vec3_t p2, vec4_t clipped[9]); +/*! device-space line for clipped line */ +unsigned int m4x4_clip_line(const m4x4_t matrix, const vec3_t p0, const vec3_t p1, vec4_t clipped[2]); + + +//! quaternion identity +void quat_identity(vec4_t quat); +//! quaternion from two unit vectors +void quat_for_unit_vectors(vec4_t quat, const vec3_t from, const vec3_t to); +//! quaternion from axis and angle (radians) +void quat_for_axisangle(vec4_t quat, const vec3_t axis, double angle); +//! concatenates two rotations.. equivalent to m4x4_multiply_by_m4x4 .. postmultiply.. the right-hand side is the first rotation performed +void quat_multiply_by_quat(vec4_t quat, const vec4_t other); +//! negate a quaternion +void quat_conjugate(vec4_t quat); +//! normalise a quaternion +void quat_normalise(vec4_t quat); + + + +/*! +\todo object/ray intersection functions should maybe return a point rather than a distance? +*/ + +/*! +aabb_t - "axis-aligned" bounding box... + origin: centre of bounding box... + extents: +/- extents of box from origin... +*/ +typedef struct aabb_s +{ + vec3_t origin; + vec3_t extents; +} aabb_t; + +extern const aabb_t g_aabb_null; + +/*! +bbox_t - oriented bounding box... + aabb: axis-aligned bounding box... + axes: orientation axes... +*/ +typedef struct bbox_s +{ + aabb_t aabb; + vec3_t axes[3]; + vec_t radius; +} bbox_t; + +/*! +ray_t - origin point and direction unit-vector +*/ +typedef struct ray_s +{ + vec3_t origin; + vec3_t direction; +} ray_t; + +/*! +line_t - centre point and displacement of end point from centre +*/ +typedef struct line_s +{ + vec3_t origin; + vec3_t extents; +} line_t; + + +/*! Generate line from start/end points. */ +void line_construct_for_vec3(line_t* line, const vec3_t start, const vec3_t end); +/*! Return 2 if line is behind plane, else return 1 if line intersects plane, else return 0. */ +int line_test_plane(const line_t* line, const vec4_t plane); + +/*! Generate AABB from min/max. */ +void aabb_construct_for_vec3(aabb_t* aabb, const vec3_t min, const vec3_t max); +/*! Initialise AABB to negative size. */ +void aabb_clear(aabb_t* aabb); + +/*! Extend AABB to include point. */ +void aabb_extend_by_point(aabb_t* aabb, const vec3_t point); +/*! Extend AABB to include aabb_src. */ +void aabb_extend_by_aabb(aabb_t* aabb, const aabb_t* aabb_src); +/*! Extend AABB by +/- extension vector. */ +void aabb_extend_by_vec3(aabb_t* aabb, vec3_t extension); + +/*! Return 2 if point is inside, else 1 if point is on surface, else 0. */ +int aabb_test_point(const aabb_t* aabb, const vec3_t point); +/*! Return 2 if aabb_src intersects, else 1 if aabb_src touches exactly, else 0. */ +int aabb_test_aabb(const aabb_t* aabb, const aabb_t* aabb_src); +/*! Return 2 if aabb is behind plane, else 1 if aabb intersects plane, else 0. */ +int aabb_test_plane(const aabb_t* aabb, const float* plane); +/*! Return 1 if aabb intersects ray, else 0... dist = closest intersection. */ +int aabb_intersect_ray(const aabb_t* aabb, const ray_t* ray, vec3_t intersection); +/*! Return 1 if aabb intersects ray, else 0. Faster, but does not provide point of intersection */ +int aabb_test_ray(const aabb_t* aabb, const ray_t* ray); + +/*! Return 2 if oriented aabb is behind plane, else 1 if aabb intersects plane, else 0. */ +int aabb_oriented_intersect_plane(const aabb_t* aabb, const m4x4_t transform, const vec_t* plane); + +/*! Calculate the corners of the aabb. */ +void aabb_corners(const aabb_t* aabb, vec3_t corners[8]); + +/*! (deprecated) Generate AABB from oriented bounding box. */ +void aabb_for_bbox(aabb_t* aabb, const bbox_t* bbox); +/*! (deprecated) Generate AABB from 2-dimensions of min/max, specified by axis. */ +void aabb_for_area(aabb_t* aabb, vec3_t area_tl, vec3_t area_br, int axis); +/*! Generate AABB to contain src* transform. NOTE: transform must be orthogonal */ +void aabb_for_transformed_aabb(aabb_t* dst, const aabb_t* src, const m4x4_t transform); + +/*! Update bounding-sphere radius. */ +void bbox_update_radius(bbox_t* bbox); +/*! Generate oriented bounding box from AABB and transformation matrix. */ +/*!\todo Remove need to specify eulerZYX/scale. */ +void bbox_for_oriented_aabb(bbox_t* bbox, const aabb_t* aabb, + const m4x4_t matrix, const vec3_t eulerZYX, const vec3_t scale); +/*! Return 2 if bbox is behind plane, else return 1 if bbox intersects plane, else return 0. */ +int bbox_intersect_plane(const bbox_t* bbox, const vec_t* plane); + + +/*! Generate a ray from an origin point and a direction unit-vector */ +void ray_construct_for_vec3(ray_t* ray, const vec3_t origin, const vec3_t direction); + +/*! Transform a ray */ +void ray_transform(ray_t* ray, const m4x4_t matrix); + +/*! distance from ray origin in ray direction to point. FLT_MAX if no intersection. */ +vec_t ray_intersect_point(const ray_t* ray, const vec3_t point, vec_t epsilon, vec_t divergence); +/*! distance from ray origin in ray direction to triangle. FLT_MAX if no intersection. */ +vec_t ray_intersect_triangle(const ray_t* ray, qboolean bCullBack, const vec3_t vert0, const vec3_t vert1, const vec3_t vert2); +/*! distance from ray origin in ray direction to plane. */ +vec_t ray_intersect_plane(const ray_t* ray, const vec3_t normal, vec_t dist); + + +int plane_intersect_planes(const vec4_t plane1, const vec4_t plane2, const vec4_t plane3, vec3_t intersection); + + +#ifdef __cplusplus +} +#endif + +#endif /* __MATHLIB__ */ diff --git a/libs/mathlib/bbox.c b/libs/mathlib/bbox.c new file mode 100644 index 00000000..e8dc8657 --- /dev/null +++ b/libs/mathlib/bbox.c @@ -0,0 +1,462 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +#include "mathlib.h" + +const aabb_t g_aabb_null = { + { 0, 0, 0, }, + { -FLT_MAX, -FLT_MAX, -FLT_MAX, }, +}; + +void aabb_construct_for_vec3(aabb_t *aabb, const vec3_t min, const vec3_t max) +{ + VectorMid(min, max, aabb->origin); + VectorSubtract(max, aabb->origin, aabb->extents); +} + +void aabb_clear(aabb_t *aabb) +{ + VectorCopy(g_aabb_null.origin, aabb->origin); + VectorCopy(g_aabb_null.extents, aabb->extents); +} + +void aabb_extend_by_point(aabb_t *aabb, const vec3_t point) +{ +#if 1 + int i; + vec_t min, max, displacement; + for(i=0; i<3; i++) + { + displacement = point[i] - aabb->origin[i]; + if(fabs(displacement) > aabb->extents[i]) + { + if(aabb->extents[i] < 0) // degenerate + { + min = max = point[i]; + } + else if(displacement > 0) + { + min = aabb->origin[i] - aabb->extents[i]; + max = aabb->origin[i] + displacement; + } + else + { + max = aabb->origin[i] + aabb->extents[i]; + min = aabb->origin[i] + displacement; + } + aabb->origin[i] = (min + max) * 0.5f; + aabb->extents[i] = max - aabb->origin[i]; + } + } +#else + unsigned int i; + for(i=0; i<3; ++i) + { + if(aabb->extents[i] < 0) // degenerate + { + aabb->origin[i] = point[i]; + aabb->extents[i] = 0; + } + else + { + vec_t displacement = point[i] - aabb->origin[i]; + vec_t increment = (vec_t)fabs(displacement) - aabb->extents[i]; + if(increment > 0) + { + increment *= (vec_t)((displacement > 0) ? 0.5 : -0.5); + aabb->extents[i] += increment; + aabb->origin[i] += increment; + } + } + } +#endif +} + +void aabb_extend_by_aabb(aabb_t *aabb, const aabb_t *aabb_src) +{ + int i; + vec_t min, max, displacement, difference; + for(i=0; i<3; i++) + { + displacement = aabb_src->origin[i] - aabb->origin[i]; + difference = aabb_src->extents[i] - aabb->extents[i]; + if(aabb->extents[i] < 0 + || difference >= fabs(displacement)) + { + // 2nd contains 1st + aabb->extents[i] = aabb_src->extents[i]; + aabb->origin[i] = aabb_src->origin[i]; + } + else if(aabb_src->extents[i] < 0 + || -difference >= fabs(displacement)) + { + // 1st contains 2nd + continue; + } + else + { + // not contained + if(displacement > 0) + { + min = aabb->origin[i] - aabb->extents[i]; + max = aabb_src->origin[i] + aabb_src->extents[i]; + } + else + { + min = aabb_src->origin[i] - aabb_src->extents[i]; + max = aabb->origin[i] + aabb->extents[i]; + } + aabb->origin[i] = (min + max) * 0.5f; + aabb->extents[i] = max - aabb->origin[i]; + } + } +} + +void aabb_extend_by_vec3(aabb_t *aabb, vec3_t extension) +{ + VectorAdd(aabb->extents, extension, aabb->extents); +} + +int aabb_test_point(const aabb_t *aabb, const vec3_t point) +{ + int i; + for(i=0; i<3; i++) + if(fabs(point[i] - aabb->origin[i]) >= aabb->extents[i]) + return 0; + return 1; +} + +int aabb_test_aabb(const aabb_t *aabb, const aabb_t *aabb_src) +{ + int i; + for (i=0; i<3; i++) + if ( fabs(aabb_src->origin[i] - aabb->origin[i]) > (fabs(aabb->extents[i]) + fabs(aabb_src->extents[i])) ) + return 0; + return 1; +} + +int aabb_test_plane(const aabb_t *aabb, const float *plane) +{ + float fDist, fIntersect; + + // calc distance of origin from plane + fDist = DotProduct(plane, aabb->origin) + plane[3]; + + // calc extents distance relative to plane normal + fIntersect = (vec_t)(fabs(plane[0] * aabb->extents[0]) + fabs(plane[1] * aabb->extents[1]) + fabs(plane[2] * aabb->extents[2])); + // accept if origin is less than or equal to this distance + if (fabs(fDist) < fIntersect) return 1; // partially inside + else if (fDist < 0) return 2; // totally inside + return 0; // totally outside +} + +/* +Fast Ray-Box Intersection +by Andrew Woo +from "Graphics Gems", Academic Press, 1990 +*/ + +#define NUMDIM 3 +#define RIGHT 0 +#define LEFT 1 +#define MIDDLE 2 + +int aabb_intersect_ray(const aabb_t *aabb, const ray_t *ray, vec3_t intersection) +{ + int inside = 1; + char quadrant[NUMDIM]; + register int i; + int whichPlane; + double maxT[NUMDIM]; + double candidatePlane[NUMDIM]; + + const float *origin = ray->origin; + const float *direction = ray->direction; + + /* Find candidate planes; this loop can be avoided if + rays cast all from the eye(assume perpsective view) */ + for (i=0; iorigin[i] - aabb->extents[i])) + { + quadrant[i] = LEFT; + candidatePlane[i] = (aabb->origin[i] - aabb->extents[i]); + inside = 0; + } + else if (origin[i] > (aabb->origin[i] + aabb->extents[i])) + { + quadrant[i] = RIGHT; + candidatePlane[i] = (aabb->origin[i] + aabb->extents[i]); + inside = 0; + } + else + { + quadrant[i] = MIDDLE; + } + } + + /* Ray origin inside bounding box */ + if(inside == 1) + { + VectorCopy(ray->origin, intersection); + return 1; + } + + + /* Calculate T distances to candidate planes */ + for (i = 0; i < NUMDIM; i++) + { + if (quadrant[i] != MIDDLE && direction[i] !=0.) + maxT[i] = (candidatePlane[i] - origin[i]) / direction[i]; + else + maxT[i] = -1.; + } + + /* Get largest of the maxT's for final choice of intersection */ + whichPlane = 0; + for (i = 1; i < NUMDIM; i++) + if (maxT[whichPlane] < maxT[i]) + whichPlane = i; + + /* Check final candidate actually inside box */ + if (maxT[whichPlane] < 0.) + return 0; + for (i = 0; i < NUMDIM; i++) + { + if (whichPlane != i) + { + intersection[i] = (vec_t)(origin[i] + maxT[whichPlane] * direction[i]); + if (fabs(intersection[i] - aabb->origin[i]) > aabb->extents[i]) + return 0; + } + else + { + intersection[i] = (vec_t)candidatePlane[i]; + } + } + + return 1; /* ray hits box */ +} + +int aabb_test_ray(const aabb_t* aabb, const ray_t* ray) +{ + vec3_t displacement, ray_absolute; + vec_t f; + + displacement[0] = ray->origin[0] - aabb->origin[0]; + if(fabs(displacement[0]) > aabb->extents[0] && displacement[0] * ray->direction[0] >= 0.0f) + return 0; + + displacement[1] = ray->origin[1] - aabb->origin[1]; + if(fabs(displacement[1]) > aabb->extents[1] && displacement[1] * ray->direction[1] >= 0.0f) + return 0; + + displacement[2] = ray->origin[2] - aabb->origin[2]; + if(fabs(displacement[2]) > aabb->extents[2] && displacement[2] * ray->direction[2] >= 0.0f) + return 0; + + ray_absolute[0] = (float)fabs(ray->direction[0]); + ray_absolute[1] = (float)fabs(ray->direction[1]); + ray_absolute[2] = (float)fabs(ray->direction[2]); + + f = ray->direction[1] * displacement[2] - ray->direction[2] * displacement[1]; + if((float)fabs(f) > aabb->extents[1] * ray_absolute[2] + aabb->extents[2] * ray_absolute[1]) + return 0; + + f = ray->direction[2] * displacement[0] - ray->direction[0] * displacement[2]; + if((float)fabs(f) > aabb->extents[0] * ray_absolute[2] + aabb->extents[2] * ray_absolute[0]) + return 0; + + f = ray->direction[0] * displacement[1] - ray->direction[1] * displacement[0]; + if((float)fabs(f) > aabb->extents[0] * ray_absolute[1] + aabb->extents[1] * ray_absolute[0]) + return 0; + + return 1; +} + +void aabb_orthogonal_transform(aabb_t* dst, const aabb_t* src, const m4x4_t transform) +{ + VectorCopy(src->origin, dst->origin); + m4x4_transform_point(transform, dst->origin); + + dst->extents[0] = (vec_t)(fabs(transform[0] * src->extents[0]) + + fabs(transform[4] * src->extents[1]) + + fabs(transform[8] * src->extents[2])); + dst->extents[1] = (vec_t)(fabs(transform[1] * src->extents[0]) + + fabs(transform[5] * src->extents[1]) + + fabs(transform[9] * src->extents[2])); + dst->extents[2] = (vec_t)(fabs(transform[2] * src->extents[0]) + + fabs(transform[6] * src->extents[1]) + + fabs(transform[10] * src->extents[2])); +} + +void aabb_for_bbox(aabb_t *aabb, const bbox_t *bbox) +{ + int i; + vec3_t temp[3]; + + VectorCopy(bbox->aabb.origin, aabb->origin); + + // calculate the AABB extents in local coord space from the OBB extents and axes + VectorScale(bbox->axes[0], bbox->aabb.extents[0], temp[0]); + VectorScale(bbox->axes[1], bbox->aabb.extents[1], temp[1]); + VectorScale(bbox->axes[2], bbox->aabb.extents[2], temp[2]); + for(i=0;i<3;i++) aabb->extents[i] = (vec_t)(fabs(temp[0][i]) + fabs(temp[1][i]) + fabs(temp[2][i])); +} + +void aabb_for_area(aabb_t *aabb, vec3_t area_tl, vec3_t area_br, int axis) +{ + aabb_clear(aabb); + aabb->extents[axis] = FLT_MAX; + aabb_extend_by_point(aabb, area_tl); + aabb_extend_by_point(aabb, area_br); +} + +int aabb_oriented_intersect_plane(const aabb_t *aabb, const m4x4_t transform, const vec_t* plane) +{ + vec_t fDist, fIntersect; + + // calc distance of origin from plane + fDist = DotProduct(plane, aabb->origin) + plane[3]; + + // calc extents distance relative to plane normal + fIntersect = (vec_t)(fabs(aabb->extents[0] * DotProduct(plane, transform)) + + fabs(aabb->extents[1] * DotProduct(plane, transform+4)) + + fabs(aabb->extents[2] * DotProduct(plane, transform+8))); + // accept if origin is less than this distance + if (fabs(fDist) < fIntersect) return 1; // partially inside + else if (fDist < 0) return 2; // totally inside + return 0; // totally outside +} + +void aabb_corners(const aabb_t* aabb, vec3_t corners[8]) +{ + vec3_t min, max; + VectorSubtract(aabb->origin, aabb->extents, min); + VectorAdd(aabb->origin, aabb->extents, max); + VectorSet(corners[0], min[0], max[1], max[2]); + VectorSet(corners[1], max[0], max[1], max[2]); + VectorSet(corners[2], max[0], min[1], max[2]); + VectorSet(corners[3], min[0], min[1], max[2]); + VectorSet(corners[4], min[0], max[1], min[2]); + VectorSet(corners[5], max[0], max[1], min[2]); + VectorSet(corners[6], max[0], min[1], min[2]); + VectorSet(corners[7], min[0], min[1], min[2]); +} + + +void bbox_update_radius(bbox_t *bbox) +{ + bbox->radius = VectorLength(bbox->aabb.extents); +} + +void aabb_for_transformed_aabb(aabb_t* dst, const aabb_t* src, const m4x4_t transform) +{ + if(src->extents[0] < 0 + || src->extents[1] < 0 + || src->extents[2] < 0) + { + aabb_clear(dst); + return; + } + + VectorCopy(src->origin, dst->origin); + m4x4_transform_point(transform, dst->origin); + + dst->extents[0] = (vec_t)(fabs(transform[0] * src->extents[0]) + + fabs(transform[4] * src->extents[1]) + + fabs(transform[8] * src->extents[2])); + dst->extents[1] = (vec_t)(fabs(transform[1] * src->extents[0]) + + fabs(transform[5] * src->extents[1]) + + fabs(transform[9] * src->extents[2])); + dst->extents[2] = (vec_t)(fabs(transform[2] * src->extents[0]) + + fabs(transform[6] * src->extents[1]) + + fabs(transform[10] * src->extents[2])); +} + +void bbox_for_oriented_aabb(bbox_t *bbox, const aabb_t *aabb, const m4x4_t matrix, const vec3_t euler, const vec3_t scale) +{ + double rad[3]; + double pi_180 = Q_PI / 180; + double A, B, C, D, E, F, AD, BD; + + VectorCopy(aabb->origin, bbox->aabb.origin); + + m4x4_transform_point(matrix, bbox->aabb.origin); + + bbox->aabb.extents[0] = aabb->extents[0] * scale[0]; + bbox->aabb.extents[1] = aabb->extents[1] * scale[1]; + bbox->aabb.extents[2] = aabb->extents[2] * scale[2]; + + rad[0] = euler[0] * pi_180; + rad[1] = euler[1] * pi_180; + rad[2] = euler[2] * pi_180; + + A = cos(rad[0]); + B = sin(rad[0]); + C = cos(rad[1]); + D = sin(rad[1]); + E = cos(rad[2]); + F = sin(rad[2]); + + AD = A * -D; + BD = B * -D; + + bbox->axes[0][0] = (vec_t)(C*E); + bbox->axes[0][1] = (vec_t)(-BD*E + A*F); + bbox->axes[0][2] = (vec_t)(AD*E + B*F); + bbox->axes[1][0] = (vec_t)(-C*F); + bbox->axes[1][1] = (vec_t)(BD*F + A*E); + bbox->axes[1][2] = (vec_t)(-AD*F + B*E); + bbox->axes[2][0] = (vec_t)D; + bbox->axes[2][1] = (vec_t)(-B*C); + bbox->axes[2][2] = (vec_t)(A*C); + + bbox_update_radius(bbox); +} + +int bbox_intersect_plane(const bbox_t *bbox, const vec_t* plane) +{ + vec_t fDist, fIntersect; + + // calc distance of origin from plane + fDist = DotProduct(plane, bbox->aabb.origin) + plane[3]; + + // trivial accept/reject using bounding sphere + if (fabs(fDist) > bbox->radius) + { + if (fDist < 0) + return 2; // totally inside + else + return 0; // totally outside + } + + // calc extents distance relative to plane normal + fIntersect = (vec_t)(fabs(bbox->aabb.extents[0] * DotProduct(plane, bbox->axes[0])) + + fabs(bbox->aabb.extents[1] * DotProduct(plane, bbox->axes[1])) + + fabs(bbox->aabb.extents[2] * DotProduct(plane, bbox->axes[2]))); + // accept if origin is less than this distance + if (fabs(fDist) < fIntersect) return 1; // partially inside + else if (fDist < 0) return 2; // totally inside + return 0; // totally outside +} diff --git a/libs/mathlib/line.c b/libs/mathlib/line.c new file mode 100644 index 00000000..41b868fb --- /dev/null +++ b/libs/mathlib/line.c @@ -0,0 +1,41 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "mathlib.h" + +void line_construct_for_vec3(line_t *line, const vec3_t start, const vec3_t end) +{ + VectorMid(start, end, line->origin); + VectorSubtract(end, line->origin, line->extents); +} + +int line_test_plane(const line_t* line, const vec4_t plane) +{ + float fDist; + + // calc distance of origin from plane + fDist = DotProduct(plane, line->origin) + plane[3]; + + // accept if origin is less than or equal to this distance + if (fabs(fDist) < fabs(DotProduct(plane, line->extents))) return 1; // partially inside + else if (fDist < 0) return 2; // totally inside + return 0; // totally outside +} diff --git a/libs/mathlib/m4x4.c b/libs/mathlib/m4x4.c new file mode 100644 index 00000000..b2c2db71 --- /dev/null +++ b/libs/mathlib/m4x4.c @@ -0,0 +1,1877 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "mathlib.h" + +const m4x4_t g_m4x4_identity = { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, +}; + +void m4x4_identity(m4x4_t matrix) +{ + matrix[1] = matrix[2] = matrix[3] = + matrix[4] = matrix[6] = matrix[7] = + matrix[8] = matrix[9] = matrix[11] = + matrix[12] = matrix[13] = matrix[14] = 0; + + matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1; +} + +m4x4Handedness_t m4x4_handedness(const m4x4_t matrix) +{ + vec3_t cross; + CrossProduct(matrix+0, matrix+4, cross); + return (DotProduct(matrix+8, cross) < 0) ? eLeftHanded : eRightHanded; +} + +void m4x4_assign(m4x4_t matrix, const m4x4_t other) +{ + M4X4_COPY(matrix, other); +} + +void m4x4_translation_for_vec3(m4x4_t matrix, const vec3_t translation) +{ + matrix[1] = matrix[2] = matrix[3] = + matrix[4] = matrix[6] = matrix[7] = + matrix[8] = matrix[9] = matrix[11] = 0; + + matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1; + + matrix[12] = translation[0]; + matrix[13] = translation[1]; + matrix[14] = translation[2]; +} + +/* +clockwise rotation around X, Y, Z, facing along axis + 1 0 0 cy 0 sy cz sz 0 + 0 cx sx 0 1 0 -sz cz 0 + 0 -sx cx -sy 0 cy 0 0 1 + +rows of Z by cols of Y + cy*cz -sy*cz+sz -sy*sz+cz +-sz*cy -sz*sy+cz + + .. or something like that.. + +final rotation is Z * Y * X + cy*cz -sx*-sy*cz+cx*sz cx*-sy*sz+sx*cz +-cy*sz sx*sy*sz+cx*cz -cx*-sy*sz+sx*cz + sy -sx*cy cx*cy +*/ + +/* transposed +| cy.cz + 0.sz + sy.0 cy.-sz + 0 .cz + sy.0 cy.0 + 0 .0 + sy.1 | +| sx.sy.cz + cx.sz + -sx.cy.0 sx.sy.-sz + cx.cz + -sx.cy.0 sx.sy.0 + cx.0 + -sx.cy.1 | +| -cx.sy.cz + sx.sz + cx.cy.0 -cx.sy.-sz + sx.cz + cx.cy.0 -cx.sy.0 + 0 .0 + cx.cy.1 | +*/ +void m4x4_rotation_for_vec3(m4x4_t matrix, const vec3_t euler, eulerOrder_t order) +{ + double cx, sx, cy, sy, cz, sz; + + cx = cos(DEG2RAD(euler[0])); + sx = sin(DEG2RAD(euler[0])); + cy = cos(DEG2RAD(euler[1])); + sy = sin(DEG2RAD(euler[1])); + cz = cos(DEG2RAD(euler[2])); + sz = sin(DEG2RAD(euler[2])); + + switch(order) + { + case eXYZ: + +#if 1 + + { + matrix[0] = (vec_t)(cy*cz); + matrix[1] = (vec_t)(cy*sz); + matrix[2] = (vec_t)-sy; + matrix[4] = (vec_t)(sx*sy*cz + cx*-sz); + matrix[5] = (vec_t)(sx*sy*sz + cx*cz); + matrix[6] = (vec_t)(sx*cy); + matrix[8] = (vec_t)(cx*sy*cz + sx*sz); + matrix[9] = (vec_t)(cx*sy*sz + -sx*cz); + matrix[10] = (vec_t)(cx*cy); + } + + matrix[12] = matrix[13] = matrix[14] = matrix[3] = matrix[7] = matrix[11] = 0; + matrix[15] = 1; + +#else + + m4x4_identity(matrix); + matrix[5] =(vec_t) cx; matrix[6] =(vec_t) sx; + matrix[9] =(vec_t)-sx; matrix[10]=(vec_t) cx; + + { + m4x4_t temp; + m4x4_identity(temp); + temp[0] =(vec_t) cy; temp[2] =(vec_t)-sy; + temp[8] =(vec_t) sy; temp[10]=(vec_t) cy; + m4x4_premultiply_by_m4x4(matrix, temp); + m4x4_identity(temp); + temp[0] =(vec_t) cz; temp[1] =(vec_t) sz; + temp[4] =(vec_t)-sz; temp[5] =(vec_t) cz; + m4x4_premultiply_by_m4x4(matrix, temp); + } +#endif + + break; + + case eYZX: + m4x4_identity(matrix); + matrix[0] =(vec_t) cy; matrix[2] =(vec_t)-sy; + matrix[8] =(vec_t) sy; matrix[10]=(vec_t) cy; + + { + m4x4_t temp; + m4x4_identity(temp); + temp[5] =(vec_t) cx; temp[6] =(vec_t) sx; + temp[9] =(vec_t)-sx; temp[10]=(vec_t) cx; + m4x4_premultiply_by_m4x4(matrix, temp); + m4x4_identity(temp); + temp[0] =(vec_t) cz; temp[1] =(vec_t) sz; + temp[4] =(vec_t)-sz; temp[5] =(vec_t) cz; + m4x4_premultiply_by_m4x4(matrix, temp); + } + break; + + case eZXY: + m4x4_identity(matrix); + matrix[0] =(vec_t) cz; matrix[1] =(vec_t) sz; + matrix[4] =(vec_t)-sz; matrix[5] =(vec_t) cz; + + { + m4x4_t temp; + m4x4_identity(temp); + temp[5] =(vec_t) cx; temp[6] =(vec_t) sx; + temp[9] =(vec_t)-sx; temp[10]=(vec_t) cx; + m4x4_premultiply_by_m4x4(matrix, temp); + m4x4_identity(temp); + temp[0] =(vec_t) cy; temp[2] =(vec_t)-sy; + temp[8] =(vec_t) sy; temp[10]=(vec_t) cy; + m4x4_premultiply_by_m4x4(matrix, temp); + } + break; + + case eXZY: + m4x4_identity(matrix); + matrix[5] =(vec_t) cx; matrix[6] =(vec_t) sx; + matrix[9] =(vec_t)-sx; matrix[10]=(vec_t) cx; + + { + m4x4_t temp; + m4x4_identity(temp); + temp[0] =(vec_t) cz; temp[1] =(vec_t) sz; + temp[4] =(vec_t)-sz; temp[5] =(vec_t) cz; + m4x4_premultiply_by_m4x4(matrix, temp); + m4x4_identity(temp); + temp[0] =(vec_t) cy; temp[2] =(vec_t)-sy; + temp[8] =(vec_t) sy; temp[10]=(vec_t) cy; + m4x4_premultiply_by_m4x4(matrix, temp); + } + break; + + case eYXZ: + +/* transposed +| cy.cz + sx.sy.-sz + -cx.sy.0 0.cz + cx.-sz + sx.0 sy.cz + -sx.cy.-sz + cx.cy.0 | +| cy.sz + sx.sy.cz + -cx.sy.0 0.sz + cx.cz + sx.0 sy.sz + -sx.cy.cz + cx.cy.0 | +| cy.0 + sx.sy.0 + -cx.sy.1 0.0 + cx.0 + sx.1 sy.0 + -sx.cy.0 + cx.cy.1 | +*/ + +#if 1 + + { + matrix[0] = (vec_t)(cy*cz + sx*sy*-sz); + matrix[1] = (vec_t)(cy*sz + sx*sy*cz); + matrix[2] = (vec_t)(-cx*sy); + matrix[4] = (vec_t)(cx*-sz); + matrix[5] = (vec_t)(cx*cz); + matrix[6] = (vec_t)(sx); + matrix[8] = (vec_t)(sy*cz + -sx*cy*-sz); + matrix[9] = (vec_t)(sy*sz + -sx*cy*cz); + matrix[10] = (vec_t)(cx*cy); + } + + matrix[12] = matrix[13] = matrix[14] = matrix[3] = matrix[7] = matrix[11] = 0; + matrix[15] = 1; + +#else + + m4x4_identity(matrix); + matrix[0] =(vec_t) cy; matrix[2] =(vec_t)-sy; + matrix[8] =(vec_t) sy; matrix[10]=(vec_t) cy; + + { + m4x4_t temp; + m4x4_identity(temp); + temp[5] =(vec_t) cx; temp[6] =(vec_t) sx; + temp[9] =(vec_t)-sx; temp[10]=(vec_t) cx; + m4x4_premultiply_by_m4x4(matrix, temp); + m4x4_identity(temp); + temp[0] =(vec_t) cz; temp[1] =(vec_t) sz; + temp[4] =(vec_t)-sz; temp[5] =(vec_t) cz; + m4x4_premultiply_by_m4x4(matrix, temp); + } +#endif + break; + + case eZYX: +#if 1 + + { + matrix[0] = (vec_t)(cy*cz); + matrix[4] = (vec_t)(cy*-sz); + matrix[8] = (vec_t)sy; + matrix[1] = (vec_t)(sx*sy*cz + cx*sz); + matrix[5] = (vec_t)(sx*sy*-sz + cx*cz); + matrix[9] = (vec_t)(-sx*cy); + matrix[2] = (vec_t)(cx*-sy*cz + sx*sz); + matrix[6] = (vec_t)(cx*-sy*-sz + sx*cz); + matrix[10] = (vec_t)(cx*cy); + } + + matrix[12] = matrix[13] = matrix[14] = matrix[3] = matrix[7] = matrix[11] = 0; + matrix[15] = 1; + +#else + + m4x4_identity(matrix); + matrix[0] =(vec_t) cz; matrix[1] =(vec_t) sz; + matrix[4] =(vec_t)-sz; matrix[5] =(vec_t) cz; + { + m4x4_t temp; + m4x4_identity(temp); + temp[0] =(vec_t) cy; temp[2] =(vec_t)-sy; + temp[8] =(vec_t) sy; temp[10]=(vec_t) cy; + m4x4_premultiply_by_m4x4(matrix, temp); + m4x4_identity(temp); + temp[5] =(vec_t) cx; temp[6] =(vec_t) sx; + temp[9] =(vec_t)-sx; temp[10]=(vec_t) cx; + m4x4_premultiply_by_m4x4(matrix, temp); + } + +#endif + break; + + } +} + +void m4x4_scale_for_vec3(m4x4_t matrix, const vec3_t scale) +{ + matrix[1] = matrix[2] = matrix[3] = + matrix[4] = matrix[6] = matrix[7] = + matrix[8] = matrix[9] = matrix[11] = + matrix[12] = matrix[13] = matrix[14] = 0; + + matrix[15] = 1; + + matrix[0] = scale[0]; + matrix[5] = scale[1]; + matrix[10] = scale[2]; +} + +void m4x4_rotation_for_quat(m4x4_t matrix, const vec4_t quat) +{ +#if 0 + const double xx = quat[0] * quat[0]; + const double xy = quat[0] * quat[1]; + const double xz = quat[0] * quat[2]; + const double xw = quat[0] * quat[3]; + + const double yy = quat[1] * quat[1]; + const double yz = quat[1] * quat[2]; + const double yw = quat[1] * quat[3]; + + const double zz = quat[2] * quat[2]; + const double zw = quat[2] * quat[3]; + + matrix[0] = 1 - 2 * ( yy + zz ); + matrix[4] = 2 * ( xy - zw ); + matrix[8] = 2 * ( xz + yw ); + + matrix[1] = 2 * ( xy + zw ); + matrix[5] = 1 - 2 * ( xx + zz ); + matrix[9] = 2 * ( yz - xw ); + + matrix[2] = 2 * ( xz - yw ); + matrix[6] = 2 * ( yz + xw ); + matrix[10] = 1 - 2 * ( xx + yy ); +#else + const double x2 = quat[0] + quat[0]; + const double y2 = quat[1] + quat[1]; + const double z2 = quat[2] + quat[2]; + const double xx = quat[0] * x2; + const double xy = quat[0] * y2; + const double xz = quat[0] * z2; + const double yy = quat[1] * y2; + const double yz = quat[1] * z2; + const double zz = quat[2] * z2; + const double wx = quat[3] * x2; + const double wy = quat[3] * y2; + const double wz = quat[3] * z2; + + matrix[0] = (vec_t)( 1.0 - (yy + zz) ); + matrix[4] = (vec_t)(xy - wz); + matrix[8] = (vec_t)(xz + wy); + + matrix[1] = (vec_t)(xy + wz); + matrix[5] = (vec_t)( 1.0 - (xx + zz) ); + matrix[9] = (vec_t)(yz - wx); + + matrix[2] = (vec_t)(xz - wy); + matrix[6] = (vec_t)(yz + wx); + matrix[10] = (vec_t)( 1.0 - (xx + yy) ); +#endif + + matrix[3] = matrix[7] = matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0; + matrix[15] = 1; +} + +void m4x4_rotation_for_axisangle(m4x4_t matrix, const vec3_t axis, double angle) +{ + vec4_t quat; + quat_for_axisangle(quat, axis, angle); + m4x4_rotation_for_quat(matrix, quat); +} + +void m4x4_frustum(m4x4_t matrix, + vec_t left, vec_t right, + vec_t bottom, vec_t top, + vec_t nearval, vec_t farval) +{ + matrix[0] = (vec_t)( (2*nearval) / (right-left) ); + matrix[1] = 0; + matrix[2] = 0; + matrix[3] = 0; + + matrix[4] = 0; + matrix[5] = (vec_t)( (2*nearval) / (top-bottom) ); + matrix[6] = 0; + matrix[7] = 0; + + matrix[8] = (vec_t)( (right+left) / (right-left) ); + matrix[9] = (vec_t)( (top+bottom) / (top-bottom) ); + matrix[10] = (vec_t)( -(farval+nearval) / (farval-nearval) ); + matrix[11] =-1; + + matrix[12] = 0; + matrix[13] = 0; + matrix[14] = (vec_t)( -(2*farval*nearval) / (farval-nearval) ); + matrix[15] = 0; +} + + +void m4x4_get_translation_vec3(const m4x4_t matrix, vec3_t translation) +{ + translation[0] = matrix[12]; + translation[1] = matrix[13]; + translation[2] = matrix[14]; +} + +void m4x4_get_rotation_vec3(const m4x4_t matrix, vec3_t euler, eulerOrder_t order) +{ + double a, ca; + + switch(order) + { + case eXYZ: + a = asin(-matrix[2]); + ca = cos(a); + euler[1] = (vec_t)RAD2DEG(a); /* Calculate Y-axis angle */ + + if (fabs(ca) > 0.005) /* Gimbal lock? */ + { + /* No, so get Z-axis angle */ + euler[2] = (vec_t)RAD2DEG(atan2(matrix[1] / ca, matrix[0]/ ca)); + + /* Get X-axis angle */ + euler[0] = (vec_t)RAD2DEG(atan2(matrix[6] / ca, matrix[10] / ca)); + } + else /* Gimbal lock has occurred */ + { + /* Set Z-axis angle to zero */ + euler[2] = 0; + + /* And calculate X-axis angle */ + euler[0] = (vec_t)RAD2DEG(atan2(-matrix[9], matrix[5])); + } + break; + case eYZX: + /* NOT IMPLEMENTED */ + break; + case eZXY: + /* NOT IMPLEMENTED */ + break; + case eXZY: + /* NOT IMPLEMENTED */ + break; + case eYXZ: + a = asin(matrix[6]); + ca = cos(a); + euler[0] = (vec_t)RAD2DEG(a); /* Calculate X-axis angle */ + + if (fabs(ca) > 0.005) /* Gimbal lock? */ + { + /* No, so get Y-axis angle */ + euler[1] = (vec_t)RAD2DEG(atan2(-matrix[2] / ca, matrix[10]/ ca)); + + /* Get Z-axis angle */ + euler[2] = (vec_t)RAD2DEG(atan2(-matrix[4] / ca, matrix[5] / ca)); + } + else /* Gimbal lock has occurred */ + { + /* Set Z-axis angle to zero */ + euler[2] = 0; + + /* And calculate Y-axis angle */ + euler[1] = (vec_t)RAD2DEG(atan2(matrix[8], matrix[0])); + } + break; + case eZYX: + a = asin(matrix[8]); + ca = cos(a); + euler[1] = (vec_t)RAD2DEG(a); /* Calculate Y-axis angle */ + + if (fabs(ca) > 0.005) /* Gimbal lock? */ + { + /* No, so get X-axis angle */ + euler[0] = (vec_t)RAD2DEG(atan2(-matrix[9] / ca, matrix[10]/ ca)); + + /* Get Z-axis angle */ + euler[2] = (vec_t)RAD2DEG(atan2(-matrix[4] / ca, matrix[0] / ca)); + } + else /* Gimbal lock has occurred */ + { + /* Set X-axis angle to zero */ + euler[0] = 0; + + /* And calculate Z-axis angle */ + euler[2] = (vec_t)RAD2DEG(atan2(matrix[1], matrix[5])); + } + break; + } + + /* return only positive angles in [0,360] */ + if (euler[0] < 0) euler[0] += 360; + if (euler[1] < 0) euler[1] += 360; + if (euler[2] < 0) euler[2] += 360; +} + +void m4x4_get_scale_vec3(const m4x4_t matrix, vec3_t scale) +{ + scale[0] = VectorLength(matrix+0); + scale[1] = VectorLength(matrix+4); + scale[2] = VectorLength(matrix+8); +} + +void m4x4_get_transform_vec3(const m4x4_t matrix, vec3_t translation, vec3_t euler, eulerOrder_t order, vec3_t scale) +{ + m4x4_t normalised; + m4x4_assign(normalised, matrix); + scale[0] = VectorNormalize(normalised+0, normalised+0); + scale[1] = VectorNormalize(normalised+4, normalised+4); + scale[2] = VectorNormalize(normalised+8, normalised+8); + if(m4x4_handedness(normalised) == eLeftHanded) + { + VectorNegate(normalised+0, normalised+0); + VectorNegate(normalised+4, normalised+4); + VectorNegate(normalised+8, normalised+8); + scale[0] = -scale[0]; + scale[1] = -scale[1]; + scale[2] = -scale[2]; + } + m4x4_get_rotation_vec3(normalised, euler, order); + m4x4_get_translation_vec3(matrix, translation); +} + +void m4x4_translate_by_vec3(m4x4_t matrix, const vec3_t translation) +{ + m4x4_t temp; + m4x4_translation_for_vec3(temp, translation); + m4x4_multiply_by_m4x4(matrix, temp); +} + +void m4x4_rotate_by_vec3(m4x4_t matrix, const vec3_t euler, eulerOrder_t order) +{ + m4x4_t temp; + m4x4_rotation_for_vec3(temp, euler, order); + m4x4_multiply_by_m4x4(matrix, temp); +} + +void m4x4_scale_by_vec3(m4x4_t matrix, const vec3_t scale) +{ + m4x4_t temp; + m4x4_scale_for_vec3(temp, scale); + m4x4_multiply_by_m4x4(matrix, temp); +} + +void m4x4_rotate_by_quat(m4x4_t matrix, const vec4_t rotation) +{ + m4x4_t temp; + m4x4_rotation_for_quat(temp, rotation); + m4x4_multiply_by_m4x4(matrix, temp); +} + +void m4x4_rotate_by_axisangle(m4x4_t matrix, const vec3_t axis, double angle) +{ + m4x4_t temp; + m4x4_rotation_for_axisangle(temp, axis, angle); + m4x4_multiply_by_m4x4(matrix, temp); +} + +void m4x4_transform_by_vec3(m4x4_t matrix, const vec3_t translation, const vec3_t euler, eulerOrder_t order, const vec3_t scale) +{ + m4x4_translate_by_vec3(matrix, translation); + m4x4_rotate_by_vec3(matrix, euler, order); + m4x4_scale_by_vec3(matrix, scale); +} + +void m4x4_pivoted_rotate_by_vec3(m4x4_t matrix, const vec3_t euler, eulerOrder_t order, const vec3_t pivotpoint) +{ + vec3_t vec3_temp; + VectorNegate(pivotpoint, vec3_temp); + + m4x4_translate_by_vec3(matrix, pivotpoint); + m4x4_rotate_by_vec3(matrix, euler, order); + m4x4_translate_by_vec3(matrix, vec3_temp); +} + +void m4x4_pivoted_scale_by_vec3(m4x4_t matrix, const vec3_t scale, const vec3_t pivotpoint) +{ + vec3_t vec3_temp; + VectorNegate(pivotpoint, vec3_temp); + + m4x4_translate_by_vec3(matrix, pivotpoint); + m4x4_scale_by_vec3(matrix, scale); + m4x4_translate_by_vec3(matrix, vec3_temp); +} + +void m4x4_pivoted_transform_by_vec3(m4x4_t matrix, const vec3_t translation, const vec3_t euler, eulerOrder_t order, const vec3_t scale, const vec3_t pivotpoint) +{ + vec3_t vec3_temp; + + VectorAdd(pivotpoint, translation, vec3_temp); + m4x4_translate_by_vec3(matrix, vec3_temp); + m4x4_rotate_by_vec3(matrix, euler, order); + m4x4_scale_by_vec3(matrix, scale); + VectorNegate(pivotpoint, vec3_temp); + m4x4_translate_by_vec3(matrix, vec3_temp); +} + +void m4x4_pivoted_transform_by_rotation(m4x4_t matrix, const vec3_t translation, const m4x4_t rotation, const vec3_t scale, const vec3_t pivotpoint) +{ + vec3_t vec3_temp; + + VectorAdd(pivotpoint, translation, vec3_temp); + m4x4_translate_by_vec3(matrix, vec3_temp); + m4x4_multiply_by_m4x4(matrix, rotation); + m4x4_scale_by_vec3(matrix, scale); + VectorNegate(pivotpoint, vec3_temp); + m4x4_translate_by_vec3(matrix, vec3_temp); +} + +void m4x4_pivoted_rotate_by_quat(m4x4_t matrix, const vec4_t rotation, const vec3_t pivotpoint) +{ + vec3_t vec3_temp; + VectorNegate(pivotpoint, vec3_temp); + + m4x4_translate_by_vec3(matrix, pivotpoint); + m4x4_rotate_by_quat(matrix, rotation); + m4x4_translate_by_vec3(matrix, vec3_temp); +} + +void m4x4_pivoted_rotate_by_axisangle(m4x4_t matrix, const vec3_t axis, double angle, const vec3_t pivotpoint) +{ + vec3_t vec3_temp; + VectorNegate(pivotpoint, vec3_temp); + + m4x4_translate_by_vec3(matrix, pivotpoint); + m4x4_rotate_by_axisangle(matrix, axis, angle); + m4x4_translate_by_vec3(matrix, vec3_temp); +} + +/* +A = A.B + +A0 = B0 * A0 + B1 * A4 + B2 * A8 + B3 * A12 +A4 = B4 * A0 + B5 * A4 + B6 * A8 + B7 * A12 +A8 = B8 * A0 + B9 * A4 + B10* A8 + B11* A12 +A12= B12* A0 + B13* A4 + B14* A8 + B15* A12 + +A1 = B0 * A1 + B1 * A5 + B2 * A9 + B3 * A13 +A5 = B4 * A1 + B5 * A5 + B6 * A9 + B7 * A13 +A9 = B8 * A1 + B9 * A5 + B10* A9 + B11* A13 +A13= B12* A1 + B13* A5 + B14* A9 + B15* A13 + +A2 = B0 * A2 + B1 * A6 + B2 * A10+ B3 * A14 +A6 = B4 * A2 + B5 * A6 + B6 * A10+ B7 * A14 +A10= B8 * A2 + B9 * A6 + B10* A10+ B11* A14 +A14= B12* A2 + B13* A6 + B14* A10+ B15* A14 + +A3 = B0 * A3 + B1 * A7 + B2 * A11+ B3 * A15 +A7 = B4 * A3 + B5 * A7 + B6 * A11+ B7 * A15 +A11= B8 * A3 + B9 * A7 + B10* A11+ B11* A15 +A15= B12* A3 + B13* A7 + B14* A11+ B15* A15 +*/ + +void m4x4_multiply_by_m4x4(m4x4_t dst, const m4x4_t src) +{ + vec_t dst0, dst1, dst2, dst3; + +#if 1 + + dst0 = src[0] * dst[0] + src[1] * dst[4] + src[2] * dst[8] + src[3] * dst[12]; + dst1 = src[4] * dst[0] + src[5] * dst[4] + src[6] * dst[8] + src[7] * dst[12]; + dst2 = src[8] * dst[0] + src[9] * dst[4] + src[10]* dst[8] + src[11]* dst[12]; + dst3 = src[12]* dst[0] + src[13]* dst[4] + src[14]* dst[8] + src[15]* dst[12]; + dst[0] = dst0; dst[4] = dst1; dst[8] = dst2; dst[12]= dst3; + + dst0 = src[0] * dst[1] + src[1] * dst[5] + src[2] * dst[9] + src[3] * dst[13]; + dst1 = src[4] * dst[1] + src[5] * dst[5] + src[6] * dst[9] + src[7] * dst[13]; + dst2 = src[8] * dst[1] + src[9] * dst[5] + src[10]* dst[9] + src[11]* dst[13]; + dst3 = src[12]* dst[1] + src[13]* dst[5] + src[14]* dst[9] + src[15]* dst[13]; + dst[1] = dst0; dst[5] = dst1; dst[9] = dst2; dst[13]= dst3; + + dst0 = src[0] * dst[2] + src[1] * dst[6] + src[2] * dst[10]+ src[3] * dst[14]; + dst1 = src[4] * dst[2] + src[5] * dst[6] + src[6] * dst[10]+ src[7] * dst[14]; + dst2 = src[8] * dst[2] + src[9] * dst[6] + src[10]* dst[10]+ src[11]* dst[14]; + dst3 = src[12]* dst[2] + src[13]* dst[6] + src[14]* dst[10]+ src[15]* dst[14]; + dst[2] = dst0; dst[6] = dst1; dst[10]= dst2; dst[14]= dst3; + + dst0 = src[0] * dst[3] + src[1] * dst[7] + src[2] * dst[11]+ src[3] * dst[15]; + dst1 = src[4] * dst[3] + src[5] * dst[7] + src[6] * dst[11]+ src[7] * dst[15]; + dst2 = src[8] * dst[3] + src[9] * dst[7] + src[10]* dst[11]+ src[11]* dst[15]; + dst3 = src[12]* dst[3] + src[13]* dst[7] + src[14]* dst[11]+ src[15]* dst[15]; + dst[3] = dst0; dst[7] = dst1; dst[11]= dst2; dst[15]= dst3; + +#else + + vec_t * p = dst; + for(int i=0;i<4;i++) + { + dst1 = src[0] * p[0]; + dst1 += src[1] * p[4]; + dst1 += src[2] * p[8]; + dst1 += src[3] * p[12]; + dst2 = src[4] * p[0]; + dst2 += src[5] * p[4]; + dst2 += src[6] * p[8]; + dst2 += src[7] * p[12]; + dst3 = src[8] * p[0]; + dst3 += src[9] * p[4]; + dst3 += src[10] * p[8]; + dst3 += src[11] * p[12]; + dst4 = src[12] * p[0]; + dst4 += src[13] * p[4]; + dst4 += src[14] * p[8]; + dst4 += src[15] * p[12]; + + p[0] = dst1; + p[4] = dst2; + p[8] = dst3; + p[12] = dst4; + p++; + } + +#endif +} + +/* +A = B.A + +A0 = A0 * B0 + A1 * B4 + A2 * B8 + A3 * B12 +A1 = A0 * B1 + A1 * B5 + A2 * B9 + A3 * B13 +A2 = A0 * B2 + A1 * B6 + A2 * B10+ A3 * B14 +A3 = A0 * B3 + A1 * B7 + A2 * B11+ A3 * B15 + +A4 = A4 * B0 + A5 * B4 + A6 * B8 + A7 * B12 +A5 = A4 * B1 + A5 * B5 + A6 * B9 + A7 * B13 +A6 = A4 * B2 + A5 * B6 + A6 * B10+ A7 * B14 +A7 = A4 * B3 + A5 * B7 + A6 * B11+ A7 * B15 + +A8 = A8 * B0 + A9 * B4 + A10* B8 + A11* B12 +A9 = A8 * B1 + A9 * B5 + A10* B9 + A11* B13 +A10= A8 * B2 + A9 * B6 + A10* B10+ A11* B14 +A11= A8 * B3 + A9 * B7 + A10* B11+ A11* B15 + +A12= A12* B0 + A13* B4 + A14* B8 + A15* B12 +A13= A12* B1 + A13* B5 + A14* B9 + A15* B13 +A14= A12* B2 + A13* B6 + A14* B10+ A15* B14 +A15= A12* B3 + A13* B7 + A14* B11+ A15* B15 +*/ + +void m4x4_premultiply_by_m4x4(m4x4_t dst, const m4x4_t src) +{ + vec_t dst0, dst1, dst2, dst3; + +#if 1 + + dst0 = dst[0] * src[0] + dst[1] * src[4] + dst[2] * src[8] + dst[3] * src[12]; + dst1 = dst[0] * src[1] + dst[1] * src[5] + dst[2] * src[9] + dst[3] * src[13]; + dst2 = dst[0] * src[2] + dst[1] * src[6] + dst[2] * src[10]+ dst[3] * src[14]; + dst3 = dst[0] * src[3] + dst[1] * src[7] + dst[2] * src[11]+ dst[3] * src[15]; + dst[0] = dst0; dst[1] = dst1; dst[2] = dst2; dst[3]= dst3; + + dst0 = dst[4] * src[0] + dst[5] * src[4] + dst[6] * src[8] + dst[7] * src[12]; + dst1 = dst[4] * src[1] + dst[5] * src[5] + dst[6] * src[9] + dst[7] * src[13]; + dst2 = dst[4] * src[2] + dst[5] * src[6] + dst[6] * src[10]+ dst[7] * src[14]; + dst3 = dst[4] * src[3] + dst[5] * src[7] + dst[6] * src[11]+ dst[7] * src[15]; + dst[4] = dst0; dst[5] = dst1; dst[6] = dst2; dst[7]= dst3; + + dst0 = dst[8] * src[0] + dst[9] * src[4] + dst[10]* src[8] + dst[11]* src[12]; + dst1 = dst[8] * src[1] + dst[9] * src[5] + dst[10]* src[9] + dst[11]* src[13]; + dst2 = dst[8] * src[2] + dst[9] * src[6] + dst[10]* src[10]+ dst[11]* src[14]; + dst3 = dst[8] * src[3] + dst[9] * src[7] + dst[10]* src[11]+ dst[11]* src[15]; + dst[8] = dst0; dst[9] = dst1; dst[10] = dst2; dst[11]= dst3; + + dst0 = dst[12]* src[0] + dst[13]* src[4] + dst[14]* src[8] + dst[15]* src[12]; + dst1 = dst[12]* src[1] + dst[13]* src[5] + dst[14]* src[9] + dst[15]* src[13]; + dst2 = dst[12]* src[2] + dst[13]* src[6] + dst[14]* src[10]+ dst[15]* src[14]; + dst3 = dst[12]* src[3] + dst[13]* src[7] + dst[14]* src[11]+ dst[15]* src[15]; + dst[12] = dst0; dst[13] = dst1; dst[14] = dst2; dst[15]= dst3; + +#else + + vec_t* p = dst; + for(int i=0;i<4;i++) + { + dst1 = src[0] * p[0]; + dst2 = src[1] * p[0]; + dst3 = src[2] * p[0]; + dst4 = src[3] * p[0]; + dst1 += src[4] * p[1]; + dst2 += src[5] * p[1]; + dst3 += src[6] * p[1]; + dst4 += src[7] * p[1]; + dst1 += src[8] * p[2]; + dst2 += src[9] * p[2]; + dst4 += src[11] * p[2]; + dst3 += src[10] * p[2]; + dst1 += src[12] * p[3]; + dst2 += src[13] * p[3]; + dst3 += src[14] * p[3]; + dst4 += src[15] * p[3]; + + *p++ = dst1; + *p++ = dst2; + *p++ = dst3; + *p++ = dst4; + } + +#endif +} + +void m4x4_orthogonal_multiply_by_m4x4(m4x4_t dst, const m4x4_t src) +{ + vec_t dst0, dst1, dst2, dst3; + + dst0 = src[0] * dst[0] + src[1] * dst[4] + src[2] * dst[8]; + dst1 = src[4] * dst[0] + src[5] * dst[4] + src[6] * dst[8]; + dst2 = src[8] * dst[0] + src[9] * dst[4] + src[10]* dst[8]; + dst3 = src[12]* dst[0] + src[13]* dst[4] + src[14]* dst[8] + dst[12]; + dst[0] = dst0; dst[4] = dst1; dst[8] = dst2; dst[12]= dst3; + + dst0 = src[0] * dst[1] + src[1] * dst[5] + src[2] * dst[9]; + dst1 = src[4] * dst[1] + src[5] * dst[5] + src[6] * dst[9]; + dst2 = src[8] * dst[1] + src[9] * dst[5] + src[10]* dst[9]; + dst3 = src[12]* dst[1] + src[13]* dst[5] + src[14]* dst[9] + dst[13]; + dst[1] = dst0; dst[5] = dst1; dst[9] = dst2; dst[13]= dst3; + + dst0 = src[0] * dst[2] + src[1] * dst[6] + src[2] * dst[10]; + dst1 = src[4] * dst[2] + src[5] * dst[6] + src[6] * dst[10]; + dst2 = src[8] * dst[2] + src[9] * dst[6] + src[10]* dst[10]; + dst3 = src[12]* dst[2] + src[13]* dst[6] + src[14]* dst[10]+ dst[14]; + dst[2] = dst0; dst[6] = dst1; dst[10]= dst2; dst[14]= dst3; +} + +void m4x4_orthogonal_premultiply_by_m4x4(m4x4_t dst, const m4x4_t src) +{ + vec_t dst0, dst1, dst2; + + dst0 = dst[0] * src[0] + dst[1] * src[4] + dst[2] * src[8]; + dst1 = dst[0] * src[1] + dst[1] * src[5] + dst[2] * src[9]; + dst2 = dst[0] * src[2] + dst[1] * src[6] + dst[2] * src[10]; + dst[0] = dst0; dst[1] = dst1; dst[2] = dst2; + + dst0 = dst[4] * src[0] + dst[5] * src[4] + dst[6] * src[8]; + dst1 = dst[4] * src[1] + dst[5] * src[5] + dst[6] * src[9]; + dst2 = dst[4] * src[2] + dst[5] * src[6] + dst[6] * src[10]; + dst[4] = dst0; dst[5] = dst1; dst[6] = dst2; + + dst0 = dst[8] * src[0] + dst[9] * src[4] + dst[10]* src[8]; + dst1 = dst[8] * src[1] + dst[9] * src[5] + dst[10]* src[9]; + dst2 = dst[8] * src[2] + dst[9] * src[6] + dst[10]* src[10]; + dst[8] = dst0; dst[9] = dst1; dst[10] = dst2; + + dst0 = dst[12]* src[0] + dst[13]* src[4] + dst[14]* src[8] + dst[15]* src[12]; + dst1 = dst[12]* src[1] + dst[13]* src[5] + dst[14]* src[9] + dst[15]* src[13]; + dst2 = dst[12]* src[2] + dst[13]* src[6] + dst[14]* src[10]+ dst[15]* src[14]; + dst[12] = dst0; dst[13] = dst1; dst[14] = dst2; +} + +void m4x4_transform_point(const m4x4_t matrix, vec3_t point) +{ + float out1, out2, out3; + + out1 = matrix[0] * point[0] + matrix[4] * point[1] + matrix[8] * point[2] + matrix[12]; + out2 = matrix[1] * point[0] + matrix[5] * point[1] + matrix[9] * point[2] + matrix[13]; + out3 = matrix[2] * point[0] + matrix[6] * point[1] + matrix[10] * point[2] + matrix[14]; + + point[0] = out1; + point[1] = out2; + point[2] = out3; +} + +void m4x4_transform_normal(const m4x4_t matrix, vec3_t normal) +{ + float out1, out2, out3; + + out1 = matrix[0] * normal[0] + matrix[4] * normal[1] + matrix[8] * normal[2]; + out2 = matrix[1] * normal[0] + matrix[5] * normal[1] + matrix[9] * normal[2]; + out3 = matrix[2] * normal[0] + matrix[6] * normal[1] + matrix[10] * normal[2]; + + normal[0] = out1; + normal[1] = out2; + normal[2] = out3; +} + +void m4x4_transform_vec4(const m4x4_t matrix, vec4_t vector) +{ + float out1, out2, out3, out4; + + out1 = matrix[0] * vector[0] + matrix[4] * vector[1] + matrix[8] * vector[2] + matrix[12] * vector[3]; + out2 = matrix[1] * vector[0] + matrix[5] * vector[1] + matrix[9] * vector[2] + matrix[13] * vector[3]; + out3 = matrix[2] * vector[0] + matrix[6] * vector[1] + matrix[10] * vector[2] + matrix[14] * vector[3]; + out4 = matrix[3] * vector[0] + matrix[7] * vector[1] + matrix[11] * vector[2] + matrix[15] * vector[3]; + + vector[0] = out1; + vector[1] = out2; + vector[2] = out3; + vector[3] = out4; +} + +#define CLIP_X_LT_W(p) ((p)[0] < (p)[3]) +#define CLIP_X_GT_W(p) ((p)[0] > -(p)[3]) +#define CLIP_Y_LT_W(p) ((p)[1] < (p)[3]) +#define CLIP_Y_GT_W(p) ((p)[1] > -(p)[3]) +#define CLIP_Z_LT_W(p) ((p)[2] < (p)[3]) +#define CLIP_Z_GT_W(p) ((p)[2] > -(p)[3]) + +clipmask_t homogenous_clip_point(const vec4_t clipped) +{ + clipmask_t result = CLIP_FAIL; + if(CLIP_X_LT_W(clipped)) result &= ~CLIP_LT_X; // X < W + if(CLIP_X_GT_W(clipped)) result &= ~CLIP_GT_X; // X > -W + if(CLIP_Y_LT_W(clipped)) result &= ~CLIP_LT_Y; // Y < W + if(CLIP_Y_GT_W(clipped)) result &= ~CLIP_GT_Y; // Y > -W + if(CLIP_Z_LT_W(clipped)) result &= ~CLIP_LT_Z; // Z < W + if(CLIP_Z_GT_W(clipped)) result &= ~CLIP_GT_Z; // Z > -W + return result; +} + +clipmask_t m4x4_clip_point(const m4x4_t matrix, const vec3_t point, vec4_t clipped) +{ + clipped[0] = point[0]; + clipped[1] = point[1]; + clipped[2] = point[2]; + clipped[3] = 1; + m4x4_transform_vec4(matrix, clipped); + return homogenous_clip_point(clipped); +} + + +unsigned int homogenous_clip_triangle(vec4_t clipped[9]) +{ + vec4_t buffer[9]; + unsigned int rcount = 3; + unsigned int wcount = 0; + vec_t const* rptr = clipped[0]; + vec_t* wptr = buffer[0]; + const vec_t* p0; + const vec_t* p1; + unsigned char b0, b1; + + unsigned int i; + double scale; + + p0 = rptr; + b0 = CLIP_X_LT_W(p0); + for(i=0; i= 0.0) pos += det; else neg += det; + + det = src[1] * src[6] * src[8]; + if (det >= 0.0) pos += det; else neg += det; + + det = src[2] * src[4] * src[9]; + if (det >= 0.0) pos += det; else neg += det; + + det = -src[2] * src[5] * src[8]; + if (det >= 0.0) pos += det; else neg += det; + + det = -src[1] * src[4] * src[10]; + if (det >= 0.0) pos += det; else neg += det; + + det = -src[0] * src[6] * src[9]; + if (det >= 0.0) pos += det; else neg += det; + + det = pos + neg; +#elif 0 + float det + = (src[0] * src[5] * src[10]) + + (src[1] * src[6] * src[8]) + + (src[2] * src[4] * src[9]) + - (src[2] * src[5] * src[8]) + - (src[1] * src[4] * src[10]) + - (src[0] * src[6] * src[9]); +#else + float det + = src[0] * ( src[5]*src[10] - src[9]*src[6] ) + - src[1] * ( src[4]*src[10] - src[8]*src[6] ) + + src[2] * ( src[4]*src[9] - src[8]*src[5] ); + +#endif + + if (det*det < 1e-25) + return 1; + + det = 1.0f / det; + matrix[0] = ( (src[5]*src[10]- src[6]*src[9] )*det); + matrix[1] = (- (src[1]*src[10]- src[2]*src[9] )*det); + matrix[2] = ( (src[1]*src[6] - src[2]*src[5] )*det); + matrix[4] = (- (src[4]*src[10]- src[6]*src[8] )*det); + matrix[5] = ( (src[0]*src[10]- src[2]*src[8] )*det); + matrix[6] = (- (src[0]*src[6] - src[2]*src[4] )*det); + matrix[8] = ( (src[4]*src[9] - src[5]*src[8] )*det); + matrix[9] = (- (src[0]*src[9] - src[1]*src[8] )*det); + matrix[10]= ( (src[0]*src[5] - src[1]*src[4] )*det); + } + + /* Do the translation part */ + matrix[12] = - (src[12] * matrix[0] + + src[13] * matrix[4] + + src[14] * matrix[8]); + matrix[13] = - (src[12] * matrix[1] + + src[13] * matrix[5] + + src[14] * matrix[9]); + matrix[14] = - (src[12] * matrix[2] + + src[13] * matrix[6] + + src[14] * matrix[10]); + + return 0; +} + +void quat_identity(vec4_t quat) +{ + quat[0] = quat[1] = quat[2] = 0; + quat[3] = 1; +} + +void quat_multiply_by_quat(vec4_t quat, const vec4_t other) +{ + const vec_t x = quat[3]*other[0] + quat[0]*other[3] + quat[1]*other[2] - quat[2]*other[1]; + const vec_t y = quat[3]*other[1] + quat[1]*other[3] + quat[2]*other[0] - quat[0]*other[2]; + const vec_t z = quat[3]*other[2] + quat[2]*other[3] + quat[0]*other[1] - quat[1]*other[0]; + const vec_t w = quat[3]*other[3] - quat[0]*other[0] - quat[1]*other[1] - quat[2]*other[2]; + quat[0] = x; + quat[1] = y; + quat[2] = z; + quat[3] = w; +} + +void quat_conjugate(vec4_t quat) +{ + VectorNegate(quat, quat); +} + +//! quaternion from two unit vectors +void quat_for_unit_vectors(vec4_t quat, const vec3_t from, const vec3_t to) +{ + CrossProduct(from, to, quat); + quat[3] = DotProduct(from, to); +} + +void quat_normalise(vec4_t quat) +{ + const vec_t n = 1 / ( quat[0] * quat[0] + quat[1] * quat[1] + quat[2] * quat[2] + quat[3] * quat[3] ); + quat[0] *= n; + quat[1] *= n; + quat[2] *= n; + quat[3] *= n; +} + +void quat_for_axisangle(vec4_t quat, const vec3_t axis, double angle) +{ + angle *= 0.5; + + quat[3] = (float)sin(angle); + + quat[0] = axis[0] * quat[3]; + quat[1] = axis[1] * quat[3]; + quat[2] = axis[2] * quat[3]; + quat[3] = (float)cos(angle); +} + +void m3x3_multiply_by_m3x3(m3x3_t matrix, const m3x3_t matrix_src) +{ + float *pDest = matrix; + float out1, out2, out3; + int i; + + for(i=0;i<3;i++) + { + out1 = matrix_src[0] * pDest[0]; + out1 += matrix_src[1] * pDest[3]; + out1 += matrix_src[2] * pDest[6]; + out2 = matrix_src[3] * pDest[0]; + out2 += matrix_src[4] * pDest[3]; + out2 += matrix_src[5] * pDest[6]; + out3 = matrix_src[6] * pDest[0]; + out3 += matrix_src[7] * pDest[3]; + out3 += matrix_src[8] * pDest[6]; + + pDest[0] = out1; + pDest[3] = out2; + pDest[6] = out3; + + pDest++; + } +} + +void m3x3_transform_vec3(const m3x3_t matrix, vec3_t vector) +{ + float out1, out2, out3; + + out1 = matrix[0] * vector[0]; + out1 += matrix[3] * vector[1]; + out1 += matrix[6] * vector[2]; + out2 = matrix[1] * vector[0]; + out2 += matrix[4] * vector[1]; + out2 += matrix[7] * vector[2]; + out3 = matrix[2] * vector[0]; + out3 += matrix[5] * vector[1]; + out3 += matrix[8] * vector[2]; + + vector[0] = out1; + vector[1] = out2; + vector[2] = out3; +} + +float m3_det( m3x3_t mat ) +{ + float det; + + det = mat[0] * ( mat[4]*mat[8] - mat[7]*mat[5] ) + - mat[1] * ( mat[3]*mat[8] - mat[6]*mat[5] ) + + mat[2] * ( mat[3]*mat[7] - mat[6]*mat[4] ); + + return( det ); +} + +int m3_inverse( m3x3_t mr, m3x3_t ma ) +{ + float det = m3_det( ma ); + + if (det == 0 ) + { + return 1; + } + + + mr[0] = ma[4]*ma[8] - ma[5]*ma[7] / det; + mr[1] = -( ma[1]*ma[8] - ma[7]*ma[2] ) / det; + mr[2] = ma[1]*ma[5] - ma[4]*ma[2] / det; + + mr[3] = -( ma[3]*ma[8] - ma[5]*ma[6] ) / det; + mr[4] = ma[0]*ma[8] - ma[6]*ma[2] / det; + mr[5] = -( ma[0]*ma[5] - ma[3]*ma[2] ) / det; + + mr[6] = ma[3]*ma[7] - ma[6]*ma[4] / det; + mr[7] = -( ma[0]*ma[7] - ma[6]*ma[1] ) / det; + mr[8] = ma[0]*ma[4] - ma[1]*ma[3] / det; + + return 0; +} + +void m4_submat( m4x4_t mr, m3x3_t mb, int i, int j ) +{ + int ti, tj, idst, jdst; + + for ( ti = 0; ti < 4; ti++ ) + { + if ( ti < i ) + idst = ti; + else + if ( ti > i ) + idst = ti-1; + + for ( tj = 0; tj < 4; tj++ ) + { + if ( tj < j ) + jdst = tj; + else + if ( tj > j ) + jdst = tj-1; + + if ( ti != i && tj != j ) + mb[idst*3 + jdst] = mr[ti*4 + tj ]; + } + } +} + +float m4_det( m4x4_t mr ) +{ + float det, result = 0, i = 1; + m3x3_t msub3; + int n; + + for ( n = 0; n < 4; n++, i *= -1 ) + { + m4_submat( mr, msub3, 0, n ); + + det = m3_det( msub3 ); + result += mr[n] * det * i; + } + + return result; +} + +int m4x4_invert(m4x4_t matrix) +{ + float mdet = m4_det( matrix ); + m3x3_t mtemp; + int i, j, sign; + m4x4_t m4x4_temp; + +#if 0 + if ( fabs( mdet ) < 0.0000000001 ) + return 1; +#endif + + m4x4_assign(m4x4_temp, matrix); + + for ( i = 0; i < 4; i++ ) + for ( j = 0; j < 4; j++ ) + { + sign = 1 - ( (i +j) % 2 ) * 2; + + m4_submat( m4x4_temp, mtemp, i, j ); + + matrix[i+j*4] = ( m3_det( mtemp ) * sign ) / mdet; /* FIXME: try using * inverse det and see if speed/accuracy are good enough */ + } + + return 0; +} +#if 0 +void m4x4_solve_ge(m4x4_t matrix, vec4_t x) +{ + int indx[4]; + int c,r; + int i; + int best; + float scale[4]; + float f, pivot; + float aug[4]; + float recip, ratio; + float* p; + + for(r=0; r<4; r++) + { + aug[r] = 0; + indx[r] = r; + } + + for (r=0; r<4; r++) + { + scale[r] = 0; + for (c=0; c<4; c++, p++) + { + if (fabs(*p) > scale[r]) + { + scale[r] = (float)fabs(*p); + } + } + } + + for (c=0; c<3; c++) + { + pivot = 0; + for (r=c; r<4; r++) + { + f = (float)fabs(matrix[(indx[r]<<2)+c]) / scale[indx[r]]; + if (f > pivot) + { + pivot = f; + best = r; + } + } + + i = indx[c]; + indx[c] = indx[best]; + indx[best] = i; + + recip = 1 / matrix[(indx[c]<<2)+c]; + + for (r=c+1; r<4; r++) + { + p = matrix + (indx[r]<<2); + ratio = p[c] * recip; + + for (i=c+1; i<4; i++) + p[i] -= ratio * matrix[(indx[c]<<2)+i]; + aug[indx[r]] -= ratio * aug[indx[c]]; + } + } + + x[indx[3]] = aug[indx[3]] / matrix[(indx[3]<<2)+3]; + for(r=2; r>=0; r--) + { + f = aug[indx[r]]; + p = matrix + (indx[r]<<2); + recip = 1 / p[r]; + for(c=(r+1); c<4; c++) + { + f -= (p[c] * x[indx[c]]); + } + x[indx[r]] = f * recip; + } +} +#endif + +#define N 3 + +int matrix_solve_ge(vec_t* matrix, vec_t* aug, vec3_t x) +{ + int indx[N]; + int c,r; + int i; + int best; + float scale[N]; + float f, pivot; + float ratio; + float* p; + + for(r=0; r scale[r]) + { + scale[r] = (float)fabs(*p); + } + } + } + + for (c=0; c pivot) + { + pivot = f; + best = r; + } + } + + if(best == -1) return 1; + + i = indx[c]; + indx[c] = indx[best]; + indx[best] = i; + + for (r=c+1; r=0; r--) + { + f = aug[indx[r]]; + p = matrix + (indx[r]*N); + for(c=(r+1); c=0;i--) + { + temp = b[i]; + for(j=(i+1);j + +const vec3_t vec3_origin = {0.0f,0.0f,0.0f}; + +const vec3_t g_vec3_axis_x = { 1, 0, 0, }; +const vec3_t g_vec3_axis_y = { 0, 1, 0, }; +const vec3_t g_vec3_axis_z = { 0, 0, 1, }; + +/* +================ +MakeNormalVectors + +Given a normalized forward vector, create two +other perpendicular vectors +================ +*/ +void MakeNormalVectors (vec3_t forward, vec3_t right, vec3_t up) +{ + float d; + + // this rotate and negate guarantees a vector + // not colinear with the original + right[1] = -forward[0]; + right[2] = forward[1]; + right[0] = forward[2]; + + d = DotProduct (right, forward); + VectorMA (right, -d, forward, right); + VectorNormalize (right, right); + CrossProduct (right, forward, up); +} + +vec_t VectorLength(const vec3_t v) +{ + int i; + float length; + + length = 0.0f; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = (float)sqrt (length); + + return length; +} + +qboolean VectorCompare (const vec3_t v1, const vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (fabs(v1[i]-v2[i]) > EQUAL_EPSILON) + return qfalse; + + return qtrue; +} + +void VectorMA( const vec3_t va, vec_t scale, const vec3_t vb, vec3_t vc ) +{ + vc[0] = va[0] + scale*vb[0]; + vc[1] = va[1] + scale*vb[1]; + vc[2] = va[2] + scale*vb[2]; +} + +void _CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]-vb[0]; + out[1] = va[1]-vb[1]; + out[2] = va[2]-vb[2]; +} + +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]+vb[0]; + out[1] = va[1]+vb[1]; + out[2] = va[2]+vb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +vec_t VectorNormalize( const vec3_t in, vec3_t out ) { + vec_t length, ilength; + + length = (vec_t)sqrt (in[0]*in[0] + in[1]*in[1] + in[2]*in[2]); + if (length == 0) + { + VectorClear (out); + return 0; + } + + ilength = 1.0f/length; + out[0] = in[0]*ilength; + out[1] = in[1]*ilength; + out[2] = in[2]*ilength; + + return length; +} + +vec_t ColorNormalize( const vec3_t in, vec3_t out ) { + float max, scale; + + max = in[0]; + if (in[1] > max) + max = in[1]; + if (in[2] > max) + max = in[2]; + + if (max == 0) { + out[0] = out[1] = out[2] = 1.0; + return 0; + } + + scale = 1.0f / max; + + VectorScale (in, scale, out); + + return max; +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +/* +void VectorScale (vec3_t v, vec_t scale, vec3_t out) +{ + out[0] = v[0] * scale; + out[1] = v[1] * scale; + out[2] = v[2] * scale; +} +*/ + +void VectorRotate (vec3_t vIn, vec3_t vRotation, vec3_t out) +{ + vec3_t vWork, va; + int nIndex[3][2]; + int i; + + VectorCopy(vIn, va); + VectorCopy(va, vWork); + nIndex[0][0] = 1; nIndex[0][1] = 2; + nIndex[1][0] = 2; nIndex[1][1] = 0; + nIndex[2][0] = 0; nIndex[2][1] = 1; + + for (i = 0; i < 3; i++) + { + if (vRotation[i] != 0) + { + float dAngle = vRotation[i] * Q_PI / 180.0f; + float c = (vec_t)cos(dAngle); + float s = (vec_t)sin(dAngle); + vWork[nIndex[i][0]] = va[nIndex[i][0]] * c - va[nIndex[i][1]] * s; + vWork[nIndex[i][1]] = va[nIndex[i][0]] * s + va[nIndex[i][1]] * c; + } + VectorCopy(vWork, va); + } + VectorCopy(vWork, out); +} + +void VectorRotateOrigin (vec3_t vIn, vec3_t vRotation, vec3_t vOrigin, vec3_t out) +{ + vec3_t vTemp, vTemp2; + + VectorSubtract(vIn, vOrigin, vTemp); + VectorRotate(vTemp, vRotation, vTemp2); + VectorAdd(vTemp2, vOrigin, out); +} + +void VectorPolar(vec3_t v, float radius, float theta, float phi) +{ + v[0]=(float)(radius * cos(theta) * cos(phi)); + v[1]=(float)(radius * sin(theta) * cos(phi)); + v[2]=(float)(radius * sin(phi)); +} + +void VectorSnap(vec3_t v) +{ + int i; + for (i = 0; i < 3; i++) + { + v[i] = (vec_t)FLOAT_TO_INTEGER(v[i]); + } +} + +void VectorISnap(vec3_t point, int snap) +{ + int i; + for (i = 0 ;i < 3 ; i++) + { + point[i] = (vec_t)FLOAT_SNAP(point[i], snap); + } +} + +void VectorFSnap(vec3_t point, float snap) +{ + int i; + for (i = 0 ;i < 3 ; i++) + { + point[i] = (vec_t)FLOAT_SNAP(point[i], snap); + } +} + +void _Vector5Add (vec5_t va, vec5_t vb, vec5_t out) +{ + out[0] = va[0]+vb[0]; + out[1] = va[1]+vb[1]; + out[2] = va[2]+vb[2]; + out[3] = va[3]+vb[3]; + out[4] = va[4]+vb[4]; +} + +void _Vector5Scale (vec5_t v, vec_t scale, vec5_t out) +{ + out[0] = v[0] * scale; + out[1] = v[1] * scale; + out[2] = v[2] * scale; + out[3] = v[3] * scale; + out[4] = v[4] * scale; +} + +void _Vector53Copy (vec5_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +// NOTE: added these from Ritual's Q3Radiant +void ClearBounds (vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + angle = angles[YAW] * (Q_PI*2.0f / 360.0f); + sy = (vec_t)sin(angle); + cy = (vec_t)cos(angle); + angle = angles[PITCH] * (Q_PI*2.0f / 360.0f); + sp = (vec_t)sin(angle); + cp = (vec_t)cos(angle); + angle = angles[ROLL] * (Q_PI*2.0f / 360.0f); + sr = (vec_t)sin(angle); + cr = (vec_t)cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = -sr*sp*cy+cr*sy; + right[1] = -sr*sp*sy-cr*cy; + right[2] = -sr*cp; + } + if (up) + { + up[0] = cr*sp*cy+sr*sy; + up[1] = cr*sp*sy-sr*cy; + up[2] = cr*cp; + } +} + +void VectorToAngles( vec3_t vec, vec3_t angles ) +{ + float forward; + float yaw, pitch; + + if ( ( vec[ 0 ] == 0 ) && ( vec[ 1 ] == 0 ) ) + { + yaw = 0; + if ( vec[ 2 ] > 0 ) + { + pitch = 90; + } + else + { + pitch = 270; + } + } + else + { + yaw = (vec_t)atan2( vec[ 1 ], vec[ 0 ] ) * 180 / Q_PI; + if ( yaw < 0 ) + { + yaw += 360; + } + + forward = ( float )sqrt( vec[ 0 ] * vec[ 0 ] + vec[ 1 ] * vec[ 1 ] ); + pitch = (vec_t)atan2( vec[ 2 ], forward ) * 180 / Q_PI; + if ( pitch < 0 ) + { + pitch += 360; + } + } + + angles[ 0 ] = pitch; + angles[ 1 ] = yaw; + angles[ 2 ] = 0; +} + +/* +===================== +PlaneFromPoints + +Returns false if the triangle is degenrate. +The normal will point out of the clock for clockwise ordered points +===================== +*/ +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ) { + vec3_t d1, d2; + + VectorSubtract( b, a, d1 ); + VectorSubtract( c, a, d2 ); + CrossProduct( d2, d1, plane ); + if ( VectorNormalize( plane, plane ) == 0 ) { + return qfalse; + } + + plane[3] = DotProduct( a, plane ); + return qtrue; +} + +/* +** NormalToLatLong +** +** We use two byte encoded normals in some space critical applications. +** Lat = 0 at (1,0,0) to 360 (-1,0,0), encoded in 8-bit sine table format +** Lng = 0 at (0,0,1) to 180 (0,0,-1), encoded in 8-bit sine table format +** +*/ +void NormalToLatLong( const vec3_t normal, byte bytes[2] ) { + // check for singularities + if ( normal[0] == 0 && normal[1] == 0 ) { + if ( normal[2] > 0 ) { + bytes[0] = 0; + bytes[1] = 0; // lat = 0, long = 0 + } else { + bytes[0] = 128; + bytes[1] = 0; // lat = 0, long = 128 + } + } else { + int a, b; + + a = (int)( RAD2DEG( atan2( normal[1], normal[0] ) ) * (255.0f / 360.0f ) ); + a &= 0xff; + + b = (int)( RAD2DEG( acos( normal[2] ) ) * ( 255.0f / 360.0f ) ); + b &= 0xff; + + bytes[0] = b; // longitude + bytes[1] = a; // lattitude + } +} + +/* +================= +PlaneTypeForNormal +================= +*/ +int PlaneTypeForNormal (vec3_t normal) { + if (normal[0] == 1.0 || normal[0] == -1.0) + return PLANE_X; + if (normal[1] == 1.0 || normal[1] == -1.0) + return PLANE_Y; + if (normal[2] == 1.0 || normal[2] == -1.0) + return PLANE_Z; + + return PLANE_NON_AXIAL; +} + +/* +================ +MatrixMultiply +================ +*/ +void MatrixMultiply(float in1[3][3], float in2[3][3], float out[3][3]) { + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; +} + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ) +{ + float d; + vec3_t n; + float inv_denom; + + inv_denom = 1.0F / DotProduct( normal, normal ); + + d = DotProduct( normal, p ) * inv_denom; + + n[0] = normal[0] * inv_denom; + n[1] = normal[1] * inv_denom; + n[2] = normal[2] * inv_denom; + + dst[0] = p[0] - d * n[0]; + dst[1] = p[1] - d * n[1]; + dst[2] = p[2] - d * n[2]; +} + +/* +** assumes "src" is normalized +*/ +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ + int pos; + int i; + vec_t minelem = 1.0F; + vec3_t tempvec; + + /* + ** find the smallest magnitude axially aligned vector + */ + for ( pos = 0, i = 0; i < 3; i++ ) + { + if ( fabs( src[i] ) < minelem ) + { + pos = i; + minelem = (vec_t)fabs( src[i] ); + } + } + tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; + tempvec[pos] = 1.0F; + + /* + ** project the point onto the plane defined by src + */ + ProjectPointOnPlane( dst, tempvec, src ); + + /* + ** normalize the result + */ + VectorNormalize( dst, dst ); +} + +/* +=============== +RotatePointAroundVector + +This is not implemented very well... +=============== +*/ +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, + float degrees ) { + float m[3][3]; + float im[3][3]; + float zrot[3][3]; + float tmpmat[3][3]; + float rot[3][3]; + int i; + vec3_t vr, vup, vf; + float rad; + + vf[0] = dir[0]; + vf[1] = dir[1]; + vf[2] = dir[2]; + + PerpendicularVector( vr, dir ); + CrossProduct( vr, vf, vup ); + + m[0][0] = vr[0]; + m[1][0] = vr[1]; + m[2][0] = vr[2]; + + m[0][1] = vup[0]; + m[1][1] = vup[1]; + m[2][1] = vup[2]; + + m[0][2] = vf[0]; + m[1][2] = vf[1]; + m[2][2] = vf[2]; + + memcpy( im, m, sizeof( im ) ); + + im[0][1] = m[1][0]; + im[0][2] = m[2][0]; + im[1][0] = m[0][1]; + im[1][2] = m[2][1]; + im[2][0] = m[0][2]; + im[2][1] = m[1][2]; + + memset( zrot, 0, sizeof( zrot ) ); + zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F; + + rad = (float)DEG2RAD( degrees ); + zrot[0][0] = (vec_t)cos( rad ); + zrot[0][1] = (vec_t)sin( rad ); + zrot[1][0] = (vec_t)-sin( rad ); + zrot[1][1] = (vec_t)cos( rad ); + + MatrixMultiply( m, zrot, tmpmat ); + MatrixMultiply( tmpmat, im, rot ); + + for ( i = 0; i < 3; i++ ) { + dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2]; + } +} diff --git a/libs/mathlib/mathlib.vcproj b/libs/mathlib/mathlib.vcproj new file mode 100644 index 00000000..71b708dd --- /dev/null +++ b/libs/mathlib/mathlib.vcproj @@ -0,0 +1,320 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/mathlib/ray.c b/libs/mathlib/ray.c new file mode 100644 index 00000000..bea7c5c5 --- /dev/null +++ b/libs/mathlib/ray.c @@ -0,0 +1,140 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "mathlib.h" +#include + +vec3_t identity = { 0,0,0 }; + +void ray_construct_for_vec3(ray_t *ray, const vec3_t origin, const vec3_t direction) +{ + VectorCopy(origin, ray->origin); + VectorCopy(direction, ray->direction); +} + +void ray_transform(ray_t *ray, const m4x4_t matrix) +{ + m4x4_transform_point(matrix, ray->origin); + m4x4_transform_normal(matrix, ray->direction); +} + +vec_t ray_intersect_point(const ray_t *ray, const vec3_t point, vec_t epsilon, vec_t divergence) +{ + vec3_t displacement; + vec_t depth; + + // calc displacement of test point from ray origin + VectorSubtract(point, ray->origin, displacement); + // calc length of displacement vector along ray direction + depth = DotProduct(displacement, ray->direction); + if(depth < 0.0f) return (vec_t)FLT_MAX; + // calc position of closest point on ray to test point + VectorMA (ray->origin, depth, ray->direction, displacement); + // calc displacement of test point from closest point + VectorSubtract(point, displacement, displacement); + // calc length of displacement, subtract depth-dependant epsilon + if (VectorLength(displacement) - (epsilon + (depth * divergence)) > 0.0f) return (vec_t)FLT_MAX; + return depth; +} + +// Tomas Moller and Ben Trumbore. Fast, minimum storage ray-triangle intersection. Journal of graphics tools, 2(1):21-28, 1997 + +#define EPSILON 0.000001 + +vec_t ray_intersect_triangle(const ray_t *ray, qboolean bCullBack, const vec3_t vert0, const vec3_t vert1, const vec3_t vert2) +{ + float edge1[3], edge2[3], tvec[3], pvec[3], qvec[3]; + float det,inv_det; + float u, v; + vec_t depth = (vec_t)FLT_MAX; + + /* find vectors for two edges sharing vert0 */ + VectorSubtract(vert1, vert0, edge1); + VectorSubtract(vert2, vert0, edge2); + + /* begin calculating determinant - also used to calculate U parameter */ + CrossProduct(ray->direction, edge2, pvec); + + /* if determinant is near zero, ray lies in plane of triangle */ + det = DotProduct(edge1, pvec); + + if (bCullBack == qtrue) + { + if (det < EPSILON) + return depth; + + // calculate distance from vert0 to ray origin + VectorSubtract(ray->origin, vert0, tvec); + + // calculate U parameter and test bounds + u = DotProduct(tvec, pvec); + if (u < 0.0 || u > det) + return depth; + + // prepare to test V parameter + CrossProduct(tvec, edge1, qvec); + + // calculate V parameter and test bounds + v = DotProduct(ray->direction, qvec); + if (v < 0.0 || u + v > det) + return depth; + + // calculate t, scale parameters, ray intersects triangle + depth = DotProduct(edge2, qvec); + inv_det = 1.0f / det; + depth *= inv_det; + //u *= inv_det; + //v *= inv_det; + } + else + { + /* the non-culling branch */ + if (det > -EPSILON && det < EPSILON) + return depth; + inv_det = 1.0f / det; + + /* calculate distance from vert0 to ray origin */ + VectorSubtract(ray->origin, vert0, tvec); + + /* calculate U parameter and test bounds */ + u = DotProduct(tvec, pvec) * inv_det; + if (u < 0.0 || u > 1.0) + return depth; + + /* prepare to test V parameter */ + CrossProduct(tvec, edge1, qvec); + + /* calculate V parameter and test bounds */ + v = DotProduct(ray->direction, qvec) * inv_det; + if (v < 0.0 || u + v > 1.0) + return depth; + + /* calculate t, ray intersects triangle */ + depth = DotProduct(edge2, qvec) * inv_det; + } + return depth; +} + +vec_t ray_intersect_plane(const ray_t* ray, const vec3_t normal, vec_t dist) +{ + return -(DotProduct(normal, ray->origin) - dist) / DotProduct(ray->direction, normal); +} + diff --git a/libs/md5lib.h b/libs/md5lib.h new file mode 100644 index 00000000..f0aca115 --- /dev/null +++ b/libs/md5lib.h @@ -0,0 +1,91 @@ +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5lib.h,v 1.1.2.1 2003/07/19 23:25:50 spog Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke . + 1999-05-03 lpd Original version. + */ + +#ifndef md5_INCLUDED +# define md5_INCLUDED + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef unsigned int md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* md5_INCLUDED */ diff --git a/libs/md5lib/md5lib.c b/libs/md5lib/md5lib.c new file mode 100644 index 00000000..60b45148 --- /dev/null +++ b/libs/md5lib/md5lib.c @@ -0,0 +1,395 @@ +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5lib.c,v 1.1 2003/07/18 04:24:39 ydnar Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2003-07-17 ydnar added to gtkradiant project from + http://sourceforge.net/projects/libmd5-rfc/ + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +#include "md5lib.h" /* ydnar */ +#include + +/* ydnar: gtkradiant endian picking */ +#ifdef _SGI_SOURCE +#define __BIG_ENDIAN__ +#endif + +#ifdef __BIG_ENDIAN__ +#define ARCH_IS_BIG_ENDIAN 1 +#else +#define ARCH_IS_BIG_ENDIAN 0 +#endif +/* ydnar: end */ + +#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#ifdef ARCH_IS_BIG_ENDIAN +# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +#else +# define BYTE_ORDER 0 +#endif + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#if BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; +#endif + + { +#if BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ +#endif +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } +#endif +#if BYTE_ORDER == 0 + else /* dynamic big-endian */ +#endif +#if BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + +# if BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +# else +# define xbuf X /* (static only) */ +# endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} diff --git a/libs/md5lib/md5lib.vcproj b/libs/md5lib/md5lib.vcproj new file mode 100644 index 00000000..e7b6e474 --- /dev/null +++ b/libs/md5lib/md5lib.vcproj @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/memory/allocator.cpp b/libs/memory/allocator.cpp new file mode 100644 index 00000000..17fbdaca --- /dev/null +++ b/libs/memory/allocator.cpp @@ -0,0 +1,80 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "memory/allocator.h" + +#include + +template +struct Vector +{ + typedef std::vector > Type; +}; + +namespace +{ + class Bleh + { + int* m_blah; + public: + Bleh(int* blah) : m_blah(blah) + { + } + ~Bleh() + { + *m_blah = 15; + } + }; + + void TestAllocator() + { + Vector::Type test; + + int i = 0; + test.push_back(Bleh(&i)); + } + + void TestNewDelete() + { + { + NamedAllocator allocator("test"); + int* p = NamedNew::type(allocator).scalar(); + //new int(); + NamedDelete::type(allocator).scalar(p); + } + + { + int* p = New().scalar(3); + Delete().scalar(p); + } + + { + int* p = New().scalar(int(15.9)); + Delete().scalar(p); + } + + { + int* p = New().vector(15); + // new int[15] + Delete().vector(p, 15); + } + } +} \ No newline at end of file diff --git a/libs/memory/allocator.h b/libs/memory/allocator.h new file mode 100644 index 00000000..c513d708 --- /dev/null +++ b/libs/memory/allocator.h @@ -0,0 +1,336 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MEMORY_ALLOCATOR_H) +#define INCLUDED_MEMORY_ALLOCATOR_H + +#include + +#if 0 + +#define DefaultAllocator std::allocator + +#else + +/// \brief An allocator that uses c++ new/delete. +/// Compliant with the std::allocator interface. +template +class DefaultAllocator +{ +public: + + typedef Type value_type; + typedef value_type* pointer; + typedef const Type* const_pointer; + typedef Type& reference; + typedef const Type& const_reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + + template + struct rebind + { + typedef DefaultAllocator other; + }; + + DefaultAllocator() + { + } + DefaultAllocator(const DefaultAllocator&) + { + } + template DefaultAllocator(const DefaultAllocator&) + { + } + ~DefaultAllocator() + { + } + + pointer address(reference instance) const + { + return &instance; + } + const_pointer address(const_reference instance) const + { + return &instance; + } + Type* allocate(size_type size, const void* = 0) + { + return static_cast(::operator new(size * sizeof(Type))); + } + void deallocate(pointer p, size_type) + { + ::operator delete(p); + } + size_type max_size() const + { + return std::size_t(-1) / sizeof (Type); + } + void construct(pointer p, const Type& value) + { + new(p) Type(value); + } + void destroy(pointer p) + { + p->~Type(); + } +}; + +template +inline bool operator==(const DefaultAllocator&, const DefaultAllocator&) +{ + return true; +} +template +inline bool operator==(const DefaultAllocator&, const OtherAllocator&) +{ + return false; +} + +#endif + + +template +class NamedAllocator : public DefaultAllocator +{ + typedef DefaultAllocator allocator_type; + + const char* m_name; +public: + + typedef Type value_type; + typedef value_type* pointer; + typedef const Type* const_pointer; + typedef Type& reference; + typedef const Type& const_reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + + template + struct rebind + { + typedef NamedAllocator other; + }; + + explicit NamedAllocator(const char* name) : m_name(name) + { + } + NamedAllocator(const NamedAllocator& other) : m_name(other.m_name) + { + } + template NamedAllocator(const NamedAllocator& other) : m_name(other.m_name) + { + } + ~NamedAllocator() + { + } + + pointer address(reference instance) const + { + return allocator_type::address(instance); + } + const_pointer address(const_reference instance) const + { + return allocator_type::address(instance); + } + Type* allocate(size_type size, const void* = 0) + { + return allocator_type::allocate(size); + } + void deallocate(pointer p, size_type size) + { + allocator_type::deallocate(p, size); + } + size_type max_size() const + { + return allocator_type::max_size(); + } + void construct(pointer p, const Type& value) + { + allocator_type::construct(p, value); + } + void destroy(pointer p) + { + allocator_type::destroy(p); + } + + template + bool operator==(const NamedAllocator& other) + { + return true; + } + + // returns true if the allocators are not interchangeable + template + bool operator!=(const NamedAllocator& other) + { + return false; + } +}; + + + +#include +#include "generic/object.h" + + + +template +class DefaultConstruct +{ +public: + void operator()(Type& t) + { + constructor(t); + } +}; + +template +class Construct +{ + const T1& other; +public: + Construct(const T1& other_) : other(other_) + { + } + void operator()(Type& t) + { + constructor(t, other); + } +}; + +template +class Destroy +{ +public: + void operator()(Type& t) + { + destructor(t); + } +}; + +template > +class New : public Allocator +{ +public: + New() + { + } + explicit New(const Allocator& allocator) : Allocator(allocator) + { + } + + Type* scalar() + { + return new(Allocator::allocate(1)) Type(); + } + template + Type* scalar(const T1& t1) + { + return new(Allocator::allocate(1)) Type(t1); + } + template + Type* scalar(const T1& t1, const T2& t2) + { + return new(Allocator::allocate(1)) Type(t1, t2); + } + template + Type* scalar(const T1& t1, const T2& t2, const T3& t3) + { + return new(Allocator::allocate(1)) Type(t1, t2, t3); + } + template + Type* scalar(const T1& t1, const T2& t2, const T3& t3, const T4& t4) + { + return new(Allocator::allocate(1)) Type(t1, t2, t3, t4); + } + template + Type* scalar(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5) + { + return new(Allocator::allocate(1)) Type(t1, t2, t3, t4, t5); + } + Type* vector(std::size_t size) + { +#if 1 + Type* p = Allocator::allocate(size); + std::for_each(p, p + size, DefaultConstruct()); + return p; +#else + // this does not work with msvc71 runtime + return new(Allocator::allocate(size)) Type[size]; +#endif + } + template + Type* vector(std::size_t size, const T1& t1) + { + Type* p = Allocator::allocate(size); + std::for_each(p, p + size, Construct(t1)); + return p; + } +}; + +template > +class Delete : public Allocator +{ +public: + Delete() + { + } + explicit Delete(const Allocator& allocator) : Allocator(allocator) + { + } + + void scalar(Type* p) + { + if(p != 0) + { + p->~Type(); + Allocator::deallocate(p, 1); + } + } + void vector(Type* p, std::size_t size) + { + // '::operator delete' handles null + // 'std::allocator::deallocate' requires non-null + if(p != 0) + { + std::for_each(p, p + size, Destroy()); + Allocator::deallocate(p, size); + } + } +}; + + +template +class NamedNew +{ +public: + typedef New > type; +}; + +template +class NamedDelete +{ +public: + typedef Delete > type; +}; + +#endif diff --git a/libs/moduleobservers.cpp b/libs/moduleobservers.cpp new file mode 100644 index 00000000..a2d51f7d --- /dev/null +++ b/libs/moduleobservers.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "moduleobservers.h" + diff --git a/libs/moduleobservers.h b/libs/moduleobservers.h new file mode 100644 index 00000000..cd3bb1ef --- /dev/null +++ b/libs/moduleobservers.h @@ -0,0 +1,64 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MODULEOBSERVERS_H) +#define INCLUDED_MODULEOBSERVERS_H + +#include "debugging/debugging.h" +#include +#include "moduleobserver.h" + +class ModuleObservers +{ + typedef std::set Observers; + Observers m_observers; +public: + ~ModuleObservers() + { + ASSERT_MESSAGE(m_observers.empty(), "ModuleObservers::~ModuleObservers: observers still attached"); + } + void attach(ModuleObserver& observer) + { + ASSERT_MESSAGE(m_observers.find(&observer) == m_observers.end(), "ModuleObservers::attach: cannot attach observer"); + m_observers.insert(&observer); + } + void detach(ModuleObserver& observer) + { + ASSERT_MESSAGE(m_observers.find(&observer) != m_observers.end(), "ModuleObservers::detach: cannot detach observer"); + m_observers.erase(&observer); + } + void realise() + { + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->realise(); + } + } + void unrealise() + { + for(Observers::reverse_iterator i = m_observers.rbegin(); i != m_observers.rend(); ++i) + { + (*i)->unrealise(); + } + } +}; + +#endif diff --git a/libs/modulesystem/moduleregistry.cpp b/libs/modulesystem/moduleregistry.cpp new file mode 100644 index 00000000..9841f797 --- /dev/null +++ b/libs/modulesystem/moduleregistry.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "moduleregistry.h" + diff --git a/libs/modulesystem/moduleregistry.h b/libs/modulesystem/moduleregistry.h new file mode 100644 index 00000000..bd39cb06 --- /dev/null +++ b/libs/modulesystem/moduleregistry.h @@ -0,0 +1,65 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MODULESYSTEM_MODULEREGISTRY_H) +#define INCLUDED_MODULESYSTEM_MODULEREGISTRY_H + +#include "generic/static.h" +#include + +class ModuleRegisterable +{ +public: + virtual void selfRegister() = 0; +}; + +class ModuleRegistryList +{ + typedef std::list RegisterableModules; + RegisterableModules m_modules; +public: + void addModule(ModuleRegisterable& module) + { + m_modules.push_back(&module); + } + void registerModules() const + { + for(RegisterableModules::const_iterator i = m_modules.begin(); i != m_modules.end(); ++i) + { + (*i)->selfRegister(); + } + } +}; + +typedef SmartStatic StaticModuleRegistryList; + + +class StaticRegisterModule : public StaticModuleRegistryList +{ +public: + StaticRegisterModule(ModuleRegisterable& module) + { + StaticModuleRegistryList::instance().addModule(module); + } +}; + + +#endif diff --git a/libs/modulesystem/modulesmap.cpp b/libs/modulesystem/modulesmap.cpp new file mode 100644 index 00000000..4edfc701 --- /dev/null +++ b/libs/modulesystem/modulesmap.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "modulesmap.h" diff --git a/libs/modulesystem/modulesmap.h b/libs/modulesystem/modulesmap.h new file mode 100644 index 00000000..dfc2ea45 --- /dev/null +++ b/libs/modulesystem/modulesmap.h @@ -0,0 +1,152 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MODULESYSTEM_MODULESMAP_H) +#define INCLUDED_MODULESYSTEM_MODULESMAP_H + +#include "modulesystem.h" +#include "string/string.h" +#include +#include + +template +class ModulesMap : public Modules +{ + typedef std::map modules_t; + modules_t m_modules; +public: + ~ModulesMap() + { + for(modules_t::iterator i = m_modules.begin(); i != m_modules.end(); ++i) + { + (*i).second->release(); + } + } + + typedef modules_t::const_iterator iterator; + + iterator begin() const + { + return m_modules.begin(); + } + iterator end() const + { + return m_modules.end(); + } + + void insert(const char* name, Module& module) + { + module.capture(); + if(globalModuleServer().getError()) + { + module.release(); + globalModuleServer().setError(false); + } + else + { + m_modules.insert(modules_t::value_type(name, &module)); + } + } + + Type* find(const char* name) + { + modules_t::iterator i = m_modules.find(name); + if(i != m_modules.end()) + { + return static_cast(Module_getTable(*(*i).second)); + } + return 0; + } + + Type* findModule(const char* name) + { + return find(name); + } + void foreachModule(const typename Modules::Visitor& visitor) + { + for(modules_t::iterator i = m_modules.begin(); i != m_modules.end(); ++i) + { + visitor.visit((*i).first.c_str(), *static_cast(Module_getTable(*(*i).second))); + } + } +}; + +template +class InsertModules : public ModuleServer::Visitor +{ + ModulesMap& m_modules; +public: + InsertModules(ModulesMap& modules) + : m_modules(modules) + { + } + void visit(const char* name, Module& module) const + { + m_modules.insert(name, module); + } +}; + +template +class ModulesRef +{ + ModulesMap m_modules; +public: + ModulesRef(const char* names) + { + if(!globalModuleServer().getError()) + { + if(string_equal(names, "*")) + { + InsertModules visitor(m_modules); + globalModuleServer().foreachModule(typename Type::Name(), typename Type::Version(), visitor); + } + else + { + StringTokeniser tokeniser(names); + for(;;) + { + const char* name = tokeniser.getToken(); + if(string_empty(name)) + { + break; + } + Module* module = globalModuleServer().findModule(typename Type::Name(), typename Type::Version(), name); + if(module == 0) + { + globalModuleServer().setError(true); + globalErrorStream() << "ModulesRef::initialise: type=" << makeQuoted(typename Type::Name()) << " version=" << makeQuoted(typename Type::Version()) << " name=" << makeQuoted(name) << " - not found\n"; + break; + } + else + { + m_modules.insert(name, *module); + } + } + } + } + } + ModulesMap& get() + { + return m_modules; + } +}; + +#endif diff --git a/libs/modulesystem/singletonmodule.cpp b/libs/modulesystem/singletonmodule.cpp new file mode 100644 index 00000000..866c24fa --- /dev/null +++ b/libs/modulesystem/singletonmodule.cpp @@ -0,0 +1,48 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "singletonmodule.h" +#include "generic/constant.h" + +class NullType +{ +public: + INTEGER_CONSTANT(Version, 1); + STRING_CONSTANT(Name, ""); +}; + +class NullModule +{ +public: + typedef NullType Type; + STRING_CONSTANT(Name, ""); + void* getTable() + { + return NULL; + } +}; + +void TEST_SINGLETONMODULE() +{ + SingletonModule null; + null.capture(); + null.release(); +} diff --git a/libs/modulesystem/singletonmodule.h b/libs/modulesystem/singletonmodule.h new file mode 100644 index 00000000..f68587b7 --- /dev/null +++ b/libs/modulesystem/singletonmodule.h @@ -0,0 +1,150 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MODULESYSTEM_SINGLETONMODULE_H) +#define INCLUDED_MODULESYSTEM_SINGLETONMODULE_H + +#include "modulesystem.h" +#include +#include "debugging/debugging.h" +#include "modulesystem/moduleregistry.h" +#include "generic/reference.h" + +template +class DefaultAPIConstructor +{ +public: + const char* getName() + { + return typename API::Name(); + } + + API* constructAPI(Dependencies& dependencies) + { + return new API; + } + void destroyAPI(API* api) + { + delete api; + } +}; + +template +class DependenciesAPIConstructor +{ +public: + const char* getName() + { + return typename API::Name(); + } + + API* constructAPI(Dependencies& dependencies) + { + return new API(dependencies); + } + void destroyAPI(API* api) + { + delete api; + } +}; + +class NullDependencies +{ +}; + + +template > +class SingletonModule : public APIConstructor, public Module, public ModuleRegisterable +{ + Dependencies* m_dependencies; + API* m_api; + std::size_t m_refcount; + bool m_dependencyCheck; + bool m_cycleCheck; +public: + typedef typename API::Type Type; + + SingletonModule() + : m_dependencies(0), m_api(0), m_refcount(0), m_dependencyCheck(false), m_cycleCheck(false) + { + } + explicit SingletonModule(const APIConstructor& constructor) + : APIConstructor(constructor), m_dependencies(0), m_api(0), m_refcount(0), m_dependencyCheck(false), m_cycleCheck(false) + { + } + ~SingletonModule() + { + ASSERT_MESSAGE(m_refcount == 0, "module still referenced at shutdown"); + } + + void selfRegister() + { + globalModuleServer().registerModule(typename Type::Name(), typename Type::Version(), APIConstructor::getName(), *this); + } + + Dependencies& getDependencies() + { + return *m_dependencies; + } + void* getTable() + { + if(m_api != 0) + { + return m_api->getTable(); + } + return 0; + } + void capture() + { + if(++m_refcount == 1) + { + globalOutputStream() << "Module Initialising: '" << typename Type::Name() << "' '" << APIConstructor::getName() << "'\n"; + m_dependencies = new Dependencies(); + m_dependencyCheck = !globalModuleServer().getError(); + if(m_dependencyCheck) + { + m_api = APIConstructor::constructAPI(*m_dependencies); + globalOutputStream() << "Module Ready: '" << typename Type::Name() << "' '" << APIConstructor::getName() << "'\n"; + } + else + { + globalOutputStream() << "Module Dependencies Failed: '" << typename Type::Name() << "' '" << APIConstructor::getName() << "'\n"; + } + m_cycleCheck = true; + } + + ASSERT_MESSAGE(m_cycleCheck, "cyclic dependency detected"); + } + void release() + { + if(--m_refcount == 0) + { + if(m_dependencyCheck) + { + APIConstructor::destroyAPI(m_api); + } + delete m_dependencies; + } + } +}; + + +#endif diff --git a/libs/os/dir.cpp b/libs/os/dir.cpp new file mode 100644 index 00000000..a775e5bd --- /dev/null +++ b/libs/os/dir.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "dir.h" + diff --git a/libs/os/dir.h b/libs/os/dir.h new file mode 100644 index 00000000..bdb385e1 --- /dev/null +++ b/libs/os/dir.h @@ -0,0 +1,75 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_OS_DIR_H) +#define INCLUDED_OS_DIR_H + +/// \file +/// \brief OS directory-listing object. + +#include + +typedef GDir Directory; + +inline bool directory_good(Directory* directory) +{ + return directory != 0; +} + +inline Directory* directory_open(const char* name) +{ + return g_dir_open(name, 0, 0); +} + +inline void directory_close(Directory* directory) +{ + g_dir_close(directory); +} + +inline const char* directory_read_and_increment(Directory* directory) +{ + return g_dir_read_name(directory); +} + +template +void Directory_forEach(const char* path, const Functor& functor) +{ + Directory* dir = directory_open(path); + + if(directory_good(dir)) + { + for(;;) + { + const char* name = directory_read_and_increment(dir); + if(name == 0) + { + break; + } + + functor(name); + } + + directory_close(dir); + } +} + + +#endif diff --git a/libs/os/file.cpp b/libs/os/file.cpp new file mode 100644 index 00000000..766638f9 --- /dev/null +++ b/libs/os/file.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "file.h" + diff --git a/libs/os/file.h b/libs/os/file.h new file mode 100644 index 00000000..b1290760 --- /dev/null +++ b/libs/os/file.h @@ -0,0 +1,152 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_OS_FILE_H) +#define INCLUDED_OS_FILE_H + +/// \file +/// \brief OS file-system querying and manipulation. + +#if defined( WIN32 ) +#define S_ISDIR(mode) (mode & _S_IFDIR) +#include // _access() +#define F_OK 0x00 +#define W_OK 0x02 +#define R_OK 0x04 +#define access(path, mode) _access(path, mode) +#else +#include // access() +#endif + +#include // rename(), remove() +#include // stat() +#include // this is included by stat.h on win32 +#include +#include + +#include "debugging/debugging.h" + +/// \brief Attempts to move the file identified by \p from to \p to and returns true if the operation was successful. +/// +/// The operation will fail unless: +/// - The path \p from identifies an existing file which is accessible for writing. +/// - The directory component of \p from identifies an existing directory which is accessible for writing. +/// - The path \p to does not identify an existing file or directory. +/// - The directory component of \p to identifies an existing directory which is accessible for writing. +inline bool file_move(const char* from, const char* to) +{ + ASSERT_MESSAGE(from != 0 && to != 0, "file_move: invalid path"); + return rename(from, to) == 0; +} + +/// \brief Attempts to remove the file identified by \p path and returns true if the operation was successful. +/// +/// The operation will fail unless: +/// - The \p path identifies an existing file. +/// - The parent-directory component of \p path identifies an existing directory which is accessible for writing. +inline bool file_remove(const char* path) +{ + ASSERT_MESSAGE(path != 0, "file_remove: invalid path"); + return remove(path) == 0; +} + +namespace FileAccess +{ + enum Mode + { + Read = R_OK, + Write = W_OK, + ReadWrite = Read | Write, + Exists = F_OK + }; +} + +/// \brief Returns true if the file or directory identified by \p path exists and/or may be accessed for reading, writing or both, depending on the value of \p mode. +inline bool file_accessible(const char* path, FileAccess::Mode mode) +{ + ASSERT_MESSAGE(path != 0, "file_accessible: invalid path"); + return access(path, static_cast(mode)) == 0; +} + +/// \brief Returns true if the file or directory identified by \p path exists and may be opened for reading. +inline bool file_readable(const char* path) +{ + return file_accessible(path, FileAccess::Read); +} + +/// \brief Returns true if the file or directory identified by \p path exists and may be opened for writing. +inline bool file_writeable(const char* path) +{ + return file_accessible(path, FileAccess::Write); +} + +/// \brief Returns true if the file or directory identified by \p path exists. +inline bool file_exists(const char* path) +{ + return file_accessible(path, FileAccess::Exists); +} + +/// \brief Returns true if the file or directory identified by \p path exists and is a directory. +inline bool file_is_directory(const char* path) +{ + ASSERT_MESSAGE(path != 0, "file_is_directory: invalid path"); + struct stat st; + if(stat(path, &st) == -1) + { + return false; + } + return S_ISDIR (st.st_mode) != 0; +} + +typedef std::size_t FileSize; + +/// \brief Returns the size in bytes of the file identified by \p path, or 0 if the file was not found. +inline FileSize file_size(const char* path) +{ + ASSERT_MESSAGE(path != 0, "file_size: invalid path"); + struct stat st; + if(stat(path, &st) == -1) + { + return 0; + } + return st.st_size; +} + +/// Seconds elapsed since Jan 1, 1970 +typedef std::time_t FileTime; +/// No file can have been modified earlier than this time. +const FileTime c_invalidFileTime = -1; + +/// \brief Returns the time that the file identified by \p path was last modified, or c_invalidFileTime if the file was not found. +inline FileTime file_modified(const char* path) +{ + ASSERT_MESSAGE(path != 0, "file_modified: invalid path"); + struct stat st; + if(stat(path, &st) == -1) + { + return c_invalidFileTime; + } + return st.st_mtime; +} + + + +#endif diff --git a/libs/os/path.cpp b/libs/os/path.cpp new file mode 100644 index 00000000..ab03e1d2 --- /dev/null +++ b/libs/os/path.cpp @@ -0,0 +1,22 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "path.h" diff --git a/libs/os/path.h b/libs/os/path.h new file mode 100644 index 00000000..082b56fa --- /dev/null +++ b/libs/os/path.h @@ -0,0 +1,283 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_OS_PATH_H) +#define INCLUDED_OS_PATH_H + +/// \file +/// \brief OS file-system path comparison, decomposition and manipulation. +/// +/// - Paths are c-style null-terminated-character-arrays. +/// - Path separators must be forward slashes (unix style). +/// - Directory paths must end in a separator. +/// - Paths must not contain the ascii characters \\ : * ? " < > or |. +/// - Paths may be encoded in UTF-8 or any extended-ascii character set. + +#include "string/string.h" + +#if defined(WIN32) +#define OS_CASE_INSENSITIVE +#endif + +/// \brief Returns true if \p path is lexicographically sorted before \p other. +/// If both \p path and \p other refer to the same file, neither will be sorted before the other. +/// O(n) +inline bool path_less(const char* path, const char* other) +{ +#if defined(OS_CASE_INSENSITIVE) + return string_less_nocase(path, other); +#else + return string_less(path, other); +#endif +} + +/// \brief Returns <0 if \p path is lexicographically less than \p other. +/// Returns >0 if \p path is lexicographically greater than \p other. +/// Returns 0 if both \p path and \p other refer to the same file. +/// O(n) +inline int path_compare(const char* path, const char* other) +{ +#if defined(OS_CASE_INSENSITIVE) + return string_compare_nocase(path, other); +#else + return string_compare(path, other); +#endif +} + +/// \brief Returns true if \p path and \p other refer to the same file or directory. +/// O(n) +inline bool path_equal(const char* path, const char* other) +{ +#if defined(OS_CASE_INSENSITIVE) + return string_equal_nocase(path, other); +#else + return string_equal(path, other); +#endif +} + +/// \brief Returns true if the first \p n bytes of \p path and \p other form paths that refer to the same file or directory. +/// If the paths are UTF-8 encoded, [\p path, \p path + \p n) must be a complete path. +/// O(n) +inline bool path_equal_n(const char* path, const char* other, std::size_t n) +{ +#if defined(OS_CASE_INSENSITIVE) + return string_equal_nocase_n(path, other, n); +#else + return string_equal_n(path, other, n); +#endif +} + + +/// \brief Returns true if \p path is a fully qualified file-system path. +/// O(1) +inline bool path_is_absolute(const char* path) +{ +#if defined(WIN32) + return path[0] == '/' + || (path[0] != '\0' && path[1] == ':'); // local drive +#elif defined(POSIX) + return path[0] == '/'; +#endif +} + +/// \brief Returns true if \p path is a directory. +/// O(n) +inline bool path_is_directory(const char* path) +{ + std::size_t length = strlen(path); + if(length > 0) + { + return path[length-1] == '/'; + } + return false; +} + +/// \brief Returns a pointer to the first character of the component of \p path following the first directory component. +/// O(n) +inline const char* path_remove_directory(const char* path) +{ + const char* first_separator = strchr(path, '/'); + if(first_separator != 0) + { + return ++first_separator; + } + return ""; +} + +/// \brief Returns a pointer to the first character of the filename component of \p path. +/// O(n) +inline const char* path_get_filename_start(const char* path) +{ + { + const char* last_forward_slash = strrchr(path, '/'); + if(last_forward_slash != 0) + { + return last_forward_slash + 1; + } + } + + // not strictly necessary,since paths should not contain '\' + { + const char* last_backward_slash = strrchr(path, '\\'); + if(last_backward_slash != 0) + { + return last_backward_slash + 1; + } + } + + return path; +} + +/// \brief Returns a pointer to the character after the end of the filename component of \p path - either the extension separator or the terminating null character. +/// O(n) +inline const char* path_get_filename_base_end(const char* path) +{ + const char* last_period = strrchr(path_get_filename_start(path), '.'); + return (last_period != 0) ? last_period : path + string_length(path); +} + +/// \brief Returns the length of the filename component (not including extension) of \p path. +/// O(n) +inline std::size_t path_get_filename_base_length(const char* path) +{ + return path_get_filename_base_end(path) - path; +} + +/// \brief If \p path is a child of \p base, returns the subpath relative to \p base, else returns \p path. +/// O(n) +inline const char* path_make_relative(const char* path, const char* base) +{ + const std::size_t length = string_length(base); + if(path_equal_n(path, base, length)) + { + return path + length; + } + return path; +} + +/// \brief Returns a pointer to the first character of the file extension of \p path, or "" if not found. +/// O(n) +inline const char* path_get_extension(const char* path) +{ + const char* last_period = strrchr(path_get_filename_start(path), '.'); + if(last_period != 0) + { + return ++last_period; + } + return ""; +} + +/// \brief Returns true if \p extension is of the same type as \p other. +/// O(n) +inline bool extension_equal(const char* extension, const char* other) +{ + return path_equal(extension, other); +} + +template +class MatchFileExtension +{ + const char* m_extension; + const Functor& m_functor; +public: + MatchFileExtension(const char* extension, const Functor& functor) : m_extension(extension), m_functor(functor) + { + } + void operator()(const char* name) const + { + const char* extension = path_get_extension(name); + if(extension_equal(extension, m_extension)) + { + m_functor(name); + } + } +}; + +/// \brief A functor which invokes its contained \p functor if the \p name argument matches its \p extension. +template +inline MatchFileExtension matchFileExtension(const char* extension, const Functor& functor) +{ + return MatchFileExtension(extension, functor); +} + +class PathCleaned +{ +public: + const char* m_path; + PathCleaned(const char* path) : m_path(path) + { + } +}; + +/// \brief Writes \p path to \p ostream with dos-style separators replaced by unix-style separators. +template +TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const PathCleaned& path) +{ + const char* i = path.m_path; + for(; *i != '\0'; ++i) + { + if(*i == '\\') + { + ostream << '/'; + } + else + { + ostream << *i; + } + } + return ostream; +} + +class DirectoryCleaned +{ +public: + const char* m_path; + DirectoryCleaned(const char* path) : m_path(path) + { + } +}; + +/// \brief Writes \p path to \p ostream with dos-style separators replaced by unix-style separators, and appends a separator if necessary. +template +TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const DirectoryCleaned& path) +{ + const char* i = path.m_path; + for(; *i != '\0'; ++i) + { + if(*i == '\\') + { + ostream << '/'; + } + else + { + ostream << *i; + } + } + char c = *(i - 1); + if(c != '/' && c != '\\') + { + ostream << '/'; + } + return ostream; +} + + +#endif diff --git a/libs/picomodel.h b/libs/picomodel.h new file mode 100644 index 00000000..f7ac8a3a --- /dev/null +++ b/libs/picomodel.h @@ -0,0 +1,351 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef PICOMODEL_H +#define PICOMODEL_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + + + +/* version */ +#define PICOMODEL_VERSION "0.8.20" + + +/* constants */ +#define PICO_GROW_SHADERS 16 +#define PICO_GROW_SURFACES 16 +#define PICO_GROW_VERTEXES 1024 +#define PICO_GROW_INDEXES 1024 +#define PICO_GROW_ARRAYS 8 +#define PICO_GROW_FACES 256 +#define PICO_MAX_SPECIAL 8 +#define PICO_MAX_DEFAULT_EXTS 4 /* max default extensions per module */ + + +/* types */ +typedef unsigned char picoByte_t; +typedef float picoVec_t; +typedef float picoVec2_t[ 2 ]; +typedef float picoVec3_t[ 3 ]; +typedef float picoVec4_t[ 4 ]; +typedef picoByte_t picoColor_t[ 4 ]; +typedef int picoIndex_t; + +typedef enum +{ + PICO_BAD, + PICO_TRIANGLES, + PICO_PATCH +} +picoSurfaceType_t; + +typedef enum +{ + PICO_NORMAL, + PICO_VERBOSE, + PICO_WARNING, + PICO_ERROR, + PICO_FATAL +} +picoPrintLevel_t; + +typedef struct picoSurface_s picoSurface_t; +typedef struct picoShader_s picoShader_t; +typedef struct picoModel_s picoModel_t; +typedef struct picoModule_s picoModule_t; + +struct picoSurface_s +{ + void *data; + + picoModel_t *model; /* owner model */ + + picoSurfaceType_t type; + char *name; /* sea: surface name */ + picoShader_t *shader; /* ydnar: changed to ptr */ + + int numVertexes, maxVertexes; + picoVec3_t *xyz; + picoVec3_t *normal; + picoIndex_t *smoothingGroup; + + int numSTArrays, maxSTArrays; + picoVec2_t **st; + + int numColorArrays, maxColorArrays; + picoColor_t **color; + + int numIndexes, maxIndexes; + picoIndex_t *index; + + int numFaceNormals, maxFaceNormals; + picoVec3_t *faceNormal; + + int special[ PICO_MAX_SPECIAL ]; +}; + + +/* seaw0lf */ +struct picoShader_s +{ + picoModel_t *model; /* owner model */ + + char *name; /* shader name */ + char *mapName; /* shader file name (name of diffuse texturemap) */ + picoColor_t ambientColor; /* ambient color of mesh (rgba) */ + picoColor_t diffuseColor; /* diffuse color of mesh (rgba) */ + picoColor_t specularColor; /* specular color of mesh (rgba) */ + float transparency; /* transparency (0..1; 1 = 100% transparent) */ + float shininess; /* shininess (0..128; 128 = 100% shiny) */ +}; + +struct picoModel_s +{ + void *data; + char *name; /* model name */ + char *fileName; /* sea: model file name */ + int frameNum; /* sea: renamed to frameNum */ + int numFrames; /* sea: number of frames */ + picoVec3_t mins; + picoVec3_t maxs; + + int numShaders, maxShaders; + picoShader_t **shader; + + int numSurfaces, maxSurfaces; + picoSurface_t **surface; + + const picoModule_t *module; /* sea */ +}; + + +/* seaw0lf */ +/* return codes used by the validation callbacks; pmv is short */ +/* for 'pico module validation'. everything >PICO_PMV_OK means */ +/* that there was an error. */ +enum +{ + PICO_PMV_OK, /* file valid */ + PICO_PMV_ERROR, /* file not valid */ + PICO_PMV_ERROR_IDENT, /* unknown file magic (aka ident) */ + PICO_PMV_ERROR_VERSION, /* unsupported file version */ + PICO_PMV_ERROR_SIZE, /* file size error */ + PICO_PMV_ERROR_MEMORY, /* out of memory error */ +}; + +/* convenience (makes it easy to add new params to the callbacks) */ +#define PM_PARAMS_CANLOAD \ + char *fileName, const void *buffer, int bufSize + +#define PM_PARAMS_LOAD \ + char *fileName, int frameNum, const void *buffer, int bufSize + +#define PM_PARAMS_CANSAVE \ + void + +#define PM_PARAMS_SAVE \ + char *fileName, picoModel_t *model + +/* pico file format module structure */ +struct picoModule_s +{ + char *version; /* internal module version (e.g. '1.5-b2') */ + + char *displayName; /* string used to display in guis, etc. */ + char *authorName; /* author name (eg. 'My Real Name') */ + char *copyright; /* copyright year and holder (eg. '2002 My Company') */ + + char *defaultExts[ PICO_MAX_DEFAULT_EXTS ]; /* default file extensions used by this file type */ + int (*canload)( PM_PARAMS_CANLOAD ); /* checks whether module can load given file (returns PMVR_*) */ + picoModel_t *(*load)( PM_PARAMS_LOAD ); /* parses model file data */ + int (*cansave)( PM_PARAMS_CANSAVE ); /* checks whether module can save (returns 1 or 0 and might spit out a message) */ + int (*save)( PM_PARAMS_SAVE ); /* saves a pico model in module's native model format */ +}; + + + +/* general functions */ +int PicoInit( void ); +void PicoShutdown( void ); +int PicoError( void ); + +void PicoSetMallocFunc( void *(*func)( size_t ) ); +void PicoSetFreeFunc( void (*func)( void* ) ); +void PicoSetLoadFileFunc( void (*func)( char*, unsigned char**, int* ) ); +void PicoSetFreeFileFunc( void (*func)( void* ) ); +void PicoSetPrintFunc( void (*func)( int, const char* ) ); + +const picoModule_t **PicoModuleList( int *numModules ); + +picoModel_t *PicoLoadModel( char *name, int frameNum ); + +typedef size_t (*PicoInputStreamReadFunc)(void* inputStream, unsigned char* buffer, size_t length); +picoModel_t* PicoModuleLoadModelStream( const picoModule_t* module, void* inputStream, PicoInputStreamReadFunc inputStreamRead, size_t streamLength, int frameNum ); + +/* model functions */ +picoModel_t *PicoNewModel( void ); +void PicoFreeModel( picoModel_t *model ); +int PicoAdjustModel( picoModel_t *model, int numShaders, int numSurfaces ); + + +/* shader functions */ +picoShader_t *PicoNewShader( picoModel_t *model ); +void PicoFreeShader( picoShader_t *shader ); +picoShader_t *PicoFindShader( picoModel_t *model, char *name, int caseSensitive ); + + +/* surface functions */ +picoSurface_t *PicoNewSurface( picoModel_t *model ); +void PicoFreeSurface( picoSurface_t *surface ); +picoSurface_t *PicoFindSurface( picoModel_t *model, char *name, int caseSensitive ); +int PicoAdjustSurface( picoSurface_t *surface, int numVertexes, int numSTArrays, int numColorArrays, int numIndexes, int numFaceNormals ); + + +/* setter functions */ +void PicoSetModelName( picoModel_t *model, char *name ); +void PicoSetModelFileName( picoModel_t *model, char *fileName ); +void PicoSetModelFrameNum( picoModel_t *model, int frameNum ); +void PicoSetModelNumFrames( picoModel_t *model, int numFrames ); +void PicoSetModelData( picoModel_t *model, void *data ); + +void PicoSetShaderName( picoShader_t *shader, char *name ); +void PicoSetShaderMapName( picoShader_t *shader, char *mapName ); +void PicoSetShaderAmbientColor( picoShader_t *shader, picoColor_t color ); +void PicoSetShaderDiffuseColor( picoShader_t *shader, picoColor_t color ); +void PicoSetShaderSpecularColor( picoShader_t *shader, picoColor_t color ); +void PicoSetShaderTransparency( picoShader_t *shader, float value ); +void PicoSetShaderShininess( picoShader_t *shader, float value ); + +void PicoSetSurfaceData( picoSurface_t *surface, void *data ); +void PicoSetSurfaceType( picoSurface_t *surface, picoSurfaceType_t type ); +void PicoSetSurfaceName( picoSurface_t *surface, char *name ); +void PicoSetSurfaceShader( picoSurface_t *surface, picoShader_t *shader ); +void PicoSetSurfaceXYZ( picoSurface_t *surface, int num, picoVec3_t xyz ); +void PicoSetSurfaceNormal( picoSurface_t *surface, int num, picoVec3_t normal ); +void PicoSetSurfaceST( picoSurface_t *surface, int array, int num, picoVec2_t st ); +void PicoSetSurfaceColor( picoSurface_t *surface, int array, int num, picoColor_t color ); +void PicoSetSurfaceIndex( picoSurface_t *surface, int num, picoIndex_t index ); +void PicoSetSurfaceIndexes( picoSurface_t *surface, int num, picoIndex_t *index, int count ); +void PicoSetFaceNormal( picoSurface_t *surface, int num, picoVec3_t normal ); +void PicoSetSurfaceSpecial( picoSurface_t *surface, int num, int special ); +void PicoSetSurfaceSmoothingGroup( picoSurface_t *surface, int num, picoIndex_t smoothingGroup ); + + +/* getter functions */ +char *PicoGetModelName( picoModel_t *model ); +char *PicoGetModelFileName( picoModel_t *model ); +int PicoGetModelFrameNum( picoModel_t *model ); +int PicoGetModelNumFrames( picoModel_t *model ); +void *PicoGetModelData( picoModel_t *model ); +int PicoGetModelNumShaders( picoModel_t *model ); +picoShader_t *PicoGetModelShader( picoModel_t *model, int num ); /* sea */ +int PicoGetModelNumSurfaces( picoModel_t *model ); +picoSurface_t *PicoGetModelSurface( picoModel_t *model, int num ); +int PicoGetModelTotalVertexes( picoModel_t *model ); +int PicoGetModelTotalIndexes( picoModel_t *model ); + +char *PicoGetShaderName( picoShader_t *shader ); +char *PicoGetShaderMapName( picoShader_t *shader ); +picoByte_t *PicoGetShaderAmbientColor( picoShader_t *shader ); +picoByte_t *PicoGetShaderDiffuseColor( picoShader_t *shader ); +picoByte_t *PicoGetShaderSpecularColor( picoShader_t *shader ); +float PicoGetShaderTransparency( picoShader_t *shader ); +float PicoGetShaderShininess( picoShader_t *shader ); + +void *PicoGetSurfaceData( picoSurface_t *surface ); +char *PicoGetSurfaceName( picoSurface_t *surface ); /* sea */ +picoSurfaceType_t PicoGetSurfaceType( picoSurface_t *surface ); +char *PicoGetSurfaceName( picoSurface_t *surface ); +picoShader_t *PicoGetSurfaceShader( picoSurface_t *surface ); /* sea */ + +int PicoGetSurfaceNumVertexes( picoSurface_t *surface ); +picoVec_t *PicoGetSurfaceXYZ( picoSurface_t *surface, int num ); +picoVec_t *PicoGetSurfaceNormal( picoSurface_t *surface, int num ); +picoVec_t *PicoGetSurfaceST( picoSurface_t *surface, int array, int num ); +picoByte_t *PicoGetSurfaceColor( picoSurface_t *surface, int array, int num ); +int PicoGetSurfaceNumIndexes( picoSurface_t *surface ); +picoIndex_t PicoGetSurfaceIndex( picoSurface_t *surface, int num ); +picoIndex_t *PicoGetSurfaceIndexes( picoSurface_t *surface, int num ); +picoVec_t *PicoGetFaceNormal( picoSurface_t *surface, int num ); +int PicoGetSurfaceSpecial( picoSurface_t *surface, int num ); + + +/* hashtable related functions */ +typedef struct picoVertexCombinationData_s +{ + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; +} picoVertexCombinationData_t; + +typedef struct picoVertexCombinationHash_s +{ + picoVertexCombinationData_t vcd; + picoIndex_t index; + + void *data; + + struct picoVertexCombinationHash_s *next; +} picoVertexCombinationHash_t; + +int PicoGetHashTableSize( void ); +unsigned int PicoVertexCoordGenerateHash( picoVec3_t xyz ); +picoVertexCombinationHash_t **PicoNewVertexCombinationHashTable( void ); +void PicoFreeVertexCombinationHashTable( picoVertexCombinationHash_t **hashTable ); +picoVertexCombinationHash_t *PicoFindVertexCombinationInHashTable( picoVertexCombinationHash_t **hashTable, picoVec3_t xyz, picoVec3_t normal, picoVec3_t st, picoColor_t color ); +picoVertexCombinationHash_t *PicoAddVertexCombinationToHashTable( picoVertexCombinationHash_t **hashTable, picoVec3_t xyz, picoVec3_t normal, picoVec3_t st, picoColor_t color, picoIndex_t index ); + +/* specialized functions */ +int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t normal, int numSTs, picoVec2_t *st, int numColors, picoColor_t *color, picoIndex_t smoothingGroup ); +void PicoFixSurfaceNormals( picoSurface_t *surface ); +int PicoRemapModel( picoModel_t *model, char *remapFile ); + + +void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t** normals, int numSTs, picoVec2_t **st, int numColors, picoColor_t **colors, picoShader_t* shader, picoIndex_t* smoothingGroup); + +/* end marker */ +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libs/picomodel/lwo/clip.c b/libs/picomodel/lwo/clip.c new file mode 100644 index 00000000..d83ea61f --- /dev/null +++ b/libs/picomodel/lwo/clip.c @@ -0,0 +1,278 @@ +/* +====================================================================== +clip.c + +Functions for LWO2 image references. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + + +/* +====================================================================== +lwFreeClip() + +Free memory used by an lwClip. +====================================================================== */ + +void lwFreeClip( lwClip *clip ) +{ + if ( clip ) { + lwListFree( clip->ifilter, (void *) lwFreePlugin ); + lwListFree( clip->pfilter, (void *) lwFreePlugin ); + + switch ( clip->type ) { + case ID_STIL: + _pico_free( clip->source.still.name); + break; + + case ID_ISEQ: + _pico_free( clip->source.seq.prefix ); + _pico_free( clip->source.seq.suffix ); + break; + + case ID_ANIM: + _pico_free( clip->source.anim.name ); + _pico_free( clip->source.anim.server ); + _pico_free( clip->source.anim.data ); + break; + + case ID_XREF: + _pico_free( clip->source.xref.string ); + break; + + case ID_STCC: + _pico_free( clip->source.cycle.name ); + break; + + default: + break; + } + + _pico_free( clip ); + } +} + + +/* +====================================================================== +lwGetClip() + +Read image references from a CLIP chunk in an LWO2 file. +====================================================================== */ + +lwClip *lwGetClip( picoMemStream_t *fp, int cksize ) +{ + lwClip *clip; + lwPlugin *filt; + unsigned int id; + unsigned short sz; + int pos, rlen; + + + /* allocate the Clip structure */ + + clip = _pico_calloc( 1, sizeof( lwClip )); + if ( !clip ) goto Fail; + + clip->contrast.val = 1.0f; + clip->brightness.val = 1.0f; + clip->saturation.val = 1.0f; + clip->gamma.val = 1.0f; + + /* remember where we started */ + + set_flen( 0 ); + pos = _pico_memstream_tell( fp ); + + /* index */ + + clip->index = getI4( fp ); + + /* first subchunk header */ + + clip->type = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + sz += sz & 1; + set_flen( 0 ); + + switch ( clip->type ) { + case ID_STIL: + clip->source.still.name = getS0( fp ); + break; + + case ID_ISEQ: + clip->source.seq.digits = getU1( fp ); + clip->source.seq.flags = getU1( fp ); + clip->source.seq.offset = getI2( fp ); + getU2( fp ); /* not sure what this is yet */ + clip->source.seq.start = getI2( fp ); + clip->source.seq.end = getI2( fp ); + clip->source.seq.prefix = getS0( fp ); + clip->source.seq.suffix = getS0( fp ); + break; + + case ID_ANIM: + clip->source.anim.name = getS0( fp ); + clip->source.anim.server = getS0( fp ); + rlen = get_flen(); + clip->source.anim.data = getbytes( fp, sz - rlen ); + break; + + case ID_XREF: + clip->source.xref.index = getI4( fp ); + clip->source.xref.string = getS0( fp ); + break; + + case ID_STCC: + clip->source.cycle.lo = getI2( fp ); + clip->source.cycle.hi = getI2( fp ); + clip->source.cycle.name = getS0( fp ); + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the CLIP chunk? */ + + rlen = _pico_memstream_tell( fp ) - pos; + if ( cksize < rlen ) goto Fail; + if ( cksize == rlen ) + return clip; + + /* process subchunks as they're encountered */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TIME: + clip->start_time = getF4( fp ); + clip->duration = getF4( fp ); + clip->frame_rate = getF4( fp ); + break; + + case ID_CONT: + clip->contrast.val = getF4( fp ); + clip->contrast.eindex = getVX( fp ); + break; + + case ID_BRIT: + clip->brightness.val = getF4( fp ); + clip->brightness.eindex = getVX( fp ); + break; + + case ID_SATR: + clip->saturation.val = getF4( fp ); + clip->saturation.eindex = getVX( fp ); + break; + + case ID_HUE: + clip->hue.val = getF4( fp ); + clip->hue.eindex = getVX( fp ); + break; + + case ID_GAMM: + clip->gamma.val = getF4( fp ); + clip->gamma.eindex = getVX( fp ); + break; + + case ID_NEGA: + clip->negative = getU2( fp ); + break; + + case ID_IFLT: + case ID_PFLT: + filt = _pico_calloc( 1, sizeof( lwPlugin )); + if ( !filt ) goto Fail; + + filt->name = getS0( fp ); + filt->flags = getU2( fp ); + rlen = get_flen(); + filt->data = getbytes( fp, sz - rlen ); + + if ( id == ID_IFLT ) { + lwListAdd( (void *) &clip->ifilter, filt ); + clip->nifilters++; + } + else { + lwListAdd( (void *) &clip->pfilter, filt ); + clip->npfilters++; + } + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the CLIP chunk? */ + + rlen = _pico_memstream_tell( fp ) - pos; + if ( cksize < rlen ) goto Fail; + if ( cksize == rlen ) break; + + /* get the next chunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return clip; + +Fail: + lwFreeClip( clip ); + return NULL; +} + + +/* +====================================================================== +lwFindClip() + +Returns an lwClip pointer, given a clip index. +====================================================================== */ + +lwClip *lwFindClip( lwClip *list, int index ) +{ + lwClip *clip; + + clip = list; + while ( clip ) { + if ( clip->index == index ) break; + clip = clip->next; + } + return clip; +} diff --git a/libs/picomodel/lwo/envelope.c b/libs/picomodel/lwo/envelope.c new file mode 100644 index 00000000..0b8ef4ea --- /dev/null +++ b/libs/picomodel/lwo/envelope.c @@ -0,0 +1,600 @@ +/* +====================================================================== +envelope.c + +Envelope functions for an LWO2 reader. + +Ernie Wright 16 Nov 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + +/* +====================================================================== +lwFreeEnvelope() + +Free the memory used by an lwEnvelope. +====================================================================== */ + +void lwFreeEnvelope( lwEnvelope *env ) +{ + if ( env ) { + if ( env->name ) _pico_free( env->name ); + lwListFree( env->key, _pico_free ); + lwListFree( env->cfilter, (void *) lwFreePlugin ); + _pico_free( env ); + } +} + + +static int compare_keys( lwKey *k1, lwKey *k2 ) +{ + return k1->time > k2->time ? 1 : k1->time < k2->time ? -1 : 0; +} + + +/* +====================================================================== +lwGetEnvelope() + +Read an ENVL chunk from an LWO2 file. +====================================================================== */ + +lwEnvelope *lwGetEnvelope( picoMemStream_t *fp, int cksize ) +{ + lwEnvelope *env; + lwKey *key; + lwPlugin *plug; + unsigned int id; + unsigned short sz; + float f[ 4 ]; + int i, nparams, pos, rlen; + + + /* allocate the Envelope structure */ + + env = _pico_calloc( 1, sizeof( lwEnvelope )); + if ( !env ) goto Fail; + + /* remember where we started */ + + set_flen( 0 ); + pos = _pico_memstream_tell( fp ); + + /* index */ + + env->index = getVX( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TYPE: + env->type = getU2( fp ); + break; + + case ID_NAME: + env->name = getS0( fp ); + break; + + case ID_PRE: + env->behavior[ 0 ] = getU2( fp ); + break; + + case ID_POST: + env->behavior[ 1 ] = getU2( fp ); + break; + + case ID_KEY: + key = _pico_calloc( 1, sizeof( lwKey )); + if ( !key ) goto Fail; + key->time = getF4( fp ); + key->value = getF4( fp ); + lwListInsert( (void **) &env->key, key, (void *) compare_keys ); + env->nkeys++; + break; + + case ID_SPAN: + if ( !key ) goto Fail; + key->shape = getU4( fp ); + + nparams = ( sz - 4 ) / 4; + if ( nparams > 4 ) nparams = 4; + for ( i = 0; i < nparams; i++ ) + f[ i ] = getF4( fp ); + + switch ( key->shape ) { + case ID_TCB: + key->tension = f[ 0 ]; + key->continuity = f[ 1 ]; + key->bias = f[ 2 ]; + break; + + case ID_BEZI: + case ID_HERM: + case ID_BEZ2: + for ( i = 0; i < nparams; i++ ) + key->param[ i ] = f[ i ]; + break; + } + break; + + case ID_CHAN: + plug = _pico_calloc( 1, sizeof( lwPlugin )); + if ( !plug ) goto Fail; + + plug->name = getS0( fp ); + plug->flags = getU2( fp ); + plug->data = getbytes( fp, sz - get_flen() ); + + lwListAdd( (void *) &env->cfilter, plug ); + env->ncfilters++; + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the ENVL chunk? */ + + rlen = _pico_memstream_tell( fp ) - pos; + if ( cksize < rlen ) goto Fail; + if ( cksize == rlen ) break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return env; + +Fail: + lwFreeEnvelope( env ); + return NULL; +} + + +/* +====================================================================== +lwFindEnvelope() + +Returns an lwEnvelope pointer, given an envelope index. +====================================================================== */ + +lwEnvelope *lwFindEnvelope( lwEnvelope *list, int index ) +{ + lwEnvelope *env; + + env = list; + while ( env ) { + if ( env->index == index ) break; + env = env->next; + } + return env; +} + + +/* +====================================================================== +range() + +Given the value v of a periodic function, returns the equivalent value +v2 in the principal interval [lo, hi]. If i isn't NULL, it receives +the number of wavelengths between v and v2. + + v2 = v - i * (hi - lo) + +For example, range( 3 pi, 0, 2 pi, i ) returns pi, with i = 1. +====================================================================== */ + +static float range( float v, float lo, float hi, int *i ) +{ + float v2, r = hi - lo; + + if ( r == 0.0 ) { + if ( i ) *i = 0; + return lo; + } + + v2 = lo + v - r * ( float ) floor(( double ) v / r ); + if ( i ) *i = -( int )(( v2 - v ) / r + ( v2 > v ? 0.5 : -0.5 )); + + return v2; +} + + +/* +====================================================================== +hermite() + +Calculate the Hermite coefficients. +====================================================================== */ + +static void hermite( float t, float *h1, float *h2, float *h3, float *h4 ) +{ + float t2, t3; + + t2 = t * t; + t3 = t * t2; + + *h2 = 3.0f * t2 - t3 - t3; + *h1 = 1.0f - *h2; + *h4 = t3 - t2; + *h3 = *h4 - t2 + t; +} + + +/* +====================================================================== +bezier() + +Interpolate the value of a 1D Bezier curve. +====================================================================== */ + +static float bezier( float x0, float x1, float x2, float x3, float t ) +{ + float a, b, c, t2, t3; + + t2 = t * t; + t3 = t2 * t; + + c = 3.0f * ( x1 - x0 ); + b = 3.0f * ( x2 - x1 ) - c; + a = x3 - x0 - c - b; + + return a * t3 + b * t2 + c * t + x0; +} + + +/* +====================================================================== +bez2_time() + +Find the t for which bezier() returns the input time. The handle +endpoints of a BEZ2 curve represent the control points, and these have +(time, value) coordinates, so time is used as both a coordinate and a +parameter for this curve type. +====================================================================== */ + +static float bez2_time( float x0, float x1, float x2, float x3, float time, + float *t0, float *t1 ) +{ + float v, t; + + t = *t0 + ( *t1 - *t0 ) * 0.5f; + v = bezier( x0, x1, x2, x3, t ); + if ( fabs( time - v ) > .0001f ) { + if ( v > time ) + *t1 = t; + else + *t0 = t; + return bez2_time( x0, x1, x2, x3, time, t0, t1 ); + } + else + return t; +} + + +/* +====================================================================== +bez2() + +Interpolate the value of a BEZ2 curve. +====================================================================== */ + +static float bez2( lwKey *key0, lwKey *key1, float time ) +{ + float x, y, t, t0 = 0.0f, t1 = 1.0f; + + if ( key0->shape == ID_BEZ2 ) + x = key0->time + key0->param[ 2 ]; + else + x = key0->time + ( key1->time - key0->time ) / 3.0f; + + t = bez2_time( key0->time, x, key1->time + key1->param[ 0 ], key1->time, + time, &t0, &t1 ); + + if ( key0->shape == ID_BEZ2 ) + y = key0->value + key0->param[ 3 ]; + else + y = key0->value + key0->param[ 1 ] / 3.0f; + + return bezier( key0->value, y, key1->param[ 1 ] + key1->value, key1->value, t ); +} + + +/* +====================================================================== +outgoing() + +Return the outgoing tangent to the curve at key0. The value returned +for the BEZ2 case is used when extrapolating a linear pre behavior and +when interpolating a non-BEZ2 span. +====================================================================== */ + +static float outgoing( lwKey *key0, lwKey *key1 ) +{ + float a, b, d, t, out; + + switch ( key0->shape ) + { + case ID_TCB: + a = ( 1.0f - key0->tension ) + * ( 1.0f + key0->continuity ) + * ( 1.0f + key0->bias ); + b = ( 1.0f - key0->tension ) + * ( 1.0f - key0->continuity ) + * ( 1.0f - key0->bias ); + d = key1->value - key0->value; + + if ( key0->prev ) { + t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time ); + out = t * ( a * ( key0->value - key0->prev->value ) + b * d ); + } + else + out = b * d; + break; + + case ID_LINE: + d = key1->value - key0->value; + if ( key0->prev ) { + t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time ); + out = t * ( key0->value - key0->prev->value + d ); + } + else + out = d; + break; + + case ID_BEZI: + case ID_HERM: + out = key0->param[ 1 ]; + if ( key0->prev ) + out *= ( key1->time - key0->time ) / ( key1->time - key0->prev->time ); + break; + + case ID_BEZ2: + out = key0->param[ 3 ] * ( key1->time - key0->time ); + if ( fabs( key0->param[ 2 ] ) > 1e-5f ) + out /= key0->param[ 2 ]; + else + out *= 1e5f; + break; + + case ID_STEP: + default: + out = 0.0f; + break; + } + + return out; +} + + +/* +====================================================================== +incoming() + +Return the incoming tangent to the curve at key1. The value returned +for the BEZ2 case is used when extrapolating a linear post behavior. +====================================================================== */ + +static float incoming( lwKey *key0, lwKey *key1 ) +{ + float a, b, d, t, in; + + switch ( key1->shape ) + { + case ID_LINE: + d = key1->value - key0->value; + if ( key1->next ) { + t = ( key1->time - key0->time ) / ( key1->next->time - key0->time ); + in = t * ( key1->next->value - key1->value + d ); + } + else + in = d; + break; + + case ID_TCB: + a = ( 1.0f - key1->tension ) + * ( 1.0f - key1->continuity ) + * ( 1.0f + key1->bias ); + b = ( 1.0f - key1->tension ) + * ( 1.0f + key1->continuity ) + * ( 1.0f - key1->bias ); + d = key1->value - key0->value; + + if ( key1->next ) { + t = ( key1->time - key0->time ) / ( key1->next->time - key0->time ); + in = t * ( b * ( key1->next->value - key1->value ) + a * d ); + } + else + in = a * d; + break; + + case ID_BEZI: + case ID_HERM: + in = key1->param[ 0 ]; + if ( key1->next ) + in *= ( key1->time - key0->time ) / ( key1->next->time - key0->time ); + break; + return in; + + case ID_BEZ2: + in = key1->param[ 1 ] * ( key1->time - key0->time ); + if ( fabs( key1->param[ 0 ] ) > 1e-5f ) + in /= key1->param[ 0 ]; + else + in *= 1e5f; + break; + + case ID_STEP: + default: + in = 0.0f; + break; + } + + return in; +} + + +/* +====================================================================== +evalEnvelope() + +Given a list of keys and a time, returns the interpolated value of the +envelope at that time. +====================================================================== */ + +float evalEnvelope( lwEnvelope *env, float time ) +{ + lwKey *key0, *key1, *skey, *ekey; + float t, h1, h2, h3, h4, in, out, offset = 0.0f; + int noff; + + + /* if there's no key, the value is 0 */ + + if ( env->nkeys == 0 ) return 0.0f; + + /* if there's only one key, the value is constant */ + + if ( env->nkeys == 1 ) + return env->key->value; + + /* find the first and last keys */ + + skey = ekey = env->key; + while ( ekey->next ) ekey = ekey->next; + + /* use pre-behavior if time is before first key time */ + + if ( time < skey->time ) { + switch ( env->behavior[ 0 ] ) + { + case BEH_RESET: + return 0.0f; + + case BEH_CONSTANT: + return skey->value; + + case BEH_REPEAT: + time = range( time, skey->time, ekey->time, NULL ); + break; + + case BEH_OSCILLATE: + time = range( time, skey->time, ekey->time, &noff ); + if ( noff % 2 ) + time = ekey->time - skey->time - time; + break; + + case BEH_OFFSET: + time = range( time, skey->time, ekey->time, &noff ); + offset = noff * ( ekey->value - skey->value ); + break; + + case BEH_LINEAR: + out = outgoing( skey, skey->next ) + / ( skey->next->time - skey->time ); + return out * ( time - skey->time ) + skey->value; + } + } + + /* use post-behavior if time is after last key time */ + + else if ( time > ekey->time ) { + switch ( env->behavior[ 1 ] ) + { + case BEH_RESET: + return 0.0f; + + case BEH_CONSTANT: + return ekey->value; + + case BEH_REPEAT: + time = range( time, skey->time, ekey->time, NULL ); + break; + + case BEH_OSCILLATE: + time = range( time, skey->time, ekey->time, &noff ); + if ( noff % 2 ) + time = ekey->time - skey->time - time; + break; + + case BEH_OFFSET: + time = range( time, skey->time, ekey->time, &noff ); + offset = noff * ( ekey->value - skey->value ); + break; + + case BEH_LINEAR: + in = incoming( ekey->prev, ekey ) + / ( ekey->time - ekey->prev->time ); + return in * ( time - ekey->time ) + ekey->value; + } + } + + /* get the endpoints of the interval being evaluated */ + + key0 = env->key; + while ( time > key0->next->time ) + key0 = key0->next; + key1 = key0->next; + + /* check for singularities first */ + + if ( time == key0->time ) + return key0->value + offset; + else if ( time == key1->time ) + return key1->value + offset; + + /* get interval length, time in [0, 1] */ + + t = ( time - key0->time ) / ( key1->time - key0->time ); + + /* interpolate */ + + switch ( key1->shape ) + { + case ID_TCB: + case ID_BEZI: + case ID_HERM: + out = outgoing( key0, key1 ); + in = incoming( key0, key1 ); + hermite( t, &h1, &h2, &h3, &h4 ); + return h1 * key0->value + h2 * key1->value + h3 * out + h4 * in + offset; + + case ID_BEZ2: + return bez2( key0, key1, time ) + offset; + + case ID_LINE: + return key0->value + t * ( key1->value - key0->value ) + offset; + + case ID_STEP: + return key0->value + offset; + + default: + return offset; + } +} diff --git a/libs/picomodel/lwo/list.c b/libs/picomodel/lwo/list.c new file mode 100644 index 00000000..d337296c --- /dev/null +++ b/libs/picomodel/lwo/list.c @@ -0,0 +1,101 @@ +/* +====================================================================== +list.c + +Generic linked list operations. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + + +/* +====================================================================== +lwListFree() + +Free the items in a list. +====================================================================== */ + +void lwListFree( void *list, void ( *freeNode )( void * )) +{ + lwNode *node, *next; + + node = ( lwNode * ) list; + while ( node ) { + next = node->next; + freeNode( node ); + node = next; + } +} + + +/* +====================================================================== +lwListAdd() + +Append a node to a list. +====================================================================== */ + +void lwListAdd( void **list, void *node ) +{ + lwNode *head, *tail; + + head = *(( lwNode ** ) list ); + if ( !head ) { + *list = node; + return; + } + while ( head ) { + tail = head; + head = head->next; + } + tail->next = ( lwNode * ) node; + (( lwNode * ) node )->prev = tail; +} + + +/* +====================================================================== +lwListInsert() + +Insert a node into a list in sorted order. +====================================================================== */ + +void lwListInsert( void **vlist, void *vitem, int ( *compare )( void *, void * )) +{ + lwNode **list, *item, *node, *prev; + + if ( !*vlist ) { + *vlist = vitem; + return; + } + + list = ( lwNode ** ) vlist; + item = ( lwNode * ) vitem; + node = *list; + prev = NULL; + + while ( node ) { + if ( 0 < compare( node, item )) break; + prev = node; + node = node->next; + } + + if ( !prev ) { + *list = item; + node->prev = item; + item->next = node; + } + else if ( !node ) { + prev->next = item; + item->prev = prev; + } + else { + item->next = node; + item->prev = prev; + prev->next = item; + node->prev = item; + } +} diff --git a/libs/picomodel/lwo/lwio.c b/libs/picomodel/lwo/lwio.c new file mode 100644 index 00000000..9853c55a --- /dev/null +++ b/libs/picomodel/lwo/lwio.c @@ -0,0 +1,442 @@ +/* +====================================================================== +lwio.c + +Functions for reading basic LWO2 data types. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + + +/* +====================================================================== +flen + +This accumulates a count of the number of bytes read. Callers can set +it at the beginning of a sequence of reads and then retrieve it to get +the number of bytes actually read. If one of the I/O functions fails, +flen is set to an error code, after which the I/O functions ignore +read requests until flen is reset. +====================================================================== */ + +#define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */ +#define FLEN_ERROR INT_MIN + +static int flen; + +void set_flen( int i ) { flen = i; } + +int get_flen( void ) { return flen; } + + +#ifndef __BIG_ENDIAN__ +/* +===================================================================== +revbytes() + +Reverses byte order in place. + +INPUTS + bp bytes to reverse + elsize size of the underlying data type + elcount number of elements to swap + +RESULTS + Reverses the byte order in each of elcount elements. + +This only needs to be defined on little-endian platforms, most +notably Windows. lwo2.h replaces this with a #define on big-endian +platforms. +===================================================================== */ + +void revbytes( void *bp, int elsize, int elcount ) +{ + register unsigned char *p, *q; + + p = ( unsigned char * ) bp; + + if ( elsize == 2 ) { + q = p + 1; + while ( elcount-- ) { + *p ^= *q; + *q ^= *p; + *p ^= *q; + p += 2; + q += 2; + } + return; + } + + while ( elcount-- ) { + q = p + elsize - 1; + while ( p < q ) { + *p ^= *q; + *q ^= *p; + *p ^= *q; + ++p; + --q; + } + p += elsize >> 1; + } +} +#endif + + +void *getbytes( picoMemStream_t *fp, int size ) +{ + void *data; + + if ( flen == FLEN_ERROR ) return NULL; + if ( size < 0 ) { + flen = FLEN_ERROR; + return NULL; + } + data = _pico_alloc( size ); + if ( !data ) { + flen = FLEN_ERROR; + return NULL; + } + if ( 1 != _pico_memstream_read( fp, data, size )) { + flen = FLEN_ERROR; + _pico_free( data ); + return NULL; + } + + flen += size; + return data; +} + + +void skipbytes( picoMemStream_t *fp, int n ) +{ + if ( flen == FLEN_ERROR ) return; + if ( _pico_memstream_seek( fp, n, PICO_SEEK_CUR )) + flen = FLEN_ERROR; + else + flen += n; +} + + +int getI1( picoMemStream_t *fp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + i = _pico_memstream_getc( fp ); + if ( i < 0 ) { + flen = FLEN_ERROR; + return 0; + } + if ( i > 127 ) i -= 256; + flen += 1; + return i; +} + + +short getI2( picoMemStream_t *fp ) +{ + short i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 1 != _pico_memstream_read( fp, &i, 2 )) { + flen = FLEN_ERROR; + return 0; + } + revbytes( &i, 2, 1 ); + flen += 2; + return i; +} + + +int getI4( picoMemStream_t *fp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 1 != _pico_memstream_read( fp, &i, 4 )) { + flen = FLEN_ERROR; + return 0; + } + revbytes( &i, 4, 1 ); + flen += 4; + return i; +} + + +unsigned char getU1( picoMemStream_t *fp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + i = _pico_memstream_getc( fp ); + if ( i < 0 ) { + flen = FLEN_ERROR; + return 0; + } + flen += 1; + return i; +} + + +unsigned short getU2( picoMemStream_t *fp ) +{ + unsigned short i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 1 != _pico_memstream_read( fp, &i, 2 )) { + flen = FLEN_ERROR; + return 0; + } + revbytes( &i, 2, 1 ); + flen += 2; + return i; +} + + +unsigned int getU4( picoMemStream_t *fp ) +{ + unsigned int i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 1 != _pico_memstream_read( fp, &i, 4 )) { + flen = FLEN_ERROR; + return 0; + } + revbytes( &i, 4, 1 ); + flen += 4; + return i; +} + + +int getVX( picoMemStream_t *fp ) +{ + int i, c; + + if ( flen == FLEN_ERROR ) return 0; + + c = _pico_memstream_getc( fp ); + if ( c != 0xFF ) { + i = c << 8; + c = _pico_memstream_getc( fp ); + i |= c; + flen += 2; + } + else { + c = _pico_memstream_getc( fp ); + i = c << 16; + c = _pico_memstream_getc( fp ); + i |= c << 8; + c = _pico_memstream_getc( fp ); + i |= c; + flen += 4; + } + + if ( _pico_memstream_error( fp )) { + flen = FLEN_ERROR; + return 0; + } + return i; +} + + +float getF4( picoMemStream_t *fp ) +{ + float f; + + if ( flen == FLEN_ERROR ) return 0.0f; + if ( 1 != _pico_memstream_read( fp, &f, 4 )) { + flen = FLEN_ERROR; + return 0.0f; + } + revbytes( &f, 4, 1 ); + flen += 4; + return f; +} + + +char *getS0( picoMemStream_t *fp ) +{ + char *s; + int i, c, len, pos; + + if ( flen == FLEN_ERROR ) return NULL; + + pos = _pico_memstream_tell( fp ); + for ( i = 1; ; i++ ) { + c = _pico_memstream_getc( fp ); + if ( c <= 0 ) break; + } + if ( c < 0 ) { + flen = FLEN_ERROR; + return NULL; + } + + if ( i == 1 ) { + if ( _pico_memstream_seek( fp, pos + 2, PICO_SEEK_SET )) + flen = FLEN_ERROR; + else + flen += 2; + return NULL; + } + + len = i + ( i & 1 ); + s = _pico_alloc( len ); + if ( !s ) { + flen = FLEN_ERROR; + return NULL; + } + + if ( _pico_memstream_seek( fp, pos, PICO_SEEK_SET )) { + flen = FLEN_ERROR; + return NULL; + } + if ( 1 != _pico_memstream_read( fp, s, len )) { + flen = FLEN_ERROR; + return NULL; + } + + flen += len; + return s; +} + + +int sgetI1( unsigned char **bp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + i = **bp; + if ( i > 127 ) i -= 256; + flen += 1; + *bp++; + return i; +} + + +short sgetI2( unsigned char **bp ) +{ + short i; + + if ( flen == FLEN_ERROR ) return 0; + memcpy( &i, *bp, 2 ); + revbytes( &i, 2, 1 ); + flen += 2; + *bp += 2; + return i; +} + + +int sgetI4( unsigned char **bp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + memcpy( &i, *bp, 4 ); + revbytes( &i, 4, 1 ); + flen += 4; + *bp += 4; + return i; +} + + +unsigned char sgetU1( unsigned char **bp ) +{ + unsigned char c; + + if ( flen == FLEN_ERROR ) return 0; + c = **bp; + flen += 1; + *bp++; + return c; +} + + +unsigned short sgetU2( unsigned char **bp ) +{ + unsigned char *buf = *bp; + unsigned short i; + + if ( flen == FLEN_ERROR ) return 0; + i = ( buf[ 0 ] << 8 ) | buf[ 1 ]; + flen += 2; + *bp += 2; + return i; +} + + +unsigned int sgetU4( unsigned char **bp ) +{ + unsigned int i; + + if ( flen == FLEN_ERROR ) return 0; + memcpy( &i, *bp, 4 ); + revbytes( &i, 4, 1 ); + flen += 4; + *bp += 4; + return i; +} + + +int sgetVX( unsigned char **bp ) +{ + unsigned char *buf = *bp; + int i; + + if ( flen == FLEN_ERROR ) return 0; + + if ( buf[ 0 ] != 0xFF ) { + i = buf[ 0 ] << 8 | buf[ 1 ]; + flen += 2; + *bp += 2; + } + else { + i = ( buf[ 1 ] << 16 ) | ( buf[ 2 ] << 8 ) | buf[ 3 ]; + flen += 4; + *bp += 4; + } + return i; +} + + +float sgetF4( unsigned char **bp ) +{ + float f; + + if ( flen == FLEN_ERROR ) return 0.0f; + memcpy( &f, *bp, 4 ); + revbytes( &f, 4, 1 ); + flen += 4; + *bp += 4; + return f; +} + + +char *sgetS0( unsigned char **bp ) +{ + char *s; + unsigned char *buf = *bp; + int len; + + if ( flen == FLEN_ERROR ) return NULL; + + len = strlen( buf ) + 1; + if ( len == 1 ) { + flen += 2; + *bp += 2; + return NULL; + } + len += len & 1; + s = _pico_alloc( len ); + if ( !s ) { + flen = FLEN_ERROR; + return NULL; + } + + memcpy( s, buf, len ); + flen += len; + *bp += len; + return s; +} diff --git a/libs/picomodel/lwo/lwo2.c b/libs/picomodel/lwo/lwo2.c new file mode 100644 index 00000000..ff83d5c2 --- /dev/null +++ b/libs/picomodel/lwo/lwo2.c @@ -0,0 +1,308 @@ +/* +====================================================================== +lwo2.c + +The entry point for loading LightWave object files. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + +/* disable warnings */ +#ifdef WIN32 +#pragma warning( disable:4018 ) /* signed/unsigned mismatch */ +#endif + + +/* +====================================================================== +lwFreeLayer() + +Free memory used by an lwLayer. +====================================================================== */ + +void lwFreeLayer( lwLayer *layer ) +{ + if ( layer ) { + if ( layer->name ) _pico_free( layer->name ); + lwFreePoints( &layer->point ); + lwFreePolygons( &layer->polygon ); + lwListFree( layer->vmap, (void *) lwFreeVMap ); + _pico_free( layer ); + } +} + + +/* +====================================================================== +lwFreeObject() + +Free memory used by an lwObject. +====================================================================== */ + +void lwFreeObject( lwObject *object ) +{ + if ( object ) { + lwListFree( object->layer, (void *) lwFreeLayer ); + lwListFree( object->env, (void *) lwFreeEnvelope ); + lwListFree( object->clip, (void *) lwFreeClip ); + lwListFree( object->surf, (void *) lwFreeSurface ); + lwFreeTags( &object->taglist ); + _pico_free( object ); + } +} + + +/* +====================================================================== +lwGetObject() + +Returns the contents of a LightWave object, given its filename, or +NULL if the file couldn't be loaded. On failure, failID and failpos +can be used to diagnose the cause. + +1. If the file isn't an LWO2 or an LWOB, failpos will contain 12 and + failID will be unchanged. + +2. If an error occurs while reading, failID will contain the most + recently read IFF chunk ID, and failpos will contain the value + returned by _pico_memstream_tell() at the time of the failure. + +3. If the file couldn't be opened, or an error occurs while reading + the first 12 bytes, both failID and failpos will be unchanged. + +If you don't need this information, failID and failpos can be NULL. +====================================================================== */ + +lwObject *lwGetObject( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ) +{ + lwObject *object; + lwLayer *layer; + lwNode *node; + unsigned int id, formsize, type, cksize; + int i, rlen; + + /* open the file */ + + if ( !fp ) return NULL; + + /* read the first 12 bytes */ + + set_flen( 0 ); + id = getU4( fp ); + formsize = getU4( fp ); + type = getU4( fp ); + if ( 12 != get_flen() ) { + return NULL; + } + + /* is this a LW object? */ + + if ( id != ID_FORM ) { + if ( failpos ) *failpos = 12; + return NULL; + } + + if ( type != ID_LWO2 ) { + if ( type == ID_LWOB ) + return lwGetObject5( filename, fp, failID, failpos ); + else { + if ( failpos ) *failpos = 12; + return NULL; + } + } + + /* allocate an object and a default layer */ + + object = _pico_calloc( 1, sizeof( lwObject )); + if ( !object ) goto Fail; + + layer = _pico_calloc( 1, sizeof( lwLayer )); + if ( !layer ) goto Fail; + object->layer = layer; + + /* get the first chunk header */ + + id = getU4( fp ); + cksize = getU4( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process chunks as they're encountered */ + + while ( 1 ) { + cksize += cksize & 1; + + switch ( id ) + { + case ID_LAYR: + if ( object->nlayers > 0 ) { + layer = _pico_calloc( 1, sizeof( lwLayer )); + if ( !layer ) goto Fail; + lwListAdd( (void **) &object->layer, layer ); + } + object->nlayers++; + + set_flen( 0 ); + layer->index = getU2( fp ); + layer->flags = getU2( fp ); + layer->pivot[ 0 ] = getF4( fp ); + layer->pivot[ 1 ] = getF4( fp ); + layer->pivot[ 2 ] = getF4( fp ); + layer->name = getS0( fp ); + + rlen = get_flen(); + if ( rlen < 0 || rlen > cksize ) goto Fail; + if ( rlen <= cksize - 2 ) + layer->parent = getU2( fp ); + rlen = get_flen(); + if ( rlen < cksize ) + _pico_memstream_seek( fp, cksize - rlen, PICO_SEEK_CUR ); + break; + + case ID_PNTS: + if ( !lwGetPoints( fp, cksize, &layer->point )) + goto Fail; + break; + + case ID_POLS: + if ( !lwGetPolygons( fp, cksize, &layer->polygon, + layer->point.offset )) + goto Fail; + break; + + case ID_VMAP: + case ID_VMAD: + node = ( lwNode * ) lwGetVMap( fp, cksize, layer->point.offset, + layer->polygon.offset, id == ID_VMAD ); + if ( !node ) goto Fail; + lwListAdd( (void **) &layer->vmap, node ); + layer->nvmaps++; + break; + + case ID_PTAG: + if ( !lwGetPolygonTags( fp, cksize, &object->taglist, + &layer->polygon )) + goto Fail; + break; + + case ID_BBOX: + set_flen( 0 ); + for ( i = 0; i < 6; i++ ) + layer->bbox[ i ] = getF4( fp ); + rlen = get_flen(); + if ( rlen < 0 || rlen > cksize ) goto Fail; + if ( rlen < cksize ) + _pico_memstream_seek( fp, cksize - rlen, PICO_SEEK_CUR ); + break; + + case ID_TAGS: + if ( !lwGetTags( fp, cksize, &object->taglist )) + goto Fail; + break; + + case ID_ENVL: + node = ( lwNode * ) lwGetEnvelope( fp, cksize ); + if ( !node ) goto Fail; + lwListAdd( (void **) &object->env, node ); + object->nenvs++; + break; + + case ID_CLIP: + node = ( lwNode * ) lwGetClip( fp, cksize ); + if ( !node ) goto Fail; + lwListAdd( (void **) &object->clip, node ); + object->nclips++; + break; + + case ID_SURF: + node = ( lwNode * ) lwGetSurface( fp, cksize ); + if ( !node ) goto Fail; + lwListAdd( (void **) &object->surf, node ); + object->nsurfs++; + break; + + case ID_DESC: + case ID_TEXT: + case ID_ICON: + default: + _pico_memstream_seek( fp, cksize, PICO_SEEK_CUR ); + break; + } + + /* end of the file? */ + + if ( formsize <= _pico_memstream_tell( fp ) - 8 ) break; + + /* get the next chunk header */ + + set_flen( 0 ); + id = getU4( fp ); + cksize = getU4( fp ); + if ( 8 != get_flen() ) goto Fail; + } + + if ( object->nlayers == 0 ) + object->nlayers = 1; + + layer = object->layer; + while ( layer ) { + lwGetBoundingBox( &layer->point, layer->bbox ); + lwGetPolyNormals( &layer->point, &layer->polygon ); + if ( !lwGetPointPolygons( &layer->point, &layer->polygon )) goto Fail; + if ( !lwResolvePolySurfaces( &layer->polygon, &object->taglist, + &object->surf, &object->nsurfs )) goto Fail; + lwGetVertNormals( &layer->point, &layer->polygon ); + if ( !lwGetPointVMaps( &layer->point, layer->vmap )) goto Fail; + if ( !lwGetPolyVMaps( &layer->polygon, layer->vmap )) goto Fail; + layer = layer->next; + } + + return object; + +Fail: + if ( failID ) *failID = id; + if ( fp ) { + if ( failpos ) *failpos = _pico_memstream_tell( fp ); + } + lwFreeObject( object ); + return NULL; +} + +int lwValidateObject( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ) +{ + unsigned int id, formsize, type; + + /* open the file */ + + if ( !fp ) return PICO_PMV_ERROR_MEMORY; + + /* read the first 12 bytes */ + + set_flen( 0 ); + id = getU4( fp ); + formsize = getU4( fp ); + type = getU4( fp ); + if ( 12 != get_flen() ) { + return PICO_PMV_ERROR_SIZE; + } + + /* is this a LW object? */ + + if ( id != ID_FORM ) { + if ( failpos ) *failpos = 12; + return PICO_PMV_ERROR_SIZE; + } + + if ( type != ID_LWO2 ) { + if ( type == ID_LWOB ) + return lwValidateObject5( filename, fp, failID, failpos ); + else { + if ( failpos ) *failpos = 12; + return PICO_PMV_ERROR_IDENT; + } + } + + return PICO_PMV_OK; +} diff --git a/libs/picomodel/lwo/lwo2.h b/libs/picomodel/lwo/lwo2.h new file mode 100644 index 00000000..29724cce --- /dev/null +++ b/libs/picomodel/lwo/lwo2.h @@ -0,0 +1,651 @@ +/* +====================================================================== +lwo2.h + +Definitions and typedefs for LWO2 files. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#ifndef LWO2_H +#define LWO2_H + +/* chunk and subchunk IDs */ + +#define LWID_(a,b,c,d) (((a)<<24)|((b)<<16)|((c)<<8)|(d)) + +#define ID_FORM LWID_('F','O','R','M') +#define ID_LWO2 LWID_('L','W','O','2') +#define ID_LWOB LWID_('L','W','O','B') + +/* top-level chunks */ +#define ID_LAYR LWID_('L','A','Y','R') +#define ID_TAGS LWID_('T','A','G','S') +#define ID_PNTS LWID_('P','N','T','S') +#define ID_BBOX LWID_('B','B','O','X') +#define ID_VMAP LWID_('V','M','A','P') +#define ID_VMAD LWID_('V','M','A','D') +#define ID_POLS LWID_('P','O','L','S') +#define ID_PTAG LWID_('P','T','A','G') +#define ID_ENVL LWID_('E','N','V','L') +#define ID_CLIP LWID_('C','L','I','P') +#define ID_SURF LWID_('S','U','R','F') +#define ID_DESC LWID_('D','E','S','C') +#define ID_TEXT LWID_('T','E','X','T') +#define ID_ICON LWID_('I','C','O','N') + +/* polygon types */ +#define ID_FACE LWID_('F','A','C','E') +#define ID_CURV LWID_('C','U','R','V') +#define ID_PTCH LWID_('P','T','C','H') +#define ID_MBAL LWID_('M','B','A','L') +#define ID_BONE LWID_('B','O','N','E') + +/* polygon tags */ +#define ID_SURF LWID_('S','U','R','F') +#define ID_PART LWID_('P','A','R','T') +#define ID_SMGP LWID_('S','M','G','P') + +/* envelopes */ +#define ID_PRE LWID_('P','R','E',' ') +#define ID_POST LWID_('P','O','S','T') +#define ID_KEY LWID_('K','E','Y',' ') +#define ID_SPAN LWID_('S','P','A','N') +#define ID_TCB LWID_('T','C','B',' ') +#define ID_HERM LWID_('H','E','R','M') +#define ID_BEZI LWID_('B','E','Z','I') +#define ID_BEZ2 LWID_('B','E','Z','2') +#define ID_LINE LWID_('L','I','N','E') +#define ID_STEP LWID_('S','T','E','P') + +/* clips */ +#define ID_STIL LWID_('S','T','I','L') +#define ID_ISEQ LWID_('I','S','E','Q') +#define ID_ANIM LWID_('A','N','I','M') +#define ID_XREF LWID_('X','R','E','F') +#define ID_STCC LWID_('S','T','C','C') +#define ID_TIME LWID_('T','I','M','E') +#define ID_CONT LWID_('C','O','N','T') +#define ID_BRIT LWID_('B','R','I','T') +#define ID_SATR LWID_('S','A','T','R') +#define ID_HUE LWID_('H','U','E',' ') +#define ID_GAMM LWID_('G','A','M','M') +#define ID_NEGA LWID_('N','E','G','A') +#define ID_IFLT LWID_('I','F','L','T') +#define ID_PFLT LWID_('P','F','L','T') + +/* surfaces */ +#define ID_COLR LWID_('C','O','L','R') +#define ID_LUMI LWID_('L','U','M','I') +#define ID_DIFF LWID_('D','I','F','F') +#define ID_SPEC LWID_('S','P','E','C') +#define ID_GLOS LWID_('G','L','O','S') +#define ID_REFL LWID_('R','E','F','L') +#define ID_RFOP LWID_('R','F','O','P') +#define ID_RIMG LWID_('R','I','M','G') +#define ID_RSAN LWID_('R','S','A','N') +#define ID_TRAN LWID_('T','R','A','N') +#define ID_TROP LWID_('T','R','O','P') +#define ID_TIMG LWID_('T','I','M','G') +#define ID_RIND LWID_('R','I','N','D') +#define ID_TRNL LWID_('T','R','N','L') +#define ID_BUMP LWID_('B','U','M','P') +#define ID_SMAN LWID_('S','M','A','N') +#define ID_SIDE LWID_('S','I','D','E') +#define ID_CLRH LWID_('C','L','R','H') +#define ID_CLRF LWID_('C','L','R','F') +#define ID_ADTR LWID_('A','D','T','R') +#define ID_SHRP LWID_('S','H','R','P') +#define ID_LINE LWID_('L','I','N','E') +#define ID_LSIZ LWID_('L','S','I','Z') +#define ID_ALPH LWID_('A','L','P','H') +#define ID_AVAL LWID_('A','V','A','L') +#define ID_GVAL LWID_('G','V','A','L') +#define ID_BLOK LWID_('B','L','O','K') + +/* texture layer */ +#define ID_TYPE LWID_('T','Y','P','E') +#define ID_CHAN LWID_('C','H','A','N') +#define ID_NAME LWID_('N','A','M','E') +#define ID_ENAB LWID_('E','N','A','B') +#define ID_OPAC LWID_('O','P','A','C') +#define ID_FLAG LWID_('F','L','A','G') +#define ID_PROJ LWID_('P','R','O','J') +#define ID_STCK LWID_('S','T','C','K') +#define ID_TAMP LWID_('T','A','M','P') + +/* texture coordinates */ +#define ID_TMAP LWID_('T','M','A','P') +#define ID_AXIS LWID_('A','X','I','S') +#define ID_CNTR LWID_('C','N','T','R') +#define ID_SIZE LWID_('S','I','Z','E') +#define ID_ROTA LWID_('R','O','T','A') +#define ID_OREF LWID_('O','R','E','F') +#define ID_FALL LWID_('F','A','L','L') +#define ID_CSYS LWID_('C','S','Y','S') + +/* image map */ +#define ID_IMAP LWID_('I','M','A','P') +#define ID_IMAG LWID_('I','M','A','G') +#define ID_WRAP LWID_('W','R','A','P') +#define ID_WRPW LWID_('W','R','P','W') +#define ID_WRPH LWID_('W','R','P','H') +#define ID_VMAP LWID_('V','M','A','P') +#define ID_AAST LWID_('A','A','S','T') +#define ID_PIXB LWID_('P','I','X','B') + +/* procedural */ +#define ID_PROC LWID_('P','R','O','C') +#define ID_COLR LWID_('C','O','L','R') +#define ID_VALU LWID_('V','A','L','U') +#define ID_FUNC LWID_('F','U','N','C') +#define ID_FTPS LWID_('F','T','P','S') +#define ID_ITPS LWID_('I','T','P','S') +#define ID_ETPS LWID_('E','T','P','S') + +/* gradient */ +#define ID_GRAD LWID_('G','R','A','D') +#define ID_GRST LWID_('G','R','S','T') +#define ID_GREN LWID_('G','R','E','N') +#define ID_PNAM LWID_('P','N','A','M') +#define ID_INAM LWID_('I','N','A','M') +#define ID_GRPT LWID_('G','R','P','T') +#define ID_FKEY LWID_('F','K','E','Y') +#define ID_IKEY LWID_('I','K','E','Y') + +/* shader */ +#define ID_SHDR LWID_('S','H','D','R') +#define ID_DATA LWID_('D','A','T','A') + + +/* generic linked list */ + +typedef struct st_lwNode { + struct st_lwNode *next, *prev; + void *data; +} lwNode; + + +/* plug-in reference */ + +typedef struct st_lwPlugin { + struct st_lwPlugin *next, *prev; + char *ord; + char *name; + int flags; + void *data; +} lwPlugin; + + +/* envelopes */ + +typedef struct st_lwKey { + struct st_lwKey *next, *prev; + float value; + float time; + unsigned int shape; /* ID_TCB, ID_BEZ2, etc. */ + float tension; + float continuity; + float bias; + float param[ 4 ]; +} lwKey; + +typedef struct st_lwEnvelope { + struct st_lwEnvelope *next, *prev; + int index; + int type; + char *name; + lwKey *key; /* linked list of keys */ + int nkeys; + int behavior[ 2 ]; /* pre and post (extrapolation) */ + lwPlugin *cfilter; /* linked list of channel filters */ + int ncfilters; +} lwEnvelope; + +#define BEH_RESET 0 +#define BEH_CONSTANT 1 +#define BEH_REPEAT 2 +#define BEH_OSCILLATE 3 +#define BEH_OFFSET 4 +#define BEH_LINEAR 5 + + +/* values that can be enveloped */ + +typedef struct st_lwEParam { + float val; + int eindex; +} lwEParam; + +typedef struct st_lwVParam { + float val[ 3 ]; + int eindex; +} lwVParam; + + +/* clips */ + +typedef struct st_lwClipStill { + char *name; +} lwClipStill; + +typedef struct st_lwClipSeq { + char *prefix; /* filename before sequence digits */ + char *suffix; /* after digits, e.g. extensions */ + int digits; + int flags; + int offset; + int start; + int end; +} lwClipSeq; + +typedef struct st_lwClipAnim { + char *name; + char *server; /* anim loader plug-in */ + void *data; +} lwClipAnim; + +typedef struct st_lwClipXRef { + char *string; + int index; + struct st_lwClip *clip; +} lwClipXRef; + +typedef struct st_lwClipCycle { + char *name; + int lo; + int hi; +} lwClipCycle; + +typedef struct st_lwClip { + struct st_lwClip *next, *prev; + int index; + unsigned int type; /* ID_STIL, ID_ISEQ, etc. */ + union { + lwClipStill still; + lwClipSeq seq; + lwClipAnim anim; + lwClipXRef xref; + lwClipCycle cycle; + } source; + float start_time; + float duration; + float frame_rate; + lwEParam contrast; + lwEParam brightness; + lwEParam saturation; + lwEParam hue; + lwEParam gamma; + int negative; + lwPlugin *ifilter; /* linked list of image filters */ + int nifilters; + lwPlugin *pfilter; /* linked list of pixel filters */ + int npfilters; +} lwClip; + + +/* textures */ + +typedef struct st_lwTMap { + lwVParam size; + lwVParam center; + lwVParam rotate; + lwVParam falloff; + int fall_type; + char *ref_object; + int coord_sys; +} lwTMap; + +typedef struct st_lwImageMap { + int cindex; + int projection; + char *vmap_name; + int axis; + int wrapw_type; + int wraph_type; + lwEParam wrapw; + lwEParam wraph; + float aa_strength; + int aas_flags; + int pblend; + lwEParam stck; + lwEParam amplitude; +} lwImageMap; + +#define PROJ_PLANAR 0 +#define PROJ_CYLINDRICAL 1 +#define PROJ_SPHERICAL 2 +#define PROJ_CUBIC 3 +#define PROJ_FRONT 4 + +#define WRAP_NONE 0 +#define WRAP_EDGE 1 +#define WRAP_REPEAT 2 +#define WRAP_MIRROR 3 + +typedef struct st_lwProcedural { + int axis; + float value[ 3 ]; + char *name; + void *data; +} lwProcedural; + +typedef struct st_lwGradKey { + struct st_lwGradKey *next, *prev; + float value; + float rgba[ 4 ]; +} lwGradKey; + +typedef struct st_lwGradient { + char *paramname; + char *itemname; + float start; + float end; + int repeat; + lwGradKey *key; /* array of gradient keys */ + short *ikey; /* array of interpolation codes */ +} lwGradient; + +typedef struct st_lwTexture { + struct st_lwTexture *next, *prev; + char *ord; + unsigned int type; + unsigned int chan; + lwEParam opacity; + short opac_type; + short enabled; + short negative; + short axis; + union { + lwImageMap imap; + lwProcedural proc; + lwGradient grad; + } param; + lwTMap tmap; +} lwTexture; + + +/* values that can be textured */ + +typedef struct st_lwTParam { + float val; + int eindex; + lwTexture *tex; /* linked list of texture layers */ +} lwTParam; + +typedef struct st_lwCParam { + float rgb[ 3 ]; + int eindex; + lwTexture *tex; /* linked list of texture layers */ +} lwCParam; + + +/* surfaces */ + +typedef struct st_lwGlow { + short enabled; + short type; + lwEParam intensity; + lwEParam size; +} Glow; + +typedef struct st_lwRMap { + lwTParam val; + int options; + int cindex; + float seam_angle; +} lwRMap; + +typedef struct st_lwLine { + short enabled; + unsigned short flags; + lwEParam size; +} lwLine; + +typedef struct st_lwSurface { + struct st_lwSurface *next, *prev; + char *name; + char *srcname; + lwCParam color; + lwTParam luminosity; + lwTParam diffuse; + lwTParam specularity; + lwTParam glossiness; + lwRMap reflection; + lwRMap transparency; + lwTParam eta; + lwTParam translucency; + lwTParam bump; + float smooth; + int sideflags; + float alpha; + int alpha_mode; + lwEParam color_hilite; + lwEParam color_filter; + lwEParam add_trans; + lwEParam dif_sharp; + lwEParam glow; + lwLine line; + lwPlugin *shader; /* linked list of shaders */ + int nshaders; +} lwSurface; + + +/* vertex maps */ + +typedef struct st_lwVMap { + struct st_lwVMap *next, *prev; + char *name; + unsigned int type; + int dim; + int nverts; + int perpoly; + int *vindex; /* array of point indexes */ + int *pindex; /* array of polygon indexes */ + float **val; +} lwVMap; + +typedef struct st_lwVMapPt { + lwVMap *vmap; + int index; /* vindex or pindex element */ +} lwVMapPt; + + +/* points and polygons */ + +typedef struct st_lwPoint { + float pos[ 3 ]; + int npols; /* number of polygons sharing the point */ + int *pol; /* array of polygon indexes */ + int nvmaps; + lwVMapPt *vm; /* array of vmap references */ +} lwPoint; + +typedef struct st_lwPolVert { + int index; /* index into the point array */ + float norm[ 3 ]; + int nvmaps; + lwVMapPt *vm; /* array of vmap references */ +} lwPolVert; + +typedef struct st_lwPolygon { + lwSurface *surf; + int part; /* part index */ + int smoothgrp; /* smoothing group */ + int flags; + unsigned int type; + float norm[ 3 ]; + int nverts; + lwPolVert *v; /* array of vertex records */ +} lwPolygon; + +typedef struct st_lwPointList { + int count; + int offset; /* only used during reading */ + lwPoint *pt; /* array of points */ +} lwPointList; + +typedef struct st_lwPolygonList { + int count; + int offset; /* only used during reading */ + int vcount; /* total number of vertices */ + int voffset; /* only used during reading */ + lwPolygon *pol; /* array of polygons */ +} lwPolygonList; + + +/* geometry layers */ + +typedef struct st_lwLayer { + struct st_lwLayer *next, *prev; + char *name; + int index; + int parent; + int flags; + float pivot[ 3 ]; + float bbox[ 6 ]; + lwPointList point; + lwPolygonList polygon; + int nvmaps; + lwVMap *vmap; /* linked list of vmaps */ +} lwLayer; + + +/* tag strings */ + +typedef struct st_lwTagList { + int count; + int offset; /* only used during reading */ + char **tag; /* array of strings */ +} lwTagList; + + +/* an object */ + +typedef struct st_lwObject { + lwLayer *layer; /* linked list of layers */ + lwEnvelope *env; /* linked list of envelopes */ + lwClip *clip; /* linked list of clips */ + lwSurface *surf; /* linked list of surfaces */ + lwTagList taglist; + int nlayers; + int nenvs; + int nclips; + int nsurfs; +} lwObject; + + +/* lwo2.c */ + +void lwFreeLayer( lwLayer *layer ); +void lwFreeObject( lwObject *object ); +lwObject *lwGetObject( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ); +int lwValidateObject( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ); + +/* pntspols.c */ + +void lwFreePoints( lwPointList *point ); +void lwFreePolygons( lwPolygonList *plist ); +int lwGetPoints( picoMemStream_t *fp, int cksize, lwPointList *point ); +void lwGetBoundingBox( lwPointList *point, float bbox[] ); +int lwAllocPolygons( lwPolygonList *plist, int npols, int nverts ); +int lwGetPolygons( picoMemStream_t *fp, int cksize, lwPolygonList *plist, int ptoffset ); +void lwGetPolyNormals( lwPointList *point, lwPolygonList *polygon ); +int lwGetPointPolygons( lwPointList *point, lwPolygonList *polygon ); +int lwResolvePolySurfaces( lwPolygonList *polygon, lwTagList *tlist, + lwSurface **surf, int *nsurfs ); +void lwGetVertNormals( lwPointList *point, lwPolygonList *polygon ); +void lwFreeTags( lwTagList *tlist ); +int lwGetTags( picoMemStream_t *fp, int cksize, lwTagList *tlist ); +int lwGetPolygonTags( picoMemStream_t *fp, int cksize, lwTagList *tlist, + lwPolygonList *plist ); + +/* vmap.c */ + +void lwFreeVMap( lwVMap *vmap ); +lwVMap *lwGetVMap( picoMemStream_t *fp, int cksize, int ptoffset, int poloffset, + int perpoly ); +int lwGetPointVMaps( lwPointList *point, lwVMap *vmap ); +int lwGetPolyVMaps( lwPolygonList *polygon, lwVMap *vmap ); + +/* clip.c */ + +void lwFreeClip( lwClip *clip ); +lwClip *lwGetClip( picoMemStream_t *fp, int cksize ); +lwClip *lwFindClip( lwClip *list, int index ); + +/* envelope.c */ + +void lwFreeEnvelope( lwEnvelope *env ); +lwEnvelope *lwGetEnvelope( picoMemStream_t *fp, int cksize ); +lwEnvelope *lwFindEnvelope( lwEnvelope *list, int index ); +float lwEvalEnvelope( lwEnvelope *env, float time ); + +/* surface.c */ + +void lwFreePlugin( lwPlugin *p ); +void lwFreeTexture( lwTexture *t ); +void lwFreeSurface( lwSurface *surf ); +int lwGetTHeader( picoMemStream_t *fp, int hsz, lwTexture *tex ); +int lwGetTMap( picoMemStream_t *fp, int tmapsz, lwTMap *tmap ); +int lwGetImageMap( picoMemStream_t *fp, int rsz, lwTexture *tex ); +int lwGetProcedural( picoMemStream_t *fp, int rsz, lwTexture *tex ); +int lwGetGradient( picoMemStream_t *fp, int rsz, lwTexture *tex ); +lwTexture *lwGetTexture( picoMemStream_t *fp, int bloksz, unsigned int type ); +lwPlugin *lwGetShader( picoMemStream_t *fp, int bloksz ); +lwSurface *lwGetSurface( picoMemStream_t *fp, int cksize ); +lwSurface *lwDefaultSurface( void ); + +/* lwob.c */ + +lwSurface *lwGetSurface5( picoMemStream_t *fp, int cksize, lwObject *obj ); +int lwGetPolygons5( picoMemStream_t *fp, int cksize, lwPolygonList *plist, int ptoffset ); +lwObject *lwGetObject5( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ); +int lwValidateObject5( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ); + +/* list.c */ + +void lwListFree( void *list, void ( *freeNode )( void * )); +void lwListAdd( void **list, void *node ); +void lwListInsert( void **vlist, void *vitem, + int ( *compare )( void *, void * )); + +/* vecmath.c */ + +float dot( float a[], float b[] ); +void cross( float a[], float b[], float c[] ); +void normalize( float v[] ); +#define vecangle( a, b ) ( float ) acos( dot( a, b )) + +/* lwio.c */ + +void set_flen( int i ); +int get_flen( void ); +void *getbytes( picoMemStream_t *fp, int size ); +void skipbytes( picoMemStream_t *fp, int n ); +int getI1( picoMemStream_t *fp ); +short getI2( picoMemStream_t *fp ); +int getI4( picoMemStream_t *fp ); +unsigned char getU1( picoMemStream_t *fp ); +unsigned short getU2( picoMemStream_t *fp ); +unsigned int getU4( picoMemStream_t *fp ); +int getVX( picoMemStream_t *fp ); +float getF4( picoMemStream_t *fp ); +char *getS0( picoMemStream_t *fp ); +int sgetI1( unsigned char **bp ); +short sgetI2( unsigned char **bp ); +int sgetI4( unsigned char **bp ); +unsigned char sgetU1( unsigned char **bp ); +unsigned short sgetU2( unsigned char **bp ); +unsigned int sgetU4( unsigned char **bp ); +int sgetVX( unsigned char **bp ); +float sgetF4( unsigned char **bp ); +char *sgetS0( unsigned char **bp ); + +#ifndef __BIG_ENDIAN__ + void revbytes( void *bp, int elsize, int elcount ); +#else + #define revbytes( b, s, c ) +#endif + +#endif diff --git a/libs/picomodel/lwo/lwob.c b/libs/picomodel/lwo/lwob.c new file mode 100644 index 00000000..0b7c372d --- /dev/null +++ b/libs/picomodel/lwo/lwob.c @@ -0,0 +1,723 @@ +/* +====================================================================== +lwob.c + +Functions for an LWOB reader. LWOB is the LightWave object format +for versions of LW prior to 6.0. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + +/* disable warnings */ +#ifdef WIN32 +#pragma warning( disable:4018 ) /* signed/unsigned mismatch */ +#endif + + +/* IDs specific to LWOB */ + +#define ID_SRFS LWID_('S','R','F','S') +#define ID_FLAG LWID_('F','L','A','G') +#define ID_VLUM LWID_('V','L','U','M') +#define ID_VDIF LWID_('V','D','I','F') +#define ID_VSPC LWID_('V','S','P','C') +#define ID_RFLT LWID_('R','F','L','T') +#define ID_BTEX LWID_('B','T','E','X') +#define ID_CTEX LWID_('C','T','E','X') +#define ID_DTEX LWID_('D','T','E','X') +#define ID_LTEX LWID_('L','T','E','X') +#define ID_RTEX LWID_('R','T','E','X') +#define ID_STEX LWID_('S','T','E','X') +#define ID_TTEX LWID_('T','T','E','X') +#define ID_TFLG LWID_('T','F','L','G') +#define ID_TSIZ LWID_('T','S','I','Z') +#define ID_TCTR LWID_('T','C','T','R') +#define ID_TFAL LWID_('T','F','A','L') +#define ID_TVEL LWID_('T','V','E','L') +#define ID_TCLR LWID_('T','C','L','R') +#define ID_TVAL LWID_('T','V','A','L') +#define ID_TAMP LWID_('T','A','M','P') +#define ID_TIMG LWID_('T','I','M','G') +#define ID_TAAS LWID_('T','A','A','S') +#define ID_TREF LWID_('T','R','E','F') +#define ID_TOPC LWID_('T','O','P','C') +#define ID_SDAT LWID_('S','D','A','T') +#define ID_TFP0 LWID_('T','F','P','0') +#define ID_TFP1 LWID_('T','F','P','1') + + +/* +====================================================================== +add_clip() + +Add a clip to the clip list. Used to store the contents of an RIMG or +TIMG surface subchunk. +====================================================================== */ + +static int add_clip( char *s, lwClip **clist, int *nclips ) +{ + lwClip *clip; + char *p; + + clip = _pico_calloc( 1, sizeof( lwClip )); + if ( !clip ) return 0; + + clip->contrast.val = 1.0f; + clip->brightness.val = 1.0f; + clip->saturation.val = 1.0f; + clip->gamma.val = 1.0f; + + if ( p = strstr( s, "(sequence)" )) { + p[ -1 ] = 0; + clip->type = ID_ISEQ; + clip->source.seq.prefix = s; + clip->source.seq.digits = 3; + } + else { + clip->type = ID_STIL; + clip->source.still.name = s; + } + + *nclips++; + clip->index = *nclips; + + lwListAdd( (void *) clist, clip ); + + return clip->index; +} + + +/* +====================================================================== +add_tvel() + +Add a triple of envelopes to simulate the old texture velocity +parameters. +====================================================================== */ + +static int add_tvel( float pos[], float vel[], lwEnvelope **elist, int *nenvs ) +{ + lwEnvelope *env; + lwKey *key0, *key1; + int i; + + for ( i = 0; i < 3; i++ ) { + env = _pico_calloc( 1, sizeof( lwEnvelope )); + key0 = _pico_calloc( 1, sizeof( lwKey )); + key1 = _pico_calloc( 1, sizeof( lwKey )); + if ( !env || !key0 || !key1 ) return 0; + + key0->next = key1; + key0->value = pos[ i ]; + key0->time = 0.0f; + key1->prev = key0; + key1->value = pos[ i ] + vel[ i ] * 30.0f; + key1->time = 1.0f; + key0->shape = key1->shape = ID_LINE; + + env->index = *nenvs + i + 1; + env->type = 0x0301 + i; + env->name = _pico_alloc( 11 ); + if ( env->name ) { + strcpy( env->name, "Position.X" ); + env->name[ 9 ] += i; + } + env->key = key0; + env->nkeys = 2; + env->behavior[ 0 ] = BEH_LINEAR; + env->behavior[ 1 ] = BEH_LINEAR; + + lwListAdd( (void *) elist, env ); + } + + *nenvs += 3; + return env->index - 2; +} + + +/* +====================================================================== +get_texture() + +Create a new texture for BTEX, CTEX, etc. subchunks. +====================================================================== */ + +static lwTexture *get_texture( char *s ) +{ + lwTexture *tex; + + tex = _pico_calloc( 1, sizeof( lwTexture )); + if ( !tex ) return NULL; + + tex->tmap.size.val[ 0 ] = + tex->tmap.size.val[ 1 ] = + tex->tmap.size.val[ 2 ] = 1.0f; + tex->opacity.val = 1.0f; + tex->enabled = 1; + + if ( strstr( s, "Image Map" )) { + tex->type = ID_IMAP; + if ( strstr( s, "Planar" )) tex->param.imap.projection = 0; + else if ( strstr( s, "Cylindrical" )) tex->param.imap.projection = 1; + else if ( strstr( s, "Spherical" )) tex->param.imap.projection = 2; + else if ( strstr( s, "Cubic" )) tex->param.imap.projection = 3; + else if ( strstr( s, "Front" )) tex->param.imap.projection = 4; + tex->param.imap.aa_strength = 1.0f; + tex->param.imap.amplitude.val = 1.0f; + _pico_free( s ); + } + else { + tex->type = ID_PROC; + tex->param.proc.name = s; + } + + return tex; +} + + +/* +====================================================================== +lwGetSurface5() + +Read an lwSurface from an LWOB file. +====================================================================== */ + +lwSurface *lwGetSurface5( picoMemStream_t *fp, int cksize, lwObject *obj ) +{ + lwSurface *surf; + lwTexture *tex; + lwPlugin *shdr; + char *s; + float v[ 3 ]; + unsigned int id, flags; + unsigned short sz; + int pos, rlen, i; + + + /* allocate the Surface structure */ + + surf = _pico_calloc( 1, sizeof( lwSurface )); + if ( !surf ) goto Fail; + + /* non-zero defaults */ + + surf->color.rgb[ 0 ] = 0.78431f; + surf->color.rgb[ 1 ] = 0.78431f; + surf->color.rgb[ 2 ] = 0.78431f; + surf->diffuse.val = 1.0f; + surf->glossiness.val = 0.4f; + surf->bump.val = 1.0f; + surf->eta.val = 1.0f; + surf->sideflags = 1; + + /* remember where we started */ + + set_flen( 0 ); + pos = _pico_memstream_tell( fp ); + + /* name */ + + surf->name = getS0( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_COLR: + surf->color.rgb[ 0 ] = getU1( fp ) / 255.0f; + surf->color.rgb[ 1 ] = getU1( fp ) / 255.0f; + surf->color.rgb[ 2 ] = getU1( fp ) / 255.0f; + break; + + case ID_FLAG: + flags = getU2( fp ); + if ( flags & 4 ) surf->smooth = 1.56207f; + if ( flags & 8 ) surf->color_hilite.val = 1.0f; + if ( flags & 16 ) surf->color_filter.val = 1.0f; + if ( flags & 128 ) surf->dif_sharp.val = 0.5f; + if ( flags & 256 ) surf->sideflags = 3; + if ( flags & 512 ) surf->add_trans.val = 1.0f; + break; + + case ID_LUMI: + surf->luminosity.val = getI2( fp ) / 256.0f; + break; + + case ID_VLUM: + surf->luminosity.val = getF4( fp ); + break; + + case ID_DIFF: + surf->diffuse.val = getI2( fp ) / 256.0f; + break; + + case ID_VDIF: + surf->diffuse.val = getF4( fp ); + break; + + case ID_SPEC: + surf->specularity.val = getI2( fp ) / 256.0f; + break; + + case ID_VSPC: + surf->specularity.val = getF4( fp ); + break; + + case ID_GLOS: + surf->glossiness.val = ( float ) log( getU2( fp )) / 20.7944f; + break; + + case ID_SMAN: + surf->smooth = getF4( fp ); + break; + + case ID_REFL: + surf->reflection.val.val = getI2( fp ) / 256.0f; + break; + + case ID_RFLT: + surf->reflection.options = getU2( fp ); + break; + + case ID_RIMG: + s = getS0( fp ); + surf->reflection.cindex = add_clip( s, &obj->clip, &obj->nclips ); + surf->reflection.options = 3; + break; + + case ID_RSAN: + surf->reflection.seam_angle = getF4( fp ); + break; + + case ID_TRAN: + surf->transparency.val.val = getI2( fp ) / 256.0f; + break; + + case ID_RIND: + surf->eta.val = getF4( fp ); + break; + + case ID_BTEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->bump.tex, tex ); + break; + + case ID_CTEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->color.tex, tex ); + break; + + case ID_DTEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->diffuse.tex, tex ); + break; + + case ID_LTEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->luminosity.tex, tex ); + break; + + case ID_RTEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->reflection.val.tex, tex ); + break; + + case ID_STEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->specularity.tex, tex ); + break; + + case ID_TTEX: + s = getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void *) &surf->transparency.val.tex, tex ); + break; + + case ID_TFLG: + flags = getU2( fp ); + + if ( flags & 1 ) i = 0; + if ( flags & 2 ) i = 1; + if ( flags & 4 ) i = 2; + tex->axis = i; + if ( tex->type == ID_IMAP ) + tex->param.imap.axis = i; + else + tex->param.proc.axis = i; + + if ( flags & 8 ) tex->tmap.coord_sys = 1; + if ( flags & 16 ) tex->negative = 1; + if ( flags & 32 ) tex->param.imap.pblend = 1; + if ( flags & 64 ) { + tex->param.imap.aa_strength = 1.0f; + tex->param.imap.aas_flags = 1; + } + break; + + case ID_TSIZ: + for ( i = 0; i < 3; i++ ) + tex->tmap.size.val[ i ] = getF4( fp ); + break; + + case ID_TCTR: + for ( i = 0; i < 3; i++ ) + tex->tmap.center.val[ i ] = getF4( fp ); + break; + + case ID_TFAL: + for ( i = 0; i < 3; i++ ) + tex->tmap.falloff.val[ i ] = getF4( fp ); + break; + + case ID_TVEL: + for ( i = 0; i < 3; i++ ) + v[ i ] = getF4( fp ); + tex->tmap.center.eindex = add_tvel( tex->tmap.center.val, v, + &obj->env, &obj->nenvs ); + break; + + case ID_TCLR: + if ( tex->type == ID_PROC ) + for ( i = 0; i < 3; i++ ) + tex->param.proc.value[ i ] = getU1( fp ) / 255.0f; + break; + + case ID_TVAL: + tex->param.proc.value[ 0 ] = getI2( fp ) / 256.0f; + break; + + case ID_TAMP: + if ( tex->type == ID_IMAP ) + tex->param.imap.amplitude.val = getF4( fp ); + break; + + case ID_TIMG: + s = getS0( fp ); + tex->param.imap.cindex = add_clip( s, &obj->clip, &obj->nclips ); + break; + + case ID_TAAS: + tex->param.imap.aa_strength = getF4( fp ); + tex->param.imap.aas_flags = 1; + break; + + case ID_TREF: + tex->tmap.ref_object = getbytes( fp, sz ); + break; + + case ID_TOPC: + tex->opacity.val = getF4( fp ); + break; + + case ID_TFP0: + if ( tex->type == ID_IMAP ) + tex->param.imap.wrapw.val = getF4( fp ); + break; + + case ID_TFP1: + if ( tex->type == ID_IMAP ) + tex->param.imap.wraph.val = getF4( fp ); + break; + + case ID_SHDR: + shdr = _pico_calloc( 1, sizeof( lwPlugin )); + if ( !shdr ) goto Fail; + shdr->name = getbytes( fp, sz ); + lwListAdd( (void *) &surf->shader, shdr ); + surf->nshaders++; + break; + + case ID_SDAT: + shdr->data = getbytes( fp, sz ); + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the SURF chunk? */ + + if ( cksize <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return surf; + +Fail: + if ( surf ) lwFreeSurface( surf ); + return NULL; +} + + +/* +====================================================================== +lwGetPolygons5() + +Read polygon records from a POLS chunk in an LWOB file. The polygons +are added to the array in the lwPolygonList. +====================================================================== */ + +int lwGetPolygons5( picoMemStream_t *fp, int cksize, lwPolygonList *plist, int ptoffset ) +{ + lwPolygon *pp; + lwPolVert *pv; + unsigned char *buf, *bp; + int i, j, nv, nverts, npols; + + + if ( cksize == 0 ) return 1; + + /* read the whole chunk */ + + set_flen( 0 ); + buf = getbytes( fp, cksize ); + if ( !buf ) goto Fail; + + /* count the polygons and vertices */ + + nverts = 0; + npols = 0; + bp = buf; + + while ( bp < buf + cksize ) { + nv = sgetU2( &bp ); + nverts += nv; + npols++; + bp += 2 * nv; + i = sgetI2( &bp ); + if ( i < 0 ) bp += 2; /* detail polygons */ + } + + if ( !lwAllocPolygons( plist, npols, nverts )) + goto Fail; + + /* fill in the new polygons */ + + bp = buf; + pp = plist->pol + plist->offset; + pv = plist->pol[ 0 ].v + plist->voffset; + + for ( i = 0; i < npols; i++ ) { + nv = sgetU2( &bp ); + + pp->nverts = nv; + pp->type = ID_FACE; + if ( !pp->v ) pp->v = pv; + for ( j = 0; j < nv; j++ ) + pv[ j ].index = sgetU2( &bp ) + ptoffset; + j = sgetI2( &bp ); + if ( j < 0 ) { + j = -j; + bp += 2; + } + j -= 1; + pp->surf = ( lwSurface * ) j; + + pp++; + pv += nv; + } + + _pico_free( buf ); + return 1; + +Fail: + if ( buf ) _pico_free( buf ); + lwFreePolygons( plist ); + return 0; +} + + +/* +====================================================================== +getLWObject5() + +Returns the contents of an LWOB, given its filename, or NULL if the +file couldn't be loaded. On failure, failID and failpos can be used +to diagnose the cause. + +1. If the file isn't an LWOB, failpos will contain 12 and failID will + be unchanged. + +2. If an error occurs while reading an LWOB, failID will contain the + most recently read IFF chunk ID, and failpos will contain the + value returned by _pico_memstream_tell() at the time of the failure. + +3. If the file couldn't be opened, or an error occurs while reading + the first 12 bytes, both failID and failpos will be unchanged. + +If you don't need this information, failID and failpos can be NULL. +====================================================================== */ + +lwObject *lwGetObject5( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ) +{ + lwObject *object; + lwLayer *layer; + lwNode *node; + unsigned int id, formsize, type, cksize; + + + /* open the file */ + + if ( !fp ) return NULL; + + /* read the first 12 bytes */ + + set_flen( 0 ); + id = getU4( fp ); + formsize = getU4( fp ); + type = getU4( fp ); + if ( 12 != get_flen() ) { + return NULL; + } + + /* LWOB? */ + + if ( id != ID_FORM || type != ID_LWOB ) { + if ( failpos ) *failpos = 12; + return NULL; + } + + /* allocate an object and a default layer */ + + object = _pico_calloc( 1, sizeof( lwObject )); + if ( !object ) goto Fail; + + layer = _pico_calloc( 1, sizeof( lwLayer )); + if ( !layer ) goto Fail; + object->layer = layer; + object->nlayers = 1; + + /* get the first chunk header */ + + id = getU4( fp ); + cksize = getU4( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process chunks as they're encountered */ + + while ( 1 ) { + cksize += cksize & 1; + + switch ( id ) + { + case ID_PNTS: + if ( !lwGetPoints( fp, cksize, &layer->point )) + goto Fail; + break; + + case ID_POLS: + if ( !lwGetPolygons5( fp, cksize, &layer->polygon, + layer->point.offset )) + goto Fail; + break; + + case ID_SRFS: + if ( !lwGetTags( fp, cksize, &object->taglist )) + goto Fail; + break; + + case ID_SURF: + node = ( lwNode * ) lwGetSurface5( fp, cksize, object ); + if ( !node ) goto Fail; + lwListAdd( (void *) &object->surf, node ); + object->nsurfs++; + break; + + default: + _pico_memstream_seek( fp, cksize, PICO_SEEK_CUR ); + break; + } + + /* end of the file? */ + + if ( formsize <= _pico_memstream_tell( fp ) - 8 ) break; + + /* get the next chunk header */ + + set_flen( 0 ); + id = getU4( fp ); + cksize = getU4( fp ); + if ( 8 != get_flen() ) goto Fail; + } + + lwGetBoundingBox( &layer->point, layer->bbox ); + lwGetPolyNormals( &layer->point, &layer->polygon ); + if ( !lwGetPointPolygons( &layer->point, &layer->polygon )) goto Fail; + if ( !lwResolvePolySurfaces( &layer->polygon, &object->taglist, + &object->surf, &object->nsurfs )) goto Fail; + lwGetVertNormals( &layer->point, &layer->polygon ); + + return object; + +Fail: + if ( failID ) *failID = id; + if ( fp ) { + if ( failpos ) *failpos = _pico_memstream_tell( fp ); + } + lwFreeObject( object ); + return NULL; +} + +int lwValidateObject5( char *filename, picoMemStream_t *fp, unsigned int *failID, int *failpos ) +{ + unsigned int id, formsize, type; + + + /* open the file */ + + if ( !fp ) return PICO_PMV_ERROR_MEMORY; + + /* read the first 12 bytes */ + + set_flen( 0 ); + id = getU4( fp ); + formsize = getU4( fp ); + type = getU4( fp ); + if ( 12 != get_flen() ) { + return PICO_PMV_ERROR_SIZE; + } + + /* LWOB? */ + + if ( id != ID_FORM || type != ID_LWOB ) { + if ( failpos ) *failpos = 12; + return PICO_PMV_ERROR_IDENT; + } + + return PICO_PMV_OK; +} diff --git a/libs/picomodel/lwo/pntspols.c b/libs/picomodel/lwo/pntspols.c new file mode 100644 index 00000000..a7f54d25 --- /dev/null +++ b/libs/picomodel/lwo/pntspols.c @@ -0,0 +1,537 @@ +/* +====================================================================== +pntspols.c + +Point and polygon functions for an LWO2 reader. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + + +/* +====================================================================== +lwFreePoints() + +Free the memory used by an lwPointList. +====================================================================== */ + +void lwFreePoints( lwPointList *point ) +{ + int i; + + if ( point ) { + if ( point->pt ) { + for ( i = 0; i < point->count; i++ ) { + if ( point->pt[ i ].pol ) _pico_free( point->pt[ i ].pol ); + if ( point->pt[ i ].vm ) _pico_free( point->pt[ i ].vm ); + } + _pico_free( point->pt ); + } + memset( point, 0, sizeof( lwPointList )); + } +} + + +/* +====================================================================== +lwFreePolygons() + +Free the memory used by an lwPolygonList. +====================================================================== */ + +void lwFreePolygons( lwPolygonList *plist ) +{ + int i, j; + + if ( plist ) { + if ( plist->pol ) { + for ( i = 0; i < plist->count; i++ ) { + if ( plist->pol[ i ].v ) { + for ( j = 0; j < plist->pol[ i ].nverts; j++ ) + if ( plist->pol[ i ].v[ j ].vm ) + _pico_free( plist->pol[ i ].v[ j ].vm ); + } + } + if ( plist->pol[ 0 ].v ) + _pico_free( plist->pol[ 0 ].v ); + _pico_free( plist->pol ); + } + memset( plist, 0, sizeof( lwPolygonList )); + } +} + + +/* +====================================================================== +lwGetPoints() + +Read point records from a PNTS chunk in an LWO2 file. The points are +added to the array in the lwPointList. +====================================================================== */ + +int lwGetPoints( picoMemStream_t *fp, int cksize, lwPointList *point ) +{ + float *f; + int np, i, j; + + if ( cksize == 1 ) return 1; + + /* extend the point array to hold the new points */ + + np = cksize / 12; + point->offset = point->count; + point->count += np; + if ( !_pico_realloc( (void *) &point->pt, (point->count - np) * sizeof( lwPoint ), point->count * sizeof( lwPoint )) ) + return 0; + memset( &point->pt[ point->offset ], 0, np * sizeof( lwPoint )); + + /* read the whole chunk */ + + f = ( float * ) getbytes( fp, cksize ); + if ( !f ) return 0; + revbytes( f, 4, np * 3 ); + + /* assign position values */ + + for ( i = 0, j = 0; i < np; i++, j += 3 ) { + point->pt[ i ].pos[ 0 ] = f[ j ]; + point->pt[ i ].pos[ 1 ] = f[ j + 1 ]; + point->pt[ i ].pos[ 2 ] = f[ j + 2 ]; + } + + _pico_free( f ); + return 1; +} + + +/* +====================================================================== +lwGetBoundingBox() + +Calculate the bounding box for a point list, but only if the bounding +box hasn't already been initialized. +====================================================================== */ + +void lwGetBoundingBox( lwPointList *point, float bbox[] ) +{ + int i, j; + + if ( point->count == 0 ) return; + + for ( i = 0; i < 6; i++ ) + if ( bbox[ i ] != 0.0f ) return; + + bbox[ 0 ] = bbox[ 1 ] = bbox[ 2 ] = 1e20f; + bbox[ 3 ] = bbox[ 4 ] = bbox[ 5 ] = -1e20f; + for ( i = 0; i < point->count; i++ ) { + for ( j = 0; j < 3; j++ ) { + if ( bbox[ j ] > point->pt[ i ].pos[ j ] ) + bbox[ j ] = point->pt[ i ].pos[ j ]; + if ( bbox[ j + 3 ] < point->pt[ i ].pos[ j ] ) + bbox[ j + 3 ] = point->pt[ i ].pos[ j ]; + } + } +} + + +/* +====================================================================== +lwAllocPolygons() + +Allocate or extend the polygon arrays to hold new records. +====================================================================== */ + +int lwAllocPolygons( lwPolygonList *plist, int npols, int nverts ) +{ + int i; + + plist->offset = plist->count; + plist->count += npols; + if ( !_pico_realloc( (void *) &plist->pol, (plist->count - npols) * sizeof( lwPolygon ), plist->count * sizeof( lwPolygon )) ) + return 0; + memset( plist->pol + plist->offset, 0, npols * sizeof( lwPolygon )); + + plist->voffset = plist->vcount; + plist->vcount += nverts; + if ( !_pico_realloc( (void *) &plist->pol[ 0 ].v, (plist->vcount - nverts) * sizeof( lwPolVert ), plist->vcount * sizeof( lwPolVert )) ) + return 0; + memset( plist->pol[ 0 ].v + plist->voffset, 0, nverts * sizeof( lwPolVert )); + + /* fix up the old vertex pointers */ + + for ( i = 1; i < plist->offset; i++ ) + plist->pol[ i ].v = plist->pol[ i - 1 ].v + plist->pol[ i - 1 ].nverts; + + return 1; +} + + +/* +====================================================================== +lwGetPolygons() + +Read polygon records from a POLS chunk in an LWO2 file. The polygons +are added to the array in the lwPolygonList. +====================================================================== */ + +int lwGetPolygons( picoMemStream_t *fp, int cksize, lwPolygonList *plist, int ptoffset ) +{ + lwPolygon *pp; + lwPolVert *pv; + unsigned char *buf, *bp; + int i, j, flags, nv, nverts, npols; + unsigned int type; + + + if ( cksize == 0 ) return 1; + + /* read the whole chunk */ + + set_flen( 0 ); + type = getU4( fp ); + buf = getbytes( fp, cksize - 4 ); + if ( cksize != get_flen() ) goto Fail; + + /* count the polygons and vertices */ + + nverts = 0; + npols = 0; + bp = buf; + + while ( bp < buf + cksize - 4 ) { + nv = sgetU2( &bp ); + nv &= 0x03FF; + nverts += nv; + npols++; + for ( i = 0; i < nv; i++ ) + j = sgetVX( &bp ); + } + + if ( !lwAllocPolygons( plist, npols, nverts )) + goto Fail; + + /* fill in the new polygons */ + + bp = buf; + pp = plist->pol + plist->offset; + pv = plist->pol[ 0 ].v + plist->voffset; + + for ( i = 0; i < npols; i++ ) { + nv = sgetU2( &bp ); + flags = nv & 0xFC00; + nv &= 0x03FF; + + pp->nverts = nv; + pp->flags = flags; + pp->type = type; + if ( !pp->v ) pp->v = pv; + for ( j = 0; j < nv; j++ ) + pp->v[ j ].index = sgetVX( &bp ) + ptoffset; + + pp++; + pv += nv; + } + + _pico_free( buf ); + return 1; + +Fail: + if ( buf ) _pico_free( buf ); + lwFreePolygons( plist ); + return 0; +} + + +/* +====================================================================== +lwGetPolyNormals() + +Calculate the polygon normals. By convention, LW's polygon normals +are found as the cross product of the first and last edges. It's +undefined for one- and two-point polygons. +====================================================================== */ + +void lwGetPolyNormals( lwPointList *point, lwPolygonList *polygon ) +{ + int i, j; + float p1[ 3 ], p2[ 3 ], pn[ 3 ], v1[ 3 ], v2[ 3 ]; + + for ( i = 0; i < polygon->count; i++ ) { + if ( polygon->pol[ i ].nverts < 3 ) continue; + for ( j = 0; j < 3; j++ ) { + p1[ j ] = point->pt[ polygon->pol[ i ].v[ 0 ].index ].pos[ j ]; + p2[ j ] = point->pt[ polygon->pol[ i ].v[ 1 ].index ].pos[ j ]; + pn[ j ] = point->pt[ polygon->pol[ i ].v[ + polygon->pol[ i ].nverts - 1 ].index ].pos[ j ]; + } + + for ( j = 0; j < 3; j++ ) { + v1[ j ] = p2[ j ] - p1[ j ]; + v2[ j ] = pn[ j ] - p1[ j ]; + } + + cross( v1, v2, polygon->pol[ i ].norm ); + normalize( polygon->pol[ i ].norm ); + } +} + + +/* +====================================================================== +lwGetPointPolygons() + +For each point, fill in the indexes of the polygons that share the +point. Returns 0 if any of the memory allocations fail, otherwise +returns 1. +====================================================================== */ + +int lwGetPointPolygons( lwPointList *point, lwPolygonList *polygon ) +{ + int i, j, k; + + /* count the number of polygons per point */ + + for ( i = 0; i < polygon->count; i++ ) + for ( j = 0; j < polygon->pol[ i ].nverts; j++ ) + ++point->pt[ polygon->pol[ i ].v[ j ].index ].npols; + + /* alloc per-point polygon arrays */ + + for ( i = 0; i < point->count; i++ ) { + if ( point->pt[ i ].npols == 0 ) continue; + point->pt[ i ].pol = _pico_calloc( point->pt[ i ].npols, sizeof( int )); + if ( !point->pt[ i ].pol ) return 0; + point->pt[ i ].npols = 0; + } + + /* fill in polygon array for each point */ + + for ( i = 0; i < polygon->count; i++ ) { + for ( j = 0; j < polygon->pol[ i ].nverts; j++ ) { + k = polygon->pol[ i ].v[ j ].index; + point->pt[ k ].pol[ point->pt[ k ].npols ] = i; + ++point->pt[ k ].npols; + } + } + + return 1; +} + + +/* +====================================================================== +lwResolvePolySurfaces() + +Convert tag indexes into actual lwSurface pointers. If any polygons +point to tags for which no corresponding surface can be found, a +default surface is created. +====================================================================== */ + +int lwResolvePolySurfaces( lwPolygonList *polygon, lwTagList *tlist, + lwSurface **surf, int *nsurfs ) +{ + lwSurface **s, *st; + int i, index; + + if ( tlist->count == 0 ) return 1; + + s = _pico_calloc( tlist->count, sizeof( lwSurface * )); + if ( !s ) return 0; + + for ( i = 0; i < tlist->count; i++ ) { + st = *surf; + while ( st ) { + if ( !strcmp( st->name, tlist->tag[ i ] )) { + s[ i ] = st; + break; + } + st = st->next; + } + } + + for ( i = 0; i < polygon->count; i++ ) { + index = ( int ) polygon->pol[ i ].surf; + if ( index < 0 || index > tlist->count ) return 0; + if ( !s[ index ] ) { + s[ index ] = lwDefaultSurface(); + if ( !s[ index ] ) return 0; + s[ index ]->name = _pico_alloc( strlen( tlist->tag[ index ] ) + 1 ); + if ( !s[ index ]->name ) return 0; + strcpy( s[ index ]->name, tlist->tag[ index ] ); + lwListAdd( (void *) surf, s[ index ] ); + *nsurfs = *nsurfs + 1; + } + polygon->pol[ i ].surf = s[ index ]; + } + + _pico_free( s ); + return 1; +} + + +/* +====================================================================== +lwGetVertNormals() + +Calculate the vertex normals. For each polygon vertex, sum the +normals of the polygons that share the point. If the normals of the +current and adjacent polygons form an angle greater than the max +smoothing angle for the current polygon's surface, the normal of the +adjacent polygon is excluded from the sum. It's also excluded if the +polygons aren't in the same smoothing group. + +Assumes that lwGetPointPolygons(), lwGetPolyNormals() and +lwResolvePolySurfaces() have already been called. +====================================================================== */ + +void lwGetVertNormals( lwPointList *point, lwPolygonList *polygon ) +{ + int j, k, n, g, h, p; + float a; + + for ( j = 0; j < polygon->count; j++ ) { + for ( n = 0; n < polygon->pol[ j ].nverts; n++ ) { + for ( k = 0; k < 3; k++ ) + polygon->pol[ j ].v[ n ].norm[ k ] = polygon->pol[ j ].norm[ k ]; + + if ( polygon->pol[ j ].surf->smooth <= 0 ) continue; + + p = polygon->pol[ j ].v[ n ].index; + + for ( g = 0; g < point->pt[ p ].npols; g++ ) { + h = point->pt[ p ].pol[ g ]; + if ( h == j ) continue; + + if ( polygon->pol[ j ].smoothgrp != polygon->pol[ h ].smoothgrp ) + continue; + a = vecangle( polygon->pol[ j ].norm, polygon->pol[ h ].norm ); + if ( a > polygon->pol[ j ].surf->smooth ) continue; + + for ( k = 0; k < 3; k++ ) + polygon->pol[ j ].v[ n ].norm[ k ] += polygon->pol[ h ].norm[ k ]; + } + + normalize( polygon->pol[ j ].v[ n ].norm ); + } + } +} + + +/* +====================================================================== +lwFreeTags() + +Free memory used by an lwTagList. +====================================================================== */ + +void lwFreeTags( lwTagList *tlist ) +{ + int i; + + if ( tlist ) { + if ( tlist->tag ) { + for ( i = 0; i < tlist->count; i++ ) + if ( tlist->tag[ i ] ) _pico_free( tlist->tag[ i ] ); + _pico_free( tlist->tag ); + } + memset( tlist, 0, sizeof( lwTagList )); + } +} + + +/* +====================================================================== +lwGetTags() + +Read tag strings from a TAGS chunk in an LWO2 file. The tags are +added to the lwTagList array. +====================================================================== */ + +int lwGetTags( picoMemStream_t *fp, int cksize, lwTagList *tlist ) +{ + char *buf, *bp; + int i, len, ntags; + + if ( cksize == 0 ) return 1; + + /* read the whole chunk */ + + set_flen( 0 ); + buf = getbytes( fp, cksize ); + if ( !buf ) return 0; + + /* count the strings */ + + ntags = 0; + bp = buf; + while ( bp < buf + cksize ) { + len = strlen( bp ) + 1; + len += len & 1; + bp += len; + ++ntags; + } + + /* expand the string array to hold the new tags */ + + tlist->offset = tlist->count; + tlist->count += ntags; + if ( !_pico_realloc( (void *) &tlist->tag, (tlist->count - ntags) * sizeof( char * ), tlist->count * sizeof( char * )) ) + goto Fail; + memset( &tlist->tag[ tlist->offset ], 0, ntags * sizeof( char * )); + + /* copy the new tags to the tag array */ + + bp = buf; + for ( i = 0; i < ntags; i++ ) + tlist->tag[ i + tlist->offset ] = sgetS0( (unsigned char **) &bp ); + + _pico_free( buf ); + return 1; + +Fail: + if ( buf ) _pico_free( buf ); + return 0; +} + + +/* +====================================================================== +lwGetPolygonTags() + +Read polygon tags from a PTAG chunk in an LWO2 file. +====================================================================== */ + +int lwGetPolygonTags( picoMemStream_t *fp, int cksize, lwTagList *tlist, + lwPolygonList *plist ) +{ + unsigned int type; + int rlen = 0, i, j; + + set_flen( 0 ); + type = getU4( fp ); + rlen = get_flen(); + if ( rlen < 0 ) return 0; + + if ( type != ID_SURF && type != ID_PART && type != ID_SMGP ) { + _pico_memstream_seek( fp, cksize - 4, PICO_SEEK_CUR ); + return 1; + } + + while ( rlen < cksize ) { + i = getVX( fp ) + plist->offset; + j = getVX( fp ) + tlist->offset; + rlen = get_flen(); + if ( rlen < 0 || rlen > cksize ) return 0; + + switch ( type ) { + case ID_SURF: plist->pol[ i ].surf = ( lwSurface * ) j; break; + case ID_PART: plist->pol[ i ].part = j; break; + case ID_SMGP: plist->pol[ i ].smoothgrp = j; break; + } + } + + return 1; +} diff --git a/libs/picomodel/lwo/surface.c b/libs/picomodel/lwo/surface.c new file mode 100644 index 00000000..6b2bbe62 --- /dev/null +++ b/libs/picomodel/lwo/surface.c @@ -0,0 +1,1005 @@ +/* +====================================================================== +surface.c + +Surface functions for an LWO2 reader. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + + +/* +====================================================================== +lwFreePlugin() + +Free the memory used by an lwPlugin. +====================================================================== */ + +void lwFreePlugin( lwPlugin *p ) +{ + if ( p ) { + if ( p->ord ) _pico_free( p->ord ); + if ( p->name ) _pico_free( p->name ); + if ( p->data ) _pico_free( p->data ); + _pico_free( p ); + } +} + + +/* +====================================================================== +lwFreeTexture() + +Free the memory used by an lwTexture. +====================================================================== */ + +void lwFreeTexture( lwTexture *t ) +{ + if ( t ) { + if ( t->ord ) _pico_free( t->ord ); + switch ( t->type ) { + case ID_IMAP: + if ( t->param.imap.vmap_name ) _pico_free( t->param.imap.vmap_name ); + if ( t->tmap.ref_object ) _pico_free( t->tmap.ref_object ); + break; + case ID_PROC: + if ( t->param.proc.name ) _pico_free( t->param.proc.name ); + if ( t->param.proc.data ) _pico_free( t->param.proc.data ); + break; + case ID_GRAD: + if ( t->param.grad.key ) _pico_free( t->param.grad.key ); + if ( t->param.grad.ikey ) _pico_free( t->param.grad.ikey ); + break; + } + _pico_free( t ); + } +} + + +/* +====================================================================== +lwFreeSurface() + +Free the memory used by an lwSurface. +====================================================================== */ + +void lwFreeSurface( lwSurface *surf ) +{ + if ( surf ) { + if ( surf->name ) _pico_free( surf->name ); + if ( surf->srcname ) _pico_free( surf->srcname ); + + lwListFree( surf->shader, (void *) lwFreePlugin ); + + lwListFree( surf->color.tex, (void *) lwFreeTexture ); + lwListFree( surf->luminosity.tex, (void *) lwFreeTexture ); + lwListFree( surf->diffuse.tex, (void *) lwFreeTexture ); + lwListFree( surf->specularity.tex, (void *) lwFreeTexture ); + lwListFree( surf->glossiness.tex, (void *) lwFreeTexture ); + lwListFree( surf->reflection.val.tex, (void *) lwFreeTexture ); + lwListFree( surf->transparency.val.tex, (void *) lwFreeTexture ); + lwListFree( surf->eta.tex, (void *) lwFreeTexture ); + lwListFree( surf->translucency.tex, (void *) lwFreeTexture ); + lwListFree( surf->bump.tex, (void *) lwFreeTexture ); + + _pico_free( surf ); + } +} + + +/* +====================================================================== +lwGetTHeader() + +Read a texture map header from a SURF.BLOK in an LWO2 file. This is +the first subchunk in a BLOK, and its contents are common to all three +texture types. +====================================================================== */ + +int lwGetTHeader( picoMemStream_t *fp, int hsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int pos, rlen; + + + /* remember where we started */ + + set_flen( 0 ); + pos = _pico_memstream_tell( fp ); + + /* ordinal string */ + + tex->ord = getS0( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_CHAN: + tex->chan = getU4( fp ); + break; + + case ID_OPAC: + tex->opac_type = getU2( fp ); + tex->opacity.val = getF4( fp ); + tex->opacity.eindex = getVX( fp ); + break; + + case ID_ENAB: + tex->enabled = getU2( fp ); + break; + + case ID_NEGA: + tex->negative = getU2( fp ); + break; + + case ID_AXIS: + tex->axis = getU2( fp ); + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the texture header subchunk? */ + + if ( hsz <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( _pico_memstream_tell( fp ) - pos ); + return 1; +} + + +/* +====================================================================== +lwGetTMap() + +Read a texture map from a SURF.BLOK in an LWO2 file. The TMAP +defines the mapping from texture to world or object coordinates. +====================================================================== */ + +int lwGetTMap( picoMemStream_t *fp, int tmapsz, lwTMap *tmap ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos, i; + + pos = _pico_memstream_tell( fp ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_SIZE: + for ( i = 0; i < 3; i++ ) + tmap->size.val[ i ] = getF4( fp ); + tmap->size.eindex = getVX( fp ); + break; + + case ID_CNTR: + for ( i = 0; i < 3; i++ ) + tmap->center.val[ i ] = getF4( fp ); + tmap->center.eindex = getVX( fp ); + break; + + case ID_ROTA: + for ( i = 0; i < 3; i++ ) + tmap->rotate.val[ i ] = getF4( fp ); + tmap->rotate.eindex = getVX( fp ); + break; + + case ID_FALL: + tmap->fall_type = getU2( fp ); + for ( i = 0; i < 3; i++ ) + tmap->falloff.val[ i ] = getF4( fp ); + tmap->falloff.eindex = getVX( fp ); + break; + + case ID_OREF: + tmap->ref_object = getS0( fp ); + break; + + case ID_CSYS: + tmap->coord_sys = getU2( fp ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the TMAP subchunk? */ + + if ( tmapsz <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( _pico_memstream_tell( fp ) - pos ); + return 1; +} + + +/* +====================================================================== +lwGetImageMap() + +Read an lwImageMap from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +int lwGetImageMap( picoMemStream_t *fp, int rsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos; + + pos = _pico_memstream_tell( fp ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TMAP: + if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0; + break; + + case ID_PROJ: + tex->param.imap.projection = getU2( fp ); + break; + + case ID_VMAP: + tex->param.imap.vmap_name = getS0( fp ); + break; + + case ID_AXIS: + tex->param.imap.axis = getU2( fp ); + break; + + case ID_IMAG: + tex->param.imap.cindex = getVX( fp ); + break; + + case ID_WRAP: + tex->param.imap.wrapw_type = getU2( fp ); + tex->param.imap.wraph_type = getU2( fp ); + break; + + case ID_WRPW: + tex->param.imap.wrapw.val = getF4( fp ); + tex->param.imap.wrapw.eindex = getVX( fp ); + break; + + case ID_WRPH: + tex->param.imap.wraph.val = getF4( fp ); + tex->param.imap.wraph.eindex = getVX( fp ); + break; + + case ID_AAST: + tex->param.imap.aas_flags = getU2( fp ); + tex->param.imap.aa_strength = getF4( fp ); + break; + + case ID_PIXB: + tex->param.imap.pblend = getU2( fp ); + break; + + case ID_STCK: + tex->param.imap.stck.val = getF4( fp ); + tex->param.imap.stck.eindex = getVX( fp ); + break; + + case ID_TAMP: + tex->param.imap.amplitude.val = getF4( fp ); + tex->param.imap.amplitude.eindex = getVX( fp ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the image map? */ + + if ( rsz <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( _pico_memstream_tell( fp ) - pos ); + return 1; +} + + +/* +====================================================================== +lwGetProcedural() + +Read an lwProcedural from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +int lwGetProcedural( picoMemStream_t *fp, int rsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos; + + pos = _pico_memstream_tell( fp ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TMAP: + if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0; + break; + + case ID_AXIS: + tex->param.proc.axis = getU2( fp ); + break; + + case ID_VALU: + tex->param.proc.value[ 0 ] = getF4( fp ); + if ( sz >= 8 ) tex->param.proc.value[ 1 ] = getF4( fp ); + if ( sz >= 12 ) tex->param.proc.value[ 2 ] = getF4( fp ); + break; + + case ID_FUNC: + tex->param.proc.name = getS0( fp ); + rlen = get_flen(); + tex->param.proc.data = getbytes( fp, sz - rlen ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the procedural block? */ + + if ( rsz <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( _pico_memstream_tell( fp ) - pos ); + return 1; +} + + +/* +====================================================================== +lwGetGradient() + +Read an lwGradient from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +int lwGetGradient( picoMemStream_t *fp, int rsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos, i, j, nkeys; + + pos = _pico_memstream_tell( fp ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TMAP: + if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0; + break; + + case ID_PNAM: + tex->param.grad.paramname = getS0( fp ); + break; + + case ID_INAM: + tex->param.grad.itemname = getS0( fp ); + break; + + case ID_GRST: + tex->param.grad.start = getF4( fp ); + break; + + case ID_GREN: + tex->param.grad.end = getF4( fp ); + break; + + case ID_GRPT: + tex->param.grad.repeat = getU2( fp ); + break; + + case ID_FKEY: + nkeys = sz / sizeof( lwGradKey ); + tex->param.grad.key = _pico_calloc( nkeys, sizeof( lwGradKey )); + if ( !tex->param.grad.key ) return 0; + for ( i = 0; i < nkeys; i++ ) { + tex->param.grad.key[ i ].value = getF4( fp ); + for ( j = 0; j < 4; j++ ) + tex->param.grad.key[ i ].rgba[ j ] = getF4( fp ); + } + break; + + case ID_IKEY: + nkeys = sz / 2; + tex->param.grad.ikey = _pico_calloc( nkeys, sizeof( short )); + if ( !tex->param.grad.ikey ) return 0; + for ( i = 0; i < nkeys; i++ ) + tex->param.grad.ikey[ i ] = getU2( fp ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the gradient? */ + + if ( rsz <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( _pico_memstream_tell( fp ) - pos ); + return 1; +} + + +/* +====================================================================== +lwGetTexture() + +Read an lwTexture from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +lwTexture *lwGetTexture( picoMemStream_t *fp, int bloksz, unsigned int type ) +{ + lwTexture *tex; + unsigned short sz; + int ok; + + tex = _pico_calloc( 1, sizeof( lwTexture )); + if ( !tex ) return NULL; + + tex->type = type; + tex->tmap.size.val[ 0 ] = + tex->tmap.size.val[ 1 ] = + tex->tmap.size.val[ 2 ] = 1.0f; + tex->opacity.val = 1.0f; + tex->enabled = 1; + + sz = getU2( fp ); + if ( !lwGetTHeader( fp, sz, tex )) { + _pico_free( tex ); + return NULL; + } + + sz = bloksz - sz - 6; + switch ( type ) { + case ID_IMAP: ok = lwGetImageMap( fp, sz, tex ); break; + case ID_PROC: ok = lwGetProcedural( fp, sz, tex ); break; + case ID_GRAD: ok = lwGetGradient( fp, sz, tex ); break; + default: + ok = !_pico_memstream_seek( fp, sz, PICO_SEEK_CUR ); + } + + if ( !ok ) { + lwFreeTexture( tex ); + return NULL; + } + + set_flen( bloksz ); + return tex; +} + + +/* +====================================================================== +lwGetShader() + +Read a shader record from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +lwPlugin *lwGetShader( picoMemStream_t *fp, int bloksz ) +{ + lwPlugin *shdr; + unsigned int id; + unsigned short sz; + int hsz, rlen, pos; + + shdr = _pico_calloc( 1, sizeof( lwPlugin )); + if ( !shdr ) return NULL; + + pos = _pico_memstream_tell( fp ); + set_flen( 0 ); + hsz = getU2( fp ); + shdr->ord = getS0( fp ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + while ( hsz > 0 ) { + sz += sz & 1; + hsz -= sz; + if ( id == ID_ENAB ) { + shdr->flags = getU2( fp ); + break; + } + else { + _pico_memstream_seek( fp, sz, PICO_SEEK_CUR ); + id = getU4( fp ); + sz = getU2( fp ); + } + } + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_FUNC: + shdr->name = getS0( fp ); + rlen = get_flen(); + shdr->data = getbytes( fp, sz - rlen ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the shader block? */ + + if ( bloksz <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + set_flen( _pico_memstream_tell( fp ) - pos ); + return shdr; + +Fail: + lwFreePlugin( shdr ); + return NULL; +} + + +/* +====================================================================== +compare_textures() +compare_shaders() + +Callbacks for the lwListInsert() function, which is called to add +textures to surface channels and shaders to surfaces. +====================================================================== */ + +static int compare_textures( lwTexture *a, lwTexture *b ) +{ + return strcmp( a->ord, b->ord ); +} + + +static int compare_shaders( lwPlugin *a, lwPlugin *b ) +{ + return strcmp( a->ord, b->ord ); +} + + +/* +====================================================================== +add_texture() + +Finds the surface channel (lwTParam or lwCParam) to which a texture is +applied, then calls lwListInsert(). +====================================================================== */ + +static int add_texture( lwSurface *surf, lwTexture *tex ) +{ + lwTexture **list; + + switch ( tex->chan ) { + case ID_COLR: list = &surf->color.tex; break; + case ID_LUMI: list = &surf->luminosity.tex; break; + case ID_DIFF: list = &surf->diffuse.tex; break; + case ID_SPEC: list = &surf->specularity.tex; break; + case ID_GLOS: list = &surf->glossiness.tex; break; + case ID_REFL: list = &surf->reflection.val.tex; break; + case ID_TRAN: list = &surf->transparency.val.tex; break; + case ID_RIND: list = &surf->eta.tex; break; + case ID_TRNL: list = &surf->translucency.tex; break; + case ID_BUMP: list = &surf->bump.tex; break; + default: return 0; + } + + lwListInsert( (void **) list, tex, ( void *) compare_textures ); + return 1; +} + + +/* +====================================================================== +lwDefaultSurface() + +Allocate and initialize a surface. +====================================================================== */ + +lwSurface *lwDefaultSurface( void ) +{ + lwSurface *surf; + + surf = _pico_calloc( 1, sizeof( lwSurface )); + if ( !surf ) return NULL; + + surf->color.rgb[ 0 ] = 0.78431f; + surf->color.rgb[ 1 ] = 0.78431f; + surf->color.rgb[ 2 ] = 0.78431f; + surf->diffuse.val = 1.0f; + surf->glossiness.val = 0.4f; + surf->bump.val = 1.0f; + surf->eta.val = 1.0f; + surf->sideflags = 1; + + return surf; +} + + +/* +====================================================================== +lwGetSurface() + +Read an lwSurface from an LWO2 file. +====================================================================== */ + +lwSurface *lwGetSurface( picoMemStream_t *fp, int cksize ) +{ + lwSurface *surf; + lwTexture *tex; + lwPlugin *shdr; + unsigned int id, type; + unsigned short sz; + int pos, rlen; + + + /* allocate the Surface structure */ + + surf = _pico_calloc( 1, sizeof( lwSurface )); + if ( !surf ) goto Fail; + + /* non-zero defaults */ + + surf->color.rgb[ 0 ] = 0.78431f; + surf->color.rgb[ 1 ] = 0.78431f; + surf->color.rgb[ 2 ] = 0.78431f; + surf->diffuse.val = 1.0f; + surf->glossiness.val = 0.4f; + surf->bump.val = 1.0f; + surf->eta.val = 1.0f; + surf->sideflags = 1; + + /* remember where we started */ + + set_flen( 0 ); + pos = _pico_memstream_tell( fp ); + + /* names */ + + surf->name = getS0( fp ); + surf->srcname = getS0( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_COLR: + surf->color.rgb[ 0 ] = getF4( fp ); + surf->color.rgb[ 1 ] = getF4( fp ); + surf->color.rgb[ 2 ] = getF4( fp ); + surf->color.eindex = getVX( fp ); + break; + + case ID_LUMI: + surf->luminosity.val = getF4( fp ); + surf->luminosity.eindex = getVX( fp ); + break; + + case ID_DIFF: + surf->diffuse.val = getF4( fp ); + surf->diffuse.eindex = getVX( fp ); + break; + + case ID_SPEC: + surf->specularity.val = getF4( fp ); + surf->specularity.eindex = getVX( fp ); + break; + + case ID_GLOS: + surf->glossiness.val = getF4( fp ); + surf->glossiness.eindex = getVX( fp ); + break; + + case ID_REFL: + surf->reflection.val.val = getF4( fp ); + surf->reflection.val.eindex = getVX( fp ); + break; + + case ID_RFOP: + surf->reflection.options = getU2( fp ); + break; + + case ID_RIMG: + surf->reflection.cindex = getVX( fp ); + break; + + case ID_RSAN: + surf->reflection.seam_angle = getF4( fp ); + break; + + case ID_TRAN: + surf->transparency.val.val = getF4( fp ); + surf->transparency.val.eindex = getVX( fp ); + break; + + case ID_TROP: + surf->transparency.options = getU2( fp ); + break; + + case ID_TIMG: + surf->transparency.cindex = getVX( fp ); + break; + + case ID_RIND: + surf->eta.val = getF4( fp ); + surf->eta.eindex = getVX( fp ); + break; + + case ID_TRNL: + surf->translucency.val = getF4( fp ); + surf->translucency.eindex = getVX( fp ); + break; + + case ID_BUMP: + surf->bump.val = getF4( fp ); + surf->bump.eindex = getVX( fp ); + break; + + case ID_SMAN: + surf->smooth = getF4( fp ); + break; + + case ID_SIDE: + surf->sideflags = getU2( fp ); + break; + + case ID_CLRH: + surf->color_hilite.val = getF4( fp ); + surf->color_hilite.eindex = getVX( fp ); + break; + + case ID_CLRF: + surf->color_filter.val = getF4( fp ); + surf->color_filter.eindex = getVX( fp ); + break; + + case ID_ADTR: + surf->add_trans.val = getF4( fp ); + surf->add_trans.eindex = getVX( fp ); + break; + + case ID_SHRP: + surf->dif_sharp.val = getF4( fp ); + surf->dif_sharp.eindex = getVX( fp ); + break; + + case ID_GVAL: + surf->glow.val = getF4( fp ); + surf->glow.eindex = getVX( fp ); + break; + + case ID_LINE: + surf->line.enabled = 1; + if ( sz >= 2 ) surf->line.flags = getU2( fp ); + if ( sz >= 6 ) surf->line.size.val = getF4( fp ); + if ( sz >= 8 ) surf->line.size.eindex = getVX( fp ); + break; + + case ID_ALPH: + surf->alpha_mode = getU2( fp ); + surf->alpha = getF4( fp ); + break; + + case ID_AVAL: + surf->alpha = getF4( fp ); + break; + + case ID_BLOK: + type = getU4( fp ); + + switch ( type ) { + case ID_IMAP: + case ID_PROC: + case ID_GRAD: + tex = lwGetTexture( fp, sz - 4, type ); + if ( !tex ) goto Fail; + if ( !add_texture( surf, tex )) + lwFreeTexture( tex ); + set_flen( 4 + get_flen() ); + break; + case ID_SHDR: + shdr = lwGetShader( fp, sz - 4 ); + if ( !shdr ) goto Fail; + lwListInsert( (void **) &surf->shader, shdr, (void *) compare_shaders ); + ++surf->nshaders; + set_flen( 4 + get_flen() ); + break; + } + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR ); + + /* end of the SURF chunk? */ + + if ( cksize <= _pico_memstream_tell( fp ) - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return surf; + +Fail: + if ( surf ) lwFreeSurface( surf ); + return NULL; +} diff --git a/libs/picomodel/lwo/vecmath.c b/libs/picomodel/lwo/vecmath.c new file mode 100644 index 00000000..eaa1e8fc --- /dev/null +++ b/libs/picomodel/lwo/vecmath.c @@ -0,0 +1,37 @@ +/* +====================================================================== +vecmath.c + +Basic vector and matrix functions. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include + + +float dot( float a[], float b[] ) +{ + return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] + a[ 2 ] * b[ 2 ]; +} + + +void cross( float a[], float b[], float c[] ) +{ + c[ 0 ] = a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ]; + c[ 1 ] = a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ]; + c[ 2 ] = a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ]; +} + + +void normalize( float v[] ) +{ + float r; + + r = ( float ) sqrt( dot( v, v )); + if ( r > 0 ) { + v[ 0 ] /= r; + v[ 1 ] /= r; + v[ 2 ] /= r; + } +} diff --git a/libs/picomodel/lwo/vmap.c b/libs/picomodel/lwo/vmap.c new file mode 100644 index 00000000..69f87cf9 --- /dev/null +++ b/libs/picomodel/lwo/vmap.c @@ -0,0 +1,243 @@ +/* +====================================================================== +vmap.c + +Vertex map functions for an LWO2 reader. + +Ernie Wright 17 Sep 00 +====================================================================== */ + +#include "../picointernal.h" +#include "lwo2.h" + + +/* +====================================================================== +lwFreeVMap() + +Free memory used by an lwVMap. +====================================================================== */ + +void lwFreeVMap( lwVMap *vmap ) +{ + if ( vmap ) { + if ( vmap->name ) _pico_free( vmap->name ); + if ( vmap->vindex ) _pico_free( vmap->vindex ); + if ( vmap->pindex ) _pico_free( vmap->pindex ); + if ( vmap->val ) { + if ( vmap->val[ 0 ] ) _pico_free( vmap->val[ 0 ] ); + _pico_free( vmap->val ); + } + _pico_free( vmap ); + } +} + + +/* +====================================================================== +lwGetVMap() + +Read an lwVMap from a VMAP or VMAD chunk in an LWO2. +====================================================================== */ + +lwVMap *lwGetVMap( picoMemStream_t *fp, int cksize, int ptoffset, int poloffset, + int perpoly ) +{ + unsigned char *buf, *bp; + lwVMap *vmap; + float *f; + int i, j, npts, rlen; + + + /* read the whole chunk */ + + set_flen( 0 ); + buf = getbytes( fp, cksize ); + if ( !buf ) return NULL; + + vmap = _pico_calloc( 1, sizeof( lwVMap )); + if ( !vmap ) { + _pico_free( buf ); + return NULL; + } + + /* initialize the vmap */ + + vmap->perpoly = perpoly; + + bp = buf; + set_flen( 0 ); + vmap->type = sgetU4( &bp ); + vmap->dim = sgetU2( &bp ); + vmap->name = sgetS0( &bp ); + rlen = get_flen(); + + /* count the vmap records */ + + npts = 0; + while ( bp < buf + cksize ) { + i = sgetVX( &bp ); + if ( perpoly ) + i = sgetVX( &bp ); + bp += vmap->dim * sizeof( float ); + ++npts; + } + + /* allocate the vmap */ + + vmap->nverts = npts; + vmap->vindex = _pico_calloc( npts, sizeof( int )); + if ( !vmap->vindex ) goto Fail; + if ( perpoly ) { + vmap->pindex = _pico_calloc( npts, sizeof( int )); + if ( !vmap->pindex ) goto Fail; + } + + if ( vmap->dim > 0 ) { + vmap->val = _pico_calloc( npts, sizeof( float * )); + if ( !vmap->val ) goto Fail; + f = _pico_alloc( npts * vmap->dim * sizeof( float )); + if ( !f ) goto Fail; + for ( i = 0; i < npts; i++ ) + vmap->val[ i ] = f + i * vmap->dim; + } + + /* fill in the vmap values */ + + bp = buf + rlen; + for ( i = 0; i < npts; i++ ) { + vmap->vindex[ i ] = sgetVX( &bp ); + if ( perpoly ) + vmap->pindex[ i ] = sgetVX( &bp ); + for ( j = 0; j < vmap->dim; j++ ) + vmap->val[ i ][ j ] = sgetF4( &bp ); + } + + _pico_free( buf ); + return vmap; + +Fail: + if ( buf ) _pico_free( buf ); + lwFreeVMap( vmap ); + return NULL; +} + + +/* +====================================================================== +lwGetPointVMaps() + +Fill in the lwVMapPt structure for each point. +====================================================================== */ + +int lwGetPointVMaps( lwPointList *point, lwVMap *vmap ) +{ + lwVMap *vm; + int i, j, n; + + /* count the number of vmap values for each point */ + + vm = vmap; + while ( vm ) { + if ( !vm->perpoly ) + for ( i = 0; i < vm->nverts; i++ ) + ++point->pt[ vm->vindex[ i ]].nvmaps; + vm = vm->next; + } + + /* allocate vmap references for each mapped point */ + + for ( i = 0; i < point->count; i++ ) { + if ( point->pt[ i ].nvmaps ) { + point->pt[ i ].vm = _pico_calloc( point->pt[ i ].nvmaps, sizeof( lwVMapPt )); + if ( !point->pt[ i ].vm ) return 0; + point->pt[ i ].nvmaps = 0; + } + } + + /* fill in vmap references for each mapped point */ + + vm = vmap; + while ( vm ) { + if ( !vm->perpoly ) { + for ( i = 0; i < vm->nverts; i++ ) { + j = vm->vindex[ i ]; + n = point->pt[ j ].nvmaps; + point->pt[ j ].vm[ n ].vmap = vm; + point->pt[ j ].vm[ n ].index = i; + ++point->pt[ j ].nvmaps; + } + } + vm = vm->next; + } + + return 1; +} + + +/* +====================================================================== +lwGetPolyVMaps() + +Fill in the lwVMapPt structure for each polygon vertex. +====================================================================== */ + +int lwGetPolyVMaps( lwPolygonList *polygon, lwVMap *vmap ) +{ + lwVMap *vm; + lwPolVert *pv; + int i, j; + + /* count the number of vmap values for each polygon vertex */ + + vm = vmap; + while ( vm ) { + if ( vm->perpoly ) { + for ( i = 0; i < vm->nverts; i++ ) { + for ( j = 0; j < polygon->pol[ vm->pindex[ i ]].nverts; j++ ) { + pv = &polygon->pol[ vm->pindex[ i ]].v[ j ]; + if ( vm->vindex[ i ] == pv->index ) { + ++pv->nvmaps; + break; + } + } + } + } + vm = vm->next; + } + + /* allocate vmap references for each mapped vertex */ + + for ( i = 0; i < polygon->count; i++ ) { + for ( j = 0; j < polygon->pol[ i ].nverts; j++ ) { + pv = &polygon->pol[ i ].v[ j ]; + if ( pv->nvmaps ) { + pv->vm = _pico_calloc( pv->nvmaps, sizeof( lwVMapPt )); + if ( !pv->vm ) return 0; + pv->nvmaps = 0; + } + } + } + + /* fill in vmap references for each mapped point */ + + vm = vmap; + while ( vm ) { + if ( vm->perpoly ) { + for ( i = 0; i < vm->nverts; i++ ) { + for ( j = 0; j < polygon->pol[ vm->pindex[ i ]].nverts; j++ ) { + pv = &polygon->pol[ vm->pindex[ i ]].v[ j ]; + if ( vm->vindex[ i ] == pv->index ) { + pv->vm[ pv->nvmaps ].vmap = vm; + pv->vm[ pv->nvmaps ].index = i; + ++pv->nvmaps; + break; + } + } + } + } + vm = vm->next; + } + + return 1; +} diff --git a/libs/picomodel/picointernal.c b/libs/picomodel/picointernal.c new file mode 100644 index 00000000..22214923 --- /dev/null +++ b/libs/picomodel/picointernal.c @@ -0,0 +1,1356 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PICOINTERNAL_C + + + +/* todo: + * - fix p->curLine for parser routines. increased twice + */ + +/* dependencies */ +#include +#include "picointernal.h" + + + +/* function pointers */ +void *(*_pico_ptr_malloc )( size_t ) = malloc; +void (*_pico_ptr_free )( void* ) = free; +void (*_pico_ptr_load_file )( char*, unsigned char**, int* ) = NULL; +void (*_pico_ptr_free_file )( void* ) = NULL; +void (*_pico_ptr_print )( int, const char* ) = NULL; + +typedef union +{ + float f; + char c[4]; +} +floatSwapUnion; + +/* _pico_alloc: + * kludged memory allocation wrapper + */ +void *_pico_alloc( size_t size ) +{ + void *ptr; + + /* some sanity checks */ + if( size == 0 ) + return NULL; + if (_pico_ptr_malloc == NULL) + return NULL; + + /* allocate memory */ + ptr = _pico_ptr_malloc(size); + if (ptr == NULL) + return NULL; + + /* zero out allocated memory */ + memset(ptr,0,size); + + /* return pointer to allocated memory */ + return ptr; +} + +/* _pico_calloc: + * _pico_calloc wrapper + */ +void *_pico_calloc( size_t num, size_t size ) +{ + void *ptr; + + /* some sanity checks */ + if( num == 0 || size == 0 ) + return NULL; + if (_pico_ptr_malloc == NULL) + return NULL; + + /* allocate memory */ + ptr = _pico_ptr_malloc(num*size); + if (ptr == NULL) + return NULL; + + /* zero out allocated memory */ + memset(ptr,0,num*size); + + /* return pointer to allocated memory */ + return ptr; +} + +/* _pico_realloc: + * memory reallocation wrapper (note: only grows, + * but never shrinks or frees) + */ +void *_pico_realloc( void **ptr, size_t oldSize, size_t newSize ) +{ + void *ptr2; + + /* sanity checks */ + if( ptr == NULL ) + return NULL; + if( newSize < oldSize ) + return *ptr; + if (_pico_ptr_malloc == NULL) + return NULL; + + /* allocate new pointer */ + ptr2 = _pico_alloc( newSize ); + if( ptr2 == NULL ) + return NULL; + + /* copy */ + if( *ptr != NULL ) + { + memcpy( ptr2, *ptr, oldSize ); + _pico_free( *ptr ); + } + + /* fix up and return */ + *ptr = ptr2; + return *ptr; +} + +/* _pico_clone_alloc: + * handy function for quick string allocation/copy. it clones + * the given string and returns a pointer to the new allocated + * clone (which must be freed by caller of course) or returns + * NULL on memory alloc or param errors. if 'size' is -1 the + * length of the input string is used, otherwise 'size' is used + * as custom clone size (the string is cropped to fit into mem + * if needed). -sea + */ +char *_pico_clone_alloc( const char *str ) +{ + char* cloned; + + /* sanity check */ + if (str == NULL) + return NULL; + + /* allocate memory */ + cloned = _pico_alloc( strlen(str) + 1 ); + if (cloned == NULL) + return NULL; + + /* copy input string to cloned string */ + strcpy( cloned, str ); + + /* return ptr to cloned string */ + return cloned; +} + +/* _pico_free: + * wrapper around the free function pointer + */ +void _pico_free( void *ptr ) +{ + /* sanity checks */ + if( ptr == NULL ) + return; + if (_pico_ptr_free == NULL) + return; + + /* free the allocated memory */ + _pico_ptr_free( ptr ); +} + +/* _pico_load_file: + * wrapper around the loadfile function pointer + */ +void _pico_load_file( char *name, unsigned char **buffer, int *bufSize ) +{ + /* sanity checks */ + if( name == NULL ) + { + *bufSize = -1; + return; + } + if (_pico_ptr_load_file == NULL) + { + *bufSize = -1; + return; + } + /* do the actual call to read in the file; */ + /* BUFFER IS ALLOCATED BY THE EXTERNAL LOADFILE FUNC */ + _pico_ptr_load_file( name,buffer,bufSize ); +} + +/* _pico_free_file: + * wrapper around the file free function pointer + */ +void _pico_free_file( void *buffer ) +{ + /* sanity checks */ + if( buffer == NULL ) + return; + + /* use default free */ + if( _pico_ptr_free_file == NULL ) + { + free( buffer ); + return; + } + /* free the allocated file */ + _pico_ptr_free_file( buffer ); +} + +/* _pico_printf: + * wrapper around the print function pointer -sea + */ +void _pico_printf( int level, const char *format, ...) +{ + char str[4096]; + va_list argptr; + + /* sanity checks */ + if( format == NULL ) + return; + if (_pico_ptr_print == NULL) + return; + + /* format string */ + va_start( argptr,format ); + vsprintf( str,format,argptr ); + va_end( argptr ); + + /* remove linefeeds */ + if (str[ strlen(str)-1 ] == '\n') + str[ strlen(str)-1 ] = '\0'; + + /* do the actual call */ + _pico_ptr_print( level,str ); +} + +/* _pico_first_token: + * trims everything after the first whitespace-delimited token + */ + +void _pico_first_token( char *str ) +{ + if( !str || !*str ) + return; + while( *str && !isspace( *str ) ) + *str++; + *str = '\0'; +} + +/* _pico_strltrim: + * left trims the given string -sea + */ +char *_pico_strltrim( char *str ) +{ + char *str1 = str, *str2 = str; + + while (isspace(*str2)) str2++; + if( str2 != str ) + while( *str2 != '\0' ) /* fix: ydnar */ + *str1++ = *str2++; + return str; +} + +/* _pico_strrtrim: + * right trims the given string -sea + */ +char *_pico_strrtrim( char *str ) +{ + if (str && *str) + { + char *str1 = str; + int allspace = 1; + + while (*str1) + { + if (allspace && !isspace(*str1)) allspace = 0; + str1++; + } + if (allspace) *str = '\0'; + else { + str1--; + while ((isspace(*str1)) && (str1 >= str)) + *str1-- = '\0'; + } + } + return str; +} + +/* _pico_strlwr: + * pico internal string-to-lower routine. + */ +char *_pico_strlwr( char *str ) +{ + char *cp; + for (cp=str; *cp; ++cp) + { + if ('A' <= *cp && *cp <= 'Z') + { + *cp += ('a' - 'A'); + } + } + return str; +} + +/* _pico_strchcount: + * counts how often the given char appears in str. -sea + */ +int _pico_strchcount( char *str, int ch ) +{ + int count = 0; + while (*str++) if (*str == ch) count++; + return count; +} + +void _pico_zero_bounds( picoVec3_t mins, picoVec3_t maxs ) +{ + int i; + for (i=0; i<3; i++) + { + mins[i] = +999999; + maxs[i] = -999999; + } +} + +void _pico_expand_bounds( picoVec3_t p, picoVec3_t mins, picoVec3_t maxs ) +{ + int i; + for (i=0; i<3; i++) + { + float value = p[i]; + if (value < mins[i]) mins[i] = value; + if (value > maxs[i]) maxs[i] = value; + } +} + +void _pico_zero_vec( picoVec3_t vec ) +{ + vec[ 0 ] = vec[ 1 ] = vec[ 2 ] = 0; +} + +void _pico_zero_vec2( picoVec2_t vec ) +{ + vec[ 0 ] = vec[ 1 ] = 0; +} + +void _pico_zero_vec4( picoVec4_t vec ) +{ + vec[ 0 ] = vec[ 1 ] = vec[ 2 ] = vec[ 3 ] = 0; +} + +void _pico_set_vec( picoVec3_t v, float a, float b, float c ) +{ + v[ 0 ] = a; + v[ 1 ] = b; + v[ 2 ] = c; +} + +void _pico_set_vec4( picoVec4_t v, float a, float b, float c, float d ) +{ + v[ 0 ] = a; + v[ 1 ] = b; + v[ 2 ] = c; + v[ 3 ] = d; +} + +void _pico_copy_vec( picoVec3_t src, picoVec3_t dest ) +{ + dest[ 0 ] = src[ 0 ]; + dest[ 1 ] = src[ 1 ]; + dest[ 2 ] = src[ 2 ]; +} + +void _pico_copy_vec2( picoVec2_t src, picoVec2_t dest ) +{ + dest[ 0 ] = src[ 0 ]; + dest[ 1 ] = src[ 1 ]; +} + +void _pico_copy_vec4( picoVec4_t src, picoVec4_t dest ) +{ + dest[ 0 ] = src[ 0 ]; + dest[ 1 ] = src[ 1 ]; + dest[ 2 ] = src[ 2 ]; + dest[ 3 ] = src[ 3 ]; +} + +/* ydnar */ +picoVec_t _pico_normalize_vec( picoVec3_t vec ) +{ + double len, ilen; + + len = sqrt( vec[ 0 ] * vec[ 0 ] + vec[ 1 ] * vec[ 1 ] + vec[ 2 ] * vec[ 2 ] ); + if( len == 0.0 ) return 0.0; + ilen = 1.0 / len; + vec[ 0 ] *= (picoVec_t) ilen; + vec[ 1 ] *= (picoVec_t) ilen; + vec[ 2 ] *= (picoVec_t) ilen; + return (picoVec_t) len; +} + +void _pico_add_vec( picoVec3_t a, picoVec3_t b, picoVec3_t dest ) +{ + dest[ 0 ] = a[ 0 ] + b[ 0 ]; + dest[ 1 ] = a[ 1 ] + b[ 1 ]; + dest[ 2 ] = a[ 2 ] + b[ 2 ]; +} + +void _pico_subtract_vec( picoVec3_t a, picoVec3_t b, picoVec3_t dest ) +{ + dest[ 0 ] = a[ 0 ] - b[ 0 ]; + dest[ 1 ] = a[ 1 ] - b[ 1 ]; + dest[ 2 ] = a[ 2 ] - b[ 2 ]; +} + +void _pico_scale_vec( picoVec3_t v, float scale, picoVec3_t dest ) +{ + dest[ 0 ] = v[ 0 ] * scale; + dest[ 1 ] = v[ 1 ] * scale; + dest[ 2 ] = v[ 2 ] * scale; +} + +void _pico_scale_vec4( picoVec4_t v, float scale, picoVec4_t dest ) +{ + dest[ 0 ] = v[ 0 ] * scale; + dest[ 1 ] = v[ 1 ] * scale; + dest[ 2 ] = v[ 2 ] * scale; + dest[ 3 ] = v[ 3 ] * scale; +} + +picoVec_t _pico_dot_vec( picoVec3_t a, picoVec3_t b ) +{ + return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] + a[ 2 ] * b[ 2 ]; +} + +void _pico_cross_vec( picoVec3_t a, picoVec3_t b, picoVec3_t dest ) +{ + dest[ 0 ] = a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ]; + dest[ 1 ] = a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ]; + dest[ 2 ] = a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ]; +} + +picoVec_t _pico_calc_plane( picoVec4_t plane, picoVec3_t a, picoVec3_t b, picoVec3_t c ) +{ + picoVec3_t ba, ca; + + _pico_subtract_vec( b, a, ba ); + _pico_subtract_vec( c, a, ca ); + _pico_cross_vec( ca, ba, plane ); + plane[ 3 ] = _pico_dot_vec( a, plane ); + return _pico_normalize_vec( plane ); +} + +/* separate from _pico_set_vec4 */ +void _pico_set_color( picoColor_t c, int r, int g, int b, int a ) +{ + c[ 0 ] = r; + c[ 1 ] = g; + c[ 2 ] = b; + c[ 3 ] = a; +} + +void _pico_copy_color( picoColor_t src, picoColor_t dest ) +{ + dest[ 0 ] = src[ 0 ]; + dest[ 1 ] = src[ 1 ]; + dest[ 2 ] = src[ 2 ]; + dest[ 3 ] = src[ 3 ]; +} + +#ifdef __BIG_ENDIAN__ + +int _pico_big_long ( int src ) { return src; } +short _pico_big_short( short src ) { return src; } +float _pico_big_float( float src ) { return src; } + +int _pico_little_long( int src ) +{ + return ((src & 0xFF000000) >> 24) | + ((src & 0x00FF0000) >> 8) | + ((src & 0x0000FF00) << 8) | + ((src & 0x000000FF) << 24); +} + +short _pico_little_short( short src ) +{ + return ((src & 0xFF00) >> 8) | + ((src & 0x00FF) << 8); +} + +float _pico_little_float( float src ) +{ + floatSwapUnion in,out; + in.f = src; + out.c[ 0 ] = in.c[ 3 ]; + out.c[ 1 ] = in.c[ 2 ]; + out.c[ 2 ] = in.c[ 1 ]; + out.c[ 3 ] = in.c[ 0 ]; + return out.f; +} +#else /*__BIG_ENDIAN__*/ + +int _pico_little_long ( int src ) { return src; } +short _pico_little_short( short src ) { return src; } +float _pico_little_float( float src ) { return src; } + +int _pico_big_long( int src ) +{ + return ((src & 0xFF000000) >> 24) | + ((src & 0x00FF0000) >> 8) | + ((src & 0x0000FF00) << 8) | + ((src & 0x000000FF) << 24); +} + +short _pico_big_short( short src ) +{ + return ((src & 0xFF00) >> 8) | + ((src & 0x00FF) << 8); +} + +float _pico_big_float( float src ) +{ + floatSwapUnion in,out; + in.f = src; + out.c[ 0 ] = in.c[ 3 ]; + out.c[ 1 ] = in.c[ 2 ]; + out.c[ 2 ] = in.c[ 1 ]; + out.c[ 3 ] = in.c[ 0 ]; + return out.f; +} +#endif /*__BIG_ENDIAN__*/ + +/* _pico_stristr: + * case-insensitive strstr. -sea + */ +char *_pico_stristr( char *str, const char *substr ) +{ + const size_t sublen = strlen(substr); + while (*str) + { + if (!_pico_strnicmp(str,substr,sublen)) break; + str++; + } + if (!(*str)) str = NULL; + return str; +} + +/* +_pico_unixify() +changes dos \ style path separators to / +*/ + +void _pico_unixify( char *path ) +{ + if( path == NULL ) + return; + while( *path ) + { + if( *path == '\\' ) + *path = '/'; + path++; + } +} + +/* _pico_nofname: + * removes file name portion from given file path and converts + * the directory separators to un*x style. returns 1 on success + * or 0 when 'destSize' was exceeded. -sea + */ +int _pico_nofname( const char *path, char *dest, int destSize ) +{ + int left = destSize; + char *temp = dest; + + while ((*dest = *path) != '\0') + { + if (*dest == '/' || *dest == '\\') + { + temp = (dest + 1); + *dest = '/'; + } + dest++; path++; + + if (--left < 1) + { + *temp = '\0'; + return 0; + } + } + *temp = '\0'; + return 1; +} + +/* _pico_nopath: + * returns ptr to filename portion in given path or an empty + * string otherwise. given 'path' is not altered. -sea + */ +const char *_pico_nopath( const char *path ) +{ + const char *src; + src = path + (strlen(path) - 1); + + if (path == NULL) return ""; + if (!strchr(path,'/') && !strchr(path,'\\')) + return (path); + + while ((src--) != path) + { + if (*src == '/' || *src == '\\') + return (++src); + } + return ""; +} + +/* _pico_setfext: + * sets/changes the file extension for the given filename + * or filepath's filename portion. the given 'path' *is* + * altered. leave 'ext' empty to remove extension. -sea + */ +char *_pico_setfext( char *path, const char *ext ) +{ + char *src; + int remfext = 0; + + src = path + (strlen(path) - 1); + + if (ext == NULL) ext = ""; + if (strlen(ext ) < 1) remfext = 1; + if (strlen(path) < 1) + return path; + + while ((src--) != path) + { + if (*src == '/' || *src == '\\') + return path; + + if (*src == '.') + { + if (remfext) + { + *src = '\0'; + return path; + } + *(++src) = '\0'; + break; + } + } + strcat(path,ext); + return path; +} + +/* _pico_getline: + * extracts one line from the given buffer and stores it in dest. + * returns -1 on error or the length of the line on success. i've + * removed string trimming here. this can be done manually by the + * calling func. + */ +int _pico_getline( char *buf, int bufsize, char *dest, int destsize ) +{ + int pos; + + /* check output */ + if (dest == NULL || destsize < 1) return -1; + memset( dest,0,destsize ); + + /* check input */ + if (buf == NULL || bufsize < 1) + return -1; + + /* get next line */ + for (pos=0; poscursor == NULL) + return; + + /* skin white spaces */ + while( 1 ) + { + /* sanity checks */ + if (p->cursor < p->buffer || + p->cursor >= p->max) + { + return; + } + /* break for chars other than white spaces */ + if (*p->cursor > 0x20) break; + if (*p->cursor == 0x00) return; + + /* a bit of linefeed handling */ + if (*p->cursor == '\n') + { + *hasLFs = 1; + p->curLine++; + } + /* go to next character */ + p->cursor++; + } +} + +/* _pico_new_parser: + * allocates a new ascii parser object. + */ +picoParser_t *_pico_new_parser( picoByte_t *buffer, int bufSize ) +{ + picoParser_t *p; + + /* sanity check */ + if( buffer == NULL || bufSize <= 0 ) + return NULL; + + /* allocate reader */ + p = _pico_alloc( sizeof(picoParser_t) ); + if (p == NULL) return NULL; + memset( p,0,sizeof(picoParser_t) ); + + /* allocate token space */ + p->tokenSize = 0; + p->tokenMax = 1024; + p->token = _pico_alloc( p->tokenMax ); + if( p->token == NULL ) + { + _pico_free( p ); + return NULL; + } + /* setup */ + p->buffer = buffer; + p->cursor = buffer; + p->bufSize = bufSize; + p->max = p->buffer + bufSize; + p->curLine = 1; /* sea: new */ + + /* return ptr to parser */ + return p; +} + +/* _pico_free_parser: + * frees an existing pico parser object. + */ +void _pico_free_parser( picoParser_t *p ) +{ + /* sanity check */ + if (p == NULL) return; + + /* free the parser */ + if (p->token != NULL) + { + _pico_free( p->token ); + } + _pico_free( p ); +} + +/* _pico_parse_ex: + * reads the next token from given pico parser object. if param + * 'allowLFs' is 1 it will read beyond linefeeds and return 0 when + * the EOF is reached. if 'allowLFs' is 0 it will return 0 when + * the EOL is reached. if 'handleQuoted' is 1 the parser function + * will handle "quoted" strings and return the data between the + * quotes as token. returns 0 on end/error or 1 on success. -sea + */ +int _pico_parse_ex( picoParser_t *p, int allowLFs, int handleQuoted ) +{ + int hasLFs = 0; + char *old; + + /* sanity checks */ + if( p == NULL || p->buffer == NULL || + p->cursor < p->buffer || + p->cursor >= p->max ) + { + return 0; + } + /* clear parser token */ + p->tokenSize = 0; + p->token[ 0 ] = '\0'; + old = p->cursor; + + /* skip whitespaces */ + while( p->cursor < p->max && *p->cursor <= 32 ) + { + if (*p->cursor == '\n') + { + p->curLine++; + hasLFs++; + } + p->cursor++; + } + /* return if we're not allowed to go beyond lfs */ + if ((hasLFs > 0) && !allowLFs) + { + p->cursor = old; + return 0; + } + /* get next quoted string */ + if (*p->cursor == '\"' && handleQuoted) + { + p->cursor++; + while (p->cursor < p->max && *p->cursor) + { + if (*p->cursor == '\\') + { + if (*(p->cursor+1) == '"') + { + p->cursor++; + } + p->token[ p->tokenSize++ ] = *p->cursor++; + continue; + } + else if (*p->cursor == '\"') + { + p->cursor++; + break; + } + else if (*p->cursor == '\n') + { + p->curLine++; + } + p->token[ p->tokenSize++ ] = *p->cursor++; + } + /* terminate token */ + p->token[ p->tokenSize ] = '\0'; + return 1; + } + /* otherwise get next word */ + while( p->cursor < p->max && *p->cursor > 32 ) + { + if (*p->cursor == '\n') + { + p->curLine++; + } + p->token[ p->tokenSize++ ] = *p->cursor++; + } + /* terminate token */ + p->token[ p->tokenSize ] = '\0'; + return 1; +} + +/* _pico_parse_first: + * reads the first token from the next line and returns + * a pointer to it. returns NULL on EOL or EOF. -sea + */ +char *_pico_parse_first( picoParser_t *p ) +{ + /* sanity check */ + if (p == NULL) return NULL; + + /* try to read next token (with lfs & quots) */ + if (!_pico_parse_ex( p,1,1 )) + return NULL; + + /* return ptr to the token string */ + return p->token; +} + +/* _pico_parse: + * reads the next token from the parser and returns a pointer + * to it. quoted strings are handled as usual. returns NULL + * on EOL or EOF. -sea + */ +char *_pico_parse( picoParser_t *p, int allowLFs ) +{ + /* sanity check */ + if (p == NULL) return NULL; + + /* try to read next token (with quots) */ + if (!_pico_parse_ex( p,allowLFs,1 )) + return NULL; + + /* return ptr to the token string */ + return p->token; +} + +/* _pico_parse_skip_rest: + * skips the rest of the current line in parser. + */ +void _pico_parse_skip_rest( picoParser_t *p ) +{ + while( _pico_parse_ex( p,0,0 ) ) ; +} + +/* _pico_parse_skip_braced: + * parses/skips over a braced section. returns 1 on success + * or 0 on error (when there was no closing bracket and the + * end of buffer was reached or when the opening bracket was + * missing). + */ +int _pico_parse_skip_braced( picoParser_t *p ) +{ + int firstToken = 1; + int level; + + /* sanity check */ + if (p == NULL) return 0; + + /* set the initial level for parsing */ + level = 0; + + /* skip braced section */ + while( 1 ) + { + /* read next token (lfs allowed) */ + if (!_pico_parse_ex( p,1,1 )) + { + /* end of parser buffer reached */ + return 0; + } + /* first token must be an opening bracket */ + if (firstToken && p->token[0] != '{') + { + /* opening bracket missing */ + return 0; + } + /* we only check this once */ + firstToken = 0; + + /* update level */ + if (p->token[1] == '\0') + { + if (p->token[0] == '{') level++; + if (p->token[0] == '}') level--; + } + /* break if we're back at our starting level */ + if (level == 0) break; + } + /* successfully skipped braced section */ + return 1; +} + +int _pico_parse_check( picoParser_t *p, int allowLFs, char *str ) +{ + if (!_pico_parse_ex( p,allowLFs,1 )) + return 0; + if (!strcmp(p->token,str)) + return 1; + return 0; +} + +int _pico_parse_checki( picoParser_t *p, int allowLFs, char *str ) +{ + if (!_pico_parse_ex( p,allowLFs,1 )) + return 0; + if (!_pico_stricmp(p->token,str)) + return 1; + return 0; +} + +int _pico_parse_int( picoParser_t *p, int *out ) +{ + char *token; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* get token and turn it into an integer */ + *out = 0; + token = _pico_parse( p,0 ); + if (token == NULL) return 0; + *out = atoi( token ); + + /* success */ + return 1; +} + +int _pico_parse_int_def( picoParser_t *p, int *out, int def ) +{ + char *token; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* get token and turn it into an integer */ + *out = def; + token = _pico_parse( p,0 ); + if (token == NULL) return 0; + *out = atoi( token ); + + /* success */ + return 1; +} + +int _pico_parse_float( picoParser_t *p, float *out ) +{ + char *token; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* get token and turn it into a float */ + *out = 0.0f; + token = _pico_parse( p,0 ); + if (token == NULL) return 0; + *out = (float) atof( token ); + + /* success */ + return 1; +} + +int _pico_parse_float_def( picoParser_t *p, float *out, float def ) +{ + char *token; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* get token and turn it into a float */ + *out = def; + token = _pico_parse( p,0 ); + if (token == NULL) return 0; + *out = (float) atof( token ); + + /* success */ + return 1; +} + +int _pico_parse_vec( picoParser_t *p, picoVec3_t out ) +{ + char *token; + int i; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* zero out outination vector */ + _pico_zero_vec( out ); + + /* parse three vector components */ + for (i=0; i<3; i++) + { + token = _pico_parse( p,0 ); + if (token == NULL) + { + _pico_zero_vec( out ); + return 0; + } + out[ i ] = (float) atof( token ); + } + /* success */ + return 1; +} + +int _pico_parse_vec_def( picoParser_t *p, picoVec3_t out, picoVec3_t def ) +{ + char *token; + int i; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* assign default vector value */ + _pico_copy_vec( def,out ); + + /* parse three vector components */ + for (i=0; i<3; i++) + { + token = _pico_parse( p,0 ); + if (token == NULL) + { + _pico_copy_vec( def,out ); + return 0; + } + out[ i ] = (float) atof( token ); + } + /* success */ + return 1; +} + +int _pico_parse_vec2( picoParser_t *p, picoVec2_t out ) +{ + char *token; + int i; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* zero out outination vector */ + _pico_zero_vec2( out ); + + /* parse two vector components */ + for (i=0; i<2; i++) + { + token = _pico_parse( p,0 ); + if (token == NULL) + { + _pico_zero_vec2( out ); + return 0; + } + out[ i ] = (float) atof( token ); + } + /* success */ + return 1; +} + +int _pico_parse_vec2_def( picoParser_t *p, picoVec2_t out, picoVec2_t def ) +{ + char *token; + int i; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* assign default vector value */ + _pico_copy_vec2( def,out ); + + /* parse two vector components */ + for (i=0; i<2; i++) + { + token = _pico_parse( p,0 ); + if (token == NULL) + { + _pico_copy_vec2( def,out ); + return 0; + } + out[ i ] = (float) atof( token ); + } + /* success */ + return 1; +} + +int _pico_parse_vec4( picoParser_t *p, picoVec4_t out ) +{ + char *token; + int i; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* zero out outination vector */ + _pico_zero_vec4( out ); + + /* parse four vector components */ + for (i=0; i<4; i++) + { + token = _pico_parse( p,0 ); + if (token == NULL) + { + _pico_zero_vec4( out ); + return 0; + } + out[ i ] = (float) atof( token ); + } + /* success */ + return 1; +} + +int _pico_parse_vec4_def( picoParser_t *p, picoVec4_t out, picoVec4_t def ) +{ + char *token; + int i; + + /* sanity checks */ + if (p == NULL || out == NULL) + return 0; + + /* assign default vector value */ + _pico_copy_vec4( def,out ); + + /* parse four vector components */ + for (i=0; i<4; i++) + { + token = _pico_parse( p,0 ); + if (token == NULL) + { + _pico_copy_vec4( def,out ); + return 0; + } + out[ i ] = (float) atof( token ); + } + /* success */ + return 1; +} + +/* _pico_new_memstream: + * allocates a new memorystream object. + */ +picoMemStream_t *_pico_new_memstream( picoByte_t *buffer, int bufSize ) +{ + picoMemStream_t *s; + + /* sanity check */ + if( buffer == NULL || bufSize <= 0 ) + return NULL; + + /* allocate stream */ + s = _pico_alloc( sizeof(picoMemStream_t) ); + if (s == NULL) return NULL; + memset( s,0,sizeof(picoMemStream_t) ); + + /* setup */ + s->buffer = buffer; + s->curPos = buffer; + s->bufSize = bufSize; + s->flag = 0; + + /* return ptr to stream */ + return s; +} + +/* _pico_free_memstream: + * frees an existing pico memorystream object. + */ +void _pico_free_memstream( picoMemStream_t *s ) +{ + /* sanity check */ + if (s == NULL) return; + + /* free the stream */ + _pico_free( s ); +} + +/* _pico_memstream_read: + * reads data from a pico memorystream into a buffer. + */ +int _pico_memstream_read( picoMemStream_t *s, void *buffer, int len ) +{ + int ret = 1; + + /* sanity checks */ + if (s == NULL || buffer == NULL) + return 0; + + if (s->curPos + len > s->buffer + s->bufSize) + { + s->flag |= PICO_IOEOF; + len = s->buffer + s->bufSize - s->curPos; + ret = 0; + } + + /* read the data */ + memcpy( buffer, s->curPos, len ); + s->curPos += len; + return ret; +} + +/* _pico_memstream_read: + * reads a character from a pico memorystream + */ +int _pico_memstream_getc( picoMemStream_t *s ) +{ + int c = 0; + + /* sanity check */ + if (s == NULL) + return -1; + + /* read the character */ + if (_pico_memstream_read( s, &c, 1) == 0) + return -1; + + return c; +} + +/* _pico_memstream_seek: + * sets the current read position to a different location + */ +int _pico_memstream_seek( picoMemStream_t *s, long offset, int origin ) +{ + int overflow; + + /* sanity check */ + if (s == NULL) + return -1; + + if (origin == PICO_SEEK_SET) + { + s->curPos = s->buffer + offset; + overflow = s->curPos - ( s->buffer + s->bufSize ); + if (overflow > 0) + { + s->curPos = s->buffer + s->bufSize; + return offset - overflow; + } + return 0; + } + else if (origin == PICO_SEEK_CUR) + { + s->curPos += offset; + overflow = s->curPos - ( s->buffer + s->bufSize ); + if (overflow > 0) + { + s->curPos = s->buffer + s->bufSize; + return offset - overflow; + } + return 0; + } + else if (origin == PICO_SEEK_END) + { + s->curPos = ( s->buffer + s->bufSize ) - offset; + overflow = s->buffer - s->curPos; + if (overflow > 0) + { + s->curPos = s->buffer; + return offset - overflow; + } + return 0; + } + + return -1; +} + +/* _pico_memstream_tell: + * returns the current read position in the pico memorystream + */ +long _pico_memstream_tell( picoMemStream_t *s ) +{ + /* sanity check */ + if (s == NULL) + return -1; + + return s->curPos - s->buffer; +} diff --git a/libs/picomodel/picointernal.h b/libs/picomodel/picointernal.h new file mode 100644 index 00000000..6ede9f60 --- /dev/null +++ b/libs/picomodel/picointernal.h @@ -0,0 +1,206 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef PICOINTERNAL_H +#define PICOINTERNAL_H + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/* dependencies */ +#include +#include +#include +#include +#include +#include + +#include "picomodel.h" + + +/* os dependent replacements */ +#if WIN32 || _WIN32 + #define _pico_stricmp stricmp + #define _pico_strnicmp strnicmp +#else + #define _pico_stricmp strcasecmp + #define _pico_strnicmp strncasecmp +#endif + + +/* constants */ +#define PICO_PI 3.14159265358979323846 + +#define PICO_SEEK_SET 0 +#define PICO_SEEK_CUR 1 +#define PICO_SEEK_END 2 + +#define PICO_IOEOF 1 +#define PICO_IOERR 2 + +/* types */ +typedef struct picoParser_s +{ + char *buffer; + int bufSize; + char *token; + int tokenSize; + int tokenMax; + char *cursor; + char *max; + int curLine; +} +picoParser_t; + +typedef struct picoMemStream_s +{ + picoByte_t *buffer; + int bufSize; + picoByte_t *curPos; + int flag; +} +picoMemStream_t; + + +/* variables */ +extern const picoModule_t *picoModules[]; + +extern void *(*_pico_ptr_malloc)( size_t ); +extern void (*_pico_ptr_free)( void* ); +extern void (*_pico_ptr_load_file)( char*, unsigned char**, int* ); +extern void (*_pico_ptr_free_file)( void* ); +extern void (*_pico_ptr_print)( int, const char* ); + + + +/* prototypes */ + +/* memory */ +void *_pico_alloc( size_t size ); +void *_pico_calloc( size_t num, size_t size ); +void *_pico_realloc( void **ptr, size_t oldSize, size_t newSize ); +char *_pico_clone_alloc( const char *str ); +void _pico_free( void *ptr ); + +/* files */ +void _pico_load_file( char *name, unsigned char **buffer, int *bufSize ); +void _pico_free_file( void *buffer ); + +/* strings */ +void _pico_first_token( char *str ); +char *_pico_strltrim( char *str ); +char *_pico_strrtrim( char *str ); +int _pico_strchcount( char *str, int ch ); +void _pico_printf( int level, const char *format, ... ); +char *_pico_stristr( char *str, const char *substr ); +void _pico_unixify( char *path ); +int _pico_nofname( const char *path, char *dest, int destSize ); +const char *_pico_nopath( const char *path ); +char *_pico_setfext( char *path, const char *ext ); +int _pico_getline( char *buf, int bufsize, char *dest, int destsize ); +char *_pico_strlwr( char *str ); + +/* vectors */ +void _pico_zero_bounds( picoVec3_t mins, picoVec3_t maxs ); +void _pico_expand_bounds( picoVec3_t p, picoVec3_t mins, picoVec3_t maxs ); +void _pico_zero_vec( picoVec3_t vec ); +void _pico_zero_vec2( picoVec2_t vec ); +void _pico_zero_vec4( picoVec4_t vec ); +void _pico_set_vec( picoVec3_t v, float a, float b, float c ); +void _pico_set_vec4( picoVec4_t v, float a, float b, float c, float d ); +void _pico_set_color( picoColor_t c, int r, int g, int b, int a ); +void _pico_copy_color( picoColor_t src, picoColor_t dest ); +void _pico_copy_vec( picoVec3_t src, picoVec3_t dest ); +void _pico_copy_vec2( picoVec2_t src, picoVec2_t dest ); +picoVec_t _pico_normalize_vec( picoVec3_t vec ); +void _pico_add_vec( picoVec3_t a, picoVec3_t b, picoVec3_t dest ); +void _pico_subtract_vec( picoVec3_t a, picoVec3_t b, picoVec3_t dest ); +picoVec_t _pico_dot_vec( picoVec3_t a, picoVec3_t b ); +void _pico_cross_vec( picoVec3_t a, picoVec3_t b, picoVec3_t dest ); +picoVec_t _pico_calc_plane( picoVec4_t plane, picoVec3_t a, picoVec3_t b, picoVec3_t c ); +void _pico_scale_vec( picoVec3_t v, float scale, picoVec3_t dest ); +void _pico_scale_vec4( picoVec4_t v, float scale, picoVec4_t dest ); + +/* endian */ +int _pico_big_long( int src ); +short _pico_big_short( short src ); +float _pico_big_float( float src ); + +int _pico_little_long( int src ); +short _pico_little_short( short src ); +float _pico_little_float( float src ); + +/* pico ascii parser */ +picoParser_t *_pico_new_parser( picoByte_t *buffer, int bufSize ); +void _pico_free_parser( picoParser_t *p ); +int _pico_parse_ex( picoParser_t *p, int allowLFs, int handleQuoted ); +char *_pico_parse_first( picoParser_t *p ); +char *_pico_parse( picoParser_t *p, int allowLFs ); +void _pico_parse_skip_rest( picoParser_t *p ); +int _pico_parse_skip_braced( picoParser_t *p ); +int _pico_parse_check( picoParser_t *p, int allowLFs, char *str ); +int _pico_parse_checki( picoParser_t *p, int allowLFs, char *str ); +int _pico_parse_int( picoParser_t *p, int *out ); +int _pico_parse_int_def( picoParser_t *p, int *out, int def ); +int _pico_parse_float( picoParser_t *p, float *out ); +int _pico_parse_float_def( picoParser_t *p, float *out, float def ); +int _pico_parse_vec( picoParser_t *p, picoVec3_t out); +int _pico_parse_vec_def( picoParser_t *p, picoVec3_t out, picoVec3_t def); +int _pico_parse_vec2( picoParser_t *p, picoVec2_t out ); +int _pico_parse_vec2_def( picoParser_t *p, picoVec2_t out, picoVec2_t def ); +int _pico_parse_vec4( picoParser_t *p, picoVec4_t out); +int _pico_parse_vec4_def( picoParser_t *p, picoVec4_t out, picoVec4_t def); + +/* pico memory stream */ +picoMemStream_t *_pico_new_memstream( picoByte_t *buffer, int bufSize ); +void _pico_free_memstream( picoMemStream_t *s ); +int _pico_memstream_read( picoMemStream_t *s, void *buffer, int len ); +int _pico_memstream_getc( picoMemStream_t *s ); +int _pico_memstream_seek( picoMemStream_t *s, long offset, int origin ); +long _pico_memstream_tell( picoMemStream_t *s ); +#define _pico_memstream_eof( _pico_memstream ) ((_pico_memstream)->flag & PICO_IOEOF) +#define _pico_memstream_error( _pico_memstream ) ((_pico_memstream)->flag & PICO_IOERR) + +/* end marker */ +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libs/picomodel/picomodel.c b/libs/picomodel/picomodel.c new file mode 100644 index 00000000..240d729b --- /dev/null +++ b/libs/picomodel/picomodel.c @@ -0,0 +1,2292 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PICOMODEL_C + + + +/* dependencies */ +#include "picointernal.h" + + + +/* +PicoInit() +initializes the picomodel library +*/ + +int PicoInit( void ) +{ + /* successfully initialized -sea */ + return 1; +} + + + +/* +PicoShutdown() +shuts the pico model library down +*/ + +void PicoShutdown( void ) +{ + /* do something interesting here in the future */ + return; +} + + + +/* +PicoError() +returns last picomodel error code (see PME_* defines) +*/ + +int PicoError( void ) +{ + /* todo: do something here */ + return 0; +} + + + +/* +PicoSetMallocFunc() +sets the ptr to the malloc function +*/ + +void PicoSetMallocFunc( void *(*func)( size_t ) ) +{ + if( func != NULL ) + _pico_ptr_malloc = func; +} + + + +/* +PicoSetFreeFunc() +sets the ptr to the free function +*/ + +void PicoSetFreeFunc( void (*func)( void* ) ) +{ + if( func != NULL ) + _pico_ptr_free = func; +} + + + +/* +PicoSetLoadFileFunc() +sets the ptr to the file load function +*/ + +void PicoSetLoadFileFunc( void (*func)( char*, unsigned char**, int* ) ) +{ + if( func != NULL ) + _pico_ptr_load_file = func; +} + + + +/* +PicoSetFreeFileFunc() +sets the ptr to the free function +*/ + +void PicoSetFreeFileFunc( void (*func)( void* ) ) +{ + if( func != NULL ) + _pico_ptr_free_file = func; +} + + + +/* +PicoSetPrintFunc() +sets the ptr to the print function +*/ + +void PicoSetPrintFunc( void (*func)( int, const char* ) ) +{ + if( func != NULL ) + _pico_ptr_print = func; +} + + + +picoModel_t *PicoModuleLoadModel( const picoModule_t* pm, char* fileName, picoByte_t* buffer, int bufSize, int frameNum ) +{ + char *modelFileName, *remapFileName; + + /* see whether this module can load the model file or not */ + if( pm->canload( fileName, buffer, bufSize ) == PICO_PMV_OK ) + { + /* use loader provided by module to read the model data */ + picoModel_t* model = pm->load( fileName, frameNum, buffer, bufSize ); + if( model == NULL ) + { + _pico_free_file( buffer ); + return NULL; + } + + /* assign pointer to file format module */ + model->module = pm; + + /* get model file name */ + modelFileName = PicoGetModelFileName( model ); + + /* apply model remappings from .remap */ + if( strlen( modelFileName ) ) + { + /* alloc copy of model file name */ + remapFileName = _pico_alloc( strlen( modelFileName ) + 20 ); + if( remapFileName != NULL ) + { + /* copy model file name and change extension */ + strcpy( remapFileName, modelFileName ); + _pico_setfext( remapFileName, "remap" ); + + /* try to remap model; we don't handle the result */ + PicoRemapModel( model, remapFileName ); + + /* free the remap file name string */ + _pico_free( remapFileName ); + } + } + + return model; + } + + return NULL; +} + +/* +PicoLoadModel() +the meat and potatoes function +*/ + +picoModel_t *PicoLoadModel( char *fileName, int frameNum ) +{ + const picoModule_t **modules, *pm; + picoModel_t *model; + picoByte_t *buffer; + int bufSize; + + + /* init */ + model = NULL; + + /* make sure we've got a file name */ + if( fileName == NULL ) + { + _pico_printf( PICO_ERROR, "PicoLoadModel: No filename given (fileName == NULL)" ); + return NULL; + } + + /* load file data (buffer is allocated by host app) */ + _pico_load_file( fileName, &buffer, &bufSize ); + if( bufSize < 0 ) + { + _pico_printf( PICO_ERROR, "PicoLoadModel: Failed loading model %s", fileName ); + return NULL; + } + + /* get ptr to list of supported modules */ + modules = PicoModuleList( NULL ); + + /* run it through the various loader functions and try */ + /* to find a loader that fits the given file data */ + for( ; *modules != NULL; modules++ ) + { + /* get module */ + pm = *modules; + + /* sanity check */ + if( pm == NULL) + break; + + /* module must be able to load */ + if( pm->canload == NULL || pm->load == NULL ) + continue; + + model = PicoModuleLoadModel(pm, fileName, buffer, bufSize, frameNum); + if(model != NULL) + { + /* model was loaded, so break out of loop */ + break; + } + } + + /* free memory used by file buffer */ + if( buffer) + _pico_free_file( buffer ); + + /* return */ + return model; +} + +picoModel_t *PicoModuleLoadModelStream( const picoModule_t* module, void* inputStream, PicoInputStreamReadFunc inputStreamRead, size_t streamLength, int frameNum ) +{ + picoModel_t *model; + picoByte_t *buffer; + int bufSize; + + + /* init */ + model = NULL; + + if( inputStream == NULL ) + { + _pico_printf( PICO_ERROR, "PicoLoadModel: invalid input stream (inputStream == NULL)" ); + return NULL; + } + + if( inputStreamRead == NULL ) + { + _pico_printf( PICO_ERROR, "PicoLoadModel: invalid input stream (inputStreamRead == NULL)" ); + return NULL; + } + + buffer = _pico_alloc(streamLength + 1); + + bufSize = (int)inputStreamRead(inputStream, buffer, streamLength); + buffer[bufSize] = '\0'; + + { + // dummy filename + char fileName[128]; + fileName[0] = '.'; + strncpy(fileName + 1, module->defaultExts[0], 126); + fileName[127] = '\0'; + model = PicoModuleLoadModel(module, fileName, buffer, bufSize, frameNum); + } + + if(model != 0) + { + _pico_free(buffer); + } + + /* return */ + return model; +} + + +/* ---------------------------------------------------------------------------- +models +---------------------------------------------------------------------------- */ + +/* +PicoNewModel() +creates a new pico model +*/ + +picoModel_t *PicoNewModel( void ) +{ + picoModel_t *model; + + /* allocate */ + model = _pico_alloc( sizeof(picoModel_t) ); + if( model == NULL ) + return NULL; + + /* clear */ + memset( model,0,sizeof(picoModel_t) ); + + /* model set up */ + _pico_zero_bounds( model->mins,model->maxs ); + + /* set initial frame count to 1 -sea */ + model->numFrames = 1; + + /* return ptr to new model */ + return model; +} + + + +/* +PicoFreeModel() +frees a model and all associated data +*/ + +void PicoFreeModel( picoModel_t *model ) +{ + int i; + + + /* sanity check */ + if( model == NULL ) + return; + + /* free bits */ + if( model->name ) + _pico_free( model->name ); + + if( model->fileName ) + _pico_free( model->fileName ); + + /* free shaders */ + for( i = 0; i < model->numShaders; i++ ) + PicoFreeShader( model->shader[ i ] ); + free( model->shader ); + + /* free surfaces */ + for( i = 0; i < model->numSurfaces; i++ ) + PicoFreeSurface( model->surface[ i ] ); + free( model->surface ); + + /* free the model */ + _pico_free( model ); +} + + + +/* +PicoAdjustModel() +adjusts a models's memory allocations to handle the requested sizes. +will always grow, never shrink +*/ + +int PicoAdjustModel( picoModel_t *model, int numShaders, int numSurfaces ) +{ + /* dummy check */ + if( model == NULL ) + return 0; + + /* bare minimums */ + /* sea: null surface/shader fix (1s=>0s) */ + if( numShaders < 0 ) + numShaders = 0; + if( numSurfaces < 0 ) + numSurfaces = 0; + + /* additional shaders? */ + while( numShaders > model->maxShaders ) + { + model->maxShaders += PICO_GROW_SHADERS; + if( !_pico_realloc( (void *) &model->shader, model->numShaders * sizeof( *model->shader ), model->maxShaders * sizeof( *model->shader ) ) ) + return 0; + } + + /* set shader count to higher */ + if( numShaders > model->numShaders ) + model->numShaders = numShaders; + + /* additional surfaces? */ + while( numSurfaces > model->maxSurfaces ) + { + model->maxSurfaces += PICO_GROW_SURFACES; + if( !_pico_realloc( (void *) &model->surface, model->numSurfaces * sizeof( *model->surface ), model->maxSurfaces * sizeof( *model->surface ) ) ) + return 0; + } + + /* set shader count to higher */ + if( numSurfaces > model->numSurfaces ) + model->numSurfaces = numSurfaces; + + /* return ok */ + return 1; +} + + + +/* ---------------------------------------------------------------------------- +shaders +---------------------------------------------------------------------------- */ + +/* +PicoNewShader() +creates a new pico shader and returns its index. -sea +*/ + +picoShader_t *PicoNewShader( picoModel_t *model ) +{ + picoShader_t *shader; + + + /* allocate and clear */ + shader = _pico_alloc( sizeof(picoShader_t) ); + if( shader == NULL ) + return NULL; + memset( shader, 0, sizeof(picoShader_t) ); + + /* attach it to the model */ + if( model != NULL ) + { + /* adjust model */ + if( !PicoAdjustModel( model, model->numShaders + 1, 0 ) ) + { + _pico_free( shader ); + return NULL; + } + + /* attach */ + model->shader[ model->numShaders - 1 ] = shader; + shader->model = model; + } + + /* setup default shader colors */ + _pico_set_color( shader->ambientColor,0,0,0,0 ); + _pico_set_color( shader->diffuseColor,255,255,255,1 ); + _pico_set_color( shader->specularColor,0,0,0,0 ); + + /* no need to do this, but i do it anyway */ + shader->transparency = 0; + shader->shininess = 0; + + /* return the newly created shader */ + return shader; +} + + + +/* +PicoFreeShader() +frees a shader and all associated data -sea +*/ + +void PicoFreeShader( picoShader_t *shader ) +{ + /* dummy check */ + if( shader == NULL ) + return; + + /* free bits */ + if( shader->name ) + _pico_free( shader->name ); + if( shader->mapName ) + _pico_free( shader->mapName ); + + /* free the shader */ + _pico_free( shader ); +} + + + +/* +PicoFindShader() +finds a named shader in a model +*/ + +picoShader_t *PicoFindShader( picoModel_t *model, char *name, int caseSensitive ) +{ + int i; + + + /* sanity checks */ + if( model == NULL || name == NULL ) /* sea: null name fix */ + return NULL; + + /* walk list */ + for( i = 0; i < model->numShaders; i++ ) + { + /* skip null shaders or shaders with null names */ + if( model->shader[ i ] == NULL || + model->shader[ i ]->name == NULL ) + continue; + + /* compare the shader name with name we're looking for */ + if( caseSensitive ) + { + if( !strcmp( name, model->shader[ i ]->name ) ) + return model->shader[ i ]; + } + else if( !_pico_stricmp( name, model->shader[ i ]->name ) ) + return model->shader[ i ]; + } + + /* named shader not found */ + return NULL; +} + + + +/* ---------------------------------------------------------------------------- +surfaces +---------------------------------------------------------------------------- */ + +/* +PicoNewSurface() +creates a new pico surface +*/ + +picoSurface_t *PicoNewSurface( picoModel_t *model ) +{ + picoSurface_t *surface; + char surfaceName[64]; + + /* allocate and clear */ + surface = _pico_alloc( sizeof( *surface ) ); + if( surface == NULL ) + return NULL; + memset( surface, 0, sizeof( *surface ) ); + + /* attach it to the model */ + if( model != NULL ) + { + /* adjust model */ + if( !PicoAdjustModel( model, 0, model->numSurfaces + 1 ) ) + { + _pico_free( surface ); + return NULL; + } + + /* attach */ + model->surface[ model->numSurfaces - 1 ] = surface; + surface->model = model; + + /* set default name */ + sprintf( surfaceName, "Unnamed_%d", model->numSurfaces ); + PicoSetSurfaceName( surface, surfaceName ); + } + + /* return */ + return surface; +} + + + +/* +PicoFreeSurface() +frees a surface and all associated data +*/ +void PicoFreeSurface( picoSurface_t *surface ) +{ + int i; + + + /* dummy check */ + if( surface == NULL ) + return; + + /* free bits */ + _pico_free( surface->xyz ); + _pico_free( surface->normal ); + _pico_free( surface->smoothingGroup ); + _pico_free( surface->index ); + _pico_free( surface->faceNormal ); + + if( surface->name ) + _pico_free( surface->name ); + + /* free arrays */ + for( i = 0; i < surface->numSTArrays; i++ ) + _pico_free( surface->st[ i ] ); + free( surface->st ); + for( i = 0; i < surface->numColorArrays; i++ ) + _pico_free( surface->color[ i ] ); + free( surface->color ); + + /* free the surface */ + _pico_free( surface ); +} + + + +/* +PicoAdjustSurface() +adjusts a surface's memory allocations to handle the requested sizes. +will always grow, never shrink +*/ + +int PicoAdjustSurface( picoSurface_t *surface, int numVertexes, int numSTArrays, int numColorArrays, int numIndexes, int numFaceNormals ) +{ + int i; + + + /* dummy check */ + if( surface == NULL ) + return 0; + + /* bare minimums */ + if( numVertexes < 1 ) + numVertexes = 1; + if( numSTArrays < 1 ) + numSTArrays = 1; + if( numColorArrays < 1 ) + numColorArrays = 1; + if( numIndexes < 1 ) + numIndexes = 1; + + /* additional vertexes? */ + while( numVertexes > surface->maxVertexes ) /* fix */ + { + surface->maxVertexes += PICO_GROW_VERTEXES; + if( !_pico_realloc( (void *) &surface->xyz, surface->numVertexes * sizeof( *surface->xyz ), surface->maxVertexes * sizeof( *surface->xyz ) ) ) + return 0; + if( !_pico_realloc( (void *) &surface->normal, surface->numVertexes * sizeof( *surface->normal ), surface->maxVertexes * sizeof( *surface->normal ) ) ) + return 0; + if( !_pico_realloc( (void *) &surface->smoothingGroup, surface->numVertexes * sizeof( *surface->smoothingGroup ), surface->maxVertexes * sizeof( *surface->smoothingGroup ) ) ) + return 0; + for( i = 0; i < surface->numSTArrays; i++ ) + if( !_pico_realloc( (void*) &surface->st[ i ], surface->numVertexes * sizeof( *surface->st[ i ] ), surface->maxVertexes * sizeof( *surface->st[ i ] ) ) ) + return 0; + for( i = 0; i < surface->numColorArrays; i++ ) + if( !_pico_realloc( (void*) &surface->color[ i ], surface->numVertexes * sizeof( *surface->color[ i ] ), surface->maxVertexes * sizeof( *surface->color[ i ] ) ) ) + return 0; + } + + /* set vertex count to higher */ + if( numVertexes > surface->numVertexes ) + surface->numVertexes = numVertexes; + + /* additional st arrays? */ + while( numSTArrays > surface->maxSTArrays ) /* fix */ + { + surface->maxSTArrays += PICO_GROW_ARRAYS; + if( !_pico_realloc( (void*) &surface->st, surface->numSTArrays * sizeof( *surface->st ), surface->maxSTArrays * sizeof( *surface->st ) ) ) + return 0; + while( surface->numSTArrays < numSTArrays ) + { + surface->st[ surface->numSTArrays ] = _pico_alloc( surface->maxVertexes * sizeof( *surface->st[ 0 ] ) ); + memset( surface->st[ surface->numSTArrays ], 0, surface->maxVertexes * sizeof( *surface->st[ 0 ] ) ); + surface->numSTArrays++; + } + } + + /* additional color arrays? */ + while( numColorArrays > surface->maxColorArrays ) /* fix */ + { + surface->maxColorArrays += PICO_GROW_ARRAYS; + if( !_pico_realloc( (void*) &surface->color, surface->numColorArrays * sizeof( *surface->color ), surface->maxColorArrays * sizeof( *surface->color ) ) ) + return 0; + while( surface->numColorArrays < numColorArrays ) + { + surface->color[ surface->numColorArrays ] = _pico_alloc( surface->maxVertexes * sizeof( *surface->color[ 0 ] ) ); + memset( surface->color[ surface->numColorArrays ], 0, surface->maxVertexes * sizeof( *surface->color[ 0 ] ) ); + surface->numColorArrays++; + } + } + + /* additional indexes? */ + while( numIndexes > surface->maxIndexes ) /* fix */ + { + surface->maxIndexes += PICO_GROW_INDEXES; + if( !_pico_realloc( (void*) &surface->index, surface->numIndexes * sizeof( *surface->index ), surface->maxIndexes * sizeof( *surface->index ) ) ) + return 0; + } + + /* set index count to higher */ + if( numIndexes > surface->numIndexes ) + surface->numIndexes = numIndexes; + + /* additional face normals? */ + while( numFaceNormals > surface->maxFaceNormals ) /* fix */ + { + surface->maxFaceNormals += PICO_GROW_FACES; + if( !_pico_realloc( (void *) &surface->faceNormal, surface->numFaceNormals * sizeof( *surface->faceNormal ), surface->maxFaceNormals * sizeof( *surface->faceNormal ) ) ) + return 0; + } + + /* set face normal count to higher */ + if( numFaceNormals > surface->numFaceNormals ) + surface->numFaceNormals = numFaceNormals; + + /* return ok */ + return 1; +} + + +/* PicoFindSurface: + * Finds first matching named surface in a model. + */ +picoSurface_t *PicoFindSurface( + picoModel_t *model, char *name, int caseSensitive ) +{ + int i; + + /* sanity check */ + if( model == NULL || name == NULL ) + return NULL; + + /* walk list */ + for( i = 0; i < model->numSurfaces; i++ ) + { + /* skip null surfaces or surfaces with null names */ + if( model->surface[ i ] == NULL || + model->surface[ i ]->name == NULL ) + continue; + + /* compare the surface name with name we're looking for */ + if (caseSensitive) { + if( !strcmp(name,model->surface[ i ]->name) ) + return model->surface[ i ]; + } else { + if( !_pico_stricmp(name,model->surface[ i ]->name) ) + return model->surface[ i ]; + } + } + /* named surface not found */ + return NULL; +} + + + +/*---------------------------------------------------------------------------- + PicoSet*() Setter Functions +----------------------------------------------------------------------------*/ + +void PicoSetModelName( picoModel_t *model, char *name ) +{ + if( model == NULL || name == NULL ) + return; + if( model->name != NULL ) + _pico_free( model->name ); + + model->name = _pico_clone_alloc( name ); +} + + + +void PicoSetModelFileName( picoModel_t *model, char *fileName ) +{ + if( model == NULL || fileName == NULL ) + return; + if( model->fileName != NULL ) + _pico_free( model->fileName ); + + model->fileName = _pico_clone_alloc( fileName ); +} + + + +void PicoSetModelFrameNum( picoModel_t *model, int frameNum ) +{ + if( model == NULL ) + return; + model->frameNum = frameNum; +} + + + +void PicoSetModelNumFrames( picoModel_t *model, int numFrames ) +{ + if( model == NULL ) + return; + model->numFrames = numFrames; +} + + + +void PicoSetModelData( picoModel_t *model, void *data ) +{ + if( model == NULL ) + return; + model->data = data; +} + + + +void PicoSetShaderName( picoShader_t *shader, char *name ) +{ + if( shader == NULL || name == NULL ) + return; + if( shader->name != NULL ) + _pico_free( shader->name ); + + shader->name = _pico_clone_alloc( name ); +} + + + +void PicoSetShaderMapName( picoShader_t *shader, char *mapName ) +{ + if( shader == NULL || mapName == NULL ) + return; + if( shader->mapName != NULL ) + _pico_free( shader->mapName ); + + shader->mapName = _pico_clone_alloc( mapName ); +} + + + +void PicoSetShaderAmbientColor( picoShader_t *shader, picoColor_t color ) +{ + if( shader == NULL || color == NULL ) + return; + shader->ambientColor[ 0 ] = color[ 0 ]; + shader->ambientColor[ 1 ] = color[ 1 ]; + shader->ambientColor[ 2 ] = color[ 2 ]; + shader->ambientColor[ 3 ] = color[ 3 ]; +} + + + +void PicoSetShaderDiffuseColor( picoShader_t *shader, picoColor_t color ) +{ + if( shader == NULL || color == NULL ) + return; + shader->diffuseColor[ 0 ] = color[ 0 ]; + shader->diffuseColor[ 1 ] = color[ 1 ]; + shader->diffuseColor[ 2 ] = color[ 2 ]; + shader->diffuseColor[ 3 ] = color[ 3 ]; +} + + + +void PicoSetShaderSpecularColor( picoShader_t *shader, picoColor_t color ) +{ + if( shader == NULL || color == NULL ) + return; + shader->specularColor[ 0 ] = color[ 0 ]; + shader->specularColor[ 1 ] = color[ 1 ]; + shader->specularColor[ 2 ] = color[ 2 ]; + shader->specularColor[ 3 ] = color[ 3 ]; +} + + + +void PicoSetShaderTransparency( picoShader_t *shader, float value ) +{ + if( shader == NULL ) + return; + shader->transparency = value; + + /* cap to 0..1 range */ + if (shader->transparency < 0.0) + shader->transparency = 0.0; + if (shader->transparency > 1.0) + shader->transparency = 1.0; +} + + + +void PicoSetShaderShininess( picoShader_t *shader, float value ) +{ + if( shader == NULL ) + return; + shader->shininess = value; + + /* cap to 0..127 range */ + if (shader->shininess < 0.0) + shader->shininess = 0.0; + if (shader->shininess > 127.0) + shader->shininess = 127.0; +} + + + +void PicoSetSurfaceData( picoSurface_t *surface, void *data ) +{ + if( surface == NULL ) + return; + surface->data = data; +} + + + +void PicoSetSurfaceType( picoSurface_t *surface, picoSurfaceType_t type ) +{ + if( surface == NULL ) + return; + surface->type = type; +} + + + +void PicoSetSurfaceName( picoSurface_t *surface, char *name ) +{ + if( surface == NULL || name == NULL ) + return; + if( surface->name != NULL ) + _pico_free( surface->name ); + + surface->name = _pico_clone_alloc( name ); +} + + + +void PicoSetSurfaceShader( picoSurface_t *surface, picoShader_t *shader ) +{ + if( surface == NULL ) + return; + surface->shader = shader; +} + + + +void PicoSetSurfaceXYZ( picoSurface_t *surface, int num, picoVec3_t xyz ) +{ + if( surface == NULL || num < 0 || xyz == NULL ) + return; + if( !PicoAdjustSurface( surface, num + 1, 0, 0, 0, 0 ) ) + return; + _pico_copy_vec( xyz, surface->xyz[ num ] ); + if( surface->model != NULL ) + _pico_expand_bounds( xyz, surface->model->mins, surface->model->maxs ); +} + + + +void PicoSetSurfaceNormal( picoSurface_t *surface, int num, picoVec3_t normal ) +{ + if( surface == NULL || num < 0 || normal == NULL ) + return; + if( !PicoAdjustSurface( surface, num + 1, 0, 0, 0, 0 ) ) + return; + _pico_copy_vec( normal, surface->normal[ num ] ); +} + + + +void PicoSetSurfaceST( picoSurface_t *surface, int array, int num, picoVec2_t st ) +{ + if( surface == NULL || num < 0 || st == NULL ) + return; + if( !PicoAdjustSurface( surface, num + 1, array + 1, 0, 0, 0 ) ) + return; + surface->st[ array ][ num ][ 0 ] = st[ 0 ]; + surface->st[ array ][ num ][ 1 ] = st[ 1 ]; +} + + + +void PicoSetSurfaceColor( picoSurface_t *surface, int array, int num, picoColor_t color ) +{ + if( surface == NULL || num < 0 || color == NULL ) + return; + if( !PicoAdjustSurface( surface, num + 1, 0, array + 1, 0, 0 ) ) + return; + surface->color[ array ][ num ][ 0 ] = color[ 0 ]; + surface->color[ array ][ num ][ 1 ] = color[ 1 ]; + surface->color[ array ][ num ][ 2 ] = color[ 2 ]; + surface->color[ array ][ num ][ 3 ] = color[ 3 ]; +} + + + +void PicoSetSurfaceIndex( picoSurface_t *surface, int num, picoIndex_t index ) +{ + if( surface == NULL || num < 0 ) + return; + if( !PicoAdjustSurface( surface, 0, 0, 0, num + 1, 0 ) ) + return; + surface->index[ num ] = index; +} + + + +void PicoSetSurfaceIndexes( picoSurface_t *surface, int num, picoIndex_t *index, int count ) +{ + if( num < 0 || index == NULL || count < 1 ) + return; + if( !PicoAdjustSurface( surface, 0, 0, 0, num + count, 0 ) ) + return; + memcpy( &surface->index[ num ], index, count * sizeof( surface->index[ num ] ) ); +} + + + +void PicoSetFaceNormal( picoSurface_t *surface, int num, picoVec3_t normal ) +{ + if( surface == NULL || num < 0 || normal == NULL ) + return; + if( !PicoAdjustSurface( surface, 0, 0, 0, 0, num + 1 ) ) + return; + _pico_copy_vec( normal, surface->faceNormal[ num ] ); +} + + +void PicoSetSurfaceSmoothingGroup( picoSurface_t *surface, int num, picoIndex_t smoothingGroup ) +{ + if( num < 0 ) + return; + if( !PicoAdjustSurface( surface, num + 1, 0, 0, 0, 0 ) ) + return; + surface->smoothingGroup[ num ] = smoothingGroup; +} + + +void PicoSetSurfaceSpecial( picoSurface_t *surface, int num, int special ) +{ + if( surface == NULL || num < 0 || num >= PICO_MAX_SPECIAL ) + return; + surface->special[ num ] = special; +} + + + +/*---------------------------------------------------------------------------- + PicoGet*() Getter Functions +----------------------------------------------------------------------------*/ + +char *PicoGetModelName( picoModel_t *model ) +{ + if( model == NULL ) + return NULL; + if( model->name == NULL) + return (char*) ""; + return model->name; +} + + + +char *PicoGetModelFileName( picoModel_t *model ) +{ + if( model == NULL ) + return NULL; + if( model->fileName == NULL) + return (char*) ""; + return model->fileName; +} + + + +int PicoGetModelFrameNum( picoModel_t *model ) +{ + if( model == NULL ) + return 0; + return model->frameNum; +} + + + +int PicoGetModelNumFrames( picoModel_t *model ) +{ + if( model == NULL ) + return 0; + return model->numFrames; +} + + + +void *PicoGetModelData( picoModel_t *model ) +{ + if( model == NULL ) + return NULL; + return model->data; +} + + + +int PicoGetModelNumShaders( picoModel_t *model ) +{ + if( model == NULL ) + return 0; + return model->numShaders; +} + + + +picoShader_t *PicoGetModelShader( picoModel_t *model, int num ) +{ + /* a few sanity checks */ + if( model == NULL ) + return NULL; + if( model->shader == NULL) + return NULL; + if( num < 0 || num >= model->numShaders ) + return NULL; + + /* return the shader */ + return model->shader[ num ]; +} + + + +int PicoGetModelNumSurfaces( picoModel_t *model ) +{ + if( model == NULL ) + return 0; + return model->numSurfaces; +} + + + +picoSurface_t *PicoGetModelSurface( picoModel_t *model, int num ) +{ + /* a few sanity checks */ + if( model == NULL ) + return NULL; + if( model->surface == NULL) + return NULL; + if( num < 0 || num >= model->numSurfaces ) + return NULL; + + /* return the surface */ + return model->surface[ num ]; +} + + + +int PicoGetModelTotalVertexes( picoModel_t *model ) +{ + int i, count; + + + if( model == NULL ) + return 0; + if( model->surface == NULL ) + return 0; + + count = 0; + for( i = 0; i < model->numSurfaces; i++ ) + count += PicoGetSurfaceNumVertexes( model->surface[ i ] ); + + return count; +} + + + +int PicoGetModelTotalIndexes( picoModel_t *model ) +{ + int i, count; + + + if( model == NULL ) + return 0; + if( model->surface == NULL ) + return 0; + + count = 0; + for( i = 0; i < model->numSurfaces; i++ ) + count += PicoGetSurfaceNumIndexes( model->surface[ i ] ); + + return count; +} + + + +char *PicoGetShaderName( picoShader_t *shader ) +{ + if( shader == NULL ) + return NULL; + if( shader->name == NULL) + return (char*) ""; + return shader->name; +} + + + +char *PicoGetShaderMapName( picoShader_t *shader ) +{ + if( shader == NULL ) + return NULL; + if( shader->mapName == NULL) + return (char*) ""; + return shader->mapName; +} + + + +picoByte_t *PicoGetShaderAmbientColor( picoShader_t *shader ) +{ + if( shader == NULL ) + return NULL; + return shader->ambientColor; +} + + + +picoByte_t *PicoGetShaderDiffuseColor( picoShader_t *shader ) +{ + if( shader == NULL ) + return NULL; + return shader->diffuseColor; +} + + + +picoByte_t *PicoGetShaderSpecularColor( picoShader_t *shader ) +{ + if( shader == NULL ) + return NULL; + return shader->specularColor; +} + + + +float PicoGetShaderTransparency( picoShader_t *shader ) +{ + if( shader == NULL ) + return 0.0f; + return shader->transparency; +} + + + +float PicoGetShaderShininess( picoShader_t *shader ) +{ + if( shader == NULL ) + return 0.0f; + return shader->shininess; +} + + + +void *PicoGetSurfaceData( picoSurface_t *surface ) +{ + if( surface == NULL ) + return NULL; + return surface->data; +} + + + +picoSurfaceType_t PicoGetSurfaceType( picoSurface_t *surface ) +{ + if( surface == NULL ) + return PICO_BAD; + return surface->type; +} + + + +char *PicoGetSurfaceName( picoSurface_t *surface ) +{ + if( surface == NULL ) + return NULL; + if( surface->name == NULL ) + return (char*) ""; + return surface->name; +} + + + +picoShader_t *PicoGetSurfaceShader( picoSurface_t *surface ) +{ + if( surface == NULL ) + return NULL; + return surface->shader; +} + + + +int PicoGetSurfaceNumVertexes( picoSurface_t *surface ) +{ + if( surface == NULL ) + return 0; + return surface->numVertexes; +} + + + +picoVec_t *PicoGetSurfaceXYZ( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num > surface->numVertexes ) + return NULL; + return surface->xyz[ num ]; +} + + + +picoVec_t *PicoGetSurfaceNormal( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num > surface->numVertexes ) + return NULL; + return surface->normal[ num ]; +} + + + +picoVec_t *PicoGetSurfaceST( picoSurface_t *surface, int array, int num ) +{ + if( surface == NULL || array < 0 || array > surface->numSTArrays || num < 0 || num > surface->numVertexes ) + return NULL; + return surface->st[ array ][ num ]; +} + + + +picoByte_t *PicoGetSurfaceColor( picoSurface_t *surface, int array, int num ) +{ + if( surface == NULL || array < 0 || array > surface->numColorArrays || num < 0 || num > surface->numVertexes ) + return NULL; + return surface->color[ array ][ num ]; +} + + + +int PicoGetSurfaceNumIndexes( picoSurface_t *surface ) +{ + if( surface == NULL ) + return 0; + return surface->numIndexes; +} + + + +picoIndex_t PicoGetSurfaceIndex( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num > surface->numIndexes ) + return 0; + return surface->index[ num ]; +} + + + +picoIndex_t *PicoGetSurfaceIndexes( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num > surface->numIndexes ) + return NULL; + return &surface->index[ num ]; +} + + +picoVec_t *PicoGetFaceNormal( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num > surface->numFaceNormals ) + return NULL; + return surface->faceNormal[ num ]; +} + +picoIndex_t PicoGetSurfaceSmoothingGroup( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num > surface->numVertexes ) + return -1; + return surface->smoothingGroup[ num ]; +} + + +int PicoGetSurfaceSpecial( picoSurface_t *surface, int num ) +{ + if( surface == NULL || num < 0 || num >= PICO_MAX_SPECIAL ) + return 0; + return surface->special[ num ]; +} + + + +/* ---------------------------------------------------------------------------- +hashtable related functions +---------------------------------------------------------------------------- */ + +/* hashtable code for faster vertex lookups */ +//#define HASHTABLE_SIZE 32768 // 2048 /* power of 2, use & */ +#define HASHTABLE_SIZE 7919 // 32749 // 2039 /* prime, use % */ + +int PicoGetHashTableSize( void ) +{ + return HASHTABLE_SIZE; +} + +#define HASH_USE_EPSILON + +#ifdef HASH_USE_EPSILON +#define HASH_XYZ_EPSILON 0.01f +#define HASH_XYZ_EPSILONSPACE_MULTIPLIER 1.f / HASH_XYZ_EPSILON +#define HASH_ST_EPSILON 0.0001f +#define HASH_NORMAL_EPSILON 0.02f +#endif + +unsigned int PicoVertexCoordGenerateHash( picoVec3_t xyz ) +{ + unsigned int hash = 0; + +#ifndef HASH_USE_EPSILON + hash += ~(*((unsigned int*) &xyz[ 0 ]) << 15); + hash ^= (*((unsigned int*) &xyz[ 0 ]) >> 10); + hash += (*((unsigned int*) &xyz[ 1 ]) << 3); + hash ^= (*((unsigned int*) &xyz[ 1 ]) >> 6); + hash += ~(*((unsigned int*) &xyz[ 2 ]) << 11); + hash ^= (*((unsigned int*) &xyz[ 2 ]) >> 16); +#else + picoVec3_t xyz_epsilonspace; + + _pico_scale_vec( xyz, HASH_XYZ_EPSILONSPACE_MULTIPLIER, xyz_epsilonspace ); + xyz_epsilonspace[ 0 ] = (float)floor(xyz_epsilonspace[ 0 ]); + xyz_epsilonspace[ 1 ] = (float)floor(xyz_epsilonspace[ 1 ]); + xyz_epsilonspace[ 2 ] = (float)floor(xyz_epsilonspace[ 2 ]); + + hash += ~(*((unsigned int*) &xyz_epsilonspace[ 0 ]) << 15); + hash ^= (*((unsigned int*) &xyz_epsilonspace[ 0 ]) >> 10); + hash += (*((unsigned int*) &xyz_epsilonspace[ 1 ]) << 3); + hash ^= (*((unsigned int*) &xyz_epsilonspace[ 1 ]) >> 6); + hash += ~(*((unsigned int*) &xyz_epsilonspace[ 2 ]) << 11); + hash ^= (*((unsigned int*) &xyz_epsilonspace[ 2 ]) >> 16); +#endif + + //hash = hash & (HASHTABLE_SIZE-1); + hash = hash % (HASHTABLE_SIZE); + return hash; +} + +picoVertexCombinationHash_t **PicoNewVertexCombinationHashTable( void ) +{ + picoVertexCombinationHash_t **hashTable = _pico_alloc( HASHTABLE_SIZE * sizeof(picoVertexCombinationHash_t*) ); + + memset( hashTable, 0, HASHTABLE_SIZE * sizeof(picoVertexCombinationHash_t*) ); + + return hashTable; +} + +void PicoFreeVertexCombinationHashTable( picoVertexCombinationHash_t **hashTable ) +{ + int i; + picoVertexCombinationHash_t *vertexCombinationHash; + picoVertexCombinationHash_t *nextVertexCombinationHash; + + /* dummy check */ + if (hashTable == NULL) + return; + + for( i = 0; i < HASHTABLE_SIZE; i++ ) + { + if (hashTable[ i ]) + { + nextVertexCombinationHash = NULL; + + for( vertexCombinationHash = hashTable[ i ]; vertexCombinationHash; vertexCombinationHash = nextVertexCombinationHash ) + { + nextVertexCombinationHash = vertexCombinationHash->next; + if (vertexCombinationHash->data != NULL) + { + _pico_free( vertexCombinationHash->data ); + } + _pico_free( vertexCombinationHash ); + } + } + } + + _pico_free( hashTable ); +} + +picoVertexCombinationHash_t *PicoFindVertexCombinationInHashTable( picoVertexCombinationHash_t **hashTable, picoVec3_t xyz, picoVec3_t normal, picoVec3_t st, picoColor_t color ) +{ + unsigned int hash; + picoVertexCombinationHash_t *vertexCombinationHash; + + /* dumy check */ + if (hashTable == NULL || xyz == NULL || normal == NULL || st == NULL || color == NULL ) + return NULL; + + hash = PicoVertexCoordGenerateHash( xyz ); + + for( vertexCombinationHash = hashTable[ hash ]; vertexCombinationHash; vertexCombinationHash = vertexCombinationHash->next ) + { +#ifndef HASH_USE_EPSILON + /* check xyz */ + if( (vertexCombinationHash->vcd.xyz[ 0 ] != xyz[ 0 ] || vertexCombinationHash->vcd.xyz[ 1 ] != xyz[ 1 ] || vertexCombinationHash->vcd.xyz[ 2 ] != xyz[ 2 ]) ) + continue; + + /* check normal */ + if( (vertexCombinationHash->vcd.normal[ 0 ] != normal[ 0 ] || vertexCombinationHash->vcd.normal[ 1 ] != normal[ 1 ] || vertexCombinationHash->vcd.normal[ 2 ] != normal[ 2 ]) ) + continue; + + /* check st */ + if( vertexCombinationHash->vcd.st[ 0 ] != st[ 0 ] || vertexCombinationHash->vcd.st[ 1 ] != st[ 1 ] ) + continue; +#else + /* check xyz */ + if( ( fabs(xyz[ 0 ] - vertexCombinationHash->vcd.xyz[ 0 ]) ) > HASH_XYZ_EPSILON || + ( fabs(xyz[ 1 ] - vertexCombinationHash->vcd.xyz[ 1 ]) ) > HASH_XYZ_EPSILON || + ( fabs(xyz[ 2 ] - vertexCombinationHash->vcd.xyz[ 2 ]) ) > HASH_XYZ_EPSILON ) + continue; + + /* check normal */ + if( ( fabs(normal[ 0 ] - vertexCombinationHash->vcd.normal[ 0 ]) ) > HASH_NORMAL_EPSILON || + ( fabs(normal[ 1 ] - vertexCombinationHash->vcd.normal[ 1 ]) ) > HASH_NORMAL_EPSILON || + ( fabs(normal[ 2 ] - vertexCombinationHash->vcd.normal[ 2 ]) ) > HASH_NORMAL_EPSILON ) + continue; + + /* check st */ + if( ( fabs(st[ 0 ] - vertexCombinationHash->vcd.st[ 0 ]) ) > HASH_ST_EPSILON || + ( fabs(st[ 1 ] - vertexCombinationHash->vcd.st[ 1 ]) ) > HASH_ST_EPSILON ) + continue; +#endif + + /* check color */ + if( *((int*) vertexCombinationHash->vcd.color) != *((int*) color) ) + continue; + + /* gotcha */ + return vertexCombinationHash; + } + + return NULL; +} + +picoVertexCombinationHash_t *PicoAddVertexCombinationToHashTable( picoVertexCombinationHash_t **hashTable, picoVec3_t xyz, picoVec3_t normal, picoVec3_t st, picoColor_t color, picoIndex_t index ) +{ + unsigned int hash; + picoVertexCombinationHash_t *vertexCombinationHash; + + /* dumy check */ + if (hashTable == NULL || xyz == NULL || normal == NULL || st == NULL || color == NULL ) + return NULL; + + vertexCombinationHash = _pico_alloc( sizeof(picoVertexCombinationHash_t) ); + + if (!vertexCombinationHash) + return NULL; + + hash = PicoVertexCoordGenerateHash( xyz ); + + _pico_copy_vec( xyz, vertexCombinationHash->vcd.xyz ); + _pico_copy_vec( normal, vertexCombinationHash->vcd.normal ); + _pico_copy_vec2( st, vertexCombinationHash->vcd.st ); + _pico_copy_color( color, vertexCombinationHash->vcd.color ); + vertexCombinationHash->index = index; + vertexCombinationHash->data = NULL; + vertexCombinationHash->next = hashTable[ hash ]; + hashTable[ hash ] = vertexCombinationHash; + + return vertexCombinationHash; +} + +/* ---------------------------------------------------------------------------- +specialized routines +---------------------------------------------------------------------------- */ + +/* +PicoFindSurfaceVertex() +finds a vertex matching the set parameters +fixme: needs non-naive algorithm +*/ + +int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t normal, int numSTs, picoVec2_t *st, int numColors, picoColor_t *color, picoIndex_t smoothingGroup) +{ + int i, j; + + + /* dummy check */ + if( surface == NULL || surface->numVertexes <= 0 ) + return -1; + + /* walk vertex list */ + for( i = 0; i < surface->numVertexes; i++ ) + { + /* check xyz */ + if( xyz != NULL && (surface->xyz[ i ][ 0 ] != xyz[ 0 ] || surface->xyz[ i ][ 1 ] != xyz[ 1 ] || surface->xyz[ i ][ 2 ] != xyz[ 2 ]) ) + continue; + + /* check normal */ + if( normal != NULL && (surface->normal[ i ][ 0 ] != normal[ 0 ] || surface->normal[ i ][ 1 ] != normal[ 1 ] || surface->normal[ i ][ 2 ] != normal[ 2 ]) ) + continue; + + /* check normal */ + if( surface->smoothingGroup[ i ] != smoothingGroup ) + continue; + + /* check st */ + if( numSTs > 0 && st != NULL ) + { + for( j = 0; j < numSTs; j++ ) + { + if( surface->st[ j ][ i ][ 0 ] != st[ j ][ 0 ] || surface->st[ j ][ i ][ 1 ] != st[ j ][ 1 ] ) + break; + } + if( j != numSTs ) + continue; + } + + /* check color */ + if( numColors > 0 && color != NULL ) + { + for( j = 0; j < numSTs; j++ ) + { + if( *((int*) surface->color[ j ]) != *((int*) color[ j ]) ) + break; + } + if( j != numColors ) + continue; + } + + /* vertex matches */ + return i; + } + + /* nada */ + return -1; +} + + + + +typedef struct _IndexArray IndexArray; +struct _IndexArray +{ + picoIndex_t* data; + picoIndex_t* last; +}; + +void indexarray_push_back(IndexArray* self, picoIndex_t value) +{ + *self->last++ = value; +} + +size_t indexarray_size(IndexArray* self) +{ + return self->last - self->data; +} + +void indexarray_reserve(IndexArray* self, size_t size) +{ + self->data = self->last = _pico_calloc(size, sizeof(picoIndex_t)); +} + +void indexarray_clear(IndexArray* self) +{ + _pico_free(self->data); +} + +typedef struct _BinaryTreeNode BinaryTreeNode; +struct _BinaryTreeNode +{ + picoIndex_t left; + picoIndex_t right; +}; + +typedef struct _BinaryTree BinaryTree; +struct _BinaryTree +{ + BinaryTreeNode* data; + BinaryTreeNode* last; +}; + +void binarytree_extend(BinaryTree* self) +{ + self->last->left = 0; + self->last->right = 0; + ++self->last; +} + +size_t binarytree_size(BinaryTree* self) +{ + return self->last - self->data; +} + +void binarytree_reserve(BinaryTree* self, size_t size) +{ + self->data = self->last = _pico_calloc(size, sizeof(BinaryTreeNode)); +} + +void binarytree_clear(BinaryTree* self) +{ + _pico_free(self->data); +} + +typedef int (*LessFunc)(void*, picoIndex_t, picoIndex_t); + +typedef struct _UniqueIndices UniqueIndices; +struct _UniqueIndices +{ + BinaryTree tree; + IndexArray indices; + LessFunc lessFunc; + void* lessData; +}; + +size_t UniqueIndices_size(UniqueIndices* self) +{ + return binarytree_size(&self->tree); +} + +void UniqueIndices_reserve(UniqueIndices* self, size_t size) +{ + binarytree_reserve(&self->tree, size); + indexarray_reserve(&self->indices, size); +} + +void UniqueIndices_init(UniqueIndices* self, LessFunc lessFunc, void* lessData) +{ + self->lessFunc = lessFunc; + self->lessData = lessData; +} + +void UniqueIndices_destroy(UniqueIndices* self) +{ + binarytree_clear(&self->tree); + indexarray_clear(&self->indices); +} + + +picoIndex_t UniqueIndices_find_or_insert(UniqueIndices* self, picoIndex_t value) +{ + picoIndex_t index = 0; + + for(;;) + { + if(self->lessFunc(self->lessData, value, self->indices.data[index])) + { + BinaryTreeNode* node = self->tree.data + index; + if(node->left != 0) + { + index = node->left; + continue; + } + else + { + node->left = (picoIndex_t)binarytree_size(&self->tree); + binarytree_extend(&self->tree); + indexarray_push_back(&self->indices, value); + return node->left; + } + } + if(self->lessFunc(self->lessData, self->indices.data[index], value)) + { + BinaryTreeNode* node = self->tree.data + index; + if(node->right != 0) + { + index = node->right; + continue; + } + else + { + node->right = (picoIndex_t)binarytree_size(&self->tree); + binarytree_extend(&self->tree); + indexarray_push_back(&self->indices, value); + return node->right; + } + } + + return index; + } +} + +picoIndex_t UniqueIndices_insert(UniqueIndices* self, picoIndex_t value) +{ + if(self->tree.data == self->tree.last) + { + binarytree_extend(&self->tree); + indexarray_push_back(&self->indices, value); + return 0; + } + else + { + return UniqueIndices_find_or_insert(self, value); + } +} + +typedef struct picoSmoothVertices_s picoSmoothVertices_t; +struct picoSmoothVertices_s +{ + picoVec3_t* xyz; + picoIndex_t* smoothingGroups; +}; + +int lessSmoothVertex(void* data, picoIndex_t first, picoIndex_t second) +{ + picoSmoothVertices_t* smoothVertices = data; + + if(smoothVertices->xyz[first][0] != smoothVertices->xyz[second][0]) + { + return smoothVertices->xyz[first][0] < smoothVertices->xyz[second][0]; + } + if(smoothVertices->xyz[first][1] != smoothVertices->xyz[second][1]) + { + return smoothVertices->xyz[first][1] < smoothVertices->xyz[second][1]; + } + if(smoothVertices->xyz[first][2] != smoothVertices->xyz[second][2]) + { + return smoothVertices->xyz[first][2] < smoothVertices->xyz[second][2]; + } + if(smoothVertices->smoothingGroups[first] != smoothVertices->smoothingGroups[second]) + { + return smoothVertices->smoothingGroups[first] < smoothVertices->smoothingGroups[second]; + } + return 0; +} + +void _pico_vertices_combine_shared_normals(picoVec3_t* xyz, picoIndex_t* smoothingGroups, picoVec3_t* normals, picoIndex_t numVertices) +{ + UniqueIndices vertices; + IndexArray indices; + picoSmoothVertices_t smoothVertices = { xyz, smoothingGroups }; + UniqueIndices_init(&vertices, lessSmoothVertex, &smoothVertices); + UniqueIndices_reserve(&vertices, numVertices); + indexarray_reserve(&indices, numVertices); + + + { + picoIndex_t i = 0; + for(; i < numVertices; ++i) + { + size_t size = UniqueIndices_size(&vertices); + picoIndex_t index = UniqueIndices_insert(&vertices, i); + if((size_t)index != size) + { + float* normal = normals[vertices.indices.data[index]]; + _pico_add_vec(normal, normals[i], normal); + } + indexarray_push_back(&indices, index); + } + } + + { + picoIndex_t maxIndex = 0; + picoIndex_t* i = indices.data; + for(; i != indices.last; ++i) + { + if(*i <= maxIndex) + { + _pico_copy_vec(normals[vertices.indices.data[*i]], normals[i - indices.data]); + } + else + { + maxIndex = *i; + } + } + } + + UniqueIndices_destroy(&vertices); + indexarray_clear(&indices); +} + +typedef picoVec3_t* picoNormalIter_t; +typedef picoIndex_t* picoIndexIter_t; + +#define THE_CROSSPRODUCTS_OF_ANY_PAIR_OF_EDGES_OF_A_GIVEN_TRIANGLE_ARE_EQUAL 1 + +void _pico_triangles_generate_weighted_normals(picoIndexIter_t first, picoIndexIter_t end, picoVec3_t* xyz, picoVec3_t* normals) +{ + for(; first != end; first += 3) + { +#if (THE_CROSSPRODUCTS_OF_ANY_PAIR_OF_EDGES_OF_A_GIVEN_TRIANGLE_ARE_EQUAL) + picoVec3_t weightedNormal; + { + float* a = xyz[*(first + 0)]; + float* b = xyz[*(first + 1)]; + float* c = xyz[*(first + 2)]; + picoVec3_t ba, ca; + _pico_subtract_vec( b, a, ba ); + _pico_subtract_vec( c, a, ca ); + _pico_cross_vec( ca, ba, weightedNormal ); + } +#endif + { + int j = 0; + for(; j < 3; ++j) + { + float* normal = normals[*(first + j)]; +#if (!THE_CROSSPRODUCTS_OF_ANY_PAIR_OF_EDGES_OF_A_GIVEN_TRIANGLE_ARE_EQUAL) + picoVec3_t weightedNormal; + { + float* a = xyz[*(first + ((j + 0) % 3))]; + float* b = xyz[*(first + ((j + 1) % 3))]; + float* c = xyz[*(first + ((j + 2) % 3))]; + picoVec3_t ba, ca; + _pico_subtract_vec( b, a, ba ); + _pico_subtract_vec( c, a, ca ); + _pico_cross_vec( ca, ba, weightedNormal ); + } +#endif + _pico_add_vec(weightedNormal, normal, normal); + } + } + } +} + +void _pico_normals_zero(picoNormalIter_t first, picoNormalIter_t last) +{ + for(; first != last; ++first) + { + _pico_zero_vec(*first); + } +} + +void _pico_normals_normalize(picoNormalIter_t first, picoNormalIter_t last) +{ + for(; first != last; ++first) + { + _pico_normalize_vec(*first); + } +} + +double _pico_length_vec( picoVec3_t vec ) +{ + return sqrt( vec[ 0 ] * vec[ 0 ] + vec[ 1 ] * vec[ 1 ] + vec[ 2 ] * vec[ 2 ] ); +} + +#define NORMAL_UNIT_LENGTH_EPSILON 0.01 +#define FLOAT_EQUAL_EPSILON(f, other, epsilon) (fabs(f - other) < epsilon) + +int _pico_normal_is_unit_length(picoVec3_t normal) +{ + return FLOAT_EQUAL_EPSILON(_pico_length_vec(normal), 1.0, NORMAL_UNIT_LENGTH_EPSILON); +} + +int _pico_normal_within_tolerance(picoVec3_t normal, picoVec3_t other) +{ + return _pico_dot_vec(normal, other) > 0.0f; +} + + +void _pico_normals_assign_generated_normals(picoNormalIter_t first, picoNormalIter_t last, picoNormalIter_t generated) +{ + for(; first != last; ++first, ++generated) + { + if(!_pico_normal_is_unit_length(*first) || !_pico_normal_within_tolerance(*first, *generated)) + { + _pico_copy_vec(*generated, *first); + } + } +} + +void PicoFixSurfaceNormals(picoSurface_t* surface) +{ + picoVec3_t* normals = (picoVec3_t*)_pico_calloc(surface->numVertexes, sizeof(picoVec3_t)); + + _pico_normals_zero(normals, normals + surface->numVertexes); + + _pico_triangles_generate_weighted_normals(surface->index, surface->index + surface->numIndexes, surface->xyz, normals); + _pico_vertices_combine_shared_normals(surface->xyz, surface->smoothingGroup, normals, surface->numVertexes); + + _pico_normals_normalize(normals, normals + surface->numVertexes); + + _pico_normals_assign_generated_normals(surface->normal, surface->normal + surface->numVertexes, normals); + + _pico_free(normals); +} + + +/* +PicoRemapModel() - sea +remaps model material/etc. information using the remappings +contained in the given 'remapFile' (full path to the ascii file to open) +returns 1 on success or 0 on error +*/ + +#define _prm_error_return \ +{ \ + _pico_free_parser( p ); \ + _pico_free_file( remapBuffer ); \ + return 0; \ +} + +int PicoRemapModel( picoModel_t *model, char *remapFile ) +{ + picoParser_t *p; + picoByte_t *remapBuffer; + int remapBufSize; + + + /* sanity checks */ + if( model == NULL || remapFile == NULL ) + return 0; + + /* load remap file contents */ + _pico_load_file( remapFile,&remapBuffer,&remapBufSize ); + + /* check result */ + if( remapBufSize == 0 ) + return 1; /* file is empty: no error */ + if( remapBufSize < 0 ) + return 0; /* load failed: error */ + + /* create a new pico parser */ + p = _pico_new_parser( remapBuffer, remapBufSize ); + if (p == NULL) + { + /* ram is really cheap nowadays... */ + _prm_error_return; + } + + /* doo teh parse */ + while( 1 ) + { + /* get next token in remap file */ + if (!_pico_parse( p,1 )) + break; + + /* skip over c++ style comment lines */ + if (!_pico_stricmp(p->token,"//")) + { + _pico_parse_skip_rest( p ); + continue; + } + + /* block for quick material shader name remapping */ + /* materials { "m" (=>|->|=) "s" } */ + if( !_pico_stricmp(p->token, "materials" ) ) + { + int level = 1; + + /* check bracket */ + if (!_pico_parse_check( p,1,"{" )) + _prm_error_return; + + /* process assignments */ + while( 1 ) + { + picoShader_t *shader; + char *materialName; + + + /* get material name */ + if (_pico_parse( p,1 ) == NULL) break; + if (!strlen(p->token)) continue; + materialName = _pico_clone_alloc( p->token ); + if (materialName == NULL) + _prm_error_return; + + /* handle levels */ + if (p->token[0] == '{') level++; + if (p->token[0] == '}') level--; + if (!level) break; + + /* get next token (assignment token or shader name) */ + if (!_pico_parse( p,0 )) + { + _pico_free( materialName ); + _prm_error_return; + } + /* skip assignment token (if present) */ + if (!strcmp(p->token,"=>") || + !strcmp(p->token,"->") || + !strcmp(p->token,"=")) + { + /* simply grab the next token */ + if (!_pico_parse( p,0 )) + { + _pico_free( materialName ); + _prm_error_return; + } + } + /* try to find material by name */ + shader = PicoFindShader( model,materialName,0 ); + + /* we've found a material matching the name */ + if (shader != NULL) + { + PicoSetShaderName( shader,p->token ); + } + /* free memory used by material name */ + _pico_free( materialName ); + + /* skip rest */ + _pico_parse_skip_rest( p ); + } + } + /* block for detailed single material remappings */ + /* materials[ "m" ] { key data... } */ + else if (!_pico_stricmp(p->token,"materials[")) + { + picoShader_t *shader; + char *tempMaterialName; + int level = 1; + + /* get material name */ + if (!_pico_parse( p,0 )) + _prm_error_return; + + /* temporary copy of material name */ + tempMaterialName = _pico_clone_alloc( p->token ); + if (tempMaterialName == NULL) + _prm_error_return; + + /* check square closing bracket */ + if (!_pico_parse_check( p,0,"]" )) + _prm_error_return; + + /* try to find material by name */ + shader = PicoFindShader( model,tempMaterialName,0 ); + + /* free memory used by temporary material name */ + _pico_free( tempMaterialName ); + + /* we haven't found a material matching the name */ + /* so we simply skip the braced section now and */ + /* continue parsing with the next main token */ + if (shader == NULL) + { + _pico_parse_skip_braced( p ); + continue; + } + /* check opening bracket */ + if (!_pico_parse_check( p,1,"{" )) + _prm_error_return; + + /* process material info keys */ + while( 1 ) + { + /* get key name */ + if (_pico_parse( p,1 ) == NULL) break; + if (!strlen(p->token)) continue; + + /* handle levels */ + if (p->token[0] == '{') level++; + if (p->token[0] == '}') level--; + if (!level) break; + + /* remap shader name */ + if (!_pico_stricmp(p->token,"shader")) + { + if (!_pico_parse( p,0 )) _prm_error_return; + PicoSetShaderName( shader,p->token ); + } + /* remap shader map name */ + else if (!_pico_stricmp(p->token,"mapname")) + { + if (!_pico_parse( p,0 )) _prm_error_return; + PicoSetShaderMapName( shader,p->token ); + } + /* remap shader's ambient color */ + else if (!_pico_stricmp(p->token,"ambient")) + { + picoColor_t color; + picoVec3_t v; + + /* get vector from parser */ + if (!_pico_parse_vec( p,v )) _prm_error_return; + + /* store as color */ + color[ 0 ] = (picoByte_t)v[ 0 ]; + color[ 1 ] = (picoByte_t)v[ 1 ]; + color[ 2 ] = (picoByte_t)v[ 2 ]; + + /* set new ambient color */ + PicoSetShaderAmbientColor( shader,color ); + } + /* remap shader's diffuse color */ + else if (!_pico_stricmp(p->token,"diffuse")) + { + picoColor_t color; + picoVec3_t v; + + /* get vector from parser */ + if (!_pico_parse_vec( p,v )) _prm_error_return; + + /* store as color */ + color[ 0 ] = (picoByte_t)v[ 0 ]; + color[ 1 ] = (picoByte_t)v[ 1 ]; + color[ 2 ] = (picoByte_t)v[ 2 ]; + + /* set new ambient color */ + PicoSetShaderDiffuseColor( shader,color ); + } + /* remap shader's specular color */ + else if (!_pico_stricmp(p->token,"specular")) + { + picoColor_t color; + picoVec3_t v; + + /* get vector from parser */ + if (!_pico_parse_vec( p,v )) _prm_error_return; + + /* store as color */ + color[ 0 ] = (picoByte_t)v[ 0 ]; + color[ 1 ] = (picoByte_t)v[ 1 ]; + color[ 2 ] = (picoByte_t)v[ 2 ]; + + /* set new ambient color */ + PicoSetShaderSpecularColor( shader,color ); + } + /* skip rest */ + _pico_parse_skip_rest( p ); + } + } + /* end 'materials[' */ + } + + /* free both parser and file buffer */ + _pico_free_parser( p ); + _pico_free_file( remapBuffer ); + + /* return with success */ + return 1; +} + + +/* +PicoAddTriangleToModel() - jhefty +A nice way to add individual triangles to the model. +Chooses an appropriate surface based on the shader, or adds a new surface if necessary +*/ + +void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t** normals, + int numSTs, picoVec2_t **st, int numColors, picoColor_t **colors, + picoShader_t* shader, picoIndex_t* smoothingGroup ) +{ + int i,j; + int vertDataIndex; + picoSurface_t* workSurface = NULL; + + /* see if a surface already has the shader */ + for ( i = 0 ; i < model->numSurfaces ; i++ ) + { + workSurface = model->surface[i]; + if ( workSurface->shader == shader ) + { + break; + } + } + + /* no surface uses this shader yet, so create a new surface */ + if ( !workSurface || i >=model->numSurfaces ) + { + /* create a new surface in the model for the unique shader */ + workSurface = PicoNewSurface(model); + if ( !workSurface ) + { + _pico_printf ( PICO_ERROR , "Could not allocate a new surface!\n" ); + return; + } + + /* do surface setup */ + PicoSetSurfaceType( workSurface, PICO_TRIANGLES ); + PicoSetSurfaceName( workSurface, shader->name ); + PicoSetSurfaceShader( workSurface, shader ); + } + + /* add the triangle data to the surface */ + for ( i = 0 ; i < 3 ; i++ ) + { + /* get the next free spot in the index array */ + int newVertIndex = PicoGetSurfaceNumIndexes ( workSurface ); + + /* get the index of the vertex that we're going to store at newVertIndex */ + vertDataIndex = PicoFindSurfaceVertexNum ( workSurface , *xyz[i] , *normals[i] , numSTs , st[i] , numColors , colors[i], smoothingGroup[i]); + + /* the vertex wasn't found, so create a new vertex in the pool from the data we have */ + if ( vertDataIndex == -1 ) + { + /* find the next spot for a new vertex */ + vertDataIndex = PicoGetSurfaceNumVertexes ( workSurface ); + + /* assign the data to it */ + PicoSetSurfaceXYZ ( workSurface ,vertDataIndex , *xyz[i] ); + PicoSetSurfaceNormal ( workSurface , vertDataIndex , *normals[i] ); + + /* make sure to copy over all available ST's and colors for the vertex */ + for ( j = 0 ; j < numColors ; j++ ) + { + PicoSetSurfaceColor( workSurface , j , vertDataIndex , colors[i][j] ); + } + for ( j = 0 ; j < numSTs ; j++ ) + { + PicoSetSurfaceST ( workSurface , j , vertDataIndex , st[i][j] ); + } + + PicoSetSurfaceSmoothingGroup ( workSurface , vertDataIndex , smoothingGroup[i] ); + } + + /* add this vertex to the triangle */ + PicoSetSurfaceIndex ( workSurface , newVertIndex , vertDataIndex ); + } +} diff --git a/libs/picomodel/picomodel.vcproj b/libs/picomodel/picomodel.vcproj new file mode 100644 index 00000000..de1f22b1 --- /dev/null +++ b/libs/picomodel/picomodel.vcproj @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/picomodel/picomodules.c b/libs/picomodel/picomodules.c new file mode 100644 index 00000000..59a6a9ee --- /dev/null +++ b/libs/picomodel/picomodules.c @@ -0,0 +1,94 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PICOMODULES_C + + + +/* dependencies */ +#include "picointernal.h" + + + +/* external modules */ +extern const picoModule_t picoModuleMD3; +extern const picoModule_t picoModule3DS; +extern const picoModule_t picoModuleASE; +extern const picoModule_t picoModuleOBJ; +extern const picoModule_t picoModuleMS3D; +extern const picoModule_t picoModuleMDC; +extern const picoModule_t picoModuleMD2; +extern const picoModule_t picoModuleFM; +extern const picoModule_t picoModuleLWO; +extern const picoModule_t picoModuleTerrain; + + + +/* list of all supported file format modules */ +const picoModule_t *picoModules[] = +{ + &picoModuleMD3, /* quake3 arena md3 */ + &picoModule3DS, /* autodesk 3ds */ + &picoModuleASE, /* autodesk ase */ + &picoModuleMS3D, /* milkshape3d */ + &picoModuleMDC, /* return to castle wolfenstein mdc */ + &picoModuleMD2, /* quake2 md2 */ + &picoModuleFM, /* heretic2 fm */ + &picoModuleLWO, /* lightwave object */ + &picoModuleTerrain, /* picoterrain object */ + &picoModuleOBJ, /* wavefront object */ + NULL /* arnold */ +}; + + + +/* +PicoModuleList() +returns a pointer to the module list and optionally stores +the number of supported modules in 'numModules'. Note that +this param can be NULL when the count is not needed. +*/ + +const picoModule_t **PicoModuleList( int *numModules ) +{ + /* get module count */ + if( numModules != NULL ) + for( (*numModules) = 0; picoModules[ *numModules ] != NULL; (*numModules)++ ); + + /* return list of modules */ + return (const picoModule_t**) picoModules; +} diff --git a/libs/picomodel/pm_3ds.c b/libs/picomodel/pm_3ds.c new file mode 100644 index 00000000..49a05f13 --- /dev/null +++ b/libs/picomodel/pm_3ds.c @@ -0,0 +1,777 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PM_3DS_C + +/* dependencies */ +#include "picointernal.h" + +/* ydnar */ +static picoColor_t white = { 255,255,255,255 }; + +/* remarks: + * - 3ds file version is stored in pico special field 0 on load (ydnar: removed) + * todo: + * - sometimes there is one unnamed surface 0 having 0 verts as + * well as 0 faces. this error occurs since pm 0.6 (ydnar?) + */ +/* uncomment when debugging this module */ +/* #define DEBUG_PM_3DS +#define DEBUG_PM_3DS_EX */ + +/* structure holding persistent 3ds loader specific data used */ +/* to store formerly static vars to keep the module reentrant */ +/* safe. put everything that needs to be static in here. */ +typedef struct S3dsLoaderPers +{ + picoModel_t *model; /* ptr to output model */ + picoSurface_t *surface; /* ptr to current surface */ + picoShader_t *shader; /* ptr to current shader */ + picoByte_t *bufptr; /* ptr to raw data */ + char *basename; /* ptr to model base name (eg. jeep) */ + int cofs; + int maxofs; +} +T3dsLoaderPers; + +/* 3ds chunk types that we use */ +enum { + /* primary chunk */ + CHUNK_MAIN = 0x4D4D, + + /* main chunks */ + CHUNK_VERSION = 0x0002, + CHUNK_EDITOR_CONFIG = 0x3D3E, + CHUNK_EDITOR_DATA = 0x3D3D, + CHUNK_KEYFRAME_DATA = 0xB000, + + /* editor data sub chunks */ + CHUNK_MATERIAL = 0xAFFF, + CHUNK_OBJECT = 0x4000, + + /* material sub chunks */ + CHUNK_MATNAME = 0xA000, + CHUNK_MATDIFFUSE = 0xA020, + CHUNK_MATMAP = 0xA200, + CHUNK_MATMAPFILE = 0xA300, + + /* lets us know we're reading a new object */ + CHUNK_OBJECT_MESH = 0x4100, + + /* object mesh sub chunks */ + CHUNK_OBJECT_VERTICES = 0x4110, + CHUNK_OBJECT_FACES = 0x4120, + CHUNK_OBJECT_MATERIAL = 0x4130, + CHUNK_OBJECT_UV = 0x4140, +}; +#ifdef DEBUG_PM_3DS +static struct +{ + int id; + char *name; +} +debugChunkNames[] = +{ + { CHUNK_MAIN , "CHUNK_MAIN" }, + { CHUNK_VERSION , "CHUNK_VERSION" }, + { CHUNK_EDITOR_CONFIG , "CHUNK_EDITOR_CONFIG" }, + { CHUNK_EDITOR_DATA , "CHUNK_EDITOR_DATA" }, + { CHUNK_KEYFRAME_DATA , "CHUNK_KEYFRAME_DATA" }, + { CHUNK_MATERIAL , "CHUNK_MATERIAL" }, + { CHUNK_OBJECT , "CHUNK_OBJECT" }, + { CHUNK_MATNAME , "CHUNK_MATNAME" }, + { CHUNK_MATDIFFUSE , "CHUNK_MATDIFFUSE" }, + { CHUNK_MATMAP , "CHUNK_MATMAP" }, + { CHUNK_MATMAPFILE , "CHUNK_MATMAPFILE" }, + { CHUNK_OBJECT_MESH , "CHUNK_OBJECT_MESH" }, + { CHUNK_OBJECT_VERTICES , "CHUNK_OBJECT_VERTICES" }, + { CHUNK_OBJECT_FACES , "CHUNK_OBJECT_FACES" }, + { CHUNK_OBJECT_MATERIAL , "CHUNK_OBJECT_MATERIAL" }, + { CHUNK_OBJECT_UV , "CHUNK_OBJECT_UV" }, + { 0 , NULL } +}; +static char *DebugGetChunkName (int id) +{ + int i,max; /* imax? ;) */ + max = sizeof(debugChunkNames) / sizeof(debugChunkNames[0]); + + for (i=0; ilen)) + return PICO_PMV_ERROR_SIZE; + + /* check 3ds magic */ + if (_pico_little_short(chunk->id) != CHUNK_MAIN) + return PICO_PMV_ERROR_IDENT; + + /* file seems to be a valid 3ds */ + return PICO_PMV_OK; +} + +static T3dsChunk *GetChunk (T3dsLoaderPers *pers) +{ + T3dsChunk *chunk; + + /* sanity check */ + if (pers->cofs > pers->maxofs) return 0; + +#ifdef DEBUG_PM_3DS +/* printf("GetChunk: pers->cofs %x\n",pers->cofs); */ +#endif + /* fill in pointer to chunk */ + chunk = (T3dsChunk *)&pers->bufptr[ pers->cofs ]; + if (!chunk) return NULL; + + chunk->id = _pico_little_short(chunk->id ); + chunk->len = _pico_little_long (chunk->len); + + /* advance in buffer */ + pers->cofs += sizeof(T3dsChunk); + + /* this means yay */ + return chunk; +} + +static int GetASCIIZ (T3dsLoaderPers *pers, char *dest, int max) +{ + int pos = 0; + int ch; + + for (;;) + { + ch = pers->bufptr[ pers->cofs++ ]; + if (ch == '\0') break; + if (pers->cofs >= pers->maxofs) + { + dest[ pos ] = '\0'; + return 0; + } + dest[ pos++ ] = ch; + if (pos >= max) break; + } + dest[ pos ] = '\0'; + return 1; +} + +static picoByte_t GetByte (T3dsLoaderPers *pers) +{ + picoByte_t *value; + + /* sanity check */ + if (pers->cofs > pers->maxofs) return 0; + + /* get and return value */ + value = (picoByte_t *)(pers->bufptr + pers->cofs); + pers->cofs += 1; + return *value; +} + +static int GetWord (T3dsLoaderPers *pers) +{ + unsigned short *value; + + /* sanity check */ + if (pers->cofs > pers->maxofs) return 0; + + /* get and return value */ + value = (unsigned short *)(pers->bufptr + pers->cofs); + pers->cofs += 2; + return _pico_little_short(*value); +} + +static float GetFloat (T3dsLoaderPers *pers) +{ + float *value; + + /* sanity check */ + if (pers->cofs > pers->maxofs) return 0; + + /* get and return value */ + value = (float *)(pers->bufptr + pers->cofs); + pers->cofs += 4; + return _pico_little_float(*value); +} + +static int GetMeshVertices (T3dsLoaderPers *pers) +{ + int numVerts; + int i; + + /* get number of verts for this surface */ + numVerts = GetWord(pers); + +#ifdef DEBUG_PM_3DS + printf("GetMeshVertices: numverts %d\n",numVerts); +#endif + /* read in vertices for current surface */ + for (i=0; isurface,i,v ); + PicoSetSurfaceColor( pers->surface,0,i,white ); /* ydnar */ + +#ifdef DEBUG_PM_3DS_EX + printf("Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2]); +#endif + } + /* success (no errors occured) */ + return 1; +} + +static int GetMeshFaces (T3dsLoaderPers *pers) +{ + int numFaces; + int i; + + /* get number of faces for this surface */ + numFaces = GetWord(pers); + +#ifdef DEBUG_PM_3DS + printf("GetMeshFaces: numfaces %d\n",numFaces); +#endif + /* read in vertex indices for current surface */ + for (i=0; isurface, (i * 3 + 0), (picoIndex_t)face.a ); + PicoSetSurfaceIndex( pers->surface, (i * 3 + 1), (picoIndex_t)face.b ); + PicoSetSurfaceIndex( pers->surface, (i * 3 + 2), (picoIndex_t)face.c ); + +#ifdef DEBUG_PM_3DS_EX + printf("Face: a: %d b: %d c: %d (%d)\n",face.a,face.b,face.c,face.visible); +#endif + } + /* success (no errors occured) */ + return 1; +} + +static int GetMeshTexCoords (T3dsLoaderPers *pers) +{ + int numTexCoords; + int i; + + /* get number of uv coords for this surface */ + numTexCoords = GetWord(pers); + +#ifdef DEBUG_PM_3DS + printf("GetMeshTexCoords: numcoords %d\n",numTexCoords); +#endif + /* read in uv coords for current surface */ + for (i=0; isurface == NULL) + continue; + + /* add current uv */ + PicoSetSurfaceST( pers->surface,0,i,uv ); + +#ifdef DEBUG_PM_3DS_EX + printf("u: %f v: %f\n",uv[0],uv[1]); +#endif + } + /* success (no errors occured) */ + return 1; +} + +static int GetMeshShader (T3dsLoaderPers *pers) +{ + char shaderName[255] = { 0 }; + picoShader_t *shader; + int numSharedVerts; + int setShaderName = 0; + int i; + + /* the shader is either the color or the texture map of the */ + /* object. it can also hold other information like the brightness, */ + /* shine, etc. stuff we don't really care about. we just want the */ + /* color, or the texture map file name really */ + + /* get in the shader name */ + if (!GetASCIIZ(pers,shaderName,sizeof(shaderName))) + return 0; + + /* ydnar: trim to first whitespace */ + _pico_first_token( shaderName ); + + /* now that we have the shader name we need to go through all of */ + /* the shaders and check the name against each shader. when we */ + /* find a shader in our shader list that matches this name we */ + /* just read in, then we assign the shader's id of the object to */ + /* that shader */ + + /* get shader id for shader name */ + shader = PicoFindShader( pers->model, shaderName, 1 ); + + /* we've found a matching shader */ + if ((shader != NULL) && pers->surface) + { + char mapName[1024+1]; + char *mapNamePtr; + memset( mapName,0,sizeof(mapName) ); + + /* get ptr to shader's map name */ + mapNamePtr = PicoGetShaderMapName( shader ); + + /* we have a valid map name ptr */ + if (mapNamePtr != NULL) + { + char temp[128]; + const char *name; + + /* copy map name to local buffer */ + strcpy( mapName,mapNamePtr ); + + /* extract file name */ + name = _pico_nopath( mapName ); + strncpy( temp, name, sizeof(temp) ); + + /* remove file extension */ + /* name = _pico_setfext( name,"" ); */ + + /* assign default name if no name available */ + if (strlen(temp) < 1) + strcpy(temp,pers->basename); + + /* build shader name */ + _pico_strlwr( temp ); /* gaynux update -sea */ + sprintf( mapName,"models/mapobjects/%s/%s",pers->basename,temp ); + + /* set shader name */ + /* PicoSetShaderName( shader,mapName ); */ /* ydnar: this will screw up the named shader */ + + /* set surface's shader index */ + PicoSetSurfaceShader( pers->surface, shader ); + + setShaderName = 1; + } + } + /* we didn't set a shader name; throw out warning */ + if (!setShaderName) + { + _pico_printf( PICO_WARNING,"3DS mesh is missing shader name"); + } + /* we don't process the list of shared vertices here; there is a */ + /* short int that gives the number of faces of the mesh concerned */ + /* by this shader, then there is the list itself of these faces. */ + /* 0000 means the first face of the (4120) face list */ + + /* get number of shared verts */ + numSharedVerts = GetWord(pers); + +#ifdef DEBUG_PM_3DS + printf("GetMeshShader: uses shader '%s' (nsv %d)\n",shaderName,numSharedVerts); +#endif + /* skip list of shared verts */ + for (i=0; ishader ) + { + PicoSetShaderDiffuseColor( pers->shader,color ); + } +#ifdef DEBUG_PM_3DS + printf("GetDiffuseColor: %d %d %d\n",color[0],color[1],color[2]); +#endif + /* success (no errors occured) */ + return 1; +} + +static int DoNextEditorDataChunk (T3dsLoaderPers *pers, long endofs) +{ + T3dsChunk *chunk; + +#ifdef DEBUG_PM_3DS_EX + printf("DoNextEditorDataChunk: endofs %d\n",endofs); +#endif + while (pers->cofs < endofs) + { + long nextofs = pers->cofs; + if ((chunk = GetChunk(pers)) == NULL) return 0; + if (!chunk->len) return 0; + nextofs += chunk->len; + +#ifdef DEBUG_PM_3DS_EX + printf("Chunk %04x (%s), len %d pers->cofs %x\n",chunk->id,DebugGetChunkName(chunk->id),chunk->len,pers->cofs); +#endif + /*** meshes ***/ + if (chunk->id == CHUNK_OBJECT) + { + picoSurface_t *surface; + char surfaceName[ 0xff ] = { 0 }; + + /* read in surface name */ + if( !GetASCIIZ(pers,surfaceName,sizeof(surfaceName)) ) + return 0; /* this is bad */ + +//PicoGetSurfaceName + /* ignore NULL name surfaces */ +// if( surfaceName + + /* allocate a pico surface */ + surface = PicoNewSurface( pers->model ); + if( surface == NULL ) + { + pers->surface = NULL; + return 0; /* this is bad too */ + } + /* assign ptr to current surface */ + pers->surface = surface; + + /* 3ds models surfaces are all triangle meshes */ + PicoSetSurfaceType( pers->surface,PICO_TRIANGLES ); + + /* set surface name */ + PicoSetSurfaceName( pers->surface,surfaceName ); + + /* continue mess with object's sub chunks */ + DoNextEditorDataChunk(pers,nextofs); + continue; + } + if (chunk->id == CHUNK_OBJECT_MESH) + { + /* continue mess with mesh's sub chunks */ + if (!DoNextEditorDataChunk(pers,nextofs)) return 0; + continue; + } + if (chunk->id == CHUNK_OBJECT_VERTICES) + { + if (!GetMeshVertices(pers)) return 0; + continue; + } + if (chunk->id == CHUNK_OBJECT_FACES) + { + if (!GetMeshFaces(pers)) return 0; + continue; + } + if (chunk->id == CHUNK_OBJECT_UV) + { + if (!GetMeshTexCoords(pers)) return 0; + continue; + } + if (chunk->id == CHUNK_OBJECT_MATERIAL) + { + if (!GetMeshShader(pers)) return 0; + continue; + } + /*** materials ***/ + if (chunk->id == CHUNK_MATERIAL) + { + /* new shader specific things should be */ + /* initialized right here */ + picoShader_t *shader; + + /* allocate a pico shader */ + shader = PicoNewShader( pers->model ); /* ydnar */ + if( shader == NULL ) + { + pers->shader = NULL; + return 0; /* this is bad too */ + } + + /* assign ptr to current shader */ + pers->shader = shader; + + /* continue and process the material's sub chunks */ + DoNextEditorDataChunk(pers,nextofs); + continue; + } + if (chunk->id == CHUNK_MATNAME) + { + /* new material's names should be stored here. note that */ + /* GetMeshMaterial returns the name of the material that */ + /* is used by the mesh. new material names are set HERE. */ + /* but for now we skip the new material's name ... */ + if (pers->shader) + { + char *name = (char*) (pers->bufptr + pers->cofs); + char *cleanedName = _pico_clone_alloc( name ); + _pico_first_token( cleanedName ); + PicoSetShaderName( pers->shader, cleanedName ); +#ifdef DEBUG_PM_3DS + printf( "NewShader: '%s'\n", cleanedName ); +#endif + _pico_free( cleanedName ); + } + } + if (chunk->id == CHUNK_MATDIFFUSE) + { + /* todo: color for last inserted new material should be */ + /* stored somewhere by GetDiffuseColor */ + if (!GetDiffuseColor(pers)) return 0; + + /* rest of chunk is skipped here */ + } + if (chunk->id == CHUNK_MATMAP) + { + /* continue and process the material map sub chunks */ + DoNextEditorDataChunk(pers,nextofs); + continue; + } + if (chunk->id == CHUNK_MATMAPFILE) + { + /* map file name for last inserted new material should */ + /* be stored here. but for now we skip this too ... */ + if( pers->shader ) + { + char *name = (char *)(pers->bufptr + pers->cofs); + PicoSetShaderMapName( pers->shader,name ); +#ifdef DEBUG_PM_3DS + printf("NewShaderMapfile: '%s'\n",name); +#endif + } + } + /*** keyframes ***/ + if (chunk->id == CHUNK_KEYFRAME_DATA) + { + /* well umm, this is a bit too much since we don't really */ + /* need model animation sequences right now. we skip this */ +#ifdef DEBUG_PM_3DS + printf("KeyframeData: len %d\n",chunk->len); +#endif + } + /* skip unknown chunk */ + pers->cofs = nextofs; + if (pers->cofs >= pers->maxofs) break; + } + return 1; +} + +static int DoNextChunk (T3dsLoaderPers *pers, int endofs) +{ + T3dsChunk *chunk; + +#ifdef DEBUG_PM_3DS + printf("DoNextChunk: endofs %d\n",endofs); +#endif + while (pers->cofs < endofs) + { + long nextofs = pers->cofs; + if ((chunk = GetChunk(pers)) == NULL) return 0; + if (!chunk->len) return 0; + nextofs += chunk->len; + +#ifdef DEBUG_PM_3DS_EX + printf("Chunk %04x (%s), len %d pers->cofs %x\n",chunk->id,DebugGetChunkName(chunk->id),chunk->len,pers->cofs); +#endif + /*** version ***/ + if (chunk->id == CHUNK_VERSION) + { + /* at this point i get the 3ds file version. since there */ + /* might be new additions to the 3ds file format in 4.0 */ + /* it might be a good idea to store the version somewhere */ + /* for later handling or message displaying */ + + /* get the version */ + int version; + version = GetWord(pers); + GetWord(pers); +#ifdef DEBUG_PM_3DS + printf("FileVersion: %d\n",version); +#endif + + /* throw out a warning for version 4 models */ + if (version == 4) + { + _pico_printf( PICO_WARNING, + "3DS version is 4. Model might load incorrectly."); + } + /* store the 3ds file version in pico special field 0 */ + /* PicoSetSurfaceSpecial(pers->surface,0,version); */ /* ydnar: this was causing a crash accessing uninitialized surface */ + + /* rest of chunk is skipped here */ + } + /*** editor data ***/ + if (chunk->id == CHUNK_EDITOR_DATA) + { + if (!DoNextEditorDataChunk(pers,nextofs)) return 0; + continue; + } + /* skip unknown chunk */ + pers->cofs = nextofs; + if (pers->cofs >= pers->maxofs) break; + } + return 1; +} + +/* _3ds_load: + * loads an autodesk 3ds model file. +*/ +static picoModel_t *_3ds_load( PM_PARAMS_LOAD ) +{ + T3dsLoaderPers pers; + picoModel_t *model; + char basename[128]; + + /* create a new pico model */ + model = PicoNewModel(); + if (model == NULL) + { + /* user must have some serious ram problems ;) */ + return NULL; + } + /* get model's base name (eg. jeep from c:\models\jeep.3ds) */ + memset( basename,0,sizeof(basename) ); + strncpy( basename,_pico_nopath(fileName),sizeof(basename) ); + _pico_setfext( basename,"" ); + + /* initialize persistant vars (formerly static) */ + pers.model = model; + pers.bufptr = (picoByte_t *)buffer; + pers.basename = (char *)basename; + pers.maxofs = bufSize; + pers.cofs = 0L; + + /* do model setup */ + PicoSetModelFrameNum( model,frameNum ); + PicoSetModelName( model,fileName ); + PicoSetModelFileName( model,fileName ); + + /* skip first chunk in file (magic) */ + GetChunk(&pers); + + /* process chunks */ + if (!DoNextChunk(&pers,pers.maxofs)) + { + /* well, bleh i guess */ + PicoFreeModel(model); + return NULL; + } + /* return allocated pico model */ + return model; +} + +/* pico file format module definition */ +const picoModule_t picoModule3DS = +{ + "0.86-b", /* module version string */ + "Autodesk 3Dstudio", /* module display name */ + "seaw0lf", /* author's name */ + "2002 seaw0lf", /* module copyright */ + { + "3ds",NULL,NULL,NULL /* default extensions to use */ + }, + _3ds_canload, /* validation routine */ + _3ds_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/libs/picomodel/pm_ase.c b/libs/picomodel/pm_ase.c new file mode 100644 index 00000000..985ea8a0 --- /dev/null +++ b/libs/picomodel/pm_ase.c @@ -0,0 +1,1193 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other aseMaterialList provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + +/* marker */ +#define PM_ASE_C + +/* uncomment when debugging this module */ +//#define DEBUG_PM_ASE +//#define DEBUG_PM_ASE_EX + + +/* dependencies */ +#include "picointernal.h" + +#ifdef DEBUG_PM_ASE +#include "time.h" +#endif + +/* plain white */ +static picoColor_t white = { 255, 255, 255, 255 }; + +/* jhefty - multi-subobject material support */ + +/* Material/SubMaterial management */ +/* A material should have 1..n submaterials assigned to it */ + +typedef struct aseSubMaterial_s +{ + struct aseSubMaterial_s* next; + int subMtlId; + picoShader_t* shader; + +} aseSubMaterial_t; + +typedef struct aseMaterial_s +{ + struct aseMaterial_s* next; + struct aseSubMaterial_s* subMtls; + int mtlId; +} aseMaterial_t; + +/* Material/SubMaterial management functions */ +static aseMaterial_t* _ase_get_material ( aseMaterial_t* list , int mtlIdParent ) +{ + aseMaterial_t* mtl = list; + + while ( mtl ) + { + if ( mtlIdParent == mtl->mtlId ) + { + break; + } + mtl = mtl->next; + } + return mtl; +} + +static aseSubMaterial_t* _ase_get_submaterial ( aseMaterial_t* list, int mtlIdParent , int subMtlId ) +{ + aseMaterial_t* parent = _ase_get_material ( list , mtlIdParent ); + aseSubMaterial_t* subMtl = NULL; + + if ( !parent ) + { + _pico_printf ( PICO_ERROR , "No ASE material exists with id %i\n" , mtlIdParent ); + return NULL; + } + + subMtl = parent->subMtls; + while ( subMtl ) + { + if ( subMtlId == subMtl->subMtlId ) + { + break; + } + subMtl = subMtl->next; + } + return subMtl; +} + +aseSubMaterial_t* _ase_get_submaterial_or_default ( aseMaterial_t* materials, int mtlIdParent , int subMtlId ) +{ + aseSubMaterial_t* subMtl = _ase_get_submaterial( materials, mtlIdParent, subMtlId ); + if(subMtl != NULL) + { + return subMtl; + } + + /* ydnar: trying default submaterial */ + subMtl = _ase_get_submaterial( materials, mtlIdParent, 0 ); + if( subMtl != NULL ) + { + return subMtl; + } + + _pico_printf( PICO_ERROR, "Could not find material/submaterial for id %d/%d\n", mtlIdParent, subMtlId ); + return NULL; +} + + + + +static aseMaterial_t* _ase_add_material( aseMaterial_t **list, int mtlIdParent ) +{ + aseMaterial_t *mtl = _pico_calloc( 1, sizeof( aseMaterial_t ) ); + mtl->mtlId = mtlIdParent; + mtl->subMtls = NULL; + mtl->next = *list; + *list = mtl; + + return mtl; +} + +static aseSubMaterial_t* _ase_add_submaterial( aseMaterial_t **list, int mtlIdParent, int subMtlId, picoShader_t* shader ) +{ + aseMaterial_t *parent = _ase_get_material( *list, mtlIdParent ); + aseSubMaterial_t *subMtl = _pico_calloc( 1, sizeof ( aseSubMaterial_t ) ); + + if ( !parent ) + { + parent = _ase_add_material ( list , mtlIdParent ); + } + + subMtl->shader = shader; + subMtl->subMtlId = subMtlId; + subMtl->next = parent->subMtls; + parent->subMtls = subMtl; + + return subMtl; +} + +static void _ase_free_materials( aseMaterial_t **list ) +{ + aseMaterial_t* mtl = *list; + aseSubMaterial_t* subMtl = NULL; + + aseMaterial_t* mtlTemp = NULL; + aseSubMaterial_t* subMtlTemp = NULL; + + while ( mtl ) + { + subMtl = mtl->subMtls; + while ( subMtl ) + { + subMtlTemp = subMtl->next; + _pico_free ( subMtl ); + subMtl = subMtlTemp; + } + mtlTemp = mtl->next; + _pico_free ( mtl ); + mtl = mtlTemp; + } + (*list) = NULL; +} + +#ifdef DEBUG_PM_ASE +static void _ase_print_materials( aseMaterial_t *list ) +{ + aseMaterial_t* mtl = list; + aseSubMaterial_t* subMtl = NULL; + + while ( mtl ) + { + _pico_printf ( PICO_NORMAL , "ASE Material %i" , mtl->mtlId ); + subMtl = mtl->subMtls; + while ( subMtl ) + { + _pico_printf ( PICO_NORMAL , " -- ASE SubMaterial %i - %s\n" , subMtl->subMtlId , subMtl->shader->name ); + subMtl = subMtl->next; + } + mtl = mtl->next; + } +} +#endif //DEBUG_PM_ASE + +/* todo: + * - apply material specific uv offsets to uv coordinates + */ + +/* _ase_canload: + * validates a 3dsmax ase model file. + */ +static int _ase_canload( PM_PARAMS_CANLOAD ) +{ + picoParser_t *p; + + + /* quick data length validation */ + if( bufSize < 80 ) + return PICO_PMV_ERROR_SIZE; + + /* keep the friggin compiler happy */ + *fileName = *fileName; + + /* create pico parser */ + p = _pico_new_parser( (picoByte_t*) buffer, bufSize ); + if( p == NULL ) + return PICO_PMV_ERROR_MEMORY; + + /* get first token */ + if( _pico_parse_first( p ) == NULL) + { + return PICO_PMV_ERROR_IDENT; + } + + /* check first token */ + if( _pico_stricmp( p->token, "*3dsmax_asciiexport" ) ) + { + _pico_free_parser( p ); + return PICO_PMV_ERROR_IDENT; + } + + /* free the pico parser object */ + _pico_free_parser( p ); + + /* file seems to be a valid ase file */ + return PICO_PMV_OK; +} + +typedef struct aseVertex_s aseVertex_t; +struct aseVertex_s +{ + picoVec3_t xyz; + picoVec3_t normal; + picoIndex_t id; +}; + +typedef struct aseTexCoord_s aseTexCoord_t; +struct aseTexCoord_s +{ + picoVec2_t texcoord; +}; + +typedef struct aseColor_s aseColor_t; +struct aseColor_s +{ + picoColor_t color; +}; + +typedef struct aseFace_s aseFace_t; +struct aseFace_s +{ + picoIndex_t indices[9]; + picoIndex_t smoothingGroup; + picoIndex_t materialId; + picoIndex_t subMaterialId; +}; +typedef aseFace_t* aseFacesIter_t; + +picoSurface_t* PicoModelFindOrAddSurface( picoModel_t *model, picoShader_t* shader ) +{ + /* see if a surface already has the shader */ + int i = 0; + for ( ; i < model->numSurfaces ; i++ ) + { + picoSurface_t* workSurface = model->surface[i]; + if ( workSurface->shader == shader ) + { + return workSurface; + } + } + + /* no surface uses this shader yet, so create a new surface */ + + { + /* create a new surface in the model for the unique shader */ + picoSurface_t* workSurface = PicoNewSurface(model); + if ( !workSurface ) + { + _pico_printf ( PICO_ERROR , "Could not allocate a new surface!\n" ); + return 0; + } + + /* do surface setup */ + PicoSetSurfaceType( workSurface, PICO_TRIANGLES ); + PicoSetSurfaceName( workSurface, shader->name ); + PicoSetSurfaceShader( workSurface, shader ); + + return workSurface; + } +} + +/* _ase_submit_triangles - jhefty + use the surface and the current face list to look up material/submaterial IDs + and submit them to the model for proper processing + +The following still holds from ydnar's _ase_make_surface: + indexes 0 1 2 = vert indexes + indexes 3 4 5 = st indexes + indexes 6 7 8 = color indexes (new) +*/ + +#if 0 +typedef picoIndex_t* picoIndexIter_t; + +typedef struct aseUniqueIndices_s aseUniqueIndices_t; +struct aseUniqueIndices_s +{ + picoIndex_t* data; + picoIndex_t* last; + + aseFace_t* faces; +}; + +size_t aseUniqueIndices_size(aseUniqueIndices_t* self) +{ + return self->last - self->data; +} + +void aseUniqueIndices_reserve(aseUniqueIndices_t* self, picoIndex_t size) +{ + self->data = self->last = (picoIndex_t*)_pico_calloc(size, sizeof(picoIndex_t)); +} + +void aseUniqueIndices_clear(aseUniqueIndices_t* self) +{ + _pico_free(self->data); +} + +void aseUniqueIndices_pushBack(aseUniqueIndices_t* self, picoIndex_t index) +{ + *self->last++ = index; +} + +picoIndex_t aseFaces_getVertexIndex(aseFace_t* faces, picoIndex_t index) +{ + return faces[index / 3].indices[index % 3]; +} + +picoIndex_t aseFaces_getTexCoordIndex(aseFace_t* faces, picoIndex_t index) +{ + return faces[index / 3].indices[(index % 3) + 3]; +} + +picoIndex_t aseFaces_getColorIndex(aseFace_t* faces, picoIndex_t index) +{ + return faces[index / 3].indices[(index % 3) + 6]; +} + +int aseUniqueIndex_equal(aseFace_t* faces, picoIndex_t index, picoIndex_t other) +{ + return aseFaces_getVertexIndex(faces, index) == aseFaces_getVertexIndex(faces, other) + && aseFaces_getTexCoordIndex(faces, index) == aseFaces_getTexCoordIndex(faces, other) + && aseFaces_getColorIndex(faces, index) == aseFaces_getColorIndex(faces, other); +} + +picoIndex_t aseUniqueIndices_insertUniqueVertex(aseUniqueIndices_t* self, picoIndex_t index) +{ + picoIndexIter_t i = self->data; + for(; i != self->last; ++i) + { + picoIndex_t other = (picoIndex_t)(i - self->data); + if(aseUniqueIndex_equal(self->faces, index, other)) + { + return other; + } + } + + aseUniqueIndices_pushBack(self, index); + return (picoIndex_t)(aseUniqueIndices_size(self) - 1); +} + +static void _ase_submit_triangles_unshared ( picoModel_t* model , aseMaterial_t* materials , aseVertex_t* vertices, aseTexCoord_t* texcoords, aseColor_t* colors, aseFace_t* faces, int numFaces, int meshHasNormals ) +{ + aseFacesIter_t i = faces, end = faces + numFaces; + + aseUniqueIndices_t indices; + aseUniqueIndices_t remap; + aseUniqueIndices_reserve(&indices, numFaces * 3); + aseUniqueIndices_reserve(&remap, numFaces * 3); + indices.faces = faces; + + for(; i != end; ++i) + { + /* look up the shader for the material/submaterial pair */ + aseSubMaterial_t* subMtl = _ase_get_submaterial_or_default( materials, (*i).materialId, (*i).subMaterialId ); + if( subMtl == NULL ) + { + return; + } + + { + picoSurface_t* surface = PicoModelFindOrAddSurface(model, subMtl->shader); + int j; + /* we pull the data from the vertex, color and texcoord arrays using the face index data */ + for ( j = 0 ; j < 3 ; j ++ ) + { + picoIndex_t index = (picoIndex_t)(((i - faces) * 3) + j); + picoIndex_t size = (picoIndex_t)aseUniqueIndices_size(&indices); + picoIndex_t unique = aseUniqueIndices_insertUniqueVertex(&indices, index); + + picoIndex_t numVertexes = PicoGetSurfaceNumVertexes(surface); + picoIndex_t numIndexes = PicoGetSurfaceNumIndexes(surface); + + aseUniqueIndices_pushBack(&remap, numIndexes); + + PicoSetSurfaceIndex(surface, numIndexes, remap.data[unique]); + + if(unique == size) + { + PicoSetSurfaceXYZ(surface, numVertexes, vertices[(*i).indices[j]].xyz); + PicoSetSurfaceNormal(surface, numVertexes, vertices[(*i).indices[j]].normal); + PicoSetSurfaceST(surface, 0, numVertexes, texcoords[(*i).indices[j + 3]].texcoord); + + if ( (*i).indices[j + 6] >= 0 ) + { + PicoSetSurfaceColor(surface, 0, numVertexes, colors[(*i).indices[j + 6]].color); + } + else + { + PicoSetSurfaceColor(surface, 0, numVertexes, white); + } + + PicoSetSurfaceSmoothingGroup(surface, numVertexes, (vertices[(*i).indices[j]].id * (1 << 16)) + (*i).smoothingGroup); + } + } + } + } + + aseUniqueIndices_clear(&indices); + aseUniqueIndices_clear(&remap); +} + +#endif + +static void _ase_submit_triangles( picoModel_t* model , aseMaterial_t* materials , aseVertex_t* vertices, aseTexCoord_t* texcoords, aseColor_t* colors, aseFace_t* faces, int numFaces ) +{ + aseFacesIter_t i = faces, end = faces + numFaces; + for(; i != end; ++i) + { + /* look up the shader for the material/submaterial pair */ + aseSubMaterial_t* subMtl = _ase_get_submaterial_or_default( materials, (*i).materialId, (*i).subMaterialId ); + if( subMtl == NULL ) + { + return; + } + + { + picoVec3_t* xyz[3]; + picoVec3_t* normal[3]; + picoVec2_t* st[3]; + picoColor_t* color[3]; + picoIndex_t smooth[3]; + int j; + /* we pull the data from the vertex, color and texcoord arrays using the face index data */ + for ( j = 0 ; j < 3 ; j ++ ) + { + xyz[j] = &vertices[(*i).indices[j]].xyz; + normal[j] = &vertices[(*i).indices[j]].normal; + st[j] = &texcoords[(*i).indices[j + 3]].texcoord; + + if( colors != NULL && (*i).indices[j + 6] >= 0 ) + { + color[j] = &colors[(*i).indices[j + 6]].color; + } + else + { + color[j] = &white; + } + + smooth[j] = (vertices[(*i).indices[j]].id * (1 << 16)) + (*i).smoothingGroup; /* don't merge vertices */ + + } + + /* submit the triangle to the model */ + PicoAddTriangleToModel ( model , xyz , normal , 1 , st , 1 , color , subMtl->shader, smooth ); + } + } +} + +static void shadername_convert(char* shaderName) +{ + /* unix-style path separators */ + char* s = shaderName; + for(; *s != '\0'; ++s) + { + if(*s == '\\') + { + *s = '/'; + } + } +} + + +/* _ase_load: + * loads a 3dsmax ase model file. +*/ +static picoModel_t *_ase_load( PM_PARAMS_LOAD ) +{ + picoModel_t *model; + picoParser_t *p; + char lastNodeName[ 1024 ]; + + aseVertex_t* vertices = NULL; + aseTexCoord_t* texcoords = NULL; + aseColor_t* colors = NULL; + aseFace_t* faces = NULL; + int numVertices = 0; + int numFaces = 0; + int numTextureVertices = 0; + int numTextureVertexFaces = 0; + int numColorVertices = 0; + int numColorVertexFaces = 0; + int vertexId = 0; + + aseMaterial_t* materials = NULL; + +#ifdef DEBUG_PM_ASE + clock_t start, finish; + double elapsed; + start = clock(); +#endif + + /* helper */ + #define _ase_error_return(m) \ + { \ + _pico_printf( PICO_ERROR,"%s in ASE, line %d.",m,p->curLine); \ + _pico_free_parser( p ); \ + PicoFreeModel( model ); \ + return NULL; \ + } + /* create a new pico parser */ + p = _pico_new_parser( (picoByte_t *)buffer,bufSize ); + if (p == NULL) return NULL; + + /* create a new pico model */ + model = PicoNewModel(); + if (model == NULL) + { + _pico_free_parser( p ); + return NULL; + } + /* do model setup */ + PicoSetModelFrameNum( model, frameNum ); + PicoSetModelName( model, fileName ); + PicoSetModelFileName( model, fileName ); + + /* initialize some stuff */ + memset( lastNodeName,0,sizeof(lastNodeName) ); + + /* parse ase model file */ + while( 1 ) + { + /* get first token on line */ + if (_pico_parse_first( p ) == NULL) + break; + + /* we just skip empty lines */ + if (p->token == NULL || !strlen( p->token )) + continue; + + /* we skip invalid ase statements */ + if (p->token[0] != '*' && p->token[0] != '{' && p->token[0] != '}') + { + _pico_parse_skip_rest( p ); + continue; + } + /* remember node name */ + if (!_pico_stricmp(p->token,"*node_name")) + { + /* read node name */ + char *ptr = _pico_parse( p,0 ); + if (ptr == NULL) + _ase_error_return("Node name parse error"); + + /* remember node name */ + strncpy( lastNodeName,ptr,sizeof(lastNodeName) ); + } + /* model mesh (originally contained within geomobject) */ + else if (!_pico_stricmp(p->token,"*mesh")) + { + /* finish existing surface */ + _ase_submit_triangles(model, materials, vertices, texcoords, colors, faces, numFaces); + _pico_free(faces); + _pico_free(vertices); + _pico_free(texcoords); + _pico_free(colors); + } + else if (!_pico_stricmp(p->token,"*mesh_numvertex")) + { + if (!_pico_parse_int( p, &numVertices) ) + _ase_error_return("Missing MESH_NUMVERTEX value"); + + vertices = _pico_calloc(numVertices, sizeof(aseVertex_t)); + } + else if (!_pico_stricmp(p->token,"*mesh_numfaces")) + { + if (!_pico_parse_int( p, &numFaces) ) + _ase_error_return("Missing MESH_NUMFACES value"); + + faces = _pico_calloc(numFaces, sizeof(aseFace_t)); + } + else if (!_pico_stricmp(p->token,"*mesh_numtvertex")) + { + if (!_pico_parse_int( p, &numTextureVertices) ) + _ase_error_return("Missing MESH_NUMTVERTEX value"); + + texcoords = _pico_calloc(numTextureVertices, sizeof(aseTexCoord_t)); + } + else if (!_pico_stricmp(p->token,"*mesh_numtvfaces")) + { + if (!_pico_parse_int( p, &numTextureVertexFaces) ) + _ase_error_return("Missing MESH_NUMTVFACES value"); + } + else if (!_pico_stricmp(p->token,"*mesh_numcvertex")) + { + if (!_pico_parse_int( p, &numColorVertices) ) + _ase_error_return("Missing MESH_NUMCVERTEX value"); + + colors = _pico_calloc(numColorVertices, sizeof(aseColor_t)); + memset( colors, 255, numColorVertices * sizeof( aseColor_t ) ); /* ydnar: force colors to white initially */ + } + else if (!_pico_stricmp(p->token,"*mesh_numcvfaces")) + { + if (!_pico_parse_int( p, &numColorVertexFaces) ) + _ase_error_return("Missing MESH_NUMCVFACES value"); + } + /* mesh material reference. this usually comes at the end of */ + /* geomobjects after the mesh blocks. we must assume that the */ + /* new mesh was already created so all we can do here is assign */ + /* the material reference id (shader index) now. */ + else if (!_pico_stricmp(p->token,"*material_ref")) + { + int mtlId; + + /* get the material ref (0..n) */ + if (!_pico_parse_int( p,&mtlId) ) + _ase_error_return("Missing material reference ID"); + + { + int i = 0; + /* fix up all of the aseFaceList in the surface to point to the parent material */ + /* we've already saved off their subMtl */ + for(; i < numFaces; ++i) + { + faces[i].materialId = mtlId; + } + } + } + /* model mesh vertex */ + else if (!_pico_stricmp(p->token,"*mesh_vertex")) + { + int index; + + if( numVertices == 0 ) + _ase_error_return("Vertex parse error"); + + /* get vertex data (orig: index +y -x +z) */ + if (!_pico_parse_int( p,&index )) + _ase_error_return("Vertex parse error"); + if (!_pico_parse_vec( p,vertices[index].xyz )) + _ase_error_return("Vertex parse error"); + + vertices[index].id = vertexId++; + } + /* model mesh vertex normal */ + else if (!_pico_stricmp(p->token,"*mesh_vertexnormal")) + { + int index; + + if( numVertices == 0 ) + _ase_error_return("Vertex parse error"); + + /* get vertex data (orig: index +y -x +z) */ + if (!_pico_parse_int( p,&index )) + _ase_error_return("Vertex parse error"); + if (!_pico_parse_vec( p,vertices[index].normal )) + _ase_error_return("Vertex parse error"); + } + /* model mesh face */ + else if (!_pico_stricmp(p->token,"*mesh_face")) + { + picoIndex_t indexes[3]; + int index; + + if( numFaces == 0 ) + _ase_error_return("Face parse error"); + + /* get face index */ + if (!_pico_parse_int( p,&index )) + _ase_error_return("Face parse error"); + + /* get 1st vertex index */ + _pico_parse( p,0 ); + if (!_pico_parse_int( p,&indexes[0] )) + _ase_error_return("Face parse error"); + + /* get 2nd vertex index */ + _pico_parse( p,0 ); + if (!_pico_parse_int( p,&indexes[1] )) + _ase_error_return("Face parse error"); + + /* get 3rd vertex index */ + _pico_parse( p,0 ); + if (!_pico_parse_int( p,&indexes[2] )) + _ase_error_return("Face parse error"); + + /* parse to the subMaterial ID */ + while ( 1 ) + { + if (!_pico_parse (p,0)) /* EOL */ + { + break; + } + if (!_pico_stricmp (p->token,"*MESH_SMOOTHING" )) + { + _pico_parse_int ( p , &faces[index].smoothingGroup ); + } + if (!_pico_stricmp (p->token,"*MESH_MTLID" )) + { + _pico_parse_int ( p , &faces[index].subMaterialId ); + } + } + + faces[index].materialId = 0; + faces[index].indices[0] = indexes[2]; + faces[index].indices[1] = indexes[1]; + faces[index].indices[2] = indexes[0]; + } + /* model texture vertex */ + else if (!_pico_stricmp(p->token,"*mesh_tvert")) + { + int index; + + if( numVertices == 0 ) + _ase_error_return("Texture Vertex parse error"); + + /* get uv vertex index */ + if (!_pico_parse_int( p,&index ) || index >= numTextureVertices) + _ase_error_return("Texture vertex parse error"); + + /* get uv vertex s */ + if (!_pico_parse_float( p,&texcoords[index].texcoord[0] )) + _ase_error_return("Texture vertex parse error"); + + /* get uv vertex t */ + if (!_pico_parse_float( p,&texcoords[index].texcoord[1] )) + _ase_error_return("Texture vertex parse error"); + + /* ydnar: invert t */ + texcoords[index].texcoord[ 1 ] = 1.0f - texcoords[index].texcoord[ 1 ]; + } + /* ydnar: model mesh texture face */ + else if( !_pico_stricmp( p->token, "*mesh_tface" ) ) + { + picoIndex_t indexes[3]; + int index; + + if( numFaces == 0 ) + _ase_error_return("Texture face parse error"); + + /* get face index */ + if (!_pico_parse_int( p,&index )) + _ase_error_return("Texture face parse error"); + + /* get 1st vertex index */ + if (!_pico_parse_int( p,&indexes[0] )) + _ase_error_return("Texture face parse error"); + + /* get 2nd vertex index */ + if (!_pico_parse_int( p,&indexes[1] )) + _ase_error_return("Texture face parse error"); + + /* get 3rd vertex index */ + if (!_pico_parse_int( p,&indexes[2] )) + _ase_error_return("Texture face parse error"); + + faces[index].indices[3] = indexes[2]; + faces[index].indices[4] = indexes[1]; + faces[index].indices[5] = indexes[0]; + } + /* model color vertex */ + else if (!_pico_stricmp(p->token,"*mesh_vertcol")) + { + int index; + float colorInput; + + if( numVertices == 0 ) + _ase_error_return("Color Vertex parse error"); + + /* get color vertex index */ + if (!_pico_parse_int( p,&index )) + _ase_error_return("Color vertex parse error"); + + /* get R component */ + if (!_pico_parse_float( p,&colorInput )) + _ase_error_return("Color vertex parse error"); + colors[index].color[0] = (picoByte_t)(colorInput * 255); + + /* get G component */ + if (!_pico_parse_float( p,&colorInput )) + _ase_error_return("Color vertex parse error"); + colors[index].color[1] = (picoByte_t)(colorInput * 255); + + /* get B component */ + if (!_pico_parse_float( p,&colorInput )) + _ase_error_return("Color vertex parse error"); + colors[index].color[2] = (picoByte_t)(colorInput * 255); + + /* leave alpha alone since we don't get any data from the ASE format */ + colors[index].color[3] = 255; + } + /* model color face */ + else if (!_pico_stricmp(p->token,"*mesh_cface")) + { + picoIndex_t indexes[3]; + int index; + + if( numFaces == 0 ) + _ase_error_return("Face parse error"); + + /* get face index */ + if (!_pico_parse_int( p,&index )) + _ase_error_return("Face parse error"); + + /* get 1st cvertex index */ + // _pico_parse( p,0 ); + if (!_pico_parse_int( p,&indexes[0] )) + _ase_error_return("Face parse error"); + + /* get 2nd cvertex index */ + // _pico_parse( p,0 ); + if (!_pico_parse_int( p,&indexes[1] )) + _ase_error_return("Face parse error"); + + /* get 3rd cvertex index */ + // _pico_parse( p,0 ); + if (!_pico_parse_int( p,&indexes[2] )) + _ase_error_return("Face parse error"); + + faces[index].indices[6] = indexes[2]; + faces[index].indices[7] = indexes[1]; + faces[index].indices[8] = indexes[0]; + } + /* model material */ + else if( !_pico_stricmp( p->token, "*material" ) ) + { + aseSubMaterial_t* subMaterial = NULL; + picoShader_t *shader; + int level = 1, index; + char materialName[ 1024 ]; + float transValue = 0.0f, shineValue = 1.0f; + picoColor_t ambientColor, diffuseColor, specularColor; + char *mapname = NULL; + int subMtlId, subMaterialLevel = -1; + + + /* get material index */ + _pico_parse_int( p,&index ); + + /* check brace */ + if (!_pico_parse_check(p,1,"{")) + _ase_error_return("Material missing opening brace"); + + /* parse material block */ + while( 1 ) + { + /* get next token */ + if (_pico_parse(p,1) == NULL) break; + if (!strlen(p->token)) continue; + + /* handle levels */ + if (p->token[0] == '{') level++; + if (p->token[0] == '}') level--; + if (!level) break; + + if( level == subMaterialLevel ) + { + /* set material name */ + _pico_first_token( materialName ); + shadername_convert(materialName); + PicoSetShaderName( shader, materialName); + + /* set shader's transparency */ + PicoSetShaderTransparency( shader,transValue ); + + /* set shader's ambient color */ + PicoSetShaderAmbientColor( shader,ambientColor ); + + /* set diffuse alpha to transparency */ + diffuseColor[3] = (picoByte_t)( transValue * 255.0 ); + + /* set shader's diffuse color */ + PicoSetShaderDiffuseColor( shader,diffuseColor ); + + /* set shader's specular color */ + PicoSetShaderSpecularColor( shader,specularColor ); + + /* set shader's shininess */ + PicoSetShaderShininess( shader,shineValue ); + + /* set material map name */ + PicoSetShaderMapName( shader, mapname ); + + subMaterial = _ase_add_submaterial( &materials, index, subMtlId, shader ); + subMaterialLevel = -1; + } + + /* parse submaterial index */ + if (!_pico_stricmp(p->token,"*submaterial")) + { + /* allocate new pico shader */ + _pico_parse_int( p , &subMtlId ); + + shader = PicoNewShader( model ); + if (shader == NULL) + { + PicoFreeModel( model ); + return NULL; + } + subMaterialLevel = level; + } + /* parse material name */ + else if (!_pico_stricmp(p->token,"*material_name")) + { + char* name = _pico_parse(p,0); + if ( name == NULL) + _ase_error_return("Missing material name"); + + strcpy ( materialName , name ); + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + /* parse material transparency */ + else if (!_pico_stricmp(p->token,"*material_transparency")) + { + /* get transparency value from ase */ + if (!_pico_parse_float( p,&transValue )) + _ase_error_return("Material transparency parse error"); + + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + /* parse material shininess */ + else if (!_pico_stricmp(p->token,"*material_shine")) + { + /* remark: + * - not sure but instead of '*material_shine' i might + * need to use '*material_shinestrength' */ + + /* get shine value from ase */ + if (!_pico_parse_float( p,&shineValue )) + _ase_error_return("Material shine parse error"); + + /* scale ase shine range 0..1 to pico range 0..127 */ + shineValue *= 128.0; + + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + /* parse ambient material color */ + else if (!_pico_stricmp(p->token,"*material_ambient")) + { + picoVec3_t vec; + /* get r,g,b float values from ase */ + if (!_pico_parse_vec( p,vec )) + _ase_error_return("Material color parse error"); + + /* setup 0..255 range color values */ + ambientColor[ 0 ] = (int)( vec[ 0 ] * 255.0 ); + ambientColor[ 1 ] = (int)( vec[ 1 ] * 255.0 ); + ambientColor[ 2 ] = (int)( vec[ 2 ] * 255.0 ); + ambientColor[ 3 ] = (int)( 255 ); + + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + /* parse diffuse material color */ + else if (!_pico_stricmp(p->token,"*material_diffuse")) + { + picoVec3_t vec; + + /* get r,g,b float values from ase */ + if (!_pico_parse_vec( p,vec )) + _ase_error_return("Material color parse error"); + + /* setup 0..255 range color */ + diffuseColor[ 0 ] = (int)( vec[ 0 ] * 255.0 ); + diffuseColor[ 1 ] = (int)( vec[ 1 ] * 255.0 ); + diffuseColor[ 2 ] = (int)( vec[ 2 ] * 255.0 ); + diffuseColor[ 3 ] = (int)( 255 ); + + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + /* parse specular material color */ + else if (!_pico_stricmp(p->token,"*material_specular")) + { + picoVec3_t vec; + + /* get r,g,b float values from ase */ + if (!_pico_parse_vec( p,vec )) + _ase_error_return("Material color parse error"); + + /* setup 0..255 range color */ + specularColor[ 0 ] = (int)( vec[ 0 ] * 255 ); + specularColor[ 1 ] = (int)( vec[ 1 ] * 255 ); + specularColor[ 2 ] = (int)( vec[ 2 ] * 255 ); + specularColor[ 3 ] = (int)( 255 ); + + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + /* material diffuse map */ + else if (!_pico_stricmp(p->token,"*map_diffuse") ) + { + int sublevel = 0; + + /* parse material block */ + while( 1 ) + { + /* get next token */ + if (_pico_parse(p,1) == NULL) break; + if (!strlen(p->token)) continue; + + /* handle levels */ + if (p->token[0] == '{') sublevel++; + if (p->token[0] == '}') sublevel--; + if (!sublevel) break; + + /* parse diffuse map bitmap */ + if (!_pico_stricmp(p->token,"*bitmap")) + { + char* name = _pico_parse(p,0); + if (name == NULL) + _ase_error_return("Missing material map bitmap name"); + mapname = _pico_alloc ( strlen ( name ) + 1 ); + strcpy ( mapname, name ); + /* skip rest and continue with next token */ + _pico_parse_skip_rest( p ); + continue; + } + } + } + /* end map_diffuse block */ + } + /* end material block */ + + if( subMaterial == NULL ) + { + /* allocate new pico shader */ + shader = PicoNewShader( model ); + if (shader == NULL) + { + PicoFreeModel( model ); + return NULL; + } + + /* set material name */ + shadername_convert(materialName); + PicoSetShaderName( shader,materialName ); + + /* set shader's transparency */ + PicoSetShaderTransparency( shader,transValue ); + + /* set shader's ambient color */ + PicoSetShaderAmbientColor( shader,ambientColor ); + + /* set diffuse alpha to transparency */ + diffuseColor[3] = (picoByte_t)( transValue * 255.0 ); + + /* set shader's diffuse color */ + PicoSetShaderDiffuseColor( shader,diffuseColor ); + + /* set shader's specular color */ + PicoSetShaderSpecularColor( shader,specularColor ); + + /* set shader's shininess */ + PicoSetShaderShininess( shader,shineValue ); + + /* set material map name */ + PicoSetShaderMapName( shader, mapname ); + + /* extract shadername from bitmap path */ + if(mapname != NULL) + { + char* p = mapname; + + /* convert to shader-name format */ + shadername_convert(mapname); + { + /* remove extension */ + char* last_period = strrchr(p, '.'); + if(last_period != NULL) + { + *last_period = '\0'; + } + } + + /* find shader path */ + for(; *p != '\0'; ++p) + { + if(_pico_strnicmp(p, "models/", 7) == 0 || _pico_strnicmp(p, "textures/", 9) == 0) + { + break; + } + } + + if(*p != '\0') + { + /* set material name */ + PicoSetShaderName( shader,p ); + } + } + + /* this is just a material with 1 submaterial */ + subMaterial = _ase_add_submaterial( &materials, index, 0, shader ); + } + + /* ydnar: free mapname */ + if( mapname != NULL ) + _pico_free( mapname ); + } // !_pico_stricmp ( "*material" ) + + /* skip unparsed rest of line and continue */ + _pico_parse_skip_rest( p ); + } + + /* ydnar: finish existing surface */ + _ase_submit_triangles(model, materials, vertices, texcoords, colors, faces, numFaces); + _pico_free(faces); + _pico_free(vertices); + _pico_free(texcoords); + _pico_free(colors); + +#ifdef DEBUG_PM_ASE + _ase_print_materials(materials); + finish = clock(); + elapsed = (double)(finish - start) / CLOCKS_PER_SEC; + _pico_printf( PICO_NORMAL, "Loaded model in in %-.2f second(s)\n", elapsed ); +#endif //DEBUG_PM_ASE + + _ase_free_materials(&materials); + + _pico_free_parser( p ); + + /* return allocated pico model */ + return model; +} + +/* pico file format module definition */ +const picoModule_t picoModuleASE = +{ + "1.0", /* module version string */ + "Autodesk 3DSMAX ASCII", /* module display name */ + "Jared Hefty, seaw0lf", /* author's name */ + "2003 Jared Hefty, 2002 seaw0lf", /* module copyright */ + { + "ase",NULL,NULL,NULL /* default extensions to use */ + }, + _ase_canload, /* validation routine */ + _ase_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/libs/picomodel/pm_fm.c b/libs/picomodel/pm_fm.c new file mode 100644 index 00000000..4e64fe6d --- /dev/null +++ b/libs/picomodel/pm_fm.c @@ -0,0 +1,667 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + +/* +Nurail: Used pm_md3.c (Randy Reddig) as a template. +*/ + +/* marker */ +#define PM_FM_C + +/* dependencies */ +#include "pm_fm.h" + +//#define FM_VERBOSE_DBG 0 +#undef FM_VERBOSE_DBG +#undef FM_DBG + +typedef struct index_LUT_s +{ + short Vert; + short ST; + struct index_LUT_s *next; + +} index_LUT_t; + +typedef struct index_DUP_LUT_s +{ + short ST; + short OldVert; + +} index_DUP_LUT_t; + + +// _fm_canload() +static int _fm_canload( PM_PARAMS_CANLOAD ) +{ + fm_t fm; + unsigned char *bb; + int fm_file_pos; + + bb = (unsigned char *) buffer; + + // Header + fm.fm_header_hdr = (fm_chunk_header_t *) bb; + fm_file_pos = sizeof(fm_chunk_header_t) + fm.fm_header_hdr->size; +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_VERBOSE, "IDENT: %s\n", (unsigned char *) fm.fm_header_hdr->ident ); +#endif + if( (strcmp(fm.fm_header_hdr->ident, FM_HEADERCHUNKNAME)) ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Header Ident incorrect\n"); +#endif + return PICO_PMV_ERROR_IDENT; + } + + // check fm + if( _pico_little_long( fm.fm_header_hdr->version ) != FM_HEADERCHUNKVER ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Header Version incorrect\n"); +#endif + return PICO_PMV_ERROR_VERSION; + } + + // Skin + fm.fm_skin_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t) + fm.fm_skin_hdr->size; +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_VERBOSE, "SKIN: %s\n", (unsigned char *) fm.fm_skin_hdr->ident ); +#endif + if( (strcmp(fm.fm_skin_hdr->ident, FM_SKINCHUNKNAME)) ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Skin Ident incorrect\n"); +#endif + return PICO_PMV_ERROR_IDENT; + } + + // check fm + if( _pico_little_long( fm.fm_skin_hdr->version ) != FM_SKINCHUNKVER ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Skin Version incorrect\n"); +#endif + return PICO_PMV_ERROR_VERSION; + } + + // st + fm.fm_st_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t) + fm.fm_st_hdr->size; +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_VERBOSE, "ST: %s\n", (unsigned char *) fm.fm_st_hdr->ident ); +#endif + if( (strcmp(fm.fm_st_hdr->ident, FM_STCOORDCHUNKNAME)) ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM ST Ident incorrect\n"); +#endif + return PICO_PMV_ERROR_IDENT; + } + + // check fm + if( _pico_little_long( fm.fm_st_hdr->version ) != FM_STCOORDCHUNKVER ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM ST Version incorrect\n"); +#endif + return PICO_PMV_ERROR_VERSION; + } + + // tri + fm.fm_tri_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t) + fm.fm_tri_hdr->size; +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_VERBOSE, "TRI: %s\n", (unsigned char *) fm.fm_tri_hdr->ident ); +#endif + if( (strcmp(fm.fm_tri_hdr->ident, FM_TRISCHUNKNAME)) ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Tri Ident incorrect\n"); +#endif + return PICO_PMV_ERROR_IDENT; + } + + // check fm + if( _pico_little_long( fm.fm_tri_hdr->version ) != FM_TRISCHUNKVER ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Tri Version incorrect\n"); +#endif + return PICO_PMV_ERROR_VERSION; + } + + // frame + fm.fm_frame_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t); +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_VERBOSE, "FRAME: %s\n", (unsigned char *) fm.fm_frame_hdr->ident ); +#endif + if( (strcmp(fm.fm_frame_hdr->ident, FM_FRAMESCHUNKNAME)) ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Frame Ident incorrect\n"); +#endif + return PICO_PMV_ERROR_IDENT; + } + + // check fm + if( _pico_little_long( fm.fm_frame_hdr->version ) != FM_FRAMESCHUNKVER ) + { +#ifdef FM_DBG + _pico_printf( PICO_WARNING, "FM Frame Version incorrect\n"); +#endif + return PICO_PMV_ERROR_VERSION; + } + + // file seems to be a valid fm + return PICO_PMV_OK; +} + + + +// _fm_load() loads a Heretic 2 model file. +static picoModel_t *_fm_load( PM_PARAMS_LOAD ) +{ + int i, j, dups, dup_index; + int fm_file_pos; + short tot_numVerts; + index_LUT_t *p_index_LUT, *p_index_LUT2, *p_index_LUT3; + index_DUP_LUT_t *p_index_LUT_DUPS; + + fm_vert_normal_t *vert; + + char skinname[FM_SKINPATHSIZE]; + fm_t fm; + fm_header_t *fm_head; + fm_st_t *texCoord; + fm_xyz_st_t *tri_verts; + fm_xyz_st_t *triangle; + fm_frame_t *frame; + + picoByte_t *bb; + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + + bb = (picoByte_t*) buffer; + + // Header Header + fm.fm_header_hdr = (fm_chunk_header_t *) bb; + fm_file_pos = sizeof(fm_chunk_header_t) + fm.fm_header_hdr->size; + if( (strcmp(fm.fm_header_hdr->ident, FM_HEADERCHUNKNAME)) ) + { + _pico_printf( PICO_WARNING, "FM Header Ident incorrect\n"); + return NULL; + } + + if( _pico_little_long( fm.fm_header_hdr->version ) != FM_HEADERCHUNKVER ) + { + _pico_printf( PICO_WARNING, "FM Header Version incorrect\n"); + return NULL; + } + + // Skin Header + fm.fm_skin_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t) + fm.fm_skin_hdr->size; + if( (strcmp(fm.fm_skin_hdr->ident, FM_SKINCHUNKNAME)) ) + { + _pico_printf( PICO_WARNING, "FM Skin Ident incorrect\n"); + return NULL; + } + + if( _pico_little_long( fm.fm_skin_hdr->version ) != FM_SKINCHUNKVER ) + { + _pico_printf( PICO_WARNING, "FM Skin Version incorrect\n"); + return NULL; + } + + // ST Header + fm.fm_st_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t) + fm.fm_st_hdr->size; + if( (strcmp(fm.fm_st_hdr->ident, FM_STCOORDCHUNKNAME)) ) + { + _pico_printf( PICO_WARNING, "FM ST Ident incorrect\n"); + return NULL; + } + + if( _pico_little_long( fm.fm_st_hdr->version ) != FM_STCOORDCHUNKVER ) + { + _pico_printf( PICO_WARNING, "FM ST Version incorrect\n"); + return NULL; + } + + // Tris Header + fm.fm_tri_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t) + fm.fm_tri_hdr->size; + if( (strcmp(fm.fm_tri_hdr->ident, FM_TRISCHUNKNAME)) ) + { + _pico_printf( PICO_WARNING, "FM Tri Ident incorrect\n"); + return NULL; + } + + if( _pico_little_long( fm.fm_tri_hdr->version ) != FM_TRISCHUNKVER ) + { + _pico_printf( PICO_WARNING, "FM Tri Version incorrect\n"); + return NULL; + } + + // Frame Header + fm.fm_frame_hdr = (fm_chunk_header_t *) (bb + fm_file_pos); + fm_file_pos += sizeof(fm_chunk_header_t); + if( (strcmp(fm.fm_frame_hdr->ident, FM_FRAMESCHUNKNAME)) ) + { + _pico_printf( PICO_WARNING, "FM Frame Ident incorrect\n"); + return NULL; + } + + if( _pico_little_long( fm.fm_frame_hdr->version ) != FM_FRAMESCHUNKVER ) + { + _pico_printf( PICO_WARNING, "FM Frame Version incorrect\n"); + return NULL; + } + + // Header + fm_file_pos = sizeof(fm_chunk_header_t); + fm_head = fm.fm_header = (fm_header_t *) (bb + fm_file_pos); + fm_file_pos += fm.fm_header_hdr->size; + + // Skin + fm_file_pos += sizeof(fm_chunk_header_t); + fm.fm_skin = (fm_skinpath_t *) (bb + fm_file_pos); + fm_file_pos += fm.fm_skin_hdr->size; + + // ST + fm_file_pos += sizeof(fm_chunk_header_t); + texCoord = fm.fm_st = (fm_st_t *) (bb + fm_file_pos); + fm_file_pos += fm.fm_st_hdr->size; + + // Tri + fm_file_pos += sizeof(fm_chunk_header_t); + tri_verts = fm.fm_tri = (fm_xyz_st_t *) (bb + fm_file_pos); + fm_file_pos += fm.fm_tri_hdr->size; + + // Frame + fm_file_pos += sizeof(fm_chunk_header_t); + frame = fm.fm_frame = (fm_frame_t *) (bb + fm_file_pos); + + // do frame check + if( fm_head->numFrames < 1 ) + { + _pico_printf( PICO_ERROR, "%s has 0 frames!", fileName ); + return NULL; + } + + if( frameNum < 0 || frameNum >= fm_head->numFrames ) + { + _pico_printf( PICO_ERROR, "Invalid or out-of-range FM frame specified" ); + return NULL; + } + + // swap fm + fm_head->skinWidth = _pico_little_long( fm_head->skinWidth ); + fm_head->skinHeight = _pico_little_long( fm_head->skinHeight ); + fm_head->frameSize = _pico_little_long( fm_head->frameSize ); + + fm_head->numSkins = _pico_little_long( fm_head->numSkins ); + fm_head->numXYZ = _pico_little_long( fm_head->numXYZ ); + fm_head->numST = _pico_little_long( fm_head->numST ); + fm_head->numTris = _pico_little_long( fm_head->numTris ); + fm_head->numGLCmds = _pico_little_long( fm_head->numGLCmds ); + fm_head->numFrames = _pico_little_long( fm_head->numFrames ); + + // swap frame scale and translation + for( i = 0; i < 3; i++ ) + { + frame->header.scale[ i ] = _pico_little_float( frame->header.scale[ i ] ); + frame->header.translate[ i ] = _pico_little_float( frame->header.translate[ i ] ); + } + + // swap triangles + triangle = tri_verts; + for( i = 0; i < fm_head->numTris; i++, triangle++ ) + { + for( j = 0; j < 3; j++ ) + { + triangle->index_xyz[ j ] = _pico_little_short( triangle->index_xyz[ j ] ); + triangle->index_st[ j ] = _pico_little_short( triangle->index_st[ j ] ); + } + } + + // swap st coords + for( i = 0; i < fm_head->numST; i++ ) + { + texCoord->s = _pico_little_short( texCoord[i].s ); + texCoord->t = _pico_little_short( texCoord[i].t ); + } + // set Skin Name + strncpy(skinname, (unsigned char *) fm.fm_skin, FM_SKINPATHSIZE ); + +#ifdef FM_VERBOSE_DBG + // Print out md2 values + _pico_printf(PICO_VERBOSE,"numSkins->%d numXYZ->%d numST->%d numTris->%d numFrames->%d\nSkin Name \"%s\"\n", fm_head->numSkins, fm_head->numXYZ, fm_head->numST, fm_head->numTris, fm_head->numFrames, &skinname ); +#endif + + // detox Skin name + _pico_setfext( skinname, "" ); + _pico_unixify( skinname ); + + /* create new pico model */ + picoModel = PicoNewModel(); + if( picoModel == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model" ); + return NULL; + } + + /* do model setup */ + PicoSetModelFrameNum( picoModel, frameNum ); + PicoSetModelNumFrames( picoModel, fm_head->numFrames ); /* sea */ + PicoSetModelName( picoModel, fileName ); + PicoSetModelFileName( picoModel, fileName ); + + // allocate new pico surface + picoSurface = PicoNewSurface( picoModel ); + if( picoSurface == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" ); + PicoFreeModel( picoModel ); + return NULL; + } + + + PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); + PicoSetSurfaceName( picoSurface, frame->header.name ); + picoShader = PicoNewShader( picoModel ); + if( picoShader == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" ); + PicoFreeModel( picoModel ); + return NULL; + } + + PicoSetShaderName( picoShader, skinname ); + + // associate current surface with newly created shader + PicoSetSurfaceShader( picoSurface, picoShader ); + + // Init LUT for Verts + p_index_LUT = (index_LUT_t *)_pico_alloc(sizeof(index_LUT_t) * fm_head->numXYZ); + for(i=0; inumXYZ; i++) + { + p_index_LUT[i].Vert = -1; + p_index_LUT[i].ST = -1; + p_index_LUT[i].next = NULL; + } + + // Fill in Look Up Table, and allocate/fill Linked List from vert array as needed for dup STs per Vert. + tot_numVerts = fm_head->numXYZ; + dups = 0; + triangle = tri_verts; + + for(i=0; inumTris; i++) + { + for(j=0; j<3; j++) + { + if (p_index_LUT[triangle->index_xyz[j]].ST == -1) // No Main Entry + p_index_LUT[triangle->index_xyz[j]].ST = triangle->index_st[j]; + + else if (triangle->index_st[j] == p_index_LUT[triangle->index_xyz[j]].ST ) // Equal to Main Entry + { +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, "-> Tri #%d, Vert %d:\t XYZ:%d ST:%d\n", i, j, triangle->index_xyz[j], triangle->index_st[j]); +#endif + continue; + } + else if ( (p_index_LUT[triangle->index_xyz[j]].next == NULL) ) // Not equal to Main entry, and no LL entry + { // Add first entry of LL from Main + p_index_LUT2 = (index_LUT_t *)_pico_alloc(sizeof(index_LUT_t)); + if (p_index_LUT2 == NULL) + _pico_printf( PICO_NORMAL, " Couldn't allocate memory!\n"); + p_index_LUT[triangle->index_xyz[j]].next = (index_LUT_t *)p_index_LUT2; + p_index_LUT2->Vert = dups; + p_index_LUT2->ST = triangle->index_st[j]; + p_index_LUT2->next = NULL; +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, " ADDING first LL XYZ:%d DUP:%d ST:%d\n", triangle->index_xyz[j], dups, triangle->index_st[j]); +#endif + triangle->index_xyz[j] = dups + fm_head->numXYZ; // Make change in Tri hunk + dups++; + } + else // Try to find in LL from Main Entry + { + p_index_LUT3 = p_index_LUT2 = p_index_LUT[triangle->index_xyz[j]].next; + while ( (p_index_LUT2 != NULL) && (triangle->index_xyz[j] != p_index_LUT2->Vert) ) // Walk down LL + { + p_index_LUT3 = p_index_LUT2; + p_index_LUT2 = p_index_LUT2->next; + } + p_index_LUT2 = p_index_LUT3; + + if ( triangle->index_st[j] == p_index_LUT2->ST ) // Found it + { + triangle->index_xyz[j] = p_index_LUT2->Vert + fm_head->numXYZ; // Make change in Tri hunk +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, "--> Tri #%d, Vert %d:\t XYZ:%d ST:%d\n", i, j, triangle->index_xyz[j], triangle->index_st[j]); +#endif + continue; + } + + if ( p_index_LUT2->next == NULL) // Didn't find it. Add entry to LL. + { + // Add the Entry + p_index_LUT3 = (index_LUT_t *)_pico_alloc(sizeof(index_LUT_t)); + if (p_index_LUT3 == NULL) + _pico_printf( PICO_NORMAL, " Couldn't allocate memory!\n"); + p_index_LUT2->next = (index_LUT_t *)p_index_LUT3; + p_index_LUT3->Vert = dups; + p_index_LUT3->ST = triangle->index_st[j]; + p_index_LUT3->next = NULL; +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, " ADDING additional LL XYZ:%d DUP:%d NewXYZ:%d ST:%d\n", triangle->index_xyz[j], dups, dups + (fm_head->numXYZ), triangle->index_st[j]); +#endif + triangle->index_xyz[j] = dups + fm_head->numXYZ; // Make change in Tri hunk + dups++; + } + } +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, "---> Tri #%d, Vert %d:\t XYZ:%d ST:%d\n", i, j, triangle->index_xyz[j], triangle->index_st[j]); +#endif + } + triangle++; + } + + // malloc and build array for Dup STs + p_index_LUT_DUPS = (index_DUP_LUT_t *)_pico_alloc(sizeof(index_DUP_LUT_t) * dups); + if (p_index_LUT_DUPS == NULL) + _pico_printf( PICO_NORMAL, " Couldn't allocate memory!\n"); + + dup_index = 0; + for(i=0; inumXYZ; i++) + { + p_index_LUT2 = p_index_LUT[i].next; + while (p_index_LUT2 != NULL) + { + p_index_LUT_DUPS[p_index_LUT2->Vert].OldVert = i; + p_index_LUT_DUPS[p_index_LUT2->Vert].ST = p_index_LUT2->ST; + dup_index++; + p_index_LUT2 = p_index_LUT2->next; + } + } +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, " Dups = %d\n", dups); + _pico_printf( PICO_NORMAL, " Dup Index = %d\n", dup_index); +#endif + for(i=0; inumXYZ; i++) + { +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, "Vert: %4d\t%4d",i, p_index_LUT[i].ST); +#endif + if (p_index_LUT[i].next != NULL) + { + + p_index_LUT2 = p_index_LUT[i].next; + do { +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, " %4d %4d", p_index_LUT2->Vert, p_index_LUT2->ST); +#endif + p_index_LUT2 = p_index_LUT2->next; + } while ( p_index_LUT2 != NULL); + + } +#ifdef FM_VERBOSE_DBG + _pico_printf( PICO_NORMAL, "\n"); +#endif + } + + +#ifdef FM_VERBOSE_DBG + for(i=0; inumTris; i++) + { + for(j=0; j<3; j++) + _pico_printf( PICO_NORMAL, "Tri #%d, Vert %d:\t XYZ:%d ST:%d\n", i, j, triangle->index_xyz[j], triangle->index_st[j]); + _pico_printf( PICO_NORMAL, "\n"); + triangle++; + } +#endif + // Build Picomodel + triangle = tri_verts; + for( j = 0; j < fm_head->numTris; j++, triangle++ ) + { + PicoSetSurfaceIndex( picoSurface, j*3 , triangle->index_xyz[0] ); + PicoSetSurfaceIndex( picoSurface, j*3+1 , triangle->index_xyz[1] ); + PicoSetSurfaceIndex( picoSurface, j*3+2 , triangle->index_xyz[2] ); + } + + vert = (fm_vert_normal_t*) ((picoByte_t*) (frame->verts) ); + for(i=0; i< fm_head->numXYZ; i++, vert++) + { + /* set vertex origin */ + xyz[ 0 ] = vert->v[0] * frame->header.scale[0] + frame->header.translate[0]; + xyz[ 1 ] = vert->v[1] * frame->header.scale[1] + frame->header.translate[1]; + xyz[ 2 ] = vert->v[2] * frame->header.scale[2] + frame->header.translate[2]; + PicoSetSurfaceXYZ( picoSurface, i , xyz ); + + /* set normal */ + normal[ 0 ] = fm_normals[vert->lightnormalindex][0]; + normal[ 1 ] = fm_normals[vert->lightnormalindex][1]; + normal[ 2 ] = fm_normals[vert->lightnormalindex][2]; + PicoSetSurfaceNormal( picoSurface, i , normal ); + + /* set st coords */ + st[ 0 ] = ((texCoord[p_index_LUT[i].ST].s) / ((float)fm_head->skinWidth)); + st[ 1 ] = (texCoord[p_index_LUT[i].ST].t / ((float)fm_head->skinHeight)); + PicoSetSurfaceST( picoSurface, 0, i , st ); + } + + if (dups) + { + for(i=0; iverts[j].v[0] * frame->header.scale[0] + frame->header.translate[0]; + xyz[ 1 ] = frame->verts[j].v[1] * frame->header.scale[1] + frame->header.translate[1]; + xyz[ 2 ] = frame->verts[j].v[2] * frame->header.scale[2] + frame->header.translate[2]; + PicoSetSurfaceXYZ( picoSurface, i + fm_head->numXYZ , xyz ); + + /* set normal */ + normal[ 0 ] = fm_normals[frame->verts[j].lightnormalindex][0]; + normal[ 1 ] = fm_normals[frame->verts[j].lightnormalindex][1]; + normal[ 2 ] = fm_normals[frame->verts[j].lightnormalindex][2]; + PicoSetSurfaceNormal( picoSurface, i + fm_head->numXYZ , normal ); + + /* set st coords */ + st[ 0 ] = ((texCoord[p_index_LUT_DUPS[i].ST].s) / ((float)fm_head->skinWidth)); + st[ 1 ] = (texCoord[p_index_LUT_DUPS[i].ST].t / ((float)fm_head->skinHeight)); + PicoSetSurfaceST( picoSurface, 0, i + fm_head->numXYZ , st ); + } + } + + /* set color */ + PicoSetSurfaceColor( picoSurface, 0, 0, color ); + + // Free up malloc'ed LL entries + for(i=0; inumXYZ; i++) + { + if(p_index_LUT[i].next != NULL) + { + p_index_LUT2 = p_index_LUT[i].next; + do { + p_index_LUT3 = p_index_LUT2->next; + _pico_free(p_index_LUT2); + p_index_LUT2 = p_index_LUT3; + dups--; + } while (p_index_LUT2 != NULL); + } + } + + if (dups) + _pico_printf(PICO_WARNING, " Not all LL mallocs freed\n"); + + // Free malloc'ed LUTs + _pico_free(p_index_LUT); + _pico_free(p_index_LUT_DUPS); + + /* return the new pico model */ + return picoModel; + +} + + + +/* pico file format module definition */ +const picoModule_t picoModuleFM = +{ + "0.85", /* module version string */ + "Heretic 2 FM", /* module display name */ + "Nurail", /* author's name */ + "2003 Nurail", /* module copyright */ + { + "fm", NULL, NULL, NULL /* default extensions to use */ + }, + _fm_canload, /* validation routine */ + _fm_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/libs/picomodel/pm_fm.h b/libs/picomodel/pm_fm.h new file mode 100644 index 00000000..3fd5148e --- /dev/null +++ b/libs/picomodel/pm_fm.h @@ -0,0 +1,367 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + +// This header file is based from the following: + +/* + FlexModel.H - Header file for FlexModel file structure + + By Chris Burke + serotonin@earthlink.net +*/ + +#ifndef __PM_FM_H__ +#define __PM_FM_H__ + +#include "picointernal.h" + + +// +// Absolute limits (from QData / QMView source) +// +#define MAX_FM_TRIANGLES 2048 +#define MAX_FM_VERTS 2048 +#define MAX_FM_FRAMES 2048 +#define MAX_FM_SKINS 64 +#define MAX_FM_SKINNAME 64 +#define MAX_FM_MESH_NODES 16 + +#define DTRIVERTX_V0 0 +#define DTRIVERTX_V1 1 +#define DTRIVERTX_V2 2 +#define DTRIVERTX_LNI 3 +#define DTRIVERTX_SIZE 4 + +#define SKINPAGE_WIDTH 640 +#define SKINPAGE_HEIGHT 480 + +#define ENCODED_WIDTH_X 92 +#define ENCODED_WIDTH_Y 475 +#define ENCODED_HEIGHT_X 128 +#define ENCODED_HEIGHT_Y 475 + +#define SCALE_ADJUST_FACTOR 0.96 + +#define INFO_HEIGHT 5 +#define INFO_Y (SKINPAGE_HEIGHT-INFO_HEIGHT) + +#ifndef byte + #define byte unsigned char +#endif + + +// +// Generic header on every chunk +// +#define FM_MAXCHUNKIDENT 32L +typedef struct +{ + char ident[FM_MAXCHUNKIDENT]; + unsigned int version; + unsigned int size; +} fm_chunk_header_t; + +// +// The format of the "header" chunk +// +#define FM_HEADERCHUNKNAME "header" +#define FM_HEADERCHUNKVER 2 +#define FM_HEADERCHUNKSIZE 40 +typedef struct +{ + int skinWidth; // in pixels + int skinHeight; // in pixels + int frameSize; // size of each frame (in bytes) + int numSkins; // number of skins + int numXYZ; // number of unique vertices in 3D space + int numST; // number of unique vertices in texture space + int numTris; // number of unique triangles + int numGLCmds; // # 32-bit elements in strip/fan command list + int numFrames; // number of animation frames + int numMeshNodes; // number of mesh nodes +} fm_header_t; + +// +// The format of an entry in the "skin" chunk. +// The number of entries is given in the fmheader chunk +// +#define FM_SKINCHUNKNAME "skin" +#define FM_SKINCHUNKVER 1 +#define FM_MAXPATHLENGTH 64L +#define FM_SKINPATHSIZE (FM_MAXPATHLENGTH) +typedef struct +{ + char path[FM_SKINPATHSIZE]; // path, relative to 'base' +} fm_skinpath_t; + +// +// The format of the "st coord" chunk. This is a list +// of unique skin texture (u, v) coordinates to be mapped +// to verteces of the model +// +#define FM_STCOORDCHUNKNAME "st coord" +#define FM_STCOORDCHUNKVER 1 +#define FM_STCOORDUVSIZE (2L + 2L) + +typedef struct +{ + short s; + short t; +} fm_st_t; + +// +// The format of the "tris" chunk. This is a list of vertex indeces +// in 3D space, and the corresponding vertex indeces in texture space. +// +#define FM_TRISCHUNKNAME "tris" +#define FM_TRISCHUNKVER 1 +#define FM_TRISINFOSIZE (2L*3 + 2L*3) + +typedef struct +{ + short index_xyz[3]; + short index_st[3]; +} fm_xyz_st_t; + + +// +// The format of the "frames" chunk. This is a list of animation +// frames, each specifying the coordinates and "light normal" index +// of every vertex of the model in 3D space. +// +#define FM_FRAMESCHUNKNAME "frames" +#define FM_FRAMESCHUNKVER 1 + +#define FM_NUMVERTEXNORMALS 162 + +// Frame info +typedef struct +{ + byte v[3]; // scaled by header info + byte lightnormalindex; // index in canned table of closest vertex normal +} fm_vert_normal_t; + +typedef struct +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name +} fm_framehdr_t; + +typedef struct +{ + fm_framehdr_t header; // One header per frame + fm_vert_normal_t verts[1]; // variable number of these +} fm_frame_t; + +typedef struct +{ + fm_chunk_header_t *fm_header_hdr; + fm_header_t *fm_header; + fm_chunk_header_t *fm_skin_hdr; + fm_skinpath_t *fm_skin; + fm_chunk_header_t *fm_st_hdr; + fm_st_t *fm_st; + fm_chunk_header_t *fm_tri_hdr; + fm_xyz_st_t *fm_tri; + fm_chunk_header_t *fm_frame_hdr; + fm_frame_t *fm_frame; +} fm_t; + +float fm_normals[FM_NUMVERTEXNORMALS][3] = { + {-0.525731f, 0.000000f, 0.850651f}, + {-0.442863f, 0.238856f, 0.864188f}, + {-0.295242f, 0.000000f, 0.955423f}, + {-0.309017f, 0.500000f, 0.809017f}, + {-0.162460f, 0.262866f, 0.951056f}, + {0.000000f, 0.000000f, 1.000000f}, + {0.000000f, 0.850651f, 0.525731f}, + {-0.147621f, 0.716567f, 0.681718f}, + {0.147621f, 0.716567f, 0.681718f}, + {0.000000f, 0.525731f, 0.850651f}, + {0.309017f, 0.500000f, 0.809017f}, + {0.525731f, 0.000000f, 0.850651f}, + {0.295242f, 0.000000f, 0.955423f}, + {0.442863f, 0.238856f, 0.864188f}, + {0.162460f, 0.262866f, 0.951056f}, + {-0.681718f, 0.147621f, 0.716567f}, + {-0.809017f, 0.309017f, 0.500000f}, + {-0.587785f, 0.425325f, 0.688191f}, + {-0.850651f, 0.525731f, 0.000000f}, + {-0.864188f, 0.442863f, 0.238856f}, + {-0.716567f, 0.681718f, 0.147621f}, + {-0.688191f, 0.587785f, 0.425325f}, + {-0.500000f, 0.809017f, 0.309017f}, + {-0.238856f, 0.864188f, 0.442863f}, + {-0.425325f, 0.688191f, 0.587785f}, + {-0.716567f, 0.681718f, -0.147621f}, + {-0.500000f, 0.809017f, -0.309017f}, + {-0.525731f, 0.850651f, 0.000000f}, + {0.000000f, 0.850651f, -0.525731f}, + {-0.238856f, 0.864188f, -0.442863f}, + {0.000000f, 0.955423f, -0.295242f}, + {-0.262866f, 0.951056f, -0.162460f}, + {0.000000f, 1.000000f, 0.000000f}, + {0.000000f, 0.955423f, 0.295242f}, + {-0.262866f, 0.951056f, 0.162460f}, + {0.238856f, 0.864188f, 0.442863f}, + {0.262866f, 0.951056f, 0.162460f}, + {0.500000f, 0.809017f, 0.309017f}, + {0.238856f, 0.864188f, -0.442863f}, + {0.262866f, 0.951056f, -0.162460f}, + {0.500000f, 0.809017f, -0.309017f}, + {0.850651f, 0.525731f, 0.000000f}, + {0.716567f, 0.681718f, 0.147621f}, + {0.716567f, 0.681718f, -0.147621f}, + {0.525731f, 0.850651f, 0.000000f}, + {0.425325f, 0.688191f, 0.587785f}, + {0.864188f, 0.442863f, 0.238856f}, + {0.688191f, 0.587785f, 0.425325f}, + {0.809017f, 0.309017f, 0.500000f}, + {0.681718f, 0.147621f, 0.716567f}, + {0.587785f, 0.425325f, 0.688191f}, + {0.955423f, 0.295242f, 0.000000f}, + {1.000000f, 0.000000f, 0.000000f}, + {0.951056f, 0.162460f, 0.262866f}, + {0.850651f, -0.525731f, 0.000000f}, + {0.955423f, -0.295242f, 0.000000f}, + {0.864188f, -0.442863f, 0.238856f}, + {0.951056f, -0.162460f, 0.262866f}, + {0.809017f, -0.309017f, 0.500000f}, + {0.681718f, -0.147621f, 0.716567f}, + {0.850651f, 0.000000f, 0.525731f}, + {0.864188f, 0.442863f, -0.238856f}, + {0.809017f, 0.309017f, -0.500000f}, + {0.951056f, 0.162460f, -0.262866f}, + {0.525731f, 0.000000f, -0.850651f}, + {0.681718f, 0.147621f, -0.716567f}, + {0.681718f, -0.147621f, -0.716567f}, + {0.850651f, 0.000000f, -0.525731f}, + {0.809017f, -0.309017f, -0.500000f}, + {0.864188f, -0.442863f, -0.238856f}, + {0.951056f, -0.162460f, -0.262866f}, + {0.147621f, 0.716567f, -0.681718f}, + {0.309017f, 0.500000f, -0.809017f}, + {0.425325f, 0.688191f, -0.587785f}, + {0.442863f, 0.238856f, -0.864188f}, + {0.587785f, 0.425325f, -0.688191f}, + {0.688191f, 0.587785f, -0.425325f}, + {-0.147621f, 0.716567f, -0.681718f}, + {-0.309017f, 0.500000f, -0.809017f}, + {0.000000f, 0.525731f, -0.850651f}, + {-0.525731f, 0.000000f, -0.850651f}, + {-0.442863f, 0.238856f, -0.864188f}, + {-0.295242f, 0.000000f, -0.955423f}, + {-0.162460f, 0.262866f, -0.951056f}, + {0.000000f, 0.000000f, -1.000000f}, + {0.295242f, 0.000000f, -0.955423f}, + {0.162460f, 0.262866f, -0.951056f}, + {-0.442863f, -0.238856f, -0.864188f}, + {-0.309017f, -0.500000f, -0.809017f}, + {-0.162460f, -0.262866f, -0.951056f}, + {0.000000f, -0.850651f, -0.525731f}, + {-0.147621f, -0.716567f, -0.681718f}, + {0.147621f, -0.716567f, -0.681718f}, + {0.000000f, -0.525731f, -0.850651f}, + {0.309017f, -0.500000f, -0.809017f}, + {0.442863f, -0.238856f, -0.864188f}, + {0.162460f, -0.262866f, -0.951056f}, + {0.238856f, -0.864188f, -0.442863f}, + {0.500000f, -0.809017f, -0.309017f}, + {0.425325f, -0.688191f, -0.587785f}, + {0.716567f, -0.681718f, -0.147621f}, + {0.688191f, -0.587785f, -0.425325f}, + {0.587785f, -0.425325f, -0.688191f}, + {0.000000f, -0.955423f, -0.295242f}, + {0.000000f, -1.000000f, 0.000000f}, + {0.262866f, -0.951056f, -0.162460f}, + {0.000000f, -0.850651f, 0.525731f}, + {0.000000f, -0.955423f, 0.295242f}, + {0.238856f, -0.864188f, 0.442863f}, + {0.262866f, -0.951056f, 0.162460f}, + {0.500000f, -0.809017f, 0.309017f}, + {0.716567f, -0.681718f, 0.147621f}, + {0.525731f, -0.850651f, 0.000000f}, + {-0.238856f, -0.864188f, -0.442863f}, + {-0.500000f, -0.809017f, -0.309017f}, + {-0.262866f, -0.951056f, -0.162460f}, + {-0.850651f, -0.525731f, 0.000000f}, + {-0.716567f, -0.681718f, -0.147621f}, + {-0.716567f, -0.681718f, 0.147621f}, + {-0.525731f, -0.850651f, 0.000000f}, + {-0.500000f, -0.809017f, 0.309017f}, + {-0.238856f, -0.864188f, 0.442863f}, + {-0.262866f, -0.951056f, 0.162460f}, + {-0.864188f, -0.442863f, 0.238856f}, + {-0.809017f, -0.309017f, 0.500000f}, + {-0.688191f, -0.587785f, 0.425325f}, + {-0.681718f, -0.147621f, 0.716567f}, + {-0.442863f, -0.238856f, 0.864188f}, + {-0.587785f, -0.425325f, 0.688191f}, + {-0.309017f, -0.500000f, 0.809017f}, + {-0.147621f, -0.716567f, 0.681718f}, + {-0.425325f, -0.688191f, 0.587785f}, + {-0.162460f, -0.262866f, 0.951056f}, + {0.442863f, -0.238856f, 0.864188f}, + {0.162460f, -0.262866f, 0.951056f}, + {0.309017f, -0.500000f, 0.809017f}, + {0.147621f, -0.716567f, 0.681718f}, + {0.000000f, -0.525731f, 0.850651f}, + {0.425325f, -0.688191f, 0.587785f}, + {0.587785f, -0.425325f, 0.688191f}, + {0.688191f, -0.587785f, 0.425325f}, + {-0.955423f, 0.295242f, 0.000000f}, + {-0.951056f, 0.162460f, 0.262866f}, + {-1.000000f, 0.000000f, 0.000000f}, + {-0.850651f, 0.000000f, 0.525731f}, + {-0.955423f, -0.295242f, 0.000000f}, + {-0.951056f, -0.162460f, 0.262866f}, + {-0.864188f, 0.442863f, -0.238856f}, + {-0.951056f, 0.162460f, -0.262866f}, + {-0.809017f, 0.309017f, -0.500000f}, + {-0.864188f, -0.442863f, -0.238856f}, + {-0.951056f, -0.162460f, -0.262866f}, + {-0.809017f, -0.309017f, -0.500000f}, + {-0.681718f, 0.147621f, -0.716567f}, + {-0.681718f, -0.147621f, -0.716567f}, + {-0.850651f, 0.000000f, -0.525731f}, + {-0.688191f, 0.587785f, -0.425325f}, + {-0.587785f, 0.425325f, -0.688191f}, + {-0.425325f, 0.688191f, -0.587785f}, + {-0.425325f, -0.688191f, -0.587785f}, + {-0.587785f, -0.425325f, -0.688191f}, + {-0.688191f, -0.587785f, -0.425325f}, +}; + +#endif diff --git a/libs/picomodel/pm_lwo.c b/libs/picomodel/pm_lwo.c new file mode 100644 index 00000000..e36bb6f2 --- /dev/null +++ b/libs/picomodel/pm_lwo.c @@ -0,0 +1,445 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + +/* marker */ +#define PM_LWO_C + +/* dependencies */ +#include "picointernal.h" +#include "lwo/lwo2.h" + +/* uncomment when debugging this module */ +/*#define DEBUG_PM_LWO*/ + +#ifdef DEBUG_PM_LWO +#include "time.h" +#endif + +/* helper functions */ +static const char *lwo_lwIDToStr( unsigned int lwID ) +{ + static char lwIDStr[5]; + + if (!lwID) + { + return "n/a"; + } + + lwIDStr[ 0 ] = (char)((lwID) >> 24); + lwIDStr[ 1 ] = (char)((lwID) >> 16); + lwIDStr[ 2 ] = (char)((lwID) >> 8); + lwIDStr[ 3 ] = (char)((lwID)); + lwIDStr[ 4 ] = '\0'; + + return lwIDStr; +} + +/* +_lwo_canload() +validates a LightWave Object model file. btw, i use the +preceding underscore cause it's a static func referenced +by one structure only. +*/ +static int _lwo_canload( PM_PARAMS_CANLOAD ) +{ + picoMemStream_t *s; + unsigned int failID = 0; + int failpos = -1; + int ret; + + /* create a new pico memorystream */ + s = _pico_new_memstream( (picoByte_t *)buffer, bufSize ); + if (s == NULL) + { + return PICO_PMV_ERROR_MEMORY; + } + + ret = lwValidateObject( fileName, s, &failID, &failpos ); + + _pico_free_memstream( s ); + + return ret; +} + +/* +_lwo_load() +loads a LightWave Object model file. +*/ +static picoModel_t *_lwo_load( PM_PARAMS_LOAD ) +{ + picoMemStream_t *s; + unsigned int failID = 0; + int failpos = -1; + lwObject *obj; + lwSurface *surface; + lwLayer *layer; + lwPoint *pt; + lwPolygon *pol; + lwPolVert *v; + lwVMapPt *vm; + char name[ 256 ]; + int i, j, k, numverts; + + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + int defaultSTAxis[ 2 ]; + picoVec2_t defaultXYZtoSTScale; + + picoVertexCombinationHash_t **hashTable; + picoVertexCombinationHash_t *vertexCombinationHash; + +#ifdef DEBUG_PM_LWO + clock_t load_start, load_finish, convert_start, convert_finish; + double load_elapsed, convert_elapsed; + + load_start = clock(); +#endif + + /* do frame check */ + if( frameNum < 0 || frameNum >= 1 ) + { + _pico_printf( PICO_ERROR, "Invalid or out-of-range LWO frame specified" ); + return NULL; + } + + /* create a new pico memorystream */ + s = _pico_new_memstream( (picoByte_t *)buffer, bufSize ); + if (s == NULL) + { + return NULL; + } + + obj = lwGetObject( fileName, s, &failID, &failpos ); + + _pico_free_memstream( s ); + + if( !obj ) { + _pico_printf( PICO_ERROR, "Couldn't load LWO file, failed on ID '%s', position %d", lwo_lwIDToStr( failID ), failpos ); + return NULL; + } + +#ifdef DEBUG_PM_LWO + convert_start = load_finish = clock(); + load_elapsed = (double)(load_finish - load_start) / CLOCKS_PER_SEC; +#endif + + /* ------------------------------------------------- + pico model creation + ------------------------------------------------- */ + + /* create a new pico model */ + picoModel = PicoNewModel(); + if (picoModel == NULL) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model" ); + return NULL; + } + + /* do model setup */ + PicoSetModelFrameNum( picoModel, frameNum ); + PicoSetModelNumFrames( picoModel, 1 ); + PicoSetModelName( picoModel, fileName ); + PicoSetModelFileName( picoModel, fileName ); + + /* create all polygons from layer[ 0 ] that belong to this surface */ + layer = &obj->layer[0]; + + /* warn the user that other layers are discarded */ + if (obj->nlayers > 1) + { + _pico_printf( PICO_WARNING, "LWO loader discards any geometry data not in Layer 1 (%d layers found)", obj->nlayers ); + } + + /* initialize dummy normal */ + normal[ 0 ] = normal[ 1 ] = normal[ 2 ] = 0.f; + + /* setup default st map */ + st[ 0 ] = st[ 1 ] = 0.f; /* st[0] holds max, st[1] holds max par one */ + defaultSTAxis[ 0 ] = 0; + defaultSTAxis[ 1 ] = 1; + for( i = 0; i < 3; i++ ) + { + float min = layer->bbox[ i ]; + float max = layer->bbox[ i + 3 ]; + float size = max - min; + + if (size > st[ 0 ]) + { + defaultSTAxis[ 1 ] = defaultSTAxis[ 0 ]; + defaultSTAxis[ 0 ] = i; + + st[ 1 ] = st[ 0 ]; + st[ 0 ] = size; + } + else if (size > st[ 1 ]) + { + defaultSTAxis[ 1 ] = i; + st[ 1 ] = size; + } + } + defaultXYZtoSTScale[ 0 ] = 4.f / st[ 0 ]; + defaultXYZtoSTScale[ 1 ] = 4.f / st[ 1 ]; + + /* LWO surfaces become pico surfaces */ + surface = obj->surf; + while (surface) + { + /* allocate new pico surface */ + picoSurface = PicoNewSurface( picoModel ); + if (picoSurface == NULL) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" ); + PicoFreeModel( picoModel ); + lwFreeObject( obj ); + return NULL; + } + + /* LWO model surfaces are all triangle meshes */ + PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); + + /* set surface name */ + PicoSetSurfaceName( picoSurface, surface->name ); + + /* create new pico shader */ + picoShader = PicoNewShader( picoModel ); + if (picoShader == NULL) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" ); + PicoFreeModel( picoModel ); + lwFreeObject( obj ); + return NULL; + } + + /* detox and set shader name */ + strncpy( name, surface->name, sizeof(name) ); + _pico_first_token( name ); + _pico_setfext( name, "" ); + _pico_unixify( name ); + PicoSetShaderName( picoShader, name ); + + /* associate current surface with newly created shader */ + PicoSetSurfaceShader( picoSurface, picoShader ); + + /* copy indices and vertex data */ + numverts = 0; + + hashTable = PicoNewVertexCombinationHashTable(); + + if (hashTable == NULL) + { + _pico_printf( PICO_ERROR, "Unable to allocate hash table" ); + PicoFreeModel( picoModel ); + lwFreeObject( obj ); + return NULL; + } + + for( i = 0, pol = layer->polygon.pol; i < layer->polygon.count; i++, pol++ ) + { + /* does this polygon belong to this surface? */ + if (pol->surf != surface) + continue; + + /* we only support polygons of the FACE type */ + if (pol->type != ID_FACE) + { + _pico_printf( PICO_WARNING, "LWO loader discarded a polygon because it's type != FACE (%s)", lwo_lwIDToStr( pol->type ) ); + continue; + } + + /* NOTE: LWO has support for non-convex polygons, do we want to store them as well? */ + if (pol->nverts != 3) + { + _pico_printf( PICO_WARNING, "LWO loader discarded a polygon because it has != 3 verts (%d)", pol->nverts ); + continue; + } + + for( j = 0, v = pol->v; j < 3; j++, v++ ) + { + pt = &layer->point.pt[ v->index ]; + + /* setup data */ + xyz[ 0 ] = pt->pos[ 0 ]; + xyz[ 1 ] = pt->pos[ 2 ]; + xyz[ 2 ] = pt->pos[ 1 ]; + +/* doom3 lwo data doesn't seem to have smoothing-angle information */ +#if 0 + if(surface->smooth <= 0) + { + /* use face normals */ + normal[ 0 ] = v->norm[ 0 ]; + normal[ 1 ] = v->norm[ 2 ]; + normal[ 2 ] = v->norm[ 1 ]; + } + else +#endif + { + /* smooth normals later */ + normal[ 0 ] = 0; + normal[ 1 ] = 0; + normal[ 2 ] = 0; + } + + st[ 0 ] = xyz[ defaultSTAxis[ 0 ] ] * defaultXYZtoSTScale[ 0 ]; + st[ 1 ] = xyz[ defaultSTAxis[ 1 ] ] * defaultXYZtoSTScale[ 1 ]; + + color[ 0 ] = (picoByte_t)(surface->color.rgb[ 0 ] * surface->diffuse.val * 0xFF); + color[ 1 ] = (picoByte_t)(surface->color.rgb[ 1 ] * surface->diffuse.val * 0xFF); + color[ 2 ] = (picoByte_t)(surface->color.rgb[ 2 ] * surface->diffuse.val * 0xFF); + color[ 3 ] = 0xFF; + + /* set from points */ + for( k = 0, vm = pt->vm; k < pt->nvmaps; k++, vm++ ) + { + if (vm->vmap->type == LWID_('T','X','U','V')) + { + /* set st coords */ + st[ 0 ] = vm->vmap->val[ vm->index ][ 0 ]; + st[ 1 ] = 1.f - vm->vmap->val[ vm->index ][ 1 ]; + } + else if (vm->vmap->type == LWID_('R','G','B','A')) + { + /* set rgba */ + color[ 0 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 0 ] * surface->color.rgb[ 0 ] * surface->diffuse.val * 0xFF); + color[ 1 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 1 ] * surface->color.rgb[ 1 ] * surface->diffuse.val * 0xFF); + color[ 2 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 2 ] * surface->color.rgb[ 2 ] * surface->diffuse.val * 0xFF); + color[ 3 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 3 ] * 0xFF); + } + } + + /* override with polygon data */ + for( k = 0, vm = v->vm; k < v->nvmaps; k++, vm++ ) + { + if (vm->vmap->type == LWID_('T','X','U','V')) + { + /* set st coords */ + st[ 0 ] = vm->vmap->val[ vm->index ][ 0 ]; + st[ 1 ] = 1.f - vm->vmap->val[ vm->index ][ 1 ]; + } + else if (vm->vmap->type == LWID_('R','G','B','A')) + { + /* set rgba */ + color[ 0 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 0 ] * surface->color.rgb[ 0 ] * surface->diffuse.val * 0xFF); + color[ 1 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 1 ] * surface->color.rgb[ 1 ] * surface->diffuse.val * 0xFF); + color[ 2 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 2 ] * surface->color.rgb[ 2 ] * surface->diffuse.val * 0xFF); + color[ 3 ] = (picoByte_t)(vm->vmap->val[ vm->index ][ 3 ] * 0xFF); + } + } + + /* find vertex in this surface and if we can't find it there create it */ + vertexCombinationHash = PicoFindVertexCombinationInHashTable( hashTable, xyz, normal, st, color ); + + if (vertexCombinationHash) + { + /* found an existing one */ + PicoSetSurfaceIndex( picoSurface, (i * 3 + j ), vertexCombinationHash->index ); + } + else + { + /* it is a new one */ + vertexCombinationHash = PicoAddVertexCombinationToHashTable( hashTable, xyz, normal, st, color, (picoIndex_t) numverts ); + + if (vertexCombinationHash == NULL) + { + _pico_printf( PICO_ERROR, "Unable to allocate hash bucket entry table" ); + PicoFreeVertexCombinationHashTable( hashTable ); + PicoFreeModel( picoModel ); + lwFreeObject( obj ); + return NULL; + } + + /* add the vertex to this surface */ + PicoSetSurfaceXYZ( picoSurface, numverts, xyz ); + + /* set dummy normal */ + PicoSetSurfaceNormal( picoSurface, numverts, normal ); + + /* set color */ + PicoSetSurfaceColor( picoSurface, 0, numverts, color ); + + /* set st coords */ + PicoSetSurfaceST( picoSurface, 0, numverts, st ); + + /* set index */ + PicoSetSurfaceIndex( picoSurface, (i * 3 + j ), (picoIndex_t) numverts ); + + numverts++; + } + } + } + + /* free the hashtable */ + PicoFreeVertexCombinationHashTable( hashTable ); + + /* get next surface */ + surface = surface->next; + } + +#ifdef DEBUG_PM_LWO + load_start = convert_finish = clock(); +#endif + + lwFreeObject( obj ); + +#ifdef DEBUG_PM_LWO + load_finish = clock(); + load_elapsed += (double)(load_finish - load_start) / CLOCKS_PER_SEC; + convert_elapsed = (double)(convert_finish - convert_start) / CLOCKS_PER_SEC; + _pico_printf( PICO_NORMAL, "Loaded model in in %-.2f second(s) (loading: %-.2fs converting: %-.2fs)\n", load_elapsed + convert_elapsed, load_elapsed, convert_elapsed ); +#endif + + /* return the new pico model */ + return picoModel; +} + +/* pico file format module definition */ +const picoModule_t picoModuleLWO = +{ + "1.0", /* module version string */ + "LightWave Object", /* module display name */ + "Arnout van Meer", /* author's name */ + "2003 Arnout van Meer, 2000 Ernie Wright", /* module copyright */ + { + "lwo", NULL, NULL, NULL /* default extensions to use */ + }, + _lwo_canload, /* validation routine */ + _lwo_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/libs/picomodel/pm_md2.c b/libs/picomodel/pm_md2.c new file mode 100644 index 00000000..bf78a99b --- /dev/null +++ b/libs/picomodel/pm_md2.c @@ -0,0 +1,667 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + +/* +Nurail: Used pm_md3.c (Randy Reddig) as a template. +*/ + + +/* marker */ +#define PM_MD2_C + +/* dependencies */ +#include "picointernal.h" + + +/* md2 model format */ +#define MD2_MAGIC "IDP2" +#define MD2_VERSION 8 + +#define MD2_NUMVERTEXNORMALS 162 +#define MD2_MAX_SKINNAME 64 +#define MD2_MAX_TRIANGLES 4096 +#define MD2_MAX_VERTS 2048 +#define MD2_MAX_FRAMES 512 +#define MD2_MAX_MD2SKINS 32 +#define MD2_MAX_SKINNAME 64 + +#ifndef byte + #define byte unsigned char +#endif + +typedef struct index_LUT_s +{ + short Vert; + short ST; + struct index_LUT_s *next; + +} index_LUT_t; + +typedef struct index_DUP_LUT_s +{ + short ST; + short OldVert; + +} index_DUP_LUT_t; + +typedef struct +{ + short s; + short t; +} md2St_t; + +typedef struct +{ + short index_xyz[3]; + short index_st[3]; +} md2Triangle_t; + +typedef struct +{ + byte v[3]; // scaled byte to fit in frame mins/maxs + byte lightnormalindex; +} md2XyzNormal_t; + +typedef struct md2Frame_s +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name from grabbing + md2XyzNormal_t verts[1]; // variable sized +} +md2Frame_t; + + +/* md2 model file md2 structure */ +typedef struct md2_s +{ + char magic[ 4 ]; + int version; + + int skinWidth; + int skinHeight; + int frameSize; + + int numSkins; + int numXYZ; + int numST; + int numTris; + int numGLCmds; + int numFrames; + + int ofsSkins; + int ofsST; + int ofsTris; + int ofsFrames; + int ofsGLCmds; + int ofsEnd; +} +md2_t; + +float md2_normals[ MD2_NUMVERTEXNORMALS ][ 3 ] = +{ + { -0.525731f, 0.000000f, 0.850651f }, + { -0.442863f, 0.238856f, 0.864188f }, + { -0.295242f, 0.000000f, 0.955423f }, + { -0.309017f, 0.500000f, 0.809017f }, + { -0.162460f, 0.262866f, 0.951056f }, + { 0.000000f, 0.000000f, 1.000000f }, + { 0.000000f, 0.850651f, 0.525731f }, + { -0.147621f, 0.716567f, 0.681718f }, + { 0.147621f, 0.716567f, 0.681718f }, + { 0.000000f, 0.525731f, 0.850651f }, + { 0.309017f, 0.500000f, 0.809017f }, + { 0.525731f, 0.000000f, 0.850651f }, + { 0.295242f, 0.000000f, 0.955423f }, + { 0.442863f, 0.238856f, 0.864188f }, + { 0.162460f, 0.262866f, 0.951056f }, + { -0.681718f, 0.147621f, 0.716567f }, + { -0.809017f, 0.309017f, 0.500000f }, + { -0.587785f, 0.425325f, 0.688191f }, + { -0.850651f, 0.525731f, 0.000000f }, + { -0.864188f, 0.442863f, 0.238856f }, + { -0.716567f, 0.681718f, 0.147621f }, + { -0.688191f, 0.587785f, 0.425325f }, + { -0.500000f, 0.809017f, 0.309017f }, + { -0.238856f, 0.864188f, 0.442863f }, + { -0.425325f, 0.688191f, 0.587785f }, + { -0.716567f, 0.681718f, -0.147621f }, + { -0.500000f, 0.809017f, -0.309017f }, + { -0.525731f, 0.850651f, 0.000000f }, + { 0.000000f, 0.850651f, -0.525731f }, + { -0.238856f, 0.864188f, -0.442863f }, + { 0.000000f, 0.955423f, -0.295242f }, + { -0.262866f, 0.951056f, -0.162460f }, + { 0.000000f, 1.000000f, 0.000000f }, + { 0.000000f, 0.955423f, 0.295242f }, + { -0.262866f, 0.951056f, 0.162460f }, + { 0.238856f, 0.864188f, 0.442863f }, + { 0.262866f, 0.951056f, 0.162460f }, + { 0.500000f, 0.809017f, 0.309017f }, + { 0.238856f, 0.864188f, -0.442863f }, + { 0.262866f, 0.951056f, -0.162460f }, + { 0.500000f, 0.809017f, -0.309017f }, + { 0.850651f, 0.525731f, 0.000000f }, + { 0.716567f, 0.681718f, 0.147621f }, + { 0.716567f, 0.681718f, -0.147621f }, + { 0.525731f, 0.850651f, 0.000000f }, + { 0.425325f, 0.688191f, 0.587785f }, + { 0.864188f, 0.442863f, 0.238856f }, + { 0.688191f, 0.587785f, 0.425325f }, + { 0.809017f, 0.309017f, 0.500000f }, + { 0.681718f, 0.147621f, 0.716567f }, + { 0.587785f, 0.425325f, 0.688191f }, + { 0.955423f, 0.295242f, 0.000000f }, + { 1.000000f, 0.000000f, 0.000000f }, + { 0.951056f, 0.162460f, 0.262866f }, + { 0.850651f, -0.525731f, 0.000000f }, + { 0.955423f, -0.295242f, 0.000000f }, + { 0.864188f, -0.442863f, 0.238856f }, + { 0.951056f, -0.162460f, 0.262866f }, + { 0.809017f, -0.309017f, 0.500000f }, + { 0.681718f, -0.147621f, 0.716567f }, + { 0.850651f, 0.000000f, 0.525731f }, + { 0.864188f, 0.442863f, -0.238856f }, + { 0.809017f, 0.309017f, -0.500000f }, + { 0.951056f, 0.162460f, -0.262866f }, + { 0.525731f, 0.000000f, -0.850651f }, + { 0.681718f, 0.147621f, -0.716567f }, + { 0.681718f, -0.147621f, -0.716567f }, + { 0.850651f, 0.000000f, -0.525731f }, + { 0.809017f, -0.309017f, -0.500000f }, + { 0.864188f, -0.442863f, -0.238856f }, + { 0.951056f, -0.162460f, -0.262866f }, + { 0.147621f, 0.716567f, -0.681718f }, + { 0.309017f, 0.500000f, -0.809017f }, + { 0.425325f, 0.688191f, -0.587785f }, + { 0.442863f, 0.238856f, -0.864188f }, + { 0.587785f, 0.425325f, -0.688191f }, + { 0.688191f, 0.587785f, -0.425325f }, + { -0.147621f, 0.716567f, -0.681718f }, + { -0.309017f, 0.500000f, -0.809017f }, + { 0.000000f, 0.525731f, -0.850651f }, + { -0.525731f, 0.000000f, -0.850651f }, + { -0.442863f, 0.238856f, -0.864188f }, + { -0.295242f, 0.000000f, -0.955423f }, + { -0.162460f, 0.262866f, -0.951056f }, + { 0.000000f, 0.000000f, -1.000000f }, + { 0.295242f, 0.000000f, -0.955423f }, + { 0.162460f, 0.262866f, -0.951056f }, + { -0.442863f, -0.238856f, -0.864188f }, + { -0.309017f, -0.500000f, -0.809017f }, + { -0.162460f, -0.262866f, -0.951056f }, + { 0.000000f, -0.850651f, -0.525731f }, + { -0.147621f, -0.716567f, -0.681718f }, + { 0.147621f, -0.716567f, -0.681718f }, + { 0.000000f, -0.525731f, -0.850651f }, + { 0.309017f, -0.500000f, -0.809017f }, + { 0.442863f, -0.238856f, -0.864188f }, + { 0.162460f, -0.262866f, -0.951056f }, + { 0.238856f, -0.864188f, -0.442863f }, + { 0.500000f, -0.809017f, -0.309017f }, + { 0.425325f, -0.688191f, -0.587785f }, + { 0.716567f, -0.681718f, -0.147621f }, + { 0.688191f, -0.587785f, -0.425325f }, + { 0.587785f, -0.425325f, -0.688191f }, + { 0.000000f, -0.955423f, -0.295242f }, + { 0.000000f, -1.000000f, 0.000000f }, + { 0.262866f, -0.951056f, -0.162460f }, + { 0.000000f, -0.850651f, 0.525731f }, + { 0.000000f, -0.955423f, 0.295242f }, + { 0.238856f, -0.864188f, 0.442863f }, + { 0.262866f, -0.951056f, 0.162460f }, + { 0.500000f, -0.809017f, 0.309017f }, + { 0.716567f, -0.681718f, 0.147621f }, + { 0.525731f, -0.850651f, 0.000000f }, + { -0.238856f, -0.864188f, -0.442863f }, + { -0.500000f, -0.809017f, -0.309017f }, + { -0.262866f, -0.951056f, -0.162460f }, + { -0.850651f, -0.525731f, 0.000000f }, + { -0.716567f, -0.681718f, -0.147621f }, + { -0.716567f, -0.681718f, 0.147621f }, + { -0.525731f, -0.850651f, 0.000000f }, + { -0.500000f, -0.809017f, 0.309017f }, + { -0.238856f, -0.864188f, 0.442863f }, + { -0.262866f, -0.951056f, 0.162460f }, + { -0.864188f, -0.442863f, 0.238856f }, + { -0.809017f, -0.309017f, 0.500000f }, + { -0.688191f, -0.587785f, 0.425325f }, + { -0.681718f, -0.147621f, 0.716567f }, + { -0.442863f, -0.238856f, 0.864188f }, + { -0.587785f, -0.425325f, 0.688191f }, + { -0.309017f, -0.500000f, 0.809017f }, + { -0.147621f, -0.716567f, 0.681718f }, + { -0.425325f, -0.688191f, 0.587785f }, + { -0.162460f, -0.262866f, 0.951056f }, + { 0.442863f, -0.238856f, 0.864188f }, + { 0.162460f, -0.262866f, 0.951056f }, + { 0.309017f, -0.500000f, 0.809017f }, + { 0.147621f, -0.716567f, 0.681718f }, + { 0.000000f, -0.525731f, 0.850651f }, + { 0.425325f, -0.688191f, 0.587785f }, + { 0.587785f, -0.425325f, 0.688191f }, + { 0.688191f, -0.587785f, 0.425325f }, + { -0.955423f, 0.295242f, 0.000000f }, + { -0.951056f, 0.162460f, 0.262866f }, + { -1.000000f, 0.000000f, 0.000000f }, + { -0.850651f, 0.000000f, 0.525731f }, + { -0.955423f, -0.295242f, 0.000000f }, + { -0.951056f, -0.162460f, 0.262866f }, + { -0.864188f, 0.442863f, -0.238856f }, + { -0.951056f, 0.162460f, -0.262866f }, + { -0.809017f, 0.309017f, -0.500000f }, + { -0.864188f, -0.442863f, -0.238856f }, + { -0.951056f, -0.162460f, -0.262866f }, + { -0.809017f, -0.309017f, -0.500000f }, + { -0.681718f, 0.147621f, -0.716567f }, + { -0.681718f, -0.147621f, -0.716567f }, + { -0.850651f, 0.000000f, -0.525731f }, + { -0.688191f, 0.587785f, -0.425325f }, + { -0.587785f, 0.425325f, -0.688191f }, + { -0.425325f, 0.688191f, -0.587785f }, + { -0.425325f, -0.688191f, -0.587785f }, + { -0.587785f, -0.425325f, -0.688191f }, + { -0.688191f, -0.587785f, -0.425325f }, +}; + + +// _md2_canload() + +static int _md2_canload( PM_PARAMS_CANLOAD ) +{ + md2_t *md2; + + /* to keep the compiler happy */ + *fileName = *fileName; + + /* sanity check */ + if( bufSize < ( sizeof( *md2 ) * 2) ) + return PICO_PMV_ERROR_SIZE; + + /* set as md2 */ + md2 = (md2_t*) buffer; + + /* check md2 magic */ + if( *((int*) md2->magic) != *((int*) MD2_MAGIC) ) + return PICO_PMV_ERROR_IDENT; + + /* check md2 version */ + if( _pico_little_long( md2->version ) != MD2_VERSION ) + return PICO_PMV_ERROR_VERSION; + + /* file seems to be a valid md2 */ + return PICO_PMV_OK; +} + + + +// _md2_load() loads a quake2 md2 model file. + + +static picoModel_t *_md2_load( PM_PARAMS_LOAD ) +{ + int i, j, dups, dup_index; + short tot_numVerts; + index_LUT_t *p_index_LUT, *p_index_LUT2, *p_index_LUT3; + index_DUP_LUT_t *p_index_LUT_DUPS; + md2Triangle_t *p_md2Triangle; + + char skinname[ MD2_MAX_SKINNAME ]; + md2_t *md2; + md2St_t *texCoord; + md2Frame_t *frame; + md2Triangle_t *triangle; + md2XyzNormal_t *vertex; + + picoByte_t *bb; + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + + /* set as md2 */ + bb = (picoByte_t*) buffer; + md2 = (md2_t*) buffer; + + /* check ident and version */ + if( *((int*) md2->magic) != *((int*) MD2_MAGIC) || _pico_little_long( md2->version ) != MD2_VERSION ) + { + /* not an md2 file (todo: set error) */ + _pico_printf( PICO_ERROR, "%s is not an MD2 File!", fileName ); + return NULL; + } + + // swap md2 + md2->version = _pico_little_long( md2->version ); + + md2->skinWidth = _pico_little_long( md2->skinWidth ); + md2->skinHeight = _pico_little_long( md2->skinHeight ); + md2->frameSize = _pico_little_long( md2->frameSize ); + + md2->numSkins = _pico_little_long( md2->numSkins ); + md2->numXYZ = _pico_little_long( md2->numXYZ ); + md2->numST = _pico_little_long( md2->numST ); + md2->numTris = _pico_little_long( md2->numTris ); + md2->numGLCmds = _pico_little_long( md2->numGLCmds ); + md2->numFrames = _pico_little_long( md2->numFrames ); + + md2->ofsSkins = _pico_little_long( md2->ofsSkins ); + md2->ofsST = _pico_little_long( md2->ofsST ); + md2->ofsTris = _pico_little_long( md2->ofsTris ); + md2->ofsFrames = _pico_little_long( md2->ofsFrames ); + md2->ofsGLCmds = _pico_little_long( md2->ofsGLCmds ); + md2->ofsEnd = _pico_little_long( md2->ofsEnd ); + + // do frame check + if( md2->numFrames < 1 ) + { + _pico_printf( PICO_ERROR, "%s has 0 frames!", fileName ); + return NULL; + } + + if( frameNum < 0 || frameNum >= md2->numFrames ) + { + _pico_printf( PICO_ERROR, "Invalid or out-of-range MD2 frame specified" ); + return NULL; + } + + // Setup Frame + frame = (md2Frame_t *) (bb + md2->ofsFrames + (sizeof(md2Frame_t) * frameNum)); + + // swap frame scale and translation + for( i = 0; i < 3; i++ ) + { + frame->scale[ i ] = _pico_little_float( frame->scale[ i ] ); + frame->translate[ i ] = _pico_little_float( frame->translate[ i ] ); + } + + // swap triangles + triangle = (md2Triangle_t *) ((picoByte_t *) (bb + md2->ofsTris) ); + for( i = 0; i < md2->numTris; i++, triangle++ ) + { + for( j = 0; j < 3; j++ ) + { + triangle->index_xyz[ j ] = _pico_little_short( triangle->index_xyz[ j ] ); + triangle->index_st[ j ] = _pico_little_short( triangle->index_st[ j ] ); + } + } + + // swap st coords + texCoord = (md2St_t*) ((picoByte_t *) (bb + md2->ofsST) ); + for( i = 0; i < md2->numST; i++, texCoord++ ) + { + texCoord->s = _pico_little_short( texCoord->s ); + texCoord->t = _pico_little_short( texCoord->t ); + } + + // set Skin Name + strncpy(skinname, (bb + md2->ofsSkins), MD2_MAX_SKINNAME ); + + // Print out md2 values + _pico_printf(PICO_VERBOSE,"Skins: %d Verts: %d STs: %d Triangles: %d Frames: %d\nSkin Name \"%s\"\n", md2->numSkins, md2->numXYZ, md2->numST, md2->numTris, md2->numFrames, &skinname ); + + // detox Skin name + _pico_setfext( skinname, "" ); + _pico_unixify( skinname ); + + /* create new pico model */ + picoModel = PicoNewModel(); + if( picoModel == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model" ); + return NULL; + } + + /* do model setup */ + PicoSetModelFrameNum( picoModel, frameNum ); + PicoSetModelNumFrames( picoModel, md2->numFrames ); /* sea */ + PicoSetModelName( picoModel, fileName ); + PicoSetModelFileName( picoModel, fileName ); + + // allocate new pico surface + picoSurface = PicoNewSurface( picoModel ); + if( picoSurface == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" ); + PicoFreeModel( picoModel ); + return NULL; + } + + + PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); + PicoSetSurfaceName( picoSurface, frame->name ); + picoShader = PicoNewShader( picoModel ); + if( picoShader == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" ); + PicoFreeModel( picoModel ); + return NULL; + } + + PicoSetShaderName( picoShader, skinname ); + + // associate current surface with newly created shader + PicoSetSurfaceShader( picoSurface, picoShader ); + + // Init LUT for Verts + p_index_LUT = (index_LUT_t *)_pico_alloc(sizeof(index_LUT_t) * md2->numXYZ); + for(i=0; inumXYZ; i++) + { + p_index_LUT[i].Vert = -1; + p_index_LUT[i].ST = -1; + p_index_LUT[i].next = NULL; + } + + // Fill in Look Up Table, and allocate/fill Linked List from vert array as needed for dup STs per Vert. + tot_numVerts = md2->numXYZ; + dups = 0; + for(i=0; inumTris; i++) + { + p_md2Triangle = (md2Triangle_t *) ( bb + md2->ofsTris + (sizeof(md2Triangle_t)*i)); + for(j=0; j<3; j++) + { + if (p_index_LUT[p_md2Triangle->index_xyz[j]].ST == -1) // No Main Entry + p_index_LUT[p_md2Triangle->index_xyz[j]].ST = p_md2Triangle->index_st[j]; + + else if (p_md2Triangle->index_st[j] == p_index_LUT[p_md2Triangle->index_xyz[j]].ST ) // Equal to Main Entry + continue; + + else if ( (p_index_LUT[p_md2Triangle->index_xyz[j]].next == NULL) ) // Not equal to Main entry, and no LL entry + { // Add first entry of LL from Main + p_index_LUT2 = (index_LUT_t *)_pico_alloc(sizeof(index_LUT_t)); + if (p_index_LUT2 == NULL) + _pico_printf( PICO_ERROR," Couldn't allocate memory!\n"); + p_index_LUT[p_md2Triangle->index_xyz[j]].next = (index_LUT_t *)p_index_LUT2; + p_index_LUT2->Vert = dups; + p_index_LUT2->ST = p_md2Triangle->index_st[j]; + p_index_LUT2->next = NULL; + p_md2Triangle->index_xyz[j] = dups + md2->numXYZ; // Make change in Tri hunk + dups++; + } + else // Try to find in LL from Main Entry + { + p_index_LUT3 = p_index_LUT2 = p_index_LUT[p_md2Triangle->index_xyz[j]].next; + while ( (p_index_LUT2 != NULL) && (p_md2Triangle->index_xyz[j] != p_index_LUT2->Vert) ) // Walk down LL + { + p_index_LUT3 = p_index_LUT2; + p_index_LUT2 = p_index_LUT2->next; + } + p_index_LUT2 = p_index_LUT3; + + if ( p_md2Triangle->index_st[j] == p_index_LUT2->ST ) // Found it + { + p_md2Triangle->index_xyz[j] = p_index_LUT2->Vert + md2->numXYZ; // Make change in Tri hunk + continue; + } + + if ( p_index_LUT2->next == NULL) // Didn't find it. Add entry to LL. + { + // Add the Entry + p_index_LUT3 = (index_LUT_t *)_pico_alloc(sizeof(index_LUT_t)); + if (p_index_LUT3 == NULL) + _pico_printf( PICO_ERROR," Couldn't allocate memory!\n"); + p_index_LUT2->next = (index_LUT_t *)p_index_LUT3; + p_index_LUT3->Vert = p_md2Triangle->index_xyz[j]; + p_index_LUT3->ST = p_md2Triangle->index_st[j]; + p_index_LUT3->next = NULL; + p_md2Triangle->index_xyz[j] = dups + md2->numXYZ; // Make change in Tri hunk + dups++; + } + } + } + } + + // malloc and build array for Dup STs + p_index_LUT_DUPS = (index_DUP_LUT_t *)_pico_alloc(sizeof(index_DUP_LUT_t) * dups); + if (p_index_LUT_DUPS == NULL) + _pico_printf( PICO_ERROR," Couldn't allocate memory!\n"); + + dup_index = 0; + for(i=0; inumXYZ; i++) + { + p_index_LUT2 = p_index_LUT[i].next; + while (p_index_LUT2 != NULL) + { + p_index_LUT_DUPS[p_index_LUT2->Vert].OldVert = i; + p_index_LUT_DUPS[p_index_LUT2->Vert].ST = p_index_LUT2->ST; + dup_index++; + p_index_LUT2 = p_index_LUT2->next; + } + } + + // Build Picomodel + triangle = (md2Triangle_t *) ((picoByte_t *) (bb + md2->ofsTris) ); + texCoord = (md2St_t*) ((picoByte_t *) (bb + md2->ofsST) ); + vertex = (md2XyzNormal_t*) ((picoByte_t*) (frame->verts) ); + for( j = 0; j < md2->numTris; j++, triangle++ ) + { + PicoSetSurfaceIndex( picoSurface, j*3 , triangle->index_xyz[0] ); + PicoSetSurfaceIndex( picoSurface, j*3+1 , triangle->index_xyz[1] ); + PicoSetSurfaceIndex( picoSurface, j*3+2 , triangle->index_xyz[2] ); + } + + for(i=0; i< md2->numXYZ; i++, vertex++) + { + /* set vertex origin */ + xyz[ 0 ] = vertex->v[0] * frame->scale[0] + frame->translate[0]; + xyz[ 1 ] = vertex->v[1] * frame->scale[1] + frame->translate[1]; + xyz[ 2 ] = vertex->v[2] * frame->scale[2] + frame->translate[2]; + PicoSetSurfaceXYZ( picoSurface, i , xyz ); + + /* set normal */ + normal[ 0 ] = md2_normals[vertex->lightnormalindex][0]; + normal[ 1 ] = md2_normals[vertex->lightnormalindex][1]; + normal[ 2 ] = md2_normals[vertex->lightnormalindex][2]; + PicoSetSurfaceNormal( picoSurface, i , normal ); + + /* set st coords */ + st[ 0 ] = ((texCoord[p_index_LUT[i].ST].s) / ((float)md2->skinWidth)); + st[ 1 ] = (texCoord[p_index_LUT[i].ST].t / ((float)md2->skinHeight)); + PicoSetSurfaceST( picoSurface, 0, i , st ); + } + + if (dups) + { + for(i=0; iverts[j].v[0] * frame->scale[0] + frame->translate[0]; + xyz[ 1 ] = frame->verts[j].v[1] * frame->scale[1] + frame->translate[1]; + xyz[ 2 ] = frame->verts[j].v[2] * frame->scale[2] + frame->translate[2]; + PicoSetSurfaceXYZ( picoSurface, i + md2->numXYZ , xyz ); + + /* set normal */ + normal[ 0 ] = md2_normals[frame->verts[j].lightnormalindex][0]; + normal[ 1 ] = md2_normals[frame->verts[j].lightnormalindex][1]; + normal[ 2 ] = md2_normals[frame->verts[j].lightnormalindex][2]; + PicoSetSurfaceNormal( picoSurface, i + md2->numXYZ , normal ); + + /* set st coords */ + st[ 0 ] = ((texCoord[p_index_LUT_DUPS[i].ST].s) / ((float)md2->skinWidth)); + st[ 1 ] = (texCoord[p_index_LUT_DUPS[i].ST].t / ((float)md2->skinHeight)); + PicoSetSurfaceST( picoSurface, 0, i + md2->numXYZ , st ); + } + } + + /* set color */ + PicoSetSurfaceColor( picoSurface, 0, 0, color ); + + // Free up malloc'ed LL entries + for(i=0; inumXYZ; i++) + { + if(p_index_LUT[i].next != NULL) + { + p_index_LUT2 = p_index_LUT[i].next; + do { + p_index_LUT3 = p_index_LUT2->next; + _pico_free(p_index_LUT2); + p_index_LUT2 = p_index_LUT3; + dups--; + } while (p_index_LUT2 != NULL); + } + } + + if (dups) + _pico_printf(PICO_WARNING, " Not all LL mallocs freed\n"); + + // Free malloc'ed LUTs + _pico_free(p_index_LUT); + _pico_free(p_index_LUT_DUPS); + + /* return the new pico model */ + return picoModel; + +} + + + +/* pico file format module definition */ +const picoModule_t picoModuleMD2 = +{ + "0.875", /* module version string */ + "Quake 2 MD2", /* module display name */ + "Nurail", /* author's name */ + "2003 Nurail", /* module copyright */ + { + "md2", NULL, NULL, NULL /* default extensions to use */ + }, + _md2_canload, /* validation routine */ + _md2_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/libs/picomodel/pm_md3.c b/libs/picomodel/pm_md3.c new file mode 100644 index 00000000..55022b28 --- /dev/null +++ b/libs/picomodel/pm_md3.c @@ -0,0 +1,425 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PM_MD3_C + + + +/* dependencies */ +#include "picointernal.h" + + + +/* md3 model format */ +#define MD3_MAGIC "IDP3" +#define MD3_VERSION 15 + +/* md3 vertex scale */ +#define MD3_SCALE (1.0f / 64.0f) + +/* md3 model frame information */ +typedef struct md3Frame_s +{ + float bounds[ 2 ][ 3 ]; + float localOrigin[ 3 ]; + float radius; + char creator[ 16 ]; +} +md3Frame_t; + +/* md3 model tag information */ +typedef struct md3Tag_s +{ + char name[ 64 ]; + float origin[ 3 ]; + float axis[ 3 ][ 3 ]; +} +md3Tag_t; + +/* md3 surface md3 (one object mesh) */ +typedef struct md3Surface_s +{ + char magic[ 4 ]; + char name[ 64 ]; /* polyset name */ + int flags; + int numFrames; /* all model surfaces should have the same */ + int numShaders; /* all model surfaces should have the same */ + int numVerts; + int numTriangles; + int ofsTriangles; + int ofsShaders; /* offset from start of md3Surface_t */ + int ofsSt; /* texture coords are common for all frames */ + int ofsVertexes; /* numVerts * numFrames */ + int ofsEnd; /* next surface follows */ +} +md3Surface_t; + +typedef struct md3Shader_s +{ + char name[ 64 ]; + int shaderIndex; /* for ingame use */ +} +md3Shader_t; + +typedef struct md3Triangle_s +{ + int indexes[ 3 ]; +} +md3Triangle_t; + +typedef struct md3TexCoord_s +{ + float st[ 2 ]; +} +md3TexCoord_t; + +typedef struct md3Vertex_s +{ + short xyz[ 3 ]; + short normal; +} +md3Vertex_t; + + +/* md3 model file md3 structure */ +typedef struct md3_s +{ + char magic[ 4 ]; /* MD3_MAGIC */ + int version; + char name[ 64 ]; /* model name */ + int flags; + int numFrames; + int numTags; + int numSurfaces; + int numSkins; /* number of skins for the mesh */ + int ofsFrames; /* offset for first frame */ + int ofsTags; /* numFrames * numTags */ + int ofsSurfaces; /* first surface, others follow */ + int ofsEnd; /* end of file */ +} +md3_t; + + + + +/* +_md3_canload() +validates a quake3 arena md3 model file. btw, i use the +preceding underscore cause it's a static func referenced +by one structure only. +*/ + +static int _md3_canload( PM_PARAMS_CANLOAD ) +{ + md3_t *md3; + + + /* to keep the compiler happy */ + *fileName = *fileName; + + /* sanity check */ + if( bufSize < ( sizeof( *md3 ) * 2) ) + return PICO_PMV_ERROR_SIZE; + + /* set as md3 */ + md3 = (md3_t*) buffer; + + /* check md3 magic */ + if( *((int*) md3->magic) != *((int*) MD3_MAGIC) ) + return PICO_PMV_ERROR_IDENT; + + /* check md3 version */ + if( _pico_little_long( md3->version ) != MD3_VERSION ) + return PICO_PMV_ERROR_VERSION; + + /* file seems to be a valid md3 */ + return PICO_PMV_OK; +} + + + +/* +_md3_load() +loads a quake3 arena md3 model file. +*/ + +static picoModel_t *_md3_load( PM_PARAMS_LOAD ) +{ + int i, j; + picoByte_t *bb; + md3_t *md3; + md3Surface_t *surface; + md3Shader_t *shader; + md3TexCoord_t *texCoord; + md3Frame_t *frame; + md3Triangle_t *triangle; + md3Vertex_t *vertex; + double lat, lng; + + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + + /* ------------------------------------------------- + md3 loading + ------------------------------------------------- */ + + + /* set as md3 */ + bb = (picoByte_t*) buffer; + md3 = (md3_t*) buffer; + + /* check ident and version */ + if( *((int*) md3->magic) != *((int*) MD3_MAGIC) || _pico_little_long( md3->version ) != MD3_VERSION ) + { + /* not an md3 file (todo: set error) */ + return NULL; + } + + /* swap md3; sea: swaps fixed */ + md3->version = _pico_little_long( md3->version ); + md3->numFrames = _pico_little_long( md3->numFrames ); + md3->numTags = _pico_little_long( md3->numTags ); + md3->numSurfaces = _pico_little_long( md3->numSurfaces ); + md3->numSkins = _pico_little_long( md3->numSkins ); + md3->ofsFrames = _pico_little_long( md3->ofsFrames ); + md3->ofsTags = _pico_little_long( md3->ofsTags ); + md3->ofsSurfaces = _pico_little_long( md3->ofsSurfaces ); + md3->ofsEnd = _pico_little_long( md3->ofsEnd ); + + /* do frame check */ + if( md3->numFrames < 1 ) + { + _pico_printf( PICO_ERROR, "MD3 with 0 frames" ); + return NULL; + } + + if( frameNum < 0 || frameNum >= md3->numFrames ) + { + _pico_printf( PICO_ERROR, "Invalid or out-of-range MD3 frame specified" ); + return NULL; + } + + /* swap frames */ + frame = (md3Frame_t*) (bb + md3->ofsFrames ); + for( i = 0; i < md3->numFrames; i++, frame++ ) + { + frame->radius = _pico_little_float( frame->radius ); + for( j = 0; j < 3; j++ ) + { + frame->bounds[ 0 ][ j ] = _pico_little_float( frame->bounds[ 0 ][ j ] ); + frame->bounds[ 1 ][ j ] = _pico_little_float( frame->bounds[ 1 ][ j ] ); + frame->localOrigin[ j ] = _pico_little_float( frame->localOrigin[ j ] ); + } + } + + /* swap surfaces */ + surface = (md3Surface_t*) (bb + md3->ofsSurfaces); + for( i = 0; i < md3->numSurfaces; i++ ) + { + /* swap surface md3; sea: swaps fixed */ + surface->flags = _pico_little_long( surface->flags ); + surface->numFrames = _pico_little_long( surface->numFrames ); + surface->numShaders = _pico_little_long( surface->numShaders ); + surface->numTriangles = _pico_little_long( surface->numTriangles ); + surface->ofsTriangles = _pico_little_long( surface->ofsTriangles ); + surface->numVerts = _pico_little_long( surface->numVerts ); + surface->ofsShaders = _pico_little_long( surface->ofsShaders ); + surface->ofsSt = _pico_little_long( surface->ofsSt ); + surface->ofsVertexes = _pico_little_long( surface->ofsVertexes ); + surface->ofsEnd = _pico_little_long( surface->ofsEnd ); + + /* swap triangles */ + triangle = (md3Triangle_t*) ((picoByte_t*) surface + surface->ofsTriangles); + for( j = 0; j < surface->numTriangles; j++, triangle++ ) + { + /* sea: swaps fixed */ + triangle->indexes[ 0 ] = _pico_little_long( triangle->indexes[ 0 ] ); + triangle->indexes[ 1 ] = _pico_little_long( triangle->indexes[ 1 ] ); + triangle->indexes[ 2 ] = _pico_little_long( triangle->indexes[ 2 ] ); + } + + /* swap st coords */ + texCoord = (md3TexCoord_t*) ((picoByte_t*) surface + surface->ofsSt); + for( j = 0; j < surface->numVerts; j++, texCoord++ ) + { + texCoord->st[ 0 ] = _pico_little_float( texCoord->st[ 0 ] ); + texCoord->st[ 1 ] = _pico_little_float( texCoord->st[ 1 ] ); + } + + /* swap xyz/normals */ + vertex = (md3Vertex_t*) ((picoByte_t*) surface + surface->ofsVertexes); + for( j = 0; j < (surface->numVerts * surface->numFrames); j++, vertex++) + { + vertex->xyz[ 0 ] = _pico_little_short( vertex->xyz[ 0 ] ); + vertex->xyz[ 1 ] = _pico_little_short( vertex->xyz[ 1 ] ); + vertex->xyz[ 2 ] = _pico_little_short( vertex->xyz[ 2 ] ); + vertex->normal = _pico_little_short( vertex->normal ); + } + + /* get next surface */ + surface = (md3Surface_t*) ((picoByte_t*) surface + surface->ofsEnd); + } + + /* ------------------------------------------------- + pico model creation + ------------------------------------------------- */ + + /* create new pico model */ + picoModel = PicoNewModel(); + if( picoModel == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model" ); + return NULL; + } + + /* do model setup */ + PicoSetModelFrameNum( picoModel, frameNum ); + PicoSetModelNumFrames( picoModel, md3->numFrames ); /* sea */ + PicoSetModelName( picoModel, fileName ); + PicoSetModelFileName( picoModel, fileName ); + + /* md3 surfaces become picomodel surfaces */ + surface = (md3Surface_t*) (bb + md3->ofsSurfaces); + + /* run through md3 surfaces */ + for( i = 0; i < md3->numSurfaces; i++ ) + { + /* allocate new pico surface */ + picoSurface = PicoNewSurface( picoModel ); + if( picoSurface == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" ); + PicoFreeModel( picoModel ); /* sea */ + return NULL; + } + + /* md3 model surfaces are all triangle meshes */ + PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); + + /* set surface name */ + PicoSetSurfaceName( picoSurface, surface->name ); + + /* create new pico shader -sea */ + picoShader = PicoNewShader( picoModel ); + if( picoShader == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" ); + PicoFreeModel( picoModel ); + return NULL; + } + + /* detox and set shader name */ + shader = (md3Shader_t*) ((picoByte_t*) surface + surface->ofsShaders); + _pico_setfext( shader->name, "" ); + _pico_unixify( shader->name ); + PicoSetShaderName( picoShader, shader->name ); + + /* associate current surface with newly created shader */ + PicoSetSurfaceShader( picoSurface, picoShader ); + + /* copy indexes */ + triangle = (md3Triangle_t *) ((picoByte_t*) surface + surface->ofsTriangles); + + for( j = 0; j < surface->numTriangles; j++, triangle++ ) + { + PicoSetSurfaceIndex( picoSurface, (j * 3 + 0), (picoIndex_t) triangle->indexes[ 0 ] ); + PicoSetSurfaceIndex( picoSurface, (j * 3 + 1), (picoIndex_t) triangle->indexes[ 1 ] ); + PicoSetSurfaceIndex( picoSurface, (j * 3 + 2), (picoIndex_t) triangle->indexes[ 2 ] ); + } + + /* copy vertexes */ + texCoord = (md3TexCoord_t*) ((picoByte_t *) surface + surface->ofsSt); + vertex = (md3Vertex_t*) ((picoByte_t*) surface + surface->ofsVertexes + surface->numVerts * frameNum * sizeof( md3Vertex_t ) ); + _pico_set_color( color, 255, 255, 255, 255 ); + + for( j = 0; j < surface->numVerts; j++, texCoord++, vertex++ ) + { + /* set vertex origin */ + xyz[ 0 ] = MD3_SCALE * vertex->xyz[ 0 ]; + xyz[ 1 ] = MD3_SCALE * vertex->xyz[ 1 ]; + xyz[ 2 ] = MD3_SCALE * vertex->xyz[ 2 ]; + PicoSetSurfaceXYZ( picoSurface, j, xyz ); + + /* decode lat/lng normal to 3 float normal */ + lat = (float) ((vertex->normal >> 8) & 0xff); + lng = (float) (vertex->normal & 0xff); + lat *= PICO_PI / 128; + lng *= PICO_PI / 128; + normal[ 0 ] = (picoVec_t) cos( lat ) * (picoVec_t) sin( lng ); + normal[ 1 ] = (picoVec_t) sin( lat ) * (picoVec_t) sin( lng ); + normal[ 2 ] = (picoVec_t) cos( lng ); + PicoSetSurfaceNormal( picoSurface, j, normal ); + + /* set st coords */ + st[ 0 ] = texCoord->st[ 0 ]; + st[ 1 ] = texCoord->st[ 1 ]; + PicoSetSurfaceST( picoSurface, 0, j, st ); + + /* set color */ + PicoSetSurfaceColor( picoSurface, 0, j, color ); + } + + /* get next surface */ + surface = (md3Surface_t*) ((picoByte_t*) surface + surface->ofsEnd); + } + + /* return the new pico model */ + return picoModel; +} + + + +/* pico file format module definition */ +const picoModule_t picoModuleMD3 = +{ + "1.3", /* module version string */ + "Quake 3 Arena", /* module display name */ + "Randy Reddig", /* author's name */ + "2002 Randy Reddig", /* module copyright */ + { + "md3", NULL, NULL, NULL /* default extensions to use */ + }, + _md3_canload, /* validation routine */ + _md3_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/libs/picomodel/pm_mdc.c b/libs/picomodel/pm_mdc.c new file mode 100644 index 00000000..27e612e8 --- /dev/null +++ b/libs/picomodel/pm_mdc.c @@ -0,0 +1,750 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PM_MDC_C + + + +/* dependencies */ +#include "picointernal.h" + +/* mdc model format */ +#define MDC_MAGIC "IDPC" +#define MDC_VERSION 2 + +/* mdc vertex scale */ +#define MDC_SCALE (1.0f / 64.0f) +#define MDC_MAX_OFS 127.0f +#define MDC_DIST_SCALE 0.05f + +/* mdc decoding normal table */ +double mdcNormals[ 256 ][ 3 ] = +{ + { 1.000000, 0.000000, 0.000000 }, + { 0.980785, 0.195090, 0.000000 }, + { 0.923880, 0.382683, 0.000000 }, + { 0.831470, 0.555570, 0.000000 }, + { 0.707107, 0.707107, 0.000000 }, + { 0.555570, 0.831470, 0.000000 }, + { 0.382683, 0.923880, 0.000000 }, + { 0.195090, 0.980785, 0.000000 }, + { -0.000000, 1.000000, 0.000000 }, + { -0.195090, 0.980785, 0.000000 }, + { -0.382683, 0.923880, 0.000000 }, + { -0.555570, 0.831470, 0.000000 }, + { -0.707107, 0.707107, 0.000000 }, + { -0.831470, 0.555570, 0.000000 }, + { -0.923880, 0.382683, 0.000000 }, + { -0.980785, 0.195090, 0.000000 }, + { -1.000000, -0.000000, 0.000000 }, + { -0.980785, -0.195090, 0.000000 }, + { -0.923880, -0.382683, 0.000000 }, + { -0.831470, -0.555570, 0.000000 }, + { -0.707107, -0.707107, 0.000000 }, + { -0.555570, -0.831469, 0.000000 }, + { -0.382684, -0.923880, 0.000000 }, + { -0.195090, -0.980785, 0.000000 }, + { 0.000000, -1.000000, 0.000000 }, + { 0.195090, -0.980785, 0.000000 }, + { 0.382684, -0.923879, 0.000000 }, + { 0.555570, -0.831470, 0.000000 }, + { 0.707107, -0.707107, 0.000000 }, + { 0.831470, -0.555570, 0.000000 }, + { 0.923880, -0.382683, 0.000000 }, + { 0.980785, -0.195090, 0.000000 }, + { 0.980785, 0.000000, -0.195090 }, + { 0.956195, 0.218245, -0.195090 }, + { 0.883657, 0.425547, -0.195090 }, + { 0.766809, 0.611510, -0.195090 }, + { 0.611510, 0.766809, -0.195090 }, + { 0.425547, 0.883657, -0.195090 }, + { 0.218245, 0.956195, -0.195090 }, + { -0.000000, 0.980785, -0.195090 }, + { -0.218245, 0.956195, -0.195090 }, + { -0.425547, 0.883657, -0.195090 }, + { -0.611510, 0.766809, -0.195090 }, + { -0.766809, 0.611510, -0.195090 }, + { -0.883657, 0.425547, -0.195090 }, + { -0.956195, 0.218245, -0.195090 }, + { -0.980785, -0.000000, -0.195090 }, + { -0.956195, -0.218245, -0.195090 }, + { -0.883657, -0.425547, -0.195090 }, + { -0.766809, -0.611510, -0.195090 }, + { -0.611510, -0.766809, -0.195090 }, + { -0.425547, -0.883657, -0.195090 }, + { -0.218245, -0.956195, -0.195090 }, + { 0.000000, -0.980785, -0.195090 }, + { 0.218245, -0.956195, -0.195090 }, + { 0.425547, -0.883657, -0.195090 }, + { 0.611510, -0.766809, -0.195090 }, + { 0.766809, -0.611510, -0.195090 }, + { 0.883657, -0.425547, -0.195090 }, + { 0.956195, -0.218245, -0.195090 }, + { 0.923880, 0.000000, -0.382683 }, + { 0.892399, 0.239118, -0.382683 }, + { 0.800103, 0.461940, -0.382683 }, + { 0.653281, 0.653281, -0.382683 }, + { 0.461940, 0.800103, -0.382683 }, + { 0.239118, 0.892399, -0.382683 }, + { -0.000000, 0.923880, -0.382683 }, + { -0.239118, 0.892399, -0.382683 }, + { -0.461940, 0.800103, -0.382683 }, + { -0.653281, 0.653281, -0.382683 }, + { -0.800103, 0.461940, -0.382683 }, + { -0.892399, 0.239118, -0.382683 }, + { -0.923880, -0.000000, -0.382683 }, + { -0.892399, -0.239118, -0.382683 }, + { -0.800103, -0.461940, -0.382683 }, + { -0.653282, -0.653281, -0.382683 }, + { -0.461940, -0.800103, -0.382683 }, + { -0.239118, -0.892399, -0.382683 }, + { 0.000000, -0.923880, -0.382683 }, + { 0.239118, -0.892399, -0.382683 }, + { 0.461940, -0.800103, -0.382683 }, + { 0.653281, -0.653282, -0.382683 }, + { 0.800103, -0.461940, -0.382683 }, + { 0.892399, -0.239117, -0.382683 }, + { 0.831470, 0.000000, -0.555570 }, + { 0.790775, 0.256938, -0.555570 }, + { 0.672673, 0.488726, -0.555570 }, + { 0.488726, 0.672673, -0.555570 }, + { 0.256938, 0.790775, -0.555570 }, + { -0.000000, 0.831470, -0.555570 }, + { -0.256938, 0.790775, -0.555570 }, + { -0.488726, 0.672673, -0.555570 }, + { -0.672673, 0.488726, -0.555570 }, + { -0.790775, 0.256938, -0.555570 }, + { -0.831470, -0.000000, -0.555570 }, + { -0.790775, -0.256938, -0.555570 }, + { -0.672673, -0.488726, -0.555570 }, + { -0.488725, -0.672673, -0.555570 }, + { -0.256938, -0.790775, -0.555570 }, + { 0.000000, -0.831470, -0.555570 }, + { 0.256938, -0.790775, -0.555570 }, + { 0.488725, -0.672673, -0.555570 }, + { 0.672673, -0.488726, -0.555570 }, + { 0.790775, -0.256938, -0.555570 }, + { 0.707107, 0.000000, -0.707107 }, + { 0.653281, 0.270598, -0.707107 }, + { 0.500000, 0.500000, -0.707107 }, + { 0.270598, 0.653281, -0.707107 }, + { -0.000000, 0.707107, -0.707107 }, + { -0.270598, 0.653282, -0.707107 }, + { -0.500000, 0.500000, -0.707107 }, + { -0.653281, 0.270598, -0.707107 }, + { -0.707107, -0.000000, -0.707107 }, + { -0.653281, -0.270598, -0.707107 }, + { -0.500000, -0.500000, -0.707107 }, + { -0.270598, -0.653281, -0.707107 }, + { 0.000000, -0.707107, -0.707107 }, + { 0.270598, -0.653281, -0.707107 }, + { 0.500000, -0.500000, -0.707107 }, + { 0.653282, -0.270598, -0.707107 }, + { 0.555570, 0.000000, -0.831470 }, + { 0.481138, 0.277785, -0.831470 }, + { 0.277785, 0.481138, -0.831470 }, + { -0.000000, 0.555570, -0.831470 }, + { -0.277785, 0.481138, -0.831470 }, + { -0.481138, 0.277785, -0.831470 }, + { -0.555570, -0.000000, -0.831470 }, + { -0.481138, -0.277785, -0.831470 }, + { -0.277785, -0.481138, -0.831470 }, + { 0.000000, -0.555570, -0.831470 }, + { 0.277785, -0.481138, -0.831470 }, + { 0.481138, -0.277785, -0.831470 }, + { 0.382683, 0.000000, -0.923880 }, + { 0.270598, 0.270598, -0.923880 }, + { -0.000000, 0.382683, -0.923880 }, + { -0.270598, 0.270598, -0.923880 }, + { -0.382683, -0.000000, -0.923880 }, + { -0.270598, -0.270598, -0.923880 }, + { 0.000000, -0.382683, -0.923880 }, + { 0.270598, -0.270598, -0.923880 }, + { 0.195090, 0.000000, -0.980785 }, + { -0.000000, 0.195090, -0.980785 }, + { -0.195090, -0.000000, -0.980785 }, + { 0.000000, -0.195090, -0.980785 }, + { 0.980785, 0.000000, 0.195090 }, + { 0.956195, 0.218245, 0.195090 }, + { 0.883657, 0.425547, 0.195090 }, + { 0.766809, 0.611510, 0.195090 }, + { 0.611510, 0.766809, 0.195090 }, + { 0.425547, 0.883657, 0.195090 }, + { 0.218245, 0.956195, 0.195090 }, + { -0.000000, 0.980785, 0.195090 }, + { -0.218245, 0.956195, 0.195090 }, + { -0.425547, 0.883657, 0.195090 }, + { -0.611510, 0.766809, 0.195090 }, + { -0.766809, 0.611510, 0.195090 }, + { -0.883657, 0.425547, 0.195090 }, + { -0.956195, 0.218245, 0.195090 }, + { -0.980785, -0.000000, 0.195090 }, + { -0.956195, -0.218245, 0.195090 }, + { -0.883657, -0.425547, 0.195090 }, + { -0.766809, -0.611510, 0.195090 }, + { -0.611510, -0.766809, 0.195090 }, + { -0.425547, -0.883657, 0.195090 }, + { -0.218245, -0.956195, 0.195090 }, + { 0.000000, -0.980785, 0.195090 }, + { 0.218245, -0.956195, 0.195090 }, + { 0.425547, -0.883657, 0.195090 }, + { 0.611510, -0.766809, 0.195090 }, + { 0.766809, -0.611510, 0.195090 }, + { 0.883657, -0.425547, 0.195090 }, + { 0.956195, -0.218245, 0.195090 }, + { 0.923880, 0.000000, 0.382683 }, + { 0.892399, 0.239118, 0.382683 }, + { 0.800103, 0.461940, 0.382683 }, + { 0.653281, 0.653281, 0.382683 }, + { 0.461940, 0.800103, 0.382683 }, + { 0.239118, 0.892399, 0.382683 }, + { -0.000000, 0.923880, 0.382683 }, + { -0.239118, 0.892399, 0.382683 }, + { -0.461940, 0.800103, 0.382683 }, + { -0.653281, 0.653281, 0.382683 }, + { -0.800103, 0.461940, 0.382683 }, + { -0.892399, 0.239118, 0.382683 }, + { -0.923880, -0.000000, 0.382683 }, + { -0.892399, -0.239118, 0.382683 }, + { -0.800103, -0.461940, 0.382683 }, + { -0.653282, -0.653281, 0.382683 }, + { -0.461940, -0.800103, 0.382683 }, + { -0.239118, -0.892399, 0.382683 }, + { 0.000000, -0.923880, 0.382683 }, + { 0.239118, -0.892399, 0.382683 }, + { 0.461940, -0.800103, 0.382683 }, + { 0.653281, -0.653282, 0.382683 }, + { 0.800103, -0.461940, 0.382683 }, + { 0.892399, -0.239117, 0.382683 }, + { 0.831470, 0.000000, 0.555570 }, + { 0.790775, 0.256938, 0.555570 }, + { 0.672673, 0.488726, 0.555570 }, + { 0.488726, 0.672673, 0.555570 }, + { 0.256938, 0.790775, 0.555570 }, + { -0.000000, 0.831470, 0.555570 }, + { -0.256938, 0.790775, 0.555570 }, + { -0.488726, 0.672673, 0.555570 }, + { -0.672673, 0.488726, 0.555570 }, + { -0.790775, 0.256938, 0.555570 }, + { -0.831470, -0.000000, 0.555570 }, + { -0.790775, -0.256938, 0.555570 }, + { -0.672673, -0.488726, 0.555570 }, + { -0.488725, -0.672673, 0.555570 }, + { -0.256938, -0.790775, 0.555570 }, + { 0.000000, -0.831470, 0.555570 }, + { 0.256938, -0.790775, 0.555570 }, + { 0.488725, -0.672673, 0.555570 }, + { 0.672673, -0.488726, 0.555570 }, + { 0.790775, -0.256938, 0.555570 }, + { 0.707107, 0.000000, 0.707107 }, + { 0.653281, 0.270598, 0.707107 }, + { 0.500000, 0.500000, 0.707107 }, + { 0.270598, 0.653281, 0.707107 }, + { -0.000000, 0.707107, 0.707107 }, + { -0.270598, 0.653282, 0.707107 }, + { -0.500000, 0.500000, 0.707107 }, + { -0.653281, 0.270598, 0.707107 }, + { -0.707107, -0.000000, 0.707107 }, + { -0.653281, -0.270598, 0.707107 }, + { -0.500000, -0.500000, 0.707107 }, + { -0.270598, -0.653281, 0.707107 }, + { 0.000000, -0.707107, 0.707107 }, + { 0.270598, -0.653281, 0.707107 }, + { 0.500000, -0.500000, 0.707107 }, + { 0.653282, -0.270598, 0.707107 }, + { 0.555570, 0.000000, 0.831470 }, + { 0.481138, 0.277785, 0.831470 }, + { 0.277785, 0.481138, 0.831470 }, + { -0.000000, 0.555570, 0.831470 }, + { -0.277785, 0.481138, 0.831470 }, + { -0.481138, 0.277785, 0.831470 }, + { -0.555570, -0.000000, 0.831470 }, + { -0.481138, -0.277785, 0.831470 }, + { -0.277785, -0.481138, 0.831470 }, + { 0.000000, -0.555570, 0.831470 }, + { 0.277785, -0.481138, 0.831470 }, + { 0.481138, -0.277785, 0.831470 }, + { 0.382683, 0.000000, 0.923880 }, + { 0.270598, 0.270598, 0.923880 }, + { -0.000000, 0.382683, 0.923880 }, + { -0.270598, 0.270598, 0.923880 }, + { -0.382683, -0.000000, 0.923880 }, + { -0.270598, -0.270598, 0.923880 }, + { 0.000000, -0.382683, 0.923880 }, + { 0.270598, -0.270598, 0.923880 }, + { 0.195090, 0.000000, 0.980785 }, + { -0.000000, 0.195090, 0.980785 }, + { -0.195090, -0.000000, 0.980785 }, + { 0.000000, -0.195090, 0.980785 } +}; + +/* mdc model frame information */ +typedef struct mdcFrame_s +{ + float bounds[ 2 ][ 3 ]; + float localOrigin[ 3 ]; + float radius; + char creator[ 16 ]; +} +mdcFrame_t; + +/* mdc model tag information */ +typedef struct mdcTag_s +{ + short xyz[3]; + short angles[3]; +} +mdcTag_t; + +/* mdc surface mdc (one object mesh) */ +typedef struct mdcSurface_s +{ + char magic[ 4 ]; + char name[ 64 ]; /* polyset name */ + int flags; + int numCompFrames; /* all surfaces in a model should have the same */ + int numBaseFrames; /* ditto */ + int numShaders; /* all model surfaces should have the same */ + int numVerts; + int numTriangles; + int ofsTriangles; + int ofsShaders; /* offset from start of mdcSurface_t */ + int ofsSt; /* texture coords are common for all frames */ + int ofsXyzNormals; /* numVerts * numBaseFrames */ + int ofsXyzCompressed; /* numVerts * numCompFrames */ + + int ofsFrameBaseFrames; /* numFrames */ + int ofsFrameCompFrames; /* numFrames */ + int ofsEnd; /* next surface follows */ +} +mdcSurface_t; + +typedef struct mdcShader_s +{ + char name[ 64 ]; + int shaderIndex; /* for ingame use */ +} +mdcShader_t; + +typedef struct mdcTriangle_s +{ + int indexes[ 3 ]; +} +mdcTriangle_t; + +typedef struct mdcTexCoord_s +{ + float st[ 2 ]; +} +mdcTexCoord_t; + +typedef struct mdcVertex_s +{ + short xyz[ 3 ]; + short normal; +} +mdcVertex_t; + +typedef struct mdcXyzCompressed_s +{ + unsigned int ofsVec; /* offset direction from the last base frame */ +} +mdcXyzCompressed_t; + + +/* mdc model file mdc structure */ +typedef struct mdc_s +{ + char magic[ 4 ]; /* MDC_MAGIC */ + int version; + char name[ 64 ]; /* model name */ + int flags; + int numFrames; + int numTags; + int numSurfaces; + int numSkins; /* number of skins for the mesh */ + int ofsFrames; /* offset for first frame */ + int ofsTagNames; /* numTags */ + int ofsTags; /* numFrames * numTags */ + int ofsSurfaces; /* first surface, others follow */ + int ofsEnd; /* end of file */ +} +mdc_t; + + + + +/* +_mdc_canload() +validates a Return to Castle Wolfenstein model file. btw, i use the +preceding underscore cause it's a static func referenced +by one structure only. +*/ + +static int _mdc_canload( PM_PARAMS_CANLOAD ) +{ + mdc_t *mdc; + + + /* to keep the compiler happy */ + *fileName = *fileName; + + /* sanity check */ + if( bufSize < ( sizeof( *mdc ) * 2) ) + return PICO_PMV_ERROR_SIZE; + + /* set as mdc */ + mdc = (mdc_t*) buffer; + + /* check mdc magic */ + if( *((int*) mdc->magic) != *((int*) MDC_MAGIC) ) + return PICO_PMV_ERROR_IDENT; + + /* check mdc version */ + if( _pico_little_long( mdc->version ) != MDC_VERSION ) + return PICO_PMV_ERROR_VERSION; + + /* file seems to be a valid mdc */ + return PICO_PMV_OK; +} + + + +/* +_mdc_load() +loads a Return to Castle Wolfenstein mdc model file. +*/ + +static picoModel_t *_mdc_load( PM_PARAMS_LOAD ) +{ + int i, j; + picoByte_t *bb; + mdc_t *mdc; + mdcSurface_t *surface; + mdcShader_t *shader; + mdcTexCoord_t *texCoord; + mdcFrame_t *frame; + mdcTriangle_t *triangle; + mdcVertex_t *vertex; + mdcXyzCompressed_t *vertexComp; + short *mdcShort, *mdcCompVert; + double lat, lng; + + picoModel_t *picoModel; + picoSurface_t *picoSurface; + picoShader_t *picoShader; + picoVec3_t xyz, normal; + picoVec2_t st; + picoColor_t color; + + + /* ------------------------------------------------- + mdc loading + ------------------------------------------------- */ + + + /* set as mdc */ + bb = (picoByte_t*) buffer; + mdc = (mdc_t*) buffer; + + /* check ident and version */ + if( *((int*) mdc->magic) != *((int*) MDC_MAGIC) || _pico_little_long( mdc->version ) != MDC_VERSION ) + { + /* not an mdc file (todo: set error) */ + return NULL; + } + + /* swap mdc */ + mdc->version = _pico_little_long( mdc->version ); + mdc->numFrames = _pico_little_long( mdc->numFrames ); + mdc->numTags = _pico_little_long( mdc->numTags ); + mdc->numSurfaces = _pico_little_long( mdc->numSurfaces ); + mdc->numSkins = _pico_little_long( mdc->numSkins ); + mdc->ofsFrames = _pico_little_long( mdc->ofsFrames ); + mdc->ofsTags = _pico_little_long( mdc->ofsTags ); + mdc->ofsTagNames = _pico_little_long( mdc->ofsTagNames ); + mdc->ofsSurfaces = _pico_little_long( mdc->ofsSurfaces ); + mdc->ofsEnd = _pico_little_long( mdc->ofsEnd ); + + /* do frame check */ + if( mdc->numFrames < 1 ) + { + _pico_printf( PICO_ERROR, "MDC with 0 frames" ); + return NULL; + } + + if( frameNum < 0 || frameNum >= mdc->numFrames ) + { + _pico_printf( PICO_ERROR, "Invalid or out-of-range MDC frame specified" ); + return NULL; + } + + /* swap frames */ + frame = (mdcFrame_t*) (bb + mdc->ofsFrames ); + for( i = 0; i < mdc->numFrames; i++, frame++ ) + { + frame->radius = _pico_little_float( frame->radius ); + for( j = 0; j < 3; j++ ) + { + frame->bounds[ 0 ][ j ] = _pico_little_float( frame->bounds[ 0 ][ j ] ); + frame->bounds[ 1 ][ j ] = _pico_little_float( frame->bounds[ 1 ][ j ] ); + frame->localOrigin[ j ] = _pico_little_float( frame->localOrigin[ j ] ); + } + } + + /* swap surfaces */ + surface = (mdcSurface_t*) (bb + mdc->ofsSurfaces); + for( i = 0; i < mdc->numSurfaces; i++ ) + { + /* swap surface mdc */ + surface->flags = _pico_little_long( surface->flags ); + surface->numBaseFrames = _pico_little_long( surface->numBaseFrames ); + surface->numCompFrames = _pico_little_long( surface->numCompFrames ); + surface->numShaders = _pico_little_long( surface->numShaders ); + surface->numTriangles = _pico_little_long( surface->numTriangles ); + surface->ofsTriangles = _pico_little_long( surface->ofsTriangles ); + surface->numVerts = _pico_little_long( surface->numVerts ); + surface->ofsShaders = _pico_little_long( surface->ofsShaders ); + surface->ofsSt = _pico_little_long( surface->ofsSt ); + surface->ofsXyzNormals = _pico_little_long( surface->ofsXyzNormals ); + surface->ofsXyzCompressed = _pico_little_long( surface->ofsXyzCompressed ); + surface->ofsFrameBaseFrames = _pico_little_long( surface->ofsFrameBaseFrames ); + surface->ofsFrameCompFrames = _pico_little_long( surface->ofsFrameCompFrames ); + surface->ofsEnd = _pico_little_long( surface->ofsEnd ); + + /* swap triangles */ + triangle = (mdcTriangle_t*) ((picoByte_t*) surface + surface->ofsTriangles); + for( j = 0; j < surface->numTriangles; j++, triangle++ ) + { + /* sea: swaps fixed */ + triangle->indexes[ 0 ] = _pico_little_long( triangle->indexes[ 0 ] ); + triangle->indexes[ 1 ] = _pico_little_long( triangle->indexes[ 1 ] ); + triangle->indexes[ 2 ] = _pico_little_long( triangle->indexes[ 2 ] ); + } + + /* swap st coords */ + texCoord = (mdcTexCoord_t*) ((picoByte_t*) surface + surface->ofsSt); + for( j = 0; j < surface->numVerts; j++, texCoord++ ) + { + texCoord->st[ 0 ] = _pico_little_float( texCoord->st[ 0 ] ); + texCoord->st[ 1 ] = _pico_little_float( texCoord->st[ 1 ] ); + } + + /* swap xyz/normals */ + vertex = (mdcVertex_t*) ((picoByte_t*) surface + surface->ofsXyzNormals); + for( j = 0; j < (surface->numVerts * surface->numBaseFrames); j++, vertex++) + { + vertex->xyz[ 0 ] = _pico_little_short( vertex->xyz[ 0 ] ); + vertex->xyz[ 1 ] = _pico_little_short( vertex->xyz[ 1 ] ); + vertex->xyz[ 2 ] = _pico_little_short( vertex->xyz[ 2 ] ); + vertex->normal = _pico_little_short( vertex->normal ); + } + + /* swap xyz/compressed */ + vertexComp = (mdcXyzCompressed_t*) ((picoByte_t*) surface + surface->ofsXyzCompressed); + for( j = 0; j < (surface->numVerts * surface->numCompFrames); j++, vertexComp++) + { + vertexComp->ofsVec = _pico_little_long( vertexComp->ofsVec ); + } + + /* swap base frames */ + mdcShort = (short *) ((picoByte_t*) surface + surface->ofsFrameBaseFrames); + for( j = 0; j < mdc->numFrames; j++, mdcShort++) + { + *mdcShort = _pico_little_short( *mdcShort ); + } + + /* swap compressed frames */ + mdcShort = (short *) ((picoByte_t*) surface + surface->ofsFrameCompFrames); + for( j = 0; j < mdc->numFrames; j++, mdcShort++) + { + *mdcShort = _pico_little_short( *mdcShort ); + } + + /* get next surface */ + surface = (mdcSurface_t*) ((picoByte_t*) surface + surface->ofsEnd); + } + + /* ------------------------------------------------- + pico model creation + ------------------------------------------------- */ + + /* create new pico model */ + picoModel = PicoNewModel(); + if( picoModel == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model" ); + return NULL; + } + + /* do model setup */ + PicoSetModelFrameNum( picoModel, frameNum ); + PicoSetModelNumFrames( picoModel, mdc->numFrames ); /* sea */ + PicoSetModelName( picoModel, fileName ); + PicoSetModelFileName( picoModel, fileName ); + + /* mdc surfaces become picomodel surfaces */ + surface = (mdcSurface_t*) (bb + mdc->ofsSurfaces); + + /* run through mdc surfaces */ + for( i = 0; i < mdc->numSurfaces; i++ ) + { + /* allocate new pico surface */ + picoSurface = PicoNewSurface( picoModel ); + if( picoSurface == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model surface" ); + PicoFreeModel( picoModel ); /* sea */ + return NULL; + } + + /* mdc model surfaces are all triangle meshes */ + PicoSetSurfaceType( picoSurface, PICO_TRIANGLES ); + + /* set surface name */ + PicoSetSurfaceName( picoSurface, surface->name ); + + /* create new pico shader -sea */ + picoShader = PicoNewShader( picoModel ); + if( picoShader == NULL ) + { + _pico_printf( PICO_ERROR, "Unable to allocate a new model shader" ); + PicoFreeModel( picoModel ); + return NULL; + } + + /* detox and set shader name */ + shader = (mdcShader_t*) ((picoByte_t*) surface + surface->ofsShaders); + _pico_setfext( shader->name, "" ); + _pico_unixify( shader->name ); + PicoSetShaderName( picoShader, shader->name ); + + /* associate current surface with newly created shader */ + PicoSetSurfaceShader( picoSurface, picoShader ); + + /* copy indexes */ + triangle = (mdcTriangle_t *) ((picoByte_t*) surface + surface->ofsTriangles); + + for( j = 0; j < surface->numTriangles; j++, triangle++ ) + { + PicoSetSurfaceIndex( picoSurface, (j * 3 + 0), (picoIndex_t) triangle->indexes[ 0 ] ); + PicoSetSurfaceIndex( picoSurface, (j * 3 + 1), (picoIndex_t) triangle->indexes[ 1 ] ); + PicoSetSurfaceIndex( picoSurface, (j * 3 + 2), (picoIndex_t) triangle->indexes[ 2 ] ); + } + + /* copy vertexes */ + texCoord = (mdcTexCoord_t*) ((picoByte_t *) surface + surface->ofsSt); + mdcShort = (short *) ((picoByte_t *) surface + surface->ofsXyzNormals) + ((int)*((short *) ((picoByte_t *) surface + surface->ofsFrameBaseFrames) + frameNum) * surface->numVerts * 4); + if( surface->numCompFrames > 0 ) + { + mdcCompVert = (short *) ((picoByte_t *) surface + surface->ofsFrameCompFrames) + frameNum; + if( *mdcCompVert >= 0 ) + vertexComp = (mdcXyzCompressed_t *) ((picoByte_t *) surface + surface->ofsXyzCompressed) + (*mdcCompVert * surface->numVerts); + } + _pico_set_color( color, 255, 255, 255, 255 ); + + for( j = 0; j < surface->numVerts; j++, texCoord++, mdcShort+=4 ) + { + /* set vertex origin */ + xyz[ 0 ] = MDC_SCALE * mdcShort[ 0 ]; + xyz[ 1 ] = MDC_SCALE * mdcShort[ 1 ]; + xyz[ 2 ] = MDC_SCALE * mdcShort[ 2 ]; + + /* add compressed ofsVec */ + if( surface->numCompFrames > 0 && *mdcCompVert >= 0 ) + { + xyz[ 0 ] += ((float) ((vertexComp->ofsVec) & 255) - MDC_MAX_OFS) * MDC_DIST_SCALE; + xyz[ 1 ] += ((float) ((vertexComp->ofsVec >> 8) & 255) - MDC_MAX_OFS) * MDC_DIST_SCALE; + xyz[ 2 ] += ((float) ((vertexComp->ofsVec >> 16) & 255) - MDC_MAX_OFS) * MDC_DIST_SCALE; + PicoSetSurfaceXYZ( picoSurface, j, xyz ); + + normal[ 0 ] = (float) mdcNormals[ (vertexComp->ofsVec >> 24) ][ 0 ]; + normal[ 1 ] = (float) mdcNormals[ (vertexComp->ofsVec >> 24) ][ 1 ]; + normal[ 2 ] = (float) mdcNormals[ (vertexComp->ofsVec >> 24) ][ 2 ]; + PicoSetSurfaceNormal( picoSurface, j, normal ); + + vertexComp++; + } + else + { + PicoSetSurfaceXYZ( picoSurface, j, xyz ); + + /* decode lat/lng normal to 3 float normal */ + lat = (float) ((*(mdcShort + 3) >> 8) & 0xff); + lng = (float) (*(mdcShort + 3) & 0xff); + lat *= PICO_PI / 128; + lng *= PICO_PI / 128; + normal[ 0 ] = (picoVec_t) cos( lat ) * (picoVec_t) sin( lng ); + normal[ 1 ] = (picoVec_t) sin( lat ) * (picoVec_t) sin( lng ); + normal[ 2 ] = (picoVec_t) cos( lng ); + PicoSetSurfaceNormal( picoSurface, j, normal ); + } + + /* set st coords */ + st[ 0 ] = texCoord->st[ 0 ]; + st[ 1 ] = texCoord->st[ 1 ]; + PicoSetSurfaceST( picoSurface, 0, j, st ); + + /* set color */ + PicoSetSurfaceColor( picoSurface, 0, j, color ); + } + + /* get next surface */ + surface = (mdcSurface_t*) ((picoByte_t*) surface + surface->ofsEnd); + } + + /* return the new pico model */ + return picoModel; +} + + + +/* pico file format module definition */ +const picoModule_t picoModuleMDC = +{ + "1.3", /* module version string */ + "RtCW MDC", /* module display name */ + "Arnout van Meer", /* author's name */ + "2002 Arnout van Meer", /* module copyright */ + { + "mdc", NULL, NULL, NULL /* default extensions to use */ + }, + _mdc_canload, /* validation routine */ + _mdc_load, /* load routine */ + NULL, /* save validation routine */ + NULL /* save routine */ +}; diff --git a/libs/picomodel/pm_ms3d.c b/libs/picomodel/pm_ms3d.c new file mode 100644 index 00000000..cf411eed --- /dev/null +++ b/libs/picomodel/pm_ms3d.c @@ -0,0 +1,494 @@ +/* ----------------------------------------------------------------------------- + +PicoModel Library + +Copyright (c) 2002, Randy Reddig & seaw0lf +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the names of the copyright holders nor the names of its contributors may +be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----------------------------------------------------------------------------- */ + + + +/* marker */ +#define PM_MS3D_C + +/* dependencies */ +#include "picointernal.h" + +/* disable warnings */ +#ifdef WIN32 +#pragma warning( disable:4100 ) /* unref param */ +#endif + +/* remarks: + * - loader seems stable + * todo: + * - fix uv coordinate problem + * - check for buffer overflows ('bufptr' accesses) + */ +/* uncomment when debugging this module */ + #define DEBUG_PM_MS3D + #define DEBUG_PM_MS3D_EX + +/* plain white */ +static picoColor_t white = { 255,255,255,255 }; + +/* ms3d limits */ +#define MS3D_MAX_VERTS 8192 +#define MS3D_MAX_TRIS 16384 +#define MS3D_MAX_GROUPS 128 +#define MS3D_MAX_MATERIALS 128 +#define MS3D_MAX_JOINTS 128 +#define MS3D_MAX_KEYFRAMES 216 + +/* ms3d flags */ +#define MS3D_SELECTED 1 +#define MS3D_HIDDEN 2 +#define MS3D_SELECTED2 4 +#define MS3D_DIRTY 8 + +/* this freaky loader needs byte alignment */ +#pragma pack(push, 1) + +/* ms3d header */ +typedef struct SMsHeader +{ + char magic[10]; + int version; +} +TMsHeader; + +/* ms3d vertex */ +typedef struct SMsVertex +{ + unsigned char flags; /* sel, sel2, or hidden */ + float xyz[3]; + char boneID; /* -1 means 'no bone' */ + unsigned char refCount; +} +TMsVertex; + +/* ms3d triangle */ +typedef struct SMsTriangle +{ + unsigned short flags; /* sel, sel2, or hidden */ + unsigned short vertexIndices[3]; + float vertexNormals[3][3]; + float s[3]; + float t[3]; + unsigned char smoothingGroup; /* 1 - 32 */ + unsigned char groupIndex; +} +TMsTriangle; + +/* ms3d material */ +typedef struct SMsMaterial +{ + char name[32]; + float ambient[4]; + float diffuse[4]; + float specular[4]; + float emissive[4]; + float shininess; /* range 0..128 */ + float transparency; /* range 0..1 */ + unsigned char mode; + char texture [128]; /* texture.bmp */ + char alphamap[128]; /* alpha.bmp */ +} +TMsMaterial; + +// ms3d group (static part) +// followed by a variable size block (see below) +typedef struct SMsGroup +{ + unsigned char flags; // sel, hidden + char name[32]; + unsigned short numTriangles; +/* + unsigned short triangleIndices[ numTriangles ]; + char materialIndex; // -1 means 'no material' +*/ +} +TMsGroup; + +// ms3d joint +typedef struct SMsJoint +{ + unsigned char flags; + char name[32]; + char parentName[32]; + float rotation[3]; + float translation[3]; + unsigned short numRotationKeyframes; + unsigned short numTranslationKeyframes; +} +TMsJoint; + +// ms3d keyframe +typedef struct SMsKeyframe +{ + float time; + float parameter[3]; +} +TMsKeyframe; + +/* restore previous data alignment */ +#pragma pack(pop) + +/* _ms3d_canload: + * validates a milkshape3d model file. + */ +static int _ms3d_canload( PM_PARAMS_CANLOAD ) +{ + TMsHeader *hdr; + + + /* to keep the compiler happy */ + *fileName = *fileName; + + /* sanity check */ + if (bufSize < sizeof(TMsHeader)) + return PICO_PMV_ERROR_SIZE; + + /* get ms3d header */ + hdr = (TMsHeader *)buffer; + + /* check ms3d magic */ + if (strncmp(hdr->magic,"MS3D000000",10) != 0) + return PICO_PMV_ERROR_IDENT; + + /* check ms3d version */ + if (_pico_little_long(hdr->version) < 3 || + _pico_little_long(hdr->version) > 4) + { + _pico_printf( PICO_ERROR,"MS3D file ignored. Only MS3D 1.3 and 1.4 is supported." ); + return PICO_PMV_ERROR_VERSION; + } + /* file seems to be a valid ms3d */ + return PICO_PMV_OK; +} + +static unsigned char *GetWord( unsigned char *bufptr, int *out ) +{ + if (bufptr == NULL) return NULL; + *out = _pico_little_short( *(unsigned short *)bufptr ); + return( bufptr + 2 ); +} + +/* _ms3d_load: + * loads a milkshape3d model file. +*/ +static picoModel_t *_ms3d_load( PM_PARAMS_LOAD ) +{ + picoModel_t *model; + unsigned char *bufptr; + int shaderRefs[ MS3D_MAX_GROUPS ]; + int numGroups; + int numMaterials; +// unsigned char *ptrToGroups; + int numVerts; + unsigned char *ptrToVerts; + int numTris; + unsigned char *ptrToTris; + int i,k,m; + + /* create new pico model */ + model = PicoNewModel(); + if (model == NULL) return NULL; + + /* do model setup */ + PicoSetModelFrameNum( model, frameNum ); + PicoSetModelName( model, fileName ); + PicoSetModelFileName( model, fileName ); + + /* skip header */ + bufptr = (unsigned char *)buffer + sizeof(TMsHeader); + + /* get number of vertices */ + bufptr = GetWord( bufptr,&numVerts ); + ptrToVerts = bufptr; + +#ifdef DEBUG_PM_MS3D + printf("NumVertices: %d\n",numVerts); +#endif + /* swap verts */ + for (i=0; ixyz[ 0 ] = _pico_little_float( vertex->xyz[ 0 ] ); + vertex->xyz[ 1 ] = _pico_little_float( vertex->xyz[ 1 ] ); + vertex->xyz[ 2 ] = _pico_little_float( vertex->xyz[ 2 ] ); + +#ifdef DEBUG_PM_MS3D_EX_ + printf("Vertex: x: %f y: %f z: %f\n", + msvd[i]->vertex[0], + msvd[i]->vertex[1], + msvd[i]->vertex[2]); +#endif + } + /* get number of triangles */ + bufptr = GetWord( bufptr,&numTris ); + ptrToTris = bufptr; + +#ifdef DEBUG_PM_MS3D + printf("NumTriangles: %d\n",numTris); +#endif + /* swap tris */ + for (i=0; iflags = _pico_little_short( triangle->flags ); + + /* run through all tri verts */ + for (k=0; k<3; k++) + { + /* swap tex coords */ + triangle->s[ k ] = _pico_little_float( triangle->s[ k ] ); + triangle->t[ k ] = _pico_little_float( triangle->t[ k ] ); + + /* swap fields */ + triangle->vertexIndices[ k ] = _pico_little_short( triangle->vertexIndices[ k ] ); + triangle->vertexNormals[ 0 ][ k ] = _pico_little_float( triangle->vertexNormals[ 0 ][ k ] ); + triangle->vertexNormals[ 1 ][ k ] = _pico_little_float( triangle->vertexNormals[ 1 ][ k ] ); + triangle->vertexNormals[ 2 ][ k ] = _pico_little_float( triangle->vertexNormals[ 2 ][ k ] ); + + /* check for out of range indices */ + if (triangle->vertexIndices[ k ] >= numVerts) + { + _pico_printf( PICO_ERROR,"Vertex %d index %d out of range (%d, max %d)",i,k,triangle->vertexIndices[k],numVerts-1); + PicoFreeModel( model ); + return NULL; /* yuck */ + } + } + } + /* get number of groups */ + bufptr = GetWord( bufptr,&numGroups ); +// ptrToGroups = bufptr; + +#ifdef DEBUG_PM_MS3D + printf("NumGroups: %d\n",numGroups); +#endif + /* run through all groups in model */ + for (i=0; i
2Af9YDRoPX5d44{qku}iphSy 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%0Df$FSswf%4 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

({RX16-Zk;7E4z5eH!R-~8q``QpF)%fGO} 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 literal 0 HcmV?d00001 diff --git a/plugins/model/model.cpp b/plugins/model/model.cpp new file mode 100644 index 00000000..47589744 --- /dev/null +++ b/plugins/model/model.cpp @@ -0,0 +1,1048 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "model.h" + +#include "picomodel.h" + +#include "iarchive.h" +#include "idatastream.h" +#include "imodel.h" +#include "modelskin.h" + +#include "cullable.h" +#include "renderable.h" +#include "selectable.h" + +#include "math/frustum.h" +#include "string/string.h" +#include "generic/static.h" +#include "shaderlib.h" +#include "scenelib.h" +#include "instancelib.h" +#include "transformlib.h" +#include "traverselib.h" +#include "render.h" + +class VectorLightList : public LightList +{ + typedef std::vector Lights; + Lights m_lights; +public: + void addLight(const RendererLight& light) + { + m_lights.push_back(&light); + } + void clear() + { + m_lights.clear(); + } + void evaluateLights() const + { + } + void lightsChanged() const + { + } + void forEachLight(const RendererLightCallback& callback) const + { + for(Lights::const_iterator i = m_lights.begin(); i != m_lights.end(); ++i) + { + callback(*(*i)); + } + } +}; + +class PicoSurface : +public OpenGLRenderable +{ + AABB m_aabb_local; + CopiedString m_shader; + Shader* m_state; + + Array m_vertices; + Array m_indices; + +public: + + PicoSurface() + { + constructNull(); + CaptureShader(); + } + PicoSurface(picoSurface_t* surface) + { + CopyPicoSurface(surface); + CaptureShader(); + } + ~PicoSurface() + { + ReleaseShader(); + } + + void render(RenderStateFlags state) const + { + if((state & RENDER_BUMP) != 0) + { + if(GlobalShaderCache().useShaderLanguage()) + { + glNormalPointer(GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_vertices.data()->normal); + glVertexAttribPointerARB(c_attr_TexCoord0, 2, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_vertices.data()->texcoord); + glVertexAttribPointerARB(c_attr_Tangent, 3, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_vertices.data()->tangent); + glVertexAttribPointerARB(c_attr_Binormal, 3, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_vertices.data()->bitangent); + } + else + { + glVertexAttribPointerARB(11, 3, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_vertices.data()->normal); + glVertexAttribPointerARB(8, 2, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_vertices.data()->texcoord); + glVertexAttribPointerARB(9, 3, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_vertices.data()->tangent); + glVertexAttribPointerARB(10, 3, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_vertices.data()->bitangent); + } + } + else + { + glNormalPointer(GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_vertices.data()->normal); + glTexCoordPointer(2, GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_vertices.data()->texcoord); + } + glVertexPointer(3, GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_vertices.data()->vertex); + glDrawElements(GL_TRIANGLES, GLsizei(m_indices.size()), RenderIndexTypeID, m_indices.data()); + +#if defined(_DEBUG) + GLfloat modelview[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, modelview); // I know this is slow as hell, but hey - we're in _DEBUG + Matrix4 modelview_inv( + modelview[0], modelview[1], modelview[2], modelview[3], + modelview[4], modelview[5], modelview[6], modelview[7], + modelview[8], modelview[9], modelview[10], modelview[11], + modelview[12], modelview[13], modelview[14], modelview[15]); + matrix4_full_invert(modelview_inv); + Matrix4 modelview_inv_transposed = matrix4_transposed(modelview_inv); + + glBegin(GL_LINES); + + for(Array::const_iterator i = m_vertices.begin(); i != m_vertices.end(); ++i) + { + Vector3 normal = normal3f_to_vector3((*i).normal); + normal = matrix4_transformed_direction(modelview_inv, vector3_normalised(matrix4_transformed_direction(modelview_inv_transposed, normal))); // do some magic + Vector3 normalTransformed = vector3_added(vertex3f_to_vector3((*i).vertex), vector3_scaled(normal, 8)); + glVertex3fv(vertex3f_to_array((*i).vertex)); + glVertex3fv(vector3_to_array(normalTransformed)); + } + glEnd(); +#endif + } + + VolumeIntersectionValue intersectVolume(const VolumeTest& test, const Matrix4& localToWorld) const + { + return test.TestAABB(m_aabb_local, localToWorld); + } + + const AABB& localAABB() const + { + return m_aabb_local; + } + + void render(Renderer& renderer, const Matrix4& localToWorld, Shader* state) const + { + renderer.SetState(state, Renderer::eFullMaterials); + renderer.addRenderable(*this, localToWorld); + } + + void render(Renderer& renderer, const Matrix4& localToWorld) const + { + render(renderer, localToWorld, m_state); + } + + void testSelect(Selector& selector, SelectionTest& test, const Matrix4& localToWorld) + { + test.BeginMesh(localToWorld); + + SelectionIntersection best; + testSelect(test, best); + if(best.valid()) + { + selector.addIntersection(best); + } + } + + const char* getShader() const + { + return m_shader.c_str(); + } + Shader* getState() const + { + return m_state; + } + +private: + + void CaptureShader() + { + m_state = GlobalShaderCache().capture(m_shader.c_str()); + } + void ReleaseShader() + { + GlobalShaderCache().release(m_shader.c_str()); + } + + void UpdateAABB() + { + m_aabb_local = AABB(); + for(std::size_t i = 0; i < m_vertices.size(); ++i ) + aabb_extend_by_point_safe(m_aabb_local, reinterpret_cast(m_vertices[i].vertex)); + + + for(Array::iterator i = m_indices.begin(); i != m_indices.end(); i += 3) + { + ArbitraryMeshVertex& a = m_vertices[*(i + 0)]; + ArbitraryMeshVertex& b = m_vertices[*(i + 1)]; + ArbitraryMeshVertex& c = m_vertices[*(i + 2)]; + + ArbitraryMeshTriangle_sumTangents(a, b, c); + } + + for(Array::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i) + { + vector3_normalise(reinterpret_cast((*i).tangent)); + vector3_normalise(reinterpret_cast((*i).bitangent)); + } + } + + void testSelect(SelectionTest& test, SelectionIntersection& best) + { + test.TestTriangles( + VertexPointer(VertexPointer::pointer(&m_vertices.data()->vertex), sizeof(ArbitraryMeshVertex)), + IndexPointer(m_indices.data(), IndexPointer::index_type(m_indices.size())), + best + ); + } + + void CopyPicoSurface(picoSurface_t* surface) + { + picoShader_t* shader = PicoGetSurfaceShader( surface ); + if( shader == 0 ) + m_shader = ""; + else + m_shader = PicoGetShaderName( shader ); + + m_vertices.resize( PicoGetSurfaceNumVertexes( surface ) ); + m_indices.resize( PicoGetSurfaceNumIndexes( surface ) ); + + for(std::size_t i = 0; i < m_vertices.size(); ++i ) + { + picoVec_t* xyz = PicoGetSurfaceXYZ( surface, int(i) ); + m_vertices[i].vertex = vertex3f_from_array(xyz); + + picoVec_t* normal = PicoGetSurfaceNormal( surface, int(i) ); + m_vertices[i].normal = normal3f_from_array(normal); + + picoVec_t* st = PicoGetSurfaceST( surface, 0, int(i) ); + m_vertices[i].texcoord = TexCoord2f(st[0], st[1]); + +#if 0 + picoVec_t* color = PicoGetSurfaceColor( surface, 0, int(i) ); + m_vertices[i].colour = Colour4b(color[0], color[1], color[2], color[3]); +#endif + } + + picoIndex_t* indexes = PicoGetSurfaceIndexes( surface, 0 ); + for(std::size_t j = 0; j < m_indices.size(); ++j ) + m_indices[ j ] = indexes[ j ]; + + UpdateAABB(); + } + + void constructQuad(std::size_t index, const Vector3& a, const Vector3& b, const Vector3& c, const Vector3& d, const Vector3& normal) + { + m_vertices[index * 4 + 0] = ArbitraryMeshVertex( + vertex3f_for_vector3(a), + normal3f_for_vector3(normal), + texcoord2f_from_array(aabb_texcoord_topleft) + ); + m_vertices[index * 4 + 1] = ArbitraryMeshVertex( + vertex3f_for_vector3(b), + normal3f_for_vector3(normal), + texcoord2f_from_array(aabb_texcoord_topright) + ); + m_vertices[index * 4 + 2] = ArbitraryMeshVertex( + vertex3f_for_vector3(c), + normal3f_for_vector3(normal), + texcoord2f_from_array(aabb_texcoord_botright) + ); + m_vertices[index * 4 + 3] = ArbitraryMeshVertex( + vertex3f_for_vector3(d), + normal3f_for_vector3(normal), + texcoord2f_from_array(aabb_texcoord_botleft) + ); + } + + void constructNull() + { + AABB aabb(Vector3(0, 0, 0), Vector3(8, 8, 8)); + + Vector3 points[8]; + aabb_corners(aabb, points); + + m_vertices.resize(24); + + constructQuad(0, points[2], points[1], points[5], points[6], aabb_normals[0]); + constructQuad(1, points[1], points[0], points[4], points[5], aabb_normals[1]); + constructQuad(2, points[0], points[1], points[2], points[3], aabb_normals[2]); + constructQuad(3, points[0], points[3], points[7], points[4], aabb_normals[3]); + constructQuad(4, points[3], points[2], points[6], points[7], aabb_normals[4]); + constructQuad(5, points[7], points[6], points[5], points[4], aabb_normals[5]); + + m_indices.resize(36); + + RenderIndex indices[36] = { + 0, 1, 2, 0, 2, 3, + 4, 5, 6, 4, 6, 7, + 8, 9, 10, 8, 10, 11, + 12, 13, 14, 12, 14, 15, + 16, 17, 18, 16, 18, 19, + 20, 21, 22, 10, 22, 23, + }; + + + Array::iterator j = m_indices.begin(); + for(RenderIndex* i = indices; i != indices+(sizeof(indices)/sizeof(RenderIndex)); ++i) + { + *j++ = *i; + } + + m_shader = ""; + + UpdateAABB(); + } +}; + + +typedef std::pair PicoModelKey; + + +class PicoModel : +public Cullable, +public Bounded +{ + typedef std::vector surfaces_t; + surfaces_t m_surfaces; + + AABB m_aabb_local; +public: + Callback m_lightsChanged; + + PicoModel() + { + constructNull(); + } + PicoModel(picoModel_t* model) + { + CopyPicoModel(model); + } + ~PicoModel() + { + for(surfaces_t::iterator i = m_surfaces.begin(); i != m_surfaces.end(); ++i) + delete *i; + } + + typedef surfaces_t::const_iterator const_iterator; + + const_iterator begin() const + { + return m_surfaces.begin(); + } + const_iterator end() const + { + return m_surfaces.end(); + } + std::size_t size() const + { + return m_surfaces.size(); + } + + VolumeIntersectionValue intersectVolume(const VolumeTest& test, const Matrix4& localToWorld) const + { + return test.TestAABB(m_aabb_local, localToWorld); + } + + virtual const AABB& localAABB() const + { + return m_aabb_local; + } + + void render(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, std::vector states) const + { + for(surfaces_t::const_iterator i = m_surfaces.begin(); i != m_surfaces.end(); ++i) + { + if((*i)->intersectVolume(volume, localToWorld) != c_volumeOutside) + { + (*i)->render(renderer, localToWorld, states[i - m_surfaces.begin()]); + } + } + } + + void testSelect(Selector& selector, SelectionTest& test, const Matrix4& localToWorld) + { + for(surfaces_t::iterator i = m_surfaces.begin(); i != m_surfaces.end(); ++i) + { + if((*i)->intersectVolume(test.getVolume(), localToWorld) != c_volumeOutside) + { + (*i)->testSelect(selector, test, localToWorld); + } + } + } + +private: + void CopyPicoModel(picoModel_t* model) + { + m_aabb_local = AABB(); + + /* each surface on the model will become a new map drawsurface */ + int numSurfaces = PicoGetModelNumSurfaces( model ); + //% SYs_FPrintf( SYS_VRB, "Model %s has %d surfaces\n", name, numSurfaces ); + for(int s = 0; s < numSurfaces; ++s) + { + /* get surface */ + picoSurface_t* surface = PicoGetModelSurface( model, s ); + if( surface == 0 ) + continue; + + /* only handle triangle surfaces initially (fixme: support patches) */ + if( PicoGetSurfaceType( surface ) != PICO_TRIANGLES ) + continue; + + /* fix the surface's normals */ + PicoFixSurfaceNormals( surface ); + + PicoSurface* picosurface = new PicoSurface(surface); + aabb_extend_by_aabb_safe(m_aabb_local, picosurface->localAABB()); + m_surfaces.push_back(picosurface); + } + } + void constructNull() + { + PicoSurface* picosurface = new PicoSurface(); + m_aabb_local = picosurface->localAABB(); + m_surfaces.push_back(picosurface); + } +}; + +inline void Surface_addLight(PicoSurface& surface, VectorLightList& lights, const Matrix4& localToWorld, const RendererLight& light) +{ + if(light.testAABB(aabb_for_oriented_aabb(surface.localAABB(), localToWorld))) + { + lights.addLight(light); + } +} + +class PicoModelInstance : + public scene::Instance, + public Renderable, + public SelectionTestable, + public LightCullable, + public SkinnedModel +{ + class TypeCasts + { + InstanceTypeCastTable m_casts; + public: + TypeCasts() + { + InstanceContainedCast::install(m_casts); + InstanceContainedCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + } + InstanceTypeCastTable& get() + { + return m_casts; + } + }; + + PicoModel& m_picomodel; + + const LightList* m_lightList; + typedef Array SurfaceLightLists; + SurfaceLightLists m_surfaceLightLists; + + class Remap + { + public: + CopiedString first; + Shader* second; + Remap() : second(0) + { + } + }; + typedef Array SurfaceRemaps; + SurfaceRemaps m_skins; + + PicoModelInstance(const PicoModelInstance&); + PicoModelInstance operator=(const PicoModelInstance&); +public: + typedef LazyStatic StaticTypeCasts; + + void* m_test; + + Bounded& get(NullType) + { + return m_picomodel; + } + Cullable& get(NullType) + { + return m_picomodel; + } + + void lightsChanged() + { + m_lightList->lightsChanged(); + } + typedef MemberCaller LightsChangedCaller; + + void constructRemaps() + { + ASSERT_MESSAGE(m_skins.size() == m_picomodel.size(), "ERROR"); + ModelSkin* skin = NodeTypeCast::cast(path().parent()); + if(skin != 0 && skin->realised()) + { + SurfaceRemaps::iterator j = m_skins.begin(); + for(PicoModel::const_iterator i = m_picomodel.begin(); i != m_picomodel.end(); ++i, ++j) + { + const char* remap = skin->getRemap((*i)->getShader()); + if(!string_empty(remap)) + { + (*j).first = remap; + (*j).second = GlobalShaderCache().capture(remap); + } + else + { + (*j).second = 0; + } + } + SceneChangeNotify(); + } + } + void destroyRemaps() + { + ASSERT_MESSAGE(m_skins.size() == m_picomodel.size(), "ERROR"); + for(SurfaceRemaps::iterator i = m_skins.begin(); i != m_skins.end(); ++i) + { + if((*i).second != 0) + { + GlobalShaderCache().release((*i).first.c_str()); + (*i).second = 0; + } + } + } + void skinChanged() + { + destroyRemaps(); + constructRemaps(); + } + + PicoModelInstance(const scene::Path& path, scene::Instance* parent, PicoModel& picomodel) : + Instance(path, parent, this, StaticTypeCasts::instance().get()), + m_picomodel(picomodel), + m_surfaceLightLists(m_picomodel.size()), + m_skins(m_picomodel.size()) + { + m_lightList = &GlobalShaderCache().attach(*this); + m_picomodel.m_lightsChanged = LightsChangedCaller(*this); + + Instance::setTransformChangedCallback(LightsChangedCaller(*this)); + + constructRemaps(); + } + ~PicoModelInstance() + { + destroyRemaps(); + + Instance::setTransformChangedCallback(Callback()); + + m_picomodel.m_lightsChanged = Callback(); + GlobalShaderCache().detach(*this); + } + + void render(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld) const + { + SurfaceLightLists::const_iterator j = m_surfaceLightLists.begin(); + SurfaceRemaps::const_iterator k = m_skins.begin(); + for(PicoModel::const_iterator i = m_picomodel.begin(); i != m_picomodel.end(); ++i, ++j, ++k) + { + if((*i)->intersectVolume(volume, localToWorld) != c_volumeOutside) + { + renderer.setLights(*j); + (*i)->render(renderer, localToWorld, (*k).second != 0 ? (*k).second : (*i)->getState()); + } + } + } + + void renderSolid(Renderer& renderer, const VolumeTest& volume) const + { + m_lightList->evaluateLights(); + + render(renderer, volume, Instance::localToWorld()); + } + void renderWireframe(Renderer& renderer, const VolumeTest& volume) const + { + renderSolid(renderer, volume); + } + + void testSelect(Selector& selector, SelectionTest& test) + { + m_picomodel.testSelect(selector, test, Instance::localToWorld()); + } + + bool testLight(const RendererLight& light) const + { + return light.testAABB(worldAABB()); + } + void insertLight(const RendererLight& light) + { + const Matrix4& localToWorld = Instance::localToWorld(); + SurfaceLightLists::iterator j = m_surfaceLightLists.begin(); + for(PicoModel::const_iterator i = m_picomodel.begin(); i != m_picomodel.end(); ++i) + { + Surface_addLight(*(*i), *j++, localToWorld, light); + } + } + void clearLights() + { + for(SurfaceLightLists::iterator i = m_surfaceLightLists.begin(); i != m_surfaceLightLists.end(); ++i) + { + (*i).clear(); + } + } +}; + +class PicoModelNode : public scene::Node::Symbiot, public scene::Instantiable +{ + class TypeCasts + { + NodeTypeCastTable m_casts; + public: + TypeCasts() + { + NodeStaticCast::install(m_casts); + } + NodeTypeCastTable& get() + { + return m_casts; + } + }; + + + scene::Node m_node; + InstanceSet m_instances; + PicoModel m_picomodel; + +public: + typedef LazyStatic StaticTypeCasts; + + PicoModelNode() : m_node(this, this, StaticTypeCasts::instance().get()) + { + } + PicoModelNode(picoModel_t* model) : m_node(this, this, StaticTypeCasts::instance().get()), m_picomodel(model) + { + } + + void release() + { + delete this; + } + scene::Node& node() + { + return m_node; + } + + scene::Instance* create(const scene::Path& path, scene::Instance* parent) + { + return new PicoModelInstance(path, parent, m_picomodel); + } + void forEachInstance(const scene::Instantiable::Visitor& visitor) + { + m_instances.forEachInstance(visitor); + } + void insert(scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance) + { + m_instances.insert(observer, path, instance); + } + scene::Instance* erase(scene::Instantiable::Observer* observer, const scene::Path& path) + { + return m_instances.erase(observer, path); + } +}; + + +#if 0 + +template +class create_new +{ +public: + static Type* construct(const Key& key) + { + return new Type(key); + } + static void destroy(Type* value) + { + delete value; + } +}; + +template > +class cache_element : public creation_policy +{ +public: + inline cache_element() : m_count(0), m_value(0) {} + inline ~cache_element() + { + ASSERT_MESSAGE(m_count == 0 , "destroyed a reference before it was released\n"); + if(m_count > 0) + destroy(); + } + inline Type* capture(const Key& key) + { + if(++m_count == 1) + construct(key); + return m_value; + } + inline void release() + { + ASSERT_MESSAGE(!empty(), "failed to release reference - not found in cache\n"); + if(--m_count == 0) + destroy(); + } + inline bool empty() + { + return m_count == 0; + } + inline void refresh(const Key& key) + { + m_value->refresh(key); + } +private: + inline void construct(const Key& key) + { + m_value = creation_policy::construct(key); + } + inline void destroy() + { + creation_policy::destroy(m_value); + } + + std::size_t m_count; + Type* m_value; +}; + +class create_picomodel +{ + typedef PicoModelKey key_type; + typedef PicoModel value_type; +public: + static value_type* construct(const key_type& key) + { + picoModel_t* picomodel = PicoLoadModel(const_cast(key.first.c_str()), key.second); + value_type* value = new value_type(picomodel); + PicoFreeModel(picomodel); + return value; + } + static void destroy(value_type* value) + { + delete value; + } +}; + +#include + +class ModelCache +{ + typedef PicoModel value_type; + +public: + typedef PicoModelKey key_type; + typedef cache_element elem_type; + typedef std::map cache_type; + + value_type* capture(const key_type& key) + { + return m_cache[key].capture(key); + } + void release(const key_type& key) + { + m_cache[key].release(); + } + +private: + cache_type m_cache; +}; + +ModelCache g_model_cache; + + + +typedef struct remap_s { + char m_remapbuff[64+1024]; + char *original; + char *remap; +} remap_t; + +class RemapWrapper : +public Cullable, +public Bounded +{ +public: + RemapWrapper(const char* name) + { + parse_namestr(name); + + m_model = g_model_cache.capture(ModelCache::key_type(m_name, m_frame)); + + construct_shaders(); + } + virtual ~RemapWrapper() + { + g_model_cache.release(ModelCache::key_type(m_name, m_frame)); + + for(shaders_t::iterator i = m_shaders.begin(); i != m_shaders.end(); ++i) + { + GlobalShaderCache().release((*i).c_str()); + } + + for(remaps_t::iterator j = m_remaps.begin(); j != m_remaps.end(); ++j) + { + delete (*j); + } + } + + VolumeIntersectionValue intersectVolume(const VolumeTest& test, const Matrix4& localToWorld) const + { + return m_model->intersectVolume(test, localToWorld); + } + + virtual const AABB& localAABB() const + { + return m_model->localAABB(); + } + + void render(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld) const + { + m_model->render(renderer, volume, localToWorld, m_states); + } + + void testSelect(Selector& selector, SelectionTest& test, const Matrix4& localToWorld) + { + m_model->testSelect(selector, test, localToWorld); + } + +private: + void add_remap(const char *remap) + { + const char *ch; + remap_t *pRemap; + + ch = remap; + + while( *ch && *ch != ';' ) + ch++; + + if( *ch == '\0' ) { + // bad remap + globalErrorStream() << "WARNING: Shader _remap key found in a model entity without a ; character\n"; + } else { + pRemap = new remap_t; + + strncpy( pRemap->m_remapbuff, remap, sizeof(pRemap->m_remapbuff) ); + + pRemap->m_remapbuff[ch - remap] = '\0'; + + pRemap->original = pRemap->m_remapbuff; + pRemap->remap = pRemap->m_remapbuff + ( ch - remap ) + 1; + + m_remaps.push_back( pRemap ); + } + } + + void parse_namestr(const char *name) + { + const char *ptr, *s; + bool hasName, hasFrame; + + hasName = hasFrame = false; + + m_frame = 0; + + for( s = ptr = name; ; ++ptr ) + { + if( !hasName && (*ptr == ':' || *ptr == '\0')) + { + // model name + hasName = true; + m_name = CopiedString(s, ptr); + s = ptr + 1; + } + else if(*ptr == '?' || *ptr == '\0') + { + // model frame + hasFrame = true; + m_frame = atoi(CopiedString(s, ptr).c_str()); + s = ptr + 1; + } + else if(*ptr == '&' || *ptr == '\0') + { + // a remap + add_remap(CopiedString(s, ptr).c_str()); + s = ptr + 1; + } + + if(*ptr == '\0') + break; + } + } + + void construct_shaders() + { + const char* global_shader = shader_for_remap("*"); + + m_shaders.reserve(m_model->size()); + m_states.reserve(m_model->size()); + for(PicoModel::iterator i = m_model->begin(); i != m_model->end(); ++i) + { + const char* shader = shader_for_remap((*i)->getShader()); + m_shaders.push_back( + (shader[0] != '\0') + ? shader + : (global_shader[0] != '\0') + ? global_shader + : (*i)->getShader()); + m_states.push_back(GlobalShaderCache().capture(m_shaders.back().c_str())); + } + } + + inline const char* shader_for_remap(const char* remap) + { + for(remaps_t::iterator i = m_remaps.begin(); i != m_remaps.end(); ++i) + { + if(shader_equal(remap, (*i)->original)) + { + return (*i)->remap; + } + } + return ""; + } + + CopiedString m_name; + int m_frame; + PicoModel* m_model; + + typedef std::vector remaps_t; + remaps_t m_remaps; + typedef std::vector shaders_t; + shaders_t m_shaders; + typedef std::vector states_t; + states_t m_states; +}; + +class RemapWrapperInstance : public scene::Instance, public Renderable, public SelectionTestable +{ + RemapWrapper& m_remapwrapper; +public: + RemapWrapperInstance(const scene::Path& path, scene::Instance* parent, RemapWrapper& remapwrapper) : Instance(path, parent), m_remapwrapper(remapwrapper) + { + scene::Instance::m_cullable = &m_remapwrapper; + scene::Instance::m_render = this; + scene::Instance::m_select = this; + } + + void renderSolid(Renderer& renderer, const VolumeTest& volume) const + { + m_remapwrapper.render(renderer, volume, Instance::localToWorld()); + } + void renderWireframe(Renderer& renderer, const VolumeTest& volume) const + { + renderSolid(renderer, volume); + } + + void testSelect(Selector& selector, SelectionTest& test) + { + m_remapwrapper.testSelect(selector, test, Instance::localToWorld()); + } +}; + +class RemapWrapperNode : public scene::Node::Symbiot, public scene::Instantiable +{ + scene::Node m_node; + typedef RemapWrapperInstance instance_type; + InstanceSet m_instances; + RemapWrapper m_remapwrapper; +public: + RemapWrapperNode(const char* name) : m_node(this), m_remapwrapper(name) + { + m_node.m_instance = this; + } + + void release() + { + delete this; + } + scene::Node& node() + { + return m_node; + } + + scene::Instance* create(const scene::Path& path, scene::Instance* parent) + { + return new instance_type(path, parent, m_remapwrapper); + } + void forEachInstance(const scene::Instantiable::Visitor& visitor) + { + m_instances.forEachInstance(visitor); + } + void insert(scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance) + { + m_instances.insert(observer, path, instance); + } + scene::Instance* erase(scene::Instantiable::Observer* observer, const scene::Path& path) + { + return m_instances.erase(observer, path); + } +}; + +scene::Node& LoadRemapModel(const char* name) +{ + return (new RemapWrapperNode(name))->node(); +} + +#endif + + +size_t picoInputStreamReam(void* inputStream, unsigned char* buffer, size_t length) +{ + return reinterpret_cast(inputStream)->read(buffer, length); +} + +scene::Node& loadPicoModel(const picoModule_t* module, ArchiveFile& file) +{ + picoModel_t* model = PicoModuleLoadModelStream(module, &file.getInputStream(), picoInputStreamReam, file.size(), 0); + PicoModelNode* modelNode = new PicoModelNode(model); + PicoFreeModel(model); + return modelNode->node(); +} diff --git a/plugins/model/model.h b/plugins/model/model.h new file mode 100644 index 00000000..9de1ffa2 --- /dev/null +++ b/plugins/model/model.h @@ -0,0 +1,30 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MODEL_H) +#define INCLUDED_MODEL_H + +namespace scene { class Node; } +class ArchiveFile; +typedef struct picoModule_s picoModule_t; +scene::Node& loadPicoModel(const picoModule_t* module, ArchiveFile& file); + +#endif diff --git a/plugins/model/modelpico.def b/plugins/model/modelpico.def new file mode 100644 index 00000000..b92e0dbb --- /dev/null +++ b/plugins/model/modelpico.def @@ -0,0 +1,7 @@ +; modelpico.def : Declares the module parameters for the DLL. + +LIBRARY "MODELPICO" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 \ No newline at end of file diff --git a/plugins/model/modelpico.vcproj b/plugins/model/modelpico.vcproj new file mode 100644 index 00000000..bffb4086 --- /dev/null +++ b/plugins/model/modelpico.vcproj @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/model/plugin.cpp b/plugins/model/plugin.cpp new file mode 100644 index 00000000..e032e398 --- /dev/null +++ b/plugins/model/plugin.cpp @@ -0,0 +1,189 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "plugin.h" + +#include +#include "picomodel.h" +typedef unsigned char byte; +#include +#include +#include + +#include "iscenegraph.h" +#include "irender.h" +#include "iselection.h" +#include "iimage.h" +#include "imodel.h" +#include "igl.h" +#include "ifilesystem.h" +#include "iundo.h" +#include "ifiletypes.h" + +#include "modulesystem/singletonmodule.h" +#include "stream/textstream.h" +#include "string/string.h" +#include "stream/stringstream.h" +#include "typesystem.h" + +#include "model.h" + +void PicoPrintFunc( int level, const char *str ) +{ + if( str == 0 ) + return; + switch( level ) + { + case PICO_NORMAL: + globalOutputStream() << str << "\n"; + break; + + case PICO_VERBOSE: + //globalOutputStream() << "PICO_VERBOSE: " << str << "\n"; + break; + + case PICO_WARNING: + globalErrorStream() << "PICO_WARNING: " << str << "\n"; + break; + + case PICO_ERROR: + globalErrorStream() << "PICO_ERROR: " << str << "\n"; + break; + + case PICO_FATAL: + globalErrorStream() << "PICO_FATAL: " << str << "\n"; + break; + } +} + +void PicoLoadFileFunc( char *name, byte **buffer, int *bufSize ) +{ + *bufSize = vfsLoadFile( (const char*) name, (void**) buffer); +} + +void PicoFreeFileFunc( void* file ) +{ + vfsFreeFile(file); +} + +void pico_initialise() +{ + PicoInit(); + PicoSetMallocFunc( malloc ); + PicoSetFreeFunc( free ); + PicoSetPrintFunc( PicoPrintFunc ); + PicoSetLoadFileFunc( PicoLoadFileFunc ); + PicoSetFreeFileFunc( PicoFreeFileFunc ); +} + + +class PicoModelLoader : public ModelLoader +{ + const picoModule_t* m_module; +public: + PicoModelLoader(const picoModule_t* module) : m_module(module) + { + } + scene::Node& loadModel(ArchiveFile& file) + { + return loadPicoModel(m_module, file); + } +}; + +class ModelPicoDependencies : + public GlobalFileSystemModuleRef, + public GlobalOpenGLModuleRef, + public GlobalUndoModuleRef, + public GlobalSceneGraphModuleRef, + public GlobalShaderCacheModuleRef, + public GlobalSelectionModuleRef, + public GlobalFiletypesModuleRef +{ +}; + +class ModelPicoAPI : public TypeSystemRef +{ + PicoModelLoader m_modelLoader; +public: + typedef ModelLoader Type; + + ModelPicoAPI(const char* extension, const picoModule_t* module) : + m_modelLoader(module) + { + StringOutputStream filter(128); + filter << "*." << extension; + GlobalFiletypesModule::getTable().addType(Type::Name(), extension, filetype_t(module->displayName, filter.c_str())); + } + ModelLoader* getTable() + { + return &m_modelLoader; + } +}; + +class PicoModelAPIConstructor +{ + CopiedString m_extension; + const picoModule_t* m_module; +public: + PicoModelAPIConstructor(const char* extension, const picoModule_t* module) : + m_extension(extension), m_module(module) + { + } + const char* getName() + { + return m_extension.c_str(); + } + ModelPicoAPI* constructAPI(ModelPicoDependencies& dependencies) + { + return new ModelPicoAPI(m_extension.c_str(), m_module); + } + void destroyAPI(ModelPicoAPI* api) + { + delete api; + } +}; + + +typedef SingletonModule PicoModelModule; +typedef std::list PicoModelModules; +PicoModelModules g_PicoModelModules; + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer& server) +{ + initialiseModule(server); + + pico_initialise(); + + const picoModule_t** modules = PicoModuleList( 0 ); + while(*modules != 0) + { + const picoModule_t* module = *modules++; + if(module->canload && module->load) + { + for(char*const* ext = module->defaultExts; *ext != 0; ++ext) + { + g_PicoModelModules.push_back(PicoModelModule(PicoModelAPIConstructor(*ext, module))); + g_PicoModelModules.back().selfRegister(); + } + } + } +} diff --git a/plugins/model/plugin.h b/plugins/model/plugin.h new file mode 100644 index 00000000..2f28d852 --- /dev/null +++ b/plugins/model/plugin.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_PLUGIN_H) +#define INCLUDED_PLUGIN_H + +#endif diff --git a/plugins/sample/sample.cpp b/plugins/sample/sample.cpp new file mode 100644 index 00000000..41aa9bf8 --- /dev/null +++ b/plugins/sample/sample.cpp @@ -0,0 +1,95 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "sample.h" + +#include "debugging/debugging.h" + +#include "iplugin.h" + +#include "string/string.h" +#include "modulesystem/singletonmodule.h" +#include "typesystem.h" + +namespace Sample +{ + const char* init(void* hApp, void* pMainWidget) + { + return ""; + } + const char* getName() + { + return "Sample Plugin"; + } + const char* getCommandList() + { + return "About;Do Something"; + } + const char* getCommandTitleList() + { + return ""; + } + void dispatch(const char* command, float* vMin, float* vMax, bool bSingleBrush) + { + if(string_equal(command, "About")) + { + globalOutputStream() << "Sample Demo Plugin\n"; + } + if(string_equal(command, "Do Something")) + { + globalOutputStream() << "Sample Command\n"; + } + } + +} // namespace + +class SamplePluginModule : public TypeSystemRef +{ + _QERPluginTable m_plugin; +public: + typedef _QERPluginTable Type; + STRING_CONSTANT(Name, "sample"); + + SamplePluginModule() + { + m_plugin.m_pfnQERPlug_Init = &Sample::init; + m_plugin.m_pfnQERPlug_GetName = &Sample::getName; + m_plugin.m_pfnQERPlug_GetCommandList = &Sample::getCommandList; + m_plugin.m_pfnQERPlug_GetCommandTitleList = &Sample::getCommandTitleList; + m_plugin.m_pfnQERPlug_Dispatch = &Sample::dispatch; + } + _QERPluginTable* getTable() + { + return &m_plugin; + } +}; + +typedef SingletonModule SingletonSamplePluginModule; + +SingletonSamplePluginModule g_SamplePluginModule; + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer& server) +{ + initialiseModule(server); + + g_SamplePluginModule.selfRegister(); +} diff --git a/plugins/sample/sample.def b/plugins/sample/sample.def new file mode 100644 index 00000000..4906f782 --- /dev/null +++ b/plugins/sample/sample.def @@ -0,0 +1,7 @@ +; sample.def : Declares the module parameters for the DLL. + +LIBRARY "SAMPLE" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/plugins/sample/sample.h b/plugins/sample/sample.h new file mode 100644 index 00000000..2f338b60 --- /dev/null +++ b/plugins/sample/sample.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_SAMPLE_H) +#define INCLUDED_SAMPLE_H + +#endif diff --git a/plugins/sample/sample.vcproj b/plugins/sample/sample.vcproj new file mode 100644 index 00000000..f4e10f8c --- /dev/null +++ b/plugins/sample/sample.vcproj @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/shaders/plugin.cpp b/plugins/shaders/plugin.cpp new file mode 100644 index 00000000..6ef23c37 --- /dev/null +++ b/plugins/shaders/plugin.cpp @@ -0,0 +1,157 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "plugin.h" + +#include "ishaders.h" +#include "ifilesystem.h" +#include "itextures.h" +#include "iscriplib.h" +#include "qerplugin.h" + +#include "string/string.h" +#include "modulesystem/singletonmodule.h" + +#include "shaders.h" + +class ShadersDependencies : + public GlobalFileSystemModuleRef, + public GlobalTexturesModuleRef, + public GlobalScripLibModuleRef, + public GlobalRadiantModuleRef +{ + ImageModuleRef m_bitmapModule; +public: + ShadersDependencies() : + m_bitmapModule("bmp") + { + } + ImageModuleRef& getBitmapModule() + { + return m_bitmapModule; + } +}; + +class ShadersQ3API +{ + ShaderSystem* m_shadersq3; +public: + typedef ShaderSystem Type; + STRING_CONSTANT(Name, "quake3"); + + ShadersQ3API(ShadersDependencies& dependencies) + { + g_shadersExtension = "shader"; + g_shadersDirectory = "scripts/"; + g_bitmapModule = dependencies.getBitmapModule().getTable(); + Shaders_Construct(); + m_shadersq3 = &GetShaderSystem(); + } + ~ShadersQ3API() + { + Shaders_Destroy(); + } + ShaderSystem* getTable() + { + return m_shadersq3; + } +}; + +typedef SingletonModule > ShadersQ3Module; + +ShadersQ3Module g_ShadersQ3Module; + + +class ShadersDoom3API +{ + ShaderSystem* m_shadersdoom3; +public: + typedef ShaderSystem Type; + STRING_CONSTANT(Name, "doom3"); + + ShadersDoom3API(ShadersDependencies& dependencies) + { + g_shadersExtension = "mtr"; + g_shadersDirectory = "materials/"; + g_enableDefaultShaders = false; + g_shaderLanguage = SHADERLANGUAGE_DOOM3; + g_useShaderList = false; + g_bitmapModule = dependencies.getBitmapModule().getTable(); + Shaders_Construct(); + m_shadersdoom3 = &GetShaderSystem(); + } + ~ShadersDoom3API() + { + Shaders_Destroy(); + } + ShaderSystem* getTable() + { + return m_shadersdoom3; + } +}; + +typedef SingletonModule > ShadersDoom3Module; + +ShadersDoom3Module g_ShadersDoom3Module; + + +class ShadersQuake4API +{ + ShaderSystem* m_shadersquake4; +public: + typedef ShaderSystem Type; + STRING_CONSTANT(Name, "quake4"); + + ShadersQuake4API(ShadersDependencies& dependencies) + { + g_shadersExtension = "mtr"; + g_shadersDirectory = "materials/"; + g_enableDefaultShaders = false; + g_shaderLanguage = SHADERLANGUAGE_QUAKE4; + g_useShaderList = false; + g_bitmapModule = dependencies.getBitmapModule().getTable(); + Shaders_Construct(); + m_shadersquake4 = &GetShaderSystem(); + } + ~ShadersQuake4API() + { + Shaders_Destroy(); + } + ShaderSystem* getTable() + { + return m_shadersquake4; + } +}; + +typedef SingletonModule > ShadersQuake4Module; + +ShadersQuake4Module g_ShadersQuake4Module; + + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer& server) +{ + initialiseModule(server); + + g_ShadersQ3Module.selfRegister(); + g_ShadersDoom3Module.selfRegister(); + g_ShadersQuake4Module.selfRegister(); +} diff --git a/plugins/shaders/plugin.h b/plugins/shaders/plugin.h new file mode 100644 index 00000000..2f28d852 --- /dev/null +++ b/plugins/shaders/plugin.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_PLUGIN_H) +#define INCLUDED_PLUGIN_H + +#endif diff --git a/plugins/shaders/shaders.cpp b/plugins/shaders/shaders.cpp new file mode 100644 index 00000000..e95adaff --- /dev/null +++ b/plugins/shaders/shaders.cpp @@ -0,0 +1,2074 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// Shaders Manager Plugin +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "shaders.h" + +#include +#include +#include +#include + +#include "ifilesystem.h" +#include "ishaders.h" +#include "iscriplib.h" +#include "itextures.h" +#include "qerplugin.h" +#include "irender.h" + +#include + +#include "debugging/debugging.h" +#include "string/pooledstring.h" +#include "math/vector.h" +#include "generic/callback.h" +#include "generic/referencecounted.h" +#include "stream/memstream.h" +#include "stream/stringstream.h" +#include "stream/textfilestream.h" +#include "os/path.h" +#include "os/dir.h" +#include "os/file.h" +#include "stringio.h" +#include "shaderlib.h" +#include "texturelib.h" +#include "cmdlib.h" +#include "moduleobservers.h" +#include "archivelib.h" +#include "imagelib.h" + +const char* g_shadersExtension = ""; +const char* g_shadersDirectory = ""; +bool g_enableDefaultShaders = true; +ShaderLanguage g_shaderLanguage = SHADERLANGUAGE_QUAKE3; +bool g_useShaderList = true; +_QERPlugImageTable* g_bitmapModule = 0; +const char* g_texturePrefix = "textures/"; + +void ActiveShaders_IteratorBegin(); +bool ActiveShaders_IteratorAtEnd(); +IShader *ActiveShaders_IteratorCurrent(); +void ActiveShaders_IteratorIncrement(); +Callback g_ActiveShadersChangedNotify; + +void FreeShaders(); +void LoadShaderFile (const char *filename); +qtexture_t *Texture_ForName (const char *filename); + + +/*! +NOTE TTimo: there is an important distinction between SHADER_NOT_FOUND and SHADER_NOTEX: +SHADER_NOT_FOUND means we didn't find the raw texture or the shader for this +SHADER_NOTEX means we recognize this as a shader script, but we are missing the texture to represent it +this was in the initial design of the shader code since early GtkRadiant alpha, and got sort of foxed in 1.2 and put back in +*/ + +Image* loadBitmap(void* environment, const char* name) +{ + DirectoryArchiveFile file(name, name); + if(!file.failed()) + { + return g_bitmapModule->loadImage(file); + } + return 0; +} + +inline byte* getPixel(byte* pixels, int width, int height, int x, int y) +{ + return pixels + (((((y + height) % height) * width) + ((x + width) % width)) * 4); +} + +class KernelElement +{ +public: + int x, y; + float w; +}; + +Image& convertHeightmapToNormalmap(Image& heightmap, float scale) +{ + int w = heightmap.getWidth(); + int h = heightmap.getHeight(); + + Image& normalmap = *(new RGBAImage(heightmap.getWidth(), heightmap.getHeight())); + + byte* in = heightmap.getRGBAPixels(); + byte* out = normalmap.getRGBAPixels(); + +#if 1 + // no filtering + const int kernelSize = 2; + KernelElement kernel_du[kernelSize] = { + {-1, 0,-0.5f }, + { 1, 0, 0.5f } + }; + KernelElement kernel_dv[kernelSize] = { + { 0, 1, 0.5f }, + { 0,-1,-0.5f } + }; +#else + // 3x3 Prewitt + const int kernelSize = 6; + KernelElement kernel_du[kernelSize] = { + {-1, 1,-1.0f }, + {-1, 0,-1.0f }, + {-1,-1,-1.0f }, + { 1, 1, 1.0f }, + { 1, 0, 1.0f }, + { 1,-1, 1.0f } + }; + KernelElement kernel_dv[kernelSize] = { + {-1, 1, 1.0f }, + { 0, 1, 1.0f }, + { 1, 1, 1.0f }, + {-1,-1,-1.0f }, + { 0,-1,-1.0f }, + { 1,-1,-1.0f } + }; +#endif + + int x, y = 0; + while( y < h ) + { + x = 0; + while( x < w ) + { + float du = 0; + for(KernelElement* i = kernel_du; i != kernel_du + kernelSize; ++i) + { + du += (getPixel(in, w, h, x + (*i).x, y + (*i).y)[0] / 255.0) * (*i).w; + } + float dv = 0; + for(KernelElement* i = kernel_dv; i != kernel_dv + kernelSize; ++i) + { + dv += (getPixel(in, w, h, x + (*i).x, y + (*i).y)[0] / 255.0) * (*i).w; + } + + float nx = -du * scale; + float ny = -dv * scale; + float nz = 1.0; + + // Normalize + float norm = 1.0/sqrt(nx*nx + ny*ny + nz*nz); + out[0] = float_to_integer(((nx * norm) + 1) * 127.5); + out[1] = float_to_integer(((ny * norm) + 1) * 127.5); + out[2] = float_to_integer(((nz * norm) + 1) * 127.5); + out[3] = 255; + + x++; + out += 4; + } + + y++; + } + + return normalmap; +} + +Image* loadHeightmap(void* environment, const char* name) +{ + Image* heightmap = GlobalTexturesCache().loadImage(name); + if(heightmap != 0) + { + Image& normalmap = convertHeightmapToNormalmap(*heightmap, *reinterpret_cast(environment)); + heightmap->release(); + return &normalmap; + } + return 0; +} + + +Image* loadSpecial(void* environment, const char* name) +{ + if(*name == '_') // special image + { + StringOutputStream bitmapName(256); + bitmapName << GlobalRadiant().getAppPath() << "bitmaps/" << name + 1 << ".bmp"; + Image* image = loadBitmap(environment, bitmapName.c_str()); + if(image != 0) + { + return image; + } + } + return GlobalTexturesCache().loadImage(name); +} + +class ShaderPoolContext +{ +}; +typedef Static ShaderPool; +typedef PooledString ShaderString; +typedef ShaderString ShaderVariable; +typedef ShaderString ShaderValue; +typedef CopiedString TextureExpression; + +// clean a texture name to the qtexture_t name format we use internally +// NOTE: case sensitivity: the engine is case sensitive. we store the shader name with case information and save with case +// information as well. but we assume there won't be any case conflict and so when doing lookups based on shader name, +// we compare as case insensitive. That is Radiant is case insensitive, but knows that the engine is case sensitive. +//++timo FIXME: we need to put code somewhere to detect when two shaders that are case insensitive equal are present +template +void parseTextureName(StringType& name, const char* token) +{ + StringOutputStream cleaned(256); + cleaned << PathCleaned(token); + name = CopiedString(StringRange(cleaned.c_str(), path_get_filename_base_end(cleaned.c_str()))).c_str(); // remove extension +} + +bool Tokeniser_parseTextureName(Tokeniser& tokeniser, TextureExpression& name) +{ + const char* token = tokeniser.getToken(); + if(token == 0) + { + Tokeniser_unexpectedError(tokeniser, token, "#texture-name"); + return false; + } + parseTextureName(name, token); + return true; +} + +bool Tokeniser_parseShaderName(Tokeniser& tokeniser, CopiedString& name) +{ + const char* token = tokeniser.getToken(); + if(token == 0) + { + Tokeniser_unexpectedError(tokeniser, token, "#shader-name"); + return false; + } + parseTextureName(name, token); + return true; +} + +bool Tokeniser_parseString(Tokeniser& tokeniser, ShaderString& string) +{ + const char* token = tokeniser.getToken(); + if(token == 0) + { + Tokeniser_unexpectedError(tokeniser, token, "#string"); + return false; + } + string = token; + return true; +} + + + +typedef std::list ShaderParameters; +typedef std::list ShaderArguments; + +typedef std::pair BlendFuncExpression; + +class ShaderTemplate +{ + std::size_t m_refcount; + CopiedString m_Name; +public: + + ShaderParameters m_params; + + TextureExpression m_textureName; + TextureExpression m_diffuse; + TextureExpression m_bump; + ShaderValue m_heightmapScale; + TextureExpression m_specular; + TextureExpression m_lightFalloffImage; + + int m_nFlags; + float m_fTrans; + + // alphafunc stuff + IShader::EAlphaFunc m_AlphaFunc; + float m_AlphaRef; + // cull stuff + IShader::ECull m_Cull; + + ShaderTemplate() : + m_refcount(0) + { + m_nFlags = 0; + m_fTrans = 1.0f; + } + + void IncRef() + { + ++m_refcount; + } + void DecRef() + { + ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero"); + if(--m_refcount == 0) + { + delete this; + } + } + + std::size_t refcount() + { + return m_refcount; + } + + const char* getName() const + { + return m_Name.c_str(); + } + void setName(const char* name) + { + m_Name = name; + } + + // ----------------------------------------- + + bool parseDoom3(Tokeniser& tokeniser); + bool parseQuake3(Tokeniser& tokeniser); + bool parseTemplate(Tokeniser& tokeniser); + + + void CreateDefault(const char *name) + { + if(g_enableDefaultShaders) + { + m_textureName = name; + } + else + { + m_textureName = ""; + } + setName(name); + } + + + class MapLayerTemplate + { + TextureExpression m_texture; + BlendFuncExpression m_blendFunc; + bool m_clampToBorder; + ShaderValue m_alphaTest; + public: + MapLayerTemplate(const TextureExpression& texture, const BlendFuncExpression& blendFunc, bool clampToBorder, const ShaderValue& alphaTest) : + m_texture(texture), + m_blendFunc(blendFunc), + m_clampToBorder(false), + m_alphaTest(alphaTest) + { + } + const TextureExpression& texture() const + { + return m_texture; + } + const BlendFuncExpression& blendFunc() const + { + return m_blendFunc; + } + bool clampToBorder() const + { + return m_clampToBorder; + } + const ShaderValue& alphaTest() const + { + return m_alphaTest; + } + }; + typedef std::vector MapLayers; + MapLayers m_layers; +}; + + +bool Doom3Shader_parseHeightmap(Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale) +{ + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, bump)); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ",")); + RETURN_FALSE_IF_FAIL(Tokeniser_parseString(tokeniser, heightmapScale)); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")")); + return true; +} + +bool Doom3Shader_parseAddnormals(Tokeniser& tokeniser, TextureExpression& bump) +{ + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, bump)); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ",")); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "heightmap")); + TextureExpression heightmapName; + ShaderValue heightmapScale; + RETURN_FALSE_IF_FAIL(Doom3Shader_parseHeightmap(tokeniser, heightmapName, heightmapScale)); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")")); + return true; +} + +bool Doom3Shader_parseBumpmap(Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale) +{ + const char* token = tokeniser.getToken(); + if(token == 0) + { + Tokeniser_unexpectedError(tokeniser, token, "#bumpmap"); + return false; + } + if(string_equal(token, "heightmap")) + { + RETURN_FALSE_IF_FAIL(Doom3Shader_parseHeightmap(tokeniser, bump, heightmapScale)); + } + else if(string_equal(token, "addnormals")) + { + RETURN_FALSE_IF_FAIL(Doom3Shader_parseAddnormals(tokeniser, bump)); + } + else + { + parseTextureName(bump, token); + } + return true; +} + +enum LayerTypeId +{ + LAYER_NONE, + LAYER_BLEND, + LAYER_DIFFUSEMAP, + LAYER_BUMPMAP, + LAYER_SPECULARMAP +}; + +class LayerTemplate +{ +public: + LayerTypeId m_type; + TextureExpression m_texture; + BlendFuncExpression m_blendFunc; + bool m_clampToBorder; + ShaderValue m_alphaTest; + ShaderValue m_heightmapScale; + + LayerTemplate() : m_type(LAYER_NONE), m_blendFunc("GL_ONE", "GL_ZERO"), m_clampToBorder(false), m_alphaTest("-1"), m_heightmapScale("0") + { + } +}; + +bool parseShaderParameters(Tokeniser& tokeniser, ShaderParameters& params) +{ + Tokeniser_parseToken(tokeniser, "("); + for(;;) + { + const char* param = tokeniser.getToken(); + if(string_equal(param, ")")) + { + break; + } + params.push_back(param); + const char* comma = tokeniser.getToken(); + if(string_equal(comma, ")")) + { + break; + } + if(!string_equal(comma, ",")) + { + Tokeniser_unexpectedError(tokeniser, comma, ","); + return false; + } + } + return true; +} + +bool ShaderTemplate::parseTemplate(Tokeniser& tokeniser) +{ + m_Name = tokeniser.getToken(); + if(!parseShaderParameters(tokeniser, m_params)) + { + globalErrorStream() << "shader template: " << makeQuoted(m_Name.c_str()) << ": parameter parse failed\n"; + return false; + } + + return parseDoom3(tokeniser); +} + +bool ShaderTemplate::parseDoom3(Tokeniser& tokeniser) +{ + LayerTemplate currentLayer; + bool isFog = false; + + // we need to read until we hit a balanced } + int depth = 0; + for(;;) + { + tokeniser.nextLine(); + const char* token = tokeniser.getToken(); + + if(token == 0) + return false; + + if(string_equal(token, "{")) + { + ++depth; + continue; + } + else if(string_equal(token, "}")) + { + --depth; + if(depth < 0) // error + { + return false; + } + if(depth == 0) // end of shader + { + break; + } + if(depth == 1) // end of layer + { + if(currentLayer.m_type == LAYER_DIFFUSEMAP) + { + m_diffuse = currentLayer.m_texture; + } + else if(currentLayer.m_type == LAYER_BUMPMAP) + { + m_bump = currentLayer.m_texture; + } + else if(currentLayer.m_type == LAYER_SPECULARMAP) + { + m_specular = currentLayer.m_texture; + } + else if(!string_empty(currentLayer.m_texture.c_str())) + { + m_layers.push_back(MapLayerTemplate( + currentLayer.m_texture.c_str(), + currentLayer.m_blendFunc, + currentLayer.m_clampToBorder, + currentLayer.m_alphaTest + )); + } + currentLayer.m_type = LAYER_NONE; + currentLayer.m_texture = ""; + } + continue; + } + + if(depth == 2) // in layer + { + if(string_equal_nocase(token, "blend")) + { + const char* blend = tokeniser.getToken(); + + if(blend == 0) + { + Tokeniser_unexpectedError(tokeniser, blend, "#blend"); + return false; + } + + if(string_equal_nocase(blend, "diffusemap")) + { + currentLayer.m_type = LAYER_DIFFUSEMAP; + } + else if(string_equal_nocase(blend, "bumpmap")) + { + currentLayer.m_type = LAYER_BUMPMAP; + } + else if(string_equal_nocase(blend, "specularmap")) + { + currentLayer.m_type = LAYER_SPECULARMAP; + } + else + { + currentLayer.m_blendFunc.first = blend; + + const char* comma = tokeniser.getToken(); + + if(comma == 0) + { + Tokeniser_unexpectedError(tokeniser, comma, "#comma"); + return false; + } + + if(string_equal(comma, ",")) + { + RETURN_FALSE_IF_FAIL(Tokeniser_parseString(tokeniser, currentLayer.m_blendFunc.second)); + } + else + { + currentLayer.m_blendFunc.second = ""; + tokeniser.ungetToken(); + } + } + } + else if(string_equal_nocase(token, "map")) + { + if(currentLayer.m_type == LAYER_BUMPMAP) + { + RETURN_FALSE_IF_FAIL(Doom3Shader_parseBumpmap(tokeniser, currentLayer.m_texture, currentLayer.m_heightmapScale)); + } + else + { + const char* map = tokeniser.getToken(); + + if(map == 0) + { + Tokeniser_unexpectedError(tokeniser, map, "#map"); + return false; + } + + if(string_equal(map, "makealpha")) + { + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + const char* texture = tokeniser.getToken(); + if(texture == 0) + { + Tokeniser_unexpectedError(tokeniser, texture, "#texture"); + return false; + } + currentLayer.m_texture = texture; + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")")); + } + else + { + parseTextureName(currentLayer.m_texture, map); + } + } + } + else if(string_equal_nocase(token, "zeroclamp")) + { + currentLayer.m_clampToBorder = true; + } +#if 0 + else if(string_equal_nocase(token, "alphaTest")) + { + Tokeniser_getFloat(tokeniser, currentLayer.m_alphaTest); + } +#endif + } + else if(depth == 1) + { + if(string_equal_nocase(token, "qer_editorimage")) + { + RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_textureName)); + } + else if (string_equal_nocase(token, "qer_trans")) + { + m_fTrans = string_read_float(tokeniser.getToken()); + m_nFlags |= QER_TRANS; + } + else if (string_equal_nocase(token, "translucent")) + { + m_fTrans = 1; + m_nFlags |= QER_TRANS; + } + else if (string_equal(token, "DECAL_MACRO")) + { + m_fTrans = 1; + m_nFlags |= QER_TRANS; + } + else if (string_equal_nocase(token, "bumpmap")) + { + RETURN_FALSE_IF_FAIL(Doom3Shader_parseBumpmap(tokeniser, m_bump, m_heightmapScale)); + } + else if (string_equal_nocase(token, "diffusemap")) + { + RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_diffuse)); + } + else if (string_equal_nocase(token, "specularmap")) + { + RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_specular)); + } + else if (string_equal_nocase(token, "twosided")) + { + m_Cull = IShader::eCullNone; + m_nFlags |= QER_CULL; + } + else if (string_equal_nocase(token, "nodraw")) + { + m_nFlags |= QER_NODRAW; + } + else if (string_equal_nocase(token, "nonsolid")) + { + m_nFlags |= QER_NONSOLID; + } + else if (string_equal_nocase(token, "liquid")) + { + m_nFlags |= QER_WATER; + } + else if (string_equal_nocase(token, "areaportal")) + { + m_nFlags |= QER_AREAPORTAL; + } + else if (string_equal_nocase(token, "playerclip") + || string_equal_nocase(token, "monsterclip") + || string_equal_nocase(token, "ikclip") + || string_equal_nocase(token, "moveableclip")) + { + m_nFlags |= QER_CLIP; + } + if (string_equal_nocase(token, "fogLight")) + { + isFog = true; + } + else if (!isFog && string_equal_nocase(token, "lightFalloffImage")) + { + const char* lightFalloffImage = tokeniser.getToken(); + if(lightFalloffImage == 0) + { + Tokeniser_unexpectedError(tokeniser, lightFalloffImage, "#lightFalloffImage"); + return false; + } + if(string_equal_nocase(lightFalloffImage, "makeintensity")) + { + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + TextureExpression name; + RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, name)); + m_lightFalloffImage = name; + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")")); + } + else + { + m_lightFalloffImage = lightFalloffImage; + } + } + } + } + + if(string_empty(m_textureName.c_str())) + { + m_textureName = m_diffuse; + } + + return true; +} + +typedef SmartPointer ShaderTemplatePointer; +typedef std::map ShaderTemplateMap; + +ShaderTemplateMap g_shaders; +ShaderTemplateMap g_shaderTemplates; + +ShaderTemplate* findTemplate(const char* name) +{ + ShaderTemplateMap::iterator i = g_shaderTemplates.find(name); + if(i != g_shaderTemplates.end()) + { + return (*i).second.get(); + } + return 0; +} + +class ShaderDefinition +{ +public: + ShaderDefinition(ShaderTemplate* shaderTemplate, const ShaderArguments& args, const char* filename) + : shaderTemplate(shaderTemplate), args(args), filename(filename) + { + } + ShaderTemplate* shaderTemplate; + ShaderArguments args; + const char* filename; +}; + +typedef std::map ShaderDefinitionMap; + +ShaderDefinitionMap g_shaderDefinitions; + +bool parseTemplateInstance(Tokeniser& tokeniser, const char* filename) +{ + CopiedString name; + RETURN_FALSE_IF_FAIL(Tokeniser_parseShaderName(tokeniser, name)); + const char* templateName = tokeniser.getToken(); + ShaderTemplate* shaderTemplate = findTemplate(templateName); + if(shaderTemplate == 0) + { + globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": shader template not found: " << makeQuoted(templateName) << "\n"; + } + + ShaderArguments args; + if(!parseShaderParameters(tokeniser, args)) + { + globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": argument parse failed\n"; + return false; + } + + if(shaderTemplate != 0) + { + if(!g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate, args, filename))).second) + { + globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": already exists, second definition ignored\n"; + } + } + return true; +} + + +const char* evaluateShaderValue(const char* value, const ShaderParameters& params, const ShaderArguments& args) +{ + ShaderArguments::const_iterator j = args.begin(); + for(ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j) + { + const char* other = (*i).c_str(); + if(string_equal(value, other)) + { + return (*j).c_str(); + } + } + return value; +} + +///\todo BlendFunc parsing +BlendFunc evaluateBlendFunc(const BlendFuncExpression& blendFunc, const ShaderParameters& params, const ShaderArguments& args) +{ + return BlendFunc(BLEND_ONE, BLEND_ZERO); +} + +qtexture_t* evaluateTexture(const TextureExpression& texture, const ShaderParameters& params, const ShaderArguments& args, const LoadImageCallback& loader = GlobalTexturesCache().defaultLoader()) +{ + StringOutputStream result(64); + const char* expression = texture.c_str(); + const char* end = expression + string_length(expression); + if(!string_empty(expression)) + { + for(;;) + { + const char* best = end; + const char* bestParam = 0; + const char* bestArg = 0; + ShaderArguments::const_iterator j = args.begin(); + for(ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j) + { + const char* found = strstr(expression, (*i).c_str()); + if(found != 0 && found < best) + { + best = found; + bestParam = (*i).c_str(); + bestArg = (*j).c_str(); + } + } + if(best != end) + { + result << StringRange(expression, best); + result << PathCleaned(bestArg); + expression = best + string_length(bestParam); + } + else + { + break; + } + } + result << expression; + } + return GlobalTexturesCache().capture(loader, result.c_str()); +} + +float evaluateFloat(const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args) +{ + const char* result = evaluateShaderValue(value.c_str(), params, args); + float f; + if(!string_parse_float(result, f)) + { + globalErrorStream() << "parsing float value failed: " << makeQuoted(result) << "\n"; + } + return f; +} + +BlendFactor evaluateBlendFactor(const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args) +{ + const char* result = evaluateShaderValue(value.c_str(), params, args); + + if(string_equal_nocase(result, "gl_zero")) + { + return BLEND_ZERO; + } + if(string_equal_nocase(result, "gl_one")) + { + return BLEND_ONE; + } + if(string_equal_nocase(result, "gl_src_color")) + { + return BLEND_SRC_COLOUR; + } + if(string_equal_nocase(result, "gl_one_minus_src_color")) + { + return BLEND_ONE_MINUS_SRC_COLOUR; + } + if(string_equal_nocase(result, "gl_src_alpha")) + { + return BLEND_SRC_ALPHA; + } + if(string_equal_nocase(result, "gl_one_minus_src_alpha")) + { + return BLEND_ONE_MINUS_SRC_ALPHA; + } + if(string_equal_nocase(result, "gl_dst_color")) + { + return BLEND_DST_COLOUR; + } + if(string_equal_nocase(result, "gl_one_minus_dst_color")) + { + return BLEND_ONE_MINUS_DST_COLOUR; + } + if(string_equal_nocase(result, "gl_dst_alpha")) + { + return BLEND_DST_ALPHA; + } + if(string_equal_nocase(result, "gl_one_minus_dst_alpha")) + { + return BLEND_ONE_MINUS_DST_ALPHA; + } + if(string_equal_nocase(result, "gl_src_alpha_saturate")) + { + return BLEND_SRC_ALPHA_SATURATE; + } + + globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted(result) << "\n"; + return BLEND_ZERO; +} + +class CShader : public IShader +{ + std::size_t m_refcount; + + const ShaderTemplate& m_template; + const ShaderArguments& m_args; + const char* m_filename; + // name is shader-name, otherwise texture-name (if not a real shader) + CopiedString m_Name; + + qtexture_t* m_pTexture; + qtexture_t* m_notfound; + qtexture_t* m_pDiffuse; + float m_heightmapScale; + qtexture_t* m_pBump; + qtexture_t* m_pSpecular; + qtexture_t* m_pLightFalloffImage; + BlendFunc m_blendFunc; + + bool m_bInUse; + + +public: + static bool m_lightingEnabled; + + CShader(const ShaderDefinition& definition) : + m_refcount(0), + m_template(*definition.shaderTemplate), + m_args(definition.args), + m_filename(definition.filename), + m_blendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA), + m_bInUse(false) + { + m_pTexture = 0; + m_pDiffuse = 0; + m_pBump = 0; + m_pSpecular = 0; + + m_notfound = 0; + + realise(); + } + virtual ~CShader() + { + unrealise(); + + ASSERT_MESSAGE(m_refcount == 0, "deleting active shader"); + } + + // IShaders implementation ----------------- + void IncRef() + { + ++m_refcount; + } + void DecRef() + { + ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero"); + if(--m_refcount == 0) + { + delete this; + } + } + + std::size_t refcount() + { + return m_refcount; + } + + // get/set the qtexture_t* Radiant uses to represent this shader object + qtexture_t* getTexture() const + { + return m_pTexture; + } + qtexture_t* getDiffuse() const + { + return m_pDiffuse; + } + qtexture_t* getBump() const + { + return m_pBump; + } + qtexture_t* getSpecular() const + { + return m_pSpecular; + } + // get shader name + const char* getName() const + { + return m_Name.c_str(); + } + bool IsInUse() const + { + return m_bInUse; + } + void SetInUse(bool bInUse) + { + m_bInUse = bInUse; + g_ActiveShadersChangedNotify(); + } + // get the shader flags + int getFlags() const + { + return m_template.m_nFlags; + } + // get the transparency value + float getTrans() const + { + return m_template.m_fTrans; + } + // test if it's a true shader, or a default shader created to wrap around a texture + bool IsDefault() const + { + return string_empty(m_filename); + } + // get the alphaFunc + void getAlphaFunc(EAlphaFunc *func, float *ref) { *func = m_template.m_AlphaFunc; *ref = m_template.m_AlphaRef; }; + BlendFunc getBlendFunc() const + { + return m_blendFunc; + } + // get the cull type + ECull getCull() + { + return m_template.m_Cull; + }; + // get shader file name (ie the file where this one is defined) + const char* getShaderFileName() const + { + return m_filename; + } + // ----------------------------------------- + + void realise() + { + m_pTexture = evaluateTexture(m_template.m_textureName, m_template.m_params, m_args); + + if(m_pTexture->texture_number == 0) + { + m_notfound = m_pTexture; + + { + StringOutputStream name(256); + name << GlobalRadiant().getAppPath() << "bitmaps/" << (IsDefault() ? "notex.bmp" : "shadernotex.bmp"); + m_pTexture = GlobalTexturesCache().capture(LoadImageCallback(0, loadBitmap), name.c_str()); + } + } + + realiseLighting(); + } + + void unrealise() + { + GlobalTexturesCache().release(m_pTexture); + + if(m_notfound != 0) + { + GlobalTexturesCache().release(m_notfound); + } + + unrealiseLighting(); + } + + void realiseLighting() + { + if(m_lightingEnabled) + { + LoadImageCallback loader = GlobalTexturesCache().defaultLoader(); + if(!string_empty(m_template.m_heightmapScale.c_str())) + { + m_heightmapScale = evaluateFloat(m_template.m_heightmapScale, m_template.m_params, m_args); + loader = LoadImageCallback(&m_heightmapScale, loadHeightmap); + } + m_pDiffuse = evaluateTexture(m_template.m_diffuse, m_template.m_params, m_args); + m_pBump = evaluateTexture(m_template.m_bump, m_template.m_params, m_args, loader); + m_pSpecular = evaluateTexture(m_template.m_specular, m_template.m_params, m_args); + m_pLightFalloffImage = evaluateTexture(m_template.m_lightFalloffImage, m_template.m_params, m_args); + + for(ShaderTemplate::MapLayers::const_iterator i = m_template.m_layers.begin(); i != m_template.m_layers.end(); ++i) + { + m_layers.push_back(evaluateLayer(*i, m_template.m_params, m_args)); + } + + if(m_layers.size() == 1) + { + const BlendFuncExpression& blendFunc = m_template.m_layers.front().blendFunc(); + if(!string_empty(blendFunc.second.c_str())) + { + m_blendFunc = BlendFunc( + evaluateBlendFactor(blendFunc.first.c_str(), m_template.m_params, m_args), + evaluateBlendFactor(blendFunc.second.c_str(), m_template.m_params, m_args) + ); + } + else + { + const char* blend = evaluateShaderValue(blendFunc.first.c_str(), m_template.m_params, m_args); + + if(string_equal_nocase(blend, "add")) + { + m_blendFunc = BlendFunc(BLEND_ONE, BLEND_ONE); + } + else if(string_equal_nocase(blend, "filter")) + { + m_blendFunc = BlendFunc(BLEND_DST_COLOUR, BLEND_ZERO); + } + else if(string_equal_nocase(blend, "blend")) + { + m_blendFunc = BlendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA); + } + else + { + globalErrorStream() << "parsing blend value failed: " << makeQuoted(blend) << "\n"; + } + } + } + } + } + + void unrealiseLighting() + { + if(m_lightingEnabled) + { + GlobalTexturesCache().release(m_pDiffuse); + GlobalTexturesCache().release(m_pBump); + GlobalTexturesCache().release(m_pSpecular); + + GlobalTexturesCache().release(m_pLightFalloffImage); + + for(MapLayers::iterator i = m_layers.begin(); i != m_layers.end(); ++i) + { + GlobalTexturesCache().release((*i).texture()); + } + m_layers.clear(); + + m_blendFunc = BlendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA); + } + } + + // set shader name + void setName(const char* name) + { + m_Name = name; + } + + class MapLayer : public ShaderLayer + { + qtexture_t* m_texture; + BlendFunc m_blendFunc; + bool m_clampToBorder; + float m_alphaTest; + public: + MapLayer(qtexture_t* texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest) : + m_texture(texture), + m_blendFunc(blendFunc), + m_clampToBorder(false), + m_alphaTest(alphaTest) + { + } + qtexture_t* texture() const + { + return m_texture; + } + BlendFunc blendFunc() const + { + return m_blendFunc; + } + bool clampToBorder() const + { + return m_clampToBorder; + } + float alphaTest() const + { + return m_alphaTest; + } + }; + + static MapLayer evaluateLayer(const ShaderTemplate::MapLayerTemplate& layerTemplate, const ShaderParameters& params, const ShaderArguments& args) + { + return MapLayer( + evaluateTexture(layerTemplate.texture(), params, args), + evaluateBlendFunc(layerTemplate.blendFunc(), params, args), + layerTemplate.clampToBorder(), + evaluateFloat(layerTemplate.alphaTest(), params, args) + ); + } + + typedef std::vector MapLayers; + MapLayers m_layers; + + const ShaderLayer* firstLayer() const + { + if(m_layers.empty()) + { + return 0; + } + return &m_layers.front(); + } + void forEachLayer(const ShaderLayerCallback& callback) const + { + for(MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) + { + callback(*i); + } + } + + qtexture_t* lightFalloffImage() const + { + if(!string_empty(m_template.m_lightFalloffImage.c_str())) + { + return m_pLightFalloffImage; + } + return 0; + } +}; + +bool CShader::m_lightingEnabled = false; + +typedef SmartPointer ShaderPointer; +typedef std::map shaders_t; + +shaders_t g_ActiveShaders; + +static shaders_t::iterator g_ActiveShadersIterator; + +void ActiveShaders_IteratorBegin() +{ + g_ActiveShadersIterator = g_ActiveShaders.begin(); +} + +bool ActiveShaders_IteratorAtEnd() +{ + return g_ActiveShadersIterator == g_ActiveShaders.end(); +} + +IShader *ActiveShaders_IteratorCurrent() +{ + return static_cast(g_ActiveShadersIterator->second); +} + +void ActiveShaders_IteratorIncrement() +{ + ++g_ActiveShadersIterator; +} + +void debug_check_shaders(shaders_t& shaders) +{ + for(shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i) + { + ASSERT_MESSAGE(i->second->refcount() == 1, "orphan shader still referenced"); + } +} + +// will free all GL binded qtextures and shaders +// NOTE: doesn't make much sense out of Radiant exit or called during a reload +void FreeShaders() +{ + // reload shaders + // empty the actives shaders list + debug_check_shaders(g_ActiveShaders); + g_ActiveShaders.clear(); + g_shaders.clear(); + g_shaderTemplates.clear(); + g_shaderDefinitions.clear(); + g_ActiveShadersChangedNotify(); +} + +bool ShaderTemplate::parseQuake3(Tokeniser& tokeniser) +{ + // name of the qtexture_t we'll use to represent this shader (this one has the "textures\" before) + m_textureName = m_Name.c_str(); + + tokeniser.nextLine(); + + // we need to read until we hit a balanced } + int depth = 0; + for(;;) + { + tokeniser.nextLine(); + const char* token = tokeniser.getToken(); + + if(token == 0) + return false; + + if(string_equal(token, "{")) + { + ++depth; + continue; + } + else if(string_equal(token, "}")) + { + --depth; + if(depth < 0) // underflow + { + return false; + } + if(depth == 0) // end of shader + { + break; + } + + continue; + } + + if(depth == 1) + { + if (string_equal_nocase(token, "qer_nocarve")) + { + m_nFlags |= QER_NOCARVE; + } + else if (string_equal_nocase(token, "qer_trans")) + { + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_fTrans)); + m_nFlags |= QER_TRANS; + } + else if (string_equal_nocase(token, "qer_editorimage")) + { + RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_textureName)); + } + else if (string_equal_nocase(token, "qer_alphafunc")) + { + const char* alphafunc = tokeniser.getToken(); + + if(alphafunc == 0) + { + Tokeniser_unexpectedError(tokeniser, alphafunc, "#alphafunc"); + return false; + } + + if(string_equal_nocase(alphafunc, "equal")) + { + m_AlphaFunc = IShader::eEqual; + } + else if(string_equal_nocase(alphafunc, "greater")) + { + m_AlphaFunc = IShader::eGreater; + } + else if(string_equal_nocase(alphafunc, "less")) + { + m_AlphaFunc = IShader::eLess; + } + else if(string_equal_nocase(alphafunc, "gequal")) + { + m_AlphaFunc = IShader::eGEqual; + } + else if(string_equal_nocase(alphafunc, "lequal")) + { + m_AlphaFunc = IShader::eLEqual; + } + else + { + m_AlphaFunc = IShader::eAlways; + } + + m_nFlags |= QER_ALPHATEST; + + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_AlphaRef)); + } + else if (string_equal_nocase(token, "cull")) + { + const char* cull = tokeniser.getToken(); + + if(cull == 0) + { + Tokeniser_unexpectedError(tokeniser, cull, "#cull"); + return false; + } + + if(string_equal_nocase(cull, "none") + || string_equal_nocase(cull, "twosided") + || string_equal_nocase(cull, "disable")) + { + m_Cull = IShader::eCullNone; + } + else if(string_equal_nocase(cull, "back") + || string_equal_nocase(cull, "backside") + || string_equal_nocase(cull, "backsided")) + { + m_Cull = IShader::eCullBack; + } + else + { + m_Cull = IShader::eCullBack; + } + + m_nFlags |= QER_CULL; + } + else if (string_equal_nocase(token, "surfaceparm")) + { + const char* surfaceparm = tokeniser.getToken(); + + if(surfaceparm == 0) + { + Tokeniser_unexpectedError(tokeniser, surfaceparm, "#surfaceparm"); + return false; + } + + if (string_equal_nocase(surfaceparm, "fog")) + { + m_nFlags |= QER_FOG; + if (m_fTrans == 1.0f) // has not been explicitly set by qer_trans + { + m_fTrans = 0.35f; + } + } + else if (string_equal_nocase(surfaceparm, "nodraw")) + { + m_nFlags |= QER_NODRAW; + } + else if (string_equal_nocase(surfaceparm, "nonsolid")) + { + m_nFlags |= QER_NONSOLID; + } + else if (string_equal_nocase(surfaceparm, "water")) + { + m_nFlags |= QER_WATER; + } + else if (string_equal_nocase(surfaceparm, "lava")) + { + m_nFlags |= QER_LAVA; + } + else if (string_equal_nocase(surfaceparm, "areaportal")) + { + m_nFlags |= QER_AREAPORTAL; + } + else if (string_equal_nocase(surfaceparm, "playerclip")) + { + m_nFlags |= QER_CLIP; + } + else if (string_equal_nocase(surfaceparm, "botclip")) + { + m_nFlags |= QER_BOTCLIP; + } + } + } + } + + return true; +} + +class Layer +{ +public: + LayerTypeId m_type; + TextureExpression m_texture; + BlendFunc m_blendFunc; + bool m_clampToBorder; + float m_alphaTest; + float m_heightmapScale; + + Layer() : m_type(LAYER_NONE), m_blendFunc(BLEND_ONE, BLEND_ZERO), m_clampToBorder(false), m_alphaTest(-1), m_heightmapScale(0) + { + } +}; + +std::list g_shaderFilenames; + +void ParseShaderFile(Tokeniser& tokeniser, const char* filename) +{ + g_shaderFilenames.push_back(filename); + filename = g_shaderFilenames.back().c_str(); + tokeniser.nextLine(); + for(;;) + { + const char* token = tokeniser.getToken(); + + if(token == 0) + { + break; + } + + if(string_equal(token, "table")) + { + if(tokeniser.getToken() == 0) + { + Tokeniser_unexpectedError(tokeniser, 0, "#table-name"); + return; + } + if(!Tokeniser_parseToken(tokeniser, "{")) + { + return; + } + for(;;) + { + const char* option = tokeniser.getToken(); + if(string_equal(option, "{")) + { + for(;;) + { + const char* value = tokeniser.getToken(); + if(string_equal(value, "}")) + { + break; + } + } + + if(!Tokeniser_parseToken(tokeniser, "}")) + { + return; + } + break; + } + } + } + else + { + if(string_equal(token, "guide")) + { + parseTemplateInstance(tokeniser, filename); + } + else + { + if(!string_equal(token, "material") + && !string_equal(token, "particle") + && !string_equal(token, "skin")) + { + tokeniser.ungetToken(); + } + // first token should be the path + name.. (from base) + CopiedString name; + if(!Tokeniser_parseShaderName(tokeniser, name)) + { + } + ShaderTemplatePointer shaderTemplate(new ShaderTemplate()); + shaderTemplate->setName(name.c_str()); + + g_shaders.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)); + + bool result = (g_shaderLanguage == SHADERLANGUAGE_QUAKE3) + ? shaderTemplate->parseQuake3(tokeniser) + : shaderTemplate->parseDoom3(tokeniser); + if (result) + { + // do we already have this shader? + if(!g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(shaderTemplate->getName(), ShaderDefinition(shaderTemplate.get(), ShaderArguments(), filename))).second) + { + #ifdef _DEBUG + globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " is already in memory, definition in " << filename << " ignored.\n"; + #endif + } + } + else + { + globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n"; + return; + } + } + } + } +} + +void parseGuideFile(Tokeniser& tokeniser, const char* filename) +{ + tokeniser.nextLine(); + for(;;) + { + const char* token = tokeniser.getToken(); + + if(token == 0) + { + break; + } + + if(string_equal(token, "guide")) + { + // first token should be the path + name.. (from base) + ShaderTemplatePointer shaderTemplate(new ShaderTemplate); + shaderTemplate->parseTemplate(tokeniser); + if(!g_shaderTemplates.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)).second) + { + globalErrorStream() << "guide " << makeQuoted(shaderTemplate->getName()) << ": already defined, second definition ignored\n"; + } + } + else if(string_equal(token, "inlineGuide")) + { + // skip entire inlineGuide definition + std::size_t depth = 0; + for(;;) + { + tokeniser.nextLine(); + token = tokeniser.getToken(); + if(string_equal(token, "{")) + { + ++depth; + } + else if(string_equal(token, "}")) + { + if(--depth == 0) + { + break; + } + } + } + } + } +} + +void LoadShaderFile(const char* filename) +{ + ArchiveTextFile* file = GlobalFileSystem().openTextFile(filename); + + if(file != 0) + { + globalOutputStream() << "Parsing shaderfile " << filename << "\n"; + + Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream()); + + ParseShaderFile(tokeniser, filename); + + tokeniser.release(); + file->release(); + } + else + { + globalOutputStream() << "Unable to read shaderfile " << filename << "\n"; + } +} + +typedef FreeCaller1 LoadShaderFileCaller; + + +void loadGuideFile(const char* filename) +{ + StringOutputStream fullname(256); + fullname << "guides/" << filename; + ArchiveTextFile* file = GlobalFileSystem().openTextFile(fullname.c_str()); + + if(file != 0) + { + globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n"; + + Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream()); + + parseGuideFile(tokeniser, fullname.c_str()); + + tokeniser.release(); + file->release(); + } + else + { + globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n"; + } +} + +typedef FreeCaller1 LoadGuideFileCaller; + + +CShader* Try_Shader_ForName(const char* name) +{ + { + shaders_t::iterator i = g_ActiveShaders.find(name); + if(i != g_ActiveShaders.end()) + { + return (*i).second; + } + } + // active shader was not found + + // find matching shader definition + ShaderDefinitionMap::iterator i = g_shaderDefinitions.find(name); + if(i == g_shaderDefinitions.end()) + { + // shader definition was not found + + // create new shader definition from default shader template + ShaderTemplatePointer shaderTemplate(new ShaderTemplate()); + shaderTemplate->CreateDefault(name); + g_shaderTemplates.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)); + + i = g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate.get(), ShaderArguments(), ""))).first; + } + + // create shader from existing definition + ShaderPointer pShader(new CShader((*i).second)); + pShader->setName(name); + g_ActiveShaders.insert(shaders_t::value_type(name, pShader)); + g_ActiveShadersChangedNotify(); + return pShader; +} + +IShader *Shader_ForName(const char *name) +{ + ASSERT_NOTNULL(name); + + IShader *pShader = Try_Shader_ForName(name); + pShader->IncRef(); + return pShader; +} + + + + +// the list of scripts/*.shader files we need to work with +// those are listed in shaderlist file +GSList *l_shaderfiles = 0; + +GSList* Shaders_getShaderFileList() +{ + return l_shaderfiles; +} + +/* +================== +DumpUnreferencedShaders +usefull function: dumps the list of .shader files that are not referenced to the console +================== +*/ +void IfFound_dumpUnreferencedShader(bool& bFound, const char* filename) +{ + bool listed = false; + + for(GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next(sh)) + { + if(!strcmp((char*)sh->data, filename)) + { + listed = true; + break; + } + } + + if(!listed) + { + if(!bFound) + { + bFound = true; + globalOutputStream() << "Following shader files are not referenced in shaderlist.txt:\n"; + } + globalOutputStream() << filename << "\n"; + } +} +typedef ReferenceCaller1 IfFoundDumpUnreferencedShaderCaller; + +void DumpUnreferencedShaders() +{ + bool bFound = false; + GlobalFileSystem().forEachFile(g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller(bFound)); +} + +void ShaderList_addShaderFile(const char* dirstring) +{ + bool found = false; + + for(GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next) + { + if(string_equal_nocase(dirstring, (char*)tmp->data)) + { + found = true; + globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n"; + break; + } + } + + if(!found) + { + l_shaderfiles = g_slist_append(l_shaderfiles, strdup(dirstring)); + } +} + +typedef FreeCaller1 AddShaderFileCaller; + + +/* +================== +BuildShaderList +build a CStringList of shader names +================== +*/ +void BuildShaderList(TextInputStream& shaderlist) +{ + Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser(shaderlist); + tokeniser.nextLine(); + const char* token = tokeniser.getToken(); + StringOutputStream shaderFile(64); + while(token != 0) + { + // each token should be a shader filename + shaderFile << token << "." << g_shadersExtension; + + ShaderList_addShaderFile(shaderFile.c_str()); + + tokeniser.nextLine(); + token = tokeniser.getToken(); + + shaderFile.clear(); + } + tokeniser.release(); +} + +void FreeShaderList() +{ + while(l_shaderfiles != 0) + { + free(l_shaderfiles->data); + l_shaderfiles = g_slist_remove(l_shaderfiles, l_shaderfiles->data); + } +} + +#include "stream/filestream.h" + +bool shaderlist_findOrInstall(const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename) +{ + StringOutputStream absShaderList(256); + absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt"; + if(file_exists(absShaderList.c_str())) + { + return true; + } + { + StringOutputStream directory(256); + directory << enginePath << gamename << '/' << shaderPath; + if(!file_exists(directory.c_str()) && !Q_mkdir(directory.c_str())) + { + return false; + } + } + { + StringOutputStream defaultShaderList(256); + defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt"; + if(file_exists(defaultShaderList.c_str())) + { + return file_copy(defaultShaderList.c_str(), absShaderList.c_str()); + } + } + return false; +} + +void Shaders_Load() +{ + if(g_shaderLanguage == SHADERLANGUAGE_QUAKE4) + { + GlobalFileSystem().forEachFile("guides/", "guide", LoadGuideFileCaller(), 0); + } + + const char* shaderPath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath"); + if(!string_empty(shaderPath)) + { + StringOutputStream path(256); + path << DirectoryCleaned(shaderPath); + + if(g_useShaderList) + { + // preload shader files that have been listed in shaderlist.txt + const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue("basegame"); + const char* gamename = GlobalRadiant().getGameName(); + const char* enginePath = GlobalRadiant().getEnginePath(); + const char* toolsPath = GlobalRadiant().getGameToolsPath(); + + bool isMod = !string_equal(basegame, gamename); + + if(!isMod || !shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename)) + { + gamename = basegame; + shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename); + } + + StringOutputStream absShaderList(256); + absShaderList << enginePath << gamename << '/' << path.c_str() << "shaderlist.txt"; + + { + globalOutputStream() << "Parsing shader files from " << absShaderList.c_str() << "\n"; + TextFileInputStream shaderlistFile(absShaderList.c_str()); + if(shaderlistFile.failed()) + { + globalErrorStream() << "Couldn't find '" << absShaderList.c_str() << "'\n"; + } + else + { + BuildShaderList(shaderlistFile); + DumpUnreferencedShaders(); + } + } + } + else + { + GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, AddShaderFileCaller(), 0); + } + + GSList *lst = l_shaderfiles; + StringOutputStream shadername(256); + while(lst) + { + shadername << path.c_str() << reinterpret_cast(lst->data); + LoadShaderFile(shadername.c_str()); + shadername.clear(); + lst = lst->next; + } + } + + //StringPool_analyse(ShaderPool::instance()); +} + +void Shaders_Free() +{ + FreeShaders(); + FreeShaderList(); + g_shaderFilenames.clear(); +} + +ModuleObservers g_observers; + +std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything +bool Shaders_realised() +{ + return g_shaders_unrealised == 0; +} +void Shaders_Realise() +{ + if(--g_shaders_unrealised == 0) + { + Shaders_Load(); + g_observers.realise(); + } +} +void Shaders_Unrealise() +{ + if(++g_shaders_unrealised == 1) + { + g_observers.unrealise(); + Shaders_Free(); + } +} + +void Shaders_Refresh() +{ + Shaders_Unrealise(); + Shaders_Realise(); +} + +class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver +{ +public: + void realise() + { + Shaders_Realise(); + } + void unrealise() + { + Shaders_Unrealise(); + } + void refresh() + { + Shaders_Refresh(); + } + + IShader* getShaderForName(const char* name) + { + return Shader_ForName(name); + } + + void foreachShaderName(const ShaderNameCallback& callback) + { + for(ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i) + { + callback((*i).first.c_str()); + } + } + + void beginActiveShadersIterator() + { + ActiveShaders_IteratorBegin(); + } + bool endActiveShadersIterator() + { + return ActiveShaders_IteratorAtEnd(); + } + IShader* dereferenceActiveShadersIterator() + { + return ActiveShaders_IteratorCurrent(); + } + void incrementActiveShadersIterator() + { + ActiveShaders_IteratorIncrement(); + } + void setActiveShadersChangedNotify(const Callback& notify) + { + g_ActiveShadersChangedNotify = notify; + } + + void attach(ModuleObserver& observer) + { + g_observers.attach(observer); + } + void detach(ModuleObserver& observer) + { + g_observers.detach(observer); + } + + void setLightingEnabled(bool enabled) + { + if(CShader::m_lightingEnabled != enabled) + { + for(shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i) + { + (*i).second->unrealiseLighting(); + } + CShader::m_lightingEnabled = enabled; + for(shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i) + { + (*i).second->realiseLighting(); + } + } + } + + const char* getTexturePrefix() const + { + return g_texturePrefix; + } +}; + +Quake3ShaderSystem g_Quake3ShaderSystem; + +ShaderSystem& GetShaderSystem() +{ + return g_Quake3ShaderSystem; +} + +void Shaders_Construct() +{ + GlobalFileSystem().attach(g_Quake3ShaderSystem); +} +void Shaders_Destroy() +{ + GlobalFileSystem().detach(g_Quake3ShaderSystem); + + if(Shaders_realised()) + { + Shaders_Free(); + } +} diff --git a/plugins/shaders/shaders.h b/plugins/shaders/shaders.h new file mode 100644 index 00000000..8296b791 --- /dev/null +++ b/plugins/shaders/shaders.h @@ -0,0 +1,55 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(INCLUDED_SHADERS_H) +#define INCLUDED_SHADERS_H + +void Shaders_Construct(); +void Shaders_Destroy(); +class ShaderSystem; +ShaderSystem& GetShaderSystem(); + +enum ShaderLanguage +{ + SHADERLANGUAGE_QUAKE3, + SHADERLANGUAGE_DOOM3, + SHADERLANGUAGE_QUAKE4 +}; + +extern const char* g_shadersExtension; +extern const char* g_shadersDirectory; +extern ShaderLanguage g_shaderLanguage; +extern bool g_enableDefaultShaders; +extern bool g_useShaderList; +struct _QERPlugImageTable; +extern _QERPlugImageTable* g_bitmapModule; + + +#endif diff --git a/plugins/shaders/shaders.proj b/plugins/shaders/shaders.proj new file mode 100644 index 0000000000000000000000000000000000000000..a3056b57e0b3f4151946d67ec3786d57112a5021 GIT binary patch literal 73728 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 D`>M#J literal 0 HcmV?d00001 diff --git a/plugins/shaders/shadershl.def b/plugins/shaders/shadershl.def new file mode 100644 index 00000000..8c046f04 --- /dev/null +++ b/plugins/shaders/shadershl.def @@ -0,0 +1,8 @@ +; shaders.def : Declares the module parameters for the DLL. + +LIBRARY "ShadersHL" +DESCRIPTION 'ShadersHL Windows Dynamic Link Library' + +EXPORTS + ; Explicit exports can go here + Synapse_EnumerateInterfaces @1 diff --git a/plugins/shaders/shadersq3.def b/plugins/shaders/shadersq3.def new file mode 100644 index 00000000..ab664138 --- /dev/null +++ b/plugins/shaders/shadersq3.def @@ -0,0 +1,7 @@ +; shadersq3.def : Declares the module parameters for the DLL. + +LIBRARY "ShadersQ3" + +EXPORTS + ; Explicit exports can go here + Radiant_RegisterModules @1 diff --git a/plugins/shaders/shadersq3.vcproj b/plugins/shaders/shadersq3.vcproj new file mode 100644 index 00000000..21223707 --- /dev/null +++ b/plugins/shaders/shadersq3.vcproj @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/spritemodel/plugin.cpp b/plugins/spritemodel/plugin.cpp new file mode 100644 index 00000000..5b74b87b --- /dev/null +++ b/plugins/spritemodel/plugin.cpp @@ -0,0 +1,221 @@ +/* +Copyright (C) 2002 Dominic Clifton. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// Sprite Model Plugin +// +// Code by Hydra aka Dominic Clifton +// +// Based on MD3Model source code by SPoG +// + +/* + Overview + ======== + + + Why ? + ----- + + It allows the user to see a graphical representation of the entity in the 3D view (maybe 2D views later) where the entity would just otherwise be a non-descriptive coloured box. + + It is designed to be used with the entity view set to WireFrame (as the sprite images are rendered in the middle of the entity's bbox. + + How ? + ----- + + Implemented as a model module, without any ISelect stuff. + + For an entity to use an image (instead of a model) you just update the entity defintion file so that the eclass_t's modelpath is filled in with a relative path and filename of an image file. + + e.g: + + baseq3/scripts/entities.def + =========================== + + \/\*QUAKED ammo_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) SUSPENDED + ... + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="sprites/powerups/ammo/bfgam.bmp"\*\/ + + + valve/scripts/halflife.fgd + ========================== + + @PointClass iconsprite("sprites/lightbulb.spr") base(Target, Targetname, Light) = light : "Invisible lightsource" + [ + ... + ] + + What image formats are supported ? + ---------------------------------- + + This module can load any image format that there is an active image module for. For q3 this would be bmp, tga and jpg. For Half-Life this would be hlw and spr. + + Version History + =============== + + v0.1 - 27/May/2002 + - Created an inital implementation of a sprite model plugin. + According to the powers that be, it seems creating a model + plugin is hackish. + It works ok, but there is no way to attach models (sprites if you will) + to non-fixedsize entities (like func_bombtarget) + Also, I can't get the alpha map stuff right so I had to invert the alpha + mask in the spr loader so that 0xff = not drawn pixel. + + ToDo + ==== + + * make sprites always face the camera (is this done in camwindow.cpp ?) + + * maybe add an option to scale the sprites in the prefs ? + + * un-hack the default fall-though to "sprite" model version (see m_version) + + * maybe convert to a new kind of class not based on model. + + * allow sprites on non-fixedsize ents + + * fix reversed alpha map in spr loader + + * allow an entity to have both an .md? and a sprite model. +*/ + +#include "plugin.h" +#include "spritemodel.h" + +// ============================================================================= +// Globals + +// function tables +_QERFuncTable_1 __QERTABLENAME; +OpenGLBinding g_QglTable; +_QERShadersTable g_ShadersTable; + +// ============================================================================= +// SYNAPSE + +#include "synapse.h" + + +char *supportedmodelformats[] = {"spr","bmp","tga","jpg","hlw",NULL}; // NULL is list delimiter + +static void add_model_apis(CSynapseClient& client) +{ + char **ext; + for (ext = supportedmodelformats; *ext != NULL; ext++) + { + client.AddAPI(MODEL_MAJOR, *ext, sizeof(_QERPlugModelTable)); + } +} + +static bool model_is_supported(const char* extension) +{ + char **ext; + for (ext = supportedmodelformats; *ext != NULL; ext++) + { + if (stricmp(extension,*ext)==0) + return true; + } + return false; +} + +void init_filetypes() +{ + char **ext; + for (ext = supportedmodelformats; *ext != NULL; ext++) + { + GetFileTypeRegistry()->addType(MODEL_MAJOR, filetype_t("sprite", *ext)); + } +} + +extern CSynapseServer* g_pSynapseServer; + +class CSynapseClientModel : public CSynapseClient +{ +public: + // CSynapseClient API + bool RequestAPI(APIDescriptor_t *pAPI); + const char* GetInfo(); + const char* GetName(); + + CSynapseClientModel() { } + virtual ~CSynapseClientModel() { } + + bool OnActivate() + { + init_filetypes(); // see todo list above. + return true; + } +}; + +CSynapseServer* g_pSynapseServer = NULL; +CSynapseClientModel g_SynapseClient; + +extern "C" CSynapseClient* SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces (const char *version, CSynapseServer *pServer) +{ + if (strcmp(version, SYNAPSE_VERSION)) + { + Syn_Printf("ERROR: synapse API version mismatch: should be '" SYNAPSE_VERSION "', got '%s'\n", version); + return NULL; + } + g_pSynapseServer = pServer; + g_pSynapseServer->IncRef(); + Set_Syn_Printf(g_pSynapseServer->Get_Syn_Printf()); + + add_model_apis(g_SynapseClient); // see todo list above. + + g_SynapseClient.AddAPI( PLUGIN_MAJOR, "sprite", sizeof( _QERPluginTable ) ); + g_SynapseClient.AddAPI( RADIANT_MAJOR, NULL, sizeof( g_FuncTable ), SYN_REQUIRE, &g_FuncTable ); + g_SynapseClient.AddAPI( QGL_MAJOR, NULL, sizeof( g_QglTable ), SYN_REQUIRE, &g_QglTable ); + g_SynapseClient.AddAPI( SHADERS_MAJOR, "*", sizeof( g_ShadersTable ), SYN_REQUIRE, &g_ShadersTable ); + + return &g_SynapseClient; +} + +bool CSynapseClientModel::RequestAPI(APIDescriptor_t *pAPI) +{ + if (!strcmp(pAPI->major_name, MODEL_MAJOR)) + { + _QERPlugModelTable* pTable= static_cast<_QERPlugModelTable*>(pAPI->mpTable); + + if (!strcmp(pAPI->minor_name, "sprite")) + { + pTable->m_pfnLoadModel = &LoadSpriteModel; + return true; + } + } + + Syn_Printf("ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo()); + return false; +} + +#include "version.h" + +const char* CSynapseClientModel::GetInfo() +{ + return "Sprite Model module built " __DATE__ " " RADIANT_VERSION; +} + +const char* CSynapseClientModel::GetName() +{ + return "sprite"; +} diff --git a/plugins/spritemodel/plugin.h b/plugins/spritemodel/plugin.h new file mode 100644 index 00000000..ec2b3995 --- /dev/null +++ b/plugins/spritemodel/plugin.h @@ -0,0 +1,47 @@ +/* +Copyright (C) 2002 Dominic Clifton. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// Sprite Model Plugin +// +// Code by Hydra aka Dominic Clifton +// +// Based on MD3Model source code by SPoG +// + +#ifndef _PLUGIN_H_ +#define _PLUGIN_H_ + +#include "mathlib.h" +#include +#include "qertypes.h" +#include + +#define USE_QERTABLE_DEFINE +#include "qerplugin.h" +extern _QERFuncTable_1 __QERTABLENAME; + +#define USE_QGLTABLE_DEFINE +#include "igl.h" +extern OpenGLBinding __QGLTABLENAME; + +#include "imodel.h" + +#endif // _PLUGIN_H_ diff --git a/plugins/spritemodel/spritemodel.cpp b/plugins/spritemodel/spritemodel.cpp new file mode 100644 index 00000000..a936cd3b --- /dev/null +++ b/plugins/spritemodel/spritemodel.cpp @@ -0,0 +1,177 @@ +/* +Copyright (C) 2002 Dominic Clifton. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// Sprite Model Plugin +// +// Code by Hydra aka Dominic Clifton +// +// Based on MD3Model source code by SPoG +// + +#include "spritemodel.h" + +void LoadSpriteModel(entity_interfaces_t *interfaces, const char *name) +{ + IShader *pShader; + + pShader = QERApp_Shader_ForName(name); + + if (!pShader) + { + Sys_Printf("ERROR: can't find shader (or image) for: %s\n", name ); + return;// NULL; + } + + CSpriteModel *model = new CSpriteModel(); + model->Construct(pShader); + interfaces->pRender = (IRender*)model; + interfaces->pRender->IncRef(); + //interfaces->pSelect = (ISelect*)model; + //interfaces->pSelect->IncRef(); + interfaces->pSelect = NULL; + interfaces->pEdit = NULL; + model->DecRef(); + +} + +void CSpriteModel::Construct(IShader *pShader) +{ + m_pShader = pShader; + aabb_clear(&m_BBox); + /* + md3Surface_t *pSurface = (md3Surface_t *)(((unsigned char *)pHeader) + pHeader->ofsSurfaces); + m_nSurfaces = pHeader->numSurfaces; + CMD3Surface* surfaces = new CMD3Surface[m_nSurfaces]; + for (int i = 0; i < m_nSurfaces; i++ ) + { + surfaces[i].Construct(pSurface); + pSurface = (md3Surface_t *) ((( char * ) pSurface) + pSurface->ofsEnd); + } + m_children = surfaces; + AccumulateBBox(); + */ +} + +CSpriteModel::CSpriteModel() +{ + refCount = 1; + //m_nSurfaces = 0; + //m_children = NULL; + m_pShader = NULL; +} + +CSpriteModel::~CSpriteModel() +{ + // if(m_children) delete[] m_children; + if (m_pShader) + m_pShader->DecRef(); +} + +void CSpriteModel::Draw(int state, int rflags) const +{ + +/* + // Draw a point in the middle of the bbox + vec3_t middle = {0,0,0}; + g_QglTable.m_pfn_qglPointSize (4); + g_QglTable.m_pfn_qglColor3f (0,1,0); + g_QglTable.m_pfn_qglBegin (GL_POINTS); + g_QglTable.m_pfn_qglVertex3fv (middle); + g_QglTable.m_pfn_qglEnd (); +*/ + + qtexture_t *q = m_pShader->getTexture(); + + // convert pixels to units and divide in half again so we draw in the middle + // of the bbox. + int h = q->height / 8; + int w = q->width / 8; + + // setup opengl stuff + + g_QglTable.m_pfn_qglPushAttrib (GL_ALL_ATTRIB_BITS); // GL_ENABLE_BIT + //g_QglTable.m_pfn_qglColor3f (1,1,1); //testing + //g_QglTable.m_pfn_qglColor4f (1,1,1,1); //testing + g_QglTable.m_pfn_qglBindTexture (GL_TEXTURE_2D, q->texture_number); + + //g_QglTable.m_pfn_qglEnable (GL_TEXTURE_2D); // FIXME: ? this forces textures, even in wireframe mode, bad... ? + + g_QglTable.m_pfn_qglAlphaFunc (GL_LESS, 1); + g_QglTable.m_pfn_qglEnable (GL_ALPHA_TEST); + + // get rid of this when sprite always faces camera + g_QglTable.m_pfn_qglDisable(GL_CULL_FACE); + g_QglTable.m_pfn_qglPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + + // draw the sprite + +#if 0 + // using x/y axis, it appears FLAT without the proper transform and rotation. + + g_QglTable.m_pfn_qglBegin(GL_QUADS); + g_QglTable.m_pfn_qglTexCoord2f (0,0); + g_QglTable.m_pfn_qglVertex3f (0-w,0-h, 0); + g_QglTable.m_pfn_qglTexCoord2f (1,0); + g_QglTable.m_pfn_qglVertex3f ( w,0-h, 0); + g_QglTable.m_pfn_qglTexCoord2f (1,1); + g_QglTable.m_pfn_qglVertex3f ( w, h, 0); + g_QglTable.m_pfn_qglTexCoord2f (0,1); + g_QglTable.m_pfn_qglVertex3f (0-w, h, 0); + g_QglTable.m_pfn_qglEnd (); +#else + + // so draw it using y/z instead. + g_QglTable.m_pfn_qglBegin(GL_QUADS); + g_QglTable.m_pfn_qglTexCoord2f(0.0f, 0.0f); + g_QglTable.m_pfn_qglVertex3f(0.0f, static_cast(w), static_cast(h)); + g_QglTable.m_pfn_qglTexCoord2f(1.0f, 0.0f); + g_QglTable.m_pfn_qglVertex3f(0.0f, 0.0f - static_cast(w), static_cast(h)); + g_QglTable.m_pfn_qglTexCoord2f(1.0f, 1.0f); + g_QglTable.m_pfn_qglVertex3f(0.0f, 0.0f - static_cast(w), 0.0f - static_cast(h)); + g_QglTable.m_pfn_qglTexCoord2f(0.0f, 0.0f); + g_QglTable.m_pfn_qglVertex3f(0.0f, static_cast(w), 0.0f - static_cast(h)); + g_QglTable.m_pfn_qglEnd (); +#endif + + g_QglTable.m_pfn_qglBindTexture (GL_TEXTURE_2D, 0); + g_QglTable.m_pfn_qglPopAttrib(); +} + +/* +bool CSpriteModel::TestRay(const ray_t *ray, vec_t *dist) const +{ + vec_t depth_start = *dist; + vec_t depth_local = *dist; + + if (aabb_test_ray(&m_BBox, ray) == 0) + return false; + + for(int i=0; i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/textool/.cvsignore b/plugins/textool/.cvsignore new file mode 100644 index 00000000..5d257e72 --- /dev/null +++ b/plugins/textool/.cvsignore @@ -0,0 +1,13 @@ +*.d +*.o +*.so +Debug +Release +TexTool___Win32_Q3Debug +TexTool___Win32_Q3Release +*.aps +*.plg +*.bak +*.BAK +*.opt +*.ncb \ No newline at end of file diff --git a/plugins/textool/.cvswrappers b/plugins/textool/.cvswrappers new file mode 100644 index 00000000..21e5b0a3 --- /dev/null +++ b/plugins/textool/.cvswrappers @@ -0,0 +1,2 @@ +*.dsp -m 'COPY' -k 'b' +*.rc -m 'COPY' -k 'b' diff --git a/plugins/textool/2DView.cpp b/plugins/textool/2DView.cpp new file mode 100644 index 00000000..fa902149 --- /dev/null +++ b/plugins/textool/2DView.cpp @@ -0,0 +1,202 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//----------------------------------------------------------------------------- +// +// DESCRIPTION: +// a class to provide basic services for 2D view of a world +// window <-> local 2D space transforms +// snap to grid +// TODO: this one can be placed under an interface, and provided to the editor as a service + +#include "StdAfx.h" + +static void view_ZoomIn (GtkWidget* widget, gpointer data) +{ + ((C2DView*)data)->ZoomIn (); +} + +static void view_ZoomOut (GtkWidget* widget, gpointer data) +{ + ((C2DView*)data)->ZoomOut (); +} + +void C2DView::PreparePaint() +{ + g_QglTable.m_pfn_qglClearColor( 0, 0, 0, 0 ); + g_QglTable.m_pfn_qglViewport( 0, 0, m_rect.right, m_rect.bottom ); + g_QglTable.m_pfn_qglMatrixMode( GL_PROJECTION ); + g_QglTable.m_pfn_qglLoadIdentity(); + g_QglTable.m_pfn_qglOrtho( m_Mins[0], m_Maxs[0], m_Maxs[1], m_Mins[1], -1, 1 ); +} + +void C2DView::SpaceForWindow( float c[2], int x, int y) +{ + c[0] = ((float)(x))/((float)(m_rect.right-m_rect.left))*(m_Maxs[0]-m_Mins[0])+m_Mins[0]; + c[1] = ((float)(y))/((float)(m_rect.bottom-m_rect.top))*(m_Maxs[1]-m_Mins[1])+m_Mins[1]; +} + +void C2DView::GridForWindow( float c[2], int x, int y) +{ + SpaceForWindow( c, x, y ); + if ( !m_bDoGrid ) + return; + c[0] /= m_GridStep[0]; + c[1] /= m_GridStep[1]; + c[0] = (float)floor( c[0] + 0.5f ); + c[1] = (float)floor( c[1] + 0.5f ); + c[0] *= m_GridStep[0]; + c[1] *= m_GridStep[1]; +} + +void C2DView::WindowForSpace( int &x, int &y, const float c[2] ) +{ + x = m_rect.left + (int)( ((float)(m_rect.right-m_rect.left))*(c[0]-m_Mins[0])/(m_Maxs[0]-m_Mins[0]) ); + y = m_rect.top + (int)( ((float)(m_rect.bottom-m_rect.top))*(c[1]-m_Mins[1])/(m_Maxs[1]-m_Mins[1]) ); +} + +qboolean C2DView::DoesSelect( int x, int y, float c[2] ) +{ + int xc,yc; + WindowForSpace( xc, yc, c ); + if ( abs(xc-x)<=3 && abs(yc-y)<=3 ) + return true; + return false; +} + +void C2DView::ZoomIn() +{ + m_Mins[0] = 0.5f * ( m_Mins[0] - m_Center[0] ) + m_Center[0]; + m_Mins[1] = 0.5f * ( m_Mins[1] - m_Center[1] ) + m_Center[1]; + m_Maxs[0] = 0.5f * ( m_Maxs[0] - m_Center[0] ) + m_Center[0]; + m_Maxs[1] = 0.5f * ( m_Maxs[1] - m_Center[1] ) + m_Center[1]; + g_pToolWnd->Redraw (); +} + +void C2DView::ZoomOut() +{ + m_Mins[0] = 2.0f * ( m_Mins[0] - m_Center[0] ) + m_Center[0]; + m_Mins[1] = 2.0f * ( m_Mins[1] - m_Center[1] ) + m_Center[1]; + m_Maxs[0] = 2.0f * ( m_Maxs[0] - m_Center[0] ) + m_Center[0]; + m_Maxs[1] = 2.0f * ( m_Maxs[1] - m_Center[1] ) + m_Center[1]; + g_pToolWnd->Redraw (); +} + +bool C2DView::OnRButtonDown (int x, int y) +{ + if (ViewState == View_Idle) + { + m_xPosMove = x; // horizontal position of cursor + m_yPosMove = y; // vertical position of cursor + // store + m_MinsMove[0] = m_Mins[0]; m_MinsMove[1] = m_Mins[1]; + m_MaxsMove[0] = m_Maxs[0]; m_MaxsMove[1] = m_Maxs[1]; + ViewState = View_Move; + // set popup to true + m_bPopup = true; + return true; + } + return false; +} + +bool C2DView::OnRButtonUp (int x, int y) +{ + if (ViewState == View_Move) + { + // maybe it's time for popup menu + if (m_bPopup) + { + GtkWidget *menu, *item; + + menu = gtk_menu_new (); + + item = gtk_menu_item_new_with_label ("Validate (RETURN)"); + gtk_signal_connect (GTK_OBJECT (item), "activate", GTK_SIGNAL_FUNC (Textool_Validate), NULL); + gtk_widget_show (item); + gtk_menu_append (GTK_MENU (menu), item); + + item = gtk_menu_item_new_with_label ("Zoom in (INSERT)"); + gtk_signal_connect (GTK_OBJECT (item), "activate", GTK_SIGNAL_FUNC (view_ZoomIn), this); + gtk_widget_show (item); + gtk_menu_append (GTK_MENU (menu), item); + + item = gtk_menu_item_new_with_label ("Zoom out (DELETE)"); + gtk_signal_connect (GTK_OBJECT (item), "activate", GTK_SIGNAL_FUNC (view_ZoomOut), this); + gtk_widget_show (item); + gtk_menu_append (GTK_MENU (menu), item); + + item = gtk_menu_item_new_with_label ("Cancel (ESC)"); + gtk_signal_connect (GTK_OBJECT (item), "activate", GTK_SIGNAL_FUNC (Textool_Cancel), NULL); + gtk_widget_show (item); + gtk_menu_append (GTK_MENU (menu), item); + + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 1, GDK_CURRENT_TIME); + } + + // back to Idle mode + ViewState = View_Idle; + return true; + } + return false; +} + +bool C2DView::OnMouseMove (int xPos, int yPos) +{ + if (ViewState == View_Move) + { + float V[2]; + // V is the offset + V[0] = ((float)( xPos - m_xPosMove )) * ( m_MaxsMove[0] - m_MinsMove[0] ) / ((float)( m_rect.left - m_rect.right )); + V[1] = ((float)( yPos - m_yPosMove )) * ( m_MaxsMove[1] - m_MinsMove[1] ) / ((float)( m_rect.top - m_rect.bottom )); + // update m_Mins m_Maxs and m_Center + m_Mins[0] = m_MinsMove[0] + V[0]; + m_Mins[1] = m_MinsMove[1] + V[1]; + m_Maxs[0] = m_MaxsMove[0] + V[0]; + m_Maxs[1] = m_MaxsMove[1] + V[1]; + m_Center[0] = 0.5f * ( m_Mins[0] + m_Maxs[0] ); + m_Center[1] = 0.5f * ( m_Mins[1] + m_Maxs[1] ); + // no popup menu if we moved + m_bPopup = false; + // send a repaint message + g_pToolWnd->Redraw (); + return true; + } + return false; +} + +bool C2DView::OnKeyDown (char *s) +{ + if (ViewState == View_Idle) + { + if (!strcmp(s,"Insert")) + { + ZoomOut(); + return true; + } + if (!strcmp(s,"Delete")) + { + ZoomIn(); + return true; + } + } + return false; +} + diff --git a/plugins/textool/2DView.h b/plugins/textool/2DView.h new file mode 100644 index 00000000..65c95ca9 --- /dev/null +++ b/plugins/textool/2DView.h @@ -0,0 +1,70 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//----------------------------------------------------------------------------- +// +// DESCRIPTION: +// a class to provide basic services for 2D view of a world +// window <-> local 2D space transforms +// snap to grid +// TODO: this one could be placed under an interface, and provided to the editor as a service + +#ifndef _2DVIEW_H_ +#define _2DVIEW_H_ + +class C2DView +{ + enum E2DViewState { View_Idle, View_Move } ViewState; + int m_xPosMove, m_yPosMove; + float m_MinsMove[2], m_MaxsMove[2]; + qboolean m_bDoGrid; + float m_GridStep[2]; + qboolean m_bPopup; +public: + RECT m_rect; + float m_Mins[2],m_Maxs[2],m_Center[2]; + C2DView() + { + ViewState = View_Idle; + m_bDoGrid = false; + m_bPopup = false; + } + ~C2DView() { } + void SetGrid( float xGridStep, float yGridStep ) + { m_bDoGrid = true; m_GridStep[0] = xGridStep; m_GridStep[1] = yGridStep; } + + // get window coordinates for space coordinates + void WindowForSpace( int &x, int &y, const float c[2]); + void SpaceForWindow( float c[2], int x, int y); + void GridForWindow( float c[2], int x, int y); + qboolean DoesSelect( int x, int y, float c[2] ); + void PreparePaint(); + + bool OnRButtonDown (int x, int y); + bool OnMouseMove (int x, int y); + bool OnRButtonUp (int x, int y); + bool OnKeyDown (char *s); + + void ZoomIn(); + void ZoomOut(); +}; + +#endif diff --git a/plugins/textool/ControlPointsManager.cpp b/plugins/textool/ControlPointsManager.cpp new file mode 100644 index 00000000..4af38038 --- /dev/null +++ b/plugins/textool/ControlPointsManager.cpp @@ -0,0 +1,332 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//----------------------------------------------------------------------------- +// +// DESCRIPTION: +// a class to handle control points in a 2D view +// TODO: this one can be placed under an interface, and provided to the editor as service +// + +#include "StdAfx.h" + +void CControlPointsManagerBFace::Init (int iPts, CtrlPts_t *Pts, C2DView *p2DView, int TexSize[2], + _QERFaceData* pFaceData, OpenGLBinding *pQglTable) +{ + ManagerState = Idle; + m_NumPoints = iPts; + m_pPts = Pts; + // store the initial config + memcpy( &m_RefPts, Pts, sizeof( CtrlPts_t ) ); + // init TM + memset( m_TM, 0, sizeof( float[2][3] ) ); + m_TM[0][0] = 1.0f; m_TM[1][1] = 1.0f; + m_bGotAnchor = false; + m_TransOffset[0] = 0.0f; m_TransOffset[1] = 0.0f; + m_TexSize[0] = TexSize[0]; + m_TexSize[1] = TexSize[1]; + m_pFaceData = pFaceData; + + CControlPointsManager::Init( p2DView, pQglTable ); +} + +bool CControlPointsManagerBFace::OnLButtonDown (int xPos, int yPos) +{ + if (ManagerState == Idle) + { + int i; + + // scan the point list to see if we selected something + for ( i=0; iDoesSelect( xPos, yPos, m_pPts->data[i] ) ) + { + m_iDragPoint = i; + ManagerState = Drag; + if (m_bGotAnchor && i == m_iAnchorPoint) + { + // this means we selected the Anchor, so we'll translate + m_bGotAnchor = false; + } + // perhaps we won't use translation, but we can compute it anyway + ComputeTransOffset(i); + if (m_bGotAnchor) + { + // we have an Anchor and selected another point + m_Anchor[0] = m_pPts->data[m_iAnchorPoint][0]; + m_Anchor[1] = m_pPts->data[m_iAnchorPoint][1]; + } + } + // send a repaint message + g_pToolWnd->Redraw (); + return true; + } + return false; +} + +bool CControlPointsManagerBFace::OnMouseMove (int xPos, int yPos) +{ + if (ManagerState == Drag) + { + if (m_bGotAnchor) + { + // there's an anchor, we are rotating the shape + // we need to work in XY space for orthonormality + float Pt[2]; + vec3_t V1,V2; + vec3_t cross; + float c,s; + // used in XY space + float XYTM[2][3]; + float XYRefAnchor[2]; + float XYAnchor[2]; + m_p2DView->GridForWindow( Pt, xPos, yPos ); + V2[0] = Pt[0] - m_Anchor[0]; + V2[1] = Pt[1] - m_Anchor[1]; + V2[2] = 0.0f; + V1[0] = m_RefPts.data[m_iDragPoint][0] - m_RefPts.data[m_iAnchorPoint][0]; + V1[1] = m_RefPts.data[m_iDragPoint][1] - m_RefPts.data[m_iAnchorPoint][1]; + V1[2] = 0.0f; + // compute transformation from V1 to V2 + // we need to work in XY orthonormal space + XYSpaceForSTSpace( V1, V1 ); + XYSpaceForSTSpace( V2, V2 ); + VectorNormalize( V2, V2 ); + VectorNormalize( V1, V1 ); + c = DotProduct( V1, V2 ); + CrossProduct( V1, V2, cross ); + s = VectorLength( cross ); + // we compute the transformation matrix in XY space + // reference position of the Anchor in XY space + XYSpaceForSTSpace( XYRefAnchor, m_RefPts.data[m_iAnchorPoint] ); + // current position of the Anchor in XY space + XYSpaceForSTSpace( XYAnchor, m_Anchor ); + // compute transformation matrix + XYTM[0][0] = c; XYTM[1][1] = c; + if (cross[2]>0) + s *= -1.0f; + XYTM[0][1] = s; XYTM[1][0] = -s; + XYTM[0][2] = -c*XYRefAnchor[0] - s*XYRefAnchor[1] + XYAnchor[0]; + XYTM[1][2] = s*XYRefAnchor[0] - c*XYRefAnchor[1] + XYAnchor[1]; + // express this transformation matrix in ST space + m_TM[0][0] = XYTM[0][0]; + m_TM[1][0] = XYTM[1][0] * (float)m_TexSize[0] / (float)m_TexSize[1]; + m_TM[0][1] = XYTM[0][1] * (float)m_TexSize[1] / (float)m_TexSize[0]; + m_TM[1][1] = XYTM[1][1]; + m_TM[0][2] = XYTM[0][2] / (float)m_TexSize[0]; + m_TM[1][2] = XYTM[1][2] / (float)m_TexSize[1]; + // update all points + UpdateCtrlPts(); + } + else + { + // no Anchor point is defined, we translate all points + m_p2DView->GridForWindow( m_pPts->data[m_iDragPoint], xPos, yPos ); + m_TM[0][2] = m_pPts->data[m_iDragPoint][0] + m_TransOffset[0]; + m_TM[1][2] = m_pPts->data[m_iDragPoint][1] + m_TransOffset[1]; + // update all points + UpdateCtrlPts(); + } + // send a repaint message + g_pToolWnd->Redraw (); + return true; + } + + return false; +} + +bool CControlPointsManagerBFace::OnLButtonUp (int x, int y) +{ + if (ManagerState == Drag) + { + // this button is gonna become our Anchor + m_bGotAnchor = true; + m_iAnchorPoint = m_iDragPoint; + // let's get out of Drag mode + ManagerState = Idle; + // send a repaint message + g_pToolWnd->Redraw (); + return true; + } + return false; +} + +void CControlPointsManagerBFace::render() +{ + int i; + + m_pQglTable->m_pfn_qglColor3f(0, 1, 0); + m_pQglTable->m_pfn_qglPointSize(6); + m_pQglTable->m_pfn_qglBegin( GL_POINTS ); + for ( i=0; im_pfn_qglColor3f(1, 0, 0); + else if ( m_bGotAnchor && i == m_iAnchorPoint ) + m_pQglTable->m_pfn_qglColor3f(0, 0, 1); + m_pQglTable->m_pfn_qglVertex2f( m_pPts->data[i][0], m_pPts->data[i][1] ); + m_pQglTable->m_pfn_qglColor3f(0, 1, 0); + } + m_pQglTable->m_pfn_qglEnd(); +} + +void CControlPointsManagerBFace::UpdateCtrlPts() +{ + int i; + + // update all points + for ( i=0; idata[i][0] = m_RefPts.data[i][0]*m_TM[0][0]+m_RefPts.data[i][1]*m_TM[0][1]+m_TM[0][2]; + m_pPts->data[i][1] = m_RefPts.data[i][0]*m_TM[1][0]+m_RefPts.data[i][1]*m_TM[1][1]+m_TM[1][2]; + } + + if (g_bPrefsUpdateCameraView) + { + Commit(); + // tell Radiant to update + // NOTE: little speed optimisation, disable window updates, and only update camera view + g_FuncTable.m_pfnSetScreenUpdate( false ); + g_SelectedFaceTable.m_pfnSetFaceInfo( 0, m_pFaceData ); + g_FuncTable.m_pfnSetScreenUpdate( true ); + g_FuncTable.m_pfnSysUpdateWindows( W_CAMERA ); + } +} + +//++timo FIXME: we are using a global for the reference data, use a m_pCancelFaceData instead +void CControlPointsManagerBFace::Commit( ) +{ + brushprimit_texdef_t aux; + aux.coords[0][0] = m_TM[0][0]*g_CancelFaceData.brushprimit_texdef.coords[0][0] + m_TM[0][1]*g_CancelFaceData.brushprimit_texdef.coords[1][0]; + aux.coords[0][1] = m_TM[0][0]*g_CancelFaceData.brushprimit_texdef.coords[0][1] + m_TM[0][1]*g_CancelFaceData.brushprimit_texdef.coords[1][1]; + aux.coords[0][2] = m_TM[0][0]*g_CancelFaceData.brushprimit_texdef.coords[0][2] + m_TM[0][1]*g_CancelFaceData.brushprimit_texdef.coords[1][2] + m_TM[0][2]; + aux.coords[1][0] = m_TM[1][0]*g_CancelFaceData.brushprimit_texdef.coords[0][0] + m_TM[1][1]*g_CancelFaceData.brushprimit_texdef.coords[1][0]; + aux.coords[1][1] = m_TM[1][0]*g_CancelFaceData.brushprimit_texdef.coords[0][1] + m_TM[1][1]*g_CancelFaceData.brushprimit_texdef.coords[1][1]; + aux.coords[1][2] = m_TM[1][0]*g_CancelFaceData.brushprimit_texdef.coords[0][2] + m_TM[1][1]*g_CancelFaceData.brushprimit_texdef.coords[1][2] + m_TM[1][2]; + memcpy( &m_pFaceData->brushprimit_texdef, &aux, sizeof(brushprimit_texdef_t) ); +} + +void CControlPointsManagerBFace::ComputeTransOffset(int i) +{ + // compute the translation offset used to counteract rotation + m_TransOffset[0] = -m_TM[0][0]*m_RefPts.data[i][0] - m_TM[0][1]*m_RefPts.data[i][1]; + m_TransOffset[1] = -m_TM[1][0]*m_RefPts.data[i][0] - m_TM[1][1]*m_RefPts.data[i][1]; +} + +void CControlPointsManagerBFace::XYSpaceForSTSpace( float xy[2], const float st[2] ) +{ + xy[0] = st[0] * (float)m_TexSize[0]; + xy[1] = st[1] * (float)m_TexSize[1]; +} + +/* +====================================================================== +patch manager +====================================================================== +*/ + +void CControlPointsManagerPatch::Init( patchMesh_t* pWorkPatch, C2DView *p2DView, OpenGLBinding *pQglTable, patchMesh_t* pPatch ) +{ + CControlPointsManager::Init( p2DView, pQglTable ); + m_pPatch = pPatch; + m_pWorkPatch = pWorkPatch; +} + +bool CControlPointsManagerPatch::OnLButtonDown (int xPos, int yPos) +{ + if (ManagerState == Idle) + { + int i,j; + + // scan the point list to see if we selected something + for ( i=0; iwidth; i++ ) + for ( j=0; jheight; j++ ) + if ( m_p2DView->DoesSelect( xPos, yPos, m_pWorkPatch->ctrl[i][j].st ) ) + { + m_iDragPoint[0] = i; + m_iDragPoint[1] = j; + ManagerState = Drag; + } + // send a repaint message + g_pToolWnd->Redraw (); + return true; + } + return false; +} + +bool CControlPointsManagerPatch::OnMouseMove (int xPos, int yPos) +{ + if (ManagerState == Drag) + { + m_p2DView->GridForWindow( m_pWorkPatch->ctrl[ m_iDragPoint[0] ][ m_iDragPoint[1] ].st, xPos, yPos ); + if (g_bPrefsUpdateCameraView) + { + Commit(); + // ask to rebuild the patch display data + m_pPatch->bDirty = true; + // send a repaint to the camera window as well + g_FuncTable.m_pfnSysUpdateWindows( W_CAMERA ); + } + // send a repaint message + g_pToolWnd->Redraw (); + return true; + } + return false; +} + +bool CControlPointsManagerPatch::OnLButtonUp (int x, int y) +{ + if (ManagerState == Drag) + { + ManagerState = Idle; + // send a repaint message + g_pToolWnd->Redraw (); + } + return false; +} + +void CControlPointsManagerPatch::render() +{ + int i,j; + + m_pQglTable->m_pfn_qglColor3f(0, 1, 0); + m_pQglTable->m_pfn_qglPointSize(6); + m_pQglTable->m_pfn_qglBegin( GL_POINTS ); + for ( i=0; iwidth; i++ ) + for ( j=0; jheight; j++ ) + { + if ( ManagerState == Drag && i == m_iDragPoint[0] && j == m_iDragPoint[1] ) + m_pQglTable->m_pfn_qglColor3f(1, 0, 0); + m_pQglTable->m_pfn_qglVertex2f( m_pWorkPatch->ctrl[i][j].st[0], m_pWorkPatch->ctrl[i][j].st[1] ); + m_pQglTable->m_pfn_qglColor3f(0, 1, 0); + } + m_pQglTable->m_pfn_qglEnd(); +} + +void CControlPointsManagerPatch::Commit() +{ + int i,j; + for ( i=0; iwidth; i++ ) + for ( j=0; jheight; j++ ) + { + m_pPatch->ctrl[i][j].st[0] = m_pWorkPatch->ctrl[i][j].st[0]; + m_pPatch->ctrl[i][j].st[1] = m_pWorkPatch->ctrl[i][j].st[1]; + } +} diff --git a/plugins/textool/ControlPointsManager.h b/plugins/textool/ControlPointsManager.h new file mode 100644 index 00000000..4305098c --- /dev/null +++ b/plugins/textool/ControlPointsManager.h @@ -0,0 +1,133 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//----------------------------------------------------------------------------- +// +// DESCRIPTION: +// a class to handle control points in a 2D view +// TODO: this one can be placed under an interface, and provided to the editor as service +// +// NOTE: the C2DView *m_p2DView is the orthogonal mapping between window and ST space +// in Drag mode (for rotation) we need an orthonormal XY space +// we do ST <-> XY transformations using the texture size +// ( for translation-only moves, orthogonal is enough ) +// FIXME: is there a better way to deal between Window space <-> ST space <-> XY space ? +// +// NOTE: ControlPointsManagers are a bit different between brush faces and patches +// so there's a base virtual class, and we have two versions + +#ifndef _CONTROLPOINTSMANAGER_H_ +#define _CONTROLPOINTSMANAGER_H_ + +class CControlPointsManager +{ +protected: + // used by Render + OpenGLBinding *m_pQglTable; + C2DView *m_p2DView; +public: + CControlPointsManager() { m_pQglTable = NULL; m_p2DView = NULL; } + virtual ~CControlPointsManager() { } + void Init( C2DView *p2DView, OpenGLBinding *pQglTable ) { m_pQglTable = pQglTable; m_p2DView = p2DView; } + + virtual bool OnLButtonDown (int x, int y) = 0; + virtual bool OnMouseMove (int x, int y) = 0; + virtual bool OnLButtonUp (int x, int y) = 0; + + virtual void render() = 0; + virtual void Commit() = 0; +}; + +// brush face manager +class CControlPointsManagerBFace : public CControlPointsManager +{ + enum EManagerState { Idle, Drag } ManagerState; + int m_NumPoints; + // initial geometry + CtrlPts_t m_RefPts; + // current geometry + CtrlPts_t *m_pPts; + // transform matrix ( 2DView is Window <-> ST ) + float m_TM[2][3]; + // texture size for ST <-> XY + int m_TexSize[2]; + // used when translating + float m_TransOffset[2]; + // dragged point index + int m_iDragPoint; + // do we have an anchor ? + bool m_bGotAnchor; + // anchor point index + int m_iAnchorPoint; + // coordinates of Anchor + float m_Anchor[2]; + // used for commit + _QERFaceData *m_pFaceData; + +public: + // construction / init ------------------------------------------------- + CControlPointsManagerBFace() { ManagerState = Idle; } + virtual ~CControlPointsManagerBFace() { } + // NOTE: pQglTable is sent to CControlPointsManager::Init + void Init(int iPts, CtrlPts_t *Pts, C2DView *p2DView, int TexSize[2], _QERFaceData* pFaceData, OpenGLBinding *pQglTable); + // CControlPointsManager interface ------------------------------------- + + virtual bool OnLButtonDown (int x, int y); + virtual bool OnMouseMove (int x, int y); + virtual bool OnLButtonUp (int x, int y); + + virtual void render(); + virtual void Commit(); + +private: + // internal members + void UpdateCtrlPts(); + void ComputeTransOffset(int i); + void XYSpaceForSTSpace( float xy[2], const float st[2] ); +}; + +// patch manager +class CControlPointsManagerPatch : public CControlPointsManager +{ + enum EManagerState { Idle, Drag } ManagerState; + // reference data, used for commits + patchMesh_t* m_pPatch; + // work patch, holds current data + patchMesh_t* m_pWorkPatch; + int m_iDragPoint[2]; + +public: + // construction / init ------------------------------------------------- + CControlPointsManagerPatch() { ManagerState = Idle; } + virtual ~CControlPointsManagerPatch() { } + // NOTE: pQglTable is sent to CControlPointsManager::Init + void Init( patchMesh_t* pWorkPatch, C2DView *p2DView, OpenGLBinding *pQglTable, patchMesh_t* pPatch ); + // CControlPointsManager interface ------------------------------------- + + virtual bool OnLButtonDown (int x, int y); + virtual bool OnMouseMove (int x, int y); + virtual bool OnLButtonUp (int x, int y); + + virtual void render(); + virtual void Commit(); +}; + +#endif diff --git a/plugins/textool/Doc/.cvswrappers b/plugins/textool/Doc/.cvswrappers new file mode 100644 index 00000000..89bb1f8b --- /dev/null +++ b/plugins/textool/Doc/.cvswrappers @@ -0,0 +1 @@ +*.jpg -m 'COPY' -k 'b' diff --git a/plugins/textool/Doc/Image2.jpg b/plugins/textool/Doc/Image2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0e443d1e15b2556f2905680cc5b030b5a068023f GIT binary patch literal 50020 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

=O?W*tNZ_m;rk!IC$(4uXF& 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 G*Z&)7PHANT literal 0 HcmV?d00001 diff --git a/plugins/textool/Doc/TexTool.html b/plugins/textool/Doc/TexTool.html new file mode 100644 index 00000000..7e619092 --- /dev/null +++ b/plugins/textool/Doc/TexTool.html @@ -0,0 +1,123 @@ + + + TexTool plugin for Q3Radiant - documentation +
+ + +TexTool plugin for Q3Radiant
+
Introduction +

TexTool is a set of texture tools for Q3Radiant. It was designed to help fine-tuning texture placement +on brushes and patches.

+Warning: +

The plugin needs brush primitives texture coordinates to operate. If this feature is not enabled in your project +settings you'll only be able to edit patches.

+
Basic use +

It's a "drag-n-drop control points" interface. Select a single face on a brush ( with shift+ctrl+left mouse clic), +or a patch, and do Plugins > Q3 Texture Tools > Go.... You can select points and move them... if you are editing +a brush face, you can rotate the shape by clicking on an anchor point first, then dragging another point.

+
+ + + + + +TexTool screenshot + +
+ +

There are also a few misc keys to help you. You can + +right clic + move the mouse to change the point of view +and use + +Inser and + +Suppr to zoom in/out. + + + + + +
+ +


+Appendix: keyboard shortcuts +

You can use the following keyboard shortcuts:

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + KEY
+
+ + usage
+
ESC
+
cancel the changes and hide TexTool window
+
RETURN
+
validate changes into the editor (also resets the view)
+
INSERT
+
zoom in
+
SUPPR
+
zoom out
+
RIGHT CLIC+MOVE
+
move the view
+
LEFT CLIC+MOVE
+
grab a control point and move it
+
RIGHT CLIC
+
drop down menu
+

+

Feedback, enhancements bugs etc. to timo@qeradiant.com

+

+ + + diff --git a/plugins/textool/StdAfx.cpp b/plugins/textool/StdAfx.cpp new file mode 100644 index 00000000..1b6e3d3c --- /dev/null +++ b/plugins/textool/StdAfx.cpp @@ -0,0 +1,28 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// stdafx.cpp : source file that includes just the standard includes +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/plugins/textool/StdAfx.h b/plugins/textool/StdAfx.h new file mode 100644 index 00000000..f23eeafd --- /dev/null +++ b/plugins/textool/StdAfx.h @@ -0,0 +1,154 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// stdafx.h +// precompiled headers + +// standard headers +#include +#include +#include +#include + +#if !defined(WIN32) + +// Necessary for proper boolean type declaration +#include "qertypes.h" + +typedef void* HMODULE; +typedef void* LPVOID; +typedef char* LPCSTR; + +#define MB_OK 0x00000000L +#define MB_OKCANCEL 0x00000001L +#define MB_ABORTRETRYIGNORE 0x00000002L +#define MB_YESNOCANCEL 0x00000003L +#define MB_YESNO 0x00000004L +#define MB_RETRYCANCEL 0x00000005L + + +#define MB_ICONHAND 0x00000010L +#define MB_ICONQUESTION 0x00000020L +#define MB_ICONEXCLAMATION 0x00000030L +#define MB_ICONASTERISK 0x00000040L + +#define MB_USERICON 0x00000080L +#define MB_ICONWARNING MB_ICONEXCLAMATION +#define MB_ICONERROR MB_ICONHAND +#define MB_ICONINFORMATION MB_ICONASTERISK +#define MB_ICONSTOP MB_ICONHAND + +#define MB_TYPEMASK 0x0000000FL +#define MB_ICONMASK 0x000000F0L +#define MB_DEFMASK 0x00000F00L +#define MB_MODEMASK 0x00003000L +#define MB_MISCMASK 0x0000C000L + +#define IDOK 1 +#define IDCANCEL 2 +#define IDABORT 3 +#define IDRETRY 4 +#define IDIGNORE 5 +#define IDYES 6 +#define IDNO 7 + +typedef struct tagRECT +{ + long left; + long top; + long right; + long bottom; +} RECT, *PRECT, *LPRECT; + +#endif // __linux__ + +// plugin +// FIXME TTimo: drop this +extern "C" void Sys_Printf (char *text, ...); + +#include "synapse.h" +#include "iplugin.h" +#include "qerplugin.h" +#include "mathlib.h" +#include "igl.h" +#include "iselectedface.h" +#include "isurfaceplugin.h" +#include "iui.h" + +// internals +// the implementation of a IWindowListener interface to use with the native UI +// TODO: move in it's own set of files? +// NOTE: I'm not too sure about the bool flags being any use.. they are supposed to tell if we handle the event or not +class CWindowListener : public IWindowListener +{ + int refCount; +public: + // Increment the number of references to this object + void IncRef () { refCount++; } + // Decrement the reference count + void DecRef () { if ( --refCount <= 0 ) delete this; } + // IWindowListener --------------------------------------- + bool OnLButtonDown(guint32 nFlags, double x, double y); + bool OnMButtonDown(guint32 nFlags, double x, double y) { return false; } + bool OnRButtonDown(guint32 nFlags, double x, double y); + bool OnLButtonUp(guint32 nFlags, double x, double y); + bool OnMButtonUp(guint32 nFlags, double x, double y) { return false; } + bool OnRButtonUp(guint32 nFlags, double x, double y); + bool OnMouseMove(guint32 nFlags, double x, double y); + bool OnKeyPressed(char *s); + bool Paint(); + void Close(); +}; + +#include "2DView.h" +typedef struct +{ + float data[MAX_POINTS_ON_WINDING][2]; +} CtrlPts_t; +#include "ControlPointsManager.h" + +extern OpenGLBinding g_QglTable; +extern _QERFuncTable_1 g_FuncTable; +// prefs globals +// NOTE: these are used by the CControlPointsManager classes, not very C++ish +extern bool g_bPrefsUpdateCameraView; +extern _QERSelectedFaceTable g_SelectedFaceTable; +extern _QERFaceData g_CancelFaceData; + +#define Sys_Printf g_FuncTable.m_pfnSysPrintf +#define Sys_FPrintf g_FuncTable.m_pfnSysFPrintf + +// call to validate the current changes into the editor +extern void Textool_Validate(); +extern void Textool_Cancel(); + +class CSynapseClientTexTool : public CSynapseClient +{ +public: + // CSynapseClient API + bool RequestAPI(APIDescriptor_t *pAPI); + const char* GetInfo(); + + CSynapseClientTexTool() { } + virtual ~CSynapseClientTexTool() { } +}; + +extern IWindow *g_pToolWnd; diff --git a/plugins/textool/TexTool.cpp b/plugins/textool/TexTool.cpp new file mode 100644 index 00000000..9ba7b01b --- /dev/null +++ b/plugins/textool/TexTool.cpp @@ -0,0 +1,962 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//----------------------------------------------------------------------------- +// +// DESCRIPTION: +// main plugin implementation +// texturing tools for Radiant +// + +#include "StdAfx.h" + +static void dialog_button_callback (GtkWidget *widget, gpointer data) +{ + GtkWidget *parent; + int *loop, *ret; + + parent = gtk_widget_get_toplevel (widget); + loop = (int*)g_object_get_data (G_OBJECT (parent), "loop"); + ret = (int*)g_object_get_data (G_OBJECT (parent), "ret"); + + *loop = 0; + *ret = gpointer_to_int (data); +} + +static gint dialog_delete_callback (GtkWidget *widget, GdkEvent* event, gpointer data) +{ + int *loop; + + gtk_widget_hide (widget); + loop = (int*)g_object_get_data (G_OBJECT (widget), "loop"); + *loop = 0; + + return TRUE; +} + +int DoMessageBox (const char* lpText, const char* lpCaption, guint32 uType) +{ + GtkWidget *window, *w, *vbox, *hbox; + int mode = (uType & MB_TYPEMASK), ret, loop = 1; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_signal_connect (GTK_OBJECT (window), "delete_event", + GTK_SIGNAL_FUNC (dialog_delete_callback), NULL); + gtk_signal_connect (GTK_OBJECT (window), "destroy", + GTK_SIGNAL_FUNC (gtk_widget_destroy), NULL); + gtk_window_set_title (GTK_WINDOW (window), lpCaption); + gtk_container_border_width (GTK_CONTAINER (window), 10); + g_object_set_data (G_OBJECT (window), "loop", &loop); + g_object_set_data (G_OBJECT (window), "ret", &ret); + gtk_widget_realize (window); + + vbox = gtk_vbox_new (FALSE, 10); + gtk_container_add (GTK_CONTAINER (window), vbox); + gtk_widget_show (vbox); + + w = gtk_label_new (lpText); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2); + gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT); + gtk_widget_show (w); + + w = gtk_hseparator_new (); + gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2); + gtk_widget_show (w); + + hbox = gtk_hbox_new (FALSE, 10); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 2); + gtk_widget_show (hbox); + + if (mode == MB_OK) + { + w = gtk_button_new_with_label ("Ok"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK)); + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + ret = IDOK; + } + else if (mode == MB_OKCANCEL) + { + w = gtk_button_new_with_label ("Ok"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDOK)); + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDCANCEL)); + gtk_widget_show (w); + ret = IDCANCEL; + } + else if (mode == MB_YESNOCANCEL) + { + w = gtk_button_new_with_label ("Yes"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDYES)); + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("No"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDNO)); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("Cancel"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDCANCEL)); + gtk_widget_show (w); + ret = IDCANCEL; + } + else /* if (mode == MB_YESNO) */ + { + w = gtk_button_new_with_label ("Yes"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDYES)); + GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT); + gtk_widget_grab_default (w); + gtk_widget_show (w); + + w = gtk_button_new_with_label ("No"); + gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0); + gtk_signal_connect (GTK_OBJECT (w), "clicked", + GTK_SIGNAL_FUNC (dialog_button_callback), GINT_TO_POINTER (IDNO)); + gtk_widget_show (w); + ret = IDNO; + } + + gtk_widget_show (window); + gtk_grab_add (window); + + while (loop) + gtk_main_iteration (); + + gtk_grab_remove (window); + gtk_widget_destroy (window); + + return ret; +} + +// Radiant function table +_QERFuncTable_1 g_FuncTable; + +// plugin name +const char *PLUGIN_NAME = "Q3 Texture Tools"; + +// commands in the menu +const char *PLUGIN_COMMANDS = "About...;Go..."; + +// cast to GtkWidget* +void *g_pMainWnd; +IWindow *g_pToolWnd = NULL; // handle to the window +CWindowListener g_Listen; + +// plugin interfaces --------------------------- +bool g_bQglInitDone = false; +OpenGLBinding g_QglTable; +bool g_bSelectedFaceInitDone = false; +_QERSelectedFaceTable g_SelectedFaceTable; +bool g_bUITable = false; +_QERUITable g_UITable; + +// selected face ------------------------------- +// we use this one to commit / read with Radiant +_QERFaceData g_SelectedFaceData; +// g_pSelectedFaceWindings gets allocated with MAX_POINTS_ON_WINDING at plugin startup ( QERPlug_Init ) +winding_t *g_pSelectedFaceWinding = NULL; +const float g_ViewportRatio = 1.2f; +// usefull class to manage the 2D view +C2DView g_2DView; +// control points to move the polygon +CControlPointsManagerBFace g_ControlPointsBFace; +// tells if a face is selected and we have something to render in the TexWindow +bool g_bTexViewReady = false; +// data for texture work +int g_NumPoints; +CtrlPts_t g_WorkWinding; +// reference _QERFaceData we use on Cancel, and for Commit +_QERFaceData g_CancelFaceData; + +// patches ------------------------------------- +bool g_bPatch = false; +//++timo we use this one to grab selected patchMesh_t +// FIXME: update when there's a real interface to read/write patches +bool g_bSurfaceTableInitDone = false; +_QERAppSurfaceTable g_SurfaceTable; +CControlPointsManagerPatch g_ControlPointsPatch; +// data for texture work +patchMesh_t* g_pPatch; +// we only use ctrl[][].st in this one +patchMesh_t g_WorkPatch; +// copy of initial g_pPatch for Cancel situation +patchMesh_t g_CancelPatch; + +// --------------------------------------------- +// holds the manager we are currently using +CControlPointsManager *g_pManager = NULL; + +// --------------------------------------------- +// globals flags for user preferences +//++timo TODO: this should be retrieved from the Editor's .INI prefs in a dedicated interface +// update camera view during manipulation ? +bool g_bPrefsUpdateCameraView = true; + +// misc ---------------------------------------- +bool g_bHelp = false; +//++timo FIXME: used to close the plugin window if InitTexView fails +// it's dirty, only use is to prevent infinite loop in DialogProc +bool g_bClosing = false; + +const char *PLUGIN_ABOUT = "Texture Tools for Radiant\n\n" + "Gtk port by Leonardo Zide (leo@lokigames.com)\n" + "Original version by Timothee \"TTimo\" Besset (timo@qeradiant.com)"; + +extern "C" void* WINAPI QERPlug_GetFuncTable () +{ + return &g_FuncTable; +} + +const char* QERPlug_Init (void* hApp, void *pWidget) +{ + int size; + GtkWidget* pMainWidget = static_cast(pWidget); + + g_pMainWnd = pMainWidget; + memset(&g_FuncTable, 0, sizeof(_QERFuncTable_1)); + g_FuncTable.m_nSize = sizeof(_QERFuncTable_1); + size = (int)((winding_t *)0)->points[MAX_POINTS_ON_WINDING]; + g_pSelectedFaceWinding = (winding_t *)malloc( size ); + memset( g_pSelectedFaceWinding, 0, size ); + return "Texture tools for Radiant"; +} + +const char* QERPlug_GetName() +{ + return (char*)PLUGIN_NAME; +} + +const char* QERPlug_GetCommandList() +{ + return PLUGIN_COMMANDS; +} + +char *TranslateString (char *buf) +{ + static char buf2[32768]; + int i, l; + char *out; + + l = strlen(buf); + out = buf2; + for (i=0 ; igetHeight(); + g_2DView.m_rect.right = hwndDlg->getWidth(); + + // we need to draw this area, now compute a bigger area so the texture scale is the same along X and Y + // compute box shape in XY space, let's say X <-> S we'll get a ratio for Y: + if (!g_bPatch) + { + g_SelectedFaceTable.m_pfnGetTextureSize( 0, TexSize ); + } + else + { + TexSize[0] = g_pPatch->d_texture->width; + TexSize[1] = g_pPatch->d_texture->height; + } + // we want a texture with the same X / Y ratio + // compute XY space / window size ratio + float SSize = (float)fabs( g_2DView.m_Maxs[0] - g_2DView.m_Mins[0] ); + float TSize = (float)fabs( g_2DView.m_Maxs[1] - g_2DView.m_Mins[1] ); + float XSize = TexSize[0] * SSize; + float YSize = TexSize[1] * TSize; + float RatioX = XSize / (float)abs( g_2DView.m_rect.left - g_2DView.m_rect.right ); + float RatioY = YSize / (float)abs( g_2DView.m_rect.top - g_2DView.m_rect.bottom ); + if ( RatioX > RatioY ) + { + YSize = (float)abs( g_2DView.m_rect.top - g_2DView.m_rect.bottom ) * RatioX; + TSize = YSize / (float)TexSize[1]; + } + else + { + XSize = (float)abs( g_2DView.m_rect.left - g_2DView.m_rect.right ) * RatioY; + SSize = XSize / (float)TexSize[0]; + } + g_2DView.m_Mins[0] = g_2DView.m_Center[0] - 0.5f * SSize; + g_2DView.m_Maxs[0] = g_2DView.m_Center[0] + 0.5f * SSize; + g_2DView.m_Mins[1] = g_2DView.m_Center[1] - 0.5f * TSize; + g_2DView.m_Maxs[1] = g_2DView.m_Center[1] + 0.5f * TSize; +} + +// call this one each time we need to re-init +//++timo TODO: re-init objects state, g_2DView and g_ControlPointsManager +void InitTexView( IWindow* hwndDlg ) +{ + // size of the texture we are working on + int TexSize[2]; + g_bTexViewReady = false; + if (g_SelectedFaceTable.m_pfnGetSelectedFaceCount() != 0) + { + g_SelectedFaceTable.m_pfnGetFaceInfo( 0, &g_SelectedFaceData, g_pSelectedFaceWinding ); + g_bPatch = false; + int i; + // we have something selected + // setup: compute BBox for the winding ( in ST space ) + //++timo FIXME: move this in a C2DView member ? used as well for patches + g_2DView.m_Mins[0] = +9999.0f; g_2DView.m_Mins[1] = +9999.0f; + g_2DView.m_Maxs[0] = -9999.0f; g_2DView.m_Maxs[1] = -9999.0f; + for ( i=0; inumpoints; i++ ) + { + if ( g_pSelectedFaceWinding->points[i][3] < g_2DView.m_Mins[0] ) + g_2DView.m_Mins[0] = g_pSelectedFaceWinding->points[i][3]; + if ( g_pSelectedFaceWinding->points[i][3] > g_2DView.m_Maxs[0] ) + g_2DView.m_Maxs[0] = g_pSelectedFaceWinding->points[i][3]; + if ( g_pSelectedFaceWinding->points[i][4] < g_2DView.m_Mins[1] ) + g_2DView.m_Mins[1] = g_pSelectedFaceWinding->points[i][4]; + if ( g_pSelectedFaceWinding->points[i][4] > g_2DView.m_Maxs[1] ) + g_2DView.m_Maxs[1] = g_pSelectedFaceWinding->points[i][4]; + } + // NOTE: FitView will read and init TexSize + FitView( hwndDlg, TexSize ); + // now init the work tables + g_NumPoints = g_pSelectedFaceWinding->numpoints; + for ( i=0; ipoints[i][3]; + g_WorkWinding.data[i][1] = g_pSelectedFaceWinding->points[i][4]; + } + g_ControlPointsBFace.Init( g_NumPoints, &g_WorkWinding, &g_2DView, TexSize, &g_SelectedFaceData, &g_QglTable ); + // init snap-to-grid + float fTexStep[2]; + fTexStep[0] = 1.0f / float(TexSize[0]); + fTexStep[1] = 1.0f / float(TexSize[1]); + g_2DView.SetGrid( fTexStep[0], fTexStep[1] ); + g_pManager = &g_ControlPointsBFace; + // prepare the "Cancel" data + memcpy( &g_CancelFaceData, &g_SelectedFaceData, sizeof(_QERFaceData) ); + // we are done + g_bTexViewReady = true; + } + else if ( g_SurfaceTable.m_pfnAnyPatchesSelected()) + { + g_pPatch = g_SurfaceTable.m_pfnGetSelectedPatch(); + g_bPatch = true; + int i,j; + // compute BBox for all patch points + g_2DView.m_Mins[0] = +9999.0f; g_2DView.m_Mins[1] = +9999.0f; + g_2DView.m_Maxs[0] = -9999.0f; g_2DView.m_Maxs[1] = -9999.0f; + for ( i=0; iwidth; i++ ) + { + for ( j=0; jheight; j++ ) + { + if ( g_pPatch->ctrl[i][j].st[0] < g_2DView.m_Mins[0] ) + g_2DView.m_Mins[0] = g_pPatch->ctrl[i][j].st[0]; + if ( g_pPatch->ctrl[i][j].st[0] > g_2DView.m_Maxs[0] ) + g_2DView.m_Maxs[0] = g_pPatch->ctrl[i][j].st[0]; + if ( g_pPatch->ctrl[i][j].st[1] < g_2DView.m_Mins[1] ) + g_2DView.m_Mins[1] = g_pPatch->ctrl[i][j].st[1]; + if ( g_pPatch->ctrl[i][j].st[1] > g_2DView.m_Maxs[1] ) + g_2DView.m_Maxs[1] = g_pPatch->ctrl[i][j].st[1]; + } + } + FitView( hwndDlg, TexSize); + // init the work tables + g_WorkPatch = *g_pPatch; + g_ControlPointsPatch.Init( &g_WorkPatch, &g_2DView, &g_QglTable, g_pPatch ); + // init snap-to-grid + float fTexStep[2]; + fTexStep[0] = 1.0f / float(TexSize[0]); + fTexStep[1] = 1.0f / float(TexSize[1]); + g_2DView.SetGrid( fTexStep[0], fTexStep[1] ); + g_pManager = &g_ControlPointsPatch; + // prepare the "cancel" data + g_CancelPatch = *g_pPatch; + // we are done + g_bTexViewReady = true; + } +} + +void Textool_Validate() +{ + // validate current situation into the main view + g_pManager->Commit( ); + // for a brush face we have an aditionnal step + if (!g_bPatch) + { + // tell Radiant to update (will also send update windows messages ) + g_SelectedFaceTable.m_pfnSetFaceInfo( 0, &g_SelectedFaceData ); + } + else + { + // ask to rebuild the patch display data + g_pPatch->bDirty = true; + // send a repaint to the camera window as well + g_FuncTable.m_pfnSysUpdateWindows( W_CAMERA ); + } + // we'll need to update after that as well: + g_bTexViewReady = false; + // send a repaint message + g_pToolWnd->Redraw (); +} + +void Textool_Cancel() +{ + if (!g_bPatch) + { + // tell Radiant to update (will also send update windows messages ) + g_SelectedFaceTable.m_pfnSetFaceInfo( 0, &g_CancelFaceData ); + } + else + { + *g_pPatch = g_CancelPatch; + g_pPatch->bDirty = true; + g_FuncTable.m_pfnSysUpdateWindows( W_CAMERA ); + } + // do not call destroy, decref it + g_pToolWnd->DecRef(); + g_pToolWnd = NULL; +} + +static void DoExpose () +{ + int i,j; + + g_2DView.PreparePaint(); + g_QglTable.m_pfn_qglColor3f(1, 1, 1); + // draw the texture background + g_QglTable.m_pfn_qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + if (!g_bPatch) + { + g_QglTable.m_pfn_qglBindTexture( GL_TEXTURE_2D, g_SelectedFaceTable.m_pfnGetTextureNumber(0) ); + } + else + { + g_QglTable.m_pfn_qglBindTexture( GL_TEXTURE_2D, g_pPatch->d_texture->texture_number ); + } + + g_QglTable.m_pfn_qglEnable( GL_TEXTURE_2D ); + g_QglTable.m_pfn_qglBegin( GL_QUADS ); + g_QglTable.m_pfn_qglTexCoord2f( g_2DView.m_Mins[0], g_2DView.m_Mins[1] ); + g_QglTable.m_pfn_qglVertex2f( g_2DView.m_Mins[0], g_2DView.m_Mins[1] ); + g_QglTable.m_pfn_qglTexCoord2f( g_2DView.m_Maxs[0], g_2DView.m_Mins[1] ); + g_QglTable.m_pfn_qglVertex2f( g_2DView.m_Maxs[0], g_2DView.m_Mins[1] ); + g_QglTable.m_pfn_qglTexCoord2f( g_2DView.m_Maxs[0], g_2DView.m_Maxs[1] ); + g_QglTable.m_pfn_qglVertex2f( g_2DView.m_Maxs[0], g_2DView.m_Maxs[1] ); + g_QglTable.m_pfn_qglTexCoord2f( g_2DView.m_Mins[0], g_2DView.m_Maxs[1] ); + g_QglTable.m_pfn_qglVertex2f( g_2DView.m_Mins[0], g_2DView.m_Maxs[1] ); + g_QglTable.m_pfn_qglEnd(); + g_QglTable.m_pfn_qglDisable( GL_TEXTURE_2D ); + + if (!g_bPatch) + { + g_QglTable.m_pfn_qglBegin( GL_LINE_LOOP ); + for ( i=0; iwidth; i++ ) + for ( j=0; jheight; j++ ) + { + if ( i < g_pPatch->width-1 ) + { + g_QglTable.m_pfn_qglVertex2f( g_WorkPatch.ctrl[i][j].st[0], g_WorkPatch.ctrl[i][j].st[1] ); + g_QglTable.m_pfn_qglVertex2f( g_WorkPatch.ctrl[i+1][j].st[0], g_WorkPatch.ctrl[i+1][j].st[1] ); + } + + if ( j < g_pPatch->height-1 ) + { + g_QglTable.m_pfn_qglVertex2f( g_WorkPatch.ctrl[i][j].st[0], g_WorkPatch.ctrl[i][j].st[1] ); + g_QglTable.m_pfn_qglVertex2f( g_WorkPatch.ctrl[i][j+1].st[0], g_WorkPatch.ctrl[i][j+1].st[1] ); + } + } + g_QglTable.m_pfn_qglEnd(); + } + + // let the control points manager render + g_pManager->render( ); +} + +static bool CanProcess () +{ + if (!g_bTexViewReady && !g_bClosing) + { + InitTexView (g_pToolWnd); + + if (!g_bTexViewReady) + { + g_bClosing = true; + DoMessageBox ("You must have brush primitives activated in your project settings and\n" + "have a patch or a single face selected to use the TexTool plugin.\n" + "See plugins/TexToolHelp for documentation.", "TexTool plugin", MB_ICONERROR | MB_OK); + // decref, this will destroy + g_pToolWnd->DecRef(); + g_pToolWnd = NULL; + return 0; + } + else + g_bClosing = false; + } + else if (!g_bTexViewReady && g_bClosing) + { + return 0; + } + + return 1; +} + +#if 0 +static void button_press (GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + if (CanProcess ()) + { + switch (event->button) + { + case 1: + g_pManager->OnLButtonDown (event->x, event->y); break; + case 3: + g_2DView.OnRButtonDown (event->x, event->y); break; + } + } +} + +static void button_release (GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + if (CanProcess ()) + { + switch (event->button) + { + case 1: + g_pManager->OnLButtonUp (event->x, event->y); break; + case 3: + g_2DView.OnRButtonUp (event->x, event->y); break; + } + } +} + +static void motion (GtkWidget *widget, GdkEventMotion *event, gpointer data) +{ + if (CanProcess ()) + { + if (g_2DView.OnMouseMove (event->x, event->y)) + return; + + if (g_pManager->OnMouseMove (event->x, event->y)) + return; + } +} + +static gint expose (GtkWidget *widget, GdkEventExpose *event, gpointer data) +{ + if (event->count > 0) + return TRUE; + + if (!CanProcess ()) + return TRUE; + + if (g_bTexViewReady) + { + g_2DView.m_rect.bottom = widget->allocation.height; + g_2DView.m_rect.right = widget->allocation.width; + + if (!g_QglTable.m_pfn_glwidget_make_current (g_pToolWidget)) + { + Sys_Printf("TexTool: glMakeCurrent failed\n"); + return TRUE; + } + + DoExpose (); + + g_QglTable.m_pfn_glwidget_swap_buffers (g_pToolWidget); + } + + return TRUE; +} + +static gint keypress (GtkWidget* widget, GdkEventKey* event, gpointer data) +{ + unsigned int code = gdk_keyval_to_upper(event->keyval); + + if (code == GDK_Escape) + { + gtk_widget_destroy (g_pToolWnd); + g_pToolWnd = NULL; + return TRUE; + } + + if (CanProcess ()) + { + if (g_2DView.OnKeyDown (code)) + return FALSE; + + if (code == GDK_Return) + { + Textool_Validate(); + return FALSE; + } + } + + return TRUE; +} + +static gint close (GtkWidget *widget, GdkEvent* event, gpointer data) +{ + gtk_widget_destroy (widget); + g_pToolWnd = NULL; + + return TRUE; +} + +static GtkWidget* CreateOpenGLWidget () +{ + g_pToolWidget = g_QglTable.m_pfn_glwidget_new (FALSE, g_QglTable.m_pfn_GetQeglobalsGLWidget ()); + + gtk_widget_set_events (g_pToolWidget, GDK_DESTROY | GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK | + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK); + + // Connect signal handlers + gtk_signal_connect (GTK_OBJECT (g_pToolWidget), "expose_event", GTK_SIGNAL_FUNC (expose), NULL); + gtk_signal_connect (GTK_OBJECT (g_pToolWidget), "motion_notify_event", + GTK_SIGNAL_FUNC (motion), NULL); + gtk_signal_connect (GTK_OBJECT (g_pToolWidget), "button_press_event", + GTK_SIGNAL_FUNC (button_press), NULL); + gtk_signal_connect (GTK_OBJECT (g_pToolWidget), "button_release_event", + GTK_SIGNAL_FUNC (button_release), NULL); + + gtk_signal_connect (GTK_OBJECT (g_pToolWnd), "delete_event", GTK_SIGNAL_FUNC (close), NULL); + gtk_signal_connect (GTK_OBJECT (g_pToolWnd), "key_press_event", + GTK_SIGNAL_FUNC (keypress), NULL); + + return g_pToolWidget; +} +#endif + +#if 0 +static void DoPaint () +{ + if (!CanProcess ()) + return; + + if (g_bTexViewReady) + { + g_2DView.m_rect.bottom = g_pToolWnd->getHeight(); + g_2DView.m_rect.right = g_pToolWnd->getWidth(); + + // set GL_PROJECTION + g_2DView.PreparePaint(); + // render the objects + // the master is not rendered the same way, draw over a unified texture background + g_2DView.TextureBackground(g_DrawObjects[0].pObject->getTextureNumber()); + if (g_nDrawObjects >= 1) + { + int i; + for (i=1;iPrepareModelView(g_DrawObjects[i].pTopo); + g_DrawObjects[i].pObject->RenderAuxiliary(); + } + } + // draw the polygon outline and control points + g_DrawObjects[0].pObject->PrepareModelView(NULL); + g_DrawObjects[0].pObject->RenderUI(); + } +} +#endif + +bool CWindowListener::OnLButtonDown(guint32 nFlags, double x, double y) +{ + if (CanProcess()) + { + g_pManager->OnLButtonDown((int)x, (int)y); + return true; + } + return false; +} + +bool CWindowListener::OnRButtonDown(guint32 nFlags, double x, double y) +{ + if (CanProcess()) + { + g_2DView.OnRButtonDown ((int)x, (int)y); + return true; + } + return false; +} + +bool CWindowListener::OnLButtonUp(guint32 nFlags, double x, double y) +{ + if (CanProcess()) + { + g_pManager->OnLButtonUp((int)x, (int)y); + return true; + } + return false; +} + +bool CWindowListener::OnRButtonUp(guint32 nFlags, double x, double y) +{ + if (CanProcess()) + { + g_2DView.OnRButtonUp ((int)x, (int)y); + return true; + } + return false; +} + +bool CWindowListener::OnMouseMove(guint32 nFlags, double x, double y) +{ + if (CanProcess ()) + { + if (g_2DView.OnMouseMove ((int)x, (int)y)) + return true; + + g_pManager->OnMouseMove((int)x, (int)y); + return true; + } + return false; +} + +// the widget is closing +void CWindowListener::Close() +{ + g_pToolWnd = NULL; +} + +bool CWindowListener::Paint() +{ + if (!CanProcess ()) + return false; + + if (g_bTexViewReady) + DoExpose(); + + return true; +} + +bool CWindowListener::OnKeyPressed(char *s) +{ + if (!strcmp(s,"Escape")) + { + Textool_Cancel(); + return TRUE; + } + if (CanProcess ()) + { + if (g_2DView.OnKeyDown (s)) + return TRUE; + + if (!strcmp(s,"Return")) + { + Textool_Validate(); + return TRUE; + } + } + return FALSE; +} + +extern "C" void QERPlug_Dispatch(const char* p, vec3_t vMin, vec3_t vMax, bool bSingleBrush) +{ + #if 0 + // if it's the first call, perhaps we need some additional init steps + if (!g_bQglInitDone) + { + g_QglTable.m_nSize = sizeof(OpenGLBinding); + if ( g_FuncTable.m_pfnRequestInterface( QERQglTable_GUID, static_cast(&g_QglTable) ) ) + { + g_bQglInitDone = true; + } + else + { + Sys_Printf("TexTool plugin: OpenGLBinding interface request failed\n"); + return; + } + } + + if (!g_bSelectedFaceInitDone) + { + g_SelectedFaceTable.m_nSize = sizeof(_QERSelectedFaceTable); + if (g_FuncTable.m_pfnRequestInterface (QERSelectedFaceTable_GUID, + static_cast(&g_SelectedFaceTable))) + { + g_bSelectedFaceInitDone = true; + } + else + { + Sys_Printf("TexTool plugin: _QERSelectedFaceTable interface request failed\n"); + return; + } + } + + if (!g_bSurfaceTableInitDone) + { + g_SurfaceTable.m_nSize = sizeof(_QERAppSurfaceTable); + if ( g_FuncTable.m_pfnRequestInterface( QERAppSurfaceTable_GUID, static_cast(&g_SurfaceTable) ) ) + { + g_bSurfaceTableInitDone = true; + } + else + { + Sys_Printf("TexTool plugin: _QERAppSurfaceTable interface request failed\n"); + return; + } + } + + if (!g_bUITable) + { + g_UITable.m_nSize = sizeof(_QERUITable); + if ( g_FuncTable.m_pfnRequestInterface( QERUI_GUID, static_cast(&g_UITable) ) ) + { + g_bUITable = true; + } + else + { + Sys_Printf("TexTool plugin: _QERUITable interface request failed\n"); + return; + } + } + #endif + + if (!strcmp(p, "About...")) + { + DoMessageBox (PLUGIN_ABOUT, "About ...", MB_OK ); + } + else if (!strcmp(p, "Go...")) + { + if (!g_pToolWnd) + { + g_pToolWnd = g_UITable.m_pfnCreateGLWindow(); + g_pToolWnd->setSizeParm(300,300); + g_pToolWnd->setName("TexTool"); + // g_Listener is a static class, we need to bump the refCount to avoid premature release problems + g_Listen.IncRef(); + // setListener will incRef on the listener too + g_pToolWnd->setListener(&g_Listen); + if (!g_pToolWnd->Show()) + { + DoMessageBox ("Error creating texture tools window!", "TexTool plugin", MB_ICONERROR | MB_OK); + return; + } + } + + g_bTexViewReady = false; + g_bClosing = false; + } + else if (!strcmp(p, "Help...")) + { + if (!g_bHelp) + DoMessageBox ("Select a brush face (ctrl+shift+left mouse) or a patch, and hit Go...\n" + "See tutorials for more", "TexTool plugin", MB_OK ); + else + DoMessageBox ("Are you kidding me ?", "TexTool plugin", MB_OK ); + g_bHelp = true; + } +} + +// ============================================================================= +// SYNAPSE + +CSynapseServer* g_pSynapseServer = NULL; +CSynapseClientTexTool g_SynapseClient; + +extern "C" CSynapseClient* SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces (const char *version, CSynapseServer *pServer) +{ + if (strcmp(version, SYNAPSE_VERSION)) + { + Syn_Printf("ERROR: synapse API version mismatch: should be '" SYNAPSE_VERSION "', got '%s'\n", version); + return NULL; + } + g_pSynapseServer = pServer; + g_pSynapseServer->IncRef(); + Set_Syn_Printf(g_pSynapseServer->Get_Syn_Printf()); + + g_SynapseClient.AddAPI(PLUGIN_MAJOR, "textool", sizeof(_QERPluginTable)); + g_SynapseClient.AddAPI(RADIANT_MAJOR, NULL, sizeof(g_FuncTable), SYN_REQUIRE, &g_FuncTable); + g_SynapseClient.AddAPI(QGL_MAJOR, NULL, sizeof(g_QglTable), SYN_REQUIRE, &g_QglTable); + g_SynapseClient.AddAPI(SELECTEDFACE_MAJOR, NULL, sizeof(g_SelectedFaceTable), SYN_REQUIRE, &g_SelectedFaceTable); + + return &g_SynapseClient; +} + +bool CSynapseClientTexTool::RequestAPI(APIDescriptor_t *pAPI) +{ + if (!strcmp(pAPI->major_name, PLUGIN_MAJOR)) + { + _QERPluginTable *pTable = static_cast<_QERPluginTable*>(pAPI->mpTable); + pTable->m_pfnQERPlug_Init = QERPlug_Init; + pTable->m_pfnQERPlug_GetName = QERPlug_GetName; + pTable->m_pfnQERPlug_GetCommandList = QERPlug_GetCommandList; + pTable->m_pfnQERPlug_Dispatch = QERPlug_Dispatch; + return true; + } + + Syn_Printf("ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo()); + return false; +} + +#include "version.h" + +const char* CSynapseClientTexTool::GetInfo() +{ + return "Texture Tools plugin built " __DATE__ " " RADIANT_VERSION; +} diff --git a/plugins/textool/TexTool.def b/plugins/textool/TexTool.def new file mode 100644 index 00000000..95cbb273 --- /dev/null +++ b/plugins/textool/TexTool.def @@ -0,0 +1,12 @@ +; TexTool.def : Declares the module parameters for the DLL. + +LIBRARY "TexTool" +DESCRIPTION 'TexTool Windows Dynamic Link Library' + +EXPORTS + ; Explicit exports can go here + QERPlug_Init @1 + QERPlug_GetName @2 + QERPlug_GetCommandList @3 + QERPlug_Dispatch @4 + QERPlug_GetFuncTable @5 diff --git a/plugins/textool/TexTool.rc b/plugins/textool/TexTool.rc new file mode 100644 index 00000000..69d0eaf5 --- /dev/null +++ b/plugins/textool/TexTool.rc @@ -0,0 +1,136 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Neutral resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU) +#ifdef _WIN32 +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG2 DIALOGEX 0, 0, 290, 206 +STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_TOOLWINDOW +FONT 8, "MS Sans Serif" +BEGIN +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_DIALOG2, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 283 + TOPMARGIN, 7 + BOTTOMMARGIN, 199 + END +END +#endif // APSTUDIO_INVOKED + +#endif // Neutral resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_DROP_MENU MENU DISCARDABLE +BEGIN + POPUP "Drop" + BEGIN + MENUITEM "Validate (RETURN)", ID_DROP_VALIDATE + MENUITEM "Zoom in (INSERT)", ID_DROP_ZOOMIN + MENUITEM "Zoom out (SUPPR)", ID_DROP_ZOOMOUT + MENUITEM "Cancel (ESC)", ID_DROP_CANCEL + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// French (France) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_FRA) +#ifdef _WIN32 +LANGUAGE LANG_FRENCH, SUBLANG_FRENCH +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // French (France) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/textool/TexTool.vcproj b/plugins/textool/TexTool.vcproj new file mode 100644 index 00000000..e5e285a3 --- /dev/null +++ b/plugins/textool/TexTool.vcproj @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/textool/changelog.txt b/plugins/textool/changelog.txt new file mode 100644 index 00000000..9d660cbc --- /dev/null +++ b/plugins/textool/changelog.txt @@ -0,0 +1,8 @@ +11/19/99 +first usable version +here is the TODO-list for next release, ( most certainly a wish list ) + +- TODO: add hooks with the selected face and selected patch data. tell the plugin when selected face +or selected patch has changed. +the hooks should use a generic interface inside Radiant for "observers" +- TODO: add other usefull texturing tools, if designers come up with good ideas \ No newline at end of file diff --git a/plugins/textool/resource.h b/plugins/textool/resource.h new file mode 100644 index 00000000..85ba8d7d --- /dev/null +++ b/plugins/textool/resource.h @@ -0,0 +1,23 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by TexTool.rc +// +#define IDUNDO 3 +#define IDD_DIALOG 101 +#define IDD_DIALOG2 102 +#define IDR_DROP_MENU 103 +#define ID_DROP_VALIDATE 40001 +#define ID_DROP_ZOOMIN 40002 +#define ID_DROP_ZOOMOUT 40003 +#define ID_DROP_CANCEL 40004 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40005 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/plugins/vfspk3/.cvsignore b/plugins/vfspk3/.cvsignore new file mode 100644 index 00000000..af3ce37b --- /dev/null +++ b/plugins/vfspk3/.cvsignore @@ -0,0 +1,11 @@ +*.d +*.o +*.so +Debug +Release +*.aps +*.plg +*.bak +*.BAK +*.opt +*.ncb diff --git a/plugins/vfspk3/archive.cpp b/plugins/vfspk3/archive.cpp new file mode 100644 index 00000000..9147f8f0 --- /dev/null +++ b/plugins/vfspk3/archive.cpp @@ -0,0 +1,178 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "archive.h" + +#include "idatastream.h" +#include "iarchive.h" + +#include +#include +#include + +#include "stream/filestream.h" +#include "stream/textfilestream.h" +#include "string/string.h" +#include "os/path.h" +#include "os/file.h" +#include "os/dir.h" +#include "archivelib.h" +#include "fs_path.h" + +#include "vfspk3.h" + + +class DirectoryArchive : public Archive +{ + CopiedString m_root; +public: + DirectoryArchive(const char* root) : m_root(root) + { + } + + void release() + { + delete this; + } + virtual ArchiveFile* openFile(const char* name) + { + UnixPath path(m_root.c_str()); + path.push_filename(name); + DirectoryArchiveFile* file = new DirectoryArchiveFile(name, path.c_str()); + if(!file->failed()) + { + return file; + } + file->release(); + return 0; + } + virtual ArchiveTextFile* openTextFile(const char* name) + { + UnixPath path(m_root.c_str()); + path.push_filename(name); + DirectoryArchiveTextFile* file = new DirectoryArchiveTextFile(name, path.c_str()); + if(!file->failed()) + { + return file; + } + file->release(); + return 0; + } + virtual bool containsFile(const char* name) + { + UnixPath path(m_root.c_str()); + path.push_filename(name); + return file_readable(path.c_str()); + } + virtual void forEachFile(VisitorFunc visitor, const char* root) + { + std::vector dirs; + UnixPath path(m_root.c_str()); + path.push(root); + dirs.push_back(directory_open(path.c_str())); + + while(!dirs.empty() && directory_good(dirs.back())) + { + const char* name = directory_read_and_increment(dirs.back()); + + if(name == 0) + { + directory_close(dirs.back()); + dirs.pop_back(); + path.pop(); + } + else if(!string_equal(name, ".") && !string_equal(name, "..")) + { + path.push_filename(name); + + bool is_directory = file_is_directory(path.c_str()); + + if(!is_directory) + visitor.file(path_make_relative(path.c_str(), m_root.c_str())); + + path.pop(); + + if(is_directory) + { + path.push(name); + + if(!visitor.directory(path_make_relative(path.c_str(), m_root.c_str()), dirs.size())) + dirs.push_back(directory_open(path.c_str())); + else + path.pop(); + } + } + } + } +}; + +Archive* OpenArchive(const char* name) +{ + return new DirectoryArchive(name); +} + +#if 0 + +class TestArchive +{ + class TestVisitor : public Archive::IVisitor + { + public: + virtual void visit(const char* name) + { + int bleh = 0; + } + }; +public: + void test1() + { + Archive* archive = OpenArchive("d:/quake/id1/"); + ArchiveFile* file = archive->openFile("quake101.wad"); + if(file != 0) + { + char buffer[1024]; + file->getInputStream().read(buffer, 1024); + file->release(); + } + TestVisitor visitor; + archive->forEachFile(Archive::VisitorFunc(&visitor, Archive::eFilesAndDirectories, 0), ""); + archive->release(); + } + void test2() + { + Archive* archive = OpenArchive("d:/gtkradiant_root/baseq3/"); + TestVisitor visitor; + archive->forEachFile(Archive::VisitorFunc(&visitor, Archive::eFilesAndDirectories, 2), ""); + archive->forEachFile(Archive::VisitorFunc(&visitor, Archive::eFiles, 1), "textures"); + archive->forEachFile(Archive::VisitorFunc(&visitor, Archive::eDirectories, 1), "textures"); + archive->forEachFile(Archive::VisitorFunc(&visitor, Archive::eFilesAndDirectories, 1), "textures"); + archive->release(); + } + TestArchive() + { + test1(); + test2(); + } +}; + +TestArchive g_test; + +#endif diff --git a/plugins/vfspk3/archive.h b/plugins/vfspk3/archive.h new file mode 100644 index 00000000..09874c37 --- /dev/null +++ b/plugins/vfspk3/archive.h @@ -0,0 +1,29 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ARCHIVE_H) +#define INCLUDED_ARCHIVE_H + +#include "iarchive.h" + +const _QERArchiveTable* GetArchiveTable(ArchiveModules& archiveModules, const char* type); + +#endif diff --git a/plugins/vfspk3/vfs.cpp b/plugins/vfspk3/vfs.cpp new file mode 100644 index 00000000..68837483 --- /dev/null +++ b/plugins/vfspk3/vfs.cpp @@ -0,0 +1,683 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// Rules: +// +// - Directories should be searched in the following order: ~/.q3a/baseq3, +// install dir (/usr/local/games/quake3/baseq3) and cd_path (/mnt/cdrom/baseq3). +// +// - Pak files are searched first inside the directories. +// - Case insensitive. +// - Unix-style slashes (/) (windows is backwards .. everyone knows that) +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "vfs.h" + +#include +#include +#include +#include +#include + +#include "qerplugin.h" +#include "idatastream.h" +#include "iarchive.h" +ArchiveModules& FileSystemQ3API_getArchiveModules(); +#include "ifilesystem.h" + +#include "generic/callback.h" +#include "string/string.h" +#include "stream/stringstream.h" +#include "os/path.h" +#include "moduleobservers.h" + + +#define VFS_MAXDIRS 8 + +#if defined(WIN32) +#define PATH_MAX 260 +#endif + +#define gamemode_get GlobalRadiant().getGameMode + + + +// ============================================================================= +// Global variables + +Archive* OpenArchive(const char* name); + +struct archive_entry_t +{ + CopiedString name; + Archive* archive; + bool is_pakfile; +}; + +#include + +typedef std::list archives_t; + +static archives_t g_archives; +static char g_strDirs[VFS_MAXDIRS][PATH_MAX+1]; +static int g_numDirs; +static bool g_bUsePak = true; + +ModuleObservers g_observers; + +// ============================================================================= +// Static functions + +static void AddSlash (char *str) +{ + std::size_t n = strlen (str); + if (n > 0) + { + if (str[n-1] != '\\' && str[n-1] != '/') + { + globalErrorStream() << "WARNING: directory path does not end with separator: " << str << "\n"; + strcat (str, "/"); + } + } +} + +static void FixDOSName (char *src) +{ + if (src == 0 || strchr(src, '\\') == 0) + return; + + globalErrorStream() << "WARNING: invalid path separator '\\': " << src << "\n"; + + while (*src) + { + if (*src == '\\') + *src = '/'; + src++; + } +} + + + +const _QERArchiveTable* GetArchiveTable(ArchiveModules& archiveModules, const char* ext) +{ + StringOutputStream tmp(16); + tmp << LowerCase(ext); + return archiveModules.findModule(tmp.c_str()); +} +static void InitPakFile (ArchiveModules& archiveModules, const char *filename) +{ + const _QERArchiveTable* table = GetArchiveTable(archiveModules, path_get_extension(filename)); + + if(table != 0) + { + archive_entry_t entry; + entry.name = filename; + + entry.archive = table->m_pfnOpenArchive(filename); + entry.is_pakfile = true; + g_archives.push_back(entry); + globalOutputStream() << " pak file: " << filename << "\n"; + } +} + +inline void pathlist_prepend_unique(GSList*& pathlist, char* path) +{ + if(g_slist_find_custom(pathlist, path, (GCompareFunc)path_compare) == 0) + { + pathlist = g_slist_prepend(pathlist, path); + } + else + { + g_free(path); + } +} + +class DirectoryListVisitor : public Archive::Visitor +{ + GSList*& m_matches; + const char* m_directory; +public: + DirectoryListVisitor(GSList*& matches, const char* directory) + : m_matches(matches), m_directory(directory) + {} + void visit(const char* name) + { + const char* subname = path_make_relative(name, m_directory); + if(subname != name) + { + if(subname[0] == '/') + ++subname; + char* dir = g_strdup(subname); + char* last_char = dir + strlen(dir); + if(last_char != dir && *(--last_char) == '/') + *last_char = '\0'; + pathlist_prepend_unique(m_matches, dir); + } + } +}; + +class FileListVisitor : public Archive::Visitor +{ + GSList*& m_matches; + const char* m_directory; + const char* m_extension; +public: + FileListVisitor(GSList*& matches, const char* directory, const char* extension) + : m_matches(matches), m_directory(directory), m_extension(extension) + {} + void visit(const char* name) + { + const char* subname = path_make_relative(name, m_directory); + if(subname != name) + { + if(subname[0] == '/') + ++subname; + if(m_extension[0] == '*' || extension_equal(path_get_extension(subname), m_extension)) + pathlist_prepend_unique(m_matches, g_strdup (subname)); + } + } +}; + +static GSList* GetListInternal (const char *refdir, const char *ext, bool directories, std::size_t depth) +{ + GSList* files = 0; + + ASSERT_MESSAGE(refdir[strlen(refdir) - 1] == '/', "search path does not end in '/'"); + + if(directories) + { + for(archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) + { + DirectoryListVisitor visitor(files, refdir); + (*i).archive->forEachFile(Archive::VisitorFunc(visitor, Archive::eDirectories, depth), refdir); + } + } + else + { + for(archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) + { + FileListVisitor visitor(files, refdir, ext); + (*i).archive->forEachFile(Archive::VisitorFunc(visitor, Archive::eFiles, depth), refdir); + } + } + + files = g_slist_reverse(files); + + return files; +} + +inline int ascii_to_upper(int c) +{ + if (c >= 'a' && c <= 'z') + { + return c - ('a' - 'A'); + } + return c; +} + +/*! +This behaves identically to stricmp(a,b), except that ASCII chars +[\]^`_ come AFTER alphabet chars instead of before. This is because +it converts all alphabet chars to uppercase before comparison, +while stricmp converts them to lowercase. +*/ +static int string_compare_nocase_upper(const char* a, const char* b) +{ + for(;;) + { + int c1 = ascii_to_upper(*a++); + int c2 = ascii_to_upper(*b++); + + if (c1 < c2) + { + return -1; // a < b + } + if (c1 > c2) + { + return 1; // a > b + } + if(c1 == 0) + { + return 0; // a == b + } + } +} + +// Arnout: note - sort pakfiles in reverse order. This ensures that +// later pakfiles override earlier ones. This because the vfs module +// returns a filehandle to the first file it can find (while it should +// return the filehandle to the file in the most overriding pakfile, the +// last one in the list that is). + +//!\todo Analyse the code in rtcw/q3 to see which order it sorts pak files. +class PakLess +{ +public: + bool operator()(const CopiedString& self, const CopiedString& other) const + { + return string_compare_nocase_upper(self.c_str(), other.c_str()) > 0; + } +}; + +typedef std::set Archives; + +// ============================================================================= +// Global functions + +// reads all pak files from a dir +void InitDirectory(const char* directory, ArchiveModules& archiveModules) +{ + if (g_numDirs == (VFS_MAXDIRS-1)) + return; + + strncpy(g_strDirs[g_numDirs], directory, PATH_MAX); + g_strDirs[g_numDirs][PATH_MAX] = '\0'; + FixDOSName (g_strDirs[g_numDirs]); + AddSlash (g_strDirs[g_numDirs]); + + const char* path = g_strDirs[g_numDirs]; + + g_numDirs++; + + { + archive_entry_t entry; + entry.name = path; + entry.archive = OpenArchive(path); + entry.is_pakfile = false; + g_archives.push_back(entry); + } + + if (g_bUsePak) + { + GDir* dir = g_dir_open (path, 0, 0); + + if (dir != 0) + { + globalOutputStream() << "vfs directory: " << path << "\n"; + + const char* ignore_prefix = ""; + const char* override_prefix = ""; + + { + // See if we are in "sp" or "mp" mapping mode + const char* gamemode = gamemode_get(); + + if (strcmp (gamemode, "sp") == 0) + { + ignore_prefix = "mp_"; + override_prefix = "sp_"; + } + else if (strcmp (gamemode, "mp") == 0) + { + ignore_prefix = "sp_"; + override_prefix = "mp_"; + } + } + + Archives archives; + Archives archivesOverride; + for(;;) + { + const char* name = g_dir_read_name(dir); + if(name == 0) + break; + + const char *ext = strrchr (name, '.'); + if ((ext == 0) || *(++ext) == '\0' || GetArchiveTable(archiveModules, ext) == 0) + continue; + + // using the same kludge as in engine to ensure consistency + if(!string_empty(ignore_prefix) && strncmp(name, ignore_prefix, strlen(ignore_prefix)) == 0) + { + continue; + } + if(!string_empty(override_prefix) && strncmp(name, override_prefix, strlen(override_prefix)) == 0) + { + archivesOverride.insert(name); + continue; + } + + archives.insert(name); + } + + g_dir_close (dir); + + // add the entries to the vfs + for(Archives::iterator i = archivesOverride.begin(); i != archivesOverride.end(); ++i) + { + char filename[PATH_MAX]; + strcpy(filename, path); + strcat(filename, (*i).c_str()); + InitPakFile(archiveModules, filename); + } + for(Archives::iterator i = archives.begin(); i != archives.end(); ++i) + { + char filename[PATH_MAX]; + strcpy(filename, path); + strcat(filename, (*i).c_str()); + InitPakFile(archiveModules, filename); + } + } + else + { + globalErrorStream() << "vfs directory not found: " << path << "\n"; + } + } +} + +// frees all memory that we allocated +// FIXME TTimo this should be improved so that we can shutdown and restart the VFS without exiting Radiant? +// (for instance when modifying the project settings) +void Shutdown() +{ + for(archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) + { + (*i).archive->release(); + } + g_archives.clear(); + + g_numDirs = 0; +} + +#define VFS_SEARCH_PAK 0x1 +#define VFS_SEARCH_DIR 0x2 + +int GetFileCount (const char *filename, int flag) +{ + int count = 0; + char fixed[PATH_MAX+1]; + + strncpy(fixed, filename, PATH_MAX); + fixed[PATH_MAX] = '\0'; + FixDOSName (fixed); + + if(!flag) + flag = VFS_SEARCH_PAK | VFS_SEARCH_DIR; + + for(archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) + { + if((*i).is_pakfile && (flag & VFS_SEARCH_PAK) != 0 + || !(*i).is_pakfile && (flag & VFS_SEARCH_DIR) != 0) + { + if((*i).archive->containsFile(fixed)) + ++count; + } + } + + return count; +} + +ArchiveFile* OpenFile(const char* filename) +{ + ASSERT_MESSAGE(strchr(filename, '\\') == 0, "path contains invalid separator '\\': \"" << filename << "\""); + for(archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) + { + ArchiveFile* file = (*i).archive->openFile(filename); + if(file != 0) + { + return file; + } + } + + return 0; +} + +ArchiveTextFile* OpenTextFile(const char* filename) +{ + ASSERT_MESSAGE(strchr(filename, '\\') == 0, "path contains invalid separator '\\': \"" << filename << "\""); + for(archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) + { + ArchiveTextFile* file = (*i).archive->openTextFile(filename); + if(file != 0) + { + return file; + } + } + + return 0; +} + +// NOTE: when loading a file, you have to allocate one extra byte and set it to \0 +std::size_t LoadFile (const char *filename, void **bufferptr, int index) +{ + char fixed[PATH_MAX+1]; + + strncpy (fixed, filename, PATH_MAX); + fixed[PATH_MAX] = '\0'; + FixDOSName (fixed); + + ArchiveFile* file = OpenFile(fixed); + + if(file != 0) + { + *bufferptr = malloc (file->size()+1); + // we need to end the buffer with a 0 + ((char*) (*bufferptr))[file->size()] = 0; + + std::size_t length = file->getInputStream().read((InputStream::byte_type*)*bufferptr, file->size()); + file->release(); + return length; + } + + *bufferptr = 0; + return 0; +} + +void FreeFile (void *p) +{ + free(p); +} + +GSList* GetFileList (const char *dir, const char *ext, std::size_t depth) +{ + return GetListInternal (dir, ext, false, depth); +} + +GSList* GetDirList (const char *dir, std::size_t depth) +{ + return GetListInternal (dir, 0, true, depth); +} + +void ClearFileDirList (GSList **lst) +{ + while (*lst) + { + g_free ((*lst)->data); + *lst = g_slist_remove (*lst, (*lst)->data); + } +} + +const char* FindFile(const char* relative) +{ + for(archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) + { + if((*i).archive->containsFile(relative)) + { + return (*i).name.c_str(); + } + } + + return ""; +} + +const char* FindPath(const char* absolute) +{ + for(archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) + { + if(path_equal_n(absolute, (*i).name.c_str(), string_length((*i).name.c_str()))) + { + return (*i).name.c_str(); + } + } + + return ""; +} + + +class Quake3FileSystem : public VirtualFileSystem +{ +public: + void initDirectory(const char *path) + { + InitDirectory(path, FileSystemQ3API_getArchiveModules()); + } + void initialise() + { + globalOutputStream() << "filesystem initialised\n"; + g_observers.realise(); + } + void shutdown() + { + g_observers.unrealise(); + globalOutputStream() << "filesystem shutdown\n"; + Shutdown(); + } + + int getFileCount(const char *filename, int flags) + { + return GetFileCount(filename, flags); + } + ArchiveFile* openFile(const char* filename) + { + return OpenFile(filename); + } + ArchiveTextFile* openTextFile(const char* filename) + { + return OpenTextFile(filename); + } + std::size_t loadFile(const char *filename, void **buffer) + { + return LoadFile(filename, buffer, 0); + } + void freeFile(void *p) + { + FreeFile(p); + } + + void forEachDirectory(const char* basedir, const FileNameCallback& callback, std::size_t depth) + { + GSList* list = GetDirList(basedir, depth); + + for(GSList* i = list; i != 0; i = g_slist_next(i)) + { + callback(reinterpret_cast((*i).data)); + } + + ClearFileDirList(&list); + } + void forEachFile(const char* basedir, const char* extension, const FileNameCallback& callback, std::size_t depth) + { + GSList* list = GetFileList(basedir, extension, depth); + + for(GSList* i = list; i != 0; i = g_slist_next(i)) + { + const char* name = reinterpret_cast((*i).data); + if(extension_equal(path_get_extension(name), extension)) + { + callback(name); + } + } + + ClearFileDirList(&list); + } + GSList* getDirList(const char *basedir) + { + return GetDirList(basedir, 1); + } + GSList* getFileList(const char *basedir, const char *extension) + { + return GetFileList(basedir, extension, 1); + } + void clearFileDirList(GSList **lst) + { + ClearFileDirList(lst); + } + + const char* findFile(const char *name) + { + return FindFile(name); + } + const char* findRoot(const char *name) + { + return FindPath(name); + } + + void attach(ModuleObserver& observer) + { + g_observers.attach(observer); + } + void detach(ModuleObserver& observer) + { + g_observers.detach(observer); + } + + Archive* getArchive(const char* archiveName) + { + for(archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) + { + if((*i).is_pakfile) + { + if(path_equal((*i).name.c_str(), archiveName)) + { + return (*i).archive; + } + } + } + return 0; + } + void forEachArchive(const ArchiveNameCallback& callback) + { + for(archives_t::iterator i = g_archives.begin(); i != g_archives.end(); ++i) + { + if((*i).is_pakfile) + { + callback((*i).name.c_str()); + } + } + } +}; + +Quake3FileSystem g_Quake3FileSystem; + +void FileSystem_Init() +{ +} + +void FileSystem_Shutdown() +{ +} + +VirtualFileSystem& GetFileSystem() +{ + return g_Quake3FileSystem; +} diff --git a/plugins/vfspk3/vfs.h b/plugins/vfspk3/vfs.h new file mode 100644 index 00000000..3fe10791 --- /dev/null +++ b/plugins/vfspk3/vfs.h @@ -0,0 +1,39 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(INCLUDED_VFS_H) +#define INCLUDED_VFS_H + +void FileSystem_Init(); +void FileSystem_Shutdown(); +class VirtualFileSystem; +VirtualFileSystem& GetFileSystem(); + +#endif diff --git a/plugins/vfspk3/vfspk3.cpp b/plugins/vfspk3/vfspk3.cpp new file mode 100644 index 00000000..e7fca372 --- /dev/null +++ b/plugins/vfspk3/vfspk3.cpp @@ -0,0 +1,85 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "vfspk3.h" + +#include "qerplugin.h" +#include "iarchive.h" +#include "ifilesystem.h" + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/modulesmap.h" + +#include "vfs.h" + +class FileSystemDependencies : public GlobalRadiantModuleRef +{ + ArchiveModulesRef m_archive_modules; +public: + FileSystemDependencies() : + m_archive_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("archivetypes")) + { + } + ArchiveModules& getArchiveModules() + { + return m_archive_modules.get(); + } +}; + +class FileSystemQ3API +{ + VirtualFileSystem* m_filesystemq3; +public: + typedef VirtualFileSystem Type; + STRING_CONSTANT(Name, "*"); + + FileSystemQ3API() + { + FileSystem_Init(); + m_filesystemq3 = &GetFileSystem(); + } + ~FileSystemQ3API() + { + FileSystem_Shutdown(); + } + VirtualFileSystem* getTable() + { + return m_filesystemq3; + } +}; + +typedef SingletonModule FileSystemQ3Module; + +FileSystemQ3Module g_FileSystemQ3Module; + +ArchiveModules& FileSystemQ3API_getArchiveModules() +{ + return g_FileSystemQ3Module.getDependencies().getArchiveModules(); +} + + + +extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer& server) +{ + initialiseModule(server); + + g_FileSystemQ3Module.selfRegister(); +} diff --git a/plugins/vfspk3/vfspk3.h b/plugins/vfspk3/vfspk3.h new file mode 100644 index 00000000..8710fd13 --- /dev/null +++ b/plugins/vfspk3/vfspk3.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_VFSPK3_H) +#define INCLUDED_VFSPK3_H + +#endif diff --git a/plugins/vfspk3/vfspk3.proj b/plugins/vfspk3/vfspk3.proj new file mode 100644 index 0000000000000000000000000000000000000000..558e2e3b9100c365f843aa2175e1621c9c19a921 GIT binary patch literal 16384 zcmeHLZEPGz8Gdm>vuoeo5?PgkAkw4}mE+{jcju49iHMxgCPZyWnuIj{LAQImbM_|t z<()m-m&0mRAxe-cJ|xuqtg29nA604L4+v2N6hfPpG_({_NK(pYp%4|RQh(6!zWZU{ zrnTcsP(`Yp)85QHGw*ynJ2U&d12^2L0YDeP@-qM*!6f7WK7%>bbEyqip!!3%`VqW4&4%whT@~4`MoED`UJ1UI0MM zXsg!?XW(rkH^vcDVtg6=3f@4hj`{GW1LIzJ5ne%T9MhNXjVkuSOYjV06K&W%UB8B> z5i=3%iK^lM%kX`~4yUl+z+;FVK`fkVSogPZ0x=8I$$UlESvZcEox)y$2N82p*ze$e z#M~72dzeJbOJV20L~IhVJU$de*LhG8^D#Z&?*%x7m`Gu-!lw`m$Y?J70R|9j%;hQZ z8hi|~sTB4{xB;>06!s^06W`_06!vF0k60^({RK`T_W2a{S9l7snKrB!{st!yYqnv_ z;C1*K-cFrfxq6HqDoQS(zIv}bc3eLvPh$D1n}l={%PIf!u7|s%+h1C}mhSQ171q8BSsL)s6#x$-r}QN2Y7LwO75CuX*u;n9 zX4b$jU_WAN8>YiE@BqHcLx|z42!rT)7Mh6NmBLQJcC@`GsuKTa=SFsJ&{DXzbK@>t zc%R0#OAocyes1*r%&H%DoYDjDr+TYi*w`mLuwM%8!()R!Q z+;|lSx(Yd^Cp$MXy>Y$i4cbdgSv^J%?XC86V=s!rL$uMql^Yv7c9V49*1yY7$L9vc zRMcrmY!=$zG#(1qm)>Yin~vd5v%%pqs}2vbO@mc7GBQl@494pRhlbWyMn~DW&pqa8 z<9wDSCL?S(te2`^USDS1Fay@2%B&TpH&r(=dRleU;%s2TwfR6`+U|hon^RiA2X+QW z_GyNxIl+MG1l+G{Iv+5pwrGlg+ocKSxq?+Ir7{x%S}@1;ZOvltls_G?G7GetFpqLp zWp2GLI6|`+V0Xg`%sayw3MfO(4JJ0=!+lYl_RWApCBnvix8cRmS{OHK#c9_+VwgU( zTH7p(g(I0|{j3BL=4G!aFhuwswRUElmW} z%|Ju=ICb4>7@U>xw|G-?BtdLb6mG-Uc@f*D=*}JmiuAu z+quVckL2tgwdd}heLZ*d+|skIdsX+1-OIY)k>8Xr%ID=L#`vq zl=sM6Li&pIpme`obl(lJR2zmVykgWspW`~3&DTK`hYi4R8TRX% z#IicV7C(Ii6OfP}i{7*Mp@W@h1dEdj02A8XmF zS9f;uU91b%|k<*8QZ(#GwQ(|`*+@YV8@<)ySE)+aY%Qvr91Lp zO1kH5-Z+YOjClvhAu4W7*JPh$H_wf2OE(Jm!N`rQlr{~rN@;Xs_d-CmgnOV`8Y*|s zqFFfCblZLX>t-2heJWDxzH29L$n2a}r=oeSj(*a7Mn|^`IgI>N-F4U*tGH;($j|X^J)xLV;y@oc&F7*ny6Ar7E>EMoc8aBkc zk=8Vy30>JMY>zhTYQn?Uh$D3jboQ6LiVZE$I;qb+&Gbbl7R2n_CB%})HEwm%hs}l1 z@NQtd6Qk&CK~LTc(A)3CQs}~n5Q_*;muD1P;vsIi<{^(4FAE) m!z^SBWDH~sWDH~sWDH~sWDH~sWDH~sWDH~sWDI<;8Tc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/q3map2build.sh b/q3map2build.sh new file mode 100644 index 00000000..fc1788a2 --- /dev/null +++ b/q3map2build.sh @@ -0,0 +1,152 @@ +if [ -n "$CROSS" ]; then + CFLAGS="-I$HOME/mingw/include" + LDFLAGS="-L$HOME/mingw/lib -lws2_32 -lole32 -lintl -liconv" + XML2_CFLAGS="-I$HOME/mingw/include/libxml2" + GLIB2_CFLAGS="-I$HOME/mingw/include/glib-2.0 -I$HOME/mingw/lib/glib-2.0/include" + CC=i586-mingw32msvc-gcc + CXX=i586-mingw32msvc-g++ + netlib=libs/l_net/l_net_wins.c + SUFFIX=.exe +else + CFLAGS="" + LDFLAGS="-lpthread" + XML2_CFLAGS="-I/usr/include/libxml2" + GLIB2_CFLAGS="-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include" + CC=gcc + CXX=g++ + netlib=libs/l_net/l_net_berkley.c + SUFFIX= +fi + +LIBS="-lpng -lmhash -lxml2 -lglib-2.0" +CFLAGS_COMMON="-O3 -ffast-math -fno-unsafe-math-optimizations -fno-strict-aliasing -DQ_NO_STLPORT" # -fvisibility=hidden +CFLAGS_LIBS="$CFLAGS $CFLAGS_COMMON $XML2_CFLAGS $GLIB2_CFLAGS -Iinclude -Ilibs -Ilibs/jpeg6" +CFLAGS_Q3MAP2="-Itools/quake3/common $CFLAGS_LIBS" + +temp="obj/" +mkdir -p $temp +OBJECTS= +compile() +{ + eval sourcefile=\$\{$#\} + objectfile=${sourcefile%.*} + objectfile=`echo "$objectfile" | tr / -` + objectfile="$temp/$objectfile.o" + OBJECTS="$OBJECTS $objectfile" + if ! [ -f "$objectfile" ]; then + echo "$1 $sourcefile" + "$@" -c -o $objectfile || exit 1 + fi +} +link() +{ + out=$1 + shift + linker=$1 + shift + echo "$linker $out" + + "$linker" $OBJECTS "$@" -o $out$SUFFIX || exit 1 +} + +compile $CXX $CFLAGS_LIBS libs/cmdlib/cmdlib.cpp +compile $CC $CFLAGS_LIBS libs/mathlib/bbox.c +compile $CC $CFLAGS_LIBS libs/mathlib/m4x4.c +compile $CC $CFLAGS_LIBS libs/mathlib/mathlib.c +compile $CC $CFLAGS_LIBS libs/mathlib/ray.c +compile $CC $CFLAGS_LIBS libs/l_net/l_net.c +compile $CC $CFLAGS_LIBS $netlib +compile $CC $CFLAGS_LIBS libs/ddslib/ddslib.c +compile $CC $CFLAGS_LIBS libs/picomodel/picointernal.c +compile $CC $CFLAGS_LIBS libs/picomodel/picomodel.c +compile $CC $CFLAGS_LIBS libs/picomodel/picomodules.c +compile $CC $CFLAGS_LIBS libs/picomodel/pm_3ds.c +compile $CC $CFLAGS_LIBS libs/picomodel/pm_ase.c +compile $CC $CFLAGS_LIBS libs/picomodel/pm_fm.c +compile $CC $CFLAGS_LIBS libs/picomodel/pm_lwo.c +compile $CC $CFLAGS_LIBS libs/picomodel/pm_md2.c +compile $CC $CFLAGS_LIBS libs/picomodel/pm_md3.c +compile $CC $CFLAGS_LIBS libs/picomodel/pm_mdc.c +compile $CC $CFLAGS_LIBS libs/picomodel/pm_ms3d.c +compile $CC $CFLAGS_LIBS libs/picomodel/pm_obj.c +compile $CC $CFLAGS_LIBS libs/picomodel/pm_terrain.c +compile $CC $CFLAGS_LIBS libs/picomodel/lwo/clip.c +compile $CC $CFLAGS_LIBS libs/picomodel/lwo/envelope.c +compile $CC $CFLAGS_LIBS libs/picomodel/lwo/list.c +compile $CC $CFLAGS_LIBS libs/picomodel/lwo/lwio.c +compile $CC $CFLAGS_LIBS libs/picomodel/lwo/lwo2.c +compile $CC $CFLAGS_LIBS libs/picomodel/lwo/lwob.c +compile $CC $CFLAGS_LIBS libs/picomodel/lwo/pntspols.c +compile $CC $CFLAGS_LIBS libs/picomodel/lwo/surface.c +compile $CC $CFLAGS_LIBS libs/picomodel/lwo/vecmath.c +compile $CC $CFLAGS_LIBS libs/picomodel/lwo/vmap.c +compile $CC $CFLAGS_LIBS libs/md5lib/md5lib.c +compile $CXX $CFLAGS_LIBS libs/jpeg6/jcomapi.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jdapimin.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jdapistd.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jdatasrc.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jdcoefct.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jdcolor.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jddctmgr.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jdhuff.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jdinput.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jdmainct.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jdmarker.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jdmaster.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jdpostct.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jdsample.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jdtrans.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jerror.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jfdctflt.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jidctflt.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jmemmgr.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jmemnobs.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jpgload.cpp +compile $CXX $CFLAGS_LIBS libs/jpeg6/jutils.cpp +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/bspfile_abstract.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/bspfile_ibsp.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/bspfile_rbsp.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/image.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/main.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/mesh.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/model.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/path_init.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/shaders.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/surface_extra.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/common/cmdlib.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/common/imagelib.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/common/inout.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/common/mutex.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/common/polylib.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/common/scriplib.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/common/threads.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/common/unzip.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/common/vfs.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/brush.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/brush_primit.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/bsp.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/decals.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/facebsp.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/fog.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/leakfile.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/map.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/patch.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/portals.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/prtfile.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/surface.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/surface_foliage.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/surface_fur.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/surface_meta.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/tjunction.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/tree.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/writebsp.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/light.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/light_bounce.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/light_trace.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/light_ydnar.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/lightmaps_ydnar.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/vis.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/visflow.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/convert_ase.c +compile $CC $CFLAGS_Q3MAP2 tools/quake3/q3map2/convert_map.c +link q3map2 $CXX $LIBS $LDFLAGS diff --git a/radiant/.cvsignore b/radiant/.cvsignore new file mode 100644 index 00000000..72b49b5f --- /dev/null +++ b/radiant/.cvsignore @@ -0,0 +1,16 @@ +Debug +Release +radiant +*.d +*.o +*.opt +*.ncb +*.BAK +*.00* +*.plg +radiant +q3map +unnamed.map +.snprj +tools +*.gcse diff --git a/radiant/GtkRadiant.vcproj b/radiant/GtkRadiant.vcproj new file mode 100644 index 00000000..366a4187 --- /dev/null +++ b/radiant/GtkRadiant.vcproj @@ -0,0 +1,961 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/radiant/autosave.cpp b/radiant/autosave.cpp new file mode 100644 index 00000000..febe95af --- /dev/null +++ b/radiant/autosave.cpp @@ -0,0 +1,224 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "autosave.h" + +#include "os/file.h" +#include "os/path.h" +#include "cmdlib.h" +#include "stream/stringstream.h" +#include "gtkutil/messagebox.h" +#include "scenelib.h" +#include "mapfile.h" + +#include "map.h" +#include "mainframe.h" +#include "qe3.h" +#include "preferences.h" + + +#if defined(WIN32) +#define PATH_MAX 260 +#endif + + +bool DoesFileExist(const char* name, std::size_t& size) +{ + if(file_exists(name)) + { + size += file_size(name); + return true; + } + return false; +} + +void Map_Snapshot() +{ + // we need to do the following + // 1. make sure the snapshot directory exists (create it if it doesn't) + // 2. find out what the lastest save is based on number + // 3. inc that and save the map + const char* path = Map_Name(g_map); + const char* name = path_get_filename_start(path); + + StringOutputStream snapshotsDir(256); + snapshotsDir << StringRange(path, name) << "snapshots"; + + if(file_exists(snapshotsDir.c_str()) || Q_mkdir(snapshotsDir.c_str())) + { + std::size_t lSize = 0; + StringOutputStream strNewPath(256); + strNewPath << snapshotsDir.c_str() << '/' << name; + + StringOutputStream snapshotFilename(256); + + for(int nCount = 0; ; ++nCount) + { + // The original map's filename is "/." + // The snapshot's filename will be "/snapshots/.." + const char* end = path_get_filename_base_end(strNewPath.c_str()); + snapshotFilename << StringRange(strNewPath.c_str(), end) << '.' << nCount << end; + + if(!DoesFileExist(snapshotFilename.c_str(), lSize)) + { + break; + } + + snapshotFilename.clear(); + } + + // save in the next available slot + Map_SaveFile(snapshotFilename.c_str()); + + if (lSize > 50 * 1024 * 1024) // total size of saves > 50 mb + { + globalOutputStream() << "The snapshot files in " << snapshotsDir.c_str() << " total more than 50 megabytes. You might consider cleaning up."; + } + } + else + { + StringOutputStream strMsg(256); + strMsg << "Snapshot save failed.. unabled to create directory\n" << snapshotsDir.c_str(); + gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), strMsg.c_str()); + } +} +/* +=============== +QE_CheckAutoSave + +If five minutes have passed since making a change +and the map hasn't been saved, save it out. +=============== +*/ + +bool g_AutoSave_Enabled = true; +int m_AutoSave_Frequency = 5; +bool g_SnapShots_Enabled = false; + +namespace +{ + time_t s_start = 0; + std::size_t s_changes = 0; +} + +void AutoSave_clear() +{ + s_changes = 0; +} + +scene::Node& Map_Node() +{ + return GlobalSceneGraph().root(); +} + +void QE_CheckAutoSave( void ) +{ + if(!Map_Valid(g_map) || !ScreenUpdates_Enabled()) + { + return; + } + + time_t now; + time(&now); + + if(s_start == 0 || s_changes == Node_getMapFile(Map_Node())->changes()) + { + s_start = now; + } + + if((now - s_start) > (60 * m_AutoSave_Frequency)) + { + s_start = now; + s_changes = Node_getMapFile(Map_Node())->changes(); + + if (g_AutoSave_Enabled) + { + const char* strMsg = g_SnapShots_Enabled ? "Autosaving snapshot..." : "Autosaving..."; + globalOutputStream() << strMsg << "\n"; + //Sys_Status(strMsg); + + // only snapshot if not working on a default map + if (g_SnapShots_Enabled && !Map_Unnamed(g_map)) + { + Map_Snapshot(); + } + else + { + if(Map_Unnamed(g_map)) + { + StringOutputStream autosave(256); + autosave << g_qeglobals.m_userGamePath.c_str() << "maps/"; + Q_mkdir(autosave.c_str()); + autosave << "autosave.map"; + Map_SaveFile(autosave.c_str()); + } + else + { + const char* name = Map_Name(g_map); + const char* extension = path_get_filename_base_end(name); + StringOutputStream autosave(256); + autosave << StringRange(name, extension) << ".autosave" << extension; + Map_SaveFile(autosave.c_str()); + } + } + } + else + { + globalOutputStream() << "Autosave skipped...\n"; + //Sys_Status ("Autosave skipped..."); + } + } +} + +void Autosave_constructPreferences(PreferencesPage& page) +{ + GtkWidget* autosave_enabled = page.appendCheckBox("Autosave", "Enable Autosave", g_AutoSave_Enabled); + GtkWidget* autosave_frequency = page.appendSpinner("Autosave Frequency (minutes)", m_AutoSave_Frequency, 1, 1, 60); + Widget_connectToggleDependency(autosave_frequency, autosave_enabled); + page.appendCheckBox("", "Save Snapshots", g_SnapShots_Enabled); +} +void Autosave_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Autosave", "Autosave Preferences")); + Autosave_constructPreferences(page); +} +void Autosave_registerPreferencesPage() +{ + PreferencesDialog_addSettingsPage(FreeCaller1()); +} + + +#include "preferencesystem.h" +#include "stringio.h" + +void Autosave_Construct() +{ + GlobalPreferenceSystem().registerPreference("Autosave", BoolImportStringCaller(g_AutoSave_Enabled), BoolExportStringCaller(g_AutoSave_Enabled)); + GlobalPreferenceSystem().registerPreference("AutosaveMinutes", IntImportStringCaller(m_AutoSave_Frequency), IntExportStringCaller(m_AutoSave_Frequency)); + GlobalPreferenceSystem().registerPreference("Snapshots", BoolImportStringCaller(g_SnapShots_Enabled), BoolExportStringCaller(g_SnapShots_Enabled)); + + Autosave_registerPreferencesPage(); +} + +void Autosave_Destroy() +{ +} + diff --git a/radiant/autosave.h b/radiant/autosave.h new file mode 100644 index 00000000..730ef2e1 --- /dev/null +++ b/radiant/autosave.h @@ -0,0 +1,35 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_AUTOSAVE_H) +#define INCLUDED_AUTOSAVE_H + +extern bool g_SnapShots_Enabled; + +void AutoSave_clear(); +void QE_CheckAutoSave( void ); +void Map_Snapshot(); + +void Autosave_Construct(); +void Autosave_Destroy(); + + +#endif diff --git a/radiant/brush.cpp b/radiant/brush.cpp new file mode 100644 index 00000000..d2f217b3 --- /dev/null +++ b/radiant/brush.cpp @@ -0,0 +1,419 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "brush.h" +#include "signal/signal.h" + +Signal0 g_brushTextureChangedCallbacks; + +void Brush_addTextureChangedCallback(const SignalHandler& handler) +{ + g_brushTextureChangedCallbacks.connectLast(handler); +} + +void Brush_textureChanged() +{ + g_brushTextureChangedCallbacks(); +} + +QuantiseFunc Face::m_quantise; +EBrushType Face::m_type; +EBrushType FacePlane::m_type; +bool g_brush_texturelock_enabled = false; + +EBrushType Brush::m_type; +double Brush::m_maxWorldCoord = 0; +Shader* Brush::m_state_point; +Shader* BrushClipPlane::m_state = 0; +Shader* BrushInstance::m_state_selpoint; +Counter* BrushInstance::m_counter = 0; + +FaceInstanceSet g_SelectedFaceInstances; + + +struct SListNode +{ + SListNode* m_next; +}; + +class ProximalVertex +{ +public: + const SListNode* m_vertices; + + ProximalVertex(const SListNode* next) + : m_vertices(next) + { + } + + bool operator<(const ProximalVertex& other) const + { + if(!(operator==(other))) + { + return m_vertices < other.m_vertices; + } + return false; + } + bool operator==(const ProximalVertex& other) const + { + const SListNode* v = m_vertices; + std::size_t DEBUG_LOOP = 0; + do + { + if(v == other.m_vertices) + return true; + v = v->m_next; + //ASSERT_MESSAGE(DEBUG_LOOP < c_brush_maxFaces, "infinite loop"); + if(!(DEBUG_LOOP < c_brush_maxFaces)) + { + break; + } + ++DEBUG_LOOP; + } + while(v != m_vertices); + return false; + } +}; + +typedef Array ProximalVertexArray; +std::size_t ProximalVertexArray_index(const ProximalVertexArray& array, const ProximalVertex& vertex) +{ + return vertex.m_vertices - array.data(); +} + + + +inline bool Brush_isBounded(const Brush& brush) +{ + for(Brush::const_iterator i = brush.begin(); i != brush.end(); ++i) + { + if(!(*i)->is_bounded()) + { + return false; + } + } + return true; +} + +void Brush::buildBRep() +{ + bool degenerate = buildWindings(); + + std::size_t faces_size = 0; + std::size_t faceVerticesCount = 0; + for(Faces::const_iterator i = m_faces.begin(); i != m_faces.end(); ++i) + { + if((*i)->contributes()) + { + ++faces_size; + } + faceVerticesCount += (*i)->getWinding().numpoints; + } + + if(degenerate || faces_size < 4 || faceVerticesCount != (faceVerticesCount>>1)<<1) // sum of vertices for each face of a valid polyhedron is always even + { + m_uniqueVertexPoints.resize(0); + + vertex_clear(); + edge_clear(); + + m_edge_indices.resize(0); + m_edge_faces.resize(0); + + m_faceCentroidPoints.resize(0); + m_uniqueEdgePoints.resize(0); + m_uniqueVertexPoints.resize(0); + + for(Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i) + { + (*i)->getWinding().resize(0); + } + } + else + { + { + typedef std::vector FaceVertices; + FaceVertices faceVertices; + faceVertices.reserve(faceVerticesCount); + + { + for(std::size_t i = 0; i != m_faces.size(); ++i) + { + for(std::size_t j = 0; j < m_faces[i]->getWinding().numpoints; ++j) + { + faceVertices.push_back(FaceVertexId(i, j)); + } + } + } + + IndexBuffer uniqueEdgeIndices; + typedef VertexBuffer UniqueEdges; + UniqueEdges uniqueEdges; + + uniqueEdgeIndices.reserve(faceVertices.size()); + uniqueEdges.reserve(faceVertices.size()); + + { + ProximalVertexArray edgePairs; + edgePairs.resize(faceVertices.size()); + + { + for(std::size_t i=0; i inserter(uniqueEdges); + for(ProximalVertexArray::iterator i = edgePairs.begin(); i != edgePairs.end(); ++i) + { + uniqueEdgeIndices.insert(inserter.insert(ProximalVertex(&(*i)))); + } + } + + { + edge_clear(); + m_select_edges.reserve(uniqueEdges.size()); + for(UniqueEdges::iterator i = uniqueEdges.begin(); i != uniqueEdges.end(); ++i) + { + edge_push_back(faceVertices[ProximalVertexArray_index(edgePairs, *i)]); + } + } + + { + m_edge_faces.resize(uniqueEdges.size()); + for(std::size_t i=0; igetWinding()[faceVertex.getVertex()].adjacent); + } + } + + { + m_uniqueEdgePoints.resize(uniqueEdges.size()); + for(std::size_t i=0; igetWinding(); + Vector3 edge = vector3_mid(w[faceVertex.getVertex()].vertex, w[Winding_next(w, faceVertex.getVertex())].vertex); + m_uniqueEdgePoints[i] = pointvertex_for_windingpoint(edge, colour_vertex); + } + } + + } + + + IndexBuffer uniqueVertexIndices; + typedef VertexBuffer UniqueVertices; + UniqueVertices uniqueVertices; + + uniqueVertexIndices.reserve(faceVertices.size()); + uniqueVertices.reserve(faceVertices.size()); + + { + ProximalVertexArray vertexRings; + vertexRings.resize(faceVertices.size()); + + { + for(std::size_t i=0; i inserter(uniqueVertices); + for(ProximalVertexArray::iterator i = vertexRings.begin(); i != vertexRings.end(); ++i) + { + uniqueVertexIndices.insert(inserter.insert(ProximalVertex(&(*i)))); + } + } + + { + vertex_clear(); + m_select_vertices.reserve(uniqueVertices.size()); + for(UniqueVertices::iterator i = uniqueVertices.begin(); i != uniqueVertices.end(); ++i) + { + vertex_push_back(faceVertices[ProximalVertexArray_index(vertexRings, (*i))]); + } + } + + { + m_uniqueVertexPoints.resize(uniqueVertices.size()); + for(std::size_t i=0; igetWinding(); + m_uniqueVertexPoints[i] = pointvertex_for_windingpoint(winding[faceVertex.getVertex()].vertex, colour_vertex); + } + } + } + + if((uniqueVertices.size() + faces_size) - uniqueEdges.size() != 2) + { + globalErrorStream() << "Final B-Rep: inconsistent vertex count\n"; + } + +#if BRUSH_CONNECTIVITY_DEBUG + if((uniqueVertices.size() + faces_size) - uniqueEdges.size() != 2) + { + for(Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i) + { + std::size_t faceIndex = std::distance(m_faces.begin(), i); + + if(!(*i)->contributes()) + { + globalOutputStream() << "face: " << Unsigned(faceIndex) << " does not contribute\n"; + } + + Winding_printConnectivity((*i)->getWinding()); + } + } +#endif + + // edge-index list for wireframe rendering + { + m_edge_indices.resize(uniqueEdgeIndices.size()); + + for(std::size_t i=0, count=0; igetWinding(); + for(std::size_t j = 0; j < winding.numpoints; ++j) + { + const RenderIndex edge_index = uniqueEdgeIndices[count+j]; + + m_edge_indices[edge_index].first = uniqueVertexIndices[count + j]; + m_edge_indices[edge_index].second = uniqueVertexIndices[count + Winding_next(winding, j)]; + } + count += winding.numpoints; + } + } + } + + { + m_faceCentroidPoints.resize(m_faces.size()); + for(std::size_t i=0; iconstruct_centroid(); + m_faceCentroidPoints[i] = pointvertex_for_windingpoint(m_faces[i]->centroid(), colour_vertex); + } + } + } +} + + +class FaceFilterWrapper : public Filter +{ + FaceFilter& m_filter; + bool m_active; + bool m_invert; +public: + FaceFilterWrapper(FaceFilter& filter, bool invert) : + m_filter(filter), + m_invert(invert) + { + } + void setActive(bool active) + { + m_active = active; + } + bool active() + { + return m_active; + } + bool filter(const Face& face) + { + return m_invert ^ m_filter.filter(face); + } +}; + + +typedef std::list FaceFilters; +FaceFilters g_faceFilters; + +void add_face_filter(FaceFilter& filter, int mask, bool invert) +{ + g_faceFilters.push_back(FaceFilterWrapper(filter, invert)); + GlobalFilterSystem().addFilter(g_faceFilters.back(), mask); +} + +bool face_filtered(Face& face) +{ + for(FaceFilters::iterator i = g_faceFilters.begin(); i != g_faceFilters.end(); ++i) + { + if((*i).active() && (*i).filter(face)) + { + return true; + } + } + return false; +} + + +class BrushFilterWrapper : public Filter +{ + bool m_active; + bool m_invert; + BrushFilter& m_filter; +public: + BrushFilterWrapper(BrushFilter& filter, bool invert) : m_invert(invert), m_filter(filter) + { + } + void setActive(bool active) + { + m_active = active; + } + bool active() + { + return m_active; + } + bool filter(const Brush& brush) + { + return m_invert ^ m_filter.filter(brush); + } +}; + + +typedef std::list BrushFilters; +BrushFilters g_brushFilters; + +void add_brush_filter(BrushFilter& filter, int mask, bool invert) +{ + g_brushFilters.push_back(BrushFilterWrapper(filter, invert)); + GlobalFilterSystem().addFilter(g_brushFilters.back(), mask); +} + +bool brush_filtered(Brush& brush) +{ + for(BrushFilters::iterator i = g_brushFilters.begin(); i != g_brushFilters.end(); ++i) + { + if((*i).active() && (*i).filter(brush)) + { + return true; + } + } + return false; +} + + diff --git a/radiant/brush.h b/radiant/brush.h new file mode 100644 index 00000000..851a9836 --- /dev/null +++ b/radiant/brush.h @@ -0,0 +1,4218 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_BRUSH_H) +#define INCLUDED_BRUSH_H + +/// \file +/// \brief The brush primitive. +/// +/// A collection of planes that define a convex polyhedron. +/// The Boundary-Representation of this primitive is a manifold polygonal mesh. +/// Each face polygon is represented by a list of vertices in a \c Winding. +/// Each vertex is associated with another face that is adjacent to the edge +/// formed by itself and the next vertex in the winding. This information can +/// be used to find edge-pairs and vertex-rings. + + +#include "debugging/debugging.h" + +#include "itexdef.h" +#include "iundo.h" +#include "iselection.h" +#include "irender.h" +#include "imap.h" +#include "ibrush.h" +#include "igl.h" +#include "ifilter.h" +#include "nameable.h" +#include "moduleobserver.h" + +#include + +#include "cullable.h" +#include "renderable.h" +#include "selectable.h" +#include "editable.h" +#include "mapfile.h" + +#include "math/frustum.h" +#include "selectionlib.h" +#include "render.h" +#include "texturelib.h" +#include "container/container.h" +#include "generic/bitfield.h" +#include "signal/signalfwd.h" + +#include "winding.h" +#include "brush_primit.h" + +const unsigned int BRUSH_DETAIL_FLAG = 27; +const unsigned int BRUSH_DETAIL_MASK = (1 << BRUSH_DETAIL_FLAG); + +enum EBrushType +{ + eBrushTypeQuake, + eBrushTypeQuake2, + eBrushTypeQuake3, + eBrushTypeQuake3BP, + eBrushTypeDoom3, + eBrushTypeQuake4, + eBrushTypeHalfLife, +}; + + +#define BRUSH_CONNECTIVITY_DEBUG 0 +#define BRUSH_DEGENERATE_DEBUG 0 + +template +inline TextOuputStreamType& ostream_write(TextOuputStreamType& ostream, const Matrix4& m) +{ + return ostream << "(" << m[0] << " " << m[1] << " " << m[2] << " " << m[3] << ", " + << m[4] << " " << m[5] << " " << m[6] << " " << m[7] << ", " + << m[8] << " " << m[9] << " " << m[10] << " " << m[11] << ", " + << m[12] << " " << m[13] << " " << m[14] << " " << m[15] << ")"; +} + +inline void print_vector3(const Vector3& v) +{ + globalOutputStream() << "( " << v.x() << " " << v.y() << " " << v.z() << " )\n"; +} + +inline void print_3x3(const Matrix4& m) +{ + globalOutputStream() << "( " << m.xx() << " " << m.xy() << " " << m.xz() << " ) " + << "( " << m.yx() << " " << m.yy() << " " << m.yz() << " ) " + << "( " << m.zx() << " " << m.zy() << " " << m.zz() << " )\n"; +} + + + +inline bool texdef_sane(const texdef_t& texdef) +{ + return fabs(texdef.shift[0]) < (1 << 16) + && fabs(texdef.shift[1]) < (1 << 16); +} + +inline void Winding_DrawWireframe(const Winding& winding) +{ + glVertexPointer(3, GL_FLOAT, sizeof(WindingVertex), &winding.points.data()->vertex); + glDrawArrays(GL_LINE_LOOP, 0, GLsizei(winding.numpoints)); +} + +inline void Winding_Draw(const Winding& winding, const Vector3& normal, RenderStateFlags state) +{ + glVertexPointer(3, GL_FLOAT, sizeof(WindingVertex), &winding.points.data()->vertex); + + if((state & RENDER_BUMP) != 0) + { + Vector3 normals[c_brush_maxFaces]; + typedef Vector3* Vector3Iter; + for(Vector3Iter i = normals, end = normals + winding.numpoints; i != end; ++i) + { + *i = normal; + } + if(GlobalShaderCache().useShaderLanguage()) + { + glNormalPointer(GL_FLOAT, sizeof(Vector3), normals); + glVertexAttribPointerARB(c_attr_TexCoord0, 2, GL_FLOAT, 0, sizeof(WindingVertex), &winding.points.data()->texcoord); + glVertexAttribPointerARB(c_attr_Tangent, 3, GL_FLOAT, 0, sizeof(WindingVertex), &winding.points.data()->tangent); + glVertexAttribPointerARB(c_attr_Binormal, 3, GL_FLOAT, 0, sizeof(WindingVertex), &winding.points.data()->bitangent); + } + else + { + glVertexAttribPointerARB(11, 3, GL_FLOAT, 0, sizeof(Vector3), normals); + glVertexAttribPointerARB(8, 2, GL_FLOAT, 0, sizeof(WindingVertex), &winding.points.data()->texcoord); + glVertexAttribPointerARB(9, 3, GL_FLOAT, 0, sizeof(WindingVertex), &winding.points.data()->tangent); + glVertexAttribPointerARB(10, 3, GL_FLOAT, 0, sizeof(WindingVertex), &winding.points.data()->bitangent); + } + } + else + { + if (state & RENDER_LIGHTING) + { + Vector3 normals[c_brush_maxFaces]; + typedef Vector3* Vector3Iter; + for(Vector3Iter i = normals, last = normals + winding.numpoints; i != last; ++i) + { + *i = normal; + } + glNormalPointer(GL_FLOAT, sizeof(Vector3), normals); + } + + if (state & RENDER_TEXTURE) + { + glTexCoordPointer(2, GL_FLOAT, sizeof(WindingVertex), &winding.points.data()->texcoord); + } + } +#if 0 + if (state & RENDER_FILL) + { + glDrawArrays(GL_TRIANGLE_FAN, 0, GLsizei(winding.numpoints)); + } + else + { + glDrawArrays(GL_LINE_LOOP, 0, GLsizei(winding.numpoints)); + } +#else + glDrawArrays(GL_POLYGON, 0, GLsizei(winding.numpoints)); +#endif + +#if 0 + const Winding& winding = winding; + + if(state & RENDER_FILL) + { + glBegin(GL_POLYGON); + } + else + { + glBegin(GL_LINE_LOOP); + } + + if (state & RENDER_LIGHTING) + glNormal3fv(normal); + + for(int i = 0; i < winding.numpoints; ++i) + { + if (state & RENDER_TEXTURE) + glTexCoord2fv(&winding.points[i][3]); + glVertex3fv(winding.points[i]); + } + glEnd(); +#endif +} + + +#include "shaderlib.h" + +typedef DoubleVector3 PlanePoints[3]; + +inline bool planepts_equal(const PlanePoints planepts, const PlanePoints other) +{ + return planepts[0] == other[0] && planepts[1] == other[1] && planepts[2] == other[2]; +} + +inline void planepts_assign(PlanePoints planepts, const PlanePoints other) +{ + planepts[0] = other[0]; + planepts[1] = other[1]; + planepts[2] = other[2]; +} + +inline void planepts_quantise(PlanePoints planepts, double snap) +{ + vector3_snap(planepts[0], snap); + vector3_snap(planepts[1], snap); + vector3_snap(planepts[2], snap); +} + +inline float vector3_max_component(const Vector3& vec3) +{ + return std::max(fabsf(vec3[0]), std::max(fabsf(vec3[1]), fabsf(vec3[2]))); +} + +inline void edge_snap(Vector3& edge, double snap) +{ + float scale = static_cast(ceil(fabs(snap / vector3_max_component(edge)))); + if(scale > 0.0f) + { + vector3_scale(edge, scale); + } + vector3_snap(edge, snap); +} + +inline void planepts_snap(PlanePoints planepts, double snap) +{ + Vector3 edge01(vector3_subtracted(planepts[1], planepts[0])); + Vector3 edge12(vector3_subtracted(planepts[2], planepts[1])); + Vector3 edge20(vector3_subtracted(planepts[0], planepts[2])); + + double length_squared_01 = vector3_dot(edge01, edge01); + double length_squared_12 = vector3_dot(edge12, edge12); + double length_squared_20 = vector3_dot(edge20, edge20); + + vector3_snap(planepts[0], snap); + + if(length_squared_01 < length_squared_12) + { + if(length_squared_12 < length_squared_20) + { + edge_snap(edge01, snap); + edge_snap(edge12, snap); + planepts[1] = vector3_added(planepts[0], edge01); + planepts[2] = vector3_added(planepts[1], edge12); + } + else + { + edge_snap(edge20, snap); + edge_snap(edge01, snap); + planepts[1] = vector3_added(planepts[0], edge20); + planepts[2] = vector3_added(planepts[1], edge01); + } + } + else + { + if(length_squared_01 < length_squared_20) + { + edge_snap(edge01, snap); + edge_snap(edge12, snap); + planepts[1] = vector3_added(planepts[0], edge01); + planepts[2] = vector3_added(planepts[1], edge12); + } + else + { + edge_snap(edge12, snap); + edge_snap(edge20, snap); + planepts[1] = vector3_added(planepts[0], edge12); + planepts[2] = vector3_added(planepts[1], edge20); + } + } +} + +inline PointVertex pointvertex_for_planept(const DoubleVector3& point, const Colour4b& colour) +{ + return PointVertex( + Vertex3f( + static_cast(point.x()), + static_cast(point.y()), + static_cast(point.z()) + ), + colour + ); +} + +inline PointVertex pointvertex_for_windingpoint(const Vector3& point, const Colour4b& colour) +{ + return PointVertex( + vertex3f_for_vector3(point), + colour + ); +} + +inline bool check_plane_is_integer(const PlanePoints& planePoints) +{ + return !float_is_integer(planePoints[0][0]) + || !float_is_integer(planePoints[0][1]) + || !float_is_integer(planePoints[0][2]) + || !float_is_integer(planePoints[1][0]) + || !float_is_integer(planePoints[1][1]) + || !float_is_integer(planePoints[1][2]) + || !float_is_integer(planePoints[2][0]) + || !float_is_integer(planePoints[2][1]) + || !float_is_integer(planePoints[2][2]); +} + +inline void brush_check_shader(const char* name) +{ + if(!shader_valid(name)) + { + globalErrorStream() << "brush face has invalid texture name: '" << name << "'\n"; + } +} + +class FaceShaderObserver +{ +public: + virtual void realiseShader() = 0; + virtual void unrealiseShader() = 0; +}; + +class FaceShaderObserverRealise +{ +public: + void operator()(FaceShaderObserver& observer) const + { + observer.realiseShader(); + } +}; + +class FaceShaderObserverUnrealise +{ +public: + void operator()(FaceShaderObserver& observer) const + { + observer.unrealiseShader(); + } +}; + +typedef ReferencePair FaceShaderObserverPair; + + +class ContentsFlagsValue +{ +public: + ContentsFlagsValue() + { + } + ContentsFlagsValue(int surfaceFlags, int contentFlags, int value, bool specified) : + m_surfaceFlags(surfaceFlags), + m_contentFlags(contentFlags), + m_value(value), + m_specified(specified) + { + } + int m_surfaceFlags; + int m_contentFlags; + int m_value; + bool m_specified; +}; + +inline void ContentsFlagsValue_assignMasked(ContentsFlagsValue& flags, const ContentsFlagsValue& other) +{ + bool detail = bitfield_enabled(flags.m_contentFlags, BRUSH_DETAIL_MASK); + flags = other; + if(detail) + { + flags.m_contentFlags = bitfield_enable(flags.m_contentFlags, BRUSH_DETAIL_MASK); + } + else + { + flags.m_contentFlags = bitfield_disable(flags.m_contentFlags, BRUSH_DETAIL_MASK); + } +} + + +class FaceShader : public ModuleObserver +{ +public: + class SavedState + { + public: + CopiedString m_shader; + ContentsFlagsValue m_flags; + + SavedState(const FaceShader& faceShader) + { + m_shader = faceShader.getShader(); + m_flags = faceShader.m_flags; + } + + void exportState(FaceShader& faceShader) const + { + faceShader.setShader(m_shader.c_str()); + faceShader.setFlags(m_flags); + } + }; + + CopiedString m_shader; + Shader* m_state; + ContentsFlagsValue m_flags; + FaceShaderObserverPair m_observers; + bool m_instanced; + bool m_realised; + + FaceShader(const char* shader, const ContentsFlagsValue& flags = ContentsFlagsValue(0, 0, 0, false)) : + m_shader(shader), + m_state(0), + m_flags(flags), + m_instanced(false), + m_realised(false) + { + captureShader(); + } + ~FaceShader() + { + releaseShader(); + } + // copy-construction not supported + FaceShader(const FaceShader& other); + + void instanceAttach() + { + m_instanced = true; + m_state->incrementUsed(); + } + void instanceDetach() + { + m_state->decrementUsed(); + m_instanced = false; + } + + void captureShader() + { + ASSERT_MESSAGE(m_state == 0, "shader cannot be captured"); + brush_check_shader(m_shader.c_str()); + m_state = GlobalShaderCache().capture(m_shader.c_str()); + m_state->attach(*this); + } + void releaseShader() + { + ASSERT_MESSAGE(m_state != 0, "shader cannot be released"); + m_state->detach(*this); + GlobalShaderCache().release(m_shader.c_str()); + m_state = 0; + } + + void realise() + { + ASSERT_MESSAGE(!m_realised, "FaceTexdef::realise: already realised"); + m_realised = true; + m_observers.forEach(FaceShaderObserverRealise()); + } + void unrealise() + { + ASSERT_MESSAGE(m_realised, "FaceTexdef::unrealise: already unrealised"); + m_observers.forEach(FaceShaderObserverUnrealise()); + m_realised = false; + } + + void attach(FaceShaderObserver& observer) + { + m_observers.attach(observer); + if(m_realised) + { + observer.realiseShader(); + } + } + + void detach(FaceShaderObserver& observer) + { + if(m_realised) + { + observer.unrealiseShader(); + } + m_observers.detach(observer); + } + + const char* getShader() const + { + return m_shader.c_str(); + } + void setShader(const char* name) + { + if(m_instanced) + { + m_state->decrementUsed(); + } + releaseShader(); + m_shader = name; + captureShader(); + if(m_instanced) + { + m_state->incrementUsed(); + } + } + ContentsFlagsValue getFlags() const + { + ASSERT_MESSAGE(m_realised, "FaceShader::getFlags: flags not valid when unrealised"); + if(!m_flags.m_specified) + { + return ContentsFlagsValue( + m_state->getTexture().surfaceFlags, + m_state->getTexture().contentFlags, + m_state->getTexture().value, + true + ); + } + return m_flags; + } + void setFlags(const ContentsFlagsValue& flags) + { + ASSERT_MESSAGE(m_realised, "FaceShader::setFlags: flags not valid when unrealised"); + ContentsFlagsValue_assignMasked(m_flags, flags); + } + + Shader* state() const + { + return m_state; + } + + std::size_t width() const + { + if(m_realised) + { + return m_state->getTexture().width; + } + return 1; + } + std::size_t height() const + { + if(m_realised) + { + return m_state->getTexture().height; + } + return 1; + } + unsigned int shaderFlags() const + { + if(m_realised) + { + return m_state->getFlags(); + } + return 0; + } +}; + + + + +class FaceTexdef : public FaceShaderObserver +{ + // not copyable + FaceTexdef(const FaceTexdef& other); + // not assignable + FaceTexdef& operator=(const FaceTexdef& other); +public: + class SavedState + { + public: + TextureProjection m_projection; + + SavedState(const FaceTexdef& faceTexdef) + { + m_projection = faceTexdef.m_projection; + } + + void exportState(FaceTexdef& faceTexdef) const + { + Texdef_Assign(faceTexdef.m_projection, m_projection); + } + }; + + FaceShader& m_shader; + TextureProjection m_projection; + bool m_projectionInitialised; + bool m_scaleApplied; + + FaceTexdef( + FaceShader& shader, + const TextureProjection& projection, + bool projectionInitialised = true + ) : + m_shader(shader), + m_projection(projection), + m_projectionInitialised(projectionInitialised), + m_scaleApplied(false) + { + m_shader.attach(*this); + } + ~FaceTexdef() + { + m_shader.detach(*this); + } + + void addScale() + { + ASSERT_MESSAGE(!m_scaleApplied, "texture scale aready added"); + m_scaleApplied = true; + m_projection.m_brushprimit_texdef.addScale(m_shader.width(), m_shader.height()); + } + void removeScale() + { + ASSERT_MESSAGE(m_scaleApplied, "texture scale aready removed"); + m_scaleApplied = false; + m_projection.m_brushprimit_texdef.removeScale(m_shader.width(), m_shader.height()); + } + + void realiseShader() + { + if(m_projectionInitialised && !m_scaleApplied) + { + addScale(); + } + } + void unrealiseShader() + { + if(m_projectionInitialised && m_scaleApplied) + { + removeScale(); + } + } + + void setTexdef(const TextureProjection& projection) + { + removeScale(); + Texdef_Assign(m_projection, projection); + addScale(); + } + + void shift(float s, float t) + { + ASSERT_MESSAGE(texdef_sane(m_projection.m_texdef), "FaceTexdef::shift: bad texdef"); + removeScale(); + Texdef_Shift(m_projection, s, t); + addScale(); + } + + void scale(float s, float t) + { + removeScale(); + Texdef_Scale(m_projection, s, t); + addScale(); + } + + void rotate(float angle) + { + removeScale(); + Texdef_Rotate(m_projection, angle); + addScale(); + } + + void fit(const Vector3& normal, const Winding& winding, float s_repeat, float t_repeat) + { + Texdef_FitTexture(m_projection, m_shader.width(), m_shader.height(), normal, winding, s_repeat, t_repeat); + } + + void emitTextureCoordinates(Winding& winding, const Vector3& normal, const Matrix4& localToWorld) + { + Texdef_EmitTextureCoordinates(m_projection, m_shader.width(), m_shader.height(), winding, normal, localToWorld); + } + + void transform(const Plane3& plane, const Matrix4& matrix) + { + removeScale(); + Texdef_transformLocked(m_projection, m_shader.width(), m_shader.height(), plane, matrix); + addScale(); + } + + TextureProjection normalised() const + { + brushprimit_texdef_t tmp(m_projection.m_brushprimit_texdef); + tmp.removeScale(m_shader.width(), m_shader.height()); + return TextureProjection(m_projection.m_texdef, tmp, m_projection.m_basis_s, m_projection.m_basis_t); + } + void setBasis(const Vector3& normal) + { + Matrix4 basis; + Normal_GetTransform(normal, basis); + m_projection.m_basis_s = Vector3(basis.xx(), basis.yx(), basis.zx()); + m_projection.m_basis_t = Vector3(-basis.xy(), -basis.yy(), -basis.zy()); + } +}; + +inline void planepts_print(const PlanePoints& planePoints, TextOutputStream& ostream) +{ + ostream << "( " << planePoints[0][0] << " " << planePoints[0][1] << " " << planePoints[0][2] << " ) " + << "( " << planePoints[1][0] << " " << planePoints[1][1] << " " << planePoints[1][2] << " ) " + << "( " << planePoints[2][0] << " " << planePoints[2][1] << " " << planePoints[2][2] << " )"; +} + + +inline Plane3 Plane3_applyTranslation(const Plane3& plane, const Vector3& translation) +{ + Plane3 tmp(plane3_translated(Plane3(plane.normal(), -plane.dist()), translation)); + return Plane3(tmp.normal(), -tmp.dist()); +} + +inline Plane3 Plane3_applyTransform(const Plane3& plane, const Matrix4& matrix) +{ + Plane3 tmp(plane3_transformed(Plane3(plane.normal(), -plane.dist()), matrix)); + return Plane3(tmp.normal(), -tmp.dist()); +} + +class FacePlane +{ + PlanePoints m_planepts; + Plane3 m_planeCached; + Plane3 m_plane; +public: + Vector3 m_funcStaticOrigin; + + static EBrushType m_type; + + static bool isDoom3Plane() + { + return FacePlane::m_type == eBrushTypeDoom3 || FacePlane::m_type == eBrushTypeQuake4; + } + + class SavedState + { + public: + PlanePoints m_planepts; + Plane3 m_plane; + + SavedState(const FacePlane& facePlane) + { + if(facePlane.isDoom3Plane()) + { + m_plane = facePlane.m_plane; + } + else + { + planepts_assign(m_planepts, facePlane.planePoints()); + } + } + + void exportState(FacePlane& facePlane) const + { + if(facePlane.isDoom3Plane()) + { + facePlane.m_plane = m_plane; + facePlane.updateTranslated(); + } + else + { + planepts_assign(facePlane.planePoints(), m_planepts); + facePlane.MakePlane(); + } + } + }; + + FacePlane() : m_funcStaticOrigin(0, 0, 0) + { + } + FacePlane(const FacePlane& other) : m_funcStaticOrigin(0, 0, 0) + { + if(!isDoom3Plane()) + { + planepts_assign(m_planepts, other.m_planepts); + MakePlane(); + } + else + { + m_plane = other.m_plane; + updateTranslated(); + } + } + + void MakePlane() + { + if(!isDoom3Plane()) + { +#if 0 + if(check_plane_is_integer(m_planepts)) + { + globalErrorStream() << "non-integer planepts: "; + planepts_print(m_planepts, globalErrorStream()); + globalErrorStream() << "\n"; + } +#endif + m_planeCached = plane3_for_points(m_planepts); + } + } + + void reverse() + { + if(!isDoom3Plane()) + { + vector3_swap(m_planepts[0], m_planepts[2]); + MakePlane(); + } + else + { + m_planeCached = plane3_flipped(m_plane); + updateSource(); + } + } + void transform(const Matrix4& matrix, bool mirror) + { + if(!isDoom3Plane()) + { + +#if 0 + bool off = check_plane_is_integer(planePoints()); +#endif + + matrix4_transform_point(matrix, m_planepts[0]); + matrix4_transform_point(matrix, m_planepts[1]); + matrix4_transform_point(matrix, m_planepts[2]); + + if(mirror) + { + reverse(); + } + +#if 0 + if(check_plane_is_integer(planePoints())) + { + if(!off) + { + globalErrorStream() << "caused by transform\n"; + } + } +#endif + MakePlane(); + } + else + { + m_planeCached = Plane3_applyTransform(m_planeCached, matrix); + updateSource(); + } + } + void offset(float offset) + { + if(!isDoom3Plane()) + { + Vector3 move(vector3_scaled(m_planeCached.normal(), -offset)); + + vector3_subtract(m_planepts[0], move); + vector3_subtract(m_planepts[1], move); + vector3_subtract(m_planepts[2], move); + + MakePlane(); + } + else + { + m_planeCached.d += offset; + updateSource(); + } + } + + void updateTranslated() + { + m_planeCached = Plane3_applyTranslation(m_plane, m_funcStaticOrigin); + } + void updateSource() + { + m_plane = Plane3_applyTranslation(m_planeCached, vector3_negated(m_funcStaticOrigin)); + } + + + PlanePoints& planePoints() + { + return m_planepts; + } + const PlanePoints& planePoints() const + { + return m_planepts; + } + const Plane3& plane3() const + { + return m_planeCached; + } + void setDoom3Plane(const Plane3& plane) + { + m_plane = plane; + updateTranslated(); + } + const Plane3& getDoom3Plane() const + { + return m_plane; + } + + void copy(const FacePlane& other) + { + if(!isDoom3Plane()) + { + planepts_assign(m_planepts, other.m_planepts); + MakePlane(); + } + else + { + m_planeCached = other.m_plane; + updateSource(); + } + } + void copy(const Vector3& p0, const Vector3& p1, const Vector3& p2) + { + if(!isDoom3Plane()) + { + m_planepts[0] = p0; + m_planepts[1] = p1; + m_planepts[2] = p2; + MakePlane(); + } + else + { + m_planeCached = plane3_for_points(p2, p1, p0); + updateSource(); + } + } +}; + +inline void Winding_testSelect(Winding& winding, SelectionTest& test, SelectionIntersection& best) +{ + test.TestPolygon(VertexPointer(reinterpret_cast(&winding.points.data()->vertex), sizeof(WindingVertex)), winding.numpoints, best); +} + +const double GRID_MIN = 0.125; + +inline double quantiseInteger(double f) +{ + return float_to_integer(f); +} + +inline double quantiseFloating(double f) +{ + return float_snapped(f, 1.f / (1 << 16)); +} + +typedef double (*QuantiseFunc)(double f); + +class Face; + +class FaceFilter +{ +public: + virtual bool filter(const Face& face) const = 0; +}; + +bool face_filtered(Face& face); +void add_face_filter(FaceFilter& filter, int mask, bool invert = false); + +void Brush_addTextureChangedCallback(const SignalHandler& callback); +void Brush_textureChanged(); + + +extern bool g_brush_texturelock_enabled; + +class FaceObserver +{ +public: + virtual void planeChanged() = 0; + virtual void connectivityChanged() = 0; + virtual void shaderChanged() = 0; + virtual void evaluateTransform() = 0; +}; + +class Face : +public OpenGLRenderable, +public Filterable, +public Undoable, +public FaceShaderObserver +{ + std::size_t m_refcount; + + class SavedState : public UndoMemento + { + public: + FacePlane::SavedState m_planeState; + FaceTexdef::SavedState m_texdefState; + FaceShader::SavedState m_shaderState; + + SavedState(const Face& face) : m_planeState(face.getPlane()), m_texdefState(face.getTexdef()), m_shaderState(face.getShader()) + { + } + + void exportState(Face& face) const + { + m_planeState.exportState(face.getPlane()); + m_shaderState.exportState(face.getShader()); + m_texdefState.exportState(face.getTexdef()); + } + + void release() + { + delete this; + } + }; + +public: + static QuantiseFunc m_quantise; + static EBrushType m_type; + + PlanePoints m_move_planepts; + PlanePoints m_move_planeptsTransformed; +private: + FacePlane m_plane; + FacePlane m_planeTransformed; + FaceShader m_shader; + FaceTexdef m_texdef; + TextureProjection m_texdefTransformed; + + Winding m_winding; + Vector3 m_centroid; + bool m_filtered; + + FaceObserver* m_observer; + UndoObserver* m_undoable_observer; + MapFile* m_map; + + // assignment not supported + Face& operator=(const Face& other); + // copy-construction not supported + Face(const Face& other); + +public: + + Face(FaceObserver* observer) : + m_refcount(0), + m_shader(texdef_name_default()), + m_texdef(m_shader, TextureProjection(), false), + m_filtered(false), + m_observer(observer), + m_undoable_observer(0), + m_map(0) + { + m_shader.attach(*this); + m_plane.copy(Vector3(0, 0, 0), Vector3(64, 0, 0), Vector3(0, 64, 0)); + m_texdef.setBasis(m_plane.plane3().normal()); + planeChanged(); + } + Face( + const Vector3& p0, + const Vector3& p1, + const Vector3& p2, + const char* shader, + const TextureProjection& projection, + FaceObserver* observer + ) : + m_refcount(0), + m_shader(shader), + m_texdef(m_shader, projection), + m_observer(observer), + m_undoable_observer(0), + m_map(0) + { + m_shader.attach(*this); + m_plane.copy(p0, p1, p2); + m_texdef.setBasis(m_plane.plane3().normal()); + planeChanged(); + updateFiltered(); + } + Face(const Face& other, FaceObserver* observer) : + m_refcount(0), + m_shader(other.m_shader.getShader(), other.m_shader.m_flags), + m_texdef(m_shader, other.getTexdef().normalised()), + m_observer(observer), + m_undoable_observer(0), + m_map(0) + { + m_shader.attach(*this); + m_plane.copy(other.m_plane); + planepts_assign(m_move_planepts, other.m_move_planepts); + m_texdef.setBasis(m_plane.plane3().normal()); + planeChanged(); + updateFiltered(); + } + ~Face() + { + m_shader.detach(*this); + } + + void planeChanged() + { + revertTransform(); + m_observer->planeChanged(); + } + + void realiseShader() + { + m_observer->shaderChanged(); + } + void unrealiseShader() + { + } + + void instanceAttach(MapFile* map) + { + m_shader.instanceAttach(); + m_map = map; + m_undoable_observer = GlobalUndoSystem().observer(this); + GlobalFilterSystem().registerFilterable(*this); + } + void instanceDetach(MapFile* map) + { + GlobalFilterSystem().unregisterFilterable(*this); + m_undoable_observer = 0; + GlobalUndoSystem().release(this); + m_map = 0; + m_shader.instanceDetach(); + } + + void render(RenderStateFlags state) const + { + Winding_Draw(m_winding, m_planeTransformed.plane3().normal(), state); + } + + void updateFiltered() + { + m_filtered = face_filtered(*this); + } + bool isFiltered() const + { + return m_filtered; + } + + void undoSave() + { + if(m_map != 0) + { + m_map->changed(); + } + if(m_undoable_observer != 0) + { + m_undoable_observer->save(this); + } + } + + // undoable + UndoMemento* exportState() const + { + return new SavedState(*this); + } + void importState(const UndoMemento* data) + { + undoSave(); + + static_cast(data)->exportState(*this); + + planeChanged(); + m_observer->connectivityChanged(); + texdefChanged(); + m_observer->shaderChanged(); + updateFiltered(); + } + + void IncRef() + { + ++m_refcount; + } + void DecRef() + { + if(--m_refcount == 0) + delete this; + } + + void flipWinding() + { + m_plane.reverse(); + planeChanged(); + } + + bool intersectVolume(const VolumeTest& volume, const Matrix4& localToWorld) const + { + return volume.TestPlane(Plane3(plane3().normal(), -plane3().dist()), localToWorld); + } + + void render(Renderer& renderer, const Matrix4& localToWorld) const + { + renderer.SetState(m_shader.state(), Renderer::eFullMaterials); + renderer.addRenderable(*this, localToWorld); + } + + void transform(const Matrix4& matrix, bool mirror) + { + if(g_brush_texturelock_enabled) + { + Texdef_transformLocked(m_texdefTransformed, m_shader.width(), m_shader.height(), m_plane.plane3(), matrix); + } + + m_planeTransformed.transform(matrix, mirror); + +#if 0 + ASSERT_MESSAGE(projectionaxis_for_normal(normal) == projectionaxis_for_normal(plane3().normal()), "bleh"); +#endif + m_observer->planeChanged(); + } + + void assign_planepts(const PlanePoints planepts) + { + m_planeTransformed.copy(planepts[0], planepts[1], planepts[2]); + m_observer->planeChanged(); + } + + /// \brief Reverts the transformable state of the brush to identity. + void revertTransform() + { + m_planeTransformed = m_plane; + planepts_assign(m_move_planeptsTransformed, m_move_planepts); + m_texdefTransformed = m_texdef.m_projection; + } + void freezeTransform() + { + undoSave(); + m_plane = m_planeTransformed; + planepts_assign(m_move_planepts, m_move_planeptsTransformed); + m_texdef.m_projection = m_texdefTransformed; + } + + void update_move_planepts_vertex(std::size_t index, PlanePoints planePoints) + { + std::size_t numpoints = getWinding().numpoints; + ASSERT_MESSAGE(index < numpoints, "update_move_planepts_vertex: invalid index"); + + std::size_t opposite = Winding_Opposite(getWinding(), index); + std::size_t adjacent = Winding_wrap(getWinding(), opposite+numpoints-1); + planePoints[0] = getWinding()[opposite].vertex; + planePoints[1] = getWinding()[index].vertex; + planePoints[2] = getWinding()[adjacent].vertex; + // winding points are very inaccurate, so they must be quantised before using them to generate the face-plane + planepts_quantise(planePoints, GRID_MIN); + } + + void snapto(float snap) + { + if(contributes()) + { +#if 0 + ASSERT_MESSAGE(plane3_valid(m_plane.plane3()), "invalid plane before snap to grid"); + planepts_snap(m_plane.planePoints(), snap); + ASSERT_MESSAGE(plane3_valid(m_plane.plane3()), "invalid plane after snap to grid"); +#else + PlanePoints planePoints; + update_move_planepts_vertex(0, planePoints); + vector3_snap(planePoints[0], snap); + vector3_snap(planePoints[1], snap); + vector3_snap(planePoints[2], snap); + assign_planepts(planePoints); + freezeTransform(); +#endif + SceneChangeNotify(); + if(!plane3_valid(m_plane.plane3())) + { + globalErrorStream() << "WARNING: invalid plane after snap to grid\n"; + } + } + } + + void testSelect(SelectionTest& test, SelectionIntersection& best) + { + Winding_testSelect(m_winding, test, best); + } + + void testSelect_centroid(SelectionTest& test, SelectionIntersection& best) + { + test.TestPoint(m_centroid, best); + } + + void shaderChanged() + { + EmitTextureCoordinates(); + Brush_textureChanged(); + m_observer->shaderChanged(); + updateFiltered(); + planeChanged(); + SceneChangeNotify(); + } + + const char* GetShader() const + { + return m_shader.getShader(); + } + void SetShader(const char* name) + { + undoSave(); + m_shader.setShader(name); + shaderChanged(); + } + + void revertTexdef() + { + m_texdefTransformed = m_texdef.m_projection; + } + void texdefChanged() + { + revertTexdef(); + EmitTextureCoordinates(); + Brush_textureChanged(); + } + + void GetTexdef(TextureProjection& projection) const + { + projection = m_texdef.normalised(); + } + void SetTexdef(const TextureProjection& projection) + { + undoSave(); + m_texdef.setTexdef(projection); + texdefChanged(); + } + + void GetFlags(ContentsFlagsValue& flags) const + { + flags = m_shader.getFlags(); + } + void SetFlags(const ContentsFlagsValue& flags) + { + undoSave(); + m_shader.setFlags(flags); + m_observer->shaderChanged(); + updateFiltered(); + } + + void ShiftTexdef(float s, float t) + { + undoSave(); + m_texdef.shift(s, t); + texdefChanged(); + } + + void ScaleTexdef(float s, float t) + { + undoSave(); + m_texdef.scale(s, t); + texdefChanged(); + } + + void RotateTexdef(float angle) + { + undoSave(); + m_texdef.rotate(angle); + texdefChanged(); + } + + void FitTexture(float s_repeat, float t_repeat) + { + undoSave(); + m_texdef.fit(m_plane.plane3().normal(), m_winding, s_repeat, t_repeat); + texdefChanged(); + } + + void EmitTextureCoordinates() + { + Texdef_EmitTextureCoordinates(m_texdefTransformed, m_shader.width(), m_shader.height(), m_winding, plane3().normal(), g_matrix4_identity); + } + + + const Vector3& centroid() const + { + return m_centroid; + } + + void construct_centroid() + { + Winding_Centroid(m_winding, plane3(), m_centroid); + } + + const Winding& getWinding() const + { + return m_winding; + } + Winding& getWinding() + { + return m_winding; + } + + const Plane3& plane3() const + { + m_observer->evaluateTransform(); + return m_planeTransformed.plane3(); + } + FacePlane& getPlane() + { + return m_plane; + } + const FacePlane& getPlane() const + { + return m_plane; + } + FaceTexdef& getTexdef() + { + return m_texdef; + } + const FaceTexdef& getTexdef() const + { + return m_texdef; + } + FaceShader& getShader() + { + return m_shader; + } + const FaceShader& getShader() const + { + return m_shader; + } + + bool isDetail() const + { + return (m_shader.m_flags.m_contentFlags & BRUSH_DETAIL_MASK) != 0; + } + void setDetail(bool detail) + { + undoSave(); + if(detail && !isDetail()) + { + m_shader.m_flags.m_contentFlags |= BRUSH_DETAIL_MASK; + } + else if(!detail && isDetail()) + { + m_shader.m_flags.m_contentFlags &= ~BRUSH_DETAIL_MASK; + } + m_observer->shaderChanged(); + } + + bool contributes() const + { + return m_winding.numpoints > 2; + } + bool is_bounded() const + { + for(Winding::const_iterator i = m_winding.begin(); i != m_winding.end(); ++i) + { + if((*i).adjacent == c_brush_maxFaces) + { + return false; + } + } + return true; + } +}; + + +class FaceVertexId +{ + std::size_t m_face; + std::size_t m_vertex; + +public: + FaceVertexId(std::size_t face, std::size_t vertex) + : m_face(face), m_vertex(vertex) + { + } + + std::size_t getFace() const + { + return m_face; + } + std::size_t getVertex() const + { + return m_vertex; + } +}; + +typedef std::size_t faceIndex_t; + +struct EdgeRenderIndices +{ + RenderIndex first; + RenderIndex second; + + EdgeRenderIndices() + : first(0), second(0) + { + } + EdgeRenderIndices(const RenderIndex _first, const RenderIndex _second) + : first(_first), second(_second) + { + } +}; + +struct EdgeFaces +{ + faceIndex_t first; + faceIndex_t second; + + EdgeFaces() + : first(c_brush_maxFaces), second(c_brush_maxFaces) + { + } + EdgeFaces(const faceIndex_t _first, const faceIndex_t _second) + : first(_first), second(_second) + { + } +}; + +class RenderableWireframe : public OpenGLRenderable +{ +public: + void render(RenderStateFlags state) const + { +#if 1 + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_vertices->colour); + glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_vertices->vertex); + glDrawElements(GL_LINES, GLsizei(m_size<<1), RenderIndexTypeID, m_faceVertex.data()); +#else + glBegin(GL_LINES); + for(std::size_t i = 0; i < m_size; ++i) + { + glVertex3fv(&m_vertices[m_faceVertex[i].first].vertex.x); + glVertex3fv(&m_vertices[m_faceVertex[i].second].vertex.x); + } + glEnd(); +#endif + } + + Array m_faceVertex; + std::size_t m_size; + const PointVertex* m_vertices; +}; + +class Brush; +typedef std::vector brush_vector_t; + +class BrushFilter +{ +public: + virtual bool filter(const Brush& brush) const = 0; +}; + +bool brush_filtered(Brush& brush); +void add_brush_filter(BrushFilter& filter, int mask, bool invert = false); + + +/// \brief Returns true if 'self' takes priority when building brush b-rep. +inline bool plane3_inside(const Plane3& self, const Plane3& other) +{ + if(vector3_equal_epsilon(self.normal(), other.normal(), 0.001)) + { + return self.dist() < other.dist(); + } + return true; +} + +typedef SmartPointer FaceSmartPointer; +typedef std::vector Faces; + +/// \brief Returns the unique-id of the edge adjacent to \p faceVertex in the edge-pair for the set of \p faces. +inline FaceVertexId next_edge(const Faces& faces, FaceVertexId faceVertex) +{ + std::size_t adjacent_face = faces[faceVertex.getFace()]->getWinding()[faceVertex.getVertex()].adjacent; + std::size_t adjacent_vertex = Winding_FindAdjacent(faces[adjacent_face]->getWinding(), faceVertex.getFace()); + + ASSERT_MESSAGE(adjacent_vertex != c_brush_maxFaces, "connectivity data invalid"); + if(adjacent_vertex == c_brush_maxFaces) + { + return faceVertex; + } + + return FaceVertexId(adjacent_face, adjacent_vertex); +} + +/// \brief Returns the unique-id of the vertex adjacent to \p faceVertex in the vertex-ring for the set of \p faces. +inline FaceVertexId next_vertex(const Faces& faces, FaceVertexId faceVertex) +{ + FaceVertexId nextEdge = next_edge(faces, faceVertex); + return FaceVertexId(nextEdge.getFace(), Winding_next(faces[nextEdge.getFace()]->getWinding(), nextEdge.getVertex())); +} + +class SelectableEdge +{ + Vector3 getEdge() const + { + const Winding& winding = getFace().getWinding(); + return vector3_mid(winding[m_faceVertex.getVertex()].vertex, winding[Winding_next(winding, m_faceVertex.getVertex())].vertex); + } + +public: + Faces& m_faces; + FaceVertexId m_faceVertex; + + SelectableEdge(Faces& faces, FaceVertexId faceVertex) + : m_faces(faces), m_faceVertex(faceVertex) + { + } + SelectableEdge& operator=(const SelectableEdge& other) + { + m_faceVertex = other.m_faceVertex; + return *this; + } + + Face& getFace() const + { + return *m_faces[m_faceVertex.getFace()]; + } + + void testSelect(SelectionTest& test, SelectionIntersection& best) + { + test.TestPoint(getEdge(), best); + } +}; + +class SelectableVertex +{ + Vector3 getVertex() const + { + return getFace().getWinding()[m_faceVertex.getVertex()].vertex; + } + +public: + Faces& m_faces; + FaceVertexId m_faceVertex; + + SelectableVertex(Faces& faces, FaceVertexId faceVertex) + : m_faces(faces), m_faceVertex(faceVertex) + { + } + SelectableVertex& operator=(const SelectableVertex& other) + { + m_faceVertex = other.m_faceVertex; + return *this; + } + + Face& getFace() const + { + return *m_faces[m_faceVertex.getFace()]; + } + + void testSelect(SelectionTest& test, SelectionIntersection& best) + { + test.TestPoint(getVertex(), best); + } +}; + +class BrushObserver +{ +public: + virtual void reserve(std::size_t size) = 0; + virtual void clear() = 0; + virtual void push_back(Face& face) = 0; + virtual void pop_back() = 0; + virtual void erase(std::size_t index) = 0; + virtual void connectivityChanged() = 0; + + virtual void edge_clear() = 0; + virtual void edge_push_back(SelectableEdge& edge) = 0; + + virtual void vertex_clear() = 0; + virtual void vertex_push_back(SelectableVertex& vertex) = 0; + + virtual void DEBUG_verify() const = 0; +}; + +class BrushVisitor +{ +public: + virtual void visit(Face& face) const = 0; +}; + +class Brush : + public TransformNode, + public Bounded, + public Cullable, + public Snappable, + public Undoable, + public FaceObserver, + public Filterable, + public Nameable, + public BrushDoom3 +{ +private: + scene::Node* m_node; + typedef UniqueSet Observers; + Observers m_observers; + UndoObserver* m_undoable_observer; + MapFile* m_map; + + // state + Faces m_faces; + // ---- + + // cached data compiled from state + Array m_faceCentroidPoints; + RenderablePointArray m_render_faces; + + Array m_uniqueVertexPoints; + typedef std::vector SelectableVertices; + SelectableVertices m_select_vertices; + RenderablePointArray m_render_vertices; + + Array m_uniqueEdgePoints; + typedef std::vector SelectableEdges; + SelectableEdges m_select_edges; + RenderablePointArray m_render_edges; + + Array m_edge_indices; + Array m_edge_faces; + + AABB m_aabb_local; + // ---- + + Callback m_evaluateTransform; + Callback m_boundsChanged; + + mutable bool m_planeChanged; // b-rep evaluation required + mutable bool m_transformChanged; // transform evaluation required + // ---- + +public: + STRING_CONSTANT(Name, "Brush"); + + Callback m_lightsChanged; + + // static data + static Shader* m_state_point; + // ---- + + static EBrushType m_type; + static double m_maxWorldCoord; + + Brush(scene::Node& node, const Callback& evaluateTransform, const Callback& boundsChanged) : + m_node(&node), + m_undoable_observer(0), + m_map(0), + m_render_faces(m_faceCentroidPoints, GL_POINTS), + m_render_vertices(m_uniqueVertexPoints, GL_POINTS), + m_render_edges(m_uniqueEdgePoints, GL_POINTS), + m_evaluateTransform(evaluateTransform), + m_boundsChanged(boundsChanged), + m_planeChanged(false), + m_transformChanged(false) + { + planeChanged(); + } + Brush(const Brush& other, scene::Node& node, const Callback& evaluateTransform, const Callback& boundsChanged) : + m_node(&node), + m_undoable_observer(0), + m_map(0), + m_render_faces(m_faceCentroidPoints, GL_POINTS), + m_render_vertices(m_uniqueVertexPoints, GL_POINTS), + m_render_edges(m_uniqueEdgePoints, GL_POINTS), + m_evaluateTransform(evaluateTransform), + m_boundsChanged(boundsChanged), + m_planeChanged(false), + m_transformChanged(false) + { + copy(other); + } + Brush(const Brush& other) : + TransformNode(other), + Bounded(other), + Cullable(other), + Snappable(), + Undoable(other), + FaceObserver(other), + Filterable(other), + Nameable(other), + BrushDoom3(other), + m_node(0), + m_undoable_observer(0), + m_map(0), + m_render_faces(m_faceCentroidPoints, GL_POINTS), + m_render_vertices(m_uniqueVertexPoints, GL_POINTS), + m_render_edges(m_uniqueEdgePoints, GL_POINTS), + m_planeChanged(false), + m_transformChanged(false) + { + copy(other); + } + ~Brush() + { + ASSERT_MESSAGE(m_observers.empty(), "Brush::~Brush: observers still attached"); + } + + // assignment not supported + Brush& operator=(const Brush& other); + + void setDoom3GroupOrigin(const Vector3& origin) + { + //globalOutputStream() << "func_static origin before: " << m_funcStaticOrigin << " after: " << origin << "\n"; + for(Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i) + { + (*i)->getPlane().m_funcStaticOrigin = origin; + (*i)->getPlane().updateTranslated(); + (*i)->planeChanged(); + } + planeChanged(); + } + + void attach(BrushObserver& observer) + { + for(Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i) + { + observer.push_back(*(*i)); + } + + for(SelectableEdges::iterator i = m_select_edges.begin(); i !=m_select_edges.end(); ++i) + { + observer.edge_push_back(*i); + } + + for(SelectableVertices::iterator i = m_select_vertices.begin(); i != m_select_vertices.end(); ++i) + { + observer.vertex_push_back(*i); + } + + m_observers.insert(&observer); + } + void detach(BrushObserver& observer) + { + m_observers.erase(&observer); + } + + void forEachFace(const BrushVisitor& visitor) const + { + for(Faces::const_iterator i = m_faces.begin(); i != m_faces.end(); ++i) + { + visitor.visit(*(*i)); + } + } + + void forEachFace_instanceAttach(MapFile* map) const + { + for(Faces::const_iterator i = m_faces.begin(); i != m_faces.end(); ++i) + { + (*i)->instanceAttach(map); + } + } + void forEachFace_instanceDetach(MapFile* map) const + { + for(Faces::const_iterator i = m_faces.begin(); i != m_faces.end(); ++i) + { + (*i)->instanceDetach(map); + } + } + + InstanceCounter m_instanceCounter; + void instanceAttach(const scene::Path& path) + { + if(++m_instanceCounter.m_count == 1) + { + m_map = path_find_mapfile(path.begin(), path.end()); + m_undoable_observer = GlobalUndoSystem().observer(this); + GlobalFilterSystem().registerFilterable(*this); + forEachFace_instanceAttach(m_map); + } + else + { + ASSERT_MESSAGE(path_find_mapfile(path.begin(), path.end()) == m_map, "node is instanced across more than one file"); + } + } + void instanceDetach(const scene::Path& path) + { + if(--m_instanceCounter.m_count == 0) + { + forEachFace_instanceDetach(m_map); + GlobalFilterSystem().unregisterFilterable(*this); + m_map = 0; + m_undoable_observer = 0; + GlobalUndoSystem().release(this); + } + } + + // nameable + const char* name() const + { + return "brush"; + } + void attach(const NameCallback& callback) + { + } + void detach(const NameCallback& callback) + { + } + + // filterable + void updateFiltered() + { + if(m_node != 0) + { + if(brush_filtered(*this)) + { + m_node->enable(scene::Node::eFiltered); + } + else + { + m_node->disable(scene::Node::eFiltered); + } + } + } + + // observer + void planeChanged() + { + m_planeChanged = true; + aabbChanged(); + m_lightsChanged(); + } + void shaderChanged() + { + updateFiltered(); + planeChanged(); + } + + void evaluateBRep() const + { + if(m_planeChanged) + { + m_planeChanged = false; + const_cast(this)->buildBRep(); + } + } + + void transformChanged() + { + m_transformChanged = true; + planeChanged(); + } + typedef MemberCaller TransformChangedCaller; + + void evaluateTransform() + { + if(m_transformChanged) + { + m_transformChanged = false; + revertTransform(); + m_evaluateTransform(); + } + } + const Matrix4& localToParent() const + { + return g_matrix4_identity; + } + void aabbChanged() + { + m_boundsChanged(); + } + const AABB& localAABB() const + { + evaluateBRep(); + return m_aabb_local; + } + + VolumeIntersectionValue intersectVolume(const VolumeTest& test, const Matrix4& localToWorld) const + { + return test.TestAABB(m_aabb_local, localToWorld); + } + + void renderComponents(SelectionSystem::EComponentMode mode, Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld) const + { + switch(mode) + { + case SelectionSystem::eVertex: + renderer.addRenderable(m_render_vertices, localToWorld); + break; + case SelectionSystem::eEdge: + renderer.addRenderable(m_render_edges, localToWorld); + break; + case SelectionSystem::eFace: + renderer.addRenderable(m_render_faces, localToWorld); + break; + default: + break; + } + } + + void transform(const Matrix4& matrix) + { + bool mirror = matrix4_handedness(matrix) == MATRIX4_LEFTHANDED; + + for(Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i) + { + (*i)->transform(matrix, mirror); + } + } + void snapto(float snap) + { + for(Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i) + { + (*i)->snapto(snap); + } + } + void revertTransform() + { + for(Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i) + { + (*i)->revertTransform(); + } + } + void freezeTransform() + { + for(Faces::iterator i = m_faces.begin(); i != m_faces.end(); ++i) + { + (*i)->freezeTransform(); + } + } + + /// \brief Returns the absolute index of the \p faceVertex. + std::size_t absoluteIndex(FaceVertexId faceVertex) + { + std::size_t index = 0; + for(std::size_t i = 0; i < faceVertex.getFace(); ++i) + { + index += m_faces[i]->getWinding().numpoints; + } + return index + faceVertex.getVertex(); + } + + void appendFaces(const Faces& other) + { + clear(); + for(Faces::const_iterator i = other.begin(); i != other.end(); ++i) + { + push_back(*i); + } + } + + /// \brief The undo memento for a brush stores only the list of face references - the faces are not copied. + class BrushUndoMemento : public UndoMemento + { + public: + BrushUndoMemento(const Faces& faces) : m_faces(faces) + { + } + void release() + { + delete this; + } + + Faces m_faces; + }; + + void undoSave() + { + if(m_map != 0) + { + m_map->changed(); + } + if(m_undoable_observer != 0) + { + m_undoable_observer->save(this); + } + } + + UndoMemento* exportState() const + { + return new BrushUndoMemento(m_faces); + } + + void importState(const UndoMemento* state) + { + undoSave(); + appendFaces(static_cast(state)->m_faces); + planeChanged(); + + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->DEBUG_verify(); + } + } + + bool isDetail() + { + return !m_faces.empty() && m_faces.front()->isDetail(); + } + + /// \brief Appends a copy of \p face to the end of the face list. + Face* addFace(const Face& face) + { + if(m_faces.size() == c_brush_maxFaces) + { + return 0; + } + undoSave(); + push_back(FaceSmartPointer(new Face(face, this))); + m_faces.back()->setDetail(isDetail()); + planeChanged(); + return m_faces.back(); + } + + /// \brief Appends a new face constructed from the parameters to the end of the face list. + Face* addPlane(const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, const TextureProjection& projection) + { + if(m_faces.size() == c_brush_maxFaces) + { + return 0; + } + undoSave(); + push_back(FaceSmartPointer(new Face(p0, p1, p2, shader, projection, this))); + m_faces.back()->setDetail(isDetail()); + planeChanged(); + return m_faces.back(); + } + + static void constructStatic(EBrushType type) + { + m_type = type; + Face::m_type = type; + FacePlane::m_type = type; + + g_bp_globals.m_texdefTypeId = TEXDEFTYPEID_QUAKE; + if(m_type == eBrushTypeQuake3BP || m_type == eBrushTypeDoom3 || m_type == eBrushTypeQuake4) + { + g_bp_globals.m_texdefTypeId = TEXDEFTYPEID_BRUSHPRIMITIVES; + g_brush_texturelock_enabled = true; + } + else if(m_type == eBrushTypeHalfLife) + { + g_bp_globals.m_texdefTypeId = TEXDEFTYPEID_HALFLIFE; + g_brush_texturelock_enabled = true; + } + + Face::m_quantise = (m_type == eBrushTypeQuake) ? quantiseInteger : quantiseFloating; + + m_state_point = GlobalShaderCache().capture("$POINT"); + } + static void destroyStatic() + { + GlobalShaderCache().release("$POINT"); + } + + std::size_t DEBUG_size() + { + return m_faces.size(); + } + + typedef Faces::const_iterator const_iterator; + + const_iterator begin() const + { + return m_faces.begin(); + } + const_iterator end() const + { + return m_faces.end(); + } + + Face* back() + { + return m_faces.back(); + } + const Face* back() const + { + return m_faces.back(); + } + void reserve(std::size_t count) + { + m_faces.reserve(count); + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->reserve(count); + } + } + void push_back(Faces::value_type face) + { + m_faces.push_back(face); + if(m_instanceCounter.m_count != 0) + { + m_faces.back()->instanceAttach(m_map); + } + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->push_back(*face); + (*i)->DEBUG_verify(); + } + } + void pop_back() + { + if(m_instanceCounter.m_count != 0) + { + m_faces.back()->instanceDetach(m_map); + } + m_faces.pop_back(); + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->pop_back(); + (*i)->DEBUG_verify(); + } + } + void erase(std::size_t index) + { + if(m_instanceCounter.m_count != 0) + { + m_faces[index]->instanceDetach(m_map); + } + m_faces.erase(m_faces.begin() + index); + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->erase(index); + (*i)->DEBUG_verify(); + } + } + void connectivityChanged() + { + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->connectivityChanged(); + } + } + + + void clear() + { + undoSave(); + if(m_instanceCounter.m_count != 0) + { + forEachFace_instanceDetach(m_map); + } + m_faces.clear(); + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->clear(); + (*i)->DEBUG_verify(); + } + } + std::size_t size() const + { + return m_faces.size(); + } + bool empty() const + { + return m_faces.empty(); + } + + /// \brief Returns true if any face of the brush contributes to the final B-Rep. + bool hasContributingFaces() const + { + for(const_iterator i = begin(); i != end(); ++i) + { + if((*i)->contributes()) + { + return true; + } + } + return false; + } + + /// \brief Removes faces that do not contribute to the brush. This is useful for cleaning up after CSG operations on the brush. + /// Note: removal of empty faces is not performed during direct brush manipulations, because it would make a manipulation irreversible if it created an empty face. + void removeEmptyFaces() + { + evaluateBRep(); + + { + std::size_t i = 0; + while(i < m_faces.size()) + { + if(!m_faces[i]->contributes()) + { + erase(i); + planeChanged(); + } + else + { + ++i; + } + } + } + } + + /// \brief Constructs \p winding from the intersection of \p plane with the other planes of the brush. + void windingForClipPlane(Winding& winding, const Plane3& plane) const + { + FixedWinding buffer[2]; + bool swap = false; + + // get a poly that covers an effectively infinite area + Winding_createInfinite(buffer[swap], plane, m_maxWorldCoord + 1); + + // chop the poly by all of the other faces + { + for (std::size_t i = 0; i < m_faces.size(); ++i) + { + const Face& clip = *m_faces[i]; + + if(plane3_equal(clip.plane3(), plane) + || !plane3_valid(clip.plane3()) || !plane_unique(i) + || plane3_opposing(plane, clip.plane3())) + { + continue; + } + + buffer[!swap].clear(); + +#if BRUSH_CONNECTIVITY_DEBUG + globalOutputStream() << "clip vs face: " << i << "\n"; +#endif + + { + // flip the plane, because we want to keep the back side + Plane3 clipPlane(vector3_negated(clip.plane3().normal()), -clip.plane3().dist()); + Winding_Clip(buffer[swap], plane, clipPlane, i, buffer[!swap]); + } + +#if BRUSH_CONNECTIVITY_DEBUG + for(FixedWinding::Points::iterator k = buffer[!swap].points.begin(), j = buffer[!swap].points.end() - 1; k != buffer[!swap].points.end(); j = k, ++k) + { + if(vector3_length_squared(vector3_subtracted((*k).vertex, (*j).vertex)) < 1) + { + globalOutputStream() << "v: " << std::distance(buffer[!swap].points.begin(), j) << " tiny edge adjacent to face " << (*j).adjacent << "\n"; + } + } +#endif + + //ASSERT_MESSAGE(buffer[!swap].numpoints != 1, "created single-point winding"); + + swap = !swap; + } + } + + Winding_forFixedWinding(winding, buffer[swap]); + +#if BRUSH_CONNECTIVITY_DEBUG + Winding_printConnectivity(winding); + + for(Winding::iterator i = winding.begin(), j = winding.end() - 1; i != winding.end(); j = i, ++i) + { + if(vector3_length_squared(vector3_subtracted((*i).vertex, (*j).vertex)) < 1) + { + globalOutputStream() << "v: " << std::distance(winding.begin(), j) << " tiny edge adjacent to face " << (*j).adjacent << "\n"; + } + } +#endif + } + + void update_wireframe(RenderableWireframe& wire, const bool* faces_visible) const + { + wire.m_faceVertex.resize(m_edge_indices.size()); + wire.m_vertices = m_uniqueVertexPoints.data(); + wire.m_size = 0; + for(std::size_t i = 0; i < m_edge_faces.size(); ++i) + { + if(faces_visible[m_edge_faces[i].first] + || faces_visible[m_edge_faces[i].second]) + { + wire.m_faceVertex[wire.m_size++] = m_edge_indices[i]; + } + } + } + + + void update_faces_wireframe(Array& wire, const bool* faces_visible) const + { + std::size_t count = 0; + for(std::size_t i = 0; i < m_faceCentroidPoints.size(); ++i) + { + if(faces_visible[i]) + { + ++count; + } + } + + wire.resize(count); + Array::iterator p = wire.begin(); + for(std::size_t i = 0; i < m_faceCentroidPoints.size(); ++i) + { + if(faces_visible[i]) + { + *p++ = m_faceCentroidPoints[i]; + } + } + } + + /// \brief Makes this brush a deep-copy of the \p other. + void copy(const Brush& other) + { + for(Faces::const_iterator i = other.m_faces.begin(); i != other.m_faces.end(); ++i) + { + addFace(*(*i)); + } + planeChanged(); + } + +private: + void edge_push_back(FaceVertexId faceVertex) + { + m_select_edges.push_back(SelectableEdge(m_faces, faceVertex)); + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->edge_push_back(m_select_edges.back()); + } + } + void edge_clear() + { + m_select_edges.clear(); + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->edge_clear(); + } + } + void vertex_push_back(FaceVertexId faceVertex) + { + m_select_vertices.push_back(SelectableVertex(m_faces, faceVertex)); + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->vertex_push_back(m_select_vertices.back()); + } + } + void vertex_clear() + { + m_select_vertices.clear(); + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->vertex_clear(); + } + } + + /// \brief Returns true if the face identified by \p index is preceded by another plane that takes priority over it. + bool plane_unique(std::size_t index) const + { + // duplicate plane + for(std::size_t i = 0; i < m_faces.size(); ++i) + { + if(index != i && !plane3_inside(m_faces[index]->plane3(), m_faces[i]->plane3())) + { + return false; + } + } + return true; + } + + /// \brief Removes edges that are smaller than the tolerance used when generating brush windings. + void removeDegenerateEdges() + { + for (std::size_t i = 0; i < m_faces.size(); ++i) + { + Winding& winding = m_faces[i]->getWinding(); + for(Winding::iterator j = winding.begin(); j != winding.end();) + { + std::size_t index = std::distance(winding.begin(), j); + std::size_t next = Winding_next(winding, index); + if(Edge_isDegenerate(winding[index].vertex, winding[next].vertex)) + { +#if BRUSH_DEGENERATE_DEBUG + globalOutputStream() << "Brush::buildWindings: face " << i << ": degenerate edge adjacent to " << winding[index].adjacent << "\n"; +#endif + Winding& other = m_faces[winding[index].adjacent]->getWinding(); + std::size_t adjacent = Winding_FindAdjacent(other, i); + if(adjacent != c_brush_maxFaces) + { + other.erase(other.begin() + adjacent); + } + winding.erase(j); + } + else + { + ++j; + } + } + } + } + + /// \brief Invalidates faces that have only two vertices in their winding, while preserving edge-connectivity information. + void removeDegenerateFaces() + { + // save adjacency info for degenerate faces + for (std::size_t i = 0; i < m_faces.size(); ++i) + { + Winding& degen = m_faces[i]->getWinding(); + + if(degen.numpoints == 2) + { +#if BRUSH_DEGENERATE_DEBUG + globalOutputStream() << "Brush::buildWindings: face " << i << ": degenerate winding adjacent to " << degen[0].adjacent << ", " << degen[1].adjacent << "\n"; +#endif + // this is an "edge" face, where the plane touches the edge of the brush + { + Winding& winding = m_faces[degen[0].adjacent]->getWinding(); + std::size_t index = Winding_FindAdjacent(winding, i); + if(index != c_brush_maxFaces) + { +#if BRUSH_DEGENERATE_DEBUG + globalOutputStream() << "Brush::buildWindings: face " << degen[0].adjacent << ": remapping adjacent " << winding[index].adjacent << " to " << degen[1].adjacent << "\n"; +#endif + winding[index].adjacent = degen[1].adjacent; + } + } + + { + Winding& winding = m_faces[degen[1].adjacent]->getWinding(); + std::size_t index = Winding_FindAdjacent(winding, i); + if(index != c_brush_maxFaces) + { +#if BRUSH_DEGENERATE_DEBUG + globalOutputStream() << "Brush::buildWindings: face " << degen[1].adjacent << ": remapping adjacent " << winding[index].adjacent << " to " << degen[0].adjacent << "\n"; +#endif + winding[index].adjacent = degen[0].adjacent; + } + } + + degen.resize(0); + } + } + } + + /// \brief Removes edges that have the same adjacent-face as their immediate neighbour. + void removeDuplicateEdges() + { + // verify face connectivity graph + for(std::size_t i = 0; i < m_faces.size(); ++i) + { + //if(m_faces[i]->contributes()) + { + Winding& winding = m_faces[i]->getWinding(); + for(std::size_t j = 0; j != winding.numpoints;) + { + std::size_t next = Winding_next(winding, j); + if(winding[j].adjacent == winding[next].adjacent) + { +#if BRUSH_DEGENERATE_DEBUG + globalOutputStream() << "Brush::buildWindings: face " << i << ": removed duplicate edge adjacent to face " << winding[j].adjacent << "\n"; +#endif + winding.erase(winding.begin() + next); + } + else + { + ++j; + } + } + } + } + } + + /// \brief Removes edges that do not have a matching pair in their adjacent-face. + void verifyConnectivityGraph() + { + // verify face connectivity graph + for(std::size_t i = 0; i < m_faces.size(); ++i) + { + //if(m_faces[i]->contributes()) + { + Winding& winding = m_faces[i]->getWinding(); + for(Winding::iterator j = winding.begin(); j != winding.end();) + { +#if BRUSH_CONNECTIVITY_DEBUG + globalOutputStream() << "Brush::buildWindings: face " << i << ": adjacent to face " << (*j).adjacent << "\n"; +#endif + // remove unidirectional graph edges + if((*j).adjacent == c_brush_maxFaces + || Winding_FindAdjacent(m_faces[(*j).adjacent]->getWinding(), i) == c_brush_maxFaces) + { +#if BRUSH_CONNECTIVITY_DEBUG + globalOutputStream() << "Brush::buildWindings: face " << i << ": removing unidirectional connectivity graph edge adjacent to face " << (*j).adjacent << "\n"; +#endif + winding.erase(j); + } + else + { + ++j; + } + } + } + } + } + + /// \brief Returns true if the brush is a finite volume. A brush without a finite volume extends past the maximum world bounds and is not valid. + bool isBounded() + { + for(const_iterator i = begin(); i != end(); ++i) + { + if(!(*i)->is_bounded()) + { + return false; + } + } + return true; + } + + /// \brief Constructs the polygon windings for each face of the brush. Also updates the brush bounding-box and face texture-coordinates. + bool buildWindings() + { + + { + m_aabb_local = AABB(); + + for (std::size_t i = 0; i < m_faces.size(); ++i) + { + Face& f = *m_faces[i]; + + if(!plane3_valid(f.plane3()) || !plane_unique(i)) + { + f.getWinding().resize(0); + } + else + { +#if BRUSH_CONNECTIVITY_DEBUG + globalOutputStream() << "face: " << i << "\n"; +#endif + windingForClipPlane(f.getWinding(), f.plane3()); + + // update brush bounds + const Winding& winding = f.getWinding(); + for(Winding::const_iterator i = winding.begin(); i != winding.end(); ++i) + { + aabb_extend_by_point_safe(m_aabb_local, (*i).vertex); + } + + // update texture coordinates + f.EmitTextureCoordinates(); + } + } + } + + bool degenerate = !isBounded(); + + if(!degenerate) + { + // clean up connectivity information. + // these cleanups must be applied in a specific order. + removeDegenerateEdges(); + removeDegenerateFaces(); + removeDuplicateEdges(); + verifyConnectivityGraph(); + } + + return degenerate; + } + + /// \brief Constructs the face windings and updates anything that depends on them. + void buildBRep(); +}; + + + +class FaceInstance; + +class FaceInstanceSet +{ + typedef SelectionList FaceInstances; + FaceInstances m_faceInstances; +public: + void insert(FaceInstance& faceInstance) + { + m_faceInstances.append(faceInstance); + } + void erase(FaceInstance& faceInstance) + { + m_faceInstances.erase(faceInstance); + } + + template + void foreach(Functor functor) + { + for(FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + functor(*(*i)); + } + } + + bool empty() const + { + return m_faceInstances.empty(); + } + FaceInstance& last() const + { + return m_faceInstances.back(); + } +}; + +extern FaceInstanceSet g_SelectedFaceInstances; + +typedef std::list VertexSelection; + +inline VertexSelection::iterator VertexSelection_find(VertexSelection& self, std::size_t value) +{ + return std::find(self.begin(), self.end(), value); +} + +inline VertexSelection::const_iterator VertexSelection_find(const VertexSelection& self, std::size_t value) +{ + return std::find(self.begin(), self.end(), value); +} + +inline VertexSelection::iterator VertexSelection_insert(VertexSelection& self, std::size_t value) +{ + VertexSelection::iterator i = VertexSelection_find(self, value); + if(i == self.end()) + { + self.push_back(value); + return --self.end(); + } + return i; +} +inline void VertexSelection_erase(VertexSelection& self, std::size_t value) +{ + VertexSelection::iterator i = VertexSelection_find(self, value); + if(i != self.end()) + { + self.erase(i); + } +} + +inline bool triangle_reversed(std::size_t x, std::size_t y, std::size_t z) +{ + return !((x < y && y < z) || (z < x && x < y) || (y < z && z < x)); +} +template +inline Vector3 triangle_cross(const BasicVector3& x, const BasicVector3 y, const BasicVector3& z) +{ + return vector3_cross(y - x, z - x); +} +template +inline bool triangles_same_winding(const BasicVector3& x1, const BasicVector3 y1, const BasicVector3& z1, const BasicVector3& x2, const BasicVector3 y2, const BasicVector3& z2) +{ + return vector3_dot(triangle_cross(x1, y1, z1), triangle_cross(x2, y2, z2)) > 0; +} + + +typedef const Plane3* PlanePointer; +typedef PlanePointer* PlanesIterator; + +class VectorLightList : public LightList +{ + typedef std::vector Lights; + Lights m_lights; +public: + void addLight(const RendererLight& light) + { + m_lights.push_back(&light); + } + void clear() + { + m_lights.clear(); + } + void evaluateLights() const + { + } + void lightsChanged() const + { + } + void forEachLight(const RendererLightCallback& callback) const + { + for(Lights::const_iterator i = m_lights.begin(); i != m_lights.end(); ++i) + { + callback(*(*i)); + } + } +}; + +class FaceInstance +{ + Face* m_face; + ObservedSelectable m_selectable; + ObservedSelectable m_selectableVertices; + ObservedSelectable m_selectableEdges; + SelectionChangeCallback m_selectionChanged; + + VertexSelection m_vertexSelection; + VertexSelection m_edgeSelection; + +public: + mutable VectorLightList m_lights; + + FaceInstance(Face& face, const SelectionChangeCallback& observer) : + m_face(&face), + m_selectable(SelectedChangedCaller(*this)), + m_selectableVertices(observer), + m_selectableEdges(observer), + m_selectionChanged(observer) + { + } + FaceInstance(const FaceInstance& other) : + m_face(other.m_face), + m_selectable(SelectedChangedCaller(*this)), + m_selectableVertices(other.m_selectableVertices), + m_selectableEdges(other.m_selectableEdges), + m_selectionChanged(other.m_selectionChanged) + { + } + FaceInstance& operator=(const FaceInstance& other) + { + m_face = other.m_face; + return *this; + } + + Face& getFace() + { + return *m_face; + } + const Face& getFace() const + { + return *m_face; + } + + void selectedChanged(const Selectable& selectable) + { + if(selectable.isSelected()) + { + g_SelectedFaceInstances.insert(*this); + } + else + { + g_SelectedFaceInstances.erase(*this); + } + m_selectionChanged(selectable); + } + typedef MemberCaller1 SelectedChangedCaller; + + bool selectedVertices() const + { + return !m_vertexSelection.empty(); + } + bool selectedEdges() const + { + return !m_edgeSelection.empty(); + } + bool isSelected() const + { + return m_selectable.isSelected(); + } + + bool selectedComponents() const + { + return selectedVertices() || selectedEdges() || isSelected(); + } + bool selectedComponents(SelectionSystem::EComponentMode mode) const + { + switch(mode) + { + case SelectionSystem::eVertex: + return selectedVertices(); + case SelectionSystem::eEdge: + return selectedEdges(); + case SelectionSystem::eFace: + return isSelected(); + default: + return false; + } + } + void setSelected(SelectionSystem::EComponentMode mode, bool select) + { + switch(mode) + { + case SelectionSystem::eFace: + m_selectable.setSelected(select); + break; + case SelectionSystem::eVertex: + ASSERT_MESSAGE(!select, "select-all not supported"); + + m_vertexSelection.clear(); + m_selectableVertices.setSelected(false); + break; + case SelectionSystem::eEdge: + ASSERT_MESSAGE(!select, "select-all not supported"); + + m_edgeSelection.clear(); + m_selectableEdges.setSelected(false); + break; + default: + break; + } + } + + template + void SelectedVertices_foreach(Functor functor) const + { + for(VertexSelection::const_iterator i = m_vertexSelection.begin(); i != m_vertexSelection.end(); ++i) + { + std::size_t index = Winding_FindAdjacent(getFace().getWinding(), *i); + if(index != c_brush_maxFaces) + { + functor(getFace().getWinding()[index].vertex); + } + } + } + template + void SelectedEdges_foreach(Functor functor) const + { + for(VertexSelection::const_iterator i = m_edgeSelection.begin(); i != m_edgeSelection.end(); ++i) + { + std::size_t index = Winding_FindAdjacent(getFace().getWinding(), *i); + if(index != c_brush_maxFaces) + { + const Winding& winding = getFace().getWinding(); + std::size_t adjacent = Winding_next(winding, index); + functor(vector3_mid(winding[index].vertex, winding[adjacent].vertex)); + } + } + } + template + void SelectedFaces_foreach(Functor functor) const + { + if(isSelected()) + { + functor(centroid()); + } + } + + template + void SelectedComponents_foreach(Functor functor) const + { + SelectedVertices_foreach(functor); + SelectedEdges_foreach(functor); + SelectedFaces_foreach(functor); + } + + void iterate_selected(AABB& aabb) const + { + SelectedComponents_foreach(AABBExtendByPoint(aabb)); + } + + class RenderablePointVectorPushBack + { + RenderablePointVector& m_points; + public: + RenderablePointVectorPushBack(RenderablePointVector& points) : m_points(points) + { + } + void operator()(const Vector3& point) const + { + const Colour4b colour_selected(0, 0, 255, 255); + m_points.push_back(pointvertex_for_windingpoint(point, colour_selected)); + } + }; + + void iterate_selected(RenderablePointVector& points) const + { + SelectedComponents_foreach(RenderablePointVectorPushBack(points)); + } + + bool intersectVolume(const VolumeTest& volume, const Matrix4& localToWorld) const + { + return m_face->intersectVolume(volume, localToWorld); + } + + void render(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld) const + { + if(!m_face->isFiltered() && m_face->contributes() && intersectVolume(volume, localToWorld)) + { + renderer.PushState(); + if(selectedComponents()) + { + renderer.Highlight(Renderer::eFace); + } + m_face->render(renderer, localToWorld); + renderer.PopState(); + } + } + + void testSelect(SelectionTest& test, SelectionIntersection& best) + { + if(!m_face->isFiltered()) + { + m_face->testSelect(test, best); + } + } + void testSelect(Selector& selector, SelectionTest& test) + { + SelectionIntersection best; + testSelect(test, best); + if(best.valid()) + { + Selector_add(selector, m_selectable, best); + } + } + void testSelect_centroid(Selector& selector, SelectionTest& test) + { + if(m_face->contributes() && !m_face->isFiltered()) + { + SelectionIntersection best; + m_face->testSelect_centroid(test, best); + if(best.valid()) + { + Selector_add(selector, m_selectable, best); + } + } + } + + void selectPlane(Selector& selector, const Line& line, PlanesIterator first, PlanesIterator last, const PlaneCallback& selectedPlaneCallback) + { + for(Winding::const_iterator i = getFace().getWinding().begin(); i != getFace().getWinding().end(); ++i) + { + Vector3 v(vector3_subtracted(line_closest_point(line, (*i).vertex), (*i).vertex)); + double dot = vector3_dot(getFace().plane3().normal(), v); + if(dot <= 0) + { + return; + } + } + + Selector_add(selector, m_selectable); + + selectedPlaneCallback(getFace().plane3()); + } + void selectReversedPlane(Selector& selector, const SelectedPlanes& selectedPlanes) + { + if(selectedPlanes.contains(plane3_flipped(getFace().plane3()))) + { + Selector_add(selector, m_selectable); + } + } + + void transformComponents(const Matrix4& matrix) + { + if(isSelected()) + { + m_face->transform(matrix, false); + } + if(selectedVertices()) + { + if(m_vertexSelection.size() == 1) + { + matrix4_transform_point(matrix, m_face->m_move_planeptsTransformed[1]); + m_face->assign_planepts(m_face->m_move_planeptsTransformed); + } + else if(m_vertexSelection.size() == 2) + { + matrix4_transform_point(matrix, m_face->m_move_planeptsTransformed[1]); + matrix4_transform_point(matrix, m_face->m_move_planeptsTransformed[2]); + m_face->assign_planepts(m_face->m_move_planeptsTransformed); + } + else if(m_vertexSelection.size() >= 3) + { + matrix4_transform_point(matrix, m_face->m_move_planeptsTransformed[0]); + matrix4_transform_point(matrix, m_face->m_move_planeptsTransformed[1]); + matrix4_transform_point(matrix, m_face->m_move_planeptsTransformed[2]); + m_face->assign_planepts(m_face->m_move_planeptsTransformed); + } + } + if(selectedEdges()) + { + if(m_edgeSelection.size() == 1) + { + matrix4_transform_point(matrix, m_face->m_move_planeptsTransformed[0]); + matrix4_transform_point(matrix, m_face->m_move_planeptsTransformed[1]); + m_face->assign_planepts(m_face->m_move_planeptsTransformed); + } + else if(m_edgeSelection.size() >= 2) + { + matrix4_transform_point(matrix, m_face->m_move_planeptsTransformed[0]); + matrix4_transform_point(matrix, m_face->m_move_planeptsTransformed[1]); + matrix4_transform_point(matrix, m_face->m_move_planeptsTransformed[2]); + m_face->assign_planepts(m_face->m_move_planeptsTransformed); + } + } + } + + void snapto(float snap) + { + m_face->snapto(snap); + } + + void snapComponents(float snap) + { + if(isSelected()) + { + snapto(snap); + } + if(selectedVertices()) + { + vector3_snap(m_face->m_move_planepts[0], snap); + vector3_snap(m_face->m_move_planepts[1], snap); + vector3_snap(m_face->m_move_planepts[2], snap); + m_face->assign_planepts(m_face->m_move_planepts); + planepts_assign(m_face->m_move_planeptsTransformed, m_face->m_move_planepts); + m_face->freezeTransform(); + } + if(selectedEdges()) + { + vector3_snap(m_face->m_move_planepts[0], snap); + vector3_snap(m_face->m_move_planepts[1], snap); + vector3_snap(m_face->m_move_planepts[2], snap); + m_face->assign_planepts(m_face->m_move_planepts); + planepts_assign(m_face->m_move_planeptsTransformed, m_face->m_move_planepts); + m_face->freezeTransform(); + } + } + void update_move_planepts_vertex(std::size_t index) + { + m_face->update_move_planepts_vertex(index, m_face->m_move_planepts); + } + void update_move_planepts_vertex2(std::size_t index, std::size_t other) + { + const std::size_t numpoints = m_face->getWinding().numpoints; + ASSERT_MESSAGE(index < numpoints, "select_vertex: invalid index"); + + const std::size_t opposite = Winding_Opposite(m_face->getWinding(), index, other); + + if(triangle_reversed(index, other, opposite)) + { + std::swap(index, other); + } + + ASSERT_MESSAGE( + triangles_same_winding( + m_face->getWinding()[opposite].vertex, + m_face->getWinding()[index].vertex, + m_face->getWinding()[other].vertex, + m_face->getWinding()[0].vertex, + m_face->getWinding()[1].vertex, + m_face->getWinding()[2].vertex + ), + "update_move_planepts_vertex2: error" + ); + + m_face->m_move_planepts[0] = m_face->getWinding()[opposite].vertex; + m_face->m_move_planepts[1] = m_face->getWinding()[index].vertex; + m_face->m_move_planepts[2] = m_face->getWinding()[other].vertex; + planepts_quantise(m_face->m_move_planepts, GRID_MIN); // winding points are very inaccurate + } + void update_selection_vertex() + { + if(m_vertexSelection.size() == 0) + { + m_selectableVertices.setSelected(false); + } + else + { + m_selectableVertices.setSelected(true); + + if(m_vertexSelection.size() == 1) + { + std::size_t index = Winding_FindAdjacent(getFace().getWinding(), *m_vertexSelection.begin()); + + if(index != c_brush_maxFaces) + { + update_move_planepts_vertex(index); + } + } + else if(m_vertexSelection.size() == 2) + { + std::size_t index = Winding_FindAdjacent(getFace().getWinding(), *m_vertexSelection.begin()); + std::size_t other = Winding_FindAdjacent(getFace().getWinding(), *(++m_vertexSelection.begin())); + + if(index != c_brush_maxFaces + && other != c_brush_maxFaces) + { + update_move_planepts_vertex2(index, other); + } + } + } + } + void select_vertex(std::size_t index, bool select) + { + if(select) + { + VertexSelection_insert(m_vertexSelection, getFace().getWinding()[index].adjacent); + } + else + { + VertexSelection_erase(m_vertexSelection, getFace().getWinding()[index].adjacent); + } + + SceneChangeNotify(); + update_selection_vertex(); + } + + bool selected_vertex(std::size_t index) const + { + return VertexSelection_find(m_vertexSelection, getFace().getWinding()[index].adjacent) != m_vertexSelection.end(); + } + + void update_move_planepts_edge(std::size_t index) + { + std::size_t numpoints = m_face->getWinding().numpoints; + ASSERT_MESSAGE(index < numpoints, "select_edge: invalid index"); + + std::size_t adjacent = Winding_next(m_face->getWinding(), index); + std::size_t opposite = Winding_Opposite(m_face->getWinding(), index); + m_face->m_move_planepts[0] = m_face->getWinding()[index].vertex; + m_face->m_move_planepts[1] = m_face->getWinding()[adjacent].vertex; + m_face->m_move_planepts[2] = m_face->getWinding()[opposite].vertex; + planepts_quantise(m_face->m_move_planepts, GRID_MIN); // winding points are very inaccurate + } + void update_selection_edge() + { + if(m_edgeSelection.size() == 0) + { + m_selectableEdges.setSelected(false); + } + else + { + m_selectableEdges.setSelected(true); + + if(m_edgeSelection.size() == 1) + { + std::size_t index = Winding_FindAdjacent(getFace().getWinding(), *m_edgeSelection.begin()); + + if(index != c_brush_maxFaces) + { + update_move_planepts_edge(index); + } + } + } + } + void select_edge(std::size_t index, bool select) + { + if(select) + { + VertexSelection_insert(m_edgeSelection, getFace().getWinding()[index].adjacent); + } + else + { + VertexSelection_erase(m_edgeSelection, getFace().getWinding()[index].adjacent); + } + + SceneChangeNotify(); + update_selection_edge(); + } + + bool selected_edge(std::size_t index) const + { + return VertexSelection_find(m_edgeSelection, getFace().getWinding()[index].adjacent) != m_edgeSelection.end(); + } + + const Vector3& centroid() const + { + return m_face->centroid(); + } + + void connectivityChanged() + { + // This occurs when a face is added or removed. + // The current vertex and edge selections no longer valid and must be cleared. + m_vertexSelection.clear(); + m_selectableVertices.setSelected(false); + m_edgeSelection.clear(); + m_selectableEdges.setSelected(false); + } +}; + +class BrushClipPlane : public OpenGLRenderable +{ + Plane3 m_plane; + Winding m_winding; + static Shader* m_state; +public: + static void constructStatic() + { + m_state = GlobalShaderCache().capture("$CLIPPER_OVERLAY"); + } + static void destroyStatic() + { + GlobalShaderCache().release("$CLIPPER_OVERLAY"); + } + + void setPlane(const Brush& brush, const Plane3& plane) + { + m_plane = plane; + if(plane3_valid(m_plane)) + { + brush.windingForClipPlane(m_winding, m_plane); + } + else + { + m_winding.resize(0); + } + } + + void render(RenderStateFlags state) const + { + if((state & RENDER_FILL) != 0) + { + Winding_Draw(m_winding, m_plane.normal(), state); + } + else + { + Winding_DrawWireframe(m_winding); + } + } + + void render(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld) const + { + renderer.SetState(m_state, Renderer::eWireframeOnly); + renderer.SetState(m_state, Renderer::eFullMaterials); + renderer.addRenderable(*this, localToWorld); + } +}; + +inline void Face_addLight(const FaceInstance& face, const Matrix4& localToWorld, const RendererLight& light) +{ + const Plane3& facePlane = face.getFace().plane3(); + const Vector3& origin = light.aabb().origin; + Plane3 tmp(plane3_transformed(Plane3(facePlane.normal(), -facePlane.dist()), localToWorld)); + if(!plane3_test_point(tmp, origin) + || !plane3_test_point(tmp, vector3_added(origin, light.offset()))) + { + face.m_lights.addLight(light); + } +} + + + +typedef std::vector FaceInstances; + +class EdgeInstance : public Selectable +{ + FaceInstances& m_faceInstances; + SelectableEdge* m_edge; + + void select_edge(bool select) + { + FaceVertexId faceVertex = m_edge->m_faceVertex; + m_faceInstances[faceVertex.getFace()].select_edge(faceVertex.getVertex(), select); + faceVertex = next_edge(m_edge->m_faces, faceVertex); + m_faceInstances[faceVertex.getFace()].select_edge(faceVertex.getVertex(), select); + } + bool selected_edge() const + { + FaceVertexId faceVertex = m_edge->m_faceVertex; + if(!m_faceInstances[faceVertex.getFace()].selected_edge(faceVertex.getVertex())) + { + return false; + } + faceVertex = next_edge(m_edge->m_faces, faceVertex); + if(!m_faceInstances[faceVertex.getFace()].selected_edge(faceVertex.getVertex())) + { + return false; + } + + return true; + } + +public: + EdgeInstance(FaceInstances& faceInstances, SelectableEdge& edge) + : m_faceInstances(faceInstances), m_edge(&edge) + { + } + EdgeInstance& operator=(const EdgeInstance& other) + { + m_edge = other.m_edge; + return *this; + } + + void setSelected(bool select) + { + select_edge(select); + } + bool isSelected() const + { + return selected_edge(); + } + + + void testSelect(Selector& selector, SelectionTest& test) + { + SelectionIntersection best; + m_edge->testSelect(test, best); + if(best.valid()) + { + Selector_add(selector, *this, best); + } + } +}; + +class VertexInstance : public Selectable +{ + FaceInstances& m_faceInstances; + SelectableVertex* m_vertex; + + void select_vertex(bool select) + { + FaceVertexId faceVertex = m_vertex->m_faceVertex; + do + { + m_faceInstances[faceVertex.getFace()].select_vertex(faceVertex.getVertex(), select); + faceVertex = next_vertex(m_vertex->m_faces, faceVertex); + } + while(faceVertex.getFace() != m_vertex->m_faceVertex.getFace()); + } + bool selected_vertex() const + { + FaceVertexId faceVertex = m_vertex->m_faceVertex; + do + { + if(!m_faceInstances[faceVertex.getFace()].selected_vertex(faceVertex.getVertex())) + { + return false; + } + faceVertex = next_vertex(m_vertex->m_faces, faceVertex); + } + while(faceVertex.getFace() != m_vertex->m_faceVertex.getFace()); + return true; + } + +public: + VertexInstance(FaceInstances& faceInstances, SelectableVertex& vertex) + : m_faceInstances(faceInstances), m_vertex(&vertex) + { + } + VertexInstance& operator=(const VertexInstance& other) + { + m_vertex = other.m_vertex; + return *this; + } + + void setSelected(bool select) + { + select_vertex(select); + } + bool isSelected() const + { + return selected_vertex(); + } + + void testSelect(Selector& selector, SelectionTest& test) + { + SelectionIntersection best; + m_vertex->testSelect(test, best); + if(best.valid()) + { + Selector_add(selector, *this, best); + } + } +}; + +class BrushInstanceVisitor +{ +public: + virtual void visit(FaceInstance& face) const = 0; +}; + +class BrushInstance : +public BrushObserver, +public scene::Instance, +public Selectable, +public Renderable, +public SelectionTestable, +public ComponentSelectionTestable, +public ComponentEditable, +public ComponentSnappable, +public PlaneSelectable, +public LightCullable +{ + class TypeCasts + { + InstanceTypeCastTable m_casts; + public: + TypeCasts() + { + InstanceStaticCast::install(m_casts); + InstanceContainedCast::install(m_casts); + InstanceContainedCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceIdentityCast::install(m_casts); + InstanceContainedCast::install(m_casts); + } + InstanceTypeCastTable& get() + { + return m_casts; + } + }; + + + Brush& m_brush; + + FaceInstances m_faceInstances; + + typedef std::vector EdgeInstances; + EdgeInstances m_edgeInstances; + typedef std::vector VertexInstances; + VertexInstances m_vertexInstances; + + ObservedSelectable m_selectable; + + mutable RenderableWireframe m_render_wireframe; + mutable RenderablePointVector m_render_selected; + mutable AABB m_aabb_component; + mutable Array m_faceCentroidPointsCulled; + RenderablePointArray m_render_faces_wireframe; + mutable bool m_viewChanged; // requires re-evaluation of view-dependent cached data + + BrushClipPlane m_clipPlane; + + static Shader* m_state_selpoint; + + const LightList* m_lightList; + + TransformModifier m_transform; + + BrushInstance(const BrushInstance& other); // NOT COPYABLE + BrushInstance& operator=(const BrushInstance& other); // NOT ASSIGNABLE +public: + static Counter* m_counter; + + typedef LazyStatic StaticTypeCasts; + + void lightsChanged() + { + m_lightList->lightsChanged(); + } + typedef MemberCaller LightsChangedCaller; + + STRING_CONSTANT(Name, "BrushInstance"); + + BrushInstance(const scene::Path& path, scene::Instance* parent, Brush& brush) : + Instance(path, parent, this, StaticTypeCasts::instance().get()), + m_brush(brush), + m_selectable(SelectedChangedCaller(*this)), + m_render_selected(GL_POINTS), + m_render_faces_wireframe(m_faceCentroidPointsCulled, GL_POINTS), + m_viewChanged(false), + m_transform(Brush::TransformChangedCaller(m_brush), ApplyTransformCaller(*this)) + { + m_brush.instanceAttach(Instance::path()); + m_brush.attach(*this); + m_counter->increment(); + + m_lightList = &GlobalShaderCache().attach(*this); + m_brush.m_lightsChanged = LightsChangedCaller(*this); ///\todo Make this work with instancing. + + Instance::setTransformChangedCallback(LightsChangedCaller(*this)); + } + ~BrushInstance() + { + Instance::setTransformChangedCallback(Callback()); + + m_brush.m_lightsChanged = Callback(); + GlobalShaderCache().detach(*this); + + m_counter->decrement(); + m_brush.detach(*this); + m_brush.instanceDetach(Instance::path()); + } + + Brush& getBrush() + { + return m_brush; + } + const Brush& getBrush() const + { + return m_brush; + } + + Bounded& get(NullType) + { + return m_brush; + } + Cullable& get(NullType) + { + return m_brush; + } + Transformable& get(NullType) + { + return m_transform; + } + + void selectedChanged(const Selectable& selectable) + { + GlobalSelectionSystem().getObserver(SelectionSystem::ePrimitive)(selectable); + GlobalSelectionSystem().onSelectedChanged(*this, selectable); + + Instance::selectedChanged(); + } + typedef MemberCaller1 SelectedChangedCaller; + + void selectedChangedComponent(const Selectable& selectable) + { + GlobalSelectionSystem().getObserver(SelectionSystem::eComponent)(selectable); + GlobalSelectionSystem().onComponentSelection(*this, selectable); + } + typedef MemberCaller1 SelectedChangedComponentCaller; + + const BrushInstanceVisitor& forEachFaceInstance(const BrushInstanceVisitor& visitor) + { + for(FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + visitor.visit(*i); + } + return visitor; + } + + static void constructStatic() + { + m_state_selpoint = GlobalShaderCache().capture("$SELPOINT"); + } + static void destroyStatic() + { + GlobalShaderCache().release("$SELPOINT"); + } + + void clear() + { + m_faceInstances.clear(); + } + void reserve(std::size_t size) + { + m_faceInstances.reserve(size); + } + + void push_back(Face& face) + { + m_faceInstances.push_back(FaceInstance(face, SelectedChangedComponentCaller(*this))); + } + void pop_back() + { + ASSERT_MESSAGE(!m_faceInstances.empty(), "erasing invalid element"); + m_faceInstances.pop_back(); + } + void erase(std::size_t index) + { + ASSERT_MESSAGE(index < m_faceInstances.size(), "erasing invalid element"); + m_faceInstances.erase(m_faceInstances.begin() + index); + } + void connectivityChanged() + { + for(FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + (*i).connectivityChanged(); + } + } + + void edge_clear() + { + m_edgeInstances.clear(); + } + void edge_push_back(SelectableEdge& edge) + { + m_edgeInstances.push_back(EdgeInstance(m_faceInstances, edge)); + } + + void vertex_clear() + { + m_vertexInstances.clear(); + } + void vertex_push_back(SelectableVertex& vertex) + { + m_vertexInstances.push_back(VertexInstance(m_faceInstances, vertex)); + } + + void DEBUG_verify() const + { + ASSERT_MESSAGE(m_faceInstances.size() == m_brush.DEBUG_size(), "FATAL: mismatch"); + } + + bool isSelected() const + { + return m_selectable.isSelected(); + } + void setSelected(bool select) + { + m_selectable.setSelected(select); + } + + void update_selected() const + { + m_render_selected.clear(); + for(FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + if((*i).getFace().contributes()) + { + (*i).iterate_selected(m_render_selected); + } + } + } + + void evaluateViewDependent(const VolumeTest& volume, const Matrix4& localToWorld) const + { + if(m_viewChanged) + { + m_viewChanged = false; + + bool faces_visible[c_brush_maxFaces]; + { + bool* j = faces_visible; + for(FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i, ++j) + { + *j = (*i).intersectVolume(volume, localToWorld); + } + } + + m_brush.update_wireframe(m_render_wireframe, faces_visible); + m_brush.update_faces_wireframe(m_faceCentroidPointsCulled, faces_visible); + } + } + + void renderComponentsSelected(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld) const + { + m_brush.evaluateBRep(); + + update_selected(); + if(!m_render_selected.empty()) + { + renderer.Highlight(Renderer::ePrimitive, false); + renderer.SetState(m_state_selpoint, Renderer::eWireframeOnly); + renderer.SetState(m_state_selpoint, Renderer::eFullMaterials); + renderer.addRenderable(m_render_selected, localToWorld); + } + } + + void renderComponents(Renderer& renderer, const VolumeTest& volume) const + { + m_brush.evaluateBRep(); + + const Matrix4& localToWorld = Instance::localToWorld(); + + renderer.SetState(m_brush.m_state_point, Renderer::eWireframeOnly); + renderer.SetState(m_brush.m_state_point, Renderer::eFullMaterials); + + if(volume.fill() && GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace) + { + evaluateViewDependent(volume, localToWorld); + renderer.addRenderable(m_render_faces_wireframe, localToWorld); + } + else + { + m_brush.renderComponents(GlobalSelectionSystem().ComponentMode(), renderer, volume, localToWorld); + } + } + + void renderClipPlane(Renderer& renderer, const VolumeTest& volume) const + { + if(GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eClip && isSelected()) + { + m_clipPlane.render(renderer, volume, localToWorld()); + } + } + + void renderCommon(Renderer& renderer, const VolumeTest& volume) const + { + bool componentMode = GlobalSelectionSystem().Mode() == SelectionSystem::eComponent; + + if(componentMode && isSelected()) + { + renderComponents(renderer, volume); + } + + if(parentSelected()) + { + if(!componentMode) + { + renderer.Highlight(Renderer::eFace); + } + renderer.Highlight(Renderer::ePrimitive); + } + } + + void renderSolid(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld) const + { + //renderCommon(renderer, volume); + + m_lightList->evaluateLights(); + + for(FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + renderer.setLights((*i).m_lights); + (*i).render(renderer, volume, localToWorld); + } + + renderComponentsSelected(renderer, volume, localToWorld); + } + + void renderWireframe(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld) const + { + //renderCommon(renderer, volume); + + evaluateViewDependent(volume, localToWorld); + + if(m_render_wireframe.m_size != 0) + { + renderer.addRenderable(m_render_wireframe, localToWorld); + } + + renderComponentsSelected(renderer, volume, localToWorld); + } + + void renderSolid(Renderer& renderer, const VolumeTest& volume) const + { + m_brush.evaluateBRep(); + + renderClipPlane(renderer, volume); + + renderSolid(renderer, volume, localToWorld()); + } + + void renderWireframe(Renderer& renderer, const VolumeTest& volume) const + { + m_brush.evaluateBRep(); + + renderClipPlane(renderer, volume); + + renderWireframe(renderer, volume, localToWorld()); + } + + void viewChanged() const + { + m_viewChanged = true; + } + + void testSelect(Selector& selector, SelectionTest& test) + { + test.BeginMesh(localToWorld()); + + SelectionIntersection best; + for(FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + (*i).testSelect(test, best); + } + if(best.valid()) + { + selector.addIntersection(best); + } + } + + bool isSelectedComponents() const + { + for(FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + if((*i).selectedComponents()) + { + return true; + } + } + return false; + } + void setSelectedComponents(bool select, SelectionSystem::EComponentMode mode) + { + for(FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + (*i).setSelected(mode, select); + } + } + void testSelectComponents(Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode) + { + test.BeginMesh(localToWorld()); + + switch(mode) + { + case SelectionSystem::eVertex: + { + for(VertexInstances::iterator i = m_vertexInstances.begin(); i != m_vertexInstances.end(); ++i) + { + (*i).testSelect(selector, test); + } + } + break; + case SelectionSystem::eEdge: + { + for(EdgeInstances::iterator i = m_edgeInstances.begin(); i != m_edgeInstances.end(); ++i) + { + (*i).testSelect(selector, test); + } + } + break; + case SelectionSystem::eFace: + { + if(test.getVolume().fill()) + { + for(FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + (*i).testSelect(selector, test); + } + } + else + { + for(FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + (*i).testSelect_centroid(selector, test); + } + } + } + break; + default: + break; + } + } + + void selectPlanes(Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback) + { + test.BeginMesh(localToWorld()); + + PlanePointer brushPlanes[c_brush_maxFaces]; + PlanesIterator j = brushPlanes; + + for(Brush::const_iterator i = m_brush.begin(); i != m_brush.end(); ++i) + { + *j++ = &(*i)->plane3(); + } + + for(FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + (*i).selectPlane(selector, Line(test.getNear(), test.getFar()), brushPlanes, j, selectedPlaneCallback); + } + } + void selectReversedPlanes(Selector& selector, const SelectedPlanes& selectedPlanes) + { + for(FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + (*i).selectReversedPlane(selector, selectedPlanes); + } + } + + + void transformComponents(const Matrix4& matrix) + { + for(FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + (*i).transformComponents(matrix); + } + } + const AABB& getSelectedComponentsBounds() const + { + m_aabb_component = AABB(); + + for(FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + (*i).iterate_selected(m_aabb_component); + } + + return m_aabb_component; + } + + void snapComponents(float snap) + { + for(FaceInstances::iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + (*i).snapComponents(snap); + } + } + void evaluateTransform() + { + Matrix4 matrix(m_transform.calculateTransform()); + //globalOutputStream() << "matrix: " << matrix << "\n"; + + if(m_transform.getType() == TRANSFORM_PRIMITIVE) + { + m_brush.transform(matrix); + } + else + { + transformComponents(matrix); + } + } + void applyTransform() + { + m_brush.revertTransform(); + evaluateTransform(); + m_brush.freezeTransform(); + } + typedef MemberCaller ApplyTransformCaller; + + void setClipPlane(const Plane3& plane) + { + m_clipPlane.setPlane(m_brush, plane); + } + + bool testLight(const RendererLight& light) const + { + return light.testAABB(worldAABB()); + } + void insertLight(const RendererLight& light) + { + const Matrix4& localToWorld = Instance::localToWorld(); + for(FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + Face_addLight(*i, localToWorld, light); + } + } + void clearLights() + { + for(FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) + { + (*i).m_lights.clear(); + } + } +}; + +inline BrushInstance* Instance_getBrush(scene::Instance& instance) +{ + return InstanceTypeCast::cast(instance); +} + + +template +class BrushSelectedVisitor : public SelectionSystem::Visitor +{ + const Functor& m_functor; +public: + BrushSelectedVisitor(const Functor& functor) : m_functor(functor) + { + } + void visit(scene::Instance& instance) const + { + BrushInstance* brush = Instance_getBrush(instance); + if(brush != 0) + { + m_functor(*brush); + } + } +}; + +template +inline const Functor& Scene_forEachSelectedBrush(const Functor& functor) +{ + GlobalSelectionSystem().foreachSelected(BrushSelectedVisitor(functor)); + return functor; +} + +template +class BrushVisibleSelectedVisitor : public SelectionSystem::Visitor +{ + const Functor& m_functor; +public: + BrushVisibleSelectedVisitor(const Functor& functor) : m_functor(functor) + { + } + void visit(scene::Instance& instance) const + { + BrushInstance* brush = Instance_getBrush(instance); + if(brush != 0 + && instance.path().top().get().visible()) + { + m_functor(*brush); + } + } +}; + +template +inline const Functor& Scene_forEachVisibleSelectedBrush(const Functor& functor) +{ + GlobalSelectionSystem().foreachSelected(BrushVisibleSelectedVisitor(functor)); + return functor; +} + +class BrushForEachFace +{ + const BrushInstanceVisitor& m_visitor; +public: + BrushForEachFace(const BrushInstanceVisitor& visitor) : m_visitor(visitor) + { + } + void operator()(BrushInstance& brush) const + { + brush.forEachFaceInstance(m_visitor); + } +}; + +template +class FaceInstanceVisitFace : public BrushInstanceVisitor +{ + const Functor& functor; +public: + FaceInstanceVisitFace(const Functor& functor) + : functor(functor) + { + } + void visit(FaceInstance& face) const + { + functor(face.getFace()); + } +}; + +template +inline const Functor& Brush_forEachFace(BrushInstance& brush, const Functor& functor) +{ + brush.forEachFaceInstance(FaceInstanceVisitFace(functor)); + return functor; +} + +template +class FaceVisitAll : public BrushVisitor +{ + const Functor& functor; +public: + FaceVisitAll(const Functor& functor) + : functor(functor) + { + } + void visit(Face& face) const + { + functor(face); + } +}; + +template +inline const Functor& Brush_forEachFace(const Brush& brush, const Functor& functor) +{ + brush.forEachFace(FaceVisitAll(functor)); + return functor; +} + +template +inline const Functor& Brush_forEachFace(Brush& brush, const Functor& functor) +{ + brush.forEachFace(FaceVisitAll(functor)); + return functor; +} + +template +class FaceInstanceVisitAll : public BrushInstanceVisitor +{ + const Functor& functor; +public: + FaceInstanceVisitAll(const Functor& functor) + : functor(functor) + { + } + void visit(FaceInstance& face) const + { + functor(face); + } +}; + +template +inline const Functor& Brush_ForEachFaceInstance(BrushInstance& brush, const Functor& functor) +{ + brush.forEachFaceInstance(FaceInstanceVisitAll(functor)); + return functor; +} + +template +inline const Functor& Scene_forEachBrush(scene::Graph& graph, const Functor& functor) +{ + graph.traverse(InstanceWalker< InstanceApply >(functor)); + return functor; +} + +template +class InstanceIfVisible : public Functor +{ +public: + InstanceIfVisible(const Functor& functor) : Functor(functor) + { + } + void operator()(scene::Instance& instance) + { + if(instance.path().top().get().visible()) + { + Functor::operator()(instance); + } + } +}; + +template +class BrushVisibleWalker : public scene::Graph::Walker +{ + const Functor& m_functor; +public: + BrushVisibleWalker(const Functor& functor) : m_functor(functor) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + BrushInstance* brush = Instance_getBrush(instance); + if(brush != 0) + { + m_functor(*brush); + } + } + return true; + } +}; + +template +inline const Functor& Scene_forEachVisibleBrush(scene::Graph& graph, const Functor& functor) +{ + graph.traverse(BrushVisibleWalker(functor)); + return functor; +} + +template +inline const Functor& Scene_ForEachBrush_ForEachFace(scene::Graph& graph, const Functor& functor) +{ + Scene_forEachBrush(graph, BrushForEachFace(FaceInstanceVisitFace(functor))); + return functor; +} + +// d1223m +template +inline const Functor& Scene_ForEachBrush_ForEachFaceInstance(scene::Graph& graph, const Functor& functor) +{ + Scene_forEachBrush(graph, BrushForEachFace(FaceInstanceVisitAll(functor))); + return functor; +} + +template +inline const Functor& Scene_ForEachSelectedBrush_ForEachFace(scene::Graph& graph, const Functor& functor) +{ + Scene_forEachSelectedBrush(BrushForEachFace(FaceInstanceVisitFace(functor))); + return functor; +} + +template +inline const Functor& Scene_ForEachSelectedBrush_ForEachFaceInstance(scene::Graph& graph, const Functor& functor) +{ + Scene_forEachSelectedBrush(BrushForEachFace(FaceInstanceVisitAll(functor))); + return functor; +} + +template +class FaceVisitorWrapper +{ + const Functor& functor; +public: + FaceVisitorWrapper(const Functor& functor) : functor(functor) + { + } + + void operator()(FaceInstance& faceInstance) const + { + functor(faceInstance.getFace()); + } +}; + +template +inline const Functor& Scene_ForEachSelectedBrushFace(scene::Graph& graph, const Functor& functor) +{ + g_SelectedFaceInstances.foreach(FaceVisitorWrapper(functor)); + return functor; +} + + +#endif diff --git a/radiant/brush_primit.cpp b/radiant/brush_primit.cpp new file mode 100644 index 00000000..02b821dd --- /dev/null +++ b/radiant/brush_primit.cpp @@ -0,0 +1,1466 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "brush_primit.h" + +#include "debugging/debugging.h" + +#include "itexdef.h" +#include "itextures.h" + +#include + +#include "stringio.h" +#include "texturelib.h" +#include "math/matrix.h" +#include "math/plane.h" +#include "math/aabb.h" + +#include "winding.h" +#include "preferences.h" + + +/*! +\brief Construct a transform from XYZ space to ST space (3d to 2d). +This will be one of three axis-aligned spaces, depending on the surface normal. +NOTE: could also be done by swapping values. +*/ +void Normal_GetTransform(const Vector3& normal, Matrix4& transform) +{ + switch (projectionaxis_for_normal(normal)) + { + case eProjectionAxisZ: + transform[0] = 1; + transform[1] = 0; + transform[2] = 0; + + transform[4] = 0; + transform[5] = 1; + transform[6] = 0; + + transform[8] = 0; + transform[9] = 0; + transform[10] = 1; + break; + case eProjectionAxisY: + transform[0] = 1; + transform[1] = 0; + transform[2] = 0; + + transform[4] = 0; + transform[5] = 0; + transform[6] = -1; + + transform[8] = 0; + transform[9] = 1; + transform[10] = 0; + break; + case eProjectionAxisX: + transform[0] = 0; + transform[1] = 0; + transform[2] = 1; + + transform[4] = 1; + transform[5] = 0; + transform[6] = 0; + + transform[8] = 0; + transform[9] = 1; + transform[10] = 0; + break; + } + transform[3] = transform[7] = transform[11] = transform[12] = transform[13] = transform[14] = 0; + transform[15] = 1; +} + +/*! +\brief Construct a transform in ST space from the texdef. +Transforms constructed from quake's texdef format are (-shift)*(1/scale)*(-rotate) with x translation sign flipped. +This would really make more sense if it was inverseof(shift*rotate*scale).. oh well. +*/ +inline void Texdef_toTransform(const texdef_t& texdef, float width, float height, Matrix4& transform) +{ + double inverse_scale[2]; + + // transform to texdef shift/scale/rotate + inverse_scale[0] = 1 / (texdef.scale[0] * width); + inverse_scale[1] = 1 / (texdef.scale[1] * -height); + transform[12] = texdef.shift[0] / width; + transform[13] = -texdef.shift[1] / -height; + double c = cos(degrees_to_radians(-texdef.rotate)); + double s = sin(degrees_to_radians(-texdef.rotate)); + transform[0] = static_cast(c * inverse_scale[0]); + transform[1] = static_cast(s * inverse_scale[1]); + transform[4] = static_cast(-s * inverse_scale[0]); + transform[5] = static_cast(c * inverse_scale[1]); + transform[2] = transform[3] = transform[6] = transform[7] = transform[8] = transform[9] = transform[11] = transform[14] = 0; + transform[10] = transform[15] = 1; +} + +inline void BPTexdef_toTransform(const brushprimit_texdef_t& bp_texdef, Matrix4& transform) +{ + transform = g_matrix4_identity; + transform.xx() = bp_texdef.coords[0][0]; + transform.yx() = bp_texdef.coords[0][1]; + transform.tx() = bp_texdef.coords[0][2]; + transform.xy() = bp_texdef.coords[1][0]; + transform.yy() = bp_texdef.coords[1][1]; + transform.ty() = bp_texdef.coords[1][2]; +} + +inline void Texdef_toTransform(const TextureProjection& projection, float width, float height, Matrix4& transform) +{ + if(g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) + { + BPTexdef_toTransform(projection.m_brushprimit_texdef, transform); + } + else + { + Texdef_toTransform(projection.m_texdef, width, height, transform); + } +} + +// handles degenerate cases, just in case library atan2 doesn't +inline double arctangent_yx(double y, double x) +{ + if(fabs(x) > 1.0E-6) + { + return atan2(y, x); + } + else if(y > 0) + { + return c_half_pi; + } + else + { + return -c_half_pi; + } +} + +inline void Texdef_fromTransform(texdef_t& texdef, float width, float height, const Matrix4& transform) +{ + texdef.scale[0] = static_cast((1.0 / vector2_length(Vector2(transform[0], transform[4]))) / width); + texdef.scale[1] = static_cast((1.0 / vector2_length(Vector2(transform[1], transform[5]))) / height); + + texdef.rotate = static_cast(-radians_to_degrees(arctangent_yx(-transform[4], transform[0]))); + + if(texdef.rotate == -180.0f) + { + texdef.rotate = 180.0f; + } + + texdef.shift[0] = transform[12] * width; + texdef.shift[1] = transform[13] * height; + + // If the 2d cross-product of the x and y axes is positive, one of the axes has a negative scale. + if(vector2_cross(Vector2(transform[0], transform[4]), Vector2(transform[1], transform[5])) > 0) + { + if(texdef.rotate >= 180.0f) + { + texdef.rotate -= 180.0f; + texdef.scale[0] = -texdef.scale[0]; + } + else + { + texdef.scale[1] = -texdef.scale[1]; + } + } + //globalOutputStream() << "fromTransform: " << texdef.shift[0] << " " << texdef.shift[1] << " " << texdef.scale[0] << " " << texdef.scale[1] << " " << texdef.rotate << "\n"; +} + +inline void BPTexdef_fromTransform(brushprimit_texdef_t& bp_texdef, const Matrix4& transform) +{ + bp_texdef.coords[0][0] = transform.xx(); + bp_texdef.coords[0][1] = transform.yx(); + bp_texdef.coords[0][2] = transform.tx(); + bp_texdef.coords[1][0] = transform.xy(); + bp_texdef.coords[1][1] = transform.yy(); + bp_texdef.coords[1][2] = transform.ty(); +} + +inline void Texdef_fromTransform(TextureProjection& projection, float width, float height, const Matrix4& transform) +{ + ASSERT_MESSAGE((transform[0] != 0 || transform[4] != 0) + && (transform[1] != 0 || transform[5] != 0), "invalid texture matrix"); + + if(g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) + { + BPTexdef_fromTransform(projection.m_brushprimit_texdef, transform); + } + else + { + Texdef_fromTransform(projection.m_texdef, width, height, transform); + } +} + +inline void Texdef_normalise(texdef_t& texdef, float width, float height) +{ + // it may be useful to also normalise the rotation here, if this function is used elsewhere. + texdef.shift[0] = float_mod(texdef.shift[0], width); + texdef.shift[1] = float_mod(texdef.shift[1], height); + //globalOutputStream() << "normalise: " << texdef.shift[0] << " " << texdef.shift[1] << " " << texdef.scale[0] << " " << texdef.scale[1] << " " << texdef.rotate << "\n"; +} + +inline void BPTexdef_normalise(brushprimit_texdef_t& bp_texdef, float width, float height) +{ + bp_texdef.coords[0][2] = float_mod(bp_texdef.coords[0][2], width); + bp_texdef.coords[1][2] = float_mod(bp_texdef.coords[1][2], height); +} + +/// \brief Normalise \p projection for a given texture \p width and \p height. +/// +/// All texture-projection translation (shift) values are congruent modulo the dimensions of the texture. +/// This function normalises shift values to the smallest positive congruent values. +void Texdef_normalise(TextureProjection& projection, float width, float height) +{ + if(g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) + { + BPTexdef_normalise(projection.m_brushprimit_texdef, width, height); + } + else + { + Texdef_normalise(projection.m_texdef, width, height); + } +} + +void ComputeAxisBase(const Vector3& normal, Vector3& texS, Vector3& texT); + +inline void DebugAxisBase(const Vector3& normal) +{ + Vector3 x, y; + ComputeAxisBase(normal, x, y); + globalOutputStream() << "BP debug: " << x << y << normal << "\n"; +} + +void Texdef_basisForNormal(const TextureProjection& projection, const Vector3& normal, Matrix4& basis) +{ + if(g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) + { + basis = g_matrix4_identity; + ComputeAxisBase(normal, vector4_to_vector3(basis.x()), vector4_to_vector3(basis.y())); + vector4_to_vector3(basis.z()) = normal; + matrix4_transpose(basis); + //DebugAxisBase(normal); + } + else if(g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_HALFLIFE) + { + basis = g_matrix4_identity; + vector4_to_vector3(basis.x()) = projection.m_basis_s; + vector4_to_vector3(basis.y()) = vector3_negated(projection.m_basis_t); + vector4_to_vector3(basis.z()) = vector3_normalised(vector3_cross(vector4_to_vector3(basis.x()), vector4_to_vector3(basis.y()))); + matrix4_multiply_by_matrix4(basis, matrix4_rotation_for_z_degrees(-projection.m_texdef.rotate)); + //globalOutputStream() << "debug: " << projection.m_basis_s << projection.m_basis_t << normal << "\n"; + matrix4_transpose(basis); + } + else + { + Normal_GetTransform(normal, basis); + } +} + +void Texdef_EmitTextureCoordinates(const TextureProjection& projection, std::size_t width, std::size_t height, Winding& w, const Vector3& normal, const Matrix4& localToWorld) +{ + if(w.numpoints < 3) + { + return; + } + //globalOutputStream() << "normal: " << normal << "\n"; + + Matrix4 local2tex; + Texdef_toTransform(projection, (float)width, (float)height, local2tex); + //globalOutputStream() << "texdef: " << static_cast(local2tex.x()) << static_cast(local2tex.y()) << "\n"; + +#if 0 + { + TextureProjection tmp; + Texdef_fromTransform(tmp, (float)width, (float)height, local2tex); + Matrix4 tmpTransform; + Texdef_toTransform(tmp, (float)width, (float)height, tmpTransform); + ASSERT_MESSAGE(matrix4_equal_epsilon(local2tex, tmpTransform, 0.0001f), "bleh"); + } +#endif + + { + Matrix4 xyz2st; + // we don't care if it's not normalised... + Texdef_basisForNormal(projection, matrix4_transformed_direction(localToWorld, normal), xyz2st); + //globalOutputStream() << "basis: " << static_cast(xyz2st.x()) << static_cast(xyz2st.y()) << static_cast(xyz2st.z()) << "\n"; + matrix4_multiply_by_matrix4(local2tex, xyz2st); + } + + Vector3 tangent(vector3_normalised(vector4_to_vector3(matrix4_transposed(local2tex).x()))); + Vector3 bitangent(vector3_normalised(vector4_to_vector3(matrix4_transposed(local2tex).y()))); + + matrix4_multiply_by_matrix4(local2tex, localToWorld); + + for(Winding::iterator i = w.begin(); i != w.end(); ++i) + { + Vector3 texcoord = matrix4_transformed_point(local2tex, (*i).vertex); + (*i).texcoord[0] = texcoord[0]; + (*i).texcoord[1] = texcoord[1]; + + (*i).tangent = tangent; + (*i).bitangent = bitangent; + } +} + +/*! +\brief Provides the axis-base of the texture ST space for this normal, +as they had been transformed to world XYZ space. +*/ +void TextureAxisFromNormal(const Vector3& normal, Vector3& s, Vector3& t) +{ + switch (projectionaxis_for_normal(normal)) + { + case eProjectionAxisZ: + s[0] = 1; + s[1] = 0; + s[2] = 0; + + t[0] = 0; + t[1] = -1; + t[2] = 0; + + break; + case eProjectionAxisY: + s[0] = 1; + s[1] = 0; + s[2] = 0; + + t[0] = 0; + t[1] = 0; + t[2] = -1; + + break; + case eProjectionAxisX: + s[0] = 0; + s[1] = 1; + s[2] = 0; + + t[0] = 0; + t[1] = 0; + t[2] = -1; + + break; + } +} + +void Texdef_Assign(texdef_t& td, const texdef_t& other) +{ + td = other; +} + +void Texdef_Shift(texdef_t& td, float s, float t) +{ + td.shift[0] += s; + td.shift[1] += t; +} + +void Texdef_Scale(texdef_t& td, float s, float t) +{ + td.scale[0] += s; + td.scale[1] += t; +} + +void Texdef_Rotate(texdef_t& td, float angle) +{ + td.rotate += angle; + td.rotate = static_cast(float_to_integer(td.rotate) % 360); +} + +// NOTE: added these from Ritual's Q3Radiant +void ClearBounds(Vector3& mins, Vector3& maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds(const Vector3& v, Vector3& mins, Vector3& maxs) +{ + int i; + float val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} + +template +inline BasicVector3 vector3_inverse(const BasicVector3& self) +{ + return BasicVector3( + Element(1.0 / self.x()), + Element(1.0 / self.y()), + Element(1.0 / self.z()) + ); +} + +// low level functions .. put in mathlib? +#define BPMatCopy(a,b) {b[0][0] = a[0][0]; b[0][1] = a[0][1]; b[0][2] = a[0][2]; b[1][0] = a[1][0]; b[1][1] = a[1][1]; b[1][2] = a[1][2];} +// apply a scale transformation to the BP matrix +#define BPMatScale(m,sS,sT) {m[0][0]*=sS; m[1][0]*=sS; m[0][1]*=sT; m[1][1]*=sT;} +// apply a translation transformation to a BP matrix +#define BPMatTranslate(m,s,t) {m[0][2] += m[0][0]*s + m[0][1]*t; m[1][2] += m[1][0]*s+m[1][1]*t;} +// 2D homogeneous matrix product C = A*B +void BPMatMul(float A[2][3], float B[2][3], float C[2][3]); +// apply a rotation (degrees) +void BPMatRotate(float A[2][3], float theta); +#ifdef _DEBUG +void BPMatDump(float A[2][3]); +#endif + +#ifdef _DEBUG +//#define DBG_BP +#endif + + +bp_globals_t g_bp_globals; +float g_texdef_default_scale; + +// compute a determinant using Sarrus rule +//++timo "inline" this with a macro +// NOTE : the three vectors are understood as columns of the matrix +inline float SarrusDet(const Vector3& a, const Vector3& b, const Vector3& c) +{ + return a[0]*b[1]*c[2]+b[0]*c[1]*a[2]+c[0]*a[1]*b[2] + -c[0]*b[1]*a[2]-a[1]*b[0]*c[2]-a[0]*b[2]*c[1]; +} + +// in many case we know three points A,B,C in two axis base B1 and B2 +// and we want the matrix M so that A(B1) = T * A(B2) +// NOTE: 2D homogeneous space stuff +// NOTE: we don't do any check to see if there's a solution or we have a particular case .. need to make sure before calling +// NOTE: the third coord of the A,B,C point is ignored +// NOTE: see the commented out section to fill M and D +//++timo TODO: update the other members to use this when possible +void MatrixForPoints( Vector3 M[3], Vector3 D[2], brushprimit_texdef_t *T ) +{ +// Vector3 M[3]; // columns of the matrix .. easier that way (the indexing is not standard! it's column-line .. later computations are easier that way) + float det; +// Vector3 D[2]; + M[2][0]=1.0f; M[2][1]=1.0f; M[2][2]=1.0f; +#if 0 + // fill the data vectors + M[0][0]=A2[0]; M[0][1]=B2[0]; M[0][2]=C2[0]; + M[1][0]=A2[1]; M[1][1]=B2[1]; M[1][2]=C2[1]; + M[2][0]=1.0f; M[2][1]=1.0f; M[2][2]=1.0f; + D[0][0]=A1[0]; + D[0][1]=B1[0]; + D[0][2]=C1[0]; + D[1][0]=A1[1]; + D[1][1]=B1[1]; + D[1][2]=C1[1]; +#endif + // solve + det = SarrusDet( M[0], M[1], M[2] ); + T->coords[0][0] = SarrusDet( D[0], M[1], M[2] ) / det; + T->coords[0][1] = SarrusDet( M[0], D[0], M[2] ) / det; + T->coords[0][2] = SarrusDet( M[0], M[1], D[0] ) / det; + T->coords[1][0] = SarrusDet( D[1], M[1], M[2] ) / det; + T->coords[1][1] = SarrusDet( M[0], D[1], M[2] ) / det; + T->coords[1][2] = SarrusDet( M[0], M[1], D[1] ) / det; +} + +//++timo replace everywhere texX by texS etc. ( ----> and in q3map !) +// NOTE : ComputeAxisBase here and in q3map code must always BE THE SAME ! +// WARNING : special case behaviour of atan2(y,x) <-> atan(y/x) might not be the same everywhere when x == 0 +// rotation by (0,RotY,RotZ) assigns X to normal +void ComputeAxisBase(const Vector3& normal, Vector3& texS, Vector3& texT) +{ +#if 1 + const Vector3 up(0, 0, 1); + const Vector3 down(0, 0, -1); + + if(vector3_equal_epsilon(normal, up, float(1e-6))) + { + texS = Vector3(0, 1, 0); + texT = Vector3(1, 0, 0); + } + else if(vector3_equal_epsilon(normal, down, float(1e-6))) + { + texS = Vector3(0, 1, 0); + texT = Vector3(-1, 0, 0); + } + else + { + texS = vector3_normalised(vector3_cross(normal, up)); + texT = vector3_normalised(vector3_cross(normal, texS)); + vector3_negate(texS); + } + +#else + float RotY,RotZ; + // do some cleaning + /* + if (fabs(normal[0])<1e-6) + normal[0]=0.0f; + if (fabs(normal[1])<1e-6) + normal[1]=0.0f; + if (fabs(normal[2])<1e-6) + normal[2]=0.0f; + */ + RotY=-atan2(normal[2],sqrt(normal[1]*normal[1]+normal[0]*normal[0])); + RotZ=atan2(normal[1],normal[0]); + // rotate (0,1,0) and (0,0,1) to compute texS and texT + texS[0]=-sin(RotZ); + texS[1]=cos(RotZ); + texS[2]=0; + // the texT vector is along -Z ( T texture coorinates axis ) + texT[0]=-sin(RotY)*cos(RotZ); + texT[1]=-sin(RotY)*sin(RotZ); + texT[2]=-cos(RotY); +#endif +} + +#if 0 // texdef conversion +void FaceToBrushPrimitFace(face_t *f) +{ + Vector3 texX,texY; + Vector3 proj; + // ST of (0,0) (1,0) (0,1) + float ST[3][5]; // [ point index ] [ xyz ST ] + //++timo not used as long as brushprimit_texdef and texdef are static +/* f->brushprimit_texdef.contents=f->texdef.contents; + f->brushprimit_texdef.flags=f->texdef.flags; + f->brushprimit_texdef.value=f->texdef.value; + strcpy(f->brushprimit_texdef.name,f->texdef.name); */ +#ifdef DBG_BP + if ( f->plane.normal[0]==0.0f && f->plane.normal[1]==0.0f && f->plane.normal[2]==0.0f ) + { + globalOutputStream() << "Warning : f->plane.normal is (0,0,0) in FaceToBrushPrimitFace\n"; + } + // check d_texture + if (!f->d_texture) + { + globalOutputStream() << "Warning : f.d_texture is 0 in FaceToBrushPrimitFace\n"; + return; + } +#endif + // compute axis base + ComputeAxisBase(f->plane.normal,texX,texY); + // compute projection vector + VectorCopy(f->plane.normal,proj); + VectorScale(proj,f->plane.dist,proj); + // (0,0) in plane axis base is (0,0,0) in world coordinates + projection on the affine plane + // (1,0) in plane axis base is texX in world coordinates + projection on the affine plane + // (0,1) in plane axis base is texY in world coordinates + projection on the affine plane + // use old texture code to compute the ST coords of these points + VectorCopy(proj,ST[0]); + EmitTextureCoordinates(ST[0], f->pShader->getTexture(), f); + VectorCopy(texX,ST[1]); + VectorAdd(ST[1],proj,ST[1]); + EmitTextureCoordinates(ST[1], f->pShader->getTexture(), f); + VectorCopy(texY,ST[2]); + VectorAdd(ST[2],proj,ST[2]); + EmitTextureCoordinates(ST[2], f->pShader->getTexture(), f); + // compute texture matrix + f->brushprimit_texdef.coords[0][2]=ST[0][3]; + f->brushprimit_texdef.coords[1][2]=ST[0][4]; + f->brushprimit_texdef.coords[0][0]=ST[1][3]-f->brushprimit_texdef.coords[0][2]; + f->brushprimit_texdef.coords[1][0]=ST[1][4]-f->brushprimit_texdef.coords[1][2]; + f->brushprimit_texdef.coords[0][1]=ST[2][3]-f->brushprimit_texdef.coords[0][2]; + f->brushprimit_texdef.coords[1][1]=ST[2][4]-f->brushprimit_texdef.coords[1][2]; +} + +// compute texture coordinates for the winding points +void EmitBrushPrimitTextureCoordinates(face_t * f, Winding * w) +{ + Vector3 texX,texY; + float x,y; + // compute axis base + ComputeAxisBase(f->plane.normal,texX,texY); + // in case the texcoords matrix is empty, build a default one + // same behaviour as if scale[0]==0 && scale[1]==0 in old code + if (f->brushprimit_texdef.coords[0][0]==0 && f->brushprimit_texdef.coords[1][0]==0 && f->brushprimit_texdef.coords[0][1]==0 && f->brushprimit_texdef.coords[1][1]==0) + { + f->brushprimit_texdef.coords[0][0] = 1.0f; + f->brushprimit_texdef.coords[1][1] = 1.0f; + ConvertTexMatWithQTexture( &f->brushprimit_texdef, 0, &f->brushprimit_texdef, f->pShader->getTexture() ); + } + int i; + for (i=0 ; ibrushprimit_texdef.coords[0][0]*x+f->brushprimit_texdef.coords[0][1]*y+f->brushprimit_texdef.coords[0][2]; + float T=f->brushprimit_texdef.coords[1][0]*x+f->brushprimit_texdef.coords[1][1]*y+f->brushprimit_texdef.coords[1][2]; + if ( fabs(S-w.point_at(i)[3])>1e-2 || fabs(T-w.point_at(i)[4])>1e-2 ) + { + if ( fabs(S-w.point_at(i)[3])>1e-4 || fabs(T-w.point_at(i)[4])>1e-4 ) + globalOutputStream() << "Warning : precision loss in brush -> brush primitive texture computation\n"; + else + globalOutputStream() << "Warning : brush -> brush primitive texture computation bug detected\n"; + } + } +#endif +#endif + w.point_at(i)[3]=f->brushprimit_texdef.coords[0][0]*x+f->brushprimit_texdef.coords[0][1]*y+f->brushprimit_texdef.coords[0][2]; + w.point_at(i)[4]=f->brushprimit_texdef.coords[1][0]*x+f->brushprimit_texdef.coords[1][1]*y+f->brushprimit_texdef.coords[1][2]; + } +} +#endif + +typedef float texmat_t[2][3]; + +void TexMat_Scale(texmat_t texmat, float s, float t) +{ + texmat[0][0] *= s; + texmat[0][1] *= s; + texmat[0][2] *= s; + texmat[1][0] *= t; + texmat[1][1] *= t; + texmat[1][2] *= t; +} + +void TexMat_Assign(texmat_t texmat, const texmat_t other) +{ + texmat[0][0] = other[0][0]; + texmat[0][1] = other[0][1]; + texmat[0][2] = other[0][2]; + texmat[1][0] = other[1][0]; + texmat[1][1] = other[1][1]; + texmat[1][2] = other[1][2]; +} + +void ConvertTexMatWithDimensions(const texmat_t texmat1, std::size_t w1, std::size_t h1, + texmat_t texmat2, std::size_t w2, std::size_t h2) +{ + TexMat_Assign(texmat2, texmat1); + TexMat_Scale(texmat2, static_cast(w1) / static_cast(w2), static_cast(h1) / static_cast(h2)); +} + +#if 0 +// convert a texture matrix between two qtexture_t +// if 0 for qtexture_t, basic 2x2 texture is assumed ( straight mapping between s/t coordinates and geometric coordinates ) +void ConvertTexMatWithQTexture( const float texMat1[2][3], const qtexture_t *qtex1, float texMat2[2][3], const qtexture_t *qtex2 ) +{ + ConvertTexMatWithDimensions(texMat1, (qtex1) ? qtex1->width : 2, (qtex1) ? qtex1->height : 2, + texMat2, (qtex2) ? qtex2->width : 2, (qtex2) ? qtex2->height : 2); +} + +void ConvertTexMatWithQTexture( const brushprimit_texdef_t *texMat1, const qtexture_t *qtex1, brushprimit_texdef_t *texMat2, const qtexture_t *qtex2 ) +{ + ConvertTexMatWithQTexture(texMat1->coords, qtex1, texMat2->coords, qtex2); +} +#endif + +// compute a fake shift scale rot representation from the texture matrix +// these shift scale rot values are to be understood in the local axis base +// Note: this code looks similar to Texdef_fromTransform, but the algorithm is slightly different. + +void TexMatToFakeTexCoords(const brushprimit_texdef_t& bp_texdef, texdef_t& texdef) +{ + texdef.scale[0] = static_cast(1.0 / vector2_length(Vector2(bp_texdef.coords[0][0], bp_texdef.coords[1][0]))); + texdef.scale[1] = static_cast(1.0 / vector2_length(Vector2(bp_texdef.coords[0][1], bp_texdef.coords[1][1]))); + + texdef.rotate = -static_cast(radians_to_degrees(arctangent_yx(bp_texdef.coords[1][0], bp_texdef.coords[0][0]))); + + texdef.shift[0] = -bp_texdef.coords[0][2]; + texdef.shift[1] = bp_texdef.coords[1][2]; + + // determine whether or not an axis is flipped using a 2d cross-product + double cross = vector2_cross(Vector2(bp_texdef.coords[0][0], bp_texdef.coords[0][1]), Vector2(bp_texdef.coords[1][0], bp_texdef.coords[1][1])); + if(cross < 0) + { + // This is a bit of a compromise when using BPs--since we don't know *which* axis was flipped, + // we pick one (rather arbitrarily) using the following convention: If the X-axis is between + // 0 and 180, we assume it's the Y-axis that flipped, otherwise we assume it's the X-axis and + // subtract out 180 degrees to compensate. + if(texdef.rotate >= 180.0f) + { + texdef.rotate -= 180.0f; + texdef.scale[0] = -texdef.scale[0]; + } + else + { + texdef.scale[1] = -texdef.scale[1]; + } + } +} + +// compute back the texture matrix from fake shift scale rot +void FakeTexCoordsToTexMat(const texdef_t& texdef, brushprimit_texdef_t& bp_texdef) +{ + double r = degrees_to_radians(-texdef.rotate); + double c = cos(r); + double s = sin(r); + double x = 1.0f / texdef.scale[0]; + double y = 1.0f / texdef.scale[1]; + bp_texdef.coords[0][0] = static_cast(x * c); + bp_texdef.coords[1][0] = static_cast(x * s); + bp_texdef.coords[0][1] = static_cast(y * -s); + bp_texdef.coords[1][1] = static_cast(y * c); + bp_texdef.coords[0][2] = -texdef.shift[0]; + bp_texdef.coords[1][2] = texdef.shift[1]; +} + +#if 0 // texture locking (brush primit) +// used for texture locking +// will move the texture according to a geometric vector +void ShiftTextureGeometric_BrushPrimit(face_t *f, Vector3& delta) +{ + Vector3 texS,texT; + float tx,ty; + Vector3 M[3]; // columns of the matrix .. easier that way + float det; + Vector3 D[2]; + // compute plane axis base ( doesn't change with translation ) + ComputeAxisBase( f->plane.normal, texS, texT ); + // compute translation vector in plane axis base + tx = vector3_dot( delta, texS ); + ty = vector3_dot( delta, texT ); + // fill the data vectors + M[0][0]=tx; M[0][1]=1.0f+tx; M[0][2]=tx; + M[1][0]=ty; M[1][1]=ty; M[1][2]=1.0f+ty; + M[2][0]=1.0f; M[2][1]=1.0f; M[2][2]=1.0f; + D[0][0]=f->brushprimit_texdef.coords[0][2]; + D[0][1]=f->brushprimit_texdef.coords[0][0]+f->brushprimit_texdef.coords[0][2]; + D[0][2]=f->brushprimit_texdef.coords[0][1]+f->brushprimit_texdef.coords[0][2]; + D[1][0]=f->brushprimit_texdef.coords[1][2]; + D[1][1]=f->brushprimit_texdef.coords[1][0]+f->brushprimit_texdef.coords[1][2]; + D[1][2]=f->brushprimit_texdef.coords[1][1]+f->brushprimit_texdef.coords[1][2]; + // solve + det = SarrusDet( M[0], M[1], M[2] ); + f->brushprimit_texdef.coords[0][0] = SarrusDet( D[0], M[1], M[2] ) / det; + f->brushprimit_texdef.coords[0][1] = SarrusDet( M[0], D[0], M[2] ) / det; + f->brushprimit_texdef.coords[0][2] = SarrusDet( M[0], M[1], D[0] ) / det; + f->brushprimit_texdef.coords[1][0] = SarrusDet( D[1], M[1], M[2] ) / det; + f->brushprimit_texdef.coords[1][1] = SarrusDet( M[0], D[1], M[2] ) / det; + f->brushprimit_texdef.coords[1][2] = SarrusDet( M[0], M[1], D[1] ) / det; +} + +// shift a texture (texture adjustments) along it's current texture axes +// x and y are geometric values, which we must compute as ST increments +// this depends on the texture size and the pixel/texel ratio +void ShiftTextureRelative_BrushPrimit( face_t *f, float x, float y) +{ + float s,t; + // as a ratio against texture size + // the scale of the texture is not relevant here (we work directly on a transformation from the base vectors) + s = (x * 2.0) / (float)f->pShader->getTexture().width; + t = (y * 2.0) / (float)f->pShader->getTexture().height; + f->brushprimit_texdef.coords[0][2] -= s; + f->brushprimit_texdef.coords[1][2] -= t; +} +#endif + +// TTimo: FIXME: I don't like that, it feels broken +// (and it's likely that it's not used anymore) +// best fitted 2D vector is x.X+y.Y +void ComputeBest2DVector( Vector3& v, Vector3& X, Vector3& Y, int &x, int &y ) +{ + double sx,sy; + sx = vector3_dot( v, X ); + sy = vector3_dot( v, Y ); + if ( fabs(sy) > fabs(sx) ) + { + x = 0; + if ( sy > 0.0 ) + y = 1; + else + y = -1; + } + else + { + y = 0; + if ( sx > 0.0 ) + x = 1; + else + x = -1; + } +} + + +#if 0 // texdef conversion +void BrushPrimitFaceToFace(face_t *face) +{ + // we have parsed brush primitives and need conversion back to standard format + // NOTE: converting back is a quick hack, there's some information lost and we can't do anything about it + // FIXME: if we normalize the texture matrix to a standard 2x2 size, we end up with wrong scaling + // I tried various tweaks, no luck .. seems shifting is lost + brushprimit_texdef_t aux; + ConvertTexMatWithQTexture( &face->brushprimit_texdef, face->pShader->getTexture(), &aux, 0 ); + TexMatToFakeTexCoords( aux.coords, face->texdef.shift, &face->texdef.rotate, face->texdef.scale ); + face->texdef.scale[0]/=2.0; + face->texdef.scale[1]/=2.0; +} +#endif + + +#if 0 // texture locking (brush primit) +// TEXTURE LOCKING ----------------------------------------------------------------------------------------------------- +// (Relevant to the editor only?) + +// internally used for texture locking on rotation and flipping +// the general algorithm is the same for both lockings, it's only the geometric transformation part that changes +// so I wanted to keep it in a single function +// if there are more linear transformations that need the locking, going to a C++ or code pointer solution would be best +// (but right now I want to keep brush_primit.cpp striclty C) + +bool txlock_bRotation; + +// rotation locking params +int txl_nAxis; +float txl_fDeg; +Vector3 txl_vOrigin; + +// flip locking params +Vector3 txl_matrix[3]; +Vector3 txl_origin; + +void TextureLockTransformation_BrushPrimit(face_t *f) +{ + Vector3 Orig,texS,texT; // axis base of initial plane + // used by transformation algo + Vector3 temp; int j; + Vector3 vRotate; // rotation vector + + Vector3 rOrig,rvecS,rvecT; // geometric transformation of (0,0) (1,0) (0,1) { initial plane axis base } + Vector3 rNormal,rtexS,rtexT; // axis base for the transformed plane + Vector3 lOrig,lvecS,lvecT; // [2] are not used ( but usefull for debugging ) + Vector3 M[3]; + float det; + Vector3 D[2]; + + // compute plane axis base + ComputeAxisBase( f->plane.normal, texS, texT ); + VectorSet(Orig, 0.0f, 0.0f, 0.0f); + + // compute coordinates of (0,0) (1,0) (0,1) ( expressed in initial plane axis base ) after transformation + // (0,0) (1,0) (0,1) ( expressed in initial plane axis base ) <-> (0,0,0) texS texT ( expressed world axis base ) + // input: Orig, texS, texT (and the global locking params) + // ouput: rOrig, rvecS, rvecT, rNormal + if (txlock_bRotation) { + // rotation vector + VectorSet( vRotate, 0.0f, 0.0f, 0.0f ); + vRotate[txl_nAxis]=txl_fDeg; + VectorRotateOrigin ( Orig, vRotate, txl_vOrigin, rOrig ); + VectorRotateOrigin ( texS, vRotate, txl_vOrigin, rvecS ); + VectorRotateOrigin ( texT, vRotate, txl_vOrigin, rvecT ); + // compute normal of plane after rotation + VectorRotate ( f->plane.normal, vRotate, rNormal ); + } + else + { + for (j=0 ; j<3 ; j++) + rOrig[j] = vector3_dot(vector3_subtracted(Orig, txl_origin), txl_matrix[j]) + txl_origin[j]; + for (j=0 ; j<3 ; j++) + rvecS[j] = vector3_dot(vector3_subtracted(texS, txl_origin), txl_matrix[j]) + txl_origin[j]; + for (j=0 ; j<3 ; j++) + rvecT[j] = vector3_dot(vector3_subtracted(texT, txl_origin), txl_matrix[j]) + txl_origin[j]; + // we also need the axis base of the target plane, apply the transformation matrix to the normal too.. + for (j=0 ; j<3 ; j++) + rNormal[j] = vector3_dot(f->plane.normal, txl_matrix[j]); + } + + // compute rotated plane axis base + ComputeAxisBase( rNormal, rtexS, rtexT ); + // compute S/T coordinates of the three points in rotated axis base ( in M matrix ) + lOrig[0] = vector3_dot( rOrig, rtexS ); + lOrig[1] = vector3_dot( rOrig, rtexT ); + lvecS[0] = vector3_dot( rvecS, rtexS ); + lvecS[1] = vector3_dot( rvecS, rtexT ); + lvecT[0] = vector3_dot( rvecT, rtexS ); + lvecT[1] = vector3_dot( rvecT, rtexT ); + M[0][0] = lOrig[0]; M[1][0] = lOrig[1]; M[2][0] = 1.0f; + M[0][1] = lvecS[0]; M[1][1] = lvecS[1]; M[2][1] = 1.0f; + M[0][2] = lvecT[0]; M[1][2] = lvecT[1]; M[2][2] = 1.0f; + // fill data vector + D[0][0]=f->brushprimit_texdef.coords[0][2]; + D[0][1]=f->brushprimit_texdef.coords[0][0]+f->brushprimit_texdef.coords[0][2]; + D[0][2]=f->brushprimit_texdef.coords[0][1]+f->brushprimit_texdef.coords[0][2]; + D[1][0]=f->brushprimit_texdef.coords[1][2]; + D[1][1]=f->brushprimit_texdef.coords[1][0]+f->brushprimit_texdef.coords[1][2]; + D[1][2]=f->brushprimit_texdef.coords[1][1]+f->brushprimit_texdef.coords[1][2]; + // solve + det = SarrusDet( M[0], M[1], M[2] ); + f->brushprimit_texdef.coords[0][0] = SarrusDet( D[0], M[1], M[2] ) / det; + f->brushprimit_texdef.coords[0][1] = SarrusDet( M[0], D[0], M[2] ) / det; + f->brushprimit_texdef.coords[0][2] = SarrusDet( M[0], M[1], D[0] ) / det; + f->brushprimit_texdef.coords[1][0] = SarrusDet( D[1], M[1], M[2] ) / det; + f->brushprimit_texdef.coords[1][1] = SarrusDet( M[0], D[1], M[2] ) / det; + f->brushprimit_texdef.coords[1][2] = SarrusDet( M[0], M[1], D[1] ) / det; +} + +// texture locking +// called before the points on the face are actually rotated +void RotateFaceTexture_BrushPrimit(face_t *f, int nAxis, float fDeg, Vector3& vOrigin ) +{ + // this is a placeholder to call the general texture locking algorithm + txlock_bRotation = true; + txl_nAxis = nAxis; + txl_fDeg = fDeg; + VectorCopy(vOrigin, txl_vOrigin); + TextureLockTransformation_BrushPrimit(f); +} + +// compute the new brush primit texture matrix for a transformation matrix and a flip order flag (change plane orientation) +// this matches the select_matrix algo used in select.cpp +// this needs to be called on the face BEFORE any geometric transformation +// it will compute the texture matrix that will represent the same texture on the face after the geometric transformation is done +void ApplyMatrix_BrushPrimit(face_t *f, Vector3 matrix[3], Vector3& origin) +{ + // this is a placeholder to call the general texture locking algorithm + txlock_bRotation = false; + VectorCopy(matrix[0], txl_matrix[0]); + VectorCopy(matrix[1], txl_matrix[1]); + VectorCopy(matrix[2], txl_matrix[2]); + VectorCopy(origin, txl_origin); + TextureLockTransformation_BrushPrimit(f); +} +#endif + +// don't do C==A! +void BPMatMul(float A[2][3], float B[2][3], float C[2][3]) +{ + C[0][0] = A[0][0]*B[0][0]+A[0][1]*B[1][0]; + C[1][0] = A[1][0]*B[0][0]+A[1][1]*B[1][0]; + C[0][1] = A[0][0]*B[0][1]+A[0][1]*B[1][1]; + C[1][1] = A[1][0]*B[0][1]+A[1][1]*B[1][1]; + C[0][2] = A[0][0]*B[0][2]+A[0][1]*B[1][2]+A[0][2]; + C[1][2] = A[1][0]*B[0][2]+A[1][1]*B[1][2]+A[1][2]; +} + +void BPMatDump(float A[2][3]) +{ + globalOutputStream() << "" << A[0][0] + << " " << A[0][1] + << " " << A[0][2] + << "\n" << A[1][0] + << " " << A[1][2] + << " " << A[1][2] + << "\n0 0 1\n"; +} + +void BPMatRotate(float A[2][3], float theta) +{ + float m[2][3]; + float aux[2][3]; + memset(&m, 0, sizeof(float)*6); + m[0][0] = static_cast(cos(degrees_to_radians(theta))); + m[0][1] = static_cast(-sin(degrees_to_radians(theta))); + m[1][0] = -m[0][1]; + m[1][1] = m[0][0]; + BPMatMul(A, m, aux); + BPMatCopy(aux,A); +} + +#if 0 // camera-relative texture shift +// get the relative axes of the current texturing +void BrushPrimit_GetRelativeAxes(face_t *f, Vector3& vecS, Vector3& vecT) +{ + float vS[2],vT[2]; + // first we compute them as expressed in plane axis base + // BP matrix has coordinates of plane axis base expressed in geometric axis base + // so we use the line vectors + vS[0] = f->brushprimit_texdef.coords[0][0]; + vS[1] = f->brushprimit_texdef.coords[0][1]; + vT[0] = f->brushprimit_texdef.coords[1][0]; + vT[1] = f->brushprimit_texdef.coords[1][1]; + // now compute those vectors in geometric space + Vector3 texS, texT; // axis base of the plane (geometric) + ComputeAxisBase(f->plane.normal, texS, texT); + // vecS[] = vS[0].texS[] + vS[1].texT[] + // vecT[] = vT[0].texS[] + vT[1].texT[] + vecS[0] = vS[0]*texS[0] + vS[1]*texT[0]; + vecS[1] = vS[0]*texS[1] + vS[1]*texT[1]; + vecS[2] = vS[0]*texS[2] + vS[1]*texT[2]; + vecT[0] = vT[0]*texS[0] + vT[1]*texT[0]; + vecT[1] = vT[0]*texS[1] + vT[1]*texT[1]; + vecT[2] = vT[0]*texS[2] + vT[1]*texT[2]; +} + +// brush primitive texture adjustments, use the camera view to map adjustments +// ShiftTextureRelative_BrushPrimit ( s , t ) will shift relative to the texture +void ShiftTextureRelative_Camera(face_t *f, int x, int y) +{ + Vector3 vecS, vecT; + float XY[2]; // the values we are going to send for translation + float sgn[2]; // +1 or -1 + int axis[2]; + CamWnd* pCam; + + // get the two relative texture axes for the current texturing + BrushPrimit_GetRelativeAxes(f, vecS, vecT); + + // center point of the face, project it on the camera space + Vector3 C; + VectorClear(C); + int i; + for (i=0; iface_winding->numpoints; i++) + { + VectorAdd(C,f->face_winding->point_at(i),C); + } + VectorScale(C,1.0/f->face_winding->numpoints,C); + + pCam = g_pParentWnd->GetCamWnd(); + pCam->MatchViewAxes(C, vecS, axis[0], sgn[0]); + pCam->MatchViewAxes(C, vecT, axis[1], sgn[1]); + + // this happens when the two directions can't be mapped on two different directions on the screen + // then the move will occur against a single axis + // (i.e. the user is not positioned well enough to send understandable shift commands) + // NOTE: in most cases this warning is not very relevant because the user would use one of the two axes + // for which the solution is easy (the other one being unknown) + // so this warning could be removed + if (axis[0] == axis[1]) + globalOutputStream() << "Warning: degenerate in ShiftTextureRelative_Camera\n"; + + // compute the X Y geometric increments + // those geometric increments will be applied along the texture axes (the ones we computed above) + XY[0] = 0; + XY[1] = 0; + if (x!=0) + { + // moving right/left + XY[axis[0]] += sgn[0]*x; + } + if (y!=0) + { + XY[axis[1]] += sgn[1]*y; + } + // we worked out a move along vecS vecT, and we now it's geometric amplitude + // apply it + ShiftTextureRelative_BrushPrimit(f, XY[0], XY[1]); +} +#endif + + +void BPTexdef_Assign(brushprimit_texdef_t& bp_td, const brushprimit_texdef_t& bp_other) +{ + bp_td = bp_other; +} + +void BPTexdef_Shift(brushprimit_texdef_t& bp_td, float s, float t) +{ + // shift a texture (texture adjustments) along it's current texture axes + // x and y are geometric values, which we must compute as ST increments + // this depends on the texture size and the pixel/texel ratio + // as a ratio against texture size + // the scale of the texture is not relevant here (we work directly on a transformation from the base vectors) + bp_td.coords[0][2] -= s; + bp_td.coords[1][2] += t; +} + +void BPTexdef_Scale(brushprimit_texdef_t& bp_td, float s, float t) +{ + // apply same scale as the spinner button of the surface inspector + texdef_t texdef; + // compute fake shift scale rot + TexMatToFakeTexCoords( bp_td, texdef ); + // update + texdef.scale[0] += s; + texdef.scale[1] += t; + // compute new normalized texture matrix + FakeTexCoordsToTexMat( texdef, bp_td ); +} + +void BPTexdef_Rotate(brushprimit_texdef_t& bp_td, float angle) +{ + // apply same scale as the spinner button of the surface inspector + texdef_t texdef; + // compute fake shift scale rot + TexMatToFakeTexCoords( bp_td, texdef ); + // update + texdef.rotate += angle; + // compute new normalized texture matrix + FakeTexCoordsToTexMat( texdef, bp_td ); +} + +void BPTexdef_Construct(brushprimit_texdef_t& bp_td, std::size_t width, std::size_t height) +{ + bp_td.coords[0][0] = 1.0f; + bp_td.coords[1][1] = 1.0f; + ConvertTexMatWithDimensions(bp_td.coords, 2, 2, bp_td.coords, width, height); +} + +void Texdef_Assign(TextureProjection& projection, const TextureProjection& other) +{ + if (g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) + { + BPTexdef_Assign(projection.m_brushprimit_texdef, other.m_brushprimit_texdef); + } + else + { + Texdef_Assign(projection.m_texdef, other.m_texdef); + if(g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_HALFLIFE) + { + projection.m_basis_s = other.m_basis_s; + projection.m_basis_t = other.m_basis_t; + } + } +} + +void Texdef_Shift(TextureProjection& projection, float s, float t) +{ + if (g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) + { + BPTexdef_Shift(projection.m_brushprimit_texdef, s, t); + } + else + { + Texdef_Shift(projection.m_texdef, s, t); + } +} + +void Texdef_Scale(TextureProjection& projection, float s, float t) +{ + if (g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) + { + BPTexdef_Scale(projection.m_brushprimit_texdef, s, t); + } + else + { + Texdef_Scale(projection.m_texdef, s, t); + } +} + +void Texdef_Rotate(TextureProjection& projection, float angle) +{ + if (g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) + { + BPTexdef_Rotate(projection.m_brushprimit_texdef, angle); + } + else + { + Texdef_Rotate(projection.m_texdef, angle); + } +} + +void Texdef_FitTexture(TextureProjection& projection, std::size_t width, std::size_t height, const Vector3& normal, const Winding& w, float s_repeat, float t_repeat) +{ + if(w.numpoints < 3) + { + return; + } + + Matrix4 st2tex; + Texdef_toTransform(projection, (float)width, (float)height, st2tex); + + // the current texture transform + Matrix4 local2tex = st2tex; + { + Matrix4 xyz2st; + Texdef_basisForNormal(projection, normal, xyz2st); + matrix4_multiply_by_matrix4(local2tex, xyz2st); + } + + // the bounds of the current texture transform + AABB bounds; + for(Winding::const_iterator i = w.begin(); i != w.end(); ++i) + { + Vector3 texcoord = matrix4_transformed_point(local2tex, (*i).vertex); + aabb_extend_by_point_safe(bounds, texcoord); + } + bounds.origin.z() = 0; + bounds.extents.z() = 1; + + // the bounds of a perfectly fitted texture transform + AABB perfect(Vector3(s_repeat * 0.5, t_repeat * 0.5, 0), Vector3(s_repeat * 0.5, t_repeat * 0.5, 1)); + + // the difference between the current texture transform and the perfectly fitted transform + Matrix4 matrix(matrix4_translation_for_vec3(bounds.origin - perfect.origin)); + matrix4_pivoted_scale_by_vec3(matrix, bounds.extents / perfect.extents, perfect.origin); + matrix4_affine_invert(matrix); + + // apply the difference to the current texture transform + matrix4_premultiply_by_matrix4(st2tex, matrix); + + Texdef_fromTransform(projection, (float)width, (float)height, st2tex); + Texdef_normalise(projection, (float)width, (float)height); +} + +float Texdef_getDefaultTextureScale() +{ + return g_texdef_default_scale; +} + +void TexDef_Construct_Default(TextureProjection& projection) +{ + projection.m_texdef.scale[0] = Texdef_getDefaultTextureScale(); + projection.m_texdef.scale[1] = Texdef_getDefaultTextureScale(); + + if(g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) + { + FakeTexCoordsToTexMat(projection.m_texdef, projection.m_brushprimit_texdef); + } +} + + + +void ShiftScaleRotate_fromFace(texdef_t& shiftScaleRotate, const TextureProjection& projection) +{ + if(g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) + { + TexMatToFakeTexCoords(projection.m_brushprimit_texdef, shiftScaleRotate); + } + else + { + shiftScaleRotate = projection.m_texdef; + } +} + +void ShiftScaleRotate_toFace(const texdef_t& shiftScaleRotate, TextureProjection& projection) +{ + if (g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) + { + // compute texture matrix + // the matrix returned must be understood as a qtexture_t with width=2 height=2 + FakeTexCoordsToTexMat( shiftScaleRotate, projection.m_brushprimit_texdef ); + } + else + { + projection.m_texdef = shiftScaleRotate; + } +} + + +inline void print_vector3(const Vector3& v) +{ + globalOutputStream() << "( " << v.x() << " " << v.y() << " " << v.z() << " )\n"; +} + +inline void print_3x3(const Matrix4& m) +{ + globalOutputStream() << "( " << m.xx() << " " << m.xy() << " " << m.xz() << " ) " + << "( " << m.yx() << " " << m.yy() << " " << m.yz() << " ) " + << "( " << m.zx() << " " << m.zy() << " " << m.zz() << " )\n"; +} + + +inline Matrix4 matrix4_rotation_for_vector3(const Vector3& x, const Vector3& y, const Vector3& z) +{ + return Matrix4( + x.x(), x.y(), x.z(), 0, + y.x(), y.y(), y.z(), 0, + z.x(), z.y(), z.z(), 0, + 0, 0, 0, 1 + ); +} + +inline Matrix4 matrix4_swap_axes(const Vector3& from, const Vector3& to) +{ + if(from.x() != 0 && to.y() != 0) + { + return matrix4_rotation_for_vector3(to, from, g_vector3_axis_z); + } + + if(from.x() != 0 && to.z() != 0) + { + return matrix4_rotation_for_vector3(to, g_vector3_axis_y, from); + } + + if(from.y() != 0 && to.z() != 0) + { + return matrix4_rotation_for_vector3(g_vector3_axis_x, to, from); + } + + if(from.y() != 0 && to.x() != 0) + { + return matrix4_rotation_for_vector3(from, to, g_vector3_axis_z); + } + + if(from.z() != 0 && to.x() != 0) + { + return matrix4_rotation_for_vector3(from, g_vector3_axis_y, to); + } + + if(from.z() != 0 && to.y() != 0) + { + return matrix4_rotation_for_vector3(g_vector3_axis_x, from, to); + } + + ERROR_MESSAGE("unhandled axis swap case"); + + return g_matrix4_identity; +} + +inline Matrix4 matrix4_reflection_for_plane(const Plane3& plane) +{ + return Matrix4( + static_cast(1 - (2 * plane.a * plane.a)), + static_cast(-2 * plane.a * plane.b), + static_cast(-2 * plane.a * plane.c), + 0, + static_cast(-2 * plane.b * plane.a), + static_cast(1 - (2 * plane.b * plane.b)), + static_cast(-2 * plane.b * plane.c), + 0, + static_cast(-2 * plane.c * plane.a), + static_cast(-2 * plane.c * plane.b), + static_cast(1 - (2 * plane.c * plane.c)), + 0, + static_cast(-2 * plane.d * plane.a), + static_cast(-2 * plane.d * plane.b), + static_cast(-2 * plane.d * plane.c), + 1 + ); +} + +inline Matrix4 matrix4_reflection_for_plane45(const Plane3& plane, const Vector3& from, const Vector3& to) +{ + Vector3 first = from; + Vector3 second = to; + + if(vector3_dot(from, plane.normal()) > 0 == vector3_dot(to, plane.normal()) > 0) + { + first = vector3_negated(first); + second = vector3_negated(second); + } + +#if 0 + globalOutputStream() << "normal: "; + print_vector3(plane.normal()); + + globalOutputStream() << "from: "; + print_vector3(first); + + globalOutputStream() << "to: "; + print_vector3(second); +#endif + + Matrix4 swap = matrix4_swap_axes(first, second); + + Matrix4 tmp = matrix4_reflection_for_plane(plane); + + swap.tx() = -static_cast(-2 * plane.a * plane.d); + swap.ty() = -static_cast(-2 * plane.b * plane.d); + swap.tz() = -static_cast(-2 * plane.c * plane.d); + + return swap; +} + +void Texdef_transformLocked(TextureProjection& projection, std::size_t width, std::size_t height, const Plane3& plane, const Matrix4& identity2transformed) +{ + //globalOutputStream() << "identity2transformed: " << identity2transformed << "\n"; + + //globalOutputStream() << "plane.normal(): " << plane.normal() << "\n"; + + Vector3 normalTransformed(matrix4_transformed_direction(identity2transformed, plane.normal())); + + //globalOutputStream() << "normalTransformed: " << normalTransformed << "\n"; + + // identity: identity space + // transformed: transformation + // stIdentity: base st projection space before transformation + // stTransformed: base st projection space after transformation + // stOriginal: original texdef space + + // stTransformed2stOriginal = stTransformed -> transformed -> identity -> stIdentity -> stOriginal + + Matrix4 identity2stIdentity; + Texdef_basisForNormal(projection, plane.normal(), identity2stIdentity); + //globalOutputStream() << "identity2stIdentity: " << identity2stIdentity << "\n"; + + if(g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_HALFLIFE) + { + matrix4_transform_direction(identity2transformed, projection.m_basis_s); + matrix4_transform_direction(identity2transformed, projection.m_basis_t); + } + + Matrix4 transformed2stTransformed; + Texdef_basisForNormal(projection, normalTransformed, transformed2stTransformed); + + Matrix4 stTransformed2identity(matrix4_affine_inverse(matrix4_multiplied_by_matrix4(transformed2stTransformed, identity2transformed))); + + Vector3 originalProjectionAxis(vector4_to_vector3(matrix4_affine_inverse(identity2stIdentity).z())); + + Vector3 transformedProjectionAxis(vector4_to_vector3(stTransformed2identity.z())); + + Matrix4 stIdentity2stOriginal; + Texdef_toTransform(projection, (float)width, (float)height, stIdentity2stOriginal); + Matrix4 identity2stOriginal(matrix4_multiplied_by_matrix4(stIdentity2stOriginal, identity2stIdentity)); + + //globalOutputStream() << "originalProj: " << originalProjectionAxis << "\n"; + //globalOutputStream() << "transformedProj: " << transformedProjectionAxis << "\n"; + double dot = vector3_dot(originalProjectionAxis, transformedProjectionAxis); + //globalOutputStream() << "dot: " << dot << "\n"; + if(dot == 0) + { + // The projection axis chosen for the transformed normal is at 90 degrees + // to the transformed projection axis chosen for the original normal. + // This happens when the projection axis is ambiguous - e.g. for the plane + // 'X == Y' the projection axis could be either X or Y. + //globalOutputStream() << "flipped\n"; +#if 0 + globalOutputStream() << "projection off by 90\n"; + globalOutputStream() << "normal: "; + print_vector3(plane.normal()); + globalOutputStream() << "original projection: "; + print_vector3(originalProjectionAxis); + globalOutputStream() << "transformed projection: "; + print_vector3(transformedProjectionAxis); +#endif + + Matrix4 identityCorrected = matrix4_reflection_for_plane45(plane, originalProjectionAxis, transformedProjectionAxis); + + identity2stOriginal = matrix4_multiplied_by_matrix4(identity2stOriginal, identityCorrected); + } + + Matrix4 stTransformed2stOriginal = matrix4_multiplied_by_matrix4(identity2stOriginal, stTransformed2identity); + + Texdef_fromTransform(projection, (float)width, (float)height, stTransformed2stOriginal); + Texdef_normalise(projection, (float)width, (float)height); +} + +#if 1 +void Q3_to_matrix(const texdef_t& texdef, float width, float height, const Vector3& normal, Matrix4& matrix) +{ + Normal_GetTransform(normal, matrix); + + Matrix4 transform; + + Texdef_toTransform(texdef, width, height, transform); + + matrix4_multiply_by_matrix4(matrix, transform); +} + +void BP_from_matrix(brushprimit_texdef_t& bp_texdef, const Vector3& normal, const Matrix4& transform) +{ + Matrix4 basis; + basis = g_matrix4_identity; + ComputeAxisBase(normal, vector4_to_vector3(basis.x()), vector4_to_vector3(basis.y())); + vector4_to_vector3(basis.z()) = normal; + matrix4_transpose(basis); + matrix4_affine_invert(basis); + + Matrix4 basis2texture = matrix4_multiplied_by_matrix4(basis, transform); + + BPTexdef_fromTransform(bp_texdef, basis2texture); +} + +void Q3_to_BP(const texdef_t& texdef, float width, float height, const Vector3& normal, brushprimit_texdef_t& bp_texdef) +{ + Matrix4 matrix; + Q3_to_matrix(texdef, width, height, normal, matrix); + BP_from_matrix(bp_texdef, normal, matrix); +} +#endif diff --git a/radiant/brush_primit.h b/radiant/brush_primit.h new file mode 100644 index 00000000..897f19f9 --- /dev/null +++ b/radiant/brush_primit.h @@ -0,0 +1,140 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_BRUSH_PRIMIT_H) +#define INCLUDED_BRUSH_PRIMIT_H + +#include "math/vector.h" +#include "itexdef.h" +#include "debugging/debugging.h" +// Timo +// new brush primitive texdef +struct brushprimit_texdef_t +{ + brushprimit_texdef_t() + { + coords[0][0] = 2.0f; + coords[0][1] = 0.f; + coords[0][2] = 0.f; + coords[1][0] = 0.f; + coords[1][1] = 2.0f; + coords[1][2] = 0.f; + } + void removeScale(std::size_t width, std::size_t height) + { +#if 1 + coords[0][0] *= width; + coords[0][1] *= width; + coords[0][2] *= width; + coords[1][0] *= height; + coords[1][1] *= height; + coords[1][2] *= height; +#endif + } + void addScale(std::size_t width, std::size_t height) + { +#if 1 + ASSERT_MESSAGE(width > 0, "shader-width is 0"); + ASSERT_MESSAGE(height > 0, "shader-height is 0"); + coords[0][0] /= width; + coords[0][1] /= width; + coords[0][2] /= width; + coords[1][0] /= height; + coords[1][1] /= height; + coords[1][2] /= height; +#endif + } + float coords[2][3]; +}; + +class TextureProjection +{ +public: + texdef_t m_texdef; + brushprimit_texdef_t m_brushprimit_texdef; + Vector3 m_basis_s; + Vector3 m_basis_t; + + TextureProjection() + { + } + TextureProjection( + const texdef_t& texdef, + const brushprimit_texdef_t& brushprimit_texdef, + const Vector3& basis_s, + const Vector3& basis_t + ) : + m_texdef(texdef), + m_brushprimit_texdef(brushprimit_texdef), + m_basis_s(basis_s), + m_basis_t(basis_t) + { + } +}; + +float Texdef_getDefaultTextureScale(); + +class texdef_t; +struct Winding; +template class BasicVector3; +typedef BasicVector3 Vector3; +template class BasicVector4; +typedef BasicVector4 Vector4; +typedef Vector4 Quaternion; +class Matrix4; +class Plane3; + +void Normal_GetTransform(const Vector3& normal, Matrix4& transform); + +void TexDef_Construct_Default(TextureProjection& projection); + +void Texdef_Assign(TextureProjection& projection, const TextureProjection& other); +void Texdef_Shift(TextureProjection& projection, float s, float t); +void Texdef_Scale(TextureProjection& projection, float s, float t); +void Texdef_Rotate(TextureProjection& projection, float angle); +void Texdef_FitTexture(TextureProjection& projection, std::size_t width, std::size_t height, const Vector3& normal, const Winding& w, float s_repeat, float t_repeat); +void Texdef_EmitTextureCoordinates(const TextureProjection& projection, std::size_t width, std::size_t height, Winding& w, const Vector3& normal, const Matrix4& localToWorld); + +void ShiftScaleRotate_fromFace(texdef_t& shiftScaleRotate, const TextureProjection& projection); +void ShiftScaleRotate_toFace(const texdef_t& shiftScaleRotate, TextureProjection& projection); + +void Texdef_transformLocked(TextureProjection& projection, std::size_t width, std::size_t height, const Plane3& plane, const Matrix4& transform); +void Texdef_normalise(TextureProjection& projection, float width, float height); + +enum TexdefTypeId +{ + TEXDEFTYPEID_QUAKE, + TEXDEFTYPEID_BRUSHPRIMITIVES, + TEXDEFTYPEID_HALFLIFE, +}; + +struct bp_globals_t +{ + // tells if we are internally using brush primitive (texture coordinates and map format) + // this is a shortcut for IntForKey( g_qeglobals.d_project_entity, "brush_primit" ) + // NOTE: must keep the two ones in sync + TexdefTypeId m_texdefTypeId; +}; + +extern bp_globals_t g_bp_globals; +extern float g_texdef_default_scale; + +#endif diff --git a/radiant/brushmanip.cpp b/radiant/brushmanip.cpp new file mode 100644 index 00000000..441f099d --- /dev/null +++ b/radiant/brushmanip.cpp @@ -0,0 +1,1490 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "brushmanip.h" + + +#include "gtkutil/widget.h" +#include "gtkutil/menu.h" +#include "gtkmisc.h" +#include "brushnode.h" +#include "map.h" +#include "texwindow.h" +#include "gtkdlgs.h" +#include "commands.h" +#include "mainframe.h" +#include "dialog.h" +#include "xywindow.h" +#include "preferences.h" + +#include + +void Brush_ConstructCuboid(Brush& brush, const AABB& bounds, const char* shader, const TextureProjection& projection) +{ + const unsigned char box[3][2] = { { 0, 1 }, { 2, 0 }, { 1, 2 } }; + Vector3 mins(vector3_subtracted(bounds.origin, bounds.extents)); + Vector3 maxs(vector3_added(bounds.origin, bounds.extents)); + + brush.clear(); + brush.reserve(6); + + { + for(int i=0; i < 3; ++i) + { + Vector3 planepts1(maxs); + Vector3 planepts2(maxs); + planepts2[box[i][0]] = mins[box[i][0]]; + planepts1[box[i][1]] = mins[box[i][1]]; + + brush.addPlane(maxs, planepts1, planepts2, shader, projection); + } + } + { + for(int i=0; i < 3; ++i) + { + Vector3 planepts1(mins); + Vector3 planepts2(mins); + planepts1[box[i][0]] = maxs[box[i][0]]; + planepts2[box[i][1]] = maxs[box[i][1]]; + + brush.addPlane(mins, planepts1, planepts2, shader, projection); + } + } +} + +inline float max_extent(const Vector3& extents) +{ + return std::max(std::max(extents[0], extents[1]), extents[2]); +} + +inline float max_extent_2d(const Vector3& extents, int axis) +{ + switch(axis) + { + case 0: + return std::max(extents[1], extents[2]); + case 1: + return std::max(extents[0], extents[2]); + default: + return std::max(extents[0], extents[1]); + } +} + +const std::size_t c_brushPrism_minSides = 3; +const std::size_t c_brushPrism_maxSides = c_brush_maxFaces - 2; +const char* const c_brushPrism_name = "brushPrism"; + +void Brush_ConstructPrism(Brush& brush, const AABB& bounds, std::size_t sides, int axis, const char* shader, const TextureProjection& projection) +{ + if(sides < c_brushPrism_minSides) + { + globalErrorStream() << c_brushPrism_name << ": sides " << Unsigned(sides) << ": too few sides, minimum is " << Unsigned(c_brushPrism_minSides) << "\n"; + return; + } + if(sides > c_brushPrism_maxSides) + { + globalErrorStream() << c_brushPrism_name << ": sides " << Unsigned(sides) << ": too many sides, maximum is " << Unsigned(c_brushPrism_maxSides) << "\n"; + return; + } + + brush.clear(); + brush.reserve(sides+2); + + Vector3 mins(vector3_subtracted(bounds.origin, bounds.extents)); + Vector3 maxs(vector3_added(bounds.origin, bounds.extents)); + + float radius = max_extent_2d(bounds.extents, axis); + const Vector3& mid = bounds.origin; + Vector3 planepts[3]; + + planepts[2][(axis+1)%3] = mins[(axis+1)%3]; + planepts[2][(axis+2)%3] = mins[(axis+2)%3]; + planepts[2][axis] = maxs[axis]; + planepts[1][(axis+1)%3] = maxs[(axis+1)%3]; + planepts[1][(axis+2)%3] = mins[(axis+2)%3]; + planepts[1][axis] = maxs[axis]; + planepts[0][(axis+1)%3] = maxs[(axis+1)%3]; + planepts[0][(axis+2)%3] = maxs[(axis+2)%3]; + planepts[0][axis] = maxs[axis]; + + brush.addPlane(planepts[0], planepts[1], planepts[2], shader, projection); + + planepts[0][(axis+1)%3] = mins[(axis+1)%3]; + planepts[0][(axis+2)%3] = mins[(axis+2)%3]; + planepts[0][axis] = mins[axis]; + planepts[1][(axis+1)%3] = maxs[(axis+1)%3]; + planepts[1][(axis+2)%3] = mins[(axis+2)%3]; + planepts[1][axis] = mins[axis]; + planepts[2][(axis+1)%3] = maxs[(axis+1)%3]; + planepts[2][(axis+2)%3] = maxs[(axis+2)%3]; + planepts[2][axis] = mins[axis]; + + brush.addPlane(planepts[0], planepts[1], planepts[2], shader, projection); + + for (std::size_t i=0 ; i(floor(mid[(axis+1)%3]+radius*cv+0.5)); + planepts[0][(axis+2)%3] = static_cast(floor(mid[(axis+2)%3]+radius*sv+0.5)); + planepts[0][axis] = mins[axis]; + + planepts[1][(axis+1)%3] = planepts[0][(axis+1)%3]; + planepts[1][(axis+2)%3] = planepts[0][(axis+2)%3]; + planepts[1][axis] = maxs[axis]; + + planepts[2][(axis+1)%3] = static_cast(floor(planepts[0][(axis+1)%3] - radius*sv + 0.5)); + planepts[2][(axis+2)%3] = static_cast(floor(planepts[0][(axis+2)%3] + radius*cv + 0.5)); + planepts[2][axis] = maxs[axis]; + + brush.addPlane(planepts[0], planepts[1], planepts[2], shader, projection); + } +} + +const std::size_t c_brushCone_minSides = 3; +const std::size_t c_brushCone_maxSides = 32; +const char* const c_brushCone_name = "brushCone"; + +void Brush_ConstructCone(Brush& brush, const AABB& bounds, std::size_t sides, const char* shader, const TextureProjection& projection) +{ + if(sides < c_brushCone_minSides) + { + globalErrorStream() << c_brushCone_name << ": sides " << Unsigned(sides) << ": too few sides, minimum is " << Unsigned(c_brushCone_minSides) << "\n"; + return; + } + if(sides > c_brushCone_maxSides) + { + globalErrorStream() << c_brushCone_name << ": sides " << Unsigned(sides) << ": too many sides, maximum is " << Unsigned(c_brushCone_maxSides) << "\n"; + return; + } + + brush.clear(); + brush.reserve(sides+1); + + Vector3 mins(vector3_subtracted(bounds.origin, bounds.extents)); + Vector3 maxs(vector3_added(bounds.origin, bounds.extents)); + + float radius = max_extent(bounds.extents); + const Vector3& mid = bounds.origin; + Vector3 planepts[3]; + + planepts[0][0] = mins[0];planepts[0][1] = mins[1];planepts[0][2] = mins[2]; + planepts[1][0] = maxs[0];planepts[1][1] = mins[1];planepts[1][2] = mins[2]; + planepts[2][0] = maxs[0];planepts[2][1] = maxs[1];planepts[2][2] = mins[2]; + + brush.addPlane(planepts[0], planepts[1], planepts[2], shader, projection); + + for (std::size_t i=0 ; i(floor(mid[0]+radius*cv+0.5)); + planepts[0][1] = static_cast(floor(mid[1]+radius*sv+0.5)); + planepts[0][2] = mins[2]; + + planepts[1][0] = mid[0]; + planepts[1][1] = mid[1]; + planepts[1][2] = maxs[2]; + + planepts[2][0] = static_cast(floor(planepts[0][0] - radius * sv + 0.5)); + planepts[2][1] = static_cast(floor(planepts[0][1] + radius * cv + 0.5)); + planepts[2][2] = maxs[2]; + + brush.addPlane(planepts[0], planepts[1], planepts[2], shader, projection); + } +} + +const std::size_t c_brushSphere_minSides = 3; +const std::size_t c_brushSphere_maxSides = 7; +const char* const c_brushSphere_name = "brushSphere"; + +void Brush_ConstructSphere(Brush& brush, const AABB& bounds, std::size_t sides, const char* shader, const TextureProjection& projection) +{ + if(sides < c_brushSphere_minSides) + { + globalErrorStream() << c_brushSphere_name << ": sides " << Unsigned(sides) << ": too few sides, minimum is " << Unsigned(c_brushSphere_minSides) << "\n"; + return; + } + if(sides > c_brushSphere_maxSides) + { + globalErrorStream() << c_brushSphere_name << ": sides " << Unsigned(sides) << ": too many sides, maximum is " << Unsigned(c_brushSphere_maxSides) << "\n"; + return; + } + + brush.clear(); + brush.reserve(sides*sides); + + float radius = max_extent(bounds.extents); + const Vector3& mid = bounds.origin; + Vector3 planepts[3]; + + double dt = 2 * c_pi / sides; + double dp = c_pi / sides; + for(std::size_t i=0; i < sides; i++) + { + for(std::size_t j=0;j < sides-1; j++) + { + double t = i * dt; + double p = float(j * dp - c_pi / 2); + + planepts[0] = vector3_added(mid, vector3_scaled(vector3_for_spherical(t, p), radius)); + planepts[1] = vector3_added(mid, vector3_scaled(vector3_for_spherical(t, p + dp), radius)); + planepts[2] = vector3_added(mid, vector3_scaled(vector3_for_spherical(t + dt, p + dp), radius)); + + brush.addPlane(planepts[0], planepts[1], planepts[2], shader, projection); + } + } + + { + double p = (sides - 1) * dp - c_pi / 2; + for(std::size_t i = 0; i < sides; i++) + { + double t = i * dt; + + planepts[0] = vector3_added(mid, vector3_scaled(vector3_for_spherical(t, p), radius)); + planepts[1] = vector3_added(mid, vector3_scaled(vector3_for_spherical(t + dt, p + dp), radius)); + planepts[2] = vector3_added(mid, vector3_scaled(vector3_for_spherical(t + dt, p), radius)); + + brush.addPlane(planepts[0], planepts[1], planepts[2], shader, projection); + } + } +} + +int GetViewAxis() +{ + switch(GlobalXYWnd_getCurrentViewType()) + { + case XY: + return 2; + case XZ: + return 1; + case YZ: + return 0; + } + return 2; +} + +void Brush_ConstructPrefab(Brush& brush, EBrushPrefab type, const AABB& bounds, std::size_t sides, const char* shader, const TextureProjection& projection) +{ + switch(type) + { + case eBrushCuboid: + { + UndoableCommand undo("brushCuboid"); + + Brush_ConstructCuboid(brush, bounds, shader, projection); + } + break; + case eBrushPrism: + { + int axis = GetViewAxis(); + StringOutputStream command; + command << c_brushPrism_name << " -sides " << Unsigned(sides) << " -axis " << axis; + UndoableCommand undo(command.c_str()); + + Brush_ConstructPrism(brush, bounds, sides, axis, shader, projection); + } + break; + case eBrushCone: + { + StringOutputStream command; + command << c_brushCone_name << " -sides " << Unsigned(sides); + UndoableCommand undo(command.c_str()); + + Brush_ConstructCone(brush, bounds, sides, shader, projection); + } + break; + case eBrushSphere: + { + StringOutputStream command; + command << c_brushSphere_name << " -sides " << Unsigned(sides); + UndoableCommand undo(command.c_str()); + + Brush_ConstructSphere(brush, bounds, sides, shader, projection); + } + break; + } +} + + +void ConstructRegionBrushes(scene::Node* brushes[6], const Vector3& region_mins, const Vector3& region_maxs) +{ + { + // set mins + Vector3 mins(region_mins[0]-32, region_mins[1]-32, region_mins[2]-32); + + // vary maxs + for(std::size_t i=0; i<3; i++) + { + Vector3 maxs(region_maxs[0]+32, region_maxs[1]+32, region_maxs[2]+32); + maxs[i] = region_mins[i]; + Brush_ConstructCuboid(*Node_getBrush(*brushes[i]), aabb_for_minmax(mins, maxs), texdef_name_default(), TextureProjection()); + } + } + + { + // set maxs + Vector3 maxs(region_maxs[0]+32, region_maxs[1]+32, region_maxs[2]+32); + + // vary mins + for(std::size_t i=0; i<3; i++) + { + Vector3 mins(region_mins[0]-32, region_mins[1]-32, region_mins[2]-32); + mins[i] = region_maxs[i]; + Brush_ConstructCuboid(*Node_getBrush(*brushes[i+3]), aabb_for_minmax(mins, maxs), texdef_name_default(), TextureProjection()); + } + } +} + + +class FaceSetTexdef +{ + const TextureProjection& m_projection; +public: + FaceSetTexdef(const TextureProjection& projection) : m_projection(projection) + { + } + void operator()(Face& face) const + { + face.SetTexdef(m_projection); + } +}; + +void Scene_BrushSetTexdef_Selected(scene::Graph& graph, const TextureProjection& projection) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, FaceSetTexdef(projection)); + SceneChangeNotify(); +} + +void Scene_BrushSetTexdef_Component_Selected(scene::Graph& graph, const TextureProjection& projection) +{ + Scene_ForEachSelectedBrushFace(graph, FaceSetTexdef(projection)); + SceneChangeNotify(); +} + + +class FaceSetFlags +{ + const ContentsFlagsValue& m_projection; +public: + FaceSetFlags(const ContentsFlagsValue& flags) : m_projection(flags) + { + } + void operator()(Face& face) const + { + face.SetFlags(m_projection); + } +}; + +void Scene_BrushSetFlags_Selected(scene::Graph& graph, const ContentsFlagsValue& flags) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, FaceSetFlags(flags)); + SceneChangeNotify(); +} + +void Scene_BrushSetFlags_Component_Selected(scene::Graph& graph, const ContentsFlagsValue& flags) +{ + Scene_ForEachSelectedBrushFace(graph, FaceSetFlags(flags)); + SceneChangeNotify(); +} + +class FaceShiftTexdef +{ + float m_s, m_t; +public: + FaceShiftTexdef(float s, float t) : m_s(s), m_t(t) + { + } + void operator()(Face& face) const + { + face.ShiftTexdef(m_s, m_t); + } +}; + +void Scene_BrushShiftTexdef_Selected(scene::Graph& graph, float s, float t) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, FaceShiftTexdef(s, t)); + SceneChangeNotify(); +} + +void Scene_BrushShiftTexdef_Component_Selected(scene::Graph& graph, float s, float t) +{ + Scene_ForEachSelectedBrushFace(graph, FaceShiftTexdef(s, t)); + SceneChangeNotify(); +} + +class FaceScaleTexdef +{ + float m_s, m_t; +public: + FaceScaleTexdef(float s, float t) : m_s(s), m_t(t) + { + } + void operator()(Face& face) const + { + face.ScaleTexdef(m_s, m_t); + } +}; + +void Scene_BrushScaleTexdef_Selected(scene::Graph& graph, float s, float t) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, FaceScaleTexdef(s, t)); + SceneChangeNotify(); +} + +void Scene_BrushScaleTexdef_Component_Selected(scene::Graph& graph, float s, float t) +{ + Scene_ForEachSelectedBrushFace(graph, FaceScaleTexdef(s, t)); + SceneChangeNotify(); +} + +class FaceRotateTexdef +{ + float m_angle; +public: + FaceRotateTexdef(float angle) : m_angle(angle) + { + } + void operator()(Face& face) const + { + face.RotateTexdef(m_angle); + } +}; + +void Scene_BrushRotateTexdef_Selected(scene::Graph& graph, float angle) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, FaceRotateTexdef(angle)); + SceneChangeNotify(); +} + +void Scene_BrushRotateTexdef_Component_Selected(scene::Graph& graph, float angle) +{ + Scene_ForEachSelectedBrushFace(graph, FaceRotateTexdef(angle)); + SceneChangeNotify(); +} + + +class FaceSetShader +{ + const char* m_name; +public: + FaceSetShader(const char* name) : m_name(name) {} + void operator()(Face& face) const + { + face.SetShader(m_name); + } +}; + +void Scene_BrushSetShader_Selected(scene::Graph& graph, const char* name) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, FaceSetShader(name)); + SceneChangeNotify(); +} + +void Scene_BrushSetShader_Component_Selected(scene::Graph& graph, const char* name) +{ + Scene_ForEachSelectedBrushFace(graph, FaceSetShader(name)); + SceneChangeNotify(); +} + +class FaceSetDetail +{ + bool m_detail; +public: + FaceSetDetail(bool detail) : m_detail(detail) + { + } + void operator()(Face& face) const + { + face.setDetail(m_detail); + } +}; + +void Scene_BrushSetDetail_Selected(scene::Graph& graph, bool detail) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, FaceSetDetail(detail)); + SceneChangeNotify(); +} + +bool Face_FindReplaceShader(Face& face, const char* find, const char* replace) +{ + if(shader_equal(face.GetShader(), find)) + { + face.SetShader(replace); + return true; + } + return false; +} + +class FaceFindReplaceShader +{ + const char* m_find; + const char* m_replace; +public: + FaceFindReplaceShader(const char* find, const char* replace) : m_find(find), m_replace(replace) + { + } + void operator()(Face& face) const + { + Face_FindReplaceShader(face, m_find, m_replace); + } +}; + +class FaceFindShader +{ + const char* m_find; + const char* m_replace; +public: + FaceFindShader(const char* find) : m_find(find) + { + } + void operator()(FaceInstance& faceinst) const + { + if(shader_equal(faceinst.getFace().GetShader(), m_find)) + { + faceinst.setSelected(SelectionSystem::eFace, true); + } + } +}; + +bool DoingSearch(const char *repl) +{ + return (repl == NULL || (strcmp("textures/", repl)==0)); +} + +void Scene_BrushFindReplaceShader(scene::Graph& graph, const char* find, const char* replace) +{ + if (DoingSearch(replace)) + { + Scene_ForEachBrush_ForEachFaceInstance(graph, FaceFindShader(find)); + } + else + { + Scene_ForEachBrush_ForEachFace(graph, FaceFindReplaceShader(find, replace)); + } +} + +void Scene_BrushFindReplaceShader_Selected(scene::Graph& graph, const char* find, const char* replace) +{ + if (DoingSearch(replace)) + { + Scene_ForEachSelectedBrush_ForEachFaceInstance(graph, + FaceFindShader(find)); + } + else + { + Scene_ForEachSelectedBrush_ForEachFace(graph, + FaceFindReplaceShader(find, replace)); + } +} + +// TODO: find for components +// d1223m: dont even know what they are... +void Scene_BrushFindReplaceShader_Component_Selected(scene::Graph& graph, const char* find, const char* replace) +{ + if (DoingSearch(replace)) + { + + } + else + { + Scene_ForEachSelectedBrushFace(graph, FaceFindReplaceShader(find, replace)); + } +} + + +class FaceFitTexture +{ + float m_s_repeat, m_t_repeat; +public: + FaceFitTexture(float s_repeat, float t_repeat) : m_s_repeat(s_repeat), m_t_repeat(t_repeat) + { + } + void operator()(Face& face) const + { + face.FitTexture(m_s_repeat, m_t_repeat); + } +}; + +void Scene_BrushFitTexture_Selected(scene::Graph& graph, float s_repeat, float t_repeat) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, FaceFitTexture(s_repeat, t_repeat)); + SceneChangeNotify(); +} + +void Scene_BrushFitTexture_Component_Selected(scene::Graph& graph, float s_repeat, float t_repeat) +{ + Scene_ForEachSelectedBrushFace(graph, FaceFitTexture(s_repeat, t_repeat)); + SceneChangeNotify(); +} + +TextureProjection g_defaultTextureProjection; +const TextureProjection& TextureTransform_getDefault() +{ + TexDef_Construct_Default(g_defaultTextureProjection); + return g_defaultTextureProjection; +} + +void Scene_BrushConstructPrefab(scene::Graph& graph, EBrushPrefab type, std::size_t sides, const char* shader) +{ + if(GlobalSelectionSystem().countSelected() != 0) + { + const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path(); + + Brush* brush = Node_getBrush(path.top()); + if(brush != 0) + { + AABB bounds = brush->localAABB(); // copy bounds because the brush will be modified + Brush_ConstructPrefab(*brush, type, bounds, sides, shader, TextureTransform_getDefault()); + SceneChangeNotify(); + } + } +} + +void Scene_BrushResize_Selected(scene::Graph& graph, const AABB& bounds, const char* shader) +{ + if(GlobalSelectionSystem().countSelected() != 0) + { + const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path(); + + Brush* brush = Node_getBrush(path.top()); + if(brush != 0) + { + Brush_ConstructCuboid(*brush, bounds, shader, TextureTransform_getDefault()); + SceneChangeNotify(); + } + } +} + +bool Brush_hasShader(const Brush& brush, const char* name) +{ + for(Brush::const_iterator i = brush.begin(); i != brush.end(); ++i) + { + if(shader_equal((*i)->GetShader(), name)) + { + return true; + } + } + return false; +} + +class BrushSelectByShaderWalker : public scene::Graph::Walker +{ + const char* m_name; +public: + BrushSelectByShaderWalker(const char* name) + : m_name(name) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + Brush* brush = Node_getBrush(path.top()); + if(brush != 0 && Brush_hasShader(*brush, m_name)) + { + Instance_getSelectable(instance)->setSelected(true); + } + } + return true; + } +}; + +void Scene_BrushSelectByShader(scene::Graph& graph, const char* name) +{ + graph.traverse(BrushSelectByShaderWalker(name)); +} + +class FaceSelectByShader +{ + const char* m_name; +public: + FaceSelectByShader(const char* name) + : m_name(name) + { + } + void operator()(FaceInstance& face) const + { + printf("checking %s = %s\n", face.getFace().GetShader(), m_name); + if(shader_equal(face.getFace().GetShader(), m_name)) + { + face.setSelected(SelectionSystem::eFace, true); + } + } +}; + +void Scene_BrushSelectByShader_Component(scene::Graph& graph, const char* name) +{ + Scene_ForEachSelectedBrush_ForEachFaceInstance(graph, FaceSelectByShader(name)); +} + +class FaceGetTexdef +{ + TextureProjection& m_projection; + mutable bool m_done; +public: + FaceGetTexdef(TextureProjection& projection) + : m_projection(projection), m_done(false) + { + } + void operator()(Face& face) const + { + if(!m_done) + { + m_done = true; + face.GetTexdef(m_projection); + } + } +}; + + +void Scene_BrushGetTexdef_Selected(scene::Graph& graph, TextureProjection& projection) +{ + Scene_ForEachSelectedBrush_ForEachFace(graph, FaceGetTexdef(projection)); +} + +void Scene_BrushGetTexdef_Component_Selected(scene::Graph& graph, TextureProjection& projection) +{ +#if 1 + if(!g_SelectedFaceInstances.empty()) + { + FaceInstance& faceInstance = g_SelectedFaceInstances.last(); + faceInstance.getFace().GetTexdef(projection); + } +#else + FaceGetTexdef visitor(projection); + Scene_ForEachSelectedBrushFace(graph, visitor); +#endif +} + +void Scene_BrushGetShaderSize_Component_Selected(scene::Graph& graph, size_t& width, size_t& height) +{ + if(!g_SelectedFaceInstances.empty()) + { + FaceInstance& faceInstance = g_SelectedFaceInstances.last(); + width = faceInstance.getFace().getShader().width(); + height = faceInstance.getFace().getShader().height(); + } +} + + +class FaceGetFlags +{ + ContentsFlagsValue& m_flags; + mutable bool m_done; +public: + FaceGetFlags(ContentsFlagsValue& flags) + : m_flags(flags), m_done(false) + { + } + void operator()(Face& face) const + { + if(!m_done) + { + m_done = true; + face.GetFlags(m_flags); + } + } +}; + + +void Scene_BrushGetFlags_Selected(scene::Graph& graph, ContentsFlagsValue& flags) +{ +#if 1 + if(GlobalSelectionSystem().countSelected() != 0) + { + BrushInstance* brush = Instance_getBrush(GlobalSelectionSystem().ultimateSelected()); + if(brush != 0) + { + Brush_forEachFace(*brush, FaceGetFlags(flags)); + } + } +#else + Scene_ForEachSelectedBrush_ForEachFace(graph, FaceGetFlags(flags)); +#endif +} + +void Scene_BrushGetFlags_Component_Selected(scene::Graph& graph, ContentsFlagsValue& flags) +{ +#if 1 + if(!g_SelectedFaceInstances.empty()) + { + FaceInstance& faceInstance = g_SelectedFaceInstances.last(); + faceInstance.getFace().GetFlags(flags); + } +#else + Scene_ForEachSelectedBrushFace(graph, FaceGetFlags(flags)); +#endif +} + + +class FaceGetShader +{ + CopiedString& m_shader; + mutable bool m_done; +public: + FaceGetShader(CopiedString& shader) + : m_shader(shader), m_done(false) + { + } + void operator()(Face& face) const + { + if(!m_done) + { + m_done = true; + m_shader = face.GetShader(); + } + } +}; + +void Scene_BrushGetShader_Selected(scene::Graph& graph, CopiedString& shader) +{ +#if 1 + if(GlobalSelectionSystem().countSelected() != 0) + { + BrushInstance* brush = Instance_getBrush(GlobalSelectionSystem().ultimateSelected()); + if(brush != 0) + { + Brush_forEachFace(*brush, FaceGetShader(shader)); + } + } +#else + Scene_ForEachSelectedBrush_ForEachFace(graph, FaceGetShader(shader)); +#endif +} + +void Scene_BrushGetShader_Component_Selected(scene::Graph& graph, CopiedString& shader) +{ +#if 1 + if(!g_SelectedFaceInstances.empty()) + { + FaceInstance& faceInstance = g_SelectedFaceInstances.last(); + shader = faceInstance.getFace().GetShader(); + } +#else + FaceGetShader visitor(shader); + Scene_ForEachSelectedBrushFace(graph, visitor); +#endif +} + + +class filter_face_shader : public FaceFilter +{ + const char* m_shader; +public: + filter_face_shader(const char* shader) : m_shader(shader) + { + } + bool filter(const Face& face) const + { + return shader_equal(face.GetShader(), m_shader); + } +}; + +class filter_face_shader_prefix : public FaceFilter +{ + const char* m_prefix; +public: + filter_face_shader_prefix(const char* prefix) : m_prefix(prefix) + { + } + bool filter(const Face& face) const + { + return shader_equal_n(face.GetShader(), m_prefix, strlen(m_prefix)); + } +}; + +class filter_face_flags : public FaceFilter +{ + int m_flags; +public: + filter_face_flags(int flags) : m_flags(flags) + { + } + bool filter(const Face& face) const + { + return (face.getShader().shaderFlags() & m_flags) != 0; + } +}; + +class filter_face_contents : public FaceFilter +{ + int m_contents; +public: + filter_face_contents(int contents) : m_contents(contents) + { + } + bool filter(const Face& face) const + { + return (face.getShader().m_flags.m_contentFlags & m_contents) != 0; + } +}; + + + +class FaceFilterAny +{ + FaceFilter* m_filter; + bool& m_filtered; +public: + FaceFilterAny(FaceFilter* filter, bool& filtered) : m_filter(filter), m_filtered(filtered) + { + m_filtered = false; + } + void operator()(Face& face) const + { + if(m_filter->filter(face)) + { + m_filtered = true; + } + } +}; + +class filter_brush_any_face : public BrushFilter +{ + FaceFilter* m_filter; +public: + filter_brush_any_face(FaceFilter* filter) : m_filter(filter) + { + } + bool filter(const Brush& brush) const + { + bool filtered; + Brush_forEachFace(brush, FaceFilterAny(m_filter, filtered)); + return filtered; + } +}; + +class FaceFilterAll +{ + FaceFilter* m_filter; + bool& m_filtered; +public: + FaceFilterAll(FaceFilter* filter, bool& filtered) : m_filter(filter), m_filtered(filtered) + { + m_filtered = true; + } + void operator()(Face& face) const + { + if(!m_filter->filter(face)) + { + m_filtered = false; + } + } +}; + +class filter_brush_all_faces : public BrushFilter +{ + FaceFilter* m_filter; +public: + filter_brush_all_faces(FaceFilter* filter) : m_filter(filter) + { + } + bool filter(const Brush& brush) const + { + bool filtered; + Brush_forEachFace(brush, FaceFilterAll(m_filter, filtered)); + return filtered; + } +}; + + +filter_face_flags g_filter_face_clip(QER_CLIP); +filter_brush_all_faces g_filter_brush_clip(&g_filter_face_clip); + +filter_face_shader g_filter_face_clip_q2("textures/clip"); +filter_brush_all_faces g_filter_brush_clip_q2(&g_filter_face_clip_q2); + +filter_face_shader g_filter_face_weapclip("textures/common/weapclip"); +filter_brush_all_faces g_filter_brush_weapclip(&g_filter_face_weapclip); + +filter_face_shader g_filter_face_botclip("textures/common/botclip"); +filter_brush_all_faces g_filter_brush_botclip(&g_filter_face_botclip); + +filter_face_shader_prefix g_filter_face_caulk("textures/common/caulk"); +filter_brush_all_faces g_filter_brush_caulk(&g_filter_face_caulk); + +filter_face_shader_prefix g_filter_face_caulk_ja("textures/system/caulk"); +filter_brush_all_faces g_filter_brush_caulk_ja(&g_filter_face_caulk_ja); + +filter_face_shader_prefix g_filter_face_liquids("textures/liquids/"); +filter_brush_any_face g_filter_brush_liquids(&g_filter_face_liquids); + +filter_face_shader g_filter_face_hint("textures/common/hint"); +filter_brush_any_face g_filter_brush_hint(&g_filter_face_hint); + +filter_face_shader g_filter_face_hint_q2("textures/hint"); +filter_brush_any_face g_filter_brush_hint_q2(&g_filter_face_hint_q2); + +filter_face_shader g_filter_face_hint_ja("textures/system/hint"); +filter_brush_any_face g_filter_brush_hint_ja(&g_filter_face_hint_ja); + +filter_face_shader g_filter_face_areaportal("textures/common/areaportal"); +filter_brush_all_faces g_filter_brush_areaportal(&g_filter_face_areaportal); + +filter_face_shader g_filter_face_visportal("textures/editor/visportal"); +filter_brush_any_face g_filter_brush_visportal(&g_filter_face_visportal); + +filter_face_shader g_filter_face_clusterportal("textures/common/clusterportal"); +filter_brush_all_faces g_filter_brush_clusterportal(&g_filter_face_clusterportal); + +filter_face_shader g_filter_face_lightgrid("textures/common/lightgrid"); +filter_brush_all_faces g_filter_brush_lightgrid(&g_filter_face_lightgrid); + +filter_face_flags g_filter_face_translucent(QER_TRANS); +filter_brush_all_faces g_filter_brush_translucent(&g_filter_face_translucent); + +filter_face_contents g_filter_face_detail(BRUSH_DETAIL_MASK); +filter_brush_all_faces g_filter_brush_detail(&g_filter_face_detail); + + +void BrushFilters_construct() +{ + add_brush_filter(g_filter_brush_clip, EXCLUDE_CLIP); + add_brush_filter(g_filter_brush_clip_q2, EXCLUDE_CLIP); + add_brush_filter(g_filter_brush_weapclip, EXCLUDE_CLIP); + add_brush_filter(g_filter_brush_botclip, EXCLUDE_BOTCLIP); + add_brush_filter(g_filter_brush_caulk, EXCLUDE_CAULK); + add_brush_filter(g_filter_brush_caulk_ja, EXCLUDE_CAULK); + add_face_filter(g_filter_face_caulk, EXCLUDE_CAULK); + add_face_filter(g_filter_face_caulk_ja, EXCLUDE_CAULK); + add_brush_filter(g_filter_brush_liquids, EXCLUDE_LIQUIDS); + add_brush_filter(g_filter_brush_hint, EXCLUDE_HINTSSKIPS); + add_brush_filter(g_filter_brush_hint_q2, EXCLUDE_HINTSSKIPS); + add_brush_filter(g_filter_brush_hint_ja, EXCLUDE_HINTSSKIPS); + add_brush_filter(g_filter_brush_clusterportal, EXCLUDE_CLUSTERPORTALS); + add_brush_filter(g_filter_brush_visportal, EXCLUDE_VISPORTALS); + add_brush_filter(g_filter_brush_areaportal, EXCLUDE_AREAPORTALS); + add_brush_filter(g_filter_brush_translucent, EXCLUDE_TRANSLUCENT); + add_brush_filter(g_filter_brush_detail, EXCLUDE_DETAILS); + add_brush_filter(g_filter_brush_detail, EXCLUDE_STRUCTURAL, true); + add_brush_filter(g_filter_brush_lightgrid, EXCLUDE_LIGHTGRID); +} + +#if 0 + +void normalquantisation_draw() +{ + glPointSize(1); + glBegin(GL_POINTS); + for(std::size_t i = 0; i <= c_quantise_normal; ++i) + { + for(std::size_t j = 0; j <= c_quantise_normal; ++j) + { + Normal3f vertex(normal3f_normalised(Normal3f( + static_cast(c_quantise_normal - j - i), + static_cast(i), + static_cast(j) + ))); + VectorScale(normal3f_to_array(vertex), 64.f, normal3f_to_array(vertex)); + glVertex3fv(normal3f_to_array(vertex)); + vertex.x = -vertex.x; + glVertex3fv(normal3f_to_array(vertex)); + } + } + glEnd(); +} + +class RenderableNormalQuantisation : public OpenGLRenderable +{ +public: + void render(RenderStateFlags state) const + { + normalquantisation_draw(); + } +}; + +const float g_test_quantise_normal = 1.f / static_cast(1 << 3); + +class TestNormalQuantisation +{ + void check_normal(const Normal3f& normal, const Normal3f& other) + { + spherical_t spherical = spherical_from_normal3f(normal); + double longditude = RAD2DEG(spherical.longditude); + double latitude = RAD2DEG(spherical.latitude); + double x = cos(spherical.longditude) * sin(spherical.latitude); + double y = sin(spherical.longditude) * sin(spherical.latitude); + double z = cos(spherical.latitude); + + ASSERT_MESSAGE(normal3f_dot(normal, other) > 0.99, "bleh"); + } + + void test_normal(const Normal3f& normal) + { + Normal3f test = normal3f_from_spherical(spherical_from_normal3f(normal)); + check_normal(normal, test); + + EOctant octant = normal3f_classify_octant(normal); + Normal3f folded = normal3f_fold_octant(normal, octant); + ESextant sextant = normal3f_classify_sextant(folded); + folded = normal3f_fold_sextant(folded, sextant); + + double scale = static_cast(c_quantise_normal) / (folded.x + folded.y + folded.z); + + double zbits = folded.z * scale; + double ybits = folded.y * scale; + + std::size_t zbits_q = static_cast(zbits); + std::size_t ybits_q = static_cast(ybits); + + ASSERT_MESSAGE(zbits_q <= (c_quantise_normal / 8) * 3, "bleh"); + ASSERT_MESSAGE(ybits_q <= (c_quantise_normal / 2), "bleh"); + ASSERT_MESSAGE(zbits_q + ((c_quantise_normal / 2) - ybits_q) <= (c_quantise_normal / 2), "bleh"); + + std::size_t y_t = (zbits_q < (c_quantise_normal / 4)) ? ybits_q : (c_quantise_normal / 2) - ybits_q; + std::size_t z_t = (zbits_q < (c_quantise_normal / 4)) ? zbits_q : (c_quantise_normal / 2) - zbits_q; + std::size_t index = (c_quantise_normal / 4) * y_t + z_t; + ASSERT_MESSAGE(index <= (c_quantise_normal / 4)*(c_quantise_normal / 2), "bleh"); + + Normal3f tmp(c_quantise_normal - zbits_q - ybits_q, ybits_q, zbits_q); + tmp = normal3f_normalised(tmp); + + Normal3f unfolded = normal3f_unfold_octant(normal3f_unfold_sextant(tmp, sextant), octant); + + check_normal(normal, unfolded); + + double dot = normal3f_dot(normal, unfolded); + float length = VectorLength(normal3f_to_array(unfolded)); + float inv_length = 1 / length; + + Normal3f quantised = normal3f_quantised(normal); + check_normal(normal, quantised); + } + void test2(const Normal3f& normal, const Normal3f& other) + { + if(normal3f_quantised(normal) != normal3f_quantised(other)) + { + int bleh = 0; + } + } + + static Normal3f normalise(float x, float y, float z) + { + return normal3f_normalised(Normal3f(x, y, z)); + } + + float vec_rand() + { + return static_cast(rand() - (RAND_MAX/2)); + } + + Normal3f normal3f_rand() + { + return normalise(vec_rand(), vec_rand(), vec_rand()); + } + +public: + TestNormalQuantisation() + { + for(int i = 4096; i > 0; --i) + test_normal(normal3f_rand()); + + test_normal(normalise(1, 0, 0)); + test_normal(normalise(0, 1, 0)); + test_normal(normalise(0, 0, 1)); + test_normal(normalise(1, 1, 0)); + test_normal(normalise(1, 0, 1)); + test_normal(normalise(0, 1, 1)); + + test_normal(normalise(10000, 10000, 10000)); + test_normal(normalise(10000, 10000, 10001)); + test_normal(normalise(10000, 10000, 10002)); + test_normal(normalise(10000, 10000, 10010)); + test_normal(normalise(10000, 10000, 10020)); + test_normal(normalise(10000, 10000, 10030)); + test_normal(normalise(10000, 10000, 10100)); + test_normal(normalise(10000, 10000, 10101)); + test_normal(normalise(10000, 10000, 10102)); + test_normal(normalise(10000, 10000, 10200)); + test_normal(normalise(10000, 10000, 10201)); + test_normal(normalise(10000, 10000, 10202)); + test_normal(normalise(10000, 10000, 10203)); + test_normal(normalise(10000, 10000, 10300)); + + + test2(normalise(10000, 10000, 10000), normalise(10000, 10000, 10001)); + test2(normalise(10000, 10000, 10001), normalise(10000, 10001, 10000)); + } +}; + +TestNormalQuantisation g_testNormalQuantisation; + + +#endif + +#if 0 +class TestSelectableObserver : public observer_template +{ +public: + void notify(const Selectable& arguments) + { + bool bleh = arguments.isSelected(); + } +}; + +inline void test_bleh() +{ + TestSelectableObserver test; + ObservableSelectableInstance< SingleObservable< SelectionChangeCallback > > bleh; + bleh.attach(test); + bleh.setSelected(true); + bleh.detach(test); +} + +class TestBleh +{ +public: + TestBleh() + { + test_bleh(); + } +}; + +const TestBleh testbleh; +#endif + + +#if 0 +class TestRefcountedString +{ +public: + TestRefcountedString() + { + { + // copy construct + SmartString string1("string1"); + SmartString string2(string1); + SmartString string3(string2); + } + { + // refcounted assignment + SmartString string1("string1"); + SmartString string2("string2"); + string1 = string2; + } + { + // copy assignment + SmartString string1("string1"); + SmartString string2("string2"); + string1 = string2.c_str(); + } + { + // self-assignment + SmartString string1("string1"); + string1 = string1; + } + { + // self-assignment via another reference + SmartString string1("string1"); + SmartString string2(string1); + string1 = string2; + } + } +}; + +const TestRefcountedString g_testRefcountedString; + +#endif + +void Select_MakeDetail() +{ + UndoableCommand undo("brushSetDetail"); + Scene_BrushSetDetail_Selected(GlobalSceneGraph(), true); +} + +void Select_MakeStructural() +{ + UndoableCommand undo("brushClearDetail"); + Scene_BrushSetDetail_Selected(GlobalSceneGraph(), false); +} + +class BrushMakeSided +{ + std::size_t m_count; +public: + BrushMakeSided(std::size_t count) + : m_count(count) + { + } + void set() + { + Scene_BrushConstructPrefab(GlobalSceneGraph(), eBrushPrism, m_count, TextureBrowser_GetSelectedShader(GlobalTextureBrowser())); + } + typedef MemberCaller SetCaller; +}; + + +BrushMakeSided g_brushmakesided3(3); +BrushMakeSided g_brushmakesided4(4); +BrushMakeSided g_brushmakesided5(5); +BrushMakeSided g_brushmakesided6(6); +BrushMakeSided g_brushmakesided7(7); +BrushMakeSided g_brushmakesided8(8); +BrushMakeSided g_brushmakesided9(9); + +inline int axis_for_viewtype(int viewtype) +{ + switch(viewtype) + { + case XY: + return 2; + case XZ: + return 1; + case YZ: + return 0; + } + return 2; +} + +class BrushPrefab +{ + EBrushPrefab m_type; +public: + BrushPrefab(EBrushPrefab type) + : m_type(type) + { + } + void set() + { + DoSides(m_type, axis_for_viewtype(GetViewAxis())); + } + typedef MemberCaller SetCaller; +}; + +BrushPrefab g_brushprism(eBrushPrism); +BrushPrefab g_brushcone(eBrushCone); +BrushPrefab g_brushsphere(eBrushSphere); + + +void FlipClip(); +void SplitClip(); +void Clip(); +void OnClipMode(bool enable); +bool ClipMode(); + + +void ClipSelected() +{ + if(ClipMode()) + { + UndoableCommand undo("clipperClip"); + Clip(); + } +} + +void SplitSelected() +{ + if(ClipMode()) + { + UndoableCommand undo("clipperSplit"); + SplitClip(); + } +} + +void FlipClipper() +{ + FlipClip(); +} + + +Callback g_texture_lock_status_changed; +BoolExportCaller g_texdef_movelock_caller(g_brush_texturelock_enabled); +ToggleItem g_texdef_movelock_item(g_texdef_movelock_caller); + +void Texdef_ToggleMoveLock() +{ + g_brush_texturelock_enabled = !g_brush_texturelock_enabled; + g_texdef_movelock_item.update(); + g_texture_lock_status_changed(); +} + + + + + +void Brush_registerCommands() +{ + GlobalToggles_insert("TogTexLock", FreeCaller(), ToggleItem::AddCallbackCaller(g_texdef_movelock_item), Accelerator('T', (GdkModifierType)GDK_SHIFT_MASK)); + + GlobalCommands_insert("BrushPrism", BrushPrefab::SetCaller(g_brushprism)); + GlobalCommands_insert("BrushCone", BrushPrefab::SetCaller(g_brushcone)); + GlobalCommands_insert("BrushSphere", BrushPrefab::SetCaller(g_brushsphere)); + + GlobalCommands_insert("Brush3Sided", BrushMakeSided::SetCaller(g_brushmakesided3), Accelerator('3', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("Brush4Sided", BrushMakeSided::SetCaller(g_brushmakesided4), Accelerator('4', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("Brush5Sided", BrushMakeSided::SetCaller(g_brushmakesided5), Accelerator('5', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("Brush6Sided", BrushMakeSided::SetCaller(g_brushmakesided6), Accelerator('6', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("Brush7Sided", BrushMakeSided::SetCaller(g_brushmakesided7), Accelerator('7', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("Brush8Sided", BrushMakeSided::SetCaller(g_brushmakesided8), Accelerator('8', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("Brush9Sided", BrushMakeSided::SetCaller(g_brushmakesided9), Accelerator('9', (GdkModifierType)GDK_CONTROL_MASK)); + + GlobalCommands_insert("ClipSelected", FreeCaller(), Accelerator(GDK_Return)); + GlobalCommands_insert("SplitSelected", FreeCaller(), Accelerator(GDK_Return, (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("FlipClip", FreeCaller(), Accelerator(GDK_Return, (GdkModifierType)GDK_CONTROL_MASK)); + + GlobalCommands_insert("MakeDetail", FreeCaller(), Accelerator('M', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("MakeStructural", FreeCaller(), Accelerator('S', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); +} + +void Brush_constructMenu(GtkMenu* menu) +{ + create_menu_item_with_mnemonic(menu, "Prism...", "BrushPrism"); + create_menu_item_with_mnemonic(menu, "Cone...", "BrushCone"); + create_menu_item_with_mnemonic(menu, "Sphere...", "BrushSphere"); + menu_separator (menu); + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "CSG"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "Make _Hollow", "CSGHollow"); + create_menu_item_with_mnemonic(menu_in_menu, "CSG _Subtract", "CSGSubtract"); + create_menu_item_with_mnemonic(menu_in_menu, "CSG _Merge", "CSGMerge"); + } + menu_separator(menu); + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "Clipper"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + + create_menu_item_with_mnemonic(menu_in_menu, "Clip selection", "ClipSelected"); + create_menu_item_with_mnemonic(menu_in_menu, "Split selection", "SplitSelected"); + create_menu_item_with_mnemonic(menu_in_menu, "Flip Clip orientation", "FlipClip"); + } + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "Make detail", "MakeDetail"); + create_menu_item_with_mnemonic(menu, "Make structural", "MakeStructural"); + + create_check_menu_item_with_mnemonic(menu, "Texture Lock", "TogTexLock"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "Copy Face Texture", "FaceCopyTexture"); + create_menu_item_with_mnemonic(menu, "Paste Face Texture", "FacePasteTexture"); + + command_connect_accelerator("Brush3Sided"); + command_connect_accelerator("Brush4Sided"); + command_connect_accelerator("Brush5Sided"); + command_connect_accelerator("Brush6Sided"); + command_connect_accelerator("Brush7Sided"); + command_connect_accelerator("Brush8Sided"); + command_connect_accelerator("Brush9Sided"); +} diff --git a/radiant/brushmanip.h b/radiant/brushmanip.h new file mode 100644 index 00000000..088a89fe --- /dev/null +++ b/radiant/brushmanip.h @@ -0,0 +1,81 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_BRUSHWRAPPER_H) +#define INCLUDED_BRUSHWRAPPER_H + +#include +#include "string/stringfwd.h" +#include "generic/callbackfwd.h" + +enum EBrushPrefab +{ + eBrushCuboid, + eBrushPrism, + eBrushCone, + eBrushSphere, +}; + +class TextureProjection; +class ContentsFlagsValue; +namespace scene +{ + class Graph; +} +void Scene_BrushConstructPrefab(scene::Graph& graph, EBrushPrefab type, std::size_t sides, const char* shader); +class AABB; +void Scene_BrushResize_Selected(scene::Graph& graph, const AABB& bounds, const char* shader); +void Scene_BrushSetTexdef_Selected(scene::Graph& graph, const TextureProjection& projection); +void Scene_BrushSetTexdef_Component_Selected(scene::Graph& graph, const TextureProjection& projection); +void Scene_BrushGetTexdef_Selected(scene::Graph& graph, TextureProjection& projection); +void Scene_BrushGetTexdef_Component_Selected(scene::Graph& graph, TextureProjection& projection); +void Scene_BrushGetShaderSize_Component_Selected(scene::Graph& graph, size_t& width, size_t& height); +void Scene_BrushSetFlags_Selected(scene::Graph& graph, const ContentsFlagsValue& flags); +void Scene_BrushSetFlags_Component_Selected(scene::Graph& graph, const ContentsFlagsValue& flags); +void Scene_BrushGetFlags_Selected(scene::Graph& graph, ContentsFlagsValue& flags); +void Scene_BrushGetFlags_Component_Selected(scene::Graph& graph, ContentsFlagsValue& flags); +void Scene_BrushShiftTexdef_Selected(scene::Graph& graph, float s, float t); +void Scene_BrushShiftTexdef_Component_Selected(scene::Graph& graph, float s, float t); +void Scene_BrushScaleTexdef_Selected(scene::Graph& graph, float s, float t); +void Scene_BrushScaleTexdef_Component_Selected(scene::Graph& graph, float s, float t); +void Scene_BrushRotateTexdef_Selected(scene::Graph& graph, float angle); +void Scene_BrushRotateTexdef_Component_Selected(scene::Graph& graph, float angle); +void Scene_BrushSetShader_Selected(scene::Graph& graph, const char* name); +void Scene_BrushSetShader_Component_Selected(scene::Graph& graph, const char* name); +void Scene_BrushGetShader_Selected(scene::Graph& graph, CopiedString& shader); +void Scene_BrushGetShader_Component_Selected(scene::Graph& graph, CopiedString& shader); +void Scene_BrushFindReplaceShader(scene::Graph& graph, const char* find, const char* replace); +void Scene_BrushFindReplaceShader_Selected(scene::Graph& graph, const char* find, const char* replace); +void Scene_BrushFindReplaceShader_Component_Selected(scene::Graph& graph, const char* find, const char* replace); +void Scene_BrushSelectByShader(scene::Graph& graph, const char* name); +void Scene_BrushSelectByShader_Component(scene::Graph& graph, const char* name); +void Scene_BrushFitTexture_Selected(scene::Graph& graph, float s_repeat, float t_repeat); +void Scene_BrushFitTexture_Component_Selected(scene::Graph& graph, float s_repeat, float t_repeat); + +typedef struct _GtkMenu GtkMenu; +void Brush_constructMenu(GtkMenu* menu); + +extern Callback g_texture_lock_status_changed; + +void BrushFilters_construct(); +void Brush_registerCommands(); + +#endif diff --git a/radiant/brushmodule.cpp b/radiant/brushmodule.cpp new file mode 100644 index 00000000..6b47e092 --- /dev/null +++ b/radiant/brushmodule.cpp @@ -0,0 +1,395 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "brushmodule.h" + +#include "qerplugin.h" + +#include "brushnode.h" +#include "brushmanip.h" + +#include "preferencesystem.h" +#include "stringio.h" + +#include "map.h" +#include "qe3.h" +#include "mainframe.h" +#include "preferences.h" + +LatchedBool g_useAlternativeTextureProjection(false, "Use alternative texture-projection"); +bool g_showAlternativeTextureProjectionOption = false; +bool g_brush_always_caulk; + +bool getTextureLockEnabled() +{ + return g_brush_texturelock_enabled; +} + +void Face_importSnapPlanes(bool value) +{ + Face::m_quantise = value ? quantiseInteger : quantiseFloating; +} +typedef FreeCaller1 FaceImportSnapPlanesCaller; + +void Face_exportSnapPlanes(const BoolImportCallback& importer) +{ + importer(Face::m_quantise == quantiseInteger); +} +typedef FreeCaller1 FaceExportSnapPlanesCaller; + +void Brush_constructPreferences(PreferencesPage& page) +{ + page.appendCheckBox( + "", "Snap planes to integer grid", + FaceImportSnapPlanesCaller(), + FaceExportSnapPlanesCaller() + ); + page.appendEntry( + "Default texture scale", + g_texdef_default_scale + ); + if(g_showAlternativeTextureProjectionOption) + { + page.appendCheckBox( + "", "Use alternative texture-projection", + LatchedBoolImportCaller(g_useAlternativeTextureProjection), + BoolExportCaller(g_useAlternativeTextureProjection.m_latched) + ); + } + // d1223m + page.appendCheckBox("", + "Always use caulk for new brushes", + g_brush_always_caulk + ); +} +void Brush_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Brush", "Brush Settings")); + Brush_constructPreferences(page); +} +void Brush_registerPreferencesPage() +{ + PreferencesDialog_addSettingsPage(FreeCaller1()); +} + + +void Brush_Construct(EBrushType type) +{ + if(type == eBrushTypeQuake3) + { + g_showAlternativeTextureProjectionOption = true; + + GlobalPreferenceSystem().registerPreference( + "AlternativeTextureProjection", + BoolImportStringCaller(g_useAlternativeTextureProjection.m_latched), + BoolExportStringCaller(g_useAlternativeTextureProjection.m_latched) + ); + g_useAlternativeTextureProjection.useLatched(); + + if(g_useAlternativeTextureProjection.m_value) + { + type = eBrushTypeQuake3BP; + } + + // d1223m + GlobalPreferenceSystem().registerPreference( + "BrushAlwaysCaulk", + BoolImportStringCaller(g_brush_always_caulk), + BoolExportStringCaller(g_brush_always_caulk)); + } + + Brush_registerCommands(); + Brush_registerPreferencesPage(); + + BrushFilters_construct(); + + BrushClipPlane::constructStatic(); + BrushInstance::constructStatic(); + Brush::constructStatic(type); + + Brush::m_maxWorldCoord = g_MaxWorldCoord; + BrushInstance::m_counter = &g_brushCount; + + g_texdef_default_scale = 0.5f; + const char* value = g_pGameDescription->getKeyValue("default_scale"); + if(!string_empty(value)) + { + float scale = static_cast(atof(value)); + if(scale != 0) + { + g_texdef_default_scale = scale; + } + else + { + globalErrorStream() << "error parsing \"default_scale\" attribute\n"; + } + } + + GlobalPreferenceSystem().registerPreference("TextureLock", BoolImportStringCaller(g_brush_texturelock_enabled), BoolExportStringCaller(g_brush_texturelock_enabled)); + GlobalPreferenceSystem().registerPreference("BrushSnapPlanes", makeBoolStringImportCallback(FaceImportSnapPlanesCaller()), makeBoolStringExportCallback(FaceExportSnapPlanesCaller())); + GlobalPreferenceSystem().registerPreference("TexdefDefaultScale", FloatImportStringCaller(g_texdef_default_scale), FloatExportStringCaller(g_texdef_default_scale)); + + GridStatus_getTextureLockEnabled = getTextureLockEnabled; + g_texture_lock_status_changed = FreeCaller(); +} + +void Brush_Destroy() +{ + Brush::m_maxWorldCoord = 0; + BrushInstance::m_counter = 0; + + Brush::destroyStatic(); + BrushInstance::destroyStatic(); + BrushClipPlane::destroyStatic(); +} + +void Brush_clipperColourChanged() +{ + BrushClipPlane::destroyStatic(); + BrushClipPlane::constructStatic(); +} + +void BrushFaceData_fromFace(const BrushFaceDataCallback& callback, Face& face) +{ + _QERFaceData faceData; + faceData.m_p0 = face.getPlane().planePoints()[0]; + faceData.m_p1 = face.getPlane().planePoints()[1]; + faceData.m_p2 = face.getPlane().planePoints()[2]; + faceData.m_shader = face.GetShader(); + faceData.m_texdef = face.getTexdef().m_projection.m_texdef; + faceData.contents = face.getShader().m_flags.m_contentFlags; + faceData.flags = face.getShader().m_flags.m_surfaceFlags; + faceData.value = face.getShader().m_flags.m_value; + callback(faceData); +} +typedef ConstReferenceCaller1 BrushFaceDataFromFaceCaller; +typedef Callback1 FaceCallback; + +class Quake3BrushCreator : public BrushCreator +{ +public: + scene::Node& createBrush() + { + return (new BrushNode)->node(); + } + bool useAlternativeTextureProjection() const + { + return g_useAlternativeTextureProjection.m_value; + } + void Brush_forEachFace(scene::Node& brush, const BrushFaceDataCallback& callback) + { + ::Brush_forEachFace(*Node_getBrush(brush), FaceCallback(BrushFaceDataFromFaceCaller(callback))); + } + bool Brush_addFace(scene::Node& brush, const _QERFaceData& faceData) + { + Node_getBrush(brush)->undoSave(); + return Node_getBrush(brush)->addPlane(faceData.m_p0, faceData.m_p1, faceData.m_p2, faceData.m_shader, TextureProjection(faceData.m_texdef, brushprimit_texdef_t(), Vector3(0, 0, 0), Vector3(0, 0, 0))) != 0; + } +}; + +Quake3BrushCreator g_Quake3BrushCreator; + +BrushCreator& GetBrushCreator() +{ + return g_Quake3BrushCreator; +} + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + + +class BrushDependencies : + public GlobalRadiantModuleRef, + public GlobalSceneGraphModuleRef, + public GlobalShaderCacheModuleRef, + public GlobalSelectionModuleRef, + public GlobalOpenGLModuleRef, + public GlobalUndoModuleRef, + public GlobalFilterModuleRef +{ +}; + +class BrushDoom3API : public TypeSystemRef +{ + BrushCreator* m_brushdoom3; +public: + typedef BrushCreator Type; + STRING_CONSTANT(Name, "doom3"); + + BrushDoom3API() + { + Brush_Construct(eBrushTypeDoom3); + + m_brushdoom3 = &GetBrushCreator(); + } + ~BrushDoom3API() + { + Brush_Destroy(); + } + BrushCreator* getTable() + { + return m_brushdoom3; + } +}; + +typedef SingletonModule BrushDoom3Module; +typedef Static StaticBrushDoom3Module; +StaticRegisterModule staticRegisterBrushDoom3(StaticBrushDoom3Module::instance()); + + +class BrushQuake4API : public TypeSystemRef +{ + BrushCreator* m_brushquake4; +public: + typedef BrushCreator Type; + STRING_CONSTANT(Name, "quake4"); + + BrushQuake4API() + { + Brush_Construct(eBrushTypeQuake4); + + m_brushquake4 = &GetBrushCreator(); + } + ~BrushQuake4API() + { + Brush_Destroy(); + } + BrushCreator* getTable() + { + return m_brushquake4; + } +}; + +typedef SingletonModule BrushQuake4Module; +typedef Static StaticBrushQuake4Module; +StaticRegisterModule staticRegisterBrushQuake4(StaticBrushQuake4Module::instance()); + + +class BrushQuake3API : public TypeSystemRef +{ + BrushCreator* m_brushquake3; +public: + typedef BrushCreator Type; + STRING_CONSTANT(Name, "quake3"); + + BrushQuake3API() + { + Brush_Construct(eBrushTypeQuake3); + + m_brushquake3 = &GetBrushCreator(); + } + ~BrushQuake3API() + { + Brush_Destroy(); + } + BrushCreator* getTable() + { + return m_brushquake3; + } +}; + +typedef SingletonModule BrushQuake3Module; +typedef Static StaticBrushQuake3Module; +StaticRegisterModule staticRegisterBrushQuake3(StaticBrushQuake3Module::instance()); + + +class BrushQuake2API : public TypeSystemRef +{ + BrushCreator* m_brushquake2; +public: + typedef BrushCreator Type; + STRING_CONSTANT(Name, "quake2"); + + BrushQuake2API() + { + Brush_Construct(eBrushTypeQuake2); + + m_brushquake2 = &GetBrushCreator(); + } + ~BrushQuake2API() + { + Brush_Destroy(); + } + BrushCreator* getTable() + { + return m_brushquake2; + } +}; + +typedef SingletonModule BrushQuake2Module; +typedef Static StaticBrushQuake2Module; +StaticRegisterModule staticRegisterBrushQuake2(StaticBrushQuake2Module::instance()); + + +class BrushQuake1API : public TypeSystemRef +{ + BrushCreator* m_brushquake1; +public: + typedef BrushCreator Type; + STRING_CONSTANT(Name, "quake"); + + BrushQuake1API() + { + Brush_Construct(eBrushTypeQuake); + + m_brushquake1 = &GetBrushCreator(); + } + ~BrushQuake1API() + { + Brush_Destroy(); + } + BrushCreator* getTable() + { + return m_brushquake1; + } +}; + +typedef SingletonModule BrushQuake1Module; +typedef Static StaticBrushQuake1Module; +StaticRegisterModule staticRegisterBrushQuake1(StaticBrushQuake1Module::instance()); + + +class BrushHalfLifeAPI : public TypeSystemRef +{ + BrushCreator* m_brushhalflife; +public: + typedef BrushCreator Type; + STRING_CONSTANT(Name, "halflife"); + + BrushHalfLifeAPI() + { + Brush_Construct(eBrushTypeHalfLife); + + m_brushhalflife = &GetBrushCreator(); + } + ~BrushHalfLifeAPI() + { + Brush_Destroy(); + } + BrushCreator* getTable() + { + return m_brushhalflife; + } +}; + +typedef SingletonModule BrushHalfLifeModule; +typedef Static StaticBrushHalfLifeModule; +StaticRegisterModule staticRegisterBrushHalfLife(StaticBrushHalfLifeModule::instance()); diff --git a/radiant/brushmodule.h b/radiant/brushmodule.h new file mode 100644 index 00000000..20f73473 --- /dev/null +++ b/radiant/brushmodule.h @@ -0,0 +1,27 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_BRUSHMODULE_H) +#define INCLUDED_BRUSHMODULE_H + +void Brush_clipperColourChanged(); + +#endif diff --git a/radiant/brushnode.cpp b/radiant/brushnode.cpp new file mode 100644 index 00000000..35f46877 --- /dev/null +++ b/radiant/brushnode.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "brushnode.h" + diff --git a/radiant/brushnode.h b/radiant/brushnode.h new file mode 100644 index 00000000..5e1465e3 --- /dev/null +++ b/radiant/brushnode.h @@ -0,0 +1,167 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_BRUSHNODE_H) +#define INCLUDED_BRUSHNODE_H + +#include "instancelib.h" +#include "brush.h" +#include "brushtokens.h" +#include "brushxml.h" + +class BrushNode : +public scene::Node::Symbiot, +public scene::Instantiable, +public scene::Cloneable +{ + class TypeCasts + { + NodeTypeCastTable m_casts; + public: + TypeCasts() + { + NodeStaticCast::install(m_casts); + NodeStaticCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + } + NodeTypeCastTable& get() + { + return m_casts; + } + }; + + + scene::Node m_node; + InstanceSet m_instances; + Brush m_brush; + BrushTokenImporter m_mapImporter; + BrushTokenExporter m_mapExporter; + BrushXMLImporter m_xmlImporter; + BrushXMLExporter m_xmlExporter; + +public: + + typedef LazyStatic StaticTypeCasts; + + Snappable& get(NullType) + { + return m_brush; + } + TransformNode& get(NullType) + { + return m_brush; + } + Brush& get(NullType) + { + return m_brush; + } + XMLImporter& get(NullType) + { + return m_xmlImporter; + } + XMLExporter& get(NullType) + { + return m_xmlExporter; + } + MapImporter& get(NullType) + { + return m_mapImporter; + } + MapExporter& get(NullType) + { + return m_mapExporter; + } + Nameable& get(NullType) + { + return m_brush; + } + BrushDoom3& get(NullType) + { + return m_brush; + } + + BrushNode() : + m_node(this, this, StaticTypeCasts::instance().get()), + m_brush(m_node, InstanceSetEvaluateTransform::Caller(m_instances), InstanceSet::BoundsChangedCaller(m_instances)), + m_mapImporter(m_brush), + m_mapExporter(m_brush), + m_xmlImporter(m_brush), + m_xmlExporter(m_brush) + { + } + BrushNode(const BrushNode& other) : + scene::Node::Symbiot(other), + scene::Instantiable(other), + scene::Cloneable(other), + m_node(this, this, StaticTypeCasts::instance().get()), + m_brush(other.m_brush, m_node, InstanceSetEvaluateTransform::Caller(m_instances), InstanceSet::BoundsChangedCaller(m_instances)), + m_mapImporter(m_brush), + m_mapExporter(m_brush), + m_xmlImporter(m_brush), + m_xmlExporter(m_brush) + { + } + void release() + { + delete this; + } + scene::Node& node() + { + return m_node; + } + + scene::Node& clone() const + { + return (new BrushNode(*this))->node(); + } + + scene::Instance* create(const scene::Path& path, scene::Instance* parent) + { + return new BrushInstance(path, parent, m_brush); + } + void forEachInstance(const scene::Instantiable::Visitor& visitor) + { + m_instances.forEachInstance(visitor); + } + void insert(scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance) + { + m_instances.insert(observer, path, instance); + } + scene::Instance* erase(scene::Instantiable::Observer* observer, const scene::Path& path) + { + return m_instances.erase(observer, path); + } +}; + +inline Brush* Node_getBrush(scene::Node& node) +{ + return NodeTypeCast::cast(node); +} + +#endif diff --git a/radiant/brushtokens.cpp b/radiant/brushtokens.cpp new file mode 100644 index 00000000..e8674c98 --- /dev/null +++ b/radiant/brushtokens.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "brushtokens.h" + diff --git a/radiant/brushtokens.h b/radiant/brushtokens.h new file mode 100644 index 00000000..19d8c7b9 --- /dev/null +++ b/radiant/brushtokens.h @@ -0,0 +1,738 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_BRUSHTOKENS_H) +#define INCLUDED_BRUSHTOKENS_H + +#include "stringio.h" +#include "stream/stringstream.h" +#include "brush.h" + +inline bool FaceShader_importContentsFlagsValue(FaceShader& faceShader, Tokeniser& tokeniser) +{ + // parse the optional contents/flags/value + RETURN_FALSE_IF_FAIL(Tokeniser_getInteger(tokeniser, faceShader.m_flags.m_contentFlags)); + RETURN_FALSE_IF_FAIL(Tokeniser_getInteger(tokeniser, faceShader.m_flags.m_surfaceFlags)); + RETURN_FALSE_IF_FAIL(Tokeniser_getInteger(tokeniser, faceShader.m_flags.m_value)); + return true; +} + +inline bool FaceTexdef_importTokens(FaceTexdef& texdef, Tokeniser& tokeniser) +{ + // parse texdef + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_texdef.shift[0])); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_texdef.shift[1])); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_texdef.rotate)); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_texdef.scale[0])); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_texdef.scale[1])); + + ASSERT_MESSAGE(texdef_sane(texdef.m_projection.m_texdef), "FaceTexdef_importTokens: bad texdef"); + return true; +} + +inline bool FaceTexdef_BP_importTokens(FaceTexdef& texdef, Tokeniser& tokeniser) +{ + // parse alternate texdef + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + { + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_brushprimit_texdef.coords[0][0])); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_brushprimit_texdef.coords[0][1])); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_brushprimit_texdef.coords[0][2])); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")")); + } + { + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_brushprimit_texdef.coords[1][0])); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_brushprimit_texdef.coords[1][1])); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_brushprimit_texdef.coords[1][2])); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")")); + } + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")")); + return true; +} + +inline bool FaceTexdef_HalfLife_importTokens(FaceTexdef& texdef, Tokeniser& tokeniser) +{ + // parse texdef + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "[")); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_basis_s.x())); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_basis_s.y())); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_basis_s.z())); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_texdef.shift[0])); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "]")); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "[")); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_basis_t.x())); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_basis_t.y())); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_basis_t.z())); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_texdef.shift[1])); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "]")); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_texdef.rotate)); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_texdef.scale[0])); + RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, texdef.m_projection.m_texdef.scale[1])); + + texdef.m_projection.m_texdef.rotate = -texdef.m_projection.m_texdef.rotate; + + ASSERT_MESSAGE(texdef_sane(texdef.m_projection.m_texdef), "FaceTexdef_importTokens: bad texdef"); + return true; +} + +inline bool FacePlane_importTokens(FacePlane& facePlane, Tokeniser& tokeniser) +{ + // parse planepts + for(std::size_t i = 0; i<3; i++) + { + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + for(std::size_t j = 0; j < 3; ++j) + { + RETURN_FALSE_IF_FAIL(Tokeniser_getDouble(tokeniser, facePlane.planePoints()[i][j])); + } + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")")); + } + facePlane.MakePlane(); + return true; +} + +inline bool FacePlane_Doom3_importTokens(FacePlane& facePlane, Tokeniser& tokeniser) +{ + Plane3 plane; + // parse plane equation + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + RETURN_FALSE_IF_FAIL(Tokeniser_getDouble(tokeniser, plane.a)); + RETURN_FALSE_IF_FAIL(Tokeniser_getDouble(tokeniser, plane.b)); + RETURN_FALSE_IF_FAIL(Tokeniser_getDouble(tokeniser, plane.c)); + RETURN_FALSE_IF_FAIL(Tokeniser_getDouble(tokeniser, plane.d)); + plane.d = -plane.d; + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")")); + + facePlane.setDoom3Plane(plane); + return true; +} + +inline bool FaceShader_Doom3_importTokens(FaceShader& faceShader, Tokeniser& tokeniser) +{ + const char *shader = tokeniser.getToken(); + if(shader == 0) + { + Tokeniser_unexpectedError(tokeniser, shader, "#shader-name"); + return false; + } + if(string_equal(shader, "_emptyname")) + { + shader = texdef_name_default(); + } + faceShader.setShader(shader); + return true; +} + +inline bool FaceShader_importTokens(FaceShader& faceShader, Tokeniser& tokeniser) +{ + const char* texture = tokeniser.getToken(); + if(texture == 0) + { + Tokeniser_unexpectedError(tokeniser, texture, "#texture-name"); + return false; + } + if(string_equal(texture, "NULL")) + { + faceShader.setShader(texdef_name_default()); + } + else + { + StringOutputStream shader(string_length(GlobalTexturePrefix_get()) + string_length(texture)); + shader << GlobalTexturePrefix_get() << texture; + faceShader.setShader(shader.c_str()); + } + return true; +} + + + + +class Doom3FaceTokenImporter +{ + Face& m_face; +public: + Doom3FaceTokenImporter(Face& face) : m_face(face) + { + } + bool importTokens(Tokeniser& tokeniser) + { + RETURN_FALSE_IF_FAIL(FacePlane_Doom3_importTokens(m_face.getPlane(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceTexdef_BP_importTokens(m_face.getTexdef(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceShader_Doom3_importTokens(m_face.getShader(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceShader_importContentsFlagsValue(m_face.getShader(), tokeniser)); + + m_face.getTexdef().m_projectionInitialised = true; + m_face.getTexdef().m_scaleApplied = true; + + return true; + } +}; + +class Quake4FaceTokenImporter +{ + Face& m_face; +public: + Quake4FaceTokenImporter(Face& face) : m_face(face) + { + } + bool importTokens(Tokeniser& tokeniser) + { + RETURN_FALSE_IF_FAIL(FacePlane_Doom3_importTokens(m_face.getPlane(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceTexdef_BP_importTokens(m_face.getTexdef(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceShader_Doom3_importTokens(m_face.getShader(), tokeniser)); + + m_face.getTexdef().m_projectionInitialised = true; + m_face.getTexdef().m_scaleApplied = true; + + return true; + } +}; + +class Quake2FaceTokenImporter +{ + Face& m_face; +public: + Quake2FaceTokenImporter(Face& face) : m_face(face) + { + } + bool importTokens(Tokeniser& tokeniser) + { + RETURN_FALSE_IF_FAIL(FacePlane_importTokens(m_face.getPlane(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceShader_importTokens(m_face.getShader(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceTexdef_importTokens(m_face.getTexdef(), tokeniser)); + if(Tokeniser_nextTokenIsDigit(tokeniser)) + { + m_face.getShader().m_flags.m_specified = true; + RETURN_FALSE_IF_FAIL(FaceShader_importContentsFlagsValue(m_face.getShader(), tokeniser)); + } + m_face.getTexdef().m_scaleApplied = true; + return true; + } +}; + +class Quake3FaceTokenImporter +{ + Face& m_face; +public: + Quake3FaceTokenImporter(Face& face) : m_face(face) + { + } + bool importTokens(Tokeniser& tokeniser) + { + RETURN_FALSE_IF_FAIL(FacePlane_importTokens(m_face.getPlane(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceShader_importTokens(m_face.getShader(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceTexdef_importTokens(m_face.getTexdef(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceShader_importContentsFlagsValue(m_face.getShader(), tokeniser)); + m_face.getTexdef().m_scaleApplied = true; + return true; + } +}; + +class Quake3BPFaceTokenImporter +{ + Face& m_face; +public: + Quake3BPFaceTokenImporter(Face& face) : m_face(face) + { + } + bool importTokens(Tokeniser& tokeniser) + { + RETURN_FALSE_IF_FAIL(FacePlane_importTokens(m_face.getPlane(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceTexdef_BP_importTokens(m_face.getTexdef(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceShader_importTokens(m_face.getShader(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceShader_importContentsFlagsValue(m_face.getShader(), tokeniser)); + + m_face.getTexdef().m_projectionInitialised = true; + m_face.getTexdef().m_scaleApplied = true; + + return true; + } +}; + +class QuakeFaceTokenImporter +{ + Face& m_face; +public: + QuakeFaceTokenImporter(Face& face) : m_face(face) + { + } + bool importTokens(Tokeniser& tokeniser) + { + RETURN_FALSE_IF_FAIL(FacePlane_importTokens(m_face.getPlane(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceShader_importTokens(m_face.getShader(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceTexdef_importTokens(m_face.getTexdef(), tokeniser)); + m_face.getTexdef().m_scaleApplied = true; + return true; + } +}; + +class HalfLifeFaceTokenImporter +{ + Face& m_face; +public: + HalfLifeFaceTokenImporter(Face& face) : m_face(face) + { + } + bool importTokens(Tokeniser& tokeniser) + { + RETURN_FALSE_IF_FAIL(FacePlane_importTokens(m_face.getPlane(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceShader_importTokens(m_face.getShader(), tokeniser)); + RETURN_FALSE_IF_FAIL(FaceTexdef_HalfLife_importTokens(m_face.getTexdef(), tokeniser)); + m_face.getTexdef().m_scaleApplied = true; + return true; + } +}; + + +inline void FacePlane_Doom3_exportTokens(const FacePlane& facePlane, TokenWriter& writer) +{ + // write plane equation + writer.writeToken("("); + writer.writeFloat(facePlane.getDoom3Plane().a); + writer.writeFloat(facePlane.getDoom3Plane().b); + writer.writeFloat(facePlane.getDoom3Plane().c); + writer.writeFloat(-facePlane.getDoom3Plane().d); + writer.writeToken(")"); +} + +inline void FacePlane_exportTokens(const FacePlane& facePlane, TokenWriter& writer) +{ + // write planepts + for(std::size_t i=0; i<3; i++) + { + writer.writeToken("("); + for(std::size_t j=0; j<3; j++) + { + writer.writeFloat(Face::m_quantise(facePlane.planePoints()[i][j])); + } + writer.writeToken(")"); + } +} + +inline void FaceTexdef_BP_exportTokens(const FaceTexdef& faceTexdef, TokenWriter& writer) +{ + // write alternate texdef + writer.writeToken("("); + { + writer.writeToken("("); + for(std::size_t i=0;i<3;i++) + { + writer.writeFloat(faceTexdef.m_projection.m_brushprimit_texdef.coords[0][i]); + } + writer.writeToken(")"); + } + { + writer.writeToken("("); + for(std::size_t i=0;i<3;i++) + { + writer.writeFloat(faceTexdef.m_projection.m_brushprimit_texdef.coords[1][i]); + } + writer.writeToken(")"); + } + writer.writeToken(")"); +} + +inline void FaceTexdef_exportTokens(const FaceTexdef& faceTexdef, TokenWriter& writer) +{ + ASSERT_MESSAGE(texdef_sane(faceTexdef.m_projection.m_texdef), "FaceTexdef_exportTokens: bad texdef"); + // write texdef + writer.writeFloat(faceTexdef.m_projection.m_texdef.shift[0]); + writer.writeFloat(faceTexdef.m_projection.m_texdef.shift[1]); + writer.writeFloat(faceTexdef.m_projection.m_texdef.rotate); + writer.writeFloat(faceTexdef.m_projection.m_texdef.scale[0]); + writer.writeFloat(faceTexdef.m_projection.m_texdef.scale[1]); +} + +inline void FaceTexdef_HalfLife_exportTokens(const FaceTexdef& faceTexdef, TokenWriter& writer) +{ + ASSERT_MESSAGE(texdef_sane(faceTexdef.m_projection.m_texdef), "FaceTexdef_exportTokens: bad texdef"); + // write texdef + writer.writeToken("["); + writer.writeFloat(faceTexdef.m_projection.m_basis_s.x()); + writer.writeFloat(faceTexdef.m_projection.m_basis_s.y()); + writer.writeFloat(faceTexdef.m_projection.m_basis_s.z()); + writer.writeFloat(faceTexdef.m_projection.m_texdef.shift[0]); + writer.writeToken("]"); + writer.writeToken("["); + writer.writeFloat(faceTexdef.m_projection.m_basis_t.x()); + writer.writeFloat(faceTexdef.m_projection.m_basis_t.y()); + writer.writeFloat(faceTexdef.m_projection.m_basis_t.z()); + writer.writeFloat(faceTexdef.m_projection.m_texdef.shift[1]); + writer.writeToken("]"); + writer.writeFloat(-faceTexdef.m_projection.m_texdef.rotate); + writer.writeFloat(faceTexdef.m_projection.m_texdef.scale[0]); + writer.writeFloat(faceTexdef.m_projection.m_texdef.scale[1]); +} + +inline void FaceShader_ContentsFlagsValue_exportTokens(const FaceShader& faceShader, TokenWriter& writer) +{ + // write surface flags + writer.writeInteger(faceShader.m_flags.m_contentFlags); + writer.writeInteger(faceShader.m_flags.m_surfaceFlags); + writer.writeInteger(faceShader.m_flags.m_value); +} + +inline void FaceShader_exportTokens(const FaceShader& faceShader, TokenWriter& writer) +{ + // write shader name + if(string_empty(shader_get_textureName(faceShader.getShader()))) + { + writer.writeToken("NULL"); + } + else + { + writer.writeToken(shader_get_textureName(faceShader.getShader())); + } +} + +inline void FaceShader_Doom3_exportTokens(const FaceShader& faceShader, TokenWriter& writer) +{ + // write shader name + if(string_empty(shader_get_textureName(faceShader.getShader()))) + { + writer.writeString("_emptyname"); + } + else + { + writer.writeString(faceShader.getShader()); + } +} + +class Doom3FaceTokenExporter +{ + const Face& m_face; +public: + Doom3FaceTokenExporter(const Face& face) : m_face(face) + { + } + void exportTokens(TokenWriter& writer) const + { + FacePlane_Doom3_exportTokens(m_face.getPlane(), writer); + FaceTexdef_BP_exportTokens(m_face.getTexdef(), writer); + FaceShader_Doom3_exportTokens(m_face.getShader(), writer); + FaceShader_ContentsFlagsValue_exportTokens(m_face.getShader(), writer); + writer.nextLine(); + } +}; + +class Quake4FaceTokenExporter +{ + const Face& m_face; +public: + Quake4FaceTokenExporter(const Face& face) : m_face(face) + { + } + void exportTokens(TokenWriter& writer) const + { + FacePlane_Doom3_exportTokens(m_face.getPlane(), writer); + FaceTexdef_BP_exportTokens(m_face.getTexdef(), writer); + FaceShader_Doom3_exportTokens(m_face.getShader(), writer); + writer.nextLine(); + } +}; + +class Quake2FaceTokenExporter +{ + const Face& m_face; +public: + Quake2FaceTokenExporter(const Face& face) : m_face(face) + { + } + void exportTokens(TokenWriter& writer) const + { + FacePlane_exportTokens(m_face.getPlane(), writer); + FaceShader_exportTokens(m_face.getShader(), writer); + FaceTexdef_exportTokens(m_face.getTexdef(), writer); + if(m_face.getShader().m_flags.m_specified || m_face.isDetail()) + { + FaceShader_ContentsFlagsValue_exportTokens(m_face.getShader(), writer); + } + writer.nextLine(); + } +}; + +class Quake3FaceTokenExporter +{ + const Face& m_face; +public: + Quake3FaceTokenExporter(const Face& face) : m_face(face) + { + } + void exportTokens(TokenWriter& writer) const + { + FacePlane_exportTokens(m_face.getPlane(), writer); + FaceShader_exportTokens(m_face.getShader(), writer); + FaceTexdef_exportTokens(m_face.getTexdef(), writer); + FaceShader_ContentsFlagsValue_exportTokens(m_face.getShader(), writer); + writer.nextLine(); + } +}; + +class Quake3BPFaceTokenExporter +{ + const Face& m_face; +public: + Quake3BPFaceTokenExporter(const Face& face) : m_face(face) + { + } + void exportTokens(TokenWriter& writer) const + { + FacePlane_exportTokens(m_face.getPlane(), writer); + FaceTexdef_BP_exportTokens(m_face.getTexdef(), writer); + FaceShader_exportTokens(m_face.getShader(), writer); + FaceShader_ContentsFlagsValue_exportTokens(m_face.getShader(), writer); + writer.nextLine(); + } +}; + +class QuakeFaceTokenExporter +{ + const Face& m_face; +public: + QuakeFaceTokenExporter(const Face& face) : m_face(face) + { + } + void exportTokens(TokenWriter& writer) const + { + FacePlane_exportTokens(m_face.getPlane(), writer); + FaceShader_exportTokens(m_face.getShader(), writer); + FaceTexdef_exportTokens(m_face.getTexdef(), writer); + writer.nextLine(); + } +}; + +class HalfLifeFaceTokenExporter +{ + const Face& m_face; +public: + HalfLifeFaceTokenExporter(const Face& face) : m_face(face) + { + } + void exportTokens(TokenWriter& writer) const + { + FacePlane_exportTokens(m_face.getPlane(), writer); + FaceShader_exportTokens(m_face.getShader(), writer); + FaceTexdef_HalfLife_exportTokens(m_face.getTexdef(), writer); + writer.nextLine(); + } +}; + + +class BrushTokenImporter : public MapImporter +{ + Brush& m_brush; + +public: + BrushTokenImporter(Brush& brush) : m_brush(brush) + { + } + bool importTokens(Tokeniser& tokeniser) + { + if(Brush::m_type == eBrushTypeQuake3BP || Brush::m_type == eBrushTypeDoom3 || Brush::m_type == eBrushTypeQuake4) + { + tokeniser.nextLine(); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "{")); + } + while(1) + { + // check for end of brush + tokeniser.nextLine(); + const char* token = tokeniser.getToken(); + if(string_equal(token, "}")) + { + break; + } + + tokeniser.ungetToken(); + + m_brush.push_back(FaceSmartPointer(new Face(&m_brush))); + + //!todo BP support + tokeniser.nextLine(); + + Face& face = *m_brush.back(); + + switch(Brush::m_type) + { + case eBrushTypeDoom3: + { + Doom3FaceTokenImporter importer(face); + RETURN_FALSE_IF_FAIL(importer.importTokens(tokeniser)); + } + break; + case eBrushTypeQuake4: + { + Quake4FaceTokenImporter importer(face); + RETURN_FALSE_IF_FAIL(importer.importTokens(tokeniser)); + } + break; + case eBrushTypeQuake2: + { + Quake2FaceTokenImporter importer(face); + RETURN_FALSE_IF_FAIL(importer.importTokens(tokeniser)); + } + break; + case eBrushTypeQuake3: + { + Quake3FaceTokenImporter importer(face); + RETURN_FALSE_IF_FAIL(importer.importTokens(tokeniser)); + } + break; + case eBrushTypeQuake3BP: + { + Quake3BPFaceTokenImporter importer(face); + RETURN_FALSE_IF_FAIL(importer.importTokens(tokeniser)); + } + break; + case eBrushTypeQuake: + { + QuakeFaceTokenImporter importer(face); + RETURN_FALSE_IF_FAIL(importer.importTokens(tokeniser)); + } + break; + case eBrushTypeHalfLife: + { + HalfLifeFaceTokenImporter importer(face); + RETURN_FALSE_IF_FAIL(importer.importTokens(tokeniser)); + } + break; + } + face.planeChanged(); + } + if(Brush::m_type == eBrushTypeQuake3BP || Brush::m_type == eBrushTypeDoom3 || Brush::m_type == eBrushTypeQuake4) + { + tokeniser.nextLine(); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "}")); + } + + m_brush.planeChanged(); + m_brush.shaderChanged(); + + return true; + } +}; + + +class BrushTokenExporter : public MapExporter +{ + const Brush& m_brush; + +public: + BrushTokenExporter(const Brush& brush) : m_brush(brush) + { + } + void exportTokens(TokenWriter& writer) const + { + m_brush.evaluateBRep(); // ensure b-rep is up-to-date, so that non-contributing faces can be identified. + + if(!m_brush.hasContributingFaces()) + { + return; + } + + writer.writeToken("{"); + writer.nextLine(); + + if(Brush::m_type == eBrushTypeQuake3BP) + { + writer.writeToken("brushDef"); + writer.nextLine(); + writer.writeToken("{"); + writer.nextLine(); + } + + if(Brush::m_type == eBrushTypeDoom3 || Brush::m_type == eBrushTypeQuake4) + { + writer.writeToken("brushDef3"); + writer.nextLine(); + writer.writeToken("{"); + writer.nextLine(); + } + + for(Brush::const_iterator i = m_brush.begin(); i != m_brush.end(); ++i) + { + const Face& face = *(*i); + + if(face.contributes()) + { + switch(Brush::m_type) + { + case eBrushTypeDoom3: + { + Doom3FaceTokenExporter exporter(face); + exporter.exportTokens(writer); + } + break; + case eBrushTypeQuake4: + { + Quake4FaceTokenExporter exporter(face); + exporter.exportTokens(writer); + } + break; + case eBrushTypeQuake2: + { + Quake2FaceTokenExporter exporter(face); + exporter.exportTokens(writer); + } + break; + case eBrushTypeQuake3: + { + Quake3FaceTokenExporter exporter(face); + exporter.exportTokens(writer); + } + break; + case eBrushTypeQuake3BP: + { + Quake3BPFaceTokenExporter exporter(face); + exporter.exportTokens(writer); + } + break; + case eBrushTypeQuake: + { + QuakeFaceTokenExporter exporter(face); + exporter.exportTokens(writer); + } + break; + case eBrushTypeHalfLife: + { + HalfLifeFaceTokenExporter exporter(face); + exporter.exportTokens(writer); + } + break; + } + } + } + + if(Brush::m_type == eBrushTypeQuake3BP || Brush::m_type == eBrushTypeDoom3 || Brush::m_type == eBrushTypeQuake4) + { + writer.writeToken("}"); + writer.nextLine(); + } + + writer.writeToken("}"); + writer.nextLine(); + } +}; + + +#endif diff --git a/radiant/brushxml.cpp b/radiant/brushxml.cpp new file mode 100644 index 00000000..ae18cf34 --- /dev/null +++ b/radiant/brushxml.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "brushxml.h" + diff --git a/radiant/brushxml.h b/radiant/brushxml.h new file mode 100644 index 00000000..afadc8a0 --- /dev/null +++ b/radiant/brushxml.h @@ -0,0 +1,457 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_BRUSHXML_H) +#define INCLUDED_BRUSHXML_H + +#include "stream/stringstream.h" +#include "xml/xmlelement.h" + +#include "brush.h" + +inline void FaceTexdef_BP_importXML(FaceTexdef& texdef, const char* xmlContent) +{ + StringTokeniser content(xmlContent); + + texdef.m_projection.m_brushprimit_texdef.coords[0][0] = static_cast(atof(content.getToken())); + texdef.m_projection.m_brushprimit_texdef.coords[0][1] = static_cast(atof(content.getToken())); + texdef.m_projection.m_brushprimit_texdef.coords[0][2] = static_cast(atof(content.getToken())); + texdef.m_projection.m_brushprimit_texdef.coords[1][0] = static_cast(atof(content.getToken())); + texdef.m_projection.m_brushprimit_texdef.coords[1][1] = static_cast(atof(content.getToken())); + texdef.m_projection.m_brushprimit_texdef.coords[1][2] = static_cast(atof(content.getToken())); +} +inline void FaceTexdef_importXML(FaceTexdef& texdef, const char* xmlContent) +{ + StringTokeniser content(xmlContent); + + texdef.m_projection.m_texdef.shift[0] = static_cast(atof(content.getToken())); + texdef.m_projection.m_texdef.shift[1] = static_cast(atof(content.getToken())); + texdef.m_projection.m_texdef.rotate = static_cast(atof(content.getToken())); + texdef.m_projection.m_texdef.scale[0] = static_cast(atof(content.getToken())); + texdef.m_projection.m_texdef.scale[1] = static_cast(atof(content.getToken())); + + ASSERT_MESSAGE(texdef_sane(texdef.m_projection.m_texdef), "FaceTexdef_importXML: bad texdef"); +} + +inline void FacePlane_importXML(FacePlane& facePlane, const char* xmlContent) +{ + StringTokeniser content(xmlContent); + + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + { + facePlane.planePoints()[i][j] = atof(content.getToken()); + } + } + facePlane.MakePlane(); +} + + +class FaceXMLImporter +{ + struct xml_state_t + { + enum EState + { + eDefault, + ePlanePts, + eTexdef, + eBPMatrix, + eFlags, + eShader, + }; + + EState m_state; + StringOutputStream m_content; + + xml_state_t(EState state) + : m_state(state) + {} + + EState state() const + { + return m_state; + } + const char* content() const + { + return m_content.c_str(); + } + std::size_t write(const char* buffer, std::size_t length) + { + return m_content.write(buffer, length); + } + }; + + std::vector m_xml_state; + Face& m_face; +public: + FaceXMLImporter(Face& face) : m_face(face) + { + m_xml_state.push_back(xml_state_t::eDefault); + } + ~FaceXMLImporter() + { + m_face.planeChanged(); + } + + void pushElement(const XMLElement& element) + { + ASSERT_MESSAGE(m_xml_state.back().state() == xml_state_t::eDefault, "parse error"); + + if(strcmp(element.name(), "planepts") == 0) + { + m_xml_state.push_back(xml_state_t::ePlanePts); + } + else if(strcmp(element.name(), "texdef") == 0) + { + m_xml_state.push_back(xml_state_t::eTexdef); + } + else if(strcmp(element.name(), "bpmatrix") == 0) + { + m_xml_state.push_back(xml_state_t::eBPMatrix); + } + else if(strcmp(element.name(), "flags") == 0) + { + m_xml_state.push_back(xml_state_t::eFlags); + } + else if(strcmp(element.name(), "shader") == 0) + { + m_xml_state.push_back(xml_state_t::eShader); + } + } + void popElement(const char* name) + { + ASSERT_MESSAGE(m_xml_state.back().state() != xml_state_t::eDefault, "parse error"); + + switch(m_xml_state.back().state()) + { + case xml_state_t::ePlanePts: + { + FacePlane_importXML(m_face.getPlane(), m_xml_state.back().content()); + } + break; + case xml_state_t::eTexdef: + { + FaceTexdef_importXML(m_face.getTexdef(), m_xml_state.back().content()); + } + break; + case xml_state_t::eBPMatrix: + { + FaceTexdef_BP_importXML(m_face.getTexdef(), m_xml_state.back().content()); + } + break; + case xml_state_t::eFlags: + { + StringTokeniser content(m_xml_state.back().content()); + + m_face.getShader().m_flags.m_contentFlags = atoi(content.getToken()); + m_face.getShader().m_flags.m_surfaceFlags = atoi(content.getToken()); + m_face.getShader().m_flags.m_value = atoi(content.getToken()); + } + break; + case xml_state_t::eShader: + { + m_face.getShader().setShader(m_xml_state.back().content()); + } + break; + default: + break; + } + + m_xml_state.pop_back(); + } + std::size_t write(const char* data, std::size_t length) + { + ASSERT_MESSAGE(!m_xml_state.empty(), "parse error"); + return m_xml_state.back().write(data, length); + } +}; + + +inline void FaceTexdef_exportXML(const FaceTexdef& texdef, XMLImporter& importer) +{ + StaticElement element("texdef"); + importer.pushElement(element); + + ASSERT_MESSAGE(texdef_sane(texdef.m_projection.m_texdef), "FaceTexdef_exportXML: bad texdef"); + + importer << texdef.m_projection.m_texdef.shift[0] + << ' ' << texdef.m_projection.m_texdef.shift[1] + << ' ' << texdef.m_projection.m_texdef.rotate + << ' ' << texdef.m_projection.m_texdef.scale[0] + << ' ' << texdef.m_projection.m_texdef.scale[1]; + + importer.popElement(element.name()); +} +inline void FaceTexdef_BP_exportXML(const FaceTexdef& texdef, XMLImporter& importer) +{ + StaticElement element("texdef"); + importer.pushElement(element); + + for(int i = 0; i < 2; ++i) + { + for(int j = 0; j < 3; ++j) + { + importer << texdef.m_projection.m_brushprimit_texdef.coords[i][j] << ' '; + } + } + + importer.popElement(element.name()); +} +inline void FaceShader_ContentsFlagsValue_exportXML(const FaceShader& faceShader, XMLImporter& importer) +{ + StaticElement element("flags"); + importer.pushElement(element); + + { + importer << faceShader.m_flags.m_contentFlags + << ' ' << faceShader.m_flags.m_surfaceFlags + << ' ' << faceShader.m_flags.m_value; + } + + importer.popElement(element.name()); +} + +inline void FacePlane_exportXML(const FacePlane& facePlane, XMLImporter& importer) +{ + StaticElement element("planepts"); + importer.pushElement(element); + + { + // write planepts + for (int i=0 ; i<3 ; i++) + { + for (int j=0 ; j<3 ; j++) + { + importer << Face::m_quantise(facePlane.planePoints()[i][j]) << ' '; + } + } + } + + importer.popElement(element.name()); +} + +inline void FacePolygon_exportXML(const Winding& w, const BasicVector3& normal, XMLImporter& importer) +{ + DynamicElement element("polygon"); + + char tmp[32]; + + sprintf(tmp, "%f", normal.x()); + element.insertAttribute("nx", tmp); + + sprintf(tmp, "%f", normal.y()); + element.insertAttribute("ny", tmp); + + sprintf(tmp, "%f", normal.z()); + element.insertAttribute("nz", tmp); + + importer.pushElement(element); + + for(unsigned int i = 0; i < w.numpoints; ++i) + { + DynamicElement c("vertex"); + + sprintf(tmp, "%f", w.points[i].vertex.x()); + c.insertAttribute("x", tmp); + + sprintf(tmp, "%f", w.points[i].vertex.y()); + c.insertAttribute("y", tmp); + + sprintf(tmp, "%f", w.points[i].vertex.z()); + c.insertAttribute("z", tmp); + + sprintf(tmp, "%f", w.points[i].texcoord.x()); + c.insertAttribute("s", tmp); + + sprintf(tmp, "%f", w.points[i].texcoord.y()); + c.insertAttribute("t", tmp); + + importer.pushElement(c); + importer.popElement(c.name()); + } + + importer.popElement(element.name()); +} + +class FaceXMLExporter +{ + const Face& m_face; +public: + FaceXMLExporter(const Face& face) : m_face(face) + { + } + void exportXML(XMLImporter& importer) + { + bool bAlternateTexdef = (Face::m_type == eBrushTypeQuake3BP || Face::m_type == eBrushTypeDoom3 || Face::m_type == eBrushTypeQuake4); + + // write shader + { + StaticElement element("shader"); + importer.pushElement(element); + importer << m_face.getShader().getShader(); + importer.popElement(element.name()); + } + + FacePolygon_exportXML(m_face.getWinding(), m_face.getPlane().plane3().normal(), importer); + FacePlane_exportXML(m_face.getPlane(), importer); + + if(!bAlternateTexdef) + { + FaceTexdef_exportXML(m_face.getTexdef(), importer); + } + else + { + FaceTexdef_BP_exportXML(m_face.getTexdef(), importer); + } + + FaceShader_ContentsFlagsValue_exportXML(m_face.getShader(), importer); + } +}; + + +class BrushXMLImporter : public XMLImporter +{ + class xml_state_t + { + public: + enum EState + { + eDefault, + eBrush, + eFace, + }; + + private: + EState m_state; + + public: + xml_state_t(EState state) + : m_state(state) + { + } + EState state() const + { + return m_state; + } + }; + + std::vector m_xml_state; + char m_faceImporter[sizeof(FaceXMLImporter)]; + Brush& m_brush; + + FaceXMLImporter& faceImporter() + { + return *reinterpret_cast(m_faceImporter); + } + +public: + BrushXMLImporter(Brush& brush) : m_brush(brush) + { + m_xml_state.push_back(xml_state_t::eDefault); + } + void pushElement(const XMLElement& element) + { + switch(m_xml_state.back().state()) + { + case xml_state_t::eDefault: + ASSERT_MESSAGE(strcmp(element.name(), "brush") == 0, "parse error"); + m_xml_state.push_back(xml_state_t::eBrush); + break; + case xml_state_t::eBrush: + ASSERT_MESSAGE(strcmp(element.name(), "plane") == 0, "parse error"); + m_xml_state.push_back(xml_state_t::eFace); + m_brush.push_back(FaceSmartPointer(new Face(&m_brush))); + constructor(faceImporter(), makeReference(*m_brush.back())); + m_brush.planeChanged(); + m_brush.shaderChanged(); + break; + case xml_state_t::eFace: + m_xml_state.push_back(xml_state_t::eFace); + faceImporter().pushElement(element); + break; + } + } + void popElement(const char* name) + { + ASSERT_MESSAGE(!m_xml_state.empty(), "parse error"); + m_xml_state.pop_back(); + + switch(m_xml_state.back().state()) + { + case xml_state_t::eDefault: + break; + case xml_state_t::eBrush: + destructor(faceImporter()); + break; + case xml_state_t::eFace: + faceImporter().popElement(name); + break; + } + } + std::size_t write(const char* data, std::size_t length) + { + switch(m_xml_state.back().state()) + { + case xml_state_t::eFace: + return faceImporter().write(data, length); + break; + default: + break; + } + return length; + } +}; + +class BrushXMLExporter : public XMLExporter +{ + const Brush& m_brush; + +public: + BrushXMLExporter(const Brush& brush) : m_brush(brush) + { + } + void exportXML(XMLImporter& importer) + { + m_brush.evaluateBRep(); // ensure b-rep is up-to-date, so that non-contributing faces can be identified. + ASSERT_MESSAGE(m_brush.hasContributingFaces(), "exporting an empty brush"); + + const StaticElement brushElement("brush"); + importer.pushElement(brushElement); + + for(Brush::const_iterator i = m_brush.begin(); i != m_brush.end(); ++i) + { + if((*i)->contributes()) + { + const StaticElement element("plane"); + importer.pushElement(element); + FaceXMLExporter(*(*i)).exportXML(importer); + importer.popElement(element.name()); + } + } + + importer.popElement(brushElement.name()); + } +}; + + +#endif diff --git a/radiant/build.cpp b/radiant/build.cpp new file mode 100644 index 00000000..dcb23a28 --- /dev/null +++ b/radiant/build.cpp @@ -0,0 +1,1158 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "build.h" +#include "debugging/debugging.h" + +#include +#include +#include "stream/stringstream.h" +#include "versionlib.h" + +#include "mainframe.h" + +typedef std::map Variables; +Variables g_build_variables; + +void build_clear_variables() +{ + g_build_variables.clear(); +} + +void build_set_variable(const char* name, const char* value) +{ + g_build_variables[name] = value; +} + +const char* build_get_variable(const char* name) +{ + Variables::iterator i = g_build_variables.find(name); + if(i != g_build_variables.end()) + { + return (*i).second.c_str(); + } + globalErrorStream() << "undefined build variable: " << makeQuoted(name) << "\n"; + return ""; +} + +#include "xml/ixml.h" +#include "xml/xmlelement.h" + +class Evaluatable +{ +public: + virtual void evaluate(StringBuffer& output) = 0; + virtual void exportXML(XMLImporter& importer) = 0; +}; + +class VariableString : public Evaluatable +{ + CopiedString m_string; +public: + VariableString() : m_string() + { + } + VariableString(const char* string) : m_string(string) + { + } + const char* c_str() const + { + return m_string.c_str(); + } + void setString(const char* string) + { + m_string = string; + } + void evaluate(StringBuffer& output) + { + StringBuffer variable; + bool in_variable = false; + for(const char* i = m_string.c_str(); *i != '\0'; ++i) + { + if(!in_variable) + { + switch(*i) + { + case '[': + in_variable = true; + break; + default: + output.push_back(*i); + break; + } + } + else + { + switch(*i) + { + case ']': + in_variable = false; + output.push_string(build_get_variable(variable.c_str())); + variable.clear(); + break; + default: + variable.push_back(*i); + break; + } + } + } + } + void exportXML(XMLImporter& importer) + { + importer << c_str(); + } +}; + +class Conditional : public Evaluatable +{ + VariableString* m_test; +public: + Evaluatable* m_result; + Conditional(VariableString* test) : m_test(test) + { + } + ~Conditional() + { + delete m_test; + delete m_result; + } + void evaluate(StringBuffer& output) + { + StringBuffer buffer; + m_test->evaluate(buffer); + if(!string_empty(buffer.c_str())) + { + m_result->evaluate(output); + } + } + void exportXML(XMLImporter& importer) + { + StaticElement conditionElement("cond"); + conditionElement.insertAttribute("value", m_test->c_str()); + importer.pushElement(conditionElement); + m_result->exportXML(importer); + importer.popElement(conditionElement.name()); + } +}; + +typedef std::vector Evaluatables; + +class Tool : public Evaluatable +{ + Evaluatables m_evaluatables; +public: + ~Tool() + { + for(Evaluatables::iterator i = m_evaluatables.begin(); i != m_evaluatables.end(); ++i) + { + delete (*i); + } + } + void push_back(Evaluatable* evaluatable) + { + m_evaluatables.push_back(evaluatable); + } + void evaluate(StringBuffer& output) + { + for(Evaluatables::iterator i = m_evaluatables.begin(); i != m_evaluatables.end(); ++i) + { + (*i)->evaluate(output); + } + } + void exportXML(XMLImporter& importer) + { + for(Evaluatables::iterator i = m_evaluatables.begin(); i != m_evaluatables.end(); ++i) + { + (*i)->exportXML(importer); + } + } +}; + +#include "xml/ixml.h" + +class XMLElementParser : public TextOutputStream +{ +public: + virtual XMLElementParser& pushElement(const XMLElement& element) = 0; + virtual void popElement(const char* name) = 0; +}; + +class VariableStringXMLConstructor : public XMLElementParser +{ + StringBuffer m_buffer; + VariableString& m_variableString; +public: + VariableStringXMLConstructor(VariableString& variableString) : m_variableString(variableString) + { + } + ~VariableStringXMLConstructor() + { + m_variableString.setString(m_buffer.c_str()); + } + std::size_t write(const char* buffer, std::size_t length) + { + m_buffer.push_range(buffer, buffer + length); + return length; + } + XMLElementParser& pushElement(const XMLElement& element) + { + ERROR_MESSAGE("parse error: invalid element \"" << element.name() << "\""); + return *this; + } + void popElement(const char* name) + { + } +}; + +class ConditionalXMLConstructor : public XMLElementParser +{ + StringBuffer m_buffer; + Conditional& m_conditional; +public: + ConditionalXMLConstructor(Conditional& conditional) : m_conditional(conditional) + { + } + ~ConditionalXMLConstructor() + { + m_conditional.m_result = new VariableString(m_buffer.c_str()); + } + std::size_t write(const char* buffer, std::size_t length) + { + m_buffer.push_range(buffer, buffer + length); + return length; + } + XMLElementParser& pushElement(const XMLElement& element) + { + ERROR_MESSAGE("parse error: invalid element \"" << element.name() << "\""); + return *this; + } + void popElement(const char* name) + { + } +}; + +class ToolXMLConstructor : public XMLElementParser +{ + StringBuffer m_buffer; + Tool& m_tool; + ConditionalXMLConstructor* m_conditional; +public: + ToolXMLConstructor(Tool& tool) : m_tool(tool) + { + } + ~ToolXMLConstructor() + { + flush(); + } + std::size_t write(const char* buffer, std::size_t length) + { + m_buffer.push_range(buffer, buffer + length); + return length; + } + XMLElementParser& pushElement(const XMLElement& element) + { + if(string_equal(element.name(), "cond")) + { + flush(); + Conditional* conditional = new Conditional(new VariableString(element.attribute("value"))); + m_tool.push_back(conditional); + m_conditional = new ConditionalXMLConstructor(*conditional); + return *m_conditional; + } + else + { + ERROR_MESSAGE("parse error: invalid element \"" << element.name() << "\""); + return *this; + } + } + void popElement(const char* name) + { + if(string_equal(name, "cond")) + { + delete m_conditional; + } + } + + void flush() + { + if(!m_buffer.empty()) + { + m_tool.push_back(new VariableString(m_buffer.c_str())); + m_buffer.clear(); + } + } +}; + +typedef VariableString BuildCommand; +typedef std::list Build; + +class BuildXMLConstructor : public XMLElementParser +{ + VariableStringXMLConstructor* m_variableString; + Build& m_build; +public: + BuildXMLConstructor(Build& build) : m_build(build) + { + } + std::size_t write(const char* buffer, std::size_t length) + { + return length; + } + XMLElementParser& pushElement(const XMLElement& element) + { + if(string_equal(element.name(), "command")) + { + m_build.push_back(BuildCommand()); + m_variableString = new VariableStringXMLConstructor(m_build.back()); + return *m_variableString; + } + else + { + ERROR_MESSAGE("parse error: invalid element"); + return *this; + } + } + void popElement(const char* name) + { + delete m_variableString; + } +}; + +typedef std::pair BuildPair; + +class BuildPairEqual +{ + const char* m_name; +public: + BuildPairEqual(const char* name) : m_name(name) + { + } + bool operator()(const BuildPair& self) const + { + return string_equal(self.first.c_str(), m_name); + } +}; + +typedef std::list Project; + +Project::iterator Project_find(Project& project, const char* name) +{ + return std::find_if(project.begin(), project.end(), BuildPairEqual(name)); +} + +Project::iterator Project_find(Project& project, std::size_t index) +{ + Project::iterator i = project.begin(); + while(index-- != 0 && i != project.end()) + { + ++i; + } + return i; +} + +Build& project_find(Project& project, const char* build) +{ + Project::iterator i = Project_find(project, build); + ASSERT_MESSAGE(i != project.end(), "error finding build command"); + return (*i).second; +} + +Build::iterator Build_find(Build& build, std::size_t index) +{ + Build::iterator i = build.begin(); + while(index-- != 0 && i != build.end()) + { + ++i; + } + return i; +} + +typedef std::map Tools; + +class ProjectXMLConstructor : public XMLElementParser +{ + ToolXMLConstructor* m_tool; + BuildXMLConstructor* m_build; + Project& m_project; + Tools& m_tools; +public: + ProjectXMLConstructor(Project& project, Tools& tools) : m_project(project), m_tools(tools) + { + } + std::size_t write(const char* buffer, std::size_t length) + { + return length; + } + XMLElementParser& pushElement(const XMLElement& element) + { + if(string_equal(element.name(), "var")) + { + Tools::iterator i = m_tools.insert(Tools::value_type(element.attribute("name"), Tool())).first; + m_tool = new ToolXMLConstructor((*i).second); + return *m_tool; + } + else if(string_equal(element.name(), "build")) + { + m_project.push_back(Project::value_type(element.attribute("name"), Build())); + m_build = new BuildXMLConstructor(m_project.back().second); + return *m_build; + } + else + { + ERROR_MESSAGE("parse error: invalid element"); + return *this; + } + } + void popElement(const char* name) + { + if(string_equal(name, "var")) + { + delete m_tool; + } + else if(string_equal(name, "build")) + { + delete m_build; + } + } +}; + +class SkipAllParser : public XMLElementParser +{ +public: + std::size_t write(const char* buffer, std::size_t length) + { + return length; + } + XMLElementParser& pushElement(const XMLElement& element) + { + return *this; + } + void popElement(const char* name) + { + } +}; + +class RootXMLConstructor : public XMLElementParser +{ + CopiedString m_elementName; + XMLElementParser& m_parser; + SkipAllParser m_skip; + Version m_version; + bool m_compatible; +public: + RootXMLConstructor(const char* elementName, XMLElementParser& parser, const char* version) : + m_elementName(elementName), + m_parser(parser), + m_version(version_parse(version)), + m_compatible(false) + { + } + std::size_t write(const char* buffer, std::size_t length) + { + return length; + } + XMLElementParser& pushElement(const XMLElement& element) + { + if(string_equal(element.name(), m_elementName.c_str())) + { + Version dataVersion(version_parse(element.attribute("version"))); + if(version_compatible(m_version, dataVersion)) + { + m_compatible = true; + return m_parser; + } + else + { + return m_skip; + } + } + else + { + //ERROR_MESSAGE("parse error: invalid element \"" << element.name() << "\""); + return *this; + } + } + void popElement(const char* name) + { + } + + bool versionCompatible() const + { + return m_compatible; + } +}; + +namespace +{ + Project g_build_project; + Tools g_build_tools; + bool g_build_changed = false; +} + +void build_error_undefined_tool(const char* build, const char* tool) +{ + globalErrorStream() << "build " << makeQuoted(build) << " refers to undefined tool " << makeQuoted(tool) << '\n'; +} + +void project_verify(Project& project, Tools& tools) +{ +#if 0 + for(Project::iterator i = project.begin(); i != project.end(); ++i) + { + Build& build = (*i).second; + for(Build::iterator j = build.begin(); j != build.end(); ++j) + { + Tools::iterator k = tools.find((*j).first); + if(k == g_build_tools.end()) + { + build_error_undefined_tool((*i).first.c_str(), (*j).first.c_str()); + } + } + } +#endif +} + +void build_run(const char* name, CommandListener& listener) +{ + for(Tools::iterator i = g_build_tools.begin(); i != g_build_tools.end(); ++i) + { + StringBuffer output; + (*i).second.evaluate(output); + build_set_variable((*i).first.c_str(), output.c_str()); + } + + { + Project::iterator i = Project_find(g_build_project, name); + if(i != g_build_project.end()) + { + Build& build = (*i).second; + for(Build::iterator j = build.begin(); j != build.end(); ++j) + { + StringBuffer output; + (*j).evaluate(output); + listener.execute(output.c_str()); + } + } + else + { + globalErrorStream() << "build " << makeQuoted(name) << " not defined"; + } + } +} + + +typedef std::vector XMLElementStack; + +class XMLParser : public XMLImporter +{ + XMLElementStack m_stack; +public: + XMLParser(XMLElementParser& parser) + { + m_stack.push_back(&parser); + } + std::size_t write(const char* buffer, std::size_t length) + { + return m_stack.back()->write(buffer, length); + } + void pushElement(const XMLElement& element) + { + m_stack.push_back(&m_stack.back()->pushElement(element)); + } + void popElement(const char* name) + { + m_stack.pop_back(); + m_stack.back()->popElement(name); + } +}; + +#include "stream/textfilestream.h" +#include "xml/xmlparser.h" + +const char* const BUILDMENU_VERSION = "2.0"; + +bool build_commands_parse(const char* filename) +{ + TextFileInputStream projectFile(filename); + if(!projectFile.failed()) + { + ProjectXMLConstructor projectConstructor(g_build_project, g_build_tools); + RootXMLConstructor rootConstructor("project", projectConstructor, BUILDMENU_VERSION); + XMLParser importer(rootConstructor); + XMLStreamParser parser(projectFile); + parser.exportXML(importer); + + if(rootConstructor.versionCompatible()) + { + project_verify(g_build_project, g_build_tools); + + return true; + } + globalErrorStream() << "failed to parse build menu: " << makeQuoted(filename) << "\n"; + } + return false; +} + +void build_commands_clear() +{ + g_build_project.clear(); + g_build_tools.clear(); +} + +class BuildXMLExporter +{ + Build& m_build; +public: + BuildXMLExporter(Build& build) : m_build(build) + { + } + void exportXML(XMLImporter& importer) + { + importer << "\n"; + for(Build::iterator i = m_build.begin(); i != m_build.end(); ++i) + { + StaticElement commandElement("command"); + importer.pushElement(commandElement); + (*i).exportXML(importer); + importer.popElement(commandElement.name()); + importer << "\n"; + } + } +}; + +class ProjectXMLExporter +{ + Project& m_project; + Tools& m_tools; +public: + ProjectXMLExporter(Project& project, Tools& tools) : m_project(project), m_tools(tools) + { + } + void exportXML(XMLImporter& importer) + { + StaticElement projectElement("project"); + projectElement.insertAttribute("version", BUILDMENU_VERSION); + importer.pushElement(projectElement); + importer << "\n"; + + for(Tools::iterator i = m_tools.begin(); i != m_tools.end(); ++i) + { + StaticElement toolElement("var"); + toolElement.insertAttribute("name", (*i).first.c_str()); + importer.pushElement(toolElement); + (*i).second.exportXML(importer); + importer.popElement(toolElement.name()); + importer << "\n"; + } + for(Project::iterator i = m_project.begin(); i != m_project.end(); ++i) + { + StaticElement buildElement("build"); + buildElement.insertAttribute("name", (*i).first.c_str()); + importer.pushElement(buildElement); + BuildXMLExporter buildExporter((*i).second); + buildExporter.exportXML(importer); + importer.popElement(buildElement.name()); + importer << "\n"; + } + importer.popElement(projectElement.name()); + } +}; + +#include "xml/xmlwriter.h" + +void build_commands_write(const char* filename) +{ + TextFileOutputStream projectFile(filename); + if(!projectFile.failed()) + { + XMLStreamWriter writer(projectFile); + ProjectXMLExporter projectExporter(g_build_project, g_build_tools); + writer << "\n"; + projectExporter.exportXML(writer); + writer << "\n"; + } +} + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gtkutil/dialog.h" +#include "gtkutil/closure.h" +#include "gtkutil/window.h" +#include "gtkdlgs.h" + +void Build_refreshMenu(GtkMenu* menu); + + +void BSPCommandList_Construct(GtkListStore* store, Project& project) +{ + gtk_list_store_clear(store); + + for(Project::iterator i = project.begin(); i != project.end(); ++i) + { + const char* buildName = (*i).first.c_str(); + + GtkTreeIter buildIter; + gtk_list_store_append(store, &buildIter); + gtk_list_store_set(store, &buildIter, 0, const_cast(buildName), -1); + } + + GtkTreeIter lastIter; + gtk_list_store_append(store, &lastIter); +} + +class ProjectList +{ +public: + Project& m_project; + GtkListStore* m_store; + bool m_changed; + ProjectList(Project& project) : m_project(project), m_changed(false) + { + } +}; + +gboolean project_cell_edited(GtkCellRendererText* cell, gchar* path_string, gchar* new_text, ProjectList* projectList) +{ + Project& project = projectList->m_project; + + GtkTreePath* path = gtk_tree_path_new_from_string(path_string); + + ASSERT_MESSAGE(gtk_tree_path_get_depth(path) == 1, "invalid path length"); + + GtkTreeIter iter; + gtk_tree_model_get_iter(GTK_TREE_MODEL(projectList->m_store), &iter, path); + + Project::iterator i = Project_find(project, gtk_tree_path_get_indices(path)[0]); + if(i != project.end()) + { + projectList->m_changed = true; + if(string_empty(new_text)) + { + project.erase(i); + Build_refreshMenu(g_bsp_menu); + + gtk_list_store_remove(projectList->m_store, &iter); + } + else + { + (*i).first = new_text; + Build_refreshMenu(g_bsp_menu); + + gtk_list_store_set(projectList->m_store, &iter, 0, new_text, -1); + } + } + else if(!string_empty(new_text)) + { + projectList->m_changed = true; + project.push_back(Project::value_type(new_text, Build())); + Build_refreshMenu(g_bsp_menu); + + gtk_list_store_set(projectList->m_store, &iter, 0, new_text, -1); + GtkTreeIter lastIter; + gtk_list_store_append(projectList->m_store, &lastIter); + } + + gtk_tree_path_free(path); + + return FALSE; +} + +gboolean project_key_press(GtkWidget* widget, GdkEventKey* event, ProjectList* projectList) +{ + Project& project = projectList->m_project; + + if(event->keyval == GDK_Delete) + { + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)); + GtkTreeIter iter; + GtkTreeModel* model; + if(gtk_tree_selection_get_selected(selection, &model, &iter)) + { + GtkTreePath* path = gtk_tree_model_get_path(model, &iter); + Project::iterator x = Project_find(project, gtk_tree_path_get_indices(path)[0]); + gtk_tree_path_free(path); + + if(x != project.end()) + { + projectList->m_changed = true; + project.erase(x); + Build_refreshMenu(g_bsp_menu); + + gtk_list_store_remove(projectList->m_store, &iter); + } + } + } + return FALSE; +} + + +Build* g_current_build = 0; + +gboolean project_selection_changed(GtkTreeSelection* selection, GtkListStore* store) +{ + Project& project = g_build_project; + + gtk_list_store_clear(store); + + GtkTreeIter iter; + GtkTreeModel* model; + if(gtk_tree_selection_get_selected(selection, &model, &iter)) + { + GtkTreePath* path = gtk_tree_model_get_path(model, &iter); + Project::iterator x = Project_find(project, gtk_tree_path_get_indices(path)[0]); + gtk_tree_path_free(path); + + if(x != project.end()) + { + Build& build = (*x).second; + g_current_build = &build; + + for(Build::iterator i = build.begin(); i != build.end(); ++i) + { + GtkTreeIter commandIter; + gtk_list_store_append(store, &commandIter); + gtk_list_store_set(store, &commandIter, 0, const_cast((*i).c_str()), -1); + } + GtkTreeIter lastIter; + gtk_list_store_append(store, &lastIter); + } + else + { + g_current_build = 0; + } + } + else + { + g_current_build = 0; + } + + return FALSE; +} + +gboolean commands_cell_edited(GtkCellRendererText* cell, gchar* path_string, gchar* new_text, GtkListStore* store) +{ + if(g_current_build == 0) + { + return FALSE; + } + Build& build = *g_current_build; + + GtkTreePath* path = gtk_tree_path_new_from_string(path_string); + + ASSERT_MESSAGE(gtk_tree_path_get_depth(path) == 1, "invalid path length"); + + GtkTreeIter iter; + gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path); + + Build::iterator i = Build_find(build, gtk_tree_path_get_indices(path)[0]); + if(i != build.end()) + { + g_build_changed = true; + (*i).setString(new_text); + + gtk_list_store_set(store, &iter, 0, new_text, -1); + } + else if(!string_empty(new_text)) + { + g_build_changed = true; + build.push_back(Build::value_type(VariableString(new_text))); + + gtk_list_store_set(store, &iter, 0, new_text, -1); + + GtkTreeIter lastIter; + gtk_list_store_append(store, &lastIter); + } + + gtk_tree_path_free(path); + + return FALSE; +} + +gboolean commands_key_press(GtkWidget* widget, GdkEventKey* event, GtkListStore* store) +{ + if(g_current_build == 0) + { + return FALSE; + } + Build& build = *g_current_build; + + if(event->keyval == GDK_Delete) + { + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)); + GtkTreeIter iter; + GtkTreeModel* model; + if(gtk_tree_selection_get_selected(selection, &model, &iter)) + { + GtkTreePath* path = gtk_tree_model_get_path(model, &iter); + Build::iterator i = Build_find(build, gtk_tree_path_get_indices(path)[0]); + gtk_tree_path_free(path); + + if(i != build.end()) + { + g_build_changed = true; + build.erase(i); + + gtk_list_store_remove(store, &iter); + } + } + } + return FALSE; +} + + +GtkWindow* BuildMenuDialog_construct(ModalDialog& modal, ProjectList& projectList) +{ + GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Build Menu", G_CALLBACK(dialog_delete_callback), &modal, -1, 400); + + GtkWidget* buildView = 0; + + { + GtkTable* table1 = create_dialog_table(2, 2, 4, 4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(table1)); + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_table_attach(table1, GTK_WIDGET(vbox), 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + { + GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &modal); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + } + { + GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &modal); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + } + } + { + GtkFrame* frame = create_dialog_frame("Build menu"); + gtk_table_attach(table1, GTK_WIDGET(frame), 0, 1, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0); + { + GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(scr)); + + { + GtkListStore* store = gtk_list_store_new(1, G_TYPE_STRING); + + GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); + + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + object_set_boolean_property(G_OBJECT(renderer), "editable", TRUE); + g_signal_connect(renderer, "edited", G_CALLBACK(project_cell_edited), &projectList); + + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("", renderer, "text", 0, 0); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + + GtkTreeSelection* selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE); + + gtk_widget_show(view); + + buildView = view; + projectList.m_store = store; + gtk_container_add(GTK_CONTAINER (scr), view); + + g_signal_connect(G_OBJECT(view), "key_press_event", G_CALLBACK(project_key_press), &projectList); + + g_object_unref(G_OBJECT(store)); + } + } + } + { + GtkFrame* frame = create_dialog_frame("Commandline"); + gtk_table_attach(table1, GTK_WIDGET(frame), 0, 1, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0); + { + GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(scr)); + + { + GtkListStore* store = gtk_list_store_new(1, G_TYPE_STRING); + + GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); + + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + object_set_boolean_property(G_OBJECT(renderer), "editable", TRUE); + g_signal_connect(renderer, "edited", G_CALLBACK(commands_cell_edited), store); + + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("", renderer, "text", 0, 0); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + + GtkTreeSelection* selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE); + + gtk_widget_show(view); + + gtk_container_add(GTK_CONTAINER (scr), view); + + g_object_unref(G_OBJECT(store)); + + g_signal_connect(G_OBJECT(view), "key_press_event", G_CALLBACK(commands_key_press), store); + + g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(buildView))), "changed", G_CALLBACK(project_selection_changed), store); + } + } + } + } + + BSPCommandList_Construct(projectList.m_store, g_build_project); + + return window; +} + +namespace +{ + CopiedString g_buildMenu; +} + +void LoadBuildMenu(); + +void DoBuildMenu() +{ + ModalDialog modal; + + ProjectList projectList(g_build_project); + + GtkWindow* window = BuildMenuDialog_construct(modal, projectList); + + if(modal_dialog_show(window, modal) == eIDCANCEL) + { + build_commands_clear(); + LoadBuildMenu(); + + Build_refreshMenu(g_bsp_menu); + } + else if(projectList.m_changed) + { + g_build_changed = true; + } + + gtk_widget_destroy(GTK_WIDGET(window)); +} + + + +#include "gtkutil/menu.h" +#include "mainframe.h" +#include "preferences.h" +#include "qe3.h" + +typedef struct _GtkMenuItem GtkMenuItem; + +class BuildMenuItem +{ + const char* m_name; +public: + GtkMenuItem* m_item; + BuildMenuItem(const char* name, GtkMenuItem* item) + : m_name(name), m_item(item) + { + } + void run() + { + RunBSP(m_name); + } + typedef MemberCaller RunCaller; +}; + +typedef std::list BuildMenuItems; +BuildMenuItems g_BuildMenuItems; + + +GtkMenu* g_bsp_menu; + +void Build_constructMenu(GtkMenu* menu) +{ + for(Project::iterator i = g_build_project.begin(); i != g_build_project.end(); ++i) + { + g_BuildMenuItems.push_back(BuildMenuItem((*i).first.c_str(), 0)); + g_BuildMenuItems.back().m_item = create_menu_item_with_mnemonic(menu, (*i).first.c_str(), BuildMenuItem::RunCaller(g_BuildMenuItems.back())); + } +} + + +void Build_refreshMenu(GtkMenu* menu) +{ + for(BuildMenuItems::iterator i = g_BuildMenuItems.begin(); i != g_BuildMenuItems.end(); ++i) + { + gtk_container_remove(GTK_CONTAINER(menu), GTK_WIDGET((*i).m_item)); + } + + g_BuildMenuItems.clear(); + + Build_constructMenu(menu); +} + + +void LoadBuildMenu() +{ + if(string_empty(g_buildMenu.c_str()) || !build_commands_parse(g_buildMenu.c_str())) + { + { + StringOutputStream buffer(256); + buffer << GameToolsPath_get() << "default_build_menu.xml"; + + bool success = build_commands_parse(buffer.c_str()); + ASSERT_MESSAGE(success, "failed to parse default build commands: " << buffer.c_str()); + } + { + StringOutputStream buffer(256); + buffer << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/build_menu.xml"; + + g_buildMenu = buffer.c_str(); + } + } +} + +void SaveBuildMenu() +{ + if(g_build_changed) + { + g_build_changed = false; + build_commands_write(g_buildMenu.c_str()); + } +} + +#include "preferencesystem.h" +#include "stringio.h" + +void BuildMenu_Construct() +{ + GlobalPreferenceSystem().registerPreference("BuildMenu", CopiedStringImportStringCaller(g_buildMenu), CopiedStringExportStringCaller(g_buildMenu)); + LoadBuildMenu(); +} +void BuildMenu_Destroy() +{ + SaveBuildMenu(); +} diff --git a/radiant/build.h b/radiant/build.h new file mode 100644 index 00000000..3b3244d7 --- /dev/null +++ b/radiant/build.h @@ -0,0 +1,44 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#if !defined(INCLUDED_BUILD_H) +#define INCLUDED_BUILD_H + +void build_set_variable(const char* name, const char* value); +void build_clear_variables(); + +class CommandListener +{ +public: + virtual void execute(const char* command) = 0; +}; +void build_run(const char* name, CommandListener& listener); + +void DoBuildMenu(); + +void BuildMenu_Construct(); +void BuildMenu_Destroy(); + +typedef struct _GtkMenu GtkMenu; +void Build_constructMenu(GtkMenu* menu); +extern GtkMenu* g_bsp_menu; + + +#endif diff --git a/radiant/camwindow.cpp b/radiant/camwindow.cpp new file mode 100644 index 00000000..b89e5447 --- /dev/null +++ b/radiant/camwindow.cpp @@ -0,0 +1,2091 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// Camera Window +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "camwindow.h" + +#include "debugging/debugging.h" + +#include "iscenegraph.h" +#include "irender.h" +#include "igl.h" +#include "icamera.h" +#include "cullable.h" +#include "renderable.h" +#include "preferencesystem.h" + +#include "signal/signal.h" +#include "container/array.h" +#include "scenelib.h" +#include "render.h" +#include "cmdlib.h" +#include "math/frustum.h" + +#include "gtkutil/widget.h" +#include "gtkutil/button.h" +#include "gtkutil/toolbar.h" +#include "gtkutil/glwidget.h" +#include "gtkutil/xorrectangle.h" +#include "gtkmisc.h" +#include "selection.h" +#include "mainframe.h" +#include "preferences.h" +#include "commands.h" +#include "xywindow.h" +#include "windowobservers.h" +#include "renderstate.h" + +#include "timer.h" + +Signal0 g_cameraMoved_callbacks; + +void AddCameraMovedCallback(const SignalHandler& handler) +{ + g_cameraMoved_callbacks.connectLast(handler); +} + +void CameraMovedNotify() +{ + g_cameraMoved_callbacks(); +} + + +struct camwindow_globals_private_t +{ + int m_nMoveSpeed; + bool m_bCamLinkSpeed; + int m_nAngleSpeed; + bool m_bCamInverseMouse; + bool m_bCamDiscrete; + bool m_bCubicClipping; + bool m_showStats; + + camwindow_globals_private_t() : + m_nMoveSpeed(100), + m_bCamLinkSpeed(true), + m_nAngleSpeed(3), + m_bCamInverseMouse(false), + m_bCamDiscrete(true), + m_bCubicClipping(true), + m_showStats(true) + { + } + +}; + +camwindow_globals_private_t g_camwindow_globals_private; + + +const Matrix4 g_opengl2radiant( + 0, 0,-1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +); + +const Matrix4 g_radiant2opengl( + 0,-1, 0, 0, + 0, 0, 1, 0, + -1, 0, 0, 0, + 0, 0, 0, 1 +); + +struct camera_t; +void Camera_mouseMove(camera_t& camera, int x, int y); + +enum camera_draw_mode +{ + cd_wire, + cd_solid, + cd_texture, + cd_lighting +}; + +struct camera_t +{ + int width, height; + + bool timing; + + Vector3 origin; + Vector3 angles; + + Vector3 color; // background + + Vector3 forward, right; // move matrix (TTimo: used to have up but it was not updated) + Vector3 vup, vpn, vright; // view matrix (taken from the modelview matrix) + + Matrix4 projection; + Matrix4 modelview; + + bool m_strafe; // true when in strafemode toggled by the ctrl-key + bool m_strafe_forward; // true when in strafemode by ctrl-key and shift is pressed for forward strafing + + unsigned int movementflags; // movement flags + Timer m_keycontrol_timer; + guint m_keymove_handler; + + + float fieldOfView; + + DeferredMotionDelta m_mouseMove; + + static void motionDelta(int x, int y, void* data) + { + Camera_mouseMove(*reinterpret_cast(data), x, y); + } + + View* m_view; + Callback m_update; + + static camera_draw_mode draw_mode; + + camera_t(View* view, const Callback& update) + : width(0), + height(0), + timing(false), + origin(0, 0, 0), + angles(0, 0, 0), + color(0, 0, 0), + movementflags(0), + m_keymove_handler(0), + fieldOfView(90.0f), + m_mouseMove(motionDelta, this), + m_view(view), + m_update(update) + { + } +}; + +camera_draw_mode camera_t::draw_mode = cd_texture; + +inline Matrix4 projection_for_camera(float near_z, float far_z, float fieldOfView, int width, int height) +{ + const float half_width = static_cast(near_z * tan(degrees_to_radians(fieldOfView * 0.5))); + const float half_height = half_width * (static_cast(height) / static_cast(width)); + + return matrix4_frustum( + -half_width, + half_width, + -half_height, + half_height, + near_z, + far_z + ); +} + +float Camera_getFarClipPlane(camera_t& camera) +{ + return (g_camwindow_globals_private.m_bCubicClipping)? pow(2.0, (g_camwindow_globals.m_nCubicScale + 7) / 2.0) : 32768.0f; +} + +void Camera_updateProjection(camera_t& camera) +{ + float farClip = Camera_getFarClipPlane(camera); + camera.projection = projection_for_camera(farClip / 4096.0f, farClip, camera.fieldOfView, camera.width, camera.height); + + camera.m_view->Construct(camera.projection, camera.modelview, camera.width, camera.height); +} + +void Camera_updateVectors(camera_t& camera) +{ + for (int i=0 ; i<3 ; i++) + { + camera.vright[i] = camera.modelview[(i<<2)+0]; + camera.vup[i] = camera.modelview[(i<<2)+1]; + camera.vpn[i] = camera.modelview[(i<<2)+2]; + } +} + +void Camera_updateModelview(camera_t& camera) +{ + camera.modelview = g_matrix4_identity; + + // roll, pitch, yaw + Vector3 radiant_eulerXYZ(0, -camera.angles[CAMERA_PITCH], camera.angles[CAMERA_YAW]); + + matrix4_translate_by_vec3(camera.modelview, camera.origin); + matrix4_rotate_by_euler_xyz_degrees(camera.modelview, radiant_eulerXYZ); + matrix4_multiply_by_matrix4(camera.modelview, g_radiant2opengl); + matrix4_affine_invert(camera.modelview); + + Camera_updateVectors(camera); + + camera.m_view->Construct(camera.projection, camera.modelview, camera.width, camera.height); +} + + +void Camera_Move_updateAxes(camera_t& camera) +{ + double ya = degrees_to_radians(camera.angles[CAMERA_YAW]); + + // the movement matrix is kept 2d + camera.forward[0] = static_cast(cos(ya)); + camera.forward[1] = static_cast(sin(ya)); + camera.forward[2] = 0; + camera.right[0] = camera.forward[1]; + camera.right[1] = -camera.forward[0]; +} + +void Camera_Freemove_updateAxes(camera_t& camera) +{ + camera.right = camera.vright; + camera.forward = vector3_negated(camera.vpn); +} + +const Vector3& Camera_getOrigin(camera_t& camera) +{ + return camera.origin; +} + +void Camera_setOrigin(camera_t& camera, const Vector3& origin) +{ + camera.origin = origin; + Camera_updateModelview(camera); + camera.m_update(); + CameraMovedNotify(); +} + +const Vector3& Camera_getAngles(camera_t& camera) +{ + return camera.angles; +} + +void Camera_setAngles(camera_t& camera, const Vector3& angles) +{ + camera.angles = angles; + Camera_updateModelview(camera); + camera.m_update(); + CameraMovedNotify(); +} + + +void Camera_FreeMove(camera_t& camera, int dx, int dy) +{ + // free strafe mode, toggled by the ctrl key with optional shift for forward movement + if(camera.m_strafe) + { + float strafespeed = 0.65f; + + if(g_camwindow_globals_private.m_bCamLinkSpeed) + { + strafespeed = (float)g_camwindow_globals_private.m_nMoveSpeed / 100; + } + + camera.origin -= camera.vright * strafespeed * dx; + if(camera.m_strafe_forward) + camera.origin += camera.vpn * strafespeed * dy; + else + camera.origin += camera.vup * strafespeed * dy; + } + else// free rotation + { + const float dtime = 0.1f; + + if (g_camwindow_globals_private.m_bCamInverseMouse) + camera.angles[CAMERA_PITCH] -= dy * dtime * g_camwindow_globals_private.m_nAngleSpeed; + else + camera.angles[CAMERA_PITCH] += dy * dtime * g_camwindow_globals_private.m_nAngleSpeed; + + camera.angles[CAMERA_YAW] += dx * dtime * g_camwindow_globals_private.m_nAngleSpeed; + + if (camera.angles[CAMERA_PITCH] > 90) + camera.angles[CAMERA_PITCH] = 90; + else if (camera.angles[CAMERA_PITCH] < -90) + camera.angles[CAMERA_PITCH] = -90; + + if (camera.angles[CAMERA_YAW] >= 360) + camera.angles[CAMERA_YAW] -=360; + else if (camera.angles[CAMERA_YAW] <= 0) + camera.angles[CAMERA_YAW] +=360; + } + + Camera_updateModelview(camera); + Camera_Freemove_updateAxes(camera); +} + +void Cam_MouseControl(camera_t& camera, int x, int y) +{ + int xl, xh; + int yl, yh; + float xf, yf; + + xf = (float)(x - camera.width/2) / (camera.width/2); + yf = (float)(y - camera.height/2) / (camera.height/2); + + xl = camera.width/3; + xh = xl*2; + yl = camera.height/3; + yh = yl*2; + + xf *= 1.0f - fabsf(yf); + if (xf < 0) + { + xf += 0.1f; + if (xf > 0) + xf = 0; + } + else + { + xf -= 0.1f; + if (xf < 0) + xf = 0; + } + + vector3_add(camera.origin, vector3_scaled(camera.forward, yf * 0.1f* g_camwindow_globals_private.m_nMoveSpeed)); + camera.angles[CAMERA_YAW] += xf * -0.1f * g_camwindow_globals_private.m_nAngleSpeed; + + Camera_updateModelview(camera); +} + +void Camera_mouseMove(camera_t& camera, int x, int y) +{ + //globalOutputStream() << "mousemove... "; + Camera_FreeMove(camera, -x, -y); + camera.m_update(); + CameraMovedNotify(); +} + +const unsigned int MOVE_NONE = 0; +const unsigned int MOVE_FORWARD = 1 << 0; +const unsigned int MOVE_BACK = 1 << 1; +const unsigned int MOVE_ROTRIGHT = 1 << 2; +const unsigned int MOVE_ROTLEFT = 1 << 3; +const unsigned int MOVE_STRAFERIGHT = 1 << 4; +const unsigned int MOVE_STRAFELEFT = 1 << 5; +const unsigned int MOVE_UP = 1 << 6; +const unsigned int MOVE_DOWN = 1 << 7; +const unsigned int MOVE_PITCHUP = 1 << 8; +const unsigned int MOVE_PITCHDOWN = 1 << 9; +const unsigned int MOVE_ALL = MOVE_FORWARD|MOVE_BACK|MOVE_ROTRIGHT|MOVE_ROTLEFT|MOVE_STRAFERIGHT|MOVE_STRAFELEFT|MOVE_UP|MOVE_DOWN|MOVE_PITCHUP|MOVE_PITCHDOWN; + +void Cam_KeyControl(camera_t& camera, float dtime) +{ + // Update angles + if (camera.movementflags & MOVE_ROTLEFT) + camera.angles[CAMERA_YAW] += 15 * dtime* g_camwindow_globals_private.m_nAngleSpeed; + if (camera.movementflags & MOVE_ROTRIGHT) + camera.angles[CAMERA_YAW] -= 15 * dtime * g_camwindow_globals_private.m_nAngleSpeed; + if (camera.movementflags & MOVE_PITCHUP) + { + camera.angles[CAMERA_PITCH] += 15 * dtime* g_camwindow_globals_private.m_nAngleSpeed; + if(camera.angles[CAMERA_PITCH] > 90) + camera.angles[CAMERA_PITCH] = 90; + } + if (camera.movementflags & MOVE_PITCHDOWN) + { + camera.angles[CAMERA_PITCH] -= 15 * dtime * g_camwindow_globals_private.m_nAngleSpeed; + if(camera.angles[CAMERA_PITCH] < -90) + camera.angles[CAMERA_PITCH] = -90; + } + + Camera_updateModelview(camera); + Camera_Freemove_updateAxes(camera); + + // Update position + if (camera.movementflags & MOVE_FORWARD) + vector3_add(camera.origin, vector3_scaled(camera.forward, dtime * g_camwindow_globals_private.m_nMoveSpeed)); + if (camera.movementflags & MOVE_BACK) + vector3_add(camera.origin, vector3_scaled(camera.forward, -dtime * g_camwindow_globals_private.m_nMoveSpeed)); + if (camera.movementflags & MOVE_STRAFELEFT) + vector3_add(camera.origin, vector3_scaled(camera.right, -dtime * g_camwindow_globals_private.m_nMoveSpeed)); + if (camera.movementflags & MOVE_STRAFERIGHT) + vector3_add(camera.origin, vector3_scaled(camera.right, dtime * g_camwindow_globals_private.m_nMoveSpeed)); + if (camera.movementflags & MOVE_UP) + vector3_add(camera.origin, vector3_scaled(g_vector3_axis_z, dtime * g_camwindow_globals_private.m_nMoveSpeed)); + if (camera.movementflags & MOVE_DOWN) + vector3_add(camera.origin, vector3_scaled(g_vector3_axis_z, -dtime * g_camwindow_globals_private.m_nMoveSpeed)); + + Camera_updateModelview(camera); +} + +void Camera_keyMove(camera_t& camera) +{ + camera.m_mouseMove.flush(); + + //globalOutputStream() << "keymove... "; + float time_seconds = camera.m_keycontrol_timer.elapsed_msec() / static_cast(msec_per_sec); + camera.m_keycontrol_timer.start(); + if(time_seconds > 0.05f) + { + time_seconds = 0.05f; // 20fps + } + Cam_KeyControl(camera, time_seconds * 5.0f); + + camera.m_update(); + CameraMovedNotify(); +} + +gboolean camera_keymove(gpointer data) +{ + Camera_keyMove(*reinterpret_cast(data)); + return TRUE; +} + +void Camera_setMovementFlags(camera_t& camera, unsigned int mask) +{ + if((~camera.movementflags & mask) != 0 && camera.movementflags == 0) + { + camera.m_keymove_handler = g_idle_add(camera_keymove, &camera); + } + camera.movementflags |= mask; +} +void Camera_clearMovementFlags(camera_t& camera, unsigned int mask) +{ + if((camera.movementflags & ~mask) == 0 && camera.movementflags != 0) + { + g_source_remove(camera.m_keymove_handler); + camera.m_keymove_handler = 0; + } + camera.movementflags &= ~mask; +} + +void Camera_MoveForward_KeyDown(camera_t& camera) +{ + Camera_setMovementFlags(camera, MOVE_FORWARD); +} +void Camera_MoveForward_KeyUp(camera_t& camera) +{ + Camera_clearMovementFlags(camera, MOVE_FORWARD); +} +void Camera_MoveBack_KeyDown(camera_t& camera) +{ + Camera_setMovementFlags(camera, MOVE_BACK); +} +void Camera_MoveBack_KeyUp(camera_t& camera) +{ + Camera_clearMovementFlags(camera, MOVE_BACK); +} + +void Camera_MoveLeft_KeyDown(camera_t& camera) +{ + Camera_setMovementFlags(camera, MOVE_STRAFELEFT); +} +void Camera_MoveLeft_KeyUp(camera_t& camera) +{ + Camera_clearMovementFlags(camera, MOVE_STRAFELEFT); +} +void Camera_MoveRight_KeyDown(camera_t& camera) +{ + Camera_setMovementFlags(camera, MOVE_STRAFERIGHT); +} +void Camera_MoveRight_KeyUp(camera_t& camera) +{ + Camera_clearMovementFlags(camera, MOVE_STRAFERIGHT); +} + +void Camera_MoveUp_KeyDown(camera_t& camera) +{ + Camera_setMovementFlags(camera, MOVE_UP); +} +void Camera_MoveUp_KeyUp(camera_t& camera) +{ + Camera_clearMovementFlags(camera, MOVE_UP); +} +void Camera_MoveDown_KeyDown(camera_t& camera) +{ + Camera_setMovementFlags(camera, MOVE_DOWN); +} +void Camera_MoveDown_KeyUp(camera_t& camera) +{ + Camera_clearMovementFlags(camera, MOVE_DOWN); +} + +void Camera_RotateLeft_KeyDown(camera_t& camera) +{ + Camera_setMovementFlags(camera, MOVE_ROTLEFT); +} +void Camera_RotateLeft_KeyUp(camera_t& camera) +{ + Camera_clearMovementFlags(camera, MOVE_ROTLEFT); +} +void Camera_RotateRight_KeyDown(camera_t& camera) +{ + Camera_setMovementFlags(camera, MOVE_ROTRIGHT); +} +void Camera_RotateRight_KeyUp(camera_t& camera) +{ + Camera_clearMovementFlags(camera, MOVE_ROTRIGHT); +} + +void Camera_PitchUp_KeyDown(camera_t& camera) +{ + Camera_setMovementFlags(camera, MOVE_PITCHUP); +} +void Camera_PitchUp_KeyUp(camera_t& camera) +{ + Camera_clearMovementFlags(camera, MOVE_PITCHUP); +} +void Camera_PitchDown_KeyDown(camera_t& camera) +{ + Camera_setMovementFlags(camera, MOVE_PITCHDOWN); +} +void Camera_PitchDown_KeyUp(camera_t& camera) +{ + Camera_clearMovementFlags(camera, MOVE_PITCHDOWN); +} + + +typedef ReferenceCaller FreeMoveCameraMoveForwardKeyDownCaller; +typedef ReferenceCaller FreeMoveCameraMoveForwardKeyUpCaller; +typedef ReferenceCaller FreeMoveCameraMoveBackKeyDownCaller; +typedef ReferenceCaller FreeMoveCameraMoveBackKeyUpCaller; +typedef ReferenceCaller FreeMoveCameraMoveLeftKeyDownCaller; +typedef ReferenceCaller FreeMoveCameraMoveLeftKeyUpCaller; +typedef ReferenceCaller FreeMoveCameraMoveRightKeyDownCaller; +typedef ReferenceCaller FreeMoveCameraMoveRightKeyUpCaller; +typedef ReferenceCaller FreeMoveCameraMoveUpKeyDownCaller; +typedef ReferenceCaller FreeMoveCameraMoveUpKeyUpCaller; +typedef ReferenceCaller FreeMoveCameraMoveDownKeyDownCaller; +typedef ReferenceCaller FreeMoveCameraMoveDownKeyUpCaller; + + +#define SPEED_MOVE 32 +#define SPEED_TURN 22.5 +#define MIN_CAM_SPEED 10 +#define MAX_CAM_SPEED 610 +#define CAM_SPEED_STEP 50 + +void Camera_MoveForward_Discrete(camera_t& camera) +{ + Camera_Move_updateAxes(camera); + Camera_setOrigin(camera, vector3_added(Camera_getOrigin(camera), vector3_scaled(camera.forward, SPEED_MOVE))); +} +void Camera_MoveBack_Discrete(camera_t& camera) +{ + Camera_Move_updateAxes(camera); + Camera_setOrigin(camera, vector3_added(Camera_getOrigin(camera), vector3_scaled(camera.forward, -SPEED_MOVE))); +} + +void Camera_MoveUp_Discrete(camera_t& camera) +{ + Vector3 origin(Camera_getOrigin(camera)); + origin[2] += SPEED_MOVE; + Camera_setOrigin(camera, origin); +} +void Camera_MoveDown_Discrete(camera_t& camera) +{ + Vector3 origin(Camera_getOrigin(camera)); + origin[2] -= SPEED_MOVE; + Camera_setOrigin(camera, origin); +} + +void Camera_MoveLeft_Discrete(camera_t& camera) +{ + Camera_Move_updateAxes(camera); + Camera_setOrigin(camera, vector3_added(Camera_getOrigin(camera), vector3_scaled(camera.right, -SPEED_MOVE))); +} +void Camera_MoveRight_Discrete(camera_t& camera) +{ + Camera_Move_updateAxes(camera); + Camera_setOrigin(camera, vector3_added(Camera_getOrigin(camera), vector3_scaled(camera.right, SPEED_MOVE))); +} + +void Camera_RotateLeft_Discrete(camera_t& camera) +{ + Vector3 angles(Camera_getAngles(camera)); + angles[CAMERA_YAW] += SPEED_TURN; + Camera_setAngles(camera, angles); +} +void Camera_RotateRight_Discrete(camera_t& camera) +{ + Vector3 angles(Camera_getAngles(camera)); + angles[CAMERA_YAW] -= SPEED_TURN; + Camera_setAngles(camera, angles); +} + +void Camera_PitchUp_Discrete(camera_t& camera) +{ + Vector3 angles(Camera_getAngles(camera)); + angles[CAMERA_PITCH] += SPEED_TURN; + if (angles[CAMERA_PITCH] > 90) + angles[CAMERA_PITCH] = 90; + Camera_setAngles(camera, angles); +} +void Camera_PitchDown_Discrete(camera_t& camera) +{ + Vector3 angles(Camera_getAngles(camera)); + angles[CAMERA_PITCH] -= SPEED_TURN; + if (angles[CAMERA_PITCH] < -90) + angles[CAMERA_PITCH] = -90; + Camera_setAngles(camera, angles); +} + + +class RadiantCameraView : public CameraView +{ + camera_t& m_camera; + View* m_view; + Callback m_update; +public: + RadiantCameraView(camera_t& camera, View* view, const Callback& update) : m_camera(camera), m_view(view), m_update(update) + { + } + void update() + { + m_view->Construct(m_camera.projection, m_camera.modelview, m_camera.width, m_camera.height); + m_update(); + } + void setModelview(const Matrix4& modelview) + { + m_camera.modelview = modelview; + matrix4_multiply_by_matrix4(m_camera.modelview, g_radiant2opengl); + matrix4_affine_invert(m_camera.modelview); + Camera_updateVectors(m_camera); + update(); + } + void setFieldOfView(float fieldOfView) + { + float farClip = Camera_getFarClipPlane(m_camera); + m_camera.projection = projection_for_camera(farClip / 4096.0f, farClip, fieldOfView, m_camera.width, m_camera.height); + update(); + } +}; + + +void Camera_motionDelta(int x, int y, unsigned int state, void* data) +{ + camera_t* cam = reinterpret_cast(data); + + cam->m_mouseMove.motion_delta(x, y, state); + cam->m_strafe = (state & GDK_CONTROL_MASK) != 0; + + if(cam->m_strafe) + cam->m_strafe_forward = (state & GDK_SHIFT_MASK) != 0; + else + cam->m_strafe_forward = false; +} + +class CamWnd +{ + View m_view; + camera_t m_Camera; + RadiantCameraView m_cameraview; +#if 0 + int m_PositionDragCursorX; + int m_PositionDragCursorY; +#endif + + guint m_freemove_handle_focusout; + + static Shader* m_state_select1; + static Shader* m_state_select2; + + FreezePointer m_freezePointer; + +public: + GtkWidget* m_gl_widget; + GtkWindow* m_parent; + + SelectionSystemWindowObserver* m_window_observer; + XORRectangle m_XORRectangle; + + DeferredDraw m_deferredDraw; + DeferredMotion m_deferred_motion; + + guint m_selection_button_press_handler; + guint m_selection_button_release_handler; + guint m_selection_motion_handler; + + guint m_freelook_button_press_handler; + + guint m_sizeHandler; + guint m_exposeHandler; + + CamWnd(); + ~CamWnd(); + + bool m_drawing; + void queue_draw() + { + //ASSERT_MESSAGE(!m_drawing, "CamWnd::queue_draw(): called while draw is already in progress"); + if(m_drawing) + { + return; + } + //globalOutputStream() << "queue... "; + m_deferredDraw.draw(); + } + void draw(); + + static void captureStates() + { + m_state_select1 = GlobalShaderCache().capture("$CAM_HIGHLIGHT"); + m_state_select2 = GlobalShaderCache().capture("$CAM_OVERLAY"); + } + static void releaseStates() + { + GlobalShaderCache().release("$CAM_HIGHLIGHT"); + GlobalShaderCache().release("$CAM_OVERLAY"); + } + + camera_t& getCamera() + { + return m_Camera; + }; + + void BenchMark(); + void Cam_ChangeFloor(bool up); + + void DisableFreeMove(); + void EnableFreeMove(); + bool m_bFreeMove; + + CameraView& getCameraView() + { + return m_cameraview; + } + +private: + void Cam_Draw(); +}; + +typedef MemberCaller CamWndQueueDraw; + +Shader* CamWnd::m_state_select1 = 0; +Shader* CamWnd::m_state_select2 = 0; + +CamWnd* NewCamWnd() +{ + return new CamWnd; +} +void DeleteCamWnd(CamWnd* camwnd) +{ + delete camwnd; +} + +void CamWnd_constructStatic() +{ + CamWnd::captureStates(); +} + +void CamWnd_destroyStatic() +{ + CamWnd::releaseStates(); +} + +static CamWnd* g_camwnd = 0; + +void GlobalCamera_setCamWnd(CamWnd& camwnd) +{ + g_camwnd = &camwnd; +} + + +GtkWidget* CamWnd_getWidget(CamWnd& camwnd) +{ + return camwnd.m_gl_widget; +} + +GtkWindow* CamWnd_getParent(CamWnd& camwnd) +{ + return camwnd.m_parent; +} + +ToggleShown g_camera_shown(true); + +void CamWnd_setParent(CamWnd& camwnd, GtkWindow* parent) +{ + camwnd.m_parent = parent; + g_camera_shown.connect(GTK_WIDGET(camwnd.m_parent)); +} + +void CamWnd_Update(CamWnd& camwnd) +{ + camwnd.queue_draw(); +} + + + +camwindow_globals_t g_camwindow_globals; + +const Vector3& Camera_getOrigin(CamWnd& camwnd) +{ + return Camera_getOrigin(camwnd.getCamera()); +} + +void Camera_setOrigin(CamWnd& camwnd, const Vector3& origin) +{ + Camera_setOrigin(camwnd.getCamera(), origin); +} + +const Vector3& Camera_getAngles(CamWnd& camwnd) +{ + return Camera_getAngles(camwnd.getCamera()); +} + +void Camera_setAngles(CamWnd& camwnd, const Vector3& angles) +{ + Camera_setAngles(camwnd.getCamera(), angles); +} + + +// ============================================================================= +// CamWnd class + +gboolean enable_freelook_button_press(GtkWidget* widget, GdkEventButton* event, CamWnd* camwnd) +{ + if(event->type == GDK_BUTTON_PRESS && event->button == 3) + { + camwnd->EnableFreeMove(); + return TRUE; + } + return FALSE; +} + +gboolean disable_freelook_button_press(GtkWidget* widget, GdkEventButton* event, CamWnd* camwnd) +{ + if(event->type == GDK_BUTTON_PRESS && event->button == 3) + { + camwnd->DisableFreeMove(); + return TRUE; + } + return FALSE; +} + +#if 0 +gboolean mousecontrol_button_press(GtkWidget* widget, GdkEventButton* event, CamWnd* camwnd) +{ + if(event->type == GDK_BUTTON_PRESS && event->button == 3) + { + Cam_MouseControl(camwnd->getCamera(), event->x, widget->allocation.height - 1 - event->y); + } + return FALSE; +} +#endif + +void camwnd_update_xor_rectangle(CamWnd& self, rect_t area) +{ + if(GTK_WIDGET_VISIBLE(self.m_gl_widget)) + { + self.m_XORRectangle.set(rectangle_from_area(area.min, area.max, self.getCamera().width, self.getCamera().height)); + } +} + + +gboolean selection_button_press(GtkWidget* widget, GdkEventButton* event, WindowObserver* observer) +{ + if(event->type == GDK_BUTTON_PRESS) + { + observer->onMouseDown(WindowVector_forDouble(event->x, event->y), button_for_button(event->button), modifiers_for_state(event->state)); + } + return FALSE; +} + +gboolean selection_button_release(GtkWidget* widget, GdkEventButton* event, WindowObserver* observer) +{ + if(event->type == GDK_BUTTON_RELEASE) + { + observer->onMouseUp(WindowVector_forDouble(event->x, event->y), button_for_button(event->button), modifiers_for_state(event->state)); + } + return FALSE; +} + +void selection_motion(gdouble x, gdouble y, guint state, void* data) +{ + //globalOutputStream() << "motion... "; + reinterpret_cast(data)->onMouseMotion(WindowVector_forDouble(x, y), modifiers_for_state(state)); +} + +inline WindowVector windowvector_for_widget_centre(GtkWidget* widget) +{ + return WindowVector(static_cast(widget->allocation.width / 2), static_cast(widget->allocation.height / 2)); +} + +gboolean selection_button_press_freemove(GtkWidget* widget, GdkEventButton* event, WindowObserver* observer) +{ + if(event->type == GDK_BUTTON_PRESS) + { + observer->onMouseDown(windowvector_for_widget_centre(widget), button_for_button(event->button), modifiers_for_state(event->state)); + } + return FALSE; +} + +gboolean selection_button_release_freemove(GtkWidget* widget, GdkEventButton* event, WindowObserver* observer) +{ + if(event->type == GDK_BUTTON_RELEASE) + { + observer->onMouseUp(windowvector_for_widget_centre(widget), button_for_button(event->button), modifiers_for_state(event->state)); + } + return FALSE; +} + +gboolean selection_motion_freemove(GtkWidget *widget, GdkEventMotion *event, WindowObserver* observer) +{ + observer->onMouseMotion(windowvector_for_widget_centre(widget), modifiers_for_state(event->state)); + return FALSE; +} + +gboolean wheelmove_scroll(GtkWidget* widget, GdkEventScroll* event, CamWnd* camwnd) +{ + if(event->direction == GDK_SCROLL_UP) + { + Camera_Freemove_updateAxes(camwnd->getCamera()); + Camera_setOrigin(*camwnd, vector3_added(Camera_getOrigin(*camwnd), vector3_scaled(camwnd->getCamera().forward, static_cast(g_camwindow_globals_private.m_nMoveSpeed)))); + } + else if(event->direction == GDK_SCROLL_DOWN) + { + Camera_Freemove_updateAxes(camwnd->getCamera()); + Camera_setOrigin(*camwnd, vector3_added(Camera_getOrigin(*camwnd), vector3_scaled(camwnd->getCamera().forward, -static_cast(g_camwindow_globals_private.m_nMoveSpeed)))); + } + + return FALSE; +} + +gboolean camera_size_allocate(GtkWidget* widget, GtkAllocation* allocation, CamWnd* camwnd) +{ + camwnd->getCamera().width = allocation->width; + camwnd->getCamera().height = allocation->height; + Camera_updateProjection(camwnd->getCamera()); + camwnd->m_window_observer->onSizeChanged(camwnd->getCamera().width, camwnd->getCamera().height); + camwnd->queue_draw(); + return FALSE; +} + +gboolean camera_expose(GtkWidget* widget, GdkEventExpose* event, gpointer data) +{ + reinterpret_cast(data)->draw(); + return FALSE; +} + +void KeyEvent_connect(const char* name) +{ + const KeyEvent& keyEvent = GlobalKeyEvents_find(name); + keydown_accelerators_add(keyEvent.m_accelerator, keyEvent.m_keyDown); + keyup_accelerators_add(keyEvent.m_accelerator, keyEvent.m_keyUp); +} + +void KeyEvent_disconnect(const char* name) +{ + const KeyEvent& keyEvent = GlobalKeyEvents_find(name); + keydown_accelerators_remove(keyEvent.m_accelerator); + keyup_accelerators_remove(keyEvent.m_accelerator); +} + +void CamWnd_registerCommands(CamWnd& camwnd) +{ + GlobalKeyEvents_insert("CameraForward", Accelerator(GDK_Up), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraBack", Accelerator(GDK_Down), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraLeft", Accelerator(GDK_Left), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraRight", Accelerator(GDK_Right), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraStrafeRight", Accelerator(GDK_period), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraStrafeLeft", Accelerator(GDK_comma), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraUp", Accelerator('D'), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraDown", Accelerator('C'), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraAngleDown", Accelerator('A'), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraAngleUp", Accelerator('Z'), + ReferenceCaller(camwnd.getCamera()), + ReferenceCaller(camwnd.getCamera()) + ); + + GlobalKeyEvents_insert("CameraFreeMoveForward", Accelerator(GDK_Up), + FreeMoveCameraMoveForwardKeyDownCaller(camwnd.getCamera()), + FreeMoveCameraMoveForwardKeyUpCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraFreeMoveBack", Accelerator(GDK_Down), + FreeMoveCameraMoveBackKeyDownCaller(camwnd.getCamera()), + FreeMoveCameraMoveBackKeyUpCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraFreeMoveLeft", Accelerator(GDK_Left), + FreeMoveCameraMoveLeftKeyDownCaller(camwnd.getCamera()), + FreeMoveCameraMoveLeftKeyUpCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraFreeMoveRight", Accelerator(GDK_Right), + FreeMoveCameraMoveRightKeyDownCaller(camwnd.getCamera()), + FreeMoveCameraMoveRightKeyUpCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraFreeMoveUp", Accelerator('D'), + FreeMoveCameraMoveUpKeyDownCaller(camwnd.getCamera()), + FreeMoveCameraMoveUpKeyUpCaller(camwnd.getCamera()) + ); + GlobalKeyEvents_insert("CameraFreeMoveDown", Accelerator('C'), + FreeMoveCameraMoveDownKeyDownCaller(camwnd.getCamera()), + FreeMoveCameraMoveDownKeyUpCaller(camwnd.getCamera()) + ); + + GlobalCommands_insert("CameraForward", ReferenceCaller(camwnd.getCamera()), Accelerator(GDK_Up)); + GlobalCommands_insert("CameraBack", ReferenceCaller(camwnd.getCamera()), Accelerator(GDK_Down)); + GlobalCommands_insert("CameraLeft", ReferenceCaller(camwnd.getCamera()), Accelerator(GDK_Left)); + GlobalCommands_insert("CameraRight", ReferenceCaller(camwnd.getCamera()), Accelerator(GDK_Right)); + GlobalCommands_insert("CameraStrafeRight", ReferenceCaller(camwnd.getCamera()), Accelerator(GDK_period)); + GlobalCommands_insert("CameraStrafeLeft", ReferenceCaller(camwnd.getCamera()), Accelerator(GDK_comma)); + + GlobalCommands_insert("CameraUp", ReferenceCaller(camwnd.getCamera()), Accelerator('D')); + GlobalCommands_insert("CameraDown", ReferenceCaller(camwnd.getCamera()), Accelerator('C')); + GlobalCommands_insert("CameraAngleUp", ReferenceCaller(camwnd.getCamera()), Accelerator('A')); + GlobalCommands_insert("CameraAngleDown", ReferenceCaller(camwnd.getCamera()), Accelerator('Z')); +} + +void CamWnd_Move_Enable(CamWnd& camwnd) +{ + KeyEvent_connect("CameraForward"); + KeyEvent_connect("CameraBack"); + KeyEvent_connect("CameraLeft"); + KeyEvent_connect("CameraRight"); + KeyEvent_connect("CameraStrafeRight"); + KeyEvent_connect("CameraStrafeLeft"); + KeyEvent_connect("CameraUp"); + KeyEvent_connect("CameraDown"); + KeyEvent_connect("CameraAngleUp"); + KeyEvent_connect("CameraAngleDown"); +} + +void CamWnd_Move_Disable(CamWnd& camwnd) +{ + KeyEvent_disconnect("CameraForward"); + KeyEvent_disconnect("CameraBack"); + KeyEvent_disconnect("CameraLeft"); + KeyEvent_disconnect("CameraRight"); + KeyEvent_disconnect("CameraStrafeRight"); + KeyEvent_disconnect("CameraStrafeLeft"); + KeyEvent_disconnect("CameraUp"); + KeyEvent_disconnect("CameraDown"); + KeyEvent_disconnect("CameraAngleUp"); + KeyEvent_disconnect("CameraAngleDown"); +} + +void CamWnd_Move_Discrete_Enable(CamWnd& camwnd) +{ + command_connect_accelerator("CameraForward"); + command_connect_accelerator("CameraBack"); + command_connect_accelerator("CameraLeft"); + command_connect_accelerator("CameraRight"); + command_connect_accelerator("CameraStrafeRight"); + command_connect_accelerator("CameraStrafeLeft"); + command_connect_accelerator("CameraUp"); + command_connect_accelerator("CameraDown"); + command_connect_accelerator("CameraAngleUp"); + command_connect_accelerator("CameraAngleDown"); +} + +void CamWnd_Move_Discrete_Disable(CamWnd& camwnd) +{ + command_disconnect_accelerator("CameraForward"); + command_disconnect_accelerator("CameraBack"); + command_disconnect_accelerator("CameraLeft"); + command_disconnect_accelerator("CameraRight"); + command_disconnect_accelerator("CameraStrafeRight"); + command_disconnect_accelerator("CameraStrafeLeft"); + command_disconnect_accelerator("CameraUp"); + command_disconnect_accelerator("CameraDown"); + command_disconnect_accelerator("CameraAngleUp"); + command_disconnect_accelerator("CameraAngleDown"); +} + +void CamWnd_Move_Discrete_Import(CamWnd& camwnd, bool value) +{ + if(g_camwindow_globals_private.m_bCamDiscrete) + { + CamWnd_Move_Discrete_Disable(camwnd); + } + else + { + CamWnd_Move_Disable(camwnd); + } + + g_camwindow_globals_private.m_bCamDiscrete = value; + + if(g_camwindow_globals_private.m_bCamDiscrete) + { + CamWnd_Move_Discrete_Enable(camwnd); + } + else + { + CamWnd_Move_Enable(camwnd); + } +} + +void CamWnd_Move_Discrete_Import(bool value) +{ + if(g_camwnd != 0) + { + CamWnd_Move_Discrete_Import(*g_camwnd, value); + } + else + { + g_camwindow_globals_private.m_bCamDiscrete = value; + } +} + + + +void CamWnd_Add_Handlers_Move(CamWnd& camwnd) +{ + camwnd.m_selection_button_press_handler = g_signal_connect(G_OBJECT(camwnd.m_gl_widget), "button_press_event", G_CALLBACK(selection_button_press), camwnd.m_window_observer); + camwnd.m_selection_button_release_handler = g_signal_connect(G_OBJECT(camwnd.m_gl_widget), "button_release_event", G_CALLBACK(selection_button_release), camwnd.m_window_observer); + camwnd.m_selection_motion_handler = g_signal_connect(G_OBJECT(camwnd.m_gl_widget), "motion_notify_event", G_CALLBACK(DeferredMotion::gtk_motion), &camwnd.m_deferred_motion); + + camwnd.m_freelook_button_press_handler = g_signal_connect(G_OBJECT(camwnd.m_gl_widget), "button_press_event", G_CALLBACK(enable_freelook_button_press), &camwnd); + + if(g_camwindow_globals_private.m_bCamDiscrete) + { + CamWnd_Move_Discrete_Enable(camwnd); + } + else + { + CamWnd_Move_Enable(camwnd); + } +} + +void CamWnd_Remove_Handlers_Move(CamWnd& camwnd) +{ + g_signal_handler_disconnect(G_OBJECT(camwnd.m_gl_widget), camwnd.m_selection_button_press_handler); + g_signal_handler_disconnect(G_OBJECT(camwnd.m_gl_widget), camwnd.m_selection_button_release_handler); + g_signal_handler_disconnect(G_OBJECT(camwnd.m_gl_widget), camwnd.m_selection_motion_handler); + + g_signal_handler_disconnect(G_OBJECT(camwnd.m_gl_widget), camwnd.m_freelook_button_press_handler); + + if(g_camwindow_globals_private.m_bCamDiscrete) + { + CamWnd_Move_Discrete_Disable(camwnd); + } + else + { + CamWnd_Move_Disable(camwnd); + } +} + +void CamWnd_Add_Handlers_FreeMove(CamWnd& camwnd) +{ + camwnd.m_selection_button_press_handler = g_signal_connect(G_OBJECT(camwnd.m_gl_widget), "button_press_event", G_CALLBACK(selection_button_press_freemove), camwnd.m_window_observer); + camwnd.m_selection_button_release_handler = g_signal_connect(G_OBJECT(camwnd.m_gl_widget), "button_release_event", G_CALLBACK(selection_button_release_freemove), camwnd.m_window_observer); + camwnd.m_selection_motion_handler = g_signal_connect(G_OBJECT(camwnd.m_gl_widget), "motion_notify_event", G_CALLBACK(selection_motion_freemove), camwnd.m_window_observer); + + camwnd.m_freelook_button_press_handler = g_signal_connect(G_OBJECT(camwnd.m_gl_widget), "button_press_event", G_CALLBACK(disable_freelook_button_press), &camwnd); + + KeyEvent_connect("CameraFreeMoveForward"); + KeyEvent_connect("CameraFreeMoveBack"); + KeyEvent_connect("CameraFreeMoveLeft"); + KeyEvent_connect("CameraFreeMoveRight"); + KeyEvent_connect("CameraFreeMoveUp"); + KeyEvent_connect("CameraFreeMoveDown"); +} + +void CamWnd_Remove_Handlers_FreeMove(CamWnd& camwnd) +{ + KeyEvent_disconnect("CameraFreeMoveForward"); + KeyEvent_disconnect("CameraFreeMoveBack"); + KeyEvent_disconnect("CameraFreeMoveLeft"); + KeyEvent_disconnect("CameraFreeMoveRight"); + KeyEvent_disconnect("CameraFreeMoveUp"); + KeyEvent_disconnect("CameraFreeMoveDown"); + + g_signal_handler_disconnect(G_OBJECT(camwnd.m_gl_widget), camwnd.m_selection_button_press_handler); + g_signal_handler_disconnect(G_OBJECT(camwnd.m_gl_widget), camwnd.m_selection_button_release_handler); + g_signal_handler_disconnect(G_OBJECT(camwnd.m_gl_widget), camwnd.m_selection_motion_handler); + + g_signal_handler_disconnect(G_OBJECT(camwnd.m_gl_widget), camwnd.m_freelook_button_press_handler); +} + +CamWnd::CamWnd() : + m_view(true), + m_Camera(&m_view, CamWndQueueDraw(*this)), + m_cameraview(m_Camera, &m_view, ReferenceCaller(*this)), + m_gl_widget(glwidget_new(TRUE)), + m_window_observer(NewWindowObserver()), + m_XORRectangle(m_gl_widget), + m_deferredDraw(WidgetQueueDrawCaller(*m_gl_widget)), + m_deferred_motion(selection_motion, m_window_observer), + m_selection_button_press_handler(0), + m_selection_button_release_handler(0), + m_selection_motion_handler(0), + m_freelook_button_press_handler(0), + m_drawing(false) +{ + m_bFreeMove = false; + + GlobalWindowObservers_add(m_window_observer); + GlobalWindowObservers_connectWidget(m_gl_widget); + + m_window_observer->setRectangleDrawCallback(ReferenceCaller1(*this)); + m_window_observer->setView(m_view); + + gtk_widget_ref(m_gl_widget); + + gtk_widget_set_events(m_gl_widget, GDK_DESTROY | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK); + GTK_WIDGET_SET_FLAGS (m_gl_widget, GTK_CAN_FOCUS); + + m_sizeHandler = g_signal_connect(G_OBJECT(m_gl_widget), "size_allocate", G_CALLBACK(camera_size_allocate), this); + m_exposeHandler = g_signal_connect(G_OBJECT(m_gl_widget), "expose_event", G_CALLBACK(camera_expose), this); + + Map_addValidCallback(g_map, DeferredDrawOnMapValidChangedCaller(m_deferredDraw)); + + CamWnd_registerCommands(*this); + + CamWnd_Add_Handlers_Move(*this); + + g_signal_connect(G_OBJECT(m_gl_widget), "scroll_event", G_CALLBACK(wheelmove_scroll), this); + + AddSceneChangeCallback(ReferenceCaller(*this)); + + PressedButtons_connect(g_pressedButtons, m_gl_widget); +} + +CamWnd::~CamWnd() +{ + if(m_bFreeMove) + { + DisableFreeMove(); + } + + CamWnd_Remove_Handlers_Move(*this); + + g_signal_handler_disconnect(G_OBJECT(m_gl_widget), m_sizeHandler); + g_signal_handler_disconnect(G_OBJECT(m_gl_widget), m_exposeHandler); + + gtk_widget_unref(m_gl_widget); + + m_window_observer->release(); +} + +class FloorHeightWalker : public scene::Graph::Walker +{ + float m_current; + float& m_bestUp; + float& m_bestDown; +public: + FloorHeightWalker(float current, float& bestUp, float& bestDown) : + m_current(current), m_bestUp(bestUp), m_bestDown(bestDown) + { + bestUp = g_MaxWorldCoord; + bestDown = -g_MaxWorldCoord; + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible() + && Node_isBrush(path.top())) // this node is a floor + { + const AABB& aabb = instance.worldAABB(); + float floorHeight = aabb.origin.z() + aabb.extents.z(); + if(floorHeight > m_current && floorHeight < m_bestUp) + { + m_bestUp = floorHeight; + } + if(floorHeight < m_current && floorHeight > m_bestDown) + { + m_bestDown = floorHeight; + } + } + return true; + } +}; + +void CamWnd::Cam_ChangeFloor(bool up) +{ + float current = m_Camera.origin[2] - 48; + float bestUp; + float bestDown; + GlobalSceneGraph().traverse(FloorHeightWalker(current, bestUp, bestDown)); + + if(up && bestUp != g_MaxWorldCoord) + { + current = bestUp; + } + if(!up && bestDown != -g_MaxWorldCoord) + { + current = bestDown; + } + + m_Camera.origin[2] = current + 48; + Camera_updateModelview(getCamera()); + CamWnd_Update(*this); + CameraMovedNotify(); +} + + +#if 0 + +// button_press + Sys_GetCursorPos(&m_PositionDragCursorX, &m_PositionDragCursorY); + +// motion + if ( (m_bFreeMove && (buttons == (RAD_CONTROL|RAD_SHIFT))) + || (!m_bFreeMove && (buttons == (RAD_RBUTTON|RAD_CONTROL))) ) + { + Cam_PositionDrag(); + CamWnd_Update(camwnd); + CameraMovedNotify(); + return; + } + +void CamWnd::Cam_PositionDrag() +{ + int x, y; + + Sys_GetCursorPos(GTK_WINDOW(m_gl_widget), &x, &y); + if (x != m_PositionDragCursorX || y != m_PositionDragCursorY) + { + x -= m_PositionDragCursorX; + vector3_add(m_Camera.origin, vector3_scaled(m_Camera.vright, x)); + y -= m_PositionDragCursorY; + m_Camera.origin[2] -= y; + Camera_updateModelview(); + CamWnd_Update(camwnd); + CameraMovedNotify(); + + Sys_SetCursorPos(GTK_WINDOW(m_parent), m_PositionDragCursorX, m_PositionDragCursorY); + } +} +#endif + + +// NOTE TTimo if there's an OS-level focus out of the application +// then we can release the camera cursor grab +static gboolean camwindow_freemove_focusout(GtkWidget* widget, GdkEventFocus* event, gpointer data) +{ + reinterpret_cast(data)->DisableFreeMove(); + return FALSE; +} + +void CamWnd::EnableFreeMove() +{ + //globalOutputStream() << "EnableFreeMove\n"; + + ASSERT_MESSAGE(!m_bFreeMove, "EnableFreeMove: free-move was already enabled"); + m_bFreeMove = true; + Camera_clearMovementFlags(getCamera(), MOVE_ALL); + + CamWnd_Remove_Handlers_Move(*this); + CamWnd_Add_Handlers_FreeMove(*this); + + gtk_window_set_focus(m_parent, m_gl_widget); + m_freemove_handle_focusout = g_signal_connect(G_OBJECT(m_gl_widget), "focus_out_event", G_CALLBACK(camwindow_freemove_focusout), this); + m_freezePointer.freeze_pointer(m_parent, Camera_motionDelta, &m_Camera); + + CamWnd_Update(*this); +} + +void CamWnd::DisableFreeMove() +{ + //globalOutputStream() << "DisableFreeMove\n"; + + ASSERT_MESSAGE(m_bFreeMove, "DisableFreeMove: free-move was not enabled"); + m_bFreeMove = false; + Camera_clearMovementFlags(getCamera(), MOVE_ALL); + + CamWnd_Remove_Handlers_FreeMove(*this); + CamWnd_Add_Handlers_Move(*this); + + m_freezePointer.unfreeze_pointer(m_parent); + g_signal_handler_disconnect(G_OBJECT(m_gl_widget), m_freemove_handle_focusout); + + CamWnd_Update(*this); +} + + +#include "renderer.h" + +class CamRenderer: public Renderer +{ + struct state_type + { + state_type() : m_highlight(0), m_state(0), m_lights(0) + { + } + unsigned int m_highlight; + Shader* m_state; + const LightList* m_lights; + }; + + std::vector m_state_stack; + RenderStateFlags m_globalstate; + Shader* m_state_select0; + Shader* m_state_select1; + const Vector3& m_viewer; + +public: + CamRenderer(RenderStateFlags globalstate, Shader* select0, Shader* select1, const Vector3& viewer) : + m_globalstate(globalstate), + m_state_select0(select0), + m_state_select1(select1), + m_viewer(viewer) + { + ASSERT_NOTNULL(select0); + ASSERT_NOTNULL(select1); + m_state_stack.push_back(state_type()); + } + + void SetState(Shader* state, EStyle style) + { + ASSERT_NOTNULL(state); + if(style == eFullMaterials) + { + m_state_stack.back().m_state = state; + } + } + const EStyle getStyle() const + { + return eFullMaterials; + } + void PushState() + { + m_state_stack.push_back(m_state_stack.back()); + } + void PopState() + { + ASSERT_MESSAGE(!m_state_stack.empty(), "popping empty stack"); + m_state_stack.pop_back(); + } + void Highlight(EHighlightMode mode, bool bEnable = true) + { + (bEnable) + ? m_state_stack.back().m_highlight |= mode + : m_state_stack.back().m_highlight &= ~mode; + } + void setLights(const LightList& lights) + { + m_state_stack.back().m_lights = &lights; + } + void addRenderable(const OpenGLRenderable& renderable, const Matrix4& world) + { + if(m_state_stack.back().m_highlight & ePrimitive) + { + m_state_select0->addRenderable(renderable, world, m_state_stack.back().m_lights); + } + if(m_state_stack.back().m_highlight & eFace) + { + m_state_select1->addRenderable(renderable, world, m_state_stack.back().m_lights); + } + + m_state_stack.back().m_state->addRenderable(renderable, world, m_state_stack.back().m_lights); + } + + void render(const Matrix4& modelview, const Matrix4& projection) + { + GlobalShaderCache().render(m_globalstate, modelview, projection, m_viewer); + } +}; + +/* +============== +Cam_Draw +============== +*/ + +void ShowStatsToggle() +{ + g_camwindow_globals_private.m_showStats ^= 1; +} +typedef FreeCaller ShowStatsToggleCaller; + +void ShowStatsExport(const BoolImportCallback& importer) +{ + importer(g_camwindow_globals_private.m_showStats); +} +typedef FreeCaller1 ShowStatsExportCaller; + +ShowStatsExportCaller g_show_stats_caller; +BoolExportCallback g_show_stats_callback(g_show_stats_caller); +ToggleItem g_show_stats(g_show_stats_callback); + +void CamWnd::Cam_Draw() +{ + glViewport(0, 0, m_Camera.width, m_Camera.height); +#if 0 + GLint viewprt[4]; + glGetIntegerv (GL_VIEWPORT, viewprt); +#endif + + // enable depth buffer writes + glDepthMask(GL_TRUE); + + Vector3 clearColour(0, 0, 0); + if(m_Camera.draw_mode != cd_lighting) + { + clearColour = g_camwindow_globals.color_cameraback; + } + + glClearColor(clearColour[0], clearColour[1], clearColour[2], 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + extern void Renderer_ResetStats(); + Renderer_ResetStats(); + extern void Cull_ResetStats(); + Cull_ResetStats(); + + glMatrixMode(GL_PROJECTION); + glLoadMatrixf(reinterpret_cast(&m_Camera.projection)); + + glMatrixMode(GL_MODELVIEW); + glLoadMatrixf(reinterpret_cast(&m_Camera.modelview)); + + + // one directional light source directly behind the viewer + { + GLfloat inverse_cam_dir[4], ambient[4], diffuse[4];//, material[4]; + + ambient[0] = ambient[1] = ambient[2] = 0.4f; + ambient[3] = 1.0f; + diffuse[0] = diffuse[1] = diffuse[2] = 0.4f; + diffuse[3] = 1.0f; + //material[0] = material[1] = material[2] = 0.8f; + //material[3] = 1.0f; + + inverse_cam_dir[0] = m_Camera.vpn[0]; + inverse_cam_dir[1] = m_Camera.vpn[1]; + inverse_cam_dir[2] = m_Camera.vpn[2]; + inverse_cam_dir[3] = 0; + + glLightfv(GL_LIGHT0, GL_POSITION, inverse_cam_dir); + + glLightfv(GL_LIGHT0, GL_AMBIENT, ambient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse); + + glEnable(GL_LIGHT0); + } + + + unsigned int globalstate = RENDER_DEPTHTEST|RENDER_COLOURWRITE|RENDER_DEPTHWRITE|RENDER_ALPHATEST|RENDER_BLEND|RENDER_CULLFACE|RENDER_COLOURARRAY|RENDER_OFFSETLINE|RENDER_POLYGONSMOOTH|RENDER_LINESMOOTH|RENDER_FOG|RENDER_COLOURCHANGE; + switch (m_Camera.draw_mode) + { + case cd_wire: + break; + case cd_solid: + globalstate |= RENDER_FILL + | RENDER_LIGHTING + | RENDER_SMOOTH + | RENDER_SCALED; + break; + case cd_texture: + globalstate |= RENDER_FILL + | RENDER_LIGHTING + | RENDER_TEXTURE + | RENDER_SMOOTH + | RENDER_SCALED; + break; + case cd_lighting: + globalstate |= RENDER_FILL + | RENDER_LIGHTING + | RENDER_TEXTURE + | RENDER_SMOOTH + | RENDER_SCALED + | RENDER_BUMP + | RENDER_PROGRAM + | RENDER_SCREEN; + break; + default: + globalstate = 0; + break; + } + + if(!g_xywindow_globals.m_bNoStipple) + { + globalstate |= RENDER_LINESTIPPLE|RENDER_POLYGONSTIPPLE; + } + + { + CamRenderer renderer(globalstate, m_state_select2, m_state_select1, m_view.getViewer()); + + Scene_Render(renderer, m_view); + + renderer.render(m_Camera.modelview, m_Camera.projection); + } + + // prepare for 2d stuff + glColor4f(1, 1, 1, 1); + glDisable(GL_BLEND); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, (float)m_Camera.width, 0, (float)m_Camera.height, -100, 100); + glScalef(1, -1, 1); + glTranslatef(0, -(float)m_Camera.height, 0); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + if(GlobalOpenGL().GL_1_3()) + { + glClientActiveTexture(GL_TEXTURE0); + glActiveTexture(GL_TEXTURE0); + } + + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_LIGHTING); + glDisable(GL_COLOR_MATERIAL); + glDisable(GL_DEPTH_TEST); + glColor3f( 1.f, 1.f, 1.f ); + glLineWidth(1); + + // draw the crosshair + if (m_bFreeMove) + { + glBegin( GL_LINES ); + glVertex2f( (float)m_Camera.width / 2.f, (float)m_Camera.height / 2.f + 6 ); + glVertex2f( (float)m_Camera.width / 2.f, (float)m_Camera.height / 2.f + 2 ); + glVertex2f( (float)m_Camera.width / 2.f, (float)m_Camera.height / 2.f - 6 ); + glVertex2f( (float)m_Camera.width / 2.f, (float)m_Camera.height / 2.f - 2 ); + glVertex2f( (float)m_Camera.width / 2.f + 6, (float)m_Camera.height / 2.f ); + glVertex2f( (float)m_Camera.width / 2.f + 2, (float)m_Camera.height / 2.f ); + glVertex2f( (float)m_Camera.width / 2.f - 6, (float)m_Camera.height / 2.f ); + glVertex2f( (float)m_Camera.width / 2.f - 2, (float)m_Camera.height / 2.f ); + glEnd(); + } + + if(g_camwindow_globals_private.m_showStats) + { + glRasterPos3f(1.0f, static_cast(m_Camera.height) - 1.0f, 0.0f); + extern const char* Renderer_GetStats(); + GlobalOpenGL().drawString(Renderer_GetStats()); + + glRasterPos3f(1.0f, static_cast(m_Camera.height) - 11.0f, 0.0f); + extern const char* Cull_GetStats(); + GlobalOpenGL().drawString(Cull_GetStats()); + } + + // bind back to the default texture so that we don't have problems + // elsewhere using/modifying texture maps between contexts + glBindTexture( GL_TEXTURE_2D, 0 ); +} + +void CamWnd::draw() +{ + m_drawing = true; + + //globalOutputStream() << "draw...\n"; + if (glwidget_make_current(m_gl_widget) != FALSE) + { + if(Map_Valid(g_map) && ScreenUpdates_Enabled()) + { + GlobalOpenGL_debugAssertNoErrors(); + Cam_Draw(); + GlobalOpenGL_debugAssertNoErrors(); + //qglFinish(); + + m_XORRectangle.set(rectangle_t()); + } + + glwidget_swap_buffers(m_gl_widget); + } + + m_drawing = false; +} + +void CamWnd::BenchMark() +{ + double dStart = Sys_DoubleTime(); + for (int i=0 ; i < 100 ; i++) + { + Vector3 angles; + angles[CAMERA_ROLL] = 0; + angles[CAMERA_PITCH] = 0; + angles[CAMERA_YAW] = static_cast(i * (360.0 / 100.0)); + Camera_setAngles(*this, angles); + } + double dEnd = Sys_DoubleTime(); + globalOutputStream() << FloatFormat(dEnd - dStart, 5, 2) << " seconds\n"; +} + + +void fill_view_camera_menu(GtkMenu* menu) +{ + create_check_menu_item_with_mnemonic(menu, "Camera View", "ToggleCamera"); +} + +void GlobalCamera_ResetAngles() +{ + CamWnd& camwnd = *g_camwnd; + Vector3 angles; + angles[CAMERA_ROLL] = angles[CAMERA_PITCH] = 0; + angles[CAMERA_YAW] = static_cast(22.5 * floor((Camera_getAngles(camwnd)[CAMERA_YAW]+11)/22.5)); + Camera_setAngles(camwnd, angles); +} + +void Camera_ChangeFloorUp() +{ + CamWnd& camwnd = *g_camwnd; + camwnd.Cam_ChangeFloor (true); +} + +void Camera_ChangeFloorDown() +{ + CamWnd& camwnd = *g_camwnd; + camwnd.Cam_ChangeFloor (false); +} + +void Camera_CubeIn() +{ + CamWnd& camwnd = *g_camwnd; + g_camwindow_globals.m_nCubicScale--; + if (g_camwindow_globals.m_nCubicScale < 1) + g_camwindow_globals.m_nCubicScale = 1; + Camera_updateProjection(camwnd.getCamera()); + CamWnd_Update(camwnd); + g_pParentWnd->SetGridStatus(); +} + +void Camera_CubeOut() +{ + CamWnd& camwnd = *g_camwnd; + g_camwindow_globals.m_nCubicScale++; + if (g_camwindow_globals.m_nCubicScale > 23) + g_camwindow_globals.m_nCubicScale = 23; + Camera_updateProjection(camwnd.getCamera()); + CamWnd_Update(camwnd); + g_pParentWnd->SetGridStatus(); +} + +bool Camera_GetFarClip() +{ + return g_camwindow_globals_private.m_bCubicClipping; +} + +BoolExportCaller g_getfarclip_caller(g_camwindow_globals_private.m_bCubicClipping); +ToggleItem g_getfarclip_item(g_getfarclip_caller); + +void Camera_SetFarClip(bool value) +{ + CamWnd& camwnd = *g_camwnd; + g_camwindow_globals_private.m_bCubicClipping = value; + g_getfarclip_item.update(); + Camera_updateProjection(camwnd.getCamera()); + CamWnd_Update(camwnd); +} + +void Camera_ToggleFarClip() +{ + Camera_SetFarClip(!Camera_GetFarClip()); +} + + +void CamWnd_constructToolbar(GtkToolbar* toolbar) +{ + toolbar_append_toggle_button(toolbar, "Cubic clip the camera view (\\)", "view_cubicclipping.bmp", "ToggleCubicClip"); +} + +void CamWnd_registerShortcuts() +{ + toggle_add_accelerator("ToggleCubicClip"); + + if(g_pGameDescription->mGameType == "doom3") + { + command_connect_accelerator("TogglePreview"); + } + + command_connect_accelerator("CameraSpeedInc"); + command_connect_accelerator("CameraSpeedDec"); +} + + +void GlobalCamera_Benchmark() +{ + CamWnd& camwnd = *g_camwnd; + camwnd.BenchMark(); +} + +void GlobalCamera_Update() +{ + CamWnd& camwnd = *g_camwnd; + CamWnd_Update(camwnd); +} + +camera_draw_mode CamWnd_GetMode() +{ + return camera_t::draw_mode; +} +void CamWnd_SetMode(camera_draw_mode mode) +{ + ShaderCache_setBumpEnabled(mode == cd_lighting); + camera_t::draw_mode = mode; + if(g_camwnd != 0) + { + CamWnd_Update(*g_camwnd); + } +} + +void CamWnd_TogglePreview(void) +{ + // gametype must be doom3 for this function to work + // if the gametype is not doom3 something is wrong with the + // global command list or somebody else calls this function. + ASSERT_MESSAGE(g_pGameDescription->mGameType == "doom3", "CamWnd_TogglePreview called although mGameType is not doom3 compatible"); + + // switch between textured and lighting mode + CamWnd_SetMode((CamWnd_GetMode() == cd_lighting) ? cd_texture : cd_lighting); +} + + +CameraModel* g_camera_model = 0; + +void CamWnd_LookThroughCamera(CamWnd& camwnd) +{ + if(g_camera_model != 0) + { + CamWnd_Add_Handlers_Move(camwnd); + g_camera_model->setCameraView(0, Callback()); + g_camera_model = 0; + Camera_updateModelview(camwnd.getCamera()); + Camera_updateProjection(camwnd.getCamera()); + CamWnd_Update(camwnd); + } +} + +inline CameraModel* Instance_getCameraModel(scene::Instance& instance) +{ + return InstanceTypeCast::cast(instance); +} + +void CamWnd_LookThroughSelected(CamWnd& camwnd) +{ + if(g_camera_model != 0) + { + CamWnd_LookThroughCamera(camwnd); + } + + if(GlobalSelectionSystem().countSelected() != 0) + { + scene::Instance& instance = GlobalSelectionSystem().ultimateSelected(); + CameraModel* cameraModel = Instance_getCameraModel(instance); + if(cameraModel != 0) + { + CamWnd_Remove_Handlers_Move(camwnd); + g_camera_model = cameraModel; + g_camera_model->setCameraView(&camwnd.getCameraView(), ReferenceCaller(camwnd)); + } + } +} + +void GlobalCamera_LookThroughSelected() +{ + CamWnd_LookThroughSelected(*g_camwnd); +} + +void GlobalCamera_LookThroughCamera() +{ + CamWnd_LookThroughCamera(*g_camwnd); +} + + +void RenderModeImport(int value) +{ + switch(value) + { + case 0: + CamWnd_SetMode(cd_wire); + break; + case 1: + CamWnd_SetMode(cd_solid); + break; + case 2: + CamWnd_SetMode(cd_texture); + break; + case 3: + CamWnd_SetMode(cd_lighting); + break; + default: + CamWnd_SetMode(cd_texture); + } +} +typedef FreeCaller1 RenderModeImportCaller; + +void RenderModeExport(const IntImportCallback& importer) +{ + switch(CamWnd_GetMode()) + { + case cd_wire: + importer(0); + break; + case cd_solid: + importer(1); + break; + case cd_texture: + importer(2); + break; + case cd_lighting: + importer(3); + break; + } +} +typedef FreeCaller1 RenderModeExportCaller; + +void Camera_constructPreferences(PreferencesPage& page) +{ + page.appendSlider("Movement Speed", g_camwindow_globals_private.m_nMoveSpeed, TRUE, 0, 0, 100, MIN_CAM_SPEED, MAX_CAM_SPEED, 1, 10, 10); + page.appendCheckBox("", "Link strafe speed to movement speed", g_camwindow_globals_private.m_bCamLinkSpeed); + page.appendSlider("Rotation Speed", g_camwindow_globals_private.m_nAngleSpeed, TRUE, 0, 0, 3, 1, 180, 1, 10, 10); + page.appendCheckBox("", "Invert mouse vertical axis", g_camwindow_globals_private.m_bCamInverseMouse); + page.appendCheckBox( + "", "Discrete movement", + FreeCaller1(), + BoolExportCaller(g_camwindow_globals_private.m_bCamDiscrete) + ); + page.appendCheckBox( + "", "Enable far-clip plane", + FreeCaller1(), + BoolExportCaller(g_camwindow_globals_private.m_bCubicClipping) + ); + + if(g_pGameDescription->mGameType == "doom3") + { + const char* render_mode[] = { "Wireframe", "Flatshade", "Textured", "Lighting" }; + + page.appendCombo( + "Render Mode", + STRING_ARRAY_RANGE(render_mode), + IntImportCallback(RenderModeImportCaller()), + IntExportCallback(RenderModeExportCaller()) + ); + } + else + { + const char* render_mode[] = { "Wireframe", "Flatshade", "Textured" }; + + page.appendCombo( + "Render Mode", + STRING_ARRAY_RANGE(render_mode), + IntImportCallback(RenderModeImportCaller()), + IntExportCallback(RenderModeExportCaller()) + ); + } +} +void Camera_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Camera", "Camera View Preferences")); + Camera_constructPreferences(page); +} +void Camera_registerPreferencesPage() +{ + PreferencesDialog_addSettingsPage(FreeCaller1()); +} + +#include "preferencesystem.h" +#include "stringio.h" +#include "dialog.h" + +typedef FreeCaller1 CamWndMoveDiscreteImportCaller; + +void CameraSpeed_increase() +{ + if(g_camwindow_globals_private.m_nMoveSpeed <= (MAX_CAM_SPEED - CAM_SPEED_STEP - 10)) + { + g_camwindow_globals_private.m_nMoveSpeed += CAM_SPEED_STEP; + } else { + g_camwindow_globals_private.m_nMoveSpeed = MAX_CAM_SPEED - 10; + } +} + +void CameraSpeed_decrease() +{ + if(g_camwindow_globals_private.m_nMoveSpeed >= (MIN_CAM_SPEED + CAM_SPEED_STEP)) + { + g_camwindow_globals_private.m_nMoveSpeed -= CAM_SPEED_STEP; + } else { + g_camwindow_globals_private.m_nMoveSpeed = MIN_CAM_SPEED; + } +} + +/// \brief Initialisation for things that have the same lifespan as this module. +void CamWnd_Construct() +{ + GlobalCommands_insert("CenterView", FreeCaller(), Accelerator(GDK_End)); + + GlobalToggles_insert("ToggleCubicClip", FreeCaller(), ToggleItem::AddCallbackCaller(g_getfarclip_item), Accelerator('\\', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("CubicClipZoomIn", FreeCaller(), Accelerator('[', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("CubicClipZoomOut", FreeCaller(), Accelerator(']', (GdkModifierType)GDK_CONTROL_MASK)); + + GlobalCommands_insert("UpFloor", FreeCaller(), Accelerator(GDK_Prior)); + GlobalCommands_insert("DownFloor", FreeCaller(), Accelerator(GDK_Next)); + + GlobalToggles_insert("ToggleCamera", ToggleShown::ToggleCaller(g_camera_shown), ToggleItem::AddCallbackCaller(g_camera_shown.m_item), Accelerator('C', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + GlobalCommands_insert("LookThroughSelected", FreeCaller()); + GlobalCommands_insert("LookThroughCamera", FreeCaller()); + + if(g_pGameDescription->mGameType == "doom3") + { + GlobalCommands_insert("TogglePreview", FreeCaller(), Accelerator(GDK_F3)); + } + + GlobalCommands_insert("CameraSpeedInc", FreeCaller(), Accelerator(GDK_KP_Add, (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("CameraSpeedDec", FreeCaller(), Accelerator(GDK_KP_Subtract, (GdkModifierType)GDK_SHIFT_MASK)); + + GlobalShortcuts_insert("CameraForward", Accelerator(GDK_Up)); + GlobalShortcuts_insert("CameraBack", Accelerator(GDK_Down)); + GlobalShortcuts_insert("CameraLeft", Accelerator(GDK_Left)); + GlobalShortcuts_insert("CameraRight", Accelerator(GDK_Right)); + GlobalShortcuts_insert("CameraStrafeRight", Accelerator(GDK_period)); + GlobalShortcuts_insert("CameraStrafeLeft", Accelerator(GDK_comma)); + + GlobalShortcuts_insert("CameraUp", Accelerator('D')); + GlobalShortcuts_insert("CameraDown", Accelerator('C')); + GlobalShortcuts_insert("CameraAngleUp", Accelerator('A')); + GlobalShortcuts_insert("CameraAngleDown", Accelerator('Z')); + + GlobalShortcuts_insert("CameraFreeMoveForward", Accelerator(GDK_Up)); + GlobalShortcuts_insert("CameraFreeMoveBack", Accelerator(GDK_Down)); + GlobalShortcuts_insert("CameraFreeMoveLeft", Accelerator(GDK_Left)); + GlobalShortcuts_insert("CameraFreeMoveRight", Accelerator(GDK_Right)); + + GlobalToggles_insert("ShowStats", ShowStatsToggleCaller(), ToggleItem::AddCallbackCaller(g_show_stats)); + + GlobalPreferenceSystem().registerPreference("ShowStats", BoolImportStringCaller(g_camwindow_globals_private.m_showStats), BoolExportStringCaller(g_camwindow_globals_private.m_showStats)); + GlobalPreferenceSystem().registerPreference("MoveSpeed", IntImportStringCaller(g_camwindow_globals_private.m_nMoveSpeed), IntExportStringCaller(g_camwindow_globals_private.m_nMoveSpeed)); + GlobalPreferenceSystem().registerPreference("CamLinkSpeed", BoolImportStringCaller(g_camwindow_globals_private.m_bCamLinkSpeed), BoolExportStringCaller(g_camwindow_globals_private.m_bCamLinkSpeed)); + GlobalPreferenceSystem().registerPreference("AngleSpeed", IntImportStringCaller(g_camwindow_globals_private.m_nAngleSpeed), IntExportStringCaller(g_camwindow_globals_private.m_nAngleSpeed)); + GlobalPreferenceSystem().registerPreference("CamInverseMouse", BoolImportStringCaller(g_camwindow_globals_private.m_bCamInverseMouse), BoolExportStringCaller(g_camwindow_globals_private.m_bCamInverseMouse)); + GlobalPreferenceSystem().registerPreference("CamDiscrete", makeBoolStringImportCallback(CamWndMoveDiscreteImportCaller()), BoolExportStringCaller(g_camwindow_globals_private.m_bCamDiscrete)); + GlobalPreferenceSystem().registerPreference("CubicClipping", BoolImportStringCaller(g_camwindow_globals_private.m_bCubicClipping), BoolExportStringCaller(g_camwindow_globals_private.m_bCubicClipping)); + GlobalPreferenceSystem().registerPreference("CubicScale", IntImportStringCaller(g_camwindow_globals.m_nCubicScale), IntExportStringCaller(g_camwindow_globals.m_nCubicScale)); + GlobalPreferenceSystem().registerPreference("SI_Colors4", Vector3ImportStringCaller(g_camwindow_globals.color_cameraback), Vector3ExportStringCaller(g_camwindow_globals.color_cameraback)); + GlobalPreferenceSystem().registerPreference("SI_Colors12", Vector3ImportStringCaller(g_camwindow_globals.color_selbrushes3d), Vector3ExportStringCaller(g_camwindow_globals.color_selbrushes3d)); + GlobalPreferenceSystem().registerPreference("CameraRenderMode", makeIntStringImportCallback(RenderModeImportCaller()), makeIntStringExportCallback(RenderModeExportCaller())); + + CamWnd_constructStatic(); + + Camera_registerPreferencesPage(); +} +void CamWnd_Destroy() +{ + CamWnd_destroyStatic(); +} diff --git a/radiant/camwindow.h b/radiant/camwindow.h new file mode 100644 index 00000000..2c8c59f7 --- /dev/null +++ b/radiant/camwindow.h @@ -0,0 +1,87 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_CAMWINDOW_H) +#define INCLUDED_CAMWINDOW_H + +#include "math/vector.h" +#include "signal/signalfwd.h" + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkWindow GtkWindow; + +class CamWnd; +CamWnd* NewCamWnd(); +void DeleteCamWnd(CamWnd* camwnd); + +void AddCameraMovedCallback(const SignalHandler& handler); + +void CamWnd_Update(CamWnd& camwnd); + +GtkWidget* CamWnd_getWidget(CamWnd& camwnd); +void CamWnd_setParent(CamWnd& camwnd, GtkWindow* parent); + +void GlobalCamera_setCamWnd(CamWnd& camwnd); + +typedef struct _GtkMenu GtkMenu; +void fill_view_camera_menu(GtkMenu* menu); +typedef struct _GtkToolbar GtkToolbar; +void CamWnd_constructToolbar(GtkToolbar* toolbar); +void CamWnd_registerShortcuts(); + +void GlobalCamera_Benchmark(); + +const Vector3& Camera_getOrigin(CamWnd& camwnd); +void Camera_setOrigin(CamWnd& camwnd, const Vector3& origin); + +enum +{ + CAMERA_PITCH = 0, // up / down + CAMERA_YAW = 1, // left / right + CAMERA_ROLL = 2, // fall over +}; + +const Vector3& Camera_getAngles(CamWnd& camwnd); +void Camera_setAngles(CamWnd& camwnd, const Vector3& angles); + + +struct camwindow_globals_t +{ + Vector3 color_cameraback; + Vector3 color_selbrushes3d; + + int m_nCubicScale; + + camwindow_globals_t() : + color_cameraback(0.25f, 0.25f, 0.25f), + color_selbrushes3d(1.0f, 0.f, 0.f), + m_nCubicScale(13) + { + } + +}; + +extern camwindow_globals_t g_camwindow_globals; + +void CamWnd_Construct(); +void CamWnd_Destroy(); + +#endif diff --git a/radiant/clippertool.cpp b/radiant/clippertool.cpp new file mode 100644 index 00000000..a49b7246 --- /dev/null +++ b/radiant/clippertool.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "clippertool.h" + diff --git a/radiant/clippertool.h b/radiant/clippertool.h new file mode 100644 index 00000000..6101f1a6 --- /dev/null +++ b/radiant/clippertool.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_CLIPPERTOOL_H) +#define INCLUDED_CLIPPERTOOL_H + +#endif diff --git a/radiant/commands.cpp b/radiant/commands.cpp new file mode 100644 index 00000000..c1e1b2a3 --- /dev/null +++ b/radiant/commands.cpp @@ -0,0 +1,424 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "commands.h" + +#include "debugging/debugging.h" +#include "warnings.h" + +#include +#include "string/string.h" +#include "versionlib.h" +#include "gtkutil/accelerator.h" + +typedef std::pair ShortcutValue; // accelerator, isRegistered +typedef std::map Shortcuts; + +void Shortcuts_foreach(Shortcuts& shortcuts, CommandVisitor& visitor) +{ + for(Shortcuts::iterator i = shortcuts.begin(); i != shortcuts.end(); ++i) + { + visitor.visit((*i).first.c_str(), (*i).second.first); + } +} + +Shortcuts g_shortcuts; + +const Accelerator& GlobalShortcuts_insert(const char* name, const Accelerator& accelerator) +{ + return (*g_shortcuts.insert(Shortcuts::value_type(name, ShortcutValue(accelerator, false))).first).second.first; +} + +void GlobalShortcuts_foreach(CommandVisitor& visitor) +{ + Shortcuts_foreach(g_shortcuts, visitor); +} + +void GlobalShortcuts_register(const char* name) +{ + Shortcuts::iterator i = g_shortcuts.find(name); + if(i != g_shortcuts.end()) + { + (*i).second.second = true; + } +} + +void GlobalShortcuts_reportUnregistered() +{ + for(Shortcuts::iterator i = g_shortcuts.begin(); i != g_shortcuts.end(); ++i) + { + if((*i).second.first.key != 0 && !(*i).second.second) + { + globalOutputStream() << "shortcut not registered: " << (*i).first.c_str() << "\n"; + } + } +} + +typedef std::map Commands; + +Commands g_commands; + +void GlobalCommands_insert(const char* name, const Callback& callback, const Accelerator& accelerator) +{ + bool added = g_commands.insert(Commands::value_type(name, Command(callback, GlobalShortcuts_insert(name, accelerator)))).second; + ASSERT_MESSAGE(added, "command already registered: " << makeQuoted(name)); +} + +const Command& GlobalCommands_find(const char* command) +{ + Commands::iterator i = g_commands.find(command); + ASSERT_MESSAGE(i != g_commands.end(), "failed to lookup command " << makeQuoted(command)); + return (*i).second; +} + +typedef std::map Toggles; + + +Toggles g_toggles; + +void GlobalToggles_insert(const char* name, const Callback& callback, const BoolExportCallback& exportCallback, const Accelerator& accelerator) +{ + bool added = g_toggles.insert(Toggles::value_type(name, Toggle(callback, GlobalShortcuts_insert(name, accelerator), exportCallback))).second; + ASSERT_MESSAGE(added, "toggle already registered: " << makeQuoted(name)); +} +const Toggle& GlobalToggles_find(const char* name) +{ + Toggles::iterator i = g_toggles.find(name); + ASSERT_MESSAGE(i != g_toggles.end(), "failed to lookup toggle " << makeQuoted(name)); + return (*i).second; +} + +typedef std::map KeyEvents; + + +KeyEvents g_keyEvents; + +void GlobalKeyEvents_insert(const char* name, const Accelerator& accelerator, const Callback& keyDown, const Callback& keyUp) +{ + bool added = g_keyEvents.insert(KeyEvents::value_type(name, KeyEvent(GlobalShortcuts_insert(name, accelerator), keyDown, keyUp))).second; + ASSERT_MESSAGE(added, "command already registered: " << makeQuoted(name)); +} +const KeyEvent& GlobalKeyEvents_find(const char* name) +{ + KeyEvents::iterator i = g_keyEvents.find(name); + ASSERT_MESSAGE(i != g_keyEvents.end(), "failed to lookup keyEvent " << makeQuoted(name)); + return (*i).second; +} + + + + +#include + +#include +#include +#include +#include +#include + +#include "gtkutil/dialog.h" +#include "mainframe.h" + +#include "stream/textfilestream.h" +#include "stream/stringstream.h" + + +struct command_list_dialog_t : public ModalDialog +{ + command_list_dialog_t() + : m_close_button(*this, eIDCANCEL) + { + } + ModalDialogButton m_close_button; +}; + +void DoCommandListDlg() +{ + command_list_dialog_t dialog; + + GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), "Mapped Commands", dialog, -1, 400); + + GtkAccelGroup* accel = gtk_accel_group_new(); + gtk_window_add_accel_group(window, accel); + + GtkHBox* hbox = create_dialog_hbox(4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox)); + + { + GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(scr), TRUE, TRUE, 0); + + { + GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); + + GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + + { + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("Command", renderer, "text", 0, 0); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + } + + { + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("Key", renderer, "text", 1, 0); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + } + + gtk_widget_show(view); + gtk_container_add(GTK_CONTAINER (scr), view); + + { + // Initialize dialog + StringOutputStream path(256); + path << SettingsPath_get() << "commandlist.txt"; + globalOutputStream() << "Writing the command list to " << path.c_str() << "\n"; + class BuildCommandList : public CommandVisitor + { + TextFileOutputStream m_commandList; + GtkListStore* m_store; + public: + BuildCommandList(const char* filename, GtkListStore* store) : m_commandList(filename), m_store(store) + { + } + void visit(const char* name, Accelerator& accelerator) + { + StringOutputStream modifiers; + modifiers << accelerator; + + { + GtkTreeIter iter; + gtk_list_store_append(m_store, &iter); + gtk_list_store_set(m_store, &iter, 0, name, 1, modifiers.c_str(), -1); + } + + if(!m_commandList.failed()) + { + m_commandList << makeLeftJustified(name, 25) << " " << modifiers.c_str() << '\n'; + } + } + } visitor(path.c_str(), store); + + GlobalShortcuts_foreach(visitor); + } + + g_object_unref(G_OBJECT(store)); + } + } + + GtkVBox* vbox = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, FALSE, 0); + { + GtkButton* button = create_modal_dialog_button("Close", dialog.m_close_button); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_grab_focus(GTK_WIDGET(button)); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0); + } + + modal_dialog_show(window, dialog); + gtk_widget_destroy(GTK_WIDGET(window)); +} + +#include "profile/profile.h" + +const char* const COMMANDS_VERSION = "1.0"; + +void SaveCommandMap(const char* path) +{ + StringOutputStream strINI(256); + strINI << path << "shortcuts.ini"; + + TextFileOutputStream file(strINI.c_str()); + if(!file.failed()) + { + file << "[Version]\n"; + file << "number=" << COMMANDS_VERSION << "\n"; + file << "\n"; + file << "[Commands]\n"; + class WriteCommandMap : public CommandVisitor + { + TextFileOutputStream& m_file; + public: + WriteCommandMap(TextFileOutputStream& file) : m_file(file) + { + } + void visit(const char* name, Accelerator& accelerator) + { + m_file << name << "="; + + const char* key = global_keys_find(accelerator.key); + if(!string_empty(key)) + { + m_file << key; + } + else if(accelerator.key != 0) + { + m_file << gdk_keyval_name(accelerator.key); + } + + if(accelerator.modifiers & GDK_MOD1_MASK) + { + m_file << "+Alt"; + } + if(accelerator.modifiers & GDK_CONTROL_MASK) + { + m_file << "+Ctrl"; + } + if(accelerator.modifiers & GDK_SHIFT_MASK) + { + m_file << "+Shift"; + } + + m_file << "\n"; + } + } visitor(file); + GlobalShortcuts_foreach(visitor); + } +} + +const char* stringrange_find(const char* first, const char* last, char c) +{ + const char* p = strchr(first, '+'); + if(p == 0) + { + return last; + } + return p; +} + +class ReadCommandMap : public CommandVisitor +{ + const char* m_filename; + std::size_t m_count; +public: + ReadCommandMap(const char* filename) : m_filename(filename), m_count(0) + { + } + void visit(const char* name, Accelerator& accelerator) + { + char value[1024]; + if (read_var(m_filename, "Commands", name, value )) + { + if(string_empty(value)) + { + accelerator.key = 0; + accelerator.modifiers = (GdkModifierType)0; + return; + } + int modifiers = 0; + const char* last = value + string_length(value); + const char* keyEnd = stringrange_find(value, last, '+'); + for(const char* modifier = keyEnd; modifier != last;) + { + const char* next = stringrange_find(modifier + 1, last, '+'); + if(next - modifier == 4 + && string_equal_nocase_n(modifier, "+alt", 4)) + { + modifiers |= GDK_MOD1_MASK; + } + else if(next - modifier == 5 + && string_equal_nocase_n(modifier, "+ctrl", 5) != 0) + { + modifiers |= GDK_CONTROL_MASK; + } + else if(next - modifier == 6 + && string_equal_nocase_n(modifier, "+shift", 6) != 0) + { + modifiers |= GDK_SHIFT_MASK; + } + else + { + globalOutputStream() << "WARNING: failed to parse user command " << makeQuoted(value) << ": unknown modifier " << makeQuoted(StringRange(modifier, next)) << "\n"; + } + modifier = next; + } + accelerator.modifiers = (GdkModifierType)modifiers; + + + // strBuff has been cleaned of it's modifiers .. switch between a regular key and a virtual one + // based on length + if(keyEnd - value == 1) // most often case.. deal with first + { + accelerator.key = std::toupper(value[0]); + ++m_count; + } + else // special key + { + CopiedString keyName(StringRange(value, keyEnd)); + accelerator.key = global_keys_find(keyName.c_str()); + if(accelerator.key != 0) + { + ++m_count; + } + else + { + globalOutputStream() << "WARNING: failed to parse user command " << makeQuoted(value) << ": unknown key " << makeQuoted(keyName.c_str()) << "\n"; + } + } + } + } + std::size_t count() const + { + return m_count; + } +}; + +void LoadCommandMap(const char* path) +{ + StringOutputStream strINI(256); + strINI << path << "shortcuts.ini"; + + FILE* f = fopen (strINI.c_str(), "r"); + if (f != 0) + { + fclose(f); + globalOutputStream() << "loading custom shortcuts list from " << makeQuoted(strINI.c_str()) << "\n"; + + Version version = version_parse(COMMANDS_VERSION); + Version dataVersion = { 0, 0 }; + + { + char value[1024]; + if(read_var(strINI.c_str(), "Version", "number", value)) + { + dataVersion = version_parse(value); + } + } + + if(version_compatible(version, dataVersion)) + { + globalOutputStream() << "commands import: data version " << dataVersion << " is compatible with code version " << version << "\n"; + ReadCommandMap visitor(strINI.c_str()); + GlobalShortcuts_foreach(visitor); + globalOutputStream() << "parsed " << Unsigned(visitor.count()) << " custom shortcuts\n"; + } + else + { + globalOutputStream() << "commands import: data version " << dataVersion << " is not compatible with code version " << version << "\n"; + } + } + else + { + globalOutputStream() << "failed to load custom shortcuts from " << makeQuoted(strINI.c_str()) << "\n"; + } +} diff --git a/radiant/commands.h b/radiant/commands.h new file mode 100644 index 00000000..e97ec22b --- /dev/null +++ b/radiant/commands.h @@ -0,0 +1,54 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_COMMANDS_H) +#define INCLUDED_COMMANDS_H + +#include "gtkutil/accelerator.h" + + +const Accelerator& GlobalShortcuts_insert(const char* name, const Accelerator& accelerator); +void GlobalShortcuts_register(const char* name); +void GlobalShortcuts_reportUnregistered(); + +class CommandVisitor +{ +public: + virtual void visit(const char* name, Accelerator& accelerator) = 0; +}; + +void GlobalCommands_insert(const char* name, const Callback& callback, const Accelerator& accelerator = accelerator_null()); +const Command& GlobalCommands_find(const char* name); + +void GlobalToggles_insert(const char* name, const Callback& callback, const BoolExportCallback& exportCallback, const Accelerator& accelerator = accelerator_null()); +const Toggle& GlobalToggles_find(const char* name); + +void GlobalKeyEvents_insert(const char* name, const Accelerator& accelerator, const Callback& keyDown, const Callback& keyUp); +const KeyEvent& GlobalKeyEvents_find(const char* name); + + +void DoCommandListDlg(); + +void LoadCommandMap(const char* path); +void SaveCommandMap(const char* path); + + +#endif diff --git a/radiant/console.cpp b/radiant/console.cpp new file mode 100644 index 00000000..37a03428 --- /dev/null +++ b/radiant/console.cpp @@ -0,0 +1,273 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "console.h" + +#include +#include +#include +#include +#include + +#include "gtkutil/accelerator.h" +#include "gtkutil/messagebox.h" +#include "gtkutil/container.h" +#include "gtkutil/menu.h" +#include "gtkutil/nonmodal.h" +#include "stream/stringstream.h" +#include "convert.h" + +#include "version.h" +#include "aboutmsg.h" +#include "gtkmisc.h" +#include "mainframe.h" + +// handle to the console log file +namespace +{ + FILE* g_hLogFile; +} + +bool g_Console_enableLogging = false; + +// called whenever we need to open/close/check the console log file +void Sys_LogFile(bool enable) +{ + if (enable && !g_hLogFile) + { + // settings say we should be logging and we don't have a log file .. so create it + // open a file to log the console (if user prefs say so) + // the file handle is g_hLogFile + // the log file is erased + StringOutputStream name(256); + name << SettingsPath_get() << "radiant.log"; + g_hLogFile = fopen( name.c_str(), "w" ); + if (g_hLogFile != 0) + { + globalOutputStream() << "Started logging to " << name.c_str() << "\n"; + time_t localtime; + time(&localtime); + globalOutputStream() << "Today is: " << ctime(&localtime) + << "This is GtkRadiant '" RADIANT_VERSION "' compiled " __DATE__ "\n" RADIANT_ABOUTMSG "\n"; + } + else + gtk_MessageBox (0, "Failed to create log file, check write permissions in Radiant directory.\n", + "Console logging", eMB_OK, eMB_ICONERROR ); + } + else if (!enable && g_hLogFile != 0) + { + // settings say we should not be logging but still we have an active logfile .. close it + time_t localtime; + time(&localtime); + globalOutputStream() << "Closing log file at " << ctime(&localtime) << "\n"; + fclose( g_hLogFile ); + g_hLogFile = 0; + } +} + +GtkWidget* g_console = 0; + +void console_clear() +{ + GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(g_console)); + gtk_text_buffer_set_text(buffer, "", -1); +} + +void console_populate_popup(GtkTextView* textview, GtkMenu* menu, gpointer user_data) +{ + menu_separator(menu); + + GtkWidget* item = gtk_menu_item_new_with_label ("Clear"); + g_signal_connect(G_OBJECT (item), "activate", G_CALLBACK(console_clear), 0); + gtk_widget_show (item); + container_add_widget(GTK_CONTAINER(menu), item); +} + +gboolean destroy_set_null(GtkWindow* widget, GtkWidget** p) +{ + *p = 0; + return FALSE; +} + +WidgetFocusPrinter g_consoleWidgetFocusPrinter("console"); + +GtkWidget* Console_constructWindow(GtkWindow* toplevel) +{ + GtkWidget* scr = gtk_scrolled_window_new (0, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scr), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scr), GTK_SHADOW_IN); + gtk_widget_show(scr); + + { + GtkWidget* text = gtk_text_view_new(); + gtk_widget_set_size_request(text, 0, -1); // allow shrinking + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE); + gtk_container_add(GTK_CONTAINER (scr), text); + gtk_widget_show(text); + g_console = text; + + //globalExtendedASCIICharacterSet().print(); + + widget_connect_escape_clear_focus_widget(g_console); + + //g_consoleWidgetFocusPrinter.connect(g_console); + + g_signal_connect(G_OBJECT(g_console), "populate-popup", G_CALLBACK(console_populate_popup), 0); + g_signal_connect(G_OBJECT(g_console), "destroy", G_CALLBACK(destroy_set_null), &g_console); + } + + gtk_container_set_focus_chain(GTK_CONTAINER(scr), NULL); + + return scr; +} + +class GtkTextBufferOutputStream : public TextOutputStream +{ + GtkTextBuffer* textBuffer; + GtkTextIter* iter; + GtkTextTag* tag; +public: + GtkTextBufferOutputStream(GtkTextBuffer* textBuffer, GtkTextIter* iter, GtkTextTag* tag) : textBuffer(textBuffer), iter(iter), tag(tag) + { + } + std::size_t write(const char* buffer, std::size_t length) + { + gtk_text_buffer_insert_with_tags(textBuffer, iter, buffer, gint(length), tag, 0); + return length; + } +}; + +std::size_t Sys_Print(int level, const char* buf, std::size_t length) +{ + bool contains_newline = std::find(buf, buf+length, '\n') != buf+length; + + if(level == SYS_ERR) + { + Sys_LogFile(true); + } + + if (g_hLogFile != 0) + { + fwrite(buf, 1, length, g_hLogFile); + if(contains_newline) + { + fflush(g_hLogFile); + } + } + + if (level != SYS_NOCON) + { + if (g_console != 0) + { + GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(g_console)); + + GtkTextIter iter; + gtk_text_buffer_get_end_iter(buffer, &iter); + + static GtkTextMark* end = gtk_text_buffer_create_mark(buffer, "end", &iter, FALSE); + + const GdkColor yellow = { 0, 0xb0ff, 0xb0ff, 0x0000 }; + const GdkColor red = { 0, 0xffff, 0x0000, 0x0000 }; + const GdkColor black = { 0, 0x0000, 0x0000, 0x0000 }; + + static GtkTextTag* error_tag = gtk_text_buffer_create_tag (buffer, "red_foreground", "foreground-gdk", &red, 0); + static GtkTextTag* warning_tag = gtk_text_buffer_create_tag (buffer, "yellow_foreground", "foreground-gdk", &yellow, 0); + static GtkTextTag* standard_tag = gtk_text_buffer_create_tag (buffer, "black_foreground", "foreground-gdk", &black, 0); + GtkTextTag* tag; + switch (level) + { + case SYS_WRN: + tag = warning_tag; + break; + case SYS_ERR: + tag = error_tag; + break; + case SYS_STD: + case SYS_VRB: + default: + tag = standard_tag; + break; + } + + + { + GtkTextBufferOutputStream textBuffer(buffer, &iter, tag); + if(!globalCharacterSet().isUTF8()) + { + BufferedTextOutputStream buffered(textBuffer); + buffered << ConvertLocaleToUTF8(StringRange(buf, buf + length)); + } + else + { + textBuffer << StringRange(buf, buf + length); + } + } + + // update console widget immediatly if we're doing something time-consuming + if(contains_newline) + { + gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(g_console), end); + + if(!ScreenUpdates_Enabled() && GTK_WIDGET_REALIZED(g_console)) + { + ScreenUpdates_process(); + } + } + } + } + return length; +} + + +class SysPrintOutputStream : public TextOutputStream +{ +public: + std::size_t write(const char* buffer, std::size_t length) + { + return Sys_Print(SYS_STD, buffer, length); + } +}; + +class SysPrintErrorStream : public TextOutputStream +{ +public: + std::size_t write(const char* buffer, std::size_t length) + { + return Sys_Print(SYS_ERR, buffer, length); + } +}; + +SysPrintOutputStream g_outputStream; + +TextOutputStream& getSysPrintOutputStream() +{ + return g_outputStream; +} + +SysPrintErrorStream g_errorStream; + +TextOutputStream& getSysPrintErrorStream() +{ + return g_errorStream; +} + + diff --git a/radiant/console.h b/radiant/console.h new file mode 100644 index 00000000..4e79782e --- /dev/null +++ b/radiant/console.h @@ -0,0 +1,47 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_CONSOLE_H) +#define INCLUDED_CONSOLE_H + +#include + +#define SYS_VRB 0 ///< verbose support (on/off) +#define SYS_STD 1 ///< standard print level - this is the default +#define SYS_WRN 2 ///< warnings +#define SYS_ERR 3 ///< error +#define SYS_NOCON 4 ///< no console, only print to the file (useful whenever Sys_Printf and output IS the problem) + +std::size_t Sys_Print(int level, const char* buf, std::size_t length); +class TextOutputStream; +TextOutputStream& getSysPrintOutputStream(); +TextOutputStream& getSysPrintErrorStream(); + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkWindow GtkWindow; +GtkWidget* Console_constructWindow(GtkWindow* toplevel); + +// will open/close the log file based on the parameter +void Sys_LogFile(bool enable); +extern bool g_Console_enableLogging; + + +#endif diff --git a/radiant/csg.cpp b/radiant/csg.cpp new file mode 100644 index 00000000..f30fb980 --- /dev/null +++ b/radiant/csg.cpp @@ -0,0 +1,692 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "csg.h" + +#include "debugging/debugging.h" + +#include + +#include "map.h" +#include "brushmanip.h" +#include "brushnode.h" +#include "grid.h" + +void Face_makeBrush(Face& face, const Brush& brush, brush_vector_t& out, float offset) +{ + if(face.contributes()) + { + out.push_back(new Brush(brush)); + Face* newFace = out.back()->addFace(face); + if(newFace != 0) + { + newFace->flipWinding(); + newFace->getPlane().offset(offset); + newFace->planeChanged(); + } + } +} + +class FaceMakeBrush +{ + const Brush& brush; + brush_vector_t& out; + float offset; +public: + FaceMakeBrush(const Brush& brush, brush_vector_t& out, float offset) + : brush(brush), out(out), offset(offset) + { + } + void operator()(Face& face) const + { + Face_makeBrush(face, brush, out, offset); + } +}; + +void Brush_makeHollow(const Brush& brush, brush_vector_t& out, float offset) +{ + Brush_forEachFace(brush, FaceMakeBrush(brush, out, offset)); +} + +class BrushHollowSelectedWalker : public scene::Graph::Walker +{ + float m_offset; +public: + BrushHollowSelectedWalker(float offset) + : m_offset(offset) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + Brush* brush = Node_getBrush(path.top()); + if(brush != 0 + && Instance_getSelectable(instance)->isSelected() + && path.size() > 1) + { + brush_vector_t out; + Brush_makeHollow(*brush, out, m_offset); + for(brush_vector_t::const_iterator i = out.begin(); i != out.end(); ++i) + { + (*i)->removeEmptyFaces(); + NodeSmartReference node((new BrushNode())->node()); + Node_getBrush(node)->copy(*(*i)); + delete (*i); + Node_getTraversable(path.parent())->insert(node); + } + } + } + return true; + } +}; + +typedef std::list brushlist_t; + +class BrushGatherSelected : public scene::Graph::Walker +{ + brush_vector_t& m_brushlist; +public: + BrushGatherSelected(brush_vector_t& brushlist) + : m_brushlist(brushlist) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + Brush* brush = Node_getBrush(path.top()); + if(brush != 0 + && Instance_getSelectable(instance)->isSelected()) + { + m_brushlist.push_back(brush); + } + } + return true; + } +}; + +class BrushDeleteSelected : public scene::Graph::Walker +{ +public: + bool pre(const scene::Path& path, scene::Instance& instance) const + { + return true; + } + void post(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + Brush* brush = Node_getBrush(path.top()); + if(brush != 0 + && Instance_getSelectable(instance)->isSelected() + && path.size() > 1) + { + Path_deleteTop(path); + } + } + } +}; + +void Scene_BrushMakeHollow_Selected(scene::Graph& graph) +{ + GlobalSceneGraph().traverse(BrushHollowSelectedWalker(GetGridSize())); + GlobalSceneGraph().traverse(BrushDeleteSelected()); +} + +/* +============= +CSG_MakeHollow +============= +*/ + +void CSG_MakeHollow (void) +{ + UndoableCommand undo("brushHollow"); + + Scene_BrushMakeHollow_Selected(GlobalSceneGraph()); + + SceneChangeNotify(); +} + +template +class RemoveReference +{ +public: + typedef Type type; +}; + +template +class RemoveReference +{ +public: + typedef Type type; +}; + +template +class Dereference +{ + const Functor& functor; +public: + typedef typename RemoveReference::type* first_argument_type; + typedef typename Functor::result_type result_type; + Dereference(const Functor& functor) : functor(functor) + { + } + result_type operator()(first_argument_type firstArgument) const + { + return functor(*firstArgument); + } +}; + +template +inline Dereference makeDereference(const Functor& functor) +{ + return Dereference(functor); +} + +typedef Face* FacePointer; +const FacePointer c_nullFacePointer = 0; + +template +Face* Brush_findIf(const Brush& brush, const Predicate& predicate) +{ + Brush::const_iterator i = std::find_if(brush.begin(), brush.end(), makeDereference(predicate)); + return i == brush.end() ? c_nullFacePointer : *i; // uses c_nullFacePointer instead of 0 because otherwise gcc 4.1 attempts conversion to int +} + +template +class BindArguments1 +{ + typedef typename Caller::second_argument_type FirstBound; + FirstBound firstBound; +public: + typedef typename Caller::result_type result_type; + typedef typename Caller::first_argument_type first_argument_type; + BindArguments1(FirstBound firstBound) + : firstBound(firstBound) + { + } + result_type operator()(first_argument_type firstArgument) const + { + return Caller::call(firstArgument, firstBound); + } +}; + +template +class BindArguments2 +{ + typedef typename Caller::second_argument_type FirstBound; + typedef typename Caller::third_argument_type SecondBound; + FirstBound firstBound; + SecondBound secondBound; +public: + typedef typename Caller::result_type result_type; + typedef typename Caller::first_argument_type first_argument_type; + BindArguments2(FirstBound firstBound, SecondBound secondBound) + : firstBound(firstBound), secondBound(secondBound) + { + } + result_type operator()(first_argument_type firstArgument) const + { + return Caller::call(firstArgument, firstBound, secondBound); + } +}; + +template +BindArguments2 bindArguments(const Caller& caller, FirstBound firstBound, SecondBound secondBound) +{ + return BindArguments2(firstBound, secondBound); +} + +inline bool Face_testPlane(const Face& face, const Plane3& plane, bool flipped) +{ + return face.contributes() && !Winding_TestPlane(face.getWinding(), plane, flipped); +} +typedef Function3 FaceTestPlane; + + + +/// \brief Returns true if +/// \li !flipped && brush is BACK or ON +/// \li flipped && brush is FRONT or ON +bool Brush_testPlane(const Brush& brush, const Plane3& plane, bool flipped) +{ + brush.evaluateBRep(); +#if 1 + for(Brush::const_iterator i(brush.begin()); i != brush.end(); ++i) + { + if(Face_testPlane(*(*i), plane, flipped)) + { + return false; + } + } + return true; +#else + return Brush_findIf(brush, bindArguments(FaceTestPlane(), makeReference(plane), flipped)) == 0; +#endif +} + +brushsplit_t Brush_classifyPlane(const Brush& brush, const Plane3& plane) +{ + brush.evaluateBRep(); + brushsplit_t split; + for(Brush::const_iterator i(brush.begin()); i != brush.end(); ++i) + { + if((*i)->contributes()) + { + split += Winding_ClassifyPlane((*i)->getWinding(), plane); + } + } + return split; +} + +bool Brush_subtract(const Brush& brush, const Brush& other, brush_vector_t& ret_fragments) +{ + if(aabb_intersects_aabb(brush.localAABB(), other.localAABB())) + { + brush_vector_t fragments; + fragments.reserve(other.size()); + Brush back(brush); + + for(Brush::const_iterator i(other.begin()); i != other.end(); ++i) + { + if((*i)->contributes()) + { + brushsplit_t split = Brush_classifyPlane(back, (*i)->plane3()); + if(split.counts[ePlaneFront] != 0 + && split.counts[ePlaneBack] != 0) + { + fragments.push_back(new Brush(back)); + Face* newFace = fragments.back()->addFace(*(*i)); + if(newFace != 0) + { + newFace->flipWinding(); + } + back.addFace(*(*i)); + } + else if(split.counts[ePlaneBack] == 0) + { + for(brush_vector_t::iterator i = fragments.begin(); i != fragments.end(); ++i) + { + delete(*i); + } + return false; + } + } + } + ret_fragments.insert(ret_fragments.end(), fragments.begin(), fragments.end()); + return true; + } + return false; +} + +class SubtractBrushesFromUnselected : public scene::Graph::Walker +{ + const brush_vector_t& m_brushlist; + std::size_t& m_before; + std::size_t& m_after; +public: + SubtractBrushesFromUnselected(const brush_vector_t& brushlist, std::size_t& before, std::size_t& after) + : m_brushlist(brushlist), m_before(before), m_after(after) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + return true; + } + void post(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + Brush* brush = Node_getBrush(path.top()); + if(brush != 0 + && !Instance_getSelectable(instance)->isSelected()) + { + brush_vector_t buffer[2]; + bool swap = false; + Brush* original = new Brush(*brush); + buffer[static_cast(swap)].push_back(original); + + { + for(brush_vector_t::const_iterator i(m_brushlist.begin()); i != m_brushlist.end(); ++i) + { + for(brush_vector_t::iterator j(buffer[static_cast(swap)].begin()); j != buffer[static_cast(swap)].end(); ++j) + { + if(Brush_subtract(*(*j), *(*i), buffer[static_cast(!swap)])) + { + delete (*j); + } + else + { + buffer[static_cast(!swap)].push_back((*j)); + } + } + buffer[static_cast(swap)].clear(); + swap = !swap; + } + } + + brush_vector_t& out = buffer[static_cast(swap)]; + + if(out.size() == 1 && out.back() == original) + { + delete original; + } + else + { + ++m_before; + for(brush_vector_t::const_iterator i = out.begin(); i != out.end(); ++i) + { + ++m_after; + NodeSmartReference node((new BrushNode())->node()); + (*i)->removeEmptyFaces(); + ASSERT_MESSAGE(!(*i)->empty(), "brush left with no faces after subtract"); + Node_getBrush(node)->copy(*(*i)); + delete (*i); + Node_getTraversable(path.parent())->insert(node); + } + Path_deleteTop(path); + } + } + } + } +}; + +void CSG_Subtract() +{ + brush_vector_t selected_brushes; + GlobalSceneGraph().traverse(BrushGatherSelected(selected_brushes)); + + if (selected_brushes.empty()) + { + globalOutputStream() << "CSG Subtract: No brushes selected.\n"; + } + else + { + globalOutputStream() << "CSG Subtract: Subtracting " << Unsigned(selected_brushes.size()) << " brushes.\n"; + + UndoableCommand undo("brushSubtract"); + + // subtract selected from unselected + std::size_t before = 0; + std::size_t after = 0; + GlobalSceneGraph().traverse(SubtractBrushesFromUnselected(selected_brushes, before, after)); + globalOutputStream() << "CSG Subtract: Result: " + << Unsigned(after) << " fragment" << (after == 1 ? "" : "s") + << " from " << Unsigned(before) << " brush" << (before == 1? "" : "es") << ".\n"; + + SceneChangeNotify(); + } +} + +class BrushSplitByPlaneSelected : public scene::Graph::Walker +{ + const Vector3& m_p0; + const Vector3& m_p1; + const Vector3& m_p2; + const char* m_shader; + const TextureProjection& m_projection; + EBrushSplit m_split; +public: + BrushSplitByPlaneSelected(const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, const TextureProjection& projection, EBrushSplit split) + : m_p0(p0), m_p1(p1), m_p2(p2), m_shader(shader), m_projection(projection), m_split(split) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + return true; + } + void post(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + Brush* brush = Node_getBrush(path.top()); + if(brush != 0 + && Instance_getSelectable(instance)->isSelected()) + { + Plane3 plane(plane3_for_points(m_p0, m_p1, m_p2)); + if(plane3_valid(plane)) + { + brushsplit_t split = Brush_classifyPlane(*brush, m_split == eFront ? plane3_flipped(plane) : plane); + if(split.counts[ePlaneBack] && split.counts[ePlaneFront]) + { + // the plane intersects this brush + if(m_split == eFrontAndBack) + { + NodeSmartReference node((new BrushNode())->node()); + Brush* fragment = Node_getBrush(node); + fragment->copy(*brush); + Face* newFace = fragment->addPlane(m_p0, m_p1, m_p2, m_shader, m_projection); + if(newFace != 0 && m_split != eFront) + { + newFace->flipWinding(); + } + fragment->removeEmptyFaces(); + ASSERT_MESSAGE(!fragment->empty(), "brush left with no faces after split"); + + Node_getTraversable(path.parent())->insert(node); + { + scene::Path fragmentPath = path; + fragmentPath.top() = makeReference(node.get()); + selectPath(fragmentPath, true); + } + } + + Face* newFace = brush->addPlane(m_p0, m_p1, m_p2, m_shader, m_projection); + if(newFace != 0 && m_split == eFront) + { + newFace->flipWinding(); + } + brush->removeEmptyFaces(); + ASSERT_MESSAGE(!brush->empty(), "brush left with no faces after split"); + } + else + // the plane does not intersect this brush + if(m_split != eFrontAndBack && split.counts[ePlaneBack] != 0) + { + // the brush is "behind" the plane + Path_deleteTop(path); + } + } + } + } + } +}; + +void Scene_BrushSplitByPlane(scene::Graph& graph, const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, EBrushSplit split) +{ + TextureProjection projection; + TexDef_Construct_Default(projection); + graph.traverse(BrushSplitByPlaneSelected(p0, p1, p2, shader, projection, split)); + SceneChangeNotify(); +} + + +class BrushInstanceSetClipPlane : public scene::Graph::Walker +{ + Plane3 m_plane; +public: + BrushInstanceSetClipPlane(const Plane3& plane) + : m_plane(plane) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + BrushInstance* brush = Instance_getBrush(instance); + if(brush != 0 + && path.top().get().visible() + && brush->isSelected()) + { + BrushInstance& brushInstance = *brush; + brushInstance.setClipPlane(m_plane); + } + return true; + } +}; + +void Scene_BrushSetClipPlane(scene::Graph& graph, const Plane3& plane) +{ + graph.traverse(BrushInstanceSetClipPlane(plane)); +} + +/* +============= +CSG_Merge +============= +*/ +bool Brush_merge(Brush& brush, const brush_vector_t& in, bool onlyshape) +{ + // gather potential outer faces + + { + typedef std::vector Faces; + Faces faces; + for(brush_vector_t::const_iterator i(in.begin()); i != in.end(); ++i) + { + (*i)->evaluateBRep(); + for(Brush::const_iterator j((*i)->begin()); j != (*i)->end(); ++j) + { + if(!(*j)->contributes()) + { + continue; + } + + const Face& face1 = *(*j); + + bool skip = false; + // test faces of all input brushes + //!\todo SPEEDUP: Flag already-skip faces and only test brushes from i+1 upwards. + for(brush_vector_t::const_iterator k(in.begin()); !skip && k != in.end(); ++k) + { + if(k != i) // don't test a brush against itself + { + for(Brush::const_iterator l((*k)->begin()); !skip && l != (*k)->end(); ++l) + { + const Face& face2 = *(*l); + + // face opposes another face + if(plane3_opposing(face1.plane3(), face2.plane3())) + { + // skip opposing planes + skip = true; + break; + } + } + } + } + + // check faces already stored + for(Faces::const_iterator m = faces.begin(); !skip && m != faces.end(); ++m) + { + const Face& face2 = *(*m); + + // face equals another face + if (plane3_equal(face1.plane3(), face2.plane3())) + { + //if the texture/shader references should be the same but are not + if (!onlyshape && !shader_equal(face1.getShader().getShader(), face2.getShader().getShader())) + { + return false; + } + // skip duplicate planes + skip = true; + break; + } + + // face1 plane intersects face2 winding or vice versa + if (Winding_PlanesConcave(face1.getWinding(), face2.getWinding(), face1.plane3(), face2.plane3())) + { + // result would not be convex + return false; + } + } + + if(!skip) + { + faces.push_back(&face1); + } + } + } + for(Faces::const_iterator i = faces.begin(); i != faces.end(); ++i) + { + if(!brush.addFace(*(*i))) + { + // result would have too many sides + return false; + } + } + } + + brush.removeEmptyFaces(); + + return true; +} + +void CSG_Merge(void) +{ + brush_vector_t selected_brushes; + + // remove selected + GlobalSceneGraph().traverse(BrushGatherSelected(selected_brushes)); + + if (selected_brushes.empty()) + { + globalOutputStream() << "CSG Merge: No brushes selected.\n"; + return; + } + + if (selected_brushes.size() < 2) + { + globalOutputStream() << "CSG Merge: At least two brushes have to be selected.\n"; + return; + } + + globalOutputStream() << "CSG Merge: Merging " << Unsigned(selected_brushes.size()) << " brushes.\n"; + + UndoableCommand undo("brushMerge"); + + scene::Path merged_path = GlobalSelectionSystem().ultimateSelected().path(); + + NodeSmartReference node((new BrushNode())->node()); + Brush* brush = Node_getBrush(node); + // if the new brush would not be convex + if(!Brush_merge(*brush, selected_brushes, true)) + { + globalOutputStream() << "CSG Merge: Failed - result would not be convex.\n"; + } + else + { + ASSERT_MESSAGE(!brush->empty(), "brush left with no faces after merge"); + + // free the original brushes + GlobalSceneGraph().traverse(BrushDeleteSelected()); + + merged_path.pop(); + Node_getTraversable(merged_path.top())->insert(node); + merged_path.push(makeReference(node.get())); + + selectPath(merged_path, true); + + globalOutputStream() << "CSG Merge: Succeeded.\n"; + SceneChangeNotify(); + } +} diff --git a/radiant/csg.h b/radiant/csg.h new file mode 100644 index 00000000..867ab199 --- /dev/null +++ b/radiant/csg.h @@ -0,0 +1,46 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_CSG_H) +#define INCLUDED_CSG_H + +void CSG_MakeHollow (void); +void CSG_Subtract (void); +void CSG_Merge (void); + +namespace scene +{ + class Graph; +} +template class BasicVector3; +typedef BasicVector3 Vector3; +class Plane3; + +void Scene_BrushSetClipPlane(scene::Graph& graph, const Plane3& plane); +enum EBrushSplit +{ + eFront, + eBack, + eFrontAndBack, +}; +void Scene_BrushSplitByPlane(scene::Graph& graph, const Vector3& p0, const Vector3& p1, const Vector3& p2, const char* shader, EBrushSplit split); + +#endif diff --git a/radiant/dialog.cpp b/radiant/dialog.cpp new file mode 100644 index 00000000..cb5c7069 --- /dev/null +++ b/radiant/dialog.cpp @@ -0,0 +1,753 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// Base dialog class, provides a way to run modal dialogs and +// set/get the widget values in member variables. +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "dialog.h" + +#include "debugging/debugging.h" + + +#include "mainframe.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stream/stringstream.h" +#include "convert.h" +#include "gtkutil/dialog.h" +#include "gtkutil/button.h" +#include "gtkutil/entry.h" +#include "gtkutil/image.h" + +#include "gtkmisc.h" + + +GtkEntry* DialogEntry_new() +{ + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_widget_set_size_request(GTK_WIDGET(entry), 64, -1); + return entry; +} + +class DialogEntryRow +{ +public: + DialogEntryRow(GtkWidget* row, GtkEntry* entry) : m_row(row), m_entry(entry) + { + } + GtkWidget* m_row; + GtkEntry* m_entry; +}; + +DialogEntryRow DialogEntryRow_new(const char* name) +{ + GtkWidget* alignment = gtk_alignment_new(0.0, 0.5, 0.0, 0.0); + gtk_widget_show(alignment); + + GtkEntry* entry = DialogEntry_new(); + gtk_container_add(GTK_CONTAINER(alignment), GTK_WIDGET(entry)); + + return DialogEntryRow(GTK_WIDGET(DialogRow_new(name, alignment)), entry); +} + + +GtkSpinButton* DialogSpinner_new(double value, double lower, double upper, int fraction) +{ + double step = 1.0 / double(fraction); + unsigned int digits = 0; + for(;fraction > 1; fraction /= 10) + { + ++digits; + } + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(GTK_ADJUSTMENT(gtk_adjustment_new(value, lower, upper, step, 10, 10)), step, digits)); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_widget_set_size_request(GTK_WIDGET(spin), 64, -1); + return spin; +} + +class DialogSpinnerRow +{ +public: + DialogSpinnerRow(GtkWidget* row, GtkSpinButton* spin) : m_row(row), m_spin(spin) + { + } + GtkWidget* m_row; + GtkSpinButton* m_spin; +}; + +DialogSpinnerRow DialogSpinnerRow_new(const char* name, double value, double lower, double upper, int fraction) +{ + GtkWidget* alignment = gtk_alignment_new(0.0, 0.5, 0.0, 0.0); + gtk_widget_show(alignment); + + GtkSpinButton* spin = DialogSpinner_new(value, lower, upper, fraction); + gtk_container_add(GTK_CONTAINER(alignment), GTK_WIDGET(spin)); + + return DialogSpinnerRow(GTK_WIDGET(DialogRow_new(name, alignment)), spin); +} + + + +template< + typename Type_, + typename Other_, + void(*Import)(Type_&, Other_), + void(*Export)(Type_&, const Callback1&) +> +class ImportExport +{ +public: + typedef Type_ Type; + typedef Other_ Other; + + typedef ReferenceCaller1 ImportCaller; + typedef ReferenceCaller1&, Export> ExportCaller; +}; + +typedef ImportExport BoolImportExport; +typedef ImportExport IntImportExport; +typedef ImportExport SizeImportExport; +typedef ImportExport FloatImportExport; +typedef ImportExport StringImportExport; + + + +void BoolToggleImport(GtkToggleButton& widget, bool value) +{ + gtk_toggle_button_set_active(&widget, value); +} +void BoolToggleExport(GtkToggleButton& widget, const BoolImportCallback& importCallback) +{ + importCallback(gtk_toggle_button_get_active(&widget) != FALSE); +} +typedef ImportExport BoolToggleImportExport; + + +void IntRadioImport(GtkRadioButton& widget, int index) +{ + radio_button_set_active(&widget, index); +} +void IntRadioExport(GtkRadioButton& widget, const IntImportCallback& importCallback) +{ + importCallback(radio_button_get_active(&widget)); +} +typedef ImportExport IntRadioImportExport; + +template +class StringFromType +{ + StringOutputStream value; +public: + StringFromType(const Type& type) + { + value << Formatter(type); + } + operator const char*() const + { + return value.c_str(); + } +}; + +typedef StringFromType LocaleToUTF8; +typedef StringFromType UTF8ToLocale; + +void TextEntryImport(GtkEntry& widget, const char* text) +{ + gtk_entry_set_text(&widget, LocaleToUTF8(text)); +} +void TextEntryExport(GtkEntry& widget, const StringImportCallback& importCallback) +{ + importCallback(UTF8ToLocale(gtk_entry_get_text(&widget))); +} +typedef ImportExport TextEntryImportExport; + + +void IntEntryImport(GtkEntry& widget, int value) +{ + entry_set_int(&widget, value); +} +void IntEntryExport(GtkEntry& widget, const IntImportCallback& importCallback) +{ + importCallback(atoi(gtk_entry_get_text (&widget))); +} +typedef ImportExport IntEntryImportExport; + + +void SizeEntryImport(GtkEntry& widget, std::size_t value) +{ + entry_set_int(&widget, int(value)); +} +void SizeEntryExport(GtkEntry& widget, const SizeImportCallback& importCallback) +{ + int value = atoi(gtk_entry_get_text(&widget)); + if(value < 0) + { + value = 0; + } + importCallback(value); +} +typedef ImportExport SizeEntryImportExport; + + +void FloatEntryImport(GtkEntry& widget, float value) +{ + entry_set_float(&widget, value); +} +void FloatEntryExport(GtkEntry& widget, const FloatImportCallback& importCallback) +{ + importCallback((float)atof(gtk_entry_get_text(&widget))); +} +typedef ImportExport FloatEntryImportExport; + + +void FloatSpinnerImport(GtkSpinButton& widget, float value) +{ + gtk_spin_button_set_value(&widget, value); +} +void FloatSpinnerExport(GtkSpinButton& widget, const FloatImportCallback& importCallback) +{ + importCallback(float(gtk_spin_button_get_value_as_float(&widget))); +} +typedef ImportExport FloatSpinnerImportExport; + + +void IntSpinnerImport(GtkSpinButton& widget, int value) +{ + gtk_spin_button_set_value(&widget, value); +} +void IntSpinnerExport(GtkSpinButton& widget, const IntImportCallback& importCallback) +{ + importCallback(gtk_spin_button_get_value_as_int(&widget)); +} +typedef ImportExport IntSpinnerImportExport; + + +void IntAdjustmentImport(GtkAdjustment& widget, int value) +{ + gtk_adjustment_set_value(&widget, value); +} +void IntAdjustmentExport(GtkAdjustment& widget, const IntImportCallback& importCallback) +{ + importCallback((int)gtk_adjustment_get_value(&widget)); +} +typedef ImportExport IntAdjustmentImportExport; + + +void IntComboImport(GtkComboBox& widget, int value) +{ + gtk_combo_box_set_active(&widget, value); +} +void IntComboExport(GtkComboBox& widget, const IntImportCallback& importCallback) +{ + importCallback(gtk_combo_box_get_active(&widget)); +} +typedef ImportExport IntComboImportExport; + + +template +class CallbackDialogData : public DLG_DATA +{ +public: + typedef Callback1 ImportCallback; + typedef Callback1 ExportCallback; + +private: + ImportCallback m_importWidget; + ExportCallback m_exportWidget; + ImportCallback m_importViewer; + ExportCallback m_exportViewer; + +public: + CallbackDialogData(const ImportCallback& importWidget, const ExportCallback& exportWidget, const ImportCallback& importViewer, const ExportCallback& exportViewer) + : m_importWidget(importWidget), m_exportWidget(exportWidget), m_importViewer(importViewer), m_exportViewer(exportViewer) + { + } + void release() + { + delete this; + } + void importData() const + { + m_exportViewer(m_importWidget); + } + void exportData() const + { + m_exportWidget(m_importViewer); + } +}; + +template +class AddData +{ + DialogDataList& m_data; +public: + AddData(DialogDataList& data) : m_data(data) + { + } + void apply(typename Widget::Type& widget, typename Viewer::Type& viewer) const + { + m_data.push_back( + new CallbackDialogData( + typename Widget::ImportCaller(widget), + typename Widget::ExportCaller(widget), + typename Viewer::ImportCaller(viewer), + typename Viewer::ExportCaller(viewer) + ) + ); + } +}; + +template +class AddCustomData +{ + DialogDataList& m_data; +public: + AddCustomData(DialogDataList& data) : m_data(data) + { + } + void apply( + typename Widget::Type& widget, + const Callback1& importViewer, + const Callback1&>& exportViewer + ) const + { + m_data.push_back( + new CallbackDialogData( + typename Widget::ImportCaller(widget), + typename Widget::ExportCaller(widget), + importViewer, + exportViewer + ) + ); + } +}; + +// ============================================================================= +// Dialog class + +Dialog::Dialog() : m_window(0), m_parent(0) +{ +} + +Dialog::~Dialog() +{ + for(DialogDataList::iterator i = m_data.begin(); i != m_data.end(); ++i) + { + (*i)->release(); + } + + ASSERT_MESSAGE(m_window == 0, "dialog window not destroyed"); +} + +void Dialog::ShowDlg() +{ + ASSERT_MESSAGE(m_window != 0, "dialog was not constructed"); + importData(); + gtk_widget_show(GTK_WIDGET(m_window)); +} + +void Dialog::HideDlg() +{ + ASSERT_MESSAGE(m_window != 0, "dialog was not constructed"); + exportData(); + gtk_widget_hide(GTK_WIDGET(m_window)); +} + +static gint delete_event_callback(GtkWidget *widget, GdkEvent* event, gpointer data) +{ + reinterpret_cast(data)->HideDlg(); + reinterpret_cast(data)->EndModal(eIDCANCEL); + return TRUE; +} + +void Dialog::Create() +{ + ASSERT_MESSAGE(m_window == 0, "dialog cannot be constructed"); + + m_window = BuildDialog(); + g_signal_connect(G_OBJECT(m_window), "delete_event", G_CALLBACK(delete_event_callback), this); +} + +void Dialog::Destroy() +{ + ASSERT_MESSAGE(m_window != 0, "dialog cannot be destroyed"); + + gtk_widget_destroy(GTK_WIDGET(m_window)); + m_window = 0; +} + + +void Dialog::AddBoolToggleData(GtkToggleButton& widget, const BoolImportCallback& importViewer, const BoolExportCallback& exportViewer) +{ + AddCustomData(m_data).apply(widget, importViewer, exportViewer); +} + +void Dialog::AddIntRadioData(GtkRadioButton& widget, const IntImportCallback& importViewer, const IntExportCallback& exportViewer) +{ + AddCustomData(m_data).apply(widget, importViewer, exportViewer); +} + +void Dialog::AddTextEntryData(GtkEntry& widget, const StringImportCallback& importViewer, const StringExportCallback& exportViewer) +{ + AddCustomData(m_data).apply(widget, importViewer, exportViewer); +} + +void Dialog::AddIntEntryData(GtkEntry& widget, const IntImportCallback& importViewer, const IntExportCallback& exportViewer) +{ + AddCustomData(m_data).apply(widget, importViewer, exportViewer); +} + +void Dialog::AddSizeEntryData(GtkEntry& widget, const SizeImportCallback& importViewer, const SizeExportCallback& exportViewer) +{ + AddCustomData(m_data).apply(widget, importViewer, exportViewer); +} + +void Dialog::AddFloatEntryData(GtkEntry& widget, const FloatImportCallback& importViewer, const FloatExportCallback& exportViewer) +{ + AddCustomData(m_data).apply(widget, importViewer, exportViewer); +} + +void Dialog::AddFloatSpinnerData(GtkSpinButton& widget, const FloatImportCallback& importViewer, const FloatExportCallback& exportViewer) +{ + AddCustomData(m_data).apply(widget, importViewer, exportViewer); +} + +void Dialog::AddIntSpinnerData(GtkSpinButton& widget, const IntImportCallback& importViewer, const IntExportCallback& exportViewer) +{ + AddCustomData(m_data).apply(widget, importViewer, exportViewer); +} + +void Dialog::AddIntAdjustmentData(GtkAdjustment& widget, const IntImportCallback& importViewer, const IntExportCallback& exportViewer) +{ + AddCustomData(m_data).apply(widget, importViewer, exportViewer); +} + +void Dialog::AddIntComboData(GtkComboBox& widget, const IntImportCallback& importViewer, const IntExportCallback& exportViewer) +{ + AddCustomData(m_data).apply(widget, importViewer, exportViewer); +} + + +void Dialog::AddDialogData(GtkToggleButton& widget, bool& data) +{ + AddData(m_data).apply(widget, data); +} +void Dialog::AddDialogData(GtkRadioButton& widget, int& data) +{ + AddData(m_data).apply(widget, data); +} +void Dialog::AddDialogData(GtkEntry& widget, CopiedString& data) +{ + AddData(m_data).apply(widget, data); +} +void Dialog::AddDialogData(GtkEntry& widget, int& data) +{ + AddData(m_data).apply(widget, data); +} +void Dialog::AddDialogData(GtkEntry& widget, std::size_t& data) +{ + AddData(m_data).apply(widget, data); +} +void Dialog::AddDialogData(GtkEntry& widget, float& data) +{ + AddData(m_data).apply(widget, data); +} +void Dialog::AddDialogData(GtkSpinButton& widget, float& data) +{ + AddData(m_data).apply(widget, data); +} +void Dialog::AddDialogData(GtkSpinButton& widget, int& data) +{ + AddData(m_data).apply(widget, data); +} +void Dialog::AddDialogData(GtkAdjustment& widget, int& data) +{ + AddData(m_data).apply(widget, data); +} +void Dialog::AddDialogData(GtkComboBox& widget, int& data) +{ + AddData(m_data).apply(widget, data); +} + +void Dialog::exportData() +{ + for(DialogDataList::iterator i = m_data.begin(); i != m_data.end(); ++i) + { + (*i)->exportData(); + } +} + +void Dialog::importData() +{ + for(DialogDataList::iterator i = m_data.begin(); i != m_data.end(); ++i) + { + (*i)->importData(); + } +} + +void Dialog::EndModal (EMessageBoxReturn code) +{ + m_modal.loop = 0; + m_modal.ret = code; +} + +EMessageBoxReturn Dialog::DoModal() +{ + importData(); + + PreModal(); + + EMessageBoxReturn ret = modal_dialog_show(m_window, m_modal); + ASSERT_NOTNULL(m_window); + if(ret == eIDOK) + { + exportData(); + } + + gtk_widget_hide(GTK_WIDGET(m_window)); + + PostModal(m_modal.ret); + + return m_modal.ret; +} + + +GtkWidget* Dialog::addCheckBox(GtkWidget* vbox, const char* name, const char* flag, const BoolImportCallback& importViewer, const BoolExportCallback& exportViewer) +{ + GtkWidget* check = gtk_check_button_new_with_label(flag); + gtk_widget_show(check); + AddBoolToggleData(*GTK_TOGGLE_BUTTON(check), importViewer, exportViewer); + + DialogVBox_packRow(GTK_VBOX(vbox), GTK_WIDGET(DialogRow_new(name, check))); + return check; +} + +GtkWidget* Dialog::addCheckBox(GtkWidget* vbox, const char* name, const char* flag, bool& data) +{ + return addCheckBox(vbox, name, flag, BoolImportCaller(data), BoolExportCaller(data)); +} + +void Dialog::addCombo(GtkWidget* vbox, const char* name, StringArrayRange values, const IntImportCallback& importViewer, const IntExportCallback& exportViewer) +{ + GtkWidget* alignment = gtk_alignment_new(0.0, 0.5, 0.0, 0.0); + gtk_widget_show(alignment); + { + GtkWidget* combo = gtk_combo_box_new_text(); + + for(StringArrayRange::Iterator i = values.first; i != values.last; ++i) + { + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), *i); + } + + AddIntComboData(*GTK_COMBO_BOX(combo), importViewer, exportViewer); + + gtk_widget_show (combo); + gtk_container_add(GTK_CONTAINER(alignment), combo); + } + + GtkTable* row = DialogRow_new(name, alignment); + DialogVBox_packRow(GTK_VBOX(vbox), GTK_WIDGET(row)); +} + +void Dialog::addCombo(GtkWidget* vbox, const char* name, int& data, StringArrayRange values) +{ + addCombo(vbox, name, values, IntImportCaller(data), IntExportCaller(data)); +} + +void Dialog::addSlider(GtkWidget* vbox, const char* name, int& data, gboolean draw_value, const char* low, const char* high, double value, double lower, double upper, double step_increment, double page_increment, double page_size) +{ +#if 0 + if(draw_value == FALSE) + { + GtkWidget* hbox2 = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox2); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox2), FALSE, FALSE, 0); + { + GtkWidget* label = gtk_label_new (low); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox2), label, FALSE, FALSE, 0); + } + { + GtkWidget* label = gtk_label_new (high); + gtk_widget_show (label); + gtk_box_pack_end (GTK_BOX (hbox2), label, FALSE, FALSE, 0); + } + } +#endif + + // adjustment + GtkObject* adj = gtk_adjustment_new(value, lower, upper, step_increment, page_increment, page_size); + AddIntAdjustmentData(*GTK_ADJUSTMENT(adj), IntImportCaller(data), IntExportCaller(data)); + + // scale + GtkWidget* alignment = gtk_alignment_new(0.0, 0.5, 1.0, 0.0); + gtk_widget_show(alignment); + + GtkWidget* scale = gtk_hscale_new(GTK_ADJUSTMENT(adj)); + gtk_scale_set_value_pos(GTK_SCALE(scale), GTK_POS_LEFT); + gtk_widget_show(scale); + gtk_container_add(GTK_CONTAINER(alignment), scale); + + gtk_scale_set_draw_value(GTK_SCALE (scale), draw_value); + gtk_scale_set_digits(GTK_SCALE (scale), 0); + + GtkTable* row = DialogRow_new(name, alignment); + DialogVBox_packRow(GTK_VBOX(vbox), GTK_WIDGET(row)); +} + +void Dialog::addRadio(GtkWidget* vbox, const char* name, StringArrayRange names, const IntImportCallback& importViewer, const IntExportCallback& exportViewer) +{ + GtkWidget* alignment = gtk_alignment_new(0.0, 0.5, 0.0, 0.0); + gtk_widget_show(alignment); + { + RadioHBox radioBox = RadioHBox_new(names); + gtk_container_add(GTK_CONTAINER(alignment), GTK_WIDGET(radioBox.m_hbox)); + AddIntRadioData(*GTK_RADIO_BUTTON(radioBox.m_radio), importViewer, exportViewer); + } + + GtkTable* row = DialogRow_new(name, alignment); + DialogVBox_packRow(GTK_VBOX(vbox), GTK_WIDGET(row)); +} + +void Dialog::addRadio(GtkWidget* vbox, const char* name, int& data, StringArrayRange names) +{ + addRadio(vbox, name, names, IntImportCaller(data), IntExportCaller(data)); +} + +void Dialog::addRadioIcons(GtkWidget* vbox, const char* name, StringArrayRange icons, const IntImportCallback& importViewer, const IntExportCallback& exportViewer) +{ + GtkWidget* table = gtk_table_new (2, static_cast(icons.last - icons.first), FALSE); + gtk_widget_show (table); + + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + GSList* group = 0; + GtkWidget* radio = 0; + for(StringArrayRange::Iterator icon = icons.first; icon != icons.last; ++icon) + { + guint pos = static_cast(icon - icons.first); + GtkImage* image = new_local_image(*icon); + gtk_widget_show(GTK_WIDGET(image)); + gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(image), pos, pos+1, 0, 1, + (GtkAttachOptions) (0), + (GtkAttachOptions) (0), 0, 0); + + radio = gtk_radio_button_new(group); + gtk_widget_show (radio); + gtk_table_attach (GTK_TABLE (table), radio, pos, pos+1, 1, 2, + (GtkAttachOptions) (0), + (GtkAttachOptions) (0), 0, 0); + + group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio)); + } + + AddIntRadioData(*GTK_RADIO_BUTTON(radio), importViewer, exportViewer); + + DialogVBox_packRow(GTK_VBOX(vbox), GTK_WIDGET(DialogRow_new(name, table))); +} + +void Dialog::addRadioIcons(GtkWidget* vbox, const char* name, int& data, StringArrayRange icons) +{ + addRadioIcons(vbox, name, icons, IntImportCaller(data), IntExportCaller(data)); +} + +GtkWidget* Dialog::addIntEntry(GtkWidget* vbox, const char* name, const IntImportCallback& importViewer, const IntExportCallback& exportViewer) +{ + DialogEntryRow row(DialogEntryRow_new(name)); + AddIntEntryData(*row.m_entry, importViewer, exportViewer); + DialogVBox_packRow(GTK_VBOX(vbox), row.m_row); + return row.m_row; +} + +GtkWidget* Dialog::addSizeEntry(GtkWidget* vbox, const char* name, const SizeImportCallback& importViewer, const SizeExportCallback& exportViewer) +{ + DialogEntryRow row(DialogEntryRow_new(name)); + AddSizeEntryData(*row.m_entry, importViewer, exportViewer); + DialogVBox_packRow(GTK_VBOX(vbox), row.m_row); + return row.m_row; +} + +GtkWidget* Dialog::addFloatEntry(GtkWidget* vbox, const char* name, const FloatImportCallback& importViewer, const FloatExportCallback& exportViewer) +{ + DialogEntryRow row(DialogEntryRow_new(name)); + AddFloatEntryData(*row.m_entry, importViewer, exportViewer); + DialogVBox_packRow(GTK_VBOX(vbox), row.m_row); + return row.m_row; +} + +GtkWidget* Dialog::addPathEntry(GtkWidget* vbox, const char* name, bool browse_directory, const StringImportCallback& importViewer, const StringExportCallback& exportViewer) +{ + PathEntry pathEntry = PathEntry_new(); + g_signal_connect(G_OBJECT(pathEntry.m_button), "clicked", G_CALLBACK(browse_directory ? button_clicked_entry_browse_directory : button_clicked_entry_browse_file), pathEntry.m_entry); + + AddTextEntryData(*GTK_ENTRY(pathEntry.m_entry), importViewer, exportViewer); + + GtkTable* row = DialogRow_new(name, GTK_WIDGET(pathEntry.m_frame)); + DialogVBox_packRow(GTK_VBOX(vbox), GTK_WIDGET(row)); + + return GTK_WIDGET(row); +} + +GtkWidget* Dialog::addPathEntry(GtkWidget* vbox, const char* name, CopiedString& data, bool browse_directory) +{ + return addPathEntry(vbox, name, browse_directory, StringImportCallback(StringImportCaller(data)), StringExportCallback(StringExportCaller(data))); +} + +GtkWidget* Dialog::addSpinner(GtkWidget* vbox, const char* name, double value, double lower, double upper, const IntImportCallback& importViewer, const IntExportCallback& exportViewer) +{ + DialogSpinnerRow row(DialogSpinnerRow_new(name, value, lower, upper, 1)); + AddIntSpinnerData(*row.m_spin, importViewer, exportViewer); + DialogVBox_packRow(GTK_VBOX(vbox), row.m_row); + return row.m_row; +} + +GtkWidget* Dialog::addSpinner(GtkWidget* vbox, const char* name, int& data, double value, double lower, double upper) +{ + return addSpinner(vbox, name, value, lower, upper, IntImportCallback(IntImportCaller(data)), IntExportCallback(IntExportCaller(data))); +} + +GtkWidget* Dialog::addSpinner(GtkWidget* vbox, const char* name, double value, double lower, double upper, const FloatImportCallback& importViewer, const FloatExportCallback& exportViewer) +{ + DialogSpinnerRow row(DialogSpinnerRow_new(name, value, lower, upper, 10)); + AddFloatSpinnerData(*row.m_spin, importViewer, exportViewer); + DialogVBox_packRow(GTK_VBOX(vbox), row.m_row); + return row.m_row; +} diff --git a/radiant/dialog.h b/radiant/dialog.h new file mode 100644 index 00000000..d7ac94bd --- /dev/null +++ b/radiant/dialog.h @@ -0,0 +1,208 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_DIALOG_H) +#define INCLUDED_DIALOG_H + +#include + +#include "gtkutil/dialog.h" +#include "generic/callback.h" +#include "string/string.h" + +template +class ReferenceCaller1; + +inline void BoolImport(bool& self, bool value) +{ + self = value; +} +typedef ReferenceCaller1 BoolImportCaller; + +inline void BoolExport(bool& self, const BoolImportCallback& importCallback) +{ + importCallback(self); +} +typedef ReferenceCaller1 BoolExportCaller; + + +inline void IntImport(int& self, int value) +{ + self = value; +} +typedef ReferenceCaller1 IntImportCaller; + +inline void IntExport(int& self, const IntImportCallback& importCallback) +{ + importCallback(self); +} +typedef ReferenceCaller1 IntExportCaller; + + +inline void SizeImport(std::size_t& self, std::size_t value) +{ + self = value; +} +typedef ReferenceCaller1 SizeImportCaller; + +inline void SizeExport(std::size_t& self, const SizeImportCallback& importCallback) +{ + importCallback(self); +} +typedef ReferenceCaller1 SizeExportCaller; + + +inline void FloatImport(float& self, float value) +{ + self = value; +} +typedef ReferenceCaller1 FloatImportCaller; + +inline void FloatExport(float& self, const FloatImportCallback& importCallback) +{ + importCallback(self); +} +typedef ReferenceCaller1 FloatExportCaller; + + +inline void StringImport(CopiedString& self, const char* value) +{ + self = value; +} +typedef ReferenceCaller1 StringImportCaller; +inline void StringExport(CopiedString& self, const StringImportCallback& importCallback) +{ + importCallback(self.c_str()); +} +typedef ReferenceCaller1 StringExportCaller; + + +struct DLG_DATA +{ + virtual void release() = 0; + virtual void importData() const = 0; + virtual void exportData() const = 0; +}; + +typedef struct _GtkWindow GtkWindow; +typedef struct _GtkToggleButton GtkToggleButton; +typedef struct _GtkRadioButton GtkRadioButton; +typedef struct _GtkSpinButton GtkSpinButton; +typedef struct _GtkComboBox GtkComboBox; +typedef struct _GtkEntry GtkEntry; +typedef struct _GtkAdjustment GtkAdjustment; + +template +class CallbackDialogData; + +typedef std::list DialogDataList; + +class Dialog +{ + GtkWindow* m_window; + DialogDataList m_data; +public: + ModalDialog m_modal; + GtkWindow* m_parent; + + Dialog(); + virtual ~Dialog(); + + /*! + start modal dialog box + you need to use AddModalButton to select eIDOK eIDCANCEL buttons + */ + EMessageBoxReturn DoModal(); + void EndModal (EMessageBoxReturn code); + virtual GtkWindow* BuildDialog() = 0; + virtual void exportData(); + virtual void importData(); + virtual void PreModal() { }; + virtual void PostModal (EMessageBoxReturn code) { }; + virtual void ShowDlg(); + virtual void HideDlg(); + void Create(); + void Destroy(); + GtkWindow* GetWidget() + { + return m_window; + } + const GtkWindow* GetWidget() const + { + return m_window; + } + + GtkWidget* addCheckBox(GtkWidget* vbox, const char* name, const char* flag, const BoolImportCallback& importCallback, const BoolExportCallback& exportCallback); + GtkWidget* addCheckBox(GtkWidget* vbox, const char* name, const char* flag, bool& data); + void addCombo(GtkWidget* vbox, const char* name, StringArrayRange values, const IntImportCallback& importCallback, const IntExportCallback& exportCallback); + void addCombo(GtkWidget* vbox, const char* name, int& data, StringArrayRange values); + void addSlider(GtkWidget* vbox, const char* name, int& data, gboolean draw_value, const char* low, const char* high, double value, double lower, double upper, double step_increment, double page_increment, double page_size); + void addRadio(GtkWidget* vbox, const char* name, StringArrayRange names, const IntImportCallback& importCallback, const IntExportCallback& exportCallback); + void addRadio(GtkWidget* vbox, const char* name, int& data, StringArrayRange names); + void addRadioIcons(GtkWidget* vbox, const char* name, StringArrayRange icons, const IntImportCallback& importCallback, const IntExportCallback& exportCallback); + void addRadioIcons(GtkWidget* vbox, const char* name, int& data, StringArrayRange icons); + GtkWidget* addIntEntry(GtkWidget* vbox, const char* name, const IntImportCallback& importCallback, const IntExportCallback& exportCallback); + GtkWidget* addEntry(GtkWidget* vbox, const char* name, int& data) + { + return addIntEntry(vbox, name, IntImportCaller(data), IntExportCaller(data)); + } + GtkWidget* addSizeEntry(GtkWidget* vbox, const char* name, const SizeImportCallback& importCallback, const SizeExportCallback& exportCallback); + GtkWidget* addEntry(GtkWidget* vbox, const char* name, std::size_t& data) + { + return addSizeEntry(vbox, name, SizeImportCaller(data), SizeExportCaller(data)); + } + GtkWidget* addFloatEntry(GtkWidget* vbox, const char* name, const FloatImportCallback& importCallback, const FloatExportCallback& exportCallback); + GtkWidget* addEntry(GtkWidget* vbox, const char* name, float& data) + { + return addFloatEntry(vbox, name, FloatImportCaller(data), FloatExportCaller(data)); + } + GtkWidget* addPathEntry(GtkWidget* vbox, const char* name, bool browse_directory, const StringImportCallback& importCallback, const StringExportCallback& exportCallback); + GtkWidget* addPathEntry(GtkWidget* vbox, const char* name, CopiedString& data, bool directory); + GtkWidget* addSpinner(GtkWidget* vbox, const char* name, int& data, double value, double lower, double upper); + GtkWidget* addSpinner(GtkWidget* vbox, const char* name, double value, double lower, double upper, const IntImportCallback& importCallback, const IntExportCallback& exportCallback); + GtkWidget* addSpinner(GtkWidget* vbox, const char* name, double value, double lower, double upper, const FloatImportCallback& importCallback, const FloatExportCallback& exportCallback); + +protected: + + void AddBoolToggleData(GtkToggleButton& object, const BoolImportCallback& importCallback, const BoolExportCallback& exportCallback); + void AddIntRadioData(GtkRadioButton& object, const IntImportCallback& importCallback, const IntExportCallback& exportCallback); + void AddTextEntryData(GtkEntry& object, const StringImportCallback& importCallback, const StringExportCallback& exportCallback); + void AddIntEntryData(GtkEntry& object, const IntImportCallback& importCallback, const IntExportCallback& exportCallback); + void AddSizeEntryData(GtkEntry& object, const SizeImportCallback& importCallback, const SizeExportCallback& exportCallback); + void AddFloatEntryData(GtkEntry& object, const FloatImportCallback& importCallback, const FloatExportCallback& exportCallback); + void AddFloatSpinnerData(GtkSpinButton& object, const FloatImportCallback& importCallback, const FloatExportCallback& exportCallback); + void AddIntSpinnerData(GtkSpinButton& object, const IntImportCallback& importCallback, const IntExportCallback& exportCallback); + void AddIntAdjustmentData(GtkAdjustment& object, const IntImportCallback& importCallback, const IntExportCallback& exportCallback); + void AddIntComboData(GtkComboBox& object, const IntImportCallback& importCallback, const IntExportCallback& exportCallback); + + void AddDialogData(GtkToggleButton& object, bool& data); + void AddDialogData(GtkRadioButton& object, int& data); + void AddDialogData(GtkEntry& object, CopiedString& data); + void AddDialogData(GtkEntry& object, int& data); + void AddDialogData(GtkEntry& object, std::size_t& data); + void AddDialogData(GtkEntry& object, float& data); + void AddDialogData(GtkSpinButton& object, float& data); + void AddDialogData(GtkSpinButton& object, int& data); + void AddDialogData(GtkAdjustment& object, int& data); + void AddDialogData(GtkComboBox& object, int& data); +}; + +#endif diff --git a/radiant/eclass.cpp b/radiant/eclass.cpp new file mode 100644 index 00000000..edbf4156 --- /dev/null +++ b/radiant/eclass.cpp @@ -0,0 +1,414 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "eclass.h" + +#include "debugging/debugging.h" + +#include + +#include "ifilesystem.h" + +#include "string/string.h" +#include "eclasslib.h" +#include "os/path.h" +#include "os/dir.h" +#include "stream/stringstream.h" +#include "moduleobservers.h" + +#include "cmdlib.h" + +#include "preferences.h" +#include "mainframe.h" + + +namespace +{ + typedef std::map EntityClasses; + EntityClasses g_entityClasses; + EntityClass *eclass_bad = 0; + char eclass_directory[1024]; + typedef std::map ListAttributeTypes; + ListAttributeTypes g_listTypes; +} + +EClassModules& EntityClassManager_getEClassModules(); + +/*! +implementation of the EClass manager API +*/ + +void CleanEntityList(EntityClasses& entityClasses) +{ + for(EntityClasses::iterator i = entityClasses.begin(); i != entityClasses.end(); ++i) + { + (*i).second->free((*i).second); + } + entityClasses.clear(); +} + +void Eclass_Clear() +{ + CleanEntityList(g_entityClasses); + g_listTypes.clear(); +} + +EntityClass* EClass_InsertSortedList(EntityClasses& entityClasses, EntityClass *entityClass) +{ + std::pair result = entityClasses.insert(EntityClasses::value_type(entityClass->name(), entityClass)); + if(!result.second) + { + entityClass->free(entityClass); + } + return (*result.first).second; +} + +EntityClass* Eclass_InsertAlphabetized (EntityClass *e) +{ + return EClass_InsertSortedList(g_entityClasses, e); +} + +void Eclass_forEach(EntityClassVisitor& visitor) +{ + for(EntityClasses::iterator i = g_entityClasses.begin(); i != g_entityClasses.end(); ++i) + { + visitor.visit((*i).second); + } +} + + +class RadiantEclassCollector : public EntityClassCollector +{ +public: + void insert(EntityClass* eclass) + { + Eclass_InsertAlphabetized(eclass); + } + void insert(const char* name, const ListAttributeType& list) + { + g_listTypes.insert(ListAttributeTypes::value_type(name, list)); + } +}; + +RadiantEclassCollector g_collector; + +const ListAttributeType* EntityClass_findListType(const char* name) +{ + ListAttributeTypes::iterator i = g_listTypes.find(name); + if(i != g_listTypes.end()) + { + return &(*i).second; + } + return 0; +} + + +class EntityClassFilterMode +{ +public: + bool filter_mp_sp; + const char* mp_ignore_prefix; + const char* sp_ignore_prefix; + + EntityClassFilterMode() : + filter_mp_sp(!string_empty(g_pGameDescription->getKeyValue("eclass_filter_gamemode"))), + mp_ignore_prefix(g_pGameDescription->getKeyValue("eclass_sp_prefix")), + sp_ignore_prefix(g_pGameDescription->getKeyValue("eclass_mp_prefix")) + { + if(string_empty(mp_ignore_prefix)) + { + mp_ignore_prefix = "sp_"; + } + if(string_empty(sp_ignore_prefix)) + { + sp_ignore_prefix = "mp_"; + } + } +}; + +class EntityClassesLoadFile +{ + const EntityClassScanner& scanner; + const char* m_directory; +public: + EntityClassesLoadFile(const EntityClassScanner& scanner, const char* directory) : scanner(scanner), m_directory(directory) + { + } + void operator()(const char* name) const + { + EntityClassFilterMode filterMode; + + if(filterMode.filter_mp_sp) + { + if(string_empty(GlobalRadiant().getGameMode()) || string_equal(GlobalRadiant().getGameMode(), "sp")) + { + if(string_equal_n(name, filterMode.sp_ignore_prefix, strlen(filterMode.sp_ignore_prefix))) + { + globalOutputStream() << "Ignoring '" << name << "'\n"; + return; + } + } + else + { + if(string_equal_n(name, filterMode.mp_ignore_prefix, strlen(filterMode.mp_ignore_prefix))) + { + globalOutputStream() << "Ignoring '" << name << "'\n"; + return; + } + } + } + + // for a given name, we grab the first .def in the vfs + // this allows to override baseq3/scripts/entities.def for instance + StringOutputStream relPath(256); + relPath << m_directory << name; + + scanner.scanFile(g_collector, relPath.c_str()); + } +}; + +struct PathLess +{ + bool operator()(const CopiedString& path, const CopiedString& other) const + { + return path_less(path.c_str(), other.c_str()); + } +}; + +typedef std::map Paths; + +class PathsInsert +{ + Paths& m_paths; + const char* m_directory; +public: + PathsInsert(Paths& paths, const char* directory) : m_paths(paths), m_directory(directory) + { + } + void operator()(const char* name) const + { + m_paths.insert(Paths::value_type(name, m_directory)); + } +}; + + +void EntityClassQuake3_constructDirectory(const char* directory, const char* extension, Paths& paths) +{ + globalOutputStream() << "EntityClass: searching " << makeQuoted(directory) << " for *." << extension << '\n'; + Directory_forEach(directory, matchFileExtension(extension, PathsInsert(paths, directory))); +} + + +void EntityClassQuake3_Construct() +{ + StringOutputStream baseDirectory(256); + StringOutputStream gameDirectory(256); + const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue("basegame"); + const char* gamename = GlobalRadiant().getGameName(); + baseDirectory << GlobalRadiant().getGameToolsPath() << basegame << '/'; + gameDirectory << GlobalRadiant().getGameToolsPath() << gamename << '/'; + + class LoadEntityDefinitionsVisitor : public EClassModules::Visitor + { + const char* baseDirectory; + const char* gameDirectory; + public: + LoadEntityDefinitionsVisitor(const char* baseDirectory, const char* gameDirectory) + : baseDirectory(baseDirectory), gameDirectory(gameDirectory) + { + } + void visit(const char* name, const EntityClassScanner& table) const + { + Paths paths; + EntityClassQuake3_constructDirectory(baseDirectory, table.getExtension(), paths); + if(!string_equal(baseDirectory, gameDirectory)) + { + EntityClassQuake3_constructDirectory(gameDirectory, table.getExtension(), paths); + } + + for(Paths::iterator i = paths.begin(); i != paths.end(); ++i) + { + EntityClassesLoadFile(table, (*i).second)((*i).first.c_str()); + } + } + }; + + EntityClassManager_getEClassModules().foreachModule(LoadEntityDefinitionsVisitor(baseDirectory.c_str(), gameDirectory.c_str())); +} + +EntityClass *Eclass_ForName(const char *name, bool has_brushes) +{ + ASSERT_NOTNULL(name); + + if(string_empty(name)) + { + return eclass_bad; + } + + EntityClasses::iterator i = g_entityClasses.find(name); + if(i != g_entityClasses.end() && string_equal((*i).first, name)) + { + return (*i).second; + } + + EntityClass* e = EntityClass_Create_Default(name, has_brushes); + return Eclass_InsertAlphabetized(e); +} + +class EntityClassQuake3 : public ModuleObserver +{ + std::size_t m_unrealised; + ModuleObservers m_observers; +public: + EntityClassQuake3() : m_unrealised(4) + { + } + void realise() + { + if(--m_unrealised == 0) + { + //globalOutputStream() << "Entity Classes: realise\n"; + EntityClassQuake3_Construct(); + m_observers.realise(); + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + m_observers.unrealise(); + //globalOutputStream() << "Entity Classes: unrealise\n"; + Eclass_Clear(); + } + } + void attach(ModuleObserver& observer) + { + m_observers.attach(observer); + } + void detach(ModuleObserver& observer) + { + m_observers.detach(observer); + } +}; + +EntityClassQuake3 g_EntityClassQuake3; + +void EntityClass_attach(ModuleObserver& observer) +{ + g_EntityClassQuake3.attach(observer); +} +void EntityClass_detach(ModuleObserver& observer) +{ + g_EntityClassQuake3.detach(observer); +} + +void EntityClass_realise() +{ + g_EntityClassQuake3.realise(); +} +void EntityClass_unrealise() +{ + g_EntityClassQuake3.unrealise(); +} + +void EntityClassQuake3_construct() +{ + // start by creating the default unknown eclass + eclass_bad = EClass_Create("UNKNOWN_CLASS", Vector3(0.0f, 0.5f, 0.0f), ""); + + EntityClass_realise(); +} + +void EntityClassQuake3_destroy() +{ + EntityClass_unrealise(); + + eclass_bad->free(eclass_bad); +} + +#include "modulesystem/modulesmap.h" + +class EntityClassQuake3Dependencies : + public GlobalRadiantModuleRef, + public GlobalFileSystemModuleRef, + public GlobalShaderCacheModuleRef +{ + EClassModulesRef m_eclass_modules; +public: + EntityClassQuake3Dependencies() : + m_eclass_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("entityclasstype")) + { + } + EClassModules& getEClassModules() + { + return m_eclass_modules.get(); + } +}; + +class EclassManagerAPI +{ + EntityClassManager m_eclassmanager; +public: + typedef EntityClassManager Type; + STRING_CONSTANT(Name, "quake3"); + + EclassManagerAPI() + { + EntityClassQuake3_construct(); + + m_eclassmanager.findOrInsert = &Eclass_ForName; + m_eclassmanager.findListType = &EntityClass_findListType; + m_eclassmanager.forEach = &Eclass_forEach; + m_eclassmanager.attach = &EntityClass_attach; + m_eclassmanager.detach = &EntityClass_detach; + m_eclassmanager.realise = &EntityClass_realise; + m_eclassmanager.unrealise = &EntityClass_unrealise; + + GlobalRadiant().attachGameToolsPathObserver(g_EntityClassQuake3); + GlobalRadiant().attachGameModeObserver(g_EntityClassQuake3); + GlobalRadiant().attachGameNameObserver(g_EntityClassQuake3); + } + ~EclassManagerAPI() + { + GlobalRadiant().detachGameNameObserver(g_EntityClassQuake3); + GlobalRadiant().detachGameModeObserver(g_EntityClassQuake3); + GlobalRadiant().detachGameToolsPathObserver(g_EntityClassQuake3); + + EntityClassQuake3_destroy(); + } + EntityClassManager* getTable() + { + return &m_eclassmanager; + } +}; + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +typedef SingletonModule EclassManagerModule; +typedef Static StaticEclassManagerModule; +StaticRegisterModule staticRegisterEclassManager(StaticEclassManagerModule::instance()); + +EClassModules& EntityClassManager_getEClassModules() +{ + return StaticEclassManagerModule::instance().getDependencies().getEClassModules(); +} + diff --git a/radiant/eclass.h b/radiant/eclass.h new file mode 100644 index 00000000..48d52ba3 --- /dev/null +++ b/radiant/eclass.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ECLASS_H) +#define INCLUDED_ECLASS_H + +#endif diff --git a/radiant/eclass_def.cpp b/radiant/eclass_def.cpp new file mode 100644 index 00000000..01341ed5 --- /dev/null +++ b/radiant/eclass_def.cpp @@ -0,0 +1,407 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "eclass_def.h" + +#include "iscriplib.h" +#include "ifilesystem.h" +#include "iarchive.h" + +#include "eclasslib.h" +#include "stream/stringstream.h" +#include "stream/textfilestream.h" +#include "modulesystem/moduleregistry.h" +#include "os/path.h" + +const char* EClass_GetExtension() +{ + return "def"; +} +void Eclass_ScanFile (EntityClassCollector& collector, const char *filename); + + +#include "modulesystem/singletonmodule.h" + +class EntityClassDefDependencies : public GlobalShaderCacheModuleRef, public GlobalScripLibModuleRef +{ +}; + +class EclassDefAPI +{ + EntityClassScanner m_eclassdef; +public: + typedef EntityClassScanner Type; + STRING_CONSTANT(Name, "def"); + + EclassDefAPI() + { + m_eclassdef.scanFile = &Eclass_ScanFile; + m_eclassdef.getExtension = &EClass_GetExtension; + } + EntityClassScanner* getTable() + { + return &m_eclassdef; + } +}; + +typedef SingletonModule EclassDefModule; +typedef Static StaticEclassDefModule; +StaticRegisterModule staticRegisterEclassDef(StaticEclassDefModule::instance()); + + +#include "string/string.h" + +#include + + +char com_token[1024]; +bool com_eof; + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +const char *COM_Parse (const char *data) +{ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if (!data) + return 0; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + { + com_eof = true; + return 0; // end of file; + } + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + do + { + c = *data++; + if (c=='\"') + { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } while (1); + } + +// parse single characters + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') + { + com_token[len] = c; + len++; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + com_token[len] = c; + data++; + len++; + c = *data; + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') + break; + } while (c>32); + + com_token[len] = 0; + return data; +} + +const char* Get_COM_Token() +{ + return com_token; +} + + +const char *debugname; + +void setSpecialLoad(EntityClass *e, const char* pWhat, CopiedString& p) +{ + // Hydra: removed some amazingly bad cstring usage, whoever wrote that + // needs to be taken out and shot. + + const char *pText = 0; + const char *where = 0; + + where = strstr(e->comments(),pWhat); + if (!where) + return; + + pText = where + strlen(pWhat); + if (*pText == '\"') + pText++; + + where = strchr(pText,'\"'); + if (where) + { + p = StringRange(pText, where); + } + else + { + p = pText; + } +} + +#include "eclasslib.h" + +/* + +the classname, color triple, and bounding box are parsed out of comments +A ? size means take the exact brush size. + +/ *QUAKED (0 0 0) ? +/ *QUAKED (0 0 0) (-8 -8 -8) (8 8 8) + +Flag names can follow the size description: + +/ *QUAKED func_door (0 .5 .8) ? START_OPEN STONE_SOUND DOOR_DONT_LINK GOLD_KEY SILVER_KEY + +*/ + +EntityClass *Eclass_InitFromText (const char *text) +{ + EntityClass* e = Eclass_Alloc(); + e->free = &Eclass_Free; + + // grab the name + text = COM_Parse (text); + e->m_name = Get_COM_Token(); + debugname = e->name(); + + { + // grab the color, reformat as texture name + int r = sscanf (text," (%f %f %f)", &e->color[0], &e->color[1], &e->color[2]); + if (r != 3) + return e; + eclass_capture_state(e); + } + + while (*text != ')') + { + if (!*text) { + return 0; + } + text++; + } + text++; + + // get the size + text = COM_Parse (text); + if (Get_COM_Token()[0] == '(') + { // parse the size as two vectors + e->fixedsize = true; + int r = sscanf (text,"%f %f %f) (%f %f %f)", &e->mins[0], &e->mins[1], &e->mins[2], + &e->maxs[0], &e->maxs[1], &e->maxs[2]); + if (r != 6) { + return 0; + } + + for (int i=0 ; i<2 ; i++) + { + while (*text != ')') + { + if (!*text) { + return 0; + } + text++; + } + text++; + } + } + + char parms[256]; + // get the flags + { + // copy to the first /n + char* p = parms; + while (*text && *text != '\n') + *p++ = *text++; + *p = 0; + text++; + } + + { + // any remaining words are parm flags + const char* p = parms; + for (std::size_t i=0 ; iflagnames[i], Get_COM_Token()); + } + } + + e->m_comments = text; + + setSpecialLoad(e, "model=", e->m_modelpath); + StringOutputStream buffer(string_length(e->m_modelpath.c_str())); + buffer << PathCleaned(e->m_modelpath.c_str()); + e->m_modelpath = buffer.c_str(); + + if(!e->fixedsize) + { + EntityClass_insertAttribute(*e, "angle", EntityClassAttribute("direction", "Direction", "0")); + } + else + { + EntityClass_insertAttribute(*e, "angle", EntityClassAttribute("angle", "Yaw Angle", "0")); + } + EntityClass_insertAttribute(*e, "model", EntityClassAttribute("model", "Model")); + EntityClass_insertAttribute(*e, "noise", EntityClassAttribute("sound", "Sound")); + + return e; +} + +void Eclass_ScanFile (EntityClassCollector& collector, const char *filename) +{ + EntityClass *e; + + TextFileInputStream inputFile(filename); + if(inputFile.failed()) + { + globalErrorStream() << "ScanFile: " << filename << " not found\n"; + return; + } + globalOutputStream() << "ScanFile: " << filename << "\n"; + + enum EParserState + { + eParseDefault, + eParseSolidus, + eParseComment, + eParseQuakeED, + eParseEntityClass, + eParseEntityClassEnd, + } state = eParseDefault; + const char* quakeEd = "QUAKED"; + const char* p = 0; + StringBuffer buffer; + SingleCharacterInputStream bufferedInput(inputFile); + for(;;) + { + char c; + if(!bufferedInput.readChar(c)) + { + break; + } + + switch(state) + { + case eParseDefault: + if(c == '/') + { + state = eParseSolidus; + } + break; + case eParseSolidus: + if(c == '/') + { + state = eParseComment; + } + else if(c == '*') + { + p = quakeEd; + state = eParseQuakeED; + } + break; + case eParseComment: + if(c == '\n') + { + state = eParseDefault; + } + break; + case eParseQuakeED: + if(c == *p) + { + if(*(++p) == '\0') + { + state = eParseEntityClass; + } + } + else + { + state = eParseDefault; + } + break; + case eParseEntityClass: + if(c == '*') + { + state = eParseEntityClassEnd; + } + else + { + buffer.push_back(c); + } + break; + case eParseEntityClassEnd: + if(c == '/') + { + e = Eclass_InitFromText(buffer.c_str()); + state = eParseDefault; + if (e) + collector.insert(e); + else + globalErrorStream() << "Error parsing: " << debugname << " in " << filename << "\n"; + + buffer.clear(); + state = eParseDefault; + } + else + { + buffer.push_back('*'); + buffer.push_back(c); + state = eParseEntityClass; + } + break; + } + } +} diff --git a/radiant/eclass_def.h b/radiant/eclass_def.h new file mode 100644 index 00000000..23a5ef4d --- /dev/null +++ b/radiant/eclass_def.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ECLASS_DEF_H) +#define INCLUDED_ECLASS_DEF_H + +#endif diff --git a/radiant/eclass_doom3.cpp b/radiant/eclass_doom3.cpp new file mode 100644 index 00000000..217c4ec5 --- /dev/null +++ b/radiant/eclass_doom3.cpp @@ -0,0 +1,929 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "eclass_doom3.h" + +#include "debugging/debugging.h" + +#include + +#include "ifilesystem.h" +#include "iscriplib.h" +#include "iarchive.h" +#include "qerplugin.h" + +#include "generic/callback.h" +#include "string/string.h" +#include "eclasslib.h" +#include "os/path.h" +#include "os/dir.h" +#include "stream/stringstream.h" +#include "moduleobservers.h" +#include "stringio.h" + +class RawString +{ + const char* m_value; +public: + RawString(const char* value) : m_value(value) + { + } + const char* c_str() const + { + return m_value; + } +}; + +inline bool operator<(const RawString& self, const RawString& other) +{ + return string_less_nocase(self.c_str(), other.c_str()); +} + +typedef std::map EntityClasses; +EntityClasses g_EntityClassDoom3_classes; +EntityClass *g_EntityClassDoom3_bad = 0; + + +void EntityClassDoom3_clear() +{ + for(EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i) + { + (*i).second->free((*i).second); + } + g_EntityClassDoom3_classes.clear(); +} + +// entityClass will be inserted only if another of the same name does not already exist. +// if entityClass was inserted, the same object is returned, otherwise the already-existing object is returned. +EntityClass* EntityClassDoom3_insertUnique(EntityClass* entityClass) +{ + return (*g_EntityClassDoom3_classes.insert(EntityClasses::value_type(entityClass->name(), entityClass)).first).second; +} + +void EntityClassDoom3_forEach(EntityClassVisitor& visitor) +{ + for(EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i) + { + visitor.visit((*i).second); + } +} + +inline void printParseError(const char* message) +{ + globalErrorStream() << message; +} + +#define PARSE_RETURN_FALSE_IF_FAIL(expression) if(!(expression)) { printParseError(FILE_LINE "\nparse failed: " #expression "\n"); return false; } else + +bool EntityClassDoom3_parseToken(Tokeniser& tokeniser) +{ + const char* token = tokeniser.getToken(); + PARSE_RETURN_FALSE_IF_FAIL(token != 0); + return true; +} + +bool EntityClassDoom3_parseToken(Tokeniser& tokeniser, const char* string) +{ + const char* token = tokeniser.getToken(); + PARSE_RETURN_FALSE_IF_FAIL(token != 0); + return string_equal(token, string); +} + +bool EntityClassDoom3_parseString(Tokeniser& tokeniser, const char*& s) +{ + const char* token = tokeniser.getToken(); + PARSE_RETURN_FALSE_IF_FAIL(token != 0); + s = token; + return true; +} + +bool EntityClassDoom3_parseString(Tokeniser& tokeniser, CopiedString& s) +{ + const char* token = tokeniser.getToken(); + PARSE_RETURN_FALSE_IF_FAIL(token != 0); + s = token; + return true; +} + +bool EntityClassDoom3_parseString(Tokeniser& tokeniser, StringOutputStream& s) +{ + const char* token = tokeniser.getToken(); + PARSE_RETURN_FALSE_IF_FAIL(token != 0); + s << token; + return true; +} + +bool EntityClassDoom3_parseUnknown(Tokeniser& tokeniser) +{ + //const char* name = + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); + + //globalOutputStream() << "parsing unknown block " << makeQuoted(name) << "\n"; + + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "{")); + tokeniser.nextLine(); + + std::size_t depth = 1; + for(;;) + { + const char* token; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token)); + if(string_equal(token, "}")) + { + if(--depth == 0) + { + tokeniser.nextLine(); + break; + } + } + else if(string_equal(token, "{")) + { + ++depth; + } + tokeniser.nextLine(); + } + return true; +} + + +class Model +{ +public: + bool m_resolved; + CopiedString m_mesh; + CopiedString m_skin; + CopiedString m_parent; + typedef std::map Anims; + Anims m_anims; + Model() : m_resolved(false) + { + } +}; + +typedef std::map Models; + +Models g_models; + +void Model_resolveInheritance(const char* name, Model& model) +{ + if(model.m_resolved == false) + { + model.m_resolved = true; + + if(!string_empty(model.m_parent.c_str())) + { + Models::iterator i = g_models.find(model.m_parent); + if(i == g_models.end()) + { + globalErrorStream() << "model " << name << " inherits unknown model " << model.m_parent.c_str() << "\n"; + } + else + { + Model_resolveInheritance((*i).first.c_str(), (*i).second); + model.m_mesh = (*i).second.m_mesh; + model.m_skin = (*i).second.m_skin; + } + } + } +} + +bool EntityClassDoom3_parseModel(Tokeniser& tokeniser) +{ + const char* name; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, name)); + + Model& model = g_models[name]; + + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "{")); + tokeniser.nextLine(); + + for(;;) + { + const char* parameter; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, parameter)); + if(string_equal(parameter, "}")) + { + tokeniser.nextLine(); + break; + } + else if(string_equal(parameter, "inherit")) + { + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, model.m_parent)); + tokeniser.nextLine(); + } + else if(string_equal(parameter, "remove")) + { + //const char* remove = + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); + tokeniser.nextLine(); + } + else if(string_equal(parameter, "mesh")) + { + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, model.m_mesh)); + tokeniser.nextLine(); + } + else if(string_equal(parameter, "skin")) + { + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, model.m_skin)); + tokeniser.nextLine(); + } + else if(string_equal(parameter, "offset")) + { + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "(")); + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, ")")); + tokeniser.nextLine(); + } + else if(string_equal(parameter, "channel")) + { + //const char* channelName = + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "(")); + for(;;) + { + const char* end; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, end)); + if(string_equal(end, ")")) + { + tokeniser.nextLine(); + break; + } + } + } + else if(string_equal(parameter, "anim")) + { + CopiedString animName; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, animName)); + const char* animFile; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, animFile)); + model.m_anims.insert(Model::Anims::value_type(animName, animFile)); + + const char* token; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token)); + + while(string_equal(token, ",")) + { + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, animFile)); + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token)); + } + + if(string_equal(token, "{")) + { + for(;;) + { + const char* end; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, end)); + if(string_equal(end, "}")) + { + tokeniser.nextLine(); + break; + } + tokeniser.nextLine(); + } + } + else + { + tokeniser.ungetToken(); + } + } + else + { + globalErrorStream() << "unknown model parameter: " << makeQuoted(parameter) << "\n"; + return false; + } + tokeniser.nextLine(); + } + return true; +} + +inline bool char_isSpaceOrTab(char c) +{ + return c == ' ' || c == '\t'; +} + +inline bool char_isNotSpaceOrTab(char c) +{ + return !char_isSpaceOrTab(c); +} + +template +inline const char* string_find_if(const char* string, Predicate predicate) +{ + for(; *string != 0; ++string) + { + if(predicate(*string)) + { + return string; + } + } + return string; +} + +inline const char* string_findFirstSpaceOrTab(const char* string) +{ + return string_find_if(string, char_isSpaceOrTab); +} + +inline const char* string_findFirstNonSpaceOrTab(const char* string) +{ + return string_find_if(string, char_isNotSpaceOrTab); +} + + +static bool EntityClass_parse(EntityClass& entityClass, Tokeniser& tokeniser) +{ + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, entityClass.m_name)); + + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "{")); + tokeniser.nextLine(); + + StringOutputStream usage(256); + StringOutputStream description(256); + CopiedString* currentDescription = 0; + StringOutputStream* currentString = 0; + + for(;;) + { + const char* key; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, key)); + + const char* last = string_findFirstSpaceOrTab(key); + CopiedString first(StringRange(key, last)); + + if(!string_empty(last)) + { + last = string_findFirstNonSpaceOrTab(last); + } + + if(currentString != 0 && string_equal(key, "\\")) + { + tokeniser.nextLine(); + *currentString << " "; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, *currentString)); + continue; + } + + if(currentDescription != 0) + { + *currentDescription = description.c_str(); + description.clear(); + currentDescription = 0; + } + currentString = 0; + + if(string_equal(key, "}")) + { + tokeniser.nextLine(); + break; + } + else if(string_equal(key, "model")) + { + const char* token; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token)); + entityClass.fixedsize = true; + StringOutputStream buffer(256); + buffer << PathCleaned(token); + entityClass.m_modelpath = buffer.c_str(); + } + else if(string_equal(key, "editor_color")) + { + const char* value; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value)); + if(!string_empty(value)) + { + entityClass.colorSpecified = true; + bool success = string_parse_vector3(value, entityClass.color); + ASSERT_MESSAGE(success, "editor_color: parse error"); + } + } + else if(string_equal(key, "editor_ragdoll")) + { + //bool ragdoll = atoi(tokeniser.getToken()) != 0; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); + } + else if(string_equal(key, "editor_mins")) + { + entityClass.sizeSpecified = true; + const char* value; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value)); + if(!string_empty(value) && !string_equal(value, "?")) + { + entityClass.fixedsize = true; + bool success = string_parse_vector3(value, entityClass.mins); + ASSERT_MESSAGE(success, "editor_mins: parse error"); + } + } + else if(string_equal(key, "editor_maxs")) + { + entityClass.sizeSpecified = true; + const char* value; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value)); + if(!string_empty(value) && !string_equal(value, "?")) + { + entityClass.fixedsize = true; + bool success = string_parse_vector3(value, entityClass.maxs); + ASSERT_MESSAGE(success, "editor_maxs: parse error"); + } + } + else if(string_equal(key, "editor_usage")) + { + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, usage)); + currentString = &usage; + } + else if(string_equal_n(key, "editor_usage", 12)) + { + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, usage)); + currentString = &usage; + } + else if(string_equal(key, "editor_rotatable") + || string_equal(key, "editor_showangle") + || string_equal(key, "editor_showangles") // typo? in prey movables.def + || string_equal(key, "editor_mover") + || string_equal(key, "editor_model") + || string_equal(key, "editor_material") + || string_equal(key, "editor_combatnode") + || (!string_empty(last) && string_equal(first.c_str(), "editor_gui")) + || string_equal_n(key, "editor_copy", 11)) + { + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); + } + else if(!string_empty(last) && (string_equal(first.c_str(), "editor_var") || string_equal(first.c_str(), "editor_string"))) + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; + attribute.m_type = "string"; + currentDescription = &attribute.m_description; + currentString = &description; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); + } + else if(!string_empty(last) && string_equal(first.c_str(), "editor_float")) + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; + attribute.m_type = "string"; + currentDescription = &attribute.m_description; + currentString = &description; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); + } + else if(!string_empty(last) && string_equal(first.c_str(), "editor_snd")) + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; + attribute.m_type = "sound"; + currentDescription = &attribute.m_description; + currentString = &description; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); + } + else if(!string_empty(last) && string_equal(first.c_str(), "editor_bool")) + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; + attribute.m_type = "boolean"; + currentDescription = &attribute.m_description; + currentString = &description; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); + } + else if(!string_empty(last) && string_equal(first.c_str(), "editor_int")) + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; + attribute.m_type = "integer"; + currentDescription = &attribute.m_description; + currentString = &description; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); + } + else if(!string_empty(last) && string_equal(first.c_str(), "editor_model")) + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; + attribute.m_type = "model"; + currentDescription = &attribute.m_description; + currentString = &description; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); + } + else if(!string_empty(last) && string_equal(first.c_str(), "editor_color")) + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; + attribute.m_type = "color"; + currentDescription = &attribute.m_description; + currentString = &description; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); + } + else if(!string_empty(last) && (string_equal(first.c_str(), "editor_material") || string_equal(first.c_str(), "editor_mat"))) + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second; + attribute.m_type = "shader"; + currentDescription = &attribute.m_description; + currentString = &description; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description)); + } + else if(string_equal(key, "inherit")) + { + entityClass.inheritanceResolved = false; + ASSERT_MESSAGE(entityClass.m_parent.empty(), "only one 'inherit' supported per entityDef"); + const char* token; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token)); + entityClass.m_parent.push_back(token); + } + // begin quake4-specific keys + else if(string_equal(key, "editor_targetonsel")) + { + //const char* value = + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); + } + else if(string_equal(key, "editor_menu")) + { + //const char* value = + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); + } + else if(string_equal(key, "editor_ignore")) + { + //const char* value = + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); + } + // end quake4-specific keys + // begin ignore prey (unknown/unused?) entity keys + else if(string_equal(key, "editor_light") + || string_equal(key, "editor_def def_debrisspawner") + || string_equal(key, "editor_def def_drop") + || string_equal(key, "editor_def def_guihand") + || string_equal(key, "editor_def def_mine")) + { + //const char* value = + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser)); + } + // end ignore prey entity keys + else + { + CopiedString tmp(key); + ASSERT_MESSAGE(!string_equal_n(key, "editor_", 7), "unsupported editor key: " << makeQuoted(key)); + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, key).second; + attribute.m_type = "string"; + const char* value; + PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value)); + if(string_equal(value, "}")) // hack for quake4 powerups.def bug + { + globalErrorStream() << "entityDef " << makeQuoted(entityClass.m_name.c_str()) << " key " << makeQuoted(tmp.c_str()) << " has no value\n"; + break; + } + else + { + attribute.m_value = value; + } + } + tokeniser.nextLine(); + } + + entityClass.m_comments = usage.c_str(); + + if(string_equal(entityClass.m_name.c_str(), "light")) + { + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "light_radius").second; + attribute.m_type = "vector3"; + attribute.m_value = "300 300 300"; + } + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "light_center").second; + attribute.m_type = "vector3"; + } + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "noshadows").second; + attribute.m_type = "boolean"; + attribute.m_value = "0"; + } + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "nospecular").second; + attribute.m_type = "boolean"; + attribute.m_value = "0"; + } + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "nodiffuse").second; + attribute.m_type = "boolean"; + attribute.m_value = "0"; + } + { + EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "falloff").second; + attribute.m_type = "real"; + } + } + + return true; +} + +bool EntityClassDoom3_parseEntityDef(Tokeniser& tokeniser) +{ + EntityClass* entityClass = Eclass_Alloc(); + entityClass->free = &Eclass_Free; + + if(!EntityClass_parse(*entityClass, tokeniser)) + { + eclass_capture_state(entityClass); // finish constructing the entity so that it can be destroyed cleanly. + entityClass->free(entityClass); + return false; + } + + EntityClass* inserted = EntityClassDoom3_insertUnique(entityClass); + if(inserted != entityClass) + { + globalErrorStream() << "entityDef " << entityClass->name() << " is already defined, second definition ignored\n"; + eclass_capture_state(entityClass); // finish constructing the entity so that it can be destroyed cleanly. + entityClass->free(entityClass); + } + return true; +} + +bool EntityClassDoom3_parseBlock(Tokeniser& tokeniser, const char* blockType) +{ + if(string_equal(blockType, "entityDef")) + { + return EntityClassDoom3_parseEntityDef(tokeniser); + } + else if(string_equal(blockType, "model")) + { + return EntityClassDoom3_parseModel(tokeniser); + } + else + { + return EntityClassDoom3_parseUnknown(tokeniser); + } +} + +bool EntityClassDoom3_parse(TextInputStream& inputStream, const char* filename) +{ + Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(inputStream); + + tokeniser.nextLine(); + + for(;;) + { + const char* blockType = tokeniser.getToken(); + if(blockType == 0) + { + return true; + } + CopiedString tmp(blockType); + if(!EntityClassDoom3_parseBlock(tokeniser, tmp.c_str())) + { + globalErrorStream() << GlobalFileSystem().findFile(filename) << filename << ":" << (unsigned int)tokeniser.getLine() << ": " << tmp.c_str() << " parse failed, skipping rest of file\n"; + return false; + } + } + + tokeniser.release(); +} + + +void EntityClassDoom3_loadFile(const char* filename) +{ + globalOutputStream() << "parsing entity classes from " << makeQuoted(filename) << "\n"; + + StringOutputStream fullname(256); + fullname << "def/" << filename; + + ArchiveTextFile* file = GlobalFileSystem().openTextFile(fullname.c_str()); + if(file != 0) + { + EntityClassDoom3_parse(file->getInputStream(), fullname.c_str()); + file->release(); + } +} + +EntityClass* EntityClassDoom3_findOrInsert(const char *name, bool has_brushes) +{ + ASSERT_NOTNULL(name); + + if(string_empty(name)) + { + return g_EntityClassDoom3_bad; + } + + EntityClasses::iterator i = g_EntityClassDoom3_classes.find(name); + if(i != g_EntityClassDoom3_classes.end() + //&& string_equal((*i).first, name) + ) + { + return (*i).second; + } + + EntityClass* e = EntityClass_Create_Default(name, has_brushes); + EntityClass* inserted = EntityClassDoom3_insertUnique(e); + ASSERT_MESSAGE(inserted == e, ""); + return inserted; +} + +const ListAttributeType* EntityClassDoom3_findListType(const char* name) +{ + return 0; +} + + +void EntityClass_resolveInheritance(EntityClass* derivedClass) +{ + if(derivedClass->inheritanceResolved == false) + { + derivedClass->inheritanceResolved = true; + EntityClasses::iterator i = g_EntityClassDoom3_classes.find(derivedClass->m_parent.front().c_str()); + if(i == g_EntityClassDoom3_classes.end()) + { + globalErrorStream() << "failed to find entityDef " << makeQuoted(derivedClass->m_parent.front().c_str()) << " inherited by " << makeQuoted(derivedClass->m_name.c_str()) << "\n"; + } + else + { + EntityClass* parentClass = (*i).second; + EntityClass_resolveInheritance(parentClass); + if(!derivedClass->colorSpecified) + { + derivedClass->colorSpecified = parentClass->colorSpecified; + derivedClass->color = parentClass->color; + } + if(!derivedClass->sizeSpecified) + { + derivedClass->sizeSpecified = parentClass->sizeSpecified; + derivedClass->mins = parentClass->mins; + derivedClass->maxs = parentClass->maxs; + derivedClass->fixedsize = parentClass->fixedsize; + } + + for(EntityClassAttributes::iterator j = parentClass->m_attributes.begin(); j != parentClass->m_attributes.end(); ++j) + { + EntityClass_insertAttribute(*derivedClass, (*j).first.c_str(), (*j).second); + } + } + } +} + +class EntityClassDoom3 : public ModuleObserver +{ + std::size_t m_unrealised; + ModuleObservers m_observers; +public: + EntityClassDoom3() : m_unrealised(2) + { + } + void realise() + { + if(--m_unrealised == 0) + { + globalOutputStream() << "searching vfs directory " << makeQuoted("def") << " for *.def\n"; + GlobalFileSystem().forEachFile("def/", "def", FreeCaller1()); + + { + for(Models::iterator i = g_models.begin(); i != g_models.end(); ++i) + { + Model_resolveInheritance((*i).first.c_str(), (*i).second); + } + } + { + for(EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i) + { + EntityClass_resolveInheritance((*i).second); + if(!string_empty((*i).second->m_modelpath.c_str())) + { + Models::iterator j = g_models.find((*i).second->m_modelpath); + if(j != g_models.end()) + { + (*i).second->m_modelpath = (*j).second.m_mesh; + (*i).second->m_skin = (*j).second.m_skin; + } + } + eclass_capture_state((*i).second); + + StringOutputStream usage(256); + + usage << "-------- NOTES --------\n"; + + if(!string_empty((*i).second->m_comments.c_str())) + { + usage << (*i).second->m_comments.c_str() << "\n"; + } + + usage << "\n-------- KEYS --------\n"; + + for(EntityClassAttributes::iterator j = (*i).second->m_attributes.begin(); j != (*i).second->m_attributes.end(); ++j) + { + const char* name = EntityClassAttributePair_getName(*j); + const char* description = EntityClassAttributePair_getDescription(*j); + if(!string_equal(name, description)) + { + usage << EntityClassAttributePair_getName(*j) << " : " << EntityClassAttributePair_getDescription(*j) << "\n"; + } + } + + (*i).second->m_comments = usage.c_str(); + } + } + + m_observers.realise(); + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + m_observers.unrealise(); + EntityClassDoom3_clear(); + } + } + void attach(ModuleObserver& observer) + { + m_observers.attach(observer); + } + void detach(ModuleObserver& observer) + { + m_observers.detach(observer); + } +}; + +EntityClassDoom3 g_EntityClassDoom3; + +void EntityClassDoom3_attach(ModuleObserver& observer) +{ + g_EntityClassDoom3.attach(observer); +} +void EntityClassDoom3_detach(ModuleObserver& observer) +{ + g_EntityClassDoom3.detach(observer); +} + +void EntityClassDoom3_realise() +{ + g_EntityClassDoom3.realise(); +} +void EntityClassDoom3_unrealise() +{ + g_EntityClassDoom3.unrealise(); +} + +void EntityClassDoom3_construct() +{ + GlobalFileSystem().attach(g_EntityClassDoom3); + + // start by creating the default unknown eclass + g_EntityClassDoom3_bad = EClass_Create("UNKNOWN_CLASS", Vector3(0.0f, 0.5f, 0.0f), ""); + + EntityClassDoom3_realise(); +} + +void EntityClassDoom3_destroy() +{ + EntityClassDoom3_unrealise(); + + g_EntityClassDoom3_bad->free(g_EntityClassDoom3_bad); + + GlobalFileSystem().detach(g_EntityClassDoom3); +} + +class EntityClassDoom3Dependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef +{ +}; + +class EntityClassDoom3API +{ + EntityClassManager m_eclassmanager; +public: + typedef EntityClassManager Type; + STRING_CONSTANT(Name, "doom3"); + + EntityClassDoom3API() + { + EntityClassDoom3_construct(); + + m_eclassmanager.findOrInsert = &EntityClassDoom3_findOrInsert; + m_eclassmanager.findListType = &EntityClassDoom3_findListType; + m_eclassmanager.forEach = &EntityClassDoom3_forEach; + m_eclassmanager.attach = &EntityClassDoom3_attach; + m_eclassmanager.detach = &EntityClassDoom3_detach; + m_eclassmanager.realise = &EntityClassDoom3_realise; + m_eclassmanager.unrealise = &EntityClassDoom3_unrealise; + } + ~EntityClassDoom3API() + { + EntityClassDoom3_destroy(); + } + EntityClassManager* getTable() + { + return &m_eclassmanager; + } +}; + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +typedef SingletonModule EntityClassDoom3Module; +typedef Static StaticEntityClassDoom3Module; +StaticRegisterModule staticRegisterEntityClassDoom3(StaticEntityClassDoom3Module::instance()); diff --git a/radiant/eclass_doom3.h b/radiant/eclass_doom3.h new file mode 100644 index 00000000..7c8169cb --- /dev/null +++ b/radiant/eclass_doom3.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ECLASS_DOOM3_H) +#define INCLUDED_ECLASS_DOOM3_H + +#endif diff --git a/radiant/eclass_fgd.cpp b/radiant/eclass_fgd.cpp new file mode 100644 index 00000000..6d5fb663 --- /dev/null +++ b/radiant/eclass_fgd.cpp @@ -0,0 +1,807 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "eclass_fgd.h" + +#include "debugging/debugging.h" + +#include + +#include "ifilesystem.h" +#include "iscriplib.h" +#include "qerplugin.h" + +#include "string/string.h" +#include "eclasslib.h" +#include "os/path.h" +#include "os/dir.h" +#include "stream/stringstream.h" +#include "moduleobservers.h" +#include "stringio.h" +#include "stream/textfilestream.h" + +namespace +{ + typedef std::map EntityClasses; + EntityClasses g_EntityClassFGD_classes; + typedef std::map BaseClasses; + BaseClasses g_EntityClassFGD_bases; + EntityClass *g_EntityClassFGD_bad = 0; + typedef std::map ListAttributeTypes; + ListAttributeTypes g_listTypesFGD; +} + + +void EntityClassFGD_clear() +{ + for(BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i) + { + (*i).second->free((*i).second); + } + g_EntityClassFGD_bases.clear(); + g_listTypesFGD.clear(); +} + +EntityClass* EntityClassFGD_insertUniqueBase(EntityClass* entityClass) +{ + std::pair result = g_EntityClassFGD_bases.insert(BaseClasses::value_type(entityClass->name(), entityClass)); + if(!result.second) + { + globalErrorStream() << "duplicate base class: " << makeQuoted(entityClass->name()) << "\n"; + //eclass_capture_state(entityClass); + //entityClass->free(entityClass); + } + return (*result.first).second; +} + +EntityClass* EntityClassFGD_insertUnique(EntityClass* entityClass) +{ + EntityClassFGD_insertUniqueBase(entityClass); + std::pair result = g_EntityClassFGD_classes.insert(EntityClasses::value_type(entityClass->name(), entityClass)); + if(!result.second) + { + globalErrorStream() << "duplicate entity class: " << makeQuoted(entityClass->name()) << "\n"; + eclass_capture_state(entityClass); + entityClass->free(entityClass); + } + return (*result.first).second; +} + +void EntityClassFGD_forEach(EntityClassVisitor& visitor) +{ + for(EntityClasses::iterator i = g_EntityClassFGD_classes.begin(); i != g_EntityClassFGD_classes.end(); ++i) + { + visitor.visit((*i).second); + } +} + +inline bool EntityClassFGD_parseToken(Tokeniser& tokeniser, const char* token) +{ + return string_equal(tokeniser.getToken(), token); +} + +#define PARSE_ERROR "error parsing entity class definition" + +void EntityClassFGD_parseSplitString(Tokeniser& tokeniser, CopiedString& string) +{ + StringOutputStream buffer(256); + for(;;) + { + buffer << tokeniser.getToken(); + if(!string_equal(tokeniser.getToken(), "+")) + { + tokeniser.ungetToken(); + string = buffer.c_str(); + return; + } + } +} + +void EntityClassFGD_parseClass(Tokeniser& tokeniser, bool fixedsize, bool isBase) +{ + EntityClass* entityClass = Eclass_Alloc(); + entityClass->free = &Eclass_Free; + entityClass->fixedsize = fixedsize; + entityClass->inheritanceResolved = false; + entityClass->mins = Vector3(-8, -8, -8); + entityClass->maxs = Vector3(8, 8, 8); + + for(;;) + { + const char* property = tokeniser.getToken(); + if(string_equal(property, "=")) + { + break; + } + else if(string_equal(property, "base")) + { + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR); + for(;;) + { + const char* base = tokeniser.getToken(); + if(string_equal(base, ")")) + { + break; + } + else if(!string_equal(base, ",")) + { + entityClass->m_parent.push_back(base); + } + } + } + else if(string_equal(property, "size")) + { + entityClass->sizeSpecified = true; + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR); + Tokeniser_getFloat(tokeniser, entityClass->mins.x()); + Tokeniser_getFloat(tokeniser, entityClass->mins.y()); + Tokeniser_getFloat(tokeniser, entityClass->mins.z()); + const char* token = tokeniser.getToken(); + if(string_equal(token, ",")) + { + Tokeniser_getFloat(tokeniser, entityClass->maxs.x()); + Tokeniser_getFloat(tokeniser, entityClass->maxs.y()); + Tokeniser_getFloat(tokeniser, entityClass->maxs.z()); + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR); + } + else + { + entityClass->maxs = entityClass->mins; + vector3_negate(entityClass->mins); + ASSERT_MESSAGE(string_equal(token, ")"), ""); + } + } + else if(string_equal(property, "color")) + { + entityClass->colorSpecified = true; + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR); + Tokeniser_getFloat(tokeniser, entityClass->color.x()); + entityClass->color.x() /= 256.0; + Tokeniser_getFloat(tokeniser, entityClass->color.y()); + entityClass->color.y() /= 256.0; + Tokeniser_getFloat(tokeniser, entityClass->color.z()); + entityClass->color.z() /= 256.0; + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR); + } + else if(string_equal(property, "iconsprite")) + { + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR); + StringOutputStream buffer(256); + buffer << PathCleaned(tokeniser.getToken()); + entityClass->m_modelpath = buffer.c_str(); + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR); + } + else if(string_equal(property, "sprite") + || string_equal(property, "decal") + // hl2 below + || string_equal(property, "overlay") + || string_equal(property, "light") + || string_equal(property, "keyframe") + || string_equal(property, "animator") + || string_equal(property, "quadbounds")) + { + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR); + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR); + } + // hl2 below + else if(string_equal(property, "sphere") + || string_equal(property, "sweptplayerhull") + || string_equal(property, "studio") + || string_equal(property, "studioprop") + || string_equal(property, "lightprop") + || string_equal(property, "lightcone") + || string_equal(property, "sidelist")) + { + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR); + if(string_equal(tokeniser.getToken(), ")")) + { + tokeniser.ungetToken(); + } + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR); + } + else if(string_equal(property, "line") + || string_equal(property, "cylinder")) + { + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR); + //const char* r = + tokeniser.getToken(); + //const char* g = + tokeniser.getToken(); + //const char* b = + tokeniser.getToken(); + for(;;) + { + if(string_equal(tokeniser.getToken(), ")")) + { + tokeniser.ungetToken(); + break; + } + //const char* name = + tokeniser.getToken(); + } + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR); + } + else if(string_equal(property, "wirebox")) + { + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR); + //const char* mins = + tokeniser.getToken(); + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ","), PARSE_ERROR); + //const char* maxs = + tokeniser.getToken(); + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR); + } + else if(string_equal(property, "halfgridsnap")) + { + } + else + { + ERROR_MESSAGE(PARSE_ERROR); + } + } + + entityClass->m_name = tokeniser.getToken(); + + if(!isBase) + { + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR); + + EntityClassFGD_parseSplitString(tokeniser, entityClass->m_comments); + } + + tokeniser.nextLine(); + + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "["), PARSE_ERROR); + + tokeniser.nextLine(); + + for(;;) + { + CopiedString key = tokeniser.getToken(); + if(string_equal(key.c_str(), "]")) + { + tokeniser.nextLine(); + break; + } + + if(string_equal_nocase(key.c_str(), "input") + || string_equal_nocase(key.c_str(), "output")) + { + const char* name = tokeniser.getToken(); + if(!string_equal(name, "(")) + { + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR); + //const char* type = + tokeniser.getToken(); + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR); + const char* descriptionSeparator = tokeniser.getToken(); + if(string_equal(descriptionSeparator, ":")) + { + CopiedString description; + EntityClassFGD_parseSplitString(tokeniser, description); + } + else + { + tokeniser.ungetToken(); + } + tokeniser.nextLine(); + continue; + } + } + + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR); + CopiedString type = tokeniser.getToken(); + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR); + + if(string_equal_nocase(type.c_str(), "flags")) + { + EntityClassAttribute attribute; + + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "="), PARSE_ERROR); + tokeniser.nextLine(); + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "["), PARSE_ERROR); + tokeniser.nextLine(); + for(;;) + { + const char* flag = tokeniser.getToken(); + if(string_equal(flag, "]")) + { + tokeniser.nextLine(); + break; + } + else + { + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR); + //const char* name = + tokeniser.getToken(); + { + const char* defaultSeparator = tokeniser.getToken(); + if(string_equal(defaultSeparator, ":")) + { + tokeniser.getToken(); + { + const char* descriptionSeparator = tokeniser.getToken(); + if(string_equal(descriptionSeparator, ":")) + { + EntityClassFGD_parseSplitString(tokeniser, attribute.m_description); + } + else + { + tokeniser.ungetToken(); + } + } + } + else + { + tokeniser.ungetToken(); + } + } + } + tokeniser.nextLine(); + } + EntityClass_insertAttribute(*entityClass, key.c_str(), attribute); + } + else if(string_equal_nocase(type.c_str(), "choices")) + { + EntityClassAttribute attribute; + + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR); + attribute.m_name = tokeniser.getToken(); + const char* valueSeparator = tokeniser.getToken(); + if(string_equal(valueSeparator, ":")) + { + const char* value = tokeniser.getToken(); + if(!string_equal(value, ":")) + { + attribute.m_value = value; + } + else + { + tokeniser.ungetToken(); + } + { + const char* descriptionSeparator = tokeniser.getToken(); + if(string_equal(descriptionSeparator, ":")) + { + EntityClassFGD_parseSplitString(tokeniser, attribute.m_description); + } + else + { + tokeniser.ungetToken(); + } + } + } + else + { + tokeniser.ungetToken(); + } + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "="), PARSE_ERROR); + tokeniser.nextLine(); + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "["), PARSE_ERROR); + tokeniser.nextLine(); + + StringOutputStream listTypeName(64); + listTypeName << entityClass->m_name.c_str() << "_" << attribute.m_name.c_str(); + attribute.m_type = listTypeName.c_str(); + + ListAttributeType& listType = g_listTypesFGD[listTypeName.c_str()]; + + for(;;) + { + const char* value = tokeniser.getToken(); + if(string_equal(value, "]")) + { + tokeniser.nextLine(); + break; + } + else + { + CopiedString tmp(value); + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR); + const char* name = tokeniser.getToken(); + listType.push_back(name, tmp.c_str()); + } + tokeniser.nextLine(); + } + + for(ListAttributeType::const_iterator i = listType.begin(); i != listType.end(); ++i) + { + if(string_equal(attribute.m_value.c_str(), (*i).first.c_str())) + { + attribute.m_value = (*i).second.c_str(); + } + } + + EntityClass_insertAttribute(*entityClass, key.c_str(), attribute); + } + else if(string_equal_nocase(type.c_str(), "decal")) + { + } + else if(string_equal_nocase(type.c_str(), "string") + || string_equal_nocase(type.c_str(), "integer") + || string_equal_nocase(type.c_str(), "studio") + || string_equal_nocase(type.c_str(), "sprite") + || string_equal_nocase(type.c_str(), "color255") + || string_equal_nocase(type.c_str(), "target_source") + || string_equal_nocase(type.c_str(), "target_destination") + || string_equal_nocase(type.c_str(), "sound") + // hl2 below + || string_equal_nocase(type.c_str(), "angle") + || string_equal_nocase(type.c_str(), "origin") + || string_equal_nocase(type.c_str(), "float") + || string_equal_nocase(type.c_str(), "node_dest") + || string_equal_nocase(type.c_str(), "filterclass") + || string_equal_nocase(type.c_str(), "vector") + || string_equal_nocase(type.c_str(), "sidelist") + || string_equal_nocase(type.c_str(), "material") + || string_equal_nocase(type.c_str(), "vecline") + || string_equal_nocase(type.c_str(), "axis") + || string_equal_nocase(type.c_str(), "npcclass") + || string_equal_nocase(type.c_str(), "target_name_or_class") + || string_equal_nocase(type.c_str(), "pointentityclass") + || string_equal_nocase(type.c_str(), "scene")) + { + if(!string_equal(tokeniser.getToken(), "readonly")) + { + tokeniser.ungetToken(); + } + + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR); + const char* attributeType = "string"; + if(string_equal_nocase(type.c_str(), "studio")) + { + attributeType = "model"; + } + + EntityClassAttribute attribute; + attribute.m_type = attributeType; + attribute.m_name = tokeniser.getToken(); + + const char* defaultSeparator = tokeniser.getToken(); + if(string_equal(defaultSeparator, ":")) + { + const char* value = tokeniser.getToken(); + if(!string_equal(value, ":")) + { + attribute.m_value = value; + } + else + { + tokeniser.ungetToken(); + } + + { + const char* descriptionSeparator = tokeniser.getToken(); + if(string_equal(descriptionSeparator, ":")) + { + EntityClassFGD_parseSplitString(tokeniser, attribute.m_description); + } + else + { + tokeniser.ungetToken(); + } + } + } + else + { + tokeniser.ungetToken(); + } + EntityClass_insertAttribute(*entityClass, key.c_str(), attribute); + } + else + { + ERROR_MESSAGE("unknown key type: " << makeQuoted(type.c_str())); + } + tokeniser.nextLine(); + } + + if(isBase) + { + EntityClassFGD_insertUniqueBase(entityClass); + } + else + { + EntityClassFGD_insertUnique(entityClass); + } +} + +void EntityClassFGD_loadFile(const char* filename); + +void EntityClassFGD_parse(TextInputStream& inputStream, const char* path) +{ + Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(inputStream); + + tokeniser.nextLine(); + + for(;;) + { + const char* blockType = tokeniser.getToken(); + if(blockType == 0) + { + break; + } + if(string_equal(blockType, "@SolidClass")) + { + EntityClassFGD_parseClass(tokeniser, false, false); + } + else if(string_equal(blockType, "@BaseClass")) + { + EntityClassFGD_parseClass(tokeniser, false, true); + } + else if(string_equal(blockType, "@PointClass") + // hl2 below + || string_equal(blockType, "@KeyFrameClass") + || string_equal(blockType, "@MoveClass") + || string_equal(blockType, "@FilterClass") + || string_equal(blockType, "@NPCClass")) + { + EntityClassFGD_parseClass(tokeniser, true, false); + } + // hl2 below + else if(string_equal(blockType, "@include")) + { + StringOutputStream includePath(256); + includePath << StringRange(path, path_get_filename_start(path)); + includePath << tokeniser.getToken(); + EntityClassFGD_loadFile(includePath.c_str()); + } + else if(string_equal(blockType, "@mapsize")) + { + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR); + //const char* min = + tokeniser.getToken(); + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ","), PARSE_ERROR); + //const char* max = + tokeniser.getToken(); + ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR); + } + else + { + ERROR_MESSAGE("unknown block type: " << makeQuoted(blockType)); + } + } + + tokeniser.release(); +} + + +void EntityClassFGD_loadFile(const char* filename) +{ + TextFileInputStream file(filename); + if(!file.failed()) + { + globalOutputStream() << "parsing entity classes from " << makeQuoted(filename) << "\n"; + + EntityClassFGD_parse(file, filename); + } +} + +EntityClass* EntityClassFGD_findOrInsert(const char *name, bool has_brushes) +{ + ASSERT_NOTNULL(name); + + if(string_empty(name)) + { + return g_EntityClassFGD_bad; + } + + EntityClasses::iterator i = g_EntityClassFGD_classes.find(name); + if(i != g_EntityClassFGD_classes.end() + //&& string_equal((*i).first, name) + ) + { + return (*i).second; + } + + EntityClass* e = EntityClass_Create_Default(name, has_brushes); + return EntityClassFGD_insertUnique(e); +} + +const ListAttributeType* EntityClassFGD_findListType(const char *name) +{ + ListAttributeTypes::iterator i = g_listTypesFGD.find(name); + if(i != g_listTypesFGD.end()) + { + return &(*i).second; + } + return 0; + +} + + +void EntityClassFGD_resolveInheritance(EntityClass* derivedClass) +{ + if(derivedClass->inheritanceResolved == false) + { + derivedClass->inheritanceResolved = true; + for(StringList::iterator j = derivedClass->m_parent.begin(); j != derivedClass->m_parent.end(); ++j) + { + BaseClasses::iterator i = g_EntityClassFGD_bases.find((*j).c_str()); + if(i == g_EntityClassFGD_bases.end()) + { + globalErrorStream() << "failed to find entityDef " << makeQuoted((*j).c_str()) << " inherited by " << makeQuoted(derivedClass->m_name.c_str()) << "\n"; + } + else + { + EntityClass* parentClass = (*i).second; + EntityClassFGD_resolveInheritance(parentClass); + if(!derivedClass->colorSpecified) + { + derivedClass->colorSpecified = parentClass->colorSpecified; + derivedClass->color = parentClass->color; + } + if(!derivedClass->sizeSpecified) + { + derivedClass->sizeSpecified = parentClass->sizeSpecified; + derivedClass->mins = parentClass->mins; + derivedClass->maxs = parentClass->maxs; + } + + for(EntityClassAttributes::iterator k = parentClass->m_attributes.begin(); k != parentClass->m_attributes.end(); ++k) + { + EntityClass_insertAttribute(*derivedClass, (*k).first.c_str(), (*k).second); + } + } + } + } +} + +class EntityClassFGD : public ModuleObserver +{ + std::size_t m_unrealised; + ModuleObservers m_observers; +public: + EntityClassFGD() : m_unrealised(3) + { + } + void realise() + { + if(--m_unrealised == 0) + { + StringOutputStream filename(256); + filename << GlobalRadiant().getGameToolsPath() << GlobalRadiant().getGameName() << "/halflife.fgd"; + EntityClassFGD_loadFile(filename.c_str()); + + { + for(EntityClasses::iterator i = g_EntityClassFGD_classes.begin(); i != g_EntityClassFGD_classes.end(); ++i) + { + EntityClassFGD_resolveInheritance((*i).second); + if((*i).second->fixedsize && string_empty((*i).second->m_modelpath.c_str())) + { + if(!(*i).second->sizeSpecified) + { + globalErrorStream() << "size not specified for entity class: " << makeQuoted((*i).second->m_name.c_str()) << '\n'; + } + if(!(*i).second->colorSpecified) + { + globalErrorStream() << "color not specified for entity class: " << makeQuoted((*i).second->m_name.c_str()) << '\n'; + } + } + } + } + { + for(BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i) + { + eclass_capture_state((*i).second); + } + } + + m_observers.realise(); + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + m_observers.unrealise(); + EntityClassFGD_clear(); + } + } + void attach(ModuleObserver& observer) + { + m_observers.attach(observer); + } + void detach(ModuleObserver& observer) + { + m_observers.detach(observer); + } +}; + +EntityClassFGD g_EntityClassFGD; + +void EntityClassFGD_attach(ModuleObserver& observer) +{ + g_EntityClassFGD.attach(observer); +} +void EntityClassFGD_detach(ModuleObserver& observer) +{ + g_EntityClassFGD.detach(observer); +} + +void EntityClassFGD_realise() +{ + g_EntityClassFGD.realise(); +} +void EntityClassFGD_unrealise() +{ + g_EntityClassFGD.unrealise(); +} + +void EntityClassFGD_construct() +{ + // start by creating the default unknown eclass + g_EntityClassFGD_bad = EClass_Create("UNKNOWN_CLASS", Vector3(0.0f, 0.5f, 0.0f), ""); + + EntityClassFGD_realise(); +} + +void EntityClassFGD_destroy() +{ + EntityClassFGD_unrealise(); + + g_EntityClassFGD_bad->free(g_EntityClassFGD_bad); +} + +class EntityClassFGDDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef, public GlobalRadiantModuleRef +{ +}; + +class EntityClassFGDAPI +{ + EntityClassManager m_eclassmanager; +public: + typedef EntityClassManager Type; + STRING_CONSTANT(Name, "halflife"); + + EntityClassFGDAPI() + { + EntityClassFGD_construct(); + + m_eclassmanager.findOrInsert = &EntityClassFGD_findOrInsert; + m_eclassmanager.findListType = &EntityClassFGD_findListType; + m_eclassmanager.forEach = &EntityClassFGD_forEach; + m_eclassmanager.attach = &EntityClassFGD_attach; + m_eclassmanager.detach = &EntityClassFGD_detach; + m_eclassmanager.realise = &EntityClassFGD_realise; + m_eclassmanager.unrealise = &EntityClassFGD_unrealise; + + GlobalRadiant().attachGameToolsPathObserver(g_EntityClassFGD); + GlobalRadiant().attachGameNameObserver(g_EntityClassFGD); + } + ~EntityClassFGDAPI() + { + GlobalRadiant().detachGameNameObserver(g_EntityClassFGD); + GlobalRadiant().detachGameToolsPathObserver(g_EntityClassFGD); + + EntityClassFGD_destroy(); + } + EntityClassManager* getTable() + { + return &m_eclassmanager; + } +}; + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +typedef SingletonModule EntityClassFGDModule; +typedef Static StaticEntityClassFGDModule; +StaticRegisterModule staticRegisterEntityClassFGD(StaticEntityClassFGDModule::instance()); diff --git a/radiant/eclass_fgd.h b/radiant/eclass_fgd.h new file mode 100644 index 00000000..f8540b39 --- /dev/null +++ b/radiant/eclass_fgd.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ECLASS_FGD_H) +#define INCLUDED_ECLASS_FGD_H + +#endif diff --git a/radiant/eclass_xml.cpp b/radiant/eclass_xml.cpp new file mode 100644 index 00000000..5195b4e7 --- /dev/null +++ b/radiant/eclass_xml.cpp @@ -0,0 +1,610 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +///\file +///\brief EntityClass plugin that supports the .ent xml entity-definition format. +/// +/// the .ent xml format expresses entity-definitions. +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// the attributes of an entity type are defined like this: +/// +/// <[name of attribute type] +/// key="[entity key name]" +/// name="[name shown in gui]" +/// value="[default entity key value]" +/// >[comment text shown in gui] +/// +/// each attribute type has a specialised attribute-editor GUI +/// +/// currently-supported attribute types: +/// +/// string a string +/// array an array of strings - value is a semi-colon-delimited string +/// integer an integer value +/// boolean an integer - shows as a checkbox - true = non-zero +/// integer2 two integer values +/// integer3 three integer values +/// real3 three floating-point values +/// angle specialisation of real - Yaw angle +/// direction specialisation of real - Yaw angle, -1 = down, -2 = up +/// angles specialisation of real3 - Pitch Yaw Roll +/// color specialisation of real3 - RGB floating-point colour +/// target a string that uniquely identifies an entity or group of entities +/// targetname a string that uniquely identifies an entity or group of entities +/// sound the VFS path to a sound file +/// texture the VFS path to a texture file or a shader name +/// model the VFS path to a model file +/// skin the VFS path to a skin file +/// +/// +/// flag attributes define a flag in the "spawnflags" key: +/// +/// [comment text shown in gui] +/// +/// the default value for a flag bit is always 0. +/// +/// +/// List attributes have a set of valid values. +/// Create new list attribute types like this: +/// +/// +/// +/// +/// +/// +/// these can then be used as attribute types. +/// +/// +/// An attribute definition should specify a default value that corresponds +/// with the default value given by the game. If the default value is not +/// specified in the attribute definition, it is assumed to be an empty string. +/// +/// If the currently-selected entity in Radiant does not specify a value for +/// the key of an attribute, the default value from the attribute-definition +/// will be displayed in the attribute-editor and used when visualising the +/// entity in the preview windows. E.g. the Doom3 "light" entity has a +/// "light_radius" key. Light entities without a "light_radius" key are +/// displayed in Doom3 with a radius of 300. The default value for the +/// "light_radius" attribute definition should be specified as "300 300 300". +/// + + + + +#include "eclass_xml.h" + +#include "ieclass.h" +#include "irender.h" +#include "ifilesystem.h" +#include "iarchive.h" + +#include "xml/xmlparser.h" +#include "generic/object.h" +#include "generic/reference.h" +#include "stream/stringstream.h" +#include "stream/textfilestream.h" +#include "os/path.h" +#include "eclasslib.h" +#include "modulesystem/moduleregistry.h" +#include "stringio.h" + +#define PARSE_ERROR(elementName, name) makeQuoted(elementName) << " is not a valid child of " << makeQuoted(name) + +class IgnoreBreaks +{ +public: + const char* m_first; + const char* m_last; + IgnoreBreaks(const char* first, const char* last) : m_first(first), m_last(last) + { + } +}; + +template +TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const IgnoreBreaks& ignoreBreaks) +{ + for(const char* i = ignoreBreaks.m_first; i != ignoreBreaks.m_last; ++i) + { + if(*i != '\n') + { + ostream << *i; + } + } + return ostream; +} + +namespace +{ + +class TreeXMLImporter : public TextOutputStream +{ +public: + virtual TreeXMLImporter& pushElement(const XMLElement& element) = 0; + virtual void popElement(const char* name) = 0; +}; + +template +class Storage +{ + char m_storage[sizeof(Type)]; +public: + Type& get() + { + return *reinterpret_cast(m_storage); + } + const Type& get() const + { + return *reinterpret_cast(m_storage); + } +}; + +class BreakImporter : public TreeXMLImporter +{ +public: + BreakImporter(StringOutputStream& comment) + { + comment << '\n'; + } + static const char* name() + { + return "n"; + } + TreeXMLImporter& pushElement(const XMLElement& element) + { + ERROR_MESSAGE(PARSE_ERROR(element.name(), name())); + return *this; + } + void popElement(const char* elementName) + { + ERROR_MESSAGE(PARSE_ERROR(elementName, name())); + } + std::size_t write(const char* data, std::size_t length) + { + return length; + } +}; + +class AttributeImporter : public TreeXMLImporter +{ + StringOutputStream& m_comment; + +public: + AttributeImporter(StringOutputStream& comment, EntityClass* entityClass, const XMLElement& element) : m_comment(comment) + { + const char* type = element.name(); + const char* key = element.attribute("key"); + const char* name = element.attribute("name"); + const char* value = element.attribute("value"); + + ASSERT_MESSAGE(!string_empty(key), "key attribute not specified"); + ASSERT_MESSAGE(!string_empty(name), "name attribute not specified"); + + if(string_equal(type, "flag")) + { + std::size_t bit = atoi(element.attribute("bit")); + ASSERT_MESSAGE(bit < MAX_FLAGS, "invalid flag bit"); + ASSERT_MESSAGE(string_empty(entityClass->flagnames[bit]), "non-unique flag bit"); + strcpy(entityClass->flagnames[bit], key); + } + + m_comment << key; + m_comment << " : "; + + EntityClass_insertAttribute(*entityClass, key, EntityClassAttribute(type, name, value)); + } + ~AttributeImporter() + { + } + TreeXMLImporter& pushElement(const XMLElement& element) + { + ERROR_MESSAGE(PARSE_ERROR(element.name(), "attribute")); + return *this; + } + void popElement(const char* elementName) + { + ERROR_MESSAGE(PARSE_ERROR(elementName, "attribute")); + } + std::size_t write(const char* data, std::size_t length) + { + return m_comment.write(data, length); + } +}; + +bool attributeSupported(const char* name) +{ + return string_equal(name, "real") + || string_equal(name, "integer") + || string_equal(name, "boolean") + || string_equal(name, "string") + || string_equal(name, "array") + || string_equal(name, "flag") + || string_equal(name, "real3") + || string_equal(name, "integer3") + || string_equal(name, "direction") + || string_equal(name, "angle") + || string_equal(name, "angles") + || string_equal(name, "color") + || string_equal(name, "target") + || string_equal(name, "targetname") + || string_equal(name, "sound") + || string_equal(name, "texture") + || string_equal(name, "model") + || string_equal(name, "skin") + || string_equal(name, "integer2"); +} + +typedef std::map ListAttributeTypes; + +bool listAttributeSupported(ListAttributeTypes& listTypes, const char* name) +{ + return listTypes.find(name) != listTypes.end(); +} + + +class ClassImporter : public TreeXMLImporter +{ + EntityClassCollector& m_collector; + EntityClass* m_eclass; + StringOutputStream m_comment; + Storage m_attribute; + ListAttributeTypes& m_listTypes; + +public: + ClassImporter(EntityClassCollector& collector, ListAttributeTypes& listTypes, const XMLElement& element) : m_collector(collector), m_listTypes(listTypes) + { + m_eclass = Eclass_Alloc(); + m_eclass->free = &Eclass_Free; + + const char* name = element.attribute("name"); + ASSERT_MESSAGE(!string_empty(name), "name attribute not specified for class"); + m_eclass->m_name = name; + + const char* color = element.attribute("color"); + ASSERT_MESSAGE(!string_empty(name), "color attribute not specified for class " << name); + string_parse_vector3(color, m_eclass->color); + eclass_capture_state(m_eclass); + + const char* model = element.attribute("model"); + if(!string_empty(model)) + { + StringOutputStream buffer(256); + buffer << PathCleaned(model); + m_eclass->m_modelpath = buffer.c_str(); + } + + const char* type = element.name(); + if(string_equal(type, "point")) + { + const char* box = element.attribute("box"); + ASSERT_MESSAGE(!string_empty(box), "box attribute not found for class " << name); + m_eclass->fixedsize = true; + string_parse_vector(box, &m_eclass->mins.x(), &m_eclass->mins.x() + 6); + } + } + ~ClassImporter() + { + m_eclass->m_comments = m_comment.c_str(); + m_collector.insert(m_eclass); + + for(ListAttributeTypes::iterator i = m_listTypes.begin(); i != m_listTypes.end(); ++i) + { + m_collector.insert((*i).first.c_str(), (*i).second); + } + } + static const char* name() + { + return "class"; + } + TreeXMLImporter& pushElement(const XMLElement& element) + { + if(attributeSupported(element.name()) || listAttributeSupported(m_listTypes, element.name())) + { + constructor(m_attribute.get(), makeReference(m_comment), m_eclass, element); + return m_attribute.get(); + } + else + { + ERROR_MESSAGE(PARSE_ERROR(element.name(), name())); + return *this; + } + } + void popElement(const char* elementName) + { + if(attributeSupported(elementName) || listAttributeSupported(m_listTypes, elementName)) + { + destructor(m_attribute.get()); + } + else + { + ERROR_MESSAGE(PARSE_ERROR(elementName, name())); + } + } + std::size_t write(const char* data, std::size_t length) + { + return m_comment.write(data, length); + } +}; + +class ItemImporter : public TreeXMLImporter +{ +public: + ItemImporter(ListAttributeType& list, const XMLElement& element) + { + const char* name = element.attribute("name"); + const char* value = element.attribute("value"); + list.push_back(name, value); + } + TreeXMLImporter& pushElement(const XMLElement& element) + { + ERROR_MESSAGE(PARSE_ERROR(element.name(), "item")); + return *this; + } + void popElement(const char* elementName) + { + ERROR_MESSAGE(PARSE_ERROR(elementName, "item")); + } + std::size_t write(const char* data, std::size_t length) + { + return length; + } +}; + +bool isItem(const char* name) +{ + return string_equal(name, "item"); +} + +class ListAttributeImporter : public TreeXMLImporter +{ + ListAttributeType* m_listType; + Storage m_item; +public: + ListAttributeImporter(ListAttributeTypes& listTypes, const XMLElement& element) + { + const char* name = element.attribute("name"); + m_listType = &listTypes[name]; + } + TreeXMLImporter& pushElement(const XMLElement& element) + { + if(isItem(element.name())) + { + constructor(m_item.get(), makeReference(*m_listType), element); + return m_item.get(); + } + else + { + ERROR_MESSAGE(PARSE_ERROR(element.name(), "list")); + return *this; + } + } + void popElement(const char* elementName) + { + if(isItem(elementName)) + { + destructor(m_item.get()); + } + else + { + ERROR_MESSAGE(PARSE_ERROR(elementName, "list")); + } + } + std::size_t write(const char* data, std::size_t length) + { + return length; + } +}; + +bool classSupported(const char* name) +{ + return string_equal(name, "group") + || string_equal(name, "point"); +} + +bool listSupported(const char* name) +{ + return string_equal(name, "list"); +} + +class ClassesImporter : public TreeXMLImporter +{ + EntityClassCollector& m_collector; + Storage m_class; + Storage m_list; + ListAttributeTypes m_listTypes; + +public: + ClassesImporter(EntityClassCollector& collector) : m_collector(collector) + { + } + static const char* name() + { + return "classes"; + } + TreeXMLImporter& pushElement(const XMLElement& element) + { + if(classSupported(element.name())) + { + constructor(m_class.get(), makeReference(m_collector), makeReference(m_listTypes), element); + return m_class.get(); + } + else if(listSupported(element.name())) + { + constructor(m_list.get(), makeReference(m_listTypes), element); + return m_list.get(); + } + else + { + ERROR_MESSAGE(PARSE_ERROR(element.name(), name())); + return *this; + } + } + void popElement(const char* elementName) + { + if(classSupported(elementName)) + { + destructor(m_class.get()); + } + else if(listSupported(elementName)) + { + destructor(m_list.get()); + } + else + { + ERROR_MESSAGE(PARSE_ERROR(elementName, name())); + } + } + std::size_t write(const char* data, std::size_t length) + { + return length; + } +}; + +class EclassXMLImporter : public TreeXMLImporter +{ + EntityClassCollector& m_collector; + Storage m_classes; + +public: + EclassXMLImporter(EntityClassCollector& collector) : m_collector(collector) + { + } + static const char* name() + { + return "classes"; + } + TreeXMLImporter& pushElement(const XMLElement& element) + { + if(string_equal(element.name(), ClassesImporter::name())) + { + constructor(m_classes.get(), makeReference(m_collector)); + return m_classes.get(); + } + else + { + ERROR_MESSAGE(PARSE_ERROR(element.name(), name())); + return *this; + } + } + void popElement(const char* elementName) + { + if(string_equal(elementName, ClassesImporter::name())) + { + destructor(m_classes.get()); + } + else + { + ERROR_MESSAGE(PARSE_ERROR(elementName, name())); + } + } + std::size_t write(const char* data, std::size_t length) + { + return length; + } +}; + +class TreeXMLImporterStack : public XMLImporter +{ + std::vector< Reference > m_importers; +public: + TreeXMLImporterStack(TreeXMLImporter& importer) + { + m_importers.push_back(makeReference(importer)); + } + void pushElement(const XMLElement& element) + { + m_importers.push_back(makeReference(m_importers.back().get().pushElement(element))); + } + void popElement(const char* name) + { + m_importers.pop_back(); + m_importers.back().get().popElement(name); + } + std::size_t write(const char* buffer, std::size_t length) + { + return m_importers.back().get().write(buffer, length); + } +}; + + + +const char* GetExtension() +{ + return "ent"; +} +void ScanFile(EntityClassCollector& collector, const char *filename) +{ + TextFileInputStream inputFile(filename); + if(!inputFile.failed()) + { + XMLStreamParser parser(inputFile); + + EclassXMLImporter importer(collector); + TreeXMLImporterStack stack(importer); + parser.exportXML(stack); + } +} + + +} + +#include "modulesystem/singletonmodule.h" + +class EntityClassXMLDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef +{ +}; + +class EclassXMLAPI +{ + EntityClassScanner m_eclassxml; +public: + typedef EntityClassScanner Type; + STRING_CONSTANT(Name, "xml"); + + EclassXMLAPI() + { + m_eclassxml.scanFile = &ScanFile; + m_eclassxml.getExtension = &GetExtension; + } + EntityClassScanner* getTable() + { + return &m_eclassxml; + } +}; + +typedef SingletonModule EclassXMLModule; +typedef Static StaticEclassXMLModule; +StaticRegisterModule staticRegisterEclassXML(StaticEclassXMLModule::instance()); diff --git a/radiant/eclass_xml.h b/radiant/eclass_xml.h new file mode 100644 index 00000000..7884d0e8 --- /dev/null +++ b/radiant/eclass_xml.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ECLASS_XML_H) +#define INCLUDED_ECLASS_XML_H + +#endif diff --git a/radiant/entity.cpp b/radiant/entity.cpp new file mode 100644 index 00000000..c2a31091 --- /dev/null +++ b/radiant/entity.cpp @@ -0,0 +1,482 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "entity.h" + +#include "ientity.h" +#include "iselection.h" +#include "imodel.h" +#include "ifilesystem.h" +#include "iundo.h" +#include "editable.h" + +#include "eclasslib.h" +#include "scenelib.h" +#include "os/path.h" +#include "os/file.h" +#include "stream/stringstream.h" +#include "stringio.h" + +#include "gtkutil/filechooser.h" +#include "gtkmisc.h" +#include "select.h" +#include "map.h" +#include "preferences.h" +#include "gtkdlgs.h" +#include "mainframe.h" +#include "qe3.h" +#include "commands.h" + +struct entity_globals_t +{ + Vector3 color_entity; + + entity_globals_t() : + color_entity(0.0f, 0.0f, 0.0f) + { + } +}; + +entity_globals_t g_entity_globals; + +class EntitySetKeyValueSelected : public scene::Graph::Walker +{ + const char* m_key; + const char* m_value; +public: + EntitySetKeyValueSelected(const char* key, const char* value) + : m_key(key), m_value(value) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + return true; + } + void post(const scene::Path& path, scene::Instance& instance) const + { + Entity* entity = Node_getEntity(path.top()); + if(entity != 0 + && (instance.childSelected() || Instance_getSelectable(instance)->isSelected())) + { + entity->setKeyValue(m_key, m_value); + } + } +}; + +class EntitySetClassnameSelected : public scene::Graph::Walker +{ + const char* m_classname; +public: + EntitySetClassnameSelected(const char* classname) + : m_classname(classname) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + return true; + } + void post(const scene::Path& path, scene::Instance& instance) const + { + Entity* entity = Node_getEntity(path.top()); + if(entity != 0 + && (instance.childSelected() || Instance_getSelectable(instance)->isSelected())) + { + NodeSmartReference node(GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert(m_classname, node_is_group(path.top())))); + + EntityCopyingVisitor visitor(*Node_getEntity(node)); + + entity->forEachKeyValue(visitor); + + NodeSmartReference child(path.top().get()); + NodeSmartReference parent(path.parent().get()); + Node_getTraversable(parent)->erase(child); + if(Node_getTraversable(child) != 0 + && Node_getTraversable(node) != 0 + && node_is_group(node)) + { + parentBrushes(child, node); + } + Node_getTraversable(parent)->insert(node); + } + } +}; + +void Scene_EntitySetKeyValue_Selected(const char* key, const char* value) +{ + GlobalSceneGraph().traverse(EntitySetKeyValueSelected(key, value)); +} + +void Scene_EntitySetClassname_Selected(const char* classname) +{ + GlobalSceneGraph().traverse(EntitySetClassnameSelected(classname)); +} + + +void Entity_ungroupSelected() +{ + if (GlobalSelectionSystem().countSelected() < 1) return; + + UndoableCommand undo("ungroupSelectedEntities"); + + scene::Path world_path(makeReference(GlobalSceneGraph().root())); + world_path.push(makeReference(Map_FindOrInsertWorldspawn(g_map))); + + scene::Instance &instance = GlobalSelectionSystem().ultimateSelected(); + scene::Path path = instance.path(); + + if (!Node_isEntity(path.top())) path.pop(); + + if(Node_getEntity(path.top()) != 0 + && node_is_group(path.top())) + { + if(world_path.top().get_pointer() != path.top().get_pointer()) + { + parentBrushes(path.top(), world_path.top()); + Path_deleteTop(path); + } + } +} + + + +void Entity_connectSelected() +{ + if(GlobalSelectionSystem().countSelected() == 2) + { + GlobalEntityCreator().connectEntities( + GlobalSelectionSystem().penultimateSelected().path(), + GlobalSelectionSystem().ultimateSelected().path() + ); + } + else + { + globalErrorStream() << "entityConnectSelected: exactly two instances must be selected\n"; + } +} + +AABB Doom3Light_getBounds(const AABB& workzone) +{ + AABB aabb(workzone); + + Vector3 defaultRadius(300, 300, 300); + if(!string_parse_vector3(EntityClass_valueForKey(*GlobalEntityClassManager().findOrInsert("light", false), "light_radius"), defaultRadius)) + { + globalErrorStream() << "Doom3Light_getBounds: failed to parse default light radius\n"; + } + + if(aabb.extents[0] == 0) + { + aabb.extents[0] = defaultRadius[0]; + } + if(aabb.extents[1] == 0) + { + aabb.extents[1] = defaultRadius[1]; + } + if(aabb.extents[2] == 0) + { + aabb.extents[2] = defaultRadius[2]; + } + + if(aabb_valid(aabb)) + { + return aabb; + } + return AABB(Vector3(0, 0, 0), Vector3(64, 64, 64)); +} + +int g_iLastLightIntensity; + +void Entity_createFromSelection(const char* name, const Vector3& origin) +{ +#if 0 + if(string_equal_nocase(name, "worldspawn")) + { + gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), "Can't create an entity with worldspawn.", "info"); + return; + } +#endif + + EntityClass* entityClass = GlobalEntityClassManager().findOrInsert(name, true); + + bool isModel = string_equal_nocase(name, "misc_model") + || string_equal_nocase(name, "misc_gamemodel") + || string_equal_nocase(name, "model_static") + || (GlobalSelectionSystem().countSelected() == 0 && string_equal_nocase(name, "func_static")); + + bool brushesSelected = Scene_countSelectedBrushes(GlobalSceneGraph()) != 0; + + if(!(entityClass->fixedsize || isModel) && !brushesSelected) + { + globalErrorStream() << "failed to create a group entity - no brushes are selected\n"; + return; + } + + AABB workzone(aabb_for_minmax(Select_getWorkZone().d_work_min, Select_getWorkZone().d_work_max)); + + + NodeSmartReference node(GlobalEntityCreator().createEntity(entityClass)); + + Node_getTraversable(GlobalSceneGraph().root())->insert(node); + + scene::Path entitypath(makeReference(GlobalSceneGraph().root())); + entitypath.push(makeReference(node.get())); + scene::Instance& instance = findInstance(entitypath); + + if(entityClass->fixedsize || (isModel && !brushesSelected)) + { + Select_Delete(); + + Transformable* transform = Instance_getTransformable(instance); + if(transform != 0) + { + transform->setType(TRANSFORM_PRIMITIVE); + transform->setTranslation(origin); + transform->freezeTransform(); + } + + GlobalSelectionSystem().setSelectedAll(false); + + Instance_setSelected(instance, true); + } + else + { + if (g_pGameDescription->mGameType == "doom3") + { + Node_getEntity(node)->setKeyValue("model", Node_getEntity(node)->getKeyValue("name")); + } + + Scene_parentSelectedBrushesToEntity(GlobalSceneGraph(), node); + Scene_forEachChildSelectable(SelectableSetSelected(true), instance.path()); + } + + // tweaking: when right clic dropping a light entity, ask for light value in a custom dialog box + // see SF bug 105383 + + if (g_pGameDescription->mGameType == "hl") + { + // FIXME - Hydra: really we need a combined light AND color dialog for halflife. + if (string_equal_nocase(name, "light") + || string_equal_nocase(name, "light_environment") + || string_equal_nocase(name, "light_spot")) + { + int intensity = g_iLastLightIntensity; + + if (DoLightIntensityDlg (&intensity) == eIDOK) + { + g_iLastLightIntensity = intensity; + char buf[30]; + sprintf( buf, "255 255 255 %d", intensity ); + Node_getEntity(node)->setKeyValue("_light", buf); + } + } + } + else if(string_equal_nocase(name, "light")) + { + if(g_pGameDescription->mGameType != "doom3") + { + int intensity = g_iLastLightIntensity; + + if (DoLightIntensityDlg (&intensity) == eIDOK) + { + g_iLastLightIntensity = intensity; + char buf[10]; + sprintf( buf, "%d", intensity ); + Node_getEntity(node)->setKeyValue("light", buf); + } + } + else if(brushesSelected) // use workzone to set light position/size for doom3 lights, if there are brushes selected + { + AABB bounds(Doom3Light_getBounds(workzone)); + StringOutputStream key(64); + key << bounds.origin[0] << " " << bounds.origin[1] << " " << bounds.origin[2]; + Node_getEntity(node)->setKeyValue("origin", key.c_str()); + key.clear(); + key << bounds.extents[0] << " " << bounds.extents[1] << " " << bounds.extents[2]; + Node_getEntity(node)->setKeyValue("light_radius", key.c_str()); + } + } + + if(isModel) + { + const char* model = misc_model_dialog(GTK_WIDGET(MainFrame_getWindow())); + if(model != 0) + { + Node_getEntity(node)->setKeyValue("model", model); + } + } +} + + +bool DoNormalisedColor(Vector3& color) +{ + if(!color_dialog(GTK_WIDGET(MainFrame_getWindow()), color)) + return false; + /* + ** scale colors so that at least one component is at 1.0F + */ + + float largest = 0.0F; + + if ( color[0] > largest ) + largest = color[0]; + if ( color[1] > largest ) + largest = color[1]; + if ( color[2] > largest ) + largest = color[2]; + + if ( largest == 0.0F ) + { + color[0] = 1.0F; + color[1] = 1.0F; + color[2] = 1.0F; + } + else + { + float scaler = 1.0F / largest; + + color[0] *= scaler; + color[1] *= scaler; + color[2] *= scaler; + } + + return true; +} + +void Entity_setColour() +{ + if(GlobalSelectionSystem().countSelected() != 0) + { + const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path(); + Entity* entity = Node_getEntity(path.top()); + if(entity != 0) + { + const char* strColor = entity->getKeyValue("_color"); + if(!string_empty(strColor)) + { + Vector3 rgb; + if (string_parse_vector3(strColor, rgb)) + { + g_entity_globals.color_entity = rgb; + } + } + + if(g_pGameDescription->mGameType == "doom3" + ? color_dialog(GTK_WIDGET(MainFrame_getWindow()), g_entity_globals.color_entity) + : DoNormalisedColor(g_entity_globals.color_entity)) + { + char buffer[128]; + sprintf(buffer, "%g %g %g", g_entity_globals.color_entity[0], + g_entity_globals.color_entity[1], + g_entity_globals.color_entity[2]); + + Scene_EntitySetKeyValue_Selected("_color", buffer); + } + } + } +} + +const char* misc_model_dialog(GtkWidget* parent) +{ + StringOutputStream buffer(1024); + + buffer << g_qeglobals.m_userGamePath.c_str() << "models/"; + + if(!file_readable(buffer.c_str())) + { + // just go to fsmain + buffer.clear(); + buffer << g_qeglobals.m_userGamePath.c_str() << "/"; + } + + const char *filename = file_dialog (parent, TRUE, "Choose Model", buffer.c_str(), ModelLoader::Name()); + if (filename != 0) + { + // use VFS to get the correct relative path + const char* relative = path_make_relative(filename, GlobalFileSystem().findRoot(filename)); + if(relative == filename) + { + globalOutputStream() << "WARNING: could not extract the relative path, using full path instead\n"; + } + return relative; + } + return 0; +} + +void LightRadiiImport(EntityCreator& self, bool value) +{ + self.setLightRadii(value); +} +typedef ReferenceCaller1 LightRadiiImportCaller; + +void LightRadiiExport(EntityCreator& self, const BoolImportCallback& importer) +{ + importer(self.getLightRadii()); +} +typedef ReferenceCaller1 LightRadiiExportCaller; + +void Entity_constructPreferences(PreferencesPage& page) +{ + page.appendCheckBox( + "Show", "Light Radii", + LightRadiiImportCaller(GlobalEntityCreator()), + LightRadiiExportCaller(GlobalEntityCreator()) + ); +} +void Entity_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Entities", "Entity Display Preferences")); + Entity_constructPreferences(page); +} +void Entity_registerPreferencesPage() +{ + PreferencesDialog_addDisplayPage(FreeCaller1()); +} + + + +void Entity_constructMenu(GtkMenu* menu) +{ + create_menu_item_with_mnemonic(menu, "_Ungroup", "UngroupSelection"); + create_menu_item_with_mnemonic(menu, "_Connect", "ConnectSelection"); + create_menu_item_with_mnemonic(menu, "_Select Color...", "EntityColor"); +} + + + +#include "preferencesystem.h" +#include "stringio.h" + +void Entity_Construct() +{ + GlobalCommands_insert("EntityColor", FreeCaller(), Accelerator('K')); + GlobalCommands_insert("ConnectSelection", FreeCaller(), Accelerator('K', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("UngroupSelection", FreeCaller()); + + GlobalPreferenceSystem().registerPreference("SI_Colors5", Vector3ImportStringCaller(g_entity_globals.color_entity), Vector3ExportStringCaller(g_entity_globals.color_entity)); + GlobalPreferenceSystem().registerPreference("LastLightIntensity", IntImportStringCaller(g_iLastLightIntensity), IntExportStringCaller(g_iLastLightIntensity)); + + Entity_registerPreferencesPage(); +} + +void Entity_Destroy() +{ +} + diff --git a/radiant/entity.h b/radiant/entity.h new file mode 100644 index 00000000..3e962178 --- /dev/null +++ b/radiant/entity.h @@ -0,0 +1,42 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ENTITY_H) +#define INCLUDED_ENTITY_H + +template class BasicVector3; +typedef BasicVector3 Vector3; +void Entity_createFromSelection(const char* name, const Vector3& origin); + +void Scene_EntitySetKeyValue_Selected(const char* key, const char* value); +void Scene_EntitySetClassname_Selected(const char* classname); + + +typedef struct _GtkWidget GtkWidget; +const char* misc_model_dialog(GtkWidget* parent); + +typedef struct _GtkMenu GtkMenu; +void Entity_constructMenu(GtkMenu* menu); + +void Entity_Construct(); +void Entity_Destroy(); + +#endif diff --git a/radiant/entityinspector.cpp b/radiant/entityinspector.cpp new file mode 100644 index 00000000..e2f3c194 --- /dev/null +++ b/radiant/entityinspector.cpp @@ -0,0 +1,1761 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "entityinspector.h" + +#include "debugging/debugging.h" + +#include "ientity.h" +#include "ifilesystem.h" +#include "imodel.h" +#include "iscenegraph.h" +#include "iselection.h" +#include "iundo.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "os/path.h" +#include "eclasslib.h" +#include "scenelib.h" +#include "generic/callback.h" +#include "os/file.h" +#include "stream/stringstream.h" +#include "moduleobserver.h" +#include "convert.h" +#include "stringio.h" + +#include "gtkutil/accelerator.h" +#include "gtkutil/dialog.h" +#include "gtkutil/filechooser.h" +#include "gtkutil/messagebox.h" +#include "gtkutil/nonmodal.h" +#include "gtkutil/button.h" +#include "gtkutil/entry.h" +#include "gtkutil/container.h" + +#include "qe3.h" +#include "gtkmisc.h" +#include "gtkdlgs.h" +#include "entity.h" +#include "mainframe.h" +#include "textureentry.h" + +GtkEntry* numeric_entry_new() +{ + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_widget_set_size_request(GTK_WIDGET(entry), 64, -1); + return entry; +} + +namespace +{ + typedef std::map KeyValues; + KeyValues g_selectedKeyValues; + KeyValues g_selectedDefaultKeyValues; +} + +const char* SelectedEntity_getValueForKey(const char* key) +{ + { + KeyValues::const_iterator i = g_selectedKeyValues.find(key); + if(i != g_selectedKeyValues.end()) + { + return (*i).second.c_str(); + } + } + { + KeyValues::const_iterator i = g_selectedDefaultKeyValues.find(key); + if(i != g_selectedDefaultKeyValues.end()) + { + return (*i).second.c_str(); + } + } + return ""; +} + +void Scene_EntitySetKeyValue_Selected_Undoable(const char* key, const char* value) +{ + StringOutputStream command(256); + command << "entitySetKeyValue -key " << makeQuoted(key) << " -value " << makeQuoted(value); + UndoableCommand undo(command.c_str()); + Scene_EntitySetKeyValue_Selected(key, value); +} + +class EntityAttribute +{ +public: + virtual GtkWidget* getWidget() const = 0; + virtual void update() = 0; + virtual void release() = 0; +}; + +class BooleanAttribute : public EntityAttribute +{ + CopiedString m_key; + GtkCheckButton* m_check; + + static gboolean toggled(GtkWidget *widget, BooleanAttribute* self) + { + self->apply(); + return FALSE; + } +public: + BooleanAttribute(const char* key) : + m_key(key), + m_check(0) + { + GtkCheckButton* check = GTK_CHECK_BUTTON(gtk_check_button_new()); + gtk_widget_show(GTK_WIDGET(check)); + + m_check = check; + + guint handler = g_signal_connect(G_OBJECT(check), "toggled", G_CALLBACK(toggled), this); + g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(handler)); + + update(); + } + GtkWidget* getWidget() const + { + return GTK_WIDGET(m_check); + } + void release() + { + delete this; + } + void apply() + { + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_check)) ? "1" : "0"); + } + typedef MemberCaller ApplyCaller; + + void update() + { + const char* value = SelectedEntity_getValueForKey(m_key.c_str()); + if(!string_empty(value)) + { + toggle_button_set_active_no_signal(GTK_TOGGLE_BUTTON(m_check), atoi(value) != 0); + } + else + { + toggle_button_set_active_no_signal(GTK_TOGGLE_BUTTON(m_check), false); + } + } + typedef MemberCaller UpdateCaller; +}; + + +class StringAttribute : public EntityAttribute +{ + CopiedString m_key; + GtkEntry* m_entry; + NonModalEntry m_nonModal; +public: + StringAttribute(const char* key) : + m_key(key), + m_entry(0), + m_nonModal(ApplyCaller(*this), UpdateCaller(*this)) + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_widget_set_size_request(GTK_WIDGET(entry), 50, -1); + + m_entry = entry; + m_nonModal.connect(m_entry); + } + GtkWidget* getWidget() const + { + return GTK_WIDGET(m_entry); + } + GtkEntry* getEntry() const + { + return m_entry; + } + + void release() + { + delete this; + } + void apply() + { + StringOutputStream value(64); + value << ConvertUTF8ToLocale(gtk_entry_get_text(m_entry)); + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), value.c_str()); + } + typedef MemberCaller ApplyCaller; + + void update() + { + StringOutputStream value(64); + value << ConvertLocaleToUTF8(SelectedEntity_getValueForKey(m_key.c_str())); + gtk_entry_set_text(m_entry, value.c_str()); + } + typedef MemberCaller UpdateCaller; +}; + +class ShaderAttribute : public StringAttribute +{ +public: + ShaderAttribute(const char* key) : StringAttribute(key) + { + GlobalShaderEntryCompletion::instance().connect(StringAttribute::getEntry()); + } +}; + + +class ModelAttribute : public EntityAttribute +{ + CopiedString m_key; + BrowsedPathEntry m_entry; + NonModalEntry m_nonModal; +public: + ModelAttribute(const char* key) : + m_key(key), + m_entry(BrowseCaller(*this)), + m_nonModal(ApplyCaller(*this), UpdateCaller(*this)) + { + m_nonModal.connect(m_entry.m_entry.m_entry); + } + void release() + { + delete this; + } + GtkWidget* getWidget() const + { + return GTK_WIDGET(m_entry.m_entry.m_frame); + } + void apply() + { + StringOutputStream value(64); + value << ConvertUTF8ToLocale(gtk_entry_get_text(GTK_ENTRY(m_entry.m_entry.m_entry))); + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), value.c_str()); + } + typedef MemberCaller ApplyCaller; + void update() + { + StringOutputStream value(64); + value << ConvertLocaleToUTF8(SelectedEntity_getValueForKey(m_key.c_str())); + gtk_entry_set_text(GTK_ENTRY(m_entry.m_entry.m_entry), value.c_str()); + } + typedef MemberCaller UpdateCaller; + void browse(const BrowsedPathEntry::SetPathCallback& setPath) + { + const char *filename = misc_model_dialog(gtk_widget_get_toplevel(GTK_WIDGET(m_entry.m_entry.m_frame))); + + if(filename != 0) + { + setPath(filename); + apply(); + } + } + typedef MemberCaller1 BrowseCaller; +}; + +const char* browse_sound(GtkWidget* parent) +{ + StringOutputStream buffer(1024); + + buffer << g_qeglobals.m_userGamePath.c_str() << "sound/"; + + if(!file_readable(buffer.c_str())) + { + // just go to fsmain + buffer.clear(); + buffer << g_qeglobals.m_userGamePath.c_str() << "/"; + } + + const char* filename = file_dialog(parent, TRUE, "Open Wav File", buffer.c_str(), "sound"); + if(filename != 0) + { + const char* relative = path_make_relative(filename, GlobalFileSystem().findRoot(filename)); + if(relative == filename) + { + globalOutputStream() << "WARNING: could not extract the relative path, using full path instead\n"; + } + return relative; + } + return filename; +} + +class SoundAttribute : public EntityAttribute +{ + CopiedString m_key; + BrowsedPathEntry m_entry; + NonModalEntry m_nonModal; +public: + SoundAttribute(const char* key) : + m_key(key), + m_entry(BrowseCaller(*this)), + m_nonModal(ApplyCaller(*this), UpdateCaller(*this)) + { + m_nonModal.connect(m_entry.m_entry.m_entry); + } + void release() + { + delete this; + } + GtkWidget* getWidget() const + { + return GTK_WIDGET(m_entry.m_entry.m_frame); + } + void apply() + { + StringOutputStream value(64); + value << ConvertUTF8ToLocale(gtk_entry_get_text(GTK_ENTRY(m_entry.m_entry.m_entry))); + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), value.c_str()); + } + typedef MemberCaller ApplyCaller; + void update() + { + StringOutputStream value(64); + value << ConvertLocaleToUTF8(SelectedEntity_getValueForKey(m_key.c_str())); + gtk_entry_set_text(GTK_ENTRY(m_entry.m_entry.m_entry), value.c_str()); + } + typedef MemberCaller UpdateCaller; + void browse(const BrowsedPathEntry::SetPathCallback& setPath) + { + const char *filename = browse_sound(gtk_widget_get_toplevel(GTK_WIDGET(m_entry.m_entry.m_frame))); + + if(filename != 0) + { + setPath(filename); + apply(); + } + } + typedef MemberCaller1 BrowseCaller; +}; + +inline double angle_normalised(double angle) +{ + return float_mod(angle, 360.0); +} + +class AngleAttribute : public EntityAttribute +{ + CopiedString m_key; + GtkEntry* m_entry; + NonModalEntry m_nonModal; +public: + AngleAttribute(const char* key) : + m_key(key), + m_entry(0), + m_nonModal(ApplyCaller(*this), UpdateCaller(*this)) + { + GtkEntry* entry = numeric_entry_new(); + m_entry = entry; + m_nonModal.connect(m_entry); + } + void release() + { + delete this; + } + GtkWidget* getWidget() const + { + return GTK_WIDGET(m_entry); + } + void apply() + { + StringOutputStream angle(32); + angle << angle_normalised(entry_get_float(m_entry)); + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), angle.c_str()); + } + typedef MemberCaller ApplyCaller; + + void update() + { + const char* value = SelectedEntity_getValueForKey(m_key.c_str()); + if(!string_empty(value)) + { + StringOutputStream angle(32); + angle << angle_normalised(atof(value)); + gtk_entry_set_text(m_entry, angle.c_str()); + } + else + { + gtk_entry_set_text(m_entry, "0"); + } + } + typedef MemberCaller UpdateCaller; +}; + +namespace +{ + typedef const char* String; + const String buttons[] = { "up", "down", "z-axis" }; +} + +class DirectionAttribute : public EntityAttribute +{ + CopiedString m_key; + GtkEntry* m_entry; + NonModalEntry m_nonModal; + RadioHBox m_radio; + NonModalRadio m_nonModalRadio; + GtkHBox* m_hbox; +public: + DirectionAttribute(const char* key) : + m_key(key), + m_entry(0), + m_nonModal(ApplyCaller(*this), UpdateCaller(*this)), + m_radio(RadioHBox_new(STRING_ARRAY_RANGE(buttons))), + m_nonModalRadio(ApplyRadioCaller(*this)) + { + GtkEntry* entry = numeric_entry_new(); + m_entry = entry; + m_nonModal.connect(m_entry); + + m_nonModalRadio.connect(m_radio.m_radio); + + m_hbox = GTK_HBOX(gtk_hbox_new(FALSE, 4)); + gtk_widget_show(GTK_WIDGET(m_hbox)); + + gtk_box_pack_start(GTK_BOX(m_hbox), GTK_WIDGET(m_radio.m_hbox), TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(m_hbox), GTK_WIDGET(m_entry), TRUE, TRUE, 0); + } + void release() + { + delete this; + } + GtkWidget* getWidget() const + { + return GTK_WIDGET(m_hbox); + } + void apply() + { + StringOutputStream angle(32); + angle << angle_normalised(entry_get_float(m_entry)); + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), angle.c_str()); + } + typedef MemberCaller ApplyCaller; + + void update() + { + const char* value = SelectedEntity_getValueForKey(m_key.c_str()); + if(!string_empty(value)) + { + float f = float(atof(value)); + if(f == -1) + { + gtk_widget_set_sensitive(GTK_WIDGET(m_entry), FALSE); + radio_button_set_active_no_signal(m_radio.m_radio, 0); + gtk_entry_set_text(m_entry, ""); + } + else if(f == -2) + { + gtk_widget_set_sensitive(GTK_WIDGET(m_entry), FALSE); + radio_button_set_active_no_signal(m_radio.m_radio, 1); + gtk_entry_set_text(m_entry, ""); + } + else + { + gtk_widget_set_sensitive(GTK_WIDGET(m_entry), TRUE); + radio_button_set_active_no_signal(m_radio.m_radio, 2); + StringOutputStream angle(32); + angle << angle_normalised(f); + gtk_entry_set_text(m_entry, angle.c_str()); + } + } + else + { + gtk_entry_set_text(m_entry, "0"); + } + } + typedef MemberCaller UpdateCaller; + + void applyRadio() + { + int index = radio_button_get_active(m_radio.m_radio); + if(index == 0) + { + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), "-1"); + } + else if(index == 1) + { + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), "-2"); + } + else if(index == 2) + { + apply(); + } + } + typedef MemberCaller ApplyRadioCaller; +}; + + +class AnglesEntry +{ +public: + GtkEntry* m_roll; + GtkEntry* m_pitch; + GtkEntry* m_yaw; + AnglesEntry() : m_roll(0), m_pitch(0), m_yaw(0) + { + } +}; + +typedef BasicVector3 DoubleVector3; + +class AnglesAttribute : public EntityAttribute +{ + CopiedString m_key; + AnglesEntry m_angles; + NonModalEntry m_nonModal; + GtkBox* m_hbox; +public: + AnglesAttribute(const char* key) : + m_key(key), + m_nonModal(ApplyCaller(*this), UpdateCaller(*this)) + { + m_hbox = GTK_BOX(gtk_hbox_new(TRUE, 4)); + gtk_widget_show(GTK_WIDGET(m_hbox)); + { + GtkEntry* entry = numeric_entry_new(); + gtk_box_pack_start(m_hbox, GTK_WIDGET(entry), TRUE, TRUE, 0); + m_angles.m_pitch = entry; + m_nonModal.connect(m_angles.m_pitch); + } + { + GtkEntry* entry = numeric_entry_new(); + gtk_box_pack_start(m_hbox, GTK_WIDGET(entry), TRUE, TRUE, 0); + m_angles.m_yaw = entry; + m_nonModal.connect(m_angles.m_yaw); + } + { + GtkEntry* entry = numeric_entry_new(); + gtk_box_pack_start(m_hbox, GTK_WIDGET(entry), TRUE, TRUE, 0); + m_angles.m_roll = entry; + m_nonModal.connect(m_angles.m_roll); + } + } + void release() + { + delete this; + } + GtkWidget* getWidget() const + { + return GTK_WIDGET(m_hbox); + } + void apply() + { + StringOutputStream angles(64); + angles << angle_normalised(entry_get_float(m_angles.m_pitch)) + << " " << angle_normalised(entry_get_float(m_angles.m_yaw)) + << " " << angle_normalised(entry_get_float(m_angles.m_roll)); + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), angles.c_str()); + } + typedef MemberCaller ApplyCaller; + + void update() + { + StringOutputStream angle(32); + const char* value = SelectedEntity_getValueForKey(m_key.c_str()); + if(!string_empty(value)) + { + DoubleVector3 pitch_yaw_roll; + if(!string_parse_vector3(value, pitch_yaw_roll)) + { + pitch_yaw_roll = DoubleVector3(0, 0, 0); + } + + angle << angle_normalised(pitch_yaw_roll.x()); + gtk_entry_set_text(m_angles.m_pitch, angle.c_str()); + angle.clear(); + + angle << angle_normalised(pitch_yaw_roll.y()); + gtk_entry_set_text(m_angles.m_yaw, angle.c_str()); + angle.clear(); + + angle << angle_normalised(pitch_yaw_roll.z()); + gtk_entry_set_text(m_angles.m_roll, angle.c_str()); + angle.clear(); + } + else + { + gtk_entry_set_text(m_angles.m_pitch, "0"); + gtk_entry_set_text(m_angles.m_yaw, "0"); + gtk_entry_set_text(m_angles.m_roll, "0"); + } + } + typedef MemberCaller UpdateCaller; +}; + +class Vector3Entry +{ +public: + GtkEntry* m_x; + GtkEntry* m_y; + GtkEntry* m_z; + Vector3Entry() : m_x(0), m_y(0), m_z(0) + { + } +}; + +class Vector3Attribute : public EntityAttribute +{ + CopiedString m_key; + Vector3Entry m_vector3; + NonModalEntry m_nonModal; + GtkBox* m_hbox; +public: + Vector3Attribute(const char* key) : + m_key(key), + m_nonModal(ApplyCaller(*this), UpdateCaller(*this)) + { + m_hbox = GTK_BOX(gtk_hbox_new(TRUE, 4)); + gtk_widget_show(GTK_WIDGET(m_hbox)); + { + GtkEntry* entry = numeric_entry_new(); + gtk_box_pack_start(m_hbox, GTK_WIDGET(entry), TRUE, TRUE, 0); + m_vector3.m_x = entry; + m_nonModal.connect(m_vector3.m_x); + } + { + GtkEntry* entry = numeric_entry_new(); + gtk_box_pack_start(m_hbox, GTK_WIDGET(entry), TRUE, TRUE, 0); + m_vector3.m_y = entry; + m_nonModal.connect(m_vector3.m_y); + } + { + GtkEntry* entry = numeric_entry_new(); + gtk_box_pack_start(m_hbox, GTK_WIDGET(entry), TRUE, TRUE, 0); + m_vector3.m_z = entry; + m_nonModal.connect(m_vector3.m_z); + } + } + void release() + { + delete this; + } + GtkWidget* getWidget() const + { + return GTK_WIDGET(m_hbox); + } + void apply() + { + StringOutputStream vector3(64); + vector3 << entry_get_float(m_vector3.m_x) + << " " << entry_get_float(m_vector3.m_y) + << " " << entry_get_float(m_vector3.m_z); + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), vector3.c_str()); + } + typedef MemberCaller ApplyCaller; + + void update() + { + StringOutputStream buffer(32); + const char* value = SelectedEntity_getValueForKey(m_key.c_str()); + if(!string_empty(value)) + { + DoubleVector3 x_y_z; + if(!string_parse_vector3(value, x_y_z)) + { + x_y_z = DoubleVector3(0, 0, 0); + } + + buffer << x_y_z.x(); + gtk_entry_set_text(m_vector3.m_x, buffer.c_str()); + buffer.clear(); + + buffer << x_y_z.y(); + gtk_entry_set_text(m_vector3.m_y, buffer.c_str()); + buffer.clear(); + + buffer << x_y_z.z(); + gtk_entry_set_text(m_vector3.m_z, buffer.c_str()); + buffer.clear(); + } + else + { + gtk_entry_set_text(m_vector3.m_x, "0"); + gtk_entry_set_text(m_vector3.m_y, "0"); + gtk_entry_set_text(m_vector3.m_z, "0"); + } + } + typedef MemberCaller UpdateCaller; +}; + +class NonModalComboBox +{ + Callback m_changed; + guint m_changedHandler; + + static gboolean changed(GtkComboBox *widget, NonModalComboBox* self) + { + self->m_changed(); + return FALSE; + } + +public: + NonModalComboBox(const Callback& changed) : m_changed(changed), m_changedHandler(0) + { + } + void connect(GtkComboBox* combo) + { + m_changedHandler = g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(changed), this); + } + void setActive(GtkComboBox* combo, int value) + { + g_signal_handler_disconnect(G_OBJECT(combo), m_changedHandler); + gtk_combo_box_set_active(combo, value); + connect(combo); + } +}; + +class ListAttribute : public EntityAttribute +{ + CopiedString m_key; + GtkComboBox* m_combo; + NonModalComboBox m_nonModal; + const ListAttributeType& m_type; +public: + ListAttribute(const char* key, const ListAttributeType& type) : + m_key(key), + m_combo(0), + m_nonModal(ApplyCaller(*this)), + m_type(type) + { + GtkComboBox* combo = GTK_COMBO_BOX(gtk_combo_box_new_text()); + + for(ListAttributeType::const_iterator i = type.begin(); i != type.end(); ++i) + { + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), (*i).first.c_str()); + } + + gtk_widget_show(GTK_WIDGET(combo)); + m_nonModal.connect(combo); + + m_combo = combo; + } + void release() + { + delete this; + } + GtkWidget* getWidget() const + { + return GTK_WIDGET(m_combo); + } + void apply() + { + Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), m_type[gtk_combo_box_get_active(m_combo)].second.c_str()); + } + typedef MemberCaller ApplyCaller; + + void update() + { + const char* value = SelectedEntity_getValueForKey(m_key.c_str()); + ListAttributeType::const_iterator i = m_type.findValue(value); + if(i != m_type.end()) + { + m_nonModal.setActive(m_combo, static_cast(std::distance(m_type.begin(), i))); + } + else + { + m_nonModal.setActive(m_combo, 0); + } + } + typedef MemberCaller UpdateCaller; +}; + + +namespace +{ + GtkWidget* g_entity_split1 = 0; + GtkWidget* g_entity_split2 = 0; + int g_entitysplit1_position; + int g_entitysplit2_position; + + bool g_entityInspector_windowConstructed = false; + + GtkTreeView* g_entityClassList; + GtkTextView* g_entityClassComment; + + GtkCheckButton* g_entitySpawnflagsCheck[MAX_FLAGS]; + + GtkEntry* g_entityKeyEntry; + GtkEntry* g_entityValueEntry; + + GtkListStore* g_entlist_store; + GtkListStore* g_entprops_store; + const EntityClass* g_current_flags = 0; + const EntityClass* g_current_comment = 0; + const EntityClass* g_current_attributes = 0; + + // the number of active spawnflags + int g_spawnflag_count; + // table: index, match spawnflag item to the spawnflag index (i.e. which bit) + int spawn_table[MAX_FLAGS]; + // we change the layout depending on how many spawn flags we need to display + // the table is a 4x4 in which we need to put the comment box g_entityClassComment and the spawn flags.. + GtkTable* g_spawnflagsTable; + + GtkVBox* g_attributeBox = 0; + typedef std::vector EntityAttributes; + EntityAttributes g_entityAttributes; +} + +void GlobalEntityAttributes_clear() +{ + for(EntityAttributes::iterator i = g_entityAttributes.begin(); i != g_entityAttributes.end(); ++i) + { + (*i)->release(); + } + g_entityAttributes.clear(); +} + +class GetKeyValueVisitor : public Entity::Visitor +{ + KeyValues& m_keyvalues; +public: + GetKeyValueVisitor(KeyValues& keyvalues) + : m_keyvalues(keyvalues) + { + } + + void visit(const char* key, const char* value) + { + m_keyvalues.insert(KeyValues::value_type(CopiedString(key), CopiedString(value))); + } + +}; + +void Entity_GetKeyValues(const Entity& entity, KeyValues& keyvalues, KeyValues& defaultValues) +{ + GetKeyValueVisitor visitor(keyvalues); + + entity.forEachKeyValue(visitor); + + const EntityClassAttributes& attributes = entity.getEntityClass().m_attributes; + + for(EntityClassAttributes::const_iterator i = attributes.begin(); i != attributes.end(); ++i) + { + defaultValues.insert(KeyValues::value_type((*i).first, (*i).second.m_value)); + } +} + +void Entity_GetKeyValues_Selected(KeyValues& keyvalues, KeyValues& defaultValues) +{ + class EntityGetKeyValues : public SelectionSystem::Visitor + { + KeyValues& m_keyvalues; + KeyValues& m_defaultValues; + mutable std::set m_visited; + public: + EntityGetKeyValues(KeyValues& keyvalues, KeyValues& defaultValues) + : m_keyvalues(keyvalues), m_defaultValues(defaultValues) + { + } + void visit(scene::Instance& instance) const + { + Entity* entity = Node_getEntity(instance.path().top()); + if(entity == 0 && instance.path().size() != 1) + { + entity = Node_getEntity(instance.path().parent()); + } + if(entity != 0 && m_visited.insert(entity).second) + { + Entity_GetKeyValues(*entity, m_keyvalues, m_defaultValues); + } + } + } visitor(keyvalues, defaultValues); + GlobalSelectionSystem().foreachSelected(visitor); +} + +const char* keyvalues_valueforkey(KeyValues& keyvalues, const char* key) +{ + KeyValues::iterator i = keyvalues.find(CopiedString(key)); + if(i != keyvalues.end()) + return (*i).second.c_str(); + return ""; +} + +class EntityClassListStoreAppend : public EntityClassVisitor +{ + GtkListStore* store; +public: + EntityClassListStoreAppend(GtkListStore* store_) : store(store_) + { + } + void visit(EntityClass* e) + { + GtkTreeIter iter; + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, e->name(), 1, e, -1); + } +}; + +void EntityClassList_fill() +{ + EntityClassListStoreAppend append(g_entlist_store); + GlobalEntityClassManager().forEach(append); +} + +void EntityClassList_clear() +{ + gtk_list_store_clear(g_entlist_store); +} + +void SetComment(EntityClass* eclass) +{ + if(eclass == g_current_comment) + return; + + g_current_comment = eclass; + + GtkTextBuffer* buffer = gtk_text_view_get_buffer(g_entityClassComment); + gtk_text_buffer_set_text(buffer, eclass->comments(), -1); +} + +void SurfaceFlags_setEntityClass(EntityClass* eclass) +{ + if(eclass == g_current_flags) + return; + + g_current_flags = eclass; + + int spawnflag_count = 0; + + { + // do a first pass to count the spawn flags, don't touch the widgets, we don't know in what state they are + for (int i=0 ; iflagnames[i] && eclass->flagnames[i][0] != 0 && strcmp(eclass->flagnames[i],"-")) + { + spawn_table[spawnflag_count] = i; + spawnflag_count++; + } + } + } + + // disable all remaining boxes + // NOTE: these boxes might not even be on display + { + for (int i = 0; i < g_spawnflag_count; ++i) + { + GtkWidget* widget = GTK_WIDGET(g_entitySpawnflagsCheck[i]); + gtk_label_set_text(GTK_LABEL(GTK_BIN(widget)->child), " "); + gtk_widget_hide(widget); + gtk_widget_ref(widget); + gtk_container_remove(GTK_CONTAINER(g_spawnflagsTable), widget); + } + } + + g_spawnflag_count = spawnflag_count; + + { + for (int i = 0; i < g_spawnflag_count; ++i) + { + GtkWidget* widget = GTK_WIDGET(g_entitySpawnflagsCheck[i]); + gtk_widget_show (widget); + + StringOutputStream str(16); + str << LowerCase(eclass->flagnames[spawn_table[i]]); + + gtk_table_attach(g_spawnflagsTable, widget, i%4, i%4+1, i/4, i/4+1, + (GtkAttachOptions)(GTK_FILL), + (GtkAttachOptions)(GTK_FILL), 0, 0); + gtk_widget_unref(widget); + + gtk_label_set_text(GTK_LABEL(GTK_BIN(widget)->child), str.c_str()); + } + } +} + +void EntityClassList_selectEntityClass(EntityClass* eclass) +{ + GtkTreeModel* model = GTK_TREE_MODEL(g_entlist_store); + GtkTreeIter iter; + for(gboolean good = gtk_tree_model_get_iter_first(model, &iter); good != FALSE; good = gtk_tree_model_iter_next(model, &iter)) + { + char* text; + gtk_tree_model_get(model, &iter, 0, &text, -1); + if (strcmp (text, eclass->name()) == 0) + { + GtkTreeView* view = g_entityClassList; + GtkTreePath* path = gtk_tree_model_get_path(model, &iter); + gtk_tree_selection_select_path(gtk_tree_view_get_selection(view), path); + if(GTK_WIDGET_REALIZED(view)) + { + gtk_tree_view_scroll_to_cell(view, path, 0, FALSE, 0, 0); + } + gtk_tree_path_free(path); + good = FALSE; + } + g_free(text); + } +} + +void EntityInspector_appendAttribute(const char* name, EntityAttribute& attribute) +{ + GtkTable* row = DialogRow_new(name, attribute.getWidget()); + DialogVBox_packRow(g_attributeBox, GTK_WIDGET(row)); +} + + +template +class StatelessAttributeCreator +{ +public: + static EntityAttribute* create(const char* name) + { + return new Attribute(name); + } +}; + +class EntityAttributeFactory +{ + typedef EntityAttribute* (*CreateFunc)(const char* name); + typedef std::map Creators; + Creators m_creators; +public: + EntityAttributeFactory() + { + m_creators.insert(Creators::value_type("string", &StatelessAttributeCreator::create)); + m_creators.insert(Creators::value_type("color", &StatelessAttributeCreator::create)); + m_creators.insert(Creators::value_type("integer", &StatelessAttributeCreator::create)); + m_creators.insert(Creators::value_type("real", &StatelessAttributeCreator::create)); + m_creators.insert(Creators::value_type("shader", &StatelessAttributeCreator::create)); + m_creators.insert(Creators::value_type("boolean", &StatelessAttributeCreator::create)); + m_creators.insert(Creators::value_type("angle", &StatelessAttributeCreator::create)); + m_creators.insert(Creators::value_type("direction", &StatelessAttributeCreator::create)); + m_creators.insert(Creators::value_type("angles", &StatelessAttributeCreator::create)); + m_creators.insert(Creators::value_type("model", &StatelessAttributeCreator::create)); + m_creators.insert(Creators::value_type("sound", &StatelessAttributeCreator::create)); + m_creators.insert(Creators::value_type("vector3", &StatelessAttributeCreator::create)); + } + EntityAttribute* create(const char* type, const char* name) + { + Creators::iterator i = m_creators.find(type); + if(i != m_creators.end()) + { + return (*i).second(name); + } + const ListAttributeType* listType = GlobalEntityClassManager().findListType(type); + if(listType != 0) + { + return new ListAttribute(name, *listType); + } + return 0; + } +}; + +typedef Static GlobalEntityAttributeFactory; + +void EntityInspector_setEntityClass(EntityClass *eclass) +{ + EntityClassList_selectEntityClass(eclass); + SurfaceFlags_setEntityClass(eclass); + + if(eclass != g_current_attributes) + { + g_current_attributes = eclass; + + container_remove_all(GTK_CONTAINER(g_attributeBox)); + GlobalEntityAttributes_clear(); + + for(EntityClassAttributes::const_iterator i = eclass->m_attributes.begin(); i != eclass->m_attributes.end(); ++i) + { + EntityAttribute* attribute = GlobalEntityAttributeFactory::instance().create((*i).second.m_type.c_str(), (*i).first.c_str()); + if(attribute != 0) + { + g_entityAttributes.push_back(attribute); + EntityInspector_appendAttribute(EntityClassAttributePair_getName(*i), *g_entityAttributes.back()); + } + } + } +} + +void EntityInspector_updateSpawnflags() +{ + { + int f = atoi(SelectedEntity_getValueForKey("spawnflags")); + for (int i = 0; i < g_spawnflag_count; ++i) + { + int v = !!(f&(1<update(); + } +} + +class EntityInspectorDraw +{ + IdleDraw m_idleDraw; +public: + EntityInspectorDraw() : m_idleDraw(FreeCaller()) + { + } + void queueDraw() + { + m_idleDraw.queueDraw(); + } +}; + +EntityInspectorDraw g_EntityInspectorDraw; + + +void EntityInspector_keyValueChanged() +{ + g_EntityInspectorDraw.queueDraw(); +} +void EntityInspector_selectionChanged(const Selectable&) +{ + EntityInspector_keyValueChanged(); +} + +// Creates a new entity based on the currently selected brush and entity type. +// +void EntityClassList_createEntity() +{ + GtkTreeView* view = g_entityClassList; + + // find out what type of entity we are trying to create + GtkTreeModel* model; + GtkTreeIter iter; + if(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(view), &model, &iter) == FALSE) + { + gtk_MessageBox(gtk_widget_get_toplevel(GTK_WIDGET(g_entityClassList)), "You must have a selected class to create an entity", "info"); + return; + } + + char* text; + gtk_tree_model_get(model, &iter, 0, &text, -1); + + { + StringOutputStream command; + command << "entityCreate -class " << text; + + UndoableCommand undo(command.c_str()); + + Entity_createFromSelection(text, g_vector3_identity); + } + g_free(text); +} + +void EntityInspector_applyKeyValue() +{ + // Get current selection text + StringOutputStream key(64); + key << ConvertUTF8ToLocale(gtk_entry_get_text(g_entityKeyEntry)); + StringOutputStream value(64); + value << ConvertUTF8ToLocale(gtk_entry_get_text(g_entityValueEntry)); + + + // TTimo: if you change the classname to worldspawn you won't merge back in the structural brushes but create a parasite entity + if (!strcmp(key.c_str(), "classname") && !strcmp(value.c_str(), "worldspawn")) + { + gtk_MessageBox(gtk_widget_get_toplevel(GTK_WIDGET(g_entityKeyEntry)), "Cannot change \"classname\" key back to worldspawn.", 0, eMB_OK ); + return; + } + + + // RR2DO2: we don't want spaces in entity keys + if (strstr( key.c_str(), " " )) + { + gtk_MessageBox(gtk_widget_get_toplevel(GTK_WIDGET(g_entityKeyEntry)), "No spaces are allowed in entity keys.", 0, eMB_OK ); + return; + } + + if(strcmp(key.c_str(), "classname") == 0) + { + StringOutputStream command; + command << "entitySetClass -class " << value.c_str(); + UndoableCommand undo(command.c_str()); + Scene_EntitySetClassname_Selected(value.c_str()); + } + else + { + Scene_EntitySetKeyValue_Selected_Undoable(key.c_str(), value.c_str()); + } +} + +void EntityInspector_clearKeyValue() +{ + // Get current selection text + StringOutputStream key(64); + key << ConvertUTF8ToLocale(gtk_entry_get_text(g_entityKeyEntry)); + + if(strcmp(key.c_str(), "classname") != 0) + { + StringOutputStream command; + command << "entityDeleteKey -key " << key.c_str(); + UndoableCommand undo(command.c_str()); + Scene_EntitySetKeyValue_Selected(key.c_str(), ""); + } +} + +void EntityInspector_clearAllKeyValues() +{ + UndoableCommand undo("entityClear"); + + // remove all keys except classname + for(KeyValues::iterator i = g_selectedKeyValues.begin(); i != g_selectedKeyValues.end(); ++i) + { + if(strcmp((*i).first.c_str(), "classname") != 0) + { + Scene_EntitySetKeyValue_Selected((*i).first.c_str(), ""); + } + } +} + +// ============================================================================= +// callbacks + +static void EntityClassList_selection_changed(GtkTreeSelection* selection, gpointer data) +{ + GtkTreeModel* model; + GtkTreeIter selected; + if(gtk_tree_selection_get_selected(selection, &model, &selected)) + { + EntityClass* eclass; + gtk_tree_model_get(model, &selected, 1, &eclass, -1); + if(eclass != 0) + { + SetComment(eclass); + } + } +} + +static gint EntityClassList_button_press(GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + if (event->type == GDK_2BUTTON_PRESS) + { + EntityClassList_createEntity(); + return TRUE; + } + return FALSE; +} + +static gint EntityClassList_keypress(GtkWidget* widget, GdkEventKey* event, gpointer data) +{ + unsigned int code = gdk_keyval_to_upper (event->keyval); + + if (event->keyval == GDK_Return) + { + EntityClassList_createEntity(); + return TRUE; + } + + // select the entity that starts with the key pressed + if (code <= 'Z' && code >= 'A') + { + GtkTreeView* view = g_entityClassList; + GtkTreeModel* model; + GtkTreeIter iter; + if(gtk_tree_selection_get_selected(gtk_tree_view_get_selection(view), &model, &iter) == FALSE + || gtk_tree_model_iter_next(model, &iter) == FALSE) + { + gtk_tree_model_get_iter_first(model, &iter); + } + + for(std::size_t count = gtk_tree_model_iter_n_children(model, 0); count > 0; --count) + { + char* text; + gtk_tree_model_get(model, &iter, 0, &text, -1); + + if (toupper (text[0]) == (int)code) + { + GtkTreePath* path = gtk_tree_model_get_path(model, &iter); + gtk_tree_selection_select_path(gtk_tree_view_get_selection(view), path); + if(GTK_WIDGET_REALIZED(view)) + { + gtk_tree_view_scroll_to_cell(view, path, 0, FALSE, 0, 0); + } + gtk_tree_path_free(path); + count = 1; + } + + g_free(text); + + if(gtk_tree_model_iter_next(model, &iter) == FALSE) + gtk_tree_model_get_iter_first(model, &iter); + } + + return TRUE; + } + return FALSE; +} + +static void EntityProperties_selection_changed(GtkTreeSelection* selection, gpointer data) +{ + // find out what type of entity we are trying to create + GtkTreeModel* model; + GtkTreeIter iter; + if(gtk_tree_selection_get_selected(selection, &model, &iter) == FALSE) + { + return; + } + + char* key; + char* val; + gtk_tree_model_get(model, &iter, 0, &key, 1, &val, -1); + + gtk_entry_set_text(g_entityKeyEntry, key); + gtk_entry_set_text(g_entityValueEntry, val); + + g_free(key); + g_free(val); +} + +static void SpawnflagCheck_toggled(GtkWidget *widget, gpointer data) +{ + EntityInspector_applySpawnflags(); +} + +static gint EntityEntry_keypress(GtkEntry* widget, GdkEventKey* event, gpointer data) +{ + if (event->keyval == GDK_Return) + { + if(widget == g_entityKeyEntry) + { + gtk_entry_set_text(g_entityValueEntry, ""); + gtk_window_set_focus(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(widget))), GTK_WIDGET(g_entityValueEntry)); + } + else + { + EntityInspector_applyKeyValue(); + } + return TRUE; + } + if (event->keyval == GDK_Escape) + { + gtk_window_set_focus(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(widget))), NULL); + return TRUE; + } + + return FALSE; +} + +void EntityInspector_destroyWindow(GtkWidget* widget, gpointer data) +{ + g_entitysplit1_position = gtk_paned_get_position(GTK_PANED(g_entity_split1)); + g_entitysplit2_position = gtk_paned_get_position(GTK_PANED(g_entity_split2)); + + g_entityInspector_windowConstructed = false; + GlobalEntityAttributes_clear(); +} + +GtkWidget* EntityInspector_constructWindow(GtkWindow* toplevel) +{ + GtkWidget* vbox = gtk_vbox_new(FALSE, 2); + gtk_widget_show (vbox); + gtk_container_set_border_width(GTK_CONTAINER (vbox), 2); + + g_signal_connect(G_OBJECT(vbox), "destroy", G_CALLBACK(EntityInspector_destroyWindow), 0); + + { + GtkWidget* split1 = gtk_vpaned_new(); + gtk_box_pack_start(GTK_BOX(vbox), split1, TRUE, TRUE, 0); + gtk_widget_show (split1); + + g_entity_split1 = split1; + + { + GtkWidget* split2 = gtk_vpaned_new(); + gtk_paned_add1 (GTK_PANED (split1), split2); + gtk_widget_show (split2); + + g_entity_split2 = split2; + + { + // class list + GtkWidget* scr = gtk_scrolled_window_new (0, 0); + gtk_widget_show (scr); + gtk_paned_add1 (GTK_PANED (split2), scr); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scr), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scr), GTK_SHADOW_IN); + + { + GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER); + + GtkTreeView* view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store))); + gtk_tree_view_set_enable_search(GTK_TREE_VIEW(view), FALSE); + gtk_tree_view_set_headers_visible(view, FALSE); + g_signal_connect(G_OBJECT(view), "button_press_event", G_CALLBACK(EntityClassList_button_press), 0); + g_signal_connect(G_OBJECT(view), "key_press_event", G_CALLBACK(EntityClassList_keypress), 0); + + { + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("Key", renderer, "text", 0, 0); + gtk_tree_view_append_column(view, column); + } + + { + GtkTreeSelection* selection = gtk_tree_view_get_selection(view); + g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(EntityClassList_selection_changed), 0); + } + + gtk_widget_show(GTK_WIDGET(view)); + + gtk_container_add(GTK_CONTAINER(scr), GTK_WIDGET(view)); + + g_object_unref(G_OBJECT(store)); + g_entityClassList = view; + g_entlist_store = store; + } + } + + { + GtkWidget* scr = gtk_scrolled_window_new (0, 0); + gtk_widget_show (scr); + gtk_paned_add2 (GTK_PANED (split2), scr); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scr), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scr), GTK_SHADOW_IN); + + { + GtkTextView* text = GTK_TEXT_VIEW(gtk_text_view_new()); + gtk_widget_set_size_request(GTK_WIDGET(text), 0, -1); // allow shrinking + gtk_text_view_set_wrap_mode(text, GTK_WRAP_WORD); + gtk_text_view_set_editable(text, FALSE); + gtk_widget_show(GTK_WIDGET(text)); + gtk_container_add(GTK_CONTAINER(scr), GTK_WIDGET(text)); + g_entityClassComment = text; + } + } + } + + { + GtkWidget* split2 = gtk_vpaned_new(); + gtk_paned_add2 (GTK_PANED (split1), split2); + gtk_widget_show(split2); + + { + GtkWidget* vbox2 = gtk_vbox_new (FALSE, 2); + gtk_widget_show (vbox2); + gtk_paned_pack1(GTK_PANED(split2), vbox2, FALSE, FALSE); + + { + // Spawnflags (4 colums wide max, or window gets too wide.) + GtkTable* table = GTK_TABLE(gtk_table_new(4, 4, FALSE)); + gtk_box_pack_start (GTK_BOX (vbox2), GTK_WIDGET(table), FALSE, TRUE, 0); + gtk_widget_show(GTK_WIDGET(table)); + + g_spawnflagsTable = table; + + for (int i = 0; i < MAX_FLAGS; i++) + { + GtkCheckButton* check = GTK_CHECK_BUTTON(gtk_check_button_new_with_label("")); + gtk_widget_ref(GTK_WIDGET(check)); + g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(g_signal_connect(G_OBJECT(check), "toggled", G_CALLBACK(SpawnflagCheck_toggled), 0))); + g_entitySpawnflagsCheck[i] = check; + } + } + + { + // key/value list + GtkWidget* scr = gtk_scrolled_window_new (0, 0); + gtk_widget_show (scr); + gtk_box_pack_start (GTK_BOX (vbox2), scr, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scr), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scr), GTK_SHADOW_IN); + + { + GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); + + GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + gtk_tree_view_set_enable_search(GTK_TREE_VIEW(view), FALSE); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); + + { + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("", renderer, "text", 0, 0); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + } + + { + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("", renderer, "text", 1, 0); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + } + + { + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); + g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(EntityProperties_selection_changed), 0); + } + + gtk_widget_show(view); + + gtk_container_add(GTK_CONTAINER (scr), view); + + g_object_unref(G_OBJECT(store)); + + g_entprops_store = store; + } + } + + { + // key/value entry + GtkTable* table = GTK_TABLE(gtk_table_new(2, 2, FALSE)); + gtk_widget_show(GTK_WIDGET(table)); + gtk_box_pack_start(GTK_BOX(vbox2), GTK_WIDGET(table), FALSE, TRUE, 0); + gtk_table_set_row_spacings(table, 3); + gtk_table_set_col_spacings(table, 5); + + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_events(GTK_WIDGET(entry), GDK_KEY_PRESS_MASK); + g_signal_connect(G_OBJECT(entry), "key_press_event", G_CALLBACK(EntityEntry_keypress), 0); + g_entityKeyEntry = entry; + } + + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_events(GTK_WIDGET(entry), GDK_KEY_PRESS_MASK); + g_signal_connect(G_OBJECT(entry), "key_press_event", G_CALLBACK(EntityEntry_keypress), 0); + g_entityValueEntry = entry; + } + + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Value")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 1, 2, + (GtkAttachOptions)(GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Key")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 0, 1, + (GtkAttachOptions)(GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + } + + { + GtkBox* hbox = GTK_BOX(gtk_hbox_new(TRUE, 4)); + gtk_widget_show(GTK_WIDGET(hbox)); + gtk_box_pack_start(GTK_BOX(vbox2), GTK_WIDGET(hbox), FALSE, TRUE, 0); + + { + GtkButton* button = GTK_BUTTON(gtk_button_new_with_label("Clear All")); + gtk_widget_show(GTK_WIDGET(button)); + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(EntityInspector_clearAllKeyValues), 0); + gtk_box_pack_start(hbox, GTK_WIDGET(button), TRUE, TRUE, 0); + } + { + GtkButton* button = GTK_BUTTON(gtk_button_new_with_label("Delete Key")); + gtk_widget_show(GTK_WIDGET(button)); + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(EntityInspector_clearKeyValue), 0); + gtk_box_pack_start(hbox, GTK_WIDGET(button), TRUE, TRUE, 0); + } + } + } + + { + GtkWidget* scr = gtk_scrolled_window_new(0, 0); + gtk_widget_show(scr); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scr), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + + GtkWidget* viewport = gtk_viewport_new(0, 0); + gtk_widget_show(viewport); + gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE); + + g_attributeBox = GTK_VBOX(gtk_vbox_new(FALSE, 2)); + gtk_widget_show(GTK_WIDGET(g_attributeBox)); + + gtk_container_add(GTK_CONTAINER(viewport), GTK_WIDGET(g_attributeBox)); + gtk_container_add(GTK_CONTAINER(scr), viewport); + gtk_paned_pack2(GTK_PANED(split2), scr, FALSE, FALSE); + } + } + } + + + { + // show the sliders in any case + if(g_entitysplit2_position > 22) + { + gtk_paned_set_position (GTK_PANED(g_entity_split2), g_entitysplit2_position); + } else { + g_entitysplit2_position = 22; + gtk_paned_set_position (GTK_PANED(g_entity_split2), 22); + } + if((g_entitysplit1_position - g_entitysplit2_position) > 27) + { + gtk_paned_set_position (GTK_PANED(g_entity_split1), g_entitysplit1_position); + } else { + gtk_paned_set_position (GTK_PANED(g_entity_split1), g_entitysplit2_position + 27); + } + } + + g_entityInspector_windowConstructed = true; + EntityClassList_fill(); + + typedef FreeCaller1 EntityInspectorSelectionChangedCaller; + GlobalSelectionSystem().addSelectionChangeCallback(EntityInspectorSelectionChangedCaller()); + GlobalEntityCreator().setKeyValueChangedFunc(EntityInspector_keyValueChanged); + + // hack + gtk_container_set_focus_chain(GTK_CONTAINER(vbox), NULL); + + return vbox; +} + +class EntityInspector : public ModuleObserver +{ + std::size_t m_unrealised; +public: + EntityInspector() : m_unrealised(1) + { + } + void realise() + { + if(--m_unrealised == 0) + { + if(g_entityInspector_windowConstructed) + { + //globalOutputStream() << "Entity Inspector: realise\n"; + EntityClassList_fill(); + } + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + if(g_entityInspector_windowConstructed) + { + //globalOutputStream() << "Entity Inspector: unrealise\n"; + EntityClassList_clear(); + } + } + } +}; + +EntityInspector g_EntityInspector; + +#include "preferencesystem.h" +#include "stringio.h" + +void EntityInspector_construct() +{ + GlobalEntityClassManager().attach(g_EntityInspector); + + GlobalPreferenceSystem().registerPreference("EntitySplit1", IntImportStringCaller(g_entitysplit1_position), IntExportStringCaller(g_entitysplit1_position)); + GlobalPreferenceSystem().registerPreference("EntitySplit2", IntImportStringCaller(g_entitysplit2_position), IntExportStringCaller(g_entitysplit2_position)); + +} + +void EntityInspector_destroy() +{ + GlobalEntityClassManager().detach(g_EntityInspector); +} + diff --git a/radiant/entityinspector.h b/radiant/entityinspector.h new file mode 100644 index 00000000..62a69ab6 --- /dev/null +++ b/radiant/entityinspector.h @@ -0,0 +1,31 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ENTITYINSPECTOR_H) +#define INCLUDED_ENTITYINSPECTOR_H + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkWindow GtkWindow; +GtkWidget* EntityInspector_constructWindow(GtkWindow* parent); +void EntityInspector_construct(); +void EntityInspector_destroy(); + +#endif diff --git a/radiant/entitylist.cpp b/radiant/entitylist.cpp new file mode 100644 index 00000000..e6944a6d --- /dev/null +++ b/radiant/entitylist.cpp @@ -0,0 +1,430 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "entitylist.h" + +#include "iselection.h" + +#include +#include +#include +#include + +#include "string/string.h" +#include "scenelib.h" +#include "nameable.h" +#include "signal/isignal.h" +#include "generic/object.h" + +#include "gtkutil/widget.h" +#include "gtkutil/window.h" +#include "gtkutil/idledraw.h" +#include "gtkutil/accelerator.h" +#include "gtkutil/closure.h" + +#include "treemodel.h" + +void RedrawEntityList(); +typedef FreeCaller RedrawEntityListCaller; + +typedef struct _GtkTreeView GtkTreeView; + +class EntityList +{ +public: + enum EDirty + { + eDefault, + eSelection, + eInsertRemove, + }; + + EDirty m_dirty; + + IdleDraw m_idleDraw; + WindowPositionTracker m_positionTracker; + + GtkWindow* m_window; + GtkTreeView* m_tree_view; + GraphTreeModel* m_tree_model; + bool m_selection_disabled; + + EntityList() : + m_dirty(EntityList::eDefault), + m_idleDraw(RedrawEntityListCaller()), + m_window(0), + m_selection_disabled(false) + { + } + + bool visible() const + { + return GTK_WIDGET_VISIBLE(GTK_WIDGET(m_window)); + } +}; + +namespace +{ + EntityList* g_EntityList; + + inline EntityList& getEntityList() + { + ASSERT_NOTNULL(g_EntityList); + return *g_EntityList; + } +} + + +inline Nameable* Node_getNameable(scene::Node& node) +{ + return NodeTypeCast::cast(node); +} + +const char* node_get_name(scene::Node& node) +{ + Nameable* nameable = Node_getNameable(node); + return (nameable != 0) + ? nameable->name() + : "node"; +} + +template +inline void gtk_tree_model_get_pointer(GtkTreeModel* model, GtkTreeIter* iter, gint column, value_type** pointer) +{ + GValue value = GValue_default(); + gtk_tree_model_get_value(model, iter, column, &value); + *pointer = (value_type*)g_value_get_pointer(&value); +} + + + +void entitylist_treeviewcolumn_celldatafunc(GtkTreeViewColumn* column, GtkCellRenderer* renderer, GtkTreeModel* model, GtkTreeIter* iter, gpointer data) +{ + scene::Node* node; + gtk_tree_model_get_pointer(model, iter, 0, &node); + scene::Instance* instance; + gtk_tree_model_get_pointer(model, iter, 1, &instance); + if(node != 0) + { + gtk_cell_renderer_set_fixed_size(renderer, -1, -1); + char* name = const_cast(node_get_name(*node)); + g_object_set(G_OBJECT(renderer), "text", name, "visible", TRUE, 0); + + //globalOutputStream() << "rendering cell " << makeQuoted(name) << "\n"; + GtkStyle* style = gtk_widget_get_style(GTK_WIDGET(getEntityList().m_tree_view)); + if(instance->childSelected()) + { + g_object_set(G_OBJECT(renderer), "cell-background-gdk", &style->base[GTK_STATE_ACTIVE], 0); + } + else + { + g_object_set(G_OBJECT(renderer), "cell-background-gdk", &style->base[GTK_STATE_NORMAL], 0); + } + } + else + { + gtk_cell_renderer_set_fixed_size(renderer, -1, 0); + g_object_set(G_OBJECT(renderer), "text", "", "visible", FALSE, 0); + } +} + +static gboolean entitylist_tree_select(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer data) +{ + GtkTreeIter iter; + gtk_tree_model_get_iter(model, &iter, path); + scene::Node* node; + gtk_tree_model_get_pointer(model, &iter, 0, &node); + scene::Instance* instance; + gtk_tree_model_get_pointer(model, &iter, 1, &instance); + Selectable* selectable = Instance_getSelectable(*instance); + + if(node == 0) + { + if(path_currently_selected != FALSE) + { + getEntityList().m_selection_disabled = true; + GlobalSelectionSystem().setSelectedAll(false); + getEntityList().m_selection_disabled = false; + } + } + else if(selectable != 0) + { + getEntityList().m_selection_disabled = true; + selectable->setSelected(path_currently_selected == FALSE); + getEntityList().m_selection_disabled = false; + return TRUE; + } + + return FALSE; +} + +static gboolean entitylist_tree_select_null(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer data) +{ + return TRUE; +} + +void EntityList_ConnectSignals(GtkTreeView* view) +{ + GtkTreeSelection* select = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_select_function(select, entitylist_tree_select, NULL, 0); +} + +void EntityList_DisconnectSignals(GtkTreeView* view) +{ + GtkTreeSelection* select = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_select_function(select, entitylist_tree_select_null, 0, 0); +} + + + +gboolean treemodel_update_selection(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, gpointer data) +{ + GtkTreeView* view = reinterpret_cast(data); + + scene::Instance* instance; + gtk_tree_model_get_pointer(model, iter, 1, &instance); + Selectable* selectable = Instance_getSelectable(*instance); + + if(selectable != 0) + { + GtkTreeSelection* selection = gtk_tree_view_get_selection(view); + if(selectable->isSelected()) + { + gtk_tree_selection_select_path(selection, path); + } + else + { + gtk_tree_selection_unselect_path(selection, path); + } + } + + return FALSE; +} + +void EntityList_UpdateSelection(GtkTreeModel* model, GtkTreeView* view) +{ + EntityList_DisconnectSignals(view); + gtk_tree_model_foreach(model, treemodel_update_selection, view); + EntityList_ConnectSignals(view); +} + + +void RedrawEntityList() +{ + switch(getEntityList().m_dirty) + { + case EntityList::eInsertRemove: + case EntityList::eSelection: + EntityList_UpdateSelection(GTK_TREE_MODEL(getEntityList().m_tree_model), getEntityList().m_tree_view); + default: + break; + } + getEntityList().m_dirty = EntityList::eDefault; +} + +void entitylist_queue_draw() +{ + getEntityList().m_idleDraw.queueDraw(); +} + +void EntityList_SelectionUpdate() +{ + if(getEntityList().m_selection_disabled) + return; + + if(getEntityList().m_dirty < EntityList::eSelection) + getEntityList().m_dirty = EntityList::eSelection; + entitylist_queue_draw(); +} + +void EntityList_SelectionChanged(const Selectable& selectable) +{ + EntityList_SelectionUpdate(); +} + +void entitylist_treeview_rowcollapsed(GtkTreeView* view, GtkTreeIter* iter, GtkTreePath* path, gpointer user_data) +{ +} + +void entitylist_treeview_row_expanded(GtkTreeView* view, GtkTreeIter* iter, GtkTreePath* path, gpointer user_data) +{ + EntityList_SelectionUpdate(); +} + + +void EntityList_SetShown(bool shown) +{ + widget_set_visible(GTK_WIDGET(getEntityList().m_window), shown); +} + +void EntityList_toggleShown() +{ + EntityList_SetShown(!getEntityList().visible()); +} + +gint graph_tree_model_compare_name(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) +{ + scene::Node* first; + gtk_tree_model_get(model, a, 0, (gpointer*)&first, -1); + scene::Node* second; + gtk_tree_model_get(model, b, 0, (gpointer*)&second, -1); + int result = 0; + if(first != 0 && second != 0) + { + result = string_compare(node_get_name(*first), node_get_name(*second)); + } + if(result == 0) + { + return (first < second) ? -1 : (second < first) ? 1 : 0; + } + return result; +} + +extern GraphTreeModel* scene_graph_get_tree_model(); +void AttachEntityTreeModel() +{ + getEntityList().m_tree_model = scene_graph_get_tree_model(); + + gtk_tree_view_set_model(getEntityList().m_tree_view, GTK_TREE_MODEL(getEntityList().m_tree_model)); +} + +void DetachEntityTreeModel() +{ + getEntityList().m_tree_model = 0; + + gtk_tree_view_set_model(getEntityList().m_tree_view, 0); +} + +void EntityList_constructWindow(GtkWindow* main_window) +{ + ASSERT_MESSAGE(getEntityList().m_window == 0, "error"); + + GtkWindow* window = create_persistent_floating_window("Entity List", main_window); + + gtk_window_add_accel_group(window, global_accel); + + getEntityList().m_positionTracker.connect(window); + + + getEntityList().m_window = window; + + { + GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(scr)); + + { + GtkWidget* view = gtk_tree_view_new(); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); + + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = gtk_tree_view_column_new(); + gtk_tree_view_column_pack_start(column, renderer, TRUE); + gtk_tree_view_column_set_cell_data_func(column, renderer, entitylist_treeviewcolumn_celldatafunc, 0, 0); + + GtkTreeSelection* select = gtk_tree_view_get_selection (GTK_TREE_VIEW(view)); + gtk_tree_selection_set_mode(select, GTK_SELECTION_MULTIPLE); + + g_signal_connect(G_OBJECT(view), "row_expanded", G_CALLBACK(entitylist_treeview_row_expanded), 0); + g_signal_connect(G_OBJECT(view), "row_collapsed", G_CALLBACK(entitylist_treeview_rowcollapsed), 0); + + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + + gtk_widget_show(view); + gtk_container_add (GTK_CONTAINER(scr), view); + getEntityList().m_tree_view = GTK_TREE_VIEW(view); + } + } + + EntityList_ConnectSignals(getEntityList().m_tree_view); + AttachEntityTreeModel(); +} + +void EntityList_destroyWindow() +{ + DetachEntityTreeModel(); + EntityList_DisconnectSignals(getEntityList().m_tree_view); + destroy_floating_window(getEntityList().m_window); +} + +#include "preferencesystem.h" + +#include "iselection.h" + +namespace +{ + scene::Node* nullNode = 0; +} + +class NullSelectedInstance : public scene::Instance, public Selectable +{ + class TypeCasts + { + InstanceTypeCastTable m_casts; + public: + TypeCasts() + { + InstanceStaticCast::install(m_casts); + } + InstanceTypeCastTable& get() + { + return m_casts; + } + }; + +public: + typedef LazyStatic StaticTypeCasts; + + NullSelectedInstance() : Instance(scene::Path(makeReference(*nullNode)), 0, this, StaticTypeCasts::instance().get()) + { + } + + void setSelected(bool select) + { + ERROR_MESSAGE("error"); + } + bool isSelected() const + { + return true; + } +}; + +typedef LazyStatic StaticNullSelectedInstance; + + +void EntityList_Construct() +{ + graph_tree_model_insert(scene_graph_get_tree_model(), StaticNullSelectedInstance::instance()); + + g_EntityList = new EntityList; + + getEntityList().m_positionTracker.setPosition(c_default_window_pos); + + GlobalPreferenceSystem().registerPreference("EntityInfoDlg", WindowPositionTrackerImportStringCaller(getEntityList().m_positionTracker), WindowPositionTrackerExportStringCaller(getEntityList().m_positionTracker)); + + typedef FreeCaller1 EntityListSelectionChangedCaller; + GlobalSelectionSystem().addSelectionChangeCallback(EntityListSelectionChangedCaller()); +} +void EntityList_Destroy() +{ + delete g_EntityList; + + graph_tree_model_erase(scene_graph_get_tree_model(), StaticNullSelectedInstance::instance()); +} diff --git a/radiant/entitylist.h b/radiant/entitylist.h new file mode 100644 index 00000000..cdad18db --- /dev/null +++ b/radiant/entitylist.h @@ -0,0 +1,33 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ENTITYLIST_H) +#define INCLUDED_ENTITYLIST_H + +void EntityList_Construct(); +void EntityList_Destroy(); + +typedef struct _GtkWindow GtkWindow; +void EntityList_constructWindow(GtkWindow* main_window); +void EntityList_destroyWindow(); +void EntityList_toggleShown(); + +#endif diff --git a/radiant/environment.cpp b/radiant/environment.cpp new file mode 100644 index 00000000..a3bfd43f --- /dev/null +++ b/radiant/environment.cpp @@ -0,0 +1,200 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "environment.h" + +#include "stream/textstream.h" +#include "string/string.h" +#include "stream/stringstream.h" +#include "debugging/debugging.h" +#include "os/path.h" +#include "cmdlib.h" + + +int g_argc; +char** g_argv; + +void args_init(int argc, char* argv[]) +{ + int i, j, k; + + for (i = 1; i < argc; i++) + { + for (k = i; k < argc; k++) + if (argv[k] != 0) + break; + + if (k > i) + { + k -= i; + for (j = i + k; j < argc; j++) + argv[j-k] = argv[j]; + argc -= k; + } + } + + g_argc = argc; + g_argv = argv; +} + +namespace +{ + CopiedString home_path; + CopiedString app_path; +} + +const char* environment_get_home_path() +{ + return home_path.c_str(); +} + +const char* environment_get_app_path() +{ + return app_path.c_str(); +} + + +#if defined(POSIX) + +#include +#include +#include + +#include + +const char* LINK_NAME = +#if defined (__linux__) + "/proc/self/exe" +#else // FreeBSD and OSX + "/proc/curproc/file" +#endif +; + +/// brief Returns the filename of the executable belonging to the current process, or 0 if not found. +char* getexename(char *buf) +{ + /* Now read the symbolic link */ + int ret = readlink(LINK_NAME, buf, PATH_MAX); + + if(ret == -1) + { + globalOutputStream() << "getexename: falling back to argv[0]: " << makeQuoted(g_argv[0]); + const char* path = realpath(g_argv[0], buf); + if(path == 0) + { + /* In case of an error, leave the handling up to the caller */ + return ""; + } + } + + /* Ensure proper NUL termination */ + buf[ret] = 0; + + /* delete the program name */ + *(strrchr(buf, '/')) = '\0'; + + // NOTE: we build app path with a trailing '/' + // it's a general convention in Radiant to have the slash at the end of directories + if (buf[strlen(buf)-1] != '/') + { + strcat(buf, "/"); + } + + return buf; +} + +void environment_init(int argc, char* argv[]) +{ + // Give away unnecessary root privileges. + // Important: must be done before calling gtk_init(). + char *loginname; + struct passwd *pw; + seteuid(getuid()); + if (geteuid() == 0 && (loginname = getlogin()) != 0 && + (pw = getpwnam(loginname)) != 0) + setuid(pw->pw_uid); + + args_init(argc, argv); + + { + StringOutputStream home(256); + home << DirectoryCleaned(g_get_home_dir()) << ".radiant/"; + Q_mkdir(home.c_str()); + home_path = home.c_str(); + } + { + char real[PATH_MAX]; + app_path = getexename(real); + ASSERT_MESSAGE(!string_empty(app_path.c_str()), "failed to deduce app path"); + } +} + +#elif defined(WIN32) + +#include +#include + +void environment_init(int argc, char* argv[]) +{ + args_init(argc, argv); + + { + char appdata[MAX_PATH+1]; + SHGetFolderPath(0, CSIDL_APPDATA, 0, 0, appdata); + + StringOutputStream home(256); + if(string_empty(appdata)) + { + ERROR_MESSAGE("Application Data folder not available.\n" + "Please install shfolder redistributable package.\n" + "Radiant will use C:\\ for user preferences.\n"); + home << "C:"; + } + else + { + home << PathCleaned(appdata); + } + home << "/RadiantSettings/"; + Q_mkdir(home.c_str()); + home_path = home.c_str(); + } + { + // get path to the editor + char filename[MAX_PATH+1]; + GetModuleFileName(0, filename, MAX_PATH); + char* last_separator = strrchr(filename, '\\'); + if(last_separator != 0) + { + *(last_separator+1) = '\0'; + } + else + { + filename[0] = '\0'; + } + StringOutputStream app(256); + app << PathCleaned(filename); + app_path = app.c_str(); + } +} + +#else +#error "unsupported platform" +#endif diff --git a/radiant/environment.h b/radiant/environment.h new file mode 100644 index 00000000..7667fbfe --- /dev/null +++ b/radiant/environment.h @@ -0,0 +1,29 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ENVIRONMENT_H) +#define INCLUDED_ENVIRONMENT_H + +void environment_init(int argc, char* argv[]); +const char* environment_get_home_path(); +const char* environment_get_app_path(); + +#endif diff --git a/radiant/error.cpp b/radiant/error.cpp new file mode 100644 index 00000000..0ff75d1c --- /dev/null +++ b/radiant/error.cpp @@ -0,0 +1,138 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "error.h" + +#include "debugging/debugging.h" +#include "igl.h" + +#include "gtkutil/messagebox.h" +#include "console.h" +#include "preferences.h" + + +#ifdef WIN32 +#define UNICODE +#include +#else +#include +#include +#endif + + + +/* +================= +Error + +For abnormal program terminations +================= +*/ + +/*! +\todo +FIXME the prompt wether to do prefs dialog, may not even be possible +if the crash happens before the game is loaded +*/ + +void Error (const char *error, ...) +{ + va_list argptr; + char text[4096]; + + va_start (argptr,error); + vsprintf (text, error,argptr); + va_end (argptr); + + strcat( text, "\n" ); + +#ifdef WIN32 + if (GetLastError() != 0) + { + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + 0 + ); + strcat( text, "GetLastError: " ); + /* + Gtk will only crunch 0<=char<=127 + this is a bit hackish, but I didn't find useful functions in win32 API for this + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=516 + */ + TCHAR *scan, *next = (TCHAR*)lpMsgBuf; + do + { + scan = next; + text[strlen(text)+1] = '\0'; + if ((scan[0] >= 0) && (scan[0] <= 127)) + text[strlen(text)] = char(scan[0]); + else + text[strlen(text)] = '?'; + next = CharNext(scan); + } while (next != scan); + strcat( text, "\n"); + LocalFree( lpMsgBuf ); + } +#else + if (errno != 0) + { + strcat( text, "errno: " ); + strcat( text, strerror (errno)); + strcat( text, "\n"); + } +#endif + + +#if 0 + // we need to have a current context to call glError() + if (g_glwindow_globals.d_glBase != 0) + { + // glGetError .. can record several errors, clears after calling + //++timo TODO: be able to deal with several errors if necessary, for now I'm just warning about pending error messages + // NOTE: forget that, most boards don't seem to follow the OpenGL standard + GLenum iGLError = glGetError(); + if (iGLError != GL_NO_ERROR) + { + // use our own gluErrorString + strcat( text, "gluErrorString: " ); + strcat( text, (char*)gluErrorString( iGLError ) ); + strcat( text, "\n" ); + } + } +#endif + + strcat (text, "An unrecoverable error has occured.\n"); + + ERROR_MESSAGE(text); + + // force close logging if necessary + Sys_LogFile(false); + + _exit (1); +} diff --git a/radiant/error.h b/radiant/error.h new file mode 100644 index 00000000..b114640a --- /dev/null +++ b/radiant/error.h @@ -0,0 +1,27 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_ERROR_H) +#define INCLUDED_ERROR_H + +void Error(const char *error, ...); + +#endif diff --git a/radiant/feedback.cpp b/radiant/feedback.cpp new file mode 100644 index 00000000..9a847ce2 --- /dev/null +++ b/radiant/feedback.cpp @@ -0,0 +1,372 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//----------------------------------------------------------------------------- +// +// DESCRIPTION: +// classes used for describing geometry information from q3map feedback +// + +#include "feedback.h" + +#include "debugging/debugging.h" + +#include "igl.h" +#include "iselection.h" + +#include +#include +#include +#include +#include +#include + +#include "map.h" +#include "dialog.h" +#include "mainframe.h" + + +CDbgDlg g_DbgDlg; + +void Feedback_draw2D(VIEWTYPE viewType) +{ + g_DbgDlg.draw2D(viewType); +} + +void CSelectMsg::saxStartElement(message_info_t *ctx, const xmlChar *name, const xmlChar **attrs) +{ + if(string_equal(reinterpret_cast(name), "select")) + { + // read the message + ESelectState = SELECT_MESSAGE; + } + else + { + // read the brush + ASSERT_MESSAGE(string_equal(reinterpret_cast(name), "brush"), "FEEDBACK PARSE ERROR"); + ASSERT_MESSAGE(ESelectState == SELECT_MESSAGE, "FEEDBACK PARSE ERROR"); + ESelectState = SELECT_BRUSH; + globalOutputStream() << message.c_str() << '\n'; + } +} + +void CSelectMsg::saxEndElement(message_info_t *ctx, const xmlChar *name) +{ + if(string_equal(reinterpret_cast(name), "select")) + { + } +} + +void CSelectMsg::saxCharacters(message_info_t *ctx, const xmlChar *ch, int len) +{ + if(ESelectState == SELECT_MESSAGE) + { + message.write(reinterpret_cast(ch), len); + } + else + { + brush.write(reinterpret_cast(ch), len); + } +} + +IGL2DWindow* CSelectMsg::Highlight() +{ + GlobalSelectionSystem().setSelectedAll(false); + int entitynum, brushnum; + if(sscanf(reinterpret_cast(brush.c_str()), "%i %i", &entitynum, &brushnum) == 2) + { + SelectBrush (entitynum, brushnum); + } + return 0; +} + +void CPointMsg::saxStartElement(message_info_t *ctx, const xmlChar *name, const xmlChar **attrs) +{ + if(string_equal(reinterpret_cast(name), "pointmsg")) + { + // read the message + EPointState = POINT_MESSAGE; + } + else + { + // read the brush + ASSERT_MESSAGE(string_equal(reinterpret_cast(name), "point"), "FEEDBACK PARSE ERROR"); + ASSERT_MESSAGE(EPointState == POINT_MESSAGE, "FEEDBACK PARSE ERROR"); + EPointState = POINT_POINT; + globalOutputStream() << message.c_str() << '\n'; + } +} + +void CPointMsg::saxEndElement (message_info_t *ctx, const xmlChar *name) +{ + if(string_equal(reinterpret_cast(name), "pointmsg")) + { + } + else if(string_equal(reinterpret_cast(name), "point")) + { + sscanf(point.c_str(), "%g %g %g", &(pt[0]), &(pt[1]), &(pt[2])); + point.clear(); + } +} + +void CPointMsg::saxCharacters (message_info_t *ctx, const xmlChar *ch, int len) +{ + if(EPointState == POINT_MESSAGE) + { + message.write(reinterpret_cast(ch), len); + } + else + { + ASSERT_MESSAGE(EPointState == POINT_POINT, "FEEDBACK PARSE ERROR"); + point.write(reinterpret_cast(ch), len); + } +} + +IGL2DWindow* CPointMsg::Highlight() +{ + return this; +} + +void CPointMsg::DropHighlight() +{ +} + +void CPointMsg::Draw2D( VIEWTYPE vt ) +{ + int nDim1 = (vt == YZ) ? 1 : 0; + int nDim2 = (vt == XY) ? 1 : 2; + glPointSize(4); + glColor3f(1.0f,0.0f,0.0f); + glBegin (GL_POINTS); + glVertex2f (pt[nDim1], pt[nDim2]); + glEnd(); + glBegin (GL_LINE_LOOP); + glVertex2f (pt[nDim1]-8, pt[nDim2]-8); + glVertex2f (pt[nDim1]+8, pt[nDim2]-8); + glVertex2f (pt[nDim1]+8, pt[nDim2]+8); + glVertex2f (pt[nDim1]-8, pt[nDim2]+8); + glEnd(); +} + +void CWindingMsg::saxStartElement(message_info_t *ctx, const xmlChar *name, const xmlChar **attrs) +{ + if(string_equal(reinterpret_cast(name), "windingmsg")) + { + // read the message + EPointState = WINDING_MESSAGE; + } + else + { + // read the brush + ASSERT_MESSAGE(string_equal(reinterpret_cast(name), "winding"), "FEEDBACK PARSE ERROR"); + ASSERT_MESSAGE(EPointState == WINDING_MESSAGE, "FEEDBACK PARSE ERROR"); + EPointState = WINDING_WINDING; + globalOutputStream() << message.c_str() << '\n'; + } +} + +void CWindingMsg::saxEndElement(message_info_t *ctx, const xmlChar *name) +{ + if(string_equal(reinterpret_cast(name), "windingmsg")) + { + } + else if(string_equal(reinterpret_cast(name), "winding")) + { + const char* c = winding.c_str(); + sscanf(c, "%i ", &numpoints); + + int i = 0; + for(; i < numpoints; i++) + { + c = strchr(c + 1, '('); + if(c) // even if we are given the number of points when the cycle begins .. don't trust it too much + sscanf(c, "(%g %g %g)", &wt[i][0], &wt[i][1], &wt[i][2]); + else + break; + } + numpoints = i; + } +} + +void CWindingMsg::saxCharacters(message_info_t *ctx, const xmlChar *ch, int len) +{ + if(EPointState == WINDING_MESSAGE) + { + message.write(reinterpret_cast(ch), len); + } + else + { + ASSERT_MESSAGE(EPointState == WINDING_WINDING, "FEEDBACK PARSE ERROR"); + winding.write(reinterpret_cast(ch), len); + } +} + +IGL2DWindow* CWindingMsg::Highlight() +{ + return this; +} + +void CWindingMsg::DropHighlight() +{ +} + +void CWindingMsg::Draw2D( VIEWTYPE vt ) +{ + int i; + + int nDim1 = (vt == YZ) ? 1 : 0; + int nDim2 = (vt == XY) ? 1 : 2; + glColor3f(1.0f,0.f,0.0f); + + glPointSize(4); + glBegin (GL_POINTS); + for(i = 0; i < numpoints; i++) + glVertex2f (wt[i][nDim1], wt[i][nDim2]); + glEnd(); + glPointSize(1); + + glEnable (GL_BLEND); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(0.133f,0.4f,1.0f,0.5f); + glBegin (GL_POLYGON); + for(i = 0; i < numpoints; i++) + glVertex2f (wt[i][nDim1], wt[i][nDim2]); + glEnd(); + glDisable (GL_BLEND); +} + +// triggered when the user selects an entry in the feedback box +static void feedback_selection_changed(GtkTreeSelection* selection, gpointer data) +{ + g_DbgDlg.DropHighlight(); + + GtkTreeModel* model; + GtkTreeIter selected; + if(gtk_tree_selection_get_selected(selection, &model, &selected)) + { + GtkTreePath* path = gtk_tree_model_get_path(model, &selected); + g_DbgDlg.SetHighlight(gtk_tree_path_get_indices(path)[0]); + gtk_tree_path_free(path); + } +} + +void CDbgDlg::DropHighlight() +{ + if(m_pHighlight != 0) + { + m_pHighlight->DropHighlight(); + m_pHighlight = 0; + m_pDraw2D = 0; + } +} + +void CDbgDlg::SetHighlight(gint row) +{ + ISAXHandler *h = GetElement(row); + if(h != NULL) + { + m_pDraw2D = h->Highlight(); + m_pHighlight = h; + } +} + +ISAXHandler *CDbgDlg::GetElement (std::size_t row) +{ + return static_cast(g_ptr_array_index(m_pFeedbackElements, gint(row))); +} + +void CDbgDlg::Init() +{ + DropHighlight(); + + // free all the ISAXHandler*, clean it + while (m_pFeedbackElements->len) + { + static_cast(g_ptr_array_index (m_pFeedbackElements, 0))->Release(); + g_ptr_array_remove_index (m_pFeedbackElements, 0); + } + + if(m_clist != NULL) + gtk_list_store_clear (m_clist); +} + +void CDbgDlg::Push (ISAXHandler *pHandler) +{ + // push in the list + g_ptr_array_add (m_pFeedbackElements, (void *)pHandler); + + if(GetWidget() == 0) + { + Create(); + } + + // put stuff in the list + gtk_list_store_clear (m_clist); + for(std::size_t i = 0; i < static_cast(m_pFeedbackElements->len); ++i) + { + GtkTreeIter iter; + gtk_list_store_append(m_clist, &iter); + gtk_list_store_set(m_clist, &iter, 0, GetElement(i)->getName(), -1); + } + + ShowDlg(); +} + +GtkWindow* CDbgDlg::BuildDialog() +{ + GtkWindow* window = create_floating_window("Q3Map debug window", MainFrame_getWindow()); + + GtkWidget* scr = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (scr); + gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (scr)); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scr), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scr), GTK_SHADOW_IN); + + { + GtkListStore* store = gtk_list_store_new(1, G_TYPE_STRING); + + GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); + + { + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("", renderer, "text", 0, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + } + + { + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE); + g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(feedback_selection_changed), NULL); + } + + gtk_widget_show(view); + + gtk_container_add(GTK_CONTAINER (scr), view); + + g_object_unref(G_OBJECT(store)); + + m_clist = store; + } + + return window; +} diff --git a/radiant/feedback.h b/radiant/feedback.h new file mode 100644 index 00000000..4d199bd4 --- /dev/null +++ b/radiant/feedback.h @@ -0,0 +1,186 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//----------------------------------------------------------------------------- +// +// DESCRIPTION: +// classes used for describing geometry information from q3map feedback +// + +#ifndef __Q3MAP_FEEDBACK__ +#define __Q3MAP_FEEDBACK__ + +#include "math/vector.h" +#include "stream/stringstream.h" +#include +#include "xmlstuff.h" +#include "dialog.h" +#include "xywindow.h" + +// we use these classes to let plugins draw inside the Radiant windows +// 2D window like YZ XZ XY +class IGL2DWindow +{ +public: + // Increment the number of references to this object + virtual void IncRef() = 0; + // Decrement the reference count + virtual void DecRef() = 0; + virtual void Draw2D( VIEWTYPE vt ) = 0; +}; + +// 3D window +class IGL3DWindow +{ +public: + // Increment the number of references to this object + virtual void IncRef() = 0; + // Decrement the reference count + virtual void DecRef() = 0; + virtual void Draw3D() = 0; +}; + +// a select message with a brush/entity select information +class CSelectMsg : public ISAXHandler +{ + enum { SELECT_MESSAGE, SELECT_BRUSH } ESelectState; + StringOutputStream message; + StringOutputStream brush; +public: + CSelectMsg() { ESelectState = SELECT_MESSAGE; } + // SAX interface + void saxStartElement (message_info_t *ctx, const xmlChar *name, const xmlChar **attrs); + void saxEndElement (message_info_t *ctx, const xmlChar *name); + void saxCharacters (message_info_t *ctx, const xmlChar *ch, int len); + // for use in the dialog window + const char* getName() { return message.c_str(); } + IGL2DWindow* Highlight(); + void DropHighlight() { } +}; + +class CPointMsg : public ISAXHandler, public IGL2DWindow +{ + enum { POINT_MESSAGE, POINT_POINT } EPointState; + StringOutputStream message; + StringOutputStream point; + Vector3 pt; + int refCount; +public: + CPointMsg() { EPointState = POINT_MESSAGE; refCount = 0; } + // SAX interface + void Release() + { + delete this; + } + void saxStartElement (message_info_t *ctx, const xmlChar *name, const xmlChar **attrs); + void saxEndElement (message_info_t *ctx, const xmlChar *name); + void saxCharacters (message_info_t *ctx, const xmlChar *ch, int len); + // for use in the dialog window + const char* getName() { return message.c_str(); } + IGL2DWindow* Highlight(); + void DropHighlight(); + + // IGL2DWindow interface -------------------------------- + // Increment the number of references to this object + void IncRef() { refCount++; } + // Decrement the reference count + void DecRef() { refCount--; if (refCount <= 0) delete this; } + void Draw2D( VIEWTYPE vt ); +}; + +class CWindingMsg : public ISAXHandler, public IGL2DWindow +{ + enum { WINDING_MESSAGE, WINDING_WINDING } EPointState; + StringOutputStream message; + StringOutputStream winding; + Vector3 wt[256]; + int numpoints; + int refCount; +public: + CWindingMsg() { EPointState = WINDING_MESSAGE; refCount = 0; numpoints = 0; } + // SAX interface + void Release() + { + delete this; + } + void saxStartElement (message_info_t *ctx, const xmlChar *name, const xmlChar **attrs); + void saxEndElement (message_info_t *ctx, const xmlChar *name); + void saxCharacters (message_info_t *ctx, const xmlChar *ch, int len); + // for use in the dialog window + const char* getName() { return message.c_str(); } + IGL2DWindow* Highlight(); + void DropHighlight(); + + // IGL2DWindow interface -------------------------------- + // Increment the number of references to this object + void IncRef() { refCount++; } + // Decrement the reference count + void DecRef() { refCount--; if (refCount <= 0) delete this; } + void Draw2D( VIEWTYPE vt ); +}; + +typedef struct _GtkListStore GtkListStore; + +class CDbgDlg : public Dialog +{ + GPtrArray *m_pFeedbackElements; + // the list widget we use in the dialog + GtkListStore* m_clist; + ISAXHandler *m_pHighlight; + IGL2DWindow* m_pDraw2D; +public: + CDbgDlg() + { + m_pFeedbackElements = g_ptr_array_new(); + m_pHighlight = NULL; + m_pDraw2D = NULL; + } + // refresh items + void Push (ISAXHandler *); + // clean the debug window, release all ISAXHanlders we have + void Init(); + ISAXHandler *GetElement(std::size_t row); + void SetHighlight(gint row); + void DropHighlight(); + void draw2D(VIEWTYPE viewType) + { + if(m_pDraw2D != 0) + { + m_pDraw2D->Draw2D(viewType); + } + } + void destroyWindow() + { + if(GetWidget() != 0) + { + Destroy(); + } + } +// void HideDlg(); +protected: + GtkWindow* BuildDialog(); +}; + +extern CDbgDlg g_DbgDlg; + +void Feedback_draw2D(VIEWTYPE viewType); + +#endif diff --git a/radiant/filetypes.cpp b/radiant/filetypes.cpp new file mode 100644 index 00000000..b76b968e --- /dev/null +++ b/radiant/filetypes.cpp @@ -0,0 +1,139 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "filetypes.h" + +#include "debugging/debugging.h" + +#include "ifiletypes.h" + +#include "string/string.h" +#include "os/path.h" +#include +#include + +class RadiantFileTypeRegistry : public IFileTypeRegistry +{ + struct filetype_copy_t + { + filetype_copy_t(const char* moduleName, const filetype_t other) + : m_moduleName(moduleName), m_name(other.name), m_pattern(other.pattern) + { + } + const char* getModuleName() const + { + return m_moduleName.c_str(); + } + filetype_t getType() const + { + return filetype_t(m_name.c_str(), m_pattern.c_str()); + } + private: + CopiedString m_moduleName; + CopiedString m_name; + CopiedString m_pattern; + }; + typedef std::vector filetype_list_t; + std::map m_typelists; +public: + RadiantFileTypeRegistry() + { + addType("*", "*", filetype_t("All Files", "*.*")); + } + void addType(const char* moduleType, const char* moduleName, filetype_t type) + { + m_typelists[moduleType].push_back(filetype_copy_t(moduleName, type)); + } + void getTypeList(const char* moduleType, IFileTypeList* typelist) + { + filetype_list_t& list_ref = m_typelists[moduleType]; + for(filetype_list_t::iterator i = list_ref.begin(); i != list_ref.end(); ++i) + { + typelist->addType((*i).getModuleName(), (*i).getType()); + } + } +}; + +static RadiantFileTypeRegistry g_patterns; + +IFileTypeRegistry* GetFileTypeRegistry() +{ + return &g_patterns; +} + +const char* findModuleName(IFileTypeRegistry* registry, const char* moduleType, const char* extension) +{ + class SearchFileTypeList : public IFileTypeList + { + char m_pattern[128]; + const char* m_moduleName; + public: + SearchFileTypeList(const char* ext) + : m_moduleName("") + { + m_pattern[0] = '*'; + m_pattern[1] = '.'; + strncpy(m_pattern + 2, ext, 125); + m_pattern[127] = '\0'; + } + void addType(const char* moduleName, filetype_t type) + { + if(extension_equal(m_pattern, type.pattern)) + { + m_moduleName = moduleName; + } + } + + const char* getModuleName() + { + return m_moduleName; + } + } search(extension); + registry->getTypeList(moduleType, &search); + return search.getModuleName(); +} + + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +class FiletypesAPI +{ + IFileTypeRegistry* m_filetypes; +public: + typedef IFileTypeRegistry Type; + STRING_CONSTANT(Name, "*"); + + FiletypesAPI() + { + m_filetypes = GetFileTypeRegistry(); + } + IFileTypeRegistry* getTable() + { + return m_filetypes; + } +}; + +typedef SingletonModule FiletypesModule; +typedef Static StaticFiletypesModule; +StaticRegisterModule staticRegisterFiletypes(StaticFiletypesModule::instance()); + + diff --git a/radiant/filetypes.h b/radiant/filetypes.h new file mode 100644 index 00000000..f924c793 --- /dev/null +++ b/radiant/filetypes.h @@ -0,0 +1,29 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_FILETYPES_H) +#define INCLUDED_FILETYPES_H + +class IFileTypeRegistry; +IFileTypeRegistry* GetFileTypeRegistry(); +const char* findModuleName(IFileTypeRegistry* registry, const char* moduleType, const char* extension); + +#endif diff --git a/radiant/filters.cpp b/radiant/filters.cpp new file mode 100644 index 00000000..74e7774a --- /dev/null +++ b/radiant/filters.cpp @@ -0,0 +1,321 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "filters.h" + +#include "debugging/debugging.h" + +#include "ifilter.h" + +#include "scenelib.h" + +#include +#include + +#include "gtkutil/widget.h" +#include "gtkutil/menu.h" +#include "gtkmisc.h" +#include "mainframe.h" +#include "commands.h" +#include "preferences.h" + +struct filters_globals_t +{ + std::size_t exclude; + + filters_globals_t() : + exclude(0) + { + } +}; + +filters_globals_t g_filters_globals; + +inline bool filter_active(int mask) +{ + return (g_filters_globals.exclude & mask) > 0; +} + +class FilterWrapper +{ +public: + FilterWrapper(Filter& filter, int mask) : m_filter(filter), m_mask(mask) + { + } + void update() + { + m_filter.setActive(filter_active(m_mask)); + } +private: + Filter& m_filter; + int m_mask; +}; + +typedef std::list Filters; +Filters g_filters; + +typedef std::set Filterables; +Filterables g_filterables; + +void UpdateFilters() +{ + { + for(Filters::iterator i = g_filters.begin(); i != g_filters.end(); ++i) + { + (*i).update(); + } + } + + { + for(Filterables::iterator i = g_filterables.begin(); i != g_filterables.end(); ++i) + { + (*i)->updateFiltered(); + } + } +} + + +class BasicFilterSystem : public FilterSystem +{ +public: + void addFilter(Filter& filter, int mask) + { + g_filters.push_back(FilterWrapper(filter, mask)); + g_filters.back().update(); + } + void registerFilterable(Filterable& filterable) + { + ASSERT_MESSAGE(g_filterables.find(&filterable) == g_filterables.end(), "filterable already registered"); + filterable.updateFiltered(); + g_filterables.insert(&filterable); + } + void unregisterFilterable(Filterable& filterable) + { + ASSERT_MESSAGE(g_filterables.find(&filterable) != g_filterables.end(), "filterable not registered"); + g_filterables.erase(&filterable); + } +}; + +BasicFilterSystem g_FilterSystem; + +FilterSystem& GetFilterSystem() +{ + return g_FilterSystem; +} + +void PerformFiltering() +{ + UpdateFilters(); + SceneChangeNotify(); +} + +class ToggleFilterFlag +{ + const unsigned int m_mask; +public: + ToggleItem m_item; + + ToggleFilterFlag(unsigned int mask) : m_mask(mask), m_item(ActiveCaller(*this)) + { + } + ToggleFilterFlag(const ToggleFilterFlag& other) : m_mask(other.m_mask), m_item(ActiveCaller(*this)) + { + } + void active(const BoolImportCallback& importCallback) + { + importCallback((g_filters_globals.exclude & m_mask) != 0); + } + typedef MemberCaller1 ActiveCaller; + void toggle() + { + g_filters_globals.exclude ^= m_mask; + m_item.update(); + PerformFiltering(); + } + void reset() + { + g_filters_globals.exclude = 0; + m_item.update(); + PerformFiltering(); + } + typedef MemberCaller ToggleCaller; +}; + + +typedef std::list ToggleFilterFlags; +ToggleFilterFlags g_filter_items; + +void add_filter_command(unsigned int flag, const char* command, const Accelerator& accelerator) +{ + g_filter_items.push_back(ToggleFilterFlag(flag)); + GlobalToggles_insert(command, ToggleFilterFlag::ToggleCaller(g_filter_items.back()), ToggleItem::AddCallbackCaller(g_filter_items.back().m_item), accelerator); +} + +void InvertFilters() +{ + std::list::iterator iter; + + for(iter = g_filter_items.begin(); iter != g_filter_items.end(); ++iter) + { + iter->toggle(); + } +} + +void ResetFilters() +{ + std::list::iterator iter; + + for(iter = g_filter_items.begin(); iter != g_filter_items.end(); ++iter) + { + iter->reset(); + } +} + +void Filters_constructMenu(GtkMenu* menu_in_menu) +{ + create_check_menu_item_with_mnemonic(menu_in_menu, "World", "FilterWorldBrushes"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Entities", "FilterEntities"); + if(g_pGameDescription->mGameType == "doom3") + { + create_check_menu_item_with_mnemonic(menu_in_menu, "Visportals", "FilterVisportals"); + } + else + { + create_check_menu_item_with_mnemonic(menu_in_menu, "Areaportals", "FilterAreaportals"); + } + create_check_menu_item_with_mnemonic(menu_in_menu, "Translucent", "FilterTranslucent"); + if(g_pGameDescription->mGameType != "doom3") + { + create_check_menu_item_with_mnemonic(menu_in_menu, "Liquids", "FilterLiquids"); + } + create_check_menu_item_with_mnemonic(menu_in_menu, "Caulk", "FilterCaulk"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Clips", "FilterClips"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Paths", "FilterPaths"); + if(g_pGameDescription->mGameType != "doom3") + { + create_check_menu_item_with_mnemonic(menu_in_menu, "Clusterportals", "FilterClusterportals"); + } + create_check_menu_item_with_mnemonic(menu_in_menu, "Lights", "FilterLights"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Structural", "FilterStructural"); + if(g_pGameDescription->mGameType != "doom3") + { + create_check_menu_item_with_mnemonic(menu_in_menu, "Lightgrid", "FilterLightgrid"); + } + create_check_menu_item_with_mnemonic(menu_in_menu, "Patches", "FilterPatches"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Details", "FilterDetails"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Hints", "FilterHintsSkips"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Models", "FilterModels"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Triggers", "FilterTriggers"); + if(g_pGameDescription->mGameType != "doom3") + { + create_check_menu_item_with_mnemonic(menu_in_menu, "Botclips", "FilterBotClips"); + } + // filter manipulation + menu_separator(menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "Invert filters", "InvertFilters"); + create_menu_item_with_mnemonic(menu_in_menu, "Reset filters", "ResetFilters"); +} + + +#include "preferencesystem.h" +#include "stringio.h" + +void ConstructFilters() +{ + GlobalPreferenceSystem().registerPreference("SI_Exclude", SizeImportStringCaller(g_filters_globals.exclude), SizeExportStringCaller(g_filters_globals.exclude)); + + GlobalCommands_insert("InvertFilters", FreeCaller()); + GlobalCommands_insert("ResetFilters", FreeCaller()); + + add_filter_command(EXCLUDE_WORLD, "FilterWorldBrushes", Accelerator('1', (GdkModifierType)GDK_MOD1_MASK)); + add_filter_command(EXCLUDE_ENT, "FilterEntities", Accelerator('2', (GdkModifierType)GDK_MOD1_MASK)); + if(g_pGameDescription->mGameType == "doom3") + { + add_filter_command(EXCLUDE_VISPORTALS, "FilterVisportals", Accelerator('3', (GdkModifierType)GDK_MOD1_MASK)); + } + else + { + add_filter_command(EXCLUDE_AREAPORTALS, "FilterAreaportals", Accelerator('3', (GdkModifierType)GDK_MOD1_MASK)); + } + add_filter_command(EXCLUDE_TRANSLUCENT, "FilterTranslucent", Accelerator('4', (GdkModifierType)GDK_MOD1_MASK)); + add_filter_command(EXCLUDE_LIQUIDS, "FilterLiquids", Accelerator('5', (GdkModifierType)GDK_MOD1_MASK)); + add_filter_command(EXCLUDE_CAULK, "FilterCaulk", Accelerator('6', (GdkModifierType)GDK_MOD1_MASK )); + add_filter_command(EXCLUDE_CLIP, "FilterClips", Accelerator('7', (GdkModifierType)GDK_MOD1_MASK)); + add_filter_command(EXCLUDE_PATHS, "FilterPaths", Accelerator('8', (GdkModifierType)GDK_MOD1_MASK)); + if(g_pGameDescription->mGameType != "doom3") + { + add_filter_command(EXCLUDE_CLUSTERPORTALS, "FilterClusterportals", Accelerator('9', (GdkModifierType)GDK_MOD1_MASK)); + } + add_filter_command(EXCLUDE_LIGHTS, "FilterLights", Accelerator('0', (GdkModifierType)GDK_MOD1_MASK)); + add_filter_command(EXCLUDE_STRUCTURAL, "FilterStructural", Accelerator('D', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + if(g_pGameDescription->mGameType != "doom3") + { + add_filter_command(EXCLUDE_LIGHTGRID, "FilterLightgrid", accelerator_null()); + } + add_filter_command(EXCLUDE_CURVES, "FilterPatches", Accelerator('P', (GdkModifierType)GDK_CONTROL_MASK)); + add_filter_command(EXCLUDE_DETAILS, "FilterDetails", Accelerator('D', (GdkModifierType)GDK_CONTROL_MASK)); + add_filter_command(EXCLUDE_HINTSSKIPS, "FilterHintsSkips", Accelerator('H', (GdkModifierType)GDK_CONTROL_MASK)); + add_filter_command(EXCLUDE_MODELS, "FilterModels", Accelerator('M', (GdkModifierType)GDK_SHIFT_MASK)); + add_filter_command(EXCLUDE_TRIGGERS, "FilterTriggers", Accelerator('T', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + if(g_pGameDescription->mGameType != "doom3") + { + add_filter_command(EXCLUDE_BOTCLIP, "FilterBotClips", Accelerator('M', (GdkModifierType)GDK_MOD1_MASK)); + } + + PerformFiltering(); +} + +void DestroyFilters() +{ + g_filters.clear(); +} + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +class FilterAPI +{ + FilterSystem* m_filter; +public: + typedef FilterSystem Type; + STRING_CONSTANT(Name, "*"); + + FilterAPI() + { + ConstructFilters(); + + m_filter = &GetFilterSystem(); + } + ~FilterAPI() + { + DestroyFilters(); + } + FilterSystem* getTable() + { + return m_filter; + } +}; + +typedef SingletonModule FilterModule; +typedef Static StaticFilterModule; +StaticRegisterModule staticRegisterFilter(StaticFilterModule::instance()); + + diff --git a/radiant/filters.h b/radiant/filters.h new file mode 100644 index 00000000..631cef84 --- /dev/null +++ b/radiant/filters.h @@ -0,0 +1,28 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_FILTERS_H) +#define INCLUDED_FILTERS_H + +typedef struct _GtkMenu GtkMenu; +void Filters_constructMenu(GtkMenu* menu_in_menu); + +#endif diff --git a/radiant/findtexturedialog.cpp b/radiant/findtexturedialog.cpp new file mode 100644 index 00000000..52891d80 --- /dev/null +++ b/radiant/findtexturedialog.cpp @@ -0,0 +1,301 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// Find/Replace textures dialogs +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "findtexturedialog.h" + +#include "debugging/debugging.h" + +#include "ishaders.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gtkutil/window.h" +#include "stream/stringstream.h" + +#include "commands.h" +#include "dialog.h" +#include "select.h" +#include "textureentry.h" + + + +class FindTextureDialog : public Dialog +{ + public: + static void setReplaceStr(const char* name); + static void setFindStr(const char* name); + static bool isOpen(); + static void show(); + typedef FreeCaller<&FindTextureDialog::show> ShowCaller; + static void updateTextures(const char* name); + + FindTextureDialog(); + virtual ~FindTextureDialog(); + GtkWindow* BuildDialog(); + + void constructWindow(GtkWindow* parent) + { + m_parent = parent; + Create(); + } + void destroyWindow() + { + Destroy(); + } + + + bool m_bSelectedOnly; + CopiedString m_strFind; + CopiedString m_strReplace; +}; + +FindTextureDialog g_FindTextureDialog; +static bool g_bFindActive = true; + +namespace +{ + void FindTextureDialog_apply() + { + StringOutputStream find(256); + StringOutputStream replace(256); + + find << "textures/" << g_FindTextureDialog.m_strFind.c_str(); + replace << "textures/" << g_FindTextureDialog.m_strReplace.c_str(); + FindReplaceTextures(find.c_str(), replace.c_str(), g_FindTextureDialog.m_bSelectedOnly); + } + + static void OnApply(GtkWidget* widget, gpointer data) + { + g_FindTextureDialog.exportData(); + FindTextureDialog_apply(); + } + + static void OnFind(GtkWidget* widget, gpointer data) + { + g_FindTextureDialog.exportData(); + FindTextureDialog_apply(); + } + + static void OnOK(GtkWidget* widget, gpointer data) + { + g_FindTextureDialog.exportData(); + FindTextureDialog_apply(); + g_FindTextureDialog.HideDlg(); + } + + static void OnClose(GtkWidget* widget, gpointer data) + { + g_FindTextureDialog.HideDlg(); + } + + + static gint find_focus_in (GtkWidget* widget, GdkEventFocus *event, gpointer data) + { + g_bFindActive = true; + return FALSE; + } + + static gint replace_focus_in (GtkWidget* widget, GdkEventFocus *event, gpointer data) + { + g_bFindActive = false; + return FALSE; + } +} + +// ============================================================================= +// FindTextureDialog class + +FindTextureDialog::FindTextureDialog() +{ + m_bSelectedOnly = FALSE; +} + +FindTextureDialog::~FindTextureDialog() +{ +} + +GtkWindow* FindTextureDialog::BuildDialog() +{ + GtkWidget* vbox, *hbox, *table, *label; + GtkWidget* button, *check, *entry; + + GtkWindow* dlg = create_floating_window("Find / Replace Texture(s)", m_parent); + + hbox = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox); + gtk_container_add(GTK_CONTAINER(dlg), GTK_WIDGET(hbox)); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 5); + + vbox = gtk_vbox_new (FALSE, 5); + gtk_widget_show (vbox); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0); + + table = gtk_table_new (2, 2, FALSE); + gtk_widget_show (table); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(table), TRUE, TRUE, 0); + gtk_table_set_row_spacings (GTK_TABLE (table), 5); + gtk_table_set_col_spacings (GTK_TABLE (table), 5); + + label = gtk_label_new ("Find:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + label = gtk_label_new ("Replace:"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + entry = gtk_entry_new(); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + g_signal_connect(G_OBJECT(entry), "focus_in_event", + G_CALLBACK(find_focus_in), 0); + AddDialogData(*GTK_ENTRY(entry), m_strFind); + GlobalTextureEntryCompletion::instance().connect(GTK_ENTRY(entry)); + + entry = gtk_entry_new(); + gtk_widget_show (entry); + gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND|GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + g_signal_connect(G_OBJECT(entry), "focus_in_event", + G_CALLBACK(replace_focus_in), 0); + AddDialogData(*GTK_ENTRY(entry), m_strReplace); + GlobalTextureEntryCompletion::instance().connect(GTK_ENTRY(entry)); + + check = gtk_check_button_new_with_label ("Within selected brushes only"); + gtk_widget_show (check); + gtk_box_pack_start(GTK_BOX(vbox), check, TRUE, TRUE, 0); + AddDialogData(*GTK_TOGGLE_BUTTON(check), m_bSelectedOnly); + + vbox = gtk_vbox_new (FALSE, 5); + gtk_widget_show (vbox); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, FALSE, 0); + + button = gtk_button_new_with_label ("Apply"); + gtk_widget_show (button); + gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(OnApply), 0); + gtk_widget_set_usize (button, 60, -2); + + button = gtk_button_new_with_label ("Close"); + gtk_widget_show (button); + gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(OnClose), 0); + gtk_widget_set_usize (button, 60, -2); + + return dlg; +} + +void FindTextureDialog::updateTextures(const char* name) +{ + if (isOpen()) + { + if (g_bFindActive) + { + setFindStr(name + 9); + } + else + { + setReplaceStr(name + 9); + } + } +} + +bool FindTextureDialog::isOpen() +{ + return GTK_WIDGET_VISIBLE(g_FindTextureDialog.GetWidget()) == TRUE; +} + +void FindTextureDialog::setFindStr(const char* name) +{ + g_FindTextureDialog.exportData(); + g_FindTextureDialog.m_strFind = name; + g_FindTextureDialog.importData(); +} + +void FindTextureDialog::setReplaceStr(const char* name) +{ + g_FindTextureDialog.exportData(); + g_FindTextureDialog.m_strReplace = name; + g_FindTextureDialog.importData(); +} + +void FindTextureDialog::show() +{ + g_FindTextureDialog.ShowDlg(); +} + + +void FindTextureDialog_constructWindow(GtkWindow* main_window) +{ + g_FindTextureDialog.constructWindow(main_window); +} + +void FindTextureDialog_destroyWindow() +{ + g_FindTextureDialog.destroyWindow(); +} + +bool FindTextureDialog_isOpen() +{ + return g_FindTextureDialog.isOpen(); +} + +void FindTextureDialog_selectTexture(const char* name) +{ + g_FindTextureDialog.updateTextures(name); +} + +void FindTextureDialog_Construct() +{ + GlobalCommands_insert("FindReplaceTextures", FindTextureDialog::ShowCaller()); +} + +void FindTextureDialog_Destroy() +{ +} + diff --git a/radiant/findtexturedialog.h b/radiant/findtexturedialog.h new file mode 100644 index 00000000..5535ec5d --- /dev/null +++ b/radiant/findtexturedialog.h @@ -0,0 +1,34 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_FINDTEXTUREDIALOG_H) +#define INCLUDED_FINDTEXTUREDIALOG_H + +void FindTextureDialog_Construct(); +void FindTextureDialog_Destroy(); + +typedef struct _GtkWindow GtkWindow; +void FindTextureDialog_constructWindow(GtkWindow* main_window); +void FindTextureDialog_destroyWindow(); +bool FindTextureDialog_isOpen(); +void FindTextureDialog_selectTexture(const char* name); + +#endif diff --git a/radiant/glwidget.cpp b/radiant/glwidget.cpp new file mode 100644 index 00000000..47500097 --- /dev/null +++ b/radiant/glwidget.cpp @@ -0,0 +1,55 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "glwidget.h" + +#include "igtkgl.h" +#include "modulesystem.h" +#include "gtkutil/glwidget.h" + +class GtkGLAPI +{ + _QERGtkGLTable m_gtkgl; +public: + typedef _QERGtkGLTable Type; + STRING_CONSTANT(Name, "*"); + + GtkGLAPI() + { + m_gtkgl.glwidget_new = &glwidget_new; + m_gtkgl.glwidget_swap_buffers = &glwidget_swap_buffers; + m_gtkgl.glwidget_make_current = &glwidget_make_current; + m_gtkgl.glwidget_destroy_context = &glwidget_destroy_context; + m_gtkgl.glwidget_create_context = &glwidget_create_context; + } + _QERGtkGLTable* getTable() + { + return &m_gtkgl; + } +}; + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +typedef SingletonModule GtkGLModule; +typedef Static StaticGtkGLModule; +StaticRegisterModule staticRegisterGtkGL(StaticGtkGLModule::instance()); + diff --git a/radiant/glwidget.h b/radiant/glwidget.h new file mode 100644 index 00000000..6d9ab361 --- /dev/null +++ b/radiant/glwidget.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GLWIDGET_H) +#define INCLUDED_GLWIDGET_H + +#endif diff --git a/radiant/grid.cpp b/radiant/grid.cpp new file mode 100644 index 00000000..e93ce401 --- /dev/null +++ b/radiant/grid.cpp @@ -0,0 +1,271 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "grid.h" + +#include +#include +#include + +#include "preferencesystem.h" + +#include "gtkutil/widget.h" +#include "signal/signal.h" +#include "stringio.h" + +#include "gtkmisc.h" +#include "commands.h" +#include "preferences.h" + + + +Signal0 g_gridChange_callbacks; + +void AddGridChangeCallback(const SignalHandler& handler) +{ + g_gridChange_callbacks.connectLast(handler); + handler(); +} + +void GridChangeNotify() +{ + g_gridChange_callbacks(); +} + +enum GridPower +{ + GRIDPOWER_0125 = -3, + GRIDPOWER_025 = -2, + GRIDPOWER_05 = -1, + GRIDPOWER_1 = 0, + GRIDPOWER_2 = 1, + GRIDPOWER_4 = 2, + GRIDPOWER_8 = 3, + GRIDPOWER_16 = 4, + GRIDPOWER_32 = 5, + GRIDPOWER_64 = 6, + GRIDPOWER_128 = 7, + GRIDPOWER_256 = 8, +}; + + +typedef const char* GridName; +// this must match the GridPower enumeration +const GridName g_gridnames[] = { + "0.125", + "0.25", + "0.5", + "1", + "2", + "4", + "8", + "16", + "32", + "64", + "128", + "256", +}; + +inline GridPower GridPower_forGridDefault(int gridDefault) +{ + return static_cast(gridDefault - 3); +} + +inline int GridDefault_forGridPower(GridPower gridPower) +{ + return gridPower + 3; +} + +int g_grid_default = GridDefault_forGridPower(GRIDPOWER_8); + +int g_grid_power = GridPower_forGridDefault(g_grid_default); + +int Grid_getPower() +{ + return g_grid_power; +} + +inline float GridSize_forGridPower(int gridPower) +{ + return pow(2.0f, gridPower); +} + +float g_gridsize = GridSize_forGridPower(g_grid_power); + +float GetGridSize() +{ + return g_gridsize; +} + + +void setGridPower(GridPower power); + +class GridMenuItem +{ + GridPower m_id; + + GridMenuItem(const GridMenuItem& other); // NOT COPYABLE + GridMenuItem& operator=(const GridMenuItem& other); // NOT ASSIGNABLE +public: + ToggleItem m_item; + + GridMenuItem(GridPower id) : m_id(id), m_item(ExportCaller(*this)) + { + } + void set() + { + g_grid_power = m_id; + m_item.update(); + setGridPower(m_id); + } + typedef MemberCaller SetCaller; + void active(const BoolImportCallback& importCallback) + { + importCallback(g_grid_power == m_id); + } + typedef MemberCaller1 ExportCaller; +}; + +GridMenuItem g_gridMenu0125(GRIDPOWER_0125); +GridMenuItem g_gridMenu025(GRIDPOWER_025); +GridMenuItem g_gridMenu05(GRIDPOWER_05); +GridMenuItem g_gridMenu1(GRIDPOWER_1); +GridMenuItem g_gridMenu2(GRIDPOWER_2); +GridMenuItem g_gridMenu4(GRIDPOWER_4); +GridMenuItem g_gridMenu8(GRIDPOWER_8); +GridMenuItem g_gridMenu16(GRIDPOWER_16); +GridMenuItem g_gridMenu32(GRIDPOWER_32); +GridMenuItem g_gridMenu64(GRIDPOWER_64); +GridMenuItem g_gridMenu128(GRIDPOWER_128); +GridMenuItem g_gridMenu256(GRIDPOWER_256); + +void setGridPower(GridPower power) +{ + g_gridsize = GridSize_forGridPower(power); + + g_gridMenu0125.m_item.update(); + g_gridMenu025.m_item.update(); + g_gridMenu05.m_item.update(); + g_gridMenu1.m_item.update(); + g_gridMenu2.m_item.update(); + g_gridMenu4.m_item.update(); + g_gridMenu8.m_item.update(); + g_gridMenu16.m_item.update(); + g_gridMenu32.m_item.update(); + g_gridMenu64.m_item.update(); + g_gridMenu128.m_item.update(); + g_gridMenu256.m_item.update(); + GridChangeNotify(); +} + +void GridPrev() +{ + if(g_grid_power > GRIDPOWER_0125) + { + setGridPower(static_cast(--g_grid_power)); + } +} + +void GridNext() +{ + if(g_grid_power < GRIDPOWER_256) + { + setGridPower(static_cast(++g_grid_power)); + } +} + + +void Grid_registerCommands() +{ + GlobalCommands_insert("GridDown", FreeCaller(), Accelerator('[')); + GlobalCommands_insert("GridUp", FreeCaller(), Accelerator(']')); + + GlobalToggles_insert("SetGrid0.125", GridMenuItem::SetCaller(g_gridMenu0125), ToggleItem::AddCallbackCaller(g_gridMenu0125.m_item)); + GlobalToggles_insert("SetGrid0.25", GridMenuItem::SetCaller(g_gridMenu025), ToggleItem::AddCallbackCaller(g_gridMenu025.m_item)); + GlobalToggles_insert("SetGrid0.5", GridMenuItem::SetCaller(g_gridMenu05), ToggleItem::AddCallbackCaller(g_gridMenu05.m_item)); + GlobalToggles_insert("SetGrid1", GridMenuItem::SetCaller(g_gridMenu1), ToggleItem::AddCallbackCaller(g_gridMenu1.m_item), Accelerator('1')); + GlobalToggles_insert("SetGrid2", GridMenuItem::SetCaller(g_gridMenu2), ToggleItem::AddCallbackCaller(g_gridMenu2.m_item), Accelerator('2')); + GlobalToggles_insert("SetGrid4", GridMenuItem::SetCaller(g_gridMenu4), ToggleItem::AddCallbackCaller(g_gridMenu4.m_item), Accelerator('3')); + GlobalToggles_insert("SetGrid8", GridMenuItem::SetCaller(g_gridMenu8), ToggleItem::AddCallbackCaller(g_gridMenu8.m_item), Accelerator('4')); + GlobalToggles_insert("SetGrid16", GridMenuItem::SetCaller(g_gridMenu16), ToggleItem::AddCallbackCaller(g_gridMenu16.m_item), Accelerator('5')); + GlobalToggles_insert("SetGrid32", GridMenuItem::SetCaller(g_gridMenu32), ToggleItem::AddCallbackCaller(g_gridMenu32.m_item), Accelerator('6')); + GlobalToggles_insert("SetGrid64", GridMenuItem::SetCaller(g_gridMenu64), ToggleItem::AddCallbackCaller(g_gridMenu64.m_item), Accelerator('7')); + GlobalToggles_insert("SetGrid128", GridMenuItem::SetCaller(g_gridMenu128), ToggleItem::AddCallbackCaller(g_gridMenu128.m_item), Accelerator('8')); + GlobalToggles_insert("SetGrid256", GridMenuItem::SetCaller(g_gridMenu256), ToggleItem::AddCallbackCaller(g_gridMenu256.m_item), Accelerator('9')); +} + + +void Grid_constructMenu(GtkMenu* menu) +{ + create_check_menu_item_with_mnemonic(menu, "Grid0.125", "SetGrid0.125"); + create_check_menu_item_with_mnemonic(menu, "Grid0.25", "SetGrid0.25"); + create_check_menu_item_with_mnemonic(menu, "Grid0.5", "SetGrid0.5"); + create_check_menu_item_with_mnemonic(menu, "Grid1", "SetGrid1"); + create_check_menu_item_with_mnemonic(menu, "Grid2", "SetGrid2"); + create_check_menu_item_with_mnemonic(menu, "Grid4", "SetGrid4"); + create_check_menu_item_with_mnemonic(menu, "Grid8", "SetGrid8"); + create_check_menu_item_with_mnemonic(menu, "Grid16", "SetGrid16"); + create_check_menu_item_with_mnemonic(menu, "Grid32", "SetGrid32"); + create_check_menu_item_with_mnemonic(menu, "Grid64", "SetGrid64"); + create_check_menu_item_with_mnemonic(menu, "Grid128", "SetGrid128"); + create_check_menu_item_with_mnemonic(menu, "Grid256", "SetGrid256"); +} + +void Grid_registerShortcuts() +{ + command_connect_accelerator("ToggleGrid"); + command_connect_accelerator("GridDown"); + command_connect_accelerator("GridUp"); +} + +void Grid_constructPreferences(PreferencesPage& page) +{ + page.appendCombo( + "Default grid spacing", + g_grid_default, + ARRAY_RANGE(g_gridnames) + ); +} +void Grid_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Grid", "Grid Settings")); + Grid_constructPreferences(page); +} +void Grid_registerPreferencesPage() +{ + PreferencesDialog_addSettingsPage(FreeCaller1()); +} + +void Grid_construct() +{ + Grid_registerPreferencesPage(); + + g_grid_default = GridDefault_forGridPower(GRIDPOWER_8); + + GlobalPreferenceSystem().registerPreference("GridDefault", IntImportStringCaller(g_grid_default), IntExportStringCaller(g_grid_default)); + + g_grid_power = GridPower_forGridDefault(g_grid_default); + g_gridsize = GridSize_forGridPower(g_grid_power); +} + +void Grid_destroy() +{ +} diff --git a/radiant/grid.h b/radiant/grid.h new file mode 100644 index 00000000..cf2671e9 --- /dev/null +++ b/radiant/grid.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GRID_H) +#define INCLUDED_GRID_H + +#include "signal/signalfwd.h" + +float GetGridSize(); +int Grid_getPower(); + +void AddGridChangeCallback(const SignalHandler& handler); + +void Grid_registerCommands(); +typedef struct _GtkMenu GtkMenu; +void Grid_constructMenu(GtkMenu* menu); + +void Grid_registerShortcuts(); + +void Grid_construct(); +void Grid_destroy(); + +#endif diff --git a/radiant/groupdialog.cpp b/radiant/groupdialog.cpp new file mode 100644 index 00000000..1b89cc2f --- /dev/null +++ b/radiant/groupdialog.cpp @@ -0,0 +1,232 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// Floating dialog that contains a notebook with at least Entities and Group tabs +// I merged the 2 MS Windows dialogs in a single class +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "groupdialog.h" + +#include "debugging/debugging.h" + +#include + +#include +#include +#include +#include + +#include "gtkutil/widget.h" +#include "gtkutil/accelerator.h" +#include "entityinspector.h" +#include "gtkmisc.h" +#include "multimon.h" +#include "console.h" +#include "commands.h" + + +#include +#include "gtkutil/window.h" + +class GroupDlg +{ +public: + GtkWidget* m_pNotebook; + GtkWindow* m_window; + + GroupDlg(); + void Create(GtkWindow* parent); + + void Show() + { + // workaround for strange gtk behaviour - modifying the contents of a window while it is not visible causes the window position to change without sending a configure_event + m_position_tracker.sync(m_window); + gtk_widget_show(GTK_WIDGET(m_window)); + } + void Hide() + { + gtk_widget_hide(GTK_WIDGET(m_window)); + } + + WindowPositionTracker m_position_tracker; +}; + +namespace +{ + GroupDlg g_GroupDlg; + + std::size_t g_current_page; + std::vector g_pages; +} + +void GroupDialog_updatePageTitle(GtkWindow* window, std::size_t pageIndex) +{ + if(pageIndex < g_pages.size()) + { + g_pages[pageIndex](PointerCaller1(window)); + } +} + +static gboolean switch_page(GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, gpointer data) +{ + GroupDialog_updatePageTitle(GTK_WINDOW(data), page_num); + g_current_page = page_num; + + return FALSE; +} + +GroupDlg::GroupDlg() : m_window(0) +{ + m_position_tracker.setPosition(c_default_window_pos); +} + +void GroupDlg::Create(GtkWindow* parent) +{ + ASSERT_MESSAGE(m_window == 0, "dialog already created"); + + GtkWindow* window = create_persistent_floating_window("Entities", parent); + + global_accel_connect_window(window); + + window_connect_focus_in_clear_focus_widget(window); + + m_window = window; + +#ifdef WIN32 + if( g_multimon_globals.m_bStartOnPrimMon ) + { + WindowPosition pos(m_position_tracker.getPosition()); + PositionWindowOnPrimaryScreen(pos); + m_position_tracker.setPosition(pos); + } +#endif + m_position_tracker.connect(window); + + { + GtkWidget* notebook = gtk_notebook_new(); + gtk_widget_show(notebook); + gtk_container_add (GTK_CONTAINER (window), notebook); + gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_BOTTOM); + m_pNotebook = notebook; + + g_signal_connect(G_OBJECT(notebook), "switch_page", G_CALLBACK(switch_page), window); + } +} + + +GtkWidget* GroupDialog_addPage(const char* tabLabel, GtkWidget* widget, const StringExportCallback& title) +{ + GtkWidget* w = gtk_label_new(tabLabel); + gtk_widget_show(w); + GtkWidget* page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(g_GroupDlg.m_pNotebook), gtk_notebook_insert_page(GTK_NOTEBOOK(g_GroupDlg.m_pNotebook), widget, w, -1)); + g_pages.push_back(title); + + return page; +} + + +bool GroupDialog_isShown() +{ + return widget_is_visible(GTK_WIDGET(g_GroupDlg.m_window)); +} +void GroupDialog_setShown(bool shown) +{ + shown ? g_GroupDlg.Show() : g_GroupDlg.Hide(); +} +void GroupDialog_ToggleShow() +{ + GroupDialog_setShown(!GroupDialog_isShown()); +} + +void GroupDialog_constructWindow(GtkWindow* main_window) +{ + g_GroupDlg.Create(main_window); +} +void GroupDialog_destroyWindow() +{ + ASSERT_NOTNULL(g_GroupDlg.m_window); + destroy_floating_window(g_GroupDlg.m_window); + g_GroupDlg.m_window = 0; +} + + +GtkWindow* GroupDialog_getWindow() +{ + return g_GroupDlg.m_window; +} +void GroupDialog_show() +{ + g_GroupDlg.Show(); +} + +GtkWidget* GroupDialog_getPage() +{ + return gtk_notebook_get_nth_page(GTK_NOTEBOOK(g_GroupDlg.m_pNotebook), gint(g_current_page)); +} + +void GroupDialog_setPage(GtkWidget* page) +{ + g_current_page = gtk_notebook_page_num(GTK_NOTEBOOK(g_GroupDlg.m_pNotebook), page); + gtk_notebook_set_current_page(GTK_NOTEBOOK(g_GroupDlg.m_pNotebook), gint(g_current_page)); +} + +void GroupDialog_showPage(GtkWidget* page) +{ + if(GroupDialog_getPage() == page) + { + GroupDialog_ToggleShow(); + } + else + { + gtk_widget_show(GTK_WIDGET(g_GroupDlg.m_window)); + GroupDialog_setPage(page); + } +} + +void GroupDialog_cycle() +{ + g_current_page = (g_current_page + 1) % g_pages.size(); + gtk_notebook_set_current_page(GTK_NOTEBOOK(g_GroupDlg.m_pNotebook), gint(g_current_page)); +} + +void GroupDialog_updatePageTitle(GtkWidget* page) +{ + if(GroupDialog_getPage() == page) + { + GroupDialog_updatePageTitle(g_GroupDlg.m_window, g_current_page); + } +} + + +#include "preferencesystem.h" + +void GroupDialog_Construct() +{ + GlobalPreferenceSystem().registerPreference("EntityWnd", WindowPositionTrackerImportStringCaller(g_GroupDlg.m_position_tracker), WindowPositionTrackerExportStringCaller(g_GroupDlg.m_position_tracker)); + + GlobalCommands_insert("ViewEntityInfo", FreeCaller(), Accelerator('N')); +} +void GroupDialog_Destroy() +{ +} diff --git a/radiant/groupdialog.h b/radiant/groupdialog.h new file mode 100644 index 00000000..92caffab --- /dev/null +++ b/radiant/groupdialog.h @@ -0,0 +1,48 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_GROUPDIALOG_H) +#define INCLUDED_GROUPDIALOG_H + +#include "generic/callback.h" + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkWindow GtkWindow; + +void GroupDialog_Construct(); +void GroupDialog_Destroy(); + +void GroupDialog_constructWindow(GtkWindow* main_window); +void GroupDialog_destroyWindow(); +GtkWindow* GroupDialog_getWindow(); +void GroupDialog_show(); + +inline void RawStringExport(const char* string, const StringImportCallback& importer) +{ + importer(string); +} +typedef ConstPointerCaller1 RawStringExportCaller; +GtkWidget* GroupDialog_addPage(const char* tabLabel, GtkWidget* widget, const StringExportCallback& title); + +void GroupDialog_showPage(GtkWidget* page); +void GroupDialog_updatePageTitle(GtkWidget* page); + +#endif diff --git a/radiant/gtkdlgs.cpp b/radiant/gtkdlgs.cpp new file mode 100644 index 00000000..1b1e5cc4 --- /dev/null +++ b/radiant/gtkdlgs.cpp @@ -0,0 +1,1105 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// Some small dialogs that don't need much +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "gtkdlgs.h" + +#include "debugging/debugging.h" +#include "version.h" +#include "aboutmsg.h" + +#include "igl.h" +#include "iscenegraph.h" +#include "iselection.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "os/path.h" +#include "math/aabb.h" +#include "container/array.h" +#include "generic/static.h" +#include "stream/stringstream.h" +#include "convert.h" +#include "gtkutil/messagebox.h" +#include "gtkutil/image.h" + +#include "gtkmisc.h" +#include "brushmanip.h" +#include "build.h" +#include "qe3.h" +#include "texwindow.h" +#include "xywindow.h" +#include "mainframe.h" +#include "preferences.h" +#include "url.h" +#include "cmdlib.h" + + + +// ============================================================================= +// Project settings dialog + +class GameComboConfiguration +{ +public: + const char* basegame_dir; + const char* basegame; + const char* known_dir; + const char* known; + const char* custom; + + GameComboConfiguration() : + basegame_dir(g_pGameDescription->getRequiredKeyValue("basegame")), + basegame(g_pGameDescription->getRequiredKeyValue("basegamename")), + known_dir(g_pGameDescription->getKeyValue("knowngame")), + known(g_pGameDescription->getKeyValue("knowngamename")), + custom(g_pGameDescription->getRequiredKeyValue("unknowngamename")) + { + } +}; + +typedef LazyStatic LazyStaticGameComboConfiguration; + +inline GameComboConfiguration& globalGameComboConfiguration() +{ + return LazyStaticGameComboConfiguration::instance(); +} + + +struct gamecombo_t +{ + gamecombo_t(int _game, const char* _fs_game, bool _sensitive) + : game(_game), fs_game(_fs_game), sensitive(_sensitive) + {} + int game; + const char* fs_game; + bool sensitive; +}; + +gamecombo_t gamecombo_for_dir(const char* dir) +{ + if(string_equal(dir, globalGameComboConfiguration().basegame_dir)) + { + return gamecombo_t(0, "", false); + } + else if(string_equal(dir, globalGameComboConfiguration().known_dir)) + { + return gamecombo_t(1, dir, false); + } + else + { + return gamecombo_t(string_empty(globalGameComboConfiguration().known_dir) ? 1 : 2, dir, true); + } +} + +gamecombo_t gamecombo_for_gamename(const char* gamename) +{ + if ((strlen(gamename) == 0) || !strcmp(gamename, globalGameComboConfiguration().basegame)) + { + return gamecombo_t(0, "", false); + } + else if (!strcmp(gamename, globalGameComboConfiguration().known)) + { + return gamecombo_t(1, globalGameComboConfiguration().known_dir, false); + } + else + { + return gamecombo_t(string_empty(globalGameComboConfiguration().known_dir) ? 1 : 2, "", true); + } +} + +inline void path_copy_clean(char* destination, const char* source) +{ + char* i = destination; + + while(*source != '\0') + { + *i++ = (*source == '\\') ? '/' : *source; + ++source; + } + + if(i != destination && *(i-1) != '/') + *(i++) = '/'; + + *i = '\0'; +} + + +struct GameCombo +{ + GtkComboBox* game_select; + GtkEntry* fsgame_entry; +}; + +gboolean OnSelchangeComboWhatgame(GtkWidget *widget, GameCombo* combo) +{ + const char *gamename; + { + GtkTreeIter iter; + gtk_combo_box_get_active_iter(combo->game_select, &iter); + gtk_tree_model_get(gtk_combo_box_get_model(combo->game_select), &iter, 0, (gpointer*)&gamename, -1); + } + + gamecombo_t gamecombo = gamecombo_for_gamename(gamename); + + gtk_entry_set_text(combo->fsgame_entry, gamecombo.fs_game); + gtk_widget_set_sensitive(GTK_WIDGET(combo->fsgame_entry), gamecombo.sensitive); + + return FALSE; +} + +class MappingMode +{ +public: + bool do_mapping_mode; + const char* sp_mapping_mode; + const char* mp_mapping_mode; + + MappingMode() : + do_mapping_mode(!string_empty(g_pGameDescription->getKeyValue("show_gamemode"))), + sp_mapping_mode("Single Player mapping mode"), + mp_mapping_mode("Multiplayer mapping mode") + { + } +}; + +typedef LazyStatic LazyStaticMappingMode; + +inline MappingMode& globalMappingMode() +{ + return LazyStaticMappingMode::instance(); +} + +class ProjectSettingsDialog +{ +public: + GameCombo game_combo; + GtkComboBox* gamemode_combo; +}; + +GtkWindow* ProjectSettingsDialog_construct(ProjectSettingsDialog& dialog, ModalDialog& modal) +{ + GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Project Settings", G_CALLBACK(dialog_delete_callback), &modal); + + { + GtkTable* table1 = create_dialog_table(1, 2, 4, 4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(table1)); + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_table_attach(table1, GTK_WIDGET(vbox), 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + { + GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &modal); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + } + { + GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &modal); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + } + } + { + GtkFrame* frame = create_dialog_frame("Project settings"); + gtk_table_attach(table1, GTK_WIDGET(frame), 0, 1, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + { + GtkTable* table2 = create_dialog_table((globalMappingMode().do_mapping_mode) ? 4 : 3, 2, 4, 4, 4); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(table2)); + + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Select mod")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table2, GTK_WIDGET(label), 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + } + { + dialog.game_combo.game_select = GTK_COMBO_BOX(gtk_combo_box_new_text()); + + gtk_combo_box_append_text(dialog.game_combo.game_select, globalGameComboConfiguration().basegame); + if(globalGameComboConfiguration().known[0] != '\0') + gtk_combo_box_append_text(dialog.game_combo.game_select, globalGameComboConfiguration().known); + gtk_combo_box_append_text(dialog.game_combo.game_select, globalGameComboConfiguration().custom); + + gtk_widget_show(GTK_WIDGET(dialog.game_combo.game_select)); + gtk_table_attach(table2, GTK_WIDGET(dialog.game_combo.game_select), 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + g_signal_connect(G_OBJECT(dialog.game_combo.game_select), "changed", G_CALLBACK(OnSelchangeComboWhatgame), &dialog.game_combo); + } + + { + GtkLabel* label = GTK_LABEL(gtk_label_new("fs_game")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table2, GTK_WIDGET(label), 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table2, GTK_WIDGET(entry), 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + dialog.game_combo.fsgame_entry = entry; + } + + if(globalMappingMode().do_mapping_mode) + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Mapping mode")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table2, GTK_WIDGET(label), 0, 1, 3, 4, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + + GtkComboBox* combo = GTK_COMBO_BOX(gtk_combo_box_new_text()); + gtk_combo_box_append_text(combo, globalMappingMode().sp_mapping_mode); + gtk_combo_box_append_text(combo, globalMappingMode().mp_mapping_mode); + + gtk_widget_show(GTK_WIDGET(combo)); + gtk_table_attach(table2, GTK_WIDGET(combo), 1, 2, 3, 4, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + dialog.gamemode_combo = combo; + } + } + } + } + + // initialise the fs_game selection from the project settings into the dialog + const char* dir = gamename_get(); + gamecombo_t gamecombo = gamecombo_for_dir(dir); + + gtk_combo_box_set_active(dialog.game_combo.game_select, gamecombo.game); + gtk_entry_set_text(dialog.game_combo.fsgame_entry, gamecombo.fs_game); + gtk_widget_set_sensitive(GTK_WIDGET(dialog.game_combo.fsgame_entry), gamecombo.sensitive); + + if(globalMappingMode().do_mapping_mode) + { + const char *gamemode = gamemode_get(); + if (string_empty(gamemode) || string_equal(gamemode, "sp")) + { + gtk_combo_box_set_active(dialog.gamemode_combo, 0); + } + else + { + gtk_combo_box_set_active(dialog.gamemode_combo, 1); + } + } + + return window; +} + +void ProjectSettingsDialog_ok(ProjectSettingsDialog& dialog) +{ + const char* dir = gtk_entry_get_text(dialog.game_combo.fsgame_entry); + + const char* new_gamename = path_equal(dir, globalGameComboConfiguration().basegame_dir) + ? "" + : dir; + + if(!path_equal(new_gamename, gamename_get())) + { + ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Changing Game Name"); + + EnginePath_Unrealise(); + + gamename_set(new_gamename); + + EnginePath_Realise(); + } + + if(globalMappingMode().do_mapping_mode) + { + // read from gamemode_combo + int active = gtk_combo_box_get_active(dialog.gamemode_combo); + if(active == -1 || active == 0) + { + gamemode_set("sp"); + } + else + { + gamemode_set("mp"); + } + } +} + +void DoProjectSettings() +{ + if(ConfirmModified("Edit Project Settings")) + { + ModalDialog modal; + ProjectSettingsDialog dialog; + + GtkWindow* window = ProjectSettingsDialog_construct(dialog, modal); + + if(modal_dialog_show(window, modal) == eIDOK) + { + ProjectSettingsDialog_ok(dialog); + } + + gtk_widget_destroy(GTK_WIDGET(window)); + } +} + +// ============================================================================= +// Arbitrary Sides dialog + +void DoSides (int type, int axis) +{ + ModalDialog dialog; + GtkEntry* sides_entry; + + GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Arbitrary sides", G_CALLBACK(dialog_delete_callback), &dialog); + + GtkAccelGroup* accel = gtk_accel_group_new(); + gtk_window_add_accel_group(window, accel); + + { + GtkHBox* hbox = create_dialog_hbox(4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox)); + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Sides:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(entry), FALSE, FALSE, 0); + sides_entry = entry; + gtk_widget_grab_focus(GTK_WIDGET(entry)); + } + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0); + { + GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0); + } + { + GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &dialog); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0); + } + } + } + + if(modal_dialog_show(window, dialog) == eIDOK) + { + const char *str = gtk_entry_get_text(sides_entry); + + Scene_BrushConstructPrefab(GlobalSceneGraph(), (EBrushPrefab)type, atoi(str), TextureBrowser_GetSelectedShader(GlobalTextureBrowser())); + } + + gtk_widget_destroy(GTK_WIDGET(window)); +} + +// ============================================================================= +// About dialog (no program is complete without one) + +void about_button_changelog (GtkWidget *widget, gpointer data) +{ + StringOutputStream log(256); + log << AppPath_get() << "changelog.txt"; + OpenURL(log.c_str()); +} + +void about_button_credits (GtkWidget *widget, gpointer data) +{ + StringOutputStream cred(256); + cred << AppPath_get() << "credits.html"; + OpenURL(cred.c_str()); +} + +void DoAbout() +{ + ModalDialog dialog; + ModalDialogButton ok_button(dialog, eIDOK); + + GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), "About GtkRadiant", dialog); + + { + GtkVBox* vbox = create_dialog_vbox(4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox)); + + { + GtkHBox* hbox = create_dialog_hbox(4); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, TRUE, 0); + + { + GtkVBox* vbox2 = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox2), TRUE, FALSE, 0); + { + GtkFrame* frame = create_dialog_frame(0, GTK_SHADOW_IN); + gtk_box_pack_start(GTK_BOX (vbox2), GTK_WIDGET(frame), FALSE, FALSE, 0); + { + GtkImage* image = new_local_image("logo.bmp"); + gtk_widget_show(GTK_WIDGET(image)); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(image)); + } + } + } + + { + GtkLabel* label = GTK_LABEL(gtk_label_new("GtkRadiant " RADIANT_VERSION "\n" + __DATE__ "\n\n" + RADIANT_ABOUTMSG "\n\n" + "By qeradiant.com\n\n" + "This program is free software\n" + "licensed under the GNU GPL.\n\n" + "GtkRadiant is unsupported, however\n" + "you may report your problems at\n" + "http://zerowing.idsoftware.com/bugzilla" + )); + + gtk_widget_show(GTK_WIDGET(label)); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label), FALSE, FALSE, 0); + gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5); + gtk_label_set_justify(label, GTK_JUSTIFY_LEFT); + } + + { + GtkVBox* vbox2 = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox2), FALSE, TRUE, 0); + { + GtkButton* button = create_modal_dialog_button("OK", ok_button); + gtk_box_pack_start (GTK_BOX (vbox2), GTK_WIDGET(button), FALSE, FALSE, 0); + } + { + GtkButton* button = create_dialog_button("Credits", G_CALLBACK(about_button_credits), 0); + gtk_box_pack_start (GTK_BOX (vbox2), GTK_WIDGET(button), FALSE, FALSE, 0); + } + { + GtkButton* button = create_dialog_button("Changelog", G_CALLBACK(about_button_changelog), 0); + gtk_box_pack_start (GTK_BOX (vbox2), GTK_WIDGET(button), FALSE, FALSE, 0); + } + } + } + { + GtkFrame* frame = create_dialog_frame("OpenGL Properties"); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(frame), FALSE, FALSE, 0); + { + GtkTable* table = create_dialog_table(3, 2, 4, 4, 4); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(table)); + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Vendor:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Version:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Renderer:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new(reinterpret_cast(glGetString(GL_VENDOR)))); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new(reinterpret_cast(glGetString(GL_VERSION)))); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new(reinterpret_cast(glGetString(GL_RENDERER)))); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 1, 2, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + } + { + GtkFrame* frame = create_dialog_frame("OpenGL Extensions"); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(frame), TRUE, TRUE, 0); + { + GtkScrolledWindow* sc_extensions = create_scrolled_window(GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, 4); + gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET(sc_extensions)); + { + GtkWidget* text_extensions = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text_extensions), FALSE); + gtk_container_add (GTK_CONTAINER (sc_extensions), text_extensions); + GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_extensions)); + gtk_text_buffer_set_text(buffer, reinterpret_cast(glGetString(GL_EXTENSIONS)), -1); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_extensions), GTK_WRAP_WORD); + gtk_widget_show(text_extensions); + } + } + } + } + } + + modal_dialog_show(window, dialog); + + gtk_widget_destroy(GTK_WIDGET(window)); +} + +// ============================================================================= +// TextureLayout dialog + +EMessageBoxReturn DoTextureLayout (float *fx, float *fy) +{ + ModalDialog dialog; + ModalDialogButton ok_button(dialog, eIDOK); + ModalDialogButton cancel_button(dialog, eIDCANCEL); + GtkEntry* x; + GtkEntry* y; + + GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), "Patch texture layout", dialog); + + GtkAccelGroup* accel = gtk_accel_group_new(); + gtk_window_add_accel_group(window, accel); + + { + GtkHBox* hbox = create_dialog_hbox(4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox)); + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0); + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Texture will be fit across the patch based\n" + "on the x and y values given. Values of 1x1\n" + "will \"fit\" the texture. 2x2 will repeat\n" + "it twice, etc.")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), TRUE, TRUE, 0); + gtk_label_set_justify(label, GTK_JUSTIFY_LEFT); + } + { + GtkTable* table = create_dialog_table(2, 2, 4, 4); + gtk_widget_show(GTK_WIDGET(table)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(table), TRUE, TRUE, 0); + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Texture x:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Texture y:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + gtk_widget_grab_focus(GTK_WIDGET(entry)); + + x = entry; + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + y = entry; + } + } + } + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, FALSE, 0); + { + GtkButton* button = create_modal_dialog_button("OK", ok_button); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0); + } + { + GtkButton* button = create_modal_dialog_button("Cancel", cancel_button); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0); + } + } + } + + // Initialize + gtk_entry_set_text(x, "4.0"); + gtk_entry_set_text(y, "4.0"); + + + EMessageBoxReturn ret = modal_dialog_show(window, dialog); + if (ret == eIDOK) + { + *fx = static_cast(atof(gtk_entry_get_text(x))); + *fy = static_cast(atof(gtk_entry_get_text(y))); + } + + gtk_widget_destroy(GTK_WIDGET(window)); + + return ret; +} + +// ============================================================================= +// Text Editor dialog + +// master window widget +static GtkWidget *text_editor = 0; +static GtkWidget *text_widget; // slave, text widget from the gtk editor + +static gint editor_delete (GtkWidget *widget, gpointer data) +{ + if (gtk_MessageBox (widget, "Close the shader editor ?", "Radiant", eMB_YESNO, eMB_ICONQUESTION) == eIDNO) + return TRUE; + + gtk_widget_hide (text_editor); + + return TRUE; +} + +static void editor_save (GtkWidget *widget, gpointer data) +{ + FILE *f = fopen ((char*)g_object_get_data (G_OBJECT (data), "filename"), "w"); + gpointer text = g_object_get_data (G_OBJECT (data), "text"); + + if (f == 0) + { + gtk_MessageBox (GTK_WIDGET(data), "Error saving file !"); + return; + } + + char *str = gtk_editable_get_chars (GTK_EDITABLE (text), 0, -1); + fwrite (str, 1, strlen (str), f); + fclose (f); +} + +static void editor_close (GtkWidget *widget, gpointer data) +{ + if (gtk_MessageBox (text_editor, "Close the shader editor ?", "Radiant", eMB_YESNO, eMB_ICONQUESTION) == eIDNO) + return; + + gtk_widget_hide (text_editor); +} + +static void CreateGtkTextEditor() +{ + GtkWidget *dlg; + GtkWidget *vbox, *hbox, *button, *scr, *text; + + dlg = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + g_signal_connect(G_OBJECT(dlg), "delete_event", + G_CALLBACK(editor_delete), 0); + gtk_window_set_default_size (GTK_WINDOW (dlg), 600, 300); + + vbox = gtk_vbox_new (FALSE, 5); + gtk_widget_show (vbox); + gtk_container_add(GTK_CONTAINER(dlg), GTK_WIDGET(vbox)); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + scr = gtk_scrolled_window_new (0, 0); + gtk_widget_show (scr); + gtk_box_pack_start(GTK_BOX(vbox), scr, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scr), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scr), GTK_SHADOW_IN); + + text = gtk_text_view_new(); + gtk_container_add (GTK_CONTAINER (scr), text); + gtk_widget_show (text); + g_object_set_data (G_OBJECT (dlg), "text", text); + gtk_text_view_set_editable (GTK_TEXT_VIEW(text), TRUE); + + hbox = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, TRUE, 0); + + button = gtk_button_new_with_label ("Close"); + gtk_widget_show (button); + gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(editor_close), dlg); + gtk_widget_set_usize (button, 60, -2); + + button = gtk_button_new_with_label ("Save"); + gtk_widget_show (button); + gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(editor_save), dlg); + gtk_widget_set_usize (button, 60, -2); + + text_editor = dlg; + text_widget = text; +} + +static void DoGtkTextEditor (const char* filename, guint cursorpos) +{ + if (!text_editor) + CreateGtkTextEditor(); // build it the first time we need it + + // Load file + FILE *f = fopen (filename, "r"); + + if (f == 0) + { + globalOutputStream() << "Unable to load file " << filename << " in shader editor.\n"; + gtk_widget_hide (text_editor); + } + else + { + fseek (f, 0, SEEK_END); + int len = ftell (f); + void *buf = malloc (len); + void *old_filename; + + rewind (f); + fread (buf, 1, len, f); + + gtk_window_set_title (GTK_WINDOW (text_editor), filename); + + GtkTextBuffer* text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_widget)); + gtk_text_buffer_set_text(text_buffer, (char*)buf, len); + + old_filename = g_object_get_data (G_OBJECT (text_editor), "filename"); + if (old_filename) + free(old_filename); + g_object_set_data (G_OBJECT (text_editor), "filename", strdup (filename)); + + // trying to show later + gtk_widget_show (text_editor); + +#ifdef WIN32 + process_gui(); +#endif + + // only move the cursor if it's not exceeding the size.. + // NOTE: this is erroneous, cursorpos is the offset in bytes, not in characters + // len is the max size in bytes, not in characters either, but the character count is below that limit.. + // thinking .. the difference between character count and byte count would be only because of CR/LF? + { + GtkTextIter text_iter; + // character offset, not byte offset + gtk_text_buffer_get_iter_at_offset(text_buffer, &text_iter, cursorpos); + gtk_text_buffer_place_cursor(text_buffer, &text_iter); + } + +#ifdef WIN32 + gtk_widget_queue_draw(text_widget); +#endif + + free (buf); + fclose (f); + } +} + +// ============================================================================= +// Light Intensity dialog + +EMessageBoxReturn DoLightIntensityDlg (int *intensity) +{ + ModalDialog dialog; + GtkEntry* intensity_entry; + ModalDialogButton ok_button(dialog, eIDOK); + ModalDialogButton cancel_button(dialog, eIDCANCEL); + + GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), "Light intensity", dialog, -1, -1); + + GtkAccelGroup *accel_group = gtk_accel_group_new(); + gtk_window_add_accel_group(window, accel_group); + + { + GtkHBox* hbox = create_dialog_hbox(4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox)); + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0); + { + GtkLabel* label = GTK_LABEL(gtk_label_new("ESC for default, ENTER to validate")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(entry), TRUE, TRUE, 0); + + gtk_widget_grab_focus(GTK_WIDGET(entry)); + + intensity_entry = entry; + } + } + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, FALSE, 0); + + { + GtkButton* button = create_modal_dialog_button("OK", ok_button); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE); + } + { + GtkButton* button = create_modal_dialog_button("Cancel", cancel_button); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE); + } + } + } + + char buf[16]; + sprintf (buf, "%d", *intensity); + gtk_entry_set_text(intensity_entry, buf); + + EMessageBoxReturn ret = modal_dialog_show(window, dialog); + if(ret == eIDOK) + *intensity = atoi (gtk_entry_get_text(intensity_entry)); + + gtk_widget_destroy(GTK_WIDGET(window)); + + return ret; +} + +// ============================================================================= +// Add new shader tag dialog + +EMessageBoxReturn DoShaderTagDlg (CopiedString* tag, char* title) +{ + ModalDialog dialog; + GtkEntry* textentry; + ModalDialogButton ok_button(dialog, eIDOK); + ModalDialogButton cancel_button(dialog, eIDCANCEL); + + GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), title, dialog, -1, -1); + + GtkAccelGroup *accel_group = gtk_accel_group_new(); + gtk_window_add_accel_group(window, accel_group); + + { + GtkHBox* hbox = create_dialog_hbox(4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox)); + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0); + { + //GtkLabel* label = GTK_LABEL(gtk_label_new("Enter one ore more tags separated by spaces")); + GtkLabel* label = GTK_LABEL(gtk_label_new("ESC to cancel, ENTER to validate")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(entry), TRUE, TRUE, 0); + + gtk_widget_grab_focus(GTK_WIDGET(entry)); + + textentry = entry; + } + } + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, FALSE, 0); + + { + GtkButton* button = create_modal_dialog_button("OK", ok_button); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE); + } + { + GtkButton* button = create_modal_dialog_button("Cancel", cancel_button); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE); + } + } + } + + EMessageBoxReturn ret = modal_dialog_show(window, dialog); + if(ret == eIDOK) + { + *tag = gtk_entry_get_text(textentry); + } + + gtk_widget_destroy(GTK_WIDGET(window)); + + return ret; +} + +EMessageBoxReturn DoShaderInfoDlg (const char* name, const char* filename, char* title) +{ + ModalDialog dialog; + ModalDialogButton ok_button(dialog, eIDOK); + + GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), title, dialog, -1, -1); + + GtkAccelGroup *accel_group = gtk_accel_group_new(); + gtk_window_add_accel_group(window, accel_group); + + { + GtkHBox* hbox = create_dialog_hbox(4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox)); + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, FALSE, 0); + { + GtkLabel* label = GTK_LABEL(gtk_label_new("The selected shader")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new(name)); + gtk_widget_show(GTK_WIDGET(label)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("is located in file")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new(filename)); + gtk_widget_show(GTK_WIDGET(label)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, FALSE, 0); + } + { + GtkButton* button = create_modal_dialog_button("OK", ok_button); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE); + } + } + } + + EMessageBoxReturn ret = modal_dialog_show(window, dialog); + + gtk_widget_destroy(GTK_WIDGET(window)); + + return ret; +} + + + +#ifdef WIN32 +#include +#endif + +#ifdef WIN32 + // use the file associations to open files instead of builtin Gtk editor +bool g_TextEditor_useWin32Editor = true; +#else + // custom shader editor +bool g_TextEditor_useCustomEditor = false; +CopiedString g_TextEditor_editorCommand(""); +#endif + +void DoTextEditor (const char* filename, int cursorpos) +{ +#ifdef WIN32 + if (g_TextEditor_useWin32Editor) + { + globalOutputStream() << "opening file '" << filename << "' (line " << cursorpos << " info ignored)\n"; + ShellExecute((HWND)GDK_WINDOW_HWND (GTK_WIDGET(MainFrame_getWindow())->window), "open", filename, 0, 0, SW_SHOW ); + return; + } +#else + // check if a custom editor is set + if(g_TextEditor_useCustomEditor && !g_TextEditor_editorCommand.empty()) + { + StringOutputStream strEditCommand(256); + strEditCommand << g_TextEditor_editorCommand.c_str() << " \"" << filename << "\""; + + globalOutputStream() << "Launching: " << strEditCommand.c_str() << "\n"; + // note: linux does not return false if the command failed so it will assume success + if (Q_Exec(0, const_cast(strEditCommand.c_str()), 0, true) == false) + { + globalOutputStream() << "Failed to execute " << strEditCommand.c_str() << ", using default\n"; + } + else + { + // the command (appeared) to run successfully, no need to do anything more + return; + } + } +#endif + + DoGtkTextEditor (filename, cursorpos); +} diff --git a/radiant/gtkdlgs.h b/radiant/gtkdlgs.h new file mode 100644 index 00000000..36989916 --- /dev/null +++ b/radiant/gtkdlgs.h @@ -0,0 +1,59 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(INCLUDED_GTKDLGS_H) +#define INCLUDED_GTKDLGS_H + +#include "qerplugin.h" +#include "string/string.h" + +EMessageBoxReturn DoLightIntensityDlg (int *intensity); +EMessageBoxReturn DoShaderTagDlg (CopiedString *tag, char* title); +EMessageBoxReturn DoShaderInfoDlg (const char* name, const char* filename, char* title); +EMessageBoxReturn DoTextureLayout (float *fx, float *fy); +void DoTextEditor (const char* filename, int cursorpos); + +void DoProjectSettings(); + +void DoFind(); +void DoSides(int type, int axis); +void DoAbout(); + + +#ifdef WIN32 +extern bool g_TextEditor_useWin32Editor; +#else +#include "string/stringfwd.h" +extern bool g_TextEditor_useCustomEditor; +extern CopiedString g_TextEditor_editorCommand; +#endif + + +#endif diff --git a/radiant/gtkmisc.cpp b/radiant/gtkmisc.cpp new file mode 100644 index 00000000..f3175259 --- /dev/null +++ b/radiant/gtkmisc.cpp @@ -0,0 +1,164 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// Small functions to help with GTK +// + +#include "gtkmisc.h" + +#include +#include + +#include "math/vector.h" +#include "os/path.h" + +#include "gtkutil/dialog.h" +#include "gtkutil/filechooser.h" +#include "gtkutil/menu.h" +#include "gtkutil/toolbar.h" +#include "commands.h" + + +// ============================================================================= +// Misc stuff + +void command_connect_accelerator(const char* name) +{ + const Command& command = GlobalCommands_find(name); + GlobalShortcuts_register(name); + global_accel_group_connect(command.m_accelerator, command.m_callback); +} + +void command_disconnect_accelerator(const char* name) +{ + const Command& command = GlobalCommands_find(name); + global_accel_group_disconnect(command.m_accelerator, command.m_callback); +} + +void toggle_add_accelerator(const char* name) +{ + const Toggle& toggle = GlobalToggles_find(name); + GlobalShortcuts_register(name); + global_accel_group_connect(toggle.m_command.m_accelerator, toggle.m_command.m_callback); +} + +GtkCheckMenuItem* create_check_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const char* commandName) +{ + GlobalShortcuts_register(commandName); + const Toggle& toggle = GlobalToggles_find(commandName); + global_accel_group_connect(toggle.m_command.m_accelerator, toggle.m_command.m_callback); + return create_check_menu_item_with_mnemonic(menu, mnemonic, toggle); +} + +GtkMenuItem* create_menu_item_with_mnemonic(GtkMenu* menu, const char *mnemonic, const char* commandName) +{ + GlobalShortcuts_register(commandName); + const Command& command = GlobalCommands_find(commandName); + global_accel_group_connect(command.m_accelerator, command.m_callback); + return create_menu_item_with_mnemonic(menu, mnemonic, command); +} + +GtkButton* toolbar_append_button(GtkToolbar* toolbar, const char* description, const char* icon, const char* commandName) +{ + return toolbar_append_button(toolbar, description, icon, GlobalCommands_find(commandName)); +} + +GtkToggleButton* toolbar_append_toggle_button(GtkToolbar* toolbar, const char* description, const char* icon, const char* commandName) +{ + return toolbar_append_toggle_button(toolbar, description, icon, GlobalToggles_find(commandName)); +} + +// ============================================================================= +// File dialog + +bool color_dialog (GtkWidget *parent, Vector3& color, const char* title) +{ + GtkWidget* dlg; + double clr[3]; + ModalDialog dialog; + + clr[0] = color[0]; + clr[1] = color[1]; + clr[2] = color[2]; + + dlg = gtk_color_selection_dialog_new (title); + gtk_color_selection_set_color (GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG (dlg)->colorsel), clr); + g_signal_connect(G_OBJECT(dlg), "delete_event", G_CALLBACK(dialog_delete_callback), &dialog); + g_signal_connect(G_OBJECT(GTK_COLOR_SELECTION_DIALOG(dlg)->ok_button), "clicked", G_CALLBACK(dialog_button_ok), &dialog); + g_signal_connect(G_OBJECT(GTK_COLOR_SELECTION_DIALOG(dlg)->cancel_button), "clicked", G_CALLBACK(dialog_button_cancel), &dialog); + + if (parent != 0) + gtk_window_set_transient_for (GTK_WINDOW (dlg), GTK_WINDOW (parent)); + + bool ok = modal_dialog_show(GTK_WINDOW(dlg), dialog) == eIDOK; + if(ok) + { + GdkColor gdkcolor; + gtk_color_selection_get_current_color (GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG (dlg)->colorsel), &gdkcolor); + clr[0] = gdkcolor.red / 65535.0; + clr[1] = gdkcolor.green / 65535.0; + clr[2] = gdkcolor.blue / 65535.0; + + color[0] = (float)clr[0]; + color[1] = (float)clr[1]; + color[2] = (float)clr[2]; + } + + gtk_widget_destroy(dlg); + + return ok; +} + +void button_clicked_entry_browse_file(GtkWidget* widget, GtkEntry* entry) +{ + const char *filename = file_dialog(gtk_widget_get_toplevel(widget), TRUE, "Choose File", gtk_entry_get_text(entry)); + + if(filename != 0) + { + gtk_entry_set_text(entry, filename); + } +} + +void button_clicked_entry_browse_directory(GtkWidget* widget, GtkEntry* entry) +{ + const char* text = gtk_entry_get_text(entry); + char *dir = dir_dialog(gtk_widget_get_toplevel(widget), "Choose Directory", path_is_absolute(text) ? text : "" ); + + if(dir != 0) + { + gchar* converted = g_filename_to_utf8(dir, -1, 0, 0, 0); + gtk_entry_set_text(entry, converted); + g_free(dir); + g_free(converted); + } +} + + diff --git a/radiant/gtkmisc.h b/radiant/gtkmisc.h new file mode 100644 index 00000000..4dbe3b6a --- /dev/null +++ b/radiant/gtkmisc.h @@ -0,0 +1,75 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(INCLUDED_GTKMISC_H) +#define INCLUDED_GTKMISC_H + +#include + +inline void process_gui() +{ + while(gtk_events_pending()) + { + gtk_main_iteration(); + } +} + +void command_connect_accelerator(const char* commandName); +void command_disconnect_accelerator(const char* commandName); +void toggle_add_accelerator(const char* commandName); + +typedef struct _GtkMenu GtkMenu; +typedef struct _GtkMenuItem GtkMenuItem; +typedef struct _GtkCheckMenuItem GtkCheckMenuItem; + +// this also sets up the shortcut using command_connect_accelerator +GtkMenuItem* create_menu_item_with_mnemonic(GtkMenu *menu, const char *mnemonic, const char* commandName); +// this also sets up the shortcut using command_connect_accelerator +GtkCheckMenuItem* create_check_menu_item_with_mnemonic(GtkMenu* menu, const char* mnemonic, const char* commandName); + +typedef struct _GtkButton GtkButton; +typedef struct _GtkToggleButton GtkToggleButton; +typedef struct _GtkToolbar GtkToolbar; + +// this DOES NOT set up the shortcut using command_connect_accelerator +GtkButton* toolbar_append_button(GtkToolbar* toolbar, const char* description, const char* icon, const char* commandName); +// this DOES NOT set up the shortcut using command_connect_accelerator +GtkToggleButton* toolbar_append_toggle_button(GtkToolbar* toolbar, const char* description, const char* icon, const char* commandName); + + +template class BasicVector3; +typedef BasicVector3 Vector3; +bool color_dialog (GtkWidget *parent, Vector3& color, const char* title = "Choose Color"); + +typedef struct _GtkEntry GtkEntry; +void button_clicked_entry_browse_file(GtkWidget* widget, GtkEntry* entry); +void button_clicked_entry_browse_directory(GtkWidget* widget, GtkEntry* entry); + +#endif diff --git a/radiant/help.cpp b/radiant/help.cpp new file mode 100644 index 00000000..363b1f0a --- /dev/null +++ b/radiant/help.cpp @@ -0,0 +1,140 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "help.h" + +#include "debugging/debugging.h" + +#include +#include + +#include "libxml/parser.h" +#include "generic/callback.h" +#include "gtkutil/menu.h" +#include "stream/stringstream.h" +#include "os/file.h" + +#include "url.h" +#include "preferences.h" +#include "mainframe.h" + +/*! +the urls to fire up in the game packs help menus +*/ +namespace +{ + std::list mHelpURLs; +} + +/*! +needed for hooking in Gtk+ +*/ +void HandleHelpCommand(CopiedString& str) +{ + OpenURL(str.c_str()); +} + +void process_xlink(const char* filename, const char *menu_name, const char *base_url, GtkMenu *menu) +{ + if(file_exists(filename)) + { + xmlDocPtr pDoc = xmlParseFile(filename); + if (pDoc) + { + globalOutputStream() << "Processing .xlink file '" << filename << "'\n"; + // create sub menu + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic(menu, menu_name); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + // start walking the nodes, find the 'links' one + xmlNodePtr pNode = pDoc->children; + while (pNode && strcmp((const char*)pNode->name, "links")) + pNode=pNode->next; + if (pNode) + { + pNode = pNode->children; + while(pNode) + { + if(!strcmp((const char*)pNode->name, "item")) + { + // process the URL + CopiedString url; + + xmlChar* prop = xmlGetProp(pNode, reinterpret_cast("url")); + ASSERT_NOTNULL(prop); + if(strstr(reinterpret_cast(prop), "http://")) + { + // complete URL + url = reinterpret_cast(prop); + } + else + { + // relative URL + StringOutputStream full(256); + full << base_url << reinterpret_cast(prop); + url = full.c_str(); + } + + mHelpURLs.push_back(url); + + xmlFree(prop); + + prop = xmlGetProp(pNode, reinterpret_cast("name")); + ASSERT_NOTNULL(prop); + create_menu_item_with_mnemonic(menu_in_menu, reinterpret_cast(prop), ReferenceCaller(mHelpURLs.back())); + xmlFree(prop); + } + pNode=pNode->next; + } + } + xmlFreeDoc(pDoc); + } + else + { + globalOutputStream() << "'" << filename << "' parse failed\n"; + } + } + else + { + globalOutputStream() << "'" << filename << "' not found\n"; + } +} + +void create_game_help_menu(GtkMenu *menu) +{ + StringOutputStream filename(256); + filename << AppPath_get() << "global.xlink"; + process_xlink(filename.c_str(), "General", AppPath_get(), menu); + +#if 1 + filename.clear(); + filename << g_pGameDescription->mGameToolsPath.c_str() << "game.xlink"; + process_xlink(filename.c_str(), g_pGameDescription->getRequiredKeyValue("name"), g_pGameDescription->mGameToolsPath.c_str(), menu); +#else + for(std::list::iterator iGame = g_GamesDialog.mGames.begin(); iGame != g_GamesDialog.mGames.end(); ++iGame) + { + filename.clear(); + filename << (*iGame)->mGameToolsPath.c_str() << "game.xlink"; + process_xlink(filename.c_str(), (*iGame)->getRequiredKeyValue("name"), (*iGame)->mGameToolsPath.c_str(), menu); + } +#endif +} + diff --git a/radiant/help.h b/radiant/help.h new file mode 100644 index 00000000..02c6b14c --- /dev/null +++ b/radiant/help.h @@ -0,0 +1,28 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_HELP_H) +#define INCLUDED_HELP_H + +typedef struct _GtkMenu GtkMenu; +void create_game_help_menu(GtkMenu *menu); + +#endif diff --git a/radiant/image.cpp b/radiant/image.cpp new file mode 100644 index 00000000..7495db68 --- /dev/null +++ b/radiant/image.cpp @@ -0,0 +1,70 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "image.h" + +#include "modulesystem.h" +#include "iimage.h" +#include "ifilesystem.h" +#include "iarchive.h" + +#include "generic/reference.h" +#include "os/path.h" +#include "stream/stringstream.h" + + +typedef Modules<_QERPlugImageTable> ImageModules; +ImageModules& Textures_getImageModules(); + +/// \brief Returns a new image for the first file matching \p name in one of the available texture formats, or 0 if no file is found. +Image* QERApp_LoadImage(void* environment, const char* name) +{ + Image* image = 0; + class LoadImageVisitor : public ImageModules::Visitor + { + const char* m_name; + Image*& m_image; + public: + LoadImageVisitor(const char* name, Image*& image) + : m_name(name), m_image(image) + { + } + void visit(const char* name, const _QERPlugImageTable& table) const + { + if(m_image == 0) + { + StringOutputStream fullname(256); + fullname << m_name << '.' << name; + ArchiveFile* file = GlobalFileSystem().openFile(fullname.c_str()); + if(file != 0) + { + m_image = table.loadImage(*file); + file->release(); + } + } + } + }; + + Textures_getImageModules().foreachModule(LoadImageVisitor(name, image)); + + return image; +} + diff --git a/radiant/image.h b/radiant/image.h new file mode 100644 index 00000000..3607c071 --- /dev/null +++ b/radiant/image.h @@ -0,0 +1,28 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_IMAGE_H) +#define INCLUDED_IMAGE_H + +class Image; +Image* QERApp_LoadImage(void* environment, const char* name); + +#endif diff --git a/radiant/main.cpp b/radiant/main.cpp new file mode 100644 index 00000000..a1e39bad --- /dev/null +++ b/radiant/main.cpp @@ -0,0 +1,656 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/*! \mainpage GtkRadiant Documentation Index + +\section intro_sec Introduction + +This documentation is generated from comments in the source code. + +\section links_sec Useful Links + +\link include/itextstream.h include/itextstream.h \endlink - Global output and error message streams, similar to std::cout and std::cerr. \n + +FileInputStream - similar to std::ifstream (binary mode) \n +FileOutputStream - similar to std::ofstream (binary mode) \n +TextFileInputStream - similar to std::ifstream (text mode) \n +TextFileOutputStream - similar to std::ofstream (text mode) \n +StringOutputStream - similar to std::stringstream \n + +\link string/string.h string/string.h \endlink - C-style string comparison and memory management. \n +\link os/path.h os/path.h \endlink - Path manipulation for radiant's standard path format \n +\link os/file.h os/file.h \endlink - OS file-system access. \n + +::CopiedString - automatic string memory management \n +Array - automatic array memory management \n +HashTable - generic hashtable, similar to std::hash_map \n + +\link math/vector.h math/vector.h \endlink - Vectors \n +\link math/matrix.h math/matrix.h \endlink - Matrices \n +\link math/quaternion.h math/quaternion.h \endlink - Quaternions \n +\link math/plane.h math/plane.h \endlink - Planes \n +\link math/aabb.h math/aabb.h \endlink - AABBs \n + +Callback MemberCaller FunctionCaller - callbacks similar to using boost::function with boost::bind \n +SmartPointer SmartReference - smart-pointer and smart-reference similar to Loki's SmartPtr \n + +\link generic/bitfield.h generic/bitfield.h \endlink - Type-safe bitfield \n +\link generic/enumeration.h generic/enumeration.h \endlink - Type-safe enumeration \n + +DefaultAllocator - Memory allocation using new/delete, compliant with std::allocator interface \n + +\link debugging/debugging.h debugging/debugging.h \endlink - Debugging macros \n + +*/ + +#include "main.h" + +#include "version.h" + +#include "debugging/debugging.h" + +#include "iundo.h" + +#include + +#include "cmdlib.h" +#include "os/file.h" +#include "os/path.h" +#include "stream/stringstream.h" +#include "stream/textfilestream.h" + +#include "gtkutil/messagebox.h" +#include "gtkutil/image.h" +#include "console.h" +#include "texwindow.h" +#include "map.h" +#include "mainframe.h" +#include "commands.h" +#include "preferences.h" +#include "environment.h" +#include "referencecache.h" +#include "stacktrace.h" + +void show_splash(); +void hide_splash(); + +void error_redirect (const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) +{ + gboolean in_recursion; + gboolean is_fatal; + char buf[256]; + + in_recursion = (log_level & G_LOG_FLAG_RECURSION) != 0; + is_fatal = (log_level & G_LOG_FLAG_FATAL) != 0; + log_level = (GLogLevelFlags) (log_level & G_LOG_LEVEL_MASK); + + if (!message) + message = "(0) message"; + + if (domain) + strcpy (buf, domain); + else + strcpy (buf, "**"); + strcat (buf, "-"); + + switch (log_level) + { + case G_LOG_LEVEL_ERROR: + if (in_recursion) + strcat (buf, "ERROR (recursed) **: "); + else + strcat (buf, "ERROR **: "); + break; + case G_LOG_LEVEL_CRITICAL: + if (in_recursion) + strcat (buf, "CRITICAL (recursed) **: "); + else + strcat (buf, "CRITICAL **: "); + break; + case G_LOG_LEVEL_WARNING: + if (in_recursion) + strcat (buf, "WARNING (recursed) **: "); + else + strcat (buf, "WARNING **: "); + break; + case G_LOG_LEVEL_MESSAGE: + if (in_recursion) + strcat (buf, "Message (recursed): "); + else + strcat (buf, "Message: "); + break; + case G_LOG_LEVEL_INFO: + if (in_recursion) + strcat (buf, "INFO (recursed): "); + else + strcat (buf, "INFO: "); + break; + case G_LOG_LEVEL_DEBUG: + if (in_recursion) + strcat (buf, "DEBUG (recursed): "); + else + strcat (buf, "DEBUG: "); + break; + default: + /* we are used for a log level that is not defined by GLib itself, + * try to make the best out of it. + */ + if (in_recursion) + strcat (buf, "LOG (recursed:"); + else + strcat (buf, "LOG ("); + if (log_level) + { + gchar string[] = "0x00): "; + gchar *p = string + 2; + guint i; + + i = g_bit_nth_msf (log_level, -1); + *p = i >> 4; + p++; + *p = '0' + (i & 0xf); + if (*p > '9') + *p += 'A' - '9' - 1; + + strcat (buf, string); + } else + strcat (buf, "): "); + } + + strcat (buf, message); + if (is_fatal) + strcat (buf, "\naborting...\n"); + else + strcat (buf, "\n"); + + printf ("%s\n", buf); + + ERROR_MESSAGE("GTK+ error: " << buf); +} + +#if defined (_DEBUG) && defined (WIN32) && defined (_MSC_VER) +#include "crtdbg.h" +#endif + +void crt_init() +{ +#if defined (_DEBUG) && defined (WIN32) && defined (_MSC_VER) + _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); +#endif +} + +class Lock +{ + bool m_locked; +public: + Lock() : m_locked(false) + { + } + void lock() + { + m_locked = true; + } + void unlock() + { + m_locked = false; + } + bool locked() const + { + return m_locked; + } +}; + +class ScopedLock +{ + Lock& m_lock; +public: + ScopedLock(Lock& lock) : m_lock(lock) + { + m_lock.lock(); + } + ~ScopedLock() + { + m_lock.unlock(); + } +}; + +class LineLimitedTextOutputStream : public TextOutputStream +{ + TextOutputStream& outputStream; + std::size_t count; +public: + LineLimitedTextOutputStream(TextOutputStream& outputStream, std::size_t count) + : outputStream(outputStream), count(count) + { + } + std::size_t write(const char* buffer, std::size_t length) + { + if(count != 0) + { + const char* p = buffer; + const char* end = buffer+length; + for(;;) + { + p = std::find(p, end, '\n'); + if(p == end) + { + break; + } + ++p; + if(--count == 0) + { + length = p - buffer; + break; + } + } + outputStream.write(buffer, length); + } + return length; + } +}; + +class PopupDebugMessageHandler : public DebugMessageHandler +{ + StringOutputStream m_buffer; + Lock m_lock; +public: + TextOutputStream& getOutputStream() + { + if(!m_lock.locked()) + { + return m_buffer; + } + return globalErrorStream(); + } + bool handleMessage() + { + getOutputStream() << "----------------\n"; + LineLimitedTextOutputStream outputStream(getOutputStream(), 24); + write_stack_trace(outputStream); + getOutputStream() << "----------------\n"; + globalErrorStream() << m_buffer.c_str(); + if(!m_lock.locked()) + { + ScopedLock lock(m_lock); +#if defined _DEBUG + m_buffer << "Break into the debugger?\n"; + bool handled = gtk_MessageBox(0, m_buffer.c_str(), "Radiant - Runtime Error", eMB_YESNO, eMB_ICONERROR) == eIDNO; + m_buffer.clear(); + return handled; +#else + m_buffer << "Please report this error to the developers\n"; + gtk_MessageBox(0, m_buffer.c_str(), "Radiant - Runtime Error", eMB_OK, eMB_ICONERROR); + m_buffer.clear(); +#endif + } + return true; + } +}; + +typedef Static GlobalPopupDebugMessageHandler; + +void streams_init() +{ + GlobalErrorStream::instance().setOutputStream(getSysPrintErrorStream()); + GlobalOutputStream::instance().setOutputStream(getSysPrintOutputStream()); +} + +void paths_init() +{ + const char* home = environment_get_home_path(); + Q_mkdir(home); + + { + StringOutputStream path(256); + path << home << RADIANT_VERSION << '/'; + g_strSettingsPath = path.c_str(); + } + + Q_mkdir(g_strSettingsPath.c_str()); + + g_strAppPath = environment_get_app_path(); + + // radiant is installed in the parent dir of "tools/" + // NOTE: this is not very easy for debugging + // maybe add options to lookup in several places? + // (for now I had to create symlinks) + { + StringOutputStream path(256); + path << g_strAppPath.c_str() << "bitmaps/"; + BitmapsPath_set(path.c_str()); + } + + // we will set this right after the game selection is done + g_strGameToolsPath = g_strAppPath; +} + +bool check_version_file(const char* filename, const char* version) +{ + TextFileInputStream file(filename); + if(!file.failed()) + { + char buf[10]; + buf[file.read(buf, 9)] = '\0'; + + // chomp it (the hard way) + int chomp = 0; + while(buf[chomp] >= '0' && buf[chomp] <= '9') + chomp++; + buf[chomp] = '\0'; + + return string_equal(buf, version); + } + return false; +} + +bool check_version() +{ + // a safe check to avoid people running broken installations + // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing) + // make something idiot proof and someone will make better idiots, this may be overkill + // let's leave it disabled in debug mode in any case + // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431 +#ifndef _DEBUG +#define CHECK_VERSION +#endif +#ifdef CHECK_VERSION + // locate and open RADIANT_MAJOR and RADIANT_MINOR + bool bVerIsGood = true; + { + StringOutputStream ver_file_name(256); + ver_file_name << AppPath_get() << "RADIANT_MAJOR"; + bVerIsGood = check_version_file(ver_file_name.c_str(), RADIANT_MAJOR_VERSION); + } + { + StringOutputStream ver_file_name(256); + ver_file_name << AppPath_get() << "RADIANT_MINOR"; + bVerIsGood = check_version_file(ver_file_name.c_str(), RADIANT_MINOR_VERSION); + } + + if (!bVerIsGood) + { + StringOutputStream msg(256); + msg << "This editor binary (" RADIANT_VERSION ") doesn't match what the latest setup has configured in this directory\n" + "Make sure you run the right/latest editor binary you installed\n" + << AppPath_get(); + gtk_MessageBox(0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONDEFAULT); + } + return bVerIsGood; +#else + return true; +#endif +} + +void create_global_pid() +{ + /*! + the global prefs loading / game selection dialog might fail for any reason we don't know about + we need to catch when it happens, to cleanup the stateful prefs which might be killing it + and to turn on console logging for lookup of the problem + this is the first part of the two step .pid system + http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297 + */ + StringOutputStream g_pidFile(256); ///< the global .pid file (only for global part of the startup) + + g_pidFile << SettingsPath_get() << "radiant.pid"; + + FILE *pid; + pid = fopen (g_pidFile.c_str(), "r"); + if (pid != 0) + { + fclose (pid); + + if (remove (g_pidFile.c_str()) == -1) + { + StringOutputStream msg(256); + msg << "WARNING: Could not delete " << g_pidFile.c_str(); + gtk_MessageBox (0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR ); + } + + // in debug, never prompt to clean registry, turn console logging auto after a failed start +#if !defined(_DEBUG) + StringOutputStream msg(256); + msg << "Radiant failed to start properly the last time it was run.\n" + "The failure may be related to current global preferences.\n" + "Do you want to reset global preferences to defaults?"; + + if (gtk_MessageBox (0, msg.c_str(), "Radiant - Startup Failure", eMB_YESNO, eMB_ICONQUESTION) == eIDYES) + { + g_GamesDialog.Reset(); + } + + msg.clear(); + msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again."; + + gtk_MessageBox (0, msg.c_str(), "Radiant - Console Log", eMB_OK); +#endif + + // set without saving, the class is not in a coherent state yet + // just do the value change and call to start logging, CGamesDialog will pickup when relevant + g_GamesDialog.m_bForceLogConsole = true; + Sys_LogFile(true); + } + + // create a primary .pid for global init run + pid = fopen (g_pidFile.c_str(), "w"); + if (pid) + fclose (pid); +} + +void remove_global_pid() +{ + StringOutputStream g_pidFile(256); + g_pidFile << SettingsPath_get() << "radiant.pid"; + + // close the primary + if (remove (g_pidFile.c_str()) == -1) + { + StringOutputStream msg(256); + msg << "WARNING: Could not delete " << g_pidFile.c_str(); + gtk_MessageBox (0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR ); + } +} + +/*! +now the secondary game dependant .pid file +http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297 +*/ +void create_local_pid() +{ + StringOutputStream g_pidGameFile(256); ///< the game-specific .pid file + g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid"; + + FILE *pid = fopen (g_pidGameFile.c_str(), "r"); + if (pid != 0) + { + fclose (pid); + if (remove (g_pidGameFile.c_str()) == -1) + { + StringOutputStream msg; + msg << "WARNING: Could not delete " << g_pidGameFile.c_str(); + gtk_MessageBox (0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR ); + } + + // in debug, never prompt to clean registry, turn console logging auto after a failed start +#if !defined(_DEBUG) + StringOutputStream msg; + msg << "Radiant failed to start properly the last time it was run.\n" + "The failure may be caused by current preferences.\n" + "Do you want to reset all preferences to defaults?"; + + if (gtk_MessageBox (0, msg.c_str(), "Radiant - Startup Failure", eMB_YESNO, eMB_ICONQUESTION) == eIDYES) + { + Preferences_Reset(); + } + + msg.clear(); + msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again."; + + gtk_MessageBox (0, msg.c_str(), "Radiant - Console Log", eMB_OK); +#endif + + // force console logging on! (will go in prefs too) + g_GamesDialog.m_bForceLogConsole = true; + Sys_LogFile(true); + } + else + { + // create one, will remove right after entering message loop + pid = fopen (g_pidGameFile.c_str(), "w"); + if (pid) + fclose (pid); + } +} + + +/*! +now the secondary game dependant .pid file +http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297 +*/ +void remove_local_pid() +{ + StringOutputStream g_pidGameFile(256); + g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid"; + remove(g_pidGameFile.c_str()); +} + +void user_shortcuts_init() +{ + StringOutputStream path(256); + path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/'; + LoadCommandMap(path.c_str()); + SaveCommandMap(path.c_str()); +} + +int main (int argc, char* argv[]) +{ + crt_init(); + + streams_init(); + + gtk_disable_setlocale(); + gtk_init(&argc, &argv); + + // redirect Gtk warnings to the console + g_log_set_handler ("Gdk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING| + G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0); + g_log_set_handler ("Gtk", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING| + G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0); + g_log_set_handler ("GtkGLExt", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING| + G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0); + g_log_set_handler ("GLib", (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING| + G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0); + g_log_set_handler (0, (GLogLevelFlags)(G_LOG_LEVEL_ERROR|G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING| + G_LOG_LEVEL_MESSAGE|G_LOG_LEVEL_INFO|G_LOG_LEVEL_DEBUG), error_redirect, 0); + + GlobalDebugMessageHandler::instance().setHandler(GlobalPopupDebugMessageHandler::instance()); + + environment_init(argc, argv); + + paths_init(); + + if(!check_version()) + { + return EXIT_FAILURE; + } + + show_splash(); + + create_global_pid(); + + GlobalPreferences_Init(); + + g_GamesDialog.Init(); + + g_strGameToolsPath = g_pGameDescription->mGameToolsPath; + + remove_global_pid(); + + g_Preferences.Init(); // must occur before create_local_pid() to allow preferences to be reset + + create_local_pid(); + + // in a very particular post-.pid startup + // we may have the console turned on and want to keep it that way + // so we use a latching system + if (g_GamesDialog.m_bForceLogConsole) + { + Sys_LogFile(true); + g_Console_enableLogging = true; + g_GamesDialog.m_bForceLogConsole = false; + } + + + Radiant_Initialise(); + + global_accel_init(); + + user_shortcuts_init(); + + g_pParentWnd = 0; + g_pParentWnd = new MainFrame(); + + hide_splash(); + + if (g_bLoadLastMap && !g_strLastMap.empty()) + { + Map_LoadFile(g_strLastMap.c_str()); + } + else + { + Map_New(); + } + + // load up shaders now that we have the map loaded + // eviltypeguy + TextureBrowser_ShowStartupShaders(GlobalTextureBrowser()); + + + remove_local_pid(); + + gtk_main(); + + // avoid saving prefs when the app is minimized + if (g_pParentWnd->IsSleeping()) + { + globalOutputStream() << "Shutdown while sleeping, not saving prefs\n"; + g_preferences_globals.disable_ini = true; + } + + Map_Free(); + + if (!Map_Unnamed(g_map)) + { + g_strLastMap = Map_Name(g_map); + } + + delete g_pParentWnd; + + global_accel_destroy(); + + Radiant_Shutdown(); + + // close the log file if any + Sys_LogFile(false); + + return EXIT_SUCCESS; +} + diff --git a/radiant/main.h b/radiant/main.h new file mode 100644 index 00000000..bda8f3da --- /dev/null +++ b/radiant/main.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MAIN_H) +#define INCLUDED_MAIN_H + +#endif diff --git a/radiant/mainframe.cpp b/radiant/mainframe.cpp new file mode 100644 index 00000000..f10c4f79 --- /dev/null +++ b/radiant/mainframe.cpp @@ -0,0 +1,3574 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// Main Window for Q3Radiant +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "mainframe.h" + +#include "debugging/debugging.h" +#include "version.h" + +#include "ifilesystem.h" +#include "iundo.h" +#include "ifilter.h" +#include "itoolbar.h" +#include "editable.h" +#include "ientity.h" +#include "ishaders.h" +#include "igl.h" +#include "moduleobserver.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "cmdlib.h" +#include "scenelib.h" +#include "stream/stringstream.h" +#include "signal/isignal.h" +#include "os/path.h" +#include "os/file.h" +#include "eclasslib.h" +#include "moduleobservers.h" + +#include "gtkutil/clipboard.h" +#include "gtkutil/container.h" +#include "gtkutil/frame.h" +#include "gtkutil/glfont.h" +#include "gtkutil/glwidget.h" +#include "gtkutil/image.h" +#include "gtkutil/menu.h" +#include "gtkutil/paned.h" +#include "gtkutil/widget.h" + +#include "autosave.h" +#include "build.h" +#include "brushmanip.h" +#include "brushmodule.h" +#include "camwindow.h" +#include "csg.h" +#include "commands.h" +#include "console.h" +#include "entity.h" +#include "entityinspector.h" +#include "entitylist.h" +#include "filters.h" +#include "findtexturedialog.h" +#include "grid.h" +#include "groupdialog.h" +#include "gtkdlgs.h" +#include "gtkmisc.h" +#include "help.h" +#include "map.h" +#include "mru.h" +#include "multimon.h" +#include "patchdialog.h" +#include "patchmanip.h" +#include "plugin.h" +#include "pluginmanager.h" +#include "pluginmenu.h" +#include "plugintoolbar.h" +#include "points.h" +#include "preferences.h" +#include "qe3.h" +#include "qgl.h" +#include "select.h" +#include "server.h" +#include "surfacedialog.h" +#include "textures.h" +#include "texwindow.h" +#include "url.h" +#include "xywindow.h" +#include "windowobservers.h" +#include "renderstate.h" +#include "feedback.h" +#include "referencecache.h" + + + +struct layout_globals_t +{ + WindowPosition m_position; + + + int nXYHeight; + int nXYWidth; + int nCamWidth; + int nCamHeight; + int nState; + + layout_globals_t() : + m_position(-1, -1, 640, 480), + + nXYHeight(300), + nXYWidth(300), + nCamWidth(200), + nCamHeight(200), + nState(GDK_WINDOW_STATE_MAXIMIZED) + { + } +}; + +layout_globals_t g_layout_globals; +glwindow_globals_t g_glwindow_globals; + + +// VFS +class VFSModuleObserver : public ModuleObserver +{ + std::size_t m_unrealised; +public: + VFSModuleObserver() : m_unrealised(1) + { + } + void realise() + { + if(--m_unrealised == 0) + { + QE_InitVFS(); + GlobalFileSystem().initialise(); + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + GlobalFileSystem().shutdown(); + } + } +}; + +VFSModuleObserver g_VFSModuleObserver; + +void VFS_Construct() +{ + Radiant_attachHomePathsObserver(g_VFSModuleObserver); +} +void VFS_Destroy() +{ + Radiant_detachHomePathsObserver(g_VFSModuleObserver); +} + +// Home Paths + +void HomePaths_Realise() +{ +#if defined(POSIX) + const char* prefix = g_pGameDescription->getKeyValue("prefix"); + if(!string_empty(prefix)) + { + StringOutputStream path(256); + path << DirectoryCleaned(g_get_home_dir()) << prefix << "/"; + g_qeglobals.m_userEnginePath = path.c_str(); + Q_mkdir(g_qeglobals.m_userEnginePath.c_str()); + } + else +#endif + { + g_qeglobals.m_userEnginePath = EnginePath_get(); + } + + { + StringOutputStream path(256); + path << g_qeglobals.m_userEnginePath.c_str() << gamename_get() << '/'; + g_qeglobals.m_userGamePath = path.c_str(); + } + ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()), "HomePaths_Realise: user-game-path is empty"); + Q_mkdir(g_qeglobals.m_userGamePath.c_str()); +} + +ModuleObservers g_homePathObservers; + +void Radiant_attachHomePathsObserver(ModuleObserver& observer) +{ + g_homePathObservers.attach(observer); +} + +void Radiant_detachHomePathsObserver(ModuleObserver& observer) +{ + g_homePathObservers.detach(observer); +} + +class HomePathsModuleObserver : public ModuleObserver +{ + std::size_t m_unrealised; +public: + HomePathsModuleObserver() : m_unrealised(1) + { + } + void realise() + { + if(--m_unrealised == 0) + { + HomePaths_Realise(); + g_homePathObservers.realise(); + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + g_homePathObservers.unrealise(); + } + } +}; + +HomePathsModuleObserver g_HomePathsModuleObserver; + +void HomePaths_Construct() +{ + Radiant_attachEnginePathObserver(g_HomePathsModuleObserver); +} +void HomePaths_Destroy() +{ + Radiant_detachEnginePathObserver(g_HomePathsModuleObserver); +} + + +// Engine Path + +CopiedString g_strEnginePath; +ModuleObservers g_enginePathObservers; +std::size_t g_enginepath_unrealised = 1; + +void Radiant_attachEnginePathObserver(ModuleObserver& observer) +{ + g_enginePathObservers.attach(observer); +} + +void Radiant_detachEnginePathObserver(ModuleObserver& observer) +{ + g_enginePathObservers.detach(observer); +} + + +void EnginePath_Realise() +{ + if(--g_enginepath_unrealised == 0) + { + g_enginePathObservers.realise(); + } +} + + +const char* EnginePath_get() +{ + ASSERT_MESSAGE(g_enginepath_unrealised == 0, "EnginePath_get: engine path not realised"); + return g_strEnginePath.c_str(); +} + +void EnginePath_Unrealise() +{ + if(++g_enginepath_unrealised == 1) + { + g_enginePathObservers.unrealise(); + } +} + +void setEnginePath(const char* path) +{ + StringOutputStream buffer(256); + buffer << DirectoryCleaned(path); + if(!path_equal(buffer.c_str(), g_strEnginePath.c_str())) + { +#if 0 + while(!ConfirmModified("Paths Changed")) + { + if(Map_Unnamed(g_map)) + { + Map_SaveAs(); + } + else + { + Map_Save(); + } + } + Map_RegionOff(); +#endif + + ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Changing Engine Path"); + + EnginePath_Unrealise(); + + g_strEnginePath = buffer.c_str(); + + EnginePath_Realise(); + } +} + + +// App Path + +CopiedString g_strAppPath; ///< holds the full path of the executable + +const char* AppPath_get() +{ + return g_strAppPath.c_str(); +} + +/// the path to the local rc-dir +const char* LocalRcPath_get(void) +{ + static CopiedString rc_path; + if(rc_path.empty()) + { + StringOutputStream stream(256); + stream << GlobalRadiant().getSettingsPath() << g_pGameDescription->mGameFile.c_str() << "/"; + rc_path = stream.c_str(); + } + return rc_path.c_str(); +} + +/// directory for temp files +/// NOTE: on *nix this is were we check for .pid +CopiedString g_strSettingsPath; +const char* SettingsPath_get() +{ + return g_strSettingsPath.c_str(); +} + + +/*! +points to the game tools directory, for instance +C:/Program Files/Quake III Arena/GtkRadiant +(or other games) +this is one of the main variables that are configured by the game selection on startup +[GameToolsPath]/plugins +[GameToolsPath]/modules +and also q3map, bspc +*/ +CopiedString g_strGameToolsPath; ///< this is set by g_GamesDialog + +const char* GameToolsPath_get() +{ + return g_strGameToolsPath.c_str(); +} + +void EnginePathImport(CopiedString& self, const char* value) +{ + setEnginePath(value); +} +typedef ReferenceCaller1 EnginePathImportCaller; + +void Paths_constructPreferences(PreferencesPage& page) +{ + page.appendPathEntry("Engine Path", true, + StringImportCallback(EnginePathImportCaller(g_strEnginePath)), + StringExportCallback(StringExportCaller(g_strEnginePath)) + ); +} +void Paths_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Paths", "Path Settings")); + Paths_constructPreferences(page); +} +void Paths_registerPreferencesPage() +{ + PreferencesDialog_addSettingsPage(FreeCaller1()); +} + + +class PathsDialog : public Dialog +{ +public: + GtkWindow* BuildDialog() + { + GtkFrame* frame = create_dialog_frame("Path settings", GTK_SHADOW_ETCHED_IN); + + GtkVBox* vbox2 = create_dialog_vbox(0, 4); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(vbox2)); + + { + PreferencesPage preferencesPage(*this, GTK_WIDGET(vbox2)); + Paths_constructPreferences(preferencesPage); + } + + return create_simple_modal_dialog_window("Engine Path Not Found", m_modal, GTK_WIDGET(frame)); + } +}; + +PathsDialog g_PathsDialog; + +void EnginePath_verify() +{ + if(!file_exists(g_strEnginePath.c_str())) + { + g_PathsDialog.Create(); + g_PathsDialog.DoModal(); + g_PathsDialog.Destroy(); + } +} + +namespace +{ + CopiedString g_gamename; + CopiedString g_gamemode; + ModuleObservers g_gameNameObservers; + ModuleObservers g_gameModeObservers; +} + +void Radiant_attachGameNameObserver(ModuleObserver& observer) +{ + g_gameNameObservers.attach(observer); +} + +void Radiant_detachGameNameObserver(ModuleObserver& observer) +{ + g_gameNameObservers.detach(observer); +} + +const char* basegame_get() +{ + return g_pGameDescription->getRequiredKeyValue("basegame"); +} + +const char* gamename_get() +{ + const char* gamename = g_gamename.c_str(); + if(string_empty(gamename)) + { + return basegame_get(); + } + return gamename; +} + +void gamename_set(const char* gamename) +{ + if(!string_equal(gamename, g_gamename.c_str())) + { + g_gameNameObservers.unrealise(); + g_gamename = gamename; + g_gameNameObservers.realise(); + } +} + +void Radiant_attachGameModeObserver(ModuleObserver& observer) +{ + g_gameModeObservers.attach(observer); +} + +void Radiant_detachGameModeObserver(ModuleObserver& observer) +{ + g_gameModeObservers.detach(observer); +} + +const char* gamemode_get() +{ + return g_gamemode.c_str(); +} + +void gamemode_set(const char* gamemode) +{ + if(!string_equal(gamemode, g_gamemode.c_str())) + { + g_gameModeObservers.unrealise(); + g_gamemode = gamemode; + g_gameModeObservers.realise(); + } +} + +#include "os/dir.h" + +class CLoadModule +{ + const char* m_path; +public: + CLoadModule(const char* path) : m_path(path) + { + } + void operator()(const char* name) const + { + char fullname[1024]; + ASSERT_MESSAGE(strlen(m_path) + strlen(name) < 1024, ""); + strcpy(fullname, m_path); + strcat(fullname, name); + globalOutputStream() << "Found '" << fullname << "'\n"; + GlobalModuleServer_loadModule(fullname); + } +}; + +const char* const c_library_extension = +#if defined(WIN32) +"dll" +#elif defined (__APPLE__) +"dylib" +#elif defined(__linux__) || defined (__FreeBSD__) +"so" +#endif +; + +void Radiant_loadModules(const char* path) +{ + Directory_forEach(path, MatchFileExtension(c_library_extension, CLoadModule(path))); +} + +void Radiant_loadModulesFromRoot(const char* directory) +{ + { + StringOutputStream path(256); + path << directory << g_pluginsDir; + Radiant_loadModules(path.c_str()); + } + + if(!string_equal(g_pluginsDir, g_modulesDir)) + { + StringOutputStream path(256); + path << directory << g_modulesDir; + Radiant_loadModules(path.c_str()); + } +} + +//! Make COLOR_BRUSHES override worldspawn eclass colour. +void SetWorldspawnColour(const Vector3& colour) +{ + EntityClass* worldspawn = GlobalEntityClassManager().findOrInsert("worldspawn", true); + eclass_release_state(worldspawn); + worldspawn->color = colour; + eclass_capture_state(worldspawn); +} + + +class WorldspawnColourEntityClassObserver : public ModuleObserver +{ + std::size_t m_unrealised; +public: + WorldspawnColourEntityClassObserver() : m_unrealised(1) + { + } + void realise() + { + if(--m_unrealised == 0) + { + SetWorldspawnColour(g_xywindow_globals.color_brushes); + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + } + } +}; + +WorldspawnColourEntityClassObserver g_WorldspawnColourEntityClassObserver; + + +ModuleObservers g_gameToolsPathObservers; + +void Radiant_attachGameToolsPathObserver(ModuleObserver& observer) +{ + g_gameToolsPathObservers.attach(observer); +} + +void Radiant_detachGameToolsPathObserver(ModuleObserver& observer) +{ + g_gameToolsPathObservers.detach(observer); +} + +void Radiant_Initialise() +{ + GlobalModuleServer_Initialise(); + + Radiant_loadModulesFromRoot(AppPath_get()); + + Preferences_Load(); + + bool success = Radiant_Construct(GlobalModuleServer_get()); + ASSERT_MESSAGE(success, "module system failed to initialise - see radiant.log for error messages"); + + g_gameToolsPathObservers.realise(); + g_gameModeObservers.realise(); + g_gameNameObservers.realise(); +} + +void Radiant_Shutdown() +{ + g_gameNameObservers.unrealise(); + g_gameModeObservers.unrealise(); + g_gameToolsPathObservers.unrealise(); + + if (!g_preferences_globals.disable_ini) + { + globalOutputStream() << "Start writing prefs\n"; + Preferences_Save(); + globalOutputStream() << "Done prefs\n"; + } + + Radiant_Destroy(); + + GlobalModuleServer_Shutdown(); +} + +void Exit() +{ + if(ConfirmModified("Exit Radiant")) + { + gtk_main_quit(); + } +} + + +void Undo() +{ + GlobalUndoSystem().undo(); + SceneChangeNotify(); +} + +void Redo() +{ + GlobalUndoSystem().redo(); + SceneChangeNotify(); +} + +void deleteSelection() +{ + UndoableCommand undo("deleteSelected"); + Select_Delete(); +} + +void Map_ExportSelected(TextOutputStream& ostream) +{ + Map_ExportSelected(ostream, Map_getFormat(g_map)); +} + +void Map_ImportSelected(TextInputStream& istream) +{ + Map_ImportSelected(istream, Map_getFormat(g_map)); +} + +void Selection_Copy() +{ + clipboard_copy(Map_ExportSelected); +} + +void Selection_Paste() +{ + clipboard_paste(Map_ImportSelected); +} + +void Copy() +{ + if(SelectedFaces_empty()) + { + Selection_Copy(); + } + else + { + SelectedFaces_copyTexture(); + } +} + +void Paste() +{ + if(SelectedFaces_empty()) + { + UndoableCommand undo("paste"); + + GlobalSelectionSystem().setSelectedAll(false); + Selection_Paste(); + } + else + { + SelectedFaces_pasteTexture(); + } +} + +void PasteToCamera() +{ + CamWnd& camwnd = *g_pParentWnd->GetCamWnd(); + GlobalSelectionSystem().setSelectedAll(false); + + UndoableCommand undo("pasteToCamera"); + + Selection_Paste(); + + // Work out the delta + Vector3 mid; + Select_GetMid(mid); + Vector3 delta = vector3_subtracted(vector3_snapped(Camera_getOrigin(camwnd), GetGridSize()), mid); + + // Move to camera + GlobalSelectionSystem().translateSelected(delta); +} + + +void ColorScheme_Original() +{ + TextureBrowser_setBackgroundColour(GlobalTextureBrowser(), Vector3(0.25f, 0.25f, 0.25f)); + + g_camwindow_globals.color_selbrushes3d = Vector3(1.0f, 0.0f, 0.0f); + g_camwindow_globals.color_cameraback = Vector3(0.25f, 0.25f, 0.25f); + CamWnd_Update(*g_pParentWnd->GetCamWnd()); + + g_xywindow_globals.color_gridback = Vector3(1.0f, 1.0f, 1.0f); + g_xywindow_globals.color_gridminor = Vector3(0.75f, 0.75f, 0.75f); + g_xywindow_globals.color_gridmajor = Vector3(0.5f, 0.5f, 0.5f); + g_xywindow_globals.color_gridminor_alt = Vector3(0.5f, 0.0f, 0.0f); + g_xywindow_globals.color_gridmajor_alt = Vector3(1.0f, 0.0f, 0.0f); + g_xywindow_globals.color_gridblock = Vector3(0.0f, 0.0f, 1.0f); + g_xywindow_globals.color_gridtext = Vector3(0.0f, 0.0f, 0.0f); + g_xywindow_globals.color_selbrushes = Vector3(1.0f, 0.0f, 0.0f); + g_xywindow_globals.color_clipper = Vector3(0.0f, 0.0f, 1.0f); + g_xywindow_globals.color_brushes = Vector3(0.0f, 0.0f, 0.0f); + SetWorldspawnColour(g_xywindow_globals.color_brushes); + g_xywindow_globals.color_viewname = Vector3(0.5f, 0.0f, 0.75f); + XY_UpdateAllWindows(); +} + +void ColorScheme_QER() +{ + TextureBrowser_setBackgroundColour(GlobalTextureBrowser(), Vector3(0.25f, 0.25f, 0.25f)); + + g_camwindow_globals.color_cameraback = Vector3(0.25f, 0.25f, 0.25f); + g_camwindow_globals.color_selbrushes3d = Vector3(1.0f, 0.0f, 0.0f); + CamWnd_Update(*g_pParentWnd->GetCamWnd()); + + g_xywindow_globals.color_gridback = Vector3(1.0f, 1.0f, 1.0f); + g_xywindow_globals.color_gridminor = Vector3(1.0f, 1.0f, 1.0f); + g_xywindow_globals.color_gridmajor = Vector3(0.5f, 0.5f, 0.5f); + g_xywindow_globals.color_gridblock = Vector3(0.0f, 0.0f, 1.0f); + g_xywindow_globals.color_gridtext = Vector3(0.0f, 0.0f, 0.0f); + g_xywindow_globals.color_selbrushes = Vector3(1.0f, 0.0f, 0.0f); + g_xywindow_globals.color_clipper = Vector3(0.0f, 0.0f, 1.0f); + g_xywindow_globals.color_brushes = Vector3(0.0f, 0.0f, 0.0f); + SetWorldspawnColour(g_xywindow_globals.color_brushes); + g_xywindow_globals.color_viewname = Vector3(0.5f, 0.0f, 0.75f); + XY_UpdateAllWindows(); +} + +void ColorScheme_Black() +{ + TextureBrowser_setBackgroundColour(GlobalTextureBrowser(), Vector3(0.25f, 0.25f, 0.25f)); + + g_camwindow_globals.color_cameraback = Vector3(0.25f, 0.25f, 0.25f); + g_camwindow_globals.color_selbrushes3d = Vector3(1.0f, 0.0f, 0.0f); + CamWnd_Update(*g_pParentWnd->GetCamWnd()); + + g_xywindow_globals.color_gridback = Vector3(0.0f, 0.0f, 0.0f); + g_xywindow_globals.color_gridminor = Vector3(0.2f, 0.2f, 0.2f); + g_xywindow_globals.color_gridmajor = Vector3(0.3f, 0.5f, 0.5f); + g_xywindow_globals.color_gridblock = Vector3(0.0f, 0.0f, 1.0f); + g_xywindow_globals.color_gridtext = Vector3(1.0f, 1.0f, 1.0f); + g_xywindow_globals.color_selbrushes = Vector3(1.0f, 0.0f, 0.0f); + g_xywindow_globals.color_clipper = Vector3(0.0f, 0.0f, 1.0f); + g_xywindow_globals.color_brushes = Vector3(1.0f, 1.0f, 1.0f); + SetWorldspawnColour(g_xywindow_globals.color_brushes); + g_xywindow_globals.color_viewname = Vector3(0.7f, 0.7f, 0.0f); + XY_UpdateAllWindows(); +} + +/* ydnar: to emulate maya/max/lightwave color schemes */ +void ColorScheme_Ydnar() +{ + TextureBrowser_setBackgroundColour(GlobalTextureBrowser(), Vector3(0.25f, 0.25f, 0.25f)); + + g_camwindow_globals.color_cameraback = Vector3(0.25f, 0.25f, 0.25f); + g_camwindow_globals.color_selbrushes3d = Vector3(1.0f, 0.0f, 0.0f); + CamWnd_Update(*g_pParentWnd->GetCamWnd()); + + g_xywindow_globals.color_gridback = Vector3(0.77f, 0.77f, 0.77f); + g_xywindow_globals.color_gridminor = Vector3(0.83f, 0.83f, 0.83f); + g_xywindow_globals.color_gridmajor = Vector3(0.89f, 0.89f, 0.89f); + g_xywindow_globals.color_gridblock = Vector3(1.0f, 1.0f, 1.0f); + g_xywindow_globals.color_gridtext = Vector3(0.0f, 0.0f, 0.0f); + g_xywindow_globals.color_selbrushes = Vector3(1.0f, 0.0f, 0.0f); + g_xywindow_globals.color_clipper = Vector3(0.0f, 0.0f, 1.0f); + g_xywindow_globals.color_brushes = Vector3(0.0f, 0.0f, 0.0f); + SetWorldspawnColour(g_xywindow_globals.color_brushes); + g_xywindow_globals.color_viewname = Vector3(0.5f, 0.0f, 0.75f); + XY_UpdateAllWindows(); +} + +typedef Callback1 GetColourCallback; +typedef Callback1 SetColourCallback; + +class ChooseColour +{ + GetColourCallback m_get; + SetColourCallback m_set; +public: + ChooseColour(const GetColourCallback& get, const SetColourCallback& set) + : m_get(get), m_set(set) + { + } + void operator()() + { + Vector3 colour; + m_get(colour); + color_dialog(GTK_WIDGET(MainFrame_getWindow()), colour); + m_set(colour); + } +}; + + + +void Colour_get(const Vector3& colour, Vector3& other) +{ + other = colour; +} +typedef ConstReferenceCaller1 ColourGetCaller; + +void Colour_set(Vector3& colour, const Vector3& other) +{ + colour = other; + SceneChangeNotify(); +} +typedef ReferenceCaller1 ColourSetCaller; + +void BrushColour_set(const Vector3& other) +{ + g_xywindow_globals.color_brushes = other; + SetWorldspawnColour(g_xywindow_globals.color_brushes); + SceneChangeNotify(); +} +typedef FreeCaller1 BrushColourSetCaller; + +void ClipperColour_set(const Vector3& other) +{ + g_xywindow_globals.color_clipper = other; + Brush_clipperColourChanged(); + SceneChangeNotify(); +} +typedef FreeCaller1 ClipperColourSetCaller; + +void TextureBrowserColour_get(Vector3& other) +{ + other = TextureBrowser_getBackgroundColour(GlobalTextureBrowser()); +} +typedef FreeCaller1 TextureBrowserColourGetCaller; + +void TextureBrowserColour_set(const Vector3& other) +{ + TextureBrowser_setBackgroundColour(GlobalTextureBrowser(), other); +} +typedef FreeCaller1 TextureBrowserColourSetCaller; + + +class ColoursMenu +{ +public: + ChooseColour m_textureback; + ChooseColour m_xyback; + ChooseColour m_gridmajor; + ChooseColour m_gridminor; + ChooseColour m_gridmajor_alt; + ChooseColour m_gridminor_alt; + ChooseColour m_gridtext; + ChooseColour m_gridblock; + ChooseColour m_cameraback; + ChooseColour m_brush; + ChooseColour m_selectedbrush; + ChooseColour m_selectedbrush3d; + ChooseColour m_clipper; + ChooseColour m_viewname; + + ColoursMenu() : + m_textureback(TextureBrowserColourGetCaller(), TextureBrowserColourSetCaller()), + m_xyback(ColourGetCaller(g_xywindow_globals.color_gridback), ColourSetCaller(g_xywindow_globals.color_gridback)), + m_gridmajor(ColourGetCaller(g_xywindow_globals.color_gridmajor), ColourSetCaller(g_xywindow_globals.color_gridmajor)), + m_gridminor(ColourGetCaller(g_xywindow_globals.color_gridminor), ColourSetCaller(g_xywindow_globals.color_gridminor)), + m_gridmajor_alt(ColourGetCaller(g_xywindow_globals.color_gridmajor_alt), ColourSetCaller(g_xywindow_globals.color_gridmajor_alt)), + m_gridminor_alt(ColourGetCaller(g_xywindow_globals.color_gridminor_alt), ColourSetCaller(g_xywindow_globals.color_gridminor_alt)), + m_gridtext(ColourGetCaller(g_xywindow_globals.color_gridtext), ColourSetCaller(g_xywindow_globals.color_gridtext)), + m_gridblock(ColourGetCaller(g_xywindow_globals.color_gridblock), ColourSetCaller(g_xywindow_globals.color_gridblock)), + m_cameraback(ColourGetCaller(g_camwindow_globals.color_cameraback), ColourSetCaller(g_camwindow_globals.color_cameraback)), + m_brush(ColourGetCaller(g_xywindow_globals.color_brushes), BrushColourSetCaller()), + m_selectedbrush(ColourGetCaller(g_xywindow_globals.color_selbrushes), ColourSetCaller(g_xywindow_globals.color_selbrushes)), + m_selectedbrush3d(ColourGetCaller(g_camwindow_globals.color_selbrushes3d), ColourSetCaller(g_camwindow_globals.color_selbrushes3d)), + m_clipper(ColourGetCaller(g_xywindow_globals.color_clipper), ClipperColourSetCaller()), + m_viewname(ColourGetCaller(g_xywindow_globals.color_viewname), ColourSetCaller(g_xywindow_globals.color_viewname)) + { + } +}; + +ColoursMenu g_ColoursMenu; + +GtkMenuItem* create_colours_menu() +{ + GtkMenuItem* colours_menu_item = new_sub_menu_item_with_mnemonic("Colors"); + GtkMenu* menu_in_menu = GTK_MENU(gtk_menu_item_get_submenu(colours_menu_item)); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + + GtkMenu* menu_3 = create_sub_menu_with_mnemonic(menu_in_menu, "Themes"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_3); + + create_menu_item_with_mnemonic(menu_3, "QE4 Original", "ColorSchemeOriginal"); + create_menu_item_with_mnemonic(menu_3, "Q3Radiant Original", "ColorSchemeQER"); + create_menu_item_with_mnemonic(menu_3, "Black and Green", "ColorSchemeBlackAndGreen"); + create_menu_item_with_mnemonic(menu_3, "Maya/Max/Lightwave Emulation", "ColorSchemeYdnar"); + + menu_separator(menu_in_menu); + + create_menu_item_with_mnemonic(menu_in_menu, "_Texture Background...", "ChooseTextureBackgroundColor"); + create_menu_item_with_mnemonic(menu_in_menu, "Grid Background...", "ChooseGridBackgroundColor"); + create_menu_item_with_mnemonic(menu_in_menu, "Grid Major...", "ChooseGridMajorColor"); + create_menu_item_with_mnemonic(menu_in_menu, "Grid Minor...", "ChooseGridMinorColor"); + create_menu_item_with_mnemonic(menu_in_menu, "Grid Major Small...", "ChooseSmallGridMajorColor"); + create_menu_item_with_mnemonic(menu_in_menu, "Grid Minor Small...", "ChooseSmallGridMinorColor"); + create_menu_item_with_mnemonic(menu_in_menu, "Grid Text...", "ChooseGridTextColor"); + create_menu_item_with_mnemonic(menu_in_menu, "Grid Block...", "ChooseGridBlockColor"); + create_menu_item_with_mnemonic(menu_in_menu, "Default Brush...", "ChooseBrushColor"); + create_menu_item_with_mnemonic(menu_in_menu, "Camera Background...", "ChooseCameraBackgroundColor"); + create_menu_item_with_mnemonic(menu_in_menu, "Selected Brush...", "ChooseSelectedBrushColor"); + create_menu_item_with_mnemonic(menu_in_menu, "Selected Brush (Camera)...", "ChooseCameraSelectedBrushColor"); + create_menu_item_with_mnemonic(menu_in_menu, "Clipper...", "ChooseClipperColor"); + create_menu_item_with_mnemonic(menu_in_menu, "Active View name...", "ChooseOrthoViewNameColor"); + + return colours_menu_item; +} + + +void Restart() +{ + PluginsMenu_clear(); + PluginToolbar_clear(); + + Radiant_Shutdown(); + Radiant_Initialise(); + + PluginsMenu_populate(); + + PluginToolbar_populate(); +} + + +void thunk_OnSleep() +{ + g_pParentWnd->OnSleep(); +} + +void OpenUpdateURL() +{ + // build the URL + StringOutputStream URL(256); + URL << "http://www.qeradiant.com/index.php?data=dlupdate&query_dlup=1"; +#ifdef WIN32 + URL << "&OS_dlup=1"; +#else + URL << "&OS_dlup=2"; +#endif + URL << "&Version_dlup=" RADIANT_VERSION; + g_GamesDialog.AddPacksURL(URL); + OpenURL(URL.c_str()); +} + +// open the Q3Rad manual +void OpenHelpURL() +{ + // at least on win32, AppPath + "Q3Rad_Manual/index.htm" + StringOutputStream help(256); + help << AppPath_get() << "Q3Rad_Manual/index.htm"; + OpenURL(help.c_str()); +} + +void OpenBugReportURL() +{ + OpenURL("http://www.qeradiant.com/?data=bugreport"); +} + + +GtkWidget* g_page_console; + +void Console_ToggleShow() +{ + GroupDialog_showPage(g_page_console); +} + +GtkWidget* g_page_entity; + +void EntityInspector_ToggleShow() +{ + GroupDialog_showPage(g_page_entity); +} + + + +void SetClipMode(bool enable); +void ModeChangeNotify(); + +typedef void(*ToolMode)(); +ToolMode g_currentToolMode = 0; +bool g_currentToolModeSupportsComponentEditing = false; +ToolMode g_defaultToolMode = 0; + + + +void SelectionSystem_DefaultMode() +{ + GlobalSelectionSystem().SetMode(SelectionSystem::ePrimitive); + GlobalSelectionSystem().SetComponentMode(SelectionSystem::eDefault); + ModeChangeNotify(); +} + + +bool EdgeMode() +{ + return GlobalSelectionSystem().Mode() == SelectionSystem::eComponent + && GlobalSelectionSystem().ComponentMode() == SelectionSystem::eEdge; +} + +bool VertexMode() +{ + return GlobalSelectionSystem().Mode() == SelectionSystem::eComponent + && GlobalSelectionSystem().ComponentMode() == SelectionSystem::eVertex; +} + +bool FaceMode() +{ + return GlobalSelectionSystem().Mode() == SelectionSystem::eComponent + && GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace; +} + +template +class BoolFunctionExport +{ +public: + static void apply(const BoolImportCallback& importCallback) + { + importCallback(BoolFunction()); + } +}; + +typedef FreeCaller1::apply> EdgeModeApplyCaller; +EdgeModeApplyCaller g_edgeMode_button_caller; +BoolExportCallback g_edgeMode_button_callback(g_edgeMode_button_caller); +ToggleItem g_edgeMode_button(g_edgeMode_button_callback); + +typedef FreeCaller1::apply> VertexModeApplyCaller; +VertexModeApplyCaller g_vertexMode_button_caller; +BoolExportCallback g_vertexMode_button_callback(g_vertexMode_button_caller); +ToggleItem g_vertexMode_button(g_vertexMode_button_callback); + +typedef FreeCaller1::apply> FaceModeApplyCaller; +FaceModeApplyCaller g_faceMode_button_caller; +BoolExportCallback g_faceMode_button_callback(g_faceMode_button_caller); +ToggleItem g_faceMode_button(g_faceMode_button_callback); + +void ComponentModeChanged() +{ + g_edgeMode_button.update(); + g_vertexMode_button.update(); + g_faceMode_button.update(); +} + +void ComponentMode_SelectionChanged(const Selectable& selectable) +{ + if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent + && GlobalSelectionSystem().countSelected() == 0) + { + SelectionSystem_DefaultMode(); + ComponentModeChanged(); + } +} + +void SelectEdgeMode() +{ +#if 0 + if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent) + { + GlobalSelectionSystem().Select(false); + } +#endif + + if(EdgeMode()) + { + SelectionSystem_DefaultMode(); + } + else if(GlobalSelectionSystem().countSelected() != 0) + { + if(!g_currentToolModeSupportsComponentEditing) + { + g_defaultToolMode(); + } + + GlobalSelectionSystem().SetMode(SelectionSystem::eComponent); + GlobalSelectionSystem().SetComponentMode(SelectionSystem::eEdge); + } + + ComponentModeChanged(); + + ModeChangeNotify(); +} + +void SelectVertexMode() +{ +#if 0 + if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent) + { + GlobalSelectionSystem().Select(false); + } +#endif + + if(VertexMode()) + { + SelectionSystem_DefaultMode(); + } + else if(GlobalSelectionSystem().countSelected() != 0) + { + if(!g_currentToolModeSupportsComponentEditing) + { + g_defaultToolMode(); + } + + GlobalSelectionSystem().SetMode(SelectionSystem::eComponent); + GlobalSelectionSystem().SetComponentMode(SelectionSystem::eVertex); + } + + ComponentModeChanged(); + + ModeChangeNotify(); +} + +void SelectFaceMode() +{ +#if 0 + if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent) + { + GlobalSelectionSystem().Select(false); + } +#endif + + if(FaceMode()) + { + SelectionSystem_DefaultMode(); + } + else if(GlobalSelectionSystem().countSelected() != 0) + { + if(!g_currentToolModeSupportsComponentEditing) + { + g_defaultToolMode(); + } + + GlobalSelectionSystem().SetMode(SelectionSystem::eComponent); + GlobalSelectionSystem().SetComponentMode(SelectionSystem::eFace); + } + + ComponentModeChanged(); + + ModeChangeNotify(); +} + + +class CloneSelected : public scene::Graph::Walker +{ +public: + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.size() == 1) + return true; + + if(!path.top().get().isRoot()) + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 + && selectable->isSelected()) + { + return false; + } + } + + return true; + } + void post(const scene::Path& path, scene::Instance& instance) const + { + if(path.size() == 1) + return; + + if(!path.top().get().isRoot()) + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 + && selectable->isSelected()) + { + NodeSmartReference clone(Node_Clone(path.top())); + Map_gatherNamespaced(clone); + Node_getTraversable(path.parent().get())->insert(clone); + } + } + } +}; + +void Scene_Clone_Selected(scene::Graph& graph) +{ + graph.traverse(CloneSelected()); + + Map_mergeClonedNames(); +} + +enum ENudgeDirection +{ + eNudgeUp = 1, + eNudgeDown = 3, + eNudgeLeft = 0, + eNudgeRight = 2, +}; + +struct AxisBase +{ + Vector3 x; + Vector3 y; + Vector3 z; + AxisBase(const Vector3& x_, const Vector3& y_, const Vector3& z_) + : x(x_), y(y_), z(z_) + { + } +}; + +AxisBase AxisBase_forViewType(VIEWTYPE viewtype) +{ + switch(viewtype) + { + case XY: + return AxisBase(g_vector3_axis_x, g_vector3_axis_y, g_vector3_axis_z); + case XZ: + return AxisBase(g_vector3_axis_x, g_vector3_axis_z, g_vector3_axis_y); + case YZ: + return AxisBase(g_vector3_axis_y, g_vector3_axis_z, g_vector3_axis_x); + } + + ERROR_MESSAGE("invalid viewtype"); + return AxisBase(Vector3(0, 0, 0), Vector3(0, 0, 0), Vector3(0, 0, 0)); +} + +Vector3 AxisBase_axisForDirection(const AxisBase& axes, ENudgeDirection direction) +{ + switch (direction) + { + case eNudgeLeft: + return vector3_negated(axes.x); + case eNudgeUp: + return axes.y; + case eNudgeRight: + return axes.x; + case eNudgeDown: + return vector3_negated(axes.y); + } + + ERROR_MESSAGE("invalid direction"); + return Vector3(0, 0, 0); +} + +void NudgeSelection(ENudgeDirection direction, float fAmount, VIEWTYPE viewtype) +{ + AxisBase axes(AxisBase_forViewType(viewtype)); + Vector3 view_direction(vector3_negated(axes.z)); + Vector3 nudge(vector3_scaled(AxisBase_axisForDirection(axes, direction), fAmount)); + GlobalSelectionSystem().NudgeManipulator(nudge, view_direction); +} + +void Selection_Clone() +{ + if(GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) + { + UndoableCommand undo("cloneSelected"); + + Scene_Clone_Selected(GlobalSceneGraph()); + + //NudgeSelection(eNudgeRight, GetGridSize(), GlobalXYWnd_getCurrentViewType()); + //NudgeSelection(eNudgeDown, GetGridSize(), GlobalXYWnd_getCurrentViewType()); + } +} + +// called when the escape key is used (either on the main window or on an inspector) +void Selection_Deselect() +{ + if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent) + { + if(GlobalSelectionSystem().countSelectedComponents() != 0) + { + GlobalSelectionSystem().setSelectedAllComponents(false); + } + else + { + SelectionSystem_DefaultMode(); + ComponentModeChanged(); + } + } + else + { + if(GlobalSelectionSystem().countSelectedComponents() != 0) + { + GlobalSelectionSystem().setSelectedAllComponents(false); + } + else + { + GlobalSelectionSystem().setSelectedAll(false); + } + } +} + + +void Selection_NudgeUp() +{ + UndoableCommand undo("nudgeSelectedUp"); + NudgeSelection(eNudgeUp, GetGridSize(), GlobalXYWnd_getCurrentViewType()); +} + +void Selection_NudgeDown() +{ + UndoableCommand undo("nudgeSelectedDown"); + NudgeSelection(eNudgeDown, GetGridSize(), GlobalXYWnd_getCurrentViewType()); +} + +void Selection_NudgeLeft() +{ + UndoableCommand undo("nudgeSelectedLeft"); + NudgeSelection(eNudgeLeft, GetGridSize(), GlobalXYWnd_getCurrentViewType()); +} + +void Selection_NudgeRight() +{ + UndoableCommand undo("nudgeSelectedRight"); + NudgeSelection(eNudgeRight, GetGridSize(), GlobalXYWnd_getCurrentViewType()); +} + + +void TranslateToolExport(const BoolImportCallback& importCallback) +{ + importCallback(GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eTranslate); +} + +void RotateToolExport(const BoolImportCallback& importCallback) +{ + importCallback(GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eRotate); +} + +void ScaleToolExport(const BoolImportCallback& importCallback) +{ + importCallback(GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eScale); +} + +void DragToolExport(const BoolImportCallback& importCallback) +{ + importCallback(GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eDrag); +} + +void ClipperToolExport(const BoolImportCallback& importCallback) +{ + importCallback(GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eClip); +} + +FreeCaller1 g_translatemode_button_caller; +BoolExportCallback g_translatemode_button_callback(g_translatemode_button_caller); +ToggleItem g_translatemode_button(g_translatemode_button_callback); + +FreeCaller1 g_rotatemode_button_caller; +BoolExportCallback g_rotatemode_button_callback(g_rotatemode_button_caller); +ToggleItem g_rotatemode_button(g_rotatemode_button_callback); + +FreeCaller1 g_scalemode_button_caller; +BoolExportCallback g_scalemode_button_callback(g_scalemode_button_caller); +ToggleItem g_scalemode_button(g_scalemode_button_callback); + +FreeCaller1 g_dragmode_button_caller; +BoolExportCallback g_dragmode_button_callback(g_dragmode_button_caller); +ToggleItem g_dragmode_button(g_dragmode_button_callback); + +FreeCaller1 g_clipper_button_caller; +BoolExportCallback g_clipper_button_callback(g_clipper_button_caller); +ToggleItem g_clipper_button(g_clipper_button_callback); + +void ToolChanged() +{ + g_translatemode_button.update(); + g_rotatemode_button.update(); + g_scalemode_button.update(); + g_dragmode_button.update(); + g_clipper_button.update(); +} + +const char* const c_ResizeMode_status = "QE4 Drag Tool: move and resize objects"; + +void DragMode() +{ + if(g_currentToolMode == DragMode && g_defaultToolMode != DragMode) + { + g_defaultToolMode(); + } + else + { + g_currentToolMode = DragMode; + g_currentToolModeSupportsComponentEditing = true; + + OnClipMode(false); + + Sys_Status(c_ResizeMode_status); + GlobalSelectionSystem().SetManipulatorMode(SelectionSystem::eDrag); + ToolChanged(); + ModeChangeNotify(); + } +} + + +const char* const c_TranslateMode_status = "Translate Tool: translate objects and components"; + +void TranslateMode() +{ + if(g_currentToolMode == TranslateMode && g_defaultToolMode != TranslateMode) + { + g_defaultToolMode(); + } + else + { + g_currentToolMode = TranslateMode; + g_currentToolModeSupportsComponentEditing = true; + + OnClipMode(false); + + Sys_Status(c_TranslateMode_status); + GlobalSelectionSystem().SetManipulatorMode(SelectionSystem::eTranslate); + ToolChanged(); + ModeChangeNotify(); + } +} + +const char* const c_RotateMode_status = "Rotate Tool: rotate objects and components"; + +void RotateMode() +{ + if(g_currentToolMode == RotateMode && g_defaultToolMode != RotateMode) + { + g_defaultToolMode(); + } + else + { + g_currentToolMode = RotateMode; + g_currentToolModeSupportsComponentEditing = true; + + OnClipMode(false); + + Sys_Status(c_RotateMode_status); + GlobalSelectionSystem().SetManipulatorMode(SelectionSystem::eRotate); + ToolChanged(); + ModeChangeNotify(); + } +} + +const char* const c_ScaleMode_status = "Scale Tool: scale objects and components"; + +void ScaleMode() +{ + if(g_currentToolMode == ScaleMode && g_defaultToolMode != ScaleMode) + { + g_defaultToolMode(); + } + else + { + g_currentToolMode = ScaleMode; + g_currentToolModeSupportsComponentEditing = true; + + OnClipMode(false); + + Sys_Status(c_ScaleMode_status); + GlobalSelectionSystem().SetManipulatorMode(SelectionSystem::eScale); + ToolChanged(); + ModeChangeNotify(); + } +} + + +const char* const c_ClipperMode_status = "Clipper Tool: apply clip planes to objects"; + + +void ClipperMode() +{ + if(g_currentToolMode == ClipperMode && g_defaultToolMode != ClipperMode) + { + g_defaultToolMode(); + } + else + { + g_currentToolMode = ClipperMode; + g_currentToolModeSupportsComponentEditing = false; + + SelectionSystem_DefaultMode(); + + OnClipMode(true); + + Sys_Status(c_ClipperMode_status); + GlobalSelectionSystem().SetManipulatorMode(SelectionSystem::eClip); + ToolChanged(); + ModeChangeNotify(); + } +} + + +void Texdef_Rotate(float angle) +{ + StringOutputStream command; + command << "brushRotateTexture -angle " << angle; + UndoableCommand undo(command.c_str()); + Select_RotateTexture(angle); +} + +void Texdef_RotateClockwise() +{ + Texdef_Rotate(static_cast(fabs(g_si_globals.rotate))); +} + +void Texdef_RotateAntiClockwise() +{ + Texdef_Rotate(static_cast(-fabs(g_si_globals.rotate))); +} + +void Texdef_Scale(float x, float y) +{ + StringOutputStream command; + command << "brushScaleTexture -x " << x << " -y " << y; + UndoableCommand undo(command.c_str()); + Select_ScaleTexture(x, y); +} + +void Texdef_ScaleUp() +{ + Texdef_Scale(0, g_si_globals.scale[1]); +} + +void Texdef_ScaleDown() +{ + Texdef_Scale(0, -g_si_globals.scale[1]); +} + +void Texdef_ScaleLeft() +{ + Texdef_Scale(-g_si_globals.scale[0],0); +} + +void Texdef_ScaleRight() +{ + Texdef_Scale(g_si_globals.scale[0],0); +} + +void Texdef_Shift(float x, float y) +{ + StringOutputStream command; + command << "brushShiftTexture -x " << x << " -y " << y; + UndoableCommand undo(command.c_str()); + Select_ShiftTexture(x, y); +} + +void Texdef_ShiftLeft() +{ + Texdef_Shift(-g_si_globals.shift[0], 0); +} + +void Texdef_ShiftRight() +{ + Texdef_Shift(g_si_globals.shift[0], 0); +} + +void Texdef_ShiftUp() +{ + Texdef_Shift(0, g_si_globals.shift[1]); +} + +void Texdef_ShiftDown() +{ + Texdef_Shift(0, -g_si_globals.shift[1]); +} + + + +class SnappableSnapToGridSelected : public scene::Graph::Walker +{ + float m_snap; +public: + SnappableSnapToGridSelected(float snap) + : m_snap(snap) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + Snappable* snappable = Node_getSnappable(path.top()); + if(snappable != 0 + && Instance_getSelectable(instance)->isSelected()) + { + snappable->snapto(m_snap); + } + } + return true; + } +}; + +void Scene_SnapToGrid_Selected(scene::Graph& graph, float snap) +{ + graph.traverse(SnappableSnapToGridSelected(snap)); +} + +class ComponentSnappableSnapToGridSelected : public scene::Graph::Walker +{ + float m_snap; +public: + ComponentSnappableSnapToGridSelected(float snap) + : m_snap(snap) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + ComponentSnappable* componentSnappable = Instance_getComponentSnappable(instance); + if(componentSnappable != 0 + && Instance_getSelectable(instance)->isSelected()) + { + componentSnappable->snapComponents(m_snap); + } + } + return true; + } +}; + +void Scene_SnapToGrid_Component_Selected(scene::Graph& graph, float snap) +{ + graph.traverse(ComponentSnappableSnapToGridSelected(snap)); +} + +void Selection_SnapToGrid() +{ + StringOutputStream command; + command << "snapSelected -grid " << GetGridSize(); + UndoableCommand undo(command.c_str()); + + if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent) + { + Scene_SnapToGrid_Component_Selected(GlobalSceneGraph(), GetGridSize()); + } + else + { + Scene_SnapToGrid_Selected(GlobalSceneGraph(), GetGridSize()); + } +} + + +static gint qe_every_second(gpointer data) +{ + GdkModifierType mask; + + gdk_window_get_pointer (0, 0, 0, &mask); + + if ((mask & (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK)) == 0) + { + QE_CheckAutoSave(); + } + + return TRUE; +} + +guint s_qe_every_second_id = 0; + +void EverySecondTimer_enable() +{ + if(s_qe_every_second_id == 0) + { + s_qe_every_second_id = gtk_timeout_add(1000, qe_every_second, 0); + } +} + +void EverySecondTimer_disable() +{ + if(s_qe_every_second_id != 0) + { + gtk_timeout_remove(s_qe_every_second_id); + s_qe_every_second_id = 0; + } +} + +gint window_realize_remove_decoration(GtkWidget* widget, gpointer data) +{ + gdk_window_set_decorations(widget->window, (GdkWMDecoration)(GDK_DECOR_ALL|GDK_DECOR_MENU|GDK_DECOR_MINIMIZE|GDK_DECOR_MAXIMIZE)); + return FALSE; +} + +class WaitDialog +{ +public: + GtkWindow* m_window; + GtkLabel* m_label; +}; + +WaitDialog create_wait_dialog(const char* title, const char* text) +{ + WaitDialog dialog; + + dialog.m_window = create_floating_window(title, MainFrame_getWindow()); + gtk_window_set_resizable(dialog.m_window, FALSE); + gtk_container_set_border_width(GTK_CONTAINER(dialog.m_window), 0); + gtk_window_set_position(dialog.m_window, GTK_WIN_POS_CENTER_ON_PARENT); + + g_signal_connect(G_OBJECT(dialog.m_window), "realize", G_CALLBACK(window_realize_remove_decoration), 0); + + { + dialog.m_label = GTK_LABEL(gtk_label_new(text)); + gtk_misc_set_alignment(GTK_MISC(dialog.m_label), 0.0, 0.5); + gtk_label_set_justify(dialog.m_label, GTK_JUSTIFY_LEFT); + gtk_widget_show(GTK_WIDGET(dialog.m_label)); + gtk_widget_set_size_request(GTK_WIDGET(dialog.m_label), 200, -1); + + gtk_container_add(GTK_CONTAINER(dialog.m_window), GTK_WIDGET(dialog.m_label)); + } + return dialog; +} + +namespace +{ + clock_t g_lastRedrawTime = 0; + const clock_t c_redrawInterval = clock_t(CLOCKS_PER_SEC / 10); + + bool redrawRequired() + { + clock_t currentTime = std::clock(); + if(currentTime - g_lastRedrawTime >= c_redrawInterval) + { + g_lastRedrawTime = currentTime; + return true; + } + return false; + } +} + +bool MainFrame_isActiveApp() +{ + //globalOutputStream() << "listing\n"; + GList* list = gtk_window_list_toplevels(); + for(GList* i = list; i != 0; i = g_list_next(i)) + { + //globalOutputStream() << "toplevel.. "; + if(gtk_window_is_active(GTK_WINDOW(i->data))) + { + //globalOutputStream() << "is active\n"; + return true; + } + //globalOutputStream() << "not active\n"; + } + return false; +} + +typedef std::list StringStack; +StringStack g_wait_stack; +WaitDialog g_wait; + +bool ScreenUpdates_Enabled() +{ + return g_wait_stack.empty(); +} + +void ScreenUpdates_process() +{ + if(redrawRequired() && GTK_WIDGET_VISIBLE(g_wait.m_window)) + { + process_gui(); + } +} + + +void ScreenUpdates_Disable(const char* message, const char* title) +{ + if(g_wait_stack.empty()) + { + EverySecondTimer_disable(); + + process_gui(); + + bool isActiveApp = MainFrame_isActiveApp(); + + g_wait = create_wait_dialog(title, message); + gtk_grab_add(GTK_WIDGET(g_wait.m_window)); + + if(isActiveApp) + { + gtk_widget_show(GTK_WIDGET(g_wait.m_window)); + ScreenUpdates_process(); + } + } + else if(GTK_WIDGET_VISIBLE(g_wait.m_window)) + { + gtk_label_set_text(g_wait.m_label, message); + ScreenUpdates_process(); + } + g_wait_stack.push_back(message); +} + +void ScreenUpdates_Enable() +{ + ASSERT_MESSAGE(!ScreenUpdates_Enabled(), "screen updates already enabled"); + g_wait_stack.pop_back(); + if(g_wait_stack.empty()) + { + EverySecondTimer_enable(); + //gtk_widget_set_sensitive(GTK_WIDGET(MainFrame_getWindow()), TRUE); + + gtk_grab_remove(GTK_WIDGET(g_wait.m_window)); + destroy_floating_window(g_wait.m_window); + g_wait.m_window = 0; + + //gtk_window_present(MainFrame_getWindow()); + } + else if(GTK_WIDGET_VISIBLE(g_wait.m_window)) + { + gtk_label_set_text(g_wait.m_label, g_wait_stack.back().c_str()); + ScreenUpdates_process(); + } +} + + + +void GlobalCamera_UpdateWindow() +{ + if(g_pParentWnd != 0) + { + CamWnd_Update(*g_pParentWnd->GetCamWnd()); + } +} + +void XY_UpdateWindow(MainFrame& mainframe) +{ + if(mainframe.GetXYWnd() != 0) + { + XYWnd_Update(*mainframe.GetXYWnd()); + } +} + +void XZ_UpdateWindow(MainFrame& mainframe) +{ + if(mainframe.GetXZWnd() != 0) + { + XYWnd_Update(*mainframe.GetXZWnd()); + } +} + +void YZ_UpdateWindow(MainFrame& mainframe) +{ + if(mainframe.GetYZWnd() != 0) + { + XYWnd_Update(*mainframe.GetYZWnd()); + } +} + +void XY_UpdateAllWindows(MainFrame& mainframe) +{ + XY_UpdateWindow(mainframe); + XZ_UpdateWindow(mainframe); + YZ_UpdateWindow(mainframe); +} + +void XY_UpdateAllWindows() +{ + if(g_pParentWnd != 0) + { + XY_UpdateAllWindows(*g_pParentWnd); + } +} + +void UpdateAllWindows() +{ + GlobalCamera_UpdateWindow(); + XY_UpdateAllWindows(); +} + + +void ModeChangeNotify() +{ + SceneChangeNotify(); +} + +void ClipperChangeNotify() +{ + GlobalCamera_UpdateWindow(); + XY_UpdateAllWindows(); +} + + +LatchedInt g_Layout_viewStyle(0, "Window Layout"); +LatchedBool g_Layout_enableDetachableMenus(true, "Detachable Menus"); +LatchedBool g_Layout_enablePatchToolbar(true, "Patch Toolbar"); +LatchedBool g_Layout_enablePluginToolbar(true, "Plugin Toolbar"); + + + +GtkMenuItem* create_file_menu() +{ + // File menu + GtkMenuItem* file_menu_item = new_sub_menu_item_with_mnemonic("_File"); + GtkMenu* menu = GTK_MENU(gtk_menu_item_get_submenu(file_menu_item)); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu); + + create_menu_item_with_mnemonic(menu, "_New Map", "NewMap"); + menu_separator(menu); + +#if 0 + //++timo temporary experimental stuff for sleep mode.. + create_menu_item_with_mnemonic(menu, "_Sleep", "Sleep"); + menu_separator(menu); + // end experimental +#endif + + create_menu_item_with_mnemonic(menu, "_Open...", "OpenMap"); + + create_menu_item_with_mnemonic(menu, "_Import...", "ImportMap"); + create_menu_item_with_mnemonic(menu, "_Save", "SaveMap"); + create_menu_item_with_mnemonic(menu, "Save _as...", "SaveMapAs"); + create_menu_item_with_mnemonic(menu, "Save s_elected...", "SaveSelected"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "Save re_gion...", "SaveRegion"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "_Refresh models", "RefreshReferences"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "Pro_ject settings...", "ProjectSettings"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "_Pointfile...", "TogglePointfile"); + menu_separator(menu); + MRU_constructMenu(menu); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "Check for GtkRadiant update (web)", "CheckForUpdate"); + create_menu_item_with_mnemonic(menu, "E_xit", "Exit"); + + return file_menu_item; +} + +GtkMenuItem* create_edit_menu() +{ + // Edit menu + GtkMenuItem* edit_menu_item = new_sub_menu_item_with_mnemonic("_Edit"); + GtkMenu* menu = GTK_MENU(gtk_menu_item_get_submenu(edit_menu_item)); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu); + create_menu_item_with_mnemonic(menu, "_Undo", "Undo"); + create_menu_item_with_mnemonic(menu, "_Redo", "Redo"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "_Copy", "Copy"); + create_menu_item_with_mnemonic(menu, "_Paste", "Paste"); + create_menu_item_with_mnemonic(menu, "P_aste To Camera", "PasteToCamera"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "_Duplicate", "CloneSelection"); + create_menu_item_with_mnemonic(menu, "D_elete", "DeleteSelection"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "Pa_rent", "ParentSelection"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "C_lear Selection", "UnSelectSelection"); + create_menu_item_with_mnemonic(menu, "_Invert Selection", "InvertSelection"); + create_menu_item_with_mnemonic(menu, "Select i_nside", "SelectInside"); + create_menu_item_with_mnemonic(menu, "Select _touching", "SelectTouching"); + + GtkMenu* convert_menu = create_sub_menu_with_mnemonic(menu, "E_xpand Selection"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (convert_menu); + create_menu_item_with_mnemonic(convert_menu, "To Whole _Entities", "ExpandSelectionToEntities"); + + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "Pre_ferences...", "Preferences"); + + return edit_menu_item; +} + +void fill_view_xy_top_menu(GtkMenu* menu) +{ + create_check_menu_item_with_mnemonic(menu, "XY (Top) View", "ToggleView"); +} + + +void fill_view_yz_side_menu(GtkMenu* menu) +{ + create_check_menu_item_with_mnemonic(menu, "YZ (Side) View", "ToggleSideView"); +} + + +void fill_view_xz_front_menu(GtkMenu* menu) +{ + create_check_menu_item_with_mnemonic(menu, "XZ (Front) View", "ToggleFrontView"); +} + + +GtkWidget* g_toggle_z_item = 0; +GtkWidget* g_toggle_console_item = 0; +GtkWidget* g_toggle_entity_item = 0; +GtkWidget* g_toggle_entitylist_item = 0; + +GtkMenuItem* create_view_menu(MainFrame::EViewStyle style) +{ + // View menu + GtkMenuItem* view_menu_item = new_sub_menu_item_with_mnemonic("Vie_w"); + GtkMenu* menu = GTK_MENU(gtk_menu_item_get_submenu(view_menu_item)); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu); + + if(style == MainFrame::eFloating) + { + fill_view_camera_menu(menu); + fill_view_xy_top_menu(menu); + fill_view_yz_side_menu(menu); + fill_view_xz_front_menu(menu); + } + if(style == MainFrame::eFloating || style == MainFrame::eSplit) + { + create_menu_item_with_mnemonic(menu, "Console View", "ToggleConsole"); + create_menu_item_with_mnemonic(menu, "Texture Browser", "ToggleTextures"); + create_menu_item_with_mnemonic(menu, "Entity Inspector", "ToggleEntityInspector"); + } + else + { + create_menu_item_with_mnemonic(menu, "Entity Inspector", "ViewEntityInfo"); + } + create_menu_item_with_mnemonic(menu, "_Surface Inspector", "SurfaceInspector"); + create_menu_item_with_mnemonic(menu, "Entity List", "EntityList"); + + menu_separator(menu); + { + GtkMenu* camera_menu = create_sub_menu_with_mnemonic (menu, "Camera"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (camera_menu); + create_menu_item_with_mnemonic(camera_menu, "_Center", "CenterView"); + create_menu_item_with_mnemonic(camera_menu, "_Up Floor", "UpFloor"); + create_menu_item_with_mnemonic(camera_menu, "_Down Floor", "DownFloor"); + menu_separator(camera_menu); + create_menu_item_with_mnemonic(camera_menu, "Far Clip Plane In", "CubicClipZoomIn"); + create_menu_item_with_mnemonic(camera_menu, "Far Clip Plane Out", "CubicClipZoomOut"); + menu_separator(camera_menu); + create_menu_item_with_mnemonic(camera_menu, "Next leak spot", "NextLeakSpot"); + create_menu_item_with_mnemonic(camera_menu, "Previous leak spot", "PrevLeakSpot"); + menu_separator(camera_menu); + create_menu_item_with_mnemonic(camera_menu, "Look Through Selected", "LookThroughSelected"); + create_menu_item_with_mnemonic(camera_menu, "Look Through Camera", "LookThroughCamera"); + } + menu_separator(menu); + { + GtkMenu* orthographic_menu = create_sub_menu_with_mnemonic(menu, "Orthographic"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (orthographic_menu); + if(style == MainFrame::eRegular || style == MainFrame::eRegularLeft || style == MainFrame::eFloating) + { + create_menu_item_with_mnemonic(orthographic_menu, "_Next (XY, YZ, XY)", "NextView"); + create_menu_item_with_mnemonic(orthographic_menu, "XY (Top)", "ViewTop"); + create_menu_item_with_mnemonic(orthographic_menu, "YZ", "ViewSide"); + create_menu_item_with_mnemonic(orthographic_menu, "XZ", "ViewFront"); + menu_separator(orthographic_menu); + } + + create_menu_item_with_mnemonic(orthographic_menu, "_XY 100%", "Zoom100"); + create_menu_item_with_mnemonic(orthographic_menu, "XY Zoom _In", "ZoomIn"); + create_menu_item_with_mnemonic(orthographic_menu, "XY Zoom _Out", "ZoomOut"); + } + + menu_separator(menu); + + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "Show"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_check_menu_item_with_mnemonic(menu_in_menu, "Show _Angles", "ShowAngles"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Show _Names", "ShowNames"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Show Blocks", "ShowBlocks"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Show C_oordinates", "ShowCoordinates"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Show Window Outline", "ShowWindowOutline"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Show Axes", "ShowAxes"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Show Workzone", "ShowWorkzone"); + create_check_menu_item_with_mnemonic(menu_in_menu, "Show Stats", "ShowStats"); + } + + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "Filter"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + Filters_constructMenu(menu_in_menu); + } + menu_separator(menu); + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "Hide/Show"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "Hide Selected", "HideSelected"); + create_menu_item_with_mnemonic(menu_in_menu, "Show Hidden", "ShowHidden"); + } + menu_separator(menu); + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "Region"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "_Off", "RegionOff"); + create_menu_item_with_mnemonic(menu_in_menu, "_Set XY", "RegionSetXY"); + create_menu_item_with_mnemonic(menu_in_menu, "Set _Brush", "RegionSetBrush"); + create_menu_item_with_mnemonic(menu_in_menu, "Set Se_lected Brushes", "RegionSetSelection"); + } + + if(style == MainFrame::eSplit || style == MainFrame::eFloating) + { + command_connect_accelerator("CenterXYViews"); + } + else + { + command_connect_accelerator("CenterXYView"); + } + + return view_menu_item; +} + +GtkMenuItem* create_selection_menu() +{ + // Selection menu + GtkMenuItem* selection_menu_item = new_sub_menu_item_with_mnemonic("M_odify"); + GtkMenu* menu = GTK_MENU(gtk_menu_item_get_submenu(selection_menu_item)); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu); + + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "Components"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_check_menu_item_with_mnemonic(menu_in_menu, "_Edges", "DragEdges"); + create_check_menu_item_with_mnemonic(menu_in_menu, "_Vertices", "DragVertices"); + create_check_menu_item_with_mnemonic(menu_in_menu, "_Faces", "DragFaces"); + } + + menu_separator(menu); + + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic(menu, "Nudge"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "Nudge Left", "SelectNudgeLeft"); + create_menu_item_with_mnemonic(menu_in_menu, "Nudge Right", "SelectNudgeRight"); + create_menu_item_with_mnemonic(menu_in_menu, "Nudge Up", "SelectNudgeUp"); + create_menu_item_with_mnemonic(menu_in_menu, "Nudge Down", "SelectNudgeDown"); + } + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "Rotate"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "Rotate X", "RotateSelectionX"); + create_menu_item_with_mnemonic(menu_in_menu, "Rotate Y", "RotateSelectionY"); + create_menu_item_with_mnemonic(menu_in_menu, "Rotate Z", "RotateSelectionZ"); + } + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "Flip"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "Flip _X", "MirrorSelectionX"); + create_menu_item_with_mnemonic(menu_in_menu, "Flip _Y", "MirrorSelectionY"); + create_menu_item_with_mnemonic(menu_in_menu, "Flip _Z", "MirrorSelectionZ"); + } + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "Arbitrary rotation...", "ArbitraryRotation"); + create_menu_item_with_mnemonic(menu, "Arbitrary scale...", "ArbitraryScale"); + + return selection_menu_item; +} + +GtkMenuItem* create_bsp_menu() +{ + // BSP menu + GtkMenuItem* bsp_menu_item = new_sub_menu_item_with_mnemonic("_Build"); + GtkMenu* menu = GTK_MENU(gtk_menu_item_get_submenu(bsp_menu_item)); + + if (g_Layout_enableDetachableMenus.m_value) + { + menu_tearoff(menu); + } + + create_menu_item_with_mnemonic(menu, "Customize...", "BuildMenuCustomize"); + + menu_separator(menu); + + Build_constructMenu(menu); + + g_bsp_menu = menu; + + return bsp_menu_item; +} + +GtkMenuItem* create_grid_menu() +{ + // Grid menu + GtkMenuItem* grid_menu_item = new_sub_menu_item_with_mnemonic("_Grid"); + GtkMenu* menu = GTK_MENU(gtk_menu_item_get_submenu(grid_menu_item)); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu); + + Grid_constructMenu(menu); + + return grid_menu_item; +} + +GtkMenuItem* create_misc_menu() +{ + // Misc menu + GtkMenuItem* misc_menu_item = new_sub_menu_item_with_mnemonic("M_isc"); + GtkMenu* menu = GTK_MENU(gtk_menu_item_get_submenu(misc_menu_item)); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu); + +#if 0 + create_menu_item_with_mnemonic(menu, "_Benchmark", FreeCaller()); +#endif + gtk_container_add(GTK_CONTAINER(menu), GTK_WIDGET(create_colours_menu())); + + create_menu_item_with_mnemonic(menu, "Find brush...", "FindBrush"); + create_menu_item_with_mnemonic(menu, "Map Info...", "MapInfo"); + // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=394 +// create_menu_item_with_mnemonic(menu, "_Print XY View", FreeCaller()); + create_menu_item_with_mnemonic(menu, "_Background select", FreeCaller()); + return misc_menu_item; +} + +GtkMenuItem* create_entity_menu() +{ + // Brush menu + GtkMenuItem* entity_menu_item = new_sub_menu_item_with_mnemonic("E_ntity"); + GtkMenu* menu = GTK_MENU(gtk_menu_item_get_submenu(entity_menu_item)); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu); + + Entity_constructMenu(menu); + + return entity_menu_item; +} + +GtkMenuItem* create_brush_menu() +{ + // Brush menu + GtkMenuItem* brush_menu_item = new_sub_menu_item_with_mnemonic("B_rush"); + GtkMenu* menu = GTK_MENU(gtk_menu_item_get_submenu(brush_menu_item)); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu); + + Brush_constructMenu(menu); + + return brush_menu_item; +} + +GtkMenuItem* create_patch_menu() +{ + // Curve menu + GtkMenuItem* patch_menu_item = new_sub_menu_item_with_mnemonic("_Curve"); + GtkMenu* menu = GTK_MENU(gtk_menu_item_get_submenu(patch_menu_item)); + if (g_Layout_enableDetachableMenus.m_value) + { + menu_tearoff(menu); + } + + Patch_constructMenu(menu); + + return patch_menu_item; +} + +GtkMenuItem* create_help_menu() +{ + // Help menu + GtkMenuItem* help_menu_item = new_sub_menu_item_with_mnemonic("_Help"); + GtkMenu* menu = GTK_MENU(gtk_menu_item_get_submenu(help_menu_item)); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu); + + create_menu_item_with_mnemonic(menu, "Manual", "OpenManual"); + + // this creates all the per-game drop downs for the game pack helps + // it will take care of hooking the Sys_OpenURL calls etc. + create_game_help_menu(menu); + + create_menu_item_with_mnemonic(menu, "Bug report", FreeCaller()); + create_menu_item_with_mnemonic(menu, "Shortcuts list", FreeCaller()); + create_menu_item_with_mnemonic(menu, "_About", FreeCaller()); + + return help_menu_item; +} + +GtkMenuBar* create_main_menu(MainFrame::EViewStyle style) +{ + GtkMenuBar* menu_bar = GTK_MENU_BAR(gtk_menu_bar_new()); + gtk_widget_show(GTK_WIDGET(menu_bar)); + + gtk_container_add(GTK_CONTAINER(menu_bar), GTK_WIDGET(create_file_menu())); + gtk_container_add(GTK_CONTAINER(menu_bar), GTK_WIDGET(create_edit_menu())); + gtk_container_add(GTK_CONTAINER(menu_bar), GTK_WIDGET(create_view_menu(style))); + gtk_container_add(GTK_CONTAINER(menu_bar), GTK_WIDGET(create_selection_menu())); + gtk_container_add(GTK_CONTAINER(menu_bar), GTK_WIDGET(create_bsp_menu())); + gtk_container_add(GTK_CONTAINER(menu_bar), GTK_WIDGET(create_grid_menu())); + gtk_container_add(GTK_CONTAINER(menu_bar), GTK_WIDGET(create_misc_menu())); + gtk_container_add(GTK_CONTAINER(menu_bar), GTK_WIDGET(create_entity_menu())); + gtk_container_add(GTK_CONTAINER(menu_bar), GTK_WIDGET(create_brush_menu())); + gtk_container_add(GTK_CONTAINER(menu_bar), GTK_WIDGET(create_patch_menu())); + gtk_container_add(GTK_CONTAINER(menu_bar), GTK_WIDGET(create_plugins_menu())); + gtk_container_add(GTK_CONTAINER(menu_bar), GTK_WIDGET(create_help_menu())); + + return menu_bar; +} + + +void PatchInspector_registerShortcuts() +{ + command_connect_accelerator("PatchInspector"); +} + +void Patch_registerShortcuts() +{ + command_connect_accelerator("InvertCurveTextureX"); + command_connect_accelerator("InvertCurveTextureY"); + command_connect_accelerator("IncPatchColumn"); + command_connect_accelerator("IncPatchRow"); + command_connect_accelerator("DecPatchColumn"); + command_connect_accelerator("DecPatchRow"); + command_connect_accelerator("NaturalizePatch"); + //command_connect_accelerator("CapCurrentCurve"); +} + +void Manipulators_registerShortcuts() +{ + toggle_add_accelerator("MouseRotate"); + toggle_add_accelerator("MouseTranslate"); + toggle_add_accelerator("MouseScale"); + toggle_add_accelerator("MouseDrag"); + toggle_add_accelerator("ToggleClipper"); +} + +void TexdefNudge_registerShortcuts() +{ + command_connect_accelerator("TexRotateClock"); + command_connect_accelerator("TexRotateCounter"); + command_connect_accelerator("TexScaleUp"); + command_connect_accelerator("TexScaleDown"); + command_connect_accelerator("TexScaleLeft"); + command_connect_accelerator("TexScaleRight"); + command_connect_accelerator("TexShiftUp"); + command_connect_accelerator("TexShiftDown"); + command_connect_accelerator("TexShiftLeft"); + command_connect_accelerator("TexShiftRight"); +} + +void SelectNudge_registerShortcuts() +{ + command_connect_accelerator("MoveSelectionDOWN"); + command_connect_accelerator("MoveSelectionUP"); + //command_connect_accelerator("SelectNudgeLeft"); + //command_connect_accelerator("SelectNudgeRight"); + //command_connect_accelerator("SelectNudgeUp"); + //command_connect_accelerator("SelectNudgeDown"); +} + +void SnapToGrid_registerShortcuts() +{ + command_connect_accelerator("SnapToGrid"); +} + +void SelectByType_registerShortcuts() +{ + command_connect_accelerator("SelectAllOfType"); +} + +void SurfaceInspector_registerShortcuts() +{ + command_connect_accelerator("FitTexture"); +} + + +void register_shortcuts() +{ + PatchInspector_registerShortcuts(); + Patch_registerShortcuts(); + Grid_registerShortcuts(); + XYWnd_registerShortcuts(); + CamWnd_registerShortcuts(); + Manipulators_registerShortcuts(); + SurfaceInspector_registerShortcuts(); + TexdefNudge_registerShortcuts(); + SelectNudge_registerShortcuts(); + SnapToGrid_registerShortcuts(); + SelectByType_registerShortcuts(); +} + +void File_constructToolbar(GtkToolbar* toolbar) +{ + toolbar_append_button(toolbar, "Open an existing map (CTRL + O)", "file_open.bmp", "OpenMap"); + toolbar_append_button(toolbar, "Save the active map (CTRL + S)", "file_save.bmp", "SaveMap"); +} + +void UndoRedo_constructToolbar(GtkToolbar* toolbar) +{ + toolbar_append_button(toolbar, "Undo (CTRL + Z)", "undo.bmp", "Undo"); + toolbar_append_button(toolbar, "Redo (CTRL + Y)", "redo.bmp", "Redo"); +} + +void RotateFlip_constructToolbar(GtkToolbar* toolbar) +{ + toolbar_append_button(toolbar, "x-axis Flip", "brush_flipx.bmp", "MirrorSelectionX"); + toolbar_append_button(toolbar, "x-axis Rotate", "brush_rotatex.bmp", "RotateSelectionX"); + toolbar_append_button(toolbar, "y-axis Flip", "brush_flipy.bmp", "MirrorSelectionY"); + toolbar_append_button(toolbar, "y-axis Rotate", "brush_rotatey.bmp", "RotateSelectionY"); + toolbar_append_button(toolbar, "z-axis Flip", "brush_flipz.bmp", "MirrorSelectionZ"); + toolbar_append_button(toolbar, "z-axis Rotate", "brush_rotatez.bmp", "RotateSelectionZ"); +} + +void Select_constructToolbar(GtkToolbar* toolbar) +{ + toolbar_append_button(toolbar, "Select touching", "selection_selecttouching.bmp", "SelectTouching"); + toolbar_append_button(toolbar, "Select inside", "selection_selectinside.bmp", "SelectInside"); +} + +void CSG_constructToolbar(GtkToolbar* toolbar) +{ + toolbar_append_button(toolbar, "CSG Subtract (SHIFT + U)", "selection_csgsubtract.bmp", "CSGSubtract"); + toolbar_append_button(toolbar, "CSG Merge (CTRL + U)", "selection_csgmerge.bmp", "CSGMerge"); + toolbar_append_button(toolbar, "Hollow", "selection_makehollow.bmp", "CSGHollow"); +} + +void ComponentModes_constructToolbar(GtkToolbar* toolbar) +{ + toolbar_append_toggle_button(toolbar, "Select Vertices (V)", "modify_vertices.bmp", "DragVertices"); + toolbar_append_toggle_button(toolbar, "Select Edges (E)", "modify_edges.bmp", "DragEdges"); + toolbar_append_toggle_button(toolbar, "Select Faces (F)", "modify_faces.bmp", "DragFaces"); +} + +void Clipper_constructToolbar(GtkToolbar* toolbar) +{ + + toolbar_append_toggle_button(toolbar, "Clipper (X)", "view_clipper.bmp", "ToggleClipper"); +} + +void XYWnd_constructToolbar(GtkToolbar* toolbar) +{ + toolbar_append_button(toolbar, "Change views", "view_change.bmp", "NextView"); +} + +void Manipulators_constructToolbar(GtkToolbar* toolbar) +{ + toolbar_append_toggle_button(toolbar, "Translate (W)", "select_mousetranslate.bmp", "MouseTranslate"); + toolbar_append_toggle_button(toolbar, "Rotate (R)", "select_mouserotate.bmp", "MouseRotate"); + toolbar_append_toggle_button(toolbar, "Scale", "select_mousescale.bmp", "MouseScale"); + toolbar_append_toggle_button(toolbar, "Resize (Q)", "select_mouseresize.bmp", "MouseDrag"); + + Clipper_constructToolbar(toolbar); +} + +GtkToolbar* create_main_toolbar(MainFrame::EViewStyle style) +{ + GtkToolbar* toolbar = GTK_TOOLBAR(gtk_toolbar_new()); + gtk_toolbar_set_orientation(toolbar, GTK_ORIENTATION_HORIZONTAL); + gtk_toolbar_set_style(toolbar, GTK_TOOLBAR_ICONS); + + gtk_widget_show(GTK_WIDGET(toolbar)); + + File_constructToolbar(toolbar); + + gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + + UndoRedo_constructToolbar(toolbar); + + gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + + RotateFlip_constructToolbar(toolbar); + + gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + + Select_constructToolbar(toolbar); + + gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + + CSG_constructToolbar(toolbar); + + gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + + ComponentModes_constructToolbar(toolbar); + + if(style == MainFrame::eRegular || style == MainFrame::eRegularLeft || style == MainFrame::eFloating) + { + gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + + XYWnd_constructToolbar(toolbar); + } + + gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + + CamWnd_constructToolbar(toolbar); + + gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + + Manipulators_constructToolbar(toolbar); + + if (g_Layout_enablePatchToolbar.m_value) + { + gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + + Patch_constructToolbar(toolbar); + } + + gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + + toolbar_append_toggle_button(toolbar, "Texture Lock (SHIFT +T)", "texture_lock.bmp", "TogTexLock"); + + gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + + GtkButton* g_view_entities_button = toolbar_append_button(toolbar, "Entities (N)", "entities.bmp", "ToggleEntityInspector"); + GtkButton* g_view_console_button = toolbar_append_button(toolbar, "Console (O)", "console.bmp", "ToggleConsole"); + GtkButton* g_view_textures_button = toolbar_append_button(toolbar, "Texture Browser (T)", "texture_browser.bmp", "ToggleTextures"); + // TODO: call light inspector + //GtkButton* g_view_lightinspector_button = toolbar_append_button(toolbar, "Light Inspector", "lightinspector.bmp", "ToggleLightInspector"); + + gtk_toolbar_append_space (GTK_TOOLBAR (toolbar)); + GtkButton* g_refresh_models_button = toolbar_append_button(toolbar, "Refresh Models", "refresh_models.bmp", "RefreshReferences"); + + + // disable the console and texture button in the regular layouts + if(style == MainFrame::eRegular || style == MainFrame::eRegularLeft) + { + gtk_widget_set_sensitive(GTK_WIDGET(g_view_console_button), FALSE); + gtk_widget_set_sensitive(GTK_WIDGET(g_view_textures_button), FALSE); + } + + return toolbar; +} + +GtkWidget* create_main_statusbar(GtkWidget *pStatusLabel[c_count_status]) +{ + GtkTable* table = GTK_TABLE(gtk_table_new(1, c_count_status, FALSE)); + gtk_widget_show(GTK_WIDGET(table)); + + { + GtkLabel* label = GTK_LABEL(gtk_label_new ("Label")); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + gtk_misc_set_padding(GTK_MISC(label), 4, 2); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach_defaults(table, GTK_WIDGET(label), 0, 1, 0, 1); + pStatusLabel[c_command_status] = GTK_WIDGET(label); + } + + for(int i = 1; i < c_count_status; ++i) + { + GtkFrame* frame = GTK_FRAME(gtk_frame_new(0)); + gtk_widget_show(GTK_WIDGET(frame)); + gtk_table_attach_defaults(table, GTK_WIDGET(frame), i, i + 1, 0, 1); + gtk_frame_set_shadow_type(frame, GTK_SHADOW_IN); + + GtkLabel* label = GTK_LABEL(gtk_label_new ("Label")); + gtk_label_set_ellipsize( label, PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + gtk_misc_set_padding(GTK_MISC(label), 4, 2); + gtk_widget_show(GTK_WIDGET(label)); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(label)); + pStatusLabel[i] = GTK_WIDGET(label); + } + + return GTK_WIDGET(table); +} + +#if 0 + + +WidgetFocusPrinter g_mainframeWidgetFocusPrinter("mainframe"); + +class WindowFocusPrinter +{ + const char* m_name; + + static gboolean frame_event(GtkWidget *widget, GdkEvent* event, WindowFocusPrinter* self) + { + globalOutputStream() << self->m_name << " frame_event\n"; + return FALSE; + } + static gboolean keys_changed(GtkWidget *widget, WindowFocusPrinter* self) + { + globalOutputStream() << self->m_name << " keys_changed\n"; + return FALSE; + } + static gboolean notify(GtkWindow* window, gpointer dummy, WindowFocusPrinter* self) + { + if(gtk_window_is_active(window)) + { + globalOutputStream() << self->m_name << " takes toplevel focus\n"; + } + else + { + globalOutputStream() << self->m_name << " loses toplevel focus\n"; + } + return FALSE; + } +public: + WindowFocusPrinter(const char* name) : m_name(name) + { + } + void connect(GtkWindow* toplevel_window) + { + g_signal_connect(G_OBJECT(toplevel_window), "notify::has_toplevel_focus", G_CALLBACK(notify), this); + g_signal_connect(G_OBJECT(toplevel_window), "notify::is_active", G_CALLBACK(notify), this); + g_signal_connect(G_OBJECT(toplevel_window), "keys_changed", G_CALLBACK(keys_changed), this); + g_signal_connect(G_OBJECT(toplevel_window), "frame_event", G_CALLBACK(frame_event), this); + } +}; + +WindowFocusPrinter g_mainframeFocusPrinter("mainframe"); + +#endif + +class MainWindowActive +{ + static gboolean notify(GtkWindow* window, gpointer dummy, MainWindowActive* self) + { + if(g_wait.m_window != 0 && gtk_window_is_active(window) && !GTK_WIDGET_VISIBLE(g_wait.m_window)) + { + gtk_widget_show(GTK_WIDGET(g_wait.m_window)); + } + + return FALSE; + } +public: + void connect(GtkWindow* toplevel_window) + { + g_signal_connect(G_OBJECT(toplevel_window), "notify::is-active", G_CALLBACK(notify), this); + } +}; + +MainWindowActive g_MainWindowActive; + +SignalHandlerId XYWindowDestroyed_connect(const SignalHandler& handler) +{ + return g_pParentWnd->GetXYWnd()->onDestroyed.connectFirst(handler); +} + +void XYWindowDestroyed_disconnect(SignalHandlerId id) +{ + g_pParentWnd->GetXYWnd()->onDestroyed.disconnect(id); +} + +MouseEventHandlerId XYWindowMouseDown_connect(const MouseEventHandler& handler) +{ + return g_pParentWnd->GetXYWnd()->onMouseDown.connectFirst(handler); +} + +void XYWindowMouseDown_disconnect(MouseEventHandlerId id) +{ + g_pParentWnd->GetXYWnd()->onMouseDown.disconnect(id); +} + +// ============================================================================= +// MainFrame class + +MainFrame* g_pParentWnd = 0; + +GtkWindow* MainFrame_getWindow() +{ + if(g_pParentWnd == 0) + { + return 0; + } + return g_pParentWnd->m_window; +} + +std::vector g_floating_windows; + +MainFrame::MainFrame() : m_window(0), m_idleRedrawStatusText(RedrawStatusTextCaller(*this)) +{ + m_pXYWnd = 0; + m_pCamWnd = 0; + m_pZWnd = 0; + m_pYZWnd = 0; + m_pXZWnd = 0; + m_pActiveXY = 0; + + for (int n = 0;n < c_count_status;n++) + { + m_pStatusLabel[n] = 0; + } + + m_bSleeping = false; + + Create(); +} + +MainFrame::~MainFrame() +{ + SaveWindowInfo(); + + gtk_widget_hide(GTK_WIDGET(m_window)); + + Shutdown(); + + for(std::vector::iterator i = g_floating_windows.begin(); i != g_floating_windows.end(); ++i) + { + gtk_widget_destroy(*i); + } + + gtk_widget_destroy(GTK_WIDGET(m_window)); +} + +void MainFrame::SetActiveXY(XYWnd* p) +{ + if (m_pActiveXY) + m_pActiveXY->SetActive(false); + + m_pActiveXY = p; + + if (m_pActiveXY) + m_pActiveXY->SetActive(true); + +} + +void MainFrame::ReleaseContexts() +{ +#if 0 + if (m_pXYWnd) + m_pXYWnd->DestroyContext(); + if (m_pYZWnd) + m_pYZWnd->DestroyContext(); + if (m_pXZWnd) + m_pXZWnd->DestroyContext(); + if (m_pCamWnd) + m_pCamWnd->DestroyContext(); + if (m_pTexWnd) + m_pTexWnd->DestroyContext(); + if (m_pZWnd) + m_pZWnd->DestroyContext(); +#endif +} + +void MainFrame::CreateContexts() +{ +#if 0 + if (m_pCamWnd) + m_pCamWnd->CreateContext(); + if (m_pXYWnd) + m_pXYWnd->CreateContext(); + if (m_pYZWnd) + m_pYZWnd->CreateContext(); + if (m_pXZWnd) + m_pXZWnd->CreateContext(); + if (m_pTexWnd) + m_pTexWnd->CreateContext(); + if (m_pZWnd) + m_pZWnd->CreateContext(); +#endif +} + +#ifdef _DEBUG +//#define DBG_SLEEP +#endif + +void MainFrame::OnSleep() +{ +#if 0 + m_bSleeping ^= 1; + if (m_bSleeping) + { + // useful when trying to debug crashes in the sleep code + globalOutputStream() << "Going into sleep mode..\n"; + + globalOutputStream() << "Dispatching sleep msg..."; + DispatchRadiantMsg (RADIANT_SLEEP); + globalOutputStream() << "Done.\n"; + + gtk_window_iconify(m_window); + GlobalSelectionSystem().setSelectedAll(false); + + GlobalShaderCache().unrealise(); + Shaders_Free(); + GlobalOpenGL_debugAssertNoErrors(); + ScreenUpdates_Disable(); + + // release contexts + globalOutputStream() << "Releasing contexts..."; + ReleaseContexts(); + globalOutputStream() << "Done.\n"; + } + else + { + globalOutputStream() << "Waking up\n"; + + gtk_window_deiconify(m_window); + + // create contexts + globalOutputStream() << "Creating contexts..."; + CreateContexts(); + globalOutputStream() << "Done.\n"; + + globalOutputStream() << "Making current on camera..."; + m_pCamWnd->MakeCurrent(); + globalOutputStream() << "Done.\n"; + + globalOutputStream() << "Reloading shaders..."; + Shaders_Load(); + GlobalShaderCache().realise(); + globalOutputStream() << "Done.\n"; + + ScreenUpdates_Enable(); + + globalOutputStream() << "Dispatching wake msg..."; + DispatchRadiantMsg (RADIANT_WAKEUP); + globalOutputStream() << "Done\n"; + } +#endif +} + + +GtkWindow* create_splash() +{ + GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + gtk_window_set_decorated(window, FALSE); + gtk_window_set_resizable(window, FALSE); + gtk_window_set_modal(window, TRUE); + gtk_window_set_default_size(window, -1, -1); + gtk_window_set_position(window, GTK_WIN_POS_CENTER); + gtk_container_set_border_width(GTK_CONTAINER(window), 0); + + GtkImage* image = new_local_image("splash.bmp"); + gtk_widget_show(GTK_WIDGET(image)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(image)); + + gtk_widget_set_size_request(GTK_WIDGET(window), -1, -1); + gtk_widget_show(GTK_WIDGET(window)); + + return window; +} + +static GtkWindow *splash_screen = 0; + +void show_splash() +{ + splash_screen = create_splash(); + + process_gui(); +} + +void hide_splash() +{ + gtk_widget_destroy(GTK_WIDGET(splash_screen)); +} + +WindowPositionTracker g_posCamWnd; +WindowPositionTracker g_posXYWnd; +WindowPositionTracker g_posXZWnd; +WindowPositionTracker g_posYZWnd; + +static gint mainframe_delete (GtkWidget *widget, GdkEvent *event, gpointer data) +{ + if(ConfirmModified("Exit Radiant")) + { + gtk_main_quit(); + } + + return TRUE; +} + +void MainFrame::Create() +{ + GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + + GlobalWindowObservers_connectTopLevel(window); + + gtk_window_set_transient_for(splash_screen, window); + +#if !defined(WIN32) + { + GdkPixbuf* pixbuf = pixbuf_new_from_file_with_mask("bitmaps/icon.bmp"); + if(pixbuf != 0) + { + gtk_window_set_icon(window, pixbuf); + gdk_pixbuf_unref(pixbuf); + } + } +#endif + + gtk_widget_add_events(GTK_WIDGET(window), GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK); + g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(mainframe_delete), this); + + m_position_tracker.connect(window); + +#if 0 + g_mainframeWidgetFocusPrinter.connect(window); + g_mainframeFocusPrinter.connect(window); +#endif + + g_MainWindowActive.connect(window); + + GetPlugInMgr().Init(GTK_WIDGET(window)); + + GtkWidget* vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_widget_show(vbox); + + global_accel_connect_window(window); + + m_nCurrentStyle = (EViewStyle)g_Layout_viewStyle.m_value; + + register_shortcuts(); + + GtkMenuBar* main_menu = create_main_menu(CurrentStyle()); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(main_menu), FALSE, FALSE, 0); + + GtkToolbar* main_toolbar = create_main_toolbar(CurrentStyle()); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(main_toolbar), FALSE, FALSE, 0); + + GtkToolbar* plugin_toolbar = create_plugin_toolbar(); + if (!g_Layout_enablePluginToolbar.m_value) + { + gtk_widget_hide(GTK_WIDGET(plugin_toolbar)); + } + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(plugin_toolbar), FALSE, FALSE, 0); + + GtkWidget* main_statusbar = create_main_statusbar(m_pStatusLabel); + gtk_box_pack_end(GTK_BOX(vbox), main_statusbar, FALSE, TRUE, 2); + + GroupDialog_constructWindow(window); + g_page_entity = GroupDialog_addPage("Entities", EntityInspector_constructWindow(GroupDialog_getWindow()), RawStringExportCaller("Entities")); + + if(FloatingGroupDialog()) + { + g_page_console = GroupDialog_addPage("Console", Console_constructWindow(GroupDialog_getWindow()), RawStringExportCaller("Console")); + } + +#ifdef WIN32 + if( g_multimon_globals.m_bStartOnPrimMon ) + { + PositionWindowOnPrimaryScreen(g_layout_globals.m_position); + window_set_position(window, g_layout_globals.m_position); + } + else +#endif + if(g_layout_globals.nState & GDK_WINDOW_STATE_MAXIMIZED) + { + gtk_window_maximize(window); + WindowPosition default_position(-1, -1, 640, 480); + window_set_position(window, default_position); + } + else + { + window_set_position(window, g_layout_globals.m_position); + } + + m_window = window; + + gtk_widget_show(GTK_WIDGET(window)); + + if (CurrentStyle() == eRegular || CurrentStyle() == eRegularLeft) + { + { + GtkWidget* vsplit = gtk_vpaned_new(); + m_vSplit = vsplit; + gtk_box_pack_start(GTK_BOX(vbox), vsplit, TRUE, TRUE, 0); + gtk_widget_show (vsplit); + + // console + GtkWidget* console_window = Console_constructWindow(window); + gtk_paned_pack2(GTK_PANED(vsplit), console_window, FALSE, TRUE); + + { + GtkWidget* hsplit = gtk_hpaned_new(); + gtk_widget_show (hsplit); + m_hSplit = hsplit; + gtk_paned_add1(GTK_PANED(vsplit), hsplit); + + // xy + m_pXYWnd = new XYWnd(); + m_pXYWnd->SetViewType(XY); + GtkWidget* xy_window = GTK_WIDGET(create_framed_widget(m_pXYWnd->GetWidget())); + + { + GtkWidget* vsplit2 = gtk_vpaned_new(); + gtk_widget_show(vsplit2); + m_vSplit2 = vsplit2; + + if (CurrentStyle() == eRegular) + { + gtk_paned_add1(GTK_PANED(hsplit), xy_window); + gtk_paned_add2(GTK_PANED(hsplit), vsplit2); + } + else + { + gtk_paned_add1(GTK_PANED(hsplit), vsplit2); + gtk_paned_add2(GTK_PANED(hsplit), xy_window); + } + + + // camera + m_pCamWnd = NewCamWnd(); + GlobalCamera_setCamWnd(*m_pCamWnd); + CamWnd_setParent(*m_pCamWnd, window); + GtkFrame* camera_window = create_framed_widget(CamWnd_getWidget(*m_pCamWnd)); + + gtk_paned_add1(GTK_PANED(vsplit2), GTK_WIDGET(camera_window)); + + // textures + GtkFrame* texture_window = create_framed_widget(TextureBrowser_constructWindow(window)); + + gtk_paned_add2(GTK_PANED(vsplit2), GTK_WIDGET(texture_window)); + } + } + } + + gtk_paned_set_position(GTK_PANED(m_vSplit), g_layout_globals.nXYHeight); + + if (CurrentStyle() == eRegular) + { + gtk_paned_set_position(GTK_PANED(m_hSplit), g_layout_globals.nXYWidth); + } + else + { + gtk_paned_set_position(GTK_PANED(m_hSplit), g_layout_globals.nCamWidth); + } + + gtk_paned_set_position(GTK_PANED(m_vSplit2), g_layout_globals.nCamHeight); + } + else if (CurrentStyle() == eFloating) + { + { + GtkWindow* window = create_persistent_floating_window("Camera", m_window); + global_accel_connect_window(window); + g_posCamWnd.connect(window); + + gtk_widget_show(GTK_WIDGET(window)); + + m_pCamWnd = NewCamWnd(); + GlobalCamera_setCamWnd(*m_pCamWnd); + + { + GtkFrame* frame = create_framed_widget(CamWnd_getWidget(*m_pCamWnd)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(frame)); + } + CamWnd_setParent(*m_pCamWnd, window); + + g_floating_windows.push_back(GTK_WIDGET(window)); + } + + { + GtkWindow* window = create_persistent_floating_window(ViewType_getTitle(XY), m_window); + global_accel_connect_window(window); + g_posXYWnd.connect(window); + + m_pXYWnd = new XYWnd(); + m_pXYWnd->m_parent = window; + m_pXYWnd->SetViewType(XY); + + + { + GtkFrame* frame = create_framed_widget(m_pXYWnd->GetWidget()); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(frame)); + } + XY_Top_Shown_Construct(window); + + g_floating_windows.push_back(GTK_WIDGET(window)); + } + + { + GtkWindow* window = create_persistent_floating_window(ViewType_getTitle(XZ), m_window); + global_accel_connect_window(window); + g_posXZWnd.connect(window); + + m_pXZWnd = new XYWnd(); + m_pXZWnd->m_parent = window; + m_pXZWnd->SetViewType(XZ); + + { + GtkFrame* frame = create_framed_widget(m_pXZWnd->GetWidget()); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(frame)); + } + + XZ_Front_Shown_Construct(window); + + g_floating_windows.push_back(GTK_WIDGET(window)); + } + + { + GtkWindow* window = create_persistent_floating_window(ViewType_getTitle(YZ), m_window); + global_accel_connect_window(window); + g_posYZWnd.connect(window); + + m_pYZWnd = new XYWnd(); + m_pYZWnd->m_parent = window; + m_pYZWnd->SetViewType(YZ); + + { + GtkFrame* frame = create_framed_widget(m_pYZWnd->GetWidget()); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(frame)); + } + + YZ_Side_Shown_Construct(window); + + g_floating_windows.push_back(GTK_WIDGET(window)); + } + + { + GtkFrame* frame = create_framed_widget(TextureBrowser_constructWindow(GroupDialog_getWindow())); + g_page_textures = GroupDialog_addPage("Textures", GTK_WIDGET(frame), TextureBrowserExportTitleCaller()); + } + + GroupDialog_show(); + } + else // 4 way + { + m_pCamWnd = NewCamWnd(); + GlobalCamera_setCamWnd(*m_pCamWnd); + CamWnd_setParent(*m_pCamWnd, window); + + GtkWidget* camera = CamWnd_getWidget(*m_pCamWnd); + + m_pYZWnd = new XYWnd(); + m_pYZWnd->SetViewType(YZ); + + GtkWidget* yz = m_pYZWnd->GetWidget(); + + m_pXYWnd = new XYWnd(); + m_pXYWnd->SetViewType(XY); + + GtkWidget* xy = m_pXYWnd->GetWidget(); + + m_pXZWnd = new XYWnd(); + m_pXZWnd->SetViewType(XZ); + + GtkWidget* xz = m_pXZWnd->GetWidget(); + + GtkHPaned* split = create_split_views(camera, yz, xy, xz); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(split), TRUE, TRUE, 0); + + { + GtkFrame* frame = create_framed_widget(TextureBrowser_constructWindow(window)); + g_page_textures = GroupDialog_addPage("Textures", GTK_WIDGET(frame), TextureBrowserExportTitleCaller()); + } + } + + EntityList_constructWindow(window); + PreferencesDialog_constructWindow(window); + FindTextureDialog_constructWindow(window); + SurfaceInspector_constructWindow(window); + PatchInspector_constructWindow(window); + + SetActiveXY(m_pXYWnd); + + AddGridChangeCallback(SetGridStatusCaller(*this)); + AddGridChangeCallback(ReferenceCaller(*this)); + + g_defaultToolMode = DragMode; + g_defaultToolMode(); + SetStatusText(m_command_status, c_TranslateMode_status); + + EverySecondTimer_enable(); + + //GlobalShortcuts_reportUnregistered(); +} + +void MainFrame::SaveWindowInfo() +{ + if (!FloatingGroupDialog()) + { + g_layout_globals.nXYHeight = gtk_paned_get_position(GTK_PANED(m_vSplit)); + + if(CurrentStyle() != eRegular) + { + g_layout_globals.nCamWidth = gtk_paned_get_position(GTK_PANED(m_hSplit)); + } + else + { + g_layout_globals.nXYWidth = gtk_paned_get_position(GTK_PANED(m_hSplit)); + } + + g_layout_globals.nCamHeight = gtk_paned_get_position(GTK_PANED(m_vSplit2)); + } + + g_layout_globals.m_position = m_position_tracker.getPosition(); + + g_layout_globals.nState = gdk_window_get_state(GTK_WIDGET(m_window)->window); +} + +void MainFrame::Shutdown() +{ + EverySecondTimer_disable(); + + EntityList_destroyWindow(); + + delete m_pXYWnd; + m_pXYWnd = 0; + delete m_pYZWnd; + m_pYZWnd = 0; + delete m_pXZWnd; + m_pXZWnd = 0; + + TextureBrowser_destroyWindow(); + + DeleteCamWnd(m_pCamWnd); + m_pCamWnd = 0; + + PreferencesDialog_destroyWindow(); + SurfaceInspector_destroyWindow(); + FindTextureDialog_destroyWindow(); + PatchInspector_destroyWindow(); + + g_DbgDlg.destroyWindow(); + + // destroying group-dialog last because it may contain texture-browser + GroupDialog_destroyWindow(); +} + +void MainFrame::RedrawStatusText() +{ + gtk_label_set_text(GTK_LABEL(m_pStatusLabel[c_command_status]), m_command_status.c_str()); + gtk_label_set_text(GTK_LABEL(m_pStatusLabel[c_position_status]), m_position_status.c_str()); + gtk_label_set_text(GTK_LABEL(m_pStatusLabel[c_brushcount_status]), m_brushcount_status.c_str()); + gtk_label_set_text(GTK_LABEL(m_pStatusLabel[c_texture_status]), m_texture_status.c_str()); + gtk_label_set_text(GTK_LABEL(m_pStatusLabel[c_grid_status]), m_grid_status.c_str()); +} + +void MainFrame::UpdateStatusText() +{ + m_idleRedrawStatusText.queueDraw(); +} + +void MainFrame::SetStatusText(CopiedString& status_text, const char* pText) +{ + status_text = pText; + UpdateStatusText(); +} + +void Sys_Status(const char* status) +{ + if(g_pParentWnd != 0) + { + g_pParentWnd->SetStatusText (g_pParentWnd->m_command_status, status); + } +} + +int getRotateIncrement() +{ + return static_cast(g_si_globals.rotate); +} + +int getFarClipDistance() +{ + return g_camwindow_globals.m_nCubicScale; +} + +float (*GridStatus_getGridSize)() = GetGridSize; +int (*GridStatus_getRotateIncrement)() = getRotateIncrement; +int (*GridStatus_getFarClipDistance)() = getFarClipDistance; +bool (*GridStatus_getTextureLockEnabled)(); + +void MainFrame::SetGridStatus() +{ + StringOutputStream status(64); + const char* lock = (GridStatus_getTextureLockEnabled()) ? "ON" : "OFF"; + status << "G:" << GridStatus_getGridSize() + << " R:" << GridStatus_getRotateIncrement() + << " C:" << GridStatus_getFarClipDistance() + << " L:" << lock; + SetStatusText(m_grid_status, status.c_str()); +} + +void GridStatus_onTextureLockEnabledChanged() +{ + if(g_pParentWnd != 0) + { + g_pParentWnd->SetGridStatus(); + } +} + +namespace +{ + GLFont g_font(0, 0); +} + +void GlobalGL_sharedContextCreated() +{ + // report OpenGL information + globalOutputStream() << "GL_VENDOR: " << reinterpret_cast(glGetString (GL_VENDOR)) << "\n"; + globalOutputStream() << "GL_RENDERER: " << reinterpret_cast(glGetString (GL_RENDERER)) << "\n"; + globalOutputStream() << "GL_VERSION: " << reinterpret_cast(glGetString (GL_VERSION)) << "\n"; + globalOutputStream() << "GL_EXTENSIONS: " << reinterpret_cast(glGetString (GL_EXTENSIONS)) << "\n"; + + QGL_sharedContextCreated(GlobalOpenGL()); + + ShaderCache_extensionsInitialised(); + + GlobalShaderCache().realise(); + Textures_Realise(); + +#ifdef __linux__ + g_font = glfont_create("fixed 8"); +#else + g_font = glfont_create("courier 8"); +#endif + GlobalOpenGL().m_font = g_font.getDisplayList(); + GlobalOpenGL().m_fontHeight = g_font.getPixelHeight(); +} + +void GlobalGL_sharedContextDestroyed() +{ + Textures_Unrealise(); + GlobalShaderCache().unrealise(); + + QGL_sharedContextDestroyed(GlobalOpenGL()); +} + + +void Layout_constructPreferences(PreferencesPage& page) +{ + { + const char* layouts[] = { "window1.bmp", "window2.bmp", "window3.bmp", "window4.bmp" }; + page.appendRadioIcons( + "Window Layout", + STRING_ARRAY_RANGE(layouts), + LatchedIntImportCaller(g_Layout_viewStyle), + IntExportCaller(g_Layout_viewStyle.m_latched) + ); + } + page.appendCheckBox( + "", "Detachable Menus", + LatchedBoolImportCaller(g_Layout_enableDetachableMenus), + BoolExportCaller(g_Layout_enableDetachableMenus.m_latched) + ); + if (!string_empty(g_pGameDescription->getKeyValue("no_patch"))) + { + page.appendCheckBox( + "", "Patch Toolbar", + LatchedBoolImportCaller(g_Layout_enablePatchToolbar), + BoolExportCaller(g_Layout_enablePatchToolbar.m_latched) + ); + } + page.appendCheckBox( + "", "Plugin Toolbar", + LatchedBoolImportCaller(g_Layout_enablePluginToolbar), + BoolExportCaller(g_Layout_enablePluginToolbar.m_latched) + ); +} + +void Layout_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Layout", "Layout Preferences")); + Layout_constructPreferences(page); +} + +void Layout_registerPreferencesPage() +{ + PreferencesDialog_addInterfacePage(FreeCaller1()); +} + + +#include "preferencesystem.h" +#include "stringio.h" + +void MainFrame_Construct() +{ + GlobalCommands_insert("OpenManual", FreeCaller(), Accelerator(GDK_F1)); + + GlobalCommands_insert("Sleep", FreeCaller(), Accelerator('P', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + GlobalCommands_insert("NewMap", FreeCaller()); + GlobalCommands_insert("OpenMap", FreeCaller(), Accelerator('O', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("ImportMap", FreeCaller()); + GlobalCommands_insert("SaveMap", FreeCaller(), Accelerator('S', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("SaveMapAs", FreeCaller()); + GlobalCommands_insert("SaveSelected", FreeCaller()); + GlobalCommands_insert("SaveRegion", FreeCaller()); + GlobalCommands_insert("RefreshReferences", FreeCaller()); + GlobalCommands_insert("ProjectSettings", FreeCaller()); + GlobalCommands_insert("CheckForUpdate", FreeCaller()); + GlobalCommands_insert("Exit", FreeCaller()); + + GlobalCommands_insert("Undo", FreeCaller(), Accelerator('Z', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("Redo", FreeCaller(), Accelerator('Y', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("Copy", FreeCaller(), Accelerator('C', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("Paste", FreeCaller(), Accelerator('V', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("PasteToCamera", FreeCaller(), Accelerator('V', (GdkModifierType)GDK_MOD1_MASK)); + GlobalCommands_insert("CloneSelection", FreeCaller(), Accelerator(GDK_space)); + GlobalCommands_insert("DeleteSelection", FreeCaller(), Accelerator(GDK_BackSpace)); + GlobalCommands_insert("ParentSelection", FreeCaller()); + GlobalCommands_insert("UnSelectSelection", FreeCaller(), Accelerator(GDK_Escape)); + GlobalCommands_insert("InvertSelection", FreeCaller(), Accelerator('I')); + GlobalCommands_insert("SelectInside", FreeCaller()); + GlobalCommands_insert("SelectTouching", FreeCaller()); + GlobalCommands_insert("ExpandSelectionToEntities", FreeCaller(), Accelerator('E', (GdkModifierType)(GDK_MOD1_MASK|GDK_CONTROL_MASK))); + GlobalCommands_insert("Preferences", FreeCaller(), Accelerator('P')); + + GlobalCommands_insert("ToggleConsole", FreeCaller(), Accelerator('O')); + GlobalCommands_insert("ToggleEntityInspector", FreeCaller(), Accelerator('N')); + GlobalCommands_insert("EntityList", FreeCaller(), Accelerator('L')); + + GlobalCommands_insert("ShowHidden", FreeCaller(), Accelerator('H', (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("HideSelected", FreeCaller(), Accelerator('H')); + + GlobalToggles_insert("DragVertices", FreeCaller(), ToggleItem::AddCallbackCaller(g_vertexMode_button), Accelerator('V')); + GlobalToggles_insert("DragEdges", FreeCaller(), ToggleItem::AddCallbackCaller(g_edgeMode_button), Accelerator('E')); + GlobalToggles_insert("DragFaces", FreeCaller(), ToggleItem::AddCallbackCaller(g_faceMode_button), Accelerator('F')); + + GlobalCommands_insert("MirrorSelectionX", FreeCaller()); + GlobalCommands_insert("RotateSelectionX", FreeCaller()); + GlobalCommands_insert("MirrorSelectionY", FreeCaller()); + GlobalCommands_insert("RotateSelectionY", FreeCaller()); + GlobalCommands_insert("MirrorSelectionZ", FreeCaller()); + GlobalCommands_insert("RotateSelectionZ", FreeCaller()); + + GlobalCommands_insert("ArbitraryRotation", FreeCaller()); + GlobalCommands_insert("ArbitraryScale", FreeCaller()); + + GlobalCommands_insert("BuildMenuCustomize", FreeCaller()); + + GlobalCommands_insert("FindBrush", FreeCaller()); + + GlobalCommands_insert("MapInfo", FreeCaller(), Accelerator('M')); + + + GlobalToggles_insert("ToggleClipper", FreeCaller(), ToggleItem::AddCallbackCaller(g_clipper_button), Accelerator('X')); + + GlobalToggles_insert("MouseTranslate", FreeCaller(), ToggleItem::AddCallbackCaller(g_translatemode_button), Accelerator('W')); + GlobalToggles_insert("MouseRotate", FreeCaller(), ToggleItem::AddCallbackCaller(g_rotatemode_button), Accelerator('R')); + GlobalToggles_insert("MouseScale", FreeCaller(), ToggleItem::AddCallbackCaller(g_scalemode_button)); + GlobalToggles_insert("MouseDrag", FreeCaller(), ToggleItem::AddCallbackCaller(g_dragmode_button), Accelerator('Q')); + + GlobalCommands_insert("ColorSchemeOriginal", FreeCaller()); + GlobalCommands_insert("ColorSchemeQER", FreeCaller()); + GlobalCommands_insert("ColorSchemeBlackAndGreen", FreeCaller()); + GlobalCommands_insert("ColorSchemeYdnar", FreeCaller()); + GlobalCommands_insert("ChooseTextureBackgroundColor", makeCallback(g_ColoursMenu.m_textureback)); + GlobalCommands_insert("ChooseGridBackgroundColor", makeCallback(g_ColoursMenu.m_xyback)); + GlobalCommands_insert("ChooseGridMajorColor", makeCallback(g_ColoursMenu.m_gridmajor)); + GlobalCommands_insert("ChooseGridMinorColor", makeCallback(g_ColoursMenu.m_gridminor)); + GlobalCommands_insert("ChooseSmallGridMajorColor", makeCallback(g_ColoursMenu.m_gridmajor_alt)); + GlobalCommands_insert("ChooseSmallGridMinorColor", makeCallback(g_ColoursMenu.m_gridminor_alt)); + GlobalCommands_insert("ChooseGridTextColor", makeCallback(g_ColoursMenu.m_gridtext)); + GlobalCommands_insert("ChooseGridBlockColor", makeCallback(g_ColoursMenu.m_gridblock)); + GlobalCommands_insert("ChooseBrushColor", makeCallback(g_ColoursMenu.m_brush)); + GlobalCommands_insert("ChooseCameraBackgroundColor", makeCallback(g_ColoursMenu.m_cameraback)); + GlobalCommands_insert("ChooseSelectedBrushColor", makeCallback(g_ColoursMenu.m_selectedbrush)); + GlobalCommands_insert("ChooseCameraSelectedBrushColor", makeCallback(g_ColoursMenu.m_selectedbrush3d)); + GlobalCommands_insert("ChooseClipperColor", makeCallback(g_ColoursMenu.m_clipper)); + GlobalCommands_insert("ChooseOrthoViewNameColor", makeCallback(g_ColoursMenu.m_viewname)); + + + GlobalCommands_insert("CSGSubtract", FreeCaller(), Accelerator('U', (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("CSGMerge", FreeCaller(), Accelerator('U', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("CSGHollow", FreeCaller()); + + Grid_registerCommands(); + + GlobalCommands_insert("SnapToGrid", FreeCaller(), Accelerator('G', (GdkModifierType)GDK_CONTROL_MASK)); + + GlobalCommands_insert("SelectAllOfType", FreeCaller(), Accelerator('A', (GdkModifierType)GDK_SHIFT_MASK)); + + GlobalCommands_insert("TexRotateClock", FreeCaller(), Accelerator(GDK_Next, (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("TexRotateCounter", FreeCaller(), Accelerator(GDK_Prior, (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("TexScaleUp", FreeCaller(), Accelerator(GDK_Up, (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("TexScaleDown", FreeCaller(), Accelerator(GDK_Down, (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("TexScaleLeft", FreeCaller(), Accelerator(GDK_Left, (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("TexScaleRight", FreeCaller(), Accelerator(GDK_Right, (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("TexShiftUp", FreeCaller(), Accelerator(GDK_Up, (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("TexShiftDown", FreeCaller(), Accelerator(GDK_Down, (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("TexShiftLeft", FreeCaller(), Accelerator(GDK_Left, (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("TexShiftRight", FreeCaller(), Accelerator(GDK_Right, (GdkModifierType)GDK_SHIFT_MASK)); + + GlobalCommands_insert("MoveSelectionDOWN", FreeCaller(), Accelerator(GDK_KP_Subtract)); + GlobalCommands_insert("MoveSelectionUP", FreeCaller(), Accelerator(GDK_KP_Add)); + + GlobalCommands_insert("SelectNudgeLeft", FreeCaller(), Accelerator(GDK_Left, (GdkModifierType)GDK_MOD1_MASK)); + GlobalCommands_insert("SelectNudgeRight", FreeCaller(), Accelerator(GDK_Right, (GdkModifierType)GDK_MOD1_MASK)); + GlobalCommands_insert("SelectNudgeUp", FreeCaller(), Accelerator(GDK_Up, (GdkModifierType)GDK_MOD1_MASK)); + GlobalCommands_insert("SelectNudgeDown", FreeCaller(), Accelerator(GDK_Down, (GdkModifierType)GDK_MOD1_MASK)); + + Patch_registerCommands(); + XYShow_registerCommands(); + + typedef FreeCaller1 ComponentModeSelectionChangedCaller; + GlobalSelectionSystem().addSelectionChangeCallback(ComponentModeSelectionChangedCaller()); + + GlobalPreferenceSystem().registerPreference("DetachableMenus", BoolImportStringCaller(g_Layout_enableDetachableMenus.m_latched), BoolExportStringCaller(g_Layout_enableDetachableMenus.m_latched)); + GlobalPreferenceSystem().registerPreference("PatchToolBar", BoolImportStringCaller(g_Layout_enablePatchToolbar.m_latched), BoolExportStringCaller(g_Layout_enablePatchToolbar.m_latched)); + GlobalPreferenceSystem().registerPreference("PluginToolBar", BoolImportStringCaller(g_Layout_enablePluginToolbar.m_latched), BoolExportStringCaller(g_Layout_enablePluginToolbar.m_latched)); + GlobalPreferenceSystem().registerPreference("QE4StyleWindows", IntImportStringCaller(g_Layout_viewStyle.m_latched), IntExportStringCaller(g_Layout_viewStyle.m_latched)); + GlobalPreferenceSystem().registerPreference("XYHeight", IntImportStringCaller(g_layout_globals.nXYHeight), IntExportStringCaller(g_layout_globals.nXYHeight)); + GlobalPreferenceSystem().registerPreference("XYWidth", IntImportStringCaller(g_layout_globals.nXYWidth), IntExportStringCaller(g_layout_globals.nXYWidth)); + GlobalPreferenceSystem().registerPreference("CamWidth", IntImportStringCaller(g_layout_globals.nCamWidth), IntExportStringCaller(g_layout_globals.nCamWidth)); + GlobalPreferenceSystem().registerPreference("CamHeight", IntImportStringCaller(g_layout_globals.nCamHeight), IntExportStringCaller(g_layout_globals.nCamHeight)); + + GlobalPreferenceSystem().registerPreference("State", IntImportStringCaller(g_layout_globals.nState), IntExportStringCaller(g_layout_globals.nState)); + GlobalPreferenceSystem().registerPreference("PositionX", IntImportStringCaller(g_layout_globals.m_position.x), IntExportStringCaller(g_layout_globals.m_position.x)); + GlobalPreferenceSystem().registerPreference("PositionY", IntImportStringCaller(g_layout_globals.m_position.y), IntExportStringCaller(g_layout_globals.m_position.y)); + GlobalPreferenceSystem().registerPreference("Width", IntImportStringCaller(g_layout_globals.m_position.w), IntExportStringCaller(g_layout_globals.m_position.w)); + GlobalPreferenceSystem().registerPreference("Height", IntImportStringCaller(g_layout_globals.m_position.h), IntExportStringCaller(g_layout_globals.m_position.h)); + + GlobalPreferenceSystem().registerPreference("CamWnd", WindowPositionTrackerImportStringCaller(g_posCamWnd), WindowPositionTrackerExportStringCaller(g_posCamWnd)); + GlobalPreferenceSystem().registerPreference("XYWnd", WindowPositionTrackerImportStringCaller(g_posXYWnd), WindowPositionTrackerExportStringCaller(g_posXYWnd)); + GlobalPreferenceSystem().registerPreference("YZWnd", WindowPositionTrackerImportStringCaller(g_posYZWnd), WindowPositionTrackerExportStringCaller(g_posYZWnd)); + GlobalPreferenceSystem().registerPreference("XZWnd", WindowPositionTrackerImportStringCaller(g_posXZWnd), WindowPositionTrackerExportStringCaller(g_posXZWnd)); + + { + const char* ENGINEPATH_ATTRIBUTE = +#if defined(WIN32) + "enginepath_win32" +#elif defined(__linux__) || defined (__FreeBSD__) + "enginepath_linux" +#elif defined(__APPLE__) + "enginepath_macos" +#else +#error "unknown platform" +#endif + ; + StringOutputStream path(256); + path << DirectoryCleaned(g_pGameDescription->getRequiredKeyValue(ENGINEPATH_ATTRIBUTE)); + g_strEnginePath = path.c_str(); + } + + GlobalPreferenceSystem().registerPreference("EnginePath", CopiedStringImportStringCaller(g_strEnginePath), CopiedStringExportStringCaller(g_strEnginePath)); + + g_Layout_viewStyle.useLatched(); + g_Layout_enableDetachableMenus.useLatched(); + g_Layout_enablePatchToolbar.useLatched(); + g_Layout_enablePluginToolbar.useLatched(); + + Layout_registerPreferencesPage(); + Paths_registerPreferencesPage(); + + g_brushCount.setCountChangedCallback(FreeCaller()); + g_entityCount.setCountChangedCallback(FreeCaller()); + GlobalEntityCreator().setCounter(&g_entityCount); + + GLWidget_sharedContextCreated = GlobalGL_sharedContextCreated; + GLWidget_sharedContextDestroyed = GlobalGL_sharedContextDestroyed; + + GlobalEntityClassManager().attach(g_WorldspawnColourEntityClassObserver); +} + +void MainFrame_Destroy() +{ + GlobalEntityClassManager().detach(g_WorldspawnColourEntityClassObserver); + + GlobalEntityCreator().setCounter(0); + g_entityCount.setCountChangedCallback(Callback()); + g_brushCount.setCountChangedCallback(Callback()); +} + + +void GLWindow_Construct() +{ + GlobalPreferenceSystem().registerPreference("MouseButtons", IntImportStringCaller(g_glwindow_globals.m_nMouseType), IntExportStringCaller(g_glwindow_globals.m_nMouseType)); +} + +void GLWindow_Destroy() +{ +} diff --git a/radiant/mainframe.h b/radiant/mainframe.h new file mode 100644 index 00000000..5997abd3 --- /dev/null +++ b/radiant/mainframe.h @@ -0,0 +1,286 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MAINFRAME_H) +#define INCLUDED_MAINFRAME_H + +#include "gtkutil/window.h" +#include "gtkutil/idledraw.h" +#include "gtkutil/widget.h" +#include "string/string.h" + +#include "qerplugin.h" + +class IPlugIn; +class IToolbarButton; + +class XYWnd; +class CamWnd; +class ZWnd; + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkWindow GtkWindow; + +const int c_command_status = 0; +const int c_position_status = 1; +const int c_brushcount_status = 2; +const int c_texture_status = 3; +const int c_grid_status = 4; +const int c_count_status = 5; + +class MainFrame +{ +public: + enum EViewStyle + { + eRegular = 0, + eFloating = 1, + eSplit = 2, + eRegularLeft = 3, + }; + + MainFrame(); + ~MainFrame(); + + GtkWindow* m_window; + + CopiedString m_command_status; + CopiedString m_position_status; + CopiedString m_brushcount_status; + CopiedString m_texture_status; + CopiedString m_grid_status; +private: + + void Create(); + void SaveWindowInfo(); + void Shutdown(); + + GtkWidget* m_vSplit; + GtkWidget* m_hSplit; + GtkWidget* m_vSplit2; + + XYWnd* m_pXYWnd; + XYWnd* m_pYZWnd; + XYWnd* m_pXZWnd; + CamWnd* m_pCamWnd; + ZWnd* m_pZWnd; + XYWnd* m_pActiveXY; + + bool m_bSleeping; + + GtkWidget *m_pStatusLabel[c_count_status]; + + + EViewStyle m_nCurrentStyle; + WindowPositionTracker m_position_tracker; + + IdleDraw m_idleRedrawStatusText; + +public: + + bool IsSleeping() + { + return m_bSleeping; + } + void OnSleep(); + + void SetStatusText(CopiedString& status_text, const char* pText); + void UpdateStatusText(); + void RedrawStatusText(); + typedef MemberCaller RedrawStatusTextCaller; + + void SetGridStatus(); + typedef MemberCaller SetGridStatusCaller; + + void SetActiveXY(XYWnd* p); + XYWnd* ActiveXY() + { + return m_pActiveXY; + }; + XYWnd* GetXYWnd() + { + return m_pXYWnd; + } + XYWnd* GetXZWnd() + { + return m_pXZWnd; + } + XYWnd* GetYZWnd() + { + return m_pYZWnd; + } + ZWnd* GetZWnd() + { + return m_pZWnd; + } + CamWnd* GetCamWnd() + { + return m_pCamWnd; + } + + void ReleaseContexts(); + void CreateContexts(); + + EViewStyle CurrentStyle() + { + return m_nCurrentStyle; + }; + bool FloatingGroupDialog() + { + return CurrentStyle() == eFloating || CurrentStyle() == eSplit; + }; +}; + +extern MainFrame* g_pParentWnd; + +GtkWindow* MainFrame_getWindow(); + +enum EMouseButtonMode +{ + ETwoButton = 0, + EThreeButton = 1, +}; + +struct glwindow_globals_t +{ + int m_nMouseType; + + glwindow_globals_t() : + m_nMouseType(EThreeButton) + { + } +}; + +void GLWindow_Construct(); +void GLWindow_Destroy(); + +extern glwindow_globals_t g_glwindow_globals; +template +class LatchedValue; +typedef LatchedValue LatchedBool; +extern LatchedBool g_Layout_enableDetachableMenus; + +void deleteSelection(); + + +void Sys_Status(const char* status); + + +void ScreenUpdates_Disable(const char* message, const char* title); +void ScreenUpdates_Enable(); +bool ScreenUpdates_Enabled(); +void ScreenUpdates_process(); + +class ScopeDisableScreenUpdates +{ +public: + ScopeDisableScreenUpdates(const char* message, const char* title) + { + ScreenUpdates_Disable(message, title); + } + ~ScopeDisableScreenUpdates() + { + ScreenUpdates_Enable(); + } +}; + + +void EnginePath_Realise(); +void EnginePath_Unrealise(); + +class ModuleObserver; + +void Radiant_attachEnginePathObserver(ModuleObserver& observer); +void Radiant_detachEnginePathObserver(ModuleObserver& observer); + +void Radiant_attachGameToolsPathObserver(ModuleObserver& observer); +void Radiant_detachGameToolsPathObserver(ModuleObserver& observer); + +extern CopiedString g_strEnginePath; +void EnginePath_verify(); +const char* EnginePath_get(); +const char* QERApp_GetGamePath(); + +extern CopiedString g_strAppPath; +const char* AppPath_get(); + +extern CopiedString g_strSettingsPath; +const char* SettingsPath_get(); + +const char* LocalRcPath_get(void); + +const char* const g_pluginsDir = "plugins/"; ///< name of plugins directory, always sub-directory of toolspath +const char* const g_modulesDir = "modules/"; ///< name of modules directory, always sub-directory of toolspath + +extern CopiedString g_strGameToolsPath; +const char* GameToolsPath_get(); + +void Radiant_Initialise(); +void Radiant_Shutdown(); + +void SaveMapAs(); + + +void XY_UpdateAllWindows(); +void UpdateAllWindows(); + + +void ClipperChangeNotify(); + +void DefaultMode(); + +const char* basegame_get(); +const char* gamename_get(); +void gamename_set(const char* gamename); +void Radiant_attachGameNameObserver(ModuleObserver& observer); +void Radiant_detachGameNameObserver(ModuleObserver& observer); +const char* gamemode_get(); +void gamemode_set(const char* gamemode); +void Radiant_attachGameModeObserver(ModuleObserver& observer); +void Radiant_detachGameModeObserver(ModuleObserver& observer); + + + +void VFS_Construct(); +void VFS_Destroy(); + +void HomePaths_Construct(); +void HomePaths_Destroy(); +void Radiant_attachHomePathsObserver(ModuleObserver& observer); +void Radiant_detachHomePathsObserver(ModuleObserver& observer); + + +void MainFrame_Construct(); +void MainFrame_Destroy(); + + +extern float (*GridStatus_getGridSize)(); +extern int (*GridStatus_getRotateIncrement)(); +extern int (*GridStatus_getFarClipDistance)(); +extern bool (*GridStatus_getTextureLockEnabled)(); +void GridStatus_onTextureLockEnabledChanged(); + +SignalHandlerId XYWindowDestroyed_connect(const SignalHandler& handler); +void XYWindowDestroyed_disconnect(SignalHandlerId id); +MouseEventHandlerId XYWindowMouseDown_connect(const MouseEventHandler& handler); +void XYWindowMouseDown_disconnect(MouseEventHandlerId id); + +#endif diff --git a/radiant/map.cpp b/radiant/map.cpp new file mode 100644 index 00000000..91fb5c51 --- /dev/null +++ b/radiant/map.cpp @@ -0,0 +1,2326 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "map.h" + +#include "debugging/debugging.h" + +#include "imap.h" +#include "iselection.h" +#include "iundo.h" +#include "ibrush.h" +#include "ifilter.h" +#include "ireference.h" +#include "ifiletypes.h" +#include "ieclass.h" +#include "irender.h" +#include "ientity.h" +#include "editable.h" +#include "ifilesystem.h" +#include "namespace.h" +#include "moduleobserver.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "scenelib.h" +#include "transformlib.h" +#include "selectionlib.h" +#include "instancelib.h" +#include "traverselib.h" +#include "maplib.h" +#include "eclasslib.h" +#include "cmdlib.h" +#include "stream/textfilestream.h" +#include "os/path.h" +#include "uniquenames.h" +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" +#include "stream/stringstream.h" +#include "signal/signal.h" + +#include "gtkutil/filechooser.h" +#include "timer.h" +#include "select.h" +#include "plugin.h" +#include "filetypes.h" +#include "gtkdlgs.h" +#include "entityinspector.h" +#include "points.h" +#include "qe3.h" +#include "camwindow.h" +#include "xywindow.h" +#include "mainframe.h" +#include "preferences.h" +#include "referencecache.h" +#include "mru.h" +#include "commands.h" +#include "autosave.h" + +class NameObserver +{ + UniqueNames& m_names; + CopiedString m_name; + + void construct() + { + if(!empty()) + { + //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n"; + m_names.insert(name_read(c_str())); + } + } + void destroy() + { + if(!empty()) + { + //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n"; + m_names.erase(name_read(c_str())); + } + } + + NameObserver& operator=(const NameObserver& other); +public: + NameObserver(UniqueNames& names) : m_names(names) + { + construct(); + } + NameObserver(const NameObserver& other) : m_names(other.m_names), m_name(other.m_name) + { + construct(); + } + ~NameObserver() + { + destroy(); + } + bool empty() const + { + return string_empty(c_str()); + } + const char* c_str() const + { + return m_name.c_str(); + } + void nameChanged(const char* name) + { + destroy(); + m_name = name; + construct(); + } + typedef MemberCaller1 NameChangedCaller; +}; + +class BasicNamespace : public Namespace +{ + typedef std::map Names; + Names m_names; + UniqueNames m_uniqueNames; +public: + ~BasicNamespace() + { + ASSERT_MESSAGE(m_names.empty(), "namespace: names still registered at shutdown"); + } + void attach(const NameCallback& setName, const NameCallbackCallback& attachObserver) + { + std::pair result = m_names.insert(Names::value_type(setName, m_uniqueNames)); + ASSERT_MESSAGE(result.second, "cannot attach name"); + attachObserver(NameObserver::NameChangedCaller((*result.first).second)); + //globalOutputStream() << "attach: " << reinterpret_cast(setName) << "\n"; + } + void detach(const NameCallback& setName, const NameCallbackCallback& detachObserver) + { + Names::iterator i = m_names.find(setName); + ASSERT_MESSAGE(i != m_names.end(), "cannot detach name"); + //globalOutputStream() << "detach: " << reinterpret_cast(setName) << "\n"; + detachObserver(NameObserver::NameChangedCaller((*i).second)); + m_names.erase(i); + } + + void makeUnique(const char* name, const NameCallback& setName) const + { + char buffer[1024]; + name_write(buffer, m_uniqueNames.make_unique(name_read(name))); + setName(buffer); + } + + void mergeNames(const BasicNamespace& other) const + { + typedef std::list SetNameCallbacks; + typedef std::map NameGroups; + NameGroups groups; + + UniqueNames uniqueNames(other.m_uniqueNames); + + for(Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i) + { + groups[(*i).second.c_str()].push_back((*i).first); + } + + for(NameGroups::iterator i = groups.begin(); i != groups.end(); ++i) + { + name_t uniqueName(uniqueNames.make_unique(name_read((*i).first.c_str()))); + uniqueNames.insert(uniqueName); + + char buffer[1024]; + name_write(buffer, uniqueName); + + //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n"; + + SetNameCallbacks& setNameCallbacks = (*i).second; + + for(SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j) + { + (*j)(buffer); + } + } + } +}; + +BasicNamespace g_defaultNamespace; +BasicNamespace g_cloneNamespace; + +class NamespaceAPI +{ + Namespace* m_namespace; +public: + typedef Namespace Type; + STRING_CONSTANT(Name, "*"); + + NamespaceAPI() + { + m_namespace = &g_defaultNamespace; + } + Namespace* getTable() + { + return m_namespace; + } +}; + +typedef SingletonModule NamespaceModule; +typedef Static StaticNamespaceModule; +StaticRegisterModule staticRegisterDefaultNamespace(StaticNamespaceModule::instance()); + + +std::list g_cloned; + +inline Namespaced* Node_getNamespaced(scene::Node& node) +{ + return NodeTypeCast::cast(node); +} + +void Node_gatherNamespaced(scene::Node& node) +{ + Namespaced* namespaced = Node_getNamespaced(node); + if(namespaced != 0) + { + g_cloned.push_back(namespaced); + } +} + +class GatherNamespaced : public scene::Traversable::Walker +{ +public: + bool pre(scene::Node& node) const + { + Node_gatherNamespaced(node); + return true; + } +}; + +void Map_gatherNamespaced(scene::Node& root) +{ + Node_traverseSubgraph(root, GatherNamespaced()); +} + +void Map_mergeClonedNames() +{ + for(std::list::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i) + { + (*i)->setNamespace(g_cloneNamespace); + } + g_cloneNamespace.mergeNames(g_defaultNamespace); + for(std::list::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i) + { + (*i)->setNamespace(g_defaultNamespace); + } + + g_cloned.clear(); +} + +class WorldNode +{ + scene::Node* m_node; +public: + WorldNode() + : m_node(0) + { + } + void set(scene::Node* node) + { + if(m_node != 0) + m_node->DecRef(); + m_node = node; + if(m_node != 0) + m_node->IncRef(); + } + scene::Node* get() const + { + return m_node; + } +}; + +class Map; +void Map_SetValid(Map& map, bool valid); +void Map_UpdateTitle(const Map& map); +void Map_SetWorldspawn(Map& map, scene::Node* node); + + +class Map : public ModuleObserver +{ +public: + CopiedString m_name; + Resource* m_resource; + bool m_valid; + + bool m_modified; + void (*m_modified_changed)(const Map&); + + Signal0 m_mapValidCallbacks; + + WorldNode m_world_node; // "classname" "worldspawn" ! + + Map() : m_resource(0), m_valid(false), m_modified_changed(Map_UpdateTitle) + { + } + + void realise() + { + if(m_resource != 0) + { + if(Map_Unnamed(*this)) + { + g_map.m_resource->setNode(NewMapRoot("").get_pointer()); + MapFile* map = Node_getMapFile(*g_map.m_resource->getNode()); + if(map != 0) + { + map->save(); + } + } + else + { + m_resource->load(); + } + + GlobalSceneGraph().insert_root(*m_resource->getNode()); + + AutoSave_clear(); + + Map_SetValid(g_map, true); + } + } + void unrealise() + { + if(m_resource != 0) + { + Map_SetValid(g_map, false); + Map_SetWorldspawn(g_map, 0); + + + GlobalUndoSystem().clear(); + + GlobalSceneGraph().erase_root(); + } + } +}; + +Map g_map; +Map* g_currentMap = 0; + +void Map_addValidCallback(Map& map, const SignalHandler& handler) +{ + map.m_mapValidCallbacks.connectLast(handler); +} + +bool Map_Valid(const Map& map) +{ + return map.m_valid; +} + +void Map_SetValid(Map& map, bool valid) +{ + map.m_valid = valid; + map.m_mapValidCallbacks(); +} + + +const char* Map_Name(const Map& map) +{ + return map.m_name.c_str(); +} + +bool Map_Unnamed(const Map& map) +{ + return string_equal(Map_Name(map), "unnamed.map"); +} + +inline const MapFormat& MapFormat_forFile(const char* filename) +{ + const char* moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), path_get_extension(filename)); + MapFormat* format = Radiant_getMapModules().findModule(moduleName); + ASSERT_MESSAGE(format != 0, "map format not found for file " << makeQuoted(filename)); + return *format; +} + +const MapFormat& Map_getFormat(const Map& map) +{ + return MapFormat_forFile(Map_Name(map)); +} + + +bool Map_Modified(const Map& map) +{ + return map.m_modified; +} + +void Map_SetModified(Map& map, bool modified) +{ + if(map.m_modified ^ modified) + { + map.m_modified = modified; + + map.m_modified_changed(map); + } +} + +void Map_UpdateTitle(const Map& map) +{ + Sys_SetTitle(map.m_name.c_str(), Map_Modified(map)); +} + + + +scene::Node* Map_GetWorldspawn(const Map& map) +{ + return map.m_world_node.get(); +} + +void Map_SetWorldspawn(Map& map, scene::Node* node) +{ + map.m_world_node.set(node); +} + + +// TTimo +// need that in a variable, will have to tweak depending on the game +float g_MaxWorldCoord = 64*1024; +float g_MinWorldCoord = -64*1024; + +void AddRegionBrushes (void); +void RemoveRegionBrushes (void); + + +/* +================ +Map_Free +free all map elements, reinitialize the structures that depend on them +================ +*/ +void Map_Free() +{ + Pointfile_Clear(); + + g_map.m_resource->detach(g_map); + GlobalReferenceCache().release(g_map.m_name.c_str()); + g_map.m_resource = 0; + + FlushReferences(); + + g_currentMap = 0; +} + +class EntityFindByClassname : public scene::Graph::Walker +{ + const char* m_name; + Entity*& m_entity; +public: + EntityFindByClassname(const char* name, Entity*& entity) : m_name(name), m_entity(entity) + { + m_entity = 0; + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(m_entity == 0) + { + Entity* entity = Node_getEntity(path.top()); + if(entity != 0 + && string_equal(m_name, entity->getKeyValue("classname"))) + { + m_entity = entity; + } + } + return true; + } +}; + +Entity* Scene_FindEntityByClass(const char* name) +{ + Entity* entity; + GlobalSceneGraph().traverse(EntityFindByClassname(name, entity)); + return entity; +} + +Entity *Scene_FindPlayerStart() +{ + typedef const char* StaticString; + StaticString strings[] = { + "info_player_start", + "info_player_deathmatch", + "team_CTF_redplayer", + "team_CTF_blueplayer", + "team_CTF_redspawn", + "team_CTF_bluespawn", + }; + typedef const StaticString* StaticStringIterator; + for(StaticStringIterator i = strings, end = strings+(sizeof(strings)/sizeof(StaticString)); i != end; ++i) + { + Entity* entity = Scene_FindEntityByClass(*i); + if(entity != 0) + { + return entity; + } + } + return 0; +} + +// +// move the view to a start position +// + + +void FocusViews(const Vector3& point, float angle) +{ + CamWnd& camwnd = *g_pParentWnd->GetCamWnd(); + Camera_setOrigin(camwnd, point); + Vector3 angles(Camera_getAngles(camwnd)); + angles[CAMERA_PITCH] = 0; + angles[CAMERA_YAW] = angle; + Camera_setAngles(camwnd, angles); + + XYWnd* xywnd = g_pParentWnd->GetXYWnd(); + xywnd->SetOrigin(point); +} + +#include "stringio.h" + +void Map_StartPosition() +{ + Entity* entity = Scene_FindPlayerStart(); + + if (entity) + { + Vector3 origin; + string_parse_vector3(entity->getKeyValue("origin"), origin); + FocusViews(origin, string_read_float(entity->getKeyValue("angle"))); + } + else + { + FocusViews(g_vector3_identity, 0); + } +} + + +inline bool node_is_worldspawn(scene::Node& node) +{ + Entity* entity = Node_getEntity(node); + return entity != 0 && string_equal(entity->getKeyValue("classname"), "worldspawn"); +} + + +// use first worldspawn +class entity_updateworldspawn : public scene::Traversable::Walker +{ +public: + bool pre(scene::Node& node) const + { + if(node_is_worldspawn(node)) + { + if(Map_GetWorldspawn(g_map) == 0) + { + Map_SetWorldspawn(g_map, &node); + } + } + return false; + } +}; + +scene::Node* Map_FindWorldspawn(Map& map) +{ + Map_SetWorldspawn(map, 0); + + Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn()); + + return Map_GetWorldspawn(map); +} + + +class CollectAllWalker : public scene::Traversable::Walker +{ + scene::Node& m_root; + UnsortedNodeSet& m_nodes; +public: + CollectAllWalker(scene::Node& root, UnsortedNodeSet& nodes) : m_root(root), m_nodes(nodes) + { + } + bool pre(scene::Node& node) const + { + m_nodes.insert(NodeSmartReference(node)); + Node_getTraversable(m_root)->erase(node); + return false; + } +}; + +void Node_insertChildFirst(scene::Node& parent, scene::Node& child) +{ + UnsortedNodeSet nodes; + Node_getTraversable(parent)->traverse(CollectAllWalker(parent, nodes)); + Node_getTraversable(parent)->insert(child); + + for(UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i) + { + Node_getTraversable(parent)->insert((*i)); + } +} + +scene::Node& createWorldspawn() +{ + NodeSmartReference worldspawn(GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("worldspawn", true))); + Node_insertChildFirst(GlobalSceneGraph().root(), worldspawn); + return worldspawn; +} + +void Map_UpdateWorldspawn(Map& map) +{ + if(Map_FindWorldspawn(map) == 0) + { + Map_SetWorldspawn(map, &createWorldspawn()); + } +} + +scene::Node& Map_FindOrInsertWorldspawn(Map& map) +{ + Map_UpdateWorldspawn(map); + return *Map_GetWorldspawn(map); +} + + +class MapMergeAll : public scene::Traversable::Walker +{ + mutable scene::Path m_path; +public: + MapMergeAll(const scene::Path& root) + : m_path(root) + { + } + bool pre(scene::Node& node) const + { + Node_getTraversable(m_path.top())->insert(node); + m_path.push(makeReference(node)); + selectPath(m_path, true); + return false; + } + void post(scene::Node& node) const + { + m_path.pop(); + } +}; + +class MapMergeEntities : public scene::Traversable::Walker +{ + mutable scene::Path m_path; +public: + MapMergeEntities(const scene::Path& root) + : m_path(root) + { + } + bool pre(scene::Node& node) const + { + if(node_is_worldspawn(node)) + { + scene::Node* world_node = Map_FindWorldspawn(g_map); + if(world_node == 0) + { + Map_SetWorldspawn(g_map, &node); + Node_getTraversable(m_path.top().get())->insert(node); + m_path.push(makeReference(node)); + Node_getTraversable(node)->traverse(SelectChildren(m_path)); + } + else + { + m_path.push(makeReference(*world_node)); + Node_getTraversable(node)->traverse(MapMergeAll(m_path)); + } + } + else + { + Node_getTraversable(m_path.top())->insert(node); + m_path.push(makeReference(node)); + if(node_is_group(node)) + { + Node_getTraversable(node)->traverse(SelectChildren(m_path)); + } + else + { + selectPath(m_path, true); + } + } + return false; + } + void post(scene::Node& node) const + { + m_path.pop(); + } +}; + +class BasicContainer : public scene::Node::Symbiot +{ + class TypeCasts + { + NodeTypeCastTable m_casts; + public: + TypeCasts() + { + NodeContainedCast::install(m_casts); + } + NodeTypeCastTable& get() + { + return m_casts; + } + }; + + scene::Node m_node; + TraversableNodeSet m_traverse; +public: + + typedef LazyStatic StaticTypeCasts; + + scene::Traversable& get(NullType) + { + return m_traverse; + } + + BasicContainer() : m_node(this, this, StaticTypeCasts::instance().get()) + { + } + void release() + { + delete this; + } + scene::Node& node() + { + return m_node; + } +}; + +/// Merges the map graph rooted at \p node into the global scene-graph. +void MergeMap(scene::Node& node) +{ + Node_getTraversable(node)->traverse(MapMergeEntities(scene::Path(makeReference(GlobalSceneGraph().root())))); +} +void Map_ImportSelected(TextInputStream& in, const MapFormat& format) +{ + NodeSmartReference node((new BasicContainer)->node()); + format.readGraph(node, in, GlobalEntityCreator()); + Map_gatherNamespaced(node); + Map_mergeClonedNames(); + MergeMap(node); +} + +inline scene::Cloneable* Node_getCloneable(scene::Node& node) +{ + return NodeTypeCast::cast(node); +} + +inline scene::Node& node_clone(scene::Node& node) +{ + scene::Cloneable* cloneable = Node_getCloneable(node); + if(cloneable != 0) + { + return cloneable->clone(); + } + + return (new scene::NullNode)->node(); +} + +class CloneAll : public scene::Traversable::Walker +{ + mutable scene::Path m_path; +public: + CloneAll(scene::Node& root) + : m_path(makeReference(root)) + { + } + bool pre(scene::Node& node) const + { + if(node.isRoot()) + { + return false; + } + + m_path.push(makeReference(node_clone(node))); + m_path.top().get().IncRef(); + + return true; + } + void post(scene::Node& node) const + { + if(node.isRoot()) + { + return; + } + + Node_getTraversable(m_path.parent())->insert(m_path.top()); + + m_path.top().get().DecRef(); + m_path.pop(); + } +}; + +scene::Node& Node_Clone(scene::Node& node) +{ + scene::Node& clone = node_clone(node); + scene::Traversable* traversable = Node_getTraversable(node); + if(traversable != 0) + { + traversable->traverse(CloneAll(clone)); + } + return clone; +} + + +typedef std::map EntityBreakdown; + +class EntityBreakdownWalker : public scene::Graph::Walker +{ + EntityBreakdown& m_entitymap; +public: + EntityBreakdownWalker(EntityBreakdown& entitymap) + : m_entitymap(entitymap) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Entity* entity = Node_getEntity(path.top()); + if(entity != 0) + { + const EntityClass& eclass = entity->getEntityClass(); + if(m_entitymap.find(eclass.name()) == m_entitymap.end()) + { + m_entitymap[eclass.name()] = 1; + } + else ++m_entitymap[eclass.name()]; + } + return true; + } +}; + +void Scene_EntityBreakdown(EntityBreakdown& entitymap) +{ + GlobalSceneGraph().traverse(EntityBreakdownWalker(entitymap)); +} + + +WindowPosition g_posMapInfoWnd(c_default_window_pos); + +void DoMapInfo() +{ + ModalDialog dialog; + GtkEntry* brushes_entry; + GtkEntry* entities_entry; + GtkListStore* EntityBreakdownWalker; + + GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Map Info", G_CALLBACK(dialog_delete_callback), &dialog); + + window_set_position(window, g_posMapInfoWnd); + + { + GtkVBox* vbox = create_dialog_vbox(4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox)); + + { + GtkHBox* hbox = create_dialog_hbox(4); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, TRUE, 0); + + { + GtkTable* table = create_dialog_table(2, 2, 4, 4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(table), TRUE, TRUE, 0); + + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_entry_set_editable(entry, FALSE); + + brushes_entry = entry; + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_entry_set_editable(entry, FALSE); + + entities_entry = entry; + } + { + GtkWidget* label = gtk_label_new ("Total Brushes"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkWidget* label = gtk_label_new ("Total Entities"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + } + { + GtkVBox* vbox2 = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox2), FALSE, FALSE, 0); + + { + GtkButton* button = create_dialog_button("Close", G_CALLBACK(dialog_button_ok), &dialog); + gtk_box_pack_start(GTK_BOX(vbox2), GTK_WIDGET(button), FALSE, FALSE, 0); + } + } + } + { + GtkWidget* label = gtk_label_new ("Entity breakdown"); + gtk_widget_show (label); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(label), FALSE, TRUE, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkScrolledWindow* scr = create_scrolled_window(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4); + gtk_box_pack_start(GTK_BOX (vbox), GTK_WIDGET(scr), TRUE, TRUE, 0); + + { + GtkListStore* store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); + + GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(view), TRUE); + + { + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("Entity", renderer, "text", 0, 0); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + gtk_tree_view_column_set_sort_column_id(column, 0); + } + + { + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("Count", renderer, "text", 1, 0); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + gtk_tree_view_column_set_sort_column_id(column, 1); + } + + gtk_widget_show(view); + + gtk_container_add(GTK_CONTAINER(scr), view); + + EntityBreakdownWalker = store; + } + } + } + + // Initialize fields + + { + EntityBreakdown entitymap; + Scene_EntityBreakdown(entitymap); + + for(EntityBreakdown::iterator i=entitymap.begin(); i != entitymap.end(); ++i) + { + char tmp[16]; + sprintf (tmp, "%u", Unsigned((*i).second)); + GtkTreeIter iter; + gtk_list_store_append(GTK_LIST_STORE(EntityBreakdownWalker), &iter); + gtk_list_store_set(GTK_LIST_STORE(EntityBreakdownWalker), &iter, 0, (*i).first.c_str(), 1, tmp, -1); + } + } + + g_object_unref(G_OBJECT(EntityBreakdownWalker)); + + char tmp[16]; + sprintf (tmp, "%u", Unsigned(g_brushCount.get())); + gtk_entry_set_text (GTK_ENTRY (brushes_entry), tmp); + sprintf (tmp, "%u", Unsigned(g_entityCount.get())); + gtk_entry_set_text (GTK_ENTRY (entities_entry), tmp); + + modal_dialog_show(window, dialog); + + // save before exit + window_get_position(window, g_posMapInfoWnd); + + gtk_widget_destroy(GTK_WIDGET(window)); +} + + + +class ScopeTimer +{ + Timer m_timer; + const char* m_message; +public: + ScopeTimer(const char* message) + : m_message(message) + { + m_timer.start(); + } + ~ScopeTimer() + { + double elapsed_time = m_timer.elapsed_msec() / 1000.f; + globalOutputStream() << m_message << " timer: " << FloatFormat(elapsed_time, 5, 2) << " second(s) elapsed\n"; + } +}; + +/* +================ +Map_LoadFile +================ +*/ + +void Map_LoadFile (const char *filename) +{ + globalOutputStream() << "Loading map from " << filename << "\n"; + ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map"); + + g_map.m_name = filename; + Map_UpdateTitle(g_map); + + { + ScopeTimer timer("map load"); + + g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str()); + g_map.m_resource->attach(g_map); + + Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn()); + } + + globalOutputStream() << "--- LoadMapFile ---\n"; + globalOutputStream() << g_map.m_name.c_str() << "\n"; + + globalOutputStream() << makeLeftJustified(Unsigned(g_brushCount.get()), 5) << " primitive\n"; + globalOutputStream() << makeLeftJustified(Unsigned(g_entityCount.get()), 5) << " entities\n"; + + //GlobalEntityCreator().printStatistics(); + + // + // move the view to a start position + // + Map_StartPosition(); + + g_currentMap = &g_map; +} + +class Excluder +{ +public: + virtual bool excluded(scene::Node& node) const = 0; +}; + +class ExcludeWalker : public scene::Traversable::Walker +{ + const scene::Traversable::Walker& m_walker; + const Excluder* m_exclude; + mutable bool m_skip; +public: + ExcludeWalker(const scene::Traversable::Walker& walker, const Excluder& exclude) + : m_walker(walker), m_exclude(&exclude), m_skip(false) + { + } + bool pre(scene::Node& node) const + { + if(m_exclude->excluded(node) || node.isRoot()) + { + m_skip = true; + return false; + } + else + { + m_walker.pre(node); + } + return true; + } + void post(scene::Node& node) const + { + if(m_skip) + { + m_skip = false; + } + else + { + m_walker.post(node); + } + } +}; + +class AnyInstanceSelected : public scene::Instantiable::Visitor +{ + bool& m_selected; +public: + AnyInstanceSelected(bool& selected) : m_selected(selected) + { + m_selected = false; + } + void visit(scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 + && selectable->isSelected()) + { + m_selected = true; + } + } +}; + +bool Node_instanceSelected(scene::Node& node) +{ + scene::Instantiable* instantiable = Node_getInstantiable(node); + ASSERT_NOTNULL(instantiable); + bool selected; + instantiable->forEachInstance(AnyInstanceSelected(selected)); + return selected; +} + +class SelectedDescendantWalker : public scene::Traversable::Walker +{ + bool& m_selected; +public: + SelectedDescendantWalker(bool& selected) : m_selected(selected) + { + m_selected = false; + } + + bool pre(scene::Node& node) const + { + if(node.isRoot()) + { + return false; + } + + if(Node_instanceSelected(node)) + { + m_selected = true; + } + + return true; + } +}; + +bool Node_selectedDescendant(scene::Node& node) +{ + bool selected; + Node_traverseSubgraph(node, SelectedDescendantWalker(selected)); + return selected; +} + +class SelectionExcluder : public Excluder +{ +public: + bool excluded(scene::Node& node) const + { + return !Node_selectedDescendant(node); + } +}; + +class IncludeSelectedWalker : public scene::Traversable::Walker +{ + const scene::Traversable::Walker& m_walker; + mutable std::size_t m_selected; + mutable bool m_skip; + + bool selectedParent() const + { + return m_selected != 0; + } +public: + IncludeSelectedWalker(const scene::Traversable::Walker& walker) + : m_walker(walker), m_selected(0), m_skip(false) + { + } + bool pre(scene::Node& node) const + { + // include node if: + // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected ) + if(!node.isRoot() && (Node_selectedDescendant(node) || selectedParent())) + { + if(Node_instanceSelected(node)) + { + ++m_selected; + } + m_walker.pre(node); + return true; + } + else + { + m_skip = true; + return false; + } + } + void post(scene::Node& node) const + { + if(m_skip) + { + m_skip = false; + } + else + { + if(Node_instanceSelected(node)) + { + --m_selected; + } + m_walker.post(node); + } + } +}; + +void Map_Traverse_Selected(scene::Node& root, const scene::Traversable::Walker& walker) +{ + scene::Traversable* traversable = Node_getTraversable(root); + if(traversable != 0) + { +#if 0 + traversable->traverse(ExcludeWalker(walker, SelectionExcluder())); +#else + traversable->traverse(IncludeSelectedWalker(walker)); +#endif + } +} + +void Map_ExportSelected(TextOutputStream& out, const MapFormat& format) +{ + format.writeGraph(GlobalSceneGraph().root(), Map_Traverse_Selected, out); +} + +void Map_Traverse(scene::Node& root, const scene::Traversable::Walker& walker) +{ + scene::Traversable* traversable = Node_getTraversable(root); + if(traversable != 0) + { + traversable->traverse(walker); + } +} + +class RegionExcluder : public Excluder +{ +public: + bool excluded(scene::Node& node) const + { + return node.excluded(); + } +}; + +void Map_Traverse_Region(scene::Node& root, const scene::Traversable::Walker& walker) +{ + scene::Traversable* traversable = Node_getTraversable(root); + if(traversable != 0) + { + traversable->traverse(ExcludeWalker(walker, RegionExcluder())); + } +} + +bool Map_SaveRegion(const char *filename) +{ + AddRegionBrushes(); + + bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Region, filename); + + RemoveRegionBrushes(); + + return success; +} + + +void Map_RenameAbsolute(const char* absolute) +{ + Resource* resource = GlobalReferenceCache().capture(absolute); + NodeSmartReference clone(NewMapRoot(path_make_relative(absolute, GlobalFileSystem().findRoot(absolute)))); + resource->setNode(clone.get_pointer()); + + { + //ScopeTimer timer("clone subgraph"); + Node_getTraversable(GlobalSceneGraph().root())->traverse(CloneAll(clone)); + } + + g_map.m_resource->detach(g_map); + GlobalReferenceCache().release(g_map.m_name.c_str()); + + g_map.m_resource = resource; + + g_map.m_name = absolute; + Map_UpdateTitle(g_map); + + g_map.m_resource->attach(g_map); +} + +void Map_Rename(const char* filename) +{ + if(!string_equal(g_map.m_name.c_str(), filename)) + { + ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map"); + + Map_RenameAbsolute(filename); + + SceneChangeNotify(); + } + else + { + SaveReferences(); + } +} + +bool Map_Save() +{ + Pointfile_Clear(); + + ScopeTimer timer("map save"); + SaveReferences(); + return true; // assume success.. +} + +/* +=========== +Map_New + +=========== +*/ +void Map_New() +{ + //globalOutputStream() << "Map_New\n"; + + g_map.m_name = "unnamed.map"; + Map_UpdateTitle(g_map); + + { + g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str()); +// ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh"); + g_map.m_resource->attach(g_map); + + SceneChangeNotify(); + } + + FocusViews(g_vector3_identity, 0); + + g_currentMap = &g_map; +} + +extern void ConstructRegionBrushes(scene::Node* brushes[6], const Vector3& region_mins, const Vector3& region_maxs); + +void ConstructRegionStartpoint(scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs) +{ + /*! + \todo we need to make sure that the player start IS inside the region and bail out if it's not + the compiler will refuse to compile a map with a player_start somewhere in empty space.. + for now, let's just print an error + */ + + Vector3 vOrig(Camera_getOrigin(*g_pParentWnd->GetCamWnd())); + + for (int i=0 ; i<3 ; i++) + { + if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i]) + { + globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n"; + break; + } + } + + // write the info_playerstart + char sTmp[1024]; + sprintf(sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2]); + Node_getEntity(*startpoint)->setKeyValue("origin", sTmp); + sprintf(sTmp, "%d", (int)Camera_getAngles(*g_pParentWnd->GetCamWnd())[CAMERA_YAW]); + Node_getEntity(*startpoint)->setKeyValue("angle", sTmp); +} + +/* +=========================================================== + + REGION + +=========================================================== +*/ +bool region_active; +Vector3 region_mins(g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord); +Vector3 region_maxs(g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord); + +scene::Node* region_sides[6]; +scene::Node* region_startpoint = 0; + +/* +=========== +AddRegionBrushes +a regioned map will have temp walls put up at the region boundary +\todo TODO TTimo old implementation of region brushes + we still add them straight in the worldspawn and take them out after the map is saved + with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module +=========== +*/ +void AddRegionBrushes (void) +{ + int i; + + for(i=0; i<6; i++) + { + region_sides[i] = &GlobalBrushCreator().createBrush(); + Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(NodeSmartReference(*region_sides[i])); + } + + region_startpoint = &GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("info_player_start", false)); + + ConstructRegionBrushes(region_sides, region_mins, region_maxs); + ConstructRegionStartpoint(region_startpoint, region_mins, region_maxs); + + Node_getTraversable(GlobalSceneGraph().root())->insert(NodeSmartReference(*region_startpoint)); +} + +void RemoveRegionBrushes (void) +{ + for(std::size_t i=0; i<6; i++) + { + Node_getTraversable(*Map_GetWorldspawn(g_map))->erase(*region_sides[i]); + } + Node_getTraversable(GlobalSceneGraph().root())->erase(*region_startpoint); +} + +inline void exclude_node(scene::Node& node, bool exclude) +{ + exclude + ? node.enable(scene::Node::eExcluded) + : node.disable(scene::Node::eExcluded); +} + +class ExcludeAllWalker : public scene::Graph::Walker +{ + bool m_exclude; +public: + ExcludeAllWalker(bool exclude) + : m_exclude(exclude) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + exclude_node(path.top(), m_exclude); + + return true; + } +}; + +void Scene_Exclude_All(bool exclude) +{ + GlobalSceneGraph().traverse(ExcludeAllWalker(exclude)); +} + +bool Instance_isSelected(const scene::Instance& instance) +{ + const Selectable* selectable = Instance_getSelectable(instance); + return selectable != 0 && selectable->isSelected(); +} + +class ExcludeSelectedWalker : public scene::Graph::Walker +{ + bool m_exclude; +public: + ExcludeSelectedWalker(bool exclude) + : m_exclude(exclude) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + exclude_node(path.top(), (instance.isSelected() || instance.childSelected() || instance.parentSelected()) == m_exclude); + return true; + } +}; + +void Scene_Exclude_Selected(bool exclude) +{ + GlobalSceneGraph().traverse(ExcludeSelectedWalker(exclude)); +} + +class ExcludeRegionedWalker : public scene::Graph::Walker +{ + bool m_exclude; +public: + ExcludeRegionedWalker(bool exclude) + : m_exclude(exclude) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + exclude_node( + path.top(), + !( + ( + aabb_intersects_aabb( + instance.worldAABB(), + aabb_for_minmax(region_mins, region_maxs) + ) != 0 + ) ^ m_exclude + ) + ); + + return true; + } +}; + +void Scene_Exclude_Region(bool exclude) +{ + GlobalSceneGraph().traverse(ExcludeRegionedWalker(exclude)); +} + +/* +=========== +Map_RegionOff + +Other filtering options may still be on +=========== +*/ +void Map_RegionOff() +{ + region_active = false; + + region_maxs[0] = g_MaxWorldCoord - 64; + region_mins[0] = g_MinWorldCoord + 64; + region_maxs[1] = g_MaxWorldCoord - 64; + region_mins[1] = g_MinWorldCoord + 64; + region_maxs[2] = g_MaxWorldCoord - 64; + region_mins[2] = g_MinWorldCoord + 64; + + Scene_Exclude_All(false); +} + +void Map_ApplyRegion (void) +{ + region_active = true; + + Scene_Exclude_Region(false); +} + + +/* +======================== +Map_RegionSelectedBrushes +======================== +*/ +void Map_RegionSelectedBrushes (void) +{ + Map_RegionOff(); + + if(GlobalSelectionSystem().countSelected() != 0 + && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) + { + region_active = true; + Select_GetBounds (region_mins, region_maxs); + + Scene_Exclude_Selected(false); + + GlobalSelectionSystem().setSelectedAll(false); + } +} + + +/* +=========== +Map_RegionXY +=========== +*/ +void Map_RegionXY(float x_min, float y_min, float x_max, float y_max) +{ + Map_RegionOff(); + + region_mins[0] = x_min; + region_maxs[0] = x_max; + region_mins[1] = y_min; + region_maxs[1] = y_max; + region_mins[2] = g_MinWorldCoord + 64; + region_maxs[2] = g_MaxWorldCoord - 64; + + Map_ApplyRegion(); +} + +void Map_RegionBounds(const AABB& bounds) +{ + Map_RegionOff(); + + region_mins = vector3_subtracted(bounds.origin, bounds.extents); + region_maxs = vector3_added(bounds.origin, bounds.extents); + + deleteSelection(); + + Map_ApplyRegion(); +} + +/* +=========== +Map_RegionBrush +=========== +*/ +void Map_RegionBrush (void) +{ + if(GlobalSelectionSystem().countSelected() != 0) + { + scene::Instance& instance = GlobalSelectionSystem().ultimateSelected(); + Map_RegionBounds(instance.worldAABB()); + } +} + +// +//================ +//Map_ImportFile +//================ +// +bool Map_ImportFile(const char* filename) +{ + ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map"); + + bool success = false; + { + Resource* resource = GlobalReferenceCache().capture(filename); + resource->refresh(); // avoid loading old version if map has changed on disk since last import + if(resource->load()) + { + NodeSmartReference clone(NewMapRoot("")); + + { + //ScopeTimer timer("clone subgraph"); + Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone)); + } + + Map_gatherNamespaced(clone); + Map_mergeClonedNames(); + MergeMap(clone); + success = true; + } + GlobalReferenceCache().release(filename); + } + + SceneChangeNotify(); + + return success; +} + +/* +=========== +Map_SaveFile +=========== +*/ +bool Map_SaveFile(const char* filename) +{ + ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map"); + return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename); +} + +// +//=========== +//Map_SaveSelected +//=========== +// +// Saves selected world brushes and whole entities with partial/full selections +// +bool Map_SaveSelected(const char* filename) +{ + return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected, filename); +} + + +class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker +{ + scene::Node& m_parent; +public: + ParentSelectedBrushesToEntityWalker(scene::Node& parent) : m_parent(parent) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get_pointer() != &m_parent + && Node_isPrimitive(path.top())) + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 + && selectable->isSelected() + && path.size() > 1) + { + return false; + } + } + return true; + } + void post(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get_pointer() != &m_parent + && Node_isPrimitive(path.top())) + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 + && selectable->isSelected() + && path.size() > 1) + { + scene::Node& parent = path.parent(); + if(&parent != &m_parent) + { + NodeSmartReference node(path.top().get()); + Node_getTraversable(parent)->erase(node); + Node_getTraversable(m_parent)->insert(node); + } + } + } + } +}; + +void Scene_parentSelectedBrushesToEntity(scene::Graph& graph, scene::Node& parent) +{ + graph.traverse(ParentSelectedBrushesToEntityWalker(parent)); +} + +class CountSelectedBrushes : public scene::Graph::Walker +{ + std::size_t& m_count; + mutable std::size_t m_depth; +public: + CountSelectedBrushes(std::size_t& count) : m_count(count), m_depth(0) + { + m_count = 0; + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(++m_depth != 1 && path.top().get().isRoot()) + { + return false; + } + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 + && selectable->isSelected() + && Node_isPrimitive(path.top())) + { + ++m_count; + } + return true; + } + void post(const scene::Path& path, scene::Instance& instance) const + { + --m_depth; + } +}; + +std::size_t Scene_countSelectedBrushes(scene::Graph& graph) +{ + std::size_t count; + graph.traverse(CountSelectedBrushes(count)); + return count; +} + +enum ENodeType +{ + eNodeUnknown, + eNodeMap, + eNodeEntity, + eNodePrimitive, +}; + +const char* nodetype_get_name(ENodeType type) +{ + if(type == eNodeMap) + return "map"; + if(type == eNodeEntity) + return "entity"; + if(type == eNodePrimitive) + return "primitive"; + return "unknown"; +} + +ENodeType node_get_nodetype(scene::Node& node) +{ + if(Node_isEntity(node)) + { + return eNodeEntity; + } + if(Node_isPrimitive(node)) + { + return eNodePrimitive; + } + return eNodeUnknown; +} + +bool contains_entity(scene::Node& node) +{ + return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node); +} + +bool contains_primitive(scene::Node& node) +{ + return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer(); +} + +ENodeType node_get_contains(scene::Node& node) +{ + if(contains_entity(node)) + { + return eNodeEntity; + } + if(contains_primitive(node)) + { + return eNodePrimitive; + } + return eNodeUnknown; +} + +void Path_parent(const scene::Path& parent, const scene::Path& child) +{ + ENodeType contains = node_get_contains(parent.top()); + ENodeType type = node_get_nodetype(child.top()); + + if(contains != eNodeUnknown && contains == type) + { + NodeSmartReference node(child.top().get()); + Path_deleteTop(child); + Node_getTraversable(parent.top())->insert(node); + SceneChangeNotify(); + } + else + { + globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to " << nodetype_get_name(contains) << " container.\n"; + } +} + +void Scene_parentSelected() +{ + UndoableCommand undo("parentSelected"); + + if(GlobalSelectionSystem().countSelected() > 1) + { + class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor + { + const scene::Path& m_parent; + public: + ParentSelectedBrushesToEntityWalker(const scene::Path& parent) : m_parent(parent) + { + } + void visit(scene::Instance& instance) const + { + if(&m_parent != &instance.path()) + { + Path_parent(m_parent, instance.path()); + } + } + }; + + ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path()); + GlobalSelectionSystem().foreachSelected(visitor); + } + else + { + globalOutputStream() << "failed - did not find two selected nodes.\n"; + } +} + + + +void NewMap() +{ + if (ConfirmModified("New Map")) + { + Map_RegionOff(); + Map_Free(); + Map_New(); + } +} + +CopiedString g_mapsPath; + +const char* getMapsPath() +{ + return g_mapsPath.c_str(); +} + +const char* map_open(const char* title) +{ + return file_dialog(GTK_WIDGET(MainFrame_getWindow()), TRUE, title, getMapsPath(), MapFormat::Name()); +} + +const char* map_save(const char* title) +{ + return file_dialog(GTK_WIDGET(MainFrame_getWindow()), FALSE, title, getMapsPath(), MapFormat::Name()); +} + +void OpenMap() +{ + if (!ConfirmModified("Open Map")) + return; + + const char* filename = map_open("Open Map"); + + if (filename != 0) + { + MRU_AddFile(filename); + Map_RegionOff(); + Map_Free(); + Map_LoadFile(filename); + } +} + +void ImportMap() +{ + const char* filename = map_open("Import Map"); + + if(filename != 0) + { + UndoableCommand undo("mapImport"); + Map_ImportFile(filename); + } +} + +bool Map_SaveAs() +{ + const char* filename = map_save("Save Map"); + + if(filename != 0) + { + MRU_AddFile(filename); + Map_Rename(filename); + return Map_Save(); + } + return false; +} + +void SaveMapAs() +{ + Map_SaveAs(); +} + +void SaveMap() +{ + if(Map_Unnamed(g_map)) + { + SaveMapAs(); + } + else if(Map_Modified(g_map)) + { + Map_Save(); + } +} + +void ExportMap() +{ + const char* filename = map_save("Export Selection"); + + if(filename != 0) + { + Map_SaveSelected(filename); + } +} + +void SaveRegion() +{ + const char* filename = map_save("Export Region"); + + if(filename != 0) + { + Map_SaveRegion(filename); + } +} + + +void RegionOff() +{ + Map_RegionOff(); + SceneChangeNotify(); +} + +void RegionXY() +{ + Map_RegionXY( + g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(), + g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(), + g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(), + g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale() + ); + SceneChangeNotify(); +} + +void RegionBrush() +{ + Map_RegionBrush(); + SceneChangeNotify(); +} + +void RegionSelected() +{ + Map_RegionSelectedBrushes(); + SceneChangeNotify(); +} + + + + + +class BrushFindByIndexWalker : public scene::Traversable::Walker +{ + mutable std::size_t m_index; + scene::Path& m_path; +public: + BrushFindByIndexWalker(std::size_t index, scene::Path& path) + : m_index(index), m_path(path) + { + } + bool pre(scene::Node& node) const + { + if(Node_isPrimitive(node) && m_index-- == 0) + { + m_path.push(makeReference(node)); + } + return false; + } +}; + +class EntityFindByIndexWalker : public scene::Traversable::Walker +{ + mutable std::size_t m_index; + scene::Path& m_path; +public: + EntityFindByIndexWalker(std::size_t index, scene::Path& path) + : m_index(index), m_path(path) + { + } + bool pre(scene::Node& node) const + { + if(Node_isEntity(node) && m_index-- == 0) + { + m_path.push(makeReference(node)); + } + return false; + } +}; + +void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path& path) +{ + path.push(makeReference(GlobalSceneGraph().root())); + { + Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path)); + } + if(path.size() == 2) + { + scene::Traversable* traversable = Node_getTraversable(path.top()); + if(traversable != 0) + { + traversable->traverse(BrushFindByIndexWalker(brush, path)); + } + } +} + +inline bool Node_hasChildren(scene::Node& node) +{ + scene::Traversable* traversable = Node_getTraversable(node); + return traversable != 0 && !traversable->empty(); +} + +void SelectBrush (int entitynum, int brushnum) +{ + scene::Path path; + Scene_FindEntityBrush(entitynum, brushnum, path); + if(path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top()))) + { + scene::Instance* instance = GlobalSceneGraph().find(path); + ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph"); + Selectable* selectable = Instance_getSelectable(*instance); + ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable"); + selectable->setSelected(true); + g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin); + } +} + + +class BrushFindIndexWalker : public scene::Graph::Walker +{ + mutable const scene::Node* m_node; + std::size_t& m_count; +public: + BrushFindIndexWalker(const scene::Node& node, std::size_t& count) + : m_node(&node), m_count(count) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(Node_isPrimitive(path.top())) + { + if(m_node == path.top().get_pointer()) + { + m_node = 0; + } + if(m_node) + { + ++m_count; + } + } + return true; + } +}; + +class EntityFindIndexWalker : public scene::Graph::Walker +{ + mutable const scene::Node* m_node; + std::size_t& m_count; +public: + EntityFindIndexWalker(const scene::Node& node, std::size_t& count) + : m_node(&node), m_count(count) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(Node_isEntity(path.top())) + { + if(m_node == path.top().get_pointer()) + { + m_node = 0; + } + if(m_node) + { + ++m_count; + } + } + return true; + } +}; + +static void GetSelectionIndex (int *ent, int *brush) +{ + std::size_t count_brush = 0; + std::size_t count_entity = 0; + if(GlobalSelectionSystem().countSelected() != 0) + { + const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path(); + + GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush)); + GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity)); + } + *brush = int(count_brush); + *ent = int(count_entity); +} + +void DoFind() +{ + ModalDialog dialog; + GtkEntry* entity; + GtkEntry* brush; + + GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Find Brush", G_CALLBACK(dialog_delete_callback), &dialog); + + GtkAccelGroup* accel = gtk_accel_group_new(); + gtk_window_add_accel_group(window, accel); + + { + GtkVBox* vbox = create_dialog_vbox(4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox)); + { + GtkTable* table = create_dialog_table(2, 2, 4, 4); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(table), TRUE, TRUE, 0); + { + GtkWidget* label = gtk_label_new ("Entity number"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, + (GtkAttachOptions) (0), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkWidget* label = gtk_label_new ("Brush number"); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, + (GtkAttachOptions) (0), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_grab_focus(GTK_WIDGET(entry)); + entity = entry; + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + brush = entry; + } + } + { + GtkHBox* hbox = create_dialog_hbox(4); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), TRUE, TRUE, 0); + { + GtkButton* button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0); + } + { + GtkButton* button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0); + } + } + } + + // Initialize dialog + char buf[16]; + int ent, br; + + GetSelectionIndex (&ent, &br); + sprintf (buf, "%i", ent); + gtk_entry_set_text(entity, buf); + sprintf (buf, "%i", br); + gtk_entry_set_text(brush, buf); + + if(modal_dialog_show(window, dialog) == eIDOK) + { + const char *entstr = gtk_entry_get_text(entity); + const char *brushstr = gtk_entry_get_text(brush); + SelectBrush (atoi(entstr), atoi(brushstr)); + } + + gtk_widget_destroy(GTK_WIDGET(window)); +} + +void Map_constructPreferences(PreferencesPage& page) +{ + page.appendCheckBox("", "Load last map on open", g_bLoadLastMap); +} + + +class MapEntityClasses : public ModuleObserver +{ + std::size_t m_unrealised; +public: + MapEntityClasses() : m_unrealised(1) + { + } + void realise() + { + if(--m_unrealised == 0) + { + if(g_map.m_resource != 0) + { + ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map"); + g_map.m_resource->realise(); + } + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + if(g_map.m_resource != 0) + { + g_map.m_resource->flush(); + g_map.m_resource->unrealise(); + } + } + } +}; + +MapEntityClasses g_MapEntityClasses; + + +class MapModuleObserver : public ModuleObserver +{ + std::size_t m_unrealised; +public: + MapModuleObserver() : m_unrealised(1) + { + } + void realise() + { + if(--m_unrealised == 0) + { + ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()), "maps_directory: user-game-path is empty"); + StringOutputStream buffer(256); + buffer << g_qeglobals.m_userGamePath.c_str() << "maps/"; + Q_mkdir(buffer.c_str()); + g_mapsPath = buffer.c_str(); + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + g_mapsPath = ""; + } + } +}; + +MapModuleObserver g_MapModuleObserver; + +#include "preferencesystem.h" + +CopiedString g_strLastMap; +bool g_bLoadLastMap = false; + +void Map_Construct() +{ + GlobalCommands_insert("RegionOff", FreeCaller()); + GlobalCommands_insert("RegionSetXY", FreeCaller()); + GlobalCommands_insert("RegionSetBrush", FreeCaller()); + GlobalCommands_insert("RegionSetSelection", FreeCaller(), Accelerator('R', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + + GlobalPreferenceSystem().registerPreference("LastMap", CopiedStringImportStringCaller(g_strLastMap), CopiedStringExportStringCaller(g_strLastMap)); + GlobalPreferenceSystem().registerPreference("LoadLastMap", BoolImportStringCaller(g_bLoadLastMap), BoolExportStringCaller(g_bLoadLastMap)); + GlobalPreferenceSystem().registerPreference("MapInfoDlg", WindowPositionImportStringCaller(g_posMapInfoWnd), WindowPositionExportStringCaller(g_posMapInfoWnd)); + + PreferencesDialog_addSettingsPreferences(FreeCaller1()); + + GlobalEntityClassManager().attach(g_MapEntityClasses); + Radiant_attachHomePathsObserver(g_MapModuleObserver); +} + +void Map_Destroy() +{ + Radiant_detachHomePathsObserver(g_MapModuleObserver); + GlobalEntityClassManager().detach(g_MapEntityClasses); +} diff --git a/radiant/map.h b/radiant/map.h new file mode 100644 index 00000000..2529bd2c --- /dev/null +++ b/radiant/map.h @@ -0,0 +1,173 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MAP_H) +#define INCLUDED_MAP_H + +#include "iscenegraph.h" +#include "generic/callback.h" +#include "signal/signalfwd.h" +#include "string/stringfwd.h" + +class Map; +extern Map g_map; + +class MapFormat; + +void Map_addValidCallback(Map& map, const SignalHandler& handler); +bool Map_Valid(const Map& map); + +class DeferredDraw +{ + Callback m_draw; + bool m_defer; + bool m_deferred; +public: + DeferredDraw(const Callback& draw) : m_draw(draw), m_defer(false), m_deferred(false) + { + } + void defer() + { + m_defer = true; + } + void draw() + { + if(m_defer) + { + m_deferred = true; + } + else + { + m_draw(); + } + } + void flush() + { + if(m_defer && m_deferred) + { + m_draw(); + } + m_deferred = false; + m_defer = false; + } +}; + +inline void DeferredDraw_onMapValidChanged(DeferredDraw& self) +{ + if(Map_Valid(g_map)) + { + self.flush(); + } + else + { + self.defer(); + } +} +typedef ReferenceCaller DeferredDrawOnMapValidChangedCaller; + + + +const char* Map_Name(const Map& map); +const MapFormat& Map_getFormat(const Map& map); +bool Map_Unnamed(const Map& map); + + +namespace scene +{ + class Node; + class Graph; +} + +scene::Node* Map_GetWorldspawn(const Map& map); +scene::Node* Map_FindWorldspawn(Map& map); +scene::Node& Map_FindOrInsertWorldspawn(Map& map); + +template class BasicVector3; +typedef BasicVector3 Vector3; + +extern Vector3 region_mins, region_maxs; +extern bool region_active; + +// used to be #defines, multiple engine support suggests we should go towards dynamic +extern float g_MaxWorldCoord; +extern float g_MinWorldCoord; + +void Map_LoadFile(const char* filename); +bool Map_SaveFile(const char* filename); + +void Map_New(); +void Map_Free(); + +void Map_RegionOff(); + +bool Map_SaveRegion(const char* filename); + +class TextInputStream; +class TextOutputStream; + +void Map_ImportSelected(TextInputStream& in, const MapFormat& format); +void Map_ExportSelected(TextOutputStream& out, const MapFormat& format); + +bool Map_Modified(const Map& map); +void Map_SetModified(Map& map, bool modified); + +bool Map_Save(); +bool Map_SaveAs(); + +scene::Node& Node_Clone(scene::Node& node); + +void DoMapInfo(); + +void Scene_parentSelectedBrushesToEntity(scene::Graph& graph, scene::Node& parent); +std::size_t Scene_countSelectedBrushes(scene::Graph& graph); + +void Scene_parentSelected(); + +void OnUndoSizeChanged(); + +void NewMap(); +void OpenMap(); +void ImportMap(); +void SaveMapAs(); +void SaveMap(); +void ExportMap(); +void SaveRegion(); + + +void Map_Traverse(scene::Node& root, const scene::Traversable::Walker& walker); + + +void SelectBrush (int entitynum, int brushnum); + +extern CopiedString g_strLastMap; +extern bool g_bLoadLastMap; + +void Map_Construct(); +void Map_Destroy(); + + +void Map_gatherNamespaced(scene::Node& root); +void Map_mergeClonedNames(); + + +const char* getMapsPath(); + +#endif diff --git a/radiant/mru.cpp b/radiant/mru.cpp new file mode 100644 index 00000000..7f3fbeeb --- /dev/null +++ b/radiant/mru.cpp @@ -0,0 +1,253 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "mru.h" + +#include +#include +#include + +#include "os/file.h" +#include "generic/callback.h" +#include "stream/stringstream.h" +#include "convert.h" + +#include "gtkutil/menu.h" +#include "map.h" +#include "qe3.h" + +#define MRU_MAX 4 +namespace { + GtkMenuItem *MRU_items[MRU_MAX]; + std::size_t MRU_used; + typedef CopiedString MRU_filename_t; + MRU_filename_t MRU_filenames[MRU_MAX]; + typedef const char* MRU_key_t; + MRU_key_t MRU_keys[MRU_MAX] = { "File0", "File1", "File2", "File3" }; +} + +inline const char* MRU_GetText(std::size_t index) +{ + return MRU_filenames[index].c_str(); +} + +class EscapedMnemonic +{ + StringBuffer m_buffer; +public: + EscapedMnemonic(std::size_t capacity) : m_buffer(capacity) + { + m_buffer.push_back('_'); + } + const char* c_str() const + { + return m_buffer.c_str(); + } + void push_back(char c) // not escaped + { + m_buffer.push_back(c); + } + std::size_t write(const char* buffer, std::size_t length) + { + for(const char* end = buffer + length; buffer != end; ++buffer) + { + if(*buffer == '_') + { + m_buffer.push_back('_'); + } + + m_buffer.push_back(*buffer); + } + return length; + } +}; + +template +inline EscapedMnemonic& operator<<(EscapedMnemonic& ostream, const T& t) +{ + return ostream_write(ostream, t); +} + + +void MRU_updateWidget(std::size_t index, const char *filename) +{ + EscapedMnemonic mnemonic(64); + mnemonic << Unsigned(index + 1) << "- " << ConvertLocaleToUTF8(filename); + gtk_label_set_text_with_mnemonic(GTK_LABEL(gtk_bin_get_child(GTK_BIN(MRU_items[index]))), mnemonic.c_str()); +} + +void MRU_SetText(std::size_t index, const char *filename) +{ + MRU_filenames[index] = filename; + MRU_updateWidget(index, filename); +} + +void MRU_AddFile (const char *str) +{ + std::size_t i; + const char* text; + + // check if file is already in our list + for (i = 0; i < MRU_used; i++) + { + text = MRU_GetText (i); + + if (strcmp (text, str) == 0) + { + // reorder menu + for(; i > 0; i--) + MRU_SetText(i, MRU_GetText (i-1)); + + MRU_SetText(0, str); + + return; + } + } + + if (MRU_used < MRU_MAX) + MRU_used++; + + // move items down + for (i = MRU_used-1; i > 0; i--) + MRU_SetText (i, MRU_GetText (i-1)); + + MRU_SetText (0, str); + gtk_widget_set_sensitive(GTK_WIDGET(MRU_items[0]), TRUE); + gtk_widget_show(GTK_WIDGET(MRU_items[MRU_used-1])); +} + +void MRU_Init() +{ + if(MRU_used > MRU_MAX) + MRU_used = MRU_MAX; +} + +void MRU_AddWidget(GtkMenuItem *widget, std::size_t pos) +{ + if(pos < MRU_MAX) + { + MRU_items[pos] = widget; + if(pos < MRU_used) + { + MRU_updateWidget(pos, MRU_GetText(pos)); + gtk_widget_set_sensitive(GTK_WIDGET(MRU_items[0]), TRUE); + gtk_widget_show(GTK_WIDGET(MRU_items[pos])); + } + } +} + +void MRU_Activate (std::size_t index) +{ + char text[1024]; + strcpy(text, MRU_GetText(index)); + + if (file_readable(text)) //\todo Test 'map load succeeds' instead of 'file is readable'. + { + MRU_AddFile (text); + Map_RegionOff(); + Map_Free(); + Map_LoadFile (text); + } + else + { + MRU_used--; + + for (std::size_t i = index; i < MRU_used; i++) + MRU_SetText (i, MRU_GetText (i+1)); + + if (MRU_used == 0) + { + gtk_label_set_text(GTK_LABEL(GTK_BIN(MRU_items[0])->child), "Recent Files"); + gtk_widget_set_sensitive(GTK_WIDGET(MRU_items[0]), FALSE); + } + else + { + gtk_widget_hide(GTK_WIDGET(MRU_items[MRU_used])); + } + } +} + + +class LoadMRU +{ + std::size_t m_number; +public: + LoadMRU(std::size_t number) + : m_number(number) + { + } + void load() + { + if (ConfirmModified("Open Map")) + { + MRU_Activate(m_number - 1); + } + } +}; + +typedef MemberCaller LoadMRUCaller; + +LoadMRU g_load_mru1(1); +LoadMRU g_load_mru2(2); +LoadMRU g_load_mru3(3); +LoadMRU g_load_mru4(4); + +void MRU_constructMenu(GtkMenu* menu) +{ + { + GtkMenuItem* item = create_menu_item_with_mnemonic(menu, "_1", LoadMRUCaller(g_load_mru1)); + gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE); + MRU_AddWidget(item, 0); + } + { + GtkMenuItem* item = create_menu_item_with_mnemonic(menu, "_2", LoadMRUCaller(g_load_mru2)); + gtk_widget_hide(GTK_WIDGET(item)); + MRU_AddWidget(item, 1); + } + { + GtkMenuItem* item = create_menu_item_with_mnemonic(menu, "_3", LoadMRUCaller(g_load_mru3)); + gtk_widget_hide(GTK_WIDGET(item)); + MRU_AddWidget(item, 2); + } + { + GtkMenuItem* item = create_menu_item_with_mnemonic(menu, "_4", LoadMRUCaller(g_load_mru4)); + gtk_widget_hide(GTK_WIDGET(item)); + MRU_AddWidget(item, 3); + } +} + +#include "preferencesystem.h" +#include "stringio.h" + +void MRU_Construct() +{ + GlobalPreferenceSystem().registerPreference("Count", SizeImportStringCaller(MRU_used), SizeExportStringCaller(MRU_used)); + + for(std::size_t i = 0; i != MRU_MAX; ++i) + { + GlobalPreferenceSystem().registerPreference(MRU_keys[i], CopiedStringImportStringCaller(MRU_filenames[i]), CopiedStringExportStringCaller(MRU_filenames[i])); + } + + MRU_Init(); +} +void MRU_Destroy() +{ +} diff --git a/radiant/mru.h b/radiant/mru.h new file mode 100644 index 00000000..010e99b7 --- /dev/null +++ b/radiant/mru.h @@ -0,0 +1,33 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MRU_H) +#define INCLUDED_MRU_H + +void MRU_AddFile (const char *str); + +typedef struct _GtkMenu GtkMenu; +void MRU_constructMenu(GtkMenu* menu); + +void MRU_Construct(); +void MRU_Destroy(); + +#endif diff --git a/radiant/multimon.cpp b/radiant/multimon.cpp new file mode 100644 index 00000000..acc756fd --- /dev/null +++ b/radiant/multimon.cpp @@ -0,0 +1,108 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "multimon.h" + +#include "debugging/debugging.h" + +#include "gtkutil/window.h" +#include "preferences.h" + + +multimon_globals_t g_multimon_globals; + +LatchedBool g_Multimon_enableSysMenuPopups(false, "Floating windows sysmenu icons"); + +void MultiMonitor_constructPreferences(PreferencesPage& page) +{ + GtkWidget* primary_monitor = page.appendCheckBox("Multi Monitor", "Start on Primary Monitor", g_multimon_globals.m_bStartOnPrimMon); + GtkWidget* popup = page.appendCheckBox( + "", "Disable system menu on popup windows", + LatchedBoolImportCaller(g_Multimon_enableSysMenuPopups), + BoolExportCaller(g_Multimon_enableSysMenuPopups.m_latched) + ); + Widget_connectToggleDependency(popup, primary_monitor); +} + +#include "preferencesystem.h" +#include "stringio.h" + +#include + +namespace +{ + GdkRectangle primaryMonitor; +} + +void PositionWindowOnPrimaryScreen(WindowPosition& position) +{ + if( position.w >= primaryMonitor.width - 12 ) + { + position.w = primaryMonitor.width - 12; + } + if( position.h >= primaryMonitor.height - 24 ) + { + position.h = primaryMonitor.height - 48; + } + if( position.x <= primaryMonitor.x || position.x + position.w >= (primaryMonitor.x + primaryMonitor.width) - 12 ) + { + position.x = primaryMonitor.x + 6; + } + if( position.y <= primaryMonitor.y || position.y + position.h >= (primaryMonitor.y + primaryMonitor.height) - 48 ) + { + position.y = primaryMonitor.y + 24; + } +} + +void MultiMon_Construct() +{ + // detect multiple monitors + + GdkScreen* screen = gdk_display_get_default_screen(gdk_display_get_default()); + gint m = gdk_screen_get_n_monitors(screen); + globalOutputStream() << "default screen has " << m << " monitors\n"; + for(int j = 0; j != m; ++j) + { + GdkRectangle geom; + gdk_screen_get_monitor_geometry(screen, j, &geom); + globalOutputStream() << "monitor " << j << " geometry: " << geom.x << ", " << geom.y << ", " << geom.width << ", " << geom.height << "\n"; + if(j == 0) + { + // I am making the assumption that monitor 0 is always the primary monitor on win32. Tested on WinXP with gtk+-2.4. + primaryMonitor = geom; + } + } + + if(m > 1) + { + g_multimon_globals.m_bStartOnPrimMon = true; + } + + GlobalPreferenceSystem().registerPreference("StartOnPrimMon", BoolImportStringCaller(g_multimon_globals.m_bStartOnPrimMon), BoolExportStringCaller(g_multimon_globals.m_bStartOnPrimMon)); + GlobalPreferenceSystem().registerPreference("NoSysMenuPopups", BoolImportStringCaller(g_Multimon_enableSysMenuPopups.m_latched), BoolExportStringCaller(g_Multimon_enableSysMenuPopups.m_latched)); + + g_Multimon_enableSysMenuPopups.useLatched(); + + PreferencesDialog_addInterfacePreferences(FreeCaller1()); +} +void MultiMon_Destroy() +{ +} diff --git a/radiant/multimon.h b/radiant/multimon.h new file mode 100644 index 00000000..f14d3633 --- /dev/null +++ b/radiant/multimon.h @@ -0,0 +1,53 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_MULTIMON_H) +#define INCLUDED_MULTIMON_H + +struct WindowPosition; + +void PositionWindowOnPrimaryScreen(WindowPosition& position); + +struct multimon_globals_t +{ + bool m_bStartOnPrimMon; + + multimon_globals_t() : + m_bStartOnPrimMon(false) + { + } +}; + +extern multimon_globals_t g_multimon_globals; + +#if defined(WIN32) +void MultiMon_Construct(); +void MultiMon_Destroy(); +#else +inline void MultiMon_Construct() +{ +} +inline void MultiMon_Destroy() +{ +} +#endif + +#endif diff --git a/radiant/nullmodel.cpp b/radiant/nullmodel.cpp new file mode 100644 index 00000000..ed2333b4 --- /dev/null +++ b/radiant/nullmodel.cpp @@ -0,0 +1,214 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "nullmodel.h" + +#include "debugging/debugging.h" + +#include "iscenegraph.h" +#include "irender.h" +#include "iselection.h" +#include "iundo.h" +#include "ientity.h" +#include "ireference.h" +#include "igl.h" +#include "cullable.h" +#include "renderable.h" +#include "selectable.h" + +#include "math/frustum.h" +#include "scenelib.h" +#include "instancelib.h" +#include "entitylib.h" + +class NullModel : +public Bounded, +public Cullable +{ + Shader* m_state; + AABB m_aabb_local; + RenderableSolidAABB m_aabb_solid; + RenderableWireframeAABB m_aabb_wire; +public: + NullModel() : m_aabb_local(Vector3(0, 0, 0), Vector3(8, 8, 8)), m_aabb_solid(m_aabb_local), m_aabb_wire(m_aabb_local) + { + m_state = GlobalShaderCache().capture(""); + } + ~NullModel() + { + GlobalShaderCache().release(""); + } + + VolumeIntersectionValue intersectVolume(const VolumeTest& volume, const Matrix4& localToWorld) const + { + return volume.TestAABB(m_aabb_local, localToWorld); + } + + const AABB& localAABB() const + { + return m_aabb_local; + } + + void renderSolid(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld) const + { + renderer.SetState(m_state, Renderer::eFullMaterials); + renderer.addRenderable(m_aabb_solid, localToWorld); + } + void renderWireframe(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld) const + { + renderer.addRenderable(m_aabb_wire, localToWorld); + } + + void testSelect(Selector& selector, SelectionTest& test, const Matrix4& localToWorld) + { + test.BeginMesh(localToWorld); + + SelectionIntersection best; + aabb_testselect(m_aabb_local, test, best); + if(best.valid()) + { + selector.addIntersection(best); + } + } +}; + +class NullModelInstance : public scene::Instance, public Renderable, public SelectionTestable +{ + class TypeCasts + { + InstanceTypeCastTable m_casts; + public: + TypeCasts() + { + InstanceContainedCast::install(m_casts); + InstanceContainedCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + } + InstanceTypeCastTable& get() + { + return m_casts; + } + }; + + NullModel& m_nullmodel; +public: + + typedef LazyStatic StaticTypeCasts; + + Bounded& get(NullType) + { + return m_nullmodel; + } + Cullable& get(NullType) + { + return m_nullmodel; + } + + NullModelInstance(const scene::Path& path, scene::Instance* parent, NullModel& nullmodel) : + Instance(path, parent, this, StaticTypeCasts::instance().get()), + m_nullmodel(nullmodel) + { + } + + void renderSolid(Renderer& renderer, const VolumeTest& volume) const + { + m_nullmodel.renderSolid(renderer, volume, Instance::localToWorld()); + } + void renderWireframe(Renderer& renderer, const VolumeTest& volume) const + { + m_nullmodel.renderWireframe(renderer, volume, Instance::localToWorld()); + } + + void testSelect(Selector& selector, SelectionTest& test) + { + m_nullmodel.testSelect(selector, test, Instance::localToWorld()); + } +}; + +class NullModelNode : public scene::Node::Symbiot, public scene::Instantiable +{ + class TypeCasts + { + NodeTypeCastTable m_casts; + public: + TypeCasts() + { + NodeStaticCast::install(m_casts); + } + NodeTypeCastTable& get() + { + return m_casts; + } + }; + + + scene::Node m_node; + InstanceSet m_instances; + NullModel m_nullmodel; +public: + + typedef LazyStatic StaticTypeCasts; + + NullModelNode() : m_node(this, this, StaticTypeCasts::instance().get()) + { + m_node.m_isRoot = true; + } + + void release() + { + delete this; + } + scene::Node& node() + { + return m_node; + } + + scene::Instance* create(const scene::Path& path, scene::Instance* parent) + { + return new NullModelInstance(path, parent, m_nullmodel); + } + void forEachInstance(const scene::Instantiable::Visitor& visitor) + { + m_instances.forEachInstance(visitor); + } + void insert(scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance) + { + m_instances.insert(observer, path, instance); + } + scene::Instance* erase(scene::Instantiable::Observer* observer, const scene::Path& path) + { + return m_instances.erase(observer, path); + } +}; + +NodeSmartReference NewNullModel() +{ + return NodeSmartReference((new NullModelNode)->node()); +} + +void NullModel_construct() +{ +} +void NullModel_destroy() +{ +} + diff --git a/radiant/nullmodel.h b/radiant/nullmodel.h new file mode 100644 index 00000000..2713fb08 --- /dev/null +++ b/radiant/nullmodel.h @@ -0,0 +1,37 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_NULLMODEL_H) +#define INCLUDED_NULLMODEL_H + +namespace scene +{ + class Node; +} + +#include "generic/referencecounted.h" +typedef SmartReference > NodeSmartReference; +NodeSmartReference NewNullModel(); + +void NullModel_construct(); +void NullModel_destroy(); + +#endif diff --git a/radiant/parse.cpp b/radiant/parse.cpp new file mode 100644 index 00000000..8e3fd5c0 --- /dev/null +++ b/radiant/parse.cpp @@ -0,0 +1,51 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "parse.h" + +#include "script/scripttokeniser.h" +#include "script/scripttokenwriter.h" + +class ScriptLibraryAPI +{ + _QERScripLibTable m_scriptlibrary; +public: + typedef _QERScripLibTable Type; + STRING_CONSTANT(Name, "*"); + + ScriptLibraryAPI() + { + m_scriptlibrary.m_pfnNewScriptTokeniser = &NewScriptTokeniser; + m_scriptlibrary.m_pfnNewSimpleTokeniser = &NewSimpleTokeniser; + m_scriptlibrary.m_pfnNewSimpleTokenWriter = &NewSimpleTokenWriter; + } + _QERScripLibTable* getTable() + { + return &m_scriptlibrary; + } +}; + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +typedef SingletonModule ScriptLibraryModule; +typedef Static StaticScriptLibraryModule; +StaticRegisterModule staticRegisterScriptLibrary(StaticScriptLibraryModule::instance()); diff --git a/radiant/parse.h b/radiant/parse.h new file mode 100644 index 00000000..3e64113e --- /dev/null +++ b/radiant/parse.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_PARSE_H) +#define INCLUDED_PARSE_H + +#endif diff --git a/radiant/patch.cpp b/radiant/patch.cpp new file mode 100644 index 00000000..719e2412 --- /dev/null +++ b/radiant/patch.cpp @@ -0,0 +1,2831 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "patch.h" + +#include +#include "preferences.h" +#include "brush_primit.h" +#include "signal/signal.h" + + +Signal0 g_patchTextureChangedCallbacks; + +void Patch_addTextureChangedCallback(const SignalHandler& handler) +{ + g_patchTextureChangedCallbacks.connectLast(handler); +} + +void Patch_textureChanged() +{ + g_patchTextureChangedCallbacks(); +} + + +Shader* PatchInstance::m_state_selpoint; +Shader* Patch::m_state_ctrl; +Shader* Patch::m_state_lattice; +EPatchType Patch::m_type; + + +std::size_t MAX_PATCH_WIDTH = 0; +std::size_t MAX_PATCH_HEIGHT = 0; + +int g_PatchSubdivideThreshold = 4; + +void BezierCurveTree_Delete(BezierCurveTree *pCurve) +{ + if(pCurve) + { + BezierCurveTree_Delete(pCurve->left); + BezierCurveTree_Delete(pCurve->right); + delete pCurve; + } +} + +std::size_t BezierCurveTree_Setup(BezierCurveTree *pCurve, std::size_t index, std::size_t stride) +{ + if(pCurve) + { + if(pCurve->left && pCurve->right) + { + index = BezierCurveTree_Setup(pCurve->left, index, stride); + pCurve->index = index*stride; + index++; + index = BezierCurveTree_Setup(pCurve->right, index, stride); + } + else + { + pCurve->index = BEZIERCURVETREE_MAX_INDEX; + } + } + + return index; +} + +bool BezierCurve_IsCurved(BezierCurve *pCurve) +{ + Vector3 vTemp(vector3_subtracted(pCurve->right, pCurve->left)); + Vector3 v1(vector3_subtracted(pCurve->crd, pCurve->left)); + Vector3 v2(vector3_subtracted(pCurve->right, pCurve->crd)); + + if(vector3_equal(v1, g_vector3_identity) || vector3_equal(vTemp, v1)) // return 0 if 1->2 == 0 or 1->2 == 1->3 + return false; + + vector3_normalise(v1); + vector3_normalise(v2); + if(vector3_equal(v1, v2)) + return false; + + Vector3 v3(vTemp); + const double width = vector3_length(v3); + vector3_scale(v3, 1.0 / width); + + if(vector3_equal(v1, v3) && vector3_equal(v2, v3)) + return false; + + const double angle = acos(vector3_dot(v1, v2)) / c_pi; + + const double index = width * angle; + + if(index > static_cast(g_PatchSubdivideThreshold)) + return true; + return false; +} + +void BezierInterpolate(BezierCurve *pCurve) +{ + pCurve->left = vector3_mid(pCurve->left, pCurve->crd); + pCurve->right = vector3_mid(pCurve->crd, pCurve->right); + pCurve->crd = vector3_mid(pCurve->left, pCurve->right); +} + +const std::size_t PATCH_MAX_SUBDIVISION_DEPTH = 16; + +void BezierCurveTree_FromCurveList(BezierCurveTree *pTree, GSList *pCurveList, std::size_t depth = 0) +{ + GSList *pLeftList = 0; + GSList *pRightList = 0; + BezierCurve *pCurve, *pLeftCurve, *pRightCurve; + bool bSplit = false; + + for (GSList *l = pCurveList; l; l = l->next) + { + pCurve = (BezierCurve *)(l->data); + if(bSplit || BezierCurve_IsCurved(pCurve)) + { + bSplit = true; + pLeftCurve = new BezierCurve; + pRightCurve = new BezierCurve; + pLeftCurve->left = pCurve->left; + pRightCurve->right = pCurve->right; + BezierInterpolate(pCurve); + pLeftCurve->crd = pCurve->left; + pRightCurve->crd = pCurve->right; + pLeftCurve->right = pCurve->crd; + pRightCurve->left = pCurve->crd; + + pLeftList = g_slist_prepend(pLeftList, pLeftCurve); + pRightList = g_slist_prepend(pRightList, pRightCurve); + } + } + + if(pLeftList != 0 && pRightList != 0 && depth != PATCH_MAX_SUBDIVISION_DEPTH) + { + pTree->left = new BezierCurveTree; + pTree->right = new BezierCurveTree; + BezierCurveTree_FromCurveList(pTree->left, pLeftList, depth + 1); + BezierCurveTree_FromCurveList(pTree->right, pRightList, depth + 1); + + for(GSList* l = pLeftList; l != 0; l = g_slist_next(l)) + { + delete (BezierCurve*)l->data; + } + + for(GSList* l = pRightList; l != 0; l = g_slist_next(l)) + { + delete (BezierCurve*)l->data; + } + + g_slist_free(pLeftList); + g_slist_free(pRightList); + } + else + { + pTree->left = 0; + pTree->right = 0; + } +} + + +int Patch::m_CycleCapIndex = 0; + + +void Patch::setDims (std::size_t w, std::size_t h) +{ + if((w%2)==0) + w -= 1; + ASSERT_MESSAGE(w <= MAX_PATCH_WIDTH, "patch too wide"); + if(w > MAX_PATCH_WIDTH) + w = MAX_PATCH_WIDTH; + else if(w < MIN_PATCH_WIDTH) + w = MIN_PATCH_WIDTH; + + if((h%2)==0) + m_height -= 1; + ASSERT_MESSAGE(h <= MAX_PATCH_HEIGHT, "patch too tall"); + if(h > MAX_PATCH_HEIGHT) + h = MAX_PATCH_HEIGHT; + else if(h < MIN_PATCH_HEIGHT) + h = MIN_PATCH_HEIGHT; + + m_width = w; m_height = h; + + if(m_width * m_height != m_ctrl.size()) + { + m_ctrl.resize(m_width * m_height); + onAllocate(m_ctrl.size()); + } +} + +inline const Colour4b& colour_for_index(std::size_t i, std::size_t width) +{ + return (i%2 || (i/width)%2) ? colour_inside : colour_corner; +} + +inline bool float_valid(float f) +{ + return f == f; +} + +bool Patch::isValid() const +{ + if(!m_width || !m_height) + { + return false; + } + + for(const_iterator i = m_ctrl.begin(); i != m_ctrl.end(); ++i) + { + if(!float_valid((*i).m_vertex.x()) + || !float_valid((*i).m_vertex.y()) + || !float_valid((*i).m_vertex.z()) + || !float_valid((*i).m_texcoord.x()) + || !float_valid((*i).m_texcoord.y())) + { + globalErrorStream() << "patch has invalid control points\n"; + return false; + } + } + return true; +} + +void Patch::UpdateCachedData() +{ + m_ctrl_vertices.clear(); + m_lattice_indices.clear(); + + if(!isValid()) + { + m_tess.m_numStrips = 0; + m_tess.m_lenStrips = 0; + m_tess.m_nArrayHeight = 0; + m_tess.m_nArrayWidth = 0; + m_tess.m_curveTreeU.resize(0); + m_tess.m_curveTreeV.resize(0); + m_tess.m_indices.resize(0); + m_tess.m_vertices.resize(0); + m_tess.m_arrayHeight.resize(0); + m_tess.m_arrayWidth.resize(0); + m_aabb_local = AABB(); + return; + } + + BuildTesselationCurves(ROW); + BuildTesselationCurves(COL); + BuildVertexArray(); + AccumulateBBox(); + + IndexBuffer ctrl_indices; + + m_lattice_indices.reserve(((m_width * (m_height - 1)) + (m_height * (m_width - 1))) << 1); + ctrl_indices.reserve(m_ctrlTransformed.size()); + { + UniqueVertexBuffer inserter(m_ctrl_vertices); + for(iterator i = m_ctrlTransformed.begin(); i != m_ctrlTransformed.end(); ++i) + { + ctrl_indices.insert(inserter.insert(pointvertex_quantised(PointVertex(reinterpret_cast((*i).m_vertex), colour_for_index(i - m_ctrlTransformed.begin(), m_width))))); + } + } + { + for(IndexBuffer::iterator i = ctrl_indices.begin(); i != ctrl_indices.end(); ++i) + { + if(std::size_t(i - ctrl_indices.begin()) % m_width) + { + m_lattice_indices.insert(*(i - 1)); + m_lattice_indices.insert(*i); + } + if(std::size_t(i - ctrl_indices.begin()) >= m_width) + { + m_lattice_indices.insert(*(i - m_width)); + m_lattice_indices.insert(*i); + } + } + } + +#if 0 + { + Array::iterator first = m_tess.m_indices.begin(); + for(std::size_t s=0; s::iterator last = first + m_tess.m_lenStrips; + + for(Array::iterator i(first); i+2 != last; i += 2) + { + ArbitraryMeshTriangle_sumTangents(m_tess.m_vertices[*(i+0)], m_tess.m_vertices[*(i+1)], m_tess.m_vertices[*(i+2)]); + ArbitraryMeshTriangle_sumTangents(m_tess.m_vertices[*(i+2)], m_tess.m_vertices[*(i+1)], m_tess.m_vertices[*(i+3)]); + } + + first = last; + } + + for(Array::iterator i = m_tess.m_vertices.begin(); i != m_tess.m_vertices.end(); ++i) + { + vector3_normalise(reinterpret_cast((*i).tangent)); + vector3_normalise(reinterpret_cast((*i).bitangent)); + } + } +#endif + + SceneChangeNotify(); +} + +void Patch::InvertMatrix() +{ + undoSave(); + + PatchControlArray_invert(m_ctrl, m_width, m_height); + + controlPointsChanged(); +} + +void Patch::TransposeMatrix() +{ + undoSave(); + + { + Array tmp(m_width * m_height); + copy_ctrl(tmp.data(), m_ctrl.data(), m_ctrl.data() + m_width * m_height); + + PatchControlIter from = tmp.data(); + for(std::size_t h = 0; h != m_height; ++h) + { + PatchControlIter to = m_ctrl.data() + h; + for(std::size_t w = 0; w != m_width; ++w, ++from, to += m_height) + { + *to = *from; + } + } + } + + { + std::size_t tmp = m_width; + m_width = m_height; + m_height = tmp; + } + + controlPointsChanged(); +} + +void Patch::Redisperse(EMatrixMajor mt) +{ + std::size_t w, h, width, height, row_stride, col_stride; + PatchControl* p1, * p2, * p3; + + undoSave(); + + switch(mt) + { + case COL: + width = (m_width-1)>>1; + height = m_height; + col_stride = 1; + row_stride = m_width; + break; + case ROW: + width = (m_height-1)>>1; + height = m_width; + col_stride = m_width; + row_stride = 1; + break; + default: + ERROR_MESSAGE("neither row-major nor column-major"); + return; + } + + for(h=0;hm_vertex = vector3_mid(p1->m_vertex, p3->m_vertex); + p1 = p3; + } + } + + controlPointsChanged(); +} + +void Patch::InsertRemove(bool bInsert, bool bColumn, bool bFirst) +{ + undoSave(); + + if(bInsert) + { + if(bColumn && (m_width + 2 <= MAX_PATCH_WIDTH)) + InsertPoints(COL, bFirst); + else if(m_height + 2 <= MAX_PATCH_HEIGHT) + InsertPoints(ROW, bFirst); + } + else + { + if(bColumn && (m_width - 2 >= MIN_PATCH_WIDTH)) + RemovePoints(COL, bFirst); + else if(m_height - 2 >= MIN_PATCH_HEIGHT) + RemovePoints(ROW, bFirst); + } + + controlPointsChanged(); +} + +Patch* Patch::MakeCap(Patch* patch, EPatchCap eType, EMatrixMajor mt, bool bFirst) +{ + std::size_t i, width, height; + + switch(mt) + { + case ROW: + width = m_width; + height = m_height; + break; + case COL: + width = m_height; + height = m_width; + break; + default: + ERROR_MESSAGE("neither row-major nor column-major"); + return 0; + } + + Array p(width); + + std::size_t nIndex = (bFirst) ? 0 : height-1; + if(mt == ROW) + { + for (i=0; iConstructSeam(eType, p.data(), width); + return patch; +} + +void Patch::FlipTexture(int nAxis) +{ + undoSave(); + + for(PatchControlIter i = m_ctrl.data(); i != m_ctrl.data() + m_ctrl.size(); ++i) + { + (*i).m_texcoord[nAxis] = -(*i).m_texcoord[nAxis]; + } + + controlPointsChanged(); +} + +void Patch::TranslateTexture(float s, float t) +{ + undoSave(); + + s = -1 * s / m_state->getTexture().width; + t = t / m_state->getTexture().height; + + for(PatchControlIter i = m_ctrl.data(); i != m_ctrl.data() + m_ctrl.size(); ++i) + { + (*i).m_texcoord[0] += s; + (*i).m_texcoord[1] += t; + } + + controlPointsChanged(); +} + +void Patch::ScaleTexture(float s, float t) +{ + undoSave(); + + for(PatchControlIter i = m_ctrl.data(); i != m_ctrl.data() + m_ctrl.size(); ++i) + { + (*i).m_texcoord[0] *= s; + (*i).m_texcoord[1] *= t; + } + + controlPointsChanged(); +} + +void Patch::RotateTexture(float angle) +{ + undoSave(); + + const float s = static_cast(sin(degrees_to_radians(angle))); + const float c = static_cast(cos(degrees_to_radians(angle))); + + for(PatchControlIter i = m_ctrl.data(); i != m_ctrl.data() + m_ctrl.size(); ++i) + { + const float x = (*i).m_texcoord[0]; + const float y = (*i).m_texcoord[1]; + (*i).m_texcoord[0] = (x * c) - (y * s); + (*i).m_texcoord[1] = (y * c) + (x * s); + } + + controlPointsChanged(); +} + + +void Patch::SetTextureRepeat(float s, float t) +{ + std::size_t w, h; + float si, ti, sc, tc; + PatchControl *pDest; + + undoSave(); + + si = s / (float)(m_width - 1); + ti = t / (float)(m_height - 1); + + pDest = m_ctrl.data(); + for (h=0, tc = 0.0f; hm_texcoord[0] = sc; + pDest->m_texcoord[1] = tc; + pDest++; + } + } + + controlPointsChanged(); +} + +/* +void Patch::SetTextureInfo(texdef_t *pt) +{ + if(pt->getShift()[0] || pt->getShift()[1]) + TranslateTexture (pt->getShift()[0], pt->getShift()[1]); + else if(pt->getScale()[0] || pt->getScale()[1]) + { + if(pt->getScale()[0] == 0.0f) pt->setScale(0, 1.0f); + if(pt->getScale()[1] == 0.0f) pt->setScale(1, 1.0f); + ScaleTexture (pt->getScale()[0], pt->getScale()[1]); + } + else if(pt->rotate) + RotateTexture (pt->rotate); +} +*/ + +inline int texture_axis(const Vector3& normal) +{ + // axis dominance order: Z, X, Y + return (normal.x() >= normal.y()) ? (normal.x() > normal.z()) ? 0 : 2 : (normal.y() > normal.z()) ? 1 : 2; +} + +void Patch::CapTexture() +{ + const PatchControl& p1 = m_ctrl[m_width]; + const PatchControl& p2 = m_ctrl[m_width*(m_height-1)]; + const PatchControl& p3 = m_ctrl[(m_width*m_height)-1]; + + + Vector3 normal(g_vector3_identity); + + { + Vector3 tmp(vector3_cross( + vector3_subtracted(p2.m_vertex, m_ctrl[0].m_vertex), + vector3_subtracted(p3.m_vertex, m_ctrl[0].m_vertex) + )); + if(!vector3_equal(tmp, g_vector3_identity)) + { + vector3_add(normal, tmp); + } + } + { + Vector3 tmp(vector3_cross( + vector3_subtracted(p1.m_vertex, p3.m_vertex), + vector3_subtracted(m_ctrl[0].m_vertex, p3.m_vertex) + )); + if(!vector3_equal(tmp, g_vector3_identity)) + { + vector3_add(normal, tmp); + } + } + + ProjectTexture(texture_axis(normal)); +} + +// uses longest parallel chord to calculate texture coords for each row/col +void Patch::NaturalTexture() +{ + undoSave(); + + { + float fSize = (float)m_state->getTexture().width * Texdef_getDefaultTextureScale(); + + double texBest = 0; + double tex = 0; + PatchControl* pWidth = m_ctrl.data(); + for (std::size_t w=0; wm_texcoord[0] = static_cast(tex); + } + + if(w+1 == m_width) + break; + + { + PatchControl* pHeight = pWidth; + for (std::size_t h=0; hm_vertex, (pHeight+1)->m_vertex)); + double length = tex + (vector3_length(v) / fSize); + if(fabs(length) > texBest) texBest = length; + } + } + + tex=texBest; + } + } + + { + float fSize = -(float)m_state->getTexture().height * Texdef_getDefaultTextureScale(); + + double texBest = 0; + double tex = 0; + PatchControl* pHeight = m_ctrl.data(); + for (std::size_t h=0; hm_texcoord[1] = static_cast(tex); + } + + if(h+1 == m_height) + break; + + { + PatchControl* pWidth = pHeight; + for (std::size_t w=0; wm_vertex, (pWidth+m_width)->m_vertex)); + double length = tex + (vector3_length(v) / fSize); + if(fabs(length) > texBest) texBest = length; + } + } + + tex=texBest; + } + } + + controlPointsChanged(); +} + + + +// private: + +void Patch::AccumulateBBox() +{ + m_aabb_local = AABB(); + + for(PatchControlArray::iterator i = m_ctrlTransformed.begin(); i != m_ctrlTransformed.end(); ++i) + { + aabb_extend_by_point_safe(m_aabb_local, (*i).m_vertex); + } + + m_boundsChanged(); + m_lightsChanged(); +} + +void Patch::InsertPoints(EMatrixMajor mt, bool bFirst) +{ + std::size_t width, height, row_stride, col_stride; + + switch(mt) + { + case ROW: + col_stride = 1; + row_stride = m_width; + width = m_width; + height = m_height; + break; + case COL: + col_stride = m_width; + row_stride = 1; + width = m_height; + height = m_width; + break; + default: + ERROR_MESSAGE("neither row-major nor column-major"); + return; + } + + std::size_t pos = 0; + { + PatchControl* p1 = m_ctrl.data(); + for(std::size_t w = 0; w != width; ++w, p1 += col_stride) + { + { + PatchControl* p2 = p1; + for(std::size_t h = 1; h < height; h += 2, p2 += 2 * row_stride) + { + if(0)//p2->m_selectable.isSelected()) + { + pos = h; + break; + } + } + if(pos != 0) + { + break; + } + } + + { + PatchControl* p2 = p1; + for(std::size_t h = 0; h < height; h += 2, p2 += 2 * row_stride) + { + if(0)//p2->m_selectable.isSelected()) + { + pos = h; + break; + } + } + if(pos != 0) + { + break; + } + } + } + } + + Array tmp(m_ctrl); + + std::size_t row_stride2, col_stride2; + switch(mt) + { + case ROW: + setDims(m_width, m_height+2); + col_stride2 = 1; + row_stride2 = m_width; + break; + case COL: + setDims(m_width+2, m_height); + col_stride2 = m_width; + row_stride2 = 1; + break; + default: + ERROR_MESSAGE("neither row-major nor column-major"); + return; + } + + if(pos >= height) + { + if(bFirst) + { + pos = height - 1; + } + else + { + pos = 2; + } + } + else if(pos == 0) + { + pos = 2; + } + else if(pos % 2) + { + ++pos; + } + + + for(std::size_t w = 0; w != width; ++w) + { + PatchControl* p1 = tmp.data() + (w*col_stride); + PatchControl* p2 = m_ctrl.data() + (w*col_stride2); + for(std::size_t h = 0; h != height; ++h, p2 += row_stride2, p1 += row_stride) + { + if(h == pos) + { + p2 += 2 * row_stride2; + } + *p2 = *p1; + } + + p1 = tmp.data() + (w*col_stride+pos*row_stride); + p2 = m_ctrl.data() + (w*col_stride2+pos*row_stride2); + + PatchControl* r2a = (p2+row_stride2); + PatchControl* r2b = (p2-row_stride2); + PatchControl* c2a = (p1-2*row_stride); + PatchControl* c2b = (p1-row_stride); + + // set two new row points + *(p2+2*row_stride2) = *p1; + *r2a = *c2b; + + for(std::size_t i = 0; i != 3; ++i) + { + r2a->m_vertex[i] = float_mid(c2b->m_vertex[i], p1->m_vertex[i]); + + r2b->m_vertex[i] = float_mid(c2a->m_vertex[i], c2b->m_vertex[i]); + + p2->m_vertex[i] = float_mid(r2a->m_vertex[i], r2b->m_vertex[i]); + } + for(std::size_t i = 0; i != 2; ++i) + { + r2a->m_texcoord[i] = float_mid(c2b->m_texcoord[i], p1->m_texcoord[i]); + + r2b->m_texcoord[i] = float_mid(c2a->m_texcoord[i], c2b->m_texcoord[i]); + + p2->m_texcoord[i] = float_mid(r2a->m_texcoord[i], r2b->m_texcoord[i]); + } + } +} + +void Patch::RemovePoints(EMatrixMajor mt, bool bFirst) +{ + std::size_t width, height, row_stride, col_stride; + + switch(mt) + { + case ROW: + col_stride = 1; + row_stride = m_width; + width = m_width; + height = m_height; + break; + case COL: + col_stride = m_width; + row_stride = 1; + width = m_height; + height = m_width; + break; + default: + ERROR_MESSAGE("neither row-major nor column-major"); + return; + } + + std::size_t pos = 0; + { + PatchControl* p1 = m_ctrl.data(); + for(std::size_t w = 0; w != width; ++w, p1 += col_stride) + { + { + PatchControl* p2 = p1; + for(std::size_t h=1; h < height; h += 2, p2 += 2 * row_stride) + { + if(0)//p2->m_selectable.isSelected()) + { + pos = h; + break; + } + } + if(pos != 0) + { + break; + } + } + + { + PatchControl* p2 = p1; + for(std::size_t h=0; h < height; h += 2, p2 += 2 * row_stride) + { + if(0)//p2->m_selectable.isSelected()) + { + pos = h; + break; + } + } + if(pos != 0) + { + break; + } + } + } + } + + Array tmp(m_ctrl); + + std::size_t row_stride2, col_stride2; + switch(mt) + { + case ROW: + setDims(m_width, m_height-2); + col_stride2 = 1; + row_stride2 = m_width; + break; + case COL: + setDims(m_width-2, m_height); + col_stride2 = m_width; + row_stride2 = 1; + break; + default: + ERROR_MESSAGE("neither row-major nor column-major"); + return; + } + + if(pos >= height) + { + if(bFirst) + { + pos=height-3; + } + else + { + pos=2; + } + } + else if(pos == 0) + { + pos=2; + } + else if(pos > height - 3) + { + pos = height - 3; + } + else if(pos % 2) + { + ++pos; + } + + for(std::size_t w = 0; w != width; w++) + { + PatchControl* p1 = tmp.data() + (w*col_stride); + PatchControl* p2 = m_ctrl.data() + (w*col_stride2); + for(std::size_t h = 0; h != height; ++h, p2 += row_stride2, p1 += row_stride) + { + if(h == pos) + { + p1 += 2 * row_stride2; h += 2; + } + *p2 = *p1; + } + + p1 = tmp.data() + (w*col_stride+pos*row_stride); + p2 = m_ctrl.data() + (w*col_stride2+pos*row_stride2); + + for(std::size_t i=0; i<3; i++) + { + (p2-row_stride2)->m_vertex[i] = ((p1+2*row_stride)->m_vertex[i]+(p1-2*row_stride)->m_vertex[i]) * 0.5f; + + (p2-row_stride2)->m_vertex[i] = (p2-row_stride2)->m_vertex[i]+(2.0f * ((p1)->m_vertex[i]-(p2-row_stride2)->m_vertex[i])); + } + for(std::size_t i=0; i<2; i++) + { + (p2-row_stride2)->m_texcoord[i] = ((p1+2*row_stride)->m_texcoord[i]+(p1-2*row_stride)->m_texcoord[i]) * 0.5f; + + (p2-row_stride2)->m_texcoord[i] = (p2-row_stride2)->m_texcoord[i]+(2.0f * ((p1)->m_texcoord[i]-(p2-row_stride2)->m_texcoord[i])); + } + } +} + +void Patch::ConstructSeam(EPatchCap eType, Vector3* p, std::size_t width) +{ + switch(eType) + { + case eCapIBevel: + { + setDims(3, 3); + m_ctrl[0].m_vertex = p[0]; + m_ctrl[1].m_vertex = p[1]; + m_ctrl[2].m_vertex = p[1]; + m_ctrl[3].m_vertex = p[1]; + m_ctrl[4].m_vertex = p[1]; + m_ctrl[5].m_vertex = p[1]; + m_ctrl[6].m_vertex = p[2]; + m_ctrl[7].m_vertex = p[1]; + m_ctrl[8].m_vertex = p[1]; + } + break; + case eCapBevel: + { + setDims(3, 3); + Vector3 p3(vector3_added(p[2], vector3_subtracted(p[0], p[1]))); + m_ctrl[0].m_vertex = p3; + m_ctrl[1].m_vertex = p3; + m_ctrl[2].m_vertex = p[2]; + m_ctrl[3].m_vertex = p3; + m_ctrl[4].m_vertex = p3; + m_ctrl[5].m_vertex = p[1]; + m_ctrl[6].m_vertex = p3; + m_ctrl[7].m_vertex = p3; + m_ctrl[8].m_vertex = p[0]; + } + break; + case eCapEndCap: + { + Vector3 p5(vector3_mid(p[0], p[4])); + + setDims(3, 3); + m_ctrl[0].m_vertex = p[0]; + m_ctrl[1].m_vertex = p5; + m_ctrl[2].m_vertex = p[4]; + m_ctrl[3].m_vertex = p[1]; + m_ctrl[4].m_vertex = p[2]; + m_ctrl[5].m_vertex = p[3]; + m_ctrl[6].m_vertex = p[2]; + m_ctrl[7].m_vertex = p[2]; + m_ctrl[8].m_vertex = p[2]; + } + break; + case eCapIEndCap: + { + setDims(5, 3); + m_ctrl[0].m_vertex = p[4]; + m_ctrl[1].m_vertex = p[3]; + m_ctrl[2].m_vertex = p[2]; + m_ctrl[3].m_vertex = p[1]; + m_ctrl[4].m_vertex = p[0]; + m_ctrl[5].m_vertex = p[3]; + m_ctrl[6].m_vertex = p[3]; + m_ctrl[7].m_vertex = p[2]; + m_ctrl[8].m_vertex = p[1]; + m_ctrl[9].m_vertex = p[1]; + m_ctrl[10].m_vertex = p[3]; + m_ctrl[11].m_vertex = p[3]; + m_ctrl[12].m_vertex = p[2]; + m_ctrl[13].m_vertex = p[1]; + m_ctrl[14].m_vertex = p[1]; + } + break; + case eCapCylinder: + { + std::size_t mid = (width - 1) >> 1; + + bool degenerate = (mid % 2) != 0; + + std::size_t newHeight = mid + (degenerate ? 2 : 1); + + setDims(3, newHeight); + + if(degenerate) + { + ++mid; + for(std::size_t i = width; i != width + 2; ++i) + { + p[i] = p[width - 1]; + } + } + + { + PatchControl* pCtrl = m_ctrl.data(); + for(std::size_t i = 0; i != m_height; ++i, pCtrl += m_width) + { + pCtrl->m_vertex = p[i]; + } + } + { + PatchControl* pCtrl = m_ctrl.data() + 2; + std::size_t h = m_height - 1; + for(std::size_t i = 0; i != m_height; ++i, pCtrl += m_width) + { + pCtrl->m_vertex = p[h + (h - i)]; + } + } + + Redisperse(COL); + } + break; + default: + ERROR_MESSAGE("invalid patch-cap type"); + return; + } + CapTexture(); + controlPointsChanged(); +} + +void Patch::ProjectTexture(int nAxis) +{ + undoSave(); + + int s, t; + + switch (nAxis) + { + case 2: + s = 0; + t = 1; + break; + case 0: + s = 1; + t = 2; + break; + case 1: + s = 0; + t = 2; + break; + default: + ERROR_MESSAGE("invalid axis"); + return; + } + + float fWidth = 1 / (m_state->getTexture().width * Texdef_getDefaultTextureScale()); + float fHeight = 1 / (m_state->getTexture().height * -Texdef_getDefaultTextureScale()); + + for(PatchControlIter i = m_ctrl.data(); i != m_ctrl.data() + m_ctrl.size(); ++i) + { + (*i).m_texcoord[0] = (*i).m_vertex[s] * fWidth; + (*i).m_texcoord[1] = (*i).m_vertex[t] * fHeight; + } + + controlPointsChanged(); +} + +void Patch::constructPlane(const AABB& aabb, int axis, std::size_t width, std::size_t height) +{ + setDims(width, height); + + int x, y, z; + switch(axis) + { + case 2: x=0; y=1; z=2; break; + case 1: x=0; y=2; z=1; break; + case 0: x=1; y=2; z=0; break; + default: + ERROR_MESSAGE("invalid view-type"); + return; + } + + if(m_width < MIN_PATCH_WIDTH || m_width > MAX_PATCH_WIDTH) m_width = 3; + if(m_height < MIN_PATCH_HEIGHT || m_height > MAX_PATCH_HEIGHT) m_height = 3; + + Vector3 vStart; + vStart[x] = aabb.origin[x] - aabb.extents[x]; + vStart[y] = aabb.origin[y] - aabb.extents[y]; + vStart[z] = aabb.origin[z]; + + float xAdj = fabsf((vStart[x] - (aabb.origin[x] + aabb.extents[x])) / (float)(m_width - 1)); + float yAdj = fabsf((vStart[y] - (aabb.origin[y] + aabb.extents[y])) / (float)(m_height - 1)); + + Vector3 vTmp; + vTmp[z] = vStart[z]; + PatchControl* pCtrl = m_ctrl.data(); + + vTmp[y]=vStart[y]; + for (std::size_t h=0; hm_vertex = vTmp; + vTmp[x]+=xAdj; + } + vTmp[y]+=yAdj; + } + + NaturalTexture(); +} + +void Patch::ConstructPrefab(const AABB& aabb, EPatchPrefab eType, int axis, std::size_t width, std::size_t height) +{ + Vector3 vPos[3]; + + if(eType != ePlane) + { + vPos[0] = vector3_subtracted(aabb.origin, aabb.extents); + vPos[1] = aabb.origin; + vPos[2] = vector3_added(aabb.origin, aabb.extents); + } + + if(eType == ePlane) + { + constructPlane(aabb, axis, width, height); + } + else if(eType == eSqCylinder + || eType == eCylinder + || eType == eDenseCylinder + || eType == eVeryDenseCylinder + || eType == eCone + || eType == eSphere) + { + unsigned char *pIndex; + unsigned char pCylIndex[] = + { + 0, 0, + 1, 0, + 2, 0, + 2, 1, + 2, 2, + 1, 2, + 0, 2, + 0, 1, + 0, 0 + }; + + + PatchControl *pStart; + switch(eType) + { + case eSqCylinder: setDims(9, 3); + pStart = m_ctrl.data(); + break; + case eDenseCylinder: + case eVeryDenseCylinder: + case eCylinder: + setDims(9, 3); + pStart = m_ctrl.data() + 1; + break; + case eCone: setDims(9, 3); + pStart = m_ctrl.data() + 1; + break; + case eSphere: + setDims(9, 5); + pStart = m_ctrl.data() + (9+1); + break; + default: + ERROR_MESSAGE("this should be unreachable"); + return; + } + + for(std::size_t h=0; h<3; h++, pStart+=9) + { + pIndex = pCylIndex; + PatchControl* pCtrl = pStart; + for(std::size_t w=0; w<8; w++, pCtrl++) + { + pCtrl->m_vertex[0] = vPos[pIndex[0]][0]; + pCtrl->m_vertex[1] = vPos[pIndex[1]][1]; + pCtrl->m_vertex[2] = vPos[h][2]; + pIndex+=2; + } + } + + switch(eType) + { + case eSqCylinder: + { + PatchControl* pCtrl=m_ctrl.data(); + for(std::size_t h=0; h<3; h++, pCtrl+=9) + { + pCtrl[8].m_vertex = pCtrl[0].m_vertex; + } + } + break; + case eDenseCylinder: + case eVeryDenseCylinder: + case eCylinder: + { + PatchControl* pCtrl=m_ctrl.data(); + for (std::size_t h=0; h<3; h++, pCtrl+=9) + { + pCtrl[0].m_vertex = pCtrl[8].m_vertex; + } + } + break; + case eCone: + { + PatchControl* pCtrl=m_ctrl.data(); + for (std::size_t h=0; h<2; h++, pCtrl+=9) + { + pCtrl[0].m_vertex = pCtrl[8].m_vertex; + } + } + { + PatchControl* pCtrl=m_ctrl.data()+9*2; + for (std::size_t w=0; w<9; w++, pCtrl++) + { + pCtrl->m_vertex[0] = vPos[1][0]; + pCtrl->m_vertex[1] = vPos[1][1]; + pCtrl->m_vertex[2] = vPos[2][2]; + } + } + break; + case eSphere: + { + PatchControl* pCtrl=m_ctrl.data()+9; + for (std::size_t h=0; h<3; h++, pCtrl+=9) + { + pCtrl[0].m_vertex = pCtrl[8].m_vertex; + } + } + { + PatchControl* pCtrl = m_ctrl.data(); + for (std::size_t w=0; w<9; w++, pCtrl++) + { + pCtrl->m_vertex[0] = vPos[1][0]; + pCtrl->m_vertex[1] = vPos[1][1]; + pCtrl->m_vertex[2] = vPos[2][2]; + } + } + { + PatchControl* pCtrl = m_ctrl.data()+(9*4); + for (std::size_t w=0; w<9; w++, pCtrl++) + { + pCtrl->m_vertex[0] = vPos[1][0]; + pCtrl->m_vertex[1] = vPos[1][1]; + pCtrl->m_vertex[2] = vPos[2][2]; + } + } + default: + ERROR_MESSAGE("this should be unreachable"); + return; + } + } + else if (eType == eBevel) + { + unsigned char *pIndex; + unsigned char pBevIndex[] = + { + 0, 0, + 2, 0, + 2, 2, + }; + + setDims(3, 3); + + PatchControl* pCtrl = m_ctrl.data(); + for(std::size_t h=0; h<3; h++) + { + pIndex=pBevIndex; + for(std::size_t w=0; w<3; w++, pIndex+=2, pCtrl++) + { + pCtrl->m_vertex[0] = vPos[pIndex[0]][0]; + pCtrl->m_vertex[1] = vPos[pIndex[1]][1]; + pCtrl->m_vertex[2] = vPos[h][2]; + } + } + } + else if(eType == eEndCap) + { + unsigned char *pIndex; + unsigned char pEndIndex[] = + { + 2, 0, + 2, 2, + 1, 2, + 0, 2, + 0, 0, + }; + + setDims(5, 3); + + PatchControl* pCtrl = m_ctrl.data(); + for(std::size_t h=0; h<3; h++) + { + pIndex=pEndIndex; + for(std::size_t w=0; w<5; w++, pIndex+=2, pCtrl++) + { + pCtrl->m_vertex[0] = vPos[pIndex[0]][0]; + pCtrl->m_vertex[1] = vPos[pIndex[1]][1]; + pCtrl->m_vertex[2] = vPos[h][2]; + } + } + } + + if(eType == eDenseCylinder) + { + InsertRemove(true, false, true); + } + + if(eType == eVeryDenseCylinder) + { + InsertRemove(true, false, false); + InsertRemove(true, false, true); + } + + NaturalTexture(); +} + +void Patch::RenderDebug(RenderStateFlags state) const +{ + for (std::size_t i = 0; inormal)); + glTexCoord2fv(texcoord2f_to_array((m_tess.m_vertices.data() + m_tess.m_indices[i*m_tess.m_lenStrips+j])->texcoord)); + glVertex3fv(vertex3f_to_array((m_tess.m_vertices.data() + m_tess.m_indices[i*m_tess.m_lenStrips+j])->vertex)); + } + glEnd(); + } +} + +void RenderablePatchSolid::RenderNormals() const +{ + const std::size_t width = m_tess.m_numStrips+1; + const std::size_t height = m_tess.m_lenStrips>>1; + glBegin(GL_LINES); + for(std::size_t i=0;ivertex), + vector3_scaled(normal3f_to_vector3((m_tess.m_vertices.data() + (j*width+i))->normal), 8) + ) + ); + glVertex3fv(vertex3f_to_array((m_tess.m_vertices.data() + (j*width+i))->vertex)); + glVertex3fv(&vNormal[0]); + } + { + Vector3 vNormal( + vector3_added( + vertex3f_to_vector3((m_tess.m_vertices.data() + (j*width+i))->vertex), + vector3_scaled(normal3f_to_vector3((m_tess.m_vertices.data() + (j*width+i))->tangent), 8) + ) + ); + glVertex3fv(vertex3f_to_array((m_tess.m_vertices.data() + (j*width+i))->vertex)); + glVertex3fv(&vNormal[0]); + } + { + Vector3 vNormal( + vector3_added( + vertex3f_to_vector3((m_tess.m_vertices.data() + (j*width+i))->vertex), + vector3_scaled(normal3f_to_vector3((m_tess.m_vertices.data() + (j*width+i))->bitangent), 8) + ) + ); + glVertex3fv(vertex3f_to_array((m_tess.m_vertices.data() + (j*width+i))->vertex)); + glVertex3fv(&vNormal[0]); + } + } + } + glEnd(); +} + +#define DEGEN_0a 0x01 +#define DEGEN_1a 0x02 +#define DEGEN_2a 0x04 +#define DEGEN_0b 0x08 +#define DEGEN_1b 0x10 +#define DEGEN_2b 0x20 +#define SPLIT 0x40 +#define AVERAGE 0x80 + + +unsigned int subarray_get_degen(PatchControlIter subarray, std::size_t strideU, std::size_t strideV) +{ + unsigned int nDegen = 0; + const PatchControl* p1; + const PatchControl* p2; + + p1 = subarray; + p2 = p1 + strideU; + if(vector3_equal(p1->m_vertex, p2->m_vertex)) + nDegen |= DEGEN_0a; + p1 = p2; + p2 = p1 + strideU; + if(vector3_equal(p1->m_vertex, p2->m_vertex)) + nDegen |= DEGEN_0b; + + p1 = subarray + strideV; + p2 = p1 + strideU; + if(vector3_equal(p1->m_vertex, p2->m_vertex)) + nDegen |= DEGEN_1a; + p1 = p2; + p2 = p1 + strideU; + if(vector3_equal(p1->m_vertex, p2->m_vertex)) + nDegen |= DEGEN_1b; + + p1 = subarray + (strideV << 1); + p2 = p1 + strideU; + if(vector3_equal(p1->m_vertex, p2->m_vertex)) + nDegen |= DEGEN_2a; + p1 = p2; + p2 = p1 + strideU; + if(vector3_equal(p1->m_vertex, p2->m_vertex)) + nDegen |= DEGEN_2b; + + return nDegen; +} + + +inline void deCasteljau3(const Vector3& P0, const Vector3& P1, const Vector3& P2, Vector3& P01, Vector3& P12, Vector3& P012) +{ + P01 = vector3_mid(P0, P1); + P12 = vector3_mid(P1, P2); + P012 = vector3_mid(P01, P12); +} + +inline void BezierInterpolate3( const Vector3& start, Vector3& left, Vector3& mid, Vector3& right, const Vector3& end ) +{ + left = vector3_mid(start, mid); + right = vector3_mid(mid, end); + mid = vector3_mid(left, right); +} + +inline void BezierInterpolate2( const Vector2& start, Vector2& left, Vector2& mid, Vector2& right, const Vector2& end ) +{ + left[0]= float_mid(start[0], mid[0]); + left[1] = float_mid(start[1], mid[1]); + right[0] = float_mid(mid[0], end[0]); + right[1] = float_mid(mid[1], end[1]); + mid[0] = float_mid(left[0], right[0]); + mid[1] = float_mid(left[1], right[1]); +} + + +inline Vector2& texcoord_for_index(Array& vertices, std::size_t index) +{ + return reinterpret_cast(vertices[index].texcoord); +} + +inline Vector3& vertex_for_index(Array& vertices, std::size_t index) +{ + return reinterpret_cast(vertices[index].vertex); +} + +inline Vector3& normal_for_index(Array& vertices, std::size_t index) +{ + return reinterpret_cast(vertices[index].normal); +} + +inline Vector3& tangent_for_index(Array& vertices, std::size_t index) +{ + return reinterpret_cast(vertices[index].tangent); +} + +inline Vector3& bitangent_for_index(Array& vertices, std::size_t index) +{ + return reinterpret_cast(vertices[index].bitangent); +} + +inline const Vector2& texcoord_for_index(const Array& vertices, std::size_t index) +{ + return reinterpret_cast(vertices[index].texcoord); +} + +inline const Vector3& vertex_for_index(const Array& vertices, std::size_t index) +{ + return reinterpret_cast(vertices[index].vertex); +} + +inline const Vector3& normal_for_index(const Array& vertices, std::size_t index) +{ + return reinterpret_cast(vertices[index].normal); +} + +inline const Vector3& tangent_for_index(const Array& vertices, std::size_t index) +{ + return reinterpret_cast(vertices[index].tangent); +} + +inline const Vector3& bitangent_for_index(const Array& vertices, std::size_t index) +{ + return reinterpret_cast(vertices[index].bitangent); +} + +#include "math/curve.h" + +inline PatchControl QuadraticBezier_evaluate(const PatchControl* firstPoint, double t) +{ + PatchControl result = { Vector3(0, 0, 0), Vector2(0, 0) }; + double denominator = 0; + + { + double weight = BernsteinPolynomial::apply(t); + vector3_add(result.m_vertex, vector3_scaled(firstPoint[0].m_vertex, weight)); + vector2_add(result.m_texcoord, vector2_scaled(firstPoint[0].m_texcoord, weight)); + denominator += weight; + } + { + double weight = BernsteinPolynomial::apply(t); + vector3_add(result.m_vertex, vector3_scaled(firstPoint[1].m_vertex, weight)); + vector2_add(result.m_texcoord, vector2_scaled(firstPoint[1].m_texcoord, weight)); + denominator += weight; + } + { + double weight = BernsteinPolynomial::apply(t); + vector3_add(result.m_vertex, vector3_scaled(firstPoint[2].m_vertex, weight)); + vector2_add(result.m_texcoord, vector2_scaled(firstPoint[2].m_texcoord, weight)); + denominator += weight; + } + + vector3_divide(result.m_vertex, denominator); + vector2_divide(result.m_texcoord, denominator); + return result; +} + +inline Vector3 vector3_linear_interpolated(const Vector3& a, const Vector3& b, double t) +{ + return vector3_added(vector3_scaled(a, 1.0 - t), vector3_scaled(b, t)); +} + +inline Vector2 vector2_linear_interpolated(const Vector2& a, const Vector2& b, double t) +{ + return vector2_added(vector2_scaled(a, 1.0 - t), vector2_scaled(b, t)); +} + +void normalise_safe(Vector3& normal) +{ + if(!vector3_equal(normal, g_vector3_identity)) + { + vector3_normalise(normal); + } +} + +inline void QuadraticBezier_evaluate(const PatchControl& a, const PatchControl& b, const PatchControl& c, double t, PatchControl& point, PatchControl& left, PatchControl& right) +{ + left.m_vertex = vector3_linear_interpolated(a.m_vertex, b.m_vertex, t); + left.m_texcoord = vector2_linear_interpolated(a.m_texcoord, b.m_texcoord, t); + right.m_vertex = vector3_linear_interpolated(b.m_vertex, c.m_vertex, t); + right.m_texcoord = vector2_linear_interpolated(b.m_texcoord, c.m_texcoord, t); + point.m_vertex = vector3_linear_interpolated(left.m_vertex, right.m_vertex, t); + point.m_texcoord = vector2_linear_interpolated(left.m_texcoord, right.m_texcoord, t); +} + +void Patch::TesselateSubMatrixFixed(ArbitraryMeshVertex* vertices, std::size_t strideX, std::size_t strideY, unsigned int nFlagsX, unsigned int nFlagsY, PatchControl* subMatrix[3][3]) +{ + double incrementU = 1.0 / m_subdivisions_x; + double incrementV = 1.0 / m_subdivisions_y; + const std::size_t width = m_subdivisions_x + 1; + const std::size_t height = m_subdivisions_y + 1; + + for(std::size_t i = 0; i != width; ++i) + { + double tU = (i + 1 == width) ? 1 : i * incrementU; + PatchControl pointX[3]; + PatchControl leftX[3]; + PatchControl rightX[3]; + QuadraticBezier_evaluate(*subMatrix[0][0], *subMatrix[0][1], *subMatrix[0][2], tU, pointX[0], leftX[0], rightX[0]); + QuadraticBezier_evaluate(*subMatrix[1][0], *subMatrix[1][1], *subMatrix[1][2], tU, pointX[1], leftX[1], rightX[1]); + QuadraticBezier_evaluate(*subMatrix[2][0], *subMatrix[2][1], *subMatrix[2][2], tU, pointX[2], leftX[2], rightX[2]); + + ArbitraryMeshVertex* p = vertices + i * strideX; + for(std::size_t j = 0; j != height; ++j) + { + if((j == 0 || j + 1 == height) && (i == 0 || i + 1 == width)) + { + } + else + { + double tV = (j + 1 == height) ? 1 : j * incrementV; + + PatchControl pointY[3]; + PatchControl leftY[3]; + PatchControl rightY[3]; + QuadraticBezier_evaluate(*subMatrix[0][0], *subMatrix[1][0], *subMatrix[2][0], tV, pointY[0], leftY[0], rightY[0]); + QuadraticBezier_evaluate(*subMatrix[0][1], *subMatrix[1][1], *subMatrix[2][1], tV, pointY[1], leftY[1], rightY[1]); + QuadraticBezier_evaluate(*subMatrix[0][2], *subMatrix[1][2], *subMatrix[2][2], tV, pointY[2], leftY[2], rightY[2]); + + PatchControl point; + PatchControl left; + PatchControl right; + QuadraticBezier_evaluate(pointX[0], pointX[1], pointX[2], tV, point, left, right); + PatchControl up; + PatchControl down; + QuadraticBezier_evaluate(pointY[0], pointY[1], pointY[2], tU, point, up, down); + + vertex3f_to_vector3(p->vertex) = point.m_vertex; + texcoord2f_to_vector2(p->texcoord) = point.m_texcoord; + + ArbitraryMeshVertex a, b, c; + + a.vertex = vertex3f_for_vector3(left.m_vertex); + a.texcoord = texcoord2f_for_vector2(left.m_texcoord); + b.vertex = vertex3f_for_vector3(right.m_vertex); + b.texcoord = texcoord2f_for_vector2(right.m_texcoord); + + if(i != 0) + { + c.vertex = vertex3f_for_vector3(up.m_vertex); + c.texcoord = texcoord2f_for_vector2(up.m_texcoord); + } + else + { + c.vertex = vertex3f_for_vector3(down.m_vertex); + c.texcoord = texcoord2f_for_vector2(down.m_texcoord); + } + + Vector3 normal = vector3_normalised(vector3_cross(right.m_vertex - left.m_vertex, up.m_vertex - down.m_vertex)); + + Vector3 tangent, bitangent; + ArbitraryMeshTriangle_calcTangents(a, b, c, tangent, bitangent); + vector3_normalise(tangent); + vector3_normalise(bitangent); + + if(((nFlagsX & AVERAGE) != 0 && i == 0) || ((nFlagsY & AVERAGE) != 0 && j == 0)) + { + normal3f_to_vector3(p->normal) = vector3_normalised(vector3_added(normal3f_to_vector3(p->normal), normal)); + normal3f_to_vector3(p->tangent) = vector3_normalised(vector3_added(normal3f_to_vector3(p->tangent), tangent)); + normal3f_to_vector3(p->bitangent) = vector3_normalised(vector3_added(normal3f_to_vector3(p->bitangent), bitangent)); + } + else + { + normal3f_to_vector3(p->normal) = normal; + normal3f_to_vector3(p->tangent) = tangent; + normal3f_to_vector3(p->bitangent) = bitangent; + } + } + + p += strideY; + } + } +} + +void Patch::TesselateSubMatrix( const BezierCurveTree *BX, const BezierCurveTree *BY, + std::size_t offStartX, std::size_t offStartY, + std::size_t offEndX, std::size_t offEndY, + std::size_t nFlagsX, std::size_t nFlagsY, + Vector3& left, Vector3& mid, Vector3& right, + Vector2& texLeft, Vector2& texMid, Vector2& texRight, + bool bTranspose ) +{ + int newFlagsX, newFlagsY; + + Vector3 tmp; + Vector3 vertex_0_0, vertex_0_1, vertex_1_0, vertex_1_1, vertex_2_0, vertex_2_1; + Vector2 texTmp; + Vector2 texcoord_0_0, texcoord_0_1, texcoord_1_0, texcoord_1_1, texcoord_2_0, texcoord_2_1; + + { + // texcoords + + BezierInterpolate2( texcoord_for_index(m_tess.m_vertices, offStartX + offStartY), + texcoord_0_0, + texcoord_for_index(m_tess.m_vertices, BX->index + offStartY), + texcoord_0_1, + texcoord_for_index(m_tess.m_vertices, offEndX + offStartY) ); + + + BezierInterpolate2( texcoord_for_index(m_tess.m_vertices, offStartX + offEndY), + texcoord_2_0, + texcoord_for_index(m_tess.m_vertices, BX->index + offEndY), + texcoord_2_1, + texcoord_for_index(m_tess.m_vertices, offEndX + offEndY) ); + + texTmp = texMid; + + BezierInterpolate2(texLeft, + texcoord_1_0, + texTmp, + texcoord_1_1, + texRight); + + if(!BezierCurveTree_isLeaf(BY)) + { + texcoord_for_index(m_tess.m_vertices, BX->index + BY->index) = texTmp; + } + + + if(!BezierCurveTree_isLeaf(BX->left)) + { + texcoord_for_index(m_tess.m_vertices, BX->left->index + offStartY) = texcoord_0_0; + texcoord_for_index(m_tess.m_vertices, BX->left->index + offEndY) = texcoord_2_0; + + if(!BezierCurveTree_isLeaf(BY)) + { + texcoord_for_index(m_tess.m_vertices, BX->left->index + BY->index) = texcoord_1_0; + } + } + if(!BezierCurveTree_isLeaf(BX->right)) + { + texcoord_for_index(m_tess.m_vertices, BX->right->index + offStartY) = texcoord_0_1; + texcoord_for_index(m_tess.m_vertices, BX->right->index + offEndY) = texcoord_2_1; + + if(!BezierCurveTree_isLeaf(BY)) + { + texcoord_for_index(m_tess.m_vertices, BX->right->index + BY->index) = texcoord_1_1; + } + } + + + // verts + + BezierInterpolate3( vertex_for_index(m_tess.m_vertices, offStartX + offStartY), + vertex_0_0, + vertex_for_index(m_tess.m_vertices, BX->index + offStartY), + vertex_0_1, + vertex_for_index(m_tess.m_vertices, offEndX + offStartY) ); + + + BezierInterpolate3( vertex_for_index(m_tess.m_vertices, offStartX + offEndY), + vertex_2_0, + vertex_for_index(m_tess.m_vertices, BX->index + offEndY), + vertex_2_1, + vertex_for_index(m_tess.m_vertices, offEndX + offEndY) ); + + + tmp = mid; + + BezierInterpolate3( left, + vertex_1_0, + tmp, + vertex_1_1, + right ); + + if(!BezierCurveTree_isLeaf(BY)) + { + vertex_for_index(m_tess.m_vertices, BX->index + BY->index) = tmp; + } + + + if(!BezierCurveTree_isLeaf(BX->left)) + { + vertex_for_index(m_tess.m_vertices, BX->left->index + offStartY) = vertex_0_0; + vertex_for_index(m_tess.m_vertices, BX->left->index + offEndY) = vertex_2_0; + + if(!BezierCurveTree_isLeaf(BY)) + { + vertex_for_index(m_tess.m_vertices, BX->left->index + BY->index) = vertex_1_0; + } + } + if(!BezierCurveTree_isLeaf(BX->right)) + { + vertex_for_index(m_tess.m_vertices, BX->right->index + offStartY) = vertex_0_1; + vertex_for_index(m_tess.m_vertices, BX->right->index + offEndY) = vertex_2_1; + + if(!BezierCurveTree_isLeaf(BY)) + { + vertex_for_index(m_tess.m_vertices, BX->right->index + BY->index) = vertex_1_1; + } + } + + // normals + + if(nFlagsX & SPLIT) + { + ArbitraryMeshVertex a, b, c; + Vector3 tangentU; + + if(!(nFlagsX & DEGEN_0a) || !(nFlagsX & DEGEN_0b)) + { + tangentU = vector3_subtracted(vertex_0_1, vertex_0_0); + a.vertex = vertex3f_for_vector3(vertex_0_0); + a.texcoord = texcoord2f_for_vector2(texcoord_0_0); + c.vertex = vertex3f_for_vector3(vertex_0_1); + c.texcoord = texcoord2f_for_vector2(texcoord_0_1); + } + else if(!(nFlagsX & DEGEN_1a) || !(nFlagsX & DEGEN_1b)) + { + tangentU = vector3_subtracted(vertex_1_1, vertex_1_0); + a.vertex = vertex3f_for_vector3(vertex_1_0); + a.texcoord = texcoord2f_for_vector2(texcoord_1_0); + c.vertex = vertex3f_for_vector3(vertex_1_1); + c.texcoord = texcoord2f_for_vector2(texcoord_1_1); + } + else + { + tangentU = vector3_subtracted(vertex_2_1, vertex_2_0); + a.vertex = vertex3f_for_vector3(vertex_2_0); + a.texcoord = texcoord2f_for_vector2(texcoord_2_0); + c.vertex = vertex3f_for_vector3(vertex_2_1); + c.texcoord = texcoord2f_for_vector2(texcoord_2_1); + } + + Vector3 tangentV; + + if((nFlagsY & DEGEN_0a) && (nFlagsY & DEGEN_1a) && (nFlagsY & DEGEN_2a)) + { + tangentV = vector3_subtracted(vertex_for_index(m_tess.m_vertices, BX->index + offEndY), tmp); + b.vertex = vertex3f_for_vector3(tmp);//m_tess.m_vertices[BX->index + offEndY].vertex; + b.texcoord = texcoord2f_for_vector2(texTmp);//m_tess.m_vertices[BX->index + offEndY].texcoord; + } + else + { + tangentV = vector3_subtracted(tmp, vertex_for_index(m_tess.m_vertices, BX->index + offStartY)); + b.vertex = vertex3f_for_vector3(tmp);//m_tess.m_vertices[BX->index + offStartY].vertex; + b.texcoord = texcoord2f_for_vector2(texTmp); //m_tess.m_vertices[BX->index + offStartY].texcoord; + } + + + Vector3 normal, s, t; + ArbitraryMeshVertex& v = m_tess.m_vertices[offStartY + BX->index]; + Vector3& p = normal3f_to_vector3(v.normal); + Vector3& ps = normal3f_to_vector3(v.tangent); + Vector3& pt = normal3f_to_vector3(v.bitangent); + + if(bTranspose) + { + normal = vector3_cross(tangentV, tangentU); + } + else + { + normal = vector3_cross(tangentU, tangentV); + } + normalise_safe(normal); + + ArbitraryMeshTriangle_calcTangents(a, b, c, s, t); + normalise_safe(s); + normalise_safe(t); + + if(nFlagsX & AVERAGE) + { + p = vector3_normalised(vector3_added(p, normal)); + ps = vector3_normalised(vector3_added(ps, s)); + pt = vector3_normalised(vector3_added(pt, t)); + } + else + { + p = normal; + ps = s; + pt = t; + } + } + + { + ArbitraryMeshVertex a, b, c; + Vector3 tangentU; + + if(!(nFlagsX & DEGEN_2a) || !(nFlagsX & DEGEN_2b)) + { + tangentU = vector3_subtracted(vertex_2_1, vertex_2_0); + a.vertex = vertex3f_for_vector3(vertex_2_0); + a.texcoord = texcoord2f_for_vector2(texcoord_2_0); + c.vertex = vertex3f_for_vector3(vertex_2_1); + c.texcoord = texcoord2f_for_vector2(texcoord_2_1); + } + else if(!(nFlagsX & DEGEN_1a) || !(nFlagsX & DEGEN_1b)) + { + tangentU = vector3_subtracted(vertex_1_1, vertex_1_0); + a.vertex = vertex3f_for_vector3(vertex_1_0); + a.texcoord = texcoord2f_for_vector2(texcoord_1_0); + c.vertex = vertex3f_for_vector3(vertex_1_1); + c.texcoord = texcoord2f_for_vector2(texcoord_1_1); + } + else + { + tangentU = vector3_subtracted(vertex_0_1, vertex_0_0); + a.vertex = vertex3f_for_vector3(vertex_0_0); + a.texcoord = texcoord2f_for_vector2(texcoord_0_0); + c.vertex = vertex3f_for_vector3(vertex_0_1); + c.texcoord = texcoord2f_for_vector2(texcoord_0_1); + } + + Vector3 tangentV; + + if((nFlagsY & DEGEN_0b) && (nFlagsY & DEGEN_1b) && (nFlagsY & DEGEN_2b)) + { + tangentV = vector3_subtracted(tmp, vertex_for_index(m_tess.m_vertices, BX->index + offStartY)); + b.vertex = vertex3f_for_vector3(tmp);//m_tess.m_vertices[BX->index + offStartY].vertex; + b.texcoord = texcoord2f_for_vector2(texTmp);//m_tess.m_vertices[BX->index + offStartY].texcoord; + } + else + { + tangentV = vector3_subtracted(vertex_for_index(m_tess.m_vertices, BX->index + offEndY), tmp); + b.vertex = vertex3f_for_vector3(tmp);//m_tess.m_vertices[BX->index + offEndY].vertex; + b.texcoord = texcoord2f_for_vector2(texTmp);//m_tess.m_vertices[BX->index + offEndY].texcoord; + } + + ArbitraryMeshVertex& v = m_tess.m_vertices[offEndY+BX->index]; + Vector3& p = normal3f_to_vector3(v.normal); + Vector3& ps = normal3f_to_vector3(v.tangent); + Vector3& pt = normal3f_to_vector3(v.bitangent); + + if(bTranspose) + { + p = vector3_cross(tangentV, tangentU); + } + else + { + p = vector3_cross(tangentU, tangentV); + } + normalise_safe(p); + + ArbitraryMeshTriangle_calcTangents(a, b, c, ps, pt); + normalise_safe(ps); + normalise_safe(pt); + } + } + + + newFlagsX = newFlagsY = 0; + + if((nFlagsX & DEGEN_0a) && (nFlagsX & DEGEN_0b)) + { + newFlagsX |= DEGEN_0a; + newFlagsX |= DEGEN_0b; + } + if((nFlagsX & DEGEN_1a) && (nFlagsX & DEGEN_1b)) + { + newFlagsX |= DEGEN_1a; + newFlagsX |= DEGEN_1b; + } + if((nFlagsX & DEGEN_2a) && (nFlagsX & DEGEN_2b)) + { + newFlagsX |= DEGEN_2a; + newFlagsX |= DEGEN_2b; + } + if((nFlagsY & DEGEN_0a) && (nFlagsY & DEGEN_1a) && (nFlagsY & DEGEN_2a)) + { + newFlagsY |= DEGEN_0a; + newFlagsY |= DEGEN_1a; + newFlagsY |= DEGEN_2a; + } + if((nFlagsY & DEGEN_0b) && (nFlagsY & DEGEN_1b) && (nFlagsY & DEGEN_2b)) + { + newFlagsY |= DEGEN_0b; + newFlagsY |= DEGEN_1b; + newFlagsY |= DEGEN_2b; + } + + + //if((nFlagsX & DEGEN_0a) && (nFlagsX & DEGEN_1a) && (nFlagsX & DEGEN_2a)) { newFlagsX |= DEGEN_0a; newFlagsX |= DEGEN_1a; newFlagsX |= DEGEN_2a; } + //if((nFlagsX & DEGEN_0b) && (nFlagsX & DEGEN_1b) && (nFlagsX & DEGEN_2b)) { newFlagsX |= DEGEN_0b; newFlagsX |= DEGEN_1b; newFlagsX |= DEGEN_2b; } + + newFlagsX |= (nFlagsX & SPLIT); + newFlagsX |= (nFlagsX & AVERAGE); + + if(!BezierCurveTree_isLeaf(BY)) + { + { + int nTemp = newFlagsY; + + if((nFlagsY & DEGEN_0a) && (nFlagsY & DEGEN_0b)) + { + newFlagsY |= DEGEN_0a; + newFlagsY |= DEGEN_0b; + } + newFlagsY |= (nFlagsY & SPLIT); + newFlagsY |= (nFlagsY & AVERAGE); + + Vector3& p = vertex_for_index(m_tess.m_vertices, BX->index+BY->index); + Vector3 vTemp(p); + + Vector2& p2 = texcoord_for_index(m_tess.m_vertices, BX->index+BY->index); + Vector2 stTemp(p2); + + TesselateSubMatrix( BY, BX->left, + offStartY, offStartX, + offEndY, BX->index, + newFlagsY, newFlagsX, + vertex_0_0, vertex_1_0, vertex_2_0, + texcoord_0_0, texcoord_1_0, texcoord_2_0, + !bTranspose ); + + newFlagsY = nTemp; + p = vTemp; + p2 = stTemp; + } + + if((nFlagsY & DEGEN_2a) && (nFlagsY & DEGEN_2b)) { newFlagsY |= DEGEN_2a; newFlagsY |= DEGEN_2b; } + + TesselateSubMatrix( BY, BX->right, + offStartY, BX->index, + offEndY, offEndX, + newFlagsY, newFlagsX, + vertex_0_1, vertex_1_1, vertex_2_1, + texcoord_0_1, texcoord_1_1, texcoord_2_1, + !bTranspose ); + } + else + { + if(!BezierCurveTree_isLeaf(BX->left)) + { + TesselateSubMatrix( BX->left, BY, + offStartX, offStartY, + BX->index, offEndY, + newFlagsX, newFlagsY, + left, vertex_1_0, tmp, + texLeft, texcoord_1_0, texTmp, + bTranspose ); + } + + if(!BezierCurveTree_isLeaf(BX->right)) + { + TesselateSubMatrix( BX->right, BY, + BX->index, offStartY, + offEndX, offEndY, + newFlagsX, newFlagsY, + tmp, vertex_1_1, right, + texTmp, texcoord_1_1, texRight, + bTranspose ); + } + } + +} + +void Patch::BuildTesselationCurves(EMatrixMajor major) +{ + std::size_t nArrayStride, length, cross, strideU, strideV; + switch(major) + { + case ROW: + nArrayStride = 1; + length = (m_width - 1) >> 1; + cross = m_height; + strideU = 1; + strideV = m_width; + + if(!m_patchDef3) + { + BezierCurveTreeArray_deleteAll(m_tess.m_curveTreeU); + } + + break; + case COL: + nArrayStride = m_tess.m_nArrayWidth; + length = (m_height - 1) >> 1; + cross = m_width; + strideU = m_width; + strideV = 1; + + if(!m_patchDef3) + { + BezierCurveTreeArray_deleteAll(m_tess.m_curveTreeV); + } + + break; + default: + ERROR_MESSAGE("neither row-major nor column-major"); + return; + } + + Array arrayLength(length); + Array pCurveTree(length); + + std::size_t nArrayLength = 1; + + if(m_patchDef3) + { + for(Array::iterator i = arrayLength.begin(); i != arrayLength.end(); ++i) + { + *i = Array::value_type((major == ROW) ? m_subdivisions_x : m_subdivisions_y); + nArrayLength += *i; + } + } + else + { + // create a list of the horizontal control curves in each column of sub-patches + // adaptively tesselate each horizontal control curve in the list + // create a binary tree representing the combined tesselation of the list + for(std::size_t i = 0; i != length; ++i) + { + PatchControl* p1 = m_ctrlTransformed.data() + (i * 2 * strideU); + GSList* pCurveList = 0; + for(std::size_t j = 0; j < cross; j += 2) + { + PatchControl* p2 = p1+strideV; + PatchControl* p3 = p2+strideV; + + // directly taken from one row of control points + { + BezierCurve* pCurve = new BezierCurve; + pCurve->crd = (p1+strideU)->m_vertex; + pCurve->left = p1->m_vertex; + pCurve->right = (p1+(strideU<<1))->m_vertex; + pCurveList = g_slist_prepend(pCurveList, pCurve); + } + + if(j+2 >= cross) + { + break; + } + + // interpolated from three columns of control points + { + BezierCurve* pCurve = new BezierCurve; + pCurve->crd = vector3_mid((p1+strideU)->m_vertex, (p3+strideU)->m_vertex); + pCurve->left = vector3_mid(p1->m_vertex, p3->m_vertex); + pCurve->right = vector3_mid((p1+(strideU<<1))->m_vertex, (p3+(strideU<<1))->m_vertex); + + pCurve->crd = vector3_mid(pCurve->crd, (p2+strideU)->m_vertex); + pCurve->left = vector3_mid(pCurve->left, p2->m_vertex); + pCurve->right = vector3_mid(pCurve->right, (p2+(strideU<<1))->m_vertex); + pCurveList = g_slist_prepend(pCurveList, pCurve); + } + + p1 = p3; + } + + pCurveTree[i] = new BezierCurveTree; + BezierCurveTree_FromCurveList(pCurveTree[i], pCurveList); + for(GSList* l = pCurveList; l != 0; l = g_slist_next(l)) + { + delete static_cast((*l).data); + } + g_slist_free(pCurveList); + + // set up array indices for binary tree + // accumulate subarray width + arrayLength[i] = Array::value_type(BezierCurveTree_Setup(pCurveTree[i], nArrayLength, nArrayStride) - (nArrayLength - 1)); + // accumulate total array width + nArrayLength += arrayLength[i]; + } + } + + switch(major) + { + case ROW: + m_tess.m_nArrayWidth = nArrayLength; + std::swap(m_tess.m_arrayWidth, arrayLength); + + if(!m_patchDef3) + { + std::swap(m_tess.m_curveTreeU, pCurveTree); + } + break; + case COL: + m_tess.m_nArrayHeight = nArrayLength; + std::swap(m_tess.m_arrayHeight, arrayLength); + + if(!m_patchDef3) + { + std::swap(m_tess.m_curveTreeV, pCurveTree); + } + break; + } +} + +inline void vertex_assign_ctrl(ArbitraryMeshVertex& vertex, const PatchControl& ctrl) +{ + vertex.vertex = vertex3f_for_vector3(ctrl.m_vertex); + vertex.texcoord = texcoord2f_for_vector2(ctrl.m_texcoord); +} + +inline void vertex_clear_normal(ArbitraryMeshVertex& vertex) +{ + vertex.normal = Normal3f(0, 0, 0); + vertex.tangent = Normal3f(0, 0, 0); + vertex.bitangent = Normal3f(0, 0, 0); +} + +inline void tangents_remove_degenerate(Vector3 tangents[6], Vector2 textureTangents[6], unsigned int flags) +{ + if(flags & DEGEN_0a) + { + const std::size_t i = + (flags & DEGEN_0b) + ? (flags & DEGEN_1a) + ? (flags & DEGEN_1b) + ? (flags & DEGEN_2a) + ? 5 + : 4 + : 3 + : 2 + : 1; + tangents[0] = tangents[i]; + textureTangents[0] = textureTangents[i]; + } + if(flags & DEGEN_0b) + { + const std::size_t i = + (flags & DEGEN_0a) + ? (flags & DEGEN_1b) + ? (flags & DEGEN_1a) + ? (flags & DEGEN_2b) + ? 4 + : 5 + : 2 + : 3 + : 0; + tangents[1] = tangents[i]; + textureTangents[1] = textureTangents[i]; + } + if(flags & DEGEN_2a) + { + const std::size_t i = + (flags & DEGEN_2b) + ? (flags & DEGEN_1a) + ? (flags & DEGEN_1b) + ? (flags & DEGEN_0a) + ? 1 + : 0 + : 3 + : 2 + : 5; + tangents[4] = tangents[i]; + textureTangents[4] = textureTangents[i]; + } + if(flags & DEGEN_2b) + { + const std::size_t i = + (flags & DEGEN_2a) + ? (flags & DEGEN_1b) + ? (flags & DEGEN_1a) + ? (flags & DEGEN_0b) + ? 0 + : 1 + : 2 + : 3 + : 4; + tangents[5] = tangents[i]; + textureTangents[5] = textureTangents[i]; + } +} + +void bestTangents00(unsigned int degenerateFlags, double dot, double length, std::size_t& index0, std::size_t& index1) +{ + if(fabs(dot + length) < 0.001) // opposing direction = degenerate + { + if(!(degenerateFlags & DEGEN_1a)) // if this tangent is degenerate we cannot use it + { + index0 = 2; + index1 = 0; + } + else if(!(degenerateFlags & DEGEN_0b)) + { + index0 = 0; + index1 = 1; + } + else + { + index0 = 1; + index1 = 0; + } + } + else if(fabs(dot - length) < 0.001) // same direction = degenerate + { + if(degenerateFlags & DEGEN_0b) + { + index0 = 0; + index1 = 1; + } + else + { + index0 = 1; + index1 = 0; + } + } +} + +void bestTangents01(unsigned int degenerateFlags, double dot, double length, std::size_t& index0, std::size_t& index1) +{ + if(fabs(dot - length) < 0.001) // same direction = degenerate + { + if(!(degenerateFlags & DEGEN_1a)) // if this tangent is degenerate we cannot use it + { + index0 = 2; + index1 = 1; + } + else if(!(degenerateFlags & DEGEN_2b)) + { + index0 = 4; + index1 = 0; + } + else + { + index0 = 5; + index1 = 1; + } + } + else if(fabs(dot + length) < 0.001) // opposing direction = degenerate + { + if(degenerateFlags & DEGEN_2b) + { + index0 = 4; + index1 = 0; + } + else + { + index0 = 5; + index1 = 1; + } + } +} + +void bestTangents10(unsigned int degenerateFlags, double dot, double length, std::size_t& index0, std::size_t& index1) +{ + if(fabs(dot - length) < 0.001) // same direction = degenerate + { + if(!(degenerateFlags & DEGEN_1b)) // if this tangent is degenerate we cannot use it + { + index0 = 3; + index1 = 4; + } + else if(!(degenerateFlags & DEGEN_0a)) + { + index0 = 1; + index1 = 5; + } + else + { + index0 = 0; + index1 = 4; + } + } + else if(fabs(dot + length) < 0.001) // opposing direction = degenerate + { + if(degenerateFlags & DEGEN_0a) + { + index0 = 1; + index1 = 5; + } + else + { + index0 = 0; + index1 = 4; + } + } +} + +void bestTangents11(unsigned int degenerateFlags, double dot, double length, std::size_t& index0, std::size_t& index1) +{ + if(fabs(dot + length) < 0.001) // opposing direction = degenerate + { + if(!(degenerateFlags & DEGEN_1b)) // if this tangent is degenerate we cannot use it + { + index0 = 3; + index1 = 5; + } + else if(!(degenerateFlags & DEGEN_2a)) + { + index0 = 5; + index1 = 4; + } + else + { + index0 = 4; + index1 = 5; + } + } + else if(fabs(dot - length) < 0.001) // same direction = degenerate + { + if(degenerateFlags & DEGEN_2a) + { + index0 = 5; + index1 = 4; + } + else + { + index0 = 4; + index1 = 5; + } + } +} + +void Patch::accumulateVertexTangentSpace(std::size_t index, Vector3 tangentX[6], Vector3 tangentY[6], Vector2 tangentS[6], Vector2 tangentT[6], std::size_t index0, std::size_t index1) +{ + { + Vector3 normal(vector3_cross(tangentX[index0], tangentY[index1])); + if(!vector3_equal(normal, g_vector3_identity)) + { + vector3_add(normal_for_index(m_tess.m_vertices, index), vector3_normalised(normal)); + } + } + + { + ArbitraryMeshVertex a, b, c; + a.vertex = Vertex3f(0, 0, 0); + a.texcoord = TexCoord2f(0, 0); + b.vertex = vertex3f_for_vector3(tangentX[index0]); + b.texcoord = texcoord2f_for_vector2(tangentS[index0]); + c.vertex = vertex3f_for_vector3(tangentY[index1]); + c.texcoord = texcoord2f_for_vector2(tangentT[index1]); + + Vector3 s, t; + ArbitraryMeshTriangle_calcTangents(a, b, c, s, t); + if(!vector3_equal(s, g_vector3_identity)) + { + vector3_add(tangent_for_index(m_tess.m_vertices, index), vector3_normalised(s)); + } + if(!vector3_equal(t, g_vector3_identity)) + { + vector3_add(bitangent_for_index(m_tess.m_vertices, index), vector3_normalised(t)); + } + } +} + +const std::size_t PATCH_MAX_VERTEX_ARRAY = 1048576; + +void Patch::BuildVertexArray() +{ + const std::size_t strideU = 1; + const std::size_t strideV = m_width; + + const std::size_t numElems = m_tess.m_nArrayWidth*m_tess.m_nArrayHeight; // total number of elements in vertex array + + const bool bWidthStrips = (m_tess.m_nArrayWidth >= m_tess.m_nArrayHeight); // decide if horizontal strips are longer than vertical + + + // allocate vertex, normal, texcoord and primitive-index arrays + m_tess.m_vertices.resize(numElems); + m_tess.m_indices.resize(m_tess.m_nArrayWidth *2 * (m_tess.m_nArrayHeight - 1)); + + // set up strip indices + if(bWidthStrips) + { + m_tess.m_numStrips = m_tess.m_nArrayHeight-1; + m_tess.m_lenStrips = m_tess.m_nArrayWidth*2; + + for(std::size_t i=0; i>1]); + const std::size_t offMidY = (m_patchDef3) ? 0 : m_tess.m_curveTreeV[j>>1]->index; + const std::size_t widthY = m_tess.m_arrayHeight[j>>1] * m_tess.m_nArrayWidth; + const std::size_t offEndY = offStartY + widthY; + + for(std::size_t i = 0, offStartX = 0; i+1 < m_width; i += 2, pCtrl += (strideU << 1)) + { + const bool leafX = (m_patchDef3) ? false : BezierCurveTree_isLeaf(m_tess.m_curveTreeU[i>>1]); + const std::size_t offMidX = (m_patchDef3) ? 0 : m_tess.m_curveTreeU[i>>1]->index; + const std::size_t widthX = m_tess.m_arrayWidth[i>>1]; + const std::size_t offEndX = offStartX + widthX; + + PatchControl *subMatrix[3][3]; + subMatrix[0][0] = pCtrl; + subMatrix[0][1] = subMatrix[0][0]+strideU; + subMatrix[0][2] = subMatrix[0][1]+strideU; + subMatrix[1][0] = subMatrix[0][0]+strideV; + subMatrix[1][1] = subMatrix[1][0]+strideU; + subMatrix[1][2] = subMatrix[1][1]+strideU; + subMatrix[2][0] = subMatrix[1][0]+strideV; + subMatrix[2][1] = subMatrix[2][0]+strideU; + subMatrix[2][2] = subMatrix[2][1]+strideU; + + // assign on-patch control points to vertex array + if(i == 0 && j == 0) + { + vertex_clear_normal(m_tess.m_vertices[offStartX + offStartY]); + } + vertex_assign_ctrl(m_tess.m_vertices[offStartX + offStartY], *subMatrix[0][0]); + if(j == 0) + { + vertex_clear_normal(m_tess.m_vertices[offEndX + offStartY]); + } + vertex_assign_ctrl(m_tess.m_vertices[offEndX + offStartY], *subMatrix[0][2]); + if(i == 0) + { + vertex_clear_normal(m_tess.m_vertices[offStartX + offEndY]); + } + vertex_assign_ctrl(m_tess.m_vertices[offStartX + offEndY], *subMatrix[2][0]); + + vertex_clear_normal(m_tess.m_vertices[offEndX + offEndY]); + vertex_assign_ctrl(m_tess.m_vertices[offEndX + offEndY], *subMatrix[2][2]); + + if(!m_patchDef3) + { + // assign remaining control points to vertex array + if(!leafX) + { + vertex_assign_ctrl(m_tess.m_vertices[offMidX + offStartY], *subMatrix[0][1]); + vertex_assign_ctrl(m_tess.m_vertices[offMidX + offEndY], *subMatrix[2][1]); + } + if(!leafY) + { + vertex_assign_ctrl(m_tess.m_vertices[offStartX + offMidY], *subMatrix[1][0]); + vertex_assign_ctrl(m_tess.m_vertices[offEndX + offMidY], *subMatrix[1][2]); + + if(!leafX) + { + vertex_assign_ctrl(m_tess.m_vertices[offMidX + offMidY], *subMatrix[1][1]); + } + } + } + + // test all 12 edges for degeneracy + unsigned int nFlagsX = subarray_get_degen(pCtrl, strideU, strideV); + unsigned int nFlagsY = subarray_get_degen(pCtrl, strideV, strideU); + Vector3 tangentX[6], tangentY[6]; + Vector2 tangentS[6], tangentT[6]; + + // set up tangents for each of the 12 edges if they were not degenerate + if(!(nFlagsX & DEGEN_0a)) + { + tangentX[0] = vector3_subtracted(subMatrix[0][1]->m_vertex, subMatrix[0][0]->m_vertex); + tangentS[0] = vector2_subtracted(subMatrix[0][1]->m_texcoord, subMatrix[0][0]->m_texcoord); + } + if(!(nFlagsX & DEGEN_0b)) + { + tangentX[1] = vector3_subtracted(subMatrix[0][2]->m_vertex, subMatrix[0][1]->m_vertex); + tangentS[1] = vector2_subtracted(subMatrix[0][2]->m_texcoord, subMatrix[0][1]->m_texcoord); + } + if(!(nFlagsX & DEGEN_1a)) + { + tangentX[2] = vector3_subtracted(subMatrix[1][1]->m_vertex, subMatrix[1][0]->m_vertex); + tangentS[2] = vector2_subtracted(subMatrix[1][1]->m_texcoord, subMatrix[1][0]->m_texcoord); + } + if(!(nFlagsX & DEGEN_1b)) + { + tangentX[3] = vector3_subtracted(subMatrix[1][2]->m_vertex, subMatrix[1][1]->m_vertex); + tangentS[3] = vector2_subtracted(subMatrix[1][2]->m_texcoord, subMatrix[1][1]->m_texcoord); + } + if(!(nFlagsX & DEGEN_2a)) + { + tangentX[4] = vector3_subtracted(subMatrix[2][1]->m_vertex, subMatrix[2][0]->m_vertex); + tangentS[4] = vector2_subtracted(subMatrix[2][1]->m_texcoord, subMatrix[2][0]->m_texcoord); + } + if(!(nFlagsX & DEGEN_2b)) + { + tangentX[5] = vector3_subtracted(subMatrix[2][2]->m_vertex, subMatrix[2][1]->m_vertex); + tangentS[5] = vector2_subtracted(subMatrix[2][2]->m_texcoord, subMatrix[2][1]->m_texcoord); + } + + if(!(nFlagsY & DEGEN_0a)) + { + tangentY[0] = vector3_subtracted(subMatrix[1][0]->m_vertex, subMatrix[0][0]->m_vertex); + tangentT[0] = vector2_subtracted(subMatrix[1][0]->m_texcoord, subMatrix[0][0]->m_texcoord); + } + if(!(nFlagsY & DEGEN_0b)) + { + tangentY[1] = vector3_subtracted(subMatrix[2][0]->m_vertex, subMatrix[1][0]->m_vertex); + tangentT[1] = vector2_subtracted(subMatrix[2][0]->m_texcoord, subMatrix[1][0]->m_texcoord); + } + if(!(nFlagsY & DEGEN_1a)) + { + tangentY[2] = vector3_subtracted(subMatrix[1][1]->m_vertex, subMatrix[0][1]->m_vertex); + tangentT[2] = vector2_subtracted(subMatrix[1][1]->m_texcoord, subMatrix[0][1]->m_texcoord); + } + if(!(nFlagsY & DEGEN_1b)) + { + tangentY[3] = vector3_subtracted(subMatrix[2][1]->m_vertex, subMatrix[1][1]->m_vertex); + tangentT[3] = vector2_subtracted(subMatrix[2][1]->m_texcoord, subMatrix[1][1]->m_texcoord); + } + if(!(nFlagsY & DEGEN_2a)) + { + tangentY[4] = vector3_subtracted(subMatrix[1][2]->m_vertex, subMatrix[0][2]->m_vertex); + tangentT[4] = vector2_subtracted(subMatrix[1][2]->m_texcoord, subMatrix[0][2]->m_texcoord); + } + if(!(nFlagsY & DEGEN_2b)) + { + tangentY[5] = vector3_subtracted(subMatrix[2][2]->m_vertex, subMatrix[1][2]->m_vertex); + tangentT[5] = vector2_subtracted(subMatrix[2][2]->m_texcoord, subMatrix[1][2]->m_texcoord); + } + + // set up remaining edge tangents by borrowing the tangent from the closest parallel non-degenerate edge + tangents_remove_degenerate(tangentX, tangentS, nFlagsX); + tangents_remove_degenerate(tangentY, tangentT, nFlagsY); + + { + // x=0, y=0 + std::size_t index = offStartX + offStartY; + std::size_t index0 = 0; + std::size_t index1 = 0; + + double dot = vector3_dot(tangentX[index0], tangentY[index1]); + double length = vector3_length(tangentX[index0]) * vector3_length(tangentY[index1]); + + bestTangents00(nFlagsX, dot, length, index0, index1); + + accumulateVertexTangentSpace(index, tangentX, tangentY, tangentS, tangentT, index0, index1); + } + + { + // x=1, y=0 + std::size_t index = offEndX + offStartY; + std::size_t index0 = 1; + std::size_t index1 = 4; + + double dot = vector3_dot(tangentX[index0],tangentY[index1]); + double length = vector3_length(tangentX[index0]) * vector3_length(tangentY[index1]); + + bestTangents10(nFlagsX, dot, length, index0, index1); + + accumulateVertexTangentSpace(index, tangentX, tangentY, tangentS, tangentT, index0, index1); + } + + { + // x=0, y=1 + std::size_t index = offStartX + offEndY; + std::size_t index0 = 4; + std::size_t index1 = 1; + + double dot = vector3_dot(tangentX[index0], tangentY[index1]); + double length = vector3_length(tangentX[index1]) * vector3_length(tangentY[index1]); + + bestTangents01(nFlagsX, dot, length, index0, index1); + + accumulateVertexTangentSpace(index, tangentX, tangentY, tangentS, tangentT, index0, index1); + } + + { + // x=1, y=1 + std::size_t index = offEndX + offEndY; + std::size_t index0 = 5; + std::size_t index1 = 5; + + double dot = vector3_dot(tangentX[index0],tangentY[index1]); + double length = vector3_length(tangentX[index0]) * vector3_length(tangentY[index1]); + + bestTangents11(nFlagsX, dot, length, index0, index1); + + accumulateVertexTangentSpace(index, tangentX, tangentY, tangentS, tangentT, index0, index1); + } + + //normalise normals that won't be accumulated again + if(i!=0 || j!=0) + { + normalise_safe(normal_for_index(m_tess.m_vertices, offStartX + offStartY)); + normalise_safe(tangent_for_index(m_tess.m_vertices, offStartX + offStartY)); + normalise_safe(bitangent_for_index(m_tess.m_vertices, offStartX + offStartY)); + } + if(i+3 == m_width) + { + normalise_safe(normal_for_index(m_tess.m_vertices, offEndX + offStartY)); + normalise_safe(tangent_for_index(m_tess.m_vertices, offEndX + offStartY)); + normalise_safe(bitangent_for_index(m_tess.m_vertices, offEndX + offStartY)); + } + if(j+3 == m_height) + { + normalise_safe(normal_for_index(m_tess.m_vertices, offStartX + offEndY)); + normalise_safe(tangent_for_index(m_tess.m_vertices, offStartX + offEndY)); + normalise_safe(bitangent_for_index(m_tess.m_vertices, offStartX + offEndY)); + } + if(i+3 == m_width && j+3 == m_height) + { + normalise_safe(normal_for_index(m_tess.m_vertices, offEndX + offEndY)); + normalise_safe(tangent_for_index(m_tess.m_vertices, offEndX + offEndY)); + normalise_safe(bitangent_for_index(m_tess.m_vertices, offEndX + offEndY)); + } + + // set flags to average normals between shared edges + if(j != 0) + { + nFlagsX |= AVERAGE; + } + if(i != 0) + { + nFlagsY |= AVERAGE; + } + // set flags to save evaluating shared edges twice + nFlagsX |= SPLIT; + nFlagsY |= SPLIT; + + // if the patch is curved.. tesselate recursively + // use the relevant control curves for this sub-patch + if(m_patchDef3) + { + TesselateSubMatrixFixed(m_tess.m_vertices.data() + offStartX + offStartY, 1, m_tess.m_nArrayWidth, nFlagsX, nFlagsY, subMatrix); + } + else + { + if(!leafX) + { + TesselateSubMatrix( m_tess.m_curveTreeU[i>>1], m_tess.m_curveTreeV[j>>1], + offStartX, offStartY, offEndX, offEndY, // array offsets + nFlagsX, nFlagsY, + subMatrix[1][0]->m_vertex, subMatrix[1][1]->m_vertex, subMatrix[1][2]->m_vertex, + subMatrix[1][0]->m_texcoord, subMatrix[1][1]->m_texcoord, subMatrix[1][2]->m_texcoord, + false ); + } + else if(!leafY) + { + TesselateSubMatrix( m_tess.m_curveTreeV[j>>1], m_tess.m_curveTreeU[i>>1], + offStartY, offStartX, offEndY, offEndX, // array offsets + nFlagsY, nFlagsX, + subMatrix[0][1]->m_vertex, subMatrix[1][1]->m_vertex, subMatrix[2][1]->m_vertex, + subMatrix[0][1]->m_texcoord, subMatrix[1][1]->m_texcoord, subMatrix[2][1]->m_texcoord, + true ); + } + } + + offStartX = offEndX; + } + offStartY = offEndY; + } + } +} + + + +class PatchFilterWrapper : public Filter +{ + bool m_active; + bool m_invert; + PatchFilter& m_filter; +public: + PatchFilterWrapper(PatchFilter& filter, bool invert) : m_invert(invert), m_filter(filter) + { + } + void setActive(bool active) + { + m_active = active; + } + bool active() + { + return m_active; + } + bool filter(const Patch& patch) + { + return m_invert ^ m_filter.filter(patch); + } +}; + + +typedef std::list PatchFilters; +PatchFilters g_patchFilters; + +void add_patch_filter(PatchFilter& filter, int mask, bool invert) +{ + g_patchFilters.push_back(PatchFilterWrapper(filter, invert)); + GlobalFilterSystem().addFilter(g_patchFilters.back(), mask); +} + +bool patch_filtered(Patch& patch) +{ + for(PatchFilters::iterator i = g_patchFilters.begin(); i != g_patchFilters.end(); ++i) + { + if((*i).active() && (*i).filter(patch)) + { + return true; + } + } + return false; +} diff --git a/radiant/patch.h b/radiant/patch.h new file mode 100644 index 00000000..ddbbffbd --- /dev/null +++ b/radiant/patch.h @@ -0,0 +1,2070 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_PATCH_H) +#define INCLUDED_PATCH_H + +/// \file +/// \brief The patch primitive. +/// +/// A 2-dimensional matrix of vertices that define a quadratic bezier surface. +/// The Boundary-Representation of this primitive is a triangle mesh. +/// The surface is recursively tesselated until the angle between each triangle +/// edge is smaller than a specified tolerance. + + +#include "nameable.h" +#include "ifilter.h" +#include "imap.h" +#include "ipatch.h" +#include "cullable.h" +#include "renderable.h" +#include "editable.h" +#include "selectable.h" + +#include "debugging/debugging.h" + +#include +#include + +#include "math/frustum.h" +#include "string/string.h" +#include "stream/stringstream.h" +#include "stream/textstream.h" +#include "xml/xmlelement.h" +#include "scenelib.h" +#include "transformlib.h" +#include "instancelib.h" +#include "selectionlib.h" +#include "traverselib.h" +#include "render.h" +#include "stringio.h" +#include "shaderlib.h" +#include "generic/callback.h" +#include "signal/signalfwd.h" +#include "texturelib.h" +#include "xml/ixml.h" +#include "dragplanes.h" + +enum EPatchType +{ + ePatchTypeQuake3, + ePatchTypeDoom3, +}; + +extern int g_PatchSubdivideThreshold; + + +#define MIN_PATCH_WIDTH 3 +#define MIN_PATCH_HEIGHT 3 + +extern std::size_t MAX_PATCH_WIDTH; +extern std::size_t MAX_PATCH_HEIGHT; + +#define MAX_PATCH_ROWCTRL (((MAX_PATCH_WIDTH-1)-1)/2) +#define MAX_PATCH_COLCTRL (((MAX_PATCH_HEIGHT-1)-1)/2) + +enum EPatchCap +{ + eCapBevel, + eCapEndCap, + eCapIBevel, + eCapIEndCap, + eCapCylinder, +}; + +enum EPatchPrefab +{ + ePlane, + eBevel, + eEndCap, + eCylinder, + eDenseCylinder, + eVeryDenseCylinder, + eSqCylinder, + eCone, + eSphere, +}; + +enum EMatrixMajor +{ + ROW, COL, +}; + +struct BezierCurve +{ + Vector3 crd; + Vector3 left; + Vector3 right; +}; + +const std::size_t BEZIERCURVETREE_MAX_INDEX = std::size_t(1) << (std::numeric_limits::digits - 1); + +struct BezierCurveTree +{ + std::size_t index; + BezierCurveTree* left; + BezierCurveTree* right; +}; + +inline bool BezierCurveTree_isLeaf(const BezierCurveTree* node) +{ + return node->left == 0 && node->right == 0; +} + +void BezierCurveTree_Delete(BezierCurveTree *pCurve); + + +inline VertexPointer vertexpointer_arbitrarymeshvertex(const ArbitraryMeshVertex* array) +{ + return VertexPointer(VertexPointer::pointer(&array->vertex), sizeof(ArbitraryMeshVertex)); +} + +typedef PatchControl* PatchControlIter; +typedef const PatchControl* PatchControlConstIter; + +inline void copy_ctrl(PatchControlIter ctrl, PatchControlConstIter begin, PatchControlConstIter end) +{ + std::copy(begin, end, ctrl); +} + +const Colour4b colour_corner(0, 255, 0, 255); +const Colour4b colour_inside(255, 0, 255, 255); + +class Patch; + +class PatchFilter +{ +public: + virtual bool filter(const Patch& patch) const = 0; +}; + +bool patch_filtered(Patch& patch); +void add_patch_filter(PatchFilter& filter, int mask, bool invert = false); + +void Patch_addTextureChangedCallback(const SignalHandler& handler); +void Patch_textureChanged(); + +inline void BezierCurveTreeArray_deleteAll(Array& curveTrees) +{ + for(Array::iterator i = curveTrees.begin(); i != curveTrees.end(); ++i) + { + BezierCurveTree_Delete(*i); + } +} + +inline void PatchControlArray_invert(Array& ctrl, std::size_t width, std::size_t height) +{ + Array tmp(width); + + PatchControlIter from = ctrl.data() + (width * (height - 1)); + PatchControlIter to = ctrl.data(); + for(std::size_t h = 0; h != ((height - 1) >> 1); ++h, to += width, from -= width) + { + copy_ctrl(tmp.data(), to, to + width); + copy_ctrl(to, from, from + width); + copy_ctrl(from, tmp.data(), tmp.data() + width); + } +} + +class PatchTesselation +{ +public: + PatchTesselation() + : m_numStrips(0), m_lenStrips(0), m_nArrayWidth(0), m_nArrayHeight(0) + { + } + Array m_vertices; + Array m_indices; + std::size_t m_numStrips; + std::size_t m_lenStrips; + + Array m_arrayWidth; + std::size_t m_nArrayWidth; + Array m_arrayHeight; + std::size_t m_nArrayHeight; + + Array m_curveTreeU; + Array m_curveTreeV; +}; + +class RenderablePatchWireframe : public OpenGLRenderable +{ + PatchTesselation& m_tess; +public: + RenderablePatchWireframe(PatchTesselation& tess) : m_tess(tess) + { + } + void render(RenderStateFlags state) const + { + { + #if NV_DRIVER_BUG + glVertexPointer(3, GL_FLOAT, 0, 0); + glDrawArrays(GL_TRIANGLE_FAN, 0, 0); + #endif + + std::size_t n = 0; + glVertexPointer(3, GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->vertex); + for(std::size_t i = 0; i <= m_tess.m_curveTreeV.size(); ++i) + { + glDrawArrays(GL_LINE_STRIP, GLint(n), GLsizei(m_tess.m_nArrayWidth)); + + if(i == m_tess.m_curveTreeV.size()) break; + + if(!BezierCurveTree_isLeaf(m_tess.m_curveTreeV[i])) + glDrawArrays(GL_LINE_STRIP, GLint(m_tess.m_curveTreeV[i]->index), GLsizei(m_tess.m_nArrayWidth)); + + n += (m_tess.m_arrayHeight[i]*m_tess.m_nArrayWidth); + + } + } + + { + const ArbitraryMeshVertex* p = m_tess.m_vertices.data(); + std::size_t n = m_tess.m_nArrayWidth * sizeof(ArbitraryMeshVertex); + for(std::size_t i = 0; i <= m_tess.m_curveTreeU.size(); ++i) + { + glVertexPointer(3, GL_FLOAT, GLsizei(n), &p->vertex); + glDrawArrays(GL_LINE_STRIP, 0, GLsizei(m_tess.m_nArrayHeight)); + + if(i == m_tess.m_curveTreeU.size()) break; + + if(!BezierCurveTree_isLeaf(m_tess.m_curveTreeU[i])) + { + glVertexPointer(3, GL_FLOAT, GLsizei(n), &(m_tess.m_vertices.data() + (m_tess.m_curveTreeU[i]->index))->vertex); + glDrawArrays(GL_LINE_STRIP, 0, GLsizei(m_tess.m_nArrayHeight)); + } + + p += m_tess.m_arrayWidth[i]; + } + } + } +}; + +class RenderablePatchFixedWireframe : public OpenGLRenderable +{ + PatchTesselation& m_tess; +public: + RenderablePatchFixedWireframe(PatchTesselation& tess) : m_tess(tess) + { + } + void render(RenderStateFlags state) const + { + glVertexPointer(3, GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->vertex); + const RenderIndex* strip_indices = m_tess.m_indices.data(); + for(std::size_t i = 0; inormal); + glVertexAttribPointerARB(c_attr_TexCoord0, 2, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->texcoord); + glVertexAttribPointerARB(c_attr_Tangent, 3, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->tangent); + glVertexAttribPointerARB(c_attr_Binormal, 3, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->bitangent); + } + else + { + glVertexAttribPointerARB(11, 3, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->normal); + glVertexAttribPointerARB(8, 2, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->texcoord); + glVertexAttribPointerARB(9, 3, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->tangent); + glVertexAttribPointerARB(10, 3, GL_FLOAT, 0, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->bitangent); + } + } + else + { + glNormalPointer(GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->normal); + glTexCoordPointer(2, GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->texcoord); + } + glVertexPointer(3, GL_FLOAT, sizeof(ArbitraryMeshVertex), &m_tess.m_vertices.data()->vertex); + const RenderIndex* strip_indices = m_tess.m_indices.data(); + for(std::size_t i = 0; i m_xml_state; + + typedef Array PatchControlArray; + + class SavedState : public UndoMemento + { + public: + SavedState( + std::size_t width, + std::size_t height, + const PatchControlArray& ctrl, + const char* shader, + bool patchDef3, + std::size_t subdivisions_x, + std::size_t subdivisions_y + ) : + m_width(width), + m_height(height), + m_shader(shader), + m_ctrl(ctrl), + m_patchDef3(patchDef3), + m_subdivisions_x(subdivisions_x), + m_subdivisions_y(subdivisions_y) + { + } + + void release() + { + delete this; + } + + std::size_t m_width, m_height; + CopiedString m_shader; + PatchControlArray m_ctrl; + bool m_patchDef3; + std::size_t m_subdivisions_x; + std::size_t m_subdivisions_y; + }; + +public: + class Observer + { + public: + virtual void allocate(std::size_t size) = 0; + }; + +private: + typedef UniqueSet Observers; + Observers m_observers; + + scene::Node* m_node; + + AABB m_aabb_local; // local bbox + + CopiedString m_shader; + Shader* m_state; + + std::size_t m_width; + std::size_t m_height; +public: + bool m_patchDef3; + std::size_t m_subdivisions_x; + std::size_t m_subdivisions_y; +private: + + UndoObserver* m_undoable_observer; + MapFile* m_map; + + // dynamically allocated array of control points, size is m_width*m_height + PatchControlArray m_ctrl; + PatchControlArray m_ctrlTransformed; + + PatchTesselation m_tess; + RenderablePatchSolid m_render_solid; + RenderablePatchWireframe m_render_wireframe; + RenderablePatchFixedWireframe m_render_wireframe_fixed; + + static Shader* m_state_ctrl; + static Shader* m_state_lattice; + VertexBuffer m_ctrl_vertices; + RenderableVertexBuffer m_render_ctrl; + IndexBuffer m_lattice_indices; + RenderableIndexBuffer m_render_lattice; + + bool m_bOverlay; + + bool m_transformChanged; + Callback m_evaluateTransform; + Callback m_boundsChanged; + + void construct() + { + m_bOverlay = false; + m_width = m_height = 0; + + m_patchDef3 = false; + m_subdivisions_x = 0; + m_subdivisions_y = 0; + + check_shader(); + captureShader(); + + m_xml_state.push_back(xml_state_t::eDefault); + } + +public: + Callback m_lightsChanged; + + static int m_CycleCapIndex;// = 0; + static EPatchType m_type; + + STRING_CONSTANT(Name, "Patch"); + + Patch(scene::Node& node, const Callback& evaluateTransform, const Callback& boundsChanged) : + m_node(&node), + m_shader(texdef_name_default()), + m_state(0), + m_undoable_observer(0), + m_map(0), + m_render_solid(m_tess), + m_render_wireframe(m_tess), + m_render_wireframe_fixed(m_tess), + m_render_ctrl(GL_POINTS, m_ctrl_vertices), + m_render_lattice(GL_LINES, m_lattice_indices, m_ctrl_vertices), + m_transformChanged(false), + m_evaluateTransform(evaluateTransform), + m_boundsChanged(boundsChanged) + { + construct(); + } + Patch(const Patch& other, scene::Node& node, const Callback& evaluateTransform, const Callback& boundsChanged) : + m_node(&node), + m_shader(texdef_name_default()), + m_state(0), + m_undoable_observer(0), + m_map(0), + m_render_solid(m_tess), + m_render_wireframe(m_tess), + m_render_wireframe_fixed(m_tess), + m_render_ctrl(GL_POINTS, m_ctrl_vertices), + m_render_lattice(GL_LINES, m_lattice_indices, m_ctrl_vertices), + m_transformChanged(false), + m_evaluateTransform(evaluateTransform), + m_boundsChanged(boundsChanged) + { + construct(); + + m_patchDef3 = other.m_patchDef3; + m_subdivisions_x = other.m_subdivisions_x; + m_subdivisions_y = other.m_subdivisions_y; + setDims(other.m_width, other.m_height); + copy_ctrl(m_ctrl.data(), other.m_ctrl.data(), other.m_ctrl.data()+(m_width*m_height)); + SetShader(other.m_shader.c_str()); + controlPointsChanged(); + } + + Patch(const Patch& other) : + XMLImporter(other), + XMLExporter(other), + TransformNode(other), + Bounded(other), + Cullable(other), + Snappable(), + Undoable(other), + Filterable(other), + Nameable(other), + m_state(0), + m_undoable_observer(0), + m_map(0), + m_render_solid(m_tess), + m_render_wireframe(m_tess), + m_render_wireframe_fixed(m_tess), + m_render_ctrl(GL_POINTS, m_ctrl_vertices), + m_render_lattice(GL_LINES, m_lattice_indices, m_ctrl_vertices), + m_transformChanged(false), + m_evaluateTransform(other.m_evaluateTransform), + m_boundsChanged(other.m_boundsChanged) + { + m_bOverlay = false; + + m_patchDef3 = other.m_patchDef3; + m_subdivisions_x = other.m_subdivisions_x; + m_subdivisions_y = other.m_subdivisions_y; + setDims(other.m_width, other.m_height); + copy_ctrl(m_ctrl.data(), other.m_ctrl.data(), other.m_ctrl.data()+(m_width*m_height)); + SetShader(other.m_shader.c_str()); + controlPointsChanged(); + } + + ~Patch() + { + BezierCurveTreeArray_deleteAll(m_tess.m_curveTreeU); + BezierCurveTreeArray_deleteAll(m_tess.m_curveTreeV); + + releaseShader(); + + ASSERT_MESSAGE(m_observers.empty(), "Patch::~Patch: observers still attached"); + } + + InstanceCounter m_instanceCounter; + void instanceAttach(const scene::Path& path) + { + if(++m_instanceCounter.m_count == 1) + { + m_state->incrementUsed(); + m_map = path_find_mapfile(path.begin(), path.end()); + m_undoable_observer = GlobalUndoSystem().observer(this); + GlobalFilterSystem().registerFilterable(*this); + } + else + { + ASSERT_MESSAGE(path_find_mapfile(path.begin(), path.end()) == m_map, "node is instanced across more than one file"); + } + } + void instanceDetach(const scene::Path& path) + { + if(--m_instanceCounter.m_count == 0) + { + m_map = 0; + m_undoable_observer = 0; + GlobalUndoSystem().release(this); + GlobalFilterSystem().unregisterFilterable(*this); + m_state->decrementUsed(); + } + } + + const char* name() const + { + return "patch"; + } + void attach(const NameCallback& callback) + { + } + void detach(const NameCallback& callback) + { + } + + void attach(Observer* observer) + { + observer->allocate(m_width * m_height); + + m_observers.insert(observer); + } + void detach(Observer* observer) + { + m_observers.erase(observer); + } + + void updateFiltered() + { + if(m_node != 0) + { + if(patch_filtered(*this)) + { + m_node->enable(scene::Node::eFiltered); + } + else + { + m_node->disable(scene::Node::eFiltered); + } + } + } + + void onAllocate(std::size_t size) + { + for(Observers::iterator i = m_observers.begin(); i != m_observers.end(); ++i) + { + (*i)->allocate(size); + } + } + + const Matrix4& localToParent() const + { + return g_matrix4_identity; + } + const AABB& localAABB() const + { + return m_aabb_local; + } + VolumeIntersectionValue intersectVolume(const VolumeTest& test, const Matrix4& localToWorld) const + { + return test.TestAABB(m_aabb_local, localToWorld); + } + void render_solid(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld) const + { + renderer.SetState(m_state, Renderer::eFullMaterials); + renderer.addRenderable(m_render_solid, localToWorld); + } + void render_wireframe(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld) const + { + renderer.SetState(m_state, Renderer::eFullMaterials); + if(m_patchDef3) + { + renderer.addRenderable(m_render_wireframe_fixed, localToWorld); + } + else + { + renderer.addRenderable(m_render_wireframe, localToWorld); + } + } + + void render_component(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld) const + { + renderer.SetState(m_state_lattice, Renderer::eWireframeOnly); + renderer.SetState(m_state_lattice, Renderer::eFullMaterials); + renderer.addRenderable(m_render_lattice, localToWorld); + + renderer.SetState(m_state_ctrl, Renderer::eWireframeOnly); + renderer.SetState(m_state_ctrl, Renderer::eFullMaterials); + renderer.addRenderable(m_render_ctrl, localToWorld); + } + void testSelect(Selector& selector, SelectionTest& test) + { + SelectionIntersection best; + IndexPointer::index_type* pIndex = m_tess.m_indices.data(); + for(std::size_t s=0; s TransformChangedCaller; + + void evaluateTransform() + { + if(m_transformChanged) + { + m_transformChanged = false; + revertTransform(); + m_evaluateTransform(); + } + } + + void revertTransform() + { + m_ctrlTransformed = m_ctrl; + } + void freezeTransform() + { + undoSave(); + evaluateTransform(); + ASSERT_MESSAGE(m_ctrlTransformed.size() == m_ctrl.size(), "Patch::freeze: size mismatch"); + std::copy(m_ctrlTransformed.begin(), m_ctrlTransformed.end(), m_ctrl.begin()); + } + + void controlPointsChanged() + { + transformChanged(); + evaluateTransform(); + UpdateCachedData(); + } + bool isValid() const; + + void snapto(float snap) + { + undoSave(); + + for(PatchControlIter i = m_ctrl.data(); i != m_ctrl.data() + m_ctrl.size(); ++i) + { + vector3_snap((*i).m_vertex, snap); + } + + controlPointsChanged(); + } + + + + + void RenderDebug(RenderStateFlags state) const; + void RenderNormals(RenderStateFlags state) const; + + void pushElement(const XMLElement& element) + { + switch(m_xml_state.back().state()) + { + case xml_state_t::eDefault: + ASSERT_MESSAGE(string_equal(element.name(), "patch"), "parse error"); + m_xml_state.push_back(xml_state_t::ePatch); + break; + case xml_state_t::ePatch: + if(string_equal(element.name(), "matrix")) + { + setDims(atoi(element.attribute("width")), atoi(element.attribute("height"))); + m_xml_state.push_back(xml_state_t::eMatrix); + } + else if(string_equal(element.name(), "shader")) + { + m_xml_state.push_back(xml_state_t::eShader); + } + break; + default: + ERROR_MESSAGE("parse error"); + } + + } + void popElement(const char* name) + { + switch(m_xml_state.back().state()) + { + case xml_state_t::eDefault: + ERROR_MESSAGE("parse error"); + break; + case xml_state_t::ePatch: + break; + case xml_state_t::eMatrix: + { + StringTokeniser content(m_xml_state.back().content()); + + for(PatchControlIter i = m_ctrl.data(), end = m_ctrl.data() + m_ctrl.size(); i != end; ++i) + { + (*i).m_vertex[0] = string_read_float(content.getToken()); + (*i).m_vertex[1] = string_read_float(content.getToken()); + (*i).m_vertex[2] = string_read_float(content.getToken()); + (*i).m_texcoord[0] = string_read_float(content.getToken()); + (*i).m_texcoord[1] = string_read_float(content.getToken()); + } + controlPointsChanged(); + } + break; + case xml_state_t::eShader: + { + SetShader(m_xml_state.back().content()); + } + break; + default: + ERROR_MESSAGE("parse error"); + } + + ASSERT_MESSAGE(!m_xml_state.empty(), "popping empty stack"); + m_xml_state.pop_back(); + } + std::size_t write(const char* buffer, std::size_t length) + { + switch(m_xml_state.back().state()) + { + case xml_state_t::eDefault: + break; + case xml_state_t::ePatch: + break; + case xml_state_t::eMatrix: + case xml_state_t::eShader: + return m_xml_state.back().write(buffer, length); + break; + default: + ERROR_MESSAGE("parse error"); + } + return length; + } + + void exportXML(XMLImporter& importer) + { + StaticElement patchElement("patch"); + importer.pushElement(patchElement); + + { + const StaticElement element("shader"); + importer.pushElement(element); + importer.write(m_shader.c_str(), strlen(m_shader.c_str())); + importer.popElement(element.name()); + } + + { + char width[16], height[16]; + sprintf(width, "%u", Unsigned(m_width)); + sprintf(height, "%u", Unsigned(m_height)); + StaticElement element("matrix"); + element.insertAttribute("width", width); + element.insertAttribute("height", height); + + importer.pushElement(element); + { + for(PatchControlIter i = m_ctrl.data(), end = m_ctrl.data() + m_ctrl.size(); i != end; ++i) + { + importer << (*i).m_vertex[0] + << ' ' << (*i).m_vertex[1] + << ' ' << (*i).m_vertex[2] + << ' ' << (*i).m_texcoord[0] + << ' ' << (*i).m_texcoord[1]; + } + } + importer.popElement(element.name()); + } + + importer.popElement(patchElement.name()); + } + + void UpdateCachedData(); + + const char *GetShader() const + { + return m_shader.c_str(); + } + void SetShader(const char* name) + { + ASSERT_NOTNULL(name); + + if(shader_equal(m_shader.c_str(), name)) + return; + + undoSave(); + + if(m_instanceCounter.m_count != 0) + { + m_state->decrementUsed(); + } + releaseShader(); + m_shader = name; + captureShader(); + if(m_instanceCounter.m_count != 0) + { + m_state->incrementUsed(); + } + + check_shader(); + Patch_textureChanged(); + } + int getShaderFlags() const + { + if(m_state != 0) + { + return m_state->getFlags(); + } + return 0; + } + + typedef PatchControl* iterator; + typedef const PatchControl* const_iterator; + + iterator begin() + { + return m_ctrl.data(); + } + const_iterator begin() const + { + return m_ctrl.data(); + } + iterator end() + { + return m_ctrl.data() + m_ctrl.size(); + } + const_iterator end() const + { + return m_ctrl.data() + m_ctrl.size(); + } + + PatchControlArray& getControlPoints() + { + return m_ctrl; + } + PatchControlArray& getControlPointsTransformed() + { + return m_ctrlTransformed; + } + + void setDims (std::size_t w, std::size_t h); + std::size_t getWidth() const + { + return m_width; + } + std::size_t getHeight() const + { + return m_height; + } + PatchControl& ctrlAt(std::size_t row, std::size_t col) + { + return m_ctrl[row*m_width+col]; + } + const PatchControl& ctrlAt(std::size_t row, std::size_t col) const + { + return m_ctrl[row*m_width+col]; + } + + void ConstructPrefab(const AABB& aabb, EPatchPrefab eType, int axis, std::size_t width = 3, std::size_t height = 3); + void constructPlane(const AABB& aabb, int axis, std::size_t width, std::size_t height); + void InvertMatrix(); + void TransposeMatrix(); + void Redisperse(EMatrixMajor mt); + void InsertRemove(bool bInsert, bool bColumn, bool bFirst); + Patch* MakeCap(Patch* patch, EPatchCap eType, EMatrixMajor mt, bool bFirst); + void ConstructSeam(EPatchCap eType, Vector3* p, std::size_t width); + + void FlipTexture(int nAxis); + void TranslateTexture(float s, float t); + void ScaleTexture(float s, float t); + void RotateTexture(float angle); + void SetTextureRepeat(float s, float t); // call with s=1 t=1 for FIT + void CapTexture(); + void NaturalTexture(); + void ProjectTexture(int nAxis); + + void undoSave() + { + if(m_map != 0) + { + m_map->changed(); + } + if(m_undoable_observer != 0) + { + m_undoable_observer->save(this); + } + } + + UndoMemento* exportState() const + { + return new SavedState(m_width, m_height, m_ctrl, m_shader.c_str(), m_patchDef3, m_subdivisions_x, m_subdivisions_y); + } + void importState(const UndoMemento* state) + { + undoSave(); + + const SavedState& other = *(static_cast(state)); + + // begin duplicate of SavedState copy constructor, needs refactoring + + // copy construct + { + m_width = other.m_width; + m_height = other.m_height; + SetShader(other.m_shader.c_str()); + m_ctrl = other.m_ctrl; + onAllocate(m_ctrl.size()); + m_patchDef3 = other.m_patchDef3; + m_subdivisions_x = other.m_subdivisions_x; + m_subdivisions_y = other.m_subdivisions_y; + } + + // end duplicate code + + Patch_textureChanged(); + + controlPointsChanged(); + } + + static void constructStatic(EPatchType type) + { + Patch::m_type = type; + Patch::m_state_ctrl = GlobalShaderCache().capture("$POINT"); + Patch::m_state_lattice = GlobalShaderCache().capture("$LATTICE"); + } + + static void destroyStatic() + { + GlobalShaderCache().release("$LATTICE"); + GlobalShaderCache().release("$POINT"); + } +private: + void captureShader() + { + m_state = GlobalShaderCache().capture(m_shader.c_str()); + } + + void releaseShader() + { + GlobalShaderCache().release(m_shader.c_str()); + } + + void check_shader() + { + if(!shader_valid(GetShader())) + { + globalErrorStream() << "patch has invalid texture name: '" << GetShader() << "'\n"; + } + } + + void InsertPoints(EMatrixMajor mt, bool bFirst); + void RemovePoints(EMatrixMajor mt, bool bFirst); + + void AccumulateBBox(); + + void TesselateSubMatrixFixed(ArbitraryMeshVertex* vertices, std::size_t strideX, std::size_t strideY, unsigned int nFlagsX, unsigned int nFlagsY, PatchControl* subMatrix[3][3]); + + // uses binary trees representing bezier curves to recursively tesselate a bezier sub-patch + void TesselateSubMatrix( const BezierCurveTree *BX, const BezierCurveTree *BY, + std::size_t offStartX, std::size_t offStartY, + std::size_t offEndX, std::size_t offEndY, + std::size_t nFlagsX, std::size_t nFlagsY, + Vector3& left, Vector3& mid, Vector3& right, + Vector2& texLeft, Vector2& texMid, Vector2& texRight, + bool bTranspose ); + + // tesselates the entire surface + void BuildTesselationCurves(EMatrixMajor major); + void accumulateVertexTangentSpace(std::size_t index, Vector3 tangentX[6], Vector3 tangentY[6], Vector2 tangentS[6], Vector2 tangentT[6], std::size_t index0, std::size_t index1); + void BuildVertexArray(); +}; + +inline bool Patch_importHeader(Patch& patch, Tokeniser& tokeniser) +{ + tokeniser.nextLine(); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "{")); + return true; +} + +inline bool Patch_importShader(Patch& patch, Tokeniser& tokeniser) +{ + // parse shader name + tokeniser.nextLine(); + const char* texture = tokeniser.getToken(); + if(texture == 0) + { + Tokeniser_unexpectedError(tokeniser, texture, "#texture-name"); + return false; + } + if(string_equal(texture, "NULL")) + { + patch.SetShader(texdef_name_default()); + } + else + { + StringOutputStream shader(string_length(GlobalTexturePrefix_get()) + string_length(texture)); + shader << GlobalTexturePrefix_get() << texture; + patch.SetShader(shader.c_str()); + } + return true; +} + +inline bool PatchDoom3_importShader(Patch& patch, Tokeniser& tokeniser) +{ + // parse shader name + tokeniser.nextLine(); + const char *shader = tokeniser.getToken(); + if(shader == 0) + { + Tokeniser_unexpectedError(tokeniser, shader, "#shader-name"); + return false; + } + if(string_equal(shader, "_emptyname")) + { + shader = texdef_name_default(); + } + patch.SetShader(shader); + return true; +} + +inline bool Patch_importParams(Patch& patch, Tokeniser& tokeniser) +{ + tokeniser.nextLine(); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + + // parse matrix dimensions + { + std::size_t c, r; + RETURN_FALSE_IF_FAIL(Tokeniser_getSize(tokeniser, c)); + RETURN_FALSE_IF_FAIL(Tokeniser_getSize(tokeniser, r)); + + patch.setDims(c, r); + } + + if(patch.m_patchDef3) + { + RETURN_FALSE_IF_FAIL(Tokeniser_getSize(tokeniser, patch.m_subdivisions_x)); + RETURN_FALSE_IF_FAIL(Tokeniser_getSize(tokeniser, patch.m_subdivisions_y)); + } + + // ignore contents/flags/value + int tmp; + RETURN_FALSE_IF_FAIL(Tokeniser_getInteger(tokeniser, tmp)); + RETURN_FALSE_IF_FAIL(Tokeniser_getInteger(tokeniser, tmp)); + RETURN_FALSE_IF_FAIL(Tokeniser_getInteger(tokeniser, tmp)); + + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")")); + return true; +} + +inline bool Patch_importMatrix(Patch& patch, Tokeniser& tokeniser) +{ + // parse matrix + tokeniser.nextLine(); + RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "(")); + { + for(std::size_t c=0; cm_vertex, best); + if(best.valid()) + { + Selector_add(selector, m_selectable, best); + } + } + void snapto(float snap) + { + vector3_snap(m_ctrl->m_vertex, snap); + } +}; + + +class PatchInstance : +public Patch::Observer, +public scene::Instance, +public Selectable, +public Renderable, +public SelectionTestable, +public ComponentSelectionTestable, +public ComponentEditable, +public ComponentSnappable, +public PlaneSelectable, +public LightCullable +{ + class TypeCasts + { + InstanceTypeCastTable m_casts; + public: + TypeCasts() + { + InstanceStaticCast::install(m_casts); + InstanceContainedCast::install(m_casts); + InstanceContainedCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceStaticCast::install(m_casts); + InstanceIdentityCast::install(m_casts); + InstanceContainedCast::install(m_casts); + } + InstanceTypeCastTable& get() + { + return m_casts; + } + }; + + + Patch& m_patch; + typedef std::vector PatchControlInstances; + PatchControlInstances m_ctrl_instances; + + ObservedSelectable m_selectable; + + DragPlanes m_dragPlanes; + + mutable RenderablePointVector m_render_selected; + mutable AABB m_aabb_component; + + static Shader* m_state_selpoint; + + const LightList* m_lightList; + + TransformModifier m_transform; +public: + + typedef LazyStatic StaticTypeCasts; + + void lightsChanged() + { + m_lightList->lightsChanged(); + } + typedef MemberCaller LightsChangedCaller; + + STRING_CONSTANT(Name, "PatchInstance"); + + PatchInstance(const scene::Path& path, scene::Instance* parent, Patch& patch) : + Instance(path, parent, this, StaticTypeCasts::instance().get()), + m_patch(patch), + m_selectable(SelectedChangedCaller(*this)), + m_dragPlanes(SelectedChangedComponentCaller(*this)), + m_render_selected(GL_POINTS), + m_transform(Patch::TransformChangedCaller(m_patch), ApplyTransformCaller(*this)) + { + m_patch.instanceAttach(Instance::path()); + m_patch.attach(this); + + m_lightList = &GlobalShaderCache().attach(*this); + m_patch.m_lightsChanged = LightsChangedCaller(*this); + + Instance::setTransformChangedCallback(LightsChangedCaller(*this)); + } + ~PatchInstance() + { + Instance::setTransformChangedCallback(Callback()); + + m_patch.m_lightsChanged = Callback(); + GlobalShaderCache().detach(*this); + + m_patch.detach(this); + m_patch.instanceDetach(Instance::path()); + } + + void selectedChanged(const Selectable& selectable) + { + GlobalSelectionSystem().getObserver(SelectionSystem::ePrimitive)(selectable); + GlobalSelectionSystem().onSelectedChanged(*this, selectable); + + Instance::selectedChanged(); + } + typedef MemberCaller1 SelectedChangedCaller; + + void selectedChangedComponent(const Selectable& selectable) + { + GlobalSelectionSystem().getObserver(SelectionSystem::eComponent)(selectable); + GlobalSelectionSystem().onComponentSelection(*this, selectable); + } + typedef MemberCaller1 SelectedChangedComponentCaller; + + Patch& getPatch() + { + return m_patch; + } + Bounded& get(NullType) + { + return m_patch; + } + Cullable& get(NullType) + { + return m_patch; + } + Transformable& get(NullType) + { + return m_transform; + } + + static void constructStatic() + { + m_state_selpoint = GlobalShaderCache().capture("$SELPOINT"); + } + + static void destroyStatic() + { + GlobalShaderCache().release("$SELPOINT"); + } + + + void allocate(std::size_t size) + { + m_ctrl_instances.clear(); + m_ctrl_instances.reserve(size); + for(Patch::iterator i = m_patch.begin(); i != m_patch.end(); ++i) + { + m_ctrl_instances.push_back(PatchControlInstance(&(*i), SelectedChangedComponentCaller(*this))); + } + } + + void setSelected(bool select) + { + m_selectable.setSelected(select); + } + bool isSelected() const + { + return m_selectable.isSelected(); + } + + + void update_selected() const + { + m_render_selected.clear(); + Patch::iterator ctrl = m_patch.getControlPointsTransformed().begin(); + for(PatchControlInstances::const_iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i, ++ctrl) + { + if((*i).m_selectable.isSelected()) + { + const Colour4b colour_selected(0, 0, 255, 255); + m_render_selected.push_back(PointVertex(reinterpret_cast((*ctrl).m_vertex), colour_selected)); + } + } + } + +#if 0 + void render(Renderer& renderer, const VolumeTest& volume) const + { + if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent + && m_selectable.isSelected()) + { + renderer.Highlight(Renderer::eFace, false); + + m_patch.render(renderer, volume, localToWorld()); + + if(GlobalSelectionSystem().ComponentMode() == SelectionSystem::eVertex) + { + renderer.Highlight(Renderer::ePrimitive, false); + + m_patch.render_component(renderer, volume, localToWorld()); + + renderComponentsSelected(renderer, volume); + } + } + else + m_patch.render(renderer, volume, localToWorld()); + } +#endif + + void renderSolid(Renderer& renderer, const VolumeTest& volume) const + { + m_patch.evaluateTransform(); + renderer.setLights(*m_lightList); + m_patch.render_solid(renderer, volume, localToWorld()); + + renderComponentsSelected(renderer, volume); + } + + void renderWireframe(Renderer& renderer, const VolumeTest& volume) const + { + m_patch.evaluateTransform(); + m_patch.render_wireframe(renderer, volume, localToWorld()); + + renderComponentsSelected(renderer, volume); + } + + void renderComponentsSelected(Renderer& renderer, const VolumeTest& volume) const + { + m_patch.evaluateTransform(); + update_selected(); + if(!m_render_selected.empty()) + { + renderer.Highlight(Renderer::ePrimitive, false); + renderer.SetState(m_state_selpoint, Renderer::eWireframeOnly); + renderer.SetState(m_state_selpoint, Renderer::eFullMaterials); + renderer.addRenderable(m_render_selected, localToWorld()); + } + } + void renderComponents(Renderer& renderer, const VolumeTest& volume) const + { + m_patch.evaluateTransform(); + if(GlobalSelectionSystem().ComponentMode() == SelectionSystem::eVertex) + { + m_patch.render_component(renderer, volume, localToWorld()); + } + } + + void testSelect(Selector& selector, SelectionTest& test) + { + test.BeginMesh(localToWorld(), true); + m_patch.testSelect(selector, test); + } + + void selectCtrl(bool select) + { + for(PatchControlInstances::iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i) + { + (*i).m_selectable.setSelected(select); + } + } + bool isSelectedComponents() const + { + for(PatchControlInstances::const_iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i) + { + if((*i).m_selectable.isSelected()) + { + return true; + } + } + return false; + } + void setSelectedComponents(bool select, SelectionSystem::EComponentMode mode) + { + if(mode == SelectionSystem::eVertex) + { + selectCtrl(select); + } + else if(mode == SelectionSystem::eFace) + { + m_dragPlanes.setSelected(select); + } + } + const AABB& getSelectedComponentsBounds() const + { + m_aabb_component = AABB(); + + for(PatchControlInstances::const_iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i) + { + if((*i).m_selectable.isSelected()) + { + aabb_extend_by_point_safe(m_aabb_component, (*i).m_ctrl->m_vertex); + } + } + + return m_aabb_component; + } + + void testSelectComponents(Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode) + { + test.BeginMesh(localToWorld()); + + switch(mode) + { + case SelectionSystem::eVertex: + { + for(PatchControlInstances::iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i) + { + (*i).testSelect(selector, test); + } + } + break; + default: + break; + } + } + + bool selectedVertices() + { + for(PatchControlInstances::iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i) + { + if((*i).m_selectable.isSelected()) + { + return true; + } + } + return false; + } + + void transformComponents(const Matrix4& matrix) + { + if(selectedVertices()) + { + PatchControlIter ctrl = m_patch.getControlPointsTransformed().begin(); + for(PatchControlInstances::iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i, ++ctrl) + { + if((*i).m_selectable.isSelected()) + { + matrix4_transform_point(matrix, (*ctrl).m_vertex); + } + } + m_patch.UpdateCachedData(); + } + + if(m_dragPlanes.isSelected()) // this should only be true when the transform is a pure translation. + { + m_patch.transform(m_dragPlanes.evaluateTransform(vector4_to_vector3(matrix.t()))); + } + } + + + void selectPlanes(Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback) + { + test.BeginMesh(localToWorld()); + + m_dragPlanes.selectPlanes(m_patch.localAABB(), selector, test, selectedPlaneCallback); + } + void selectReversedPlanes(Selector& selector, const SelectedPlanes& selectedPlanes) + { + m_dragPlanes.selectReversedPlanes(m_patch.localAABB(), selector, selectedPlanes); + } + + + void snapComponents(float snap) + { + if(selectedVertices()) + { + m_patch.undoSave(); + for(PatchControlInstances::iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i) + { + if((*i).m_selectable.isSelected()) + { + (*i).snapto(snap); + } + } + m_patch.controlPointsChanged(); + } + } + + void evaluateTransform() + { + Matrix4 matrix(m_transform.calculateTransform()); + + if(m_transform.getType() == TRANSFORM_PRIMITIVE) + { + m_patch.transform(matrix); + } + else + { + transformComponents(matrix); + } + } + void applyTransform() + { + m_patch.revertTransform(); + evaluateTransform(); + m_patch.freezeTransform(); + } + typedef MemberCaller ApplyTransformCaller; + + + bool testLight(const RendererLight& light) const + { + return light.testAABB(worldAABB()); + } +}; + + +template +class PatchNode : + public scene::Node::Symbiot, + public scene::Instantiable, + public scene::Cloneable +{ + typedef PatchNode Self; + + class TypeCasts + { + InstanceTypeCastTable m_casts; + public: + TypeCasts() + { + NodeStaticCast::install(m_casts); + NodeStaticCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + NodeContainedCast::install(m_casts); + } + InstanceTypeCastTable& get() + { + return m_casts; + } + }; + + + scene::Node m_node; + InstanceSet m_instances; + Patch m_patch; + TokenImporter m_importMap; + TokenExporter m_exportMap; + +public: + + typedef LazyStatic StaticTypeCasts; + + Snappable& get(NullType) + { + return m_patch; + } + TransformNode& get(NullType) + { + return m_patch; + } + Patch& get(NullType) + { + return m_patch; + } + XMLImporter& get(NullType) + { + return m_patch; + } + XMLExporter& get(NullType) + { + return m_patch; + } + MapImporter& get(NullType) + { + return m_importMap; + } + MapExporter& get(NullType) + { + return m_exportMap; + } + Nameable& get(NullType) + { + return m_patch; + } + + PatchNode(bool patchDef3 = false) : + m_node(this, this, StaticTypeCasts::instance().get()), + m_patch(m_node, InstanceSetEvaluateTransform::Caller(m_instances), InstanceSet::BoundsChangedCaller(m_instances)), + m_importMap(m_patch), + m_exportMap(m_patch) + { + m_patch.m_patchDef3 = patchDef3; + } + PatchNode(const PatchNode& other) : + scene::Node::Symbiot(other), + scene::Instantiable(other), + scene::Cloneable(other), + m_node(this, this, StaticTypeCasts::instance().get()), + m_patch(other.m_patch, m_node, InstanceSetEvaluateTransform::Caller(m_instances), InstanceSet::BoundsChangedCaller(m_instances)), + m_importMap(m_patch), + m_exportMap(m_patch) + { + } + void release() + { + delete this; + } + scene::Node& node() + { + return m_node; + } + Patch& get() + { + return m_patch; + } + const Patch& get() const + { + return m_patch; + } + + scene::Node& clone() const + { + return (new PatchNode(*this))->node(); + } + + scene::Instance* create(const scene::Path& path, scene::Instance* parent) + { + return new PatchInstance(path, parent, m_patch); + } + void forEachInstance(const scene::Instantiable::Visitor& visitor) + { + m_instances.forEachInstance(visitor); + } + void insert(scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance) + { + m_instances.insert(observer, path, instance); + } + scene::Instance* erase(scene::Instantiable::Observer* observer, const scene::Path& path) + { + return m_instances.erase(observer, path); + } +}; + + + +typedef PatchNode PatchNodeQuake3; +typedef PatchNode PatchNodeDoom3; + +inline Patch* Node_getPatch(scene::Node& node) +{ + return NodeTypeCast::cast(node); +} + +inline PatchInstance* Instance_getPatch(scene::Instance& instance) +{ + return InstanceTypeCast::cast(instance); +} + +template +class PatchSelectedVisitor : public SelectionSystem::Visitor +{ + const Functor& m_functor; +public: + PatchSelectedVisitor(const Functor& functor) : m_functor(functor) + { + } + void visit(scene::Instance& instance) const + { + PatchInstance* patch = Instance_getPatch(instance); + if(patch != 0) + { + m_functor(*patch); + } + } +}; + +template +inline void Scene_forEachSelectedPatch(const Functor& functor) +{ + GlobalSelectionSystem().foreachSelected(PatchSelectedVisitor(functor)); +} + + +template +class PatchVisibleSelectedVisitor : public SelectionSystem::Visitor +{ + const Functor& m_functor; +public: + PatchVisibleSelectedVisitor(const Functor& functor) : m_functor(functor) + { + } + void visit(scene::Instance& instance) const + { + PatchInstance* patch = Instance_getPatch(instance); + if(patch != 0 + && instance.path().top().get().visible()) + { + m_functor(*patch); + } + } +}; + +template +inline void Scene_forEachVisibleSelectedPatchInstance(const Functor& functor) +{ + GlobalSelectionSystem().foreachSelected(PatchVisibleSelectedVisitor(functor)); +} + +template +class PatchForEachWalker : public scene::Graph::Walker +{ + const Functor& m_functor; +public: + PatchForEachWalker(const Functor& functor) : m_functor(functor) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + Patch* patch = Node_getPatch(path.top()); + if(patch != 0) + { + m_functor(*patch); + } + } + return true; + } +}; + +template +inline void Scene_forEachVisiblePatch(const Functor& functor) +{ + GlobalSceneGraph().traverse(PatchForEachWalker(functor)); +} + +template +class PatchForEachSelectedWalker : public scene::Graph::Walker +{ + const Functor& m_functor; +public: + PatchForEachSelectedWalker(const Functor& functor) : m_functor(functor) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + Patch* patch = Node_getPatch(path.top()); + if(patch != 0 + && Instance_getSelectable(instance)->isSelected()) + { + m_functor(*patch); + } + } + return true; + } +}; + +template +inline void Scene_forEachVisibleSelectedPatch(const Functor& functor) +{ + GlobalSceneGraph().traverse(PatchForEachSelectedWalker(functor)); +} + +template +class PatchForEachInstanceWalker : public scene::Graph::Walker +{ + const Functor& m_functor; +public: + PatchForEachInstanceWalker(const Functor& functor) : m_functor(functor) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + PatchInstance* patch = Instance_getPatch(instance); + if(patch != 0) + { + m_functor(*patch); + } + } + return true; + } +}; + +template +inline void Scene_forEachVisiblePatchInstance(const Functor& functor) +{ + GlobalSceneGraph().traverse(PatchForEachInstanceWalker(functor)); +} + +#endif diff --git a/radiant/patchdialog.cpp b/radiant/patchdialog.cpp new file mode 100644 index 00000000..6765ceeb --- /dev/null +++ b/radiant/patchdialog.cpp @@ -0,0 +1,1218 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// Patch Dialog +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "patchdialog.h" + +#include "itexdef.h" + +#include "debugging/debugging.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gtkutil/idledraw.h" +#include "gtkutil/entry.h" +#include "gtkutil/button.h" +#include "gtkutil/nonmodal.h" +#include "dialog.h" +#include "gtkdlgs.h" +#include "mainframe.h" +#include "patchmanip.h" +#include "patch.h" +#include "commands.h" +#include "preferences.h" +#include "signal/isignal.h" + + +#include + +// the increment we are using for the patch inspector (this is saved in the prefs) +struct pi_globals_t +{ + float shift[2]; + float scale[2]; + float rotate; + + pi_globals_t() + { + shift[0] = 8.0f; + shift[1] = 8.0f; + scale[0] = 0.5f; + scale[1] = 0.5f; + rotate = 45.0f; + } +}; + +pi_globals_t g_pi_globals; + +class PatchFixedSubdivisions +{ +public: + PatchFixedSubdivisions() : m_enabled(false), m_x(0), m_y(0) + { + } + PatchFixedSubdivisions(bool enabled, std::size_t x, std::size_t y) : m_enabled(enabled), m_x(x), m_y(y) + { + } + bool m_enabled; + std::size_t m_x; + std::size_t m_y; +}; + +void Patch_getFixedSubdivisions(const Patch& patch, PatchFixedSubdivisions& subdivisions) +{ + subdivisions.m_enabled = patch.m_patchDef3; + subdivisions.m_x = patch.m_subdivisions_x; + subdivisions.m_y = patch.m_subdivisions_y; +} + +const std::size_t MAX_PATCH_SUBDIVISIONS = 32; + +void Patch_setFixedSubdivisions(Patch& patch, const PatchFixedSubdivisions& subdivisions) +{ + patch.undoSave(); + + patch.m_patchDef3 = subdivisions.m_enabled; + patch.m_subdivisions_x = subdivisions.m_x; + patch.m_subdivisions_y = subdivisions.m_y; + + if(patch.m_subdivisions_x == 0) + { + patch.m_subdivisions_x = 4; + } + else if(patch.m_subdivisions_x > MAX_PATCH_SUBDIVISIONS) + { + patch.m_subdivisions_x = MAX_PATCH_SUBDIVISIONS; + } + if(patch.m_subdivisions_y == 0) + { + patch.m_subdivisions_y = 4; + } + else if(patch.m_subdivisions_y > MAX_PATCH_SUBDIVISIONS) + { + patch.m_subdivisions_y = MAX_PATCH_SUBDIVISIONS; + } + + SceneChangeNotify(); + Patch_textureChanged(); + patch.controlPointsChanged(); +} + +class PatchGetFixedSubdivisions +{ + PatchFixedSubdivisions& m_subdivisions; +public: + PatchGetFixedSubdivisions(PatchFixedSubdivisions& subdivisions) : m_subdivisions(subdivisions) + { + } + void operator()(Patch& patch) + { + Patch_getFixedSubdivisions(patch, m_subdivisions); + SceneChangeNotify(); + } +}; + +void Scene_PatchGetFixedSubdivisions(PatchFixedSubdivisions& subdivisions) +{ +#if 1 + if(GlobalSelectionSystem().countSelected() != 0) + { + Patch* patch = Node_getPatch(GlobalSelectionSystem().ultimateSelected().path().top()); + if(patch != 0) + { + Patch_getFixedSubdivisions(*patch, subdivisions); + } + } +#else + Scene_forEachVisibleSelectedPatch(PatchGetFixedSubdivisions(subdivisions)); +#endif +} + +class PatchSetFixedSubdivisions +{ + const PatchFixedSubdivisions& m_subdivisions; +public: + PatchSetFixedSubdivisions(const PatchFixedSubdivisions& subdivisions) : m_subdivisions(subdivisions) + { + } + void operator()(Patch& patch) const + { + Patch_setFixedSubdivisions(patch, m_subdivisions); + } +}; + +void Scene_PatchSetFixedSubdivisions(const PatchFixedSubdivisions& subdivisions) +{ + UndoableCommand command("patchSetFixedSubdivisions"); + Scene_forEachVisibleSelectedPatch(PatchSetFixedSubdivisions(subdivisions)); +} + +typedef struct _GtkCheckButton GtkCheckButton; + +class Subdivisions +{ +public: + GtkCheckButton* m_enabled; + GtkEntry* m_horizontal; + GtkEntry* m_vertical; + Subdivisions() : m_enabled(0), m_horizontal(0), m_vertical(0) + { + } + void update() + { + PatchFixedSubdivisions subdivisions; + Scene_PatchGetFixedSubdivisions(subdivisions); + + toggle_button_set_active_no_signal(GTK_TOGGLE_BUTTON(m_enabled), subdivisions.m_enabled); + + if(subdivisions.m_enabled) + { + entry_set_int(m_horizontal, static_cast(subdivisions.m_x)); + entry_set_int(m_vertical, static_cast(subdivisions.m_y)); + gtk_widget_set_sensitive(GTK_WIDGET(m_horizontal), TRUE); + gtk_widget_set_sensitive(GTK_WIDGET(m_vertical), TRUE); + } + else + { + gtk_entry_set_text(m_horizontal, ""); + gtk_entry_set_text(m_vertical, ""); + gtk_widget_set_sensitive(GTK_WIDGET(m_horizontal), FALSE); + gtk_widget_set_sensitive(GTK_WIDGET(m_vertical), FALSE); + } + } + void cancel() + { + update(); + } + typedef MemberCaller CancelCaller; + void apply() + { + Scene_PatchSetFixedSubdivisions( + PatchFixedSubdivisions( + gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_enabled)) != FALSE, + static_cast(entry_get_int(m_horizontal)), + static_cast(entry_get_int(m_vertical)) + ) + ); + } + typedef MemberCaller ApplyCaller; + static void applyGtk(GtkToggleButton* toggle, Subdivisions* self) + { + self->apply(); + } +}; + +class PatchInspector : public Dialog +{ + GtkWindow* BuildDialog(); + Subdivisions m_subdivisions; + NonModalEntry m_horizontalSubdivisionsEntry; + NonModalEntry m_verticalSubdivisionsEntry; +public: + IdleDraw m_idleDraw; + WindowPositionTracker m_position_tracker; + + Patch *m_Patch; + + CopiedString m_strName; + float m_fS; + float m_fT; + float m_fX; + float m_fY; + float m_fZ; +/* float m_fHScale; + float m_fHShift; + float m_fRotate; + float m_fVScale; + float m_fVShift; */ + int m_nCol; + int m_nRow; + GtkComboBox *m_pRowCombo; + GtkComboBox *m_pColCombo; + std::size_t m_countRows; + std::size_t m_countCols; + + // turn on/off processing of the "changed" "value_changed" messages + // (need to turn off when we are feeding data in) + // NOTE: much more simple than blocking signals + bool m_bListenChanged; + + PatchInspector() : + m_horizontalSubdivisionsEntry(Subdivisions::ApplyCaller(m_subdivisions), Subdivisions::CancelCaller(m_subdivisions)), + m_verticalSubdivisionsEntry(Subdivisions::ApplyCaller(m_subdivisions), Subdivisions::CancelCaller(m_subdivisions)), + m_idleDraw(MemberCaller(*this)) + { + m_fS = 0.0f; + m_fT = 0.0f; + m_fX = 0.0f; + m_fY = 0.0f; + m_fZ = 0.0f; + m_nCol = 0; + m_nRow = 0; + m_countRows = 0; + m_countCols = 0; + m_Patch = 0; + m_bListenChanged = true; + + m_position_tracker.setPosition(c_default_window_pos); + } + + bool visible() + { + return GTK_WIDGET_VISIBLE(GetWidget()); + } + +// void UpdateInfo(); +// void SetPatchInfo(); + void GetPatchInfo(); + void UpdateSpinners(bool bUp, int nID); + // read the current patch on map and initialize m_fX m_fY accordingly + void UpdateRowColInfo(); + // sync the dialog our internal data structures + // depending on the flag it will read or write + // we use m_nCol m_nRow m_fX m_fY m_fZ m_fS m_fT m_strName + // (NOTE: this doesn't actually commit stuff to the map or read from it) + void importData(); + void exportData(); +}; + +PatchInspector g_PatchInspector; + +void PatchInspector_constructWindow(GtkWindow* main_window) +{ + g_PatchInspector.m_parent = main_window; + g_PatchInspector.Create(); +} +void PatchInspector_destroyWindow() +{ + g_PatchInspector.Destroy(); +} + +void PatchInspector_queueDraw() +{ + if(g_PatchInspector.visible()) + { + g_PatchInspector.m_idleDraw.queueDraw(); + } +} + +void DoPatchInspector() +{ + g_PatchInspector.GetPatchInfo(); + if (!g_PatchInspector.visible()) + g_PatchInspector.ShowDlg(); +} + +void PatchInspector_toggleShown() +{ + if (g_PatchInspector.visible()) + { + g_PatchInspector.m_Patch = 0; + g_PatchInspector.HideDlg(); + } + else + DoPatchInspector(); +} + + +// ============================================================================= +// static functions + +// memorize the current state (that is don't try to undo our do before changing something else) +static void OnApply (GtkWidget *widget, gpointer data) +{ + g_PatchInspector.exportData(); + if (g_PatchInspector.m_Patch != 0) + { + UndoableCommand command("patchSetTexture"); + g_PatchInspector.m_Patch->undoSave(); + + if (!texdef_name_valid(g_PatchInspector.m_strName.c_str())) + { + globalErrorStream() << "invalid texture name '" << g_PatchInspector.m_strName.c_str() << "'\n"; + g_PatchInspector.m_strName = texdef_name_default(); + } + g_PatchInspector.m_Patch->SetShader(g_PatchInspector.m_strName.c_str()); + + std::size_t r = g_PatchInspector.m_nRow; + std::size_t c = g_PatchInspector.m_nCol; + if(r < g_PatchInspector.m_Patch->getHeight() + && c < g_PatchInspector.m_Patch->getWidth()) + { + PatchControl& p = g_PatchInspector.m_Patch->ctrlAt(r,c); + p.m_vertex[0] = g_PatchInspector.m_fX; + p.m_vertex[1] = g_PatchInspector.m_fY; + p.m_vertex[2] = g_PatchInspector.m_fZ; + p.m_texcoord[0] = g_PatchInspector.m_fS; + p.m_texcoord[1] = g_PatchInspector.m_fT; + g_PatchInspector.m_Patch->controlPointsChanged(); + } + } +} + +static void OnSelchangeComboColRow (GtkWidget *widget, gpointer data) +{ + if (!g_PatchInspector.m_bListenChanged) + return; + // retrieve the current m_nRow and m_nCol, other params are not relevant + g_PatchInspector.exportData(); + // read the changed values ourselves + g_PatchInspector.UpdateRowColInfo(); + // now reflect our changes + g_PatchInspector.importData(); +} + +class PatchSetTextureRepeat +{ + float m_s, m_t; +public: + PatchSetTextureRepeat(float s, float t) : m_s(s), m_t(t) + { + } + void operator()(Patch& patch) const + { + patch.SetTextureRepeat(m_s, m_t); + } +}; + +void Scene_PatchTileTexture_Selected(scene::Graph& graph, float s, float t) +{ + Scene_forEachVisibleSelectedPatch(PatchSetTextureRepeat(s, t)); + SceneChangeNotify(); +} + +static void OnBtnPatchdetails (GtkWidget *widget, gpointer data) +{ + UndoableCommand command("patchCapTexture"); + + Scene_PatchCapTexture_Selected(GlobalSceneGraph()); +} + +static void OnBtnPatchfit (GtkWidget *widget, gpointer data) +{ + UndoableCommand command("patchFitTexture"); + + Scene_PatchTileTexture_Selected(GlobalSceneGraph(), 1, 1); +} + +static void OnBtnPatchnatural (GtkWidget *widget, gpointer data) +{ + UndoableCommand command("patchNaturalTexture"); + + Scene_PatchNaturalTexture_Selected(GlobalSceneGraph()); +} + +static void OnBtnPatchreset (GtkWidget *widget, gpointer data) +{ + float fx, fy; + if (DoTextureLayout (&fx, &fy) == eIDOK) + { + UndoableCommand command("patchTileTexture"); + Scene_PatchTileTexture_Selected(GlobalSceneGraph(), fx, fy); + } +} + +struct PatchRotateTexture +{ + float m_angle; +public: + PatchRotateTexture(float angle) : m_angle(angle) + { + } + void operator()(Patch& patch) const + { + patch.RotateTexture(m_angle); + } +}; + +void Scene_PatchRotateTexture_Selected(scene::Graph& graph, float angle) +{ + Scene_forEachVisibleSelectedPatch(PatchRotateTexture(angle)); +} + +class PatchScaleTexture +{ + float m_s, m_t; +public: + PatchScaleTexture(float s, float t) : m_s(s), m_t(t) + { + } + void operator()(Patch& patch) const + { + patch.ScaleTexture(m_s, m_t); + } +}; + +float Patch_convertScale(float scale) +{ + if(scale > 0) + { + return scale; + } + if(scale < 0) + { + return -1 / scale; + } + return 1; +} + +void Scene_PatchScaleTexture_Selected(scene::Graph& graph, float s, float t) +{ + Scene_forEachVisibleSelectedPatch(PatchScaleTexture(Patch_convertScale(s), Patch_convertScale(t))); +} + +class PatchTranslateTexture +{ + float m_s, m_t; +public: + PatchTranslateTexture(float s, float t) + : m_s(s), m_t(t) + { + } + void operator()(Patch& patch) const + { + patch.TranslateTexture(m_s, m_t); + } +}; + +void Scene_PatchTranslateTexture_Selected(scene::Graph& graph, float s, float t) +{ + Scene_forEachVisibleSelectedPatch(PatchTranslateTexture(s, t)); +} + +static void OnSpinChanged (GtkAdjustment *adj, gpointer data) +{ + texdef_t td; + + td.rotate = 0; + td.scale[0] = td.scale[1] = 0; + td.shift[0] = td.shift[1] = 0; + + if (adj->value == 0) + return; + + if (adj == g_object_get_data (G_OBJECT (g_PatchInspector.GetWidget()), "hshift_adj")) + { + g_pi_globals.shift[0] = static_cast(atof (gtk_entry_get_text (GTK_ENTRY (data)))); + + if (adj->value > 0) + td.shift[0] = g_pi_globals.shift[0]; + else + td.shift[0] = -g_pi_globals.shift[0]; + } + else if (adj == g_object_get_data (G_OBJECT (g_PatchInspector.GetWidget()), "vshift_adj")) + { + g_pi_globals.shift[1] = static_cast(atof (gtk_entry_get_text (GTK_ENTRY (data)))); + + if (adj->value > 0) + td.shift[1] = g_pi_globals.shift[1]; + else + td.shift[1] = -g_pi_globals.shift[1]; + } + else if (adj == g_object_get_data (G_OBJECT (g_PatchInspector.GetWidget()), "hscale_adj")) + { + g_pi_globals.scale[0] = static_cast(atof (gtk_entry_get_text (GTK_ENTRY (data)))); + if (g_pi_globals.scale[0] == 0.0f) + return; + if (adj->value > 0) + td.scale[0] = g_pi_globals.scale[0]; + else + td.scale[0] = -g_pi_globals.scale[0]; + } + else if (adj == g_object_get_data (G_OBJECT (g_PatchInspector.GetWidget()), "vscale_adj")) + { + g_pi_globals.scale[1] = static_cast(atof (gtk_entry_get_text (GTK_ENTRY (data)))); + if (g_pi_globals.scale[1] == 0.0f) + return; + if (adj->value > 0) + td.scale[1] = g_pi_globals.scale[1]; + else + td.scale[1] = -g_pi_globals.scale[1]; + } + else if (adj == g_object_get_data (G_OBJECT (g_PatchInspector.GetWidget()), "rotate_adj")) + { + g_pi_globals.rotate = static_cast(atof (gtk_entry_get_text (GTK_ENTRY (data)))); + + if (adj->value > 0) + td.rotate = g_pi_globals.rotate; + else + td.rotate = -g_pi_globals.rotate; + } + + adj->value = 0; + + // will scale shift rotate the patch accordingly + + + if (td.shift[0] || td.shift[1]) + { + UndoableCommand command("patchTranslateTexture"); + Scene_PatchTranslateTexture_Selected (GlobalSceneGraph(), td.shift[0], td.shift[1]); + } + else if (td.scale[0] || td.scale[1]) + { + UndoableCommand command("patchScaleTexture"); + Scene_PatchScaleTexture_Selected (GlobalSceneGraph(), td.scale[0], td.scale[1]); + } + else if (td.rotate) + { + UndoableCommand command("patchRotateTexture"); + Scene_PatchRotateTexture_Selected (GlobalSceneGraph(), td.rotate); + } + + // update the point-by-point view + OnSelchangeComboColRow(0,0); +} + +static gint OnDialogKey (GtkWidget* widget, GdkEventKey* event, gpointer data) +{ + if (event->keyval == GDK_Return) + { + OnApply (0, 0); + return TRUE; + } + else if (event->keyval == GDK_Escape) + { + g_PatchInspector.GetPatchInfo(); + return TRUE; + } + return FALSE; +} + +// ============================================================================= +// PatchInspector class + +GtkWindow* PatchInspector::BuildDialog() +{ + GtkWindow* window = create_floating_window("Patch Properties", m_parent); + + m_position_tracker.connect(window); + + global_accel_connect_window(window); + + window_connect_focus_in_clear_focus_widget(window); + + + { + GtkVBox* vbox = GTK_VBOX(gtk_vbox_new(FALSE, 5)); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 5); + gtk_widget_show(GTK_WIDGET(vbox)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox)); + { + GtkHBox* hbox = GTK_HBOX(gtk_hbox_new(FALSE, 5)); + gtk_widget_show(GTK_WIDGET(hbox)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), TRUE, TRUE, 0); + { + GtkVBox* vbox2 = GTK_VBOX(gtk_vbox_new(FALSE, 0)); + gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0); + gtk_widget_show(GTK_WIDGET(vbox2)); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox2), TRUE, TRUE, 0); + { + GtkFrame* frame = GTK_FRAME(gtk_frame_new("Details")); + gtk_widget_show(GTK_WIDGET(frame)); + gtk_box_pack_start(GTK_BOX(vbox2), GTK_WIDGET(frame), TRUE, TRUE, 0); + { + GtkVBox* vbox3 = GTK_VBOX(gtk_vbox_new(FALSE, 5)); + gtk_container_set_border_width(GTK_CONTAINER(vbox3), 5); + gtk_widget_show(GTK_WIDGET(vbox3)); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(vbox3)); + { + GtkTable* table = GTK_TABLE(gtk_table_new(2, 2, FALSE)); + gtk_widget_show(GTK_WIDGET(table)); + gtk_box_pack_start(GTK_BOX(vbox3), GTK_WIDGET(table), TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Row:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 0, 1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Column:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 1, 2, 0, 1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + } + { + GtkComboBox* combo = GTK_COMBO_BOX(gtk_combo_box_new_text()); + g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(OnSelchangeComboColRow), this); + AddDialogData(*combo, m_nRow); + + gtk_widget_show(GTK_WIDGET(combo)); + gtk_table_attach(table, GTK_WIDGET(combo), 0, 1, 1, 2, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(combo), 60, -1); + m_pRowCombo = combo; + } + + { + GtkComboBox* combo = GTK_COMBO_BOX(gtk_combo_box_new_text()); + g_signal_connect(G_OBJECT(combo), "changed", G_CALLBACK(OnSelchangeComboColRow), this); + AddDialogData(*combo, m_nCol); + + gtk_widget_show(GTK_WIDGET(combo)); + gtk_table_attach(table, GTK_WIDGET(combo), 1, 2, 1, 2, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(combo), 60, -1); + m_pColCombo = combo; + } + } + GtkTable* table = GTK_TABLE(gtk_table_new(5, 2, FALSE)); + gtk_widget_show(GTK_WIDGET(table)); + gtk_box_pack_start(GTK_BOX(vbox3), GTK_WIDGET(table), TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + { + GtkLabel* label = GTK_LABEL(gtk_label_new("X:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 0, 1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Y:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 1, 2, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Z:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 2, 3, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("S:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 3, 4, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("T:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 4, 5, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 0, 1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + AddDialogData(*entry, m_fX); + + g_signal_connect(G_OBJECT(entry), "key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + AddDialogData(*entry, m_fY); + + g_signal_connect(G_OBJECT(entry), "key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 2, 3, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + AddDialogData(*entry, m_fZ); + + g_signal_connect(G_OBJECT(entry), "key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 3, 4, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + AddDialogData(*entry, m_fS); + + g_signal_connect(G_OBJECT(entry), "key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 4, 5, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + AddDialogData(*entry, m_fT); + + g_signal_connect(G_OBJECT(entry), "key_press_event", G_CALLBACK(OnDialogKey), 0); + } + } + } + if(g_pGameDescription->mGameType == "doom3") + { + GtkFrame* frame = GTK_FRAME(gtk_frame_new("Tesselation")); + gtk_widget_show(GTK_WIDGET(frame)); + gtk_box_pack_start(GTK_BOX(vbox2), GTK_WIDGET(frame), TRUE, TRUE, 0); + { + GtkVBox* vbox3 = GTK_VBOX(gtk_vbox_new(FALSE, 5)); + gtk_container_set_border_width(GTK_CONTAINER(vbox3), 5); + gtk_widget_show(GTK_WIDGET(vbox3)); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(vbox3)); + { + GtkTable* table = GTK_TABLE(gtk_table_new(3, 2, FALSE)); + gtk_widget_show(GTK_WIDGET(table)); + gtk_box_pack_start(GTK_BOX(vbox3), GTK_WIDGET(table), TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Fixed")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 0, 1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + } + { + GtkCheckButton* check = GTK_CHECK_BUTTON(gtk_check_button_new()); + gtk_widget_show(GTK_WIDGET(check)); + gtk_table_attach(table, GTK_WIDGET(check), 1, 2, 0, 1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + m_subdivisions.m_enabled = check; + guint handler_id = g_signal_connect(G_OBJECT(check), "toggled", G_CALLBACK(&Subdivisions::applyGtk), &m_subdivisions); + g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(handler_id)); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Horizontal")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 1, 2, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 1, 2, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + m_subdivisions.m_horizontal = entry; + m_horizontalSubdivisionsEntry.connect(entry); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Vertical")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 2, 3, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 1, 2, 2, 3, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + m_subdivisions.m_vertical = entry; + m_verticalSubdivisionsEntry.connect(entry); + } + } + } + } + } + { + GtkFrame* frame = GTK_FRAME(gtk_frame_new("Texturing")); + gtk_widget_show(GTK_WIDGET(frame)); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(frame), TRUE, TRUE, 0); + { + GtkVBox* vbox2 = GTK_VBOX(gtk_vbox_new(FALSE, 5)); + gtk_widget_show(GTK_WIDGET(vbox2)); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(vbox2)); + gtk_container_set_border_width(GTK_CONTAINER(vbox2), 5); + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Name:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_box_pack_start(GTK_BOX(vbox2), GTK_WIDGET(label), TRUE, TRUE, 0); + gtk_label_set_justify(label, GTK_JUSTIFY_LEFT); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + // gtk_entry_set_editable (GTK_ENTRY (entry), false); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_box_pack_start(GTK_BOX(vbox2), GTK_WIDGET(entry), TRUE, TRUE, 0); + AddDialogData(*entry, m_strName); + + g_signal_connect(G_OBJECT(entry), "key_press_event", G_CALLBACK(OnDialogKey), 0); + } + { + GtkTable* table = GTK_TABLE(gtk_table_new(5, 3, FALSE)); + gtk_widget_show(GTK_WIDGET(table)); + gtk_box_pack_start(GTK_BOX(vbox2), GTK_WIDGET(table), TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Horizontal Shift Step")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 2, 3, 0, 1, + (GtkAttachOptions)(GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Vertical Shift Step")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 2, 3, 1, 2, + (GtkAttachOptions)(GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Horizontal Stretch Step")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 2, 3, 2, 3, + (GtkAttachOptions)(GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Vertical Stretch Step")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 2, 3, 3, 4, + (GtkAttachOptions)(GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Rotate Step")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 2, 3, 4, 5, + (GtkAttachOptions)(GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 0, 1, 0, 1, + (GtkAttachOptions)(GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(entry), 50, -2); + g_object_set_data(G_OBJECT(window), "hshift_entry", entry); + // we fill in this data, if no patch is selected the widgets are unmodified when the inspector is raised + // so we need to have at least one initialisation somewhere + entry_set_float(entry, g_pi_globals.shift[0]); + + GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -8192, 8192, 1, 1, 1)); + g_signal_connect(G_OBJECT(adj), "value_changed", G_CALLBACK(OnSpinChanged), entry); + g_object_set_data(G_OBJECT(window), "hshift_adj", adj); + + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0)); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 0, 1, + (GtkAttachOptions)(0), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(spin), 10, -2); + GTK_WIDGET_UNSET_FLAGS(spin, GTK_CAN_FOCUS); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 0, 1, 1, 2, + (GtkAttachOptions)(GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(entry), 50, -2); + entry_set_float(entry, g_pi_globals.shift[1]); + + GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -8192, 8192, 1, 1, 1)); + g_signal_connect(G_OBJECT(adj), "value_changed", G_CALLBACK(OnSpinChanged), entry); + g_object_set_data(G_OBJECT(window), "vshift_adj", adj); + + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0)); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 1, 2, + (GtkAttachOptions)(0), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(spin), 10, -2); + GTK_WIDGET_UNSET_FLAGS(spin, GTK_CAN_FOCUS); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 0, 1, 2, 3, + (GtkAttachOptions)(GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(entry), 50, -2); + entry_set_float(entry, g_pi_globals.scale[0]); + + GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -1000, 1000, 1, 1, 1)); + g_signal_connect(G_OBJECT(adj), "value_changed", G_CALLBACK(OnSpinChanged), entry); + g_object_set_data(G_OBJECT(window), "hscale_adj", adj); + + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0)); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 2, 3, + (GtkAttachOptions)(0), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(spin), 10, -2); + GTK_WIDGET_UNSET_FLAGS(spin, GTK_CAN_FOCUS); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 0, 1, 3, 4, + (GtkAttachOptions)(GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(entry), 50, -2); + entry_set_float(entry, g_pi_globals.scale[1]); + + GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -1000, 1000, 1, 1, 1)); + g_signal_connect(G_OBJECT(adj), "value_changed", G_CALLBACK(OnSpinChanged), entry); + g_object_set_data(G_OBJECT(window), "vscale_adj", adj); + + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0)); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 3, 4, + (GtkAttachOptions)(0), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(spin), 10, -2); + GTK_WIDGET_UNSET_FLAGS(spin, GTK_CAN_FOCUS); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(table, GTK_WIDGET(entry), 0, 1, 4, 5, + (GtkAttachOptions)(GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(entry), 50, -2); + entry_set_float(entry, g_pi_globals.rotate); + + GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -1000, 1000, 1, 1, 1)); // NOTE: Arnout - this really should be 360 but can't change it anymore as it could break existing maps + g_signal_connect(G_OBJECT(adj), "value_changed", G_CALLBACK(OnSpinChanged), entry); + g_object_set_data(G_OBJECT(window), "rotate_adj", adj); + + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0)); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 4, 5, + (GtkAttachOptions)(0), + (GtkAttachOptions)(0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(spin), 10, -2); + GTK_WIDGET_UNSET_FLAGS(spin, GTK_CAN_FOCUS); + } + } + GtkHBox* hbox2 = GTK_HBOX(gtk_hbox_new(TRUE, 5)); + gtk_widget_show(GTK_WIDGET(hbox2)); + gtk_box_pack_start(GTK_BOX(vbox2), GTK_WIDGET(hbox2), TRUE, FALSE, 0); + { + GtkButton* button = GTK_BUTTON(gtk_button_new_with_label("CAP")); + gtk_widget_show(GTK_WIDGET(button)); + gtk_box_pack_end(GTK_BOX(hbox2), GTK_WIDGET(button), TRUE, FALSE, 0); + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(OnBtnPatchdetails), 0); + gtk_widget_set_usize(GTK_WIDGET(button), 60, -1); + } + { + GtkButton* button = GTK_BUTTON(gtk_button_new_with_label("Set...")); + gtk_widget_show(GTK_WIDGET(button)); + gtk_box_pack_end(GTK_BOX(hbox2), GTK_WIDGET(button), TRUE, FALSE, 0); + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(OnBtnPatchreset), 0); + gtk_widget_set_usize(GTK_WIDGET(button), 60, -1); + } + { + GtkButton* button = GTK_BUTTON(gtk_button_new_with_label("Natural")); + gtk_widget_show(GTK_WIDGET(button)); + gtk_box_pack_end(GTK_BOX(hbox2), GTK_WIDGET(button), TRUE, FALSE, 0); + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(OnBtnPatchnatural), 0); + gtk_widget_set_usize(GTK_WIDGET(button), 60, -1); + } + { + GtkButton* button = GTK_BUTTON(gtk_button_new_with_label("Fit")); + gtk_widget_show(GTK_WIDGET(button)); + gtk_box_pack_end(GTK_BOX(hbox2), GTK_WIDGET(button), TRUE, FALSE, 0); + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(OnBtnPatchfit), 0); + gtk_widget_set_usize(GTK_WIDGET(button), 60, -1); + } + } + } + } + } + + return window; +} + +// sync the dialog our internal data structures +void PatchInspector::exportData() +{ + m_bListenChanged = false; + Dialog::exportData(); + m_bListenChanged = true; +} +void PatchInspector::importData() +{ + m_bListenChanged = false; + Dialog::importData(); + m_bListenChanged = true; +} + +// read the map and feed in the stuff to the dialog box +void PatchInspector::GetPatchInfo() +{ + if(g_pGameDescription->mGameType == "doom3") + { + m_subdivisions.update(); + } + + if(GlobalSelectionSystem().countSelected() == 0) + { + m_Patch = 0; + } + else + { + m_Patch = Node_getPatch(GlobalSelectionSystem().ultimateSelected().path().top()); + } + + if (m_Patch != 0) + { + m_strName = m_Patch->GetShader(); + + // fill in the numbers for Row / Col selection + m_bListenChanged = false; + + { + gtk_combo_box_set_active(m_pRowCombo, 0); + + for (std::size_t i = 0; i < m_countRows; ++i) + { + gtk_combo_box_remove_text(m_pRowCombo, gint(m_countRows - i - 1)); + } + + m_countRows = m_Patch->getHeight(); + for (std::size_t i = 0; i < m_countRows; ++i) + { + char buffer[16]; + sprintf(buffer, "%u", Unsigned(i)); + gtk_combo_box_append_text(m_pRowCombo, buffer); + } + + gtk_combo_box_set_active(m_pRowCombo, 0); + } + + { + gtk_combo_box_set_active(m_pColCombo, 0); + + for (std::size_t i = 0; i < m_countCols; ++i) + { + gtk_combo_box_remove_text(m_pColCombo, gint(m_countCols - i - 1)); + } + + m_countCols = m_Patch->getWidth(); + for (std::size_t i = 0; i < m_countCols; ++i) + { + char buffer[16]; + sprintf(buffer, "%u", Unsigned(i)); + gtk_combo_box_append_text(m_pColCombo, buffer); + } + + gtk_combo_box_set_active(m_pColCombo, 0); + } + + m_bListenChanged = true; + + } + else + { + //globalOutputStream() << "WARNING: no patch\n"; + } + // fill in our internal structs + m_nRow = 0; m_nCol = 0; + UpdateRowColInfo(); + // now update the dialog box + importData(); +} + +// read the current patch on map and initialize m_fX m_fY accordingly +// NOTE: don't call UpdateData in there, it's not meant for +void PatchInspector::UpdateRowColInfo() +{ + m_fX = m_fY = m_fZ = m_fS = m_fT = 0.0; + + if (m_Patch != 0) + { + // we rely on whatever active row/column has been set before we get called + std::size_t r = m_nRow; + std::size_t c = m_nCol; + if(r < m_Patch->getHeight() + && c < m_Patch->getWidth()) + { + const PatchControl& p = m_Patch->ctrlAt(r,c); + m_fX = p.m_vertex[0]; + m_fY = p.m_vertex[1]; + m_fZ = p.m_vertex[2]; + m_fS = p.m_texcoord[0]; + m_fT = p.m_texcoord[1]; + } + } +} + + +void PatchInspector_SelectionChanged(const Selectable& selectable) +{ + PatchInspector_queueDraw(); +} + + +#include "preferencesystem.h" + + +void PatchInspector_Construct() +{ + GlobalCommands_insert("PatchInspector", FreeCaller(), Accelerator('S', (GdkModifierType)GDK_SHIFT_MASK)); + + GlobalPreferenceSystem().registerPreference("PatchWnd", WindowPositionTrackerImportStringCaller(g_PatchInspector.m_position_tracker), WindowPositionTrackerExportStringCaller(g_PatchInspector.m_position_tracker)); + GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Scale1", FloatImportStringCaller(g_pi_globals.scale[0]), FloatExportStringCaller(g_pi_globals.scale[0])); + GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Scale2", FloatImportStringCaller(g_pi_globals.scale[1]), FloatExportStringCaller(g_pi_globals.scale[1])); + GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Shift1", FloatImportStringCaller(g_pi_globals.shift[0]), FloatExportStringCaller(g_pi_globals.shift[0])); + GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Shift2", FloatImportStringCaller(g_pi_globals.shift[1]), FloatExportStringCaller(g_pi_globals.shift[1])); + GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Rotate", FloatImportStringCaller(g_pi_globals.rotate), FloatExportStringCaller(g_pi_globals.rotate)); + + typedef FreeCaller1 PatchInspectorSelectionChangedCaller; + GlobalSelectionSystem().addSelectionChangeCallback(PatchInspectorSelectionChangedCaller()); + typedef FreeCaller PatchInspectorQueueDrawCaller; + Patch_addTextureChangedCallback(PatchInspectorQueueDrawCaller()); +} +void PatchInspector_Destroy() +{ +} diff --git a/radiant/patchdialog.h b/radiant/patchdialog.h new file mode 100644 index 00000000..61bcb07b --- /dev/null +++ b/radiant/patchdialog.h @@ -0,0 +1,43 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_PATCHDIALOG_H) +#define INCLUDED_PATCHDIALOG_H + +void PatchInspector_Construct(); +void PatchInspector_Destroy(); + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkWindow GtkWindow; +void PatchInspector_constructWindow(GtkWindow* main_window); +void PatchInspector_destroyWindow(); + +namespace scene +{ + class Graph; +} + +void Scene_PatchTranslateTexture_Selected(scene::Graph& graph, float s, float t); +void Scene_PatchRotateTexture_Selected(scene::Graph& graph, float angle); +void Scene_PatchScaleTexture_Selected(scene::Graph& graph, float s, float t); + + +#endif diff --git a/radiant/patchmanip.cpp b/radiant/patchmanip.cpp new file mode 100644 index 00000000..42129065 --- /dev/null +++ b/radiant/patchmanip.cpp @@ -0,0 +1,1085 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "patchmanip.h" + +#include "debugging/debugging.h" + + +#include "iselection.h" +#include "ipatch.h" + +#include "math/vector.h" +#include "math/aabb.h" +#include "generic/callback.h" + +#include "gtkutil/menu.h" +#include "gtkutil/image.h" +#include "map.h" +#include "mainframe.h" +#include "commands.h" +#include "gtkmisc.h" +#include "gtkdlgs.h" +#include "texwindow.h" +#include "xywindow.h" +#include "select.h" +#include "patch.h" +#include "grid.h" + +PatchCreator* g_patchCreator = 0; + +void Scene_PatchConstructPrefab(scene::Graph& graph, const AABB aabb, const char* shader, EPatchPrefab eType, int axis, std::size_t width = 3, std::size_t height = 3) +{ + Select_Delete(); + GlobalSelectionSystem().setSelectedAll(false); + + NodeSmartReference node(g_patchCreator->createPatch()); + Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(node); + + Patch* patch = Node_getPatch(node); + patch->SetShader(shader); + + patch->ConstructPrefab(aabb, eType, axis, width, height); + patch->controlPointsChanged(); + + { + scene::Path patchpath(makeReference(GlobalSceneGraph().root())); + patchpath.push(makeReference(*Map_GetWorldspawn(g_map))); + patchpath.push(makeReference(node.get())); + Instance_getSelectable(*graph.find(patchpath))->setSelected(true); + } +} + + +void Patch_makeCaps(Patch& patch, scene::Instance& instance, EPatchCap type, const char* shader) +{ + if((type == eCapEndCap || type == eCapIEndCap) + && patch.getWidth() != 5) + { + globalErrorStream() << "cannot create end-cap - patch width != 5\n"; + return; + } + if((type == eCapBevel || type == eCapIBevel) + && patch.getWidth() != 3 && patch.getWidth() != 5) + { + globalErrorStream() << "cannot create bevel-cap - patch width != 3\n"; + return; + } + if(type == eCapCylinder + && patch.getWidth() != 9) + { + globalErrorStream() << "cannot create cylinder-cap - patch width != 9\n"; + return; + } + + { + NodeSmartReference cap(g_patchCreator->createPatch()); + Node_getTraversable(instance.path().parent())->insert(cap); + + patch.MakeCap(Node_getPatch(cap), type, ROW, true); + Node_getPatch(cap)->SetShader(shader); + + scene::Path path(instance.path()); + path.pop(); + path.push(makeReference(cap.get())); + selectPath(path, true); + } + + { + NodeSmartReference cap(g_patchCreator->createPatch()); + Node_getTraversable(instance.path().parent())->insert(cap); + + patch.MakeCap(Node_getPatch(cap), type, ROW, false); + Node_getPatch(cap)->SetShader(shader); + + scene::Path path(instance.path()); + path.pop(); + path.push(makeReference(cap.get())); + selectPath(path, true); + } +} + +typedef std::vector InstanceVector; + +class PatchStoreInstance +{ + InstanceVector& m_instances; +public: + PatchStoreInstance(InstanceVector& instances) : m_instances(instances) + { + } + void operator()(PatchInstance& patch) const + { + m_instances.push_back(&patch); + } +}; + +enum ECapDialog { + PATCHCAP_BEVEL = 0, + PATCHCAP_ENDCAP, + PATCHCAP_INVERTED_BEVEL, + PATCHCAP_INVERTED_ENDCAP, + PATCHCAP_CYLINDER +}; + +EMessageBoxReturn DoCapDlg(ECapDialog *type); + +void Scene_PatchDoCap_Selected(scene::Graph& graph, const char* shader) +{ + ECapDialog nType; + + if(DoCapDlg(&nType) == eIDOK) + { + EPatchCap eType; + switch(nType) + { + case PATCHCAP_INVERTED_BEVEL: + eType = eCapIBevel; + break; + case PATCHCAP_BEVEL: + eType = eCapBevel; + break; + case PATCHCAP_INVERTED_ENDCAP: + eType = eCapIEndCap; + break; + case PATCHCAP_ENDCAP: + eType = eCapEndCap; + break; + case PATCHCAP_CYLINDER: + eType = eCapCylinder; + break; + default: + ERROR_MESSAGE("invalid patch cap type"); + return; + } + + InstanceVector instances; + Scene_forEachVisibleSelectedPatchInstance(PatchStoreInstance(instances)); + for(InstanceVector::const_iterator i = instances.begin(); i != instances.end(); ++i) + { + Patch_makeCaps(* Node_getPatch((*i)->path().top()), *(*i), eType, shader); + } + } +} + +Patch* Scene_GetUltimateSelectedVisiblePatch() +{ + if(GlobalSelectionSystem().countSelected() != 0) + { + scene::Node& node = GlobalSelectionSystem().ultimateSelected().path().top(); + if(node.visible()) + { + return Node_getPatch(node); + } + } + return 0; +} + + +class PatchCapTexture +{ +public: + void operator()(Patch& patch) const + { + patch.ProjectTexture(Patch::m_CycleCapIndex); + } +}; + +void Scene_PatchCapTexture_Selected(scene::Graph& graph) +{ + Scene_forEachVisibleSelectedPatch(PatchCapTexture()); + Patch::m_CycleCapIndex = (Patch::m_CycleCapIndex == 0) ? 1 : (Patch::m_CycleCapIndex == 1) ? 2 : 0; + SceneChangeNotify(); +} + +class PatchFlipTexture +{ + int m_axis; +public: + PatchFlipTexture(int axis) : m_axis(axis) + { + } + void operator()(Patch& patch) const + { + patch.FlipTexture(m_axis); + } +}; + +void Scene_PatchFlipTexture_Selected(scene::Graph& graph, int axis) +{ + Scene_forEachVisibleSelectedPatch(PatchFlipTexture(axis)); +} + +class PatchNaturalTexture +{ +public: + void operator()(Patch& patch) const + { + patch.NaturalTexture(); + } +}; + +void Scene_PatchNaturalTexture_Selected(scene::Graph& graph) +{ + Scene_forEachVisibleSelectedPatch(PatchNaturalTexture()); + SceneChangeNotify(); +} + + +class PatchInsertRemove +{ + bool m_insert, m_column, m_first; +public: + PatchInsertRemove(bool insert, bool column, bool first) : m_insert(insert), m_column(column), m_first(first) + { + } + void operator()(Patch& patch) const + { + patch.InsertRemove(m_insert, m_column, m_first); + } +}; + +void Scene_PatchInsertRemove_Selected(scene::Graph& graph, bool bInsert, bool bColumn, bool bFirst) +{ + Scene_forEachVisibleSelectedPatch(PatchInsertRemove(bInsert, bColumn, bFirst)); +} + +class PatchInvertMatrix +{ +public: + void operator()(Patch& patch) const + { + patch.InvertMatrix(); + } +}; + +void Scene_PatchInvert_Selected(scene::Graph& graph) +{ + Scene_forEachVisibleSelectedPatch(PatchInvertMatrix()); +} + +class PatchRedisperse +{ + EMatrixMajor m_major; +public: + PatchRedisperse(EMatrixMajor major) : m_major(major) + { + } + void operator()(Patch& patch) const + { + patch.Redisperse(m_major); + } +}; + +void Scene_PatchRedisperse_Selected(scene::Graph& graph, EMatrixMajor major) +{ + Scene_forEachVisibleSelectedPatch(PatchRedisperse(major)); +} + +class PatchTransposeMatrix +{ +public: + void operator()(Patch& patch) const + { + patch.TransposeMatrix(); + } +}; + +void Scene_PatchTranspose_Selected(scene::Graph& graph) +{ + Scene_forEachVisibleSelectedPatch(PatchTransposeMatrix()); +} + +class PatchSetShader +{ + const char* m_name; +public: + PatchSetShader(const char* name) + : m_name(name) + { + } + void operator()(Patch& patch) const + { + patch.SetShader(m_name); + } +}; + +void Scene_PatchSetShader_Selected(scene::Graph& graph, const char* name) +{ + Scene_forEachVisibleSelectedPatch(PatchSetShader(name)); + SceneChangeNotify(); +} + +void Scene_PatchGetShader_Selected(scene::Graph& graph, CopiedString& name) +{ + Patch* patch = Scene_GetUltimateSelectedVisiblePatch(); + if(patch != 0) + { + name = patch->GetShader(); + } +} + +class PatchSelectByShader +{ + const char* m_name; +public: + inline PatchSelectByShader(const char* name) + : m_name(name) + { + } + void operator()(PatchInstance& patch) const + { + if(shader_equal(patch.getPatch().GetShader(), m_name)) + { + patch.setSelected(true); + } + } +}; + +void Scene_PatchSelectByShader(scene::Graph& graph, const char* name) +{ + Scene_forEachVisiblePatchInstance(PatchSelectByShader(name)); +} + + +class PatchFindReplaceShader +{ + const char* m_find; + const char* m_replace; +public: + PatchFindReplaceShader(const char* find, const char* replace) : m_find(find), m_replace(replace) + { + } + void operator()(Patch& patch) const + { + if(shader_equal(patch.GetShader(), m_find)) + { + patch.SetShader(m_replace); + } + } +}; + +void Scene_PatchFindReplaceShader(scene::Graph& graph, const char* find, const char* replace) +{ + Scene_forEachVisiblePatch(PatchFindReplaceShader(find, replace)); +} + +void Scene_PatchFindReplaceShader_Selected(scene::Graph& graph, const char* find, const char* replace) +{ + Scene_forEachVisibleSelectedPatch(PatchFindReplaceShader(find, replace)); +} + + +AABB PatchCreator_getBounds() +{ + AABB aabb(aabb_for_minmax(Select_getWorkZone().d_work_min, Select_getWorkZone().d_work_max)); + + float gridSize = GetGridSize(); + + if(aabb.extents[0] == 0) + { + aabb.extents[0] = gridSize; + } + if(aabb.extents[1] == 0) + { + aabb.extents[1] = gridSize; + } + if(aabb.extents[2] == 0) + { + aabb.extents[2] = gridSize; + } + + if(aabb_valid(aabb)) + { + return aabb; + } + return AABB(Vector3(0, 0, 0), Vector3(64, 64, 64)); +} + +void Patch_Cylinder() +{ + UndoableCommand undo("patchCreateCylinder"); + + Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eCylinder, GlobalXYWnd_getCurrentViewType()); +} + +void Patch_DenseCylinder() +{ + UndoableCommand undo("patchCreateDenseCylinder"); + + Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eDenseCylinder, GlobalXYWnd_getCurrentViewType()); +} + +void Patch_VeryDenseCylinder() +{ + UndoableCommand undo("patchCreateVeryDenseCylinder"); + + Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eVeryDenseCylinder, GlobalXYWnd_getCurrentViewType()); +} + +void Patch_SquareCylinder() +{ + UndoableCommand undo("patchCreateSquareCylinder"); + + Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eSqCylinder, GlobalXYWnd_getCurrentViewType()); +} + +void Patch_Endcap() +{ + UndoableCommand undo("patchCreateCaps"); + + Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eEndCap, GlobalXYWnd_getCurrentViewType()); +} + +void Patch_Bevel() +{ + UndoableCommand undo("patchCreateBevel"); + + Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eBevel, GlobalXYWnd_getCurrentViewType()); +} + +void Patch_SquareBevel() +{ +} + +void Patch_SquareEndcap() +{ +} + +void Patch_Cone() +{ + UndoableCommand undo("patchCreateCone"); + + Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eCone, GlobalXYWnd_getCurrentViewType()); +} + +void DoNewPatchDlg(); + +void Patch_Plane() +{ + UndoableCommand undo("patchCreatePlane"); + + DoNewPatchDlg(); +} + +void Patch_InsertInsertColumn() +{ + UndoableCommand undo("patchInsertColumns"); + + Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), true, true, false); +} + +void Patch_InsertAddColumn() +{ + UndoableCommand undo("patchAddColumns"); + + Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), true, true, true); +} + +void Patch_InsertInsertRow() +{ + UndoableCommand undo("patchInsertRows"); + + Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), true, false, false); +} + +void Patch_InsertAddRow() +{ + UndoableCommand undo("patchAddRows"); + + Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), true, false, true); +} + +void Patch_DeleteFirstColumn() +{ + UndoableCommand undo("patchDeleteFirstColumns"); + + Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), false, true, true); +} + +void Patch_DeleteLastColumn() +{ + UndoableCommand undo("patchDeleteLastColumns"); + + Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), false, true, false); +} + +void Patch_DeleteFirstRow() +{ + UndoableCommand undo("patchDeleteFirstRows"); + + Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), false, false, true); +} + +void Patch_DeleteLastRow() +{ + UndoableCommand undo("patchDeleteLastRows"); + + Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), false, false, false); +} + +void Patch_Invert() +{ + UndoableCommand undo("patchInvert"); + + Scene_PatchInvert_Selected(GlobalSceneGraph()); +} + +void Patch_RedisperseRows() +{ + UndoableCommand undo("patchRedisperseRows"); + + Scene_PatchRedisperse_Selected(GlobalSceneGraph(), COL); +} + +void Patch_RedisperseCols() +{ + UndoableCommand undo("patchRedisperseColumns"); + + Scene_PatchRedisperse_Selected(GlobalSceneGraph(), COL); +} + +void Patch_Transpose() +{ + UndoableCommand undo("patchTranspose"); + + Scene_PatchTranspose_Selected(GlobalSceneGraph()); +} + +void Patch_Cap() +{ + // FIXME: add support for patch cap creation + // Patch_CapCurrent(); + UndoableCommand undo("patchCreateCaps"); + + Scene_PatchDoCap_Selected(GlobalSceneGraph(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser())); +} + +void Patch_CycleProjection() +{ + UndoableCommand undo("patchCycleUVProjectionAxis"); + + Scene_PatchCapTexture_Selected(GlobalSceneGraph()); +} + +///\todo Unfinished. +void Patch_OverlayOn() +{ +} + +///\todo Unfinished. +void Patch_OverlayOff() +{ +} + +void Patch_FlipTextureX() +{ + UndoableCommand undo("patchFlipTextureU"); + + Scene_PatchFlipTexture_Selected(GlobalSceneGraph(), 0); +} + +void Patch_FlipTextureY() +{ + UndoableCommand undo("patchFlipTextureV"); + + Scene_PatchFlipTexture_Selected(GlobalSceneGraph(), 1); +} + +void Patch_NaturalTexture() +{ + UndoableCommand undo("patchNaturalTexture"); + + Scene_PatchNaturalTexture_Selected(GlobalSceneGraph()); +} + + + + +#include "ifilter.h" + + +class filter_patch_all : public PatchFilter +{ +public: + bool filter(const Patch& patch) const + { + return true; + } +}; + +class filter_patch_shader : public PatchFilter +{ + const char* m_shader; +public: + filter_patch_shader(const char* shader) : m_shader(shader) + { + } + bool filter(const Patch& patch) const + { + return shader_equal(patch.GetShader(), m_shader); + } +}; + +class filter_patch_flags : public PatchFilter +{ + int m_flags; +public: + filter_patch_flags(int flags) : m_flags(flags) + { + } + bool filter(const Patch& patch) const + { + return (patch.getShaderFlags() & m_flags) != 0; + } +}; + + +filter_patch_all g_filter_patch_all; +filter_patch_shader g_filter_patch_clip("textures/common/clip"); +filter_patch_shader g_filter_patch_weapclip("textures/common/weapclip"); +filter_patch_flags g_filter_patch_translucent(QER_TRANS); + +void PatchFilters_construct() +{ + add_patch_filter(g_filter_patch_all, EXCLUDE_CURVES); + add_patch_filter(g_filter_patch_clip, EXCLUDE_CLIP); + add_patch_filter(g_filter_patch_weapclip, EXCLUDE_CLIP); + add_patch_filter(g_filter_patch_translucent, EXCLUDE_TRANSLUCENT); +} + + +#include "preferences.h" + +void Patch_constructPreferences(PreferencesPage& page) +{ + page.appendEntry("Patch Subdivide Threshold", g_PatchSubdivideThreshold); +} +void Patch_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Patches", "Patch Display Preferences")); + Patch_constructPreferences(page); +} +void Patch_registerPreferencesPage() +{ + PreferencesDialog_addDisplayPage(FreeCaller1()); +} + + +#include "preferencesystem.h" + +void PatchPreferences_construct() +{ + GlobalPreferenceSystem().registerPreference("Subdivisions", IntImportStringCaller(g_PatchSubdivideThreshold), IntExportStringCaller(g_PatchSubdivideThreshold)); +} + + +#include "generic/callback.h" + +void Patch_registerCommands() +{ + GlobalCommands_insert("InvertCurveTextureX", FreeCaller(), Accelerator('I', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + GlobalCommands_insert("InvertCurveTextureY", FreeCaller(), Accelerator('I', (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("IncPatchColumn", FreeCaller(), Accelerator(GDK_KP_Add, (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + GlobalCommands_insert("IncPatchRow", FreeCaller(), Accelerator(GDK_KP_Add, (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("DecPatchColumn", FreeCaller(), Accelerator(GDK_KP_Subtract, (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + GlobalCommands_insert("DecPatchRow", FreeCaller(), Accelerator(GDK_KP_Subtract, (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("NaturalizePatch", FreeCaller(), Accelerator('N', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("PatchCylinder", FreeCaller()); + GlobalCommands_insert("PatchDenseCylinder", FreeCaller()); + GlobalCommands_insert("PatchVeryDenseCylinder", FreeCaller()); + GlobalCommands_insert("PatchSquareCylinder", FreeCaller()); + GlobalCommands_insert("PatchEndCap", FreeCaller()); + GlobalCommands_insert("PatchBevel", FreeCaller()); + GlobalCommands_insert("PatchSquareBevel", FreeCaller()); + GlobalCommands_insert("PatchSquareEndcap", FreeCaller()); + GlobalCommands_insert("PatchCone", FreeCaller()); + GlobalCommands_insert("SimplePatchMesh", FreeCaller(), Accelerator('P', (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("PatchInsertInsertColumn", FreeCaller()); + GlobalCommands_insert("PatchInsertAddColumn", FreeCaller()); + GlobalCommands_insert("PatchInsertInsertRow", FreeCaller()); + GlobalCommands_insert("PatchInsertAddRow", FreeCaller()); + GlobalCommands_insert("PatchDeleteFirstColumn", FreeCaller()); + GlobalCommands_insert("PatchDeleteLastColumn", FreeCaller()); + GlobalCommands_insert("PatchDeleteFirstRow", FreeCaller()); + GlobalCommands_insert("PatchDeleteLastRow", FreeCaller()); + GlobalCommands_insert("InvertCurve", FreeCaller(), Accelerator('I', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("RedisperseRows", FreeCaller(), Accelerator('E', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("RedisperseCols", FreeCaller(), Accelerator('E', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + GlobalCommands_insert("MatrixTranspose", FreeCaller(), Accelerator('M', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + GlobalCommands_insert("CapCurrentCurve", FreeCaller(), Accelerator('C', (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("CycleCapTexturePatch", FreeCaller(), Accelerator('N', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + GlobalCommands_insert("MakeOverlayPatch", FreeCaller(), Accelerator('Y')); + GlobalCommands_insert("ClearPatchOverlays", FreeCaller(), Accelerator('L', (GdkModifierType)GDK_CONTROL_MASK)); +} + +void Patch_constructToolbar(GtkToolbar* toolbar) +{ + toolbar_append_button(toolbar, "Put caps on the current patch (SHIFT + C)", "curve_cap.bmp", "CapCurrentCurve"); +} + +void Patch_constructMenu(GtkMenu* menu) +{ + create_menu_item_with_mnemonic(menu, "Cylinder", "PatchCylinder"); + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "More Cylinders"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "Dense Cylinder", "PatchDenseCylinder"); + create_menu_item_with_mnemonic(menu_in_menu, "Very Dense Cylinder", "PatchVeryDenseCylinder"); + create_menu_item_with_mnemonic(menu_in_menu, "Square Cylinder", "PatchSquareCylinder"); + } + menu_separator (menu); + create_menu_item_with_mnemonic(menu, "End cap", "PatchEndCap"); + create_menu_item_with_mnemonic(menu, "Bevel", "PatchBevel"); + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "More End caps, Bevels"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "Square Endcap", "PatchSquareBevel"); + create_menu_item_with_mnemonic(menu_in_menu, "Square Bevel", "PatchSquareEndcap"); + } + menu_separator (menu); + create_menu_item_with_mnemonic(menu, "Cone", "PatchCone"); + menu_separator (menu); + create_menu_item_with_mnemonic(menu, "Simple Patch Mesh...", "SimplePatchMesh"); + menu_separator (menu); + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "Insert"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "Insert (2) Columns", "PatchInsertInsertColumn"); + create_menu_item_with_mnemonic(menu_in_menu, "Add (2) Columns", "PatchInsertAddColumn"); + menu_separator (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "Insert (2) Rows", "PatchInsertInsertRow"); + create_menu_item_with_mnemonic(menu_in_menu, "Add (2) Rows", "PatchInsertAddRow"); + } + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "Delete"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "First (2) Columns", "PatchDeleteFirstColumn"); + create_menu_item_with_mnemonic(menu_in_menu, "Last (2) Columns", "PatchDeleteLastColumn"); + menu_separator (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "First (2) Rows", "PatchDeleteFirstRow"); + create_menu_item_with_mnemonic(menu_in_menu, "Last (2) Rows", "PatchDeleteLastRow"); + } + menu_separator (menu); + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "Matrix"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "Invert", "InvertCurve"); + GtkMenu* menu_3 = create_sub_menu_with_mnemonic (menu_in_menu, "Re-disperse"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_3); + create_menu_item_with_mnemonic(menu_3, "Rows", "RedisperseRows"); + create_menu_item_with_mnemonic(menu_3, "Columns", "RedisperseCols"); + create_menu_item_with_mnemonic(menu_in_menu, "Transpose", "MatrixTranspose"); + } + menu_separator (menu); + create_menu_item_with_mnemonic(menu, "Cap Selection", "CapCurrentCurve"); + create_menu_item_with_mnemonic(menu, "Cycle Cap Texture", "CycleCapTexturePatch"); + menu_separator (menu); + { + GtkMenu* menu_in_menu = create_sub_menu_with_mnemonic (menu, "Overlay"); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu_in_menu); + create_menu_item_with_mnemonic(menu_in_menu, "Set", "MakeOverlayPatch"); + create_menu_item_with_mnemonic(menu_in_menu, "Clear", "ClearPatchOverlays"); + } +} + + +#include +#include +#include +#include +#include +#include +#include "gtkutil/dialog.h" +#include "gtkutil/widget.h" + +void DoNewPatchDlg() +{ + ModalDialog dialog; + GtkComboBox* width; + GtkComboBox* height; + + GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Patch density", G_CALLBACK(dialog_delete_callback), &dialog); + + GtkAccelGroup* accel = gtk_accel_group_new(); + gtk_window_add_accel_group(window, accel); + + { + GtkHBox* hbox = create_dialog_hbox(4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox)); + { + GtkTable* table = create_dialog_table(2, 2, 4, 4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(table), TRUE, TRUE, 0); + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Width:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + { + GtkLabel* label = GTK_LABEL(gtk_label_new("Height:")); + gtk_widget_show(GTK_WIDGET(label)); + gtk_table_attach(table, GTK_WIDGET(label), 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + } + + { + GtkComboBox* combo = GTK_COMBO_BOX(gtk_combo_box_new_text()); + gtk_combo_box_append_text(combo, "3"); + gtk_combo_box_append_text(combo, "5"); + gtk_combo_box_append_text(combo, "7"); + gtk_combo_box_append_text(combo, "9"); + gtk_combo_box_append_text(combo, "11"); + gtk_combo_box_append_text(combo, "13"); + gtk_combo_box_append_text(combo, "15"); + gtk_widget_show(GTK_WIDGET(combo)); + gtk_table_attach(table, GTK_WIDGET(combo), 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + width = combo; + } + { + GtkComboBox* combo = GTK_COMBO_BOX(gtk_combo_box_new_text()); + gtk_combo_box_append_text(combo, "3"); + gtk_combo_box_append_text(combo, "5"); + gtk_combo_box_append_text(combo, "7"); + gtk_combo_box_append_text(combo, "9"); + gtk_combo_box_append_text(combo, "11"); + gtk_combo_box_append_text(combo, "13"); + gtk_combo_box_append_text(combo, "15"); + gtk_widget_show(GTK_WIDGET(combo)); + gtk_table_attach(table, GTK_WIDGET(combo), 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + height = combo; + } + } + + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0); + { + GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_grab_focus(GTK_WIDGET(button)); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0); + } + { + GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &dialog); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0); + } + } + } + + // Initialize dialog + gtk_combo_box_set_active(width, 0); + gtk_combo_box_set_active(height, 0); + + if(modal_dialog_show(window, dialog) == eIDOK) + { + int w = gtk_combo_box_get_active(width) * 2 + 3; + int h = gtk_combo_box_get_active(height) * 2 + 3; + + Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), ePlane, GlobalXYWnd_getCurrentViewType(), w, h); + } + + gtk_widget_destroy(GTK_WIDGET(window)); +} + + + + +EMessageBoxReturn DoCapDlg(ECapDialog* type) +{ + ModalDialog dialog; + ModalDialogButton ok_button(dialog, eIDOK); + ModalDialogButton cancel_button(dialog, eIDCANCEL); + GtkWidget* bevel; + GtkWidget* ibevel; + GtkWidget* endcap; + GtkWidget* iendcap; + GtkWidget* cylinder; + + GtkWindow* window = create_modal_dialog_window(MainFrame_getWindow(), "Cap", dialog); + + GtkAccelGroup *accel_group = gtk_accel_group_new(); + gtk_window_add_accel_group(window, accel_group); + + { + GtkHBox* hbox = create_dialog_hbox(4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox)); + + { + // Gef: Added a vbox to contain the toggle buttons + GtkVBox* radio_vbox = create_dialog_vbox(4); + gtk_container_add(GTK_CONTAINER(hbox), GTK_WIDGET(radio_vbox)); + + { + GtkTable* table = GTK_TABLE(gtk_table_new(5, 2, FALSE)); + gtk_widget_show(GTK_WIDGET(table)); + gtk_box_pack_start(GTK_BOX(radio_vbox), GTK_WIDGET(table), TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 5); + gtk_table_set_col_spacings(table, 5); + + { + GtkImage* image = new_local_image("cap_bevel.bmp"); + gtk_widget_show(GTK_WIDGET(image)); + gtk_table_attach(table, GTK_WIDGET(image), 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkImage* image = new_local_image("cap_endcap.bmp"); + gtk_widget_show(GTK_WIDGET(image)); + gtk_table_attach(table, GTK_WIDGET(image), 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkImage* image = new_local_image("cap_ibevel.bmp"); + gtk_widget_show(GTK_WIDGET(image)); + gtk_table_attach(table, GTK_WIDGET(image), 0, 1, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkImage* image = new_local_image("cap_iendcap.bmp"); + gtk_widget_show(GTK_WIDGET(image)); + gtk_table_attach(table, GTK_WIDGET(image), 0, 1, 3, 4, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkImage* image = new_local_image("cap_cylinder.bmp"); + gtk_widget_show(GTK_WIDGET(image)); + gtk_table_attach(table, GTK_WIDGET(image), 0, 1, 4, 5, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + + GSList* group = 0; + { + GtkWidget* button = gtk_radio_button_new_with_label (group, "Bevel"); + gtk_widget_show (button); + gtk_table_attach(table, button, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL | GTK_EXPAND), + (GtkAttachOptions) (0), 0, 0); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (button)); + + bevel = button; + } + { + GtkWidget* button = gtk_radio_button_new_with_label (group, "Endcap"); + gtk_widget_show (button); + gtk_table_attach(table, button, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL | GTK_EXPAND), + (GtkAttachOptions) (0), 0, 0); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (button)); + + endcap = button; + } + { + GtkWidget* button = gtk_radio_button_new_with_label (group, "Inverted Bevel"); + gtk_widget_show (button); + gtk_table_attach(table, button, 1, 2, 2, 3, + (GtkAttachOptions) (GTK_FILL | GTK_EXPAND), + (GtkAttachOptions) (0), 0, 0); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (button)); + + ibevel = button; + } + { + GtkWidget* button = gtk_radio_button_new_with_label (group, "Inverted Endcap"); + gtk_widget_show (button); + gtk_table_attach(table, button, 1, 2, 3, 4, + (GtkAttachOptions) (GTK_FILL | GTK_EXPAND), + (GtkAttachOptions) (0), 0, 0); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (button)); + + iendcap = button; + } + { + GtkWidget* button = gtk_radio_button_new_with_label (group, "Cylinder"); + gtk_widget_show (button); + gtk_table_attach(table, button, 1, 2, 4, 5, + (GtkAttachOptions) (GTK_FILL | GTK_EXPAND), + (GtkAttachOptions) (0), 0, 0); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (button)); + + cylinder = button; + } + } + } + + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), FALSE, FALSE, 0); + { + GtkButton* button = create_modal_dialog_button("OK", ok_button); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Return, (GdkModifierType)0, GTK_ACCEL_VISIBLE); + } + { + GtkButton* button = create_modal_dialog_button("Cancel", cancel_button); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel_group, GDK_Escape, (GdkModifierType)0, GTK_ACCEL_VISIBLE); + } + } + } + + // Initialize dialog + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (bevel), TRUE); + + EMessageBoxReturn ret = modal_dialog_show(window, dialog); + if (ret == eIDOK) + { + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (bevel))) + *type = PATCHCAP_BEVEL; + else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(endcap))) + *type = PATCHCAP_ENDCAP; + else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ibevel))) + *type = PATCHCAP_INVERTED_BEVEL; + else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(iendcap))) + *type = PATCHCAP_INVERTED_ENDCAP; + else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cylinder))) + *type = PATCHCAP_CYLINDER; + } + + gtk_widget_destroy(GTK_WIDGET(window)); + + return ret; +} diff --git a/radiant/patchmanip.h b/radiant/patchmanip.h new file mode 100644 index 00000000..e4d372e1 --- /dev/null +++ b/radiant/patchmanip.h @@ -0,0 +1,57 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_PATCHMANIP_H) +#define INCLUDED_PATCHMANIP_H + +#include "string/stringfwd.h" + +void Patch_registerCommands(); +typedef struct _GtkToolbar GtkToolbar; +typedef struct _GtkMenu GtkMenu; +void Patch_constructToolbar(GtkToolbar* toolbar); +void Patch_constructMenu(GtkMenu* menu); + +namespace scene +{ + class Graph; +} + +void Scene_PatchSetShader_Selected(scene::Graph& graph, const char* name); +void Scene_PatchGetShader_Selected(scene::Graph& graph, CopiedString& name); +void Scene_PatchSelectByShader(scene::Graph& graph, const char* name); +void Scene_PatchFindReplaceShader(scene::Graph& graph, const char* find, const char* replace); +void Scene_PatchFindReplaceShader_Selected(scene::Graph& graph, const char* find, const char* replace); + +void Scene_PatchCapTexture_Selected(scene::Graph& graph); +void Scene_PatchNaturalTexture_Selected(scene::Graph& graph); +void Scene_PatchTileTexture_Selected(scene::Graph& graph, float s, float t); + +void PatchFilters_construct(); + +void PatchPreferences_construct(); + +void Patch_registerPreferencesPage(); + +class PatchCreator; +extern PatchCreator* g_patchCreator; + +#endif diff --git a/radiant/patchmodule.cpp b/radiant/patchmodule.cpp new file mode 100644 index 00000000..713d2122 --- /dev/null +++ b/radiant/patchmodule.cpp @@ -0,0 +1,251 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "patchmodule.h" + +#include "qerplugin.h" +#include "ipatch.h" + +#include "patch.h" +#include "patchmanip.h" + +namespace +{ + std::size_t g_patchModuleCount = 0; +} + +void Patch_Construct(EPatchType type) +{ + if(++g_patchModuleCount != 1) + { + return; + } + + PatchFilters_construct(); + + PatchPreferences_construct(); + + Patch_registerPreferencesPage(); + + Patch::constructStatic(type); + PatchInstance::constructStatic(); + + if(type == ePatchTypeDoom3) + { + MAX_PATCH_WIDTH = MAX_PATCH_HEIGHT = 99; + } + else + { + MAX_PATCH_WIDTH = MAX_PATCH_HEIGHT = 15; + } +} + +void Patch_Destroy() +{ + if(--g_patchModuleCount != 0) + { + return; + } + + Patch::destroyStatic(); + PatchInstance::destroyStatic(); +} + +class CommonPatchCreator : public PatchCreator +{ +public: + void Patch_undoSave(scene::Node& patch) const + { + Node_getPatch(patch)->undoSave(); + } + void Patch_resize(scene::Node& patch, std::size_t width, std::size_t height) const + { + Node_getPatch(patch)->setDims(width, height); + } + PatchControlMatrix Patch_getControlPoints(scene::Node& node) const + { + Patch& patch = *Node_getPatch(node); + return PatchControlMatrix(patch.getHeight(), patch.getWidth(), patch.getControlPoints().data()); + } + void Patch_controlPointsChanged(scene::Node& patch) const + { + return Node_getPatch(patch)->controlPointsChanged(); + } + const char* Patch_getShader(scene::Node& patch) const + { + return Node_getPatch(patch)->GetShader(); + } + void Patch_setShader(scene::Node& patch, const char* shader) const + { + Node_getPatch(patch)->SetShader(shader); + } +}; + +class Quake3PatchCreator : public CommonPatchCreator +{ +public: + scene::Node& createPatch() + { + return (new PatchNodeQuake3())->node(); + } +}; + +Quake3PatchCreator g_Quake3PatchCreator; + +PatchCreator& GetQuake3PatchCreator() +{ + return g_Quake3PatchCreator; +} + +class Doom3PatchCreator : public CommonPatchCreator +{ +public: + scene::Node& createPatch() + { + return (new PatchNodeDoom3(true))->node(); + } +}; + +Doom3PatchCreator g_Doom3PatchCreator; + +PatchCreator& GetDoom3PatchCreator() +{ + return g_Doom3PatchCreator; +} + +class Doom3PatchDef2Creator : public CommonPatchCreator +{ +public: + scene::Node& createPatch() + { + return (new PatchNodeDoom3())->node(); + } +}; + +Doom3PatchDef2Creator g_Doom3PatchDef2Creator; + +PatchCreator& GetDoom3PatchDef2Creator() +{ + return g_Doom3PatchDef2Creator; +} + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +class PatchDependencies : + public GlobalRadiantModuleRef, + public GlobalSceneGraphModuleRef, + public GlobalShaderCacheModuleRef, + public GlobalSelectionModuleRef, + public GlobalOpenGLModuleRef, + public GlobalUndoModuleRef, + public GlobalFilterModuleRef +{ +}; + +class PatchQuake3API : public TypeSystemRef +{ + PatchCreator* m_patchquake3; +public: + typedef PatchCreator Type; + STRING_CONSTANT(Name, "quake3"); + + PatchQuake3API() + { + Patch_Construct(ePatchTypeQuake3); + + m_patchquake3 = &GetQuake3PatchCreator(); + g_patchCreator = m_patchquake3; + } + ~PatchQuake3API() + { + Patch_Destroy(); + } + PatchCreator* getTable() + { + return m_patchquake3; + } +}; + +typedef SingletonModule PatchQuake3Module; +typedef Static StaticPatchQuake3Module; +StaticRegisterModule staticRegisterPatchQuake3(StaticPatchQuake3Module::instance()); + + + +class PatchDoom3API : public TypeSystemRef +{ + PatchCreator* m_patchdoom3; +public: + typedef PatchCreator Type; + STRING_CONSTANT(Name, "doom3"); + + PatchDoom3API() + { + Patch_Construct(ePatchTypeDoom3); + + m_patchdoom3 = &GetDoom3PatchCreator(); + } + ~PatchDoom3API() + { + Patch_Destroy(); + } + PatchCreator* getTable() + { + return m_patchdoom3; + } +}; + +typedef SingletonModule PatchDoom3Module; +typedef Static StaticPatchDoom3Module; +StaticRegisterModule staticRegisterPatchDoom3(StaticPatchDoom3Module::instance()); + + +class PatchDef2Doom3API : public TypeSystemRef +{ + PatchCreator* m_patchdef2doom3; +public: + typedef PatchCreator Type; + STRING_CONSTANT(Name, "def2doom3"); + + PatchDef2Doom3API() + { + Patch_Construct(ePatchTypeDoom3); + + m_patchdef2doom3 = &GetDoom3PatchDef2Creator(); + g_patchCreator = m_patchdef2doom3; + } + ~PatchDef2Doom3API() + { + Patch_Destroy(); + } + PatchCreator* getTable() + { + return m_patchdef2doom3; + } +}; + +typedef SingletonModule PatchDef2Doom3Module; +typedef Static StaticPatchDef2Doom3Module; +StaticRegisterModule staticRegisterPatchDef2Doom3(StaticPatchDef2Doom3Module::instance()); + + + diff --git a/radiant/patchmodule.h b/radiant/patchmodule.h new file mode 100644 index 00000000..074d5947 --- /dev/null +++ b/radiant/patchmodule.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_PATCHMODULE_H) +#define INCLUDED_PATCHMODULE_H + +#endif diff --git a/radiant/plugin.cpp b/radiant/plugin.cpp new file mode 100644 index 00000000..fad47ea2 --- /dev/null +++ b/radiant/plugin.cpp @@ -0,0 +1,358 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "plugin.h" + +#include "debugging/debugging.h" + +#include "qerplugin.h" +#include "ifilesystem.h" +#include "ishaders.h" +#include "ientity.h" +#include "ieclass.h" +#include "irender.h" +#include "iscenegraph.h" +#include "iselection.h" +#include "ifilter.h" +#include "iscriplib.h" +#include "igl.h" +#include "iundo.h" +#include "itextures.h" +#include "ireference.h" +#include "ifiletypes.h" +#include "preferencesystem.h" +#include "ibrush.h" +#include "ipatch.h" +#include "iimage.h" +#include "itoolbar.h" +#include "iplugin.h" +#include "imap.h" +#include "namespace.h" + +#include "gtkutil/messagebox.h" +#include "gtkutil/filechooser.h" +#include "maplib.h" + +#include "error.h" +#include "map.h" +#include "qe3.h" +#include "entityinspector.h" +#include "entitylist.h" +#include "points.h" +#include "gtkmisc.h" +#include "texwindow.h" +#include "mainframe.h" +#include "build.h" +#include "mru.h" +#include "multimon.h" +#include "surfacedialog.h" +#include "groupdialog.h" +#include "patchdialog.h" +#include "camwindow.h" +#include "watchbsp.h" +#include "xywindow.h" +#include "entity.h" +#include "select.h" +#include "preferences.h" +#include "autosave.h" +#include "plugintoolbar.h" +#include "findtexturedialog.h" +#include "nullmodel.h" +#include "grid.h" + +#include "modulesystem/modulesmap.h" +#include "modulesystem/singletonmodule.h" + +#include "generic/callback.h" + +const char* GameDescription_getKeyValue(const char* key) +{ + return g_pGameDescription->getKeyValue(key); +} + +const char* GameDescription_getRequiredKeyValue(const char* key) +{ + return g_pGameDescription->getRequiredKeyValue(key); +} + +const char* getMapName() +{ + return Map_Name(g_map); +} + +scene::Node& getMapWorldEntity() +{ + return Map_FindOrInsertWorldspawn(g_map); +} + +VIEWTYPE XYWindow_getViewType() +{ + return g_pParentWnd->GetXYWnd()->GetViewType(); +} + +Vector3 XYWindow_windowToWorld(const WindowVector& position) +{ + Vector3 result(0, 0, 0); + g_pParentWnd->GetXYWnd()->XY_ToPoint(static_cast(position.x()), static_cast(position.y()), result); + return result; +} + +const char* TextureBrowser_getSelectedShader() +{ + return TextureBrowser_GetSelectedShader(GlobalTextureBrowser()); +} + +class RadiantCoreAPI +{ + _QERFuncTable_1 m_radiantcore; +public: + typedef _QERFuncTable_1 Type; + STRING_CONSTANT(Name, "*"); + + RadiantCoreAPI() + { + m_radiantcore.getEnginePath = &EnginePath_get; + m_radiantcore.getLocalRcPath = &LocalRcPath_get; + m_radiantcore.getAppPath = &AppPath_get; + m_radiantcore.getGameToolsPath = &GameToolsPath_get; + m_radiantcore.getSettingsPath = &SettingsPath_get; + m_radiantcore.getMapsPath = &getMapsPath; + + m_radiantcore.getGameName = &gamename_get; + m_radiantcore.getGameMode = &gamemode_get; + + m_radiantcore.getMapName = &getMapName; + m_radiantcore.getMapWorldEntity = getMapWorldEntity; + m_radiantcore.getGridSize = GetGridSize; + + m_radiantcore.getGameDescriptionKeyValue = &GameDescription_getKeyValue; + m_radiantcore.getRequiredGameDescriptionKeyValue = &GameDescription_getRequiredKeyValue; + + m_radiantcore.attachGameToolsPathObserver = Radiant_attachGameToolsPathObserver; + m_radiantcore.detachGameToolsPathObserver = Radiant_detachGameToolsPathObserver; + m_radiantcore.attachEnginePathObserver = Radiant_attachEnginePathObserver; + m_radiantcore.detachEnginePathObserver = Radiant_detachEnginePathObserver; + m_radiantcore.attachGameNameObserver = Radiant_attachGameNameObserver; + m_radiantcore.detachGameNameObserver = Radiant_detachGameNameObserver; + m_radiantcore.attachGameModeObserver = Radiant_attachGameModeObserver; + m_radiantcore.detachGameModeObserver = Radiant_detachGameModeObserver; + + m_radiantcore.XYWindowDestroyed_connect = XYWindowDestroyed_connect; + m_radiantcore.XYWindowDestroyed_disconnect = XYWindowDestroyed_disconnect; + m_radiantcore.XYWindowMouseDown_connect = XYWindowMouseDown_connect; + m_radiantcore.XYWindowMouseDown_disconnect = XYWindowMouseDown_disconnect; + m_radiantcore.XYWindow_getViewType = XYWindow_getViewType; + m_radiantcore.XYWindow_windowToWorld = XYWindow_windowToWorld; + m_radiantcore.TextureBrowser_getSelectedShader = TextureBrowser_getSelectedShader; + + m_radiantcore.m_pfnMessageBox = >k_MessageBox; + m_radiantcore.m_pfnFileDialog = &file_dialog; + m_radiantcore.m_pfnColorDialog = &color_dialog; + m_radiantcore.m_pfnDirDialog = &dir_dialog; + m_radiantcore.m_pfnNewImage = &new_plugin_image; + } + _QERFuncTable_1* getTable() + { + return &m_radiantcore; + } +}; + +typedef SingletonModule RadiantCoreModule; +typedef Static StaticRadiantCoreModule; +StaticRegisterModule staticRegisterRadiantCore(StaticRadiantCoreModule::instance()); + + +class RadiantDependencies : + public GlobalRadiantModuleRef, + public GlobalFileSystemModuleRef, + public GlobalEntityModuleRef, + public GlobalShadersModuleRef, + public GlobalBrushModuleRef, + public GlobalSceneGraphModuleRef, + public GlobalShaderCacheModuleRef, + public GlobalFiletypesModuleRef, + public GlobalSelectionModuleRef, + public GlobalReferenceModuleRef, + public GlobalOpenGLModuleRef, + public GlobalEntityClassManagerModuleRef, + public GlobalUndoModuleRef, + public GlobalScripLibModuleRef, + public GlobalNamespaceModuleRef +{ + ImageModulesRef m_image_modules; + MapModulesRef m_map_modules; + ToolbarModulesRef m_toolbar_modules; + PluginModulesRef m_plugin_modules; + +public: + RadiantDependencies() : + GlobalEntityModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("entities")), + GlobalShadersModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("shaders")), + GlobalBrushModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("brushtypes")), + GlobalEntityClassManagerModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("entityclass")), + m_image_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("texturetypes")), + m_map_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("maptypes")), + m_toolbar_modules("*"), + m_plugin_modules("*") + { + } + + ImageModules& getImageModules() + { + return m_image_modules.get(); + } + MapModules& getMapModules() + { + return m_map_modules.get(); + } + ToolbarModules& getToolbarModules() + { + return m_toolbar_modules.get(); + } + PluginModules& getPluginModules() + { + return m_plugin_modules.get(); + } +}; + +class Radiant : public TypeSystemRef +{ +public: + Radiant() + { + Preferences_Init(); + + GlobalFiletypes().addType("sound", "wav", filetype_t("PCM sound files", "*.wav")); + + Selection_construct(); + HomePaths_Construct(); + VFS_Construct(); + Grid_construct(); + MultiMon_Construct(); + MRU_Construct(); + Pointfile_Construct(); + GLWindow_Construct(); + BuildMenu_Construct(); + Map_Construct(); + EntityList_Construct(); + MainFrame_Construct(); + GroupDialog_Construct(); + SurfaceInspector_Construct(); + PatchInspector_Construct(); + CamWnd_Construct(); + XYWindow_Construct(); + BuildMonitor_Construct(); + TextureBrowser_Construct(); + Entity_Construct(); + Autosave_Construct(); + EntityInspector_construct(); + FindTextureDialog_Construct(); + NullModel_construct(); + MapRoot_construct(); + + EnginePath_verify(); + EnginePath_Realise(); + } + ~Radiant() + { + EnginePath_Unrealise(); + + MapRoot_destroy(); + NullModel_destroy(); + FindTextureDialog_Destroy(); + EntityInspector_destroy(); + Autosave_Destroy(); + Entity_Destroy(); + TextureBrowser_Destroy(); + BuildMonitor_Destroy(); + XYWindow_Destroy(); + CamWnd_Destroy(); + PatchInspector_Destroy(); + SurfaceInspector_Destroy(); + GroupDialog_Destroy(); + MainFrame_Destroy(); + EntityList_Destroy(); + Map_Destroy(); + BuildMenu_Destroy(); + GLWindow_Destroy(); + Pointfile_Destroy(); + MRU_Destroy(); + MultiMon_Destroy(); + Grid_destroy(); + VFS_Destroy(); + HomePaths_Destroy(); + Selection_destroy(); + } +}; + +namespace +{ + bool g_RadiantInitialised = false; + RadiantDependencies* g_RadiantDependencies; + Radiant* g_Radiant; +} + + + +bool Radiant_Construct(ModuleServer& server) +{ + GlobalModuleServer::instance().set(server); + StaticModuleRegistryList().instance().registerModules(); + + g_RadiantDependencies = new RadiantDependencies(); + + g_RadiantInitialised = !server.getError(); + + if(g_RadiantInitialised) + { + g_Radiant = new Radiant; + } + + return g_RadiantInitialised; +} +void Radiant_Destroy() +{ + if(g_RadiantInitialised) + { + delete g_Radiant; + } + + delete g_RadiantDependencies; +} + +ImageModules& Radiant_getImageModules() +{ + return g_RadiantDependencies->getImageModules(); +} +MapModules& Radiant_getMapModules() +{ + return g_RadiantDependencies->getMapModules(); +} +ToolbarModules& Radiant_getToolbarModules() +{ + return g_RadiantDependencies->getToolbarModules(); +} +PluginModules& Radiant_getPluginModules() +{ + return g_RadiantDependencies->getPluginModules(); +} + + diff --git a/radiant/plugin.h b/radiant/plugin.h new file mode 100644 index 00000000..e4d2493d --- /dev/null +++ b/radiant/plugin.h @@ -0,0 +1,47 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_PLUGIN_H) +#define INCLUDED_PLUGIN_H + +class ModuleServer; +bool Radiant_Construct(ModuleServer& server); +void Radiant_Destroy(); + + +template +class Modules; + +struct _QERPlugImageTable; +typedef Modules<_QERPlugImageTable> ImageModules; +ImageModules& Radiant_getImageModules(); +class MapFormat; +typedef Modules MapModules; +MapModules& Radiant_getMapModules(); +struct _QERPlugToolbarTable; +typedef Modules<_QERPlugToolbarTable> ToolbarModules; +ToolbarModules& Radiant_getToolbarModules(); +struct _QERPluginTable; +typedef Modules<_QERPluginTable> PluginModules; +PluginModules& Radiant_getPluginModules(); + + +#endif diff --git a/radiant/pluginapi.cpp b/radiant/pluginapi.cpp new file mode 100644 index 00000000..be91b9a6 --- /dev/null +++ b/radiant/pluginapi.cpp @@ -0,0 +1,92 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "pluginapi.h" + +#include "modulesystem.h" +#include "qerplugin.h" + +#include "generic/callback.h" +#include "math/vector.h" + +#include "gtkmisc.h" + +#include "camwindow.h" + +#include "mainframe.h" + + +// camera API +void QERApp_GetCamera( Vector3& origin, Vector3& angles ) +{ + CamWnd& camwnd = *g_pParentWnd->GetCamWnd(); + origin = Camera_getOrigin(camwnd); + angles = Camera_getAngles(camwnd); +} + +void QERApp_SetCamera( const Vector3& origin, const Vector3& angles ) +{ + CamWnd& camwnd = *g_pParentWnd->GetCamWnd(); + Camera_setOrigin(camwnd, origin); + Camera_setAngles(camwnd, angles); +} + +void QERApp_GetCamWindowExtents( int *x, int *y, int *width, int *height ) +{ +#if 0 + CamWnd* camwnd = g_pParentWnd->GetCamWnd(); + + gtk_window_get_position(GTK_WINDOW(camwnd->m_window), x, y); + + *width = camwnd->Camera()->width; + *height = camwnd->Camera()->height; +#endif +} + +#include "icamera.h" + +class CameraAPI +{ + _QERCameraTable m_camera; +public: + typedef _QERCameraTable Type; + STRING_CONSTANT(Name, "*"); + + CameraAPI() + { + m_camera.m_pfnGetCamera = &QERApp_GetCamera; + m_camera.m_pfnSetCamera = &QERApp_SetCamera; + m_camera.m_pfnGetCamWindowExtents = &QERApp_GetCamWindowExtents; + } + _QERCameraTable* getTable() + { + return &m_camera; + } +}; + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +typedef SingletonModule CameraModule; +typedef Static StaticCameraModule; +StaticRegisterModule staticRegisterCamera(StaticCameraModule::instance()); + + diff --git a/radiant/pluginapi.h b/radiant/pluginapi.h new file mode 100644 index 00000000..267b0d7a --- /dev/null +++ b/radiant/pluginapi.h @@ -0,0 +1,33 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_PLUGINAPI_H) +#define INCLUDED_PLUGINAPI_H + +template class BasicVector3; +typedef BasicVector3 Vector3; + +// camera API +void QERApp_GetCamera( Vector3& origin, Vector3& angles ); +void QERApp_SetCamera( const Vector3& origin, const Vector3& angles ); +void QERApp_GetCamWindowExtents( int *x, int *y, int *width, int *height ); + +#endif diff --git a/radiant/pluginmanager.cpp b/radiant/pluginmanager.cpp new file mode 100644 index 00000000..35933014 --- /dev/null +++ b/radiant/pluginmanager.cpp @@ -0,0 +1,253 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// PlugInManager.cpp: implementation of the CPlugInManager class. +// +////////////////////////////////////////////////////////////////////// + +#include "pluginmanager.h" + +#include "modulesystem.h" +#include "qerplugin.h" +#include "iplugin.h" + +#include "math/vector.h" +#include "string/string.h" + +#include "error.h" +#include "select.h" +#include "plugin.h" + +#include "modulesystem.h" + +#include + +/* plugin manager --------------------------------------- */ +class CPluginSlot : public IPlugIn +{ + CopiedString m_menu_name; + const _QERPluginTable *mpTable; + std::list m_CommandStrings; + std::list m_CommandTitleStrings; + std::list m_CommandIDs; + +public: + /*! + build directly from a SYN_PROVIDE interface + */ + CPluginSlot(GtkWidget* main_window, const char* name, const _QERPluginTable& table); + /*! + dispatching a command by name to the plugin + */ + void Dispatch(const char *p); + + // IPlugIn ------------------------------------------------------------ + const char* getMenuName(); + std::size_t getCommandCount(); + const char* getCommand(std::size_t n); + const char* getCommandTitle(std::size_t n); + void addMenuID(std::size_t n); + bool ownsCommandID(std::size_t n); + +}; + +CPluginSlot::CPluginSlot(GtkWidget* main_window, const char* name, const _QERPluginTable& table) +{ + mpTable = &table; + m_menu_name = name; + + const char* commands = mpTable->m_pfnQERPlug_GetCommandList(); + const char* titles = mpTable->m_pfnQERPlug_GetCommandTitleList(); + + StringTokeniser commandTokeniser(commands, ",;"); + StringTokeniser titleTokeniser(titles, ",;"); + + const char* cmdToken = commandTokeniser.getToken(); + const char *titleToken = titleTokeniser.getToken(); + while (!string_empty(cmdToken)) + { + if(string_empty(titleToken)) + { + m_CommandStrings.push_back(cmdToken); + m_CommandTitleStrings.push_back(cmdToken); + cmdToken = commandTokeniser.getToken(); + titleToken = ""; + } + else + { + m_CommandStrings.push_back(cmdToken); + m_CommandTitleStrings.push_back(titleToken); + cmdToken = commandTokeniser.getToken(); + titleToken = titleTokeniser.getToken(); + } + } + mpTable->m_pfnQERPlug_Init(0, (void*)main_window); +} + +const char* CPluginSlot::getMenuName() +{ + return m_menu_name.c_str(); +} + +std::size_t CPluginSlot::getCommandCount() +{ + return m_CommandStrings.size(); +} + +const char* CPluginSlot::getCommand(std::size_t n) +{ + std::list::iterator i = m_CommandStrings.begin(); + while(n-- != 0) + ++i; + return (*i).c_str(); +} + +const char* CPluginSlot::getCommandTitle(std::size_t n) +{ + std::list::iterator i = m_CommandTitleStrings.begin(); + while(n-- != 0) + ++i; + return (*i).c_str(); +} + +void CPluginSlot::addMenuID(std::size_t n) +{ + m_CommandIDs.push_back(n); +} + +bool CPluginSlot::ownsCommandID(std::size_t n) +{ + for(std::list::iterator i = m_CommandIDs.begin(); i != m_CommandIDs.end(); ++i) + { + if (*i == n) + return true; + } + return false; +} + +void CPluginSlot::Dispatch(const char *p) +{ + Vector3 vMin, vMax; + Select_GetBounds (vMin, vMax); + mpTable->m_pfnQERPlug_Dispatch(p, reinterpret_cast(&vMin), reinterpret_cast(&vMax), true);//QE_SingleBrush(true)); +} + + +class CPluginSlots +{ + std::list mSlots; +public: + virtual ~CPluginSlots(); + + void AddPluginSlot(GtkWidget* main_window, const char* name, const _QERPluginTable& table) + { + mSlots.push_back(new CPluginSlot(main_window, name, table)); + } + + void PopulateMenu(PluginsVisitor& menu); + bool Dispatch(std::size_t n, const char* p); +}; + +CPluginSlots::~CPluginSlots() +{ + std::list::iterator iSlot; + for(iSlot=mSlots.begin(); iSlot!=mSlots.end(); ++iSlot) + { + delete *iSlot; + *iSlot = 0; + } +} + +void CPluginSlots::PopulateMenu(PluginsVisitor& menu) +{ + std::list::iterator iPlug; + for(iPlug=mSlots.begin(); iPlug != mSlots.end(); ++iPlug) + { + menu.visit(*(*iPlug)); + } +} + +bool CPluginSlots::Dispatch(std::size_t n, const char* p) +{ + std::list::iterator iPlug; + for(iPlug=mSlots.begin(); iPlug!=mSlots.end(); ++iPlug) + { + CPluginSlot *pPlug = *iPlug; + if (pPlug->ownsCommandID(n)) + { + pPlug->Dispatch(p); + return true; + } + } + return false; +} + +CPluginSlots g_plugin_slots; + + +void FillPluginSlots(CPluginSlots& slots, GtkWidget* main_window) +{ + class AddPluginVisitor : public PluginModules::Visitor + { + CPluginSlots& m_slots; + GtkWidget* m_main_window; + public: + AddPluginVisitor(CPluginSlots& slots, GtkWidget* main_window) + : m_slots(slots), m_main_window(main_window) + { + } + void visit(const char* name, const _QERPluginTable& table) const + { + m_slots.AddPluginSlot(m_main_window, name, table); + } + } visitor(slots, main_window); + + Radiant_getPluginModules().foreachModule(visitor); +} + + +#include "pluginmanager.h" + +CPlugInManager g_PlugInMgr; + +CPlugInManager& GetPlugInMgr() +{ + return g_PlugInMgr; +} + +void CPlugInManager::Dispatch(std::size_t n, const char * p) +{ + g_plugin_slots.Dispatch(n, p); +} + +void CPlugInManager::Init(GtkWidget* main_window) +{ + FillPluginSlots(g_plugin_slots, main_window); +} + +void CPlugInManager::constructMenu(PluginsVisitor& menu) +{ + g_plugin_slots.PopulateMenu(menu); +} + +void CPlugInManager::Shutdown() +{ +} diff --git a/radiant/pluginmanager.h b/radiant/pluginmanager.h new file mode 100644 index 00000000..cf148bb5 --- /dev/null +++ b/radiant/pluginmanager.h @@ -0,0 +1,65 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_PLUGINMANAGER_H) +#define INCLUDED_PLUGINMANAGER_H + +#include + +typedef struct _GtkWidget GtkWidget; + +/*! +\class IPlugin +pure virtual interface for a plugin +temporary solution for migration from old plugin tech to synapse plugins +*/ +class IPlugIn +{ +public: + IPlugIn() { } + virtual ~IPlugIn() { } + + virtual const char* getMenuName() = 0; + virtual std::size_t getCommandCount() = 0; + virtual const char* getCommand(std::size_t) = 0; + virtual const char* getCommandTitle(std::size_t) = 0; + virtual void addMenuID(std::size_t) = 0; + virtual bool ownsCommandID(std::size_t n) = 0; +}; + +class PluginsVisitor +{ +public: + virtual void visit(IPlugIn& plugin) = 0; +}; + +class CPlugInManager +{ +public: + void Dispatch(std::size_t n, const char *p); + void Init(GtkWidget* main_window); + void constructMenu(PluginsVisitor& menu); + void Shutdown(); +}; + +CPlugInManager& GetPlugInMgr(); + +#endif diff --git a/radiant/pluginmenu.cpp b/radiant/pluginmenu.cpp new file mode 100644 index 00000000..0fe758ed --- /dev/null +++ b/radiant/pluginmenu.cpp @@ -0,0 +1,196 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "pluginmenu.h" + +#include "stream/textstream.h" + +#include +#include + +#include "gtkutil/pointer.h" +#include "gtkutil/menu.h" + +#include "pluginmanager.h" +#include "mainframe.h" +#include "preferences.h" + + +int m_nNextPlugInID = 0; + +void plugin_activated(GtkWidget* widget, gpointer data) +{ + const char* str = (const char*)g_object_get_data(G_OBJECT(widget),"command"); + GetPlugInMgr().Dispatch(gpointer_to_int(data), str); +} + +#include +typedef std::stack WidgetStack; + +void PlugInMenu_Add(GtkMenu* plugin_menu, IPlugIn* pPlugIn) +{ + GtkWidget *menu, *item, *parent, *subMenu; + const char *menuText, *menuCommand; + WidgetStack menuStack; + + parent = gtk_menu_item_new_with_label (pPlugIn->getMenuName()); + gtk_widget_show (parent); + gtk_container_add (GTK_CONTAINER (plugin_menu), parent); + + std::size_t nCount = pPlugIn->getCommandCount(); + if (nCount > 0) + { + menu = gtk_menu_new(); + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (GTK_MENU(menu)); + while (nCount > 0) + { + menuText = pPlugIn->getCommandTitle(--nCount); + menuCommand = pPlugIn->getCommand(nCount); + + if (menuText != 0 && strlen(menuText) > 0) + { + if (!strcmp(menuText, "-")) + { + item = gtk_menu_item_new(); + gtk_widget_set_sensitive (item, FALSE); + } + else if (!strcmp(menuText, ">")) + { + menuText = pPlugIn->getCommandTitle(--nCount); + menuCommand = pPlugIn->getCommand(nCount); + if (!strcmp(menuText, "-") || !strcmp(menuText, ">") || !strcmp(menuText, "<")) + { + globalErrorStream() << pPlugIn->getMenuName() << " Invalid title (" << menuText << ") for submenu.\n"; + continue; + } + + item = gtk_menu_item_new_with_label(menuText); + gtk_widget_show (item); + gtk_container_add (GTK_CONTAINER (menu), item); + + subMenu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), subMenu); + menuStack.push(menu); + menu = subMenu; + continue; + } + else if (!strcmp(menuText, "<")) + { + if (!menuStack.empty()) + { + menu = menuStack.top(); + menuStack.pop(); + } + else + { + globalErrorStream() << pPlugIn->getMenuName() << ": Attempt to end non-existent submenu ignored.\n"; + } + continue; + } + else + { + item = gtk_menu_item_new_with_label (menuText); + g_object_set_data(G_OBJECT(item),"command", const_cast(static_cast(menuCommand))); + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(plugin_activated), gint_to_pointer(m_nNextPlugInID)); + } + gtk_widget_show (item); + gtk_container_add (GTK_CONTAINER (menu), item); + pPlugIn->addMenuID(m_nNextPlugInID++); + } + } + if (!menuStack.empty()) + { + std::size_t size = menuStack.size(); + if (size != 0) + { + globalErrorStream() << pPlugIn->getMenuName() << " mismatched > <. " << Unsigned(size) << " submenu(s) not closed.\n"; + } + for (std::size_t i = 0; i < (size -1); i++) + { + menuStack.pop(); + } + menu = menuStack.top(); + menuStack.pop(); + } + gtk_menu_item_set_submenu (GTK_MENU_ITEM (parent), menu); + } +} + +GtkMenu* g_plugins_menu = 0; +GtkMenuItem* g_plugins_menu_separator = 0; + +void PluginsMenu_populate() +{ + class PluginsMenuConstructor : public PluginsVisitor + { + GtkMenu* m_menu; + public: + PluginsMenuConstructor(GtkMenu* menu) : m_menu(menu) + { + } + void visit(IPlugIn& plugin) + { + PlugInMenu_Add(m_menu, &plugin); + } + }; + + PluginsMenuConstructor constructor(g_plugins_menu); + GetPlugInMgr().constructMenu(constructor); +} + +void PluginsMenu_clear() +{ + m_nNextPlugInID = 0; + + GList* lst = g_list_find (gtk_container_children(GTK_CONTAINER(g_plugins_menu)), GTK_WIDGET(g_plugins_menu_separator)); + while (lst->next) + { + gtk_container_remove (GTK_CONTAINER(g_plugins_menu), GTK_WIDGET (lst->next->data)); + lst = g_list_find (gtk_container_children(GTK_CONTAINER(g_plugins_menu)), GTK_WIDGET(g_plugins_menu_separator)); + } +} + +GtkMenuItem* create_plugins_menu() +{ + // Plugins menu + GtkMenuItem* plugins_menu_item = new_sub_menu_item_with_mnemonic("_Plugins"); + GtkMenu* menu = GTK_MENU(gtk_menu_item_get_submenu(plugins_menu_item)); + if (g_Layout_enableDetachableMenus.m_value) + { + menu_tearoff(menu); + } + + g_plugins_menu = menu; + + //TODO: some modules/plugins do not yet support refresh +#if 0 + create_menu_item_with_mnemonic(menu, "Refresh", FreeCaller()); + + // NOTE: the seperator is used when doing a refresh of the list, everything past the seperator is removed + g_plugins_menu_separator = menu_separator(menu); +#endif + + PluginsMenu_populate(); + + return plugins_menu_item; +} + diff --git a/radiant/pluginmenu.h b/radiant/pluginmenu.h new file mode 100644 index 00000000..f843bcb4 --- /dev/null +++ b/radiant/pluginmenu.h @@ -0,0 +1,33 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_PLUGINMENU_H) +#define INCLUDED_PLUGINMENU_H + +typedef struct _GtkMenuItem GtkMenuItem; +GtkMenuItem* create_plugins_menu(); + +typedef struct _GtkMenu GtkMenu; +typedef struct _GtkMenuItem GtkMenuItem; +void PluginsMenu_populate(); +void PluginsMenu_clear(); + +#endif diff --git a/radiant/plugintoolbar.cpp b/radiant/plugintoolbar.cpp new file mode 100644 index 00000000..4612fbd4 --- /dev/null +++ b/radiant/plugintoolbar.cpp @@ -0,0 +1,151 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "plugintoolbar.h" + + +#include "itoolbar.h" +#include "modulesystem.h" + +#include + +#include "stream/stringstream.h" +#include "gtkutil/image.h" +#include "gtkutil/container.h" + +#include "mainframe.h" +#include "plugin.h" + +GtkImage* new_plugin_image(const char* filename) +{ + { + StringOutputStream fullpath(256); + fullpath << GameToolsPath_get() << g_pluginsDir << "bitmaps/" << filename; + GtkImage* image = image_new_from_file_with_mask(fullpath.c_str()); + if(image != 0) + { + return image; + } + } + + { + StringOutputStream fullpath(256); + fullpath << AppPath_get() << g_pluginsDir << "bitmaps/" << filename; + GtkImage* image = image_new_from_file_with_mask(fullpath.c_str()); + if(image != 0) + { + return image; + } + } + + { + StringOutputStream fullpath(256); + fullpath << AppPath_get() << g_modulesDir << "bitmaps/" << filename; + GtkImage* image = image_new_from_file_with_mask(fullpath.c_str()); + if(image != 0) + { + return image; + } + } + + return image_new_missing(); +} + +inline GtkToolbarChildType gtktoolbarchildtype_for_toolbarbuttontype(IToolbarButton::EType type) +{ + switch(type) + { + case IToolbarButton::eSpace: + return GTK_TOOLBAR_CHILD_SPACE; + case IToolbarButton::eButton: + return GTK_TOOLBAR_CHILD_BUTTON; + case IToolbarButton::eToggleButton: + return GTK_TOOLBAR_CHILD_TOGGLEBUTTON; + case IToolbarButton::eRadioButton: + return GTK_TOOLBAR_CHILD_RADIOBUTTON; + } + ERROR_MESSAGE("invalid toolbar button type"); + return (GtkToolbarChildType)0; +} + +void toolbar_insert(GtkToolbar *toolbar, const char* icon, const char* text, const char* tooltip, IToolbarButton::EType type, GtkSignalFunc handler, gpointer data) +{ + gtk_toolbar_append_element(toolbar, gtktoolbarchildtype_for_toolbarbuttontype(type), 0, text, tooltip, "", GTK_WIDGET(new_plugin_image(icon)), handler, data); +} + +void ActivateToolbarButton(GtkWidget *widget, gpointer data) +{ + const_cast(reinterpret_cast(data))->activate(); +} + +void PlugInToolbar_AddButton(GtkToolbar* toolbar, const IToolbarButton* button) +{ + toolbar_insert(toolbar, button->getImage(), button->getText(), button->getTooltip(), button->getType(), GTK_SIGNAL_FUNC(ActivateToolbarButton), reinterpret_cast(const_cast(button))); +} + +GtkToolbar* g_plugin_toolbar = 0; + +void PluginToolbar_populate() +{ + class AddToolbarItemVisitor : public ToolbarModules::Visitor + { + GtkToolbar* m_toolbar; + public: + AddToolbarItemVisitor(GtkToolbar* toolbar) + : m_toolbar(toolbar) + { + } + void visit(const char* name, const _QERPlugToolbarTable& table) const + { + const std::size_t count = table.m_pfnToolbarButtonCount(); + for(std::size_t i=0;iGetCamWnd(); + Camera_setOrigin(camwnd, *i); + g_pParentWnd->GetXYWnd()->SetOrigin(*i); + { + Vector3 dir(vector3_normalised(vector3_subtracted(*(++i), Camera_getOrigin(camwnd)))); + Vector3 angles(Camera_getAngles(camwnd)); + angles[CAMERA_YAW] = static_cast(radians_to_degrees(atan2(dir[1], dir[0]))); + angles[CAMERA_PITCH] = static_cast(radians_to_degrees(asin(dir[2]))); + Camera_setAngles(camwnd, angles); + } +} + +// advance camera to previous point +void Pointfile_Prev (void) +{ + if(!s_pointfile.shown()) + return; + + if (s_check_point == s_pointfile.begin()) + { + globalOutputStream() << "Start of pointfile\n"; + return; + } + + CPointfile::const_iterator i = --s_check_point; + + CamWnd& camwnd = *g_pParentWnd->GetCamWnd(); + Camera_setOrigin(camwnd, *i); + g_pParentWnd->GetXYWnd()->SetOrigin(*i); + { + Vector3 dir(vector3_normalised(vector3_subtracted(*(++i), Camera_getOrigin(camwnd)))); + Vector3 angles(Camera_getAngles(camwnd)); + angles[CAMERA_YAW] = static_cast(radians_to_degrees(atan2(dir[1], dir[0]))); + angles[CAMERA_PITCH] = static_cast(radians_to_degrees(asin(dir[2]))); + Camera_setAngles(camwnd, angles); + } +} + +int LoadFile (const char *filename, void **bufferptr) +{ + FILE *f; + long len; + + f = fopen (filename, "rb"); + if (f == 0) + return -1; + + fseek (f, 0, SEEK_END); + len = ftell (f); + rewind (f); + + *bufferptr = malloc (len+1); + if (*bufferptr == 0) + return -1; + + fread (*bufferptr, 1, len, f); + fclose (f); + + // we need to end the buffer with a 0 + ((char*) (*bufferptr))[len] = 0; + + return len; +} + +void Pointfile_Parse(CPointfile& pointfile) +{ + int size; + char *data; + char *text; + int line = 1; + + const char* mapname = Map_Name(g_map); + StringOutputStream name(256); + name << StringRange(mapname, path_get_filename_base_end(mapname)) << ".lin"; + + size = LoadFile (name.c_str(), (void**)&data); + if (size == -1) + { + globalErrorStream() << "Pointfile " << name.c_str() << " not found\n"; + return; + } + + // store a pointer + text = data; + + globalOutputStream() << "Reading pointfile " << name.c_str() << "\n"; + + pointfile.Init(); + + while (*data) + { + Vector3 v; + if (sscanf(data,"%f %f %f", &v[0], &v[1], &v[2]) != 3) + { + globalOutputStream() << "Corrupt point file, line " << line << "\n"; + break; + } + + while (*data && *data != '\n') + { + if (*(data-1) == ' ' && *(data) == '-' && *(data+1) == ' ') + break; + data++; + } + // deal with zhlt style point files. + if (*data == '-') + { + if (sscanf(data,"- %f %f %f", &v[0], &v[1], &v[2]) != 3) + { + globalOutputStream() << "Corrupt point file, line " << line << "\n"; + break; + } + + while (*data && *data != '\n') + data++; + + } + while (*data == '\n') + { + data++; // skip the \n + line++; + } + pointfile.PushPoint (v); + } + + g_free(text); +} + +void Pointfile_Clear() +{ + s_pointfile.show(false); +} + +void Pointfile_Toggle() +{ + s_pointfile.show(!s_pointfile.shown()); + + s_check_point = s_pointfile.begin(); +} + +void Pointfile_Construct() +{ + CPointfile::constructStatic(); + + GlobalShaderCache().attachRenderable(s_pointfile); + + GlobalCommands_insert("TogglePointfile", FreeCaller()); + GlobalCommands_insert("NextLeakSpot", FreeCaller(), Accelerator('K', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + GlobalCommands_insert("PrevLeakSpot", FreeCaller(), Accelerator('L', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); +} + +void Pointfile_Destroy() +{ + GlobalShaderCache().detachRenderable(s_pointfile); + + CPointfile::destroyStatic(); +} + + + +// CPointfile implementation for SAX-specific stuff ------------------------------- +void CPointfile::saxStartElement (message_info_t *ctx, const xmlChar *name, const xmlChar **attrs) +{ + if(string_equal(reinterpret_cast(name), "polyline")) + { + Init(); + // there's a prefs setting to avoid stopping on leak + if (!g_WatchBSP_LeakStop) + ctx->stop_depth = 0; + } +} + +void CPointfile::saxEndElement (message_info_t *ctx, const xmlChar *name) +{ + if(string_equal(reinterpret_cast(name), "polyline")) + { + // we are done + GenerateDisplayList(); + SceneChangeNotify(); + s_check_point = begin(); + } + else if(string_equal(reinterpret_cast(name), "point")) + { + Vector3 v; + sscanf(m_characters.c_str(), "%f %f %f\n", &v[0], &v[1], &v[2]); + PushPoint(v); + m_characters.clear(); + } +} + +// only "point" is expected to have characters around here +void CPointfile::saxCharacters (message_info_t *ctx, const xmlChar *ch, int len) +{ + m_characters.write(reinterpret_cast(ch), len); +} + +const char* CPointfile::getName() +{ + return "Map leaked"; +} diff --git a/radiant/points.h b/radiant/points.h new file mode 100644 index 00000000..63665b78 --- /dev/null +++ b/radiant/points.h @@ -0,0 +1,40 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//----------------------------------------------------------------------------- +// +// DESCRIPTION: +// header for Pointfile stuff (adding a C++ class to wrap the pointfile thing in the SAX parser) +// + +#if !defined(INCLUDED_POINTS_H) +#define INCLUDED_POINTS_H + +void Pointfile_Clear(); +void Pointfile_Delete (void); + +void Pointfile_Construct(); +void Pointfile_Destroy(); + +class ISAXHandler; +extern ISAXHandler& g_pointfile; + +#endif diff --git a/radiant/preferencedictionary.cpp b/radiant/preferencedictionary.cpp new file mode 100644 index 00000000..598190eb --- /dev/null +++ b/radiant/preferencedictionary.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "preferencedictionary.h" + diff --git a/radiant/preferencedictionary.h b/radiant/preferencedictionary.h new file mode 100644 index 00000000..35a281ad --- /dev/null +++ b/radiant/preferencedictionary.h @@ -0,0 +1,295 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_PREFERENCEDICTIONARY_H) +#define INCLUDED_PREFERENCEDICTIONARY_H + +#include "preferencesystem.h" +#include "xml/ixml.h" +#include "stream/stringstream.h" +#include "generic/callback.h" +#include "versionlib.h" +#include + +class PreferenceDictionary : public PreferenceSystem +{ + class PreferenceEntry + { + StringImportCallback m_importer; + StringExportCallback m_exporter; + public: + PreferenceEntry(const StringImportCallback& importer, const StringExportCallback& exporter) + : m_importer(importer), m_exporter(exporter) + { + } + void importString(const char* string) + { + m_importer(string); + } + void exportString(const StringImportCallback& importer) + { + m_exporter(importer); + } + }; + + typedef std::map PreferenceEntries; + PreferenceEntries m_preferences; + + typedef std::map PreferenceCache; + PreferenceCache m_cache; + +public: + typedef PreferenceEntries::iterator iterator; + + iterator begin() + { + return m_preferences.begin(); + } + iterator end() + { + return m_preferences.end(); + } + iterator find(const char* name) + { + return m_preferences.find(name); + } + + void registerPreference(const char* name, const StringImportCallback& importer, const StringExportCallback& exporter) + { + m_preferences.insert(PreferenceEntries::value_type(name, PreferenceEntry(importer, exporter))); + PreferenceCache::iterator i = m_cache.find(name); + if(i != m_cache.end()) + { + importer((*i).second.c_str()); + m_cache.erase(i); + } + } + + void importPref(const char* name, const char* value) + { + PreferenceEntries::iterator i = m_preferences.find(name); + if(i != m_preferences.end()) + { + (*i).second.importString(value); + } + else + { + m_cache.insert(PreferenceCache::value_type(name, value)); + } + } +}; + +inline void XMLPreference_importString(XMLImporter& importer, const char* value) +{ + importer.write(value, string_length(value)); +} +typedef ReferenceCaller1 XMLPreferenceImportStringCaller; + +class XMLPreferenceDictionaryExporter : public XMLExporter +{ + class XMLQPrefElement : public XMLElement + { + const char* m_version; + public: + XMLQPrefElement(const char* version) : m_version(version) + { + } + const char* name() const + { + return "qpref"; + } + const char* attribute(const char* name) const + { + if(string_equal(name, "version")) + { + return m_version; + } + return ""; + } + void forEachAttribute(XMLAttrVisitor& visitor) const + { + visitor.visit("version", m_version); + } + }; + + class XMLPreferenceElement : public XMLElement + { + const char* m_name; + public: + XMLPreferenceElement(const char* name) + : m_name(name) + { + } + const char* name() const + { + return "epair"; + } + const char* attribute(const char* name) const + { + if(string_equal(name, "name")) + return m_name; + return ""; + } + void forEachAttribute(XMLAttrVisitor& visitor) const + { + visitor.visit("name", m_name); + } + }; + + typedef PreferenceDictionary PreferenceEntries; + PreferenceEntries& m_preferences; + const char* m_version; +public: + XMLPreferenceDictionaryExporter(PreferenceDictionary& preferences, const char* version) + : m_preferences(preferences), m_version(version) + { + } + + void exportXML(XMLImporter& importer) + { + importer.write("\n", 1); + + XMLQPrefElement qpref_element(m_version); + importer.pushElement(qpref_element); + importer.write("\n", 1); + + for(PreferenceEntries::iterator i = m_preferences.begin(); i != m_preferences.end(); ++i) + { + XMLPreferenceElement epair_element((*i).first.c_str()); + + importer.pushElement(epair_element); + + (*i).second.exportString(XMLPreferenceImportStringCaller(importer)); + + importer.popElement(epair_element.name()); + importer.write("\n", 1); + } + + importer.popElement(qpref_element.name()); + importer.write("\n", 1); + } +}; + +class XMLPreferenceDictionaryImporter : public XMLImporter +{ + struct xml_state_t + { + enum ETag + { + tag_qpref, + tag_qpref_ignore, + tag_epair, + tag_epair_ignore + }; + + xml_state_t(ETag tag) + : m_tag(tag) + { + } + + ETag m_tag; + CopiedString m_name; + StringOutputStream m_ostream; + }; + + typedef std::vector xml_stack_t; + xml_stack_t m_xml_stack; + + typedef PreferenceDictionary PreferenceEntries; + PreferenceEntries& m_preferences; + Version m_version; +public: + XMLPreferenceDictionaryImporter(PreferenceDictionary& preferences, const char* version) + : m_preferences(preferences), m_version(version_parse(version)) + { + } + + void pushElement(const XMLElement& element) + { + if(m_xml_stack.empty()) + { + if(string_equal(element.name(), "qpref")) + { + Version dataVersion(version_parse(element.attribute("version"))); + if(!version_compatible(m_version, dataVersion)) + { + globalOutputStream() << "qpref import: data version " << dataVersion << " is not compatible with code version " << m_version << "\n"; + m_xml_stack.push_back(xml_state_t::tag_qpref_ignore); + } + else + { + globalOutputStream() << "qpref import: data version " << dataVersion << " is compatible with code version " << m_version << "\n"; + m_xml_stack.push_back(xml_state_t::tag_qpref); + } + } + else + { + // not valid + } + } + else + { + switch(m_xml_stack.back().m_tag) + { + case xml_state_t::tag_qpref: + if(string_equal(element.name(), "epair")) + { + m_xml_stack.push_back(xml_state_t::tag_epair); + m_xml_stack.back().m_name = element.attribute("name"); + } + else + { + // not valid + } + break; + case xml_state_t::tag_qpref_ignore: + if(string_equal(element.name(), "epair")) + { + m_xml_stack.push_back(xml_state_t::tag_epair_ignore); + } + else + { + // not valid + } + break; + case xml_state_t::tag_epair: + case xml_state_t::tag_epair_ignore: + // not valid + break; + } + } + + } + void popElement(const char* name) + { + if(m_xml_stack.back().m_tag == xml_state_t::tag_epair) + { + m_preferences.importPref(m_xml_stack.back().m_name.c_str(), m_xml_stack.back().m_ostream.c_str()); + } + m_xml_stack.pop_back(); + } + std::size_t write(const char* buffer, std::size_t length) + { + return m_xml_stack.back().m_ostream.write(buffer, length); + } +}; + +#endif diff --git a/radiant/preferences.cpp b/radiant/preferences.cpp new file mode 100644 index 00000000..38693611 --- /dev/null +++ b/radiant/preferences.cpp @@ -0,0 +1,1055 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// User preferences +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "preferences.h" + +#include "debugging/debugging.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "generic/callback.h" +#include "math/vector.h" +#include "string/string.h" +#include "stream/stringstream.h" +#include "os/file.h" +#include "os/path.h" +#include "os/dir.h" +#include "gtkutil/filechooser.h" +#include "gtkutil/messagebox.h" +#include "cmdlib.h" + +#include "error.h" +#include "console.h" +#include "xywindow.h" +#include "mainframe.h" +#include "qe3.h" +#include "gtkdlgs.h" + + + +void Global_constructPreferences(PreferencesPage& page) +{ + page.appendCheckBox("Console", "Enable Logging", g_Console_enableLogging); +} + +void Interface_constructPreferences(PreferencesPage& page) +{ +#ifdef WIN32 + page.appendCheckBox("", "Default Text Editor", g_TextEditor_useWin32Editor); +#else + { + GtkWidget* use_custom = page.appendCheckBox("Text Editor", "Custom", g_TextEditor_useCustomEditor); + GtkWidget* custom_editor = page.appendPathEntry("Text Editor Command", g_TextEditor_editorCommand, true); + Widget_connectToggleDependency(custom_editor, use_custom); + } +#endif +} + +void Mouse_constructPreferences(PreferencesPage& page) +{ + { + const char* buttons[] = { "2 button", "3 button", }; + page.appendRadio("Mouse Type", g_glwindow_globals.m_nMouseType, STRING_ARRAY_RANGE(buttons)); + } + page.appendCheckBox("Right Button", "Activates Context Menu", g_xywindow_globals.m_bRightClick); +} +void Mouse_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Mouse", "Mouse Preferences")); + Mouse_constructPreferences(page); +} +void Mouse_registerPreferencesPage() +{ + PreferencesDialog_addInterfacePage(FreeCaller1()); +} + + +/*! +========================================================= +Games selection dialog +========================================================= +*/ + +#include + +inline const char* xmlAttr_getName(xmlAttrPtr attr) +{ + return reinterpret_cast(attr->name); +} + +inline const char* xmlAttr_getValue(xmlAttrPtr attr) +{ + return reinterpret_cast(attr->children->content); +} + +CGameDescription::CGameDescription(xmlDocPtr pDoc, const CopiedString& gameFile) +{ + // read the user-friendly game name + xmlNodePtr pNode = pDoc->children; + + while (strcmp((const char*)pNode->name, "game") && pNode != 0) + { + pNode=pNode->next; + } + if (!pNode) + { + Error("Didn't find 'game' node in the game description file '%s'\n", pDoc->URL); + } + + for(xmlAttrPtr attr = pNode->properties; attr != 0; attr = attr->next) + { + m_gameDescription.insert(GameDescription::value_type(xmlAttr_getName(attr), xmlAttr_getValue(attr))); + } + + { + StringOutputStream path(256); + path << AppPath_get() << gameFile.c_str() << "/"; + mGameToolsPath = path.c_str(); + } + + ASSERT_MESSAGE(file_exists(mGameToolsPath.c_str()), "game directory not found: " << makeQuoted(mGameToolsPath.c_str())); + + mGameFile = gameFile; + + { + GameDescription::iterator i = m_gameDescription.find("type"); + if(i == m_gameDescription.end()) + { + globalErrorStream() << "Warning, 'type' attribute not found in '" << reinterpret_cast(pDoc->URL) << "'\n"; + // default + mGameType = "q3"; + } + else + { + mGameType = (*i).second.c_str(); + } + } +} + +void CGameDescription::Dump() +{ + globalOutputStream() << "game description file: " << makeQuoted(mGameFile.c_str()) << "\n"; + for(GameDescription::iterator i = m_gameDescription.begin(); i != m_gameDescription.end(); ++i) + { + globalOutputStream() << (*i).first.c_str() << " = " << makeQuoted((*i).second.c_str()) << "\n"; + } +} + +CGameDescription *g_pGameDescription; ///< shortcut to g_GamesDialog.m_pCurrentDescription + + +#include "warnings.h" +#include "stream/textfilestream.h" +#include "container/array.h" +#include "xml/ixml.h" +#include "xml/xmlparser.h" +#include "xml/xmlwriter.h" + +#include "preferencedictionary.h" +#include "stringio.h" + +const char* const PREFERENCES_VERSION = "1.0"; + +bool Preferences_Load(PreferenceDictionary& preferences, const char* filename) +{ + TextFileInputStream file(filename); + if(!file.failed()) + { + XMLStreamParser parser(file); + XMLPreferenceDictionaryImporter importer(preferences, PREFERENCES_VERSION); + parser.exportXML(importer); + return true; + } + return false; +} + +bool Preferences_Save(PreferenceDictionary& preferences, const char* filename) +{ + TextFileOutputStream file(filename); + if(!file.failed()) + { + XMLStreamWriter writer(file); + XMLPreferenceDictionaryExporter exporter(preferences, PREFERENCES_VERSION); + exporter.exportXML(writer); + return true; + } + return false; +} + +bool Preferences_Save_Safe(PreferenceDictionary& preferences, const char* filename) +{ + Array tmpName(filename, filename + strlen(filename) + 1 + 3); + *(tmpName.end() - 4) = 'T'; + *(tmpName.end() - 3) = 'M'; + *(tmpName.end() - 2) = 'P'; + *(tmpName.end() - 1) = '\0'; + + return Preferences_Save(preferences, tmpName.data()) + && (!file_exists(filename) || file_remove(filename)) + && file_move(tmpName.data(), filename); +} + + + +void LogConsole_importString(const char* string) +{ + g_Console_enableLogging = string_equal(string, "true"); + Sys_LogFile(g_Console_enableLogging); +} +typedef FreeCaller1 LogConsoleImportStringCaller; + + +void RegisterGlobalPreferences(PreferenceSystem& preferences) +{ + preferences.registerPreference("gamefile", CopiedStringImportStringCaller(g_GamesDialog.m_sGameFile), CopiedStringExportStringCaller(g_GamesDialog.m_sGameFile)); + preferences.registerPreference("gamePrompt", BoolImportStringCaller(g_GamesDialog.m_bGamePrompt), BoolExportStringCaller(g_GamesDialog.m_bGamePrompt)); + preferences.registerPreference("log console", LogConsoleImportStringCaller(), BoolExportStringCaller(g_Console_enableLogging)); +} + + +PreferenceDictionary g_global_preferences; + +void GlobalPreferences_Init() +{ + RegisterGlobalPreferences(g_global_preferences); +} + +void CGameDialog::LoadPrefs() +{ + // load global .pref file + StringOutputStream strGlobalPref(256); + strGlobalPref << g_Preferences.m_global_rc_path->str << "global.pref"; + + globalOutputStream() << "loading global preferences from " << makeQuoted(strGlobalPref.c_str()) << "\n"; + + if(!Preferences_Load(g_global_preferences, strGlobalPref.c_str())) + { + globalOutputStream() << "failed to load global preferences from " << strGlobalPref.c_str() << "\n"; + } +} + +void CGameDialog::SavePrefs() +{ + StringOutputStream strGlobalPref(256); + strGlobalPref << g_Preferences.m_global_rc_path->str << "global.pref"; + + globalOutputStream() << "saving global preferences to " << strGlobalPref.c_str() << "\n"; + + if(!Preferences_Save_Safe(g_global_preferences, strGlobalPref.c_str())) + { + globalOutputStream() << "failed to save global preferences to " << strGlobalPref.c_str() << "\n"; + } +} + +void CGameDialog::DoGameDialog() +{ + // show the UI + DoModal(); + + // we save the prefs file + SavePrefs(); +} + +void CGameDialog::GameFileImport(int value) +{ + m_nComboSelect = value; + // use value to set m_sGameFile + std::list::iterator iGame = mGames.begin(); + int i; + for(i=0; imGameFile; +} + +void CGameDialog::GameFileExport(const IntImportCallback& importCallback) const +{ + // use m_sGameFile to set value + std::list::const_iterator iGame; + int i = 0; + for(iGame=mGames.begin(); iGame!=mGames.end(); ++iGame) + { + if ((*iGame)->mGameFile == m_sGameFile) + { + m_nComboSelect = i; + break; + } + i++; + } + importCallback(m_nComboSelect); +} + +void CGameDialog_GameFileImport(CGameDialog& self, int value) +{ + self.GameFileImport(value); +} + +void CGameDialog_GameFileExport(CGameDialog& self, const IntImportCallback& importCallback) +{ + self.GameFileExport(importCallback); +} + +void CGameDialog::CreateGlobalFrame(PreferencesPage& page) +{ + std::vector games; + games.reserve(mGames.size()); + for(std::list::iterator i = mGames.begin(); i != mGames.end(); ++i) + { + games.push_back((*i)->getRequiredKeyValue("name")); + } + page.appendCombo( + "Select the game", + StringArrayRange(&(*games.begin()), &(*games.end())), + ReferenceCaller1(*this), + ReferenceCaller1(*this) + ); + page.appendCheckBox("Startup", "Show Global Preferences", m_bGamePrompt); +} + +GtkWindow* CGameDialog::BuildDialog() +{ + GtkFrame* frame = create_dialog_frame("Game settings", GTK_SHADOW_ETCHED_IN); + + GtkVBox* vbox2 = create_dialog_vbox(0, 4); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(vbox2)); + + { + PreferencesPage preferencesPage(*this, GTK_WIDGET(vbox2)); + Global_constructPreferences(preferencesPage); + CreateGlobalFrame(preferencesPage); + } + + return create_simple_modal_dialog_window("Global Preferences", m_modal, GTK_WIDGET(frame)); +} + +class LoadGameFile +{ + std::list& mGames; + const char* mPath; +public: + LoadGameFile(std::list& games, const char* path) : mGames(games), mPath(path) + { + } + void operator()(const char* name) const + { + if(!extension_equal(path_get_extension(name), "game")) + { + return; + } + StringOutputStream strPath(256); + strPath << mPath << name; + globalOutputStream() << strPath.c_str() << '\n'; + + xmlDocPtr pDoc = xmlParseFile(strPath.c_str()); + if(pDoc) + { + mGames.push_front(new CGameDescription(pDoc, name)); + xmlFreeDoc(pDoc); + } + else + { + globalErrorStream() << "XML parser failed on '" << strPath.c_str() << "'\n"; + } + } +}; + +void CGameDialog::ScanForGames() +{ + StringOutputStream strGamesPath(256); + strGamesPath << AppPath_get() << "games/"; + const char *path = strGamesPath.c_str(); + + globalOutputStream() << "Scanning for game description files: " << path << '\n'; + + /*! + \todo FIXME LINUX: + do we put game description files below AppPath, or in ~/.radiant + i.e. read only or read/write? + my guess .. readonly cause it's an install + we will probably want to add ~/.radiant//games/ scanning on top of that for developers + (if that's really needed) + */ + + Directory_forEach(path, LoadGameFile(mGames, path)); +} + +CGameDescription* CGameDialog::GameDescriptionForComboItem() +{ + std::list::iterator iGame; + int i=0; + for(iGame=mGames.begin(); iGame!=mGames.end(); ++iGame,i++) + { + if (i == m_nComboSelect) + { + return (*iGame); + } + } + return 0; // not found +} + +void CGameDialog::InitGlobalPrefPath() +{ + g_Preferences.m_global_rc_path = g_string_new(SettingsPath_get()); +} + +void CGameDialog::Reset() +{ + if (!g_Preferences.m_global_rc_path) + InitGlobalPrefPath(); + StringOutputStream strGlobalPref(256); + strGlobalPref << g_Preferences.m_global_rc_path->str << "global.pref"; + file_remove(strGlobalPref.c_str()); +} + +void CGameDialog::Init() +{ + InitGlobalPrefPath(); + LoadPrefs(); + ScanForGames(); + if (mGames.empty()) + { + Error("Didn't find any valid game file descriptions, aborting\n"); + } + + CGameDescription* currentGameDescription = 0; + + if (!m_bGamePrompt) + { + // search by .game name + std::list::iterator iGame; + for(iGame=mGames.begin(); iGame!=mGames.end(); ++iGame) + { + if ((*iGame)->mGameFile == m_sGameFile) + { + currentGameDescription = (*iGame); + break; + } + } + } + if (m_bGamePrompt || !currentGameDescription) + { + Create(); + DoGameDialog(); + // use m_nComboSelect to identify the game to run as and set the globals + currentGameDescription = GameDescriptionForComboItem(); + ASSERT_NOTNULL(currentGameDescription); + } + g_pGameDescription = currentGameDescription; + + g_pGameDescription->Dump(); +} + +CGameDialog::~CGameDialog() +{ + // free all the game descriptions + std::list::iterator iGame; + for(iGame=mGames.begin(); iGame!=mGames.end(); ++iGame) + { + delete (*iGame); + *iGame = 0; + } + if(GetWidget() != 0) + { + Destroy(); + } +} + +inline const char* GameDescription_getIdentifier(const CGameDescription& gameDescription) +{ + const char* identifier = gameDescription.getKeyValue("index"); + if(string_empty(identifier)) + { + identifier = "1"; + } + return identifier; +} + +void CGameDialog::AddPacksURL(StringOutputStream &URL) +{ + // add the URLs for the list of game packs installed + // FIXME: this is kinda hardcoded for now.. + std::list::iterator iGame; + for(iGame=mGames.begin(); iGame!=mGames.end(); ++iGame) + { + URL << "&Games_dlup%5B%5D=" << GameDescription_getIdentifier(*(*iGame)); + } +} + +CGameDialog g_GamesDialog; + + +// ============================================================================= +// Widget callbacks for PrefsDlg + +static void OnButtonClean (GtkWidget *widget, gpointer data) +{ + // make sure this is what the user wants + if (gtk_MessageBox(GTK_WIDGET(g_Preferences.GetWidget()), "This will close Radiant and clean the corresponding registry entries.\n" + "Next time you start Radiant it will be good as new. Do you wish to continue?", + "Reset Registry", eMB_YESNO, eMB_ICONASTERISK) == eIDYES) + { + PrefsDlg *dlg = (PrefsDlg*)data; + dlg->EndModal (eIDCANCEL); + + g_preferences_globals.disable_ini = true; + Preferences_Reset(); + gtk_main_quit(); + } +} + +// ============================================================================= +// PrefsDlg class + +/* +======== + +very first prefs init deals with selecting the game and the game tools path +then we can load .ini stuff + +using prefs / ini settings: +those are per-game + +look in ~/.radiant//gamename +======== +*/ + +#define PREFS_LOCAL_FILENAME "local.pref" + +void PrefsDlg::Init() +{ + // m_global_rc_path has been set above + // m_rc_path is for game specific preferences + // takes the form: global-pref-path/gamename/prefs-file + + // this is common to win32 and Linux init now + m_rc_path = g_string_new (m_global_rc_path->str); + + // game sub-dir + g_string_append (m_rc_path, g_pGameDescription->mGameFile.c_str()); + g_string_append (m_rc_path, "/"); + Q_mkdir (m_rc_path->str); + + // then the ini file + m_inipath = g_string_new (m_rc_path->str); + g_string_append (m_inipath, PREFS_LOCAL_FILENAME); +} + +void notebook_set_page(GtkWidget* notebook, GtkWidget* page) +{ + int pagenum = gtk_notebook_page_num(GTK_NOTEBOOK(notebook), page); + if(gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook)) != pagenum) + { + gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), pagenum); + } +} + +void PrefsDlg::showPrefPage(GtkWidget* prefpage) +{ + notebook_set_page(m_notebook, prefpage); + return; +} + +static void treeSelection(GtkTreeSelection* selection, gpointer data) +{ + PrefsDlg *dlg = (PrefsDlg*)data; + + GtkTreeModel* model; + GtkTreeIter selected; + if(gtk_tree_selection_get_selected(selection, &model, &selected)) + { + GtkWidget* prefpage; + gtk_tree_model_get(model, &selected, 1, (gpointer*)&prefpage, -1); + dlg->showPrefPage(prefpage); + } +} + +typedef std::list PreferenceGroupCallbacks; + +inline void PreferenceGroupCallbacks_constructGroup(const PreferenceGroupCallbacks& callbacks, PreferenceGroup& group) +{ + for(PreferenceGroupCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i) + { + (*i)(group); + } +} + + +inline void PreferenceGroupCallbacks_pushBack(PreferenceGroupCallbacks& callbacks, const PreferenceGroupCallback& callback) +{ + callbacks.push_back(callback); +} + +typedef std::list PreferencesPageCallbacks; + +inline void PreferencesPageCallbacks_constructPage(const PreferencesPageCallbacks& callbacks, PreferencesPage& page) +{ + for(PreferencesPageCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i) + { + (*i)(page); + } +} + +inline void PreferencesPageCallbacks_pushBack(PreferencesPageCallbacks& callbacks, const PreferencesPageCallback& callback) +{ + callbacks.push_back(callback); +} + +PreferencesPageCallbacks g_interfacePreferences; +void PreferencesDialog_addInterfacePreferences(const PreferencesPageCallback& callback) +{ + PreferencesPageCallbacks_pushBack(g_interfacePreferences, callback); +} +PreferenceGroupCallbacks g_interfaceCallbacks; +void PreferencesDialog_addInterfacePage(const PreferenceGroupCallback& callback) +{ + PreferenceGroupCallbacks_pushBack(g_interfaceCallbacks, callback); +} + +PreferencesPageCallbacks g_displayPreferences; +void PreferencesDialog_addDisplayPreferences(const PreferencesPageCallback& callback) +{ + PreferencesPageCallbacks_pushBack(g_displayPreferences, callback); +} +PreferenceGroupCallbacks g_displayCallbacks; +void PreferencesDialog_addDisplayPage(const PreferenceGroupCallback& callback) +{ + PreferenceGroupCallbacks_pushBack(g_displayCallbacks, callback); +} + +PreferencesPageCallbacks g_settingsPreferences; +void PreferencesDialog_addSettingsPreferences(const PreferencesPageCallback& callback) +{ + PreferencesPageCallbacks_pushBack(g_settingsPreferences, callback); +} +PreferenceGroupCallbacks g_settingsCallbacks; +void PreferencesDialog_addSettingsPage(const PreferenceGroupCallback& callback) +{ + PreferenceGroupCallbacks_pushBack(g_settingsCallbacks, callback); +} + +void Widget_updateDependency(GtkWidget* self, GtkWidget* toggleButton) +{ + gtk_widget_set_sensitive(self, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggleButton)) && GTK_WIDGET_IS_SENSITIVE(toggleButton)); +} + +void ToggleButton_toggled_Widget_updateDependency(GtkWidget *toggleButton, GtkWidget* self) +{ + Widget_updateDependency(self, toggleButton); +} + +void ToggleButton_state_changed_Widget_updateDependency(GtkWidget* toggleButton, GtkStateType state, GtkWidget* self) +{ + if(state == GTK_STATE_INSENSITIVE) + { + Widget_updateDependency(self, toggleButton); + } +} + +void Widget_connectToggleDependency(GtkWidget* self, GtkWidget* toggleButton) +{ + g_signal_connect(G_OBJECT(toggleButton), "state_changed", G_CALLBACK(ToggleButton_state_changed_Widget_updateDependency), self); + g_signal_connect(G_OBJECT(toggleButton), "toggled", G_CALLBACK(ToggleButton_toggled_Widget_updateDependency), self); + Widget_updateDependency(self, toggleButton); +} + + +inline GtkWidget* getVBox(GtkWidget* page) +{ + return gtk_bin_get_child(GTK_BIN(page)); +} + +GtkTreeIter PreferenceTree_appendPage(GtkTreeStore* store, GtkTreeIter* parent, const char* name, GtkWidget* page) +{ + GtkTreeIter group; + gtk_tree_store_append(store, &group, parent); + gtk_tree_store_set(store, &group, 0, name, 1, page, -1); + return group; +} + +GtkWidget* PreferencePages_addPage(GtkWidget* notebook, const char* name) +{ + GtkWidget* preflabel = gtk_label_new(name); + gtk_widget_show(preflabel); + + GtkWidget* pageframe = gtk_frame_new(name); + gtk_container_set_border_width(GTK_CONTAINER(pageframe), 4); + gtk_widget_show(pageframe); + + GtkWidget* vbox = gtk_vbox_new(FALSE, 4); + gtk_widget_show(vbox); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 4); + gtk_container_add(GTK_CONTAINER(pageframe), vbox); + + // Add the page to the notebook + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), pageframe, preflabel); + + return pageframe; +} + +class PreferenceTreeGroup : public PreferenceGroup +{ + Dialog& m_dialog; + GtkWidget* m_notebook; + GtkTreeStore* m_store; + GtkTreeIter m_group; +public: + PreferenceTreeGroup(Dialog& dialog, GtkWidget* notebook, GtkTreeStore* store, GtkTreeIter group) : + m_dialog(dialog), + m_notebook(notebook), + m_store(store), + m_group(group) + { + } + PreferencesPage createPage(const char* treeName, const char* frameName) + { + GtkWidget* page = PreferencePages_addPage(m_notebook, frameName); + PreferenceTree_appendPage(m_store, &m_group, treeName, page); + return PreferencesPage(m_dialog, getVBox(page)); + } +}; + +GtkWindow* PrefsDlg::BuildDialog() +{ + PreferencesDialog_addInterfacePreferences(FreeCaller1()); + Mouse_registerPreferencesPage(); + + GtkWindow* dialog = create_floating_window("GtkRadiant Preferences", m_parent); + + { + GtkWidget* mainvbox = gtk_vbox_new(FALSE, 5); + gtk_container_add(GTK_CONTAINER(dialog), mainvbox); + gtk_container_set_border_width(GTK_CONTAINER(mainvbox), 5); + gtk_widget_show(mainvbox); + + { + GtkWidget* hbox = gtk_hbox_new(FALSE, 5); + gtk_widget_show(hbox); + gtk_box_pack_end(GTK_BOX(mainvbox), hbox, FALSE, TRUE, 0); + + { + GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &m_modal); + gtk_box_pack_end(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0); + } + { + GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &m_modal); + gtk_box_pack_end(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0); + } + { + GtkButton* button = create_dialog_button("Clean", G_CALLBACK(OnButtonClean), this); + gtk_box_pack_end(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0); + } + } + + { + GtkWidget* hbox = gtk_hbox_new(FALSE, 5); + gtk_box_pack_start(GTK_BOX(mainvbox), hbox, TRUE, TRUE, 0); + gtk_widget_show(hbox); + + { + GtkWidget* sc_win = gtk_scrolled_window_new(0, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sc_win), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start(GTK_BOX(hbox), sc_win, FALSE, FALSE, 0); + gtk_widget_show(sc_win); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sc_win), GTK_SHADOW_IN); + + // prefs pages notebook + m_notebook = gtk_notebook_new(); + // hide the notebook tabs since its not supposed to look like a notebook + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(m_notebook), FALSE); + gtk_box_pack_start(GTK_BOX(hbox), m_notebook, TRUE, TRUE, 0); + gtk_widget_show(m_notebook); + + + { + GtkTreeStore* store = gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_POINTER); + + GtkWidget* view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); + + { + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("Preferences", renderer, "text", 0, 0); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + } + + { + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); + g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(treeSelection), this); + } + + gtk_widget_show(view); + + gtk_container_add(GTK_CONTAINER (sc_win), view); + + { + /********************************************************************/ + /* Add preference tree options */ + /********************************************************************/ + // Front page... + //GtkWidget* front = + PreferencePages_addPage(m_notebook, "Front Page"); + + { + GtkWidget* global = PreferencePages_addPage(m_notebook, "Global Preferences"); + { + PreferencesPage preferencesPage(*this, getVBox(global)); + Global_constructPreferences(preferencesPage); + } + GtkTreeIter group = PreferenceTree_appendPage(store, 0, "Global", global); + { + GtkWidget* game = PreferencePages_addPage(m_notebook, "Game"); + PreferencesPage preferencesPage(*this, getVBox(game)); + g_GamesDialog.CreateGlobalFrame(preferencesPage); + + PreferenceTree_appendPage(store, &group, "Game", game); + } + } + + { + GtkWidget* interfacePage = PreferencePages_addPage(m_notebook, "Interface Preferences"); + { + PreferencesPage preferencesPage(*this, getVBox(interfacePage)); + PreferencesPageCallbacks_constructPage(g_interfacePreferences, preferencesPage); + } + + GtkTreeIter group = PreferenceTree_appendPage(store, 0, "Interface", interfacePage); + PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group); + + PreferenceGroupCallbacks_constructGroup(g_interfaceCallbacks, preferenceGroup); + } + + { + GtkWidget* display = PreferencePages_addPage(m_notebook, "Display Preferences"); + { + PreferencesPage preferencesPage(*this, getVBox(display)); + PreferencesPageCallbacks_constructPage(g_displayPreferences, preferencesPage); + } + GtkTreeIter group = PreferenceTree_appendPage(store, 0, "Display", display); + PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group); + + PreferenceGroupCallbacks_constructGroup(g_displayCallbacks, preferenceGroup); + } + + { + GtkWidget* settings = PreferencePages_addPage(m_notebook, "General Settings"); + { + PreferencesPage preferencesPage(*this, getVBox(settings)); + PreferencesPageCallbacks_constructPage(g_settingsPreferences, preferencesPage); + } + + GtkTreeIter group = PreferenceTree_appendPage(store, 0, "Settings", settings); + PreferenceTreeGroup preferenceGroup(*this, m_notebook, store, group); + + PreferenceGroupCallbacks_constructGroup(g_settingsCallbacks, preferenceGroup); + } + } + + gtk_tree_view_expand_all(GTK_TREE_VIEW(view)); + + g_object_unref(G_OBJECT(store)); + } + } + } + } + + gtk_notebook_set_page(GTK_NOTEBOOK(m_notebook), 0); + + return dialog; +} + +preferences_globals_t g_preferences_globals; + +PrefsDlg g_Preferences; // global prefs instance + + +void PreferencesDialog_constructWindow(GtkWindow* main_window) +{ + g_Preferences.m_parent = main_window; + g_Preferences.Create(); +} +void PreferencesDialog_destroyWindow() +{ + g_Preferences.Destroy(); +} + + +PreferenceDictionary g_preferences; + +PreferenceSystem& GetPreferenceSystem() +{ + return g_preferences; +} + +class PreferenceSystemAPI +{ + PreferenceSystem* m_preferencesystem; +public: + typedef PreferenceSystem Type; + STRING_CONSTANT(Name, "*"); + + PreferenceSystemAPI() + { + m_preferencesystem = &GetPreferenceSystem(); + } + PreferenceSystem* getTable() + { + return m_preferencesystem; + } +}; + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +typedef SingletonModule PreferenceSystemModule; +typedef Static StaticPreferenceSystemModule; +StaticRegisterModule staticRegisterPreferenceSystem(StaticPreferenceSystemModule::instance()); + +void Preferences_Load() +{ + g_GamesDialog.LoadPrefs(); + + globalOutputStream() << "loading local preferences from " << g_Preferences.m_inipath->str << "\n"; + + if(!Preferences_Load(g_preferences, g_Preferences.m_inipath->str)) + { + globalOutputStream() << "failed to load local preferences from " << g_Preferences.m_inipath->str << "\n"; + } +} + +void Preferences_Save() +{ + if (g_preferences_globals.disable_ini) + return; + + g_GamesDialog.SavePrefs(); + + globalOutputStream() << "saving local preferences to " << g_Preferences.m_inipath->str << "\n"; + + if(!Preferences_Save_Safe(g_preferences, g_Preferences.m_inipath->str)) + { + globalOutputStream() << "failed to save local preferences to " << g_Preferences.m_inipath->str << "\n"; + } +} + +void Preferences_Reset() +{ + file_remove(g_Preferences.m_inipath->str); +} + + +void PrefsDlg::PostModal (EMessageBoxReturn code) +{ + if (code == eIDOK) + { + Preferences_Save(); + UpdateAllWindows(); + } +} + +std::vector g_restart_required; + +void PreferencesDialog_restartRequired(const char* staticName) +{ + g_restart_required.push_back(staticName); +} + +void PreferencesDialog_showDialog() +{ + if(ConfirmModified("Edit Preferences") && g_Preferences.DoModal() == eIDOK) + { + if(!g_restart_required.empty()) + { + StringOutputStream message(256); + message << "Preference changes require a restart:\n"; + for(std::vector::iterator i = g_restart_required.begin(); i != g_restart_required.end(); ++i) + { + message << (*i) << '\n'; + } + gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), message.c_str()); + g_restart_required.clear(); + } + } +} + + + + + +void GameName_importString(const char* value) +{ + gamename_set(value); +} +typedef FreeCaller1 GameNameImportStringCaller; +void GameName_exportString(const StringImportCallback& importer) +{ + importer(gamename_get()); +} +typedef FreeCaller1 GameNameExportStringCaller; + +void GameMode_importString(const char* value) +{ + gamemode_set(value); +} +typedef FreeCaller1 GameModeImportStringCaller; +void GameMode_exportString(const StringImportCallback& importer) +{ + importer(gamemode_get()); +} +typedef FreeCaller1 GameModeExportStringCaller; + + +void RegisterPreferences(PreferenceSystem& preferences) +{ +#ifdef WIN32 + preferences.registerPreference("UseCustomShaderEditor", BoolImportStringCaller(g_TextEditor_useWin32Editor), BoolExportStringCaller(g_TextEditor_useWin32Editor)); +#else + preferences.registerPreference("UseCustomShaderEditor", BoolImportStringCaller(g_TextEditor_useCustomEditor), BoolExportStringCaller(g_TextEditor_useCustomEditor)); + preferences.registerPreference("CustomShaderEditorCommand", CopiedStringImportStringCaller(g_TextEditor_editorCommand), CopiedStringExportStringCaller(g_TextEditor_editorCommand)); +#endif + + preferences.registerPreference("GameName", GameNameImportStringCaller(), GameNameExportStringCaller()); + preferences.registerPreference("GameMode", GameModeImportStringCaller(), GameModeExportStringCaller()); +} + +void Preferences_Init() +{ + RegisterPreferences(GetPreferenceSystem()); +} diff --git a/radiant/preferences.h b/radiant/preferences.h new file mode 100644 index 00000000..497f183c --- /dev/null +++ b/radiant/preferences.h @@ -0,0 +1,434 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +The following source code is licensed by Id Software and subject to the terms of +its LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with +GtkRadiant. If you did not receive a LIMITED USE SOFTWARE LICENSE AGREEMENT, +please contact Id Software immediately at info@idsoftware.com. +*/ + +#if !defined(INCLUDED_PREFERENCES_H) +#define INCLUDED_PREFERENCES_H + +#include "libxml/parser.h" +#include "dialog.h" +#include +#include + +void Widget_connectToggleDependency(GtkWidget* self, GtkWidget* toggleButton); + +class PreferencesPage +{ + Dialog& m_dialog; + GtkWidget* m_vbox; +public: + PreferencesPage(Dialog& dialog, GtkWidget* vbox) : m_dialog(dialog), m_vbox(vbox) + { + } + GtkWidget* appendCheckBox(const char* name, const char* flag, bool& data) + { + return m_dialog.addCheckBox(m_vbox, name, flag, data); + } + GtkWidget* appendCheckBox(const char* name, const char* flag, const BoolImportCallback& importCallback, const BoolExportCallback& exportCallback) + { + return m_dialog.addCheckBox(m_vbox, name, flag, importCallback, exportCallback); + } + void appendCombo(const char* name, StringArrayRange values, const IntImportCallback& importCallback, const IntExportCallback& exportCallback) + { + m_dialog.addCombo(m_vbox, name, values, importCallback, exportCallback); + } + void appendCombo(const char* name, int& data, StringArrayRange values) + { + m_dialog.addCombo(m_vbox, name, data, values); + } + void appendSlider(const char* name, int& data, gboolean draw_value, const char* low, const char* high, double value, double lower, double upper, double step_increment, double page_increment, double page_size) + { + m_dialog.addSlider(m_vbox, name, data, draw_value, low, high, value, lower, upper, step_increment, page_increment, page_size); + } + void appendRadio(const char* name, StringArrayRange names, const IntImportCallback& importCallback, const IntExportCallback& exportCallback) + { + m_dialog.addRadio(m_vbox, name, names, importCallback, exportCallback); + } + void appendRadio(const char* name, int& data, StringArrayRange names) + { + m_dialog.addRadio(m_vbox, name, data, names); + } + void appendRadioIcons(const char* name, StringArrayRange icons, const IntImportCallback& importCallback, const IntExportCallback& exportCallback) + { + m_dialog.addRadioIcons(m_vbox, name, icons, importCallback, exportCallback); + } + void appendRadioIcons(const char* name, int& data, StringArrayRange icons) + { + m_dialog.addRadioIcons(m_vbox, name, data, icons); + } + GtkWidget* appendEntry(const char* name, const IntImportCallback& importCallback, const IntExportCallback& exportCallback) + { + return m_dialog.addIntEntry(m_vbox, name, importCallback, exportCallback); + } + GtkWidget* appendEntry(const char* name, int& data) + { + return m_dialog.addEntry(m_vbox, name, data); + } + GtkWidget* appendEntry(const char* name, const SizeImportCallback& importCallback, const SizeExportCallback& exportCallback) + { + return m_dialog.addSizeEntry(m_vbox, name, importCallback, exportCallback); + } + GtkWidget* appendEntry(const char* name, std::size_t& data) + { + return m_dialog.addEntry(m_vbox, name, data); + } + GtkWidget* appendEntry(const char* name, const FloatImportCallback& importCallback, const FloatExportCallback& exportCallback) + { + return m_dialog.addFloatEntry(m_vbox, name, importCallback, exportCallback); + } + GtkWidget* appendEntry(const char* name, float& data) + { + return m_dialog.addEntry(m_vbox, name, data); + } + GtkWidget* appendPathEntry(const char* name, bool browse_directory, const StringImportCallback& importCallback, const StringExportCallback& exportCallback) + { + return m_dialog.addPathEntry(m_vbox, name, browse_directory, importCallback, exportCallback); + } + GtkWidget* appendPathEntry(const char* name, CopiedString& data, bool directory) + { + return m_dialog.addPathEntry(m_vbox, name, data, directory); + } + GtkWidget* appendSpinner(const char* name, int& data, double value, double lower, double upper) + { + return m_dialog.addSpinner(m_vbox, name, data, value, lower, upper); + } + GtkWidget* appendSpinner(const char* name, double value, double lower, double upper, const IntImportCallback& importCallback, const IntExportCallback& exportCallback) + { + return m_dialog.addSpinner(m_vbox, name, value, lower, upper, importCallback, exportCallback); + } + GtkWidget* appendSpinner(const char* name, double value, double lower, double upper, const FloatImportCallback& importCallback, const FloatExportCallback& exportCallback) + { + return m_dialog.addSpinner(m_vbox, name, value, lower, upper, importCallback, exportCallback); + } +}; + +typedef Callback1 PreferencesPageCallback; + +class PreferenceGroup +{ +public: + virtual PreferencesPage createPage(const char* treeName, const char* frameName) = 0; +}; + +typedef Callback1 PreferenceGroupCallback; + +void PreferencesDialog_addInterfacePreferences(const PreferencesPageCallback& callback); +void PreferencesDialog_addInterfacePage(const PreferenceGroupCallback& callback); +void PreferencesDialog_addDisplayPreferences(const PreferencesPageCallback& callback); +void PreferencesDialog_addDisplayPage(const PreferenceGroupCallback& callback); +void PreferencesDialog_addSettingsPreferences(const PreferencesPageCallback& callback); +void PreferencesDialog_addSettingsPage(const PreferenceGroupCallback& callback); + +void PreferencesDialog_restartRequired(const char* staticName); + +template +class LatchedValue +{ +public: + Value m_value; + Value m_latched; + const char* m_description; + + LatchedValue(Value value, const char* description) : m_latched(value), m_description(description) + { + } + void useLatched() + { + m_value = m_latched; + } + void import(Value value) + { + m_latched = value; + if(m_latched != m_value) + { + PreferencesDialog_restartRequired(m_description); + } + } +}; + +typedef LatchedValue LatchedBool; +typedef MemberCaller1 LatchedBoolImportCaller; + +typedef LatchedValue LatchedInt; +typedef MemberCaller1 LatchedIntImportCaller; + +/*! +holds information for a given game +I'm a bit unclear on that still +it holds game specific configuration stuff +such as base names, engine names, some game specific features to activate in the various modules +it is not strictly a prefs thing since the user is not supposed to edit that (unless he is hacking +support for a new game) + +what we do now is fully generate the information for this during the setup. We might want to +generate a piece that just says "the game pack is there", but put the rest of the config somwhere +else (i.e. not generated, copied over during setup .. for instance in the game tools directory) +*/ +class CGameDescription +{ +typedef std::map GameDescription; + +public: + CopiedString mGameFile; ///< the .game file that describes this game + GameDescription m_gameDescription; + + CopiedString mGameToolsPath; ///< the explicit path to the game-dependent modules + CopiedString mGameType; ///< the type of the engine + + const char* getKeyValue(const char* key) const + { + GameDescription::const_iterator i = m_gameDescription.find(key); + if(i != m_gameDescription.end()) + { + return (*i).second.c_str(); + } + return ""; + } + const char* getRequiredKeyValue(const char* key) const + { + GameDescription::const_iterator i = m_gameDescription.find(key); + if(i != m_gameDescription.end()) + { + return (*i).second.c_str(); + } + ERROR_MESSAGE("game attribute " << makeQuoted(key) << " not found in " << makeQuoted(mGameFile.c_str())); + return ""; + } + + CGameDescription(xmlDocPtr pDoc, const CopiedString &GameFile); + + void Dump(); +}; + +extern CGameDescription *g_pGameDescription; + +typedef struct _GtkWidget GtkWidget; +class PrefsDlg; + +class PreferencesPage; + +class StringOutputStream; + +/*! +standalone dialog for games selection, and more generally global settings +*/ +class CGameDialog : public Dialog +{ +protected: + + mutable int m_nComboSelect; ///< intermediate int value for combo in dialog box + +public: + + /*! + those settings are saved in the global prefs file + I'm too lazy to wrap behind protected access, not sure this needs to be public + NOTE: those are preference settings. if you change them it is likely that you would + have to restart the editor for them to take effect + */ + /*@{*/ + /*! + what game has been selected + this is the name of the .game file + */ + CopiedString m_sGameFile; + /*! + prompt which game to load on startup + */ + bool m_bGamePrompt; + /*! + log console to radiant.log + m_bForceLogConsole is an obscure forced latching situation + */ + bool m_bForceLogConsole; + /*@}*/ + + /*! + the list of game descriptions we scanned from the game/ dir + */ + std::list mGames; + + CGameDialog() : + m_sGameFile(""), + m_bGamePrompt(true), + m_bForceLogConsole(false) + { + } + virtual ~CGameDialog(); + + void AddPacksURL(StringOutputStream &s); + + /*! + intialize the game dialog, called at CPrefsDlg::Init + will scan for games, load prefs, and do game selection dialog if needed + */ + void Init(); + + /*! + reset the global settings by removing the file + */ + void Reset(); + + /*! + run the dialog UI for the list of games + */ + void DoGameDialog(); + + /*! + Dialog API + this is only called when the dialog is built at startup for main engine select + */ + GtkWindow* BuildDialog(); + + void GameFileImport(int value); + void GameFileExport(const IntImportCallback& importCallback) const; + + /*! + construction of the dialog frame + this is the part to be re-used in prefs dialog + for the standalone dialog, we include this in a modal box + for prefs, we hook the frame in the main notebook + build the frame on-demand (only once) + */ + void CreateGlobalFrame(PreferencesPage& page); + + /*! + global preferences subsystem + XML-based this time, hopefully this will generalize to other prefs + LoadPrefs has hardcoded defaults + NOTE: it may not be strictly 'CGameDialog' to put the global prefs here + could have named the class differently I guess + */ + /*@{*/ + void LoadPrefs(); ///< load from file into variables + void SavePrefs(); ///< save pref variables to file + /*@}*/ + +private: + /*! + scan for .game files, load them + */ + void ScanForGames(); + + /*! + inits g_Preferences.m_global_rc_path + */ + void InitGlobalPrefPath(); + + /*! + uses m_nComboItem to find the right mGames + */ + CGameDescription *GameDescriptionForComboItem(); +}; + +/*! +this holds global level preferences +*/ +extern CGameDialog g_GamesDialog; + + +class texdef_t; + +class PrefsDlg : public Dialog +{ +public: +protected: + std::list mGames; + +public: + + GtkWidget *m_notebook; + + virtual ~PrefsDlg() + { + g_string_free (m_rc_path, true ); + g_string_free (m_inipath, true ); + } + + /*! + path for global settings + win32: AppPath + linux: ~/.radiant/[version]/ + */ + GString *m_global_rc_path; + + /*! + path to per-game settings + used for various game dependant storage + win32: GameToolsPath + linux: ~/.radiant/[version]/[gamename]/ + */ + GString *m_rc_path; + + /*! + holds per-game settings + m_rc_path+"local.pref" + \todo FIXME at some point this should become XML property bag code too + */ + GString *m_inipath; + + // initialize the above paths + void Init(); + + /*! Utility function for swapping notebook pages for tree list selections */ + void showPrefPage(GtkWidget* prefpage); + +protected: + + /*! Dialog API */ + GtkWindow* BuildDialog(); + void PostModal (EMessageBoxReturn code); +}; + +extern PrefsDlg g_Preferences; + +struct preferences_globals_t +{ + // disabled all INI / registry read write .. used when shutting down after registry cleanup + bool disable_ini; + preferences_globals_t() : disable_ini(false) + { + } +}; +extern preferences_globals_t g_preferences_globals; + +typedef struct _GtkWindow GtkWindow; +void PreferencesDialog_constructWindow(GtkWindow* main_window); +void PreferencesDialog_destroyWindow(); + +void PreferencesDialog_showDialog(); + +void GlobalPreferences_Init(); +void Preferences_Init(); + +void Preferences_Load(); +void Preferences_Save(); + +void Preferences_Reset(); + + +#endif diff --git a/radiant/qe3.cpp b/radiant/qe3.cpp new file mode 100644 index 00000000..a526c608 --- /dev/null +++ b/radiant/qe3.cpp @@ -0,0 +1,394 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +The following source code is licensed by Id Software and subject to the terms of +its LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with +GtkRadiant. If you did not receive a LIMITED USE SOFTWARE LICENSE AGREEMENT, +please contact Id Software immediately at info@idsoftware.com. +*/ + +// +// Linux stuff +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "qe3.h" + +#include "debugging/debugging.h" + +#include "ifilesystem.h" +//#include "imap.h" + +#include + +#include + +#include "stream/textfilestream.h" +#include "cmdlib.h" +#include "stream/stringstream.h" +#include "os/path.h" +#include "scenelib.h" + +#include "gtkutil/messagebox.h" +#include "error.h" +#include "map.h" +#include "build.h" +#include "points.h" +#include "camwindow.h" +#include "mainframe.h" +#include "preferences.h" +#include "watchbsp.h" +#include "autosave.h" +#include "convert.h" + +QEGlobals_t g_qeglobals; + + +#if defined(WIN32) +#define PATH_MAX 260 +#endif + + +void QE_InitVFS() +{ + // VFS initialization ----------------------- + // we will call GlobalFileSystem().initDirectory, giving the directories to look in (for files in pk3's and for standalone files) + // we need to call in order, the mod ones first, then the base ones .. they will be searched in this order + // *nix systems have a dual filesystem in ~/.q3a, which is searched first .. so we need to add that too + + const char* gamename = gamename_get(); + const char* basegame = basegame_get(); +#if defined(POSIX) + const char* userRoot = g_qeglobals.m_userEnginePath.c_str(); +#endif + const char* globalRoot = EnginePath_get(); + + // if we have a mod dir + if(!string_equal(gamename, basegame)) + { +#if defined(POSIX) + // ~/./ + { + StringOutputStream userGamePath(256); + userGamePath << userRoot << gamename << '/'; + GlobalFileSystem().initDirectory(userGamePath.c_str()); + } +#endif + + // / + { + StringOutputStream globalGamePath(256); + globalGamePath << globalRoot << gamename << '/'; + GlobalFileSystem().initDirectory(globalGamePath.c_str()); + } + } + +#if defined(POSIX) + // ~/./ + { + StringOutputStream userBasePath(256); + userBasePath << userRoot << basegame << '/'; + GlobalFileSystem().initDirectory(userBasePath.c_str()); + } +#endif + + // / + { + StringOutputStream globalBasePath(256); + globalBasePath << globalRoot << basegame << '/'; + GlobalFileSystem().initDirectory(globalBasePath.c_str()); + } +} + +int g_numbrushes = 0; +int g_numentities = 0; + +void QE_UpdateStatusBar() +{ + char buffer[128]; + sprintf(buffer, "Brushes: %d Entities: %d", g_numbrushes, g_numentities); + g_pParentWnd->SetStatusText(g_pParentWnd->m_brushcount_status, buffer); +} + +SimpleCounter g_brushCount; + +void QE_brushCountChanged() +{ + g_numbrushes = int(g_brushCount.get()); + QE_UpdateStatusBar(); +} + +SimpleCounter g_entityCount; + +void QE_entityCountChanged() +{ + g_numentities = int(g_entityCount.get()); + QE_UpdateStatusBar(); +} + +bool ConfirmModified(const char* title) +{ + if (!Map_Modified(g_map)) + return true; + + EMessageBoxReturn result = gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), "The current map has changed since it was last saved.\nDo you want to save the current map before continuing?", title, eMB_YESNOCANCEL, eMB_ICONQUESTION); + if(result == eIDCANCEL) + { + return false; + } + if(result == eIDYES) + { + if(Map_Unnamed(g_map)) + { + return Map_SaveAs(); + } + else + { + return Map_Save(); + } + } + return true; +} + + +const char* const EXECUTABLE_TYPE = +#if defined(__linux__) || defined (__FreeBSD__) +"x86" +#elif defined(__APPLE__) +"ppc" +#elif defined(WIN32) +"exe" +#else +#error "unknown platform" +#endif +; + +void bsp_init() +{ + build_set_variable("RadiantPath", AppPath_get()); + build_set_variable("ExecutableType", EXECUTABLE_TYPE); + build_set_variable("EnginePath", EnginePath_get()); + build_set_variable("MonitorAddress", (g_WatchBSP_Enabled) ? "127.0.0.1:39000" : ""); + build_set_variable("GameName", gamename_get()); + + build_set_variable("MapFile", Map_Name(g_map)); +} + +void bsp_shutdown() +{ + build_clear_variables(); +} + +class ArrayCommandListener : public CommandListener +{ + GPtrArray* m_array; +public: + ArrayCommandListener() + { + m_array = g_ptr_array_new(); + } + ~ArrayCommandListener() + { + g_ptr_array_free(m_array, TRUE); + } + + void execute(const char* command) + { + g_ptr_array_add(m_array, g_strdup(command)); + } + + GPtrArray* array() const + { + return m_array; + } +}; + +class BatchCommandListener : public CommandListener +{ + TextOutputStream& m_file; + std::size_t m_commandCount; + const char* m_outputRedirect; +public: + BatchCommandListener(TextOutputStream& file, const char* outputRedirect) : m_file(file), m_commandCount(0), m_outputRedirect(outputRedirect) + { + } + + void execute(const char* command) + { + m_file << command; + if (m_commandCount == 0) + { + m_file << " > "; + } + else + { + m_file << " >> "; + } + m_file << "\"" << m_outputRedirect << "\""; + m_file << "\n"; + ++m_commandCount; + } +}; + +bool Region_cameraValid() +{ + Vector3 vOrig(vector3_snapped(Camera_getOrigin(*g_pParentWnd->GetCamWnd()))); + + for (int i=0 ; i<3 ; i++) + { + if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i]) + { + return false; + } + } + return true; +} + + +void RunBSP(const char* name) +{ + // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=503 + // make sure we don't attempt to region compile a map with the camera outside the region + if (region_active && !Region_cameraValid()) + { + globalErrorStream() << "The camera must be in the region to start a region compile.\n"; + return; + } + + SaveMap(); + + if(Map_Unnamed(g_map)) + { + globalOutputStream() << "build cancelled\n"; + return; + } + + if (g_SnapShots_Enabled && !Map_Unnamed(g_map) && Map_Modified(g_map)) + { + Map_Snapshot(); + } + + if (region_active) + { + const char* mapname = Map_Name(g_map); + StringOutputStream name(256); + name << StringRange(mapname, path_get_filename_base_end(mapname)) << ".reg"; + Map_SaveRegion(name.c_str()); + } + + Pointfile_Delete(); + + bsp_init(); + + if (g_WatchBSP_Enabled) + { + ArrayCommandListener listener; + build_run(name, listener); + // grab the file name for engine running + const char* fullname = Map_Name(g_map); + StringOutputStream bspname(64); + bspname << StringRange(path_get_filename_start(fullname), path_get_filename_base_end(fullname)); + BuildMonitor_Run( listener.array(), bspname.c_str() ); + } + else + { + char junkpath[PATH_MAX]; + strcpy(junkpath, SettingsPath_get()); + strcat(junkpath, "junk.txt"); + + char batpath[PATH_MAX]; +#if defined(POSIX) + strcpy(batpath, SettingsPath_get()); + strcat(batpath, "qe3bsp.sh"); +#elif defined(WIN32) + strcpy(batpath, SettingsPath_get()); + strcat(batpath, "qe3bsp.bat"); +#else +#error "unsupported platform" +#endif + bool written = false; + { + TextFileOutputStream batchFile(batpath); + if(!batchFile.failed()) + { +#if defined (POSIX) + batchFile << "#!/bin/sh \n\n"; +#endif + BatchCommandListener listener(batchFile, junkpath); + build_run(name, listener); + written = true; + } + } + if(written) + { +#if defined (POSIX) + chmod (batpath, 0744); +#endif + globalOutputStream() << "Writing the compile script to '" << batpath << "'\n"; + globalOutputStream() << "The build output will be saved in '" << junkpath << "'\n"; + Q_Exec(batpath, NULL, NULL, true); + } + } + + bsp_shutdown(); +} + +// ============================================================================= +// Sys_ functions + +void Sys_SetTitle(const char *text, bool modified) +{ + StringOutputStream title; + title << ConvertLocaleToUTF8(text); + + if(modified) + { + title << " *"; + } + + gtk_window_set_title(MainFrame_getWindow(), title.c_str()); +} + +bool g_bWaitCursor = false; + +void Sys_BeginWait (void) +{ + ScreenUpdates_Disable("Processing...", "Please Wait"); + GdkCursor *cursor = gdk_cursor_new (GDK_WATCH); + gdk_window_set_cursor(GTK_WIDGET(MainFrame_getWindow())->window, cursor); + gdk_cursor_unref (cursor); + g_bWaitCursor = true; +} + +void Sys_EndWait (void) +{ + ScreenUpdates_Enable(); + gdk_window_set_cursor(GTK_WIDGET(MainFrame_getWindow())->window, 0); + g_bWaitCursor = false; +} + +void Sys_Beep (void) +{ + gdk_beep(); +} + diff --git a/radiant/qe3.h b/radiant/qe3.h new file mode 100644 index 00000000..b07223a0 --- /dev/null +++ b/radiant/qe3.h @@ -0,0 +1,66 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_QE3_H) +#define INCLUDED_QE3_H + +#include "string/string.h" + +// +// system functions +// +void Sys_SetTitle (const char *text, bool modified); + + +void RunBSP(const char* name); + + +void QE_InitVFS(); + +void QE_brushCountChanged(); +void QE_entityCountChanged(); + +bool ConfirmModified(const char* title); + + +// most of the QE globals are stored in this structure +typedef struct +{ + /*! + win32: engine full path. + unix: user home full path + engine dir. + */ + CopiedString m_userEnginePath; + /*! + cache for m_userEnginePath + mod subdirectory. + */ + CopiedString m_userGamePath; + +} QEGlobals_t; + +extern QEGlobals_t g_qeglobals; + +class SimpleCounter; +extern SimpleCounter g_brushCount; +extern SimpleCounter g_entityCount; + + +#endif diff --git a/radiant/qgl.cpp b/radiant/qgl.cpp new file mode 100644 index 00000000..18fe0532 --- /dev/null +++ b/radiant/qgl.cpp @@ -0,0 +1,1658 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "qgl.h" + +#include "debugging/debugging.h" + +#include +#include +#include + +#if defined(_WIN32) +#define WINGDIAPI __declspec(dllimport) +#define APIENTRY __stdcall +#endif + +#include + +#if defined(_WIN32) +#undef WINGDIAPI +#undef APIENTRY +#endif + +#include "igl.h" + + + + +#if defined(_WIN32) + +#include + +int ( WINAPI * qwglChoosePixelFormat )(HDC, CONST PIXELFORMATDESCRIPTOR *); +int ( WINAPI * qwglDescribePixelFormat) (HDC, int, UINT, LPPIXELFORMATDESCRIPTOR); +int ( WINAPI * qwglGetPixelFormat)(HDC); +BOOL ( WINAPI * qwglSetPixelFormat)(HDC, int, CONST PIXELFORMATDESCRIPTOR *); +BOOL ( WINAPI * qwglSwapBuffers)(HDC); + +BOOL ( WINAPI * qwglCopyContext)(HGLRC, HGLRC, UINT); +HGLRC ( WINAPI * qwglCreateContext)(HDC); +HGLRC ( WINAPI * qwglCreateLayerContext)(HDC, int); +BOOL ( WINAPI * qwglDeleteContext)(HGLRC); +HGLRC ( WINAPI * qwglGetCurrentContext)(VOID); +HDC ( WINAPI * qwglGetCurrentDC)(VOID); +PROC ( WINAPI * qwglGetProcAddress)(LPCSTR); +BOOL ( WINAPI * qwglMakeCurrent)(HDC, HGLRC); +BOOL ( WINAPI * qwglShareLists)(HGLRC, HGLRC); +BOOL ( WINAPI * qwglUseFontBitmaps)(HDC, DWORD, DWORD, DWORD); + +BOOL ( WINAPI * qwglUseFontOutlines)(HDC, DWORD, DWORD, DWORD, FLOAT, + FLOAT, int, LPGLYPHMETRICSFLOAT); + +BOOL ( WINAPI * qwglDescribeLayerPlane)(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); +int ( WINAPI * qwglSetLayerPaletteEntries)(HDC, int, int, int, CONST COLORREF *); +int ( WINAPI * qwglGetLayerPaletteEntries)(HDC, int, int, int, COLORREF *); +BOOL ( WINAPI * qwglRealizeLayerPalette)(HDC, int, BOOL); +BOOL ( WINAPI * qwglSwapLayerBuffers)(HDC, UINT); + +#elif defined (XWINDOWS) + +#include +#include +#include + +XVisualInfo* (*qglXChooseVisual)(Display *dpy, int screen, int *attribList); +GLXContext (*qglXCreateContext)(Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct); +void (*qglXDestroyContext)(Display *dpy, GLXContext ctx); +Bool (*qglXMakeCurrent)(Display *dpy, GLXDrawable drawable, GLXContext ctx); +void (*qglXCopyContext)(Display *dpy, GLXContext src, GLXContext dst, GLuint mask); +void (*qglXSwapBuffers)( Display *dpy, GLXDrawable drawable ); +GLXPixmap (*qglXCreateGLXPixmap)( Display *dpy, XVisualInfo *visual, Pixmap pixmap ); +void (*qglXDestroyGLXPixmap)( Display *dpy, GLXPixmap pixmap ); +Bool (*qglXQueryExtension)( Display *dpy, int *errorb, int *event ); +Bool (*qglXQueryVersion)( Display *dpy, int *maj, int *min ); +Bool (*qglXIsDirect)( Display *dpy, GLXContext ctx ); +int (*qglXGetConfig)( Display *dpy, XVisualInfo *visual, int attrib, int *value ); +GLXContext (*qglXGetCurrentContext)( void ); +GLXDrawable (*qglXGetCurrentDrawable)( void ); +void (*qglXWaitGL)( void ); +void (*qglXWaitX)( void ); +void (*qglXUseXFont)( Font font, int first, int count, int list ); +void* (*qglXGetProcAddressARB) (const GLubyte *procName); +typedef void* (*glXGetProcAddressARBProc) (const GLubyte *procName); + +#else +#error "unsupported platform" +#endif + + +void QGL_Shutdown(OpenGLBinding& table) +{ + globalOutputStream() << "Shutting down OpenGL module..."; + +#if defined(WIN32) + qwglCopyContext = 0; + qwglCreateContext = 0; + qwglCreateLayerContext = 0; + qwglDeleteContext = 0; + qwglDescribeLayerPlane = 0; + qwglGetCurrentContext = 0; + qwglGetCurrentDC = 0; + qwglGetLayerPaletteEntries = 0; + qwglGetProcAddress = 0; + qwglMakeCurrent = 0; + qwglRealizeLayerPalette = 0; + qwglSetLayerPaletteEntries = 0; + qwglShareLists = 0; + qwglSwapLayerBuffers = 0; + qwglUseFontBitmaps = 0; + qwglUseFontOutlines = 0; + + qwglChoosePixelFormat = 0; + qwglDescribePixelFormat = 0; + qwglGetPixelFormat = 0; + qwglSetPixelFormat = 0; + qwglSwapBuffers = 0; +#elif defined(XWINDOWS) + qglXChooseVisual = 0; + qglXCreateContext = 0; + qglXDestroyContext = 0; + qglXMakeCurrent = 0; + qglXCopyContext = 0; + qglXSwapBuffers = 0; + qglXCreateGLXPixmap = 0; + qglXDestroyGLXPixmap = 0; + qglXQueryExtension = 0; + qglXQueryVersion = 0; + qglXIsDirect = 0; + qglXGetConfig = 0; + qglXGetCurrentContext = 0; + qglXGetCurrentDrawable = 0; + qglXWaitGL = 0; + qglXWaitX = 0; + qglXUseXFont = 0; + qglXGetProcAddressARB = 0; +#else +#error "unsupported platform" +#endif + + globalOutputStream() << "Done.\n"; +} + + +typedef struct glu_error_struct +{ + GLenum errnum; + const char *errstr; +} GLU_ERROR_STRUCT; + +GLU_ERROR_STRUCT glu_errlist[] = { + {GL_NO_ERROR, "GL_NO_ERROR - no error"}, + {GL_INVALID_ENUM, "GL_INVALID_ENUM - An unacceptable value is specified for an enumerated argument."}, + {GL_INVALID_VALUE, "GL_INVALID_VALUE - A numeric argument is out of range."}, + {GL_INVALID_OPERATION, "GL_INVALID_OPERATION - The specified operation is not allowed in the current state."}, + {GL_STACK_OVERFLOW, "GL_STACK_OVERFLOW - Function would cause a stack overflow."}, + {GL_STACK_UNDERFLOW, "GL_STACK_UNDERFLOW - Function would cause a stack underflow."}, + {GL_OUT_OF_MEMORY, "GL_OUT_OF_MEMORY - There is not enough memory left to execute the function."}, + {0, 0} +}; + +const GLubyte* qgluErrorString(GLenum errCode ) +{ + int search = 0; + for (search = 0; glu_errlist[search].errstr; search++) + { + if (errCode == glu_errlist[search].errnum) + return (const GLubyte *)glu_errlist[search].errstr; + } //end for + return (const GLubyte *)"Unknown error"; +} + + +void glInvalidFunction() +{ + ERROR_MESSAGE("calling an invalid OpenGL function"); +} + +#define EXTENSIONS_ENABLED 1 + +bool QGL_ExtensionSupported(const char* extension) +{ +#if EXTENSIONS_ENABLED + const GLubyte *extensions = 0; + const GLubyte *start; + GLubyte *where, *terminator; + + // Extension names should not have spaces. + where = (GLubyte *) strchr (extension, ' '); + if (where || *extension == '\0') + return false; + + extensions = GlobalOpenGL().m_glGetString (GL_EXTENSIONS); +#ifndef __APPLE__ + if (!extensions) + return false; +#endif + + // It takes a bit of care to be fool-proof about parsing the + // OpenGL extensions string. Don't be fooled by sub-strings, etc. + for (start = extensions; ;) + { + where = (GLubyte *) strstr ((const char *) start, extension); + if (!where) + break; + + terminator = where + strlen (extension); + if (where == start || *(where - 1) == ' ') + if (*terminator == ' ' || *terminator == '\0') + return true; + + start = terminator; + } +#endif + + return false; +} + +typedef int (QGL_DLLEXPORT *QGLFunctionPointer)(); + +QGLFunctionPointer QGL_getExtensionFunc(const char* symbol) +{ +#if defined(XWINDOWS) + //ASSERT_NOTNULL(qglXGetProcAddressARB); + if (qglXGetProcAddressARB == 0) + { + return reinterpret_cast(glInvalidFunction); + } + else + { + return (QGLFunctionPointer)qglXGetProcAddressARB(reinterpret_cast(symbol)); + } +#elif defined(WIN32) + ASSERT_NOTNULL(qwglGetProcAddress); + return qwglGetProcAddress(symbol); +#else +#error "unsupported platform" +#endif +} + + +template +bool QGL_constructExtensionFunc(Func& func, const char* symbol) +{ + func = reinterpret_cast(QGL_getExtensionFunc(symbol)); + return func != 0; +} + +template +void QGL_invalidateExtensionFunc(Func& func) +{ + func = reinterpret_cast(glInvalidFunction); +} + +void QGL_clear(OpenGLBinding& table) +{ + QGL_invalidateExtensionFunc(table.m_glAccum); + QGL_invalidateExtensionFunc(table.m_glAlphaFunc); + QGL_invalidateExtensionFunc(table.m_glAreTexturesResident); + QGL_invalidateExtensionFunc(table.m_glArrayElement); + QGL_invalidateExtensionFunc(table.m_glBegin); + QGL_invalidateExtensionFunc(table.m_glBindTexture); + QGL_invalidateExtensionFunc(table.m_glBitmap); + QGL_invalidateExtensionFunc(table.m_glBlendFunc); + QGL_invalidateExtensionFunc(table.m_glCallList); + QGL_invalidateExtensionFunc(table.m_glCallLists); + QGL_invalidateExtensionFunc(table.m_glClear); + QGL_invalidateExtensionFunc(table.m_glClearAccum); + QGL_invalidateExtensionFunc(table.m_glClearColor); + QGL_invalidateExtensionFunc(table.m_glClearDepth); + QGL_invalidateExtensionFunc(table.m_glClearIndex); + QGL_invalidateExtensionFunc(table.m_glClearStencil); + QGL_invalidateExtensionFunc(table.m_glClipPlane); + QGL_invalidateExtensionFunc(table.m_glColor3b); + QGL_invalidateExtensionFunc(table.m_glColor3bv); + QGL_invalidateExtensionFunc(table.m_glColor3d); + QGL_invalidateExtensionFunc(table.m_glColor3dv); + QGL_invalidateExtensionFunc(table.m_glColor3f); + QGL_invalidateExtensionFunc(table.m_glColor3fv); + QGL_invalidateExtensionFunc(table.m_glColor3i); + QGL_invalidateExtensionFunc(table.m_glColor3iv); + QGL_invalidateExtensionFunc(table.m_glColor3s); + QGL_invalidateExtensionFunc(table.m_glColor3sv); + QGL_invalidateExtensionFunc(table.m_glColor3ub); + QGL_invalidateExtensionFunc(table.m_glColor3ubv); + QGL_invalidateExtensionFunc(table.m_glColor3ui); + QGL_invalidateExtensionFunc(table.m_glColor3uiv); + QGL_invalidateExtensionFunc(table.m_glColor3us); + QGL_invalidateExtensionFunc(table.m_glColor3usv); + QGL_invalidateExtensionFunc(table.m_glColor4b); + QGL_invalidateExtensionFunc(table.m_glColor4bv); + QGL_invalidateExtensionFunc(table.m_glColor4d); + QGL_invalidateExtensionFunc(table.m_glColor4dv); + QGL_invalidateExtensionFunc(table.m_glColor4f); + QGL_invalidateExtensionFunc(table.m_glColor4fv); + QGL_invalidateExtensionFunc(table.m_glColor4i); + QGL_invalidateExtensionFunc(table.m_glColor4iv); + QGL_invalidateExtensionFunc(table.m_glColor4s); + QGL_invalidateExtensionFunc(table.m_glColor4sv); + QGL_invalidateExtensionFunc(table.m_glColor4ub); + QGL_invalidateExtensionFunc(table.m_glColor4ubv); + QGL_invalidateExtensionFunc(table.m_glColor4ui); + QGL_invalidateExtensionFunc(table.m_glColor4uiv); + QGL_invalidateExtensionFunc(table.m_glColor4us); + QGL_invalidateExtensionFunc(table.m_glColor4usv); + QGL_invalidateExtensionFunc(table.m_glColorMask); + QGL_invalidateExtensionFunc(table.m_glColorMaterial); + QGL_invalidateExtensionFunc(table.m_glColorPointer); + QGL_invalidateExtensionFunc(table.m_glCopyPixels); + QGL_invalidateExtensionFunc(table.m_glCopyTexImage1D); + QGL_invalidateExtensionFunc(table.m_glCopyTexImage2D); + QGL_invalidateExtensionFunc(table.m_glCopyTexSubImage1D); + QGL_invalidateExtensionFunc(table.m_glCopyTexSubImage2D); + QGL_invalidateExtensionFunc(table.m_glCullFace); + QGL_invalidateExtensionFunc(table.m_glDeleteLists); + QGL_invalidateExtensionFunc(table.m_glDeleteTextures); + QGL_invalidateExtensionFunc(table.m_glDepthFunc); + QGL_invalidateExtensionFunc(table.m_glDepthMask); + QGL_invalidateExtensionFunc(table.m_glDepthRange); + QGL_invalidateExtensionFunc(table.m_glDisable); + QGL_invalidateExtensionFunc(table.m_glDisableClientState); + QGL_invalidateExtensionFunc(table.m_glDrawArrays); + QGL_invalidateExtensionFunc(table.m_glDrawBuffer); + QGL_invalidateExtensionFunc(table.m_glDrawElements); + QGL_invalidateExtensionFunc(table.m_glDrawPixels); + QGL_invalidateExtensionFunc(table.m_glEdgeFlag); + QGL_invalidateExtensionFunc(table.m_glEdgeFlagPointer); + QGL_invalidateExtensionFunc(table.m_glEdgeFlagv); + QGL_invalidateExtensionFunc(table.m_glEnable); + QGL_invalidateExtensionFunc(table.m_glEnableClientState); + QGL_invalidateExtensionFunc(table.m_glEnd); + QGL_invalidateExtensionFunc(table.m_glEndList); + QGL_invalidateExtensionFunc(table.m_glEvalCoord1d); + QGL_invalidateExtensionFunc(table.m_glEvalCoord1dv); + QGL_invalidateExtensionFunc(table.m_glEvalCoord1f); + QGL_invalidateExtensionFunc(table.m_glEvalCoord1fv); + QGL_invalidateExtensionFunc(table.m_glEvalCoord2d); + QGL_invalidateExtensionFunc(table.m_glEvalCoord2dv); + QGL_invalidateExtensionFunc(table.m_glEvalCoord2f); + QGL_invalidateExtensionFunc(table.m_glEvalCoord2fv); + QGL_invalidateExtensionFunc(table.m_glEvalMesh1); + QGL_invalidateExtensionFunc(table.m_glEvalMesh2); + QGL_invalidateExtensionFunc(table.m_glEvalPoint1); + QGL_invalidateExtensionFunc(table.m_glEvalPoint2); + QGL_invalidateExtensionFunc(table.m_glFeedbackBuffer); + QGL_invalidateExtensionFunc(table.m_glFinish); + QGL_invalidateExtensionFunc(table.m_glFlush); + QGL_invalidateExtensionFunc(table.m_glFogf); + QGL_invalidateExtensionFunc(table.m_glFogfv); + QGL_invalidateExtensionFunc(table.m_glFogi); + QGL_invalidateExtensionFunc(table.m_glFogiv); + QGL_invalidateExtensionFunc(table.m_glFrontFace); + QGL_invalidateExtensionFunc(table.m_glFrustum); + QGL_invalidateExtensionFunc(table.m_glGenLists); + QGL_invalidateExtensionFunc(table.m_glGenTextures); + QGL_invalidateExtensionFunc(table.m_glGetBooleanv); + QGL_invalidateExtensionFunc(table.m_glGetClipPlane); + QGL_invalidateExtensionFunc(table.m_glGetDoublev); + QGL_invalidateExtensionFunc(table.m_glGetError); + QGL_invalidateExtensionFunc(table.m_glGetFloatv); + QGL_invalidateExtensionFunc(table.m_glGetIntegerv); + QGL_invalidateExtensionFunc(table.m_glGetLightfv); + QGL_invalidateExtensionFunc(table.m_glGetLightiv); + QGL_invalidateExtensionFunc(table.m_glGetMapdv); + QGL_invalidateExtensionFunc(table.m_glGetMapfv); + QGL_invalidateExtensionFunc(table.m_glGetMapiv); + QGL_invalidateExtensionFunc(table.m_glGetMaterialfv); + QGL_invalidateExtensionFunc(table.m_glGetMaterialiv); + QGL_invalidateExtensionFunc(table.m_glGetPixelMapfv); + QGL_invalidateExtensionFunc(table.m_glGetPixelMapuiv); + QGL_invalidateExtensionFunc(table.m_glGetPixelMapusv); + QGL_invalidateExtensionFunc(table.m_glGetPointerv); + QGL_invalidateExtensionFunc(table.m_glGetPolygonStipple); + table.m_glGetString = glGetString; + QGL_invalidateExtensionFunc(table.m_glGetTexEnvfv); + QGL_invalidateExtensionFunc(table.m_glGetTexEnviv); + QGL_invalidateExtensionFunc(table.m_glGetTexGendv); + QGL_invalidateExtensionFunc(table.m_glGetTexGenfv); + QGL_invalidateExtensionFunc(table.m_glGetTexGeniv); + QGL_invalidateExtensionFunc(table.m_glGetTexImage); + QGL_invalidateExtensionFunc(table.m_glGetTexLevelParameterfv); + QGL_invalidateExtensionFunc(table.m_glGetTexLevelParameteriv); + QGL_invalidateExtensionFunc(table.m_glGetTexParameterfv); + QGL_invalidateExtensionFunc(table.m_glGetTexParameteriv); + QGL_invalidateExtensionFunc(table.m_glHint); + QGL_invalidateExtensionFunc(table.m_glIndexMask); + QGL_invalidateExtensionFunc(table.m_glIndexPointer); + QGL_invalidateExtensionFunc(table.m_glIndexd); + QGL_invalidateExtensionFunc(table.m_glIndexdv); + QGL_invalidateExtensionFunc(table.m_glIndexf); + QGL_invalidateExtensionFunc(table.m_glIndexfv); + QGL_invalidateExtensionFunc(table.m_glIndexi); + QGL_invalidateExtensionFunc(table.m_glIndexiv); + QGL_invalidateExtensionFunc(table.m_glIndexs); + QGL_invalidateExtensionFunc(table.m_glIndexsv); + QGL_invalidateExtensionFunc(table.m_glIndexub); + QGL_invalidateExtensionFunc(table.m_glIndexubv); + QGL_invalidateExtensionFunc(table.m_glInitNames); + QGL_invalidateExtensionFunc(table.m_glInterleavedArrays); + QGL_invalidateExtensionFunc(table.m_glIsEnabled); + QGL_invalidateExtensionFunc(table.m_glIsList); + QGL_invalidateExtensionFunc(table.m_glIsTexture); + QGL_invalidateExtensionFunc(table.m_glLightModelf); + QGL_invalidateExtensionFunc(table.m_glLightModelfv); + QGL_invalidateExtensionFunc(table.m_glLightModeli); + QGL_invalidateExtensionFunc(table.m_glLightModeliv); + QGL_invalidateExtensionFunc(table.m_glLightf); + QGL_invalidateExtensionFunc(table.m_glLightfv); + QGL_invalidateExtensionFunc(table.m_glLighti); + QGL_invalidateExtensionFunc(table.m_glLightiv); + QGL_invalidateExtensionFunc(table.m_glLineStipple); + QGL_invalidateExtensionFunc(table.m_glLineWidth); + QGL_invalidateExtensionFunc(table.m_glListBase); + QGL_invalidateExtensionFunc(table.m_glLoadIdentity); + QGL_invalidateExtensionFunc(table.m_glLoadMatrixd); + QGL_invalidateExtensionFunc(table.m_glLoadMatrixf); + QGL_invalidateExtensionFunc(table.m_glLoadName); + QGL_invalidateExtensionFunc(table.m_glLogicOp); + QGL_invalidateExtensionFunc(table.m_glMap1d); + QGL_invalidateExtensionFunc(table.m_glMap1f); + QGL_invalidateExtensionFunc(table.m_glMap2d); + QGL_invalidateExtensionFunc(table.m_glMap2f); + QGL_invalidateExtensionFunc(table.m_glMapGrid1d); + QGL_invalidateExtensionFunc(table.m_glMapGrid1f); + QGL_invalidateExtensionFunc(table.m_glMapGrid2d); + QGL_invalidateExtensionFunc(table.m_glMapGrid2f); + QGL_invalidateExtensionFunc(table.m_glMaterialf); + QGL_invalidateExtensionFunc(table.m_glMaterialfv); + QGL_invalidateExtensionFunc(table.m_glMateriali); + QGL_invalidateExtensionFunc(table.m_glMaterialiv); + QGL_invalidateExtensionFunc(table.m_glMatrixMode); + QGL_invalidateExtensionFunc(table.m_glMultMatrixd); + QGL_invalidateExtensionFunc(table.m_glMultMatrixf); + QGL_invalidateExtensionFunc(table.m_glNewList); + QGL_invalidateExtensionFunc(table.m_glNormal3b); + QGL_invalidateExtensionFunc(table.m_glNormal3bv); + QGL_invalidateExtensionFunc(table.m_glNormal3d); + QGL_invalidateExtensionFunc(table.m_glNormal3dv); + QGL_invalidateExtensionFunc(table.m_glNormal3f); + QGL_invalidateExtensionFunc(table.m_glNormal3fv); + QGL_invalidateExtensionFunc(table.m_glNormal3i); + QGL_invalidateExtensionFunc(table.m_glNormal3iv); + QGL_invalidateExtensionFunc(table.m_glNormal3s); + QGL_invalidateExtensionFunc(table.m_glNormal3sv); + QGL_invalidateExtensionFunc(table.m_glNormalPointer); + QGL_invalidateExtensionFunc(table.m_glOrtho); + QGL_invalidateExtensionFunc(table.m_glPassThrough); + QGL_invalidateExtensionFunc(table.m_glPixelMapfv); + QGL_invalidateExtensionFunc(table.m_glPixelMapuiv); + QGL_invalidateExtensionFunc(table.m_glPixelMapusv); + QGL_invalidateExtensionFunc(table.m_glPixelStoref); + QGL_invalidateExtensionFunc(table.m_glPixelStorei); + QGL_invalidateExtensionFunc(table.m_glPixelTransferf); + QGL_invalidateExtensionFunc(table.m_glPixelTransferi); + QGL_invalidateExtensionFunc(table.m_glPixelZoom); + QGL_invalidateExtensionFunc(table.m_glPointSize); + QGL_invalidateExtensionFunc(table.m_glPolygonMode); + QGL_invalidateExtensionFunc(table.m_glPolygonOffset); + QGL_invalidateExtensionFunc(table.m_glPolygonStipple); + QGL_invalidateExtensionFunc(table.m_glPopAttrib); + QGL_invalidateExtensionFunc(table.m_glPopClientAttrib); + QGL_invalidateExtensionFunc(table.m_glPopMatrix); + QGL_invalidateExtensionFunc(table.m_glPopName); + QGL_invalidateExtensionFunc(table.m_glPrioritizeTextures); + QGL_invalidateExtensionFunc(table.m_glPushAttrib); + QGL_invalidateExtensionFunc(table.m_glPushClientAttrib); + QGL_invalidateExtensionFunc(table.m_glPushMatrix); + QGL_invalidateExtensionFunc(table.m_glPushName); + QGL_invalidateExtensionFunc(table.m_glRasterPos2d); + QGL_invalidateExtensionFunc(table.m_glRasterPos2dv); + QGL_invalidateExtensionFunc(table.m_glRasterPos2f); + QGL_invalidateExtensionFunc(table.m_glRasterPos2fv); + QGL_invalidateExtensionFunc(table.m_glRasterPos2i); + QGL_invalidateExtensionFunc(table.m_glRasterPos2iv); + QGL_invalidateExtensionFunc(table.m_glRasterPos2s); + QGL_invalidateExtensionFunc(table.m_glRasterPos2sv); + QGL_invalidateExtensionFunc(table.m_glRasterPos3d); + QGL_invalidateExtensionFunc(table.m_glRasterPos3dv); + QGL_invalidateExtensionFunc(table.m_glRasterPos3f); + QGL_invalidateExtensionFunc(table.m_glRasterPos3fv); + QGL_invalidateExtensionFunc(table.m_glRasterPos3i); + QGL_invalidateExtensionFunc(table.m_glRasterPos3iv); + QGL_invalidateExtensionFunc(table.m_glRasterPos3s); + QGL_invalidateExtensionFunc(table.m_glRasterPos3sv); + QGL_invalidateExtensionFunc(table.m_glRasterPos4d); + QGL_invalidateExtensionFunc(table.m_glRasterPos4dv); + QGL_invalidateExtensionFunc(table.m_glRasterPos4f); + QGL_invalidateExtensionFunc(table.m_glRasterPos4fv); + QGL_invalidateExtensionFunc(table.m_glRasterPos4i); + QGL_invalidateExtensionFunc(table.m_glRasterPos4iv); + QGL_invalidateExtensionFunc(table.m_glRasterPos4s); + QGL_invalidateExtensionFunc(table.m_glRasterPos4sv); + QGL_invalidateExtensionFunc(table.m_glReadBuffer); + QGL_invalidateExtensionFunc(table.m_glReadPixels); + QGL_invalidateExtensionFunc(table.m_glRectd); + QGL_invalidateExtensionFunc(table.m_glRectdv); + QGL_invalidateExtensionFunc(table.m_glRectf); + QGL_invalidateExtensionFunc(table.m_glRectfv); + QGL_invalidateExtensionFunc(table.m_glRecti); + QGL_invalidateExtensionFunc(table.m_glRectiv); + QGL_invalidateExtensionFunc(table.m_glRects); + QGL_invalidateExtensionFunc(table.m_glRectsv); + QGL_invalidateExtensionFunc(table.m_glRenderMode); + QGL_invalidateExtensionFunc(table.m_glRotated); + QGL_invalidateExtensionFunc(table.m_glRotatef); + QGL_invalidateExtensionFunc(table.m_glScaled); + QGL_invalidateExtensionFunc(table.m_glScalef); + QGL_invalidateExtensionFunc(table.m_glScissor); + QGL_invalidateExtensionFunc(table.m_glSelectBuffer); + QGL_invalidateExtensionFunc(table.m_glShadeModel); + QGL_invalidateExtensionFunc(table.m_glStencilFunc); + QGL_invalidateExtensionFunc(table.m_glStencilMask); + QGL_invalidateExtensionFunc(table.m_glStencilOp); + QGL_invalidateExtensionFunc(table.m_glTexCoord1d); + QGL_invalidateExtensionFunc(table.m_glTexCoord1dv); + QGL_invalidateExtensionFunc(table.m_glTexCoord1f); + QGL_invalidateExtensionFunc(table.m_glTexCoord1fv); + QGL_invalidateExtensionFunc(table.m_glTexCoord1i); + QGL_invalidateExtensionFunc(table.m_glTexCoord1iv); + QGL_invalidateExtensionFunc(table.m_glTexCoord1s); + QGL_invalidateExtensionFunc(table.m_glTexCoord1sv); + QGL_invalidateExtensionFunc(table.m_glTexCoord2d); + QGL_invalidateExtensionFunc(table.m_glTexCoord2dv); + QGL_invalidateExtensionFunc(table.m_glTexCoord2f); + QGL_invalidateExtensionFunc(table.m_glTexCoord2fv); + QGL_invalidateExtensionFunc(table.m_glTexCoord2i); + QGL_invalidateExtensionFunc(table.m_glTexCoord2iv); + QGL_invalidateExtensionFunc(table.m_glTexCoord2s); + QGL_invalidateExtensionFunc(table.m_glTexCoord2sv); + QGL_invalidateExtensionFunc(table.m_glTexCoord3d); + QGL_invalidateExtensionFunc(table.m_glTexCoord3dv); + QGL_invalidateExtensionFunc(table.m_glTexCoord3f); + QGL_invalidateExtensionFunc(table.m_glTexCoord3fv); + QGL_invalidateExtensionFunc(table.m_glTexCoord3i); + QGL_invalidateExtensionFunc(table.m_glTexCoord3iv); + QGL_invalidateExtensionFunc(table.m_glTexCoord3s); + QGL_invalidateExtensionFunc(table.m_glTexCoord3sv); + QGL_invalidateExtensionFunc(table.m_glTexCoord4d); + QGL_invalidateExtensionFunc(table.m_glTexCoord4dv); + QGL_invalidateExtensionFunc(table.m_glTexCoord4f); + QGL_invalidateExtensionFunc(table.m_glTexCoord4fv); + QGL_invalidateExtensionFunc(table.m_glTexCoord4i); + QGL_invalidateExtensionFunc(table.m_glTexCoord4iv); + QGL_invalidateExtensionFunc(table.m_glTexCoord4s); + QGL_invalidateExtensionFunc(table.m_glTexCoord4sv); + QGL_invalidateExtensionFunc(table.m_glTexCoordPointer); + QGL_invalidateExtensionFunc(table.m_glTexEnvf); + QGL_invalidateExtensionFunc(table.m_glTexEnvfv); + QGL_invalidateExtensionFunc(table.m_glTexEnvi); + QGL_invalidateExtensionFunc(table.m_glTexEnviv); + QGL_invalidateExtensionFunc(table.m_glTexGend); + QGL_invalidateExtensionFunc(table.m_glTexGendv); + QGL_invalidateExtensionFunc(table.m_glTexGenf); + QGL_invalidateExtensionFunc(table.m_glTexGenfv); + QGL_invalidateExtensionFunc(table.m_glTexGeni); + QGL_invalidateExtensionFunc(table.m_glTexGeniv); + QGL_invalidateExtensionFunc(table.m_glTexImage1D); + QGL_invalidateExtensionFunc(table.m_glTexImage2D); + QGL_invalidateExtensionFunc(table.m_glTexParameterf); + QGL_invalidateExtensionFunc(table.m_glTexParameterfv); + QGL_invalidateExtensionFunc(table.m_glTexParameteri); + QGL_invalidateExtensionFunc(table.m_glTexParameteriv); + QGL_invalidateExtensionFunc(table.m_glTexSubImage1D); + QGL_invalidateExtensionFunc(table.m_glTexSubImage2D); + QGL_invalidateExtensionFunc(table.m_glTranslated); + QGL_invalidateExtensionFunc(table.m_glTranslatef); + QGL_invalidateExtensionFunc(table.m_glVertex2d); + QGL_invalidateExtensionFunc(table.m_glVertex2dv); + QGL_invalidateExtensionFunc(table.m_glVertex2f); + QGL_invalidateExtensionFunc(table.m_glVertex2fv); + QGL_invalidateExtensionFunc(table.m_glVertex2i); + QGL_invalidateExtensionFunc(table.m_glVertex2iv); + QGL_invalidateExtensionFunc(table.m_glVertex2s); + QGL_invalidateExtensionFunc(table.m_glVertex2sv); + QGL_invalidateExtensionFunc(table.m_glVertex3d); + QGL_invalidateExtensionFunc(table.m_glVertex3dv); + QGL_invalidateExtensionFunc(table.m_glVertex3f); + QGL_invalidateExtensionFunc(table.m_glVertex3fv); + QGL_invalidateExtensionFunc(table.m_glVertex3i); + QGL_invalidateExtensionFunc(table.m_glVertex3iv); + QGL_invalidateExtensionFunc(table.m_glVertex3s); + QGL_invalidateExtensionFunc(table.m_glVertex3sv); + QGL_invalidateExtensionFunc(table.m_glVertex4d); + QGL_invalidateExtensionFunc(table.m_glVertex4dv); + QGL_invalidateExtensionFunc(table.m_glVertex4f); + QGL_invalidateExtensionFunc(table.m_glVertex4fv); + QGL_invalidateExtensionFunc(table.m_glVertex4i); + QGL_invalidateExtensionFunc(table.m_glVertex4iv); + QGL_invalidateExtensionFunc(table.m_glVertex4s); + QGL_invalidateExtensionFunc(table.m_glVertex4sv); + QGL_invalidateExtensionFunc(table.m_glVertexPointer); + QGL_invalidateExtensionFunc(table.m_glViewport); +} + +int QGL_Init(OpenGLBinding& table) +{ + QGL_clear(table); + +#if defined(WIN32) + qwglCopyContext = wglCopyContext; + qwglCreateContext = wglCreateContext; + qwglCreateLayerContext = wglCreateLayerContext; + qwglDeleteContext = wglDeleteContext; + qwglDescribeLayerPlane = wglDescribeLayerPlane; + qwglGetCurrentContext = wglGetCurrentContext; + qwglGetCurrentDC = wglGetCurrentDC; + qwglGetLayerPaletteEntries = wglGetLayerPaletteEntries; + qwglGetProcAddress = wglGetProcAddress; + qwglMakeCurrent = wglMakeCurrent; + qwglRealizeLayerPalette = wglRealizeLayerPalette; + qwglSetLayerPaletteEntries = wglSetLayerPaletteEntries; + qwglShareLists = wglShareLists; + qwglSwapLayerBuffers = wglSwapLayerBuffers; + qwglUseFontBitmaps = wglUseFontBitmapsA; + qwglUseFontOutlines = wglUseFontOutlinesA; + + qwglChoosePixelFormat = ChoosePixelFormat; + qwglDescribePixelFormat = DescribePixelFormat; + qwglGetPixelFormat = GetPixelFormat; + qwglSetPixelFormat = SetPixelFormat; + qwglSwapBuffers = SwapBuffers; +#elif defined(XWINDOWS) + qglXChooseVisual = glXChooseVisual; + qglXCreateContext = glXCreateContext; + qglXDestroyContext = glXDestroyContext; + qglXMakeCurrent = glXMakeCurrent; + //qglXCopyContext = glXCopyContext; + qglXSwapBuffers = glXSwapBuffers; + qglXCreateGLXPixmap = glXCreateGLXPixmap; + qglXDestroyGLXPixmap = glXDestroyGLXPixmap; + qglXQueryExtension = glXQueryExtension; + qglXQueryVersion = glXQueryVersion; + qglXIsDirect = glXIsDirect; + qglXGetConfig = glXGetConfig; + qglXGetCurrentContext = glXGetCurrentContext; + qglXGetCurrentDrawable = glXGetCurrentDrawable; + qglXWaitGL = glXWaitGL; + qglXWaitX = glXWaitX; + qglXUseXFont = glXUseXFont; +// qglXGetProcAddressARB = glXGetProcAddressARB; // Utah-GLX fix + + qglXGetProcAddressARB = (glXGetProcAddressARBProc)dlsym(RTLD_DEFAULT, "glXGetProcAddressARB"); + if ((qglXQueryExtension == 0) || (qglXQueryExtension(GDK_DISPLAY(),0,0) != True)) + return 0; +#else +#error "unsupported platform" +#endif + + return 1; +} + +int g_qglMajorVersion = 0; +int g_qglMinorVersion = 0; + +// requires a valid gl context +void QGL_InitVersion() +{ +#if EXTENSIONS_ENABLED + const std::size_t versionSize = 256; + char version[versionSize]; + strncpy(version, reinterpret_cast(GlobalOpenGL().m_glGetString(GL_VERSION)), versionSize - 1); + version[versionSize - 1] = '\0'; + char* firstDot = strchr(version, '.'); + ASSERT_NOTNULL(firstDot); + *firstDot = '\0'; + g_qglMajorVersion = atoi(version); + char* secondDot = strchr(firstDot + 1, '.'); + if(secondDot != 0) + { + *secondDot = '\0'; + } + g_qglMinorVersion = atoi(firstDot + 1); +#else + g_qglMajorVersion = 1; + g_qglMinorVersion = 1; +#endif +} + + +inline void extension_not_implemented(const char* extension) +{ + globalErrorStream() << "WARNING: OpenGL driver reports support for " << extension << " but does not implement it\n"; +} + +float g_maxTextureAnisotropy; + +float QGL_maxTextureAnisotropy() +{ + return g_maxTextureAnisotropy; +} + +void QGL_sharedContextCreated(OpenGLBinding& table) +{ + QGL_InitVersion(); + + table.major_version = g_qglMajorVersion; + table.minor_version = g_qglMinorVersion; + + table.m_glAccum = glAccum; + table.m_glAlphaFunc = glAlphaFunc; + table.m_glAreTexturesResident = glAreTexturesResident; + table.m_glArrayElement = glArrayElement; + table.m_glBegin = glBegin; + table.m_glBindTexture = glBindTexture; + table.m_glBitmap = glBitmap; + table.m_glBlendFunc = glBlendFunc; + table.m_glCallList = glCallList; + table.m_glCallLists = glCallLists; + table.m_glClear = glClear; + table.m_glClearAccum = glClearAccum; + table.m_glClearColor = glClearColor; + table.m_glClearDepth = glClearDepth; + table.m_glClearIndex = glClearIndex; + table.m_glClearStencil = glClearStencil; + table.m_glClipPlane = glClipPlane; + table.m_glColor3b = glColor3b; + table.m_glColor3bv = glColor3bv; + table.m_glColor3d = glColor3d; + table.m_glColor3dv = glColor3dv; + table.m_glColor3f = glColor3f; + table.m_glColor3fv = glColor3fv; + table.m_glColor3i = glColor3i; + table.m_glColor3iv = glColor3iv; + table.m_glColor3s = glColor3s; + table.m_glColor3sv = glColor3sv; + table.m_glColor3ub = glColor3ub; + table.m_glColor3ubv = glColor3ubv; + table.m_glColor3ui = glColor3ui; + table.m_glColor3uiv = glColor3uiv; + table.m_glColor3us = glColor3us; + table.m_glColor3usv = glColor3usv; + table.m_glColor4b = glColor4b; + table.m_glColor4bv = glColor4bv; + table.m_glColor4d = glColor4d; + table.m_glColor4dv = glColor4dv; + table.m_glColor4f = glColor4f; + table.m_glColor4fv = glColor4fv; + table.m_glColor4i = glColor4i; + table.m_glColor4iv = glColor4iv; + table.m_glColor4s = glColor4s; + table.m_glColor4sv = glColor4sv; + table.m_glColor4ub = glColor4ub; + table.m_glColor4ubv = glColor4ubv; + table.m_glColor4ui = glColor4ui; + table.m_glColor4uiv = glColor4uiv; + table.m_glColor4us = glColor4us; + table.m_glColor4usv = glColor4usv; + table.m_glColorMask = glColorMask; + table.m_glColorMaterial = glColorMaterial; + table.m_glColorPointer = glColorPointer; + table.m_glCopyPixels = glCopyPixels; + table.m_glCopyTexImage1D = glCopyTexImage1D; + table.m_glCopyTexImage2D = glCopyTexImage2D; + table.m_glCopyTexSubImage1D = glCopyTexSubImage1D; + table.m_glCopyTexSubImage2D = glCopyTexSubImage2D; + table.m_glCullFace = glCullFace; + table.m_glDeleteLists = glDeleteLists; + table.m_glDeleteTextures = glDeleteTextures; + table.m_glDepthFunc = glDepthFunc; + table.m_glDepthMask = glDepthMask; + table.m_glDepthRange = glDepthRange; + table.m_glDisable = glDisable; + table.m_glDisableClientState = glDisableClientState; + table.m_glDrawArrays = glDrawArrays; + table.m_glDrawBuffer = glDrawBuffer; + table.m_glDrawElements = glDrawElements; + table.m_glDrawPixels = glDrawPixels; + table.m_glEdgeFlag = glEdgeFlag; + table.m_glEdgeFlagPointer = glEdgeFlagPointer; + table.m_glEdgeFlagv = glEdgeFlagv; + table.m_glEnable = glEnable; + table.m_glEnableClientState = glEnableClientState; + table.m_glEnd = glEnd; + table.m_glEndList = glEndList; + table.m_glEvalCoord1d = glEvalCoord1d; + table.m_glEvalCoord1dv = glEvalCoord1dv; + table.m_glEvalCoord1f = glEvalCoord1f; + table.m_glEvalCoord1fv = glEvalCoord1fv; + table.m_glEvalCoord2d = glEvalCoord2d; + table.m_glEvalCoord2dv = glEvalCoord2dv; + table.m_glEvalCoord2f = glEvalCoord2f; + table.m_glEvalCoord2fv = glEvalCoord2fv; + table.m_glEvalMesh1 = glEvalMesh1; + table.m_glEvalMesh2 = glEvalMesh2; + table.m_glEvalPoint1 = glEvalPoint1; + table.m_glEvalPoint2 = glEvalPoint2; + table.m_glFeedbackBuffer = glFeedbackBuffer; + table.m_glFinish = glFinish; + table.m_glFlush = glFlush; + table.m_glFogf = glFogf; + table.m_glFogfv = glFogfv; + table.m_glFogi = glFogi; + table.m_glFogiv = glFogiv; + table.m_glFrontFace = glFrontFace; + table.m_glFrustum = glFrustum; + table.m_glGenLists = glGenLists; + table.m_glGenTextures = glGenTextures; + table.m_glGetBooleanv = glGetBooleanv; + table.m_glGetClipPlane = glGetClipPlane; + table.m_glGetDoublev = glGetDoublev; + table.m_glGetError = glGetError; + table.m_glGetFloatv = glGetFloatv; + table.m_glGetIntegerv = glGetIntegerv; + table.m_glGetLightfv = glGetLightfv; + table.m_glGetLightiv = glGetLightiv; + table.m_glGetMapdv = glGetMapdv; + table.m_glGetMapfv = glGetMapfv; + table.m_glGetMapiv = glGetMapiv; + table.m_glGetMaterialfv = glGetMaterialfv; + table.m_glGetMaterialiv = glGetMaterialiv; + table.m_glGetPixelMapfv = glGetPixelMapfv; + table.m_glGetPixelMapuiv = glGetPixelMapuiv; + table.m_glGetPixelMapusv = glGetPixelMapusv; + table.m_glGetPointerv = glGetPointerv; + table.m_glGetPolygonStipple = glGetPolygonStipple; + table.m_glGetString = glGetString; + table.m_glGetTexEnvfv = glGetTexEnvfv; + table.m_glGetTexEnviv = glGetTexEnviv; + table.m_glGetTexGendv = glGetTexGendv; + table.m_glGetTexGenfv = glGetTexGenfv; + table.m_glGetTexGeniv = glGetTexGeniv; + table.m_glGetTexImage = glGetTexImage; + table.m_glGetTexLevelParameterfv = glGetTexLevelParameterfv; + table.m_glGetTexLevelParameteriv = glGetTexLevelParameteriv; + table.m_glGetTexParameterfv = glGetTexParameterfv; + table.m_glGetTexParameteriv = glGetTexParameteriv; + table.m_glHint = glHint; + table.m_glIndexMask = glIndexMask; + table.m_glIndexPointer = glIndexPointer; + table.m_glIndexd = glIndexd; + table.m_glIndexdv = glIndexdv; + table.m_glIndexf = glIndexf; + table.m_glIndexfv = glIndexfv; + table.m_glIndexi = glIndexi; + table.m_glIndexiv = glIndexiv; + table.m_glIndexs = glIndexs; + table.m_glIndexsv = glIndexsv; + table.m_glIndexub = glIndexub; + table.m_glIndexubv = glIndexubv; + table.m_glInitNames = glInitNames; + table.m_glInterleavedArrays = glInterleavedArrays; + table.m_glIsEnabled = glIsEnabled; + table.m_glIsList = glIsList; + table.m_glIsTexture = glIsTexture; + table.m_glLightModelf = glLightModelf; + table.m_glLightModelfv = glLightModelfv; + table.m_glLightModeli = glLightModeli; + table.m_glLightModeliv = glLightModeliv; + table.m_glLightf = glLightf; + table.m_glLightfv = glLightfv; + table.m_glLighti = glLighti; + table.m_glLightiv = glLightiv; + table.m_glLineStipple = glLineStipple; + table.m_glLineWidth = glLineWidth; + table.m_glListBase = glListBase; + table.m_glLoadIdentity = glLoadIdentity; + table.m_glLoadMatrixd = glLoadMatrixd; + table.m_glLoadMatrixf = glLoadMatrixf; + table.m_glLoadName = glLoadName; + table.m_glLogicOp = glLogicOp; + table.m_glMap1d = glMap1d; + table.m_glMap1f = glMap1f; + table.m_glMap2d = glMap2d; + table.m_glMap2f = glMap2f; + table.m_glMapGrid1d = glMapGrid1d; + table.m_glMapGrid1f = glMapGrid1f; + table.m_glMapGrid2d = glMapGrid2d; + table.m_glMapGrid2f = glMapGrid2f; + table.m_glMaterialf = glMaterialf; + table.m_glMaterialfv = glMaterialfv; + table.m_glMateriali = glMateriali; + table.m_glMaterialiv = glMaterialiv; + table.m_glMatrixMode = glMatrixMode; + table.m_glMultMatrixd = glMultMatrixd; + table.m_glMultMatrixf = glMultMatrixf; + table.m_glNewList = glNewList; + table.m_glNormal3b = glNormal3b; + table.m_glNormal3bv = glNormal3bv; + table.m_glNormal3d = glNormal3d; + table.m_glNormal3dv = glNormal3dv; + table.m_glNormal3f = glNormal3f; + table.m_glNormal3fv = glNormal3fv; + table.m_glNormal3i = glNormal3i; + table.m_glNormal3iv = glNormal3iv; + table.m_glNormal3s = glNormal3s; + table.m_glNormal3sv = glNormal3sv; + table.m_glNormalPointer = glNormalPointer; + table.m_glOrtho = glOrtho; + table.m_glPassThrough = glPassThrough; + table.m_glPixelMapfv = glPixelMapfv; + table.m_glPixelMapuiv = glPixelMapuiv; + table.m_glPixelMapusv = glPixelMapusv; + table.m_glPixelStoref = glPixelStoref; + table.m_glPixelStorei = glPixelStorei; + table.m_glPixelTransferf = glPixelTransferf; + table.m_glPixelTransferi = glPixelTransferi; + table.m_glPixelZoom = glPixelZoom; + table.m_glPointSize = glPointSize; + table.m_glPolygonMode = glPolygonMode; + table.m_glPolygonOffset = glPolygonOffset; + table.m_glPolygonStipple = glPolygonStipple; + table.m_glPopAttrib = glPopAttrib; + table.m_glPopClientAttrib = glPopClientAttrib; + table.m_glPopMatrix = glPopMatrix; + table.m_glPopName = glPopName; + table.m_glPrioritizeTextures = glPrioritizeTextures; + table.m_glPushAttrib = glPushAttrib; + table.m_glPushClientAttrib = glPushClientAttrib; + table.m_glPushMatrix = glPushMatrix; + table.m_glPushName = glPushName; + table.m_glRasterPos2d = glRasterPos2d; + table.m_glRasterPos2dv = glRasterPos2dv; + table.m_glRasterPos2f = glRasterPos2f; + table.m_glRasterPos2fv = glRasterPos2fv; + table.m_glRasterPos2i = glRasterPos2i; + table.m_glRasterPos2iv = glRasterPos2iv; + table.m_glRasterPos2s = glRasterPos2s; + table.m_glRasterPos2sv = glRasterPos2sv; + table.m_glRasterPos3d = glRasterPos3d; + table.m_glRasterPos3dv = glRasterPos3dv; + table.m_glRasterPos3f = glRasterPos3f; + table.m_glRasterPos3fv = glRasterPos3fv; + table.m_glRasterPos3i = glRasterPos3i; + table.m_glRasterPos3iv = glRasterPos3iv; + table.m_glRasterPos3s = glRasterPos3s; + table.m_glRasterPos3sv = glRasterPos3sv; + table.m_glRasterPos4d = glRasterPos4d; + table.m_glRasterPos4dv = glRasterPos4dv; + table.m_glRasterPos4f = glRasterPos4f; + table.m_glRasterPos4fv = glRasterPos4fv; + table.m_glRasterPos4i = glRasterPos4i; + table.m_glRasterPos4iv = glRasterPos4iv; + table.m_glRasterPos4s = glRasterPos4s; + table.m_glRasterPos4sv = glRasterPos4sv; + table.m_glReadBuffer = glReadBuffer; + table.m_glReadPixels = glReadPixels; + table.m_glRectd = glRectd; + table.m_glRectdv = glRectdv; + table.m_glRectf = glRectf; + table.m_glRectfv = glRectfv; + table.m_glRecti = glRecti; + table.m_glRectiv = glRectiv; + table.m_glRects = glRects; + table.m_glRectsv = glRectsv; + table.m_glRenderMode = glRenderMode; + table.m_glRotated = glRotated; + table.m_glRotatef = glRotatef; + table.m_glScaled = glScaled; + table.m_glScalef = glScalef; + table.m_glScissor = glScissor; + table.m_glSelectBuffer = glSelectBuffer; + table.m_glShadeModel = glShadeModel; + table.m_glStencilFunc = glStencilFunc; + table.m_glStencilMask = glStencilMask; + table.m_glStencilOp = glStencilOp; + table.m_glTexCoord1d = glTexCoord1d; + table.m_glTexCoord1dv = glTexCoord1dv; + table.m_glTexCoord1f = glTexCoord1f; + table.m_glTexCoord1fv = glTexCoord1fv; + table.m_glTexCoord1i = glTexCoord1i; + table.m_glTexCoord1iv = glTexCoord1iv; + table.m_glTexCoord1s = glTexCoord1s; + table.m_glTexCoord1sv = glTexCoord1sv; + table.m_glTexCoord2d = glTexCoord2d; + table.m_glTexCoord2dv = glTexCoord2dv; + table.m_glTexCoord2f = glTexCoord2f; + table.m_glTexCoord2fv = glTexCoord2fv; + table.m_glTexCoord2i = glTexCoord2i; + table.m_glTexCoord2iv = glTexCoord2iv; + table.m_glTexCoord2s = glTexCoord2s; + table.m_glTexCoord2sv = glTexCoord2sv; + table.m_glTexCoord3d = glTexCoord3d; + table.m_glTexCoord3dv = glTexCoord3dv; + table.m_glTexCoord3f = glTexCoord3f; + table.m_glTexCoord3fv = glTexCoord3fv; + table.m_glTexCoord3i = glTexCoord3i; + table.m_glTexCoord3iv = glTexCoord3iv; + table.m_glTexCoord3s = glTexCoord3s; + table.m_glTexCoord3sv = glTexCoord3sv; + table.m_glTexCoord4d = glTexCoord4d; + table.m_glTexCoord4dv = glTexCoord4dv; + table.m_glTexCoord4f = glTexCoord4f; + table.m_glTexCoord4fv = glTexCoord4fv; + table.m_glTexCoord4i = glTexCoord4i; + table.m_glTexCoord4iv = glTexCoord4iv; + table.m_glTexCoord4s = glTexCoord4s; + table.m_glTexCoord4sv = glTexCoord4sv; + table.m_glTexCoordPointer = glTexCoordPointer; + table.m_glTexEnvf = glTexEnvf; + table.m_glTexEnvfv = glTexEnvfv; + table.m_glTexEnvi = glTexEnvi; + table.m_glTexEnviv = glTexEnviv; + table.m_glTexGend = glTexGend; + table.m_glTexGendv = glTexGendv; + table.m_glTexGenf = glTexGenf; + table.m_glTexGenfv = glTexGenfv; + table.m_glTexGeni = glTexGeni; + table.m_glTexGeniv = glTexGeniv; + table.m_glTexImage1D = glTexImage1D; + table.m_glTexImage2D = glTexImage2D; + table.m_glTexParameterf = glTexParameterf; + table.m_glTexParameterfv = glTexParameterfv; + table.m_glTexParameteri = glTexParameteri; + table.m_glTexParameteriv = glTexParameteriv; + table.m_glTexSubImage1D = glTexSubImage1D; + table.m_glTexSubImage2D = glTexSubImage2D; + table.m_glTranslated = glTranslated; + table.m_glTranslatef = glTranslatef; + table.m_glVertex2d = glVertex2d; + table.m_glVertex2dv = glVertex2dv; + table.m_glVertex2f = glVertex2f; + table.m_glVertex2fv = glVertex2fv; + table.m_glVertex2i = glVertex2i; + table.m_glVertex2iv = glVertex2iv; + table.m_glVertex2s = glVertex2s; + table.m_glVertex2sv = glVertex2sv; + table.m_glVertex3d = glVertex3d; + table.m_glVertex3dv = glVertex3dv; + table.m_glVertex3f = glVertex3f; + table.m_glVertex3fv = glVertex3fv; + table.m_glVertex3i = glVertex3i; + table.m_glVertex3iv = glVertex3iv; + table.m_glVertex3s = glVertex3s; + table.m_glVertex3sv = glVertex3sv; + table.m_glVertex4d = glVertex4d; + table.m_glVertex4dv = glVertex4dv; + table.m_glVertex4f = glVertex4f; + table.m_glVertex4fv = glVertex4fv; + table.m_glVertex4i = glVertex4i; + table.m_glVertex4iv = glVertex4iv; + table.m_glVertex4s = glVertex4s; + table.m_glVertex4sv = glVertex4sv; + table.m_glVertexPointer = glVertexPointer; + table.m_glViewport = glViewport; + + if(QGL_ExtensionSupported("GL_ARB_multitexture")) + { + table.support_ARB_multitexture = + QGL_constructExtensionFunc(table.m_glActiveTextureARB, "glActiveTextureARB") + && QGL_constructExtensionFunc(table.m_glClientActiveTextureARB, "glClientActiveTextureARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1dARB, "glMultiTexCoord1dARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1dvARB, "glMultiTexCoord1dvARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1fARB, "glMultiTexCoord1fARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1fvARB, "glMultiTexCoord1fvARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1iARB, "glMultiTexCoord1iARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1ivARB, "glMultiTexCoord1ivARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1sARB, "glMultiTexCoord1sARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1svARB, "glMultiTexCoord1svARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2dARB, "glMultiTexCoord2dARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2dvARB, "glMultiTexCoord2dvARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2fARB, "glMultiTexCoord2fARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2fvARB, "glMultiTexCoord2fvARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2iARB, "glMultiTexCoord2iARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2ivARB, "glMultiTexCoord2ivARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2sARB, "glMultiTexCoord2sARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2svARB, "glMultiTexCoord2svARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3dARB, "glMultiTexCoord3dARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3dvARB, "glMultiTexCoord3dvARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3fARB, "glMultiTexCoord3fARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3fvARB, "glMultiTexCoord3fvARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3iARB, "glMultiTexCoord3iARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3ivARB, "glMultiTexCoord3ivARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3sARB, "glMultiTexCoord3sARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3svARB, "glMultiTexCoord3svARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4dARB, "glMultiTexCoord4dARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4dvARB, "glMultiTexCoord4dvARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4fARB, "glMultiTexCoord4fARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4fvARB, "glMultiTexCoord4fvARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4iARB, "glMultiTexCoord4iARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4ivARB, "glMultiTexCoord4ivARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4sARB, "glMultiTexCoord4sARB") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4svARB, "glMultiTexCoord4svARB"); + + if(!table.support_ARB_multitexture) + { + extension_not_implemented("GL_ARB_multitexture"); + } + } + else + { + table.support_ARB_multitexture = false; + } + + if(QGL_ExtensionSupported("GL_ARB_texture_compression")) + { + table.support_ARB_texture_compression = + QGL_constructExtensionFunc(table.m_glCompressedTexImage3DARB, "glCompressedTexImage3DARB") + && QGL_constructExtensionFunc(table.m_glCompressedTexImage2DARB, "glCompressedTexImage2DARB") + && QGL_constructExtensionFunc(table.m_glCompressedTexImage1DARB, "glCompressedTexImage1DARB") + && QGL_constructExtensionFunc(table.m_glCompressedTexSubImage3DARB, "glCompressedTexSubImage3DARB") + && QGL_constructExtensionFunc(table.m_glCompressedTexSubImage2DARB, "glCompressedTexSubImage2DARB") + && QGL_constructExtensionFunc(table.m_glCompressedTexSubImage1DARB, "glCompressedTexSubImage1DARB") + && QGL_constructExtensionFunc(table.m_glGetCompressedTexImageARB, "glGetCompressedTexImageARB"); + + if(!table.support_ARB_texture_compression) + { + extension_not_implemented("GL_ARB_texture_compression"); + } + } + else + { + table.support_ARB_texture_compression = false; + } + + table.support_EXT_texture_compression_s3tc = QGL_ExtensionSupported("GL_EXT_texture_compression_s3tc"); + + // GL 1.2 + if(table.major_version > 1 || table.minor_version >= 2) + { + table.support_GL_1_2 = + QGL_constructExtensionFunc(table.m_glCopyTexSubImage3D, "glCopyTexSubImage3D") + && QGL_constructExtensionFunc(table.m_glDrawRangeElements, "glDrawRangeElements") + && QGL_constructExtensionFunc(table.m_glTexImage3D, "glTexImage3D") + && QGL_constructExtensionFunc(table.m_glTexSubImage3D, "glTexSubImage3D"); + + if(!table.support_GL_1_2) + { + extension_not_implemented("GL_VERSION_1_2"); + } + } + else + { + table.support_GL_1_2 = false; + } + + // GL 1.3 + if(table.major_version > 1 || table.minor_version >= 3) + { + table.support_GL_1_3 = + QGL_constructExtensionFunc(table.m_glActiveTexture, "glActiveTexture") + && QGL_constructExtensionFunc(table.m_glClientActiveTexture, "glClientActiveTexture") + && QGL_constructExtensionFunc(table.m_glCompressedTexImage1D, "glCompressedTexImage1D") + && QGL_constructExtensionFunc(table.m_glCompressedTexImage2D, "glCompressedTexImage2D") + && QGL_constructExtensionFunc(table.m_glCompressedTexImage3D, "glCompressedTexImage3D") + && QGL_constructExtensionFunc(table.m_glCompressedTexSubImage1D, "glCompressedTexSubImage1D") + && QGL_constructExtensionFunc(table.m_glCompressedTexSubImage2D, "glCompressedTexSubImage2D") + && QGL_constructExtensionFunc(table.m_glCompressedTexSubImage3D, "glCompressedTexSubImage3D") + && QGL_constructExtensionFunc(table.m_glGetCompressedTexImage, "glGetCompressedTexImage") + && QGL_constructExtensionFunc(table.m_glLoadTransposeMatrixd, "glLoadTransposeMatrixd") + && QGL_constructExtensionFunc(table.m_glLoadTransposeMatrixf, "glLoadTransposeMatrixf") + && QGL_constructExtensionFunc(table.m_glMultTransposeMatrixd, "glMultTransposeMatrixd") + && QGL_constructExtensionFunc(table.m_glMultTransposeMatrixf, "glMultTransposeMatrixf") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1d, "glMultiTexCoord1d") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1dv, "glMultiTexCoord1dv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1f, "glMultiTexCoord1f") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1fv, "glMultiTexCoord1fv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1i, "glMultiTexCoord1i") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1iv, "glMultiTexCoord1iv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1s, "glMultiTexCoord1s") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord1sv, "glMultiTexCoord1sv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2d, "glMultiTexCoord2d") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2dv, "glMultiTexCoord2dv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2f, "glMultiTexCoord2f") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2fv, "glMultiTexCoord2fv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2i, "glMultiTexCoord2i") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2iv, "glMultiTexCoord2iv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2s, "glMultiTexCoord2s") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord2sv, "glMultiTexCoord2sv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3d, "glMultiTexCoord3d") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3dv, "glMultiTexCoord3dv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3f, "glMultiTexCoord3f") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3fv, "glMultiTexCoord3fv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3i, "glMultiTexCoord3i") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3iv, "glMultiTexCoord3iv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3s, "glMultiTexCoord3s") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord3sv, "glMultiTexCoord3sv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4d, "glMultiTexCoord4d") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4dv, "glMultiTexCoord4dv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4f, "glMultiTexCoord4f") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4fv, "glMultiTexCoord4fv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4i, "glMultiTexCoord4i") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4iv, "glMultiTexCoord4iv") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4s, "glMultiTexCoord4s") + && QGL_constructExtensionFunc(table.m_glMultiTexCoord4sv, "glMultiTexCoord4sv") + && QGL_constructExtensionFunc(table.m_glSampleCoverage, "glSampleCoverage"); + + if(!table.support_GL_1_3) + { + extension_not_implemented("GL_VERSION_1_3"); + } + } + else + { + table.support_GL_1_3 = false; + } + + // GL 1.4 + if(table.major_version > 1 || table.minor_version >= 4) + { + table.support_GL_1_4 = + QGL_constructExtensionFunc(table.m_glBlendColor, "glBlendColor") + && QGL_constructExtensionFunc(table.m_glBlendEquation, "glBlendEquation") + && QGL_constructExtensionFunc(table.m_glBlendFuncSeparate, "glBlendFuncSeparate") + && QGL_constructExtensionFunc(table.m_glFogCoordPointer, "glFogCoordPointer") + && QGL_constructExtensionFunc(table.m_glFogCoordd, "glFogCoordd") + && QGL_constructExtensionFunc(table.m_glFogCoorddv, "glFogCoorddv") + && QGL_constructExtensionFunc(table.m_glFogCoordf, "glFogCoordf") + && QGL_constructExtensionFunc(table.m_glFogCoordfv, "glFogCoordfv") + && QGL_constructExtensionFunc(table.m_glMultiDrawArrays, "glMultiDrawArrays") + && QGL_constructExtensionFunc(table.m_glMultiDrawElements, "glMultiDrawElements") + && QGL_constructExtensionFunc(table.m_glPointParameterf, "glPointParameterf") + && QGL_constructExtensionFunc(table.m_glPointParameterfv, "glPointParameterfv") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3b, "glSecondaryColor3b") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3bv, "glSecondaryColor3bv") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3d, "glSecondaryColor3d") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3dv, "glSecondaryColor3dv") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3f, "glSecondaryColor3f") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3fv, "glSecondaryColor3fv") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3i, "glSecondaryColor3i") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3iv, "glSecondaryColor3iv") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3s, "glSecondaryColor3s") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3sv, "glSecondaryColor3sv") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3ub, "glSecondaryColor3ub") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3ubv, "glSecondaryColor3ubv") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3ui, "glSecondaryColor3ui") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3uiv, "glSecondaryColor3uiv") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3us, "glSecondaryColor3us") + && QGL_constructExtensionFunc(table.m_glSecondaryColor3usv, "glSecondaryColor3usv") + && QGL_constructExtensionFunc(table.m_glSecondaryColorPointer, "glSecondaryColorPointer") + && QGL_constructExtensionFunc(table.m_glWindowPos2d, "glWindowPos2d") + && QGL_constructExtensionFunc(table.m_glWindowPos2dv, "glWindowPos2dv") + && QGL_constructExtensionFunc(table.m_glWindowPos2f, "glWindowPos2f") + && QGL_constructExtensionFunc(table.m_glWindowPos2fv, "glWindowPos2fv") + && QGL_constructExtensionFunc(table.m_glWindowPos2i, "glWindowPos2i") + && QGL_constructExtensionFunc(table.m_glWindowPos2iv, "glWindowPos2iv") + && QGL_constructExtensionFunc(table.m_glWindowPos2s, "glWindowPos2s") + && QGL_constructExtensionFunc(table.m_glWindowPos2sv, "glWindowPos2sv") + && QGL_constructExtensionFunc(table.m_glWindowPos3d, "glWindowPos3d") + && QGL_constructExtensionFunc(table.m_glWindowPos3dv, "glWindowPos3dv") + && QGL_constructExtensionFunc(table.m_glWindowPos3f, "glWindowPos3f") + && QGL_constructExtensionFunc(table.m_glWindowPos3fv, "glWindowPos3fv") + && QGL_constructExtensionFunc(table.m_glWindowPos3i, "glWindowPos3i") + && QGL_constructExtensionFunc(table.m_glWindowPos3iv, "glWindowPos3iv") + && QGL_constructExtensionFunc(table.m_glWindowPos3s, "glWindowPos3s") + && QGL_constructExtensionFunc(table.m_glWindowPos3sv, "glWindowPos3sv"); + + if(!table.support_GL_1_4) + { + extension_not_implemented("GL_VERSION_1_4"); + } + } + else + { + table.support_GL_1_4 = false; + } + + // GL 1.5 + if(table.major_version > 1 || table.minor_version >= 5) + { + table.support_GL_1_5 = + QGL_constructExtensionFunc(table.m_glBeginQuery, "glBeginQuery") + && QGL_constructExtensionFunc(table.m_glBindBuffer, "glBindBuffer") + && QGL_constructExtensionFunc(table.m_glBufferData, "glBufferData") + && QGL_constructExtensionFunc(table.m_glBufferSubData, "glBufferSubData") + && QGL_constructExtensionFunc(table.m_glDeleteBuffers, "glDeleteBuffers") + && QGL_constructExtensionFunc(table.m_glDeleteQueries, "glDeleteQueries") + && QGL_constructExtensionFunc(table.m_glEndQuery, "glEndQuery") + && QGL_constructExtensionFunc(table.m_glGenBuffers, "glGenBuffers") + && QGL_constructExtensionFunc(table.m_glGenQueries, "glGenQueries") + && QGL_constructExtensionFunc(table.m_glGetBufferParameteriv, "glGetBufferParameteriv") + && QGL_constructExtensionFunc(table.m_glGetBufferPointerv, "glGetBufferPointerv") + && QGL_constructExtensionFunc(table.m_glGetBufferSubData, "glGetBufferSubData") + && QGL_constructExtensionFunc(table.m_glGetQueryObjectiv, "glGetQueryObjectiv") + && QGL_constructExtensionFunc(table.m_glGetQueryObjectuiv, "glGetQueryObjectuiv") + && QGL_constructExtensionFunc(table.m_glGetQueryiv, "glGetQueryiv") + && QGL_constructExtensionFunc(table.m_glIsBuffer, "glIsBuffer") + && QGL_constructExtensionFunc(table.m_glIsQuery, "glIsQuery") + && QGL_constructExtensionFunc(table.m_glMapBuffer, "glMapBuffer") + && QGL_constructExtensionFunc(table.m_glUnmapBuffer, "glUnmapBuffer"); + + if(!table.support_GL_1_5) + { + extension_not_implemented("GL_VERSION_1_5"); + } + } + else + { + table.support_GL_1_5 = false; + } + + + if(QGL_ExtensionSupported("GL_ARB_vertex_program")) + { + table.support_ARB_vertex_program = + QGL_constructExtensionFunc(table.m_glVertexAttrib1sARB, "glVertexAttrib1sARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib1fARB, "glVertexAttrib1fARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib1dARB, "glVertexAttrib1dARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2sARB, "glVertexAttrib2sARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2fARB, "glVertexAttrib2fARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2dARB, "glVertexAttrib2dARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3sARB, "glVertexAttrib3sARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3fARB, "glVertexAttrib3fARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3dARB, "glVertexAttrib3dARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4sARB, "glVertexAttrib4sARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4fARB, "glVertexAttrib4fARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4dARB, "glVertexAttrib4dARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NubARB, "glVertexAttrib4NubARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib1svARB, "glVertexAttrib1svARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib1fvARB, "glVertexAttrib1fvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib1dvARB, "glVertexAttrib1dvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2svARB, "glVertexAttrib2svARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2fvARB, "glVertexAttrib2fvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2dvARB, "glVertexAttrib2dvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3svARB, "glVertexAttrib3svARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3fvARB, "glVertexAttrib3fvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3dvARB, "glVertexAttrib3dvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4bvARB, "glVertexAttrib4bvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4svARB, "glVertexAttrib4svARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4ivARB, "glVertexAttrib4ivARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4ubvARB, "glVertexAttrib4ubvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4usvARB, "glVertexAttrib4usvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4uivARB, "glVertexAttrib4uivARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4fvARB, "glVertexAttrib4fvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4dvARB, "glVertexAttrib4dvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NbvARB, "glVertexAttrib4NbvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NsvARB, "glVertexAttrib4NsvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NivARB, "glVertexAttrib4NivARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NubvARB, "glVertexAttrib4NubvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NusvARB, "glVertexAttrib4NusvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NuivARB, "glVertexAttrib4NuivARB") + && QGL_constructExtensionFunc(table.m_glVertexAttribPointerARB, "glVertexAttribPointerARB") + && QGL_constructExtensionFunc(table.m_glEnableVertexAttribArrayARB, "glEnableVertexAttribArrayARB") + && QGL_constructExtensionFunc(table.m_glDisableVertexAttribArrayARB, "glDisableVertexAttribArrayARB") + && QGL_constructExtensionFunc(table.m_glProgramStringARB, "glProgramStringARB") + && QGL_constructExtensionFunc(table.m_glBindProgramARB, "glBindProgramARB") + && QGL_constructExtensionFunc(table.m_glDeleteProgramsARB, "glDeleteProgramsARB") + && QGL_constructExtensionFunc(table.m_glGenProgramsARB, "glGenProgramsARB") + && QGL_constructExtensionFunc(table.m_glProgramEnvParameter4dARB, "glProgramEnvParameter4dARB") + && QGL_constructExtensionFunc(table.m_glProgramEnvParameter4dvARB, "glProgramEnvParameter4dvARB") + && QGL_constructExtensionFunc(table.m_glProgramEnvParameter4fARB, "glProgramEnvParameter4fARB") + && QGL_constructExtensionFunc(table.m_glProgramEnvParameter4fvARB, "glProgramEnvParameter4fvARB") + && QGL_constructExtensionFunc(table.m_glProgramLocalParameter4dARB, "glProgramLocalParameter4dARB") + && QGL_constructExtensionFunc(table.m_glProgramLocalParameter4dvARB, "glProgramLocalParameter4dvARB") + && QGL_constructExtensionFunc(table.m_glProgramLocalParameter4fARB, "glProgramLocalParameter4fARB") + && QGL_constructExtensionFunc(table.m_glProgramLocalParameter4fvARB, "glProgramLocalParameter4fvARB") + && QGL_constructExtensionFunc(table.m_glGetProgramEnvParameterdvARB, "glGetProgramEnvParameterdvARB") + && QGL_constructExtensionFunc(table.m_glGetProgramEnvParameterfvARB, "glGetProgramEnvParameterfvARB") + && QGL_constructExtensionFunc(table.m_glGetProgramLocalParameterdvARB, "glGetProgramLocalParameterdvARB") + && QGL_constructExtensionFunc(table.m_glGetProgramLocalParameterfvARB, "glGetProgramLocalParameterfvARB") + && QGL_constructExtensionFunc(table.m_glGetProgramivARB, "glGetProgramivARB") + && QGL_constructExtensionFunc(table.m_glGetProgramStringARB, "glGetProgramStringARB") + && QGL_constructExtensionFunc(table.m_glGetVertexAttribdvARB, "glGetVertexAttribdvARB") + && QGL_constructExtensionFunc(table.m_glGetVertexAttribfvARB, "glGetVertexAttribfvARB") + && QGL_constructExtensionFunc(table.m_glGetVertexAttribivARB, "glGetVertexAttribivARB") + && QGL_constructExtensionFunc(table.m_glGetVertexAttribPointervARB, "glGetVertexAttribPointervARB") + && QGL_constructExtensionFunc(table.m_glIsProgramARB, "glIsProgramARB"); + + if(!table.support_ARB_vertex_program) + { + extension_not_implemented("GL_ARB_vertex_program"); + } + } + else + { + table.support_ARB_vertex_program = false; + } + + + table.support_ARB_fragment_program = QGL_ExtensionSupported("GL_ARB_fragment_program"); + + if(QGL_ExtensionSupported("GL_ARB_shader_objects")) + { + table.support_ARB_shader_objects = + QGL_constructExtensionFunc(table.m_glDeleteObjectARB, "glDeleteObjectARB") + && QGL_constructExtensionFunc(table.m_glGetHandleARB, "glGetHandleARB") + && QGL_constructExtensionFunc(table.m_glDetachObjectARB, "glDetachObjectARB") + && QGL_constructExtensionFunc(table.m_glCreateShaderObjectARB, "glCreateShaderObjectARB") + && QGL_constructExtensionFunc(table.m_glShaderSourceARB, "glShaderSourceARB") + && QGL_constructExtensionFunc(table.m_glCompileShaderARB, "glCompileShaderARB") + && QGL_constructExtensionFunc(table.m_glCreateProgramObjectARB, "glCreateProgramObjectARB") + && QGL_constructExtensionFunc(table.m_glAttachObjectARB, "glAttachObjectARB") + && QGL_constructExtensionFunc(table.m_glLinkProgramARB, "glLinkProgramARB") + && QGL_constructExtensionFunc(table.m_glUseProgramObjectARB, "glUseProgramObjectARB") + && QGL_constructExtensionFunc(table.m_glValidateProgramARB, "glValidateProgramARB") + && QGL_constructExtensionFunc(table.m_glUniform1fARB, "glUniform1fARB") + && QGL_constructExtensionFunc(table.m_glUniform2fARB, "glUniform2fARB") + && QGL_constructExtensionFunc(table.m_glUniform3fARB, "glUniform3fARB") + && QGL_constructExtensionFunc(table.m_glUniform4fARB, "glUniform4fARB") + && QGL_constructExtensionFunc(table.m_glUniform1iARB, "glUniform1iARB") + && QGL_constructExtensionFunc(table.m_glUniform2iARB, "glUniform2iARB") + && QGL_constructExtensionFunc(table.m_glUniform3iARB, "glUniform3iARB") + && QGL_constructExtensionFunc(table.m_glUniform4iARB, "glUniform4iARB") + && QGL_constructExtensionFunc(table.m_glUniform1fvARB, "glUniform1fvARB") + && QGL_constructExtensionFunc(table.m_glUniform2fvARB, "glUniform2fvARB") + && QGL_constructExtensionFunc(table.m_glUniform3fvARB, "glUniform3fvARB") + && QGL_constructExtensionFunc(table.m_glUniform4fvARB, "glUniform4fvARB") + && QGL_constructExtensionFunc(table.m_glUniform1ivARB, "glUniform1ivARB") + && QGL_constructExtensionFunc(table.m_glUniform2ivARB, "glUniform2ivARB") + && QGL_constructExtensionFunc(table.m_glUniform3ivARB, "glUniform3ivARB") + && QGL_constructExtensionFunc(table.m_glUniform4ivARB, "glUniform4ivARB") + && QGL_constructExtensionFunc(table.m_glUniformMatrix2fvARB, "glUniformMatrix2fvARB") + && QGL_constructExtensionFunc(table.m_glUniformMatrix3fvARB, "glUniformMatrix3fvARB") + && QGL_constructExtensionFunc(table.m_glUniformMatrix4fvARB, "glUniformMatrix4fvARB") + && QGL_constructExtensionFunc(table.m_glGetObjectParameterfvARB, "glGetObjectParameterfvARB") + && QGL_constructExtensionFunc(table.m_glGetObjectParameterivARB, "glGetObjectParameterivARB") + && QGL_constructExtensionFunc(table.m_glGetInfoLogARB, "glGetInfoLogARB") + && QGL_constructExtensionFunc(table.m_glGetAttachedObjectsARB, "glGetAttachedObjectsARB") + && QGL_constructExtensionFunc(table.m_glGetUniformLocationARB, "glGetUniformLocationARB") + && QGL_constructExtensionFunc(table.m_glGetActiveUniformARB, "glGetActiveUniformARB") + && QGL_constructExtensionFunc(table.m_glGetUniformfvARB, "glGetUniformfvARB") + && QGL_constructExtensionFunc(table.m_glGetUniformivARB, "glGetUniformivARB") + && QGL_constructExtensionFunc(table.m_glGetShaderSourceARB, "glGetShaderSourceARB"); + + if(!table.support_ARB_shader_objects) + { + extension_not_implemented("GL_ARB_shader_objects"); + } + } + else + { + table.support_ARB_shader_objects = false; + } + + if(QGL_ExtensionSupported("GL_ARB_vertex_shader")) + { + table.support_ARB_vertex_shader = + QGL_constructExtensionFunc(table.m_glVertexAttrib1fARB, "glVertexAttrib1fARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib1sARB, "glVertexAttrib1sARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib1dARB, "glVertexAttrib1dARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2fARB, "glVertexAttrib2fARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2sARB, "glVertexAttrib2sARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2dARB, "glVertexAttrib2dARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3fARB, "glVertexAttrib3fARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3sARB, "glVertexAttrib3sARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3dARB, "glVertexAttrib3dARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4fARB, "glVertexAttrib4fARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4sARB, "glVertexAttrib4sARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4dARB, "glVertexAttrib4dARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NubARB, "glVertexAttrib4NubARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib1fvARB, "glVertexAttrib1fvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib1svARB, "glVertexAttrib1svARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib1dvARB, "glVertexAttrib1dvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2fvARB, "glVertexAttrib2fvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2svARB, "glVertexAttrib2svARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2dvARB, "glVertexAttrib2dvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3fvARB, "glVertexAttrib3fvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3svARB, "glVertexAttrib3svARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3dvARB, "glVertexAttrib3dvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4fvARB, "glVertexAttrib4fvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4svARB, "glVertexAttrib4svARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4dvARB, "glVertexAttrib4dvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4ivARB, "glVertexAttrib4ivARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4bvARB, "glVertexAttrib4bvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4ubvARB, "glVertexAttrib4ubvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4usvARB, "glVertexAttrib4usvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4uivARB, "glVertexAttrib4uivARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NbvARB, "glVertexAttrib4NbvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NsvARB, "glVertexAttrib4NsvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NivARB, "glVertexAttrib4NivARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NubvARB, "glVertexAttrib4NubvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NusvARB, "glVertexAttrib4NusvARB") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4NuivARB, "glVertexAttrib4NuivARB") + && QGL_constructExtensionFunc(table.m_glVertexAttribPointerARB, "glVertexAttribPointerARB") + && QGL_constructExtensionFunc(table.m_glEnableVertexAttribArrayARB, "glEnableVertexAttribArrayARB") + && QGL_constructExtensionFunc(table.m_glDisableVertexAttribArrayARB, "glDisableVertexAttribArrayARB") + && QGL_constructExtensionFunc(table.m_glGetVertexAttribdvARB, "glGetVertexAttribdvARB") + && QGL_constructExtensionFunc(table.m_glGetVertexAttribfvARB, "glGetVertexAttribfvARB") + && QGL_constructExtensionFunc(table.m_glGetVertexAttribivARB, "glGetVertexAttribivARB") + && QGL_constructExtensionFunc(table.m_glGetVertexAttribPointervARB, "glGetVertexAttribPointervARB") + && QGL_constructExtensionFunc(table.m_glBindAttribLocationARB, "glBindAttribLocationARB") + && QGL_constructExtensionFunc(table.m_glGetActiveAttribARB, "glGetActiveAttribARB") + && QGL_constructExtensionFunc(table.m_glGetAttribLocationARB, "glGetAttribLocationARB"); + + if(!table.support_ARB_vertex_shader) + { + extension_not_implemented("GL_ARB_vertex_shader"); + } + } + else + { + table.support_ARB_vertex_shader = false; + } + + if(QGL_ExtensionSupported("GL_NV_vertex_program2")) + { + table.support_NV_vertex_program2 = + QGL_constructExtensionFunc(table.m_glAreProgramsResidentNV, "glAreProgramsResidentNV") + && QGL_constructExtensionFunc(table.m_glBindProgramNV, "glBindProgramNV") + && QGL_constructExtensionFunc(table.m_glDeleteProgramsNV, "glDeleteProgramsNV") + && QGL_constructExtensionFunc(table.m_glExecuteProgramNV, "glExecuteProgramNV") + && QGL_constructExtensionFunc(table.m_glGenProgramsNV, "glGenProgramsNV") + && QGL_constructExtensionFunc(table.m_glGetProgramParameterdvNV, "glGetProgramParameterdvNV") + && QGL_constructExtensionFunc(table.m_glGetProgramParameterfvNV, "glGetProgramParameterfvNV") + && QGL_constructExtensionFunc(table.m_glGetProgramivNV, "glGetProgramivNV") + && QGL_constructExtensionFunc(table.m_glGetProgramStringNV, "glGetProgramStringNV") + && QGL_constructExtensionFunc(table.m_glGetTrackMatrixivNV, "glGetTrackMatrixivNV") + && QGL_constructExtensionFunc(table.m_glGetVertexAttribdvNV, "glGetVertexAttribdvNV") + && QGL_constructExtensionFunc(table.m_glGetVertexAttribfvNV, "glGetVertexAttribfvNV") + && QGL_constructExtensionFunc(table.m_glGetVertexAttribivNV, "glGetVertexAttribivNV") + && QGL_constructExtensionFunc(table.m_glGetVertexAttribPointervNV, "glGetVertexAttribPointervNV") + && QGL_constructExtensionFunc(table.m_glIsProgramNV, "glIsProgramNV") + && QGL_constructExtensionFunc(table.m_glLoadProgramNV, "glLoadProgramNV") + && QGL_constructExtensionFunc(table.m_glProgramParameter4fNV, "glProgramParameter4fNV") + && QGL_constructExtensionFunc(table.m_glProgramParameter4fvNV, "glProgramParameter4fvNV") + && QGL_constructExtensionFunc(table.m_glProgramParameters4fvNV, "glProgramParameters4fvNV") + && QGL_constructExtensionFunc(table.m_glRequestResidentProgramsNV, "glRequestResidentProgramsNV") + && QGL_constructExtensionFunc(table.m_glTrackMatrixNV, "glTrackMatrixNV") + && QGL_constructExtensionFunc(table.m_glVertexAttribPointerNV, "glVertexAttribPointerNV") + && QGL_constructExtensionFunc(table.m_glVertexAttrib1fNV, "glVertexAttrib1fNV") + && QGL_constructExtensionFunc(table.m_glVertexAttrib1fvNV, "glVertexAttrib1fvNV") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2fNV, "glVertexAttrib2fNV") + && QGL_constructExtensionFunc(table.m_glVertexAttrib2fvNV, "glVertexAttrib2fvNV") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3fNV, "glVertexAttrib3fNV") + && QGL_constructExtensionFunc(table.m_glVertexAttrib3fvNV, "glVertexAttrib3fvNV") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4fNV, "glVertexAttrib4fNV") + && QGL_constructExtensionFunc(table.m_glVertexAttrib4fvNV, "glVertexAttrib4fvNV") + && QGL_constructExtensionFunc(table.m_glVertexAttribs1fvNV, "glVertexAttribs1fvNV") + && QGL_constructExtensionFunc(table.m_glVertexAttribs2fvNV, "glVertexAttribs2fvNV") + && QGL_constructExtensionFunc(table.m_glVertexAttribs3fvNV, "glVertexAttribs3fvNV") + && QGL_constructExtensionFunc(table.m_glVertexAttribs4fvNV, "glVertexAttribs4fvNV"); + + if(!table.support_NV_vertex_program2) + { + extension_not_implemented("GL_NV_vertex_program2"); + } + } + else + { + table.support_NV_vertex_program2 = false; + QGL_invalidateExtensionFunc(table.m_glAreProgramsResidentNV); + QGL_invalidateExtensionFunc(table.m_glBindProgramNV); + QGL_invalidateExtensionFunc(table.m_glDeleteProgramsNV); + QGL_invalidateExtensionFunc(table.m_glExecuteProgramNV); + QGL_invalidateExtensionFunc(table.m_glGenProgramsNV); + QGL_invalidateExtensionFunc(table.m_glGetProgramParameterdvNV); + QGL_invalidateExtensionFunc(table.m_glGetProgramParameterfvNV); + QGL_invalidateExtensionFunc(table.m_glGetProgramivNV); + QGL_invalidateExtensionFunc(table.m_glGetProgramStringNV); + QGL_invalidateExtensionFunc(table.m_glGetTrackMatrixivNV); + QGL_invalidateExtensionFunc(table.m_glGetVertexAttribdvNV); + QGL_invalidateExtensionFunc(table.m_glGetVertexAttribfvNV); + QGL_invalidateExtensionFunc(table.m_glGetVertexAttribivNV); + QGL_invalidateExtensionFunc(table.m_glGetVertexAttribPointervNV); + QGL_invalidateExtensionFunc(table.m_glIsProgramNV); + QGL_invalidateExtensionFunc(table.m_glLoadProgramNV); + QGL_invalidateExtensionFunc(table.m_glProgramParameter4fNV); + QGL_invalidateExtensionFunc(table.m_glProgramParameter4fvNV); + QGL_invalidateExtensionFunc(table.m_glProgramParameters4fvNV); + QGL_invalidateExtensionFunc(table.m_glRequestResidentProgramsNV); + QGL_invalidateExtensionFunc(table.m_glTrackMatrixNV); + QGL_invalidateExtensionFunc(table.m_glVertexAttribPointerNV); + QGL_invalidateExtensionFunc(table.m_glVertexAttrib1fNV); + QGL_invalidateExtensionFunc(table.m_glVertexAttrib1fvNV); + QGL_invalidateExtensionFunc(table.m_glVertexAttrib2fNV); + QGL_invalidateExtensionFunc(table.m_glVertexAttrib2fvNV); + QGL_invalidateExtensionFunc(table.m_glVertexAttrib3fNV); + QGL_invalidateExtensionFunc(table.m_glVertexAttrib3fvNV); + QGL_invalidateExtensionFunc(table.m_glVertexAttrib4fNV); + QGL_invalidateExtensionFunc(table.m_glVertexAttrib4fvNV); + QGL_invalidateExtensionFunc(table.m_glVertexAttribs1fvNV); + QGL_invalidateExtensionFunc(table.m_glVertexAttribs2fvNV); + QGL_invalidateExtensionFunc(table.m_glVertexAttribs3fvNV); + QGL_invalidateExtensionFunc(table.m_glVertexAttribs4fvNV); + } + + if(QGL_ExtensionSupported("GL_NV_fragment_program")) + { + table.support_NV_fragment_program = + QGL_constructExtensionFunc(table.m_glProgramNamedParameter4fNV, "glProgramNamedParameter4fNV") + && QGL_constructExtensionFunc(table.m_glProgramNamedParameter4fvNV, "glProgramNamedParameter4fvNV") + && QGL_constructExtensionFunc(table.m_glGetProgramNamedParameterfvNV, "glGetProgramNamedParameterfvNV"); + + if(!table.support_NV_fragment_program) + { + extension_not_implemented("GL_NV_fragment_program"); + } + } + else + { + table.support_NV_fragment_program = false; + } + + table.support_ARB_fragment_shader = QGL_ExtensionSupported("GL_ARB_fragment_shader"); + table.support_ARB_shading_language_100 = QGL_ExtensionSupported("GL_ARB_shading_language_100"); + + if(QGL_ExtensionSupported("GL_EXT_texture_filter_anisotropic")) + { + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &g_maxTextureAnisotropy); + globalOutputStream() << "Anisotropic filtering possible (max " << g_maxTextureAnisotropy << "x)\n"; + } + else + { + globalOutputStream() << "No Anisotropic filtering available\n"; + } +} + +void QGL_sharedContextDestroyed(OpenGLBinding& table) +{ + QGL_clear(table); +} + + +void QGL_assertNoErrors() +{ + GLenum error = GlobalOpenGL().m_glGetError(); + while (error != GL_NO_ERROR) + { + const char* errorString = reinterpret_cast(qgluErrorString(error)); + if (error == GL_OUT_OF_MEMORY) + { + ERROR_MESSAGE("OpenGL out of memory error: " << errorString); + } + else + { + ERROR_MESSAGE("OpenGL error: " << errorString); + } + error = GlobalOpenGL().m_glGetError(); + } +} + + +class QglAPI +{ + OpenGLBinding m_qgl; +public: + typedef OpenGLBinding Type; + STRING_CONSTANT(Name, "*"); + + QglAPI() + { + QGL_Init(m_qgl); + + m_qgl.assertNoErrors = &QGL_assertNoErrors; + } + ~QglAPI() + { + QGL_Shutdown(m_qgl); + } + OpenGLBinding* getTable() + { + return &m_qgl; + } +}; + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +typedef SingletonModule QglModule; +typedef Static StaticQglModule; +StaticRegisterModule staticRegisterQgl(StaticQglModule::instance()); diff --git a/radiant/qgl.h b/radiant/qgl.h new file mode 100644 index 00000000..55d1570e --- /dev/null +++ b/radiant/qgl.h @@ -0,0 +1,33 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_QGL_H) +#define INCLUDED_QGL_H + +struct OpenGLBinding; +void QGL_sharedContextCreated(OpenGLBinding& table); +void QGL_sharedContextDestroyed(OpenGLBinding& table); + +bool QGL_ExtensionSupported(const char* extension); + +float QGL_maxTextureAnisotropy(); + +#endif diff --git a/radiant/radiant.ico b/radiant/radiant.ico new file mode 100644 index 0000000000000000000000000000000000000000..45e8258ee5d5404cd07ffafbb32e08e36b765e3b GIT binary patch literal 2238 zcmb_eX;4#F6#gj8SQr@`$aI(qkZ29y0%8P36eDUyQJw;gTImoiDlSEm)`gBJDBuF( zhDE4~tr%29b}=Hm$SS*RE{F)CV@0CX;EZE>UP7f%9sl%Z&b{y5bIy0a^WF1i0_f0> z&xdAQ_!9Vvo~+6EWV6Y1K%4B)(=M7>hi2Aj@GUGb%E$Hd~;|A;u+>RaFw&GRq zb9gfDNWXX9}+{+WKwbg{3gZ%6)toq&?67QApS@|8h?zW+=t_DrjRk(et5)GB* zs4Fi;O=$_LZWN)ixDXXZ1t>4fM_EB0Hm+NXwX0WQ%_<3eB|c~&+~%5F@ZYc=wN;h) ztM3Jr&;LaC!w#G~eG+NJA?3UrmlMxH9)Ai2ifrT&hukZfxSo}PoXm76(o=CYEd|-B zmvJQ}8CjPvAu~A%^~9x?xD@3m(A(3E=a0MatgDlBoeq~-&bU)wgO-{q;+IeS^045W zd6++U4xV=0M^F1*bhb6)VQUi}G&iEXseyR1HC9lKWvH)UYrKK#o5fUP0qJ=RanVO{ z;^<+xyDh}xg$vNZ=C<5MJLTWIU5~qsb-2?|i#9s_S-ZPK53XLi8c(fv89vj@XZb`@#{qHw+Qsp@<{SN%90VlKy>! z_o)3I>2?trmo6YZ={%CR)15DLosT7?t@|Q3>a||HQudKXI}jfi zgJoVGShHG!?#>Q`1pR`!E+UK>&Btgy4}6{lz8*6gR+a+FUxe}FtYK|s30s?qu%Wsp zQhjz*kBNyfjOqI|G&F>c&cFw*`N}^&ss7&~`V5@B`BMSVIG9euPX@3NGGVb&8_+%& zXJF2cwAKPkWdr*$YXB2~1ZZyTr7n>9XsGVZaf@ zvOA0ho-0#uhx%1~j)DM;<`z9sskU^xRWuLK{D9yi^)E8z2wh_73-FJOT}U_BY7YYa>NWrDpa^e@Li>Ag^e1PgKZ;Gq%ks~ zL?Miztg)sQLyLf_PLRe>wx3Mvc*BAZ4yd>ZVlhW5t!o`STpKpZ!dxio;)>mhLsdsA z^#}8`PqF58rtFQ0E7|F2R(g`d8-AM=sMdbYu8i ztmW{XVeg;w4-5b7tNNWT)E9;H4@dU(|1A$Ln{rGwKg?9MRu5YsA}r M_6)N3WNNMb4eby8hX4Qo literal 0 HcmV?d00001 diff --git a/radiant/radiant.rc b/radiant/radiant.rc new file mode 100644 index 00000000..51087c04 --- /dev/null +++ b/radiant/radiant.rc @@ -0,0 +1,72 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +//#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +//#ifdef _WIN32 +//LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +//#pragma code_page(1252) +//#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_RADIANT ICON DISCARDABLE "radiant.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "//#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/radiant/radiant_old.ico b/radiant/radiant_old.ico new file mode 100644 index 0000000000000000000000000000000000000000..b45ef36ace4af290cc1bf043fb406fee57614ee1 GIT binary patch literal 1078 zcmbtTy>8nu5dNsJgO=0;grb`2EJcLW1dZ0->)7@RI9f;_;~hj*oy-+NNYn zR?w6r`bH9OA0d`%cu`fNk@-;Rx{j+@-0;FV(a3znB;qisG56rGhm{KDhUoucqb6qi zpnaV0?dPg)w*B+;F<6;4ty>c+FfECgB$m8w8)MSi7}1b6!_CuLj#qh|`Z7EQ>hK}d zun!_{2y%1D;O2uRQ~aJ9S{GIq8Ku?Q3I?@okVNdb>|}!^E-Z&Yut?b-;|t47w2-vV zAN+Y9{dXvyzhfle3^>=9o`1e~F7o$x&i#wet^1bPr(I%?mVJ49)t7%R`|`(qU*5%h ztLG;lR_j-bPtRU2mh0Jq*nIBGU(0^(oGZ!B$(&1VQ*xa1ih3m38*8t?(I!`Zp`?oL zfpZ@6#hz>iBMwMh8y*FdH%((;j8U8s@s&6ZaA>|Q-eznV3T?B3%_;!tm4uFU1APhn eo*&>Bte^k9(3>;h;v;bL1h}T{%go2gZ~7OnpTqS4 literal 0 HcmV?d00001 diff --git a/radiant/referencecache.cpp b/radiant/referencecache.cpp new file mode 100644 index 00000000..d41b9344 --- /dev/null +++ b/radiant/referencecache.cpp @@ -0,0 +1,846 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "referencecache.h" + +#include "debugging/debugging.h" + +#include "iscenegraph.h" +#include "iselection.h" +#include "iundo.h" +#include "imap.h" +MapModules& ReferenceAPI_getMapModules(); +#include "imodel.h" +ModelModules& ReferenceAPI_getModelModules(); +#include "ifilesystem.h" +#include "iarchive.h" +#include "ifiletypes.h" +#include "ireference.h" +#include "ientity.h" +#include "qerplugin.h" + +#include + +#include "container/cache.h" +#include "container/hashfunc.h" +#include "os/path.h" +#include "stream/textfilestream.h" +#include "nullmodel.h" +#include "maplib.h" +#include "stream/stringstream.h" +#include "os/file.h" +#include "moduleobserver.h" +#include "moduleobservers.h" + +#include "mainframe.h" +#include "map.h" +#include "filetypes.h" + + +bool References_Saved(); + +void MapChanged() +{ + Map_SetModified(g_map, !References_Saved()); +} + + +EntityCreator* g_entityCreator = 0; + +bool MapResource_loadFile(const MapFormat& format, scene::Node& root, const char* filename) +{ + globalOutputStream() << "Open file " << filename << " for read..."; + TextFileInputStream file(filename); + if(!file.failed()) + { + globalOutputStream() << "success\n"; + ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(filename), "Loading Map"); + ASSERT_NOTNULL(g_entityCreator); + format.readGraph(root, file, *g_entityCreator); + return true; + } + else + { + globalErrorStream() << "failure\n"; + return false; + } +} + +NodeSmartReference MapResource_load(const MapFormat& format, const char* path, const char* name) +{ + NodeSmartReference root(NewMapRoot(name)); + + StringOutputStream fullpath(256); + fullpath << path << name; + + if(path_is_absolute(fullpath.c_str())) + { + MapResource_loadFile(format, root, fullpath.c_str()); + } + else + { + globalErrorStream() << "map path is not fully qualified: " << makeQuoted(fullpath.c_str()) << "\n"; + } + + return root; +} + +bool MapResource_saveFile(const MapFormat& format, scene::Node& root, GraphTraversalFunc traverse, const char* filename) +{ + //ASSERT_MESSAGE(path_is_absolute(filename), "MapResource_saveFile: path is not absolute: " << makeQuoted(filename)); + globalOutputStream() << "Open file " << filename << " for write..."; + TextFileOutputStream file(filename); + if(!file.failed()) + { + globalOutputStream() << "success\n"; + ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(filename), "Saving Map"); + format.writeGraph(root, traverse, file); + return true; + } + + globalErrorStream() << "failure\n"; + return false; +} + +bool file_saveBackup(const char* path) +{ + if(file_writeable(path)) + { + StringOutputStream backup(256); + backup << StringRange(path, path_get_extension(path)) << "bak"; + + return (!file_exists(backup.c_str()) || file_remove(backup.c_str())) // remove backup + && file_move(path, backup.c_str()); // rename current to backup + } + + globalErrorStream() << "map path is not writeable: " << makeQuoted(path) << "\n"; + return false; +} + +bool MapResource_save(const MapFormat& format, scene::Node& root, const char* path, const char* name) +{ + StringOutputStream fullpath(256); + fullpath << path << name; + + if(path_is_absolute(fullpath.c_str())) + { + if(!file_exists(fullpath.c_str()) || file_saveBackup(fullpath.c_str())) + { + return MapResource_saveFile(format, root, Map_Traverse, fullpath.c_str()); + } + + globalErrorStream() << "failed to save a backup map file: " << makeQuoted(fullpath.c_str()) << "\n"; + return false; + } + + globalErrorStream() << "map path is not fully qualified: " << makeQuoted(fullpath.c_str()) << "\n"; + return false; +} + +namespace +{ + NodeSmartReference g_nullNode(NewNullNode()); + NodeSmartReference g_nullModel(g_nullNode); +} + +class NullModelLoader : public ModelLoader +{ +public: + scene::Node& loadModel(ArchiveFile& file) + { + return g_nullModel; + } +}; + +namespace +{ + NullModelLoader g_NullModelLoader; +} + + +/// \brief Returns the model loader for the model \p type or 0 if the model \p type has no loader module +ModelLoader* ModelLoader_forType(const char* type) +{ + const char* moduleName = findModuleName(&GlobalFiletypes(), ModelLoader::Name(), type); + if(string_not_empty(moduleName)) + { + ModelLoader* table = ReferenceAPI_getModelModules().findModule(moduleName); + if(table != 0) + { + return table; + } + else + { + globalErrorStream() << "ERROR: Model type incorrectly registered: \"" << moduleName << "\"\n"; + return &g_NullModelLoader; + } + } + return 0; +} + +NodeSmartReference ModelResource_load(ModelLoader* loader, const char* name) +{ + ScopeDisableScreenUpdates disableScreenUpdates(path_get_filename_start(name), "Loading Model"); + + NodeSmartReference model(g_nullModel); + + { + ArchiveFile* file = GlobalFileSystem().openFile(name); + + if(file != 0) + { + globalOutputStream() << "Loaded Model: \"" << name << "\"\n"; + model = loader->loadModel(*file); + file->release(); + } + else + { + globalErrorStream() << "Model load failed: \"" << name << "\"\n"; + } + } + + model.get().m_isRoot = true; + + return model; +} + + +inline hash_t path_hash(const char* path, hash_t previous = 0) +{ +#if defined(WIN32) + return string_hash_nocase(path, previous); +#else // UNIX + return string_hash(path, previous); +#endif +} + +struct PathEqual +{ + bool operator()(const CopiedString& path, const CopiedString& other) const + { + return path_equal(path.c_str(), other.c_str()); + } +}; + +struct PathHash +{ + typedef hash_t hash_type; + hash_type operator()(const CopiedString& path) const + { + return path_hash(path.c_str()); + } +}; + +typedef std::pair ModelKey; + +struct ModelKeyEqual +{ + bool operator()(const ModelKey& key, const ModelKey& other) const + { + return path_equal(key.first.c_str(), other.first.c_str()) && path_equal(key.second.c_str(), other.second.c_str()); + } +}; + +struct ModelKeyHash +{ + typedef hash_t hash_type; + hash_type operator()(const ModelKey& key) const + { + return hash_combine(path_hash(key.first.c_str()), path_hash(key.second.c_str())); + } +}; + +typedef HashTable ModelCache; +ModelCache g_modelCache; +bool g_modelCache_enabled = true; + +ModelCache::iterator ModelCache_find(const char* path, const char* name) +{ + if(g_modelCache_enabled) + { + return g_modelCache.find(ModelKey(path, name)); + } + return g_modelCache.end(); +} + +ModelCache::iterator ModelCache_insert(const char* path, const char* name, scene::Node& node) +{ + if(g_modelCache_enabled) + { + return g_modelCache.insert(ModelKey(path, name), NodeSmartReference(node)); + } + return g_modelCache.insert(ModelKey("", ""), g_nullModel); +} + +void ModelCache_flush(const char* path, const char* name) +{ + ModelCache::iterator i = g_modelCache.find(ModelKey(path, name)); + if(i != g_modelCache.end()) + { + //ASSERT_MESSAGE((*i).value.getCount() == 0, "resource flushed while still in use: " << (*i).key.first.c_str() << (*i).key.second.c_str()); + g_modelCache.erase(i); + } +} + +void ModelCache_clear() +{ + g_modelCache_enabled = false; + g_modelCache.clear(); + g_modelCache_enabled = true; +} + +NodeSmartReference Model_load(ModelLoader* loader, const char* path, const char* name, const char* type) +{ + if(loader != 0) + { + return ModelResource_load(loader, name); + } + else + { + const char* moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), type); + if(string_not_empty(moduleName)) + { + const MapFormat* format = ReferenceAPI_getMapModules().findModule(moduleName); + if(format != 0) + { + return MapResource_load(*format, path, name); + } + else + { + globalErrorStream() << "ERROR: Map type incorrectly registered: \"" << moduleName << "\"\n"; + return g_nullModel; + } + } + else + { + if(string_not_empty(type)) + { + globalErrorStream() << "Model type not supported: \"" << name << "\"\n"; + } + return g_nullModel; + } + } +} + +namespace +{ + bool g_realised = false; + + // name may be absolute or relative + const char* rootPath(const char* name) + { + return GlobalFileSystem().findRoot( + path_is_absolute(name) + ? name + : GlobalFileSystem().findFile(name) + ); + } +} + +struct ModelResource : public Resource +{ + NodeSmartReference m_model; + const CopiedString m_originalName; + CopiedString m_path; + CopiedString m_name; + CopiedString m_type; + ModelLoader* m_loader; + ModuleObservers m_observers; + std::time_t m_modified; + std::size_t m_unrealised; + + ModelResource(const CopiedString& name) : + m_model(g_nullModel), + m_originalName(name), + m_type(path_get_extension(name.c_str())), + m_loader(0), + m_modified(0), + m_unrealised(1) + { + m_loader = ModelLoader_forType(m_type.c_str()); + + if(g_realised) + { + realise(); + } + } + ~ModelResource() + { + if(realised()) + { + unrealise(); + } + ASSERT_MESSAGE(!realised(), "ModelResource::~ModelResource: resource reference still realised: " << makeQuoted(m_name.c_str())); + } + // NOT COPYABLE + ModelResource(const ModelResource&); + // NOT ASSIGNABLE + ModelResource& operator=(const ModelResource&); + + void setModel(const NodeSmartReference& model) + { + m_model = model; + } + void clearModel() + { + m_model = g_nullModel; + } + + void loadCached() + { + if(g_modelCache_enabled) + { + // cache lookup + ModelCache::iterator i = ModelCache_find(m_path.c_str(), m_name.c_str()); + if(i == g_modelCache.end()) + { + i = ModelCache_insert( + m_path.c_str(), + m_name.c_str(), + Model_load(m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str()) + ); + } + + setModel((*i).value); + } + else + { + setModel(Model_load(m_loader, m_path.c_str(), m_name.c_str(), m_type.c_str())); + } + } + + void loadModel() + { + loadCached(); + connectMap(); + mapSave(); + } + + bool load() + { + ASSERT_MESSAGE(realised(), "resource not realised"); + if(m_model == g_nullModel) + { + loadModel(); + } + + return m_model != g_nullModel; + } + bool save() + { + if(!mapSaved()) + { + const char* moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), m_type.c_str()); + if(string_not_empty(moduleName)) + { + const MapFormat* format = ReferenceAPI_getMapModules().findModule(moduleName); + if(format != 0 && MapResource_save(*format, m_model.get(), m_path.c_str(), m_name.c_str())) + { + mapSave(); + return true; + } + } + } + return false; + } + void flush() + { + if(realised()) + { + ModelCache_flush(m_path.c_str(), m_name.c_str()); + } + } + scene::Node* getNode() + { + //if(m_model != g_nullModel) + { + return m_model.get_pointer(); + } + //return 0; + } + void setNode(scene::Node* node) + { + ModelCache::iterator i = ModelCache_find(m_path.c_str(), m_name.c_str()); + if(i != g_modelCache.end()) + { + (*i).value = NodeSmartReference(*node); + } + setModel(NodeSmartReference(*node)); + + connectMap(); + } + void attach(ModuleObserver& observer) + { + if(realised()) + { + observer.realise(); + } + m_observers.attach(observer); + } + void detach(ModuleObserver& observer) + { + if(realised()) + { + observer.unrealise(); + } + m_observers.detach(observer); + } + bool realised() + { + return m_unrealised == 0; + } + void realise() + { + ASSERT_MESSAGE(m_unrealised != 0, "ModelResource::realise: already realised"); + if(--m_unrealised == 0) + { + m_path = rootPath(m_originalName.c_str()); + m_name = path_make_relative(m_originalName.c_str(), m_path.c_str()); + + //globalOutputStream() << "ModelResource::realise: " << m_path.c_str() << m_name.c_str() << "\n"; + + m_observers.realise(); + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + m_observers.unrealise(); + + //globalOutputStream() << "ModelResource::unrealise: " << m_path.c_str() << m_name.c_str() << "\n"; + clearModel(); + } + } + bool isMap() const + { + return Node_getMapFile(m_model) != 0; + } + void connectMap() + { + MapFile* map = Node_getMapFile(m_model); + if(map != 0) + { + map->setChangedCallback(FreeCaller()); + } + } + std::time_t modified() const + { + StringOutputStream fullpath(256); + fullpath << m_path.c_str() << m_name.c_str(); + return file_modified(fullpath.c_str()); + } + void mapSave() + { + m_modified = modified(); + MapFile* map = Node_getMapFile(m_model); + if(map != 0) + { + map->save(); + } + } + bool mapSaved() const + { + MapFile* map = Node_getMapFile(m_model); + if(map != 0) + { + return m_modified == modified() && map->saved(); + } + return true; + } + bool isModified() const + { + return ((!string_empty(m_path.c_str()) // had or has an absolute path + && m_modified != modified()) // AND disk timestamp changed + || !path_equal(rootPath(m_originalName.c_str()), m_path.c_str())); // OR absolute vfs-root changed + } + void refresh() + { + if(isModified()) + { + flush(); + unrealise(); + realise(); + } + } +}; + +class HashtableReferenceCache : public ReferenceCache, public ModuleObserver +{ + typedef HashedCache ModelReferences; + ModelReferences m_references; + std::size_t m_unrealised; + + class ModelReferencesSnapshot + { + ModelReferences& m_references; + typedef std::list Iterators; + Iterators m_iterators; + public: + typedef Iterators::iterator iterator; + ModelReferencesSnapshot(ModelReferences& references) : m_references(references) + { + for(ModelReferences::iterator i = m_references.begin(); i != m_references.end(); ++i) + { + m_references.capture(i); + m_iterators.push_back(i); + } + } + ~ModelReferencesSnapshot() + { + for(Iterators::iterator i = m_iterators.begin(); i != m_iterators.end(); ++i) + { + m_references.release(*i); + } + } + iterator begin() + { + return m_iterators.begin(); + } + iterator end() + { + return m_iterators.end(); + } + }; + +public: + + typedef ModelReferences::iterator iterator; + + HashtableReferenceCache() : m_unrealised(1) + { + } + + iterator begin() + { + return m_references.begin(); + } + iterator end() + { + return m_references.end(); + } + + void clear() + { + m_references.clear(); + } + + Resource* capture(const char* path) + { + //globalOutputStream() << "capture: \"" << path << "\"\n"; + return m_references.capture(CopiedString(path)).get(); + } + void release(const char* path) + { + m_references.release(CopiedString(path)); + //globalOutputStream() << "release: \"" << path << "\"\n"; + } + + void setEntityCreator(EntityCreator& entityCreator) + { + g_entityCreator = &entityCreator; + } + + bool realised() const + { + return m_unrealised == 0; + } + void realise() + { + ASSERT_MESSAGE(m_unrealised != 0, "HashtableReferenceCache::realise: already realised"); + if(--m_unrealised == 0) + { + g_realised = true; + + { + ModelReferencesSnapshot snapshot(m_references); + for(ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i) + { + ModelReferences::value_type& value = *(*i); + if(value.value.count() != 1) + { + value.value.get()->realise(); + } + } + } + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + g_realised = false; + + { + ModelReferencesSnapshot snapshot(m_references); + for(ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i) + { + ModelReferences::value_type& value = *(*i); + if(value.value.count() != 1) + { + value.value.get()->unrealise(); + } + } + } + + ModelCache_clear(); + } + } + void refresh() + { + ModelReferencesSnapshot snapshot(m_references); + for(ModelReferencesSnapshot::iterator i = snapshot.begin(); i != snapshot.end(); ++i) + { + ModelResource* resource = (*(*i)).value.get(); + if(!resource->isMap()) + { + resource->refresh(); + } + } + } +}; + +namespace +{ + HashtableReferenceCache g_referenceCache; +} + +#if 0 +class ResourceVisitor +{ +public: + virtual void visit(const char* name, const char* path, const +}; +#endif + +void SaveReferences() +{ + ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map"); + for(HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i) + { + (*i).value->save(); + } + MapChanged(); +} + +bool References_Saved() +{ + for(HashtableReferenceCache::iterator i = g_referenceCache.begin(); i != g_referenceCache.end(); ++i) + { + scene::Node* node = (*i).value->getNode(); + if(node != 0) + { + MapFile* map = Node_getMapFile(*node); + if(map != 0 && !map->saved()) + { + return false; + } + } + } + return true; +} + +void RefreshReferences() +{ + ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Refreshing Models"); + g_referenceCache.refresh(); +} + + +void FlushReferences() +{ + ModelCache_clear(); + + g_referenceCache.clear(); +} + +ReferenceCache& GetReferenceCache() +{ + return g_referenceCache; +} + + +#include "modulesystem/modulesmap.h" +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +class ReferenceDependencies : + public GlobalRadiantModuleRef, + public GlobalFileSystemModuleRef, + public GlobalFiletypesModuleRef +{ + ModelModulesRef m_model_modules; + MapModulesRef m_map_modules; +public: + ReferenceDependencies() : + m_model_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("modeltypes")), + m_map_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("maptypes")) + { + } + ModelModules& getModelModules() + { + return m_model_modules.get(); + } + MapModules& getMapModules() + { + return m_map_modules.get(); + } +}; + +class ReferenceAPI : public TypeSystemRef +{ + ReferenceCache* m_reference; +public: + typedef ReferenceCache Type; + STRING_CONSTANT(Name, "*"); + + ReferenceAPI() + { + g_nullModel = NewNullModel(); + + GlobalFileSystem().attach(g_referenceCache); + + m_reference = &GetReferenceCache(); + } + ~ReferenceAPI() + { + GlobalFileSystem().detach(g_referenceCache); + + g_nullModel = g_nullNode; + } + ReferenceCache* getTable() + { + return m_reference; + } +}; + +typedef SingletonModule ReferenceModule; +typedef Static StaticReferenceModule; +StaticRegisterModule staticRegisterReference(StaticReferenceModule::instance()); + +ModelModules& ReferenceAPI_getModelModules() +{ + return StaticReferenceModule::instance().getDependencies().getModelModules(); +} +MapModules& ReferenceAPI_getMapModules() +{ + return StaticReferenceModule::instance().getDependencies().getMapModules(); +} diff --git a/radiant/referencecache.h b/radiant/referencecache.h new file mode 100644 index 00000000..32d121e7 --- /dev/null +++ b/radiant/referencecache.h @@ -0,0 +1,42 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_REFERENCECACHE_H) +#define INCLUDED_REFERENCECACHE_H + +/// \brief Saves all open resource references if they differ from the version on disk. +void SaveReferences(); +/// \brief Flushes the cache of resource references. All resource references must be released before calling this. +void FlushReferences(); +/// \brief Reloads all resource references that differ from the version on disk. +void RefreshReferences(); + +#include "iscenegraph.h" +namespace scene +{ + class Node; +} +class MapFormat; +typedef void(*GraphTraversalFunc)(scene::Node& root, const scene::Traversable::Walker& walker); + +bool MapResource_saveFile(const MapFormat& format, scene::Node& root, GraphTraversalFunc traverse, const char* filename); + +#endif diff --git a/radiant/renderer.cpp b/radiant/renderer.cpp new file mode 100644 index 00000000..386653b9 --- /dev/null +++ b/radiant/renderer.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "renderer.h" + diff --git a/radiant/renderer.h b/radiant/renderer.h new file mode 100644 index 00000000..71865d62 --- /dev/null +++ b/radiant/renderer.h @@ -0,0 +1,199 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_RENDERER_H) +#define INCLUDED_RENDERER_H + +#include "irender.h" +#include "renderable.h" +#include "iselection.h" +#include "cullable.h" +#include "scenelib.h" +#include "math/frustum.h" +#include + +inline Cullable* Instance_getCullable(scene::Instance& instance) +{ + return InstanceTypeCast::cast(instance); +} + +inline Renderable* Instance_getRenderable(scene::Instance& instance) +{ + return InstanceTypeCast::cast(instance); +} + +inline VolumeIntersectionValue Cullable_testVisible(scene::Instance& instance, const VolumeTest& volume, VolumeIntersectionValue parentVisible) +{ + if(parentVisible == c_volumePartial) + { + Cullable* cullable = Instance_getCullable(instance); + if(cullable != 0) + { + return cullable->intersectVolume(volume, instance.localToWorld()); + } + } + return parentVisible; +} + +template +class CullingWalker +{ + const VolumeTest& m_volume; + const _Walker& m_walker; +public: + CullingWalker(const VolumeTest& volume, const _Walker& walker) + : m_volume(volume), m_walker(walker) + { + } + bool pre(const scene::Path& path, scene::Instance& instance, VolumeIntersectionValue parentVisible) const + { + VolumeIntersectionValue visible = Cullable_testVisible(instance, m_volume, parentVisible); + if(visible != c_volumeOutside) + { + return m_walker.pre(path, instance); + } + return true; + } + void post(const scene::Path& path, scene::Instance& instance, VolumeIntersectionValue parentVisible) const + { + return m_walker.post(path, instance); + } +}; + +template +class ForEachVisible : public scene::Graph::Walker +{ + const VolumeTest& m_volume; + const Walker_& m_walker; + mutable std::vector m_state; +public: + ForEachVisible(const VolumeTest& volume, const Walker_& walker) + : m_volume(volume), m_walker(walker) + { + m_state.push_back(c_volumePartial); + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + VolumeIntersectionValue visible = (path.top().get().visible()) ? m_state.back() : c_volumeOutside; + + if(visible == c_volumePartial) + { + visible = m_volume.TestAABB(instance.worldAABB()); + } + + m_state.push_back(visible); + + if(visible == c_volumeOutside) + { + return false; + } + else + { + return m_walker.pre(path, instance, m_state.back()); + } + } + void post(const scene::Path& path, scene::Instance& instance) const + { + if(m_state.back() != c_volumeOutside) + { + m_walker.post(path, instance, m_state.back()); + } + + m_state.pop_back(); + } +}; + +template +inline void Scene_forEachVisible(scene::Graph& graph, const VolumeTest& volume, const Functor& functor) +{ + graph.traverse(ForEachVisible< CullingWalker >(volume, CullingWalker(volume, functor))); +} + +class RenderHighlighted +{ + Renderer& m_renderer; + const VolumeTest& m_volume; +public: + RenderHighlighted(Renderer& renderer, const VolumeTest& volume) + : m_renderer(renderer), m_volume(volume) + { + } + void render(const Renderable& renderable) const + { + switch(m_renderer.getStyle()) + { + case Renderer::eFullMaterials: + renderable.renderSolid(m_renderer, m_volume); + break; + case Renderer::eWireframeOnly: + renderable.renderWireframe(m_renderer, m_volume); + break; + } + } + typedef ConstMemberCaller1 RenderCaller; + + bool pre(const scene::Path& path, scene::Instance& instance, VolumeIntersectionValue parentVisible) const + { + m_renderer.PushState(); + + if(Cullable_testVisible(instance, m_volume, parentVisible) != c_volumeOutside) + { + Renderable* renderable = Instance_getRenderable(instance); + if(renderable) + { + renderable->viewChanged(); + } + + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 && selectable->isSelected()) + { + if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) + { + m_renderer.Highlight(Renderer::eFace); + } + else if(renderable) + { + renderable->renderComponents(m_renderer, m_volume); + } + m_renderer.Highlight(Renderer::ePrimitive); + } + + if(renderable) + { + render(*renderable); + } + } + + return true; + } + void post(const scene::Path& path, scene::Instance& instance, VolumeIntersectionValue parentVisible) const + { + m_renderer.PopState(); + } +}; + +inline void Scene_Render(Renderer& renderer, const VolumeTest& volume) +{ + GlobalSceneGraph().traverse(ForEachVisible(volume, RenderHighlighted(renderer, volume))); + GlobalShaderCache().forEachRenderable(RenderHighlighted::RenderCaller(RenderHighlighted(renderer, volume))); +} + +#endif diff --git a/radiant/renderstate.cpp b/radiant/renderstate.cpp new file mode 100644 index 00000000..2f5596d1 --- /dev/null +++ b/radiant/renderstate.cpp @@ -0,0 +1,2693 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "renderstate.h" + +#include "debugging/debugging.h" +#include "warnings.h" + +#include "ishaders.h" +#include "irender.h" +#include "itextures.h" +#include "igl.h" +#include "iglrender.h" +#include "renderable.h" +#include "qerplugin.h" + +#include +#include +#include +#include + +#include "math/matrix.h" +#include "math/aabb.h" +#include "generic/callback.h" +#include "texturelib.h" +#include "string/string.h" +#include "container/hashfunc.h" +#include "container/cache.h" +#include "generic/reference.h" +#include "moduleobservers.h" +#include "stream/filestream.h" +#include "stream/stringstream.h" +#include "os/file.h" +#include "preferences.h" + +#include "xywindow.h" + + + +#define DEBUG_RENDER 0 + +inline void debug_string(const char* string) +{ +#if(DEBUG_RENDER) + globalOutputStream() << string << "\n"; +#endif +} + +inline void debug_int(const char* comment, int i) +{ +#if(DEBUG_RENDER) + globalOutputStream() << comment << " " << i << "\n"; +#endif +} + +inline void debug_colour(const char* comment) +{ +#if(DEBUG_RENDER) + Vector4 v; + glGetFloatv(GL_CURRENT_COLOR, reinterpret_cast(&v)); + globalOutputStream() << comment << " colour: " + << v[0] << " " + << v[1] << " " + << v[2] << " " + << v[3]; + if(glIsEnabled(GL_COLOR_ARRAY)) + { + globalOutputStream() << " ARRAY"; + } + if(glIsEnabled(GL_COLOR_MATERIAL)) + { + globalOutputStream() << " MATERIAL"; + } + globalOutputStream() << "\n"; +#endif +} + +#include "timer.h" + +StringOutputStream g_renderer_stats; +std::size_t g_count_prims; +std::size_t g_count_states; +std::size_t g_count_transforms; +Timer g_timer; + +inline void count_prim() +{ + ++g_count_prims; +} + +inline void count_state() +{ + ++g_count_states; +} + +inline void count_transform() +{ + ++g_count_transforms; +} + +void Renderer_ResetStats() +{ + g_count_prims = 0; + g_count_states = 0; + g_count_transforms = 0; + g_timer.start(); +} + +const char* Renderer_GetStats() +{ + g_renderer_stats.clear(); + g_renderer_stats << "prims: " << Unsigned(g_count_prims) + << " | states: " << Unsigned(g_count_states) + << " | transforms: " << Unsigned(g_count_transforms) + << " | msec: " << g_timer.elapsed_msec(); + return g_renderer_stats.c_str(); +} + + +void printShaderLog(GLhandleARB object) +{ + GLint log_length = 0; + glGetObjectParameterivARB(object, GL_OBJECT_INFO_LOG_LENGTH_ARB, &log_length); + + Array log(log_length); + glGetInfoLogARB(object, log_length, &log_length, log.data()); + + globalErrorStream() << StringRange(log.begin(), log.begin() + log_length) << "\n"; +} + +void createShader(GLhandleARB program, const char* filename, GLenum type) +{ + GLhandleARB shader = glCreateShaderObjectARB(type); + GlobalOpenGL_debugAssertNoErrors(); + + // load shader + { + std::size_t size = file_size(filename); + FileInputStream file(filename); + ASSERT_MESSAGE(!file.failed(), "failed to open " << makeQuoted(filename)); + Array buffer(size); + size = file.read(reinterpret_cast(buffer.data()), size); + + const GLcharARB* string = buffer.data(); + GLint length = GLint(size); + glShaderSourceARB(shader, 1, &string, &length); + } + + // compile shader + { + glCompileShaderARB(shader); + + GLint compiled = 0; + glGetObjectParameterivARB(shader, GL_OBJECT_COMPILE_STATUS_ARB, &compiled); + + if(!compiled) + { + printShaderLog(shader); + } + + ASSERT_MESSAGE(compiled, "shader compile failed: " << makeQuoted(filename)); + } + + // attach shader + glAttachObjectARB(program, shader); + + glDeleteObjectARB(shader); + + GlobalOpenGL_debugAssertNoErrors(); +} + +void GLSLProgram_link(GLhandleARB program) +{ + glLinkProgramARB(program); + + GLint linked = false; + glGetObjectParameterivARB(program, GL_OBJECT_LINK_STATUS_ARB, &linked); + + if(!linked) + { + printShaderLog(program); + } + + ASSERT_MESSAGE(linked, "program link failed"); +} + +void GLSLProgram_validate(GLhandleARB program) +{ + glValidateProgramARB(program); + + GLint validated = false; + glGetObjectParameterivARB(program, GL_OBJECT_VALIDATE_STATUS_ARB, &validated); + + if(!validated) + { + printShaderLog(program); + } + + ASSERT_MESSAGE(validated, "program validation failed"); +} + +bool g_bumpGLSLPass_enabled = false; +bool g_depthfillPass_enabled = false; + +class GLSLBumpProgram : public GLProgram +{ +public: + GLhandleARB m_program; + qtexture_t* m_light_attenuation_xy; + qtexture_t* m_light_attenuation_z; + GLint u_view_origin; + GLint u_light_origin; + GLint u_light_color; + GLint u_bump_scale; + GLint u_specular_exponent; + + GLSLBumpProgram() : m_program(0), m_light_attenuation_xy(0), m_light_attenuation_z(0) + { + } + + void create() + { + // create program + m_program = glCreateProgramObjectARB(); + + // create shader + { + StringOutputStream filename(256); + filename << GlobalRadiant().getAppPath() << "gl/lighting_DBS_omni_vp.glsl"; + createShader(m_program, filename.c_str(), GL_VERTEX_SHADER_ARB); + filename.clear(); + filename << GlobalRadiant().getAppPath() << "gl/lighting_DBS_omni_fp.glsl"; + createShader(m_program, filename.c_str(), GL_FRAGMENT_SHADER_ARB); + } + + GLSLProgram_link(m_program); + GLSLProgram_validate(m_program); + + glUseProgramObjectARB(m_program); + + glBindAttribLocationARB(m_program, c_attr_TexCoord0, "attr_TexCoord0"); + glBindAttribLocationARB(m_program, c_attr_Tangent, "attr_Tangent"); + glBindAttribLocationARB(m_program, c_attr_Binormal, "attr_Binormal"); + + glUniform1iARB(glGetUniformLocationARB(m_program, "u_diffusemap"), 0); + glUniform1iARB(glGetUniformLocationARB(m_program, "u_bumpmap"), 1); + glUniform1iARB(glGetUniformLocationARB(m_program, "u_specularmap"), 2); + glUniform1iARB(glGetUniformLocationARB(m_program, "u_attenuationmap_xy"), 3); + glUniform1iARB(glGetUniformLocationARB(m_program, "u_attenuationmap_z"), 4); + + u_view_origin = glGetUniformLocationARB(m_program, "u_view_origin"); + u_light_origin = glGetUniformLocationARB(m_program, "u_light_origin"); + u_light_color = glGetUniformLocationARB(m_program, "u_light_color"); + u_bump_scale = glGetUniformLocationARB(m_program, "u_bump_scale"); + u_specular_exponent = glGetUniformLocationARB(m_program, "u_specular_exponent"); + + glUseProgramObjectARB(0); + + GlobalOpenGL_debugAssertNoErrors(); + } + + void destroy() + { + glDeleteObjectARB(m_program); + m_program = 0; + } + + void enable() + { + glUseProgramObjectARB(m_program); + + glEnableVertexAttribArrayARB(c_attr_TexCoord0); + glEnableVertexAttribArrayARB(c_attr_Tangent); + glEnableVertexAttribArrayARB(c_attr_Binormal); + + GlobalOpenGL_debugAssertNoErrors(); + + debug_string("enable bump"); + g_bumpGLSLPass_enabled = true; + } + + void disable() + { + glUseProgramObjectARB(0); + + glDisableVertexAttribArrayARB(c_attr_TexCoord0); + glDisableVertexAttribArrayARB(c_attr_Tangent); + glDisableVertexAttribArrayARB(c_attr_Binormal); + + GlobalOpenGL_debugAssertNoErrors(); + + debug_string("disable bump"); + g_bumpGLSLPass_enabled = false; + } + + void setParameters(const Vector3& viewer, const Matrix4& localToWorld, const Vector3& origin, const Vector3& colour, const Matrix4& world2light) + { + Matrix4 world2local(localToWorld); + matrix4_affine_invert(world2local); + + Vector3 localLight(origin); + matrix4_transform_point(world2local, localLight); + + Vector3 localViewer(viewer); + matrix4_transform_point(world2local, localViewer); + + Matrix4 local2light(world2light); + matrix4_multiply_by_matrix4(local2light, localToWorld); // local->world->light + + glUniform3fARB(u_view_origin, localViewer.x(), localViewer.y(), localViewer.z()); + glUniform3fARB(u_light_origin, localLight.x(), localLight.y(), localLight.z()); + glUniform3fARB(u_light_color, colour.x(), colour.y(), colour.z()); + glUniform1fARB(u_bump_scale, 1.0); + glUniform1fARB(u_specular_exponent, 32.0); + + glActiveTexture(GL_TEXTURE3); + glClientActiveTexture(GL_TEXTURE3); + + glMatrixMode(GL_TEXTURE); + glLoadMatrixf(reinterpret_cast(&local2light)); + glMatrixMode(GL_MODELVIEW); + + GlobalOpenGL_debugAssertNoErrors(); + } +}; + +GLSLBumpProgram g_bumpGLSL; + + +class GLSLDepthFillProgram : public GLProgram +{ +public: + GLhandleARB m_program; + + void create() + { + // create program + m_program = glCreateProgramObjectARB(); + + // create shader + { + StringOutputStream filename(256); + filename << GlobalRadiant().getAppPath() << "gl/zfill_vp.glsl"; + createShader(m_program, filename.c_str(), GL_VERTEX_SHADER_ARB); + filename.clear(); + filename << GlobalRadiant().getAppPath() << "gl/zfill_fp.glsl"; + createShader(m_program, filename.c_str(), GL_FRAGMENT_SHADER_ARB); + } + + GLSLProgram_link(m_program); + GLSLProgram_validate(m_program); + + GlobalOpenGL_debugAssertNoErrors(); + } + + void destroy() + { + glDeleteObjectARB(m_program); + m_program = 0; + } + void enable() + { + glUseProgramObjectARB(m_program); + GlobalOpenGL_debugAssertNoErrors(); + debug_string("enable depthfill"); + g_depthfillPass_enabled = true; + } + void disable() + { + glUseProgramObjectARB(0); + GlobalOpenGL_debugAssertNoErrors(); + debug_string("disable depthfill"); + g_depthfillPass_enabled = false; + } + void setParameters(const Vector3& viewer, const Matrix4& localToWorld, const Vector3& origin, const Vector3& colour, const Matrix4& world2light) + { + } +}; + +GLSLDepthFillProgram g_depthFillGLSL; + + +// ARB path + +void createProgram(const char* filename, GLenum type) +{ + std::size_t size = file_size(filename); + FileInputStream file(filename); + ASSERT_MESSAGE(!file.failed(), "failed to open " << makeQuoted(filename)); + Array buffer(size); + size = file.read(reinterpret_cast(buffer.data()), size); + + glProgramStringARB(type, GL_PROGRAM_FORMAT_ASCII_ARB, GLsizei(size), buffer.data()); + + if(GL_INVALID_OPERATION == glGetError()) + { + GLint errPos; + glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errPos); + const GLubyte* errString = glGetString(GL_PROGRAM_ERROR_STRING_ARB); + + globalErrorStream() << reinterpret_cast(filename) << ":" << errPos << "\n" << reinterpret_cast(errString); + + ERROR_MESSAGE("error in gl program"); + } +} + +class ARBBumpProgram : public GLProgram +{ +public: + GLuint m_vertex_program; + GLuint m_fragment_program; + + void create() + { + glEnable(GL_VERTEX_PROGRAM_ARB); + glEnable(GL_FRAGMENT_PROGRAM_ARB); + + { + glGenProgramsARB(1, &m_vertex_program); + glBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_vertex_program); + StringOutputStream filename(256); + filename << GlobalRadiant().getAppPath() << "gl/lighting_DBS_omni_vp.glp"; + createProgram(filename.c_str(), GL_VERTEX_PROGRAM_ARB); + + glGenProgramsARB(1, &m_fragment_program); + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_fragment_program); + filename.clear(); + filename << GlobalRadiant().getAppPath() << "gl/lighting_DBS_omni_fp.glp"; + createProgram(filename.c_str(), GL_FRAGMENT_PROGRAM_ARB); + } + + glDisable(GL_VERTEX_PROGRAM_ARB); + glDisable(GL_FRAGMENT_PROGRAM_ARB); + + GlobalOpenGL_debugAssertNoErrors(); + } + + void destroy() + { + glDeleteProgramsARB(1, &m_vertex_program); + glDeleteProgramsARB(1, &m_fragment_program); + GlobalOpenGL_debugAssertNoErrors(); + } + + void enable() + { + glEnable(GL_VERTEX_PROGRAM_ARB); + glEnable(GL_FRAGMENT_PROGRAM_ARB); + glBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_vertex_program); + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_fragment_program); + + glEnableVertexAttribArrayARB(8); + glEnableVertexAttribArrayARB(9); + glEnableVertexAttribArrayARB(10); + glEnableVertexAttribArrayARB(11); + + GlobalOpenGL_debugAssertNoErrors(); + } + + void disable() + { + glDisable(GL_VERTEX_PROGRAM_ARB); + glDisable(GL_FRAGMENT_PROGRAM_ARB); + + glDisableVertexAttribArrayARB(8); + glDisableVertexAttribArrayARB(9); + glDisableVertexAttribArrayARB(10); + glDisableVertexAttribArrayARB(11); + + GlobalOpenGL_debugAssertNoErrors(); + } + + void setParameters(const Vector3& viewer, const Matrix4& localToWorld, const Vector3& origin, const Vector3& colour, const Matrix4& world2light) + { + Matrix4 world2local(localToWorld); + matrix4_affine_invert(world2local); + + Vector3 localLight(origin); + matrix4_transform_point(world2local, localLight); + + Vector3 localViewer(viewer); + matrix4_transform_point(world2local, localViewer); + + Matrix4 local2light(world2light); + matrix4_multiply_by_matrix4(local2light, localToWorld); // local->world->light + + // view origin + glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 4, localViewer.x(), localViewer.y(), localViewer.z(), 0); + + // light origin + glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 2, localLight.x(), localLight.y(), localLight.z(), 1); + + // light colour + glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 3, colour.x(), colour.y(), colour.z(), 0); + + // bump scale + glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, 1, 0, 0, 0); + + // specular exponent + glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 5, 32, 0, 0, 0); + + + glActiveTexture(GL_TEXTURE3); + glClientActiveTexture(GL_TEXTURE3); + + glMatrixMode(GL_TEXTURE); + glLoadMatrixf(reinterpret_cast(&local2light)); + glMatrixMode(GL_MODELVIEW); + + GlobalOpenGL_debugAssertNoErrors(); + } +}; + +class ARBDepthFillProgram : public GLProgram +{ +public: + GLuint m_vertex_program; + GLuint m_fragment_program; + + void create() + { + glEnable(GL_VERTEX_PROGRAM_ARB); + glEnable(GL_FRAGMENT_PROGRAM_ARB); + + { + glGenProgramsARB(1, &m_vertex_program); + glBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_vertex_program); + StringOutputStream filename(256); + filename << GlobalRadiant().getAppPath() << "gl/zfill_vp.glp"; + createProgram(filename.c_str(), GL_VERTEX_PROGRAM_ARB); + + glGenProgramsARB(1, &m_fragment_program); + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_fragment_program); + filename.clear(); + filename << GlobalRadiant().getAppPath() << "gl/zfill_fp.glp"; + createProgram(filename.c_str(), GL_FRAGMENT_PROGRAM_ARB); + } + + glDisable(GL_VERTEX_PROGRAM_ARB); + glDisable(GL_FRAGMENT_PROGRAM_ARB); + + GlobalOpenGL_debugAssertNoErrors(); + } + + void destroy() + { + glDeleteProgramsARB(1, &m_vertex_program); + glDeleteProgramsARB(1, &m_fragment_program); + GlobalOpenGL_debugAssertNoErrors(); + } + + void enable() + { + glEnable(GL_VERTEX_PROGRAM_ARB); + glEnable(GL_FRAGMENT_PROGRAM_ARB); + glBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_vertex_program); + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_fragment_program); + + GlobalOpenGL_debugAssertNoErrors(); + } + + void disable() + { + glDisable(GL_VERTEX_PROGRAM_ARB); + glDisable(GL_FRAGMENT_PROGRAM_ARB); + + GlobalOpenGL_debugAssertNoErrors(); + } + + void setParameters(const Vector3& viewer, const Matrix4& localToWorld, const Vector3& origin, const Vector3& colour, const Matrix4& world2light) + { + } +}; + +ARBBumpProgram g_bumpARB; +ARBDepthFillProgram g_depthFillARB; + + +#if 0 +// NV20 path (unfinished) + +void createProgram(GLint program, const char* filename, GLenum type) +{ + std::size_t size = file_size(filename); + FileInputStream file(filename); + ASSERT_MESSAGE(!file.failed(), "failed to open " << makeQuoted(filename)); + Array buffer(size); + size = file.read(reinterpret_cast(buffer.data()), size); + + glLoadProgramNV(type, program, GLsizei(size), buffer.data()); + + if(GL_INVALID_OPERATION == glGetError()) + { + GLint errPos; + glGetIntegerv(GL_PROGRAM_ERROR_POSITION_NV, &errPos); + const GLubyte* errString = glGetString(GL_PROGRAM_ERROR_STRING_NV); + + globalErrorStream() << filename << ":" << errPos << "\n" << errString; + + ERROR_MESSAGE("error in gl program"); + } +} + +GLuint m_vertex_program; +GLuint m_fragment_program; +qtexture_t* g_cube = 0; +qtexture_t* g_specular_lookup = 0; +qtexture_t* g_attenuation_xy = 0; +qtexture_t* g_attenuation_z = 0; + +void createVertexProgram() +{ + { + glGenProgramsNV(1, &m_vertex_program); + glBindProgramNV(GL_VERTEX_PROGRAM_NV, m_vertex_program); + StringOutputStream filename(256); + filename << GlobalRadiant().getAppPath() << "gl/lighting_DBS_omni_vp.nv30"; + createProgram(m_vertex_program, filename.c_str(), GL_VERTEX_PROGRAM_NV); + + glGenProgramsNV(1, &m_fragment_program); + glBindProgramNV(GL_FRAGMENT_PROGRAM_NV, m_fragment_program); + filename.clear(); + filename << GlobalRadiant().getAppPath() << "gl/lighting_DBS_omni_fp.nv30"; + createProgram(m_fragment_program, filename.c_str(), GL_FRAGMENT_PROGRAM_NV); + } + + g_cube = GlobalTexturesCache().capture("generated/cube"); + g_specular_lookup = GlobalTexturesCache().capture("generated/specular"); + + g_attenuation_xy = GlobalTexturesCache().capture("lights/squarelight1"); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, g_attenuation_xy->texture_number); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + + g_attenuation_z = GlobalTexturesCache().capture("lights/squarelight1a"); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, g_attenuation_z->texture_number); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + GlobalOpenGL_debugAssertNoErrors(); +} + +void destroyVertexProgram() +{ + glDeleteProgramsNV(1, &m_vertex_program); + glDeleteProgramsNV(1, &m_fragment_program); + GlobalOpenGL_debugAssertNoErrors(); + + GlobalTexturesCache().release(g_cube); + GlobalTexturesCache().release(g_specular_lookup); + GlobalTexturesCache().release(g_attenuation_xy); + GlobalTexturesCache().release(g_attenuation_z); +} + +bool g_vertexProgram_enabled = false; + +void enableVertexProgram() +{ + //set up the register combiners + //two general combiners + glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, 2); + + //combiner 0 does tex0+tex1 -> spare0 + glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE0_ARB, + GL_UNSIGNED_IDENTITY_NV, GL_RGB); + glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_ZERO, + GL_UNSIGNED_INVERT_NV, GL_RGB); + glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE1_ARB, + GL_UNSIGNED_IDENTITY_NV, GL_RGB); + glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_ZERO, + GL_UNSIGNED_INVERT_NV, GL_RGB); + glCombinerOutputNV(GL_COMBINER0_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE0_NV, + GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE); + + //combiner 1 does tex2 dot tex3 -> spare1 + glCombinerInputNV(GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE2_ARB, + GL_EXPAND_NORMAL_NV, GL_RGB); + glCombinerInputNV(GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV, GL_TEXTURE3_ARB, + GL_EXPAND_NORMAL_NV, GL_RGB); + glCombinerOutputNV(GL_COMBINER1_NV, GL_RGB, GL_SPARE1_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_TRUE, GL_FALSE, GL_FALSE); + + + + //final combiner outputs (1-spare0)*constant color 0*spare1 + //do constant color 0*spare1 in the EF multiplier + glFinalCombinerInputNV(GL_VARIABLE_E_NV, GL_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB); + glFinalCombinerInputNV(GL_VARIABLE_F_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB); + + //now do (1-spare0)*EF + glFinalCombinerInputNV(GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB); + glFinalCombinerInputNV(GL_VARIABLE_B_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB); + glFinalCombinerInputNV(GL_VARIABLE_C_NV, GL_E_TIMES_F_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB); + glFinalCombinerInputNV(GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB); + + glEnable(GL_VERTEX_PROGRAM_NV); + glEnable(GL_REGISTER_COMBINERS_NV); + glBindProgramNV(GL_VERTEX_PROGRAM_NV, m_vertex_program); + glBindProgramNV(GL_FRAGMENT_PROGRAM_NV, m_fragment_program); + + glActiveTexture(GL_TEXTURE0); + glEnable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE1); + glEnable(GL_TEXTURE_1D); + glActiveTexture(GL_TEXTURE2); + glEnable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE3); + glEnable(GL_TEXTURE_2D); + + glEnableClientState(GL_VERTEX_ATTRIB_ARRAY8_NV); + glEnableClientState(GL_VERTEX_ATTRIB_ARRAY9_NV); + glEnableClientState(GL_VERTEX_ATTRIB_ARRAY10_NV); + glEnableClientState(GL_VERTEX_ATTRIB_ARRAY11_NV); + + GlobalOpenGL_debugAssertNoErrors(); + g_vertexProgram_enabled = true; +} + +void disableVertexProgram() +{ + glDisable(GL_VERTEX_PROGRAM_NV); + glDisable(GL_REGISTER_COMBINERS_NV); + + glActiveTexture(GL_TEXTURE0); + glDisable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE1); + glDisable(GL_TEXTURE_1D); + glActiveTexture(GL_TEXTURE2); + glDisable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE3); + glDisable(GL_TEXTURE_2D); + + glDisableClientState(GL_VERTEX_ATTRIB_ARRAY8_NV); + glDisableClientState(GL_VERTEX_ATTRIB_ARRAY9_NV); + glDisableClientState(GL_VERTEX_ATTRIB_ARRAY10_NV); + glDisableClientState(GL_VERTEX_ATTRIB_ARRAY11_NV); + + GlobalOpenGL_debugAssertNoErrors(); + g_vertexProgram_enabled = false; +} + +class GLstringNV +{ +public: + const GLubyte* m_string; + const GLint m_length; + GLstringNV(const char* string) : m_string(reinterpret_cast(string)), m_length(GLint(string_length(string))) + { + } +}; + +GLstringNV g_light_origin("light_origin"); +GLstringNV g_view_origin("view_origin"); +GLstringNV g_light_color("light_color"); +GLstringNV g_bumpGLSL_scale("bump_scale"); +GLstringNV g_specular_exponent("specular_exponent"); + +void setVertexProgramEnvironment(const Vector3& localViewer) +{ + Matrix4 local2light(g_matrix4_identity); + matrix4_translate_by_vec3(local2light, Vector3(0.5, 0.5, 0.5)); + matrix4_scale_by_vec3(local2light, Vector3(0.5, 0.5, 0.5)); + matrix4_scale_by_vec3(local2light, Vector3(1.0 / 512.0, 1.0 / 512.0, 1.0 / 512.0)); + matrix4_translate_by_vec3(local2light, vector3_negated(localViewer)); + + glActiveTexture(GL_TEXTURE3); + glClientActiveTexture(GL_TEXTURE3); + + glMatrixMode(GL_TEXTURE); + glLoadMatrixf(reinterpret_cast(&local2light)); + glMatrixMode(GL_MODELVIEW); + + glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 0, GL_MODELVIEW_PROJECTION_NV, GL_IDENTITY_NV); + glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 4, GL_TEXTURE0_ARB, GL_IDENTITY_NV); + + // view origin + //qglProgramNamedParameter4fNV(m_fragment_program, g_view_origin.m_length, g_view_origin.m_string, localViewer.x(), localViewer.y(), localViewer.z(), 0); + + // light origin + glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 8, localViewer.x(), localViewer.y(), localViewer.z(), 1.0f); + + // light colour + glCombinerParameterfNV(GL_CONSTANT_COLOR0_NV, 1, 1, 1, 1) + + // bump scale + //qglProgramNamedParameter4fNV(m_fragment_program, g_bumpGLSL_scale.m_length, g_bumpGLSL_scale.m_string, 1, 0, 0, 0); + + // specular exponent + //qglProgramNamedParameter4fNV(m_fragment_program, g_specular_exponent.m_length, g_specular_exponent.m_string, 32, 0, 0, 0); + + GlobalOpenGL_debugAssertNoErrors(); +} + +#endif + + +bool g_vertexArray_enabled = false; +bool g_normalArray_enabled = false; +bool g_texcoordArray_enabled = false; +bool g_colorArray_enabled = false; + +inline bool OpenGLState_less(const OpenGLState& self, const OpenGLState& other) +{ + //! Sort by sort-order override. + if(self.m_sort != other.m_sort) + { + return self.m_sort < other.m_sort; + } + //! Sort by texture handle. + if(self.m_texture != other.m_texture) + { + return self.m_texture < other.m_texture; + } + if(self.m_texture1 != other.m_texture1) + { + return self.m_texture1 < other.m_texture1; + } + if(self.m_texture2 != other.m_texture2) + { + return self.m_texture2 < other.m_texture2; + } + if(self.m_texture3 != other.m_texture3) + { + return self.m_texture3 < other.m_texture3; + } + if(self.m_texture4 != other.m_texture4) + { + return self.m_texture4 < other.m_texture4; + } + if(self.m_texture5 != other.m_texture5) + { + return self.m_texture5 < other.m_texture5; + } + if(self.m_texture6 != other.m_texture6) + { + return self.m_texture6 < other.m_texture6; + } + if(self.m_texture7 != other.m_texture7) + { + return self.m_texture7 < other.m_texture7; + } + //! Sort by state bit-vector. + if(self.m_state != other.m_state) + { + return self.m_state < other.m_state; + } + //! Comparing address makes sure states are never equal. + return &self < &other; +} + +void OpenGLState_constructDefault(OpenGLState& state) +{ + state.m_state = RENDER_DEFAULT; + + state.m_texture = 0; + state.m_texture1 = 0; + state.m_texture2 = 0; + state.m_texture3 = 0; + state.m_texture4 = 0; + state.m_texture5 = 0; + state.m_texture6 = 0; + state.m_texture7 = 0; + + state.m_colour[0] = 1; + state.m_colour[1] = 1; + state.m_colour[2] = 1; + state.m_colour[3] = 1; + + state.m_depthfunc = GL_LESS; + + state.m_blend_src = GL_SRC_ALPHA; + state.m_blend_dst = GL_ONE_MINUS_SRC_ALPHA; + + state.m_alphafunc = GL_ALWAYS; + state.m_alpharef = 0; + + state.m_linewidth = 1; + state.m_pointsize = 1; + + state.m_linestipple_factor = 1; + state.m_linestipple_pattern = 0xaaaa; + + state.m_fog = OpenGLFogState(); +} + + + + +/// \brief A container of Renderable references. +/// May contain the same Renderable multiple times, with different transforms. +class OpenGLStateBucket +{ +public: + struct RenderTransform + { + const Matrix4* m_transform; + const OpenGLRenderable *m_renderable; + const RendererLight* m_light; + + RenderTransform(const OpenGLRenderable& renderable, const Matrix4& transform, const RendererLight* light) + : m_transform(&transform), m_renderable(&renderable), m_light(light) + { + } + }; + + typedef std::vector Renderables; + +private: + + OpenGLState m_state; + Renderables m_renderables; + +public: + OpenGLStateBucket() + { + } + void addRenderable(const OpenGLRenderable& renderable, const Matrix4& modelview, const RendererLight* light = 0) + { + m_renderables.push_back(RenderTransform(renderable, modelview, light)); + } + + OpenGLState& state() + { + return m_state; + } + + void render(OpenGLState& current, unsigned int globalstate, const Vector3& viewer); +}; + +#define LIGHT_SHADER_DEBUG 0 + +#if LIGHT_SHADER_DEBUG +typedef std::vector LightDebugShaders; +LightDebugShaders g_lightDebugShaders; +#endif + +class OpenGLStateLess +{ +public: + bool operator()(const OpenGLState& self, const OpenGLState& other) const + { + return OpenGLState_less(self, other); + } +}; + +typedef ConstReference OpenGLStateReference; +typedef std::map OpenGLStates; +OpenGLStates g_state_sorted; + +class OpenGLStateBucketAdd +{ + OpenGLStateBucket& m_bucket; + const OpenGLRenderable& m_renderable; + const Matrix4& m_modelview; +public: + typedef const RendererLight& first_argument_type; + + OpenGLStateBucketAdd(OpenGLStateBucket& bucket, const OpenGLRenderable& renderable, const Matrix4& modelview) : + m_bucket(bucket), m_renderable(renderable), m_modelview(modelview) + { + } + void operator()(const RendererLight& light) + { + m_bucket.addRenderable(m_renderable, m_modelview, &light); + } +}; + +class CountLights +{ + std::size_t m_count; +public: + typedef RendererLight& first_argument_type; + + CountLights() : m_count(0) + { + } + void operator()(const RendererLight& light) + { + ++m_count; + } + std::size_t count() const + { + return m_count; + } +}; + +class OpenGLShader : public Shader +{ + typedef std::list Passes; + Passes m_passes; + IShader* m_shader; + std::size_t m_used; + ModuleObservers m_observers; +public: + OpenGLShader() : m_shader(0), m_used(0) + { + } + ~OpenGLShader() + { + } + void construct(const char* name); + void destroy() + { + if(m_shader) + { + m_shader->DecRef(); + } + m_shader = 0; + + for(Passes::iterator i = m_passes.begin(); i != m_passes.end(); ++i) + { + delete *i; + } + m_passes.clear(); + } + void addRenderable(const OpenGLRenderable& renderable, const Matrix4& modelview, const LightList* lights) + { + for(Passes::iterator i = m_passes.begin(); i != m_passes.end(); ++i) + { +#if LIGHT_SHADER_DEBUG + if(((*i)->state().m_state & RENDER_BUMP) != 0) + { + if(lights != 0) + { + CountLights counter; + lights->forEachLight(makeCallback1(counter)); + globalOutputStream() << "count = " << counter.count() << "\n"; + for(std::size_t i = 0; i < counter.count(); ++i) + { + g_lightDebugShaders[counter.count()]->addRenderable(renderable, modelview); + } + } + } + else +#else + if(((*i)->state().m_state & RENDER_BUMP) != 0) + { + if(lights != 0) + { + OpenGLStateBucketAdd add(*(*i), renderable, modelview); + lights->forEachLight(makeCallback1(add)); + } + } + else +#endif + { + (*i)->addRenderable(renderable, modelview); + } + } + } + void incrementUsed() + { + if(++m_used == 1 && m_shader != 0) + { + m_shader->SetInUse(true); + } + } + void decrementUsed() + { + if(--m_used == 0 && m_shader != 0) + { + m_shader->SetInUse(false); + } + } + bool realised() const + { + return m_shader != 0; + } + void attach(ModuleObserver& observer) + { + if(realised()) + { + observer.realise(); + } + m_observers.attach(observer); + } + void detach(ModuleObserver& observer) + { + if(realised()) + { + observer.unrealise(); + } + m_observers.detach(observer); + } + void realise(const CopiedString& name) + { + construct(name.c_str()); + + if(m_used != 0 && m_shader != 0) + { + m_shader->SetInUse(true); + } + + for(Passes::iterator i = m_passes.begin(); i != m_passes.end(); ++i) + { + g_state_sorted.insert(OpenGLStates::value_type(OpenGLStateReference((*i)->state()), *i)); + } + + m_observers.realise(); + } + void unrealise() + { + m_observers.unrealise(); + + for(Passes::iterator i = m_passes.begin(); i != m_passes.end(); ++i) + { + g_state_sorted.erase(OpenGLStateReference((*i)->state())); + } + + destroy(); + } + qtexture_t& getTexture() const + { + ASSERT_NOTNULL(m_shader); + return *m_shader->getTexture(); + } + unsigned int getFlags() const + { + ASSERT_NOTNULL(m_shader); + return m_shader->getFlags(); + } + IShader& getShader() const + { + ASSERT_NOTNULL(m_shader); + return *m_shader; + } + OpenGLState& appendDefaultPass() + { + m_passes.push_back(new OpenGLStateBucket); + OpenGLState& state = m_passes.back()->state(); + OpenGLState_constructDefault(state); + return state; + } +}; + + +inline bool lightEnabled(const RendererLight& light, const LightCullable& cullable) +{ + return cullable.testLight(light); +} + +typedef std::set RendererLights; + +#define DEBUG_LIGHT_SYNC 0 + +class LinearLightList : public LightList +{ + LightCullable& m_cullable; + RendererLights& m_allLights; + Callback m_evaluateChanged; + + typedef std::list Lights; + mutable Lights m_lights; + mutable bool m_lightsChanged; +public: + LinearLightList(LightCullable& cullable, RendererLights& lights, const Callback& evaluateChanged) : + m_cullable(cullable), m_allLights(lights), m_evaluateChanged(evaluateChanged) + { + m_lightsChanged = true; + } + void evaluateLights() const + { + m_evaluateChanged(); + if(m_lightsChanged) + { + m_lightsChanged = false; + + m_lights.clear(); + m_cullable.clearLights(); + for(RendererLights::const_iterator i = m_allLights.begin(); i != m_allLights.end(); ++i) + { + if(lightEnabled(*(*i), m_cullable)) + { + m_lights.push_back(*i); + m_cullable.insertLight(*(*i)); + } + } + } +#if(DEBUG_LIGHT_SYNC) + else + { + Lights lights; + for(RendererLights::const_iterator i = m_allLights.begin(); i != m_allLights.end(); ++i) + { + if(lightEnabled(*(*i), m_cullable)) + { + lights.push_back(*i); + } + } + ASSERT_MESSAGE( + !std::lexicographical_compare(lights.begin(), lights.end(), m_lights.begin(), m_lights.end()) + && !std::lexicographical_compare(m_lights.begin(), m_lights.end(), lights.begin(), lights.end()), + "lights out of sync" + ); + } +#endif + } + void forEachLight(const RendererLightCallback& callback) const + { + evaluateLights(); + + for(Lights::const_iterator i = m_lights.begin(); i != m_lights.end(); ++i) + { + callback(*(*i)); + } + } + void lightsChanged() const + { + m_lightsChanged = true; + } +}; + +inline void setFogState(const OpenGLFogState& state) +{ + glFogi(GL_FOG_MODE, state.mode); + glFogf(GL_FOG_DENSITY, state.density); + glFogf(GL_FOG_START, state.start); + glFogf(GL_FOG_END, state.end); + glFogi(GL_FOG_INDEX, state.index); + glFogfv(GL_FOG_COLOR, vector4_to_array(state.colour)); +} + +#define DEBUG_SHADERS 0 + +class OpenGLShaderCache : public ShaderCache, public TexturesCacheObserver, public ModuleObserver +{ + class CreateOpenGLShader + { + OpenGLShaderCache* m_cache; + public: + explicit CreateOpenGLShader(OpenGLShaderCache* cache = 0) + : m_cache(cache) + { + } + OpenGLShader* construct(const CopiedString& name) + { + OpenGLShader* shader = new OpenGLShader; + if(m_cache->realised()) + { + shader->realise(name); + } + return shader; + } + void destroy(OpenGLShader* shader) + { + if(m_cache->realised()) + { + shader->unrealise(); + } + delete shader; + } + }; + + typedef HashedCache, CreateOpenGLShader> Shaders; + Shaders m_shaders; + std::size_t m_unrealised; + + bool m_lightingEnabled; + bool m_lightingSupported; + bool m_useShaderLanguage; + +public: + OpenGLShaderCache() + : m_shaders(CreateOpenGLShader(this)), + m_unrealised(3), // wait until shaders, gl-context and textures are realised before creating any render-states + m_lightingEnabled(true), + m_lightingSupported(false), + m_useShaderLanguage(false), + m_lightsChanged(true), + m_traverseRenderablesMutex(false) + { + } + ~OpenGLShaderCache() + { + for(Shaders::iterator i = m_shaders.begin(); i != m_shaders.end(); ++i) + { + globalOutputStream() << "leaked shader: " << makeQuoted((*i).key.c_str()) << "\n"; + } + } + Shader* capture(const char* name) + { + ASSERT_MESSAGE(name[0] == '$' + || *name == '[' + || *name == '<' + || *name == '(' + || strchr(name, '\\') == 0, "shader name contains invalid characters: \"" << name << "\""); +#if DEBUG_SHADERS + globalOutputStream() << "shaders capture: " << makeQuoted(name) << '\n'; +#endif + return m_shaders.capture(name).get(); + } + void release(const char *name) + { +#if DEBUG_SHADERS + globalOutputStream() << "shaders release: " << makeQuoted(name) << '\n'; +#endif + m_shaders.release(name); + } + void render(RenderStateFlags globalstate, const Matrix4& modelview, const Matrix4& projection, const Vector3& viewer) + { + glMatrixMode(GL_PROJECTION); + glLoadMatrixf(reinterpret_cast(&projection)); + #if 0 + //qglGetFloatv(GL_PROJECTION_MATRIX, reinterpret_cast(&projection)); + #endif + + glMatrixMode(GL_MODELVIEW); + glLoadMatrixf(reinterpret_cast(&modelview)); + #if 0 + //qglGetFloatv(GL_MODELVIEW_MATRIX, reinterpret_cast(&modelview)); + #endif + + ASSERT_MESSAGE(realised(), "render states are not realised"); + + // global settings that are not set in renderstates + glFrontFace(GL_CW); + glCullFace(GL_BACK); + glPolygonOffset(-1, 1); + { + const GLubyte pattern[132] = { + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55 + }; + glPolygonStipple(pattern); + } + glEnableClientState(GL_VERTEX_ARRAY); + g_vertexArray_enabled = true; + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); + + if(GlobalOpenGL().GL_1_3()) + { + glActiveTexture(GL_TEXTURE0); + glClientActiveTexture(GL_TEXTURE0); + } + + if(GlobalOpenGL().ARB_shader_objects()) + { + glUseProgramObjectARB(0); + glDisableVertexAttribArrayARB(c_attr_TexCoord0); + glDisableVertexAttribArrayARB(c_attr_Tangent); + glDisableVertexAttribArrayARB(c_attr_Binormal); + } + + if(globalstate & RENDER_TEXTURE) + { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + + OpenGLState current; + OpenGLState_constructDefault(current); + current.m_sort = OpenGLState::eSortFirst; + + // default renderstate settings + glLineStipple(current.m_linestipple_factor, current.m_linestipple_pattern); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glDisable(GL_LIGHTING); + glDisable(GL_TEXTURE_2D); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + g_texcoordArray_enabled = false; + glDisableClientState(GL_COLOR_ARRAY); + g_colorArray_enabled = false; + glDisableClientState(GL_NORMAL_ARRAY); + g_normalArray_enabled = false; + glDisable(GL_BLEND); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glShadeModel(GL_FLAT); + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + glDisable(GL_ALPHA_TEST); + glDisable(GL_LINE_STIPPLE); + glDisable(GL_POLYGON_STIPPLE); + glDisable(GL_POLYGON_OFFSET_LINE); + + glBindTexture(GL_TEXTURE_2D, 0); + glColor4f(1,1,1,1); + glDepthFunc(GL_LESS); + glAlphaFunc(GL_ALWAYS, 0); + glLineWidth(1); + glPointSize(1); + + glHint(GL_FOG_HINT, GL_NICEST); + glDisable(GL_FOG); + setFogState(OpenGLFogState()); + + GlobalOpenGL_debugAssertNoErrors(); + + debug_string("begin rendering"); + for(OpenGLStates::iterator i = g_state_sorted.begin(); i != g_state_sorted.end(); ++i) + { + (*i).second->render(current, globalstate, viewer); + } + debug_string("end rendering"); + } + void realise() + { + if(--m_unrealised == 0) + { + if(lightingSupported() && lightingEnabled()) + { + if(useShaderLanguage()) + { + g_bumpGLSL.create(); + g_depthFillGLSL.create(); + } + else + { + g_bumpARB.create(); + g_depthFillARB.create(); + } + } + + for(Shaders::iterator i = m_shaders.begin(); i != m_shaders.end(); ++i) + { + if(!(*i).value.empty()) + { + (*i).value->realise(i->key); + } + } + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + for(Shaders::iterator i = m_shaders.begin(); i != m_shaders.end(); ++i) + { + if(!(*i).value.empty()) + { + (*i).value->unrealise(); + } + } + if(GlobalOpenGL().contextValid && lightingSupported() && lightingEnabled()) + { + if(useShaderLanguage()) + { + g_bumpGLSL.destroy(); + g_depthFillGLSL.destroy(); + } + else + { + g_bumpARB.destroy(); + g_depthFillARB.destroy(); + } + } + } + } + bool realised() + { + return m_unrealised == 0; + } + + + bool lightingEnabled() const + { + return m_lightingEnabled; + } + bool lightingSupported() const + { + return m_lightingSupported; + } + bool useShaderLanguage() const + { + return m_useShaderLanguage; + } + void setLighting(bool supported, bool enabled) + { + bool refresh = (m_lightingSupported && m_lightingEnabled) != (supported && enabled); + + if(refresh) + { + unrealise(); + GlobalShaderSystem().setLightingEnabled(supported && enabled); + } + + m_lightingSupported = supported; + m_lightingEnabled = enabled; + + if(refresh) + { + realise(); + } + } + void extensionsInitialised() + { + setLighting(GlobalOpenGL().GL_1_3() + && GlobalOpenGL().ARB_vertex_program() + && GlobalOpenGL().ARB_fragment_program() + && GlobalOpenGL().ARB_shader_objects() + && GlobalOpenGL().ARB_vertex_shader() + && GlobalOpenGL().ARB_fragment_shader() + && GlobalOpenGL().ARB_shading_language_100(), + m_lightingEnabled + ); + + if(!lightingSupported()) + { + globalOutputStream() << "Lighting mode requires OpenGL features not supported by your graphics drivers:\n"; + if(!GlobalOpenGL().GL_1_3()) + { + globalOutputStream() << " GL version 1.3 or better\n"; + } + if(!GlobalOpenGL().ARB_vertex_program()) + { + globalOutputStream() << " GL_ARB_vertex_program\n"; + } + if(!GlobalOpenGL().ARB_fragment_program()) + { + globalOutputStream() << " GL_ARB_fragment_program\n"; + } + if(!GlobalOpenGL().ARB_shader_objects()) + { + globalOutputStream() << " GL_ARB_shader_objects\n"; + } + if(!GlobalOpenGL().ARB_vertex_shader()) + { + globalOutputStream() << " GL_ARB_vertex_shader\n"; + } + if(!GlobalOpenGL().ARB_fragment_shader()) + { + globalOutputStream() << " GL_ARB_fragment_shader\n"; + } + if(!GlobalOpenGL().ARB_shading_language_100()) + { + globalOutputStream() << " GL_ARB_shading_language_100\n"; + } + } + } + void setLightingEnabled(bool enabled) + { + setLighting(m_lightingSupported, enabled); + } + + // light culling + + RendererLights m_lights; + bool m_lightsChanged; + typedef std::map LightLists; + LightLists m_lightLists; + + const LightList& attach(LightCullable& cullable) + { + return (*m_lightLists.insert(LightLists::value_type(&cullable, LinearLightList(cullable, m_lights, EvaluateChangedCaller(*this)))).first).second; + } + void detach(LightCullable& cullable) + { + m_lightLists.erase(&cullable); + } + void changed(LightCullable& cullable) + { + LightLists::iterator i = m_lightLists.find(&cullable); + ASSERT_MESSAGE(i != m_lightLists.end(), "cullable not attached"); + (*i).second.lightsChanged(); + } + void attach(RendererLight& light) + { + ASSERT_MESSAGE(m_lights.find(&light) == m_lights.end(), "light could not be attached"); + m_lights.insert(&light); + changed(light); + } + void detach(RendererLight& light) + { + ASSERT_MESSAGE(m_lights.find(&light) != m_lights.end(), "light could not be detached"); + m_lights.erase(&light); + changed(light); + } + void changed(RendererLight& light) + { + m_lightsChanged = true; + } + void evaluateChanged() + { + if(m_lightsChanged) + { + m_lightsChanged = false; + for(LightLists::iterator i = m_lightLists.begin(); i != m_lightLists.end(); ++i) + { + (*i).second.lightsChanged(); + } + } + } + typedef MemberCaller EvaluateChangedCaller; + + typedef std::set Renderables; + Renderables m_renderables; + mutable bool m_traverseRenderablesMutex; + + // renderables + void attachRenderable(const Renderable& renderable) + { + ASSERT_MESSAGE(!m_traverseRenderablesMutex, "attaching renderable during traversal"); + ASSERT_MESSAGE(m_renderables.find(&renderable) == m_renderables.end(), "renderable could not be attached"); + m_renderables.insert(&renderable); + } + void detachRenderable(const Renderable& renderable) + { + ASSERT_MESSAGE(!m_traverseRenderablesMutex, "detaching renderable during traversal"); + ASSERT_MESSAGE(m_renderables.find(&renderable) != m_renderables.end(), "renderable could not be detached"); + m_renderables.erase(&renderable); + } + void forEachRenderable(const RenderableCallback& callback) const + { + ASSERT_MESSAGE(!m_traverseRenderablesMutex, "for-each during traversal"); + m_traverseRenderablesMutex = true; + for(Renderables::const_iterator i = m_renderables.begin(); i != m_renderables.end(); ++i) + { + callback(*(*i)); + } + m_traverseRenderablesMutex = false; + } +}; + +static OpenGLShaderCache* g_ShaderCache; + +void ShaderCache_extensionsInitialised() +{ + g_ShaderCache->extensionsInitialised(); +} + +void ShaderCache_setBumpEnabled(bool enabled) +{ + g_ShaderCache->setLightingEnabled(enabled); +} + + +Vector3 g_DebugShaderColours[256]; +Shader* g_defaultPointLight = 0; + +void ShaderCache_Construct() +{ + g_ShaderCache = new OpenGLShaderCache; + GlobalTexturesCache().attach(*g_ShaderCache); + GlobalShaderSystem().attach(*g_ShaderCache); + + if(g_pGameDescription->mGameType == "doom3") + { + g_defaultPointLight = g_ShaderCache->capture("lights/defaultPointLight"); + //Shader* overbright = + g_ShaderCache->capture("$OVERBRIGHT"); + +#if LIGHT_SHADER_DEBUG + for(std::size_t i = 0; i < 256; ++i) + { + g_DebugShaderColours[i] = Vector3(i / 256.0, i / 256.0, i / 256.0); + } + + g_DebugShaderColours[0] = Vector3(1, 0, 0); + g_DebugShaderColours[1] = Vector3(1, 0.5, 0); + g_DebugShaderColours[2] = Vector3(1, 1, 0); + g_DebugShaderColours[3] = Vector3(0.5, 1, 0); + g_DebugShaderColours[4] = Vector3(0, 1, 0); + g_DebugShaderColours[5] = Vector3(0, 1, 0.5); + g_DebugShaderColours[6] = Vector3(0, 1, 1); + g_DebugShaderColours[7] = Vector3(0, 0.5, 1); + g_DebugShaderColours[8] = Vector3(0, 0, 1); + g_DebugShaderColours[9] = Vector3(0.5, 0, 1); + g_DebugShaderColours[10] = Vector3(1, 0, 1); + g_DebugShaderColours[11] = Vector3(1, 0, 0.5); + + g_lightDebugShaders.reserve(256); + StringOutputStream buffer(256); + for(std::size_t i = 0; i < 256; ++i) + { + buffer << "(" << g_DebugShaderColours[i].x() << " " << g_DebugShaderColours[i].y() << " " << g_DebugShaderColours[i].z() << ")"; + g_lightDebugShaders.push_back(g_ShaderCache->capture(buffer.c_str())); + buffer.clear(); + } +#endif + } +} + +void ShaderCache_Destroy() +{ + if(g_pGameDescription->mGameType == "doom3") + { + g_ShaderCache->release("lights/defaultPointLight"); + g_ShaderCache->release("$OVERBRIGHT"); + g_defaultPointLight = 0; + +#if LIGHT_SHADER_DEBUG + g_lightDebugShaders.clear(); + StringOutputStream buffer(256); + for(std::size_t i = 0; i < 256; ++i) + { + buffer << "(" << g_DebugShaderColours[i].x() << " " << g_DebugShaderColours[i].y() << " " << g_DebugShaderColours[i].z() << ")"; + g_ShaderCache->release(buffer.c_str()); + } +#endif + } + + GlobalShaderSystem().detach(*g_ShaderCache); + GlobalTexturesCache().detach(*g_ShaderCache); + delete g_ShaderCache; +} + +ShaderCache* GetShaderCache() +{ + return g_ShaderCache; +} + +inline void setTextureState(GLint& current, const GLint& texture, GLenum textureUnit) +{ + if(texture != current) + { + glActiveTexture(textureUnit); + glClientActiveTexture(textureUnit); + glBindTexture(GL_TEXTURE_2D, texture); + GlobalOpenGL_debugAssertNoErrors(); + current = texture; + } +} + +inline void setTextureState(GLint& current, const GLint& texture) +{ + if(texture != current) + { + glBindTexture(GL_TEXTURE_2D, texture); + GlobalOpenGL_debugAssertNoErrors(); + current = texture; + } +} + +inline void setState(unsigned int state, unsigned int delta, unsigned int flag, GLenum glflag) +{ + if(delta & state & flag) + { + glEnable(glflag); + GlobalOpenGL_debugAssertNoErrors(); + } + else if(delta & ~state & flag) + { + glDisable(glflag); + GlobalOpenGL_debugAssertNoErrors(); + } +} + +void OpenGLState_apply(const OpenGLState& self, OpenGLState& current, unsigned int globalstate) +{ + debug_int("sort", int(self.m_sort)); + debug_int("texture", self.m_texture); + debug_int("state", self.m_state); + debug_int("address", int(std::size_t(&self))); + + count_state(); + + if(self.m_state & RENDER_OVERRIDE) + { + globalstate |= RENDER_FILL | RENDER_DEPTHWRITE; + } + + const unsigned int state = self.m_state & globalstate; + const unsigned int delta = state ^ current.m_state; + + GlobalOpenGL_debugAssertNoErrors(); + + GLProgram* program = (state & RENDER_PROGRAM) != 0 ? self.m_program : 0; + + if(program != current.m_program) + { + if(current.m_program != 0) + { + current.m_program->disable(); + glColor4fv(vector4_to_array(current.m_colour)); + debug_colour("cleaning program"); + } + + current.m_program = program; + + if(current.m_program != 0) + { + current.m_program->enable(); + } + } + + if(delta & state & RENDER_FILL) + { + //qglPolygonMode (GL_BACK, GL_LINE); + //qglPolygonMode (GL_FRONT, GL_FILL); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + GlobalOpenGL_debugAssertNoErrors(); + } + else if(delta & ~state & RENDER_FILL) + { + glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + GlobalOpenGL_debugAssertNoErrors(); + } + + setState(state, delta, RENDER_OFFSETLINE, GL_POLYGON_OFFSET_LINE); + + if(delta & state & RENDER_LIGHTING) + { + glEnable(GL_LIGHTING); + glEnable(GL_COLOR_MATERIAL); + //qglEnable(GL_RESCALE_NORMAL); + glEnableClientState(GL_NORMAL_ARRAY); + GlobalOpenGL_debugAssertNoErrors(); + g_normalArray_enabled = true; + } + else if(delta & ~state & RENDER_LIGHTING) + { + glDisable(GL_LIGHTING); + glDisable(GL_COLOR_MATERIAL); + //qglDisable(GL_RESCALE_NORMAL); + glDisableClientState(GL_NORMAL_ARRAY); + GlobalOpenGL_debugAssertNoErrors(); + g_normalArray_enabled = false; + } + + if(delta & state & RENDER_TEXTURE) + { + GlobalOpenGL_debugAssertNoErrors(); + + if(GlobalOpenGL().GL_1_3()) + { + glActiveTexture(GL_TEXTURE0); + glClientActiveTexture(GL_TEXTURE0); + } + + glEnable(GL_TEXTURE_2D); + + glColor4f(1,1,1,self.m_colour[3]); + debug_colour("setting texture"); + + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + GlobalOpenGL_debugAssertNoErrors(); + g_texcoordArray_enabled = true; + } + else if(delta & ~state & RENDER_TEXTURE) + { + if(GlobalOpenGL().GL_1_3()) + { + glActiveTexture(GL_TEXTURE0); + glClientActiveTexture(GL_TEXTURE0); + } + + glDisable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + GlobalOpenGL_debugAssertNoErrors(); + g_texcoordArray_enabled = false; + } + + if(delta & state & RENDER_BLEND) + { +// FIXME: some .TGA are buggy, have a completely empty alpha channel +// if such brushes are rendered in this loop they would be totally transparent with GL_MODULATE +// so I decided using GL_DECAL instead +// if an empty-alpha-channel or nearly-empty texture is used. It will be blank-transparent. +// this could get better if you can get glTexEnviv (GL_TEXTURE_ENV, to work .. patches are welcome + + glEnable(GL_BLEND); + if(GlobalOpenGL().GL_1_3()) + { + glActiveTexture(GL_TEXTURE0); + } + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + GlobalOpenGL_debugAssertNoErrors(); + } + else if(delta & ~state & RENDER_BLEND) + { + glDisable(GL_BLEND); + if(GlobalOpenGL().GL_1_3()) + { + glActiveTexture(GL_TEXTURE0); + } + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GlobalOpenGL_debugAssertNoErrors(); + } + + setState(state, delta, RENDER_CULLFACE, GL_CULL_FACE); + + if(delta & state & RENDER_SMOOTH) + { + glShadeModel(GL_SMOOTH); + GlobalOpenGL_debugAssertNoErrors(); + } + else if(delta & ~state & RENDER_SMOOTH) + { + glShadeModel(GL_FLAT); + GlobalOpenGL_debugAssertNoErrors(); + } + + setState(state, delta, RENDER_SCALED, GL_NORMALIZE); // not GL_RESCALE_NORMAL + + setState(state, delta, RENDER_DEPTHTEST, GL_DEPTH_TEST); + + if(delta & state & RENDER_DEPTHWRITE) + { + glDepthMask(GL_TRUE); + +#if DEBUG_RENDER + GLboolean depthEnabled; + glGetBooleanv(GL_DEPTH_WRITEMASK, &depthEnabled); + ASSERT_MESSAGE(depthEnabled, "failed to set depth buffer mask bit"); +#endif + debug_string("enabled depth-buffer writing"); + + GlobalOpenGL_debugAssertNoErrors(); + } + else if(delta & ~state & RENDER_DEPTHWRITE) + { + glDepthMask(GL_FALSE); + +#if DEBUG_RENDER + GLboolean depthEnabled; + glGetBooleanv(GL_DEPTH_WRITEMASK, &depthEnabled); + ASSERT_MESSAGE(!depthEnabled, "failed to set depth buffer mask bit"); +#endif + debug_string("disabled depth-buffer writing"); + + GlobalOpenGL_debugAssertNoErrors(); + } + + if(delta & state & RENDER_COLOURWRITE) + { + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + GlobalOpenGL_debugAssertNoErrors(); + } + else if(delta & ~state & RENDER_COLOURWRITE) + { + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + GlobalOpenGL_debugAssertNoErrors(); + } + + setState(state, delta, RENDER_ALPHATEST, GL_ALPHA_TEST); + + if(delta & state & RENDER_COLOURARRAY) + { + glEnableClientState(GL_COLOR_ARRAY); + GlobalOpenGL_debugAssertNoErrors(); + debug_colour("enabling color_array"); + g_colorArray_enabled = true; + } + else if(delta & ~state & RENDER_COLOURARRAY) + { + glDisableClientState(GL_COLOR_ARRAY); + glColor4fv(vector4_to_array(self.m_colour)); + debug_colour("cleaning color_array"); + GlobalOpenGL_debugAssertNoErrors(); + g_colorArray_enabled = false; + } + + if(delta & ~state & RENDER_COLOURCHANGE) + { + glColor4fv(vector4_to_array(self.m_colour)); + GlobalOpenGL_debugAssertNoErrors(); + } + + setState(state, delta, RENDER_LINESTIPPLE, GL_LINE_STIPPLE); + setState(state, delta, RENDER_LINESMOOTH, GL_LINE_SMOOTH); + + setState(state, delta, RENDER_POLYGONSTIPPLE, GL_POLYGON_STIPPLE); + setState(state, delta, RENDER_POLYGONSMOOTH, GL_POLYGON_SMOOTH); + + setState(state, delta, RENDER_FOG, GL_FOG); + + if((state & RENDER_FOG) != 0) + { + setFogState(self.m_fog); + GlobalOpenGL_debugAssertNoErrors(); + current.m_fog = self.m_fog; + } + + if(state & RENDER_DEPTHTEST && self.m_depthfunc != current.m_depthfunc) + { + glDepthFunc(self.m_depthfunc); + GlobalOpenGL_debugAssertNoErrors(); + current.m_depthfunc = self.m_depthfunc; + } + + if(state & RENDER_LINESTIPPLE + && (self.m_linestipple_factor != current.m_linestipple_factor + || self.m_linestipple_pattern != current.m_linestipple_pattern)) + { + glLineStipple(self.m_linestipple_factor, self.m_linestipple_pattern); + GlobalOpenGL_debugAssertNoErrors(); + current.m_linestipple_factor = self.m_linestipple_factor; + current.m_linestipple_pattern = self.m_linestipple_pattern; + } + + + if(state & RENDER_ALPHATEST + && ( self.m_alphafunc != current.m_alphafunc + || self.m_alpharef != current.m_alpharef ) ) + { + glAlphaFunc(self.m_alphafunc, self.m_alpharef); + GlobalOpenGL_debugAssertNoErrors(); + current.m_alphafunc = self.m_alphafunc; + current.m_alpharef = self.m_alpharef; + } + + { + GLint texture0 = 0; + GLint texture1 = 0; + GLint texture2 = 0; + GLint texture3 = 0; + GLint texture4 = 0; + GLint texture5 = 0; + GLint texture6 = 0; + GLint texture7 = 0; + //if(state & RENDER_TEXTURE) != 0) + { + texture0 = self.m_texture; + texture1 = self.m_texture1; + texture2 = self.m_texture2; + texture3 = self.m_texture3; + texture4 = self.m_texture4; + texture5 = self.m_texture5; + texture6 = self.m_texture6; + texture7 = self.m_texture7; + } + + if(GlobalOpenGL().GL_1_3()) + { + setTextureState(current.m_texture, texture0, GL_TEXTURE0); + setTextureState(current.m_texture1, texture1, GL_TEXTURE1); + setTextureState(current.m_texture2, texture2, GL_TEXTURE2); + setTextureState(current.m_texture3, texture3, GL_TEXTURE3); + setTextureState(current.m_texture4, texture4, GL_TEXTURE4); + setTextureState(current.m_texture5, texture5, GL_TEXTURE5); + setTextureState(current.m_texture6, texture6, GL_TEXTURE6); + setTextureState(current.m_texture7, texture7, GL_TEXTURE7); + } + else + { + setTextureState(current.m_texture, texture0); + } + } + + + if(state & RENDER_TEXTURE && self.m_colour[3] != current.m_colour[3]) + { + debug_colour("setting alpha"); + glColor4f(1,1,1,self.m_colour[3]); + GlobalOpenGL_debugAssertNoErrors(); + } + + if(!(state & RENDER_TEXTURE) + && (self.m_colour[0] != current.m_colour[0] + || self.m_colour[1] != current.m_colour[1] + || self.m_colour[2] != current.m_colour[2] + || self.m_colour[3] != current.m_colour[3])) + { + glColor4fv(vector4_to_array(self.m_colour)); + debug_colour("setting non-texture"); + GlobalOpenGL_debugAssertNoErrors(); + } + current.m_colour = self.m_colour; + + if(state & RENDER_BLEND + && (self.m_blend_src != current.m_blend_src || self.m_blend_dst != current.m_blend_dst)) + { + glBlendFunc(self.m_blend_src, self.m_blend_dst); + GlobalOpenGL_debugAssertNoErrors(); + current.m_blend_src = self.m_blend_src; + current.m_blend_dst = self.m_blend_dst; + } + + if(!(state & RENDER_FILL) + && self.m_linewidth != current.m_linewidth) + { + glLineWidth(self.m_linewidth); + GlobalOpenGL_debugAssertNoErrors(); + current.m_linewidth = self.m_linewidth; + } + + if(!(state & RENDER_FILL) + && self.m_pointsize != current.m_pointsize) + { + glPointSize(self.m_pointsize); + GlobalOpenGL_debugAssertNoErrors(); + current.m_pointsize = self.m_pointsize; + } + + current.m_state = state; + + GlobalOpenGL_debugAssertNoErrors(); +} + +void Renderables_flush(OpenGLStateBucket::Renderables& renderables, OpenGLState& current, unsigned int globalstate, const Vector3& viewer) +{ + const Matrix4* transform = 0; + glPushMatrix(); + for(OpenGLStateBucket::Renderables::const_iterator i = renderables.begin(); i != renderables.end(); ++i) + { + //qglLoadMatrixf(i->m_transform); + if(!transform || (transform != (*i).m_transform && !matrix4_affine_equal(*transform, *(*i).m_transform))) + { + count_transform(); + transform = (*i).m_transform; + glPopMatrix(); + glPushMatrix(); + glMultMatrixf(reinterpret_cast(transform)); + glFrontFace(((current.m_state & RENDER_CULLFACE) != 0 && matrix4_handedness(*transform) == MATRIX4_RIGHTHANDED) ? GL_CW : GL_CCW); + } + + count_prim(); + + if(current.m_program != 0 && (*i).m_light != 0) + { + const IShader& lightShader = static_cast((*i).m_light->getShader())->getShader(); + if(lightShader.firstLayer() != 0) + { + GLuint attenuation_xy = lightShader.firstLayer()->texture()->texture_number; + GLuint attenuation_z = lightShader.lightFalloffImage() != 0 + ? lightShader.lightFalloffImage()->texture_number + : static_cast(g_defaultPointLight)->getShader().lightFalloffImage()->texture_number; + + setTextureState(current.m_texture3, attenuation_xy, GL_TEXTURE3); + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_2D, attenuation_xy); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + + setTextureState(current.m_texture4, attenuation_z, GL_TEXTURE4); + glActiveTexture(GL_TEXTURE4); + glBindTexture(GL_TEXTURE_2D, attenuation_z); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + + AABB lightBounds((*i).m_light->aabb()); + + Matrix4 world2light(g_matrix4_identity); + + if((*i).m_light->isProjected()) + { + world2light = (*i).m_light->projection(); + matrix4_multiply_by_matrix4(world2light, matrix4_transposed((*i).m_light->rotation())); + matrix4_translate_by_vec3(world2light, vector3_negated(lightBounds.origin)); // world->lightBounds + } + if(!(*i).m_light->isProjected()) + { + matrix4_translate_by_vec3(world2light, Vector3(0.5f, 0.5f, 0.5f)); + matrix4_scale_by_vec3(world2light, Vector3(0.5f, 0.5f, 0.5f)); + matrix4_scale_by_vec3(world2light, Vector3(1.0f / lightBounds.extents.x(), 1.0f / lightBounds.extents.y(), 1.0f / lightBounds.extents.z())); + matrix4_multiply_by_matrix4(world2light, matrix4_transposed((*i).m_light->rotation())); + matrix4_translate_by_vec3(world2light, vector3_negated(lightBounds.origin)); // world->lightBounds + } + + current.m_program->setParameters(viewer, *(*i).m_transform, lightBounds.origin + (*i).m_light->offset(), (*i).m_light->colour(), world2light); + debug_string("set lightBounds parameters"); + } + } + + (*i).m_renderable->render(current.m_state); + } + glPopMatrix(); + renderables.clear(); +} + +void OpenGLStateBucket::render(OpenGLState& current, unsigned int globalstate, const Vector3& viewer) +{ + if((globalstate & m_state.m_state & RENDER_SCREEN) != 0) + { + OpenGLState_apply(m_state, current, globalstate); + debug_colour("screen fill"); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadMatrixf(reinterpret_cast(&g_matrix4_identity)); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadMatrixf(reinterpret_cast(&g_matrix4_identity)); + + glBegin(GL_QUADS); + glVertex3f(-1, -1, 0); + glVertex3f(1, -1, 0); + glVertex3f(1, 1, 0); + glVertex3f(-1, 1, 0); + glEnd(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + } + else if(!m_renderables.empty()) + { + OpenGLState_apply(m_state, current, globalstate); + Renderables_flush(m_renderables, current, globalstate, viewer); + } +} + + +class OpenGLStateMap : public OpenGLStateLibrary +{ + typedef std::map States; + States m_states; +public: + ~OpenGLStateMap() + { + ASSERT_MESSAGE(m_states.empty(), "OpenGLStateMap::~OpenGLStateMap: not empty"); + } + + typedef States::iterator iterator; + iterator begin() + { + return m_states.begin(); + } + iterator end() + { + return m_states.end(); + } + + void getDefaultState(OpenGLState& state) const + { + OpenGLState_constructDefault(state); + } + + void insert(const char* name, const OpenGLState& state) + { + bool inserted = m_states.insert(States::value_type(name, state)).second; + ASSERT_MESSAGE(inserted, "OpenGLStateMap::insert: " << name << " already exists"); + } + void erase(const char* name) + { + std::size_t count = m_states.erase(name); + ASSERT_MESSAGE(count == 1, "OpenGLStateMap::erase: " << name << " does not exist"); + } + + iterator find(const char* name) + { + return m_states.find(name); + } +}; + +OpenGLStateMap* g_openglStates = 0; + +inline GLenum convertBlendFactor(BlendFactor factor) +{ + switch(factor) + { + case BLEND_ZERO: + return GL_ZERO; + case BLEND_ONE: + return GL_ONE; + case BLEND_SRC_COLOUR: + return GL_SRC_COLOR; + case BLEND_ONE_MINUS_SRC_COLOUR: + return GL_ONE_MINUS_SRC_COLOR; + case BLEND_SRC_ALPHA: + return GL_SRC_ALPHA; + case BLEND_ONE_MINUS_SRC_ALPHA: + return GL_ONE_MINUS_SRC_ALPHA; + case BLEND_DST_COLOUR: + return GL_DST_COLOR; + case BLEND_ONE_MINUS_DST_COLOUR: + return GL_ONE_MINUS_DST_COLOR; + case BLEND_DST_ALPHA: + return GL_DST_ALPHA; + case BLEND_ONE_MINUS_DST_ALPHA: + return GL_ONE_MINUS_DST_ALPHA; + case BLEND_SRC_ALPHA_SATURATE: + return GL_SRC_ALPHA_SATURATE; + } + return GL_ZERO; +} + +/// \todo Define special-case shaders in a data file. +void OpenGLShader::construct(const char* name) +{ + OpenGLState& state = appendDefaultPass(); + switch(name[0]) + { + case '(': + sscanf(name, "(%g %g %g)", &state.m_colour[0], &state.m_colour[1], &state.m_colour[2]); + state.m_colour[3] = 1.0f; + state.m_state = RENDER_FILL|RENDER_LIGHTING|RENDER_DEPTHTEST|RENDER_CULLFACE|RENDER_COLOURWRITE|RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortFullbright; + break; + + case '[': + sscanf(name, "[%g %g %g]", &state.m_colour[0], &state.m_colour[1], &state.m_colour[2]); + state.m_colour[3] = 0.5f; + state.m_state = RENDER_FILL|RENDER_LIGHTING|RENDER_DEPTHTEST|RENDER_CULLFACE|RENDER_COLOURWRITE|RENDER_DEPTHWRITE|RENDER_BLEND; + state.m_sort = OpenGLState::eSortTranslucent; + break; + + case '<': + sscanf(name, "<%g %g %g>", &state.m_colour[0], &state.m_colour[1], &state.m_colour[2]); + state.m_colour[3] = 1; + state.m_state = RENDER_DEPTHTEST|RENDER_COLOURWRITE|RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortFullbright; + state.m_depthfunc = GL_LESS; + state.m_linewidth = 1; + state.m_pointsize = 1; + break; + + case '$': + { + OpenGLStateMap::iterator i = g_openglStates->find(name); + if(i != g_openglStates->end()) + { + state = (*i).second; + break; + } + } + if(string_equal(name+1, "POINT")) + { + state.m_state = RENDER_COLOURARRAY|RENDER_COLOURWRITE|RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortControlFirst; + state.m_pointsize = 4; + } + else if(string_equal(name+1, "SELPOINT")) + { + state.m_state = RENDER_COLOURARRAY|RENDER_COLOURWRITE|RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortControlFirst + 1; + state.m_pointsize = 4; + } + else if(string_equal(name+1, "BIGPOINT")) + { + state.m_state = RENDER_COLOURARRAY|RENDER_COLOURWRITE|RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortControlFirst; + state.m_pointsize = 6; + } + else if(string_equal(name+1, "PIVOT")) + { + state.m_state = RENDER_COLOURARRAY|RENDER_COLOURWRITE|RENDER_DEPTHTEST|RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortGUI1; + state.m_linewidth = 2; + state.m_depthfunc = GL_LEQUAL; + + OpenGLState& hiddenLine = appendDefaultPass(); + hiddenLine.m_state = RENDER_COLOURARRAY|RENDER_COLOURWRITE|RENDER_DEPTHTEST|RENDER_LINESTIPPLE; + hiddenLine.m_sort = OpenGLState::eSortGUI0; + hiddenLine.m_linewidth = 2; + hiddenLine.m_depthfunc = GL_GREATER; + } + else if(string_equal(name+1, "LATTICE")) + { + state.m_colour[0] = 1; + state.m_colour[1] = 0.5; + state.m_colour[2] = 0; + state.m_colour[3] = 1; + state.m_state = RENDER_COLOURWRITE|RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortControlFirst; + } + else if(string_equal(name+1, "WIREFRAME")) + { + state.m_state = RENDER_DEPTHTEST|RENDER_COLOURWRITE|RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortFullbright; + } + else if(string_equal(name+1, "CAM_HIGHLIGHT")) + { + state.m_colour[0] = 1; + state.m_colour[1] = 0; + state.m_colour[2] = 0; + state.m_colour[3] = 0.3f; + state.m_state = RENDER_FILL|RENDER_DEPTHTEST|RENDER_CULLFACE|RENDER_BLEND|RENDER_COLOURWRITE|RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortHighlight; + state.m_depthfunc = GL_LEQUAL; + } + else if(string_equal(name+1, "CAM_OVERLAY")) + { +#if 0 + state.m_state = RENDER_CULLFACE|RENDER_COLOURWRITE|RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortOverlayFirst; +#else + state.m_state = RENDER_CULLFACE|RENDER_DEPTHTEST|RENDER_COLOURWRITE|RENDER_DEPTHWRITE|RENDER_OFFSETLINE; + state.m_sort = OpenGLState::eSortOverlayFirst + 1; + state.m_depthfunc = GL_LEQUAL; + + OpenGLState& hiddenLine = appendDefaultPass(); + hiddenLine.m_colour[0] = 0.75; + hiddenLine.m_colour[1] = 0.75; + hiddenLine.m_colour[2] = 0.75; + hiddenLine.m_colour[3] = 1; + hiddenLine.m_state = RENDER_CULLFACE|RENDER_DEPTHTEST|RENDER_COLOURWRITE|RENDER_OFFSETLINE|RENDER_LINESTIPPLE; + hiddenLine.m_sort = OpenGLState::eSortOverlayFirst; + hiddenLine.m_depthfunc = GL_GREATER; + hiddenLine.m_linestipple_factor = 2; +#endif + } + else if(string_equal(name+1, "XY_OVERLAY")) + { + state.m_colour[0] = g_xywindow_globals.color_selbrushes[0]; + state.m_colour[1] = g_xywindow_globals.color_selbrushes[1]; + state.m_colour[2] = g_xywindow_globals.color_selbrushes[2]; + state.m_colour[3] = 1; + state.m_state = RENDER_COLOURWRITE | RENDER_LINESTIPPLE; + state.m_sort = OpenGLState::eSortOverlayFirst; + state.m_linewidth = 2; + state.m_linestipple_factor = 3; + } + else if(string_equal(name+1, "DEBUG_CLIPPED")) + { + state.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortLast; + } + else if(string_equal(name+1, "POINTFILE")) + { + state.m_colour[0] = 1; + state.m_colour[1] = 0; + state.m_colour[2] = 0; + state.m_colour[3] = 1; + state.m_state = RENDER_DEPTHTEST | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortFullbright; + state.m_linewidth = 4; + } + else if(string_equal(name+1, "LIGHT_SPHERE")) + { + state.m_colour[0] = .15f * .95f; + state.m_colour[1] = .15f * .95f; + state.m_colour[2] = .15f * .95f; + state.m_colour[3] = 1; + state.m_state = RENDER_CULLFACE | RENDER_DEPTHTEST | RENDER_BLEND | RENDER_FILL | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; + state.m_blend_src = GL_ONE; + state.m_blend_dst = GL_ONE; + state.m_sort = OpenGLState::eSortTranslucent; + } + else if(string_equal(name+1, "Q3MAP2_LIGHT_SPHERE")) + { + state.m_colour[0] = .05f; + state.m_colour[1] = .05f; + state.m_colour[2] = .05f; + state.m_colour[3] = 1; + state.m_state = RENDER_CULLFACE | RENDER_DEPTHTEST | RENDER_BLEND | RENDER_FILL; + state.m_blend_src = GL_ONE; + state.m_blend_dst = GL_ONE; + state.m_sort = OpenGLState::eSortTranslucent; + } + else if(string_equal(name+1, "WIRE_OVERLAY")) + { +#if 0 + state.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_DEPTHTEST | RENDER_OVERRIDE; + state.m_sort = OpenGLState::eSortOverlayFirst; +#else + state.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_DEPTHTEST | RENDER_OVERRIDE; + state.m_sort = OpenGLState::eSortGUI1; + state.m_depthfunc = GL_LEQUAL; + + OpenGLState& hiddenLine = appendDefaultPass(); + hiddenLine.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_DEPTHTEST | RENDER_OVERRIDE | RENDER_LINESTIPPLE; + hiddenLine.m_sort = OpenGLState::eSortGUI0; + hiddenLine.m_depthfunc = GL_GREATER; +#endif + } + else if(string_equal(name+1, "FLATSHADE_OVERLAY")) + { + state.m_state = RENDER_CULLFACE | RENDER_LIGHTING | RENDER_SMOOTH | RENDER_SCALED | RENDER_COLOURARRAY | RENDER_FILL | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_DEPTHTEST | RENDER_OVERRIDE; + state.m_sort = OpenGLState::eSortGUI1; + state.m_depthfunc = GL_LEQUAL; + + OpenGLState& hiddenLine = appendDefaultPass(); + hiddenLine.m_state = RENDER_CULLFACE | RENDER_LIGHTING | RENDER_SMOOTH | RENDER_SCALED | RENDER_COLOURARRAY | RENDER_FILL | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_DEPTHTEST | RENDER_OVERRIDE | RENDER_POLYGONSTIPPLE; + hiddenLine.m_sort = OpenGLState::eSortGUI0; + hiddenLine.m_depthfunc = GL_GREATER; + } + else if(string_equal(name+1, "CLIPPER_OVERLAY")) + { + state.m_colour[0] = g_xywindow_globals.color_clipper[0]; + state.m_colour[1] = g_xywindow_globals.color_clipper[1]; + state.m_colour[2] = g_xywindow_globals.color_clipper[2]; + state.m_colour[3] = 1; + state.m_state = RENDER_CULLFACE | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_FILL | RENDER_POLYGONSTIPPLE; + state.m_sort = OpenGLState::eSortOverlayFirst; + } + else if(string_equal(name+1, "OVERBRIGHT")) + { + const float lightScale = 2; + state.m_colour[0] = lightScale * 0.5f; + state.m_colour[1] = lightScale * 0.5f; + state.m_colour[2] = lightScale * 0.5f; + state.m_colour[3] = 0.5; + state.m_state = RENDER_FILL|RENDER_BLEND|RENDER_COLOURWRITE|RENDER_SCREEN; + state.m_sort = OpenGLState::eSortOverbrighten; + state.m_blend_src = GL_DST_COLOR; + state.m_blend_dst = GL_SRC_COLOR; + } + else + { + // default to something recognisable.. =) + ERROR_MESSAGE("hardcoded renderstate not found"); + state.m_colour[0] = 1; + state.m_colour[1] = 0; + state.m_colour[2] = 1; + state.m_colour[3] = 1; + state.m_state = RENDER_COLOURWRITE | RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortFirst; + } + break; + default: + // construction from IShader + m_shader = QERApp_Shader_ForName(name); + + if(g_ShaderCache->lightingSupported() && g_ShaderCache->lightingEnabled() && m_shader->getBump() != 0 && m_shader->getBump()->texture_number != 0) // is a bump shader + { + state.m_state = RENDER_FILL | RENDER_CULLFACE | RENDER_TEXTURE | RENDER_DEPTHTEST | RENDER_DEPTHWRITE | RENDER_COLOURWRITE | RENDER_PROGRAM; + state.m_colour[0] = 0; + state.m_colour[1] = 0; + state.m_colour[2] = 0; + state.m_colour[3] = 1; + state.m_sort = OpenGLState::eSortOpaque; + + if(g_ShaderCache->useShaderLanguage()) + { + state.m_program = &g_depthFillGLSL; + } + else + { + state.m_program = &g_depthFillARB; + } + + OpenGLState& bumpPass = appendDefaultPass(); + bumpPass.m_texture = m_shader->getDiffuse()->texture_number; + bumpPass.m_texture1 = m_shader->getBump()->texture_number; + bumpPass.m_texture2 = m_shader->getSpecular()->texture_number; + + bumpPass.m_state = RENDER_BLEND|RENDER_FILL|RENDER_CULLFACE|RENDER_DEPTHTEST|RENDER_COLOURWRITE|RENDER_SMOOTH|RENDER_BUMP|RENDER_PROGRAM; + + if(g_ShaderCache->useShaderLanguage()) + { + bumpPass.m_state |= RENDER_LIGHTING; + bumpPass.m_program = &g_bumpGLSL; + } + else + { + bumpPass.m_program = &g_bumpARB; + } + + bumpPass.m_depthfunc = GL_LEQUAL; + bumpPass.m_sort = OpenGLState::eSortMultiFirst; + bumpPass.m_blend_src = GL_ONE; + bumpPass.m_blend_dst = GL_ONE; + } + else + { + state.m_texture = m_shader->getTexture()->texture_number; + + state.m_state = RENDER_FILL|RENDER_TEXTURE|RENDER_DEPTHTEST|RENDER_COLOURWRITE|RENDER_LIGHTING|RENDER_SMOOTH; + if((m_shader->getFlags() & QER_CULL) != 0) + { + if(m_shader->getCull() == IShader::eCullBack) + { + state.m_state |= RENDER_CULLFACE; + } + } + else + { + state.m_state |= RENDER_CULLFACE; + } + if((m_shader->getFlags() & QER_ALPHATEST) != 0) + { + state.m_state |= RENDER_ALPHATEST; + IShader::EAlphaFunc alphafunc; + m_shader->getAlphaFunc(&alphafunc, &state.m_alpharef); + switch(alphafunc) + { + case IShader::eAlways: + state.m_alphafunc = GL_ALWAYS; + case IShader::eEqual: + state.m_alphafunc = GL_EQUAL; + case IShader::eLess: + state.m_alphafunc = GL_LESS; + case IShader::eGreater: + state.m_alphafunc = GL_GREATER; + case IShader::eLEqual: + state.m_alphafunc = GL_LEQUAL; + case IShader::eGEqual: + state.m_alphafunc = GL_GEQUAL; + } + } + reinterpret_cast(state.m_colour) = m_shader->getTexture()->color; + state.m_colour[3] = 1.0f; + + if((m_shader->getFlags() & QER_TRANS) != 0) + { + state.m_state |= RENDER_BLEND; + state.m_colour[3] = m_shader->getTrans(); + state.m_sort = OpenGLState::eSortTranslucent; + BlendFunc blendFunc = m_shader->getBlendFunc(); + state.m_blend_src = convertBlendFactor(blendFunc.m_src); + state.m_blend_dst = convertBlendFactor(blendFunc.m_dst); + if(state.m_blend_src == GL_SRC_ALPHA || state.m_blend_dst == GL_SRC_ALPHA) + { + state.m_state |= RENDER_DEPTHWRITE; + } + } + else + { + state.m_state |= RENDER_DEPTHWRITE; + state.m_sort = OpenGLState::eSortFullbright; + } + } + } +} + + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +class OpenGLStateLibraryAPI +{ + OpenGLStateMap m_stateMap; +public: + typedef OpenGLStateLibrary Type; + STRING_CONSTANT(Name, "*"); + + OpenGLStateLibraryAPI() + { + g_openglStates = &m_stateMap; + } + ~OpenGLStateLibraryAPI() + { + g_openglStates = 0; + } + OpenGLStateLibrary* getTable() + { + return &m_stateMap; + } +}; + +typedef SingletonModule OpenGLStateLibraryModule; +typedef Static StaticOpenGLStateLibraryModule; +StaticRegisterModule staticRegisterOpenGLStateLibrary(StaticOpenGLStateLibraryModule::instance()); + +class ShaderCacheDependencies : public GlobalShadersModuleRef, public GlobalTexturesModuleRef, public GlobalOpenGLStateLibraryModuleRef +{ +public: + ShaderCacheDependencies() : + GlobalShadersModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("shaders")) + { + } +}; + +class ShaderCacheAPI +{ + ShaderCache* m_shaderCache; +public: + typedef ShaderCache Type; + STRING_CONSTANT(Name, "*"); + + ShaderCacheAPI() + { + ShaderCache_Construct(); + + m_shaderCache = GetShaderCache(); + } + ~ShaderCacheAPI() + { + ShaderCache_Destroy(); + } + ShaderCache* getTable() + { + return m_shaderCache; + } +}; + +typedef SingletonModule ShaderCacheModule; +typedef Static StaticShaderCacheModule; +StaticRegisterModule staticRegisterShaderCache(StaticShaderCacheModule::instance()); + + diff --git a/radiant/renderstate.h b/radiant/renderstate.h new file mode 100644 index 00000000..9bbd2918 --- /dev/null +++ b/radiant/renderstate.h @@ -0,0 +1,28 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_RENDERSTATE_H) +#define INCLUDED_RENDERSTATE_H + +void ShaderCache_setBumpEnabled(bool enabled); +void ShaderCache_extensionsInitialised(); + +#endif diff --git a/radiant/resource.h b/radiant/resource.h new file mode 100644 index 00000000..dc86a878 --- /dev/null +++ b/radiant/resource.h @@ -0,0 +1,18 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by radiant.rc +// +#define IDI_RADIANT 101 +#define IDR_MENU1 102 +#define IDD_DIALOG1 103 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/radiant/scenegraph.cpp b/radiant/scenegraph.cpp new file mode 100644 index 00000000..cb83691e --- /dev/null +++ b/radiant/scenegraph.cpp @@ -0,0 +1,312 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "scenegraph.h" + +#include "debugging/debugging.h" + +#include +#include +#include + +#include "string/string.h" +#include "signal/signal.h" +#include "scenelib.h" +#include "instancelib.h" +#include "treemodel.h" + +class StringEqualPredicate +{ + const char* m_string; +public: + StringEqualPredicate(const char* string) : m_string(string) + { + } + bool operator()(const char* other) const + { + return string_equal(m_string, other); + } +}; + +template +class TypeIdMap +{ + typedef const char* TypeName; + typedef TypeName TypeNames[SIZE]; + TypeNames m_typeNames; + TypeName* m_typeNamesEnd; + +public: + TypeIdMap() : m_typeNamesEnd(m_typeNames) + { + } + TypeId getTypeId(const char* name) + { + TypeName* i = std::find_if(m_typeNames, m_typeNamesEnd, StringEqualPredicate(name)); + if(i == m_typeNamesEnd) + { + ASSERT_MESSAGE(m_typeNamesEnd != m_typeNames + SIZE, "reached maximum number of type names supported (" << Unsigned(SIZE) << ")"); + *m_typeNamesEnd++ = name; + } + return i - m_typeNames; + } +}; + +class CompiledGraph : public scene::Graph, public scene::Instantiable::Observer +{ + typedef std::map InstanceMap; + + InstanceMap m_instances; + scene::Instantiable::Observer* m_observer; + Signal0 m_boundsChanged; + scene::Path m_rootpath; + Signal0 m_sceneChangedCallbacks; + + TypeIdMap m_nodeTypeIds; + TypeIdMap m_instanceTypeIds; + +public: + + CompiledGraph(scene::Instantiable::Observer* observer) + : m_observer(observer) + { + } + + void addSceneChangedCallback(const SignalHandler& handler) + { + m_sceneChangedCallbacks.connectLast(handler); + } + void sceneChanged() + { + m_sceneChangedCallbacks(); + } + + scene::Node& root() + { + ASSERT_MESSAGE(!m_rootpath.empty(), "scenegraph root does not exist"); + return m_rootpath.top(); + } + void insert_root(scene::Node& root) + { + //globalOutputStream() << "insert_root\n"; + + ASSERT_MESSAGE(m_rootpath.empty(), "scenegraph root already exists"); + + root.IncRef(); + + Node_traverseSubgraph(root, InstanceSubgraphWalker(this, scene::Path(), 0)); + + m_rootpath.push(makeReference(root)); + } + void erase_root() + { + //globalOutputStream() << "erase_root\n"; + + ASSERT_MESSAGE(!m_rootpath.empty(), "scenegraph root does not exist"); + + scene::Node& root = m_rootpath.top(); + + m_rootpath.pop(); + + Node_traverseSubgraph(root, UninstanceSubgraphWalker(this, scene::Path())); + + root.DecRef(); + } + void boundsChanged() + { + m_boundsChanged(); + } + + void traverse(const Walker& walker) + { + traverse_subgraph(walker, m_instances.begin()); + } + + void traverse_subgraph(const Walker& walker, const scene::Path& start) + { + if(!m_instances.empty()) + { + traverse_subgraph(walker, m_instances.find(PathConstReference(start))); + } + } + + scene::Instance* find(const scene::Path& path) + { + InstanceMap::iterator i = m_instances.find(PathConstReference(path)); + if(i == m_instances.end()) + { + return 0; + } + return (*i).second; + } + + void insert(scene::Instance* instance) + { + m_instances.insert(InstanceMap::value_type(PathConstReference(instance->path()), instance)); + + m_observer->insert(instance); + } + void erase(scene::Instance* instance) + { + m_observer->erase(instance); + + m_instances.erase(PathConstReference(instance->path())); + } + + SignalHandlerId addBoundsChangedCallback(const SignalHandler& boundsChanged) + { + return m_boundsChanged.connectLast(boundsChanged); + } + void removeBoundsChangedCallback(SignalHandlerId id) + { + m_boundsChanged.disconnect(id); + } + + TypeId getNodeTypeId(const char* name) + { + return m_nodeTypeIds.getTypeId(name); + } + + TypeId getInstanceTypeId(const char* name) + { + return m_instanceTypeIds.getTypeId(name); + } + +private: + + bool pre(const Walker& walker, const InstanceMap::iterator& i) + { + return walker.pre(i->first, *i->second); + } + + void post(const Walker& walker, const InstanceMap::iterator& i) + { + walker.post(i->first, *i->second); + } + + void traverse_subgraph(const Walker& walker, InstanceMap::iterator i) + { + Stack stack; + if(i != m_instances.end()) + { + const std::size_t startSize = (*i).first.get().size(); + do + { + if(i != m_instances.end() + && stack.size() < ((*i).first.get().size() - startSize + 1)) + { + stack.push(i); + ++i; + if(!pre(walker, stack.top())) + { + // skip subgraph + while(i != m_instances.end() + && stack.size() < ((*i).first.get().size() - startSize + 1)) + { + ++i; + } + } + } + else + { + post(walker, stack.top()); + stack.pop(); + } + } + while(!stack.empty()); + } + } +}; + +namespace +{ + CompiledGraph* g_sceneGraph; + GraphTreeModel* g_tree_model; +} + +GraphTreeModel* scene_graph_get_tree_model() +{ + return g_tree_model; +} + + +class SceneGraphObserver : public scene::Instantiable::Observer +{ +public: + void insert(scene::Instance* instance) + { + g_sceneGraph->sceneChanged(); + graph_tree_model_insert(g_tree_model, *instance); + } + void erase(scene::Instance* instance) + { + g_sceneGraph->sceneChanged(); + graph_tree_model_erase(g_tree_model, *instance); + } +}; + +SceneGraphObserver g_SceneGraphObserver; + +void SceneGraph_Construct() +{ + g_tree_model = graph_tree_model_new(); + + g_sceneGraph = new CompiledGraph(&g_SceneGraphObserver); +} + +void SceneGraph_Destroy() +{ + delete g_sceneGraph; + + graph_tree_model_delete(g_tree_model); +} + + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +class SceneGraphAPI +{ + scene::Graph* m_scenegraph; +public: + typedef scene::Graph Type; + STRING_CONSTANT(Name, "*"); + + SceneGraphAPI() + { + SceneGraph_Construct(); + + m_scenegraph = g_sceneGraph; + } + ~SceneGraphAPI() + { + SceneGraph_Destroy(); + } + scene::Graph* getTable() + { + return m_scenegraph; + } +}; + +typedef SingletonModule SceneGraphModule; +typedef Static StaticSceneGraphModule; +StaticRegisterModule staticRegisterSceneGraph(StaticSceneGraphModule::instance()); + diff --git a/radiant/scenegraph.h b/radiant/scenegraph.h new file mode 100644 index 00000000..d269f8b4 --- /dev/null +++ b/radiant/scenegraph.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDEDE_SCENEGRAPH_H) +#define INCLUDED_SCENEGRAPH_H + +#endif diff --git a/radiant/select.cpp b/radiant/select.cpp new file mode 100644 index 00000000..e84ef152 --- /dev/null +++ b/radiant/select.cpp @@ -0,0 +1,1216 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "select.h" + +#include "debugging/debugging.h" + +#include "ientity.h" +#include "iselection.h" +#include "iundo.h" + +#include + +#include "stream/stringstream.h" +#include "signal/isignal.h" +#include "shaderlib.h" +#include "scenelib.h" + +#include "gtkutil/idledraw.h" +#include "gtkutil/dialog.h" +#include "gtkutil/widget.h" +#include "brushmanip.h" +#include "brush.h" +#include "patchmanip.h" +#include "patchdialog.h" +#include "selection.h" +#include "texwindow.h" +#include "gtkmisc.h" +#include "mainframe.h" +#include "grid.h" +#include "map.h" + + + +select_workzone_t g_select_workzone; + + +/** + Loops over all selected brushes and stores their + world AABBs in the specified array. +*/ +class CollectSelectedBrushesBounds : public SelectionSystem::Visitor +{ + AABB* m_bounds; // array of AABBs + Unsigned m_max; // max AABB-elements in array + Unsigned& m_count;// count of valid AABBs stored in array + +public: + CollectSelectedBrushesBounds(AABB* bounds, Unsigned max, Unsigned& count) + : m_bounds(bounds), + m_max(max), + m_count(count) + { + m_count = 0; + } + + void visit(scene::Instance& instance) const + { + ASSERT_MESSAGE(m_count <= m_max, "Invalid m_count in CollectSelectedBrushesBounds"); + + // stop if the array is already full + if(m_count == m_max) + return; + + Selectable* selectable = Instance_getSelectable(instance); + if((selectable != 0) + && instance.isSelected()) + { + // brushes only + if(Instance_getBrush(instance) != 0) + { + m_bounds[m_count] = instance.worldAABB(); + ++m_count; + } + } + } +}; + +/** + Selects all objects that intersect one of the bounding AABBs. + The exact intersection-method is specified through TSelectionPolicy +*/ +template +class SelectByBounds : public scene::Graph::Walker +{ + AABB* m_aabbs; // selection aabbs + Unsigned m_count; // number of aabbs in m_aabbs + TSelectionPolicy policy; // type that contains a custom intersection method aabb<->aabb + +public: + SelectByBounds(AABB* aabbs, Unsigned count) + : m_aabbs(aabbs), + m_count(count) + { + } + + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + + // ignore worldspawn + Entity* entity = Node_getEntity(path.top()); + if(entity) + { + if(string_equal(entity->getKeyValue("classname"), "worldspawn")) + return true; + } + + if( (path.size() > 1) && + (!path.top().get().isRoot()) && + (selectable != 0) + ) + { + for(Unsigned i = 0; i < m_count; ++i) + { + if(policy.Evaluate(m_aabbs[i], instance)) + { + selectable->setSelected(true); + } + } + } + + return true; + } + + /** + Performs selection operation on the global scenegraph. + If delete_bounds_src is true, then the objects which were + used as source for the selection aabbs will be deleted. +*/ + static void DoSelection(bool delete_bounds_src = true) + { + if(GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) + { + // we may not need all AABBs since not all selected objects have to be brushes + const Unsigned max = (Unsigned)GlobalSelectionSystem().countSelected(); + AABB* aabbs = new AABB[max]; + + Unsigned count; + CollectSelectedBrushesBounds collector(aabbs, max, count); + GlobalSelectionSystem().foreachSelected(collector); + + // nothing usable in selection + if(!count) + { + delete[] aabbs; + return; + } + + // delete selected objects + if(delete_bounds_src)// see deleteSelection + { + UndoableCommand undo("deleteSelected"); + Select_Delete(); + } + + // select objects with bounds + GlobalSceneGraph().traverse(SelectByBounds(aabbs, count)); + + SceneChangeNotify(); + delete[] aabbs; + } + } +}; + +/** + SelectionPolicy for SelectByBounds + Returns true if box and the AABB of instance intersect +*/ +class SelectionPolicy_Touching +{ +public: + bool Evaluate(const AABB& box, scene::Instance& instance) const + { + const AABB& other(instance.worldAABB()); + for(Unsigned i = 0; i < 3; ++i) + { + if(fabsf(box.origin[i] - other.origin[i]) > (box.extents[i] + other.extents[i])) + return false; + } + return true; + } +}; + +/** + SelectionPolicy for SelectByBounds + Returns true if the AABB of instance is inside box +*/ +class SelectionPolicy_Inside +{ +public: + bool Evaluate(const AABB& box, scene::Instance& instance) const + { + const AABB& other(instance.worldAABB()); + for(Unsigned i = 0; i < 3; ++i) + { + if(fabsf(box.origin[i] - other.origin[i]) > (box.extents[i] - other.extents[i])) + return false; + } + return true; + } +}; + +class DeleteSelected : public scene::Graph::Walker +{ + mutable bool m_remove; + mutable bool m_removedChild; +public: + DeleteSelected() + : m_remove(false), m_removedChild(false) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + m_removedChild = false; + + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 + && selectable->isSelected() + && path.size() > 1 + && !path.top().get().isRoot()) + { + m_remove = true; + + return false;// dont traverse into child elements + } + return true; + } + void post(const scene::Path& path, scene::Instance& instance) const + { + + if(m_removedChild) + { + m_removedChild = false; + + // delete empty entities + Entity* entity = Node_getEntity(path.top()); + if(entity != 0 + && path.top().get_pointer() != Map_FindWorldspawn(g_map) + && Node_getTraversable(path.top())->empty()) + { + Path_deleteTop(path); + } + } + + // node should be removed + if(m_remove) + { + if(Node_isEntity(path.parent()) != 0) + { + m_removedChild = true; + } + + m_remove = false; + Path_deleteTop(path); + } + } +}; + +void Scene_DeleteSelected(scene::Graph& graph) +{ + graph.traverse(DeleteSelected()); + SceneChangeNotify(); +} + +void Select_Delete (void) +{ + Scene_DeleteSelected(GlobalSceneGraph()); +} + +class InvertSelectionWalker : public scene::Graph::Walker +{ + SelectionSystem::EMode m_mode; + mutable Selectable* m_selectable; +public: + InvertSelectionWalker(SelectionSystem::EMode mode) + : m_mode(mode), m_selectable(0) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable) + { + switch(m_mode) + { + case SelectionSystem::eEntity: + if(Node_isEntity(path.top()) != 0) + { + m_selectable = path.top().get().visible() ? selectable : 0; + } + break; + case SelectionSystem::ePrimitive: + m_selectable = path.top().get().visible() ? selectable : 0; + break; + case SelectionSystem::eComponent: + break; + } + } + return true; + } + void post(const scene::Path& path, scene::Instance& instance) const + { + if(m_selectable != 0) + { + m_selectable->setSelected(!m_selectable->isSelected()); + m_selectable = 0; + } + } +}; + +void Scene_Invert_Selection(scene::Graph& graph) +{ + graph.traverse(InvertSelectionWalker(GlobalSelectionSystem().Mode())); +} + +void Select_Invert() +{ + Scene_Invert_Selection(GlobalSceneGraph()); +} + +class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker +{ + mutable std::size_t m_depth; +public: + ExpandSelectionToEntitiesWalker() : m_depth(0) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + ++m_depth; + if(m_depth == 2) // entity depth + { + // traverse and select children if any one is selected + if(instance.childSelected()) + Instance_setSelected(instance, true); + return Node_getEntity(path.top())->isContainer() && instance.childSelected(); + } + else if(m_depth == 3) // primitive depth + { + Instance_setSelected(instance, true); + return false; + } + return true; + } + void post(const scene::Path& path, scene::Instance& instance) const + { + --m_depth; + } +}; + +void Scene_ExpandSelectionToEntities() +{ + GlobalSceneGraph().traverse(ExpandSelectionToEntitiesWalker()); +} + + +namespace +{ + void Selection_UpdateWorkzone() + { + if(GlobalSelectionSystem().countSelected() != 0) + { + Select_GetBounds(g_select_workzone.d_work_min, g_select_workzone.d_work_max); + } + } + typedef FreeCaller SelectionUpdateWorkzoneCaller; + + IdleDraw g_idleWorkzone = IdleDraw(SelectionUpdateWorkzoneCaller()); +} + +const select_workzone_t& Select_getWorkZone() +{ + g_idleWorkzone.flush(); + return g_select_workzone; +} + +void UpdateWorkzone_ForSelection() +{ + g_idleWorkzone.queueDraw(); +} + +// update the workzone to the current selection +void UpdateWorkzone_ForSelectionChanged(const Selectable& selectable) +{ + if(selectable.isSelected()) + { + UpdateWorkzone_ForSelection(); + } +} + +void Select_SetShader(const char* shader) +{ + if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) + { + Scene_BrushSetShader_Selected(GlobalSceneGraph(), shader); + Scene_PatchSetShader_Selected(GlobalSceneGraph(), shader); + } + Scene_BrushSetShader_Component_Selected(GlobalSceneGraph(), shader); +} + +void Select_SetTexdef(const TextureProjection& projection) +{ + if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) + { + Scene_BrushSetTexdef_Selected(GlobalSceneGraph(), projection); + } + Scene_BrushSetTexdef_Component_Selected(GlobalSceneGraph(), projection); +} + +void Select_SetFlags(const ContentsFlagsValue& flags) +{ + if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) + { + Scene_BrushSetFlags_Selected(GlobalSceneGraph(), flags); + } + Scene_BrushSetFlags_Component_Selected(GlobalSceneGraph(), flags); +} + +void Select_GetBounds (Vector3& mins, Vector3& maxs) +{ + AABB bounds; + Scene_BoundsSelected(GlobalSceneGraph(), bounds); + maxs = vector3_added(bounds.origin, bounds.extents); + mins = vector3_subtracted(bounds.origin, bounds.extents); +} + +void Select_GetMid (Vector3& mid) +{ + AABB bounds; + Scene_BoundsSelected(GlobalSceneGraph(), bounds); + mid = vector3_snapped(bounds.origin); +} + + +void Select_FlipAxis (int axis) +{ + Vector3 flip(1, 1, 1); + flip[axis] = -1; + GlobalSelectionSystem().scaleSelected(flip); +} + + +void Select_Scale(float x, float y, float z) +{ + GlobalSelectionSystem().scaleSelected(Vector3(x, y, z)); +} + +enum axis_t +{ + eAxisX = 0, + eAxisY = 1, + eAxisZ = 2, +}; + +enum sign_t +{ + eSignPositive = 1, + eSignNegative = -1, +}; + +inline Matrix4 matrix4_rotation_for_axis90(axis_t axis, sign_t sign) +{ + switch(axis) + { + case eAxisX: + if(sign == eSignPositive) + { + return matrix4_rotation_for_sincos_x(1, 0); + } + else + { + return matrix4_rotation_for_sincos_x(-1, 0); + } + case eAxisY: + if(sign == eSignPositive) + { + return matrix4_rotation_for_sincos_y(1, 0); + } + else + { + return matrix4_rotation_for_sincos_y(-1, 0); + } + default://case eAxisZ: + if(sign == eSignPositive) + { + return matrix4_rotation_for_sincos_z(1, 0); + } + else + { + return matrix4_rotation_for_sincos_z(-1, 0); + } + } +} + +inline void matrix4_rotate_by_axis90(Matrix4& matrix, axis_t axis, sign_t sign) +{ + matrix4_multiply_by_matrix4(matrix, matrix4_rotation_for_axis90(axis, sign)); +} + +inline void matrix4_pivoted_rotate_by_axis90(Matrix4& matrix, axis_t axis, sign_t sign, const Vector3& pivotpoint) +{ + matrix4_translate_by_vec3(matrix, pivotpoint); + matrix4_rotate_by_axis90(matrix, axis, sign); + matrix4_translate_by_vec3(matrix, vector3_negated(pivotpoint)); +} + +inline Quaternion quaternion_for_axis90(axis_t axis, sign_t sign) +{ +#if 1 + switch(axis) + { + case eAxisX: + if(sign == eSignPositive) + { + return Quaternion(c_half_sqrt2f, 0, 0, c_half_sqrt2f); + } + else + { + return Quaternion(-c_half_sqrt2f, 0, 0, -c_half_sqrt2f); + } + case eAxisY: + if(sign == eSignPositive) + { + return Quaternion(0, c_half_sqrt2f, 0, c_half_sqrt2f); + } + else + { + return Quaternion(0, -c_half_sqrt2f, 0, -c_half_sqrt2f); + } + default://case eAxisZ: + if(sign == eSignPositive) + { + return Quaternion(0, 0, c_half_sqrt2f, c_half_sqrt2f); + } + else + { + return Quaternion(0, 0, -c_half_sqrt2f, -c_half_sqrt2f); + } + } +#else + quaternion_for_matrix4_rotation(matrix4_rotation_for_axis90((axis_t)axis, (deg > 0) ? eSignPositive : eSignNegative)); +#endif +} + +void Select_RotateAxis (int axis, float deg) +{ + if(fabs(deg) == 90.f) + { + GlobalSelectionSystem().rotateSelected(quaternion_for_axis90((axis_t)axis, (deg > 0) ? eSignPositive : eSignNegative)); + } + else + { + switch(axis) + { + case 0: + GlobalSelectionSystem().rotateSelected(quaternion_for_matrix4_rotation(matrix4_rotation_for_x_degrees(deg))); + break; + case 1: + GlobalSelectionSystem().rotateSelected(quaternion_for_matrix4_rotation(matrix4_rotation_for_y_degrees(deg))); + break; + case 2: + GlobalSelectionSystem().rotateSelected(quaternion_for_matrix4_rotation(matrix4_rotation_for_z_degrees(deg))); + break; + } + } +} + + +void Select_ShiftTexture(float x, float y) +{ + if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) + { + Scene_BrushShiftTexdef_Selected(GlobalSceneGraph(), x, y); + Scene_PatchTranslateTexture_Selected(GlobalSceneGraph(), x, y); + } + //globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n'; + Scene_BrushShiftTexdef_Component_Selected(GlobalSceneGraph(), x, y); +} + +void Select_ScaleTexture(float x, float y) +{ + if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) + { + Scene_BrushScaleTexdef_Selected(GlobalSceneGraph(), x, y); + Scene_PatchScaleTexture_Selected(GlobalSceneGraph(), x, y); + } + Scene_BrushScaleTexdef_Component_Selected(GlobalSceneGraph(), x, y); +} + +void Select_RotateTexture(float amt) +{ + if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) + { + Scene_BrushRotateTexdef_Selected(GlobalSceneGraph(), amt); + Scene_PatchRotateTexture_Selected(GlobalSceneGraph(), amt); + } + Scene_BrushRotateTexdef_Component_Selected(GlobalSceneGraph(), amt); +} + +// TTimo modified to handle shader architecture: +// expects shader names at input, comparison relies on shader names .. texture names no longer relevant +void FindReplaceTextures(const char* pFind, const char* pReplace, bool bSelected) +{ + if(!texdef_name_valid(pFind)) + { + globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n"; + return; + } + if(!texdef_name_valid(pReplace)) + { + globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n"; + return; + } + + StringOutputStream command; + command << "textureFindReplace -find " << pFind << " -replace " << pReplace; + UndoableCommand undo(command.c_str()); + + if(bSelected) + { + if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) + { + Scene_BrushFindReplaceShader_Selected(GlobalSceneGraph(), pFind, pReplace); + Scene_PatchFindReplaceShader_Selected(GlobalSceneGraph(), pFind, pReplace); + } + Scene_BrushFindReplaceShader_Component_Selected(GlobalSceneGraph(), pFind, pReplace); + } + else + { + Scene_BrushFindReplaceShader(GlobalSceneGraph(), pFind, pReplace); + Scene_PatchFindReplaceShader(GlobalSceneGraph(), pFind, pReplace); + } +} + +typedef std::vector Classnames; + +bool classnames_match_entity(const Classnames& classnames, Entity* entity) +{ + for(Classnames::const_iterator i = classnames.begin(); i != classnames.end(); ++i) + { + if(string_equal(entity->getKeyValue("classname"), *i)) + { + return true; + } + } + return false; +} + +class EntityFindByClassnameWalker : public scene::Graph::Walker +{ + const Classnames& m_classnames; +public: + EntityFindByClassnameWalker(const Classnames& classnames) + : m_classnames(classnames) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Entity* entity = Node_getEntity(path.top()); + if(entity != 0 + && classnames_match_entity(m_classnames, entity)) + { + Instance_getSelectable(instance)->setSelected(true); + } + return true; + } +}; + +void Scene_EntitySelectByClassnames(scene::Graph& graph, const Classnames& classnames) +{ + graph.traverse(EntityFindByClassnameWalker(classnames)); +} + +class EntityGetSelectedClassnamesWalker : public scene::Graph::Walker +{ + Classnames& m_classnames; +public: + EntityGetSelectedClassnamesWalker(Classnames& classnames) + : m_classnames(classnames) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 + && selectable->isSelected()) + { + Entity* entity = Node_getEntity(path.top()); + if(entity != 0) + { + m_classnames.push_back(entity->getKeyValue("classname")); + } + } + return true; + } +}; + +void Scene_EntityGetClassnames(scene::Graph& graph, Classnames& classnames) +{ + graph.traverse(EntityGetSelectedClassnamesWalker(classnames)); +} + +void Select_AllOfType() +{ + if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent) + { + if(GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace) + { + GlobalSelectionSystem().setSelectedAllComponents(false); + Scene_BrushSelectByShader_Component(GlobalSceneGraph(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser())); + } + } + else + { + Classnames classnames; + Scene_EntityGetClassnames(GlobalSceneGraph(), classnames); + GlobalSelectionSystem().setSelectedAll(false); + if(!classnames.empty()) + { + Scene_EntitySelectByClassnames(GlobalSceneGraph(), classnames); + } + else + { + Scene_BrushSelectByShader(GlobalSceneGraph(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser())); + Scene_PatchSelectByShader(GlobalSceneGraph(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser())); + } + } +} + +void Select_Inside(void) +{ + SelectByBounds::DoSelection(); +} + +void Select_Touching(void) +{ + SelectByBounds::DoSelection(false); +} + +void Select_FitTexture(float horizontal, float vertical) +{ + if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent) + { + Scene_BrushFitTexture_Selected(GlobalSceneGraph(), horizontal, vertical); + } + Scene_BrushFitTexture_Component_Selected(GlobalSceneGraph(), horizontal, vertical); + + SceneChangeNotify(); +} + +inline void hide_node(scene::Node& node, bool hide) +{ + hide + ? node.enable(scene::Node::eHidden) + : node.disable(scene::Node::eHidden); +} + +class HideSelectedWalker : public scene::Graph::Walker +{ + bool m_hide; +public: + HideSelectedWalker(bool hide) + : m_hide(hide) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 + && selectable->isSelected()) + { + hide_node(path.top(), m_hide); + } + return true; + } +}; + +void Scene_Hide_Selected(bool hide) +{ + GlobalSceneGraph().traverse(HideSelectedWalker(hide)); +} + +void Select_Hide() +{ + Scene_Hide_Selected(true); + SceneChangeNotify(); +} + +void HideSelected() +{ + Select_Hide(); + GlobalSelectionSystem().setSelectedAll(false); +} + + +class HideAllWalker : public scene::Graph::Walker +{ + bool m_hide; +public: + HideAllWalker(bool hide) + : m_hide(hide) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + hide_node(path.top(), m_hide); + return true; + } +}; + +void Scene_Hide_All(bool hide) +{ + GlobalSceneGraph().traverse(HideAllWalker(hide)); +} + +void Select_ShowAllHidden() +{ + Scene_Hide_All(false); + SceneChangeNotify(); +} + + + +void Selection_Flipx() +{ + UndoableCommand undo("mirrorSelected -axis x"); + Select_FlipAxis(0); +} + +void Selection_Flipy() +{ + UndoableCommand undo("mirrorSelected -axis y"); + Select_FlipAxis(1); +} + +void Selection_Flipz() +{ + UndoableCommand undo("mirrorSelected -axis z"); + Select_FlipAxis(2); +} + +void Selection_Rotatex() +{ + UndoableCommand undo("rotateSelected -axis x -angle -90"); + Select_RotateAxis(0,-90); +} + +void Selection_Rotatey() +{ + UndoableCommand undo("rotateSelected -axis y -angle 90"); + Select_RotateAxis(1, 90); +} + +void Selection_Rotatez() +{ + UndoableCommand undo("rotateSelected -axis z -angle -90"); + Select_RotateAxis(2,-90); +} + + + +void Nudge(int nDim, float fNudge) +{ + Vector3 translate(0, 0, 0); + translate[nDim] = fNudge; + + GlobalSelectionSystem().translateSelected(translate); +} + +void Selection_NudgeZ(float amount) +{ + StringOutputStream command; + command << "nudgeSelected -axis z -amount " << amount; + UndoableCommand undo(command.c_str()); + + Nudge(2, amount); +} + +void Selection_MoveDown() +{ + Selection_NudgeZ(-GetGridSize()); +} + +void Selection_MoveUp() +{ + Selection_NudgeZ(GetGridSize()); +} + +void SceneSelectionChange(const Selectable& selectable) +{ + SceneChangeNotify(); +} + +SignalHandlerId Selection_boundsChanged; + +void Selection_construct() +{ + typedef FreeCaller1 SceneSelectionChangeCaller; + GlobalSelectionSystem().addSelectionChangeCallback(SceneSelectionChangeCaller()); + typedef FreeCaller1 UpdateWorkzoneForSelectionChangedCaller; + GlobalSelectionSystem().addSelectionChangeCallback(UpdateWorkzoneForSelectionChangedCaller()); + typedef FreeCaller UpdateWorkzoneForSelectionCaller; + Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback(UpdateWorkzoneForSelectionCaller()); +} + +void Selection_destroy() +{ + GlobalSceneGraph().removeBoundsChangedCallback(Selection_boundsChanged); +} + + +#include "gtkdlgs.h" +#include +#include +#include +#include +#include + + +inline Quaternion quaternion_for_euler_xyz_degrees(const Vector3& eulerXYZ) +{ +#if 0 + return quaternion_for_matrix4_rotation(matrix4_rotation_for_euler_xyz_degrees(eulerXYZ)); +#elif 0 + return quaternion_multiplied_by_quaternion( + quaternion_multiplied_by_quaternion( + quaternion_for_z(degrees_to_radians(eulerXYZ[2])), + quaternion_for_y(degrees_to_radians(eulerXYZ[1])) + ), + quaternion_for_x(degrees_to_radians(eulerXYZ[0])) + ); +#elif 1 + double cx = cos(degrees_to_radians(eulerXYZ[0] * 0.5)); + double sx = sin(degrees_to_radians(eulerXYZ[0] * 0.5)); + double cy = cos(degrees_to_radians(eulerXYZ[1] * 0.5)); + double sy = sin(degrees_to_radians(eulerXYZ[1] * 0.5)); + double cz = cos(degrees_to_radians(eulerXYZ[2] * 0.5)); + double sz = sin(degrees_to_radians(eulerXYZ[2] * 0.5)); + + return Quaternion( + cz * cy * sx - sz * sy * cx, + cz * sy * cx + sz * cy * sx, + sz * cy * cx - cz * sy * sx, + cz * cy * cx + sz * sy * sx + ); +#endif +} + +struct RotateDialog +{ + GtkSpinButton* x; + GtkSpinButton* y; + GtkSpinButton* z; +}; + +static void rotatedlg_apply (GtkWidget *widget, RotateDialog* rotateDialog) +{ + Vector3 eulerXYZ; + + eulerXYZ[0] = static_cast(gtk_spin_button_get_value(rotateDialog->x)); + gtk_spin_button_set_value(rotateDialog->x, 0.0f); // reset to 0 on Apply + + eulerXYZ[1] = static_cast(gtk_spin_button_get_value(rotateDialog->y)); + gtk_spin_button_set_value(rotateDialog->y, 0.0f); + + eulerXYZ[2] = static_cast(gtk_spin_button_get_value(rotateDialog->z)); + gtk_spin_button_set_value(rotateDialog->z, 0.0f); + + StringOutputStream command; + command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2]; + UndoableCommand undo(command.c_str()); + + GlobalSelectionSystem().rotateSelected(quaternion_for_euler_xyz_degrees(eulerXYZ)); +} + +void DoRotateDlg() +{ + ModalDialog dialog; + RotateDialog rotateDialog; + + GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Arbitrary rotation", G_CALLBACK(dialog_delete_callback), &dialog); + + GtkAccelGroup* accel = gtk_accel_group_new(); + gtk_window_add_accel_group(window, accel); + + { + GtkHBox* hbox = create_dialog_hbox(4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox)); + { + GtkTable* table = create_dialog_table(3, 2, 4, 4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(table), TRUE, TRUE, 0); + { + GtkWidget* label = gtk_label_new (" X "); + gtk_widget_show (label); + gtk_table_attach(table, label, 0, 1, 0, 1, + (GtkAttachOptions) (0), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkWidget* label = gtk_label_new (" Y "); + gtk_widget_show (label); + gtk_table_attach(table, label, 0, 1, 1, 2, + (GtkAttachOptions) (0), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkWidget* label = gtk_label_new (" Z "); + gtk_widget_show (label); + gtk_table_attach(table, label, 0, 1, 2, 3, + (GtkAttachOptions) (0), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -359, 359, 1, 10, 10)); + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0)); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_size_request(GTK_WIDGET(spin), 64, -1); + gtk_spin_button_set_wrap(spin, TRUE); + + gtk_widget_grab_focus(GTK_WIDGET(spin)); + + rotateDialog.x = spin; + } + { + GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -359, 359, 1, 10, 10)); + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0)); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_size_request(GTK_WIDGET(spin), 64, -1); + gtk_spin_button_set_wrap(spin, TRUE); + + rotateDialog.y = spin; + } + { + GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -359, 359, 1, 10, 10)); + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0)); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 2, 3, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_size_request(GTK_WIDGET(spin), 64, -1); + gtk_spin_button_set_wrap(spin, TRUE); + + rotateDialog.z = spin; + } + } + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0); + { + GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0); + } + { + GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &dialog); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0); + } + { + GtkButton* button = create_dialog_button("Apply", G_CALLBACK(rotatedlg_apply), &rotateDialog); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + } + } + } + + if(modal_dialog_show(window, dialog) == eIDOK) + { + rotatedlg_apply(0, &rotateDialog); + } + + gtk_widget_destroy(GTK_WIDGET(window)); +} + +void DoScaleDlg() +{ + ModalDialog dialog; + GtkWidget* x; + GtkWidget* y; + GtkWidget* z; + + GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Scale", G_CALLBACK(dialog_delete_callback), &dialog); + + GtkAccelGroup* accel = gtk_accel_group_new(); + gtk_window_add_accel_group(window, accel); + + { + GtkHBox* hbox = create_dialog_hbox(4, 4); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox)); + { + GtkTable* table = create_dialog_table(3, 2, 4, 4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(table), TRUE, TRUE, 0); + { + GtkWidget* label = gtk_label_new ("X:"); + gtk_widget_show (label); + gtk_table_attach(table, label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + } + { + GtkWidget* label = gtk_label_new ("Y:"); + gtk_widget_show (label); + gtk_table_attach(table, label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + } + { + GtkWidget* label = gtk_label_new ("Z:"); + gtk_widget_show (label); + gtk_table_attach(table, label, 0, 1, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + } + { + GtkWidget* entry = gtk_entry_new(); + gtk_widget_show (entry); + gtk_table_attach(table, entry, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + gtk_widget_grab_focus(entry); + + x = entry; + } + { + GtkWidget* entry = gtk_entry_new(); + gtk_widget_show (entry); + gtk_table_attach(table, entry, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + y = entry; + } + { + GtkWidget* entry = gtk_entry_new(); + gtk_widget_show (entry); + gtk_table_attach(table, entry, 1, 2, 2, 3, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + z = entry; + } + } + { + GtkVBox* vbox = create_dialog_vbox(4); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0); + { + GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + widget_make_default(GTK_WIDGET(button)); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0); + } + { + GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &dialog); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0); + gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0); + } + } + } + + // Initialize dialog + gtk_entry_set_text (GTK_ENTRY (x), "1.0"); + gtk_entry_set_text (GTK_ENTRY (y), "1.0"); + gtk_entry_set_text (GTK_ENTRY (z), "1.0"); + + if(modal_dialog_show(window, dialog) == eIDOK) + { + float sx, sy, sz; + sx = static_cast(atof(gtk_entry_get_text (GTK_ENTRY (x)))); + sy = static_cast(atof(gtk_entry_get_text (GTK_ENTRY (y)))); + sz = static_cast(atof(gtk_entry_get_text (GTK_ENTRY (z)))); + + if (sx > 0 && sy > 0 && sz > 0) + { + StringOutputStream command; + command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz; + UndoableCommand undo(command.c_str()); + + Select_Scale(sx, sy, sz); + } + else + { + globalOutputStream() << "Warning.. Tried to scale by a zero value."; + } + } + + gtk_widget_destroy(GTK_WIDGET(window)); +} diff --git a/radiant/select.h b/radiant/select.h new file mode 100644 index 00000000..5a019f7c --- /dev/null +++ b/radiant/select.h @@ -0,0 +1,91 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_SELECT_H) +#define INCLUDED_SELECT_H + +#include "math/vector.h" + +void Select_GetBounds(Vector3& mins, Vector3& maxs); +void Select_GetMid(Vector3& mid); + +void Select_Delete(); +void Select_Invert(); +void Select_Inside(); +void Select_Touching(); +void Scene_ExpandSelectionToEntities(); + +void Selection_Flipx(); +void Selection_Flipy(); +void Selection_Flipz(); +void Selection_Rotatex(); +void Selection_Rotatey(); +void Selection_Rotatez(); + + +void Selection_MoveDown(); +void Selection_MoveUp(); + +void Select_AllOfType(); + +void DoRotateDlg(); +void DoScaleDlg(); + + +void Select_SetShader(const char* shader); + +class TextureProjection; +void Select_SetTexdef(const TextureProjection& projection); + +class ContentsFlagsValue; +void Select_SetFlags(const ContentsFlagsValue& flags); + +void Select_RotateTexture(float amt); +void Select_ScaleTexture(float x, float y); +void Select_ShiftTexture(float x, float y); +void Select_FitTexture(float horizontal = 1, float vertical = 1); +void FindReplaceTextures(const char* pFind, const char* pReplace, bool bSelected); + +void HideSelected(); +void Select_ShowAllHidden(); + +// updating workzone to a given brush (depends on current view) + +void Selection_construct(); +void Selection_destroy(); + + +struct select_workzone_t +{ + // defines the boundaries of the current work area + // is used to guess brushes and drop points third coordinate when creating from 2D view + Vector3 d_work_min, d_work_max; + + select_workzone_t() : + d_work_min(-64.0f,-64.0f,-64.0f), + d_work_max( 64.0f, 64.0f, 64.0f) + { + } +}; + +const select_workzone_t& Select_getWorkZone(); + +#endif diff --git a/radiant/selection.cpp b/radiant/selection.cpp new file mode 100644 index 00000000..d3995312 --- /dev/null +++ b/radiant/selection.cpp @@ -0,0 +1,4141 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "selection.h" + +#include "debugging/debugging.h" + +#include +#include +#include + +#include "windowobserver.h" +#include "iundo.h" +#include "ientity.h" +#include "cullable.h" +#include "renderable.h" +#include "selectable.h" +#include "editable.h" + +#include "math/frustum.h" +#include "signal/signal.h" +#include "generic/object.h" +#include "selectionlib.h" +#include "render.h" +#include "view.h" +#include "renderer.h" +#include "stream/stringstream.h" +#include "eclasslib.h" +#include "generic/bitfield.h" +#include "generic/static.h" +#include "pivot.h" +#include "stringio.h" +#include "container/container.h" + +#include "grid.h" + +struct Pivot2World +{ + Matrix4 m_worldSpace; + Matrix4 m_viewpointSpace; + Matrix4 m_viewplaneSpace; + Vector3 m_axis_screen; + + void update(const Matrix4& pivot2world, const Matrix4& modelview, const Matrix4& projection, const Matrix4& viewport) + { + Pivot2World_worldSpace(m_worldSpace, pivot2world, modelview, projection, viewport); + Pivot2World_viewpointSpace(m_viewpointSpace, m_axis_screen, pivot2world, modelview, projection, viewport); + Pivot2World_viewplaneSpace(m_viewplaneSpace, pivot2world, modelview, projection, viewport); + } +}; + + +void point_for_device_point(Vector3& point, const Matrix4& device2object, const float x, const float y, const float z) +{ + // transform from normalised device coords to object coords + point = vector4_projected(matrix4_transformed_vector4(device2object, Vector4(x, y, z, 1))); +} + +void ray_for_device_point(Ray& ray, const Matrix4& device2object, const float x, const float y) +{ + // point at x, y, zNear + point_for_device_point(ray.origin, device2object, x, y, -1); + + // point at x, y, zFar + point_for_device_point(ray.direction, device2object, x, y, 1); + + // construct ray + vector3_subtract(ray.direction, ray.origin); + vector3_normalise(ray.direction); +} + +bool sphere_intersect_ray(const Vector3& origin, float radius, const Ray& ray, Vector3& intersection) +{ + intersection = vector3_subtracted(origin, ray.origin); + const double a = vector3_dot(intersection, ray.direction); + const double d = radius * radius - (vector3_dot(intersection, intersection) - a * a); + + if(d > 0) + { + intersection = vector3_added(ray.origin, vector3_scaled(ray.direction, a - sqrt(d))); + return true; + } + else + { + intersection = vector3_added( ray.origin, vector3_scaled(ray.direction, a)); + return false; + } +} + +void ray_intersect_ray(const Ray& ray, const Ray& other, Vector3& intersection) +{ + intersection = vector3_subtracted(ray.origin, other.origin); + //float a = 1;//vector3_dot(ray.direction, ray.direction); // always >= 0 + double dot = vector3_dot(ray.direction, other.direction); + //float c = 1;//vector3_dot(other.direction, other.direction); // always >= 0 + double d = vector3_dot(ray.direction, intersection); + double e = vector3_dot(other.direction, intersection); + double D = 1 - dot*dot;//a*c - dot*dot; // always >= 0 + + if (D < 0.000001) + { + // the lines are almost parallel + intersection = vector3_added(other.origin, vector3_scaled(other.direction, e)); + } + else + { + intersection = vector3_added(other.origin, vector3_scaled(other.direction, (e - dot*d) / D)); + } +} + +const Vector3 g_origin(0, 0, 0); +const float g_radius = 64; + +void point_on_sphere(Vector3& point, const Matrix4& device2object, const float x, const float y) +{ + Ray ray; + ray_for_device_point(ray, device2object, x, y); + sphere_intersect_ray(g_origin, g_radius, ray, point); +} + +void point_on_axis(Vector3& point, const Vector3& axis, const Matrix4& device2object, const float x, const float y) +{ + Ray ray; + ray_for_device_point(ray, device2object, x, y); + ray_intersect_ray(ray, Ray(Vector3(0, 0, 0), axis), point); +} + +void point_on_plane(Vector3& point, const Matrix4& device2object, const float x, const float y) +{ + Matrix4 object2device(matrix4_full_inverse(device2object)); + point = vector4_projected(matrix4_transformed_vector4(device2object, Vector4(x, y, object2device[14] / object2device[15], 1))); +} + +//! a and b are unit vectors .. returns angle in radians +inline float angle_between(const Vector3& a, const Vector3& b) +{ + return static_cast(2.0 * atan2( + vector3_length(vector3_subtracted(a, b)), + vector3_length(vector3_added(a, b)) + )); +} + + +#if defined(_DEBUG) +class test_quat +{ +public: + test_quat(const Vector3& from, const Vector3& to) + { + Vector4 quaternion(quaternion_for_unit_vectors(from, to)); + Matrix4 matrix(matrix4_rotation_for_quaternion(quaternion_multiplied_by_quaternion(quaternion, c_quaternion_identity))); + } +private: +}; + +static test_quat bleh(g_vector3_axis_x, g_vector3_axis_y); +#endif + +//! axis is a unit vector +inline void constrain_to_axis(Vector3& vec, const Vector3& axis) +{ + vec = vector3_normalised(vector3_added(vec, vector3_scaled(axis, -vector3_dot(vec, axis)))); +} + +//! a and b are unit vectors .. a and b must be orthogonal to axis .. returns angle in radians +float angle_for_axis(const Vector3& a, const Vector3& b, const Vector3& axis) +{ + if(vector3_dot(axis, vector3_cross(a, b)) > 0.0) + return angle_between(a, b); + else + return -angle_between(a, b); +} + +float distance_for_axis(const Vector3& a, const Vector3& b, const Vector3& axis) +{ + return static_cast(vector3_dot(b, axis) - vector3_dot(a, axis)); +} + +class Manipulatable +{ +public: + virtual void Construct(const Matrix4& device2manip, const float x, const float y) = 0; + virtual void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) = 0; +}; + +void transform_local2object(Matrix4& object, const Matrix4& local, const Matrix4& local2object) +{ + object = matrix4_multiplied_by_matrix4( + matrix4_multiplied_by_matrix4(local2object, local), + matrix4_full_inverse(local2object) + ); +} + +class Rotatable +{ +public: + virtual void rotate(const Quaternion& rotation) = 0; +}; + +class RotateFree : public Manipulatable +{ + Vector3 m_start; + Rotatable& m_rotatable; +public: + RotateFree(Rotatable& rotatable) + : m_rotatable(rotatable) + { + } + void Construct(const Matrix4& device2manip, const float x, const float y) + { + point_on_sphere(m_start, device2manip, x, y); + vector3_normalise(m_start); + } + void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) + { + Vector3 current; + + point_on_sphere(current, device2manip, x, y); + vector3_normalise(current); + + m_rotatable.rotate(quaternion_for_unit_vectors(m_start, current)); + } +}; + +class RotateAxis : public Manipulatable +{ + Vector3 m_axis; + Vector3 m_start; + Rotatable& m_rotatable; +public: + RotateAxis(Rotatable& rotatable) + : m_rotatable(rotatable) + { + } + void Construct(const Matrix4& device2manip, const float x, const float y) + { + point_on_sphere(m_start, device2manip, x, y); + constrain_to_axis(m_start, m_axis); + } + /// \brief Converts current position to a normalised vector orthogonal to axis. + void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) + { + Vector3 current; + point_on_sphere(current, device2manip, x, y); + constrain_to_axis(current, m_axis); + + m_rotatable.rotate(quaternion_for_axisangle(m_axis, angle_for_axis(m_start, current, m_axis))); + } + + void SetAxis(const Vector3& axis) + { + m_axis = axis; + } +}; + +void translation_local2object(Vector3& object, const Vector3& local, const Matrix4& local2object) +{ + object = matrix4_get_translation_vec3( + matrix4_multiplied_by_matrix4( + matrix4_translated_by_vec3(local2object, local), + matrix4_full_inverse(local2object) + ) + ); +} + +class Translatable +{ +public: + virtual void translate(const Vector3& translation) = 0; +}; + +class TranslateAxis : public Manipulatable +{ + Vector3 m_start; + Vector3 m_axis; + Translatable& m_translatable; +public: + TranslateAxis(Translatable& translatable) + : m_translatable(translatable) + { + } + void Construct(const Matrix4& device2manip, const float x, const float y) + { + point_on_axis(m_start, m_axis, device2manip, x, y); + } + void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) + { + Vector3 current; + point_on_axis(current, m_axis, device2manip, x, y); + current = vector3_scaled(m_axis, distance_for_axis(m_start, current, m_axis)); + + translation_local2object(current, current, manip2object); + vector3_snap(current, GetGridSize()); + + m_translatable.translate(current); + } + + void SetAxis(const Vector3& axis) + { + m_axis = axis; + } +}; + +class TranslateFree : public Manipulatable +{ +private: + Vector3 m_start; + Translatable& m_translatable; +public: + TranslateFree(Translatable& translatable) + : m_translatable(translatable) + { + } + void Construct(const Matrix4& device2manip, const float x, const float y) + { + point_on_plane(m_start, device2manip, x, y); + } + void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) + { + Vector3 current; + point_on_plane(current, device2manip, x, y); + current = vector3_subtracted(current, m_start); + + translation_local2object(current, current, manip2object); + vector3_snap(current, GetGridSize()); + + m_translatable.translate(current); + } +}; + + +class Scalable +{ +public: + virtual void scale(const Vector3& scaling) = 0; +}; + + +class ScaleAxis : public Manipulatable +{ +private: + Vector3 m_start; + Vector3 m_axis; + Scalable& m_scalable; +public: + ScaleAxis(Scalable& scalable) + : m_scalable(scalable) + { + } + void Construct(const Matrix4& device2manip, const float x, const float y) + { + point_on_axis(m_start, m_axis, device2manip, x, y); + } + void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) + { + Vector3 current; + point_on_axis(current, m_axis, device2manip, x, y); + Vector3 delta = vector3_subtracted(current, m_start); + + translation_local2object(delta, delta, manip2object); + vector3_snap(delta, GetGridSize()); + + Vector3 start(vector3_snapped(m_start, GetGridSize())); + Vector3 scale( + start[0] == 0 ? 1 : 1 + delta[0] / start[0], + start[1] == 0 ? 1 : 1 + delta[1] / start[1], + start[2] == 0 ? 1 : 1 + delta[2] / start[2] + ); + m_scalable.scale(scale); + } + + void SetAxis(const Vector3& axis) + { + m_axis = axis; + } +}; + +class ScaleFree : public Manipulatable +{ +private: + Vector3 m_start; + Scalable& m_scalable; +public: + ScaleFree(Scalable& scalable) + : m_scalable(scalable) + { + } + void Construct(const Matrix4& device2manip, const float x, const float y) + { + point_on_plane(m_start, device2manip, x, y); + } + void Transform(const Matrix4& manip2object, const Matrix4& device2manip, const float x, const float y) + { + Vector3 current; + point_on_plane(current, device2manip, x, y); + Vector3 delta = vector3_subtracted(current, m_start); + + translation_local2object(delta, delta, manip2object); + vector3_snap(delta, GetGridSize()); + + Vector3 start(vector3_snapped(m_start, GetGridSize())); + Vector3 scale( + start[0] == 0 ? 1 : 1 + delta[0] / start[0], + start[1] == 0 ? 1 : 1 + delta[1] / start[1], + start[2] == 0 ? 1 : 1 + delta[2] / start[2] + ); + m_scalable.scale(scale); + } +}; + + + + + + + + + + +class RenderableClippedPrimitive : public OpenGLRenderable +{ + struct primitive_t + { + PointVertex m_points[9]; + std::size_t m_count; + }; + Matrix4 m_inverse; + std::vector m_primitives; +public: + Matrix4 m_world; + + void render(RenderStateFlags state) const + { + for(std::size_t i=0; i P[1])) // an upward crossing + || (((*prev)[1] > P[1]) && ((*cur)[1] <= P[1]))) + { // a downward crossing + // compute the actual edge-ray intersect x-coordinate + float vt = (float)(P[1] - (*prev)[1]) / ((*cur)[1] - (*prev)[1]); + if (P[0] < (*prev)[0] + vt * ((*cur)[0] - (*prev)[0])) // P[0] < intersect + { + ++crossings; // a valid crossing of y=P[1] right of P[0] + } + } + } + return (crossings & 0x1) != 0; // 0 if even (out), and 1 if odd (in) +} + +inline double triangle_signed_area_XY(const Vector3& p0, const Vector3& p1, const Vector3& p2) +{ + return ((p1[0] - p0[0]) * (p2[1] - p0[1])) - ((p2[0] - p0[0]) * (p1[1] - p0[1])); +} + +enum clipcull_t +{ + eClipCullNone, + eClipCullCW, + eClipCullCCW, +}; + + +inline SelectionIntersection select_point_from_clipped(Vector4& clipped) +{ + return SelectionIntersection(clipped[2] / clipped[3], static_cast(vector3_length_squared(Vector3(clipped[0] / clipped[3], clipped[1] / clipped[3], 0)))); +} + +void BestPoint(std::size_t count, Vector4 clipped[9], SelectionIntersection& best, clipcull_t cull) +{ + Vector3 normalised[9]; + + { + for(std::size_t i=0; i 2) + { + double signed_area = triangle_signed_area_XY(normalised[0], normalised[1], normalised[2]); + + if((cull == eClipCullCW && signed_area > 0) + || (cull == eClipCullCCW && signed_area < 0)) + return; + } + + if(count == 2) + { + Segment3D segment(normalised[0], normalised[1]); + Point3D point = segment_closest_point_to_point(segment, Vector3(0, 0, 0)); + assign_if_closer(best, SelectionIntersection(point.z(), 0)); + } + else if(count > 2 && !point_test_polygon_2d(Vector3(0, 0, 0), normalised, normalised + count)) + { + point_iterator_t end = normalised + count; + for(point_iterator_t previous = end-1, current = normalised; current != end; previous = current, ++current) + { + Segment3D segment(*previous, *current); + Point3D point = segment_closest_point_to_point(segment, Vector3(0, 0, 0)); + float depth = point.z(); + point.z() = 0; + float distance = static_cast(vector3_length_squared(point)); + + assign_if_closer(best, SelectionIntersection(depth, distance)); + } + } + else if(count > 2) + { + assign_if_closer( + best, + SelectionIntersection( + static_cast(ray_distance_to_plane( + Ray(Vector3(0, 0, 0), Vector3(0, 0, 1)), + plane3_for_points(normalised[0], normalised[1], normalised[2]) + )), + 0 + ) + ); + } + +#if defined(DEBUG_SELECTION) + if(count >= 2) + g_render_clipped.insert(clipped, count); +#endif +} + +void LineStrip_BestPoint(const Matrix4& local2view, const PointVertex* vertices, const std::size_t size, SelectionIntersection& best) +{ + Vector4 clipped[2]; + for(std::size_t i = 0; (i + 1) < size; ++i) + { + const std::size_t count = matrix4_clip_line(local2view, vertex3f_to_vector3(vertices[i].vertex), vertex3f_to_vector3(vertices[i + 1].vertex), clipped); + BestPoint(count, clipped, best, eClipCullNone); + } +} + +void LineLoop_BestPoint(const Matrix4& local2view, const PointVertex* vertices, const std::size_t size, SelectionIntersection& best) +{ + Vector4 clipped[2]; + for(std::size_t i = 0; i < size; ++i) + { + const std::size_t count = matrix4_clip_line(local2view, vertex3f_to_vector3(vertices[i].vertex), vertex3f_to_vector3(vertices[(i+1)%size].vertex), clipped); + BestPoint(count, clipped, best, eClipCullNone); + } +} + +void Line_BestPoint(const Matrix4& local2view, const PointVertex vertices[2], SelectionIntersection& best) +{ + Vector4 clipped[2]; + const std::size_t count = matrix4_clip_line(local2view, vertex3f_to_vector3(vertices[0].vertex), vertex3f_to_vector3(vertices[1].vertex), clipped); + BestPoint(count, clipped, best, eClipCullNone); +} + +void Circle_BestPoint(const Matrix4& local2view, clipcull_t cull, const PointVertex* vertices, const std::size_t size, SelectionIntersection& best) +{ + Vector4 clipped[9]; + for(std::size_t i=0; i((*x).vertex), + reinterpret_cast((*y).vertex), + reinterpret_cast((*z).vertex), + clipped + ), + clipped, + best, + cull + ); + } +} + + +typedef std::multimap SelectableSortedSet; + +class SelectionPool : public Selector +{ + SelectableSortedSet m_pool; + SelectionIntersection m_intersection; + Selectable* m_selectable; + +public: + void pushSelectable(Selectable& selectable) + { + m_intersection = SelectionIntersection(); + m_selectable = &selectable; + } + void popSelectable() + { + addSelectable(m_intersection, m_selectable); + m_intersection = SelectionIntersection(); + } + void addIntersection(const SelectionIntersection& intersection) + { + assign_if_closer(m_intersection, intersection); + } + void addSelectable(const SelectionIntersection& intersection, Selectable* selectable) + { + if(intersection.valid()) + { + m_pool.insert(SelectableSortedSet::value_type(intersection, selectable)); + } + } + + typedef SelectableSortedSet::iterator iterator; + + iterator begin() + { + return m_pool.begin(); + } + iterator end() + { + return m_pool.end(); + } + + bool failed() + { + return m_pool.empty(); + } +}; + + +const Colour4b g_colour_sphere(0, 0, 0, 255); +const Colour4b g_colour_screen(0, 255, 255, 255); +const Colour4b g_colour_selected(255, 255, 0, 255); + +inline const Colour4b& colourSelected(const Colour4b& colour, bool selected) +{ + return (selected) ? g_colour_selected : colour; +} + +template +inline void draw_semicircle(const std::size_t segments, const float radius, PointVertex* vertices, remap_policy remap) +{ + const double increment = c_pi / double(segments << 2); + + std::size_t count = 0; + float x = radius; + float y = 0; + remap_policy::set(vertices[segments << 2].vertex, -radius, 0, 0); + while(count < segments) + { + PointVertex* i = vertices + count; + PointVertex* j = vertices + ((segments << 1) - (count + 1)); + + PointVertex* k = i + (segments << 1); + PointVertex* l = j + (segments << 1); + +#if 0 + PointVertex* m = i + (segments << 2); + PointVertex* n = j + (segments << 2); + PointVertex* o = k + (segments << 2); + PointVertex* p = l + (segments << 2); +#endif + + remap_policy::set(i->vertex, x,-y, 0); + remap_policy::set(k->vertex,-y,-x, 0); +#if 0 + remap_policy::set(m->vertex,-x, y, 0); + remap_policy::set(o->vertex, y, x, 0); +#endif + + ++count; + + { + const double theta = increment * count; + x = static_cast(radius * cos(theta)); + y = static_cast(radius * sin(theta)); + } + + remap_policy::set(j->vertex, y,-x, 0); + remap_policy::set(l->vertex,-x,-y, 0); +#if 0 + remap_policy::set(n->vertex,-y, x, 0); + remap_policy::set(p->vertex, x, y, 0); +#endif + } +} + +class Manipulator +{ +public: + virtual Manipulatable* GetManipulatable() = 0; + virtual void testSelect(const View& view, const Matrix4& pivot2world) + { + } + virtual void render(Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world) + { + } + virtual void setSelected(bool select) = 0; + virtual bool isSelected() const = 0; +}; + + +inline Vector3 normalised_safe(const Vector3& self) +{ + if(vector3_equal(self, g_vector3_identity)) + { + return g_vector3_identity; + } + return vector3_normalised(self); +} + + +class RotateManipulator : public Manipulator +{ + struct RenderableCircle : public OpenGLRenderable + { + Array m_vertices; + + RenderableCircle(std::size_t size) : m_vertices(size) + { + } + void render(RenderStateFlags state) const + { + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_vertices.data()->colour); + glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_vertices.data()->vertex); + glDrawArrays(GL_LINE_LOOP, 0, GLsizei(m_vertices.size())); + } + void setColour(const Colour4b& colour) + { + for(Array::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i) + { + (*i).colour = colour; + } + } + }; + + struct RenderableSemiCircle : public OpenGLRenderable + { + Array m_vertices; + + RenderableSemiCircle(std::size_t size) : m_vertices(size) + { + } + void render(RenderStateFlags state) const + { + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_vertices.data()->colour); + glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_vertices.data()->vertex); + glDrawArrays(GL_LINE_STRIP, 0, GLsizei(m_vertices.size())); + } + void setColour(const Colour4b& colour) + { + for(Array::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i) + { + (*i).colour = colour; + } + } + }; + + RotateFree m_free; + RotateAxis m_axis; + Vector3 m_axis_screen; + RenderableSemiCircle m_circle_x; + RenderableSemiCircle m_circle_y; + RenderableSemiCircle m_circle_z; + RenderableCircle m_circle_screen; + RenderableCircle m_circle_sphere; + SelectableBool m_selectable_x; + SelectableBool m_selectable_y; + SelectableBool m_selectable_z; + SelectableBool m_selectable_screen; + SelectableBool m_selectable_sphere; + Pivot2World m_pivot; + Matrix4 m_local2world_x; + Matrix4 m_local2world_y; + Matrix4 m_local2world_z; + bool m_circle_x_visible; + bool m_circle_y_visible; + bool m_circle_z_visible; +public: + static Shader* m_state_outer; + + RotateManipulator(Rotatable& rotatable, std::size_t segments, float radius) : + m_free(rotatable), + m_axis(rotatable), + m_circle_x((segments << 2) + 1), + m_circle_y((segments << 2) + 1), + m_circle_z((segments << 2) + 1), + m_circle_screen(segments<<3), + m_circle_sphere(segments<<3) + { + draw_semicircle(segments, radius, m_circle_x.m_vertices.data(), RemapYZX()); + draw_semicircle(segments, radius, m_circle_y.m_vertices.data(), RemapZXY()); + draw_semicircle(segments, radius, m_circle_z.m_vertices.data(), RemapXYZ()); + + draw_circle(segments, radius * 1.15f, m_circle_screen.m_vertices.data(), RemapXYZ()); + draw_circle(segments, radius, m_circle_sphere.m_vertices.data(), RemapXYZ()); + } + + + void UpdateColours() + { + m_circle_x.setColour(colourSelected(g_colour_x, m_selectable_x.isSelected())); + m_circle_y.setColour(colourSelected(g_colour_y, m_selectable_y.isSelected())); + m_circle_z.setColour(colourSelected(g_colour_z, m_selectable_z.isSelected())); + m_circle_screen.setColour(colourSelected(g_colour_screen, m_selectable_screen.isSelected())); + m_circle_sphere.setColour(colourSelected(g_colour_sphere, false)); + } + + void updateCircleTransforms() + { + Vector3 localViewpoint(matrix4_transformed_direction(matrix4_transposed(m_pivot.m_worldSpace), vector4_to_vector3(m_pivot.m_viewpointSpace.z()))); + + m_circle_x_visible = !vector3_equal_epsilon(g_vector3_axis_x, localViewpoint, 1e-6f); + if(m_circle_x_visible) + { + m_local2world_x = g_matrix4_identity; + vector4_to_vector3(m_local2world_x.y()) = normalised_safe( + vector3_cross(g_vector3_axis_x, localViewpoint) + ); + vector4_to_vector3(m_local2world_x.z()) = normalised_safe( + vector3_cross(vector4_to_vector3(m_local2world_x.x()), vector4_to_vector3(m_local2world_x.y())) + ); + matrix4_premultiply_by_matrix4(m_local2world_x, m_pivot.m_worldSpace); + } + + m_circle_y_visible = !vector3_equal_epsilon(g_vector3_axis_y, localViewpoint, 1e-6f); + if(m_circle_y_visible) + { + m_local2world_y = g_matrix4_identity; + vector4_to_vector3(m_local2world_y.z()) = normalised_safe( + vector3_cross(g_vector3_axis_y, localViewpoint) + ); + vector4_to_vector3(m_local2world_y.x()) = normalised_safe( + vector3_cross(vector4_to_vector3(m_local2world_y.y()), vector4_to_vector3(m_local2world_y.z())) + ); + matrix4_premultiply_by_matrix4(m_local2world_y, m_pivot.m_worldSpace); + } + + m_circle_z_visible = !vector3_equal_epsilon(g_vector3_axis_z, localViewpoint, 1e-6f); + if(m_circle_z_visible) + { + m_local2world_z = g_matrix4_identity; + vector4_to_vector3(m_local2world_z.x()) = normalised_safe( + vector3_cross(g_vector3_axis_z, localViewpoint) + ); + vector4_to_vector3(m_local2world_z.y()) = normalised_safe( + vector3_cross(vector4_to_vector3(m_local2world_z.z()), vector4_to_vector3(m_local2world_z.x())) + ); + matrix4_premultiply_by_matrix4(m_local2world_z, m_pivot.m_worldSpace); + } + } + + void render(Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world) + { + m_pivot.update(pivot2world, volume.GetModelview(), volume.GetProjection(), volume.GetViewport()); + updateCircleTransforms(); + + // temp hack + UpdateColours(); + + renderer.SetState(m_state_outer, Renderer::eWireframeOnly); + renderer.SetState(m_state_outer, Renderer::eFullMaterials); + + renderer.addRenderable(m_circle_screen, m_pivot.m_viewpointSpace); + renderer.addRenderable(m_circle_sphere, m_pivot.m_viewpointSpace); + + if(m_circle_x_visible) + { + renderer.addRenderable(m_circle_x, m_local2world_x); + } + if(m_circle_y_visible) + { + renderer.addRenderable(m_circle_y, m_local2world_y); + } + if(m_circle_z_visible) + { + renderer.addRenderable(m_circle_z, m_local2world_z); + } + } + void testSelect(const View& view, const Matrix4& pivot2world) + { + m_pivot.update(pivot2world, view.GetModelview(), view.GetProjection(), view.GetViewport()); + updateCircleTransforms(); + + SelectionPool selector; + + { + { + Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_local2world_x)); + +#if defined(DEBUG_SELECTION) + g_render_clipped.construct(view.GetViewMatrix()); +#endif + + SelectionIntersection best; + LineStrip_BestPoint(local2view, m_circle_x.m_vertices.data(), m_circle_x.m_vertices.size(), best); + selector.addSelectable(best, &m_selectable_x); + } + + { + Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_local2world_y)); + +#if defined(DEBUG_SELECTION) + g_render_clipped.construct(view.GetViewMatrix()); +#endif + + SelectionIntersection best; + LineStrip_BestPoint(local2view, m_circle_y.m_vertices.data(), m_circle_y.m_vertices.size(), best); + selector.addSelectable(best, &m_selectable_y); + } + + { + Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_local2world_z)); + +#if defined(DEBUG_SELECTION) + g_render_clipped.construct(view.GetViewMatrix()); +#endif + + SelectionIntersection best; + LineStrip_BestPoint(local2view, m_circle_z.m_vertices.data(), m_circle_z.m_vertices.size(), best); + selector.addSelectable(best, &m_selectable_z); + } + } + + { + Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_pivot.m_viewpointSpace)); + + { + SelectionIntersection best; + LineLoop_BestPoint(local2view, m_circle_screen.m_vertices.data(), m_circle_screen.m_vertices.size(), best); + selector.addSelectable(best, &m_selectable_screen); + } + + { + SelectionIntersection best; + Circle_BestPoint(local2view, eClipCullCW, m_circle_sphere.m_vertices.data(), m_circle_sphere.m_vertices.size(), best); + selector.addSelectable(best, &m_selectable_sphere); + } + } + + m_axis_screen = m_pivot.m_axis_screen; + + if(!selector.failed()) + { + (*selector.begin()).second->setSelected(true); + } + } + + Manipulatable* GetManipulatable() + { + if(m_selectable_x.isSelected()) + { + m_axis.SetAxis(g_vector3_axis_x); + return &m_axis; + } + else if(m_selectable_y.isSelected()) + { + m_axis.SetAxis(g_vector3_axis_y); + return &m_axis; + } + else if(m_selectable_z.isSelected()) + { + m_axis.SetAxis(g_vector3_axis_z); + return &m_axis; + } + else if(m_selectable_screen.isSelected()) + { + m_axis.SetAxis(m_axis_screen); + return &m_axis; + } + else + return &m_free; + } + + void setSelected(bool select) + { + m_selectable_x.setSelected(select); + m_selectable_y.setSelected(select); + m_selectable_z.setSelected(select); + m_selectable_screen.setSelected(select); + } + bool isSelected() const + { + return m_selectable_x.isSelected() + | m_selectable_y.isSelected() + | m_selectable_z.isSelected() + | m_selectable_screen.isSelected() + | m_selectable_sphere.isSelected(); + } +}; + +Shader* RotateManipulator::m_state_outer; + + +const float arrowhead_length = 16; +const float arrowhead_radius = 4; + +inline void draw_arrowline(const float length, PointVertex* line, const std::size_t axis) +{ + (*line++).vertex = vertex3f_identity; + (*line).vertex = vertex3f_identity; + vertex3f_to_array((*line).vertex)[axis] = length - arrowhead_length; +} + +template +inline void draw_arrowhead(const std::size_t segments, const float length, FlatShadedVertex* vertices, VertexRemap, NormalRemap) +{ + std::size_t head_tris = (segments << 3); + const double head_segment = c_2pi / head_tris; + for(std::size_t i = 0; i < head_tris; ++i) + { + { + FlatShadedVertex& point = vertices[i*6+0]; + VertexRemap::x(point.vertex) = length - arrowhead_length; + VertexRemap::y(point.vertex) = arrowhead_radius * static_cast(cos(i * head_segment)); + VertexRemap::z(point.vertex) = arrowhead_radius * static_cast(sin(i * head_segment)); + NormalRemap::x(point.normal) = arrowhead_radius / arrowhead_length; + NormalRemap::y(point.normal) = static_cast(cos(i * head_segment)); + NormalRemap::z(point.normal) = static_cast(sin(i * head_segment)); + } + { + FlatShadedVertex& point = vertices[i*6+1]; + VertexRemap::x(point.vertex) = length; + VertexRemap::y(point.vertex) = 0; + VertexRemap::z(point.vertex) = 0; + NormalRemap::x(point.normal) = arrowhead_radius / arrowhead_length; + NormalRemap::y(point.normal) = static_cast(cos((i + 0.5) * head_segment)); + NormalRemap::z(point.normal) = static_cast(sin((i + 0.5) * head_segment)); + } + { + FlatShadedVertex& point = vertices[i*6+2]; + VertexRemap::x(point.vertex) = length - arrowhead_length; + VertexRemap::y(point.vertex) = arrowhead_radius * static_cast(cos((i+1) * head_segment)); + VertexRemap::z(point.vertex) = arrowhead_radius * static_cast(sin((i+1) * head_segment)); + NormalRemap::x(point.normal) = arrowhead_radius / arrowhead_length; + NormalRemap::y(point.normal) = static_cast(cos((i+1) * head_segment)); + NormalRemap::z(point.normal) = static_cast(sin((i+1) * head_segment)); + } + + { + FlatShadedVertex& point = vertices[i*6+3]; + VertexRemap::x(point.vertex) = length - arrowhead_length; + VertexRemap::y(point.vertex) = 0; + VertexRemap::z(point.vertex) = 0; + NormalRemap::x(point.normal) = -1; + NormalRemap::y(point.normal) = 0; + NormalRemap::z(point.normal) = 0; + } + { + FlatShadedVertex& point = vertices[i*6+4]; + VertexRemap::x(point.vertex) = length - arrowhead_length; + VertexRemap::y(point.vertex) = arrowhead_radius * static_cast(cos(i * head_segment)); + VertexRemap::z(point.vertex) = arrowhead_radius * static_cast(sin(i * head_segment)); + NormalRemap::x(point.normal) = -1; + NormalRemap::y(point.normal) = 0; + NormalRemap::z(point.normal) = 0; + } + { + FlatShadedVertex& point = vertices[i*6+5]; + VertexRemap::x(point.vertex) = length - arrowhead_length; + VertexRemap::y(point.vertex) = arrowhead_radius * static_cast(cos((i+1) * head_segment)); + VertexRemap::z(point.vertex) = arrowhead_radius * static_cast(sin((i+1) * head_segment)); + NormalRemap::x(point.normal) = -1; + NormalRemap::y(point.normal) = 0; + NormalRemap::z(point.normal) = 0; + } + } +} + +template +class TripleRemapXYZ +{ +public: + static float& x(Triple& triple) + { + return triple.x(); + } + static float& y(Triple& triple) + { + return triple.y(); + } + static float& z(Triple& triple) + { + return triple.z(); + } +}; + +template +class TripleRemapYZX +{ +public: + static float& x(Triple& triple) + { + return triple.y(); + } + static float& y(Triple& triple) + { + return triple.z(); + } + static float& z(Triple& triple) + { + return triple.x(); + } +}; + +template +class TripleRemapZXY +{ +public: + static float& x(Triple& triple) + { + return triple.z(); + } + static float& y(Triple& triple) + { + return triple.x(); + } + static float& z(Triple& triple) + { + return triple.y(); + } +}; + +void vector3_print(const Vector3& v) +{ + globalOutputStream() << "( " << v.x() << " " << v.y() << " " << v.z() << " )"; +} + +class TranslateManipulator : public Manipulator +{ + struct RenderableArrowLine : public OpenGLRenderable + { + PointVertex m_line[2]; + + RenderableArrowLine() + { + } + void render(RenderStateFlags state) const + { + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_line[0].colour); + glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_line[0].vertex); + glDrawArrays(GL_LINES, 0, 2); + } + void setColour(const Colour4b& colour) + { + m_line[0].colour = colour; + m_line[1].colour = colour; + } + }; + struct RenderableArrowHead : public OpenGLRenderable + { + Array m_vertices; + + RenderableArrowHead(std::size_t size) + : m_vertices(size) + { + } + void render(RenderStateFlags state) const + { + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(FlatShadedVertex), &m_vertices.data()->colour); + glVertexPointer(3, GL_FLOAT, sizeof(FlatShadedVertex), &m_vertices.data()->vertex); + glNormalPointer(GL_FLOAT, sizeof(FlatShadedVertex), &m_vertices.data()->normal); + glDrawArrays(GL_TRIANGLES, 0, GLsizei(m_vertices.size())); + } + void setColour(const Colour4b& colour) + { + for(Array::iterator i = m_vertices.begin(); i != m_vertices.end(); ++i) + { + (*i).colour = colour; + } + } + }; + struct RenderableQuad : public OpenGLRenderable + { + PointVertex m_quad[4]; + void render(RenderStateFlags state) const + { + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_quad[0].colour); + glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_quad[0].vertex); + glDrawArrays(GL_LINE_LOOP, 0, 4); + } + void setColour(const Colour4b& colour) + { + m_quad[0].colour = colour; + m_quad[1].colour = colour; + m_quad[2].colour = colour; + m_quad[3].colour = colour; + } + }; + + TranslateFree m_free; + TranslateAxis m_axis; + RenderableArrowLine m_arrow_x; + RenderableArrowLine m_arrow_y; + RenderableArrowLine m_arrow_z; + RenderableArrowHead m_arrow_head_x; + RenderableArrowHead m_arrow_head_y; + RenderableArrowHead m_arrow_head_z; + RenderableQuad m_quad_screen; + SelectableBool m_selectable_x; + SelectableBool m_selectable_y; + SelectableBool m_selectable_z; + SelectableBool m_selectable_screen; + Pivot2World m_pivot; +public: + static Shader* m_state_wire; + static Shader* m_state_fill; + + TranslateManipulator(Translatable& translatable, std::size_t segments, float length) : + m_free(translatable), + m_axis(translatable), + m_arrow_head_x(3 * 2 * (segments << 3)), + m_arrow_head_y(3 * 2 * (segments << 3)), + m_arrow_head_z(3 * 2 * (segments << 3)) + { + draw_arrowline(length, m_arrow_x.m_line, 0); + draw_arrowhead(segments, length, m_arrow_head_x.m_vertices.data(), TripleRemapXYZ(), TripleRemapXYZ()); + draw_arrowline(length, m_arrow_y.m_line, 1); + draw_arrowhead(segments, length, m_arrow_head_y.m_vertices.data(), TripleRemapYZX(), TripleRemapYZX()); + draw_arrowline(length, m_arrow_z.m_line, 2); + draw_arrowhead(segments, length, m_arrow_head_z.m_vertices.data(), TripleRemapZXY(), TripleRemapZXY()); + + draw_quad(16, m_quad_screen.m_quad); + } + + void UpdateColours() + { + m_arrow_x.setColour(colourSelected(g_colour_x, m_selectable_x.isSelected())); + m_arrow_head_x.setColour(colourSelected(g_colour_x, m_selectable_x.isSelected())); + m_arrow_y.setColour(colourSelected(g_colour_y, m_selectable_y.isSelected())); + m_arrow_head_y.setColour(colourSelected(g_colour_y, m_selectable_y.isSelected())); + m_arrow_z.setColour(colourSelected(g_colour_z, m_selectable_z.isSelected())); + m_arrow_head_z.setColour(colourSelected(g_colour_z, m_selectable_z.isSelected())); + m_quad_screen.setColour(colourSelected(g_colour_screen, m_selectable_screen.isSelected())); + } + + bool manipulator_show_axis(const Pivot2World& pivot, const Vector3& axis) + { + return fabs(vector3_dot(pivot.m_axis_screen, axis)) < 0.95; + } + + void render(Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world) + { + m_pivot.update(pivot2world, volume.GetModelview(), volume.GetProjection(), volume.GetViewport()); + + // temp hack + UpdateColours(); + + Vector3 x = vector3_normalised(vector4_to_vector3(m_pivot.m_worldSpace.x())); + bool show_x = manipulator_show_axis(m_pivot, x); + + Vector3 y = vector3_normalised(vector4_to_vector3(m_pivot.m_worldSpace.y())); + bool show_y = manipulator_show_axis(m_pivot, y); + + Vector3 z = vector3_normalised(vector4_to_vector3(m_pivot.m_worldSpace.z())); + bool show_z = manipulator_show_axis(m_pivot, z); + + renderer.SetState(m_state_wire, Renderer::eWireframeOnly); + renderer.SetState(m_state_wire, Renderer::eFullMaterials); + + if(show_x) + { + renderer.addRenderable(m_arrow_x, m_pivot.m_worldSpace); + } + if(show_y) + { + renderer.addRenderable(m_arrow_y, m_pivot.m_worldSpace); + } + if(show_z) + { + renderer.addRenderable(m_arrow_z, m_pivot.m_worldSpace); + } + + renderer.addRenderable(m_quad_screen, m_pivot.m_viewplaneSpace); + + renderer.SetState(m_state_fill, Renderer::eWireframeOnly); + renderer.SetState(m_state_fill, Renderer::eFullMaterials); + + if(show_x) + { + renderer.addRenderable(m_arrow_head_x, m_pivot.m_worldSpace); + } + if(show_y) + { + renderer.addRenderable(m_arrow_head_y, m_pivot.m_worldSpace); + } + if(show_z) + { + renderer.addRenderable(m_arrow_head_z, m_pivot.m_worldSpace); + } + } + void testSelect(const View& view, const Matrix4& pivot2world) + { + m_pivot.update(pivot2world, view.GetModelview(), view.GetProjection(), view.GetViewport()); + + SelectionPool selector; + + Vector3 x = vector3_normalised(vector4_to_vector3(m_pivot.m_worldSpace.x())); + bool show_x = manipulator_show_axis(m_pivot, x); + + Vector3 y = vector3_normalised(vector4_to_vector3(m_pivot.m_worldSpace.y())); + bool show_y = manipulator_show_axis(m_pivot, y); + + Vector3 z = vector3_normalised(vector4_to_vector3(m_pivot.m_worldSpace.z())); + bool show_z = manipulator_show_axis(m_pivot, z); + + { + Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_pivot.m_viewpointSpace)); + + { + SelectionIntersection best; + Quad_BestPoint(local2view, eClipCullCW, m_quad_screen.m_quad, best); + if(best.valid()) + { + best = SelectionIntersection(0, 0); + selector.addSelectable(best, &m_selectable_screen); + } + } + } + + { + Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_pivot.m_worldSpace)); + +#if defined(DEBUG_SELECTION) + g_render_clipped.construct(view.GetViewMatrix()); +#endif + + if(show_x) + { + SelectionIntersection best; + Line_BestPoint(local2view, m_arrow_x.m_line, best); + Triangles_BestPoint(local2view, eClipCullCW, m_arrow_head_x.m_vertices.begin(), m_arrow_head_x.m_vertices.end(), best); + selector.addSelectable(best, &m_selectable_x); + } + + if(show_y) + { + SelectionIntersection best; + Line_BestPoint(local2view, m_arrow_y.m_line, best); + Triangles_BestPoint(local2view, eClipCullCW, m_arrow_head_y.m_vertices.begin(), m_arrow_head_y.m_vertices.end(), best); + selector.addSelectable(best, &m_selectable_y); + } + + if(show_z) + { + SelectionIntersection best; + Line_BestPoint(local2view, m_arrow_z.m_line, best); + Triangles_BestPoint(local2view, eClipCullCW, m_arrow_head_z.m_vertices.begin(), m_arrow_head_z.m_vertices.end(), best); + selector.addSelectable(best, &m_selectable_z); + } + } + + if(!selector.failed()) + { + (*selector.begin()).second->setSelected(true); + } + } + + Manipulatable* GetManipulatable() + { + if(m_selectable_x.isSelected()) + { + m_axis.SetAxis(g_vector3_axis_x); + return &m_axis; + } + else if(m_selectable_y.isSelected()) + { + m_axis.SetAxis(g_vector3_axis_y); + return &m_axis; + } + else if(m_selectable_z.isSelected()) + { + m_axis.SetAxis(g_vector3_axis_z); + return &m_axis; + } + else + { + return &m_free; + } + } + + void setSelected(bool select) + { + m_selectable_x.setSelected(select); + m_selectable_y.setSelected(select); + m_selectable_z.setSelected(select); + m_selectable_screen.setSelected(select); + } + bool isSelected() const + { + return m_selectable_x.isSelected() + | m_selectable_y.isSelected() + | m_selectable_z.isSelected() + | m_selectable_screen.isSelected(); + } +}; + +Shader* TranslateManipulator::m_state_wire; +Shader* TranslateManipulator::m_state_fill; + +class ScaleManipulator : public Manipulator +{ + struct RenderableArrow : public OpenGLRenderable + { + PointVertex m_line[2]; + + void render(RenderStateFlags state) const + { + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_line[0].colour); + glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_line[0].vertex); + glDrawArrays(GL_LINES, 0, 2); + } + void setColour(const Colour4b& colour) + { + m_line[0].colour = colour; + m_line[1].colour = colour; + } + }; + struct RenderableQuad : public OpenGLRenderable + { + PointVertex m_quad[4]; + void render(RenderStateFlags state) const + { + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(PointVertex), &m_quad[0].colour); + glVertexPointer(3, GL_FLOAT, sizeof(PointVertex), &m_quad[0].vertex); + glDrawArrays(GL_QUADS, 0, 4); + } + void setColour(const Colour4b& colour) + { + m_quad[0].colour = colour; + m_quad[1].colour = colour; + m_quad[2].colour = colour; + m_quad[3].colour = colour; + } + }; + + ScaleFree m_free; + ScaleAxis m_axis; + RenderableArrow m_arrow_x; + RenderableArrow m_arrow_y; + RenderableArrow m_arrow_z; + RenderableQuad m_quad_screen; + SelectableBool m_selectable_x; + SelectableBool m_selectable_y; + SelectableBool m_selectable_z; + SelectableBool m_selectable_screen; + Pivot2World m_pivot; +public: + ScaleManipulator(Scalable& scalable, std::size_t segments, float length) : + m_free(scalable), + m_axis(scalable) + { + draw_arrowline(length, m_arrow_x.m_line, 0); + draw_arrowline(length, m_arrow_y.m_line, 1); + draw_arrowline(length, m_arrow_z.m_line, 2); + + draw_quad(16, m_quad_screen.m_quad); + } + + Pivot2World& getPivot() + { + return m_pivot; + } + + void UpdateColours() + { + m_arrow_x.setColour(colourSelected(g_colour_x, m_selectable_x.isSelected())); + m_arrow_y.setColour(colourSelected(g_colour_y, m_selectable_y.isSelected())); + m_arrow_z.setColour(colourSelected(g_colour_z, m_selectable_z.isSelected())); + m_quad_screen.setColour(colourSelected(g_colour_screen, m_selectable_screen.isSelected())); + } + + void render(Renderer& renderer, const VolumeTest& volume, const Matrix4& pivot2world) + { + m_pivot.update(pivot2world, volume.GetModelview(), volume.GetProjection(), volume.GetViewport()); + + // temp hack + UpdateColours(); + + renderer.addRenderable(m_arrow_x, m_pivot.m_worldSpace); + renderer.addRenderable(m_arrow_y, m_pivot.m_worldSpace); + renderer.addRenderable(m_arrow_z, m_pivot.m_worldSpace); + + renderer.addRenderable(m_quad_screen, m_pivot.m_viewpointSpace); + } + void testSelect(const View& view, const Matrix4& pivot2world) + { + m_pivot.update(pivot2world, view.GetModelview(), view.GetProjection(), view.GetViewport()); + + SelectionPool selector; + + { + Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_pivot.m_worldSpace)); + +#if defined(DEBUG_SELECTION) + g_render_clipped.construct(view.GetViewMatrix()); +#endif + + { + SelectionIntersection best; + Line_BestPoint(local2view, m_arrow_x.m_line, best); + selector.addSelectable(best, &m_selectable_x); + } + + { + SelectionIntersection best; + Line_BestPoint(local2view, m_arrow_y.m_line, best); + selector.addSelectable(best, &m_selectable_y); + } + + { + SelectionIntersection best; + Line_BestPoint(local2view, m_arrow_z.m_line, best); + selector.addSelectable(best, &m_selectable_z); + } + } + + { + Matrix4 local2view(matrix4_multiplied_by_matrix4(view.GetViewMatrix(), m_pivot.m_viewpointSpace)); + + { + SelectionIntersection best; + Quad_BestPoint(local2view, eClipCullCW, m_quad_screen.m_quad, best); + selector.addSelectable(best, &m_selectable_screen); + } + } + + if(!selector.failed()) + { + (*selector.begin()).second->setSelected(true); + } + } + + Manipulatable* GetManipulatable() + { + if(m_selectable_x.isSelected()) + { + m_axis.SetAxis(g_vector3_axis_x); + return &m_axis; + } + else if(m_selectable_y.isSelected()) + { + m_axis.SetAxis(g_vector3_axis_y); + return &m_axis; + } + else if(m_selectable_z.isSelected()) + { + m_axis.SetAxis(g_vector3_axis_z); + return &m_axis; + } + else + return &m_free; + } + + void setSelected(bool select) + { + m_selectable_x.setSelected(select); + m_selectable_y.setSelected(select); + m_selectable_z.setSelected(select); + m_selectable_screen.setSelected(select); + } + bool isSelected() const + { + return m_selectable_x.isSelected() + | m_selectable_y.isSelected() + | m_selectable_z.isSelected() + | m_selectable_screen.isSelected(); + } +}; + + +inline PlaneSelectable* Instance_getPlaneSelectable(scene::Instance& instance) +{ + return InstanceTypeCast::cast(instance); +} + +class PlaneSelectableSelectPlanes : public scene::Graph::Walker +{ + Selector& m_selector; + SelectionTest& m_test; + PlaneCallback m_selectedPlaneCallback; +public: + PlaneSelectableSelectPlanes(Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback) + : m_selector(selector), m_test(test), m_selectedPlaneCallback(selectedPlaneCallback) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 && selectable->isSelected()) + { + PlaneSelectable* planeSelectable = Instance_getPlaneSelectable(instance); + if(planeSelectable != 0) + { + planeSelectable->selectPlanes(m_selector, m_test, m_selectedPlaneCallback); + } + } + } + return true; + } +}; + +class PlaneSelectableSelectReversedPlanes : public scene::Graph::Walker +{ + Selector& m_selector; + const SelectedPlanes& m_selectedPlanes; +public: + PlaneSelectableSelectReversedPlanes(Selector& selector, const SelectedPlanes& selectedPlanes) + : m_selector(selector), m_selectedPlanes(selectedPlanes) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 && selectable->isSelected()) + { + PlaneSelectable* planeSelectable = Instance_getPlaneSelectable(instance); + if(planeSelectable != 0) + { + planeSelectable->selectReversedPlanes(m_selector, m_selectedPlanes); + } + } + } + return true; + } +}; + +void Scene_forEachPlaneSelectable_selectPlanes(scene::Graph& graph, Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback) +{ + graph.traverse(PlaneSelectableSelectPlanes(selector, test, selectedPlaneCallback)); +} + +void Scene_forEachPlaneSelectable_selectReversedPlanes(scene::Graph& graph, Selector& selector, const SelectedPlanes& selectedPlanes) +{ + graph.traverse(PlaneSelectableSelectReversedPlanes(selector, selectedPlanes)); +} + + +class PlaneLess +{ +public: + bool operator()(const Plane3& plane, const Plane3& other) const + { + if(plane.a < other.a) + { + return true; + } + if(other.a < plane.a) + { + return false; + } + + if(plane.b < other.b) + { + return true; + } + if(other.b < plane.b) + { + return false; + } + + if(plane.c < other.c) + { + return true; + } + if(other.c < plane.c) + { + return false; + } + + if(plane.d < other.d) + { + return true; + } + if(other.d < plane.d) + { + return false; + } + + return false; + } +}; + +typedef std::set PlaneSet; + +inline void PlaneSet_insert(PlaneSet& self, const Plane3& plane) +{ + self.insert(plane); +} + +inline bool PlaneSet_contains(const PlaneSet& self, const Plane3& plane) +{ + return self.find(plane) != self.end(); +} + + +class SelectedPlaneSet : public SelectedPlanes +{ + PlaneSet m_selectedPlanes; +public: + bool empty() const + { + return m_selectedPlanes.empty(); + } + + void insert(const Plane3& plane) + { + PlaneSet_insert(m_selectedPlanes, plane); + } + bool contains(const Plane3& plane) const + { + return PlaneSet_contains(m_selectedPlanes, plane); + } + typedef MemberCaller1 InsertCaller; +}; + + +bool Scene_forEachPlaneSelectable_selectPlanes(scene::Graph& graph, Selector& selector, SelectionTest& test) +{ + SelectedPlaneSet selectedPlanes; + + Scene_forEachPlaneSelectable_selectPlanes(graph, selector, test, SelectedPlaneSet::InsertCaller(selectedPlanes)); + Scene_forEachPlaneSelectable_selectReversedPlanes(graph, selector, selectedPlanes); + + return !selectedPlanes.empty(); +} + +void Scene_Translate_Component_Selected(scene::Graph& graph, const Vector3& translation); +void Scene_Translate_Selected(scene::Graph& graph, const Vector3& translation); +void Scene_TestSelect_Primitive(Selector& selector, SelectionTest& test, const VolumeTest& volume); +void Scene_TestSelect_Component(Selector& selector, SelectionTest& test, const VolumeTest& volume, SelectionSystem::EComponentMode componentMode); +void Scene_TestSelect_Component_Selected(Selector& selector, SelectionTest& test, const VolumeTest& volume, SelectionSystem::EComponentMode componentMode); +void Scene_SelectAll_Component(bool select, SelectionSystem::EComponentMode componentMode); + +class ResizeTranslatable : public Translatable +{ + void translate(const Vector3& translation) + { + Scene_Translate_Component_Selected(GlobalSceneGraph(), translation); + } +}; + +class DragTranslatable : public Translatable +{ + void translate(const Vector3& translation) + { + if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent) + { + Scene_Translate_Component_Selected(GlobalSceneGraph(), translation); + } + else + { + Scene_Translate_Selected(GlobalSceneGraph(), translation); + } + } +}; + +class SelectionVolume : public SelectionTest +{ + Matrix4 m_local2view; + const View& m_view; + clipcull_t m_cull; + Vector3 m_near; + Vector3 m_far; +public: + SelectionVolume(const View& view) + : m_view(view) + { + } + + const VolumeTest& getVolume() const + { + return m_view; + } + + const Vector3& getNear() const + { + return m_near; + } + const Vector3& getFar() const + { + return m_far; + } + + void BeginMesh(const Matrix4& localToWorld, bool twoSided) + { + m_local2view = matrix4_multiplied_by_matrix4(m_view.GetViewMatrix(), localToWorld); + + // Cull back-facing polygons based on winding being clockwise or counter-clockwise. + // Don't cull if the view is wireframe and the polygons are two-sided. + m_cull = twoSided && !m_view.fill() ? eClipCullNone : (matrix4_handedness(localToWorld) == MATRIX4_RIGHTHANDED) ? eClipCullCW : eClipCullCCW; + + { + Matrix4 screen2world(matrix4_full_inverse(m_local2view)); + + m_near = vector4_projected( + matrix4_transformed_vector4( + screen2world, + Vector4(0, 0, -1, 1) + ) + ); + + m_far = vector4_projected( + matrix4_transformed_vector4( + screen2world, + Vector4(0, 0, 1, 1) + ) + ); + } + +#if defined(DEBUG_SELECTION) + g_render_clipped.construct(m_view.GetViewMatrix()); +#endif + } + void TestPoint(const Vector3& point, SelectionIntersection& best) + { + Vector4 clipped; + if(matrix4_clip_point(m_local2view, point, clipped) == c_CLIP_PASS) + { + best = select_point_from_clipped(clipped); + } + } + void TestPolygon(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) + { + Vector4 clipped[9]; + for(std::size_t i=0; i+2(vertices[0]), + reinterpret_cast(vertices[i+1]), + reinterpret_cast(vertices[i+2]), + clipped + ), + clipped, + best, + m_cull + ); + } + } + void TestLineLoop(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) + { + if(count == 0) + return; + Vector4 clipped[9]; + for(VertexPointer::iterator i = vertices.begin(), end = i + count, prev = i + (count-1); i != end; prev = i, ++i) + { + BestPoint( + matrix4_clip_line( + m_local2view, + reinterpret_cast((*prev)), + reinterpret_cast((*i)), + clipped + ), + clipped, + best, + m_cull + ); + } + } + void TestLineStrip(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) + { + if(count == 0) + return; + Vector4 clipped[9]; + for(VertexPointer::iterator i = vertices.begin(), end = i + count, next = i + 1; next != end; i = next, ++next) + { + BestPoint( + matrix4_clip_line( + m_local2view, + reinterpret_cast((*i)), + reinterpret_cast((*next)), + clipped + ), + clipped, + best, + m_cull + ); + } + } + void TestLines(const VertexPointer& vertices, std::size_t count, SelectionIntersection& best) + { + if(count == 0) + return; + Vector4 clipped[9]; + for(VertexPointer::iterator i = vertices.begin(), end = i + count; i != end; i += 2) + { + BestPoint( + matrix4_clip_line( + m_local2view, + reinterpret_cast((*i)), + reinterpret_cast((*(i+1))), + clipped + ), + clipped, + best, + m_cull + ); + } + } + void TestTriangles(const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best) + { + Vector4 clipped[9]; + for(IndexPointer::iterator i(indices.begin()); i != indices.end(); i += 3) + { + BestPoint( + matrix4_clip_triangle( + m_local2view, + reinterpret_cast(vertices[*i]), + reinterpret_cast(vertices[*(i+1)]), + reinterpret_cast(vertices[*(i+2)]), + clipped + ), + clipped, + best, + m_cull + ); + } + } + void TestQuads(const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best) + { + Vector4 clipped[9]; + for(IndexPointer::iterator i(indices.begin()); i != indices.end(); i += 4) + { + BestPoint( + matrix4_clip_triangle( + m_local2view, + reinterpret_cast(vertices[*i]), + reinterpret_cast(vertices[*(i+1)]), + reinterpret_cast(vertices[*(i+3)]), + clipped + ), + clipped, + best, + m_cull + ); + BestPoint( + matrix4_clip_triangle( + m_local2view, + reinterpret_cast(vertices[*(i+1)]), + reinterpret_cast(vertices[*(i+2)]), + reinterpret_cast(vertices[*(i+3)]), + clipped + ), + clipped, + best, + m_cull + ); + } + } + void TestQuadStrip(const VertexPointer& vertices, const IndexPointer& indices, SelectionIntersection& best) + { + Vector4 clipped[9]; + for(IndexPointer::iterator i(indices.begin()); i+2 != indices.end(); i += 2) + { + BestPoint( + matrix4_clip_triangle( + m_local2view, + reinterpret_cast(vertices[*i]), + reinterpret_cast(vertices[*(i+1)]), + reinterpret_cast(vertices[*(i+2)]), + clipped + ), + clipped, + best, + m_cull + ); + BestPoint( + matrix4_clip_triangle( + m_local2view, + reinterpret_cast(vertices[*(i+2)]), + reinterpret_cast(vertices[*(i+1)]), + reinterpret_cast(vertices[*(i+3)]), + clipped + ), + clipped, + best, + m_cull + ); + } + } +}; + +class SelectionCounter +{ +public: + typedef const Selectable& first_argument_type; + + SelectionCounter(const SelectionChangeCallback& onchanged) + : m_count(0), m_onchanged(onchanged) + { + } + void operator()(const Selectable& selectable) + { + if(selectable.isSelected()) + { + ++m_count; + } + else + { + ASSERT_MESSAGE(m_count != 0, "selection counter underflow"); + --m_count; + } + + m_onchanged(selectable); + } + bool empty() const + { + return m_count == 0; + } + std::size_t size() const + { + return m_count; + } +private: + std::size_t m_count; + SelectionChangeCallback m_onchanged; +}; + +inline void ConstructSelectionTest(View& view, const rect_t selection_box) +{ + view.EnableScissor(selection_box.min[0], selection_box.max[0], selection_box.min[1], selection_box.max[1]); +} + +inline const rect_t SelectionBoxForPoint(const float device_point[2], const float device_epsilon[2]) +{ + rect_t selection_box; + selection_box.min[0] = device_point[0] - device_epsilon[0]; + selection_box.min[1] = device_point[1] - device_epsilon[1]; + selection_box.max[0] = device_point[0] + device_epsilon[0]; + selection_box.max[1] = device_point[1] + device_epsilon[1]; + return selection_box; +} + +inline const rect_t SelectionBoxForArea(const float device_point[2], const float device_delta[2]) +{ + rect_t selection_box; + selection_box.min[0] = (device_delta[0] < 0) ? (device_point[0] + device_delta[0]) : (device_point[0]); + selection_box.min[1] = (device_delta[1] < 0) ? (device_point[1] + device_delta[1]) : (device_point[1]); + selection_box.max[0] = (device_delta[0] > 0) ? (device_point[0] + device_delta[0]) : (device_point[0]); + selection_box.max[1] = (device_delta[1] > 0) ? (device_point[1] + device_delta[1]) : (device_point[1]); + return selection_box; +} + +Quaternion construct_local_rotation(const Quaternion& world, const Quaternion& localToWorld) +{ + return quaternion_normalised(quaternion_multiplied_by_quaternion( + quaternion_normalised(quaternion_multiplied_by_quaternion( + quaternion_inverse(localToWorld), + world + )), + localToWorld + )); +} + +inline void matrix4_assign_rotation(Matrix4& matrix, const Matrix4& other) +{ + matrix[0] = other[0]; + matrix[1] = other[1]; + matrix[2] = other[2]; + matrix[4] = other[4]; + matrix[5] = other[5]; + matrix[6] = other[6]; + matrix[8] = other[8]; + matrix[9] = other[9]; + matrix[10] = other[10]; +} + +void matrix4_assign_rotation_for_pivot(Matrix4& matrix, scene::Instance& instance) +{ + Editable* editable = Node_getEditable(instance.path().top()); + if(editable != 0) + { + matrix4_assign_rotation(matrix, matrix4_multiplied_by_matrix4(instance.localToWorld(), editable->getLocalPivot())); + } + else + { + matrix4_assign_rotation(matrix, instance.localToWorld()); + } +} + +inline bool Instance_isSelectedComponents(scene::Instance& instance) +{ + ComponentSelectionTestable* componentSelectionTestable = Instance_getComponentSelectionTestable(instance); + return componentSelectionTestable != 0 + && componentSelectionTestable->isSelectedComponents(); +} + +class TranslateSelected : public SelectionSystem::Visitor +{ + const Vector3& m_translate; +public: + TranslateSelected(const Vector3& translate) + : m_translate(translate) + { + } + void visit(scene::Instance& instance) const + { + Transformable* transform = Instance_getTransformable(instance); + if(transform != 0) + { + transform->setType(TRANSFORM_PRIMITIVE); + transform->setTranslation(m_translate); + } + } +}; + +void Scene_Translate_Selected(scene::Graph& graph, const Vector3& translation) +{ + if(GlobalSelectionSystem().countSelected() != 0) + { + GlobalSelectionSystem().foreachSelected(TranslateSelected(translation)); + } +} + +Vector3 get_local_pivot(const Vector3& world_pivot, const Matrix4& localToWorld) +{ + return Vector3( + matrix4_transformed_point( + matrix4_full_inverse(localToWorld), + world_pivot + ) + ); +} + +void translation_for_pivoted_rotation(Vector3& parent_translation, const Quaternion& local_rotation, const Vector3& world_pivot, const Matrix4& localToWorld, const Matrix4& localToParent) +{ + Vector3 local_pivot(get_local_pivot(world_pivot, localToWorld)); + + Vector3 translation( + vector3_added( + local_pivot, + matrix4_transformed_point( + matrix4_rotation_for_quaternion_quantised(local_rotation), + vector3_negated(local_pivot) + ) + ) + ); + + //globalOutputStream() << "translation: " << translation << "\n"; + + translation_local2object(parent_translation, translation, localToParent); + + //globalOutputStream() << "parent_translation: " << parent_translation << "\n"; +} + +void translation_for_pivoted_scale(Vector3& parent_translation, const Vector3& local_scale, const Vector3& world_pivot, const Matrix4& localToWorld, const Matrix4& localToParent) +{ + Vector3 local_pivot(get_local_pivot(world_pivot, localToWorld)); + + Vector3 translation( + vector3_added( + local_pivot, + vector3_scaled( + vector3_negated(local_pivot), + local_scale + ) + ) + ); + + translation_local2object(parent_translation, translation, localToParent); +} + +class rotate_selected : public SelectionSystem::Visitor +{ + const Quaternion& m_rotate; + const Vector3& m_world_pivot; +public: + rotate_selected(const Quaternion& rotation, const Vector3& world_pivot) + : m_rotate(rotation), m_world_pivot(world_pivot) + { + } + void visit(scene::Instance& instance) const + { + TransformNode* transformNode = Node_getTransformNode(instance.path().top()); + if(transformNode != 0) + { + Transformable* transform = Instance_getTransformable(instance); + if(transform != 0) + { + transform->setType(TRANSFORM_PRIMITIVE); + transform->setScale(c_scale_identity); + transform->setTranslation(c_translation_identity); + + transform->setType(TRANSFORM_PRIMITIVE); + transform->setRotation(m_rotate); + + { + Editable* editable = Node_getEditable(instance.path().top()); + const Matrix4& localPivot = editable != 0 ? editable->getLocalPivot() : g_matrix4_identity; + + Vector3 parent_translation; + translation_for_pivoted_rotation( + parent_translation, + m_rotate, + m_world_pivot, + matrix4_multiplied_by_matrix4(instance.localToWorld(), localPivot), + matrix4_multiplied_by_matrix4(transformNode->localToParent(), localPivot) + ); + + transform->setTranslation(parent_translation); + } + } + } + } +}; + +void Scene_Rotate_Selected(scene::Graph& graph, const Quaternion& rotation, const Vector3& world_pivot) +{ + if(GlobalSelectionSystem().countSelected() != 0) + { + GlobalSelectionSystem().foreachSelected(rotate_selected(rotation, world_pivot)); + } +} + +class scale_selected : public SelectionSystem::Visitor +{ + const Vector3& m_scale; + const Vector3& m_world_pivot; +public: + scale_selected(const Vector3& scaling, const Vector3& world_pivot) + : m_scale(scaling), m_world_pivot(world_pivot) + { + } + void visit(scene::Instance& instance) const + { + TransformNode* transformNode = Node_getTransformNode(instance.path().top()); + if(transformNode != 0) + { + Transformable* transform = Instance_getTransformable(instance); + if(transform != 0) + { + transform->setType(TRANSFORM_PRIMITIVE); + transform->setScale(c_scale_identity); + transform->setTranslation(c_translation_identity); + + transform->setType(TRANSFORM_PRIMITIVE); + transform->setScale(m_scale); + { + Editable* editable = Node_getEditable(instance.path().top()); + const Matrix4& localPivot = editable != 0 ? editable->getLocalPivot() : g_matrix4_identity; + + Vector3 parent_translation; + translation_for_pivoted_scale( + parent_translation, + m_scale, + m_world_pivot, + matrix4_multiplied_by_matrix4(instance.localToWorld(), localPivot), + matrix4_multiplied_by_matrix4(transformNode->localToParent(), localPivot) + ); + + transform->setTranslation(parent_translation); + } + } + } + } +}; + +void Scene_Scale_Selected(scene::Graph& graph, const Vector3& scaling, const Vector3& world_pivot) +{ + if(GlobalSelectionSystem().countSelected() != 0) + { + GlobalSelectionSystem().foreachSelected(scale_selected(scaling, world_pivot)); + } +} + + +class translate_component_selected : public SelectionSystem::Visitor +{ + const Vector3& m_translate; +public: + translate_component_selected(const Vector3& translate) + : m_translate(translate) + { + } + void visit(scene::Instance& instance) const + { + Transformable* transform = Instance_getTransformable(instance); + if(transform != 0) + { + transform->setType(TRANSFORM_COMPONENT); + transform->setTranslation(m_translate); + } + } +}; + +void Scene_Translate_Component_Selected(scene::Graph& graph, const Vector3& translation) +{ + if(GlobalSelectionSystem().countSelected() != 0) + { + GlobalSelectionSystem().foreachSelectedComponent(translate_component_selected(translation)); + } +} + +class rotate_component_selected : public SelectionSystem::Visitor +{ + const Quaternion& m_rotate; + const Vector3& m_world_pivot; +public: + rotate_component_selected(const Quaternion& rotation, const Vector3& world_pivot) + : m_rotate(rotation), m_world_pivot(world_pivot) + { + } + void visit(scene::Instance& instance) const + { + Transformable* transform = Instance_getTransformable(instance); + if(transform != 0) + { + Vector3 parent_translation; + translation_for_pivoted_rotation(parent_translation, m_rotate, m_world_pivot, instance.localToWorld(), Node_getTransformNode(instance.path().top())->localToParent()); + + transform->setType(TRANSFORM_COMPONENT); + transform->setRotation(m_rotate); + transform->setTranslation(parent_translation); + } + } +}; + +void Scene_Rotate_Component_Selected(scene::Graph& graph, const Quaternion& rotation, const Vector3& world_pivot) +{ + if(GlobalSelectionSystem().countSelectedComponents() != 0) + { + GlobalSelectionSystem().foreachSelectedComponent(rotate_component_selected(rotation, world_pivot)); + } +} + +class scale_component_selected : public SelectionSystem::Visitor +{ + const Vector3& m_scale; + const Vector3& m_world_pivot; +public: + scale_component_selected(const Vector3& scaling, const Vector3& world_pivot) + : m_scale(scaling), m_world_pivot(world_pivot) + { + } + void visit(scene::Instance& instance) const + { + Transformable* transform = Instance_getTransformable(instance); + if(transform != 0) + { + Vector3 parent_translation; + translation_for_pivoted_scale(parent_translation, m_scale, m_world_pivot, instance.localToWorld(), Node_getTransformNode(instance.path().top())->localToParent()); + + transform->setType(TRANSFORM_COMPONENT); + transform->setScale(m_scale); + transform->setTranslation(parent_translation); + } + } +}; + +void Scene_Scale_Component_Selected(scene::Graph& graph, const Vector3& scaling, const Vector3& world_pivot) +{ + if(GlobalSelectionSystem().countSelectedComponents() != 0) + { + GlobalSelectionSystem().foreachSelectedComponent(scale_component_selected(scaling, world_pivot)); + } +} + + +class BooleanSelector : public Selector +{ + bool m_selected; + SelectionIntersection m_intersection; + Selectable* m_selectable; +public: + BooleanSelector() : m_selected(false) + { + } + + void pushSelectable(Selectable& selectable) + { + m_intersection = SelectionIntersection(); + m_selectable = &selectable; + } + void popSelectable() + { + if(m_intersection.valid()) + { + m_selected = true; + } + m_intersection = SelectionIntersection(); + } + void addIntersection(const SelectionIntersection& intersection) + { + if(m_selectable->isSelected()) + { + assign_if_closer(m_intersection, intersection); + } + } + + bool isSelected() + { + return m_selected; + } +}; + +class BestSelector : public Selector +{ + SelectionIntersection m_intersection; + Selectable* m_selectable; + SelectionIntersection m_bestIntersection; + std::list m_bestSelectable; +public: + BestSelector() : m_bestIntersection(SelectionIntersection()), m_bestSelectable(0) + { + } + + void pushSelectable(Selectable& selectable) + { + m_intersection = SelectionIntersection(); + m_selectable = &selectable; + } + void popSelectable() + { + if(m_intersection.equalEpsilon(m_bestIntersection, 0.25f, 0.001f)) + { + m_bestSelectable.push_back(m_selectable); + m_bestIntersection = m_intersection; + } + else if(m_intersection < m_bestIntersection) + { + m_bestSelectable.clear(); + m_bestSelectable.push_back(m_selectable); + m_bestIntersection = m_intersection; + } + m_intersection = SelectionIntersection(); + } + void addIntersection(const SelectionIntersection& intersection) + { + assign_if_closer(m_intersection, intersection); + } + + std::list& best() + { + return m_bestSelectable; + } +}; + +class DragManipulator : public Manipulator +{ + TranslateFree m_freeResize; + TranslateFree m_freeDrag; + ResizeTranslatable m_resize; + DragTranslatable m_drag; + SelectableBool m_dragSelectable; +public: + + bool m_selected; + + DragManipulator() : m_freeResize(m_resize), m_freeDrag(m_drag), m_selected(false) + { + } + + Manipulatable* GetManipulatable() + { + return m_dragSelectable.isSelected() ? &m_freeDrag : &m_freeResize; + } + + void testSelect(const View& view, const Matrix4& pivot2world) + { + SelectionPool selector; + + SelectionVolume test(view); + + if(GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) + { + BooleanSelector booleanSelector; + + Scene_TestSelect_Primitive(booleanSelector, test, view); + + if(booleanSelector.isSelected()) + { + selector.addSelectable(SelectionIntersection(0, 0), &m_dragSelectable); + m_selected = false; + } + else + { + m_selected = Scene_forEachPlaneSelectable_selectPlanes(GlobalSceneGraph(), selector, test); + } + } + else + { + BestSelector bestSelector; + Scene_TestSelect_Component_Selected(bestSelector, test, view, GlobalSelectionSystem().ComponentMode()); + for(std::list::iterator i = bestSelector.best().begin(); i != bestSelector.best().end(); ++i) + { + if(!(*i)->isSelected()) + { + GlobalSelectionSystem().setSelectedAllComponents(false); + } + m_selected = false; + selector.addSelectable(SelectionIntersection(0, 0), (*i)); + m_dragSelectable.setSelected(true); + } + } + + for(SelectionPool::iterator i = selector.begin(); i != selector.end(); ++i) + { + (*i).second->setSelected(true); + } + } + + void setSelected(bool select) + { + m_selected = select; + m_dragSelectable.setSelected(select); + } + bool isSelected() const + { + return m_selected || m_dragSelectable.isSelected(); + } +}; + +class ClipManipulator : public Manipulator +{ +public: + + Manipulatable* GetManipulatable() + { + ERROR_MESSAGE("clipper is not manipulatable"); + return 0; + } + + void setSelected(bool select) + { + } + bool isSelected() const + { + return false; + } +}; + +class select_all : public scene::Graph::Walker +{ + bool m_select; +public: + select_all(bool select) + : m_select(select) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0) + { + selectable->setSelected(m_select); + } + return true; + } +}; + +class select_all_component : public scene::Graph::Walker +{ + bool m_select; + SelectionSystem::EComponentMode m_mode; +public: + select_all_component(bool select, SelectionSystem::EComponentMode mode) + : m_select(select), m_mode(mode) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + ComponentSelectionTestable* componentSelectionTestable = Instance_getComponentSelectionTestable(instance); + if(componentSelectionTestable) + { + componentSelectionTestable->setSelectedComponents(m_select, m_mode); + } + return true; + } +}; + +void Scene_SelectAll_Component(bool select, SelectionSystem::EComponentMode componentMode) +{ + GlobalSceneGraph().traverse(select_all_component(select, componentMode)); +} + + +// RadiantSelectionSystem +class RadiantSelectionSystem : + public SelectionSystem, + public Translatable, + public Rotatable, + public Scalable, + public Renderable +{ + mutable Matrix4 m_pivot2world; + Matrix4 m_pivot2world_start; + Matrix4 m_manip2pivot_start; + Translation m_translation; + Rotation m_rotation; + Scale m_scale; +public: + static Shader* m_state; +private: + EManipulatorMode m_manipulator_mode; + Manipulator* m_manipulator; + + // state + bool m_undo_begun; + EMode m_mode; + EComponentMode m_componentmode; + + SelectionCounter m_count_primitive; + SelectionCounter m_count_component; + + TranslateManipulator m_translate_manipulator; + RotateManipulator m_rotate_manipulator; + ScaleManipulator m_scale_manipulator; + DragManipulator m_drag_manipulator; + ClipManipulator m_clip_manipulator; + + typedef SelectionList selection_t; + selection_t m_selection; + selection_t m_component_selection; + + Signal1 m_selectionChanged_callbacks; + + void ConstructPivot() const; + mutable bool m_pivotChanged; + bool m_pivot_moving; + + void Scene_TestSelect(Selector& selector, SelectionTest& test, const View& view, SelectionSystem::EMode mode, SelectionSystem::EComponentMode componentMode); + + bool nothingSelected() const + { + return (Mode() == eComponent && m_count_component.empty()) + || (Mode() == ePrimitive && m_count_primitive.empty()); + } + + +public: + enum EModifier + { + eManipulator, + eToggle, + eReplace, + eCycle, + }; + + RadiantSelectionSystem() : + m_undo_begun(false), + m_mode(ePrimitive), + m_componentmode(eDefault), + m_count_primitive(SelectionChangedCaller(*this)), + m_count_component(SelectionChangedCaller(*this)), + m_translate_manipulator(*this, 2, 64), + m_rotate_manipulator(*this, 8, 64), + m_scale_manipulator(*this, 0, 64), + m_pivotChanged(false), + m_pivot_moving(false) + { + SetManipulatorMode(eTranslate); + pivotChanged(); + addSelectionChangeCallback(PivotChangedSelectionCaller(*this)); + AddGridChangeCallback(PivotChangedCaller(*this)); + } + void pivotChanged() const + { + m_pivotChanged = true; + SceneChangeNotify(); + } + typedef ConstMemberCaller PivotChangedCaller; + void pivotChangedSelection(const Selectable& selectable) + { + pivotChanged(); + } + typedef MemberCaller1 PivotChangedSelectionCaller; + + void SetMode(EMode mode) + { + if(m_mode != mode) + { + m_mode = mode; + pivotChanged(); + } + } + EMode Mode() const + { + return m_mode; + } + void SetComponentMode(EComponentMode mode) + { + m_componentmode = mode; + } + EComponentMode ComponentMode() const + { + return m_componentmode; + } + void SetManipulatorMode(EManipulatorMode mode) + { + m_manipulator_mode = mode; + switch(m_manipulator_mode) + { + case eTranslate: m_manipulator = &m_translate_manipulator; break; + case eRotate: m_manipulator = &m_rotate_manipulator; break; + case eScale: m_manipulator = &m_scale_manipulator; break; + case eDrag: m_manipulator = &m_drag_manipulator; break; + case eClip: m_manipulator = &m_clip_manipulator; break; + } + pivotChanged(); + } + EManipulatorMode ManipulatorMode() const + { + return m_manipulator_mode; + } + + SelectionChangeCallback getObserver(EMode mode) + { + if(mode == ePrimitive) + { + return makeCallback1(m_count_primitive); + } + else + { + return makeCallback1(m_count_component); + } + } + std::size_t countSelected() const + { + return m_count_primitive.size(); + } + std::size_t countSelectedComponents() const + { + return m_count_component.size(); + } + void onSelectedChanged(scene::Instance& instance, const Selectable& selectable) + { + if(selectable.isSelected()) + { + m_selection.append(instance); + } + else + { + m_selection.erase(instance); + } + + ASSERT_MESSAGE(m_selection.size() == m_count_primitive.size(), "selection-tracking error"); + } + void onComponentSelection(scene::Instance& instance, const Selectable& selectable) + { + if(selectable.isSelected()) + { + m_component_selection.append(instance); + } + else + { + m_component_selection.erase(instance); + } + + ASSERT_MESSAGE(m_component_selection.size() == m_count_component.size(), "selection-tracking error"); + } + scene::Instance& ultimateSelected() const + { + ASSERT_MESSAGE(m_selection.size() > 0, "no instance selected"); + return m_selection.back(); + } + scene::Instance& penultimateSelected() const + { + ASSERT_MESSAGE(m_selection.size() > 1, "only one instance selected"); + return *(*(--(--m_selection.end()))); + } + void setSelectedAll(bool selected) + { + GlobalSceneGraph().traverse(select_all(selected)); + + m_manipulator->setSelected(selected); + } + void setSelectedAllComponents(bool selected) + { + Scene_SelectAll_Component(selected, SelectionSystem::eVertex); + Scene_SelectAll_Component(selected, SelectionSystem::eEdge); + Scene_SelectAll_Component(selected, SelectionSystem::eFace); + + m_manipulator->setSelected(selected); + } + + void foreachSelected(const Visitor& visitor) const + { + selection_t::const_iterator i = m_selection.begin(); + while(i != m_selection.end()) + { + visitor.visit(*(*(i++))); + } + } + void foreachSelectedComponent(const Visitor& visitor) const + { + selection_t::const_iterator i = m_component_selection.begin(); + while(i != m_component_selection.end()) + { + visitor.visit(*(*(i++))); + } + } + + void addSelectionChangeCallback(const SelectionChangeHandler& handler) + { + m_selectionChanged_callbacks.connectLast(handler); + } + void selectionChanged(const Selectable& selectable) + { + m_selectionChanged_callbacks(selectable); + } + typedef MemberCaller1 SelectionChangedCaller; + + + void startMove() + { + m_pivot2world_start = GetPivot2World(); + } + + bool SelectManipulator(const View& view, const float device_point[2], const float device_epsilon[2]) + { + if(!nothingSelected() || (ManipulatorMode() == eDrag && Mode() == eComponent)) + { +#if defined (DEBUG_SELECTION) + g_render_clipped.destroy(); +#endif + + m_manipulator->setSelected(false); + + if(!nothingSelected() || (ManipulatorMode() == eDrag && Mode() == eComponent)) + { + View scissored(view); + ConstructSelectionTest(scissored, SelectionBoxForPoint(device_point, device_epsilon)); + m_manipulator->testSelect(scissored, GetPivot2World()); + } + + startMove(); + + m_pivot_moving = m_manipulator->isSelected(); + + if(m_pivot_moving) + { + Pivot2World pivot; + pivot.update(GetPivot2World(), view.GetModelview(), view.GetProjection(), view.GetViewport()); + + m_manip2pivot_start = matrix4_multiplied_by_matrix4(matrix4_full_inverse(m_pivot2world_start), pivot.m_worldSpace); + + Matrix4 device2manip; + ConstructDevice2Manip(device2manip, m_pivot2world_start, view.GetModelview(), view.GetProjection(), view.GetViewport()); + m_manipulator->GetManipulatable()->Construct(device2manip, device_point[0], device_point[1]); + + m_undo_begun = false; + } + + SceneChangeNotify(); + } + + return m_pivot_moving; + } + + void deselectAll() + { + if(Mode() == eComponent) + { + setSelectedAllComponents(false); + } + else + { + setSelectedAll(false); + } + } + + void SelectPoint(const View& view, const float device_point[2], const float device_epsilon[2], RadiantSelectionSystem::EModifier modifier, bool face) + { + ASSERT_MESSAGE(fabs(device_point[0]) <= 1.0f && fabs(device_point[1]) <= 1.0f, "point-selection error"); + if(modifier == eReplace) + { + if(face) + { + setSelectedAllComponents(false); + } + else + { + deselectAll(); + } + } + + #if defined (DEBUG_SELECTION) + g_render_clipped.destroy(); + #endif + + { + View scissored(view); + ConstructSelectionTest(scissored, SelectionBoxForPoint(device_point, device_epsilon)); + + SelectionVolume volume(scissored); + SelectionPool selector; + if(face) + { + Scene_TestSelect_Component(selector, volume, scissored, eFace); + } + else + { + Scene_TestSelect(selector, volume, scissored, Mode(), ComponentMode()); + } + + if(!selector.failed()) + { + switch(modifier) + { + case RadiantSelectionSystem::eToggle: + { + SelectableSortedSet::iterator best = selector.begin(); + // toggle selection of the object with least depth + if((*best).second->isSelected()) + (*best).second->setSelected(false); + else + (*best).second->setSelected(true); + } + break; + // if cycle mode not enabled, enable it + case RadiantSelectionSystem::eReplace: + { + // select closest + (*selector.begin()).second->setSelected(true); + } + break; + // select the next object in the list from the one already selected + case RadiantSelectionSystem::eCycle: + { + SelectionPool::iterator i = selector.begin(); + while(i != selector.end()) + { + if((*i).second->isSelected()) + { + (*i).second->setSelected(false); + ++i; + if(i != selector.end()) + { + i->second->setSelected(true); + } + else + { + selector.begin()->second->setSelected(true); + } + break; + } + ++i; + } + } + break; + default: + break; + } + } + } + } + + void SelectArea(const View& view, const float device_point[2], const float device_delta[2], RadiantSelectionSystem::EModifier modifier, bool face) + { + if(modifier == eReplace) + { + if(face) + { + setSelectedAllComponents(false); + } + else + { + deselectAll(); + } + } + + #if defined (DEBUG_SELECTION) + g_render_clipped.destroy(); + #endif + + { + View scissored(view); + ConstructSelectionTest(scissored, SelectionBoxForArea(device_point, device_delta)); + + SelectionVolume volume(scissored); + SelectionPool pool; + if(face) + { + Scene_TestSelect_Component(pool, volume, scissored, eFace); + } + else + { + Scene_TestSelect(pool, volume, scissored, Mode(), ComponentMode()); + } + + for(SelectionPool::iterator i = pool.begin(); i != pool.end(); ++i) + { + (*i).second->setSelected(!(modifier == RadiantSelectionSystem::eToggle && (*i).second->isSelected())); + } + } + } + + + void translate(const Vector3& translation) + { + if(!nothingSelected()) + { + //ASSERT_MESSAGE(!m_pivotChanged, "pivot is invalid"); + + m_translation = translation; + + m_pivot2world = m_pivot2world_start; + matrix4_translate_by_vec3(m_pivot2world, translation); + + if(Mode() == eComponent) + { + Scene_Translate_Component_Selected(GlobalSceneGraph(), m_translation); + } + else + { + Scene_Translate_Selected(GlobalSceneGraph(), m_translation); + } + + SceneChangeNotify(); + } + } + void outputTranslation(TextOutputStream& ostream) + { + ostream << " -xyz " << m_translation.x() << " " << m_translation.y() << " " << m_translation.z(); + } + void rotate(const Quaternion& rotation) + { + if(!nothingSelected()) + { + //ASSERT_MESSAGE(!m_pivotChanged, "pivot is invalid"); + + m_rotation = rotation; + + if(Mode() == eComponent) + { + Scene_Rotate_Component_Selected(GlobalSceneGraph(), m_rotation, vector4_to_vector3(m_pivot2world.t())); + + matrix4_assign_rotation_for_pivot(m_pivot2world, m_component_selection.back()); + } + else + { + Scene_Rotate_Selected(GlobalSceneGraph(), m_rotation, vector4_to_vector3(m_pivot2world.t())); + + matrix4_assign_rotation_for_pivot(m_pivot2world, m_selection.back()); + } + + SceneChangeNotify(); + } + } + void outputRotation(TextOutputStream& ostream) + { + ostream << " -eulerXYZ " << m_rotation.x() << " " << m_rotation.y() << " " << m_rotation.z(); + } + void scale(const Vector3& scaling) + { + if(!nothingSelected()) + { + m_scale = scaling; + + if(Mode() == eComponent) + { + Scene_Scale_Component_Selected(GlobalSceneGraph(), m_scale, vector4_to_vector3(m_pivot2world.t())); + } + else + { + Scene_Scale_Selected(GlobalSceneGraph(), m_scale, vector4_to_vector3(m_pivot2world.t())); + } + + SceneChangeNotify(); + } + } + void outputScale(TextOutputStream& ostream) + { + ostream << " -scale " << m_scale.x() << " " << m_scale.y() << " " << m_scale.z(); + } + + void rotateSelected(const Quaternion& rotation) + { + startMove(); + rotate(rotation); + freezeTransforms(); + } + void translateSelected(const Vector3& translation) + { + startMove(); + translate(translation); + freezeTransforms(); + } + void scaleSelected(const Vector3& scaling) + { + startMove(); + scale(scaling); + freezeTransforms(); + } + + void MoveSelected(const View& view, const float device_point[2]) + { + if(m_manipulator->isSelected()) + { + if(!m_undo_begun) + { + m_undo_begun = true; + GlobalUndoSystem().start(); + } + + Matrix4 device2manip; + ConstructDevice2Manip(device2manip, m_pivot2world_start, view.GetModelview(), view.GetProjection(), view.GetViewport()); + m_manipulator->GetManipulatable()->Transform(m_manip2pivot_start, device2manip, device_point[0], device_point[1]); + } + } + + /// \todo Support view-dependent nudge. + void NudgeManipulator(const Vector3& nudge, const Vector3& view) + { + if(ManipulatorMode() == eTranslate || ManipulatorMode() == eDrag) + { + translateSelected(nudge); + } + } + + void endMove(); + void freezeTransforms(); + + void renderSolid(Renderer& renderer, const VolumeTest& volume) const; + void renderWireframe(Renderer& renderer, const VolumeTest& volume) const + { + renderSolid(renderer, volume); + } + + const Matrix4& GetPivot2World() const + { + ConstructPivot(); + return m_pivot2world; + } + + static void constructStatic() + { + m_state = GlobalShaderCache().capture("$POINT"); + #if defined(DEBUG_SELECTION) + g_state_clipped = GlobalShaderCache().capture("$DEBUG_CLIPPED"); + #endif + TranslateManipulator::m_state_wire = GlobalShaderCache().capture("$WIRE_OVERLAY"); + TranslateManipulator::m_state_fill = GlobalShaderCache().capture("$FLATSHADE_OVERLAY"); + RotateManipulator::m_state_outer = GlobalShaderCache().capture("$WIRE_OVERLAY"); + } + + static void destroyStatic() + { + #if defined(DEBUG_SELECTION) + GlobalShaderCache().release("$DEBUG_CLIPPED"); + #endif + GlobalShaderCache().release("$WIRE_OVERLAY"); + GlobalShaderCache().release("$FLATSHADE_OVERLAY"); + GlobalShaderCache().release("$WIRE_OVERLAY"); + GlobalShaderCache().release("$POINT"); + } +}; + +Shader* RadiantSelectionSystem::m_state = 0; + + +namespace +{ + RadiantSelectionSystem* g_RadiantSelectionSystem; + + inline RadiantSelectionSystem& getSelectionSystem() + { + return *g_RadiantSelectionSystem; + } +} + + + +class testselect_entity_visible : public scene::Graph::Walker +{ + Selector& m_selector; + SelectionTest& m_test; +public: + testselect_entity_visible(Selector& selector, SelectionTest& test) + : m_selector(selector), m_test(test) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 + && Node_isEntity(path.top())) + { + m_selector.pushSelectable(*selectable); + } + + SelectionTestable* selectionTestable = Instance_getSelectionTestable(instance); + if(selectionTestable) + { + selectionTestable->testSelect(m_selector, m_test); + } + + return true; + } + void post(const scene::Path& path, scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 + && Node_isEntity(path.top())) + { + m_selector.popSelectable(); + } + } +}; + +class testselect_primitive_visible : public scene::Graph::Walker +{ + Selector& m_selector; + SelectionTest& m_test; +public: + testselect_primitive_visible(Selector& selector, SelectionTest& test) + : m_selector(selector), m_test(test) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0) + { + m_selector.pushSelectable(*selectable); + } + + SelectionTestable* selectionTestable = Instance_getSelectionTestable(instance); + if(selectionTestable) + { + selectionTestable->testSelect(m_selector, m_test); + } + + return true; + } + void post(const scene::Path& path, scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0) + { + m_selector.popSelectable(); + } + } +}; + +class testselect_component_visible : public scene::Graph::Walker +{ + Selector& m_selector; + SelectionTest& m_test; + SelectionSystem::EComponentMode m_mode; +public: + testselect_component_visible(Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode) + : m_selector(selector), m_test(test), m_mode(mode) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + ComponentSelectionTestable* componentSelectionTestable = Instance_getComponentSelectionTestable(instance); + if(componentSelectionTestable) + { + componentSelectionTestable->testSelectComponents(m_selector, m_test, m_mode); + } + + return true; + } +}; + + +class testselect_component_visible_selected : public scene::Graph::Walker +{ + Selector& m_selector; + SelectionTest& m_test; + SelectionSystem::EComponentMode m_mode; +public: + testselect_component_visible_selected(Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode) + : m_selector(selector), m_test(test), m_mode(mode) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 && selectable->isSelected()) + { + ComponentSelectionTestable* componentSelectionTestable = Instance_getComponentSelectionTestable(instance); + if(componentSelectionTestable) + { + componentSelectionTestable->testSelectComponents(m_selector, m_test, m_mode); + } + } + + return true; + } +}; + +void Scene_TestSelect_Primitive(Selector& selector, SelectionTest& test, const VolumeTest& volume) +{ + Scene_forEachVisible(GlobalSceneGraph(), volume, testselect_primitive_visible(selector, test)); +} + +void Scene_TestSelect_Component_Selected(Selector& selector, SelectionTest& test, const VolumeTest& volume, SelectionSystem::EComponentMode componentMode) +{ + Scene_forEachVisible(GlobalSceneGraph(), volume, testselect_component_visible_selected(selector, test, componentMode)); +} + +void Scene_TestSelect_Component(Selector& selector, SelectionTest& test, const VolumeTest& volume, SelectionSystem::EComponentMode componentMode) +{ + Scene_forEachVisible(GlobalSceneGraph(), volume, testselect_component_visible(selector, test, componentMode)); +} + +void RadiantSelectionSystem::Scene_TestSelect(Selector& selector, SelectionTest& test, const View& view, SelectionSystem::EMode mode, SelectionSystem::EComponentMode componentMode) +{ + switch(mode) + { + case eEntity: + { + Scene_forEachVisible(GlobalSceneGraph(), view, testselect_entity_visible(selector, test)); + } + break; + case ePrimitive: + Scene_TestSelect_Primitive(selector, test, view); + break; + case eComponent: + Scene_TestSelect_Component_Selected(selector, test, view, componentMode); + break; + } +} + +class FreezeTransforms : public scene::Graph::Walker +{ +public: + bool pre(const scene::Path& path, scene::Instance& instance) const + { + TransformNode* transformNode = Node_getTransformNode(path.top()); + if(transformNode != 0) + { + Transformable* transform = Instance_getTransformable(instance); + if(transform != 0) + { + transform->freezeTransform(); + } + } + return true; + } +}; + +void RadiantSelectionSystem::freezeTransforms() +{ + GlobalSceneGraph().traverse(FreezeTransforms()); +} + + +void RadiantSelectionSystem::endMove() +{ + freezeTransforms(); + + if(Mode() == ePrimitive) + { + if(ManipulatorMode() == eDrag) + { + Scene_SelectAll_Component(false, SelectionSystem::eFace); + } + } + + m_pivot_moving = false; + pivotChanged(); + + SceneChangeNotify(); + + if(m_undo_begun) + { + StringOutputStream command; + + if(ManipulatorMode() == eTranslate) + { + command << "translateTool"; + outputTranslation(command); + } + else if(ManipulatorMode() == eRotate) + { + command << "rotateTool"; + outputRotation(command); + } + else if(ManipulatorMode() == eScale) + { + command << "scaleTool"; + outputScale(command); + } + else if(ManipulatorMode() == eDrag) + { + command << "dragTool"; + } + + GlobalUndoSystem().finish(command.c_str()); + } + +} + +inline AABB Instance_getPivotBounds(scene::Instance& instance) +{ + Entity* entity = Node_getEntity(instance.path().top()); + if(entity != 0 + && (entity->getEntityClass().fixedsize + || !node_is_group(instance.path().top()))) + { + Editable* editable = Node_getEditable(instance.path().top()); + if(editable != 0) + { + return AABB(vector4_to_vector3(matrix4_multiplied_by_matrix4(instance.localToWorld(), editable->getLocalPivot()).t()), Vector3(0, 0, 0)); + } + else + { + return AABB(vector4_to_vector3(instance.localToWorld().t()), Vector3(0, 0, 0)); + } + } + + return instance.worldAABB(); +} + +class bounds_selected : public scene::Graph::Walker +{ + AABB& m_bounds; +public: + bounds_selected(AABB& bounds) + : m_bounds(bounds) + { + m_bounds = AABB(); + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 + && selectable->isSelected()) + { + aabb_extend_by_aabb_safe(m_bounds, Instance_getPivotBounds(instance)); + } + return true; + } +}; + +class bounds_selected_component : public scene::Graph::Walker +{ + AABB& m_bounds; +public: + bounds_selected_component(AABB& bounds) + : m_bounds(bounds) + { + m_bounds = AABB(); + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + Selectable* selectable = Instance_getSelectable(instance); + if(selectable != 0 + && selectable->isSelected()) + { + ComponentEditable* componentEditable = Instance_getComponentEditable(instance); + if(componentEditable) + { + aabb_extend_by_aabb_safe(m_bounds, aabb_for_oriented_aabb_safe(componentEditable->getSelectedComponentsBounds(), instance.localToWorld())); + } + } + return true; + } +}; + +void Scene_BoundsSelected(scene::Graph& graph, AABB& bounds) +{ + graph.traverse(bounds_selected(bounds)); +} + +void Scene_BoundsSelectedComponent(scene::Graph& graph, AABB& bounds) +{ + graph.traverse(bounds_selected_component(bounds)); +} + +#if 0 +inline void pivot_for_node(Matrix4& pivot, scene::Node& node, scene::Instance& instance) +{ + ComponentEditable* componentEditable = Instance_getComponentEditable(instance); + if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent + && componentEditable != 0) + { + pivot = matrix4_translation_for_vec3(componentEditable->getSelectedComponentsBounds().origin); + } + else + { + Bounded* bounded = Instance_getBounded(instance); + if(bounded != 0) + { + pivot = matrix4_translation_for_vec3(bounded->localAABB().origin); + } + else + { + pivot = g_matrix4_identity; + } + } +} +#endif + +void RadiantSelectionSystem::ConstructPivot() const +{ + if(!m_pivotChanged || m_pivot_moving) + return; + m_pivotChanged = false; + + Vector3 m_object_pivot; + + if(!nothingSelected()) + { + { + AABB bounds; + if(Mode() == eComponent) + { + Scene_BoundsSelectedComponent(GlobalSceneGraph(), bounds); + } + else + { + Scene_BoundsSelected(GlobalSceneGraph(), bounds); + } + m_object_pivot = bounds.origin; + } + + vector3_snap(m_object_pivot, GetGridSize()); + m_pivot2world = matrix4_translation_for_vec3(m_object_pivot); + + switch(m_manipulator_mode) + { + case eTranslate: + break; + case eRotate: + if(Mode() == eComponent) + { + matrix4_assign_rotation_for_pivot(m_pivot2world, m_component_selection.back()); + } + else + { + matrix4_assign_rotation_for_pivot(m_pivot2world, m_selection.back()); + } + break; + case eScale: + if(Mode() == eComponent) + { + matrix4_assign_rotation_for_pivot(m_pivot2world, m_component_selection.back()); + } + else + { + matrix4_assign_rotation_for_pivot(m_pivot2world, m_selection.back()); + } + break; + default: + break; + } + } +} + +void RadiantSelectionSystem::renderSolid(Renderer& renderer, const VolumeTest& volume) const +{ + //if(view->TestPoint(m_object_pivot)) + if(!nothingSelected()) + { + renderer.Highlight(Renderer::ePrimitive, false); + renderer.Highlight(Renderer::eFace, false); + + renderer.SetState(m_state, Renderer::eWireframeOnly); + renderer.SetState(m_state, Renderer::eFullMaterials); + + m_manipulator->render(renderer, volume, GetPivot2World()); + } + +#if defined(DEBUG_SELECTION) + renderer.SetState(g_state_clipped, Renderer::eWireframeOnly); + renderer.SetState(g_state_clipped, Renderer::eFullMaterials); + renderer.addRenderable(g_render_clipped, g_render_clipped.m_world); +#endif +} + + +void SelectionSystem_OnBoundsChanged() +{ + getSelectionSystem().pivotChanged(); +} + + +SignalHandlerId SelectionSystem_boundsChanged; + +void SelectionSystem_Construct() +{ + RadiantSelectionSystem::constructStatic(); + + g_RadiantSelectionSystem = new RadiantSelectionSystem; + + SelectionSystem_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback(FreeCaller()); + + GlobalShaderCache().attachRenderable(getSelectionSystem()); +} + +void SelectionSystem_Destroy() +{ + GlobalShaderCache().detachRenderable(getSelectionSystem()); + + GlobalSceneGraph().removeBoundsChangedCallback(SelectionSystem_boundsChanged); + + delete g_RadiantSelectionSystem; + + RadiantSelectionSystem::destroyStatic(); +} + + + + +inline float screen_normalised(float pos, std::size_t size) +{ + return ((2.0f * pos) / size) - 1.0f; +} + +typedef Vector2 DeviceVector; + +inline DeviceVector window_to_normalised_device(WindowVector window, std::size_t width, std::size_t height) +{ + return DeviceVector(screen_normalised(window.x(), width), screen_normalised(height - 1 - window.y(), height)); +} + +inline float device_constrained(float pos) +{ + return std::min(1.0f, std::max(-1.0f, pos)); +} + +inline DeviceVector device_constrained(DeviceVector device) +{ + return DeviceVector(device_constrained(device.x()), device_constrained(device.y())); +} + +inline float window_constrained(float pos, std::size_t origin, std::size_t size) +{ + return std::min(static_cast(origin + size), std::max(static_cast(origin), pos)); +} + +inline WindowVector window_constrained(WindowVector window, std::size_t x, std::size_t y, std::size_t width, std::size_t height) +{ + return WindowVector(window_constrained(window.x(), x, width), window_constrained(window.y(), y, height)); +} + +typedef Callback1 MouseEventCallback; + +Single g_mouseMovedCallback; +Single g_mouseUpCallback; + +#if 1 +const ButtonIdentifier c_button_select = c_buttonLeft; +const ModifierFlags c_modifier_manipulator = c_modifierNone; +const ModifierFlags c_modifier_toggle = c_modifierShift; +const ModifierFlags c_modifier_replace = c_modifierShift | c_modifierAlt; +const ModifierFlags c_modifier_face = c_modifierControl; +#else +const ButtonIdentifier c_button_select = c_buttonLeft; +const ModifierFlags c_modifier_manipulator = c_modifierNone; +const ModifierFlags c_modifier_toggle = c_modifierControl; +const ModifierFlags c_modifier_replace = c_modifierNone; +const ModifierFlags c_modifier_face = c_modifierShift; +#endif +const ModifierFlags c_modifier_toggle_face = c_modifier_toggle | c_modifier_face; +const ModifierFlags c_modifier_replace_face = c_modifier_replace | c_modifier_face; + +const ButtonIdentifier c_button_texture = c_buttonMiddle; +const ModifierFlags c_modifier_apply_texture = c_modifierControl | c_modifierShift; +const ModifierFlags c_modifier_copy_texture = c_modifierNone; + +class Selector_ +{ + RadiantSelectionSystem::EModifier modifier_for_state(ModifierFlags state) + { + if(state == c_modifier_toggle || state == c_modifier_toggle_face) + { + return RadiantSelectionSystem::eToggle; + } + if(state == c_modifier_replace || state == c_modifier_replace_face) + { + return RadiantSelectionSystem::eReplace; + } + return RadiantSelectionSystem::eManipulator; + } + + rect_t getDeviceArea() const + { + DeviceVector delta(m_current - m_start); + if(selecting() && fabs(delta.x()) > m_epsilon.x() && fabs(delta.y()) > m_epsilon.y()) + { + return SelectionBoxForArea(&m_start[0], &delta[0]); + } + else + { + rect_t default_area = { { 0, 0, }, { 0, 0, }, }; + return default_area; + } + } + +public: + DeviceVector m_start; + DeviceVector m_current; + DeviceVector m_epsilon; + std::size_t m_unmoved_replaces; + ModifierFlags m_state; + const View* m_view; + RectangleCallback m_window_update; + + Selector_() : m_start(0.0f, 0.0f), m_current(0.0f, 0.0f), m_unmoved_replaces(0), m_state(c_modifierNone) + { + } + + void draw_area() + { + m_window_update(getDeviceArea()); + } + + void testSelect(DeviceVector position) + { + RadiantSelectionSystem::EModifier modifier = modifier_for_state(m_state); + if(modifier != RadiantSelectionSystem::eManipulator) + { + DeviceVector delta(position - m_start); + if(fabs(delta.x()) > m_epsilon.x() && fabs(delta.y()) > m_epsilon.y()) + { + DeviceVector delta(position - m_start); + getSelectionSystem().SelectArea(*m_view, &m_start[0], &delta[0], modifier, (m_state & c_modifier_face) != c_modifierNone); + } + else + { + if(modifier == RadiantSelectionSystem::eReplace && m_unmoved_replaces++ > 0) + { + modifier = RadiantSelectionSystem::eCycle; + } + getSelectionSystem().SelectPoint(*m_view, &position[0], &m_epsilon[0], modifier, (m_state & c_modifier_face) != c_modifierNone); + } + } + + m_start = m_current = DeviceVector(0.0f, 0.0f); + draw_area(); + } + + bool selecting() const + { + return m_state != c_modifier_manipulator; + } + + void setState(ModifierFlags state) + { + bool was_selecting = selecting(); + m_state = state; + if(was_selecting ^ selecting()) + { + draw_area(); + } + } + + ModifierFlags getState() const + { + return m_state; + } + + void modifierEnable(ModifierFlags type) + { + setState(bitfield_enable(getState(), type)); + } + void modifierDisable(ModifierFlags type) + { + setState(bitfield_disable(getState(), type)); + } + + void mouseDown(DeviceVector position) + { + m_start = m_current = device_constrained(position); + } + + void mouseMoved(DeviceVector position) + { + m_current = device_constrained(position); + draw_area(); + } + typedef MemberCaller1 MouseMovedCaller; + + void mouseUp(DeviceVector position) + { + testSelect(device_constrained(position)); + + g_mouseMovedCallback.clear(); + g_mouseUpCallback.clear(); + } + typedef MemberCaller1 MouseUpCaller; +}; + + +class Manipulator_ +{ +public: + DeviceVector m_epsilon; + const View* m_view; + + bool mouseDown(DeviceVector position) + { + return getSelectionSystem().SelectManipulator(*m_view, &position[0], &m_epsilon[0]); + } + + void mouseMoved(DeviceVector position) + { + getSelectionSystem().MoveSelected(*m_view, &position[0]); + } + typedef MemberCaller1 MouseMovedCaller; + + void mouseUp(DeviceVector position) + { + getSelectionSystem().endMove(); + g_mouseMovedCallback.clear(); + g_mouseUpCallback.clear(); + } + typedef MemberCaller1 MouseUpCaller; +}; + +void Scene_copyClosestTexture(SelectionTest& test); +void Scene_applyClosestTexture(SelectionTest& test); + +class RadiantWindowObserver : public SelectionSystemWindowObserver +{ + enum + { + SELECT_EPSILON = 8, + }; + + int m_width; + int m_height; + + bool m_mouse_down; + +public: + Selector_ m_selector; + Manipulator_ m_manipulator; + + RadiantWindowObserver() : m_mouse_down(false) + { + } + void release() + { + delete this; + } + void setView(const View& view) + { + m_selector.m_view = &view; + m_manipulator.m_view = &view; + } + void setRectangleDrawCallback(const RectangleCallback& callback) + { + m_selector.m_window_update = callback; + } + void onSizeChanged(int width, int height) + { + m_width = width; + m_height = height; + DeviceVector epsilon(SELECT_EPSILON / static_cast(m_width), SELECT_EPSILON / static_cast(m_height)); + m_selector.m_epsilon = m_manipulator.m_epsilon = epsilon; + } + void onMouseDown(const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers) + { + if(button == c_button_select) + { + m_mouse_down = true; + + DeviceVector devicePosition(window_to_normalised_device(position, m_width, m_height)); + if(modifiers == c_modifier_manipulator && m_manipulator.mouseDown(devicePosition)) + { + g_mouseMovedCallback.insert(MouseEventCallback(Manipulator_::MouseMovedCaller(m_manipulator))); + g_mouseUpCallback.insert(MouseEventCallback(Manipulator_::MouseUpCaller(m_manipulator))); + } + else + { + m_selector.mouseDown(devicePosition); + g_mouseMovedCallback.insert(MouseEventCallback(Selector_::MouseMovedCaller(m_selector))); + g_mouseUpCallback.insert(MouseEventCallback(Selector_::MouseUpCaller(m_selector))); + } + } + else if(button == c_button_texture) + { + DeviceVector devicePosition(device_constrained(window_to_normalised_device(position, m_width, m_height))); + + View scissored(*m_selector.m_view); + ConstructSelectionTest(scissored, SelectionBoxForPoint(&devicePosition[0], &m_selector.m_epsilon[0])); + SelectionVolume volume(scissored); + + if(modifiers == c_modifier_apply_texture) + { + Scene_applyClosestTexture(volume); + } + else if(modifiers == c_modifier_copy_texture) + { + Scene_copyClosestTexture(volume); + } + } + } + void onMouseMotion(const WindowVector& position, ModifierFlags modifiers) + { + m_selector.m_unmoved_replaces = 0; + + if(m_mouse_down && !g_mouseMovedCallback.empty()) + { + g_mouseMovedCallback.get()(window_to_normalised_device(position, m_width, m_height)); + } + } + void onMouseUp(const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers) + { + if(button == c_button_select && !g_mouseUpCallback.empty()) + { + m_mouse_down = false; + + g_mouseUpCallback.get()(window_to_normalised_device(position, m_width, m_height)); + } + } + void onModifierDown(ModifierFlags type) + { + m_selector.modifierEnable(type); + } + void onModifierUp(ModifierFlags type) + { + m_selector.modifierDisable(type); + } +}; + + + +SelectionSystemWindowObserver* NewWindowObserver() +{ + return new RadiantWindowObserver; +} + + + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +class SelectionDependencies : + public GlobalSceneGraphModuleRef, + public GlobalShaderCacheModuleRef, + public GlobalOpenGLModuleRef +{ +}; + +class SelectionAPI : public TypeSystemRef +{ + SelectionSystem* m_selection; +public: + typedef SelectionSystem Type; + STRING_CONSTANT(Name, "*"); + + SelectionAPI() + { + SelectionSystem_Construct(); + + m_selection = &getSelectionSystem(); + } + ~SelectionAPI() + { + SelectionSystem_Destroy(); + } + SelectionSystem* getTable() + { + return m_selection; + } +}; + +typedef SingletonModule SelectionModule; +typedef Static StaticSelectionModule; +StaticRegisterModule staticRegisterSelection(StaticSelectionModule::instance()); diff --git a/radiant/selection.h b/radiant/selection.h new file mode 100644 index 00000000..8334ec63 --- /dev/null +++ b/radiant/selection.h @@ -0,0 +1,54 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_SELECTION_H) +#define INCLUDED_SELECTION_H + +#include "windowobserver.h" +#include "generic/callbackfwd.h" + +struct rect_t +{ + float min[2]; + float max[2]; +}; + +typedef Callback1 RectangleCallback; + +class View; + +class SelectionSystemWindowObserver : public WindowObserver +{ +public: + virtual void setView(const View& view) = 0; + virtual void setRectangleDrawCallback(const RectangleCallback& callback) = 0; +}; + +SelectionSystemWindowObserver* NewWindowObserver(); + +class AABB; +namespace scene +{ + class Graph; +} +void Scene_BoundsSelected(scene::Graph& graph, AABB& bounds); + +#endif diff --git a/radiant/server.cpp b/radiant/server.cpp new file mode 100644 index 00000000..a4c7b83a --- /dev/null +++ b/radiant/server.cpp @@ -0,0 +1,287 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "server.h" + +#include "debugging/debugging.h" +#include "warnings.h" + +#include +#include +#include "os/path.h" + +#include "modulesystem.h" + +class RadiantModuleServer : public ModuleServer +{ + typedef std::pair ModuleType; + typedef std::pair ModuleKey; + typedef std::map Modules_; + Modules_ m_modules; + bool m_error; + +public: + RadiantModuleServer() : m_error(false) + { + } + + void setError(bool error) + { + m_error = error; + } + bool getError() const + { + return m_error; + } + + TextOutputStream& getOutputStream() + { + return globalOutputStream(); + } + TextOutputStream& getErrorStream() + { + return globalErrorStream(); + } + DebugMessageHandler& getDebugMessageHandler() + { + return globalDebugMessageHandler(); + } + + void registerModule(const char* type, int version, const char* name, Module& module) + { + ASSERT_NOTNULL(&module); + if(!m_modules.insert(Modules_::value_type(ModuleKey(ModuleType(type, version), name), &module)).second) + { + globalErrorStream() << "module already registered: type=" << makeQuoted(type) << " name=" << makeQuoted(name) << "\n"; + } + else + { + globalOutputStream() << "Module Registered: type=" << makeQuoted(type) << " version=" << makeQuoted(version) << " name=" << makeQuoted(name) << "\n"; + } + } + + Module* findModule(const char* type, int version, const char* name) const + { + Modules_::const_iterator i = m_modules.find(ModuleKey(ModuleType(type, version), name)); + if(i != m_modules.end()) + { + return (*i).second; + } + return 0; + } + + void foreachModule(const char* type, int version, const Visitor& visitor) + { + for(Modules_::const_iterator i = m_modules.begin(); i != m_modules.end(); ++i) + { + if(string_equal((*i).first.first.first.c_str(), type)) + { + visitor.visit((*i).first.second.c_str(), *(*i).second); + } + } + } +}; + + +#if defined(WIN32) + +#include + +#define FORMAT_BUFSIZE 2048 +const char* FormatGetLastError() +{ + static char buf[FORMAT_BUFSIZE]; + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + buf, + FORMAT_BUFSIZE, + NULL + ); + return buf; +} + +class DynamicLibrary +{ + HMODULE m_library; +public: + typedef int (__stdcall* FunctionPointer)(); + + DynamicLibrary(const char* filename) + { + m_library = LoadLibrary(filename); + if(m_library == 0) + { + globalErrorStream() << "LoadLibrary failed: '" << filename << "'\n"; + globalErrorStream() << "GetLastError: " << FormatGetLastError(); + } + } + ~DynamicLibrary() + { + if(!failed()) + { + FreeLibrary(m_library); + } + } + bool failed() + { + return m_library == 0; + } + FunctionPointer findSymbol(const char* symbol) + { + FunctionPointer address = GetProcAddress(m_library, symbol); + if(address == 0) + { + globalErrorStream() << "GetProcAddress failed: '" << symbol << "'\n"; + globalErrorStream() << "GetLastError: " << FormatGetLastError(); + } + return address; + } +}; + +#elif defined(POSIX) + +#include + +class DynamicLibrary +{ + void* m_library; +public: + typedef int (* FunctionPointer)(); + + DynamicLibrary(const char* filename) + { + m_library = dlopen(filename, RTLD_NOW); + } + ~DynamicLibrary() + { + if(!failed()) + dlclose(m_library); + } + bool failed() + { + return m_library == 0; + } + FunctionPointer findSymbol(const char* symbol) + { + FunctionPointer p = (FunctionPointer)dlsym(m_library, symbol); + if(p == 0) + { + const char* error = reinterpret_cast(dlerror()); + if(error != 0) + { + globalErrorStream() << error; + } + } + return p; + } +}; + +#else +#error "unsupported platform" +#endif + +class DynamicLibraryModule +{ + typedef void (RADIANT_DLLEXPORT* RegisterModulesFunc)(ModuleServer& server); + DynamicLibrary m_library; + RegisterModulesFunc m_registerModule; +public: + DynamicLibraryModule(const char* filename) + : m_library(filename), m_registerModule(0) + { + if(!m_library.failed()) + { + m_registerModule = reinterpret_cast(m_library.findSymbol("Radiant_RegisterModules")); + } + } + bool failed() + { + return m_registerModule == 0; + } + void registerModules(ModuleServer& server) + { + m_registerModule(server); + } +}; + + +class Libraries +{ + typedef std::vector libraries_t; + libraries_t m_libraries; + +public: + ~Libraries() + { + release(); + } + void registerLibrary(const char* filename, ModuleServer& server) + { + DynamicLibraryModule* library = new DynamicLibraryModule(filename); + + if(library->failed()) + { + delete library; + } + else + { + m_libraries.push_back(library); + library->registerModules(server); + } + } + void release() + { + for(libraries_t::iterator i = m_libraries.begin(); i != m_libraries.end(); ++i) + { + delete *i; + } + } + void clear() + { + m_libraries.clear(); + } +}; + + +Libraries g_libraries; +RadiantModuleServer g_server; + +ModuleServer& GlobalModuleServer_get() +{ + return g_server; +} + +void GlobalModuleServer_loadModule(const char* filename) +{ + g_libraries.registerLibrary(filename, g_server); +} + +void GlobalModuleServer_Initialise() +{ +} + +void GlobalModuleServer_Shutdown() +{ +} diff --git a/radiant/server.h b/radiant/server.h new file mode 100644 index 00000000..ded73098 --- /dev/null +++ b/radiant/server.h @@ -0,0 +1,31 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_SERVER_H) +#define INCLUDED_SERVER_H + +class ModuleServer; +ModuleServer& GlobalModuleServer_get(); +void GlobalModuleServer_loadModule(const char* filename); +void GlobalModuleServer_Initialise(); +void GlobalModuleServer_Shutdown(); + +#endif diff --git a/radiant/shaders.cpp b/radiant/shaders.cpp new file mode 100644 index 00000000..696ef5ba --- /dev/null +++ b/radiant/shaders.cpp @@ -0,0 +1,73 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "shaders.h" + +#include "ifilesystem.h" + +#include "stream/stringstream.h" + +#include "gtkdlgs.h" + +void ViewShader(const char *pFile, const char *pName) +{ + char* pBuff = 0; + //int nSize = + vfsLoadFile(pFile, reinterpret_cast(&pBuff)); + if (pBuff == 0) + { + globalErrorStream() << "Failed to load shader file " << pFile << "\n"; + return; + } + // look for the shader declaration + StringOutputStream strFind(string_length(pName)); + strFind << LowerCase(pName); + StringOutputStream strLook(string_length(pBuff)); + strFind << LowerCase(pBuff); + // offset used when jumping over commented out definitions + std::size_t nOffset = 0; + while (true) + { + const char* substr = strstr(strFind.c_str() + nOffset, strFind.c_str()); + if (substr == 0) + break; + std::size_t nStart = substr - strLook.c_str(); + // we have found something, maybe it's a commented out shader name? + char *strCheck = new char[string_length(strLook.c_str())+1]; + strcpy( strCheck, strLook.c_str() ); + strCheck[nStart] = 0; + char *pCheck = strrchr( strCheck, '\n' ); + // if there's a commentary sign in-between we'll continue + if (pCheck && strstr( pCheck, "//" )) + { + delete[] strCheck; + nOffset = nStart + 1; + continue; + } + delete[] strCheck; + nOffset = nStart; + break; + } + // now close the file + vfsFreeFile(pBuff); + + DoTextEditor (pFile, static_cast(nOffset)); +} diff --git a/radiant/shaders.h b/radiant/shaders.h new file mode 100644 index 00000000..58b398cf --- /dev/null +++ b/radiant/shaders.h @@ -0,0 +1,27 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_SHADERS_H) +#define INCLUDED_SHADERS_H + +void ViewShader(const char* file, const char* shader); + +#endif diff --git a/radiant/sockets.cpp b/radiant/sockets.cpp new file mode 100644 index 00000000..e08680be --- /dev/null +++ b/radiant/sockets.cpp @@ -0,0 +1,47 @@ + +#include "sockets.h" + +#if defined(WIN32) +#include +#elif defined (POSIX) +#include +#define SOCKET_ERROR -1 +#else +#error "unsupported platform" +#endif + +#ifdef __APPLE__ +#include +#endif + +int Net_Wait(socket_t *sock, long sec, long usec) +{ +// used for select() +#ifdef WIN32 + TIMEVAL tout = { sec, usec }; +#endif +#if defined (POSIX) + timeval tout; + tout.tv_sec = sec; + tout.tv_usec = usec; +#endif + + // select() will identify if the socket needs an update + // if the socket is identified that means there's either a message or the connection has been closed/reset/terminated + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(((unsigned int)sock->socket), &readfds); + // from select man page: + // n is the highest-numbered descriptor in any of the three sets, plus 1 + // (no use on windows) + switch( select( sock->socket + 1, &readfds, 0, 0, &tout ) ) + { + case SOCKET_ERROR: + return -1; + case 0: + return 0; + default: + return 1; + } +} + diff --git a/radiant/sockets.h b/radiant/sockets.h new file mode 100644 index 00000000..3bc2274d --- /dev/null +++ b/radiant/sockets.h @@ -0,0 +1,14 @@ + +#if !defined(INCLUDED_SOCKETS_H) +#define INCLUDED_SOCKETS_H + +#include "l_net/l_net.h" + +// waits for a socket to become ready +// returns +// -1: error +// 0: timeout +// 1: ready +int Net_Wait(socket_t *sock, long sec, long usec); + +#endif diff --git a/radiant/stacktrace.cpp b/radiant/stacktrace.cpp new file mode 100644 index 00000000..7c75db42 --- /dev/null +++ b/radiant/stacktrace.cpp @@ -0,0 +1,318 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "stacktrace.h" +#include "stream/textstream.h" + +#include "environment.h" + +#ifdef __linux__ +#include + +void write_stack_trace(TextOutputStream& outputStream) +{ + const unsigned int MAX_SYMBOLS = 256; + void* symbols[MAX_SYMBOLS]; + + // get return addresses + int symbol_count = backtrace(symbols, MAX_SYMBOLS); + + if(!symbol_count) + return; + + // resolve and print names + char** symbol_names = backtrace_symbols(symbols, symbol_count); + if(symbol_names) + { + for(int i = 0; (i < symbol_count); ++i) + outputStream << symbol_names[i] << "\n"; + + // not a memleak, see www.gnu.org/software/libc/manual (Debugging Support, Backtraces) + free(symbol_names); + } +} +#endif + +#if defined (WIN32) && defined (_MSC_VER) && defined (DEBUG) + +#include "windows.h" +#include "winnt.h" +#include "dbghelp.h" + +class Address +{ +public: + void* m_value; + Address(void* value) : m_value(value) + { + } +}; + +/// \brief Writes an address \p p to \p ostream in hexadecimal form. +template +inline TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const Address& p) +{ + const std::size_t bufferSize = (sizeof(void*) * 2) + 1; + char buf[bufferSize]; + ostream.write(buf, snprintf(buf, bufferSize, "%0p", p.m_value)); + return ostream; +} + +class Offset +{ +public: + void* m_value; + Offset(void* value) : m_value(value) + { + } +}; + +/// \brief Writes an address \p p to \p ostream in hexadecimal form. +template +inline TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const Offset& p) +{ + const std::size_t bufferSize = (sizeof(void*) * 2) + 1; + char buf[bufferSize]; + ostream.write(buf, snprintf(buf, bufferSize, "%X", p.m_value)); + return ostream; +} + +/// \brief Writes a WCHAR string \p s to \p ostream. +template +inline TextOutputStreamType& ostream_write(TextOutputStreamType& ostream, const WCHAR* s) +{ + const std::size_t bufferSize = 1024; + char buf[bufferSize]; + ostream.write(buf, snprintf(buf, bufferSize, "%ls", s)); + return ostream; +} + +struct EnumerateSymbolsContext +{ + STACKFRAME64& sf; + TextOutputStream& outputStream; + std::size_t count; + EnumerateSymbolsContext(STACKFRAME64& sf, TextOutputStream& outputStream) : sf(sf), outputStream(outputStream), count(0) + { + } +}; + +void write_symbol(PSYMBOL_INFO pSym, STACKFRAME64& sf, TextOutputStream& outputStream, std::size_t& count) +{ +#if 0 + if ( pSym->Flags & SYMFLAG_PARAMETER ) + { + + DWORD basicType; + if ( SymGetTypeInfo( GetCurrentProcess(), pSym->ModBase, pSym->TypeIndex, + TI_GET_BASETYPE, &basicType ) ) + { + int bleh = 0; + } + else + { + DWORD typeId; + if(SymGetTypeInfo( GetCurrentProcess(), pSym->ModBase, pSym->TypeIndex, + TI_GET_TYPEID, &typeId )) + { + if ( SymGetTypeInfo( GetCurrentProcess(), pSym->ModBase, pSym->TypeIndex, + TI_GET_BASETYPE, &basicType ) ) + { + int bleh = 0; + } + else + { + const char* FormatGetLastError(); + const char* error = FormatGetLastError(); + int bleh = 0; + + WCHAR* name; + if(SymGetTypeInfo( GetCurrentProcess(), pSym->ModBase, typeId, + TI_GET_SYMNAME, &name )) + { + outputStream << name << " "; + LocalFree(name); + int bleh = 0; + } + else + { + const char* FormatGetLastError(); + const char* error = FormatGetLastError(); + int bleh = 0; + } + } + } + else + { + const char* FormatGetLastError(); + const char* error = FormatGetLastError(); + int bleh = 0; + } + } + if(count != 0) + { + outputStream << ", "; + } + outputStream << pSym->Name; + ++count; + } +#endif +} + +BOOL CALLBACK +EnumerateSymbolsCallback( + PSYMBOL_INFO pSymInfo, + ULONG SymbolSize, + PVOID UserContext ) +{ + write_symbol( pSymInfo, ((EnumerateSymbolsContext*)UserContext)->sf, ((EnumerateSymbolsContext*)UserContext)->outputStream, ((EnumerateSymbolsContext*)UserContext)->count); + + + return TRUE; +} + +void write_stack_trace(PCONTEXT pContext, TextOutputStream& outputStream) +{ + HANDLE m_hProcess = GetCurrentProcess(); + DWORD dwMachineType = 0; + + CONTEXT context = *pContext; + + // Could use SymSetOptions here to add the SYMOPT_DEFERRED_LOADS flag + if ( !SymInitialize( m_hProcess, (PSTR)environment_get_app_path(), TRUE ) ) + { + return; + } + + STACKFRAME64 sf; + memset( &sf, 0, sizeof(sf) ); + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrStack.Mode = AddrModeFlat; + sf.AddrFrame.Mode = AddrModeFlat; + +#ifdef _M_IX86 + // Initialize the STACKFRAME structure for the first call. This is only + // necessary for Intel CPUs, and isn't mentioned in the documentation. + sf.AddrPC.Offset = context.Eip; + sf.AddrStack.Offset = context.Esp; + sf.AddrFrame.Offset = context.Ebp; + + dwMachineType = IMAGE_FILE_MACHINE_I386; +#elif _M_X64 + sf.AddrPC.Offset = context.Rip; + sf.AddrStack.Offset = context.Rsp; + + // MSDN: x64: The frame pointer is RBP or RDI. This value is not always used. + // very funny, we'll try Rdi for now + sf.AddrFrame.Offset = context.Rdi; + + dwMachineType = IMAGE_FILE_MACHINE_AMD64; +#endif + + const unsigned int max_sym_name = 1024;// should be enough + + while ( 1 ) + { + // Get the next stack frame + if ( ! StackWalk64( dwMachineType, + m_hProcess, + GetCurrentThread(), + &sf, + &context, + 0, + SymFunctionTableAccess64, + SymGetModuleBase64, + 0 ) ) + break; + + if ( 0 == sf.AddrFrame.Offset ) // Basic sanity check to make sure + break; // the frame is OK. Bail if not. + + // Get the name of the function for this stack frame entry + BYTE symbolBuffer[ sizeof(SYMBOL_INFO) + max_sym_name ]; + PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)symbolBuffer; + pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); + pSymbol->MaxNameLen = max_sym_name; + + DWORD64 symDisplacement = 0; // Displacement of the input address, + // relative to the start of the symbol + + IMAGEHLP_MODULE64 module = { sizeof(IMAGEHLP_MODULE64) }; + if(SymGetModuleInfo64(m_hProcess, sf.AddrPC.Offset, &module)) + { + outputStream << module.ModuleName << "!"; + + if ( SymFromAddr(m_hProcess, sf.AddrPC.Offset, &symDisplacement, pSymbol)) + { + char undecoratedName[max_sym_name]; + UnDecorateSymbolName(pSymbol->Name, undecoratedName, max_sym_name, UNDNAME_COMPLETE); + + outputStream << undecoratedName; + + outputStream << "("; + // Use SymSetContext to get just the locals/params for this frame + IMAGEHLP_STACK_FRAME imagehlpStackFrame; + imagehlpStackFrame.InstructionOffset = sf.AddrPC.Offset; + SymSetContext( m_hProcess, &imagehlpStackFrame, 0 ); + + // Enumerate the locals/parameters + EnumerateSymbolsContext context(sf, outputStream); + SymEnumSymbols( m_hProcess, 0, 0, EnumerateSymbolsCallback, &context ); + outputStream << ")"; + + outputStream << " + " << Offset(reinterpret_cast(symDisplacement)); + + // Get the source line for this stack frame entry + IMAGEHLP_LINE64 lineInfo = { sizeof(IMAGEHLP_LINE64) }; + DWORD dwLineDisplacement; + if ( SymGetLineFromAddr64( m_hProcess, sf.AddrPC.Offset, + &dwLineDisplacement, &lineInfo ) ) + { + outputStream << " " << lineInfo.FileName << " line " << Unsigned(lineInfo.LineNumber); + } + } + else + { + outputStream << Address(reinterpret_cast(sf.AddrPC.Offset)); + } + } + + outputStream << "\n"; + } + + SymCleanup(m_hProcess); + + return; +} + +void write_stack_trace(TextOutputStream& outputStream) +{ + __try{ RaiseException(0,0,0,0); } __except(write_stack_trace((GetExceptionInformation())->ContextRecord, outputStream), EXCEPTION_CONTINUE_EXECUTION) {} +} + +#else +#if defined (WIN32) +void write_stack_trace(TextOutputStream& outputStream) +{ + outputStream << "\nStacktrace is disabled in release-builds\n"; +} +#endif +#endif diff --git a/radiant/stacktrace.h b/radiant/stacktrace.h new file mode 100644 index 00000000..6f2ee0e5 --- /dev/null +++ b/radiant/stacktrace.h @@ -0,0 +1,28 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_STACKTRACE_H) +#define INCLUDED_STACKTRACE_H + +class TextOutputStream; +void write_stack_trace(TextOutputStream& outputStream); + +#endif diff --git a/radiant/surfacedialog.cpp b/radiant/surfacedialog.cpp new file mode 100644 index 00000000..7b2eb43e --- /dev/null +++ b/radiant/surfacedialog.cpp @@ -0,0 +1,2450 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// Surface Dialog +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "surfacedialog.h" + +#include "debugging/debugging.h" +#include "warnings.h" + +#include "iscenegraph.h" +#include "itexdef.h" +#include "iundo.h" +#include "iselection.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include //Shamus: For Textool + +#include "signal/isignal.h" +#include "generic/object.h" +#include "math/vector.h" +#include "texturelib.h" +#include "shaderlib.h" +#include "stringio.h" + +#include "gtkutil/idledraw.h" +#include "gtkutil/dialog.h" +#include "gtkutil/entry.h" +#include "gtkutil/nonmodal.h" +#include "gtkutil/pointer.h" +#include "gtkutil/glwidget.h" //Shamus: For Textool +#include "gtkutil/button.h" +#include "map.h" +#include "select.h" +#include "patchmanip.h" +#include "brushmanip.h" +#include "patchdialog.h" +#include "preferences.h" +#include "brush_primit.h" +#include "xywindow.h" +#include "mainframe.h" +#include "gtkdlgs.h" +#include "dialog.h" +#include "brush.h" //Shamus: for Textool +#include "patch.h" +#include "commands.h" +#include "stream/stringstream.h" +#include "grid.h" +#include "textureentry.h" + +//NOTE: Proper functioning of Textool currently requires that the "#if 1" lines in +// brush_primit.h be changed to "#if 0". add/removeScale screws this up ATM. :-) +// Plus, Radiant seems to work just fine without that stuff. ;-) + +#define TEXTOOL_ENABLED 0 + +#if TEXTOOL_ENABLED + +namespace TexTool +{ + +//Shamus: Textool function prototypes +gboolean size_allocate(GtkWidget *, GtkAllocation *, gpointer); +gboolean expose(GtkWidget *, GdkEventExpose *, gpointer); +gboolean button_press(GtkWidget *, GdkEventButton *, gpointer); +gboolean button_release(GtkWidget *, GdkEventButton *, gpointer); +gboolean motion(GtkWidget *, GdkEventMotion *, gpointer); +void flipX(GtkToggleButton *, gpointer); +void flipY(GtkToggleButton *, gpointer); + +//End Textool function prototypes + +//Shamus: Textool globals +GtkWidget * g_textoolWin; +//End Textool globals + +void queueDraw() +{ + gtk_widget_queue_draw(g_textoolWin); +} + +} + +#endif + +inline void spin_button_set_step(GtkSpinButton* spin, gfloat step) +{ +#if 1 + gtk_spin_button_get_adjustment(spin)->step_increment = step; +#else + GValue gvalue = GValue_default(); + g_value_init(&gvalue, G_TYPE_DOUBLE); + g_value_set_double(&gvalue, step); + g_object_set(G_OBJECT(gtk_spin_button_get_adjustment(spin)), "step-increment", &gvalue, NULL); +#endif +} + +class Increment +{ + float& m_f; +public: + GtkSpinButton* m_spin; + GtkEntry* m_entry; + Increment(float& f) : m_f(f), m_spin(0), m_entry(0) + { + } + void cancel() + { + entry_set_float(m_entry, m_f); + } + typedef MemberCaller CancelCaller; + void apply() + { + m_f = static_cast(entry_get_float(m_entry)); + spin_button_set_step(m_spin, m_f); + } + typedef MemberCaller ApplyCaller; +}; + +void SurfaceInspector_GridChange(); + +class SurfaceInspector : public Dialog +{ + GtkWindow* BuildDialog(); + + NonModalEntry m_textureEntry; + NonModalSpinner m_hshiftSpinner; + NonModalEntry m_hshiftEntry; + NonModalSpinner m_vshiftSpinner; + NonModalEntry m_vshiftEntry; + NonModalSpinner m_hscaleSpinner; + NonModalEntry m_hscaleEntry; + NonModalSpinner m_vscaleSpinner; + NonModalEntry m_vscaleEntry; + NonModalSpinner m_rotateSpinner; + NonModalEntry m_rotateEntry; + + IdleDraw m_idleDraw; + + GtkCheckButton* m_surfaceFlags[32]; + GtkCheckButton* m_contentFlags[32]; + + NonModalEntry m_valueEntry; + GtkEntry* m_valueEntryWidget; +public: + WindowPositionTracker m_positionTracker; + WindowPositionTrackerImportStringCaller m_importPosition; + WindowPositionTrackerExportStringCaller m_exportPosition; + + // Dialog Data + float m_fitHorizontal; + float m_fitVertical; + + Increment m_hshiftIncrement; + Increment m_vshiftIncrement; + Increment m_hscaleIncrement; + Increment m_vscaleIncrement; + Increment m_rotateIncrement; + GtkEntry* m_texture; + + SurfaceInspector() : + m_textureEntry(ApplyShaderCaller(*this), UpdateCaller(*this)), + m_hshiftSpinner(ApplyTexdefCaller(*this), UpdateCaller(*this)), + m_hshiftEntry(Increment::ApplyCaller(m_hshiftIncrement), Increment::CancelCaller(m_hshiftIncrement)), + m_vshiftSpinner(ApplyTexdefCaller(*this), UpdateCaller(*this)), + m_vshiftEntry(Increment::ApplyCaller(m_vshiftIncrement), Increment::CancelCaller(m_vshiftIncrement)), + m_hscaleSpinner(ApplyTexdefCaller(*this), UpdateCaller(*this)), + m_hscaleEntry(Increment::ApplyCaller(m_hscaleIncrement), Increment::CancelCaller(m_hscaleIncrement)), + m_vscaleSpinner(ApplyTexdefCaller(*this), UpdateCaller(*this)), + m_vscaleEntry(Increment::ApplyCaller(m_vscaleIncrement), Increment::CancelCaller(m_vscaleIncrement)), + m_rotateSpinner(ApplyTexdefCaller(*this), UpdateCaller(*this)), + m_rotateEntry(Increment::ApplyCaller(m_rotateIncrement), Increment::CancelCaller(m_rotateIncrement)), + m_idleDraw(UpdateCaller(*this)), + m_valueEntry(ApplyFlagsCaller(*this), UpdateCaller(*this)), + m_importPosition(m_positionTracker), + m_exportPosition(m_positionTracker), + m_hshiftIncrement(g_si_globals.shift[0]), + m_vshiftIncrement(g_si_globals.shift[1]), + m_hscaleIncrement(g_si_globals.scale[0]), + m_vscaleIncrement(g_si_globals.scale[1]), + m_rotateIncrement(g_si_globals.rotate) + { + m_fitVertical = 1; + m_fitHorizontal = 1; + m_positionTracker.setPosition(c_default_window_pos); + } + + void constructWindow(GtkWindow* main_window) + { + m_parent = main_window; + Create(); + AddGridChangeCallback(FreeCaller()); + } + void destroyWindow() + { + Destroy(); + } + bool visible() const + { + return GTK_WIDGET_VISIBLE(const_cast(GetWidget())); + } + void queueDraw() + { + if(visible()) + { + m_idleDraw.queueDraw(); + } + } + + void Update(); + typedef MemberCaller UpdateCaller; + void ApplyShader(); + typedef MemberCaller ApplyShaderCaller; + void ApplyTexdef(); + typedef MemberCaller ApplyTexdefCaller; + void ApplyFlags(); + typedef MemberCaller ApplyFlagsCaller; +}; + +namespace +{ + SurfaceInspector* g_SurfaceInspector; + + inline SurfaceInspector& getSurfaceInspector() + { + ASSERT_NOTNULL(g_SurfaceInspector); + return *g_SurfaceInspector; + } +} + +void SurfaceInspector_constructWindow(GtkWindow* main_window) +{ + getSurfaceInspector().constructWindow(main_window); +} +void SurfaceInspector_destroyWindow() +{ + getSurfaceInspector().destroyWindow(); +} + +void SurfaceInspector_queueDraw() +{ + getSurfaceInspector().queueDraw(); +} + +namespace +{ + CopiedString g_selectedShader; + TextureProjection g_selectedTexdef; + ContentsFlagsValue g_selectedFlags; + size_t g_selectedShaderSize[2]; +} + +void SurfaceInspector_SetSelectedShader(const char* shader) +{ + g_selectedShader = shader; + SurfaceInspector_queueDraw(); +} + +void SurfaceInspector_SetSelectedTexdef(const TextureProjection& projection) +{ + g_selectedTexdef = projection; + SurfaceInspector_queueDraw(); +} + +void SurfaceInspector_SetSelectedFlags(const ContentsFlagsValue& flags) +{ + g_selectedFlags = flags; + SurfaceInspector_queueDraw(); +} + +static bool s_texture_selection_dirty = false; + +void SurfaceInspector_updateSelection() +{ + s_texture_selection_dirty = true; + SurfaceInspector_queueDraw(); + +#if TEXTOOL_ENABLED + if (g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) + { + TexTool::queueDraw(); + //globalOutputStream() << "textool texture changed..\n"; + } +#endif +} + +void SurfaceInspector_SelectionChanged(const Selectable& selectable) +{ + SurfaceInspector_updateSelection(); +} + +void SurfaceInspector_SetCurrent_FromSelected() +{ + if(s_texture_selection_dirty == true) + { + s_texture_selection_dirty = false; + if(!g_SelectedFaceInstances.empty()) + { + TextureProjection projection; +//This *may* be the point before it fucks up... Let's see! +//Yep, there was a call to removeScale in there... + Scene_BrushGetTexdef_Component_Selected(GlobalSceneGraph(), projection); + + SurfaceInspector_SetSelectedTexdef(projection); + + Scene_BrushGetShaderSize_Component_Selected(GlobalSceneGraph(), g_selectedShaderSize[0], g_selectedShaderSize[1]); + g_selectedTexdef.m_brushprimit_texdef.coords[0][2] = float_mod(g_selectedTexdef.m_brushprimit_texdef.coords[0][2], (float)g_selectedShaderSize[0]); + g_selectedTexdef.m_brushprimit_texdef.coords[1][2] = float_mod(g_selectedTexdef.m_brushprimit_texdef.coords[1][2], (float)g_selectedShaderSize[1]); + + CopiedString name; + Scene_BrushGetShader_Component_Selected(GlobalSceneGraph(), name); + if(string_not_empty(name.c_str())) + { + SurfaceInspector_SetSelectedShader(name.c_str()); + } + + ContentsFlagsValue flags; + Scene_BrushGetFlags_Component_Selected(GlobalSceneGraph(), flags); + SurfaceInspector_SetSelectedFlags(flags); + } + else + { + TextureProjection projection; + Scene_BrushGetTexdef_Selected(GlobalSceneGraph(), projection); + SurfaceInspector_SetSelectedTexdef(projection); + + CopiedString name; + Scene_BrushGetShader_Selected(GlobalSceneGraph(), name); + if(string_empty(name.c_str())) + { + Scene_PatchGetShader_Selected(GlobalSceneGraph(), name); + } + if(string_not_empty(name.c_str())) + { + SurfaceInspector_SetSelectedShader(name.c_str()); + } + + ContentsFlagsValue flags(0, 0, 0, false); + Scene_BrushGetFlags_Selected(GlobalSceneGraph(), flags); + SurfaceInspector_SetSelectedFlags(flags); + } + } +} + +const char* SurfaceInspector_GetSelectedShader() +{ + SurfaceInspector_SetCurrent_FromSelected(); + return g_selectedShader.c_str(); +} + +const TextureProjection& SurfaceInspector_GetSelectedTexdef() +{ + SurfaceInspector_SetCurrent_FromSelected(); + return g_selectedTexdef; +} + +const ContentsFlagsValue& SurfaceInspector_GetSelectedFlags() +{ + SurfaceInspector_SetCurrent_FromSelected(); + return g_selectedFlags; +} + + + +/* +=================================================== + + SURFACE INSPECTOR + +=================================================== +*/ + +si_globals_t g_si_globals; + + +// make the shift increments match the grid settings +// the objective being that the shift+arrows shortcuts move the texture by the corresponding grid size +// this depends on a scale value if you have selected a particular texture on which you want it to work: +// we move the textures in pixels, not world units. (i.e. increment values are in pixel) +// depending on the texture scale it doesn't take the same amount of pixels to move of GetGridSize() +// increment * scale = gridsize +// hscale and vscale are optional parameters, if they are zero they will be set to the default scale +// NOTE: the default scale depends if you are using BP mode or regular. +// For regular it's 0.5f (128 pixels cover 64 world units), for BP it's simply 1.0f +// see fenris #2810 +void DoSnapTToGrid(float hscale, float vscale) +{ + g_si_globals.shift[0] = static_cast(float_to_integer(static_cast(GetGridSize()) / hscale)); + g_si_globals.shift[1] = static_cast(float_to_integer(static_cast(GetGridSize()) / vscale)); + getSurfaceInspector().queueDraw(); +} + +void SurfaceInspector_GridChange() +{ + if (g_si_globals.m_bSnapTToGrid) + DoSnapTToGrid(Texdef_getDefaultTextureScale(), Texdef_getDefaultTextureScale()); +} + +// make the shift increments match the grid settings +// the objective being that the shift+arrows shortcuts move the texture by the corresponding grid size +// this depends on the current texture scale used? +// we move the textures in pixels, not world units. (i.e. increment values are in pixel) +// depending on the texture scale it doesn't take the same amount of pixels to move of GetGridSize() +// increment * scale = gridsize +static void OnBtnMatchGrid(GtkWidget *widget, gpointer data) +{ + float hscale, vscale; + hscale = static_cast(gtk_spin_button_get_value_as_float(getSurfaceInspector().m_hscaleIncrement.m_spin)); + vscale = static_cast(gtk_spin_button_get_value_as_float(getSurfaceInspector().m_vscaleIncrement.m_spin)); + + if (hscale == 0.0f || vscale == 0.0f) + { + globalOutputStream() << "ERROR: unexpected scale == 0.0f\n"; + return; + } + + DoSnapTToGrid (hscale, vscale); +} + +// DoSurface will always try to show the surface inspector +// or update it because something new has been selected +// Shamus: It does get called when the SI is hidden, but not when you select something new. ;-) +void DoSurface (void) +{ + if(getSurfaceInspector().GetWidget() == 0) + { + getSurfaceInspector().Create(); + + } + getSurfaceInspector().Update(); + getSurfaceInspector().importData(); + getSurfaceInspector().ShowDlg(); +} + +void SurfaceInspector_toggleShown() +{ + if (getSurfaceInspector().visible()) + { + getSurfaceInspector().HideDlg(); + } + else + { + DoSurface(); + } +} + +void SurfaceInspector_FitTexture() +{ + UndoableCommand undo("textureAutoFit"); + Select_FitTexture(getSurfaceInspector().m_fitHorizontal, getSurfaceInspector().m_fitVertical); +} + +static void OnBtnPatchdetails(GtkWidget *widget, gpointer data) +{ + Scene_PatchCapTexture_Selected(GlobalSceneGraph()); +} + +static void OnBtnPatchnatural(GtkWidget *widget, gpointer data) +{ + Scene_PatchNaturalTexture_Selected(GlobalSceneGraph()); +} + +static void OnBtnPatchreset(GtkWidget *widget, gpointer data) +{ + float fx, fy; + + if (DoTextureLayout (&fx, &fy) == eIDOK) + { + Scene_PatchTileTexture_Selected(GlobalSceneGraph(), fx, fy); + } +} + +static void OnBtnPatchFit(GtkWidget *widget, gpointer data) +{ + Scene_PatchTileTexture_Selected(GlobalSceneGraph(), 1, 1); +} + +static void OnBtnAxial(GtkWidget *widget, gpointer data) +{ +//globalOutputStream() << "--> [OnBtnAxial]...\n"; + UndoableCommand undo("textureDefault"); + TextureProjection projection; +//globalOutputStream() << " TexDef_Construct_Default()...\n"; + TexDef_Construct_Default(projection); +//globalOutputStream() << " Select_SetTexdef()...\n"; + +#if TEXTOOL_ENABLED + + //Shamus: + if (g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) + { + // Scale up texture width/height if in BP mode... +//NOTE: This may not be correct any more! :-P + if (!g_SelectedFaceInstances.empty()) + { + Face & face = g_SelectedFaceInstances.last().getFace(); + float x = face.getShader().m_state->getTexture().width; + float y = face.getShader().m_state->getTexture().height; + projection.m_brushprimit_texdef.coords[0][0] /= x; + projection.m_brushprimit_texdef.coords[0][1] /= y; + projection.m_brushprimit_texdef.coords[1][0] /= x; + projection.m_brushprimit_texdef.coords[1][1] /= y; + } + } +#endif + + Select_SetTexdef(projection); +} + +static void OnBtnFaceFit(GtkWidget *widget, gpointer data) +{ + getSurfaceInspector().exportData(); + SurfaceInspector_FitTexture(); +} + +typedef const char* FlagName; + +const FlagName surfaceflagNamesDefault[32] = { + "surf1", + "surf2", + "surf3", + "surf4", + "surf5", + "surf6", + "surf7", + "surf8", + "surf9", + "surf10", + "surf11", + "surf12", + "surf13", + "surf14", + "surf15", + "surf16", + "surf17", + "surf18", + "surf19", + "surf20", + "surf21", + "surf22", + "surf23", + "surf24", + "surf25", + "surf26", + "surf27", + "surf28", + "surf29", + "surf30", + "surf31", + "surf32" +}; + +const FlagName contentflagNamesDefault[32] = { + "cont1", + "cont2", + "cont3", + "cont4", + "cont5", + "cont6", + "cont7", + "cont8", + "cont9", + "cont10", + "cont11", + "cont12", + "cont13", + "cont14", + "cont15", + "cont16", + "cont17", + "cont18", + "cont19", + "cont20", + "cont21", + "cont22", + "cont23", + "cont24", + "cont25", + "cont26", + "cont27", + "cont28", + "cont29", + "cont30", + "cont31", + "cont32" +}; + +const char* getSurfaceFlagName(std::size_t bit) +{ + const char* value = g_pGameDescription->getKeyValue(surfaceflagNamesDefault[bit]); + if(string_empty(value)) + { + return surfaceflagNamesDefault[bit]; + } + return value; +} + +const char* getContentFlagName(std::size_t bit) +{ + const char* value = g_pGameDescription->getKeyValue(contentflagNamesDefault[bit]); + if(string_empty(value)) + { + return contentflagNamesDefault[bit]; + } + return value; +} + + +// ============================================================================= +// SurfaceInspector class + +guint togglebutton_connect_toggled(GtkToggleButton* button, const Callback& callback) +{ + return g_signal_connect_swapped(G_OBJECT(button), "toggled", G_CALLBACK(callback.getThunk()), callback.getEnvironment()); +} + +GtkWindow* SurfaceInspector::BuildDialog() +{ + GtkWindow* window = create_floating_window("Surface Inspector", m_parent); + + m_positionTracker.connect(window); + + global_accel_connect_window(window); + + window_connect_focus_in_clear_focus_widget(window); + + + { + // replaced by only the vbox: + GtkWidget* vbox = gtk_vbox_new (FALSE, 5); + gtk_widget_show (vbox); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox)); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + { + GtkWidget* hbox2 = gtk_hbox_new (FALSE, 5); + gtk_widget_show (hbox2); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox2), FALSE, FALSE, 0); + + { + GtkWidget* label = gtk_label_new ("Texture"); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox2), label, FALSE, TRUE, 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_box_pack_start(GTK_BOX(hbox2), GTK_WIDGET(entry), TRUE, TRUE, 0); + m_texture = entry; + m_textureEntry.connect(entry); + GlobalTextureEntryCompletion::instance().connect(entry); + } + } + + + { + GtkWidget* table = gtk_table_new (6, 4, FALSE); + gtk_widget_show (table); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(table), FALSE, FALSE, 0); + gtk_table_set_row_spacings(GTK_TABLE(table), 5); + gtk_table_set_col_spacings(GTK_TABLE(table), 5); + { + GtkWidget* label = gtk_label_new ("Horizontal shift"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(GTK_ADJUSTMENT(gtk_adjustment_new(0, -8192, 8192, 2, 8, 8)), 0, 2)); + m_hshiftIncrement.m_spin = spin; + m_hshiftSpinner.connect(spin); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_table_attach (GTK_TABLE(table), GTK_WIDGET(spin), 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(spin), 60, -2); + } + { + GtkWidget* label = gtk_label_new ("Step"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(entry), 3, 4, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(entry), 50, -2); + m_hshiftIncrement.m_entry = entry; + m_hshiftEntry.connect(entry); + } + { + GtkWidget* label = gtk_label_new ("Vertical shift"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(GTK_ADJUSTMENT(gtk_adjustment_new(0, -8192, 8192, 2, 8, 8)), 0, 2)); + m_vshiftIncrement.m_spin = spin; + m_vshiftSpinner.connect(spin); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(spin), 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(spin), 60, -2); + } + { + GtkWidget* label = gtk_label_new ("Step"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(entry), 3, 4, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(entry), 50, -2); + m_vshiftIncrement.m_entry = entry; + m_vshiftEntry.connect(entry); + } + { + GtkWidget* label = gtk_label_new ("Horizontal stretch"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(GTK_ADJUSTMENT(gtk_adjustment_new(0, -8192, 8192, 2, 8, 8)), 0, 5)); + m_hscaleIncrement.m_spin = spin; + m_hscaleSpinner.connect(spin); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(spin), 1, 2, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(spin), 60, -2); + } + { + GtkWidget* label = gtk_label_new ("Step"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 2, 3); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(entry), 3, 4, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 2, 3); + gtk_widget_set_usize(GTK_WIDGET(entry), 50, -2); + m_hscaleIncrement.m_entry = entry; + m_hscaleEntry.connect(entry); + } + { + GtkWidget* label = gtk_label_new ("Vertical stretch"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(GTK_ADJUSTMENT(gtk_adjustment_new(0, -8192, 8192, 2, 8, 8)), 0, 5)); + m_vscaleIncrement.m_spin = spin; + m_vscaleSpinner.connect(spin); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(spin), 1, 2, 3, 4, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(spin), 60, -2); + } + { + GtkWidget* label = gtk_label_new ("Step"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 3, 4, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(entry), 3, 4, 3, 4, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(entry), 50, -2); + m_vscaleIncrement.m_entry = entry; + m_vscaleEntry.connect(entry); + } + { + GtkWidget* label = gtk_label_new ("Rotate"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 4, 5, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(GTK_ADJUSTMENT(gtk_adjustment_new(0, -8192, 8192, 2, 8, 8)), 0, 2)); + m_rotateIncrement.m_spin = spin; + m_rotateSpinner.connect(spin); + gtk_widget_show(GTK_WIDGET(spin)); + gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(spin), 1, 2, 4, 5, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(spin), 60, -2); + gtk_spin_button_set_wrap(spin, TRUE); + } + { + GtkWidget* label = gtk_label_new ("Step"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 4, 5, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_table_attach(GTK_TABLE(table), GTK_WIDGET(entry), 3, 4, 4, 5, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_usize(GTK_WIDGET(entry), 50, -2); + m_rotateIncrement.m_entry = entry; + m_rotateEntry.connect(entry); + } + { + // match grid button + GtkWidget* button = gtk_button_new_with_label ("Match Grid"); + gtk_widget_show (button); + gtk_table_attach(GTK_TABLE(table), button, 2, 4, 5, 6, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(OnBtnMatchGrid), 0); + } + } + + { + GtkWidget* frame = gtk_frame_new ("Texturing"); + gtk_widget_show (frame); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(frame), FALSE, FALSE, 0); + { + GtkWidget* table = gtk_table_new (4, 4, FALSE); + gtk_widget_show (table); + gtk_container_add (GTK_CONTAINER (frame), table); + gtk_table_set_row_spacings(GTK_TABLE(table), 5); + gtk_table_set_col_spacings(GTK_TABLE(table), 5); + gtk_container_set_border_width (GTK_CONTAINER (table), 5); + { + GtkWidget* label = gtk_label_new ("Brush"); + gtk_widget_show (label); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkWidget* label = gtk_label_new ("Patch"); + gtk_widget_show (label); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkWidget* label = gtk_label_new ("Width"); + gtk_widget_show (label); + gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkWidget* label = gtk_label_new ("Height"); + gtk_widget_show (label); + gtk_table_attach(GTK_TABLE(table), label, 3, 4, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + } + { + GtkWidget* button = gtk_button_new_with_label ("Axial"); + gtk_widget_show (button); + gtk_table_attach(GTK_TABLE(table), button, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(OnBtnAxial), 0); + gtk_widget_set_usize (button, 60, -2); + } + { + GtkWidget* button = gtk_button_new_with_label ("Fit"); + gtk_widget_show (button); + gtk_table_attach(GTK_TABLE(table), button, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(OnBtnFaceFit), 0); + gtk_widget_set_usize (button, 60, -2); + } + { + GtkWidget* button = gtk_button_new_with_label ("CAP"); + gtk_widget_show (button); + gtk_table_attach(GTK_TABLE(table), button, 0, 1, 3, 4, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(OnBtnPatchdetails), 0); + gtk_widget_set_usize (button, 60, -2); + } + { + GtkWidget* button = gtk_button_new_with_label ("Set..."); + gtk_widget_show (button); + gtk_table_attach(GTK_TABLE(table), button, 1, 2, 3, 4, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(OnBtnPatchreset), 0); + gtk_widget_set_usize (button, 60, -2); + } + { + GtkWidget* button = gtk_button_new_with_label ("Natural"); + gtk_widget_show (button); + gtk_table_attach(GTK_TABLE(table), button, 2, 3, 3, 4, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(OnBtnPatchnatural), 0); + gtk_widget_set_usize (button, 60, -2); + } + { + GtkWidget* button = gtk_button_new_with_label ("Fit"); + gtk_widget_show (button); + gtk_table_attach(GTK_TABLE(table), button, 3, 4, 3, 4, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(OnBtnPatchFit), 0); + gtk_widget_set_usize (button, 60, -2); + } + { + GtkWidget* spin = gtk_spin_button_new (GTK_ADJUSTMENT (gtk_adjustment_new (1, 0, 1 << 16, 1, 10, 10)), 0, 6); + gtk_widget_show (spin); + gtk_table_attach(GTK_TABLE(table), spin, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_usize (spin, 60, -2); + AddDialogData(*GTK_SPIN_BUTTON(spin), m_fitHorizontal); + } + { + GtkWidget* spin = gtk_spin_button_new (GTK_ADJUSTMENT (gtk_adjustment_new (1, 0, 1 << 16, 1, 10, 10)), 0, 6); + gtk_widget_show (spin); + gtk_table_attach(GTK_TABLE(table), spin, 3, 4, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_set_usize (spin, 60, -2); + AddDialogData(*GTK_SPIN_BUTTON(spin), m_fitVertical); + } + } + } + if(!string_empty(g_pGameDescription->getKeyValue("si_flags"))) + { + { + GtkFrame* frame = GTK_FRAME(gtk_frame_new("Surface Flags")); + gtk_widget_show(GTK_WIDGET(frame)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(frame), TRUE, TRUE, 0); + { + GtkVBox* vbox3 = GTK_VBOX(gtk_vbox_new(FALSE, 4)); + //gtk_container_set_border_width(GTK_CONTAINER(vbox3), 4); + gtk_widget_show(GTK_WIDGET(vbox3)); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(vbox3)); + { + GtkTable* table = GTK_TABLE(gtk_table_new(8, 4, FALSE)); + gtk_widget_show(GTK_WIDGET(table)); + gtk_box_pack_start(GTK_BOX(vbox3), GTK_WIDGET(table), TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 0); + gtk_table_set_col_spacings(table, 0); + + GtkCheckButton** p = m_surfaceFlags; + + for(int c = 0; c != 4; ++c) + { + for(int r = 0; r != 8; ++r) + { + GtkCheckButton* check = GTK_CHECK_BUTTON(gtk_check_button_new_with_label(getSurfaceFlagName(c * 8 + r))); + gtk_widget_show(GTK_WIDGET(check)); + gtk_table_attach(table, GTK_WIDGET(check), c, c+1, r, r+1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + *p++ = check; + guint handler_id = togglebutton_connect_toggled(GTK_TOGGLE_BUTTON(check), ApplyFlagsCaller(*this)); + g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(handler_id)); + } + } + } + } + } + { + GtkFrame* frame = GTK_FRAME(gtk_frame_new("Content Flags")); + gtk_widget_show(GTK_WIDGET(frame)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(frame), TRUE, TRUE, 0); + { + GtkVBox* vbox3 = GTK_VBOX(gtk_vbox_new(FALSE, 4)); + //gtk_container_set_border_width(GTK_CONTAINER(vbox3), 4); + gtk_widget_show(GTK_WIDGET(vbox3)); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(vbox3)); + { + + GtkTable* table = GTK_TABLE(gtk_table_new(8, 4, FALSE)); + gtk_widget_show(GTK_WIDGET(table)); + gtk_box_pack_start(GTK_BOX(vbox3), GTK_WIDGET(table), TRUE, TRUE, 0); + gtk_table_set_row_spacings(table, 0); + gtk_table_set_col_spacings(table, 0); + + GtkCheckButton** p = m_contentFlags; + + for(int c = 0; c != 4; ++c) + { + for(int r = 0; r != 8; ++r) + { + GtkCheckButton* check = GTK_CHECK_BUTTON(gtk_check_button_new_with_label(getContentFlagName(c * 8 + r))); + gtk_widget_show(GTK_WIDGET(check)); + gtk_table_attach(table, GTK_WIDGET(check), c, c+1, r, r+1, + (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), + (GtkAttachOptions)(0), 0, 0); + *p++ = check; + guint handler_id = togglebutton_connect_toggled(GTK_TOGGLE_BUTTON(check), ApplyFlagsCaller(*this)); + g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(handler_id)); + } + } + + // not allowed to modify detail flag using Surface Inspector + gtk_widget_set_sensitive(GTK_WIDGET(m_contentFlags[BRUSH_DETAIL_FLAG]), FALSE); + } + } + } + { + GtkFrame* frame = GTK_FRAME(gtk_frame_new("Value")); + gtk_widget_show(GTK_WIDGET(frame)); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(frame), TRUE, TRUE, 0); + { + GtkVBox* vbox3 = GTK_VBOX(gtk_vbox_new(FALSE, 4)); + gtk_container_set_border_width(GTK_CONTAINER(vbox3), 4); + gtk_widget_show(GTK_WIDGET(vbox3)); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(vbox3)); + + { + GtkEntry* entry = GTK_ENTRY(gtk_entry_new()); + gtk_widget_show(GTK_WIDGET(entry)); + gtk_box_pack_start(GTK_BOX(vbox3), GTK_WIDGET(entry), TRUE, TRUE, 0); + m_valueEntryWidget = entry; + m_valueEntry.connect(entry); + } + } + } + } + +#if TEXTOOL_ENABLED + if(g_bp_globals.m_texdefTypeId == TEXDEFTYPEID_BRUSHPRIMITIVES) +// Shamus: Textool goodies... + { + GtkWidget * frame = gtk_frame_new("Textool"); + gtk_widget_show(frame); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(frame), FALSE, FALSE, 0); + { + //Prolly should make this a member or global var, so the SI can draw on it... + TexTool::g_textoolWin = glwidget_new(FALSE); + // --> Dunno, but this stuff may be necessary... (Looks like it!) + gtk_widget_ref(TexTool::g_textoolWin); + gtk_widget_set_events(TexTool::g_textoolWin, GDK_DESTROY | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK); + GTK_WIDGET_SET_FLAGS(TexTool::g_textoolWin, GTK_CAN_FOCUS); + // <-- end stuff... + gtk_widget_show(TexTool::g_textoolWin); + gtk_widget_set_usize(TexTool::g_textoolWin, -1, 240); //Yeah! + gtk_container_add(GTK_CONTAINER(frame), TexTool::g_textoolWin); + + g_signal_connect(G_OBJECT(TexTool::g_textoolWin), "size_allocate", G_CALLBACK(TexTool::size_allocate), NULL); + g_signal_connect(G_OBJECT(TexTool::g_textoolWin), "expose_event", G_CALLBACK(TexTool::expose), NULL); + g_signal_connect(G_OBJECT(TexTool::g_textoolWin), "button_press_event", G_CALLBACK(TexTool::button_press), NULL); + g_signal_connect(G_OBJECT(TexTool::g_textoolWin), "button_release_event", G_CALLBACK(TexTool::button_release), NULL); + g_signal_connect(G_OBJECT(TexTool::g_textoolWin), "motion_notify_event", G_CALLBACK(TexTool::motion), NULL); + } + { + GtkWidget * hbox = gtk_hbox_new(FALSE, 5); + gtk_widget_show(hbox); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(hbox), FALSE, FALSE, 0); + // Checkboxes go here... (Flip X/Y) + GtkWidget * flipX = gtk_check_button_new_with_label("Flip X axis"); + GtkWidget * flipY = gtk_check_button_new_with_label("Flip Y axis"); + gtk_widget_show(flipX); + gtk_widget_show(flipY); + gtk_box_pack_start(GTK_BOX(hbox), flipX, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), flipY, FALSE, FALSE, 0); + +//Instead of this, we probably need to create a vbox to put into the frame, then the +//window, then the hbox. !!! FIX !!! +// gtk_container_add(GTK_CONTAINER(frame), hbox); + +//Hmm. Do we really need g_object_set_data? Mebbe not... And we don't! :-) +// g_object_set_data(G_OBJECT(flipX), "handler", gint_to_pointer(g_signal_connect(G_OBJECT(flipX), "toggled", G_CALLBACK(TexTool::flipX), 0))); +// g_object_set_data(G_OBJECT(flipY), "handler", gint_to_pointer(g_signal_connect(G_OBJECT(flipY), "toggled", G_CALLBACK(TexTool::flipY), 0))); +//Instead, just do: + g_signal_connect(G_OBJECT(flipX), "toggled", G_CALLBACK(TexTool::flipX), NULL); + g_signal_connect(G_OBJECT(flipY), "toggled", G_CALLBACK(TexTool::flipY), NULL); + } + } +#endif + } + + return window; +} + +/* +============== +Update + +Set the fields to the current texdef (i.e. map/texdef -> dialog widgets) +if faces selected (instead of brushes) -> will read this face texdef, else current texdef +if only patches selected, will read the patch texdef +=============== +*/ + +void spin_button_set_value_no_signal(GtkSpinButton* spin, gdouble value) +{ + guint handler_id = gpointer_to_int(g_object_get_data(G_OBJECT(spin), "handler")); + g_signal_handler_block(G_OBJECT(gtk_spin_button_get_adjustment(spin)), handler_id); + gtk_spin_button_set_value(spin, value); + g_signal_handler_unblock(G_OBJECT(gtk_spin_button_get_adjustment(spin)), handler_id); +} + +void spin_button_set_step_increment(GtkSpinButton* spin, gdouble value) +{ + GtkAdjustment* adjust = gtk_spin_button_get_adjustment(spin); + adjust->step_increment = value; +} + +void SurfaceInspector::Update() +{ + const char * name = SurfaceInspector_GetSelectedShader(); + + if(shader_is_texture(name)) + { + gtk_entry_set_text(m_texture, shader_get_textureName(name)); + } + else + { + gtk_entry_set_text(m_texture, ""); + } + + texdef_t shiftScaleRotate; +//Shamus: This is where we get into trouble--the BP code tries to convert to a "faked" +//shift, rotate & scale values from the brush face, which seems to screw up for some reason. +//!!! FIX !!! +/*globalOutputStream() << "--> SI::Update. About to do ShiftScaleRotate_fromFace()...\n"; +SurfaceInspector_GetSelectedBPTexdef(); +globalOutputStream() << "BP: (" << g_selectedBrushPrimitTexdef.coords[0][0] << ", " << g_selectedBrushPrimitTexdef.coords[0][1] << ")(" + << g_selectedBrushPrimitTexdef.coords[1][0] << ", " << g_selectedBrushPrimitTexdef.coords[1][1] << ")(" + << g_selectedBrushPrimitTexdef.coords[0][2] << ", " << g_selectedBrushPrimitTexdef.coords[1][2] << ") SurfaceInspector::Update\n";//*/ +//Ok, it's screwed up *before* we get here... + ShiftScaleRotate_fromFace(shiftScaleRotate, SurfaceInspector_GetSelectedTexdef()); + + // normalize again to hide the ridiculously high scale values that get created when using texlock + shiftScaleRotate.shift[0] = float_mod(shiftScaleRotate.shift[0], (float)g_selectedShaderSize[0]); + shiftScaleRotate.shift[1] = float_mod(shiftScaleRotate.shift[1], (float)g_selectedShaderSize[1]); + + { + spin_button_set_value_no_signal(m_hshiftIncrement.m_spin, shiftScaleRotate.shift[0]); + spin_button_set_step_increment(m_hshiftIncrement.m_spin, g_si_globals.shift[0]); + entry_set_float(m_hshiftIncrement.m_entry, g_si_globals.shift[0]); + } + + { + spin_button_set_value_no_signal(m_vshiftIncrement.m_spin, shiftScaleRotate.shift[1]); + spin_button_set_step_increment(m_vshiftIncrement.m_spin, g_si_globals.shift[1]); + entry_set_float(m_vshiftIncrement.m_entry, g_si_globals.shift[1]); + } + + { + spin_button_set_value_no_signal(m_hscaleIncrement.m_spin, shiftScaleRotate.scale[0]); + spin_button_set_step_increment(m_hscaleIncrement.m_spin, g_si_globals.scale[0]); + entry_set_float(m_hscaleIncrement.m_entry, g_si_globals.scale[0]); + } + + { + spin_button_set_value_no_signal(m_vscaleIncrement.m_spin, shiftScaleRotate.scale[1]); + spin_button_set_step_increment(m_vscaleIncrement.m_spin, g_si_globals.scale[1]); + entry_set_float(m_vscaleIncrement.m_entry, g_si_globals.scale[1]); + } + + { + spin_button_set_value_no_signal(m_rotateIncrement.m_spin, shiftScaleRotate.rotate); + spin_button_set_step_increment(m_rotateIncrement.m_spin, g_si_globals.rotate); + entry_set_float(m_rotateIncrement.m_entry, g_si_globals.rotate); + } + + if(!string_empty(g_pGameDescription->getKeyValue("si_flags"))) + { + ContentsFlagsValue flags(SurfaceInspector_GetSelectedFlags()); + + entry_set_int(m_valueEntryWidget, flags.m_value); + + for(GtkCheckButton** p = m_surfaceFlags; p != m_surfaceFlags + 32; ++p) + { + toggle_button_set_active_no_signal(GTK_TOGGLE_BUTTON(*p), flags.m_surfaceFlags & (1 << (p - m_surfaceFlags))); + } + + for(GtkCheckButton** p = m_contentFlags; p != m_contentFlags + 32; ++p) + { + toggle_button_set_active_no_signal(GTK_TOGGLE_BUTTON(*p), flags.m_contentFlags & (1 << (p - m_contentFlags))); + } + } +} + +/* +============== +Apply + +Reads the fields to get the current texdef (i.e. widgets -> MAP) +in brush primitive mode, grab the fake shift scale rot and compute a new texture matrix +=============== +*/ +void SurfaceInspector::ApplyShader() +{ + StringOutputStream name(256); + name << GlobalTexturePrefix_get() << gtk_entry_get_text(m_texture); + + // TTimo: detect and refuse invalid texture names (at least the ones with spaces) + if(!texdef_name_valid(name.c_str())) + { + globalErrorStream() << "invalid texture name '" << name.c_str() << "'\n"; + SurfaceInspector_queueDraw(); + return; + } + + UndoableCommand undo("textureNameSetSelected"); + Select_SetShader(name.c_str()); +} + +void SurfaceInspector::ApplyTexdef() +{ + texdef_t shiftScaleRotate; + + shiftScaleRotate.shift[0] = static_cast(gtk_spin_button_get_value_as_float(m_hshiftIncrement.m_spin)); + shiftScaleRotate.shift[1] = static_cast(gtk_spin_button_get_value_as_float(m_vshiftIncrement.m_spin)); + shiftScaleRotate.scale[0] = static_cast(gtk_spin_button_get_value_as_float(m_hscaleIncrement.m_spin)); + shiftScaleRotate.scale[1] = static_cast(gtk_spin_button_get_value_as_float(m_vscaleIncrement.m_spin)); + shiftScaleRotate.rotate = static_cast(gtk_spin_button_get_value_as_float(m_rotateIncrement.m_spin)); + + TextureProjection projection; +//Shamus: This is the other place that screws up, it seems, since it doesn't seem to do the +//conversion from the face (I think) and so bogus values end up in the thing... !!! FIX !!! +//This is actually OK. :-P + ShiftScaleRotate_toFace(shiftScaleRotate, projection); + + UndoableCommand undo("textureProjectionSetSelected"); + Select_SetTexdef(projection); +} + +void SurfaceInspector::ApplyFlags() +{ + unsigned int surfaceflags = 0; + for(GtkCheckButton** p = m_surfaceFlags; p != m_surfaceFlags + 32; ++p) + { + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(*p))) + { + surfaceflags |= (1 << (p - m_surfaceFlags)); + } + } + + unsigned int contentflags = 0; + for(GtkCheckButton** p = m_contentFlags; p != m_contentFlags + 32; ++p) + { + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(*p))) + { + contentflags |= (1 << (p - m_contentFlags)); + } + } + + int value = entry_get_int(m_valueEntryWidget); + + UndoableCommand undo("flagsSetSelected"); + Select_SetFlags(ContentsFlagsValue(surfaceflags, contentflags, value, true)); +} + + +void Face_getTexture(Face& face, CopiedString& shader, TextureProjection& projection, ContentsFlagsValue& flags) +{ + shader = face.GetShader(); + face.GetTexdef(projection); + flags = face.getShader().m_flags; +} +typedef Function4 FaceGetTexture; + +void Face_setTexture(Face& face, const char* shader, const TextureProjection& projection, const ContentsFlagsValue& flags) +{ + face.SetShader(shader); + face.SetTexdef(projection); + face.SetFlags(flags); +} +typedef Function4 FaceSetTexture; + + +void Patch_getTexture(Patch& patch, CopiedString& shader, TextureProjection& projection, ContentsFlagsValue& flags) +{ + shader = patch.GetShader(); + projection = TextureProjection(texdef_t(), brushprimit_texdef_t(), Vector3(0, 0, 0), Vector3(0, 0, 0)); + flags = ContentsFlagsValue(0, 0, 0, false); +} +typedef Function4 PatchGetTexture; + +void Patch_setTexture(Patch& patch, const char* shader, const TextureProjection& projection, const ContentsFlagsValue& flags) +{ + patch.SetShader(shader); +} +typedef Function4 PatchSetTexture; + + +typedef Callback3 GetTextureCallback; +typedef Callback3 SetTextureCallback; + +struct Texturable +{ + GetTextureCallback getTexture; + SetTextureCallback setTexture; +}; + + +void Face_getClosest(Face& face, SelectionTest& test, SelectionIntersection& bestIntersection, Texturable& texturable) +{ + SelectionIntersection intersection; + face.testSelect(test, intersection); + if(intersection.valid() + && SelectionIntersection_closer(intersection, bestIntersection)) + { + bestIntersection = intersection; + texturable.setTexture = makeCallback3(FaceSetTexture(), face); + texturable.getTexture = makeCallback3(FaceGetTexture(), face); + } +} + + +class OccludeSelector : public Selector +{ + SelectionIntersection& m_bestIntersection; + bool& m_occluded; +public: + OccludeSelector(SelectionIntersection& bestIntersection, bool& occluded) : m_bestIntersection(bestIntersection), m_occluded(occluded) + { + m_occluded = false; + } + void pushSelectable(Selectable& selectable) + { + } + void popSelectable() + { + } + void addIntersection(const SelectionIntersection& intersection) + { + if(SelectionIntersection_closer(intersection, m_bestIntersection)) + { + m_bestIntersection = intersection; + m_occluded = true; + } + } +}; + +class BrushGetClosestFaceVisibleWalker : public scene::Graph::Walker +{ + SelectionTest& m_test; + Texturable& m_texturable; + mutable SelectionIntersection m_bestIntersection; +public: + BrushGetClosestFaceVisibleWalker(SelectionTest& test, Texturable& texturable) : m_test(test), m_texturable(texturable) + { + } + bool pre(const scene::Path& path, scene::Instance& instance) const + { + if(path.top().get().visible()) + { + BrushInstance* brush = Instance_getBrush(instance); + if(brush != 0) + { + m_test.BeginMesh(brush->localToWorld()); + + for(Brush::const_iterator i = brush->getBrush().begin(); i != brush->getBrush().end(); ++i) + { + Face_getClosest(*(*i), m_test, m_bestIntersection, m_texturable); + } + } + else + { + SelectionTestable* selectionTestable = Instance_getSelectionTestable(instance); + if(selectionTestable) + { + bool occluded; + OccludeSelector selector(m_bestIntersection, occluded); + selectionTestable->testSelect(selector, m_test); + if(occluded) + { + Patch* patch = Node_getPatch(path.top()); + if(patch != 0) + { + m_texturable.setTexture = makeCallback3(PatchSetTexture(), *patch); + m_texturable.getTexture = makeCallback3(PatchGetTexture(), *patch); + } + else + { + m_texturable = Texturable(); + } + } + } + } + } + return true; + } +}; + +Texturable Scene_getClosestTexturable(scene::Graph& graph, SelectionTest& test) +{ + Texturable texturable; + graph.traverse(BrushGetClosestFaceVisibleWalker(test, texturable)); + return texturable; +} + +bool Scene_getClosestTexture(scene::Graph& graph, SelectionTest& test, CopiedString& shader, TextureProjection& projection, ContentsFlagsValue& flags) +{ + Texturable texturable = Scene_getClosestTexturable(graph, test); + if(texturable.getTexture != GetTextureCallback()) + { + texturable.getTexture(shader, projection, flags); + return true; + } + return false; +} + +void Scene_setClosestTexture(scene::Graph& graph, SelectionTest& test, const char* shader, const TextureProjection& projection, const ContentsFlagsValue& flags) +{ + Texturable texturable = Scene_getClosestTexturable(graph, test); + if(texturable.setTexture != SetTextureCallback()) + { + texturable.setTexture(shader, projection, flags); + } +} + + +class FaceTexture +{ +public: + TextureProjection m_projection; + ContentsFlagsValue m_flags; +}; + +FaceTexture g_faceTextureClipboard; + +void FaceTextureClipboard_setDefault() +{ + g_faceTextureClipboard.m_flags = ContentsFlagsValue(0, 0, 0, false); + TexDef_Construct_Default(g_faceTextureClipboard.m_projection); +} + +void TextureClipboard_textureSelected(const char* shader) +{ + FaceTextureClipboard_setDefault(); +} + +class TextureBrowser; +extern TextureBrowser g_TextureBrowser; +void TextureBrowser_SetSelectedShader(TextureBrowser& textureBrowser, const char* shader); +const char* TextureBrowser_GetSelectedShader(TextureBrowser& textureBrowser); + +void Scene_copyClosestTexture(SelectionTest& test) +{ + CopiedString shader; + if(Scene_getClosestTexture(GlobalSceneGraph(), test, shader, g_faceTextureClipboard.m_projection, g_faceTextureClipboard.m_flags)) + { + TextureBrowser_SetSelectedShader(g_TextureBrowser, shader.c_str()); + } +} + +void Scene_applyClosestTexture(SelectionTest& test) +{ + UndoableCommand command("facePaintTexture"); + + Scene_setClosestTexture(GlobalSceneGraph(), test, TextureBrowser_GetSelectedShader(g_TextureBrowser), g_faceTextureClipboard.m_projection, g_faceTextureClipboard.m_flags); + + SceneChangeNotify(); +} + + + + + +void SelectedFaces_copyTexture() +{ + if(!g_SelectedFaceInstances.empty()) + { + Face& face = g_SelectedFaceInstances.last().getFace(); + face.GetTexdef(g_faceTextureClipboard.m_projection); + g_faceTextureClipboard.m_flags = face.getShader().m_flags; + + TextureBrowser_SetSelectedShader(g_TextureBrowser, face.getShader().getShader()); + } +} + +void FaceInstance_pasteTexture(FaceInstance& faceInstance) +{ + faceInstance.getFace().SetTexdef(g_faceTextureClipboard.m_projection); + faceInstance.getFace().SetShader(TextureBrowser_GetSelectedShader(g_TextureBrowser)); + faceInstance.getFace().SetFlags(g_faceTextureClipboard.m_flags); + SceneChangeNotify(); +} + +bool SelectedFaces_empty() +{ + return g_SelectedFaceInstances.empty(); +} + +void SelectedFaces_pasteTexture() +{ + UndoableCommand command("facePasteTexture"); + g_SelectedFaceInstances.foreach(FaceInstance_pasteTexture); +} + + + +void SurfaceInspector_constructPreferences(PreferencesPage& page) +{ + page.appendCheckBox("", "Surface Inspector Increments Match Grid", g_si_globals.m_bSnapTToGrid); +} +void SurfaceInspector_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Surface Inspector", "Surface Inspector Preferences")); + SurfaceInspector_constructPreferences(page); +} +void SurfaceInspector_registerPreferencesPage() +{ + PreferencesDialog_addSettingsPage(FreeCaller1()); +} + +void SurfaceInspector_registerCommands() +{ + GlobalCommands_insert("FitTexture", FreeCaller(), Accelerator('B', (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("SurfaceInspector", FreeCaller(), Accelerator('S')); + + GlobalCommands_insert("FaceCopyTexture", FreeCaller()); + GlobalCommands_insert("FacePasteTexture", FreeCaller()); +} + + +#include "preferencesystem.h" + + +void SurfaceInspector_Construct() +{ + g_SurfaceInspector = new SurfaceInspector; + + SurfaceInspector_registerCommands(); + + FaceTextureClipboard_setDefault(); + + GlobalPreferenceSystem().registerPreference("SurfaceWnd", getSurfaceInspector().m_importPosition, getSurfaceInspector().m_exportPosition); + GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Scale1", FloatImportStringCaller(g_si_globals.scale[0]), FloatExportStringCaller(g_si_globals.scale[0])); + GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Scale2", FloatImportStringCaller(g_si_globals.scale[1]), FloatExportStringCaller(g_si_globals.scale[1])); + GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Shift1", FloatImportStringCaller(g_si_globals.shift[0]), FloatExportStringCaller(g_si_globals.shift[0])); + GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Shift2", FloatImportStringCaller(g_si_globals.shift[1]), FloatExportStringCaller(g_si_globals.shift[1])); + GlobalPreferenceSystem().registerPreference("SI_SurfaceTexdef_Rotate", FloatImportStringCaller(g_si_globals.rotate), FloatExportStringCaller(g_si_globals.rotate)); + GlobalPreferenceSystem().registerPreference("SnapTToGrid", BoolImportStringCaller(g_si_globals.m_bSnapTToGrid), BoolExportStringCaller(g_si_globals.m_bSnapTToGrid)); + + typedef FreeCaller1 SurfaceInspectorSelectionChangedCaller; + GlobalSelectionSystem().addSelectionChangeCallback(SurfaceInspectorSelectionChangedCaller()); + typedef FreeCaller SurfaceInspectorUpdateSelectionCaller; + Brush_addTextureChangedCallback(SurfaceInspectorUpdateSelectionCaller()); + Patch_addTextureChangedCallback(SurfaceInspectorUpdateSelectionCaller()); + + SurfaceInspector_registerPreferencesPage(); +} +void SurfaceInspector_Destroy() +{ + delete g_SurfaceInspector; +} + + + +#if TEXTOOL_ENABLED + +namespace TexTool { // namespace hides these symbols from other object-files +// +//Shamus: Textool functions, including GTK+ callbacks +// + +//NOTE: Black screen when TT first comes up is caused by an uninitialized Extent... !!! FIX !!! +// But... You can see down below that it *is* initialized! WTF? +struct Extent +{ + float minX, minY, maxX, maxY; + float width(void) { return fabs(maxX - minX); } + float height(void) { return fabs(maxY - minY); } +}; + +//This seems to control the texture scale... (Yep! ;-) +Extent extents = { -2.0f, -2.0f, +2.0f, +2.0f }; +brushprimit_texdef_t tm; // Texture transform matrix +Vector2 pts[c_brush_maxFaces]; +Vector2 center; +int numPts; +int textureNum; +Vector2 textureSize; +Vector2 windowSize; +#define VP_PADDING 1.2 +#define PI 3.14159265358979 +bool lButtonDown = false; +bool rButtonDown = false; +//int dragPoint; +//int anchorPoint; +bool haveAnchor = false; +brushprimit_texdef_t currentBP; +brushprimit_texdef_t origBP; // Original brush primitive (before we muck it up) +float controlRadius = 5.0f; +float rotationAngle = 0.0f; +float rotationAngle2 = 0.0f; +float oldRotationAngle; +Vector2 rotationPoint; +bool translatingX = false; // Widget state variables +bool translatingY = false; +bool scalingX = false; +bool scalingY = false; +bool rotating = false; +bool resizingX = false; // Not sure what this means... :-/ +bool resizingY = false; +float origAngle, origScaleX, origScaleY; +Vector2 oldCenter; + + +// Function prototypes (move up to top later...) + +void DrawCircularArc(Vector2 ctr, float startAngle, float endAngle, float radius); + + +void CopyPointsFromSelectedFace(void) +{ + // Make sure that there's a face and winding to get! + + if (g_SelectedFaceInstances.empty()) + { + numPts = 0; + return; + } + + Face & face = g_SelectedFaceInstances.last().getFace(); + textureNum = face.getShader().m_state->getTexture().texture_number; + textureSize.x() = face.getShader().m_state->getTexture().width; + textureSize.y() = face.getShader().m_state->getTexture().height; +//globalOutputStream() << "--> Texture #" << textureNum << ": " << textureSize.x() << " x " << textureSize.y() << "...\n"; + + currentBP = SurfaceInspector_GetSelectedTexdef().m_brushprimit_texdef; + + face.EmitTextureCoordinates(); + Winding & w = face.getWinding(); + int count = 0; + + for(Winding::const_iterator i=w.begin(); i!=w.end(); i++) + { + //globalOutputStream() << (*i).texcoord.x() << " " << (*i).texcoord.y() << ", "; + pts[count].x() = (*i).texcoord.x(); + pts[count].y() = (*i).texcoord.y(); + count++; + } + + numPts = count; + + //globalOutputStream() << " ..copied points\n"; +} + + brushprimit_texdef_t bp; +//This approach is probably wrongheaded and just not right anyway. So, !!! FIX !!! [DONE] +void CommitChanges(void) +{ + texdef_t t; // Throwaway, since this is BP only + + bp.coords[0][0] = tm.coords[0][0] * origBP.coords[0][0] + tm.coords[0][1] * origBP.coords[1][0]; + bp.coords[0][1] = tm.coords[0][0] * origBP.coords[0][1] + tm.coords[0][1] * origBP.coords[1][1]; + bp.coords[0][2] = tm.coords[0][0] * origBP.coords[0][2] + tm.coords[0][1] * origBP.coords[1][2] + tm.coords[0][2]; +//Ok, this works for translation... +// bp.coords[0][2] = tm.coords[0][0] * origBP.coords[0][2] + tm.coords[0][1] * origBP.coords[1][2] + tm.coords[0][2] * textureSize.x(); + bp.coords[1][0] = tm.coords[1][0] * origBP.coords[0][0] + tm.coords[1][1] * origBP.coords[1][0]; + bp.coords[1][1] = tm.coords[1][0] * origBP.coords[0][1] + tm.coords[1][1] * origBP.coords[1][1]; + bp.coords[1][2] = tm.coords[1][0] * origBP.coords[0][2] + tm.coords[1][1] * origBP.coords[1][2] + tm.coords[1][2]; +// bp.coords[1][2] = tm.coords[1][0] * origBP.coords[0][2] + tm.coords[1][1] * origBP.coords[1][2] + tm.coords[1][2] * textureSize.y(); + +//This doesn't work: g_brush_texture_changed(); +// Let's try this: +//Note: We should only set an undo *after* the button has been released... !!! FIX !!! +//Definitely *should* have an undo, though! +// UndoableCommand undo("textureProjectionSetSelected"); + Select_SetTexdef(TextureProjection(t, bp, Vector3(0, 0, 0), Vector3(0, 0, 0))); +//This is working, but for some reason the translate is causing the rest of the SI +//widgets to yield bad readings... !!! FIX !!! +//I.e., click on textool window, translate face wireframe, then controls go crazy. Dunno why. +//It's because there were some uncommented out add/removeScale functions in brush.h and a +//removeScale in brushmanip.cpp... :-/ +//Translate isn't working at all now... :-( +//It's because we need to multiply in some scaling factor (prolly the texture width/height) +//Yep. :-P +} + +void UpdateControlPoints(void) +{ + CommitChanges(); + + // Init texture transform matrix + + tm.coords[0][0] = 1.0f; tm.coords[0][1] = 0.0f; tm.coords[0][2] = 0.0f; + tm.coords[1][0] = 0.0f; tm.coords[1][1] = 1.0f; tm.coords[1][2] = 0.0f; +} + + +/* +For shifting we have: +*/ +/* +The code that should provide reasonable defaults, but doesn't for some reason: +It's scaling the BP by 128 for some reason, between the time it's created and the +time we get back to the SI widgets: + +static void OnBtnAxial(GtkWidget *widget, gpointer data) +{ + UndoableCommand undo("textureDefault"); + TextureProjection projection; + TexDef_Construct_Default(projection); + Select_SetTexdef(projection); +} + +Select_SetTexdef() calls Scene_BrushSetTexdef_Component_Selected(GlobalSceneGraph(), projection) +which is in brushmanip.h: This eventually calls +Texdef_Assign(m_texdef, texdef, m_brushprimit_texdef, brushprimit_texdef) in class Face... +which just copies from brushpr to m_brushpr... +*/ + +//Small problem with this thing: It's scaled to the texture which is all screwed up... !!! FIX !!! [DONE] +//Prolly should separate out the grid drawing so that we can draw it behind the polygon. +const float gridWidth = 1.3f;// Let's try an absolute height... WORKS!!! +// NOTE that 2.0 is the height of the viewport. Dunno why... Should make collision +// detection easier... +const float gridRadius = gridWidth * 0.5f; + +typedef const float WidgetColor[3]; +const WidgetColor widgetColor[10] = { + { 1.0000f, 0.2000f, 0.0000f }, // Red + { 0.9137f, 0.9765f, 0.4980f }, // Yellow + { 0.0000f, 0.6000f, 0.3216f }, // Green + { 0.6157f, 0.7726f, 0.8196f }, // Cyan + { 0.4980f, 0.5000f, 0.4716f }, // Grey + + // Highlight colors + { 1.0000f, 0.6000f, 0.4000f }, // Light Red + { 1.0000f, 1.0000f, 0.8980f }, // Light Yellow + { 0.4000f, 1.0000f, 0.7216f }, // Light Green + { 1.0000f, 1.0000f, 1.0000f }, // Light Cyan + { 0.8980f, 0.9000f, 0.8716f } // Light Grey +}; + +#define COLOR_RED 0 +#define COLOR_YELLOW 1 +#define COLOR_GREEN 2 +#define COLOR_CYAN 3 +#define COLOR_GREY 4 +#define COLOR_LT_RED 5 +#define COLOR_LT_YELLOW 6 +#define COLOR_LT_GREEN 7 +#define COLOR_LT_CYAN 8 +#define COLOR_LT_GREY 9 + +void DrawControlWidgets(void) +{ +//Note that the grid should go *behind* the face outline... !!! FIX !!! + // Grid + float xStart = center.x() - (gridWidth / 2.0f); + float yStart = center.y() - (gridWidth / 2.0f); + float xScale = (extents.height() / extents.width()) * (textureSize.y() / textureSize.x()); + + glPushMatrix(); +//Small problem with this approach: Changing the center point in the TX code doesn't seem to +//change anything here--prolly because we load a new identity matrix. A couple of ways to fix +//this would be to get rid of that code, or change the center to a new point by taking into +//account the transforms that we toss with the new identity matrix. Dunno which is better. + glLoadIdentity(); + glScalef(xScale, 1.0, 1.0); // Will that square it up? Yup. + glRotatef(static_cast(radians_to_degrees(atan2(-currentBP.coords[0][1], currentBP.coords[0][0]))), 0.0, 0.0, -1.0); + glTranslatef(-center.x(), -center.y(), 0.0); + + // Circle + glColor3fv(translatingX && translatingY ? widgetColor[COLOR_LT_YELLOW] : widgetColor[COLOR_YELLOW]); + glBegin(GL_LINE_LOOP); + DrawCircularArc(center, 0, 2.0f * PI, gridRadius * 0.16); + + glEnd(); + + // Axes + glBegin(GL_LINES); + glColor3fv(translatingY && !translatingX ? widgetColor[COLOR_LT_GREEN] : widgetColor[COLOR_GREEN]); + glVertex2f(center.x(), center.y() + (gridRadius * 0.16)); + glVertex2f(center.x(), center.y() + (gridRadius * 1.00)); + glColor3fv(translatingX && !translatingY ? widgetColor[COLOR_LT_RED] : widgetColor[COLOR_RED]); + glVertex2f(center.x() + (gridRadius * 0.16), center.y()); + glVertex2f(center.x() + (gridRadius * 1.00), center.y()); + glEnd(); + + // Arrowheads + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glBegin(GL_TRIANGLES); + glColor3fv(translatingY && !translatingX ? widgetColor[COLOR_LT_GREEN] : widgetColor[COLOR_GREEN]); + glVertex2f(center.x(), center.y() + (gridRadius * 1.10)); + glVertex2f(center.x() + (gridRadius * 0.06), center.y() + (gridRadius * 0.94)); + glVertex2f(center.x() - (gridRadius * 0.06), center.y() + (gridRadius * 0.94)); + glColor3fv(translatingX && !translatingY ? widgetColor[COLOR_LT_RED] : widgetColor[COLOR_RED]); + glVertex2f(center.x() + (gridRadius * 1.10), center.y()); + glVertex2f(center.x() + (gridRadius * 0.94), center.y() + (gridRadius * 0.06)); + glVertex2f(center.x() + (gridRadius * 0.94), center.y() - (gridRadius * 0.06)); + glEnd(); + + // Arc + glBegin(GL_LINE_STRIP); + glColor3fv(rotating ? widgetColor[COLOR_LT_CYAN] : widgetColor[COLOR_CYAN]); + DrawCircularArc(center, 0.03f * PI, 0.47f * PI, gridRadius * 0.90); + glEnd(); + + // Boxes + glColor3fv(scalingY && !scalingX ? widgetColor[COLOR_LT_GREEN] : widgetColor[COLOR_GREEN]); + glBegin(GL_LINES); + glVertex2f(center.x() + (gridRadius * 0.20), center.y() + (gridRadius * 1.50)); + glVertex2f(center.x() - (gridRadius * 0.20), center.y() + (gridRadius * 1.50)); + glEnd(); + glBegin(GL_LINE_LOOP); + glVertex2f(center.x() + (gridRadius * 0.10), center.y() + (gridRadius * 1.40)); + glVertex2f(center.x() - (gridRadius * 0.10), center.y() + (gridRadius * 1.40)); + glVertex2f(center.x() - (gridRadius * 0.10), center.y() + (gridRadius * 1.20)); + glVertex2f(center.x() + (gridRadius * 0.10), center.y() + (gridRadius * 1.20)); + glEnd(); + + glColor3fv(scalingX && !scalingY ? widgetColor[COLOR_LT_RED] : widgetColor[COLOR_RED]); + glBegin(GL_LINES); + glVertex2f(center.x() + (gridRadius * 1.50), center.y() + (gridRadius * 0.20)); + glVertex2f(center.x() + (gridRadius * 1.50), center.y() - (gridRadius * 0.20)); + glEnd(); + glBegin(GL_LINE_LOOP); + glVertex2f(center.x() + (gridRadius * 1.40), center.y() + (gridRadius * 0.10)); + glVertex2f(center.x() + (gridRadius * 1.40), center.y() - (gridRadius * 0.10)); + glVertex2f(center.x() + (gridRadius * 1.20), center.y() - (gridRadius * 0.10)); + glVertex2f(center.x() + (gridRadius * 1.20), center.y() + (gridRadius * 0.10)); + glEnd(); + + glColor3fv(scalingX && scalingY ? widgetColor[COLOR_LT_CYAN] : widgetColor[COLOR_CYAN]); + glBegin(GL_LINE_STRIP); + glVertex2f(center.x() + (gridRadius * 1.50), center.y() + (gridRadius * 1.10)); + glVertex2f(center.x() + (gridRadius * 1.50), center.y() + (gridRadius * 1.50)); + glVertex2f(center.x() + (gridRadius * 1.10), center.y() + (gridRadius * 1.50)); + glEnd(); + glBegin(GL_LINE_LOOP); + glVertex2f(center.x() + (gridRadius * 1.40), center.y() + (gridRadius * 1.40)); + glVertex2f(center.x() + (gridRadius * 1.40), center.y() + (gridRadius * 1.20)); + glVertex2f(center.x() + (gridRadius * 1.20), center.y() + (gridRadius * 1.20)); + glVertex2f(center.x() + (gridRadius * 1.20), center.y() + (gridRadius * 1.40)); + glEnd(); + + glPopMatrix(); +} + +void DrawControlPoints(void) +{ + glColor3f(1, 1, 1); + glBegin(GL_LINE_LOOP); + + for(int i=0; i extents.maxX) + extents.maxX = pts[i].x(); + if (pts[i].y() < extents.minY) + extents.minY = pts[i].y(); + if (pts[i].y() > extents.maxY) + extents.maxY = pts[i].y(); + } + + // Do some viewport fitting stuff... + +//globalOutputStream() << "--> Center: " << center.x() << ", " << center.y() << "\n"; +//globalOutputStream() << "--> Extents (stage 1): " << extents.minX << ", " +// << extents.maxX << ", " << extents.minY << ", " << extents.maxY << "\n"; + // TTimo: Apply a ratio to get the area we'll draw. + center.x() = 0.5f * (extents.minX + extents.maxX), + center.y() = 0.5f * (extents.minY + extents.maxY); + extents.minX = center.x() + VP_PADDING * (extents.minX - center.x()), + extents.minY = center.y() + VP_PADDING * (extents.minY - center.y()), + extents.maxX = center.x() + VP_PADDING * (extents.maxX - center.x()), + extents.maxY = center.y() + VP_PADDING * (extents.maxY - center.y()); +//globalOutputStream() << "--> Extents (stage 2): " << extents.minX << ", " +// << extents.maxX << ", " << extents.minY << ", " << extents.maxY << "\n"; + + // TTimo: We want a texture with the same X / Y ratio. + // TTimo: Compute XY space / window size ratio. + float SSize = extents.width(), TSize = extents.height(); + float ratioX = textureSize.x() * extents.width() / windowSize.x(), + ratioY = textureSize.y() * extents.height() / windowSize.y(); +//globalOutputStream() << "--> Texture size: " << textureSize.x() << ", " << textureSize.y() << "\n"; +//globalOutputStream() << "--> Window size: " << windowSize.x() << ", " << windowSize.y() << "\n"; + + if (ratioX > ratioY) + { + TSize = (windowSize.y() * ratioX) / textureSize.y(); +// TSize = extents.width() * (windowSize.y() / windowSize.x()) * (textureSize.x() / textureSize.y()); + } + else + { + SSize = (windowSize.x() * ratioY) / textureSize.x(); +// SSize = extents.height() * (windowSize.x() / windowSize.y()) * (textureSize.y() / textureSize.x()); + } + + extents.minX = center.x() - 0.5f * SSize, extents.maxX = center.x() + 0.5f * SSize, + extents.minY = center.y() - 0.5f * TSize, extents.maxY = center.y() + 0.5f * TSize; +//globalOutputStream() << "--> Extents (stage 3): " << extents.minX << ", " +// << extents.maxX << ", " << extents.minY << ", " << extents.maxY << "\n"; +} + +gboolean size_allocate(GtkWidget * win, GtkAllocation * a, gpointer) +{ + windowSize.x() = a->width; + windowSize.y() = a->height; + queueDraw(); + return false; +} + +gboolean expose(GtkWidget * win, GdkEventExpose * e, gpointer) +{ +// globalOutputStream() << "--> Textool Window was exposed!\n"; +// globalOutputStream() << " (window width/height: " << cc << "/" << e->area.height << ")\n"; + +// windowSize.x() = e->area.width, windowSize.y() = e->area.height; +//This needs to go elsewhere... +// InitTextool(); + + if (glwidget_make_current(win) == FALSE) + { + globalOutputStream() << " FAILED to make current! Oh, the agony! :-(\n"; + return true; + } + + CopyPointsFromSelectedFace(); + + if(!lButtonDown) + { + focus(); + } + + // Probably should init button/anchor states here as well... +// rotationAngle = 0.0f; + glClearColor(0, 0, 0, 0); + glViewport(0, 0, e->area.width, e->area.height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + +//??? + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + + glOrtho(extents.minX, extents.maxX, extents.maxY, extents.minY, -1, 1); + + glColor3f(1, 1, 1); + // draw the texture background + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glBindTexture(GL_TEXTURE_2D, textureNum); + + glEnable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + glTexCoord2f(extents.minX, extents.minY); + glVertex2f(extents.minX, extents.minY); + glTexCoord2f(extents.maxX, extents.minY); + glVertex2f(extents.maxX, extents.minY); + glTexCoord2f(extents.maxX, extents.maxY); + glVertex2f(extents.maxX, extents.maxY); + glTexCoord2f(extents.minX, extents.maxY); + glVertex2f(extents.minX, extents.maxY); + glEnd(); + glDisable(GL_TEXTURE_2D); + + // draw the texture-space grid + glColor3fv(widgetColor[COLOR_GREY]); + glBegin(GL_LINES); + + const int gridSubdivisions = 8; + const float gridExtents = 4.0f; + + for(int i = 0; i < gridSubdivisions + 1; ++i) + { + float y = i * (gridExtents / float(gridSubdivisions)); + float x = i * (gridExtents / float(gridSubdivisions)); + glVertex2f(0, y); + glVertex2f(gridExtents, y); + glVertex2f(x, 0); + glVertex2f(x, gridExtents); + } + + glEnd(); + + DrawControlPoints(); + DrawControlWidgets(); +//??? + // reset the current texture +// glBindTexture(GL_TEXTURE_2D, 0); +// glFinish(); + glwidget_swap_buffers(win); + + return false; +} + +/*int FindSelectedPoint(int x, int y) +{ + for(int i=0; i Textool button press...\n"; + + if (e->button == 1) + { + lButtonDown = true; + GlobalUndoSystem().start(); + + origBP = currentBP; + + //globalOutputStream() << "--> Original BP: [" << origBP.coords[0][0] << "][" << origBP.coords[0][1] << "][" << origBP.coords[0][2] << "]\n"; + //globalOutputStream() << " [" << origBP.coords[1][0] << "][" << origBP.coords[1][1] << "][" << origBP.coords[1][2] << "]\n"; + //float angle = atan2(origBP.coords[0][1], origBP.coords[0][0]) * 180.0f / 3.141592653589f; + origAngle = (origBP.coords[0][1] > 0 ? PI : -PI); // Could also be -PI... !!! FIX !!! [DONE] + + if (origBP.coords[0][0] != 0.0f) + origAngle = atan(origBP.coords[0][1] / origBP.coords[0][0]); + + origScaleX = origBP.coords[0][0] / cos(origAngle); + origScaleY = origBP.coords[1][1] / cos(origAngle); + rotationAngle = origAngle; + oldCenter[0] = oldCenter[1] = 0; + + //globalOutputStream() << "--> BP stats: ang=" << origAngle * RAD_TO_DEG << ", scale=" << origScaleX << "/" << origScaleY << "\n"; + //Should also set the Flip X/Y checkboxes here as well... !!! FIX !!! + //Also: should reverse texture left/right up/down instead of flipping the points... + +//disnowok +//float nx = windowSize.x() * (e->x - extents.minX) / (extents.maxX - extents.minX); +//float ny = windowSize.y() * (e->y - extents.minY) / (extents.maxY - extents.minY); +//disdoes... +//But I want it to scroll the texture window, not the points... !!! FIX !!! +//Actually, should scroll the texture window only when mouse is down on no widgets... + float nx = e->x / windowSize.x() * extents.width() + extents.minX; + float ny = e->y / windowSize.y() * extents.height() + extents.minY; + trans.x() = -tm.coords[0][0] * nx - tm.coords[0][1] * ny; + trans.y() = -tm.coords[1][0] * nx - tm.coords[1][1] * ny; + + dragPoint.x() = e->x, dragPoint.y() = e->y; + trans2.x() = nx, trans2.y() = ny; + oldRotationAngle = rotationAngle; +// oldTrans.x() = tm.coords[0][2] - nx * textureSize.x(); +// oldTrans.y() = tm.coords[1][2] - ny * textureSize.y(); + oldTrans.x() = tm.coords[0][2]; + oldTrans.y() = tm.coords[1][2]; + oldCenter.x() = center.x(); + oldCenter.y() = center.y(); + + queueDraw(); + + return true; + } +/* else if (e->button == 3) + { + rButtonDown = true; + }//*/ + +//globalOutputStream() << "(" << (haveAnchor ? "anchor" : "released") << ")\n"; + + return false; +} + +gboolean button_release(GtkWidget * win, GdkEventButton * e, gpointer) +{ +// globalOutputStream() << "--> Textool button release...\n"; + + if (e->button == 1) + { +/* float ptx = e->x / windowSize.x() * extents.width() + extents.minX; + float pty = e->y / windowSize.y() * extents.height() + extents.minY; + +//This prolly should go into the mouse move code... +//Doesn't work correctly anyway... + if (translatingX || translatingY) + center.x() = ptx, center.y() = pty;//*/ + + lButtonDown = false; + + if(translatingX || translatingY) + { + GlobalUndoSystem().finish("translateTexture"); + } + else if(rotating) + { + GlobalUndoSystem().finish("rotateTexture"); + } + else if(scalingX || scalingY) + { + GlobalUndoSystem().finish("scaleTexture"); + } + else if(resizingX || resizingY) + { + GlobalUndoSystem().finish("resizeTexture"); + } + else + { + GlobalUndoSystem().finish("textoolUnknown"); + } + + rotating = translatingX = translatingY = scalingX = scalingY + = resizingX = resizingY = false; + + queueDraw(); + } + else if (e->button == 3) + { + rButtonDown = false; + } + + return true; +} + +/* +void C2DView::GridForWindow( float c[2], int x, int y) +{ + SpaceForWindow( c, x, y ); + if ( !m_bDoGrid ) + return; + c[0] /= m_GridStep[0]; + c[1] /= m_GridStep[1]; + c[0] = (float)floor( c[0] + 0.5f ); + c[1] = (float)floor( c[1] + 0.5f ); + c[0] *= m_GridStep[0]; + c[1] *= m_GridStep[1]; +} +void C2DView::SpaceForWindow( float c[2], int x, int y) +{ + c[0] = ((float)(x))/((float)(m_rect.right-m_rect.left))*(m_Maxs[0]-m_Mins[0])+m_Mins[0]; + c[1] = ((float)(y))/((float)(m_rect.bottom-m_rect.top))*(m_Maxs[1]-m_Mins[1])+m_Mins[1]; +} +*/ +gboolean motion(GtkWidget * win, GdkEventMotion * e, gpointer) +{ +// globalOutputStream() << "--> Textool motion...\n"; + + if (lButtonDown) + { + if (translatingX || translatingY) + { + float ptx = e->x / windowSize.x() * extents.width() + extents.minX; + float pty = e->y / windowSize.y() * extents.height() + extents.minY; + +//Need to fix this to take the rotation angle into account, so that it moves along +//the rotated X/Y axis... + if (translatingX) + { +// tm.coords[0][2] = (trans.x() + ptx) * textureSize.x(); +//This works, but only when the angle is zero. !!! FIX !!! [DONE] +// tm.coords[0][2] = oldCenter.x() + (ptx * textureSize.x()); + tm.coords[0][2] = oldTrans.x() + (ptx - trans2.x()) * textureSize.x(); +// center.x() = oldCenter.x() + (ptx - trans2.x()); + } + + if (translatingY) + { +// tm.coords[1][2] = (trans.y() + pty) * textureSize.y(); +// tm.coords[1][2] = oldCenter.y() + (pty * textureSize.y()); + tm.coords[1][2] = oldTrans.y() + (pty - trans2.y()) * textureSize.y(); +// center.y() = oldCenter.y() + (pty - trans2.y()); + } + +//Need to update center.x/y() so that the widget translates as well. Also, oldCenter +//is badly named... Should be oldTrans or something like that... !!! FIX !!! +//Changing center.x/y() here doesn't seem to change anything... :-/ + UpdateControlPoints(); + } + else if (rotating) + { + // Shamus: New rotate code + int cx = (int)(windowSize.x() * (center.x() - extents.minX) / extents.width()); + int cy = (int)(windowSize.y() * (center.y() - extents.minY) / extents.height()); + Vector3 v1(dragPoint.x() - cx, dragPoint.y() - cy, 0), v2(e->x - cx, e->y - cy, 0); + + vector3_normalise(v1); + vector3_normalise(v2); + float c = vector3_dot(v1, v2); + Vector3 cross = vector3_cross(v1, v2); + float s = vector3_length(cross); + + if (cross[2] > 0) + s = -s; + +// Problem with this: arcsin/cos seems to only return -90 to 90 and 0 to 180... +// Can't derive angle from that! + +//rotationAngle = asin(s);// * 180.0f / 3.141592653589f; +rotationAngle = acos(c); +//rotationAngle2 = asin(s); +if (cross[2] < 0) + rotationAngle = -rotationAngle; + +//NO! DOESN'T WORK! rotationAngle -= 45.0f * DEG_TO_RAD; +//Let's try this: +//No wok. +/*c = cos(rotationAngle - oldRotationAngle); +s = sin(rotationAngle - oldRotationAngle); +rotationAngle += oldRotationAngle; +//c += cos(oldRotationAngle); +//s += sin(oldRotationAngle); +//rotationAngle += oldRotationAngle; +//c %= 2.0 * PI; +//s %= 2.0 * PI; +//rotationAngle %= 2.0 * PI;//*/ + +//This is wrong... Hmm... +//It seems to shear the texture instead of rotating it... !!! FIX !!! +// Now it rotates correctly. Seems TTimo was overcomplicating things here... ;-) + +// Seems like what needs to happen here is multiplying these rotations by tm... !!! FIX !!! + +// See brush_primit.cpp line 244 (Texdef_EmitTextureCoordinates()) for where texcoords come from... + + tm.coords[0][0] = c; + tm.coords[0][1] = s; + tm.coords[1][0] = -s; + tm.coords[1][1] = c; +//It doesn't work anymore... Dunno why... +//tm.coords[0][2] = -trans.x(); // This works!!! Yeah!!! +//tm.coords[1][2] = -trans.y(); +//nope. +//tm.coords[0][2] = rotationPoint.x(); // This works, but strangely... +//tm.coords[1][2] = rotationPoint.y(); +//tm.coords[0][2] = 0;// center.x() / 2.0f; +//tm.coords[1][2] = 0;// center.y() / 2.0f; +//No. +//tm.coords[0][2] = -(center.x() * textureSize.x()); +//tm.coords[1][2] = -(center.y() * textureSize.y()); +//Eh? No, but seems to be getting closer... +/*float ptx = e->x / windowSize.x() * extents.width() + extents.minX; +float pty = e->y / windowSize.y() * extents.height() + extents.minY; +tm.coords[0][2] = -c * center.x() - s * center.y() + ptx; +tm.coords[1][2] = s * center.x() - c * center.x() + pty;//*/ +//Kinda works, but center drifts around on non-square textures... +/*tm.coords[0][2] = (-c * center.x() - s * center.y()) * textureSize.x(); +tm.coords[1][2] = ( s * center.x() - c * center.y()) * textureSize.y();//*/ +//Rotates correctly, but not around the actual center of the face's points... +/*tm.coords[0][2] = -c * center.x() * textureSize.x() - s * center.y() * textureSize.y(); +tm.coords[1][2] = s * center.x() * textureSize.x() - c * center.y() * textureSize.y();//*/ +//Yes!!! + tm.coords[0][2] = (-c * center.x() * textureSize.x() - s * center.y() * textureSize.y()) + center.x() * textureSize.x(); + tm.coords[1][2] = ( s * center.x() * textureSize.x() - c * center.y() * textureSize.y()) + center.y() * textureSize.y();//*/ +//This doesn't work... +//And this is the wrong place for this anyway (I'm pretty sure). +/*tm.coords[0][2] += oldCenter.x(); +tm.coords[1][2] += oldCenter.y();//*/ + UpdateControlPoints(); // will cause a redraw + } + + return true; + } + else // Check for widget mouseovers + { + Vector2 tran; + float nx = e->x / windowSize.x() * extents.width() + extents.minX; + float ny = e->y / windowSize.y() * extents.height() + extents.minY; + // Translate nx/y to the "center" point... + nx -= center.x(); + ny -= center.y(); + ny = -ny; // Flip Y-axis so that increasing numbers move up + + tran.x() = tm.coords[0][0] * nx + tm.coords[0][1] * ny; + tran.y() = tm.coords[1][0] * nx + tm.coords[1][1] * ny; +//This doesn't seem to generate a valid distance from the center--for some reason it +//calculates a fixed number every time +//Look at nx/y above: they're getting fixed there! !!! FIX !!! [DONE] + float dist = sqrt((nx * nx) + (ny * ny)); + // Normalize to the 2.0 = height standard (for now) +//globalOutputStream() << "--> Distance before: " << dist; + dist = dist * 2.0f / extents.height(); +//globalOutputStream() << ". After: " << dist; + tran.x() = tran.x() * 2.0f / extents.height(); + tran.y() = tran.y() * 2.0f / extents.height(); +//globalOutputStream() << ". Trans: " << tran.x() << ", " << tran.y() << "\n"; + +//Let's try this instead... +//Interesting! It seems that e->x/y are rotated +//(no, they're not--the TM above is what's doing it...) +nx = ((e->x / windowSize.y()) * 2.0f) - (windowSize.x() / windowSize.y()); +ny = ((e->y / windowSize.y()) * 2.0f) - (windowSize.y() / windowSize.y()); +ny = -ny; +//Cool! It works! Now just need to do rotation... + + rotating = translatingX = translatingY = scalingX = scalingY + = resizingX = resizingY = false; + + if (dist < (gridRadius * 0.16f)) + { + translatingX = translatingY = true; + } + else if (dist > (gridRadius * 0.16f) && dist < (gridRadius * 1.10f) + && fabs(ny) < (gridRadius * 0.05f) && nx > 0) + { + translatingX = true; + } + else if (dist > (gridRadius * 0.16f) && dist < (gridRadius * 1.10f) + && fabs(nx) < (gridRadius * 0.05f) && ny > 0) + { + translatingY = true; + } + // Should tighten up the angle on this, or put this test after the axis tests... + else if (tran.x() > 0 && tran.y() > 0 + && (dist > (gridRadius * 0.82f) && dist < (gridRadius * 0.98f))) + { + rotating = true; + } + + queueDraw(); + + return true; + } + + return false; +} + +//It seems the fake tex coords conversion is screwing this stuff up... !!! FIX !!! +//This is still wrong... Prolly need to do something with the oldScaleX/Y stuff... +void flipX(GtkToggleButton *, gpointer) +{ +// globalOutputStream() << "--> Flip X...\n"; + //Shamus: +// SurfaceInspector_GetSelectedBPTexdef(); // Refresh g_selectedBrushPrimitTexdef... +// tm.coords[0][0] = -tm.coords[0][0]; +// tm.coords[1][0] = -tm.coords[1][0]; +// tm.coords[0][0] = -tm.coords[0][0]; // This should be correct now...Nope. +// tm.coords[1][1] = -tm.coords[1][1]; + tm.coords[0][0] = -tm.coords[0][0]; // This should be correct now... + tm.coords[1][0] = -tm.coords[1][0]; +// tm.coords[2][0] = -tm.coords[2][0];//wil wok? no. + UpdateControlPoints(); +} + +void flipY(GtkToggleButton *, gpointer) +{ +// globalOutputStream() << "--> Flip Y...\n"; +// tm.coords[0][1] = -tm.coords[0][1]; +// tm.coords[1][1] = -tm.coords[1][1]; +// tm.coords[0][1] = -tm.coords[0][1]; // This should be correct now...Nope. +// tm.coords[1][0] = -tm.coords[1][0]; + tm.coords[0][1] = -tm.coords[0][1]; // This should be correct now... + tm.coords[1][1] = -tm.coords[1][1]; +// tm.coords[2][1] = -tm.coords[2][1];//wil wok? no. + UpdateControlPoints(); +} + +} // end namespace TexTool + +#endif diff --git a/radiant/surfacedialog.h b/radiant/surfacedialog.h new file mode 100644 index 00000000..034cf1d6 --- /dev/null +++ b/radiant/surfacedialog.h @@ -0,0 +1,60 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_SURFACEDIALOG_H) +#define INCLUDED_SURFACEDIALOG_H + + +void SurfaceInspector_Construct(); +void SurfaceInspector_Destroy(); + +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkWindow GtkWindow; +void SurfaceInspector_constructWindow(GtkWindow* widget); +void SurfaceInspector_destroyWindow(); + +bool SelectedFaces_empty(); +void SelectedFaces_copyTexture(); +void SelectedFaces_pasteTexture(); +void FaceTextureClipboard_setDefault(); + + +// the increment we are using for the surface inspector (this is saved in the prefs) +struct si_globals_t +{ + float shift[2]; + float scale[2]; + float rotate; + + bool m_bSnapTToGrid; + + si_globals_t() : m_bSnapTToGrid(false) + { + shift[0] = 8.0f; + shift[1] = 8.0f; + scale[0] = 0.5f; + scale[1] = 0.5f; + rotate = 45.0f; + } +}; +extern si_globals_t g_si_globals; + +#endif diff --git a/radiant/texmanip.cpp b/radiant/texmanip.cpp new file mode 100644 index 00000000..8fa88ca2 --- /dev/null +++ b/radiant/texmanip.cpp @@ -0,0 +1,388 @@ +/* +Copyright (c) 2002 Forest "LordHavoc" Hale + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Forest Hale nor the names of other contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "texmanip.h" + +#include +#include "stream/textstream.h" + +static byte *row1 = NULL, *row2 = NULL; +static int rowsize = 0; + +void R_ResampleTextureLerpLine (const byte *in, byte *out, int inwidth, int outwidth, int bytesperpixel) +{ + int j, xi, oldx = 0, f, fstep, endx, lerp; +#define LERPBYTE(i) out[i] = (byte) ((((row2[i] - row1[i]) * lerp) >> 16) + row1[i]) + + fstep = (int) (inwidth * 65536.0f / outwidth); + endx = (inwidth - 1); + if (bytesperpixel == 4) + { + for (j = 0,f = 0;j < outwidth;j++, f += fstep) + { + xi = f >> 16; + if (xi != oldx) + { + in += (xi - oldx) * 4; + oldx = xi; + } + + if (xi < endx) + { + lerp = f & 0xFFFF; + *out++ = (byte) ((((in[4] - in[0]) * lerp) >> 16) + in[0]); + *out++ = (byte) ((((in[5] - in[1]) * lerp) >> 16) + in[1]); + *out++ = (byte) ((((in[6] - in[2]) * lerp) >> 16) + in[2]); + *out++ = (byte) ((((in[7] - in[3]) * lerp) >> 16) + in[3]); + } + else // last pixel of the line has no pixel to lerp to + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + *out++ = in[3]; + } + } + } + else if (bytesperpixel == 3) + { + for (j = 0, f = 0; j < outwidth; j++, f += fstep) + { + xi = f >> 16; + if (xi != oldx) + { + in += (xi - oldx) * 3; + oldx = xi; + } + + if (xi < endx) + { + lerp = f & 0xFFFF; + *out++ = (byte) ((((in[3] - in[0]) * lerp) >> 16) + in[0]); + *out++ = (byte) ((((in[4] - in[1]) * lerp) >> 16) + in[1]); + *out++ = (byte) ((((in[5] - in[2]) * lerp) >> 16) + in[2]); + } + else // last pixel of the line has no pixel to lerp to + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + } + } + } + else + { + globalOutputStream() << "R_ResampleTextureLerpLine: unsupported bytesperpixel " << bytesperpixel << "\n"; + } +} + +/* +================ +R_ResampleTexture +================ +*/ +void R_ResampleTexture (const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight, int bytesperpixel) +{ + if (rowsize < outwidth * bytesperpixel) + { + if (row1) + free(row1); + if (row2) + free(row2); + + rowsize = outwidth * bytesperpixel; + row1 = (byte *)malloc(rowsize); + row2 = (byte *)malloc(rowsize); + } + + if (bytesperpixel == 4) + { + int i, j, yi, oldy, f, fstep, lerp, endy = (inheight-1), inwidth4 = inwidth*4, outwidth4 = outwidth*4; + byte *inrow, *out; + out = (byte *)outdata; + fstep = (int) (inheight * 65536.0f / outheight); +#define LERPBYTE(i) out[i] = (byte) ((((row2[i] - row1[i]) * lerp) >> 16) + row1[i]) + + inrow = (byte *)indata; + oldy = 0; + R_ResampleTextureLerpLine (inrow, row1, inwidth, outwidth, bytesperpixel); + R_ResampleTextureLerpLine (inrow + inwidth4, row2, inwidth, outwidth, bytesperpixel); + + for (i = 0, f = 0;i < outheight;i++,f += fstep) + { + yi = f >> 16; + if (yi < endy) + { + lerp = f & 0xFFFF; + if (yi != oldy) + { + inrow = (byte *)indata + inwidth4 * yi; + if (yi == oldy+1) + memcpy(row1, row2, outwidth4); + else + R_ResampleTextureLerpLine (inrow, row1, inwidth, outwidth, bytesperpixel); + + R_ResampleTextureLerpLine (inrow + inwidth4, row2, inwidth, outwidth, bytesperpixel); + oldy = yi; + } + j = outwidth - 4; + while(j >= 0) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + LERPBYTE( 8); + LERPBYTE( 9); + LERPBYTE(10); + LERPBYTE(11); + LERPBYTE(12); + LERPBYTE(13); + LERPBYTE(14); + LERPBYTE(15); + out += 16; + row1 += 16; + row2 += 16; + j -= 4; + } + if (j & 2) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + out += 8; + row1 += 8; + row2 += 8; + } + if (j & 1) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + out += 4; + row1 += 4; + row2 += 4; + } + row1 -= outwidth4; + row2 -= outwidth4; + } + else + { + if (yi != oldy) + { + inrow = (byte *)indata + inwidth4*yi; + if (yi == oldy+1) + memcpy(row1, row2, outwidth4); + else + R_ResampleTextureLerpLine (inrow, row1, inwidth, outwidth, bytesperpixel); + + oldy = yi; + } + memcpy(out, row1, outwidth4); + } + } + } + else if (bytesperpixel == 3) + { + int i, j, yi, oldy, f, fstep, lerp, endy = (inheight-1), inwidth3 = inwidth * 3, outwidth3 = outwidth * 3; + byte *inrow, *out; + out = (byte *)outdata; + fstep = (int) (inheight*65536.0f/outheight); +#define LERPBYTE(i) out[i] = (byte) ((((row2[i] - row1[i]) * lerp) >> 16) + row1[i]) + + inrow = (byte *)indata; + oldy = 0; + R_ResampleTextureLerpLine (inrow, row1, inwidth, outwidth, bytesperpixel); + R_ResampleTextureLerpLine (inrow + inwidth3, row2, inwidth, outwidth, bytesperpixel); + for (i = 0, f = 0;i < outheight;i++,f += fstep) + { + yi = f >> 16; + if (yi < endy) + { + lerp = f & 0xFFFF; + if (yi != oldy) + { + inrow = (byte *)indata + inwidth3*yi; + if (yi == oldy+1) + memcpy(row1, row2, outwidth3); + else + R_ResampleTextureLerpLine (inrow, row1, inwidth, outwidth, bytesperpixel); + + R_ResampleTextureLerpLine (inrow + inwidth3, row2, inwidth, outwidth, bytesperpixel); + oldy = yi; + } + j = outwidth - 4; + while(j >= 0) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + LERPBYTE( 8); + LERPBYTE( 9); + LERPBYTE(10); + LERPBYTE(11); + out += 12; + row1 += 12; + row2 += 12; + j -= 4; + } + if (j & 2) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + out += 6; + row1 += 6; + row2 += 6; + } + if (j & 1) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + out += 3; + row1 += 3; + row2 += 3; + } + row1 -= outwidth3; + row2 -= outwidth3; + } + else + { + if (yi != oldy) + { + inrow = (byte *)indata + inwidth3*yi; + if (yi == oldy+1) + memcpy(row1, row2, outwidth3); + else + R_ResampleTextureLerpLine (inrow, row1, inwidth, outwidth, bytesperpixel); + + oldy = yi; + } + memcpy(out, row1, outwidth3); + } + } + } + else + { + globalOutputStream() << "R_ResampleTexture: unsupported bytesperpixel " << bytesperpixel << "\n"; + } +} + +// in can be the same as out +void GL_MipReduce(byte *in, byte *out, int width, int height, int destwidth, int destheight) +{ + int x, y, width2, height2, nextrow; + if (width > destwidth) + { + if (height > destheight) + { + // reduce both + width2 = width >> 1; + height2 = height >> 1; + nextrow = width << 2; + for (y = 0;y < height2;y++) + { + for (x = 0;x < width2;x++) + { + out[0] = (byte) ((in[0] + in[4] + in[nextrow ] + in[nextrow+4]) >> 2); + out[1] = (byte) ((in[1] + in[5] + in[nextrow+1] + in[nextrow+5]) >> 2); + out[2] = (byte) ((in[2] + in[6] + in[nextrow+2] + in[nextrow+6]) >> 2); + out[3] = (byte) ((in[3] + in[7] + in[nextrow+3] + in[nextrow+7]) >> 2); + out += 4; + in += 8; + } + in += nextrow; // skip a line + } + } + else + { + // reduce width + width2 = width >> 1; + for (y = 0;y < height;y++) + { + for (x = 0;x < width2;x++) + { + out[0] = (byte) ((in[0] + in[4]) >> 1); + out[1] = (byte) ((in[1] + in[5]) >> 1); + out[2] = (byte) ((in[2] + in[6]) >> 1); + out[3] = (byte) ((in[3] + in[7]) >> 1); + out += 4; + in += 8; + } + } + } + } + else + { + if (height > destheight) + { + // reduce height + height2 = height >> 1; + nextrow = width << 2; + for (y = 0;y < height2;y++) + { + for (x = 0;x < width;x++) + { + out[0] = (byte) ((in[0] + in[nextrow ]) >> 1); + out[1] = (byte) ((in[1] + in[nextrow+1]) >> 1); + out[2] = (byte) ((in[2] + in[nextrow+2]) >> 1); + out[3] = (byte) ((in[3] + in[nextrow+3]) >> 1); + out += 4; + in += 4; + } + in += nextrow; // skip a line + } + } + else + { + globalOutputStream() << "GL_MipReduce: desired size already achieved\n"; + } + } +} diff --git a/radiant/texmanip.h b/radiant/texmanip.h new file mode 100644 index 00000000..3cca3ce2 --- /dev/null +++ b/radiant/texmanip.h @@ -0,0 +1,40 @@ +/* +Copyright (c) 2002 Forest "LordHavoc" Hale + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Forest Hale nor the names of other contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(INCLUDED_TEXMANIP_H) +#define INCLUDED_TEXMANIP_H + +typedef unsigned char byte; + +void R_ResampleTexture (const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight, int bytesperpixel); +void GL_MipReduce(byte *in, byte *out, int width, int height, int destwidth, int destheight); + +#endif diff --git a/radiant/textureentry.cpp b/radiant/textureentry.cpp new file mode 100644 index 00000000..615143c1 --- /dev/null +++ b/radiant/textureentry.cpp @@ -0,0 +1,23 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "textureentry.h" + diff --git a/radiant/textureentry.h b/radiant/textureentry.h new file mode 100644 index 00000000..3965fe29 --- /dev/null +++ b/radiant/textureentry.h @@ -0,0 +1,129 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_TEXTUREENTRY_H) +#define INCLUDED_TEXTUREENTRY_H + + +#include +#include +#include "gtkutil/idledraw.h" + +#include "generic/static.h" +#include "signal/isignal.h" +#include "shaderlib.h" + +#include "texwindow.h" + +template +class EntryCompletion +{ + GtkListStore* m_store; + IdleDraw m_idleUpdate; +public: + EntryCompletion() : m_store(0), m_idleUpdate(UpdateCaller(*this)) + { + } + + void connect(GtkEntry* entry) + { + if(m_store == 0) + { + m_store = gtk_list_store_new(1, G_TYPE_STRING); + + fill(); + + StringList().connect(IdleDraw::QueueDrawCaller(m_idleUpdate)); + } + + GtkEntryCompletion* completion = gtk_entry_completion_new(); + gtk_entry_set_completion(entry, completion); + gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(m_store)); + gtk_entry_completion_set_text_column(completion, 0); + } + + void append(const char* string) + { + GtkTreeIter iter; + gtk_list_store_append(m_store, &iter); + gtk_list_store_set(m_store, &iter, 0, string, -1); + } + typedef MemberCaller1 AppendCaller; + + void fill() + { + StringList().forEach(AppendCaller(*this)); + } + + void clear() + { + gtk_list_store_clear(m_store); + } + + void update() + { + clear(); + fill(); + } + typedef MemberCaller UpdateCaller; +}; + +class TextureNameList +{ +public: + void forEach(const ShaderNameCallback& callback) const + { + for(QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement()) + { + IShader *shader = QERApp_ActiveShaders_IteratorCurrent(); + + if(shader_equal_prefix(shader->getName(), "textures/")) + { + callback(shader->getName() + 9); + } + } + } + void connect(const SignalHandler& update) const + { + TextureBrowser_addActiveShadersChangedCallback(update); + } +}; + +typedef Static< EntryCompletion > GlobalTextureEntryCompletion; + + +class ShaderList +{ +public: + void forEach(const ShaderNameCallback& callback) const + { + GlobalShaderSystem().foreachShaderName(callback); + } + void connect(const SignalHandler& update) const + { + TextureBrowser_addShadersRealiseCallback(update); + } +}; + +typedef Static< EntryCompletion > GlobalShaderEntryCompletion; + + +#endif diff --git a/radiant/textures.cpp b/radiant/textures.cpp new file mode 100644 index 00000000..76a79704 --- /dev/null +++ b/radiant/textures.cpp @@ -0,0 +1,899 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "textures.h" + +#include "debugging/debugging.h" +#include "warnings.h" + +#include "itextures.h" +#include "igl.h" +#include "preferencesystem.h" +#include "qgl.h" + +#include "texturelib.h" +#include "container/hashfunc.h" +#include "container/cache.h" +#include "generic/callback.h" +#include "stringio.h" + +#include "image.h" +#include "texmanip.h" +#include "preferences.h" + + + +enum ETexturesMode +{ + eTextures_NEAREST = 0, + eTextures_NEAREST_MIPMAP_NEAREST = 1, + eTextures_NEAREST_MIPMAP_LINEAR = 2, + eTextures_LINEAR = 3, + eTextures_LINEAR_MIPMAP_NEAREST = 4, + eTextures_LINEAR_MIPMAP_LINEAR = 5, + eTextures_MAX_ANISOTROPY = 6, +}; + +enum TextureCompressionFormat +{ + TEXTURECOMPRESSION_NONE = 0, + TEXTURECOMPRESSION_RGBA = 1, + TEXTURECOMPRESSION_RGBA_S3TC_DXT1 = 2, + TEXTURECOMPRESSION_RGBA_S3TC_DXT3 = 3, + TEXTURECOMPRESSION_RGBA_S3TC_DXT5 = 4, +}; + +struct texture_globals_t +{ + // RIANT + // texture compression format + TextureCompressionFormat m_nTextureCompressionFormat; + + float fGamma; + + bool bTextureCompressionSupported; // is texture compression supported by hardware? + GLint texture_components; + + // temporary values that should be initialised only once at run-time + bool m_bOpenGLCompressionSupported; + bool m_bS3CompressionSupported; + + texture_globals_t(GLint components) : + m_nTextureCompressionFormat(TEXTURECOMPRESSION_NONE), + fGamma(1.0f), + bTextureCompressionSupported(false), + texture_components(components), + m_bOpenGLCompressionSupported(false), + m_bS3CompressionSupported(false) + { + } +}; + +texture_globals_t g_texture_globals(GL_RGBA); + +void SetTexParameters(ETexturesMode mode) +{ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); + + switch (mode) + { + case eTextures_NEAREST: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + break; + case eTextures_NEAREST_MIPMAP_NEAREST: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST ); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + break; + case eTextures_NEAREST_MIPMAP_LINEAR: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR ); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + break; + case eTextures_LINEAR: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + break; + case eTextures_LINEAR_MIPMAP_NEAREST: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST ); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + break; + case eTextures_LINEAR_MIPMAP_LINEAR: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + break; + case eTextures_MAX_ANISOTROPY: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, QGL_maxTextureAnisotropy()); + break; + default: + globalOutputStream() << "invalid texture mode\n"; + } +} + +ETexturesMode g_texture_mode = eTextures_LINEAR_MIPMAP_LINEAR; + + + + +byte g_gammatable[256]; +void ResampleGamma(float fGamma) +{ + int i,inf; + if (fGamma == 1.0) + { + for (i = 0; i < 256; i++) + g_gammatable[i] = i; + } else + { + for (i = 0; i < 256; i++) + { + inf = (int)(255 * pow ( static_cast((i + 0.5) / 255.5) , static_cast(fGamma) ) + 0.5); + if (inf < 0) + inf = 0; + if (inf > 255) + inf = 255; + g_gammatable[i] = inf; + } + } +} + +inline const int& min_int(const int& left, const int& right) +{ + return std::min(left, right); +} + +int max_tex_size = 0; +const int max_texture_quality = 3; +LatchedInt g_Textures_textureQuality(3, "Texture Quality"); + +/// \brief This function does the actual processing of raw RGBA data into a GL texture. +/// It will also resample to power-of-two dimensions, generate the mipmaps and adjust gamma. +void LoadTextureRGBA(qtexture_t* q, unsigned char* pPixels, int nWidth, int nHeight) +{ + static float fGamma = -1; + float total[3]; + byte *outpixels = 0; + int nCount = nWidth * nHeight; + + if (fGamma != g_texture_globals.fGamma) + { + fGamma = g_texture_globals.fGamma; + ResampleGamma(fGamma); + } + + q->width = nWidth; + q->height = nHeight; + + total[0] = total[1] = total[2] = 0.0f; + + // resample texture gamma according to user settings + for (int i = 0; i < (nCount * 4); i += 4) + { + for (int j = 0; j < 3; j++) + { + total[j] += (pPixels + i)[j]; + byte b = (pPixels + i)[j]; + (pPixels + i)[j] = g_gammatable[b]; + } + } + + q->color[0] = total[0] / (nCount * 255); + q->color[1] = total[1] / (nCount * 255); + q->color[2] = total[2] / (nCount * 255); + + glGenTextures (1, &q->texture_number); + + glBindTexture( GL_TEXTURE_2D, q->texture_number ); + + SetTexParameters(g_texture_mode); + + int gl_width = 1; + while(gl_width < nWidth) + gl_width <<= 1; + + int gl_height = 1; + while(gl_height < nHeight) + gl_height <<= 1; + + bool resampled = false; + if (!(gl_width == nWidth && gl_height == nHeight)) + { + resampled = true; + outpixels = (byte *)malloc(gl_width * gl_height * 4); + R_ResampleTexture(pPixels, nWidth, nHeight, outpixels, gl_width, gl_height, 4); + } + else + { + outpixels = pPixels; + } + + int quality_reduction = max_texture_quality - g_Textures_textureQuality.m_value; + int target_width = min_int(gl_width >> quality_reduction, max_tex_size); + int target_height = min_int(gl_height >> quality_reduction, max_tex_size); + + while (gl_width > target_width || gl_height > target_height) + { + GL_MipReduce(outpixels, outpixels, gl_width, gl_height, target_width, target_height); + + if (gl_width > target_width) + gl_width >>= 1; + if (gl_height > target_height) + gl_height >>= 1; + } + + int mip = 0; + glTexImage2D(GL_TEXTURE_2D, mip++, g_texture_globals.texture_components, gl_width, gl_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, outpixels); + while (gl_width > 1 || gl_height > 1) + { + GL_MipReduce(outpixels, outpixels, gl_width, gl_height, 1, 1); + + if (gl_width > 1) + gl_width >>= 1; + if (gl_height > 1) + gl_height >>= 1; + + glTexImage2D(GL_TEXTURE_2D, mip++, g_texture_globals.texture_components, gl_width, gl_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, outpixels); + } + + glBindTexture(GL_TEXTURE_2D, 0); + if (resampled) + free(outpixels); +} + +#if 0 +/* +============== +Texture_InitPalette +============== +*/ +void Texture_InitPalette (byte *pal) +{ + int r,g,b; + int i; + int inf; + byte gammatable[256]; + float gamma; + + gamma = g_texture_globals.fGamma; + + if (gamma == 1.0) + { + for (i=0 ; i<256 ; i++) + gammatable[i] = i; + } else + { + for (i=0 ; i<256 ; i++) + { + inf = (int)(255 * pow ( (i+0.5)/255.5 , gamma ) + 0.5); + if (inf < 0) + inf = 0; + if (inf > 255) + inf = 255; + gammatable[i] = inf; + } + } + + for (i=0 ; i<256 ; i++) + { + r = gammatable[pal[0]]; + g = gammatable[pal[1]]; + b = gammatable[pal[2]]; + pal += 3; + + //v = (r<<24) + (g<<16) + (b<<8) + 255; + //v = BigLong (v); + + //tex_palette[i] = v; + tex_palette[i*3+0] = r; + tex_palette[i*3+1] = g; + tex_palette[i*3+2] = b; + } +} +#endif + +#if 0 +class TestHashtable +{ +public: + TestHashtable() + { + HashTable strings; + strings["Monkey"] = "bleh"; + strings["MonkeY"] = "blah"; + } +}; + +const TestHashtable g_testhashtable; + +#endif + +typedef std::pair TextureKey; + +void qtexture_realise(qtexture_t& texture, const TextureKey& key) +{ + texture.texture_number = 0; + if(!string_empty(key.second.c_str())) + { + Image* image = key.first.loadImage(key.second.c_str()); + if(image != 0) + { + LoadTextureRGBA(&texture, image->getRGBAPixels(), image->getWidth(), image->getHeight()); + texture.surfaceFlags = image->getSurfaceFlags(); + texture.contentFlags = image->getContentFlags(); + texture.value = image->getValue(); + image->release(); + globalOutputStream() << "Loaded Texture: \"" << key.second.c_str() << "\"\n"; + GlobalOpenGL_debugAssertNoErrors(); + } + else + { + globalErrorStream() << "Texture load failed: \"" << key.second.c_str() << "\"\n"; + } + } +} + +void qtexture_unrealise(qtexture_t& texture) +{ + if(GlobalOpenGL().contextValid && texture.texture_number != 0) + { + glDeleteTextures(1, &texture.texture_number); + GlobalOpenGL_debugAssertNoErrors(); + } +} + +class TextureKeyEqualNoCase +{ +public: + bool operator()(const TextureKey& key, const TextureKey& other) const + { + return key.first == other.first && string_equal_nocase(key.second.c_str(), other.second.c_str()); + } +}; + +class TextureKeyHashNoCase +{ +public: + typedef hash_t hash_type; + hash_t operator()(const TextureKey& key) const + { + return hash_combine(string_hash_nocase(key.second.c_str()), pod_hash(key.first)); + } +}; + +#define DEBUG_TEXTURES 0 + +class TexturesMap : public TexturesCache +{ + class TextureConstructor + { + TexturesMap* m_cache; + public: + explicit TextureConstructor(TexturesMap* cache) + : m_cache(cache) + { + } + qtexture_t* construct(const TextureKey& key) + { + qtexture_t* texture = new qtexture_t(key.first, key.second.c_str()); + if(m_cache->realised()) + { + qtexture_realise(*texture, key); + } + return texture; + } + void destroy(qtexture_t* texture) + { + if(m_cache->realised()) + { + qtexture_unrealise(*texture); + } + delete texture; + } + }; + + typedef HashedCache qtextures_t; + qtextures_t m_qtextures; + TexturesCacheObserver* m_observer; + std::size_t m_unrealised; + +public: + TexturesMap() : m_qtextures(TextureConstructor(this)), m_observer(0), m_unrealised(1) + { + } + typedef qtextures_t::iterator iterator; + + iterator begin() + { + return m_qtextures.begin(); + } + iterator end() + { + return m_qtextures.end(); + } + + LoadImageCallback defaultLoader() const + { + return LoadImageCallback(0, QERApp_LoadImage); + } + Image* loadImage(const char* name) + { + return defaultLoader().loadImage(name); + } + qtexture_t* capture(const char* name) + { + return capture(defaultLoader(), name); + } + qtexture_t* capture(const LoadImageCallback& loader, const char* name) + { +#if DEBUG_TEXTURES + globalOutputStream() << "textures capture: " << makeQuoted(name) << '\n'; +#endif + return m_qtextures.capture(TextureKey(loader, name)).get(); + } + void release(qtexture_t* texture) + { +#if DEBUG_TEXTURES + globalOutputStream() << "textures release: " << makeQuoted(texture->name) << '\n'; +#endif + m_qtextures.release(TextureKey(texture->load, texture->name)); + } + void attach(TexturesCacheObserver& observer) + { + ASSERT_MESSAGE(m_observer == 0, "TexturesMap::attach: cannot attach observer"); + m_observer = &observer; + } + void detach(TexturesCacheObserver& observer) + { + ASSERT_MESSAGE(m_observer == &observer, "TexturesMap::detach: cannot detach observer"); + m_observer = 0; + } + void realise() + { + if(--m_unrealised == 0) + { + g_texture_globals.bTextureCompressionSupported = false; + + if(GlobalOpenGL().ARB_texture_compression()) + { + g_texture_globals.bTextureCompressionSupported = true; + g_texture_globals.m_bOpenGLCompressionSupported = true; + } + + if(GlobalOpenGL().EXT_texture_compression_s3tc()) + { + g_texture_globals.bTextureCompressionSupported = true; + g_texture_globals.m_bS3CompressionSupported = true; + } + + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size); + if(max_tex_size == 0) + { + max_tex_size = 1024; + } + + for(qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i) + { + if(!(*i).value.empty()) + { + qtexture_realise(*(*i).value, (*i).key); + } + } + if(m_observer != 0) + { + m_observer->realise(); + } + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + if(m_observer != 0) + { + m_observer->unrealise(); + } + for(qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i) + { + if(!(*i).value.empty()) + { + qtexture_unrealise(*(*i).value); + } + } + } + } + bool realised() + { + return m_unrealised == 0; + } +}; + +TexturesMap* g_texturesmap; + +TexturesCache& GetTexturesCache() +{ + return *g_texturesmap; +} + + +void Textures_Realise() +{ + g_texturesmap->realise(); +} + +void Textures_Unrealise() +{ + g_texturesmap->unrealise(); +} + + +Callback g_texturesModeChangedNotify; + +void Textures_setModeChangedNotify(const Callback& notify) +{ + g_texturesModeChangedNotify = notify; +} + +void Textures_ModeChanged() +{ + if(g_texturesmap->realised()) + { + SetTexParameters(g_texture_mode); + + for(TexturesMap::iterator i = g_texturesmap->begin(); i != g_texturesmap->end(); ++i) + { + glBindTexture (GL_TEXTURE_2D, (*i).value->texture_number); + SetTexParameters(g_texture_mode); + } + + glBindTexture( GL_TEXTURE_2D, 0 ); + } + g_texturesModeChangedNotify(); +} + +void Textures_SetMode(ETexturesMode mode) +{ + if(g_texture_mode != mode) + { + g_texture_mode = mode; + + Textures_ModeChanged(); + } +} + +void Textures_setTextureComponents(GLint texture_components) +{ + if(g_texture_globals.texture_components != texture_components) + { + Textures_Unrealise(); + g_texture_globals.texture_components = texture_components; + Textures_Realise(); + } +} + +void Textures_UpdateTextureCompressionFormat() +{ + GLint texture_components = GL_RGBA; + + if(!g_texturesmap->realised()) + { + texture_components = g_texture_globals.m_nTextureCompressionFormat; + if(texture_components == TEXTURECOMPRESSION_NONE) + texture_components = GL_RGBA; + } + else + { + if (g_texture_globals.bTextureCompressionSupported) + { + if(g_texture_globals.m_nTextureCompressionFormat != TEXTURECOMPRESSION_NONE + && g_texture_globals.m_nTextureCompressionFormat != TEXTURECOMPRESSION_RGBA + && !g_texture_globals.m_bS3CompressionSupported) + { + globalOutputStream() << "OpenGL extension GL_EXT_texture_compression_s3tc not supported by current graphics drivers\n"; + g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_RGBA; // if this is not supported either, see below + } + if (g_texture_globals.m_nTextureCompressionFormat == TEXTURECOMPRESSION_RGBA && !g_texture_globals.m_bOpenGLCompressionSupported) + { + globalOutputStream() << "OpenGL extension GL_ARB_texture_compression not supported by current graphics drivers\n"; + g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE; + } + + switch (g_texture_globals.m_nTextureCompressionFormat) + { + case (TEXTURECOMPRESSION_NONE): + { + texture_components = GL_RGBA; + break; + } + case (TEXTURECOMPRESSION_RGBA): + { + texture_components = GL_COMPRESSED_RGBA_ARB; + break; + } + case (TEXTURECOMPRESSION_RGBA_S3TC_DXT1): + { + texture_components = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + break; + } + case (TEXTURECOMPRESSION_RGBA_S3TC_DXT3): + { + texture_components = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + break; + } + case (TEXTURECOMPRESSION_RGBA_S3TC_DXT5): + { + texture_components = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + break; + } + } + } + else + { + texture_components = GL_RGBA; + g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE; + } + } + + Textures_setTextureComponents(texture_components); +} + +void TextureCompressionImport(TextureCompressionFormat& self, int value) +{ + if(!g_texture_globals.m_bOpenGLCompressionSupported + && g_texture_globals.m_bS3CompressionSupported + && value >= 1) + { + ++value; + } + switch(value) + { + case 0: + self = TEXTURECOMPRESSION_NONE; + break; + case 1: + self = TEXTURECOMPRESSION_RGBA; + break; + case 2: + self = TEXTURECOMPRESSION_RGBA_S3TC_DXT1; + break; + case 3: + self = TEXTURECOMPRESSION_RGBA_S3TC_DXT3; + break; + case 4: + self = TEXTURECOMPRESSION_RGBA_S3TC_DXT5; + break; + } + Textures_UpdateTextureCompressionFormat(); +} +typedef ReferenceCaller1 TextureCompressionImportCaller; + +void TextureGammaImport(float& self, float value) +{ + if(self != value) + { + Textures_Unrealise(); + self = value; + Textures_Realise(); + } +} +typedef ReferenceCaller1 TextureGammaImportCaller; + +void TextureModeImport(ETexturesMode& self, int value) +{ + switch(value) + { + case 0: + Textures_SetMode(eTextures_NEAREST); + break; + case 1: + Textures_SetMode(eTextures_NEAREST_MIPMAP_NEAREST); + break; + case 2: + Textures_SetMode(eTextures_LINEAR); + break; + case 3: + Textures_SetMode(eTextures_NEAREST_MIPMAP_LINEAR); + break; + case 4: + Textures_SetMode(eTextures_LINEAR_MIPMAP_NEAREST); + break; + case 5: + Textures_SetMode(eTextures_LINEAR_MIPMAP_LINEAR); + break; + case 6: + Textures_SetMode(eTextures_MAX_ANISOTROPY); + } +} +typedef ReferenceCaller1 TextureModeImportCaller; + +void TextureModeExport(ETexturesMode& self, const IntImportCallback& importer) +{ + switch(self) + { + case eTextures_NEAREST: + importer(0); + break; + case eTextures_NEAREST_MIPMAP_NEAREST: + importer(1); + break; + case eTextures_LINEAR: + importer(2); + break; + case eTextures_NEAREST_MIPMAP_LINEAR: + importer(3); + break; + case eTextures_LINEAR_MIPMAP_NEAREST: + importer(4); + break; + case eTextures_LINEAR_MIPMAP_LINEAR: + importer(5); + break; + case eTextures_MAX_ANISOTROPY: + importer(6); + break; + default: + importer(4); + } +} +typedef ReferenceCaller1 TextureModeExportCaller; + +void Textures_constructPreferences(PreferencesPage& page) +{ + { + const char* percentages[] = { "12.5%", "25%", "50%", "100%", }; + page.appendRadio( + "Texture Quality", + STRING_ARRAY_RANGE(percentages), + LatchedIntImportCaller(g_Textures_textureQuality), + IntExportCaller(g_Textures_textureQuality.m_latched) + ); + } + page.appendSpinner( + "Texture Gamma", + 1.0, + 0.0, + 1.0, + FloatImportCallback(TextureGammaImportCaller(g_texture_globals.fGamma)), + FloatExportCallback(FloatExportCaller(g_texture_globals.fGamma)) + ); + { + const char* texture_mode[] = { "Nearest", "Nearest Mipmap", "Linear", "Bilinear", "Bilinear Mipmap", "Trilinear", "Anisotropy" }; + page.appendCombo( + "Texture Render Mode", + STRING_ARRAY_RANGE(texture_mode), + IntImportCallback(TextureModeImportCaller(g_texture_mode)), + IntExportCallback(TextureModeExportCaller(g_texture_mode)) + ); + } + { + const char* compression_none[] = { "None" }; + const char* compression_opengl[] = { "None", "OpenGL ARB" }; + const char* compression_s3tc[] = { "None", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5" }; + const char* compression_opengl_s3tc[] = { "None", "OpenGL ARB", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5" }; + StringArrayRange compression( + (g_texture_globals.m_bOpenGLCompressionSupported) + ? (g_texture_globals.m_bS3CompressionSupported) + ? STRING_ARRAY_RANGE(compression_opengl_s3tc) + : STRING_ARRAY_RANGE(compression_opengl) + : (g_texture_globals.m_bS3CompressionSupported) + ? STRING_ARRAY_RANGE(compression_s3tc) + : STRING_ARRAY_RANGE(compression_none) + ); + page.appendCombo( + "Hardware Texture Compression", + compression, + TextureCompressionImportCaller(g_texture_globals.m_nTextureCompressionFormat), + IntExportCaller(reinterpret_cast(g_texture_globals.m_nTextureCompressionFormat)) + ); + } +} +void Textures_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Textures", "Texture Settings")); + Textures_constructPreferences(page); +} +void Textures_registerPreferencesPage() +{ + PreferencesDialog_addDisplayPage(FreeCaller1()); +} + +void TextureCompression_importString(const char* string) +{ + g_texture_globals.m_nTextureCompressionFormat = static_cast(atoi(string)); + Textures_UpdateTextureCompressionFormat(); +} +typedef FreeCaller1 TextureCompressionImportStringCaller; + + +void Textures_Construct() +{ + g_texturesmap = new TexturesMap; + + GlobalPreferenceSystem().registerPreference("TextureCompressionFormat", TextureCompressionImportStringCaller(), IntExportStringCaller(reinterpret_cast(g_texture_globals.m_nTextureCompressionFormat))); + GlobalPreferenceSystem().registerPreference("TextureFiltering", IntImportStringCaller(reinterpret_cast(g_texture_mode)), IntExportStringCaller(reinterpret_cast(g_texture_mode))); + GlobalPreferenceSystem().registerPreference("TextureQuality", IntImportStringCaller(g_Textures_textureQuality.m_latched), IntExportStringCaller(g_Textures_textureQuality.m_latched)); + GlobalPreferenceSystem().registerPreference("SI_Gamma", FloatImportStringCaller(g_texture_globals.fGamma), FloatExportStringCaller(g_texture_globals.fGamma)); + + g_Textures_textureQuality.useLatched(); + + Textures_registerPreferencesPage(); + + Textures_ModeChanged(); +} +void Textures_Destroy() +{ + delete g_texturesmap; +} + + +#include "modulesystem/modulesmap.h" +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +class TexturesDependencies : + public GlobalRadiantModuleRef, + public GlobalOpenGLModuleRef, + public GlobalPreferenceSystemModuleRef +{ + ImageModulesRef m_image_modules; +public: + TexturesDependencies() : + m_image_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("texturetypes")) + { + } + ImageModules& getImageModules() + { + return m_image_modules.get(); + } +}; + +class TexturesAPI +{ + TexturesCache* m_textures; +public: + typedef TexturesCache Type; + STRING_CONSTANT(Name, "*"); + + TexturesAPI() + { + Textures_Construct(); + + m_textures = &GetTexturesCache(); + } + ~TexturesAPI() + { + Textures_Destroy(); + } + TexturesCache* getTable() + { + return m_textures; + } +}; + +typedef SingletonModule TexturesModule; +typedef Static StaticTexturesModule; +StaticRegisterModule staticRegisterTextures(StaticTexturesModule::instance()); + +ImageModules& Textures_getImageModules() +{ + return StaticTexturesModule::instance().getDependencies().getImageModules(); +} + + + diff --git a/radiant/textures.h b/radiant/textures.h new file mode 100644 index 00000000..0a570b7d --- /dev/null +++ b/radiant/textures.h @@ -0,0 +1,33 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_TEXTURES_H) +#define INCLUDED_TEXTURES_H + +#include "generic/callbackfwd.h" + +void Textures_Realise(); +void Textures_Unrealise(); +void Textures_sharedContextDestroyed(); + +void Textures_setModeChangedNotify(const Callback& notify); + +#endif diff --git a/radiant/texwindow.cpp b/radiant/texwindow.cpp new file mode 100644 index 00000000..810fc195 --- /dev/null +++ b/radiant/texwindow.cpp @@ -0,0 +1,2703 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// Texture Window +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "texwindow.h" + +#include "debugging/debugging.h" +#include "warnings.h" + +#include "ifilesystem.h" +#include "iundo.h" +#include "igl.h" +#include "iarchive.h" +#include "moduleobserver.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "signal/signal.h" +#include "math/vector.h" +#include "texturelib.h" +#include "string/string.h" +#include "shaderlib.h" +#include "os/file.h" +#include "os/path.h" +#include "stream/memstream.h" +#include "stream/textfilestream.h" +#include "stream/stringstream.h" +#include "cmdlib.h" +#include "texmanip.h" +#include "textures.h" +#include "convert.h" + +#include "gtkutil/menu.h" +#include "gtkutil/nonmodal.h" +#include "gtkutil/cursor.h" +#include "gtkutil/widget.h" +#include "gtkutil/glwidget.h" +#include "gtkutil/messagebox.h" + +#include "error.h" +#include "map.h" +#include "qgl.h" +#include "select.h" +#include "brush_primit.h" +#include "brushmanip.h" +#include "patchmanip.h" +#include "plugin.h" +#include "qe3.h" +#include "gtkdlgs.h" +#include "gtkmisc.h" +#include "mainframe.h" +#include "findtexturedialog.h" +#include "surfacedialog.h" +#include "patchdialog.h" +#include "groupdialog.h" +#include "preferences.h" +#include "shaders.h" +#include "commands.h" + +bool TextureBrowser_showWads() +{ + return !string_empty(g_pGameDescription->getKeyValue("show_wads")); +} + +void TextureBrowser_queueDraw(TextureBrowser& textureBrowser); + +bool string_equal_start(const char* string, StringRange start) +{ + return string_equal_n(string, start.first, start.last - start.first); +} + +typedef std::set TextureGroups; + +void TextureGroups_addWad(TextureGroups& groups, const char* archive) +{ + if(extension_equal(path_get_extension(archive), "wad")) + { +#if 1 + groups.insert(archive); +#else + CopiedString archiveBaseName(path_get_filename_start(archive), path_get_filename_base_end(archive)); + groups.insert(archiveBaseName); +#endif + } +} +typedef ReferenceCaller1 TextureGroupsAddWadCaller; + +void TextureGroups_addShader(TextureGroups& groups, const char* shaderName) +{ + const char* texture = path_make_relative(shaderName, "textures/"); + if(texture != shaderName) + { + const char* last = path_remove_directory(texture); + if(!string_empty(last)) + { + groups.insert(CopiedString(StringRange(texture, --last))); + } + } +} +typedef ReferenceCaller1 TextureGroupsAddShaderCaller; + +void TextureGroups_addDirectory(TextureGroups& groups, const char* directory) +{ + groups.insert(directory); +} +typedef ReferenceCaller1 TextureGroupsAddDirectoryCaller; + +namespace +{ + bool g_TextureBrowser_shaderlistOnly = false; + bool g_TextureBrowser_fixedSize = false; + bool g_TextureBrowser_filterNotex = false; +} + +class DeferredAdjustment +{ + gdouble m_value; + guint m_handler; + typedef void (*ValueChangedFunction)(void* data, gdouble value); + ValueChangedFunction m_function; + void* m_data; + + static gboolean deferred_value_changed(gpointer data) + { + reinterpret_cast(data)->m_function( + reinterpret_cast(data)->m_data, + reinterpret_cast(data)->m_value + ); + reinterpret_cast(data)->m_handler = 0; + reinterpret_cast(data)->m_value = 0; + return FALSE; + } +public: + DeferredAdjustment(ValueChangedFunction function, void* data) : m_value(0), m_handler(0), m_function(function), m_data(data) + { + } + void flush() + { + if(m_handler != 0) + { + g_source_remove(m_handler); + deferred_value_changed(this); + } + } + void value_changed(gdouble value) + { + m_value = value; + if(m_handler == 0) + { + m_handler = g_idle_add(deferred_value_changed, this); + } + } + static void adjustment_value_changed(GtkAdjustment *adjustment, DeferredAdjustment* self) + { + self->value_changed(adjustment->value); + } +}; + + + +class TextureBrowser; + +typedef ReferenceCaller TextureBrowserQueueDrawCaller; + +void TextureBrowser_scrollChanged(void* data, gdouble value); + + +enum StartupShaders +{ + STARTUPSHADERS_NONE = 0, + STARTUPSHADERS_COMMON, +}; + +void TextureBrowser_hideUnusedExport(const BoolImportCallback& importer); +typedef FreeCaller1 TextureBrowserHideUnusedExport; + +void TextureBrowser_showShadersExport(const BoolImportCallback& importer); +typedef FreeCaller1 TextureBrowserShowShadersExport; + +void TextureBrowser_showShaderlistOnly(const BoolImportCallback& importer); +typedef FreeCaller1 TextureBrowserShowShaderlistOnlyExport; + +void TextureBrowser_fixedSize(const BoolImportCallback& importer); +typedef FreeCaller1 TextureBrowserFixedSizeExport; + +void TextureBrowser_filterNotex(const BoolImportCallback& importer); +typedef FreeCaller1 TextureBrowserFilterNotexExport; + +class TextureBrowser +{ +public: + int width, height; + int originy; + int m_nTotalHeight; + + CopiedString shader; + + GtkWindow* m_parent; + GtkWidget* m_gl_widget; + GtkWidget* m_texture_scroll; + GtkWidget* m_treeViewTree; + GtkWidget* m_treeViewTags; + GtkWidget* m_tag_frame; + GtkListStore* m_assigned_store; + GtkListStore* m_available_store; + GtkWidget* m_assigned_tree; + GtkWidget* m_available_tree; + GtkWidget* m_scr_win_tree; + GtkWidget* m_scr_win_tags; + GtkWidget* m_tag_notebook; + GtkWidget* m_search_button; + GtkWidget* m_shader_info_item; + + std::set m_all_tags; + GtkListStore* m_all_tags_list; + std::vector m_copied_tags; + std::set m_found_shaders; + + ToggleItem m_hideunused_item; + ToggleItem m_showshaders_item; + ToggleItem m_showshaderlistonly_item; + ToggleItem m_fixedsize_item; + ToggleItem m_filternotex_item; + + guint m_sizeHandler; + guint m_exposeHandler; + + bool m_heightChanged; + bool m_originInvalid; + + DeferredAdjustment m_scrollAdjustment; + FreezePointer m_freezePointer; + + Vector3 color_textureback; + // the increment step we use against the wheel mouse + std::size_t m_mouseWheelScrollIncrement; + std::size_t m_textureScale; + // make the texture increments match the grid changes + bool m_showShaders; + bool m_showTextureScrollbar; + StartupShaders m_startupShaders; + // if true, the texture window will only display in-use shaders + // if false, all the shaders in memory are displayed + bool m_hideUnused; + bool m_rmbSelected; + bool m_searchedTags; + bool m_tags; + // The uniform size (in pixels) that textures are resized to when m_resizeTextures is true. + int m_uniformTextureSize; + // Return the display width of a texture in the texture browser + int getTextureWidth(qtexture_t* tex) + { + int width; + if (!g_TextureBrowser_fixedSize) + { + // Don't use uniform size + width = (int)(tex->width * ((float)m_textureScale / 100)); + } else if + (tex->width >= tex->height) + { + // Texture is square, or wider than it is tall + width = m_uniformTextureSize; + } else { + // Otherwise, preserve the texture's aspect ratio + width = (int)(m_uniformTextureSize * ((float)tex->width / tex->height)); + } + return width; + } + // Return the display height of a texture in the texture browser + int getTextureHeight(qtexture_t* tex) + { + int height; + if (!g_TextureBrowser_fixedSize) + { + // Don't use uniform size + height = (int)(tex->height * ((float)m_textureScale / 100)); + } else if (tex->height >= tex->width) + { + // Texture is square, or taller than it is wide + height = m_uniformTextureSize; + } else { + // Otherwise, preserve the texture's aspect ratio + height = (int)(m_uniformTextureSize * ((float)tex->height / tex->width)); + } + return height; + } + + TextureBrowser() : + m_texture_scroll(0), + m_hideunused_item(TextureBrowserHideUnusedExport()), + m_showshaders_item(TextureBrowserShowShadersExport()), + m_showshaderlistonly_item(TextureBrowserShowShaderlistOnlyExport()), + m_fixedsize_item(TextureBrowserFixedSizeExport()), + m_filternotex_item(TextureBrowserFilterNotexExport()), + m_heightChanged(true), + m_originInvalid(true), + m_scrollAdjustment(TextureBrowser_scrollChanged, this), + color_textureback(0.25f, 0.25f, 0.25f), + m_mouseWheelScrollIncrement(64), + m_textureScale(50), + m_showShaders(true), + m_showTextureScrollbar(true), + m_startupShaders(STARTUPSHADERS_NONE), + m_hideUnused(false), + m_rmbSelected(false), + m_searchedTags(false), + m_tags(false), + m_uniformTextureSize(128) + { + } +}; + +void(*TextureBrowser_textureSelected)(const char* shader); + + +void TextureBrowser_updateScroll(TextureBrowser& textureBrowser); + + +const char* TextureBrowser_getComonShadersName() +{ + const char* value = g_pGameDescription->getKeyValue("common_shaders_name"); + if(!string_empty(value)) + { + return value; + } + return "Common"; +} + +const char* TextureBrowser_getComonShadersDir() +{ + const char* value = g_pGameDescription->getKeyValue("common_shaders_dir"); + if(!string_empty(value)) + { + return value; + } + return "common/"; +} + +inline int TextureBrowser_fontHeight(TextureBrowser& textureBrowser) +{ + return GlobalOpenGL().m_fontHeight; +} + +const char* TextureBrowser_GetSelectedShader(TextureBrowser& textureBrowser) +{ + return textureBrowser.shader.c_str(); +} + +void TextureBrowser_SetStatus(TextureBrowser& textureBrowser, const char* name) +{ + IShader* shader = QERApp_Shader_ForName( name); + qtexture_t* q = shader->getTexture(); + StringOutputStream strTex(256); + strTex << name << " W: " << Unsigned(q->width) << " H: " << Unsigned(q->height); + shader->DecRef(); + g_pParentWnd->SetStatusText(g_pParentWnd->m_texture_status, strTex.c_str()); +} + +void TextureBrowser_Focus(TextureBrowser& textureBrowser, const char* name); + +void TextureBrowser_SetSelectedShader(TextureBrowser& textureBrowser, const char* shader) +{ + textureBrowser.shader = shader; + TextureBrowser_SetStatus(textureBrowser, shader); + TextureBrowser_Focus(textureBrowser, shader); + + if(FindTextureDialog_isOpen()) + { + FindTextureDialog_selectTexture(shader); + } + + // disable the menu item "shader info" if no shader was selected + IShader* ishader = QERApp_Shader_ForName(shader); + CopiedString filename = ishader->getShaderFileName(); + + if(filename.empty()) + { + if(textureBrowser.m_shader_info_item != NULL) + { + gtk_widget_set_sensitive(textureBrowser.m_shader_info_item, FALSE); + } + } else { + gtk_widget_set_sensitive(textureBrowser.m_shader_info_item, TRUE); + } + + ishader->DecRef(); +} + + +CopiedString g_TextureBrowser_currentDirectory; + +/* +============================================================================ + +TEXTURE LAYOUT + +TTimo: now based on a rundown through all the shaders +NOTE: we expect the Active shaders count doesn't change during a Texture_StartPos .. Texture_NextPos cycle + otherwise we may need to rely on a list instead of an array storage +============================================================================ +*/ + +class TextureLayout +{ +public: + // texture layout functions + // TTimo: now based on shaders + int current_x, current_y, current_row; +}; + +void Texture_StartPos(TextureLayout& layout) +{ + layout.current_x = 8; + layout.current_y = -8; + layout.current_row = 0; +} + +void Texture_NextPos(TextureBrowser& textureBrowser, TextureLayout& layout, qtexture_t* current_texture, int *x, int *y) +{ + qtexture_t* q = current_texture; + + int nWidth = textureBrowser.getTextureWidth(q); + int nHeight = textureBrowser.getTextureHeight(q); + if (layout.current_x + nWidth > textureBrowser.width-8 && layout.current_row) + { // go to the next row unless the texture is the first on the row + layout.current_x = 8; + layout.current_y -= layout.current_row + TextureBrowser_fontHeight(textureBrowser) + 4; + layout.current_row = 0; + } + + *x = layout.current_x; + *y = layout.current_y; + + // Is our texture larger than the row? If so, grow the + // row height to match it + + if (layout.current_row < nHeight) + layout.current_row = nHeight; + + // never go less than 96, or the names get all crunched up + layout.current_x += nWidth < 96 ? 96 : nWidth; + layout.current_x += 8; +} + +bool TextureSearch_IsShown(const char* name) +{ + std::set::iterator iter; + + iter = GlobalTextureBrowser().m_found_shaders.find(name); + + if(iter == GlobalTextureBrowser().m_found_shaders.end()) + { + return false; + } else { + return true; + } +} + +CopiedString g_notex; +CopiedString g_shadernotex; + +// if texture_showinuse jump over non in-use textures +bool Texture_IsShown(IShader* shader, bool show_shaders, bool hideUnused) +{ + // filter notex / shadernotex images + if(g_TextureBrowser_filterNotex && (string_equal(g_notex.c_str(), shader->getTexture()->name) || string_equal(g_shadernotex.c_str(), shader->getTexture()->name))) + { + return false; + } + + if(g_TextureBrowser_currentDirectory == "Untagged") + { + std::set::iterator iter; + + iter = GlobalTextureBrowser().m_found_shaders.find(shader->getName()); + + if(iter == GlobalTextureBrowser().m_found_shaders.end()) + { + return false; + } else { + return true; + } + } + + if(!shader_equal_prefix(shader->getName(), "textures/")) + return false; + + if (!show_shaders && !shader->IsDefault()) + return false; + + if(hideUnused && !shader->IsInUse()) + return false; + + if(GlobalTextureBrowser().m_searchedTags) + { + if(!TextureSearch_IsShown(shader->getName())) + { + return false; + } else { + return true; + } + } else { + if(!shader_equal_prefix(shader_get_textureName(shader->getName()), g_TextureBrowser_currentDirectory.c_str())) + { + return false; + } + } + + return true; +} + +void TextureBrowser_heightChanged(TextureBrowser& textureBrowser) +{ + textureBrowser.m_heightChanged = true; + + TextureBrowser_updateScroll(textureBrowser); + TextureBrowser_queueDraw(textureBrowser); +} + +void TextureBrowser_evaluateHeight(TextureBrowser& textureBrowser) +{ + if(textureBrowser.m_heightChanged) + { + textureBrowser.m_heightChanged = false; + + textureBrowser.m_nTotalHeight = 0; + + TextureLayout layout; + Texture_StartPos(layout); + for(QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement()) + { + IShader* shader = QERApp_ActiveShaders_IteratorCurrent(); + + if(!Texture_IsShown(shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused)) + continue; + + int x, y; + Texture_NextPos(textureBrowser, layout, shader->getTexture(), &x, &y); + textureBrowser.m_nTotalHeight = std::max(textureBrowser.m_nTotalHeight, abs(layout.current_y) + TextureBrowser_fontHeight(textureBrowser) + textureBrowser.getTextureHeight(shader->getTexture()) + 4); + } + } +} + +int TextureBrowser_TotalHeight(TextureBrowser& textureBrowser) +{ + TextureBrowser_evaluateHeight(textureBrowser); + return textureBrowser.m_nTotalHeight; +} + +inline const int& min_int(const int& left, const int& right) +{ + return std::min(left, right); +} + +void TextureBrowser_clampOriginY(TextureBrowser& textureBrowser) +{ + if(textureBrowser.originy > 0) + { + textureBrowser.originy = 0; + } + int lower = min_int(textureBrowser.height - TextureBrowser_TotalHeight(textureBrowser), 0); + if(textureBrowser.originy < lower) + { + textureBrowser.originy = lower; + } +} + +int TextureBrowser_getOriginY(TextureBrowser& textureBrowser) +{ + if(textureBrowser.m_originInvalid) + { + textureBrowser.m_originInvalid = false; + TextureBrowser_clampOriginY(textureBrowser); + TextureBrowser_updateScroll(textureBrowser); + } + return textureBrowser.originy; +} + +void TextureBrowser_setOriginY(TextureBrowser& textureBrowser, int originy) +{ + textureBrowser.originy = originy; + TextureBrowser_clampOriginY(textureBrowser); + TextureBrowser_updateScroll(textureBrowser); + TextureBrowser_queueDraw(textureBrowser); +} + + +Signal0 g_activeShadersChangedCallbacks; + +void TextureBrowser_addActiveShadersChangedCallback(const SignalHandler& handler) +{ + g_activeShadersChangedCallbacks.connectLast(handler); +} + +class ShadersObserver : public ModuleObserver +{ + Signal0 m_realiseCallbacks; +public: + void realise() + { + m_realiseCallbacks(); + } + void unrealise() + { + } + void insert(const SignalHandler& handler) + { + m_realiseCallbacks.connectLast(handler); + } +}; + +namespace +{ + ShadersObserver g_ShadersObserver; +} + +void TextureBrowser_addShadersRealiseCallback(const SignalHandler& handler) +{ + g_ShadersObserver.insert(handler); +} + +void TextureBrowser_activeShadersChanged(TextureBrowser& textureBrowser) +{ + TextureBrowser_heightChanged(textureBrowser); + textureBrowser.m_originInvalid = true; + + g_activeShadersChangedCallbacks(); +} + +void TextureBrowser_importShowScrollbar(TextureBrowser& textureBrowser, bool value) +{ + textureBrowser.m_showTextureScrollbar = value; + if(textureBrowser.m_texture_scroll != 0) + { + widget_set_visible(textureBrowser.m_texture_scroll, textureBrowser.m_showTextureScrollbar); + TextureBrowser_updateScroll(textureBrowser); + } +} +typedef ReferenceCaller1 TextureBrowserImportShowScrollbarCaller; + + +/* +============== +TextureBrowser_ShowDirectory +relies on texture_directory global for the directory to use +1) Load the shaders for the given directory +2) Scan the remaining texture, load them and assign them a default shader (the "noshader" shader) +NOTE: when writing a texture plugin, or some texture extensions, this function may need to be overriden, and made + available through the IShaders interface +NOTE: for texture window layout: + all shaders are stored with alphabetical order after load + previously loaded and displayed stuff is hidden, only in-use and newly loaded is shown + ( the GL textures are not flushed though) +============== +*/ +bool texture_name_ignore(const char* name) +{ + StringOutputStream strTemp(string_length(name)); + strTemp << LowerCase(name); + + return strstr(strTemp.c_str(), ".specular") != 0 || + strstr(strTemp.c_str(), ".glow") != 0 || + strstr(strTemp.c_str(), ".bump") != 0 || + strstr(strTemp.c_str(), ".diffuse") != 0 || + strstr(strTemp.c_str(), ".blend") != 0 || + strstr(strTemp.c_str(), ".alpha") != 0; +} + +class LoadShaderVisitor : public Archive::Visitor +{ +public: + void visit(const char* name) + { + IShader* shader = QERApp_Shader_ForName(CopiedString(StringRange(name, path_get_filename_base_end(name))).c_str()); + shader->DecRef(); + } +}; + +void TextureBrowser_SetHideUnused(TextureBrowser& textureBrowser, bool hideUnused); + +GtkWidget* g_page_textures; + +void TextureBrowser_toggleShow() +{ + GroupDialog_showPage(g_page_textures); +} + + +void TextureBrowser_updateTitle() +{ + GroupDialog_updatePageTitle(g_page_textures); +} + + + +class TextureCategoryLoadShader +{ + const char* m_directory; + std::size_t& m_count; +public: + typedef const char* first_argument_type; + + TextureCategoryLoadShader(const char* directory, std::size_t& count) + : m_directory(directory), m_count(count) + { + m_count = 0; + } + void operator()(const char* name) const + { + if(shader_equal_prefix(name, "textures/") + && shader_equal_prefix(name + string_length("textures/"), m_directory)) + { + ++m_count; + // request the shader, this will load the texture if needed + // this Shader_ForName call is a kind of hack + IShader *pFoo = QERApp_Shader_ForName(name); + pFoo->DecRef(); + } + } +}; + +void TextureDirectory_loadTexture(const char* directory, const char* texture) +{ + StringOutputStream name(256); + name << directory << StringRange(texture, path_get_filename_base_end(texture)); + + if(texture_name_ignore(name.c_str())) + { + return; + } + + if (!shader_valid(name.c_str())) + { + globalOutputStream() << "Skipping invalid texture name: [" << name.c_str() << "]\n"; + return; + } + + // if a texture is already in use to represent a shader, ignore it + IShader* shader = QERApp_Shader_ForName(name.c_str()); + shader->DecRef(); +} +typedef ConstPointerCaller1 TextureDirectoryLoadTextureCaller; + +class LoadTexturesByTypeVisitor : public ImageModules::Visitor +{ + const char* m_dirstring; +public: + LoadTexturesByTypeVisitor(const char* dirstring) + : m_dirstring(dirstring) + { + } + void visit(const char* minor, const _QERPlugImageTable& table) const + { + GlobalFileSystem().forEachFile(m_dirstring, minor, TextureDirectoryLoadTextureCaller(m_dirstring)); + } +}; + +void TextureBrowser_ShowDirectory(TextureBrowser& textureBrowser, const char* directory) +{ + if(TextureBrowser_showWads()) + { + Archive* archive = GlobalFileSystem().getArchive(directory); + ASSERT_NOTNULL(archive); + LoadShaderVisitor visitor; + archive->forEachFile(Archive::VisitorFunc(visitor, Archive::eFiles, 0), "textures/"); + } + else + { + g_TextureBrowser_currentDirectory = directory; + TextureBrowser_heightChanged(textureBrowser); + + std::size_t shaders_count; + GlobalShaderSystem().foreachShaderName(makeCallback1(TextureCategoryLoadShader(directory, shaders_count))); + globalOutputStream() << "Showing " << Unsigned(shaders_count) << " shaders.\n"; + + if(g_pGameDescription->mGameType != "doom3") + { + // load remaining texture files + + StringOutputStream dirstring(64); + dirstring << "textures/" << directory; + + Radiant_getImageModules().foreachModule(LoadTexturesByTypeVisitor(dirstring.c_str())); + } + } + + // we'll display the newly loaded textures + all the ones already in use + TextureBrowser_SetHideUnused(textureBrowser, false); + + TextureBrowser_updateTitle(); +} + +void TextureBrowser_ShowTagSearchResult(TextureBrowser& textureBrowser, const char* directory) +{ + g_TextureBrowser_currentDirectory = directory; + TextureBrowser_heightChanged(textureBrowser); + + std::size_t shaders_count; + GlobalShaderSystem().foreachShaderName(makeCallback1(TextureCategoryLoadShader(directory, shaders_count))); + globalOutputStream() << "Showing " << Unsigned(shaders_count) << " shaders.\n"; + + if(g_pGameDescription->mGameType != "doom3") + { + // load remaining texture files + StringOutputStream dirstring(64); + dirstring << "textures/" << directory; + + { + LoadTexturesByTypeVisitor visitor(dirstring.c_str()); + Radiant_getImageModules().foreachModule(visitor); + } + } + + // we'll display the newly loaded textures + all the ones already in use + TextureBrowser_SetHideUnused(textureBrowser, false); +} + + +bool TextureBrowser_hideUnused(); + +void TextureBrowser_hideUnusedExport(const BoolImportCallback& importer) +{ + importer(TextureBrowser_hideUnused()); +} +typedef FreeCaller1 TextureBrowserHideUnusedExport; + +void TextureBrowser_showShadersExport(const BoolImportCallback& importer) +{ + importer(GlobalTextureBrowser().m_showShaders); +} +typedef FreeCaller1 TextureBrowserShowShadersExport; + +void TextureBrowser_showShaderlistOnly(const BoolImportCallback& importer) +{ + importer(g_TextureBrowser_shaderlistOnly); +} +typedef FreeCaller1 TextureBrowserShowShaderlistOnlyExport; + +void TextureBrowser_fixedSize(const BoolImportCallback& importer) +{ + importer(g_TextureBrowser_fixedSize); +} +typedef FreeCaller1 TextureBrowser_FixedSizeExport; + +void TextureBrowser_filterNotex(const BoolImportCallback& importer) +{ + importer(g_TextureBrowser_filterNotex); +} +typedef FreeCaller1 TextureBrowser_filterNotexExport; + +void TextureBrowser_SetHideUnused(TextureBrowser& textureBrowser, bool hideUnused) +{ + if(hideUnused) + { + textureBrowser.m_hideUnused = true; + } + else + { + textureBrowser.m_hideUnused = false; + } + + textureBrowser.m_hideunused_item.update(); + + TextureBrowser_heightChanged(textureBrowser); + textureBrowser.m_originInvalid = true; +} + +void TextureBrowser_ShowStartupShaders(TextureBrowser& textureBrowser) +{ + if(textureBrowser.m_startupShaders == STARTUPSHADERS_COMMON) + { + TextureBrowser_ShowDirectory(textureBrowser, TextureBrowser_getComonShadersDir()); + } +} + + +//++timo NOTE: this is a mix of Shader module stuff and texture explorer +// it might need to be split in parts or moved out .. dunno +// scroll origin so the specified texture is completely on screen +// if current texture is not displayed, nothing is changed +void TextureBrowser_Focus(TextureBrowser& textureBrowser, const char* name) +{ + TextureLayout layout; + // scroll origin so the texture is completely on screen + Texture_StartPos(layout); + + for(QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement()) + { + IShader* shader = QERApp_ActiveShaders_IteratorCurrent(); + + if(!Texture_IsShown(shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused)) + continue; + + int x, y; + Texture_NextPos(textureBrowser, layout, shader->getTexture(), &x, &y); + qtexture_t* q = shader->getTexture(); + if (!q) + break; + + // we have found when texdef->name and the shader name match + // NOTE: as everywhere else for our comparisons, we are not case sensitive + if (shader_equal(name, shader->getName())) + { + int textureHeight = (int)(q->height * ((float)textureBrowser.m_textureScale / 100)) + + 2 * TextureBrowser_fontHeight(textureBrowser); + + int originy = TextureBrowser_getOriginY(textureBrowser); + if (y > originy) + { + originy = y; + } + + if (y - textureHeight < originy - textureBrowser.height) + { + originy = (y - textureHeight) + textureBrowser.height; + } + + TextureBrowser_setOriginY(textureBrowser, originy); + return; + } + } +} + +IShader* Texture_At(TextureBrowser& textureBrowser, int mx, int my) +{ + my += TextureBrowser_getOriginY(textureBrowser) - textureBrowser.height; + + TextureLayout layout; + Texture_StartPos(layout); + for(QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement()) + { + IShader* shader = QERApp_ActiveShaders_IteratorCurrent(); + + if(!Texture_IsShown(shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused)) + continue; + + int x, y; + Texture_NextPos(textureBrowser, layout, shader->getTexture(), &x, &y); + qtexture_t *q = shader->getTexture(); + if (!q) + break; + + int nWidth = textureBrowser.getTextureWidth(q); + int nHeight = textureBrowser.getTextureHeight(q); + if (mx > x && mx - x < nWidth + && my < y && y - my < nHeight + TextureBrowser_fontHeight(textureBrowser)) + { + return shader; + } + } + + return 0; +} + +/* +============== +SelectTexture + + By mouse click +============== +*/ +void SelectTexture(TextureBrowser& textureBrowser, int mx, int my, bool bShift) +{ + IShader* shader = Texture_At(textureBrowser, mx, my); + if(shader != 0) + { + if (bShift) + { + if (shader->IsDefault()) + globalOutputStream() << "ERROR: " << shader->getName() << " is not a shader, it's a texture.\n"; + else + ViewShader( shader->getShaderFileName(), shader->getName() ); + } + else + { + TextureBrowser_SetSelectedShader(textureBrowser, shader->getName()); + TextureBrowser_textureSelected(shader->getName()); + + if (!FindTextureDialog_isOpen() && !textureBrowser.m_rmbSelected) + { + UndoableCommand undo("textureNameSetSelected"); + Select_SetShader(shader->getName()); + } + } + } +} + +/* +============================================================================ + + MOUSE ACTIONS + +============================================================================ +*/ + +void TextureBrowser_trackingDelta(int x, int y, unsigned int state, void* data) +{ + TextureBrowser& textureBrowser = *reinterpret_cast(data); + if(y != 0) + { + int scale = 1; + + if(state & GDK_SHIFT_MASK) + scale = 4; + + int originy = TextureBrowser_getOriginY(textureBrowser); + originy += y * scale; + TextureBrowser_setOriginY(textureBrowser, originy); + } +} + +void TextureBrowser_Tracking_MouseDown(TextureBrowser& textureBrowser) +{ + textureBrowser.m_freezePointer.freeze_pointer(textureBrowser.m_parent, TextureBrowser_trackingDelta, &textureBrowser); +} + +void TextureBrowser_Tracking_MouseUp(TextureBrowser& textureBrowser) +{ + textureBrowser.m_freezePointer.unfreeze_pointer(textureBrowser.m_parent); +} + +void TextureBrowser_Selection_MouseDown(TextureBrowser& textureBrowser, guint32 flags, int pointx, int pointy) +{ + SelectTexture(textureBrowser, pointx, textureBrowser.height - 1 - pointy, (flags & GDK_SHIFT_MASK) != 0); +} + +/* +============================================================================ + +DRAWING + +============================================================================ +*/ + +/* +============ +Texture_Draw +TTimo: relying on the shaders list to display the textures +we must query all qtexture_t* to manage and display through the IShaders interface +this allows a plugin to completely override the texture system +============ +*/ +void Texture_Draw(TextureBrowser& textureBrowser) +{ + int originy = TextureBrowser_getOriginY(textureBrowser); + + glClearColor(textureBrowser.color_textureback[0], + textureBrowser.color_textureback[1], + textureBrowser.color_textureback[2], + 0); + glViewport(0, 0, textureBrowser.width, textureBrowser.height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDisable (GL_DEPTH_TEST); + glDisable(GL_BLEND); + glOrtho (0, textureBrowser.width, originy-textureBrowser.height, originy, -100, 100); + glEnable (GL_TEXTURE_2D); + + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + + int last_y = 0, last_height = 0; + + TextureLayout layout; + Texture_StartPos(layout); + for(QERApp_ActiveShaders_IteratorBegin(); !QERApp_ActiveShaders_IteratorAtEnd(); QERApp_ActiveShaders_IteratorIncrement()) + { + IShader* shader = QERApp_ActiveShaders_IteratorCurrent(); + + if(!Texture_IsShown(shader, textureBrowser.m_showShaders, textureBrowser.m_hideUnused)) + continue; + + int x, y; + Texture_NextPos(textureBrowser, layout, shader->getTexture(), &x, &y); + qtexture_t *q = shader->getTexture(); + if (!q) + break; + + int nWidth = textureBrowser.getTextureWidth(q); + int nHeight = textureBrowser.getTextureHeight(q); + + if (y != last_y) + { + last_y = y; + last_height = 0; + } + last_height = std::max (nHeight, last_height); + + // Is this texture visible? + if ((y-nHeight-TextureBrowser_fontHeight(textureBrowser) < originy) + && (y > originy - textureBrowser.height)) + { + // borders rules: + // if it's the current texture, draw a thick red line, else: + // shaders have a white border, simple textures don't + // if !texture_showinuse: (some textures displayed may not be in use) + // draw an additional square around with 0.5 1 0.5 color + if (shader_equal(TextureBrowser_GetSelectedShader(textureBrowser), shader->getName())) + { + glLineWidth (3); + if(textureBrowser.m_rmbSelected) + { + glColor3f (0,0,1); + } else { + glColor3f (1,0,0); + } + glDisable (GL_TEXTURE_2D); + + glBegin (GL_LINE_LOOP); + glVertex2i (x-4,y-TextureBrowser_fontHeight(textureBrowser)+4); + glVertex2i (x-4,y-TextureBrowser_fontHeight(textureBrowser)-nHeight-4); + glVertex2i (x+4+nWidth,y-TextureBrowser_fontHeight(textureBrowser)-nHeight-4); + glVertex2i (x+4+nWidth,y-TextureBrowser_fontHeight(textureBrowser)+4); + glEnd(); + + glEnable (GL_TEXTURE_2D); + glLineWidth (1); + } + else + { + glLineWidth (1); + // shader border: + if (!shader->IsDefault()) + { + glColor3f (1,1,1); + glDisable (GL_TEXTURE_2D); + + glBegin (GL_LINE_LOOP); + glVertex2i (x-1,y+1-TextureBrowser_fontHeight(textureBrowser)); + glVertex2i (x-1,y-nHeight-1-TextureBrowser_fontHeight(textureBrowser)); + glVertex2i (x+1+nWidth,y-nHeight-1-TextureBrowser_fontHeight(textureBrowser)); + glVertex2i (x+1+nWidth,y+1-TextureBrowser_fontHeight(textureBrowser)); + glEnd(); + glEnable (GL_TEXTURE_2D); + } + + // highlight in-use textures + if (!textureBrowser.m_hideUnused && shader->IsInUse()) + { + glColor3f (0.5,1,0.5); + glDisable (GL_TEXTURE_2D); + glBegin (GL_LINE_LOOP); + glVertex2i (x-3,y+3-TextureBrowser_fontHeight(textureBrowser)); + glVertex2i (x-3,y-nHeight-3-TextureBrowser_fontHeight(textureBrowser)); + glVertex2i (x+3+nWidth,y-nHeight-3-TextureBrowser_fontHeight(textureBrowser)); + glVertex2i (x+3+nWidth,y+3-TextureBrowser_fontHeight(textureBrowser)); + glEnd(); + glEnable (GL_TEXTURE_2D); + } + } + + // Draw the texture + glBindTexture (GL_TEXTURE_2D, q->texture_number); + GlobalOpenGL_debugAssertNoErrors(); + glColor3f (1,1,1); + glBegin (GL_QUADS); + glTexCoord2i (0,0); + glVertex2i (x,y-TextureBrowser_fontHeight(textureBrowser)); + glTexCoord2i (1,0); + glVertex2i (x+nWidth,y-TextureBrowser_fontHeight(textureBrowser)); + glTexCoord2i (1,1); + glVertex2i (x+nWidth,y-TextureBrowser_fontHeight(textureBrowser)-nHeight); + glTexCoord2i (0,1); + glVertex2i (x,y-TextureBrowser_fontHeight(textureBrowser)-nHeight); + glEnd(); + + // draw the texture name + glDisable (GL_TEXTURE_2D); + glColor3f (1,1,1); + + glRasterPos2i (x, y-TextureBrowser_fontHeight(textureBrowser)+5); + + // don't draw the directory name + const char* name = shader->getName(); + name += strlen(name); + while(name != shader->getName() && *(name-1) != '/' && *(name-1) != '\\') + name--; + + GlobalOpenGL().drawString(name); + glEnable (GL_TEXTURE_2D); + } + + //int totalHeight = abs(y) + last_height + TextureBrowser_fontHeight(textureBrowser) + 4; + } + + + // reset the current texture + glBindTexture(GL_TEXTURE_2D, 0); + //qglFinish(); +} + +void TextureBrowser_queueDraw(TextureBrowser& textureBrowser) +{ + if(textureBrowser.m_gl_widget != 0) + { + gtk_widget_queue_draw(textureBrowser.m_gl_widget); + } +} + + +void TextureBrowser_setScale(TextureBrowser& textureBrowser, std::size_t scale) +{ + textureBrowser.m_textureScale = scale; + + TextureBrowser_queueDraw(textureBrowser); +} + + +void TextureBrowser_MouseWheel(TextureBrowser& textureBrowser, bool bUp) +{ + int originy = TextureBrowser_getOriginY(textureBrowser); + + if (bUp) + { + originy += int(textureBrowser.m_mouseWheelScrollIncrement); + } + else + { + originy -= int(textureBrowser.m_mouseWheelScrollIncrement); + } + + TextureBrowser_setOriginY(textureBrowser, originy); +} + +XmlTagBuilder TagBuilder; + +enum +{ + TAG_COLUMN, + N_COLUMNS +}; + +void BuildStoreAssignedTags(GtkListStore* store, const char* shader, TextureBrowser* textureBrowser) +{ + GtkTreeIter iter; + + gtk_list_store_clear(store); + + std::vector assigned_tags; + TagBuilder.GetShaderTags(shader, assigned_tags); + + for (size_t i = 0; i < assigned_tags.size(); i++) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, TAG_COLUMN, assigned_tags[i].c_str(), -1); + } +} + +void BuildStoreAvailableTags( GtkListStore* storeAvailable, + GtkListStore* storeAssigned, + const std::set& allTags, + TextureBrowser* textureBrowser) +{ + GtkTreeIter iterAssigned; + GtkTreeIter iterAvailable; + std::set::const_iterator iterAll; + gchar* tag_assigned; + + gtk_list_store_clear(storeAvailable); + + bool row = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(storeAssigned), &iterAssigned) != 0; + + if(!row) // does the shader have tags assigned? + { + for (iterAll = allTags.begin(); iterAll != allTags.end(); ++iterAll) + { + gtk_list_store_append (storeAvailable, &iterAvailable); + gtk_list_store_set (storeAvailable, &iterAvailable, TAG_COLUMN, (*iterAll).c_str(), -1); + } + } + else + { + while(row) // available tags = all tags - assigned tags + { + gtk_tree_model_get(GTK_TREE_MODEL(storeAssigned), &iterAssigned, TAG_COLUMN, &tag_assigned, -1); + + for (iterAll = allTags.begin(); iterAll != allTags.end(); ++iterAll) + { + if(strcmp((char*)tag_assigned, (*iterAll).c_str()) != 0) + { + gtk_list_store_append (storeAvailable, &iterAvailable); + gtk_list_store_set (storeAvailable, &iterAvailable, TAG_COLUMN, (*iterAll).c_str(), -1); + } + else + { + row = gtk_tree_model_iter_next(GTK_TREE_MODEL(storeAssigned), &iterAssigned) != 0; + + if(row) + { + gtk_tree_model_get(GTK_TREE_MODEL(storeAssigned), &iterAssigned, TAG_COLUMN, &tag_assigned, -1); + } + } + } + } + } +} + +gboolean TextureBrowser_button_press(GtkWidget* widget, GdkEventButton* event, TextureBrowser* textureBrowser) +{ + if(event->type == GDK_BUTTON_PRESS) + { + if(event->button == 3) + { + if(GlobalTextureBrowser().m_tags) + { + textureBrowser->m_rmbSelected = true; + TextureBrowser_Selection_MouseDown (*textureBrowser, event->state, static_cast(event->x), static_cast(event->y)); + + BuildStoreAssignedTags(textureBrowser->m_assigned_store, textureBrowser->shader.c_str(), textureBrowser); + BuildStoreAvailableTags(textureBrowser->m_available_store, textureBrowser->m_assigned_store, textureBrowser->m_all_tags, textureBrowser); + textureBrowser->m_heightChanged = true; + gtk_widget_show(textureBrowser->m_tag_frame); + + process_gui(); + + TextureBrowser_Focus(*textureBrowser, textureBrowser->shader.c_str()); + } + else + { + TextureBrowser_Tracking_MouseDown(*textureBrowser); + } + } + else if(event->button == 1) + { + TextureBrowser_Selection_MouseDown(*textureBrowser, event->state, static_cast(event->x), static_cast(event->y)); + + if(GlobalTextureBrowser().m_tags) + { + textureBrowser->m_rmbSelected = false; + gtk_widget_hide(textureBrowser->m_tag_frame); + } + } + } + return FALSE; +} + +gboolean TextureBrowser_button_release(GtkWidget* widget, GdkEventButton* event, TextureBrowser* textureBrowser) +{ + if(event->type == GDK_BUTTON_RELEASE) + { + if(event->button == 3) + { + if(!GlobalTextureBrowser().m_tags) + { + TextureBrowser_Tracking_MouseUp(*textureBrowser); + } + } + } + return FALSE; +} + +gboolean TextureBrowser_motion(GtkWidget *widget, GdkEventMotion *event, TextureBrowser* textureBrowser) +{ + return FALSE; +} + +gboolean TextureBrowser_scroll(GtkWidget* widget, GdkEventScroll* event, TextureBrowser* textureBrowser) +{ + if(event->direction == GDK_SCROLL_UP) + { + TextureBrowser_MouseWheel(*textureBrowser, true); + } + else if(event->direction == GDK_SCROLL_DOWN) + { + TextureBrowser_MouseWheel(*textureBrowser, false); + } + return FALSE; +} + +void TextureBrowser_scrollChanged(void* data, gdouble value) +{ + //globalOutputStream() << "vertical scroll\n"; + TextureBrowser_setOriginY(*reinterpret_cast(data), -(int)value); +} + +static void TextureBrowser_verticalScroll(GtkAdjustment *adjustment, TextureBrowser* textureBrowser) +{ + textureBrowser->m_scrollAdjustment.value_changed(adjustment->value); +} + +void TextureBrowser_updateScroll(TextureBrowser& textureBrowser) +{ + if(textureBrowser.m_showTextureScrollbar) + { + int totalHeight = TextureBrowser_TotalHeight(textureBrowser); + + totalHeight = std::max(totalHeight, textureBrowser.height); + + GtkAdjustment *vadjustment = gtk_range_get_adjustment(GTK_RANGE(textureBrowser.m_texture_scroll)); + + vadjustment->value = -TextureBrowser_getOriginY(textureBrowser); + vadjustment->page_size = textureBrowser.height; + vadjustment->page_increment = textureBrowser.height/2; + vadjustment->step_increment = 20; + vadjustment->lower = 0; + vadjustment->upper = totalHeight; + + g_signal_emit_by_name(G_OBJECT (vadjustment), "changed"); + } +} + +gboolean TextureBrowser_size_allocate(GtkWidget* widget, GtkAllocation* allocation, TextureBrowser* textureBrowser) +{ + textureBrowser->width = allocation->width; + textureBrowser->height = allocation->height; + TextureBrowser_heightChanged(*textureBrowser); + textureBrowser->m_originInvalid = true; + TextureBrowser_queueDraw(*textureBrowser); + return FALSE; +} + +gboolean TextureBrowser_expose(GtkWidget* widget, GdkEventExpose* event, TextureBrowser* textureBrowser) +{ + if(glwidget_make_current(textureBrowser->m_gl_widget) != FALSE) + { + GlobalOpenGL_debugAssertNoErrors(); + TextureBrowser_evaluateHeight(*textureBrowser); + Texture_Draw(*textureBrowser); + GlobalOpenGL_debugAssertNoErrors(); + glwidget_swap_buffers(textureBrowser->m_gl_widget); + } + return FALSE; +} + + +TextureBrowser g_TextureBrowser; + +TextureBrowser& GlobalTextureBrowser() +{ + return g_TextureBrowser; +} + +bool TextureBrowser_hideUnused() +{ + return g_TextureBrowser.m_hideUnused; +} + +void TextureBrowser_ToggleHideUnused() +{ + if(g_TextureBrowser.m_hideUnused) + { + TextureBrowser_SetHideUnused(g_TextureBrowser, false); + } + else + { + TextureBrowser_SetHideUnused(g_TextureBrowser, true); + } +} + +void TextureGroups_constructTreeModel(TextureGroups groups, GtkTreeStore* store) +{ + // put the information from the old textures menu into a treeview + GtkTreeIter iter, child; + + TextureGroups::const_iterator i = groups.begin(); + while (i != groups.end()) + { + const char* dirName = (*i).c_str(); + const char* firstUnderscore = strchr(dirName, '_'); + StringRange dirRoot (dirName, (firstUnderscore == 0) ? dirName : firstUnderscore + 1); + + TextureGroups::const_iterator next = i; + ++next; + if(firstUnderscore != 0 + && next != groups.end() + && string_equal_start((*next).c_str(), dirRoot)) + { + gtk_tree_store_append(store, &iter, NULL); + gtk_tree_store_set (store, &iter, 0, CopiedString(StringRange(dirName, firstUnderscore)).c_str(), -1); + + // keep going... + while (i != groups.end() && string_equal_start((*i).c_str(), dirRoot)) + { + gtk_tree_store_append(store, &child, &iter); + gtk_tree_store_set (store, &child, 0, (*i).c_str(), -1); + ++i; + } + } + else + { + gtk_tree_store_append(store, &iter, NULL); + gtk_tree_store_set (store, &iter, 0, dirName, -1); + ++i; + } + } +} + +TextureGroups TextureGroups_constructTreeView() +{ + TextureGroups groups; + + if (TextureBrowser_showWads()) + { + GlobalFileSystem().forEachArchive (TextureGroupsAddWadCaller (groups)); + } + else + { + // scan texture dirs and pak files only if not restricting to shaderlist + if (g_pGameDescription->mGameType != "doom3" && !g_TextureBrowser_shaderlistOnly) + { + GlobalFileSystem().forEachDirectory ("textures/", TextureGroupsAddDirectoryCaller(groups)); + } + + GlobalShaderSystem().foreachShaderName(TextureGroupsAddShaderCaller(groups)); + } + + return groups; +} + +void TextureBrowser_constructTreeStore() +{ + TextureGroups groups = TextureGroups_constructTreeView(); + GtkTreeStore* store = gtk_tree_store_new(1, G_TYPE_STRING); + TextureGroups_constructTreeModel(groups, store); + std::set::iterator iter; + + GtkTreeModel* model = GTK_TREE_MODEL(store); + + gtk_tree_view_set_model(GTK_TREE_VIEW(g_TextureBrowser.m_treeViewTree), model); + + g_object_unref(G_OBJECT(store)); +} + +void TextureBrowser_constructTreeStoreTags() +{ + TextureGroups groups; + GtkTreeStore* store = gtk_tree_store_new(1, G_TYPE_STRING); + GtkTreeModel* model = GTK_TREE_MODEL(g_TextureBrowser.m_all_tags_list); + + gtk_tree_view_set_model(GTK_TREE_VIEW(g_TextureBrowser.m_treeViewTags), model); + + g_object_unref(G_OBJECT(store)); +} + +void TreeView_onRowActivated(GtkTreeView* treeview, GtkTreePath* path, GtkTreeViewColumn* col, gpointer userdata) +{ + GtkTreeIter iter; + + GtkTreeModel* model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview)); + + if (gtk_tree_model_get_iter (model, &iter, path)) + { + gchar dirName[1024]; + + gchar* buffer; + gtk_tree_model_get(model, &iter, 0, &buffer, -1); + strcpy(dirName, buffer); + g_free(buffer); + + g_TextureBrowser.m_searchedTags = false; + + if(!TextureBrowser_showWads()) + strcat(dirName, "/"); + + ScopeDisableScreenUpdates disableScreenUpdates(dirName, "Loading Textures"); + TextureBrowser_ShowDirectory(GlobalTextureBrowser (), dirName); + TextureBrowser_queueDraw(GlobalTextureBrowser ()); + } +} + +void TextureBrowser_createTreeViewTree() +{ + GtkCellRenderer* renderer; + g_TextureBrowser.m_treeViewTree = GTK_WIDGET(gtk_tree_view_new()); + gtk_tree_view_set_enable_search(GTK_TREE_VIEW(g_TextureBrowser.m_treeViewTree), FALSE); + + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(g_TextureBrowser.m_treeViewTree), FALSE); + g_signal_connect(g_TextureBrowser.m_treeViewTree, "row-activated", (GCallback) TreeView_onRowActivated, NULL); + + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(g_TextureBrowser.m_treeViewTree), -1, "", renderer, "text", 0, NULL); + + TextureBrowser_constructTreeStore(); +} + +void TextureBrowser_addTag(); +void TextureBrowser_renameTag(); +void TextureBrowser_deleteTag(); + +void TextureBrowser_createContextMenu(GtkWidget *treeview, GdkEventButton *event) +{ + GtkWidget* menu = gtk_menu_new(); + + GtkWidget* menuitem = gtk_menu_item_new_with_label("Add tag"); + g_signal_connect(menuitem, "activate", (GCallback)TextureBrowser_addTag, treeview); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + menuitem = gtk_menu_item_new_with_label("Rename tag"); + g_signal_connect(menuitem, "activate", (GCallback)TextureBrowser_renameTag, treeview); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + menuitem = gtk_menu_item_new_with_label("Delete tag"); + g_signal_connect(menuitem, "activate", (GCallback)TextureBrowser_deleteTag, treeview); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + gtk_widget_show_all(menu); + + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, + (event != NULL) ? event->button : 0, + gdk_event_get_time((GdkEvent*)event)); +} + +gboolean TreeViewTags_onButtonPressed(GtkWidget *treeview, GdkEventButton *event) +{ + if (event->type == GDK_BUTTON_PRESS && event->button == 3) + { + GtkTreePath *path; + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); + + if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), event->x, event->y, &path, NULL, NULL, NULL)) + { + gtk_tree_selection_unselect_all(selection); + gtk_tree_selection_select_path(selection, path); + gtk_tree_path_free(path); + } + + TextureBrowser_createContextMenu(treeview, event); + return TRUE; + } + return FALSE; +} + +void TextureBrowser_createTreeViewTags() +{ + GtkCellRenderer* renderer; + g_TextureBrowser.m_treeViewTags = GTK_WIDGET(gtk_tree_view_new()); + gtk_tree_view_set_enable_search(GTK_TREE_VIEW(g_TextureBrowser.m_treeViewTags), FALSE); + + g_signal_connect(GTK_TREE_VIEW(g_TextureBrowser.m_treeViewTags), "button-press-event", (GCallback)TreeViewTags_onButtonPressed, NULL); + + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(g_TextureBrowser.m_treeViewTags), FALSE); + + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(g_TextureBrowser.m_treeViewTags), -1, "", renderer, "text", 0, NULL); + + TextureBrowser_constructTreeStoreTags(); +} + +GtkMenuItem* TextureBrowser_constructViewMenu(GtkMenu* menu) +{ + GtkMenuItem* textures_menu_item = new_sub_menu_item_with_mnemonic("_View"); + + if(g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu); + + create_check_menu_item_with_mnemonic(menu, "Hide _Unused", "ShowInUse"); + if(string_empty(g_pGameDescription->getKeyValue("show_wads"))) + { + create_check_menu_item_with_mnemonic(menu, "Hide Image Missing", "FilterNotex"); + } + menu_separator(menu); + + create_menu_item_with_mnemonic(menu, "Show All", "ShowAllTextures"); + + // we always want to show shaders but don't want a "Show Shaders" menu for doom3 and .wad file games + if(g_pGameDescription->mGameType == "doom3" || !string_empty(g_pGameDescription->getKeyValue("show_wads"))) + { + g_TextureBrowser.m_showShaders = true; + } + else + { + create_check_menu_item_with_mnemonic(menu, "Show shaders", "ToggleShowShaders"); + } + + if(g_pGameDescription->mGameType != "doom3" && string_empty(g_pGameDescription->getKeyValue("show_wads"))) + { + create_check_menu_item_with_mnemonic (menu, "Shaders Only", "ToggleShowShaderlistOnly"); + } + if(g_TextureBrowser.m_tags) + { + create_menu_item_with_mnemonic(menu, "Show Untagged", "ShowUntagged"); + } + + create_check_menu_item_with_mnemonic(menu, "Fixed Size", "FixedSize"); + + if(string_empty(g_pGameDescription->getKeyValue("show_wads"))) + { + menu_separator(menu); + g_TextureBrowser.m_shader_info_item = GTK_WIDGET(create_menu_item_with_mnemonic(menu, "Shader Info", "ShaderInfo")); + gtk_widget_set_sensitive(g_TextureBrowser.m_shader_info_item, FALSE); + } + + return textures_menu_item; +} + +GtkMenuItem* TextureBrowser_constructToolsMenu(GtkMenu* menu) +{ + GtkMenuItem* textures_menu_item = new_sub_menu_item_with_mnemonic("_Tools"); + + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu); + + create_menu_item_with_mnemonic(menu, "Flush & Reload Shaders", "RefreshShaders"); + create_menu_item_with_mnemonic(menu, "Find / Replace...", "FindReplaceTextures"); + + return textures_menu_item; +} + +GtkMenuItem* TextureBrowser_constructTagsMenu(GtkMenu* menu) +{ + GtkMenuItem* textures_menu_item = new_sub_menu_item_with_mnemonic("T_ags"); + + if (g_Layout_enableDetachableMenus.m_value) + menu_tearoff (menu); + + create_menu_item_with_mnemonic(menu, "Add tag", "AddTag"); + create_menu_item_with_mnemonic(menu, "Rename tag", "RenameTag"); + create_menu_item_with_mnemonic(menu, "Delete tag", "DeleteTag"); + menu_separator(menu); + create_menu_item_with_mnemonic(menu, "Copy tags from selected", "CopyTag"); + create_menu_item_with_mnemonic(menu, "Paste tags to selected", "PasteTag"); + + return textures_menu_item; +} + +gboolean TextureBrowser_tagMoveHelper(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, GSList** selected) +{ + g_assert(selected != NULL); + + GtkTreeRowReference* rowref = gtk_tree_row_reference_new (model, path); + *selected = g_slist_append(*selected, rowref); + + return FALSE; +} + +void TextureBrowser_assignTags() +{ + GSList* selected = NULL; + GSList* node; + gchar* tag_assigned; + + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(g_TextureBrowser.m_available_tree)); + + gtk_tree_selection_selected_foreach(selection, (GtkTreeSelectionForeachFunc)TextureBrowser_tagMoveHelper, &selected); + + if(selected != NULL) + { + for (node = selected; node != NULL; node = node->next) + { + GtkTreePath* path = gtk_tree_row_reference_get_path((GtkTreeRowReference*)node->data); + + if(path) + { + GtkTreeIter iter; + + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(g_TextureBrowser.m_available_store), &iter, path)) + { + gtk_tree_model_get(GTK_TREE_MODEL(g_TextureBrowser.m_available_store), &iter, TAG_COLUMN, &tag_assigned, -1); + if(!TagBuilder.CheckShaderTag(g_TextureBrowser.shader.c_str())) + { + // create a custom shader/texture entry + IShader* ishader = QERApp_Shader_ForName(g_TextureBrowser.shader.c_str()); + CopiedString filename = ishader->getShaderFileName(); + + if(filename.empty()) + { + // it's a texture + TagBuilder.AddShaderNode(g_TextureBrowser.shader.c_str(), CUSTOM, TEXTURE); + } else { + // it's a shader + TagBuilder.AddShaderNode(g_TextureBrowser.shader.c_str(), CUSTOM, SHADER); + } + ishader->DecRef(); + } + TagBuilder.AddShaderTag(g_TextureBrowser.shader.c_str(), (char*)tag_assigned, TAG); + + gtk_list_store_remove(g_TextureBrowser.m_available_store, &iter); + gtk_list_store_append (g_TextureBrowser.m_assigned_store, &iter); + gtk_list_store_set (g_TextureBrowser.m_assigned_store, &iter, TAG_COLUMN, (char*)tag_assigned, -1); + } + } + } + + g_slist_foreach(selected, (GFunc)gtk_tree_row_reference_free, NULL); + + // Save changes + TagBuilder.SaveXmlDoc(); + } + g_slist_free(selected); +} + +void TextureBrowser_removeTags() +{ + GSList* selected = NULL; + GSList* node; + gchar* tag; + + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(g_TextureBrowser.m_assigned_tree)); + + gtk_tree_selection_selected_foreach(selection, (GtkTreeSelectionForeachFunc)TextureBrowser_tagMoveHelper, &selected); + + if(selected != NULL) + { + for (node = selected; node != NULL; node = node->next) + { + GtkTreePath* path = gtk_tree_row_reference_get_path((GtkTreeRowReference*)node->data); + + if (path) + { + GtkTreeIter iter; + + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(g_TextureBrowser.m_assigned_store), &iter, path)) + { + gtk_tree_model_get(GTK_TREE_MODEL(g_TextureBrowser.m_assigned_store), &iter, TAG_COLUMN, &tag, -1); + TagBuilder.DeleteShaderTag(g_TextureBrowser.shader.c_str(), tag); + gtk_list_store_remove(g_TextureBrowser.m_assigned_store, &iter); + } + } + } + + g_slist_foreach(selected, (GFunc)gtk_tree_row_reference_free, NULL); + + // Update the "available tags list" + BuildStoreAvailableTags(g_TextureBrowser.m_available_store, g_TextureBrowser.m_assigned_store, g_TextureBrowser.m_all_tags, &g_TextureBrowser); + + // Save changes + TagBuilder.SaveXmlDoc(); + } + g_slist_free(selected); +} + +void TextureBrowser_buildTagList() +{ + GtkTreeIter treeIter; + gtk_list_store_clear(g_TextureBrowser.m_all_tags_list); + + std::set::iterator iter; + + for (iter = g_TextureBrowser.m_all_tags.begin(); iter != g_TextureBrowser.m_all_tags.end(); ++iter) + { + gtk_list_store_append(g_TextureBrowser.m_all_tags_list, &treeIter); + gtk_list_store_set(g_TextureBrowser.m_all_tags_list, &treeIter, TAG_COLUMN, (*iter).c_str(), -1); + } +} + +void TextureBrowser_searchTags() +{ + GSList* selected = NULL; + GSList* node; + gchar* tag; + char buffer[256]; + char tags_searched[256]; + + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(g_TextureBrowser.m_treeViewTags)); + + gtk_tree_selection_selected_foreach(selection, (GtkTreeSelectionForeachFunc)TextureBrowser_tagMoveHelper, &selected); + + if(selected != NULL) + { + strcpy(buffer, "/root/*/*[tag='"); + strcpy(tags_searched, "[TAGS] "); + + for (node = selected; node != NULL; node = node->next) + { + GtkTreePath* path = gtk_tree_row_reference_get_path((GtkTreeRowReference*)node->data); + + if (path) + { + GtkTreeIter iter; + + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(g_TextureBrowser.m_all_tags_list), &iter, path)) + { + gtk_tree_model_get(GTK_TREE_MODEL(g_TextureBrowser.m_all_tags_list), &iter, TAG_COLUMN, &tag, -1); + + strcat(buffer, tag); + strcat(tags_searched, tag); + if(node != g_slist_last(node)) + { + strcat(buffer, "' and tag='"); + strcat(tags_searched, ", "); + } + } + } + } + + strcat(buffer, "']"); + + g_slist_foreach(selected, (GFunc)gtk_tree_row_reference_free, NULL); + + g_TextureBrowser.m_found_shaders.clear(); // delete old list + TagBuilder.TagSearch(buffer, g_TextureBrowser.m_found_shaders); + + if(!g_TextureBrowser.m_found_shaders.empty()) // found something + { + size_t shaders_found = g_TextureBrowser.m_found_shaders.size(); + + globalOutputStream() << "Found " << shaders_found << " textures and shaders with " << tags_searched << "\n"; + ScopeDisableScreenUpdates disableScreenUpdates("Searching...", "Loading Textures"); + + std::set::iterator iter; + + for(iter = g_TextureBrowser.m_found_shaders.begin(); iter != g_TextureBrowser.m_found_shaders.end(); iter++) + { + std::string path = (*iter).c_str(); + size_t pos = path.find_last_of("/", path.size()); + std::string name = path.substr(pos + 1, path.size()); + path = path.substr(0, pos + 1); + TextureDirectory_loadTexture(path.c_str(), name.c_str()); + } + } + g_TextureBrowser.m_searchedTags = true; + g_TextureBrowser_currentDirectory = tags_searched; + + g_TextureBrowser.m_nTotalHeight = 0; + TextureBrowser_setOriginY(g_TextureBrowser, 0); + TextureBrowser_heightChanged(g_TextureBrowser); + TextureBrowser_updateTitle(); + } + g_slist_free(selected); +} + +void TextureBrowser_toggleSearchButton() +{ + gint page = gtk_notebook_get_current_page(GTK_NOTEBOOK(g_TextureBrowser.m_tag_notebook)); + + if(page == 0) // tag page + { + gtk_widget_show_all(g_TextureBrowser.m_search_button); + } else { + gtk_widget_hide_all(g_TextureBrowser.m_search_button); + } +} + +void TextureBrowser_constructTagNotebook() +{ + g_TextureBrowser.m_tag_notebook = gtk_notebook_new(); + GtkWidget* labelTags = gtk_label_new("Tags"); + GtkWidget* labelTextures = gtk_label_new("Textures"); + + gtk_notebook_append_page(GTK_NOTEBOOK(g_TextureBrowser.m_tag_notebook), g_TextureBrowser.m_scr_win_tree, labelTextures); + gtk_notebook_append_page(GTK_NOTEBOOK(g_TextureBrowser.m_tag_notebook), g_TextureBrowser.m_scr_win_tags, labelTags); + + g_signal_connect(G_OBJECT(g_TextureBrowser.m_tag_notebook), "switch-page", G_CALLBACK(TextureBrowser_toggleSearchButton), NULL); + + gtk_widget_show_all(g_TextureBrowser.m_tag_notebook); +} + +void TextureBrowser_constructSearchButton() +{ + GtkTooltips* tooltips = gtk_tooltips_new(); + + GtkWidget* image = gtk_image_new_from_stock(GTK_STOCK_FIND, GTK_ICON_SIZE_SMALL_TOOLBAR); + g_TextureBrowser.m_search_button = gtk_button_new(); + g_signal_connect(G_OBJECT(g_TextureBrowser.m_search_button), "clicked", G_CALLBACK(TextureBrowser_searchTags), NULL); + gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), g_TextureBrowser.m_search_button, "Search with selected tags", "Search with selected tags"); + gtk_container_add(GTK_CONTAINER(g_TextureBrowser.m_search_button), image); +} + +void TextureBrowser_checkTagFile() +{ + const char SHADERTAG_FILE[] = "shadertags.xml"; + CopiedString default_filename, rc_filename; + StringOutputStream stream(256); + + stream << LocalRcPath_get(); + stream << SHADERTAG_FILE; + rc_filename = stream.c_str(); + + if(file_exists(rc_filename.c_str())) + { + g_TextureBrowser.m_tags = TagBuilder.OpenXmlDoc(rc_filename.c_str()); + + if(g_TextureBrowser.m_tags) + { + globalOutputStream() << "Loading tag file " << rc_filename.c_str() << ".\n"; + } + } + else + { + // load default tagfile + stream.clear(); + stream << g_pGameDescription->mGameToolsPath.c_str(); + stream << SHADERTAG_FILE; + default_filename = stream.c_str(); + + if(file_exists(default_filename.c_str())) + { + g_TextureBrowser.m_tags = TagBuilder.OpenXmlDoc(default_filename.c_str(), rc_filename.c_str()); + + if(g_TextureBrowser.m_tags) + { + globalOutputStream() << "Loading default tag file " << default_filename.c_str() << ".\n"; + } + } + else + { + globalErrorStream() << "Unable to find default tag file " << default_filename.c_str() << ". No tag support.\n"; + } + } +} + +void TextureBrowser_SetNotex() +{ + StringOutputStream name(256); + name << GlobalRadiant().getAppPath() << "bitmaps/notex.bmp"; + g_notex = name.c_str(); + + name = NULL; + name << GlobalRadiant().getAppPath() << "bitmaps/shadernotex.bmp"; + g_shadernotex = name.c_str(); +} + +GtkWidget* TextureBrowser_constructWindow(GtkWindow* toplevel) +{ + // The gl_widget and the tag assignment frame should be packed into a GtkVPaned with the slider + // position stored in local.pref. gtk_paned_get_position() and gtk_paned_set_position() don't + // seem to work in gtk 2.4 and the arrow buttons don't handle GTK_FILL, so here's another thing + // for the "once-the-gtk-libs-are-updated-TODO-list" :x + + TextureBrowser_checkTagFile(); + TextureBrowser_SetNotex(); + + GlobalShaderSystem().setActiveShadersChangedNotify(ReferenceCaller(g_TextureBrowser)); + + g_TextureBrowser.m_parent = toplevel; + + GtkWidget* table = gtk_table_new(3, 3, FALSE); + GtkWidget* frame_table = NULL; + GtkWidget* vbox = gtk_vbox_new(FALSE, 0); + gtk_table_attach(GTK_TABLE(table), vbox, 0, 1, 1, 3, GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show(vbox); + + GtkWidget* menu_bar; + + { // menu bar + menu_bar = gtk_menu_bar_new(); + GtkWidget* menu_view = gtk_menu_new(); + GtkWidget* view_item = (GtkWidget*)TextureBrowser_constructViewMenu(GTK_MENU(menu_view)); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(view_item), menu_view); + gtk_menu_bar_append(GTK_MENU_BAR(menu_bar), view_item); + + GtkWidget* menu_tools = gtk_menu_new(); + GtkWidget* tools_item = (GtkWidget*)TextureBrowser_constructToolsMenu(GTK_MENU(menu_tools)); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(tools_item), menu_tools); + gtk_menu_bar_append(GTK_MENU_BAR(menu_bar), tools_item); + + gtk_table_attach(GTK_TABLE (table), menu_bar, 0, 3, 0, 1, GTK_FILL, GTK_SHRINK, 0, 0); + gtk_widget_show(menu_bar); + } + { // Texture TreeView + g_TextureBrowser.m_scr_win_tree = gtk_scrolled_window_new(NULL, NULL); + gtk_container_set_border_width(GTK_CONTAINER(g_TextureBrowser.m_scr_win_tree), 0); + + // vertical only scrolling for treeview + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(g_TextureBrowser.m_scr_win_tree), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + + gtk_widget_show(g_TextureBrowser.m_scr_win_tree); + + TextureBrowser_createTreeViewTree(); + + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(g_TextureBrowser.m_scr_win_tree), GTK_WIDGET(g_TextureBrowser.m_treeViewTree)); + gtk_widget_show(GTK_WIDGET(g_TextureBrowser.m_treeViewTree)); + } + { // gl_widget scrollbar + GtkWidget* w = gtk_vscrollbar_new(GTK_ADJUSTMENT(gtk_adjustment_new (0,0,0,1,1,1))); + gtk_table_attach(GTK_TABLE (table), w, 2, 3, 1, 2, GTK_SHRINK, GTK_FILL, 0, 0); + gtk_widget_show(w); + g_TextureBrowser.m_texture_scroll = w; + + GtkAdjustment *vadjustment = gtk_range_get_adjustment (GTK_RANGE (g_TextureBrowser.m_texture_scroll)); + g_signal_connect(G_OBJECT(vadjustment), "value_changed", G_CALLBACK(TextureBrowser_verticalScroll), &g_TextureBrowser); + + widget_set_visible(g_TextureBrowser.m_texture_scroll, g_TextureBrowser.m_showTextureScrollbar); + } + { // gl_widget + g_TextureBrowser.m_gl_widget = glwidget_new(FALSE); + gtk_widget_ref(g_TextureBrowser.m_gl_widget); + + gtk_widget_set_events(g_TextureBrowser.m_gl_widget, GDK_DESTROY | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK); + GTK_WIDGET_SET_FLAGS(g_TextureBrowser.m_gl_widget, GTK_CAN_FOCUS); + + gtk_table_attach_defaults(GTK_TABLE(table), g_TextureBrowser.m_gl_widget, 1, 2, 1, 2); + gtk_widget_show(g_TextureBrowser.m_gl_widget); + + g_TextureBrowser.m_sizeHandler = g_signal_connect(G_OBJECT(g_TextureBrowser.m_gl_widget), "size_allocate", G_CALLBACK(TextureBrowser_size_allocate), &g_TextureBrowser); + g_TextureBrowser.m_exposeHandler = g_signal_connect(G_OBJECT(g_TextureBrowser.m_gl_widget), "expose_event", G_CALLBACK(TextureBrowser_expose), &g_TextureBrowser); + + g_signal_connect(G_OBJECT(g_TextureBrowser.m_gl_widget), "button_press_event", G_CALLBACK(TextureBrowser_button_press), &g_TextureBrowser); + g_signal_connect(G_OBJECT(g_TextureBrowser.m_gl_widget), "button_release_event", G_CALLBACK(TextureBrowser_button_release), &g_TextureBrowser); + g_signal_connect(G_OBJECT(g_TextureBrowser.m_gl_widget), "motion_notify_event", G_CALLBACK(TextureBrowser_motion), &g_TextureBrowser); + g_signal_connect(G_OBJECT(g_TextureBrowser.m_gl_widget), "scroll_event", G_CALLBACK(TextureBrowser_scroll), &g_TextureBrowser); + } + + // tag stuff + if(g_TextureBrowser.m_tags) + { + { // fill tag GtkListStore + g_TextureBrowser.m_all_tags_list = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING); + GtkTreeSortable* sortable = GTK_TREE_SORTABLE(g_TextureBrowser.m_all_tags_list); + gtk_tree_sortable_set_sort_column_id(sortable, TAG_COLUMN, GTK_SORT_ASCENDING); + + TagBuilder.GetAllTags(g_TextureBrowser.m_all_tags); + TextureBrowser_buildTagList(); + } + { // tag menu bar + GtkWidget* menu_tags = gtk_menu_new(); + GtkWidget* tags_item = (GtkWidget*)TextureBrowser_constructTagsMenu(GTK_MENU(menu_tags)); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(tags_item), menu_tags); + gtk_menu_bar_append(GTK_MENU_BAR(menu_bar), tags_item); + } + { // Tag TreeView + g_TextureBrowser.m_scr_win_tags = gtk_scrolled_window_new(NULL, NULL); + gtk_container_set_border_width(GTK_CONTAINER(g_TextureBrowser.m_scr_win_tags), 0); + + // vertical only scrolling for treeview + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(g_TextureBrowser.m_scr_win_tags), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + + TextureBrowser_createTreeViewTags(); + + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(g_TextureBrowser.m_treeViewTags)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); + + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW (g_TextureBrowser.m_scr_win_tags), GTK_WIDGET (g_TextureBrowser.m_treeViewTags)); + gtk_widget_show(GTK_WIDGET(g_TextureBrowser.m_treeViewTags)); + } + { // Texture/Tag notebook + TextureBrowser_constructTagNotebook(); + gtk_box_pack_start(GTK_BOX(vbox), g_TextureBrowser.m_tag_notebook, TRUE, TRUE, 0); + } + { // Tag search button + TextureBrowser_constructSearchButton(); + gtk_box_pack_end(GTK_BOX(vbox), g_TextureBrowser.m_search_button, FALSE, FALSE, 0); + } + { // Tag frame + frame_table = gtk_table_new(3, 3, FALSE); + + g_TextureBrowser.m_tag_frame = gtk_frame_new("Tag assignment"); + gtk_frame_set_label_align(GTK_FRAME(g_TextureBrowser.m_tag_frame), 0.5, 0.5); + gtk_frame_set_shadow_type(GTK_FRAME(g_TextureBrowser.m_tag_frame), GTK_SHADOW_NONE); + + gtk_table_attach(GTK_TABLE(table), g_TextureBrowser.m_tag_frame, 1, 3, 2, 3, GTK_FILL, GTK_SHRINK, 0, 0); + + gtk_widget_show(frame_table); + + gtk_container_add (GTK_CONTAINER(g_TextureBrowser.m_tag_frame), frame_table); + } + { // assigned tag list + GtkWidget* scrolled_win = gtk_scrolled_window_new(NULL, NULL); + gtk_container_set_border_width(GTK_CONTAINER (scrolled_win), 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_win), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + + g_TextureBrowser.m_assigned_store = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING); + + GtkTreeSortable* sortable = GTK_TREE_SORTABLE(g_TextureBrowser.m_assigned_store); + gtk_tree_sortable_set_sort_column_id(sortable, TAG_COLUMN, GTK_SORT_ASCENDING); + + GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); + + g_TextureBrowser.m_assigned_tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL (g_TextureBrowser.m_assigned_store)); + g_object_unref(G_OBJECT (g_TextureBrowser.m_assigned_store)); + g_signal_connect(g_TextureBrowser.m_assigned_tree, "row-activated", (GCallback) TextureBrowser_removeTags, NULL); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW (g_TextureBrowser.m_assigned_tree), FALSE); + + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(g_TextureBrowser.m_assigned_tree)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); + + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes("", renderer, "text", TAG_COLUMN, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW (g_TextureBrowser.m_assigned_tree), column); + gtk_widget_show(g_TextureBrowser.m_assigned_tree); + + gtk_widget_show(scrolled_win); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW (scrolled_win), GTK_WIDGET (g_TextureBrowser.m_assigned_tree)); + + gtk_table_attach(GTK_TABLE(frame_table), scrolled_win, 0, 1, 1, 3, GTK_FILL, GTK_FILL, 0, 0); + } + { // available tag list + GtkWidget* scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + + g_TextureBrowser.m_available_store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING); + GtkTreeSortable* sortable = GTK_TREE_SORTABLE(g_TextureBrowser.m_available_store); + gtk_tree_sortable_set_sort_column_id(sortable, TAG_COLUMN, GTK_SORT_ASCENDING); + + GtkCellRenderer* renderer = gtk_cell_renderer_text_new (); + + g_TextureBrowser.m_available_tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (g_TextureBrowser.m_available_store)); + g_object_unref (G_OBJECT (g_TextureBrowser.m_available_store)); + g_signal_connect(g_TextureBrowser.m_available_tree, "row-activated", (GCallback) TextureBrowser_assignTags, NULL); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (g_TextureBrowser.m_available_tree), FALSE); + + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(g_TextureBrowser.m_available_tree)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); + + GtkTreeViewColumn* column = gtk_tree_view_column_new_with_attributes ("", renderer, "text", TAG_COLUMN, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (g_TextureBrowser.m_available_tree), column); + gtk_widget_show (g_TextureBrowser.m_available_tree); + + gtk_widget_show (scrolled_win); + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_win), GTK_WIDGET (g_TextureBrowser.m_available_tree)); + + gtk_table_attach (GTK_TABLE (frame_table), scrolled_win, 2, 3, 1, 3, GTK_FILL, GTK_FILL, 0, 0); + } + { // tag arrow buttons + GtkWidget* m_btn_left = gtk_button_new(); + GtkWidget* m_btn_right = gtk_button_new(); + GtkWidget* m_arrow_left = gtk_arrow_new(GTK_ARROW_LEFT, GTK_SHADOW_OUT); + GtkWidget* m_arrow_right = gtk_arrow_new(GTK_ARROW_RIGHT, GTK_SHADOW_OUT); + gtk_container_add(GTK_CONTAINER(m_btn_left), m_arrow_left); + gtk_container_add(GTK_CONTAINER(m_btn_right), m_arrow_right); + + // workaround. the size of the tag frame depends of the requested size of the arrow buttons. + gtk_widget_set_size_request(m_arrow_left, -1, 68); + gtk_widget_set_size_request(m_arrow_right, -1, 68); + + gtk_table_attach(GTK_TABLE(frame_table), m_btn_left, 1, 2, 1, 2, GTK_SHRINK, GTK_EXPAND, 0, 0); + gtk_table_attach(GTK_TABLE(frame_table), m_btn_right, 1, 2, 2, 3, GTK_SHRINK, GTK_EXPAND, 0, 0); + + g_signal_connect(G_OBJECT (m_btn_left), "clicked", G_CALLBACK(TextureBrowser_assignTags), NULL); + g_signal_connect(G_OBJECT (m_btn_right), "clicked", G_CALLBACK(TextureBrowser_removeTags), NULL); + + gtk_widget_show(m_btn_left); + gtk_widget_show(m_btn_right); + gtk_widget_show(m_arrow_left); + gtk_widget_show(m_arrow_right); + } + { // tag fram labels + GtkWidget* m_lbl_assigned = gtk_label_new ("Assigned"); + GtkWidget* m_lbl_unassigned = gtk_label_new ("Available"); + + gtk_table_attach (GTK_TABLE (frame_table), m_lbl_assigned, 0, 1, 0, 1, GTK_EXPAND, GTK_SHRINK, 0, 0); + gtk_table_attach (GTK_TABLE (frame_table), m_lbl_unassigned, 2, 3, 0, 1, GTK_EXPAND, GTK_SHRINK, 0, 0); + + gtk_widget_show (m_lbl_assigned); + gtk_widget_show (m_lbl_unassigned); + } + } else { // no tag support, show the texture tree only + gtk_box_pack_start(GTK_BOX(vbox), g_TextureBrowser.m_scr_win_tree, TRUE, TRUE, 0); + } + + // TODO do we need this? + //gtk_container_set_focus_chain(GTK_CONTAINER(hbox_table), NULL); + + return table; +} + +void TextureBrowser_destroyWindow() +{ + GlobalShaderSystem().setActiveShadersChangedNotify(Callback()); + + g_signal_handler_disconnect(G_OBJECT(g_TextureBrowser.m_gl_widget), g_TextureBrowser.m_sizeHandler); + g_signal_handler_disconnect(G_OBJECT(g_TextureBrowser.m_gl_widget), g_TextureBrowser.m_exposeHandler); + + gtk_widget_unref(g_TextureBrowser.m_gl_widget); +} + +const Vector3& TextureBrowser_getBackgroundColour(TextureBrowser& textureBrowser) +{ + return textureBrowser.color_textureback; +} + +void TextureBrowser_setBackgroundColour(TextureBrowser& textureBrowser, const Vector3& colour) +{ + textureBrowser.color_textureback = colour; + TextureBrowser_queueDraw(textureBrowser); +} + +void TextureBrowser_selectionHelper(GtkTreeModel* model, GtkTreePath* path, GtkTreeIter* iter, GSList** selected) +{ + g_assert(selected != NULL); + + gchar* name; + gtk_tree_model_get(model, iter, TAG_COLUMN, &name, -1); + *selected = g_slist_append(*selected, name); +} + +void TextureBrowser_shaderInfo() +{ + const char* name = TextureBrowser_GetSelectedShader(g_TextureBrowser); + IShader* shader = QERApp_Shader_ForName(name); + + DoShaderInfoDlg(name, shader->getShaderFileName(), "Shader Info"); + + shader->DecRef(); +} + +void TextureBrowser_addTag() +{ + CopiedString tag; + + EMessageBoxReturn result = DoShaderTagDlg(&tag, "Add shader tag"); + + if (result == eIDOK && !tag.empty()) + { + GtkTreeIter iter, iter2; + g_TextureBrowser.m_all_tags.insert(tag.c_str()); + gtk_list_store_append(g_TextureBrowser.m_available_store, &iter); + gtk_list_store_set(g_TextureBrowser.m_available_store, &iter, TAG_COLUMN, tag.c_str(), -1); + + // Select the currently added tag in the available list + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(g_TextureBrowser.m_available_tree)); + gtk_tree_selection_select_iter(selection, &iter); + + gtk_list_store_append(g_TextureBrowser.m_all_tags_list, &iter2); + gtk_list_store_set(g_TextureBrowser.m_all_tags_list, &iter2, TAG_COLUMN, tag.c_str(), -1); + } +} + +void TextureBrowser_renameTag() +{ + /* WORKAROUND: The tag treeview is set to GTK_SELECTION_MULTIPLE. Because + gtk_tree_selection_get_selected() doesn't work with GTK_SELECTION_MULTIPLE, + we need to count the number of selected rows first and use + gtk_tree_selection_selected_foreach() then to go through the list of selected + rows (which always containins a single row). + */ + + GSList* selected = NULL; + + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(g_TextureBrowser.m_treeViewTags)); + gtk_tree_selection_selected_foreach(selection, GtkTreeSelectionForeachFunc(TextureBrowser_selectionHelper), &selected); + + if(g_slist_length(selected) == 1) // we only rename a single tag + { + CopiedString newTag; + EMessageBoxReturn result = DoShaderTagDlg(&newTag, "Rename shader tag"); + + if (result == eIDOK && !newTag.empty()) + { + GtkTreeIter iterList; + gchar* rowTag; + gchar* oldTag = (char*)selected->data; + + bool row = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(g_TextureBrowser.m_all_tags_list), &iterList) != 0; + + while(row) + { + gtk_tree_model_get(GTK_TREE_MODEL(g_TextureBrowser.m_all_tags_list), &iterList, TAG_COLUMN, &rowTag, -1); + + if(strcmp(rowTag, oldTag) == 0) + { + gtk_list_store_set(g_TextureBrowser.m_all_tags_list, &iterList, TAG_COLUMN, newTag.c_str(), -1); + } + row = gtk_tree_model_iter_next(GTK_TREE_MODEL(g_TextureBrowser.m_all_tags_list), &iterList) != 0; + } + + TagBuilder.RenameShaderTag(oldTag, newTag.c_str()); + + g_TextureBrowser.m_all_tags.erase((CopiedString)oldTag); + g_TextureBrowser.m_all_tags.insert(newTag); + + BuildStoreAssignedTags(g_TextureBrowser.m_assigned_store, g_TextureBrowser.shader.c_str(), &g_TextureBrowser); + BuildStoreAvailableTags(g_TextureBrowser.m_available_store, g_TextureBrowser.m_assigned_store, g_TextureBrowser.m_all_tags, &g_TextureBrowser); + } + } + else + { + gtk_MessageBox(GTK_WIDGET(g_TextureBrowser.m_parent), "Select a single tag for renaming."); + } +} + +void TextureBrowser_deleteTag() +{ + GSList* selected = NULL; + + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(g_TextureBrowser.m_treeViewTags)); + gtk_tree_selection_selected_foreach(selection, GtkTreeSelectionForeachFunc(TextureBrowser_selectionHelper), &selected); + + if(g_slist_length(selected) == 1) // we only delete a single tag + { + EMessageBoxReturn result = gtk_MessageBox(GTK_WIDGET(g_TextureBrowser.m_parent), "Are you sure you want to delete the selected tag?", "Delete Tag", eMB_YESNO, eMB_ICONQUESTION); + + if(result == eIDYES) + { + GtkTreeIter iterSelected; + gchar *rowTag; + + gchar* tagSelected = (char*)selected->data; + + bool row = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(g_TextureBrowser.m_all_tags_list), &iterSelected) != 0; + + while(row) + { + gtk_tree_model_get(GTK_TREE_MODEL(g_TextureBrowser.m_all_tags_list), &iterSelected, TAG_COLUMN, &rowTag, -1); + + if(strcmp(rowTag, tagSelected) == 0) + { + gtk_list_store_remove(g_TextureBrowser.m_all_tags_list, &iterSelected); + break; + } + row = gtk_tree_model_iter_next(GTK_TREE_MODEL(g_TextureBrowser.m_all_tags_list), &iterSelected) != 0; + } + + TagBuilder.DeleteTag(tagSelected); + g_TextureBrowser.m_all_tags.erase((CopiedString)tagSelected); + + BuildStoreAssignedTags(g_TextureBrowser.m_assigned_store, g_TextureBrowser.shader.c_str(), &g_TextureBrowser); + BuildStoreAvailableTags(g_TextureBrowser.m_available_store, g_TextureBrowser.m_assigned_store, g_TextureBrowser.m_all_tags, &g_TextureBrowser); + } + } else { + gtk_MessageBox(GTK_WIDGET(g_TextureBrowser.m_parent), "Select a single tag for deletion."); + } +} + +void TextureBrowser_copyTag() +{ + g_TextureBrowser.m_copied_tags.clear(); + TagBuilder.GetShaderTags(g_TextureBrowser.shader.c_str(), g_TextureBrowser.m_copied_tags); +} + +void TextureBrowser_pasteTag() +{ + IShader* ishader = QERApp_Shader_ForName(g_TextureBrowser.shader.c_str()); + CopiedString shader = g_TextureBrowser.shader.c_str(); + + if(!TagBuilder.CheckShaderTag(shader.c_str())) + { + CopiedString shaderFile = ishader->getShaderFileName(); + if(shaderFile.empty()) + { + // it's a texture + TagBuilder.AddShaderNode(shader.c_str(), CUSTOM, TEXTURE); + } + else + { + // it's a shader + TagBuilder.AddShaderNode(shader.c_str(), CUSTOM, SHADER); + } + + for(size_t i = 0; i < g_TextureBrowser.m_copied_tags.size(); ++i) + { + TagBuilder.AddShaderTag(shader.c_str(), g_TextureBrowser.m_copied_tags[i].c_str(), TAG); + } + } + else + { + for(size_t i = 0; i < g_TextureBrowser.m_copied_tags.size(); ++i) + { + if(!TagBuilder.CheckShaderTag(shader.c_str(), g_TextureBrowser.m_copied_tags[i].c_str())) + { + // the tag doesn't exist - let's add it + TagBuilder.AddShaderTag(shader.c_str(), g_TextureBrowser.m_copied_tags[i].c_str(), TAG); + } + } + } + + ishader->DecRef(); + + TagBuilder.SaveXmlDoc(); + BuildStoreAssignedTags(g_TextureBrowser.m_assigned_store, shader.c_str(), &g_TextureBrowser); + BuildStoreAvailableTags (g_TextureBrowser.m_available_store, g_TextureBrowser.m_assigned_store, g_TextureBrowser.m_all_tags, &g_TextureBrowser); +} + +void RefreshShaders() +{ + ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Shaders"); + GlobalShaderSystem().refresh(); + UpdateAllWindows(); +} + +void TextureBrowser_ToggleShowShaders() +{ + g_TextureBrowser.m_showShaders ^= 1; + g_TextureBrowser.m_showshaders_item.update(); + TextureBrowser_queueDraw(g_TextureBrowser); +} + +void TextureBrowser_ToggleShowShaderListOnly() +{ + g_TextureBrowser_shaderlistOnly ^= 1; + g_TextureBrowser.m_showshaderlistonly_item.update(); + + TextureBrowser_constructTreeStore(); +} + +void TextureBrowser_showAll() +{ + g_TextureBrowser_currentDirectory = ""; + g_TextureBrowser.m_searchedTags = false; + TextureBrowser_heightChanged(g_TextureBrowser); + TextureBrowser_updateTitle(); +} + +void TextureBrowser_showUntagged() +{ + EMessageBoxReturn result = gtk_MessageBox(GTK_WIDGET(g_TextureBrowser.m_parent), "WARNING! This function might need a lot of memory and time. Are you sure you want to use it?", "Show Untagged", eMB_YESNO, eMB_ICONWARNING); + + if(result == eIDYES) + { + g_TextureBrowser.m_found_shaders.clear(); + TagBuilder.GetUntagged(g_TextureBrowser.m_found_shaders); + std::set::iterator iter; + + ScopeDisableScreenUpdates disableScreenUpdates("Searching untagged textures...", "Loading Textures"); + + for(iter = g_TextureBrowser.m_found_shaders.begin(); iter != g_TextureBrowser.m_found_shaders.end(); iter++) + { + std::string path = (*iter).c_str(); + size_t pos = path.find_last_of("/", path.size()); + std::string name = path.substr(pos + 1, path.size()); + path = path.substr(0, pos + 1); + TextureDirectory_loadTexture(path.c_str(), name.c_str()); + globalErrorStream() << path.c_str() << name.c_str() << "\n"; + } + + g_TextureBrowser_currentDirectory = "Untagged"; + TextureBrowser_queueDraw(GlobalTextureBrowser()); + TextureBrowser_heightChanged(g_TextureBrowser); + TextureBrowser_updateTitle(); + } +} + +void TextureBrowser_FixedSize() +{ + g_TextureBrowser_fixedSize ^= 1; + GlobalTextureBrowser().m_fixedsize_item.update(); + TextureBrowser_activeShadersChanged(GlobalTextureBrowser()); +} + +void TextureBrowser_FilterNotex() +{ + g_TextureBrowser_filterNotex ^= 1; + GlobalTextureBrowser().m_filternotex_item.update(); + TextureBrowser_activeShadersChanged(GlobalTextureBrowser()); +} + +void TextureBrowser_exportTitle(const StringImportCallback& importer) +{ + StringOutputStream buffer(64); + buffer << "Textures: "; + if(!string_empty(g_TextureBrowser_currentDirectory.c_str())) + { + buffer << g_TextureBrowser_currentDirectory.c_str(); + } + else + { + buffer << "all"; + } + importer(buffer.c_str()); +} + + +void TextureScaleImport(TextureBrowser& textureBrowser, int value) +{ + switch(value) + { + case 0: + TextureBrowser_setScale(textureBrowser, 10); + break; + case 1: + TextureBrowser_setScale(textureBrowser, 25); + break; + case 2: + TextureBrowser_setScale(textureBrowser, 50); + break; + case 3: + TextureBrowser_setScale(textureBrowser, 100); + break; + case 4: + TextureBrowser_setScale(textureBrowser, 200); + break; + } +} +typedef ReferenceCaller1 TextureScaleImportCaller; + +void TextureScaleExport(TextureBrowser& textureBrowser, const IntImportCallback& importer) +{ + switch(textureBrowser.m_textureScale) + { + case 10: + importer(0); + break; + case 25: + importer(1); + break; + case 50: + importer(2); + break; + case 100: + importer(3); + break; + case 200: + importer(4); + break; + } +} +typedef ReferenceCaller1 TextureScaleExportCaller; + +void TextureBrowser_constructPreferences(PreferencesPage& page) +{ + page.appendCheckBox( + "", "Texture scrollbar", + TextureBrowserImportShowScrollbarCaller(GlobalTextureBrowser()), + BoolExportCaller(GlobalTextureBrowser().m_showTextureScrollbar) + ); + { + const char* texture_scale[] = { "10%", "25%", "50%", "100%", "200%" }; + page.appendCombo( + "Texture Thumbnail Scale", + STRING_ARRAY_RANGE(texture_scale), + IntImportCallback(TextureScaleImportCaller(GlobalTextureBrowser())), + IntExportCallback(TextureScaleExportCaller(GlobalTextureBrowser())) + ); + } + page.appendEntry("Mousewheel Increment", GlobalTextureBrowser().m_mouseWheelScrollIncrement); + { + const char* startup_shaders[] = { "None", TextureBrowser_getComonShadersName() }; + page.appendCombo("Load Shaders at Startup", reinterpret_cast(GlobalTextureBrowser().m_startupShaders), STRING_ARRAY_RANGE(startup_shaders)); + } +} +void TextureBrowser_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Texture Browser", "Texture Browser Preferences")); + TextureBrowser_constructPreferences(page); +} +void TextureBrowser_registerPreferencesPage() +{ + PreferencesDialog_addSettingsPage(FreeCaller1()); +} + + +#include "preferencesystem.h" +#include "stringio.h" + +typedef ReferenceCaller1 TextureBrowserSetScaleCaller; + + + +void TextureClipboard_textureSelected(const char* shader); + +void TextureBrowser_Construct() +{ + GlobalCommands_insert("ShaderInfo", FreeCaller()); + GlobalCommands_insert("ShowUntagged", FreeCaller()); + GlobalCommands_insert("AddTag", FreeCaller()); + GlobalCommands_insert("RenameTag", FreeCaller()); + GlobalCommands_insert("DeleteTag", FreeCaller()); + GlobalCommands_insert("CopyTag", FreeCaller()); + GlobalCommands_insert("PasteTag", FreeCaller()); + GlobalCommands_insert("RefreshShaders", FreeCaller()); + GlobalToggles_insert("ShowInUse", FreeCaller(), ToggleItem::AddCallbackCaller(g_TextureBrowser.m_hideunused_item), Accelerator('U')); + GlobalCommands_insert("ShowAllTextures", FreeCaller(), Accelerator('A', (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("ToggleTextures", FreeCaller(), Accelerator('T')); + GlobalToggles_insert("ToggleShowShaders", FreeCaller(), ToggleItem::AddCallbackCaller(g_TextureBrowser.m_showshaders_item)); + GlobalToggles_insert("ToggleShowShaderlistOnly", FreeCaller(), ToggleItem::AddCallbackCaller(g_TextureBrowser.m_showshaderlistonly_item)); + GlobalToggles_insert("FixedSize", FreeCaller(), ToggleItem::AddCallbackCaller(g_TextureBrowser.m_fixedsize_item)); + GlobalToggles_insert("FilterNotex", FreeCaller(), ToggleItem::AddCallbackCaller(g_TextureBrowser.m_filternotex_item)); + + GlobalPreferenceSystem().registerPreference("TextureScale", + makeSizeStringImportCallback(TextureBrowserSetScaleCaller(g_TextureBrowser)), + SizeExportStringCaller(g_TextureBrowser.m_textureScale) + ); + GlobalPreferenceSystem().registerPreference("TextureScrollbar", + makeBoolStringImportCallback(TextureBrowserImportShowScrollbarCaller(g_TextureBrowser)), + BoolExportStringCaller(GlobalTextureBrowser().m_showTextureScrollbar) + ); + GlobalPreferenceSystem().registerPreference("ShowShaders", BoolImportStringCaller(GlobalTextureBrowser().m_showShaders), BoolExportStringCaller(GlobalTextureBrowser().m_showShaders)); + GlobalPreferenceSystem().registerPreference("ShowShaderlistOnly", BoolImportStringCaller(g_TextureBrowser_shaderlistOnly), BoolExportStringCaller(g_TextureBrowser_shaderlistOnly)); + GlobalPreferenceSystem().registerPreference("FixedSize", BoolImportStringCaller(g_TextureBrowser_fixedSize), BoolExportStringCaller(g_TextureBrowser_fixedSize)); + GlobalPreferenceSystem().registerPreference("FilterNotex", BoolImportStringCaller(g_TextureBrowser_filterNotex), BoolExportStringCaller(g_TextureBrowser_filterNotex)); + GlobalPreferenceSystem().registerPreference("LoadShaders", IntImportStringCaller(reinterpret_cast(GlobalTextureBrowser().m_startupShaders)), IntExportStringCaller(reinterpret_cast(GlobalTextureBrowser().m_startupShaders))); + GlobalPreferenceSystem().registerPreference("WheelMouseInc", SizeImportStringCaller(GlobalTextureBrowser().m_mouseWheelScrollIncrement), SizeExportStringCaller(GlobalTextureBrowser().m_mouseWheelScrollIncrement)); + GlobalPreferenceSystem().registerPreference("SI_Colors0", Vector3ImportStringCaller(GlobalTextureBrowser().color_textureback), Vector3ExportStringCaller(GlobalTextureBrowser().color_textureback)); + + g_TextureBrowser.shader = texdef_name_default(); + + Textures_setModeChangedNotify(ReferenceCaller(g_TextureBrowser)); + + TextureBrowser_registerPreferencesPage(); + + GlobalShaderSystem().attach(g_ShadersObserver); + + TextureBrowser_textureSelected = TextureClipboard_textureSelected; +} +void TextureBrowser_Destroy() +{ + GlobalShaderSystem().detach(g_ShadersObserver); + + Textures_setModeChangedNotify(Callback()); +} diff --git a/radiant/texwindow.h b/radiant/texwindow.h new file mode 100644 index 00000000..c5dbf33c --- /dev/null +++ b/radiant/texwindow.h @@ -0,0 +1,62 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_TEXWINDOW_H) +#define INCLUDED_TEXWINDOW_H + +#include "math/vector.h" +#include "generic/callbackfwd.h" +#include "signal/signalfwd.h" +#include "xml/xmltextags.h" + +typedef struct _GtkWidget GtkWidget; + +class TextureBrowser; +TextureBrowser& GlobalTextureBrowser(); + +typedef struct _GtkWindow GtkWindow; +GtkWidget* TextureBrowser_constructWindow(GtkWindow* toplevel); +void TextureBrowser_destroyWindow(); + + +void TextureBrowser_ShowDirectory(TextureBrowser& textureBrowser, const char* name); +void TextureBrowser_ShowStartupShaders(TextureBrowser& textureBrowser); + +const char* TextureBrowser_GetSelectedShader(TextureBrowser& textureBrower); + +void TextureBrowser_Construct(); +void TextureBrowser_Destroy(); + +typedef Callback1 StringImportCallback; +template +class FreeCaller1; + +extern GtkWidget* g_page_textures; +void TextureBrowser_exportTitle(const StringImportCallback& importer); +typedef FreeCaller1 TextureBrowserExportTitleCaller; + +const Vector3& TextureBrowser_getBackgroundColour(TextureBrowser& textureBrowser); +void TextureBrowser_setBackgroundColour(TextureBrowser& textureBrowser, const Vector3& colour); + +void TextureBrowser_addActiveShadersChangedCallback(const SignalHandler& handler); +void TextureBrowser_addShadersRealiseCallback(const SignalHandler& handler); + +#endif diff --git a/radiant/timer.cpp b/radiant/timer.cpp new file mode 100644 index 00000000..617112e1 --- /dev/null +++ b/radiant/timer.cpp @@ -0,0 +1,109 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "timer.h" + + +#if defined(WIN32) + +#include + +MillisecondTime MillisecondTime::current() +{ + static class Cached + { + LONGLONG m_frequency; + LONGLONG m_base; + public: + Cached() + { + QueryPerformanceFrequency((LARGE_INTEGER *) &m_frequency); + QueryPerformanceCounter((LARGE_INTEGER *) &m_base); + } + LONGLONG frequency() + { + return m_frequency; + } + LONGLONG base() + { + return m_base; + } + } cached; + + if(cached.frequency() > 0) + { + LONGLONG count; + QueryPerformanceCounter((LARGE_INTEGER *) &count); + return time_from_ticks(count - cached.base(), cached.frequency()); + } + else + { +#if 1 + return MillisecondTime(); +#else + return time_from_ticks(timeGetTime(), 1000); +#endif + } +} + + + + +#elif defined(POSIX) + +#include +#include "sys/time.h" + +MillisecondTime MillisecondTime::current() +{ + static class Cached + { + time_t m_base; + public: + Cached() + { + time(&m_base); + } + time_t base() + { + return m_base; + } + } cached; + + timeval time; + gettimeofday(&time, 0); + return MillisecondTime((time.tv_sec - cached.base()) * 1000 + time.tv_usec / 1000); +} + + + +#else + +#include + +MillisecondTime MillisecondTime::current() +{ + return time_from_ticks(std::clock(), CLOCKS_PER_SEC); +} + + + +#endif diff --git a/radiant/timer.h b/radiant/timer.h new file mode 100644 index 00000000..a8945200 --- /dev/null +++ b/radiant/timer.h @@ -0,0 +1,103 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined (INCLUDED_TIMER_H) +#define INCLUDED_TIMER_H + +#if 1 + +const int msec_per_sec = 1000; + +class MillisecondTime +{ + unsigned int m_milliseconds; +public: + MillisecondTime(unsigned int milliseconds) + : m_milliseconds(milliseconds) + { + } + MillisecondTime() + { + } + static MillisecondTime current(); + + unsigned int milliseconds_since(const MillisecondTime& other) const + { + return m_milliseconds - other.m_milliseconds; + } +}; + +template +inline MillisecondTime time_from_ticks(tick_type tick_count, tick_type ticks_per_sec) +{ + return MillisecondTime(static_cast(tick_count / static_cast(ticks_per_sec / msec_per_sec))); +} + +#else + +const unsigned int usec_per_sec = 1000000; + +class MillisecondTime +{ + unsigned int m_sec; + unsigned int m_usec; +public: + MillisecondTime(unsigned int sec, unsigned int usec) + : m_sec(sec), m_usec(usec) + { + } + MillisecondTime() + { + } + staticMillisecondTime current(); + + unsigned int milliseconds_since(const MillisecondTime& other) const + { + return static_cast((m_sec * static_cast(usec_per_sec) + m_usec) + - (other.m_sec * static_cast(usec_per_sec) + other.m_usec)) / 1000; + } +}; + +template +inline MillisecondTime time_from_ticks(tick_type tick_count, tick_type ticks_per_sec) +{ + return MillisecondTime(static_cast(tick_count / ticks_per_sec), + static_cast((tick_count % ticks_per_sec) * (usec_per_sec / static_cast(ticks_per_sec)))); +} + +#endif + +class Timer +{ + MillisecondTime m_start; + +public: + void start() + { + m_start = MillisecondTime::current(); + } + unsigned int elapsed_msec() + { + return MillisecondTime::current().milliseconds_since(m_start); + } +}; + +#endif diff --git a/radiant/treemodel.cpp b/radiant/treemodel.cpp new file mode 100644 index 00000000..cd13ecff --- /dev/null +++ b/radiant/treemodel.cpp @@ -0,0 +1,1551 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "treemodel.h" + +#include "debugging/debugging.h" + +#include +#include +#include +#include + +#include "iscenegraph.h" +#include "nameable.h" + +#include "generic/callback.h" +#include "scenelib.h" +#include "string/string.h" +#include "generic/reference.h" + +inline Nameable* Node_getNameable(scene::Node& node) +{ + return NodeTypeCast::cast(node); +} + +#if 0 + +#include "gtkutil/gtktreestore.h" + +template +inline void gtk_tree_model_get_pointer(GtkTreeModel* model, GtkTreeIter* iter, gint column, value_type** pointer) +{ + GValue value = GValue_default(); + gtk_tree_model_get_value(model, iter, column, &value); + *pointer = (value_type*)g_value_get_pointer(&value); +} + + +typedef GtkTreeStore GraphTreeModel; + +GtkTreeStore* graph_tree_model_new(graph_type* graph) +{ + return gtk_tree_store_new(2, G_TYPE_POINTER, G_TYPE_POINTER); +} + +void graph_tree_model_delete(GraphTreeModel* model) +{ + g_object_unref(G_OBJECT(model)); +} + + +bool graph_tree_model_subtree_find_node(GraphTreeModel* model, GtkTreeIter* parent, const scene::Node& node, GtkTreeIter* iter) +{ + for(gboolean success = gtk_tree_model_iter_children(GTK_TREE_MODEL(model), iter, parent); + success != FALSE; + success = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), iter)) + { + scene::Node* current; + gtk_tree_model_get_pointer(GTK_TREE_MODEL(model), iter, 0, ¤t); + if(current == node) + { + return true; + } + } + return false; +} + +typedef GtkTreeIter DoubleGtkTreeIter[2]; + +bool graph_tree_model_find_top(GraphTreeModel* model, const scene::Path& path, GtkTreeIter& iter) +{ + int swap = 0; + GtkTreeIter* parent_pointer = NULL; + GtkTreeIter parent; + for(scene::Path::const_iterator i = path.begin(); i != path.end(); ++i) + { + if(!graph_tree_model_subtree_find_node(model, parent_pointer, *i, &iter)) + { + return false; + } + parent = iter; + parent_pointer = &parent; + } + return true; +} + +bool graph_tree_model_find_parent(GraphTreeModel* model, const scene::Path& path, GtkTreeIter& iter) +{ + int swap = 0; + GtkTreeIter* parent_pointer = NULL; + ASSERT_MESSAGE(path.size() > 1, "path too short"); + for(scene::Path::const_iterator i = path.begin(); i != path.end()-1; ++i) + { + GtkTreeIter child; + if(!graph_tree_model_subtree_find_node(model, parent_pointer, *i, &child)) + { + return false; + } + iter = child; + parent_pointer = &iter; + } + return true; +} + +void node_attach_name_changed_callback(scene::Node& node, const Callback& callback) +{ + if(node != 0) + { + Nameable* nameable = Node_getNameable(node); + if(nameable != 0) + { + nameable->attach(callback); + } + } +} +void node_detach_name_changed_callback(scene::Node& node, const Callback& callback) +{ + if(node != 0) + { + Nameable* nameable = Node_getNameable(node); + if(nameable != 0) + { + nameable->detach(callback); + } + } +} + +GraphTreeModel* scene_graph_get_tree_model(); // temp hack + +void graph_tree_model_row_changed(const scene::Instance& instance) +{ + GraphTreeModel* model = scene_graph_get_tree_model(); + + GtkTreeIter child; + ASSERT_MESSAGE(graph_tree_model_find_top(model, instance.path(), child), "RUNTIME ERROR"); + + gtk_tree_store_set(GTK_TREE_STORE(model), &child, 0, instance.path().top(), -1); +} + +void graph_tree_model_row_inserted(GraphTreeModel* model, const scene::Instance& instance) +{ + GtkTreeIter parent; + GtkTreeIter* parent_pointer = NULL; + if(instance.path().size() != 1) + { + ASSERT_MESSAGE(graph_tree_model_find_parent(model, instance.path(), parent), "RUNTIME ERROR"); + parent_pointer = &parent; + } + + gpointer node = instance.path().top(); + gconstpointer selectable = Instance_getSelectable(instance); + + GtkTreeIter child; + gtk_tree_store_append(GTK_TREE_STORE(model), &child, parent_pointer); + gtk_tree_store_set(GTK_TREE_STORE(model), &child, 0, node, 1, selectable, -1); + + node_attach_name_changed_callback(instance.path().top(), ConstReferenceCaller(instance)); +} + +void graph_tree_model_row_deleted(GraphTreeModel* model, const scene::Instance& instance) +{ + GtkTreeIter child; + ASSERT_MESSAGE(graph_tree_model_find_top(model, instance.path(), child), "RUNTIME ERROR"); + + node_detach_name_changed_callback(instance.path().top(), ConstReferenceCaller(instance)); + + gtk_tree_store_remove(GTK_TREE_STORE(model), &child); +} + +#elif 0 + +const char* node_get_name(scene::Node& node); + +typedef scene::Node* NodePointer; + +class NodeNameLess +{ +public: + bool operator()(const NodePointer& self, const NodePointer& other) const + { + if(self == 0) + { + return true; + } + if(other == 0) + { + return false; + } + int result = string_compare(node_get_name(self), node_get_name(other)); + if(result == 0) + { + return self < other; + } + return result < 0; + } +}; + +class PathNameLess +{ +public: + bool operator()(const PathConstReference& self, const PathConstReference& other) const + { + return std::lexicographical_compare(self.get().begin(), self.get().end(), other.get().begin(), other.get().end(), NodeNameLess()); + } +}; + +typedef std::map graph_type; + +struct GraphTreeModel +{ + GObject parent; + + graph_type* graph; +}; + +struct GraphTreeModelClass +{ + GObjectClass parent_class; +}; + +#define GRAPH_TREE_MODEL(p) (reinterpret_cast(p)) + +static GtkTreeModelFlags graph_tree_model_get_flags (GtkTreeModel* tree_model) +{ + return GTK_TREE_MODEL_ITERS_PERSIST; +} + +static gint graph_tree_model_get_n_columns (GtkTreeModel* tree_model) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + GraphTreeModel* graph_tree_model = (GraphTreeModel*) tree_model; + + return 2; +} + +static const gint c_stamp = 0xabcdef; + +inline graph_type::iterator graph_iterator_read_tree_iter(GtkTreeIter* iter) +{ + ASSERT_MESSAGE(iter != 0, "tree model error"); + ASSERT_MESSAGE(iter->user_data != 0, "tree model error"); + ASSERT_MESSAGE(iter->stamp == c_stamp, "tree model error"); + return *reinterpret_cast(&iter->user_data); +} + +inline void graph_iterator_write_tree_iter(graph_type::iterator i, GtkTreeIter* iter) +{ + ASSERT_MESSAGE(iter != 0, "tree model error"); + iter->stamp = c_stamp; + *reinterpret_cast(&iter->user_data) = i; + ASSERT_MESSAGE(iter->user_data != 0, "tree model error"); +} + +static GType graph_tree_model_get_column_type (GtkTreeModel *tree_model, gint index) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + GraphTreeModel *graph_tree_model = (GraphTreeModel *) tree_model; + + return G_TYPE_POINTER; +} + +static gboolean graph_tree_model_get_iter(GtkTreeModel* tree_model, GtkTreeIter* iter, GtkTreePath* path) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + gint* indices = gtk_tree_path_get_indices (path); + gint depth = gtk_tree_path_get_depth (path); + + g_return_val_if_fail (depth > 0, FALSE); + + graph_type& graph = *GRAPH_TREE_MODEL(tree_model)->graph; + + if(graph.empty()) + return FALSE; + + GtkTreeIter tmp; + GtkTreeIter* parent = 0; + + for(gint i = 0; i < depth; i++) + { + if (! gtk_tree_model_iter_nth_child (tree_model, iter, parent, indices[i])) + return FALSE; + tmp = *iter; + parent = &tmp; + } + + return TRUE; +} + +static GtkTreePath* graph_tree_model_get_path(GtkTreeModel* tree_model, GtkTreeIter* iter) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + graph_type& graph = *GRAPH_TREE_MODEL(tree_model)->graph; + graph_type::iterator i = graph_iterator_read_tree_iter(iter); + + GtkTreePath* path = gtk_tree_path_new(); + + for(std::size_t depth = (*i).first.get().size(); depth != 0; --depth) + { + std::size_t index = 0; + + while(i != graph.begin() && (*i).first.get().size() >= depth) + { + --i; + if((*i).first.get().size() == depth) + ++index; + } + + gtk_tree_path_prepend_index(path, index); + } + + return path; +} + + +static void graph_tree_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, gint column, GValue *value) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + ASSERT_MESSAGE(column == 0 || column == 1, "tree model error"); + + graph_type::iterator i = graph_iterator_read_tree_iter(iter); + + g_value_init (value, G_TYPE_POINTER); + + if(column == 0) + g_value_set_pointer(value, reinterpret_cast((*i).first.get().top())); + else + g_value_set_pointer(value, reinterpret_cast(Instance_getSelectable(*(*i).second))); +} + +static gboolean graph_tree_model_iter_next (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + graph_type& graph = *GRAPH_TREE_MODEL(tree_model)->graph; + graph_type::iterator i = graph_iterator_read_tree_iter(iter); + std::size_t depth = (*i).first.get().size(); + + ++i; + + while(i != graph.end() && (*i).first.get().size() > depth) + { + ++i; + } + + if(i == graph.end() || (*i).first.get().size() != depth) + { + return FALSE; + } + + graph_iterator_write_tree_iter(i, iter); + + return TRUE; +} + +static gboolean graph_tree_model_iter_children (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + graph_type& graph = *GRAPH_TREE_MODEL(tree_model)->graph; + graph_type::iterator i = (parent == 0) ? graph.begin() : graph_iterator_read_tree_iter(parent); + std::size_t depth = (parent == 0) ? 1 : (*i).first.get().size() + 1; + + if(parent != 0) + ++i; + + if(i != graph.end() && (*i).first.get().size() == depth) + { + graph_iterator_write_tree_iter(i, iter); + return TRUE; + } + + return FALSE; +} + +static gboolean graph_tree_model_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + graph_type& graph = *GRAPH_TREE_MODEL(tree_model)->graph; + graph_type::iterator i = graph_iterator_read_tree_iter(iter); + std::size_t depth = (*i).first.get().size() + 1; + + return ++i != graph.end() && (*i).first.get().size() == depth; +} + +static gint graph_tree_model_iter_n_children (GtkTreeModel *tree_model, GtkTreeIter *parent) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + graph_type& graph = *GRAPH_TREE_MODEL(tree_model)->graph; + graph_type::iterator i = (parent == 0) ? graph.begin() : graph_iterator_read_tree_iter(parent); + std::size_t depth = (parent == 0) ? 1 : (*i).first.get().size() + 1; + + if(parent != 0) + ++i; + + gint count = 0; + while(i != graph.end() && (*i).first.get().size() >= depth) + { + ++count; + ++i; + } + + return count; +} + +static gboolean graph_tree_model_iter_nth_child (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, gint n) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + graph_type& graph = *GRAPH_TREE_MODEL(tree_model)->graph; + graph_type::iterator i = (parent == 0) ? graph.begin() : graph_iterator_read_tree_iter(parent); + std::size_t depth = (parent == 0) ? 1 : (*i).first.get().size() + 1; + + if(parent != 0) + ++i; + + while(i != graph.end() && (*i).first.get().size() >= depth) + { + if((*i).first.get().size() == depth && n-- == 0) + { + graph_iterator_write_tree_iter(i, iter); + return TRUE; + } + ++i; + } + + return FALSE; +} + +static gboolean graph_tree_model_iter_parent(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + graph_type& graph = *GRAPH_TREE_MODEL(tree_model)->graph; + graph_type::iterator i = graph_iterator_read_tree_iter(child); + std::size_t depth = (*i).first.get().size(); + if(depth == 1) + { + return FALSE; + } + else + { + do + { + --i; + } + while((*i).first.get().size() >= depth); + graph_iterator_write_tree_iter(i, iter); + return TRUE; + } +} + +static GObjectClass *g_parent_class = 0; + +static void graph_tree_model_init (GraphTreeModel *graph_tree_model) +{ + graph_tree_model->graph = 0; +} + +static void graph_tree_model_finalize(GObject* object) +{ + GraphTreeModel* graph_tree_model = GRAPH_TREE_MODEL(object); + + /* must chain up */ + (* g_parent_class->finalize) (object); +} + +static void graph_tree_model_class_init (GraphTreeModelClass *class_) +{ + GObjectClass *object_class; + + g_parent_class = (GObjectClass*)g_type_class_peek_parent (class_); + object_class = (GObjectClass *) class_; + + object_class->finalize = graph_tree_model_finalize; +} + +static void graph_tree_model_tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = graph_tree_model_get_flags; + iface->get_n_columns = graph_tree_model_get_n_columns; + iface->get_column_type = graph_tree_model_get_column_type; + iface->get_iter = graph_tree_model_get_iter; + iface->get_path = graph_tree_model_get_path; + iface->get_value = graph_tree_model_get_value; + iface->iter_next = graph_tree_model_iter_next; + iface->iter_children = graph_tree_model_iter_children; + iface->iter_has_child = graph_tree_model_iter_has_child; + iface->iter_n_children = graph_tree_model_iter_n_children; + iface->iter_nth_child = graph_tree_model_iter_nth_child; + iface->iter_parent = graph_tree_model_iter_parent; +} + +static gboolean graph_tree_model_row_draggable(GtkTreeDragSource *drag_source, GtkTreePath *path) +{ +#ifdef _DEBUG + gint depth = gtk_tree_path_get_depth(path); +#endif + return gtk_tree_path_get_depth(path) > 1; +} + +static gboolean graph_tree_model_drag_data_delete(GtkTreeDragSource *drag_source, GtkTreePath *path) +{ + GtkTreeIter iter; + + if(gtk_tree_model_get_iter(GTK_TREE_MODEL(drag_source), &iter, path)) + { + graph_type::iterator i = graph_iterator_read_tree_iter(&iter); + Path_deleteTop((*i).first); + return TRUE; + } + else + { + return FALSE; + } +} + +static gboolean graph_tree_model_drag_data_get (GtkTreeDragSource *drag_source, GtkTreePath *path, GtkSelectionData *selection_data) +{ + if(gtk_tree_set_row_drag_data(selection_data, GTK_TREE_MODEL(drag_source), path)) + { + return TRUE; + } + else + { + /* FIXME handle text targets at least. */ + } + + return FALSE; +} + +static void graph_tree_model_drag_source_init (GtkTreeDragSourceIface *iface) +{ + iface->row_draggable = graph_tree_model_row_draggable; + iface->drag_data_delete = graph_tree_model_drag_data_delete; + iface->drag_data_get = graph_tree_model_drag_data_get; +} + +static gboolean graph_tree_model_drag_data_received (GtkTreeDragDest *drag_dest, GtkTreePath *dest, GtkSelectionData *selection_data) +{ + GtkTreeModel *tree_model = GTK_TREE_MODEL (drag_dest); + + GtkTreeModel *src_model = 0; + GtkTreePath *src_path = 0; + if(gtk_tree_get_row_drag_data(selection_data, &src_model, &src_path) + && src_model == tree_model) + { + /* Copy the given row to a new position */ + GtkTreeIter iter; + + if(gtk_tree_model_get_iter(src_model, &iter, src_path)) + { + int bleh = 0; + } + } + else + { + /* FIXME maybe add some data targets eventually, or handle text + * targets in the simple case. + */ + } + + return FALSE; +} + +static gboolean graph_tree_model_row_drop_possible(GtkTreeDragDest *drag_dest, GtkTreePath *dest_path, GtkSelectionData *selection_data) +{ + gboolean retval = FALSE; + + GtkTreeModel *src_model = 0; + GtkTreePath *src_path = 0; + if(gtk_tree_get_row_drag_data(selection_data, &src_model, &src_path) != FALSE) + { + /* can only drag to ourselves */ + if(src_model == GTK_TREE_MODEL(drag_dest)) + { + /* Can't drop into ourself. */ + if(!gtk_tree_path_is_ancestor(src_path, dest_path)) + { + /* Can't drop if dest_path's parent doesn't exist */ + if (gtk_tree_path_get_depth (dest_path) > 1) + { + GtkTreePath* tmp = gtk_tree_path_copy (dest_path); + gtk_tree_path_up (tmp); + + GtkTreeIter iter; + retval = gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_dest), &iter, tmp); + + gtk_tree_path_free (tmp); + } + } + } + + gtk_tree_path_free (src_path); + } + + return retval; +} + +static void graph_tree_model_drag_dest_init (GtkTreeDragDestIface *iface) +{ + iface->drag_data_received = graph_tree_model_drag_data_received; + iface->row_drop_possible = graph_tree_model_row_drop_possible; +} + +GType graph_tree_model_get_type (void) +{ + static GType graph_tree_model_type = 0; + + if (!graph_tree_model_type) + { + static const GTypeInfo graph_tree_model_info = + { + sizeof (GraphTreeModelClass), + 0, /* base_init */ + 0, /* base_finalize */ + (GClassInitFunc) graph_tree_model_class_init, + 0, /* class_finalize */ + 0, /* class_data */ + sizeof (GraphTreeModel), + 0, /* n_preallocs */ + (GInstanceInitFunc) graph_tree_model_init + }; + + static const GInterfaceInfo tree_model_info = + { + (GInterfaceInitFunc) graph_tree_model_tree_model_init, + 0, + 0 + }; + + static const GInterfaceInfo drag_source_info = + { + (GInterfaceInitFunc) graph_tree_model_drag_source_init, + 0, + 0 + }; + + static const GInterfaceInfo drag_dest_info = + { + (GInterfaceInitFunc) graph_tree_model_drag_dest_init, + 0, + 0 + }; + + graph_tree_model_type = g_type_register_static (G_TYPE_OBJECT, "GraphTreeModel", + &graph_tree_model_info, (GTypeFlags)0); + + g_type_add_interface_static (graph_tree_model_type, + GTK_TYPE_TREE_MODEL, + &tree_model_info); + g_type_add_interface_static (graph_tree_model_type, + GTK_TYPE_TREE_DRAG_SOURCE, + &drag_source_info); + g_type_add_interface_static (graph_tree_model_type, + GTK_TYPE_TREE_DRAG_DEST, + &drag_dest_info); + } + + return graph_tree_model_type; +} + +GraphTreeModel* graph_tree_model_new() +{ + GraphTreeModel* graph_tree_model = GRAPH_TREE_MODEL(g_object_new (graph_tree_model_get_type(), 0)); + + graph_tree_model->graph = new graph_type; + + return graph_tree_model; +} + +void graph_tree_model_delete(GraphTreeModel* model) +{ + delete model->graph; + g_object_unref(G_OBJECT(model)); +} + + +class TempNameable : public Nameable +{ + const char* m_name; +public: + TempNameable(const char* name) : m_name(name) + { + } + const char* name() const + { + return m_name; + } + void attach(const NameCallback& callback) + { + } + void detach(const NameCallback& callback) + { + } +}; + +void node_attach_name_changed_callback(scene::Node& node, const NameCallback& callback) +{ + if(&node != 0) + { + Nameable* nameable = Node_getNameable(node); + if(nameable != 0) + { + nameable->attach(callback); + } + } +} +void node_detach_name_changed_callback(scene::Node& node, const NameCallback& callback) +{ + if(&node != 0) + { + Nameable* nameable = Node_getNameable(node); + if(nameable != 0) + { + nameable->detach(callback); + } + } +} + +GraphTreeModel* scene_graph_get_tree_model(); // temp hack + +void graph_tree_model_row_inserted(GraphTreeModel* model, graph_type::iterator i) +{ + GtkTreeIter iter; + graph_iterator_write_tree_iter(i, &iter); + + GtkTreePath* tree_path = graph_tree_model_get_path(GTK_TREE_MODEL(model), &iter); + + gint depth = gtk_tree_path_get_depth(tree_path); + gint* indices = gtk_tree_path_get_indices(tree_path); + + gtk_tree_model_row_inserted(GTK_TREE_MODEL(model), tree_path, &iter); + + gtk_tree_path_free(tree_path); +} + +void graph_tree_model_row_deleted(GraphTreeModel* model, graph_type::iterator i) +{ + GtkTreeIter iter; + graph_iterator_write_tree_iter(i, &iter); + + GtkTreePath* tree_path = graph_tree_model_get_path(GTK_TREE_MODEL(model), &iter); + + gtk_tree_model_row_deleted(GTK_TREE_MODEL(model), tree_path); + + gtk_tree_path_free(tree_path); +} + +#include "generic/referencecounted.h" + +void graph_tree_model_set_name(const scene::Instance& instance, const char* name) +{ + GraphTreeModel* model = scene_graph_get_tree_model(); + + if(string_empty(name)) // hack! + { + graph_type::iterator i = model->graph->find(PathConstReference(instance.path())); + ASSERT_MESSAGE(i != model->graph->end(), "ERROR"); + + graph_tree_model_row_deleted(model, i); + + model->graph->erase(i); + } + else + { + graph_type::iterator i = model->graph->insert(graph_type::value_type(PathConstReference(instance.path()), &const_cast(instance))).first; + + graph_tree_model_row_inserted(model, i); + } +} + +void graph_tree_model_insert(GraphTreeModel* model, const scene::Instance& instance) +{ + graph_type::iterator i = model->graph->insert(graph_type::value_type(PathConstReference(instance.path()), &const_cast(instance))).first; + + graph_tree_model_row_inserted(model, i); + + node_attach_name_changed_callback(instance.path().top(), ConstReferenceCaller1(instance)); +} + +void graph_tree_model_erase(GraphTreeModel* model, const scene::Instance& instance) +{ + node_detach_name_changed_callback(instance.path().top(), ConstReferenceCaller1(instance)); + + graph_type::iterator i = model->graph->find(PathConstReference(instance.path())); + ASSERT_MESSAGE(i != model->graph->end(), "ERROR"); + + graph_tree_model_row_deleted(model, i); + + model->graph->erase(i); +} + +#elif 1 + +class GraphTreeNode; +void graph_tree_model_row_changed(GraphTreeNode& node); + +class GraphTreeNode +{ + typedef std::map, GraphTreeNode*> ChildNodes; + ChildNodes m_childnodes; +public: + Reference m_instance; + GraphTreeNode* m_parent; + + typedef ChildNodes::iterator iterator; + typedef ChildNodes::key_type key_type; + typedef ChildNodes::value_type value_type; + typedef ChildNodes::size_type size_type; + + GraphTreeNode(scene::Instance& instance) : m_instance(instance), m_parent(0) + { + m_instance.get().setChildSelectedChangedCallback(RowChangedCaller(*this)); + } + ~GraphTreeNode() + { + m_instance.get().setChildSelectedChangedCallback(Callback()); + ASSERT_MESSAGE(empty(), "GraphTreeNode::~GraphTreeNode: memory leak"); + } + + iterator begin() + { + return m_childnodes.begin(); + } + iterator end() + { + return m_childnodes.end(); + } + + size_type size() const + { + return m_childnodes.size(); + } + bool empty() const + { + return m_childnodes.empty(); + } + + iterator insert(const value_type& value) + { + iterator i = m_childnodes.insert(value).first; + (*i).second->m_parent = this; + return i; + } + void erase(iterator i) + { + m_childnodes.erase(i); + } + iterator find(const key_type& key) + { + return m_childnodes.find(key); + } + + void swap(GraphTreeNode& other) + { + std::swap(m_parent, other.m_parent); + std::swap(m_childnodes, other.m_childnodes); + std::swap(m_instance, other.m_instance); + } + + void rowChanged() + { + graph_tree_model_row_changed(*this); + } + typedef MemberCaller RowChangedCaller; +}; + +struct GraphTreeModel +{ + GObject parent; + + GraphTreeNode* m_graph; +}; + +struct GraphTreeModelClass +{ + GObjectClass parent_class; +}; + +#define GRAPH_TREE_MODEL(p) (reinterpret_cast(p)) + +static GtkTreeModelFlags graph_tree_model_get_flags (GtkTreeModel* tree_model) +{ + return GTK_TREE_MODEL_ITERS_PERSIST; +} + +static gint graph_tree_model_get_n_columns (GtkTreeModel* tree_model) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + //GraphTreeModel* graph_tree_model = (GraphTreeModel*) tree_model; + + return 2; +} + +static const gint c_stamp = 0xabcdef; + +inline GraphTreeNode::iterator graph_iterator_read_tree_iter(GtkTreeIter* iter) +{ + ASSERT_MESSAGE(iter != 0, "tree model error"); + ASSERT_MESSAGE(iter->user_data != 0, "tree model error"); + ASSERT_MESSAGE(iter->stamp == c_stamp, "tree model error"); + return *reinterpret_cast(&iter->user_data); +} + +inline void graph_iterator_write_tree_iter(GraphTreeNode::iterator i, GtkTreeIter* iter) +{ + ASSERT_MESSAGE(iter != 0, "tree model error"); + iter->stamp = c_stamp; + *reinterpret_cast(&iter->user_data) = i; + ASSERT_MESSAGE(iter->user_data != 0, "tree model error"); +} + +static GType graph_tree_model_get_column_type (GtkTreeModel *tree_model, gint index) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + //GraphTreeModel *graph_tree_model = (GraphTreeModel *) tree_model; + + return G_TYPE_POINTER; +} + +static gboolean graph_tree_model_get_iter(GtkTreeModel* tree_model, GtkTreeIter* iter, GtkTreePath* path) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + gint* indices = gtk_tree_path_get_indices (path); + gint depth = gtk_tree_path_get_depth (path); + + g_return_val_if_fail (depth > 0, FALSE); + + GraphTreeNode* graph = GRAPH_TREE_MODEL(tree_model)->m_graph; + + if(graph->empty()) + return FALSE; + + GtkTreeIter tmp; + GtkTreeIter* parent = 0; + + for(gint i = 0; i < depth; i++) + { + if (! gtk_tree_model_iter_nth_child (tree_model, iter, parent, indices[i])) + return FALSE; + tmp = *iter; + parent = &tmp; + } + + return TRUE; +} + +static GtkTreePath* graph_tree_model_get_path(GtkTreeModel* tree_model, GtkTreeIter* iter) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + GraphTreeNode* graph = GRAPH_TREE_MODEL(tree_model)->m_graph; + + GtkTreePath* path = gtk_tree_path_new(); + + for(GraphTreeNode* node = (*graph_iterator_read_tree_iter(iter)).second; node != graph; node = node->m_parent) + { + std::size_t index = 0; + for(GraphTreeNode::iterator i = node->m_parent->begin(); i != node->m_parent->end(); ++i, ++index) + { + if((*i).second == node) + { + gtk_tree_path_prepend_index(path, gint(index)); + break; + } + } + ASSERT_MESSAGE(index != node->m_parent->size(), "error resolving tree path"); + } + + return path; +} + + +static void graph_tree_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, gint column, GValue *value) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + ASSERT_MESSAGE(column == 0 || column == 1, "tree model error"); + + GraphTreeNode::iterator i = graph_iterator_read_tree_iter(iter); + + g_value_init (value, G_TYPE_POINTER); + + if(column == 0) + { + g_value_set_pointer(value, reinterpret_cast((*i).first.second)); + } + else + { + g_value_set_pointer(value, reinterpret_cast(&(*i).second->m_instance.get())); + } +} + +static gboolean graph_tree_model_iter_next (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + GraphTreeNode::iterator i = graph_iterator_read_tree_iter(iter); + GraphTreeNode& parent = *(*i).second->m_parent; + + ASSERT_MESSAGE(i != parent.end(), "RUNTIME ERROR"); + + if(++i == parent.end()) + { + return FALSE; + } + + graph_iterator_write_tree_iter(i, iter); + + return TRUE; +} + +static gboolean graph_tree_model_iter_children (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + GraphTreeNode& node = (parent == 0) ? *GRAPH_TREE_MODEL(tree_model)->m_graph : *(*graph_iterator_read_tree_iter(parent)).second; + if(!node.empty()) + { + graph_iterator_write_tree_iter(node.begin(), iter); + return TRUE; + } + + return FALSE; +} + +static gboolean graph_tree_model_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + GraphTreeNode& node = *(*graph_iterator_read_tree_iter(iter)).second; + return !node.empty(); +} + +static gint graph_tree_model_iter_n_children (GtkTreeModel *tree_model, GtkTreeIter *parent) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + GraphTreeNode& node = (parent == 0) ? *GRAPH_TREE_MODEL(tree_model)->m_graph : *(*graph_iterator_read_tree_iter(parent)).second; + return static_cast(node.size()); +} + +static gboolean graph_tree_model_iter_nth_child (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, gint n) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + GraphTreeNode& node = (parent == 0) ? *GRAPH_TREE_MODEL(tree_model)->m_graph : *(*graph_iterator_read_tree_iter(parent)).second; + if(static_cast(n) < node.size()) + { + GraphTreeNode::iterator i = node.begin(); + std::advance(i, n); + graph_iterator_write_tree_iter(i, iter); + return TRUE; + } + + return FALSE; +} + +static gboolean graph_tree_model_iter_parent(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child) +{ + ASSERT_MESSAGE(tree_model != 0, "RUNTIME ERROR"); + GraphTreeNode& node = *(*graph_iterator_read_tree_iter(child)).second; + if(node.m_parent != GRAPH_TREE_MODEL(tree_model)->m_graph) + { + GraphTreeNode& parentParent = *node.m_parent->m_parent; + for(GraphTreeNode::iterator i = parentParent.begin(); i != parentParent.end(); ++i) + { + if((*i).second == node.m_parent) + { + graph_iterator_write_tree_iter(i, iter); + return TRUE; + } + } + } + return FALSE; +} + +static GObjectClass *g_parent_class = 0; + +namespace +{ + scene::Node* g_null_node = 0; +} + +class NullInstance : public scene::Instance +{ +public: + NullInstance() : scene::Instance(scene::Path(makeReference(*g_null_node)), 0, 0, Static::instance()) + { + } +}; + +namespace +{ + NullInstance g_null_instance; +} + +static void graph_tree_model_init (GraphTreeModel *graph_tree_model) +{ + graph_tree_model->m_graph = new GraphTreeNode(g_null_instance); +} + +static void graph_tree_model_finalize(GObject* object) +{ + GraphTreeModel* graph_tree_model = GRAPH_TREE_MODEL(object); + + delete graph_tree_model->m_graph; + + /* must chain up */ + (* g_parent_class->finalize) (object); +} + +static void graph_tree_model_class_init (GraphTreeModelClass *class_) +{ + GObjectClass *object_class; + + g_parent_class = (GObjectClass*)g_type_class_peek_parent (class_); + object_class = (GObjectClass *) class_; + + object_class->finalize = graph_tree_model_finalize; +} + +static void graph_tree_model_tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = graph_tree_model_get_flags; + iface->get_n_columns = graph_tree_model_get_n_columns; + iface->get_column_type = graph_tree_model_get_column_type; + iface->get_iter = graph_tree_model_get_iter; + iface->get_path = graph_tree_model_get_path; + iface->get_value = graph_tree_model_get_value; + iface->iter_next = graph_tree_model_iter_next; + iface->iter_children = graph_tree_model_iter_children; + iface->iter_has_child = graph_tree_model_iter_has_child; + iface->iter_n_children = graph_tree_model_iter_n_children; + iface->iter_nth_child = graph_tree_model_iter_nth_child; + iface->iter_parent = graph_tree_model_iter_parent; +} + +GType graph_tree_model_get_type (void) +{ + static GType graph_tree_model_type = 0; + + if (!graph_tree_model_type) + { + static const GTypeInfo graph_tree_model_info = + { + sizeof (GraphTreeModelClass), + 0, /* base_init */ + 0, /* base_finalize */ + (GClassInitFunc) graph_tree_model_class_init, + 0, /* class_finalize */ + 0, /* class_data */ + sizeof (GraphTreeModel), + 0, /* n_preallocs */ + (GInstanceInitFunc) graph_tree_model_init, + 0 + }; + + static const GInterfaceInfo tree_model_info = + { + (GInterfaceInitFunc) graph_tree_model_tree_model_init, + 0, + 0 + }; + + graph_tree_model_type = g_type_register_static (G_TYPE_OBJECT, "GraphTreeModel", + &graph_tree_model_info, (GTypeFlags)0); + + g_type_add_interface_static (graph_tree_model_type, + GTK_TYPE_TREE_MODEL, + &tree_model_info); + } + + return graph_tree_model_type; +} + +GraphTreeModel* graph_tree_model_new() +{ + GraphTreeModel* graph_tree_model = GRAPH_TREE_MODEL(g_object_new (graph_tree_model_get_type(), 0)); + + return graph_tree_model; +} + +void graph_tree_model_delete(GraphTreeModel* model) +{ + g_object_unref(G_OBJECT(model)); +} + +void graph_tree_model_row_changed(GraphTreeModel* model, GraphTreeNode::iterator i) +{ + GtkTreeIter iter; + graph_iterator_write_tree_iter(i, &iter); + + GtkTreePath* tree_path = graph_tree_model_get_path(GTK_TREE_MODEL(model), &iter); + + gtk_tree_model_row_changed(GTK_TREE_MODEL(model), tree_path, &iter); + + gtk_tree_path_free(tree_path); +} + +void graph_tree_model_row_inserted(GraphTreeModel* model, GraphTreeNode::iterator i) +{ + GtkTreeIter iter; + graph_iterator_write_tree_iter(i, &iter); + + GtkTreePath* tree_path = graph_tree_model_get_path(GTK_TREE_MODEL(model), &iter); + + gtk_tree_model_row_inserted(GTK_TREE_MODEL(model), tree_path, &iter); + + gtk_tree_path_free(tree_path); +} + +void graph_tree_model_row_deleted(GraphTreeModel* model, GraphTreeNode::iterator i) +{ + GtkTreeIter iter; + graph_iterator_write_tree_iter(i, &iter); + + GtkTreePath* tree_path = graph_tree_model_get_path(GTK_TREE_MODEL(model), &iter); + + gtk_tree_model_row_deleted(GTK_TREE_MODEL(model), tree_path); + + gtk_tree_path_free(tree_path); +} + +void graph_tree_model_row_inserted(GraphTreeModel& model, GraphTreeNode::iterator i) +{ + graph_tree_model_row_inserted(&model, i); +} + +void graph_tree_model_row_deleted(GraphTreeModel& model, GraphTreeNode::iterator i) +{ + graph_tree_model_row_deleted(&model, i); +} + +const char* node_get_name(scene::Node& node); + +const char* node_get_name_safe(scene::Node& node) +{ + if(&node == 0) + { + return ""; + } + return node_get_name(node); +} + +GraphTreeNode* graph_tree_model_find_parent(GraphTreeModel* model, const scene::Path& path) +{ + GraphTreeNode* parent = model->m_graph; + for(scene::Path::const_iterator i = path.begin(); i != path.end()-1; ++i) + { + GraphTreeNode::iterator child = parent->find(GraphTreeNode::key_type(node_get_name_safe((*i).get()), (*i).get_pointer())); + ASSERT_MESSAGE(child != parent->end(), "ERROR"); + parent = (*child).second; + } + return parent; +} + +void node_attach_name_changed_callback(scene::Node& node, const NameCallback& callback) +{ + if(&node != 0) + { + Nameable* nameable = Node_getNameable(node); + if(nameable != 0) + { + nameable->attach(callback); + } + } +} +void node_detach_name_changed_callback(scene::Node& node, const NameCallback& callback) +{ + if(&node != 0) + { + Nameable* nameable = Node_getNameable(node); + if(nameable != 0) + { + nameable->detach(callback); + } + } +} + +GraphTreeModel* scene_graph_get_tree_model(); // temp hack + +void graph_tree_node_foreach_pre(GraphTreeNode::iterator root, const Callback1& callback) +{ + callback(root); + for(GraphTreeNode::iterator i = (*root).second->begin(); i != (*root).second->end(); ++i) + { + graph_tree_node_foreach_pre(i, callback); + } +} + +void graph_tree_node_foreach_post(GraphTreeNode::iterator root, const Callback1& callback) +{ + for(GraphTreeNode::iterator i = (*root).second->begin(); i != (*root).second->end(); ++i) + { + graph_tree_node_foreach_post(i, callback); + } + callback(root); +} + +void graph_tree_model_row_changed(GraphTreeNode& node) +{ + GraphTreeModel* model = scene_graph_get_tree_model(); + const scene::Instance& instance = node.m_instance.get(); + + GraphTreeNode::iterator i = node.m_parent->find(GraphTreeNode::key_type(node_get_name_safe(instance.path().top().get()), instance.path().top().get_pointer())); + + graph_tree_model_row_changed(model, i); +} + +void graph_tree_model_set_name(const scene::Instance& instance, const char* name) +{ + GraphTreeModel* model = scene_graph_get_tree_model(); + GraphTreeNode* parent = graph_tree_model_find_parent(model, instance.path()); + + GraphTreeNode::iterator oldNode = parent->find(GraphTreeNode::key_type(node_get_name_safe(instance.path().top().get()), instance.path().top().get_pointer())); + graph_tree_node_foreach_post(oldNode, ReferenceCaller1(*model)); + GraphTreeNode* node((*oldNode).second); + parent->erase(oldNode); + + GraphTreeNode::iterator newNode = parent->insert(GraphTreeNode::value_type(GraphTreeNode::key_type(name, &instance.path().top().get()), node)); + graph_tree_node_foreach_pre(newNode, ReferenceCaller1(*model)); +} + +void graph_tree_model_insert(GraphTreeModel* model, const scene::Instance& instance) +{ + GraphTreeNode* parent = graph_tree_model_find_parent(model, instance.path()); + + GraphTreeNode::iterator i = parent->insert(GraphTreeNode::value_type(GraphTreeNode::key_type(node_get_name_safe(instance.path().top().get()), instance.path().top().get_pointer()), new GraphTreeNode(const_cast(instance)))); + + graph_tree_model_row_inserted(model, i); + + node_attach_name_changed_callback(instance.path().top(), ConstReferenceCaller1(instance)); +} + +void graph_tree_model_erase(GraphTreeModel* model, const scene::Instance& instance) +{ + node_detach_name_changed_callback(instance.path().top(), ConstReferenceCaller1(instance)); + + GraphTreeNode* parent = graph_tree_model_find_parent(model, instance.path()); + + GraphTreeNode::iterator i = parent->find(GraphTreeNode::key_type(node_get_name_safe(instance.path().top().get()), instance.path().top().get_pointer())); + + graph_tree_model_row_deleted(model, i); + + GraphTreeNode* node((*i).second); + parent->erase(i); + delete node; +} + + + +#endif + + + + +#if 0 +class TestGraphTreeModel +{ +public: + TestGraphTreeModel() + { + gtk_init(0, 0); + + graph_type graph; + + scene::Node* root = *(scene::Node*)0xa0000000; + scene::Node* node1 = (scene::Node*)0xa0000001; + scene::Node* node2 = (scene::Node*)0xa0000002; + scene::Node* node3 = (scene::Node*)0xa0000003; + scene::Node* node4 = (scene::Node*)0xa0000004; + scene::Instance* instance = (scene::Instance*)0xaaaaaaaa; + + scene::Path rootpath(root); + + graph.insert(graph_type::value_type(rootpath, instance)); + + rootpath.push(node1); + graph.insert(graph_type::value_type(rootpath, instance)); + rootpath.pop(); + + rootpath.push(node2); + graph.insert(graph_type::value_type(rootpath, instance)); + rootpath.push(node3); + graph.insert(graph_type::value_type(rootpath, instance)); + rootpath.pop(); + rootpath.push(node4); + graph.insert(graph_type::value_type(rootpath, instance)); + rootpath.pop(); + rootpath.pop(); + + GtkTreeModel* model = GTK_TREE_MODEL(graph_tree_model_new(&graph)); + + { + gint n_columns = gtk_tree_model_get_n_columns(model); + ASSERT_MESSAGE(n_columns == 2, "test failed!"); + } + + { + GType type = gtk_tree_model_get_column_type(model, 0); + ASSERT_MESSAGE(type == G_TYPE_POINTER, "test failed!"); + } + + { + GType type = gtk_tree_model_get_column_type(model, 1); + ASSERT_MESSAGE(type == G_TYPE_POINTER, "test failed!"); + } + + + { + GtkTreeIter iter; + gtk_tree_model_get_iter_first(model, &iter); + + graph_type::iterator i = graph_iterator_read_tree_iter(&iter); + ASSERT_MESSAGE((*i).first.get().size() == 2 && (*i).first.get().top() == node1, "test failed!"); + } + + { + GtkTreeIter iter; + gtk_tree_model_get_iter_first(model, &iter); + + ASSERT_MESSAGE(gtk_tree_model_iter_has_child(model, &iter) == FALSE, "test failed!"); + + ASSERT_MESSAGE(gtk_tree_model_iter_n_children(model, &iter) == 0, "test failed!"); + + gtk_tree_model_iter_next(model, &iter); + + ASSERT_MESSAGE(gtk_tree_model_iter_has_child(model, &iter) != FALSE, "test failed!"); + + ASSERT_MESSAGE(gtk_tree_model_iter_n_children(model, &iter) == 2, "test failed!"); + + { + GtkTreeIter child; + gtk_tree_model_iter_nth_child(model, &child, &iter, 0); + + scene::Node* test; + gtk_tree_model_get_value(model, &child, 0, (GValue*)&test); + ASSERT_MESSAGE(test == node3, "test failed!"); + + { + GtkTreeIter parent; + gtk_tree_model_iter_parent(model, &parent, &child); + + scene::Node* test; + gtk_tree_model_get_value(model, &parent, 0, (GValue*)&test); + ASSERT_MESSAGE(test == node2, "test failed!"); + } + } + + { + GtkTreeIter child; + gtk_tree_model_iter_nth_child(model, &child, &iter, 1); + + scene::Node* test; + gtk_tree_model_get_value(model, &child, 0, (GValue*)&test); + ASSERT_MESSAGE(test == node4, "test failed!"); + } + } + + { + GtkTreeIter iter; + std::size_t count = 0; + for(gboolean good = gtk_tree_model_get_iter_first(model, &iter); good; good = gtk_tree_model_iter_next(model, &iter)) + { + scene::Node* test; + gtk_tree_model_get_value(model, &iter, 0, (GValue*)&test); + + ASSERT_MESSAGE((count == 0 && test == node1) || (count == 1 && test == node2), "test failed!"); + ++count; + } + + ASSERT_MESSAGE(count == 2, "test failed!"); + + } + + { + GtkTreeIter iter; + gtk_tree_model_get_iter_first(model, &iter); + + scene::Node* test; + gtk_tree_model_get_value(model, &iter, 0, (GValue*)&test); + ASSERT_MESSAGE(test == node1, "test failed!"); + } + + { + GtkTreeIter iter; + GtkTreePath* path = gtk_tree_path_new_from_string("0"); + gtk_tree_model_get_iter(model, &iter, path); + gtk_tree_path_free(path); + + graph_type::iterator i = graph_iterator_read_tree_iter(&iter); + ASSERT_MESSAGE((*i).first.get().size() == 2 && (*i).first.get().top() == node1, "test failed!"); + } + + { + GtkTreeIter iter; + GtkTreePath* path = gtk_tree_path_new_from_string("1"); + gtk_tree_model_get_iter(model, &iter, path); + gtk_tree_path_free(path); + + graph_type::iterator i = graph_iterator_read_tree_iter(&iter); + ASSERT_MESSAGE((*i).first.get().size() == 2 && (*i).first.get().top() == node2, "test failed!"); + } + + { + GtkTreeIter iter; + graph_type::iterator i = graph.begin(); + ++i; + graph_iterator_write_tree_iter(i, &iter); + + GtkTreePath* path = gtk_tree_model_get_path(model, &iter); + + gint depth = gtk_tree_path_get_depth(path); + gint* indices = gtk_tree_path_get_indices(path); + + ASSERT_MESSAGE(depth == 1 && indices[0] == 0, "test failed!"); + + gtk_tree_path_free(path); + } + + { + GtkTreeIter iter; + graph_type::iterator i = graph.begin(); + ++i; + ++i; + graph_iterator_write_tree_iter(i, &iter); + + GtkTreePath* path = gtk_tree_model_get_path(model, &iter); + + gint depth = gtk_tree_path_get_depth(path); + gint* indices = gtk_tree_path_get_indices(path); + + ASSERT_MESSAGE(depth == 1 && indices[0] == 1, "test failed!"); + + gtk_tree_path_free(path); + } + } +}; + + +TestGraphTreeModel g_TestGraphTreeModel; + +#endif diff --git a/radiant/treemodel.h b/radiant/treemodel.h new file mode 100644 index 00000000..7c33485b --- /dev/null +++ b/radiant/treemodel.h @@ -0,0 +1,37 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_TREEMODEL_H) +#define INCLUDED_TREEMODEL_H + +struct GraphTreeModel; + +GraphTreeModel* graph_tree_model_new(); +void graph_tree_model_delete(GraphTreeModel* model); + +namespace scene +{ + class Instance; +} +void graph_tree_model_insert(GraphTreeModel* model, const scene::Instance& instance); +void graph_tree_model_erase(GraphTreeModel* model, const scene::Instance& instance); + +#endif diff --git a/radiant/undo.cpp b/radiant/undo.cpp new file mode 100644 index 00000000..aa75d950 --- /dev/null +++ b/radiant/undo.cpp @@ -0,0 +1,590 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "undo.h" + +#include "debugging/debugging.h" +#include "warnings.h" + +#include "iundo.h" +#include "preferencesystem.h" +#include "string/string.h" +#include "generic/callback.h" +#include "preferences.h" +#include "stringio.h" + +#include +#include +#include + +#include "timer.h" + +class DebugScopeTimer +{ + Timer m_timer; + const char* m_operation; +public: + DebugScopeTimer(const char* operation) + : m_operation(operation) + { + m_timer.start(); + } + ~DebugScopeTimer() + { + unsigned int elapsed = m_timer.elapsed_msec(); + if(elapsed > 0) + { + globalOutputStream() << m_operation << ": " << elapsed << " msec\n"; + } + } +}; + + +class RadiantUndoSystem : public UndoSystem +{ + INTEGER_CONSTANT(MAX_UNDO_LEVELS, 1024); + + class Snapshot + { + class StateApplicator + { + public: + Undoable* m_undoable; + private: + UndoMemento* m_data; + public: + + StateApplicator(Undoable* undoable, UndoMemento* data) + : m_undoable(undoable), m_data(data) + { + } + void restore() + { + m_undoable->importState(m_data); + } + void release() + { + m_data->release(); + } + }; + + typedef std::list states_t; + states_t m_states; + + public: + bool empty() const + { + return m_states.empty(); + } + std::size_t size() const + { + return m_states.size(); + } + void save(Undoable* undoable) + { + m_states.push_front(StateApplicator(undoable, undoable->exportState())); + } + void restore() + { + for(states_t::iterator i = m_states.begin(); i != m_states.end(); ++i) + { + (*i).restore(); + } + } + void release() + { + for(states_t::iterator i = m_states.begin(); i != m_states.end(); ++i) + { + (*i).release(); + } + } + }; + + struct Operation + { + Snapshot m_snapshot; + CopiedString m_command; + + Operation(const char* command) + : m_command(command) + { + } + ~Operation() + { + m_snapshot.release(); + } + }; + + + class UndoStack + { + //! Note: using std::list instead of vector/deque, to avoid copying of undos + typedef std::list Operations; + + Operations m_stack; + Operation* m_pending; + + public: + UndoStack() : m_pending(0) + { + } + ~UndoStack() + { + clear(); + } + bool empty() const + { + return m_stack.empty(); + } + std::size_t size() const + { + return m_stack.size(); + } + Operation* back() + { + return m_stack.back(); + } + const Operation* back() const + { + return m_stack.back(); + } + Operation* front() + { + return m_stack.front(); + } + const Operation* front() const + { + return m_stack.front(); + } + void pop_front() + { + delete m_stack.front(); + m_stack.pop_front(); + } + void pop_back() + { + delete m_stack.back(); + m_stack.pop_back(); + } + void clear() + { + if(!m_stack.empty()) + { + for(Operations::iterator i = m_stack.begin(); i != m_stack.end(); ++i) + { + delete *i; + } + m_stack.clear(); + } + } + void start(const char* command) + { + if(m_pending != 0) + { + delete m_pending; + } + m_pending = new Operation(command); + } + bool finish(const char* command) + { + if(m_pending != 0) + { + delete m_pending; + m_pending = 0; + return false; + } + else + { + ASSERT_MESSAGE(!m_stack.empty(), "undo stack empty"); + m_stack.back()->m_command = command; + return true; + } + } + void save(Undoable* undoable) + { + if(m_pending != 0) + { + m_stack.push_back(m_pending); + m_pending = 0; + } + back()->m_snapshot.save(undoable); + } + }; + + UndoStack m_undo_stack; + UndoStack m_redo_stack; + + class UndoStackFiller : public UndoObserver + { + UndoStack* m_stack; + public: + + UndoStackFiller() + : m_stack(0) + { + } + void save(Undoable* undoable) + { + ASSERT_NOTNULL(undoable); + + if(m_stack != 0) + { + m_stack->save(undoable); + m_stack = 0; + } + } + void setStack(UndoStack* stack) + { + m_stack = stack; + } + }; + + typedef std::map undoables_t; + undoables_t m_undoables; + + void mark_undoables(UndoStack* stack) + { + for(undoables_t::iterator i = m_undoables.begin(); i != m_undoables.end(); ++i) + { + (*i).second.setStack(stack); + } + } + + std::size_t m_undo_levels; + + typedef std::set Trackers; + Trackers m_trackers; +public: + RadiantUndoSystem() + : m_undo_levels(64) + { + } + ~RadiantUndoSystem() + { + clear(); + } + UndoObserver* observer(Undoable* undoable) + { + ASSERT_NOTNULL(undoable); + + return &m_undoables[undoable]; + } + void release(Undoable* undoable) + { + ASSERT_NOTNULL(undoable); + + m_undoables.erase(undoable); + } + void setLevels(std::size_t levels) + { + if(levels > MAX_UNDO_LEVELS()) + { + levels = MAX_UNDO_LEVELS(); + } + + while(m_undo_stack.size() > levels) + { + m_undo_stack.pop_front(); + } + m_undo_levels = levels; + } + std::size_t getLevels() const + { + return m_undo_levels; + } + std::size_t size() const + { + return m_undo_stack.size(); + } + void startUndo() + { + m_undo_stack.start("unnamedCommand"); + mark_undoables(&m_undo_stack); + } + bool finishUndo(const char* command) + { + bool changed = m_undo_stack.finish(command); + mark_undoables(0); + return changed; + } + void startRedo() + { + m_redo_stack.start("unnamedCommand"); + mark_undoables(&m_redo_stack); + } + bool finishRedo(const char* command) + { + bool changed = m_redo_stack.finish(command); + mark_undoables(0); + return changed; + } + void start() + { + m_redo_stack.clear(); + if(m_undo_stack.size() == m_undo_levels) + { + m_undo_stack.pop_front(); + } + startUndo(); + trackersBegin(); + } + void finish(const char* command) + { + if(finishUndo(command)) + { + globalOutputStream() << command << '\n'; + } + } + void undo() + { + if(m_undo_stack.empty()) + { + globalOutputStream() << "Undo: no undo available\n"; + } + else + { + Operation* operation = m_undo_stack.back(); + globalOutputStream() << "Undo: " << operation->m_command.c_str() << "\n"; + + startRedo(); + trackersUndo(); + operation->m_snapshot.restore(); + finishRedo(operation->m_command.c_str()); + m_undo_stack.pop_back(); + } + } + void redo() + { + if(m_redo_stack.empty()) + { + globalOutputStream() << "Redo: no redo available\n"; + } + else + { + Operation* operation = m_redo_stack.back(); + globalOutputStream() << "Redo: " << operation->m_command.c_str() << "\n"; + + startUndo(); + trackersRedo(); + operation->m_snapshot.restore(); + finishUndo(operation->m_command.c_str()); + m_redo_stack.pop_back(); + } + } + void clear() + { + mark_undoables(0); + m_undo_stack.clear(); + m_redo_stack.clear(); + trackersClear(); + } + void trackerAttach(UndoTracker& tracker) + { + ASSERT_MESSAGE(m_trackers.find(&tracker) == m_trackers.end(), "undo tracker already attached"); + m_trackers.insert(&tracker); + } + void trackerDetach(UndoTracker& tracker) + { + ASSERT_MESSAGE(m_trackers.find(&tracker) != m_trackers.end(), "undo tracker cannot be detached"); + m_trackers.erase(&tracker); + } + void trackersClear() const + { + for(Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i) + { + (*i)->clear(); + } + } + void trackersBegin() const + { + for(Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i) + { + (*i)->begin(); + } + } + void trackersUndo() const + { + for(Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i) + { + (*i)->undo(); + } + } + void trackersRedo() const + { + for(Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i) + { + (*i)->redo(); + } + } +}; + + + +void UndoLevels_importString(RadiantUndoSystem& undo, const char* value) +{ + int levels; + Int_importString(levels, value); + undo.setLevels(levels); +} +typedef ReferenceCaller1 UndoLevelsImportStringCaller; +void UndoLevels_exportString(const RadiantUndoSystem& undo, const StringImportCallback& importer) +{ + Int_exportString(static_cast(undo.getLevels()), importer); +} +typedef ConstReferenceCaller1 UndoLevelsExportStringCaller; + +#include "generic/callback.h" + +void UndoLevelsImport(RadiantUndoSystem& self, int value) +{ + self.setLevels(value); +} +typedef ReferenceCaller1 UndoLevelsImportCaller; +void UndoLevelsExport(const RadiantUndoSystem& self, const IntImportCallback& importCallback) +{ + importCallback(static_cast(self.getLevels())); +} +typedef ConstReferenceCaller1 UndoLevelsExportCaller; + + +void Undo_constructPreferences(RadiantUndoSystem& undo, PreferencesPage& page) +{ + page.appendSpinner("Undo Queue Size", 64, 0, 1024, IntImportCallback(UndoLevelsImportCaller(undo)), IntExportCallback(UndoLevelsExportCaller(undo))); +} +void Undo_constructPage(RadiantUndoSystem& undo, PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Undo", "Undo Queue Settings")); + Undo_constructPreferences(undo, page); +} +void Undo_registerPreferencesPage(RadiantUndoSystem& undo) +{ + PreferencesDialog_addSettingsPage(ReferenceCaller1(undo)); +} + +class UndoSystemDependencies : public GlobalPreferenceSystemModuleRef +{ +}; + +class UndoSystemAPI +{ + RadiantUndoSystem m_undosystem; +public: + typedef UndoSystem Type; + STRING_CONSTANT(Name, "*"); + + UndoSystemAPI() + { + GlobalPreferenceSystem().registerPreference("UndoLevels", makeIntStringImportCallback(UndoLevelsImportCaller(m_undosystem)), makeIntStringExportCallback(UndoLevelsExportCaller(m_undosystem))); + + Undo_registerPreferencesPage(m_undosystem); + } + UndoSystem* getTable() + { + return &m_undosystem; + } +}; + +#include "modulesystem/singletonmodule.h" +#include "modulesystem/moduleregistry.h" + +typedef SingletonModule UndoSystemModule; +typedef Static StaticUndoSystemModule; +StaticRegisterModule staticRegisterUndoSystem(StaticUndoSystemModule::instance()); + + + + + + + + + + +class undoable_test : public Undoable +{ + struct state_type : public UndoMemento + { + state_type() : test_data(0) + { + } + state_type(const state_type& other) : UndoMemento(other), test_data(other.test_data) + { + } + void release() + { + delete this; + } + + int test_data; + }; + state_type m_state; + UndoObserver* m_observer; +public: + undoable_test() + : m_observer(GlobalUndoSystem().observer(this)) + { + } + ~undoable_test() + { + GlobalUndoSystem().release(this); + } + UndoMemento* exportState() const + { + return new state_type(m_state); + } + void importState(const UndoMemento* state) + { + ASSERT_NOTNULL(state); + + m_observer->save(this); + m_state = *(static_cast(state)); + } + + void mutate(unsigned int data) + { + m_observer->save(this); + m_state.test_data = data; + } +}; + +#if 0 + +class TestUndo +{ +public: + TestUndo() + { + undoable_test test; + GlobalUndoSystem().begin("bleh"); + test.mutate(3); + GlobalUndoSystem().begin("blah"); + test.mutate(4); + GlobalUndoSystem().undo(); + GlobalUndoSystem().undo(); + GlobalUndoSystem().redo(); + GlobalUndoSystem().redo(); + } +}; + +TestUndo g_TestUndo; + +#endif + diff --git a/radiant/undo.h b/radiant/undo.h new file mode 100644 index 00000000..171af8cf --- /dev/null +++ b/radiant/undo.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_UNDO_H) +#define INCLUDED_UNDO_H + +#endif diff --git a/radiant/url.cpp b/radiant/url.cpp new file mode 100644 index 00000000..382dcd90 --- /dev/null +++ b/radiant/url.cpp @@ -0,0 +1,66 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "url.h" + +#include "mainframe.h" +#include "gtkutil/messagebox.h" + +#ifdef WIN32 +#include +#include +bool open_url(const char* url) +{ + return ShellExecute( (HWND)GDK_WINDOW_HWND (GTK_WIDGET(MainFrame_getWindow())->window), "open", url, 0, 0, SW_SHOW ) > (HINSTANCE)32; +} +#endif + +#if defined(__linux__) || defined(__FreeBSD__) +#include +bool open_url(const char* url) +{ + // \todo FIXME: the way we open URLs on *nix should be improved. A script is good (see how I do on RTCW) + char command[2*PATH_MAX]; + snprintf (command, sizeof(command), + "netscape -remote \"openURL(%s,new-window)\" || netscape \"%s\" &", url, url); + return system(command) == 0; +} +#endif + +#ifdef __APPLE__ +#include +bool open_url(const char* url) +{ + char command[2*PATH_MAX]; + snprintf (command, sizeof(command), "open \"%s\" &", url); + return system(command) == 0; +} +#endif + +void OpenURL(const char *url) +{ + // let's put a little comment + globalOutputStream() << "OpenURL: " << url << "\n"; + if(!open_url(url)) + { + gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), "Failed to launch browser!"); + } +} diff --git a/radiant/url.h b/radiant/url.h new file mode 100644 index 00000000..38d50b51 --- /dev/null +++ b/radiant/url.h @@ -0,0 +1,27 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_URL_H) +#define INCLUDED_URL_H + +void OpenURL(const char *url); + +#endif diff --git a/radiant/view.cpp b/radiant/view.cpp new file mode 100644 index 00000000..5282eb0f --- /dev/null +++ b/radiant/view.cpp @@ -0,0 +1,57 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "view.h" + +#if defined(DEBUG_CULLING) + +#include + +char g_cull_stats[1024]; +int g_count_dots; +int g_count_planes; +int g_count_oriented_planes; +int g_count_bboxs; +int g_count_oriented_bboxs; + +#endif + +void Cull_ResetStats() +{ +#if defined(DEBUG_CULLING) + g_count_dots = 0; + g_count_planes = 0; + g_count_oriented_planes = 0; + g_count_bboxs = 0; + g_count_oriented_bboxs = 0; +#endif +} + + +const char* Cull_GetStats() +{ +#if defined(DEBUG_CULLING) + sprintf(g_cull_stats, "dots: %d | planes %d + %d | bboxs %d + %d", g_count_dots, g_count_planes, g_count_oriented_planes, g_count_bboxs, g_count_oriented_bboxs); + return g_cull_stats; +#else + return ""; +#endif +} diff --git a/radiant/view.h b/radiant/view.h new file mode 100644 index 00000000..b6dc414a --- /dev/null +++ b/radiant/view.h @@ -0,0 +1,211 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_VIEW_H) +#define INCLUDED_VIEW_H + +#include "cullable.h" +#include "math/frustum.h" + + +#if defined(_DEBUG) +#define DEBUG_CULLING +#endif + + +#if defined(DEBUG_CULLING) + +extern int g_count_dots; +extern int g_count_planes; +extern int g_count_oriented_planes; +extern int g_count_bboxs; +extern int g_count_oriented_bboxs; + +#endif + +inline void debug_count_dot() +{ +#if defined(DEBUG_CULLING) + ++g_count_dots; +#endif +} + +inline void debug_count_plane() +{ +#if defined(DEBUG_CULLING) + ++g_count_planes; +#endif +} + +inline void debug_count_oriented_plane() +{ +#if defined(DEBUG_CULLING) + ++g_count_oriented_planes; +#endif +} + +inline void debug_count_bbox() +{ +#if defined(DEBUG_CULLING) + ++g_count_bboxs; +#endif +} + +inline void debug_count_oriented_bbox() +{ +#if defined(DEBUG_CULLING) + ++g_count_oriented_bboxs; +#endif +} + + + + +/// \brief View-volume culling and transformations. +class View : public VolumeTest +{ + /// modelview matrix + Matrix4 m_modelview; + /// projection matrix + Matrix4 m_projection; + /// device-to-screen transform + Matrix4 m_viewport; + + Matrix4 m_scissor; + + /// combined modelview and projection matrix + Matrix4 m_viewproj; + /// camera position in world space + Vector4 m_viewer; + /// view frustum in world space + Frustum m_frustum; + + bool m_fill; + + void construct() + { + m_viewproj = matrix4_multiplied_by_matrix4(matrix4_multiplied_by_matrix4(m_scissor, m_projection), m_modelview); + + m_frustum = frustum_from_viewproj(m_viewproj); + m_viewer = viewer_from_viewproj(m_viewproj); + } +public: + View(bool fill = false) : + m_modelview(g_matrix4_identity), + m_projection(g_matrix4_identity), + m_scissor(g_matrix4_identity), + m_fill(fill) + { + } + void Construct(const Matrix4& projection, const Matrix4& modelview, std::size_t width, std::size_t height) + { + // modelview + m_modelview = modelview; + + // projection + m_projection = projection; + + // viewport + m_viewport = g_matrix4_identity; + m_viewport[0] = float(width/2); + m_viewport[5] = float(height/2); + if(fabs(m_projection[11]) > 0.0000001) + m_viewport[10] = m_projection[0] * m_viewport[0]; + else + m_viewport[10] = 1 / m_projection[10]; + + construct(); + } + void EnableScissor(float min_x, float max_x, float min_y, float max_y) + { + m_scissor = g_matrix4_identity; + m_scissor[0] = static_cast((max_x - min_x) * 0.5); + m_scissor[5] = static_cast((max_y - min_y) * 0.5); + m_scissor[12] = static_cast((min_x + max_x) * 0.5); + m_scissor[13] = static_cast((min_y + max_y) * 0.5); + matrix4_full_invert(m_scissor); + + construct(); + } + void DisableScissor() + { + m_scissor = g_matrix4_identity; + + construct(); + } + + bool TestPoint(const Vector3& point) const + { + return viewproj_test_point(m_viewproj, point); + } + bool TestLine(const Segment& segment) const + { + return frustum_test_line(m_frustum, segment); + } + bool TestPlane(const Plane3& plane) const + { + debug_count_plane(); + return viewer_test_plane(m_viewer, plane); + } + bool TestPlane(const Plane3& plane, const Matrix4& localToWorld) const + { + debug_count_oriented_plane(); + return viewer_test_transformed_plane(m_viewer, plane, localToWorld); + } + VolumeIntersectionValue TestAABB(const AABB& aabb) const + { + debug_count_bbox(); + return frustum_test_aabb(m_frustum, aabb); + } + VolumeIntersectionValue TestAABB(const AABB& aabb, const Matrix4& localToWorld) const + { + debug_count_oriented_bbox(); + return frustum_intersects_transformed_aabb(m_frustum, aabb, localToWorld); + } + + const Matrix4& GetViewMatrix() const + { + return m_viewproj; + } + const Matrix4& GetViewport() const + { + return m_viewport; + }; + const Matrix4& GetModelview() const + { + return m_modelview; + } + const Matrix4& GetProjection() const + { + return m_projection; + } + + bool fill() const + { + return m_fill; + } + const Vector3& getViewer() const + { + return vector4_to_vector3(m_viewer); + } +}; + +#endif diff --git a/radiant/watchbsp.cpp b/radiant/watchbsp.cpp new file mode 100644 index 00000000..e2cc86c3 --- /dev/null +++ b/radiant/watchbsp.cpp @@ -0,0 +1,879 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +//----------------------------------------------------------------------------- +// +// DESCRIPTION: +// monitoring window for running BSP processes (and possibly various other stuff) + +#include "watchbsp.h" + +#include +#include + +#include "cmdlib.h" +#include "convert.h" +#include "string/string.h" +#include "stream/stringstream.h" + +#include "gtkutil/messagebox.h" +#include "xmlstuff.h" +#include "console.h" +#include "preferences.h" +#include "points.h" +#include "feedback.h" +#include "mainframe.h" +#include "sockets.h" + +void message_flush(message_info_t* self) +{ + Sys_Print(self->msg_level, self->m_buffer, self->m_length); + self->m_length = 0; +} + +void message_print(message_info_t* self, const char* characters, std::size_t length) +{ + const char* end = characters + length; + while(characters != end) + { + std::size_t space = message_info_t::bufsize - 1 - self->m_length; + if(space == 0) + { + message_flush(self); + } + else + { + std::size_t size = std::min(space, std::size_t(end - characters)); + memcpy(self->m_buffer + self->m_length, characters, size); + self->m_length += size; + characters += size; + } + } +} + + +#include +#include +#include "xmlstuff.h" + +class CWatchBSP +{ +private: + // a flag we have set to true when using an external BSP plugin + // the resulting code with that is a bit dirty, cleaner solution would be to seperate the succession of commands from the listening loop + // (in two seperate classes probably) + bool m_bBSPPlugin; + + // EIdle: we are not listening + // DoMonitoringLoop will change state to EBeginStep + // EBeginStep: the socket is up for listening, we are expecting incoming connection + // incoming connection will change state to EWatching + // EWatching: we have a connection, monitor it + // connection closed will see if we start a new step (EBeginStep) or launch Quake3 and end (EIdle) + enum EWatchBSPState { EIdle, EBeginStep, EWatching } m_eState; + socket_t *m_pListenSocket; + socket_t *m_pInSocket; + netmessage_t msg; + GPtrArray *m_pCmd; + // used to timeout EBeginStep + GTimer *m_pTimer; + std::size_t m_iCurrentStep; + // name of the map so we can run the engine + char *m_sBSPName; + // buffer we use in push mode to receive data directly from the network + xmlParserInputBufferPtr m_xmlInputBuffer; + xmlParserInputPtr m_xmlInput; + xmlParserCtxtPtr m_xmlParserCtxt; + // call this to switch the set listening mode + bool SetupListening(); + // start a new EBeginStep + void DoEBeginStep(); + // the xml and sax parser state + char m_xmlBuf[MAX_NETMESSAGE]; + bool m_bNeedCtxtInit; + message_info_t m_message_info; + +public: + CWatchBSP() + { + m_pCmd = 0; + m_bBSPPlugin = false; + m_pListenSocket = NULL; + m_pInSocket = NULL; + m_eState = EIdle; + m_pTimer = g_timer_new(); + m_sBSPName = NULL; + m_xmlInputBuffer = NULL; + m_bNeedCtxtInit = true; + } + virtual ~CWatchBSP() + { + EndMonitoringLoop(); + Net_Shutdown(); + + g_timer_destroy(m_pTimer); + } + + bool HasBSPPlugin() const + { return m_bBSPPlugin; } + + // called regularly to keep listening + void RoutineProcessing(); + // start a monitoring loop with the following steps + void DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName ); + void EndMonitoringLoop() + { + Reset(); + if (m_sBSPName) + { + string_release(m_sBSPName, string_length(m_sBSPName)); + m_sBSPName = 0; + } + if(m_pCmd) + { + g_ptr_array_free(m_pCmd, TRUE); + m_pCmd = 0; + } + } + // close everything - may be called from the outside to abort the process + void Reset(); + // start a listening loop for an external process, possibly a BSP plugin + void ExternalListen(); +}; + +CWatchBSP* g_pWatchBSP; + + // watch the BSP process through network connections + // true: trigger the BSP steps one by one and monitor them through the network + // false: create a BAT / .sh file and execute it. don't bother monitoring it. +bool g_WatchBSP_Enabled = true; + // do we stop the compilation process if we come accross a leak? +bool g_WatchBSP_LeakStop = true; +bool g_WatchBSP_RunQuake = false; + // store prefs setting for automatic sleep mode activation +bool g_WatchBSP_DoSleep = true; + // timeout when beginning a step (in seconds) + // if we don't get a connection quick enough we assume something failed and go back to idling +int g_WatchBSP_Timeout = 10; + + +void Build_constructPreferences(PreferencesPage& page) +{ + GtkWidget* monitorbsp = page.appendCheckBox("", "Enable Build Process Monitoring", g_WatchBSP_Enabled); + GtkWidget* leakstop = page.appendCheckBox("", "Stop Compilation on Leak", g_WatchBSP_LeakStop); + GtkWidget* runengine = page.appendCheckBox("", "Run Engine After Compile", g_WatchBSP_RunQuake); + GtkWidget* sleep = page.appendCheckBox("", "Sleep When Running the Engine", g_WatchBSP_DoSleep); + Widget_connectToggleDependency(leakstop, monitorbsp); + Widget_connectToggleDependency(runengine, monitorbsp); + Widget_connectToggleDependency(sleep, runengine); +} +void Build_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Build", "Build Preferences")); + Build_constructPreferences(page); +} +void Build_registerPreferencesPage() +{ + PreferencesDialog_addSettingsPage(FreeCaller1()); +} + +#include "preferencesystem.h" +#include "stringio.h" + +void BuildMonitor_Construct() +{ + g_pWatchBSP = new CWatchBSP(); + + g_WatchBSP_Enabled = !string_equal(g_pGameDescription->getKeyValue("no_bsp_monitor"), "1"); + + GlobalPreferenceSystem().registerPreference("WatchBSP", BoolImportStringCaller(g_WatchBSP_Enabled), BoolExportStringCaller(g_WatchBSP_Enabled)); + GlobalPreferenceSystem().registerPreference("RunQuake2Run", BoolImportStringCaller(g_WatchBSP_RunQuake), BoolExportStringCaller(g_WatchBSP_RunQuake)); + GlobalPreferenceSystem().registerPreference("LeakStop", BoolImportStringCaller(g_WatchBSP_LeakStop), BoolExportStringCaller(g_WatchBSP_LeakStop)); + GlobalPreferenceSystem().registerPreference("SleepMode", BoolImportStringCaller(g_WatchBSP_DoSleep), BoolExportStringCaller(g_WatchBSP_DoSleep)); + + Build_registerPreferencesPage(); +} + +void BuildMonitor_Destroy() +{ + delete g_pWatchBSP; +} + +CWatchBSP *GetWatchBSP() +{ + return g_pWatchBSP; +} + +void BuildMonitor_Run(GPtrArray* commands, const char* mapName) +{ + GetWatchBSP()->DoMonitoringLoop(commands, mapName); +} + + +// Static functions for the SAX callbacks ------------------------------------------------------- + +// utility for saxStartElement below +static void abortStream(message_info_t *data) +{ + GetWatchBSP()->EndMonitoringLoop(); + // tell there has been an error +#if 0 + if (GetWatchBSP()->HasBSPPlugin()) + g_BSPFrontendTable.m_pfnEndListen(2); +#endif + // yeah this doesn't look good.. but it's needed so that everything will be ignored until the stream goes out + data->ignore_depth = -1; + data->recurse++; +} + +#include "stream_version.h" + +static void saxStartElement(message_info_t *data, const xmlChar *name, const xmlChar **attrs) +{ +#if 0 + globalOutputStream() << "<" << name; + if(attrs != 0) + { + for(const xmlChar** p = attrs; *p != 0; p += 2) + { + globalOutputStream() << " " << p[0] << "=" << makeQuoted(p[1]); + } + } + globalOutputStream() << ">\n"; +#endif + + if (data->ignore_depth == 0) + { + if(data->pGeometry != 0) + // we have a handler + { + data->pGeometry->saxStartElement (data, name, attrs); + } + else + { + if (strcmp(reinterpret_cast(name), "q3map_feedback") == 0) + { + // check the correct version + // old q3map don't send a version attribute + // the ones we support .. send Q3MAP_STREAM_VERSION + if (!attrs[0] || !attrs[1] || (strcmp(reinterpret_cast(attrs[0]), "version") != 0)) + { + message_flush(data); + globalErrorStream() << "No stream version given in the feedback stream, this is an old q3map version.\n" + "Please turn off monitored compiling if you still wish to use this q3map executable\n"; + abortStream(data); + return; + } + else if (strcmp(reinterpret_cast(attrs[1]), Q3MAP_STREAM_VERSION) != 0) + { + message_flush(data); + globalErrorStream() << + "This version of Radiant reads version " Q3MAP_STREAM_VERSION " debug streams, I got an incoming connection with version " << reinterpret_cast(attrs[1]) << "\n" + "Please make sure your versions of Radiant and q3map are matching.\n"; + abortStream(data); + return; + } + } + // we don't treat locally + else if (strcmp(reinterpret_cast(name), "message") == 0) + { + int msg_level = atoi(reinterpret_cast(attrs[1])); + if(msg_level != data->msg_level) + { + message_flush(data); + data->msg_level = msg_level; + } + } + else if (strcmp(reinterpret_cast(name), "polyline") == 0) + // polyline has a particular status .. right now we only use it for leakfile .. + { + data->geometry_depth = data->recurse; + data->pGeometry = &g_pointfile; + data->pGeometry->saxStartElement (data, name, attrs); + } + else if (strcmp(reinterpret_cast(name), "select") == 0) + { + CSelectMsg *pSelect = new CSelectMsg(); + data->geometry_depth = data->recurse; + data->pGeometry = pSelect; + data->pGeometry->saxStartElement (data, name, attrs); + } + else if (strcmp(reinterpret_cast(name), "pointmsg") == 0) + { + CPointMsg *pPoint = new CPointMsg(); + data->geometry_depth = data->recurse; + data->pGeometry = pPoint; + data->pGeometry->saxStartElement (data, name, attrs); + } + else if (strcmp(reinterpret_cast(name), "windingmsg") == 0) + { + CWindingMsg *pWinding = new CWindingMsg(); + data->geometry_depth = data->recurse; + data->pGeometry = pWinding; + data->pGeometry->saxStartElement (data, name, attrs); + } + else + { + globalErrorStream() << "Warning: ignoring unrecognized node in XML stream (" << reinterpret_cast(name) << ")\n"; + // we don't recognize this node, jump over it + // (NOTE: the ignore mechanism is a bit screwed, only works when starting an ignore at the highest level) + data->ignore_depth = data->recurse; + } + } + } + data->recurse++; +} + +static void saxEndElement(message_info_t *data, const xmlChar *name) +{ +#if 0 + globalOutputStream() << "<" << name << "/>\n"; +#endif + + data->recurse--; + // we are out of an ignored chunk + if(data->recurse == data->ignore_depth) + { + data->ignore_depth = 0; + return; + } + if(data->pGeometry != 0) + { + data->pGeometry->saxEndElement (data, name); + // we add the object to the debug window + if(data->geometry_depth == data->recurse) + { + g_DbgDlg.Push(data->pGeometry); + data->pGeometry = 0; + } + } + if (data->recurse == data->stop_depth) + { + message_flush(data); +#ifdef _DEBUG + globalOutputStream() << "Received error msg .. shutting down..\n"; +#endif + GetWatchBSP()->EndMonitoringLoop(); + // tell there has been an error +#if 0 + if (GetWatchBSP()->HasBSPPlugin()) + g_BSPFrontendTable.m_pfnEndListen(2); +#endif + return; + } +} + +class MessageOutputStream : public TextOutputStream +{ + message_info_t* m_data; +public: + MessageOutputStream(message_info_t* data) : m_data(data) + { + } + std::size_t write(const char* buffer, std::size_t length) + { + if(m_data->pGeometry != 0) + { + m_data->pGeometry->saxCharacters(m_data, reinterpret_cast(buffer), int(length)); + } + else + { + if (m_data->ignore_depth == 0) + { + // output the message using the level + message_print(m_data, buffer, length); + // if this message has error level flag, we mark the depth to stop the compilation when we get out + // we don't set the msg level if we don't stop on leak + if (m_data->msg_level == 3) + { + m_data->stop_depth = m_data->recurse-1; + } + } + } + + return length; + } +}; + +template +inline MessageOutputStream& operator<<(MessageOutputStream& ostream, const T& t) +{ + return ostream_write(ostream, t); +} + +static void saxCharacters(message_info_t *data, const xmlChar *ch, int len) +{ + MessageOutputStream ostream(data); + ostream << ConvertUTF8ToLocale(StringRange(reinterpret_cast(ch), reinterpret_cast(ch + len))); +} + +static void saxComment(void *ctx, const xmlChar *msg) +{ + globalOutputStream() << "XML comment: " << reinterpret_cast(msg) << "\n"; +} + +static void saxWarning(void *ctx, const char *msg, ...) +{ + char saxMsgBuffer[4096]; + va_list args; + + va_start(args, msg); + vsprintf (saxMsgBuffer, msg, args); + va_end(args); + globalOutputStream() << "XML warning: " << saxMsgBuffer << "\n"; +} + +static void saxError(void *ctx, const char *msg, ...) +{ + char saxMsgBuffer[4096]; + va_list args; + + va_start(args, msg); + vsprintf (saxMsgBuffer, msg, args); + va_end(args); + globalErrorStream() << "XML error: " << saxMsgBuffer << "\n"; +} + +static void saxFatal(void *ctx, const char *msg, ...) +{ + char buffer[4096]; + + va_list args; + + va_start(args, msg); + vsprintf (buffer, msg, args); + va_end(args); + globalErrorStream() << "XML fatal error: " << buffer << "\n"; +} + +static xmlSAXHandler saxParser = { + 0, /* internalSubset */ + 0, /* isStandalone */ + 0, /* hasInternalSubset */ + 0, /* hasExternalSubset */ + 0, /* resolveEntity */ + 0, /* getEntity */ + 0, /* entityDecl */ + 0, /* notationDecl */ + 0, /* attributeDecl */ + 0, /* elementDecl */ + 0, /* unparsedEntityDecl */ + 0, /* setDocumentLocator */ + 0, /* startDocument */ + 0, /* endDocument */ + (startElementSAXFunc)saxStartElement, /* startElement */ + (endElementSAXFunc)saxEndElement, /* endElement */ + 0, /* reference */ + (charactersSAXFunc)saxCharacters, /* characters */ + 0, /* ignorableWhitespace */ + 0, /* processingInstruction */ + (commentSAXFunc)saxComment, /* comment */ + (warningSAXFunc)saxWarning, /* warning */ + (errorSAXFunc)saxError, /* error */ + (fatalErrorSAXFunc)saxFatal, /* fatalError */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 +}; + +// ------------------------------------------------------------------------------------------------ + + +guint s_routine_id; +static gint watchbsp_routine(gpointer data) +{ + reinterpret_cast(data)->RoutineProcessing(); + return TRUE; +} + +void CWatchBSP::Reset() +{ + if (m_pInSocket) + { + Net_Disconnect(m_pInSocket); + m_pInSocket = NULL; + } + if (m_pListenSocket) + { + Net_Disconnect(m_pListenSocket); + m_pListenSocket = NULL; + } + if (m_xmlInputBuffer) + { + xmlFreeParserInputBuffer (m_xmlInputBuffer); + m_xmlInputBuffer = NULL; + } + m_eState = EIdle; + if (s_routine_id) + gtk_timeout_remove(s_routine_id); +} + +bool CWatchBSP::SetupListening() +{ +#ifdef _DEBUG + if (m_pListenSocket) + { + globalOutputStream() << "ERROR: m_pListenSocket != NULL in CWatchBSP::SetupListening\n"; + return false; + } +#endif + globalOutputStream() << "Setting up\n"; + Net_Setup(); + m_pListenSocket = Net_ListenSocket(39000); + if (m_pListenSocket == NULL) + return false; + globalOutputStream() << "Listening...\n"; + return true; +} + +void CWatchBSP::DoEBeginStep() +{ + Reset(); + if (SetupListening() == false) + { + const char* msg = "Failed to get a listening socket on port 39000.\nTry running with Build monitoring disabled if you can't fix this.\n"; + globalOutputStream() << msg; + gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), msg, "Build monitoring", eMB_OK, eMB_ICONERROR); + return; + } + // set the timer for timeouts and step cancellation + g_timer_reset( m_pTimer ); + g_timer_start( m_pTimer ); + + if (!m_bBSPPlugin) + { + globalOutputStream() << "=== running build command ===\n" + << static_cast(g_ptr_array_index( m_pCmd, m_iCurrentStep )) << "\n"; + + if (!Q_Exec(NULL, (char *)g_ptr_array_index( m_pCmd, m_iCurrentStep ), NULL, true )) + { + StringOutputStream msg(256); + msg << "Failed to execute the following command: "; + msg << reinterpret_cast(g_ptr_array_index(m_pCmd, m_iCurrentStep)); + msg << "\nCheck that the file exists and that you don't run out of system resources.\n"; + globalOutputStream() << msg.c_str(); + gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), msg.c_str(), "Build monitoring", eMB_OK, eMB_ICONERROR ); + return; + } + // re-initialise the debug window + if (m_iCurrentStep == 0) + g_DbgDlg.Init(); + } + m_eState = EBeginStep; + s_routine_id = gtk_timeout_add(25, watchbsp_routine, this); +} + + +#if defined(WIN32) +#define ENGINE_ATTRIBUTE "engine_win32" +#define MP_ENGINE_ATTRIBUTE "mp_engine_win32" +#elif defined(__linux__) || defined (__FreeBSD__) +#define ENGINE_ATTRIBUTE "engine_linux" +#define MP_ENGINE_ATTRIBUTE "mp_engine_linux" +#elif defined(__APPLE__) +#define ENGINE_ATTRIBUTE "engine_macos" +#define MP_ENGINE_ATTRIBUTE "mp_engine_macos" +#else +#error "unsupported platform" +#endif + +class RunEngineConfiguration +{ +public: + const char* executable; + const char* mp_executable; + bool do_sp_mp; + + RunEngineConfiguration() : + executable(g_pGameDescription->getRequiredKeyValue(ENGINE_ATTRIBUTE)), + mp_executable(g_pGameDescription->getKeyValue(MP_ENGINE_ATTRIBUTE)) + { + do_sp_mp = !string_empty(mp_executable); + } +}; + +inline void GlobalGameDescription_string_write_mapparameter(StringOutputStream& string, const char* mapname) +{ + if(g_pGameDescription->mGameType == "q2" + || g_pGameDescription->mGameType == "heretic2") + { + string << ". +exec radiant.cfg +map " << mapname; + } + else + { + string << "+set sv_pure 0 "; + // TTimo: a check for vm_* but that's all fine + //cmdline = "+set sv_pure 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 "; + const char* fs_game = gamename_get(); + if (!string_equal(fs_game, basegame_get())) + { + string << "+set fs_game " << fs_game << " "; + } + if(g_pGameDescription->mGameType == "wolf" + || g_pGameDescription->mGameType == "et") + { + if (string_equal(gamemode_get(), "mp")) + { + // MP + string << "+devmap " << mapname; + } + else + { + // SP + string << "+set nextmap \"spdevmap " << mapname << "\""; + } + } + else + { + string << "+devmap " << mapname; + } + } +} + + +void CWatchBSP::RoutineProcessing() +{ + switch (m_eState) + { + case EBeginStep: + // timeout: if we don't get an incoming connection fast enough, go back to idle + if ( g_timer_elapsed( m_pTimer, NULL ) > g_WatchBSP_Timeout ) + { + gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), "The connection timed out, assuming the build process failed\nMake sure you are using a networked version of Q3Map?\nOtherwise you need to disable BSP Monitoring in prefs.", "BSP process monitoring", eMB_OK ); + EndMonitoringLoop(); +#if 0 + if (m_bBSPPlugin) + { + // status == 1 : didn't get the connection + g_BSPFrontendTable.m_pfnEndListen(1); + } +#endif + return; + } +#ifdef _DEBUG + // some debug checks + if (!m_pListenSocket) + { + globalErrorStream() << "ERROR: m_pListenSocket == NULL in CWatchBSP::RoutineProcessing EBeginStep state\n"; + return; + } +#endif + // we are not connected yet, accept any incoming connection + m_pInSocket = Net_Accept(m_pListenSocket); + if (m_pInSocket) + { + globalOutputStream() << "Connected.\n"; + // prepare the message info struct for diving in + memset (&m_message_info, 0, sizeof(message_info_t)); + // a dumb flag to make sure we init the push parser context when first getting a msg + m_bNeedCtxtInit = true; + m_eState = EWatching; + } + break; + case EWatching: + { +#ifdef _DEBUG + // some debug checks + if (!m_pInSocket) + { + globalErrorStream() << "ERROR: m_pInSocket == NULL in CWatchBSP::RoutineProcessing EWatching state\n"; + return; + } +#endif + + int ret = Net_Wait(m_pInSocket, 0, 0); + if (ret == -1) + { + globalOutputStream() << "WARNING: SOCKET_ERROR in CWatchBSP::RoutineProcessing\n"; + globalOutputStream() << "Terminating the connection.\n"; + EndMonitoringLoop(); + return; + } + + if (ret == 1) + { + // the socket has been identified, there's something (message or disconnection) + // see if there's anything in input + ret = Net_Receive( m_pInSocket, &msg ); + if (ret > 0) + { + // unsigned int size = msg.size; //++timo just a check + strcpy (m_xmlBuf, NMSG_ReadString (&msg)); + if (m_bNeedCtxtInit) + { + m_xmlParserCtxt = NULL; + m_xmlParserCtxt = xmlCreatePushParserCtxt (&saxParser, &m_message_info, m_xmlBuf, static_cast(strlen(m_xmlBuf)), NULL); + + if (m_xmlParserCtxt == NULL) + { + globalErrorStream() << "Failed to create the XML parser (incoming stream began with: " << m_xmlBuf << ")\n"; + EndMonitoringLoop(); + } + m_bNeedCtxtInit = false; + } + else + { + xmlParseChunk(m_xmlParserCtxt, m_xmlBuf, static_cast(strlen(m_xmlBuf)), 0); + } + } + else + { + message_flush(&m_message_info); + // error or connection closed/reset + // NOTE: if we get an error down the XML stream we don't reach here + Net_Disconnect( m_pInSocket ); + m_pInSocket = NULL; + globalOutputStream() << "Connection closed.\n"; +#if 0 + if (m_bBSPPlugin) + { + EndMonitoringLoop(); + // let the BSP plugin know that the job is done + g_BSPFrontendTable.m_pfnEndListen(0); + return; + } +#endif + // move to next step or finish + m_iCurrentStep++; + if (m_iCurrentStep < m_pCmd->len ) + { + DoEBeginStep(); + } + else + { + // launch the engine .. OMG + if (g_WatchBSP_RunQuake) + { +#if 0 + // do we enter sleep mode before? + if (g_WatchBSP_DoSleep) + { + globalOutputStream() << "Going into sleep mode..\n"; + g_pParentWnd->OnSleep(); + } +#endif + globalOutputStream() << "Running engine...\n"; + StringOutputStream cmd(256); + // build the command line + cmd << EnginePath_get(); + // this is game dependant + + RunEngineConfiguration engineConfig; + + if(engineConfig.do_sp_mp) + { + if (string_equal(gamemode_get(), "mp")) + { + cmd << engineConfig.mp_executable; + } + else + { + cmd << engineConfig.executable; + } + } + else + { + cmd << engineConfig.executable; + } + + StringOutputStream cmdline; + + GlobalGameDescription_string_write_mapparameter(cmdline, m_sBSPName); + + globalOutputStream() << cmd.c_str() << " " << cmdline.c_str() << "\n"; + + // execute now + if (!Q_Exec(cmd.c_str(), (char *)cmdline.c_str(), EnginePath_get(), false)) + { + StringOutputStream msg; + msg << "Failed to execute the following command: " << cmd.c_str() << cmdline.c_str(); + globalOutputStream() << msg.c_str(); + gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), msg.c_str(), "Build monitoring", eMB_OK, eMB_ICONERROR ); + } + } + EndMonitoringLoop(); + } + } + } + } + break; + default: + break; + } +} + +GPtrArray* str_ptr_array_clone(GPtrArray* array) +{ + GPtrArray* cloned = g_ptr_array_sized_new(array->len); + for(guint i = 0; i < array->len; ++i) + { + g_ptr_array_add(cloned, g_strdup((char*)g_ptr_array_index(array, i))); + } + return cloned; +} + +void CWatchBSP::DoMonitoringLoop( GPtrArray *pCmd, const char *sBSPName ) +{ + m_sBSPName = string_clone(sBSPName); + if (m_eState != EIdle) + { + globalOutputStream() << "WatchBSP got a monitoring request while not idling...\n"; + // prompt the user, should we cancel the current process and go ahead? + if (gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), "I am already monitoring a Build process.\nDo you want me to override and start a new compilation?", + "Build process monitoring", eMB_YESNO ) == eIDYES) + { + // disconnect and set EIdle state + Reset(); + } + } + m_pCmd = str_ptr_array_clone(pCmd); + m_iCurrentStep = 0; + DoEBeginStep(); +} + +void CWatchBSP::ExternalListen() +{ + m_bBSPPlugin = true; + DoEBeginStep(); +} + +// the part of the watchbsp interface we export to plugins +// NOTE: in the long run, the whole watchbsp.cpp interface needs to go out and be handled at the BSP plugin level +// for now we provide something really basic and limited, the essential is to have something that works fine and fast (for 1.1 final) +void QERApp_Listen() +{ + // open the listening socket + GetWatchBSP()->ExternalListen(); +} diff --git a/radiant/watchbsp.h b/radiant/watchbsp.h new file mode 100644 index 00000000..ac6a2b22 --- /dev/null +++ b/radiant/watchbsp.h @@ -0,0 +1,43 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(INCLUDED_WATCHBSP_H) +#define INCLUDED_WATCHBSP_H + +void BuildMonitor_Construct(); +void BuildMonitor_Destroy(); + +typedef struct _GPtrArray GPtrArray; +void BuildMonitor_Run(GPtrArray* commands, const char* mapName); + +extern bool g_WatchBSP_Enabled; +extern bool g_WatchBSP_LeakStop; + +#endif diff --git a/radiant/winding.cpp b/radiant/winding.cpp new file mode 100644 index 00000000..bba89501 --- /dev/null +++ b/radiant/winding.cpp @@ -0,0 +1,346 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "winding.h" + +#include + +#include "math/line.h" + + +inline double plane3_distance_to_point(const Plane3& plane, const DoubleVector3& point) +{ + return vector3_dot(point, plane.normal()) - plane.dist(); +} + +inline double plane3_distance_to_point(const Plane3& plane, const Vector3& point) +{ + return vector3_dot(point, plane.normal()) - plane.dist(); +} + +/// \brief Returns the point at which \p line intersects \p plane, or an undefined value if there is no intersection. +inline DoubleVector3 line_intersect_plane(const DoubleLine& line, const Plane3& plane) +{ + return line.origin + vector3_scaled( + line.direction, + -plane3_distance_to_point(plane, line.origin) + / vector3_dot(line.direction, plane.normal()) + ); +} + +inline bool float_is_largest_absolute(double axis, double other) +{ + return fabs(axis) > fabs(other); +} + +/// \brief Returns the index of the component of \p v that has the largest absolute value. +inline int vector3_largest_absolute_component_index(const DoubleVector3& v) +{ + return (float_is_largest_absolute(v[1], v[0])) + ? (float_is_largest_absolute(v[1], v[2])) + ? 1 + : 2 + : (float_is_largest_absolute(v[0], v[2])) + ? 0 + : 2; +} + +/// \brief Returns the infinite line that is the intersection of \p plane and \p other. +inline DoubleLine plane3_intersect_plane3(const Plane3& plane, const Plane3& other) +{ + DoubleLine line; + line.direction = vector3_cross(plane.normal(), other.normal()); + switch(vector3_largest_absolute_component_index(line.direction)) + { + case 0: + line.origin.x() = 0; + line.origin.y() = (-other.dist() * plane.normal().z() - -plane.dist() * other.normal().z()) / line.direction.x(); + line.origin.z() = (-plane.dist() * other.normal().y() - -other.dist() * plane.normal().y()) / line.direction.x(); + break; + case 1: + line.origin.x() = (-plane.dist() * other.normal().z() - -other.dist() * plane.normal().z()) / line.direction.y(); + line.origin.y() = 0; + line.origin.z() = (-other.dist() * plane.normal().x() - -plane.dist() * other.normal().x()) / line.direction.y(); + break; + case 2: + line.origin.x() = (-other.dist() * plane.normal().y() - -plane.dist() * other.normal().y()) / line.direction.z(); + line.origin.y() = (-plane.dist() * other.normal().x() - -other.dist() * plane.normal().x()) / line.direction.z(); + line.origin.z() = 0; + break; + default: + break; + } + + return line; +} + + +/// \brief Keep the value of \p infinity as small as possible to improve precision in Winding_Clip. +void Winding_createInfinite(FixedWinding& winding, const Plane3& plane, double infinity) +{ + double max = -infinity; + int x = -1; + for (int i=0 ; i<3; i++) + { + double d = fabs(plane.normal()[i]); + if (d > max) + { + x = i; + max = d; + } + } + if(x == -1) + { + globalErrorStream() << "invalid plane\n"; + return; + } + + DoubleVector3 vup = g_vector3_identity; + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + + vector3_add(vup, vector3_scaled(plane.normal(), -vector3_dot(vup, plane.normal()))); + vector3_normalise(vup); + + DoubleVector3 org = vector3_scaled(plane.normal(), plane.dist()); + + DoubleVector3 vright = vector3_cross(vup, plane.normal()); + + vector3_scale(vup, infinity); + vector3_scale(vright, infinity); + + // project a really big axis aligned box onto the plane + + DoubleLine r1, r2, r3, r4; + r1.origin = vector3_added(vector3_subtracted(org, vright), vup); + r1.direction = vector3_normalised(vright); + winding.push_back(FixedWindingVertex(r1.origin, r1, c_brush_maxFaces)); + r2.origin = vector3_added(vector3_added(org, vright), vup); + r2.direction = vector3_normalised(vector3_negated(vup)); + winding.push_back(FixedWindingVertex(r2.origin, r2, c_brush_maxFaces)); + r3.origin = vector3_subtracted(vector3_added(org, vright), vup); + r3.direction = vector3_normalised(vector3_negated(vright)); + winding.push_back(FixedWindingVertex(r3.origin, r3, c_brush_maxFaces)); + r4.origin = vector3_subtracted(vector3_subtracted(org, vright), vup); + r4.direction = vector3_normalised(vup); + winding.push_back(FixedWindingVertex(r4.origin, r4, c_brush_maxFaces)); +} + + +inline PlaneClassification Winding_ClassifyDistance(const double distance, const double epsilon) +{ + if(distance > epsilon) + { + return ePlaneFront; + } + if(distance < -epsilon) + { + return ePlaneBack; + } + return ePlaneOn; +} + +/// \brief Returns true if +/// !flipped && winding is completely BACK or ON +/// or flipped && winding is completely FRONT or ON +bool Winding_TestPlane(const Winding& winding, const Plane3& plane, bool flipped) +{ + const int test = (flipped) ? ePlaneBack : ePlaneFront; + for(Winding::const_iterator i = winding.begin(); i != winding.end(); ++i) + { + if(test == Winding_ClassifyDistance(plane3_distance_to_point(plane, (*i).vertex), ON_EPSILON)) + { + return false; + } + } + return true; +} + +/// \brief Returns true if any point in \p w1 is in front of plane2, or any point in \p w2 is in front of plane1 +bool Winding_PlanesConcave(const Winding& w1, const Winding& w2, const Plane3& plane1, const Plane3& plane2) +{ + return !Winding_TestPlane(w1, plane2, false) || !Winding_TestPlane(w2, plane1, false); +} + +brushsplit_t Winding_ClassifyPlane(const Winding& winding, const Plane3& plane) +{ + brushsplit_t split; + for(Winding::const_iterator i = winding.begin(); i != winding.end(); ++i) + { + ++split.counts[Winding_ClassifyDistance(plane3_distance_to_point(plane, (*i).vertex), ON_EPSILON)]; + } + return split; +} + + +#define DEBUG_EPSILON ON_EPSILON +const double DEBUG_EPSILON_SQUARED = DEBUG_EPSILON * DEBUG_EPSILON; + +#define WINDING_DEBUG 0 + +/// \brief Clip \p winding which lies on \p plane by \p clipPlane, resulting in \p clipped. +/// If \p winding is completely in front of the plane, \p clipped will be identical to \p winding. +/// If \p winding is completely in back of the plane, \p clipped will be empty. +/// If \p winding intersects the plane, the edge of \p clipped which lies on \p clipPlane will store the value of \p adjacent. +void Winding_Clip(const FixedWinding& winding, const Plane3& plane, const Plane3& clipPlane, std::size_t adjacent, FixedWinding& clipped) +{ + PlaneClassification classification = Winding_ClassifyDistance(plane3_distance_to_point(clipPlane, winding.back().vertex), ON_EPSILON); + PlaneClassification nextClassification; + // for each edge + for(std::size_t next = 0, i = winding.size()-1; next != winding.size(); i = next, ++next, classification = nextClassification) + { + nextClassification = Winding_ClassifyDistance(plane3_distance_to_point(clipPlane, winding[next].vertex), ON_EPSILON); + const FixedWindingVertex& vertex = winding[i]; + + // if first vertex of edge is ON + if(classification == ePlaneOn) + { + // append first vertex to output winding + if(nextClassification == ePlaneBack) + { + // this edge lies on the clip plane + clipped.push_back(FixedWindingVertex(vertex.vertex, plane3_intersect_plane3(plane, clipPlane), adjacent)); + } + else + { + clipped.push_back(vertex); + } + continue; + } + + // if first vertex of edge is FRONT + if(classification == ePlaneFront) + { + // add first vertex to output winding + clipped.push_back(vertex); + } + // if second vertex of edge is ON + if(nextClassification == ePlaneOn) + { + continue; + } + // else if second vertex of edge is same as first + else if(nextClassification == classification) + { + continue; + } + // else if first vertex of edge is FRONT and there are only two edges + else if(classification == ePlaneFront && winding.size() == 2) + { + continue; + } + // else first vertex is FRONT and second is BACK or vice versa + else + { + // append intersection point of line and plane to output winding + DoubleVector3 mid(line_intersect_plane(vertex.edge, clipPlane)); + + if(classification == ePlaneFront) + { + // this edge lies on the clip plane + clipped.push_back(FixedWindingVertex(mid, plane3_intersect_plane3(plane, clipPlane), adjacent)); + } + else + { + clipped.push_back(FixedWindingVertex(mid, vertex.edge, vertex.adjacent)); + } + } + } +} + +std::size_t Winding_FindAdjacent(const Winding& winding, std::size_t face) +{ + for(std::size_t i=0; i dist_best) + { + dist_best = dist_squared; + index_best = i; + } + } + return index_best; +} + +std::size_t Winding_Opposite(const Winding& winding, const std::size_t index) +{ + return Winding_Opposite(winding, index, Winding_next(winding, index)); +} + +/// \brief Calculate the \p centroid of the polygon defined by \p winding which lies on plane \p plane. +void Winding_Centroid(const Winding& winding, const Plane3& plane, Vector3& centroid) +{ + double area2 = 0, x_sum = 0, y_sum = 0; + const ProjectionAxis axis = projectionaxis_for_normal(plane.normal()); + const indexremap_t remap = indexremap_for_projectionaxis(axis); + for(std::size_t i = winding.numpoints-1, j = 0; j < winding.numpoints; i = j, ++j) + { + const double ai = winding[i].vertex[remap.x] * winding[j].vertex[remap.y] - winding[j].vertex[remap.x] * winding[i].vertex[remap.y]; + area2 += ai; + x_sum += (winding[j].vertex[remap.x] + winding[i].vertex[remap.x]) * ai; + y_sum += (winding[j].vertex[remap.y] + winding[i].vertex[remap.y]) * ai; + } + + centroid[remap.x] = static_cast(x_sum / (3 * area2)); + centroid[remap.y] = static_cast(y_sum / (3 * area2)); + { + Ray ray(Vector3(0, 0, 0), Vector3(0, 0, 0)); + ray.origin[remap.x] = centroid[remap.x]; + ray.origin[remap.y] = centroid[remap.y]; + ray.direction[remap.z] = 1; + centroid[remap.z] = static_cast(ray_distance_to_plane(ray, plane)); + } +} diff --git a/radiant/winding.h b/radiant/winding.h new file mode 100644 index 00000000..233a9c82 --- /dev/null +++ b/radiant/winding.h @@ -0,0 +1,322 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_WINDING_H) +#define INCLUDED_WINDING_H + +#include "debugging/debugging.h" + +#include + +#include "math/vector.h" +#include "container/array.h" + +enum ProjectionAxis +{ + eProjectionAxisX = 0, + eProjectionAxisY = 1, + eProjectionAxisZ = 2, +}; + +const float ProjectionAxisEpsilon = static_cast(0.0001); + +inline bool projectionaxis_better(float axis, float other) +{ + return fabs(axis) > fabs(other) + ProjectionAxisEpsilon; +} + +/// \brief Texture axis precedence: Z > X > Y +inline ProjectionAxis projectionaxis_for_normal(const Vector3& normal) +{ + return (projectionaxis_better(normal[eProjectionAxisY], normal[eProjectionAxisX])) + ? (projectionaxis_better(normal[eProjectionAxisY], normal[eProjectionAxisZ])) + ? eProjectionAxisY + : eProjectionAxisZ + : (projectionaxis_better(normal[eProjectionAxisX], normal[eProjectionAxisZ])) + ? eProjectionAxisX + : eProjectionAxisZ; +} + + +struct indexremap_t +{ + indexremap_t(std::size_t _x, std::size_t _y, std::size_t _z) + : x(_x), y(_y), z(_z) + { + } + std::size_t x, y, z; +}; + +inline indexremap_t indexremap_for_projectionaxis(const ProjectionAxis axis) +{ + switch(axis) + { + case eProjectionAxisX: return indexremap_t(1, 2, 0); + case eProjectionAxisY: return indexremap_t(2, 0, 1); + default: return indexremap_t(0, 1, 2); + } +} + +enum PlaneClassification +{ + ePlaneFront = 0, + ePlaneBack = 1, + ePlaneOn = 2, +}; + +#define MAX_POINTS_ON_WINDING 64 +const std::size_t c_brush_maxFaces = 1024; + + +class WindingVertex +{ +public: + Vector3 vertex; + Vector2 texcoord; + Vector3 tangent; + Vector3 bitangent; + std::size_t adjacent; +}; + + + +struct Winding +{ + typedef Array container_type; + + std::size_t numpoints; + container_type points; + + typedef container_type::iterator iterator; + typedef container_type::const_iterator const_iterator; + + Winding() : numpoints(0) + { + } + Winding(std::size_t size) : numpoints(0), points(size) + { + } + void resize(std::size_t size) + { + points.resize(size); + numpoints = 0; + } + + iterator begin() + { + return points.begin(); + } + const_iterator begin() const + { + return points.begin(); + } + iterator end() + { + return points.begin() + numpoints; + } + const_iterator end() const + { + return points.begin() + numpoints; + } + + WindingVertex& operator[](std::size_t index) + { + ASSERT_MESSAGE(index < points.size(), "winding: index out of bounds"); + return points[index]; + } + const WindingVertex& operator[](std::size_t index) const + { + ASSERT_MESSAGE(index < points.size(), "winding: index out of bounds"); + return points[index]; + } + + void push_back(const WindingVertex& point) + { + points[numpoints] = point; + ++numpoints; + } + void erase(iterator point) + { + for(iterator i = point + 1; i != end(); point = i, ++i) + { + *point = *i; + } + --numpoints; + } +}; + +typedef BasicVector3 DoubleVector3; + +class DoubleLine +{ +public: + DoubleVector3 origin; + DoubleVector3 direction; +}; + +class FixedWindingVertex +{ +public: + DoubleVector3 vertex; + DoubleLine edge; + std::size_t adjacent; + + FixedWindingVertex(const DoubleVector3& vertex_, const DoubleLine& edge_, std::size_t adjacent_) + : vertex(vertex_), edge(edge_), adjacent(adjacent_) + { + } +}; + +struct FixedWinding +{ + typedef std::vector Points; + Points points; + + FixedWinding() + { + points.reserve(MAX_POINTS_ON_WINDING); + } + + FixedWindingVertex& front() + { + return points.front(); + } + const FixedWindingVertex& front() const + { + return points.front(); + } + FixedWindingVertex& back() + { + return points.back(); + } + const FixedWindingVertex& back() const + { + return points.back(); + } + + void clear() + { + points.clear(); + } + + void push_back(const FixedWindingVertex& point) + { + points.push_back(point); + } + std::size_t size() const + { + return points.size(); + } + + FixedWindingVertex& operator[](std::size_t index) + { + //ASSERT_MESSAGE(index < MAX_POINTS_ON_WINDING, "winding: index out of bounds"); + return points[index]; + } + const FixedWindingVertex& operator[](std::size_t index) const + { + //ASSERT_MESSAGE(index < MAX_POINTS_ON_WINDING, "winding: index out of bounds"); + return points[index]; + } + +}; + + +inline void Winding_forFixedWinding(Winding& winding, const FixedWinding& fixed) +{ + winding.resize(fixed.size()); + winding.numpoints = fixed.size(); + for(std::size_t i = 0; i < fixed.size(); ++i) + { + winding[i].vertex[0] = static_cast(fixed[i].vertex[0]); + winding[i].vertex[1] = static_cast(fixed[i].vertex[1]); + winding[i].vertex[2] = static_cast(fixed[i].vertex[2]); + winding[i].adjacent = fixed[i].adjacent; + } +} + +inline std::size_t Winding_wrap(const Winding& winding, std::size_t i) +{ + ASSERT_MESSAGE(winding.numpoints != 0, "Winding_wrap: empty winding"); + return i % winding.numpoints; +} + +inline std::size_t Winding_next(const Winding& winding, std::size_t i) +{ + return Winding_wrap(winding, ++i); +} + + +class Plane3; + +void Winding_createInfinite(FixedWinding& w, const Plane3& plane, double infinity); + +const double ON_EPSILON = 1.0 / (1 << 8); + +/// \brief Returns true if edge (\p x, \p y) is smaller than the epsilon used to classify winding points against a plane. +inline bool Edge_isDegenerate(const Vector3& x, const Vector3& y) +{ + return vector3_length_squared(y - x) < (ON_EPSILON * ON_EPSILON); +} + +void Winding_Clip(const FixedWinding& winding, const Plane3& plane, const Plane3& clipPlane, std::size_t adjacent, FixedWinding& clipped); + +struct brushsplit_t +{ + brushsplit_t() + { + counts[0] = 0; + counts[1] = 0; + counts[2] = 0; + } + brushsplit_t& operator+=(const brushsplit_t& other) + { + counts[0] += other.counts[0]; + counts[1] += other.counts[1]; + counts[2] += other.counts[2]; + return *this; + } + std::size_t counts[3]; +}; + +brushsplit_t Winding_ClassifyPlane(const Winding& w, const Plane3& plane); + +bool Winding_PlanesConcave(const Winding& w1, const Winding& w2, const Plane3& plane1, const Plane3& plane2); +bool Winding_TestPlane(const Winding& w, const Plane3& plane, bool flipped); + +std::size_t Winding_FindAdjacent(const Winding& w, std::size_t face); + +std::size_t Winding_Opposite(const Winding& w, const std::size_t index, const std::size_t other); +std::size_t Winding_Opposite(const Winding& w, std::size_t index); + +void Winding_Centroid(const Winding& w, const Plane3& plane, Vector3& centroid); + + +inline void Winding_printConnectivity(Winding& winding) +{ + for(Winding::iterator i = winding.begin(); i != winding.end(); ++i) + { + std::size_t vertexIndex = std::distance(winding.begin(), i); + globalOutputStream() << "vertex: " << Unsigned(vertexIndex) << " adjacent: " << Unsigned((*i).adjacent) << "\n"; + } +} + +#endif diff --git a/radiant/windowobservers.cpp b/radiant/windowobservers.cpp new file mode 100644 index 00000000..481f6998 --- /dev/null +++ b/radiant/windowobservers.cpp @@ -0,0 +1,170 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "windowobservers.h" + +#include +#include +#include "generic/bitfield.h" + +namespace +{ + ModifierFlags g_modifier_state = c_modifierNone; +} + +typedef std::vector WindowObservers; + +inline void WindowObservers_OnModifierDown(WindowObservers& observers, ModifierFlags type) +{ + g_modifier_state = bitfield_enable(g_modifier_state, type); + for(WindowObservers::iterator i = observers.begin(); i != observers.end(); ++i) + { + (*i)->onModifierDown(type); + } +} + +inline void WindowObservers_OnModifierUp(WindowObservers& observers, ModifierFlags type) +{ + g_modifier_state = bitfield_disable(g_modifier_state, type); + for(WindowObservers::iterator i = observers.begin(); i != observers.end(); ++i) + { + (*i)->onModifierUp(type); + } +} + +#include + +gboolean selection_modifier_key_press(GtkWidget* widget, GdkEventKey* event, WindowObservers& observers) +{ + switch(event->keyval) + { + case GDK_Alt_L: + case GDK_Alt_R: + //globalOutputStream() << "Alt PRESSED\n"; + WindowObservers_OnModifierDown(observers, c_modifierAlt); + break; + case GDK_Shift_L: + case GDK_Shift_R: + //globalOutputStream() << "Shift PRESSED\n"; + WindowObservers_OnModifierDown(observers, c_modifierShift); + break; + case GDK_Control_L: + case GDK_Control_R: + //globalOutputStream() << "Control PRESSED\n"; + WindowObservers_OnModifierDown(observers, c_modifierControl); + break; + } + return FALSE; +} + +gboolean selection_modifier_key_release(GtkWidget* widget, GdkEventKey* event, WindowObservers& observers) +{ + switch(event->keyval) + { + case GDK_Alt_L: + case GDK_Alt_R: + //globalOutputStream() << "Alt RELEASED\n"; + WindowObservers_OnModifierUp(observers, c_modifierAlt); + break; + case GDK_Shift_L: + case GDK_Shift_R: + //globalOutputStream() << "Shift RELEASED\n"; + WindowObservers_OnModifierUp(observers, c_modifierShift); + break; + case GDK_Control_L: + case GDK_Control_R: + //globalOutputStream() << "Control RELEASED\n"; + WindowObservers_OnModifierUp(observers, c_modifierControl); + break; + } + return FALSE; +} + +void WindowObservers_UpdateModifier(WindowObservers& observers, ModifierFlags modifiers, ModifierFlags modifier) +{ + if(!bitfield_enabled(g_modifier_state, modifier) && bitfield_enabled(modifiers, modifier)) + { + WindowObservers_OnModifierDown(observers, modifier); + } + if(bitfield_enabled(g_modifier_state, modifier) && !bitfield_enabled(modifiers, modifier)) + { + WindowObservers_OnModifierUp(observers, modifier); + } +} + +void WindowObservers_UpdateModifiers(WindowObservers& observers, ModifierFlags modifiers) +{ + WindowObservers_UpdateModifier(observers, modifiers, c_modifierAlt); + WindowObservers_UpdateModifier(observers, modifiers, c_modifierShift); + WindowObservers_UpdateModifier(observers, modifiers, c_modifierControl); +} + +gboolean modifiers_button_press(GtkWidget* widget, GdkEventButton* event, WindowObservers* observers) +{ + if(event->type == GDK_BUTTON_PRESS) + { + WindowObservers_UpdateModifiers(*observers, modifiers_for_state(event->state)); + } + return FALSE; +} + +gboolean modifiers_button_release(GtkWidget* widget, GdkEventButton* event, WindowObservers* observers) +{ + if(event->type == GDK_BUTTON_RELEASE) + { + WindowObservers_UpdateModifiers(*observers, modifiers_for_state(event->state)); + } + return FALSE; +} + +gboolean modifiers_motion(GtkWidget *widget, GdkEventMotion *event, WindowObservers* observers) +{ + WindowObservers_UpdateModifiers(*observers, modifiers_for_state(event->state)); + return FALSE; +} + + +WindowObservers g_window_observers; + +void GlobalWindowObservers_updateModifiers(ModifierFlags modifiers) +{ + WindowObservers_UpdateModifiers(g_window_observers, modifiers); +} + +void GlobalWindowObservers_add(WindowObserver* observer) +{ + g_window_observers.push_back(observer); +} + +void GlobalWindowObservers_connectTopLevel(GtkWindow* window) +{ + g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(selection_modifier_key_press), &g_window_observers); + g_signal_connect(G_OBJECT(window), "key_release_event", G_CALLBACK(selection_modifier_key_release), &g_window_observers); +} + +void GlobalWindowObservers_connectWidget(GtkWidget* widget) +{ + g_signal_connect(G_OBJECT(widget), "button_press_event", G_CALLBACK(modifiers_button_press), &g_window_observers); + g_signal_connect(G_OBJECT(widget), "button_release_event", G_CALLBACK(modifiers_button_release), &g_window_observers); + g_signal_connect(G_OBJECT(widget), "motion_notify_event", G_CALLBACK(modifiers_motion), &g_window_observers); +} + + diff --git a/radiant/windowobservers.h b/radiant/windowobservers.h new file mode 100644 index 00000000..7a47a936 --- /dev/null +++ b/radiant/windowobservers.h @@ -0,0 +1,69 @@ +/* +Copyright (C) 2001-2006, William Joseph. +All Rights Reserved. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_WINDOWOBSERVERS_H) +#define INCLUDED_WINDOWOBSERVERS_H + +#include "windowobserver.h" + +#include + +#include "math/vector.h" + +class WindowObserver; +void GlobalWindowObservers_add(WindowObserver* observer); +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkWindow GtkWindow; +void GlobalWindowObservers_connectWidget(GtkWidget* widget); +void GlobalWindowObservers_connectTopLevel(GtkWindow* window); + +inline ButtonIdentifier button_for_button(unsigned int button) +{ + switch(button) + { + case 1: + return c_buttonLeft; + case 2: + return c_buttonMiddle; + case 3: + return c_buttonRight; + } + return c_buttonInvalid; +} + +inline ModifierFlags modifiers_for_state(unsigned int state) +{ + ModifierFlags modifiers = c_modifierNone; + if(state & GDK_SHIFT_MASK) + modifiers |= c_modifierShift; + if(state & GDK_CONTROL_MASK) + modifiers |= c_modifierControl; + if(state & GDK_MOD1_MASK) + modifiers |= c_modifierAlt; + return modifiers; +} + +inline WindowVector WindowVector_forDouble(double x, double y) +{ + return WindowVector(static_cast(x), static_cast(y)); +} + +#endif diff --git a/radiant/xmlstuff.cpp b/radiant/xmlstuff.cpp new file mode 100644 index 00000000..a92a1683 --- /dev/null +++ b/radiant/xmlstuff.cpp @@ -0,0 +1,32 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "xmlstuff.h" + diff --git a/radiant/xmlstuff.h b/radiant/xmlstuff.h new file mode 100644 index 00000000..0ffb8e2b --- /dev/null +++ b/radiant/xmlstuff.h @@ -0,0 +1,90 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +//----------------------------------------------------------------------------- +// +// DESCRIPTION: +// header for xml stuff used in radiant +// + +#ifndef __XMLSTUFF__ +#define __XMLSTUFF__ + +#include "libxml/parser.h" + +#include + +class ISAXHandler; + +// a 'user data' structure we pass along in the SAX callbacks to represent the current state +// the recurse value tracks the current depth in the tree +// message_info also stores information to exit the stream listening cleanly with an error: +// if msg_level == SYS_ERR, then we will reset the listening at the end of the current node +// the level for stopping the feed is stored in stop_depth +// unkown nodes are ignored, we use ignore_depth to track the level we start ignoring from +struct message_info_t +{ + int msg_level; // current message level (SYS_MSG, SYS_WRN, SYS_ERR) + int recurse; // current recursion depth (used to track various things) + int ignore_depth; // the ignore depth limit when we are jumping over unknown nodes (0 means we are not ignoring) + int stop_depth; // the depth we need to stop at the end + int geometry_depth; // are we parsing some geometry information (i.e. do we forward the SAX calls?) + ISAXHandler* pGeometry; // the handler + + enum unnamed0 { bufsize = 1024 }; + char m_buffer[bufsize]; + std::size_t m_length; +}; + +class IGL2DWindow; + +class ISAXHandler +{ +public: + virtual void Release() + { + } + virtual void saxStartElement(message_info_t* ctx, const xmlChar* name, const xmlChar** attrs) = 0; + virtual void saxEndElement(message_info_t* ctx, const xmlChar* name) = 0; + virtual void saxCharacters(message_info_t* ctx, const xmlChar* ch, int len) = 0; + virtual const char* getName() + { + return NULL; + } + virtual IGL2DWindow* Highlight() + { + return 0; + } + virtual void DropHighlight() + { + } +}; + +#endif diff --git a/radiant/xywindow.cpp b/radiant/xywindow.cpp new file mode 100644 index 00000000..dfc0c25b --- /dev/null +++ b/radiant/xywindow.cpp @@ -0,0 +1,2954 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// XY Window +// +// Leonardo Zide (leo@lokigames.com) +// + +#include "xywindow.h" + +#include "debugging/debugging.h" + +#include "ientity.h" +#include "igl.h" +#include "ibrush.h" +#include "iundo.h" +#include "iimage.h" +#include "ifilesystem.h" +#include "os/path.h" +#include "image.h" +#include "gtkutil/messagebox.h" + +#include +#include + +#include "generic/callback.h" +#include "string/string.h" +#include "stream/stringstream.h" + +#include "scenelib.h" +#include "eclasslib.h" +#include "renderer.h" +#include "moduleobserver.h" + +#include "gtkutil/menu.h" +#include "gtkutil/container.h" +#include "gtkutil/widget.h" +#include "gtkutil/glwidget.h" +#include "gtkutil/filechooser.h" +#include "gtkmisc.h" +#include "select.h" +#include "csg.h" +#include "brushmanip.h" +#include "selection.h" +#include "entity.h" +#include "camwindow.h" +#include "texwindow.h" +#include "mainframe.h" +#include "preferences.h" +#include "commands.h" +#include "feedback.h" +#include "grid.h" +#include "windowobservers.h" + +void LoadTextureRGBA(qtexture_t* q, unsigned char* pPixels, int nWidth, int nHeight); + +// d1223m +extern bool g_brush_always_caulk; + +//!\todo Rewrite. +class ClipPoint +{ +public: + Vector3 m_ptClip; // the 3d point + bool m_bSet; + + ClipPoint() + { + Reset(); + }; + void Reset() + { + m_ptClip[0] = m_ptClip[1] = m_ptClip[2] = 0.0; + m_bSet = false; + } + bool Set() + { + return m_bSet; + } + void Set(bool b) + { + m_bSet = b; + } + operator Vector3&() + { + return m_ptClip; + }; + + /*! Draw clip/path point with rasterized number label */ + void Draw(int num, float scale); + /*! Draw clip/path point with rasterized string label */ + void Draw(const char *label, float scale); +}; + +VIEWTYPE g_clip_viewtype; +bool g_bSwitch = true; +bool g_clip_useCaulk = false; +ClipPoint g_Clip1; +ClipPoint g_Clip2; +ClipPoint g_Clip3; +ClipPoint* g_pMovingClip = 0; + +/* Drawing clip points */ +void ClipPoint::Draw(int num, float scale) +{ + StringOutputStream label(4); + label << num; + Draw(label.c_str(), scale); +} + +void ClipPoint::Draw(const char *label, float scale) +{ + // draw point + glPointSize (4); + glColor3fv(vector3_to_array(g_xywindow_globals.color_clipper)); + glBegin (GL_POINTS); + glVertex3fv(vector3_to_array(m_ptClip)); + glEnd(); + glPointSize (1); + + float offset = 2.0f / scale; + + // draw label + glRasterPos3f (m_ptClip[0] + offset, m_ptClip[1] + offset, m_ptClip[2] + offset); + glCallLists (GLsizei(strlen(label)), GL_UNSIGNED_BYTE, label); +} + +float fDiff(float f1, float f2) +{ + if (f1 > f2) + return f1 - f2; + else + return f2 - f1; +} + +inline double ClipPoint_Intersect(const ClipPoint& clip, const Vector3& point, VIEWTYPE viewtype, float scale) +{ + int nDim1 = (viewtype == YZ) ? 1 : 0; + int nDim2 = (viewtype == XY) ? 1 : 2; + double screenDistanceSquared(vector2_length_squared(Vector2(fDiff(clip.m_ptClip[nDim1], point[nDim1]) * scale, fDiff(clip.m_ptClip[nDim2], point[nDim2]) * scale))); + if(screenDistanceSquared < 8*8) + { + return screenDistanceSquared; + } + return FLT_MAX; +} + +inline void ClipPoint_testSelect(ClipPoint& clip, const Vector3& point, VIEWTYPE viewtype, float scale, double& bestDistance, ClipPoint*& bestClip) +{ + if(clip.Set()) + { + double distance = ClipPoint_Intersect(clip, point, viewtype, scale); + if(distance < bestDistance) + { + bestDistance = distance; + bestClip = &clip; + } + } +} + +inline ClipPoint* GlobalClipPoints_Find(const Vector3& point, VIEWTYPE viewtype, float scale) +{ + double bestDistance = FLT_MAX; + ClipPoint* bestClip = 0; + ClipPoint_testSelect(g_Clip1, point, viewtype, scale, bestDistance, bestClip); + ClipPoint_testSelect(g_Clip2, point, viewtype, scale, bestDistance, bestClip); + ClipPoint_testSelect(g_Clip3, point, viewtype, scale, bestDistance, bestClip); + return bestClip; +} + +inline void GlobalClipPoints_Draw(float scale) +{ + // Draw clip points + if (g_Clip1.Set()) + g_Clip1.Draw(1, scale); + if (g_Clip2.Set()) + g_Clip2.Draw(2, scale); + if (g_Clip3.Set()) + g_Clip3.Draw(3, scale); +} + +inline bool GlobalClipPoints_valid() +{ + return g_Clip1.Set() && g_Clip2.Set(); +} + +void PlanePointsFromClipPoints(Vector3 planepts[3], const AABB& bounds, int viewtype) +{ + ASSERT_MESSAGE(GlobalClipPoints_valid(), "clipper points not initialised"); + planepts[0] = g_Clip1.m_ptClip; + planepts[1] = g_Clip2.m_ptClip; + planepts[2] = g_Clip3.m_ptClip; + Vector3 maxs(vector3_added(bounds.origin, bounds.extents)); + Vector3 mins(vector3_subtracted(bounds.origin, bounds.extents)); + if(!g_Clip3.Set()) + { + int n = (viewtype == XY) ? 2 : (viewtype == YZ) ? 0 : 1; + int x = (n == 0) ? 1 : 0; + int y = (n == 2) ? 1 : 2; + + if (n == 1) // on viewtype XZ, flip clip points + { + planepts[0][n] = maxs[n]; + planepts[1][n] = maxs[n]; + planepts[2][x] = g_Clip1.m_ptClip[x]; + planepts[2][y] = g_Clip1.m_ptClip[y]; + planepts[2][n] = mins[n]; + } + else + { + planepts[0][n] = mins[n]; + planepts[1][n] = mins[n]; + planepts[2][x] = g_Clip1.m_ptClip[x]; + planepts[2][y] = g_Clip1.m_ptClip[y]; + planepts[2][n] = maxs[n]; + } + } +} + +void Clip_Update() +{ + Vector3 planepts[3]; + if(!GlobalClipPoints_valid()) + { + planepts[0] = Vector3(0, 0, 0); + planepts[1] = Vector3(0, 0, 0); + planepts[2] = Vector3(0, 0, 0); + Scene_BrushSetClipPlane(GlobalSceneGraph(), Plane3(0, 0, 0, 0)); + } + else + { + AABB bounds(Vector3(0, 0, 0), Vector3(64, 64, 64)); + PlanePointsFromClipPoints(planepts, bounds, g_clip_viewtype); + if(g_bSwitch) + { + std::swap(planepts[0], planepts[1]); + } + Scene_BrushSetClipPlane(GlobalSceneGraph(), plane3_for_points(planepts[0], planepts[1], planepts[2])); + } + ClipperChangeNotify(); +} + +const char* Clip_getShader() +{ + return g_clip_useCaulk ? "textures/common/caulk" : TextureBrowser_GetSelectedShader(GlobalTextureBrowser()); +} + +void Clip() +{ + if (ClipMode() && GlobalClipPoints_valid()) + { + Vector3 planepts[3]; + AABB bounds(Vector3(0, 0, 0), Vector3(64, 64, 64)); + PlanePointsFromClipPoints(planepts, bounds, g_clip_viewtype); + Scene_BrushSplitByPlane(GlobalSceneGraph(), planepts[0], planepts[1], planepts[2], Clip_getShader(), (!g_bSwitch) ? eFront : eBack); + g_Clip1.Reset(); + g_Clip2.Reset(); + g_Clip3.Reset(); + Clip_Update(); + ClipperChangeNotify(); + } +} + +void SplitClip() +{ + if (ClipMode() && GlobalClipPoints_valid()) + { + Vector3 planepts[3]; + AABB bounds(Vector3(0, 0, 0), Vector3(64, 64, 64)); + PlanePointsFromClipPoints(planepts, bounds, g_clip_viewtype); + Scene_BrushSplitByPlane(GlobalSceneGraph(), planepts[0], planepts[1], planepts[2], Clip_getShader(), eFrontAndBack); + g_Clip1.Reset(); + g_Clip2.Reset(); + g_Clip3.Reset(); + Clip_Update(); + ClipperChangeNotify(); + } +} + +void FlipClip() +{ + g_bSwitch = !g_bSwitch; + Clip_Update(); + ClipperChangeNotify(); +} + +void OnClipMode(bool enabled) +{ + g_Clip1.Reset(); + g_Clip2.Reset(); + g_Clip3.Reset(); + + if(!enabled && g_pMovingClip) + { + g_pMovingClip = 0; + } + + Clip_Update(); + ClipperChangeNotify(); +} + +bool ClipMode() +{ + return GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eClip; +} + +void NewClipPoint(const Vector3& point) +{ + if (g_Clip1.Set() == false) + { + g_Clip1.m_ptClip = point; + g_Clip1.Set(true); + } + else if (g_Clip2.Set() == false) + { + g_Clip2.m_ptClip = point; + g_Clip2.Set(true); + } + else if (g_Clip3.Set() == false) + { + g_Clip3.m_ptClip = point; + g_Clip3.Set(true); + } + else + { + g_Clip1.Reset(); + g_Clip2.Reset(); + g_Clip3.Reset(); + g_Clip1.m_ptClip = point; + g_Clip1.Set(true); + } + + Clip_Update(); + ClipperChangeNotify(); +} + + + +struct xywindow_globals_private_t +{ + bool d_showgrid; + + // these are in the View > Show menu with Show coordinates + bool show_names; + bool show_coordinates; + bool show_angles; + bool show_outline; + bool show_axis; + + bool d_show_work; + + bool show_blocks; + int blockSize; + + bool m_bCamXYUpdate; + bool m_bChaseMouse; + bool m_bSizePaint; + + xywindow_globals_private_t() : + d_showgrid(true), + + show_names(false), + show_coordinates(true), + show_angles(true), + show_outline(false), + show_axis(true), + + d_show_work(false), + + show_blocks(false), + + m_bCamXYUpdate(true), + m_bChaseMouse(true), + m_bSizePaint(false) + { + } + +}; + +xywindow_globals_t g_xywindow_globals; +xywindow_globals_private_t g_xywindow_globals_private; + +const unsigned int RAD_NONE = 0x00; +const unsigned int RAD_SHIFT = 0x01; +const unsigned int RAD_ALT = 0x02; +const unsigned int RAD_CONTROL = 0x04; +const unsigned int RAD_PRESS = 0x08; +const unsigned int RAD_LBUTTON = 0x10; +const unsigned int RAD_MBUTTON = 0x20; +const unsigned int RAD_RBUTTON = 0x40; + +inline ButtonIdentifier button_for_flags(unsigned int flags) +{ + if(flags & RAD_LBUTTON) + return c_buttonLeft; + if(flags & RAD_RBUTTON) + return c_buttonRight; + if(flags & RAD_MBUTTON) + return c_buttonMiddle; + return c_buttonInvalid; +} + +inline ModifierFlags modifiers_for_flags(unsigned int flags) +{ + ModifierFlags modifiers = c_modifierNone; + if(flags & RAD_SHIFT) + modifiers |= c_modifierShift; + if(flags & RAD_CONTROL) + modifiers |= c_modifierControl; + if(flags & RAD_ALT) + modifiers |= c_modifierAlt; + return modifiers; +} + +inline unsigned int buttons_for_button_and_modifiers(ButtonIdentifier button, ModifierFlags flags) +{ + unsigned int buttons = 0; + + switch (button.get()) + { + case ButtonEnumeration::LEFT: buttons |= RAD_LBUTTON; break; + case ButtonEnumeration::MIDDLE: buttons |= RAD_MBUTTON; break; + case ButtonEnumeration::RIGHT: buttons |= RAD_RBUTTON; break; + } + + if(bitfield_enabled(flags, c_modifierControl)) + buttons |= RAD_CONTROL; + + if(bitfield_enabled(flags, c_modifierShift)) + buttons |= RAD_SHIFT; + + if(bitfield_enabled(flags, c_modifierAlt)) + buttons |= RAD_ALT; + + return buttons; +} + +inline unsigned int buttons_for_event_button(GdkEventButton* event) +{ + unsigned int flags = 0; + + switch (event->button) + { + case 1: flags |= RAD_LBUTTON; break; + case 2: flags |= RAD_MBUTTON; break; + case 3: flags |= RAD_RBUTTON; break; + } + + if ((event->state & GDK_CONTROL_MASK) != 0) + flags |= RAD_CONTROL; + + if ((event->state & GDK_SHIFT_MASK) != 0) + flags |= RAD_SHIFT; + + if((event->state & GDK_MOD1_MASK) != 0) + flags |= RAD_ALT; + + return flags; +} + +inline unsigned int buttons_for_state(guint state) +{ + unsigned int flags = 0; + + if ((state & GDK_BUTTON1_MASK) != 0) + flags |= RAD_LBUTTON; + + if ((state & GDK_BUTTON2_MASK) != 0) + flags |= RAD_MBUTTON; + + if ((state & GDK_BUTTON3_MASK) != 0) + flags |= RAD_RBUTTON; + + if ((state & GDK_CONTROL_MASK) != 0) + flags |= RAD_CONTROL; + + if ((state & GDK_SHIFT_MASK) != 0) + flags |= RAD_SHIFT; + + if ((state & GDK_MOD1_MASK) != 0) + flags |= RAD_ALT; + + return flags; +} + + +void XYWnd::SetScale(float f) +{ + m_fScale = f; + updateProjection(); + updateModelview(); + XYWnd_Update(*this); +} + +void XYWnd_ZoomIn(XYWnd* xy) +{ + float max_scale = 64; + float scale = xy->Scale() * 5.0f / 4.0f; + if(scale > max_scale) + { + if(xy->Scale() != max_scale) + { + xy->SetScale (max_scale); + } + } + else + { + xy->SetScale(scale); + } +} + + +// NOTE: the zoom out factor is 4/5, we could think about customizing it +// we don't go below a zoom factor corresponding to 10% of the max world size +// (this has to be computed against the window size) +void XYWnd_ZoomOut(XYWnd* xy) +{ + float min_scale = MIN(xy->Width(),xy->Height()) / ( 1.1f * (g_MaxWorldCoord-g_MinWorldCoord)); + float scale = xy->Scale() * 4.0f / 5.0f; + if(scale < min_scale) + { + if(xy->Scale() != min_scale) + { + xy->SetScale (min_scale); + } + } + else + { + xy->SetScale(scale); + } +} + +VIEWTYPE GlobalXYWnd_getCurrentViewType() +{ + ASSERT_NOTNULL(g_pParentWnd); + ASSERT_NOTNULL(g_pParentWnd->ActiveXY()); + return g_pParentWnd->ActiveXY()->GetViewType(); +} + +// ============================================================================= +// variables + +bool g_bCrossHairs = false; + +GtkMenu* XYWnd::m_mnuDrop = 0; + +// this is disabled, and broken +// http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=394 +#if 0 +void WXY_Print() +{ + long width, height; + width = g_pParentWnd->ActiveXY()->Width(); + height = g_pParentWnd->ActiveXY()->Height(); + unsigned char* img; + const char* filename; + + filename = file_dialog(GTK_WIDGET(MainFrame_getWindow()), FALSE, "Save Image", 0, FILTER_BMP); + if (!filename) + return; + + g_pParentWnd->ActiveXY()->MakeCurrent(); + img = (unsigned char*)malloc (width*height*3); + glReadPixels (0,0,width,height,GL_RGB,GL_UNSIGNED_BYTE,img); + + FILE *fp; + fp = fopen(filename, "wb"); + if (fp) + { + unsigned short bits; + unsigned long cmap, bfSize; + + bits = 24; + cmap = 0; + bfSize = 54 + width*height*3; + + long byteswritten = 0; + long pixoff = 54 + cmap*4; + short res = 0; + char m1 ='B', m2 ='M'; + fwrite(&m1, 1, 1, fp); byteswritten++; // B + fwrite(&m2, 1, 1, fp); byteswritten++; // M + fwrite(&bfSize, 4, 1, fp); byteswritten+=4;// bfSize + fwrite(&res, 2, 1, fp); byteswritten+=2;// bfReserved1 + fwrite(&res, 2, 1, fp); byteswritten+=2;// bfReserved2 + fwrite(&pixoff, 4, 1, fp); byteswritten+=4;// bfOffBits + + unsigned long biSize = 40, compress = 0, size = 0; + long pixels = 0; + unsigned short planes = 1; + fwrite(&biSize, 4, 1, fp); byteswritten+=4;// biSize + fwrite(&width, 4, 1, fp); byteswritten+=4;// biWidth + fwrite(&height, 4, 1, fp); byteswritten+=4;// biHeight + fwrite(&planes, 2, 1, fp); byteswritten+=2;// biPlanes + fwrite(&bits, 2, 1, fp); byteswritten+=2;// biBitCount + fwrite(&compress, 4, 1, fp);byteswritten+=4;// biCompression + fwrite(&size, 4, 1, fp); byteswritten+=4;// biSizeImage + fwrite(&pixels, 4, 1, fp); byteswritten+=4;// biXPelsPerMeter + fwrite(&pixels, 4, 1, fp); byteswritten+=4;// biYPelsPerMeter + fwrite(&cmap, 4, 1, fp); byteswritten+=4;// biClrUsed + fwrite(&cmap, 4, 1, fp); byteswritten+=4;// biClrImportant + + unsigned long widthDW = (((width*24) + 31) / 32 * 4); + long row, row_size = width*3; + for (row = 0; row < height; row++) + { + unsigned char* buf = img+row*row_size; + + // write a row + int col; + for (col = 0; col < row_size; col += 3) + { + putc(buf[col+2], fp); + putc(buf[col+1], fp); + putc(buf[col], fp); + } + byteswritten += row_size; + + unsigned long count; + for (count = row_size; count < widthDW; count++) + { + putc(0, fp); // dummy + byteswritten++; + } + } + + fclose(fp); + } + + free (img); +} +#endif + + +#include "timer.h" + +Timer g_chasemouse_timer; + +void XYWnd::ChaseMouse() +{ + float multiplier = g_chasemouse_timer.elapsed_msec() / 10.0f; + Scroll(float_to_integer(multiplier * m_chasemouse_delta_x), float_to_integer(multiplier * -m_chasemouse_delta_y)); + + //globalOutputStream() << "chasemouse: multiplier=" << multiplier << " x=" << m_chasemouse_delta_x << " y=" << m_chasemouse_delta_y << '\n'; + + XY_MouseMoved(m_chasemouse_current_x, m_chasemouse_current_y , getButtonState()); + g_chasemouse_timer.start(); +} + +gboolean xywnd_chasemouse(gpointer data) +{ + reinterpret_cast(data)->ChaseMouse(); + return TRUE; +} + +inline const int& min_int(const int& left, const int& right) +{ + return std::min(left, right); +} + +bool XYWnd::chaseMouseMotion(int pointx, int pointy) +{ + m_chasemouse_delta_x = 0; + m_chasemouse_delta_y = 0; + + if (g_xywindow_globals_private.m_bChaseMouse && getButtonState() == RAD_LBUTTON) + { + const int epsilon = 16; + + if (pointx < epsilon) + { + m_chasemouse_delta_x = std::max(pointx, 0) - epsilon; + } + else if ((pointx - m_nWidth) > -epsilon) + { + m_chasemouse_delta_x = min_int((pointx - m_nWidth), 0) + epsilon; + } + + if (pointy < epsilon) + { + m_chasemouse_delta_y = std::max(pointy, 0) - epsilon; + } + else if ((pointy - m_nHeight) > -epsilon) + { + m_chasemouse_delta_y = min_int((pointy - m_nHeight), 0) + epsilon; + } + + if(m_chasemouse_delta_y != 0 || m_chasemouse_delta_x != 0) + { + //globalOutputStream() << "chasemouse motion: x=" << pointx << " y=" << pointy << "... "; + m_chasemouse_current_x = pointx; + m_chasemouse_current_y = pointy; + if(m_chasemouse_handler == 0) + { + //globalOutputStream() << "chasemouse timer start... "; + g_chasemouse_timer.start(); + m_chasemouse_handler = g_idle_add(xywnd_chasemouse, this); + } + return true; + } + else + { + if(m_chasemouse_handler != 0) + { + //globalOutputStream() << "chasemouse cancel\n"; + g_source_remove(m_chasemouse_handler); + m_chasemouse_handler = 0; + } + } + } + else + { + if(m_chasemouse_handler != 0) + { + //globalOutputStream() << "chasemouse cancel\n"; + g_source_remove(m_chasemouse_handler); + m_chasemouse_handler = 0; + } + } + return false; +} + +// ============================================================================= +// XYWnd class +Shader* XYWnd::m_state_selected = 0; + +void xy_update_xor_rectangle(XYWnd& self, rect_t area) +{ + if(GTK_WIDGET_VISIBLE(self.GetWidget())) + { + self.m_XORRectangle.set(rectangle_from_area(area.min, area.max, self.Width(), self.Height())); + } +} + +gboolean xywnd_button_press(GtkWidget* widget, GdkEventButton* event, XYWnd* xywnd) +{ + if(event->type == GDK_BUTTON_PRESS) + { + g_pParentWnd->SetActiveXY(xywnd); + + xywnd->ButtonState_onMouseDown(buttons_for_event_button(event)); + + xywnd->onMouseDown(WindowVector(event->x, event->y), button_for_button(event->button), modifiers_for_state(event->state)); + } + return FALSE; +} + +gboolean xywnd_button_release(GtkWidget* widget, GdkEventButton* event, XYWnd* xywnd) +{ + if(event->type == GDK_BUTTON_RELEASE) + { + xywnd->XY_MouseUp(static_cast(event->x), static_cast(event->y), buttons_for_event_button(event)); + + xywnd->ButtonState_onMouseUp(buttons_for_event_button(event)); + } + return FALSE; +} + +void xywnd_motion(gdouble x, gdouble y, guint state, void* data) +{ + if(reinterpret_cast(data)->chaseMouseMotion(static_cast(x), static_cast(y))) + { + return; + } + reinterpret_cast(data)->XY_MouseMoved(static_cast(x), static_cast(y), buttons_for_state(state)); +} + +gboolean xywnd_wheel_scroll(GtkWidget* widget, GdkEventScroll* event, XYWnd* xywnd) +{ + if(event->direction == GDK_SCROLL_UP) + { + XYWnd_ZoomIn(xywnd); + } + else if(event->direction == GDK_SCROLL_DOWN) + { + XYWnd_ZoomOut(xywnd); + } + return FALSE; +} + +gboolean xywnd_size_allocate(GtkWidget* widget, GtkAllocation* allocation, XYWnd* xywnd) +{ + xywnd->m_nWidth = allocation->width; + xywnd->m_nHeight = allocation->height; + xywnd->updateProjection(); + xywnd->m_window_observer->onSizeChanged(xywnd->Width(), xywnd->Height()); + return FALSE; +} + +gboolean xywnd_expose(GtkWidget* widget, GdkEventExpose* event, XYWnd* xywnd) +{ + if(glwidget_make_current(xywnd->GetWidget()) != FALSE) + { + if(Map_Valid(g_map) && ScreenUpdates_Enabled()) + { + GlobalOpenGL_debugAssertNoErrors(); + xywnd->XY_Draw(); + GlobalOpenGL_debugAssertNoErrors(); + + xywnd->m_XORRectangle.set(rectangle_t()); + } + glwidget_swap_buffers(xywnd->GetWidget()); + } + return FALSE; +} + + +void XYWnd_CameraMoved(XYWnd& xywnd) +{ + if(g_xywindow_globals_private.m_bCamXYUpdate) + { + XYWnd_Update(xywnd); + } +} + +XYWnd::XYWnd() : + m_gl_widget(glwidget_new(FALSE)), + m_deferredDraw(WidgetQueueDrawCaller(*m_gl_widget)), + m_deferred_motion(xywnd_motion, this), + m_parent(0), + m_window_observer(NewWindowObserver()), + m_XORRectangle(m_gl_widget), + m_chasemouse_handler(0) +{ + m_bActive = false; + m_buttonstate = 0; + + m_bNewBrushDrag = false; + m_move_started = false; + m_zoom_started = false; + + m_nWidth = 0; + m_nHeight = 0; + + m_vOrigin[0] = 0; + m_vOrigin[1] = 20; + m_vOrigin[2] = 46; + m_fScale = 1; + m_viewType = XY; + + m_backgroundActivated = false; + m_alpha = 1.0f; + m_xmin = 0.0f; + m_ymin = 0.0f; + m_xmax = 0.0f; + m_ymax = 0.0f; + + m_entityCreate = false; + + m_mnuDrop = 0; + + GlobalWindowObservers_add(m_window_observer); + GlobalWindowObservers_connectWidget(m_gl_widget); + + m_window_observer->setRectangleDrawCallback(ReferenceCaller1(*this)); + m_window_observer->setView(m_view); + + gtk_widget_ref(m_gl_widget); + + gtk_widget_set_events(m_gl_widget, GDK_DESTROY | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK); + GTK_WIDGET_SET_FLAGS(m_gl_widget, GTK_CAN_FOCUS); + + m_sizeHandler = g_signal_connect(G_OBJECT(m_gl_widget), "size_allocate", G_CALLBACK(xywnd_size_allocate), this); + m_exposeHandler = g_signal_connect(G_OBJECT(m_gl_widget), "expose_event", G_CALLBACK(xywnd_expose), this); + + g_signal_connect(G_OBJECT(m_gl_widget), "button_press_event", G_CALLBACK(xywnd_button_press), this); + g_signal_connect(G_OBJECT(m_gl_widget), "button_release_event", G_CALLBACK(xywnd_button_release), this); + g_signal_connect(G_OBJECT(m_gl_widget), "motion_notify_event", G_CALLBACK(DeferredMotion::gtk_motion), &m_deferred_motion); + + g_signal_connect(G_OBJECT(m_gl_widget), "scroll_event", G_CALLBACK(xywnd_wheel_scroll), this); + + Map_addValidCallback(g_map, DeferredDrawOnMapValidChangedCaller(m_deferredDraw)); + + updateProjection(); + updateModelview(); + + AddSceneChangeCallback(ReferenceCaller(*this)); + AddCameraMovedCallback(ReferenceCaller(*this)); + + PressedButtons_connect(g_pressedButtons, m_gl_widget); + + onMouseDown.connectLast(makeSignalHandler3(MouseDownCaller(), *this)); +} + +XYWnd::~XYWnd() +{ + onDestroyed(); + + if(m_mnuDrop != 0) + { + gtk_widget_destroy(GTK_WIDGET(m_mnuDrop)); + m_mnuDrop = 0; + } + + g_signal_handler_disconnect(G_OBJECT(m_gl_widget), m_sizeHandler); + g_signal_handler_disconnect(G_OBJECT(m_gl_widget), m_exposeHandler); + + gtk_widget_unref(m_gl_widget); + + m_window_observer->release(); +} + +void XYWnd::captureStates() +{ + m_state_selected = GlobalShaderCache().capture("$XY_OVERLAY"); +} + +void XYWnd::releaseStates() +{ + GlobalShaderCache().release("$XY_OVERLAY"); +} + +const Vector3& XYWnd::GetOrigin() +{ + return m_vOrigin; +} + +void XYWnd::SetOrigin(const Vector3& origin) +{ + m_vOrigin = origin; + updateModelview(); +} + +void XYWnd::Scroll(int x, int y) +{ + int nDim1 = (m_viewType == YZ) ? 1 : 0; + int nDim2 = (m_viewType == XY) ? 1 : 2; + m_vOrigin[nDim1] += x / m_fScale; + m_vOrigin[nDim2] += y / m_fScale; + updateModelview(); + queueDraw(); +} + +unsigned int Clipper_buttons() +{ + return RAD_LBUTTON; +} + +void XYWnd::DropClipPoint(int pointx, int pointy) +{ + Vector3 point; + + XY_ToPoint(pointx, pointy, point); + + Vector3 mid; + Select_GetMid(mid); + g_clip_viewtype = static_cast(GetViewType()); + const int nDim = (g_clip_viewtype == YZ ) ? 0 : ( (g_clip_viewtype == XZ) ? 1 : 2 ); + point[nDim] = mid[nDim]; + vector3_snap(point, GetGridSize()); + NewClipPoint(point); +} + +void XYWnd::Clipper_OnLButtonDown(int x, int y) +{ + Vector3 mousePosition; + XY_ToPoint(x, y , mousePosition); + g_pMovingClip = GlobalClipPoints_Find(mousePosition, (VIEWTYPE)m_viewType, m_fScale); + if(!g_pMovingClip) + { + DropClipPoint(x, y); + } +} + +void XYWnd::Clipper_OnLButtonUp(int x, int y) +{ + if (g_pMovingClip) + { + g_pMovingClip = 0; + } +} + +void XYWnd::Clipper_OnMouseMoved(int x, int y) +{ + if (g_pMovingClip) + { + XY_ToPoint(x, y , g_pMovingClip->m_ptClip); + XY_SnapToGrid(g_pMovingClip->m_ptClip); + Clip_Update(); + ClipperChangeNotify(); + } +} + +void XYWnd::Clipper_Crosshair_OnMouseMoved(int x, int y) +{ + Vector3 mousePosition; + XY_ToPoint(x, y , mousePosition); + if(ClipMode() && GlobalClipPoints_Find(mousePosition, (VIEWTYPE)m_viewType, m_fScale) != 0) + { + GdkCursor *cursor; + cursor = gdk_cursor_new (GDK_CROSSHAIR); + gdk_window_set_cursor (m_gl_widget->window, cursor); + gdk_cursor_unref (cursor); + } + else + { + gdk_window_set_cursor (m_gl_widget->window, 0); + } +} + +unsigned int MoveCamera_buttons() +{ + return RAD_CONTROL | (g_glwindow_globals.m_nMouseType == ETwoButton ? RAD_RBUTTON : RAD_MBUTTON); +} + +void XYWnd_PositionCamera(XYWnd* xywnd, int x, int y, CamWnd& camwnd) +{ + Vector3 origin(Camera_getOrigin(camwnd)); + xywnd->XY_ToPoint(x, y, origin); + xywnd->XY_SnapToGrid(origin); + Camera_setOrigin(camwnd, origin); +} + +unsigned int OrientCamera_buttons() +{ + if(g_glwindow_globals.m_nMouseType == ETwoButton) + return RAD_RBUTTON | RAD_SHIFT | RAD_CONTROL; + return RAD_MBUTTON; +} + +void XYWnd_OrientCamera(XYWnd* xywnd, int x, int y, CamWnd& camwnd) +{ + Vector3 point = g_vector3_identity; + xywnd->XY_ToPoint(x, y, point); + xywnd->XY_SnapToGrid(point); + vector3_subtract(point, Camera_getOrigin(camwnd)); + + int n1 = (xywnd->GetViewType() == XY) ? 1 : 2; + int n2 = (xywnd->GetViewType() == YZ) ? 1 : 0; + int nAngle = (xywnd->GetViewType() == XY) ? CAMERA_YAW : CAMERA_PITCH; + if (point[n1] || point[n2]) + { + Vector3 angles(Camera_getAngles(camwnd)); + angles[nAngle] = static_cast(radians_to_degrees(atan2 (point[n1], point[n2]))); + Camera_setAngles(camwnd, angles); + } +} + +/* +============== +NewBrushDrag +============== +*/ +unsigned int NewBrushDrag_buttons() +{ + return RAD_LBUTTON; +} + +void XYWnd::NewBrushDrag_Begin(int x, int y) +{ + m_NewBrushDrag = 0; + m_nNewBrushPressx = x; + m_nNewBrushPressy = y; + + m_bNewBrushDrag = true; + GlobalUndoSystem().start(); +} + +void XYWnd::NewBrushDrag_End(int x, int y) +{ + if(m_NewBrushDrag != 0) + { + GlobalUndoSystem().finish("brushDragNew"); + } +} + +void XYWnd::NewBrushDrag(int x, int y) +{ + Vector3 mins, maxs; + XY_ToPoint(m_nNewBrushPressx, m_nNewBrushPressy, mins); + XY_SnapToGrid(mins); + XY_ToPoint(x, y, maxs); + XY_SnapToGrid(maxs); + + int nDim = (m_viewType == XY) ? 2 : (m_viewType == YZ) ? 0 : 1; + + mins[nDim] = float_snapped(Select_getWorkZone().d_work_min[nDim], GetGridSize()); + maxs[nDim] = float_snapped(Select_getWorkZone().d_work_max[nDim], GetGridSize()); + + if (maxs[nDim] <= mins[nDim]) + maxs[nDim] = mins[nDim] + GetGridSize(); + + for(int i=0 ; i<3 ; i++) + { + if (mins[i] == maxs[i]) + return; // don't create a degenerate brush + if (mins[i] > maxs[i]) + { + float temp = mins[i]; + mins[i] = maxs[i]; + maxs[i] = temp; + } + } + + if(m_NewBrushDrag == 0) + { + NodeSmartReference node(GlobalBrushCreator().createBrush()); + Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(node); + + scene::Path brushpath(makeReference(GlobalSceneGraph().root())); + brushpath.push(makeReference(*Map_GetWorldspawn(g_map))); + brushpath.push(makeReference(node.get())); + selectPath(brushpath, true); + + m_NewBrushDrag = node.get_pointer(); + } + + // d1223m + //Scene_BrushResize_Selected(GlobalSceneGraph(), aabb_for_minmax(mins, maxs), TextureBrowser_GetSelectedShader(GlobalTextureBrowser())); + Scene_BrushResize_Selected(GlobalSceneGraph(), aabb_for_minmax(mins, maxs), + g_brush_always_caulk ? + "textures/common/caulk" : TextureBrowser_GetSelectedShader(GlobalTextureBrowser())); +} + +void entitycreate_activated(GtkWidget* item) +{ + scene::Node* world_node = Map_FindWorldspawn(g_map); + const char* entity_name = gtk_label_get_text(GTK_LABEL(GTK_BIN(item)->child)); + + if(!(world_node && string_equal(entity_name, "worldspawn"))) + { + g_pParentWnd->ActiveXY()->OnEntityCreate(entity_name); + } else { + GlobalRadiant().m_pfnMessageBox(GTK_WIDGET(MainFrame_getWindow()), "There's already a worldspawn in your map!" + "", + "Info", + eMB_OK, + eMB_ICONDEFAULT); + } +} + +void EntityClassMenu_addItem(GtkMenu* menu, const char* name) +{ + GtkMenuItem* item = GTK_MENU_ITEM(gtk_menu_item_new_with_label(name)); + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(entitycreate_activated), item); + gtk_widget_show(GTK_WIDGET(item)); + menu_add_item(menu, item); +} + +class EntityClassMenuInserter : public EntityClassVisitor +{ + typedef std::pair MenuPair; + typedef std::vector MenuStack; + MenuStack m_stack; + CopiedString m_previous; +public: + EntityClassMenuInserter(GtkMenu* menu) + { + m_stack.reserve(2); + m_stack.push_back(MenuPair(menu, "")); + } + ~EntityClassMenuInserter() + { + if(!string_empty(m_previous.c_str())) + { + addItem(m_previous.c_str(), ""); + } + } + void visit(EntityClass* e) + { + ASSERT_MESSAGE(!string_empty(e->name()), "entity-class has no name"); + if(!string_empty(m_previous.c_str())) + { + addItem(m_previous.c_str(), e->name()); + } + m_previous = e->name(); + } + void pushMenu(const CopiedString& name) + { + GtkMenuItem* item = GTK_MENU_ITEM(gtk_menu_item_new_with_label(name.c_str())); + gtk_widget_show(GTK_WIDGET(item)); + container_add_widget(GTK_CONTAINER(m_stack.back().first), GTK_WIDGET(item)); + + GtkMenu* submenu = GTK_MENU(gtk_menu_new()); + gtk_menu_item_set_submenu(item, GTK_WIDGET(submenu)); + + m_stack.push_back(MenuPair(submenu, name)); + } + void popMenu() + { + m_stack.pop_back(); + } + void addItem(const char* name, const char* next) + { + const char* underscore = strchr(name, '_'); + + if(underscore != 0 && underscore != name) + { + bool nextEqual = string_equal_n(name, next, (underscore + 1) - name); + const char* parent = m_stack.back().second.c_str(); + + if(!string_empty(parent) + && string_length(parent) == std::size_t(underscore - name) + && string_equal_n(name, parent, underscore - name)) // this is a child + { + } + else if(nextEqual) + { + if(m_stack.size() == 2) + { + popMenu(); + } + pushMenu(CopiedString(StringRange(name, underscore))); + } + else if(m_stack.size() == 2) + { + popMenu(); + } + } + else if(m_stack.size() == 2) + { + popMenu(); + } + + EntityClassMenu_addItem(m_stack.back().first, name); + } +}; + +void XYWnd::OnContextMenu() +{ + if (g_xywindow_globals.m_bRightClick == false) + return; + + if (m_mnuDrop == 0) // first time, load it up + { + GtkMenu* menu = m_mnuDrop = GTK_MENU(gtk_menu_new()); + + EntityClassMenuInserter inserter(menu); + GlobalEntityClassManager().forEach(inserter); + } + + gtk_menu_popup(m_mnuDrop, 0, 0, 0, 0, 1, GDK_CURRENT_TIME); +} + +FreezePointer g_xywnd_freezePointer; + +unsigned int Move_buttons() +{ + return RAD_RBUTTON; +} + +void XYWnd_moveDelta(int x, int y, unsigned int state, void* data) +{ + reinterpret_cast(data)->EntityCreate_MouseMove(x, y); + reinterpret_cast(data)->Scroll(-x, y); +} + +gboolean XYWnd_Move_focusOut(GtkWidget* widget, GdkEventFocus* event, XYWnd* xywnd) +{ + xywnd->Move_End(); + return FALSE; +} + +void XYWnd::Move_Begin() +{ + if(m_move_started) + { + Move_End(); + } + m_move_started = true; + g_xywnd_freezePointer.freeze_pointer(m_parent != 0 ? m_parent : MainFrame_getWindow(), XYWnd_moveDelta, this); + m_move_focusOut = g_signal_connect(G_OBJECT(m_gl_widget), "focus_out_event", G_CALLBACK(XYWnd_Move_focusOut), this); +} + +void XYWnd::Move_End() +{ + m_move_started = false; + g_xywnd_freezePointer.unfreeze_pointer(m_parent != 0 ? m_parent : MainFrame_getWindow()); + g_signal_handler_disconnect(G_OBJECT(m_gl_widget), m_move_focusOut); +} + +unsigned int Zoom_buttons() +{ + return RAD_RBUTTON | RAD_SHIFT; +} + +int g_dragZoom = 0; + +void XYWnd_zoomDelta(int x, int y, unsigned int state, void* data) +{ + if(y != 0) + { + g_dragZoom += y; + + while(abs(g_dragZoom) > 8) + { + if(g_dragZoom > 0) + { + XYWnd_ZoomOut(reinterpret_cast(data)); + g_dragZoom -= 8; + } + else + { + XYWnd_ZoomIn(reinterpret_cast(data)); + g_dragZoom += 8; + } + } + } +} + +gboolean XYWnd_Zoom_focusOut(GtkWidget* widget, GdkEventFocus* event, XYWnd* xywnd) +{ + xywnd->Zoom_End(); + return FALSE; +} + +void XYWnd::Zoom_Begin() +{ + if(m_zoom_started) + { + Zoom_End(); + } + m_zoom_started = true; + g_dragZoom = 0; + g_xywnd_freezePointer.freeze_pointer(m_parent != 0 ? m_parent : MainFrame_getWindow(), XYWnd_zoomDelta, this); + m_zoom_focusOut = g_signal_connect(G_OBJECT(m_gl_widget), "focus_out_event", G_CALLBACK(XYWnd_Zoom_focusOut), this); +} + +void XYWnd::Zoom_End() +{ + m_zoom_started = false; + g_xywnd_freezePointer.unfreeze_pointer(m_parent != 0 ? m_parent : MainFrame_getWindow()); + g_signal_handler_disconnect(G_OBJECT(m_gl_widget), m_zoom_focusOut); +} + +// makes sure the selected brush or camera is in view +void XYWnd::PositionView(const Vector3& position) +{ + int nDim1 = (m_viewType == YZ) ? 1 : 0; + int nDim2 = (m_viewType == XY) ? 1 : 2; + + m_vOrigin[nDim1] = position[nDim1]; + m_vOrigin[nDim2] = position[nDim2]; + + updateModelview(); + + XYWnd_Update(*this); +} + +void XYWnd::SetViewType(VIEWTYPE viewType) +{ + m_viewType = viewType; + updateModelview(); + + if(m_parent != 0) + { + gtk_window_set_title(m_parent, ViewType_getTitle(m_viewType)); + } +} + + +inline WindowVector WindowVector_forInteger(int x, int y) +{ + return WindowVector(static_cast(x), static_cast(y)); +} + +void XYWnd::mouseDown(const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers) +{ + XY_MouseDown(static_cast(position.x()), static_cast(position.y()), buttons_for_button_and_modifiers(button, modifiers)); +} +void XYWnd::XY_MouseDown (int x, int y, unsigned int buttons) +{ + if(buttons == Move_buttons()) + { + Move_Begin(); + EntityCreate_MouseDown(x, y); + } + else if(buttons == Zoom_buttons()) + { + Zoom_Begin(); + } + else if(ClipMode() && buttons == Clipper_buttons()) + { + Clipper_OnLButtonDown(x, y); + } + else if(buttons == NewBrushDrag_buttons() && GlobalSelectionSystem().countSelected() == 0) + { + NewBrushDrag_Begin(x, y); + } + // control mbutton = move camera + else if (buttons == MoveCamera_buttons()) + { + XYWnd_PositionCamera(this, x, y, *g_pParentWnd->GetCamWnd()); + } + // mbutton = angle camera + else if(buttons == OrientCamera_buttons()) + { + XYWnd_OrientCamera(this, x, y, *g_pParentWnd->GetCamWnd()); + } + else + { + m_window_observer->onMouseDown(WindowVector_forInteger(x, y), button_for_flags(buttons), modifiers_for_flags(buttons)); + } +} + +void XYWnd::XY_MouseUp(int x, int y, unsigned int buttons) +{ + if(m_move_started) + { + Move_End(); + EntityCreate_MouseUp(x, y); + } + else if(m_zoom_started) + { + Zoom_End(); + } + else if (ClipMode() && buttons == Clipper_buttons()) + { + Clipper_OnLButtonUp(x, y); + } + else if (m_bNewBrushDrag) + { + m_bNewBrushDrag = false; + NewBrushDrag_End(x, y); + } + else + { + m_window_observer->onMouseUp(WindowVector_forInteger(x, y), button_for_flags(buttons), modifiers_for_flags(buttons)); + } +} + +void XYWnd::XY_MouseMoved (int x, int y, unsigned int buttons) +{ + // rbutton = drag xy origin + if(m_move_started) + { + } + // zoom in/out + else if(m_zoom_started) + { + } + + else if (ClipMode() && g_pMovingClip != 0) + { + Clipper_OnMouseMoved(x, y); + } + // lbutton without selection = drag new brush + else if (m_bNewBrushDrag) + { + NewBrushDrag(x, y); + } + + // control mbutton = move camera + else if (getButtonState() == MoveCamera_buttons()) + { + XYWnd_PositionCamera(this, x, y, *g_pParentWnd->GetCamWnd()); + } + + // mbutton = angle camera + else if (getButtonState() == OrientCamera_buttons()) + { + XYWnd_OrientCamera(this, x, y, *g_pParentWnd->GetCamWnd()); + } + + else + { + m_window_observer->onMouseMotion(WindowVector_forInteger(x, y), modifiers_for_flags(buttons)); + + m_mousePosition[0] = m_mousePosition[1] = m_mousePosition[2] = 0.0; + XY_ToPoint(x, y , m_mousePosition); + XY_SnapToGrid(m_mousePosition); + + StringOutputStream status(64); + status << "x:: " << FloatFormat(m_mousePosition[0], 6, 1) + << " y:: " << FloatFormat(m_mousePosition[1], 6, 1) + << " z:: " << FloatFormat(m_mousePosition[2], 6, 1); + g_pParentWnd->SetStatusText(g_pParentWnd->m_position_status, status.c_str()); + + if (g_bCrossHairs) + { + XYWnd_Update(*this); + } + + Clipper_Crosshair_OnMouseMoved(x, y); + } +} + +void XYWnd::EntityCreate_MouseDown(int x, int y) +{ + m_entityCreate = true; + m_entityCreate_x = x; + m_entityCreate_y = y; +} + +void XYWnd::EntityCreate_MouseMove(int x, int y) +{ + if(m_entityCreate && (m_entityCreate_x != x || m_entityCreate_y != y)) + { + m_entityCreate = false; + } +} + +void XYWnd::EntityCreate_MouseUp(int x, int y) +{ + if(m_entityCreate) + { + m_entityCreate = false; + OnContextMenu(); + } +} + +inline float screen_normalised(int pos, unsigned int size) +{ + return ((2.0f * pos) / size) - 1.0f; +} + +inline float normalised_to_world(float normalised, float world_origin, float normalised2world_scale) +{ + return world_origin + normalised * normalised2world_scale; +} + + +// TTimo: watch it, this doesn't init one of the 3 coords +void XYWnd::XY_ToPoint (int x, int y, Vector3& point) +{ + float normalised2world_scale_x = m_nWidth / 2 / m_fScale; + float normalised2world_scale_y = m_nHeight / 2 / m_fScale; + if (m_viewType == XY) + { + point[0] = normalised_to_world(screen_normalised(x, m_nWidth), m_vOrigin[0], normalised2world_scale_x); + point[1] = normalised_to_world(-screen_normalised(y, m_nHeight), m_vOrigin[1], normalised2world_scale_y); + } + else if (m_viewType == YZ) + { + point[1] = normalised_to_world(screen_normalised(x, m_nWidth), m_vOrigin[1], normalised2world_scale_x); + point[2] = normalised_to_world(-screen_normalised(y, m_nHeight), m_vOrigin[2], normalised2world_scale_y); + } + else + { + point[0] = normalised_to_world(screen_normalised(x, m_nWidth), m_vOrigin[0], normalised2world_scale_x); + point[2] = normalised_to_world(-screen_normalised(y, m_nHeight), m_vOrigin[2], normalised2world_scale_y); + } +} + +void XYWnd::XY_SnapToGrid(Vector3& point) +{ + if (m_viewType == XY) + { + point[0] = float_snapped(point[0], GetGridSize()); + point[1] = float_snapped(point[1], GetGridSize()); + } + else if (m_viewType == YZ) + { + point[1] = float_snapped(point[1], GetGridSize()); + point[2] = float_snapped(point[2], GetGridSize()); + } + else + { + point[0] = float_snapped(point[0], GetGridSize()); + point[2] = float_snapped(point[2], GetGridSize()); + } +} + +void XYWnd::XY_LoadBackgroundImage(const char *name) +{ + const char* relative = path_make_relative(name, GlobalFileSystem().findRoot(name)); + if (relative == name) + globalOutputStream() << "WARNING: could not extract the relative path, using full path instead\n"; + + char fileNameWithoutExt[512]; + strncpy(fileNameWithoutExt, relative, sizeof(fileNameWithoutExt) - 1); + fileNameWithoutExt[512 - 1] = '\0'; + fileNameWithoutExt[strlen(fileNameWithoutExt) - 4] = '\0'; + + Image *image = QERApp_LoadImage(0, fileNameWithoutExt); + if (!image) { + globalOutputStream() << "Could not load texture " << fileNameWithoutExt << "\n"; + return; + } + g_pParentWnd->ActiveXY()->m_tex = (qtexture_t*)malloc(sizeof(qtexture_t)); + LoadTextureRGBA(g_pParentWnd->ActiveXY()->XYWnd::m_tex, image->getRGBAPixels(), image->getWidth(), image->getHeight()); + globalOutputStream() << "Loaded background texture " << relative << "\n"; + g_pParentWnd->ActiveXY()->m_backgroundActivated = true; + + int m_ix, m_iy; + switch(g_pParentWnd->ActiveXY()->m_viewType) + { + case XY: + m_ix = 0; + m_iy = 1; + break; + case XZ: + m_ix = 0; + m_iy = 2; + break; + case YZ: + m_ix = 1; + m_iy = 2; + break; + } + + Vector3 min, max; + Select_GetBounds(min, max); + g_pParentWnd->ActiveXY()->m_xmin = min[m_ix]; + g_pParentWnd->ActiveXY()->m_ymin = min[m_iy]; + g_pParentWnd->ActiveXY()->m_xmax = max[m_ix]; + g_pParentWnd->ActiveXY()->m_ymax = max[m_iy]; +} + +void XYWnd::XY_DisableBackground (void) +{ + g_pParentWnd->ActiveXY()->m_backgroundActivated = false; + if (g_pParentWnd->ActiveXY()->m_tex) + free(g_pParentWnd->ActiveXY()->m_tex); + g_pParentWnd->ActiveXY()->m_tex = NULL; +} + +void WXY_BackgroundSelect(void) +{ + bool brushesSelected = Scene_countSelectedBrushes(GlobalSceneGraph()) != 0; + if (!brushesSelected) { + gtk_MessageBox(0, "You have to select some brushes to get the bounding box for.\n", + "No selection", eMB_OK, eMB_ICONERROR); + return; + } + + const char *filename = file_dialog(GTK_WIDGET(MainFrame_getWindow()), TRUE, "Background Image", NULL, NULL); + g_pParentWnd->ActiveXY()->XY_DisableBackground(); + if (filename) + g_pParentWnd->ActiveXY()->XY_LoadBackgroundImage(filename); +} + +/* +============================================================================ + +DRAWING + +============================================================================ +*/ + +/* +============== +XY_DrawGrid +============== +*/ + +double two_to_the_power(int power) +{ + return pow(2.0f, power); +} + +void XYWnd::XY_DrawAxis(void) +{ + if ( g_xywindow_globals_private.show_axis) { + const char g_AxisName[3] = { 'X', 'Y', 'Z' }; + const int nDim1 = (m_viewType == YZ) ? 1 : 0; + const int nDim2 = (m_viewType == XY) ? 1 : 2; + const int w = (m_nWidth / 2 / m_fScale); + const int h = (m_nHeight / 2 / m_fScale); + + const Vector3& colourX = (m_viewType == YZ) ? g_xywindow_globals.AxisColorY : g_xywindow_globals.AxisColorX; + const Vector3& colourY = (m_viewType == XY) ? g_xywindow_globals.AxisColorY : g_xywindow_globals.AxisColorZ; + + // draw two lines with corresponding axis colors to highlight current view + // horizontal line: nDim1 color + glLineWidth(2); + glBegin( GL_LINES ); + glColor3fv (vector3_to_array(colourX)); + glVertex2f( m_vOrigin[nDim1] - w + 40 / m_fScale, m_vOrigin[nDim2] + h - 45 / m_fScale ); + glVertex2f( m_vOrigin[nDim1] - w + 65 / m_fScale, m_vOrigin[nDim2] + h - 45 / m_fScale ); + glVertex2f( 0, 0 ); + glVertex2f( 32 / m_fScale, 0 ); + glColor3fv (vector3_to_array(colourY)); + glVertex2f( m_vOrigin[nDim1] - w + 40 / m_fScale, m_vOrigin[nDim2] + h - 45 / m_fScale ); + glVertex2f( m_vOrigin[nDim1] - w + 40 / m_fScale, m_vOrigin[nDim2] + h - 20 / m_fScale ); + glVertex2f( 0, 0 ); + glVertex2f( 0, 32 / m_fScale ); + glEnd(); + glLineWidth(1); + // now print axis symbols + glColor3fv (vector3_to_array(colourX)); + glRasterPos2f ( m_vOrigin[nDim1] - w + 55 / m_fScale, m_vOrigin[nDim2] + h - 55 / m_fScale ); + GlobalOpenGL().drawChar(g_AxisName[nDim1]); + glRasterPos2f (28 / m_fScale, -10 / m_fScale ); + GlobalOpenGL().drawChar(g_AxisName[nDim1]); + glColor3fv (vector3_to_array(colourY)); + glRasterPos2f ( m_vOrigin[nDim1] - w + 25 / m_fScale, m_vOrigin[nDim2] + h - 30 / m_fScale ); + GlobalOpenGL().drawChar(g_AxisName[nDim2]); + glRasterPos2f ( -10 / m_fScale, 28 / m_fScale ); + GlobalOpenGL().drawChar(g_AxisName[nDim2]); + } +} + +void XYWnd::XY_DrawBackground(void) +{ + glPushAttrib(GL_ALL_ATTRIB_BITS); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + + glPolygonMode(GL_FRONT, GL_FILL); + + glBindTexture(GL_TEXTURE_2D, m_tex->texture_number); + glBegin(GL_QUADS); + + glColor4f(1.0, 1.0, 1.0, m_alpha); + glTexCoord2f(0.0, 1.0); + glVertex2f(m_xmin, m_ymin); + + glTexCoord2f(1.0, 1.0); + glVertex2f(m_xmax, m_ymin); + + glTexCoord2f(1.0, 0.0); + glVertex2f(m_xmax, m_ymax); + + glTexCoord2f(0.0, 0.0); + glVertex2f(m_xmin, m_ymax); + + glEnd(); + glBindTexture(GL_TEXTURE_2D, 0); + + glPopAttrib(); +} + +void XYWnd::XY_DrawGrid(void) { + float x, y, xb, xe, yb, ye; + float w, h; + char text[32]; + float step, minor_step, stepx, stepy; + step = minor_step = stepx = stepy = GetGridSize(); + + int minor_power = Grid_getPower(); + int mask; + + while ((minor_step * m_fScale) <= 4.0f) { // make sure minor grid spacing is at least 4 pixels on the screen + ++minor_power; + minor_step *= 2; + } + int power = minor_power; + while ((power % 3) != 0 || (step * m_fScale) <= 32.0f) { // make sure major grid spacing is at least 32 pixels on the screen + ++power; + step = float(two_to_the_power(power)); + } + mask = (1 << (power - minor_power)) - 1; + while ((stepx * m_fScale) <= 32.0f) // text step x must be at least 32 + stepx *= 2; + while ((stepy * m_fScale) <= 32.0f) // text step y must be at least 32 + stepy *= 2; + + + glDisable(GL_TEXTURE_2D); + glDisable(GL_TEXTURE_1D); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glLineWidth(1); + + w = (m_nWidth / 2 / m_fScale); + h = (m_nHeight / 2 / m_fScale); + + const int nDim1 = (m_viewType == YZ) ? 1 : 0; + const int nDim2 = (m_viewType == XY) ? 1 : 2; + + xb = m_vOrigin[nDim1] - w; + if (xb < region_mins[nDim1]) + xb = region_mins[nDim1]; + xb = step * floor (xb / step); + + xe = m_vOrigin[nDim1] + w; + if (xe > region_maxs[nDim1]) + xe = region_maxs[nDim1]; + xe = step * ceil (xe / step); + + yb = m_vOrigin[nDim2] - h; + if (yb < region_mins[nDim2]) + yb = region_mins[nDim2]; + yb = step * floor (yb / step); + + ye = m_vOrigin[nDim2] + h; + if (ye > region_maxs[nDim2]) + ye = region_maxs[nDim2]; + ye = step * ceil (ye / step); + +#define COLORS_DIFFER(a,b) \ + ((a)[0] != (b)[0] || \ + (a)[1] != (b)[1] || \ + (a)[2] != (b)[2]) + + // djbob + // draw minor blocks + if (g_xywindow_globals_private.d_showgrid) { + if (COLORS_DIFFER(g_xywindow_globals.color_gridminor, g_xywindow_globals.color_gridback)) { + glColor3fv(vector3_to_array(g_xywindow_globals.color_gridminor)); + + glBegin (GL_LINES); + int i = 0; + for (x = xb ; x < xe ; x += minor_step, ++i) { + if ((i & mask) != 0) { + glVertex2f (x, yb); + glVertex2f (x, ye); + } + } + i = 0; + for (y = yb ; y < ye ; y += minor_step, ++i) { + if ((i & mask) != 0) { + glVertex2f (xb, y); + glVertex2f (xe, y); + } + } + glEnd(); + } + + // draw major blocks + if (COLORS_DIFFER(g_xywindow_globals.color_gridmajor, g_xywindow_globals.color_gridback)) { + glColor3fv(vector3_to_array(g_xywindow_globals.color_gridmajor)); + + glBegin (GL_LINES); + for (x = xb ; x <= xe ; x += step) { + glVertex2f (x, yb); + glVertex2f (x, ye); + } + for (y = yb ; y <= ye ; y += step) { + glVertex2f (xb, y); + glVertex2f (xe, y); + } + glEnd(); + } + } + + // draw coordinate text if needed + if ( g_xywindow_globals_private.show_coordinates) { + glColor3fv(vector3_to_array(g_xywindow_globals.color_gridtext)); + float offx = m_vOrigin[nDim2] + h - 6 / m_fScale, offy = m_vOrigin[nDim1] - w + 1 / m_fScale; + for (x = xb - fmod(xb, stepx); x <= xe ; x += stepx) { + glRasterPos2f (x, offx); + sprintf (text, "%g", x); + GlobalOpenGL().drawString(text); + } + for (y = yb - fmod(yb, stepy); y <= ye ; y += stepy) { + glRasterPos2f (offy, y); + sprintf (text, "%g", y); + GlobalOpenGL().drawString(text); + } + + if (Active()) + glColor3fv(vector3_to_array(g_xywindow_globals.color_viewname)); + + // we do this part (the old way) only if show_axis is disabled + if (!g_xywindow_globals_private.show_axis) { + glRasterPos2f ( m_vOrigin[nDim1] - w + 35 / m_fScale, m_vOrigin[nDim2] + h - 20 / m_fScale ); + + GlobalOpenGL().drawString(ViewType_getTitle(m_viewType)); + } + } + + XYWnd::XY_DrawAxis(); + + // show current work zone? + // the work zone is used to place dropped points and brushes + if (g_xywindow_globals_private.d_show_work) { + glColor3f( 1.0f, 0.0f, 0.0f ); + glBegin( GL_LINES ); + glVertex2f( xb, Select_getWorkZone().d_work_min[nDim2] ); + glVertex2f( xe, Select_getWorkZone().d_work_min[nDim2] ); + glVertex2f( xb, Select_getWorkZone().d_work_max[nDim2] ); + glVertex2f( xe, Select_getWorkZone().d_work_max[nDim2] ); + glVertex2f( Select_getWorkZone().d_work_min[nDim1], yb ); + glVertex2f( Select_getWorkZone().d_work_min[nDim1], ye ); + glVertex2f( Select_getWorkZone().d_work_max[nDim1], yb ); + glVertex2f( Select_getWorkZone().d_work_max[nDim1], ye ); + glEnd(); + } +} + +/* +============== +XY_DrawBlockGrid +============== +*/ +void XYWnd::XY_DrawBlockGrid() +{ + if(Map_FindWorldspawn(g_map) == 0) + { + return; + } + const char *value = Node_getEntity(*Map_GetWorldspawn(g_map))->getKeyValue("_blocksize" ); + if (strlen(value)) + sscanf( value, "%i", &g_xywindow_globals_private.blockSize ); + + if (!g_xywindow_globals_private.blockSize || g_xywindow_globals_private.blockSize > 65536 || g_xywindow_globals_private.blockSize < 1024) + // don't use custom blocksize if it is less than the default, or greater than the maximum world coordinate + g_xywindow_globals_private.blockSize = 1024; + + float x, y, xb, xe, yb, ye; + float w, h; + char text[32]; + + glDisable(GL_TEXTURE_2D); + glDisable(GL_TEXTURE_1D); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + + w = (m_nWidth / 2 / m_fScale); + h = (m_nHeight / 2 / m_fScale); + + int nDim1 = (m_viewType == YZ) ? 1 : 0; + int nDim2 = (m_viewType == XY) ? 1 : 2; + + xb = m_vOrigin[nDim1] - w; + if (xb < region_mins[nDim1]) + xb = region_mins[nDim1]; + xb = static_cast(g_xywindow_globals_private.blockSize * floor (xb/g_xywindow_globals_private.blockSize)); + + xe = m_vOrigin[nDim1] + w; + if (xe > region_maxs[nDim1]) + xe = region_maxs[nDim1]; + xe = static_cast(g_xywindow_globals_private.blockSize * ceil (xe/g_xywindow_globals_private.blockSize)); + + yb = m_vOrigin[nDim2] - h; + if (yb < region_mins[nDim2]) + yb = region_mins[nDim2]; + yb = static_cast(g_xywindow_globals_private.blockSize * floor (yb/g_xywindow_globals_private.blockSize)); + + ye = m_vOrigin[nDim2] + h; + if (ye > region_maxs[nDim2]) + ye = region_maxs[nDim2]; + ye = static_cast(g_xywindow_globals_private.blockSize * ceil (ye/g_xywindow_globals_private.blockSize)); + + // draw major blocks + + glColor3fv(vector3_to_array(g_xywindow_globals.color_gridblock)); + glLineWidth (2); + + glBegin (GL_LINES); + + for (x=xb ; x<=xe ; x+=g_xywindow_globals_private.blockSize) + { + glVertex2f (x, yb); + glVertex2f (x, ye); + } + + if (m_viewType == XY) + { + for (y=yb ; y<=ye ; y+=g_xywindow_globals_private.blockSize) + { + glVertex2f (xb, y); + glVertex2f (xe, y); + } + } + + glEnd(); + glLineWidth (1); + + // draw coordinate text if needed + + if (m_viewType == XY && m_fScale > .1) + { + for (x=xb ; x(fov*cos(a+c_pi/4)), y + static_cast(fov*sin(a+c_pi/4)), 0); + glVertex3f (x, y, 0); + glVertex3f (x + static_cast(fov*cos(a-c_pi/4)), y + static_cast(fov*sin(a-c_pi/4)), 0); + glEnd(); + +} + + +float Betwixt(float f1, float f2) +{ + if (f1 > f2) + return f2 + ((f1 - f2) / 2); + else + return f1 + ((f2 - f1) / 2); +} + + +// can be greatly simplified but per usual i am in a hurry +// which is not an excuse, just a fact +void XYWnd::PaintSizeInfo(int nDim1, int nDim2, Vector3& vMinBounds, Vector3& vMaxBounds) +{ + if(vector3_equal(vMinBounds, vMaxBounds)) + { + return; + } + const char* g_pDimStrings[] = {"x:", "y:", "z:"}; + typedef const char* OrgStrings[2]; + const OrgStrings g_pOrgStrings[] = { { "x:", "y:", }, { "x:", "z:", }, { "y:", "z:", } }; + + Vector3 vSize(vector3_subtracted(vMaxBounds, vMinBounds)); + + glColor3f(g_xywindow_globals.color_selbrushes[0] * .65f, + g_xywindow_globals.color_selbrushes[1] * .65f, + g_xywindow_globals.color_selbrushes[2] * .65f); + + StringOutputStream dimensions(16); + + if (m_viewType == XY) + { + glBegin (GL_LINES); + + glVertex3f(vMinBounds[nDim1], vMinBounds[nDim2] - 6.0f / m_fScale, 0.0f); + glVertex3f(vMinBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale, 0.0f); + + glVertex3f(vMinBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale, 0.0f); + glVertex3f(vMaxBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale, 0.0f); + + glVertex3f(vMaxBounds[nDim1], vMinBounds[nDim2] - 6.0f / m_fScale, 0.0f); + glVertex3f(vMaxBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale, 0.0f); + + + glVertex3f(vMaxBounds[nDim1] + 6.0f / m_fScale, vMinBounds[nDim2], 0.0f); + glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, vMinBounds[nDim2], 0.0f); + + glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, vMinBounds[nDim2], 0.0f); + glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, vMaxBounds[nDim2], 0.0f); + + glVertex3f(vMaxBounds[nDim1] + 6.0f / m_fScale, vMaxBounds[nDim2], 0.0f); + glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, vMaxBounds[nDim2], 0.0f); + + glEnd(); + + glRasterPos3f (Betwixt(vMinBounds[nDim1], vMaxBounds[nDim1]), vMinBounds[nDim2] - 20.0f / m_fScale, 0.0f); + dimensions << g_pDimStrings[nDim1] << vSize[nDim1]; + GlobalOpenGL().drawString(dimensions.c_str()); + dimensions.clear(); + + glRasterPos3f (vMaxBounds[nDim1] + 16.0f / m_fScale, Betwixt(vMinBounds[nDim2], vMaxBounds[nDim2]), 0.0f); + dimensions << g_pDimStrings[nDim2] << vSize[nDim2]; + GlobalOpenGL().drawString(dimensions.c_str()); + dimensions.clear(); + + glRasterPos3f (vMinBounds[nDim1] + 4, vMaxBounds[nDim2] + 8 / m_fScale, 0.0f); + dimensions << "(" << g_pOrgStrings[0][0] << vMinBounds[nDim1] << " " << g_pOrgStrings[0][1] << vMaxBounds[nDim2] << ")"; + GlobalOpenGL().drawString(dimensions.c_str()); + } + else if (m_viewType == XZ) + { + glBegin (GL_LINES); + + glVertex3f(vMinBounds[nDim1], 0, vMinBounds[nDim2] - 6.0f / m_fScale); + glVertex3f(vMinBounds[nDim1], 0, vMinBounds[nDim2] - 10.0f / m_fScale); + + glVertex3f(vMinBounds[nDim1], 0,vMinBounds[nDim2] - 10.0f / m_fScale); + glVertex3f(vMaxBounds[nDim1], 0,vMinBounds[nDim2] - 10.0f / m_fScale); + + glVertex3f(vMaxBounds[nDim1], 0,vMinBounds[nDim2] - 6.0f / m_fScale); + glVertex3f(vMaxBounds[nDim1], 0,vMinBounds[nDim2] - 10.0f / m_fScale); + + + glVertex3f(vMaxBounds[nDim1] + 6.0f / m_fScale, 0,vMinBounds[nDim2]); + glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, 0,vMinBounds[nDim2]); + + glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, 0,vMinBounds[nDim2]); + glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, 0,vMaxBounds[nDim2]); + + glVertex3f(vMaxBounds[nDim1] + 6.0f / m_fScale, 0,vMaxBounds[nDim2]); + glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, 0,vMaxBounds[nDim2]); + + glEnd(); + + glRasterPos3f (Betwixt(vMinBounds[nDim1], vMaxBounds[nDim1]), 0, vMinBounds[nDim2] - 20.0f / m_fScale); + dimensions << g_pDimStrings[nDim1] << vSize[nDim1]; + GlobalOpenGL().drawString(dimensions.c_str()); + dimensions.clear(); + + glRasterPos3f (vMaxBounds[nDim1] + 16.0f / m_fScale, 0, Betwixt(vMinBounds[nDim2], vMaxBounds[nDim2])); + dimensions << g_pDimStrings[nDim2] << vSize[nDim2]; + GlobalOpenGL().drawString(dimensions.c_str()); + dimensions.clear(); + + glRasterPos3f (vMinBounds[nDim1] + 4, 0, vMaxBounds[nDim2] + 8 / m_fScale); + dimensions << "(" << g_pOrgStrings[1][0] << vMinBounds[nDim1] << " " << g_pOrgStrings[1][1] << vMaxBounds[nDim2] << ")"; + GlobalOpenGL().drawString(dimensions.c_str()); + } + else + { + glBegin (GL_LINES); + + glVertex3f(0, vMinBounds[nDim1], vMinBounds[nDim2] - 6.0f / m_fScale); + glVertex3f(0, vMinBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale); + + glVertex3f(0, vMinBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale); + glVertex3f(0, vMaxBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale); + + glVertex3f(0, vMaxBounds[nDim1], vMinBounds[nDim2] - 6.0f / m_fScale); + glVertex3f(0, vMaxBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale); + + + glVertex3f(0, vMaxBounds[nDim1] + 6.0f / m_fScale, vMinBounds[nDim2]); + glVertex3f(0, vMaxBounds[nDim1] + 10.0f / m_fScale, vMinBounds[nDim2]); + + glVertex3f(0, vMaxBounds[nDim1] + 10.0f / m_fScale, vMinBounds[nDim2]); + glVertex3f(0, vMaxBounds[nDim1] + 10.0f / m_fScale, vMaxBounds[nDim2]); + + glVertex3f(0, vMaxBounds[nDim1] + 6.0f / m_fScale, vMaxBounds[nDim2]); + glVertex3f(0, vMaxBounds[nDim1] + 10.0f / m_fScale, vMaxBounds[nDim2]); + + glEnd(); + + glRasterPos3f (0, Betwixt(vMinBounds[nDim1], vMaxBounds[nDim1]), vMinBounds[nDim2] - 20.0f / m_fScale); + dimensions << g_pDimStrings[nDim1] << vSize[nDim1]; + GlobalOpenGL().drawString(dimensions.c_str()); + dimensions.clear(); + + glRasterPos3f (0, vMaxBounds[nDim1] + 16.0f / m_fScale, Betwixt(vMinBounds[nDim2], vMaxBounds[nDim2])); + dimensions << g_pDimStrings[nDim2] << vSize[nDim2]; + GlobalOpenGL().drawString(dimensions.c_str()); + dimensions.clear(); + + glRasterPos3f (0, vMinBounds[nDim1] + 4.0f, vMaxBounds[nDim2] + 8 / m_fScale); + dimensions << "(" << g_pOrgStrings[2][0] << vMinBounds[nDim1] << " " << g_pOrgStrings[2][1] << vMaxBounds[nDim2] << ")"; + GlobalOpenGL().drawString(dimensions.c_str()); + } +} + +class XYRenderer: public Renderer +{ + struct state_type + { + state_type() : + m_highlight(0), + m_state(0) + { + } + unsigned int m_highlight; + Shader* m_state; + }; +public: + XYRenderer(RenderStateFlags globalstate, Shader* selected) : + m_globalstate(globalstate), + m_state_selected(selected) + { + ASSERT_NOTNULL(selected); + m_state_stack.push_back(state_type()); + } + + void SetState(Shader* state, EStyle style) + { + ASSERT_NOTNULL(state); + if(style == eWireframeOnly) + m_state_stack.back().m_state = state; + } + const EStyle getStyle() const + { + return eWireframeOnly; + } + void PushState() + { + m_state_stack.push_back(m_state_stack.back()); + } + void PopState() + { + ASSERT_MESSAGE(!m_state_stack.empty(), "popping empty stack"); + m_state_stack.pop_back(); + } + void Highlight(EHighlightMode mode, bool bEnable = true) + { + (bEnable) + ? m_state_stack.back().m_highlight |= mode + : m_state_stack.back().m_highlight &= ~mode; + } + void addRenderable(const OpenGLRenderable& renderable, const Matrix4& localToWorld) + { + if(m_state_stack.back().m_highlight & ePrimitive) + { + m_state_selected->addRenderable(renderable, localToWorld); + } + else + { + m_state_stack.back().m_state->addRenderable(renderable, localToWorld); + } + } + + void render(const Matrix4& modelview, const Matrix4& projection) + { + GlobalShaderCache().render(m_globalstate, modelview, projection); + } +private: + std::vector m_state_stack; + RenderStateFlags m_globalstate; + Shader* m_state_selected; +}; + +void XYWnd::updateProjection() +{ + m_projection[0] = 1.0f / static_cast(m_nWidth / 2); + m_projection[5] = 1.0f / static_cast(m_nHeight / 2); + m_projection[10] = 1.0f / (g_MaxWorldCoord * m_fScale); + + m_projection[12] = 0.0f; + m_projection[13] = 0.0f; + m_projection[14] = -1.0f; + + m_projection[1] = + m_projection[2] = + m_projection[3] = + + m_projection[4] = + m_projection[6] = + m_projection[7] = + + m_projection[8] = + m_projection[9] = + m_projection[11] = 0.0f; + + m_projection[15] = 1.0f; + + m_view.Construct(m_projection, m_modelview, m_nWidth, m_nHeight); +} + +// note: modelview matrix must have a uniform scale, otherwise strange things happen when rendering the rotation manipulator. +void XYWnd::updateModelview() +{ + int nDim1 = (m_viewType == YZ) ? 1 : 0; + int nDim2 = (m_viewType == XY) ? 1 : 2; + + // translation + m_modelview[12] = -m_vOrigin[nDim1] * m_fScale; + m_modelview[13] = -m_vOrigin[nDim2] * m_fScale; + m_modelview[14] = g_MaxWorldCoord * m_fScale; + + // axis base + switch(m_viewType) + { + case XY: + m_modelview[0] = m_fScale; + m_modelview[1] = 0; + m_modelview[2] = 0; + + m_modelview[4] = 0; + m_modelview[5] = m_fScale; + m_modelview[6] = 0; + + m_modelview[8] = 0; + m_modelview[9] = 0; + m_modelview[10] = -m_fScale; + break; + case XZ: + m_modelview[0] = m_fScale; + m_modelview[1] = 0; + m_modelview[2] = 0; + + m_modelview[4] = 0; + m_modelview[5] = 0; + m_modelview[6] = m_fScale; + + m_modelview[8] = 0; + m_modelview[9] = m_fScale; + m_modelview[10] = 0; + break; + case YZ: + m_modelview[0] = 0; + m_modelview[1] = 0; + m_modelview[2] = -m_fScale; + + m_modelview[4] = m_fScale; + m_modelview[5] = 0; + m_modelview[6] = 0; + + m_modelview[8] = 0; + m_modelview[9] = m_fScale; + m_modelview[10] = 0; + break; + } + + m_modelview[3] = m_modelview[7] = m_modelview[11] = 0; + m_modelview[15] = 1; + + m_view.Construct(m_projection, m_modelview, m_nWidth, m_nHeight); +} + +/* +============== +XY_Draw +============== +*/ + +//#define DBG_SCENEDUMP + +void XYWnd::XY_Draw() +{ + // + // clear + // + glViewport(0, 0, m_nWidth, m_nHeight); + glClearColor (g_xywindow_globals.color_gridback[0], + g_xywindow_globals.color_gridback[1], + g_xywindow_globals.color_gridback[2],0); + + glClear(GL_COLOR_BUFFER_BIT); + + // + // set up viewpoint + // + + glMatrixMode(GL_PROJECTION); + glLoadMatrixf(reinterpret_cast(&m_projection)); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glScalef(m_fScale, m_fScale, 1); + int nDim1 = (m_viewType == YZ) ? 1 : 0; + int nDim2 = (m_viewType == XY) ? 1 : 2; + glTranslatef(-m_vOrigin[nDim1], -m_vOrigin[nDim2], 0); + + glDisable (GL_LINE_STIPPLE); + glLineWidth(1); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glDisable(GL_TEXTURE_2D); + glDisable(GL_LIGHTING); + glDisable(GL_COLOR_MATERIAL); + glDisable(GL_DEPTH_TEST); + + if (m_backgroundActivated) + XY_DrawBackground(); + XY_DrawGrid(); + + if ( g_xywindow_globals_private.show_blocks) + XY_DrawBlockGrid(); + + glLoadMatrixf(reinterpret_cast(&m_modelview)); + + unsigned int globalstate = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_POLYGONSMOOTH | RENDER_LINESMOOTH; + if(!g_xywindow_globals.m_bNoStipple) + { + globalstate |= RENDER_LINESTIPPLE; + } + + { + XYRenderer renderer(globalstate, m_state_selected); + + Scene_Render(renderer, m_view); + + GlobalOpenGL_debugAssertNoErrors(); + renderer.render(m_modelview, m_projection); + GlobalOpenGL_debugAssertNoErrors(); + } + + glDepthMask(GL_FALSE); + + GlobalOpenGL_debugAssertNoErrors(); + + glLoadMatrixf(reinterpret_cast(&m_modelview)); + + GlobalOpenGL_debugAssertNoErrors(); + glDisable(GL_LINE_STIPPLE); + GlobalOpenGL_debugAssertNoErrors(); + glLineWidth(1); + GlobalOpenGL_debugAssertNoErrors(); + if(GlobalOpenGL().GL_1_3()) + { + glActiveTexture(GL_TEXTURE0); + glClientActiveTexture(GL_TEXTURE0); + } + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + GlobalOpenGL_debugAssertNoErrors(); + glDisableClientState(GL_NORMAL_ARRAY); + GlobalOpenGL_debugAssertNoErrors(); + glDisableClientState(GL_COLOR_ARRAY); + GlobalOpenGL_debugAssertNoErrors(); + glDisable(GL_TEXTURE_2D); + GlobalOpenGL_debugAssertNoErrors(); + glDisable(GL_LIGHTING); + GlobalOpenGL_debugAssertNoErrors(); + glDisable(GL_COLOR_MATERIAL); + GlobalOpenGL_debugAssertNoErrors(); + + GlobalOpenGL_debugAssertNoErrors(); + + + // size info + if(g_xywindow_globals_private.m_bSizePaint && GlobalSelectionSystem().countSelected() != 0) + { + Vector3 min, max; + Select_GetBounds(min, max); + PaintSizeInfo(nDim1, nDim2, min, max); + } + + if (g_bCrossHairs) + { + glColor4f(0.2f, 0.9f, 0.2f, 0.8f); + glBegin (GL_LINES); + if (m_viewType == XY) + { + glVertex2f(2.0f * g_MinWorldCoord, m_mousePosition[1]); + glVertex2f(2.0f * g_MaxWorldCoord, m_mousePosition[1]); + glVertex2f(m_mousePosition[0], 2.0f * g_MinWorldCoord); + glVertex2f(m_mousePosition[0], 2.0f * g_MaxWorldCoord); + } + else if (m_viewType == YZ) + { + glVertex3f(m_mousePosition[0], 2.0f * g_MinWorldCoord, m_mousePosition[2]); + glVertex3f(m_mousePosition[0], 2.0f * g_MaxWorldCoord, m_mousePosition[2]); + glVertex3f(m_mousePosition[0], m_mousePosition[1], 2.0f * g_MinWorldCoord); + glVertex3f(m_mousePosition[0], m_mousePosition[1], 2.0f * g_MaxWorldCoord); + } + else + { + glVertex3f (2.0f * g_MinWorldCoord, m_mousePosition[1], m_mousePosition[2]); + glVertex3f (2.0f * g_MaxWorldCoord, m_mousePosition[1], m_mousePosition[2]); + glVertex3f(m_mousePosition[0], m_mousePosition[1], 2.0f * g_MinWorldCoord); + glVertex3f(m_mousePosition[0], m_mousePosition[1], 2.0f * g_MaxWorldCoord); + } + glEnd(); + } + + if (ClipMode()) + { + GlobalClipPoints_Draw(m_fScale); + } + + GlobalOpenGL_debugAssertNoErrors(); + + // reset modelview + glLoadIdentity(); + glScalef(m_fScale, m_fScale, 1); + glTranslatef(-m_vOrigin[nDim1], -m_vOrigin[nDim2], 0); + + DrawCameraIcon (Camera_getOrigin(*g_pParentWnd->GetCamWnd()), Camera_getAngles(*g_pParentWnd->GetCamWnd())); + + Feedback_draw2D( m_viewType ); + + if (g_xywindow_globals_private.show_outline) + { + if (Active()) + { + glMatrixMode (GL_PROJECTION); + glLoadIdentity(); + glOrtho (0, m_nWidth, 0, m_nHeight, 0, 1); + + glMatrixMode (GL_MODELVIEW); + glLoadIdentity(); + + // four view mode doesn't colorize + if (g_pParentWnd->CurrentStyle() == MainFrame::eSplit) + glColor3fv(vector3_to_array(g_xywindow_globals.color_viewname)); + else + { + switch(m_viewType) + { + case YZ: + glColor3fv(vector3_to_array(g_xywindow_globals.AxisColorX)); + break; + case XZ: + glColor3fv(vector3_to_array(g_xywindow_globals.AxisColorY)); + break; + case XY: + glColor3fv(vector3_to_array(g_xywindow_globals.AxisColorZ)); + break; + } + } + glBegin (GL_LINE_LOOP); + glVertex2i (0, 0); + glVertex2i (m_nWidth-1, 0); + glVertex2i (m_nWidth-1, m_nHeight-1); + glVertex2i (0, m_nHeight-1); + glEnd(); + } + } + + GlobalOpenGL_debugAssertNoErrors(); + + glFinish(); +} + +void XYWnd_MouseToPoint(XYWnd* xywnd, int x, int y, Vector3& point) +{ + xywnd->XY_ToPoint(x, y, point); + xywnd->XY_SnapToGrid(point); + + int nDim = (xywnd->GetViewType() == XY) ? 2 : (xywnd->GetViewType() == YZ) ? 0 : 1; + float fWorkMid = float_mid(Select_getWorkZone().d_work_min[nDim], Select_getWorkZone().d_work_max[nDim]); + point[nDim] = float_snapped(fWorkMid, GetGridSize()); +} + +void XYWnd::OnEntityCreate (const char* item) +{ + StringOutputStream command; + command << "entityCreate -class " << item; + UndoableCommand undo(command.c_str()); + Vector3 point; + XYWnd_MouseToPoint(this, m_entityCreate_x, m_entityCreate_y, point); + Entity_createFromSelection(item, point); +} + + + +void GetFocusPosition(Vector3& position) +{ + if(GlobalSelectionSystem().countSelected() != 0) + { + Select_GetMid(position); + } + else + { + position = Camera_getOrigin(*g_pParentWnd->GetCamWnd()); + } +} + +void XYWnd_Focus(XYWnd* xywnd) +{ + Vector3 position; + GetFocusPosition(position); + xywnd->PositionView(position); +} + +void XY_Split_Focus() +{ + Vector3 position; + GetFocusPosition(position); + g_pParentWnd->GetXYWnd()->PositionView(position); + g_pParentWnd->GetXZWnd()->PositionView(position); + g_pParentWnd->GetYZWnd()->PositionView(position); +} + +void XY_Focus() +{ + XYWnd* xywnd = g_pParentWnd->GetXYWnd(); + XYWnd_Focus(xywnd); +} + +void XY_Top() +{ + XYWnd* xywnd = g_pParentWnd->GetXYWnd(); + xywnd->SetViewType(XY); + XYWnd_Focus(xywnd); +} + +void XY_Side() +{ + XYWnd* xywnd = g_pParentWnd->GetXYWnd(); + xywnd->SetViewType(XZ); + XYWnd_Focus(xywnd); +} + +void XY_Front() +{ + g_pParentWnd->GetXYWnd()->SetViewType(YZ); + XYWnd_Focus(g_pParentWnd->GetXYWnd()); +} + +void XY_Next() +{ + XYWnd* xywnd = g_pParentWnd->GetXYWnd(); + if (xywnd->GetViewType() == XY) + xywnd->SetViewType(XZ); + else if (xywnd->GetViewType() == XZ) + xywnd->SetViewType(YZ); + else + xywnd->SetViewType(XY); + XYWnd_Focus(xywnd); +} + +void XY_Zoom100() +{ + if (g_pParentWnd->GetXYWnd()) + g_pParentWnd->GetXYWnd()->SetScale(1); + if (g_pParentWnd->GetXZWnd()) + g_pParentWnd->GetXZWnd()->SetScale(1); + if (g_pParentWnd->GetYZWnd()) + g_pParentWnd->GetYZWnd()->SetScale(1); +} + +void XY_ZoomIn() +{ + XYWnd_ZoomIn(g_pParentWnd->ActiveXY()); +} + +// NOTE: the zoom out factor is 4/5, we could think about customizing it +// we don't go below a zoom factor corresponding to 10% of the max world size +// (this has to be computed against the window size) +void XY_ZoomOut() +{ + XYWnd_ZoomOut(g_pParentWnd->ActiveXY()); +} + + + +void ToggleShowCrosshair() +{ + g_bCrossHairs ^= 1; + XY_UpdateAllWindows(); +} + +void ToggleShowSizeInfo() +{ + g_xywindow_globals_private.m_bSizePaint = !g_xywindow_globals_private.m_bSizePaint; + XY_UpdateAllWindows(); +} + +void ToggleShowGrid() +{ + g_xywindow_globals_private.d_showgrid = !g_xywindow_globals_private.d_showgrid; + XY_UpdateAllWindows(); +} + +ToggleShown g_xy_top_shown(true); + +void XY_Top_Shown_Construct(GtkWindow* parent) +{ + g_xy_top_shown.connect(GTK_WIDGET(parent)); +} + +ToggleShown g_yz_side_shown(false); + +void YZ_Side_Shown_Construct(GtkWindow* parent) +{ + g_yz_side_shown.connect(GTK_WIDGET(parent)); +} + +ToggleShown g_xz_front_shown(false); + +void XZ_Front_Shown_Construct(GtkWindow* parent) +{ + g_xz_front_shown.connect(GTK_WIDGET(parent)); +} + + +class EntityClassMenu : public ModuleObserver +{ + std::size_t m_unrealised; +public: + EntityClassMenu() : m_unrealised(1) + { + } + void realise() + { + if(--m_unrealised == 0) + { + } + } + void unrealise() + { + if(++m_unrealised == 1) + { + if(XYWnd::m_mnuDrop != 0) + { + gtk_widget_destroy(GTK_WIDGET(XYWnd::m_mnuDrop)); + XYWnd::m_mnuDrop = 0; + } + } + } +}; + +EntityClassMenu g_EntityClassMenu; + + + + +void ShowNamesToggle() +{ + GlobalEntityCreator().setShowNames(!GlobalEntityCreator().getShowNames()); + XY_UpdateAllWindows(); +} +typedef FreeCaller ShowNamesToggleCaller; +void ShowNamesExport(const BoolImportCallback& importer) +{ + importer(GlobalEntityCreator().getShowNames()); +} +typedef FreeCaller1 ShowNamesExportCaller; + +void ShowAnglesToggle() +{ + GlobalEntityCreator().setShowAngles(!GlobalEntityCreator().getShowAngles()); + XY_UpdateAllWindows(); +} +typedef FreeCaller ShowAnglesToggleCaller; +void ShowAnglesExport(const BoolImportCallback& importer) +{ + importer(GlobalEntityCreator().getShowAngles()); +} +typedef FreeCaller1 ShowAnglesExportCaller; + +void ShowBlocksToggle() +{ + g_xywindow_globals_private.show_blocks ^= 1; + XY_UpdateAllWindows(); +} +typedef FreeCaller ShowBlocksToggleCaller; +void ShowBlocksExport(const BoolImportCallback& importer) +{ + importer(g_xywindow_globals_private.show_blocks); +} +typedef FreeCaller1 ShowBlocksExportCaller; + +void ShowCoordinatesToggle() +{ + g_xywindow_globals_private.show_coordinates ^= 1; + XY_UpdateAllWindows(); +} +typedef FreeCaller ShowCoordinatesToggleCaller; +void ShowCoordinatesExport(const BoolImportCallback& importer) +{ + importer(g_xywindow_globals_private.show_coordinates); +} +typedef FreeCaller1 ShowCoordinatesExportCaller; + +void ShowOutlineToggle() +{ + g_xywindow_globals_private.show_outline ^= 1; + XY_UpdateAllWindows(); +} +typedef FreeCaller ShowOutlineToggleCaller; +void ShowOutlineExport(const BoolImportCallback& importer) +{ + importer(g_xywindow_globals_private.show_outline); +} +typedef FreeCaller1 ShowOutlineExportCaller; + +void ShowAxesToggle() +{ + g_xywindow_globals_private.show_axis ^= 1; + XY_UpdateAllWindows(); +} +typedef FreeCaller ShowAxesToggleCaller; +void ShowAxesExport(const BoolImportCallback& importer) +{ + importer(g_xywindow_globals_private.show_axis); +} +typedef FreeCaller1 ShowAxesExportCaller; + +void ShowWorkzoneToggle() +{ + g_xywindow_globals_private.d_show_work ^= 1; + XY_UpdateAllWindows(); +} +typedef FreeCaller ShowWorkzoneToggleCaller; +void ShowWorkzoneExport(const BoolImportCallback& importer) +{ + importer(g_xywindow_globals_private.d_show_work); +} +typedef FreeCaller1 ShowWorkzoneExportCaller; + +ShowNamesExportCaller g_show_names_caller; +BoolExportCallback g_show_names_callback(g_show_names_caller); +ToggleItem g_show_names(g_show_names_callback); + +ShowAnglesExportCaller g_show_angles_caller; +BoolExportCallback g_show_angles_callback(g_show_angles_caller); +ToggleItem g_show_angles(g_show_angles_callback); + +ShowBlocksExportCaller g_show_blocks_caller; +BoolExportCallback g_show_blocks_callback(g_show_blocks_caller); +ToggleItem g_show_blocks(g_show_blocks_callback); + +ShowCoordinatesExportCaller g_show_coordinates_caller; +BoolExportCallback g_show_coordinates_callback(g_show_coordinates_caller); +ToggleItem g_show_coordinates(g_show_coordinates_callback); + +ShowOutlineExportCaller g_show_outline_caller; +BoolExportCallback g_show_outline_callback(g_show_outline_caller); +ToggleItem g_show_outline(g_show_outline_callback); + +ShowAxesExportCaller g_show_axes_caller; +BoolExportCallback g_show_axes_callback(g_show_axes_caller); +ToggleItem g_show_axes(g_show_axes_callback); + +ShowWorkzoneExportCaller g_show_workzone_caller; +BoolExportCallback g_show_workzone_callback(g_show_workzone_caller); +ToggleItem g_show_workzone(g_show_workzone_callback); + +void XYShow_registerCommands() +{ + GlobalToggles_insert("ShowAngles", ShowAnglesToggleCaller(), ToggleItem::AddCallbackCaller(g_show_angles)); + GlobalToggles_insert("ShowNames", ShowNamesToggleCaller(), ToggleItem::AddCallbackCaller(g_show_names)); + GlobalToggles_insert("ShowBlocks", ShowBlocksToggleCaller(), ToggleItem::AddCallbackCaller(g_show_blocks)); + GlobalToggles_insert("ShowCoordinates", ShowCoordinatesToggleCaller(), ToggleItem::AddCallbackCaller(g_show_coordinates)); + GlobalToggles_insert("ShowWindowOutline", ShowOutlineToggleCaller(), ToggleItem::AddCallbackCaller(g_show_outline)); + GlobalToggles_insert("ShowAxes", ShowAxesToggleCaller(), ToggleItem::AddCallbackCaller(g_show_axes)); + GlobalToggles_insert("ShowWorkzone", ShowWorkzoneToggleCaller(), ToggleItem::AddCallbackCaller(g_show_workzone)); +} + +void XYWnd_registerShortcuts() +{ + command_connect_accelerator("ToggleCrosshairs"); + command_connect_accelerator("ToggleSizePaint"); +} + + + +void Orthographic_constructPreferences(PreferencesPage& page) +{ + page.appendCheckBox("", "Solid selection boxes", g_xywindow_globals.m_bNoStipple); + page.appendCheckBox("", "Display size info", g_xywindow_globals_private.m_bSizePaint); + page.appendCheckBox("", "Chase mouse during drags", g_xywindow_globals_private.m_bChaseMouse); + page.appendCheckBox("", "Update views on camera move", g_xywindow_globals_private.m_bCamXYUpdate); +} +void Orthographic_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Orthographic", "Orthographic View Preferences")); + Orthographic_constructPreferences(page); +} +void Orthographic_registerPreferencesPage() +{ + PreferencesDialog_addSettingsPage(FreeCaller1()); +} + +void Clipper_constructPreferences(PreferencesPage& page) +{ + page.appendCheckBox("", "Clipper tool uses caulk", g_clip_useCaulk); +} +void Clipper_constructPage(PreferenceGroup& group) +{ + PreferencesPage page(group.createPage("Clipper", "Clipper Tool Settings")); + Clipper_constructPreferences(page); +} +void Clipper_registerPreferencesPage() +{ + PreferencesDialog_addSettingsPage(FreeCaller1()); +} + + +#include "preferencesystem.h" +#include "stringio.h" + + + + +void ToggleShown_importBool(ToggleShown& self, bool value) +{ + self.set(value); +} +typedef ReferenceCaller1 ToggleShownImportBoolCaller; +void ToggleShown_exportBool(const ToggleShown& self, const BoolImportCallback& importer) +{ + importer(self.active()); +} +typedef ConstReferenceCaller1 ToggleShownExportBoolCaller; + + +void XYWindow_Construct() +{ + GlobalCommands_insert("ToggleCrosshairs", FreeCaller(), Accelerator('X', (GdkModifierType)GDK_SHIFT_MASK)); + GlobalCommands_insert("ToggleSizePaint", FreeCaller(), Accelerator('J')); + GlobalCommands_insert("ToggleGrid", FreeCaller(), Accelerator('0')); + + GlobalToggles_insert("ToggleView", ToggleShown::ToggleCaller(g_xy_top_shown), ToggleItem::AddCallbackCaller(g_xy_top_shown.m_item), Accelerator('V', (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + GlobalToggles_insert("ToggleSideView", ToggleShown::ToggleCaller(g_yz_side_shown), ToggleItem::AddCallbackCaller(g_yz_side_shown.m_item)); + GlobalToggles_insert("ToggleFrontView", ToggleShown::ToggleCaller(g_xz_front_shown), ToggleItem::AddCallbackCaller(g_xz_front_shown.m_item)); + GlobalCommands_insert("NextView", FreeCaller(), Accelerator(GDK_Tab, (GdkModifierType)GDK_CONTROL_MASK)); + GlobalCommands_insert("ZoomIn", FreeCaller(), Accelerator(GDK_Delete)); + GlobalCommands_insert("ZoomOut", FreeCaller(), Accelerator(GDK_Insert)); + GlobalCommands_insert("ViewTop", FreeCaller()); + GlobalCommands_insert("ViewSide", FreeCaller()); + GlobalCommands_insert("ViewFront", FreeCaller()); + GlobalCommands_insert("Zoom100", FreeCaller()); + GlobalCommands_insert("CenterXYViews", FreeCaller(), Accelerator(GDK_Tab, (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + GlobalCommands_insert("CenterXYView", FreeCaller(), Accelerator(GDK_Tab, (GdkModifierType)(GDK_SHIFT_MASK|GDK_CONTROL_MASK))); + + GlobalPreferenceSystem().registerPreference("ClipCaulk", BoolImportStringCaller(g_clip_useCaulk), BoolExportStringCaller(g_clip_useCaulk)); + + GlobalPreferenceSystem().registerPreference("NewRightClick", BoolImportStringCaller(g_xywindow_globals.m_bRightClick), BoolExportStringCaller(g_xywindow_globals.m_bRightClick)); + GlobalPreferenceSystem().registerPreference("ChaseMouse", BoolImportStringCaller(g_xywindow_globals_private.m_bChaseMouse), BoolExportStringCaller(g_xywindow_globals_private.m_bChaseMouse)); + GlobalPreferenceSystem().registerPreference("SizePainting", BoolImportStringCaller(g_xywindow_globals_private.m_bSizePaint), BoolExportStringCaller(g_xywindow_globals_private.m_bSizePaint)); + GlobalPreferenceSystem().registerPreference("NoStipple", BoolImportStringCaller(g_xywindow_globals.m_bNoStipple), BoolExportStringCaller(g_xywindow_globals.m_bNoStipple)); + GlobalPreferenceSystem().registerPreference("SI_ShowCoords", BoolImportStringCaller(g_xywindow_globals_private.show_coordinates), BoolExportStringCaller(g_xywindow_globals_private.show_coordinates)); + GlobalPreferenceSystem().registerPreference("SI_ShowOutlines", BoolImportStringCaller(g_xywindow_globals_private.show_outline), BoolExportStringCaller(g_xywindow_globals_private.show_outline)); + GlobalPreferenceSystem().registerPreference("SI_ShowAxis", BoolImportStringCaller(g_xywindow_globals_private.show_axis), BoolExportStringCaller(g_xywindow_globals_private.show_axis)); + GlobalPreferenceSystem().registerPreference("CamXYUpdate", BoolImportStringCaller(g_xywindow_globals_private.m_bCamXYUpdate), BoolExportStringCaller(g_xywindow_globals_private.m_bCamXYUpdate)); + GlobalPreferenceSystem().registerPreference("ShowWorkzone", BoolImportStringCaller(g_xywindow_globals_private.d_show_work), BoolExportStringCaller(g_xywindow_globals_private.d_show_work)); + + GlobalPreferenceSystem().registerPreference("SI_AxisColors0", Vector3ImportStringCaller(g_xywindow_globals.AxisColorX), Vector3ExportStringCaller(g_xywindow_globals.AxisColorX)); + GlobalPreferenceSystem().registerPreference("SI_AxisColors1", Vector3ImportStringCaller(g_xywindow_globals.AxisColorY), Vector3ExportStringCaller(g_xywindow_globals.AxisColorY)); + GlobalPreferenceSystem().registerPreference("SI_AxisColors2", Vector3ImportStringCaller(g_xywindow_globals.AxisColorZ), Vector3ExportStringCaller(g_xywindow_globals.AxisColorZ)); + GlobalPreferenceSystem().registerPreference("SI_Colors1", Vector3ImportStringCaller(g_xywindow_globals.color_gridback), Vector3ExportStringCaller(g_xywindow_globals.color_gridback)); + GlobalPreferenceSystem().registerPreference("SI_Colors2", Vector3ImportStringCaller(g_xywindow_globals.color_gridminor), Vector3ExportStringCaller(g_xywindow_globals.color_gridminor)); + GlobalPreferenceSystem().registerPreference("SI_Colors3", Vector3ImportStringCaller(g_xywindow_globals.color_gridmajor), Vector3ExportStringCaller(g_xywindow_globals.color_gridmajor)); + GlobalPreferenceSystem().registerPreference("SI_Colors6", Vector3ImportStringCaller(g_xywindow_globals.color_gridblock), Vector3ExportStringCaller(g_xywindow_globals.color_gridblock)); + GlobalPreferenceSystem().registerPreference("SI_Colors7", Vector3ImportStringCaller(g_xywindow_globals.color_gridtext), Vector3ExportStringCaller(g_xywindow_globals.color_gridtext)); + GlobalPreferenceSystem().registerPreference("SI_Colors8", Vector3ImportStringCaller(g_xywindow_globals.color_brushes), Vector3ExportStringCaller(g_xywindow_globals.color_brushes)); + GlobalPreferenceSystem().registerPreference("SI_Colors9", Vector3ImportStringCaller(g_xywindow_globals.color_selbrushes), Vector3ExportStringCaller(g_xywindow_globals.color_selbrushes)); + GlobalPreferenceSystem().registerPreference("SI_Colors10", Vector3ImportStringCaller(g_xywindow_globals.color_clipper), Vector3ExportStringCaller(g_xywindow_globals.color_clipper)); + GlobalPreferenceSystem().registerPreference("SI_Colors11", Vector3ImportStringCaller(g_xywindow_globals.color_viewname), Vector3ExportStringCaller(g_xywindow_globals.color_viewname)); + GlobalPreferenceSystem().registerPreference("SI_Colors13", Vector3ImportStringCaller(g_xywindow_globals.color_gridminor_alt), Vector3ExportStringCaller(g_xywindow_globals.color_gridminor_alt)); + GlobalPreferenceSystem().registerPreference("SI_Colors14", Vector3ImportStringCaller(g_xywindow_globals.color_gridmajor_alt), Vector3ExportStringCaller(g_xywindow_globals.color_gridmajor_alt)); + + + GlobalPreferenceSystem().registerPreference("XZVIS", makeBoolStringImportCallback(ToggleShownImportBoolCaller(g_xz_front_shown)), makeBoolStringExportCallback(ToggleShownExportBoolCaller(g_xz_front_shown))); + GlobalPreferenceSystem().registerPreference("YZVIS", makeBoolStringImportCallback(ToggleShownImportBoolCaller(g_yz_side_shown)), makeBoolStringExportCallback(ToggleShownExportBoolCaller(g_yz_side_shown))); + + Orthographic_registerPreferencesPage(); + Clipper_registerPreferencesPage(); + + XYWnd::captureStates(); + GlobalEntityClassManager().attach(g_EntityClassMenu); +} + +void XYWindow_Destroy() +{ + GlobalEntityClassManager().detach(g_EntityClassMenu); + XYWnd::releaseStates(); +} diff --git a/radiant/xywindow.h b/radiant/xywindow.h new file mode 100644 index 00000000..fd795c45 --- /dev/null +++ b/radiant/xywindow.h @@ -0,0 +1,315 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if !defined(INCLUDED_XYWINDOW_H) +#define INCLUDED_XYWINDOW_H + +#include "math/matrix.h" +#include "signal/signal.h" + +#include "gtkutil/cursor.h" +#include "gtkutil/window.h" +#include "gtkutil/xorrectangle.h" +#include "view.h" +#include "map.h" +#include "texturelib.h" + +#include "qerplugin.h" + +class Shader; +class SelectionSystemWindowObserver; +namespace scene +{ + class Node; +} +typedef struct _GtkWindow GtkWindow; +typedef struct _GtkMenu GtkMenu; + + +void FlipClip(); +void SplitClip(); +void Clip(); +void OnClipMode(bool enabled); +bool ClipMode(); + +inline const char* ViewType_getTitle(VIEWTYPE viewtype) +{ + if(viewtype == XY) + { + return "XY Top"; + } + if(viewtype == XZ) + { + return "XZ Front"; + } + if(viewtype == YZ) + { + return "YZ Side"; + } + return ""; +} + +class XYWnd +{ + GtkWidget* m_gl_widget; + guint m_sizeHandler; + guint m_exposeHandler; + + DeferredDraw m_deferredDraw; + DeferredMotion m_deferred_motion; +public: + GtkWindow* m_parent; + XYWnd(); + ~XYWnd(); + + void queueDraw() + { + m_deferredDraw.draw(); + } + GtkWidget* GetWidget() + { + return m_gl_widget; + } + +public: + SelectionSystemWindowObserver* m_window_observer; + XORRectangle m_XORRectangle; + WindowPositionTracker m_positionTracker; + + static void captureStates(); + static void releaseStates(); + + void PositionView(const Vector3& position); + const Vector3& GetOrigin(); + void SetOrigin(const Vector3& origin); + void Scroll(int x, int y); + + void XY_Draw(); + void DrawCameraIcon(const Vector3& origin, const Vector3& angles); + void XY_DrawBlockGrid(); + void XY_DrawAxis(); + void XY_DrawGrid(); + void XY_DrawBackground(); + void XY_LoadBackgroundImage(const char *name); + void XY_DisableBackground(); + + void XY_MouseUp(int x, int y, unsigned int buttons); + void XY_MouseDown(int x, int y, unsigned int buttons); + void XY_MouseMoved(int x, int y, unsigned int buttons); + + void NewBrushDrag_Begin(int x, int y); + void NewBrushDrag(int x, int y); + void NewBrushDrag_End(int x, int y); + + void XY_ToPoint(int x, int y, Vector3& point); + void XY_SnapToGrid(Vector3& point); + + void Move_Begin(); + void Move_End(); + bool m_move_started; + guint m_move_focusOut; + + void Zoom_Begin(); + void Zoom_End(); + bool m_zoom_started; + guint m_zoom_focusOut; + + void SetActive(bool b) + { + m_bActive = b; + }; + bool Active() + { + return m_bActive; + }; + + void Clipper_OnLButtonDown(int x, int y); + void Clipper_OnLButtonUp(int x, int y); + void Clipper_OnMouseMoved(int x, int y); + void Clipper_Crosshair_OnMouseMoved(int x, int y); + void DropClipPoint(int pointx, int pointy); + + void SetViewType(VIEWTYPE n); + bool m_bActive; + + static GtkMenu* m_mnuDrop; + + int m_chasemouse_current_x, m_chasemouse_current_y; + int m_chasemouse_delta_x, m_chasemouse_delta_y; + + + guint m_chasemouse_handler; + void ChaseMouse(); + bool chaseMouseMotion(int pointx, int pointy); + + void updateModelview(); + void updateProjection(); + Matrix4 m_projection; + Matrix4 m_modelview; + + int m_nWidth; + int m_nHeight; + // background image stuff + qtexture_t *m_tex; + bool m_backgroundActivated; + float m_alpha; // vertex alpha + float m_xmin, m_ymin, m_xmax, m_ymax; +private: + float m_fScale; + Vector3 m_vOrigin; + + + View m_view; + static Shader* m_state_selected; + + int m_ptCursorX, m_ptCursorY; + + unsigned int m_buttonstate; + + int m_nNewBrushPressx; + int m_nNewBrushPressy; + scene::Node* m_NewBrushDrag; + bool m_bNewBrushDrag; + + Vector3 m_mousePosition; + + VIEWTYPE m_viewType; + + void OriginalButtonUp(guint32 nFlags, int point, int pointy); + void OriginalButtonDown(guint32 nFlags, int point, int pointy); + + void OnContextMenu(); + void PaintSizeInfo(int nDim1, int nDim2, Vector3& vMinBounds, Vector3& vMaxBounds); + + int m_entityCreate_x, m_entityCreate_y; + bool m_entityCreate; + +public: + void ButtonState_onMouseDown(unsigned int buttons) + { + m_buttonstate |= buttons; + } + void ButtonState_onMouseUp(unsigned int buttons) + { + m_buttonstate &= ~buttons; + } + unsigned int getButtonState() const + { + return m_buttonstate; + } + void EntityCreate_MouseDown(int x, int y); + void EntityCreate_MouseMove(int x, int y); + void EntityCreate_MouseUp(int x, int y); + + void OnEntityCreate(const char* item); + VIEWTYPE GetViewType() + { + return m_viewType; + } + void SetScale(float f); + float Scale() + { + return m_fScale; + } + int Width() + { + return m_nWidth; + } + int Height() + { + return m_nHeight; + } + + Signal0 onDestroyed; + Signal3 onMouseDown; + void mouseDown(const WindowVector& position, ButtonIdentifier button, ModifierFlags modifiers); + typedef Member3 MouseDownCaller; +}; + +inline void XYWnd_Update(XYWnd& xywnd) +{ + xywnd.queueDraw(); +} + + +struct xywindow_globals_t +{ + Vector3 color_gridback; + Vector3 color_gridminor; + Vector3 color_gridmajor; + Vector3 color_gridblock; + Vector3 color_gridtext; + Vector3 color_brushes; + Vector3 color_selbrushes; + Vector3 color_clipper; + Vector3 color_viewname; + Vector3 color_gridminor_alt; + Vector3 color_gridmajor_alt; + Vector3 AxisColorX; + Vector3 AxisColorY; + Vector3 AxisColorZ; + + bool m_bRightClick; + bool m_bNoStipple; + + xywindow_globals_t() : + color_gridback(1.f, 1.f, 1.f), + color_gridminor(0.75f, 0.75f, 0.75f), + color_gridmajor(0.5f, 0.5f, 0.5f), + color_gridblock(0.f, 0.f, 1.f), + color_gridtext(0.f, 0.f, 0.f), + color_brushes(0.f, 0.f, 0.f), + color_selbrushes(1.f, 0.f, 0.f), + color_clipper(0.f, 0.f, 1.f), + color_viewname(0.5f, 0.f, 0.75f), + color_gridminor_alt(0.f, 0.f, 0.f), + color_gridmajor_alt(0.f, 0.f, 0.f), + + AxisColorX(1.f, 0.f, 0.f), + AxisColorY(0.f, 1.f, 0.f), + AxisColorZ(0.f, 0.f, 1.f), + m_bRightClick(true), + m_bNoStipple(false) + { + } + +}; + +extern xywindow_globals_t g_xywindow_globals; + + +VIEWTYPE GlobalXYWnd_getCurrentViewType(); + +typedef struct _GtkWindow GtkWindow; +void XY_Top_Shown_Construct(GtkWindow* parent); +void YZ_Side_Shown_Construct(GtkWindow* parent); +void XZ_Front_Shown_Construct(GtkWindow* parent); + +void XYWindow_Construct(); +void XYWindow_Destroy(); + +void WXY_Print(); +void WXY_BackgroundSelect(); + +void XYShow_registerCommands(); +void XYWnd_registerShortcuts(); + +#endif diff --git a/run_python.bat b/run_python.bat new file mode 100644 index 00000000..be3466d2 --- /dev/null +++ b/run_python.bat @@ -0,0 +1,9 @@ +python.exe -V +if errorlevel=1 echo please install python and add python.exe to the path (http://www.python.org) +if errorlevel=1 goto end + +echo python.exe %1 %2 %3 %4 %5 %6 %7 %8 %9 +rem FIXME: for some wacked reason, output of the python script doesn't get back to VC window .. +python.exe %1 %2 %3 %4 %5 %6 %7 %8 %9 + +:end \ No newline at end of file diff --git a/setup/PluginSDK/BuildGtkSrc b/setup/PluginSDK/BuildGtkSrc new file mode 100644 index 00000000..5756b64e --- /dev/null +++ b/setup/PluginSDK/BuildGtkSrc @@ -0,0 +1,13 @@ +#!/bin/sh +# temporary.. + +# grab the gtk devel package +# FIXME: for win32 SDK only +echo "Updating src/" +echo "NOTE: check we are using the latest gtk dev packages" +echo "NOTE: we are not putting our patched versions in" +rm -rf src +unzip /cygdrive/c/Download/Gtk-20001023/glib-dev-20001023.zip +unzip /cygdrive/c/Download/Gtk-20001023/gtk+-dev-20000722.zip +unzip /cygdrive/c/Download/Gtk-20001023/libiconv-dev-20001007.zip +unzip /cygdrive/c/Download/Gtk-20001023/extralibs-dev-20001007.zip diff --git a/setup/PluginSDK/BuildSDK b/setup/PluginSDK/BuildSDK new file mode 100644 index 00000000..882a5cd5 --- /dev/null +++ b/setup/PluginSDK/BuildSDK @@ -0,0 +1,53 @@ +#!/bin/sh + +# what I use to run this script on my win32 box: +# have cygwin, perl (ActiveState) and the cygwin zip utility +# for zip: http://www.hirmke.de/software/develop/gnuwin32/cygwin/porters/Hirmke_Michael/GNUWin32-links.html#zip-2.3%20(cygwin1.1) + +# FIXME: I'd like to have this same script used for both platforms +# for the SDK we recreate a smaller tree and only put the stuff we want +# what potentially changes across platforms is the binaries and the src/ subtree for gtk stuff + +# proceed in order: + +# include directory for all plugin API +mkdir GtkRadiant +mkdir GtkRadiant/include +rm -rf ./GtkRadiant/include/* +echo "Copying GtkRadiant API headers" +cp ../../include/* ./GtkRadiant/include + +# now fill in the plugins menu: +# copy common, textool and the sample plugin +mkdir GtkRadiant +mkdir GtkRadiant/plugins +mkdir GtkRadiant/plugins/common +mkdir GtkRadiant/plugins/textool +mkdir GtkRadiant/plugins/sample +rm -rf ./GtkRadiant/plugins/* +echo "Copying TexTool" +cp -R ../../plugins/textool ./GtkRadiant/plugins/textool +cp -R ../../plugins/common ./GtkRadiant/plugins/common +echo "Copying Sample plugin" +cp -R ../../plugins/sample ./GtkRadiant/plugins/sample + +# get gtkradiant binary (you know you want it) +# FIXME: for win32 SDK only +#cp ../radiant/Release/Q3Radiant.exe GtkRadiant.exe + +# grab the gtk devel package +# FIXME: for win32 SDK only +echo "Updating src/" +echo "NOTE: check we are using the latest gtk dev packages" +echo "NOTE: we are not putting our patched versions in" +rm -rf src +unzip /cygdrive/c/Download/Gtk-20001023/glib-dev-20001023.zip +unzip /cygdrive/c/Download/Gtk-20001023/gtk+-dev-20000722.zip +unzip /cygdrive/c/Download/Gtk-20001023/libiconv-dev-20001007.zip +unzip /cygdrive/c/Download/Gtk-20001023/extralibs-dev-20001007.zip +# grab the src/build directory, we use it with the cygwin makefiles +cp -R ../../../src/build src + +# build the SDK +#echo "Check the sample/ directory is clean" +#zip -r PluginSDK.zip src private sample README.html GtkRadiant.exe \ No newline at end of file diff --git a/setup/PluginSDK/BuildZip b/setup/PluginSDK/BuildZip new file mode 100644 index 00000000..d6731d4e --- /dev/null +++ b/setup/PluginSDK/BuildZip @@ -0,0 +1,6 @@ +#!/bin/sh +# temporary.. + +# build the SDK zip +rm PluginSDK.zip +zip -r PluginSDK.zip src GtkRadiant README.html \ No newline at end of file diff --git a/setup/PluginSDK/README.html b/setup/PluginSDK/README.html new file mode 100644 index 00000000..76bb69a5 --- /dev/null +++ b/setup/PluginSDK/README.html @@ -0,0 +1,171 @@ + + + + + + Q3Radiant plugin SDK + + + + +
GtkRadiant plugin SDK
+
http://www.gtkradiant.com
+ +


+

Version: GtkRadiant +1.1-TA-beta +
Last updated: 01/14/2001
+ +


+


What do I have? + +
GtkRadiant/include/: Radiant plugin API +headers + +
GtkRadiant/plugins/sample/: an empty plugin nutshell, +handy to start a new one (NOTE: a cygwin makefile for Sample is also +provided) + +
GtkRadiant/plugins/textool/: TexTool plugin +source code + +
src/: Gtk headers and libraries for win32 + +

A Word about Gtk: + +
GtkRadiant is using the Gtk toolkit for all the user interface. There's a specific +page for the Gtk on +win32. If you are going to write a plugin for GtkRadiant, we recommend you +use Gtk, but it's not a required feature. You can write plugins using the native +windows API or even VB. Don't hesitate to ask for help on the plugin mailing list or to drop by on irc.telefragged.com +#qeradiant

+ +

Plugin basics: +
main features available to plugins: +

    +
  • +Manipulate MAP data: read and write brushes, patches, epairs and entities
  • + +
  • +Override the BSP menu to process your own building commands
  • + +
  • +Read / Write project settings epairs
  • + +
  • +Use OpenGL to draw in the 2D/3D view, or in your own plugin +windows
  • + +
  • +Use listeners to catch events in Radiant (like a change of +texture or brush select / deselect)
  • + +
  • +Use listeners to listen for window events (for mouse interaction +in the 2D view)
  • + +
  • +Create new plugin entities that show up in the Radiant window, +which you can draw yourself and handle the user interaction
  • + +
  • +Use the Radiant internal parser to hook your own MAP format +changes for plugin entities
  • + +
  • +Access polygon and texture information on the current selected +face
  • + +
  • +Override the texture / shader code to provide your own texture +formats and shader system
  • + +
  • +Add new surface properties, and change the MAP format accordingly
  • +
+overall structure of a plugin: +
A plugin is a DLL (dynamic loading library) that exposes +some API to Radiant. Plugins must be put in the plugins/ directory, they +get loaded at startup by Radiant. Any plugin has a few required entry points +in order to get loaded correctly: +
    +
  • +QERPlug_Init is the first entry point called, used +for initialization and sending back the plugin name that appears in the +console
  • + +
  • +QERPlug_GetName returns the name of the plugin as +it appears in the plugin menu
  • + +
  • +QERPlug_GetCommandList returns a list describing +the items in the plugin submenu
  • + +
  • +QERPlug_GetFuncTable returns the address of the +_QERFuncTable_1 of the plugin. After Radiant got the pointer to the function +table it will fill it and the plugin is able to call into the editor.
  • + +
  • +QERPlug_Dispatch is called when the user hits a +command in the plugin menu. The plugin is then free to process.
  • +
+the function table and interfaces: +
Instead of exporting entry points, Radiant fills in function +pointer tables. The main and required function table is _QERFuncTable_1. +Each plugin must have it and let Radiant fill it. Some additional or specialized +functionalities can be accessed with other tables like _QERQglTable +for GL stuff. These have to be requested to Radiant using _QERFuncTable_1::m_pfnRequestInterface +(see TexTool for an example). Each additional function table has +a "globally unique identifier" (QERQglTable_GUID for GL stuff) +that's used to identify the interface across plugins. +

NOTE: some function tables are used by Radiant and must +be filled by the plugin (ie. they work in reverse compared to usual ones). +In this case the plugin must export an additional entry point int WINAPI +QERPlug_RequestInterface( REFGUID, LPVOID ); +

virtual classes: +
some stuff is better represented by an abstract C++ class. +For those with COM knowledge, it's just a very lightweight COM way of doing +things. These classes have very basic reference counting through IncRef() +and DecRef() .. you are supposed to increment or decrement the +reference count each time you store or release a pointer to these classes, +otherwise they could get unexpectedly erased and you would be very very +sorry. The IListener class in IMessaging.h can be implemented +by the plugin and handed over to Radiant to listen for events. See TexTool +for examples. +
  + +

More stuff: + +
If you need more information or if you want new features for +plugins, see the source code for TexTool and Curry. +TexTool source comes with the plugin SDK, and Curry source is +available from their web site. You +can browse it +online from the +project page. PrtView has neat code +samples on how to draw in the 2D views or camera window. These two plugins are +under GPL licensing. The dedicated place to talk about plugin coding is the plugins-coding mailing +list ... see you there. You may also get an updated list of the plugins for +Radiant on our dedicated web +site.
+ +Update: you can also have a look to the pk3man source.
+ +

Conclusion: +
this document is intended as a quickstart for potential +plugin writers. I hope it meets it's objective. It's not a complete and +systematic documentation, I doubt there will be one ever unless someone +decides to do it. Feel free to send me contributions to this document. +Send feedback to me. + + diff --git a/setup/PluginSDK/TODO b/setup/PluginSDK/TODO new file mode 100644 index 00000000..52f801fe --- /dev/null +++ b/setup/PluginSDK/TODO @@ -0,0 +1,33 @@ +Plugin SDK for GtkRadiant +TODO list + +win32: +write script to generate the plugin SDK +sample dll +documentation +headers +gtk libs (devel package + our stuff on top) + +linux: +same thing more or less.. +except we rely on the user for his Gtk installation + +put some docs, links to curry and pk3man for source + +[15:23] you don't need to do: +[15:23] #ifdef __linux__ +[15:23] extern "C" char* QERPlug_Init (void* hApp, GtkWidget* pMainWidget) +[15:23] #else +[15:23] //LPCSTR __declspec(dllexport) QERPlug_Init(HMODULE hApp, GtkWidget* pMainWidget) +[15:23] extern "C" LPCSTR WINAPI QERPlug_Init(HMODULE hApp, GtkWidget* pMainWidget) +[15:23] #endif +[15:23] yes that needs cleaning +[15:23] is it yet? +[15:23] extern "C" char* WINAPI QERPlug_Init works +[15:24] way cool +[15:24] some plugins have it +[15:24] just thought I'd mention as you're writing the SDK + +need to put TexTool source in as well.. + +put a f* updated binary of gktradiant in there diff --git a/setup/changelog.txt b/setup/changelog.txt new file mode 100644 index 00000000..37598624 --- /dev/null +++ b/setup/changelog.txt @@ -0,0 +1,1800 @@ +GtkRadiant +(c) 2000-2006 Id Software, Inc. +additions by qeradiant.com and Loki Software, see Credits + +GtkRadiant and all associated map/level design tools are unsupported products. Use at your own risk. + +Bug reports and feedback: +http://www.qeradiant.com/bug.shtml + +When submitting bugs, please send a full system/video configuration list +and a detailed duplication sequence. + +NOTE TO NIGHTLY BUILDS USERS: +When nightly builds are released, we don't update this ChangeLog file +systematically. You should be watching the developer ChangeLog instead, which +is available online: +https://zerowing.idsoftware.com/svn/radiant/GtkRadiant/trunk/CHANGES + +GtkRadiant-1.5.0 Changes +------------------------ + + - Games: + Doom3 and Quake4 support with realtime lighting preview. + Added support for the original Quake. + - File formats: + q1/q2 .pak + q1/hl .wad + doom3/quake4/hl .map + doom3 .md5/.def/.mtr + quake4 .guide + - Undo: + Very fast, even with large changes. + Uses minimum memory, allowing very long undo queues. + All scene modifications now undoable, including: + Texture name/shift/scale/rotate on brushes and patches. + Vertex/edge/face manipulation. + Cloning, deleting, pasting, importing. + Entity key/value setting. + - Selection: + Per-polygon selection for all selectables, including: + Vertices, edges, faces, brushes, patches, models, entities. + Per-polygon area-selection for all selectables. + Edge/Vertex/Face mode automatically resets to Object mode when selection is cleared. + - Editing: + Maya-style tools for translate (w) and rotate (r): + Axis-constrained translate/rotate in 2d and 3d views. + Editing uses lazy evaluation to make movements smoother in complex scenes. + QE-style editing tool (q) provides old style editing. + - Renderer: + Fast view-frustum culling, replacing cubic-clipping. + Batched state-sorted rendering: + Fast rendering of large scenes with many textures + Fast rendering of detailed mesh models. + Realtime lighting preview. + - Scene Graph: + Directed Acyclic Graph backend: + Allows Maya-style "instancing" of sub-scenes (aka prefabs). + Edit one instance, other instances show updates in real time. + Maps can be loaded as misc_model. + - Outliner (aka entity lister): + Shows a tree-view of the current scene: + Non-modal window, updates automatically when scene graph changes. + Allows inspection/manipulation of scene graph. + Brushes can be moved between entities, and entities moved between maps. + Sorted by name. + - Entity Inspector: + Non-modal window, updates automatically when selection changes. + Displays GUI to edit certain keys, based on entity-definition. + Light texture key-entry GUI is auto-completed. + - Surface Inspector: + Non-modal window, updates automatically when selection changes. + Editing shortcuts: escape reverts current values to previous state, enter commits current values immediatly. + Applies shader seperately from shift/scale/rotate. + Shortcuts are active when surface inspector is visible/focussed. + Material name-entry is auto-completed based on currently-in-use materials. + - Brush: + Supports selecting and editing multiple vertices per brush. + CSG subtract algorithm does not suffer from false-positive intersection bug. + Face mode: + Selection/manipulation of multiple faces in 2d and 3d views. + Robust and fast B-Rep generation: + Safe manipulation of large brush selections. + Fast updates for brush operations on large maps. + - Patch: + Fast rendering. + - Filters: + Objects are filtered when their filterable attributes change. + - Map Load/Save: + Fast loading of large maps. + Loading of maps without worldspawns. + Quantised plane coordinates. + - Preferences: + Preferences saving is atomic so that it cannot be corrupted by a crash during save. + All user-editable config files and per-user preferences are created in %appdata%/RadiantSettings/ + Engine path stored in per-game preferences file and editable in preferences dialog. + Changing mod-name and engine path does not require a restart. + - GUI: + Instant window updates (used to be 40fps limited). + Fast startup/shutdown. + Automatic repositioning of separators in 4-way-split mode. + Console prints all undoable commands (and parameters) when executed and undone. + Status bar shows mode information. + New preferences layout, unused settings removed. + Rearranged menus: + File = new/open/import/export + Edit = manipulate scene hierarchy and selection + View = toggle entityinspector/entitylist/surfaceinspector/camera/xy + edit camera/xy parameters + filters/hideshow/region + Modify = transform scene elements + Build = bsp menu + Upgraded win32 gtk2 version from 1.3 to 2.4, providing stability/cosmetic improvements. + - 2d view: + Limited scroll speed when mouse moves outside window. + - Textures module: + Fast texture cache lookups. + Gamma change without restarting. + - Installer: + New xml-scripted windows installer using Windows-Installer tech (.msi). + Linux version released as RPM package. + To avoid deletion on uninstall, shaderlist.txt is generated from default data in the application directory and copied to the game directory. + - Saving + The map is only saved if the scene has been modified since the last full save. + Autosave/Snapshots are only saved if map has been modified since last full save. + Autosave saves as .autosave.map. + - Debug + New assert/error messages as popup windows. + Full stack-trace for assert/error on win32. + + + +GtkRadiant-1.3.14 Changes +------------------------- + + ydnar: + - clipper tool: select a patch mesh, and the clipper tool plane will be set to the patch's mesh (default shortcut X) + EvilTypeGuy: + - texture find and replace menu can be used to select faces by matching texture (not only replacing textures anymore) + so you can apply fit and translate commands to the whole set + if you have brushes selected, it will select only within the selected brushes set + SCDS_ReyalP: + - new plugin: background image in the 2D view. scan your layout, use it as a background to build the structure.. + - some fixes to the Z floating window + - added to ET and RTCW: hullcaulk, hintskip, and subtlehint (see below) + - fixed to CenterXYView shortcut, correctly bound to Shift+Control+Tab now + - added shortcuts to change the texture window scale + TextureWindowScaleup (Alt + Delete) + TextureWindowScaledown (Alt + Insert) + also fixed various oddities with the texture window behaviour + Nurail: + - more updates to quake2 and heretic2 support. BSP monitoring supported now. + ydnar: + - q3map2 2.5.11 + - New: added support for _skybox entities to generate "portal sky" + surfaces in games w/o native support (Quake 3). _skybox entities have + 3 keys: _scale (default 64), and angle/angles (for rotation of the + skybox relative to the map) + - New: added -skyfix switch to BSP phase as a workaround hack for + the black GL_CLAMP border on skybox edges on ATI (and newer nvidia) + video cards. Note: unnecessary in ET or JA + - New: Added _anglescale to light entities for scaling angle attenuation. + Use a small value (< 1.0) to lessen the angle attenuation, and a high + value (> 1.0) for sharper, more faceted lighting + - New: Added _lightmapscale support to misc_model entities + - Custom shaders (external lightmaps, styles) will not be generated + if the find/replace text cannot be found + - Tightened up light culling epsilon from 1.0 to 0.125 to stop certain + surface lights from being incorrectly culled (thanks RasputiN!) + - Fixed bug where small 3 and 4 sided brush faces were getting fanned, + adding triangle/vertex counts + - Moved to Visual Studio .NET, with aggressive optimizations enabled + - Cleaned up missing image warnings + - Parsing images out of shader stages if not found explicit/implicitly + - Loads Enemy Territory implicitMap images if editor/light image not found + Raven: + - new sample maps for JA media + - MP SDK had a bunch of editing tools. Packaged those up with our JA media as well + look under Tools/ in the GtkRadiant game pack install + (default location C:\Program Files\LucasArts\Star Wars Jedi Knight Jedi Academy\GameData\Radiant-1.3) + +common/hullcaulk +This is exactly the same as caulk, just has a different editor image so people +using the 'caulk hull' method of mapping can see at a glance what is part of the +hull and what is part of the detail brushes. It is up to the mapper follow the +convention. + +common/hintskip +This is exactly the same as skip, with 'hint' in the name so brushes with mixed +hint and skiphint faces will filter with the 'hints' filter. I didn't add a new +image for this, since it is functionally identical to skip. + +common/subtlehint +hint without the surfaceparam hint. A lower priority hint which is sometimes +useful if you don't want the splits from your hint spreading out all the way to +block bounderies. You can mix it freely on a brush with normal hint and skip. + +GtkRadiant-1.3.13 Changes +------------------------- + + AstroCreep + - cleaned up shaders scripts for JA + BirdDawg + - ASE submaterial/subobject code + Anders & TTimo + - OSX setups + Nurail + - Heretic 2 support + - Q2 and Her2 tools now have BSP monitoring, and ability to run engine after + compile + SCDS_reyalP + - fix to jpegs having garbaged alpha channel ( + ydnar fixes ) + - update to ET .def + - fix floating window mode startup crash + Spog + - patch for 16 bit GL, win32 16 bit desktops were either crashing or rendering incorrectly + Arnout + - LWO support in picomodel + - support for 'vertical flipped' TGAs + - fixed multiple monitor support, use the 'start on primary monitor' option in prefs + - fixed mouse on win32 going crazy when radiant non-fullscreen or with hidden taskbar + +q3map2 2.5.10 (2003-10-22) + +- New: Lightwave model support (beta) courtesy of RR2DO2 +- New: Heretic 2 FM model support courtesy of Nurail +- Re-enabled vertex cache friendly triangle reordering with fix +- Disabled triangle reordering on certain surfaces, including autosprite + shaders due to visual errors +- Fixed bug in radiosity where sorting of lights by style took forever. + Thanks ReBoOT! +- Fixed bug in sun lighting code where maps too far off the origin would + not be properly it by sun or sky light. Thanks MindLink! +- Entity causing a leak will be printed and selected in Radiant if BSP + monitoring is enabled. Requested by heeen +- Fixed odd bug causing 10x slowdown in lighting in some maps. Should + be back to 2.5.7 performance. Also fixed a couple old bugs related to + autosprite shader (point) lights and backsplash lights not being styled + or setup correctly + + +GtkRadiant-1.3.12 Changes +------------------------- + + SPoG + - Changed gl widget to request maximum available Z buffer precision (fixes artifacts on some cards) + - fix texture subsets + ydnar + - q3map2 2.5.9 http://shaderlab.com/q3map2/ + - fixed long BSP commands causing a crash + - remove Ctrl+X, you have to exit through the File menu now + Anders Gudmundson + - general work on OSX port + TTimo + - make 'move into worldspawn' work again + - fixed bobtoolz plugin causing HL startup failure + - fixed inspector in camera plugin (RTCW/ET) + - support PNG images with an alpha channel for JA + James Monroe, Mike Majernik & TTimo + - Jedi Academy game pack content and win32 installer + Nurail + - Q2 code / Q2 game pack + Hydra + - fixes to HL/CS support + - support for sprites on entities is enabled for all games, not only HL. See the wiki for details: + http://www.qeradiant.com/wikifaq/index.php?Adding%20Sprites%20on%20Entities + +GtkRadiant-1.3.10 Changes +------------------------- + + SplashDamage + - ET game pack merged into trunk + Spog & TTimo + - Gtk2 as seen in 1.3.9-test becomes the official version + TTimo + - ET Linux setup + +GtkRadiant-ET Changes +--------------------- + + SplashDamage + - plugins porting (prtview, bobtoolz) + - ET media and config elements + - custom splash screen per gamepack + TTimo + - ET setup code + +GtkRadiant 1.3.9 Gtk2 Test +-------------------------- + + Spog + - update the source to compile against Gtk2 API, corresponding setup updates to install the Gtk2 stuff. + +GtkRadiant 1.3.8 Changes +------------------------ + + David Olofson & TTimo + - fix for buggy ATI drivers. Likely this doesn't fix all ATI issues, but fixes a rendering bug related to polygon backfaces + the workaround has to be enabled in Edit > Preferences, in 2D display/rendering tree node + Riant + - fix to png textures not showing in SoF2 mode. was a bad config file + +GtkRadiant 1.3.7 Changes +------------------------ + + ydnar + - q3map2 2.5.4 - see changelog.q3map2.txt + Riant + - several fixes to the SoF2/JK2 support, added missing files + - working project template - BSP menus working for q3map2 on all games + LordHavoc + - work on q1 support. no gamepack release yet + TTimo + - fix to texture compression support + - fixed models not drawing on win32. was a setup bug + +GtkRadiant 1.2.11 Changes +------------------------- + + ydnar + - added Misc > Colors > Themes > Maya/Max/Lightwave Emulation theme + TTimo + - run GtkRadiant from a network share with multiple users (win32 only): + Preferences > Game Settings allow to configure the editor to save preferences in user directory (on win 2k/XP) + ydnar & TTimo + - q3map2 integration and distribution with GtkRadiant + FIXME: the q3map2 ChangeLog is not included here + q3map2 is installed by default with GtkRadiant 1.2.11 + the BSP menus have new q3map2 options + all current games are supported by q3map2: Q3 RTCW JKII SoF2 STV:EF + BSP monitoring is supported + - fixed rare texturing bug, texture showing fine in editor and broken after compile on angled faces + fix is in q3map2, needs matching code in GtkRadiant - this is toggled by a preference item in BSP Monitoring menu: + http://www.qeradiant.com/wikifaq/index.php?Texturing%20Compatible%20with%20q3map2 + seaw0lf & ydnar & Arnout + - new picomodel library - used in q3map2 and GtkR, provide load and rendering functionality for model formats + EvilTypeGuy & TTimo + - many changes to the Linux build binaries. ABI is different, stdc++ statically linked + we expect the new Linux binaries to be much more compatible on different platforms + - linux build loads libGL.so.1 by default + James Monroe & RR2DO2 & TTimo + - added in-editor light envelope drawing. + Outer circle is max envelope, inner fullbright radius, center is effective light radius + Optional classic mode (see prefs dialog) emulates the similar drawing from Rituals and Ravens tools + (not q3map correct, easier for the level designer to understand/legacy) + Arnout + - area selection in 2d view: + Alt+LMB (LMB=left mouse button): area select + Alt+Ctrl+Shift+LMB : additive area select + NOTE: with Alt+multidrag enabled in preferences, you only have additive + - vertex mode edit, vertex select (reminder: use V for vertex edit): + regular drag for select, Ctrl+drag for additive select. Works both in 2d view and 3d + - added 'angles' support for models misc_model/misc_gamemodel. (yaw-pitch-roll vector angle rotation) + note: this is supported in q3map2 only + - upped MAX_TEXTUREDIRS to 256 (from 128) + - added 'Paste to Camera' which pastes the contents of the clipboard to the current camera origin (shortcut: Alt+V) + - added centerview functionality to 4 window mode. Ctrl+tab will focus on the selection, or if non existant, on the camera. + - added botclip filter (filters *botclip* and *monsterclip*) + - added outline style cycling, cycle between z buffered outlines and selected colour rendering (shortcut: j) + - added menu Misc > Colors > Selected Brush (camera) - configure the color + of selected surfaces in the camera view + - third coordinate for clip points now gets set to the center of the selection + - changed arbitrary rotation dialog to accept negative angles as well + - changed texture alignment dialog to accept values up to 2 decimal points + - changed entity inspector so that tab doesn't clear the epair value field anymore, + so it retains the value while jumping to it. + - paint select in camera view: + press shift and move the mouse over the camera view to paint over brushes you want to select + use the preferences menu to configure: shift key is default, 'classic mode' is Ctrl+Alt + - misc_gamemodel drawing + - new plugin toolbar + TTimo & Arnout + - with clip or caulk filtered out, you could select invisible faces on partly drawn brushes + this is bug #556, there are still some issues with it + TTimo + - added cascading to the entity menus. JKII NPC_* are all showing + - mark map modified on editing entity keys + - guard junk.txt path between " " (non monitored BSP) + - preferences: mouse AngleSpeed setting was getting clobbered. fixed and upped the max values + - RTCW setup: detect GOTY install from registry and use it as default path + Riant + - more work on texture compression, more compression options (S3TC support) + - IS scripts maintenance, media updates for JKII/SoF2/STVEF + Michael Schlueter + - fixes to Linux setup scripts + SCDS_reyalP + - RTCW town_*.shader + EvilTypeGuy + - fixed his very own memory leak in the texture code, great improvement to the memory usage + +GtkRadiant 1.2.10 Changes +------------------------- + + SCDS_reyalP + - misc RTCW .def updates + see http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=526 for details + + Michael Schlueter + - misc GL code warnings fixes, improvements to the GL font code + + TTimo + - removed the "light 1" parameter from the shader files (RTCW & Q3) + this is an old/unsupported map compiler flag that was meant for shader flares + recent q3map versions produce .bsp that will crash RTCW/Q3 when used + - pk3 loading is case insensitive (loads .pk3 and .PK3) + - fixed a MAX_POINTS_ON_WINDING overflow situation in q3map (when reporting the error through debug stream) + - don't straffe when using Ctrl+Shift(+Alt) + + Riant && TTimo + - Soldier Of Fortune II support: + - complete game pack for Sof2 editing, latest tools, sample maps from Raven + - png support in GtkRadiant + - texture compression support. This requires OpenGL 1.3 drivers. Can be disabled with a preferences setting + the textures are manipulated in a compressed format on the card, memory bonus is about 1/3 improvement + Sof2 textures are eating up a LOT of memory, texture compression brings an improvement, but it's still very heavy on mem + there are still some potential improvements regarding memory footprint, this will be for later versions + + Riant + - Star Trek Voyager: Elite Forces media/scripts updates + - Jedi Knight II: Jedi Outcast media/scripts updates, contains the new elements from the second official SDK + + RR2DO2 + - camera plugin for RTCW, support for .camera files editing + the plugin allows to manipulate camera paths and splines + - RTCW VFS is filtering the files depending on game mode + Single Player mapping will ignore mp_*.pk3 + Multiplayer mapping will ignore sp_*.pk3 + + djbob + - fix to spawnflags getting corrupted when multiple entities are selected + + +GtkRadiant 1.2.9 (general update + Star Trek Voyager : Elite Forces) Changes +---------------------------------------------------------------------------- + + Riant + - Star Trek Voyager : Elite Forces game pack, STVEF-specific code tweaks + + SCDS_reyalP + - fixed bobtoolz vis viewer to work with RTCW + - RTCW entities update (various fixes, some ents colors, sizes and documentation) + + TTimo + - several fixes to region compiling, should be fully working again + - if a model load fails, the default box is bigger and easier to select + - JKII update: the default texture scale is configured per-game, JKII has correct default now + - Q3 entities update: correct trigger_hurt spawnflags + - RTCW media update: misc .shader updates, and updated some .md3 + there are still things to be looked at, cf. http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=539 + - removed broken print XY view menu item. Use a screen capture tool for now + - fixed broken game spawning after compilation in RTCW mode + - changing texture window scale no longer changes selected brushes texture + - workaround for empty gtk error box on non-english Windows installs (was i18n problem) + +GtkRadiant 1.2.8 (Jedi Knight II release) Changes +------------------------------------------------- + + Riant & TTimo + - improved the editor console logs when compiling in non-monitored mode + - JKII game pack, JKII specific tweaks + +GtkRadiant 1.2.7 Changes +------------------------ + + Spog + - fixed a regioning bug, radiant loosing track of some entities outside the region (#479) + - fixed flush and reload affecting texturing of selected brushes + - added undo for pasted/cloned brushes + + Gef + - fixed bug in velocity sliders (Edit > Preferences > 3D View) (#217) + - fix File/New Project for mods so it doesn't fail if the dir exists (bugzilla: #459) + - add Linux-isms for New Projects & read/write permissions... + note: for a total conversion, basepath needs to be manually set + - prevent opening multiple internal shader editor dialogs + - added preference for using a custom shader editor + - set horizontal scrollbar to be automatic instead of never for entity keyval list (bugzilla: #4) + - added a call to Select_Reselect() in XYWnd->OnViewEntity() to make sure its modifying the + current selection (bugzilla: #436) + - fixed entity dialog passing events through to main window (bugzilla: #454) return values + were backwards + - patching in the .pfb extension adding stuff (bugzilla: #259) + - fixed thickened patches not being grouped (bugzilla: #226). this was supposed to be happening + anyway, the entity create code was called before the patches were selected + - setting the sel_mode accordingly when (i)nverting selection, verts were being drawn when + they shouldn't have been + - fixes to Curry and PrtView (proper prefs load/save, curry logo on Linux) + - fixed patches losing their shader if outside region when calling flush/reload + - added filtering on unselect for newly created brushes/entities + + Gef & TTimo + - made sure textures with spaces in name are never loaded from tex dirs (warning if bad textures detected), + never save them to .map, avoid map corruption situations. + + EvilTypeGuy + - added back brush numbers and entity numbers in the .map (#457) + - RTCW skies.shader update + + TTimo + - correct default prompt location for file operations (map load/save, etc.) .. works for mods again + - some mod setup documentation: http://www.qeradiant.com/faq/fom-serve/cache/220.html (Q3UT2 as example) + - File > Check for update command, sends you to the qeradiant.com website with the version information + the easiest way to check if a new version is available + - cleanup of the project settings dialog. Removed a lot of unused / outdated / confusing settings we had + there + - fixed the Shift+Click shortcut to edit shaders. Fixed crashes and behaviour in the internal shader editor. + on Win32, either use internal editor, or use win32 .shader association to open + on Linux, Gef added prefs to select custom shader command + NOTE: we don't have a free text editor for win32 that allows to jump to a given line after you open + the file. If anyone has this around, please send the info along. + +GtkRadiant 1.2.6 Changes +------------------------ + + TTimo + - fixed the shader scripts so that the sky in escape1 is ok again (new skies.shader) + - safer find and replace (denies malformed shader names) + - shift + middle mouse button works again + - Linux: now distributing a new/experimental bspc for RTCW + please report problems you may have with this version + + ydnar + - fix to q3map vis stage code, should be all good now + see http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=417 for details + + Hydra + - improved snap to grid in gensurf plugin + +GtkRadiant 1.2.5 Changes +------------------------ + + TTimo + - drawing dlight and lightjuniors like regular lights with a small shape change + - from RR2DO2 and ydnar: + fixing a q3map vis bug + vis is correct now, but we know that it introduces some instability and potential crashes + see http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=417 + + EvilTypeGuy & RR2DO2 + - added the ability to strafe up/down/left/right while freelook is active + + Gef + - fixed target/targetnames collisions + entities being cross-linked when copied have their target/targetname + updated to maintain the links + - find brush dialog window has correct title + - linux setup tweaking + +GtkRadiant 1.2.4 Changes +------------------------ + + ydnar + q3map 1.2.4-y2 + + New features: + - -nopatchfix argument. This disables lightmap patch fixes and makes a map suitable for lighting with -vlight. + - Degenerate patches are treated like broken brushes. They are ignored, warned about, and selected in Radiant if you ran with the -connect option (or from the BSP menu). This was what was causing the "0 valued axis" error some people were experiencing. + + New entity keys: + - "_lightmapscale" key for brush entities (worldspawn, func_*). This lets a mapper scale the lightmap samplesize per-entity. For large constructions, 2.0 or 3.0 is a fine value, and keeps BSP size down and compile times low. For those areas you want to have high-detail shadows, make a func_group and use a value of 0.25 or so. It will scale the samplesize value for the surface's shader (default 16) or the -samplesize argument. + - "modelscale" and "modelscale_vec" keys for misc_models (1.0 = default). This was for proper RTCW support and is available for Quake 3 maps as well. Lets you scale up map models in the world, getting around the MD3 size limitation. The next build of GtkRadiant has SPoG's code to support this in-editor so you can see what effect a scale has. + - Flare surfaces are now supressed from the BSP. They serve no purpose other than add to the vert & surfacecount in a BSP. These surfaces were created silently when a shader has "light 1" or "q3map_flareshader X." Use the new -flares switch when BSPing your map to have them emitted. + + Changes: + - GtkRadiant 1.2.4-nightly version increment. + - Full WolfSDK style lighting enabled with -game wolf, including lightJuniors. This includes linear lights by default (no angle attenuation) and support for the additional RTCW "fade" and "angle" keys, and spawnflag changes, including q3map_nondynamic on light entities. This may require maps being constructed for RTCW with the current toolset to change their light entities. Sorry. :) Note, Wolf-style lighting only works with -light, and not -vlight. + - Vertex light stitching now uses a near-ambient light check for dark vertexes as opposed to lower-than-average fixups. This preserves some shadow detail better while getting the buried verts lit properly. Comments encouraged. + - Surfaces' samplesize are now stored in the BSP. This change makes BSPs generated from this version incompatible with all other q3maps. The upside is that -samplesize N is no longer necessary on the -light or -vlight stage. This feature is necessary to support the "_lightmapscale" key. + - Additional PVS optimizations in lighting. + + Fixes: + - Will compile for RTCW properly (1.2.1-y12 didn't). + - No more sparklies where fog meets brush faces. They're split properly now. + - Crash bug in vlight fixed. + - Vertex light fixups/stitching is considerably faster. + - Vertex light fixups ONLY stitch faces with lightmaps. For pointlight surfaces you're on your own. + - Better snapping logic when merging nearly-coincident vertexes on complex brush windings. + - Bug where the .prt file had some bogus or nearly-borked portals. They're cleaned up like everything else now. + - A few stupid bugs in path initialization. Should work better. Also includes TTimo's fixes to my code so it would work properly on Linux. + - RR2DO2's PCX loading patch for alphamaps. This bug was manifesting itself in the form of offset or incorrect samples being used on terrain entities. + - A ton of other minor little fixes here and there. + + EvilTypeGuy + - fixed broken 'load shaders at startup' preference + + djbob + - EClass_ForName fix if malformed name + + SPoG + - fixed misc_model "modelscale" and "modelscale_vec" support + + TTimo + - ability to do nightly setups again on win32 + +GtkRadiant 1.2.3 Changes +--------------------------- + + djbob +- bobtoolz update + + ydnar - TTimo +- fixes to q3map and radiant's bsp command expansion + support mod directories correctly + + TTimo +- removed the 'refresh' command from plugins menu. it is broken right now +- various fixes to the plugins` + + Hydra +- fixed the loading of texture menu + works correctly if 'shaderlist.txt only' is disabled + + Gef - Michael Schlueter - TTimo +- putting together everything for a linux release (setup scripts) +- fixes to GenSurf texture requests code + + SPoG +- added modelscale key check to misc_model entity +- added angle key check to eclassmodel class +- fixed bugs in BP (brush primitives) writing and reading +- fixed gensurf to create faces/patches with correct textures +- fixed md3 models missing in 2D views + +GtkRadiant 1.2.2 Changes +--------------------------- + +1.2.2 was born dead + +GtkRadiant 1.2.1 Changes +--------------------------- + +General changes: + +Modular architecture. During startup the editor gets configured to load specific modules, +which direct it's functionalities. The immediate consequence is the ability to support +several games in a more easy and clean way. The editor core installs in a central location +(on win32, C:\Program Files\GtkRadiant), and installs it's game specific modules and +programs in 'game packs' folders (C:\Program Files\Return To Castle Wolfenstein\Radiant +and C:\Program Files\Quake III Arena\Radiant typically). + +List of the main modules and their functionalities: +- map module, read/write from/to file and memory +- shaders management module +- filesystem module +- models loading, editing and display (.md3 currently) +- image formats loader + +Return To Castle Wolfenstein is the first additional game supported by the 1.2 architecture, +along with Quake III Arena / Quake III: Team Arena + +Specific changes and fixes: + + AstroCreep +- common shaders, cleaned up wolf shader scripts, and general Wolf pack testing + + djbob +- updated 1.2 plugin API, making bobtoolz compatible with 1.2 +- View > Filter > Structural (hide all except detail brushes) + + Wolfen +- updates to the Q3Radiant manual, links page and setup instructions + + EvilTypeGuy +- general Wolf support (.qe4 template) and bug fixes (on linux also) +- fixed patch toolbar prefs settings +- menus can be configured detachable or not from prefs + + ydnar +- changes to q3map, rough list: + Quake 3 and RTCW support through a -game switch with the same binary + light code speedups and improvements + radiosity! + new common shader 'lightgrid' + NOTE: see the q3map document for more specific info + + MrElusive +- q3map vis code speedup +- radiosity in vlight + + RR2DO2 +- general bug fixes +- q3map terrain blending fix for >5 layers +- View > Filter > LightGrid (hide lightgrid brushes) +- improved multimonitor support + (some preferences options to ensure you get accelerated OpenGL on both monitors) + + SCDS_reyalP +- q3map realloc bug in terrain code + + Gef +- brand new notebook layout for preferences dialog +- general maintenance of the builds on linux +- setup code for 1.2 on linux, corresponding enhancements to Loki Software's setup tool + + Michael Schlueter +- help on the linux setup + + Spog +- improvements to the camera rendering code +- md3 module (load / display / edit of the models) +- q3map uses a protected malloc that will exit cleanly with an error if we run out of mem + (95% of the q3map 'bugs' are just that, running out of mem) +- map module (.map read/write) + + TTimo +- complete rewrite of the win32 setup code to match the 1.2 specifics + building setups on demand, selecting the game packs that should be included +- change of the linux prefs path storage to ~/.radiant// +- BSP monitoring network stream is versioned so that we don't risk + non-matching q3map and radiant connecting to each other. +- RTCW specific project settings dialog +- Wolf spawning, use correct binary depending on SP and MP mapping mode +- game packs can customize the editor's Help menu through game.xlink files + +GtkRadiant 1.1.1 Changes +--------------------------- + + TTimo +- distributing q3data.exe and q3data.qdt in the win32 setup + (q3data is the .ase -> .md3 converter) +- fixed installer problem on win9x +- entity inspector fix, will focus on the current entity in the list +- fixed GDI leak affecting text widgets, specially the console and the entity inspector +- removed some unneeded stuff from Gtk libraries +- nightly build system +- fixed known sleep/wake issues +- .def files are now scanned from the scripts/ directory +- changed the way we handle paths to use long filenames everywhere + (in project settings, misc_model paths ..) +- Fixed -onlyents bug in q3map / origin brushes +- added general mouse wheel code and texture window +- added "notta" and "notq3a" documentation to the TA Mapping manual +- fixed entities.def on shootable doors and buttons +- added targetShaderName documentation to shader manual +- NEW TA Teams manual in the documentation +- fixed select all of type bug (made behaviour more consistent) +- fixed win32 Gtk keyboard bug Ctrl + [ and ] +- texture sub-menu cascading (when they get too big) + + TTimo & MarsMattel +- added general mod support (configured from the project settings) + + SPoG +- rendering improvements: + Added variable LOD for PatchMeshes based on curvature + Added LOD-matching to eliminate gaps between patches with mismatched LOD + Added opengl lighting (three infinite light sources) + Added dynamically calculating vertex normals for PatchMeshes, for gl lighting + Added decoding/transforming md3 vertex normals for gl lighting + Various rendering speedups +- Fixed texture shift/scale on LOD'd PatchMeshes +- Removed Patch_InsertDelete() - not functional +- Added CV lattice to selected patches +- Added Per-polygon patch selection +- Added Per-polygon misc_model selection +- Changed default "patch subdivisions" to 4 +- Fixed .wal texture support, searches for .wal extension if .tga and .jpg fail + NOTE: requires a "pics/colormap.pcx" file to obtain a palette from +- Added variable default texture scale in preferences (ini key: TextureDefaultScale) + + djbob +- fixed Radiant highjacking win32 copy/paste +- proper md3 filtering in misc_model dialog +- better MAX_POINT_ON_WINDING error handling in q3map debug stream +- if you use face selection on a globally selected brush, it will switch + to face selection, and deselect the single face. +- fixed surface inspector "fit" bug +- added patch splitting to bobtoolz +- handling of Node With Unbounded Volume (NWUV) errors in q3map debug stream +- remember last key/pair in entity dialog for easy "apply again" + + Gef +- added a button to use func_group or not in curves cap dialog +- GL extensions getting too long: using a text box in About dialog +- added a prefs switch for Kyro II GL driver bug workaround (point handles) + + RR2DO2 +- noclip-type camera movement: + documented in the section "New functionalities in GtkRadiant" + of the Radiant manual (see local installation or on the website) + + +GtkRadiant 1.1-TA Changes +------------------------- + +djbob: +- bobtoolz plugin is now included in the standard setup + +Spog: +- Fixed "Fix entity-target/targetname collisions" to use next available tN if tN, else use next available name_N +- Fixed ctrl+G SnapToGrid, now never creates degenerate face-planes +- Fixed setting an origin for multiple brushes to use origin point of fixedsize entities +- Fixed mirroring and rotation of fixedsize entities including misc_model +- Fixed undo/redo on multiple entities to link brushes to entities correctly +- Fixed "view > entities as.." menu to display correct default setting +- Fixed "view > entities as.." toolbar button to show menu +- Changed selection-area of edge/vertex control handles to stay constant when zoomed +- Fixed undo on ctrl+G SnapToGrid +- Fixed origin drift on saving misc_model with null md3Class +- Fixed creation of cap for 'Bevel' type patches +- Fixed inverted cap being created for 'Endcap' type patches +- Fixed inverting patches on mirror operations +- Added snap-selected-to-grid affects only the patch points selected +- Fixed texture quality slider adjustment +- Changed Patch Inspector Horizontal/Vertical increment to use pixel values (default 8) +- Changed Patch Inspector Horizontal increment to subtract from S values but not T values +- Changed Patch Inspector Stretch spinner to do something useful +- Changed Patch Inspector Stretch default amount to 0.5 +- Changed Arbitrary Rotation dialog to reset rotation spinner values to 0 on Apply +- non-monitored BSP compiling: sending q3map output to temp/junk.txt !! stops output in console window !! +- Fixed Patch_Naturalize to calculate T values backwards, correcting texture vertical flip +- Changed patch row/column Insert/Remove to interpolate/extrapolate from existing curves +- Fixed point selection on patches when new points are added +- Fixed redundant edge/vertex handles being created for patch brushes and fixedsize brushes +- Fixed refusal to activate brush vertex-drag mode if any patches are selected +- Partly fixed Undo picking up patch point drags when no points are selected +- Fixed behaviour of vertex selection on patches +- Fixed patch point colours in textured mode in cam window +- Changed patch point selection to update selection pool on each selection click +- changed: Curve > matrix > redisperse > rows/columns + treat a patch as independent 3x3 sections when doing redispersal +- rewrote patch cap texturing functions to be more reliable +- fixed clipper-caulker bug +- fix 2pt-clip orientation problems +- overall changes and improvements to the brush filtering and view/hide code +- fixed some grid drawing code, camera widget drawing (2D views), and min/max world coordinates bugs + +Maj: +- new splash screen + +Hydra: +- *.pfb filter in prefab load dialog + +^Fishman/Hydra: +- gtk-gensurf 1.05 + snap to grid + checkbox for adding terrain key to func_group +- gtk-gensurf 1.0 + Now the user is allowed to make terrains bigger than +/-8192 + New Green/Black theme for the preview window. + Line antialiasing for the preview window. + +MrElusive: +- bspc 2.1h + modelskins.txt and headskins.txt files + +Astrocreep: +- clean shaders for Quake III and Quake III: Team Arena + +TTimo: +- brush primitives only: texture adjustments commands (shift+arrows) are interpreted relative to the camera view +- improved the conversion prompt when there's a reguar/BP texturing conflict +- trying to print out more information if parse error occurs while loading a map +- texture locking in BP mode for axis flip and axis rotation +- better undo in BP mode +- fenris #2866, added a pref to turn on/off name conflicts resolution, rewrote the whole algorithm +- fenris #2823, fixed patch and brush dragging in 0.25 0.5 grids +- fenris #2867, limiting the amount of "spawnflags" "0" appearing in entities +- Added back texture increments follow grid +- Raised the brush max size to world size +- Fixed vertex edit prefs broken (Vertex editing splits faces / doesn't) +- New Gtk file selector on win32 (the same as earlier added to linux version) +- Fixed shader blending on terrain maps bug +- Added undo for patch redisperse rows and patch redisperse cols commands + +leo: +- Fixed the minus key on numeric keypad being ignored +- Fixed sleep mode not restoring windows correctly +- Fixed some minimize/restore issues on floating views mode +- Added an option to keep the Z and XY views on the same window in floating views mode +- Fixed elapsed time displayed by q3map +- Fixed q3map is crash if a .shader file is referenced in shaderlist and not found +- Minimize all windows when the main window is minimized in floating views mode +- Added new filter system based on FAKK2 Radiant +- Fixed View/Show/Entities menu not being checked correctly +- Fixed "Invert Selection" command selecting hidden brushes +- Fixed q3map crash when trying to load missing pcx files +- Fixed q3map not finding .bmp files for the terrain alpha map +- Fixed Surface Inspector spin buttons rate (#2776) +- Fixed some shortcuts not appearing on menu items (#2786) + +Jonas: +- Using RC file to tweak the font size on win32 (gtkradiantrc), looks much nicer now! + +GtkRadiant 1.1-TA-beta Changes +------------------------------ + +- moved the game selection to the project settings dialog. You need to set the +game mode to "Quake III: Team Arena" to enable Team Arena support in the editor + +leo: +- Fixed pk3man plugin not finding the toolbar bitmaps +- Fixed double clicks being considered 2 mouse clicks +- added virtual file system to Radiant and q3map (Team Arena support) +- Changed max number of shader files parsed by q3map to 128 +- Updated manual images +- Fixed bug with the texture window scrollbar range +- Fixed crash on Shift-A (Select all of type) +- Fixed texture window not scrolling to the top when a new directory is loaded +- Fixed crash after map compilation if the map leaked + +TTimo: +- fix multiple edge dragging crash +- terrain entity parsing speedup +- fixed some sleep/wake code (crashes and wakeup problems on models) +- fixed the clamping problems when flipping or mirroring patches +- added 0.5 and 0.25 grids +- added undo to Select_CompleteTall Select_PartialTall and Select_Inside +- two new commands in the drop down menu: + "move into worldspawn" will move selected brushes to worldspawn and eventually delete entities which end up with no brushes + "merge brushes" will merge brushes into an entity (from worldspawn or from another entity) +- merged Radiant 200f changes in + bigger map size + added ctrl-alt-LBUTTON: multiple brush select without selecting whole entities +- fixed MAX_NETMESSAGE error in monitoring + +RR2DO2: +- Clusterportal filtering ('View > Show > Show clusterportal' toggle) +- fix to CSG Merge in the menu drop down (menu was there, command not hooked) +- Added bug report link to help menu +- patch to q3map, added option -custinfoparams for custom surface flags (mod makers usage) +this is documented in the Terrain Manual ("New or Revised Q3map Shader Commands") + +leo and MrHyde: +- gtk-based gensurf + +GtkRadiant 1.1 Changes +---------------------- + +leo: +- Fixed copy text from the console (win32) +- inspectors and Z window always floating on top +- Added ungroup command to right click menu +- Fixed GL error on win32 startup +- Added splitters to Entity dialog (customizable layout) +- Fixed shortcuts.ini parsing bug +- Fixed editpad crash if editpad not present (win32) + +TTimo: +- HTML versions of Q3Radiant manual, Shader manual and Model manual +- added Escape key to hide the entity inspector +- S and Shift+S now act as toggles on the inspectors +- fixed DestroyCursor error +- new option in prefs: clipper caulks faces +- more plugin interface for BSP frontends, plugin SDK additions +- fix to the pointfile not drawing in 2D views +- MAX_BUILD_SIDES error reported in q3map debug stream + +GtkRadiant 1.1 beta Changes +--------------------------- + +MrElusive: +- bspc 2.1c, MAX_MAPFILE_PLANES bumped up (needed on big maps) + +G_Dewan: +- fix to BSP menu order getting mixed up +- fix to bogus noshader error message in q3map +- suppressed winsock2 dependency, only requires winsock1 now +- improved q3map, reducing minimal memory footprint by about 45Mb + +leo: +- Fixed q3map crashing if visbytes is greater than the maximum allowed +- fixed texture window not scrolling when last texture is large +- Fixed q3map Makefile +- Fixed +/- numpad keys not working in win32 +- Fixed Alt shortcuts not working in win32 +- fixed prefab path and user ini in preferences dialog +- fixed add/remove bsp items in project settings dialog +- fixed some console not working in view #3 +- fixed warning when exiting in views #2 #3 +- Fixed patch inspector not showing after it has been closed +- Fixed load window position bug (saved pos greater than screen resolution) +- Fixed selection nudge bug +- fixed Z window not shrinking small enough in floating windows mode (win32) +- Improved entity windows layout +- Added 'Reset' button to entity dialog +- added LOD for patches, see in prefs for the LOD you want to display on patches +- no more DOS dialog box under windows, Gtk errors and warnings are +redirected to the console +- remember size/position of the entities dialog +- new 128 and 256 grid + +- added shift+rclick+mouse move to zoom in/out of the view +(way cool feature! check it out!) + +TTimo: +- fixed the Region commands, "Region > Set brush" is working + Region uses the camera as spawn point. +- updated quakev2.qe4 with -vlight options +- added checks in q3map to prevent crashing on allocating a winding too big. +- improved snapshots behaviour, doesn't snapshot non-modified maps +- improved the CycleCapTexturePatch command, now cycle across the 3 planes only +and works on multiple patches in one shot +- fixed the help menu, Help > Help or F1 will try to open the Q3Radiant manual +(if you have office installed) (Help > Links also fixed) +- surface inspector allows to change increments steps and stores them in +registry. General behaviour of the surface inspector has been reworked. Undo +works better on the surface inspector. +- shift+arrows shortcuts now match the increments from the surface inspector +- button 'Match Grid' in the SI to set the increment to current grid + +- debug stream between GtkRadiant 1.1 and q3map: +As with the Alpha version, q3map connects to Q3Radiant to report on it's +progress. The progress reporting code has been enhanced with an XML stream. If +your map has errors that need geometry information to be fixed, Radiant can help +you out. To get a grip on this feature, load baseq3/maps/museum.map and compile +it (with BSP monitoring turned on of course). + +- cleaned the entity inspector window, better layout management + +- two new items in the View > Show menu: +the X, Y and Z axis have been assigned colors (Z: Red, X: Green, Y: Blue) +Show outline draws a colored outline around your view, helping you know what +view is currently active. +Show Axes will draw a small axis base in the upper left corner + +GtkRadiant alpha version Changes +-------------------------------- + +MrElusive: +-q3map 1.0q: fixed a bug in q3map that sometimes cause mapobjects not to be lit + +G_Dewan: +-more arrows drawn on entities with relevant angle information + +Fishman: +-antialiased lines + +leo & TTimo: +- based on Q3Radiant 202 +- patched q3map with network code +- shortcut keys are now in shortcuts.ini + the syntax has changed a bit, no spaces between the '=' + see shortcuts.sample +- no longer using the registry, settings are stored in radiant.ini and savedinfo.bin + in Radiant directory +- project settings templates: + the project settings file format has changed a bit. + it has version information now, and the setups come with a new quakev2.qe4 file + the project files are true templates, Radiant will read them and then store actual + project settings it uses in baseq3/scripts/user0.qe4 user1.qe4 etc. +- new compilation monitoring + GtkRadiant comes with a modified version of q3map that reports to GtkRadiant through + the network. You get the output of q3map in the console window. And the compiling + errors can be processed (currently it detects leaks) +- sleep mode: + before running Quake 3 Arena to playtest your maps, you can put Radiant to sleep: + manually with File > Sleep command + automatic if you compile and set "Activate sleep mode when running the engine" + +Q3Radiant 202 Changes +--------------------- + +MrElusive: +-q3map 1.0p +-Tool options.txt (q3map command-line doc) + +G_Dewan: +-added functions to the GL interface for PrtView + GL plugins now get the camera position information + +TTimo: +-fixed sound and model assignment. project files are supposed to be written with + short path names. If it's not it might break. Added fixes in sound and model + assignement to go around the problem. Too late before next release to try + adding automatic clamping to short path name of the project file. +-high color is always on, it's a remnant from Q2 +-some fixes to find the default project file and interpret it when it's a template +-proper error handling in the JPEG library, no longer crashes on progressive jpegs +-added a "shaderlist.txt only" option to the texture menu, will display only the + .shader files listed in shaderlist. Workaround for the 127 texture count limit. + (saved with prefs) +-when building texture menu, Radiant will dump in the console the list of .shader files + that are not referenced in shaderlist.txt +-texture menu shows .shader files that don't have an associated texture directory +-storing "don't select curves" and "don't select models" from the toolbar in the registry +-alpha channel: some buggy textures have a nearly empty alpha channel and they might be + invisible if used in a shader script with qer_trans. Will now show up blank-transparent. + Best is to fix the alpha channel of the texture by saving it as 24bits instead. + Or disable alpha channel support in the prefs (added a new checkbox) + WARNING: disabling alpha channel might break curry on some shaders + about the WARNING: .. has empty alpha message: + this means Radiant detected on these buggy textures, alpha channel will be completely ignored +-speed improvements: added hash tables, improves map load (Map_BuildBrushData) and + texture find/replace. More or less evolves in log n with size of the map instead of previous n^2 +-fixed some notepad spawning crashes + will be better at finding shader definitions +-added "HOME" keyword when parsing user prefs keyboard shortcuts for HOME key (heh) +-fixed duplicate HideSelected command in the Help > Command list, added proper ShowHidden to the command list +-fixed a crash when max texture directories reached (currently 127) +-added a log file, turned on/off from prefs (default is off) + automatically turned on after a startup crash (Radiant.pid) + +Q3Radiant 201 Changes +--------------------- + +MrElusive: +-updated q3map, fixes a -vlight bug +TTimo: +-fixed a bug in texture submenus +-installer defaults to putting an empty registry.ini in the tools directory + (will solve some setup problems) + +Q3Radiant 200 Changes +--------------------- + +Id: +-bspc 2.0 +-cfgq3.c config file with bspc +-q3map 1.0m +Spog: +-updated default project file to handle new -vlight param in q3map +TTimo: +-added surfaceparm hint to common/hint shader in the editing media +-added back the Texture > Directory list... command +-collapsing texture directory items into submenus when a common root is found +-fixed a Region > Set selected brushes bug (the kind that only happens to MrElusive) +-added back Selection > Invert (shortcut I) .. (it disappeared at some point) +-fixed U shortcut for Textures > Show in use +-fixed the texture subset filtering in the texture window +-added Select / Don't select models toggle in the toolbar +-FIT command for BP mode +-Region > SetSelectedBrushes to shift+ctrl+R + NOTE: ICQ will intercept the shortcut key .. if someone has a solution for this let me know +-disabled the qer_nocarve check for CSG-MERGE operations, you can merge hint brushes now +-added a temporary Radiant.pid file to intercept crashes during the startup sequence and prompt for a registry cleanup + (some bogus registry settings might cause a crash before entering the interactive mode) +-clip filtering works same way as caulk, if hide clip will still display non-clip faces on clip brushes +-console keeps scrolling +-Ctrl+H shortcut toggles show/hide Hint brushes +-199->200 stability fixes: + fixed Shift+A SelectAllOfType on patches + fixed a crash in Flush & Reload shaders + fixed a crash File > New Map with selected faces + fixed Vertex manipulation crash + sanity checks: don't allow textures with spaces into map files, nor color brushes + (reducing the parseEntity: { not found errors) + blocking classname editing from the edit field of the entity inspector +-Undo: + fixed Undo crashes + better Undo on fixed size entities (like misc_models and weapons) + some bugs in undo create bogus worldspawn entities that corrupt the map. + Loading several worldspawn will merge them in the primary one. + Radiant will drop supiscious worldspawn entities at save time +-find/replace bug fixes (still needs some work in BP mode) +-fixed broken View > Show In Use +-fixed installer problems +G_Dewan: +-hash code in PAK library, speed improvement for Map_BuildAllDisplayLists +-fixed a crash on exit (weird MFC bug, see #107030 +-fixed crash in vertex editing + +Q3Radiant 199 Changes +--------------------- + +Mickey: +-PakMan plugin +G_Dewan: +-GL_MODELVIEW in camera rendering for PrtView depth queuing +-fixed the texture window raising after a resolution change +-PrtView plugin +TTimo: +-light intensity prompt when you create a light (Tim Willits feature request) +-fixed some crashes in Undo code +-engine path lookup, Radiant uses same code as q3map to guess the engine path and the basepath +-fixed crash in "vertex editing splits faces" +-loading .md3 and skins from .pk3 files, fixed various .md3 loading issues +-case sensitivity fixes for shader names +-IGL interface allows drawing in the camera view +-textures sorted in the texture view +-clear patch overlays hooked to Ctrl+L +-fixed a Curve > Matrix > Transpose bug on non-square patches +-removed some old unused shortcuts +-new FitTexture shortcut Shift+B, will raise the surface inspector and perform face and patch fitting +-fixed alpha channel bugs, re-enabled qer_trans +-CSG Merge shortcut as Shift+M +-patch creation uses workzone +-fixed sfx/comprscreen/letters loading +-textures / shader code rewrite, based on a new CShader class + -texture robbing bug fixed: two shaders relying on the same texture are treated properly + -will provide basis for other games support, and better shader editing functionality + -loading a texture directory will: load all the shaders in that directory, and load all + the remaining textures that have no shader. + -shaders parsing: when several definitions for a shader exist, first is used +-better GetLastError message processing on error +-fixed Fit command in surface inspector for BP mode +-prefs choice "vertex editing splits faces" +-3 points clipping and brush creating, third coordinate in 2D view is based on the "work zone" + +q3radiant 182-197 Changes +------------------------- +Editor (TTimo): ++ textures are now loaded with their alpha channel. was a required feature for + the Curry plugin. Some issues have surfaced with 32bit TGA files having an + empty alpha channel. (the textures were totally transparent in the camera view) + these files are buggy and need replacing by either a 24bit TGA, or a proper + alpha channel ++ shortcut keys: + fixed L shortcut for entity list + fixed M shortcut for map info + fixed Ctrl + \ for cubic clipping + fixed Ctrl + Shift + K for next leak spot + fixed R shortcut for mouse rotation ++ brush grouping: not functional yet. Needs Brush Primitives (BP) to be turned on. + basic infrastructure for grouping is done ++ fixed View > Show > Show angles checkbox ++ fixed BP file load / save and loading old format from BP mode. ++ fixed q3map to read BP map format + +Plugins API (TTimo): ++ Patches in/out ++ Entities in/out ++ Added GL bindings to IGL interface ++ New IBSPFrontend interface to override the BSP menus ++ New IMessaging interface for message broadcasting to plugins ++ New IPluginEntities interface to allow plugins to render + and interact in the Radiant windows ++ New IShaders interface for more textures / shaders support ++ Fixed GL context issues, texture binding would fail if binded to a plugin's GL context + +q3radiant 180-181 Changes +------------------------- +-q3map will read jpegs from disk +-remove junk.txt before very bsp, fixes a crash +-fixed gamma changing crash +-fixed thickening multiple patch crash +-fixed hollowing patch crash +-reworked how keys are mapped +-key binding should work again +-ctf spawn points show angles + +q3map v1.0a changes +------------------- +-q3map will read jpegs from disk +-fixed crash on texure loads of less than 4K ( jpeg only ) +-added version info + +bspc v1.7 changes +----------------- +see the bspc.txt file + + + +Known Problems +-------------- +-Prefs are NOT retained when moving to 179 (or later) from earlier versions ( than 179 ) +-Windows are all stacked when switching to floating window mode. You must manually align them as you like. +-You must manually run the bot bsp'r if you want bots to be able to run around +-I have not tested plugins extensively with this version but will do so over the next day or so. +-Need to clean up several small memory leaks + + + +Info +---- +The editor/tool install MUST be installed in the same directory as Quake 3 Arena. This means if you installed Quake 3 Arena in c:\quake3\ you MUST point the tool installer at that path. + +If you want bot support in your maps, you must manually run the bot bsp tool. It is in the \YourQ3InstallPath\Tools\ directory and is called 'bspc.exe'. After you have produced a bsp, you can execute it like: + +bspc -bsp2aas c:\quake3\baseq3\maps\mymap.bsp + +The editor and included q3map support a new feature called brush primitives. This provides per vertex coordinates for texturing as opposed to per face shift/rotate/scale values. This is a good thing. This is not enabled by default ( you can do so in the project dialog ) but it is still considered experimental AND it changes the map format. A plugin that also supports it will be available soon on www.qeradiant.com. We will be moving to this new format soon but currently use the old format. Consider brush primitives "subject to change" but we are going this direction. + +Once installed properly, you should be able to open q3dm1sample.map, choose "BSP - Fast Vis" and within a few minutes you should be able to run around in that map. Remember you must have sv_pure set to 0 to load up maps that do not exist in the pk3 file. + +If you shift click on a shader ( white framed texture in the texture window ) AND you have EditPad installed and available on the path ( or the Tool directory ) it will open up EditPad and position the cursor on the shader definition within the shader. EditPad can be had at http://www.jgsoft.com/ , it is a nice replacement for Notepad. + + + + +174-179 Changes +--------------- +-The color coding for failed texture loading has changed, blue and black for failed textures, red and black for failed shaders. +-clip/hint brushes default to NOT shown +-default to bilinear display mode +-default to 50% texture window scale +-bsp text is piped to console and text file +-Project dialog tab order is correct +-Project dialog no longer truncates entries ( it still needs work ) +-Drastically reduced memory consumption +-Skins are now cached and only loaded once +-Mirroring on X or Y axis properly flips misc_models and angles +-PK3 Support +-JPG Support +-Plugins can now load files from pk3 files ( transparent to the plugin ) +-Shaders can now load from pk3 files +-Failed shaders no longer load a "notexture" image +-Shaders are loaded from shaderlist.txt instead of *.shader +-Shader texture loading is deferred based on All, Common or None rules. New Pref based on this. +-Models can now load from pk3 files +-Fixed Win98 Copy/Paste bug +-Fixed Win98 BSP execution bug +-Project info dialog sucks less +-Properly handle long path names ( embedded spaces mainly ) when calling q3map. +-Better bsp execution, output is piped to dos window and console +-Q2/Q3 Pref handling works +-Finished Installation +-Better defaults all around +-Better window resizing, defer'd decision to default to floating window resizing. +-Fixed crash on loading new projects +-Seperated block allocator and actual size allocator to reduce memory overhead +-Failed textures only try to load once +-Failed MD3's only try to load once +-Cleaned up shader loading +-Editor loads without requiring information from the user the first time ( assuming it was installed in the right place ) +-Fixed case comparision problems with some PK3 file contents +-Fixed allocation bug when cleaning out PK3 files on project reload + +Build 173 + +Changed +- Copy/Paste now uses the Win32 mechanism. This greatly speeds up editor to editor copy/paste. + + +Build 172 + +Added +- Hide and Unhide brush(s). Hide ( View Menu and 'H' ), hides the selected brushes. Shift-H shows anything previously hidden. +- Fit functionality in the Surface dialog no longer fubars the FIT exiting the dialog via OK or APPLY. +- Copy/Paste now functions between two open copies of the editor +- Textures | Show In Use now pays attention to Patches. +- The editor will now properly find a default project based on the assumption it lives in the \quake3\q3radiant\ path + + +In Progress +- Pref Dialog now has functioning Q3 and Q2 options. +- ASE Model load. Should be bug free in another evening. +- ZIP support +- Model Plugins for supporting various model types ( MD4, TIKI, MEN, ASE ) This is essentially moving all of the model preview, interaction, and animation to a plugin to clean the code up. + +Deferred +- The new file format changes and new texturing is finished but based on the probable ship date(s), I dont feel comfortable mucking with the map format at this time. This code will be used in the new editor or can be rolled out once Q3 is gone. + + + + +Build 169-171 +Added +- Control + Shift + X toggles crosshairs +- Added Fit functions to surface dialog. These are more test stuff than anything as the new texturing capabilities coming with the new file format will allow much more versatile texturing. + +In Progress +-New file format changes, true ST texturing for brushes, automatic conversion. This will happen soon. + + +Build 168 +Added +-Control + Texture Click to apply texture applies with proper scale on angled faces. + +Build 167 +--------- +Fixed +-Crash when edge dragging brushes with more than 1K points (total) + + +Build 166 changes +----------------- +Added +-Shift-C cap current patch +-Shift-A will now highlight all patches and caps based on 1. the currently selected face or 2. the current texture if there is no brush or face selected. If a brush is selected it still searches and highlights based on entity class. + +Build 165 changes +----------------- +Added +-Texture lock status information, on the right edge status pane, L:move,rotate status. so L:MR means both move and rotate are selected, L: R means just rotate, etc.. Shift-R and Shift-T are hotkeys (always have been) for the locking. Help|Command List has a list of all bound keys. + +Changed +-Curve primitives moved from a submenu back to the root. + +Fixed +-Crash when pressing 'v' with large number of brushes selected. + + +Build 164 changes +----------------- +Added +-Brush | Primitive | Sphere - With grip snapping on, this produces some pretty odd faces, not sure what they will look like in game as I have not run one through the tools. If you turn off grid snapping they are constructed very well but as soon as you turn it on and then save/load everything gets snapped. This shows that the current implementation of grid snapping is pretty useless if used as a toggle. I am looking into some painless ways to deal with this (from a users point of view) + +Changed +-The current selection is no longer de-selected when a texture directory is loaded. +-The default increment for texture manipulation on patches was change to 0.05 from 0.10. You can also type in much smaller or larger increments into the dialog which will then be used. + +Fixed +-Crash when using the clipper on a curve. + + + +Build 161 changes +----------------- +Added +-Edit | Load Prefab... This was there but it now uses .pfb as the default extension for prefabs. MAKE SURE and set the full prefab path you want to use in prefs. +-Edit | Save selection as Prefab... This saves the current selection as a prefab. Defaults to the pref'd prefab location and .pfb extension. + +I am working on a preview window that will be used for these and misc_models. Look for that in a few days. + +I also discussed grouping (i.e. func_groups) with John a visit or so ago and the current plan is to add key/val pairs to brushes and patches and do away with func_groups. This will allow a lot of flexibility for grouping (hiearchial, named things) as well as other areas too. I will be presenting the overall plan to John soon and if we go with that then this will allow the prefabs to be grouped problem etc. + + + +Build 157 changes +----------------- +Fixed +-misc_model bounding box size on intitial creation +-copying/pasting a misc_model did not update the active edit entity +-misc_models will not continually try to load if the MD3 is not found + +Added +-Origin drawing on misc_models, this is what is snapped to grid +-Pref to turn off 'Show Shader' checkbox +-Show Shader checkbox to prefs +-Shift-A with a selected face selects all brushes that 'contain' that faces texture + +Known +-Assigning a new model to a rotated misc_model screws the bounding box up + +Build 156 changes +----------------- +Fixed +-Texture replacement bug when regioning. + +Build 155 changes +----------------- +MAJOR changes internally with this version. Make sure everything is backed up. There should not be anything destructive in it but this is a good precaution. There may still be some memory leaks from the new patch storage stuff, those will be addressed in 156. + +Changed +-Patch storage, no longer fixed size, unlimited patches. + +Fixed +-Undo, should now work in many more cases, and with patches. (this has been tested the least) +-Cap texturing. Worked in my test cases. If you get a cap that does not texture properly I added +|a key combo for cycling the axis face it textures against. Ctrl+Shift+N does this. + +Added +-OpenGL Lighting, turn it on via the view menu. Provides consistent shading/preview between brushes/patches +-New vertex handling, not enabled at present. Still tweaking. +-SHIFT-A selects all entities based on the current selected entity. + +Build 154 changes +----------------- +Fixed +-Another instance of entity copy/crash across maps. + +Build 153 changes +----------------- +Changed +-"sound" to "noise" +-Show Entities toggle does not fool with func_group'd stuff + +Build 152 changes +----------------- +Fixed +-Copy/Paste crash +-Copying brushes across a new map load no longer continually copies them on each subsequent new map + + +Build 151 changes +----------------- +Added +-Shift clicking a shader in the texture window now spawns "EditPad" and places the cursor at the apporpriated place. + +-Patch Inspector (Shift-S) allows access to all patch data and dups surface patch handling. Only works with one patch at a time. If mutliples are selected it uses the latest. This is not quite finished but works. + +-Sound player in the Sound Open Dialog (entity window) + +Fixed +-Crash when 'Capping' a brush. + +Fun Stuff in progress +- MD3 -> Patches. Drop an entire MD3 as a grouped set of patches. + +Build 150 changes +----------------- +Added +-Texture show all command. +|Behaviour is as follows: +| 1. Show in use, shows in use. +| 2. Show all, shows everything. +| 3. Showing a particular path, only shows textures in that path. Show All or In Use +| can be used to un-narrow the view. + +Ctrl-A is the show all hotkey. + + +Build 147-149 changes +--------------------- +Fixed +-misc_model entity preview bug +-Texture show in use now works properly +-a few shader issues + + +Build 146 changes +----------------- +Fixed +- Copy/Paste entities across maps crash +- A ton of memory leaks + +Added +- Shift-TAB works in bend mode +- Pointfile is removed on file saves + +Build 145 changes +----------------- +Fixed +- misc_models no longer shift during save/load +- misc_models copy/paste correctly +- misc_models paint when first assigned +- assigning a new model name to misc_models works + +Added +- Shaders have white border in texture palette +- New shader test code based on the fact that shaders are not directory oriented. On by default, to return +| to the old method, turn "Shader Test" off in prefs and restart. This behaviour loads all of the shaders +| at load time and provides the ability to turn off shader display in the palette. The editor MUST FIND +| qer_editorimage in order to work properly with these (this is true for the old stuff too). This should +| fix the shader xref problems. + + +Build 139 changes +----------------- +Fixed +- MD3's are now origin'd properly with their bounding boxes +- Plugin texture load bug + +Added +- Texture quality slider (prefs). This can drastically reduce memory and increase speed at the expense of visual quality. +- Shader support for MD3's, proper shader is pulled per surface (assuming it is tga) +- Multple MD3's can be assigned on a per entity basis. Seperate them with a ';' +| i.e. model=models/players/medium/visor/head.md3;models/players/medium/visor/lower.md3; etc. +- Added 'frame=' specifier (0 based) to entity preview to use a particular frame for preview +| This is still a bit fubar'd +- Subdirectory map support ala Raven +- 'color' entity setting ala Raven +- Shader 'qer_editorimage' textures are now load deferred until their parent texture directory is loaded +- Full Plugin support +- You can now override surface dialog names with a plugin (not much use for q3) + +Changed +- After assigning a model or sound, focus is given back to XY +- If you right click and add a misc_model, the entity window is automatically +| switched to and the add model dialog is activated +- Models no longer try to load if they fail once +- Prefs layout. Still ugly. + + +Build 138 changes +----------------- +Fixed +- qer_editorimage behaviour + +Build 136-137 Changes +--------------------- +Added +- qer_editorimage flag to shader parsing +- md3 support for entities +- md3 support for misc_model <- this works but it is a cluster fuck of code +| the code tries to pick up the same name for a skin (in the same path) +| grid snapping is not happening, i need to fix this +| some models (like pentagram1.md3) appear to be OFF as far as their constructed origin +| others (all others that i tried) are correct +| Free rotation does not work for these but the entity rotation stuff works fine +- Model... button in entity window for assigning model=value to current entity +- Sound... button in entity window for assigning sound=value to current entity + + +Build 134 Changes +----------------- +Added +- qer_nocarve flag +- qer_trans flag use 'qer_trans sometransvalue' marks a shader as transparent +- patches draw transparent based on shader information +- default fog setting changed from 0.15 to 0.35 +- Plugin menu added (non functional yet) +- Fonts default to standard windows gui font for improved readibility +- Textures associated with a shader have [] around the name +- Shift-clicking a texture associated with a shader brings up the shader in notepad + +Changed +-Max brush size bumped to 8192 (Brian Hook for testing), did not test with build tools + +Fixed +-Entity dialog lets you type in more than visible space for key/val pairs +-Transparent bug when transparent brushes painted in front (visually) of a curve + + +Build 127 Changes +----------------- +Added +-Ctrl+Shift+P, this cycles the cap texturing axis on the selected cap. Basically, until +I figure out what the heck is wrong with the auto stuff, use this to correctly texture +a cap that is fubar'd. +-CAP texture button in surface dialog. This does the same as above but uses the default +axis finder (which has the bug). +-Matrox transposition + +Build 126 Changes +----------------- +Removed +-Test code for thickening. It was hooked into overlay clear... oops. + +Changed +-csg ignores patch symbiot brushes + + +Build 125 Changes +----------------- +Fixed +-Shift vertex (row/col) selection for patches. + +Build 124 Changes +----------------- +Changed +- Temporarily reverted back to patchDef2 .map file format. + +Build 123 Changes +----------------- + +Changed +-.map patch format, added 'type' field. not used anywhere except the editor (at least for now) +- Patch vertex manipulation. The editor tries to pick all existing points from other patches as well +|You will want to do area selection (in an XY-type view tp select thick patch points if you want to drag +|everything related to the thick patch + +Added +- ESC will deselect any patch vertex points selected instead of going completely out of vertex mode +|This only happens with patch vertex selection +- Thick patches, Curve | Thicken... or press Ctrl + T. The thickening happens away from the texture +|side of the patch. So if you want something to thicken inwards, make sure the texture is facing out. + + +Build 122 Changes +----------------- + +Added +-Texture Flush (forces a new map, then a total texture flush) +|One small nit on this is that if you create a brush before loading a new set of textures it +|will use one of the recently loaded textures instead of the default no texture (blue and black checkerboard) +-Patch inverting. +|Ctrl + I invertest the patch, +|Shift + I inverts texture in X, +|Ctrl + Shift + I inverts texture in Y +-Overlay function for patches. Turn on|off using menus or press 'Y' to make a patch an overlay and +|ctrl+shift+Y to clear all overlays + +Fixed +-Inverted beveles cap and texture properly + + +Build 121 Changes +----------------- +Added +-Patch texture inverting. Ctrl + Shift + I. I think this does what you wanted. +-'S'urface dialog grabs texture name from a single selected patch + +Better (a little) +-Capping bevels makes the cap the right shape but the 3rd point is not on the right plane. +In most cases you can move it and snap it to the right place. Texturing is not all fucked +up anymore either. Once I hear back from John on my math stupidity, I will finish fixing it. +These statements are probably only true for non-bent bevels.. bending will probably screw +stuff up again. + +Build 120 Changes +----------------- +Added +-Fine tune texturing for Patches. Using the surface dialog, you can type in very small +(3 decimal places) numbers for fine tuning on both scale and shift. Currently you can +only fine tune with the Surface dialog, I am in the process of adding a fine tune settings +dialog for use with the keypad keys. + +Build 119 Changes +----------------- +Added +-TAB cycles through any entity grouped brush +-Texture window can now be scaled by 10, 25, 50, 100, 200% +-Texture window now has a optional scroll bar, turn it on in prefs + +-Texture path linkage +Put a "textureinfo.ini" file in a texture directory and add the following + +[include] +Path0 = e:\games\quake2\plop\textures\24 bit\ + +You can have Path0 - Path9 as inclusive directories. The linked pathes will not +load and process link information to keep someone from linking two paths together +and causing an endless loop + + +Build 116 Changes +----------------- +Added +-Ctrl-P Toggle show patches + +Fixed +-Gamma correction + +Removed +-Model preview load code which was probably causing brandon's crash + + +Build 115 Changes +Tweaked +-Cap texturing +-Ctrl S was already bound to File | Save +-Ctrl C is bound to standard windows Copy + +Added +-Ctrl-S Save file + +Build 114 Changes +----------------- +Tweaked +-Cap texturing + +Added +-ALT + Shift click will cycle through "selectable" brushes along the +line of site from the mouse click. Works in all views. This is cool. +-Square cylinders + +Fixed +-Show curves works properly + +Pending +-Shifting textures on patches should honor scale (cannot dup) +-Disperse columns crashes (cannot dup) +-Capping bevels is messed up +-cap texturing may still be fubar'd +-Thick pipe primitive +-Scale origin + point scale similar to rotate. Will allow fluted stuff etc.. + +Build 113 Changes +----------------- +Added +-Capping (with dialog for Bevels and Endcaps) + +Build 112 Changes +----------------- +Fixed +-Yet another surface dialog glitch +-Area selection tool paints in all xy oriented views + +Build 111 Changes +----------------- +Fixed +-Surface dialog face selection bug + +Build 110 Changes +----------------- +Added +-'B' - toggles Bend mode + +Fixed +-Naturalized texturing on patches +-Bend whackiness on intial drag during start of a bend +-Setting bend rotation axis in XY Top view + + +Build 109 Changes +----------------- +Added +-Ctrl 'I' - inverts a curve +-Ctrl 'Numpad +' adds a row +-Ctrl 'Numpad -' removes a row +-Ctrl+Shift 'Numpad +' adds a column +-Ctrl+Shift 'Numpad -' removes a column +-Ctrl 'E' - redisperses rows +-Ctrl+Shift 'E' - redisperses columns +-Ctrl 'G' snaps patches to the nearest grid +-Surface control dialog now allows values to be typed in and applied to patches. + This is working except for shifts which still go in .9 and 1.1 amounts. + +Changed +-Regioning no longer puts a top and bottom on the region + +Build 108 Changes +----------------- +Added +-Area selection. Works in 3 xy views but only draws in the xy view. I will look into this asap. + +Changed +-Selection point size for vertex mode with patches. i.e. The handles for dragging patch points around are bigger. + +Build 107 Changes +----------------- +Changed +-Inverted curve options removed from menu +-Bevels and endcaps are contsructed properly +-Endcaps will not work for bevels and endcaps + +Build 106 Changes +----------------- +Fixed +-shift middle clicking a light now adds the correct average color based on .TGA files +-display updates properly when using TAB to go between func_group'd brushes +-Patch texture rotation is much better. Still not completely perfect but should be usable. + +Build 105 Changes +----------------- + +Added +-TAB cycles through func_grouped brushes +-textures with a shader have a [] wrapped around the name in the texture window +-shift + left clicking on a shader texture in the texture window will bring up notepad with the shader file diff --git a/setup/common/setup.pm b/setup/common/setup.pm new file mode 100644 index 00000000..73113ccf --- /dev/null +++ b/setup/common/setup.pm @@ -0,0 +1,139 @@ +#!/usr/bin/env perl +# +# Common setup functions for building release packages +# + +# Gef - Jan3 2002 +# - Initial framework for migrating setup/release building to perl + +# TODO: +# Gef - Cleaner handling of cons builds, currently doesn't catch cons errors + +package setup; + +# Package constructor +sub new +{ + my $this = {}; + bless $this; + + return $this; +} + +# Replace in file parm-1; parm-2 with parm-3 +sub replace +{ + shift; + my $file = shift(@_); + my $search = shift(@_); + my $replace_with = shift(@_); + + # need to use | instead of / with sed since the variables will contain /'s which confuse it + system("cat '$file' | sed -e 's|$search|$replace_with|g' > '$file.tmp'"); + system("cp '$file.tmp' '$file'; rm '$file.tmp'"); +} + +# Not exactly common (between win32/linux), but useful here all the same +sub cons_build +{ + shift; + my $BUILD_DIR = shift(@_); + my $BUILD_CMD = shift(@_); + # use a direct system() call since syscmd doesnt catch cons errors + system("cd $BUILD_DIR; $BUILD_CMD"); +} + +# Maintain a list of errors that have occured +sub collate_error +{ + #shift; + my $err_type = shift(@_); # unused + my $err_command = shift(@_); + + @errors[$err_count++] = "$err_command"; +} + +# Output the list of errors stored +sub print_errors +{ + my $count = 0; + + if($err_count gt 0) + { + if($err_count > 25) + { + print("$err_count Errors!! Ouch, looks like something screwed up.\n"); + } + else + { + print("$err_count Error(s) encountered\n"); + } + + for($count; $count lt $err_count; $count++) + { + if(@errors[$count] ne "") + { + print("-> @errors[$count]\n"); + } + } + } + #else + #{ + # print("No errors encountered.\n"); + #} +} + +# A wrapper for system() calls that catches errors +sub syscmd +{ + shift; + my $command_string = shift(@_); + + # todo: identify multiple commands (commands split with ;'s) + # todo: catch cons errors (cons doesn't return a value) + system("$command_string"); + my $sysretval = $?; + + if(($sysretval gt 0) && ($sysretval lt 257)) + { + @cmdlist = split(" ", $command_string); + if(@cmdlist[0] eq "cp") + { + collate_error("copy", $command_string); + } + elsif(@cmdlist[0] eq "mv") + { + collate_error("move", $command_string); + } + elsif(@cmdlist[0] eq "cons") + { + collate_error("cons", $command_string); + } + elsif(@cmdlist[0] eq "cd") + { + collate_error("changed dir", $command_string); + } + elsif(@cmdlist[0] eq "mkdir") + { + collate_error("make dir", $command_string); + } + elsif(@cmdlist[0] eq "cat") + { + collate_error("cat", $command_string); + } + elsif(@cmdlist[0] eq "rm") + { + collate_error("remove", $command_string); + } + else + { + collate_error("unhandled", $command_string); + } + } + + return $sysretval; +} + + +# Close package +1; diff --git a/setup/credits.html b/setup/credits.html new file mode 100644 index 00000000..4e3c7fd8 --- /dev/null +++ b/setup/credits.html @@ -0,0 +1,220 @@ + + + + + Radiant - Credits + + + + +

+
+
+ + +
+

Credits

+
+
+ +
    +

    + GtkRadiant 1.5 development
    +


    +
      + · Thomas "namespace" Nitschke spam@codecreator.net
      + · Stefan "Shaderman" Greven
      + · "Topsun"
      + · SmallPileofGibs spog@planetquake.com
      + · "Tr3b"
      + · Eric "eb" Barth
      +
    +

    + +
    +
    +

    GtkRadiant 1.4 developement

    +
    + +

    + Loki Games
    + Loki games was the instigator of the project. + Leonardo Zide wrote the initial port to Gtk+, which is based on the win32 version of Q3Radiant by + Id Software. +

    +

    + + +

    + Id software
    + Id software is actively supporting + the project by providing resources, advice and developer time. +

    + + + QERadiant.com
    + QERadiant.com is the main entity + behind the editor. The team has been handling editor developement, website + work and documentation for several years now. Many people on the team have + been involved in several areas, plugin developpement, contributing patches, + doing documentation (.. and fixing my bad html)
    +
    + + +
    + +

    + Thanks to everyone on the beta mailing list and irc.telefragged.com #qeradiant + for testing and feedback. +

    + Updated icons by AstroCreep +
    Bitch-slapping by raYGunn +

    + Special thanks to The Gtk+ project and specially Tor Lillqvist, maintainer of the win32 port of Gtk+. +
+ +
+ + +
+   +
+
+ +
+
+ + + diff --git a/setup/data/tools/bitmaps/black.bmp b/setup/data/tools/bitmaps/black.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8fc59ba111ea578deadd075b190b418f7cf1fed3 GIT binary patch literal 248 jcmZ?r{lNeMWN7Q f#>R$%h9LFAfvNT1E`3B5ljQ!3*m#rg@BqM!ipeKpfZ?gAf6xq8^jHU literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/brush_rotatez.bmp b/setup/data/tools/bitmaps/brush_rotatez.bmp new file mode 100644 index 0000000000000000000000000000000000000000..fcea0814fb8ebcd4c8faae2fc244f9f857e8be36 GIT binary patch literal 238 zcmZ?rea8R+Wk5;;hy{R{ABY(lSb!uh(7^B?2pe!o6A%|f0z!g9V4jem5`-3l@)cp? n5E`yU0Lo%u6ckiaQUtnJ5NrfkTv1REC=3z+ih|`q6bt|W69Ely literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/cap_bevel.bmp b/setup/data/tools/bitmaps/cap_bevel.bmp new file mode 100644 index 0000000000000000000000000000000000000000..0de324422fd7dba22730213de7b6f25766141550 GIT binary patch literal 154 zcmZ?roy7nFc0fu4h$Vqo9Ecf#5Ep0wO8o!-pWz3PM#q1EV#xR(kPpKDLAU{m4*=;0 VK>PuSfr|eF75oPZ0L^TGVE}qrD%t=5 literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/cap_cylinder.bmp b/setup/data/tools/bitmaps/cap_cylinder.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2e4d11af14051be96b78da94238319ff23ffa443 GIT binary patch literal 156 zcmZ?rox=bDc0fu4h$Vqo9Ecf#5C(*}85lqq2LAv54`VVg05vlHKfu8J{{aL0{|^lO g|9>#Z|NjHW_W%DeAY)`XkXn!)kY11(AhQqv0IzK-KL7v# literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/cap_endcap.bmp b/setup/data/tools/bitmaps/cap_endcap.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8e6c7f2a36d1b44edd04cbdcc55cbc5845ba75f6 GIT binary patch literal 154 zcmZ?roy7nFc0fu4h$Vqo9Ecf#5Ep0wO8o!-pWy%lV*{!90RwXb41Zu?Z}`E$-|&Y) SzTqE(eZ&8Q{~Lhjf-wN=L@Go8 literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/cap_ibevel.bmp b/setup/data/tools/bitmaps/cap_ibevel.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ccab98daa2eacb91123dba59b83ff5837d279634 GIT binary patch literal 154 zcmZ?roy7nFc0fu4h$Vqo9Ecf#5Ep0wO8o!-pMm}V{|0o-3=~7gj6gmJ12r@-)I+g7 Vkd_BxejsKBVn!flkOvtH2LR8SFW&$F literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/cap_iendcap.bmp b/setup/data/tools/bitmaps/cap_iendcap.bmp new file mode 100644 index 0000000000000000000000000000000000000000..9640c9ae8c97b9c5d183743b93a805bbbabc9f48 GIT binary patch literal 154 zcmZ?roy7nFc0fu4h$Vqo9Ecf#5Ep0wO8o!-pTYkBKa#Qh|33^c%>Vxf1N;9E49x!@ PFfjf4bBs2MD znD_9#GphUXgK+z5%-HrSLp&=nR>u zr_G|XPN+iW&llGJXu>N>Nm3YqeUvUPlGRU=i$gyC{kd zhr{XDDSnQ1T?Ybfr_*^&4((ttSglr%VfYVnIw^_*1lz4v3rM5Us8lM|YL$92nXG@V zH=9ig+3)vsO+V_*xg<$G3NnY%1DhPi8i^&_( literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/curve_cap.bmp b/setup/data/tools/bitmaps/curve_cap.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e9d00a21abb96d045cf066f79423f70e13310a76 GIT binary patch literal 238 zcmZ{bu?>JQ3M!%P$U zNR?V^^r9+|>V3ri?@?k%5JwhEi9*44#)MSiviC!AQhE0sKNck;D4f}dq&aVc^!G%@ FcYQaM77YLZ literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/dontselectcurve.bmp b/setup/data/tools/bitmaps/dontselectcurve.bmp new file mode 100644 index 0000000000000000000000000000000000000000..a7b9795453902fefcccac030a4fea52bed91b66e GIT binary patch literal 238 zcmaitK@Na02n3hLyV4f`C*R@S?|AmbexWXHk0z49vO|F5d@$mo?97e1G6f6kcQiB( z8t(7$FV%d)SE4BbbU>JZwgcKQS3!iqw3dRYW^h&daIIcTK)Kh~vy3wdgmxax1}}ue B3CaKf literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/dontselectmodel.bmp b/setup/data/tools/bitmaps/dontselectmodel.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e81b118950ff73cb8aec9d83770c59a68ede1251 GIT binary patch literal 238 zcmaitK@Na02n3<=ZnrPkdh#9K{f=i}>=)|NdTb&IEIR}^&c{Yvl3kgVtI};zokz2# zL9=^4{-uZ~dgTalvkn9U$aWx0I%?uZ5+%uYWdI<2MfO4(wAa_8j57gT_B$Ajy{#5_RE0>q496374oA#Mm}1+qbyfuVunKM*!FGyqu;AP5Ep1%fVM OC7nhZiMJkGV!2 znm{NasM{{27)fZbwmpdk6VG_)&4ckj@Kg>Qj2)yg9-6+)OkOtM&%Ez@vns>wn{c_t zus+4Sk9iOC20X?J@M{3z!QFmde!wCKg5T#CMKKnQCYBP(Wb%DFy|TK3E1%D=Z>*Qg z@|S9LtF~3I*Bjdnm(wYD1i#f4(L2!@N?doSmKhkja!QZ}18fXUl69j=X z%49ORT#lmX9>TJ1I68$>R9?VEKhEJCsV3~900)G#K!hA0M|&Ez<_ylRl-D;M=-7Ea zv=~}mUQVY|1o0B>Q8dku^E*$z{~qjIiMs>c_M@ZK$yGEHiA3V@I3iZ}{)^Zz5kc_zd_hqJ_yc{_K$ZXi literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/file_open.bmp b/setup/data/tools/bitmaps/file_open.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1f1770c33a812e769f0eec75f1333d3a5436baab GIT binary patch literal 238 zcmZ{cu?>JQ3;-zp4B;SZi0+#q2Okt^9FTKQB9X{^CM!MpnV*WX!6&CX3>cf* E1G1zTbpQYW literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/file_save.bmp b/setup/data/tools/bitmaps/file_save.bmp new file mode 100644 index 0000000000000000000000000000000000000000..651afb68952db64d2954394aa6252f2fb3bccf3b GIT binary patch literal 238 zcmZ?rea8R+Wk5;;hy{R{ABY(lSb!vMaNxiJhW|jw(9nQe5e^w87=Xw#DHsD8g3Q=y e29P)yK;#*WnG_5JfdD9umu7&wy+3Xn)XC`KI=FB>4ul=uo{cG=Yiv7On zR&@BdXY=#^{kg1JSPbSp)8r=(%bKtz9A2*Ia2T9KnBa3yVy%-UAXf&qOBN^biN=83 z(R#hGU`dKn0NZ`{_O**mzTDinDdIX+6*6nw)hq^6!QP%Xq5fT!6*&v$^JvWa*Q_?2 zM!-yu=StK0LkIQ;wv}()yh)@`F{}+e?IlGl4pa3<>sFQ&+u#dB0$>>VI`X9Z!Lhnp zq|osJhk!wzC8z3-6EC`{__zkaggw6B`6Dt4+oGj~iV_C;@2ChYT9~6dVcz%XVOK|c zeqNsP#|0NBF$e`8Qc!|gCHT5HcwkOZg9EQIRHO~KNidcqa&X`ef&TnT%@A~ciLKM5O>)y&S8n1mzl|(p@agKMhw3jz<@t$lY&c~ zWrNjJk6_Xxwx3=yjfOlW2sm1ylHg+ykLKA~S?nkbv0>=V$Y9}&v^1S33bPz2?ucBq zcMmNF>gV?ey7G&flOfHaVAK-`sIDq(Z zVi1Ohil9-pu@o8@IKK@I;s;zm_+u+sJG2;eoyHeml?tF`vVB(BTF2?ZIFNUFXj_`wG-8Zvp~@%OKBUginC zdDskaMg+5f0TcaYVE_OC literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/lightinspector.bmp b/setup/data/tools/bitmaps/lightinspector.bmp new file mode 100644 index 0000000000000000000000000000000000000000..00b00a90c0a60501a577d626a04486dbb0f2dc20 GIT binary patch literal 776 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 Ffg7`s3N!!! literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/modify_faces.bmp b/setup/data/tools/bitmaps/modify_faces.bmp new file mode 100644 index 0000000000000000000000000000000000000000..3eb3838dd75b03077d57605d68509250530e2144 GIT binary patch literal 316 zcmb78K?;B{3`;iz!Gq!}e1s>Df`8i2XPV+1-b_iiBxUPy?FQjw-^hiWNyEqLeI2d=C(OqWt%D`z7heWgvYqw+}AgO|mFjy>qvg1^je;`g6EmFt6zYrxads0*TUYG2X> E+_azyMF0Q* literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/noFalloff.bmp b/setup/data/tools/bitmaps/noFalloff.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e86d10c9eb6f461ab202f2a02ae097da4307c836 GIT binary patch literal 1592 zcmZ?rwP0fa12Z700mKeK%mKuV3=%++fq@AqFT@SOU;!ZUe-w;{z-S1JhQMeDjK~mR GU;qFoj{F(` literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/notex.bmp b/setup/data/tools/bitmaps/notex.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b06664a7aa57260440cbeea2ad98710103b6fdc8 GIT binary patch literal 4312 zcmeHJT~gyX5bjoDGa1b$&X2R}1n)R#;>;r#po*IJsp4^7IRaI10FJ;7hAVIcP5?Y? z?K4N<0-CSoIFTI%c-|dVEVnxSbxSSjbAS8(Z$hs9gZ?X?)Ahsj0aDdQnn)h`Umj`J zTC(y^60$`>_V}3W=_%Q>GqOK^kUc*qdvQVb`kL&`4cVVR$==?Qy}KiOe^2)5iR|+; z*_Ri3!Sf9H3A)F}M|ya8pnKTd!S)uI8{n_u=j!T;E-x?X0)Efo{|vEC5%c8agpLuv zz&sMnN4?S90$%CffU8%a?yukY*E8_{%wIQ-sh&U&jaj_Ra903*cN8r1x6@Fyc0AEg0IB|F*PByI>e=mNI)9pzo|*If?-V)9)AY0C?{G3H!+5uU z*dV3c{&f}PBuDorb3u6jg$Mh0LDj7nq77lTt&y+vv?KYt;)VH_BQ zFupDTX{dbLklZj13_=)R7l1TWzHLZu7zYL+jIRqo8Y&A`C{yw}M zGIfBv=XgZuLJnc$)@>baV{Ifo8tKj$c;S(pr#O2!{CWH2RX#Q-Pe>9#SvwvVpr!C_LvqV}XBfixx*b4EQQI1P zC_-G(GT#~2cz43Gl`w^zJ^56*L->dL<5QV?$tDTg(%hNoF3lyS(@DZcs+4?2ofGR9N@lLd^|(bcBDU-0BmD6XopfCqsT@K@nK zHt_wy_Ab})v$S+`ujdcSo1~!53O`^Z5G>NG=C2|?STCT)&p1DtBjxM-EeM4gBPU3g z)`M35^kCd+;Y+L@>#z4OlX^o6Jy|XDpt*nSDm3SzjxPm9X;xBh2zM-{-jMUul_Eqe k*Cf*2D5?o;@nNa5+TVv!TkU@2@A#5|ZKlG~e?Nb}01ah>YybcN literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/patch_bend.bmp b/setup/data/tools/bitmaps/patch_bend.bmp new file mode 100644 index 0000000000000000000000000000000000000000..af4fc94c5dbf5292a0440636147cf61c3671f0c3 GIT binary patch literal 238 zcmZ`w$qj%o3`-Dy+6^lA!@JplKkM*jv`)kd;1Kaasw>Cx(QUsZa8OnVx(dw`FFq~qt)ZmIHTwrJ=AM#VMe-Z7j5w?pK7Igc!V2F ViIEi)faozYgwRTgsC)=d5CGF44;ugg literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/patch_insdel.bmp b/setup/data/tools/bitmaps/patch_insdel.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b60b76b033a9991dff98b807ccafbae1a25c6298 GIT binary patch literal 238 zcmaKju@QhU3=ad$qg-d1A5k>&cv($QrrTclNHIyeq1RyX$v(`Bh{sW^N*Tt rp8EIXV+WK1gXrN-Q;q^!m^$Aud(PQcWmAq;X6>6UN~F1yh_CYj3O*JL literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/patch_showboundingbox.bmp b/setup/data/tools/bitmaps/patch_showboundingbox.bmp new file mode 100644 index 0000000000000000000000000000000000000000..357cfaf16f93ebcdc1930455a13d7da75d9a7c78 GIT binary patch literal 238 zcmZ?rea8R+Wk5;;hy{R{ABY(lSb!uh(7wDicVgu=Z{6de^abVpq`M=WIN~g^<8e=1ndvl#y)7hODG!|HCRf zYvsxQclNQv4SNRVkdQ0_>Dd@aqMAb_yiyYkHqk+S$j+@|$l(oo=I9(Hf_O;8zgKVo D3GfuK literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/patch_wireframe.bmp b/setup/data/tools/bitmaps/patch_wireframe.bmp new file mode 100644 index 0000000000000000000000000000000000000000..9804274f144f6629015ccfae9a60d24dc560d995 GIT binary patch literal 238 zcmbV@u@!(o2txzUUfAFsm2=p;ju?>JQ3=0xFRGiR-v68DE05c;O@B?!%=7nOnLTup3cI-%5w#yn$+Dwk*Kz7zs zzmTmIv%-<)tgQVXs0jPxan%HC`PB%d30UcHbVfK9fcK7t`c&^!uRCx^l!*2>)?WDr DEQ=%; literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/redo.bmp b/setup/data/tools/bitmaps/redo.bmp new file mode 100644 index 0000000000000000000000000000000000000000..acce54e401de18b44d54b674e6fdd473f2b6e77f GIT binary patch literal 776 zcmciAOA5j;6ouhmod^yTM=rq?xB%yF$@c>l6$=Uqg5Zu^j!jap2}p{m)`6DXODNw- zLeqAqT*5vr=}oDcR8^`#LpofGP_MYMFQg?T{}^48-Zy($Z-$sjzLfYr&YL-U^(poA zQ=HdZ)Gj;a{0Niw>Og!7r97_}=rt-r?TL>MAD_8h(y6yTa6#e+7&*@jfBk@T_bVS? b{Mh;>dXwJ$^&jwj=9+%W`drda{a_Avrf$C8 literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/refresh_models.bmp b/setup/data/tools/bitmaps/refresh_models.bmp new file mode 100644 index 0000000000000000000000000000000000000000..a6a0aa5d55f7f0589c989c486f343cfec6fcaa15 GIT binary patch literal 776 zcmbu5OGuPa6vzLii_k^Vq6XT8hPrSS*~&tFDb1w!I2`x$gL(7t31aP#=e# zXeHVR58+B?HGbED$NsOd@S^RvREnD;uoBLHa6928x(J_C_EIBN-C;CQM(`qmrX*T4 zK1FtDq`+c5%xK$@k#f(x;v1tsIK&&Gk9j@Yw;hX0jF>2K2~2b|(;I)<7tu%H+rB+)E#EA zC?SRa#PspM2EdSJdlnQqNqiLk@L6!#02tEC&Io3RAk)Y8>t|LB-P37NXE=8xes5&s HWD7U~1Vg00 literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/scalelockx.bmp b/setup/data/tools/bitmaps/scalelockx.bmp new file mode 100644 index 0000000000000000000000000000000000000000..61363da20402f127cdf8042fabb5731217064d0d GIT binary patch literal 238 zcmZ?rea8R+Wk5;;hy{R{ABY(lSb!vM@Sgz)|AR=;eKt0VOa%qLq!5jg)|V b7_DS%tOVi%1t2t(50i({Xy!o8huH@J;eKt0VOa1(aJ_jO897F bW3ark5l|se9gt^?OvB9qF&JUy!|Ve9-)IxJ literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/scalelockz.bmp b/setup/data/tools/bitmaps/scalelockz.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5692e4a74fdf22cf5ad7bf381c97b2ea0003028d GIT binary patch literal 238 zcmZ?rea8R+Wk5;;hy{R{ABY(lSb!vM@Sgz)|AR=;eKt0VOa%qLqz}m6U*d cWh0;rNF2yTp+OR0^-y(C^=Rfm&4<|s0M#B7h5!Hn literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/select_mouseresize.bmp b/setup/data/tools/bitmaps/select_mouseresize.bmp new file mode 100644 index 0000000000000000000000000000000000000000..58da27f805defea740ab26bac51226f49efe3f05 GIT binary patch literal 200 zcmZXMu?>JQ3 q1)vciTG`mx01Isb76+<#HZe936fib6aY3bBpnRYaV0ly;;ywTvWg7zk literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/select_mousetranslate.bmp b/setup/data/tools/bitmaps/select_mousetranslate.bmp new file mode 100644 index 0000000000000000000000000000000000000000..68374ffdd31ff0495b0f251a0c5ba1e6495c1977 GIT binary patch literal 238 zcma)!u?>YV3`C7Y4TNk!MT@)vqW$Z9QD(FZ!5pj*-X#pc*=Of`cJ|+G4S(7SCpf~+ zdaBt8uc1+%m~*y5q?9b7ODWXv2rr_0eMR3wh@@oPNNHy@FN@2ZkG-1eNX^-2Nd>&! N>#}b36VR>D{09}Q8QA~; literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/selection_csgmerge.bmp b/setup/data/tools/bitmaps/selection_csgmerge.bmp new file mode 100644 index 0000000000000000000000000000000000000000..a77eba14167bcf7ea83abf1f868c134f1d49ac55 GIT binary patch literal 238 zcmZ?rea8R+Wk5;;hy{R{ABY(lSb!uh(7YV$Y8fGXK>=oCW1u+1Y`ARzVLB4u literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/selection_csgsubtract.bmp b/setup/data/tools/bitmaps/selection_csgsubtract.bmp new file mode 100644 index 0000000000000000000000000000000000000000..902f32f55f7ed75ac5ed0af1a4a9b4199872292d GIT binary patch literal 238 zcmb7-!3}^Q3`N_-v-YzAoSehE>llw6&UsuxKQx5In>4Q<`rESYx5POpD>1SVm5Ev8 xB-b3YLQ5_W!`(;e&&`w{sVEYVVF*QkE2;}iMFA^W>9EVbI{fJG^WS~dt-J+04*mcD literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/selection_makehollow.bmp b/setup/data/tools/bitmaps/selection_makehollow.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1b62f2232652ce58a761c8f3d30534cdf6e23b88 GIT binary patch literal 238 zcmZ?rea8R+Wk5;;hy{R{ABY(lSb!u0Lj@2Y;e%kX0GMcCIB?(q!+#*81TYGMfdC_e YfB++qU<3;Q*)VZ(`7raK=D^$t05Jj%NB{r; literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/selection_selectcompletetall.bmp b/setup/data/tools/bitmaps/selection_selectcompletetall.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1f911e8c728ce13cfc8c7982dad0158aa507ba55 GIT binary patch literal 238 zcmZ?rea8R+Wk5;;hy{R{ABY(lSb!ux(7c|%te=4a%r|CILZxBi#t3_ajRB;s82JDI literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/selection_selectinside.bmp b/setup/data/tools/bitmaps/selection_selectinside.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5670d3ad700284838f890bbeefab35194c60a0ee GIT binary patch literal 238 zcmZ?rea8R+Wk5;;hy{R{ABY(lSb!ux(7$3~io^8*`Nj+k%pkrolM;l6@uB8J?E?U;nHg;W literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/selection_selectpartialtall.bmp b/setup/data/tools/bitmaps/selection_selectpartialtall.bmp new file mode 100644 index 0000000000000000000000000000000000000000..77b418be076d393d10307188fedfa2c2e2fbbce8 GIT binary patch literal 238 zcmb7;!3}^Q3`L9eE|CS0lgE;7;N5jRI~o(Yf_^AoP5k8dr9aTJ?H8F2V1+$A!H!1M zFP&6Mp&RF~G%FMoOBm!~Nvz^uT8_Tm6R+w>NCFrob`svBZ*yvnpL1nOGf2j)miK)R DkZKh# literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/selection_selecttouching.bmp b/setup/data/tools/bitmaps/selection_selecttouching.bmp new file mode 100644 index 0000000000000000000000000000000000000000..9a60815d2af9dbac34401625f72d0e8de0161108 GIT binary patch literal 238 zcmZ?rea8R+Wk5;;hy{R{ABY(lSb!ux(7k>XM;QJgg ztP#tUq7Rx4{>bYCcyt76|N4bLo`Ih+e*-mCYKXk3I5ubRXJBJYBmcmPnKOAM30~4y zR!;0c%!`JMRWOO5nl;g>bQc{y<>^=@d9GBFa+ZvM&&F!Xam2;M)V!odYp%2=U6y(? z2mX{xZ7S~YijxTsvrGc!1n)SDqbTK*D4RrN=9R=LPKm0DR*devB%>>?ZTtgHa-Qtr zqd}S#TnwV$b71ez#V>3&KuVF zLA5cPEuM=L$=e&Avq6-wtRZ&Dt$&F|;+NDJ#?$~I?P-+rY~=D6<;7@}0YB!{XqfNv zhuuIbpF#dFQA^8h`JOiP;IsF#h7&+|kG-oH$ zo;Fr;45j}Eq>|%iHWl9B0W0MA?b<$mlw=^Gp~~|_jZ^*|#4>D_ylg67)TU&r!5^TK z5MHj5jUyZKBw;lu@~kR(a|YM7mg5fo(B@}&>yO2CY^fe)z z)Ay|pN6s!J4-tJ$2%)<=3rUB-cjDbrBZPB$hnI83l^k0Z%J@^HbbiCbJQ-}s zr7KbtDir&9+MORMC^WClidXW5{A&f;5bpRSlrqBwa%W%RGQ@cypLc1_e^nFJxH9&m z47r(?m2ii>cBljF-AklC0kNd^R`u|tVGV{cyr?Wkobp1rqobr0;kvso^jK`Y@BG(1 zx21-xS+1^d#~Sl>cwvzpKAq-LZ1ntBrIcynA4P@W!5t;4j1E6#Byh_eh(C%^T=ns& zivC_9<=gz#mOGFWq$^3LUjCA)r3IP6<*&i&v3|FIm2k%#pNy_1nfCXOdAxQ1h8MzU z%m$~(NQH4G+##i_)FhTe9Cy4a`tiFseChmzq;Kz!LpZ1R?ftQ{3rXMJABS*G@7w!h pXBU#by+01&oZh$h$IdP!eS3c#!a2Qf?~k2bNc#5vID~V0q91?gkzD`) literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/show_entities.bmp b/setup/data/tools/bitmaps/show_entities.bmp new file mode 100644 index 0000000000000000000000000000000000000000..9188bee6767d6f7765250d80d6280a3f34c5d6b9 GIT binary patch literal 238 zcmZ?rea8R+Wk5;;hy{R{ABY(lSb!vM@Sov7Q1U-RLqh|@|NkI1@jws^1dI{13lIn} n2#P2vDY*beos^VRR0IV@fTB(y8YoOO?Fdm1G6tj{$b|y{jPDqp literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/splash.bmp b/setup/data/tools/bitmaps/splash.bmp new file mode 100644 index 0000000000000000000000000000000000000000..f9e7cc1dcdc0bda5f06e3fa07a5bda781ab8fb9c GIT binary patch literal 299934 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 z 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ℑrExG12tvjfEti{pS)=J(H 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%0AKg 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 literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/texture_browser.bmp b/setup/data/tools/bitmaps/texture_browser.bmp new file mode 100644 index 0000000000000000000000000000000000000000..40c0c2c5036bdde2d526678c9653e57f760cf27f GIT binary patch literal 776 zcmaKq&x_hn5XaN@Qs|+jr~U~&izaR~aie)lD#kV|*{W>}#9)53ahpUiMKo*#5hQlm zO1F!aO<4cP z#IWa2_OUyFWax&~wp`c!?0JL!U^pC($Kx=ZUR{0tc6~jc&!Z^1yIZbStMz)l*=*Ei zQ#bVX&U9UCwOXe6p?%(V9k<)<_Ika6?*~B;hT-jP1QNL+t7_9Q3@8ePLMoLa(gaD8 z*=&}kY37)LL&PFUlBVGly3}g*hTKq9_4l8@hraOCLq`Y&!EpI&DT-pfUYBM05Qh2? zh8nu~zW+0d1mVruR`M>MXBY-{`LS4(N+m%MMDZ-1&*gHS=Y7Aq;W&=pN>1Z>ilRE5 z&TKYgS(ban@jQQT;Cpg1ld0HmR#{jX;B?AhG0MphpViM}i{bnLT+;Ab<>$tg5@*2pjYdwjjU zH7w$YIUlxc#-~@pTa!1}{61bm@h=GJW}UQL6W!^LW|2DXTO(&L?)KDSL-H>|L`E+N?emqy&@r7K$ z3wQ=Q8qkkXN}-C%Ia95b(2ke?Cc42H#P{0Nrl5|zjcmQ@6l1iYI*;%OxOoKg(0tGZ hUZ5!obIU)>EJ5$dI%dq57lNQZomg<)zn$Fh><4tzA4mWI literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/undo.bmp b/setup/data/tools/bitmaps/undo.bmp new file mode 100644 index 0000000000000000000000000000000000000000..44edbbcde92a316dd6a82abce945aecdddaa6d0e GIT binary patch literal 776 zcmc(d-3mcL6oo(BC>P|)BX|N2;NB}a{)G@lQA&9uk7NGqW?Ymku zr`xMm@T()<7Bxh5QHho~+{@6c1^I%Q!~K5`aYU$?!9*@B>_yf80=4;`uNF;q?i9!0a$<>;n5CKRvQMKPu=a&4=?Ve~OLc U!z~;5B>&#`{7mIr{)ayaPj)cA-T(jq literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/view_cameratoggle.bmp b/setup/data/tools/bitmaps/view_cameratoggle.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4ec79ed636b497243c378119e7dea6e25581f807 GIT binary patch literal 238 zcmaitF%H5o3`K)fX4qIOvoKbXxBxQp#8TwmfLwsIUURgZh%1Eec1HZ@|8GB8%I)z9 zn-RJYX)F2yEyypy literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/view_cameraupdate.bmp b/setup/data/tools/bitmaps/view_cameraupdate.bmp new file mode 100644 index 0000000000000000000000000000000000000000..dbb11956b0ea7953699d52f0438b8c64efe6bd9b GIT binary patch literal 238 zcmZ?rea8R+Wk5;;hy{R{ABY(lSb!vMaNxiJhW|j=(9pop095w>|9=nx6d??hKtRC= zOu=Yn17l;508lp*SOO%k8U$9S%pMx7qyS{Hr-g##R9sxsg28lo7)U_HrMw(Oo4A*k XmjY>bAngL8OUlbZwul!42_OIf#dIZp literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/view_change.bmp b/setup/data/tools/bitmaps/view_change.bmp new file mode 100644 index 0000000000000000000000000000000000000000..31b609e5c32416e7540f43e2eb7c96462dac7b4e GIT binary patch literal 126 zcmZ?rtzv)xJ0PV2!~#If55$Z>2m&@he1s2z!2&?yKM*#+I6#{H00RRPkOu4M0P;@& bIUEcOf+7qITuKZKEFcUN;sRnpAcg?|fI$eZ literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/view_clipper.bmp b/setup/data/tools/bitmaps/view_clipper.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4ef205669234b733b1b14bcf4ea91de3398a2d2d GIT binary patch literal 238 zcmaiu!3}^g3+oh`Rwx(qLaKz7*r}aJ-H+Q|oXAEm^h_t4;4!*& vyNLQY{&&5tT5?}X49P9a;6;h>q+a|9=nx6d??h-~ggl zNkI?<1e73LMkPjr(ojP|Ak9#g8VI6YT*6WfL9}~mY9N>{F9p)fF6HIrK$@kbyu8Gb M0ji$~Zaf+T0P!LqW&i*H literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/view_entity.bmp b/setup/data/tools/bitmaps/view_entity.bmp new file mode 100644 index 0000000000000000000000000000000000000000..851ec2c3dbdeb7b938433d4bf21e5f671f257d61 GIT binary patch literal 238 zcmZ?rea8R+Wk5;;hy{R{ABY(lSb!vM@Sov7LqkIY!~g&PaVx?hBPgT<0*ZoQ3P>v( f1A!3|&0xU5fXrtGnuN>;at-kDq2@u%7lZ)-DDfB( literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/white.bmp b/setup/data/tools/bitmaps/white.bmp new file mode 100644 index 0000000000000000000000000000000000000000..cbf038f715159087594be490b88467f33ea4396b GIT binary patch literal 248 ocmZ?r{lNeMW literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/window1.bmp b/setup/data/tools/bitmaps/window1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..69444ae99a444f3713b0fdff049298f584bf1e01 GIT binary patch literal 572 zcmb7=F%E+;3`Gs1QzxqIl{$4qV(pSB6B2jgXgO0akbc_?dup literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/window2.bmp b/setup/data/tools/bitmaps/window2.bmp new file mode 100644 index 0000000000000000000000000000000000000000..dd890d24b9648d25ebcf9dc0fd0af5fa84c67708 GIT binary patch literal 568 zcmaivF%rTs3>8C3zbA@FX!%rfQruyv90aRygNe>?>cF z^VBX!$iKn<8qA9%pQku7<3-4rhscL{X8MQG(E1~nMl9e)^ZF-eR~TH$DJt4z_@^mFbPeC$Quu%<>U literal 0 HcmV?d00001 diff --git a/setup/data/tools/bitmaps/window3.bmp b/setup/data/tools/bitmaps/window3.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c65f2dbbd06c061a7f967138072bdc8309d9e746 GIT binary patch literal 572 zcma)%y$ZuH41|@CPMuP`7dmweWbI<(snB=IGxr73yOTnGD5V1B@>x1tDz8UtiYLBz z~i)z;e_ci*)$;m^E}7dDqU3+muleL_mxZ~*Hpz+aSn!xQD8qC$$LMV z5G7arppIMUPg1bXiwpfKtaUeT{Ith=%ljK{Z@b_;jN%gW{>i>??xFJgzA +Please see the file "AUTHORS" for a list of contributors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +/// ============================================================================ + +#include "utils.cg" + +struct cg_vertex2fragment +{ + float4 position : TEXCOORD0; + float4 tex_diffuse_bump : TEXCOORD1; + float4 tex_specular : TEXCOORD2; + float4 tex_atten_xy_z : TEXCOORD3; + + float3 tangent : TEXCOORD4; + float3 binormal : TEXCOORD5; + float3 normal : TEXCOORD6; +}; + +struct cg_fragment2final +{ + float4 color : COLOR; +}; + + +cg_fragment2final main(cg_vertex2fragment IN, + uniform sampler2D diffusemap, + uniform sampler2D bumpmap, + uniform sampler2D specularmap, + uniform sampler2D attenuationmap_xy, + uniform sampler2D attenuationmap_z, + uniform float3 view_origin, + uniform float3 light_origin, + uniform float3 light_color, + uniform float bump_scale, + uniform float specular_exponent) +{ + cg_fragment2final OUT; + + // construct object-space-to-tangent-space 3x3 matrix + float3x3 rotation = float3x3(IN.tangent, IN.binormal, IN.normal); + + // compute view direction in tangent space + float3 V = normalize(mul(rotation, view_origin - IN.position.xyz)); + + // compute light direction in tangent space + float3 L = normalize(mul(rotation, (light_origin - IN.position.xyz))); + + // compute half angle in tangent space + float3 H = normalize(L + V); + + // compute normal in tangent space from bumpmap + float3 T = CG_Expand(tex2D(bumpmap, IN.tex_diffuse_bump.zw).xyz); + T.z *= bump_scale; + float3 N = normalize(T); + + // compute the diffuse term + float4 diffuse = tex2D(diffusemap, IN.tex_diffuse_bump.xy); + diffuse.rgb *= light_color * saturate(dot(N, L)); + + // compute the specular term + float3 specular = tex2D(specularmap, IN.tex_specular.xy).rgb * light_color * pow(saturate(dot(N, H)), specular_exponent); + + // compute attenuation + float3 attenuation_xy = tex2Dproj(attenuationmap_xy, float3(IN.tex_atten_xy_z.x, IN.tex_atten_xy_z.y, IN.tex_atten_xy_z.w)).rgb; + float3 attenuation_z = tex2D(attenuationmap_z, float2(IN.tex_atten_xy_z.z, 0)).rgb; + + // compute final color + OUT.color.rgba = diffuse; + OUT.color.rgb += specular; + OUT.color.rgb *= attenuation_xy; + OUT.color.rgb *= attenuation_z; + + return OUT; +} diff --git a/setup/data/tools/gl/lighting_DBS_XY_Z_arbvp1.cg b/setup/data/tools/gl/lighting_DBS_XY_Z_arbvp1.cg new file mode 100644 index 00000000..59a6fc73 --- /dev/null +++ b/setup/data/tools/gl/lighting_DBS_XY_Z_arbvp1.cg @@ -0,0 +1,78 @@ +/// ============================================================================ +/* +Copyright (C) 2004 Robert Beckebans +Please see the file "AUTHORS" for a list of contributors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +/// ============================================================================ + + +struct cg_app2vertex +{ + float4 position : POSITION; + float4 tex0 : ATTR8; + + float3 tangent : ATTR9; + float3 binormal : ATTR10; + float3 normal : ATTR11; +}; + +struct cg_vertex2fragment +{ + float4 hposition : POSITION; + + float4 position : TEXCOORD0; + float4 tex_diffuse_bump : TEXCOORD1; + float4 tex_specular : TEXCOORD2; + float4 tex_atten_xy_z : TEXCOORD3; + + float3 tangent : TEXCOORD4; + float3 binormal : TEXCOORD5; + float3 normal : TEXCOORD6; +}; + + + +cg_vertex2fragment main(cg_app2vertex IN) +{ + cg_vertex2fragment OUT; + + // transform vertex position into homogenous clip-space + OUT.hposition = mul(glstate.matrix.mvp, IN.position); + + // assign position in object space + OUT.position = IN.position; + + // transform texcoords + OUT.tex_diffuse_bump.xy = mul(glstate.matrix.texture[0], IN.tex0).xy; + + // transform texcoords + OUT.tex_diffuse_bump.zw = mul(glstate.matrix.texture[1], IN.tex0).xy; + + // transform texcoords + OUT.tex_specular = mul(glstate.matrix.texture[2], IN.tex0); + + // transform vertex position into light space + OUT.tex_atten_xy_z = mul(glstate.matrix.texture[3], IN.position); + + // assign tangent space vectors + OUT.tangent = IN.tangent; + OUT.binormal = IN.binormal; + OUT.normal = IN.normal; + + return OUT; +} diff --git a/setup/data/tools/gl/lighting_DBS_omni_fp.glp b/setup/data/tools/gl/lighting_DBS_omni_fp.glp new file mode 100644 index 00000000..88dab8d3 --- /dev/null +++ b/setup/data/tools/gl/lighting_DBS_omni_fp.glp @@ -0,0 +1,86 @@ +!!ARBfp1.0 +# cgc version 1.3.0001, build date Aug 4 2004 10:01:10 +# command line args: -profile arbfp1 +# source file: ..\..\setup\data\tools\gl\lighting_DBS_XY_Z_arbfp1.cg +# source file: ..\..\setup\data\tools\gl/utils.cg +#vendor NVIDIA Corporation +#version 1.0.02 +#profile arbfp1 +#program main +#semantic main.diffusemap +#semantic main.bumpmap +#semantic main.specularmap +#semantic main.attenuationmap_xy +#semantic main.attenuationmap_z +#semantic main.view_origin +#semantic main.light_origin +#semantic main.light_color +#semantic main.bump_scale +#semantic main.specular_exponent +#var float4 IN.position : $vin.TEX0 : TEX0 : 0 : 1 +#var float4 IN.tex_diffuse_bump : $vin.TEX1 : TEX1 : 0 : 1 +#var float4 IN.tex_specular : $vin.TEX2 : TEX2 : 0 : 1 +#var float4 IN.tex_atten_xy_z : $vin.TEX3 : TEX3 : 0 : 1 +#var float3 IN.tangent : $vin.TEX4 : TEX4 : 0 : 1 +#var float3 IN.binormal : $vin.TEX5 : TEX5 : 0 : 1 +#var float3 IN.normal : $vin.TEX6 : TEX6 : 0 : 1 +#var sampler2D diffusemap : : texunit 0 : 1 : 1 +#var sampler2D bumpmap : : texunit 1 : 2 : 1 +#var sampler2D specularmap : : texunit 2 : 3 : 1 +#var sampler2D attenuationmap_xy : : texunit 3 : 4 : 1 +#var sampler2D attenuationmap_z : : texunit 4 : 5 : 1 +#var float3 view_origin : : c[4] : 6 : 1 +#var float3 light_origin : : c[2] : 7 : 1 +#var float3 light_color : : c[3] : 8 : 1 +#var float bump_scale : : c[1] : 9 : 1 +#var float specular_exponent : : c[5] : 10 : 1 +#var float4 main.color : $vout.COL : COL : -1 : 1 +#const c[0] = 0.5 2 0 +PARAM c[6] = { { 0.5, 2, 0 }, + program.local[1..5] }; +TEMP R0; +TEMP R1; +TEMP R2; +ADD R1.xyz, -fragment.texcoord[0], c[2]; +DP3 R0.z, fragment.texcoord[6], R1; +DP3 R0.x, fragment.texcoord[4], R1; +DP3 R0.y, fragment.texcoord[5], R1; +ADD R1.xyz, -fragment.texcoord[0], c[4]; +DP3 R0.w, R0, R0; +DP3 R2.z, fragment.texcoord[6], R1; +DP3 R2.x, fragment.texcoord[4], R1; +DP3 R2.y, fragment.texcoord[5], R1; +RSQ R0.w, R0.w; +MUL R1.xyz, R0.w, R0; +DP3 R1.w, R2, R2; +RSQ R0.w, R1.w; +MUL R2.xyz, R0.w, R2; +ADD R2.xyz, R1, R2; +DP3 R0.w, R2, R2; +RSQ R2.w, R0.w; +TEX R0.xyz, fragment.texcoord[1].zwzw, texture[1], 2D; +ADD R0.xyz, R0, -c[0].x; +MUL R0.xyz, R0, c[0].y; +MUL R0.z, R0, c[1].x; +DP3 R1.w, R0, R0; +RSQ R0.w, R1.w; +MUL R0.xyz, R0.w, R0; +MUL R2.xyz, R2.w, R2; +DP3_SAT R0.w, R0, R2; +DP3_SAT R0.x, R0, R1; +TEX R2.xyz, fragment.texcoord[2], texture[2], 2D; +MUL R1.xyz, R2, c[3]; +POW R0.w, R0.w, c[5].x; +MUL R2.xyz, R1, R0.w; +MUL R1.xyz, R0.x, c[3]; +TEX R0, fragment.texcoord[1], texture[0], 2D; +MAD R2.xyz, R0, R1, R2; +TXP R0.xyz, fragment.texcoord[3], texture[3], 2D; +MOV R1.y, c[0].z; +MOV R1.x, fragment.texcoord[3].z; +TEX R1.xyz, R1, texture[4], 2D; +MUL R0.xyz, R2, R0; +MUL result.color.xyz, R0, R1; +MOV result.color.w, R0; +END +# 41 instructions, 3 R-regs diff --git a/setup/data/tools/gl/lighting_DBS_omni_fp.glsl b/setup/data/tools/gl/lighting_DBS_omni_fp.glsl new file mode 100644 index 00000000..7f80fea2 --- /dev/null +++ b/setup/data/tools/gl/lighting_DBS_omni_fp.glsl @@ -0,0 +1,73 @@ +/// ============================================================================ +/* +Copyright (C) 2004 Robert Beckebans +Please see the file "CONTRIBUTORS" for a list of contributors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +/// ============================================================================ + +uniform sampler2D u_diffusemap; +uniform sampler2D u_bumpmap; +uniform sampler2D u_specularmap; +uniform sampler2D u_attenuationmap_xy; +uniform sampler2D u_attenuationmap_z; +uniform vec3 u_view_origin; +uniform vec3 u_light_origin; +uniform vec3 u_light_color; +uniform float u_bump_scale; +uniform float u_specular_exponent; + +varying vec3 var_vertex; +varying vec4 var_tex_diffuse_bump; +varying vec2 var_tex_specular; +varying vec4 var_tex_atten_xy_z; +varying mat3 var_mat_os2ts; + +void main() +{ + // compute view direction in tangent space + vec3 V = normalize(var_mat_os2ts * (u_view_origin - var_vertex)); + + // compute light direction in tangent space + vec3 L = normalize(var_mat_os2ts * (u_light_origin - var_vertex)); + + // compute half angle in tangent space + vec3 H = normalize(L + V); + + // compute normal in tangent space from bumpmap + vec3 N = 2.0 * (texture2D(u_bumpmap, var_tex_diffuse_bump.pq).xyz - 0.5); + N.z *= u_bump_scale; + N = normalize(N); + + // compute the diffuse term + vec4 diffuse = texture2D(u_diffusemap, var_tex_diffuse_bump.st); + diffuse.rgb *= u_light_color * clamp(dot(N, L), 0.0, 1.0); + + // compute the specular term + vec3 specular = texture2D(u_specularmap, var_tex_specular).rgb * u_light_color * pow(clamp(dot(N, H), 0.0, 1.0), u_specular_exponent); + + // compute attenuation + vec3 attenuation_xy = texture2DProj(u_attenuationmap_xy, vec3(var_tex_atten_xy_z.x, var_tex_atten_xy_z.y, var_tex_atten_xy_z.w)).rgb; + vec3 attenuation_z = texture2D(u_attenuationmap_z, vec2(var_tex_atten_xy_z.z, 0)).rgb; + + // compute final color + gl_FragColor.rgba = diffuse; + gl_FragColor.rgb += specular; + gl_FragColor.rgb *= attenuation_xy; + gl_FragColor.rgb *= attenuation_z; +} + diff --git a/setup/data/tools/gl/lighting_DBS_omni_vp.glp b/setup/data/tools/gl/lighting_DBS_omni_vp.glp new file mode 100644 index 00000000..b4472d70 --- /dev/null +++ b/setup/data/tools/gl/lighting_DBS_omni_vp.glp @@ -0,0 +1,410 @@ +!!ARBvp1.0 +# cgc version 1.3.0001, build date Aug 4 2004 10:01:10 +# command line args: -profile arbvp1 +# source file: ..\..\setup\data\tools\gl\lighting_DBS_XY_Z_arbvp1.cg +#vendor NVIDIA Corporation +#version 1.0.02 +#profile arbvp1 +#program main +#semantic glstate : STATE +#var float4 glstate.material.ambient : STATE.MATERIAL.AMBIENT : : -1 : 0 +#var float4 glstate.material.diffuse : STATE.MATERIAL.DIFFUSE : : -1 : 0 +#var float4 glstate.material.specular : STATE.MATERIAL.SPECULAR : : -1 : 0 +#var float4 glstate.material.emission : STATE.MATERIAL.EMISSION : : -1 : 0 +#var float4 glstate.material.shininess : STATE.MATERIAL.SHININESS : : -1 : 0 +#var float4 glstate.material.front.ambient : STATE.MATERIAL.FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.material.front.diffuse : STATE.MATERIAL.FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.material.front.specular : STATE.MATERIAL.FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.material.front.emission : STATE.MATERIAL.FRONT.EMISSION : : -1 : 0 +#var float4 glstate.material.front.shininess : STATE.MATERIAL.FRONT.SHININESS : : -1 : 0 +#var float4 glstate.material.back.ambient : STATE.MATERIAL.BACK.AMBIENT : : -1 : 0 +#var float4 glstate.material.back.diffuse : STATE.MATERIAL.BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.material.back.specular : STATE.MATERIAL.BACK.SPECULAR : : -1 : 0 +#var float4 glstate.material.back.emission : STATE.MATERIAL.BACK.EMISSION : : -1 : 0 +#var float4 glstate.material.back.shininess : STATE.MATERIAL.BACK.SHININESS : : -1 : 0 +#var float4 glstate.light[0].ambient : STATE.LIGHT[0].AMBIENT : : -1 : 0 +#var float4 glstate.light[0].diffuse : STATE.LIGHT[0].DIFFUSE : : -1 : 0 +#var float4 glstate.light[0].specular : STATE.LIGHT[0].SPECULAR : : -1 : 0 +#var float4 glstate.light[0].position : STATE.LIGHT[0].POSITION : : -1 : 0 +#var float4 glstate.light[0].attenuation : STATE.LIGHT[0].ATTENUATION : : -1 : 0 +#var float4 glstate.light[0].spot.direction : STATE.LIGHT[0].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[0].half : STATE.LIGHT[0].HALF : : -1 : 0 +#var float4 glstate.light[1].ambient : STATE.LIGHT[1].AMBIENT : : -1 : 0 +#var float4 glstate.light[1].diffuse : STATE.LIGHT[1].DIFFUSE : : -1 : 0 +#var float4 glstate.light[1].specular : STATE.LIGHT[1].SPECULAR : : -1 : 0 +#var float4 glstate.light[1].position : STATE.LIGHT[1].POSITION : : -1 : 0 +#var float4 glstate.light[1].attenuation : STATE.LIGHT[1].ATTENUATION : : -1 : 0 +#var float4 glstate.light[1].spot.direction : STATE.LIGHT[1].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[1].half : STATE.LIGHT[1].HALF : : -1 : 0 +#var float4 glstate.light[2].ambient : STATE.LIGHT[2].AMBIENT : : -1 : 0 +#var float4 glstate.light[2].diffuse : STATE.LIGHT[2].DIFFUSE : : -1 : 0 +#var float4 glstate.light[2].specular : STATE.LIGHT[2].SPECULAR : : -1 : 0 +#var float4 glstate.light[2].position : STATE.LIGHT[2].POSITION : : -1 : 0 +#var float4 glstate.light[2].attenuation : STATE.LIGHT[2].ATTENUATION : : -1 : 0 +#var float4 glstate.light[2].spot.direction : STATE.LIGHT[2].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[2].half : STATE.LIGHT[2].HALF : : -1 : 0 +#var float4 glstate.light[3].ambient : STATE.LIGHT[3].AMBIENT : : -1 : 0 +#var float4 glstate.light[3].diffuse : STATE.LIGHT[3].DIFFUSE : : -1 : 0 +#var float4 glstate.light[3].specular : STATE.LIGHT[3].SPECULAR : : -1 : 0 +#var float4 glstate.light[3].position : STATE.LIGHT[3].POSITION : : -1 : 0 +#var float4 glstate.light[3].attenuation : STATE.LIGHT[3].ATTENUATION : : -1 : 0 +#var float4 glstate.light[3].spot.direction : STATE.LIGHT[3].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[3].half : STATE.LIGHT[3].HALF : : -1 : 0 +#var float4 glstate.light[4].ambient : STATE.LIGHT[4].AMBIENT : : -1 : 0 +#var float4 glstate.light[4].diffuse : STATE.LIGHT[4].DIFFUSE : : -1 : 0 +#var float4 glstate.light[4].specular : STATE.LIGHT[4].SPECULAR : : -1 : 0 +#var float4 glstate.light[4].position : STATE.LIGHT[4].POSITION : : -1 : 0 +#var float4 glstate.light[4].attenuation : STATE.LIGHT[4].ATTENUATION : : -1 : 0 +#var float4 glstate.light[4].spot.direction : STATE.LIGHT[4].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[4].half : STATE.LIGHT[4].HALF : : -1 : 0 +#var float4 glstate.light[5].ambient : STATE.LIGHT[5].AMBIENT : : -1 : 0 +#var float4 glstate.light[5].diffuse : STATE.LIGHT[5].DIFFUSE : : -1 : 0 +#var float4 glstate.light[5].specular : STATE.LIGHT[5].SPECULAR : : -1 : 0 +#var float4 glstate.light[5].position : STATE.LIGHT[5].POSITION : : -1 : 0 +#var float4 glstate.light[5].attenuation : STATE.LIGHT[5].ATTENUATION : : -1 : 0 +#var float4 glstate.light[5].spot.direction : STATE.LIGHT[5].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[5].half : STATE.LIGHT[5].HALF : : -1 : 0 +#var float4 glstate.light[6].ambient : STATE.LIGHT[6].AMBIENT : : -1 : 0 +#var float4 glstate.light[6].diffuse : STATE.LIGHT[6].DIFFUSE : : -1 : 0 +#var float4 glstate.light[6].specular : STATE.LIGHT[6].SPECULAR : : -1 : 0 +#var float4 glstate.light[6].position : STATE.LIGHT[6].POSITION : : -1 : 0 +#var float4 glstate.light[6].attenuation : STATE.LIGHT[6].ATTENUATION : : -1 : 0 +#var float4 glstate.light[6].spot.direction : STATE.LIGHT[6].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[6].half : STATE.LIGHT[6].HALF : : -1 : 0 +#var float4 glstate.light[7].ambient : STATE.LIGHT[7].AMBIENT : : -1 : 0 +#var float4 glstate.light[7].diffuse : STATE.LIGHT[7].DIFFUSE : : -1 : 0 +#var float4 glstate.light[7].specular : STATE.LIGHT[7].SPECULAR : : -1 : 0 +#var float4 glstate.light[7].position : STATE.LIGHT[7].POSITION : : -1 : 0 +#var float4 glstate.light[7].attenuation : STATE.LIGHT[7].ATTENUATION : : -1 : 0 +#var float4 glstate.light[7].spot.direction : STATE.LIGHT[7].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[7].half : STATE.LIGHT[7].HALF : : -1 : 0 +#var float4 glstate.lightmodel.ambient : STATE.LIGHTMODEL.AMBIENT : : -1 : 0 +#var float4 glstate.lightmodel.scenecolor : STATE.LIGHTMODEL.SCENECOLOR : : -1 : 0 +#var float4 glstate.lightmodel.front.scenecolor : STATE.LIGHTMODEL.FRONT.SCENECOLOR : : -1 : 0 +#var float4 glstate.lightmodel.back.scenecolor : STATE.LIGHTMODEL.BACK.SCENECOLOR : : -1 : 0 +#var float4 glstate.lightprod[0].ambient : STATE.LIGHTPROD[0].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[0].diffuse : STATE.LIGHTPROD[0].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[0].specular : STATE.LIGHTPROD[0].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[0].front.ambient : STATE.LIGHTPROD[0].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[0].front.diffuse : STATE.LIGHTPROD[0].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[0].front.specular : STATE.LIGHTPROD[0].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[0].back.ambient : STATE.LIGHTPROD[0].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[0].back.diffuse : STATE.LIGHTPROD[0].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[0].back.specular : STATE.LIGHTPROD[0].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[1].ambient : STATE.LIGHTPROD[1].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[1].diffuse : STATE.LIGHTPROD[1].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[1].specular : STATE.LIGHTPROD[1].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[1].front.ambient : STATE.LIGHTPROD[1].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[1].front.diffuse : STATE.LIGHTPROD[1].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[1].front.specular : STATE.LIGHTPROD[1].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[1].back.ambient : STATE.LIGHTPROD[1].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[1].back.diffuse : STATE.LIGHTPROD[1].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[1].back.specular : STATE.LIGHTPROD[1].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[2].ambient : STATE.LIGHTPROD[2].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[2].diffuse : STATE.LIGHTPROD[2].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[2].specular : STATE.LIGHTPROD[2].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[2].front.ambient : STATE.LIGHTPROD[2].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[2].front.diffuse : STATE.LIGHTPROD[2].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[2].front.specular : STATE.LIGHTPROD[2].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[2].back.ambient : STATE.LIGHTPROD[2].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[2].back.diffuse : STATE.LIGHTPROD[2].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[2].back.specular : STATE.LIGHTPROD[2].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[3].ambient : STATE.LIGHTPROD[3].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[3].diffuse : STATE.LIGHTPROD[3].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[3].specular : STATE.LIGHTPROD[3].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[3].front.ambient : STATE.LIGHTPROD[3].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[3].front.diffuse : STATE.LIGHTPROD[3].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[3].front.specular : STATE.LIGHTPROD[3].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[3].back.ambient : STATE.LIGHTPROD[3].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[3].back.diffuse : STATE.LIGHTPROD[3].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[3].back.specular : STATE.LIGHTPROD[3].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[4].ambient : STATE.LIGHTPROD[4].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[4].diffuse : STATE.LIGHTPROD[4].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[4].specular : STATE.LIGHTPROD[4].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[4].front.ambient : STATE.LIGHTPROD[4].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[4].front.diffuse : STATE.LIGHTPROD[4].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[4].front.specular : STATE.LIGHTPROD[4].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[4].back.ambient : STATE.LIGHTPROD[4].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[4].back.diffuse : STATE.LIGHTPROD[4].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[4].back.specular : STATE.LIGHTPROD[4].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[5].ambient : STATE.LIGHTPROD[5].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[5].diffuse : STATE.LIGHTPROD[5].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[5].specular : STATE.LIGHTPROD[5].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[5].front.ambient : STATE.LIGHTPROD[5].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[5].front.diffuse : STATE.LIGHTPROD[5].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[5].front.specular : STATE.LIGHTPROD[5].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[5].back.ambient : STATE.LIGHTPROD[5].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[5].back.diffuse : STATE.LIGHTPROD[5].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[5].back.specular : STATE.LIGHTPROD[5].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[6].ambient : STATE.LIGHTPROD[6].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[6].diffuse : STATE.LIGHTPROD[6].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[6].specular : STATE.LIGHTPROD[6].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[6].front.ambient : STATE.LIGHTPROD[6].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[6].front.diffuse : STATE.LIGHTPROD[6].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[6].front.specular : STATE.LIGHTPROD[6].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[6].back.ambient : STATE.LIGHTPROD[6].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[6].back.diffuse : STATE.LIGHTPROD[6].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[6].back.specular : STATE.LIGHTPROD[6].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[7].ambient : STATE.LIGHTPROD[7].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[7].diffuse : STATE.LIGHTPROD[7].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[7].specular : STATE.LIGHTPROD[7].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[7].front.ambient : STATE.LIGHTPROD[7].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[7].front.diffuse : STATE.LIGHTPROD[7].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[7].front.specular : STATE.LIGHTPROD[7].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[7].back.ambient : STATE.LIGHTPROD[7].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[7].back.diffuse : STATE.LIGHTPROD[7].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[7].back.specular : STATE.LIGHTPROD[7].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.texgen[0].eye.s : STATE.TEXGEN[0].EYE.S : : -1 : 0 +#var float4 glstate.texgen[0].eye.t : STATE.TEXGEN[0].EYE.T : : -1 : 0 +#var float4 glstate.texgen[0].eye.r : STATE.TEXGEN[0].EYE.R : : -1 : 0 +#var float4 glstate.texgen[0].eye.q : STATE.TEXGEN[0].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[0].object.s : STATE.TEXGEN[0].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[0].object.t : STATE.TEXGEN[0].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[0].object.r : STATE.TEXGEN[0].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[0].object.q : STATE.TEXGEN[0].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[1].eye.s : STATE.TEXGEN[1].EYE.S : : -1 : 0 +#var float4 glstate.texgen[1].eye.t : STATE.TEXGEN[1].EYE.T : : -1 : 0 +#var float4 glstate.texgen[1].eye.r : STATE.TEXGEN[1].EYE.R : : -1 : 0 +#var float4 glstate.texgen[1].eye.q : STATE.TEXGEN[1].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[1].object.s : STATE.TEXGEN[1].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[1].object.t : STATE.TEXGEN[1].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[1].object.r : STATE.TEXGEN[1].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[1].object.q : STATE.TEXGEN[1].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[2].eye.s : STATE.TEXGEN[2].EYE.S : : -1 : 0 +#var float4 glstate.texgen[2].eye.t : STATE.TEXGEN[2].EYE.T : : -1 : 0 +#var float4 glstate.texgen[2].eye.r : STATE.TEXGEN[2].EYE.R : : -1 : 0 +#var float4 glstate.texgen[2].eye.q : STATE.TEXGEN[2].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[2].object.s : STATE.TEXGEN[2].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[2].object.t : STATE.TEXGEN[2].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[2].object.r : STATE.TEXGEN[2].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[2].object.q : STATE.TEXGEN[2].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[3].eye.s : STATE.TEXGEN[3].EYE.S : : -1 : 0 +#var float4 glstate.texgen[3].eye.t : STATE.TEXGEN[3].EYE.T : : -1 : 0 +#var float4 glstate.texgen[3].eye.r : STATE.TEXGEN[3].EYE.R : : -1 : 0 +#var float4 glstate.texgen[3].eye.q : STATE.TEXGEN[3].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[3].object.s : STATE.TEXGEN[3].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[3].object.t : STATE.TEXGEN[3].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[3].object.r : STATE.TEXGEN[3].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[3].object.q : STATE.TEXGEN[3].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[4].eye.s : STATE.TEXGEN[4].EYE.S : : -1 : 0 +#var float4 glstate.texgen[4].eye.t : STATE.TEXGEN[4].EYE.T : : -1 : 0 +#var float4 glstate.texgen[4].eye.r : STATE.TEXGEN[4].EYE.R : : -1 : 0 +#var float4 glstate.texgen[4].eye.q : STATE.TEXGEN[4].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[4].object.s : STATE.TEXGEN[4].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[4].object.t : STATE.TEXGEN[4].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[4].object.r : STATE.TEXGEN[4].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[4].object.q : STATE.TEXGEN[4].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[5].eye.s : STATE.TEXGEN[5].EYE.S : : -1 : 0 +#var float4 glstate.texgen[5].eye.t : STATE.TEXGEN[5].EYE.T : : -1 : 0 +#var float4 glstate.texgen[5].eye.r : STATE.TEXGEN[5].EYE.R : : -1 : 0 +#var float4 glstate.texgen[5].eye.q : STATE.TEXGEN[5].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[5].object.s : STATE.TEXGEN[5].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[5].object.t : STATE.TEXGEN[5].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[5].object.r : STATE.TEXGEN[5].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[5].object.q : STATE.TEXGEN[5].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[6].eye.s : STATE.TEXGEN[6].EYE.S : : -1 : 0 +#var float4 glstate.texgen[6].eye.t : STATE.TEXGEN[6].EYE.T : : -1 : 0 +#var float4 glstate.texgen[6].eye.r : STATE.TEXGEN[6].EYE.R : : -1 : 0 +#var float4 glstate.texgen[6].eye.q : STATE.TEXGEN[6].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[6].object.s : STATE.TEXGEN[6].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[6].object.t : STATE.TEXGEN[6].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[6].object.r : STATE.TEXGEN[6].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[6].object.q : STATE.TEXGEN[6].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[7].eye.s : STATE.TEXGEN[7].EYE.S : : -1 : 0 +#var float4 glstate.texgen[7].eye.t : STATE.TEXGEN[7].EYE.T : : -1 : 0 +#var float4 glstate.texgen[7].eye.r : STATE.TEXGEN[7].EYE.R : : -1 : 0 +#var float4 glstate.texgen[7].eye.q : STATE.TEXGEN[7].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[7].object.s : STATE.TEXGEN[7].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[7].object.t : STATE.TEXGEN[7].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[7].object.r : STATE.TEXGEN[7].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[7].object.q : STATE.TEXGEN[7].OBJECT.Q : : -1 : 0 +#var float4 glstate.fog.color : STATE.FOG.COLOR : : -1 : 0 +#var float4 glstate.fog.params : STATE.FOG.PARAMS : : -1 : 0 +#var float4 glstate.clip[0].plane : STATE.CLIP[0].PLANE : : -1 : 0 +#var float4 glstate.clip[1].plane : STATE.CLIP[1].PLANE : : -1 : 0 +#var float4 glstate.clip[2].plane : STATE.CLIP[2].PLANE : : -1 : 0 +#var float4 glstate.clip[3].plane : STATE.CLIP[3].PLANE : : -1 : 0 +#var float4 glstate.clip[4].plane : STATE.CLIP[4].PLANE : : -1 : 0 +#var float4 glstate.clip[5].plane : STATE.CLIP[5].PLANE : : -1 : 0 +#var float4 glstate.clip[6].plane : STATE.CLIP[6].PLANE : : -1 : 0 +#var float4 glstate.clip[7].plane : STATE.CLIP[7].PLANE : : -1 : 0 +#var float glstate.point.size : STATE.POINT.SIZE : : -1 : 0 +#var float glstate.point.attenuation : STATE.POINT.ATTENUATION : : -1 : 0 +#var float4x4 glstate.matrix.modelview[0] : STATE.MATRIX.MODELVIEW[0] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[1] : STATE.MATRIX.MODELVIEW[1] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[2] : STATE.MATRIX.MODELVIEW[2] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[3] : STATE.MATRIX.MODELVIEW[3] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[4] : STATE.MATRIX.MODELVIEW[4] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[5] : STATE.MATRIX.MODELVIEW[5] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[6] : STATE.MATRIX.MODELVIEW[6] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[7] : STATE.MATRIX.MODELVIEW[7] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.projection : STATE.MATRIX.PROJECTION : , 4 : -1 : 0 +#var float4x4 glstate.matrix.mvp : STATE.MATRIX.MVP : c[0], 4 : -1 : 1 +#var float4x4 glstate.matrix.texture[0] : STATE.MATRIX.TEXTURE[0] : c[4], 4 : -1 : 1 +#var float4x4 glstate.matrix.texture[1] : STATE.MATRIX.TEXTURE[1] : c[8], 4 : -1 : 1 +#var float4x4 glstate.matrix.texture[2] : STATE.MATRIX.TEXTURE[2] : c[12], 4 : -1 : 1 +#var float4x4 glstate.matrix.texture[3] : STATE.MATRIX.TEXTURE[3] : c[16], 4 : -1 : 1 +#var float4x4 glstate.matrix.texture[4] : STATE.MATRIX.TEXTURE[4] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.texture[5] : STATE.MATRIX.TEXTURE[5] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.texture[6] : STATE.MATRIX.TEXTURE[6] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.texture[7] : STATE.MATRIX.TEXTURE[7] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[0] : STATE.MATRIX.PALETTE[0] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[1] : STATE.MATRIX.PALETTE[1] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[2] : STATE.MATRIX.PALETTE[2] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[3] : STATE.MATRIX.PALETTE[3] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[4] : STATE.MATRIX.PALETTE[4] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[5] : STATE.MATRIX.PALETTE[5] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[6] : STATE.MATRIX.PALETTE[6] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[7] : STATE.MATRIX.PALETTE[7] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[0] : STATE.MATRIX.PROGRAM[0] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[1] : STATE.MATRIX.PROGRAM[1] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[2] : STATE.MATRIX.PROGRAM[2] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[3] : STATE.MATRIX.PROGRAM[3] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[4] : STATE.MATRIX.PROGRAM[4] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[5] : STATE.MATRIX.PROGRAM[5] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[6] : STATE.MATRIX.PROGRAM[6] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[7] : STATE.MATRIX.PROGRAM[7] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[0] : STATE.MATRIX.MODELVIEW[0].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[1] : STATE.MATRIX.MODELVIEW[1].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[2] : STATE.MATRIX.MODELVIEW[2].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[3] : STATE.MATRIX.MODELVIEW[3].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[4] : STATE.MATRIX.MODELVIEW[4].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[5] : STATE.MATRIX.MODELVIEW[5].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[6] : STATE.MATRIX.MODELVIEW[6].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[7] : STATE.MATRIX.MODELVIEW[7].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.projection : STATE.MATRIX.PROJECTION.INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.mvp : STATE.MATRIX.MVP.INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[0] : STATE.MATRIX.TEXTURE[0].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[1] : STATE.MATRIX.TEXTURE[1].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[2] : STATE.MATRIX.TEXTURE[2].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[3] : STATE.MATRIX.TEXTURE[3].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[4] : STATE.MATRIX.TEXTURE[4].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[5] : STATE.MATRIX.TEXTURE[5].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[6] : STATE.MATRIX.TEXTURE[6].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[7] : STATE.MATRIX.TEXTURE[7].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[0] : STATE.MATRIX.PALETTE[0].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[1] : STATE.MATRIX.PALETTE[1].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[2] : STATE.MATRIX.PALETTE[2].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[3] : STATE.MATRIX.PALETTE[3].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[4] : STATE.MATRIX.PALETTE[4].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[5] : STATE.MATRIX.PALETTE[5].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[6] : STATE.MATRIX.PALETTE[6].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[7] : STATE.MATRIX.PALETTE[7].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[0] : STATE.MATRIX.PROGRAM[0].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[1] : STATE.MATRIX.PROGRAM[1].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[2] : STATE.MATRIX.PROGRAM[2].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[3] : STATE.MATRIX.PROGRAM[3].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[4] : STATE.MATRIX.PROGRAM[4].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[5] : STATE.MATRIX.PROGRAM[5].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[6] : STATE.MATRIX.PROGRAM[6].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[7] : STATE.MATRIX.PROGRAM[7].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[0] : STATE.MATRIX.MODELVIEW[0].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[1] : STATE.MATRIX.MODELVIEW[1].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[2] : STATE.MATRIX.MODELVIEW[2].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[3] : STATE.MATRIX.MODELVIEW[3].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[4] : STATE.MATRIX.MODELVIEW[4].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[5] : STATE.MATRIX.MODELVIEW[5].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[6] : STATE.MATRIX.MODELVIEW[6].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[7] : STATE.MATRIX.MODELVIEW[7].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.projection : STATE.MATRIX.PROJECTION.TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.mvp : STATE.MATRIX.MVP.TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[0] : STATE.MATRIX.TEXTURE[0].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[1] : STATE.MATRIX.TEXTURE[1].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[2] : STATE.MATRIX.TEXTURE[2].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[3] : STATE.MATRIX.TEXTURE[3].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[4] : STATE.MATRIX.TEXTURE[4].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[5] : STATE.MATRIX.TEXTURE[5].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[6] : STATE.MATRIX.TEXTURE[6].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[7] : STATE.MATRIX.TEXTURE[7].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[0] : STATE.MATRIX.PALETTE[0].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[1] : STATE.MATRIX.PALETTE[1].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[2] : STATE.MATRIX.PALETTE[2].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[3] : STATE.MATRIX.PALETTE[3].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[4] : STATE.MATRIX.PALETTE[4].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[5] : STATE.MATRIX.PALETTE[5].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[6] : STATE.MATRIX.PALETTE[6].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[7] : STATE.MATRIX.PALETTE[7].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[0] : STATE.MATRIX.PROGRAM[0].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[1] : STATE.MATRIX.PROGRAM[1].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[2] : STATE.MATRIX.PROGRAM[2].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[3] : STATE.MATRIX.PROGRAM[3].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[4] : STATE.MATRIX.PROGRAM[4].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[5] : STATE.MATRIX.PROGRAM[5].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[6] : STATE.MATRIX.PROGRAM[6].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[7] : STATE.MATRIX.PROGRAM[7].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[0] : STATE.MATRIX.MODELVIEW[0].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[1] : STATE.MATRIX.MODELVIEW[1].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[2] : STATE.MATRIX.MODELVIEW[2].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[3] : STATE.MATRIX.MODELVIEW[3].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[4] : STATE.MATRIX.MODELVIEW[4].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[5] : STATE.MATRIX.MODELVIEW[5].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[6] : STATE.MATRIX.MODELVIEW[6].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[7] : STATE.MATRIX.MODELVIEW[7].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.projection : STATE.MATRIX.PROJECTION.INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.mvp : STATE.MATRIX.MVP.INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[0] : STATE.MATRIX.TEXTURE[0].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[1] : STATE.MATRIX.TEXTURE[1].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[2] : STATE.MATRIX.TEXTURE[2].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[3] : STATE.MATRIX.TEXTURE[3].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[4] : STATE.MATRIX.TEXTURE[4].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[5] : STATE.MATRIX.TEXTURE[5].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[6] : STATE.MATRIX.TEXTURE[6].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[7] : STATE.MATRIX.TEXTURE[7].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[0] : STATE.MATRIX.PALETTE[0].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[1] : STATE.MATRIX.PALETTE[1].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[2] : STATE.MATRIX.PALETTE[2].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[3] : STATE.MATRIX.PALETTE[3].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[4] : STATE.MATRIX.PALETTE[4].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[5] : STATE.MATRIX.PALETTE[5].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[6] : STATE.MATRIX.PALETTE[6].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[7] : STATE.MATRIX.PALETTE[7].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[0] : STATE.MATRIX.PROGRAM[0].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[1] : STATE.MATRIX.PROGRAM[1].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[2] : STATE.MATRIX.PROGRAM[2].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[3] : STATE.MATRIX.PROGRAM[3].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[4] : STATE.MATRIX.PROGRAM[4].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[5] : STATE.MATRIX.PROGRAM[5].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[6] : STATE.MATRIX.PROGRAM[6].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[7] : STATE.MATRIX.PROGRAM[7].INVTRANS : , 4 : -1 : 0 +#var float4 IN.position : $vin.POSITION : POSITION : 0 : 1 +#var float4 IN.tex0 : $vin.ATTR8 : ATTR8 : 0 : 1 +#var float3 IN.tangent : $vin.ATTR9 : ATTR9 : 0 : 1 +#var float3 IN.binormal : $vin.ATTR10 : ATTR10 : 0 : 1 +#var float3 IN.normal : $vin.ATTR11 : ATTR11 : 0 : 1 +#var float4 main.hposition : $vout.HPOS : HPOS : -1 : 1 +#var float4 main.position : $vout.TEX0 : TEX0 : -1 : 1 +#var float4 main.tex_diffuse_bump : $vout.TEX1 : TEX1 : -1 : 1 +#var float4 main.tex_specular : $vout.TEX2 : TEX2 : -1 : 1 +#var float4 main.tex_atten_xy_z : $vout.TEX3 : TEX3 : -1 : 1 +#var float3 main.tangent : $vout.TEX4 : TEX4 : -1 : 1 +#var float3 main.binormal : $vout.TEX5 : TEX5 : -1 : 1 +#var float3 main.normal : $vout.TEX6 : TEX6 : -1 : 1 +PARAM c[20] = { state.matrix.mvp, + state.matrix.texture[0], + state.matrix.texture[1], + state.matrix.texture[2], + state.matrix.texture[3] }; +TEMP R0; +DP4 result.position.w, vertex.position, c[3]; +DP4 result.position.z, vertex.position, c[2]; +DP4 result.position.y, vertex.position, c[1]; +DP4 result.position.x, vertex.position, c[0]; +DP4 R0.y, vertex.attrib[8], c[9]; +DP4 R0.x, vertex.attrib[8], c[8]; +MOV result.texcoord[0], vertex.position; +MOV result.texcoord[1].zw, R0.xyxy; +DP4 result.texcoord[1].y, vertex.attrib[8], c[5]; +DP4 result.texcoord[1].x, vertex.attrib[8], c[4]; +DP4 result.texcoord[2].w, vertex.attrib[8], c[15]; +DP4 result.texcoord[2].z, vertex.attrib[8], c[14]; +DP4 result.texcoord[2].y, vertex.attrib[8], c[13]; +DP4 result.texcoord[2].x, vertex.attrib[8], c[12]; +DP4 result.texcoord[3].w, vertex.position, c[19]; +DP4 result.texcoord[3].z, vertex.position, c[18]; +DP4 result.texcoord[3].y, vertex.position, c[17]; +DP4 result.texcoord[3].x, vertex.position, c[16]; +MOV result.texcoord[4].xyz, vertex.attrib[9]; +MOV result.texcoord[5].xyz, vertex.attrib[10]; +MOV result.texcoord[6].xyz, vertex.attrib[11]; +END +# 21 instructions, 1 R-regs diff --git a/setup/data/tools/gl/lighting_DBS_omni_vp.glsl b/setup/data/tools/gl/lighting_DBS_omni_vp.glsl new file mode 100644 index 00000000..6900b5af --- /dev/null +++ b/setup/data/tools/gl/lighting_DBS_omni_vp.glsl @@ -0,0 +1,58 @@ +/// ============================================================================ +/* +Copyright (C) 2004 Robert Beckebans +Please see the file "CONTRIBUTORS" for a list of contributors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +/// ============================================================================ + +attribute vec4 attr_TexCoord0; +attribute vec3 attr_Tangent; +attribute vec3 attr_Binormal; + +varying vec3 var_vertex; +varying vec4 var_tex_diffuse_bump; +varying vec2 var_tex_specular; +varying vec4 var_tex_atten_xy_z; +varying mat3 var_mat_os2ts; + +void main() +{ + // transform vertex position into homogenous clip-space + gl_Position = ftransform(); + + // assign position in object space + var_vertex = gl_Vertex.xyz; + + // transform texcoords into diffusemap texture space + var_tex_diffuse_bump.st = (gl_TextureMatrix[0] * attr_TexCoord0).st; + + // transform texcoords into bumpmap texture space + var_tex_diffuse_bump.pq = (gl_TextureMatrix[1] * attr_TexCoord0).st; + + // transform texcoords into specularmap texture space + var_tex_specular = (gl_TextureMatrix[2] * attr_TexCoord0).st; + + // calc light xy,z attenuation in light space + var_tex_atten_xy_z = gl_TextureMatrix[3] * gl_Vertex; + + + // construct object-space-to-tangent-space 3x3 matrix + var_mat_os2ts = mat3( attr_Tangent.x, attr_Binormal.x, gl_Normal.x, + attr_Tangent.y, attr_Binormal.y, gl_Normal.y, + attr_Tangent.z, attr_Binormal.z, gl_Normal.z ); +} diff --git a/setup/data/tools/gl/utils.cg b/setup/data/tools/gl/utils.cg new file mode 100644 index 00000000..63bfcb6c --- /dev/null +++ b/setup/data/tools/gl/utils.cg @@ -0,0 +1,36 @@ +/// ============================================================================ +/* +Copyright (C) 2004 Robert Beckebans +Please see the file "AUTHORS" for a list of contributors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +/// ============================================================================ + +// fresnel approximation +float fast_fresnel(float3 I, float3 N, float3 fresnel_values) +{ + float power = fresnel_values.x; + float scale = fresnel_values.y; + float bias = fresnel_values.z; + + return bias + pow(1.0 - dot(I, N), power) * scale; +} + +float3 CG_Expand(float3 v) +{ + return (v - 0.5) * 2; // expand a range-compressed vector +} diff --git a/setup/data/tools/gl/zfill_arbfp1.cg b/setup/data/tools/gl/zfill_arbfp1.cg new file mode 100644 index 00000000..c80189c2 --- /dev/null +++ b/setup/data/tools/gl/zfill_arbfp1.cg @@ -0,0 +1,47 @@ +/// ============================================================================ +/* +Copyright (C) 2003 Robert Beckebans +Copyright (C) 2003, 2004 contributors of the XreaL project +Please see the file "AUTHORS" for a list of contributors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +/// ============================================================================ + + +struct cg_vertex2fragment +{ + float4 position : POSITION; + float4 tex0 : TEXCOORD0; +}; + +struct cg_fragment2final +{ + float4 color : COLOR; +}; + + +cg_fragment2final main(in cg_vertex2fragment IN, + uniform sampler2D colormap) +{ + cg_fragment2final OUT; + + OUT.color.w = tex2D(colormap, IN.tex0.xy).a; + + OUT.color.xyz = 0; + + return OUT; +} diff --git a/setup/data/tools/gl/zfill_arbvp1.cg b/setup/data/tools/gl/zfill_arbvp1.cg new file mode 100644 index 00000000..4ffc6e27 --- /dev/null +++ b/setup/data/tools/gl/zfill_arbvp1.cg @@ -0,0 +1,49 @@ +/// ============================================================================ +/* +Copyright (C) 2003 Robert Beckebans +Copyright (C) 2003, 2004 contributors of the XreaL project +Please see the file "AUTHORS" for a list of contributors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +/// ============================================================================ + + +struct cg_app2vertex +{ + float4 position : ATTR0; + float4 texcoord0 : ATTR8; +}; + +struct cg_vertex2fragment +{ + float4 position : POSITION; + float4 tex0 : TEXCOORD0; +}; + + +cg_vertex2fragment main(cg_app2vertex IN) +{ + cg_vertex2fragment OUT; + + // transform vertex position into homogenous clip-space + OUT.position = mul(glstate.matrix.mvp, IN.position); + + // transform texcoords into 1st texture space + OUT.tex0 = mul(glstate.matrix.texture[0], IN.texcoord0); + + return OUT; +} diff --git a/setup/data/tools/gl/zfill_fp.glp b/setup/data/tools/gl/zfill_fp.glp new file mode 100644 index 00000000..5eb8b129 --- /dev/null +++ b/setup/data/tools/gl/zfill_fp.glp @@ -0,0 +1,19 @@ +!!ARBfp1.0 +# cgc version 1.3.0001, build date Aug 4 2004 10:01:10 +# command line args: -profile arbfp1 +# source file: ..\..\setup\data\tools\gl\zfill_arbfp1.cg +#vendor NVIDIA Corporation +#version 1.0.02 +#profile arbfp1 +#program main +#semantic main.colormap +#var float4 IN.position : : : 0 : 0 +#var float4 IN.tex0 : $vin.TEX0 : TEX0 : 0 : 1 +#var sampler2D colormap : : texunit 0 : 1 : 1 +#var float4 main.color : $vout.COL : COL : -1 : 1 +#const c[0] = 0 +PARAM c[1] = { { 0 } }; +MOV result.color.xyz, c[0].x; +TEX result.color.w, fragment.texcoord[0], texture[0], 2D; +END +# 2 instructions, 0 R-regs diff --git a/setup/data/tools/gl/zfill_fp.glsl b/setup/data/tools/gl/zfill_fp.glsl new file mode 100644 index 00000000..537db670 --- /dev/null +++ b/setup/data/tools/gl/zfill_fp.glsl @@ -0,0 +1,29 @@ +/// ============================================================================ +/* +Copyright (C) 2004 Robert Beckebans +Please see the file "CONTRIBUTORS" for a list of contributors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +/// ============================================================================ + +uniform sampler2D u_colormap; + +void main() +{ + gl_FragColor.a = texture2D(u_colormap, gl_TexCoord[0].st).a; + gl_FragColor.rgb = vec3(0.0, 0.0, 0.0); +} diff --git a/setup/data/tools/gl/zfill_vp.glp b/setup/data/tools/gl/zfill_vp.glp new file mode 100644 index 00000000..f6eda0c0 --- /dev/null +++ b/setup/data/tools/gl/zfill_vp.glp @@ -0,0 +1,384 @@ +!!ARBvp1.0 +# cgc version 1.3.0001, build date Aug 4 2004 10:01:10 +# command line args: -profile arbvp1 +# source file: ..\..\setup\data\tools\gl\zfill_arbvp1.cg +#vendor NVIDIA Corporation +#version 1.0.02 +#profile arbvp1 +#program main +#semantic glstate : STATE +#var float4 glstate.material.ambient : STATE.MATERIAL.AMBIENT : : -1 : 0 +#var float4 glstate.material.diffuse : STATE.MATERIAL.DIFFUSE : : -1 : 0 +#var float4 glstate.material.specular : STATE.MATERIAL.SPECULAR : : -1 : 0 +#var float4 glstate.material.emission : STATE.MATERIAL.EMISSION : : -1 : 0 +#var float4 glstate.material.shininess : STATE.MATERIAL.SHININESS : : -1 : 0 +#var float4 glstate.material.front.ambient : STATE.MATERIAL.FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.material.front.diffuse : STATE.MATERIAL.FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.material.front.specular : STATE.MATERIAL.FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.material.front.emission : STATE.MATERIAL.FRONT.EMISSION : : -1 : 0 +#var float4 glstate.material.front.shininess : STATE.MATERIAL.FRONT.SHININESS : : -1 : 0 +#var float4 glstate.material.back.ambient : STATE.MATERIAL.BACK.AMBIENT : : -1 : 0 +#var float4 glstate.material.back.diffuse : STATE.MATERIAL.BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.material.back.specular : STATE.MATERIAL.BACK.SPECULAR : : -1 : 0 +#var float4 glstate.material.back.emission : STATE.MATERIAL.BACK.EMISSION : : -1 : 0 +#var float4 glstate.material.back.shininess : STATE.MATERIAL.BACK.SHININESS : : -1 : 0 +#var float4 glstate.light[0].ambient : STATE.LIGHT[0].AMBIENT : : -1 : 0 +#var float4 glstate.light[0].diffuse : STATE.LIGHT[0].DIFFUSE : : -1 : 0 +#var float4 glstate.light[0].specular : STATE.LIGHT[0].SPECULAR : : -1 : 0 +#var float4 glstate.light[0].position : STATE.LIGHT[0].POSITION : : -1 : 0 +#var float4 glstate.light[0].attenuation : STATE.LIGHT[0].ATTENUATION : : -1 : 0 +#var float4 glstate.light[0].spot.direction : STATE.LIGHT[0].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[0].half : STATE.LIGHT[0].HALF : : -1 : 0 +#var float4 glstate.light[1].ambient : STATE.LIGHT[1].AMBIENT : : -1 : 0 +#var float4 glstate.light[1].diffuse : STATE.LIGHT[1].DIFFUSE : : -1 : 0 +#var float4 glstate.light[1].specular : STATE.LIGHT[1].SPECULAR : : -1 : 0 +#var float4 glstate.light[1].position : STATE.LIGHT[1].POSITION : : -1 : 0 +#var float4 glstate.light[1].attenuation : STATE.LIGHT[1].ATTENUATION : : -1 : 0 +#var float4 glstate.light[1].spot.direction : STATE.LIGHT[1].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[1].half : STATE.LIGHT[1].HALF : : -1 : 0 +#var float4 glstate.light[2].ambient : STATE.LIGHT[2].AMBIENT : : -1 : 0 +#var float4 glstate.light[2].diffuse : STATE.LIGHT[2].DIFFUSE : : -1 : 0 +#var float4 glstate.light[2].specular : STATE.LIGHT[2].SPECULAR : : -1 : 0 +#var float4 glstate.light[2].position : STATE.LIGHT[2].POSITION : : -1 : 0 +#var float4 glstate.light[2].attenuation : STATE.LIGHT[2].ATTENUATION : : -1 : 0 +#var float4 glstate.light[2].spot.direction : STATE.LIGHT[2].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[2].half : STATE.LIGHT[2].HALF : : -1 : 0 +#var float4 glstate.light[3].ambient : STATE.LIGHT[3].AMBIENT : : -1 : 0 +#var float4 glstate.light[3].diffuse : STATE.LIGHT[3].DIFFUSE : : -1 : 0 +#var float4 glstate.light[3].specular : STATE.LIGHT[3].SPECULAR : : -1 : 0 +#var float4 glstate.light[3].position : STATE.LIGHT[3].POSITION : : -1 : 0 +#var float4 glstate.light[3].attenuation : STATE.LIGHT[3].ATTENUATION : : -1 : 0 +#var float4 glstate.light[3].spot.direction : STATE.LIGHT[3].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[3].half : STATE.LIGHT[3].HALF : : -1 : 0 +#var float4 glstate.light[4].ambient : STATE.LIGHT[4].AMBIENT : : -1 : 0 +#var float4 glstate.light[4].diffuse : STATE.LIGHT[4].DIFFUSE : : -1 : 0 +#var float4 glstate.light[4].specular : STATE.LIGHT[4].SPECULAR : : -1 : 0 +#var float4 glstate.light[4].position : STATE.LIGHT[4].POSITION : : -1 : 0 +#var float4 glstate.light[4].attenuation : STATE.LIGHT[4].ATTENUATION : : -1 : 0 +#var float4 glstate.light[4].spot.direction : STATE.LIGHT[4].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[4].half : STATE.LIGHT[4].HALF : : -1 : 0 +#var float4 glstate.light[5].ambient : STATE.LIGHT[5].AMBIENT : : -1 : 0 +#var float4 glstate.light[5].diffuse : STATE.LIGHT[5].DIFFUSE : : -1 : 0 +#var float4 glstate.light[5].specular : STATE.LIGHT[5].SPECULAR : : -1 : 0 +#var float4 glstate.light[5].position : STATE.LIGHT[5].POSITION : : -1 : 0 +#var float4 glstate.light[5].attenuation : STATE.LIGHT[5].ATTENUATION : : -1 : 0 +#var float4 glstate.light[5].spot.direction : STATE.LIGHT[5].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[5].half : STATE.LIGHT[5].HALF : : -1 : 0 +#var float4 glstate.light[6].ambient : STATE.LIGHT[6].AMBIENT : : -1 : 0 +#var float4 glstate.light[6].diffuse : STATE.LIGHT[6].DIFFUSE : : -1 : 0 +#var float4 glstate.light[6].specular : STATE.LIGHT[6].SPECULAR : : -1 : 0 +#var float4 glstate.light[6].position : STATE.LIGHT[6].POSITION : : -1 : 0 +#var float4 glstate.light[6].attenuation : STATE.LIGHT[6].ATTENUATION : : -1 : 0 +#var float4 glstate.light[6].spot.direction : STATE.LIGHT[6].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[6].half : STATE.LIGHT[6].HALF : : -1 : 0 +#var float4 glstate.light[7].ambient : STATE.LIGHT[7].AMBIENT : : -1 : 0 +#var float4 glstate.light[7].diffuse : STATE.LIGHT[7].DIFFUSE : : -1 : 0 +#var float4 glstate.light[7].specular : STATE.LIGHT[7].SPECULAR : : -1 : 0 +#var float4 glstate.light[7].position : STATE.LIGHT[7].POSITION : : -1 : 0 +#var float4 glstate.light[7].attenuation : STATE.LIGHT[7].ATTENUATION : : -1 : 0 +#var float4 glstate.light[7].spot.direction : STATE.LIGHT[7].SPOT.DIRECTION : : -1 : 0 +#var float4 glstate.light[7].half : STATE.LIGHT[7].HALF : : -1 : 0 +#var float4 glstate.lightmodel.ambient : STATE.LIGHTMODEL.AMBIENT : : -1 : 0 +#var float4 glstate.lightmodel.scenecolor : STATE.LIGHTMODEL.SCENECOLOR : : -1 : 0 +#var float4 glstate.lightmodel.front.scenecolor : STATE.LIGHTMODEL.FRONT.SCENECOLOR : : -1 : 0 +#var float4 glstate.lightmodel.back.scenecolor : STATE.LIGHTMODEL.BACK.SCENECOLOR : : -1 : 0 +#var float4 glstate.lightprod[0].ambient : STATE.LIGHTPROD[0].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[0].diffuse : STATE.LIGHTPROD[0].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[0].specular : STATE.LIGHTPROD[0].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[0].front.ambient : STATE.LIGHTPROD[0].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[0].front.diffuse : STATE.LIGHTPROD[0].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[0].front.specular : STATE.LIGHTPROD[0].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[0].back.ambient : STATE.LIGHTPROD[0].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[0].back.diffuse : STATE.LIGHTPROD[0].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[0].back.specular : STATE.LIGHTPROD[0].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[1].ambient : STATE.LIGHTPROD[1].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[1].diffuse : STATE.LIGHTPROD[1].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[1].specular : STATE.LIGHTPROD[1].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[1].front.ambient : STATE.LIGHTPROD[1].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[1].front.diffuse : STATE.LIGHTPROD[1].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[1].front.specular : STATE.LIGHTPROD[1].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[1].back.ambient : STATE.LIGHTPROD[1].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[1].back.diffuse : STATE.LIGHTPROD[1].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[1].back.specular : STATE.LIGHTPROD[1].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[2].ambient : STATE.LIGHTPROD[2].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[2].diffuse : STATE.LIGHTPROD[2].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[2].specular : STATE.LIGHTPROD[2].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[2].front.ambient : STATE.LIGHTPROD[2].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[2].front.diffuse : STATE.LIGHTPROD[2].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[2].front.specular : STATE.LIGHTPROD[2].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[2].back.ambient : STATE.LIGHTPROD[2].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[2].back.diffuse : STATE.LIGHTPROD[2].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[2].back.specular : STATE.LIGHTPROD[2].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[3].ambient : STATE.LIGHTPROD[3].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[3].diffuse : STATE.LIGHTPROD[3].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[3].specular : STATE.LIGHTPROD[3].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[3].front.ambient : STATE.LIGHTPROD[3].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[3].front.diffuse : STATE.LIGHTPROD[3].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[3].front.specular : STATE.LIGHTPROD[3].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[3].back.ambient : STATE.LIGHTPROD[3].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[3].back.diffuse : STATE.LIGHTPROD[3].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[3].back.specular : STATE.LIGHTPROD[3].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[4].ambient : STATE.LIGHTPROD[4].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[4].diffuse : STATE.LIGHTPROD[4].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[4].specular : STATE.LIGHTPROD[4].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[4].front.ambient : STATE.LIGHTPROD[4].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[4].front.diffuse : STATE.LIGHTPROD[4].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[4].front.specular : STATE.LIGHTPROD[4].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[4].back.ambient : STATE.LIGHTPROD[4].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[4].back.diffuse : STATE.LIGHTPROD[4].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[4].back.specular : STATE.LIGHTPROD[4].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[5].ambient : STATE.LIGHTPROD[5].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[5].diffuse : STATE.LIGHTPROD[5].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[5].specular : STATE.LIGHTPROD[5].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[5].front.ambient : STATE.LIGHTPROD[5].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[5].front.diffuse : STATE.LIGHTPROD[5].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[5].front.specular : STATE.LIGHTPROD[5].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[5].back.ambient : STATE.LIGHTPROD[5].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[5].back.diffuse : STATE.LIGHTPROD[5].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[5].back.specular : STATE.LIGHTPROD[5].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[6].ambient : STATE.LIGHTPROD[6].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[6].diffuse : STATE.LIGHTPROD[6].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[6].specular : STATE.LIGHTPROD[6].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[6].front.ambient : STATE.LIGHTPROD[6].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[6].front.diffuse : STATE.LIGHTPROD[6].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[6].front.specular : STATE.LIGHTPROD[6].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[6].back.ambient : STATE.LIGHTPROD[6].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[6].back.diffuse : STATE.LIGHTPROD[6].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[6].back.specular : STATE.LIGHTPROD[6].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[7].ambient : STATE.LIGHTPROD[7].AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[7].diffuse : STATE.LIGHTPROD[7].DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[7].specular : STATE.LIGHTPROD[7].SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[7].front.ambient : STATE.LIGHTPROD[7].FRONT.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[7].front.diffuse : STATE.LIGHTPROD[7].FRONT.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[7].front.specular : STATE.LIGHTPROD[7].FRONT.SPECULAR : : -1 : 0 +#var float4 glstate.lightprod[7].back.ambient : STATE.LIGHTPROD[7].BACK.AMBIENT : : -1 : 0 +#var float4 glstate.lightprod[7].back.diffuse : STATE.LIGHTPROD[7].BACK.DIFFUSE : : -1 : 0 +#var float4 glstate.lightprod[7].back.specular : STATE.LIGHTPROD[7].BACK.SPECULAR : : -1 : 0 +#var float4 glstate.texgen[0].eye.s : STATE.TEXGEN[0].EYE.S : : -1 : 0 +#var float4 glstate.texgen[0].eye.t : STATE.TEXGEN[0].EYE.T : : -1 : 0 +#var float4 glstate.texgen[0].eye.r : STATE.TEXGEN[0].EYE.R : : -1 : 0 +#var float4 glstate.texgen[0].eye.q : STATE.TEXGEN[0].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[0].object.s : STATE.TEXGEN[0].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[0].object.t : STATE.TEXGEN[0].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[0].object.r : STATE.TEXGEN[0].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[0].object.q : STATE.TEXGEN[0].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[1].eye.s : STATE.TEXGEN[1].EYE.S : : -1 : 0 +#var float4 glstate.texgen[1].eye.t : STATE.TEXGEN[1].EYE.T : : -1 : 0 +#var float4 glstate.texgen[1].eye.r : STATE.TEXGEN[1].EYE.R : : -1 : 0 +#var float4 glstate.texgen[1].eye.q : STATE.TEXGEN[1].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[1].object.s : STATE.TEXGEN[1].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[1].object.t : STATE.TEXGEN[1].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[1].object.r : STATE.TEXGEN[1].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[1].object.q : STATE.TEXGEN[1].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[2].eye.s : STATE.TEXGEN[2].EYE.S : : -1 : 0 +#var float4 glstate.texgen[2].eye.t : STATE.TEXGEN[2].EYE.T : : -1 : 0 +#var float4 glstate.texgen[2].eye.r : STATE.TEXGEN[2].EYE.R : : -1 : 0 +#var float4 glstate.texgen[2].eye.q : STATE.TEXGEN[2].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[2].object.s : STATE.TEXGEN[2].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[2].object.t : STATE.TEXGEN[2].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[2].object.r : STATE.TEXGEN[2].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[2].object.q : STATE.TEXGEN[2].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[3].eye.s : STATE.TEXGEN[3].EYE.S : : -1 : 0 +#var float4 glstate.texgen[3].eye.t : STATE.TEXGEN[3].EYE.T : : -1 : 0 +#var float4 glstate.texgen[3].eye.r : STATE.TEXGEN[3].EYE.R : : -1 : 0 +#var float4 glstate.texgen[3].eye.q : STATE.TEXGEN[3].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[3].object.s : STATE.TEXGEN[3].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[3].object.t : STATE.TEXGEN[3].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[3].object.r : STATE.TEXGEN[3].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[3].object.q : STATE.TEXGEN[3].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[4].eye.s : STATE.TEXGEN[4].EYE.S : : -1 : 0 +#var float4 glstate.texgen[4].eye.t : STATE.TEXGEN[4].EYE.T : : -1 : 0 +#var float4 glstate.texgen[4].eye.r : STATE.TEXGEN[4].EYE.R : : -1 : 0 +#var float4 glstate.texgen[4].eye.q : STATE.TEXGEN[4].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[4].object.s : STATE.TEXGEN[4].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[4].object.t : STATE.TEXGEN[4].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[4].object.r : STATE.TEXGEN[4].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[4].object.q : STATE.TEXGEN[4].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[5].eye.s : STATE.TEXGEN[5].EYE.S : : -1 : 0 +#var float4 glstate.texgen[5].eye.t : STATE.TEXGEN[5].EYE.T : : -1 : 0 +#var float4 glstate.texgen[5].eye.r : STATE.TEXGEN[5].EYE.R : : -1 : 0 +#var float4 glstate.texgen[5].eye.q : STATE.TEXGEN[5].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[5].object.s : STATE.TEXGEN[5].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[5].object.t : STATE.TEXGEN[5].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[5].object.r : STATE.TEXGEN[5].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[5].object.q : STATE.TEXGEN[5].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[6].eye.s : STATE.TEXGEN[6].EYE.S : : -1 : 0 +#var float4 glstate.texgen[6].eye.t : STATE.TEXGEN[6].EYE.T : : -1 : 0 +#var float4 glstate.texgen[6].eye.r : STATE.TEXGEN[6].EYE.R : : -1 : 0 +#var float4 glstate.texgen[6].eye.q : STATE.TEXGEN[6].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[6].object.s : STATE.TEXGEN[6].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[6].object.t : STATE.TEXGEN[6].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[6].object.r : STATE.TEXGEN[6].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[6].object.q : STATE.TEXGEN[6].OBJECT.Q : : -1 : 0 +#var float4 glstate.texgen[7].eye.s : STATE.TEXGEN[7].EYE.S : : -1 : 0 +#var float4 glstate.texgen[7].eye.t : STATE.TEXGEN[7].EYE.T : : -1 : 0 +#var float4 glstate.texgen[7].eye.r : STATE.TEXGEN[7].EYE.R : : -1 : 0 +#var float4 glstate.texgen[7].eye.q : STATE.TEXGEN[7].EYE.Q : : -1 : 0 +#var float4 glstate.texgen[7].object.s : STATE.TEXGEN[7].OBJECT.S : : -1 : 0 +#var float4 glstate.texgen[7].object.t : STATE.TEXGEN[7].OBJECT.T : : -1 : 0 +#var float4 glstate.texgen[7].object.r : STATE.TEXGEN[7].OBJECT.R : : -1 : 0 +#var float4 glstate.texgen[7].object.q : STATE.TEXGEN[7].OBJECT.Q : : -1 : 0 +#var float4 glstate.fog.color : STATE.FOG.COLOR : : -1 : 0 +#var float4 glstate.fog.params : STATE.FOG.PARAMS : : -1 : 0 +#var float4 glstate.clip[0].plane : STATE.CLIP[0].PLANE : : -1 : 0 +#var float4 glstate.clip[1].plane : STATE.CLIP[1].PLANE : : -1 : 0 +#var float4 glstate.clip[2].plane : STATE.CLIP[2].PLANE : : -1 : 0 +#var float4 glstate.clip[3].plane : STATE.CLIP[3].PLANE : : -1 : 0 +#var float4 glstate.clip[4].plane : STATE.CLIP[4].PLANE : : -1 : 0 +#var float4 glstate.clip[5].plane : STATE.CLIP[5].PLANE : : -1 : 0 +#var float4 glstate.clip[6].plane : STATE.CLIP[6].PLANE : : -1 : 0 +#var float4 glstate.clip[7].plane : STATE.CLIP[7].PLANE : : -1 : 0 +#var float glstate.point.size : STATE.POINT.SIZE : : -1 : 0 +#var float glstate.point.attenuation : STATE.POINT.ATTENUATION : : -1 : 0 +#var float4x4 glstate.matrix.modelview[0] : STATE.MATRIX.MODELVIEW[0] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[1] : STATE.MATRIX.MODELVIEW[1] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[2] : STATE.MATRIX.MODELVIEW[2] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[3] : STATE.MATRIX.MODELVIEW[3] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[4] : STATE.MATRIX.MODELVIEW[4] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[5] : STATE.MATRIX.MODELVIEW[5] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[6] : STATE.MATRIX.MODELVIEW[6] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.modelview[7] : STATE.MATRIX.MODELVIEW[7] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.projection : STATE.MATRIX.PROJECTION : , 4 : -1 : 0 +#var float4x4 glstate.matrix.mvp : STATE.MATRIX.MVP : c[0], 4 : -1 : 1 +#var float4x4 glstate.matrix.texture[0] : STATE.MATRIX.TEXTURE[0] : c[4], 4 : -1 : 1 +#var float4x4 glstate.matrix.texture[1] : STATE.MATRIX.TEXTURE[1] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.texture[2] : STATE.MATRIX.TEXTURE[2] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.texture[3] : STATE.MATRIX.TEXTURE[3] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.texture[4] : STATE.MATRIX.TEXTURE[4] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.texture[5] : STATE.MATRIX.TEXTURE[5] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.texture[6] : STATE.MATRIX.TEXTURE[6] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.texture[7] : STATE.MATRIX.TEXTURE[7] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[0] : STATE.MATRIX.PALETTE[0] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[1] : STATE.MATRIX.PALETTE[1] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[2] : STATE.MATRIX.PALETTE[2] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[3] : STATE.MATRIX.PALETTE[3] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[4] : STATE.MATRIX.PALETTE[4] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[5] : STATE.MATRIX.PALETTE[5] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[6] : STATE.MATRIX.PALETTE[6] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.palette[7] : STATE.MATRIX.PALETTE[7] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[0] : STATE.MATRIX.PROGRAM[0] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[1] : STATE.MATRIX.PROGRAM[1] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[2] : STATE.MATRIX.PROGRAM[2] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[3] : STATE.MATRIX.PROGRAM[3] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[4] : STATE.MATRIX.PROGRAM[4] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[5] : STATE.MATRIX.PROGRAM[5] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[6] : STATE.MATRIX.PROGRAM[6] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.program[7] : STATE.MATRIX.PROGRAM[7] : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[0] : STATE.MATRIX.MODELVIEW[0].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[1] : STATE.MATRIX.MODELVIEW[1].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[2] : STATE.MATRIX.MODELVIEW[2].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[3] : STATE.MATRIX.MODELVIEW[3].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[4] : STATE.MATRIX.MODELVIEW[4].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[5] : STATE.MATRIX.MODELVIEW[5].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[6] : STATE.MATRIX.MODELVIEW[6].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.modelview[7] : STATE.MATRIX.MODELVIEW[7].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.projection : STATE.MATRIX.PROJECTION.INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.mvp : STATE.MATRIX.MVP.INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[0] : STATE.MATRIX.TEXTURE[0].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[1] : STATE.MATRIX.TEXTURE[1].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[2] : STATE.MATRIX.TEXTURE[2].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[3] : STATE.MATRIX.TEXTURE[3].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[4] : STATE.MATRIX.TEXTURE[4].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[5] : STATE.MATRIX.TEXTURE[5].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[6] : STATE.MATRIX.TEXTURE[6].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.texture[7] : STATE.MATRIX.TEXTURE[7].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[0] : STATE.MATRIX.PALETTE[0].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[1] : STATE.MATRIX.PALETTE[1].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[2] : STATE.MATRIX.PALETTE[2].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[3] : STATE.MATRIX.PALETTE[3].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[4] : STATE.MATRIX.PALETTE[4].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[5] : STATE.MATRIX.PALETTE[5].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[6] : STATE.MATRIX.PALETTE[6].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.palette[7] : STATE.MATRIX.PALETTE[7].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[0] : STATE.MATRIX.PROGRAM[0].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[1] : STATE.MATRIX.PROGRAM[1].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[2] : STATE.MATRIX.PROGRAM[2].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[3] : STATE.MATRIX.PROGRAM[3].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[4] : STATE.MATRIX.PROGRAM[4].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[5] : STATE.MATRIX.PROGRAM[5].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[6] : STATE.MATRIX.PROGRAM[6].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.inverse.program[7] : STATE.MATRIX.PROGRAM[7].INVERSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[0] : STATE.MATRIX.MODELVIEW[0].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[1] : STATE.MATRIX.MODELVIEW[1].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[2] : STATE.MATRIX.MODELVIEW[2].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[3] : STATE.MATRIX.MODELVIEW[3].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[4] : STATE.MATRIX.MODELVIEW[4].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[5] : STATE.MATRIX.MODELVIEW[5].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[6] : STATE.MATRIX.MODELVIEW[6].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.modelview[7] : STATE.MATRIX.MODELVIEW[7].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.projection : STATE.MATRIX.PROJECTION.TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.mvp : STATE.MATRIX.MVP.TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[0] : STATE.MATRIX.TEXTURE[0].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[1] : STATE.MATRIX.TEXTURE[1].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[2] : STATE.MATRIX.TEXTURE[2].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[3] : STATE.MATRIX.TEXTURE[3].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[4] : STATE.MATRIX.TEXTURE[4].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[5] : STATE.MATRIX.TEXTURE[5].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[6] : STATE.MATRIX.TEXTURE[6].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.texture[7] : STATE.MATRIX.TEXTURE[7].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[0] : STATE.MATRIX.PALETTE[0].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[1] : STATE.MATRIX.PALETTE[1].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[2] : STATE.MATRIX.PALETTE[2].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[3] : STATE.MATRIX.PALETTE[3].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[4] : STATE.MATRIX.PALETTE[4].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[5] : STATE.MATRIX.PALETTE[5].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[6] : STATE.MATRIX.PALETTE[6].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.palette[7] : STATE.MATRIX.PALETTE[7].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[0] : STATE.MATRIX.PROGRAM[0].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[1] : STATE.MATRIX.PROGRAM[1].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[2] : STATE.MATRIX.PROGRAM[2].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[3] : STATE.MATRIX.PROGRAM[3].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[4] : STATE.MATRIX.PROGRAM[4].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[5] : STATE.MATRIX.PROGRAM[5].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[6] : STATE.MATRIX.PROGRAM[6].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.transpose.program[7] : STATE.MATRIX.PROGRAM[7].TRANSPOSE : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[0] : STATE.MATRIX.MODELVIEW[0].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[1] : STATE.MATRIX.MODELVIEW[1].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[2] : STATE.MATRIX.MODELVIEW[2].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[3] : STATE.MATRIX.MODELVIEW[3].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[4] : STATE.MATRIX.MODELVIEW[4].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[5] : STATE.MATRIX.MODELVIEW[5].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[6] : STATE.MATRIX.MODELVIEW[6].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.modelview[7] : STATE.MATRIX.MODELVIEW[7].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.projection : STATE.MATRIX.PROJECTION.INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.mvp : STATE.MATRIX.MVP.INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[0] : STATE.MATRIX.TEXTURE[0].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[1] : STATE.MATRIX.TEXTURE[1].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[2] : STATE.MATRIX.TEXTURE[2].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[3] : STATE.MATRIX.TEXTURE[3].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[4] : STATE.MATRIX.TEXTURE[4].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[5] : STATE.MATRIX.TEXTURE[5].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[6] : STATE.MATRIX.TEXTURE[6].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.texture[7] : STATE.MATRIX.TEXTURE[7].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[0] : STATE.MATRIX.PALETTE[0].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[1] : STATE.MATRIX.PALETTE[1].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[2] : STATE.MATRIX.PALETTE[2].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[3] : STATE.MATRIX.PALETTE[3].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[4] : STATE.MATRIX.PALETTE[4].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[5] : STATE.MATRIX.PALETTE[5].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[6] : STATE.MATRIX.PALETTE[6].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.palette[7] : STATE.MATRIX.PALETTE[7].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[0] : STATE.MATRIX.PROGRAM[0].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[1] : STATE.MATRIX.PROGRAM[1].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[2] : STATE.MATRIX.PROGRAM[2].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[3] : STATE.MATRIX.PROGRAM[3].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[4] : STATE.MATRIX.PROGRAM[4].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[5] : STATE.MATRIX.PROGRAM[5].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[6] : STATE.MATRIX.PROGRAM[6].INVTRANS : , 4 : -1 : 0 +#var float4x4 glstate.matrix.invtrans.program[7] : STATE.MATRIX.PROGRAM[7].INVTRANS : , 4 : -1 : 0 +#var float4 IN.position : $vin.ATTR0 : ATTR0 : 0 : 1 +#var float4 IN.texcoord0 : $vin.ATTR8 : ATTR8 : 0 : 1 +#var float4 main.position : $vout.HPOS : HPOS : -1 : 1 +#var float4 main.tex0 : $vout.TEX0 : TEX0 : -1 : 1 +PARAM c[8] = { state.matrix.mvp, + state.matrix.texture[0] }; +DP4 result.position.w, vertex.attrib[0], c[3]; +DP4 result.position.z, vertex.attrib[0], c[2]; +DP4 result.position.y, vertex.attrib[0], c[1]; +DP4 result.position.x, vertex.attrib[0], c[0]; +DP4 result.texcoord[0].w, vertex.attrib[8], c[7]; +DP4 result.texcoord[0].z, vertex.attrib[8], c[6]; +DP4 result.texcoord[0].y, vertex.attrib[8], c[5]; +DP4 result.texcoord[0].x, vertex.attrib[8], c[4]; +END +# 8 instructions, 0 R-regs diff --git a/setup/data/tools/gl/zfill_vp.glsl b/setup/data/tools/gl/zfill_vp.glsl new file mode 100644 index 00000000..4c81280e --- /dev/null +++ b/setup/data/tools/gl/zfill_vp.glsl @@ -0,0 +1,35 @@ +/// ============================================================================ +/* +Copyright (C) 2004 Robert Beckebans +Please see the file "CONTRIBUTORS" for a list of contributors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +/// ============================================================================ + +attribute vec4 attr_TexCoord0; + +void main() +{ + // transform vertex position into homogenous clip-space + gl_Position = ftransform(); + + // transform texcoords + gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; + + // assign color + gl_FrontColor = gl_Color; +} diff --git a/setup/data/tools/global.xlink b/setup/data/tools/global.xlink new file mode 100644 index 00000000..8fbd0433 --- /dev/null +++ b/setup/data/tools/global.xlink @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/setup/data/tools/plugins/bitmaps/bobtoolz_caulk.bmp b/setup/data/tools/plugins/bitmaps/bobtoolz_caulk.bmp new file mode 100644 index 0000000000000000000000000000000000000000..522395034ccf7b609f78b287d027a125bac211a9 GIT binary patch literal 320 zcmZusK?;B%5S&CK=ukwD=o3M7uLt%PpZtHaK$x*uBu#mTymBRP-_I@W}# z2ZSx9P!MB;td+~p9^ncwek?|Ah$&k-ib0))637(kd6$OYm5K=^pAGz04teun@5|3g(_ zQedN*KmaVq$OsZZ=A!dK%9ubZfHXukSOkp^QGk#qjt@2v?jnc%a^)Uk83x?I_Vp=P%~v1>A*v+ zQ)*zAUT!&XQ*F7o7{TgkAP O)&Ebeo`0--ib0))637(kc}$OYm5K==m;e*&Q$5Q4z}|Np@> z7J>yuj0FVXA`muQ1TF(*ASncEp_mUffDvX9&}JqE1{P){gMmT_9>`Wy9^C051^~OX B3%~#X literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/bobtoolz_split.bmp b/setup/data/tools/plugins/bitmaps/bobtoolz_split.bmp new file mode 100644 index 0000000000000000000000000000000000000000..fbb1571ce1c870ddc44f5aa9eaa6106ba261274d GIT binary patch literal 312 zcmaKmF%p0v3!zxzy&rTA=|MU!gl(Fny8U7^z^_* z%|lYhyL5BO11D9?0q$ySoO>(g0()P$CAL)JRyYF0ml+UNw!nsY{p9&u%P~={57a0z MPcva~&g%H%2bECipJOMNtNMH`363}GgS?TM zOt`QDAM$<;p1Btr*Sc<~wW5@QoYld+HP^AsGqfP3Xe0c54 z5eK4R&knqaV-t`rEIe3*YcO72;!u^(jQO?0Gy&rX3`2+0<<5wVf2(b`vYFLdQ{l>* xmHUd#5D|IZuZS_@lm2`{0gCR^ljLrayy2m{puJmsuY1Vrk5Y)4{pU-X^9x&$8s7i_ literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/bobtoolz_turnedge.bmp b/setup/data/tools/plugins/bitmaps/bobtoolz_turnedge.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b72cb2aae6326e7c82b8be43f02ff38910d8eaf2 GIT binary patch literal 332 zcmZvW!3{zo5JZR1hiE)#j6LYZ9?U-&588m0dD7;rzy@$;7e0w`$t?R|Sa!Ny11#>$ z2WmsDselDD@S(OV*OIkVxL)Ty@IC?0BXHT-Deyokh4r5pLXUy>AvRM^nt>YaBq!C9 rIf#wr5O$7+opZ7t^}Wp2qq3XnaCVf#xFXw+#`?%3iht*Cm-GGw>kkck literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/ufoai_actorclip.bmp b/setup/data/tools/plugins/bitmaps/ufoai_actorclip.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4a2887c6ae391bde30288a53fecd32ecf7e95051 GIT binary patch literal 494 zcmbWvyGuf07zXg?bhXTD+0m|=UF_azbCU)^Fjoh!C{Bt)Ac9M0XsW3#i2i_vD1r(l zbLj$2)nI}B1wj$CXV0fjZS}zK<$GTE-nI1)35|Sny2wZdlAz%PKFO?YTIE{+Hc6!| z+NJ|Kq+>dvQ#zv}mFSYnRH0kCqX&AVC#upj)u;})djlSi3a@tuKHo0<{yhW&``pzL z3?3mAI!8Esfk>oCB}AiT#9~*7$16xAu8~aMAeFjDI$cF3^MY))Mz6@_-jL6~qfq#Q zs@5U5qO|g#Zq^yQrY05}7sP+adi}I*ctk8jH|Y$63y$$g(Z_y8S#~<-?DGz}w|l^% nm1RY-T8CB?n=JLTw{?g<<65%Mj`sIyES)CNn-OH1V76Jf4u@TV# literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/ufoai_level2.bmp b/setup/data/tools/plugins/bitmaps/ufoai_level2.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e84e254434fe221653da07189d16146fa36aa9ba GIT binary patch literal 198 zcmYj~!3}^g3&148an7_`^KR!az*Z6{=t-svwbbt&=GG@gU+PRw`2?mC&&Q z7c~!|o^MHEW>9X-vJ}+2MuRm?9{Ho2{CkVc;Dq`zL>ZzCC(Dn%DGO~*`|aQhD#j6R literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/ufoai_level3.bmp b/setup/data/tools/plugins/bitmaps/ufoai_level3.bmp new file mode 100644 index 0000000000000000000000000000000000000000..141c32457872bce1579e51417c3b8afa73c0b01f GIT binary patch literal 198 zcmY*Su?>JQ409#K!a##Kf+twm;T~?`Ag<{PRd5n2gd@kbqomt@BXCd_GLs{j(6Iw2 zISs*{Z=*p~VYRZ#Tu`qX1NJ12{3)-B&?jq=HVF}a literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/ufoai_level6.bmp b/setup/data/tools/plugins/bitmaps/ufoai_level6.bmp new file mode 100644 index 0000000000000000000000000000000000000000..418fdd280dc849add3cc4d3d307e245e0ef77906 GIT binary patch literal 198 zcmZ?rJ;ne5en3hChy{R{ABY(lSb!u0Lj@2EaYHa0kPX5h@c;jR1`xCb!Z#vs82--s y3y}bljDlbwfJ8Ge2nquE0*qin5XfTy@iAzaJWM^vAcz)_W}q&JIS}(<76Je$4-s1c literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/ufoai_level7.bmp b/setup/data/tools/plugins/bitmaps/ufoai_level7.bmp new file mode 100644 index 0000000000000000000000000000000000000000..d82dab61c387a5f974bad9a52dde617aaf298b81 GIT binary patch literal 198 zcmZ?rJ;ne5en3hChy{R{ABY(lSb!u0Lj@2EaYHa0kPX5h@c;jR1`xCb!Z#vs82--s y3y}bljDlbwfJ8F@ML>Lz9EgTvpafVQoMwdZfr4Oh7%j*E;)AS!h=a_7SqK0^mJzT3 literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/ufoai_level8.bmp b/setup/data/tools/plugins/bitmaps/ufoai_level8.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c3809538b01aa2cc6b430fa75a63c18c842e401c GIT binary patch literal 198 zcmZ?rJ;ne5en3hChy{R{ABY(lSb!u0Lj@2EaYHa0kPX5h@c;jR1`xCb!Z#vs82--s r3y}bljDlbwfS`eMQ9fD z4_?~w$(ZnCW|a;O=;u&y!~n+(al$ZXSP5d|LU4(l5Du;x;_0Yc6IS7RI8PHb~Gxy$qHKA(%He0k3a`+{hrk)rv(YZ6nFGt>0?%KB2H zBx$uO6)~|H`2|HKd38UYk&u!UpHpk7u4*($QpKOhwKYxU>B=m*TK3|yLPc)euj?9g PYGwAXi%b9iXXM2_RXB7j3R}BM$cP#^hUlW62P$mOIKpTT%XgLEz zXfK0eWIY2z{5%GRgar)xshtcusoe~&>2(bH*^?OjO8Xh?E9Nl-RZV5Et6jkm-ZY;f zs%;5FOy>%Q*zQ#f_PvJ~5+-b5NSeHbA#=`thJxj17>d_iVkldGouO{eGX@bT(0~GJ z^Z8ksxVag)*d^7a<)y`C#fA7}BqTVbM8#M*6ge0LI8+tXb0IR%-8d1&Ph Gn7;s?3r%1E literal 0 HcmV?d00001 diff --git a/setup/data/tools/plugins/bitmaps/ufoai_weaponclip.bmp b/setup/data/tools/plugins/bitmaps/ufoai_weaponclip.bmp new file mode 100644 index 0000000000000000000000000000000000000000..799468ba2bc51b1046ce470176b7fe34c129007e GIT binary patch literal 558 zcmbWx$uHbt9LMqZpeSYROSKrr&KTQZhM$>{NRbikK@UU*O==4v60t-^kTl_fLtBKu zpa&-h;y_|K&_=9@12>67B~2tvnoPex%f-0M{x{6(IwVed9U~i+xsyvUt|9;4wm0g zdP_y!Bo!YgLHR5GL7AJ%`e~}_XK*;@sA-y~wq=Qi-W3}AR&n;P(>$=rr{Qg^-EKa+ z_GlgbN!!=~?c;}ZOdN@0dZsSKC4*o8F!cQv*OHrzhu9wSoIWBT#\par +Copyright (C) \par +\pard\par +\pard\li256This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.\par +\pard\par +\pard\li256This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\par +\pard\par +\pard\li256You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA\par +\pard\par +\par +Also add information on how to contact you by electronic and paper mail.\par +\par +If the program is interactive, make it output a short notice like this when it starts in an interactive mode:\par +\par +\pard\li256Gnomovision version 69, Copyright (C) year name of author\par +Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.\par +\pard\par +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program.\par +\par +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names:\par +\par +\pard\li256Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker.\par +\pard\par +\pard\li256, 1 April 1989\par +Ty Coon, President of Vice\par +\pard\par +This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.\par +\f1\par +} \ No newline at end of file diff --git a/setup/gpl.txt b/setup/gpl.txt new file mode 100644 index 00000000..6d45519c --- /dev/null +++ b/setup/gpl.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/setup/license.rtf b/setup/license.rtf new file mode 100644 index 00000000..ca0e8b22 --- /dev/null +++ b/setup/license.rtf @@ -0,0 +1,49 @@ +{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Courier New;}} +{\*\generator Msftedit 5.41.15.1503;}\viewkind4\uc1\pard\lang2057\f0\fs20\tab LIMITED USE SOFTWARE LICENSE AGREEMENT\par +\par +This Limited Use Software License Agreement (the "Agreement") is a legal agreement between you, the end-user, and Id Software, Inc. ("ID"). BY CONTINUING THE INSTALLATION OF THIS SOFTWARE (THE "SOFTWARE"), BY LOADING OR RUNNING THE SOFTWARE, OR BY PLACING OR COPYING THE SOFTWARE ONTO YOUR COMPUTER HARD DRIVE, COMPUTER RAM OR OTHER STORAGE, YOU ARE AGREEING TO BE BOUND BY THE TERMS OF THIS AGREEMENT. \par +\par +1.\tab Grant of License. Subject to the terms and provisions of this Agreement, ID grants to you the non-exclusive and limited right to use the Software only in executable or object code form. The term "Software" includes all elements of the Software. You are not receiving any ownership or proprietary right, title or interest in or to the Software or the copyrights, trademarks, or other rights related thereto. For purposes of the first sentence of this section, "use" means loading the Software into RAM and/or onto computer hard drive, as well as installation of the Software on a hard disk or other storage device and means the uses permitted in section 3. hereinbelow. You agree that the Software will not be shipped, transferred or exported into any country in violation of the U.S. Export Administration Act (or any other law governing such matters) by you or anyone at your direction and that you will not utilize and will not authorize anyone to utilize, in any other manner, the Software in violation of any applicable law. The Software shall not be downloaded or otherwise exported or reexported into (or to a national or resident of) any country to which the U.S. has embargoed goods or to anyone or into any country who/which are prohibited, by applicable law, from receiving such property.\par +\par +2.\tab Prohibitions. You, whether directly or indirectly, shall not do any of the following acts:\par +\par +a.\tab rent the Software;\par +\par +b.\tab sell the Software;\par +\par +c.\tab lease or lend the Software;\par +\par +d.\tab distribute the Software (except as permitted by section 3. hereinbelow);\par +\par +e.\tab in any other manner and through any medium whatsoever commercially exploit the Software or use the Software for any commercial purpose;\par +\par +f.\tab disassemble, reverse engineer, decompile, modify or alter the Software;\par +\par +g.\tab translate the Software;\par +\par +h.\tab reproduce or copy the Software (except as permitted by section 3. hereinbelow);\par +\par +i.\tab publicly display the Software; \par +\par +j.\tab prepare or develop derivative works based upon the Software; or \par +\par +k.\tab remove or alter any legal notices or other markings or legends, such as trademark and copyright notices, affixed on or within the Software.\par +\par +\par +3.\tab Permitted Uses. So long as this Agreement accompanies each copy you make of the Software, and so long as you fully comply, at all times, with this Agreement, ID grants to you the non-exclusive and limited right to distribute copies of the Software free of charge for non-commercial purposes by electronic means only and the non-exclusive and limited right to use the Software to create your own modifications for operation only with the full version of the software game QUAKE III ARENA; provided, however, you shall not make any modifications unless and until you have agreed to be bound by the terms of the LIMITED USE SOFTWARE LICENSE AGREEMENT which accompanies the full version of QUAKE III ARENA. Other than the electronic copies permitted above, you may make only the following copies of the Software: (i) you may copy the Software onto your computer hard drive; (ii) you may copy the Software from your computer hard drive into your computer RAM; and (iii) you may make one (1) "back-up" or archival copy of the Software on one (1) hard disk. You shall not use, copy or distribute the Software in any infringing manner or in any manner which violates any law or third party right and you shall not distribute the Software together with any material which infringes against any third party right or which is libelous, defamatory, obscene, false, misleading, or otherwise illegal or unlawful. ID reserves all rights not granted in this Agreement, including, without limitation, all rights to ID's trademarks. You shall not commercially distribute the Software.\par +\par +4.\tab Intellectual Property Rights. The Software and all copyrights, trademarks and all other conceivable intellectual property rights related to the Software are owned by ID and are protected by United States copyright laws, international treaty provisions and all applicable law, such as the Lanham Act. You must treat the Software like any other copyrighted material, as required by 17 U.S.C. S101 et seq. and other applicable law. You agree to use your best efforts to see that any user of the Software licensed hereunder complies with this Agreement. You agree that you are receiving a copy of the Software by license only and not by sale and that the "first sale" doctrine of 17 U.S.C. S109 does not apply to your receipt or use of the Software.\par +\par +5.\tab NO WARRANTIES. ID DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE WITH RESPECT TO THE SOFTWARE. ID DOES NOT WARRANT THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR FREE OR THAT THE SOFTWARE WILL MEET YOUR SPECIFIC REQUIREMENTS. ADDITIONAL STATEMENTS, WHETHER ORAL OR WRITTEN, DO NOT CONSTITUTE WARRANTIES BY ID AND SHOULD NOT BE RELIED UPON. THIS SECTION 5. SHALL SURVIVE CANCELLATION OR TERMINATION OF THIS AGREEMENT.\par +\par +6.\tab Governing Law, Venue, Indemnity and Liability Limitation. This Agreement shall be construed in accordance with and governed by the applicable laws of the State of Texas and applicable United States federal law. Copyright and other proprietary matters will be governed by United States laws and international treaties. Exclusive venue for all litigation regarding this Agreement shall be in Dallas County, Texas and you agree to submit to the jurisdiction of the courts in Dallas, Texas for any such litigation. You agree to indemnify, defend and hold harmless ID and ID's officers, employees, directors, agents, licensees (excluding you), successors and assigns from and against all losses, lawsuits, damages, causes of action and claims relating to and/or arising from your breach of this Agreement and/or your distribution or other use of the Software. You agree that your unauthorized use of the Software, or any part thereof, may immediately and irreparably damage ID such that ID could not be adequately compensated solely by a monetary award and that at ID's option ID shall be entitled to an injunctive order, in addition to all other available remedies including a monetary award, appropriately restraining and/or prohibiting such unauthorized use without the necessity of ID posting bond or other security. IN ANY CASE, ID AND ID'S OFFICERS, EMPLOYEES, DIRECTORS, AGENTS, LICENSEES, SUBLICENSEES, SUCCESSORS AND ASSIGNS SHALL NOT BE LIABLE FOR LOSS OF DATA, LOSS OF PROFITS, LOST SAVINGS, SPECIAL, INCIDENTAL, CONSEQUENTIAL, INDIRECT, PUNITIVE OR OTHER SIMILAR DAMAGES ARISING FROM ANY ALLEGED CLAIM FOR BREACH OF WARRANTY, BREACH OF CONTRACT, NEGLIGENCE, STRICT PRODUCT LIABILITY, OR OTHER LEGAL THEORY EVEN IF ID OR ITS AGENTS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES OR EVEN IF SUCH DAMAGES ARE FORESEEABLE, OR LIABLE FOR ANY CLAIM BY ANY OTHER PARTY. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so the above limitation or exclusion may not apply to you. This Section 6. shall survive cancellation or termination of this Agreement.\par +\par +7.\tab U.S. Government Restricted Rights. To the extent applicable, the United States Government shall only have those rights to use the Software as expressly stated and expressly limited and restricted in this Agreement, as provided in 48 C.F.R. SS 227.7201 through 227.7204, inclusive.\par +\par +8.\tab General Provisions. Neither this Agreement nor any part or portion hereof shall be assigned or sublicensed by you. ID may assign its rights under this Agreement in ID's sole discretion. Should any provision of this Agreement be held to be void, invalid, unenforceable or illegal by a court of competent jurisdiction, the validity and enforceability of the other provisions shall not be affected thereby. If any provision is determined to be unenforceable by a court of competent jurisdiction, you agree to a modification of such provision to provide for enforcement of the provision's intent, to the extent permitted by applicable law. Failure of ID to enforce any provision of this Agreement shall not constitute or be construed as a waiver of such provision or of the right to enforce such provision. Immediately upon your failure to comply with or breach of any term or provision of this Agreement, THIS AGREEMENT AND YOUR LICENSE SHALL AUTOMATICALLY TERMINATE, WITHOUT NOTICE, AND ID MAY PURSUE ALL RELIEF AND REMEDIES AGAINST YOU WHICH ARE AVAILABLE UNDER APPLICABLE LAW AND/OR THIS AGREEMENT. In the event this Agreement is terminated, you shall have no right to use the Software, in any manner, and you shall immediately destroy all copies of the Software in your possession, custody or control.\par +\par +YOU ACKNOWLEDGE THAT YOU HAVE READ THIS AGREEMENT, YOU UNDERSTAND THIS AGREEMENT, AND UNDERSTAND THAT BY CONTINUING THE INSTALLATION OF THE SOFTWARE, BY LOADING OR RUNNING THE SOFTWARE, OR BY PLACING OR COPYING THE SOFTWARE ONTO YOUR COMPUTER HARD DRIVE OR RAM, YOU AGREE TO BE BOUND BY THE TERMS AND CONDITIONS OF THIS AGREEMENT. YOU FURTHER AGREE THAT, EXCEPT FOR WRITTEN SEPARATE AGREEMENTS BETWEEN ID AND YOU, THIS AGREEMENT IS A COMPLETE AND EXCLUSIVE STATEMENT OF THE RIGHTS AND LIABILITIES OF THE PARTIES HERETO. THIS AGREEMENT SUPERSEDES ALL PRIOR ORAL AGREEMENTS, PROPOSALS OR UNDERSTANDINGS, AND ANY OTHER COMMUNICATIONS BETWEEN ID AND YOU RELATING TO THE SUBJECT MATTER OF THIS AGREEMENT.\par +\par +\par +} + \ No newline at end of file diff --git a/setup/license.txt b/setup/license.txt new file mode 100644 index 00000000..f69c934b --- /dev/null +++ b/setup/license.txt @@ -0,0 +1,45 @@ + LIMITED USE SOFTWARE LICENSE AGREEMENT + +This Limited Use Software License Agreement (the "Agreement") is a legal agreement between you, the end-user, and Id Software, Inc. ("ID"). BY CONTINUING THE INSTALLATION OF THIS SOFTWARE (THE "SOFTWARE"), BY LOADING OR RUNNING THE SOFTWARE, OR BY PLACING OR COPYING THE SOFTWARE ONTO YOUR COMPUTER HARD DRIVE, COMPUTER RAM OR OTHER STORAGE, YOU ARE AGREEING TO BE BOUND BY THE TERMS OF THIS AGREEMENT. + +1. Grant of License. Subject to the terms and provisions of this Agreement, ID grants to you the non-exclusive and limited right to use the Software only in executable or object code form. The term "Software" includes all elements of the Software. You are not receiving any ownership or proprietary right, title or interest in or to the Software or the copyrights, trademarks, or other rights related thereto. For purposes of the first sentence of this section, "use" means loading the Software into RAM and/or onto computer hard drive, as well as installation of the Software on a hard disk or other storage device and means the uses permitted in section 3. hereinbelow. You agree that the Software will not be shipped, transferred or exported into any country in violation of the U.S. Export Administration Act (or any other law governing such matters) by you or anyone at your direction and that you will not utilize and will not authorize anyone to utilize, in any other manner, the Software in violation of any applicable law. The Software shall not be downloaded or otherwise exported or reexported into (or to a national or resident of) any country to which the U.S. has embargoed goods or to anyone or into any country who/which are prohibited, by applicable law, from receiving such property. + +2. Prohibitions. You, whether directly or indirectly, shall not do any of the following acts: + +a. rent the Software; + +b. sell the Software; + +c. lease or lend the Software; + +d. distribute the Software (except as permitted by section 3. hereinbelow); + +e. in any other manner and through any medium whatsoever commercially exploit the Software or use the Software for any commercial purpose; + +f. disassemble, reverse engineer, decompile, modify or alter the Software; + +g. translate the Software; + +h. reproduce or copy the Software (except as permitted by section 3. hereinbelow); + +i. publicly display the Software; + +j. prepare or develop derivative works based upon the Software; or + +k. remove or alter any legal notices or other markings or legends, such as trademark and copyright notices, affixed on or within the Software. + + +3. Permitted Uses. So long as this Agreement accompanies each copy you make of the Software, and so long as you fully comply, at all times, with this Agreement, ID grants to you the non-exclusive and limited right to distribute copies of the Software free of charge for non-commercial purposes by electronic means only and the non-exclusive and limited right to use the Software to create your own modifications for operation only with the full version of the software game QUAKE III ARENA; provided, however, you shall not make any modifications unless and until you have agreed to be bound by the terms of the LIMITED USE SOFTWARE LICENSE AGREEMENT which accompanies the full version of QUAKE III ARENA. Other than the electronic copies permitted above, you may make only the following copies of the Software: (i) you may copy the Software onto your computer hard drive; (ii) you may copy the Software from your computer hard drive into your computer RAM; and (iii) you may make one (1) "back-up" or archival copy of the Software on one (1) hard disk. You shall not use, copy or distribute the Software in any infringing manner or in any manner which violates any law or third party right and you shall not distribute the Software together with any material which infringes against any third party right or which is libelous, defamatory, obscene, false, misleading, or otherwise illegal or unlawful. ID reserves all rights not granted in this Agreement, including, without limitation, all rights to ID's trademarks. You shall not commercially distribute the Software. + +4. Intellectual Property Rights. The Software and all copyrights, trademarks and all other conceivable intellectual property rights related to the Software are owned by ID and are protected by United States copyright laws, international treaty provisions and all applicable law, such as the Lanham Act. You must treat the Software like any other copyrighted material, as required by 17 U.S.C. §101 et seq. and other applicable law. You agree to use your best efforts to see that any user of the Software licensed hereunder complies with this Agreement. You agree that you are receiving a copy of the Software by license only and not by sale and that the "first sale" doctrine of 17 U.S.C. §109 does not apply to your receipt or use of the Software. + +5. NO WARRANTIES. ID DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE WITH RESPECT TO THE SOFTWARE. ID DOES NOT WARRANT THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR FREE OR THAT THE SOFTWARE WILL MEET YOUR SPECIFIC REQUIREMENTS. ADDITIONAL STATEMENTS, WHETHER ORAL OR WRITTEN, DO NOT CONSTITUTE WARRANTIES BY ID AND SHOULD NOT BE RELIED UPON. THIS SECTION 5. SHALL SURVIVE CANCELLATION OR TERMINATION OF THIS AGREEMENT. + +6. Governing Law, Venue, Indemnity and Liability Limitation. This Agreement shall be construed in accordance with and governed by the applicable laws of the State of Texas and applicable United States federal law. Copyright and other proprietary matters will be governed by United States laws and international treaties. Exclusive venue for all litigation regarding this Agreement shall be in Dallas County, Texas and you agree to submit to the jurisdiction of the courts in Dallas, Texas for any such litigation. You agree to indemnify, defend and hold harmless ID and ID's officers, employees, directors, agents, licensees (excluding you), successors and assigns from and against all losses, lawsuits, damages, causes of action and claims relating to and/or arising from your breach of this Agreement and/or your distribution or other use of the Software. You agree that your unauthorized use of the Software, or any part thereof, may immediately and irreparably damage ID such that ID could not be adequately compensated solely by a monetary award and that at ID's option ID shall be entitled to an injunctive order, in addition to all other available remedies including a monetary award, appropriately restraining and/or prohibiting such unauthorized use without the necessity of ID posting bond or other security. IN ANY CASE, ID AND ID'S OFFICERS, EMPLOYEES, DIRECTORS, AGENTS, LICENSEES, SUBLICENSEES, SUCCESSORS AND ASSIGNS SHALL NOT BE LIABLE FOR LOSS OF DATA, LOSS OF PROFITS, LOST SAVINGS, SPECIAL, INCIDENTAL, CONSEQUENTIAL, INDIRECT, PUNITIVE OR OTHER SIMILAR DAMAGES ARISING FROM ANY ALLEGED CLAIM FOR BREACH OF WARRANTY, BREACH OF CONTRACT, NEGLIGENCE, STRICT PRODUCT LIABILITY, OR OTHER LEGAL THEORY EVEN IF ID OR ITS AGENTS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES OR EVEN IF SUCH DAMAGES ARE FORESEEABLE, OR LIABLE FOR ANY CLAIM BY ANY OTHER PARTY. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so the above limitation or exclusion may not apply to you. This Section 6. shall survive cancellation or termination of this Agreement. + +7. U.S. Government Restricted Rights. To the extent applicable, the United States Government shall only have those rights to use the Software as expressly stated and expressly limited and restricted in this Agreement, as provided in 48 C.F.R. §§ 227.7201 through 227.7204, inclusive. + +8. General Provisions. Neither this Agreement nor any part or portion hereof shall be assigned or sublicensed by you. ID may assign its rights under this Agreement in ID's sole discretion. Should any provision of this Agreement be held to be void, invalid, unenforceable or illegal by a court of competent jurisdiction, the validity and enforceability of the other provisions shall not be affected thereby. If any provision is determined to be unenforceable by a court of competent jurisdiction, you agree to a modification of such provision to provide for enforcement of the provision's intent, to the extent permitted by applicable law. Failure of ID to enforce any provision of this Agreement shall not constitute or be construed as a waiver of such provision or of the right to enforce such provision. Immediately upon your failure to comply with or breach of any term or provision of this Agreement, THIS AGREEMENT AND YOUR LICENSE SHALL AUTOMATICALLY TERMINATE, WITHOUT NOTICE, AND ID MAY PURSUE ALL RELIEF AND REMEDIES AGAINST YOU WHICH ARE AVAILABLE UNDER APPLICABLE LAW AND/OR THIS AGREEMENT. In the event this Agreement is terminated, you shall have no right to use the Software, in any manner, and you shall immediately destroy all copies of the Software in your possession, custody or control. + +YOU ACKNOWLEDGE THAT YOU HAVE READ THIS AGREEMENT, YOU UNDERSTAND THIS AGREEMENT, AND UNDERSTAND THAT BY CONTINUING THE INSTALLATION OF THE SOFTWARE, BY LOADING OR RUNNING THE SOFTWARE, OR BY PLACING OR COPYING THE SOFTWARE ONTO YOUR COMPUTER HARD DRIVE OR RAM, YOU AGREE TO BE BOUND BY THE TERMS AND CONDITIONS OF THIS AGREEMENT. YOU FURTHER AGREE THAT, EXCEPT FOR WRITTEN SEPARATE AGREEMENTS BETWEEN ID AND YOU, THIS AGREEMENT IS A COMPLETE AND EXCLUSIVE STATEMENT OF THE RIGHTS AND LIABILITIES OF THE PARTIES HERETO. THIS AGREEMENT SUPERSEDES ALL PRIOR ORAL AGREEMENTS, PROPOSALS OR UNDERSTANDINGS, AND ANY OTHER COMMUNICATIONS BETWEEN ID AND YOU RELATING TO THE SUBJECT MATTER OF THIS AGREEMENT. + diff --git a/setup/links.htm b/setup/links.htm new file mode 100644 index 00000000..8c3aac68 --- /dev/null +++ b/setup/links.htm @@ -0,0 +1,150 @@ + + + + + Related Links + + + + + + + +
+
+
+ + +
+

Radiant links, news, tutorials, forums & help

+
+
+ +

+ General Links +

    + - + GtkRadiant start page + +
    - + GtkRadiant Forum on MapCenter + +
    - + q3map2 support forum + +
    - + Download selector + +
    - + Qeradiant.com Main Website, News and Stuff + +
    - + Qeradiant.com File Section + +
    - + The FAQ + +
+

+ +

+ Quake III Arena, Quake III: Team Arena +

    + - + Quake 3 section on Qeradiant.com + +
    - + Level Editing Forums on Mapcenter + +
    - + Quake III Level Editing Forum on Quake3World + +
    - + The BFLT .. all the Q3 editing links known to man. +
+

+ +

+ Return To Castle Wolfenstein +

    + - + Return To Castle Wolfenstein Editing Forum on PlanetWolfenstein + +
    - + Level Editing Forums on Mapcenter + +
+

+ Bug reporting, testing, suggestions, flaming and developer information: +

    + - + Zerowing: mailing lists, CVS information, test builds.. + +
    - + GtkRadiant bug tracker (bugzilla) + +
+

+ IRC +

    + - + IRC #qeradiant on irc.telefragged.com + +
+

+ +

+ Plugins +

    + - + BobToolz - multipurpose quake3 mappers tool
    +
    - + PrtView - portal viewer plugin
    +
    - + Curry- shader preview and editing
    +
    - + Pk3Man- easy pk3 file creation
    +
    - + TexTool Documentation +
+

+ +

+ I forgot something Tell me! +

+ +

+ + Quake III Arena © 1999 Id Software, Inc. All Rights Reserved. + Quake III Arena ®, QIIIA ®, and the id Software ® name + are trademarks of Id Software, Inc. + in all countries in which this game is distributed. The id ® logo, Q ®, + QII ®, and Quake ®, are registered trademarks of Id Software, Inc. in + the United States. Linux is a registered + trademark of Linus Torvalds. All other trademarks and trade names are properties + of their respective owners. + +

+ +
+ + +
+ Last updated: Jan 21, 2002   +
+
+ +
+
+ + + diff --git a/setup/linux/Help/DocsArt/toolback.jpg b/setup/linux/Help/DocsArt/toolback.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fa5612272819325272e97ecefcd2bfd144411c87 GIT binary patch literal 364 zcmex=&Gk zAt<%XO2N=b-^fzm(nLWcz|UQwAh9GlL%~qbNK-)}urx6zv!qhNR>9I#M + + +Quake III Arena SDK + + + + +

+See the tools/ directory for documentation files.. this page is outdated. +

+ +

+ +
README.Q3SDK +
General links page +
+

+ +
+ +

+Quake III Arena © 1999 Id Software, Inc. All Rights Reserved. +Quake III Arena ®, QIIIA ®, and the id Software ® name are +trademarks of Id Software, Inc. in all countries in which this game is +distributed. The id ® logo, Q ®, QII ®, and Quake ®, are registered trademarks +of Id Software, Inc. in the United States. +Linux is a registered trademark of Linus Torvalds. All other trademarks and trade names are +properties of their respective owners. +

+ + + + diff --git a/setup/linux/Help/Q3A_EULA.txt b/setup/linux/Help/Q3A_EULA.txt new file mode 100644 index 00000000..50e2638e --- /dev/null +++ b/setup/linux/Help/Q3A_EULA.txt @@ -0,0 +1,234 @@ + +LIMITED USE SOFTWARE LICENSE AGREEMENT + + + +This Limited Use Software License Agreement (the "Agreement") is a legal +agreement between you, the end-user, and Id Software, Inc. ("ID"). BY +CONTINUING THE INSTALLATION OF THIS GAME DEMO PROGRAM ENTITLED QUAKE III: +ARENA (THE "SOFTWARE"), BY LOADING OR RUNNING THE SOFTWARE, OR BY PLACING +OR COPYING THE SOFTWARE ONTO YOUR COMPUTER HARD DRIVE, COMPUTER RAM OR +OTHER STORAGE, YOU ARE AGREEING TO BE BOUND BY THE TERMS OF THIS +AGREEMENT. + + + +1. Grant of License. Subject to the terms and provisions of this +Agreement, ID grants to you the non-exclusive and limited right to use the +Software only in executable or object code form. The term "Software" +includes all elements of the Software, including, without limitation, data +files and screen displays. You are not receiving any ownership or +proprietary right, title or interest in or to the Software or the +copyright, trademarks, or other rights related thereto. For purposes of +this section, "use" means loading the Software into RAM and/or onto +computer hard drive, as well as installation of the Software on a hard +disk or other storage device and means the uses permitted in section 3. +hereinbelow. You agree that the Software will not be shipped, +transferred or exported into any country in violation of the U.S. Export +Administration Act (or any other law governing such matters) by you or +anyone at your direction and that you will not utilize and will not +authorize anyone to utilize, in any other manner, the Software in +violation of any applicable law. The Software may not be downloaded +or otherwise exported or exported into (or to a national or resident +of) any country to which the U.S. has embargoed goods or to anyone +or into any country who/which are prohibited, by applicable law, from +receiving such property. + + + +2. Prohibitions. You, either directly or indirectly, shall not do +any of the following acts: + + + +a. rent the Software; + + + +b. sell the Software; + + + +c. lease or lend the Software; + + + +d. offer the Software on a "pay-per-play" basis; + + + +e. distribute the Software (except as permitted by section 3. +hereinbelow); + + + +f. in any other manner and through any medium whatsoever +commercially exploit the Software or use the Software for any commercial +purpose; + + + +g. disassemble, reverse engineer, decompile, modify or alter the +Software including, without limitation, creating or developing extra or +add-on levels for the Software; + + + +h. translate the Software; + + + +i. reproduce or copy the Software (except as permitted by section +3. hereinbelow); + + + +j. publicly display the Software; + + + +k. prepare or develop derivative works based upon the Software; or + + + +l. remove or alter any legal notices or other markings or +legends, such as trademark and copyright notices, affixed on or within +the Software. + + + +3. Permitted Distribution and Copying. So long as this Agreement +accompanies each copy you make of the Software, and so long as you fully +comply, at all times, with this Agreement, ID grants to you the +non-exclusive and limited right to copy the Software and to distribute +such copies of the Software free of charge for non-commercial purposes +which shall include the free of charge distribution of copies of the +Software as mounted on the covers of magazines; provided, however, you +shall not copy or distribute the Software in any infringing manner or +in any manner which violates any law or third party right and you shall +not distribute the Software together with any material which is +infringing, libelous, defamatory, obscene, false, misleading, or +otherwise illegal or unlawful. You agree to label conspicuously as +"SHAREWARE" or "DEMO" each CD or other non-electronic copy of the +Software that you make and distribute. ID reserves all rights not +granted in this Agreement. You shall not commercially distribute the +Software unless you first enter into a separate contract with ID, a +copy of which you may request, but which ID may decline to execute. +For more information visit www.quake3arena.com. + + + +4. Intellectual Property Rights. The Software and all copyrights, +trademarks and all other conceivable intellectual property rights related +to the Software are owned by ID and are protected by United States +copyright laws, international treaty provisions and all applicable law, +such as the Lanham Act. You must treat the Software like any other +copyrighted material, as required by 17 U.S.C., §101 et seq. and other +applicable law. You agree to use your best efforts to see that any user +of the Software licensed hereunder complies with this Agreement. You +agree that you are receiving a copy of the Software by license only +and not by sale and that the "first sale" doctrine of 17 U.S.C. §109 +does not apply to your receipt or use of the Software. + + + +5. NO WARRANTIES. ID DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS OR +IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE WITH RESPECT TO THE +SOFTWARE. ID DOES NOT WARRANT THAT THE OPERATION OF THE SOFTWARE WILL BE +UNINTERRUPTED OR ERROR FREE OR THAT THE SOFTWARE WILL MEET YOUR SPECIFIC +REQUIREMENTS. ADDITIONAL STATEMENTS SUCH AS PRESENTATIONS, WHETHER ORAL +OR WRITTEN, DO NOT CONSTITUTE WARRANTIES BY ID AND SHOULD NOT BE RELIED +UPON. THIS SECTION 5. SHALL SURVIVE CANCELLATION OR TERMINATION OF THIS +AGREEMENT. + + + +6. Governing Law, Venue, Indemnity and Liability Limitation. This +Agreement shall be construed in accordance with and governed by the +applicable laws of the State of Texas and applicable United States federal +law. Copyright and other proprietary matters will be governed by United +States laws and international treaties. Exclusive venue for all +litigation regarding this Agreement shall be in Dallas County, Texas +and you agree to submit to the jurisdiction of the courts in Dallas, +Texas for any such litigation. You agree to indemnify, defend and hold +harmless ID and ID's officers, employees, directors, agents, licensees +(excluding you), successors and assigns from and against all losses, +lawsuits, damages, causes of action and claims relating to and/or +arising from your breach of this Agreement. You agree that your +unauthorized use of the Software, or any part thereof, may immediately +and irreparably damage ID such that ID could not be adequately +compensated solely by a monetary award and that at ID's option ID shall +be entitled to an injunctive order, in addition to all other available +remedies including a monetary award, appropriately restraining and/or +prohibiting such unauthorized use without the necessity of ID posting +bond or other security. IN ANY CASE, ID AND ID'S OFFICERS, EMPLOYEES, +DIRECTORS, AGENTS, LICENSEES, SUBLICENSEES, SUCCESSORS AND ASSIGNS +SHALL NOT BE LIABLE FOR LOSS OF DATA, LOSS OF PROFITS, LOST SAVINGS, +SPECIAL, INCIDENTAL, CONSEQUENTIAL, INDIRECT, PUNITIVE OR OTHER SIMILAR +DAMAGES ARISING FROM ANY ALLEGED CLAIM FOR BREACH OF WARRANTY, BREACH +OF CONTRACT, NEGLIGENCE, STRICT PRODUCT LIABILITY, OR OTHER LEGAL +THEORY EVEN IF ID OR ITS AGENT HAVE BEEN ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES OR EVEN IF SUCH DAMAGES ARE FORESEEABLE, OR LIABLE +FOR ANY CLAIM BY ANY OTHER PARTY. Some jurisdictions do not allow +the exclusion or limitation of incidental or consequential damages, +so the above limitation or exclusion may not apply to you. This +Section 6. shall survive cancellation or termination of this Agreement. + + + +7. U.S. Government Restricted Rights. To the extent applicable, +the United States Government shall only have those rights to use the +Software as expressly stated and expressly limited and restricted in +this Agreement, as provided in 48 C.F.R. §§ 227.7201 through 227.7204, +inclusive. + + + +8. General Provisions. Neither this Agreement nor any part or +portion hereof shall be assigned or sublicensed by you. ID may assign its +rights under this Agreement in ID's sole discretion. Should any provision +of this Agreement be held to be void, invalid, unenforceable or illegal by +a court of competent jurisdiction, the validity and enforceability of the +other provisions shall not be affected thereby. If any provision is +determined to be unenforceable by a court of competent jurisdiction, you +agree to a modification of such provision to provide for enforcement of +the provision's intent, to the extent permitted by applicable law. +Failure of ID to enforce any provision of this Agreement shall not +constitute or be construed as a waiver of such provision or of the right +to enforce such provision. Immediately upon your failure to comply with +or breach of any term or provision of this Agreement, THIS AGREEMENT +AND YOUR LICENSE SHALL AUTOMATICALLY TERMINATE, WITHOUT NOTICE, AND ID +MAY PURSUE ALL RELIEF AND REMEDIES AGAINST YOU WHICH ARE AVAILABLE UNDER +APPLICABLE LAW AND/OR THIS AGREEMENT. In the event this Agreement is +terminated, you shall have no right to use the Software, in any manner, +and you shall immediately destroy all copies of the Software in your +possession, custody or control. + + + +YOU ACKNOWLEDGE THAT YOU HAVE READ THIS AGREEMENT, YOU UNDERSTAND THIS +AGREEMENT, AND UNDERSTAND THAT BY CONTINUING THE INSTALLATION OF THE +SOFTWARE, BY LOADING OR RUNNING THE SOFTWARE, OR BY PLACING OR COPYING +THE SOFTWARE ONTO YOUR COMPUTER HARD DRIVE OR RAM, YOU AGREE TO BE BOUND +BY THE TERMS AND CONDITIONS OF THIS AGREEMENT. YOU FURTHER AGREE THAT, +EXCEPT FOR WRITTEN SEPARATE AGREEMENTS BETWEEN ID AND YOU, THIS +AGREEMENT IS A COMPLETE AND EXCLUSIVE STATEMENT OF THE RIGHTS AND +LIABILITIES OF THE PARTIES HERETO. THIS AGREEMENT SUPERSEDES ALL PRIOR +ORAL AGREEMENTS, PROPOSALS OR UNDERSTANDINGS, AND ANY OTHER +COMMUNICATIONS BETWEEN ID AND YOU RELATING TO THE SUBJECT MATTER OF +THIS AGREEMENT. + + + + + + + + + + + + + + diff --git a/setup/linux/README b/setup/linux/README new file mode 100644 index 00000000..8199851d --- /dev/null +++ b/setup/linux/README @@ -0,0 +1,35 @@ + GtkRadiant Linux + =-=-=-=-=-=-=-=- + + +Installation Instructions +=-=-=-=-=-=-=-=-=-=-=-=-= + +If you are installing a full setup, it is recommended that you install in an empty directory (clear up any previous GtkRadiant installation in the target directory first). If you are installing an update, you need to have a working installation of GtkRadiant already, and point the installer to the right location for the update. + +Requirements +=-=-=-=-=-=- + +There are several required libraries that GtkRadiant and it's tools will try to link against. We try to comply with the Linux Standard Base ABI-wise regarding the binary builds we distribute (http://www.linuxbase.org/). Please let us know if this is not the case. + +- You need to have the full native Linux versions of the games we support. +- Linux 2.4 kernel recommanded (old 2.2 and unstable kernels untested) +- glibc 2.1 or above (2.2 recommended) +- XFree86 4 with hardware accelerated OpenGL support (others untested) + +You need the following dynamic libraries: + + * GTK+ 1.2 http://www.gtk.org + * zlib http://www.cdrom.com/pub/infozip/zlib/ + * libxml2 http://xmlsoft.org/ + * libpng http://www.libpng.org/pub/png/libpng.html + +Any decent distribution should provide packages for those, and in most cases have them installed by default. + +Misc +=-=- + +Once installed, use GtkRadiant's Help menu for ChangeLog, Credits and Links + +-- +The GtkRadiant team diff --git a/setup/linux/all.cf b/setup/linux/all.cf new file mode 100644 index 00000000..e5b7938c --- /dev/null +++ b/setup/linux/all.cf @@ -0,0 +1,10 @@ +# Linux build config +# all.cf +# +# includes core + wolfpack + q3pack + q2pack + her2pack + +$DO_CORE=1; +$DO_GAME_Q3=1; +$DO_GAME_WOLF=1; +$DO_GAME_Q2=1; +$DO_GAME_HER2=1; diff --git a/setup/linux/bspc b/setup/linux/bspc new file mode 100644 index 0000000000000000000000000000000000000000..0d3131f0bb8e67b04ce801038b006e38e7626367 GIT binary patch literal 417924 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�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%0Ax)K|BNZ=lVM9=dze=>3*hsCrbL2ED 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 bsMgU-ij=8fPw1Y7T|eQV)^46quD$;Oo>r<# literal 0 HcmV?d00001 diff --git a/setup/linux/bug750/loki_setup.patch b/setup/linux/bug750/loki_setup.patch new file mode 100644 index 00000000..f01adbba --- /dev/null +++ b/setup/linux/bug750/loki_setup.patch @@ -0,0 +1,606 @@ +Index: CHANGES +=================================================================== +RCS file: /cvs/cvsroot/loki_setup/CHANGES,v +retrieving revision 1.77 +diff -u -r1.77 CHANGES +--- CHANGES 2003/02/27 06:16:01 1.77 ++++ CHANGES 2003/03/29 10:46:13 +@@ -2,6 +2,10 @@ + Stephane Peter (Codehost) - Tue Feb 11 20:33:32 PST 2003 + * Added the 'environment' tag to store the values of + environment variables. ++TTimo (qeradiant.com) - Mon Feb 10 12:17:08 CET 2003 ++ * Added SETUP_COMPONENT_PATH env to run scripts ++TTimo (qeradiant.com) - Sun Jan 26 23:36:06 CET 2003 ++ * Ported over our subcomponent code + Stephane Peter (Codehost) - Fri Dec 6 16:31:27 PST 2002 + * Do not display the main GTK dialog until we're done with the EULAs. + * Various fixes for menu items +Index: Makefile.in +=================================================================== +RCS file: /cvs/cvsroot/loki_setup/Makefile.in,v +retrieving revision 1.18 +diff -u -r1.18 Makefile.in +--- Makefile.in 2003/03/26 05:45:10 1.18 ++++ Makefile.in 2003/03/29 10:46:13 +@@ -126,7 +126,7 @@ + strip image/setup.data/bin/$(os)/$(arch)/$(libc)/setup.gtk; \ + $(BRANDELF) -t $(os) image/setup.data/bin/$(os)/$(arch)/$(libc)/setup.gtk; \ + else \ +- echo No directory to copy the binary files to.; \ ++ echo image/setup.data/bin/$(os)/$(arch)/$(libc): No directory to copy the binary files to.; \ + fi + + install-image: all +@@ -151,7 +151,7 @@ + cp setup.gtk $(IMAGE)/setup.data/bin/$(os)/$(arch)/$(libc); \ + strip $(IMAGE)/setup.data/bin/$(os)/$(arch)/$(libc)/setup.gtk; \ + else \ +- echo No directory to copy the binary files to.; \ ++ echo $(IMAGE)/setup.data/bin/$(os)/$(arch)/$(libc): No directory to copy the binary files to.; \ + fi + + # Pretty LPP-specific +@@ -161,7 +161,7 @@ + strip $(IMAGE)/bin/$(os)/$(arch)/check; \ + cp check.glade $(IMAGE)/misc/; \ + else \ +- echo No directory to copy the binary files to.; \ ++ echo $(IMAGE)/bin/$(os)/$(arch): No directory to copy the binary files to.; \ + fi + + # Copy loki_uninstall and the required files +@@ -176,7 +176,7 @@ + cp $$file $$path; \ + done; \ + else \ +- echo No directory to copy the binary files to.; \ ++ echo $(IMAGE)/bin/$(os)/$(arch): No directory to copy the binary files to.; \ + fi + + install-loki_uninstall: loki_uninstall +@@ -191,7 +191,7 @@ + cp $$file $$path; \ + done; \ + else \ +- echo No directory to copy the binary files to.; \ ++ echo $(IMAGE)/loki_uninstall/bin/$(arch)/$(libc): No directory to copy the binary files to.; \ + fi + @if [ -d $(UPDATES) ]; then \ + rm -rf $(UPDATES)/bin-$(arch)-$(UNINSTALL_VERSION)/; \ +Index: copy.c +=================================================================== +RCS file: /cvs/cvsroot/loki_setup/copy.c,v +retrieving revision 1.65 +diff -u -r1.65 copy.c +--- copy.c 2003/02/27 06:16:01 1.65 ++++ copy.c 2003/03/29 10:46:16 +@@ -343,7 +343,7 @@ + *slash = '\0'; + } + push_curdir(dir); +- if ( run_script(info, buf, 0) == 0 ) { ++ if ( run_script(info, buf, NULL, 0) == 0 ) { + const char *target = xmlGetProp(node, "target"); + if ( target ) { + char targetpath[PATH_MAX]; +@@ -677,7 +677,7 @@ + if ( ! update(info, _("Running script"), 0, 0, current_option_txt) ) + return 0; + } +- return(run_script(info, script, -1)); ++ return(run_script(info, script, dest, -1)); + } + + ssize_t copy_node(install_info *info, xmlNodePtr node, const char *dest, +@@ -766,6 +766,7 @@ + { + ssize_t size, copied; + char tmppath[PATH_MAX]; ++ const char *component_dest; + + size = 0; + while ( node ) { +@@ -828,6 +829,58 @@ + } + current_component = NULL; /* Out of the component */ + } ++ } ++ /* Parse subcomponents */ ++ else if (!strcmp(node->name, "subcomponent")) { ++ const char *name, *version; ++ xmlNodePtr child; ++ name = xmlGetProp(node, "name"); ++ if (!name) ++ log_fatal(_("SubComponent element must have a name")); ++ version = xmlGetProp(node, "version"); ++ if (!version) ++ { ++ log_warning(_("SubComponent doesn't have a version")); ++ version = strdup("noversion"); ++ } ++ child = node->childs; ++ while(child) ++ { ++ if(!strcmp(child->name, "option")) ++ { ++ /* only run if it has been actually selected for install */ ++ const char *install; ++ install = xmlGetProp(child, "install"); ++ if (install && !strcmp(install,"true")) ++ { ++ /* add this subcomponent as a standard component */ ++ current_component = add_component_entry(info, name, version, ++ (strcmp(xmlGetProp(child, "install"), "true") != 0) ? 0 : 1); ++ if(xmlGetProp(child, "path")) ++ { ++ /* if the path's been changed, use the path it was changed to */ ++ component_dest = xmlGetProp(child, "path"); ++ } ++ else ++ { ++ /* if the path hasn't been changed, install to the default path */ ++ component_dest = xmlGetProp(child, "default_path"); ++ } ++ // TODO: verify the install location ? ++ copied = copy_node(info, child, component_dest, update); ++ if ( copied > 0 ) ++ { ++ size += copied; ++ } ++ copied = copy_tree(info, child->childs, component_dest, update); ++ if(copied > 0) ++ { ++ size += copied; ++ } ++ } ++ } ++ child = child->next; ++ } + } else if ( ! strcmp(node->name, "environment") ) { + const char *prop = xmlGetProp(node, "var"); + if ( prop ) { +Index: gtk_ui.c +=================================================================== +RCS file: /cvs/cvsroot/loki_setup/gtk_ui.c,v +retrieving revision 1.78 +diff -u -r1.78 gtk_ui.c +--- gtk_ui.c 2003/03/24 00:47:16 1.78 ++++ gtk_ui.c 2003/03/29 10:46:20 +@@ -143,6 +143,7 @@ + static GladeXML *setup_glade = NULL; + static GladeXML *setup_glade_readme = NULL; + static GladeXML *setup_glade_license = NULL; ++static GladeXML *setup_glade_subcomponent = NULL; + static int cur_state; + static install_info *cur_info; + static int diskspace; +@@ -159,6 +160,7 @@ + void setup_destroy_view_readme_slot(GtkWidget*, gpointer); + static yesno_answer gtkui_prompt(const char*, yesno_answer); + static void gtkui_abort(install_info *info); ++static int gtkui_dir_prompt (install_info *info, const char *message, char *path); + + static int iterate_for_state(void) + { +@@ -1189,6 +1191,189 @@ + } + } + ++/*************** subcomponent *********************/ ++ ++void setup_button_subcomponent_browse(GtkWidget *widget, gpointer func_data) ++{ ++ GtkWidget *entry = (GtkWidget *)func_data; ++ xmlNodePtr node = gtk_object_get_data(GTK_OBJECT(widget), "node"); ++ char *widget_data = gtk_object_get_data(GTK_OBJECT(widget), "data"); ++ char path[PATH_MAX]; ++ strncpy(path, gtk_entry_get_text(GTK_ENTRY(entry)), PATH_MAX); ++ ++ gtkui_dir_prompt(cur_info, widget_data, path); ++ ++ if(path[strlen(path)-1] != '/') ++ strcat(path, "/"); ++ ++ gtk_entry_set_text(GTK_ENTRY(entry), path); ++ ++ xmlSetProp(node, "path", path); ++} ++ ++void setup_subcomponent_toggle(GtkWidget *widget, gpointer func_data) ++{ ++ int state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); ++ GtkWidget *entry = gtk_object_get_data(GTK_OBJECT(widget), "entry"); ++ GtkWidget *button = gtk_object_get_data(GTK_OBJECT(widget), "button"); ++ xmlNodePtr node = gtk_object_get_data(GTK_OBJECT(widget), "node"); ++ ++ if(state) ++ { ++ gtk_widget_set_sensitive(GTK_WIDGET(entry), TRUE); ++ gtk_widget_set_sensitive(GTK_WIDGET(button), TRUE); ++ xmlSetProp(node, "install", "true"); ++ xmlSetProp(node, "path", gtk_entry_get_text(GTK_ENTRY(entry))); ++ } ++ else ++ { ++ gtk_widget_set_sensitive(GTK_WIDGET(entry), FALSE); ++ gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); ++ xmlSetProp(node, "install", "false"); ++ xmlSetProp(node, "path", ""); ++ } ++} ++ ++void subcomponent_update_entry(GtkWidget *widget, gpointer func_data) ++{ ++ xmlNodePtr node = gtk_object_get_data(GTK_OBJECT(widget), "node"); ++ char *path = malloc(strlen(gtk_entry_get_text(GTK_ENTRY(widget)))+2); ++ strcpy(path, gtk_entry_get_text(GTK_ENTRY(widget))); ++ ++ if (path[strlen(path)-1] != '/') ++ strcat(path, "/"); ++ ++ if(path) ++ xmlSetProp(node, "path", path); ++} ++ ++void setup_button_subcomponent(GtkWidget *widget, gpointer func_data) ++{ ++ xmlNodePtr node; ++ xmlNodePtr child; ++ GtkWidget *window, *frame, *w, *vbox, *hbox, ++ *check, *entry, *button, *sep; ++ const char *text; ++ char name[256]; ++ int count=0; ++ int install=0; ++ ++ install_info *info = (install_info *)func_data; ++ ++ setup_glade_subcomponent = glade_xml_new(SETUP_GLADE, "subcomponent_dialog"); ++ glade_xml_signal_autoconnect(setup_glade_subcomponent); ++ window = glade_xml_get_widget(setup_glade_subcomponent, "subcomponent_dialog"); ++ frame = glade_xml_get_widget(setup_glade_subcomponent, "subcomponent_frame"); ++ ++ w = glade_xml_get_widget(setup_glade_subcomponent, "subcomponent_button_cancel"); ++ gtk_widget_hide(w); ++ ++ gtk_widget_realize(window); ++ gtk_widget_realize(frame); ++ gtk_container_foreach(GTK_CONTAINER(frame), empty_container, frame); ++ ++ w = gtk_vbox_new(TRUE, 2); ++ gtk_container_add(GTK_CONTAINER(frame), w); ++ gtk_widget_show(w); ++ ++ gtk_object_set_data(GTK_OBJECT(window), "data", w); ++ ++ node = info->config->root->childs; ++ while(node != NULL && strcmp(node->name, "subcomponent")) ++ node = node->next; ++ if (!node) ++ log_fatal(_("subcomponent element not found")); ++ child = node->childs; ++ // subcomponent options ++ while(child != NULL) ++ { ++ if(!strcmp(child->name, "option")) ++ { ++ if(!strcmp(xmlGetProp(child, "install"), "true")) ++ install=1; ++ else ++ install=0; ++ ++ vbox = gtk_vbox_new(FALSE, 2); ++ gtk_box_pack_start(GTK_BOX(w), vbox, TRUE, FALSE, 0); ++ gtk_container_set_border_width(GTK_CONTAINER(vbox), 5); ++ gtk_widget_show(vbox); ++ ++ if(count) ++ { ++ sep = gtk_hseparator_new(); ++ gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0); ++ gtk_widget_show(sep); ++ } ++ ++ text = xmlNodeListGetString(info->config, child->childs, 1); ++ parse_line(&text, name, sizeof(name)); ++ ++ check = gtk_check_button_new_with_label(name); ++ gtk_box_pack_start(GTK_BOX(vbox), check, FALSE, FALSE, 0); ++ if(install) ++ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE); ++ gtk_signal_connect(GTK_OBJECT(check), "toggled", GTK_SIGNAL_FUNC(setup_subcomponent_toggle), NULL); ++ gtk_object_set_data(GTK_OBJECT(check), "name", name); ++ gtk_object_set_data(GTK_OBJECT(check), "node", child); ++ gtk_widget_show(check); ++ ++ hbox = gtk_hbox_new(FALSE, 2); ++ gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, FALSE, 0); ++ gtk_widget_show(hbox); ++ ++ entry = gtk_entry_new(); ++ //gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, FALSE, 0); ++ gtk_container_add(GTK_CONTAINER(hbox), entry); ++ if(!install) ++ gtk_widget_set_sensitive(GTK_WIDGET(entry), FALSE); ++ gtk_signal_connect(GTK_OBJECT(entry), "focus_out_event", GTK_SIGNAL_FUNC(subcomponent_update_entry), NULL); ++ gtk_object_set_data(GTK_OBJECT(entry), "name", name); ++ gtk_object_set_data(GTK_OBJECT(entry), "node", child); ++ gtk_widget_show(entry); ++ ++ if(xmlGetProp(child, "path")) ++ { ++ gtk_entry_set_text(GTK_ENTRY(entry), xmlGetProp(child, "path")); ++ } ++ else if (xmlGetProp(child, "default_path")) ++ { ++ gtk_entry_set_text(GTK_ENTRY(entry), xmlGetProp(child, "default_path")); ++ } ++ ++ ++ button = gtk_button_new_with_label("..."); ++ gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0); ++ gtk_object_set_data(GTK_OBJECT(button), "data", gtk_entry_get_text(GTK_ENTRY(entry))); ++ gtk_object_set_data(GTK_OBJECT(button), "node", child); ++ gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(setup_button_subcomponent_browse), entry); ++ if(!install) ++ gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); ++ gtk_widget_show(button); ++ ++ gtk_object_set_data(GTK_OBJECT(check), "entry", entry); ++ gtk_object_set_data(GTK_OBJECT(check), "button", button); ++ ++ count++; ++ } ++ child = child->next; ++ } ++ gtk_widget_show(window); ++ //gtk_window_set_modal(GTK_WINDOW(window), TRUE); ++} ++ ++void setup_button_subcomponent_cancel(GtkWidget *widget, gpointer func_data) ++{ ++ GtkWidget *window = glade_xml_get_widget(setup_glade_subcomponent, "subcomponent_dialog"); ++ gtk_widget_hide(window); ++} ++ ++void setup_button_subcomponent_ok(GtkWidget *widget, gpointer func_data) ++{ ++ GtkWidget *window = glade_xml_get_widget(setup_glade_subcomponent, "subcomponent_dialog"); ++ gtk_widget_hide(window); ++} ++ + /********** UI functions *************/ + + static install_state gtkui_init(install_info *info, int argc, char **argv, int noninteractive) +@@ -1496,7 +1681,13 @@ + parse_option(info, xmlGetProp(node, "name"), child, window, options, 0, NULL, 0, NULL); + } + } +- } ++ } else if ( ! strcmp(node->name, "subcomponent") ) { ++ GtkWidget *widget = gtk_button_new_with_label(xmlGetProp(node, "name")); ++ gtk_box_pack_start(GTK_BOX(options), GTK_WIDGET(widget), FALSE, FALSE, 5); ++ gtk_object_set_data(GTK_OBJECT(widget), "data", (gpointer)xmlGetProp(node, "name")); ++ gtk_signal_connect(GTK_OBJECT(widget), "clicked", GTK_SIGNAL_FUNC(setup_button_subcomponent), (gpointer)info); ++ gtk_widget_show(widget); ++ } + node = node->next; + } + init_install_path(); +@@ -1681,6 +1872,63 @@ + gtkui_idle(info); + } + ++static int dirname_loop; ++static int prompt_ret; ++ ++void store_dirname_slot(GtkFileSelection *selector, gpointer user_data) { ++ char *aux; ++ char *selected_dirname = (char *)user_data; ++ GtkWidget *parent; ++ ++ parent = gtk_widget_get_toplevel (GTK_WIDGET(selector)); ++ aux = gtk_file_selection_get_filename (GTK_FILE_SELECTION (parent)); ++ if (strlen(aux)) ++ strcpy(selected_dirname, aux); ++ ++ dirname_loop = 0; ++} ++ ++void abort_slot(GtkFileSelection *selector, gpointer user_data) { ++ dirname_loop = 0; ++ prompt_ret = 0; ++} ++ ++static int gtkui_dir_prompt (install_info *info, const char *message, char *path) ++{ ++ GtkWidget *selector; ++ char *aux; ++ ++ /* Create the selector */ ++ selector = gtk_file_selection_new(message); ++ ++ gtk_file_selection_set_filename(GTK_FILE_SELECTION(selector), path); ++ gtk_file_selection_hide_fileop_buttons (GTK_FILE_SELECTION(selector)); ++ ++ gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION(selector)->ok_button), ++ "clicked", GTK_SIGNAL_FUNC (store_dirname_slot), path); ++ ++ gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION(selector)->cancel_button), ++ "clicked", GTK_SIGNAL_FUNC (abort_slot), path); ++ ++ /* Display that dialog */ ++ ++ gtk_grab_add(selector); ++ gtk_widget_show (selector); ++ gtk_grab_remove(selector); ++ ++ dirname_loop = 1; ++ prompt_ret = 1; ++ ++ while (dirname_loop==1) ++ gtk_main_iteration(); ++ ++ aux = gtk_file_selection_get_filename (GTK_FILE_SELECTION(selector)); ++ ++ gtk_widget_destroy(selector); ++ ++ return prompt_ret; ++} ++ + int gtkui_okay(Install_UI *UI, int *argc, char ***argv) + { + extern int force_console; +@@ -1730,7 +1978,3 @@ + #endif + + #endif +- +- +- +- +Index: install.c +=================================================================== +RCS file: /cvs/cvsroot/loki_setup/install.c,v +retrieving revision 1.102 +diff -u -r1.102 install.c +--- install.c 2003/03/27 04:16:26 1.102 ++++ install.c 2003/03/29 10:46:24 +@@ -999,7 +999,7 @@ + log_fatal(_("XML: 'require' tag doesn't have a mandatory 'command' attribute")); + } else { + /* Launch the command */ +- if ( run_script(info, prop, 0) != 0 ) { ++ if ( run_script(info, prop, NULL, 0) != 0 ) { + /* We failed: print out error message */ + text = xmlNodeListGetString(info->config, node->childs, 1); + if(text) { +@@ -1139,7 +1139,7 @@ + return 0; + + /* Launch the command */ +- return run_script(info, txt, 0) == 0; ++ return run_script(info, txt, NULL, 0) == 0; + } + } + return 1; +@@ -1321,7 +1321,7 @@ + + if (GetPreUnInstall(info) && info->installed_bytes>0) { + snprintf(path, sizeof(path), "sh %s", GetPreUnInstall(info)); +- run_script(info, path, 0); ++ run_script(info, path, NULL, 0); + } + + if ( file_exists(info->install_path) ) { +@@ -1338,7 +1338,7 @@ + /* Do not run scripts if nothing was installed */ + if ( info->installed_bytes>0 ) { + for ( selem = opt->pre_script_list; selem; selem = selem->next ) { /* RPM pre-uninstall */ +- run_script(info, selem->script, 0); ++ run_script(info, selem->script, NULL, 0); + } + } + +@@ -1355,7 +1355,7 @@ + } + if ( info->installed_bytes>0 ) { + for ( selem = opt->post_script_list; selem; selem = selem->next ) { /* RPM post-uninstall */ +- run_script(info, selem->script, 0); ++ run_script(info, selem->script, NULL, 0); + } + } + +@@ -1373,7 +1373,7 @@ + } + if (GetPostUnInstall(info) && info->installed_bytes>0) { + snprintf(path, sizeof(path), "sh %s", GetPostUnInstall(info)); +- run_script(info, path, 0); ++ run_script(info, path, NULL, 0); + } + + if ( uninstall_generated ) { +@@ -1753,7 +1753,7 @@ + if ( ! restoring_corrupt() ) { + script = GetPreInstall(info); + if ( script ) { +- exitval = run_script(info, script, -1); ++ exitval = run_script(info, script, NULL, -1); + } + } + return exitval; +@@ -1766,7 +1766,7 @@ + if ( ! restoring_corrupt() ) { + script = GetPostInstall(info); + if ( script ) { +- exitval = run_script(info, script, -1); ++ exitval = run_script(info, script, NULL, -1); + } + } + return exitval; +@@ -1787,7 +1787,7 @@ + /* Run the command and set it to "true" if the return value is ok */ + str = xmlGetProp(child, "command"); + if ( str ) { +- cmd = run_script(info, str, 0); ++ cmd = run_script(info, str, NULL, 0); + xmlSetProp(child, "install", cmd ? "false" : "true"); + } else { + log_fatal(_("Missing 'command' attribute for an option")); +@@ -2213,12 +2213,15 @@ + } + + /* Run some shell script commands */ +-int run_script(install_info *info, const char *script, int arg) ++int run_script(install_info *info, const char *script, const char *dest, int arg) + { + char script_file[PATH_MAX]; + int fd; + int exitval; + char working_dir[PATH_MAX]; ++ ++ if (!dest) ++ dest = ""; + + /* We need to append the working directory onto the script name so + it can always be found. Do this only if the script file exists +@@ -2255,7 +2258,8 @@ + "SETUP_CDROMPATH=\"%s\"\n" + "SETUP_DISTRO=\"%s\"\n" + "SETUP_OPTIONTAGS=\"%s\"\n" +- "export SETUP_PRODUCTNAME SETUP_PRODUCTVER SETUP_INSTALLPATH SETUP_SYMLINKSPATH SETUP_CDROMPATH SETUP_DISTRO SETUP_OPTIONTAGS\n" ++ "SETUP_COMPONENT_PATH=\"%s\"\n" ++ "export SETUP_PRODUCTNAME SETUP_PRODUCTVER SETUP_INSTALLPATH SETUP_SYMLINKSPATH SETUP_CDROMPATH SETUP_DISTRO SETUP_OPTIONTAGS SETUP_COMPONENT_PATH\n" + "%s%s\n", + info->name, info->version, + info->install_path, +@@ -2263,6 +2267,7 @@ + info->cdroms_list ? info->cdroms_list->mounted : "", + info->distro ? distribution_symbol[info->distro] : "", + get_optiontags_string(info), ++ dest, + working_dir, script); + fchmod(fileno(fp),0755); /* Turn on executable bit */ + fclose(fp); +Index: install.h +=================================================================== +RCS file: /cvs/cvsroot/loki_setup/install.h,v +retrieving revision 1.65 +diff -u -r1.65 install.h +--- install.h 2003/02/27 06:16:01 1.65 ++++ install.h 2003/03/29 10:46:25 +@@ -353,7 +353,7 @@ + If 'arg' is >= 0, it is passed to the script as a numeric argument, + otherwise the install path is passed as a command line argument. + */ +-extern int run_script(install_info *info, const char *script, int arg); ++extern int run_script(install_info *info, const char *script, const char *dest, int arg); + + /* returns true if any deviant paths are not writable */ + char check_deviant_paths(xmlNodePtr node, install_info *info); +@@ -383,4 +383,3 @@ + + + #endif /* _install_h */ +- diff --git a/setup/linux/gtkradiant-1.5.0.spec b/setup/linux/gtkradiant-1.5.0.spec new file mode 100644 index 00000000..3f906abd --- /dev/null +++ b/setup/linux/gtkradiant-1.5.0.spec @@ -0,0 +1,206 @@ +%define name gtkradiant +%define version 1.5.0 +%define release 5 +%define installdir /opt/%{name} + +Summary: GtkRadiant Level Editor +Name: %{name} +Version: %{version} +Release: %{release} +Group: Applications/Editors +Copyright: Id Software +URL: www.qeradiant.com +Distribution: GtkRadiant +Packager: %{packager} +Prefix: %{installdir} +Source: %{name}-%{version}.tar.gz +Requires: gtk2 >= 2.4.0 , gtkglext >= 1.0.0 , libxml2 >= 2.0.0 , zlib >= 1.2.0 , libpng >= 1.2.0 , mhash >= 0.9.0 +BuildRequires: python >= 2.3.0 , gcc >= 3.0.0 , scons >= 0.96 , gtk2-devel >= 2.4.0 , gtkglext-devel >= 1.0.0 , libxml2-devel >= 2.0.0 , zlib-devel >= 1.2.0 , libpng-devel >= 1.2.0 , mhash-devel >= 0.9.0 + +%description +GtkRadiant is a cross-platform level editor for games based on Id Software +technology. + +%prep +%setup -q + +%build +scons BUILD="release" SETUP="0" + +%install +echo $RPM_BUILD_ROOT/ +rm -rf $RPM_BUILD_ROOT%{installdir} +mkdir -p $RPM_BUILD_ROOT%{installdir} + +cp install/radiant.x86 $RPM_BUILD_ROOT%{installdir} +cp install/q3map2.x86 $RPM_BUILD_ROOT%{installdir} +cp install/q2map $RPM_BUILD_ROOT%{installdir} +cp setup/linux/bspc $RPM_BUILD_ROOT%{installdir} +cp setup/links.htm $RPM_BUILD_ROOT%{installdir} +cp setup/credits.html $RPM_BUILD_ROOT%{installdir} +cp setup/changelog.txt $RPM_BUILD_ROOT%{installdir} +cp setup/license.txt $RPM_BUILD_ROOT%{installdir} +cp setup/data/tools/global.xlink $RPM_BUILD_ROOT%{installdir} +mkdir $RPM_BUILD_ROOT%{installdir}/gl +cp setup/data/tools/gl/lighting_DBS_omni_fp.glsl $RPM_BUILD_ROOT%{installdir}/gl +cp setup/data/tools/gl/lighting_DBS_omni_vp.glsl $RPM_BUILD_ROOT%{installdir}/gl +cp setup/data/tools/gl/zfill_fp.glsl $RPM_BUILD_ROOT%{installdir}/gl +cp setup/data/tools/gl/zfill_vp.glsl $RPM_BUILD_ROOT%{installdir}/gl +cp setup/data/tools/gl/lighting_DBS_omni_fp.glp $RPM_BUILD_ROOT%{installdir}/gl +cp setup/data/tools/gl/lighting_DBS_omni_vp.glp $RPM_BUILD_ROOT%{installdir}/gl +cp setup/data/tools/gl/zfill_fp.glp $RPM_BUILD_ROOT%{installdir}/gl +cp setup/data/tools/gl/zfill_vp.glp $RPM_BUILD_ROOT%{installdir}/gl +cp include/RADIANT_MINOR $RPM_BUILD_ROOT%{installdir} +cp include/RADIANT_MAJOR $RPM_BUILD_ROOT%{installdir} +mkdir $RPM_BUILD_ROOT%{installdir}/et.game +mkdir $RPM_BUILD_ROOT%{installdir}/et.game/etmain +cp games/ETPack/et.game/etmain/default_shaderlist.txt $RPM_BUILD_ROOT%{installdir}/et.game/etmain +cp games/ETPack/et.game/etmain/et_entities.def $RPM_BUILD_ROOT%{installdir}/et.game/etmain +cp games/ETPack/et.game/etmain/et_entities.ent $RPM_BUILD_ROOT%{installdir}/et.game/etmain +cp games/ETPack/et.game/game.xlink $RPM_BUILD_ROOT%{installdir}/et.game +cp games/ETPack/et.game/default_build_menu.xml $RPM_BUILD_ROOT%{installdir}/et.game +mkdir $RPM_BUILD_ROOT%{installdir}/et.game/bitmaps +cp games/ETPack/et.game/bitmaps/splash.bmp $RPM_BUILD_ROOT%{installdir}/et.game/bitmaps +cp games/ETPack/et.game/default_build_menu.txt $RPM_BUILD_ROOT%{installdir}/et.game/bitmaps +mkdir $RPM_BUILD_ROOT%{installdir}/games +cp games/ETPack/games/et.game $RPM_BUILD_ROOT%{installdir}/games +cp games/Q1Pack/games/q1.game $RPM_BUILD_ROOT%{installdir}/games +cp games/Q2Pack/games/q2.game $RPM_BUILD_ROOT%{installdir}/games +cp games/Q3Pack/tools/games/q3.game $RPM_BUILD_ROOT%{installdir}/games +cp games/Doom3Pack/tools/games/doom3.game $RPM_BUILD_ROOT%{installdir}/games +cp games/Q4Pack/tools/games/q4.game $RPM_BUILD_ROOT%{installdir}/games +cp games/JAPack/Tools/games/ja.game $RPM_BUILD_ROOT%{installdir}/games +cp games/NexuizPack/games/nexuiz.game $RPM_BUILD_ROOT%{installdir}/games +cp games/DarkPlacesPack/games/darkplaces.game $RPM_BUILD_ROOT%{installdir}/games +mkdir $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/entity.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/model.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/shaders.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/vfspk3.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/archivepak.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/mapxml.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/md3model.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/image.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/imagehl.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/archivezip.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/imagepng.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/imageq2.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/mapq3.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/modules/archivewad.so $RPM_BUILD_ROOT%{installdir}/modules +cp install/plugins/prtview.so $RPM_BUILD_ROOT%{installdir}/prtview +cp install/plugins/bobtoolz.so $RPM_BUILD_ROOT%{installdir}/plugins +cp install/plugins/bt/*.txt $RPM_BUILD_ROOT%{installdir}/plugins/bt +cp install/plugins/bitmaps/bobtoolz_*.bmp $RPM_BUILD_ROOT%{installdir}/plugins/bitmaps +mkdir $RPM_BUILD_ROOT%{installdir}/q1.game +cp games/Q1Pack/q1.game/default_build_menu.xml $RPM_BUILD_ROOT%{installdir}/q1.game +mkdir $RPM_BUILD_ROOT%{installdir}/q1.game/id1 +cp games/Q1Pack/q1.game/id1/entities.ent $RPM_BUILD_ROOT%{installdir}/q1.game/id1 +mkdir $RPM_BUILD_ROOT%{installdir}/q2.game +cp games/Q2Pack/q2.game/game.xlink $RPM_BUILD_ROOT%{installdir}/q2.game +cp games/Q2Pack/q2.game/default_build_menu.xml $RPM_BUILD_ROOT%{installdir}/q2.game +mkdir $RPM_BUILD_ROOT%{installdir}/q2.game/baseq2 +cp games/Q2Pack/q2.game/baseq2/entities.def $RPM_BUILD_ROOT%{installdir}/q2.game/baseq2 +mkdir $RPM_BUILD_ROOT%{installdir}/q3.game +cp games/Q3Pack/tools/q3.game/game.xlink $RPM_BUILD_ROOT%{installdir}/q3.game +cp games/Q3Pack/tools/q3.game/default_build_menu.xml $RPM_BUILD_ROOT%{installdir}/q3.game +mkdir $RPM_BUILD_ROOT%{installdir}/q3.game/missionpack +cp games/Q3Pack/tools/q3.game/missionpack/default_shaderlist.txt $RPM_BUILD_ROOT%{installdir}/q3.game/missionpack +cp games/Q3Pack/tools/q3.game/missionpack/entities-ta.def $RPM_BUILD_ROOT%{installdir}/q3.game/missionpack +cp games/Q3Pack/tools/q3.game/missionpack/entities-ta.ent $RPM_BUILD_ROOT%{installdir}/q3.game/missionpack +mkdir $RPM_BUILD_ROOT%{installdir}/q3.game/baseq3 +cp games/Q3Pack/tools/q3.game/baseq3/entities.ent $RPM_BUILD_ROOT%{installdir}/q3.game/baseq3 +cp games/Q3Pack/tools/q3.game/baseq3/default_shaderlist.txt $RPM_BUILD_ROOT%{installdir}/q3.game/baseq3 +cp games/Q3Pack/tools/q3.game/baseq3/entities.def $RPM_BUILD_ROOT%{installdir}/q3.game/baseq3 +mkdir $RPM_BUILD_ROOT%{installdir}/nexuiz.game +cp games/NexuizPack/nexuiz.game/game.xlink $RPM_BUILD_ROOT%{installdir}/nexuiz.game +cp games/NexuizPack/nexuiz.game/default_build_menu.xml $RPM_BUILD_ROOT%{installdir}/nexuiz.game +mkdir $RPM_BUILD_ROOT%{installdir}/nexuiz.game/data +cp games/NexuizPack/nexuiz.game/data/default_shaderlist.txt $RPM_BUILD_ROOT%{installdir}/nexuiz.game/data +cp games/NexuizPack/nexuiz.game/data/entities.def $RPM_BUILD_ROOT%{installdir}/nexuiz.game/data +mkdir $RPM_BUILD_ROOT%{installdir}/darkplaces.game +cp games/DarkPlacesPack/darkplaces.game/game.xlink $RPM_BUILD_ROOT%{installdir}/darkplaces.game +cp games/DarkPlacesPack/darkplaces.game/default_build_menu.xml $RPM_BUILD_ROOT%{installdir}/darkplaces.game +mkdir $RPM_BUILD_ROOT%{installdir}/darkplaces.game/id1 +cp games/DarkPlacesPack/darkplaces.game/id1/default_shaderlist.txt $RPM_BUILD_ROOT%{installdir}/darkplaces.game/id1 +cp games/DarkPlacesPack/darkplaces.game/id1/entities.ent $RPM_BUILD_ROOT%{installdir}/darkplaces.game/id1 +mkdir $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/view_cubicclipping.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/cap_endcap.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/window4.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/splash.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/show_entities.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/selection_selecttouching.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/cap_iendcap.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/patch_wireframe.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/patch_insdel.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/brush_rotatey.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/view_cameraupdate.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/dontselectcurve.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/scalelockz.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/file_open.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/ellipsis.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/brush_rotatez.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/scalelocky.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/dontselectmodel.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/modify_vertices.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/brush_rotatex.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/brush_flipy.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/selection_selectinside.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/patch_weld.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/view_cameratoggle.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/select_mousescale.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/view_change.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/selection_csgsubtract.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/patch_bend.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/window3.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/scalelockx.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/select_mouseresize.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/window1.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/view_clipper.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/logo.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/patch_drilldown.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/texture_lock.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/textures_popup.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/selection_selectpartialtall.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/selection_selectcompletetall.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/window2.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/select_mousetranslate.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/brush_flipx.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/selection_makehollow.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/select_mouserotate.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/selection_csgmerge.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/file_save.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/popup_selection.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/cap_bevel.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/brush_flipz.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/cap_ibevel.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/curve_cap.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/modify_faces.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/notex.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/modify_edges.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/icon.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/shadernotex.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/patch_showboundingbox.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/view_entity.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/cap_cylinder.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/white.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/black.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +cp setup/data/tools/bitmaps/noFalloff.bmp $RPM_BUILD_ROOT%{installdir}/bitmaps +mkdir $RPM_BUILD_ROOT%{installdir}/doom3.game +cp games/Doom3Pack/tools/doom3.game/default_build_menu.xml $RPM_BUILD_ROOT%{installdir}/doom3.game +mkdir $RPM_BUILD_ROOT%{installdir}/q4.game +cp games/Q4Pack/tools/q4.game/default_build_menu.xml $RPM_BUILD_ROOT%{installdir}/q4.game +mkdir $RPM_BUILD_ROOT%{installdir}/ja.game +cp games/JAPack/Tools/ja.game/default_build_menu.xml $RPM_BUILD_ROOT%{installdir}/ja.game +cp games/JAPack/Tools/ja.game/game.xlink $RPM_BUILD_ROOT%{installdir}/ja.game +mkdir $RPM_BUILD_ROOT%{installdir}/ja.game/base +cp games/JAPack/Tools/ja.game/base/mp_entities.def $RPM_BUILD_ROOT%{installdir}/ja.game/base +cp games/JAPack/Tools/ja.game/base/sp_entities.def $RPM_BUILD_ROOT%{installdir}/ja.game/base +cp games/JAPack/Tools/ja.game/base/default_shaderlist.txt $RPM_BUILD_ROOT%{installdir}/ja.game/base + + +%clean + +%files +%defattr(-,root,root) +%{installdir} diff --git a/setup/linux/makeself/COPYING b/setup/linux/makeself/COPYING new file mode 100644 index 00000000..a52b16e4 --- /dev/null +++ b/setup/linux/makeself/COPYING @@ -0,0 +1,341 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/setup/linux/makeself/README b/setup/linux/makeself/README new file mode 100644 index 00000000..1f0e8c35 --- /dev/null +++ b/setup/linux/makeself/README @@ -0,0 +1,294 @@ +The following was generated from http://www.megastep.org/makeself/ +----------------------- + + makeself - Make self-extractible archives on Unix + + [1]makeself.sh is a small shell script that generates a + self-extractible tar.gz archive from a directory. The resulting file + appears as a shell script (many of those have a .run suffix), and can + be launched as is. The archive will then uncompress itself to a + temporary directory and an optional arbitrary command will be executed + (for example an installation script). This is pretty similar to + archives generated with WinZip Self-Extractor in the Windows world. + Makeself archives also include checksums for integrity self-validation + (CRC and/or MD5 checksums). + + The makeself.sh script itself is used only to create the archives from + a directory of files. The resultant archive is actually a compressed + (using gzip, bzip2, or compress) TAR archive, with a small shell + script stub at the beginning. This small stub performs all the steps + of extracting the files, running the embedded command, and removing + the temporary files when it's all over. All what the user has to do to + install the software contained in such an archive is to "run" the + archive, i.e sh nice-software.run. I recommend using the "run" (which + was introduced by some Makeself archives released by Loki Software) or + "sh" suffix for such archives not to confuse the users, since they + it's actually shell scripts (with quite a lot of binary data attached + to it though!). + + I am trying to keep the code of this script as portable as possible, + i.e it's not relying on any bash-specific features and only calls + commands that are installed on any functioning UNIX-compatible system. + This script as well as the archives it generates should run on any + Unix flavor, with any compatible Bourne shell, provided of course that + the compression programs are available. + + As of version 2.1, Makeself has been rewritten and tested on the + following platforms : + * Linux (all distributions) + * Sun Solaris (8 tested) + * HP-UX (tested on 11.0 and 11i on HPPA RISC) + * SCO OpenUnix and OpenServer + * IBM AIX 5.1L + * MacOS X (Darwin) + * SGI IRIX 6.5 + * FreeBSD + * UnicOS / Cray + + If you successfully run Makeself and/or archives created with it on + another system, then [2]let me know! + + Examples of publicly available archives made using makeself are : + * Game patches and installers for [3]Id Software games like Quake 3 + for Linux or Return To Castle Wolfenstien ; + * All game patches released by [4]Loki Software for the Linux + version of popular games ; + * The [5]nVidia drivers for Linux + * The [6]Makeself distribution itself ;-) + * and countless others... + + Important note for Apache users: By default, most Web servers will + think that Makeself archives are regular text files and thus they may + show up as text in a Web browser. The correct way to prevent this is + to add a MIME type for this file format, like so (in httpd.conf) : + AddType application/x-makeself .run + + Important note for recent GNU/Linux distributions: Archives created + with Makeself prior to v2.1.2 were using an old syntax for the head + and tail Unix commands that is being progressively obsoleted in their + GNU forms. Therefore you may have problems uncompressing some of these + archives. A workaround for this is to set the environment variable + $_POSIX2_VERSION to enable the old syntax, i.e. : + export _POSIX2_VERSION=199209 + +Usage + + The syntax of makeself is the following: + + makeself.sh [args] archive_dir file_name label startup_script + [script_args] + * args are optional options for Makeself. The available ones are : + + --version : Prints the version number on stdout, then exits + immediately + + --gzip : Use gzip for compression (is the default on + platforms on which gzip is commonly available, like Linux) + + --bzip2 : Use bzip2 instead of gzip for better compression. + The bzip2 command must be available in the command path. I + recommend that you set the prefix to something like + '.bz2.run' for the archive, so that potential users know that + they'll need bzip2 to extract it. + + --compress : Use the UNIX "compress" command to compress the + data. This should be the default on all platforms that don't + have gzip available. + + --nocomp : Do not use any compression for the archive, which + will then be an uncompressed TAR. + + --notemp : The generated archive will not extract the files + to a temporary directory, but in a new directory created in + the current directory. This is better to distribute software + packages that may extract and compile by themselves (i.e. + launch the compilation through the embedded script). + + --current : Files will be extracted to the current directory, + instead of in a subdirectory. This option implies --notemp + above. + + --follow : Follow the symbolic links inside of the archive + directory, i.e. store the files that are being pointed to + instead of the links themselves. + + --append (new in 2.1.x): Append data to an existing archive, + instead of creating a new one. In this mode, the settings + from the original archive are reused (compression type, + label, embedded script), and thus don't need to be specified + again on the command line. + + --header : Makeself 2.0 uses a separate file to store the + header stub, called "makeself-header.sh". By default, it is + assumed that it is stored in the same location as + makeself.sh. This option can be used to specify its actual + location if it is stored someplace else. + + --copy : Upon extraction, the archive will first extract + itself to a temporary directory. The main application of this + is to allow self-contained installers stored in a Makeself + archive on a CD, when the installer program will later need + to unmount the CD and allow a new one to be inserted. This + prevents "Filesystem busy" errors for installers that span + multiple CDs. + + --nox11 : Disable the automatic spawning of a new terminal in + X11. + + --nowait : When executed from a new X11 terminal, disable the + user prompt at the end of the script execution. + + --lsm file : Provide and LSM file to makeself, that will be + embedded in the generated archive. LSM files are describing a + software package in a way that is easily parseable. The LSM + entry can then be later retrieved using the '-lsm' argument + to the archive. An exemple of a LSM file is provided with + Makeself. + * archive_dir is the name of the directory that contains the files + to be archived + * file_name is the name of the archive to be created + * label is an arbitrary text string describing the package. It will + be displayed while extracting the files. + * startup_script is the command to be executed from within the + directory of extracted files. Thus, if you wish to execute a + program contain in this directory, you must prefix your command + with "./". For example, ./program will be fine. The script_args + are additionnal arguments for this command. + + Here is an example, assuming the user has a package image stored in a + /home/joe/mysoft, and he wants to generate a self-extracting package + named mysoft.sh, which will launch the "setup" script initially stored + in /home/joe/mysoft : + + makeself.sh /home/joe/mysoft mysoft.sh "Joe's Nice Software Package" + ./setup + Here is also how I created the [7]makeself.run archive which contains + the Makeself distribution : + + makeself.sh --notemp makeself makeself.run "Makeself by Stephane + Peter" echo "Makeself has extracted itself" + + Archives generated with Makeself 2.1 can be passed the following + arguments: + + * --keep : Prevent the files to be extracted in a temporary + directory that will be removed after the embedded script's + execution. The files will then be extracted in the current working + directory and will stay here until you remove them. + * --verbose : Will prompt the user before executing the embedded + command + * --target dir : Allows to extract the archive in an arbitrary + place. + * --nox11 : Do not spawn a X11 terminal. + * --confirm : Prompt the user for confirmation before running the + embedded command. + * --info : Print out general information about the archive (does not + extract). + * --lsm : Print out the LSM entry, if it is present. + * --list : List the files in the archive. + * --check : Check the archive for integrity using the embedded + checksums. Does not extract the archive. + * --nochown : By default, a "chown -R" command is run on the target + directory after extraction, so that all files belong to the + current user. This is mostly needed if you are running as root, as + tar will then try to recreate the initial user ownerships. You may + disable this behavior with this flag. + + Any subsequent arguments to the archive will be passed as additional + arguments to the embedded command. You should explicitly use the -- + special command-line construct before any such options to make sure + that Makeself will not try to interpret them. + +License + + Makeself is covered by the [8]GNU General Public License (GPL) version + 2 and above. Archives generated by Makeself don't have to be placed + under this license (although I encourage it ;-)), since the archive + itself is merely data for Makeself. + +Download + + Get the latest official distribution [9]here (version 2.1.2). + + The latest development version can be grabbed from the Loki Setup CVS + module, at [10]cvs.icculus.org. + +Version history + + * v1.0: Initial public release + * v1.1: The archive can be passed parameters that will be passed on + to the embedded script, thanks to John C. Quillan + * v1.2: Cosmetic updates, support for bzip2 compression and + non-temporary archives. Many ideas thanks to Francois Petitjean. + * v1.3: More patches from Bjarni R. Einarsson and Francois + Petitjean: Support for no compression (--nocomp), script is no + longer mandatory, automatic launch in an xterm, optional verbose + output, and -target archive option to indicate where to extract + the files. + * v1.4: Many patches from Francois Petitjean: improved UNIX + compatibility, automatic integrity checking, support of LSM files + to get info on the package at run time.. + * v1.5.x: A lot of bugfixes, and many other patches, including + automatic verification through the usage of checksums. Version + 1.5.5 was the stable release for a long time, even though the Web + page didn't get updated ;-). Makeself was also officially made a + part of the [11]Loki Setup installer, and its source is being + maintained as part of this package. + * v2.0: Complete internal rewrite of Makeself. The command-line + parsing was vastly improved, the overall maintenance of the + package was greatly improved by separating the stub from + makeself.sh. Also Makeself was ported and tested to a variety of + Unix platforms. + * v2.0.1: First public release of the new 2.0 branch. Prior versions + are officially obsoleted. This release introduced the '--copy' + argument that was introduced in response to a need for the + [12]UT2K3 Linux installer. + * v2.1.0: Big change : Makeself can now support multiple embedded + tarballs, each stored separately with their own checksums. An + existing archive can be updated with the --append flag. Checksums + are also better managed, and the --nochown option for archives + appeared. + * v2.1.1: Fixes related to the Unix compression (compress command). + Some Linux distributions made the insane choice to make it + unavailable, even though gzip is capable of uncompressing these + files, plus some more bugfixes in the extraction and checksum + code. + * v2.1.2: Some bug fixes. Use head -n to avoid problems with POSIX + conformance. + +Links + + * Check out the [13]"Loki setup" installer, used to install many + Linux games and other applications, and of which I am the + co-author. Since the demise of Loki, I am now the official + maintainer of the project, and it is now being hosted on + [14]icculus.org, as well as a bunch of other ex-Loki projects (and + a lot of other good stuff!). + * Bjarni R. Einarsson also wrote the setup.sh installer script, + inspired by Makeself. [15]Check it out ! + +Contact + + This script was written by [16]Stéphane Peter (megastep at + megastep.org) I welcome any enhancements and suggestions. + + Contributions were included from John C. Quillan, Bjarni R. Einarsson, + Francois Petitjean, and Ryan C. Gordon, thanks to them! If you think I + forgot your name, don't hesitate to contact me. + + icculus.org also has a [17]Bugzilla server available that allows bug + reports to be submitted for Loki setup, and since Makeself is a part + of Loki setup, you can submit bug reports from there! + _________________________________________________________________ + + + [18]Stéphane Peter + + Last modified: Fri Jul 4 18:32:11 PDT 2003 + +Références + + 1. http://www.megastep.org/makeself/makeself.run + 2. mailto:megastep@REMOVEME.megastep.org + 3. http://www.idsoftware.com/ + 4. http://www.lokigames.com/products/myth2/updates.php3 + 5. http://www.nvidia.com/ + 6. http://www.megastep.org/makeself/makeself.run + 7. http://www.megastep.org/makeself/makeself.run + 8. http://www.gnu.org/copyleft/gpl.html + 9. http://www.megastep.org/makeself/makeself-2.1.2.run + 10. http://cvs.icculus.org/ + 11. http://www.icculus.org/loki_setup/ + 12. http://www.unrealtournament2003.com/ + 13. http://www.icculus.org/loki_setup/ + 14. http://www.icculus.org/ + 15. http://www.mmedia.is/~bre/programs/setup.sh/ + 16. mailto:megastep@@megastep.org + 17. https://bugzilla.icculus.org/ + 18. mailto:megastep@@megastep.org diff --git a/setup/linux/makeself/TODO b/setup/linux/makeself/TODO new file mode 100644 index 00000000..8bf501b7 --- /dev/null +++ b/setup/linux/makeself/TODO @@ -0,0 +1,6 @@ +What needs to be done next : + +- Generic compression code (thru a user-defined command) +- Collect names of directories potentially containing md5 program. GUESS_MD5_PATH + +Stéphane Peter diff --git a/setup/linux/makeself/makeself-header.sh b/setup/linux/makeself/makeself-header.sh new file mode 100644 index 00000000..c734b826 --- /dev/null +++ b/setup/linux/makeself/makeself-header.sh @@ -0,0 +1,359 @@ +cat << EOF > "$archname" +#!/bin/sh +# This script was generated using Makeself $MS_VERSION +CRCsum="$CRCsum" +MD5="$MD5sum" +TMPROOT=\${TMPDIR:=/tmp} + +label="$LABEL" +script="$SCRIPT" +scriptargs="$SCRIPTARGS" +targetdir="$archdirname" +filesizes="$filesizes" +keep=$KEEP + +print_cmd_arg="" +if type printf > /dev/null; then + print_cmd="printf" +elif test -x /usr/ucb/echo; then + print_cmd="/usr/ucb/echo" +else + print_cmd="echo" +fi + +MS_Printf() +{ + \$print_cmd \$print_cmd_arg "\$1" +} + +MS_Progress() +{ + while read a; do + MS_Printf . + done +} + +MS_dd() +{ + blocks=\`expr \$3 / 1024\` + bytes=\`expr \$3 % 1024\` + dd if="\$1" ibs=\$2 skip=1 obs=1024 conv=sync 2> /dev/null | \\ + { test \$blocks -gt 0 && dd ibs=1024 obs=1024 count=\$blocks ; \\ + test \$bytes -gt 0 && dd ibs=1 obs=1024 count=\$bytes ; } 2> /dev/null +} + +MS_Help() +{ + cat << EOH >&2 +Makeself version $MS_VERSION + 1) Getting help or info about \$0 : + \$0 --help Print this message + \$0 --info Print embedded info : title, default target directory, embedded script ... + \$0 --lsm Print embedded lsm entry (or no LSM) + \$0 --list Print the list of files in the archive + \$0 --check Checks integrity of the archive + + 2) Running \$0 : + \$0 [options] [--] [additional arguments to embedded script] + with following options (in that order) + --confirm Ask before running embedded script + --keep Do not erase target directory after running + the embedded script + --nox11 Do not spawn an xterm + --nochown Do not give the extracted files to the current user + --target NewDirectory Extract in NewDirectory + --tar arg1 [arg2 ...] Access the contents of the archive through the tar command + -- Following arguments will be passed to the embedded script +EOH +} + +MS_Check() +{ + OLD_PATH=\$PATH + PATH=\${GUESS_MD5_PATH:-"\$OLD_PATH:/bin:/usr/bin:/sbin:/usr/local/ssl/bin:/usr/local/bin:/opt/openssl/bin"} + MD5_PATH=\`exec 2>&-; which md5sum || type md5sum\` + MD5_PATH=\${MD5_PATH:-\`exec 2>&-; which md5 || type md5\`} + PATH=\$OLD_PATH + MS_Printf "Verifying archive integrity..." + offset=\`head -n $SKIP "\$1" | wc -c | tr -d " "\` + verb=\$2 + i=1 + for s in \$filesizes + do + crc=\`echo \$CRCsum | cut -d" " -f\$i\` + if test -x "\$MD5_PATH"; then + md5=\`echo \$MD5 | cut -d" " -f\$i\` + if test \$md5 = "00000000000000000000000000000000"; then + test x\$verb = xy && echo " \$1 does not contain an embedded MD5 checksum." >&2 + else + md5sum=\`MS_dd "\$1" \$offset \$s | "\$MD5_PATH" | cut -b-32\`; + if test "\$md5sum" != "\$md5"; then + echo "Error in MD5 checksums: \$md5sum is different from \$md5" >&2 + exit 2 + else + test x\$verb = xy && MS_Printf " MD5 checksums are OK." >&2 + fi + crc="0000000000"; verb=n + fi + fi + if test \$crc = "0000000000"; then + test x\$verb = xy && echo " \$1 does not contain a CRC checksum." >&2 + else + sum1=\`MS_dd "\$1" \$offset \$s | cksum | awk '{print \$1}'\` + if test "\$sum1" = "\$crc"; then + test x\$verb = xy && MS_Printf " CRC checksums are OK." >&2 + else + echo "Error in checksums: \$sum1 is different from \$crc" + exit 2; + fi + fi + i=\`expr \$i + 1\` + offset=\`expr \$offset + \$s\` + done + echo " All good." +} + +UnTAR() +{ + tar \$1vf - 2>&1 || { echo Extraction failed. > /dev/tty; kill -15 \$$; } +} + +finish=true +xterm_loop= +nox11=$NOX11 +copy=$COPY +ownership=y +verbose=n + +initargs="\$@" + +while true +do + case "\$1" in + -h | --help) + MS_Help + exit 0 + ;; + --info) + echo Identification: "\$label" + echo Target directory: "\$targetdir" + echo Uncompressed size: $USIZE KB + echo Compression: $COMPRESS + echo Date of packaging: $DATE + echo Built with Makeself version $MS_VERSION on $OSTYPE + if test x\$script != x; then + echo Script run after extraction: + echo " " \$script \$scriptargs + fi + if test x"$copy" = xcopy; then + echo "Archive will copy itself to a temporary location" + fi + if test x"$KEEP" = xy; then + echo "directory \$targetdir is permanent" + else + echo "\$targetdir will be removed after extraction" + fi + exit 0 + ;; + --dumpconf) + echo LABEL=\"\$label\" + echo SCRIPT=\"\$script\" + echo SCRIPTARGS=\"\$scriptargs\" + echo archdirname=\"$archdirname\" + echo KEEP=$KEEP + echo COMPRESS=$COMPRESS + echo filesizes=\"\$filesizes\" + echo CRCsum=\"\$CRCsum\" + echo MD5sum=\"\$MD5\" + echo OLDUSIZE=$USIZE + echo OLDSKIP=`expr $SKIP + 1` + exit 0 + ;; + --lsm) +cat << EOLSM +EOF +eval "$LSM_CMD" +cat << EOF >> "$archname" +EOLSM + exit 0 + ;; + --list) + echo Target directory: \$targetdir + offset=\`head -n $SKIP "\$0" | wc -c | tr -d " "\` + for s in \$filesizes + do + MS_dd "\$0" \$offset \$s | eval "$GUNZIP_CMD" | UnTAR t + offset=\`expr \$offset + \$s\` + done + exit 0 + ;; + --tar) + offset=\`head -n $SKIP "\$0" | wc -c | tr -d " "\` + arg1="\$2" + shift 2 + for s in \$filesizes + do + MS_dd "\$0" \$offset \$s | eval "$GUNZIP_CMD" | tar "\$arg1" - \$* + offset=\`expr \$offset + \$s\` + done + exit 0 + ;; + --check) + MS_Check "\$0" y + exit 0 + ;; + --confirm) + verbose=y + shift + ;; + --keep) + keep=y + shift + ;; + --target) + keep=y + targetdir=\${2:-.} + shift 2 + ;; + --nox11) + nox11=y + shift + ;; + --nochown) + ownership=n + shift + ;; + --xwin) + finish="echo Press Return to close this window...; read junk" + xterm_loop=1 + shift + ;; + --phase2) + copy=phase2 + shift + ;; + --) + shift + break ;; + -*) + echo Unrecognized flag : "\$1" >&2 + MS_Help + exit 1 + ;; + *) + break ;; + esac +done + +case "\$copy" in +copy) + SCRIPT_COPY="\$TMPROOT/makeself\$\$" + echo "Copying to a temporary location..." >&2 + cp "\$0" "\$SCRIPT_COPY" + chmod +x "\$SCRIPT_COPY" + cd "\$TMPROOT" + exec "\$SCRIPT_COPY" --phase2 + ;; +phase2) + finish="\$finish ; rm -f \$0" + ;; +esac + +if test "\$nox11" = "n"; then + if tty -s; then # Do we have a terminal? + : + else + if test x"\$DISPLAY" != x -a x"\$xterm_loop" = x; then # No, but do we have X? + if xset q > /dev/null 2>&1; then # Check for valid DISPLAY variable + GUESS_XTERMS="xterm rxvt dtterm eterm Eterm kvt konsole aterm" + for a in \$GUESS_XTERMS; do + if type \$a >/dev/null 2>&1; then + XTERM=\$a + break + fi + done + chmod a+x \$0 || echo Please add execution rights on \$0 + if test \`echo "\$0" | cut -c1\` = "/"; then # Spawn a terminal! + exec \$XTERM -title "\$label" -e "\$0" --xwin "\$initargs" + else + exec \$XTERM -title "\$label" -e "./\$0" --xwin "\$initargs" + fi + fi + fi + fi +fi + +if test "\$targetdir" = "."; then + tmpdir="." +else + if test "\$keep" = y; then + echo "Creating directory \$targetdir" >&2 + tmpdir="\$targetdir" + else + tmpdir="\$TMPROOT/selfgz\$\$" + fi + mkdir -p \$tmpdir || { + echo 'Cannot create target directory' \$tmpdir >&2 + echo 'You should try option --target OtherDirectory' >&2 + eval \$finish + exit 1 + } +fi + +location="\`pwd\`" +if test x\$SETUP_NOCHECK != x1; then + MS_Check "\$0" +fi +offset=\`head -n $SKIP "\$0" | wc -c | tr -d " "\` + +if test x"\$verbose" = xy; then + MS_Printf "About to extract $USIZE KB in \$tmpdir ... Proceed ? [Y/n] " + read yn + if test x"\$yn" = xn; then + eval \$finish; exit 1 + fi +fi + +MS_Printf "Uncompressing \$label" +res=3 +if test "\$keep" = n; then + trap 'echo Signal caught, cleaning up >&2; cd \$TMPROOT; /bin/rm -rf \$tmpdir; eval \$finish; exit 15' 1 2 3 15 +fi + +for s in \$filesizes +do + if MS_dd "\$0" \$offset \$s | eval "$GUNZIP_CMD" | ( cd "\$tmpdir"; UnTAR x ) | MS_Progress; then + if test x"\$ownership" = xy; then + (PATH=/usr/xpg4/bin:\$PATH; cd "\$tmpdir"; chown -R \`id -u\` .; chgrp -R \`id -g\` .) + fi + else + echo + echo "Unable to decompress \$0" >&2 + eval \$finish; exit 1 + fi + offset=\`expr \$offset + \$s\` +done +echo + +cd "\$tmpdir" +res=0 +if test x"\$script" != x; then + if test x"\$verbose" = xy; then + MS_Printf "OK to execute: \$script \$scriptargs \$* ? [Y/n] " + read yn + if test x"\$yn" = x -o x"\$yn" = xy -o x"\$yn" = xY; then + \$script \$scriptargs \$*; res=\$?; + fi + else + \$script \$scriptargs \$*; res=\$? + fi + if test \$res -ne 0; then + test x"\$verbose" = xy && echo "The program '\$script' returned an error code (\$res)" >&2 + fi +fi +if test "\$keep" = n; then + cd \$TMPROOT + /bin/rm -rf \$tmpdir +fi +eval \$finish; exit \$res +EOF diff --git a/setup/linux/makeself/makeself.lsm b/setup/linux/makeself/makeself.lsm new file mode 100644 index 00000000..8f75b5d0 --- /dev/null +++ b/setup/linux/makeself/makeself.lsm @@ -0,0 +1,16 @@ +Begin3 +Title: makeself.sh +Version: 2.1 +Description: makeself.sh is a shell script that generates a self-extractible + tar.gz archive from a directory. The resulting file appears as a shell + script, and can be launched as is. The archive will then uncompress + itself to a temporary directory and an arbitrary command will be + executed (for example an installation script). This is pretty similar + to archives generated with WinZip Self-Extractor in the Windows world. +Keywords: Installation archive tar winzip +Author: Stéphane Peter (megastep@megastep.org) +Maintained-by: Stéphane Peter (megastep@megastep.org) +Original-site: http://www.megastep.org/makeself/ +Platform: Unix +Copying-policy: GPL +End diff --git a/setup/linux/makeself/makeself.sh b/setup/linux/makeself/makeself.sh new file mode 100755 index 00000000..053eb1b6 --- /dev/null +++ b/setup/linux/makeself/makeself.sh @@ -0,0 +1,334 @@ +#!/bin/sh +# +# Makeself version 2.1.x +# by Stephane Peter +# +# $Id: makeself.sh,v 1.40 2003/09/12 02:19:29 megastep Exp $ +# +# Utility to create self-extracting tar.gz archives. +# The resulting archive is a file holding the tar.gz archive with +# a small Shell script stub that uncompresses the archive to a temporary +# directory and then executes a given script from withing that directory. +# +# Makeself home page: http://www.megastep.org/makeself/ +# +# Version 2.0 is a rewrite of version 1.0 to make the code easier to read and maintain. +# +# Version history : +# - 1.0 : Initial public release +# - 1.1 : The archive can be passed parameters that will be passed on to +# the embedded script, thanks to John C. Quillan +# - 1.2 : Package distribution, bzip2 compression, more command line options, +# support for non-temporary archives. Ideas thanks to Francois Petitjean +# - 1.3 : More patches from Bjarni R. Einarsson and Francois Petitjean: +# Support for no compression (--nocomp), script is no longer mandatory, +# automatic launch in an xterm, optional verbose output, and -target +# archive option to indicate where to extract the files. +# - 1.4 : Improved UNIX compatibility (Francois Petitjean) +# Automatic integrity checking, support of LSM files (Francois Petitjean) +# - 1.5 : Many bugfixes. Optionally disable xterm spawning. +# - 1.5.1 : More bugfixes, added archive options -list and -check. +# - 1.5.2 : Cosmetic changes to inform the user of what's going on with big +# archives (Quake III demo) +# - 1.5.3 : Check for validity of the DISPLAY variable before launching an xterm. +# More verbosity in xterms and check for embedded command's return value. +# Bugfix for Debian 2.0 systems that have a different "print" command. +# - 1.5.4 : Many bugfixes. Print out a message if the extraction failed. +# - 1.5.5 : More bugfixes. Added support for SETUP_NOCHECK environment variable to +# bypass checksum verification of archives. +# - 1.6.0 : Compute MD5 checksums with the md5sum command (patch from Ryan Gordon) +# - 2.0 : Brand new rewrite, cleaner architecture, separated header and UNIX ports. +# - 2.0.1 : Added --copy +# - 2.1.0 : Allow multiple tarballs to be stored in one archive, and incremental updates. +# Added --nochown for archives +# Stopped doing redundant checksums when not necesary +# - 2.1.1 : Work around insane behavior from certain Linux distros with no 'uncompress' command +# Cleaned up the code to handle error codes from compress. Simplified the extraction code. +# - 2.1.2 : Some bug fixes. Use head -n to avoid problems. +# - 2.1.3 : Bug fixes with command line when spawning terminals. +# Added --tar for archives, allowing to give arbitrary arguments to tar on the contents of the archive. +# +# (C) 1998-2003 by Stéphane Peter +# +# This software is released under the terms of the GNU GPL +# Please read the license at http://www.gnu.org/copyleft/gpl.html +# + +MS_VERSION=2.1.3 + +# Procedures + +MS_Usage() +{ + echo "Usage: $0 [params] archive_dir file_name label [startup_script] [args]" + echo "params can be one or more of the following :" + echo " --version | -v : Print out Makeself version number and exit" + echo " --help | -h : Print out this help message" + echo " --gzip : Compress using gzip (default if detected)" + echo " --bzip2 : Compress using bzip2 instead of gzip" + echo " --compress : Compress using the UNIX 'compress' command" + echo " --nocomp : Do not compress the data" + echo " --notemp : The archive will create archive_dir in the" + echo " current directory and uncompress in ./archive_dir" + echo " --copy : Upon extraction, the archive will first copy itself to" + echo " a temporary directory" + echo " --append : Append more files to an existing Makeself archive" + echo " The label and startup scripts will then be ignored" + echo " --current : Files will be extracted to the current directory." + echo " Implies --notemp." + echo " --header file : Specify location of the header script" + echo " --follow : Follow the symlinks in the archive" + echo " --nox11 : Disable automatic spawn of a xterm" + echo " --nowait : Do not wait for user input after executing embedded" + echo " program from an xterm" + echo " --lsm file : LSM file describing the package" + echo + echo "Do not forget to give a fully qualified startup script name" + echo "(i.e. with a ./ prefix if inside the archive)." + exit 1 +} + +# Default settings +if type gzip 2>&1 > /dev/null; then + COMPRESS=gzip +else + COMPRESS=Unix +fi +KEEP=n +CURRENT=n +NOX11=n +APPEND=n +COPY=none +TAR_ARGS=cvf +HEADER=`dirname $0`/makeself-header.sh + +# LSM file stuff +LSM_CMD="echo No LSM. >> \"\$archname\"" + +while true +do + case "$1" in + --version | -v) + echo Makeself version $MS_VERSION + exit 0 + ;; + --bzip2) + COMPRESS=bzip2 + shift + ;; + --gzip) + COMPRESS=gzip + shift + ;; + --compress) + COMPRESS=Unix + shift + ;; + --nocomp) + COMPRESS=none + shift + ;; + --notemp) + KEEP=y + shift + ;; + --copy) + COPY=copy + shift + ;; + --current) + CURRENT=y + KEEP=y + shift + ;; + --header) + HEADER="$2" + shift 2 + ;; + --follow) + TAR_ARGS=cvfh + shift + ;; + --nox11) + NOX11=y + shift + ;; + --nowait) + shift + ;; + --append) + APPEND=y + shift + ;; + --lsm) + LSM_CMD="cat \"$2\" >> \"\$archname\"" + shift 2 + ;; + -h | --help) + MS_Usage + ;; + -*) + echo Unrecognized flag : "$1" + MS_Usage + ;; + *) + break + ;; + esac +done + +archdir="$1" +archname="$2" + +if test "$APPEND" = y; then + if test $# -lt 2; then + MS_Usage + fi + + # Gather the info from the original archive + OLDENV=`sh "$archname" --dumpconf` + if test $? -ne 0; then + echo "Unable to update archive: $archname" >&2 + exit 1 + else + eval "$OLDENV" + fi +else + if test "$KEEP" = n -a $# = 3; then + echo "ERROR: Making a temporary archive with no embedded command does not make sense!" >&2 + echo + MS_Usage + fi + # We don't really want to create an absolute directory... + if test "$CURRENT" = y; then + archdirname="." + else + archdirname=`basename "$1"` + fi + + if test $# -lt 3; then + MS_Usage + fi + + LABEL="$3" + SCRIPT="$4" + test x$SCRIPT = x || shift 1 + shift 3 + SCRIPTARGS="$*" +fi + +if test "$KEEP" = n -a "$CURRENT" = y; then + echo "ERROR: It is A VERY DANGEROUS IDEA to try to combine --notemp and --current." >&2 + exit 1 +fi + +case $COMPRESS in +gzip) + GZIP_CMD="gzip -c9" + GUNZIP_CMD="gzip -cd" + ;; +bzip2) + GZIP_CMD="bzip2 -9" + GUNZIP_CMD="bzip2 -d" + ;; +Unix) + GZIP_CMD="compress -cf" + GUNZIP_CMD="exec 2>&-; uncompress -c || test \\\$? -eq 2 || gzip -cd" + ;; +none) + GZIP_CMD="cat" + GUNZIP_CMD="cat" + ;; +esac + +tmpfile="${TMPDIR:=/tmp}/mkself$$" + +if test -f $HEADER; then + oldarchname="$archname" + archname="$tmpfile" + # Generate a fake header to count its lines + SKIP=0 + . $HEADER + SKIP=`cat "$tmpfile" |wc -l` + # Get rid of any spaces + SKIP=`expr $SKIP` + rm -f "$tmpfile" + echo Header is $SKIP lines long >&2 + + archname="$oldarchname" +else + echo "Unable to open header file: $HEADER" >&2 + exit 1 +fi + +echo + +if test "$APPEND" = n; then + if test -f "$archname"; then + echo "WARNING: Overwriting existing file: $archname" >&2 + fi +fi + +USIZE=`du -ks $archdir | cut -f1` +DATE=`LC_ALL=C date` + +echo About to compress $USIZE KB of data... +echo Adding files to archive named \"$archname\"... +(cd "$archdir"; tar $TAR_ARGS - * | eval "$GZIP_CMD" ) >> "$tmpfile" || { echo Aborting; rm -f "$tmpfile"; exit 1; } +echo >> "$tmpfile" >&- # try to close the archive + +fsize=`cat "$tmpfile" | wc -c | tr -d " "` + +# Compute the checksums + +md5sum=00000000000000000000000000000000 +crcsum=`cat "$tmpfile" | cksum | sed -e 's/ /Z/' -e 's/ /Z/' | cut -dZ -f1` +echo "CRC: $crcsum" + +# Try to locate a MD5 binary +OLD_PATH=$PATH +PATH=${GUESS_MD5_PATH:-"$OLD_PATH:/bin:/usr/bin:/sbin:/usr/local/ssl/bin:/usr/local/bin:/opt/openssl/bin"} +MD5_PATH=`type -p md5sum` +MD5_PATH=${MD5_PATH:-`type -p md5`} +PATH=$OLD_PATH + +if test -x "$MD5_PATH"; then + md5sum=`cat "$tmpfile" | "$MD5_PATH" | cut -b-32`; + echo "MD5: $md5sum" +else + echo "MD5: none, md5sum binary not found" +fi + +if test "$APPEND" = y; then + mv "$archname" "$archname".bak || exit + + # Prepare entry for new archive + filesizes="$filesizes $fsize" + CRCsum="$CRCsum $crcsum" + MD5sum="$MD5sum $md5sum" + USIZE=`expr $USIZE + $OLDUSIZE` + # Generate the header + . $HEADER + # Append the original data + tail -n +$OLDSKIP "$archname".bak >> "$archname" + # Append the new data + cat "$tmpfile" >> "$archname" + + chmod +x "$archname" + rm -f "$archname".bak + echo Self-extractible archive \"$archname\" successfully updated. +else + filesizes="$fsize" + CRCsum="$crcsum" + MD5sum="$md5sum" + + # Generate the header + . $HEADER + + # Append the compressed tar data after the stub + echo + cat "$tmpfile" >> "$archname" + chmod +x "$archname" + echo Self-extractible archive \"$archname\" successfully created. +fi +rm -f "$tmpfile" diff --git a/setup/linux/makeself/update-readme b/setup/linux/makeself/update-readme new file mode 100755 index 00000000..5c918e6d --- /dev/null +++ b/setup/linux/makeself/update-readme @@ -0,0 +1,7 @@ +#!/bin/sh +# Grab the Makeself web page + +echo The following was generated from http://www.megastep.org/makeself/ > README +echo ----------------------- >> README +echo >> README +lynx -dump http://www.megastep.org/makeself/ >> README diff --git a/setup/linux/nightly.cf b/setup/linux/nightly.cf new file mode 100644 index 00000000..f9fe4abc --- /dev/null +++ b/setup/linux/nightly.cf @@ -0,0 +1,14 @@ +# Linux build config +# all.cf +# +# includes core + wolfpack + q3pack + +$DO_CORE=1; +$DO_GAME_Q3=1; +$DO_GAME_WOLF=1; + +$DO_NIGHTLY=1; + +# the nightly is meant to overwrite an existing full installation +# see postinstall.sh, it would be better to have a clear indication of nightly +$GTKRAD_BASE_VERSION="1.2"; diff --git a/setup/linux/q3.cf b/setup/linux/q3.cf new file mode 100644 index 00000000..0e5de223 --- /dev/null +++ b/setup/linux/q3.cf @@ -0,0 +1,8 @@ +# Linux build config +# q3.cf +# +# includes core + q3pack + +$DO_CORE=1; +$DO_GAME_Q3=1; +$DO_GAME_WOLF=0; diff --git a/setup/linux/rpm_build.sh b/setup/linux/rpm_build.sh new file mode 100644 index 00000000..8ca84117 --- /dev/null +++ b/setup/linux/rpm_build.sh @@ -0,0 +1 @@ +rpmbuild --buildroot=/home/spog/rpmbuild/ -ba ./GtkRadiant/setup/linux/gtkradiant-1.5.0.spec diff --git a/setup/linux/setup.sh b/setup/linux/setup.sh new file mode 100644 index 00000000..8d920290 --- /dev/null +++ b/setup/linux/setup.sh @@ -0,0 +1,124 @@ +#! /bin/sh +# +# Product setup script - Loki Entertainment Software + +# TTimo FIXME need a way to configure this easily +critical_error="Please contact Id software technical support at bugs@idsoftware.com" + +# Go to the proper setup directory (if not already there) +cd `dirname $0` + +# Return the appropriate architecture string +DetectARCH() +{ + status=1 + case `uname -m` in + i?86) echo "x86" + status=0;; + *) echo "`uname -m`" + status=0;; + esac + return $status +} + +# Return the appropriate version string +DetectLIBC() +{ + status=1 + if [ `uname -s` != Linux ]; then + echo "glibc-2.1" + return $status + fi + if [ -f `echo /lib/libc.so.6* | tail -1` ]; then + if fgrep GLIBC_2.1 /lib/libc.so.6* 2>&1 >/dev/null; then + echo "glibc-2.1" + status=0 + else + echo "glibc-2.0" + status=0 + fi + elif [ -f /lib/libc.so.5 ]; then + echo "libc5" + status=0 + else + echo "unknown" + fi + return $status +} + +# Detect the Linux environment +arch=`DetectARCH` +libc=`DetectLIBC` +os=`uname -s` + +# Find the installation program +# try_run INSTALLER_NAME [-fatal] [PARAMETERS_PASSED] +# INSTALLER_NAME: setup.gtk or setup +# -fatal option: if you want verbose messages in case +# - the script could not be found +# - it's execution would fail +# PARAMETERS_PASSED: additional arguments passed to the setup script +try_run() +{ + setup=$1 + shift + # added safe check, direct test seems buggy on older bash + if [ "$#" > 0 ]; then + # looks like bash < 2.* don't like == operator, using = instead + if [ "$1" = "-fatal" ]; then + # got fatal + fatal=$1 + shift + fi + fi + + # First find the binary we want to run + failed=0 + setup_bin="setup.data/bin/$os/$arch/$libc/$setup" + # trying $setup_bin + if [ ! -f "$setup_bin" ]; then + setup_bin="setup.data/bin/$os/$arch/$setup" + # libc dependant version failed, trying again + if [ ! -f "$setup_bin" ]; then + failed=1 + fi + fi + if [ "$failed" -eq 1 ]; then + if [ "$fatal" != "" ]; then + cat <<__EOF__ +This installation doesn't support $libc on $os / $arch + +$critical_error +__EOF__ + exit 1 + fi + return $failed + fi + + # Try to run the binary ($setup_bin) + # The executable is here but we can't execute it from CD + setup="$HOME/.setup$$" + cp "$setup_bin" "$setup" + chmod 700 "$setup" + if [ "$fatal" != "" ]; then + "$setup" $* + failed=$? + else + "$setup" $* 2>/dev/null + failed=$? + fi + rm -f "$setup" + return $failed +} + + +# Try to run the setup program +status=0 +rm -f "$setup" +try_run setup.gtk $* || try_run setup -fatal $* || { + echo "The setup program seems to have failed on $arch/$libc" + echo + echo $critical_error + status=1 +} +exit $status diff --git a/setup/linux/setup_image.Linux/setup.data/bin/Linux/x86/glibc-2.1/setup.gtk b/setup/linux/setup_image.Linux/setup.data/bin/Linux/x86/glibc-2.1/setup.gtk new file mode 100644 index 0000000000000000000000000000000000000000..1fdeadd283ec27a3a186128c06088074b976c9cb GIT binary patch literal 637280 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 =2|EXa`N;Bj=D%~DDX=&+lu7HskL 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

eq-8|;yu;1?jfs)7 zeMq+SKsmQwg>NwgkD29#vHlnM+#0?ZVa#S@Fqe!$#HkCV%w98j&18@HyYRP@^|yzv z!KQzip!{}fg6S@UJBN}wh^YWaK>tHey@wyL0idT|w-{?hn%_?IOcU)lf+kv_i`9BVNP|K?ayjH9iZs3>W*arpM>p~68YT_K z%{sIcftwWPjv(Bi8aK;X>tn>NSX0=z0V%N}xnYoA|HL2oH_@idcErHHsRo{Bkr0c% z58Ex#cOgFHfp_Bg2cQCdJA$nk)hUiY+)^Li(yLd6TUv*fV#c3A;}?^G=+7Q+$0h@l zT1+qifFtQ2{5d{{4A`7W+6=pU_(`0WTi_jKS1V77Y4b3dHmgBwp?g@KScqU=dP_VQ zG<|MrR)vdX;v6KYOrVN2TXJxYw$Jv(*7{)f5Z3v@Cf7#@R&Cqr4Df+3<6FjalpygfQL?P4G)w} z1MUXq7fZ|uLQX0x%lpY>bO{ti|_^U zy6xs_F5eT402bFWXzCn;Nkf@~F9*>Cl9^-4y4iehg~$9YCL>g{6C|+!Uo&WW9}=68 zxLw{ZfB;Ply?M<0+G&`8bL|&A3kuFh7-|Z$5I6@ZIBD8?qO4)ta0KTg2sm&46>X)M zisrJq?WHFel#yEI0wyF<;q#U)r6<-TRamHME2I5sJE=baaVko0zRO>3Hur|Pg$u8m z7bM}(MZi;sOZY?$RVy8+g_V+b2M=3pe>s}sp`~97mijU3+xWc*aih>&2}IuA;Z}z0 zRz5&77Cr1L>YTOVK0-VxL0Yy2@B@vma5in>5`yqH!giW#7+9_q8P_9A;xgkZEYpgN zOOZBvg|SlTGs9Pc1DR3udAIn{6Vc~+pGDH=MA;Ysgy1tr@Bl`@Lg}2a!Xk0h@Wm9v ziAsR40YF)s{%_bRjngihO5GW6-cH8?1A>vFUqk>*!r^3*Y@$Fb;v zo~lDQ!h%2&z=`->9Aj}db)fygri1zQb0%Div-EozR5t{4wACH_ zC0g(wf$h9<%}U6+Pw`8l<(w@)#0OhC>7Fm=jvg2ZllXW|;=?7B6lQEkjfpxknPEc5 z?=2isMlZJ%y25pEYFdTj{a!ZO;ZL*Vku5LTs(+RA+FAc6p$a7-`=S}v-zjKD@w0CE5I!gR z-CO!0c#_w4yKkl>9CSh^z+{*A4A#2411V&0<3i5^Sm5UEcCvrmzY-DMZwUQ==Z=wNs?$^HT$*2U@bzwLF?R+dFYyaZ9Q*?~R*24P_SP zm%rh>WgS#(!=?`rF+`ab{5yzig14i%lkxJiJv9G6!9y}~_44UU+PUG`3f^_d!Y|X6 zeNS<}U9V{D-$Wlg2v}m7FQ<2;A;bcdJhhiW>*BNT!<>l zT8jD==1cqF0iTUY_H9B8<;}z%-nA6Jw{SGv?pFo!5QBTEZ$oXTcO6;J)dP_}*1u=s zV8|V`F<~!SapAbgk$$*=4Pk&-cP3>1=& zY#955^36COAqE zQqq7o^k0a)f`O`mm1cK@Rr>ikaEgcUVZx~7qaD~tm3YW9_#M#vE%*@no!g@OWVoTe zBtHV>1DBd!gCX{pK^!4)uHE0AgqOo#mW44QxSWcvK;^(dacWAkzid2iDY)trmhwbD z^i zMyJxZ-idfpZC>&g%?+KbJ0IKUSS0v2X8AV~Q6g6T-GiupD3nFx@tAJ7hNreB>+Ye z^oprR%sGEaa`@L0yP=70K&C&1zkCu)e6@4W*S_fSi)`qCY(VNk4}lRrTSsw_Q;!5T zxfHmF6=Wruil#3xAHgVz60zC6DIH1*`lKoyE?$3M1u?st2g73oR-);s#=wBUPIR z`vS1CRG^@(Y#^8hM&%It!!2~c@F1$O#NA1&VQz$$=t{atDofC#eo zdCbhlWq1gNXFJ`S)kZqff$cNcKY)S=)4T^TYWz$)jv?7@Ad29_OMyHdFBwY|pUlva zBI$WAIGFBaL{H_wAr8`K;qjlr*z~Z~>k)!j8Wg$kSObA!Ww)y~&xb_y$y38X{ToGe zg|5<#dCV=kF}e-~oP7UUUOFSTAIn?tWubKO59~eDXjEgvx$YOe84&$26Fqd-!Vyhf zf~M>O$bNonQ#z9OLZSvrhiLNJls&#a^I=Lz89YGt=cXLWbX1FK8;ozZL|b$ma4FK{g7Xq{H0i_aMB5%5FrL z%zx-0D=;s|UzQWV{!Q8FHIAA)V1dwb={s-X`?5)ldt);9){VmP%jbMl=e2-2Qsxh<96;p+?6aNtCMN~U| zB(WT_tgjR3_dko~hehgnUvE8Bm|Z7dd!m;|rXW&VpR` zr$qBbISzPez7VD4gJVkg;!WV93zpdWx{W1pR+|TpKBSofG!tj=E$A<_ORc{IMgU z%;)3KAE{v&Kv{==FmFD4g)9wG#KtVFEI61FDA%4sXfp;y&r z$)5=kv^>b`M&ne=gSL{<^d4_8V-M}8anPY;VD7BLH22{}Ld2Z36L?77ibY|iIo~!f zv^ZFXNy&&@7fgLK0=L-?Z+BW3^rQ6%*XjalYlv&v(ZgWyBwFh2eae2~iS z9JBm5zy@Ze#kJcsqPl&;>UJ-XVV|jE@VnY)O81*?HocVWFc;%GWotd5as|=u zOnXbod{2dWE$FZ%wISfH*u6hFZ(6dKXel`f`LU56P<_XK5g~RWTSr?VuWW4cPVSlG z^%0pw6=rh{&l8iELgXsF}c~tjEZp2+U8-dw%__zzyj*GU|T@ z2T~`zvD-{)$sX8}`t`uvtq1ekj0|-uE|itmwzgZ#!p=}i%LPgJ3`<{JEB!3imA$EU zc{)avQk0%odvI+k!gx|ziaV7HEWEa;B1{C5ky0bw*E%2c0a$rn@Ol37#cRj4q<#-# zuqC^R1|C>Y5$K)=hpVflpm)b3*k*uJB=`=!0ezJ%4tVAl=IvW|T}r=caJv8qKoIa$ zz%D1rUSZB%4#LjDegs59rFq3Aj3Zb_Q^+YqMw8tT`xhBv+B-!7MrF%j**HQs!2gaV zb#cXysFS4P`KZ(~jq(UP0!52~x4b>x3I3<@-dZOg z?8ut%Iv)6srnvlWm;afF@mZd@09~!oFK|&748&eYUB`ed97q9Z!mq0*ywU9_ke*kR zx{jEp!nXlBY=Ccr3-Mk)+=QCt+W_xFci)C|N8NiGuI?4)gGu1XJ%L^U5B!}Pu6pu6 z0Ini|%j2@I3YlIN?-F{}vJc`~LC+P89`CA&$+HNYdbnEKeXS1wJxTxM<#kglkW=DH zD#;1Zy;56hQ>otivrw;wjyG4Dt`uMj7h>ArS<85Ojo|r|@ni`EEnh5I5-7onVtn1q zmTa0BzkAZ|{VDM1oLylyt|H6jY?`1}gW#%h_U)PeBUf%OSVikA$io=whxP*Q@u=^g zwiCBD_}7l^EE#Y0M!JQ$U_9caVSnJr$~s3&MW=u1_@wDDX}rc&8D3fuh2|(%h55>$??I^L*R!Z(X5+ z`Wy6&0r$~JE6i+yoy+mTPZjV-1inG-1h^&*f}8dKwh<3IxonMw?GM8L7Wx81B1$hN z{NK(GUas#O*g~E2sJ2iGp&mQixPY=lz2wT#LsYg6>khsR6#necrOH3Vn!^F-MTNPJ z+MyWw97%U!jGIiC&LFJP=i%5>WO~)`Nb@|#U&<>?o^NX`YSAvsZnyrtIJzc ztZzR(s=QtuYjW3g;yxz%kVjmJvQ1Z#rpzW|6K90is-E1q&rib=;UWml4qd%d|Zp*-1Z4xBbxP|?4If1Z?s6R0va4{=|;z;0q+QLm#0WJJ4&ndG9s4 zMj>hl4v~A7+Ot3Q~Fq0P`282FkMiI3!R1V%K`0zZmho zp=4#`+vtpN3Pv-sU{*plBje-ou#xDU?O&VaUrR*pF`p&xZ%MZQ4P*o4{mix^nE`}w zM$AAf!KN~DSS&5_d&|KNIArArIvn*lI3u{FC2JP;oZ6vT#SN&=NWwxXrhSw-vdfi= zg-FW}27>)6D{2m-AGhgYI_AtuPpZ!;OCPQL+{p8a26+v9#8^P)&Mgh-5becjsAssL zb+(2Ua6|Wm8}iP8mypsGFvz#*7xaL#jXYtq^?TFm2rsJ5Ln~Q?FVdg6R>2rz8s%)Fd~yhYMRfBPdtY!A_%NbQ zi`TU;*ps-E=?N8a9&GjIL7~f;{v+!8+E?rTu87~?6_Nej8~iPX9?=H+yEjNCtG&NM z^tU`-e}(nMGQKkOHzRg@Wt&Ip@!>dn?`K10(VabuoDC(*XcwIYrrpeT^wZ3a*;n4u zsYim$4x~Y_ZzWUr0D=3vvG|v?QLHL@@_!>$(QrgvhRS6;s(z-y`Qb`N!xc&bF%-@Z z2Z%ch>EKDCq@Js#c^MhSdcPl@}6}1>I z&SPTkz6nhe>SN)Luh`jwXV&!PdtIUP?C%XawI>t?Y73pK=o!3`9uJibaZuUvZ)ayr zg?DQbx}b{cI|`NaK2nz2fJ1sof(39%7b&{O|IBv_tB#}$z43qSmOn};w8XZg;3o7M$0nSzR-M1 zJk`zq?B38cN)pp;-AJT+VbOtfFV4oB(TbP=H<2kq;v=*w+|sjU+?(69BI#0C^7i`_L-rN&nd`#VT#=1iFfOG+iq>oUU6Q+;U4eV{A|Sz;5s? z$``wyyeE^;K2IRE-#@^&6k}t(qq1ffO(1pfl7=p>|5EN&BMx*nT?pxp-BgnLq@0=P`lJ3`;rgrz ze2`E6lDc3gM1?g#Iz)#7Hp&2T(1{!M-XH{W6*V>-jfIv2M4Z2px0Ui1CQ^INRC23_dReNYdnXw>k08{ zn?MjKVKa9ci-jD<{9t=);+;(!_@@#HyD&a*%rgU7B=k|E zbv7E9H^qi)cnYx$1>ivn^T*KoS``h@da=Y+c6dg{fwHjEYeZLm*O0m9MaOV7f|CiTR`%mMPv`0277eh z-??!Mjmq$Bf&GDL)Q`12dIZKW9y1uTqxff_LPf;VH3#^Q6ded*)A-;kmtmhC&4;|- zM)(UGw6*woO7g`i1F+=F^`D$p`SL(80ufriTq}Y^1FZ4$TD#O%92x&kI%AdX+OUay zZP*y0p?e60q9bTk1-dzOZd^QJ*cYUW04vZbH$r%Jd1_d^? zV}0)1fc4v;=E^$0{G_G`ugv%JIIcC5SiT(25fZ)4*p$o4D?RA+eFEkRV%N-pjM2`f zQlczf2?uCK8MIls)EoqHvpo$%ndd7#m=bJ2{UfC=@MbnXKn?aHUg(6wgObK$1>tPG z5Oo+L1ZQ3uxFNe?w!=HSx%>9CvUwPY|Cq1!`x95T=k2Ng8}9l5@+tS~VZ=(eSD16= zhsWq_JeM}8+LGQ2J&waB)K@V)mJee;m2Z@BwJc6g!HqJ(g9LiG!kxU~8Yn*$Kwf%q z=YsT>xrmr8J%|%bd(*};3@0YMF%hAht>4evTc019QL=rh`N%vw6!(*lt1apCP#>X( zDsxeD^7b^`J7dmkdVBLf_on5~3)TPzSzk3A9b>EjijonAv*{oVz$S&L^7N%g70HPHdi4U0?*&L$GBz704)|6-7YtCEk~2M88BOAi8zG=x3%<6;nb zq|82qzk?A8k(y4j+KOeJ|7)UP%sS%I#s0KO)VC$l7BYh*Dg&GgfI|yxA7mOXN&;VG zE%H-$BL6$O!7#As>b!-rUIZ$2>wFMb9HfKfqBV zno*Tfw_EelI;8gD&V;hdv+~Lht{pJ53eKyURVTbXXI35erZ*zl_dsA^ZyNl4*tFK^ zg_Td7c4sXf1lu)J2%^P?rN==YmFBvFFg^`ZzBQ|f`7&vL^RRH+TG&m989@vrxfQ{c z!4ENOJDIyE9~(0j;kSi?fXVruL5FXIXMT?g+tcO+@5f)^ufxEcu1p@jAujNk?B*jK zBC4SYUoBW{8E%Ab$j55&Mm+Q%X-?UmRw6`BjApch#K%RDI5FC5bHn3tHs%VWeU4^~ zCxa8v4$U}4tuFjKxNSwcf3JTfvA5=*V-pTp#23jw)cDO2jlV7Y^Pa^&hn6V*IW({7 zh~l5$W3*xZxsLg#Mh~BGbuT!_8vZt-Djv0W^Qn#ZZzcBe&Q~nW;|&#Pma+s>EWYlJ z_&!|?sT1>Dn$_%TeAye1=#o%P6J0nrv#j&2T?K^b=RLP+NOS5r1vy?GE)fbcT(0pw_I(r`#>HYXr1@|& zKouB7*$V~6{J^4Va5EM8ZXV(=MnXSQ6>rawx^)pJnhg~e4qBb!rTFTlX5+(ZW7WXM z(llQ!?o6MEeU?dhSz!)%QNKKhmx00|(3P_NfBer$@OT@q;%m&T4zPedX6^5yFF1|M zC}MI7lquEIx|>d|^wFCRyTE7>)=_94_x-DnCHkv9xgODF6eM5H>0?NRJ0V>{IU=uV z6S-fYk`BE>kab`S1;>0iWYhu+FzG!4yDvKftBa$B^(CQf;1DUNpgkSD;pS^EtVhzj zIyC9>9lc=lh~RR7ul#F_ZmZ%2&K$8XD|7&(^Po2px zoOQbh}*pAeGGUp z?XlFGbZ`ts4Sg?Sp)dX$a3B#&a~J_zfgm646Kuzd3horIv<@ zyoul>;kPTT28pxKpo+j(QeqbK+ga$ckMNsw$JeOAAv_-u?GfK0{gKrk@mFkn%yp_g z;xFfpk6{1AYfs*`;VpUx?rF;{8uwE)%JQ5$a6LjS%J95NTIl0DilpK=PKQbn_;1L? z^VL$Rr|&4zA7Xo7@)auj#iK&&Jra_i8}%NKa`?ToF#~_M9DRre)|iEIFrRF$Qzf70 zuzwjd>mZ-s;JF}SHIOhnV#;YBkn%KPe4vl*)Pj{C(mp@FcD%ky8VZxo+=#Y>d?>F{ zwuo!zjv3V8uz2!Q=x2b{B2h~0eACnV2>SU5XEQw^PG2)7kVie*5`i=CBL+ZamX2DHvhsS~;+;c|%KNGN9b%pWX)i3o*XaAdg+r zwgyeh;}^tdrXOqlGyP+RgrWF=V*Q_jq2%8uA+qAPpfwZTBg$6=wPM9Yew8(snTw~8 zrLC@hAP3Bv%W4J=hI{5$&`zX(t4Ykm-Wdsh7Nr8mu_{*RNL&2?e)1IzS#9%Vfy(|q zb=RH4u&qvKNzAZXomQJacnj!af1h zZFXm~6YXJOxH}3aT#)|J^s6qe7zN)4W;>}Bw?yHXc}JyLIU|fIXCvLG7{E5_i7uHzOXyOIF3U7<%o4F$U-1*G>>~mhb2|Z~tH} zeu7^WHjuM^9Mz_2w8)w5F>6#dd+a)a0HY#%3}-q)-pU@cM)rSB&#w66dqEgoL#*j} z`iJ59>x!P~4qtr{3#HJ#WtyFye@9E1Qn)5T&#r%$o>#rXAXxPLB1kzJQw}-F z+siMj?d9MBR3!@2a^NrYsYO6Gc{urHjWP1pRhunflMvk}u4`u+rIGhF=83(5j>*G? zyQx&XkGeDG9KfQM8;^|QFtUr-z9s+O$>is*mjq)9ks7}%mCw(@uSBWs-H%Oh`)P5} zF|v=vp=H5+7`BE_>3cXx=77XbalO+^AX5B=T z_MX(%{(~H6<8)fmsQlilHlF6lf(G^sQh@iRDmHruj}Ww=>>VWg#+GNqdpyEnE4+6$ zK8wGIdT=(rNOdHd;96k;LLcR06%|Yo{MdNg{H}hWM{<`E2~i`-de1fzEY1M)SOU|! z!vYWb36OXTh>?cK_7)FtK9Gihw}2PEM%!CFcoPpKz=x8u%RqvE910dtfZ*BhQ78BS zo<-1yURfa%I95NqhA|hx-saozMh9=hg9P*aN@ zVBUpAs9)kjmOs;`@d_Ob@;z9$>czerQeY<^+LZy2T}BkRNrCx#R0^K z_n3Yh?_~OY9)FpBU!pofzxNBvog)2y2oV!!B#Ym%jpQq$U$GBBsDC2hUDo;lPafs* z(RyW;v*{BA8`$xKM{nT~53oWn0wPS{nUahP6d_yQ-+d9B@KYi%y=3sN4Ben-@86S` ze??XcAk31Nv(fhHla~$Bx&}?t+pPEE&eGj5?P>Dt}6-zK6$*wefadGf3FP=+hCFyn*J)rYe#8R5=jAj}ShHSc-U$NAEMg zIvbxOERsHD2cpQMp~8%(NFQz`c%nw~2U*k#rGKULVbiDH{&tll`gAp(Dg7w&s|M?v zPoc}g{+1eN)BAR1hxN>1cYpUkRS5S?aI612%fgi%V~#?DW<@mfljV z_aH7LLWrif4;8(U=3shznd&J0CoFP`^!A|=JW(TAENkhnl-^ER{~@Z(&X1)3@Te>O z_p~MmrT>s^=CtX*KkBw+e`Ge=K8^ZMT92l;S&?o!yj^>g4$Mhd!!8N9#(|J9)8qXs1-wn`z$!T`|cLT!hq1&v*9`qmS zL(zY)bBXld3tXa`wD)kS8&14Ef|BUO?$C+dgHJ{3#M~I2_#>N9@1XD_?F^mKs;Q8i zp>FfeJ)pwUuuAMhE}!rbtnTm!k^kmo=dboZ$<1o4X#492N)ScOT6e4sEUQ-5gR@B4 z#n@ltEPV(2YcN%bw7=4<_t1w(KN;~Jj}Y*|>p?QRSRay?P3glQ2+N!z{k@_DQPfBZ z#K`|j<Xj2!{_j*3w?Oz6Pg%ABsK3oefsdvkL$KYAD)G_PoF-#L0XTd zx5-MdMa|N;#MG8q5_%+;yd8bHkmqt1WwPyd5S_gmi^6q5o!Le{v`uBc`_2B)F zX#x@A>-i)I;Y+5BJsj{kni zBbvNK{`~R}r&InMdRQn%p$zei7>zWeKgx^1=oL))iM-!(|lb~1f89j?SLrSBg9ohA^q{(}fPZTfDtG#*VK zLzUo(qK~aG`lIC0e_r1W6=pm|`fw}36E%`QsCDmGPaigY=f!eMV_NGwI=C94@8)>> zMC1o)$em+UsdX0aw=x#+z&FwjskTk|Z~rEeNZNl72}Nh0treQHkTE1^9roBK^q=;yfDsSk)d1!+`A;ACwNRj%PA$)_Re~#O zmR}RoS>zcaB!(m0Qr)m@h0PDISMltfh;4N4xc&pc8ZW+9URU5X)>ZHrU}0H8b*&v} zJUGXCi#6n!h_`rPBaE~+Z9whwUE*!@upp~qhR>@ESv&K$0?B1f}RFWQX$xV zw($ynM zh4>vl#T;`MLwh@b(h9Q-Jp)rD?qU)qoGDOYOG*g5SqNU}ES*kr|6Xm?RJ$#6L3D|M@Wi=; z3~4}w2EiSU`og&9H;5`#Jo)BfEMelrryrs3>!mG@`i(gY!cQYla4dVUw-xg<^y1t~`WHPp8_(jxWXK&ZAP9K~7iLO84|;YsX5tA3 zh!Es`zlsV$20m>3KxsvAZI1H(;a0E;bNYSY2Ty!{7>svd6vYo7bA#{)Ul5qld{JE} zi_aJEiFuAXHbkKLa#mOJz0!bPAk3ibtd*p2X+x0)! z<0k*D$9(;MjJs4<%{1758y>Ru-=1u|Int~inCN5N(WBgK>N+_+-ZEv{)5Vcd<3&!O zLC&pHK%h((&aI=l$Sg33i_8MOPy~rMw{_!Z%mU=UZ8!!NK+0wmzyi4J0tK)D(pRGZ z7Lcf4u)qs=61suu2}J1J`tWh9$NRa+J^q-B+~b`na*sRs8TaVpXXtSQ7trI4TtJV= z&@4S7Nh=EI5&5@?>8gcVE6l3}i+4miy-Lz)A4#X@sfoSR#BP#KfAC|K z(LUkLrEc@SU#R5Yl=34|KFHZcOFodk?0-*UeR=d}n)pOtULahe#Bl-Q(ZlDD_`F>s zIebpbdArS4*J7W%2ZB9~_DPO3ul10&N8c63){ph7v`kjx`n+8UMQww#3&WiKaC=9* z^LAe4p9mY@9N(>?uN;OgzI(^r8rR}a_{Y5%@M$}5ceb>y2d2mSi7>W)tjFtt;ZFt> zud-m({J-;d|L?qA7w7FLK9S572u!L8f`E65BuRr2~d4!6Y| z34_`FbvzfN@?_`nYN&<8=kXRuL(2TdX}6P}$D{LhU7g4K9IH36naTc+JwL}*dBl0V zr}0JN^LRhA+9PW_wmr7NBhGUg3ECrD+_|G#+5>N&>^$dWYA4QlynfPxoX1P&O3FW> zr3v(G_`Dr-ZS;9NJd*QvL_bi$d%i$Vw*6i_dH|1rMxn2}qTYkPcJh0qA;e!zUv(%$ zI>T7L>nBkfTb)SIar}=8?wmmzwH|ujRX-ysP6I_p|UWY~15)+FkrR^0*P_o3l=f*zE1jAL_43 z|5lr;@U_;zr{TQOE@@t)PV`Z=xe{IoQaslA53)O|&Al-8@t8n&Hc=9jAmz2f*`Y%J z-hAV5-KfayB{*p`5RykD6In7z+C-l;vQErUDqDR9CL*5j*#Br82pB7V3>+Yy@N|q{ zn$`og#;+U_*7%WfpT6Cryz~E&_a@*`Rp|9Ri{oO{o`nP8}c`Tm~2J`cHb&bjBF^PYDn`k)!O*gt9jrpKT3a3VgF&DPIzN*?8&NR8Z45k8WenG)MJLomBHd zFn0M-d}Wb6v3gK2R!z^iRbx@pc3eP#>Imib9Gs3kHv7pVNwz=o=XegtwR+yO;vT(P z|;OgBP=wqQ2&x;>)#I9&4V( z=QG`eQP~uaM9ITHCM9%*bnaw((pfa{4RLE2^T0vP@far{Hb0rdG)Pp_5zW(*Z_D(w z48uV^I?EHxXQX$UDBo|9BH zs!Z5CX9hbRL!LjV`R4R)iwmWH0S}*%)|!1cVVMRrhFIm8?*e-@3=7~!Y61q`Sh8>^ z4d0(C|9eD=oGKq(kK3lU5t2J#&Tu{}_b~VR|B%#bLWkItpmRsd_Tb$lB^seQBJ&($ z)JYo`uTAcOAan33`67OaJ&(-5vm7?Xa0uJ$zjjO{UW#h|CZC4ImvEn@B2nsKEs}Ky zV?V^y;)igmnln)yNSg-jy@T%fk=7E@n9`sZj1ZfyhacnGQ+ntp5U@lt(WZ zn@A$X#oB;1T+d*E{q$G$;JO1KfMBIvh`}W(w>{0@So>hZ6ci>ez(1U|nYNW&Jf$Ri z^U=k8`xjtll4K9lp#*scI6+=;^K~>1&$;oY1$J-Gq_zUkG^u2{e4oZdspg)20_9H= z6kiWEjNpEBzOd^lB_B@ap%D4P%*1^1U0=Si3Z%rlpVb#ZgkZxc^-TiW6G=%UEBV5D zfAbjm!jOZT;A0+x$8=_Ux~7DLG@baN;ZLLK@O=Qr#HZKqh#gp`^tB4)56jxnxg=g; zZeAmkO!B6s^of<(+u%3CK?&sa_wCjjYyy|8odUz&lvOQBmJjg9=n7^c$f#NKnv`m z@A8;<$RsH`ZoH5(>@j<0m4mLPnpD#qDX2eGgv5HU2V;E?OO7+9OK8NMqfJG~#Lh_q zIpHK_6`M?5P##TFUk>yE7vddFkW6Fo!XW^cx~?TTnU+(XM+S@9v9SWXJ~X0E6Q8qs z07lM`Bq7(tXW^m45d8Owjiqm30W`Bip0Q!{nNpH&fh3)#MyEj@fbjb#QPkR!?f6oQ zB^v@}kh<8Ud21lW+%lij{GRxP04$adIwNBH!lo@QA-8 zW#UzUP3@m3B?`*K(*oWCeR!QXwxeHr@^4-1d_wGz-vfCwXpe-Xd#%W6{q9BOHd77y#cJFYW1NzA4AujKzATqL1i1}cl(_hgfhSPzMRqdjg{{8(DeM?QRM8K zUzMo-Y|W0_(!!2iN;ISF*o7l8Mc16|1-=iAfjN5>d_LL&qf6%S<+yN$+}e-Wusu%ZJq#UbX!zL1ay}G_KZO?c;lOt$LNrt&_;$ z>uS<#T6?bx+j~3#;kr9$^fc%!XKE zw0DBe4-;b3{vUiIgY>dh zy4Qfx@T*bY4Ie++E~<%coYd*=su*DR4{S8Gl zWG6}1`6Q*DFRHOr`ch=1sj<$(0mZ4-IKE#hb}#Dr4&vU?HH!lg*eM9Jq6L9jmYBY< zc_-9hm`qb?DZbnjmR|he%=j+$2&T!md-BcAba{bwesppGNv5AUjExw(Ky*;pemxT1 zqHE3%M22#WeByjWuAy{BAcANew3pHu{NZuU{Gm4G?;UfiUVV~hmG75PBG56&Cdi z-g+VE7Exo&4T``6X3=jjg%bS5J&dynp78D+-t8H+!bYU9o(VC)b?l5t2@j&Pch>1P&vq`1hA1;9&Kq`5SIMy6InBERSe=@wn_}$V z)4aLtUv0~I-jH;xL89^ZMP{ynm!Nf>|#L$r0q*ke@6&{Y+LUyO?Kkg3glz0LcFc zL`k3DiOFQ`yL8$Fs1iF6jQtl;RD@8$2F@KOH`%<5LgK!KK&<2w_#yZj20i_b+N&iB z2V)P@Pr;trtVOug62C!D(}S_Q`6+u&Fm@+D<;)7kHtIo-{p84|Q@-{{I9$ zEj=Oh)azz1J*@^AWQ3l!)pN&sRWv=-DR-TZp578mRyb69(NkTNhulVb`nh5OzM1qf zXVQ-JboV~c6Q^IsC66IErAM{k(Dgoyn(1=rXdQC=HT;6Pu-XqTv`$OD0Y**wqM9@0 z%5x}D{0;Swdh5a8e(2Qaq5f`fJ^0&3lsI%oqGdG~NUlY247yvb#lW}u243P0JUJ0B zXzL5(4BO|e^T}$u%t|GcJ{@($a<;7}wX@Q`3DiK73x_s#z*Qq%ameH(95U&QLnf!D zvZiz4K@|QMh5sokh`fU+@7ZJ*Du5hcez1G$^w&urue6VVWHNR>u2?$PBh&PEL|k6F zSnR_}`zmFv8}jVDwDPRKBd@Ty-CN4DET`7R@ zB;tv3(#CK?uP7M%740;a55f!)v`(=wD7%l!@Vq9gKQ76^58NX@IvBf+pR)1v1!_=L z3%rXj+S^!Oi=Tn8ecw&M|5lDlythVUD`_89_B^2-d^E~O3UW{yEe_?t^S$C_*KuO? zhIXy;o2y*8n_fVu@F5EOl5qNR_xM0yL)i!3a{(NmmF_&mrOtTBo~g2*0K#i~CIjao z0@6Lfl&(?{`l+%XQ*Jxo`G6s!uo*EMZe^udN29}K_cfuUg`%&gTgl=C9r15Rh)yvV*Axm!Q{9#HJY2frR-Z1b{v z&x^SG8~f2$*dB}FwnlPm<(tZVQDwg&IH~0qzrPr5T6@39_x|1LeImP)=+uth-w;Gi z7thwW{?OE5jJ;-9{9tSx%nw3iBq$3)umIEVH;#xRN) zmwV4&R`1|QWBJ0-E`lT}gvd)HKWlA$KYN3BeH($;sVbL5cyHM~4|E#%C^!)1@8;OD z$q*Kstj zVsCtIt$#lR`MlMg5!Lq49gYBrd~Rc>qFS7FU2I*pu{^72B_1oRs*U9xidNupjx{?v zWdcX)6#ka}(ucFdRra3MeEk;)bwu>(9JolVf3b71PlB)hJe3e>8!d_6$}9%Zl?dYN za@!z-;1MW-1(plb2_R)&3Hfu7#S3p))n-i##**~AoW)HUi5rl4K*UCe==4wr#^fOY zcx#w^T|_Us2%nb)vzhTf1(b|CH*1FRB@{ynz4g4yS*$)Sq)_MWkV3Q38u4625P3up z1rqP<&;yi(p2YYLK`+1so_K&-(}^FToE{+@B|Zp%(tT_IDCl8dcv+yImlu|xq2h(f zXXzycsRETxmcH+tgyZ;5M>-h(+m+XAAdtR@vw5Hd(HAoyA41Z(9+{SR%EzFKMPJ1E zzb^=JRFQEY_S$)*M=Ae@njWB+USCx80BU-DQPl(1t>=@){Dnek_2BzS;$*_eq(UsMfS(HB*1EBd0U#RMondkcM0RRe`2zjZK1K>*3`;2++< zr%fp(Fa0-26dSsgiU)Awy6c)4lSBGUr4tX}V3QHw-tY4on?!$F4-(6u{iy%8m%Zu_ zf!$5UqAP?%=8Ir#B>i3_vNFW7vV>$Mg7ESp=aH;DD=ix-_OX#ME*rrKa;_&PrWZ}@BiarFc6Aob>*Yd(UaRCqi`Q^d2MR! z7Vg|MUoLY7204DFJF3K@z40nTs%iN_a*ZRj$Z9#6B*y8xN3%TJw9b`3vuhGXvve#*rg7x7aU^b*Ds zrjLEuYp^7(WJ$Va%YPrnmiUqaG}S z{OedKxZ=s%-ovN_$LQ=-l0W1ii3`WQ(C}~i$)3@Fkl#T(qp#wTkh~ttlKdT&J1-nh zzVd>VdfMWTSP62-m_Op76~YhH$b=tM+AF-V%?r?c zk}f4n!5{d##~e?IF`g|@*tTr44k!5(h5{kfsF@%d-a_i8?=q!BO`5+rJ4vSeTduKP z_CwbL`?dVbz<%hqtJC6po!2d=vE(f=T1|XE)6l0}RG}>-z)<#+PNF8>{Uq8#l6oL_ z3O~$x6urvz!G1e(bAZ3X;}sV_ zYJ_6ih#y~64m96P4lA>|9rqiBKBnTV6G>BpKS5KYAZ@DGTtwor9n>)R$6v41s3H7g z8VEjv{G(pF_v6PD<;L?t^?tFBnUBs$g!^C$Q>sV@tQM{gLQS>Y04glhTp#sd{Npfr z#rUBkRoj1jjsPd{g{?y!$t#_!>P<~Cq(tI;auM>YCQjw^BMKVOAJ?G3)E}4Q zH}pq@O)TZ7Z0LYh{6zZW9DeEuU2!@;k^VS=pE@H9<4inZk~rv>hdC^;W5*cmA1?O} zcAOrpQWcfK)G|~#d4thb24|4W6?vs1`Ga4k4&*O}% zKp+{*=do0J*F)3%v#(;5KKi&tY4FDW*7&siv(Vv>0Cu;Q{4?OxgMS|9eR6$;q}#|p zD;1;k&E!$BQ3Xd%kbDJ#9V=c#@NY2q=hqO5WZ>W6VC+_ucx7v=`Ch)c)QcZSK|-0s zk7DWFj~_>ro685)t3=gz<*U^@c`Xr_kzcNUf}&5Ee;3f-9BSgy--)8nn@k9C*v*xE>+KX;r0?DW<%>0{2MedrU3 zJb;%Zx+?9zJ>={nSZB5{v%cXV7Nq|SgX^E&)euAE;P?!B)N2VcAb$tQK*bW48I_;` zI#@21Oc?~^LK1p;z4vRpXwB&@$sLxM71{Y|I_z()CtM975~Larjf$Ie{&}7ga6UGy#d@{ z4;%p&!Z|S@G8~)n3gQE>S))T)hvF1&e}jJywYot^-2}C{(*ENkw5x=3*r90M{y>D4 z=U~I>xgdjZPm0J4;SsHIKswABE^?B?JFD~j@TYDnYg(R#ZXSEZal0Ux5eCW;j+`~>%%E?J;aPCVGZT?CZN%zdW;>DrHPk9pm4 znja4l)4{~IiR3(JaUNu#<3E6++=H5U{rA~UJ#^*cyzlfeA(1xX+Xv!a(-YJ)xmelF zClcSF-F&=%VXDs+JY|nAu>%Hh2`z(4Xjg+M)XcLdkUg z++bMalK6A4t;FJF?mmEr=6M*E=3grnqw-0`T}lU+h zf2cn`RA!xy56!Snl~3e8f{p{Yper{S!PDblcgn&Y2$i7^ik!IwV+Hh6s|@AA*bxj5 zpMsP51AfY0jCeSHqSJhT<)@BNE&hZj`CO;R5gZ+W2BD511xbVGmAnR@mH0G;ElR(Z z=_*_N)9@G9`$(~k30g8V8E+{5*`t?}pU9Xd-zjrS6kfr8=Rmys2M3VTn zPv2ks&RiZ_8~ORb^fS3llyt$7_R+7wAu8>KKWT2pP72czZR%Jfn1`8BMOz)TQpl9b z75*RZYzC#O9JbhFk{q^&fqj9-vL^Z?iD^ME!#Fi)s!Zqwr_1SJ#4yMYe@fat^iduE z%8?H)pk^C`c!0~_jJbq+p>U!d5>U6x`f1!K{PUZ>rud5e4`P8R6pCEdww!aAq zQ1qnxm-F5;)7=*_lk;v>4{E-ZvhgHZ+!wLud?6e)G@&Qt*cG3e9QNMM>dpNZRga=Q*sdmxbO-+O{`K$i!0`zNcOJDq3Zp(l3|>Ee0ZDWp4JRR5tJ$$DSe?8P1h@qqRM{WimPI;y{OKxt?~h3jD5NZi)u@lwH3h7b;x$e2n#0N0 zX!XmQ;yv*@`2r#6|B8=4DQECwKUeUvD+nrc_!ySn{i|4@*q?%+Ud0<=D6~6176^e- z_721^s}Z(}kEIvNkbN`xv9gQX5g(z0A|7civaB4!ijqB}#)zVWAL)3c;f6l1>W8P{ z$4gDj<$wf*xsMU= zS?zX}U%LgP+sH4vrJu=KF>^%{x1;=8_SIv_uRSJKm%3PO$glHmapc#i`#%l&_3ku{ z@`8_ZK{T0@Ut6SiKR#A~agd<$;p4+%8nmxIRUstQMtqDahnjCDdqq`lPkhX%{3;x1 zXkKR>6P4WvWh%Q7yd{*~2(B2s@gIKbjK1D>d?dMWkEnn; z>`xXRD_-Z5`bPvm3J~v;4-X9Kzsmn&#?zUG>{7x39ZhdKM@lh#&-=#{Pj}5^jS$3t zD(#o1I{ZiZGe1c@a!7jDQxklsv|m(=#^;|L58a*EQoH&`UKA8=qkkkj{Y+Mf65j6q zkzwya9@1IT0gq6$X{s-;@vi@>()>qoa4C@L%>IaR+ng*BlAo+Qks)5d>V@q$vGV}` zBQ1|u?M8xu5vW6j6H~jfNY-&SE>nr*Mh}+aJ|F0tYPCzPa~((K3~M;;ds+i29hub^ z{?ZcbG_}|H2*`rml^v5+K=Akh^pzhyI1Bd&&GPsw;d3?mgU_|Y0`j?Lms#++=9H7q zwPU6BxpsO)J3*5H^eW?<^$VT{xq-?_f=4K~KtOJ>(W zyO%rgojc^8k@=Oim|vMOzq#a7?y!*OH;d=Dkmq-yJ3ky{VJC005Tla$m0^DK9r%~Q zU#%JET=rKlWPkN6_E&S?aE!bDY6o_?kHB?Bedi{h!Gnx|XHl60OKiuda!7to@>a*ZyZvS(9{TDgouOK%*-~l&&_B?Xq=gcNIKKkzoCO_9Y zMWEgJ5!xug$UCQy(-_e;`3Rz+ORf?%Hr?iEhUN8*>eUjcvC{}j)?)sa7- zbwu`+0HmMXKmYDIns}Sus@M&G2VZZyVJ>agaY;_GC#)PkCA`xky{{XINlBMX|WH0 zF)jS_PdRAiix&NSfs69+Q6IWCRh!{UP+LPBpWzmpE*)OSV3Ci5~L{d(QLkJ&H8#Rq(ylqoy7dHU!)Vb92 z80E~s-#re>2`6J$wmO=6I(>lS^Yyk0h7h)Aj%{828HqY{yalX|B4eoP4&z(QV^b|w}(+qTc=<`Yj}$Bf5n!s*tm{lv2OGC~J8 z=Sx(td$w-s=++5g0P0La_<|6Ia1G#lA@F^*rs%@RHDGzHzJyYj8}@6L$Rbprc^;Zy zpquAKrU@0`e$WD|YYO+LC>Xl<4~1OgKW#Vx|BD&_@q_Wd>64%Do;FHPQQ<%6A(Qw& zQZU+&|I<9hI-a1DvaSvJ(hm49B->W}S0wAlf5=6Ll24GnBIxt6`2V%nf&VIH@a6(o ziyk>JZ3NIC89N{gY460i*EDPoM4gHLQ6UFG|C6H!r&_1u)gCn=yj-RPH-4;51$VG zxcV7@EH(ilvC_{Hi}p!rv;t6zl411p{2(!ynRBkD|A{qt}EAlv@n5jeDeH@sx7 z(Sw2n3ItamwWcCO?`ayp!YS1M0zW~;PEhD~s9GsS0jp4t4f!7i>yNAa5BPXy?31n1 zvmTU#|Kgz2#Tsk2E7Jg4G!hxwlM^jtE!CNO$`orxMCrL8T~yc}iI29H{3_^7m9{*q z>}wT)a;Dh=3fVj@c^|IeCVr=~cMS~2?m!88q-CqkLhALK@f-dXBvZeUpRy6yxRRgf ze8lDa)Da$(3;C%N%I4uoQUP`*x6dcYk5>WXOfW_A<2^7Gi#40Zn}Pgz`vwZy`SO`P zpcs=cpIHxOShQ?F(L58+ez9r@p?4}N?JrZdWhm{+KcQ+- zkS^Xy)hhpllg(J!lfW;KGujyZ3OE_Iv#|)R;qSZwdi4c=A5;X!14n$yaNzGNO5Uo^ z56At4B(EvoN4bQP{1BYL$+zpdfAPKkx_{Cqk^dO|Yke{U__am4*Q6!zW7O!kGBmh~6)_K09C4@KC>Yb?%zsx2v3 zPu6pP7Z!y0Z`0)qd=H^{LS1V9ptelTU%J;5b>^?!f_nbJ*!gVdU;%yxg+4in#*78T zd4ge)@whA8obRy6AXJ@<*d*ke>q)k?IxneuTN{jZ5c8X%xM+!nBJOi0FWA4}p+_9( zYiL8C&~Un+q=f?_<37F@`e_i+eSB5+D4=WV0{C+?fFG-*Z$ErSD@LQ?V+r!#C8|Dh zeT^g>$pMbs2-VcR%?!wkjD$_`a6YgB(@mZXLeNd;P*Z&`@*y~LsG>u)eGZkE(iiB} zi$VwqthOI);NT0s_|pep9k1$1Z_4qi4)h@Nbx7>yGc@!_->kCV1w|-*GXwA&E4};S z_m21kbg`zdDrFED{M(FIRnXS{WsZLXr|SlwvOiGOqbPtP;xEYbXXFb}^(bmRUbV`8 zM@XWL{Ci;fncOC3aN5if3RjtQegu z8F?ky*XJYI zfRuXYeb7~z`?4pb$2?b2qwky8UiM)i`8yJL%6dA!{hi;ypDXYd`>8i*Ff;bkQt4R_ zOWRL<6}$8K4{m`eK!7#kRVER$PQSmS`FU&mJDQ)jw!ee(2kG+X^%WM;M*l&j*TJYQ z!AC`@KcVp|?k0xp?*Pax{0CZYhwWWGKyR%W@4rEvOK+8SZ=pZ=05X&fgKhp2Ja>8- zhXrF#kRy+-Gz+^Hxse2YnbV^rZF#mAw7~laLG`9aWJ&58-5;=Z3Mn?|&$aehe{-yT zLx0P6`X>FY(q4hSS%1@Ae}elFAA*rY7^o0aiPxtTPG_72;M%2D}Y7`_vXNe z!iKju5#}A}l$mH@OAn}}Ke%4t{-^Fd09kzGH=2gS)JrwuOtTg>&dh<+pP61T_Ou6& zjq@@6oL*&rY(08S()I)FRgKKh=<#~;4g3QQC^!1i2*jt&boTQ`Sd;NB%vu=s68&Dl zddvmauR@w$zRz$ujlRRTyXZ7bBOwI%n3+P2k-^w==o4K-6bSEj^07XGk}d9cjQPxS zz9aK_5Co82*UccS|Rg_A5Y9IgIDYPpIu7kF;ZZTLHjxKJ*OT zF`vh)wAZ22%zWo`b&7F0@Dh}MOX=Z;em0(%RSW$LCb7%U9D7^!)~da&>cP)i zx3??pI-!U*^0S{S#^swyA9E({%Fp00eWd_+vJcT&V!w<6p)Ca_YF;ra7@Lou^>rTx ztka3WaS`)|vJX?Akp^^g*@vk`@dM*_Y^?YY^_?gF0Z}b86#Z`u3~mNOPJ9x#SNO7W z%wHB5;@y&*j+QzEu>#SP=*`)EX;mLvo3B}i=wojHV>6|X6-wV46q=rQ3*qWwEpN+} z>JIBJ8@{xWNH@mgnH$due=s9DSc`@EAh5XA)h3J5a;`pTZXwDf(? z8J=PdzfPj}V^r*mOd(^Wf?qL7Mmlhu7q88aH$pc{O@~1Xvsv7ea9O6twTKHV_M#V` z*vH2Ac>qH4UEsT(KEM{X&&5!g!>ZlIU~E6y=_U*8Q)Mc|VPEG8F!0Y4<&YH8`~Tz& zI6nURcoz+L**`x6U1pBIE|uOjqH6r@tJsf^E8U{3<#y$-eTC%O$X_ePU8g6gG3imL z+hj-m-_aL$L5DWc%QDVe{04PmEQp-zEyU=te>Z#W-}N73|2jP?`xnTYG5go)TiL(p zJ5&7jp-vvgpz(F3b_n?FUu7*D_}UKkuaH<9@%2793kWfNl$USL)2$BdUE`*$x4 z672Lm1Foh=d3^qny*V121wZCsuQyZpu|@jU1Jn3Xp%|ABst+q`xn1$2Ldd0!_z_i% z%Qus~(7HcuewJ+T1cgbw_9~RA{W~^czE|bguhv)=z@|re8+nxV5y$bsD5czCyL1<} zOG(gosIs$>jTtD%hR?0bc%M*MAL2=-MY^Syd-J@6C zVEw}V;=5V;i}&&u=h7GHdIjjibPlOYv}q*rja9n(-bkxl=A-Rc>msLTTtfj<#_fHJ z?mc)rd2;v*L~qIO5xMdh@u@)af6rfgrv8Gm_co*V%;_ULvD0a=KTW@zl&`{v^#{cE zH2qhfzP-08n}^&+`hD8#pws%uD03z!l)tyte^uUmv{ij%1nPupL?78GRODz=h=}>a ze}ERw2_h71LqU`h5e7+5k8}YtQp`X&M1YI5`M#|OkLpj0eVH$WG6xUO=cU8LWgX3g`s&^V=@<3Ta@BHlQnrzE;q5hyJAE23A;fFjrOeQB_E zff^$sr*c@k>pw&M5wT9!hZc;HuT8)1i9fp5`Ix}(_aL|o!Y?G{93m#PUPZhWwjalK0>(jP z#zDQo*gKenZnD7sChB?Q!Q21H6@c?cz(P;3E)-Of-dd0+?uVsRG#u5p}P#_ z1KxL#N9%{j1B#J2_#%9@hqBHM{d6K0wxvH4?qk=V*@K!G`!DckLPV*0I4_SgAMgW$ z;%$^~|L{7*^vaTs?{LZ!B;Uvx$eT+#en>%6#SaNpVYhgoj=xa#l+RTj5qr$%*Gu_) z{0u(7Uc%?s7xMY|66(*~(!jt;Eimvt z4+a|CYX@QiOmZ9!GD^*K$+5ea9CLYppe^*cN;=k~()2i5I~2V3^Igi?Ht=t9TKuCp zK!9KFlZ371$3N(}F8V&d?@)IS~`3pZ$K7*ZjI-Yz6j8-b)aJqa3OwXCiXRyr?mH?XXeAEAK51p}J zFM{}I*MY(^XAf<|<#2?zYUwn+)``zR7i%nk%hbbL#sjKOT8%eV^`CVA3-Sd+ZX><^ z-0RTOGwEZ_q%M5Ie(iBE3g|Qg%v`xIME!aixpu@P0*+Hok1HSm^par_ z5NfA()F`0`T|KNTu>urIBKkhkF(pUfm!t24U(yDU)s61%j0{Q-4CvTenG@gC^4(=+K~&ZK?tU*vfi{A<4Z z6UvY;Y4)H*37yrd;CxPDBcIcSPQIikfRmK%zGL!vJRx5a^Yaqq4?0iIS15i14a@0| zGX!;TvKwVT!9Ki_P^1eiNr7Ar;zsP?Omb2 zW88mZva&rsRyGT0r2}m!sE+(S^u3S2|0bzXN&Jh~?gc7k@ctW{yZ7VMO0OGEufi^6 z?Goe0H8*sOF9@e|xZ1fkDukb?B>O3Brv^zp$pOBVyvja(?qPPxE&pW~CiL@T(A)SZOsv60BYxB&RsXi2sxx-~oX5DiBqLomabszr9Wsp1DE_NrDAg zf{AZ&R}WgTTJlY(BzHVkm0J}VETUgRRYzPPt}c{2(fLk~TR-hO>_q6N>)z7f7yIl@ zAZX_F(?aRpkB_^xCGPd7l#9V`^0!ZaziPLTP#gJMxAZevD^{<}q}}8vQOnD7pj%4% z7G!^XX8*DgKqLFGeMFMFprpz!r(dPr<@o|*138~^{+^PIgEHLxhQi2NnO9ewL`S6J zH9r4Im&nCnB-rkud`0v1(&t6W1>;q|q6v<@Rv_2DdQ`_VTgP?g0kw;1R>>!bnw8oL zrSUk<7u@;}jrMB)1BjY|c)hXGtsaw>=kJKkuZuPRt$JU$wV^N4u5?R%ai}TJ5zFK8 z4?=~6yTkP#LP_w{!yZ1?`GR-gTx`2PVqp52+$L7;3B`AkugCCTqZn;oNx(wC$RY(n}H-XrweG)sMmIT7zDMSwU)Fcu(n)!D{0eAcJ_ z&5Lo?KY?+jx+#*}d41M7TiW8U-iAnPES^Rzjc;{|{V3AFe&VSZ7E{jOp)4mm1@tb48f*l zdmJbw!&KB%WAz36QyuFk)mYsqnZ}x|8Y{1UO$Q{hnoTVTCZ}l4@Hc>o!cPY>#`%i( z-qctk`r$kr9m}A8xJA16<4c8NL>gbk(jJjN&P-L2s%pJ*1$rCuR z)LO%k?|G#)0QsI*Sbccy&iF6s!LV7C@_x;9KJ*;DDHjk#3(*7ieiL%E6iLmgVV^{0 zZgfpy09afW3R6<(<$(qElzWJCU_*d)=f+Yx+7L*-hkrHrAbGdK4~3ucCljkeiP}Pu z|3#6`sR8vJm8z zMbJ0$A|q2lt#44{yhv}~bR;B`!BI(D0_$y=(9~@F_$4 z*@e=zhLq;dyTNz}9UOX_$7@!OyuzsxQIinfFc>j({dAkb7U^e|c30$MP4(q?`PqX~ zW4Y`WIQ0~r_7hrQ{C-nb#H7^MCw@L;ji0=_&}td&ztq8aUT$nwk!V|3gg?n`_(z00 z?)xl4{9RvHA1t!;M1YSt{(6LU5$JzK7(ZqBV;VDC^+$qdX(g)hQ~2QqL&5d`Q>m5x z((h2McfbA)ryj~pgRdS%pXYky3B+Gk8J5xmF^`kGLL@C|Ajo@x5a}CTHzN=M zUpUMNwfoH@&EZPwC{aG6d1uS{kJB5n?oRBvueQ$RdZ7-pb}|C5R%F47FNk*tw6BL@ zn(M^@^uC9mn3v;G;Q&oXh#28vkNsKqEqtXf`j&iTs`V$iuVblnuE(Y6x3AZ2r}g<; z#M)&LzBIz!8I${zO-y^v#57}PEcp-^spfAQI+FpN%Z0%*DNPQ>7UHJ~ogWYaInXim ziFIe$~Jbnl1H5%F^AVhx|G#MEF}JFMlk$|^tg8o;m0KI=9)z#Bq# zrDf5Z`eZ?l-LyDhjf>uNv6k1K8>-)OK+5cEVQ+whc>1dK@ z;JVpL8Sr#oc$(G*r1L)X2A^-bsWu<^`S3H^)WI61zqQ!;R$zqp&A#*|DS?!a`@g^0tVdy~)kHF1EE}Q83$JW`z z`$*GnvWW$Xv=2l&QL+`Q0CDeG;0vM;;mlh71aOLXKntXE;`b&vFDvcm4bnT4XWk~# z`!11~NpqKaX)f%exmM%jM*60a0Fh)!pV;{_Lr4>r=0}^nZZ=JC2b4ZdM(oGY+fy6< ze@AaacWJWn(Ocf1b;bWVdi&n^^gegk9YSx8TX`^~23_gp;7Ty|cl`9~#NPrryKXZ^KdhXz(wTggCNKZ%F}&_@I&{0_&fkFfKjaB z(?c1)MBmuISqh;8Cw`OKz0vnQTx&?r$t*nuejX_@t*2K^k@Qr2KDA2OpY$1z9QD0) z1>OnZTyf{?>$Q-??{%sfsw{%QqVV>sw?e&%rBe3x&MZ|IN0WCEJdbG zbS2`{+=nYR-^Wt>#nFbdSsW>0hC1*@asqKR}D>-_Z z7VGA&qT_2W3p7^KgI=e8EnoDkQ|rsI&4B^1GtF`w9LE{i5{vTV9n8>9xnGKWCAfqG zm#V3-PN2Pxk1P&#@gJc)j`-joJg=!#xK|fYZAScyVd>aUp9NmGqo&Ur{vu>+`1e2k z_}~IzDQx|otOxFR?sS3HBuW)aaXb^^Hv2Km24X-wlXl(Dx}h%xFkvjQX2jrv)EIe9 z9uXL2@EAoLibOy8eHZg~Xv|XlAHfBgsyrZGLcLfTzj}ySEvq8&tVkc3 zX8(vn&LJGcpMqF+zdwaS&ZQpC6WF$%dKBS1s8{#BQ2sLGzLy7tB-)5y|L{7*^i1X` zJG-6n>+td{#K>J+;`04GI+KU|XW-$DrHg@Xf#i$$hxxl&Bsd$Vp~9ZeB1OAGKwx`9Pm5LGhsCw6*q_x4HKK=dnl|oT%#Gm^VBl688-<(N% z@h9d)rzS$AhT*yf#L?)h8bWZf5hJa#-yXqkcr&c#k5c5MCw)dW(N`}l2{xPt{Bj+n zDCk6tPoIaLY6VjhU4QJ3QN5pfcmFbH4NeLT;76JDopi-ZL6~75@V|XOjOt zofdx|@Vepj{#_3!jxBwYd|5KX2Ky&VkdJ!!8NNmZL;%Xn4tX9yuYMc^mdn zwG7PJvHTSXEd%yYApLwF^SagG+H$3g_gL~OpR-4dhSBf zGBr%t)G62}$9*K|IMqY*7q!|bFX0c;mM6|1*1&PG1Fvc%K|T3Py@cATeErE2Ui|Hf ziDgc{hNX8sG>yLn;_=hPTC2H1Tk|wCcw?s`AWCH+L$t*Iz@{Dl#Qg*xhff*){s$k| z(-;061i@vHKZm7zJyB==Ubm#?&o{i-Du1pcaoE@y)4+MqZ=dWaT*25;ni{zpOk?|- z4u*LZdYJG#z{@@7C615Zt$R!ZOvOKL%Ot)OO7DJn?e@CCG`^I>m~gDD_TqQD1;yLQ z@4BU*$y#M6pGbZOHS6Q!!KOg|M@U4v@_$M&_8H{}Z6*8l^51HlkS)dMfb~h!-Jch! zNv+b0dVKiS)#+RC?Hcr*IsWv}Zv=xBYt{6*QZpiNyzMSgy-oYE9rS-8kv8JnePAg> zB0fza-<-(_(*HjO-&m;r5$W`xSQ1}LiTe%?KiUk$Vq@cRo598w4o4%!&5%)}Ie{H?b?Fi# zuyZAh8UON#Mt8AMUI&WOepLqUgDI4*)%^_<1RtyH-HJV^;R^pl9J1KPfMk`unR0

T9N-xD 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

q c #222328", +",q c #252323", +"'q c #34291D", +")q c #2A241B", +"!q c #070508", +"~q c #151C18", +"{q c #25342E", +"]q c #2D3F3D", +"^q c #747A7C", +"/q c #9F9EA3", +"(q c #888694", +"_q c #4F4E66", +":q c #4B4F66", +"r c #A5A49B", +",r c #92938A", +"'r c #858877", +")r c #858375", +"!r c #8C897D", +"~r c #AAA89D", +"{r c #C3C1BA", +"]r c #C7C6C1", +"^r c #C2C3BD", +"/r c #A9AA9F", +"(r c #7D7E6E", +"_r c #7B7D68", +":r c #848876", +"s c #8A92A2", +",s c #808395", +"'s c #75778F", +")s c #7D8094", +"!s c #7A7D93", +"~s c #5C5E79", +"{s c #5A5B7A", +"]s c #595C7B", +"^s c #535775", +"/s c #404364", +"(s c #484B6C", +"_s c #474A6A", +":s c #5A5E79", +"t c #111110", +",t c #14161B", +"'t c #4C5057", +")t c #5E4929", +"!t c #764D16", +"~t c #463A29", +"{t c #303035", +"]t c #1E1F22", +"^t c #1E2024", +"/t c #222327", +"(t c #242427", +"_t c #181310", +":t c #1E1612", +"u c #626883", +",u c #767E95", +"'u c #7D7989", +")u c #857C83", +"!u c #81716F", +"~u c #7E706B", +"{u c #867D75", +"]u c #777C71", +"^u c #727C6E", +"/u c #75796B", +"(u c #676B58", +"_u c #575D51", +":u c #535A4A", +"v c #6E718A", +",v c #515376", +"'v c #43466B", +")v c #3D4167", +"!v c #3B3F65", +"~v c #32375F", +"{v c #575A7B", +"]v c #868FA5", +"^v c #C4C9D1", +"/v c #DFE3E3", +"(v c #E0E1DF", +"_v c #E0E0DE", +":v c #E0E1E3", +"w c #675649", +",w c #332A28", +"'w c #131117", +")w c #302C34", +"!w c #382B23", +"~w c #5D3F1C", +"{w c #5E4E3B", +"]w c #4D5262", +"^w c #161B1E", +"/w c #18191B", +"(w c #343735", +"_w c #353937", +":w c #323635", +"x c #8D8FA0", +",x c #51536F", +"'x c #494D6D", +")x c #5B5F7B", +"!x c #646680", +"~x c #585D79", +"{x c #5E5A6C", +"]x c #978A8E", +"^x c #BAB8B4", +"/x c #C5CBC0", +"(x c #C1C5B9", +"_x c #ABACA4", +":x c #AAA6A1", +"y c #2E2B31", +",y c #2B2D30", +"'y c #302F34", +")y c #26231F", +"!y c #20272C", +"~y c #2E353F", +"{y c #1E1A1A", +"]y c #171515", +"^y c #16181D", +"/y c #232328", +"(y c #252529", +"_y c #2B3231", +":y c #101A20", +"z c #A1A096", +",z c #AFB1A9", +"'z c #A4A6A0", +")z c #919388", +"!z c #A3A696", +"~z c #9A9C8C", +"{z c #767B6B", +"]z c #565D4D", +"^z c #464C3E", +"/z c #4E4F3F", +"(z c #64604D", +"_z c #6F6557", +":z c #746D5D", +"A c #BBB8BF", +",A c #A3A2AD", +"'A c #9093A4", +")A c #8F92A4", +"!A c #45496C", +"~A c #383C62", +"{A c #393E63", +"]A c #AFB2BF", +"^A c #DFDDDD", +"/A c #E2E2E1", +"(A c #E1E2E1", +"_A c #C4C8CD", +":A c #C0C4C8", +"B c #584C3F", +",B c #2C282A", +"'B c #1C1C21", +")B c #151211", +"!B c #573A17", +"~B c #76501C", +"{B c #322D2B", +"]B c #292628", +"^B c #593B1C", +"/B c #6A502C", +"(B c #5A626E", +"_B c #282A2F", +":B c #0E0E14", +"C c #7E8096", +",C c #87899F", +"'C c #878A9D", +")C c #7F8298", +"!C c #5D617D", +"~C c #464968", +"{C c #454869", +"]C c #494C6B", +"^C c #545876", +"/C c #585B74", +"(C c #70677A", +"_C c #8E7C79", +":C c #919188", +"D c #2D2D34", +",D c #2D2E31", +"'D c #24262B", +")D c #26262D", +"!D c #31282C", +"~D c #352F24", +"{D c #2A2C2D", +"]D c #36373C", +"^D c #3B3B40", +"/D c #3B3A3F", +"(D c #191F27", +"_D c #192426", +":D c #2C3B38", +"E c #AFB2A9", +",E c #C9CCC3", +"'E c #D1D2CC", +")E c #CBCCC5", +"!E c #C4C5BC", +"~E c #B3B4A9", +"{E c #AEAD9F", +"]E c #A7AFA1", +"^E c #9CA495", +"/E c #8B8F7F", +"(E c #7F7E6E", +"_E c #938C7B", +":E c #938C79", +"F c #B6B8C3", +",F c #B8B9C6", +"'F c #ADAEBB", +")F c #A7A8B3", +"!F c #B6B7C2", +"~F c #C0C0CA", +"{F c #C4C4CD", +"]F c #C6C7CD", +"^F c #C4C6CB", +"/F c #A2A5B1", +"(F c #8F92A1", +"_F c #BBBEC1", +":F c #AFB3B7", +"G c #372815", +",G c #2E1F0E", +"'G c #181516", +")G c #1F1D1F", +"!G c #353035", +"~G c #342E34", +"{G c #332D33", +"]G c #302C32", +"^G c #040402", +"/G c #1A1F1C", +"(G c #1A1E1F", +"_G c #212322", +":G c #0F0E13", +"H c #807D8C", +",H c #9E9191", +"'H c #A29993", +")H c #9E9D99", +"!H c #8F8F92", +"~H c #898890", +"{H c #9F9BA2", +"]H c #B4B2B5", +"^H c #C0B9B5", +"/H c #C3BDB7", +"(H c #C4C4BF", +"_H c #CACCC5", +":H c #CECDCF", +"I c #9093A3", +",I c #85899B", +"'I c #5D607C", +")I c #4C4F71", +"!I c #484C6F", +"~I c #474B6B", +"{I c #505271", +"]I c #676883", +"^I c #6D6E87", +"/I c #808197", +"(I c #656A82", +"_I c #77798F", +":I c #9D9EAC", +"J c #866341", +",J c #7D5B32", +"'J c #766142", +")J c #827B6E", +"!J c #78756D", +"~J c #57514A", +"{J c #4B351A", +"]J c #664A20", +"^J c #3B2F27", +"/J c #212126", +"(J c #202025", +"_J c #2E241E", +":J c #4D3718", +"K c #464B6B", +",K c #575977", +"'K c #6F6D80", +")K c #817A79", +"!K c #968887", +"~K c #AAA198", +"{K c #9B9B89", +"]K c #9CA392", +"^K c #7C8175", +"/K c #72685F", +"(K c #88746B", +"_K c #898184", +":K c #707586", +"L c #1E2825", +",L c #171F22", +"'L c #1E2829", +")L c #38453D", +"!L c #3D4C43", +"~L c #334036", +"{L c #29352B", +"]L c #2E3A39", +"^L c #27333C", +"/L c #272D39", +"(L c #373C43", +"_L c #505458", +":L c #575A5B", +"M c #616950", +",M c #595E42", +"'M c #929380", +")M c #C0C0B7", +"!M c #C1BEB5", +"~M c #B7AFA6", +"{M c #D1CDC1", +"]M c #DCD6D0", +"^M c #C8C1B8", +"/M c #A8A398", +"(M c #BCB2A9", +"_M c #BCA999", +":M c #B29B87", +"N c #373B5D", +",N c #565977", +"'N c #676982", +")N c #7D7E94", +"!N c #666783", +"~N c #434765", +"{N c #373C5E", +"]N c #444869", +"^N c #606480", +"/N c #5C5B73", +"(N c #817780", +"_N c #91837B", +":N c #9C9083", +"O c #3A3B43", +",O c #3D3F46", +"'O c #3C3F45", +")O c #3B3E44", +"!O c #3E4149", +"~O c #3E4248", +"{O c #252D29", +"]O c #1D2621", +"^O c #121D1A", +"/O c #121E1E", +"(O c #182826", +"_O c #2A3A31", +":O c #2A362D", +"P c #BFC0B1", +",P c #C3C0B9", +"'P c #B9B5AE", +")P c #B3B6AA", +"!P c #BFC2B6", +"~P c #BBBDB2", +"{P c #BBBCB1", +"]P c #B0B1A4", +"^P c #949783", +"/P c #7B856D", +"(P c #747D64", +"_P c #A8A799", +":P c #DEDCD6", +"

Q c #464C6B", +",Q c #5C617A", +"'Q c #74758B", +")Q c #4E506E", +"!Q c #303356", +"~Q c #252A4F", +"{Q c #292D52", +"]Q c #292D51", +"^Q c #2C3051", +"/Q c #2E3353", +"(Q c #444769", +"_Q c #616580", +":Q c #777C91", +"R c #292629", +",R c #100E15", +"'R c #150F11", +")R c #362C1F", +"!R c #3F3426", +"~R c #5C6973", +"{R c #576069", +"]R c #493D30", +"^R c #474249", +"/R c #40434A", +"(R c #41444B", +"_R c #393C43", +":R c #35393F", +"S c #A0A293", +",S c #A3A495", +"'S c #A5A794", +")S c #8F927D", +"!S c #949582", +"~S c #939485", +"{S c #8C8E7A", +"]S c #969482", +"^S c #827F68", +"/S c #92917D", +"(S c #E4E5DE", +"_S c #F2F0EC", +":S c #EFEAE7", +"T c #2E3257", +",T c #5F647F", +"'T c #A2A5B0", +")T c #BCBEC4", +"!T c #AFB2BE", +"~T c #7E8294", +"{T c #73788F", +"]T c #6F738B", +"^T c #5F637D", +"/T c #484C6B", +"(T c #74758E", +"_T c #2E3253", +":T c #25294D", +"U c #34373C", +",U c #463B2D", +"'U c #7E581E", +")U c #978A77", +"!U c #8896A1", +"~U c #343837", +"{U c #2D272D", +"]U c #211E24", +"^U c #302B32", +"/U c #222322", +"(U c #1E1E25", +"_U c #241F20", +":U c #584127", +"V c #A69D98", +",V c #B0A398", +"'V c #C4BDB2", +")V c #C9CABF", +"!V c #BAB7AC", +"~V c #B6B5A7", +"{V c #B1B7A7", +"]V c #A1A795", +"^V c #909782", +"/V c #8B907A", +"(V c #7C816B", +"_V c #7E836D", +":V c #838671", +"W c #383A4F", +",W c #595965", +"'W c #726B70", +")W c #7E7579", +"!W c #82838E", +"~W c #8F919C", +"{W c #8A8C97", +"]W c #888B95", +"^W c #848693", +"/W c #8D8F9A", +"(W c #9496A2", +"_W c #8B8D9B", +":W c #64667B", +"X c #95816B", +",X c #9E8770", +"'X c #A38C74", +")X c #8D7A65", +"!X c #7D6F5D", +"~X c #796D5E", +"{X c #685B4E", +"]X c #3E3026", +"^X c #493E34", +"/X c #514C41", +"(X c #39383F", +"_X c #39393F", +":X c #3D3E42", +"Y c #797B8F", +",Y c #7D7277", +"'Y c #84736E", +")Y c #74706F", +"!Y c #7B7D8B", +"~Y c #A1A3AB", +"{Y c #91909A", +"]Y c #ABA5A9", +"^Y c #C7C3BE", +"/Y c #C6C1BE", +"(Y c #95908A", +"_Y c #6C6761", +":Y c #444053", +"Z c #343630", +",Z c #30342E", +"'Z c #313730", +")Z c #1C1920", +"!Z c #1D1B21", +"~Z c #25242B", +"{Z c #303038", +"]Z c #3D3D45", +"^Z c #3F4048", +"/Z c #3A3D44", +"(Z c #373A40", +"_Z c #32353A", +":Z c #3C3D41", +"` c #92947D", +",` c #888A73", +"'` c #AAAB9A", +")` c #A0A18F", +"!` c #8C8F7A", +"~` c #9DA18D", +"{` c #BDC1B3", +"]` c #B0B4A5", +"^` c #DADBD3", +"/` c #D4D4CB", +"(` c #CDCBC1", +"_` c #E8E5E1", +":` c #F7F3F0", +"<` c #F2EEEB", +"[` c #E2DCD6", +"}` c #D0C5BB", +"|` c #937B65", +"1` c #795C3F", +"2` c #7A6446", +"3` c #6D6A4A", +"4` c #626D50", +"5` c #647558", +"6` c #6C765D", +"7` c #A3A090", +"8` c #A29A86", +"9` c #9C8E76", +"0` c #7E7165", +"a` c #625649", +"b` c #856D55", +"c` c #9D8066", +"d` c #8A745D", +"e` c #7A6B59", +"f` c #6F6456", +"g` c #41372D", +"h` c #4A423A", +"i` c #585149", +"j` c #4C4A46", +"k` c #3D3B3B", +"l` c #5A4736", +"m` c #885C22", +"n` c #46341A", +"o` c #211F1F", +"p` c #0E0D0D", +"q` c #151313", +"r` c #41423D", +"s` c #434542", +"t` c #3E403D", +"u` c #3F413E", +"v` c #42433F", +"w` c #3A3935", +"x` c #332E30", +"y` c #0E0C11", +"z` c #2F2B2F", +"A` c #2F2E2D", +"B` c #333630", +"C` c #363A32", +"D` c #43473D", +"E` c #363932", +"F` c #303630", +"G` c #2D312F", +"H` c #292C29", +"I` c #18171C", +"J` c #201F25", +"K` c #2B2B31", +"L` c #33333A", +"M` c #37373F", +"N` c #3C3C44", +"O` c #43464D", +"P` c #43454F", +"Q` c #3E404A", +"R` c #3A3B3C", +"S` c #0C0F15", +"T` c #02040F", +"U` c #010313", +"V` c #010415", +"W` c #010417", +"X` c #02041A", +"Y` c #01031B", +"Z` c #02041D", +"`` c #04061F", +" . c #050720", +". . c #040521", +"+ . c #0A0B29", +"@ . c #1F213D", +"# . c #4A4C5E", +"$ . c #69697A", +"% . c #777985", +"& . c #797B87", +"* . c #70727F", +"= . c #727483", +"- . c #7D7F8C", +"; . c #838591", +"> . c #8E9199", +", . c #92959E", +"' . c #8C8E9A", +") . c #8A8B98", +"! . c #7B7D8F", +"~ . c #5B5E75", +"{ . c #373A58", +"] . c #313454", +"^ . c #343756", +"/ . c #313457", +"( . c #333758", +"_ . c #262A4D", +": . c #22264B", +"< . c #202549", +"[ . c #1D2146", +"} . c #333555", +"| . c #818395", +"1 . c #A9ACB4", +"2 . c #A3A6AC", +"3 . c #A3A6B0", +"4 . c #9597A5", +"5 . c #7F8196", +"6 . c #595D79", +"7 . c #1C2044", +"8 . c #101437", +"9 . c #242B4F", +"0 . c #474B64", +"a . c #5C605D", +"b . c #596256", +"c . c #566166", +"d . c #606B6F", +"e . c #5A6072", +"f . c #383B5B", +"g . c #2A2D4F", +"h . c #2A2D4D", +"i . c #25274C", +"j . c #333656", +"k . c #595B74", +"l . c #6E758E", +"m . c #63667F", +"n . c #A6A4A5", +"o . c #B2B4BA", +"p . c #ADADB4", +"q . c #BEB9B8", +"r . c #BDBAB4", +"s . c #818082", +"t . c #5B535B", +"u . c #725D62", +"v . c #37344E", +"w . c #252846", +"x . c #7D8091", +"y . c #403C59", +"z . c #7D716D", +"A . c #A39588", +"B . c #CEC6BC", +"C . c #D8D2CA", +"D . c #CFC8BF", +"E . c #C8C7B8", +"F . c #ADA998", +"G . c #9A9886", +"H . c #919683", +"I . c #A5A796", +"J . c #9EA18C", +"K . c #959880", +"L . c #9DA18B", +"M . c #A4A792", +"N . c #A5A694", +"O . c #989A85", +"P . c #888B75", +"Q . c #AAAC9B", +"R . c #D0D1C7", +"S . c #C7C9BC", +"T . c #DADBD4", +"U . c #DAD9D1", +"V . c #D2CFC5", +"W . c #E9E6E1", +"X . c #F5F2ED", +"Y . c #F3EFEA", +"Z . c #DED8D2", +"` . c #C2B7AD", +" .. c #836A54", +"... c #70512D", +"+.. c #6E512D", +"@.. c #5E502B", +"#.. c #646746", +"$.. c #6C7A5D", +"%.. c #7E8972", +"&.. c #A6A897", +"*.. c #A6A089", +"=.. c #A19178", +"-.. c #7D7066", +";.. c #464344", +">.. c #6C5D51", +",.. c #9F8266", +"'.. c #977C63", +").. c #87725E", +"!.. c #726451", +"~.. c #473F2F", +"{.. c #484038", +"].. c #5D5450", +"^.. c #55524F", +"/.. c #474238", +"(.. c #594A31", +"_.. c #5E4A30", +":.. c #423D39", +"<.. c #37393E", +"[.. c #3A3C40", +"}.. c #323438", +"|.. c #3E413E", +"1.. c #424540", +"2.. c #414340", +"3.. c #42443E", +"4.. c #484843", +"5.. c #43433E", +"6.. c #363433", +"7.. c #332F31", +"8.. c #322E32", +"9.. c #323231", +"0.. c #353631", +"a.. c #343830", +"b.. c #2E322B", +"c.. c #31362F", +"d.. c #393E35", +"e.. c #2F332D", +"f.. c #2D332D", +"g.. c #2A2F2C", +"h.. c #222525", +"i.. c #343439", +"j.. c #35353A", +"k.. c #3C3D45", +"l.. c #42434B", +"m.. c #484A51", +"n.. c #494C54", +"o.. c #4C4F57", +"p.. c #464850", +"q.. c #0F1317", +"r.. c #040710", +"s.. c #010311", +"t.. c #000313", +"u.. c #010416", +"v.. c #020417", +"w.. c #01031C", +"x.. c #03051E", +"y.. c #03041F", +"z.. c #070824", +"A.. c #10122E", +"B.. c #20243D", +"C.. c #4D5260", +"D.. c #6F727F", +"E.. c #737582", +"F.. c #717380", +"G.. c #6A6C78", +"H.. c #757885", +"I.. c #8D9099", +"J.. c #8A8D97", +"K.. c #80828E", +"L.. c #828391", +"M.. c #777989", +"N.. c #6C7181", +"O.. c #53566F", +"P.. c #3B3E5B", +"Q.. c #363A58", +"R.. c #2F3454", +"S.. c #292E4F", +"T.. c #2A2E4F", +"U.. c #202446", +"V.. c #202448", +"W.. c #1E2245", +"X.. c #4E506A", +"Y.. c #A3A5AC", +"Z.. c #A5A7AF", +"`.. c #9A9CA8", +" +. c #7A7C8F", +".+. c #393D5C", +"++. c #15183F", +"@+. c #04082E", +"#+. c #0A0F33", +"$+. c #2A3151", +"%+. c #50536B", +"&+. c #66675F", +"*+. c #585C4F", +"=+. c #5C6755", +"-+. c #6F7769", +";+. c #616575", +">+. c #3F4262", +",+. c #292C4F", +"'+. c #292B50", +")+. c #555875", +"!+. c #878899", +"~+. c #787B8C", +"{+. c #696E84", +"]+. c #5F6169", +"^+. c #7A7873", +"/+. c #9A9CA5", +"(+. c #8F8992", +"_+. c #9A918D", +":+. c #626065", +"<+. c #554E5E", +"[+. c #705A5F", +"}+. c #413C53", +"|+. c #24264B", +"1+. c #2E3051", +"2+. c #64677A", +"3+. c #4A4C6A", +"4+. c #787680", +"5+. c #A1978D", +"6+. c #C2BCB0", +"7+. c #DDDAD4", +"8+. c #D5D1C9", +"9+. c #C4C3B4", +"0+. c #94927E", +"a+. c #82836D", +"b+. c #83866F", +"c+. c #8F8D7A", +"d+. c #938F7A", +"e+. c #8F8D74", +"f+. c #9E9A87", +"g+. c #B1AD9A", +"h+. c #A19F8B", +"i+. c #93957E", +"j+. c #8C8F78", +"k+. c #82856E", +"l+. c #BCBDAE", +"m+. c #DADAD2", +"n+. c #CCCCC2", +"o+. c #CBCBC1", +"p+. c #CFCEC3", +"q+. c #C1BEB4", +"r+. c #E4E1DA", +"s+. c #F7F4EE", +"t+. c #F4F1EA", +"u+. c #DCD6CE", +"v+. c #B0A499", +"w+. c #826954", +"x+. c #6C4B2B", +"y+. c #6E4C29", +"z+. c #634A27", +"A+. c #635B3A", +"B+. c #757B5E", +"C+. c #767E69", +"D+. c #A0A28F", +"E+. c #AAA48A", +"F+. c #A59279", +"G+. c #8C7868", +"H+. c #3E3B42", +"I+. c #474448", +"J+. c #927965", +"K+. c #9C7C63", +"L+. c #90755E", +"M+. c #7C6953", +"N+. c #59503D", +"O+. c #4A4339", +"P+. c #5F5856", +"Q+. c #474444", +"R+. c #3B3B42", +"S+. c #383940", +"T+. c #393741", +"U+. c #35383F", +"V+. c #35383E", +"W+. c #32333A", +"X+. c #2B2931", +"Y+. c #1C1B20", +"Z+. c #404443", +"`+. c #454945", +" @. c #474A47", +".@. c #444743", +"+@. c #424441", +"@@. c #484A42", +"#@. c #45463F", +"$@. c #41403D", +"%@. c #3B3938", +"&@. c #323031", +"*@. c #332E33", +"=@. c #2F2B32", +"-@. c #313132", +";@. c #393B36", +">@. c #33352F", +",@. c #2F322B", +"'@. c #2A2F29", +")@. c #1A191E", +"!@. c #2C2E34", +"~@. c #3B3D43", +"{@. c #3F4148", +"]@. c #44474E", +"^@. c #4E5158", +"/@. c #51545D", +"(@. c #51545C", +"_@. c #090D11", +":@. c #060A12", +"<@. c #000410", +"[@. c #000312", +"}@. c #000314", +"|@. c #020416", +"1@. c #03051F", +"2@. c #0F112B", +"3@. c #1F2139", +"4@. c #3E3F56", +"5@. c #646674", +"6@. c #6D6F7C", +"7@. c #626474", +"8@. c #676979", +"9@. c #616373", +"0@. c #616374", +"a@. c #666978", +"b@. c #7A7C88", +"c@. c #7F8090", +"d@. c #707283", +"e@. c #616576", +"f@. c #62647B", +"g@. c #4B4D68", +"h@. c #353956", +"i@. c #222649", +"j@. c #1F2346", +"k@. c #181D41", +"l@. c #626379", +"m@. c #9D9EA8", +"n@. c #A3A6AD", +"o@. c #A3A4AD", +"p@. c #838595", +"q@. c #62637F", +"r@. c #4A4B69", +"s@. c #3D415D", +"t@. c #61657F", +"u@. c #1A1D41", +"v@. c #070B31", +"w@. c #080C31", +"x@. c #1A1E41", +"y@. c #5F615F", +"z@. c #585A4A", +"A@. c #666C5D", +"B@. c #636A6C", +"C@. c #53556E", +"D@. c #3A3E5D", +"E@. c #2E3153", +"F@. c #323556", +"G@. c #333657", +"H@. c #60647C", +"I@. c #808294", +"J@. c #5F6478", +"K@. c #565969", +"L@. c #53544C", +"M@. c #595A54", +"N@. c #666975", +"O@. c #5F5F75", +"P@. c #655C68", +"Q@. c #786261", +"R@. c #66585E", +"S@. c #44425A", +"T@. c #514E66", +"U@. c #454861", +"V@. c #252A4B", +"W@. c #292D4D", +"X@. c #6E7287", +"Y@. c #7B7A87", +"Z@. c #989385", +"`@. c #B3AEA1", +" #. c #D0D0C7", +".#. c #C2C2B7", +"+#. c #AAA493", +"@#. c #898673", +"##. c #707159", +"$#. c #8C8A71", +"%#. c #97907E", +"&#. c #9E9381", +"*#. c #948F74", +"=#. c #998F7A", +"-#. c #9E9680", +";#. c #8F8A73", +">#. c #85856A", +",#. c #8B8E75", +"'#. c #949782", +")#. c #C3C5B7", +"!#. c #DBDBD4", +"~#. c #C9CABE", +"{#. c #9E9B8A", +"]#. c #C6C2B3", +"^#. c #C4BFB5", +"/#. c #E4E1DB", +"(#. c #FAF7F2", +"_#. c #F4F1EB", +":#. c #D5CCC0", +"<#. c #9F8C7A", +"[#. c #80624B", +"}#. c #6C4A2D", +"|#. c #694721", +"1#. c #6A4821", +"2#. c #755A35", +"3#. c #766B4B", +"4#. c #6E6A57", +"5#. c #867F6E", +"6#. c #AC9E84", +"7#. c #AA9578", +"8#. c #A68D75", +"9#. c #635C5A", +"0#. c #3B3F47", +"a#. c #766860", +"b#. c #93745F", +"c#. c #95775E", +"d#. c #886F5A", +"e#. c #685A47", +"f#. c #524C3E", +"g#. c #574E48", +"h#. c #605956", +"i#. c #4B4B48", +"j#. c #3F3E41", +"k#. c #3B3E45", +"l#. c #3B3D45", +"m#. c #3A3A43", +"n#. c #323338", +"o#. c #1D1D22", +"p#. c #303433", +"q#. c #3D423F", +"r#. c #3D4140", +"s#. c #424644", +"t#. c #444946", +"u#. c #444945", +"v#. c #414541", +"w#. c #474A43", +"x#. c #52554D", +"y#. c #484A44", +"z#. c #42413D", +"A#. c #383734", +"B#. c #353135", +"C#. c #2A252C", +"D#. c #1B1B17", +"E#. c #141410", +"F#. c #2D2A30", +"G#. c #2E2C30", +"H#. c #323532", +"I#. c #313530", +"J#. c #2E322D", +"K#. c #2C302B", +"L#. c #2C312C", +"M#. c #1A171D", +"N#. c #1B191F", +"O#. c #151519", +"P#. c #1F2125", +"Q#. c #1D1F25", +"R#. c #0B0D14", +"S#. c #03060E", +"T#. c #00030D", +"U#. c #000211", +"V#. c #02041C", +"W#. c #090B24", +"X#. c #1A1C33", +"Y#. c #303247", +"Z#. c #525365", +"`#. c #656772", +" $. c #5E606F", +".$. c #515264", +"+$. c #595A6C", +"@$. c #555768", +"#$. c #5D606F", +"$$. c #6C707D", +"%$. c #828490", +"&$. c #848592", +"*$. c #646678", +"=$. c #494C63", +"-$. c #4B4D65", +";$. c #4E4F6A", +">$. c #313653", +",$. c #1F2345", +"'$. c #191D40", +")$. c #1D2144", +"!$. c #171B3E", +"~$. c #1A1F42", +"{$. c #6B6C81", +"]$. c #9D9FA7", +"^$. c #A1A4AB", +"/$. c #A3A6AE", +"($. c #5B5E76", +"_$. c #4C4F69", +":$. c #545570", +"<$. c #5A5B77", +"[$. c #444664", +"}$. c #111539", +"|$. c #2D3252", +"1$. c #383D5C", +"2$. c #474D67", +"3$. c #5C5D5F", +"4$. c #646350", +"5$. c #696D63", +"6$. c #565F6B", +"7$. c #50536C", +"8$. c #383C59", +"9$. c #202545", +"0$. c #353859", +"a$. c #313455", +"b$. c #222748", +"c$. c #222746", +"d$. c #454864", +"e$. c #3D415E", +"f$. c #565A67", +"g$. c #606158", +"h$. c #606164", +"i$. c #494A59", +"j$. c #635967", +"k$. c #5C505B", +"l$. c #5E4E55", +"m$. c #60555F", +"n$. c #3E4059", +"o$. c #6B7086", +"p$. c #3D405A", +"q$. c #5F6177", +"r$. c #89899B", +"s$. c #8E8E9B", +"t$. c #888381", +"u$. c #9D9685", +"v$. c #AAA496", +"w$. c #C3BFB5", +"x$. c #ABA79B", +"y$. c #918674", +"z$. c #857A66", +"A$. c #7C7A65", +"B$. c #ABA694", +"C$. c #ACA492", +"D$. c #9B987D", +"E$. c #8B846C", +"F$. c #847F66", +"G$. c #79765C", +"H$. c #6F6E54", +"I$. c #7D7D67", +"J$. c #ADAE9C", +"K$. c #D0D0C4", +"L$. c #D6D6CD", +"M$. c #BFC0AF", +"N$. c #7A7964", +"O$. c #A2A08E", +"P$. c #C8C5B8", +"Q$. c #E9E7E1", +"R$. c #FAF8F3", +"S$. c #CFC3B4", +"T$. c #8B755F", +"U$. c #7E5E43", +"V$. c #735131", +"W$. c #6E4920", +"X$. c #734920", +"Y$. c #855E2F", +"Z$. c #7D694E", +"`$. c #5C5754", +" %. c #625959", +".%. c #9F8A75", +"+%. c #AA9072", +"@%. c #AD9377", +"#%. c #9B8C79", +"$%. c #615B56", +"%%. c #7A6451", +"&%. c #8D7360", +"*%. c #6D5D49", +"=%. c #645D4A", +"-%. c #60574B", +";%. c #645E59", +">%. c #51514C", +",%. c #40424A", +"'%. c #404048", +")%. c #3D3E44", +"!%. c #393A3E", +"~%. c #37383D", +"{%. c #343836", +"]%. c #3E4241", +"^%. c #424642", +"/%. c #464A43", +"(%. c #50534C", +"_%. c #454841", +":%. c #40413C", +"<%. c #383836", +"[%. c #302E32", +"}%. c #292A2B", +"|%. c #323734", +"1%. c #2D332F", +"2%. c #2B302D", +"3%. c #282E28", +"4%. c #2B302B", +"5%. c #232825", +"6%. c #1D221F", +"7%. c #0B0A0E", +"8%. c #070A0D", +"9%. c #01040C", +"0%. c #00030E", +"a%. c #00020F", +"b%. c #000315", +"c%. c #04071F", +"d%. c #12142B", +"e%. c #292B3F", +"f%. c #3D3F51", +"g%. c #555764", +"h%. c #636670", +"i%. c #545665", +"j%. c #4A4B5E", +"k%. c #58596B", +"l%. c #545566", +"m%. c #575A6A", +"n%. c #616573", +"o%. c #6E727E", +"p%. c #757784", +"q%. c #80818E", +"r%. c #656678", +"s%. c #474963", +"t%. c #3C3D57", +"u%. c #494B65", +"v%. c #333753", +"w%. c #212646", +"x%. c #212547", +"y%. c #25284B", +"z%. c #232649", +"A%. c #1C2042", +"B%. c #181C3F", +"C%. c #161A3D", +"D%. c #868798", +"E%. c #9FA1A9", +"F%. c #9FA2AA", +"G%. c #9FA2A9", +"H%. c #9B9EA6", +"I%. c #787B8F", +"J%. c #4A4D67", +"K%. c #656780", +"L%. c #4A4C68", +"M%. c #343656", +"N%. c #424565", +"O%. c #3D4062", +"P%. c #44476A", +"Q%. c #2C3052", +"R%. c #383D5A", +"S%. c #424962", +"T%. c #4A4F68", +"U%. c #454A65", +"V%. c #525359", +"W%. c #636554", +"X%. c #5F6450", +"Y%. c #656A5D", +"Z%. c #606471", +"`%. c #3C415C", +" &. c #272D4C", +".&. c #343758", +"+&. c #303354", +"@&. c #1D2345", +"#&. c #111639", +"$&. c #171A3E", +"%&. c #2E3252", +"&&. c #4E506C", +"*&. c #59575D", +"=&. c #595453", +"-&. c #4A484E", +";&. c #564D53", +">&. c #4E4756", +",&. c #2D2E4B", +"'&. c #4C4C67", +")&. c #747687", +"!&. c #787A87", +"~&. c #787689", +"{&. c #716F7D", +"]&. c #8E8D90", +"^&. c #AEACB0", +"/&. c #ACA7AD", +"(&. c #ADA49E", +"_&. c #BAB1A7", +":&. c #BBB4A9", +"<&. c #BDB7AC", +"[&. c #9E968A", +"}&. c #958775", +"|&. c #978C77", +"1&. c #9E9D88", +"2&. c #A8A996", +"3&. c #A09D8B", +"4&. c #989280", +"5&. c #8D8B70", +"6&. c #847E64", +"7&. c #7F795E", +"8&. c #737155", +"9&. c #726F57", +"0&. c #8A8874", +"a&. c #C6C3B5", +"b&. c #DCD3CC", +"c&. c #D4CBC2", +"d&. c #AAA390", +"e&. c #666A51", +"f&. c #727760", +"g&. c #DFDED6", +"h&. c #F8F6F0", +"i&. c #F2EFE9", +"j&. c #BFB7A9", +"k&. c #81725F", +"l&. c #755D43", +"m&. c #816140", +"n&. c #956E45", +"o&. c #88582E", +"p&. c #8B612F", +"q&. c #746253", +"r&. c #474858", +"s&. c #474C5C", +"t&. c #9C8D86", +"u&. c #B39681", +"v&. c #AC907A", +"w&. c #B0967E", +"x&. c #A08F82", +"y&. c #797066", +"z&. c #6F614D", +"A&. c #725845", +"B&. c #886C5B", +"C&. c #725E49", +"D&. c #6A6249", +"E&. c #635A48", +"F&. c #676059", +"G&. c #53524D", +"H&. c #3E3D3C", +"I&. c #3C3E43", +"J&. c #36383E", +"K&. c #36373E", +"L&. c #313138", +"M&. c #16161A", +"N&. c #232625", +"O&. c #252926", +"P&. c #3E4240", +"Q&. c #414542", +"R&. c #41443F", +"S&. c #474B44", +"T&. c #454842", +"U&. c #40433E", +"V&. c #41433D", +"W&. c #3B3C3A", +"X&. c #302F33", +"Y&. c #2E2B30", +"Z&. c #201C22", +"`&. c #211D23", +" *. c #2F2930", +".*. c #1C1F20", +"+*. c #232827", +"@*. c #222826", +"#*. c #202624", +"$*. c #1D2220", +"%*. c #07080C", +"&*. c #00020A", +"**. c #00020D", +"=*. c #000310", +"-*. c #0E1128", +";*. c #1E2236", +">*. c #35374A", +",*. c #434454", +"'*. c #575965", +")*. c #626470", +"!*. c #5B5D69", +"~*. c #5A5C6A", +"{*. c #646673", +"]*. c #616370", +"^*. c #656976", +"/*. c #686B78", +"(*. c #626572", +"_*. c #646575", +":*. c #6A6C7B", +"<*. c #6B6D7B", +"[*. c #6C6D7D", +"}*. c #58596E", +"|*. c #54586C", +"1*. c #474B61", +"2*. c #2E314F", +"3*. c #292C4B", +"4*. c #282B4A", +"5*. c #252849", +"6*. c #232647", +"7*. c #202344", +"8*. c #1B1F41", +"9*. c #181D3F", +"0*. c #2F3352", +"a*. c #696B7D", +"b*. c #9C9DA7", +"c*. c #9C9FA8", +"d*. c #9A9DA4", +"e*. c #8E919C", +"f*. c #3D3E5C", +"g*. c #555871", +"h*. c #666A82", +"i*. c #414460", +"j*. c #3B3F5D", +"k*. c #464B68", +"l*. c #696C85", +"m*. c #2E3254", +"n*. c #2C3151", +"o*. c #2B3051", +"p*. c #2A2F50", +"q*. c #2B304F", +"r*. c #4D535F", +"s*. c #6B6E75", +"t*. c #4C5067", +"u*. c #575662", +"v*. c #68655F", +"w*. c #626250", +"x*. c #737461", +"y*. c #717473", +"z*. c #3B425E", +"A*. c #2D3253", +"B*. c #2A2D4E", +"C*. c #1F2243", +"D*. c #1E2241", +"E*. c #191E3D", +"F*. c #0E1234", +"G*. c #242A4A", +"H*. c #2F344E", +"I*. c #464150", +"J*. c #453E3E", +"K*. c #44444B", +"L*. c #4B4B57", +"M*. c #454456", +"N*. c #404159", +"O*. c #757486", +"P*. c #9B9CA5", +"Q*. c #9595A1", +"R*. c #8F8E95", +"S*. c #84837A", +"T*. c #82806F", +"U*. c #A5A299", +"V*. c #BBB6B1", +"W*. c #C1BAB3", +"X*. c #C4BDB4", +"Y*. c #C3BEB4", +"Z*. c #A7A194", +"`*. c #9B9282", +" =. c #A89E8D", +".=. c #A79F8E", +"+=. c #ABA593", +"@=. c #9F9A88", +"#=. c #857E69", +"$=. c #857D65", +"%=. c #7D7359", +"&=. c #7C7154", +"*=. c #747257", +"==. c #79785F", +"-=. c #A5A391", +";=. c #D5D1C5", +">=. c #DBD1C9", +",=. c #D1C5BA", +"'=. c #9D9081", +")=. c #635F48", +"!=. c #64654B", +"~=. c #999C8A", +"{=. c #D7D9CC", +"]=. c #F0EDE5", +"^=. c #B7ADA0", +"/=. c #7F7665", +"(=. c #74644A", +"_=. c #897250", +":=. c #946F4B", +"<=. c #855D35", +"[=. c #89653D", +"}=. c #645858", +"|=. c #3F455D", +"1=. c #495162", +"2=. c #9B9296", +"3=. c #BFA899", +"4=. c #B4977F", +"5=. c #B1967F", +"6=. c #B19A88", +"7=. c #857762", +"8=. c #696047", +"9=. c #74634C", +"0=. c #8A6E5A", +"a=. c #7F6750", +"b=. c #60553C", +"c=. c #615942", +"d=. c #655B51", +"e=. c #58534F", +"f=. c #383631", +"g=. c #221F1E", +"h=. c #1F2324", +"i=. c #2C3030", +"j=. c #323633", +"k=. c #3F4440", +"l=. c #3F423F", +"m=. c #2E282F", +"n=. c #100E13", +"o=. c #090708", +"p=. c #181E1F", +"q=. c #1E221F", +"r=. c #0E0E12", +"s=. c #04060C", +"t=. c #00020C", +"u=. c #00020E", +"v=. c #020517", +"w=. c #0B0D24", +"x=. c #171930", +"y=. c #272B3F", +"z=. c #3D3F52", +"A=. c #49495A", +"B=. c #545562", +"C=. c #60626E", +"D=. c #676974", +"E=. c #6A6D78", +"F=. c #6B6D7A", +"G=. c #656677", +"H=. c #585B6B", +"I=. c #535665", +"J=. c #525564", +"K=. c #4D5063", +"L=. c #414258", +"M=. c #6B6C7E", +"N=. c #6B6C7D", +"O=. c #696B7A", +"P=. c #6F727E", +"Q=. c #56576C", +"R=. c #30324F", +"S=. c #272A49", +"T=. c #272A4A", +"U=. c #282B4C", +"V=. c #1C1F41", +"W=. c #202445", +"X=. c #2A2E4C", +"Y=. c #575A70", +"Z=. c #90939D", +"`=. c #9D9EA5", +" -. c #94969F", +".-. c #9799A4", +"+-. c #6C6D81", +"@-. c #3B3C59", +"#-. c #424361", +"$-. c #5F6279", +"%-. c #585C75", +"&-. c #363A56", +"*-. c #454866", +"=-. c #4A4C69", +"--. c #777A90", +";-. c #777A8F", +">-. c #2C3251", +",-. c #2D3150", +"'-. c #2B2E51", +")-. c #3C4054", +"!-. c #585D58", +"~-. c #7C7566", +"{-. c #797870", +"]-. c #575B69", +"^-. c #4B4C63", +"/-. c #4D4B5C", +"(-. c #5C5C57", +"_-. c #747267", +":-. c #77716A", +"<-. c #535260", +"[-. c #3C3E59", +"}-. c #323555", +"|-. c #1A1D3E", +"1-. c #222745", +"2-. c #1D2240", +"3-. c #0B0F30", +"4-. c #101436", +"5-. c #232944", +"6-. c #3B3B4A", +"7-. c #49453E", +"8-. c #5C595A", +"9-. c #5C5E6C", +"0-. c #5A5A67", +"a-. c #7B7779", +"b-. c #999395", +"c-. c #969199", +"d-. c #A7A79C", +"e-. c #7D7E6B", +"f-. c #74745F", +"g-. c #8E8D7C", +"h-. c #B1ADA1", +"i-. c #C0B9B0", +"j-. c #B1AB9F", +"k-. c #A6A196", +"l-. c #A2A095", +"m-. c #99998A", +"n-. c #969784", +"o-. c #A29F8C", +"p-. c #A8A18F", +"q-. c #A39A89", +"r-. c #9D9887", +"s-. c #8A846F", +"t-. c #837B63", +"u-. c #7F755B", +"v-. c #786D50", +"w-. c #737055", +"x-. c #747159", +"y-. c #A8A392", +"z-. c #D6D1C5", +"A-. c #D7CEC5", +"B-. c #D0C6BB", +"C-. c #A79C8C", +"D-. c #6F6753", +"E-. c #6C6851", +"F-. c #A8A697", +"G-. c #E0DBD3", +"H-. c #F2EBE6", +"I-. c #E4DBD0", +"J-. c #978B78", +"K-. c #766F5B", +"L-. c #6E654A", +"M-. c #918660", +"N-. c #947B56", +"O-. c #896B49", +"P-. c #7D654A", +"Q-. c #5B5664", +"R-. c #555E6B", +"S-. c #99968D", +"T-. c #B7A490", +"U-. c #AC9179", +"V-. c #AC907B", +"W-. c #B8A08E", +"X-. c #6F654C", +"Y-. c #79755A", +"Z-. c #8B7860", +"`-. c #89725A", +" ;. c #65563E", +".;. c #61543B", +"+;. c #655646", +"@;. c #5E5550", +"#;. c #3C3633", +"$;. c #2B2625", +"%;. c #1E1B1D", +"&;. c #2E3231", +"*;. c #333636", +"=;. c #343537", +"-;. c #2C2B2F", +";;. c #14181B", +">;. c #181C1E", +",;. c #0D0D12", +"';. c #02040C", +");. c #00010D", +"!;. c #010319", +"~;. c #0E1026", +"{;. c #17192F", +"];. c #262B3F", +"^;. c #393B4D", +"/;. c #434353", +"(;. c #585A66", +"_;. c #636571", +":;. c #686B76", +"<;. c #696C75", +"[;. c #46475B", +"};. c #424455", +"|;. c #535565", +"1;. c #5F6171", +"2;. c #5B5F6F", +"3;. c #3B3F54", +"4;. c #2B2E49", +"5;. c #3D3E55", +"6;. c #6D6E7C", +"7;. c #42415D", +"8;. c #2A2B4B", +"9;. c #2C2F4D", +"0;. c #2F3250", +"a;. c #1E2042", +"b;. c #2B2D4C", +"c;. c #4D4F66", +"d;. c #7A7D8E", +"e;. c #9396A1", +"f;. c #8B8C98", +"g;. c #6F7084", +"h;. c #4B4C66", +"i;. c #4A4E67", +"j;. c #3A3E5A", +"k;. c #484A68", +"l;. c #52526D", +"m;. c #6C6E82", +"n;. c #313555", +"o;. c #2D3152", +"p;. c #303150", +"q;. c #292A4C", +"r;. c #2E314A", +"s;. c #494F4F", +"t;. c #696A5A", +"u;. c #8C7968", +"v;. c #77706D", +"w;. c #41455F", +"x;. c #373857", +"y;. c #514F5F", +"z;. c #706B64", +"A;. c #756C5B", +"B;. c #6D6053", +"C;. c #4E4E5C", +"D;. c #2A2F4D", +"E;. c #2B2E4E", +"F;. c #2E3251", +"G;. c #262B4A", +"H;. c #15173A", +"I;. c #333650", +"J;. c #434650", +"K;. c #434A37", +"L;. c #5D604E", +"M;. c #67685F", +"N;. c #727069", +"O;. c #877F75", +"P;. c #90867F", +"Q;. c #918B8A", +"R;. c #989892", +"S;. c #A8A89F", +"T;. c #95958C", +"U;. c #848476", +"V;. c #919183", +"W;. c #AFA99F", +"X;. c #A39E91", +"Y;. c #9A978B", +"Z;. c #9A9B8F", +"`;. c #848978", +" >. c #949B85", +".>. c #969A85", +"+>. c #999E88", +"@>. c #888E78", +"#>. c #878B76", +"$>. c #7E8168", +"%>. c #7B7B60", +"&>. c #827A5F", +"*>. c #918669", +"=>. c #878367", +"->. c #746F57", +";>. c #A7A190", +">>. c #D6CFC5", +",>. c #DCD5CC", +"'>. c #CEC7BB", +")>. c #B1A898", +"!>. c #8E8170", +"~>. c #706552", +"{>. c #A49C8E", +"]>. c #D8CDC8", +"^>. c #DACDC7", +"/>. c #BFB2A3", +"(>. c #776850", +"_>. c #6E6850", +":>. c #716D51", +"<>. c #93896C", +"[>. c #A08A6C", +"}>. c #907652", +"|>. c #7C6B53", +"1>. c #575968", +"2>. c #434A66", +"3>. c #5A606E", +"4>. c #877E70", +"5>. c #A38B6C", +"6>. c #A4896E", +"7>. c #A78B76", +"8>. c #A88F7C", +"9>. c #A68F7C", +"0>. c #7A6752", +"a>. c #756850", +"b>. c #88765D", +"c>. c #877058", +"d>. c #705E47", +"e>. c #67563C", +"f>. c #61513B", +"g>. c #61554E", +"h>. c #453C3A", +"i>. c #302928", +"j>. c #211D1F", +"k>. c #222728", +"l>. c #1C181B", +"m>. c #16151A", +"n>. c #100F14", +"o>. c #0A0B12", +"p>. c #01030D", +"q>. c #00010F", +"r>. c #000212", +"s>. c #03051A", +"t>. c #090B20", +"u>. c #111328", +"v>. c #1A1D32", +"w>. c #262A3D", +"x>. c #373949", +"y>. c #494A57", +"z>. c #5C5E68", +"A>. c #646770", +"B>. c #656872", +"C>. c #545765", +"D>. c #3B3D4F", +"E>. c #585968", +"F>. c #686A76", +"G>. c #575A6C", +"H>. c #32354C", +"I>. c #272945", +"J>. c #2E3149", +"K>. c #505265", +"L>. c #636473", +"M>. c #5B5D6D", +"N>. c #44465D", +"O>. c #2B2B4B", +"P>. c #272848", +"Q>. c #2F314F", +"R>. c #2C2D4B", +"S>. c #1E2041", +"T>. c #16193B", +"U>. c #1B1E3D", +"V>. c #2A2E4B", +"W>. c #3F445D", +"X>. c #6D7082", +"Y>. c #898C98", +"Z>. c #9395A0", +"`>. c #8E909D", +" ,. c #888A95", +".,. c #77798A", +"+,. c #52546D", +"@,. c #434761", +"#,. c #383B5A", +"$,. c #474B67", +"%,. c #4C506C", +"&,. c #3B3E5C", +"*,. c #3E425E", +"=,. c #464A64", +"-,. c #5A5C71", +";,. c #76798B", +">,. c #50526B", +",,. c #383B59", +"',. c #2C2F50", +"),. c #2D3051", +"!,. c #2E3151", +"~,. c #2E3152", +"{,. c #242748", +"],. c #2A2F46", +"^,. c #414A4B", +"/,. c #766E61", +"(,. c #8C7A68", +"_,. c #746D6B", +":,. c #40425C", +"<,. c #424760", +"[,. c #373A59", +"},. c #4F5062", +"|,. c #666558", +"1,. c #625A48", +"2,. c #5F543F", +"3,. c #5F5A59", +"4,. c #41445B", +"5,. c #3A3F5B", +"6,. c #3B405C", +"7,. c #323655", +"8,. c #252949", +"9,. c #393852", +"0,. c #454156", +"a,. c #5C575D", +"b,. c #5E5E56", +"c,. c #475341", +"d,. c #505C44", +"e,. c #646854", +"f,. c #6D6C61", +"g,. c #747068", +"h,. c #8D877D", +"i,. c #9A978F", +"j,. c #9FA098", +"k,. c #ABACA5", +"l,. c #A6A19B", +"m,. c #A8A29A", +"n,. c #ABA99E", +"o,. c #ADAAA0", +"p,. c #A2A496", +"q,. c #A0A496", +"r,. c #999E91", +"s,. c #868E78", +"t,. c #868E74", +"u,. c #939983", +"v,. c #9DA08A", +"w,. c #8F907A", +"x,. c #8D927B", +"y,. c #8E9277", +"z,. c #85866A", +"A,. c #918E73", +"B,. c #988B73", +"C,. c #80795D", +"D,. c #77745C", +"E,. c #B6B3A5", +"F,. c #DCDAD4", +"G,. c #E8E6E1", +"H,. c #CBC8BD", +"I,. c #979483", +"J,. c #7E7061", +"K,. c #674E3D", +"L,. c #9A8A79", +"M,. c #C0ADA2", +"N,. c #A78E82", +"O,. c #907D6D", +"P,. c #715943", +"Q,. c #6A6144", +"R,. c #726F50", +"S,. c #8F8669", +"T,. c #A3937B", +"U,. c #9A8467", +"V,. c #8B765F", +"W,. c #5D5A6D", +"X,. c #464D69", +"Y,. c #585D6F", +"Z,. c #7A7066", +"`,. c #8B7350", +" '. c #957F5B", +".'. c #A0836B", +"+'. c #977B65", +"@'. c #967E69", +"#'. c #7E6957", +"$'. c #796550", +"%'. c #837056", +"&'. c #80694E", +"*'. c #766149", +"='. c #685742", +"-'. c #56482F", +";'. c #5F5447", +">'. c #4C423E", +",'. c #36312B", +"''. c #232120", +")'. c #232422", +"!'. c #1F2325", +"~'. c #09070C", +"{'. c #090808", +"]'. c #141318", +"^'. c #08080F", +"/'. c #00010E", +"('. c #06081D", +"_'. c #0C0F24", +":'. c #3E4050", +"<'. c #51525E", +"['. c #5B5E67", +"}'. c #61646D", +"|'. c #3F4252", +"1'. c #3C3F4F", +"2'. c #4F5262", +"3'. c #636774", +"4'. c #535664", +"5'. c #46485A", +"6'. c #414457", +"7'. c #2D2F46", +"8'. c #2A2D47", +"9'. c #3C3E54", +"0'. c #44465B", +"a'. c #252742", +"b'. c #20213F", +"c'. c #222342", +"d'. c #191A39", +"e'. c #121535", +"f'. c #151837", +"g'. c #272B48", +"h'. c #3F435C", +"i'. c #53566A", +"j'. c #7C7D8C", +"k'. c #91939D", +"l'. c #91949E", +"m'. c #828492", +"n'. c #61647A", +"o'. c #373A57", +"p'. c #2B304C", +"q'. c #393D59", +"r'. c #353857", +"s'. c #424662", +"t'. c #5E6176", +"u'. c #707385", +"v'. c #313453", +"w'. c #292C4D", +"x'. c #292C4E", +"y'. c #1D2041", +"z'. c #2C3249", +"A'. c #44504E", +"B'. c #787265", +"C'. c #676760", +"D'. c #3C4157", +"E'. c #4E5268", +"F'. c #545B62", +"G'. c #5A625A", +"H'. c #5D6551", +"I'. c #5E624C", +"J'. c #625B46", +"K'. c #6F6658", +"L'. c #555562", +"M'. c #40435A", +"N'. c #3F425C", +"O'. c #414358", +"P'. c #615A5B", +"Q'. c #6B625A", +"R'. c #68685D", +"S'. c #646B5C", +"T'. c #5C6856", +"U'. c #566048", +"V'. c #636552", +"W'. c #6E6B5F", +"X'. c #6D695D", +"Y'. c #938D7F", +"Z'. c #989182", +"`'. c #857E71", +" ). c #9A988E", +".). c #A7A79F", +"+). c #ABA89F", +"@). c #ACA49B", +"#). c #B1A79C", +"$). c #AEA89A", +"%). c #A8A699", +"&). c #9FA193", +"*). c #8A8F7C", +"=). c #838871", +"-). c #7F846C", +";). c #91927B", +">). c #88876F", +",). c #84886E", +"'). c #888A72", +")). c #8E8D77", +"!). c #A29F8E", +"~). c #958879", +"{). c #8A8371", +"]). c #AFAD9E", +"^). c #DEDDD4", +"/). c #EDEDE8", +"(). c #E7E9E3", +"_). c #BCBDB1", +":). c #7B7C6A", +"<). c #716858", +"[). c #7B6553", +"}). c #938572", +"|). c #A68E7D", +"1). c #87675C", +"2). c #6C553F", +"3). c #6F5338", +"4). c #746142", +"5). c #726B48", +"6). c #7F7956", +"7). c #A4967C", +"8). c #A59177", +"9). c #947F64", +"0). c #6A6166", +"a). c #484D69", +"b). c #4E5062", +"c). c #71665E", +"d). c #786753", +"e). c #81694D", +"f). c #988470", +"g). c #98826C", +"h). c #7F6A58", +"i). c #7F6B56", +"j). c #7B684E", +"k). c #796247", +"l). c #63533F", +"m). c #51432D", +"n). c #574D3C", +"o). c #4C433C", +"p). c #3C352E", +"q). c #272422", +"r). c #232423", +"s). c #222423", +"t). c #121211", +"u). c #282428", +"v). c #05050C", +"w). c #00020B", +"x). c #010316", +"y). c #010317", +"z). c #05081C", +"A). c #1D2034", +"B). c #3B3D4D", +"C). c #52545F", +"D). c #5A5C65", +"E). c #595C66", +"F). c #4F5161", +"G). c #565966", +"H). c #5D616C", +"I). c #4A4F5D", +"J). c #2A2E40", +"K). c #292C44", +"L). c #2A2C43", +"M). c #262843", +"N). c #292C47", +"O). c #393B52", +"P). c #3F4054", +"Q). c #262742", +"R). c #161735", +"S). c #191A38", +"T). c #1A1A3A", +"U). c #161637", +"V). c #121233", +"W). c #111332", +"X). c #1B1F3C", +"Y). c #2F334F", +"Z). c #4E5168", +"`). c #8B8B96", +" !. c #91929C", +".!. c #838590", +"+!. c #797B89", +"@!. c #6F7283", +"#!. c #42455F", +"$!. c #232746", +"%!. c #323553", +"&!. c #3D405D", +"*!. c #323552", +"=!. c #3D405E", +"-!. c #52556F", +";!. c #5F6277", +">!. c #66697B", +",!. c #43465F", +"'!. c #282B4B", +")!. c #292C4C", +"!!. c #252847", +"~!. c #282C43", +"{!. c #374041", +"]!. c #4D5546", +"^!. c #6A675D", +"/!. c #6D6764", +"(!. c #545762", +"_!. c #515366", +":!. c #61635F", +"~. c #1A1718", +",~. c #0C0A0E", +"'~. c #131212", +")~. c #221E20", +"!~. c #252027", +"~~. c #262527", +"{~. c #17181D", +"]~. c #1A1720", +"^~. c #221E1B", +"/~. c #221915", +"(~. c #03040B", +"_~. c #030519", +":~. c #04071B", +"<~. c #04061A", +"[~. c #0F1225", +"}~. c #1E2034", +"|~. c #3F4250", +"1~. c #50545E", +"2~. c #494B57", +"3~. c #4E505B", +"4~. c #595B65", +"5~. c #60626D", +"6~. c #515260", +"7~. c #24263D", +"8~. c #2A2C42", +"9~. c #27293E", +"0~. c #2B2D45", +"a~. c #272943", +"b~. c #22243D", +"c~. c #25273F", +"d~. c #1E1F38", +"e~. c #171835", +"f~. c #111230", +"g~. c #121331", +"h~. c #141531", +"i~. c #121333", +"j~. c #131432", +"k~. c #21243E", +"l~. c #353852", +"m~. c #363952", +"n~. c #585A6C", +"o~. c #7C7E89", +"p~. c #8B8D95", +"q~. c #767885", +"r~. c #656779", +"s~. c #5D5E73", +"t~. c #43455E", +"u~. c #242746", +"v~. c #343553", +"w~. c #333453", +"x~. c #373856", +"y~. c #4A4C66", +"z~. c #54566E", +"A~. c #525569", +"B~. c #626477", +"C~. c #6A6C81", +"D~. c #51536A", +"E~. c #41435D", +"F~. c #2C2D4C", +"G~. c #2C2D4E", +"H~. c #303352", +"I~. c #2C2F4F", +"J~. c #2E3346", +"K~. c #3A433A", +"L~. c #3D433D", +"M~. c #3B3B4F", +"N~. c #37374F", +"O~. c #353754", +"P~. c #4E4F5E", +"Q~. c #65675F", +"R~. c #616453", +"S~. c #595A4E", +"T~. c #5C5D4E", +"U~. c #626152", +"V~. c #524E40", +"W~. c #4D4435", +"X~. c #404044", +"Y~. c #1B2039", +"Z~. c #1D233C", +"`~. c #454D4C", +" {. c #5B5F4E", +".{. c #626A57", +"+{. c #60695B", +"@{. c #5D6354", +"#{. c #535B46", +"${. c #55604A", +"%{. c #777C68", +"&{. c #7E7E6C", +"*{. c #646956", +"={. c #585242", +"-{. c #5D5A4A", +";{. c #726F60", +">{. c #8D8C7F", +",{. c #9E9D92", +"'{. c #A09B90", +"){. c #9B9385", +"!{. c #9A9786", +"~{. c #A3A194", +"{{. c #999586", +"]{. c #606750", +"^{. c #6D715D", +"/{. c #6D6A54", +"({. c #6A6751", +"_{. c #6A6C54", +":{. c #798067", +"<{. c #939882", +"[{. c #A6A695", +"}{. c #B2AF9F", +"|{. c #B1AB9B", +"1{. c #B0AB9F", +"2{. c #D3CFC6", +"3{. c #E4E3DE", +"4{. c #EAE9E4", +"5{. c #E6E5DF", +"6{. c #C1BBB2", +"7{. c #978D82", +"8{. c #827165", +"9{. c #7C5E4D", +"0{. c #5D5448", +"a{. c #5D585F", +"b{. c #6A6667", +"c{. c #806146", +"d{. c #7D5632", +"e{. c #7A5937", +"f{. c #6E603C", +"g{. c #686742", +"h{. c #6F6D4A", +"i{. c #827354", +"j{. c #8C7859", +"k{. c #8B745D", +"l{. c #807778", +"m{. c #54596F", +"n{. c #484E6C", +"o{. c #40455F", +"p{. c #595968", +"q{. c #56546B", +"r{. c #665A57", +"s{. c #776246", +"t{. c #755F4C", +"u{. c #755E4B", +"v{. c #674F3D", +"w{. c #604B38", +"x{. c #52432F", +"y{. c #4B422F", +"z{. c #47402E", +"A{. c #443D30", +"B{. c #42362E", +"C{. c #372D25", +"D{. c #201D1C", +"E{. c #232127", +"F{. c #231A1C", +"G{. c #372D1E", +"H{. c #795622", +"I{. c #B07D22", +"J{. c #3C2C16", +"K{. c #01020D", +"L{. c #010414", +"M{. c #0A0D1F", +"N{. c #16192B", +"O{. c #05081B", +"P{. c #010217", +"Q{. c #06091D", +"R{. c #1D2032", +"S{. c #282B3B", +"T{. c #1F2235", +"U{. c #383B4B", +"V{. c #343746", +"W{. c #313343", +"X{. c #36394A", +"Y{. c #494B5A", +"Z{. c #424556", +"`{. c #2D2F42", +" ]. c #1F2138", +".]. c #2C2E43", +"+]. c #2E3044", +"@]. c #292B40", +"#]. c #21233A", +"$]. c #191B34", +"%]. c #171932", +"&]. c #151633", +"*]. c #0E0F2D", +"=]. c #121232", +"-]. c #25263F", +";]. c #555767", +">]. c #616473", +",]. c #5B5D6C", +"']. c #6B6E7C", +")]. c #67697A", +"!]. c #45485D", +"~]. c #44475F", +"{]. c #1B1E3B", +"]]. c #1C1F3E", +"^]. c #232645", +"/]. c #2A2C4B", +"(]. c #363754", +"_]. c #3C3D58", +":]. c #44465E", +"<]. c #4B4D64", +"[]. c #4F5266", +"}]. c #585B6D", +"|]. c #717485", +"1]. c #6C6F82", +"2]. c #5A5D72", +"3]. c #4C4E65", +"4]. c #333551", +"5]. c #262948", +"6]. c #262A48", +"7]. c #1E223C", +"8]. c #212737", +"9]. c #171A36", +"0]. c #0E1034", +"a]. c #0C0F2F", +"b]. c #0F1333", +"c]. c #1D203C", +"d]. c #383B47", +"e]. c #4D4D48", +"f]. c #515448", +"g]. c #454B45", +"h]. c #5F6052", +"i]. c #656253", +"j]. c #4E493A", +"k]. c #514B40", +"l]. c #333443", +"m]. c #191C3C", +"n]. c #373951", +"o]. c #545953", +"p]. c #5C604C", +"q]. c #595F4A", +"r]. c #535748", +"s]. c #5C5E4E", +"t]. c #4F543F", +"u]. c #4A533E", +"v]. c #606451", +"w]. c #666855", +"x]. c #4B5640", +"y]. c #565443", +"z]. c #636453", +"A]. c #7C7C6D", +"B]. c #8C8E80", +"C]. c #7A7C70", +"D]. c #86857B", +"E]. c #9C9A8E", +"F]. c #9A948A", +"G]. c #8E8A7E", +"H]. c #7D7C68", +"I]. c #807C66", +"J]. c #6F725F", +"K]. c #656555", +"L]. c #766E5C", +"M]. c #7E7967", +"N]. c #868371", +"O]. c #81816E", +"P]. c #959785", +"Q]. c #A6A496", +"R]. c #B4AFA3", +"S]. c #BEB9AD", +"T]. c #CCC8C0", +"U]. c #DBD8D3", +"V]. c #E2DFDA", +"W]. c #E7E4DF", +"X]. c #E1DED8", +"Y]. c #C0B5A8", +"Z]. c #B7A79D", +"`]. c #A6938C", +" ^. c #7B695D", +".^. c #81614A", +"+^. c #64574E", +"@^. c #505264", +"#^. c #595D6E", +"$^. c #74675A", +"%^. c #785638", +"&^. c #745033", +"*^. c #6C583A", +"=^. c #686240", +"-^. c #716F4B", +";^. c #837758", +">^. c #998466", +",^. c #8D7759", +"'^. c #867A61", +")^. c #6D6E70", +"!^. c #52546C", +"~^. c #494A68", +"{^. c #444663", +"]^. c #434561", +"^^. c #4E4F64", +"/^. c #70665A", +"(^. c #7D6A4A", +"_^. c #89745F", +":^. c #6D5844", +"<^. c #5A4635", +"[^. c #534230", +"}^. c #4D412D", +"|^. c #48402D", +"1^. c #423B29", +"2^. c #433D2F", +"3^. c #40342A", +"4^. c #3C2F26", +"5^. c #2A2322", +"6^. c #2D2821", +"7^. c #60472C", +"8^. c #845A25", +"9^. c #D39130", +"0^. c #B6985D", +"a^. c #1D191B", +"b^. c #01020C", +"c^. c #010314", +"d^. c #0E1121", +"e^. c #222535", +"f^. c #101325", +"g^. c #0B0D22", +"h^. c #05071C", +"i^. c #07091D", +"j^. c #0D1023", +"k^. c #2A2C3D", +"l^. c #323544", +"m^. c #1E2133", +"n^. c #191C2F", +"o^. c #15172C", +"p^. c #10142A", +"q^. c #13172D", +"r^. c #181B31", +"s^. c #1C2035", +"t^. c #191D33", +"u^. c #181B33", +"v^. c #1D1F35", +"w^. c #282B3F", +"x^. c #3C3F51", +"y^. c #45485A", +"z^. c #2D3043", +"A^. c #1E2036", +"B^. c #1D1F37", +"C^. c #20223B", +"D^. c #1D1F38", +"E^. c #141532", +"F^. c #0F102E", +"G^. c #0F102F", +"H^. c #151634", +"I^. c #44465A", +"J^. c #636572", +"K^. c #5F606E", +"L^. c #6E6F7D", +"M^. c #696D7B", +"N^. c #535869", +"O^. c #383C54", +"P^. c #2B2F4B", +"Q^. c #252A47", +"R^. c #131737", +"S^. c #0D1131", +"T^. c #191C3B", +"U^. c #1F2241", +"V^. c #2B2D4B", +"W^. c #3E3F5A", +"X^. c #515368", +"Y^. c #535568", +"Z^. c #56586C", +"`^. c #626577", +" /. c #707383", +"./. c #747786", +"+/. c #5F6274", +"@/. c #5A5D70", +"#/. c #4D5066", +"$/. c #353751", +"%/. c #282947", +"&/. c #222443", +"*/. c #232544", +"=/. c #282A49", +"-/. c #151838", +";/. c #0A0D31", +">/. c #030728", +",/. c #030726", +"'/. c #050929", +")/. c #080B2E", +"!/. c #1C1F3B", +"~/. c #3E424A", +"{/. c #575C56", +"]/. c #5E6057", +"^/. c #6E6C60", +"//. c #656053", +"(/. c #524B3F", +"_/. c #504C49", +":/. c #2D3143", +"(. c #825E2B", +",(. c #95744F", +"'(. c #928D89", +")(. c #5F6C7A", +"!(. c #070716", +"~(. c #000210", +"{(. c #030619", +"](. c #0B0E21", +"^(. c #15182A", +"/(. c #16192C", +"((. c #101326", +"_(. c #090C1F", +":(. c #04061B", +"<(. c #121428", +"[(. c #181A2F", +"}(. c #090B22", +"|(. c #03051C", +"1(. c #05071E", +"2(. c #070920", +"3(. c #0A0C23", +"4(. c #0C0F26", +"5(. c #0B0D25", +"6(. c #0F1128", +"7(. c #1A1C32", +"8(. c #35384B", +"9(. c #424458", +"0(. c #292C41", +"a(. c #1E2037", +"b(. c #181A34", +"c(. c #0A0D2A", +"d(. c #22243C", +"e(. c #595B6B", +"f(. c #595A6B", +"g(. c #545664", +"h(. c #676976", +"i(. c #45475B", +"j(. c #31334B", +"k(. c #2A2C47", +"l(. c #1C1F3D", +"m(. c #0F1233", +"n(. c #05082A", +"o(. c #0D1030", +"p(. c #1A1D3C", +"q(. c #262946", +"r(. c #33344F", +"s(. c #4B4D63", +"t(. c #6D717E", +"u(. c #656879", +"v(. c #5A5D6F", +"w(. c #50536A", +"x(. c #3D415C", +"y(. c #282C49", +"z(. c #222542", +"A(. c #222341", +"B(. c #262745", +"C(. c #272847", +"D(. c #06092B", +"E(. c #050827", +"F(. c #030725", +"G(. c #080B29", +"H(. c #111133", +"I(. c #2B2E3D", +"J(. c #4D514C", +"K(. c #545653", +"L(. c #515254", +"M(. c #414049", +"N(. c #33313C", +"O(. c #2D2A35", +"P(. c #2D2637", +"Q(. c #292A41", +"R(. c #3A3F3A", +"S(. c #4D4C35", +"T(. c #44472E", +"U(. c #494B33", +"V(. c #524D39", +"W(. c #494B36", +"X(. c #505542", +"Y(. c #3F422E", +"Z(. c #51523D", +"`(. c #565441", +" _. c #484834", +"._. c #4B503F", +"+_. c #616153", +"@_. c #727063", +"#_. c #8B8D82", +"$_. c #7F8176", +"%_. c #82827A", +"&_. c #8C8A82", +"*_. c #8D8B7D", +"=_. c #7B7B6B", +"-_. c #797B6B", +";_. c #6B6957", +">_. c #65624F", +",_. c #77766A", +"'_. c #807E71", +")_. c #817D69", +"!_. c #928E7E", +"~_. c #A3A291", +"{_. c #A9A999", +"]_. c #B4B1A5", +"^_. c #C6C4BA", +"/_. c #CAC8C2", +"(_. c #D6D3D1", +"__. c #DBD6D3", +":_. c #E0D8D4", +"<_. c #E0D5CF", +"[_. c #DECDC5", +"}_. c #CEBCB5", +"|_. c #957C68", +"1_. c #6A5339", +"2_. c #6D5D4A", +"3_. c #575660", +"4_. c #4F546D", +"5_. c #57586D", +"6_. c #796E6E", +"7_. c #816D58", +"8_. c #74614C", +"9_. c #605337", +"0_. c #5F5539", +"a_. c #666541", +"b_. c #5F5C39", +"c_. c #665A3C", +"d_. c #756542", +"e_. c #8E8160", +"f_. c #988A6D", +"g_. c #958269", +"h_. c #8C7B6F", +"i_. c #706F7C", +"j_. c #4A506D", +"k_. c #414360", +"l_. c #4A4A5C", +"m_. c #5B5550", +"n_. c #715F3F", +"o_. c #755E46", +"p_. c #624F3D", +"q_. c #4A402E", +"r_. c #403927", +"s_. c #3C3321", +"t_. c #3E3A27", +"u_. c #3A3C28", +"v_. c #493F30", +"w_. c #453528", +"x_. c #423227", +"y_. c #423824", +"z_. c #2E271D", +"A_. c #211E25", +"B_. c #2B221A", +"C_. c #614A2D", +"D_. c #917F6B", +"E_. c #CACCCD", +"F_. c #B4B0B1", +"G_. c #03030F", +"H_. c #01030C", +"I_. c #080B1E", +"J_. c #0C0F22", +"K_. c #1B1E31", +"L_. c #0B0E22", +"M_. c #02041B", +"N_. c #0C0E25", +"O_. c #1F2137", +"P_. c #2C2E44", +"Q_. c #191B32", +"R_. c #14152F", +"S_. c #10122C", +"T_. c #151730", +"U_. c #10112D", +"V_. c #080927", +"W_. c #040523", +"X_. c #0A0D29", +"Y_. c #3B3E53", +"Z_. c #656775", +"`_. c #646672", +" :. c #5A5C6B", +".:. c #272843", +"+:. c #1F203D", +"@:. c #101232", +"#:. c #050829", +"$:. c #040627", +"%:. c #171A39", +"&:. c #191C3A", +"*:. c #21223F", +"=:. c #292B45", +"-:. c #3C3E58", +";:. c #54566D", +">:. c #5B5E6F", +",:. c #4B4E63", +"':. c #4B4D62", +"):. c #3B3D56", +"!:. c #2B2F4C", +"~:. c #222643", +"{:. c #232744", +"]:. c #242644", +"^:. c #171A38", +"/:. c #101432", +"(:. c #0C0F2E", +"_:. c #070A29", +"::. c #030625", +"<:. c #111231", +"[:. c #21223C", +"}:. c #272C36", +"|:. c #262D36", +"1:. c #1B2032", +"2:. c #161A36", +"3:. c #0C0F2C", +"4:. c #090C28", +"5:. c #090D29", +"6:. c #090D2B", +"7:. c #242535", +"8:. c #3A3A2B", +"9:. c #46432B", +"0:. c #454A30", +"a:. c #474B32", +"b:. c #40422A", +"c:. c #2B361D", +"d:. c #313C25", +"e:. c #2F341E", +"f:. c #353823", +"g:. c #43432F", +"h:. c #494A36", +"i:. c #636B5A", +"j:. c #777D70", +"k:. c #86887C", +"l:. c #90918A", +"m:. c #888A82", +"n:. c #84847B", +"o:. c #7E7B6F", +"p:. c #848273", +"q:. c #7F7F70", +"r:. c #888A7D", +"s:. c #838074", +"t:. c #5D574A", +"u:. c #646050", +"v:. c #757362", +"w:. c #888878", +"x:. c #9F9E92", +"y:. c #B7B5AA", +"z:. c #C8C6BA", +"A:. c #C4C2B4", +"B:. c #C7C4B6", +"C:. c #C9C2B8", +"D:. c #D0CFC7", +"E:. c #D4D7D1", +"F:. c #D8D4D0", +"G:. c #E0D7D3", +"H:. c #E2D6D1", +"I:. c #CBC3C2", +"J:. c #9A929A", +"K:. c #6F6363", +"L:. c #5D554B", +"M:. c #615946", +"N:. c #5F5D52", +"O:. c #585A61", +"P:. c #5A5D6D", +"Q:. c #6D6870", +"R:. c #796B68", +"S:. c #635844", +"T:. c #544F33", +"U:. c #545834", +"V:. c #626541", +"W:. c #6D6B47", +"X:. c #746D49", +"Y:. c #77754F", +"Z:. c #75744D", +"`:. c #817554", +" <. c #A39179", +".<. c #8C8075", +"+<. c #666878", +"@<. c #404661", +"#<. c #41415A", +"$<. c #3F4058", +"%<. c #595148", +"&<. c #755F43", +"*<. c #695541", +"=<. c #4B402D", +"-<. c #3D3826", +";<. c #433E2B", +"><. c #3C3C27", +",<. c #4E4433", +"'<. c #4D3D2D", +")<. c #45392A", +"!<. c #6F5024", +"~<. c #795524", +"{<. c #524028", +"]<. c #191419", +"^<. c #242027", +"/<. c #1C1A1B", +"(<. c #332B23", +"_<. c #888077", +":<. c #CACDD0", +"<<. c #B8AB83", +"[<. c #C09737", +"}<. c #3C2F18", +"|<. c #02030E", +"1<. c #00030F", +"2<. c #000311", +"3<. c #06091C", +"4<. c #0A0D20", +"5<. c #0E1124", +"6<. c #1D2033", +"7<. c #05071B", +"8<. c #000217", +"9<. c #101327", +"0<. c #0D0F23", +"a<. c #000219", +"b<. c #00021A", +"c<. c #01021D", +"d<. c #04051F", +"e<. c #11132A", +"f<. c #090B25", +"g<. c #060723", +"h<. c #14162F", +"i<. c #12142D", +"j<. c #090A27", +"k<. c #030423", +"l<. c #020321", +"m<. c #0E102A", +"n<. c #46485B", +"o<. c #656771", +"p<. c #6A6C77", +"q<. c #545568", +"r<. c #22233D", +"s<. c #0F102D", +"t<. c #0E0F2E", +"u<. c #070929", +"v<. c #020526", +"w<. c #06092A", +"x<. c #0B0E2D", +"y<. c #090C2B", +"z<. c #0A0C2B", +"A<. c #121332", +"B<. c #242542", +"C<. c #42445D", +"D<. c #55586A", +"E<. c #5E616F", +"F<. c #585A6D", +"G<. c #4E5065", +"H<. c #32344F", +"I<. c #252945", +"J<. c #222642", +"K<. c #242744", +"L<. c #272846", +"M<. c #242543", +"N<. c #202341", +"O<. c #1D213E", +"P<. c #151936", +"Q<. c #080B2A", +"R<. c #141633", +"S<. c #20213C", +"T<. c #121330", +"U<. c #1A1C38", +"V<. c #1A1C37", +"W<. c #131530", +"X<. c #11142D", +"Y<. c #090D25", +"Z<. c #090C26", +"`<. c #15192F", +" [. c #33322F", +".[. c #3F4129", +"+[. c #494A31", +"@[. c #3F462B", +"#[. c #40472C", +"$[. c #42472D", +"%[. c #444D33", +"&[. c #434932", +"*[. c #31351F", +"=[. c #222611", +"-[. c #31331E", +";[. c #545240", +">[. c #7F8277", +",[. c #828476", +"'[. c #7D7F71", +")[. c #757769", +"![. c #686859", +"~[. c #555343", +"{[. c #696759", +"][. c #6D6D5D", +"^[. c #696C58", +"/[. c #575547", +"([. c #4A4337", +"_[. c #625A47", +":[. c #9B9D93", +"<[. c #B8BAB3", +"[[. c #BFBEB8", +"}[. c #C2BFB9", +"|[. c #BEBFB3", +"1[. c #ABAC9B", +"2[. c #AAAB97", +"3[. c #BEBDAF", +"4[. c #D7D5CB", +"5[. c #D8D5CF", +"6[. c #DAD6D2", +"7[. c #DBD1CC", +"8[. c #B2B2BA", +"9[. c #6A6E86", +"0[. c #505269", +"a[. c #5A5862", +"b[. c #665D4B", +"c[. c #6A5F42", +"d[. c #5F5D5B", +"e[. c #5E5F77", +"f[. c #636277", +"g[. c #6F6E74", +"h[. c #7E7E72", +"i[. c #727B60", +"j[. c #A2AA8F", +"k[. c #7D8364", +"l[. c #A7A990", +"m[. c #939A7B", +"n[. c #6D774E", +"o[. c #797356", +"p[. c #907D64", +"q[. c #957B62", +"r[. c #7F6B6B", +"s[. c #494861", +"t[. c #414461", +"u[. c #3B415B", +"v[. c #45434D", +"w[. c #5B4A37", +"x[. c #634D39", +"y[. c #544633", +"z[. c #46422E", +"A[. c #484431", +"B[. c #48412F", +"C[. c #44412D", +"D[. c #514733", +"E[. c #50412F", +"F[. c #6F5838", +"G[. c #C88E33", +"H[. c #CB8A24", +"I[. c #614427", +"J[. c #28232A", +"K[. c #221F26", +"L[. c #231F26", +"M[. c #232027", +"N[. c #474951", +"O[. c #A09B96", +"P[. c #BA9652", +"Q[. c #CC9E2F", +"R[. c #847040", +"S[. c #00030C", +"T[. c #020518", +"U[. c #04071A", +"V[. c #101226", +"W[. c #101227", +"X[. c #080A21", +"Y[. c #02031D", +"Z[. c #02041F", +"`[. c #0C0E27", +" }. c #02061F", +".}. c #020422", +"+}. c #090A26", +"@}. c #2C2D41", +"#}. c #4D505E", +"$}. c #62646D", +"%}. c #575969", +"&}. c #3A3D52", +"*}. c #11132D", +"=}. c #050623", +"-}. c #070826", +";}. c #040624", +">}. c #040524", +",}. c #080928", +"'}. c #0D0F2F", +")}. c #060928", +"!}. c #0A0D2C", +"~}. c #0E102F", +"{}. c #10112F", +"]}. c #2A2C46", +"^}. c #505267", +"/}. c #5C5E6F", +"(}. c #5B5D6F", +"_}. c #4F5165", +":}. c #43455B", +"<}. c #3A3C55", +"[}. c #2D2F49", +"}}. c #242843", +"|}. c #212540", +"1}. c #242842", +"2}. c #252842", +"3}. c #1D1E3C", +"4}. c #21223E", +"5}. c #20233D", +"6}. c #151732", +"7}. c #12132F", +"8}. c #141631", +"9}. c #23253C", +"0}. c #262845", +"a}. c #2A2D46", +"b}. c #22263C", +"c}. c #181A36", +"d}. c #1E1D30", +"e}. c #26272E", +"f}. c #2D2D3A", +"g}. c #3F4130", +"h}. c #43482F", +"i}. c #333A1F", +"j}. c #484E34", +"k}. c #42492E", +"l}. c #62654C", +"m}. c #3E422B", +"n}. c #2D321E", +"o}. c #313520", +"p}. c #393922", +"q}. c #484430", +"r}. c #575646", +"s}. c #686659", +"t}. c #686555", +"u}. c #5B5A49", +"v}. c #545344", +"w}. c #555446", +"x}. c #5D5B4E", +"y}. c #716F60", +"z}. c #6D6D5C", +"A}. c #4C4D3D", +"B}. c #434336", +"C}. c #646257", +"D}. c #89867C", +"E}. c #9E9E93", +"F}. c #B8BAB2", +"G}. c #C0C2BE", +"H}. c #BFC0B9", +"I}. c #A9AAA0", +"J}. c #949B8B", +"K}. c #9EA190", +"L}. c #A2A391", +"M}. c #BCBCAE", +"N}. c #D2CFC4", +"O}. c #D1CBC3", +"P}. c #DBD4D1", +"Q}. c #E1D4D1", +"R}. c #C6C2BE", +"S}. c #555874", +"T}. c #4D4F6D", +"U}. c #51556C", +"V}. c #686553", +"W}. c #5C5F6A", +"X}. c #575B74", +"Y}. c #595C7C", +"Z}. c #5A5E7C", +"`}. c #5D617C", +" |. c #6C6F86", +".|. c #AFB3BA", +"+|. c #A9AFAE", +"@|. c #C6CEC2", +"#|. c #D1D9C6", +"$|. c #EFF4ED", +"%|. c #C2C8B2", +"&|. c #767C57", +"*|. c #979177", +"=|. c #92816B", +"-|. c #9C836A", +";|. c #8D6D5E", +">|. c #515063", +",|. c #3F4358", +"'|. c #403F53", +")|. c #4E4442", +"!|. c #584636", +"~|. c #5F4F3D", +"{|. c #574F3D", +"]|. c #4B4533", +"^|. c #483C2E", +"/|. c #4A3F30", +"(|. c #504732", +"_|. c #4E402B", +":|. c #493A2B", +"<|. c #5C5248", +"[|. c #BEB3A0", +"}|. c #D39F42", +"||. c #CD8C2C", +"1|. c #4B3620", +"2|. c #15141B", +"3|. c #1B1515", +"4|. c #2A2019", +"5|. c #73552F", +"6|. c #C68D2A", +"7|. c #C39430", +"8|. c #6F6656", +"9|. c #494E56", +"0|. c #070912", +"a|. c #00010A", +"b|. c #00010C", +"c|. c #02031E", +"d|. c #01031D", +"e|. c #01061E", +"f|. c #040723", +"g|. c #2B2C42", +"h|. c #565866", +"i|. c #5E606C", +"j|. c #61636E", +"k|. c #5F616C", +"l|. c #2D2F44", +"m|. c #11132E", +"n|. c #060824", +"o|. c #050625", +"p|. c #070827", +"q|. c #040726", +"r|. c #0D102F", +"s|. c #161937", +"t|. c #35374F", +"u|. c #4C4E63", +"v|. c #41445A", +"w|. c #2E2F4A", +"x|. c #262844", +"y|. c #222641", +"z|. c #242742", +"A|. c #252741", +"B|. c #232441", +"C|. c #20213D", +"D|. c #252740", +"E|. c #272942", +"F|. c #292B44", +"G|. c #292A44", +"H|. c #1E203B", +"I|. c #2B2F40", +"J|. c #202534", +"K|. c #10142B", +"L|. c #1B1D2D", +"M|. c #3D3930", +"N|. c #4B4534", +"O|. c #42412F", +"P|. c #40432E", +"Q|. c #454A32", +"R|. c #3C4127", +"S|. c #2F331A", +"T|. c #42472E", +"U|. c #465035", +"V|. c #4F563E", +"W|. c #2D351E", +"X|. c #323924", +"Y|. c #393B27", +"Z|. c #3E3B28", +"`|. c #464231", +" 1. c #4D4D3F", +".1. c #605F52", +"+1. c #615F53", +"@1. c #605F53", +"#1. c #5E5D53", +"$1. c #6C6A62", +"%1. c #7D7A71", +"&1. c #817F73", +"*1. c #7D7C6F", +"=1. c #727267", +"-1. c #7E7E76", +";1. c #9D9C95", +">1. c #B5B3AD", +",1. c #BDBDB6", +"'1. c #C0C1BB", +")1. c #C0C0BC", +"!1. c #BDBDB5", +"~1. c #9C9C90", +"{1. c #828979", +"]1. c #9C9E8D", +"^1. c #A09F8E", +"/1. c #B9B6AA", +"(1. c #C3BEB2", +"_1. c #BCB5A8", +":1. c #D6CFC8", +"<1. c #CEC6C2", +"[1. c #A29E9E", +"}1. c #626479", +"|1. c #4B4C6B", +"11. c #4E4F6C", +"21. c #595B72", +"31. c #65656C", +"41. c #5B5E72", +"51. c #595D78", +"61. c #5E627F", +"71. c #60647F", +"81. c #7B7F97", +"91. c #B9BDCA", +"01. c #A9ADB7", +"a1. c #B0B6B8", +"b1. c #E7ECE5", +"c1. c #FBFDFA", +"d1. c #E2E4D4", +"e1. c #919170", +"f1. c #ABA388", +"g1. c #A09079", +"h1. c #9C856D", +"i1. c #8E6F5C", +"j1. c #5C5661", +"k1. c #464760", +"l1. c #414559", +"m1. c #424057", +"n1. c #524848", +"o1. c #554335", +"p1. c #64543E", +"q1. c #5D523E", +"r1. c #4D4433", +"s1. c #43392A", +"t1. c #443A2C", +"u1. c #4B4230", +"v1. c #473927", +"w1. c #47372B", +"x1. c #45382B", +"y1. c #929599", +"z1. c #C4C1B9", +"A1. c #BC914A", +"B1. c #A87324", +"C1. c #322C1F", +"D1. c #221E25", +"E1. c #201E24", +"F1. c #1E1910", +"G1. c #93621D", +"H1. c #DE962A", +"I1. c #A57E43", +"J1. c #5D5B57", +"K1. c #505459", +"L1. c #3E4049", +"M1. c #050710", +"N1. c #00010B", +"O1. c #010511", +"P1. c #01021E", +"Q1. c #02041E", +"R1. c #01051E", +"S1. c #0E1129", +"T1. c #484B59", +"U1. c #555762", +"V1. c #63656F", +"W1. c #696B73", +"X1. c #4A4B59", +"Y1. c #12122C", +"Z1. c #030522", +"`1. c #030422", +" 2. c #060726", +".2. c #030524", +"+2. c #1A1C3A", +"@2. c #2C2D48", +"#2. c #414359", +"$2. c #373C54", +"%2. c #2D314B", +"&2. c #2B2D47", +"*2. c #262743", +"=2. c #232442", +"-2. c #232641", +";2. c #23253E", +">2. c #171834", +",2. c #090A28", +"'2. c #10112E", +")2. c #161831", +"!2. c #1D1F36", +"~2. c #1C1D35", +"{2. c #11132C", +"]2. c #0B0C26", +"^2. c #1E212F", +"/2. c #38372E", +"(2. c #32302E", +"_2. c #383332", +":2. c #524333", +"<2. c #4F4832", +"[2. c #3E4028", +"}2. c #3F432B", +"|2. c #41462F", +"12. c #33361E", +"22. c #44462E", +"32. c #474C35", +"42. c #3D3F2A", +"52. c #3E422F", +"62. c #4A4F3A", +"72. c #454532", +"82. c #4B4639", +"92. c #595448", +"02. c #5E6052", +"a2. c #64665A", +"b2. c #66665E", +"c2. c #6D6E69", +"d2. c #757672", +"e2. c #807F7B", +"f2. c #8A8783", +"g2. c #8D8B84", +"h2. c #96948D", +"i2. c #A1A19B", +"j2. c #B4B2AF", +"k2. c #B9B7B3", +"l2. c #BCBCB8", +"m2. c #C0C0BB", +"n2. c #BDBCB7", +"o2. c #ADAAA1", +"p2. c #807E72", +"q2. c #757869", +"r2. c #919080", +"s2. c #A19E8F", +"t2. c #B1ACA1", +"u2. c #AEA799", +"v2. c #A09785", +"w2. c #C6C0B5", +"x2. c #AFADA6", +"y2. c #716D72", +"z2. c #494A66", +"A2. c #4A4A6B", +"B2. c #555974", +"C2. c #5E627C", +"D2. c #5F6480", +"E2. c #6D718A", +"F2. c #ABAFB9", +"G2. c #BEC1CB", +"H2. c #BCC1C7", +"I2. c #E6E9E9", +"J2. c #F9FAF7", +"K2. c #EDEDE0", +"L2. c #AAA487", +"M2. c #93876B", +"N2. c #908167", +"O2. c #968069", +"P2. c #957B64", +"Q2. c #756A6B", +"R2. c #4D4C64", +"S2. c #444258", +"T2. c #5B5052", +"U2. c #614C3E", +"V2. c #6B583F", +"W2. c #5F513B", +"X2. c #4C4030", +"Y2. c #3F3728", +"Z2. c #463D2C", +"`2. c #423424", +" 3. c #443425", +".3. c #3E3125", +"+3. c #596066", +"@3. c #707582", +"#3. c #77562D", +"$3. c #775326", +"%3. c #271E18", +"&3. c #15161A", +"*3. c #100E0E", +"=3. c #432E11", +"-3. c #AB7227", +";3. c #816C4B", +">3. c #5B595D", +",3. c #4F515A", +"'3. c #4F535A", +")3. c #2D2F37", +"!3. c #080A1F", +"~3. c #04051E", +"{3. c #27283B", +"]3. c #5A5A64", +"^3. c #64646B", +"/3. c #5E5F69", +"(3. c #24253A", +"_3. c #040522", +":3. c #030421", +"<3. c #030420", +"[3. c #0A0B26", +"}3. c #151632", +"|3. c #050624", +"13. c #2E304A", +"23. c #31334C", +"33. c #282A46", +"43. c #242541", +"53. c #222340", +"63. c #20243F", +"73. c #1F233E", +"83. c #20233E", +"93. c #1E1F3A", +"03. c #0B0C28", +"a3. c #080926", +"b3. c #060722", +"c3. c #070924", +"d3. c #070823", +"e3. c #050620", +"f3. c #322F33", +"g3. c #37342D", +"h3. c #373426", +"i3. c #3E372B", +"j3. c #5A4536", +"k3. c #504837", +"l3. c #3E412B", +"m3. c #363922", +"n3. c #31361F", +"o3. c #252C15", +"p3. c #3C412B", +"q3. c #3C3E28", +"r3. c #3E402B", +"s3. c #3D4230", +"t3. c #4F5342", +"u3. c #4C4E3F", +"v3. c #4B4B41", +"w3. c #555347", +"x3. c #5E5D51", +"y3. c #575B55", +"z3. c #5C6365", +"A3. c #6C7377", +"B3. c #848483", +"C3. c #8A8784", +"D3. c #93928F", +"E3. c #9D9B99", +"F3. c #A4A2A0", +"G3. c #A9A8A6", +"H3. c #ADACA8", +"I3. c #B1AFAB", +"J3. c #B4B2AE", +"K3. c #B8B7B3", +"L3. c #BCBAB6", +"M3. c #B1AEA8", +"N3. c #8D8A82", +"O3. c #656358", +"P3. c #757365", +"Q3. c #938F80", +"R3. c #9E998A", +"S3. c #A29989", +"T3. c #968978", +"U3. c #A49B8E", +"V3. c #87837B", +"W3. c #57555E", +"X3. c #484C68", +"Y3. c #4D516B", +"Z3. c #515570", +"`3. c #565A74", +" 4. c #5A5F7B", +".4. c #5C607E", +"+4. c #5E637E", +"@4. c #5E647D", +"#4. c #606580", +"$4. c #686C85", +"%4. c #84869C", +"&4. c #B0B1BE", +"*4. c #E9E8EC", +"=4. c #DCE2E2", +"-4. c #BCC9C6", +";4. c #BEC5B5", +">4. c #BAB79E", +",4. c #A4977D", +"'4. c #9C8D72", +")4. c #928567", +"!4. c #B3A394", +"~4. c #A49893", +"{4. c #736566", +"]4. c #635A5E", +"^4. c #615B61", +"/4. c #746966", +"(4. c #6C5440", +"_4. c #70593D", +":4. c #60523C", +"<4. c #4E4130", +"[4. c #423A2B", +"}4. c #332D1F", +"|4. c #3D3527", +"14. c #413426", +"24. c #413022", +"34. c #3F3125", +"44. c #383532", +"54. c #1A1C1F", +"64. c #302515", +"74. c #322A1F", +"84. c #2C2A30", +"94. c #1D160A", +"04. c #6C5130", +"a4. c #63564C", +"b4. c #4C515A", +"c4. c #4A4D54", +"d4. c #4D5057", +"e4. c #292B33", +"f4. c #0B0D16", +"g4. c #070812", +"h4. c #080A1E", +"i4. c #272839", +"j4. c #545461", +"k4. c #65676E", +"l4. c #5F5E67", +"m4. c #5F6068", +"n4. c #3C3E4E", +"o4. c #080923", +"p4. c #1A1C36", +"q4. c #1E203A", +"r4. c #0C0D2B", +"s4. c #0B0C2A", +"t4. c #1D1E3B", +"u4. c #22233E", +"v4. c #1F203B", +"w4. c #1F203C", +"x4. c #252642", +"y4. c #191A36", +"z4. c #1B1D39", +"A4. c #1E223D", +"B4. c #1A1D39", +"C4. c #131430", +"D4. c #02031F", +"E4. c #060821", +"F4. c #14132C", +"G4. c #1E1C2E", +"H4. c #22202B", +"I4. c #302F28", +"J4. c #564839", +"K4. c #5A5440", +"L4. c #484933", +"M4. c #393B25", +"N4. c #353923", +"O4. c #383D27", +"P4. c #393E28", +"Q4. c #393D27", +"R4. c #414430", +"S4. c #373B29", +"T4. c #353B2A", +"U4. c #45493C", +"V4. c #45473E", +"W4. c #494B46", +"X4. c #52514E", +"Y4. c #4A4948", +"Z4. c #4F5154", +"`4. c #5D6266", +" 5. c #787C7E", +".5. c #90908E", +"+5. c #969491", +"@5. c #9E9D9B", +"#5. c #A6A5A3", +"$5. c #A7A6A4", +"%5. c #A5A4A1", +"&5. c #A2A19C", +"*5. c #A6A59F", +"=5. c #ABA8A3", +"-5. c #ADABA5", +";5. c #ACA9A3", +">5. c #8A867D", +",5. c #726B62", +"'5. c #60584C", +")5. c #525141", +"!5. c #5F5C4C", +"~5. c #786B5F", +"{5. c #928278", +"]5. c #A39185", +"^5. c #958173", +"/5. c #847669", +"(5. c #665E54", +"_5. c #565358", +":5. c #484C65", +"<5. c #4D516A", +"[5. c #52566F", +"}5. c #5C6079", +"|5. c #5F657D", +"15. c #60667C", +"25. c #636781", +"35. c #898C9E", +"45. c #C5C7CF", +"55. c #B4B7BE", +"65. c #E5E5E6", +"75. c #E6EBE9", +"85. c #A5B3A4", +"95. c #7E8366", +"05. c #8B7E5E", +"a5. c #A08D70", +"b5. c #97876B", +"c5. c #94856A", +"d5. c #A99C89", +"e5. c #A69C91", +"f5. c #B0A196", +"g5. c #937E6B", +"h5. c #938474", +"i5. c #96897F", +"j5. c #7F6D60", +"k5. c #775F48", +"l5. c #6F573C", +"m5. c #5D4C37", +"n5. c #4D402F", +"o5. c #484031", +"p5. c #3F382B", +"q5. c #362E20", +"r5. c #3F3224", +"s5. c #3B2C1D", +"t5. c #3A2C22", +"u5. c #423E3E", +"v5. c #414449", +"w5. c #151213", +"x5. c #1E191D", +"y5. c #000201", +"z5. c #101013", +"A5. c #43434A", +"B5. c #45454C", +"C5. c #4F5056", +"D5. c #404046", +"E5. c #363332", +"F5. c #4C4D4F", +"G5. c #494A4E", +"H5. c #4B4951", +"I5. c #4B4E56", +"J5. c #4C4F56", +"K5. c #474A51", +"L5. c #23252D", +"M5. c #0A0C15", +"N5. c #23252E", +"O5. c #161721", +"P5. c #01030F", +"Q5. c #00021B", +"R5. c #242736", +"S5. c #5B5B64", +"T5. c #69696F", +"U5. c #65646A", +"V5. c #5B5C64", +"W5. c #50535D", +"X5. c #13152F", +"Y5. c #11122D", +"Z5. c #060725", +"`5. c #050724", +" 6. c #0A0B28", +".6. c #1B1C38", +"+6. c #1C203B", +"@6. c #161935", +"#6. c #0C0D28", +"$6. c #080925", +"%6. c #080A23", +"&6. c #010520", +"*6. c #040620", +"=6. c #0D0E25", +"-6. c #4D4135", +";6. c #5A4D3B", +">6. c #4B4732", +",6. c #474732", +"'6. c #3B3D27", +")6. c #41422D", +"!6. c #3B422C", +"~6. c #373C29", +"{6. c #363A2A", +"]6. c #484D40", +"^6. c #474C43", +"/6. c #3D413F", +"(6. c #4A4D4F", +"_6. c #484A4B", +":6. c #444445", +"<6. c #656466", +"[6. c #818180", +"}6. c #90928E", +"|6. c #989994", +"16. c #9E9E9A", +"26. c #A3A3A1", +"36. c #A3A29F", +"46. c #9D9C97", +"56. c #97978F", +"66. c #939188", +"76. c #98958C", +"86. c #96928A", +"96. c #99968C", +"06. c #686558", +"a6. c #594D41", +"b6. c #5A493C", +"c6. c #4C4333", +"d6. c #50543F", +"e6. c #6C6455", +"f6. c #8E7B74", +"g6. c #806E64", +"h6. c #736255", +"i6. c #625044", +"j6. c #635749", +"k6. c #5D5757", +"l6. c #464A5F", +"m6. c #494E64", +"n6. c #4D5268", +"o6. c #51566C", +"p6. c #565B71", +"q6. c #5B6076", +"r6. c #5F657A", +"s6. c #636A7D", +"t6. c #6A7181", +"u6. c #747985", +"v6. c #EFF1F2", +"w6. c #C5C8C5", +"x6. c #CFCAC1", +"y6. c #BFB9AE", +"z6. c #999685", +"A6. c #827C5E", +"B6. c #837455", +"C6. c #9D8A6B", +"D6. c #8D7B5E", +"E6. c #8A7960", +"F6. c #9E8E79", +"G6. c #958A7A", +"H6. c #AFA297", +"I6. c #A69081", +"J6. c #9E8879", +"K6. c #8E7866", +"L6. c #775E45", +"M6. c #7A6249", +"N6. c #654E36", +"O6. c #59452F", +"P6. c #4F4231", +"Q6. c #463E2F", +"R6. c #453E31", +"S6. c #3A3224", +"T6. c #3C3022", +"U6. c #3A2A1C", +"V6. c #34261E", +"W6. c #3E3A3C", +"X6. c #47494F", +"Y6. c #111115", +"Z6. c #0F0C10", +"`6. c #1C1B1B", +" 7. c #0A0D0F", +".7. c #171A1C", +"+7. c #25282C", +"@7. c #2A2D32", +"#7. c #2D3035", +"$7. c #33353C", +"%7. c #3D3F45", +"&7. c #43464C", +"*7. c #46484F", +"=7. c #4A4C53", +"-7. c #4A4E56", +";7. c #47484E", +">7. c #474A50", +",7. c #4C4E55", +"'7. c #4B4C53", +")7. c #13161D", +"!7. c #01040B", +"~7. c #38393B", +"{7. c #040711", +"]7. c #0C0E24", +"^7. c #454851", +"/7. c #66696D", +"(7. c #626469", +"_7. c #585A5F", +":7. c #55595E", +"<7. c #383B4A", +"[7. c #090A25", +"}7. c #050722", +"|7. c #070923", +"17. c #050622", +"27. c #0B0D26", +"37. c #191B35", +"47. c #11122E", +"57. c #161834", +"67. c #111330", +"77. c #080A24", +"87. c #24263E", +"97. c #25273E", +"07. c #181A31", +"a7. c #1D1E35", +"b7. c #212339", +"c7. c #2C2F42", +"d7. c #26293E", +"e7. c #0C0E26", +"f7. c #03061F", +"g7. c #02051D", +"h7. c #05081F", +"i7. c #1F1E25", +"j7. c #362B24", +"k7. c #413227", +"l7. c #46402F", +"m7. c #4D4D3A", +"n7. c #4C4F3C", +"o7. c #4E513C", +"p7. c #494C3A", +"q7. c #444A37", +"r7. c #3A3F2B", +"s7. c #4B4E41", +"t7. c #5E625B", +"u7. c #4A4C4E", +"v7. c #3E3E41", +"w7. c #343433", +"x7. c #525251", +"y7. c #8F908D", +"z7. c #959691", +"A7. c #939391", +"B7. c #9A9797", +"C7. c #A09E9B", +"D7. c #9C9C98", +"E7. c #9B9C97", +"F7. c #94948E", +"G7. c #8D8B82", +"H7. c #858278", +"I7. c #807D74", +"J7. c #706B60", +"K7. c #747064", +"L7. c #505143", +"M7. c #504A3B", +"N7. c #655247", +"O7. c #6C5B50", +"P7. c #6B6757", +"Q7. c #757364", +"R7. c #847570", +"S7. c #6F5E54", +"T7. c #5D5243", +"U7. c #514434", +"V7. c #5F4C36", +"W7. c #524D4E", +"X7. c #44495E", +"Y7. c #484D63", +"Z7. c #50566B", +"`7. c #545B6F", +" 8. c #5B6274", +".8. c #656F7D", +"+8. c #758187", +"@8. c #808C90", +"#8. c #8E9897", +"$8. c #D0D3D2", +"%8. c #E6EAE8", +"&8. c #B6B7AF", +"*8. c #9E9382", +"=8. c #8C7A66", +"-8. c #7F6E55", +";8. c #857353", +">8. c #7D6A49", +",8. c #847253", +"'8. c #8E7F63", +")8. c #8E7D63", +"!8. c #82705B", +"~8. c #887966", +"{8. c #9A8870", +"]8. c #8A6F56", +"^8. c #8A6E56", +"/8. c #7D6447", +"(8. c #755F42", +"_8. c #6C583F", +":8. c #614B34", +"<8. c #57422E", +"[8. c #524332", +"}8. c #463C2C", +"|8. c #3F382A", +"18. c #3A3327", +"28. c #372D22", +"38. c #36281F", +"48. c #2E211C", +"58. c #383637", +"68. c #484B52", +"78. c #45474E", +"88. c #2F3036", +"98. c #13110F", +"08. c #131111", +"a8. c #17181C", +"b8. c #313439", +"c8. c #303339", +"d8. c #32353C", +"e8. c #383B42", +"f8. c #373A41", +"g8. c #44464D", +"h8. c #25272B", +"i8. c #303235", +"j8. c #44474C", +"k8. c #464950", +"l8. c #0E1018", +"m8. c #03050C", +"n8. c #28282B", +"o8. c #343639", +"p8. c #060914", +"q8. c #0B0D1F", +"r8. c #3C3E4C", +"s8. c #626369", +"t8. c #62646A", +"u8. c #50535A", +"v8. c #484C54", +"w8. c #171A2E", +"x8. c #1A1C35", +"y8. c #1B1D36", +"z8. c #161832", +"A8. c #0D0E2A", +"B8. c #0C0D29", +"C8. c #292B42", +"D8. c #1B1D32", +"E8. c #222439", +"F8. c #3F4251", +"G8. c #434756", +"H8. c #262939", +"I8. c #03051D", +"J8. c #0C0D24", +"K8. c #241C24", +"L8. c #362B22", +"M8. c #3F3B29", +"N8. c #4E4F3D", +"O8. c #4A4F3D", +"P8. c #535846", +"Q8. c #686B61", +"R8. c #5B6256", +"S8. c #575D50", +"T8. c #6C6F6A", +"U8. c #616266", +"V8. c #535155", +"W8. c #454242", +"X8. c #423F3B", +"Y8. c #706E6C", +"Z8. c #898886", +"`8. c #8E8D8C", +" 9. c #949390", +".9. c #848382", +"+9. c #68686D", +"@9. c #7A787D", +"#9. c #8A8586", +"$9. c #7D7874", +"%9. c #7E7970", +"&9. c #737066", +"*9. c #706B61", +"=9. c #676056", +"-9. c #60564D", +";9. c #564A40", +">9. c #534A3D", +",9. c #494A39", +"'9. c #534E40", +")9. c #66534A", +"!9. c #736056", +"~9. c #6D6156", +"{9. c #6D665A", +"]9. c #6A5B57", +"^9. c #5F4D47", +"/9. c #564B41", +"(9. c #54493B", +"_9. c #564832", +":9. c #4A4847", +"<9. c #43485B", +"[9. c #474C60", +"}9. c #4B5064", +"|9. c #505569", +"19. c #535C6D", +"29. c #5C6977", +"39. c #6D7B85", +"49. c #75847D", +"59. c #828E7F", +"69. c #A7B0A0", +"79. c #C1C2B3", +"89. c #9C9F8C", +"99. c #808069", +"09. c #807256", +"a9. c #867054", +"b9. c #866C52", +"c9. c #8D7352", +"d9. c #8A7150", +"e9. c #8C7A5A", +"f9. c #A19479", +"g9. c #8C7B60", +"h9. c #897865", +"i9. c #8D7D6A", +"j9. c #7C6847", +"k9. c #866E47", +"l9. c #846A47", +"m9. c #72583B", +"n9. c #6F563D", +"o9. c #665038", +"p9. c #604B35", +"q9. c #584531", +"r9. c #534433", +"s9. c #453B2B", +"t9. c #393227", +"u9. c #342B20", +"v9. c #32281F", +"w9. c #251D19", +"x9. c #353436", +"y9. c #42474E", +"z9. c #3C3D3F", +"A9. c #2F251D", +"B9. c #1E1410", +"C9. c #2D2A2F", +"D9. c #49494F", +"E9. c #313235", +"F9. c #08090B", +"G9. c #141512", +"H9. c #303036", +"I9. c #323339", +"J9. c #303138", +"K9. c #30333B", +"L9. c #31343C", +"M9. c #363940", +"N9. c #46494F", +"O9. c #36393D", +"P9. c #43464B", +"Q9. c #404349", +"R9. c #41444D", +"S9. c #454850", +"T9. c #42454C", +"U9. c #0F1119", +"V9. c #06080F", +"W9. c #2F3033", +"X9. c #36383B", +"Y9. c #070A14", +"Z9. c #0A0C20", +"`9. c #30323F", +" 0. c #5D5F62", +".0. c #626266", +"+0. c #4E5059", +"@0. c #40434D", +"#0. c #484B55", +"$0. c #353946", +"%0. c #070921", +"&0. c #1A1C34", +"*0. c #191B33", +"=0. c #23253D", +"-0. c #1E2039", +";0. c #0F112A", +">0. c #0A0C26", +",0. c #13152E", +"'0. c #28293F", +")0. c #16182D", +"!0. c #0D0F24", +"~0. c #0F1126", +"{0. c #14172C", +"]0. c #232537", +"^0. c #14172B", +"/0. c #03051B", +"(0. c #03071D", +"_0. c #1B1625", +":0. c #312A22", +"<0. c #41412E", +"[0. c #414532", +"}0. c #262E1B", +"|0. c #444B38", +"10. c #656C5F", +"20. c #616A5C", +"30. c #4D5748", +"40. c #504E52", +"50. c #403B3A", +"60. c #35312B", +"70. c #605C57", +"80. c #86827E", +"90. c #8F8C89", +"00. c #928F8C", +"a0. c #908B8A", +"b0. c #6E6A6F", +"c0. c #2E2F3C", +"d0. c #3F434F", +"e0. c #484A57", +"f0. c #423E44", +"g0. c #4C473B", +"h0. c #453E2F", +"i0. c #463C31", +"j0. c #4D4339", +"k0. c #4F463B", +"l0. c #4B4033", +"m0. c #564F43", +"n0. c #5D4843", +"o0. c #5B473E", +"p0. c #503F35", +"q0. c #53453A", +"r0. c #55473C", +"s0. c #615046", +"t0. c #574B41", +"u0. c #4B4237", +"v0. c #4E4735", +"w0. c #454A5A", +"x0. c #464C5E", +"y0. c #4C5163", +"z0. c #515668", +"A0. c #576270", +"B0. c #657880", +"C0. c #63766F", +"D0. c #576652", +"E0. c #707761", +"F0. c #A9AC98", +"G0. c #8B8771", +"H0. c #706F51", +"I0. c #777455", +"J0. c #857455", +"K0. c #957B60", +"L0. c #90735B", +"M0. c #998260", +"N0. c #A08E6B", +"O0. c #998969", +"P0. c #94876B", +"Q0. c #8F7E63", +"R0. c #827257", +"S0. c #7A6A51", +"T0. c #7C664A", +"U0. c #8F7859", +"V0. c #866D4E", +"W0. c #7A5E42", +"X0. c #765840", +"Y0. c #6C513B", +"Z0. c #624D37", +"`0. c #655440", +" a. c #5F503F", +".a. c #4A4030", +"+a. c #3B3425", +"@a. c #3B3429", +"#a. c #32291F", +"$a. c #2E261D", +"%a. c #1F1B18", +"&a. c #4E4E58", +"*a. c #4B4E55", +"=a. c #44474D", +"-a. c #524A44", +";a. c #5E4729", +">a. c #483216", +",a. c #484D52", +"'a. c #494C53", +")a. c #3D4046", +"!a. c #35383D", +"~a. c #18191D", +"{a. c #2B2B32", +"]a. c #2C2D33", +"^a. c #31343B", +"/a. c #33363D", +"(a. c #41424A", +"_a. c #45464C", +":a. c #303237", +"b. c #3F3729", +",b. c #413D2A", +"'b. c #4C4D3B", +")b. c #51585B", +"!b. c #495163", +"~b. c #4C5864", +"{b. c #505F6B", +"]b. c #586B78", +"^b. c #607372", +"/b. c #50634C", +"(b. c #4F5B40", +"_b. c #83866D", +":b. c #776D54", +"c. c #606169", +",c. c #2B2D41", +"'c. c #2F3142", +")c. c #515361", +"!c. c #50525D", +"~c. c #39394A", +"{c. c #16172C", +"]c. c #22222E", +"^c. c #5E5D5A", +"/c. c #696961", +"(c. c #484B40", +"_c. c #505548", +":c. c #54594B", +"d. c #5B4F49", +",d. c #41474F", +"'d. c #44444D", +")d. c #36373F", +"!d. c #303137", +"~d. c #18191C", +"{d. c #2D2E35", +"]d. c #2F3239", +"^d. c #35373E", +"/d. c #363840", +"(d. c #404248", +"_d. c #04060D", +":d. c #38383A", +"e. c #4D574A", +",e. c #556560", +"'e. c #62726C", +")e. c #5C6F60", +"!e. c #616E58", +"~e. c #787C62", +"{e. c #8A866B", +"]e. c #91846A", +"^e. c #8B7A5D", +"/e. c #968566", +"(e. c #948264", +"_e. c #908161", +":e. c #817050", +"f. c #3A3F4A", +",f. c #68696B", +"'f. c #717276", +")f. c #68696E", +"!f. c #3A3C4B", +"~f. c #101127", +"{f. c #0E0F22", +"]f. c #7D7C7C", +"^f. c #7C7D79", +"/f. c #7D7F7A", +"(f. c #7F827C", +"_f. c #7F7F7D", +":f. c #797877", +"g. c #5F4E38", +",g. c #5B4834", +"'g. c #403F44", +")g. c #3C3D43", +"!g. c #2E2F36", +"~g. c #2E3138", +"{g. c #3B3C42", +"]g. c #323439", +"^g. c #1D1F26", +"/g. c #0A0B15", +"(g. c #13141D", +"_g. c #0F121B", +":g. c #0E101C", +"h. c #5D624F", +",h. c #706D55", +"'h. c #8A7F65", +")h. c #908266", +"!h. c #97886B", +"~h. c #8D7E61", +"{h. c #988569", +"]h. c #806E52", +"^h. c #66563C", +"/h. c #5B543A", +"(h. c #7A6C4E", +"_h. c #998261", +":h. c #9A8465", +"i. c #54545B", +",i. c #31313E", +"'i. c #131525", +")i. c #17192B", +"!i. c #2C2D3E", +"~i. c #4F4F59", +"{i. c #58575C", +"]i. c #474651", +"^i. c #2E2F3E", +"/i. c #262833", +"(i. c #494945", +"_i. c #48453A", +":i. c #483B30", +"j. c #131425", +",j. c #020519", +"'j. c #04071C", +")j. c #202235", +"!j. c #000419", +"~j. c #070A1F", +"{j. c #131529", +"]j. c #3E414E", +"^j. c #46454D", +"/j. c #534B47", +"(j. c #554A45", +"_j. c #3A2E28", +":j. c #271D15", +"k. c #655443", +",k. c #655445", +"'k. c #584F45", +")k. c #2A2C34", +"!k. c #1D2031", +"~k. c #0F0F1F", +"{k. c #0A0A18", +"]k. c #141421", +"^k. c #36383F", +"/k. c #32363B", +"(k. c #34353C", +"_k. c #34363B", +":k. c #241F27", +"l. c #594935", +",l. c #4F3F2B", +"'l. c #5E4B35", +")l. c #766147", +"!l. c #937E67", +"~l. c #958E87", +"{l. c #777C84", +"]l. c #796C55", +"^l. c #AC9880", +"/l. c #A19378", +"(l. c #A08F76", +"_l. c #958069", +":l. c #95816D", +"m. c #232436", +",m. c #242839", +"'m. c #3A353C", +")m. c #4D3A39", +"!m. c #503D38", +"~m. c #503B34", +"{m. c #55413B", +"]m. c #49464C", +"^m. c #353748", +"/m. c #38394B", +"(m. c #3B3E4C", +"_m. c #3D424E", +":m. c #464F57", +"n. c #473B39", +",n. c #4A4645", +"'n. c #4A4B51", +")n. c #3F404A", +"!n. c #363743", +"~n. c #3B3B48", +"{n. c #4D4F57", +"]n. c #54575C", +"^n. c #595B61", +"/n. c #4F5159", +"(n. c #30313E", +"_n. c #12111C", +":n. c #13121C", +"o. c #020313", +",o. c #06081A", +"'o. c #000214", +")o. c #010214", +"!o. c #010215", +"~o. c #0C0F20", +"{o. c #1B1E2D", +"]o. c #2F323C", +"^o. c #2E313B", +"/o. c #141527", +"(o. c #131427", +"_o. c #1C1E2D", +":o. c #232533", +"p. c #2A2D3E", +",p. c #282937", +"'p. c #545042", +")p. c #5C5340", +"!p. c #4C453B", +"~p. c #15141C", +"{p. c #111321", +"]p. c #373639", +"^p. c #44403A", +"/p. c #393F45", +"(p. c #373332", +"_p. c #65491E", +":p. c #765622", +"q. c #89755D", +",q. c #86705C", +"'q. c #84705E", +")q. c #998675", +"!q. c #AF9E8E", +"~q. c #A89788", +"{q. c #A49286", +"]q. c #B7A59B", +"^q. c #AA9A8E", +"/q. c #988877", +"(q. c #908270", +"_q. c #877A5D", +":q. c #716545", +"r. c #181826", +",r. c #26212A", +"'r. c #3A3137", +")r. c #231F2F", +"!r. c #1D1F33", +"~r. c #1F2132", +"{r. c #212333", +"]r. c #252535", +"^r. c #272736", +"/r. c #292C38", +"(r. c #3C363B", +"_r. c #4C3D3B", +":r. c #4A3F3F", +"s. c #362118", +",s. c #301D10", +"'s. c #2F1F19", +")s. c #251914", +"!s. c #372821", +"~s. c #57443A", +"{s. c #594537", +"]s. c #5A462F", +"^s. c #503A24", +"/s. c #331F10", +"(s. c #2A1910", +"_s. c #372D28", +":s. c #3D3E43", +"t. c #45464D", +",t. c #43444B", +"'t. c #121011", +")t. c #141211", +"!t. c #403F46", +"~t. c #16161B", +"{t. c #060811", +"]t. c #050915", +"^t. c #080A18", +"/t. c #141523", +"(t. c #161624", +"_t. c #0D0F1D", +":t. c #1A151C", +"u. c #33333C", +",u. c #242526", +"'u. c #38383C", +")u. c #38393E", +"!u. c #11110D", +"~u. c #404047", +"{u. c #3F4248", +"]u. c #4B4E59", +"^u. c #4B4950", +"/u. c #292525", +"(u. c #141113", +"_u. c #111114", +":u. c #080A11", +"v. c #72624C", +",v. c #6E5F47", +"'v. c #453F39", +")v. c #0E0E1A", +"!v. c #201F2B", +"~v. c #4A4A4F", +"{v. c #7E602F", +"]v. c #D78D22", +"^v. c #DB8F20", +"/v. c #8D6D40", +"(v. c #4C4D53", +"_v. c #33373E", +":v. c #373840", +"w. c #897666", +",w. c #7E6B59", +"'w. c #7E6D55", +")w. c #807057", +"!w. c #7E7054", +"~w. c #796B4F", +"{w. c #766955", +"]w. c #736453", +"^w. c #6E6149", +"/w. c #393333", +"(w. c #0A0C19", +"_w. c #202029", +":w. c #484B53", +"x. c #816F5F", +",x. c #806D5E", +"'x. c #8F7C6D", +")x. c #867364", +"!x. c #7B6858", +"~x. c #85735D", +"{x. c #82725B", +"]x. c #7C6E56", +"^x. c #7A6B55", +"/x. c #7C6F5F", +"(x. c #7B6E60", +"_x. c #6F6450", +":x. c #2F2A2D", +"y. c #474957", +",y. c #4B4C5B", +"'y. c #4F4F5E", +")y. c #515262", +"!y. c #525568", +"~y. c #56596E", +"{y. c #656875", +"]y. c #70736E", +"^y. c #737362", +"/y. c #7B705A", +"(y. c #766750", +"_y. c #7A6858", +":y. c #7E6A59", +"z. c #121421", +",z. c #161825", +"'z. c #2A2C39", +")z. c #2D2F3C", +"!z. c #313340", +"~z. c #343643", +"{z. c #393B47", +"]z. c #3E404D", +"^z. c #414350", +"/z. c #454754", +"(z. c #4E4D5B", +"_z. c #515161", +":z. c #525467", +"A. c #101220", +",A. c #151724", +"'A. c #212330", +")A. c #282A37", +"!A. c #2F323D", +"~A. c #31343E", +"{A. c #333641", +"]A. c #373A46", +"^A. c #40424F", +"/A. c #424451", +"(A. c #474955", +"_A. c #4B4C58", +":A. c #525161", +"B. c #443728", +",B. c #574B38", +"'B. c #554831", +")B. c #504233", +"!B. c #231D22", +"~B. c #0C101D", +"{B. c #0F111F", +"]B. c #111320", +"^B. c #1A1C28", +"/B. c #1C1E2A", +"(B. c #1F212D", +"_B. c #272835", +":B. c #31333C", +"C. c #301C12", +",C. c #231413", +"'C. c #2D2421", +")C. c #36322C", +"!C. c #292728", +"~C. c #42413C", +"{C. c #443D2D", +"]C. c #423422", +"^C. c #38281A", +"/C. c #473829", +"(C. c #594A36", +"_C. c #564733", +":C. c #5B513F", +"D. c #E3EBF1", +",D. c #8C919D", +"'D. c #3B3027", +")D. c #262420", +"!D. c #5A5B5D", +"~D. c #BC8B38", +"{D. c #CA9225", +"]D. c #9B948F", +"^D. c #EAF0F0", +"/D. c #6D5530", +"(D. c #674823", +"_D. c #191A22", +":D. c #141520", +"E. c #80899D", +",E. c #161515", +"'E. c #424348", +")E. c #424347", +"!E. c #4D4E53", +"~E. c #9D7339", +"{E. c #D9891B", +"]E. c #7F6242", +"^E. c #A7B1C1", +"/E. c #626265", +"(E. c #28231C", +"_E. c #17130F", +":E. c #221F21", +"F. c #605646", +",F. c #776D60", +"'F. c #6D6354", +")F. c #6E6456", +"!F. c #776C5F", +"~F. c #8A7D72", +"{F. c #8E8177", +"]F. c #7E7167", +"^F. c #7E7166", +"/F. c #7C6C5F", +"(F. c #796C60", +"_F. c #867E77", +":F. c #757879", +"G. c #2B180F", +",G. c #271509", +"'G. c #271408", +")G. c #301A10", +"!G. c #3A281F", +"~G. c #4B3F37", +"{G. c #4B4238", +"]G. c #4B4437", +"^G. c #4F4B3B", +"/G. c #535044", +"(G. c #555243", +"_G. c #524E3B", +":G. c #544935", +"H. c #4C4E57", +",H. c #866B43", +"'H. c #D9951E", +")H. c #866736", +"!H. c #BFC3CA", +"~H. c #C8CED0", +"{H. c #60544A", +"]H. c #4F3A1E", +"^H. c #2E251B", +"/H. c #312A1F", +"(H. c #5A431F", +"_H. c #654822", +":H. c #6E6E6C", +"I. c #988D82", +",I. c #7F7468", +"'I. c #81766A", +")I. c #807266", +"!I. c #786B5B", +"~I. c #7F7169", +"{I. c #7F7977", +"]I. c #697174", +"^I. c #58656C", +"/I. c #56606B", +"(I. c #676971", +"_I. c #857B77", +":I. c #85776D", +"J. c #52443B", +",J. c #52473D", +"'J. c #51473C", +")J. c #53493E", +"!J. c #554B41", +"~J. c #574F46", +"{J. c #5A544C", +"]J. c #5A554F", +"^J. c #5B5652", +"/J. c #5B5653", +"(J. c #5B5852", +"_J. c #5C5953", +":J. c #5C5753", +"K. c #D2D9E0", +",K. c #8D8684", +"'K. c #B87B20", +")K. c #D38C22", +"!K. c #5A5146", +"~K. c #4F525A", +"{K. c #46484D", +"]K. c #727B8B", +"^K. c #CBD3DB", +"/K. c #836F56", +"(K. c #E0921C", +"_K. c #C47C20", +":K. c #4C3928", +"L. c #737477", +",L. c #5B606C", +"'L. c #555866", +")L. c #565968", +"!L. c #5B606A", +"~L. c #726D67", +"{L. c #6A5A4F", +"]L. c #7B6961", +"^L. c #6F5D52", +"/L. c #756055", +"(L. c #786456", +"_L. c #7E6B5B", +":L. c #8F7D6A", +"M. c #514035", +",M. c #504034", +"'M. c #4C3F31", +")M. c #483D2E", +"!M. c #4C4132", +"~M. c #514A3B", +"{M. c #5D564A", +"]M. c #5A544A", +"^M. c #5A5851", +"/M. c #615D54", +"(M. c #675F51", +"_M. c #5C523F", +":M. c #5A5037", +"N. c #352C23", +",N. c #3C3429", +"'N. c #443B2F", +")N. c #453C30", +"!N. c #42382D", +"~N. c #453B30", +"{N. c #4B4339", +"]N. c #4C433A", +"^N. c #51483F", +"/N. c #524A42", +"(N. c #534C44", +"_N. c #524D44", +":N. c #535048", +"O. c #2F251E", +",O. c #855B21", +"'O. c #CD8619", +")O. c #835D2C", +"!O. c #3F4145", +"~O. c #141213", +"{O. c #302317", +"]O. c #392C1A", +"^O. c #1C1D22", +"/O. c #0C0E21", +"(O. c #040817", +"_O. c #030407", +":O. c #181810", +"P. c #141F2B", +",P. c #0B0F20", +"'P. c #0F101F", +")P. c #1E150C", +"!P. c #3F311F", +"~P. c #676469", +"{P. c #CCD6E0", +"]P. c #726863", +"^P. c #E3A020", +"/P. c #D99D2D", +"(P. c #635F59", +"_P. c #44484E", +":P. c #474B4F", +"Q. c #4B4958", +",Q. c #615B5D", +"'Q. c #675A50", +")Q. c #6A584B", +"!Q. c #746151", +"~Q. c #7E6955", +"{Q. c #7B6856", +"]Q. c #453F3F", +"^Q. c #1D2430", +"/Q. c #212A34", +"(Q. c #0F101A", +"_Q. c #12110F", +":Q. c #24221E", +"R. c #7E7569", +",R. c #877E73", +"'R. c #948B84", +")R. c #968B84", +"!R. c #94877F", +"~R. c #93887F", +"{R. c #887A70", +"]R. c #87796F", +"^R. c #816F68", +"/R. c #796860", +"(R. c #6F5F5A", +"_R. c #826E69", +":R. c #756963", +"S. c #544D46", +",S. c #554D48", +"'S. c #524A43", +")S. c #50473E", +"!S. c #514840", +"~S. c #50443D", +"{S. c #4F4039", +"]S. c #493A33", +"^S. c #473832", +"/S. c #473732", +"(S. c #483B35", +"_S. c #4A3F37", +":S. c #4D4239", +"T. c #000008", +",T. c #020307", +"'T. c #07070B", +")T. c #1C1C17", +"!T. c #1C1913", +"~T. c #1E1A13", +"{T. c #201C15", +"]T. c #27231C", +"^T. c #302D27", +"/T. c #363029", +"(T. c #362D26", +"_T. c #383129", +":T. c #3A332B", +"U. c #3E4047", +",U. c #141312", +"'U. c #281C0F", +")U. c #332617", +"!U. c #18110D", +"~U. c #33373C", +"{U. c #363A40", +"]U. c #34373B", +"^U. c #585354", +"/U. c #AF7930", +"(U. c #5B3D17", +"_U. c #110F0B", +":U. c #4C4D52", +"V. c #262934", +",V. c #120E10", +"'V. c #19130E", +")V. c #644017", +"!V. c #B38033", +"~V. c #5A5656", +"{V. c #50525C", +"]V. c #3F4047", +"^V. c #2B2C2D", +"/V. c #0D0C0C", +"(V. c #28292C", +"_V. c #4A4A50", +":V. c #64594D", +"W. c #76655D", +",W. c #6E5F53", +"'W. c #595153", +")W. c #4B485A", +"!W. c #5D5866", +"~W. c #686671", +"{W. c #4F4C51", +"]W. c #595454", +"^W. c #2C313E", +"/W. c #8B7665", +"(W. c #2E2E33", +"_W. c #100F0D", +":W. c #17130D", +"X. c #98867A", +",X. c #988378", +"'X. c #927D71", +")X. c #937E73", +"!X. c #79645B", +"~X. c #745E53", +"{X. c #6C564A", +"]X. c #635653", +"^X. c #777476", +"/X. c #817C7D", +"(X. c #55525B", +"_X. c #3B3B45", +":X. c #353745", +"Y. c #817469", +",Y. c #9D8F84", +"'Y. c #998B80", +")Y. c #948478", +"!Y. c #917E72", +"~Y. c #A08A7D", +"{Y. c #947E71", +"]Y. c #887167", +"^Y. c #796052", +"/Y. c #6C5145", +"(Y. c #726058", +"_Y. c #867C71", +":Y. c #6C5E53", +"Z. c #5D554A", +",Z. c #60564C", +"'Z. c #685D51", +")Z. c #665B4F", +"!Z. c #63574C", +"~Z. c #686054", +"{Z. c #675D4B", +"]Z. c #69604E", +"^Z. c #81776F", +"/Z. c #8F857C", +"(Z. c #938780", +"_Z. c #86796E", +":Z. c #847568", +"`. c #4E433A", +",`. c #524942", +"'`. c #59524B", +")`. c #534B44", +"!`. c #4C4339", +"~`. c #473F35", +"{`. c #51493F", +"]`. c #5C5549", +"^`. c #63584C", +"/`. c #5B5238", +"(`. c #5F533B", +"_`. c #7D7368", +":`. c #887D74", +"<`. c #948881", +"[`. c #877A6E", +"}`. c #817164", +"|`. c #8E7D71", +"1`. c #837265", +"2`. c #8B786D", +"3`. c #857166", +"4`. c #8C786E", +"5`. c #8A7468", +"6`. c #786154", +"7`. c #654A3B", +"8`. c #6D5142", +"9`. c #745A4C", +"0`. c #675241", +"a`. c #5B4836", +"b`. c #614E40", +"c`. c #6D5C4F", +"d`. c #7D614B", +"e`. c #7A665A", +"f`. c #5F595C", +"g`. c #35363C", +"h`. c #44454A", +"i`. c #494950", +"j`. c #3F4046", +"k`. c #45474F", +"l`. c #524734", +"m`. c #342411", +"n`. c #0A0B0E", +"o`. c #1B1E1D", +"p`. c #3F4045", +"q`. c #2A2A2C", +"r`. c #3A3A41", +"s`. c #544A42", +"t`. c #855D23", +"u`. c #604E37", +"v`. c #23282C", +"w`. c #040820", +"x`. c #07081D", +"y`. c #000307", +"z`. c #080B0C", +"A`. c #30312C", +"B`. c #2F2F2A", +"C`. c #302D29", +"D`. c #2E2A24", +"E`. c #2D2520", +"F`. c #2F2722", +"G`. c #302924", +"H`. c #342C27", +"I`. c #392F2A", +"J`. c #3E3B36", +"K`. c #403D37", +"L`. c #433E35", +"M`. c #423A31", +"N`. c #40362F", +"O`. c #372D27", +"P`. c #433A33", +"Q`. c #3B322B", +"R`. c #393029", +"S`. c #39312B", +"T`. c #352D28", +"U`. c #382F2A", +"V`. c #433A34", +"W`. c #443B34", +"X`. c #443D38", +"Y`. c #4B4540", +"Z`. c #47423C", +"``. c #46443B", +" + c #4B453B", +". + c #483F36", +"+ + c #4D443B", +"@ + c #59534C", +"# + c #4D443A", +"$ + c #483F35", +"% + c #3F392F", +"& + c #565045", +"* + c #595146", +"= + c #5C5247", +"- + c #605448", +"; + c #6B5F54", +"> + c #6A5E51", +", + c #5F533C", +"' + c #645641", +") + c #746A5A", +"! + c #766C5C", +"~ + c #6F6755", +"{ + c #81776B", +"] + c #968982", +"^ + c #8A7C70", +"/ + c #847265", +"( + c #8C796D", +"_ + c #907D71", +": + c #948075", +"< + c #8B776C", +"[ + c #89756A", +"} + c #89756B", +"| + c #968075", +"1 + c #70594C", +"2 + c #604334", +"3 + c #6A4C3D", +"4 + c #6F5345", +"5 + c #71594D", +"6 + c #6A5345", +"7 + c #7F644D", +"8 + c #82644B", +"9 + c #806855", +"0 + c #6D625F", +"a + c #555257", +"b + c #2A292F", +"c + c #333438", +"d + c #37373E", +"e + c #494951", +"f + c #454449", +"g + c #3A3B41", +"h + c #48443F", +"i + c #724F22", +"j + c #271E0C", +"k + c #3B3B44", +"l + c #191A16", +"m + c #070A0B", +"n + c #33343A", +"o + c #4F443A", +"p + c #604826", +"q + c #3B3B3D", +"r + c #33363C", +"s + c #32343C", +"t + c #242A2A", +"u + c #0B1025", +"v + c #03040A", +"w + c #04040A", +"x + c #2A2A23", +"y + c #30302B", +"z + c #2F2F29", +"A + c #322F2A", +"B + c #312B29", +"C + c #302A27", +"D + c #2D2422", +"E + c #2C221F", +"F + c #2D251F", +"G + c #2E2620", +"H + c #2F261F", +"I + c #312820", +"J + c #3E3C35", +"K + c #403E37", +"L + c #3F3D36", +"M + c #403D36", +"N + c #403832", +"O + c #3F352F", +"P + c #3C322C", +"Q + c #413934", +"R + c #413A34", +"S + c #403A34", +"T + c #403A35", +"U + c #423C38", +"V + c #423C37", +"W + c #3E3833", +"X + c #3F3834", +"Y + c #413935", +"Z + c #423B35", +"` + c #48423C", +" .+ c #4A473E", +"..+ c #47443B", +"+.+ c #494137", +"@.+ c #554E44", +"#.+ c #504B42", +"$.+ c #4C463E", +"%.+ c #3B362B", +"&.+ c #443D31", +"*.+ c #544C40", +"=.+ c #5E5247", +"-.+ c #605348", +";.+ c #5D5246", +">.+ c #786D62", +",.+ c #73675B", +"'.+ c #645944", +").+ c #655945", +"!.+ c #675C48", +"~.+ c #6B614E", +"{.+ c #74695A", +"].+ c #84786E", +"^.+ c #87786C", +"/.+ c #877769", +"(.+ c #917F72", +"_.+ c #937E71", +":.+ c #8E7769", +"<.+ c #8B7267", +"[.+ c #928176", +"}.+ c #A5988F", +"|.+ c #A2938D", +"1.+ c #70594B", +"2.+ c #5D402E", +"3.+ c #664C3B", +"4.+ c #705444", +"5.+ c #674E3F", +"6.+ c #6A4E3B", +"7.+ c #785B43", +"8.+ c #7A604A", +"9.+ c #7B5F44", +"0.+ c #636165", +"a.+ c #3E4144", +"b.+ c #745429", +"c.+ c #6C4712", +"d.+ c #3C3E41", +"e.+ c #333638", +"f.+ c #080C0D", +"g.+ c #5C4B30", +"h.+ c #4C4138", +"i.+ c #37363F", +"j.+ c #171C1D", +"k.+ c #171C2A", +"l.+ c #080A10", +"m.+ c #0B0B12", +"n.+ c #292521", +"o.+ c #2F2C26", +"p.+ c #302B27", +"q.+ c #2F2A27", +"r.+ c #2D2522", +"s.+ c #29211F", +"t.+ c #2D261F", +"u.+ c #332D25", +"v.+ c #352F25", +"w.+ c #38332A", +"x.+ c #3B372E", +"y.+ c #3C3830", +"z.+ c #3E3B33", +"A.+ c #3F3B34", +"B.+ c #3E3B34", +"C.+ c #403B35", +"D.+ c #3E3631", +"E.+ c #3A302A", +"F.+ c #403630", +"G.+ c #38312C", +"H.+ c #352E28", +"I.+ c #423A33", +"J.+ c #3F3831", +"K.+ c #453E37", +"L.+ c #463F38", +"M.+ c #463F39", +"N.+ c #433C36", +"O.+ c #464239", +"P.+ c #49453C", +"Q.+ c #4B473E", +"R.+ c #48423A", +"S.+ c #4D433B", +"T.+ c #4A4237", +"U.+ c #474239", +"V.+ c #504A42", +"W.+ c #433B33", +"X.+ c #302820", +"Y.+ c #322D22", +"Z.+ c #554C40", +"`.+ c #5B5145", +" ++ c #5E5045", +".++ c #655B4F", +"+++ c #6F6559", +"@++ c #6B6155", +"#++ c #685E52", +"$++ c #675D4D", +"%++ c #6C634F", +"&++ c #685E49", +"*++ c #746957", +"=++ c #7C7064", +"-++ c #867A6F", +";++ c #87796D", +">++ c #7E6E5F", +",++ c #877567", +"'++ c #947F72", +")++ c #887263", +"!++ c #876F63", +"~++ c #9C8C81", +"{++ c #A1948A", +"]++ c #947F73", +"^++ c #735541", +"/++ c #5F412C", +"(++ c #6C513E", +"_++ c #6B4D3C", +":++ c #624433", +"<++ c #765640", +"[++ c #755740", +"}++ c #705842", +"|++ c #816A53", +"1++ c #796C64", +"2++ c #58565A", +"3++ c #343338", +"4++ c #363B3F", +"5++ c #5D482F", +"6++ c #925E18", +"7++ c #574124", +"8++ c #373A43", +"9++ c #2D2F33", +"0++ c #07090B", +"a++ c #080B0D", +"b++ c #080A0C", +"c++ c #2A2B31", +"d++ c #2B2C32", +"e++ c #3A3A40", +"f++ c #2D3037", +"g++ c #3F3C36", +"h++ c #38373D", +"i++ c #363A41", +"j++ c #3A3C43", +"k++ c #1E222A", +"l++ c #090B21", +"m++ c #03041C", +"n++ c #02031C", +"o++ c #12151A", +"p++ c #0E1015", +"q++ c #0A0B10", +"r++ c #2B2724", +"s++ c #312C28", +"t++ c #322D29", +"u++ c #312E29", +"v++ c #2B2521", +"w++ c #29241F", +"x++ c #2A251F", +"y++ c #2B2320", +"z++ c #28211E", +"A++ c #251D1A", +"B++ c #261E1B", +"C++ c #2A2521", +"D++ c #312D25", +"E++ c #332F28", +"F++ c #343128", +"G++ c #353328", +"H++ c #36362C", +"I++ c #39372E", +"J++ c #3B362E", +"K++ c #3D3731", +"L++ c #3D3832", +"M++ c #3E3832", +"N++ c #3C3831", +"O++ c #3E3933", +"P++ c #3F3832", +"Q++ c #3C342D", +"R++ c #3F372F", +"S++ c #3B332B", +"T++ c #413931", +"U++ c #443D35", +"V++ c #453E36", +"W++ c #463F37", +"X++ c #47403A", +"Y++ c #453E38", +"Z++ c #48443B", +"`++ c #473E36", +" @+ c #463E33", +".@+ c #494239", +"+@+ c #363229", +"@@+ c #363128", +"#@+ c #2D251D", +"$@+ c #2A221A", +"%@+ c #2F2A1F", +"&@+ c #473F33", +"*@+ c #5A5144", +"=@+ c #5C5246", +"-@+ c #5D5145", +";@+ c #63564B", +">@+ c #6A6054", +",@+ c #6F6558", +"'@+ c #6D6457", +")@+ c #746B5E", +"!@+ c #756C62", +"~@+ c #6E6752", +"{@+ c #695F49", +"]@+ c #726755", +"^@+ c #7A6E61", +"/@+ c #867A6C", +"(@+ c #7D7061", +"_@+ c #857566", +":@+ c #89796A", +"<@+ c #8D7B6C", +"[@+ c #776353", +"}@+ c #A09286", +"|@+ c #827061", +"1@+ c #6E5341", +"2@+ c #654F41", +"3@+ c #5F4838", +"4@+ c #6E513C", +"5@+ c #694936", +"6@+ c #75533F", +"7@+ c #7B5741", +"8@+ c #7C5E49", +"9@+ c #78604A", +"0@+ c #7D6B5C", +"a@+ c #777070", +"b@+ c #2D2C2D", +"c@+ c #434349", +"d@+ c #3E3F45", +"e@+ c #414349", +"f@+ c #433C3A", +"g@+ c #94641B", +"h@+ c #725428", +"i@+ c #3A3C3C", +"j@+ c #393C42", +"k@+ c #242625", +"l@+ c #252726", +"m@+ c #06090B", +"n@+ c #090B0D", +"o@+ c #0E1013", +"p@+ c #1C1D21", +"q@+ c #323137", +"r@+ c #181B1E", +"s@+ c #131829", +"t@+ c #030521", +"u@+ c #05041B", +"v@+ c #03041D", +"w@+ c #000215", +"x@+ c #01020B", +"y@+ c #0A0A13", +"z@+ c #080911", +"A@+ c #020409", +"B@+ c #252221", +"C@+ c #2F2A26", +"D@+ c #2C2622", +"E@+ c #2C2721", +"F@+ c #302C27", +"G@+ c #302C28", +"H@+ c #2E2A25", +"I@+ c #2D2824", +"J@+ c #2B2622", +"K@+ c #2B2421", +"L@+ c #27201D", +"M@+ c #28221E", +"N@+ c #302A25", +"O@+ c #322B22", +"P@+ c #322F24", +"Q@+ c #333027", +"R@+ c #36332B", +"S@+ c #36352D", +"T@+ c #37362D", +"U@+ c #39372F", +"V@+ c #3C3832", +"W@+ c #3D3933", +"X@+ c #3D3934", +"Y@+ c #403C37", +"Z@+ c #413C37", +"`@+ c #413A33", +" #+ c #3E362E", +".#+ c #3F3630", +"+#+ c #3A312B", +"@#+ c #352B25", +"##+ c #3B312B", +"$#+ c #3C332D", +"%#+ c #3D342E", +"&#+ c #3D332D", +"*#+ c #433B34", +"=#+ c #484139", +"-#+ c #4A433B", +";#+ c #484039", +">#+ c #473F38", +",#+ c #4B453C", +"'#+ c #413B33", +")#+ c #49453D", +"!#+ c #2C2A22", +"~#+ c #28231B", +"{#+ c #2B261D", +"]#+ c #363227", +"^#+ c #514A3E", +"/#+ c #5F5549", +"(#+ c #655A4E", +"_#+ c #665E51", +":#+ c #675F50", +"<#+ c #6D6556", +"[#+ c #787063", +"}#+ c #7E7668", +"|#+ c #6D644F", +"1#+ c #7D7463", +"2#+ c #7E7363", +"3#+ c #7D7060", +"4#+ c #867566", +"5#+ c #837160", +"6#+ c #877363", +"7#+ c #766053", +"8#+ c #736254", +"9#+ c #8D7E70", +"0#+ c #745C4C", +"a#+ c #745F56", +"b#+ c #675042", +"c#+ c #705039", +"d#+ c #7A523B", +"e#+ c #79543D", +"f#+ c #7F5E4A", +"g#+ c #896B55", +"h#+ c #866D54", +"i#+ c #504C4F", +"j#+ c #38383E", +"k#+ c #414148", +"l#+ c #3A393E", +"m#+ c #765523", +"n#+ c #7A5624", +"o#+ c #3B3C3D", +"p#+ c #37393F", +"q#+ c #34363E", +"r#+ c #27292E", +"s#+ c #1F2020", +"t#+ c #070B0B", +"u#+ c #060A0B", +"v#+ c #070B0C", +"w#+ c #090B0E", +"x#+ c #16171B", +"y#+ c #31323A", +"z#+ c #25262B", +"A#+ c #3F3F47", +"B#+ c #25252B", +"C#+ c #1F252F", +"D#+ c #080D24", +"E#+ c #04040B", +"F#+ c #06060D", +"G#+ c #02030A", +"H#+ c #03050A", +"I#+ c #1F1B1C", +"J#+ c #312B27", +"K#+ c #34312C", +"L#+ c #35332E", +"M#+ c #34302C", +"N#+ c #2E2925", +"O#+ c #2C2824", +"P#+ c #302B23", +"Q#+ c #30281E", +"R#+ c #31291E", +"S#+ c #332F21", +"T#+ c #353125", +"U#+ c #363127", +"V#+ c #37332A", +"W#+ c #38342C", +"X#+ c #3A372E", +"Y#+ c #3B3831", +"Z#+ c #3D3A32", +"`#+ c #413D38", +" $+ c #3D342F", +".$+ c #382F29", +"+$+ c #423932", +"@$+ c #423933", +"#$+ c #413833", +"$$+ c #463E37", +"%$+ c #474038", +"&$+ c #494039", +"*$+ c #493F38", +"=$+ c #4B443A", +"-$+ c #524D43", +";$+ c #4D463E", +">$+ c #4D483F", +",$+ c #2B271E", +"'$+ c #2A261D", +")$+ c #333024", +"!$+ c #51493E", +"~$+ c #5C5145", +"{$+ c #665C50", +"]$+ c #675D51", +"^$+ c #62594C", +"/$+ c #686052", +"($+ c #686152", +"_$+ c #6C6555", +":$+ c #7B7466", +"<$+ c #7C7564", +"[$+ c #6C6651", +"}$+ c #6F6750", +"|$+ c #756D57", +"1$+ c #7A715D", +"2$+ c #776B57", +"3$+ c #847462", +"4$+ c #7E6C5A", +"5$+ c #7F6A5A", +"6$+ c #7A6356", +"7$+ c #6F594E", +"8$+ c #7E7063", +"9$+ c #908376", +"0$+ c #88786C", +"a$+ c #9E8A7D", +"b$+ c #846A59", +"c$+ c #6F523F", +"d$+ c #75533A", +"e$+ c #7D5A3E", +"f$+ c #72573D", +"g$+ c #866955", +"h$+ c #8B6B58", +"i$+ c #867060", +"j$+ c #837A77", +"k$+ c #48484F", +"l$+ c #303037", +"m$+ c #584429", +"n$+ c #745124", +"o$+ c #090D0E", +"p$+ c #121418", +"q$+ c #2E2F32", +"r$+ c #373A3E", +"s$+ c #100F15", +"t$+ c #1A1E20", +"u$+ c #1A2130", +"v$+ c #040721", +"w$+ c #12131A", +"x$+ c #0A0B0F", +"y$+ c #1B1718", +"z$+ c #35322D", +"A$+ c #373530", +"B$+ c #35342F", +"C$+ c #34322D", +"D$+ c #33322D", +"E$+ c #34322F", +"F$+ c #2D2B27", +"G$+ c #2B2823", +"H$+ c #2D2A25", +"I$+ c #332E2A", +"J$+ c #302E28", +"K$+ c #302B26", +"L$+ c #2F2A20", +"M$+ c #312A1E", +"N$+ c #342C1E", +"O$+ c #352E1D", +"P$+ c #352E1F", +"Q$+ c #362F22", +"R$+ c #373126", +"S$+ c #383329", +"T$+ c #3B362C", +"U$+ c #3B382F", +"V$+ c #3D3A31", +"W$+ c #3F3C33", +"X$+ c #413E39", +"Y$+ c #3C332E", +"Z$+ c #473E39", +"`$+ c #463E36", +" %+ c #464037", +".%+ c #4C433B", +"+%+ c #4E473D", +"@%+ c #524C42", +"#%+ c #4D473D", +"$%+ c #433E34", +"%%+ c #38342A", +"&%+ c #373328", +"*%+ c #50483C", +"=%+ c #5E5448", +"-%+ c #62584B", +";%+ c #736B5D", +">%+ c #6D6656", +",%+ c #716959", +"'%+ c #797164", +")%+ c #776F5F", +"!%+ c #726C57", +"~%+ c #716956", +"{%+ c #78705B", +"]%+ c #716950", +"^%+ c #7A6D55", +"/%+ c #7E6D57", +"(%+ c #76634E", +"_%+ c #7E6B58", +":%+ c #8A7565", +"<%+ c #866E61", +"[%+ c #836F65", +"}%+ c #9B8F83", +"|%+ c #8F8277", +"1%+ c #927D6E", +"2%+ c #67503C", +"3%+ c #71523E", +"4%+ c #78553A", +"5%+ c #785E3D", +"6%+ c #766046", +"7%+ c #896954", +"8%+ c #7B6252", +"9%+ c #867B75", +"0%+ c #4F4D4C", +"a%+ c #0E0B12", +"b%+ c #3C3C41", +"c%+ c #45454B", +"d%+ c #47474F", +"e%+ c #4A4A52", +"f%+ c #484850", +"g%+ c #4D4D55", +"h%+ c #343538", +"i%+ c #393D43", +"j%+ c #49403C", +"k%+ c #634823", +"l%+ c #3A3B3E", +"m%+ c #313236", +"n%+ c #2E2E36", +"o%+ c #38383D", +"p%+ c #44464E", +"q%+ c #35353B", +"r%+ c #18181C", +"s%+ c #22282C", +"t%+ c #0C1226", +"u%+ c #01041B", +"v%+ c #01051B", +"w%+ c #02020D", +"x%+ c #0B0B14", +"y%+ c #2B2C2B", +"z%+ c #2A282B", +"A%+ c #2C2B27", +"B%+ c #36342E", +"C%+ c #393633", +"D%+ c #373632", +"E%+ c #363531", +"F%+ c #33322C", +"G%+ c #302F29", +"H%+ c #2D2C26", +"I%+ c #2D2B26", +"J%+ c #32312B", +"K%+ c #343029", +"L%+ c #333029", +"M%+ c #33302A", +"N%+ c #323029", +"O%+ c #322F29", +"P%+ c #332E29", +"Q%+ c #322D28", +"R%+ c #322D26", +"S%+ c #322C20", +"T%+ c #332C1F", +"U%+ c #342C1C", +"V%+ c #342D1C", +"W%+ c #332C1C", +"X%+ c #362F20", +"Y%+ c #3A3125", +"Z%+ c #3C3529", +"`%+ c #3D362C", +" &+ c #3D3A30", +".&+ c #3E3C32", +"+&+ c #3F3E34", +"@&+ c #403D33", +"#&+ c #423C34", +"$&+ c #403931", +"%&+ c #403731", +"&&+ c #453C36", +"*&+ c #403831", +"=&+ c #443D36", +"-&+ c #48403A", +";&+ c #473F36", +">&+ c #463F36", +",&+ c #4C453E", +"'&+ c #50493F", +")&+ c #564F45", +"!&+ c #585449", +"~&+ c #59554A", +"{&+ c #514C42", +"]&+ c #524A40", +"^&+ c #574E43", +"/&+ c #5D5247", +"(&+ c #5E5346", +"_&+ c #5B5042", +":&+ c #675B4E", +"<&+ c #665C4C", +"[&+ c #696150", +"}&+ c #686150", +"|&+ c #736D5E", +"1&+ c #706954", +"2&+ c #7A7161", +"3&+ c #7C725D", +"4&+ c #6F6247", +"5&+ c #78654C", +"6&+ c #7A664E", +"7&+ c #836C57", +"8&+ c #8D7663", +"9&+ c #8B7565", +"0&+ c #A29387", +"a&+ c #A6998E", +"b&+ c #907E72", +"c&+ c #8C7768", +"d&+ c #745F4D", +"e&+ c #67523C", +"f&+ c #70533D", +"g&+ c #765B3D", +"h&+ c #6F5D3D", +"i&+ c #806A52", +"j&+ c #856852", +"k&+ c #806F63", +"l&+ c #747171", +"m&+ c #4B4C52", +"n&+ c #4D4F54", +"o&+ c #4A4D51", +"p&+ c #46474D", +"q&+ c #3A3E46", +"r&+ c #363641", +"s&+ c #353940", +"t&+ c #3D3A3A", +"u&+ c #513F26", +"v&+ c #37363D", +"w&+ c #36383C", +"x&+ c #0B0C10", +"y&+ c #13171A", +"z&+ c #232935", +"A&+ c #0A0E23", +"B&+ c #15172A", +"C&+ c #0C0D1F", +"D&+ c #03040E", +"E&+ c #0F0F18", +"F&+ c #343536", +"G&+ c #262728", +"H&+ c #2E2D30", +"I&+ c #393731", +"J&+ c #393834", +"K&+ c #34332E", +"L&+ c #33322E", +"M&+ c #35342E", +"N&+ c #363530", +"O&+ c #36342F", +"P&+ c #36332C", +"Q&+ c #35322B", +"R&+ c #35302A", +"S&+ c #342F29", +"T&+ c #342E25", +"U&+ c #342E22", +"V&+ c #342F21", +"W&+ c #353022", +"X&+ c #373125", +"Y&+ c #383225", +"Z&+ c #3A3326", +"`&+ c #3C362A", +" *+ c #3C382D", +".*+ c #3E3B31", +"+*+ c #3F3B31", +"@*+ c #3E3A30", +"#*+ c #3F382E", +"$*+ c #423931", +"%*+ c #473E35", +"&*+ c #443A32", +"**+ c #474137", +"=*+ c #4A443B", +"-*+ c #4E4740", +";*+ c #524B41", +">*+ c #534B41", +",*+ c #585147", +"'*+ c #5A564B", +")*+ c #5C574D", +"!*+ c #5F584E", +"~*+ c #675D53", +"{*+ c #655C52", +"]*+ c #6B6156", +"^*+ c #6A5F52", +"/*+ c #605548", +"(*+ c #615649", +"_*+ c #665B4D", +":*+ c #706758", +"<*+ c #726B5C", +"[*+ c #756E5F", +"}*+ c #746C5A", +"|*+ c #716752", +"1*+ c #6C624B", +"2*+ c #6B5D43", +"3*+ c #716041", +"4*+ c #756145", +"5*+ c #78634A", +"6*+ c #78634B", +"7*+ c #7D6551", +"8*+ c #826B59", +"9*+ c #847060", +"0*+ c #97897C", +"a*+ c #8D7F72", +"b*+ c #7B6A5C", +"c*+ c #847160", +"d*+ c #68543E", +"e*+ c #644F36", +"f*+ c #72583E", +"g*+ c #6F5E40", +"h*+ c #726045", +"i*+ c #8D735D", +"j*+ c #918680", +"k*+ c #3C3A3C", +"l*+ c #4E4E56", +"m*+ c #505157", +"n*+ c #494C50", +"o*+ c #484952", +"p*+ c #4B4541", +"q*+ c #45494B", +"r*+ c #33343B", +"s*+ c #36343B", +"t*+ c #423628", +"u*+ c #353538", +"v*+ c #35383C", +"w*+ c #2F2F36", +"x*+ c #1B1C20", +"y*+ c #28282E", +"z*+ c #0D0C12", +"A*+ c #0C0D11", +"B*+ c #2A2D2C", +"C*+ c #1D202C", +"D*+ c #353645", +"E*+ c #3B3B46", +"F*+ c #1B1D2F", +"G*+ c #01020E", +"H*+ c #141720", +"I*+ c #1C1F27", +"J*+ c #04070F", +"K*+ c #02050D", +"L*+ c #070A11", +"M*+ c #070A10", +"N*+ c #15171C", +"O*+ c #282A2C", +"P*+ c #3C3A37", +"Q*+ c #3C3B37", +"R*+ c #3B3A36", +"S*+ c #373531", +"T*+ c #383430", +"U*+ c #37342F", +"V*+ c #36322B", +"W*+ c #353229", +"X*+ c #363228", +"Y*+ c #39352B", +"Z*+ c #3A362B", +"`*+ c #3B3629", +" =+ c #3C382B", +".=+ c #3D3B2F", +"+=+ c #3F3B2F", +"@=+ c #3F3A2E", +"#=+ c #3E392E", +"$=+ c #3E3A2F", +"%=+ c #40392F", +"&=+ c #42382F", +"*=+ c #463D34", +"==+ c #473E34", +"-=+ c #433931", +";=+ c #483E35", +">=+ c #4C463D", +",=+ c #514A41", +"'=+ c #534C42", +")=+ c #544D43", +"!=+ c #5C544A", +"~=+ c #5A554A", +"{=+ c #655C53", +"]=+ c #675D54", +"^=+ c #696156", +"/=+ c #676054", +"(=+ c #6F685C", +"_=+ c #6A6156", +":=+ c #6C6459", +"<=+ c #6F665A", +"[=+ c #6F6458", +"}=+ c #73695C", +"|=+ c #786F62", +"1=+ c #6E6558", +"2=+ c #7B705F", +"3=+ c #736651", +"4=+ c #6B5C43", +"5=+ c #726147", +"6=+ c #765F46", +"7=+ c #765F47", +"8=+ c #725A44", +"9=+ c #78614F", +"0=+ c #776659", +"a=+ c #817263", +"b=+ c #847564", +"c=+ c #6D5C48", +"d=+ c #6C5841", +"e=+ c #675237", +"f=+ c #685A40", +"g=+ c #7A634D", +"h=+ c #928075", +"i=+ c #666161", +"j=+ c #47484C", +"k=+ c #4E4F53", +"l=+ c #4F5055", +"m=+ c #4E4F55", +"n=+ c #504F56", +"o=+ c #4D4A44", +"p=+ c #655536", +"q=+ c #46464A", +"r=+ c #302F38", +"s=+ c #3E372D", +"t=+ c #34363C", +"u=+ c #242626", +"v=+ c #333740", +"w=+ c #443F35", +"x=+ c #454241", +"y=+ c #313234", +"z=+ c #484B50", +"A=+ c #494D54", +"B=+ c #191A17", +"C=+ c #353B35", +"D=+ c #0A0C21", +"E=+ c #333242", +"F=+ c #4D4A53", +"G=+ c #222335", +"H=+ c #010519", +"I=+ c #25262C", +"J=+ c #111118", +"K=+ c #030510", +"L=+ c #00040B", +"M=+ c #00040A", +"N=+ c #01040A", +"O=+ c #0A0D12", +"P=+ c #1C1E21", +"Q=+ c #2A2823", +"R=+ c #39352D", +"S=+ c #3E3C37", +"T=+ c #3E3D39", +"U=+ c #3D3C38", +"V=+ c #3A3934", +"W=+ c #3A3834", +"X=+ c #3A3734", +"Y=+ c #383533", +"Z=+ c #3A3632", +"`=+ c #393531", +" -+ c #38352F", +".-+ c #37342E", +"+-+ c #37352E", +"@-+ c #3A372F", +"#-+ c #3A372D", +"$-+ c #3D392F", +"%-+ c #40392D", +"&-+ c #40372B", +"*-+ c #40382C", +"=-+ c #40352D", +"--+ c #3F332B", +";-+ c #3D332B", +">-+ c #413930", +",-+ c #453A31", +"'-+ c #3E342B", +")-+ c #484138", +"!-+ c #4B453D", +"~-+ c #544D45", +"{-+ c #575048", +"]-+ c #585148", +"^-+ c #595248", +"/-+ c #5D554C", +"(-+ c #585246", +"_-+ c #595345", +":-+ c #5F574D", +"<-+ c #62594F", +"[-+ c #685E53", +"}-+ c #6C685E", +"|-+ c #676155", +"1-+ c #685F52", +"2-+ c #6F675A", +"3-+ c #70685A", +"4-+ c #6C6353", +"5-+ c #7C705E", +"6-+ c #71634F", +"7-+ c #74624D", +"8-+ c #7C6753", +"9-+ c #775F4C", +"0-+ c #755D48", +"a-+ c #735C49", +"b-+ c #897767", +"c-+ c #8D7C6D", +"d-+ c #7A6A5B", +"e-+ c #938576", +"f-+ c #766856", +"g-+ c #695942", +"h-+ c #6B583C", +"i-+ c #695A3A", +"j-+ c #69553D", +"k-+ c #7C624F", +"l-+ c #957F6E", +"m-+ c #89807C", +"n-+ c #4A4950", +"o-+ c #4C4C54", +"p-+ c #4F4F57", +"q-+ c #505058", +"r-+ c #515158", +"s-+ c #4F4D53", +"t-+ c #524635", +"u-+ c #695129", +"v-+ c #48474B", +"w-+ c #3C3C45", +"x-+ c #35363F", +"y-+ c #383735", +"z-+ c #3B3C3B", +"A-+ c #35373F", +"B-+ c #4B3F2D", +"C-+ c #3E3C3B", +"D-+ c #424447", +"E-+ c #404346", +"F-+ c #161619", +"G-+ c #303439", +"H-+ c #060822", +"I-+ c #242437", +"J-+ c #504D57", +"K-+ c #2F2E3D", +"L-+ c #1D1F30", +"M-+ c #090B1D", +"N-+ c #11111A", +"O-+ c #212129", +"P-+ c #00040D", +"Q-+ c #03060B", +"R-+ c #111417", +"S-+ c #2A2723", +"T-+ c #3C372D", +"U-+ c #3F3E37", +"V-+ c #403F3B", +"W-+ c #3D3C37", +"X-+ c #3C3B36", +"Y-+ c #3B3934", +"Z-+ c #3A3733", +"`-+ c #3A3633", +" ;+ c #3B3833", +".;+ c #3A3732", +"+;+ c #393631", +"@;+ c #3A3830", +"#;+ c #39352C", +"$;+ c #39352A", +"%;+ c #403B34", +"&;+ c #40392E", +"*;+ c #40382B", +"=;+ c #41362D", +"-;+ c #42352D", +";;+ c #42372E", +">;+ c #484035", +",;+ c #473B31", +"';+ c #443B32", +");+ c #443D34", +"!;+ c #544D42", +"~;+ c #555047", +"{;+ c #57524B", +"];+ c #59524A", +"^;+ c #575047", +"/;+ c #5B554A", +"(;+ c #5B5449", +"_;+ c #5B5448", +":;+ c #60584D", +"<;+ c #61594E", +"[;+ c #655C50", +"};+ c #6A6155", +"|;+ c #665F50", +"1;+ c #665C4D", +"2;+ c #6B6052", +"3;+ c #726B5F", +"4;+ c #766B5C", +"5;+ c #746857", +"6;+ c #736553", +"7;+ c #776451", +"8;+ c #786350", +"9;+ c #705947", +"0;+ c #6F5947", +"a;+ c #8E8071", +"b;+ c #8E8070", +"c;+ c #8D7E6F", +"d;+ c #96897B", +"e;+ c #847767", +"f;+ c #5E4F3B", +"g;+ c #665A41", +"h;+ c #64593C", +"i;+ c #6C5E40", +"j;+ c #6F5A3F", +"k;+ c #795D46", +"l;+ c #917965", +"m;+ c #8F807E", +"n;+ c #514A54", +"o;+ c #333238", +"p;+ c #53535B", +"q;+ c #5F5349", +"r;+ c #785629", +"s;+ c #805C2A", +"t;+ c #464447", +"u;+ c #3C3C3F", +"v;+ c #4B3927", +"w;+ c #37373C", +"x;+ c #3A3F48", +"y;+ c #151412", +"z;+ c #424448", +"A;+ c #2C2F2A", +"B;+ c #292C35", +"C;+ c #080924", +"D;+ c #040621", +"E;+ c #040824", +"F;+ c #11112A", +"G;+ c #444350", +"H;+ c #413F4B", +"I;+ c #484856", +"J;+ c #2A2C3B", +"K;+ c #090C14", +"L;+ c #12141D", +"M;+ c #070915", +"N;+ c #010409", +"O;+ c #06090E", +"P;+ c #0C0F14", +"Q;+ c #111316", +"R;+ c #282520", +"S;+ c #3E392F", +"T;+ c #403F38", +"U;+ c #3E3D38", +"V;+ c #3B3832", +"W;+ c #3C3933", +"X;+ c #3C3934", +"Y;+ c #393832", +"Z;+ c #3D392E", +"`;+ c #403C34", +" >+ c #403B30", +".>+ c #403A2F", +"+>+ c #413B31", +"@>+ c #433C32", +"#>+ c #433A31", +"$>+ c #443930", +"%>+ c #43382F", +"&>+ c #463E34", +"*>+ c #473F34", +"=>+ c #453930", +"->+ c #3D3229", +";>+ c #473D33", +">>+ c #534D44", +",>+ c #56534C", +"'>+ c #58544E", +")>+ c #59534B", +"!>+ c #5C564A", +"~>+ c #585144", +"{>+ c #5A5448", +"]>+ c #5F584F", +"^>+ c #625A4F", +"/>+ c #675F54", +"(>+ c #6B665A", +"_>+ c #625D50", +":>+ c #615643", +"<>+ c #65584A", +"[>+ c #6C5F51", +"}>+ c #6E6054", +"|>+ c #786D64", +"1>+ c #75685C", +"2>+ c #685A4B", +"3>+ c #6F5F4D", +"4>+ c #715F4B", +"5>+ c #715C49", +"6>+ c #715B48", +"7>+ c #665342", +"8>+ c #7B6C5C", +"9>+ c #7D7266", +"0>+ c #908476", +"a>+ c #958878", +"b>+ c #998D7D", +"c>+ c #8D8171", +"d>+ c #695D4A", +"e>+ c #5A4B35", +"f>+ c #5F5238", +"g>+ c #6B5E42", +"h>+ c #706143", +"i>+ c #957A63", +"j>+ c #958581", +"k>+ c #615E6A", +"l>+ c #1E1921", +"m>+ c #5B5B63", +"n>+ c #5A5A62", +"o>+ c #57575F", +"p>+ c #56575F", +"q>+ c #94682F", +"r>+ c #BB7819", +"s>+ c #9E6A26", +"t>+ c #4B4A49", +"u>+ c #3E4348", +"v>+ c #3C3B42", +"w>+ c #373A3F", +"x>+ c #1C1D20", +"y>+ c #1E1D1D", +"z>+ c #222424", +"A>+ c #4C423B", +"B>+ c #634B2B", +"C>+ c #383B43", +"D>+ c #29282A", +"E>+ c #333534", +"F>+ c #1E1F32", +"G>+ c #060724", +"H>+ c #1D1E31", +"I>+ c #41434E", +"J>+ c #343741", +"K>+ c #313443", +"L>+ c #242532", +"M>+ c #2A2A36", +"N>+ c #040415", +"O>+ c #00040C", +"P>+ c #01050B", +"Q>+ c #070B0E", +"R>+ c #15181B", +"S>+ c #36302B", +"T>+ c #403A2E", +"U>+ c #403C30", +"V>+ c #423E36", +"W>+ c #3F3D38", +"X>+ c #3D3B35", +"Y>+ c #3C3931", +"Z>+ c #3C3A32", +"`>+ c #3C3B32", +" ,+ c #3A3931", +".,+ c #3A3833", +"+,+ c #3C3A35", +"@,+ c #3C3731", +"#,+ c #3D3830", +"$,+ c #3F3931", +"%,+ c #3F3A30", +"&,+ c #403D35", +"*,+ c #413E35", +"=,+ c #3F3C32", +"-,+ c #453F36", +";,+ c #443C32", +">,+ c #473E33", +",,+ c #463B30", +"',+ c #45382D", +"),+ c #42352C", +"!,+ c #3F332A", +"~,+ c #4A3D32", +"{,+ c #4A3F34", +"],+ c #4F483E", +"^,+ c #555249", +"/,+ c #59554D", +"(,+ c #5A534A", +"_,+ c #585245", +":,+ c #575142", +"<,+ c #595446", +"[,+ c #666156", +"},+ c #655E53", +"|,+ c #60594E", +"1,+ c #686155", +"2,+ c #5B5444", +"3,+ c #645846", +"4,+ c #695C4D", +"5,+ c #695B4F", +"6,+ c #76695D", +"7,+ c #7C6E63", +"8,+ c #756559", +"9,+ c #665649", +"0,+ c #5F4E3E", +"a,+ c #6B5A48", +"b,+ c #705E4A", +"c,+ c #6F5A48", +"d,+ c #786352", +"e,+ c #766353", +"f,+ c #6A5A4C", +"g,+ c #7C6E60", +"h,+ c #84786B", +"i,+ c #95897B", +"j,+ c #827464", +"k,+ c #60523B", +"l,+ c #63543A", +"m,+ c #6E5C42", +"n,+ c #79654F", +"o,+ c #7B634A", +"p,+ c #987E5E", +"q,+ c #9D8F80", +"r,+ c #5F5B6A", +"s,+ c #212227", +"t,+ c #56585E", +"u,+ c #5A5D64", +"v,+ c #5D5F68", +"w,+ c #5D5D65", +"x,+ c #5C5B61", +"y,+ c #745E47", +"z,+ c #D18728", +"A,+ c #E39118", +"B,+ c #AF7425", +"C,+ c #4C4749", +"D,+ c #474B52", +"E,+ c #414149", +"F,+ c #46464B", +"G,+ c #6D4F2F", +"H,+ c #6F5228", +"I,+ c #3D404A", +"J,+ c #3E3B3A", +"K,+ c #1F1D2E", +"L,+ c #050626", +"M,+ c #181A30", +"N,+ c #2C2E40", +"O,+ c #181B30", +"P,+ c #22212F", +"Q,+ c #504F53", +"R,+ c #595658", +"S,+ c #514E50", +"T,+ c #343238", +"U,+ c #1B1B25", +"V,+ c #070817", +"W,+ c #030411", +"X,+ c #050712", +"Y,+ c #01050C", +"Z,+ c #0B0D13", +"`,+ c #15171A", +" '+ c #2D2B2A", +".'+ c #2C2522", +"+'+ c #41392D", +"@'+ c #433C31", +"#'+ c #433D34", +"$'+ c #413E37", +"%'+ c #3C362D", +"&'+ c #3C342B", +"*'+ c #3D352C", +"='+ c #3B342B", +"-'+ c #3A362C", +";'+ c #3C392F", +">'+ c #3C382F", +",'+ c #3D3A33", +"''+ c #3C3A33", +")'+ c #403932", +"!'+ c #413A32", +"~'+ c #403E36", +"{'+ c #413D35", +"]'+ c #423D35", +"^'+ c #433C34", +"/'+ c #403930", +"('+ c #423B32", +"_'+ c #453E33", +":'+ c #433B30", +"<'+ c #474036", +"['+ c #453B31", +"}'+ c #44392D", +"|'+ c #423429", +"1'+ c #413329", +"2'+ c #3E3127", +"3'+ c #3B2D23", +"4'+ c #46382D", +"5'+ c #4A3E32", +"6'+ c #524C41", +"7'+ c #544F44", +"8'+ c #585349", +"9'+ c #5B564C", +"0'+ c #585348", +"a'+ c #554F42", +"b'+ c #565041", +"c'+ c #5B5546", +"d'+ c #605A4E", +"e'+ c #625C51", +"f'+ c #625C4F", +"g'+ c #625C4C", +"h'+ c #615C4B", +"i'+ c #655F4E", +"j'+ c #605847", +"k'+ c #655A4A", +"l'+ c #645747", +"m'+ c #77695C", +"n'+ c #7B6C61", +"o'+ c #716154", +"p'+ c #675648", +"q'+ c #665344", +"r'+ c #6C5949", +"s'+ c #73604F", +"t'+ c #725F4B", +"u'+ c #6E5847", +"v'+ c #746150", +"w'+ c #6D5D4E", +"x'+ c #6D6052", +"y'+ c #84786A", +"z'+ c #95887A", +"A'+ c #8C7F70", +"B'+ c #716454", +"C'+ c #5A4B39", +"D'+ c #5C4B39", +"E'+ c #67553E", +"F'+ c #6B5943", +"G'+ c #76634D", +"H'+ c #80684F", +"I'+ c #8F7857", +"J'+ c #A38F78", +"K'+ c #6E6572", +"L'+ c #575B60", +"M'+ c #5E616A", +"N'+ c #5B5C63", +"O'+ c #8B6227", +"P'+ c #EC981E", +"Q'+ c #E89918", +"R'+ c #BC7F27", +"S'+ c #4E494A", +"T'+ c #515359", +"U'+ c #504A3E", +"V'+ c #9D651F", +"W'+ c #685133", +"X'+ c #41444E", +"Y'+ c #121010", +"Z'+ c #524745", +"`'+ c #1D182E", +" )+ c #050525", +".)+ c #0B0D2A", +"+)+ c #26283D", +"@)+ c #0F1127", +"#)+ c #03041B", +"$)+ c #010518", +"%)+ c #040519", +"&)+ c #46454B", +"*)+ c #686360", +"=)+ c #65615E", +"-)+ c #5B5859", +";)+ c #3D3C40", +">)+ c #151520", +",)+ c #14141F", +"')+ c #0A0B17", +"))+ c #060712", +"!)+ c #181822", +"~)+ c #14141B", +"{)+ c #05070D", +"])+ c #07090F", +"^)+ c #302F2E", +"/)+ c #37332B", +"()+ c #372F24", +"_)+ c #40372A", +":)+ c #433D35", +"<)+ c #3D3128", +"[)+ c #3B2F26", +"})+ c #393127", +"|)+ c #3C3329", +"1)+ c #3D352B", +"2)+ c #3D352D", +"3)+ c #3D352E", +"4)+ c #3E3930", +"5)+ c #3E3A32", +"6)+ c #3F3A32", +"7)+ c #403B32", +"8)+ c #403B33", +"9)+ c #413A31", +"0)+ c #413A30", +"a)+ c #403A32", +"b)+ c #413B30", +"c)+ c #443C31", +"d)+ c #453D32", +"e)+ c #474035", +"f)+ c #443B2E", +"g)+ c #473D31", +"h)+ c #43382C", +"i)+ c #43362A", +"j)+ c #46372C", +"k)+ c #433427", +"l)+ c #4A3C30", +"m)+ c #4B4135", +"n)+ c #4D4638", +"o)+ c #524B3E", +"p)+ c #544B41", +"q)+ c #544E43", +"r)+ c #544F43", +"s)+ c #554F43", +"t)+ c #575045", +"u)+ c #565042", +"v)+ c #565040", +"w)+ c #595346", +"x)+ c #5F584D", +"y)+ c #615A4F", +"z)+ c #635C50", +"A)+ c #666051", +"B)+ c #635545", +"C)+ c #6C5E50", +"D)+ c #7A6C5F", +"E)+ c #726458", +"F)+ c #685749", +"G)+ c #604D3D", +"H)+ c #6C5746", +"I)+ c #725C4D", +"J)+ c #715C4A", +"K)+ c #65503E", +"L)+ c #5C4735", +"M)+ c #5C4B3A", +"N)+ c #625544", +"O)+ c #7E7464", +"P)+ c #918779", +"Q)+ c #8F8576", +"R)+ c #6E6150", +"S)+ c #5D4E3C", +"T)+ c #5C4B37", +"U)+ c #5F4B39", +"V)+ c #6F5B45", +"W)+ c #7B6655", +"X)+ c #9A8668", +"Y)+ c #7A7475", +"Z)+ c #282635", +"`)+ c #434549", +" !+ c #656870", +".!+ c #5B5B62", +"+!+ c #472B11", +"@!+ c #C2811C", +"#!+ c #D98D1E", +"$!+ c #CE8A29", +"%!+ c #585555", +"&!+ c #50535C", +"*!+ c #303134", +"=!+ c #40414A", +"-!+ c #74532F", +";!+ c #BC771D", +">!+ c #635039", +",!+ c #4C4F59", +"'!+ c #484545", +")!+ c #2C2016", +"!!+ c #10110C", +"~!+ c #110E0F", +"{!+ c #262526", +"]!+ c #574C4A", +"^!+ c #1A182E", +"/!+ c #040527", +"(!+ c #050723", +"_!+ c #17192E", +":!+ c #1A1D2E", +"~+ c #544535", +",~+ c #786C5D", +"'~+ c #92877B", +")~+ c #685E4E", +"!~+ c #574937", +"~~+ c #5D4C38", +"{~+ c #624F39", +"]~+ c #66503A", +"^~+ c #755946", +"/~+ c #8A6F5F", +"(~+ c #7D6652", +"_~+ c #8F7253", +":~+ c #847372", +"<~+ c #2C2D3B", +"[~+ c #4C4C52", +"}~+ c #624215", +"|~+ c #C4811F", +"1~+ c #CE8C2C", +"2~+ c #625F60", +"3~+ c #4F535C", +"4~+ c #383A3E", +"5~+ c #2D2F36", +"6~+ c #44464B", +"7~+ c #A17123", +"8~+ c #C47F1B", +"9~+ c #675539", +"0~+ c #4E5259", +"a~+ c #4F535E", +"b~+ c #5B544F", +"c~+ c #9E6C28", +"d~+ c #4F3511", +"e~+ c #16100C", +"f~+ c #0D0C0B", +"g~+ c #0F0F0C", +"h~+ c #51453B", +"i~+ c #624F47", +"j~+ c #1D1327", +"k~+ c #040625", +"l~+ c #0E0F29", +"m~+ c #2D2E42", +"n~+ c #1E1F33", +"o~+ c #15162A", +"p~+ c #1B1B32", +"q~+ c #42444E", +"r~+ c #3B3B47", +"s~+ c #38373E", +"t~+ c #5B595B", +"u~+ c #605E5F", +"v~+ c #535255", +"w~+ c #3A383E", +"x~+ c #36353D", +"y~+ c #2F2E39", +"z~+ c #1E1D2A", +"A~+ c #2C2933", +"B~+ c #454448", +"C~+ c #1C1C28", +"D~+ c #25252E", +"E~+ c #2A2A31", +"F~+ c #28282F", +"G~+ c #201F26", +"H~+ c #353534", +"I~+ c #2C2C2A", +"J~+ c #24201A", +"K~+ c #352D22", +"L~+ c #3E362B", +"M~+ c #484137", +"N~+ c #362A21", +"O~+ c #382C24", +"P~+ c #3A3027", +"Q~+ c #3D2F25", +"R~+ c #3D3027", +"S~+ c #3D3028", +"T~+ c #3A3028", +"U~+ c #3E382E", +"V~+ c #3C352B", +"W~+ c #3D342B", +"X~+ c #3D352A", +"Y~+ c #3E362A", +"Z~+ c #3F382D", +"`~+ c #3F392E", +" {+ c #443B31", +".{+ c #41392E", +"+{+ c #3F372C", +"@{+ c #3F352B", +"#{+ c #40362C", +"${+ c #44382C", +"%{+ c #43372A", +"&{+ c #44392B", +"*{+ c #473D2E", +"={+ c #463C2D", +"-{+ c #463B2E", +";{+ c #4B3F33", +">{+ c #504438", +",{+ c #4D4337", +"'{+ c #4F473A", +"){+ c #4F463A", +"!{+ c #585045", +"~{+ c #554D44", +"{{+ c #534B42", +"]{+ c #564E41", +"^{+ c #635D51", +"/{+ c #6A665D", +"({+ c #696256", +"_{+ c #6A6358", +":{+ c #6F685F", +"<{+ c #716963", +"[{+ c #736A62", +"}{+ c #726860", +"|{+ c #736A60", +"1{+ c #706459", +"2{+ c #6F6256", +"3{+ c #786A60", +"4{+ c #75665A", +"5{+ c #5C4B3B", +"6{+ c #413020", +"7{+ c #403221", +"8{+ c #524232", +"9{+ c #67584A", +"0{+ c #897D6F", +"a{+ c #9A8D81", +"b{+ c #736556", +"c{+ c #554634", +"d{+ c #584430", +"e{+ c #654E39", +"f{+ c #6D543E", +"g{+ c #6E523B", +"h{+ c #74543F", +"i{+ c #836450", +"j{+ c #917058", +"k{+ c #887164", +"l{+ c #3E3A44", +"m{+ c #101217", +"n{+ c #11100F", +"o{+ c #20170D", +"p{+ c #93641F", +"q{+ c #BD8233", +"r{+ c #605C5E", +"s{+ c #4B5155", +"t{+ c #4E5057", +"u{+ c #292C30", +"v{+ c #37383C", +"w{+ c #323336", +"x{+ c #C38320", +"y{+ c #BA7A22", +"z{+ c #554B45", +"A{+ c #4A5157", +"B{+ c #585553", +"C{+ c #D18C29", +"D{+ c #A46A1B", +"E{+ c #68461E", +"F{+ c #372311", +"G{+ c #201916", +"H{+ c #6D5645", +"I{+ c #70534A", +"J{+ c #1E172A", +"K{+ c #15172F", +"L{+ c #2F2D44", +"M{+ c #4C4B5D", +"N{+ c #40404E", +"O{+ c #292A3D", +"P{+ c #3D3D49", +"Q{+ c #44424C", +"R{+ c #3F3E48", +"S{+ c #282732", +"T{+ c #101121", +"U{+ c #101020", +"V{+ c #444448", +"W{+ c #525150", +"X{+ c #3E3D40", +"Y{+ c #494847", +"Z{+ c #403F3F", +"`{+ c #2A292C", +" ]+ c #333334", +".]+ c #181813", +"+]+ c #2B281B", +"@]+ c #362E23", +"#]+ c #423F34", +"$]+ c #403A33", +"%]+ c #3C352C", +"&]+ c #3B332E", +"*]+ c #3D3530", +"=]+ c #3B342E", +"-]+ c #39312A", +";]+ c #392D24", +">]+ c #3A2D24", +",]+ c #3B2E25", +"']+ c #3B3228", +")]+ c #3F362D", +"!]+ c #3E352B", +"~]+ c #3F3529", +"{]+ c #41382F", +"]]+ c #433A30", +"^]+ c #483E34", +"/]+ c #443A30", +"(]+ c #463A30", +"_]+ c #43372C", +":]+ c #473A2D", +"<]+ c #473A2C", +"[]+ c #493C2E", +"}]+ c #4E4134", +"|]+ c #514539", +"1]+ c #4E4438", +"2]+ c #51483B", +"3]+ c #51493A", +"4]+ c #544D3E", +"5]+ c #575040", +"6]+ c #564E40", +"7]+ c #58554D", +"8]+ c #636059", +"9]+ c #6B675F", +"0]+ c #6C655C", +"a]+ c #5F544A", +"b]+ c #594E43", +"c]+ c #64584F", +"d]+ c #6E625B", +"e]+ c #716961", +"f]+ c #746B64", +"g]+ c #766D66", +"h]+ c #746C63", +"i]+ c #746A61", +"j]+ c #7E746A", +"k]+ c #776A60", +"l]+ c #6B5C51", +"m]+ c #58483A", +"n]+ c #4B3D2E", +"o]+ c #5A4E3E", +"p]+ c #6D5E4E", +"q]+ c #726455", +"r]+ c #8A7E70", +"s]+ c #817464", +"t]+ c #574432", +"u]+ c #5A412D", +"v]+ c #664833", +"w]+ c #6F4F39", +"x]+ c #77553D", +"y]+ c #7B593F", +"z]+ c #775A3F", +"A]+ c #907059", +"B]+ c #987664", +"C]+ c #131619", +"D]+ c #13110E", +"E]+ c #3B2C11", +"F]+ c #A57834", +"G]+ c #5D5758", +"H]+ c #51565F", +"I]+ c #52555D", +"J]+ c #2E3135", +"K]+ c #323538", +"L]+ c #3C4149", +"M]+ c #40444C", +"N]+ c #454B4F", +"O]+ c #775E37", +"P]+ c #C7811A", +"Q]+ c #B07824", +"R]+ c #4B4747", +"S]+ c #52555C", +"T]+ c #50555B", +"U]+ c #5C5C5D", +"V]+ c #D3902D", +"W]+ c #EB9A1E", +"X]+ c #D58D28", +"Y]+ c #956624", +"Z]+ c #2E2616", +"`]+ c #0E0F13", +" ^+ c #866857", +".^+ c #785952", +"+^+ c #1B132B", +"@^+ c #0C0C29", +"#^+ c #13122C", +"$^+ c #24243A", +"%^+ c #3B3C4D", +"&^+ c #383847", +"*^+ c #272838", +"=^+ c #373443", +"-^+ c #3D3A47", +";^+ c #423E4A", +">^+ c #131225", +",^+ c #121226", +"'^+ c #0A0B1B", +")^+ c #11111F", +"!^+ c #434245", +"~^+ c #5B5955", +"{^+ c #5A5754", +"]^+ c #4F4D4B", +"^^+ c #4A4745", +"/^+ c #504E4B", +"(^+ c #504F4C", +"_^+ c #4C4B4A", +":^+ c #383838", +"<^+ c #151511", +"[^+ c #212017", +"}^+ c #2E2A1E", +"|^+ c #2F2B1F", +"1^+ c #333025", +"2^+ c #3C3930", +"3^+ c #38332C", +"4^+ c #37322C", +"5^+ c #38332E", +"6^+ c #3B3630", +"7^+ c #3B352F", +"8^+ c #312720", +"9^+ c #30251C", +"0^+ c #34281F", +"a^+ c #382B22", +"b^+ c #3B3026", +"c^+ c #3C3126", +"d^+ c #3E3328", +"e^+ c #40352A", +"f^+ c #41382E", +"g^+ c #43392F", +"h^+ c #42392F", +"i^+ c #473E32", +"j^+ c #494035", +"k^+ c #4F4239", +"l^+ c #4B3E34", +"m^+ c #4E4137", +"n^+ c #524439", +"o^+ c #4F4136", +"p^+ c #504235", +"q^+ c #51463A", +"r^+ c #50453A", +"s^+ c #53493C", +"t^+ c #544B3D", +"u^+ c #544B3E", +"v^+ c #554B3E", +"w^+ c #574C3D", +"x^+ c #584F3F", +"y^+ c #584E3F", +"z^+ c #595043", +"A^+ c #5D5A55", +"B^+ c #65645F", +"C^+ c #6A6762", +"D^+ c #6B665F", +"E^+ c #675C53", +"F^+ c #63584E", +"G^+ c #584C42", +"H^+ c #6A5F56", +"I^+ c #6F685E", +"J^+ c #746D63", +"K^+ c #766F66", +"L^+ c #787067", +"M^+ c #756A5F", +"N^+ c #786C62", +"O^+ c #6D6055", +"P^+ c #64564B", +"Q^+ c #5E5043", +"R^+ c #65584B", +"S^+ c #726558", +"T^+ c #514535", +"U^+ c #4D3E2F", +"V^+ c #736253", +"W^+ c #5D4C3B", +"X^+ c #5F4733", +"Y^+ c #664532", +"Z^+ c #6E4C36", +"`^+ c #7B543A", +" /+ c #815D43", +"./+ c #815E44", +"+/+ c #85694C", +"@/+ c #95735D", +"#/+ c #64504A", +"$/+ c #1E1B22", +"%/+ c #3C3C3E", +"&/+ c #161612", +"*/+ c #141411", +"=/+ c #13120B", +"-/+ c #574326", +";/+ c #5B5658", +">/+ c #53565D", +",/+ c #33363A", +"'/+ c #2D3033", +")/+ c #40454C", +"!/+ c #444850", +"~/+ c #484A52", +"{/+ c #454B50", +"]/+ c #573E1C", +"^/+ c #A76E18", +"//+ c #9F6C27", +"(/+ c #494647", +"_/+ c #57585B", +":/+ c #BE8B37", +"(+ c #685D4E", +",(+ c #403523", +"'(+ c #473624", +")(+ c #533D2A", +"!(+ c #5C462F", +"~(+ c #664C35", +"{(+ c #724E37", +"](+ c #7E5238", +"^(+ c #745640", +"/(+ c #735A47", +"((+ c #845F48", +"_(+ c #8D674D", +":(+ c #74574B", +"<(+ c #282224", +"[(+ c #3D312C", +"}(+ c #3E3B40", +"|(+ c #1F1C1B", +"1(+ c #585660", +"2(+ c #50545C", +"3(+ c #3F4146", +"4(+ c #36393E", +"5(+ c #303337", +"6(+ c #202126", +"7(+ c #34373A", +"8(+ c #4C4F55", +"9(+ c #4B4D55", +"0(+ c #484B51", +"a(+ c #18150B", +"b(+ c #7D541D", +"c(+ c #825F2F", +"d(+ c #47464C", +"e(+ c #5E6168", +"f(+ c #B28B48", +"g(+ c #FDAF1F", +"h(+ c #EC9F24", +"i(+ c #624E31", +"j(+ c #8492A3", +"k(+ c #777E96", +"l(+ c #4B3E31", +"m(+ c #4E3D2A", +"n(+ c #2A1E11", +"o(+ c #161210", +"p(+ c #43382E", +"q(+ c #9C7B67", +"r(+ c #674F4F", +"s(+ c #0F0E27", +"t(+ c #050526", +"u(+ c #080A27", +"v(+ c #0E0E28", +"w(+ c #16162C", +"x(+ c #2C2C3D", +"y(+ c #323141", +"z(+ c #212132", +"A(+ c #121326", +"B(+ c #06091B", +"C(+ c #06091A", +"D(+ c #161824", +"E(+ c #141521", +"F(+ c #151422", +"G(+ c #23232F", +"H(+ c #2E2E35", +"I(+ c #22222A", +"J(+ c #494846", +"K(+ c #474644", +"L(+ c #4C4B48", +"M(+ c #3D3B37", +"N(+ c #33322B", +"O(+ c #2F2D26", +"P(+ c #38332D", +"Q(+ c #2C2821", +"R(+ c #322C26", +"S(+ c #332A24", +"T(+ c #2F261E", +"U(+ c #342920", +"V(+ c #362921", +"W(+ c #372920", +"X(+ c #3A2C23", +"Y(+ c #403229", +"Z(+ c #3E3225", +"`(+ c #433328", +" _+ c #433329", +"._+ c #433429", +"+_+ c #44342A", +"@_+ c #47392D", +"#_+ c #4B3E32", +"$_+ c #4B3D31", +"%_+ c #4B3F31", +"&_+ c #4A3E30", +"*_+ c #514436", +"=_+ c #514337", +"-_+ c #514435", +";_+ c #554838", +">_+ c #564939", +",_+ c #554837", +"'_+ c #574B39", +")_+ c #574B3A", +"!_+ c #56493A", +"~_+ c #5A4E41", +"{_+ c #584C40", +"]_+ c #5C5043", +"^_+ c #5B5045", +"/_+ c #5D534A", +"(_+ c #5F574F", +"__+ c #625C55", +":_+ c #67615B", +"<_+ c #635C54", +"[_+ c #554B42", +"}_+ c #696159", +"|_+ c #6A645C", +"1_+ c #6A645B", +"2_+ c #746D65", +"3_+ c #766F67", +"4_+ c #706961", +"5_+ c #776E65", +"6_+ c #6D6358", +"7_+ c #413626", +"8_+ c #372D1C", +"9_+ c #382F1E", +"0_+ c #3A3422", +"a_+ c #47422F", +"b_+ c #413B28", +"c_+ c #453723", +"d_+ c #4F3A25", +"e_+ c #573C26", +"f_+ c #674D34", +"g_+ c #68503D", +"h_+ c #77513B", +"i_+ c #7B5543", +"j_+ c #5A4B47", +"k_+ c #71584B", +"l_+ c #8A644E", +"m_+ c #8B6757", +"n_+ c #403430", +"o_+ c #2E251F", +"p_+ c #905F29", +"q_+ c #503414", +"r_+ c #1B1917", +"s_+ c #5B5B67", +"t_+ c #555964", +"u_+ c #54585F", +"v_+ c #3F4147", +"w_+ c #323639", +"x_+ c #0C0D0A", +"y_+ c #484A50", +"z_+ c #090D0D", +"A_+ c #452B12", +"B_+ c #725738", +"C_+ c #43454B", +"D_+ c #5F6269", +"E_+ c #AB8946", +"F_+ c #FBAE1E", +"G_+ c #ECA11E", +"H_+ c #66513A", +"I_+ c #C4CBD3", +"J_+ c #CAD0D9", +"K_+ c #5F5852", +"L_+ c #62462C", +"M_+ c #594326", +"N_+ c #40311C", +"O_+ c #373330", +"P_+ c #473C35", +"Q_+ c #8F7364", +"R_+ c #513F46", +"S_+ c #0A0A29", +"T_+ c #040626", +"U_+ c #020622", +"V_+ c #020621", +"W_+ c #02051E", +"X_+ c #03061D", +"Y_+ c #090A20", +"Z_+ c #0A0B21", +"`_+ c #07081F", +" :+ c #02061A", +".:+ c #020619", +"+:+ c #030417", +"@:+ c #141524", +"#:+ c #262832", +"$:+ c #2A2A34", +"%:+ c #22222C", +"&:+ c #202028", +"*:+ c #37383A", +"=:+ c #474846", +"-:+ c #444544", +";:+ c #474647", +">:+ c #424140", +",:+ c #44433D", +"':+ c #45443F", +"):+ c #36332D", +"!:+ c #38342E", +"~:+ c #39362F", +"{:+ c #2A261F", +"]:+ c #221C14", +"^:+ c #271E17", +"/:+ c #292017", +"(:+ c #33261E", +"_:+ c #36281E", +"::+ c #3A2D23", +"<:+ c #392D22", +"[:+ c #372C1F", +"}:+ c #3A2D20", +"|:+ c #37281E", +"1:+ c #3B2B21", +"2:+ c #3F2F24", +"3:+ c #433426", +"4:+ c #453628", +"5:+ c #47382A", +"6:+ c #4B3C2F", +"7:+ c #4C3E31", +"8:+ c #4B3F2F", +"9:+ c #4D4030", +"0:+ c #4C3F2F", +"a:+ c #4E3F31", +"b:+ c #504234", +"c:+ c #4D3E30", +"d:+ c #514333", +"e:+ c #4F4131", +"f:+ c #554736", +"g:+ c #564737", +"h:+ c #564735", +"i:+ c #574936", +"j:+ c #564936", +"k:+ c #5E5146", +"l:+ c #5D544B", +"m:+ c #655E57", +"n:+ c #655F57", +"o:+ c #675F57", +"p:+ c #6A6158", +"q:+ c #6B645D", +"r:+ c #706A64", +"s:+ c #757068", +"t:+ c #777269", +"u:+ c #787269", +"v:+ c #756D65", +"w:+ c #716861", +"x:+ c #786F67", +"y:+ c #70665D", +"z:+ c #574F43", +"A:+ c #3D3627", +"B:+ c #312C19", +"C:+ c #332E1B", +"D:+ c #37321F", +"E:+ c #36341F", +"F:+ c #393521", +"G:+ c #423A27", +"H:+ c #523F28", +"I:+ c #563E25", +"J:+ c #664A30", +"K:+ c #674E37", +"L:+ c #694E44", +"M:+ c #6E4E43", +"N:+ c #473A40", +"O:+ c #695756", +"P:+ c #8C6A57", +"Q:+ c #8F715D", +"R:+ c #51423D", +"S:+ c #151112", +"T:+ c #695036", +"U:+ c #DA8F27", +"V:+ c #B2761D", +"W:+ c #261E11", +"X:+ c #171714", +"Y:+ c #5F616B", +"Z:+ c #565A61", +"`:+ c #52565C", +" <+ c #4D4F55", +".<+ c #494B54", +"+<+ c #19110C", +"@<+ c #40444A", +"#<+ c #4E5157", +"$<+ c #91784D", +"%<+ c #F4AF24", +"&<+ c #EDA821", +"*<+ c #6A5841", +"=<+ c #D0D3DF", +"-<+ c #EDF0F3", +";<+ c #828086", +"><+ c #835E2D", +",<+ c #75542C", +"'<+ c #755025", +")<+ c #664A25", +"!<+ c #423622", +"~<+ c #433933", +"{<+ c #474045", +"]<+ c #0C0E2B", +"^<+ c #030626", +"/<+ c #030623", +"(<+ c #030622", +"_<+ c #030720", +":<+ c #20202B", +"<<+ c #2A2B32", +"[<+ c #27272E", +"}<+ c #1A1C25", +"|<+ c #21232B", +"1<+ c #373839", +"2<+ c #444542", +"3<+ c #494A46", +"4<+ c #4D4C48", +"5<+ c #4D4B46", +"6<+ c #474641", +"7<+ c #464540", +"8<+ c #45443E", +"9<+ c #434039", +"0<+ c #322F27", +"a<+ c #241A11", +"b<+ c #261B12", +"c<+ c #291C14", +"d<+ c #302218", +"e<+ c #362A1F", +"f<+ c #362C20", +"g<+ c #352B20", +"h<+ c #33291D", +"i<+ c #2E2419", +"j<+ c #2D2117", +"k<+ c #33251A", +"l<+ c #392A1D", +"m<+ c #3F3022", +"n<+ c #413224", +"o<+ c #48382B", +"p<+ c #4C3D2F", +"q<+ c #493D2F", +"r<+ c #493C2D", +"s<+ c #4A3D2D", +"t<+ c #483B2B", +"u<+ c #48392A", +"v<+ c #4A3B2C", +"w<+ c #463728", +"x<+ c #4C3C2D", +"y<+ c #4D3E2E", +"z<+ c #4E3F2F", +"A<+ c #4E402F", +"B<+ c #554635", +"C<+ c #524234", +"D<+ c #564636", +"E<+ c #554633", +"F<+ c #534432", +"G<+ c #534434", +"H<+ c #514237", +"I<+ c #594C43", +"J<+ c #625951", +"K<+ c #67625A", +"L<+ c #68635D", +"M<+ c #69645E", +"N<+ c #6A645D", +"O<+ c #6D665D", +"P<+ c #6A625A", +"Q<+ c #6D6660", +"R<+ c #766F69", +"S<+ c #79726C", +"T<+ c #777068", +"U<+ c #6A6359", +"V<+ c #6D645D", +"W<+ c #797169", +"X<+ c #393626", +"Y<+ c #2B2C1A", +"Z<+ c #2D2F1A", +"`<+ c #30321D", +" [+ c #333520", +".[+ c #3D3924", +"+[+ c #4F4532", +"@[+ c #695746", +"#[+ c #644833", +"$[+ c #63472F", +"%[+ c #695034", +"&[+ c #6A4E3A", +"*[+ c #6A4C40", +"=[+ c #3E333D", +"-[+ c #5E4D51", +";[+ c #886C58", +">[+ c #846B57", +",[+ c #4D403A", +"'[+ c #110E10", +")[+ c #B67C2D", +"![+ c #EF9E25", +"~[+ c #DB9225", +"{[+ c #624520", +"][+ c #231E1F", +"^[+ c #191617", +"/[+ c #5D5F6A", +"([+ c #5D6069", +"_[+ c #4B4E54", +":[+ c #2A2A2F", +"<[+ c #0D0C09", +"[[+ c #36373A", +"}[+ c #42444A", +"|[+ c #4A4C51", +"1[+ c #100D0D", +"2[+ c #54575E", +"3[+ c #55585F", +"4[+ c #595C63", +"5[+ c #53565C", +"6[+ c #555862", +"7[+ c #776752", +"8[+ c #F0B428", +"9[+ c #E9A91F", +"0[+ c #6F5B41", +"a[+ c #CED2DF", +"b[+ c #F7FAF8", +"c[+ c #97A3B5", +"d[+ c #6D5131", +"e[+ c #7D5727", +"f[+ c #7A5426", +"g[+ c #775422", +"h[+ c #634E29", +"i[+ c #272219", +"j[+ c #0D0D11", +"k[+ c #786B5D", +"l[+ c #474047", +"m[+ c #030624", +"n[+ c #030723", +"o[+ c #040720", +"p[+ c #050715", +"q[+ c #0C0F1B", +"r[+ c #11151E", +"s[+ c #22252B", +"t[+ c #27282E", +"u[+ c #464744", +"v[+ c #4E4D48", +"w[+ c #504F4A", +"x[+ c #4F4E49", +"y[+ c #4E4B44", +"z[+ c #4B4841", +"A[+ c #45423B", +"B[+ c #2F2920", +"C[+ c #231B12", +"D[+ c #22170E", +"E[+ c #281B13", +"F[+ c #2D1E16", +"G[+ c #31241B", +"H[+ c #362C22", +"I[+ c #31291F", +"J[+ c #2B2218", +"K[+ c #2A2016", +"L[+ c #2D2216", +"M[+ c #332719", +"N[+ c #36291C", +"O[+ c #36291D", +"P[+ c #403124", +"Q[+ c #453A2A", +"R[+ c #403625", +"S[+ c #423525", +"T[+ c #413525", +"U[+ c #463A2A", +"V[+ c #4B3E2E", +"W[+ c #463A2B", +"X[+ c #463A2D", +"Y[+ c #4A3C2F", +"Z[+ c #493A2D", +"`[+ c #493A2F", +" }+ c #4D3F35", +".}+ c #554A41", +"+}+ c #625C53", +"@}+ c #68645C", +"#}+ c #6A655F", +"$}+ c #6B6660", +"%}+ c #6C6760", +"&}+ c #6D6960", +"*}+ c #6C685F", +"=}+ c #6C675F", +"-}+ c #756E68", +";}+ c #7B746E", +">}+ c #79726B", +",}+ c #3E382C", +"'}+ c #4F493C", +")}+ c #686156", +"!}+ c #726C61", +"~}+ c #6F695E", +"{}+ c #5C584C", +"]}+ c #3A3B2A", +"^}+ c #2B311D", +"/}+ c #2D311B", +"(}+ c #323520", +"_}+ c #363823", +":}+ c #534A34", +"<}+ c #72624F", +"[}+ c #765F4E", +"}}+ c #7A5F4A", +"|}+ c #795E47", +"1}+ c #6B4E35", +"2}+ c #735138", +"3}+ c #5F4643", +"4}+ c #625154", +"5}+ c #8A6952", +"6}+ c #7A5F47", +"7}+ c #362B23", +"8}+ c #0B0A09", +"9}+ c #211E1F", +"0}+ c #7E5E35", +"a}+ c #E59B28", +"b}+ c #FAAF24", +"c}+ c #E9A324", +"d}+ c #625140", +"e}+ c #646E88", +"f}+ c #3C404F", +"g}+ c #191718", +"h}+ c #5B5E69", +"i}+ c #4A4C55", +"j}+ c #2B2B2E", +"k}+ c #212528", +"l}+ c #110F0E", +"m}+ c #242627", +"n}+ c #5B5E65", +"o}+ c #5B5E64", +"p}+ c #5A5D65", +"q}+ c #5C5E67", +"r}+ c #83755F", +"s}+ c #F2B726", +"t}+ c #EEB31C", +"u}+ c #6E5C3D", +"v}+ c #CAD1DD", +"w}+ c #F8FAF8", +"x}+ c #A9B5CD", +"y}+ c #7F613E", +"z}+ c #A46B24", +"A}+ c #6E4720", +"B}+ c #644420", +"C}+ c #5E4B2F", +"D}+ c #26221B", +"E}+ c #0E0B11", +"F}+ c #75675C", +"G}+ c #3D353E", +"H}+ c #060927", +"I}+ c #020727", +"J}+ c #040722", +"K}+ c #010517", +"L}+ c #060A15", +"M}+ c #0F141C", +"N}+ c #11171E", +"O}+ c #101320", +"P}+ c #10141C", +"Q}+ c #3A3C36", +"R}+ c #474743", +"S}+ c #4F4D47", +"T}+ c #4F4C45", +"U}+ c #39332B", +"V}+ c #2A2319", +"W}+ c #21190F", +"X}+ c #251C12", +"Y}+ c #2B1F16", +"Z}+ c #2B1D16", +"`}+ c #2D2118", +" |+ c #352B21", +".|+ c #362E24", +"+|+ c #322B20", +"@|+ c #2A2217", +"#|+ c #261E13", +"$|+ c #342A1D", +"%|+ c #32271C", +"&|+ c #34281D", +"*|+ c #3A2D22", +"=|+ c #38291D", +"-|+ c #382B1D", +";|+ c #413526", +">|+ c #433A29", +",|+ c #3E3624", +"'|+ c #39301F", +")|+ c #382E1E", +"!|+ c #3A2F20", +"~|+ c #3F3424", +"{|+ c #463C2B", +"]|+ c #473D2C", +"^|+ c #433729", +"/|+ c #3C3123", +"(|+ c #3B3023", +"_|+ c #342B1C", +":|+ c #31291B", +"<|+ c #322B1F", +"[|+ c #4F463C", +"}|+ c #5C534B", +"||+ c #625B52", +"1|+ c #6A665E", +"2|+ c #6B6761", +"3|+ c #6D6862", +"4|+ c #6E6963", +"5|+ c #6E6962", +"6|+ c #6F6B63", +"7|+ c #6F6961", +"8|+ c #6E685F", +"9|+ c #5C564D", +"0|+ c #3F392D", +"a|+ c #464234", +"b|+ c #494839", +"c|+ c #5A584A", +"d|+ c #595748", +"e|+ c #5D6051", +"f|+ c #494E3D", +"g|+ c #2F3522", +"h|+ c #363523", +"i|+ c #393623", +"j|+ c #433D2A", +"k|+ c #544431", +"l|+ c #5D4936", +"m|+ c #745C49", +"n|+ c #7F6953", +"o|+ c #79523E", +"p|+ c #7A5641", +"q|+ c #7B604E", +"r|+ c #987357", +"s|+ c #805E45", +"t|+ c #37291E", +"u|+ c #100D0B", +"v|+ c #534839", +"w|+ c #BC8636", +"x|+ c #F7B224", +"y|+ c #FCBF23", +"z|+ c #D89E2A", +"A|+ c #D5DCE9", +"B|+ c #788395", +"C|+ c #505358", +"D|+ c #60636D", +"E|+ c #53565F", +"F|+ c #100F0E", +"G|+ c #4E5159", +"H|+ c #26282C", +"I|+ c #585B62", +"J|+ c #565960", +"K|+ c #746C61", +"L|+ c #DBA52E", +"M|+ c #E4AA21", +"N|+ c #6A5C37", +"O|+ c #9BA9C0", +"P|+ c #A6B0BB", +"Q|+ c #6B788F", +"R|+ c #8B7450", +"S|+ c #DE931D", +"T|+ c #D1891D", +"U|+ c #C47F1E", +"V|+ c #7C5A32", +"W|+ c #232020", +"X|+ c #363131", +"Y|+ c #5D5251", +"Z|+ c #2B2538", +"`|+ c #020626", +" 1+ c #040724", +".1+ c #040622", +"+1+ c #030712", +"@1+ c #040813", +"#1+ c #010612", +"$1+ c #020912", +"%1+ c #0E1417", +"&1+ c #242721", +"*1+ c #383930", +"=1+ c #403F36", +"-1+ c #48443D", +";1+ c #48443C", +">1+ c #434038", +",1+ c #4A463D", +"'1+ c #2D291F", +")1+ c #241F13", +"!1+ c #2A2318", +"~1+ c #2C2419", +"{1+ c #2B2117", +"]1+ c #30261A", +"^1+ c #2A2418", +"/1+ c #262015", +"(1+ c #231D11", +"_1+ c #211B10", +":1+ c #272014", +"<1+ c #282116", +"[1+ c #342A20", +"}1+ c #33261D", +"|1+ c #2E2217", +"11+ c #2C2114", +"21+ c #34291A", +"31+ c #3A3222", +"41+ c #362F1E", +"51+ c #2C2514", +"61+ c #2B2416", +"71+ c #2F2519", +"81+ c #352A1E", +"91+ c #3C3323", +"01+ c #423928", +"a1+ c #3F3625", +"b1+ c #372D1F", +"c1+ c #32281D", +"d1+ c #4B463C", +"e1+ c #5A534B", +"f1+ c #625B53", +"g1+ c #655F58", +"h1+ c #67625C", +"i1+ c #6B6861", +"j1+ c #6C6962", +"k1+ c #6E6A64", +"l1+ c #5E594F", +"m1+ c #403D31", +"n1+ c #363326", +"o1+ c #393627", +"p1+ c #585447", +"q1+ c #4A493B", +"r1+ c #303021", +"s1+ c #3D402D", +"t1+ c #555644", +"u1+ c #5A5F4F", +"v1+ c #545748", +"w1+ c #4A4B3C", +"x1+ c #45422E", +"y1+ c #473F2A", +"z1+ c #4F412C", +"A1+ c #594835", +"B1+ c #7C6452", +"C1+ c #7E5F4D", +"D1+ c #73533E", +"E1+ c #77523D", +"F1+ c #7C513C", +"G1+ c #84573D", +"H1+ c #9A6C4E", +"I1+ c #86614A", +"J1+ c #3B2A1F", +"K1+ c #0F0C0E", +"L1+ c #3C3227", +"M1+ c #876B44", +"N1+ c #E19D29", +"O1+ c #FCB823", +"P1+ c #FABF23", +"Q1+ c #B58A3C", +"R1+ c #A3A8C1", +"S1+ c #F6F8FA", +"T1+ c #94A0BA", +"U1+ c #1E2025", +"V1+ c #5F636C", +"W1+ c #545861", +"X1+ c #4D5157", +"Y1+ c #404348", +"Z1+ c #1C1F18", +"`1+ c #5D6067", +" 2+ c #64676E", +".2+ c #5E6167", +"+2+ c #5E6064", +"@2+ c #C08E39", +"#2+ c #C88528", +"$2+ c #725939", +"%2+ c #5C7095", +"&2+ c #8491A4", +"*2+ c #A8B3C3", +"=2+ c #8D827E", +"-2+ c #E49F26", +";2+ c #FCA51F", +">2+ c #E0901C", +",2+ c #815D2E", +"'2+ c #322936", +")2+ c #13132E", +"!2+ c #040629", +"~2+ c #030628", +"{2+ c #000611", +"]2+ c #020710", +"^2+ c #05090F", +"/2+ c #0A0E0E", +"(2+ c #1F221A", +"_2+ c #2F3024", +":2+ c #3D3B31", +"<2+ c #312D23", +"[2+ c #272418", +"}2+ c #2D281C", +"|2+ c #2E291D", +"12+ c #252317", +"22+ c #29271A", +"32+ c #282718", +"42+ c #252416", +"52+ c #212012", +"62+ c #212013", +"72+ c #221E13", +"82+ c #221E14", +"92+ c #241E13", +"02+ c #2F281C", +"a2+ c #332A1F", +"b2+ c #352B22", +"c2+ c #2A2116", +"d2+ c #30271A", +"e2+ c #302A1C", +"f2+ c #2C2617", +"g2+ c #2E281A", +"h2+ c #312B1D", +"i2+ c #322B1E", +"j2+ c #342B1F", +"k2+ c #3B3222", +"l2+ c #362D1C", +"m2+ c #34281B", +"n2+ c #35281D", +"o2+ c #35281C", +"p2+ c #3C372B", +"q2+ c #49443A", +"r2+ c #615C54", +"s2+ c #6B6760", +"t2+ c #4E4C40", +"u2+ c #38372A", +"v2+ c #333324", +"w2+ c #262615", +"x2+ c #3A3B28", +"y2+ c #484938", +"z2+ c #2F3120", +"A2+ c #232815", +"B2+ c #353B27", +"C2+ c #525643", +"D2+ c #5A5B49", +"E2+ c #6C6B5A", +"F2+ c #6B6758", +"G2+ c #5E5949", +"H2+ c #5F5643", +"I2+ c #5D4E3B", +"J2+ c #624E3A", +"K2+ c #816351", +"L2+ c #78503F", +"M2+ c #754E3B", +"N2+ c #7A4F38", +"O2+ c #814F34", +"P2+ c #935C3E", +"Q2+ c #89624D", +"R2+ c #3B2C25", +"S2+ c #100C11", +"T2+ c #5F4B2C", +"U2+ c #805E35", +"V2+ c #A37224", +"W2+ c #E9A721", +"X2+ c #F6BB21", +"Y2+ c #998656", +"Z2+ c #CBD4E6", +"`2+ c #F9FAFC", +" 3+ c #95A1BC", +".3+ c #1C1F24", +"+3+ c #50535B", +"@3+ c #535760", +"#3+ c #575B64", +"$3+ c #4A4C54", +"%3+ c #424247", +"&3+ c #474A4F", +"*3+ c #4F5257", +"=3+ c #4D5055", +"-3+ c #070B09", +";3+ c #151712", +">3+ c #191C15", +",3+ c #6C6F76", +"'3+ c #676A71", +")3+ c #5A585C", +"!3+ c #B07F33", +"~3+ c #B1762C", +"{3+ c #684E31", +"]3+ c #7283AA", +"^3+ c #E6ECF5", +"/3+ c #F4F6F6", +"(3+ c #C78F2C", +"_3+ c #FDA820", +":3+ c #E7921F", +"<3+ c #7E5428", +"[3+ c #10101D", +"}3+ c #050729", +"|3+ c #030627", +"13+ c #030727", +"23+ c #000412", +"33+ c #090C19", +"43+ c #070B15", +"53+ c #26271D", +"63+ c #2C2C1E", +"73+ c #343324", +"83+ c #363428", +"93+ c #322F25", +"03+ c #29271D", +"a3+ c #242216", +"b3+ c #2F2C20", +"c3+ c #302D21", +"d3+ c #252619", +"e3+ c #2C2E20", +"f3+ c #282A1A", +"g3+ c #252718", +"h3+ c #222416", +"i3+ c #212115", +"j3+ c #1F1E13", +"k3+ c #242217", +"l3+ c #272317", +"m3+ c #292418", +"n3+ c #241E12", +"o3+ c #2E281D", +"p3+ c #2D271C", +"q3+ c #282217", +"r3+ c #231F13", +"s3+ c #272417", +"t3+ c #292518", +"u3+ c #2E2A1C", +"v3+ c #2A2417", +"w3+ c #292115", +"x3+ c #292212", +"y3+ c #2F2616", +"z3+ c #332919", +"A3+ c #34271B", +"B3+ c #382E23", +"C3+ c #413A2F", +"D3+ c #4B463B", +"E3+ c #5B554D", +"F3+ c #6A6760", +"G3+ c #69675E", +"H3+ c #65635B", +"I3+ c #626056", +"J3+ c #313022", +"K3+ c #575748", +"L3+ c #444634", +"M3+ c #232512", +"N3+ c #262915", +"O3+ c #222814", +"P3+ c #1F2712", +"Q3+ c #212B15", +"R3+ c #2A311C", +"S3+ c #3A3C29", +"T3+ c #595645", +"U3+ c #797263", +"V3+ c #776F61", +"W3+ c #706659", +"X3+ c #6E5E51", +"Y3+ c #695446", +"Z3+ c #826757", +"`3+ c #916F5D", +" 4+ c #8D6752", +".4+ c #7B5745", +"+4+ c #6C4638", +"@4+ c #784A33", +"#4+ c #8A5B3F", +"$4+ c #86644E", +"%4+ c #392E28", +"&4+ c #100C0D", +"*4+ c #514228", +"=4+ c #956A2D", +"-4+ c #795124", +";4+ c #B67626", +">4+ c #EAA629", +",4+ c #9B8F7D", +"'4+ c #E0E8F5", +")4+ c #FAFBFA", +"!4+ c #9BA3C1", +"~4+ c #5D6068", +"{4+ c #565A63", +"]4+ c #4A4B53", +"^4+ c #3B3124", +"/4+ c #393C3F", +"(4+ c #505457", +"_4+ c #060906", +":4+ c #070A06", +"<4+ c #4D4F5B", +"[4+ c #4F525B", +"}4+ c #4D5059", +"|4+ c #4B4F58", +"14+ c #575A61", +"24+ c #58473A", +"34+ c #533C20", +"44+ c #4E3F26", +"54+ c #525A72", +"64+ c #D9E0EE", +"74+ c #A5ABBE", +"84+ c #AA7C35", +"94+ c #FAAC20", +"04+ c #E29220", +"a4+ c #865D25", +"b4+ c #151110", +"c4+ c #0C0C17", +"d4+ c #050920", +"e4+ c #020728", +"f4+ c #020726", +"g4+ c #040725", +"h4+ c #050816", +"i4+ c #0E121E", +"j4+ c #070B16", +"k4+ c #050911", +"l4+ c #1E1F17", +"m4+ c #2F2E20", +"n4+ c #302E20", +"o4+ c #2F2B1D", +"p4+ c #2B2619", +"q4+ c #2F291E", +"r4+ c #3A3429", +"s4+ c #373227", +"t4+ c #323121", +"u4+ c #353426", +"v4+ c #313024", +"w4+ c #292B22", +"x4+ c #272A1F", +"y4+ c #222517", +"z4+ c #212314", +"A4+ c #1F2113", +"B4+ c #242517", +"C4+ c #242014", +"D4+ c #211D11", +"E4+ c #262416", +"F4+ c #1F1E12", +"G4+ c #211D16", +"H4+ c #28261D", +"I4+ c #2F2C22", +"J4+ c #322D21", +"K4+ c #2F291C", +"L4+ c #292313", +"M4+ c #2B2414", +"N4+ c #2F2617", +"O4+ c #312519", +"P4+ c #362B21", +"Q4+ c #393025", +"R4+ c #454034", +"S4+ c #464337", +"T4+ c #3C392D", +"U4+ c #383428", +"V4+ c #5D5A4F", +"W4+ c #555548", +"X4+ c #56574A", +"Y4+ c #444538", +"Z4+ c #3E3E32", +"`4+ c #3E3F31", +" 5+ c #3F4030", +".5+ c #525343", +"+5+ c #4D4F3D", +"@5+ c #2A2F19", +"#5+ c #242913", +"$5+ c #242A14", +"%5+ c #232A15", +"&5+ c #262E19", +"*5+ c #2C331F", +"=5+ c #41422E", +"-5+ c #635E4E", +";5+ c #807669", +">5+ c #877B6E", +",5+ c #86796D", +"'5+ c #7F7165", +")5+ c #7A6557", +"!5+ c #7A5E4C", +"~5+ c #7F5C46", +"{5+ c #81573F", +"]5+ c #8A5F47", +"^5+ c #6D5146", +"/5+ c #694E46", +"(5+ c #846351", +"_5+ c #867061", +":5+ c #3E342E", +"<5+ c #130F0E", +"[5+ c #45413D", +"}5+ c #8B6430", +"|5+ c #92682E", +"15+ c #9F6C2D", +"25+ c #C3842E", +"35+ c #878482", +"45+ c #BCC9D5", +"55+ c #EBF2F4", +"65+ c #95A1BF", +"75+ c #342E2B", +"85+ c #626671", +"95+ c #636772", +"05+ c #565A64", +"a5+ c #4D4F58", +"b5+ c #504E56", +"c5+ c #57575C", +"d5+ c #87622E", +"e5+ c #22180A", +"f5+ c #2D2F32", +"g5+ c #4E515A", +"h5+ c #565962", +"i5+ c #50545D", +"j5+ c #4A4E57", +"k5+ c #4D5159", +"l5+ c #48474C", +"m5+ c #191411", +"n5+ c #201912", +"o5+ c #282C33", +"p5+ c #A3ABC1", +"q5+ c #F7FBFA", +"r5+ c #AEB7CE", +"s5+ c #8A693D", +"t5+ c #F1A325", +"u5+ c #D88C27", +"v5+ c #765528", +"w5+ c #0C0D13", +"x5+ c #090A1E", +"y5+ c #050823", +"z5+ c #010725", +"A5+ c #040727", +"B5+ c #030620", +"C5+ c #121524", +"D5+ c #040815", +"E5+ c #030714", +"F5+ c #080B13", +"G5+ c #22201A", +"H5+ c #352B19", +"I5+ c #392F22", +"J5+ c #3E3427", +"K5+ c #41382A", +"L5+ c #413B2B", +"M5+ c #3D3729", +"N5+ c #3A342A", +"O5+ c #343431", +"P5+ c #32332B", +"Q5+ c #2F3224", +"R5+ c #27281A", +"S5+ c #232416", +"T5+ c #262618", +"U5+ c #272015", +"V5+ c #2F271C", +"W5+ c #342C21", +"X5+ c #2F281D", +"Y5+ c #312C23", +"Z5+ c #32302A", +"`5+ c #2E2D25", +" 6+ c #363329", +".6+ c #3D382C", +"+6+ c #484135", +"@6+ c #363120", +"#6+ c #2F2919", +"$6+ c #2E2717", +"%6+ c #352D1E", +"&6+ c #312B1C", +"*6+ c #332F20", +"=6+ c #312F20", +"-6+ c #373628", +";6+ c #39382A", +">6+ c #2F2D1E", +",6+ c #2E2B1C", +"'6+ c #2C2919", +")6+ c #2F2C1C", +"!6+ c #302E1D", +"~6+ c #313120", +"{6+ c #383928", +"]6+ c #434435", +"^6+ c #565748", +"/6+ c #616253", +"(6+ c #636454", +"_6+ c #424333", +":6+ c #272917", +"<6+ c #232812", +"[6+ c #252A14", +"}6+ c #282E18", +"|6+ c #313823", +"16+ c #404330", +"26+ c #514F3D", +"36+ c #6F6757", +"46+ c #847A6C", +"56+ c #8E8478", +"66+ c #928477", +"76+ c #8F7767", +"86+ c #836551", +"96+ c #845C45", +"06+ c #804E35", +"a6+ c #7E4D2F", +"b6+ c #85573E", +"c6+ c #775D56", +"d6+ c #917D73", +"e6+ c #553F29", +"f6+ c #82622A", +"g6+ c #8E642C", +"h6+ c #846038", +"i6+ c #7A8393", +"j6+ c #737F96", +"k6+ c #8493AD", +"l6+ c #6D80A9", +"m6+ c #554532", +"n6+ c #3D2E1B", +"o6+ c #606470", +"p6+ c #5A5E69", +"q6+ c #53575F", +"r6+ c #4F4E56", +"s6+ c #5A585B", +"t6+ c #B47B2D", +"u6+ c #593C13", +"v6+ c #272220", +"w6+ c #313337", +"x6+ c #525560", +"y6+ c #555861", +"z6+ c #4C5058", +"A6+ c #4C5258", +"B6+ c #120F11", +"C6+ c #575F74", +"D6+ c #DCE5EB", +"E6+ c #B0BED2", +"F6+ c #68533B", +"G6+ c #CE881E", +"H6+ c #E4931F", +"I6+ c #805A21", +"J6+ c #0A0C16", +"K6+ c #050922", +"L6+ c #03062A", +"M6+ c #020725", +"N6+ c #030722", +"O6+ c #030616", +"P6+ c #0B111F", +"Q6+ c #0B0F1C", +"R6+ c #0A0E1A", +"S6+ c #0A0D15", +"T6+ c #1F1E1A", +"U6+ c #342F22", +"V6+ c #3D3325", +"W6+ c #433725", +"X6+ c #433A28", +"Y6+ c #423929", +"Z6+ c #43392B", +"`6+ c #443D2E", +" 7+ c #3A3228", +".7+ c #3A3127", +"+7+ c #352D23", +"@7+ c #382B20", +"#7+ c #382B21", +"$7+ c #3D3A34", +"%7+ c #3E3C36", +"&7+ c #33312B", +"*7+ c #302B2B", +"=7+ c #2E292A", +"-7+ c #2A2523", +";7+ c #25221A", +">7+ c #292517", +",7+ c #332C1D", +"'7+ c #352F1F", +")7+ c #312E1D", +"!7+ c #32311E", +"~7+ c #2A2A19", +"{7+ c #2D2C1D", +"]7+ c #323120", +"^7+ c #2D2C19", +"/7+ c #2C2A1A", +"(7+ c #252413", +"_7+ c #272615", +":7+ c #3D3B2D", +"<7+ c #454637", +"[7+ c #3D3E2E", +"}7+ c #343625", +"|7+ c #343624", +"17+ c #383A28", +"27+ c #3C3E2E", +"37+ c #303322", +"47+ c #232715", +"57+ c #2A311A", +"67+ c #2A311D", +"77+ c #373B28", +"87+ c #555141", +"97+ c #6D6555", +"07+ c #908276", +"a7+ c #93867A", +"b7+ c #9B887A", +"c7+ c #987C6C", +"d7+ c #85634E", +"e7+ c #7E573E", +"f7+ c #804D32", +"g7+ c #824429", +"h7+ c #8A492D", +"i7+ c #8D5E41", +"j7+ c #958372", +"k7+ c #6B615C", +"l7+ c #272324", +"m7+ c #0E0D0C", +"n7+ c #2D2215", +"o7+ c #74542F", +"p7+ c #926835", +"q7+ c #BAC5D7", +"r7+ c #E5E7ED", +"s7+ c #9FAABE", +"t7+ c #5D6780", +"u7+ c #8E6835", +"v7+ c #835C2E", +"w7+ c #484542", +"x7+ c #626672", +"y7+ c #626670", +"z7+ c #5E6169", +"A7+ c #53565E", +"B7+ c #505057", +"C7+ c #5F5755", +"D7+ c #C28127", +"E7+ c #80561A", +"F7+ c #21180A", +"G7+ c #151111", +"H7+ c #150F0E", +"I7+ c #603F19", +"J7+ c #22180E", +"K7+ c #222120", +"L7+ c #5A5D66", +"M7+ c #545760", +"N7+ c #575A62", +"O7+ c #555860", +"P7+ c #2A2B2D", +"Q7+ c #222630", +"R7+ c #7F879C", +"S7+ c #7886A1", +"T7+ c #45362C", +"U7+ c #85571B", +"V7+ c #B66F19", +"W7+ c #7A5025", +"X7+ c #161214", +"Y7+ c #0E0D16", +"Z7+ c #02062B", +"`7+ c #030827", +" 8+ c #080C1B", +".8+ c #090C1B", +"+8+ c #0C0F1E", +"@8+ c #090C1A", +"#8+ c #0D1119", +"$8+ c #2F2D29", +"%8+ c #3C362C", +"&8+ c #454038", +"*8+ c #46403B", +"=8+ c #504841", +"-8+ c #4F4842", +";8+ c #423A36", +">8+ c #463C37", +",8+ c #463E39", +"'8+ c #413A37", +")8+ c #3C3839", +"!8+ c #3C393F", +"~8+ c #121323", +"{8+ c #0E0E1D", +"]8+ c #181925", +"^8+ c #332C21", +"/8+ c #362F26", +"(8+ c #322E26", +"_8+ c #2D2B25", +":8+ c #2C2825", +"<8+ c #2C2822", +"[8+ c #2C291F", +"}8+ c #262616", +"|8+ c #1E2013", +"18+ c #202313", +"28+ c #252813", +"38+ c #2A2B19", +"48+ c #252714", +"58+ c #232611", +"68+ c #222510", +"78+ c #252713", +"88+ c #303321", +"98+ c #272C1C", +"08+ c #232818", +"a8+ c #282C18", +"b8+ c #2B331C", +"c8+ c #384030", +"d8+ c #525041", +"e8+ c #6F6556", +"f8+ c #736959", +"g8+ c #7A695E", +"h8+ c #8D7B6F", +"i8+ c #9B8577", +"j8+ c #94705D", +"k8+ c #835741", +"l8+ c #764C31", +"m8+ c #7F4D30", +"n8+ c #844E2F", +"o8+ c #804F33", +"p8+ c #896047", +"q8+ c #806C5C", +"r8+ c #171110", +"s8+ c #523A22", +"t8+ c #7D5931", +"u8+ c #7D746A", +"v8+ c #FAFCF9", +"w8+ c #BEC9DF", +"x8+ c #D09430", +"y8+ c #D39331", +"z8+ c #8A775F", +"A8+ c #616570", +"B8+ c #656974", +"C8+ c #696D78", +"D8+ c #62656E", +"E8+ c #54575F", +"F8+ c #66574E", +"G8+ c #D28C23", +"H8+ c #B37620", +"I8+ c #523716", +"J8+ c #110E0C", +"K8+ c #3B2815", +"L8+ c #BA771A", +"M8+ c #533B1E", +"N8+ c #484E65", +"O8+ c #1D1C26", +"P8+ c #171513", +"Q8+ c #595C65", +"R8+ c #585B64", +"S8+ c #60646A", +"T8+ c #404145", +"U8+ c #2C200F", +"V8+ c #030828", +"W8+ c #040623", +"X8+ c #0A0D1C", +"Y8+ c #080C19", +"Z8+ c #0F131F", +"`8+ c #11151F", +" 9+ c #0C111A", +".9+ c #0A0F16", +"+9+ c #0C1018", +"@9+ c #11151C", +"#9+ c #151820", +"$9+ c #2D2E36", +"%9+ c #3E3E44", +"&9+ c #555355", +"*9+ c #565457", +"=9+ c #444247", +"-9+ c #35343B", +";9+ c #32313A", +">9+ c #2D2D37", +",9+ c #2E2F39", +"'9+ c #242634", +")9+ c #2D2E3C", +"!9+ c #302F3D", +"~9+ c #292834", +"{9+ c #282631", +"]9+ c #4D4C51", +"^9+ c #5B595C", +"/9+ c #565251", +"(9+ c #403D43", +"_9+ c #161421", +":9+ c #0B0B1E", +"<9+ c #0F0F1A", +"[9+ c #161620", +"}9+ c #1F2016", +"|9+ c #222110", +"19+ c #272512", +"29+ c #2C2918", +"39+ c #2F2C1B", +"49+ c #393725", +"59+ c #363622", +"69+ c #3E3D2A", +"79+ c #4E4C39", +"89+ c #433F2D", +"99+ c #2C2A19", +"09+ c #2A2A1A", +"a9+ c #303120", +"b9+ c #474638", +"c9+ c #666254", +"d9+ c #7E7465", +"e9+ c #6E5A4B", +"f9+ c #775B4C", +"g9+ c #886A5A", +"h9+ c #967461", +"i9+ c #815238", +"j9+ c #794228", +"k9+ c #7E4A2F", +"l9+ c #805036", +"m9+ c #7A5437", +"n9+ c #6E5941", +"o9+ c #131011", +"p9+ c #372C1D", +"q9+ c #6E5234", +"r9+ c #79777C", +"s9+ c #E8EDF4", +"t9+ c #F4F6F8", +"u9+ c #8F96AD", +"v9+ c #867025", +"w9+ c #E2B82C", +"x9+ c #E3B131", +"y9+ c #8B7B63", +"z9+ c #6B6F7A", +"A9+ c #6E727D", +"B9+ c #60646F", +"C9+ c #5C5F68", +"D9+ c #575A63", +"E9+ c #52545D", +"F9+ c #E29820", +"G9+ c #EF9B1F", +"H9+ c #A86F26", +"I9+ c #332310", +"J9+ c #6E4A1D", +"K9+ c #D48921", +"L9+ c #6D502F", +"M9+ c #7C8AA2", +"N9+ c #A8B9CD", +"O9+ c #2B3340", +"P9+ c #50545A", +"Q9+ c #5D6169", +"R9+ c #575964", +"S9+ c #494A50", +"T9+ c #1A1616", +"U9+ c #1B1818", +"V9+ c #45464A", +"W9+ c #0F0E0D", +"X9+ c #030729", +"Y9+ c #05071A", +"Z9+ c #010617", +"`9+ c #000617", +" 0+ c #000616", +".0+ c #010717", +"+0+ c #030919", +"@0+ c #060C1C", +"#0+ c #0C121F", +"$0+ c #161D26", +"%0+ c #242731", +"&0+ c #262834", +"*0+ c #222330", +"=0+ c #20202E", +"-0+ c #191828", +";0+ c #171627", +">0+ c #181C2A", +",0+ c #3D3D47", +"'0+ c #555156", +")0+ c #605B5E", +"!0+ c #666264", +"~0+ c #646062", +"{0+ c #5F5D5E", +"]0+ c #191A2A", +"^0+ c #010615", +"/0+ c #2A2512", +"(0+ c #2A2413", +"_0+ c #2B2514", +":0+ c #423C2A", +"<0+ c #393421", +"[0+ c #352E1C", +"}0+ c #433F2E", +"|0+ c #585346", +"10+ c #666058", +"20+ c #766A5E", +"30+ c #8A7A6B", +"40+ c #897968", +"50+ c #856A5A", +"60+ c #83614F", +"70+ c #916853", +"80+ c #875841", +"90+ c #7C462D", +"00+ c #824E38", +"a0+ c #755942", +"b0+ c #5A4937", +"c0+ c #2E2A20", +"d0+ c #120F14", +"e0+ c #19170E", +"f0+ c #55442C", +"g0+ c #767982", +"h0+ c #F0F4F6", +"i0+ c #D5DCE8", +"j0+ c #676263", +"k0+ c #C3A120", +"l0+ c #F7D825", +"m0+ c #DCBA3A", +"n0+ c #777377", +"o0+ c #6D717B", +"p0+ c #5E626D", +"q0+ c #61656F", +"r0+ c #62666F", +"s0+ c #5E6069", +"t0+ c #776957", +"u0+ c #EA9F25", +"v0+ c #FBA61F", +"w0+ c #D79029", +"x0+ c #785322", +"y0+ c #1A130B", +"z0+ c #080605", +"A0+ c #A26D27", +"B0+ c #DA8F23", +"C0+ c #6A573E", +"D0+ c #B8C3D2", +"E0+ c #DBE3ED", +"F0+ c #434D5E", +"G0+ c #646872", +"H0+ c #575B61", +"I0+ c #161613", +"J0+ c #171514", +"K0+ c #161514", +"L0+ c #0B0B1A", +"M0+ c #040822", +"N0+ c #030527", +"O0+ c #020824", +"P0+ c #02051A", +"Q0+ c #000716", +"R0+ c #000715", +"S0+ c #02081A", +"T0+ c #070A1C", +"U0+ c #1B1B2C", +"V0+ c #222133", +"W0+ c #34343F", +"X0+ c #646063", +"Y0+ c #6D6869", +"Z0+ c #585559", +"`0+ c #403E47", +" a+ c #2E2F3A", +".a+ c #010515", +"+a+ c #090A1A", +"@a+ c #2B2818", +"#a+ c #2F2A18", +"$a+ c #2D2B19", +"%a+ c #252612", +"&a+ c #2D2A17", +"*a+ c #2B2613", +"=a+ c #2D2814", +"-a+ c #2F2916", +";a+ c #342F1C", +">a+ c #383422", +",a+ c #463F2E", +"'a+ c #5C5242", +")a+ c #614F3F", +"!a+ c #5B4F3C", +"~a+ c #755F4D", +"{a+ c #7C624E", +"]a+ c #7F614C", +"^a+ c #7D5C47", +"/a+ c #896450", +"(a+ c #895941", +"_a+ c #7C452A", +":a+ c #7A492F", +"b+ c #774427", +",b+ c #734828", +"'b+ c #5B4028", +")b+ c #282319", +"!b+ c #303A48", +"~b+ c #627089", +"{b+ c #4F4540", +"]b+ c #C49426", +"^b+ c #FCD325", +"/b+ c #F4D42D", +"(b+ c #908561", +"_b+ c #676C79", +":b+ c #686C78", +"c+ c #38322D", +",c+ c #273443", +"'c+ c #191B1E", +")c+ c #271E1F", +"!c+ c #B97C27", +"~c+ c #FAAD20", +"{c+ c #DA9724", +"]c+ c #89878C", +"^c+ c #EAF2F6", +"/c+ c #F5F7F9", +"(c+ c #7D89A7", +"_c+ c #503B1E", +":c+ c #38322A", +"d+ c #493B30", +",d+ c #4E3D2F", +"'d+ c #433020", +")d+ c #392C19", +"!d+ c #3B301A", +"~d+ c #483420", +"{d+ c #4C331E", +"]d+ c #4F3520", +"^d+ c #583B25", +"/d+ c #654027", +"(d+ c #70442C", +"_d+ c #72422A", +":d+ c #714226", +"e+ c #5C5F65", +",e+ c #62656B", +"'e+ c #686B73", +")e+ c #666972", +"!e+ c #585962", +"~e+ c #44454E", +"{e+ c #493118", +"]e+ c #CF9731", +"^e+ c #FBCE2A", +"/e+ c #EFCA34", +"(e+ c #817D71", +"_e+ c #60656C", +":e+ c #696E78", +"f+ c #867D5B", +",f+ c #EDB126", +"'f+ c #EFBA21", +")f+ c #6B5A2E", +"!f+ c #A3A5BA", +"~f+ c #FBFDFB", +"{f+ c #BCC1CA", +"]f+ c #584B43", +"^f+ c #503F25", +"/f+ c #1A1814", +"(f+ c #10110E", +"_f+ c #120E0F", +":f+ c #544433", +"g+ c #ECC01D", +",g+ c #F8C823", +"'g+ c #9F8642", +")g+ c #565961", +"!g+ c #42464C", +"~g+ c #52565F", +"{g+ c #4B4F57", +"]g+ c #4C4E56", +"^g+ c #474A54", +"/g+ c #494F56", +"(g+ c #7E6E58", +"_g+ c #D69333", +":g+ c #DB9227", +"h+ c #040518", +",h+ c #020520", +"'h+ c #050725", +")h+ c #0B0D29", +"!h+ c #0E102E", +"~h+ c #121432", +"{h+ c #131433", +"]h+ c #101130", +"^h+ c #0D0E2C", +"/h+ c #060826", +"(h+ c #02071D", +"_h+ c #02061D", +":h+ c #07091F", +"i+ c #75787F", +",i+ c #74777E", +"'i+ c #7D8089", +")i+ c #7A7E87", +"!i+ c #7A7D85", +"~i+ c #1B1817", +"{i+ c #484242", +"]i+ c #696D76", +"^i+ c #70747D", +"/i+ c #656972", +"(i+ c #666872", +"_i+ c #3D4042", +":i+ c #563E22", +"j+ c #825C2E", +",j+ c #80582A", +"'j+ c #5B4530", +")j+ c #0F100C", +"!j+ c #10100D", +"~j+ c #151410", +"{j+ c #3F2E1F", +"]j+ c #644526", +"^j+ c #5D442B", +"/j+ c #684D31", +"(j+ c #6D6F77", +"_j+ c #E5ECF1", +":j+ c #CED7E1", +"k+ c #0A0C0F", +",k+ c #0A0C10", +"'k+ c #0B0A11", +")k+ c #0C0C12", +"!k+ c #15161C", +"~k+ c #18191F", +"{k+ c #1A1B21", +"]k+ c #1B1C22", +"^k+ c #1C1D23", +"/k+ c #1D1F23", +"(k+ c #1B1D21", +"_k+ c #4C4E53", +":k+ c #5B5F66", +"l+ c #D99331", +",l+ c #FBBD29", +"'l+ c #FDBF26", +")l+ c #C89B3C", +"!l+ c #606064", +"~l+ c #51565D", +"{l+ c #555960", +"]l+ c #53575D", +"^l+ c #464C53", +"/l+ c #50515D", +"(l+ c #5A544F", +"_l+ c #86612B", +":l+ c #5D4E37", +"m+ c #3D290E", +",m+ c #8C5C28", +"'m+ c #B17929", +")m+ c #D18624", +"!m+ c #E3A229", +"~m+ c #756A5C", +"{m+ c #4F525C", +"]m+ c #4F535D", +"^m+ c #53504F", +"/m+ c #484C51", +"(m+ c #343436", +"_m+ c #333335", +":m+ c #292428", +"n+ c #5A5F67", +",n+ c #62656F", +"'n+ c #696C76", +")n+ c #4B4B51", +"!n+ c #6E727B", +"~n+ c #71757E", +"{n+ c #1D1C1B", +"]n+ c #2D2A31", +"^n+ c #45465C", +"/n+ c #403C40", +"(n+ c #A27027", +"_n+ c #F2A022", +":n+ c #E2952B", +"o+ c #66431C", +",o+ c #654627", +"'o+ c #151214", +")o+ c #0C0F0C", +"!o+ c #110D11", +"~o+ c #0A0C0E", +"{o+ c #0A0C13", +"]o+ c #595C61", +"^o+ c #343437", +"/o+ c #3D3F43", +"(o+ c #44484C", +"_o+ c #363333", +":o+ c #AE9799", +"p+ c #22252E", +",p+ c #252833", +"'p+ c #2A2D3A", +")p+ c #2E3140", +"!p+ c #323647", +"~p+ c #363A4F", +"{p+ c #373B51", +"]p+ c #34384D", +"^p+ c #32364A", +"/p+ c #2F3245", +"(p+ c #2B2E3F", +"_p+ c #242735", +":p+ c #1B1D28", +"q+ c #6A6864", +",q+ c #29251B", +"'q+ c #E2E4E9", +")q+ c #F6F7F8", +"!q+ c #E4E5EA", +"~q+ c #DEDFE5", +"{q+ c #DDDEE4", +"]q+ c #DFE0E6", +"^q+ c #EAEBEF", +"/q+ c #EDEEF1", +"(q+ c #525769", +"_q+ c #1E2131", +":q+ c #1C1F2E", +"r+ c #212539", +",r+ c #191D2C", +"'r+ c #171A28", +")r+ c #171B27", +"!r+ c #A2A3AC", +"~r+ c #8D8F97", +"{r+ c #1B1E23", +"]r+ c #191C20", +"^r+ c #181B1F", +"/r+ c #16191C", +"(r+ c #595A60", +"_r+ c #E4E4E7", +":r+ c #ECECEE", +"s+ c #44464A", +",s+ c #E1E1E4", +"'s+ c #7D7E84", +")s+ c #1A1915", +"!s+ c #848487", +"~s+ c #FBFCFC", +"{s+ c #D1D1D4", +"]s+ c #292722", +"^s+ c #18150F", +"/s+ c #161410", +"(s+ c #1A160F", +"_s+ c #20190F", +":s+ c #1C180F", +"t+ c #1C170F", +",t+ c #1D170F", +"'t+ c #18140E", +")t+ c #1B160D", +"!t+ c #251D0F", +"~t+ c #1C1E1E", +"{t+ c #212222", +"]t+ c #25221C", +"^t+ c #292110", +"/t+ c #2C230F", +"(t+ c #2D240F", +"_t+ c #2A210F", +":t+ c #211A0E", +"u+ c #282B2B", +",u+ c #1B1A15", +"'u+ c #1F1A12", +")u+ c #1E1911", +"!u+ c #1A1711", +"~u+ c #1B1914", +"{u+ c #A2A3A3", +"]u+ c #3D3E3E", +"^u+ c #404141", +"/u+ c #313249", +"(u+ c #4F5284", +"_u+ c #404885", +":u+ c #3C426F", +"v+ c #6E6C69", +",v+ c #696B6B", +"'v+ c #666769", +")v+ c #55534A", +"!v+ c #4A4631", +"~v+ c #423D2A", +"{v+ c #35332A", +"]v+ c #2E2F30", +"^v+ c #27292A", +"/v+ c #1A1812", +"(v+ c #211A10", +"_v+ c #201A10", +":v+ c #201A0E", +"w+ c #4E504F", +",w+ c #474949", +"'w+ c #505253", +")w+ c #585A5A", +"!w+ c #5D5E5F", +"~w+ c #595B5B", +"{w+ c #5F6161", +"]w+ c #616363", +"^w+ c #606162", +"/w+ c #5E5F60", +"(w+ c #5E6060", +"_w+ c #5B5D5D", +":w+ c #606262", +"x+ c #797672", +",x+ c #938E86", +"'x+ c #8B8881", +")x+ c #888887", +"!x+ c #6B6D6E", +"~x+ c #7C7D7C", +"{x+ c #747676", +"]x+ c #545759", +"^x+ c #767778", +"/x+ c #7E7F7F", +"(x+ c #898A8A", +"_x+ c #737474", +":x+ c #737475", +"y+ c #7F8492", +",y+ c #7E8086", +"'y+ c #7A7B81", +")y+ c #F1F1F2", +"!y+ c #5F6065", +"~y+ c #838489", +"{y+ c #5C5D62", +"]y+ c #EBEBED", +"^y+ c #76787C", +"/y+ c #29220E", +"(y+ c #271F0F", +"_y+ c #1F1A0E", +":y+ c #161C21", +"z+ c #252941", +",z+ c #23273D", +"'z+ c #202437", +")z+ c #1C1F2D", +"!z+ c #161921", +"~z+ c #171920", +"{z+ c #83848A", +"]z+ c #4E4F54", +"^z+ c #8F9095", +"/z+ c #D6D6D9", +"(z+ c #1D1B14", +"_z+ c #261F0E", +":z+ c #1F1A0F", +"A+ c #898A89", +",A+ c #6C6E6D", +"'A+ c #7E7D7C", +")A+ c #8D8E8E", +"!A+ c #282A2A", +"~A+ c #1C2124", +"{A+ c #131517", +"]A+ c #2A2C40", +"^A+ c #3F477E", +"/A+ c #1B1E2C", +"(A+ c #161924", +"_A+ c #1B1F32", +":A+ c #2F324B", +"B+ c #10121A", +",B+ c #0D1013", +"'B+ c #0C0E12", +")B+ c #3B3F4A", +"!B+ c #8A8E9B", +"~B+ c #F9FAFB", +"{B+ c #696A6E", +"]B+ c #323436", +"^B+ c #8F9096", +"/B+ c #D2D2D6", +"(B+ c #323437", +"_B+ c #6A6C70", +":B+ c #D0D1D4", +"C+ c #23273C", +",C+ c #262941", +"'C+ c #252940", +")C+ c #8A8C99", +"!C+ c #F1F2F4", +"~C+ c #EAEBEE", +"{C+ c #E9E9ED", +"]C+ c #E8E8EC", +"^C+ c #ECECEF", +"/C+ c #EEEEF1", +"(C+ c #8B8C93", +"_C+ c #616267", +":C+ c #E5E6E9", +"D+ c #3C3324", +",D+ c #3D3622", +"'D+ c #3B3620", +")D+ c #3C3721", +"!D+ c #393520", +"~D+ c #2A2B2A", +"{D+ c #434546", +"]D+ c #3A4170", +"^D+ c #353B62", +"/D+ c #212437", +"(D+ c #0F1218", +"_D+ c #232740", +":D+ c #1F2336", +"E+ c #191D2A", +",E+ c #878891", +"'E+ c #C3C4C7", +")E+ c #74757B", +"!E+ c #28292B", +"~E+ c #A7A8AE", +"{E+ c #E8E8EA", +"]E+ c #B5B6BA", +"^E+ c #464749", +"/E+ c #3E4043", +"(E+ c #545558", +"_E+ c #C2C3C6", +":E+ c #4D4E54", +"F+ c #AAA8A4", +",F+ c #82817E", +"'F+ c #14161E", +")F+ c #21253B", +"!F+ c #AEAEB4", +"~F+ c #CDCDD2", +"{F+ c #D0D1D7", +"]F+ c #75798B", +"^F+ c #212530", +"/F+ c #201F17", +"(F+ c #2B250D", +"_F+ c #231D0E", +":F+ c #17150E", +" , @ ' ) ! ~ { { ] ^ ^ / ( ( ( _ { ! : : ! { < ! _ _ [ } | 1 2 3 4 5 6 = = = = = = = = = = = = = = 7 5 8 9 0 0 a 1 b c d e f f g h i j k l m n o = = = = = = = = = = = = = = = = = = = = = = = = p q r s t > , u ' ) { v w ( { ( w x y y y y y y x x x ( w ( w x y y y y y x x x w w w w w w ( x x x x y x w x x w ( ( ( ( ( w y y x w ( { _ ~ . z A B = = = = = = = = = = = = = = C + D E F G = = = = = = = = = = = = = = = H I J K L M N O P Q R S T U V W X Y Y Z Z ` . .. +. +. Y @. Y #. $. %. &. *. =. -. ;. >. ,. '. ). !. ~. {. ]. ^. [ x x w w w x w w x x x x /. (. _. :. :. :. <. [ x w w x w w x w ( ( ( { { ( ( ( _ { _ { < < ) { < : ! { { _ ( _ _ ] ( { ( ", +"[. u }. |. @ 1. 2. = = = = = = = = = = = = = = = = = = = = 3. 4. 5. A }. u ' : < ~ _ _ 6. 5. 7. ! ! < : ' + 8. ' . . ' ' ) ! 9. 0 0. 0 a. b. c. 5 6 = = = = = = = = = = = = = = d. e. 8 f. 9 | g. | h. i. j. k. l. m. n. o. p. q. r. s. t. n u. = = = = = = = = = = = = = = = = = = = = = = = = = = v. w. x. }. A z y. ' : { v w w w w x y y y y y x w w ( x x w x x y y y y y x x x x w w w w w w w w x x y x x y y x x w ( ( ( w y y x x w ( _ ~ . z A B = = = = = = = = = = = = = = C + D z. F & = = = = = = = = = = = = = = = A. B. C. D. E. N F. G. H. I. J. K. G. L. U M. N. O. Z P. P. ` +. ` Y P. .. Q. +. R. S. T. U. Q V. W. X. Y. Z. `. ). !. + .+ ++ @+ x x w ( w x w w w w w w x /. (. _. :. :. :. <. x x x w x w w x w ( ( w ( ( { { ~ < ) : #+ : . . : #+ ' . : : ) ! < < ! ( _ _ ", +"u }. A $+ %+ = = = = = = = = = = = = = = = = = = = = = = = = &+ *+ , z + ' ) { _ =+ -+ ;+ >+ ' ,+ + y. u u u + y. u u u [. ' ! '+ 1 )+ !+ ~+ {+ 5 6 = = = = = = = = = = = = = = ]+ ^+ /+ (+ )+ d _+ :+ <+ [+ }+ |+ 1+ 2+ 3+ 4+ 5+ 2+ 6+ /+ c. n 7+ = = = = = = = = = = = = = = = = = = = = = = = = = = = = 8+ 9+ @ , z + #+ ! { { ( ( w x y y y y y x w ( w y x x x y 0+ 0+ a+ x x x x x x w w w w w w w w x y y y y y y x w b+ w ( ( [ a+ a+ x w ( { { ~ ' }. |. B = = = = = = = = = = = = = = c+ + z. z. u & = = = = = = = = = = = = = = = A. d+ e+ K f+ g+ h+ i+ j+ k+ l+ m+ n+ o+ p+ q+ r+ s+ t+ u+ Q. . .. Q. v+ Z +. Q. +. @. ` w+ x+ y+ z+ >. A+ B+ C+ D+ E+ ~. F+ .+ G+ H+ w w w ( ( w x x w ( ( w x a+ (. _. _. _. _. (. x x x x x x x x w ( w w ( ( { ~ < : ' + + [. y. y. + + + y. y. + [. ,+ ' . : ! _ v ", +"I+ A , 4. J+ = = = = = = = = = = = = = = = = = = = = = = = = v. K+ =+ , @ ,+ L+ 7. 7. L+ ,+ y. M+ N+ }. O+ , , , P+ P+ , , , P+ I+ Q+ ' r. 5+ R+ S+ T+ U+ V+ 6 = = = = = = = = = = = = = = W+ X+ 3+ Y+ 4+ Z+ `+ f @ .@ +@ @ c @@ #@ $@ %@ &@ /+ r. m *@ =@ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = -@ ;@ }. P+ u . #+ : { w w w x y y y y x w w ( w x x y y y >@ 0+ a+ x w w ( w x w ( w x x x [ [ ,@ y y y y y y 0+ ,@ ,@ '@ w w [ (. (. (. x w ( ~ : [. z A B = = = = = = = = = = = = = = c+ + E E u & = = = = = = = = = = = = = = = )@ !@ ~@ {@ r+ ]@ ^@ /@ (@ _@ P :@ <@ N ]@ N [@ Y @. }@ |@ 1@ 2@ s+ 3@ 4@ ` .. Q. Q. ` ` .. 5@ 6@ 7@ 8@ 9@ 0@ a@ b@ c@ a@ c@ d@ e@ b+ 9. ( ( w w x x x w ( w x a+ y y y y f@ g@ h@ h@ /. x x x w w w w ( ( ( < { ! : [. y. z P+ }. P+ , }. }. P+ P+ P+ I+ }. }. i@ y. j@ D 6. ^ ", +"|. > k@ l@ = = = = = = = = = = = = = = = = = = = = = = = = = = v. m@ M+ I+ y. ,+ ;+ ;+ >+ u n@ r r o@ o@ o@ o@ o@ o@ o@ o@ o@ o@ o@ p@ q@ u r@ !+ 6+ %@ T+ s@ t@ u@ = = = = = = = = = = = = = = v@ ^+ w@ x@ y@ z@ f A@ m. B@ B@ @ C@ D@ E@ F@ %@ q. G@ H@ s. ^+ I@ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = B J@ A z K@ ' < ] w [ x x y y y y x w w ( w x y y L@ M@ >@ >@ a+ x x w w w x w w x y y y >@ >@ >@ y y y y y y y >@ >@ [ x x [ _. _. (. x w ( { ) [. z A B = = = = = = = = = = = = = = c+ + E E u G = = = = = = = = = = = = = = = )@ N@ O@ P@ Q@ R@ /@ /@ S@ T@ H. U@ V@ N p+ W@ @. . X@ Y@ r+ r+ Z@ `@ `@ # . .# ` +# +# ` .. @# ## $# %# &# *# *# =# c@ =# *# b@ -# 9. ;# ( w x x x x x w w x x y y y y y L@ g@ h@ ># h@ x y w ( ( w w ( w w { { < ' u ,# '# '# o@ o@ o@ o@ o@ o@ o@ o@ p@ o@ '# r )# $+ -+ 6. ^ ", +"!# ~# {# = = = = = = = = = = = = = = = = = = = = = = = = = = = = 8+ ]# A z y. ,+ ,+ y. }. ^# = = = = = = = = = = = = = = /# M+ (# T+ r. _# N@ :# *@ u@ = = = = = = = = = = = = = = v@ <# [# }# 0. } f f 2+ +@ B@ |# 1# 2# 3# 4# 2+ B. 5# (# s@ *@ I@ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 6# s P+ u . < { w w x x x x x x x w v ( w [ x y y y 0+ ,@ '@ '@ x w ( w x w ( w x y y 0+ >@ >@ 0+ y M@ y y y >@ >@ >@ a+ x x [ :. :. <. [ w ( { < ' z A B = = = = = = = = = = = = = = c+ + E ' F G = = = = = = = = = = = = = = = H 7# O@ 8# E. M g+ 9# 0# _@ k+ U@ a# ]@ @. b# c# d# e# f# g# h# i# .. M . +. +# b# +. 6@ @. +. Z U. @# j# k# *# &# l# m# n# o# p# q# r# 9. w x x x w w w x x x x a+ a+ x x y y g@ h@ h@ h@ x x w ( w x x w w ( ( { : [. z s# = = = = = = = = = = = = = = t# F E u# 6. ", +"% u. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = v# w# I+ @ y. y. y. P+ ^# = = = = = = = = = = = = = = /# I+ x# y# G@ r. r. :# 5 6 = = = = = = = = = = = = = = v@ 5 z# A# B# |# 0. B@ B@ g. | 1 5+ x@ C# 5+ 5+ j f. x# U+ D# o = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = v. E# # u . ! _ F# w w w w w w x x :. <. <. w w w a+ y x x [ w w w w w w w w [ x x y y y f@ 0+ 0+ >@ >@ 0+ y y a+ ,@ ,@ ,@ r# v v F# F# F# w v ( { ) ' M+ G# B = = = = = = = = = = = = = = c+ + : : + H# = = = = = = = = = = = = = = = I# J# K# L# E. g# M# N# O# &. P# Q# O R# S# Y Y #. r+ Y T# i# T# .. . U# V# W# X# .# h# h# i# P. Y# @# P. Z# `# $ .$ +$ o# l# @$ q# v w x x x x w v r# ,@ a+ x x [ <. ^ #$ [ [ w $$ ^ $$ F# F# F# w w [ <. %$ %$ v &$ u# L+ F O+ *$ = = = = = = = = = = = = = = =$ M+ ' ! v ", +"-$ ;$ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = J+ >$ # }. u u M+ , ,$ = = = = = = = = = = = = = = /# , '$ l 5# B. y# (# )$ u@ = = = = = = = = = = = = = = v@ !$ ~$ {$ ]$ 2+ 0. B@ ^$ /$ ($ 0. i _$ :$ d+ A@ 2+ G@ y# :# <$ o = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = [$ F z ' ! _ v w w w w w w [ [ <. ^ ^ F# v ( [ [ [ F# ( ( ( ( F# v v ( w [ x x x y y L@ y 0+ >@ 0+ 0+ y x '@ }$ r# r# 9. / _ ( ( ( ( ( ~ < : + M+ |. B = = = = = = = = = = = = = = c+ + z. ! ' |$ = = = = = = = = = = = = = = = 1$ 2$ 3$ 4$ ` 5$ /@ 6$ 7$ 8$ 7$ 9$ 0$ a$ b$ c$ E. d$ P@ e$ e$ e$ f$ 8# g$ h$ i$ j$ #. k$ f+ s+ v+ l$ Y# m$ n$ ` o$ p$ q$ +$ r$ s$ t$ @+ ( v [ [ [ [ w v r# r# v w x <. ^ 6. =+ ( ( _ 6. 6. 6. u$ u$ u$ v w F# ^ ^ ^ %$ / u# >+ v$ O+ w$ = = = = = = = = = = = = = = x$ }. ,+ ! ( ", +"W+ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = y$ z$ I+ @ z }. A A$ = = = = = = = = = = = = = = /# , U+ B$ y# C$ D$ E$ n 6 = = = = = = = = = = = = = = F$ F G$ H$ I$ J$ )+ } K$ _+ n. L$ M$ N$ O$ P$ Q$ r. r@ [# R$ V+ I@ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = v. S$ I+ + ) { ( v w w w ( v w v 6. 6. =+ { { ~ _ ] ] _ { ! ! ! ~ _ _ _ ( w w x x a+ y T$ y 0+ a+ a+ 0+ x x r# r# 9. / u# D -+ ! ! ! ! < : ' [. F I+ |. B = = = = = = = = = = = = = = U$ F z. V$ W$ X$ Y$ Z$ `$ `$ `$ `$ `$ `$ `$ `$ `$ % Y$ .% +% @% #% $% %% &% 9$ ^@ h+ *% R. =% q+ -% ;% S# >% e$ ,% D. '% )% !% ~% 3$ {% g$ ]% ]% ^% u+ /% (% _% :% U. <% [% }% |% 1% 2% 3% 4% 5% 6% 7% _ _ ( ( ( ( v [ %$ 9. _ { v v 5. 5. 7. ! < ) 7. ;+ ;+ 8% 8% 7. ~ _ ] ^ ^ %$ 9. 9% D >+ v$ G# w$ = = = = = = = = = = = = = = r }. + : _ ", +"0% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = J+ a% A I+ }. , % b% = = = = = = = = = = = = = = /# c% d% B$ T+ !+ /+ e% n 6 = = = = = = = = = = = = = = v@ f% R$ g% h% G@ C$ %@ i% j% k% P$ j% l% !+ /+ H@ m% (# z# *@ n% I@ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = o% # y. : { &$ 9. v w ( _ ( _ ! ;+ ;+ >+ : . . ' L+ L+ . ' [. [. ,+ ' : : ) ! _ F# /. a+ f@ y y y x x 0+ x /. w v / u# : ' ,+ $+ [. + + + + + + u }. # $ B = = = = = = = = = = = = = = p% u : z. E y. I+ A $ !# !# q% q% % % % |. c% r% t@ s% t% q. u% L# @. v% V@ R. g+ a# 5$ U# w% x% y% z% ~% 3$ O@ A% ~@ B% e+ C% D% E% d F% G% H% I% J% K% /% L% !% M% L# N% O% P% Q% R% S% T% ,@ ! ) ! ! < : ! _ { { 7. ;+ ;+ ;+ ;+ ;+ j@ ' ' + ,+ ,+ ,+ + + ,+ >+ L+ 5. _ _ v &$ _ : z$ N+ G# w$ = = = = = = = = = = = = = = r , y. . ! ", +"= = = = = = = = = = = = = = = - U% V% W% -@ = = = = = = = = = = = = = = X% % # , G# Y% b% = = = = = = = = = = = = = = /# |. t. m r@ r@ m Z% )$ 6 = = = = = = = = = = = = = = v@ `% *@ Z% & c. r@ .& e% +& +& ~+ ~+ h% @& ~+ #& m m X+ $& %& I@ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = && }. u . 9% 9. 9. 9. b+ ( { { < L+ >+ z$ v$ y. u @ @ M+ M+ z z }. M+ @ @ u y. + ' : ! _ v x g@ x x [ y y x [ F# _ -+ j@ + u z }. I+ }. }. }. }. }. z P+ A > !# B = = = = = = = = = = = = = = *& }. ' =& W$ $+ M+ P+ |. > $ !# Y% G# c% -& F %& %& $& :# t% ;& >& {@ &% >% ,& '& )& !& ~& ,% {& ]& ^& /& 2$ 6+ (& _& !@ q. :& I 7# <& [& }& |& M$ F@ C% 1& D. 2& 3& <% O. u+ 4& 5& 6& 7& 5& 8& t@ ' [. ' ' ' [. [. ' : : >+ >+ >+ >+ z$ z$ y. 9& @ I+ z z z }. }. z @ v$ ,+ : ! ! < : ' y. O+ % w$ = = = = = = = = = = = = = = r # @ ,+ : ", +"= = = = = = = = = = = = = = 2. 0& a& a& v$ b& = = = = = = = = = = = = = = c& Y% % |. % Y% d& = = = = = = = = = = = = = = /# % & d% U+ t. Z% e& V+ 6 = = = = = = = = = = = = = = f& g& h& i& i& '$ j& k& l& m& n& c. & o& e% y# s% R$ e& n *@ -& p& = = = = = = = = = = = = = = = - q& ;$ = = = = = = = = = = = = = = = [$ r& @ . _ 9. 9. v ( { < ! L+ $+ v$ N+ O+ , s& t& u& v& w& x& y& z& r A& B& s @ }. z u y. : { F# [ x x x g@ y x w _ : $+ F I+ , C& D& E& F& G& H& I& <. A |. > !# q% B = = = = = = = = = = = = = = *& # $+ z. J& + }. K& > Y% !# q% Y% L& M& -& c% -& `% V+ G$ (# N& O@ 4$ L O& P& Q& R& S& T& U& V& W& X& N@ Y& Z& `& * .* +* @* #* $* %* &* ** =* -* +& ;* >* C% D% {% g$ +. P. <% E. ,* '* )* !* F M+ }. }. }. z }. I+ }. z z }. O+ N+ O+ O+ O+ , , I+ ~* {* ]* y& ^* /* (* 5. O+ N+ + ' . : . $+ @ G# Y% w$ = = = = = = = = = = = = = = '# |. I+ @ + ", +"= = = = = = = = = = = = = = [$ % _* _* a& ^# = = = = = = = = = = = = = = Y$ O+ !# $ Y% q% d& = = = = = = = = = = = = = = /# Y% h& j& '$ e& Z% *@ :* 6 = = = = = = = = = = = = = = <* `% [* h& f% }* |* 1* 2* 3* 4* 5* 6* 6* 7* H 8* 9* <$ n 0* -& a* = = = = = = = = = = = = = = = z$ _. b* c* J+ = = = = = = = = = = = = = ;$ d* @ ' ! &$ v 9. 9% ! j@ ,+ v$ N+ O+ G# e* f* g* h* = = = = = = = = J+ w$ i* j* k* , }. y. L+ ! _ w [ a+ y y x ] : + M+ O+ l* 9+ * v. = = = = 8+ y$ m* 7. t q% a& B = = = = = = = = = = = = = = *& A @ ' + d* n* ,# o* /* /* /* p* p* p* q* ,# q* r* s* 8* t* ;& A% u* v* d$ w* w* e$ x* y* p. z* A* B* C* g* ;$ = = = = = = = = J+ * D* E* F* y# !@ |& G* H* I* J* K* L* M* 2@ N* O* P* n* Q* Q* Q* Q* Q* Q* Q* Q* R* R* R* R* R* S* % % > T* U* V* = = = = = 2. W* X* N+ }. @ u + + @ O+ |. !# w$ = = = = = = = = = = = = = = o@ Y% A , z ", +"= = = = = = = = = = = = = = Y* Z* t t q% p% = = = = = = = = = = = = = = `* s Z* q% q% a& d& = = = = = = = = = = = = = = /# q% = %& V+ 5 *@ h& -& .= = = = = = = = = = = = = = = c& c% -& -& += @= = = = = = = = = = = = d. )$ <# h& r% #= = = = = = = = = = = = = = = = v$ a& a& , $= = = = = = = = = = = = = = v. %= z ,+ ! _ ( _ D j@ $+ v$ O+ O+ 0& && -@ = = = = = = = = = = = = = = v. &= *= r& I+ u ,+ : _ v w w [ ( : + }. O+ == s# v. = = = = = = = = v. -= ;= a& a& B = = = = = = = = = = = = = = *& A , y. + H# = = = = = = = = = = = = = = = )@ (& O@ >= ,= ~& >% z% '= )= != ~= {= ]= 8+ = = = = = = = = = = = = = = v. ^= d. /= (= M$ J _= := <= [= }= |= 1= 2= = = = = = = = = = = = = = = = 3= t > p% {# v. = = = = = = = = -@ 4= %$ , P+ }. }. , A $ q% w$ = = = = = = = = = = = = = = v& q% !# > A ", +"= = = = = = = = = = = = = = c& q% Y% $ !# 5= = = = = = = = = = = = = = = 6= 7= a& 8= g* g* 0% = = = = = = = = = = = = = = 2. g* 9= /# /# d% 5 f% c% 0= = = = = = = = = = = = = = = f& % c% M& a= 6= = = = = = = = = = = v. b= :* !$ !$ r% #= = = = = = = = = = = = = = = = z$ Z* q% Z* A& v. = = = = = = = = = = = = = p% }. [. < { _ ! -+ $+ M+ O+ ;+ c= d= = = = = = = = = = = = = = = = = = = = e= f= , @ + : ~ _ v ( ~ ' u O+ g= h= = = = = = = = = = = = = i= u& a& B = = = = = = = = = = = = = = *& A P+ }. u G = = = = = = = = = = = = = = = j= k= ~@ l= m= n= M* o= /& ^. p= q= p = = = = = = = = = = = = = = = = = = = r= s= D$ t= u= v= w= 3& x= y= 7# z= = = = = = = = = = = = = = = = A= q% B= 6= = = = = = = = = = = = J+ C= z$ |. A A |. D= g* g* l@ = = = = = = = = = = = = = = * g* g* g* E= ", +"= = = = = = = = = = = = = = 4= !# > % $ F= = = = = = = = = = = = = = = = G= H= #* = = = = = = = = = = = = = = = = = = = = = = I= %& f% c% .= = = = = = = = = = = = = = = f& Y% J= Y% K= = = = = = = = = = = = 8+ L= n% n% !$ `% I@ = = = = = = = = = = = = = = = z$ q% !# q% M= v. = = = = = = = = = = = = = o@ }. + : ! ! -+ $+ M+ O+ r& N= v. = = = = = = = = = = = = = = = = = = = = = V* 5= , z ' : < _ _ ! y. }. O= P= = = = = = = = = = = = = = = Q= S$ B = = = = = = = = = = = = = = *& A I+ I+ P+ C = = = = = = = = = = = = = = = R= S= T= U= V= (% {% W= i% X= Y= J+ = = = = = = = = = = = = = = = = = = = = = p Z= `= (& - .- +- O@ @- -* #- = = = = = = = = = = = = = = = X% $- h= = = = = = = = = = = = = = v. v& !# Y% $ !# p = = = = = = = = = = = = = = = = = = = = = %- ", +"= = = = = = = = = = = = = = &- Y% |. A > *- = = = = = = = = = = = = = = = =- -- #* = = = = = = = = = = = = = = = = = = = = = = ;- -& `% c% 0= = = = = = = = = = = = = = = f& >- ,- '- f& = = = = = = = = = = = Y$ `% r% r% -& )- a* = = = = = = = = = = = = = = = $+ Y% $ !# ,+ v. = = = = = = = = = = = = = E# I+ y. ' -+ E $+ F I+ !- && v. = = = = = = = = = = = = = = = = = = = = = = = 0% ~- I+ y. . : ! ! L+ u y. 0= = = = = = = = = = = = = = = = = `* J+ = = = = = = = = = = = = = = *& A , I+ A c+ = = = = = = = = = = = = = = = {- y# != /& ]- e+ ^- N@ /- (- v. = = = = = = = = = = = = = = = = = = = = = = = _- :- T+ #% <- u= [- }- |- 1- = = = = = = = = = = = = = = = 2- 3- = = = = = = = = = = = = = = = `* 4- q% q% Z* p = = = = = = = = = = = = = = = = = = = = = 5- ", +"= = = = = = = = = = = = = = 6- > A A % 7- = = = = = = = = = = = = = = = r* -- #* = = = = = = = = = = = = = = = = = = = = = = ;- -& -& c% .= = = = = = = = = = = = = = = 8- a& a& q% s# = = = = = = = = = = = d. Y% Y% M& % Y% c& = = = = = = = = = = = = = = = >+ $ % $ !# v. = = = = = = = = = = = = = A= P+ y. ' j@ $+ F I+ F a* v. = = = = = = = = = = = = = = = = = = = = = = = = = `$ k* @ ,+ : 7. ! ' z 9- 8+ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = *& A , , A 0- = = = = = = = = = = = = = = = a- b- g& 5 '$ R$ n% c- d- v. = = = = = = = = = = = = = = = = = = = = = = = = = e- f- g- #% h- h- N& :& i- = = = = = = = = = = = = = = = j- v. = = = = = = = = = = = = = = = v. k- a& a& a& p = = = = = = = = = = = = = = = = = = = = = 5- ", +"= = = = = = = = = = = = = = 6- > # # |. l- = = = = = = = = = = = = = = = m- -- #* = = = = = = = = = = = = = = = = = = = = = = ;- -& `% c% 6 = = = = = = = = = = = = = = f& -- -- n- = = = = = = = = = = = v. o- Y% % G# p- ,- c& = = = = = = = = = = = = = = = >+ > A > !# v. = = = = = = = = = = = = = q- , u + + r- M+ # s- = = = = = = = = = = = = = = = = = = = = = = = = = = = v. c= }. u [. L+ : y. }. t- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = *& A , , A p% = = = = = = = = = = = = = = = V% M& )- c% c% M& $ %* v. = = = = = = = = = = = = = = = = = = = = = = = = = = v. u- `= g- v- h- 1+ N@ w- = = = = = = = = = = = = = = = v. = = = = = = = = = = = = = = = = = .% a& x- H= p = = = = = = = = = = = = = = = = = = = = = 5- ", +"= = = = = = = = = = = = = = 6- % # # |. l- = = = = = = = = = = = = = = = y- -- #* = = = = = = = = = = = = = = = = = = = = = = z- |. M& ,- 0= = = = = = = = = = = = = = = W% -- -- A- = = = = = = = = = = = &= B- J= M& M& J= ,- c& = = = = = = = = = = = = = = = j@ > A > Y% v. = = = = = = = = = = = = = q- , u + + @ }. C- D- = = = = = = = = = = = = = = = = = = = = = = = = = = = = p E- z y. ,+ ,+ @ (. F- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = *& A I+ , A p% = = = = = = = = = = = = = = = G- % c% c% M& % H- I- = = = = = = = = = = = = = = = = = = = = = = = = = = = = J- K- d% G@ I d+ L- H- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 8+ _. -- -- p = = = = = = = = = = = = = = = = = = = = = 5- ", +"= = = = = = = = = = = = = = 6- % # # |. l- = = = = = = = = = = = = = = = #* -- #* = = = = = = = = = = = = = = = = = = = = = = <$ >- q% q% 0= = = = = = = = = = = = = = = N= -- -- a* = = = = = = = = = = = M- G# % M& )- M& Y% c& = = = = = = = = = = = = = = = L+ > A > Y% v. = = = = = = = = = = = = = q- , u ,+ + @ , N- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ]* , @ y. y. z O- d= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = *& A I+ I+ A p% = = = = = = = = = = = = = = = G- J= M& M& L& p- X% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = P- J= Q- R- S- `% T- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = v. $- -- -- p = = = = = = = = = = = = = = = = = = = = = 5- ", +"= = = = = = = = = = = = = = U- % # O+ # V- = = = = = = = = = = = = = = = m@ -- #* = = = = = = = = = = = = = = = = = = = = = = 6. q% q% q% 0= = = = = = = = = = = = = = = N= -- :. @= = = = = = = = = = = = W- Y% % M& )- M& Y% c& = = = = = = = = = = = = = = = L+ > A > Y% v. = = = = = = = = = = = = = #* , u + y. }. M+ X- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Y- z$ I+ @ u }. B& v. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = *& A , I+ G# p% = = = = = = = = = = = = = = = Z- J= % % % G# M- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = .% v$ % % J= Y% <. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = v. `- -- -- p = = = = = = = = = = = = = = = = = = = = = 5- ", +"= = = = = = = = = = = = = = U- |. , I+ # V- = = = = = = = = = = = = = = = ; -- #* = = = = = = = = = = = = = = = = = = = = = = .; q% q% q% 0= = = = = = = = = = = = = = = N= -- +; l@ = = = = = = = = = = = *& p- L& L& J= p- '- c& = = = = = = = = = = = = = = = L+ > % $ Y% v. = = = = = = = = = = = = = @; , u + F }. +; l@ = = = = = = = = = = = = - p% 0& #; v. = = = = = = = = = = = = = i= $; I+ }. z , %; = = = = = = = = = = = = = = = b& S$ &; *; = = = = = = = = = = = = = = = *& A }. }. # p% = = = = = = = = = = = = = = = V% % % % Y% =; ^= = = = = = = = = = = = = `* B= -; ;; v. = = = = = = = = = = = = = >; =; p- L& L& J= <. = = = = = = = = = = = = = = = J+ ,; G= m@ -@ = = = = = = = = = = = = = = '; H= a& p = = = = = = = = = = = = = = = = = = = = = %- ", +"= = = = = = = = = = = = = = U- |. P+ I+ # M= = = = = = = = = = = = = = = = ; a& ); Y* Y* X- = = = = = = = = = = = = = = J- Y* Y* Y* Y* ;+ Y% Y% q% 0= = = = = = = = = = = = = = = N= -- r = = = = = = = = = = = ;$ !; J= J= J= p- ,- ,- ~; = = = = = = = = = = = = = = = >+ $ > Y% }$ v. = = = = = = = = = = = = = X% , @ y. u I+ 2- v. = = = = = = = = = = = = K= a& a& C& {; = = = = = = = = = = = = = v. n@ A }. }. G# ]; = = = = = = = = = = = = = = 2. ;= a& x- R* v. = = = = = = = = = = = = = = *& G# }. }. # p% = = = = = = = = = = = = = = = Z- L& ^; /; % U$ v. = = = = = = = = = = = = k- (; _; T- w$ = = = = = = = = = = = = = v. :; ,- J= M& % :. = = = = = = = = = = = = = = = <; a& -- % *; = = = = = = = = = = = = = = q- a& a& K+ Y* Y* h* = = = = = = = = = = = = = = v# Y* D* D* '; ", +"= = = = = = = = = = = = = = [; A I+ }. , }; = = = = = = = = = = = = = = = ; Z* q% q% a& |; = = = = = = = = = = = = = = /# q% Y% Y% Y% J= J= $ '- 0= = = = = = = = = = = = = = = N= -- $= = = = = = = = = = = = 1; V+ M& 2; p- J= J= p- <* = = = = = = = = = = = = = = = $+ !# Y% t B- v. = = = = = = = = = = = = = x. # z @ M+ , 3; = = = = = = = = = = = = = $- a& a& q% i* = = = = = = = = = = = = = = p% A }. }. A 2- = = = = = = = = = = = = = = s# r& a& a& a% -@ = = = = = = = = = = = = = = *& A I+ }. # 0- = = = = = = = = = = = = = = = G- q% 4; = O+ 5; = = = = = = = = = = = = = D& a& a& a& a* = = = = = = = = = = = = = = r* Y% p- J= % :. = = = = = = = = = = = = = = = 6; a& a& a& <; = = = = = = = = = = = = = = 7; a& q% q% Z* a& w$ = = = = = = = = = = = = = = 8; t > G# , ", +"= = = = = = = = = = = = = = [; A P+ }. , 9; = = = = = = = = = = = = = = = 0; Y% $ !# q% d& = = = = = = = = = = = = = = /# Y% A |. a; |. |. $ ,- 0= = = = = = = = = = = = = = = N= N+ q& = = = = = = = = = = = b; M& c% M& L& M& L& J= <* = = = = = = = = = = = = = = = z$ q% q% Z* c; v. = = = = = = = = = = = = = d; , z @ z I+ e; = = = = = = = = = = = = = f; Z* q% _* W- = = = = = = = = = = = = = = s- |. , , |. U% = = = = = = = = = = = = = = g; ,+ a& a& h; l@ = = = = = = = = = = = = = = *& A , I+ # 0- = = = = = = = = = = = = = = = i; >- p- L& G# j; = = = = = = = = = = = = = ;= a& k; a& A= = = = = = = = = = = = = = = ,; q% ,- p- % :. = = = = = = = = = = = = = = = r a& a& a& c& = = = = = = = = = = = = = = a* q% Y% Y% !# q% w$ = = = = = = = = = = = = = = o@ $ , z F ", +"= = = = = = = = = = = = = = [; A }. }. }. l; M- M- M- M- m; m; m; m; m; m; m; M- M- M- M- |; A A A $ n; = = = = = = = = = = = = = = /# % , I+ Q- Q- Q- J= ,- 0= = = = = = = = = = = = = = = o; b* 2. = = = = = = = = = = J+ p; G# M& M& q; )- M& p- c& = = = = = = = = = = = = = = = v$ a& _* a& q- v. = = = = = = = = = = = = l@ r; , }. M+ }. =+ Y$ = = = = = = = = = = = = = s; q% !# q% ^# = = = = = = = = = = = = = = 6; |. # # > '; = = = = = = = = = = = = = = -= % q% Z* N+ B = = = = = = = = = = = = = = *& A I+ }. G# p% = = = = = = = = = = = = = = = i; q% '- '- t; Y- = = = = = = = = = = = = = u; v; q% v; o@ = = = = = = = = = = = = = = 7 q% Y% p- % <. = = = = = = = = = = = = = = = w; a& Z* a& C= = = = = = = = = = = = = = = K+ !# > % > t w$ = = = = = = = = = = = = = = '# |. }. y. ' ", +"= = = = = = = = = = = = = = [; A }. u u z P+ , , , # # # # # # A # , , , P+ P+ I+ , % b% = = = = = = = = = = = = = = /# A z u I+ c% # M& Y% 0= = = = = = = = = = = = = = = D* X% v. = = = = = = = = = = J- M= M& M& )- x; x; )- p- c& = = = = = = = = = = = = = = = v$ Y% ;+ U$ V* = = = = = = = = = = = = = `* y; , M+ z , z; [$ = = = = = = = = = = = = = A; !# Y% t x$ = = = = = = = = = = = = = = b; % # # > B; = = = = = = = = = = = = = = y$ q% q% q% _* B = = = = = = = = = = = = = = *& A }. }. G# p% = = = = = = = = = = = = = = = G- q% '- q% z$ g; = = = = = = = = = = = = = #- q% '- v; V% = = = = = = = = = = = = = = C; q% Y% % G# <. = = = = = = = = = = = = = = = &; q% q% Z* D; = = = = = = = = = = = = = = i* !# > A % Y% w$ = = = = = = = = = = = = = = r # @ ,+ < ", +"= = = = = = = = = = = = = = q- # z + [. u u 9& u @ @ @ @ @ @ M+ @ @ @ u @ u @ @ }. |. b% = = = = = = = = = = = = = = /# , 9& [. E; = c% M& J= 0= = = = = = = = = = = = = = = && F; = = = = = = = = = = = F- J= c% )- c% x; x; M& J= c& = = = = = = = = = = = = = = = W% D* `* v. = = = = = = = = = = = = = = G; M+ I+ u @ , s V* = = = = = = = = = = = = = =+ Y% > Y% r* = = = = = = = = = = = = = = c* > G# # > B; = = = = = = = = = = = = = = y$ q% !# t q% B = = = = = = = = = = = = = = *& G# I+ , G# p% = = = = = = = = = = = = = = = G- q% '- '- )$ {; = = = = = = = = = = = = = C& '- Y% q% ; = = = = = = = = = = = = = = W% q% ,- % # :. = = = = = = = = = = = = = = = H; q% q% q% m@ = = = = = = = = = = = = = = I; $ A A |. $ w$ = = = = = = = = = = = = = = r I+ y. : _ ", +"= = = = = = = = = = = = = = [; # @ + ' ' . ' + ,+ ' L+ . ,+ + + ' ' ,+ ' ,+ ' ' + @ A b% = = = = = = = = = = = = = = /# , J; . F -& c% )- % 0= = = = = = = = = = = = = = = K; `* = = = = = = = = = = = L; J= -& = M; x; x; c% % <* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = N; G# }. y. F }. l; 6= = = = = = = = = = = = = = =+ O; > Y% o* = = = = = = = = = = = = = = P; > G# # % N- = = = = = = = = = = = = = = y$ q% !# !# q% B = = = = = = = = = = = = = = *& G# , , A p% = = = = = = = = = = = = = = = G- q% '- '- ^ d= = = = = = = = = = = = = = Q; Y% Y% q% &; = = = = = = = = = = = = = = R; '- Y% A A <. = = = = = = = = = = = = = = = S; q% !# q% q- = = = = = = = = = = = = = = T; > # , A > w$ = = = = = = = = = = = = = = x$ }. ' ! F# ", +"= = = = = = = = = = = = = = U- # @ + ' : : . ' L+ : : : ' [. ,+ L+ L+ : : L+ L+ ' + @ , A$ = = = = = = = = = = = = = = /# , J; L+ !$ = x; )- L& 0= = = = = = = = = = = = = = = V* v. = = = = = = = = = = = & L& U; V; U; M; M; -& L& a* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 8+ W; P+ u + y. z X; 8+ = = = = = = = = = = = = = 6. Y% > Y% o* = = = = = = = = = = = = = = q > G# # > N- = = = = = = = = = = = = = = y$ q% Y% !# q% B = = = = = = = = = = = = = = Y; A , I+ A p% = = = = = = = = = = = = = = = G- q% '- '- :. l@ = = = = = = = = = = = = = C& % Y% q% &; = = = = = = = = = = = = = = K; q% p- G# A <. = = = = = = = = = = = = = = = S$ !# Y% q% q- = = = = = = = = = = = = = = T; > # I+ , % w$ = = = = = = = = = = = = = = =$ @ L+ _ v ", +"= = = = = = = = = = = = = = U- A }. u ,+ ' ' ' ' L+ ' ' + + ,+ ' ' L+ : : : : L+ ' @ , b% = = = = = = = = = = = = = = /# , Z; : $+ = = )- L& 0= = = = = = = = = = = = = = = v. = = = = = = = = = = = p `; = > .> +> %& :* `% M& a* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = v. %+ , z @> W$ ' @ C- = = = = = = = = = = = = = = =+ !# > !# #> = = = = = = = = = = = = = = $> > A A > N- = = = = = = = = = = = = = = y$ !# Y% Y% q% B = = = = = = = = = = = = = = Y; A P+ I+ # p% = = = = = = = = = = = = = = = G- '- %> %> _. = = = = = = = = = = = = = = C& Y% q% q% U$ = = = = = = = = = = = = = = &> q% Y% # K& <. = = = = = = = = = = = = = = = S$ !# Y% t q- = = = = = = = = = = = = = = b& % # I+ , |. w$ = = = = = = = = = = = = = = t# F ) _ ( ", +"= = = = = = = = = = = = = = 6- > , }. @ @ @ @ @ u y. u z M+ @ u u y. + + ,+ ,+ ' ,+ @ A b% = = = = = = = = = = = = = = /# , Z; . E M; -& )- J= 0= = = = = = = = = = = = = = = = = = = = = = = = = = = Y$ *> /; => -> ;> > :* `% )- a* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = -@ I@ A; }. y. W$ z. : @ }; = = = = = = = = = = = = = = 5. !# !# !# m- = = = = = = = = = = = = = = >> Y% |. A > N- = = = = = = = = = = = = = = y$ !# Y% Y% t B = = = = = = = = = = = = = = Y; K& z I+ # 0- = = = = = = = = = = = = = = = G- Y% % p- _. = = = = = = = = = = = = = = C& q% q% q% ,> = = = = = = = = = = = = = = r= >- Y% # K& <. = = = = = = = = = = = = = = = '> t Y% !# q- = = = = = = = = = = = = = = b& % # , , |. w$ = = = = = = = = = = = = = = =$ + =+ v w ", +"= = = = = = = = = = = = = = 6- t > |. A A A A # # , , G# G# G# # # , }. }. M+ @ u u }. A b% = = = = = = = = = = = = = = /# , 9& . -+ `% -& )- L& 0= = = = = = = = = = = = = = = = = = = = = = = = = = = )> ^; U; !> .> ~> V+ [* `% )- a* = = = = = = = = = = = = = = = = = = = = = = = = = = = = v. v# {> F # u . V$ { ) u 9; = = = = = = = = = = = = = = 7. q% t Z* c= = = = = = = = = = = = = = = ]> $ % |. > B; = = = = = = = = = = = = = = y$ !# $ $ t B = = = = = = = = = = = = = = ^> , z M+ , /> = = = = = = = = = = = = = = = G- Y% p- '- _. = = = = = = = = = = = = = = (> q% q% q% _> = = = = = = = = = = = = = = :> >- % # A <. = = = = = = = = = = = = = = = <> !# Y% !# q- = = = = = = = = = = = = = = T; > A , A % w$ = = = = = = = = = = = = = = =$ F =+ v w ", +"= = = = = = = = = = = = = = [> a& q% Y* &= &= &= &= &= &= &= &= &= &= &= &= &= &= &= &= &= U- }. @ }. |. b% = = = = = = = = = = = = = = /# }. [. : : `% -& c% L& 0= = = = = = = = = = = = = = = = = = = = = = = = = = = B= O+ :* :* }> ~> )$ ~> %& )- a* = = = = = = = = = = = = = = = = = = = = = = = = = -@ |> A= 1> -+ q% $ P+ 2> #+ 3> 4> 5> ,+ 6> = = = = = = = = = = = = = = ;+ a& _* t K+ = = = = = = = = = = = = = = g* $ A A > B; = = = = = = = = = = = = = = y$ !# $ $ t B = = = = = = = = = = = = = = ^> , z @ , U$ = = = = = = = = = = = = = = = G- Y% p- p- 7> = = = = = = = = = = = = = = 8> k; q% q% 8- = = = = = = = = = = = = = = s# q% 9> G# A :. = = = = = = = = = = = = = = = <> !# Y% !# q- = = = = = = = = = = = = = = b& % # , A > w$ = = = = = = = = = = = = = = =$ F ! b+ 0> ", +"= = = = = = = = = = = = = = [> x- a& >> = = = = = = = = = = = = = = = = = 3= # }. I+ |. b% = = = = = = = = = = = = = = /# I+ + . : :* U; U; % 0= = = = = = = = = = = = = = = = = = = = = = = = = = J+ a> c% b> > c> .; c> ~> %& = a* = = = = = = = = = = = = = = = = = = = = = = = = = v. q& d- F= q% t > P+ 2> . d> e> 3> : 7- = = = = = = = = = = = = = = ;+ a& a& -$ Q= = = = = = = = = = = = = = = [$ > A G# % z& = = = = = = = = = = = = = = y$ !# $ $ t B = = = = = = = = = = = = = = ^> , M+ @ , U$ = = = = = = = = = = = = = = = G- p- p- 4; 7> = = = = = = = = = = = = = = 8> a& a& f> i= = = = = = = = = = = = = = = {# q% % A |. :. = = = = = = = = = = = = = = = <> !# Y% !# q- = = = = = = = = = = = = = = b& |. P+ I+ A > w$ = = = = = = = = = = = = = = =$ @ : &$ g> ", +"= = = = = = = = = = = = = = [> -- -- >> = = = = = = = = = = = = = = = = = 3; A , # > b% = = = = = = = = = = = = = = /# , + . ! ;+ N+ = J= 0= = = = = = = = = = = = = = = = = = = = = = = = = = F- `% N+ :* ~> h> j& ~> i> j> N+ a* = = = = = = = = = = = = = = = = = = = = = = = = = = = = 8+ &+ k> $ # u . 4> l> v =+ k* m> H& ); n* Q* p* p* /* k@ n> n> n> n> n> % O+ A- >; = = = = = = = = = = = = = = = o> $ A G# % N- = = = = = = = = = = = = = = y$ !# $ $ t B = = = = = = = = = = = = = = p% , @ @ , U$ = = = = = = = = = = = = = = = G- 4; L& p> > q> p* r> r> /* s> n> n> n> n> n> t> V% V% z$ % p* V* = = = = = = = = = = = = = = = Q= Y% > A A <. = = = = = = = = = = = = = = = <> !# !# !# q- = = = = = = = = = = = = = = b& |. }. }. A > w$ = = = = = = = = = = = = = = >$ z . { g> ", +"= = = = = = = = = = = = = = [> -- -- >> = = = = = = = = = = = = = = = = = 0; |. G# # > b% = = = = = = = = = = = = = = /# I+ 9& . ! : u> )- p- 0= = = = = = = = = = = = = = = = = = = = = = = = = = v> G# = %& )$ j& w> x> y> .> `% a* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = v. [$ z> # u : &$ b+ w v =+ : ' + u }. , |. $ q% _* a& a& -- q% b* <; -@ = = = = = = = = = = = = = = = = w. $ A A % N- = = = = = = = = = = = = = = y$ !# $ $ t B = = = = = = = = = = = = = = p% I+ F @ O+ U$ = = = = = = = = = = = = = = = G- L& A> )- = /; A> L& J= J= '- v; k; a& a& a& -- -- a& B> K+ 8+ = = = = = = = = = = = = = = = = Q= $ A # A <. = = = = = = = = = = = = = = = <> !# !# t q- = = = = = = = = = = = = = = b& |. }. I+ # % w$ = = = = = = = = = = = = = = x$ z ' _ x ", +"= = = = = = = = = = = = = = [> -- -- >> = = = = = = = = = = = = = = = = = 0; % A # > b% = = = = = = = = = = = = = = /# , 9& . _ ! v$ /; p- 0= = = = = = = = = = = = = = = = = = = = = = = = = = *; z$ = %& }> <$ h> C> -> D> U; a* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = E> F> z ' { b+ b+ w [ &$ { : ' u }. , |. $ q% a& a& G> H> h* = = = = = = = = = = = = = = = = = = 0% % A A % N- = = = = = = = = = = = = = = y$ !# $ $ !# B = = = = = = = = = = = = = = p% I+ @ @ O+ U$ = = = = = = = = = = = = = = = I> p> )- /; )- /; = = )- L& p- '- k; a& a& -- -- a> )> J- = = = = = = = = = = = = = = = = = = ;$ Y% A # |. <. = = = = = = = = = = = = = = = <> !# !# q% q- = = = = = = = = = = = = = = b& |. , I+ , |. w$ = = = = = = = = = = = = = = x$ @ ,+ ~ x ", +"= = = = = = = = = = = = = = [> -- -- >> = = = = = = = = = = = = = = = = = 0; % A # % b% = = = = = = = = = = = = = = /# I+ + ) _ ] v$ ^; J= 0= = = = = = = = = = = = = = = = = = = = = = = = = = v. J> `% > V+ <$ h> K> w> i> U; a* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = v. L> z + ! b+ 0+ y g@ x &$ z. ' y. M+ , |. Y% Y% M> L; ^= = = = = = = = = = = = = = = = = = = = = {# % G# G# % N- = = = = = = = = = = = = = = y$ !# > $ !# B = = = = = = = = = = = = = = ^> , @ M+ , U$ = = = = = = = = = = = = = = = I> 4; N> = = O> O> b> )- M& p- '- k; a& a& #- 9+ >; = = = = = = = = = = = = = = = = = = = = l@ $ A , |. <. = = = = = = = = = = = = = = = <> !# Y% t q- = = = = = = = = = = = = = = b& |. , I+ , |. w$ = = = = = = = = = = = = = = x$ @ . ~ w ", +"= = = = = = = = = = = = = = [> -- -- >> = = = = = = = = = = = = = = = = = 0; |. G# , > b% = = = = = = = = = = = = = = /# I+ ' _ F# ( $+ G# J= 0= = = = = = = = = = = = = = = = = = = = = = = = = = = P> -& %& V+ D# {+ Q> k& }> v$ I@ = = = = = = = = = = = = = = = $- u& z& @= v. = = = = = = = = = = = = -@ R> u z. '@ S> f@ f@ a+ v ! ' F }. A |. V- )> {# = = = = = = = = = = = = = = = = = = = = = = {# q% A T> % N- = = = = = = = = = = = = = = y$ !# > $ !# B = = = = = = = = = = = = = = p% O+ @ M+ , /> = = = = = = = = = = = = = = = V% % )- U; b> U> +> => )- % Y% '- q% V> m@ P= v. = = = = = = = = = = = = = = = = = = = = = l@ > # , K& <. = = = = = = = = = = = = = = = <> !# Y% t q- = = = = = = = = = = = = = = b& |. , }. , |. w$ = = = = = = = = = = = = = = x$ 9& ) 4> w ", +"= = = = = = = = = = = = = = [> -- -- >> = = = = = = = = = = = = = = = = = 0; % # , % b% = = = = = = = = = = = = = = /# z : ] [ g> D )- J= 0= = = = = = = = = = = = = = = = = = = = = = = = = = = #* c% b> D# ;- ;- k& h> V+ v$ I@ = = = = = = = = = = = = = = = v$ a& _* r& F- = = = = = = = = = = = = = p& u W$ W> 0+ f@ f@ a+ v 7. + M+ , C& c= p = = = = = = = = = = = = = = = = = = = = = = = = {# q% Y% % % N- = = = = = = = = = = = = = = y$ !# > > !# B = = = = = = = = = = = = = = p% I+ F @ O+ U$ = = = = = = = = = = = = = = = V% % )- U; U; !> U; = L& Y% '- X> Y> >; = = = = = = = = = = = = = = = = = = = = = = = = l@ > , I+ # <. = = = = = = = = = = = = = = = :; Y% Y% !# q- = = = = = = = = = = = = = = b& |. , }. I+ |. w$ = = = = = = = = = = = = = = Z> 9& 3> `> y ", +"= = = = = = = = = = = = = = [> -- -- >> = = = = = = = = = = = = = = = = = 0; > A , |. b% = = = = = = = = = = = = = = /# z : _ w b+ _ # Y% 0= = = = = = = = = = = = = = = = = = = = = = = = = = = #; O+ %& <$ , ., +, j& )$ `% a* = = = = = = = = = = = = = = = z$ q% !# q% Y> v. = = = = = = = = = = = = * @, @> { [ g@ a+ [ _ ' @ 7. V% :> v. = = = = = = = = = = = = = = = = = = = = = = = = = {# q% '- ,- q% z& = = = = = = = = = = = = = = y$ !# > > Y% B = = = = = = = = = = = = = = 0- }. F F }. U$ = = = = = = = = = = = = = = = V% )- = -& => U; = O+ % U; #, @= v. = = = = = = = = = = = = = = = = = = = = = = = = = l@ % # }. P+ }$ = = = = = = = = = = = = = = = :; Y% Y% !# q- = = = = = = = = = = = = = = b& % , }. , |. w$ = = = = = = = = = = = = = = x$ 9& ~ /. $, ", +"= = = = = = = = = = = = = = [> -- -- >> = = = = = = = = = = = = = = = = = 0; > # , |. b% = = = = = = = = = = = = = = /# }. L+ ] /. ( v z Y% 0= = = = = = = = = = = = = = = = = = = = = = = = = = = @= %, z$ j& 7> &, 7> n& += %& a* = = = = = = = = = = = = = = = >+ Y% > Y% H; v. = = = = = = = = = = = = v. *, + =+ [ /. F# _ L+ @ !; =, -@ = = = = = = = = = = = = = = = = = = = = = = = = = = = {# q% Y% Y% q% z& = = = = = = = = = = = = = = y$ Y% > > Y% B = = = = = = = = = = = = = = 0- }. y. y. }. c+ = = = = = = = = = = = = = = = s> N+ c% c% = = /; ^; -, c* 8+ = = = = = = = = = = = = = = = = = = = = = = = = = = = l@ > # z P+ }$ = = = = = = = = = = = = = = = :; Y% $ Y% q- = = = = = = = = = = = = = = b& |. , }. , |. w$ = = = = = = = = = = = = = = =$ r- V$ g> ;, ", +"= = = = = = = = = = = = = = [> x- a& >> = = = = = = = = = = = = = = = = = 0; > , O+ % b% = = = = = = = = = = = = = = /# @ : v F# F# v + Y% 0= = = = = = = = = = = = = = = = = = = = = = = = = = = h* f> > h> k& {+ {+ h> += :* a* = = = = = = = = = = = = = = = ;+ |. G# % +; v. = = = = = = = = = = = = = A& y. ! v _ ! L+ y. ~- I- v. = = = = = = = = = = = = = 6= = = = = = = = = = = = = = = {# q% Y% p- q% z& = = = = = = = = = = = = = = y$ Y% > $ Y% B = = = = = = = = = = = = = = /> }. y. y. M+ >, = = = = = = = = = = = = = = = a- = N+ = = = )- n- v# v. = = = = = = = = = = = = = 6= = = = = = = = = = = = = = = l@ > , z P+ }$ = = = = = = = = = = = = = = = '> !# > Y% q- = = = = = = = = = = = = = = b& |. P+ }. , % w$ = = = = = = = = = = = = = = =$ r- V$ w g@ ", +"= = = = = = = = = = = = = = [> a& Z* ,, 9+ 9+ w. = = = = = = = = = = = = = = 0; |. , O+ % b% = = = = = = = = = = = = = = /# @ : _ ] w v : Y% 0= = = = = = = = = = = = = = = = = = = = = = = = = = = = B= :* .; h> +, {+ h> <$ z$ a* = = = = = = = = = = = = = = = ;+ G# # |. ', v. = = = = = = = = = = = = = H& F : ! ! L+ y. *- ), = = = = = = = = = = = = = = 3- !, = = = = = = = = = = = = = = {# q% Y% Y% q% z& = = = = = = = = = = = = = = y$ Y% % % Y% B = = = = = = = = = = = = = = 0- }. y. y. }. c+ = = = = = = = = = = = = = = = a- O+ `% = )- ^; T* v# v. = = = = = = = = = = = = = ~, b% = = = = = = = = = = = = = = l@ > # z P+ }$ = = = = = = = = = = = = = = = :; !# > Y% q- = = = = = = = = = = = = = = b& % , }. , A w$ = = = = = = = = = = = = = = =$ r- V$ x T$ ", +"= = = = = = = = = = = = = = {, q% !# !# !# q% q = = = = = = = = = = = = = = 0; A P+ O+ |. b% = = = = = = = = = = = = = = /# @ : ] [ a+ [ ! |. 0= = = = = = = = = = = = = = = = = = = = = = = = = = = = ], z$ <$ +, Q> ^, h> c> y. p& = = = = = = = = = = = = = = = 7. # , A }$ v. = = = = = = = = = = = = = /, @ j@ : L+ y. }. i* = = = = = = = = = = = = = = I- B- n> = = = = = = = = = = = = = = {# q% Y% Y% q% z& = = = = = = = = = = = = = = y$ Y% |. % Y% B = = = = = = = = = = = = = = 0- }. F + M+ >, = = = = = = = = = = = = = = = t> O+ b> U; )- 4; c& = = = = = = = = = = = = = = w$ *- (, = = = = = = = = = = = = = = l@ |. G# }. , }$ = = = = = = = = = = = = = = = :; O; > !# q- = = = = = = = = = = = = = = T; > , z P+ |. w$ = = = = = = = = = = = = = = t# u z. `> _, ", +"= = = = = = = = = = = = = = 6- Y% % |. > !# q = = = = = = = = = = = = = = 3; A P+ , A A$ = = = = = = = = = = = = = = /# @ 7. F# a+ a+ a+ 7. O+ 0= = = = = = = = = = = = = = = B = = = = = = = = = = = = 6 `% j> z- {+ k& z- 5. M+ :, = = = = = = = = = = = = = = = u# , O+ # N+ v. = = = = = = = = = = = = = '; @ ,+ L+ ,+ @ <, 2. = = = = = = = = = = = = = d= $- -- n> = = = = = = = = = = = = = = {# q% ,- ,- ,- z& = = = = = = = = = = = = = = y$ Y% % % Y% B = = = = = = = = = = = = = = /> }. y. y. M+ c+ = = = = = = = = = = = = = = = a- = b> t; )- f; p = = = = = = = = = = = = = 2. :; -- (, = = = = = = = = = = = = = = l@ |. , }. , }$ = = = = = = = = = = = = = = = :; > > !# q- = = = = = = = = = = = = = = T; % P+ z I+ A w$ = = = = = = = = = = = = = = t# u #+ l> [, ", +"= = = = = = = = = = = = = = 6- > A # |. Y% q = = = = = = = = = = = = = = 0; |. # , A b% = = = = = = = = = = = = = = /# @ 7. F# a+ [ [ 7. M+ 0= = = = = = = = = = = = = = = /# -@ = = = = = = = = = = = V* += :* += {+ }, G$ j@ @ I@ = = = = = = = = = = = = = = = u# , M+ I+ G# v. = = = = = = = = = = = = = G& M+ ,+ ' + }. |, v. = = = = = = = = = = = = = Y> -- -- n> = = = = = = = = = = = = = = {# '- '- Y% Y% z& = = = = = = = = = = = = = = y$ Y% % % Y% B = = = = = = = = = = = = = = 0- M+ + y. z c+ = = = = = = = = = = = = = = = n> O+ U; t; )- #> v. = = = = = = = = = = = = = 1, -- -- (, = = = = = = = = = = = = = = l@ % O+ @ , }$ = = = = = = = = = = = = = = = :; > > !# q- = = = = = = = = = = = = = = T; > P+ z I+ A w$ = = = = = = = = = = = = = = t# u #+ 2, 3, ", +"= = = = = = = = = = = = = = 6- % I+ I+ # |. 4, = = = = = = = = = = = = = = 0; |. # I+ A A$ = = = = = = = = = = = = = = /# @ : v a+ a+ F# 7. F 5, = = = = = = = = = = = = = = |> - = = = = = = = = = = = 2. 6, %& ~> z- G$ / j@ + I@ = = = = = = = = = = = = = = = =+ I+ u }. # v. = = = = = = = = = = = = = G& }. $+ j@ $+ }. 7, = = = = = = = = = = = = = I- *+ a& a& n> = = = = = = = = = = = = = = {# q% '- ,- ,- z& = = = = = = = = = = = = = = y$ Y% % % $ B = = = = = = = = = = = = = = 0- z + + z c+ = = = = = = = = = = = = = = = n> = U; N+ % 5- = = = = = = = = = = = = = q& 8, -- _; (, = = = = = = = = = = = = = = l@ |. I+ M+ , }$ = = = = = = = = = = = = = = = :; > > !# q- = = = = = = = = = = = = = = T; > P+ z P+ |. w$ = = = = = = = = = = = = = = x$ 9, 0, a, [, ", +"= = = = = = = = = = = = = = [; A }. z P+ |. Y* = = = = = = = = = = = = = = 0; |. P+ I+ A A$ = = = = = = = = = = = = = = /# @ : v x g@ [ ! F 6 = = = = = = = = = = = = = = && Y* = = = = = = = = = = = v. r* %& V+ += G$ G$ j@ : I@ = = = = = = = = = = = = = = = =+ }. u z # v. = = = = = = = = = = = = = b, }. $+ j@ $+ =+ 1; = = = = = = = = = = = = = 4= a& a& a& n> = = = = = = = = = = = = = = {# q% '- Y% Y% z& = = = = = = = = = = = = = = y$ Y% % % > B = = = = = = = = = = = = = = /> M+ $+ + M+ c+ = = = = = = = = = = = = = = = n> c% N+ U; N+ 6 = = = = = = = = = = = = = c& a& a& a& (, = = = = = = = = = = = = = = l@ |. , }. , }$ = = = = = = = = = = = = = = = :; $ > !# q- = = = = = = = = = = = = = = T; > P+ }. P+ |. w$ = = = = = = = = = = = = = = x$ 9& c, a, [, ", +"= = = = = = = = = = = = = = [; A }. z P+ K& Y* = = = = = = = = = = = = = = 0; A P+ I+ A A$ = = = = = = = = = = = = = = d, F ! v a+ g@ [ ! y. 6 = = = = = = = = = = = = = = N= 0; = = = = = = = = = = = = L; %& )$ )$ $& $& $& j@ e, = = = = = = = = = = = = = = = =+ z F z # v. = = = = = = = = = = = = = f, }. $+ ,+ @ g, w. = = = = = = = = = = = = = h, a& q% q% /* = = = = = = = = = = = = = = {# q% q% ,- Y% z& = = = = = = = = = = = = = = y$ Y% % % $ B = = = = = = = = = = = = = = /> z + ' M+ >, = = = = = = = = = = = = = = = a- O+ v$ v$ ^ ~, = = = = = = = = = = = = = m- a& k; a& (, = = = = = = = = = = = = = = l@ A , }. , }$ = = = = = = = = = = = = = = = :; $ > Y% q- = = = = = = = = = = = = = = T; % # I+ P+ |. w$ = = = = = = = = = = = = = = x$ 9, 0, a, [, ", +"= = = = = = = = = = = = = = U- A }. @ }. A Y* = = = = = = = = = = = = = = 0; A P+ I+ A A$ = = = = = = = = = = = = = = i, y. ! [ f@ M@ '@ _ y. 6 = = = = = = = = = = = = = = N= K= = = = = = = = = = = = = Y- )$ [* j, t@ u# 9. : I@ = = = = = = = = = = = = = = = u# M+ F M+ # v. = = = = = = = = = = = = = f, O+ z$ z$ N+ C& >; = = = = = = = = = = = = = k, k; q% q% /* = = = = = = = = = = = = = = {# q% q% ,- Y% z& = = = = = = = = = = = = = = y$ Y% % % Y% B = = = = = = = = = = = = = = /> z + $+ u >, = = = = = = = = = = = = = = = t> -& z$ U; ;- {# = = = = = = = = = = = = = l, q% '- q% &; = = = = = = = = = = = = = = l@ A O+ }. O+ (. = = = = = = = = = = = = = = = :; $ > Y% q- = = = = = = = = = = = = = = b& A , I+ P+ |. w$ = = = = = = = = = = = = = = x$ u z. W> S> ", +"= = = = = = = = = = = = = = [; A }. @ }. A Y* = = = = = = = = = = = = = = 0; |. I+ z A b% = = = = = = = = = = = = = = i, y. ! [ L@ m, S> _ F 6 = = = = = = = = = = = = = = N= %= 8+ = = = = = = = = = = = {# -, `% h& $& -+ / ' I@ = = = = = = = = = = = = = = = u# }. F M+ # v. = = = = = = = = = = = = = q- O+ v$ z$ N+ J> h* = = = = = = = = = = = = = n- q% Y% ,- /* = = = = = = = = = = = = = = {# q% q% Y% Y% z& = = = = = = = = = = = = = = y$ Y% % % Y% B = = = = = = = = = = = = = = /> z $+ + @ & = = = = = = = = = = = = = = = a- O+ z$ N+ &, ;$ = = = = = = = = = = = = = n, %> Y% q% &; = = = = = = = = = = = = = = l@ G# I+ @ O+ (. = = = = = = = = = = = = = = = :; > > Y% q- = = = = = = = = = = = = = = b& A }. }. P+ |. w$ = = = = = = = = = = = = = = x$ u #+ o, g> ", +"= = = = = = = = = = = = = = [; # }. M+ }. A Y* = = = = = = = = = = = = = = 3; A }. I+ A b% = = = = = = = = = = = = = = i, y. =+ a+ ;, m, 0+ V$ u 6 = = = = = = = = = = = = = = N= V> ^= = = = = = = = = = = = = 6, c% -& I+ y. #$ ' I@ = = = = = = = = = = = = = = = _ M+ + M+ # v. = = = = = = = = = = = = = q- O+ v$ z$ b> p, = = = = = = = = = = = = = = C& q% Y% Y% q, = = = = = = = = = = = = = = {# q% '- p- p- z& = = = = = = = = = = = = = = y$ Y% % % Y% B = = = = = = = = = = = = = = /> z + $+ z c+ = = = = = = = = = = = = = = = n> O+ b> `% h; = = = = = = = = = = = = = = r, Y% 4; 4; s, = = = = = = = = = = = = = = l@ G# I+ @ I+ (. = = = = = = = = = = = = = = = :; Y% > Y% q- = = = = = = = = = = = = = = b& G# M+ z P+ |. w$ = = = = = = = = = = = = = = =$ u z. o, 0> ", +"= = = = = = = = = = = = = = [; # }. }. , A Y* = = = = = = = = = = = = = = 0; A I+ I+ G# ,$ = = = = = = = = = = = = = = d, @ ! '@ ;, t, 0+ V$ + 6 = = = = = = = = = = = = = = N= a& w. = = = = = = = = = = = = v& L& t !# : _ ' I@ = = = = = = = = = = = = = = = / @ + @ # v. = = = = = = = = = = = = = q- O+ v$ b> N+ T* = = = = = = = = = = = = = = u, ,- p- Y% q, = = = = = = = = = = = = = = {# q% ,- Y% Y% z& = = = = = = = = = = = = = = y$ q% % % Y% B = = = = = = = = = = = = = = /> M+ + + z c+ = = = = = = = = = = = = = = = n> = b> `% h; = = = = = = = = = = = = = = a> Y% p- Y% v, = = = = = = = = = = = = = = l@ # }. @ }. (. = = = = = = = = = = = = = = = :; > > Y% q- = = = = = = = = = = = = = = b& A M+ z I+ |. w$ = = = = = = = = = = = = = = t# F : v M@ ", +"= = = = = = = = = = = = = = w, A P+ P+ , N+ G; = = = = = = = = = = = = = = 0; A }. }. G# b% = = = = = = = = = = = = = = /# @ z. '@ m, t, 0+ V$ $+ 6 = = = = = = = = = = = = = = N= -- #; = = = = = = = = = = = = H> a& a& x, ! =+ + I@ = = = = = = = = = = = = = = = / u [. Q+ # v. = = = = = = = = = = = = = q- O+ v$ b> N+ y, = = = = = = = = = = = = = = <$ '- Y% Y% q, = = = = = = = = = = = = = = {# q% ,- p- Y% z& = = = = = = = = = = = = = = y$ q% Y% > O; B = = = = = = = = = = = = = = /> @ + + M+ >, = = = = = = = = = = = = = = = n> -& %& N+ h; = = = = = = = = = = = = = = V> Y% p- Y% v, = = = = = = = = = = = = = = l@ # I+ M+ O+ (. = = = = = = = = = = = = = = = :; $ > Y% q- = = = = = = = = = = = = = = b& A }. E; P+ |. w$ = = = = = = = = = = = = = = =$ y. 7. F# f@ ", +"= = = = = = = = = = = = = = L; |. # , , ,+ |> = = = = = = = = = = = = = = 0; G# }. M+ G# b% = = = = = = = = = = = = = = /# M+ ! 0+ m, M@ '@ V$ + 6 = = = = = = = = = = = = = = =, -- q- = = = = = = = = = = = = .% Y% a& K& ! 7. + I@ = = = = = = = = = = = = = = = / u [. @ # v. = = = = = = = = = = = = = q- c% `% b> z, B- = = = = = = = = = = = = = = 6. '- Y% ,- q, = = = = = = = = = = = = = = {# ,- Y% % 4; A, = = = = = = = = = = = = = = y$ q% q% Y% Y% B = = = = = = = = = = = = = = /> u E ' u >, = = = = = = = = = = = = = = = n> = b> N+ ., = = = = = = = = = = = = = = V> p- % Y% v, = = = = = = = = = = = = = = l@ # I+ M+ , }$ = = = = = = = = = = = = = = = :; > > Y% B, = = = = = = = = = = = = = = b& G# }. u P+ |. w$ = = = = = = = = = = = = = = =$ F ! F# f@ ", +"= = = = = = = = = = = = = = d- % A # G# ^ C, = = = = = = = = = = = = = = 3; G# I+ }. A b% = = = = = = = = = = = = = = i, u V$ M@ M@ S> '@ ! u 6 = = = = = = = = = = = = = = 8- -- /* = = = = = = = = = = = = i= V> Z* D, ! 7. F I@ = = = = = = = = = = = = = = = / @ + @ , v. = = = = = = = = = = = = = E, O+ %& :* F, T* = = = = = = = = = = = = = = 6. Y% Y% q% /* = = = = = = = = = = = = = = {# '- p- L& p- z& = = = = = = = = = = = = = = y$ q% %> ,- ,- B = = = = = = = = = = = = = = /> @ ' E F & = = = = = = = = = = = = = = = n> O+ v$ N+ %, = = = = = = = = = = = = = = V> 4; 4; %> &; = = = = = = = = = = = = = = l@ # z @ I+ }$ = = = = = = = = = = = = = = = :; $ % $ f, = = = = = = = = = = = = = = b& G# }. @ }. A w$ = = = = = = = = = = = = = = =$ @ L+ [ f@ ", +"= = = = = = = = = = = = = = i* Y% % A % !- * = = = = = = = = = = = = = = 3; G# I+ }. A G, = = = = = = = = = = = = = = X- _ D '@ 0+ 0+ b+ : M+ 6 = = = = = = = = = = = = = = 8- -- F= J+ = = = = = = = = = = = -@ %= q% u ! : u :, = = = = = = = = = = = = = = = #$ @ + Q+ , v. = = = = = = = = = = = = = q- O+ %& > U; y, = = = = = = = = = = = = = = H, Y% ,- q% R* = = = = = = = = = = = = = = {# q% p- % Y% z& = = = = = = = = = = = = = = y$ q% '- '- 5. B = = = = = = = = = = = = = = U$ F ' : F & = = = = = = = = = = = = = = = V% O+ %& `% h; = = = = = = = = = = = = = = f; Y% p- %> I, = = = = = = = = = = = = = = l@ # M+ F }. (. = = = = = = = = = = = = = = = :; $ % Y% B, = = = = = = = = = = = = = = b& G# }. @ }. A J, = = = = = = = = = = = = = = '; }. ,+ _ a+ ", +"= = = = = = = = = = = = = = F- !# Y% > Y% r, -@ = = = = = = = = = = = = = = 3; G# I+ }. , =$ = = = = = = = = = = = = = = *; K, : &$ '@ a+ b+ : M+ 6 = = = = = = = = = = = = = = 8- -- C& {# = = = = = = = = = = = = U% t + : ' @ :, = = = = = = = = = = = = = = = / @ + @ , v. = = = = = = = = = = = = = 1, -& b> > U; p, = = = = = = = = = = = = = = L, ,- ,- q% r = = = = = = = = = = = = = = {# '- p- p- %> M, = = = = = = = = = = = = = = g* O+ q% q% %, l@ = = = = = = = = = = = = = = U$ u ' ' @ >, = = = = = = = = = = = = = = = V% O+ v$ U; h; = = = = = = = = = = = = = = N, '- Y% q% V% = = = = = = = = = = = = = = l@ # M+ F }. K, = = = = = = = = = = = = = = = :; $ > Y% q- = = = = = = = = = = = = = = b& # M+ @ I+ G# v# = = = = = = = = = = = = = = z& }. y. : v ", +"= = = = = = = = = = = = = = p a> q% q% Z* s- = = = = = = = = = = = = = = = 3= # M+ @ O+ O, = = = = = = = = = = = = = = s# s + ) ( ( ~ . }. 5, = = = = = = = = = = = = = = 8- -- v$ -= = = = = = = = = = = = = 8- $ + E ' @ :, = = = = = = = = = = = = = = = / @ ,+ F P+ v. = = = = = = = = = = = = = 1, U; > %& N+ M= = = = = = = = = = = = = = = b* q% q% a& ^# = = = = = = = = = = = = = = {# ,- Y% p- Y% X% = = = = = = = = = = = = = = @= 6. q% >- G> 8+ = = = = = = = = = = = = = = U$ F ' ' M+ >, = = = = = = = = = = = = = = = V% I+ z$ U; ., = = = = = = = = = = = = = = P, q% '- q% o@ = = = = = = = = = = = = = = l@ # z F z K, = = = = = = = = = = = = = = = Q, % % Y% q- = = = = = = = = = = = = = = b& # M+ @ }. G# -= = = = = = = = = = = = = = = B, , @ ' ) ", +"= = = = = = = = = = = = = = v. R, S, A T, q& = = = = = = = = = = = = = = = 3; # @ F I+ /* = = = = = = = = = = = = = = >; U, z [. #+ . ' 9& , 0= = = = = = = = = = = = = = = 8- -- -- F; = = = = = = = = = = = = X- $ F ' + z p& = = = = = = = = = = = = = = = #$ u ' y. I+ v. = = = = = = = = = = = = = E, = b> b> U; J> ;$ = = = = = = = = = = = = = V, a& a& a& 7 = = = = = = = = = = = = = = {# ,- Y% Y% '- r* = = = = = = = = = = = = = = 0% V> a& a& 0& J+ = = = = = = = = = = = = = = U$ F E E F G = = = = = = = = = = = = = = = V% O+ > N+ *+ 6= = = = = = = = = = = = = = K= a& v; a& P- = = = = = = = = = = = = = = l@ # M+ F z K, = = = = = = = = = = = = = = = W, % A $ f, = = = = = = = = = = = = = = b& K& z u M+ # >> = = = = = = = = = = = = = = c& % I+ @ + ", +"h* = = = = = = = = = = = = = = v. w$ v# d= = = = = = = = = = = = = = = = = 3= , @ F I+ Y; = = = = = = = = = = = = = = J+ A- <. ' 9& Q+ Q+ }. A 0= = = = = = = = = = = = = = = 8- -- -- 6; = = = = = = = = = = = = -@ X, u $+ F }. p& = = = = = = = = = = = = = = = #$ F j@ F I+ v. = = = = = = = = = = = = = q- O+ `% %& v$ Q; &= = = = = = = = = = = = = = c* Y% a& O+ G; = = = = = = = = = = = = = = {# Y% p- J= ,- Y, = = = = = = = = = = = = = = 8+ :; a& a& Y> v. = = = = = = = = = = = = = = U$ y. : : + G = = = = = = = = = = = = = = = Z, , >+ U; u, i= = = = = = = = = = = = = = F; Y% a& % i* = = = = = = = = = = = = = = l@ , @ F z K, = = = = = = = = = = = = = = = W, % A > f, = = = = = = = = = = = = = = b& K& z @ }. # 0= = = = = = = = = = = = = = = X- M> -+ @ , ", +"v> = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = b; , @ F }. 9- = = = = = = = = = = = = = = = v. - b& i* 7 K& A $ 0= = = = = = = = = = = = = = = `, -- -- & = = = = = = = = = = = = v. D& }. F F }. p& = = = = = = = = = = = = = = = #$ + L+ y. }. v. = = = = = = = = = = = = = E, c% v$ %& `% , ' = = = = = = = = = = = = = 2. m@ s, .' 8+ = = = = = = = = = = = = = = {# '- p- J= Y% k, = = = = = = = = = = = = = = = F- +' @' >; = = = = = = = = = = = = = = = U$ y. : : [. H# = = = = = = = = = = = = = = = Z, G# z$ `% ^ g; = = = = = = = = = = = = = l@ L; s, m@ l@ = = = = = = = = = = = = = = l@ , Q+ y. z K, = = = = = = = = = = = = = = = Q, > |. > f, = = = = = = = = = = = = = = b& , u @ M+ , #' = = = = = = = = = = = = = = = J- .% Y* K+ ", +"V, J+ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = b; I+ y. y. M+ f= = = = = = = = = = = = = = = = = = = = .% > > t 0= = = = = = = = = = = = = = = 4= -- -- $' = = = = = = = = = = = = v. @; # }. M+ , a* = = = = = = = = = = = = = = = %$ + : y. }. v. = = = = = = = = = = = = = q- O+ %& %& %& )$ D= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = {# '- p- % % 7= v. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = >, y. ! ! ' H# = = = = = = = = = = = = = = = n> G# z$ v$ >+ 6 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = l@ , Q+ y. z K, = = = = = = = = = = = = = = = Q, % |. > f, = = = = = = = = = = = = = = b& O+ F y. u O+ d- = = = = = = = = = = = = = = = = = = = ", +"T- @= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = b; M+ ,+ ,+ u %' -@ = = = = = = = = = = = = = = = = = = .% !# !# q% 0= = = = = = = = = = = = = = = 4= -- a& N+ B = = = = = = = = = = = = F; A , I+ A a* = = = = = = = = = = = = = = = <. ,+ : [. z v. = = = = = = = = = = = = = 1, b> y> i> %& c% =, = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = {# ,- M& M& % 8, &= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = >, + u# _ . &' = = = = = = = = = = = = = = = n> # :* :* N+ %- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = l@ , Q+ u }. a+ = = = = = = = = = = = = = = = W, % A % 1, = = = = = = = = = = = = = = b& I+ y. $+ + }. x& = = = = = = = = = = = = = = = = = = = ", +"!# =$ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = b; i@ . L+ + K, X- = = = = = = = = = = = = = = = = = = Y$ q% q% Z* *' = = = = = = = = = = = = = = 4= a& a& a& 3- = = = = = = = = = = = = v# K, # # % a* = = = = = = = = = = = = = = = <. ' ! ' M+ v. = = = = = = = = = = = = = q- M& U; %& -& )- ^# v. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = {# J= L& L& % >+ >> = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = >, ,+ ! _ ' H# = = = = = = = = = = = = = = = n> I+ F V+ N+ o@ v. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = l@ , @ y. M+ K, = = = = = = = = = = = = = = = W, % G# % 1, = = = = = = = = = = = = = = b& I+ y. ,+ ,+ @ /> = = = = = = = = = = = = = = = = = = = ", +"K& A; y$ = = = = = = = = = = = = = = = = = = = = = 8+ = = = = = = = = = = b; @ . 7. j@ F C* v. = = = = = = = = = = = = = = = = = Y$ Z* q% q% 0= = = = = = = = = = = = = = = 4= a& q% v; =' = = = = = = = = = = = = ;$ -' |. A $ a* = = = = = = = = = = = = = = = [ ' ) ' }. v. = = = = = = = = = = = = = q- p- O+ -& -& c% ;' 8+ = = = = = = = = = = = = = = = = 8+ v. = = = = = = = = = = = = = {# % G# G# L& L& >' v. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ,' . { _ . H# = = = = = = = = = = = = = = = '' I+ v$ -+ `% ;= ;$ = = = = = = = = = = = = = = = = -@ v. = = = = = = = = = = = = = l@ , M+ + M+ s& = = = = = = = = = = = = = = = W, % , % 1, = = = = = = = = = = = = = = b& }. y. j@ j@ y. %' i= = = = = = = = = = = = = = = = = = = ", +"M+ , k, 6= = = = = = = = = = = = = = = = = = = = q& )' = = = = = = = = = = !' 9& ~' =+ 7. $+ {' J- = = = = = = = = = = = = = = = = = Y$ q% q% q% 0= = = = = = = = = = = = = = = ]' q% %> q% y- = = = = = = = = = = = = = =- $ % Y% c& = = = = = = = = = = = = = = = [ ' ) ,+ Y% v. = = = = = = = = = = = = = q- L& -& v$ b> U; z$ I- = = = = = = = = = = = = = = = = v> -@ = = = = = = = = = = = = = _- M& )- )- )- G# ;= B = = = = = = = = = = = = = = = = l@ v. = = = = = = = = = = = = = = C : ( ] ) H# = = = = = = = = = = = = = = = k@ F M+ t@ %& z$ .% = = = = = = = = = = = = = = = = && ^= = = = = = = = = = = = = = l@ I+ u + z s& = = = = = = = = = = = = = = = P, A , % 1, = = = = = = = = = = = = = = b& }. y. L+ L+ ,+ F 7 v. = = = = = = = = = = = = = = = = = ", +",+ @ I+ ^' B = = = = = = = = = = = = = = = = = J+ s- /' v. = = = = = = = = = (' + ! v _ : + *= J+ = = = = = = = = = = = = = = = = Y$ q% q% q% 0= = = = = = = = = = = = = = = _' Y% % Y% s, v. = = = = = = = = = = = = #* $ % Y% c& = = = = = = = = = = = = = = = a+ : ) , q% v. = = = = = = = = = = = = = q- -& :* i> h> j> U; M, v. = = = = = = = = = = = = = = 6= :' 8+ = = = = = = = = = = = = = &= M& )- = = c% G# 8- = = = = = = = = = = = = = = = 8+ ^# B = = = = = = = = = = = = = = G : [ v : &' = = = = = = = = = = = = = = = o* ' u V+ 5 b> H J+ = = = = = = = = = = = = = = 2. <' &= = = = = = = = = = = = = = l@ I+ F $+ u [' = = = = = = = = = = = = = = = P, G# , |. 1, = = = = = = = = = = = = = = b& M+ ,+ L+ L+ : + s; X- = = = = = = = = = = = = = = = = = ", +"N+ y. F j@ z& -@ = = = = = = = = = = = = = = = v. && z$ 8> i= = = = = = = = = = (' ' &$ b+ b+ _ ' f@ }' v. = = = = = = = = = = = = = = = .% t Y% t 0= = = = = = = = = = = = = = = 9+ L& c% )- |' ;$ = = = = = = = = = = = = e; % |. $ a* = = = = = = = = = = = = = = = [' 7. y. Y% q% v. = = = = = = = = = = = = = E, `% i> h> ^, -> > -, 1; = = = = = = = = = = = = = v. 1' q% 8+ = = = = = = = = = = = = = &= G# c% U; U; U; = L, I- = = = = = = = = = = = = = v. 1' = B = = = = = = = = = = = = = = B= 9% a+ ,@ ! 2' = = = = = = = = = = = = = = = Q* u# D z$ )$ j, &, b& v. = = = = = = = = = = = = v. c* q% &= = = = = = = = = = = = = = l@ z + $+ F ;, = = = = = = = = = = = = = = = S; # I+ A w, = = = = = = = = = = = = = = 3' F : ! ! ! : y. 4' {; = = = = = = = = = = = = = = = = ", +" = = M& G# I+ {> >; = = = = = = = = = = = = = = -= #- Y% , q = = = = = = = = = 5' E w 0+ 0+ '@ { : _ 6' J- = = = = = = = = = = = = = = .% % A % 0= = = = = = = = = = = = = = = >' = U; -& 8, * = = = = = = = = = = = = q& u# I+ A a* = = = = = = = = = = = = = = = 7' z$ )- L& '- v. = = = = = = = = = = = = = @* > .; +, ^, K> c> %& 8' ]> = = = = = = = = = = = v. R; h> q% 8+ = = = = = = = = = = = = = &= = -& %& %& `% > `% 9' * = = = = = = = = = = = -@ [> v$ q% B = = = = = = = = = = = = = = 0' v f@ f@ v q@ = = = = = = = = = = = = = = = H& [ _ :* {+ ~> %& a' `$ = = = = = = = = = = = = W* ., a& &= = = = = = = = = = = = = = l@ u $+ : ' b' = = = = = = = = = = = = = = = c' I+ u I+ 4. = = = = = = = = = = = = = = $> ,+ =+ v #$ v ] : y. d' K; v. = = = = = = = = = = = = = = ", +"[* b> q; M& % % -$ e' J+ = = = = = = = = = = v. D= f' G# , # w& v. = = = = = = = = g' V$ '@ t, h' i' M@ '@ ! ! *, 3; j' P= p ;$ l@ J+ = = = = = = = k' }. M+ }. 6 = = = = = = = = = = = = = = l' b> :* :* %& m' = = = = = = = = = = = = 6= I& + y. e, = = = = = = = = = = = = = = = s& N+ => )- L& v. = = = = = = = = = = = = = m@ i> n' +, o' ^, w> j> b> p' t- h* = = = = = = = = -@ q' r& Y% p- r' = = = = = = = = = = = = = &= U; %& }> }> [* j, :* M; ;= y$ v. = = = = = = = = `* p* N+ J= J= B = = = = = = = = = = = = = = s' f@ t' t' T$ u' = = = = = = = = = = = = = = = v' y [ `% U+ X+ 5 f% w' W+ ;$ = = = = = = = = J+ 6 8> q% q% x' = = = = = = = = = = = = = l@ ' D 9. ! X, = = = = = = = = = = = = = = = c; u + F m@ = = = = = = = = = = = = = = $> : v [ x g@ a+ v : ,+ z; n> N= w. &= d= 2. 8+ v. = = = = = = = ", +"~> b> x; c% M& J= ,- V+ #> .% = = = = = = = = y' F& k* @ F + + ~* z' A' B' A' C' C' D' z' E' F' b+ M@ G' H' I' J' K' 0+ b+ v ! : L' M' N' <> == z' z' O' E' g= g= g= P' -+ ! : e* Q' E' g= g= E' z' z' O' R' /' S' T' U' U' V' }> <$ .; <$ W' X' Y' }* Z' `' `' ) ) .) E' +) @) #) $) _ u# %) #) &) +) &) @) X* X* #) *) X* X* X* =) =) g= > -) O> `% N+ /' /' .) ;) /' u& A' U' Z' }* >) Z' Z' ,) ') ^, )) !) ~) {) m& Q> y> j> V+ ]) N= J- = = = = v. v# v& )$ % ^; ^; s= ^) /) D& /) u& D& /' S' S' ^) ^) () T' _) [* *@ :) <) <) [) 5 f% -& -, }) :> = = = = = q& q- p, O+ = c% |) /' /) /) ^) D& 1) 2) .) /' /' /' /' 1) S' D& C- m, 3) 4) 5) 6) 7) 8) X* X* @) #) Q' Q' Q' Q' &) #) @) 9) 0) a) b) t' -& c. x# R$ *@ *@ V+ c) u. ^= = = = = v. @= U% d) p- p- Y% :; u& z' z' O' D' z' O' z' z' O' O' Q' +) +) _ b+ a+ b+ a+ @) g= E' O' e) e) O' z' z' z' D' D' z' O' g= f) : ! : (* g= z' C' X$ u& g) g) u& C' B' B' A' O' Q' h) #$ a+ f@ $, i) ;, f@ <. =+ : ,+ F u# }; B> $- S$ D& u& j) k) l) m) T' u& ", +" > b> U; U; x; x; L& J= Y% N+ n) ,; p& W* M- 0= o) Q* s y. $+ L+ 7. _ _ V$ ! ! =+ -+ z. D V$ ! v '@ S> i' p) q) q) q) r) s) K' m, f@ '@ [ b+ b+ v &$ v &$ v b+ r# v [ f@ f@ f@ a+ [ [ v b+ r# v v v b+ D N+ U; > i> D# k& '$ k& u, Q> += ^ j& j& D# :* [* [* M; O+ x y f@ f@ m, b' ;, t) M@ L@ L@ L@ $, i) $, u) u) v) v) b) w) %$ b> D> x) y) j> i> b> -& c% L& M& %& j> i> <$ .; h> z- += D# h> o' z) A) B) C) D) ~) )) E) ~> 5 V+ s; 2) F) X% G- G) ^ > U; N+ b> O> h& j, [* [* %& ;+ N+ `% > H) > U; D> .; -> *@ :) o& <) :) I) :) [) j, f% c- += 9' a- J) q, K) z- `% %& j> :* 5 ^+ h& L) `% L) O+ )- )- O+ N+ N+ N+ = = => v$ y. 0+ M) M) N) O) P) b) ;, m, L@ y Q) x /. /. g@ g@ R) u) S) P) 3) T) v$ U) 5# 8 V) n *@ ^+ h& E$ W) 6* s- X) Y) i> U; = )- N> L& N+ ! { { ] v _ _ _ v v w x x 0+ y m, i) M@ 0+ [ _ _ ] ] ] ( _ &$ v _ ! &$ v v [ '@ '@ &$ &$ 9% ! : ' ' ,+ : -+ ! 7. : 7. ~ #$ <. a+ f@ i) b) 5) t' b) ;, a+ [ #$ ! : L+ ;+ j> ;+ Z) Z) y> `) ! .! .! Z) ", +"[* [* [* %& [* b> U; )- L& p- p- ,- Y% J= , @ y. + L+ 7. _ v [ f@ M@ M@ 0+ a+ a+ a+ 0+ y 0+ 0+ i' i' J' +! @! #! $! %! &! *! r) p) 4) G' G' I' J' K' K' K' K' K' G' G' K' J' 4) 3) I' G' K' 4) J' J' K' b) K' K' h' i' L+ j> =! ^, -! 4 4 ;! U) n& ., >! ,! '! c. e& <) n c- : )! N) N) T) !! ~! {! !! {! ]! ^! ]! ]! /! (! /! (! _! ]! :! [! }! n' E) h> i> j> > `% -& %& }> .; |! 7> ., 7> ;- , ., 1! 2! 3! 4! 5! 6! >! 7! m& n& k& .; )$ )$ V+ )$ .; += h> += z- h> ^, K> D# D# =! }> }> ~> %& ~> h> n' w> D> .> .; o' 8! 9! Z% Z% 0! a! b! c! :) <) [) h& f% d! d! f% 5 <# *@ += &, e& e& k& D# ~> j, L) = b> > c> *> 5. }> j> j> D> j> >+ _ M) e! f! g! h! {! T) T) N) N) !! N) i! j! )! k! r) N) N) /! l! m! -+ }, @& #& m U) [# e& ^+ ^+ t@ <# f% L) b> b> > b> = = v$ <. b' n! o! p! i' m, m, m, i' K' 5) q! I' I' G' I' p) I' G' i' i' i' b) L@ i) t, m, m, i) ;, ;, ;, m, i) i) K' m, m, M@ S> 0> '@ 0+ '@ w 0+ f@ f@ 0+ '@ a+ s& f@ ;, r! b) t' 5) 5) K' t' t' b' ;, f@ (. :. :. o' +, o' o' ^, s! )) t! u! v! w! ", +"[* j, j, [* }> i> > L) M; -& x; q; L& p- x! K& z j@ v [ g@ ;, i) 5) y! y! y! t, t, t, h' h' s) z! s) s) H' q) :! A! A! B! C! m! q) :! :! :! D! E! ]! *! @! *! @! q) @! q) T) *! F! F! $! @! *! :! $! %! *! *! @! G! G! *! b) i> w> H! 1! B) 2! I! G@ J! L= K! L! M! N! O! r@ #& E; f@ P! Q! D! R! R! S! C! C! C! T! U! U! T! R! R! T! T! U! V! W! &, E) s! )) H! X! Y! -! j& D# 6. ;+ z$ U; j, <$ ^, )) Y! m& n& Z! `! C) 3! ~ .~ .~ .~ +~ @~ #~ #~ 7! m& {+ k& z- {+ , $~ %~ $~ l& Y! !) Y! -! H! ^, e& a! e& ~> +, Q> E) ^, x) h> n& &~ *~ =~ t. Z% I) -~ ;~ >~ ,~ '~ b! i& ^+ i& ^+ R$ )~ [# U+ &, {+ .; o' H! ^, D# j> b> F, !~ `) o' o' |! h> += h> n' K> y> y> s& ~~ {~ ]~ ^~ C! /~ (~ /~ W! C! e! R! _~ $! m! :~ <~ [~ }~ |~ 1~ M@ 2~ @& #& @& m C$ 3~ 3~ s@ 4~ 4~ 0* ~> y> -> D> [* `% U; u$ 5~ p) p) I' p) H' H' H' H' r) r) G! q) *! q) q) @! *! &! G! r) G! r) 6~ p) T) 6~ p) 4) 6~ ~> }> }> }> V+ j, L) `% L) `% -& )- |) e~ I+ c- J& }, ;, 0~ 5~ 4) s) s) s) +! 8~ r) p) r) r) f~ 8~ g~ h~ :! %! i~ j~ k~ l~ m~ n~ C! m~ S! o~ p~ }~ C! ^~ ^~ B! B! B! B! q~ r~ s~ ^~ m~ q~ n~ 1~ t~ u~ ^~ ^~ r~ /~ n~ B! #! :. s! Y! z) v~ w~ x~ j j y~ z~ A~ B~ a. a. ~+ l r- {! o~ T! U! C~ D~ E~ F~ G~ H~ I~ I~ E~ D~ C~ D~ E~ D~ D~ J~ =* m& {) {) ~) K~ ~) ~) 1! ., n& &, :. 5. L) L) j, c> u! C> |! n& n& $~ B) L~ .~ L~ ~ L~ M~ 2! 2! +~ >! N~ $~ ., &~ X> O~ P~ -* '! $~ K~ Q~ z) z) N~ 7! :. >+ [* z- &~ 4 E) s! ;> j& 4 '! R~ S~ l Z% a! T~ U~ V~ U~ >~ ;~ o& W~ W~ X~ Y~ 5# H@ ~+ Z! n& E) o' n' w> ~> [* L) Z~ d) )) X! K~ !) `~ I= 4 Y! H! H! { +, O- .{ F~ .{ +{ @{ #{ u~ ${ {~ %{ &{ o~ *{ ]~ ]~ ={ o~ T! V! F~ t, -{ ;{ J! J! >{ S~ g 2 ,{ H$ H$ m D# x> ;> .> [* L) %& '{ #! ){ ){ !{ !{ ){ %! :~ :~ q~ !{ :~ :~ n~ $! !{ q~ n~ n~ !{ $! m! q~ !{ $! B! :~ $! :! #! ~{ {{ L' ]{ !! ~! ){ q) g~ f~ +! p) +! p) p) p) I' I' I' p) p) 5~ 5~ 5~ 5~ 7~ 7~ 5~ 5~ 5~ ^{ /{ ({ _{ :{ ({ <{ [{ @~ C) B) +~ A) C) C) D) @~ D) D) /{ ", +";> D# ;> ;> =! .; i> > %& L) [* b> L) L) `% `% c- }{ !$ t@ -+ <. b' T) G! p) )! |{ N) ){ &! &! &! *! ){ n~ :! m~ m~ 1{ 2{ 3{ l~ l~ l~ V! 4{ %{ 5{ 6{ 4{ *{ @{ @{ #{ %{ %{ 4{ V! @{ %{ 7{ 4{ V! 4{ 8{ F~ F~ ${ ${ @{ #{ 9{ %{ 0{ a{ Q> X! b{ v~ v~ x~ [& (+ c{ d{ e{ f{ g{ e{ S~ '$ -{ :~ I~ I~ h{ h{ i{ j{ k{ l{ k{ j{ i{ i{ i{ i{ m{ n{ m{ o{ p{ Q~ q{ r{ s{ @~ b{ t{ u{ $~ $~ 4 v{ h; ^ j> = `% .> K> y> .> )$ <$ Q> !) M~ 5! L~ L~ .~ w{ w{ 4! 4! L~ 2! L= L= O~ v{ O~ Q; x{ s; (> ,! y{ ., -! ;> > N+ O+ G# = V+ i> c> K> n' {+ N! &~ B~ z{ B$ r@ A{ B{ C{ D{ E{ F{ G{ Z% & H{ z# (# I ~= T+ n& D# ;> y> i> c> j> `% U; -> w! c~ @~ b{ Q~ &~ &~ &~ K~ K~ z) I{ J{ D) K{ L{ M{ N{ O{ P{ P{ Q{ R{ O{ S{ S{ E~ N{ N{ P{ E~ T{ U{ V{ :! c- s. C$ W{ y~ a. a. J! X{ I$ I$ S+ Y{ -! y> =! > -& -+ A! f! q~ <~ Z{ S! S! V! 4{ `{ ] B! m~ l~ l~ 1~ n~ n~ S! ] .] 1~ V! S! S! S! 4{ 4{ R! ] B! #! #! f! $) +] ~! !{ G! h~ g~ h~ q) q) h~ g~ r) r) r) r) g~ g~ k! r) k! k! k! 3) @] @] s #] $] %] &] *] B) w{ 4! [{ 4! w{ =] 2! >! >! -] *] -] -] s{ ", +";] x) C> u! ;] >] ,] D> > [* ~> ~> }> j, L) [* [* '] [* 5. }> ~> }> %$ 5) )] i! i! !] ^! q~ n~ q~ q~ n~ B! q~ 1~ s~ 3{ ~] ~] l~ l~ #{ {] {] I~ ]] F~ @{ F~ ]] ]] I~ S{ ^] I~ ^] ^] ^] I~ ^] S{ ^] E~ F~ /] (] M{ _] /] :] D~ I~ <] A; #~ 4! 4! [] }] N$ N$ j% [& |] 1] 2] 3] z~ V+ ;# ]] S{ 4] 5] h{ 6] 7] 8] 9] 9] 8] m{ 0] 6] a] 7] b] c] d] e] f] g] h] i] b{ j] k] l] m] $~ $~ O~ !- :. 6. z$ O+ N+ v$ >+ >+ >+ ;+ )$ j& 4 y{ 2! w{ L~ L~ ~ ~ 4! w{ 3! L= n] '! r& 8, C& s; s; T- 5. N+ N+ N+ N+ N+ N+ N+ N+ N+ N+ N+ O+ O+ v$ 5 o] .& '! $~ n& T~ N! B{ p] 2! @~ q] r] k& }, s] t] U+ &~ l& j& ~> j> :* >+ ;+ ;+ v$ N+ z$ D# n& !) A) B) @~ $~ $~ '! b{ y{ L~ u] v] w] x] 8' y] k{ m{ i{ z] h{ h{ h{ j{ n{ n{ n{ 6] R{ /] /] A] :] k~ F z- 3~ g i B] j% }# k C] D] r. E] l& D# j> -) v$ F] l~ l~ 1~ U! &{ &{ &{ G] ^] I~ &{ H] V! #{ #{ #{ u~ l~ U{ {] {] {] &{ &{ {] {] G] I~ &{ H] U! 2{ f! f! 2{ f! $! q~ !{ !{ !{ ){ h~ ){ ){ *! !! ~! ~! h~ g~ g~ g~ h~ g~ !! L' +] I] #] J] $] L~ 2! w{ w{ w{ w{ 2! 4! .~ L~ w{ J] K] L] #] K] J] &] [{ ", +"{) x) x) C> Y! M] `) H) j, <$ c> j& j& j, [* j, j, N] j, .; ~> D# D# .; ^, .; K, 3) {! *! q~ n~ q~ n~ n~ n~ n~ m~ l~ 2{ 3{ ~] l~ l~ %{ G] I~ D~ O] /] O] /] /] P] Q] ^] I~ I~ S{ i{ S{ ^] ^] S{ S{ O{ ]] O] R] O] O] O] S{ S{ I~ o~ s D) L~ 4! [] S] i i n. n. T] U] V] W] *~ L) o, X] h{ ^] ^] S{ m{ 7] 9] 9] Y] Y] 7] i{ i{ Z] 8] `] ^ .^ +^ @^ e] #^ $^ B) %^ &^ &^ *^ L= L= 8, r& T- ^ >+ O+ G# G# v$ v$ v$ z$ > [* }> ;> ;] 4! 2! [{ B) 4! B) b{ B) '! $~ v{ `! T- :. z$ N+ O+ O+ O+ N+ N+ v$ v$ v$ v$ v$ v$ v$ N+ N+ N+ N+ = )- = }> n& k& {+ k& n& 1! b{ z) =^ -^ k& G$ ~$ G$ '$ k& D# }> j, j, `% v$ z$ O+ G# v$ >+ D# {+ l& N~ [{ b{ ;^ >^ ,^ '^ '^ )^ !^ ~^ {^ ]^ ^^ /^ (^ Z] m{ j{ S{ S{ _^ 7] 8] :^ 7] m{ R{ ]] ]] <^ Q] [^ $+ >+ b. N$ #@ 3+ j% j A@ '+ }^ (+ 2 &~ j& .> z, j@ |^ u~ u~ 9{ G] G] ^] S{ ^] I~ ^] I~ ^] ]] @{ @{ F~ @{ #{ 5{ I~ G] I~ G] I~ G] G] I~ ^] I~ G] o~ ~] ~] 2{ 2{ 3{ C! m~ m~ q~ q~ n~ n~ q~ q~ $! 1^ #! !! h~ ){ h~ ~! 2^ +] 3^ 4^ 4^ 5^ #] J] ~ L~ w{ v~ 6^ L~ L~ L~ w{ L~ .~ ~ 7^ 5^ K] J] J] 8^ $] 2! ", +"z) s! ;> y> ^, n' !~ -> }> j& ~> D# k& }> j, j, }> [) ~> h> D# k& {+ k& ;> 9^ <$ 0^ a^ t' r) b^ .] .] c^ <~ q~ n~ s~ B! 2{ s~ 1~ u~ @{ <^ :] :] N{ N{ N{ P{ P{ P] O{ d^ :] h{ i{ S{ I~ ^] I~ ^] h{ h{ X] N{ R{ R{ R] R] e^ N{ E~ f^ K{ g^ h^ i^ j^ k^ l^ m^ 5+ 3+ n^ o^ p^ q^ r^ %& 0~ o{ j{ h{ ^] O{ s^ n{ 7] 8] 9] Y] 8] n{ t^ Z] u^ v^ w^ x^ y^ z^ A^ B^ e] C^ D^ E^ F^ G^ H^ I^ O~ O~ Z! h> ;+ v$ G# G# O+ v$ v$ z, b> %& %& [* O> .; c~ X! )) J^ @~ D) 8, T- d) z$ I+ A I+ I+ N+ M+ r% t] K^ a~ L^ M^ 1- I m% `= N^ O^ P^ Q^ R^ S^ ~$ u I+ c% )- U; :* .; 7! [{ m] 1! T^ c> *@ $& $& $& > b> b> b> %& `% v$ N+ G# G# v$ j> ;> j& m& b{ B) L~ U^ V^ W^ W^ X^ Y^ Z^ `^ / ./ +/ @/ #/ $/ j{ %/ E~ &/ */ 9] 8] 7] $/ s^ N{ %{ 4{ 9{ V{ +{ r# !$ ~+ =/ 3# 3# 5+ f (+ [& 1 =/ }] y{ n& c> -/ w# ^~ l~ u~ #{ G] G] I~ O{ :] <^ I~ G] ^] :] P{ f^ F~ F~ #{ T{ <^ <^ 5{ Q] :] D~ Q] Q] D~ Q] 5{ %{ ;/ 3{ 2{ s~ s~ e! S! .] c^ q~ m~ m~ n~ q~ n~ E! ]! ]! G! @! $! >/ 8^ 8^ ~ .~ i^ ,/ '/ )/ !/ ~/ ~ {/ v~ ~ ~ ~ .~ ~ v~ v~ ]/ {/ ^/ 5^ L! L~ ~ 4! ", +"b{ 7! T- :. :. ~> ~> j& ~> D# ~> k& {+ ~> }> j, j, ~> D# j& D# k& k& D# }> }> j& j& ~> 5. 5. _. k! // E! <~ n~ m~ n~ m~ m~ m~ l~ #{ @{ %{ #{ f^ ^] S{ ^] S{ ^] G] ]] R] O] z] i{ S{ ^] S{ I~ ^] ^] h{ S{ h{ S{ N{ R] O] O] ]] O] s^ (/ ', %] _/ :/ P+ . { j/ k/ l/ m/ n/ o/ p/ q/ r/ s/ t/ u/ v/ w/ x/ y/ z/ A/ B/ C/ D/ E/ F/ G/ H/ I/ J/ K/ ' L/ M/ N/ 7> ., v! `) d) 6. <$ 6. 6. 5. 5. >+ >+ >+ v$ O+ A> O/ Z~ P/ x> s! K~ [{ L~ .~ Q/ R/ S/ T/ U/ V/ W/ X/ W/ Y/ Z/ B) !; #/ `/ e^ O] R] a] 8] n{ k{ ( O] :] &{ &{ f^ /] ]] .( z (# f +( @( $@ j% y~ #( 2] V] $( -^ m& c> u$ f! *{ l~ #{ @{ G] G] ^] ]] @{ 8{ ^] ^] I~ I~ G] G] P] F~ F~ @{ @{ F~ F~ @{ F~ ]] /] /] O] ]] @{ #{ u~ l~ 1~ 1~ m~ .] %( &( *( q~ q~ q~ !{ !{ q~ =( =( E! |^ q# J> 5! ~ 5! -( ;( >( ,( '( )( !( ,/ 5^ 5! L~ ~ ~ L~ ~ ~( ~/ {( )/ ]( ^( /( 5! (( _( 4! ", +"b{ [{ %, ^ 5. c> j& ~> ~> ~> D# {+ n& D# }> }> ~> D# D# j& j& {+ j& j& ~> }> D# ~> ~> ;+ z$ % y. (. ~{ A! 1~ q~ !{ n~ m~ 1~ u~ #{ #{ u~ l~ 8{ S{ S{ i{ h{ ^] :] /] /] O] :( j{ i{ i{ j{ S{ S{ j{ i{ i{ h{ h{ e^ R] R] R] O] R] %/ z] l- C) <( [( }( |( 1( $~ h> <$ 2( 3( i' F % O+ #$ $, 2^ 3{ t~ /] 4( 7] 7] 7] Z] n{ j{ ( R] 1^ 5( '^ 6( 7( 7( 8( ~^ {^ v] 9( 0( a( b( 3] c( C{ '! l& k& j, [* v$ G# % |. # # # O+ O+ O+ O+ O+ N+ N+ M+ G# O+ d( e( f( g( h( i( j( k( l( m( n( o( p( q( r( s( t( u( v( w( x( y( y( z( A( B( C( D( E( F( G( H( I( J( K( L( M( g, N( j@ u# -+ 5. 5. 5. 5. >+ >+ z$ z$ v$ O+ G# z, f/ v! !~ -> u! {) @~ 4! ~ [] O( z^ P( Q( R( W/ Y/ S( T( U( 2! y{ C- V( R{ O] R] :( Z] Z] k{ ( O] f^ {] I~ &/ R] /] G' { ,@ P^ m% W( X( g y~ Y( 2] U] Z( w] <{ += `( _ ]] @{ #{ F~ ^] I~ G] @{ @{ F~ ^] ^] I~ G] I~ G] @{ ]] /] F~ /] /] @{ @{ F~ F~ ]] ]] ]] ]] F~ @{ #{ u~ 1~ m~ n~ <~ %( ._ +_ !{ !{ q~ q~ q~ :! {{ $) X; @_ y, #_ .~ .~ {/ ,/ $_ 9( %_ &_ *_ )/ -( 5^ ~ L~ .~ v~ v~ v~ =_ -( ,/ -_ ;_ ]( -_ K] _( _( w{ ", +"4! A) X> 6. .; E) w> =! c> w> .; D# k& E) ~> ~> j& j& D# j& {+ k& j& j& j& D# D# }> ~> )$ b> % v$ N+ z$ v 8~ %! m! l~ l~ u~ #{ #{ @{ V{ f^ N{ z] S{ j{ j{ i{ s^ >_ O] ,_ Z] n{ j{ n{ a] i{ %/ n{ Z] j{ h{ h{ k{ l{ l{ k{ '_ k{ j{ z] 6) T* )_ =] m& j& U; p- q% % !_ l~ 1~ $! s& , H= -- a& > , ! b) e! E~ ~_ :^ {_ ]_ ^_ /_ `= 8^ (_ __ ./ / :_ <_ [_ ~^ w] }_ |_ V] e/ &^ H^ $~ n& c> L) %& N+ |. % e~ e~ e~ # G# O+ , % A # 9. ^. 1_ 2_ 3_ 4_ 5_ 6_ 7_ 8_ 9_ 0_ a_ b_ c_ d_ e_ f_ g_ h_ i_ j_ k_ l_ m_ n_ o_ p_ q_ r_ s_ t_ u_ v_ w_ x_ y_ z_ A_ B_ C_ D_ E_ F_ G_ ]{ N( @ M+ }. }. I+ G# }. P+ I+ G# G# Z) w! *> H_ 9^ u! X! @~ w{ 6^ y^ Z/ I_ J_ J_ K_ 7( L_ M_ N_ O_ E{ (> P_ Q_ O] e^ R{ %/ s^ R_ S_ T_ 5{ 6{ U_ I~ #{ !{ V_ w) i' t) W_ a^ X_ Y_ L- Z_ `_ |_ : +^ N~ &, .: +: P{ ]] #{ F~ Q] D~ 5{ V{ P] P{ S{ S{ ^] ^] &/ D~ f^ /] O] O] Q{ X] #{ @{ #{ ]] F~ /] ]] @{ u~ #{ #{ u~ l~ n~ n~ .] @: E! @: !{ !{ !{ M) i' [ ^ -* 4^ #: ~/ w~ v~ v~ [] $: %: &: &: &: ;( ^/ -( 7^ {/ ]/ ]/ /( /( v~ {( 5^ 5^ )( ^( ]( *: &] =: -: 4! ", +"4! B) ;^ {+ ~> C> x> x> h/ ;: v! ! u! ;] ;> D# k& k& j& j& n& k& j& D# j& D# ~> j& ~> ~> j, 2; = v$ v$ `% %& D# F] n~ C! >: u~ F~ F~ G] I~ j{ h{ ^] j{ j{ n{ j{ j{ i{ i{ j{ n{ Z] ,: ,: ,_ j{ n{ n{ j{ h{ i{ Z] ': 7] 8] ': 7] n{ n{ n{ ]{ ;+ O+ % Y% Y% % Y% Y% ;, l~ l~ s~ 2{ !! ~ ): !: -- -- -- -- q% O+ ,+ ~: {: ]: ^: 4 b. 8> ,^ /: (: _: :: <: [: }: |: 1: 1: 2: U] 3: 4: K~ |! i> z$ v$ # e~ D, E; E; E; z K@ K@ #$ 5: R> 6: 7: 8: 9: 0: a: b: c: d: e: f: g: h: b_ b_ b_ i: i: j: k: l: m: n: o: p: q: r: s: t: u: v: w: x: y: z: A: B: C: x_ D: E: F: G: H: I: J: K: L: M: (] a+ E !$ N+ E; N: E; D, D, O+ z$ 5. j, [* }> u! X! [{ .~ 1: 8( S/ S/ O: P: Q: R: S: T: Q$ B{ $~ C& U: j{ n{ R_ (] F~ V: H+ W: X: Y: Z: `: &{ // N) k! 3) b) p! /. /. [ w M@ < M^ .< A* +< @< I~ S{ S{ /] F~ O] /] /] P{ Q] ^] z] j{ j{ S{ ]] O] R] R] R] ( &/ ^] S{ X] ]] ]] O] O] ]] /] @{ #{ @{ #{ u~ u~ l~ 1~ m~ q~ !{ q~ n~ G! M@ 6. v$ %& )$ j& N~ w{ v~ v~ 6^ v~ 6^ c/ #< $< '( &_ '( ~ .~ .~ {/ 5^ -( {( %< {( ~( 5^ 5^ &< ;_ *< =^ 4! L~ 4! 2! ", +"w{ b{ n] l& }> C> C> C> >] d~ >] =< H! z) &~ {+ j& j& j& j& j& {+ k& k& j& D# j& {+ D# ~> [* M; = v$ z$ b> [* M; L) $& 7' -< #{ #{ %{ 5{ ^] S{ S{ S{ i{ Z] 7] 7] 7] Z] n{ Z] 7] 7] k{ O] O] i{ n{ Z] j{ i{ Z] n{ Z] 7] 9] 5/ 5/ 9] 7] i{ 1^ G# % % % % % q% % [ u~ u~ *{ 3{ 2{ f! k! ( x! -- -- -- -- -- -- -- ;< >< z$ D# S~ s; >^ ,< ]^ _: '< )< !< ~< : {< Z) z$ G# e~ E; E; r- r- r- -{ /< (< _< :< << [< }< |< 1< 2< 3< 3< 4< 5< 6< 7< 8< 9< 0< c_ a< b< c< d< e< f< g< h< i< j< l_ k< k< l< l< m< n< o< p< q< r< s< t< u< v< w< x< y< z< A< B< C< D< E< F< G< H< =; w# W$ I< r- E; D, O+ O+ v$ ~> j& j& C> u! {) [{ .~ R/ |: z^ 7( J< K< L< S: M< N< f. I! 8> 7~ +] ]{ w ! T$ O< P< Q< R< S< T< o{ S{ H] g! ~! k! G' U< /. /. x L@ f@ f@ M@ >@ i' 6~ H~ d^ ^] S{ e^ R] ( R] R] /] 6{ G] h{ j{ j{ i{ O] R] ( ,: ( ( %/ h{ h{ %/ R] R] O] R] F~ R] /] F~ @{ #{ u~ l~ 1~ l~ 1~ n~ ){ z! ,@ n% %& >+ v$ b> }> }> {+ B) ~ [] Q/ v~ v~ V< @^ W< ]( ;( ^( ~ ~ ~ 5^ 5^ -( ,/ X< ~( -( 5^ 5^ ^/ ]( ;_ C^ w{ v~ w{ 4! ", +"y{ N~ N~ Q~ w> u! u! u! ^, ,] ^, ;] H! H! 4 {+ j& k& j& w> a! k& 0! k& j& w> k& |! E) D# M; q; = v$ z$ > [* [* L) j> > V+ m, :! S! G] I~ G] :] &/ s^ n{ Z] 7] Z] n{ Z] j{ n{ */ N{ ]] O] 6] n{ Z] j{ h{ j{ 6] j{ ': 5/ Y] 5/ Y< 8] :] _ $ $ % T> A> Z< G# G# + 1~ %{ t~ ~~ 3{ ~~ 1~ m~ r) F q% -- -- -- -- -- -- a& G# j, .& x{ '! _$ `< [ .[ +[ z^ @[ __ #[ $[ 9/ 2] 3: %[ z) x> j> M+ G# z E; -{ r- 9% &[ *[ =[ -[ ;[ >[ ,[ '[ )[ ![ ~[ {[ ][ ^[ /[ ([ _[ :[ <[ [[ }[ |[ 1[ f< 2[ 3[ 4[ 5[ 5[ 5[ 6[ 7[ 8[ 9[ 0[ a[ b[ c[ d[ e[ f[ g[ h[ i[ j[ k[ l[ m[ n[ o[ p[ q[ r[ s[ t[ u[ v[ w[ x[ y[ z[ A[ {{ J& r- E; N+ M+ M+ `% V+ 4 X! H! X! z) 2! v~ <( y^ B[ C[ D[ E[ F[ G[ }# P$ 3 %, Y% a& x- x- >< `( H[ I[ J[ K[ L[ M[ N[ T! H] }~ {! 5~ i' O[ g@ g@ y b) 5~ 5) b) t, P[ !{ f^ d^ i{ j{ ,_ k{ ,_ e^ R{ '_ h{ I~ h{ n{ i{ h{ N{ N{ ,_ k{ k{ l{ 6] i{ j{ s^ ,: l{ R] R{ 8{ R{ e^ V{ #{ #{ #{ u~ 1~ /~ *! m, &$ J& `% `% %& z$ v$ v$ }> =! ;> z) w{ .~ .~ v~ Q[ %: v] w] #< 0( $< [] ~ ]/ 7^ 7^ ,/ ,/ ~( -( ~( ^/ R[ S[ e] $^ T[ w{ .~ ~ U[ ", +"L= n] A) b{ m& X! u! C> ~> c> k& {) H! u! n& {+ {+ -! u! u! a! -~ -~ w> u! ;> E) u! x> ;> M; 2; c% N+ v$ > j, j, b> >+ >+ z$ >+ $+ F# V[ H] 5{ N{ R] ,_ j{ j{ Z] 7] 7] j{ i{ 7] */ P{ /] '_ 7] Z] n{ i{ ^] ^] N{ ( W[ 4/ 8] 8] X[ Y[ d% Z[ p- `[ } K& .} => G# % }. @, U: C~ {] &{ 6{ *{ 3{ 2{ A! 5) 9& a& -- -- -- -- -- a& N+ Q> '! &~ +} @} 9/ #} $} o. @} 9/ h o. %} @} V] &} @~ x> z$ e~ r- *} =} -} ;} >} ,} '} )} !} ~} {} ]} ^} /} (} _} _} :} <} [} }} |} 1} 2} 3} 4} 5} 6} 7} 8} i< 9} 0} a} b} c} d} e} f} g} x( h} i} j} k} l} m} n} o} p} q} r} s} t} u} v} w} x} y} z} A} B} C} D} E} F} G} H} X' -' >+ N+ E; I} D, D, j@ ^, >] J} #~ [{ L~ v~ O( 8( / K} L} M} N} X{ P$ b. z$ -- -- -- !# t' A! 6~ O} 2( r~ P} Q} R} N) S} T} U} t) 0+ y L@ i) b) 5) 3) 3) 5~ 5~ k! A! V} h{ Z] Z] n{ n{ j{ &/ S{ i{ S{ I~ S{ i{ h{ I~ I~ ^] h{ i{ j{ n{ n{ n{ n{ Z] 7] Z] s^ h{ S{ ^] &/ G] {] H] o~ ~~ W} ,@ -+ %& L) `% U; v$ >+ z$ v$ v$ x) x> x> ;] @~ 2! ~ v~ .~ X} '^ Q/ '^ 6^ 6^ ~ .~ v~ ~ Q[ _/ Y} $: M= ^/ ;_ *< h^ w{ 4! 4! w{ .~ i^ V< ", +"P~ L= [{ b{ z) X! H! ;> ~> j& {+ H! K~ s! E) {+ {+ s! s! s! Z} -~ -~ 8! X! u! C> u! u! j& M; q; c% N+ N+ `% [* }> > >+ >+ z$ v$ v$ @ + v 2^ ;/ `} | n{ h{ j{ j{ 7] Z] Z] 7] j{ N{ ( ,: 8] Z] n{ n{ h{ S{ N{ R] ,: */ Z] 7] Q{ #{ 7~ .| +| @| #| # b- $| G# % |. W} %| 4] &{ G] E~ V} 3{ 2{ f! 2{ $) *+ G# -- -- -- -- -- % j> j& k& &| *| =| E@ k^ ]< ]< -| @} -| 2] 3: 3: ;| b{ x> -& E; E !_ >| ,| '| )| !| ~| {| ]| ^| /| (| _| :| <| [| }| || 1| 2| 3| 4| 5| 6| 7| 8| 9| 0| a| b| c| d| e| f} f| f| g| g| h| 0} i| j| k| l| m| n| o| p| q| r| s| t| u| v| w| x| y| z| A| B| C| D| E| F| G| H| I| J| K| L| M| %< ;+ r- r- r- E; N+ u# *+ K~ @~ 2! ~ 6^ O( N| !< O| P| Q| j P$ O! k; -- -- q% ;, 2{ ~! T) W} W} `( O} R| S| T| U| G! U} t) f@ y y L@ b) k! !! #! #! A! f! f! Q! ^] 7] n{ j{ n{ i{ S{ I~ G] I~ G] S{ i{ j{ h{ S{ G] I~ i{ j{ h{ j{ j{ Z] Z] n{ j{ n{ h{ S{ I~ G] G] U! +] 0~ #$ >+ z, > L) L) > > >+ ;+ >+ v$ v$ 9^ x) C> s! H! b{ 2! L~ L~ .~ v~ [] 6^ v~ v~ .~ v~ [] v~ V| W| X| ;( )/ &< Y| ;_ K! ~ L~ L~ L~ L~ C^ c/ ", +"A) <{ [{ @~ [{ b{ K~ u! D# k& k& -! Q~ -! k& -! k& |! H! s! a! a! o] Z| {) C> u! s! H! E) x; M; = O+ N+ z$ :* ~> :* >+ :* >+ z$ v$ v$ %& 5. ^ `| >/ 1 .1 6] h{ j{ n{ t^ Z] 7] Z] >_ l{ +1 7] n{ 7] Z] Z] j{ N{ N{ '_ */ Z] a] @1 #1 $1 %1 &1 *1 =1 -1 ;1 T> G# 4; M& ,@ >1 C~ G] G] E~ 0{ <] 3{ ~~ 2{ ~] ,1 ^: }$ % -- -- -- -- % L) )$ X& '1 )1 !1 ~1 {1 ]1 p^ 3: ^1 /1 &^ G^ (1 M~ y> z- /- _1 :1 <1 [1 }1 |1 11 21 31 41 51 61 71 81 91 01 a1 b1 c1 d1 e1 f1 g1 6| h1 i1 j1 k1 l1 m1 n1 o1 p1 q1 r_ e} r1 u( s1 t1 u1 e} v1 w1 x1 y1 z1 A1 B1 C1 D1 E1 F1 G1 H1 I1 J1 K1 L1 M1 N1 O1 P1 Q1 R1 S1 T1 U1 V1 W1 X1 Y1 Z1 `1 2 / J& @> @> r- t@ +, y{ A) 2! <{ .~ 1: Z/ S( .2 /: $@ C$ x; -- -- t _. $) 3{ $) &! 1- 8, R| 8, +2 @2 #2 /{ t% J' K^ g, b' T$ y 5) #! f! ~~ ={ 0{ %| $2 %2 %( :] j{ j{ i{ h{ S{ I~ &{ 6{ Q] S{ %/ s^ j{ z] D~ Q] i{ n{ j{ n{ 7] 7] n{ j{ j{ 6] i{ d^ I~ U! &2 T$ :* z$ v$ O> D> H) L) [* j> H) >+ >+ z$ v$ N+ .> C> u! s! ;] X! B) L~ 6^ 6^ [] 6^ (_ *2 6^ [] v~ v~ .~ X} =2 -2 >( ^( ^( $_ '( U[ L~ ~ .~ L~ L~ h^ ;2 ", +"5( 5( >2 5( 5( [{ z) s! j& j& j& n& 4 {+ -! ;] u! u! s! u! j& j& {+ K~ K~ ;] |! {+ `~ j& x; x; `% v$ v$ v$ v$ >+ :* }> j, j, ~> ~> j, :* }> ;> x> H! #~ ,2 '2 )2 :( */ n{ 7] 8] 7] */ j{ Z] 7] 7] 7] Z] Z] n{ i{ S{ z] Z] Q{ F~ !2 a) ~2 {2 ]2 ^2 /2 (2 _2 :2 <2 <2 2; 9% >1 [2 G] &{ ^] G] H] {] o~ {] U! l~ l~ s~ #! v x, -- -- -- q% G# , 2 g O$ 1( S] P~ w@ N$ Y( +< +< +< i- f) }2 |2 12 22 32 42 52 62 72 82 92 02 a2 b2 c2 d2 e2 f2 g2 h2 i2 j2 k2 l2 m2 n2 o2 p2 q2 r2 s2 t2 u2 v2 w2 x2 y2 z2 A2 o| B2 C2 l| n_ D2 E2 r_ F2 G2 H2 I2 J2 K2 L2 M2 N2 O2 P2 Q2 R2 S2 T2 U2 V2 W2 X2 Y2 Z2 `2 3 .3 +3 @3 #3 $3 %3 &3 *3 =3 -3 ( =& E :* J& r# X, n] &~ ;^ 6^ R/ S/ ;3 >3 g. c. a& -- x, L@ A! 1{ j> [* j, j, +> H) Z~ z, z$ N+ N+ ^3 -! m& H! u! x> K~ ~ Q/ 1: Q/ 6^ /3 ]< Z( '^ Q/ 6^ v~ @^ (3 _/ v] $< $_ $_ _3 Q[ L~ ~ .~ .~ .~ V< :3 ", +"d~ :{ :{ :{ 5( 2! B) {) j& k& k& {+ 4 4 X! s! u! u! s! s! E) k& k& !) @~ H! k& {+ n& j& L) x; `% >+ v$ N+ v$ z$ :* }> [* }> }> j& j& }> D# }> 9^ C> 7! ~) u, <3 ]~ ( [3 7] Z] j{ j{ j{ Z] 7] Z] Z] n{ 9] 9] n{ h{ i{ 8{ }3 |3 13 23 33 f> 43 l. 53 63 73 83 x; b- b- 93 4 ,4 '4 )4 !4 ~4 {4 ]4 ^4 /4 (4 _4 :4 <4 [4 }4 |4 14 D 24 D D 4 $~ n] 34 Q/ }( 44 54 g- J= -- q% T$ f! 2{ 4) U} 7% 64 74 m% Y_ a~ a~ a~ U} a~ Y_ |! A; 84 &2 ' a+ A! 94 V( Z] Z] ]_ 04 a4 Y: l~ !{ n~ I~ i{ ^] G] &{ ^] '_ ( ( ,: R] ( ,: O] R] s^ Z] j{ j{ j{ i{ Q{ O] /] b4 !; *+ 6. 5. >+ `% M; [* j, }> }> j, ~> }> > D> Z~ z, v$ v$ N+ 93 n& s! C> C> C> u! [{ ~ '^ <( [] 3: V] U] c4 1: 6^ Q/ u] d4 e4 $< %: f4 g4 f4 -_ .~ .~ .~ [] .~ h^ ^( ", +"d~ >2 :{ h4 -] B) [{ K~ |! k& {+ l& l& n& E) E) s! ;] X! {) `~ {+ {+ K~ {) H! |! n& 4 }> M; U; b> z$ v$ v$ z$ `% U; j, }> }> }> D# ~> }> ~> w> C> ;> C> ;> <$ t] >@ <3 F* $/ m{ 6] 6] j{ t^ n{ n{ Z] Z] i4 i4 Z] m{ E~ l~ j4 k4 l4 m4 n4 o4 ** p4 q4 r4 [^ -< w- s4 I N@ t4 t' E! {3 D~ I~ {] u4 G] &{ T{ #{ 4{ #{ V} ;/ t~ >: v4 w4 Y% a& >+ y~ w@ w@ L= x{ $~ &, / _. A! ^_ x4 y4 z4 A4 B4 C4 D4 E4 F4 G4 H4 I4 J4 9: K4 L4 M4 N4 O4 P4 Q4 R4 S4 T4 U4 V4 W4 d: X4 2} Y4 Z4 `4 7< 5 .5 +5 @5 #5 $5 %5 &5 *5 =5 -5 ;5 >5 ,5 '5 )5 !5 ~5 {5 ]5 ^5 /5 (5 _5 :5 <5 [5 }5 |5 15 25 35 45 55 65 75 85 95 05 a5 b5 c5 d5 e5 f5 g5 h5 i5 j5 k5 l5 m5 94 9. 03 ! *@ u, L= n5 6^ O( o5 p5 ^3 H= Y% s& j~ 3{ 2{ 9% / f) q5 r5 s5 ,1 t5 W} W} <3 W} u5 v5 w5 x5 y5 z5 A5 B5 k* e! 6] u^ C5 D5 ~3 E5 s~ m~ <~ // &{ I~ G] I~ z] ( ( R] ( ( ,: ( O] ( j{ 7] j{ j{ :( h{ .{ F5 G5 0^ m& ;> j> j> V+ :* ~> j& ~> j, ~> j, ~> [* j> Z~ Z~ z, z$ N+ N+ 2~ -! C> C> C> u! x> H! 2! 6^ '^ H5 I5 2] J5 K5 L5 c4 [] X} d4 M5 %: ;2 ]( N5 >( *_ .~ U[ K! v~ K! S[ *_ ", +"$~ '! P~ P~ n] n] y{ &~ {+ $~ n& 4 {+ k& E) k& {+ l& n& 4 4 n& n& !) {) H! T^ O5 !) i> %& >+ > j, [* [* [* %& [* j, ~> ~> }> }> ~> j, }> ~> D# ~> ~> j& k& n& l] P5 q^ 7= n- n- Q5 R_ R5 n{ Z] Z] S5 S5 R5 ,_ F~ u~ T5 |3 |3 U5 |3 V5 W5 W5 X5 Y5 Z5 |3 ~] @+ `5 6 d% M; c> o' @~ .6 +6 I~ :] G] &{ {] <^ ^] G] &{ G] ^] 5{ G] G] @] >+ I+ $~ @6 #6 h; ^ >+ w# 2{ $6 %6 &6 *6 =6 -6 ;6 >6 ,6 '6 )6 !6 ~6 {6 ]6 ^6 /6 (6 _6 :6 <6 [6 }6 |6 16 26 36 46 56 66 66 66 76 86 96 l: 06 a6 b6 c6 d6 e6 f6 g6 h6 i6 j6 k6 l6 m6 n6 o6 p6 q6 r6 s6 t6 u6 v6 w6 x6 y6 z6 A6 B6 C6 D6 E6 F6 G6 H6 I6 J6 K6 L6 M6 N6 O6 P6 Q6 R6 S6 T6 U6 V6 W6 X6 Y6 Z6 `6 7 .7 +7 9% 9% r# =* X< @7 #7 w' $ ' !! f! ~] 3{ ~! > G# z$ $7 %7 (> &7 y= *7 F! O} W} O} =7 2( -7 ;7 >7 ,7 '7 )7 !7 g@ ~{ %2 G] I~ o~ e! A! k! k! /! 3{ P{ /] ,: ~7 ,: R] ( ( ( ( R] O] &/ i{ n{ >_ #{ p) ,@ D# [* j, }> }> ~> }> ~> }> j& ~> ~> }> j, j, }> [* +> -) Z~ Z~ -) O/ /; [* -! k& u! C> C> x> s! {) 2! .~ [] [] *2 c4 {7 2] U] Q[ c/ $< v] @^ c/ v~ v~ Q/ X} c/ %: c/ #< h^ K! [] ", +"&~ '! ;^ '! n] n] '! '! 4 &~ &~ &~ 4 k& j& {+ 4 4 4 n& l& &~ $~ !) {) H! T^ q] Q~ c> z$ v$ j> D# }> L) [* j, j, [* }> j& D# D# ~> }> }> ~> j& {+ j& k& n& +< &^ ]7 ^7 /7 )^ Y/ a/ (7 y] 7] 7] n{ _7 _7 _7 1/ }3 :7 T5 |3 j4 j4 j4 <7 j4 |3 j4 9; )1 [7 C- H+ 7% s% x; 2; D# X! B) c~ }7 ;/ F~ <^ G] {] G] S{ I~ G] ^] h{ S{ I~ ^] 94 ;/ #! W} ,@ 9% ( |7 `: 17 27 37 47 57 67 77 87 97 07 a7 b7 c7 d7 e7 f7 g7 ^6 h7 i7 j7 k7 l7 m7 n7 o7 p7 q7 4} r7 s7 s7 s7 t7 u7 v7 v7 w7 x7 y7 z7 A7 B7 C7 D7 E7 F7 G7 H7 I7 J7 K7 L7 M7 N7 O7 P7 Q7 R7 S7 T7 U7 V7 W7 X7 Y7 Z7 `7 8 .8 +8 @8 #8 $8 %8 &8 *8 =8 -8 ;8 >8 ,8 '8 )8 !8 ~8 {8 ]8 ^8 /8 (8 _8 :8 <8 [8 ]! i) ( v r& }8 |8 U} 5) f! 3{ 3{ ;/ ;/ ;, Z* % N+ 18 28 38 48 58 68 78 88 f) =7 O} O} 98 08 a8 b8 c8 d8 e8 ;+ z$ $+ { y 5) !! !! k! k! k! !! s~ O] ( ,: ( ( ( R] R] R] R] M{ (] f8 2{ ;, 9. 9% z- D# }> }> j, j, ~> }> j, j, ~> ~> }> }> }> [* L) [* > -) -) -) g8 O/ O/ L) {+ k& u! s! C> x> s! ;] z) B) L~ 6^ <( '^ J5 2] 3: h8 #< #< %: c/ #< K! .~ U[ V< c/ v] @^ c/ V< Q[ [] ", +"e% P~ ;^ $~ z{ $~ &~ n] &~ 4 3~ ;^ n] n& 8! n& 4 n& &~ &~ &~ '! n] K~ {) H! i8 j8 Q~ %& O+ v$ > D# ~> [* [* }> ~> }> j, }> ~> D# D# j& ~> k& 4 n& k& l& ;^ P~ k8 j] k] l8 m8 n8 o8 a/ p8 :^ $/ Z] q8 r8 s8 X5 :7 t8 <7 u8 j4 j4 j4 |3 v8 <7 t8 w8 P_ x8 y8 <3 $+ r% M; c% U> X! [{ @~ d~ s; f! {] 6{ [2 {] I~ ^] G] h{ i{ S{ I~ I~ I~ I~ &/ F~ g~ m~ ': z8 A8 B8 C8 D8 E8 F8 G8 H8 I8 J8 K8 L8 M8 N8 O8 P8 Q8 R8 S8 T8 U8 V8 W8 X8 Y8 Z8 `8 9 .9 +9 x7 @9 @9 @9 @9 #9 $9 @9 %9 %9 &9 *9 =9 -9 ;9 >9 ,9 '9 )9 !9 ~9 {9 ]9 ^9 /9 (9 _9 :9 <9 <9 [9 }9 |9 19 29 39 49 59 69 79 89 99 09 a9 b9 c9 d9 e9 f9 g9 h9 i9 j9 k9 l9 m9 n9 o9 p9 q9 r9 s9 t9 u9 v9 w9 x9 y9 z9 A9 9. A; %! n~ U! ~~ ~~ ~] V} 2{ u _* % 6. B9 C9 D9 E9 F9 G9 H9 P_ 88 -< =7 O} O} I9 h4 J9 K9 L9 M9 i> L) b> c% > A z { 5) k! k! ~! ){ l~ O] R] R] ( >_ R] R] L{ Q_ ${ ', %, 5. )$ V+ ~> z- k& }> }> }> ~> j, j, [* j, }> }> }> ~> }> j, [* j, U> Z~ -) -) F, F, F, [* k& m& {) s! C> u! s! K~ b{ w{ 4! .~ 6^ 6^ 3: 3: F^ @^ @^ #< @^ @^ V< K! [] [] Q[ #< c/ j^ V< h^ U[ v~ ", +"l k N9 O9 N9 3~ O9 N9 .& O! .& P5 z~ P9 X! b{ b{ '! L= L= Q~ @~ A) y{ $~ $~ A) 2! K~ x; q; L) > }> }> [* j, }> }> ~> D# D# ~> D# k& n& n& {+ :# `~ z) Q9 q^ g R9 a. N9 `_ S9 T9 O: U9 V9 W9 X9 L{ 23 Y9 q# Y9 q# q# Z9 `9 `9 <7 |3 |3 /- b4 0 .0 +0 @+ %$ |. G# O+ O+ G# !$ l @0 R~ n& {+ u, k* #0 $0 &{ G] S{ G] S{ h{ S{ &{ I~ N{ ]] O] R] ( %0 &0 *0 =0 -0 ;0 >0 ,0 '0 )0 !0 ~0 {0 ]0 ^0 /0 (0 _0 :0 <0 O4 [0 }0 |0 10 X8 20 30 40 50 60 70 80 90 %9 00 a0 a0 a0 b0 a0 c0 d0 e0 f0 g0 h0 i0 j0 k0 l0 m0 n0 o0 p0 q0 r0 s0 t0 u0 v0 w0 x0 y0 59 z0 A0 B0 C0 D0 E0 F0 G0 H0 I0 J0 K0 L0 M0 N0 O0 p0 P0 Q0 R0 S0 T0 U0 V0 W0 X0 Y0 Z0 `0 a .a +a @a #a $a %a &a *a {~ =a 5{ &{ {] G] o~ 6{ I~ /! t _* -a }$ -+ D, : @& ;a >a ,a 6> 'a )a 7% =7 =7 f) 2( !a ~a {a ]a _. ^ 5. >+ v$ O+ % $ O+ 5. h; O- #! 1^ 0{ z] h{ %/ s^ j{ 6] 9; H- l& .; D# }> ~> }> ~> k& ~> D# ~> ~> {+ j& j, j, }> ~> D# j& D# j, }> }> j, > O> 9^ D> v$ N+ U; j, n& &~ @~ H! u! s! H! X! ^a /a (a T[ ~ v~ _a %: #< #< v] %: c/ @^ i^ i^ Q/ %[ :a 3: b( K5 V| { [a }a |a G{ B) B) n] n] +< B) [{ 2! ;^ n] n] 2! 2! {) 83 q; M; [* j, j, j, ~> j, }> }> ~> D# ~> D# k& &~ L= k& t] y{ ;| 1a &^ i N$ =/ w@ i 2a 3a 4a 5a {^ 6a 7a [^ [^ q# q# q# q# k~ ^~ `9 }3 T5 8a 9a J 0a aa [ W} r- $ Y% Y% G# % % h& ;{ ba z~ {+ j& :) j, 6. ca {] G] I~ G] I~ S{ S{ &{ ^] e^ O] R] ( R] da ea fa ga ha ia ja ka la ma na oa pa qa ra sa ta ua va wa xa ya za Aa Ba Ca Da Ea Fa Ga Ha Ia Ja Ja Ja %9 a0 a0 a0 a0 a0 a0 Ka La Ma Na Oa Pa Qa Ra Sa Ta Ua Va Wa 39 Xa Ya Za `a b .b +b @b #b $b %b &b D0 E0 *b =b -b ;b >b ,b 'b )b !b ~b {b ]b ^b /b (b _b :b + O+ O+ :. #! 2{ ~] ^] S{ i{ o{ 2{ 5~ u, k& n& j& ~> ~> ~> }> D# j& D# {+ j& j& {+ {+ ~> D# D# j& D# ~> }> }> ~> }> j, +> H_ 9^ D> N+ N+ O+ M; 4 $~ Q~ b{ X! ;] H! s! s! ;] z) 4! w{ K! V< @^ $< @^ %: v] #< @^ #< i^ [] v~ lb b( b( K5 u] mb { B{ O9 N9 >{ O9 N9 N9 z~ }a ob E{ [{ @~ '! n] P~ M~ B) w{ M~ <{ <{ M~ A) H! @| q; L) [* j, }> }> }> }> }> }> }> D# e& j& k& '! 4 u, N^ m] pb qb k8 E] i y~ a. y~ S9 rb sb tb ub P: n- [^ [^ q# q# H+ q# q# Z9 :7 vb wb xb yb zb Ab Bb d> e~ a3 $ Y% Y% Y% Y% Y% f% l Cb Db j& ~> }> [) <$ <. Eb ._ 6{ Q] G] S{ E~ G] S{ ( ( ( ( R_ Fb Gb Hb Ib Jb Kb Lb Mb Nb Ob Pb Qb Rb Sb Tb Ub Vb Wb Xb Yb Zb :6 `b /} c .c +c @c #c $c Ia %c Ja %9 Ja %9 a0 %9 %9 %9 %9 Ja &c *c =c -c ;c >c ,c 'c )c !c ~c {c ]c 39 r0 ^c /c (c _c :c k& k& k& z{ {+ D# 0! k& {+ n& 4 &~ 4 k& {+ 4 4 {+ k& D# D# D# ~> ;> }> U> +> H_ Ic +> N+ N+ G# 2; n& L= @~ K~ Jc G{ X! w> E) |! `~ B) w{ i^ %: c/ @^ %: $< v] #< #< #< Q[ '^ (_ {7 2] 3: Kc U[ n5 ]/ .~ ", +"{) G{ r^ O! 4 O9 O9 .& (a *: ^a b{ p] z) [{ B) [{ N~ ;^ >^ n5 L~ ~ v~ '^ n5 }> }> }> ~> ~> }> n )~ Lc d% >{ B{ O9 w@ O9 Y{ *^ C{ 3 i O$ B] a. T: Mc Nc Oc Pc Qc Rc }; K{ q# H+ Sc Z9 <7 <7 T5 8a Tc Uc Vc Wc Xc Yc Zc 9> `c x! % Y% Y% % G# > k& k& j& ;+ %& j> a! k& {+ :. X, V- {~ ]] M{ f8 f^ Q{ j{ j{ s^ >_ d .d +d @d #d $d %d &d *d =d -d ;d >d ,d 'd )d !d ~d {d ]d ^d [0 /d (d _d :d + ;, #! 8. +e @e #e )a $e %e &e <3 U} U} F] F] 7' ,3 *e O^ U) =e I' -e U} ,3 5. G# Y% Y% q% a& q% , z; ;e >e ,e f' ~^ m] j& &~ l& @& l 'e t. 0! B{ {) )e !e y{ $~ j& k& L= '! n& {+ j& j& k& j& j, Ic Ic ~e ~e H_ !> N+ O+ % 2; n& A) (a {e O5 ]e ]e Db ^e k& k& m& w{ '^ w] v] %: $< @^ @^ _a _a ;( :3 Q[ /e H5 6^ (_ v~ ~ }> }> }> }> )$ )~ K^ y# w@ N$ =/ j X{ B. ;^ n] L= O$ _$ O$ =/ _$ Mc [e }e |e 1e 2e l- q# }2 [^ Z9 W5 W5 |3 8a 3e I/ 4e 5e 6e 7e 8e 9e 0e `c K& G# G# % % % `% ~> D# ~> z$ >+ .; ;- {+ 4 l& H- ae =7 b^ p4 f8 Q_ 2/ >_ l{ R_ ^ be ce de ee fe ge he ie je ke le me ne oe ;4 pe qe re se te ue ve we xe ye ze Ae Be Ce De c0 a0 a0 a0 Ee a0 %9 %9 %9 %9 Ja Ja Fe Ge He Ie Je Ke Le Me Ne Oe Pe Qe Re Se Te Ue Ve We Xe Ye qa Ze `e f .f +f @f A0 &b #f F0 =b $f %f &f *f =f D0 -f ;f >f ,f 'f )f !f ~f {f ]f ^f /f (f _f :f +> H_ Ic H_ ~e ~e => t; G# Y% L& j& Q~ i] $^ )e if jf jf n& j& j& u! K~ [] A^ kf $< %: %: #< _a @^ lf mf U[ nf (_ [] v~ v~ [] 6^ ~( 5^ ", +"H! ;] n& {+ n& B{ A{ ba of (e *: (a 4! w{ ~ ~ L~ 34 #6 #6 [] 6^ [] pf <( v~ +~ $~ j& [* }> [* [* j, }> }> }> }> j, j, }> *@ ~$ qf I i g P$ D] rf C] E$ '! ;^ 3 O$ B] y~ g sf [e tf uf vf wf l- @+ H+ }2 xf yf zf Af Bf Cf Df ~@ Ef Ff Gf Hf If 0e `c x, % |. |. |. Y% % M; [* U; v$ :. +, n& 4 z{ z{ O9 3~ '! 4 O~ ,1 Jf 2/ R] Tc Kf Lf Mf Nf Of Pf Qf ]b Rf Sf O0 Tf Uf Vf Wf Xf Yf Zf `f g .g +g @g #g $g %g &g *g =g -g ;g @9 s# s# s# a0 a0 a0 %9 Ja Ja Ja 3d >g ,g 'g )g !g ~g {g ]g ^g /g (g _g :g Jg Kg Lg Mg Ng +0 .e 7% O} <3 W} <3 W} T) *! Og Pg S, r- t a& a& a& a& a& a& a& q% % J& 4) Qg D~ Rg Sg S( '! e% l ;{ Tg &@ a. w@ n& l& $~ $~ n& '! 4 $~ '! $~ l& n& k& ~> }> U> Ic H_ H_ ~e ~e => N+ G# J= 2; }> !) (a Ug Vg ]7 |a Db n& 8! E) u! C> B) %: v] @^ %: X} #< ^( Wg Xg Yg X} Zg }> ~> [* L) L) }> ~> }> }> }> j, }> n .h Q$ S+ >^ P~ b. J! P$ N9 y{ b{ y{ +h U^ (_ R9 a. U& @h #h $h @h 4^ @< `5 H+ %h &[ S_ 3e (* 2e /_ @] &h *h =h -h ;h M& G# Y% $ |. D, D, # # % Y% c% N+ >+ ^ +, {+ k& O! ;! 9! n& n& n& {+ *+ Ac K{ V( >h ,h 'h )h !h ~h {h ]h ^h Qb /h (h _h :h i ,i 'i )i !i ~i -- -- -- H= b3 E w y ~! {i ]i ^i /i (i _i :i j, U> +> M; bi bi bi ci N> di ei Ic fi ^7 (( gi hi s! ii nb |a >~ C> u! {) 2! i^ 6^ !/ !( !( '( &: ji +^ i^ H5 .~ ~ ~ ]/ ~( /( !/ w~ ", +"H! ;] l& l& n& k& k& n& K~ {) K~ H! [{ 6^ v~ L~ [] ~> D# D# D# }> }> n .h gf h% ff ;^ b. S~ O9 z{ w{ 6^ w{ 4! '^ 6^ g a. =| li mi ni oi &7 Sc `5 /- x8 Af pi qi ri si ti L, ui vi wi >< A ^; G# % $ E; r# 9. E; e~ |. q% % O+ v$ v$ :* ~> D# D# j& }> ~> ~> {+ {+ {+ u, c. xi yi zi Ai Bi Ci Di ]h Ei Fi Gi Hi Ii Ji Ki Li Mi Ni Oi Ea Pi Qi Ri Si Ti Ui Kh Vi Wi Xi Yi a0 Ee Ee a0 a0 a0 a0 a0 a0 %9 Ja Zi `i j .j +j @j #j $j %j &j *j =j -j ;j >j ,j 'j )j !j ~j {j 1g ]j ^j /j (j _j :j 45 Ej Fj Gj Hj Ij Jj wj _* a& q% q% Kj x; [* ~> D# <$ h> Lj Mj Nj tb Oj Q$ O9 Pj I^ $~ n] l& 4 l& l ;{ X{ (+ i y~ &@ ;{ Qj Rj j, [* [* [* [* L) L) L) M; @| +| +| di Ic ;] }a ]7 }a {) u! Sj Tj Uj ]e s! H! s! H! 2! ~ -( {( {( :3 '( $_ Vj ~ ~ .~ L~ L~ ]/ -( -( .~ .~ ", +"s! C> k& {+ n& {+ k& n& -! H! {) ;] K~ <( 6^ .~ Q/ Wj +} +} '^ O( )^ Xj Yj Z+ '! {+ x; x; M; L) [* [* j> j, ~> j& D# ~> ~> a! gf Qj 8 E$ '! b. S~ N9 Zj pb ^< M~ L~ v~ v~ =/ w@ i `j k .k +k V5 W5 V5 x8 @k #k $k %k &k *k =k -k ;k >k ,k 'k e~ 0e G# >+ }$ =7 W} 9. 9% J& x, q% Y% Y% q% G# )- M; L) j, D# ~> D# ~> D# k& a! o] )k !k ~k {k ]k ^k /k Ji (k _k :k l ,l 'l )l !l ~l {l ]l ^l /l ;< -- -- H= (l 9. '@ L@ b) 5~ t' .! b> _l :l ~> j& D# }> .> $< 7l Mj 8l 9l :$ N9 q^ 0l '! &~ &~ &~ $~ B$ Q$ ;{ S~ i B] (+ ;{ Qj o& [* [* L) [* [* b> b> M; = /; ci ~e ~e Ic u! Tj if nb { u! al Tj ]7 %^ [{ @~ !e u! z) h4 =] 5^ -( -_ ;( ;( !/ L~ L~ ~ ~ ~ {/ 5^ 5^ $] 4! ", +"k& E) j& {+ k& {+ l& l& s! s! ;] E) |! ,^ pf [] Q/ bl cl cl V^ dl el fl N_ pf '! k& q; M& b> %& z$ z$ >+ :* ~> j& j& ~> ~> D# e& c. [# f. k J! w@ w@ S~ gl I! hl Y{ il 3+ jl 0 } kl ll ml nl s8 wb ol _] ^_ pl ql rl sl tl ul vl wl xl yl y. 2~ s& zl m! /_ |^ ,3 ! b+ w j@ % a& q% a& % % M& G# `% [* [* }> i> ~> ~> <$ Al Bl Cl Dl El Fl /k le Gl Hl Il Jl Kl Ll Ml Nl Ol Pl Ql 31 Rl Sl Tl Ul Ul Vl Wl Xl Yl Zl `l m dh w$ 00 w$ dh s# dh a0 Ee .m +m @m #m $m %m &m 6h *m =m -m ;m >m ,m 'm )m !m ~m {m ]m ^m /m (m _m :m D# j& j& }> }> -> 6^ Rm Sm Tm Um Vm Wm Xm Q/ y{ !) C) H, ~+ k O9 P$ 3 w' B# ,{ @e Ym t@ [* [* [* z$ z$ z$ %& N+ = /; O/ /; V; L) E) {) G{ F{ if Zm U~ }a Pj j] J{ {e q] {) X! b{ >2 :{ J] 7^ /( U[ v~ ~ w{ L~ .~ v~ ]/ ~ .~ ~ 4! ", +"D# 4 n& n& n& k& {+ &~ H! ;] u! k& k& L= O( 1: O( O( 1: y^ `m el 0/ ~< T( Q/ Q~ j& 2; p- N+ v$ v$ >+ 6. )$ }> }> {+ k& ~> D# j& {+ c. ;{ hf h% P$ N9 >{ B{ B{ O9 w@ 3+ g n B@ ^$ .n +n @n #n s8 $n %n %n Tc &n *n =n -n ;n >n F5 K^ i/ R| ,n 'n :7 `9 )n r~ =7 D v x y y %$ E; _* a& q% % Y% % O+ x; x; b> > j, !n ~n {n ]n ^n /n (n _n :n :> :> w$ s# `* `* a0 Ee %9 :> gn hn in jn kn ln mn Gh nn on pn qn rn sn tn un vn wn xn yn zn #b An Bn Dh Ak Cn Dn En Fn Gn Hn In Jn ,j Kn Ln Mn Nn On Pn Qn Rn Sn Tn Un Vn Wn 8n Xn Yn Zn `n o .o +o @o #o $o %o &o *o =o -o ;o >o ,o 'o )o !o ~o {o ]o ^o /o (o _o %> :o E; ,@ L@ 5~ #! ~! !_ s4 O} f) 88 g- + c> D# j& D# ~> j& n' @~ 1o 2o 3o 3o 4o / C^ h^ 2! 2! /( M~ J! >{ S~ S~ H- Ac 5o 6o 7o t] h& [* j, V+ ;+ >+ z$ z$ N+ t; O/ Z< p> q; x; E) K~ z) 8o P9 |a Pj }a j] Pj j8 9o ;2 4! [{ K~ D) >2 5( 2! .~ ~ ~ v~ ~ L~ w{ L~ 4! 2! L~ ~ 2! ", +"n& n& &~ &~ l& {+ n& l& m& H! s! k& k& 4 <( |: z^ y^ y^ Z/ a/ 0o ao bo T( v~ m& ~> 2; J= N+ N+ v$ >+ 6. <$ ~> }> j& k& D# D# k& {+ a! Qj .h co >{ O! B{ A{ A{ B{ N9 N9 >{ do eo fo 1+ go go ho io jo ko k/ k/ lo nl &n mo no oo p5 po qo ro so 9a [^ `9 to ${ ,@ D f@ b) 5) h' uo vo V$ % Z* q% q% Y% M& q; 2; )- v$ L) wo xo yo zo Ao Bo Co Do s# w$ s# eh `* eh %9 %9 Xo Yo Zo `o p .p +p @p #p $p %p &p *p =p -p ;p >p ,p 'p )p !p ~p ]j 8k {p ]p ^p gg /p (p _p :p

+ V+ }> D# k& D# j& j& !~ u! Mp Np Op Pp 3o 4o ~^ h^ 2! b{ :{ -] G@ O9 N9 Qp -, g, Rp A# @e 93 <# [* [* }> >+ v$ v$ v$ N+ N+ O/ O/ N> 2; q; w> z) K~ H! if jf Tj }a Uj Uj e] h^ J{ !e K~ X! >] ;: C) C) 4! ~ .~ v~ L~ L~ w{ w{ w{ 4! 2! 2! 2! ", +"j& j& {+ n& l& !) |! -! !) m& l& {) {) H! Sp Tp Up Vp Wp Xp Yp 0o ao Zp X/ B) +, j> % )- v$ N+ `% L) L) `% j, }> D# j& k& j& ~> ~> ~> n e& c. 0! 0! 9! {+ 9! 9! n& {+ O! `p `p q .q +q `m @q #q %0 $q %q ko &q *q =q s8 -q ;q >q ,q 'q )q y, J/ V5 xb yb 3( >+ w! t' 3) f~ f~ 4) K' i' w !q _* q% q% p- 2; q; Qm D, ~q {q ]q ^q /q (q _q :q s# s# w$ iq m s# jq kq lq mq nq oq pq qq rq sq Ik tq uq vq ~m wq xq yq Jh zq tn Aq Bq Cq Dq Eq Fq Gq Hq Iq Wk Jq Kq Lq Mq Nq Oq Pq Qq Rq Sq Tq Uq Vq Wq Xq Yq Zq `q r .r 9d +r @r #r $r %r &r *r =r -r ;r >r ,r 'r )r !r ~r {r ]r ^r /r (r _r :r : .~ 6r 7r !_ W} O} <3 <3 <3 <3 a~ U} a~ 8r 9r 0r ar a~ br >@ r# u# h& j, D# j& D# }> U> }> cr dr er Pp fr gr hr ir hl ~+ <{ 4! N~ B~ jr hl S+ /+ ]$ l k& 5 5 [* j, i> H) g8 g8 g8 t; t; A> O+ G# p- G# .; K~ z) s! ii fi G{ 0i kr Cb u{ 2! {) T^ H! {) 7> !) n] N~ w{ ~ ~ L~ w{ 4! L~ L~ n5 34 L= N~ B) ", +"k& D# k& {+ !) @~ H! K~ $~ l& L= 4! 2! 8o k] @} lr mr nr or Xp 0o oi pr 8( b{ Q> v$ % v$ >+ z$ b> L) M; M; [* }> }> j, j, j, j, }> ~> }> ~> }> D# j& j& j& D# D# j& n& N! -~ o] A{ qr |_ rr sr tr ur vr wr $n s8 `9 }3 Sc e@ /- xr H+ yr zr ~* 7% 9{ Ar Br a+ Cr Ip 3) @] r) |{ k! k! 5) L@ w + Y% a& '- Kj 2; x; Dr Er Fr Gr Hr Ir Jr Kr Lr Mr Nr Or Pr Qr /0 Rr Sr Tr Ur Vr Wr Xr Yr Zr `r s B0 .s +s @s #s w$ :> w$ w$ s# 00 jq $s 00 jq lq %s &s *s =s -s ;s >s ,s 's )s !s ~s {s ]s Yr ^s >d /s (s _s (9 :s @ ,@ }, j, ~> k& j& j& ~> }> [* |( t .t +t @t #t Rm $t #( ;{ .& %t &t #( Zj =~ c{ '+ X{ r@ }> }> j, }> ~> U> g8 g8 .! .! g8 O/ O+ O+ % Y% % *> {) @~ H! u! H! y{ N9 A{ *t 4 !) m& K~ H! z) n& {+ ;^ <{ w{ L~ w{ 2! 4! L~ ~ L~ >^ +< +< M~ ~ ", +"j& j& k& &~ n& K~ H! {) m& $~ n] y{ z) ;] ba g{ =t or U& /: 43 0o -t ;t O( K~ .; O+ G# v$ >+ z$ %& L) M; x; x; M; [* L) L) L) L) [* [* j, ~> }> }> ~> D# }> }> j, j, j& j& -~ T~ [# >t ,t 't )t !t ~t {t ( %n vb `9 `9 -# /- /- `5 ]t ^t /t (t 7/ _t :t `l st st tt ut vt wt xt yt zt At Bt Ct (p Dt Et Ft Gt Ht It sn =d Jt 8g Kt Lt `e Mt Nt Ot Pt Ke Qt Rt Kh On St Tt Ut Vt Wt Xt Yt Zt `t u .u +u [4 @u #u $u %u &u *u =u -u ;u >u ,u 'u )u !u ~u {u ]u ^u /u (u _u :u k& n& D# D# j& ~> }> |( qu .t ru su tu uu vu wu P$ X{ 9 9 0 n Q$ '+ }# k [# ~> }> j, D# }> bi Z~ .! .! Z~ g8 O/ O+ O+ % Y% 4; >+ ;] @~ @~ ;] ;] z) y~ >{ A{ O^ 0^ O^ ~) z) z) `~ {+ $~ A) L~ 4! L~ w{ 2! ~ [{ B) y{ '! '! A) 4! ", +"n& k& k& k& j& l& m& 1! &~ l& 4 Q~ 1! -! A{ O9 xu 0o Xp 43 U& yu zu U( <( ;] Z) G# G# O+ N+ N+ -& M; x; q; q; x; M; x; M; M; L) M; %& >+ V+ V+ i> 5. i> <$ .; z$ > 5. += h> +, Z! E) Au 3~ Bu Cu Du Eu %2 ]~ }2 Fu }3 v8 j4 Sc ]t Gu Hu wb Iu Ju Ku Lu Mu Nu Ou Pu Qu Ru !_ Su p) G' p! G5 y! M) 2( [' z. # Tu Uu Vu Wu Xu Yu Zu `u v .v +v @v #v $v %v &v *v =v -v V8 ;v >v ,v 'v )v !v ~v {v ]v ^v /v (v _v +m Xo :v w ,w 'w N) ={ m{ )w !w ~w {w ]w ^w {) -q wf 88 f) =7 /w s4 s4 =7 |^ l- ,3 c- `= f) O} a~ u# M+ :* z- k& k& {+ n& ~> D# j, P~ (w _w :w w> C> s! ;> O> -) g8 Z< Jg Z< Z< A> O+ t; 4; %> O> u! z) @~ -! &~ y{ >{ B{ O! d% d% I= r^ N! ;~ D# k& 4 n& N~ N~ +~ <{ +~ M~ A) N~ 1! &~ n] Q~ Q~ ", +"l& k& j& {+ k& j& k& n& &~ 4 &~ l& l& 4 O! B{ #@ Yp oi ao 6w 7w #} 8w ~ s! j> G# % G# N+ z$ -& x; q; 2; q; q; x; M; M; L) L) [* b> >+ ;+ ;+ 5. 5. ;+ 5. 6. ;+ ;+ 5. f/ i/ >t f/ )$ t@ -, F> wf 2( 9w ;^ ^- 0w aw Gu bw W5 |3 |3 W5 9a %n cw Tc dw ew fw gw hw iw jw kw lw mw +! nw ){ `9 ow _] =7 9. W_ &, pw qw rw sw tw uw vw ww xw yw zw Aw Bw Cw Dw Ew Fw Gw Hw Iw Jw D6 Kw )v !v Lw Mw Nw Ow Pw w$ s# dh Qw Rw Sw Tw Tw Uw Vw Ww Xw Xw Ww 5v Vw 5v Yw Zw }m `w x hg We .x +x @x D0 #x D6 $x %x &x *x =x -x (d ;x ue >x ,x 'x bv -m )x !x ~x {x ]x ^x /x (x _x :x zr ( Hx Ix Jx Kx Lx Mx Nx Ox q# 88 f) /w #e t5 88 @+ &7 Px Qx G$ W} r# E; Y% N+ ;+ ~> ~> j& &~ {+ ~> j, L) n& Rx /: or Sx Tx Ux Sx #} $} Vx Wx Xx Yx Zx `x H@ `= |! x> C> u! x) Ic 9^ 9^ -) Jg Jg Jg Jg A> O+ N+ N+ Jg y g8 C> z) N~ n& $~ 3~ O! O9 >{ 4 &~ n] O! -~ -~ j& k& k& D# k& l& n] ;^ ;^ n] '! n] &~ l& &~ $~ &~ ", +"&~ l& k& {+ n& j& {+ {+ &~ L= L= n] &~ n] O9 N9 N$ .y ao +y @y #y #} $y B) C> D> G# % O+ v$ ;+ %& M; x; x; x; L) [* [* [* j, j, j, :* >+ >+ ;+ ;+ ;+ ;+ ;+ ;+ ;+ ;+ ;+ ;+ D 9% / 0> h~ >: %y vb G~ .{ &y *y =y -y y= ;y yb 9a |3 |3 /t =q $n ko >y ,y 'y )y !y ~y {y ]y ^y J/ /y 8a &q k/ jo (y t) >@ N^ nf _y :y f iy jy ky ly w$ :> my ny oy py qy ry Uw Ww sy sy ty uy vy sy Xw Z8 wy Vr xy yy :m zy D0 D0 Ay By #f yq Cy Dy &x Ey >j !c Fy Gy Hy >x Iy Jy ]j Ky Ak Ly Hh My Ny Oy Py Qy Ry Sy Ty Uy Vy Wy Xy Yy Zy `y z .z +z @z #z $z %z &z *z =z -z ;z >z ,z 'z )z !z ~z {z ]z ^z /z (z _z :z D# j& D# D# j, M; j& 2 x@ T: nz oz pz qz rz lr sz tz uz Zx do fo H@ L^ E) 9^ x) x) Ic Ic Ic /; Z< Jg Jg Jg Jg Z< O+ O+ G# Jg y Z< 9^ H! b{ $~ l& b. N9 N9 O9 O! l& '! N9 -~ -~ 0! {+ k& k& j& k& l& 4 l& l& 4 l& l& &~ $~ n] n] ", +"{+ n& {+ {+ n& 9! N! 0! m l 5# b. $~ +< gl y~ ;{ O$ vz Yj ,< Yj mr wz K~ x> Z) O+ G# N+ v$ z$ %& `% U; `% `% %& n% n% !$ c- c- M+ c- F $+ j@ ;+ ;+ Z) Z) 5. -+ 03 &$ xz yz o~ (/ zz Az Bz Cz M{ Dz H* Ez Fz Gz Hz Iz [3 ^_ Jz xb 9a yb 3e 3e Kz Lz Mz no Nz Oz _o Pz Qz Rz Sz Tz Uz Vz Wz Xz (~ w U} ;* Yz Zz `z A .A +A @A #A $A %A &A *A =A -A ;A >A ,A 'A )A )x !A ~A {A )v lt qd fd =4 ]A ^A /A (A st _A :A B ,B @{ .{ 'B Fc )B !B ~B {B cb ]~ @+ 88 |^ t= |^ 3( *7 T5 ]B ^B /B (B _B fb :B [ F# ! j, j, }> }> }> j, j, n% E n% n& =! j, +> bi +> bi x; % 4; 4; ^; ^; 4; G# G# % 4; %> ^; Ic s! <{ =~ N9 I! &t p] &t C{ &~ $~ 3~ {+ N! 4 n& l& O^ e% a^ '$ j& k& N! 5# r@ co J! S+ m% ff ", +"n& 4 n& k& j& T~ O9 A{ g% Q$ Q$ E$ &~ I^ |_ gl f. &@ =| ,< Yj /: ,< 6B s! x) Z) G# G# O+ N+ N+ v$ v$ v$ @ @ @ u E; E; E; r- r- r- J& E J& L+ L+ 7. 8% a+ G' F5 ow R] 9] A8 7B 8B 8B Y] 5/ >1 8, 9B C. 0B aB bB cB dB eB Bf fB 3e &n 3e S_ Af Tc jo jo %y 3e gB G9 hB iB oo jB dw kB lB mB f@ ;, /w 3$ nB oB pB qB rB sB tB uB vB wB xB yB zB AB BB CB DB EB FB %p GB HB IB JB KB ]j 6s ~p LB MB NB OB PB QB 9 RB n7 4v 6v SB TB UB VB UB WB XB YB ZB `B C .C +C @C #C $C %C &C xh |c *C JB =C -C ;C >C ,C 'C 'C hv )C !C ~C {C ]C ^C 6g /C (C _C :C j, j, )$ 9; %{ UC s& _ d) `! p' VC WC XC N_ or x@ O9 w@ a. p] x> x> =! }> L) M; M; x; M& % % Y% % % % O+ G# Y% Y% Y% ^; Ic C> b{ O9 =/ 3~ [{ @~ [{ >^ $~ n& l& '! &~ n] n& l& P^ s] t] *@ j& k& h% ;{ hf X{ A@ Q$ {$ 0^ ", +"n] ;^ k& k& k& B{ >{ >{ J! X{ ;{ ;! n] P~ k8 0i k w@ 3+ ,< Yj ,< Q| +h C> u! U; Y% % O+ N+ N+ v$ v$ v$ u YC YC ZC J& J& J& J& J& J& J& E : `C D _~ .D z] :( +D @D c] 5/ Y] A8 A8 #D 8B 7B V( 7. $D %D &D *D =D wb -D ;D >D ,D *n &n nl 8a /t =q cw $n O] 'D )D !D ~D {D 6w kB ]D ^D /D m! >@ i- /& 3& (D _D :D E >E ,E 'E )E !E ~E {E ]E ^E /E (E _E :E 9^ U> [* M; x; M; x; M& % % Y% Y% % G# O+ % % Y% Y% G# H_ x> p] y~ w@ *t F{ b{ A) Q~ n& k& n& +< P~ '! n& 4 :# 7o t] <$ j& {+ g% hf hf N< N< N< @& 0^ ", +"$~ I! B{ T~ o] h% h% Q$ co f. J! O9 !+ !+ %^ oE A@ a. a. =| Yj pE }/ &~ +, .; |. I+ M+ M+ E; F r- F F r- ZC ZC ZC J& J& qE qE rE 24 .( p) n~ ^] n{ 7] u^ Z] 7] 9] A8 7B A8 Y] 9] 7] 9] #D sE tE F! G' uE vE wE xE yE zE AE BE CE mB Kz yb 9a |3 |3 /y >q 8a ^& G* r8 DE jB /y EE FE GE HE IE 4) 2 JE KE LE ME NE OE PE QE RE SE TE UE VE WE XE YE ZE `E 5n F .F +F eA JB Ah bv @F #F $F %F &F *F =F -F 5q ;F >F ,F 'F )F !F ~F {F xt ]F ^F $m 9 /F (F /F 5A _F :F m .F 4F 5F 6F 7F 8F 9F 0F CD aF xk bF [g 'm cF dF eF fF gF hF iF jF kF lF Rr mF nF oF pF qF rF sF tF uF vF wF xF yF zF AF BF CF DF EF FF GF HF IF JF KF LF MF NF OF PF QF RF SF TF UF VF WF XF YF ZF `F G .G +G @G #G $G %G &G *G so ${ =G -q -G #n (y ${ :7 J/ >: Z9 ${ (y {! ;G f. ( >G ,G 'G 5) i) y >+ U; :* > b> %& )G !G ~G {G 9] A8 ]G */ :] S! {! b' t) M^ Y& W& 2 '! ;> 9^ Ic ~e = = = -& = c% % % 4; ^G Jg ^; 4; 4; 4; 4; Y% N+ P/ y) )) A) %t `g `~ $~ N~ {+ n& n& {+ P~ >^ , e% U) @e @e @e s@ a^ s% V) .h gf co ;! [# U+ , ", +"$~ n] T~ o] h% hf gf Qj T~ B{ >{ k n. j% /G #( (G N9 B{ O$ xu _G >^ *+ h; N+ # E; r- r- J& E E E E E J& E W$ J& @> < :G +! 1~ F~ O] '_ */ 7] 8] 7] 7] 5/ Y] 7B 8B #D 9] 9] 9] 8] c] q Uz Uz Af 3e 8G 9G 0G aG *7 bG cG dG g^ (> eG fG gG hG iG jG kG lG mG nG oG pG qG rG sG tG u[ uG (} vG hA 9n Kt wG xG +f 3F Hh yG )c zG AG BG CG DG uD EG DG FG GG 5n HG IG 4v =u JG KG R4 LG ya MG NG OG PG QG RG SG TG 19 td UG *C *C D0 VG WG Kk XG YG ZG `G H fv M0 |c %b .H Qk {C Fh +H @H #H $H %H &H *H =H -H ;H >H ,H 'H )H !H ~H {H ]H ^H /H (H _H :H U; b> = A> Z< A> O+ G# G# G# % 4; ^G ^G 4; ^G ^G %> Y% Y% t; .! !~ ,] @~ X! ;] l& &~ $~ n& 4 $~ {+ 4 P~ `= Lc 7o 7o 7o P^ Lc 7o s] H{ EH .h C$ ;^ $~ n] P~ ", +"'! &~ o] A{ B{ hf gf h% 'e T~ B{ l] q^ A~ 3: #( X{ >{ >{ O9 }/ :e ;^ X> 6. % e~ E; r- J& E E D D D D E E { FH !] T! Q] f^ X] O] R] ,_ n{ Z] 8] 7] 8] 9] 5/ #D 7B 7B 7B 7B A8 A8 #D Cz da ,: T) Ac GH HH IH _. Ig JH KH '{ `9 ow /y 9a yb Uz Uz LH &n &n =n MH NH ]D OH PH QH RH SH TH UH VH WH XH YH ZH `H I .I +I @I #I $I %I &I *I =I -I ;I >I Gn ,I 'I |j )I !I ~I {I ]I ^I ,c /I (I _I :I J ,J 'J )J !J ~J {J ]J ^J eE |8 /t /t /J to }3 `9 `9 }3 *7 (J _J :J x DJ >m Qt EJ FJ GJ HJ IJ K6 5m JJ KJ LJ MJ NJ OJ PJ -p QJ RJ SJ TJ UJ r3 [A VJ N4 WJ XJ YJ ZJ `J K 7q .K Eh +K @K #K $K {p *C &b E0 {p %K Yr uh &K *K jd =K 8h -K ;K >K ,K 'j 'K )K !K ~K {K ]K ^K /K (K _K :K q J/ K{ ', !! K' M@ L+ -- Y% O+ z SK TK #D #D 5/ 5/ 9] 5/ 9] 8] ': %/ &/ %/ t^ t^ k{ ,_ ,_ ,_ d^ ~~ k! '@ z. @> 2> 2> M+ M+ M+ e~ e~ e~ e~ e~ e~ e~ # |. Y% 4; i/ )) H! u! C> j& k& k& u, a^ i- -* ;^ ff d+ '3 UK l 8 z# ~$ ~$ ^3 H{ V) 8 G@ !+ s. 3~ ai ", +"A{ T~ 4 {+ k& T~ -~ I) Y~ Qj gf co w@ w@ :{ B) }a {) s! C> i/ V> 8> F -a E; E; E; J& @> ' : [ J' (~ ${ /] /] O] R] ( '_ S{ ^] i{ h{ h{ j{ Z] 7] 7] 8] A8 5/ Y] 8] 5/ A8 #D 7B 7B #D Y] 5/ 8] 5/ yc VK WK nw )n ow (y nl 3e Bf ko 4( XK YK ZK *n Cf `K L .L +L @L #L $L %L &L *L =L -L ;L >L ,L 'L )L !L ~L {L ]L ^L /L (L _L :L UL VL WL XL YL ZL `L M .M +M @M #M $M %M &M 3H *M =M -M ;M >M ,M 'M )M !M ~M {M ]M ^M /M (M _M :M + O+ G# y Y] A8 7B #D A8 Y] 5/ Y] 9] Y] Y] 7] Z] j{ n{ j{ j{ j{ n{ n{ n{ ^] V( ]3 2{ I' aM bM YC E; E; D, D, D, e~ e~ e~ e~ -a -a a3 t v$ h; H! u! C> j& k& j& 7o 7o P^ L= mb L= k% j Q$ hf .h Qj Ym t] ~$ ~$ ~$ t] l '+ X{ k B{ ", +"T~ o] k& {+ {+ o] I) I) Qj gf N< A{ A{ >{ #~ r] if hi u! u! h> r& ^ # D, E; r- J& E { ~: D! ={ :] X] P] ]] O] R] ]] O] Q{ ^] ^] S{ S{ S{ j{ j{ 7] 9] 9] A8 A8 A8 8] 8] A8 #D #D 7B 7B A8 5/ 8] 8] j{ |{ F5 cM `9 ol #n Bf rl dM FE eM fM gM hM iM jM ]D kM lM @L mM nM oM pM qM &L rM P. sM tM uM vM wM xM yM zM AM BM CM DM EM FM GM HM IM JM KM LM MM NM !9 OM 7L ]C PM >d QM RM SM TM UM VM tq WM XM YM ac Th ZM `M /9 N .N 9 +N @N #N jL Tr $N %N &N *N =N -N ;N Dh Ok =b 4c F0 2c #f >N ,N 'N )N !N ~N {N ]N {C GJ ^N (m /N (N _N :N D# 7o 7o P^ $~ #6 +< k% Q$ Qj .h EH gf Ym s] t] ^3 ^3 ^3 ZN N< X{ f. A{ ", +"b. .& 4 n& l& A{ T~ Y~ gf hf ;{ B$ N! 9! ., Q~ `g G{ X! H! 5. >+ # P+ P+ u W$ xz 6~ <] U_ U_ C~ I~ u4 <^ :] e^ '_ R] O] N{ f^ P{ D~ S{ i{ n{ n{ Z] 5/ Y] Y] 5/ 9] Z] 8] #D 8B 8B 7B #D Y] 5/ 5/ 7] :^ r) xz s) +! &! *q `N Vz O .O +O @O #O Hx $O %O &O *O =O -O ;O >O ,O 'O )O !O ~O {O ]O ^O /O (O _O :O C kO lO 1c mO nO oO pO qO rO sO VJ tO uO vO wO xO HJ `e yO zO AO 'N (m BO Ph >b em ud }c CO DO EO Ko yy `7 bm FO bA (s GO HO IO JO KO LO MO NO OO PO QO RO SO TO UO VO WO XO YO ZO `O P .P +P @P !C #P $P %P &P *P =P -P ;P >P ,P 'P )P !P ~P {P ]P ^P /P (P _P :P

: -e YN ZC D, r- % -- -- qP V( 8B rP sP 2J #D A8 Y] Y] 5/ 8] 5/ 8] 7] Z] 7] n{ n{ i{ i{ z] P{ &/ d^ d^ Us D~ P{ f^ @{ DH b^ J' .( W$ 2> E; F F tP uP e~ # $ # ~) ~) .; D# i> )$ z- a^ d% v{ H, ff '+ ;{ Qj vP V) V) ~$ t] t] X+ G$ G$ n z# @& [# N! ", +"&~ l& l& $~ &~ >{ N9 B{ N< N< ;{ d% {+ {+ 4 $~ n& +, E) => |. e~ D, @ ~' r! A! u~ #{ Q] G] &{ I~ ^] &{ G] h{ j{ '_ O] O] O] O] /] e^ i{ Z] 7] 7] 7] Y] A8 Y] 8] Z] Z] 5/ #D #D #D A8 Y] A8 A8 Y] C5 wP ~~ b+ [ w ( xP yP d( p) {! %! @+ zP S> 3) AP BP rM CP &L &L CP &L 'O DP )O T& EP FP GP HP IP JP eG KP LP M' MP NP OP PP QP RP SP TP UP VP WP XP YP 6s ok Se ZP #x Wk #b }g 1c 1c `P 2g Q .Q +Q @Q 3j 2s #Q $Q yk %Q [A &Q *Q 20 =Q -Q ;Q ta 69 >Q ,Q !s 'Q )Q !Q ~Q {Q ]Q ^Q /Q +x wL (Q _Q :Q ]c j, ~> k& {+ {+ ", +"4 n& &~ n] P~ Y{ a. O9 ;{ Q$ hf k& n& {+ 4 {+ [* I+ x! A P+ D, D I' :~ t~ {~ F~ #{ %{ G] G] ^] S{ ^] S{ i{ n{ k{ ( R] R] ( R] ,: 8] 7] Z] 8] 9] #D A8 Y] 9] Z] 7] 5/ #D #D A8 A8 Y] A8 Y] Y] A8 Y< >R [ b) 5~ 3) I' Ip Ip ,3 ,R 'R )R !R ~R {R ,B ]R ^R *L /R (R (R &L _R :R S >S ,S 'S )S !S ~S {S ]S ^S /S (S _S :S >t j& V) .h .h gf )~ t] ~$ ^3 ~$ s] c. k& k& }> D# D# j& k& ", +"m& !) y{ Q~ Q~ x~ y~ b. co co #& k& Q> )$ `% J= J= K& !q pS qS 4) b^ >: #{ @{ P] :] V{ Q] I~ I~ I~ :] P{ &/ %/ :( ,_ ( ,: ~7 ( ( ,: 7] n{ 7] 5/ A8 7B 7B Y] 8] 7] Z] 9] #D 7B A8 Y] Y] A8 A8 rS ]G sS Q5 M^ [ T ,T 'T )T tD ;F !T ~T {T ]T ^T z0 hd /T dI (T sh _T ~Q :T $f H0 8 xk U ,U 'U )U !U ~U GH $$ >@ 0^ i' z! 4) -e F] J& _* H= $+ {U z8 Y] #D 7B A8 9] 8] 7] 9] A8 8B 2J 7B #D A8 5/ 8] 9] j{ S{ &/ */ [3 */ h{ S{ h{ d^ 5{ %{ ]] F~ #{ @{ #{ ]U T{ 4{ 4{ ~~ /! o! + uP P+ # |. M+ $+ ^ _. .; <$ .; o' D# o& V) z# z# H{ ~$ ~$ s% s% '$ z- k& {+ {+ k& D# k& n& ", +"K~ K~ b{ @~ z) M~ :e >^ N9 O! a! :* G# % a3 -a D, =} xP +! n~ m~ 1~ l~ u~ #{ <^ G] I~ ^] S{ S{ O{ /] /] O] ( ,: R] R] ( R] R] ( k{ j{ j{ Z] 9] A8 #D #D 5/ 9] S5 S5 ^U #D #D Y] 5/ 5/ #D ~G (^ F' vz M< [/ /U =* -e v4 m~ u~ ,1 (U _U :U b 4c #f `P .x 49 Y7 IU pd JU KU LU MU NU OU PU QU RU SU TU UU VU WU XU YU ZU `U V .V +V @V #V $V %V &V *V =V -V ;V >V ,V 'V )V !V ~V wT {V ]V ^V /V (V _V :V ^3 ~$ t] ~$ ~$ t] &, 4 n& n& 4 k& j& D# j& k& 4 ", +"[{ b{ b{ b{ b{ L= ;^ (> Z! %& M& Y% G# O+ D, D, D z! !{ n~ 1~ l~ 1~ l~ l~ @{ 8{ I~ I~ I~ ^] h{ '_ R] O] O] ( ( R] O] /] O] O] ( 3/ 7] 7] 7] Y] #D A8 Y] Y] HV S5 S5 i4 A8 Y] 5/ 9] 5/ Y] IV JV KV LV MV h {7 NV +& 8~ q~ B! e! t8 NK OV PV QV RV SV TV UV VV WV XV YV ZV `V W .W Yx @ +W @W #W $W %W &W *W =W -W ;W >W ,W 'W )W !W ~W {W ]W {W ^W /W (W _W :W d eW 2W fW gW 4j We ig ig 8 hW iW jW kW lW mW o; nW oW pW qW rW sW tW uW vW wW xW yW zW AW BW CW DW EW FW GW HW IW JW KW LW MW NW OW PW QW RW SW TW UW VW WW XW YW HQ ZW `W X .X +X @X #X $X %X &X *X =X -X ;X >X ,X 'X )X !X ~X {X ]X ^X /X T, (X _X :X @ J& I+ O+ O+ N+ v$ ;+ += ~> ~> $& 93 ~$ ~$ t] s] U) $~ l& n& 4 k& D# j& k& n& l& ", +"C) C) h^ ~/ %] H, .; %& # # # I+ M+ z D i' b^ n~ n~ m~ ]U V! S! l~ 4{ f^ O{ I~ &{ o~ Q] &/ e^ O] R] R] R] ( ( /] /] O] O] R] ,_ Z] n{ Z] 5/ A8 Y] Y] 5/ Y] S5 8X rS 5/ 9] GV ^U i4 9X 0X Q( aX bX cX m8 dX V] 1w }2 6~ q) <~ ^. eX fX yc gX hX iX jX kX lX mX nX aG oX pX J$ qX rX sX tX uX vX wX xX yX zX AX BX CX DX EX FX GX HX IX JX KX ^W IX .T LX MX NX OX PX QX QX QX ud vd RX 0W Sk SX oR QX :T ZM TX TX UX VX 71 WX o3 &Q XX YX %K nO >N ZX yk CO `X Y _s Wk .Y sd zy 2F oR Bh sn +Y @Y }Q #Y $Y %Y ^C &Y *Y =Y -Y ;Y >Y ,Y 'Y )Y !Y 3I ~Y {Y ]Y ^Y /Y (Y _Y :Y : -# =7 }$ ' M+ I+ # G# -& f% )$ t@ t@ 9% G$ G$ s% U) $~ {+ k& l& n& D# j& {+ &~ N~ ", +":{ K] -_ %Z }J O+ % O+ M+ E; E; z #+ L@ *! m~ l~ 1~ m~ l~ {] &{ {] &{ G] ^] I~ G] G] V{ /] R] R] R] ( ( ( ,: O] /] O] O] R] ( ,: j{ h{ n{ 9] Y] Y] Y] 9] 9] S5 8X 5/ HV 5/ 5/ &Z a] *Z =Z -Z ;Z >Z ,Z 'Z b nZ ]Q !Q KU oZ 4c pZ qZ rZ sZ tZ ;Q te uZ vZ wy 30 wZ C6 =f xZ `P yZ Cd &b >N CD 2c 4c |c zy }Q zZ 19 AZ Mk BZ CZ DZ EZ ak pa FZ GZ HZ IZ Qb JZ KZ LZ MZ 15 NZ OZ PZ QZ RZ SZ TZ UZ VZ WZ XZ YZ ZZ `Z ` .` +` @` #` $` %` &` *` =` -` ;` >` ,` >` '` )` !` ~` {` ]` ^` /` (` _` :` <` [` }` |` 1` 2` 3` 4` 5` 6` 7` 8` 9` 0` a` b` c` d` e` f` g` h` i` j` k` l` m` n` o` d* C& p` q` f! ,n #{ u~ l~ s~ #! '{ >+ Vj r` s` t` u` [e v` w` x` *0 FV 9] Y] 9] 5/ 5/ 5/ 9] 5/ 9] n{ j{ h{ ^] I~ ^] R{ R] ( 6] ^] G] D~ O] O] O] O] F~ @{ G] G] I~ &{ G] 5{ F~ @{ #{ 1~ l~ u~ u~ l~ 1~ /~ h~ y` J& E; D, -a a3 e~ -& >+ 5. D# '$ &~ '! &~ n& {+ {+ {+ j& j& j& -! H! ", +"#] >] H) A> Z< A> O+ N+ E; E; r- w k! f! q~ n~ m~ l~ 1~ l~ 6{ ^] I~ G] G] I~ G] G] ^] e^ R] ( ( ( /] ]] R] R] R] O] O] R] ( R] ,: j{ h{ j{ 7] 5/ 9] 8] Z] 7] i4 i4 ^U 5/ Y] Y] z` A` [_ B` C` D` E` ,Z F` G` Q| O$ H` #7 /& &! f~ f~ I` J` K` L` M` N` ^Z *L O` P` Q` R` L~ D] S` T` U` V` W` xX 2Z X` Y` Z` `` .. .+ .@ .# .$ .% .& .* .= .- .; .> ., .' .) .! .~ .{ .] .^ .je Ed =b Tk !Q / .( ._ .: .< .[ .} .| .51 1 .2 .dL 3 .4 .5 .6 .`P Pk Wk DR #f E0 3c E0 3c 3c |c 3c 2c 3c 7 .8 .9 .0 .a .b .c .d .e .f .g .h .i .j .k . Q l .m .rL 4p n .o .p .q .r .s .t .u .v .w .tq x .y .z .A .B .C .D .E .F .G .H .I .J .K .L .M .J .N .O .P .Q .R .S .T .U .V .W .X .Y .Z .` . .....+..@..#..$..%..&..*..=..-..;..>..,..'..)..!..~..{..]..^../..(.._..:..<..[..}..|3 c^ n~ B! m~ m~ m~ n~ ~{ `| dl |..1..2..u` u` 3..4..5..6..7..8..5/ #D A8 #D Y] 8] 8] 9] 7] i{ S{ S{ h{ i{ i{ %/ ( ( a] h{ I~ T{ F~ O] ]] O] O] f^ ^] ^] ^] ^] G] 5{ #{ @{ F~ l~ u~ l~ l~ u~ 1~ m~ m~ n~ O) `> Z; tP D, e~ G# G# O+ %& {+ n& {+ {+ n& k& {+ {+ n& {+ k& w> s! ", +"N+ O+ I+ N+ N+ N+ N+ M+ E; D t' f! 1^ :~ q~ ){ n~ n~ m~ u~ 5{ S{ I~ ^] I~ ^] I~ G] I~ d^ e^ >_ R{ R] R] ( R] O] R] O] R] R] ,_ k{ k{ %/ d^ i{ j{ Z] n{ Z] n{ Z] S5 i4 i4 Y] A8 `] 9..0..a..b..c..d..e..LV f..g..il Y( ~1 /e h..@+ p) g~ $n i..j..dJ k..l..m..n..n..o..p..Cf 5# q..r..s..t..u..v..xX 2Z X` w..x..x..y..z..A..B..C..D..E..F..G..= .H..PP I..J..K..L..M..N..O..P..Q..je R..S..>b 4c 2c T..U..V..xd W..X..Hy 1 .]6 Y..Z..`.. +.JJ ed Eh .+.:m Pk #f 3c >b 2c Ti &b *b sd 4c ++.@+.#+.$+.%+.&+.*+.=+.-+.;+.>+.8W ,+.'+.kZ )+.Ey !+.~+.{+.]+.^+./+.1p (+._+.:+.<+.[+.}+.|+.1+.2+.3+.4+.5+.6+.7+.8+.9+.0+.a+.b+.c+.d+.e+.f+.g+.h+.i+.j+.k+.l+.m+.n+.o+.p+.q+.r+.s+.t+.u+.v+.w+.x+.y+.z+.A+.B+.C+.D+.E+.F+.G+.H+.I+.J+.K+.L+.M+.N+.O+.uj P+.Q+.e* R+.S+.T+.U+.V+.W+.X+.Y+.6~ G! @! @! G! 7% 6, Z+.fr `+. @..@.+@.:: @@.#@.$@.%@.&@.TK A8 *@.#D 5/ 8X S5 Z] t^ h{ S{ S{ i{ j{ n{ k{ ( ( l{ %/ S{ <^ P] R{ P] 8{ Q{ D~ S{ h{ h{ S{ I~ D~ 4{ @{ /] @{ #{ l~ l~ l~ l~ m~ n~ n~ _~ D! D z. ZC E; M+ N+ O+ M& x; :* i> u, 7> &~ ~) |! k& k& n& E) x> ", +"E; E; E; E; E; E; r- J& x #! (~ 1~ 1~ n~ q~ q~ n~ n~ l~ #{ <^ ^] G] G] I~ I~ G] I~ ^] S{ S{ S{ P{ O] R] ( /] O] R] R] R] l{ n{ Z] m{ R] R] '_ i{ i{ n{ 8X R5 S5 8X 8X 8X =@.*a -@.;@.>@.,@.'@.pz n8 rz 9/ 9/ rz @} &^ 4: [] M~ <{ O^ 9~ )@.vb ow >q !@.~@.{@.]@.^@./@.(@._B _@.:@.<@.[@.}@.W` v..|@.xX X` Z` x..1@.. .2@.3@.4@.5@.6@.7@.8@.9@.0@.a@.H..IX JX b@.c@.d@.e@.f@.g@.h@.QX 5c i@.;b _ .>b j@.j@.6c k@.QU l@.m@.n@.]6 Z..o@.p@.q@.r@.Ft 'j s@.t@.>d %b sd F0 3c 1c td >b F0 u@.v@.w@.x@.Vl me y@.z@.A@.B@.C@.D@.j@.E@.F@.G@.`7 H@.I@.J@.K@.L@.M@.N@.O@.P@.Q@.R@.S@.T@.U@.V@.W@.L1 X@.Y@.Z@.`@. #..#.+#.@#.##.$#.%#.&#.*#.=#.-#.;#.>#.,#.'#.)#.!#.~#.{#.]#.^#./#.(#._#.:#.<#.[#.}#.|#.1#.2#.3#.4#.5#.6#.7#.8#.9#.0#.a#.b#.c#.d#.e#.f#.g#.h#.i#.j#.qM k#.l#.m#.jM jS n#.dw o#./< J' M^ 2+ p#.q#.Z+.r#.s#.t#.u#.v#.w#.x#.y#.z#.7i A#.B#.A8 8..3J 8X S5 R5 R5 C#.C#.S{ j{ Z] n{ j{ k{ ,: R] R] ( O] f^ S{ z] h{ ^] S{ ^] S{ S{ ^] S{ I~ I~ #{ @{ F~ u~ l~ u~ u~ 1~ l~ l~ m~ n~ !{ m~ .] ){ U< bM @> E; E; N+ c% % % >+ h; #] D#.E#.s! j& n& l& j& ", +"r- r- r- r- r- E; E a~ f! 2{ f! B! n~ n~ m~ 1~ 1~ 1~ u~ #{ 6{ ^] ^] G] I~ ^] S{ h{ S{ S{ ^] G] %{ F~ O] ( ( ( ( R] ( F#.7] n{ j{ R] O] Q{ S{ i{ Z] i4 S5 S5 S5 i4 HV G#.mo H#.P| I#.J#.K#.m8 oz L#.h @} -| b( 1] Sp b{ z) l& $+ *} M#.]U .] N#.F5 Y+.I` O#.j4 P#.Q#.R#.S#.T#.U#.}@.}@.W` v..|@.xX X` V#.Z` x..W#.X#.Y#.Z#.`#. $..$.+$.Z#.@$.#$.$$.cZ JX %$.&$.*$.=$.-$.;$.>$.,$.'$.'$.j@.-b 7c x@.)$.!$.~$.^Q {$.]$.^$.]6 /$.Nq ($._$.:$.<$.[$.bc 6 .Vk 3c %b 2c *b 4c >b =b $f '$.}$.W..|$.1$.2$.3$.4$.5$.6$.7$.8$.9$.eL 0$.a$.b$.c$.d$.e$.f$.g$.h$.i$.j$.k$.l$.m$.n$.Mt o$.p$.q$.r$.s$.t$.u$.v$.w$.x$.y$.z$.A$.|M B$.C$.D$.E$.F$.G$.H$.I$.J$.K$.L$.M$.N$.O$.P$.Q$.R$.Y .S$.T$.U$.V$.W$.X$.Y$.Z$.`$. %..%.+%.@%.#%.|* $%.%%.+B &%.*%.=%.-%.;%.>%.<> (R /R ,%.'%.)%.!%.~%.n#.nl [^ W} @< dB {%.uu ]%.Mc r#.fr Op ^%./%.(%._%.:%.r` Oc <%.{' [%.]G i4 8X R5 R5 t^ Z] j{ Z] 7] 7] i{ ,_ ,: R] ( R] O] N{ h{ i{ h{ S{ S{ S{ S{ ^] G] I~ G] Q] @{ u~ #{ u~ 1~ 1~ l~ l~ l~ 1~ 1~ m~ !{ n~ l~ n~ m~ h~ '@ r- E; M+ N+ N+ N+ G# G# H) >t ;R 7> 4 4 k& ~> ", +"r- r- E; u r- v O} @+ ~] s~ 3{ m~ 1~ 1~ ] Z{ `{ V! u~ @{ ^] S{ S{ &{ G] ^] h{ s^ i{ &/ ^] G] Q] F~ ]] e^ ( ( ,_ k{ +1 ]G (^ $/ a] R] /] /] d^ n{ n{ R5 7] n{ S5 S5 ': }%.0/ |%. I+ 7%.N{ ': $/ m{ a] a] U: UC ;, ~$ 8%.9%.0%.a%.t..b%.V` W` v..|@.xX wX w..w..c%.d%.e%.f%.g%.h%.i%.j%.k%.l%.m%.n%.o%.p%.b@.cZ q%.r%.s%.t%.u%.v%.w%.,$.x%.y%.z%.6c A%.B%.C%.U..1T D%.E%.F%.G%.H%.I%.8m J%.K%.L%.M%.N%.Lk Tk O%.P%.Vl Q%.4c Un >b $f 7c ;b R%.S%.T%.U%.V%.W%.X%.Y%.Z%.`%. &..&.+&.T..@&.#&.$&.%&.&&.*&.=&.-&.;&.>&.,&.'&.)&.!&.~&.{&.]&.^&./&.(&._&.:&.<&.[&.}&.|&.1&.2&.3&.4&.5&.6&.7&.8&.9&.0&.a&.b&.c&.d&.e&.f&.AQ g&.h&.i&.j&.k&.l&.m&.n&.o&.p&.q&.r&.s&.t&.u&.v&.w&.x&.y&.z&.A&.B&.C&.D&.E&.F&.G&.H&.I&.J&.K&.L&.fE bw /J u8 M&.i' 9. t) r. N&.O&.U& 1= su q#.P&.Q&.R&.S&.T&.U&.4o @h V&.W&.X&.Y&.8X S5 j{ R5 Z] :( %/ m{ :( :( %/ ,_ ( ( >_ O] N{ R{ i{ i{ S{ I~ ^] G] &{ &{ <^ :] :] Q] #{ l~ l~ l~ 1~ 1~ l~ u~ 1~ 1~ l~ 1~ 1~ 1~ 1~ q~ q~ m! b^ K' W$ M+ @ i@ N+ v$ N+ G# G# O+ i> Q> n& k& D# ", +"r- u 9, K@ x 1^ 3{ 2{ l~ s~ l~ s~ l~ S! %( Z&.%( `&.{] G] ^] S{ ^] I~ S{ h{ >_ ( R] N{ ^] S{ S{ S{ &/ z] O] O] e^ i{ j{ m{ ( ( ( O] ]] /] O] ,: ,_ :( Z] n{ *.a] V} .*.4# )1 +*.@*.#*.b( 3: U] y@ $*.B] x~ L= Z| k& D# ;> u> 3i O[ F~ /] O] F~ @{ @{ m~ h~ y! .( %*.&*.**.=*.[@.V` u..W` v..|@.xX wX wX 5Z -*.;*.>*.,*.'*.)*.!*.~*.{*.]*.^*./*.(*._*.:*.<*.* .[*.}*.RU |*.1*.2*.3*.4*.5*.6*.7*.8*.9*.7 .0*.a*.VD b*.c*.d*.e*.ra f*.g*.h*.i*.j*.k*..+.&d l*.Z7 m*.n*.o*.n*.p*.{Q V@.q*.r*.B@.s*.t*.u*.v*.w*.x*.y*.z*.A*.|T B*.C*.D*.E*.F*.G*.H*.I*.J*.K*.L*.M*.N*.O*.P*.Q*.R*.S*.T*.U*.V*.W*.X*.Y*.!V Z*.`*. =..=.+=.@=.#=.$=.%=.&=.*=.==.-=.;=.>=.,=.'=.)=.!=.~=.{=.7V ]=.^=./=.(=._=.:=.<=.[=.}=.|=.1=.2=.3=.4=.5=.6=.7=.8=.9=.0=.a=.b=.c=.d=.e=.f=.g=.!_ `9 O] /] @{ ]] O] ( =( u :* '$ l& E] h=.(G )1 T= T= i=.j=.[w vu uu .t k=.l=.u` O| -@.Y&.m=.Z] j{ j{ '_ O] R] R] ( >_ ( R] R] %/ h{ S{ S{ h{ i{ S{ ^] I~ 5{ #{ 9{ #{ 8{ /] F~ #{ u~ l~ l~ u~ l~ u~ l~ 1~ m~ l~ 1~ n~ m~ 1~ m~ q~ n~ m~ n~ h~ n=.8. o=.i@ v$ v$ N+ N+ N+ N+ O+ ;+ c> ~> ", +"r- u K@ 5) 3{ ;/ ~] ~] *{ u~ l~ l~ u~ l~ `&.H] H] H] &{ I~ ^] ^] I~ I~ S{ i{ e^ R] ( 6] i{ j{ i{ i{ h{ z] O] O] e^ h{ S{ d^ R] ( O] O] /] O] O] O] ,_ Z] n{ $/ a] @, Z! (G 1+ (G p=.h. `+ q=.4: M! @0 y~ N$ l& k& D# }> j, x; A Q+ q) 1~ u~ l~ m~ !{ h~ g~ g~ P[ r=.s=.t=.u=.=*.[@.V` u..W` v=.|@.xX wX wX w=.x=.y=.z=.A=.B=.C=.D=.E=.F=.G=.H=.I=.J=.m%.K=.L=.$Y M=.N=.O=.P=.Q=.R=.4*.S=.T=.U=.7*.V=.W=.X=.Y=.Z=.`=. -..-.aO +-.@-.#-.$-.%-.&-.*-.=-.%8 --.;-.`%.>-.n*.n*.6j ,-.'-.zW )-.!-.~-.{-.]-.^-./-.(-._-.:-.<-.[-.}-.5*.|-.1-.2-.3-.4-.5-.6-.7-.8-.9-.0-.a-.b-.c-.C; d-.e-.f-.g-.h-.i-.j-.k-.l-.m-.n-.o-.p-.q-.r-.s-.t-.u-.v-.w-.x-.y-.z-.A-.B-.C-.D-.E-.F-.G-.H-.I-.J-.K-.L-.M-.N-.O-.P-.Q-.X7 R-.S-.T-.U-.V-.W-.UF X-.Y-.Z-.`-. ;..;.+;.@;.#;.$;.%;.#{ R] ,: O] R] ,: 3/ &{ 8. O+ ~> {+ E$ m. j '+ A@ (G R+ lb 3: 4+ x@ *| or &;.2a *;.=;.-;.': 8X Z] n{ >_ R] O] O] O] R] R] O] O] O{ S{ ^] I~ S{ h{ h{ h{ G] <^ #{ #{ #{ @{ F~ #{ #{ l~ 1~ u~ #{ #{ #{ u~ 1~ 1~ 1~ u~ l~ 1~ m~ l~ 1~ m~ 1~ m~ m~ m~ k! ) N+ v$ v$ v$ v$ v$ N+ N+ N+ O+ N+ ", +"2> : 5~ 2{ ~] ~] *{ s~ t~ l~ 1~ l~ u~ l~ U! `&.U{ T{ G] I~ I~ ^] I~ ^] S{ ^] N{ R] >_ s^ :( n{ s^ 6] %/ &/ /] P{ &/ h{ S{ &/ O] R{ N{ O] e^ R] R] R] ,: Z] Z] n{ @, .; x> ~= @@ (+ }^ ;;.>;.(1 A) {) O! O! >{ j& j, [* M; -& 2; T> . !{ m~ 1~ q~ ){ g~ g~ nw f~ A9 ,;.';.);.a%.=*.t..V` u..W` v..|@.xX !;.3Z ~;.{;.];.^;./;.(;._;.:;.<;.~*.[;.};.|;.1;.2;.3;.4;.5;.r%.F..6;.G=.7;.8;.9;.0;.h .6*.|-.a;.b;.c;.d;.e;.:0 f;.gR g;.h;.lg r@.$-.i;.j;.k;.l;.6W m;.oe n;.F0 |$.o;.%&.p;.q;.r;.s;.t;.u;.v;.Uf w;.x;.y;.z;.A;.B;.C;.D;.E;.U=.F;.G;.B%.H;.I;.J;.K;.L;.M;.N;.O;.P;.Q;.R;.S;.T;.U;.V;.=S W;.X;.Y;.Z;.`;. >..>.+>.@>.#>.$>.%>.&>.*>.=>.->.;>.>>.,>.'>.)>.!>.~>.{>.]>.^>./>.(>._>.:>.<>.[>.}>.|>.1>.2>.3>.4>.5>.6>.7>.8>.9>.0>.a>.b>.c>.d>.e>.f>.g>.h>.i>.j>..] 1~ #{ u~ F~ /] P] 6{ w I+ c% D# c. Y~ co J! A@ %@ i% ~1 1] p^ }- 3# K$ d k>._+ h..yf L{ k{ :( n{ :( R{ /] /] /] O] R] R] e^ N{ ^] I~ ^] h{ S{ h{ ^] ^] Q] @{ @{ @{ @{ F~ F~ #{ u~ S! ]U 4{ u~ @{ %{ #{ u~ #{ 4{ u~ u~ u~ u~ l~ l~ m~ 1~ 1~ m~ s~ +] v F @ M+ M+ M+ N+ N+ N+ i@ L/ ", +"#+ k! 2{ 2{ ~] l~ u~ @{ u~ 1~ 1~ u~ u~ l~ l~ ]U #{ <^ G] G] I~ ^] ^] S{ I~ ^] S{ O{ S{ &/ e^ R] O] ( R] &/ S{ ^] ^] ^] S{ d^ S{ ^] :] d^ S{ S{ O] ]] R{ t^ 5] l>.<$ j> j, n& L- '! &~ d% &~ L= &~ {+ k& {+ a! ~> [* L) -& G# G# u> w $! q~ q~ q~ !{ !{ ){ m>.Gc n>.o>.p>.);.q>.r>.}@.u..u..W` xX xX xX s>.t>.u>.v>.w>.x>.y>.z>.A>.B>.C>.D>.>W E>.F>.D=.G>.H>.I>.J>.K>.L>.M>.N>.O>.P>.Q>.R>.S>.T>.U>.V>.W>.X>.Y>.Z>.`>. ,..,.+,.@,.#,.$,.%,.&,.*,.=,.-,.;,.>,.,,.',.Sn o;.),.!,.~,.{,.],.^,./,.(,._,.:,.<,.[,.},.|,.1,.2,.3,.4,.5,.6,.7,.8,.9,.0,.a,.b,.c,.d,.e,.f,.g,.h,.i,.j,.k,.k,.l,.m,.n,.o,.p,.q,.r,.s,.t,.u,.v,.w,.x,.y,.z,.A,.B,.C,.D,.E,.F,.G,.H,.I,.J,.K,.L,.M,.N,.O,.P,.Q,.R,.S,.T,.U,.V,.W,.X,.Y,.Z,.`,. '..'.+'.@'.#'.$'.%'.&'.*'.='.-'.;'.>'.,'.''.G! g~ ){ q~ l~ #{ #{ #{ P[ YC # z$ ;+ n k& n& L= @6 #6 #6 :e @6 )'.,^ )1 !'.u= #@ 1( ,^ O~ -' R_ s^ h{ S{ I~ f^ /] O] '_ h{ h{ h{ S{ ^] ^] S{ S{ i{ S{ S{ ^] I~ I~ G] G] G] I~ I~ I~ {] yc H] {] Q] &{ 5{ G] T{ {] 9{ #{ @{ F~ u~ 1~ m~ m~ m~ l~ 1~ *{ m~ U< ~'.E; D, E; M+ N+ N+ N+ i@ ", +"~! 2{ 2{ 3{ 3{ u~ #{ #{ l~ m~ 1~ u~ u~ l~ 1~ #{ @{ G] G] I~ ^] ^] S{ j{ h{ i{ h{ G] ^] R{ ( ( R] ( ( &/ S{ h{ ^] ^] S{ S{ S{ G] G] ^] ^] :] R] R] R] L{ ar {'.L) [* [* j& &~ &~ 4 k& {+ {+ j& ~> D# k& ~> }> j, M; c% G# O+ @ r! *! ){ h~ ){ ){ ){ !{ g~ ]'.n>.^'.t=./'.q>.r>.}@.u..W` W` xX xX 2Z 2Z ('.%W _'.;*.:'.<'.['.}'.['.|'.1'.2'.3'.4'.5'.6'.7'.I>.8'.9'..$.0'.a'.b'.c'.c'.d'.e'.f'.g'.h'.i'.j'.k'.l'.qU m'.n'.o'.p'.^ .s@.q'.r'.s'.iW t'.u'.U@.v'.w'.x'.kZ ),.E;.4*.y'.z'.A'.B'.HC C'.D'.E'.F'.G'.H'.I'.J'.K'.L'.M'.N'.h@.O'.P'.Q'.R'.S'.T'.U'.V'.W'.X'.Y'.Z'.`'. )..).+).@).#).$).%).&).*).=).-).x,.;).>).,).').)).!).~).{).]).^)./).()._).:).<).[).}).|).1).2).3).4).5).6).7).8).9).0).7N a).b).c).d).e).f).g).h).i).j).k).*'.l).m).n).o).p).q). j& l& ;^ L= ;^ P~ #6 X< r).l. 1# SV Z+ s).;^ D# t).6> u).^] ^] I~ 8{ /] O] %/ i{ S{ ^] I~ G] I~ ^] h{ j{ j{ h{ S{ S{ I~ G] G] I~ ^] ^] S{ ^] &{ {] {] &{ G] G] I~ G] I~ @{ #{ /] ]] u~ m~ m~ m~ l~ 1~ 1~ 1~ n~ !{ A9 W$ E; E; D, v$ v$ v$ v$ ", +"B! 3{ *{ 3{ ~] t~ u~ u~ u~ u~ #{ #{ u~ l~ l~ @{ #{ U{ G] I~ ^] ^] S{ j{ j{ j{ h{ I~ I~ e^ ( ( ( R] R] Q{ %/ h{ ^] h{ i{ S{ I~ G] &{ I~ ^] &/ '_ ( M{ O} v$ => L) [* [* j, j& k& j& k& k& D# }> j, ~> D# j& j, L) x; L& O+ O+ N( gb r) f~ g~ g~ G! v4 :! h~ s) O[ v).w)./'./'.r>.}@.x).y).W` W` 1Z 1Z 1Z 2Z 1Z z).A).B).C).['.D).E).F).G).H).I).J).K).XZ L).M).N).O).P).Q).R).S).T).U).V).W).X).Y).Z).N=.`). !..!.+!.@!.#!.G;.$!.%!.&!.*!.=!.:h -!.;!.>!.,!.YR '!.x'.~,.~,.)!.!!.~!.{!.]!.^!./!.(!.SM _!.:!.~.g~ ){ q~ n~ q~ n~ n~ ){ ,~.M+ G# O+ %& }> D# {+ &~ $~ -, '! >^ ]/ #6 j% J$ N& M$ ,^ {+ j, *> '~.)~.I~ db !~.T{ ]] @{ :] S{ S{ S{ ^] G] G] ^] I~ i{ Z] j{ i{ h{ I~ I~ I~ ^] I~ G] S{ S{ I~ &{ G] I~ I~ G] G] Q] I~ P{ ]] @{ F~ @{ u~ 1~ n~ l~ l~ 1~ m~ q~ m~ q~ H' 24 E; M+ M+ M+ M+ v$ ", +"m~ l~ u~ l~ u~ u~ u~ u~ #{ #{ F~ ]] @{ u~ u~ #{ #{ u~ @{ @{ O{ i{ i{ l{ ,: R] P{ G] ^] '_ ( R] R] R] R] R] R] N{ &/ i{ h{ h{ 8{ #{ 7{ ]] O] '_ i{ ~~.8, [* M; M; N+ >+ >+ L) ~> k& j& D# D# }> j, j, j, [* [* U; N+ O+ % O+ O+ (. 3) 3) 6~ T) T) Bc G! {~.]~.^~./~.(~.t=.);.q>.[@.}@.y)._~.:~.y).1Z 1Z 1Z !;.<~.[~.}~.|~.1~.C).2~.3~.4~.5~.6~.7'.7~.8~.9~.0~.a~.b~.c~.d~.e~.f~.g~.h~.i~.j~.k~.l~.m~.n~.o~.p~.q~.r~.s~.t~.WZ u~.w .v~.w~.x~.y~.z~.A~.B~.C~.D~.E~.F~.G~.H~.I~.3*.*V J~.K~.L~.M~.N~.O~.@-.P~.Q~.R~.S~.T~.U~.V~.W~.X~.Y~.Z~.`~. {..{.+{.@{.#{.${.%{.&{.*{.={.-{.;{.>{.,{.lN '{.){.!{.~{.{{.]{.^{./{.({._{.:{.<{.[{.}{.|{.1{.2{.3{.4{.5{.6{.7{.'=.8{.9{.0{.a{.b{.c{.d{.e{.f{.g{.h{.i{.j{.k{.l{.m{.n{.`X o{.p{.q{.r{.s{.t{.u{.v{.w{.x{.y{.z{.A{.B{.C{.D{.h~ h~ h~ !{ ){ ){ h~ g~ n=.2> D, -a v$ ;+ .; ^, ;] !) %, h; r& ;^ <{ L- (= Z_ }/ Y{ j, [* > z$ '~..{ X] E{.T{ I~ G] G] ^] ^] S{ h{ S{ G] I~ I~ ^] j{ j{ h{ S{ I~ S{ ^] ^] ^] G] I~ I~ I~ G] &{ I~ ^] I~ #{ @{ V{ ]] @{ l~ F~ @{ u~ l~ 1~ l~ u~ l~ 1~ q~ n~ n~ /~ h~ y` 2> E; E; E; r- ", +"m~ 1~ u~ #{ u~ u~ u~ u~ #{ u~ u~ #{ l~ l~ l~ l~ l~ #{ F~ F~ :] i{ n{ :( R] O] Q] G] S{ '_ R] R] R] R] R] O] O] /] X] ^] ^] :] u~ #{ @{ ]] O] ,_ s^ #! N+ M; x; c% N+ v$ v$ x; M; D# D# ~> ~> ~> ~> [* [* M; x; = N+ O+ O+ N+ v$ t' 3) 5~ 6~ O} O} 7J F{.G{.H{.I{.J{.K{.t=.u=.a%.[@.L{.v=.M{.N{.O{.1Z P{.1Z Q{.R{.S{.T{.U{.V{.W{.X{.:'.Y{.Z{.`{. ]..].+].+].@].#].$].%].%].&].+ .*].f~.=].-].;].>].,].'].eZ )].!].~].l~.{].]].^]./].(]._].:].<].[].}].|].1].2].3].4].8;.3*.5].3*.6].7].8].9].0].a].b].c].d].e].f].g].h].i].j].k].l].m].n].o].p].q].r].s].t].u].v].w].x].y].z].A].B].C].D].E].F].G].H].I].J].K].L].M].N].O].P].Q].R].S].T].U].V].W].X].Y].Z].`]. ^..^.+^.@^.#^.$^.%^.&^.*^.=^.-^.;^.>^.,^.'^.)^.!^.~^.{^.]^.^^./^.(^._^.:^.<^.[^.}^.|^.1^.2^.3^.4^.5^.){ h~ g~ h~ h~ h~ g~ g~ A9 r- E; e~ O+ ;+ ;+ u! ;] H! T- _. *+ &~ $~ n] ;^ P~ P~ 4 [* [* U; v$ v$ 4) ]] Q_ 6{ &{ &{ I~ S{ ^] I~ S{ ^] G] I~ I~ S{ S{ h{ S{ ^] ^] I~ &{ I~ S{ I~ I~ ^] ^] I~ G] ^] I~ I~ @{ @{ @{ #{ u~ l~ #{ u~ #{ #{ u~ u~ u~ u~ 1~ n~ n~ n~ m~ q~ ){ '@ r- E; E; r- ", +"1~ l~ l~ *{ u~ u~ l~ u~ @{ 8{ u~ #{ u~ u~ u~ l~ u~ @{ /] /] &/ i{ Z] k{ R] O] :] G] ^] R{ ,: ( R{ R] R{ O] R{ N{ X] S{ ^] T{ l~ @{ F~ ]] ]] N{ R! + J= L& q; -& N+ O+ = M& q; [* }> j> }> j> j, M; M; L) -& -& I+ O+ O+ N+ ;+ 3) k! 3) k! W} Cc 6^.7^.8^.9^.0^.a^.b^.t=.u=.a%.[@.c^.W` d^.e^.f^.g^.h^.i^.j^.k^.l^.m^.n^.o^.p^.q^.r^.s^.t^.u^.v^.w^.x^.y^.z^.A^.B^.C^.D^.E^.*].F^.G^.H^.I^.J^.K^.L^.M^.N^.O^.P^.Q^.R^.S^.T^.U^.V^.W^.X^.Y^.Z^.`^. /../.+/.@/.#/.$/.%/.&/.*/.=/.^].-/.;/.>/.,/.'/.,/.)/.!/.~/.{/.]/.^/.//.(/._/.8~.:/.,.+,.D/.E/.F/.G/.H/.I/.J/.K/.L/.M/.N/.O/.P/.Q/.R/.S/./K T/.U/.V/.W/.X/.Y/.Z/.`/. (..(.+(.@(.){ I` #(.g~ I` nw f~ f~ s) D E; M+ % >+ >+ x) C> w> :. :. :. &, &~ '! '! L= L= ~> [* L) `% N+ -& + 4) ]3 5{ {] {] I~ G] G] I~ ^] E~ :] ^] I~ h{ S{ S{ ^] G] I~ {] {] G] ^] S{ S{ O{ &/ I~ I~ S{ G] Q] @{ u~ u~ u~ u~ #{ #{ u~ #{ @{ #{ u~ u~ u~ l~ m~ n~ m~ m~ q~ n~ m! /< $(.$(.r- ", +"l~ s~ 3{ 3{ 3{ 1~ 1~ #{ <^ Q] <^ G] T{ {] {] <^ ^] Q{ O] R] s^ n{ Z] Z] n{ i{ ^] u4 !~.h{ n{ n{ 6] &/ O{ i{ %/ S{ S{ %(.&{ &(.u~ @{ 8{ *(.df =(.N+ Y% Y% % G# O+ q; L& x; q; 2; x; %& z$ z$ v$ N+ = M; M; M; r% I+ I+ M+ F ,@ (.,(.'(.)(.!(.b^.**.~(.U#.U#.}@.{(.](.^(.n^./(.((._(.:(.<(.[(.}(.|(.|(.AX 1(.2(.3(.4(.5(.6(.7(.8(.9(.0(.a(.D^.C^.b(.E^.F^.+ .c(.d(.e(.f(.g(.h(.i(.j(.k(.l(.m(.n(.o(.p(.U^.q(.r(.~h s(.+/.t(.u(.v(.i'.w(.x(.y(.z(.A(.B(.C(.m].m(.D(.E(.F(.G(.+ .H(.I(.J(.K(.L(.M(.N(.O(.P(.Q(.R(.S(.T(.U(.V(.W(.X(.Y(.Z(.`(. _.._.+_.@_.#_.$_.%_.&_.*_.=_.-_.;_.>_.,_.'_.)_.!_.~_.{_.]_.!V r/.^_./_.(_.__.:_.<_.[_.}_.|_.1_.2_.3_.4_.5_.6_.7_.8_.9_.0_.a_.b_.c_.d_.e_.f_.g_.h_.i_.j_.k_.l_.m_.n_.o_.p_.q_.r_.s_.t_.u_.v_.w_.x_.y_.z_.2^ ar <3 r& 5~ 5~ 3) ,@ r- E; O+ v$ >+ > j, ~> :. :. _. n& 4 4 '! n] n& M; M; L) L) x; x; N+ v$ h; *{ A_.,n 7{ #{ u~ #{ @{ @{ @{ @{ @{ :] I~ ^] G] &{ &{ H] &{ I~ O{ R] R] R] e^ Q{ R{ /] F~ P] 5{ <^ G] &{ G] I~ #{ #{ u~ u~ u~ #{ u~ l~ l~ 1~ 1~ 1~ m~ n~ m~ m~ m~ Ip W$ E; ", +"1~ u~ 3{ 3{ B! m~ 1~ 9{ G] I~ G] G] G] G] &{ ^] h{ R] O] R] m{ n{ 7] 7] j{ i{ %(.!~.db i{ n{ j{ i{ ^] ^] S{ ^] G] ^] db u4 9{ u~ #{ H~ Us A! u Y% Y% Y% % % % q; 2; 2; 2; 2; L& N+ v$ v$ v$ N+ N+ )- x; M; r% D, D, E; E; ,3 J' 5~ 5~ a~ pu B_.C_.D_.E_.F_.R> G_.H_.0%.=*.[@.[@.}@.I_.J_.J_._(.((.K_.L_.h^.:(.2Z !;.3Z 1(.M_.M_.wX M_.|(.M_.`` N_.O_.P_.Q_.R_.S_.CM T_.U_.V_.W_.X_.Y_.Z_.J^.`_. :.7'..:.+:.@:.#:.$:.e'.%:.%:.&:.*:.=:.-:.;:.>:.,:.':.Ml ):.!:.~:.{:.B(.B(.]:.^:./:.(:._:.::._:.<:.[:.}:.|:.1:.2:.3:.4:.5:.6:.7:.8:.9:.0:.a:.b:.c:.d:.e:.f:.g:.h:.i:.j:.k:.l:.m:.n:.o:.p:.q:.r:.s:.t:.u:.v:.w:.x:.n,.y:.z:.A:.B:.C:.D:.E:.F:.G:.H:.I:.J:.K:.L:.M:.N:.O:.P:.Q:.DL R:.S:.T:.U:.V:.W:.X:.Y:.Z:.`:. <..<.+<.@<.#<.$<.%<.&<.*<.=<.-<.r_.;<.><.,<.'<.)<.!<.~<.{<.A! >2 ]<.5~ 5~ 6~ i' r- F N+ N+ > V+ j, }> c> 6. _. {+ {+ n& l& l& [* q; x; M; M; x; x; c% % % M@ C! u~ #{ u~ u~ u~ l~ u~ u~ u~ #{ P] I~ {] {] {] &{ &{ G] S{ 6] ( ( ( ( R] ( O] /] /] I~ G] G] I~ &{ I~ #{ #{ u~ u~ l~ u~ u~ l~ l~ l~ #{ u~ l~ l~ l~ l~ 1~ m~ J' J& ", +"l~ l~ 3{ ~] s~ 1~ u~ %{ ^] ^] ^] ^] ^] G] I~ ^] S{ O] O] R] s^ 7] 7] Z] j{ i{ %(.I~ !~.S{ h{ S{ S{ S{ ^] ^] ^] I~ !~.!~.^<.^<.#{ t~ .{ 7% r- |. Y% Y% Y% Y% % M& q; 2; q; 2; 2; J= = N+ N+ N+ N+ N+ M& q; x; I+ D, D, E; V$ 7~ 5~ 5~ 3) G! /<.(<._<.:<.<<.[<.}<.|<.1<.0%.=*.2<.t..V` 3<.4<.5<.[~.6<.K_.7<.1Z 8<.1Z xX 9<.0<.X` wX wX wX a<.b<.c<.d<.e<.e<.f<.g<.=W h<.i<.j<.k<.l<.m<.n<._;.o<.p<.q<.r<.s<.t<.u<.v<.w<.e'.x<.y<.z<.A<.B<.C<.D<.E<.F<.G<.C<.H<.I<.J<.K<.L<.M<.]:.N<.O<.P<.Q<.::.E(.R<.S<.T<.U<.V<.W<.X<.Y<.Z<.`<. [..[.+[.@[.#[.$[.%[.&[.*[.=[.-[.;[.#H >[.,[.'[.)[.![.~[.{[.][.^[./[.([._[.SO :[.<[.[[.}[.|[.1[.2[.3[.4[.5[.6[.G:.7[.8[.9[.0[.a[.b[.c[.d[.Y=.k .e[.f[.g[.h[.i[.j[.k[.l[.m[.n[.o[.p[.q[.r[.s[.t[.u[.v[.w[.x[.y[.z[.A[.B[.C[.D[.E[.ew F[.G[.H[.I[.y~ 3) 5~ 5~ }> }> ;+ ;+ 6. j& k& k& k& ~> 2; 2; q; x; x; M; x; Y% Y% Y% x! x 2{ *{ #{ u~ u~ u~ #{ #{ u~ #{ V{ I~ {] {] {] {] G] I~ S{ %/ R] R] >_ ( ( >_ O] O] /] 5{ {] G] ^] ^] :] V{ u~ u~ u~ l~ l~ #{ u~ u~ 1~ l~ u~ l~ l~ l~ u~ 1~ 1~ /~ <3 ", +"1~ m~ s~ u~ #{ u~ #{ F~ S{ S{ I~ ^] S{ I~ ^] S{ S{ P{ /] O] :( 7] n{ n{ n{ n{ J[.I~ S{ R{ /] /] O] ]] @{ %{ G] I~ T{ K[.L[.M[.9{ t~ UC V+ -& q; q; q; J= Y% Y% Y% Y% % M& J= J= J= )- M; M; -& -& )- |) e~ |) I+ D, D, tP y 3) I' p) )] 8> q) N[.O[.P[.Q[.R[.iX &*.S[.1<.1<.=*.[@.L{.T[.U[.4<.V[.u>.('.1Z 1Z 1Z 1Z :(.W[.('.!;.!;.!;.1Z 1Z !;.wX V#.X[.1(.Y[.Z[.`` `[.W#. }..}.+}.@}.#}.!*.$}.%}.&}.*}.=}.-}.;}.>}.,}.'}.)}.!}.~}.{}.]}.^}./}.(}._}.:}.<}.[}.}}.|}.1}.2}.B<.A(.3}.4}.5}.6}.7}.8}.9}.0}.a'.a}.b}.c}.d}.e}.f}.+R g}.h}.&i i}.j}.k}.l}.m}.n}.o}.p}.q}.r}.s}.t}.u}.v}.w}.x}.y}.z}.A}.B}.C}.D}.E}.F}.G}.H}.I}.J}.K}.L}.M}.N}.O}.P}.Q}.R}.(} S}.T}.U}.q> V}.W}.X}.Y}.Z}.`}. |..|.+|.@|.#|.$|.%|.&|.*|.=|.-|.;|.>|.#!.,|.'|.)|.!|.~|.{|.]|.^|./|.(|._|.:|.<|.[|.}|.||.1|.ff .0 2|.4) <3 r# F M+ v$ v$ >+ ;+ >+ >+ ;+ <$ j& j& D# 6. z$ Y% p- M& % % G# M& J= Y% Y% Y% Y% %, b4 u~ u~ T{ T{ T{ 9{ #{ @{ <^ ^] G] U{ l~ l~ 4{ I~ ^] h{ &/ z] i{ s^ s^ h{ O{ S{ O{ ^] &{ &{ P{ O] R] D~ &{ &{ G] o~ R! U{ T{ {] `{ 1~ u~ u~ u~ u~ u~ l~ m~ l~ >: ", +"l~ l~ l~ l~ u~ u~ u~ F~ S{ ^] I~ I~ I~ G] ^] S{ h{ '_ R] ( Z] 8] Z] n{ n{ n{ h{ ^] h{ '_ O] O] O] ]] @{ %{ &{ G] <^ K[.K[.K[.9{ s~ : x; x; x; M; L) -& G# Y% q% Y% Y% % % % p- J= x; M; M; M; x; |) e~ e~ D, D, E; @> i) 3) I' p) i~ 3|.4|.5|.6|.7|.8|.9|.0|.a|.b|.<@.1<.=*.[@.L{.W` W` W` W` 1Z 1Z 1Z 1Z 2Z 2Z :(.h^.1Z 2Z 2Z 1Z 1Z 1Z s>.|(.a<.|(.1(.w..c|.d|.Z` x..e|.f|.g|.h|.i|.j|.k|.l|.m|.n|.l<.W_.W_.o|.o|.p|.q|.r|.s|.U<.t|.u|.XS v|.O).n].w|.x|.y|.|}.z|.A|.B|.E^.{}.C|.D|.E|.F|.G|.d(.H|.9~.I|.J|.K|.L|.M|.N|.O|.P|.Q|.R|.S|.T|.U|.V|.W|.X|.Y|.Z|.`|. 1..1.+1.@1.#1.$1.%1.&1.*1.=1.-1.;1.>1.,1.'1.)1.!1.~1.{1.]1.^1./1.(1._1.:1.<1.[1.}1.|1.11.(j 21.31.41.51.rL 61.71.81.91.01.a1.b1.c1.d1.e1.f1.g1.h1.i1.j1.k1.l1.m1.n1.o1.p1.q1.r1.s1.t1.u1.v1.w1.x1.y1.z1.A1.B1.C1.h~ m>.7! X, F] r- v$ v$ N+ z$ ;+ N+ z$ 5. )$ }> ~> c> 5. G# % % G# % % % Y% Y% % % Y% Y% O+ L' l~ u~ T{ &{ &{ 5{ @{ #{ 9{ I~ I~ o~ u~ #{ u~ {] G] h{ h{ h{ h{ S{ S{ S{ G] I~ ^] ^] G] G] /] O] O] I~ &{ &{ ^] G] yc H] I~ {] `{ l~ l~ l~ l~ #{ l~ l~ l~ l~ l~ ", +"u~ l~ u~ ]U u~ 9{ 7{ ]] S{ S{ I~ &{ G] G] I~ ^] h{ e^ R] O] :( 7] n{ Z] n{ s^ i{ S{ S{ %/ O] O] R] /] F~ %{ G] I~ db D1.E1.]U Z{ S> P+ M; L) L) [* j, %& U; O+ % Y% Y% p- Y% Y% Y% p- q; q; x; M; b- Q- -a # D, M+ u z. 5) 3) P[ G' +] F1.G1.H1.I1.J1.K1.L1.M1.w).N1.O1.<@.=*.[@.L{.W` W` T[.W` 8<.1Z 1Z 1Z xX xX 1Z y).1Z 1Z 2Z 1Z 1Z 8<.h^.2(.wX M_.|(.V#.P1.w..V#.Q1.R1.S1.T1.U1.V1.W1.X1.Y1.;}.Z1.`1.`1.W_.p|.,}. 2..2.y<.+2.@2.#2.!].$2.%2.%2.&2.*2.=2.|}.|}.-2.;2.>2.,2.'2.8}.)2. ].!2.~2.{2.]2.^2.J[ /2.(2._2.:2.<2.[2.}2.|2.12.[2.22.32.42.52.62.72.82.92.02.a2.b2.c2.d2.e2.f2.g2.h2.i2.-o j2.k2.l2.m2.n2.o2.p2.q2.r2.s2.t2.u2.v2.w2.x2.y2.z2.A2._n od B2.6 .Ze ]c C2.D2.^N E2.F2.G2.H2.I2.J2.K2.L2.M2.N2.O2.P2.Q2.R2.!].S2.T2.U2.V2.W2.X2.Y2.iz Z2.`2. 3..3.0( +3.@3.#3.$3.%3.|{ &3.<3 a~ E M+ M+ N+ v$ >+ z$ z$ ;+ ;+ V+ }> c> >+ % Y% % % % % % % Y% p- J= Y% Y% Y% tP h~ u~ <^ I~ G] :] ]] @{ 9{ &{ G] 6{ @{ F~ 8{ &{ &{ I~ S{ h{ h{ h{ ^] ^] 6{ {] G] G] {] G] N{ O] /] :] G] G] ^] I~ G] G] G] H] 9{ #{ #{ 1~ l~ u~ u~ u~ l~ u~ #{ ", +"u~ u~ <^ G] :] G] <^ D~ ^] S{ ^] G] I~ I~ f^ /] O] d^ ^] h{ */ 7] 7] */ ( R] &/ h{ h{ R{ O] R] e^ A] !~.I~ S{ h{ %(.<^ *{ ,1 [' @ @ N+ N+ %& }> ~> }> L) j, L) = L& Kj Y% Y% Y% ,- Kj 2; q; q; q; % |. G# , L/ i@ [ I' I' +! s) *3.=3.-3.;3.>3.,3.'3.)3.w).w).H_.|<.u=.=*.[@.L{.W` W` T[.vX vX vX xX xX xX xX vX vX vX 1Z 2Z 1Z 1Z 1Z ('.!3.!;.1Z wX Y` Y` X` Y` Z` ~3.{3.4~.]3.^3./3.(3.z.._3.:3.<3.<3.[3.}3.{}.|3.`1.V_.+:.13.t|.23.k(.k(.33.43.53.*:.63.73.83.93.U_.03.a3.g<.b3.c3.d3.d3.e3.`` o^.f3.g3.h3.i3.j3.k3.l3.m3.n3.o3.p3.q3.r3.Y(.s3.t3.u3.v3.w3.x3.y3.z3.A3.B3.C3.D3.E3.F3.G3.H3.I3.J3.K3.L3.M3.N3.O3.P3.ij Q3.R3.S3.T3.U3.V3.W3.%Y X3.Y3.Z3.`3. 4..4.+4.@4.#4.$4.%4.&4.*4.=4.-4.;4.>4.,4.'4.)4.N/.!4.~4.{4.]4.^4./4.(4._4.:4.<4.[4.}4.|4.14.24.34.44.54.J# ^~.64.74./! &3.3) 4) w M+ E; M+ N+ v$ z$ j, j, }> ;+ V+ ;+ = % Y% % % % % Y% Y% Kj 2; q; G# % G# O+ z, =7 0{ &{ &{ O{ O] /] @{ @{ @{ 8{ Q] I~ D~ S{ I~ G] ^] S{ h{ S{ G] &{ u~ `{ V! {] &{ I~ /] O] F~ 8{ F~ V{ I~ ^] ^] I~ G] &{ V! @{ l~ 1~ l~ u~ u~ u~ u~ u~ u~ ", +"l~ ]U T{ G] I~ I~ I~ ^] ^] S{ I~ &{ G] I~ X] O] O] h{ ^] h{ j{ n{ n{ ,_ ( R] &/ S{ h{ N{ O] R] %/ db !~.db ^] ^] I~ db DH t) v$ v$ v$ v$ N+ %& [* [* j, j, }> j, [* M; q; Y% Y% '- ~i Kj 2; 2; q; q; # K& e~ e~ E; &$ G! Lz 84.Us X_ 94.04.a4.b4.c4.d4.c4.e4.w).H_.f4.g4.u=.~(.[@.L{.W` T[.T[.T[.T[.T[.T[.T[.T[.T[.T[.T[.xX xX 2Z 2Z 1Z 1Z 2Z 2Z 1Z 1Z wX M_.M_.Y` X` h4.i4.j4.k4.l4.m4.n4.o4.. .<3.<3.. .03.p4.q4.r4.W_.|3.s4.t4.u4.v4.w4.x4.x4.4}.y4.>2.z4.A4.73.B4.C4.+}.g<.<3.D4.D4.D4.D4.<3.x..x..E4.F4.G4.H4.I4.J4.K4.L4.M4.N4.O4.P4.Q4.R4.S4.T4.U4.V4.W4.X4.Y4.Z4.`4. 5..5.+5.@5.#5.$5.%5.&5.*5.=5.-5.;5.>5.,5.'5.)5.!5.~5.{5.]5.^5./5.(5._5.U@.:5.<5.[5.X}.}5.35 |5.15.25.35.45.55.65.75.85.95.05.a5.b5.c5.d5.e5.f5.g5.h5.i5.j5.k5.l5.m5.n5.o5.p5.q5.r5.s5.t5.u5.v5.%h w# w5.x5.m~ =e 5~ 3) M@ r- E; M+ N+ N+ v$ b> [* }> ;+ >+ z$ G# % % % % Y% Y% q% Y% )- M; L) `% O+ O+ A> ^; $+ :! &{ G] E~ O] /] ]] @{ #{ P] I~ G] I~ S{ I~ &{ I~ ^] ^] I~ &{ {] V! #{ #{ {] G] I~ /] /] #{ u~ #{ @{ :] S{ ^] I~ ^] G] V! #{ l~ m~ 1~ l~ u~ #{ #{ @{ l~ ", +"1~ 1~ {] G] G] I~ S{ S{ S{ h{ I~ G] I~ S{ O] R] O] z] ^] S{ i{ j{ s^ s^ ( O] z] h{ S{ N{ R] /] R{ db !~.I~ I~ I~ ^] Q] *! $+ > >+ z$ z$ v$ b> j, }> }> j, j, }> }> j, [* I+ % a& y5.~i Kj x; L) h& z5.Gc /y K` L` A5.B5.C5.D5.8, ;- E5.F5.G5.H5.I5.J5.K5.L5.w).M5.N5.O5.P5.=*.[@.L{.W` T[.T[.T[.T[.T[.T[.T[.T[.xX xX xX xX xX 2Z 2Z 1Z y).8<.P{.8<.1Z wX wX wX Q5.s>.R5.S5.T5.U5.V5.W5.Q_.l<.<3.D4.<3.z..7}.X5.Y5._3.Z5.+ .`5.-}.r4.,2. 6.7}..6.y4.+}.+}.8}.+6.@6.X_.g<.<3.<3.. .CX #6.+}.$6.d3.%6.E4.x..&6.*6.=6.,B -6.;6.>6.,6.'6.)6.f:.!6.~6.{6.]6.^6./6.(6._6.:6.<6.[6.}6.|6.16.26.$5.36.46.56.66.76.86.96.06.a6.b6.c6.d6.e6.f6.g6.h6.i6.j6.k6.l6.m6.n6.o6.p6.q6.r6.s6.t6.u6.zU v6.w6.x6.y6.z6.A6.B6.C6.D6.E6.F6.G6.H6.I6.J6.K6.L6.M6.N6.O6.P6.Q6.R6.S6.T6.U6.V6.W6.I5.X6.Vz Y6.*+ #] I9 5~ 5~ K' E D, M+ N+ O+ -& L) L) [* z$ z$ O+ q% ,- Y% Y% Y% Y% Y% G# v$ %& [* [* b> N+ O+ t; A> # Z6.H] G] D~ /] /] ]] @{ #{ P{ ^] G] G] G] &{ G] I~ I~ G] &{ {] {] u~ @{ ]] ^] ^] S{ /] O] ]] #{ l~ #{ :] S{ S{ G] I~ G] 7{ 4{ u~ l~ S! u~ u~ u~ #{ #{ u~ ", +"l~ V! G] ^] ^] S{ I~ &{ I~ S{ I~ G] ^] &/ O] R] ( '_ Q{ O] '_ e^ e^ k{ */ i{ R{ R] Q{ s^ i{ d^ E~ I~ ^] ^<.V{ 8{ V} `6.~a o' X! )) c> c> i> ~> ~> }> }> }> }> ~> G$ X+ $& 93 03 2~ 7.B# .7.+7.@7.#7.$7.J&.%7.&7.*7.=7.-7.;7.K{ *+ go >7.,7.'7.n..>7.c4./R )7.!7.1l ~7.jB {7.U#.[@.L{.W` T[.T[.T[.T[.T[.T[.T[.T[.xX xX xX xX xX 2Z xX W` W` vX 1Z 1Z 1Z 1Z 1Z !;.a<.]7.^7./7.(7._7.:7.<7.[7.D4.D4.D4.y..E4.%6.f<.+}.<3.}7.|7.1@.Z[.<3.17.17.27.9R 37.U_.47.57.67.-}.:3.. .<3.. .77.87.97.07.a7.b7.c7.d7.e7.f7.g7.h7.i7.j7.k7.l7.m7.n7.o7.p7.q7.r7.s7.t7.u7.v7.w7.x7.K= w& y7.z7.A7.B7.C7.D7.E7.F7.G7.H7.I7.J7.K7.L7.M7.N7.O7.P7.Q7.R7.S7.T7.U7.V7.W7.X7.Y7.n6.Z7.`7. 8..8.+8.@8.#8.$8.%8.&8.*8.=8.-8.;8.>8.,8.'8.)8.!8.~8.{8.]8.^8./8.(8._8.:8.<8.[8.}8.|8.18.28.38.48.58.68.K5.78.88.dz 98.Bc cw 'a 6X v F E; t; O+ G# U; [* M; `% M; J= '- '- ,- ,- ,- )- %& j, j, L) [* ~> >+ >+ v$ O+ t; N+ i@ 08.;/ E~ :] I~ I~ Q] G] ^] ^] G] &{ G] &{ G] G] I~ G] &{ {] &{ G] 5{ &/ d^ h{ h{ z] i{ O{ #{ 1~ l~ Q] ^] S{ ^] G] G] <^ 6{ G] &{ &{ <^ 4{ u~ u~ l~ @{ ", +"#{ f^ E~ S{ S{ S{ G] G] I~ I~ ^] ^] S{ h{ e^ ( ,: ( R] R] O] O] R] a] Z] j{ %/ O] O] &/ h{ S{ ^] I~ I~ Q] @{ *{ ^: v! g/ M] {) K~ X! {+ 4 n& n& {+ k& n& {+ ;- t% a8.]t /t jP 9J #7.b8.c8.d8.e8.f8.f8._R &L /R ]@.K5.g8.h8.U} i8.j8.68.k8.68.]@.]@.O` (R l8.m8.n8.Q, o8.p8.U#.[@.L{.W` W` W` W` W` T[.T[.T[.T[.xX xX 2Z 2Z 2Z 2Z xX xX T[.xX xX 2Z 1Z 1Z 1Z 2Z q8.r8.s8.t8.u8.^@.v8.w8.Z` Y` w..Y[.Z` Z` x..y..<3.D4.y.. .E4.W#.|7.*}.9R x8.y8.z8.h~.A8.B8.17.. .. .1@.d<.E4.27.C8.8~.D8.E8.+].F8.G8.H8.2(.I8.$W J8.K8.L8.M8.N8.O8.P8.Q8.R8.S8.T8.U8.V8.W8.X8.Y8.Z8.`8. 9..9.+9.@9.#9.$9.%9.&9.*9.=9.-9.;9.>9.,9.'9.)9.!9.~9.{9.]9.^9./9.(9._9.:9.<9.[9.}9.|9.19.29.39.49.59.69.79.89.99.09.a9.b9.c9.d9.e9.f9.g9.h9.i9.j9.k9.l9.m9.n9.o9.p9.q9.r9.s9.S6.t9.u9.v9.w9.x9.,7.k8.]@.y9.z9.A9.B9.C9.K5.D9..O E9.Sc s4 7o F9.c- -{ r% L) x; ~i ~i ~i ~i q; L) j, ~> ~> }> j, j, j, %& z$ N+ O+ N+ v$ F, -/ [' t~ I~ ^] ^] I~ I~ I~ I~ G] &{ G] G] I~ I~ ^] I~ S{ G] I~ h{ h{ S{ S{ h{ h{ h{ h{ O{ 8{ u~ 4{ :] ^] I~ h{ ^] ^] ^] G] ^] I~ I~ G] %{ #{ #{ l~ 1~ ", +"#{ F~ ^] S{ h{ S{ ^] S{ ^] I~ ^] S{ ^] h{ ,_ ,: ,: ( R] R] O] R] ,: a] n{ j{ '_ R] R] O{ I~ I~ ^] G] &{ :] E{.s~ y; g/ g/ J^ G9.{) K~ $~ Q~ &~ l& l& l& &~ $~ '! /t jP 8G H9.I9.J9.K9.K9.L9.U+.f8.U+.M9.e8.qM (R DS N9.wr -< O9.P9.Q9.R9.S9.]@.DS k8.T9.CP U9.V9.W9.]) X9.Y9.r>.[@.L{.u..W` W` W` W` W` W` W` W` W` vX 1Z vX 1Z 1Z 1Z W` W` W` 1Z 1Z 1Z 2Z s>.Z9.`9. 0..0.+0.@0.#0.$0.%0.Y` !;.wX M_.Y` w..w..Y[.D4.. .d<.x..`[.&0.*0.3@.=0.-0.;0.6Z g<.<3.d3.>0.2@.,0.x=.07.x=.T_. ].'0.)0.!0.~0.{0.]0.l^.^0./0.X` (0._0.:0.<0.[0.}0.|0.10.20.30.y3.40.50.60.70.80.90.00.a0.b0.c0.d0.e0.f0.g0.h0.i0.j0.k0.l0.W~.Cs m0.n0.o0.p0.q0.r0.s0.t0.u0.v0.Qc w0.x0.y0.z0.A0.B0.C0.D0.E0.F0.G0.H0.I0.J0.K0.L0.M0.N0.O0.P0.Q0.R0.S0.T0.U0.V0.W0.X0.Y0.Z0.`0. a..a.+a.@a.#a.$a.%a.,: &a.c4.K5.*a.=a.-a.;a.>a.`d ,a.'a.k8.(R )a.!a.!@.nl ]t ~a.c. i> p- ,- 2; L) }> D# j& j& D# }> j, j, }> ;+ z$ N+ v$ v$ z$ b> bi :* k! &{ &{ G] G] G] I~ ^] I~ G] G] G] &{ G] ^] ^] h{ S{ S{ i{ j{ h{ S{ h{ h{ S{ i{ S{ F~ 4{ #{ E~ ^] &{ ^] I~ ^] ^] ^] h{ ^] I~ I~ %{ @{ #{ m~ m~ ", +"u~ #{ 8{ f^ f^ &{ I~ S{ ^] ^] S{ S{ S{ i{ :^ 8] $/ k{ ( O] '_ s^ */ */ j{ j{ s^ 6] i{ S{ G] I~ ^] G] I~ E~ DH O- ^{ {) {) K~ z) {) K~ 1! z) 1! 4 l& l& !) 1! Q~ /t LH {a.]a.88.I9.^a.L9./a.M9.f8.f8.U+.f8.qM (a._a.:a..Aa.AX Ba.Ca.Da.Ea.Fa.Ga.Ha.Ia.Ja.Ka.La.Ma.Na.Oa.Pa.Qa.Ra.Sa.Ta.Ua.Va.Wa.Xa.Ya.Za.`a. b..b.+b.@b.Sd #b.$b.%b.&b.*b.=b.-b.;b.>b.,b.'b.)b.!b.~b.{b.]b.^b./b.(b._b.*..:b. j, .> ;> -> -> y> y> x) x) =! += c.,W ,c.77.t>.'c.)c.!c.~c.{c.g7.I8.]c.^c./c.(c._c.:c.d.,d.'d.Q9.k#.e8.)d.#L .L !d.9G dw jP Tz Tz Af DE xf `5 `= x> ;> j, j, =! C> u! C> u! C> u! s! s! C> j& {{ *{ u~ l~ 4{ o~ I~ ^] I~ G] S{ S{ S{ S{ S{ ^] O{ Q{ O] /] /] O] O] i{ S{ ^] ^] S{ h{ h{ ^] S{ S{ h{ S{ S{ I~ ^] P] ]] @{ u~ ]] F~ #{ u~ ", +"]] F~ @{ #{ l~ `&.{] &{ S{ h{ ^] h{ h{ n{ j{ i{ j{ ,: ( R] s^ j{ Z] 7] 9] 8] Z] Z] i{ h{ I~ G] I~ G] I~ B! b~ /{ #~ K~ {) K~ @~ b{ [{ K~ K~ z) &~ &~ m& !) {) c~ ~d.jB {d.9J 88.kB ]d.d8.^d./d.e8._R M9.e8.k#.*O @R !%.(d./R DS (R *L k#./Z _R qM O` T9.e8.S#._d.-q ;' :d.e.,e.'e.)e.!e.~e.{e.]e.^e./e.(e._e.:e. x> x> x> x> u! s! s! ;] ;] te.C> >t f! u~ u~ `{ {] G] G] G] I~ S{ h{ S{ i{ h{ h{ &/ /] R] O] O] O] Q{ h{ S{ ^] S{ h{ h{ h{ ^] ^] S{ ^] ^] ^] I~ I~ f^ @{ @{ @{ F~ @{ l~ l~ ", +"&/ P{ F~ u~ 4{ &{ G] I~ S{ S{ ^] h{ h{ i{ N{ R] k{ k{ :( */ 8] 7] 7] 7] 7] Z] Z] Z] S{ ^] G] &{ &{ 5{ f! s H, #~ 7! {) {) z) 1! 1! Q~ !) Q~ '! &~ &~ l& l& !) !) L- &n ue.!@.ve.we.^a.d8.xe.;O ;O ye.ze.k#.YV #R Ae.Be._R *L ]@./R *L qM CP _R /Z /R K5.Fb.Ce.De.Ee.dw Fe.Ge.He.t..t..Ie.}d.|@.b%.}@.b%.b%.b%.V` b%.b%.V` Je.Ke.Le.Me.Ne.Oe.Pe.Qe.Re.Se.Te.Ue.Ve.Ve.We.L|.Xe.Ye.Ze.7<.1Z 1Z 1Z 2Z 2Z 2Z 2Z 1Z 1Z !;.1Z wX V#.Y` M_.1(.%0.$W Z` I8.`e.[(. f. f..f.+f.@f.#f.$f.%f.&f.*f.=f.-f.;f.>f.,f.'f.)f.^@.!f.~f.{f.nb.]f.^f./f.(f._f.:f.g.,g.'g.)g.-U xe.bJ !d.0J dw 8G 8G jB jP LH Tz Tz Tz &[ '$ U> x> ;> x> ;> ;> =! w> w> E) k& E) E) u! Mg Y! h; m! l~ U{ V! #{ T{ o~ G] S{ h{ h{ j{ i{ h{ h{ &/ O{ d^ R] O] /] S{ S{ S{ S{ ^] h{ h{ ^] ^] ^] G] G] I~ G] ^] f^ @{ #{ u~ u~ #{ u~ u~ ", +"h{ S{ F~ #{ f^ I~ ^] ^] S{ S{ ^] h{ h{ h{ N{ O] ( m{ Z] Z] 7] Z] n{ i{ n{ 7] Z] j{ S{ I~ ^] I~ 5{ %2 ,! N~ b{ @~ z) K~ K~ Q~ n] '! $~ $~ '! '! $~ l& $~ $~ &~ E) |! *G !g.~g.~g.Fb./a./a.f8.>O N` N` {g.OH ]g.Fb._R f8.e8.e8.*L &L CP k#.*L T9.O` O` /R ^g.9%.w)./g.(g._g.:g..dg. c.X` X` wX X` 1(.5Z |(.eg.fg.d%.3(.gg.hg.ig.jg.kg.lg.mg.ng.og.pg.qg.rg.sg.tg.ug.vg.wg.ea.xg.yg.zg.Ag.Ag.Bg.Cg.Dg.m* Eg.Fg.Gg.Hg.Ig.Jg..0.Kg.Lg.Mg.Ng.Og.Pg.vH Qg.Rg.Sg.Tg.Ug.v>.Vg.Wg.Xg.Yg.Zg.`g. h..h.+h.@h.#h.$h.%h.&h.*h.=h.-h.;h.>h.,h.'h.)h.!h.~h.{h.]h.^h./h.(h._h.:h. x) w> j& D# ~> ~> ~> D# j& k& -! H! X! `~ `= r~ 9{ u~ l~ 7{ D~ G] S{ h{ i{ i{ i{ h{ ^] h{ S{ i{ ( R] R] S{ S{ ^] ^] ^] S{ S{ S{ S{ I~ G] &{ G] I~ h{ R{ F~ u~ l~ 1~ l~ 1~ l~ ", +"S{ &/ /] /] O] S{ h{ S{ ^] S{ I~ ^] S{ S{ R{ e^ R{ %/ j{ n{ j{ n{ 9] 7] 7] Z] j{ h{ S{ S{ ^] G] p~ C& B) lh.b{ b{ @~ b{ @~ Q~ '! '! $~ $~ Q~ $~ &~ 4 4 {+ }> j, n& mh.nh.Fb.^a.^a.U+.M9.k#.oh.oM ]Z ph.qh.rh./d.M9.^a.Fb./a.e8.qM &L qM CP O` ]@.]@.(R sh.th.u=.uh.vh.wh.xh.U#.r>.[@.t..}@.}@.}@.}@.}g.}g.}@.c^.yh.V` }@.L{.U` zh.Ah.Bh.Ch.Dh.Eh.Fh.Gh.u..5a.W` Hh.Ke.Ih.Jh.Kh.U[.1Z xX xX xX xX xX xX vX vX 1Z !3.%W h^.z).vX xX xX Q{.Lh.Mh.Q{.Nh.Oh.Ph.M_.Qh.Rh.6-.Sh.Th.mg.Uh.Vh.Wh.Xh.Yh.v9.Zh.`h. i..i.+i.@i.#i.$i.w& %i.&i.*i.=i.-i.;i.>i.,i.'i.)i.!i.~i.{i.]i.^i./i.8G (i._i.:i. x> x) 9^ =! ~> }> }> }> }> ~> D# k& m& {) K~ G{ ^e X, ^~ l~ l~ %{ ^] ^] S{ S{ S{ d^ h{ h{ ^] h{ i{ :( ,_ ( '_ i{ h{ ^] ^] h{ h{ i{ S{ S{ I~ I~ G] I~ I~ ^] P{ F~ u~ l~ 1~ l~ l~ l~ ", +"^] &/ &/ &/ R{ R{ R{ N{ X] N{ P{ :] ^] S{ O{ %/ :( h{ S{ n{ j{ n{ 9] 9] 8] 7] :( '_ e^ &/ Q] t~ E- 2! 2! -: L= L= N~ L= y{ &~ &~ '! $~ Q~ z) {) {+ k& D# ~> }> }> 7> Af Wi.^a.d8.d8.:R Xi.Yi.Zi.`i. j..j.+j.dJ /a.d8.d8.d8.M9.f8.qM *L /R FS GS qM ~O @j.#j.$j.);.u=.u=.a%.a%.~(.U#.[@.t..t..[g.t..r>.r>.%j.&j.t..*j.V` }@.V` U` }@.V` t..t..t..=j.%j.t..t..t..Ah.-j.;j.>j.Te.v..vX W` xX xX xX xX xX W` W` W` xX O{.z).vX ,j.xX vX y).<~.h4.'j._(.)j.Hg.7<.!j.~j./(.<(.{j.e^.]j.^j./j.(j._j.:j.k.,k.'k.)k.!k.~k.{k.]k.,O T9./R &L CP &L &L _R U+.^k.HS /k.(k.jS NH _k.aJ @R 9J 8G jB jB jB LH jP jP Tz jP L^ ~> C> ;> =! .> =! }> j, ~> D# ~> ~> j& 4 !) Q~ Q~ !) `g k& i- l~ D1.:k.^] I~ ^] P{ :] N{ Q{ R{ N{ h{ j{ :( ( R] O] %/ h{ S{ S{ h{ h{ j{ S{ S{ S{ S{ I~ I~ G] ^] f^ @{ #{ u~ l~ #{ u~ l~ ", +"I~ ^] S{ S{ ^] /] /] O] R] /] /] h{ ^] h{ i{ i{ j{ h{ h{ j{ n{ 8] 5/ 9] 8] 7] k{ R] O] ]] .{ E- ,! B) B) <{ L= L= n] n] $~ l& &~ '! '! z) X! E) j& {+ j& D# ~> i> ^ V5 we.Fb.xe.xe..s>.1Z 2Z 2Z X` 2Z #W ck.dk.ek.fk.('.gk.hk.[~.Te.Jh.ik.jk.kk.lk.mk.nk.ok.pk.qk.rk.sk.tk.uk.vk.wk.xk.yk.zk.Ak.uj.Bk.Ck.&a.Dk.Ek.Fk.Gk.Hk.Ik.Jk.xa.Kk.Kk.Lk.Mk.Nk.Ok.Pk.Qk.Rk.Sk.Tk.Uk.Vk.Wk.Xk.Yk.Zk.`k. l..l.+l.@l.#l.$l.%l.&l.*l.=l.-l.;l.>l.,l.'l.)l.!l.~l.{l.$* ]l.^l./l.(l._l.:l. s! w> D# ~> }> }> D# D# ~> ~> D# k& &~ &~ '! '! $~ '! 4 I= F! 9{ u4 I~ ^] :] /] O] /] /] O] /] h{ j{ j{ ,: ( R] S{ ^] I~ S{ S{ h{ j{ h{ h{ h{ h{ ^] I~ I~ ^] X] @{ #{ u~ u~ @{ #{ #{ ", +"G] G] I~ ^] ^] /] O] R] R] O] O] %/ S{ i{ n{ j{ j{ h{ i{ n{ 8] A8 A8 5/ 8] 7] l{ O] O] H~ H+ I^ lh.b{ b{ N~ n] n] '! '! $~ l& &~ $~ Q~ H! C> C> n& &~ k& D# D# c> :. t8 fl./a._R e8.gl.hl.il.jl.kl.ll.vV ;U NH ml./a.f8./Z CP qM &L *L qM nl.ZV >U ol.pl.ql.rl.u=.u=.u=.a%.a%.a%.U#.[@.[@.[@.[@.U#.U#.U#.sl.Bh.~(.[@.}@.}@.}@.V` }@.}@.t..[g.t..V` }@.}g.r>.ak.ak.zh.tl.tl.5a.5a.W` W` W` W` W` T[.T[.W` W` W` 5a.W` U[.{(.s>.s>.2Z :(.2Z 1Z T[.ul.vl.Qb.ek.wl.3<.xl.yl.zl.Al.xX Bl.Cl.Dl.El.Fl.Gl.Hl.Il.Jl.Kl.Ll.Ml.Nl.Ol.Pl.Ql.Rl.Sl.Tl.Ul.Ve.Vl.Gk.Wl.Wl.Xl.Yl.Zl.`l. m..m.+m.@m.#m.((.$m.%m.&m.pj.*m.=m.-m.;m.>m.,m.'m.)m.!m.~m.{m.]m.^m./m.(m._m.:m. u! D# ~> }> }> }> ~> ~> j, }> D# 4 $~ $~ '! n] L= n] $~ ., S, ~~ I~ ^] h{ &/ O] R] R] ( R] O] i{ j{ i{ R] R] R] i{ S{ ^] S{ h{ i{ 7] Z] i{ h{ h{ S{ ^] I~ S{ P{ F~ ]] @{ u~ #{ u~ @{ ", +"I~ {] &{ I~ h{ R] R] O] O] O] R] '_ R{ :( :( n{ j{ i{ j{ n{ 8] Y] A8 5/ 9] 7] ym.&/ N{ 9; zm.+h b{ Q~ 1! 1! $~ $~ '! $~ &~ m& -! w> C> x> C> w> m& K~ |! w> D# i> y> k= jM $L Am.R@ Bm.Cm.Dm.Em.Fm.Gm.Hb.Hm.~@.=O k#._R CP &L qM k#./R /R ,%.Im.J Jm.Km.Lm.|<.u=.u=.u=.a%.a%.a%.U#.U#.[@.[@.[@.U#.U#.U#.}Z }Z U#.U#.[@.[@.[@.[@.[@.t..U` ak.sl.0k.Mm.U#.s..s..s..ak.ak.ak.U` c^.}@.6a.6a.6a.u..W` W` v..6g.Nm.x).5a.T[.v..Hh.Om.Pm.:~.1Z vX W` Qm.Rm.Sm.Tm.Um.q8.ck.Vm.Wm.Xm.Ym.Zm.uS `m. n..n.+n.@n.#n.$n.%n.&n.*n.=n.-n.;n.>n.,n.'n.)n.!n.~n.{n.]n.^n./n.(n.Se._n.:n. ~> j, j, }> }> j, }> j, ~> 4 Q~ Q~ z) y{ N~ N~ y{ y{ C) O~ y; .e &/ >_ s^ %/ '_ ,_ */ 3/ '_ '_ '_ '_ :( s^ s^ n{ j{ S{ S{ S{ i{ 7] 9] n{ s^ e^ O{ X] :] N{ /] O] /] 8{ U{ U{ U{ 8{ ", +"S{ G] {] G] h{ /] O] O] O] R] ( O] R] '_ s^ n{ h{ h{ n{ Z] 8] 8] Y] Y] 5/ n{ S{ h{ 94 p' w{ B) N~ $~ &~ &~ &~ &~ &~ n& k& -! C> x) x) C> u! u! ;] ;] H! |! D# ~> x) t4 +j. o..o.f( +o.@o.#o.$o.lB U+.qM CP (R &L qM f8.qM k#.qM (R *L T9.%o.XN :& ]g.f8.&o.8k.a%.a%.a%.a%.a%.a%.~(.U#.[@.[@.[@.U#.[@.U#.[@.[g.U#.U#.[@.[@.[@.[@.[@.[@.[g.ak.sl.*o.rl.rl.rl.rl.rl.rl.=o.=o.[d.-o.;o.>o.>o.U` c^.V` V` |g.bk.-j.,o.u..}@.'o.)o.!o.v..W` 5a.vX Um.~o.{o.]o.^o./o.(o._o.:o.m.Ao.Bo.Co.Do.Eo.Fo.Go.Ho.Io.Jo.Ko.Lo.Mo.No.Oo.Po.Qo.Ro.So.To.Uo. /.Vo.3p Wo.Xo.Yo.Zo.`o. p..p.+p.@p.#p.$p.%p.&p.*p.%=.=p.-p.;p.>p.,p.'p.)p.!p.~p.{p.]p.^p./p.%L =O e8.xe.]d.]d.~g.se.@L Ab.dM dM kB n#.dw |3 rl I9.@L d8.^a.^a.]d.f$ K# & j, [* j, j, [* j, j, j, ~> j, j& Q~ @~ [{ B) b{ b{ b{ [{ B) B) L= '! ae O] R] R{ h{ i{ i{ n{ 7] */ O] R] k{ :^ j{ i{ n{ n{ h{ S{ S{ i{ Z] 9] Z] k{ R] O] O] /] O] ]] /] O] O{ &{ {] {] G] ", +"^] &{ H] I~ d^ /] O] R] R] R] R] R] ( ( :( Z] i{ i{ n{ Z] 8] 7] 9] 9] 8] n{ z] #j.p{ 4! [{ @~ y{ $~ &~ &~ l& n& k& ~> ~> k& C> x> x> x> C> u! s! ;] K~ n& D# =! x) s% }..K. '%.(p._p.:p.m.Rp.Sp.Xn.Tp.Up.Vp.Wp.[p.Xp.Yp.F8.Zp.`p. q.u8..q.+q.@q.#q.$q.%q.&q.*q.3p =q.-q.;q.>q.,q.'q.)q.!q.~q.{q.]q.^q./q.(q._q.:q. }> }> {+ $~ z) b{ [{ [{ [{ [{ [{ 2! [{ n] n] O~ l~ O] N{ S{ h{ n{ n{ 7] :( O] ( ,_ m{ Z] n{ n{ n{ i{ i{ i{ j{ n{ 8] n{ ,_ ( ( R] O] O] F~ ]] O] R{ S{ G] G] G] ", +"G] H] {] I~ S{ Q{ /] Q{ R{ e^ '_ ( R] ( :( aq.bq.s^ n{ Z] 9] 8] i4 cq.dq.k{ eq.fq.L~ <{ y{ Q~ 1! l& n& 4 n& D# }> }> D# |! s! u! C> ;> C> w> w> s! m& -! ;> .> =! a^ gq.hq.Vz iq.jq.kq.Yi.!O v5.lq.!O /Z /R CP (R O` T9./R (R mq.DS OH X_ t).=n pM !O nq.oq.q>.q>.q>.a%.a%.a%.a%.~(.=*.=*.=*.2<.U#.U#.U#.~(.=*.=*.2<.[@.[@.[@.[@.[@.2<.pq.s..=*.1<.r.,r.'r.)r.!r.~r.{r.]r.^r./r.(r._r.:r. j& l& $~ 1! N~ C) D) C) N~ A) A) A) n] n] M^ G! ${ L{ E~ P{ :( Z] n{ s^ %/ s^ ,_ '_ ,_ :( */ :( a] m{ j{ Z] s^ */ k{ '_ ( ( /] /] R] /] /] /] P{ :] <^ 5{ ^] ", +"G] {] {] &{ ^] e^ R] R] h{ h{ j{ ,: R] ( KC Or.Or.%/ n{ 7] 9] 9] 8] S5 :( Pr.fq.[{ 2! A) n] &~ l& n& D# ~> ~> }> }> ~> k& |! s! s! w> j& j& |! ;] ;] ;] u! x) x) ~> $& 23 Qr.,@ Rr.Sr.Tr.'%.Ur./R &L CP &L /R T9.(R K5.]@.T9.(R N[.Vr.T) $$ .q>.q>.rl.rl.1<.1<.1<.<@.<@.<@.=*.2<.[@.2<.=*.<@.=*.2<.[@.[@.[@.[@.[@.<@.<@.<@.<@.<@.`r. s.O1.1<.1<.<@.<@.<@.<@.<@.2<.[@.[@.U#.U#.[@.[@.[@.[@. r.Bh.U#.=*.<@.O1...s.+s.@s.#s.$s.%s.&s.*s.=s.YY -s.;s.>s.,s.;s.'s.)s.!s.~s.{s.]s.^s./s.(s._s.:s.Tb. l& n] $~ '! n] H, 8, s; s; H, n] L= L= L= +& N@ *t.@1 L{ O] ,_ n{ n{ i{ i{ j{ m{ ( ( O] R] ( ,: s^ j{ n{ ~7 ~7 ,: R] R] /] ]] ]] R] O] /] O] /] /] F~ Q] I~ ", +"I~ &{ H] &{ D~ R] R] R] h{ S{ 6] '_ O] R] =t.-t.-t.s^ j{ 8] 8] 7] Z] n{ #/ F> C) [{ b{ $~ '! n& j& D# }> }> j, j, }> ~> {+ n& X! s! ;> j& j& `~ !) H! H! H! x> 9^ 9^ w> -, $$ s& ;t.T9.>t.mq./R qM CP *L &L CP DS O` (R k8.k8.k8.,t./~ 't.)t.E- vV !t.Wz ~t.{t.1<.1<.1<.rl.rl.1<.1<.1<.<@.<@.<@.=*.2<.[@.2<.=*.<@.=*.2<.[@.[@.[@.[@.[g.]t.O1.<@.<@.<@.<@.<@.u=.1<.<@.<@.<@.<@.<@.=*.2<.[@.[@.[g.[g.[@.U#.[@.U#.Gh.Bh.-o.rq.<@.<@.tq.-o.-o.ak.tq.tq.2<.U#.U#.U#.[d.^t./t.(t._t.:t.u.Ab.9a }$ ,u.'u.4k.)u.P^ L) L) M; [* [* }> ~> ~> }> D# &~ n] n] L= n] H, s; 8, 8, '! $~ '! n] ;^ >^ 0l n5 #6 .{ ( ,_ :( j{ h{ i{ n{ j{ ( ( O] O] R] O] h{ j{ m{ k{ ,: ( R] O] ]] ]] /] R] ( R] ( O] /] /] d^ ^] ", +"S{ G] {] &{ ^] R{ R{ z] &/ S{ i{ ,_ '_ s^ s^ :( :( n{ j{ 7] ': */ s^ V( |l `6.,! N~ y{ $~ n& }> ~> D# }> ~> j, j, }> ~> k& -! {) s! C> j& w> |! ;] u! u! !u.y> ~> Y! )) z- p` 6J &L ~u.>t.FS {u.&L /R O` k8.T9./R CP *L m..]u.^u./u.'@ 98.(u.@, pP 'B _u.xz :u.1<.1<.1<.=*.=*.1<.1<.1<.<@.<@.<@.=*.=*.=*.=*.=*.<@.=*.=*.=*.=*.=*.=*.2<.O1.<@.<@.<@.1<.1<.1<.u=.1<.1<.<@.<@.<@.1<.1<.=*.=*.=*.2<.2<.~(.~(.s..~(.s..rq.Mm.v.,v.'v.)v.!v.bt.~v.{v.]v.^v./v.(v.]@ =! w> D# ~> ~> n& &~ P~ ;^ L= n] O~ O~ %~ '! '! n] n] L= P~ A) 4! L~ p' ~* f8 o{ ,_ :( z] j{ n{ j{ s^ s^ ,_ ,_ >_ Q{ h{ n{ n{ '_ R] O] O] /] O] O] %/ ,_ */ :( l{ e^ X] R{ %/ ^] ", +"^] G] &{ {] G] h{ h{ h{ h{ i{ h{ i{ n{ j{ h{ j{ j{ j{ 7] 7] l{ R] ]] |v.C& 8, H, '! n& D# ~> ~> D# j& D# ~> }> }> }> j, D# |! H! s! u! C> C> u! ;] x> ;> 1v.+= r& 2v.:. ^ Jr.3v.K*.O` T9.CP CP /R (R ]@.68./R qM /Z /R 4v.5v.6v.7v.$$ 8v.L@ 4- Fc z5.,@ >@ 9v.1<.1<.1<.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.1<.1<.1<.<@.<@.<@.<@.<@.sq.=o.a%.1<.1<.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.O1.w.,w.'w.)w.!w.~w.{w.]w.^w./w.(w._w.;7.:w./ 3w.4w.D% O> F, }> ~> .> x) x) =! ~> j& &~ &~ ;^ P~ L= n] '! '! '! '! n] L= n] L= ;^ n] B) w{ ~ B) $; f8 R] ( R] n{ Z] j{ n{ n{ Z] R5 C#.t^ j{ Z] n{ e^ O] O] O] O] R] s^ Z] Z] 7] 7] Z] n{ j{ j{ j{ S{ ", +"^] G] &{ &{ &{ S{ S{ S{ h{ I~ I~ ^] h{ i{ h{ i{ i{ j{ n{ 7] k{ ( F~ f) 8, !- `! n& D# ~> ~> j& k& j& j& D# }> }> j, j, }> w> x> s! C> u! u! u! u! x) K> 5w.H[ ^{ >t :. 6w.7w.O` jo.8w.T9./R (R CP /R (R k8.(R /Z k#.*L 9w.0w.aw.bw.cw.dw.h; #- 9~ xz >@ >@ ew.<@.1<.1<.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.=*.sq.rl.x.,x.'x.)x.!x.~x.{x.]x.^x./x.(x._x.:x.(w. [* [* .> 9^ Ic .> ~> {+ $~ $~ n] L= n] n] n] n] n] n] n] n] n] L= L= L= 4! ~ .~ B) O- ~] (] ( l{ Z] 7] Z] Z] Z] S5 R5 R5 R5 n{ Z] 7] ,_ O] R] O] R] R] m{ 8] 7] 8] 7] Z] Z] Z] n{ i{ S{ ", +"h{ ^] G] &{ ^] S{ ^] h{ &/ O{ O{ h{ h{ j{ n{ i{ h{ %/ :( :( (] a) 3{ %, Z! >t n& k& j& D# j& j& j& j& D# ~> }> j, j, j, j, }> =! C> C> ax.|! c. bx.!~ cx.dx.RV X, >t g, 2a.ex.fx.gx.*L DS &L qM qM *L /R /R (R hx.Ur.ix.jx.kx.lx.mx.nx.ox.6w.9; z5.'@ ,@ >@ Ws px.=*.=*.=*.=*.=*.=*.=*.=*.=*.<@.<@.<@.<@.1<.1<.1<.1<.1<.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.1<.a%.qx.rx.y.,y.'y.)y.!y.~y.@/.{y.]y.^y./y.(y._y.:y. j, i> x) x) w> D# {+ &~ '! n] n] n] '! n] n] n] n] n] n] n] <{ A) P~ 2! <{ M~ B) ({ 1- +{ R] e^ n{ Z] 7] 9] 9] 8] 8] i4 S5 j{ n{ 8] */ %/ R{ N{ O] ( :( 7] Z] 7] Z] Z] Z] [3 */ s^ s^ ", +"h{ h{ ^] G] ^] h{ S{ S{ O] R] O] %/ i{ Z] 7] j{ i{ '_ ( R] py.W: qy.=< s! X! `~ k& {+ k& j& j& j& D# ~> ~> }> }> j, j, }> }> }> j& ,~ ry.|! z- E) sy.ty.uy.vy.wy.*+ `K (R T9.]@.T9./R CP /Z M9._R CP CP *L /R }a.xy.yy.zy.Ay.By.Cy.)B Dy.M) <3 >@ ,@ ,@ >@ pu Ey.U#.=*.2<.2<.2<.2<.2<.2<.=*.<@.<@.<@.<@.1<.1<.1<.1<.1<.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.1<.);.oq.Fy.Gy.a%./'./'.a%.a%.a%.a%.a%.a%.a%.a%.a%.=*.=*.a%.a%.a%.a%.1<.1<.1<.1<.1<.1<.1<.sx.sx.rl.8k.P5.P5.rl.Hy.Iy.Jy.Ky.Ly.My.Ny.Oy.Py.Qy.Ry.Sy.Ty.Uy.Vy.Wy.Xy.Yy.Zy.`y. z..z.+z.@z.#z.$z.%z.&z.*z.=z.6s.-z.;z.8s.{p.>z.hu.,z.,z.%s.Bv.Dv.Ow.Ev.Fv.Hv.gs.rt.'z.)z.9d.!z.~z.{z.Yb.]z.^z./z.y>.(z._z.:z. x) 9^ E) n& n& n& '! ;^ n] L= '! '! '! '! '! n] n] L= 2! [{ M~ +< L= M~ 2! 2! <{ p= uz.e^ i{ n{ 7] 9] 9] 7] 8] 5/ 7] i{ j{ Z] Z] i{ j{ '_ O] R] m{ Z] j{ n{ Z] 8] C9.~7 ( ( ( ", +"S{ S{ ^] G] G] S{ S{ S{ ( O] Q{ h{ n{ Z] Z] Z] n{ k{ ,: ( Y9 [' ^, ;] X! @~ 4 n& n& j& k& k& j& D# ~> ~> ~> ~> }> j, }> ~> ~> j& '$ j& s! vz.wz.xz.yz.zz.Az.o' Bz.Cz.jo./R DS /R &L &L k#.e8.e8.CP &L CP CP O` Dz.Ez.Fz.Gz.Hz.Iz.Jz.*3.f! a~ ,@ ,@ >@ >@ Kz.Lz.[@.=*.[@.[@.[@.[@.[@.2<.=*.<@.<@.<@.<@.1<.<@.sq.1<.1<.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.<@.1<.u=.Aw.Mz.Aw.a%./'./'.a%.a%.a%.a%.a%.a%.a%.a%.a%.rl.rl.a%.a%.a%.a%.a%.1<.1<.1<.1<.1<.1<.sx.sx.sq.8k.P5.Nz.Nz.Nz.Oz.Pz.Xy.Px.Qz.s..Rz.Sz.3s.Tz.]D Uz.Vz.Wz.Xz.Yz.Zz.`z. A..A.+A.@A.#A.$A.%A.&A.*A.=A.-A.;A.-z.;z.8s.>A.>z.hu.,A.,z.%s.Bv.Dv.+y.Ev.Fv.'A.Iv.hs.)A.cd.!A.~A.{A.]A.da.Uw.^A./A.(A._A.:A.%.[A.}A.|A.1A.2z.2A.3A.4A.5A.6A.7A.8A.9A.0A.aA.bA.cA.dA.eA.fA.gA.hA.iA.jA.kA.lA.2g.mA.DE G5 ,3 W} nA.r4 qy.oA.pA.<3 ;U T9.*L CP f8.U+._R /Z (R CP CP /Z k#.,%.f( qA.R' rA.sA.tA.uA.W_ ,] 9^ H_ ;> {+ n& 4 $~ ;^ n] L= $~ $~ &~ &~ '! n] L= L= [{ [{ +~ n5 ;^ M~ B) 2! w{ A) ~* N{ i{ i{ Z] 8] 8] 7] 5/ #D 5/ Z] Z] n{ Z] j{ Z] k{ ( R] s^ n{ n{ j{ n{ n{ k{ ( R] R] ( ", +"h{ S{ ^] I~ I~ ^] S{ S{ '_ &/ N{ &/ s^ :( :( */ :( ,_ R_ vA.F] j& w> |! !) y{ l& n& {+ j& k& k& k& k& j& D# D# D# D# ~> ~> ~> :) '~ 0! 0! wA.xA.yA.zA.AA.BA.CA.)3 DA.m..EA.&L &L &L &L (R _R M9.e8./Z CP qM *L rb.FA.GA.HA.IA.JA.KA.LA.MA.|^ >@ r# ,@ 0+ a+ /< NA.2<.=*.[@.[@.[@.[@.[@.2<.=*.=*.=*.<@.=*.rq.OA.PA.sq.=*.1<.sx.sx.<@.<@.<@.<@.<@.<@.<@.<@.1<.1<.1<.a%.a%.a%.u=.=o.QA.=o.u=./'./'.u=.u=.u=.u=.u=.u=.u=.u=.u=.u=.u=.u=.**.**.**.u=.u=.u=.**.T#.**.0%.1<.sx.$j.RA.Nz.T#.p>.p>.SA.TA.UA.VA.WA.XA.Jb.Sz.YA.)O ZA.`A. B..B.+B.@B.#B.$B.%B.&B.*B.=B.-B.;B.>B.Ls ,B.'B.)B.!B.~B.pu.{B.>A.]B.zv.hu.,z.%s.Cv.^B./B.Ow.(B.'A.Iv._B.:B.%7._ ", +"^] ^] I~ I~ I~ I~ I~ S{ h{ h{ ^] Q{ R] ( ( ,: ( '_ (] C& c> ~> j& 4 4 '! &~ n& {+ n& n& {+ {+ k& j& k& k& {+ j& D# D# *@ '$ a! h> LB.MB.NB.OB.PB.QB.RB.SB.TB.UB.k8.O` (R qM &L T9.(R qM f8.f8.e8.k#.(R VB.WB.XB.YB.ZB.`B. C..C.+C.@C.t5 ,@ r# '@ x w K, #C.rq.2<.[@.[@.[@.t..t..[@.U#.U#.Mm.OA.2s.$C.%C.&C.*C.$s.qq.T#.sx.<@.<@.<@.<@.1<.1<.1<.1<.1<.u=.u=.u=.a%./'./'.a%.rl.u=.u=./'./'.u=.u=.u=.u=.u=.u=.u=.u=.u=.u=.u=.t=.t=.t=.t=.**.**.**.t=.S[.t=.**.u=.Nz.$j.RA.$j.P5.P5.P5.=C.-C.;C.>C.,C.'C.)C.!C.Y) ~C.{C.]C.^C.s5./C.1m.(C._C.:C. H~ s^ Z] 8] 9] 5/ Y] 5/ 5/ 7] j{ 7] Z] */ ~7 ~7 ,: R] O] R] ( ( R] R{ h{ I~ I~ i{ ", +"^] G] G] G] G] &{ G] S{ S{ h{ S{ O] R] R] R] O] R] R] !a E$ '! ~> D# k& n& $~ 4 n& {+ n& n& n& {+ j& j& {+ {+ n& {+ D# D# x> +, g@ &D.*D.=D.-D.;D.>D.,D.'D.)D.78.(R (R K5.*L k#.e8.*L /R k#._R e8./Z qM ]@.d4.!D.~D.{D.]D.^D.#C /D.(D.~t &y r# r# r# w ( %$ _D.ww.tq.[g.[g.t..}@.}@.t..U#.U#. r.:D.z.hu.,z.,z.%s.sD.^B./B.bC.cC.tD.uD.vD.wD.xD.K4.yD.zD.AD.BD.CD.DD.ED.FD.GD.HD.ID.JD.KD.LD.MD.ND.OD.PD.QD.RD.SD.TD.UD.VD.WD.XD.YD.ZD.`D.%w E..E.+E.@E.#E.$E.0c.%E.{a.7' p! &E.z5.*E.=E.-E.;E.>E.`p.,E.4^ 'E.yy.O` O` O` T9.*L CP T9.T9./R &L &L O` (R )E.!E.~E.{E.]E.^E./E.(E._E.oA.>t z- {+ k& {+ {+ {+ {+ n& l& &~ '! n] n] n] n] n] L= P~ +~ 2! w{ w{ w{ 4! 4! V> :E.0] j{ 7] 9] Y] 5/ 9] 8] Z] n{ 8] 7] n{ l{ ~7 R] /] O] ( ( ( /] E~ I~ G] &{ ^] ", +"G] G] &{ G] &{ &{ ^] S{ ^] S{ O{ O] R] O] X] P{ R] .e ~$ e. 3~ '! k& ~> j& n& n& n& {+ k& -! l& n& j& k& {+ j& n& 4 k& j& E) :. F.,F.'F.)F.!F.~F.{F.]F.^F./F.(F._F.:F. ~> j& w> H! z) -! k& k& k& k& k& n& n& {+ n& u, BF.CF.DF.EF.FF.GF.HF.IF.JF.KF.LF.}a.T9.&L CP f8.k#.qM *L /R (R ]@.]@.MF.NF.OF.PF.QF.RF.SF.TF.UF.VF.WF.XF.Ig r# 9. 9% 9% 9% 9% YF.ZF.`F.)o.)o. G.U` [g.[@.U#.U#..G.+G.@G.#G.$G.%G.&G.ko *G.PA.1<.<@.<@.<@.<@.1<.a%.a%./'./'.rl.a%./'.a%.u=.u=.u=.u=.u=.u=.u=.t=.t=.t=.t=.t=.t=.t=.t=.t=.t=.t=.t=.t=.S[.S[.rE.rE.rE.rE.rE.S[.t=.t=.t=.w).S[.w).=G.w4 -G.;G.>G.,G.'G.)G.!G.~G.{G.]G.^G./G.(G._G.:G.H.,H.'H.)H.!H.~H.{H.]H.^H.Lg >@ Ym '$ j& k& k& {+ 4 l& &~ $~ n] n] n] L= n] n] ;^ @~ [{ ~ 4! b{ @~ &~ &~ 6w.zb ,: ~7 Z] 9] 8] 7] 7] Z] 7] Z] j{ ,_ R] O] h{ S{ h{ S{ G] G] G] &{ I~ Q] @{ ", +"G] S{ ^] &{ G] &{ &{ I~ I~ ^] G] F~ /] /] O{ /] =7 z- j& ~> ~> &~ '! '! 4 j& ~> }> j, =! ;] ;] E) {+ {+ k& k& k& j& &~ '! ~) U) /H.(H._H.:H. iH.jH.kH.lH.mH.nH.oH.pH.qH.rH.sH.tH.uH.vH.wH.xH.yH.zH.AH.BH.CH.DH.EH.FH.GH.HH.HH.IH.JH.KH.LH.MH.NH.OH.PH.QH.r5.RH.SH.TH.UH.1~ VH.WH.WH.XH.*G.YH.l/ ZH.`H.`Q I..I.+I.@I.#I.$I.%I.&I.*I.=I.-I.;I.>I.,I.'I.)I.!I.~I.{I.]I.^I./I.(I._I.:I.y 7I.8I.9I.0I.:G :G n>.aI.^|.bI.cI.dI.eI.fI.gI.hI.iI.jI.kI.lI.*L /R /R *L O` K5.'a.]@.(R 'a.J5.mI.nI.D' oI.pI.qI.rI.sI.tI.uI.vI.wI.*3.}, D# j& j& {+ l& &~ $~ $~ '! '! n] L= n] '! Q~ 1! b{ [{ @~ z) X! n& 4 I= u~ ( ,_ j{ Z] Z] Z] n{ n{ Z] n{ i{ '_ R] R{ O{ S{ S{ ^] G] &{ G] G] S{ N{ ]] ", +"G] I~ G] I~ G] G] I~ G] &{ I~ Q] f^ O] R{ L{ /_ s% '$ ~> D# D# ~> &~ n] n] &~ {+ j& =! .> =! .> ;> k& k& {+ 4 n& k& 4 4 `~ ;! xI.yI.zI.AI.BI.&'.CI.DI.R. FS EI.FI._R qM _R (R k8.DS K5.qM O` T9.k8.J5.GI.H* fb HI.II.JI.KI.LI.MI.NI.Q) }$ r# 9. 9% 9% 9% ;+ $& f4.&j.|@.)o. G.U` U` [@.[@.[@.U` OI.PI.QI.NH RI.SI.pP TI.1<.1<.<@.<@.<@.<@.1<.a%.a%.a%.a%.a%.a%.u=.u=.u=.u=.u=.**.**.0%.0%.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.rE.rE.rE.rE.rE.rE.w).w).w).rE.UI.:. VI.WI.XI.YI.ZI.`I. J..J.+J.@J.#J.$J.%J.&J.*J.=J.-J.;J.>J.,J.'J.)J.!J.~J.{J.]J.^J./J.(J._J.:J..PJ.QJ.RJ.SJ.TJ.UJ.VJ.w9 WJ.XJ.YJ.VB.]@./R *L *L O` k8.(R k#.CP k8.c4.3H.ZJ.&g.`J. K..K.+K.@K._5.#K.$K.%K.&K.pA.D# D# j& {+ &~ &~ &~ l& 4 1! n] '! '! $~ l& !) 1! b{ K~ H! `~ `~ 4 I= 9~ %{ ,_ d^ h{ n{ n{ i{ j{ Z] :( 6] R{ O] Q{ ^] S{ S{ O{ O{ G] S{ S{ h{ '_ R] ", +"G] I~ G] I~ &{ ^] S{ <^ 9{ ]] ]] :] h{ m{ *K.{+ D# k& D# k& {+ {+ n& $~ '! '! !) E) s! ;> ~> D# ~> ~> D# {+ 4 k& {+ H! ;] |! ;] =K.-K.;K.>K.,K.'K.)K.!K.=g.T9.(R CP &L *L ]@.T9.O` T9.(R CP (R T9.K5.~K.{K.O} b) ]K.^K./K.(K._K.:K.J.BK.CK.DK.EK.FK.GK.HK.IK.JK.KK.LK.MK.NK.OK.PK.QK.RK.SK.TK.UK.VK.WK.XK.YK.ZK.`K. L..L.+L.@L.#L.$L.%L.&L.*L.=L.-L.;L.>L.,L.'L.)L.!L.~L.{L.]L.^L./L.(L._L.:L. }> ~> j& k& j& k& }> n& $~ &~ !) {) s! D# j& D# j& k& D# ~> ~> ~> w> ;] {) o' t! u) oL.pL.qL.rL.sL.tL.*g.cp.O` *L qM /R ]@.]@.*L qM (R /R T9.(R ]@.d4.u8.gq.%, T) uL.vL.wL.xL.yL.zL.W> g> _ 9% 9% 9% 9% 9% 03 ;+ I$ 8C.T[.T[.6a.6a.tl.6a.6a.V` }@.[@.1<.AL.$s.Fy.BL.CL.=s.P5.1<.<@.<@.<@.<@.1<.a%.a%.a%.a%.a%.u=.u=.u=.u=.u=.u=.**.t=.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.S[.&*.5K.5K.5K.DL.DL.DL.5K.EL.FL.GL.HL.IL.JL.JL.KL.LL.ML.NL.OL.PL.QL.RL.SL.TL.UL.j0.VL.{G.j0.qK.WL.XL.YL.ZL.`L. M..M.e=.+M.@M.wK.#M.$M.%M.&M.*M.2b =M.-M.;M.>M.,M.'M.)M./|.!M.~M.{M.]M.uK.^M./M.(M._M.:M.@ >@ a+ vM.wM.xM.yM.zM.AM.BM.CM.DM.EM.FM.GM.*a.K5.(R /R O` DS DS 'a.T9.CP CP /Z /R /R ]@.&L HM.IM.JM.KM.LM.MM.NM.OM.PM.j& D# k& {+ k& j& k& k& |! ;] {) Q~ n& j& j& j& D# n& n& 4 n& n& 4 j& D# f/ /<.N{ O] O] i{ h{ h{ N{ ( ( i{ S{ h{ ^] S{ h{ '_ R] R] s^ n{ n{ '_ R] ", +"^] I~ I~ G] G] I~ ^] I~ 4{ u~ #{ <^ D~ g! z- }> }> }> D# ~> }> ~> j, j, n& n& -! {) X! n& k& k& k& n& k& ~> }> }> =! ;> s! ^, t! vo QM.RM.SM.TM.UM.VM.=g./R (R O` DS DS ]@.CP &L CP /R T9.*L T9.(R WM.XM.;& 2v.@] @1 YM.ZM.`M. N.K/ Cr y` K/ 9% 9% 9% D D D )$ .& .N.Um.U[.b%.6a.6a.6a.6a.6a.}@.t..2<.=*.He.N.,N.'N.)N.!N.~N.{G.{N.{N.]N.^N.YL./N.(N.(N._N.:N.J.6N.7N.8N.9N.0N.aN.[J.bN.cN.dN.eN.fN.gN.hN.lD.iN.jN.kN.rw.lN.mN.nN.oN.pN.qN.rN.sN.tN.uN.JR vN.wN.xN.yN.zN.AN.BN.CN.DN.EN.FN.GN.HN.IN.JN.KN.LN.MN.4y.NN.ON.PN.QN.RN.SN.TN.UN.r# 9. r# ,@ >@ 0+ n=.VN.NK WN.XN.YN.ZN.`N. O..O.+O.@O.yy.c4.DS *L (R *L O` ]@.]@.O` /Z qM k#.&L T9.K. #O.M] Yn.$O.%O.&O.*O.=O.-O.n& n& n& ~> =! =! D# D# E) u! w> E) k& D# ~> j& k& {+ n& n& {+ n& $~ l& ;O.u! 2v.DH O] Q{ z] S{ S{ N{ e^ e^ S{ S{ i{ S{ S{ S{ N{ P{ R{ s^ :( 6] R{ e^ ", +"G] G] ^] S{ ^] ^] &{ &{ u~ 1~ u~ {~ k~ ;- D# j, j, }> }> ~> }> }> [* L) D# n& u, h; _. &, n& 4 {+ $~ n& w> s! x) .> ~> }> .; D# =+ / >O.,O.'O.)O.%7./R 7E.(R /R T9.]@.O` *L &L (R qM /R /R T9.O` 'a.!O.t) ~O.'{ q` ]y {O.]O.n>.#$ r# r# r# 9. 9. 9% D D -+ ;+ 6. ^O.Ke./O.(O.W` 5a.x).6a.6a.c^.}@.yh.}@.t..2<.=*.=*.1<.1<.1<.<@.<@.<@.<@.1<.a%.a%.a%.a%.a%.u=.u=.u=.u=.u=.u=.**.t=.S[.S[.S[.S[.S[.S[.S[.S[.sE.sE.sE.&*.N1.w).&*.5K.5K.DL.DL.DL.DL.DL.DL.DL.@N._O.u! :O.O.4O.5O.6O.7O.8O.9O.0O.ab aO.bO.cO.dO.eO.eO.eO.fO.gO.hO.iO.jO.kO.lO.mO.nO.oO.pO.qO.rO.sO.pk.tO.uO.sO.;J.vO.vO.-6.wO.xO.yO.zO.AO.BO.CO.DO.EO.FO.GO.HO.IO.JO.KO.LO.MO.NO.OO.5M.PO.QO.RO.SO.TO.UO.VO.WO.XO.YO.ZO.`O. P..P.+P.@P.#P.$P.%P.&P.ZD.*P.=P.-P.;P.>P.,P.'P.<$ r# r# r# ,@ >@ 0+ ,@ s% _u.8~ )P.!P.~P.{P.]P.^P./P.(P.WM.'a.DS O` CP &L (R DS DS CP qM k#.k#./Z /Z _P.:P.!= W_ x) =! ~> }> ~> ~> }> j, j, }> }> k& n& n& l& n& n& 4 &~ 1! !) s! u! =* .{ O{ ^] S{ I~ ^] S{ ^] ^] h{ i{ R{ R{ O] O{ ^] S{ e^ R] O] N{ %/ ", +"G] I~ S{ S{ I~ ^] G] {] u~ u~ u~ u~ a~ j& D# }> j, }> j, j& D# }> j, [* [* k& c. {+ j& k& k& j& j& k& D# 4 {) u! ;> }> ~> D# +, k* aB 3P.4P.5P.6P.0#.(d.(R O` *L /R O` T9.k#._R _R qM &L T9.(R 68.kI.-q ^ h/ 7P.~O./<.8P.i' +7 / r# ,@ r# 9. 9. 9% 9% D -+ ;+ ;+ =7 %G.Lp.Jh.T[.W` x).tl.tl.6a.6a.V` }@.t..2<.=*.<@.<@.<@.<@.<@.<@.<@.<@.1<.a%.a%.a%.a%.a%.u=.u=.u=.u=.u=.u=.**.t=.S[.S[.S[.S[.S[.S[.S[.S[.sE.sE.sE.&*.&*.5K.5K.5K.5K.DL.DL.DL.DL.DL.5K.DL.9P.0P.aP.bP.:O.Q.,Q.'Q.)Q.!Q.~Q.{Q.]Q.^Q./Q.tX (Q.! 9. 9. r# ,@ >@ >@ ,@ ,@ [ X, Ac _Q.:Q. ;> 9^ Ic =! D# ~> }> j, }> }> }> D# k& k& l& &~ l& k& 4 [{ [{ @~ z) s! s! #~ i~ U_ I~ ^] ^] &{ I~ G] I~ S{ S{ Q{ R] O] &/ S{ S{ R{ O] O] &/ h{ ", +"I~ G] G] G] I~ I~ 6{ U! u~ #{ }3 r) t) u, j& D# ~> j& j& ~> }> [* ~> ~> j, D# j& I) I) :) j& j& c> D# D# |! {) ;] w> ~> }> D# l& 0Q.aQ.bQ.cQ.dQ.eQ.k..CP CP (R k#./Z &L qM _R fQ.U+.k#.qM (R n..NF.k8.`= w! gQ.hQ.iQ.jQ.^! 9. &$ R$ r# r# 9. 9. 9% 9% 9% 9% -+ ;+ -+ >t wb kQ.lQ.mQ.W` W` 6a.6a.6a.6a.6a.}@.t..2<.=*.<@.<@.<@.<@.<@.<@.<@.<@.1<.a%.a%.a%.a%.a%.u=.u=.**.**.**.u=.**.t=.S[.S[.S[.S[.S[.S[.S[.S[.&*.&*.5K.nQ.oQ.nQ.nQ.5K.5K.6K.5K.5K.nQ.DL.nQ.pQ.qQ.O> rQ.:O.sQ.tQ.uQ.vQ.|i wQ.xQ.yQ.zQ.zQ.AQ.BQ.CQ.DQ.EQ.Pn.FQ.GQ.HQ.IQ.JQ.rP.sP.KQ.LQ.MQ.NQ.OQ.PQ.QQ.RQ.SQ.TQ.UQ.6N.nj.VQ.WQ.CP.uO.tO.EP.B{.XQ.YQ.ZQ.UL.+J.`Q.-~. R..R.+R.,i @R.#R.$R.%R.&R.*R.=R.-R._[.;R.>R.,R.'R.)R.!R.~R.jA.{R.]R.^R./R.(R._R.:R.@ ,@ ,@ s] a^ )] bR.u$ cR.dR.eR.fR.gR.hR.iR.jR.kR.68.]@.CP /R k#.CP CP /R CP CP O` D.'a.X6.ou ~a lR.mR.nR.oR.pR.j, :) =! x) x) ;> D# D# D# j& ~> ~> ~> D# n& l& 4 4 4 {+ !) /a T[ (a [{ {) u! d~ 0r qR.6{ I~ db &{ I~ ^] G] ^] S{ N{ R] '_ %/ S{ ^] O{ O] O] z] h{ ", +"I~ &{ u~ #{ @{ %{ u~ u~ rR.[x.~d.c. j& D# D# j& D# k& D# ~> j, [* }> D# ~> D# D# {+ {+ j& {+ n& .; J} Y! n' n& n& $~ j& j, j, {+ ~a sR.tR.uR.vR.wR._R qM CP qM qM _R qM /R xR.'O ZV el.N` yR.0a.zR.vr += v! AR.BR.~{ s~ g> b+ b+ 9% 9. 9. 9. 9% 9% 9% 9. 9% 9% 9% 9% <$ s).CR.7:.[~.{(.W` W` 5a.b%.6a.6a.c^.[@.[@.[@.S.,S.'S.)S.!S.~S.{S.]S.^S./S.(S._S.:S.@ H+ @] LS.MS.NS.OS.PS.QS.RS.SS.TS.]@.DS ]@.CP qM e8.CP /R *L O` /R mq.*a.DS Gm.X_ '~.US.VS.WS.XS.YS.I) ^e s! u! C> C> C> |! n& j& {+ j& {+ '! r& T- ;- {+ &~ '! n] A) &~ {) {) H! J} Bu ZS.k~ K[.`S.u4 I~ S{ ^] ^] ^] E~ S{ h{ S{ I~ S{ ^] O{ h{ h{ h{ ", +"G] I~ %{ @{ F~ #{ u~ #{ T.m! x# n& k& j& D# j& ~> D# D# ~> j, [* }> ~> }> }> j, {+ {+ k& 4 n& n' ;: 5( c~ {+ k& n& j& j, [* D# <. .T.+T.@T.Sy.M9.k#.&L (R CP /R &L qM FS #T.DP $T.S+.nM %T.&T.u8.*T.=T.=< 98.,E.Vc wr m>.:G -T.V$ 9% 9. 9. 9% 9% 9% 9% 9% 9% 9. 9. 9% s; $n ;T.](.I_.{(.W` 5a.b%.6a.tl.c^.U` [g.pq.T.DR.DR.>T.HR.,T.'T.o, M@ mP )T.!T.~T.{T.1O.]T.^T./T.(T._T.:T.# $U.%U.&U.*U.=U.iR.-U.;U.]@.]@.]@.c4.T9.qM /R &L /R K5.DS k8.]@.O` >U.x{ $$ ,U.;R 'U.)U.B{ B{ Au u! ;] {) H! s! K~ l& {+ 4 l& &~ Z! !- _. ;- n& '! n] L= '! !) -! ;] ;] g/ d~ ;: V> *{ u4 &{ ^] ^] G] ^] I~ I~ G] I~ I~ &{ I~ G] ^] h{ h{ h{ ", +"{] Q] P] F~ F~ @{ #{ u~ 1~ <3 n& 4 {+ n& j& D# }> ~> }> }> }> }> D# D# }> }> [* j, D# D# k& j& ^, d~ >2 Y! ~> }> ~> *@ j, h& j& p` : !U.wg ~U.{U._R %g.*L *L CP k#.&L CP cp._R ]U.K&.#L k..X6.^U./U.(U._U.'t.M' :U.CS.Vr.@R V.P^ =+ y` w ( _ _ 9. r# r# r# 9. r# b+ r# H' |7 ,V.+= 'V.)V.!V.~V.{V.yy.'a.&L *L k8.(R /R qM qM qM *L DS DS K5.K5.]V.^V./V._l Ig Bu 4^ O9 A{ `g ;] K~ z) b{ X! C> {+ k& l& $~ &~ 4 *+ _. I= &~ '! L= A) L= n] X! ;] E) Y! ^{ ;: s; j~ 6{ &{ I~ Q] &{ G] G] &{ G] I~ G] {] I~ ^] h{ h{ h{ h{ ", +"u~ 8{ :] :] ^] G] %{ >: |- j& ~> D# j& k& j& ~> ~> ~> }> }> }> ~> j& n& }> ~> }> L) j, ~> ~> ~> E) K~ ;] |! ~> j, [* j, ^3 t8 (V.}$ L+ m, oP 9q.&O M` ^Z k#._R &L /Z *L k#._R _R HS #L $L OY _V.:V.W.,W.'W.)W.!W.~W.{W.]W.^W.Z4./W.#h.(W.r# v 9. 9. &$ _ _ &$ b+ r# 9. 9. w w w 0> A! T$ bB _W.:W. v{ '! L= ;^ a' .{ 7{ @{ {] &{ I~ G] G] G] G] &{ ^] &/ R] O] s^ i{ ", +"@{ ]] S{ ^] ^] G] #{ )n d% D# ~> ~> D# ~> }> j, j, }> ~> }> }> }> ~> {+ ~> D# ~> L) [* ~> ~> ~> E) X! x) |! D# }> }> K^ &n +j.9M a+ . Y+.]g.-u.N` $L oh.k#./a.M9._R CP k#.f8.M9.xe.ye.mM nM 1W.2W.3W.4W.5W.6W.7W.O` *L ]V.&O lM 8W.9J Uz [^ U} r# D D D 9% 9. 9% 9. b+ 9W.0W.aW.bW.cW.('.t>.1Z W` W` x).6a.tl.zh.>o.-o.[d.8k.8k.sq.<@.<@.<@.<@.1<.a%.a%.a%.u=.u=.u=.u=.u=.u=.**.t=.S[.S[.S[.S[.S[.S[.S[.S[.&*.5K.5K.5K.5K.5K.nQ.dW.ZC ,2 eW.fW.gW.7V.hW.iW.jW.JH aV.kW.lW.^T.mW.nW.oW._T.pW.dU.qW.rW.sW.tW.uW.vW.^p.wW.xW.yW.zW.AW.BW.CW.jT.}S.pU.kT.DW.EW.FW.GW.HW.IW.kT.JW.KW.jT.CV.FV.i0.LW.MW.NW.9T.rV.|S.OW.PW.QW.RW.%M.SW.TW.UW.sT.VW.WW.XW.YW.ZW.`W. X..X.+X.@X.#X.$X.%X.&X.*X.=X.aF.-X.;X.>X.,X.'X.)X.!X.~X.{X.]X.^X./X.(X._X.:X. }> [* j, [* [* j, [* j, }> }> }> D# j& k& ~> [* L) j, }> }> x> u! =! |! k& 4 8X.gq.9X.0X.aX.a+ L+ (y bX.)O &O nM &O e8.f8.-O &L cX./Z /Z e8./Z >O #L ]Z dX.eX.fX.gX.hX.iX.~O *L &L jX.nM Ab.I/ 8G jP Tz yb j4 =e D 9. 9. r# 9% &$ b+ n' kX.lX.M_.W` 2Z L_.<~.W` W` u..6a.tl.zh.>o.>o.;o.mX.8k.sq.<@.<@.<@.<@.1<.a%.a%.a%.u=.u=.u=.u=.u=.u=.**.t=.S[.S[.S[.S[.S[.S[.S[.S[.&*.5K.5K.5K.&*.5K.pQ.;# X; nX.oX.=1 pX.qX.rX.sX.bV.tX.bV.-7 uX.vX.wX.xX.yX.zX.AX.BX.Vx.CX.DX.EX.kV.vW.FX.4T.GX.HX.IX.JX.KX.KW.JW.LX.MX.MX.LX.dh.0o.oV.mV.NX.OX.KW.JW.PX.Y.,Y.'Y.)Y.!Y.,X.~Y.{Y.]Y.^Y./Y.(Y._Y.:Y. 3{ 7Y.8Y./R O` ]@.k8.(R CP /R (R k8.DS T9.T9.*L DS hq.,%.9Y.[ u$ ># ZY 4 {+ !) z) K~ {) @~ L~ #: P~ '! ;^ n] $~ n] L= P~ #6 L~ w{ ~ 0Y.+h b{ $~ n& k& k& '! N~ j8 9o (a 1{ Q_ P{ D~ G] G] ^] I~ &{ {] aY.db 7{ O] R{ N{ S{ ", +"E~ S{ h{ S{ ^] %{ T) U+ n& j& D# j& ~> j, j, j, j, j, j, j, t@ 93 $& '$ j& n& j& }> j, L) [* }> ;> u! += )~ v5 bY.9Y.!%.gl.cY.aX.}$ t) (W.0G dJ @L mM 7d.nM M` #L M` &O nM }q.U+.xe./Z dY.Q9.eY.fY.gY.hY.iY.jY.kY.k#.M9.ly.lY.`K =n LH Uz S_ yb /t Kr.9% 9% v w ( &$ 9. 9% S[ mY.nY.w..Ym.J_.oY.1Z y).1Z W` v..|@.zh.zh.>o.mX.8k.sq.sq.sq.sq.<@.1<.1<.a%.a%.u=.u=.u=.u=.u=.u=.**.t=.S[.S[.S[.S[.S[.S[.S[.S[.&*.5K.5K.&*.&*.nQ.pY.88 7a qY.rY.sY.Lm tY.uY.-D vY.wY.xY.yY.zY.zY.AY.BY.BY.CY.DY.EY.xY.FY.CX.rW.GY.HY.IY.2j.2j.JY.HX.KY.LY.MY.NY.OY.UR.PY.QY.RY.SY.TY.UY.Vx.VY.WY.XY.YY.ZY.zQ.MX.`Y.bO. Z..Z.+Z.@Z.#Z.$Z.%Z.&Z.RW.*Z.=Z.-~.-Z.;Z.>Z.,Z.'Z.)Z.!Z.~Z.#R.{Z.]Z.K'.^Z./Z.(Z._Z.:Z. |- H! {) l& n& `~ b{ w{ h^ pZ.Q~ z{ '! &~ &~ P~ @6 :e @6 #6 #6 n. [& &@ n] l& {+ E) X! b{ b{ qZ.B) L] 6> zF.8{ @{ @{ :] S{ I~ aY.!~.I~ G] :] S{ S{ ^] ", +"G] ^] S{ S{ ^] m~ ,@ ~> 4 4 {+ j& j& ~> }> j, j, }> }> }> ^+ 93 93 '$ j& {+ {+ D# j, L) [* j, =! D# rZ.W5 MH ]D 3Q.;U gl.sZ.tZ._ %' ]D NH 0G lM nM N` @L uZ.{Z @L OY nM M9.U+./a.e8.&L vZ.wZ.xZ.yZ.zZ.ih.AZ.BZ.f8./a.nh.{t Cf =n LH Tz Af 8a CZ.DZ.D 9% &$ w ( { 9% D Q> EZ.FZ. }.wX !3.#m.s>.1Z _~.vX T[.v..tl.tl.zh.GZ.=o.8k.8k.8k.sq.<@.<@.1<.1<.1<.u=.u=.u=.u=.u=.u=.**.t=.S[.S[.S[.S[.S[.S[.S[.S[.&*.5K.5K.5K.nQ.HZ.g- IZ.JZ.KZ.LZ.MZ.NZ.OZ.OZ.PZ.wY.QZ.RZ.CY.CY.CY.AY.BY.BY.CY.SZ.DY.wY.TZ.AB.UZ.VZ.GY.WZ.XZ.2j.2j.'v.YZ.[f [f ZZ.`Z.PY. `.(T.QY.QY..`.wY.QZ.+`.@`.fu #`.$`.%`.&`.*`.=`.-`.bT.]N.;`.>`.,`.'`.)`.BV.!`.`Q.~`.{`.]`.UW.YX.^`.9E ^`.YV./`.(`.)F._`.5M.:`.<`.[`.}`.|`.1`.2`.|Z.3`.4`.5`.6`.7`.8`.9`.0`.a`.b`.c`.bZ.d`.e`.f`.fX 7. 8% 5. 5. 9% D 9. y! w8 Iu W9.jS g`.)u.:s.'E.h`.hZ.:U.i`.tZ.'t.h@ h/ j`.k`.T9.*L qM k#./R (R DS ]@.O` *L &L CP *L CP (R CP (R l`.m`.n`.Hc iS 8X.$~ 4 l& m& @~ [{ 2! *: Z| >{ l& l& '! :e ,^ ,^ ,^ :e @6 }/ o`.+< ;^ '! l& s! s! K~ b{ b{ B) 4! fq.;/ *{ #{ #{ <^ S{ ^] G] !~.!~.!~.G] I~ S{ ^] ", +"G] &{ G] I~ D~ g~ 9. ~> 4 n& D# }> ~> ~> }> j, [* j, V+ 5 n% 2~ 93 *@ ~> j& *@ e& )$ j, j, j, G$ <& jB 0G L` cJ _X ;U :s.p`.ko 9. q`.]D )u.jM r`.$L M` mM {Z {Z @L #L :v.M9.U+.e8._R /Z CP s`.t`.u`.[..Lr.nM xe./a./a.Xn.9G rl rl dw LH nl |3 S^ ;# D -+ V$ ( { V$ 9% D E c( v`.w`.Z` M_.dg.:(.x`.<~.vX xX v..6a.6a.c^.s..GZ.mX.mX.8k.sq.<@.<@.<@.<@.<@.1<.u=.u=.u=.u=.u=.**.t=.t=.t=.t=.S[.S[.t=.w).w).5K.5K.5K.5K.y`.z`.s).U/ A`.B`.C`.D`.E`.DY.DY.uY.F`.G`.G`.AY.CY.CY.AY.BY.BY.AY.CY.CY.TZ.TZ.H`.I`.UZ.GY.J`.WZ.IY.K`.L`.M`.N`.Fx.O`.O`.P`.Q`.R`.P`.PY.S`.fV.DX.T`.U`.fu V`.W`.X`.Y`.Z`.mP.``. +. +. ++ + +, +' +) +! +~ +{ +] +^ +/ +( +_ +: +< +[ +} +| +1 +2 +3 +4 +5 +6 +A&.7 +8 +9 +0 +a +L@ 9% -+ 5. -+ D D =e 7J.b +l/ ho c +d +gl.Yi.p`.3v.'E.;7.e +f +1^ Ig N/ g +d4.DS k8.&L &L *L (R 68.*L /R (R qM k#.qM k#.T9.*L qM h +i +j +J/ k +AZ.m/ P~ .& %t [{ q] 0B l +G{ O9 &~ &~ +< :e mb +} ,^ :e `~ z) b{ 2! R~ M~ 88 >: u~ @{ <^ ^] O{ u4 aY.u4 db ^] I~ S{ h{ ", +"G] &{ u4 8{ k~ X_ u, j& n& n& j& }> }> }> D# ~> j, n% t@ 2~ 2~ 2~ 2~ m +z`.W~ <# $& ~$ *@ 5 P^ Jr.9G MH =U aJ n +d +$L OY kM yf a^ j..)u.)u.]D 0G I9.aJ !d.kB lY.@L .L GE M9.e8.qM e8.M9.Si.o +p +q +r +/a.s +W+.re.W+..L 8W.rl =n jB bE @1 _u.D D -+ -+ ! &$ _ _ V$ < z. ,~ t +u +I8.M_.X` !;.h4.Al.vX xX Om.x).5a.b%.c^.zh.>o.mX.8k.sq.sq.sq.sq.<@.<@.1<.u=.u=.u=.u=.u=.**.**.**.**.t=.S[.S[.b|.b|.a|.6K.DR.v +w +'T.ib x +y +z +A +{B B +C +AY.AY.AY.BY.BY.BY.CY.SZ.SZ.CY.BY.CY.D +E +F +G +H +I +O`.p).eU.J +K +L +M +mV.N +O +ZZ.P +Q +kV.QY.TR.R +S +T +U +V +W +X +Y +Q +Z +'v.` +1T. .+..+3m.kT.OW.+.+@.+#.+$.+MV.MW.%.+&.+*.+0{.=.+-.+;.+KR >.+,.+'.+).+!.+~.+{.+].+{F.^.+/.+(.+_.+:.+<.+[.+}.+|.+1.+2.+3.+4.+5.+6.+7.+8.+9.+dz.0.+Sz [ D D u# D D D K/ `9 wr pP kB jS gl.:s.:s.:s.Yi.4k.j`._a.&T.kX >t %$ Wz =7.68.T9.O` O` (R T9.(R qM &L qM qM k#.&L CP &L (R *L a.+b.+c.+xY.Am.wR.d.+e.+-y W{ N~ O5 J{ Q~ r^ l& &~ n] #6 :e mb +} _G mb ,^ s).@6 :e >^ >^ ;^ &~ k& n& 1! @~ [{ [{ B) <{ p' q# F~ 8{ f^ 8{ {] &{ I~ ^] ^] G] S{ h{ ", +"G] G] G] #{ F] j& D# D# n& l& {+ k& j& D# k& k& }> n% 93 2~ 2~ 2~ F9.m +f.+f.+z`.2~ ~$ 0^ 7# nl @R 9G 9G 9G dM kB lY.@L mM mM xf i- _X p`.;U kB 9G 9G 9G 9G kB H9.+L #L lM U+.xe.U+./a.^a.d8.g.+h.+i.+^d.U+.U+.ye.M` @L @L kB rl Cf =n (V.U} D E -+ 5. 5. 5. 9% 9% V$ { < < i> j.+k.+5Z |(.wX a<.1Z T[.xX 2Z xX W` W` 5a.6a.tl.zh.GZ.=o.8k.8k.8k.sq.<@.<@.1<.1<.1<.u=.u=.u=.u=.u=.u=.**.t=.S[.S[.N1.N1.UI.l.+m.+-} /< }7 n.+o.+1c.C`.p.+q.+p.+;~.AY.BY.BY.AY.BY.AY.SZ.SZ.SZ.CY.r.+OZ.s.+EY.t.+TZ.u.+v.+w.+x.+y.+z.+HY.A.+B.+C.+D.+E.+N`.F.+G.+H.+yQ.I.+D.+iV.sW.J.+R +K.+K.+L.+K.+M.+N.+nV.O.+P.+Q.+R.+pU.S.+T.+FU.U.+V.+W.+X.+Y.+&.+Z.+`.+UE. ++.+++++@++#++$++%++&++*++=++-++;++Zf.>++,++'++)++!++~++{++]++^++/++(++_++:++<++[++}++|++1++2++,@ 9% D D 9% D D D V$ :G $n 3++]D ~%.:s.9X.:s.:s.:s.9X.3v.3v.3v.Yi.~* r# q# >7.k8.CP /R T9.k8.k8./R CP /Z &L k#.k#.&L &L /Z /Z e8.4++5++6++7++gl.8++;U c$ T# 9++.< Y_ Q~ n] &~ n& 4 n] +< #6 :e _G +} +} s).,^ @6 :e #6 P~ ;^ '! 4 -! {) K~ z) [{ b{ B) >^ l- +{ F~ #{ u~ T{ &{ G] G] I~ I~ ^] h{ ", +"6{ <^ aY.p) D )$ D# D# D# n& D# }> n& D# ~> D# ~> n% t@ 2~ <# F9.0++0++a++f.+b++^3 .0 yf Bf c++d++8G 9J dM H9.dM H9..L .L Ab.xf &7 L e++jS dM 9G 9J @R 9G n#.bJ lM ye.:v.f8.xe.f++^a.xe.8q.g++h++Hk.i++/Z j++;O lM mM #L 0G `K `K rl /w ^3 D E -+ 5. 5. 5. 9% 9% V$ { { ] =+ ZN k++l++I8.m++n++M_.!;.1Z xX xX T[.W` u..6a.!o.c^.GZ.GZ.mX.mX.8k.sq.<@.<@.<@.1<.1<.1<.u=.u=.u=.u=.u=.**.t=.S[.S[.w)._d.o++p++q++@e ,2 r++s++t++u++p.+v++w++x++he.AY.BY.CY.SZ.SZ.SZ.r.+r.+y++z++A++B++C++MZ.D++E++F++G++H++I++J++K++L++M++N++O++P++Q++R++S++UH.P++UR.Xx.U`.VY.R`. `.yQ.T++U++V++W++X++Y++R.+P.+Z++P.+QY YZ.`++ @+.@++@+@@+#@+$@+%@+&@+*@+=@+-@+;@+>@+,@+'@+)@+!@+~@+{@+]@+^@+/@+(@+_@+:@+<@+|z [@+fA.}@+|@+1@+2@+3@+4@+5@+6@+7@+8@+9@+0@+a@+b@+D 9% D D 9% D E D 03 -T.]'.$q NH Yi.p`.3v.9X.9X.9X.D5.c@+j`.d@+:s.dC.!_ ({ e@+O` k`.(R &L ]@.*L /R /R CP qM _R k#./Z &L *L qM _R >U f@+g@+h@+i@+ze.ye.j@+fQ.fl.aJ 8a s4 &~ I= ;- l& L= M~ #6 @6 ,^ k@+l@+s).,^ mb @6 >^ +< L= '! 4 {+ `~ X! K~ @~ b{ B) +~ l- ${ @{ #{ u~ T{ &{ &{ {] &{ ^] O{ S{ ", +"7{ #{ @, %$ += D# k& D# j& k& j& }> 4 k& ~> ~> }> j, V+ 5 m +m@+m@+b++b++n@+o@+p@+Tz LH jB jB c++8G 9G 8W.{Z J9.nh.8q.Fb.Hr.9a Af aJ n#.9G @R dw dw aJ $7.(Z %g._R M9.=O e8.U+.d8.^a.U+.d8.J9.mM /d.>O &O ]Z k..nM $L mM L` HE q@+uz./ ;+ -+ -+ -+ 5. 5. 5. 9% 9% V$ _ _ / _ ~$ r@+s@+t@+u@+v@+w..Y` X` xX vX 1Z 1Z vX 5a.w@+b%.U` >o.>o.mX.8k.sq.sq.sq.tx.sx.0%.0%.0%.u=.u=.u=.u=.**.t=.w).S[.x@+y@+Uz.z@+A@+Y6.B@+C +rY.rY.58 C@+D@+x++E@+D`.F@+G@+H@+I@+J@+K@+DY.OZ.L@+M@+v++I@+N@+zX.O@+P@+Q@+R@+S@+T@+U@+V@+W@+X@+J`.Y@+Z@+`@+OY. #+sW.XY..#++#+@#+##+$#+%#+&#+*#+U++W++=#+8T.-#+-#+;#+>#+oU.9O.M`.,#+'#+)#+!#+|i ~#+{#+]#+^#+/#+^`..++(#+_#+:#+<#+[#+}#+~@+D&.|#+1#+2#+3#+4#+5#+6#+3I.7#+8#+9#+JC.0#+a#+b#+c#+d#+e#+f#+g#+h#+&Q.i#+fb _ _ 9% 9% 9% D E D D 9% 9% J9 8P.j#+Yi.9X.9X.'E.>t.B5.K*.k#+]V.]Z vV rl `= Sy.jX.yR.Ur.T9.O` &L qM _R qM k#.M9.f8._R qM &L &L _R OH l#+m#+n#+o#+ye.p#+U+.xe.q#+$7.we.r#+#e 4 , Y_ <{ +~ 34 @6 6! '^ Q/ 6^ +} ,^ :e #6 +< L= n] 4 k& H! {) K~ z) b{ B) +< A* s#+.e #{ l~ 4{ 7{ #{ 4{ u~ 7{ :] S{ ", +"#{ s~ *+ 6. _. n& n& k& j& k& j& }> ~> D# }> }> }> j, j, j, t#+u#+v#+w#+A5 x#+9a LH LH 8G 8G 8G dw 8G 9J Ir.{Z +L y#+Fb.Fb.fl.z#+Tz dM 9G 9J dw dw 9G 0G Mr.k#.k#.f8./a.M9.e8.M9.M9.M9.U+.V+.=q n#.U+.>O OY N` A#+N` nM M` M` mM B#+W_ -+ ;+ 5. 5. 5. 5. 5. 5. 9% 9% 9% 9% 9% 9% 9% 9. 7o C#+D#+4V.x..Z` n++n++cW.1Z 1Z 1Z _~.,j.5a.W` 6a.tl.zh.GZ.=o.8k.8k.8k.tx.sx.0%.0%.1<.1<.u=.u=.u=.**.w).w).S[.';.E#+F#+G#+H#+[U.I#+J#+K#+L#+K#+rY.u++2c.M#+A +u++u++u++s++p.+N#+I@+I@+O#+N#+p.+sY.P#+Q#+R#+S#+T#+U#+V#+W#+X#+Y#+Z#+z.+g++XZ.`#+`@+PY. `.%#+tW. $+.$+(T.+$+@$+ $+#$+$$+U++W++%$+=#+%$+oU.tV.&$+*$+]N.=$+-$+;$+>$+Q@+,$+'$+,$+)$+!$+~$+{$+]$+^$+/$+($+_$+:$+<$+[$+}$+|$+1$+2$+3$+4$+cF.5$+6$+7$+8$+9$+0$+a$+b$+c$+d$+e$+f$+g$+h$+i$+j$+,1 =+ ( ( 9% 9% 9% D E D 9% 9% 9% &$ ,n dC.;U 4k.p`.Cz.k$+e +e +e +4g.'%.AZ.3Q.x8 l$+N` oM ,%.(R qM &L M9.^a./a./a.d8.U+.f8./Z /Z M9.xe.)O d +m$+n$+mE.*O [v.f8.M9.U+./a.^a.Zn.Cf &[ w- t4 A) B) >^ @6 @6 v~ [] 6! s).,^ @6 :e +< L= n] &~ `~ {) z) K~ z) [{ 2! L~ 34 M~ }2 l~ l~ #{ #{ #{ u~ l~ l~ T{ G] ", +"u~ :! 6. ^ .; j& j& {+ {+ j& D# ~> j, }> }> [* j, }> j, j, z`.v#+o$+p$+|3 LH jB jB LH jB dw 9J @R 8G dw TB.{Z +L Xn.]d.Fb.Hr.Uz Tz 9J dw 9G ~%.0G )u.2w./a.k#.M9.xe.e8./a._R f8.U+.e8.M9.M9.!a q$+r$+k..oM A#+'%.N` &O $L OY .L s$+d( j@ ;+ 5. 5. 5. 5. 5. 7. ! ! ! 9% 9% 9% 9% 9. 9% t$+u$+v$+1@.x..Z` c|.n++X` 1Z 2Z t>.](.T[.5a.!o.6a.c^.s..=o.8k.8k.8k.sq.tx.sx.0%.1<.<@.O1.u=.);.);.t=.w).1D.w$+CZ.x$+H#+l.+UC.y$+I@+z$+A$+B$+C$+D$+E$+kX.F$+G$+H$+u++I$+I$+58 J$+u++u++s++p.+K$+L$+M$+N$+O$+P$+Q$+R$+S$+T$+U$+V$+W$+K`.`#+X$+uW.PY.OY.tW.Y$+ $+yQ.Xx.P`.@$+XY.Z$+>#+`$+%$+ %+%$+%$+;#+&$+o)..%+]N.+%+@%+YL.#%+$%+x.+V#+%%+&%+*%+=@+#++=%+-%+;%+>%+,%+'%+)%+!%+~%+{%+]%+^%+/%+(%+_%+:%+<%+[%+}%+|%+( +1%+2%+3%+4%+5%+6%+7%+8%+9%+0%+s% W> &$ &$ 9% u# u# D D D 9% 9% 9% V$ a%+b +b%+9X.h`.c%+d%+e%+f%+g%+go.go.4Y.:X h%+>D m#.ky.CP /Z qM qM d8.d8./a.d8.Fb.M9._R M9.U+.^a.M9.i%+j++j%+k%+l%+GE <..M9.U+.U+.xe.d8.we.aJ m%+fB `5 N~ +~ >^ #6 >^ L~ ~ ^ P~ L= n] '! l& {) {) z) @~ b{ b{ -^ $^ /a %' l~ l~ u~ u~ u~ u~ l~ l~ U{ &{ ", +"}3 F] <$ j> }> D# j& k& n& j& k& j& D# ~> }> [* }> }> t@ n`.^+ Ym ;* Af LH 8G 8G LH LH LH @R @R 9J kB kB H9.n%+{Z J9.Xn.Xn.aJ Bf LH ue.c8.we.V+.r +r +r +d8._R xe.xe./Z U+.M9.f8./a.M9.U+.U+. < ]~ o%+]V.Yq.p%+p%+lZ.Yq.f( q%+r%+03 03 u# D u# u# 5. 5. 5. 7. < < ! 9% 9% 9% 9% 9. ( (. s%+t%+*6.I8.Z` Q1.Y[.u%+v%+!;.3Z 3<.T[.v..v..x).6a.ak.[d.[d.8k.8k.8k.sq.sq.P5.P5.8k.AL.oq.);.);.**.w%+x%+/J .Z y%+_u.~t.$n z%+A%+B%+C%+D%+E%+B$+F%+G%+H%+I%+$k J%+eV.K%+L%+M%+N%+O%+P%+Q%+R%+S%+T%+U%+V%+W%+X%+Y%+Z%+`%+ &+.&++&+@&+#&+$&+OY.+$+UR.%&+#$+&&+ic.CW.W`.*&+=&+-&+`$+;&+>&+ %+%$+8T.,&+,&+qU.+Z.'&+ZL.ZL.MH.)&+!&+~&+{&+ZL.]&+^&+/&+(&+_&+:&+<&+[&+}&+|&+@x.1&+2&+3&+4&+Vu.5&+6&+7&+8&+9&+0&+a&+b&+c&+d&+e&+f&+g&+h&+i&+j&+k&+l&+t4 9. &$ &$ &$ u# 5. 5. 5. 5. 5. 5. u# 9% D V$ *e aX.Cz.;7.i`.jy.go.i`.(v.m&+n&+o&+k`.p&+j++q&+/R hx.qM (R &L e8.xe.^a.U+.d8./a.f8._R xe.xe.f8.r&+s&+t&+u&+v&+cJ w&+U+.f8.f8./a.^a.Yr.W+.+L !d.Bf 9a .< (= ;^ [{ M~ >^ #6 >^ +< +< #6 +< L= '! &~ 4 m& `~ 1! $~ '! y{ B) T[ =^ M~ X; NK u~ l~ u~ 9{ T{ U{ {] {] ", +"=7 :. ~> ~> }> }> k& ~> j& D# k& k& k& {+ j& ~> }> j, 93 x&+br Gu DE 3e jB 9J 8G LH jB dw @R @R 9G dM 9J 88.+L mM @L @L .L H9.=n 9G Xn.U+.f8.^a.d8.d8./a.xe.xe.e8.CP &L _R M9._R f8.f8._R M9.F] f@ k/ (d.k8.]@.O` O` /R Vr.`9 z. D 9% 9% 9% 9% 9% u# 6. 6. ! #+ #+ ! 9. 9. 9. 9. 9. { ! y&+z&+a3._3.I8.x..Z` Aa.A&+B&+C&+Kb.v..T[.W` W` x).c^.>o.>o.GZ.=o.rl.P5.P5.u=.tx.T` P5.8k.u=.u=.);.D&+E&+v8 Bb.F&+G&+7X.H&+Iz Yg I&+$R J&+J&+D%+K&+L&+M&+N&+N&+O&+P&+P&+Q&+Q&+eV.eV.R&+R&+S&+T&+U&+V&+W&+X&+Y&+Z&+`&+ *+.*++*+.*+@*+#*+-~.$*++$+PY.+$+}S.`Y.%*+-Z.SX.ic.KX.&*+CW.$ +**+=*+@t.@t.-*+rP.fO.;*+;*+;*+>*+KP.,*+'*+)*+!*+PV.~*+{*+]*+^*+/*+(*+_*+:*+<*+[*+}*+|*+1*+2*+3*+4*+5*+6*+7*+8*+9*+0*+a*+b*+c*+d*+e*+f*+g*+h*+i*+8*+j*+k*+9% 9. 9% 9% 9% u# 5. 5. 5. 5. 5. -+ D D D D E !a DA.~v.(v.go.l*+g%+m*+:U.hZ.n*+o*+D' p*+(R q*+g8./R k#.&L *L k#./Z e8.e8.U+./a.f8.xe.U+.Fb.r*+w% s*+t*+u*+-u.v*+/a./a.U+.U+.U+.we.{Z uZ.w*+0J @R &n Gu +& n] ;^ +< >^ >^ +< +< >^ :e +< n] l& n& n& 4 $~ '! '! n] <{ w{ B) B) 3! K{ t~ u~ u~ T{ G] &{ &{ &{ ", +">t *> ;> ~> }> }> ~> }> D# ~> ~> j& {+ n& n& n& j& D# s@ x*+yb yb Af Tz jP jB 8G LH LH dw @R dM kB dM dM aJ .L mM .L lM #L jS h%+jS K&.U+.xe.xe.f8._R M9.k#.M9.qM qM /Z qM k#./Z &L e8.qM &L !- :. J9 Be.k8.mq.]@.DS O` y*+z*+03 D u# u# u# u# u# u# u# u# 7. #+ W$ z. 9% 9% 9% _ &$ u# 03 A*+B*+C*+|3.BX d<.x..Z` ]7.D*+E*+F*+{(.{(.U[.W` W` 6a.!o.6a.GZ.GZ.rl.P5.G*+Qz.H*+I*+J*+T#.u=.P5.u=.T#.K*+L*+M*+N*+O*+nA.+; ;y }_ A$+P*+Q*+R*+w` D%+E%+E%+S*+N&+f=.LK T*+U*+g3.P&+V*+,'.,'.,'.,'.W*+X*+X*+Y*+Z*+`*+ =+.=++=+@=+#=+$=+%=+&=+SX.Xx.OY.ic.*=+KX.qc.==+MX.-=+g` g` ;=+%*+O+.>=+tP.cO.rP.eO.,=+'=+'=+)=+@.+IP.!=+~=+CO.{=+]=+^=+/=+(=+_=+:=+<=+[=+}=+|=+1=+2=+3=+4=+5=+@~.6=+7=+8=+9=+5$+oB.0=+a=+b=+c=+d=+e=+3*+f=+g=++B h=+i=+|7 9% D D 9% 9% 5. 5. 5. 5. 5. 5. -+ -+ -+ D D D 9% 'a j=+k=+go.g%+Ek.5Y.Bp.l=+m=+n=+o=+p=+q=+>U.3!.*L /Z &L qM &L &L _R _R xe.f8.U+.xe./a.]d.9q.MH r=+s=+n, re.t=+xe.U+.U+.xe./a.s ++L {Z {Z 9G @R dw LH yf p= ;^ +< +< +< P~ P~ +< #6 >^ L= &~ n& {+ l& $~ n] n] L= A) 2! [{ b{ D) $) t~ #{ #{ %{ G] &{ &{ &{ ", +":. c> D# ~> ~> c> ;+ V+ j> }> D# j& k& k& k& {+ n& 4 ff u=+S_ Tz Uz jP 8G 8G 9J 8G dw 9G kB n#.I9.l$+nh.@L .L mM Me.v&+v=+w=+x=+&O /d./a./a./Z xe.e8.qM k#.f8.M9./Z k#.f8.&L qM M9._R qM qM `! ,] ., y=+z=+A=+J5.4v._X OJ.V$ D 5. 5. 5. 5. 5. 5. u# u# D : W$ W$ : 9% 9% 9% _ &$ { qS &$ B=+C=+{j.;}.. .BX *6.D=+E=+F=+G=+Al.H=+:~.1Z y).y).x).c^.U` >o.U` rl.u=.wh.I=+l%+J=+K=+Aw.wh.T#.T#.S[.L=+M=+N=+O=+P=+xb ^- Q=+R=+S=+T=+U=+R*+w` V=+W=+X=+Y=+C%+Z=+`=+`=+ -+ -+D9 D9 D9 D9 U*+U*+.-++-+@-+#-+Pg.$-+XR.%-+&-+*-+8O.=-+--+=-+;-+>-+bG.*=+%*+$ +$ +,-+iP.'-+iP.%*+`$+)-+!-+cO.cO.uP.YL.fO.~-+{-+]-+^-+/-+(-+_-+:-+<-+[-+#++}-+|-+1-+1=+%I.2-+3-+4-+) +5-+6-+7-+8-+9-+0-+a-+IC.b-+c-+d-+:Z.e-+f-+g-+lv.h-+i-+j-+k-+l-+m-+]] { 9% D 9% 9% 9% 5. 5. 5. 5. 5. 5. ;+ ;+ ;+ ;+ -+ -+ ;+ f/ pG.n-+o-+p-+q-+r-+ba.ba.Ub.s-+t-+u-+v-+w-+Ur.&L CP k#.k#./Z k#.k#.f8.e8.U+.^a./a.~g.Fb.x-+uZ.+L y-+z-+p#+A-+xe.U+.M9.U+./a.pl.@L +L .L J9.w*+88.9G 9J Bf x8 (= <{ P~ ;^ ;^ P~ +< #6 P~ '! 4 {+ 4 $~ 1! y{ y{ Q~ [{ b{ b{ b{ g^ %' ${ V{ 5{ G] G] G] G] ", +"T- c> }> D# i> ;+ 5. 6. ~> ~> D# j& j& k& {+ {+ n& &~ &~ n] @1 3e S_ &n 9J dw @R @R kB jS 0G n#.I9.mM mM M` $L $L :v.kM :R B-+C-+w-+5k.f8./Z /Z k#./R (R T9.&L f8.e8._R CP (R CP f8.f8._R j@+v{ `) [J 4^ D-+*d.jR.E-+F-+03 9% u# 5. 5. 5. 5. 5. 5. u# 9% D D E E D 9% 9% 9% _ ( ( ( ( v v~ G-+27.. .. .H-+}(.I-+J-+K-+L-+M-+2Z 1Z P{.1Z 5a.t..)o.tl.c^.s..rl.rl.N-+kX O-+BL.Aw.oq.T#.P-+P-+L=+M=+M=+EL.Q-+O=+R-+S-+T-+U-+V-+Rn.W-+X-+X-+Y-+Z-+`-+Z-+ ;+.;+.;++;++;++;++;++;++;++;+.;++;+f=.@;+#;+$;+M|.%;+&;+*;+%-+oT.=;+-;+;;+zQ.oT.-Z.*=+*=+>;+$ +,;+KY HP.JW.$`.';+);+=#+;$+cO.RQ.!;+~;+{;+];+^;+/;+(;+(-+_;+:;+<;+[;+};+}-+|;+1;+2;+)F.3;+=l 4;+5;+6;+Hz.7;+8;+9;+0;+&x.}`.a;+b;+c;+d;+e;+f;+g;+h;+i;+j;+k;+l;+m;+n;+y` _ 9. 9. 9% 9% 9% 5. 5. 5. 5. 5. 5. ;+ ;+ ;+ ;+ ;+ 5. ;+ !~ YN o;+Ek.Dk.Gk.q-+l*+q-+p;+q;+r;+s;+t;+Xr.&L /Z CP _R /R T9.qM &L _R _R /a.d8./a.Fb./a.;u.{Z mM aX.u;+~@.e8._R _R f8.U+.Fb.#R @L @L .L +L +L L&.dM 9G @R *n &[ X& (= |- |- P~ P~ >^ +< n] l& {+ {+ &~ !) @~ b{ z) b{ @~ b{ [{ C) C& b4 5{ I~ I~ ^] I~ G] ", +"T- :. k& k& c> )$ 5. ^ j& j& j& j& k& {+ {+ {+ n& l& &~ '! s4 9a Bf dw dM @R @R @R 9G n#.0G n#.HE @L @L lM N` N` Hm./d.q +v;+w;+%L x;+M9./Z CP qM CP /R CP CP CP qM CP (R /R &L k#.M9.xe.YV %, `) v! y;+Hc z;+nI.ou 5. D 9% u# 5. 5. 5. 5. 5. 5. 5. 9% 9% D E E D 9% 9% 9% _ &$ &$ ( w ( z5.A;+B;+C;+. .D;+E;+F;+G;+H;+I;+J;+x`.P{.1Z vX u..9p.tl.tl.c^.;o.rl.rl.ak.K;+L;+M;+P5.P5.T#.T#.P-+L=+M=+M=+N;+O;+P;+Q;+R;+S;+T;+7i V-+T=+U;+X-+Y-+V;+V;+W;+X;+X;+ ;+.;+.;+.;+.;+.;+.;+ ;+ ;+ ;+Y;+@;+x.+Z;+XR.`;+ >+.>++>+@>+#>+$>+%>+HP.-Z.&>+*=+%*+>;+*>+=>+->+KW.jT.->+GP.;>+O+.tP.sP.>>+~;+,>+'>+)>+=p.!>+~>+{>+]>+5l.^>+/>+(>+_>+:>+<>+[>+}>+|>+KT.1>+2>+3>+fb.4>+5>+6>+7>+8>+9>+0>+a>+b>+c>+d>+e>+f>+g>+h>+P-.i>+j>+k>+l>+d> 03 9. 9. 9% 9% 9% 5. 5. -+ -+ 5. 5. 5. 5. 5. 5. 5. 5. -+ V$ D T) f%+m>+n>+m>+o>+p>+=&.q>+r>+s>+t>+u>+kR.*L qM CP O` ]@.(R (R *L k#.f8./Z xe.xe.k#.S+.#L .L lM v>+p#+w>+f8.e8._R f8.xe.Yr.+L @L .L @L +L J9.!d.dM 9G 88.@R Bb.Gu )a x>+>^ +< >^ #6 ;^ $~ n& k& l& !) z) @~ z) b{ b{ [{ [{ b{ >2 y>+@g.[2 ^] S{ ^] G] ", +"w! Q> j& j& {+ z- .; z- j& k& n& {+ {+ {+ {+ n& l& &~ z) @~ d% N^ z>+q$+Cf TB.dM 9G 9G 88.J9.L&.+L @L #L $L N` ]Z A#+Ho.A>+B>+=O dY.#T.f8.f8.DS ]@./R *L ]@.O` /R CP k#.&L T9.(R C>+=g. D.j@+2v.,] qy.hQ.S, D>+(t V$ ! D 9% u# u# u# u# 5. 5. 5. 5. 5. 5. -+ -+ -+ -+ 9% 9% 9% 9% &$ &$ ( w ( o, w! E>+F>+G>+d<..}.H-+H>+I>+J>+K>+Oh.h4.vX 1Z qv.L>+M>+.F.6s.N>+U` 2<.pq.sq.0%.u=.P5.P5.1<.**.T#.O>+O>+P>+Q>+R>+w8 .: S>+T>+U>+V>+K`.W>+U;+X>+Y#+BQ.y.+Y>+Z>+`>+ ,+ ;+ ;+.;+Z=+.,+V=++,++,+X;+@,+#,+$,+%,++*+&,+*,+=,+@&+-,+0o.VR.';+;,+ @+;&+%*+==+>,+,,+',+),+;;+!,+pH.~,+{,+# +],+k].-$+^,+/,+/,+(,+_,+:,+<,+1!.[,+},+|,+1,+FD.2,+3,+4,+5,+6,+7,+8,+9,+0,+a,+b,+c,+d,+e,+f,+g,+h,+i,+c>+j,+3R.k,+l,+m,+n,+o,+p,+q,+r,+hG.<. ] 03 9. 9. 9% ;# &$ ;# 5. -+ -+ u# u# -+ -+ 5. 5. 5. 5. D 03 D D s,+t,+u,+v,+w,+x,+y,+z,+A,+B,+C,+qb.D,+CP (R K5.k#.O` (R *L &L _R &L k#./a.e8.f8.^d.lM #L .L l$+OH p#+f8.e8.e8.e8.M9./d.s +#R #R mM .L L&.8W.9G @R K` dw jB jB S_ W5 Z_ +< >^ #6 +< n] 4 j& 4 m& m& !) K~ @~ b{ A) N~ N~ C) ({ :E.$2 N{ '_ O{ G] ", +"[J K> }> D# j& k& j& D# j& k& {+ {+ {+ {+ n& n& '! 1! z) z) -! {+ l& @< =n kB 9G n#.0G HE .L +L @L M` M` $L OY E,+k#+F,+G,+H,+ZV k#./R T9.CP /R /R e8./Z ]@.DS CP f8./Z k#.qM T9.T9.I,+S9.`V t)._W.qy.7P.14 iQ.ar < { 9% 9% 9% 9% 9. 9. 5. 5. 5. 5. 5. 5. 5. 5. 5. 5. 9% 9% 9% 9% 9. _ ( w ( d( 9. yF.J,+K,+L,+W_.n|.|7.M,+N,+O,+cf.%W 1Z !;.4p.P,+Q,+R,+S,+T,+U,+V,+Hy.rl.W,+wh.P5.u=.mX.X,+H_.rE.Y,+Z,+`,+|8 '+.'+Xx.+'+*;+@'+#'+$'+XZ.A.+%'+&'+*'+='+-'+;'+>'+@,+@,+@,+@,+,'+''+B.+B.+,'+)'+$&+!'+% ++*+~'+{'+XR.]'+^'+/'+('+_'+:'+ @+<'+-Z.['+SL.}'+|'+1'+2'+3'+4'+5'+iK.>7 ZX.6'+7'+8'+9'+0'+a'+b'+c'+d'+e'+f'+g'+h'+i'+j'+k'+l'+4,+m'+n'+o'+p'+q'+r'+s'+t'+u'+v'+w'+x'+y'+z'+A'+B'+C'+D'+E'+F'+G'+H'+I'+J'+K'+nG.,@ ,@ r# 9% 9. 9. &$ y` y` o, 9% D D 9% 9% D -+ 5. 5. u# 9% 9% 9% 9% D 9. 9++L'+M'+N'+e) O'+P'+Q'+R'+S'+T'+K5.CP c4.k8.T9.T9.f8.k#.k#._R CP _R f8.U+.d8.;O mM mM _X dw 4k.)g./Z _R _R &L /Z e8.M9.d8.$7.lM .L L&.@R 9J 9J dw 9J 9J 9G 9J 3e u=+@6 >^ >^ >^ ;^ l& j& {+ n& l& &~ K~ @~ y{ A) n] '! N~ v{ L' H~ R] ( &/ ^] ", +"[J `) ~> j& D# j& k& j& j& {+ {+ n& {+ n& n& 4 $~ !) z) z) -! k& k& {+ z* `K `K dM 0G n +mM lM $L nM &O N` E,+yR.qe.U'+V'+W'+)a.(R k8.c4.O` qM /R CP (R 68.]@.K5.&L e8.qM &L (R p%+X'+ D.[B.~a _W._Q.Y'+># 't.+] 5) < _ 9. 9. 9. 9. 9. 5. 5. 5. 5. 5. 5. 5. 5. 5. 5. 9% 9% 9% 9% 9. _ { ( ( 0I./ %$ PR.Z'+`'+ )+|3.+ ..)++)+ed.e<.@)+#)+wl.$)+%)+uu.&)+*)+=)+-)+;)+*G.>)+,)+@u.')+P5.))+!)+~)+{)+])+*7 6J ^)+/)+()+_)+~N.~N.'N.@'+:)+lV.MW.->+<)+<)+[)+})+|)+1)+2)+3)+SR.+z)+IO.A)+g'+k'+l'+B)+C)+D)+E)+F)+G)+H)+I)+J)+K)+L)+M)+N)+O)+P)+Q)+R)+S)+T)+U)+V)+W)+[@+V0.X)+Y)+Z)+s] }$ ,@ r# 9% 9. 9. &$ y` y` K/ 9% 9% 9% D D u# 5. 5. 5. u# 9% 9% 9% 9% D 7. '@ `)+ !+.!+^_ +!+@!+#!+$!+%!+&!+}a.K5.DS CP 68.T9.k#.CP e8.&L &L *L &L xe.xe.#R {Z lM cJ 8G dM [v./Z f8.M9._R k#.k#._R U+.9q.mM +L n%+dM kB dM @R @R 9J kB dM Jm.z>+>^ >^ >^ P~ ;^ '! {+ k& {+ n& &~ 1! z) b{ A) L= '! b{ 7> f@ *{ R] R] R{ h{ ", +"^, n' D# ~> j& {+ k& j& k& k& k& j& k& k& {+ {+ l& !) !) !) n& j& D# D# {+ |& *!+gq.)u.zb.^k.-u.S+.dY.jX.Ur.=!+lZ.f( -!+;!+>!+,%.]@.k8.k8.O` &L qM qM T9.k8.T9.CP qM qM &L O` 'a.*a.ob.,!+'!+)!+!!+y> ~!+Y'+s& L' {!+#$ D 9% 9% D 9% 9% u# u# u# 5. 5. 5. u# 5. -+ D 9% 9. 9. 9. r# 9. _ v &$ b+ r# }, :# R`.]!+^!+/!+(!+a3.2i.c7._!+l++w=.ou.:!++q!+r!+.J.s!+7)+t!+u!+u!+% +v!+w!+x!+y!+XR.0o.z!+z!+A!+B!+C!+D!+E!+F!+G!+H!+I!+J!+K!+@J.L!+*z.M!+cN.N!+O!+P!+Q!+R!+q)+9G.OV.NP.S!+T!+U!+V!+W!+X!+Y!+Z!+`!+ ~+.~++~+@~+#~+$~+iB.%~+&~+*~+=~+-~+;~+>~+,~+'~+P)+)~+!~+~~+{~+]~+^~+/~+(~+_~+:~+<~+7o s] s% r# 9. 9% 9% D 03 &$ &$ 9. 9. 9. u# -+ -+ 5. 5. 5. 5. u# 9% 9% 9% D : 7. D =e [~+DA.U} !!+}~+|~+1~+2~+3~+p%+68.O` DS DS (R O` *L (R CP qM /R CP &L _R ^a.$7.9q.Wi.zf T5 4~+k#./Z k#.k#.CP qM k#.e8.xe.$7.Wn.5~+ve.#R Zn.8W.`K ,D Cf =n e@ H, C) Q; Q; -* s; n] 4 k& {+ {+ l& z) z) b{ N~ A) y{ N~ >t T- Ac f8 '_ &/ N{ ", +";] ;] j& j& n& &~ n& {+ k& {+ j& j& {+ {+ k& j& {+ 4 {+ k& {+ D# j& k& j& ~> L^ Cf NH ^k.f8.k#.k#.&L CP /R T9.T9.6~+7~+8~+9~+/R /R K5.DS (R _R /Z &L CP O` CP qM *L &L (R ]@.c4.WM.0~+a~+b~+c~+d~+e~+f~+c> g~+^{ =; r& u# D D D D 9% 9% 9. 9. 9% 9% 9% 9% D D 9% 9% 9. r# r# ,@ r# 9. 9. 9. 9. r# s% c. Zc h~+i~+j~+k~+W_.l~+9~.m~+n~+o~+p~+lQ.q~+r~+s~+t~+u~+v~+w~+x~+y~+z~+A~+B~+@L C~+D~+E~+F~+G~+:7 (* H~+I~+J~+K~+L~+A!+M~+YZ.yQ.N~+O~+->+i!+i!+r!+P~+4^.Q~+R~+R~+S~+T~+lc.9)+U~+V~+`%+W~+X~+Y~+Z~+`~+% +#>+#>+ {+.{++{++{+g` @{+#{+${+%{+%{+&{+*{+={+-{+;{+>{+,{+.u.'{+){+)J.`J.;Z.!{+~{+{{+]{+KU.^{+/{+#a ({+_{+:{+<{+[{+}{+:8 |{+1{+2{+3{+4{+mB.5{+6{+7{+8{+9{+0{+a{+b{+c{+d{+e{+f{+g{+h{+i{+j{+k{+l{+m{+,@ ,@ r# 9. 9% 9% 9% D D 9. 9. 9. 9. 9. u# 5. 5. 5. 5. 5. 5. 9% 9. 9. 9% D D D E 9. zb 2( n{+~!+o{+p{+q{+r{+s{+t{+O` /R c4.k8.DS /R ]@.K5.qM qM qM /Z *L k#.M9.e8.M9.V+.u{+~d.v{+qM qM &L CP /Z /Z &L e8.U+./a./a.^a.^a.^a.^a.n +dM W9. d Z_ y{ ;: 5( #] 8> 8, 8, O~ 4 {+ n& n& 4 1! K~ b{ @~ @~ @~ r] h/ v! i/ C- z] %/ /] ", +"C> s! &~ n& n& &~ n& k& {+ {+ k& {+ {+ {+ j& D# j& k& j& D# k& D# D# j& ~> }> }> m% w{+[v.e8.qM &L CP /R Q9.j8.:P.!K.x{+y{+z{+Xr.(R 68.68.k8.O` k#.*L CP ]@.k8.k#.O` T9.DS J5.^@.3H.8E.A{+B{+C{+D{+E{+F{+HH #$ $$ T- a> t) D E E E D 9% 9. 9. 9% 9% 9% 9% D D 9. r# 9. r# ,@ ,@ ,@ r# 9. 9% 9% 9. [ K, Q^ G{+H{+I{+J{+ )+=}.K{+L{+M{+N{+O{+9g.We.P{+Q{+F=+R{+S{+iG.lz.T{+6p.U{+V{+W{+[q.X{+d; Y{+Z{+H&+`{+ ]+9; .]++]+@]+@a.#]+1T.$]+%]+RR._T.&]+*]+=]+-]+C{.;]+>]+>]+,]+R~+RX.']+})+lc.)]+zQ.!]+~]+)]+{]+8O.';+oT.8O.]]+D!+d)+^]+/]+/]+(]+_]+pH.:]+<]+[]+}]+|]+|]+1]+'{+$J.2]+3]+4]+5]+6]+]'+IY.7]+8]+9]+0]+{=+a]+b]+c]+d]+e]+f]+g]+h]+i]+4Q j]+k]+l]+m]+n]+o]+p]+q]+r]+s]+t]+u]+v]+w]+x]+y]+z]+A]+B]+Cj.C]+(. ,@ ,@ ,@ 9. 9% 9% 9% 9% 9% 9. 9. 9. 9. 9. u# 5. 5. 5. 5. 5. 5. 9% 9. 9. 9. 9% D D D 88 7= 2v.M] D]+[ E]+F]+G]+H]+I]+d4.^@.68.DS /R e8.CP ]@.]@./R M9.e8.e8.U+.&L &L U+.!a.J]+_u.*G *L /R *L *L *L k#.k#./Z _R f8.f8.xe.^a.xe.q#+g`.kB DE H- y{ qZ.5( >2 ;: s; 8, %~ '! l& n& 4 n& n& m& {) b{ z) K~ @~ ;: h/ v! i/ b' D~ %/ O] ", +"x) ;] 4 {+ k& 4 n& j& {+ {+ {+ {+ k& j& D# D# D# ~> }> }> j& D# ~> D# }> j, j, ~> L- K]+)O L]+M]+O` T9.DS N]+.D.O]+P]+Q]+R]+k8.]@.(R O` qM &L k#.&L /Z qM O` CP K5.'a.^@.3H.68.WM.S]+T]+U]+V]+W]+X]+Y]+Z]+Ig 6. += C& 2( -+ -+ j@ -+ -+ u# 6. 6. / 9. 9. 9% 9% 9. r# r# r# r# ,@ >@ ,@ r# 9% D 9% 9. [ '@ `]+>@ DY. ^+.^++^+q|.@^+#^+$^+%^+mg.&^+*^+=^+-^+;^+Sp.>^+,^+Nb.Ud.'^+)^+!^+~^+{^+]^+^^+/^+(^+_^+Z{+:^+r; <^+[^+}^+|^+1^+2^+{'+6)+BQ.3^+4^+5^+6^+7^+-]+8^+9^+0^+a^++i o!+b^+5O.!]+b^+c^+d^+e^+zQ.f^+g^+h^+&=+KW.e!+i^+j^+=~.DV.VL.k^+l^+m^+n^+o^+p^+>{+q^+r^+s^+t^+u^+v^+w^+x^+y^+z^+)>+A^+B^+C^+D^+~*+E^+F^+G^+SM.H^+I^+J^+K^+L^+|{+M^+N^+O^+P^+Q^+R^+S^+T^+U^+V^+W^+X^+Y^+Z^+`^+ /+./++/+@/+#/+$/+,@ Q) _. _. }$ r# r# 9. 9. 9% 9% 9. 9. 9. 9. v 9% 5. 5. 5. 5. 5. 5. u# 9% 9. 9. 9% ;# o, zl %/+_{ d~ &/+*/+zP =/+-/+;/+>/+^@.u8.u8.WM.c4.CP T9.*L *L 68.DS ]@.qM U+.M9.e8./Z k#.j@+,/+t) t5 4k.,O &L (R ]@./R qM /Z _R /Z k#.M9.M9.f8.V+.W+.Jr.$~ -! X! (a J] 5( ;: s; 8, r& 1! Q~ m& 4 l& {+ -! 1! y{ Q~ 1! Q~ v{ )) Q> w! p` 2{ (] R] ", +"u! @~ !) {+ {+ n& {+ k& k& {+ {+ k& j& D# D# D# D# }> }> }> ~> ~> }> ~> }> }> }> }> }> d% '/+)/+!/+68.k8.~/+{/+(a.]/+^/+//+(/+K5.T9.*L T9.&L CP /R CP /R T9.K5.68.*a.*a.'a.d4.'a.c4.>/+68._/+:/+@ >@ r# 9. D 9. r# ,@ ,@ ,@ }$ s! 4/+5/+6/+7/+;}.W_.j<.b(.8/+9/+0/+a/+b/+c/+d/+e/+Kb.f/+7s.g/+h/+i/+8) j/+k/+l/+m/+c; n/+o/+p/+q/+y-+Wg r/+s/+t/+u/+W#+O++@,+4^+S&+R&+,'.v/+w/+x/+y/+z/+lf.A/+B/+C/++J.c^+2'+D/+i)+E/+F/+G/+H/+Qd I/+J/+K/+L/+L/+{,+M/+pK.N/+DK.O/+>J.n^+P/+Q/+R/+BH.S/+T/+v^+0G.U/+V/+3C.W/+X/+Y/+Z/+`/+7M (+.(++(+<-+>Z.@(+#(+$(+$(+%(+&(+wC.R^+*(+=(+-(+;(+>(+,(+'(+)(+!(+~(+{(+](+^(+/(+((+_(+:(+<(+OJ..( }$ _. _. r# r# ,@ r# r# r# r# r# r# 9. v w _ 9% 9% u# 5. 5. -+ D 9% 9% 9% 9% 9% [ [(+}(+y>+Zc y;+y;+qy.SY |(+1(+g%.2(+>/+'a.J5.'a.T9.CP f8./Z *L *L (R _R e8._R k#./R 68.]@.3(+Ac t).pG.]V.xR./R T9.(R &L *L qM _R _R _R M9.4(+5(+6(+U+ te.s! s! ~) 5( >2 ^{ 8, 8, r& ~) K~ K~ l& n& n& {+ l& $~ &~ &~ &~ &~ n& n& o' d) g, _] ( ", +"C> {) `~ n& {+ 4 k& k& k& k& k& j& D# }> ~> D# ~> }> }> }> ~> }> j, ~> }> }> }> j, j, y> +, 7(+8(+^@.c4.9(+0(+ve.a(+b(+c(+d(+N[.T9./R O` (R K5.DS O` ]@.O` k8.c4.K5.*a.68.k8.c4.68.K5.u8.e(+f(+g(+h(+i(+j(+k(+l(+m(+n(+o(+f; _. 5. 5. 5. 5. 6. 6. u# 9% 9% 9% 9% 9. 9. r# r# r# ,@ >@ >@ ,@ r# 9. 9. 9. r# r# ,@ r# }, Ig p(+q(+r(+s(+t(+Z5.a3.u(+G>+-W v(+w(+x(+y(+z(+A(+B(+nY.C(+Dh.D(+E(+F(+G(+nh.H(+I(+0J == J(+K(+L(+T=+J[ M(+''+N(+O(+0U.R&+4^+4^+P(+0U.Q(+R(+S(+T(+U(+V(+W(+X(+Y(+Z(+qV `(+ _+._++_+@_+L!+*z.l(+#_+$_+%_+'M.&_+M!+P/+fS p^+*_+*_+=_+zH.-_+;_+>_+,_+'_+)_+!_+~_+{_+]_+^_+/_+(_+__+:_+<_+[_+OV.}_+|_+1_+:{+2_+3_+4_+,5.5_+6_+;'.7_+8_+9_+0_+a_+b_+c_+d_+e_+f_+g_+h_+i_+j_+k_+l_+m_+n_+(. %$ %$ :. _. _. %$ 9. r# r# ,@ ,@ r# ,@ r# 9. v w &$ 9% 9% u# 5. 5. -+ E E D D D $& o_+p_+q_+r_+Zc y;+qy.3X.pA..e s_+t_+u_+*a.'a.K5.'a.68.*L _R _R e8.qM (R qM qM (R /Z *L T9.O` z=+p4 ^ 23 v_+FS *L CP /R T9.(R *L CP k#./Z jY.w_+f E) E) s! s! u! s! J} d~ >] r& 8, /{ {) X! m& !) n& n& n& 4 &~ l& &~ l& 4 k& k& o' i/ / F! ,: ", +"C> |! j& n& {+ k& k& n& k& j& j& D# ~> }> i> 5. i> }> }> }> }> }> j, }> j, j, .> .> =! ! x_+&, Oi.y_+I5.I5.>t.|3 z_+A_+B_+C_+*L /R CP qM &L /R K5.O` ]@.'a.'a.K5.J5.K5.d4.^@.c4.d4.'a.(@.D_+E_+F_+G_+H_+I_+J_+K_+L_+M_+N_+O_+P_ -+ 5. 5. -+ D u# u# 9% 9% ! 9% 9. r# r# 9. v [ '@ 0+ W_ [ r# 9. 9% 9% 9. 9. b+ B5 aM bR.P_+Q_+R_+S_+T_+=}.U_+V_+W_+X_+v@+d<.Y_+Z_+`_+ :+.:+W` V` 6a.5a.+:+@:+#:+$:+%:+&:+*:+=:+_) -:+<' ;:+>:+,:+':+XZ.):+):+!:+%k !:+~:+{:+]:+^:+/:+*E.(:+_:+::+<:+[:+ri }:+|:+1:+2:+3:+4:+5:+6:+7:+*(+8:+9:+0:+a:+b:+c:+)B.d:+e:+GK.d:+f:+g:+>~+h:+i:+j:+fe.}C.k:+a]+l:+<_+m:+n:+o:+p:+p:+}_+q:+r:+s:+t:+u:+v:+w:+x:+y:+z:+A:+B:+C:+D:+E:+F:+G:+H:+I:+J:+K:+L:+M:+N:+O:+P:+Q:+R:+hQ.i/ }$ %$ }$ }$ }$ v &$ ( w '@ [ r# r# r# b+ _ 9. 9. 9. 9. 9% 5. 5. -+ >+ j@ n% : : S:+T:+U:+V:+W:+X:+$~ [t q` *3.*!+Y:+Z:+`:+'a.K5.O` ]@.c4.k8.DS qM f8.&L qM (R O` /Z /R O` O` J5. <+Ox y> !; 9X.Ur.(R *L (R O` T9.(R xR.*L DP go r. O! Sj w> k& u! u! s! J^ d~ #~ D) C) H, c~ H! ;] H! `~ `~ |! |! `~ 4 l& 4 4 n& n& 7> Q> -+ ,@ so ", +"D# D# k& k& {+ {+ {+ {+ j& j& j& D# ~> }> i> 5. >+ :* j, j, }> }> j, [* [* [* +> Ic 9^ -> !~ .! ;+ Jr.=g..<+b%+0~ /V.+<+&{+@<+*L *L *L qM CP DS c4.*a.J5.68.*a.O` K5.*a.68.u8.u8.#<+H/ I]+4~.$<+%<+&<+*<+=<+-<+;<+><+,<+'<+)<+!<+CB.5. 5. -+ D 9% _ ( ( ! 9% 9. r# r# 9. _ { ( b+ Cr r# }$ r# 9. 9% 9% 9% b+ [ x [ ~a ~<+j5.{<+]<+^<+/<+(<+_<+`` `` x..|(.|(./0.2Z vX 1Z 1Z 2Z xX |@.Fp.yv.`s.:<+SC.e}.<<+[<+}<+|<+1<+2<+3<+4<+5<+6<+7<+8<+9<+$'+HY.0<+9V.dK.a<+b<+c<+d<+e<+f<+g<+h<+i<+j<+k<+l<+m<+n<+o<+p<+7:+q<+r<+s<+t<+u<+v<+w<+x<+y<+z<+A<+[8.B<+vR.C<+D<+E<+F<+G<+H<+I<+J<+K<+L<+M<+N<+O<+O<+P<+Q<+R<+S<+T<+I^+U<+V<+x:+W<+:;+X<+Y<+Z<+`<+ [+.[++[+@[+#[+$[+%[+&[+*[+=[+-[+;[+>[+,[+O^ s] '[+,@ ,@ ,@ ,@ r# v ( { ( w ( v 9. r# r# 9% 9. 9. 9% 9% 9% 9% 9% u# ;+ ;+ ;+ ~' x&+1'+)[+![+~[+{[+5] ][+^[++2 <. K]+/[+([+_[+'a.68.(R k8.K5./R T9./Z k#.qM _R k#.k#.CP O` k8.'a.#<+9(+:[+<[+,U.[[+}[+T9.O` DS T9.O` ]@.FS Hb.x* U) j& j& D# D# k& |! H! H! {) {) K~ @~ b{ @~ K~ X! s! u! ;] ;] s! s! s! `~ n& n& n& n& k& {+ j& }> i> s ", +"j& j& k& j& {+ {+ {+ j& j& j& j& j& D# )$ i> 5. >+ [* }> j, [* j, j, [* L) L) ~e H_ Ic D> .! Z~ -) H) yb |[+,D w! 1[+<$ W& &L (R ]@.T9.*L /R k8.c4.K5.*a.'a.K5.O` 68.'a.d4.2[+3[+4[+5[+(@.6[+7[+8[+9[+0[+a[+b[+c[+d[+e[+f[+g[+h[+i[+5. 5. -+ D 9% &$ ( ( _ 9% 9. r# r# r# b+ ( { qS o, %$ :. r# 9. 9% 9% 9% 9. [ x w j[+T$ >'+k[+l[+B8.^<+m[+n[+o[+`` x..|(.|(.|(.M_.M_.wX !;.2Z Ym.vX 5a.V` %j.p[+')+Lz.#g.:g.q[+r[+s[+t[+MH u[+v[+w[+x[+Oo.y[+z[+A[+BQ.B[+C[+dK.D[+E[+F[+G[+H[+`Q.I[+J[+K[+L[+M[+N[+O[+D/+w_.P[+t*+Q[+Q[+R[+Ab S[+T[+U[+V[+V[+1J.%A.V[+BE W[+X[+Y[+Z[+`[+ }+.}++}+@}+#}+$}+%}+&}+*}+=}+-}+;}+>}+]>+,}+'}+)}+!}+~}+{}+]}+^}+/}+(}+_}+:}+<}+[}+}}+|}+1}+2}+3}+4}+5}+6}+7}+s$+ZN #$ '@ ,@ ,@ r# r# 9. b+ ( { ( w ( &$ 9. r# r# 9% 9% D D 9% 9% 9% 9% 9% 5. 5. ;+ 8}+9}+0}+a}+b}+c}+d}+e}+f}+2^ g}+A; &t.h}+@O.d4.'a.k#.k#.T9.T9./R *L &L *L /Z T9.k#.k#.68.'a.K5.J5.J5.i}+b%+g~+[J j}+Cz.T9.k8.'a.k8.O` ]@.[B.k}+.h }> D# D# j& ~> j& ;] H! {) @~ z) @~ b{ K~ z) z) {) u! C> s! s! s! s! s! -! {+ n& n& j& D# k& D# }> j> 7. ", +"D# ~> j& k& {+ k& j& k& k& j& D# j& D# ~> }> )$ j> > ~> j, L) [* .> U> +> bi ~e ~e !> D> Z~ Z~ ;G -) u# Q5 l* pA.l}+c> m}+(R qM &L (R 68.DS 68.'a.DS 68.K5.'a.68.68.^@.3H.>/+n}+u,+o}+p}+q}+r}+s}+t}+u}+v}+w}+x}+y}+z}+A}+B}+C}+D}+5. 5. : D 9% &$ b+ &$ 9% 9. 9. 9. ,@ ,@ ,@ '@ b+ b+ o, ] #$ 9. 9. 9% 9% 9% 9. [ [ g> E}+u# s& V`.F}+G}+H}+I}+F(.J}+v$+*6.$W 5Z |(.I8.I8.Z` X` 2Z vX vX vX K}+u..b%.L{.2<.1<.L}+M}+N}+O}+gt.P}+B*+Q}+R}+e].S}+T}+nP.^p.`;+U}+V}+W}+X}+Y}+Z}+`}+ |+.|++|+@|+#|+WI.$|+%|+&|+*|+=|+-|+;|+>|+,|+'|+)|+!|+T6.~|+{|+]|+^|+/|+(|+_|+:|+<|+7O.[|+HU.}|+||+K<+1|+2|+3|+4|+4|+5|+6|+7|+8|+9|+@a.0|+a|+b|+c|+d|+e|+f|+g|+h|+i|+j|+k|+l|+m|+n|+4@+o|+p|+q|+r|+s|+t|+`> 2, u|+F# YN r# ,@ r# &$ 9. w w w w w w b+ r# r# r# 9% D D D 9% 9% 9% 9% u# 5. 5. *@ 9! v|+w|+x|+y|+z|+M=.A|+B|+w8 5r mP C|+D|+E|+S]+]@.O` DS qM /R k8.qM /R T9./R k8.(R O` DS DS K5.J5.N9.9(+sZ.pA.F|+UC 9X.mq.o..G|+o..k8.%7.O@ W~ 5 j, }> }> D# D# D# x> C> X! @~ qZ.$^ lh.X! {) K~ !) |! C> u! s! E) E) |! {+ {+ n& 4 w> ;> j& w> ;> i> >+ ", +"D# }> j& k& {+ {+ {+ k& j& j& j& k& ~> }> }> }> }> j, j, j, U> H_ 9^ Ic H_ bi => g8 g8 -) Z~ -) F, z$ 5. 1+,1+XR.'1+)1+!1+~1+DV {1+]1+^1+/1+(1+_1+:1+<1+@|+[1+}1+|1+11+21+31+41+51+61+71+81+91+01+a1+b1+81+'q c1+K~+,}+d1+e1+f1+g1+h1+#}+$}+i1+j1+k1+r:+=}+l1+m1+n1+o1+p1+q1+r1+s1+3/.t1+u1+v1+w1+x1+y1+z1+A1+B1+C1+D1+E1+F1+G1+H1+I1+J1+r# b+ K1+( w v 9. 9. v w x w w w w w w [ ,@ r# 9. 9% 9% 9% 9% 9% 9% 9% 9% 9% 9% 9% ( L1+M1+N1+O1+P1+Q1+R1+S1+T1+U1+s; g^ n}+V1+W1+0~+k8.c4.68.e8.*L ]@./R O` (R /R /R CP K5.O` DS *a.68.c4.,3.Fr.n{+># ~O.WN I5.yy.c4.X1+Y1+j ~> j, }> }> }> }> }> D# j& C> s! H! @~ i] Z1+2! z) K~ '! '! 4 C> x> s! j& j& k& k& {+ n& 1! ;] x> u! C> x) i> 6. ", +"~> ~> k& {+ k& {+ k& D# D# j& k& {+ j& ~> }> j, }> }> [* j, U> H_ Ic Ic H_ H_ g8 g8 g8 g8 g8 g8 t; v$ _. 5: Zc 3X.v! <$ H|+N9.]@.c4.K5.'a.*a.c4.DS 68.T9.DS 68.k8.68.>/+^@.S]+>/+`1+ 2+n}+.2++2+@2+#2+$2+%2+&2+*2+=2+-2+;2+>2+,2+zr 5. 5. 7. ( ( v 9. 9% D 9% 9% 9. ,@ ,@ ,@ ,@ >@ a+ x w v 9% 9% 9% 9% 9% 9. 9. 9. 9. 9. 9. ~$ A*+vo P{ '2+)2+!2+~2+q|. 1+;}._3.BX $W `` $W x..I8.Z` X` _~._~.T[.T[.T[.W` u..t..2<.2<.tq.tq.{2+]2+^2+/2+(2+_2+:2+.&+;'+O.+M|.<2+[2+}2+|2+12+22+32+42+52+62+72+&D.82+92+02+a2+b2+c2+d2+e2+f2+g2+h2+i2+j2+k2+'|+l2+m2+n2+o2+Yh.R#+p2+q2+{-+e1+r2+h1+$}+s2+i1+j1+j1+@}+t2+u2+v2+w2+x2+y2+z2+A2+B2+C2+D2+E2+F2+G2+H2+I2+J2++~.K2+L2+M2+N2+O2+P2+Q2+R2+a+ UN.S2+4> ( ( 9. 9% 9% &$ ( w ( ( ( ( w w '@ ,@ 9. 9% 9% 9. 9% 9% 9% 9% 9% 9% 9% 9% 9. l}+T2+U2+V2+W2+X2+Y2+Z2+`2+ 3+.3+%, 7/ +3+@3+#3+yy.*a.WM.k8.*L &L _R /R *L &L /R qM *L *a.K5.d4.WM.K5.#<+$3+%3+,2 h@ 14 88 &3+*3+=3+L '+ -3+j, j, j, j, ~> }> j, }> D# u! H! H! H! ;3+>3+lh.X! K~ A) '! l& w> Ic C> D# D# j& j& {+ n& H! s! x) C> C> x) i> 6. ", +"~> j& |! {+ E) h> c> )$ += D# ~> j& {+ k& ~> j, ~> }> %& :* Ic Ic Ic !> +> !> g8 g8 -) z, -) g8 N+ v$ 4- 9B 3X._Q._Q.6. zf yy.yy.:w.n..o..c4.*a.K5.c4.K5.DS u8.S]+'a.K5.T9.K5.S]+n}+,3+'3+>/+)3+!3+~3+{3+]3+^3+/3+k7 (3+_3+:3+<3+Su 5. 5. ! _ v b+ 9% 9% 9% 9. 9. r# _. _. K, K, >@ a+ '@ b+ 9. 9% 9% 9% 9% 9% 9. r# r# 9. 9. 9% 9% u$ v xz [3+m<.}3+|3+13+,/.k~+;}..1+BX d<.~3.~3.x..I8.|(.|(.|(.2Z vX vX y).W` V` t..[g.ak.23+=*.rq.33+43+Qj f] 53+63+73+83+93+03+a3+b3+c3+d3+e3+f3+g3+h3+i3+5U.j3+k3+l3+m3+n3+o3+p3+q3+r3+s3+t3+u3+v3+w3+x3+y3+z3+A3+k<+n2+B3+C3+D3+/..);+ +E3+h1+s2+F3+G3+H3+I3+B}.J3+K3+L3+M3+N3+O3+P3+Q3+R3+S3+T3+U3+V3+W3+X3+Y3+Z3+`3+ 4+.4++4+@4+#4+$4+%4+[ [K.&4+#U.W> ( ( v r# r# r# b+ &$ { ( ( ( w w '@ r# 9. 9% 9% 9% u# u# 5. 5. 5. 5. u# u# u# ^ *4+=4+-4+;4+>4+,4+'4+)4+!4+}3 `| }8 ~4+{4+@3+lI.J5.u8.J5.K5.M9.&L &L e8.T9.]@.*L /R ]@.k8.c4.'a.J5.d4.]4+D9.^4+SY _l m, /4+(4+>= 0! _4+:4++> .> U> +> [* =! }> j, j, =! E) E) K> Mg ^a @~ {) K~ A) L= &~ w> U> .> ~> D# D# j& {+ n& k& w> ;> ;> C> x> i> Z) ", +"j& E) E) s! u! 6. 5. 5. ^ 6. ;+ D# D# ~> j& j& j& 5. z$ >+ !> H_ H_ b> z$ v$ N+ v$ z$ z$ z$ F, v$ ^ r, ZY qy.qy.g/ .; ,y <4+[4+}4+}4+ q.|4+oF.k8.k8.68.DS J5.J5.K5.O` DS d4.S]+14+n}+e(+3[+5[+24+34+44+54+64+8+ 74+84+94+04+a4+b4+9% 9% 9% 9% 9% 9% 9% 9% 9% 9. r# r# _. _. _. K, >@ >@ ,@ r# 9. 9. 9. 9. 9% 9% 9% r# r# 9. 9. 9% 9% u# _ %$ F# c4+d4+I}+e4+f4+F(.g4+k~+;}._3.d<.~3.x..I8.I8.x..x..M_.!;.!;.y).u..u..u..Ie.Ie.}@.t..h4+i4+j4+k4+^2+s@ l4+m4+n4+o4+p4+q4+r4+s4+t4+u4+v4+w4+x4+y4+z4+A4+B4+s3+C4+D4+E4+F4+G4+H4+I4+J4+X&+K4+L4+M4+N4+21+O4+P4+Q4+R4+S4+T4+)$+U4+U4+CE.V4+W4+X4+Y4+Z4+`4+ 5+.5++5+@5+#5+$5+%5+&5+*5+=5+-5+;5+>5+,5+'5+)5+!5+~5+{5+]5+^5+/5+(5+_5+:5+<5+YN b+ ( 4> ( w w v r# r# 9. r# r# &$ ( ( w ( ( [ 9. 9. 9. 9% 9% u# 5. 5. 5. 5. 5. 5. 5. 5. %$ [5+}5+|5+15+25+35+45+55+65+75+w5 no 85+95+05+(@.c4.c4.d4.68.(R (R f8.k#./R CP /R k8.'a.J5.d4.K5.K5.a5+b5+c5+d5+e5+'@ y ;y xV m 9^ ~e ~e ~e 9^ Ic bi H_ 9^ ~> }> j, ~> D# .; K> v! >] {) s! |! !) '! &~ k& }> j, }> D# ~> D# j& {+ k& j& D# ;> x> C> ! !~ ", +"{+ n& u! u! w> 5. ;+ ;+ 6. 6. )$ ~> }> D# {+ D# ~> )$ >+ >+ !> ~e !> z, v$ N+ N+ v$ v$ v$ v$ v$ v$ s; ;= ^{ y;+1x.BR.n' f5+(;.g5+g5+h5+i5+j5+k5+J5.J5.^@.k8.c4.'a.J5.DS O` ^@.3H.3H.J|+u,+>/+N'+l5+m5+n5+o5+p5+q5+r5+s5+t5+u5+v5+$, 9. 9. 9% D D D 9% 9% 9% 9. r# }$ _. _. _. K, >@ >@ ,@ ,@ r# r# r# 9. 9% 9% 9% 9% 9. 9. 9. 9% 9% 9% 9. r# ^, ,@ w5+x5+y5+z5+13+A5+A5+T_+m[+(<+B5+x..x..x..`` `` I8.V#.w..wX xX xX v..v..v..W` u..7p.C5+`x.D5+E5+ s.F5+Lc G5+T%+H5+I5+J5+K5+L5+M5+N5+O5+P5+Q5+R5+S5+T5+U5+V5+W5+X5+Y5+Z5+`5+ 6+.6++6+2^.@6+#6+$6+%6+:|+&6+*6+=6+-6+;6+>6+,6+'6+)6+!6+~6+{6+]6+^6+/6+(6+_6+:6+<6+[6+}6+|6+16+26+36+46+56+66+#w.76+86+96+06+a6+b6+c6+d6+0u %@.A; <. v ( ( ( ( w w 9. 9. 9. 9. r# r# ( ( w w ( ( b+ 9. 9. 9. 9% 9% u# 5. 5. ;+ ;+ ;+ ;+ ;+ 5. d) el.e6+f6+g6+h6+i6+j6+k6+l6+m6+n6+i8.o6+H).p6+q6+c4.c4.J5.'a.'a.T9.e8.&L *L &L &L DS ]@.WM.u8.c4.68.D,+r6+s6+t6+u6+v <. q` t).U> Ic H_ H_ H_ 9^ H_ H_ Ic 9^ }> j, }> }> ~> k& d) [J [J u! C> u! {+ 4 4 k& D# ~> ~> D# ~> D# j& k& j& j& k& w> x> x) K> N/ ", +"j& k& =< o' u! 5. ;+ ;+ *> ;+ ;+ j> j> =! j& j, j, > V+ :* !> ~e H_ b> N+ N+ v$ v$ v$ N+ N+ N+ ,+ v6+yF.X, 9w 1x.0B ;R w6+x6+[4+h5+y6+/@.g5+z6+NF.J5.^@.3[+3H.WM.3H.*L K5.J5.k8.d4.WM.u8.'3.A6+y_+#] B6+., C6+D6+E6+F6+G6+H6+I6+;, 9% 9% 9% 9% D D 9% 9% 9. r# ,@ K, _. _. }$ ,@ ,@ ,@ ,@ ,@ ,@ r# r# 9. 9% 9% 9% 9. 9. 9% D 9. 9. 9. 9. ,@ 7o a+ *3.d) J6+K6+A5+L6+L6+>/.,/.M6+N6+*6.`` `` `` x..x..x..Z` V#.X` X` 2Z xX xX vX vX u..v=.Um.O6+vq.P6+Q6+R6+S6+T6+U6+V6+T[+W6+X6+Y6+Z6+`6+B!+V~+ 7+.7++7+&|+@7+#7+C/+T++$7+IY.%7+&7+*7+=7+-7+;7+>7+,7+'7+)7+!7+~7+{7+]7+^7+/7+(7+_7+:7+<7+[7+}7+|7+17+27+37+47+[6+57+67+77+87+97+cM.07+a7+b7+c7+d7+e7+f7+g7+h7+i7+j7+k7+l7+n>.`]+s] #$ ( ( v b+ w b+ &$ 9. 9. 9. r# r# v ( w [ w w '@ r# 9. 9. 9% 9% u# 5. 5. ;+ L+ L+ L+ ;+ 5. m7+(V.n7+o7+p7+8y.q7+r7+s7+t7+u7+v7+w7+x7+p6+y7+z7+A7+^@.T9./R O` T9.CP O` *L T9.(R *L 68.'a.*a.WM.3H.*d.B7+C7+D7+E7+F7+h@ G7+y;+P/ H_ H_ O> O> =! 9^ H_ H_ Ic U> U> .> }> }> =! N/ ,] v! s! u! E) k& k& {+ k& ~> ~> }> ~> }> D# j& D# ~> D# j& ;> =! x) y> !~ ", +"j& {+ J} g/ ,] 5. 6. 5. !~ !~ f/ `) N/ N/ ;> }> }> }> ~> j, ~e ~e H_ F, N+ N+ v$ v$ v$ N+ N+ O+ H7+I7+J7+t= K7+r4 X:+Z! 2w.L7+M7+[4+N7+S]+c4.o..*a.WM.^@.3H.>/+WM.u8.*a.O` ]@.O` k8.S]+J5.u8.K1.O7+P7+R) M] Q7+R7+S7+T7+U7+V7+W7+X7+9. D 9% 9% 9% 9% 9% 9% 9. r# ,@ K, >@ >@ ,@ ,@ ,@ ,@ ,@ ,@ r# 9. 9. v v ( _ &$ 9% 9% D 9% 9. 9. r# ,@ ,@ ,@ ,@ i/ ,@ Y7+M-+$:.Z7+e4+`7+I}+,/. 1+J}+*6.d<.x..x..x..x..I8.|(.|(./0.X` X` 2Z xX v..W` u..v=.9p. 8+.8++8+@8+#8+[- $8+>'+v!+0)+/'+%8+t!+&8+*8+=8+-8+;8+*'+MW.>8+,8+'8+)8+!8+Jd.`s.~8+{8+]8+o4 H$+^8+/8+(8+_8+:8+<8+[8+}8+|8+18+28+Y<+38+48+58+68+78+88+98+08+a8+b8+c8+d8+e8+f8+g8+h8+i8+j8+k8+l8+m8+n8+o8+p8+q8+V +s t) >@ W_ ,@ r# ,@ 9. 9. 9. r# b+ v ( ( b+ r# r# r# r# ,@ '@ x x '@ w w ( 9% 9% 5. 5. 5. -+ #+ #+ : -+ 5. p` yb r8+s8+t8+u8+[P.v8+w8+IU.x8+y8+z8+A8+B8+C8+D8+M'+E8+T9.CP k#.O` ]@.T9./R T9.&L (R K5.O` K5.u8.WM.WM.5[+F8+G8+H8+I8+J8+_l Zc x_+~e Ic O> v$ j> c> 9^ ~e H_ H_ H_ Ic }> ~> ~> 5. 5. 6. j& j& j& k& k& k& j& ~> }> }> j, }> ~> j& j& ~> ~> j& j& D# }> .> x> ", +"k& +, ;: h/ N/ 5. ^ 5. N/ g/ v! `) N/ `) h> k& }> j, j, [* bi ~e ~e U; v$ v$ v$ v$ v$ O+ O+ N+ K8+L8+M8+8(.N8+O8+P8+7> xm.([+Q8+[4+R8+3[+k8.^@.c4.c4.c4.J5.S]+^@.K5.DS O` DS T9.]@.'a.'a.>/+S8+h%.T8+A; &/+;: D 2^ ib U8+$*+*e 9% 9% 9% 9. 9. 9% 9% 9% 9. r# ,@ >@ >@ >@ ,@ ,@ ,@ ,@ ,@ ,@ r# 9% 9% 9% ( ( v 9. 9% 9% 9% 9. 9. 9. r# r# r# ,@ }$ :. %$ r# A*+#g.b3.13+V8+13+13+q|. 1+W8+_3.. .d<.`` `` 5Z 5Z 5Z |(.I8.I8.V#.X` X` 2Z 1Z 1Z W` |Z |Z |d.X8+Y8+Z8+`8+ 9+.9++9+@9+#9+CL.$9+%9+&9+*9+=9+-9+;9+>9+.L ,9+'9+)9+!9+~9+{9+v&+]9+^9+~^+/9+(9+_9+:9+Uy.<9+[9+*t.}9+|9+19+29+39+49+59+69+79+89+99+09+a9+b9+c9+d9+e;+e9+f9+g9+h9+i9+j9+k9+l9+m9+n9+(/.&< S> M@ ,3 t) >@ ,@ r# 9. 9. 9. 9. r# b+ b+ ( ( &$ 9. r# r# ,@ ,@ a+ x x w w ( &$ 9% 9% 5. 5. 5. -+ #+ #+ #+ -+ 5. ^ Cf o9+p9+q9+r9+s9+t9+u9+v9+w9+x9+y9+z9+A9+B9+C9+D9+g5+lI.CP /Z /R *L &L *L O` /R ]@.68.K5.*a.WM.^@.E9+d4.PC.F9+G9+H9+I9+J8+^{ X, H_ ~e ~e U; z$ c> C> ~e ~e H_ H_ H_ =! D# += 5. ;+ )$ )$ D# D# D# j& j& j& }> j, }> j, ~> D# j& D# ~> ~> ~> k& j& }> .> C> ", +"{+ m& g/ n' -> 5. 5. 5. n' h/ ,] Z) y> y> j& w> .> .> [* L) ~e => ci = N+ N+ N+ v$ N+ -/ }. z$ J9+K9+L9+M9+N9+O9+7' K, AP M'+E|+M7+h5+3H.d4.J5.DS [a.P9+d4.d4.'a.(R ]@.K5.O` T9.T9.^@.WM.J|+Q9+R9+S9+C) mP T} T9+{i /{ U9+jo V9+P^ D D D 9% 9% 9% V$ V$ 9% b+ ,@ ,@ ,@ >@ ,@ >@ >@ >@ ,@ ,@ r# V$ V$ V$ _ v b+ v 9% D 9% 9. 9. r# r# b+ r# [ [ ,@ :. W9+&$ _ x%+ c.g4+e4+X9+|3+F(. 1+|3.>}.. .d<.d<.~3.5Z 5Z I8.x..x..I8.|(.|(.V#.M_.M_.!;.W` vX Kb.7p.ek.Y9+.s.Z9+`9+ 0+.0++0+@0+#0+$0+%0+&0+*0+=0+-0+;0+8I.@:+Ep.>0+,0+'0+)0+!0+~0+{0+q@ K*.]0+Ah.^0+^0+Ie.}o.NA.uQ./0+(0+_0+41+:0+<0+t_.sc.[0+W%+}0+|0+10+20+30+40+50+60+70+80+90+00+{5+a0+b0+c0+w5.t) d0+t) >@ >@ >@ ,@ r# r# 9. r# r# r# r# v ( v v 9. 9. v [ ,@ '@ w '@ x '@ w b+ 9% V$ ! 5. 5. -+ : z. < -+ 5. %$ E9.b~ e0+f0+g0+h0+i0+j0+k0+l0+m0+n0+o0+p0+q0+r0+h5+nF.J5.(R CP ]@.k8.K5.J5.K5.K5.WM.^@.u8.WM.J5.n}+s0+S]+t0+u0+v0+w0+x0+y0+b' e@ O> => => U; v$ z, -> O> bi Ic +> Ic .> D# D# 6. c> )$ ~> j& k& j& ;> ;> ;> }> j, j, }> }> ~> ~> ~> ~> ~> }> ~> ~> }> .> x> ", +"u! s! {+ j& D# D# )$ ~> ,] `) !~ }> ~> =! =! x) .> Ic H_ .} O/ -) g8 = x; x; -& N+ N+ z0+z _t A0+B0+C0+D0+E0+F0+R) (. ^k.G0+['.&!+NF.>/+J5.'t T]+ke.H0+WM.*a.*a.k8.]@.DS /Z (R DS J5.2[+3[+3H.I]+^@.8X.t).I0+1x.J0+K0+d~ 8> Q5 9. E E z. V$ < < { qS qS b+ 0+ i' F] |^ /_ xf yb x>+>@ ,@ v ( { V$ V$ 9% 9. 9% 9% 9% 9% 9. 9. ( ( ( ( ( ( x w w ( ] ( E}+L0+M0+N0+|3+O0+ 1+o|.o|.|3.W8+W_.. .$W $W x..x..x..x..x..x..x..x..x..I8.P0+P0+P0+T[.Um.xX xX T[.$)+Q0+R0+R0+ 0+^0+ 0+S0+T0+%r.U0+V0+V0+0g.cg.Q{.ro.W0+X0+Y0+Z0+`0+ a+Fv. y.Ah.|Z .a+|g.Ah.+a+k~ @a+#a+$a+%a+&a+*a+=a+-a+;a+>a+,a+'a+)a+!a+~a+{a+]a+^a+/a+(a+_a+:a+@ >@ >@ >@ ,@ ,@ r# r# r# ,@ ,@ r# r# r# 9. 9. 9. r# b+ w '@ a+ r# '@ a+ a+ a+ '@ ( { ! ! u# u# 9% 9% 9% 9% 9% 9% X, ~) |a+F +1a+2a+3a+4a+5a+6a+7a+8a+9a+0a+85+aa+ba+E|+c4.O` (R ]@./R /R K5.J5.*a.d4.d4.d4.J5.c4.4[+ca+14+da+ea+fa+ga+ha+ia+t) F* :. G# N+ N+ v$ v$ v$ b> b> [* j, j, .> }> }> <$ ~> ~> ~> j& j& C> ;> x> =! }> j, j, }> }> }> }> }> w> x> x) .> }> }> =! x) ", +"C> u! k& D# D# D# }> ~> N/ .! .! =! }> =! x) x) 9^ ~e ~e => => -) g8 = q; q; = O+ O+ )- |. ja+ka+la+ma+na+oa+pa+qa+ra+q%+sa+R8+E|+/@.>/+I|+J5.ta+ua+va+c4.*a.'a.c4.d4.'a.O` K5.c4.u8.WM.3H.*a.^@.WM.ho 6. ;R `s Zc 1x.1x.7/ wf D D D wa+xa+OJ.vo 7J 'B bw Tc Tc `K ya+}..c +kB @R x*+,@ r# v ( ( { 9% D D D 9% 9% 9% 9. 9. ( { ( ( ( ( w w ( ( ( _ _ .; Z,+za+E4.g4+T_+o|.o|.|3.|3.|3.=}.D;+D;+*6.`` `` `` `` `` `` `` `` x..I8.|(.|(./0.s>.X` X` X` bW.Aa+Aa+Ba+Ba+H=+Ba+Ca+Da+Ea+Fa+Ga+Ha+Ia+Ja+4Z s>.Ka+La+Ma+Na+Oa+Pa+Oa+Qa+U[.{(.}o.U[.Ra+Sa+Ta+Ua+Va+Wa+Xa+&a+Ya+Za+`a+ b+.b++b+@b+#b+$b+%b+&b+*b+=b+-b+;b+>b+,b+'b+)b+t) YF.fb S> U< G5 >@ >@ >@ >@ >@ ,@ r# r# ,@ ,@ ,@ ,@ r# r# 9. 9. 9. r# b+ w w [ 9. r# ,@ >@ >@ '@ ( { V$ 9% 9% 9% 9% 9. 9% 9% 9% V$ |^ a> fb X7+!b+~b+{b+]b+^b+/b+(b+_b+:b+/+}b+|b+1b+2b+3b+4b+5b+#_ 0^ 7~ 8, O+ O+ N+ v$ v$ v$ v$ `% M; j, }> }> }> }> }> D# D# D# ~> D# C> u! ;> ~> ~> }> }> ~> ~> ~> }> ~> s! C> x) =! }> }> U> x> ", +"s! s! D# D# ~> D# ~> ;> !~ .! .! i> }> }> .> =! U> ~e => => => F, g8 /; q; q; c% O+ Y% t 6b+7b+8b+9b+0b+ab+bb+Jw -N.cb+ql db+/@./@.L7+2[+^@.VB.eb+*d.le.c4.*a.J5.J5.68.J5.*d.pb.A=+J5.68.K5.d4.J|+fb+gb+,@ >t y;+Zc hb+hb+8> uz.Cc J9 n8.*n kB Yi.pX 9X.9X.ib+jb+;U kb+O9.lb+bX.I9.l$+7J ,@ ,@ v _ { V$ 9% D D D D D 9% 9. 9. ( b+ ( ( ( ( ( ( ( ( ( ( 9% 9. [K.s% mb+7g.. .W_.;}.;}.W_.W_.W_.=}.=}..1+*6.d<.d<.d<.1@.x..x..x..x..x..x..x..I8.I8.I8.I8.I8.I8.nb+nb+W_+ob+ob+ob+pb+qb+rb+D=+sb+}~.tb+3Z %0.%6.ub+vb+I8.m++wb+M{.xb+yb+D=+:(.s>. :++m.zb+Ab+Bb+Cb+Db+Eb+xH.Fb+Gb+Hb+Ib+Jb+Kb+Lb+Mb+Nb+Ob+Pb+Qb+Rb+Sb+Tb+Ub+Vb+ui ># [, Wb+}$ S> G5 G5 >@ >@ >@ >@ >@ ,@ b+ b+ [ ,@ ,@ ,@ ,@ ,@ r# _ 9% _ &$ ( ( 98 Xb+pu *e t) W_ '@ ( { V$ 9% 9% 9% 9% _ 9% D 9% V$ G~ $' -, &, |3 Yb+Zb+`b+ c+.c++c+@c+#c+95+q0+hM.@3+i5+^@.3H.d4.c4.I]+$c+z7+u,+O7+]@.68.d4.d4.S]+>/+%c+&c+*c+=c+-c+;c+>c+,c+'c+(. %, z, N+ N+ N+ N+ N+ v$ U; M; [* U> j, .> }> ~> D# }> ;> .> D# ;] H! s! ;> ~> }> ~> ~> ~> ~> =! ;> C> x> 9^ }> }> }> j, .> ", +"u! x> D# }> .> 9^ 9^ x) ;+ Z) ;+ }> }> j, }> }> }> >+ U; N+ N+ F, N+ O+ c% c% G# Y% -- '- )c+!c+~c+{c+]c+^c+/c+(c+%z._c+:c+D).W1+#3+Q9+^@.c4.*a.'a.c4.'a..4Z 4Z /0./0./0.6c+7c+8c+9c+0c+ac+zH.bc+cc+dc+ec+fc+gc+hc+ic+jc+kc+lc+mc+nc+oc+pc+qc+rc+sc+g@ x g@ Q) x a+ >@ M@ >@ >@ >@ >@ >@ >@ r# b+ b+ w [ ,@ ,@ ,@ ,@ 9. &$ { { ( ( ( Kz 3!.T9.|p.:Z LH /t z! _u.K/ G$ D D ) ) ! D D D wf M> 7! ~O.tc+uc+vc+wc+xc+yc+vL.G0+zc+85+Ac+Bc+#3+Cc+Dc+u,+WM.h5+Ec+Fc+aa+Gc+Hc+'t K5.O` WM.3H.J5.Ec+Ic+Jc+Kc+Lc+Mc+Nc+Oc+Pc+CB.J} w! N+ N+ N+ N+ N+ v$ U; ~e ~e H_ Ic 9^ 9^ ~> D# }> x> x> C> H! s! X! ;] x> Ic }> }> j, x) x) u! C> x) 9^ ;> }> }> }> [* ", +"x) .> ;> [* U> x> x) x> 5. ;+ )$ j, }> }> }> }> ~> j> v$ N+ N+ v$ N+ G# O+ G# q% -- % ~> Qc+Rc+Sc+Tc+Uc+Vc+Wc+Xc+Yc+Zc+`c+ d+W1+#3+R8+3[+'a.K5.(R ]@.ob..d+eb+.d+A=+J5.u8.+d+ta+je.@d+68.3H.u8.d4.I|+([+jB y> qy.y;+|c+y;+1{ l*+5B.^@.^@.c4.DS (R *L CP &L dY.oM N` K&.gl.:s.~@.Gm.0M z5.,@ ,@ 9. D D D 9% 9% 9% 9% 9. 9% D 9% 9% D 9. 9. { { ( ( w w w w ( ( ( w r# 9. J& -/ r- ))+Nm.4V.#d+_3.D;+D;+_3.W_.|3.|3.g<.17.. .17.. .. .. .D;+. .. .. .. .. .D;+D;+D;+D;+D;+`` $W $W `` `` `` b3.wa.w=.$d+x..x..x..x..x..I8.5Z I8.I8.I8.I8.I8.5Z 5Z %d+&d+*d+=d+-d+;d+>d+DK.,d+'d+)d+!d+~d+{d+]d+^d+/d+(d+_d+:d+@ >@ >@ >@ >@ ,@ b+ v ( ( b+ ,@ ,@ ,@ ,@ ,@ v { { ( ( ( eX O` 68.io.|d+1d+2d+3d+n&+!O.:a.4d+x#+K, / D 9% E D |^ 4- [X _Q.5d+6d+7d+8d+9d+0d+ad+ $.bd+cd+dd+Fc+W1+ed+n}+S]+S]+Q8+ba+r0+fd+Bc+gd+WM.k8.c4.J5.J5.^@.hd+id+jd+kd+ld+md+nd+od+pd+qd+rd+P=+y. v$ N+ N+ N+ v$ U; ~e ~e ~e Ic 9^ 9^ }> ~> =! x> C> C> ;] s! z) K~ u! H_ bi [* L) .> x) s! u! x) x> ;> }> }> j, L) ", +"u! x> =! L) [* x> x> u! d) 5. ;+ j> D# ~> }> D# ~> >+ v$ N+ N+ N+ N+ O+ % a& >- O+ 5. sd+td+ud+vd+wd+xd+yd+zd+Ad+Bd+Cd+Dd+Ed+{4+ba+R8+J|+J5.T9.O` K5.*a.Fd+ke.Gd+d4.mI.Hd+.d+le.le.*a.'a.K5.u8.3H.I5.(!.-g.d) _W.n{+_Q.98.C- p;+Id+0~+^@.k8.T9.T9.*L qM k#.pM ]Z Gb.|V.p`.:s.6X.<../J '@ ,@ ,@ r# 9% D 9% 9% 9% 9% 9% 9. 9% D D 9% 9% 9. 9% V$ { v w w x '@ x w ( ( w v D E; Jd+!i H) Kd+Ld+Md+Nd+I8.1@.BX _3.=}.17.. .17.G>+Od+Z5.|3.=}.=}.Pd+|3.|3.|3.|3.=}.W8+W8+W8+W8+.1+.1+.1+la.v$+D;+W_._3.-W . .t@+. .1@.x..x..x..`` f7.x..1@.1@.x..`` `` `` x..Qd+Rd+Sd+Td+Ud+Vd+Wd+Xd+Yd+Zd+`d+ e+.e++e+@e+#e+$e+%e+&e+*e+w =e+'@ x x y f@ f@ f@ >@ >@ >@ K, K, a+ a+ '@ 9. &$ ( ( b+ ,@ ,@ ,@ ,@ ,@ [ ( ( v ( ( ~t.&L (R ob.gd+-e+;e+>e+,e+ 2+'e+)e+q}+!e+~e+el.V5 98 W_ `5 a> ,E.Ts K, {e+]e+^e+/e+(e+_e+q0+:e+/+3e+4e+5e+6e+7e+8e+9e+0e+ae+be+ce+JS de+z, N+ N+ N+ v$ F, !> ~e ~e Ic 9^ 9^ j, V+ i> -> u! ;] X! ;] {) H! C> Ic V; L) [* U> x> s! ;] C> C> ;> ~> D# [* [* ", +"~> =! 9^ H_ 9^ H! u! C> C> C> -> j, D# }> }> }> }> >+ v$ N+ N+ N+ O+ % a& Y% [* c> F# ;]+ee+fe+ge+he+ie+je+ke+le+me+ne+oe+pe+Q8+Q8+qe+4[+d4.u8.d4.K5.68.S]+u8.u_+Cc+i5+/@.A=+'a.S]+d4.T9.*a.3H.J|+re+se+te+ue+ve+we+xe+j& }7 eo.S]+ta+%d.'t k8.T9./R O` T9./R CP ,%.k..oM '%.ye+]D @! a+ >@ ,@ r# 9% 9% 9% 9% 9% 9% 9% 9. 9% 9% 9% 9% 9. 9. 9% V$ V$ 9. b+ r# r# ,@ ,@ [ ( { { #$ >+ v$ y. y. z$ W$ ze+Ae+Be+yw.Ce+~3.~3.~3.x..`` _3.|3.|3.|3.|3.|3.o|.o|.De+t(+L,+Ee+o|.o|.k~+k~+k~+::.::.::.g4+g4+g4+::.::.m[+Fe+ 1+ 1+J}+J}+N6+N6+J}+N6+N6+N6+N6+.1+17.17.b3. .%6.Ge+la. .He+Ie+Je+Ke+Le+Me+Ne+Oe+Pe+Qe+Re+Se+Te+ox.!~ -+ =+ $$ :. ,@ [ ,@ >@ >@ >@ >@ >@ K, _. _. (. /. '@ w [ [ r# 9. r# ,@ ,@ ,@ >@ ,@ [ [ [ ( ( 0+ j..7W.68.'3.14+4[+4[+D_+Ue+Ve+We+Xe+Ve+Ye+Ze+'e+vL.p}+Ek.p{ )] dz `e+ f+.f++f+@f+#f+p6+$f+['.%f+D9+~K.u8.(@.2(+i5+W1+&f+p6+B9+ed+#3+*f+=f+Gc+-f+y6+2[+u,+;f+q}+>f+,f+'f+)f+!f+~f+{f+]f+^f+/f+h; z$ v$ O+ N+ v$ F, H_ Ic ~e H_ Ic D> ;+ i> i> -> C> {) @~ H! (f+[J K> Z~ t; N+ v$ [* ~> D# ;> x> x) =! D# ~> j, ~> ", +"~> ~> x> x) u! {) ;] C> x) x) 9^ U> j, }> j, j, [* %& v$ O+ N+ G# Y% (l e~ V$ _f+g> hu :f+/+S]+9f+[e+*f+0f+o..c4.'a.J5.K5.O` 68.14+>/+*f+af+bf+cf+df+ef+ff+gf+hf+|d++d+ua+pb.k8.(R O` O` ]@.O` T9.T9.>U.E,+E,+oh.Wr.z! 0+ >@ ,@ r# 9. 9% 9% 9% 9% 9% 9% 9. 9. 9. 9. 9% 9% 9. 9% 9% 9% 9. 9. r# ,@ ,@ r# 9. { { ( 7. v$ v$ z$ z$ >+ -+ D D 03 wa+if+jf+}u.kf+Nd+v@+1@.t@+BX BX . .W_.|3.-}.,}.,}.,}.,}. 2.o|.q|.q|.::.::.q|.::.::.q|.q|.q|.::.lf+::.g4+g4+ 1+ 1+ 1+ 1+ 1+ 1+f|.mf+f|.f|.g<.g<.=W E4. .`` `` .nf+of+pf+qf+rf+sf+tf+uf+vf+K4+&e+aB .; wf+!~ ;+ ;+ ;+ 6. r# ,@ ,@ >@ >@ >@ >@ >@ K, _. _. (. /. x w v r# >@ ,@ r# ,@ >@ >@ >@ ,@ ,@ ,@ '@ w ( '@ ql z=+'a.c4.3[+xf+yf+yf+zf+Af+Ve+Bf+Cf+Ye+Df+Ze+Ef+Ff+Gf+y, s 9w 3X.Hf+If+Jf+Kf+Lf+85+0a+Mf+['.y6+*a.3H.I|+M7+ed+ed+p0+C8+95+y7+Gc+#3+*f+Nf+Fc+A>.Of+Pf+Qf+Rf+Sf+Tf+Uf+Vf+Wf+Xf+Yf+Zf+`f+ g+dz N+ N+ v$ v$ v$ v$ H_ H_ H_ H_ Ic D> ;+ 5. ;+ .> x> u! s! u! u! ^, `) .! t; N+ N+ %& j, D# =! x) x) .> }> ~> D# k& ", +"D# k& C> 9^ ;] M] H! C> Ic Ic Ic j, j, }> j, j, > z$ z$ N+ |. Y% Y% .g+0I.n>.Q) R^ +g+@g+#g+$g+%g+&g+*g+=g+r' -g+;g+>g+,g+'g+I|+/@.)g+u8.NF.K5.!g+68.K5.c4.WM.2[+~g+@3+{g+K5.K5.k8.J5.^@.c4.K5.^@.]g+^g+/g+(g+_g+:g+@ ,@ r# r# 9. 9% 9% 9% 9% 9% 9. _ _ _ 9% V$ 9% D D 9% 9% 9. r# r# 9. 9. &$ { ( { F N+ v$ z$ >+ >+ ;+ 5. 5. _ F# 1g+i/ t] x%+2g+2Z #)+v@+v@+Q1.1@.t@+. .17.Z5.V_.+ .3g+s4.4g+_:.5g+Q<.Q<.!}.6g+!}.(:.(:.!}._:.q|.q|.q|.q|.k~+k~+k~+k~+g4+g4+f|.f|.J}+J}+}7.}7.*6.f7.`` 1@.1@.7g+>r.8g+9g+0g+ag+bg+cg+dg+y) c- $+ !~ ! eg+N/ 5. 5. 5. -+ 9% r# r# ,@ ,@ ,@ >@ >@ ,@ }$ }$ }$ [ x w r# r# ,@ ,@ ,@ K, K, K, K, ,@ [ '@ x x w b+ vb fg+68.c4.c4.u,+MG.gg+hg+Bf+ig+jg+We+kg+Ye+kg+lg+mg+ng+}8 U| g}+Zc og+pg+qg+rg+sg+A8+a~+/@.tg+jo.^@.3H.S]+h5+[b+@3+ug+$f+H).vg+Gc+[b+wg+|e+Fc+xg+yg+zg+C=.qe+Ag+Bg+Cg+Dg+Eg+Fg+Gg+Hg+Ig+Jg+aV.^ Y% O+ N+ N+ N+ => ~e !> Ic Ic D> Z) c> i> 9^ .> C> u! x> x> K> `) f/ z$ N+ N+ `% L) j> w> ,~ [! Kg+[) j, b! Au ", +"D# D# x) x) u! [J ,] y> 9^ Ic 9^ U> Ic +> +> [* [* z$ O+ % $ -a J& xz U< n=.o! f@ Lg+Mg+Ng+Og+Pg+Qg+Rg+Sg+Tg+Ug+Vg+Wg+Xg+Yg+Zg+!e+k5+o..tg+`g+je.P9+0~+ h+*f+.h+je.&d..d+'t ke.ua+3H.3[+S]+68.c4.J5.~K.+h+.0.@h+#h+$h+%h+&h+*h+0a.ke.eb+!g+]@.T9.T9.O` (R DS J5.c4.DS (R *L ,t.jo xz }$ }$ }$ }$ r# r# 9% 9% 9% 9% 9% 9. _ { { { { _ { 9% D D 9% 9% 9% ( ( _ { ( 8. }. v$ z$ >+ ;+ ;+ 7. { _ ( w /. /. x 0+ 0+ =h+-h+;h+>h+y).bW.ob+,h+%d+,h+W8+'h+j<.)h+7Z p|.,}.s4.!h+f~.~h+{h+{h+A<.<:.]h+F^.^h+4g++ .,2.-}.`5.Z5.Z5./h+|3._3.BX J}+.1+f7.(h+_h+d<.:h++ Z) `) `) f/ ;+ 5. ;+ -+ 5. 5. ,@ >@ ,@ ,@ >@ ,@ ,@ ,@ r# r# ,@ [ ,@ ,@ ,@ >@ >@ K, _. _. _. v ( x a+ a+ a+ b+ 7J Y1+K5.k8.'a.^@.4[+MG.4h+5h+ig+6h+7h+g0+kg+8h+9h+0h+ah+bh+ch+ch+r4 AR.dh+eh+fh+gh+So.0a+A7+7E.T9.'a.'a.*a.+h+#3+*f+Fc+Fc+i5+Fc+Nf+-f+Nf+ba+-f+hh+ih+jh+kh+B9+lh+mh+nh+oh+ph+qh+rh+sh+th+uh+vh+Jf u ,- G# N+ N+ U; U; N+ O> Ic Ic 9^ y> x) }> ~> D# ;> =! =! 5. 5. 6. 5. v$ N+ N+ `% j> =! ;> Kg+wh+wh+xh+Kg+ii ", +"~> }> .> C> u! N/ N/ N/ y) 9^ Ic H_ ~e ~e L) L) M; O+ Y% % e~ D t) t, P[ U< p! U< B3+yh+zh+Ah+Bh+Ch+Dh+Eh+r' Fh+mD.Gh+Hh+Ih+Jh+TT.Kh+@O.[4+2(+ke.&d.9f+W1+*f+@3+%d.Lh+le.je.je.ta+3H.3[+>/+k8.DS n..0f+@3+Mh+Nh+Oh+Ph+Qh+Rh+Sh+Th+ta+ta+pF.]@.T9.]@.]@.k8.K5.*a.k8.k8.k8.(R >t.ol .( }$ }$ _. }$ }$ ,@ 9. 9% 9% 9% 9% 9. _ { { { ( ( ( D D E E D &$ &$ { { { < K@ @ v$ z$ ;+ 5. 5. ! { ( w x y y L@ i) z; ,U.gb G' WK Uh+Vh+Wh+N>+Xh+X` v@+y..W_.G>+Yh+z..z..$6.$6.B8.47.Zh+`h+ i+ i+.i+Zh++i+7}.47.47.@i+7Z $6.$6.+}.c3.$W |(.d<.#i+v@+#)+,o.$i+%i+&i+*i+=i+-i+,U.:. 6. 5. v$ % % G# N+ z$ H) !~ `) ,] 5. ;+ ;+ ;+ ;+ ;+ 9% ,@ >@ >@ >@ ,@ r# r# 9. r# ,@ ,@ ,@ >@ >@ >@ >@ K, _. _. _. v w w '@ >@ >@ r# Y6.3(+K5.68.'a.d4.u8.`1+s*.;i+>i+,i+Ze+Ze+Ze+'i+)i+)i+!i+;)+~i+r4 r4 %D t).{i+)e+85+H).q0+;e+J5.>/+WM.*a.^@.2(+i5+#3+Nf+Ac+Fc+V1+|e+]i+^i+Fc+Gc+/i+ih+85+'].(i+_i+:i+ ~> ~> }> ~> ~> ;+ 5. 5. ;+ z$ O+ O+ N+ >+ j> j> j, V+ )$ )$ i> j& ", +"j, j, Ic x> =< .! .! K> x) 9^ H_ ~e ~e => L) M; )- % % }. u# ,3 F] t, A9 A9 A9 7i+r! 8i+9i+0i+ai+bi+ci+di+ei+9} fi+gi+hi+ii+ji+ca+V5.@O.D9+ki+T]+[c+li+mi+@3+i5+.d+ob.ni+je.%d.eb+d4.^@.*a.DS *L FI.!/+g5+oi+pi+qi+ri+si+ti+ui+iR.&d.0~+'a.]@./R O` K5.68.68.d4.*a.J5.68.DS f( m>..( ,@ }$ }$ }$ r# r# 9% 9% 9% 9% 9% 9% 9% V$ { { _ { _ D E E -+ D D < ! { { #+ K@ v$ z$ >+ ;+ 5. 5. _ w x x y y i) b) 5) Gx ]<.-(.vi+2L.)3 *e i' wi+xi+yi+g4.zi+T[.v..xX M_.M_.5Z Ai+$d+W#.Bi+Ci+7}.h~.}3.Di+Ei+h<.Fi+h<.h<.,0.i<.ia.Gi+Hi+7<.Ii+}u.V,+Ji+Ki+Li+Mi+Ni+Oi+,1 Cc ,3 s& fb 9. t] t@ G# Y% % O+ N+ v$ -) N/ ,] ^ ;+ ;+ L+ L+ L+ : D b+ ,@ ,@ ,@ r# 9. 9. [ r# '@ ,@ ,@ ,@ a+ a+ ,@ }$ }$ }$ [ w w [ ,@ 0+ '@ ,;.]g.68.k8.*a.u8.u8.u8.xf+Pi+Qi+Ri+Si+Ef+Ze+Ti+)i+Ui+Vi+Wi+Xi+Lg 1x.bB l- W}.Yi+Zi+Fc+Ac+(@.NF.3H.J5.S]+*a.tg+#3+Fc+wg+ed+Mh+Fc+ed+|e+`i+Nf+wg+W1+ j+j+,j+'j+]3 .( ;+ G# Y% G# N+ U; bi H_ H_ Ic Ic Ic j, }> ~> }> }> V+ V+ ;+ i> }> %& N+ N+ N+ >+ K> x> )j+}c+!j+y> y> ~j+", +"L) j, O> !~ N/ [* j, ~> x) Ic ~e b> b> U; N+ G# % qP u [ h' P[ P[ P[ Gc WK WK n>.n>.7% f= {j+]j+^j+/j+(j+_j+:j++ ;+ 7. { _ { w y y L@ i) b) 3) 3) 3) p) j! 5~ )] 3) 3) 5~ 5) 5) b) i' U< dj+ej+fj+Aw.ux.gj+=j.|u.4p.qv.Md+hj+x`.x5+ij+jj+kj+lj+mj+mj+Kw.;z.nj+rD.oj+pj+Sn.qj+/_ [^ 88 l- K{ }; @+ ^. w- F] f@ [ z- 6. V+ u# 6. O+ O+ O+ N+ F, Z) 6. ^ 5. ;+ : #+ . #+ 8. 8. 7. ^ %$ r# 9% _ w b+ w w w w w w w b+ r# }$ w w w ( w x w W> yH z=+'a.^@.u8.S]+>/+e(+rj+sj+tj+uj+vj+wj+xj+Ti+yj+zj+:X g^ Aj+3X.s& ES /i+ba+i5+@3+3~+5j+ed+ba+~K.c4.:w.Bj+h5+h5+*f+wg+ed+Fc+[b+#3+Ac+Ac+-f+Gc+Gc+-f+/i+Cj+7- Dj+Ej+Fj+Gj+Hj+Ij+Jj+Kj+Lj+Mj+Nj+Oj+_. %$ 9. # v; 4; O/ ~e H_ H_ H_ Ic U> j, }> j, }> }> j, :* :* }> V+ %& N+ N+ N+ >+ =! ~> }J }c+n' )$ ~> Pj+", +"L) [* g8 -) !~ j, j, j, +> ~e ~e >+ v$ O+ % % O+ u w i) A9 P[ P[ +! Gc j/ j/ n>.:G (. '3 VC Qj+Rj+Sj+Tj+Uj+Vj+Wj+Xj+Yj+Zj+`j+ k+.k+#3+ed+@3+~g+=f+zR.%d.+k++d+@k+#k+$k+k8.K5.'a.u8.S]+d4.u8.*a./R DS 68.%k+&k+*k+=k+-k+;k+|[+#<+WM.d4.*a.68.]@.k8.c4.68.^@.WM.d4.S]+3H.J5.xm.r# ,@ ,@ r# r# r# r# 9. 9% 9% 9. 9% 9% 9% 9% 9% 9. 9% D E r- E; J& E -+ 5. 5. 5. 5. 5. 5. v$ z$ >+ ;+ 5. ! { ( ( x i) 5~ 3) !! !! ~! #! #! ~! ~! ~! ~! !! 3) 5~ 5~ 5~ 5~ 5~ b) p! y '@ }, '$ R$ >k+,k+,k+'k+'k+)k+!k+~k+~k+{k+]k+^k+/k+/k+(k+x>+J y8 }; %' }; +0 +0 Bz.H+ H+ H+ q# 88 88 =7 F] >@ %$ 5. 5. h; r& !- 6. O+ G# O+ N+ >+ 5. ^ 5. L+ #+ 8. 8. 8. 8. K@ v$ z$ 5. %$ 9. 9% v w w w w w w w b+ b+ r# ,@ [ w w ( ( ( { &$ 'B _k+u8.>/+2[+J|+n}+:k+/+^@.WM.E|+Q8+/@.h5+wg+[b+-f+/i+wg+r0+]i+7k+8k+9k+0k+]i+ak+A* E#.O- bk+ck+dk+ek+fk+gk+hk+ik+jk+kk+ZS.s] >@ 9. N+ Y% L& => Ic +> Ic H_ Ic j, j, j, }> ~> ~> j, j, j, j, v$ N+ v$ b> >+ V+ ~> *@ ^+ ^+ ^+ i& O=+", +"M; U; O/ Z~ .! j, j, j, ~e => H_ v$ # Y% G# N+ ;+ i) 3) 5) A9 P[ P[ m>.]'.A9 j/ WK :G R^ a^ E- iQ.lk+mk+nk+ok+pk+qk+rk+sk+tk+uk+vk+~g+W1+@3+.h+3~+=f+gd+Lh+0~+&d.wk+U@ rb._P.@d+DS k8.*a.c4.DS O` T9.DS ]@.K5.ob.ct.xk+yk+zk+Ak+Th+J5.u8.WM.68.]@.'a.J5.d4.^@.WM.u8.u8.cL.u8.4d+,@ ,@ r# r# r# r# ,@ r# 9% 9% 9. 9% D D 9% 9% 9% 9% J& E; E; J& D D -+ 5. 7. ;+ 7. 5. T- 5. z$ >+ ;+ ;+ ! { ( y t' 3) !! A! f! f! f! 2{ 3{ 3{ ~] Bk+f! #! k! 3) 3) 3) 5~ b) L@ /. h@ <. :. i/ :. :. :. :. `> Ck+3, !; A* #- #- T* *K.F> 9; }; M= M> wf Bz..e .e Dk++0 Bz.}2 H+ }2 q# @+ @+ f) F] >@ %$ *+ 8, =* V> l* !- z$ O+ G# N+ z$ ;+ *> 6. ;+ 8. 8. [. + K@ u @ v$ v$ j@ D 9% ( w w w b+ w w w b+ 9. 9. r# b+ ( ( ( &$ { < qS Ek+Fk+u8.Gk+S]+I|+u,+Hk+Ik+EN.Jk+|k+tj+Kk+Lk+)i+Mk+Nk+Wr.8, `! X_ p`.0k+Ok+Pk+$f+i5+K( Bj+K( 3~+Qk+14+A7+g5+/@.&!+#3+#3+Ac+/i+]i+wg+Mh+Rk+wg+Sk+Tk+Tk+Uk+Vk+O~ Wk+Xk+Yk+Zk+`k+ l+.l++l+@l+#l+$l+%l+&l+f@ ,@ ,@ ,@ J& Q- q; V; +> j, j, U> =! }> }> ~> ~> }> j, j, j, j, b> N+ v$ [* }> }> ~> *@ n@+^+ ^+ t@ |a.", +"-& -& b> i> j> [* j, L) U; U; v$ # |. e~ D, -+ w# 4) Ip +! I' Ip P[ +! +! P[ A9 A9 O[ xz ,@ }$ ,1 ^: *l+=l+^- -l+;l+>l+,l+'l+)l+!l+#3+~l+{l+Gk+G|+J|+u8.d4.3[+>/+0(+>7.DS =H.&d.]l+'a.WM.WM.]@.c4.68.k8.DS ]@.^l+/l+(l+_l+:l+ L) z$ >+ z$ y. 7' E- 1- W} !_ L' f! 3{ 0{ @g.|l+Qg |l+Qg >1 3{ A! k! k! 3) 5~ 5) i) g@ ># h@ h@ a+ [ [ '@ '@ w Z6.{ n! V> 4- a> #- M= T* 9; H+ H+ q# H+ H+ Y9 +0 p4 p4 +0 p4 p4 }2 q# q# 88 O} U} ,@ >@ W} |^ 88 @+ }2 wf 2( ^ N+ G# N+ v$ z$ z, v! i/ 1l+1l+y. v$ @ @ @ v$ v$ v$ v$ j@ 9. r# r# v r# [ ,@ [ v 9% 9% 9. r# 9. 9% 9% 9. 9% D D >@ |% ke.Gd+2l+Zi+3l+ba+Fc+4l+3l+5l+|k+6l+sj+7l+8l+8a+sZ.1- BR.3^ 9l+ed+ed+Mh+*f+|4+3~+I]+WM.E8+Ik+[b+1e+ba+#3+*f+ed+~g+Fc+ad+B8+jh+95+vg+y7+0k+0l+al+bl+cl+ZY iQ.:! dl+el+fl+gl+hl+il+jl+kl+ll+ml+nl+m, :G n>.G5 G5 qE |) L) }> ~> ~> D# D# -> .! =! ~> }> ~> D# }> }> [* `% L) [* }> ~> ~> 0* Uh+2~ ^+ t@ ol+", +"N+ -& M; j, [* L) L) `% N+ N+ $ a3 e~ r- 9. U} <3 +! Ip Ip Ip Ip Ip Ip Ip P[ A9 A9 U< >@ ,@ ,@ i' ]3 )t.LS.t) pl+ql+rl+sl+tl+ul+vl+wl+xl+T]+iR.2[+2[+^@.*a.k8.c4.k8.k#.yl+zl+=H.Fd+K5.]@.68.DS (R k8.DS (R (R va+Al+Bl+Cl+Dl+/+u8.3H.J|+>/+k5+o..El+Fl+,@ ,@ ,@ ,@ ,@ ,@ r# 9. 9% 9% 9. 9% D D j@ N+ O+ O+ N+ v$ z$ >+ ;+ 5. 5. 6. /V.=+ /V.I= $~ }> M; N+ v$ v$ 5. f; -$ -@.Gl+Bz.1{ f! ;/ SK ~3 !3 !3 Hl+df Qg ~] f! A! ~! !! 3) 5) L@ g@ ># ># ># g@ x x x x w ( ( y s C& !; 4- a> M= H+ }2 H+ H+ H+ H+ }2 p4 +0 Bz.+0 ;y Dz ;y Bz.}2 |^ F] ,@ a~ 7% 88 @+ }2 ;y Gl+Il+Dz V> z$ O+ N+ N+ N+ g8 g/ #] ~' 1l+Jl+N+ v$ v$ v$ v$ v$ N+ N+ F D r# r# r# ,@ ,@ ,@ r# 9. 9% 9% 9. r# 9. 9% 9% 9. 9% 9% 9. 9. 9++T]++k+Kl+Ac+[b+*f+ed+Fc+Ll+Ml+Nl+|k+Ol+}k+Pl+Ql+)E.b~ ;R o%+Rl+*f+#3+ed+@3+W1+W1+9f+^@.u8.~l+W1+jh+V1+ba+W1+=f+i5+Mh+.n>.WK WK j/ WK K/ G$ ~> ~> ~> D# j& i> Z~ .> }> }> ~> }> }> }> V+ `% b> [* j, }> ~> 0* *m+Kd+t@ x$+oY.", +"N+ = x; M; L) L) L) x; G# Y% % D, r- YN F] a~ a~ s) +! +! Ip Ip P[ A9 A9 A9 A9 A9 U< 0+ ,@ ,@ w gb =m+E$ -m+;m+>m+,m+'m+)m+!m+~m+{m+ua++d+Gd+J|+2[+S]+68.c4.(R (R ]@.O` !g+&d.eb+k8.*a.J5.68.WM.K5.68.'a.68.S9.Bj+]m+^m+;k.CS./m+'a.'a.k8.DS S]+3[+14+^@.3H.3[+3[++3+Qk+I&.OJ.r# r# r# ,@ ,@ ,@ r# 9. D 9% 9% 9% -+ -+ -+ V+ >+ z$ >+ ;+ >+ z$ >+ 5. 5. u# u# 7. :. ae $~ }> -& N+ N+ v$ C& B> ;' N' (m+_m++Z z%+V} %| :m+!3 a4 Hl+df E5 ;/ 3{ 1^ ~! !! k! 5~ i) y ># ># ># g@ x w ( ( ( ( { w k* r& V> 4- a> M= H+ H+ H+ H+ H+ H+ }2 +0 }2 Bz.Bz.p4 +0 p4 Dz 88 a~ F] f) @+ @+ @+ q# ;y => )) |m+~a 1l+Jl+i@ N+ N+ N+ v$ v$ v$ v$ v$ F J& 9. ,@ >@ >@ >@ r# 9. 9% 9. '@ r# 9. 9% D 9% 9. 9. r# 9. 1m+2m+.n>.WK WK WK j/ 9~ K, ~> j, j, )$ ~> j, O> j, }> ~> }> j, }> ~> }> b> b> [* [* .> ~> *@ lm+mm+93 x&+q8.", +"N+ = = = N+ v$ N+ L& a; -a D, pS y` z! s) 8~ 8~ J' -e 4) s) +! Ip P[ A9 U< WK WK WK 0+ ,@ ,@ r# [ l* K- O- K, i/ nm+om+pm+qm+rm+sm+`1+]l+J|+tm+[c+T]+u8.J5.k8.^@.K5.vZ.*a.Hd+VB.c4.c4.k8.]@.K5.]@.K5.(R k8.]g+*a.um+tg+t{+,7.vm+Fd+wm+v8.*a.WM.4[+$c+3[+A7+R8+L7+h5+xm+q$+9. %$ 9. 9. 9. r# r# 9. 9. 9% 6. -+ j@ z$ F ;+ n' ;> ;> *> *> !~ z$ z$ ;+ : < ! =+ -, 1( b. }> M; O+ O+ T- d* 4e :d.>| ym+>| %/+zm+1 >1 ;/ 3{ 1^ #! k! 5~ 5) i) g@ (. _. (. x w w w ( { { { w h; !- 8, C& a> M= M= M= M= }; }; T* 9; wf B- }2 Bz.}2 @+ q# q# =7 =7 q# +0 ;y }2 p4 aE Cm+bJ.Dm+>| q +*, -' / O+ O+ )- = ;] Em+/( Fm+{'.z$ N+ N+ N+ v$ v$ v$ v$ N+ N+ N+ v$ u# >@ >@ ,@ ,@ r# [ w x b+ ( ! ! _ ( &$ 9. 9. v5 Gm+Qk+4l+ba+[b+V1+5m++Zi+Lm+=f+3~+|4+K( i5+ q.G|+y6+['.[b+r0+q0+i5+Ac+ed+W1+*f+ba+1e+#3+Mm+aa+y7+`i+hh+Nm+Om+Pm+q# Qm+K] a> Rm+Sm+Tm+Um+Vm+Wm+Xm+Ym+.6 W_ 0+ z5.M@ m, P[ Ip A9 A9 0+ <$ }> }> }> }> L) b> j, }> }> }> j, j, ~> }> %& %& [* [* i> ~> ~> Zm+w#+93 ^3 3s.", +"N+ N+ N+ N+ N+ O+ % $ e~ D, W$ .( Ip Ip Ip f~ f~ J' <3 a~ s) Ip Ip Ip P[ U< n>.n>.n>.z5.>@ ,@ r# r# t) q`.@1 2X.zP h@ `m+ n+.n++n+%f+S]+J5.d4.T]+%d.*d./R K5.'a.&L CP CP *L T9.k8.]@.(R (R CP *L d4.k8.'a.'a.68.'a.K5.@n+#n+e8.$n+%n+9(+{n.d4.^@.2[+J|+3H.+3+y6+['.Q8+xm+'a 9. %$ 9. 9. 9. 9. 9. 9. 9. 9. 5. >+ z$ v$ v$ b> n' s! n' ,] ,] ,] >+ v$ z$ 8. #+ < [' :e 3+ I! }> L) L& z$ !; q`.[m+:d.&n+*n+ a> #- M= M= T* T* M= M= B- M> wf H+ q# 7% O} f) }2 Bz.;y p4 ;y p4 Gl+[m+ym+q +&n+-n+%/+_m+Gl+=7 O+ % )- = s! kf `^ h/ 1l+v$ v$ v$ v$ v$ v$ v$ v$ N+ O+ O+ G# G# F / ,@ ,@ r# w ( w w ( { { ( ( &$ 9. 9% @e ;n+$c+Mh+Mh+ba+>n+EN.2 =] {n+]n+^n+/n+(n+_n+:n+@ t) ,3 P[ Ip A9 'w t, s% <$ ~> ~> V+ [* > j, j, }> }> j, j, ~> }> [* [* L) L) .> ~> ~> w#+w#+93 ^3 }n+", +"v$ v$ N+ O+ G# % % G# E; E m, +! Ip Ip P[ p) r) 4) a~ <3 z! Ip P[ P[ P[ A9 O[ n>.n>.z5.>@ ,@ r# r# r# t) @+ g^ |n+b4+># R) 1n+2n+3n+u,+u8.A=+&d.je.4n+c4.^@.*a.T9.*L /R DS K5.DS 68.]@.&L (R K5.K5.K5.^@.d4.J5.^@.k8.Bj+Rf+K5.5n+Cf %9+2d.kI.3H.u8.u8.u8.O7+([+R8+y6+,7.Lp v 9. 9. 9. 9. 9. 9. r# %$ -+ z$ v$ z$ v$ N+ N+ D> ;] o' [J [J [J f/ v$ v$ ' < i' l; p, 1( n] j, M; L& *+ f; }m+%/+&n+(< 6n+7n+q +q +>| 8n+Gl+Bz.W! A! !! k! !! k! 3) 5) b) i) L@ y g@ fb :. :. w ( ( { { { { { w T- h; !- 8, 8> !; #- #- M= }; }; T* M= T* B- }; f) =7 7% @+ Bz.+0 Q5 aE ;y ;y Q5 mo (m+>| 7n+-n+-n+>| (m+aE 88 5. Y% L& x; 9n+kf 0n+~( ~' z$ N+ N+ N+ N+ N+ N+ O+ N+ O+ G# % %> t; `) z- r# r# w ( w ( _ { { &$ &$ 9% 9% D A5 S ~4+*f+ba+Q9+Jm+4m+_e+5m+Fc+[b+{4+@3+~g+W1+an+_a.:X 1e+bn+.k+W1+j5+{4+ q. q.@3+[b+@O.L7+ed+[b+{4+Fc+*f+3~+*f+*f+cn+W1+r0+Ac+ed+`i+`i+`i+r0+ba+,O ,3 ({ %Z |(+E5 S_ dn+en+fn+gn+hn+>@ ,@ ,@ >@ t) >@ *e P[ P[ P[ P[ Ip z! ,@ e& ~> )$ }> :* :* }> }> }> j, }> ~> }> [* L) M; L) .> ~> ~> Zm+w#+t@ in+}n+", +"L) > v$ G# % # D, z rE y! p) I' 3) 3) J' 6~ 6~ F-+W} <3 G' P[ P[ A9 P[ A9 j/ A9 U< z5.>@ >@ ,@ ,@ ,@ ,@ Lc 7% !- jn+Gp 't.*3.7X.kn+}'.2e+-e+c4.um+0~+Lm+ln+z6+]@./R 68./R *L K5.68.O` /R T9.O` K5.*a.]@.*a.'a.'a.k8.tg+[4+(@.mn+O#.s M> nn+on+pn+3H.6[+qn+([+R9+/@.p`..( v 9. 9. 9% 9% 9. %$ / j@ $+ V+ %& [* :* L) -& F, +, ., M] s! o' :. ;+ z$ 7. 5) t~ I] J5 2 4 [* x; M+ |^ I& -n+rn+sn+tn+6n+6n+6n+ V> a> #- M= }; H+ 9; wf F> wf }; 8> 2( -' ;y ;y Q5 aE Q5 Q5 aE p8 [m+bJ.ym+7n+*n+>| Cm+Cm+aE Bz.C& >+ p- V; x> (: un+vn+ZY ) K@ Q+ O+ G# G# G# % G# O+ % Y% 83 i> k& T- K, ^ [ r# b+ 9% ! D V$ &$ &$ 9% D D 9% XN O:.['.wn+xn+yn+zn+An+Bn+z7+Cn+Z:+Dn+h5+En+W}.e%+Fn+Gn+]m+Hn+~g+*f+ed+W1+*f+y6+~g+@O.[b+Fc+@O.ed+#3+ q.{4+{4+~g+|4+wg+7k+In+|e+wg+|e+Jn+Fc+}'.DA.~a hb+Kn+Ln+`6.2( S| Mn+Nn+On+z5.x ,@ ,@ ,@ >@ >@ _u.P[ P[ P[ P[ +! f~ z! ,3 ,3 [' <$ V+ %& j, }> }> }> ~> }> j, [* L) L) L) .> ~> ~> w#+n@+t@ in+2s.", +"[* [* N+ G# O+ D, r- qE A9 f~ g~ r) k! 3) <3 a~ <3 <3 W} <3 z! P[ P[ A9 A9 A9 A9 A9 U< >@ >@ >@ >@ >@ >@ ,@ r# a~ ~* +2 +2 '{ l}+0^ Pn+Fc+#3+R8+*a.'a.'a.|4+j5+Qn+DS (R CP qM (R CP *L ]@.K5.k8.DS *a.T9.]@.*a.'a.3H.>/+ q.#3+/i+4[+Cf :. g/ 8, E- Cf Rn+Sn+Tn+n}+%f+Un+*n Cr ( 9. 9. 9% 9% 9% D v$ N+ v$ [* [* [* }> j, M; N+ >+ !- K~ H! o' T- 6. >+ [' lP Vn+Wn+{1 ai 4 L) x; }, Gl+*n+Xn+Yn+Xn+Zn+tn+`n+rn+*n+:d.[m+mo Q5 |v.1^ k! 3) 5~ 5~ 5) b) i) L@ y y x h@ [K.[K.( ( { bf bf bf { { ( >@ t) U} 8, 8, 8> V> 4- #- }; H+ H+ Bz.p4 +0 9; C& !; -' Q5 Q5 Il+q`.Dz aE Il+mo Il+q`.Cm+_m+a!+Gl+mo (< o+0- k, !; O+ ci 9^ N_ .o+M_ WC i) < 9& N+ % Y% % Y% Y% % Y% % j> D# D# ^ T- ^ 9. 9. 9. 9% 9% D D 9. 9. 9. 9% D D `5 +o+C9+`1+xf+hg+/i+7k+@o+xn+e(+2[+P9+#o+gd+$o+%o+&o+Bc+W1+#3+~g+~g+=f+K( #3+5j+|4+[b+ed+M7+L7+M'+W1+ed+Nf+[b+*f+W1+#3+[b+|e+`i+V1+r0+Ve+*o+Cf+*n 3X.r4 =o+-o+2U.;o+%Z >o+,o+'o+)o+!o+0+ >@ ,@ ,@ ,@ W_ G5 A9 A9 P[ f~ +! Ip Ip +! J' 9% !$ g& L) j, }> D# D# j, [* b> M; L) L) .> ~> b! ^+ ^+ 93 ~o+{o+", +"j, `% O+ N+ N+ E; 9. F] f~ f~ g~ ~! 3) 3) <3 <3 <3 <3 <3 a~ t, P[ +! P[ P[ A9 A9 A9 U< 0+ ,@ a+ ,@ W_ W_ ,@ r# ,@ ~* /<.g}+ar K0+}J 9G Fc+ed+ki++3+WM.9f+@3+@3+/@.68.T9.DS ]@.O` CP DS ]@.'a.S]+c4.K5.DS k8.c4.^@.d4.d4.D9+#3+-f+Nf+]o+`5 v! [X r& 1- Y& ^o+/o+(o++3+2Q.T5 Cr ( 9. 9% 9% D J& r- v$ v$ b> L) L) M; j, }> L) N+ v$ 6. {) X! ;] 7> _. 5. S> _o+:o+@ a~ r& r& s; 8> !; #- q# q# H+ }2 }2 Bz.T* !; 8, -' aE q`.q`.Gl+Dz ;y aE Gl++0 p4 Il+ym+3o+tn+4o+5o+6o+jM.=- *- z$ +| 9^ 1: 7o+8o+9o+eq.b) 4> v$ O+ G# O+ % Y% q% a& v$ j& D# h> .; :. ^ / 9% 9% 9% 9% 9% 9% 9% 9% 9% 9% D -+ P^ 0o+}'.xf+ 2+ao+|e+|e+Q9+4[+S]+T'+8E.bo+co+do+i}+jo.eo+=f+wm+K( |4+0f+wm+#3+*f+*f+ba+~g+M7+h5+@O.&!+W1+ba+*f+[b+Fc+=f+=f+Mh+Gc+[b+{4+L7+Jn+Ve+xf y;+=o+-o+cm+2U.fo+8& go+cf y` d) }$ ,@ ,@ ,@ ,@ ,@ ,@ O[ U< A9 P[ h' t, A9 A9 M@ 9. E $& F -& `% L) L) = c% [* >+ x; x; L) .> ;> ;> ^+ ^+ n`.0* ho+", +":* U; r% M+ F b+ H' F-+I' s) H' T) I' J' z! J' J' J' K' y! t, y! y! y! t, y! i' G5 io+'@ <. x b+ }, -} 0B jo+ko+lo+mo+~{ gb ;R =< M^ t{++3+no+oo+[v.j@+~O zl+yb.(Z fQ.fQ.=O V+.r +`V Mr.)O po+EI.qo+ro+{U.so+k8.T9.T9.to+A=+p}+uo+cL.Mr.d% ^, z; `| 2X.2v.Ac s4 [^ vo+-e b+ _ G$ $& n% !$ :* [* %& %& L) b> `% L) [* [* L) `% v$ > k& -! t! `~ -! *> ( wo+xo+yo+'/ P~ <$ U; `% zo+mo c) 2o+sn+Ao+Bo+Co+Do+Eo+Fo+Go+ X> -, O~ i- zo+p= 7% |^ @+ *t.q# &7 *t.X, !; a' F> ,u.H+ *t.*t.@< @+ +0 mo ym+-n+Io+Io+uw.Jo+Ko+Lo+Mo+(* K, A> O> + %& v$ O+ % % p- = <$ j& D# D# 5. 6. 5. / -+ :* t@ u# u# -+ D $& 9% 9% $& -+ ~$ X 5[+io.T'+Qo+Ro+So+ui+To+Uo+Vo+Wo+Xo+Yo+I2+Zo+fJ /R 3!.CP :w.8w.rM 7E.:w.Qn+D,+G|+A=+iR.G|+*g.to+{l+ob.ob.u_+Lm+lI.Hd+0~+ob.Hd+`o+0f+ p+&f+r& g/ >2 5( -o+,2 !; gJ @k <$ ~ _ '$ s% }, 9. r# s% ,@ n=.G5 p! p! p! i' i' t, m, *e >@ 7o 9. F z r% c% |) :* V+ ;+ -& q; L) }> ;> x> 5 5 $& n H{ ", +"%& h& :* :* }> += += += e& Q;+.p++p+@p+#p+Ed $p+=s.}, <$ <$ <$ <$ <$ <$ <$ a^ I= $~ Y_ e% U) j& h> yj %p+&p+*p+lk+h> += h> .; c> ~> i> M^ X( 7# +& m% m% I I I m% m% D$ :& I I :& d+ :& /+ /+ t% `,+=p+-p+;p+>p+,p+'p+)p+!p+8(.~p+{p+iU ]p+^p+/p+(p+_p+Xe.:p+ ~> ~> ~> }> }> h> k& .; j& D# }> :* }> u, &~ n] -* -* L- n] L- O~ X> &~ , &, ;- ;- ;- u, z- z- j& += D# <$ ~> ~> )$ )$ )$ V+ V+ V+ V+ V+ V+ V+ V+ V+ V+ V+ V+ )$ ~> <$ D# D# D# D# += += j& j& j& '$ j& k& k& z- += <$ k& k& k& j& '$ {+ n& n& U) , X> -, +& |- x{ ae 1p+w' H- l& j& > j, k& 4 >t gF.y, +, )$ V+ j, :* [* [* [* j, }> *@ *@ }> }> V+ }> V+ V+ V+ 5 j, V+ V+ V+ V+ V+ }> *@ *@ V+ V+ O! W{ (= W{ (= W{ (= _{ 2p+92+3p+4p+5p+6p+L^ y# ~+ Y_ s. D$ !+ D$ C$ C$ m% !+ I I !+ :& I /+ m% S+ !+ S+ N@ N@ N@ !@ N@ S+ N@ I :& 7# (> ;> c> D# .; .; j& {+ u, )$ *@ *@ )$ )$ )$ )$ )$ )$ ~> ~> )$ ~> n ~> n <$ <$ n <$ <$ n n ~> )$ V+ :* h& [* :* j, V+ j, [* j, }> =! =! 5 }> =! =! ;> ", +"b> j, 5 )$ D# += += += e& e& 7p+8p+9p+0p+ap+Ym n <$ <$ <$ n n e& yf cj+bp+cp+k' dp+ep+fp+gp+hp+ip+jp+kp+lp+}J ~> c> D# {+ c> =! }> mp+np+op+pp+qp+rp+rp+sp+sp+tp+qp+up+vp+wp+xp+ T yp+zp+Ap+Bp+Cp+Dp+Ep+Fp+Gp+Hp+Ip+Jp+Kp+Lp+Mp+Np+Op+*Q Pp+Pp+Mp+Qp+Rp+;U.Sp+UK '$ X+ e& '$ 0^ U+ G$ }> V+ }> 5 V+ V+ 5 :* :* j, j, j, V+ j> V+ }> }> =! ~> ~> =! }> <$ D# :) ~> =! .> j, ~> &, $~ n] n] n] '! $~ $~ $~ &~ 4 &, U+ {+ {+ {+ k& z- j& j& += D# <$ <$ ~> )$ )$ }> }> }> }> }> }> }> }> }> }> V+ V+ V+ )$ ~> c> D# D# D# D# D# += += += j& j& j& j& z- '$ += <$ .; z- k& u, u, {+ n& n& U+ 4 , $~ n] -* ;^ -* |- P~ -* X> {+ j> j, j& -! -! )) /{ z- }> j, j, j, j, j, j> }> }> }> }> }> }> }> }> }> }> }> }> }> j> j> V+ V+ }> }> *@ *@ }> }> e& !+ L- L- Y_ ., mL.Tp+Up+Vp+Wp+Xp+Yp+-, s. e% y# d% d% e% (# e% s. ~+ Y_ Y_ C$ Y_ T+ D$ D$ D$ ff D$ D$ I S+ m% :& ~= :& +& N@ L- N@ (= Zp+}J ~> c> <$ <$ Q> u, *@ *@ *@ *@ )$ )$ )$ )$ )$ )$ ~> )$ )$ )$ ~> ~> ~> n <$ <$ <$ <$ n n <$ <$ )$ }> j, j, j, j, j, j, j, j, }> }> }> }> }> }> }> }> ", +"%& j> ~> '$ '$ '$ += += e& D# Ym `p+fH.)~ <$ <$ <$ <$ <$ <$ += W9. q+.q++q+@q+#q+$q+%q+&q+*q+=q+-q+;q+>q+,q+SB.+= c> c> ~> D# z- D# n Uq.L4 'q+J+ = = = v. )q+!q+~q+{q+]q+^q+8+ v. = /q+(q+_q+:q+ i> )$ <$ D# c> k& z- n }> }> }> }> j> j> .> }> j, j> j, V+ }> }> =! ~> ~> =! }> ~> j& D# }> }> j> j, z- e% -, n] L- O~ -, X> $~ X> , I= U) n& n& U+ {+ u, z- += += D# <$ <$ ~> )$ i> i> }> }> }> }> }> }> V+ V+ V+ V+ j> j> V+ i> ~> ~> D# D# D# D# D# .; += j& j& j& j& j& z- h> j& <$ ~> k& {+ {+ u, u, n& n& n& 4 , '! +& |- |- -* x{ P~ P~ n] e% ~> j, D# |! {+ ;> D# += }> j, j, j, j, }> }> }> }> }> }> ~> }> }> }> }> }> }> }> }> j> j> j> j, }> i> *@ *@ *@ *@ k& Y_ e% n& D# 0q+aq+bq+cq+dq+eq+jb 8, m% Y_ ~+ y# d% e% y# e% e% E$ ~+ ~+ !+ m% D$ ~+ Y_ T+ Y_ ff T+ C$ m% m% I :& +& :& N@ S+ ~= N@ 3! fq+gq+~> c> <$ j& k& )$ *@ )$ )$ )$ )$ )$ )$ )$ ~> ~> ~> )$ )$ )$ ~> ~> n <$ <$ <$ <$ <$ n n <$ <$ <$ ~> }> j, j, j, j, j, i> z- ~> n) hq+iq+s- W) <$ }> ", +":* D# z- k& z- '$ += += e& D# e& D# n D# D# <$ <$ <$ D# d% U8.jq+l@ kq+lq+mq+i- Q} nq+PM.1T.oq+pq+v. 1q+qq+!a.p$+rq+sq+tq+uq+vq+m^.wq+xq+yq+zq+Aq+= = = -@ b2 Bq+Cq+Dq+Eq+Bc+Fq+Gq+v. @q+xR.Hq+Hq+Iq+Jq+Kq+k= 'c+|p.Lq+v. = = -@ Mq+,y Nq+Oq+Pq+Qq+Rq+= v. Sq+Tq+e% }> i> i> i> i> ~> D# j& += += ~> }> j, j, j, }> }> }> j, }> }> }> }> }> ~> =! }> )$ j& D# }> j> j, }> n& X> ff L- L= O~ -, X> $~ E$ , l& 4 U) n& U+ {+ c. k& <$ <$ <$ ~> )$ )$ }> }> }> }> }> V+ V+ V+ V+ V+ V+ V+ j> j> j> j> i> ~> ~> <$ D# D# D# D# D# .; += j& j& j& j& j& j& += c> }> k& Q> c. u, u, {+ n& U) d% E$ O~ +& |- |- +& (= x{ x{ ;^ n] {+ j, ~> w> j& .; h> D# }> j, j, j, }> }> }> }> }> ~> }> }> }> }> }> }> }> }> }> }> .> .> xh+xh+j, .> ~> += k& {+ k& += <$ c> i> *> Uq+Vq+Vp+Wq+Xq+ti T+ y# s. E$ B$ r@ d% d% B$ d% e% s. s. Y_ C$ D$ Y_ s. y# ~+ C$ Y_ Y_ ~+ C$ S+ :& !+ S+ I S+ ~= |- Yq+Zq+`q+)$ c> j& d% n )$ *@ )$ )$ )$ )$ )$ )$ )$ ~> ~> ~> )$ )$ )$ )$ )$ ~> <$ !; x{ J> s#+V, G- 9* S$ !2 L, ]) dE /) r+o .r+d- C; c& P= ^= p h= w$ A* }> ", +"+= '$ c. c. z- z- += += += e& e& <$ <$ <$ <$ <$ <$ <$ d% +r+@r+v. Rq+#r+bE a^ Bc K7+so ko *, lI.$r+=q+v. -@ %r+&r+*r+=r+-r+;r+>r+Ia+,r+'r+)r+(B.!r+= = = =q+~r+{r+]r+^r+/r+B# S= (r+_r+:r+ i> i> i> i> i> i> i> ~> {+ 4 +, ~> }> }> }> }> }> j, }> }> }> }> }> ~> ~> i> )$ .; w> =! j> j, <$ d% Y_ L- L- n] ff ff X> &~ &~ l& l& 4 n& ;- ;- ;- {+ '$ )$ )$ )$ )$ }> }> }> V+ V+ V+ V+ V+ V+ V+ j> j> j> j> j> j> j> j> i> ~> <$ <$ D# D# D# D# D# += j& j& j& j& j& j& j& j& c> }> .; k& k& u, k& u, U+ U) 4 E$ ff H- -* x{ |- ;^ -* ;^ x{ H- l& V+ =! ~> D# j& z- c> }> V+ }> }> }> }> }> }> ~> ~> }> }> }> }> }> }> }> }> }> }> }> }> xh+}> ;> bB 7> E$ z- D# <$ ~> ~> ~> i> )$ 3r+4r+5r+Vq+6r+ff E$ e% e% e% e% e% E$ B$ e% e% d% y# T+ T+ ~+ D$ ff E$ Y_ T+ s. E$ ~+ T+ ff L- I !+ S+ S+ W{ _# Q; 7r+jp+8r+=! .; 4 4 {+ e& <$ )$ )$ )$ )$ )$ )$ )$ ~> ~> ~> ~> )$ )$ )$ )$ ~> <$ b& v# 9r+w$ >; w$ P= `* s# e- Y$ `, a* <; @= {# ^= s# g; ^= >; {; 0r+I- @6 }> ", +"s% += k& c. c. z- '$ += += D# <$ <$ <$ += }, )~ `]+ar+br+cr+v. v. dr+er+fr+gr+hr+ir+jr+kr+lr+mr+nr+or+v. v. Rq+pr+qr+rr+sr+tr+N*+ur+vr+Ym t] wr+xr+= = = =q+yr+K^ y# s. y# B$ d% m% zr+:r+Ar+T+ T+ T+ ~+ Y_ C$ L- I Br+J+ = = 8+ Cr+m% S+ I Y_ h/ Dr+J+ = = v. Er+X:+~> n c> Fr+Gr+Gr+gq+gq+Hr+|n+Ir+Jr+Kr+i> j> xh+j, j, }> }> }> }> }> =! ~> =! )$ D# w> ~> j, j, k& l& ff L= +& H- O~ n] X> &~ &~ e% 4 n& n& ;- ;- n& U+ u, ~> }> }> }> V+ j> j> j> j> j> j> j> j, j, j, j, j> j> j> j, j, j> i> ~> <$ <$ D# D# D# D# D# j& j& j& j& j& j& j& j& j& D# ~> D# j& z- k& k& k& {+ n& &, , -, n] n] |- ae ;^ +& x{ ;^ X> 4 )$ .> }> w> j& j& ~> }> }> }> }> }> }> }> }> }> =! }> }> }> }> }> }> }> j> j> V+ }> c> ^, AR.we+Lr+xe+u, D# c> D# <$ c> =! =! i> Mr+*p+Nr+Or+N@ I T+ y# B$ (# E$ e% (# e% e% e% e% e% E$ B$ E$ D$ E$ T+ Y_ E$ Y_ ~+ ~+ m% m% C$ ~+ D$ D$ D$ S+ (= %Z Pr+Qr+Rr+`) {+ j& D# <$ c> k& k& += D# ~> )$ )$ )$ )$ <) ~> ~> ~> ~> ~> ~> ~> n #; d= d= ^= I@ X% 7 j- >) S; ]) W) ,) Y> #* <; p /# Y* h= h= ^= h= - :e }> ", +"s% e& '$ k& c. z- += += += <$ <$ <$ Sr+Tr+Ur+1j /Q Vr+Wr+v. = v. Xr+Yr+Zr+`r+ s+.s+jX +s+@s+#s+$s+%s+&s+= v. kq+*s+=s+D$ s. T+ T+ d% '$ }> c. -s+= = = =q+;s+e% e% B$ e% y# E$ e% >s+,s+z;+y# T+ E$ Y_ D$ C$ m% I 's+6q+= = 8+ Cr+C$ m% S+ L- )s+!s+~s+= = = {s+]s+'$ @& l `~ Jr+^s+/s+%p+(s+we+_s+:s+ }> }> }> }> j, j, }> }> ~> ~> ~> ~> .; c> j, j, {+ Y_ H- H- -* -* ff ff Y_ X> , n& c. {+ n& ;- ;- ;- U+ {+ ~> j> j> j, j, j, j, j, j, j, j, j, j, j, j, j, j, j, j, j, j, j> )$ ~> <$ D# D# .; j& j& j& j& j& j& j& j& j& j& j& j& D# ~> ~> += j& += j& j& k& u, {+ 4 l& e% , ff H- O~ ff n] '! l& ;- ~> j, }> w> w> D# i> }> }> }> }> }> }> }> }> }> }> }> }> }> .> }> }> }> }> ~> Q> g/ %p+[s+}s+|s+1s+2s+<$ D# ;> ;> D# ;> c> ~> i> lk+Zq+3s+5( +& m% Y_ e% d% e% e% d% d% e% e% B$ B$ s. y# y# s. Y_ T+ T+ Y_ s. Y_ ~+ s. ~+ m% C$ Y_ D$ Y_ ff ~= W{ 4s+5s+6s+7s+~j+Q> ~> =! ~> =! ~> ~> c> .; {+ {+ += )$ )$ )$ )$ )$ ~> ~> n n n n 8s+^= i= v# 9s+0s+as+bs+1$ 0& cs+4= W% .= Y* @= ^= p d= >; Q= d= d= `* @6 }> ", +"}, <$ D# k& u, z- += += += D# D# e& ds+es+fs+gs+hs+is+= = = js+ks+ls+ms+/w ns+-o+os+^r+ps+m}+>* u{+mq = = = Sq+qs+y# y# E$ e% e% d% '$ D# 's+= = = =q+rs+E$ ~+ T+ :& ss+oo s. I ts+q. y# T+ T+ D$ C$ Y_ m% :& 's+6q+= = 8+ us+ff ff I m% L- vs+8+ = = = ws+G$+c. ]r+T= xs+ff Q} ys+zs+As+:s+(s+Bs+Ec ib j& j, }> }> }> }> }> ~> D# j& j& w> ~> ~> ;> ~> j, c> &~ L= -* |- x{ w' |- +& |- L= , ;- u, u, n& n& ;- {+ {+ {+ e& j, j, j, j, j, j, j, j> j, j, j, j, j, j, j, j, j, j, j, j, V+ ~> D# D# D# D# D# j& j& j& j& j& j& j& j& j& j& j& j& D# c> ~> z- j& j& k& ;- e% X> , $~ &~ ;- I= -, ff n] n] $~ &~ l& &, u, c> |! `~ Q> .; }> }> }> }> =! =! }> }> }> }> }> ;> D# D# D# k& E) gy.~) &/+nq+_1+Cs+Wp+Ds+|s+Es+gq+~> ;> ;> ;> ;> D# c> =! i> Fs+Gs+Kn+:& I D$ m% s. B$ y# e% e% E$ E$ e% s. s. T+ y# T+ T+ T+ C$ m% T+ Y_ ~+ E$ Y_ D$ D$ C$ D$ ~+ Y_ m% ~= >! Hs+Is+Js+Ks+Ls+Ms+Ns+Os+j& j& [J oA.gQ.Ps+gQ.<^+ib /s+gy.c> *@ ~> ~> ~> n n n n ]) 1; |> P= X- @* N= Xo >> W+ Qs+Qs+Rs+b; Ss+&= d= p h= I- * ^= p I- @6 }> ", +"}, n <$ '$ u, z- '$ j& += += e& e& Ts+Us+Vs+6j Ws+Xs+= = = Ys+Gm.UK K, y;+Zs+Q; m4 f- }^ `s+,{ , t+= = = v. .t+7# Y_ Y_ Y_ T+ E$ e% a! +t+= = = =q+@t+y# T+ T+ oo #t+$t+Y_ T+ m% D$ e% D$ Y_ s. D$ C$ T+ I 's+6q+= = 8+ Cr+Y_ Y_ D$ D$ L^ %t+kq+= = v. &t+ip+*t+v- =t+-t+e+ 7# ;t+>t+,t+>t+'t+)t+!t+_1+AR.c> j, 5 }> i> h> m& n] P~ ;^ Q~ 4 j& D# D# }> D# &~ L= +< }i mb z>+*K.L, &y a' ~t+-* X> &, I= &, 4 n& n& {+ k& D# D# D# <$ D# D# ~> i> i> ~> ~> ~> ~> ~> ~> ~> ~> ~> ~> ~> <$ j& j& j& j& j& z- k& k& k& k& k& k& k& k& k& k& k& k& z- j& j& {+ n& l& '! Q; @6 _G ,^ {t+:e H- Y_ Y_ -* |- ;^ |- -* +& ae +< ;^ zm.3^ L= Z! h> ~> ~> ;> D# D# D# ~> ~> E) -! `! n] A) ;^ }i )/ ]t+yV &D.^t+Wq+/t+(t+(t+_t+:t+Gr+D# D# ;> D# .; .; ;> =! =! )$ ~> n n n n <$ S$ >' v# I- W' D# n& +} 0t+-= && hq+V' =- t- u. e; 9+ at+@6 a- h= bt+s# 4^ j, ", +"G$ n <$ += c. k& '$ '$ += += e& }, ct+dt+et+H0 ft+v. = = = 3q+|3 s% ,U.>] _# -G dl gt+R>+ , gQ.,] ht+= = = = it+bE ff E$ E$ e% T+ Y_ d% jt+= = = =q+@t+e% ~+ s. kt+lt+mt+y# C$ D$ T+ ~+ ~+ s. y# Y_ ff D$ I nt+6q+= = 8+ Cr+D$ Y_ I I N@ ot+J+ = = 1q+pt+qt+rt+l& C. el el st+ - ^{ _s+tt+ut+vt+wt+xt+yt+M] [) 5 }> h> v{ 4^ el -t zt+dl 6! Q; %~ $~ Z! &~ P~ J> {t+At+Bt+Ct+Dt+K) Et+Ft+Gt+Ht+*K.1p+;^ x{ ae Q; L= -, I= {+ {+ I= X> '! -, `! l& l& &~ $~ '! $~ `! $~ n] n] L= L= n] n] L= n] n] L= ;^ x{ P~ ;^ L= L= -* L= n] L= L= L= -* ;^ ,! ;^ (> ae }i s#+u=++y It+Ft+Jt+Kt+Lt+Mt+*K.Z_ 6w.~t+Z_ ~t+1p+@6 &y |( V' Nt+Ot+nE ,^ ae H- $~ &~ $~ Q~ Q~ &~ $~ N~ fq.x] Bt+Pt+Ot+i@+Qt+Z#+J$+Rt+St+Tt+Ut+Vt+Wt+Xt+B=+Yt+(# n& l& $~ Q~ v{ &~ Z! O^ <{ z* |& X& Z_ X& X& X& Zt+X& ~t+~t+*t.~t+X& Zt+z* &7 z* Zt+~t+X& z* `t+Zt+M$ Zt+|& |& u+|& &y z>+&y .u+;} Ht+$%++u+@u+#u+$u+%u+&u+*u+=u+-u+*t.;u+>u+s#+,u+'u+)u+F1.zs+!u+~u+)) ~> ~> <$ n n <$ <$ `' At+j- {u+;- <$ j& k& ,; 0t+a* ]u+B* 4= ^u+&, U+ 6w.}> V+ Kt+h= d= `* 4^ j, ", +"n )$ n n z- k& z- '$ j& += += }, /u+(u+_u+:u+ 4u+8+ = = = 5u+6u+Y_ e% E$ Y_ Y_ Y_ ff +t+= = = =q+7u+!@ xf 8u+9u+1q+0u+Y_ T+ E$ D$ ~+ T+ D$ m% m% S+ C$ m% au+6q+= = 8+ bu+Y_ ~+ m% :& Bp.:r+v. v. @r+cu+du+eu+fu+gu+I hu+el el st+N& iu+ju+ku+dq+dq+ku+lu+dh+[J }> ~> Z! @6 R< }* mu+nu+ou+pu+qu+ru+su+At+!2 `m +y G) tu+uu+nu+%* q, H vu+q* P> wu+9' xu++y a= a= 0/ k@+}i (> x{ A* W& el 0/ Mt+k@+k@+dl 0/ ') 0/ `m el 0o zt+zt+Pt+a= 0o yu+zu+o5 G) Y) -t E>+-t ao ao ao Bt+0/ Au+Bt+0/ yu+G) ao -t 9' qu+Bu+tu+Cu+P> q> Du+|* K= Z- uu+^u+Gt+o5 -t+Eu+** !2 Fu+a= Gu+at+Hu+Iu+}* Ju+Ku+ao +y `m `m ru+0/ Lu+dl Mu+Ct+Nu+Ou+Pu+%* Qu+Ru+Su+Tu+^p.Q6.Uu+Vu+Wu+I&+Xu+i8.K xr zm.p, }( W^ y%+dE +} Eu+Lt+Yu+Zu+`u+R< v+.v++v++v+Gt+@v+@v+#v+Lt+mY.K) +v+K) Fo+Gt+ v+$v+@v+%v+F&+@v+Lt+`u+K) Yu+Yu+8& &v+Eo+*v+{= =v+-v+;v+>v+,v+'v+I> )v+HE.!v+~v+{v+]v+mY.oi ^v+p' /v+(v+_v+:v+ <$ <$ n n <$ <$ z- ae W% W+ += z- k& {+ $* p ^= >; h= ^= }v+U) c. c. j& }> W' h= ^= {; `m j, ", +"`= '$ e& ~> <$ Q> c. z- '$ += += += s% p$+|v+1v+2v+= = = = 3v+4v+5v+6v+7v+8v+/k+({ 9v+_1+0v+`s Y_ av+bv+= = = cv+dv+E$ e% e% Y_ ff Y_ Y_ 's+= = = J+ ev+fv+gv+7q+v. 1q+n*+Y_ T+ ~+ ~+ Y_ D$ C$ Y_ ff C$ T+ m% 's+6q+= = kq+hv+iv+jv+kv+lv+mv+J+ pq+nv+#f+ov+pv+qv+rv+sv+Ms+'c+54 el xu tv+v- Kn+uv+vv+Vp+wv+xv+yv+zv+c> += ,! V' Av+Ku+%, |' Bv+Cv+q> Y, Dv+`' Ev+Fv+ ) Z' Gv+cs+P, &; Hv+,) 8, -$ Iv+Cv+Z- Jv+b= Kv+Kv+Z= W) zt+w^ nE Ct+/) at+v, Lv+2) 1) Mv+Nv+& p% Ov+b= Iu+X) i; Pv+Qv+Rv+nu+Rv+Sv++' a- Z- a- Pv+Iu+Tv+Iu+RC Y, Ou+Uv+at+Ou+nu+Vv+Pv+q, q* ,> 1$ Wv+Qv+!- 8, Xv+Yv+`' $* vu+Pv+Zv+ ) `v+Fo+G) 7X Cu+%* D& C& h; w+.w+ w+i; cs+v, v, Uv+Nv++w+^) ) Tv+2) -:+=$ @w+V> /{ #w+$w+%w+)}+&w+*w+=3 6% =w+-w+;w+f5+V' rm.>) v, >w+A' ,w+'w+)w+!w+>} ~w+cs+Z- {w+q* ]w+^w+!w+/w+(w+Z- _w+:w+]w+q* } q, [w+}w+|w+Z- nu+1w+%* q* q, A. 2w+A. 3w+Wv+4w+5w+/{ ,) 6w+Yv+Z- ^# 7w+8w+9w+0w+aw+bw+K) cw+B*+&7 dw+ew+5s+6s+fw+*N.+= ~> ~> n <$ <$ <$ s#+P- gw+X> ^ += u, N= d= d= d= d= d= m' K+ L, c. z- ~> +v+h= ^= {# V' j, ", +"Bc U) U) U+ '$ u, u, u, z- += += e& D# D# D# y# hw+= = = = iw+jw+kw+lw+mw+nw+2p.xu+ow+:Q.V> k= G@ pw+qw+= = = +q+rw+s. E$ E$ ~+ Y_ E$ s. 's+= = = J+ sw+tw+qD -q+= 1q+wb.Y_ D$ D$ ff +& ff D$ L- m% ~+ ff !+ nt+6q+= = v. uw+vw+ww+uw+kq+= xw+(r+J S+ I 8K.yw+rv+zw+Aw+AR.Bw+Cw+i% f /& B% w' Dw+Ew+bq+ku+*p+M9 |n+{+ #6 Fw+ w+W' -- +; Hv+Cv+Gw+M, Sv+3* Hw+Yv+=- Iw+$* Z= -- !- Ss+l, -- ;= 7 Cv+q= Jw+E= Wv+Pv+A- =$ Z= Kw+2) i; Lw+Iu++' Mw+Nw+Ow+_> q> R* zg.Pw+Qw+s> A- Rw+K= Sw+!, r* ^# Tw+2- Uw+w& Hw+E& ], Vw+2- Ww+Xw+X) i; Yw+Zw+n> `w+Lw+#> x+k- .x+_' +x+.) ,) @x+-- -- bs+Qw+/* $* I# s= #x+wu+wu+U$ `w+v{ -- =$ $x+.' 6w+Zw+%x+as+&x+X) nu+Ow+s> *x+=x+}i -- V% D* G# -- -x+;x+>x+,x+'x+)x+!x+1$ ~x+{x+]x+wu+Z' |* #> #, ,> `w+^x+/x+Ow+q* J) (x+Sw+H r* @; Nw+_x+d. :x+I# Tw+Jw+6* #> Rs+1$ +x+Tw+K= _x+E, 3w+a- 6w+ n <$ <$ <$ X> n- f& 0; /) D& Y> Q= d= d= ^= p h= ^= dx+Z' z- e& <$ $- h= d= 0% ' Kx+-- q% *+ &; -- :. ;+ a& f; a* `- N+ -- G# Lx+&x+ w+Du+I# h; a& a& r& Ww+0; #- a& a& _. q- A- -- >+ V> a& B> #- a& 5. Mx+s; -- T* % O+ P, O+ q% S$ Y> -- q% Nx+8, -- r G> -- 8> G# q% S; 1, -- a& V, -- -- _. 7= -- (x+2- -- B* RC Av+Uw+A- a& -- _. Ox+B> Y% -- 6. b% [x+-- -- N, :; Px+~x+q% -- z$ U$ O+ -- s; q% 4; Qx+=@ 7= q% a& _. Rx+Sx+q* iq+/' N+ -- % $- C; J) >+ -- % N, 9s+T* -- T* G# O+ S; N+ a& R> @* -- -- Kx+a& B- f> a& v$ q= :^+-- /) Rs+-- -- 0; -- -- 4- a& v$ Tx+%= -- ; N+ -- G# l- *+ n- Ux+IZ.`) Vx+Wx+X^ H* 78 v- B% w~ Xx+ax+6s+Yx+Zx+<$ <$ <$ <$ , Av++' c* ]> `x+x& /x+i* ^= v# E* :; Bu+Av+Qs+Z' += n <$ $- h= d= P= {t+j, ", +"s4 d% d% d% d% d% (# d% d% U) y+,t e& e& D# {+ fv+= = = = .y++y+Ms+@y+#y+$y+%y+&y+*y+Yr+=y+-y+;y+>y++q+= = = @r+8E.t% D$ D$ Y_ ~+ y# e% ,y+= = = =q+;s+D$ m% C$ 'y+)y+!y+D$ ff S+ C$ Y_ C$ C$ D$ C$ Y_ T+ m% nt+6q+= = 8+ ~y+6+ {y+]y+v. = v. pq+^y+I m% :& L- dw+pv+/y+(y+_y+e% :y+ P- -- -- W+ ;+ G= f& -- Kt+d. W+ p% -- *+ 2y+B= -- =; !, -- ,- c& *+ -- U% -- N+ Xw+3y+Jw+-; -- +; 4y+-- &; s- -- =; }* -- 0& b% -- T- m@ a& v$ [> v$ -- q- V> -- (, (, -- P, & -- h; 5y+-- q% 6y+V> -- `w+n- -- .) /* -- %, 4= -- q% _' -- -- @w+_x+-- z& n* -- %* :w+Oe.2w+Xv+T- -- `- 4. -- *+ p* -- M> 7y+-- -- -- /' 8y+]' V> -- &; e; O+ -- 9y+:; -- f= Rs+-- C& V, -- 0y++x+ay+.* ;+ -- 1$ N, G# _' G> -- /* *+ -- 5y+f; -- D& B= -- :' p* -- z$ %- -- -- 6- s; -- y- a> -- 2- k, -- &; by+-- -- )' -- -- 6; B- -- v& S; -- B= k, -- b* t- -@.r& u. T* ({ cy+dy+ey+fy+gy+0w B*+>u+,^ hy+pv+qt+iy+v! n ~> n ]) N= w& k, jy+rm.-, ;- b% t- s#+e& += += z- u, d% <$ n n ky+h= d= s# A* V+ ", +"r. r@ d% B$ B$ d% d% B$ e% T+ ly+my+ny+oy++= e& py+Rq+= = = qy+ry+sy+ty+ty+uy+vy+Y_ T+ ~+ /r+wy+xy+yy+v. = = = zy+Ay+By+[p+ms+C]+wr+qf D$ Cy+= = = =q+us+m% m% T+ `i.Dy+s8.D$ D$ ff Y_ E$ s. s. s. s. ~+ C$ D$ au+6q+= = 8+ Ey+L- I Fy+Sq+= = v. Gy+Hy+N@ S+ I +& Iy+sy+ju+jb %p+n& Jy+Ky+|= el el +- Jy+Bs+Ly+My+Ny+/v+w^ /) `w+<' -- $- W* -- -- &+ b* 4- 4= -- b= Oy+Py+K= -- s; ~, hq+-- k, G; -- -- =, -- -- c= -- -- E& @* 5y+9' -- ,) 8- f> n> G- -- G= k- -- *- G& -- 4- I- q% _. I- :. -- 9+ 4- -- G& Y> -- U% 1, -- _. C= -- q% ~, 4- -- !, *- -- Qw+W- -- :. %- -- q% Qy+-- -- Ry+Sy+a& R* s, Y% $* Qu+X) Rx+7y+8, -- w& A= -- V> b; -- 6. 5- -- -- -:+_> W- %- M> -- r ~, O+ -- 4= o@ -- Ty+^# -- #- v& -- %, L; 6- >' -- -- C= vu+f> Py+^ -- 0; !; -- Gw+n- -- E& q- -- 2- 4= -- % a* -- -- g* n- -- Uy+f= -- K= S; -- & )' -- -- Vy+-- -- D= :^+-- I# .) -- `- p% -- .) Wy+r* a& bs+%, 7= Xy+Yy+Zy+`y+~u+>;.O@ el -t+ u+ z+Up+Vq+.z+F|+n <$ W; H> (, 2) l& +z+z- e& Y) e% e& j& c. u, Y_ u, += n <$ n ky+h= d= /# H- <$ ", +":& d% B$ e% B$ B$ e% e% E$ #y.@z+#z+$z+%z+/B.U) &z+@r+= = = 5u+'>+*z+=z+r3+D) C$ T+ E$ (# d% e% D$ 8: = = = J+ -z+;z+>z+,z+'z+)z+^B.!z+~z+p~.= = = =q+{z+T+ T+ D$ |- ]z+oo C$ I +& f5+)= ff ~+ s. Y_ C$ D$ m% 's+6q+= = 8+ Cr+C$ D$ 7# ^z+&s+= = = /z+/o+:& :& :& L- (z+_z+:z+(s+*t+^, @& u+-t+0w U) 8v. ! y> l& Lu+>) r+B* -- $- W* -- -- F; K= ^ m- -- I# Oy+by+6* -- s; /# B= -- k, 6 -- -- W% -- -- ^ T- :. _> @x+[z+f= -- Z' |> }z+|z+`- -- $- d& -- a% P- -- 4- I- q% _. I- :. -- 7 a> -- G& Y> -- 1z+4. -- _. 5- -- q% [$ a> -- P- a% -- Qw+W- -- :. d- -- q% F; -- -- 0= 6 !; 0& 2z+^ Rs+3z+6* Hw+7y+8, -- w& !, -- !; L; -- += 4z+-- -- 5z+d. q= $x+B- -- o@ /# O+ -- 4= p@ -- f= w& -- Y% T- _. M= 6z+>' 7y+-- -- 7z+>> D= Wy+6. -- @w+!; -- E= r; -- _x+q- -- F) 4= -- % &+ -- -- 9= n- -- |> f= -- 4y+%; -- B= I@ -- -- W% -- -- 8z+:^+-- v& /' -- `- p% -- <' }z+9z+-- ^# O+ Kt+0z+x7.az+$6+bz+cz+B# dz+/= -t+^- ~u+lu+Qr+ez+d) ~> R< fz+c& q, e& d% ;- u, :' += += += ru+gz+`$ Sw+E$ n ~> )$ ky+h= d= y$ L- '$ ", +"q. e% e% e% e% B$ e% e% hz+iz+jz+kz+lz+{j n;.mz+l4 nz+= = = 8+ oz+pz+8K.L- !+ m% D$ ~+ Y_ s. y# !@ VB = = v. qz+w>+rz+sz+tz+uz+vz+Fp+wz+Bp+xz+= = = =q+yz+`,+Kq+D$ D$ C$ T+ Y_ D$ %h zz+Az+s. s. E$ ~+ T+ C$ I 's+6q+= = 8+ Bz+Y_ ~+ T+ -q #q+= = = J+ Cz+`n.m% D$ ff I Yl+Ps+}J K> c> =! D# @& Dz+Ez+]r+:# D# )$ D# l& l@+`' %i.P, -- $- $> -- -- =, )> Y% k, -- }) Hv+7y+p* -- 8, `x+V, -- 0& D* -- -- $= -- -- K+ P, -; A= m@ Fz+f= -- >) &+ -; vu+G- -- $- 4j.-- *- .r+-- 4- g; q% _. g; :. -- W- 4- -- z& x+-- X% 6- -- _. H> -- q% :> a> -- A= a% -- U% +* -- :. %- -- q% =, -- -- q 0= D& s; r, C& Ox+Gw+Wv+{x+@w+8, -- _> /x+-- V> 1' -- 6. Gz+-- -- C; .r+ r+l' M= -- 4y+Hz+O+ -- )> v& -- b* ,; -- r, 8- S$ .) )> @* 7y+-- -- `, k- -; t- 6. -- 1' !; -- #* n- -- m- c= -- 6* L; -- % o -- -- 3- r, -- $> }m+-- I# <' -- +' u. -- -- Sy+-- -- Uy+-$ -- 4y+.) -- +' ; -- P, b& Iz+N+ S$ -- l, zg.Jz+V' Kz+Q} yj bB U+ (& ^& >u+ - cz+ez+3r+oA.<$ ]) Lz+v# .% ~t+e& e& ; J) += += U+ Mz+Nz+Oz+^= ) ~> )$ )$ Ht+Sy+Pz+Qz+n& ;- ", +"q. e% B$ B$ e% e% e% e% '9+Rz+Sz+Tz+Uz+Vz+xq+1}.Wz+]0 Xz+v. = v. Yz+|m+Y_ C$ ff Y_ s. E$ E$ E$ (# h`.1q+= = 5q+Zz+Y_ E$ E$ y# T+ u5 `z+[q+ A+.A+= = = kq+/+.xq.+A+@A+N*+Hq+Yt+t% D$ #A+y' $A+~+ D$ D$ ~+ T+ C$ I %A+6q+= = 8+ Cr+Y_ D$ m% D$ )E.&A+v. = = 8+ Mq+`t+I !+ !+ L- Y_ u, ~> =! i> }> ~> G$ )~ c. j& .; c> c> 7> p, *A+Mw+=A+-- ;= q' -- -- )' e; 5. z$ a& Sw+-A+Tw+nu+-- 8, M- p% -- ;' o; -- -- a* O+ -- t- -- a& X% Gw+Tw+ky+-- S; 7y+-- D& p* -- f= 6w+-- F= ,; -- V> m' q% :. ;A+^ -- >A+!; -- d& '; -- v& P- -- :. )> -- q% F- !; -- y- r; -- r x& -- :. 4= -- a& 8- -- -- o; j- d& Y% O+ r, Pz+Xw+Qu+zg.7y+r& -- Nw+G& -- 8> q- -- r& Lz+-- -- l' *i.,A+Gw+a> -- 2- M- O+ -- 7 2- -- R< @; -- =; 'A+-- r& m@ Ww+)A+O+ -- Hv+/' q% 1' h; -- (x+C& -- c= 7= -- =$ A= -- /* W- -- % t- -- -- }z+f; -- q F= -- 6* P, -- ; c* -- -- o -- -- && 9' -- #> S; -- U$ (, -- ^u+Ss+Ss++; z$ -- a- $* ,w+!A+1p+L- U+ ;> c> D# (# 2+ ~A+5# w> c> E) ;- =- f& ~, g; 0/ D# e& (x+6w++= '$ 8' b0 Nz+d= d= ^) j& c. n& Kt+7z+Ht++& U) ;- ", +"'3 e% e% e% y# y# y# e% {A+]A+^A+RX =r+/A+(A+uq+_A+:A+r+5A+6A+7A+@A+8A+9A+0A+Zz+y# y# T+ y# y# T+ m% aA+J+ = = kq+bA+T+ ~+ D$ T+ T+ cA+dA+= = = 1q+eA+I ff L- ff D$ Y_ ;- n i> i> ~> c> c> c> c> c> c> c> Q> fq.Jt+F) :' -- v$ T* -- z$ bs+R; fA+-- )$ +x+3y+gA+cs+-- >+ 8= H; -- Ku+Mz+-- -- C; B> -- V> -- T- Xw+Hw+Ow+#, a& v$ C& q% _x+zg.a& z$ *+ -- r* X% -- 8> hA+q% 6. q 6. -- iA+C& -- r o@ -- /* d& -- ^ _' Y% -- 7= G# -- E& 7= -- r* ,; -- 6. @* -- a& Py+Y% -- /* {u+by+O+ -- -; Qs+-- h; .* )A+!- -- A- W+ q% % 8, -- /' x& -- -- jA+Zw+Z- *i.T* -- O= >' G# -- E= U]+-- f> kA+G# O+ %, -- Lx+B; Nw+Rw+f; -- 8> z$ !- #= G= -- 8, % O+ I@ *K.-- r* Ow+-- c+ .* -- Y% L; -- -- i* B- -- u. +; -- `- =- -- 6. l- -- -- H> -- -- Y= N, -- X) %= -- =- s, -- a> `w+lA+Kv+-- -- >$ mA+W) G&+w' ;* N@ 4 D# c> c> += :# {+ z- D# k& 4 2z+d. 1; [$ Y) <$ '$ K+ s- += += W) dx+Nz+d= d= >) U) X_ U) ]u+p i* L- U) ;- ", +":& (# y# nA+oA+H@ E$ T+ E$ T+ pA+%z+qA+rA+sA+tA+uA+vA+wA+xA+bv+v. v. yA+mp+Y_ Y_ ff Y_ e% B$ I zA+dA+v. lt+AA+`t+E$ E$ e% e% y# e% E$ E$ ~+ m% BA+= = = = CA+$d.DA+HS.EA+FA+GA+HA+Y>.0A+&s+IA+`z+ c> ;> c> <$ c> ~> ~> h> (> +y >w+]u+M= %= p% r, /* C= q- B> -- ae }) 4* q, Rx+F= {t+{t+b= M> <' (x+{t+{t+Tw+d. :; G> N, 4* NA+P> Sv+J) A' f; At+U$ 4. +* Rv+r; 7= &; 7z+2- T* a= OA+{t+8' @w+r; /U E& Y) B- 2- PA+M= X) r T* *- bs+QA+7= U$ |' =; X% Lx+M> p* 8= B- a% z& G> =; RA+X' G> B- #* `w+>+ -- cs+Lz+N+ {t+Ow+Xw+*- #- q, Lz+A- B> f; ,) @w+Qw+a> ae s> T' ]) RC cs+f; a' Av+At+L, SA+`- TA+z#.UA+VA+WA+XA+YA+ZA+/w+`A+]w+ B+W' L, ;= Xw+>' +x+}v+=; G) 3z+RA+:^+B- q, r T* V, _x+M> f; q= *K.M> @w+Bu+T* @* .B+M> V% 9+ f= +; n> f; Kx+(x+G> B- 7 :; G> /* X' G> cs+U- F= M> +B+7y+&; -- a& 3z+!w+K) &y N@ w' |- +& Y_ k& ;> D# .; .; D# D# D# D# Gu+W+ 6; q l@+<$ k& Ss+J) += c. Kv+d= J- J- J- @B+r@ (# X_ ph.p V* Y) n& n& ", +"#B+L>+$B+%B+&B+*B+y# E$ E$ y# H@ =B+-B+;B+>B+,B+'B+o@+%! )B+!B+]q+~B+v. jq+{B+Jz N@ I N@ ]B+^B+lt+l@ /B+Zg+N@ E$ E$ Y_ E$ E$ ff D$ D$ ^- (B+_B+|r+= = = = J+ :B+3f.l=+0u+ ~> ~> ~> c> ~> ~> ~> ;> !) mb E>+P> 1$ {x+3z+$* Ow+$* 4e 5. -, *& 6* Z' wu+0B+q* J) Cv+E* E& Hw+ug.Hw+J) 1$ J) m- Cv+1$ RC 2z+R< @v+aB+X) bB+Iw+5* Hu+t> I# Hw+Wv+iq+I# H Rx+cB+Uw+Du+H Uw+Qu+Vw+6w+Wv+8= Iw+|* Iw+|* K= =$ .x+vu+E* 4j.Ow+_> ay+Nw+Nw+dB+Qu+vu+1z+Qw+Cv+{x+Ow+eB+E* Nw+Ow+`w+;+ 6. f; Rw+8s+T* Oe.~w+1w+^w+F) R= A. 8= {x+fB+3z+gB+F) q* RC hB+Bt+el Bu+Av+r* iB+jB+Mw+kB+lB+mB+nB+i/.oB+pB+qB+rB+2* sB+tB+W uB+RC I# 6w+vB+j= (w+R= wB+Nw+xB+Cv+1$ eB+Hw+Rx+yB+6w+,v+Rx+zB+AB+BB+AB+E* I# CB+eB+H 6w+E* DB+EB+Xw+}) FB+GB+zg.Ow+3z+Vw+3w+Nw+3z+_x+Ow+$* _x+E* SA+fB+N T+ E$ e% e% d% vr+xq+PB+QB+,B+ZN (# t% RB+SB+TB+UB+VB+WB+5q+XB+YB+ZB+`B+ C+0A+8B+.C+%t.T+ E$ ~+ Y_ T+ y# E$ E$ T+ Y_ +C+@C+#C+$C+%C+%C+%C+%C+%C+%C+&C+*C+=C+Gy+2. +q++q++q+2. -C+;C+>C+,C+'C+)C+&C+!C+7q+~C+~C+{C+]C+^C+/C+Dy+(C+:& D$ ff D$ _C+:r+1r+:C+%C+ }> i> =! ~> ~> ~> c> |! v{ ;> D# D# D# v+-* Ot+v> 9' 4 e% Sw+Nw+M^ T+ EC+w$ J- J- J- cs+|- e% d% &v+p d= 3z+X_ d% ", +"FC+GC+HC+IC+JC+KC+@A+d% 4 U) 9! ~> DZ.LC+MC+NC+p$+T+ K^ {A+`,+{r+OC+C..PC+QC+`B ^q+-@ 5u+RC+SC+,D Y_ s. E$ D$ ff Y_ Y_ C$ E$ E$ L- L- ;} TC+TC+kS ]B+]B+]B+UC+b)+B!+u!+-$ VC+WC+XC+_1 o.WC+_1 54 T+ t% }> i> ~> ~> c> c> Q> l& (> p' *K.!A+/= Ht+*D+=D+tB+8C+Gt+/= z* p= }i &| !2 -G G&+-G ,u.z>+X= X= 8X.F* X= *K.&y J> '! l& , n] >^ X& z* :e w' 6w.A* ,^ {t+a' Z_ :e :e J> >^ J> :e }i @6 s#+A* s#+mb @6 @6 s#+A* @6 s#+s#+Z_ ,^ _G L, !2 !2 L, /U /U a' ,^ L, L, !2 -G -G X= -G !A+EC+%v+iC+W) Kw+i@+mY.-D+EC+W& X= X= u=+X= X= hu+/= vC+;} hu+At+&| P~ $~ 4 n& ., H, 8^ lW.;D+>D+,D+'D+'D+)D+!D+P@+~D+/= }%.m}+f- Zt+;* ;* u+u=+u=+-G |8 &| u+X= -G -G u=+z>+X= 9x+-G -G W& -G W& W& Fu+W& !A+st+u=+u=+X= z>+.u+hu+W& Fu+!A++q +q V& -t+Kx+mb /U -G /= vC+W& Fu+!A+Fu+hu+/= o5 Lt+&* {D+{= &v+3C+m}+_# :& N@ |- L- +& |- (= 7# w' Y_ j& ~> ~> ;> <$ 4 |! RC 1; o5 ~+ T+ Zv+.* Y_ E$ Y_ 5' d= d= X- 9' Y_ d% d% z>+&= J- E= U) 4 ", +"cc.]D+^D+D;.D;.ey /D+U) U) {+ ~> }> }> (D+_D+:D++sh.6D+ A+,z+EA+'C+Bp+7D+)z+.N.sz+^y Jy+wr+qf 5# 5# T+ m% Y_ Y_ ff Y_ E$ E$ T+ E$ Y_ ff ff E$ c. }> }> }> i> =! =! ~> D# j& u, {+ Y_ (= 1p+@< z>+m}+m}+&| w' L- E$ E$ Y_ ff !+ L- L- L- L- L- L- ff ff ff ff ff E$ c. j& j& k& {+ 4 I= &, U+ {+ U) d% I= I= n& u, ;- u, {+ ;- {+ {+ n& n& ;- ;- ;- ;- n& 4 4 4 I= &, n& &, 4 &~ &~ &~ &~ , l& l& l& , Y_ L- L- L- S+ +& |- !@ Z_ *K.-G u=+@- z* z* ~t+w' (= W{ |- |- :& (= w' 1p+7# w' H- n& k& .; D# ~> <$ D# o' 8D+9D+0D+aD+bD+bD+cD+dD+zm.!@ w' |- !+ I :& L- m% m% m% I +& !+ Y_ +& :& +& L- +& L- S+ (= (= (= eD+!@ w' |- |- |- N@ |- W{ W{ |- |- (= w' (= w' w' ;* ;* 1p+x{ I= -! $~ (= 7# (= W{ (= (= ~= w' eD+;* &| .u+vC+/= hu+&y 1p+I I +& I L- +& ff L- |- w' |- ff U+ ~> ~> ~> D# ;^ w& Ow+1p+Y_ Y_ Y_ b; Jv+x{ E$ X& a- Z- V' ~+ e% d% d% d% Wv+0= ; U) U) ", +"fD+2c gD+hD+T{.iD+jD+Q;+{+ ~> }> }> ~> {+ kD+lD+mD+C+wD+xD+yD+zD+AD+Jq+wr+qf qf N^ e% d% e% E$ E$ ~+ L- m% D$ L- D$ U) )$ }> }> i> i> i> i> }> }> }> K> m& C$ ff S+ ~= +& D$ E$ B$ d% d% r@ r@ x# r@ d% (# d% d% d% e% e% e% e% e% e% U+ += j& j& j& j& z- j& j& j& j& j& += <$ )$ )$ )$ )$ ~> ~> ~> ~> ~> ~> )$ )$ )$ )$ )$ )$ D# D# += D# D# D# j& k& k& k& k& k& k& j& j& z- d% (# (# B$ e% (# d% B$ E$ m% ff ff ~+ Y_ C$ E$ Y_ Y_ Y_ Y_ ff ff m% m% L- L- ff z- c> c> c> c> ~> i> }> 0q+ez+5r+BD+*z+fu+CD+pZ.q. !+ L- L- ~+ ff ff ff I !+ m% y# E$ s. e% E$ Y_ Y_ E$ d% e% d% T+ Y_ E$ T+ ff ff ~+ D$ Y_ E$ Y_ Y_ ff Y_ m% C$ ~+ Y_ C$ Y_ E$ D$ m% m% U) )$ c> U) T+ Y_ E$ ff ff ff E$ Y_ Y_ Y_ !+ (= 7# w' 1p+!@ +& !+ L- ff Y_ ff |- I I +& W{ |- :& L- U+ ~> ~> h> H, m@ el {+ E$ E$ E$ /= u@ DD+E$ ff Y_ (# E$ -, Y_ (# e% (# d% v+2y+&, U) ", +"Db.Wz+>z+ED+ap+FD+GD+HD+D# }> }> j& d% d% e% [q+>z+ID+N*+JD+KD+LD+Gy+Ys+MD+ND+:r+bv+)q+!C+OD+PD+QD+:X (= D$ Y_ ff D$ RD+SD+TD+T+ L- I m% m% ff Y_ /f+Yx+UD+:s+:z+VD+WD+ju+9r H- L- m% C$ m% ~= :& S+ I m% !+ C$ m% I C$ D$ C$ C$ C$ D$ T+ D$ Yt+sh.XD+ A+>r+b}.YD+;C+)z+0C.ZD+ms+ }> }> *> ! |a+`D+ E+.E++E+-, E$ E$ T+ s. d% d% d% r@ x# U) U) x# r@ d% e% d% d% d% (# e% B$ e% e% e% d% j& j& j& j& j& '$ j& j& j& += D# )$ )$ )$ )$ )$ )$ ~> )$ )$ )$ )$ )$ )$ )$ ~> )$ )$ )$ )$ n <$ D# += j& k& k& k& k& z- z- z- j& j& {+ e% e% (# e% e% d% d% d% d% d% d% r@ U) d% B$ e% e% E$ E$ Y_ Y_ ff L- !+ L- ff e% D# c> c> c> ~> i> i> i> gq+@E+vt+Vp+#E+6r+H- :& :& +& ff Y_ C$ m% D$ C$ I L- m% D$ T+ e% e% E$ y# y# E$ e% s. e% T+ Y_ E$ ~+ C$ D$ Y_ ff D$ E$ C$ m% m% C$ :& |- ff ff L- C$ ff L- L- e% c> ~> D# E$ ff Y_ ~+ Y_ Y_ T+ Y_ Y_ Y_ T+ m% m% Y_ !+ :& L- I I S+ m% I S+ +& ~= :& N@ L- I +& T+ T+ d% D# n& Mt+2y+mu+l& n] M^ (= (= ]v+2) s#+E$ Y_ +& R< AB+1p+L- |- N@ +& V' Rx+d% 4 ", +"m% $E+%E+&E+*E+=E+YD+-E+*@ }> '$ U) x# x# d% (# ;E+lD+>E+,E+1r+'E+)E+m%+@k !E+]z+~E+:r+v. v. = 8+ {E+]E+'f.^E+/E+(E+_E+Sq+:E+L- +& +& S+ I H, E+2E+xq+>r+3E+4E+xq.3K.5E+Iq+Jq+qf y# C$ D$ Y_ T+ E$ E$ l& {+ gq+=T.8r+:t+Rr+6E+xt+7E+@y+8E+s. e% e% B$ d% d% d% d% r@ d% d% e% B$ e% e% e% e% B$ e% e% e% y# e% e% e% c. j& j& j& j& j& j& j& e& n )$ )$ )$ ~> )$ )$ )$ )$ )$ )$ )$ )$ )$ i> ~> ~> ~> ~> )$ ~> )$ ~> D# z- z- k& k& k& k& '$ z- '$ j& += U) E$ E$ e% e% e% (# (# (# (# x# U) U) x# d% e% e% e% e% E$ Y_ Y_ Y_ ff L- L- D$ c. ~> ~> c> c> ~> i> }> i> 3r+xt+Nr+9E+0E+L- :& :& ~= ~= I D$ m% Y_ E$ D$ I m% ff ff Y_ e% e% e% B$ e% E$ Y_ s. s. s. e% y# ~+ D$ Y_ ff !+ ff Y_ ~+ Y_ ~+ C$ L- I I m% Y_ Y_ !+ ff E$ z- }> ~> j& Y_ ff T+ D$ ff Y_ E$ D$ D$ Y_ Y_ !+ m% C$ D$ :& +& m% m% !+ ff S+ +& S+ +& |- !@ :& +& L- m% +& L- Y_ }v+G- 8= o; <; Ry+<; G; aE+G& X- Uy+2y+by+&& {; q .= $> K; K; }z+j- Z' l& I= ", +"m% bE+cE+dE+eE+fE+bU gE+hE+U) d% r@ e% d% d% d% (# bg.iE+V{.{n.Gu (# e% e% e% d% (& jE+kE+&s+v. = v. v. J+ pq+lE++q+v. 8q+y= I I L- !+ D) mE+Nr+wv+ku+!t+xe+'$ }> -> 0q+[E+Ms+L= :& N@ ~= D$ !+ S+ I I !+ L- !+ ~+ L- L- m% D$ C$ ~+ Y_ D$ ~+ D$ L- !+ m% m% Y_ y# T+ t% ur+3K.tz+2E+nE+7D+)z+[q++A+=p+Dz+Kq+D$ T+ y# y# T+ ff Aj+oE+!t+oE+Es+pE+qE+rE+5r+sE+Aj+y# e% e% B$ d% d% (# (# d% d% e% e% e% e% y# E$ e% y# E$ e% e% E$ E$ E$ U) j& += j& j& j& e& ~> )$ )$ )$ )$ ~> ~> )$ )$ )$ )$ )$ )$ )$ i> =! *@ )$ ~> ~> ~> ~> ~> )$ )$ )$ += '$ k& k& z- z- z- '$ j& j& k& e% s. y# e% B$ (# d% d% d% x# U) U) U) U) U) d% d% (# e% s. T+ Y_ E$ ~+ ff D$ n& ~> ~> ~> ~> c> =! i> }> N/ tE+^t+[t+H, ff m% !+ C$ !+ m% C$ D$ ~+ e% B$ E$ T+ E$ E$ E$ e% e% E$ E$ e% Y_ ff D$ D$ D$ D$ ~+ D$ C$ Y_ Y_ L- I ff ff ff C$ ~+ m% ~= L- +& :& I L- L- !+ U) i> i> c> ;- S+ ff Y_ ff !+ C$ C$ C$ C$ Y_ D$ !+ D$ m% !+ :& I I L- Y_ C$ D$ !+ !+ L- +& ~= N@ +& Y_ ff m% ff Y_ |- ~( uE+vE+wE+xE+yE+zE+N= 6y+M- (x+xB+A= *; >; F- X- J- p h= J- d= AE+e% e% ", +"m% 7D+cE+kD+oy+BE+CE+;*. A+(# d% d% d% d% d% d% x# {A+uz+DE+|v+Kq+E$ (# e% E$ e% e% Y_ %% 8: -q+v. = = = = = v. lE+Wl.I N@ !@ L- Qm+EE+Vp+Vp+Nr+Xp+FE+N/ '$ ~> i> }> }> =! u, I I :& m% I C$ I !+ D$ !+ m% D$ ~+ m% D$ y# ~+ T+ T+ T+ Y_ ~+ s. ~+ T+ y# y# e% e% e% (# e% y# y# Iq+^y By+GE+tz+HE+3B+IE+JE+ur+Kq+C] H@ T+ y# $~ KE+sE+LE+UD+qE+ME+xv+7E+M9 `! l& (# d% r@ r@ d% d% r@ d% d% d% d% e% e% y# e% e% e% e% s. E$ E$ E$ I= '$ j& j& += D# *@ }> *@ )$ )$ ~> )$ )$ )$ *@ ~> ~> D# <$ .; [J }c+n' <$ ~> ~> i> i> )$ )$ )$ *@ )$ <$ j& z- z- k& '$ j& j& j& U) E$ ~+ E$ y# e% B$ E$ e% d% d% d% x# U) U) r@ (# E$ E$ E$ E$ Y_ t4 D$ Y_ Y_ :# ~> ~> i> i> =! i> }> }> }> Fr+Tp+NE+I L- L- m% D$ D$ I I L- ff ~+ r@ s. T+ E$ T+ Y_ E$ e% y# E$ e% e% e% T+ E$ T+ Y_ ~+ D$ y# D$ E$ ff S+ ff ff ff !+ S+ L- I L- !+ I S+ L- L- ff e% D# }> )$ ;> U) Y_ ~+ ff L- ff Y_ ff L- !+ C$ L- :& I L- S+ m% !+ I I Y_ ff !+ !+ !+ S+ |- +& |- +& L- L- ff Y_ E$ (= p, A%+A[+OE+PE+QE+RE+H> 3- v# Ow+b= 4= R, *; ]> 0% w$ SE+F- @= - u. I= e% ", +" < TE+qA+#9+UK UE+VE+WE+lD+,t d% U) d% d% U) d% d% e% qf XE+YE+kD+wr+x# B$ d% d% (# e% x# N@ ZE+a1 |A+v. = = v. @r+`E+(= m% +& S+ F+pz+ty+Vp+kp+M9 .F+dg+i> c. += =! i> i> }> i> d% I m% !+ ff L- I ff m% I m% C$ I I m% T+ Y_ Y_ ~+ D$ L- m% ~+ !+ m% E$ ~+ D$ E$ E$ s. y# y# ~+ T+ T+ T+ Y_ D$ `,+=p++A+By++F+@F+#F+,t Yt+,{ y# T+ -, !T.$F+%F+LE+vt+vt+Es+}y+., d% r@ x# r@ d% U) U) U) d% d% d% (# (# B$ e% e% e% e% E$ E$ E$ E$ y# U+ j& e& <$ }> }> }> )$ )$ ~> ~> n ~> e& D# u, U) e% Z! yj Ms+zv+&F+G9.t).)) h> D# ~> *@ )$ *@ i> i> i> D# j& z- '$ += += '$ e% E$ E$ Y_ E$ E$ e% e% e% d% d% d% U) U) U) U) d% e% e% E$ E$ E$ Y_ ff '! U+ ~> )$ ~> i> i> }> }> }> }> D# ., O~ m% Y_ T+ Y_ E$ E$ E$ C$ ff m% C$ Y_ T+ s. e% e% E$ E$ E$ Y_ E$ Y_ e% s. E$ E$ ~+ C$ m% Y_ C$ s. s. s. C$ m% D$ ff I I +& I S+ m% S+ :& +& ~= +& t4 z- }> i> ~> j& j& k& &~ ff S+ C$ Y_ E$ ff ff ff I I S+ !+ I Y_ I +& m% m% !+ I L- I I L- D$ !+ I m% ff Y_ e% e% X& ** a= *F+=F+-F+;F+>F+,F+@w+DB+aC+Eu+Kx+1p+Y) z= V' X& d% d% E$ X& (= d% d% ", +"D$ 3B+$p+H*+o@+:# UK 'F+)F+:q+r@ e% d% B$ B$ e% (# B$ e% Hq+YE+:D+#9+ y+x# d% s. E$ y# y# m% I ,y zA+!F+~F+{F+7I ]F+^F+'3 ~= ;^ /F+fu+(F+vv+_F+:F+Q> j& c> i> j& z- ~> =! =! }> }> }> d% Y_ !+ D$ m% D$ T+ ff m% D$ T+ C$ ~+ D$ T+ B$ (# (# E$ ~+ T+ E$ s. E$ y# s. y# ~+ s. y# B$ D$ Y_ T+ ~+ Y_ ff T+ D$ ~+ E$ y# D$ ,{ eE+N*+ms+`z+rz+`,+g- G@ v{ )$ }> *@ *@ n n <$ z- ;- z- .; ~> D# j& w! /s+}F+|F+Zx+%p+Hx+zs+Ls+Aj+AR.~j+o' '$ D# ~> )$ }> )$ <$ += j& j& += U+ E$ `= T+ ~+ T+ E$ y# e% e% d% d% x# U) U) U) U) U) d% d% e% e% e% E$ X> U) ~> ~> ~> }> }> }> }> }> e& d% Y_ D$ r. !+ C$ Y_ C$ E$ Y_ Y_ ~+ m% I +& m% I Y_ e% E$ E$ y# T+ C$ Y_ e% E$ s. E$ s. D$ m% Y_ D$ ~+ T+ E$ T+ ff Y_ ~+ C$ I C$ C$ L- ff D$ m% N@ +& I !+ E$ ~> i> ~> ~> u, j& i> += E$ +& m% L- m% ff m% ~= +& I L- ff L- C$ L- S+ m% ff m% !+ m% L- ff C$ ff L- I I m% ff e% E$ w' X= Z' Mt+ru+TV 1F+2F+3F+|E+4 L- @< z>+Kw+xu+&| 1p+L- U) U) U) &, d% d% d% ", +".0 IE+4F+5F+ y+U) x# p$+6F+7F+wr+e% U) e% r@ U) e% e% d% B$ }p+Dp+m^.8F+ y+d% d% U) B$ E$ Y_ D$ L- T+ ;& XN _B dU 9F+0F+aF+bF+cF+fu+dF+/y+ez+Y! R>+eE+Z% }> ~> u, U+ ~> ~> ~> ~> i> }> ~> z{ m% !+ !+ I L- ~= S+ L- C$ C$ Y_ D$ E$ s. T+ ~+ ~+ ff m% C$ D$ m% ~+ s. ~+ T+ y# T+ ~+ ~+ y# D$ ff ~+ Y_ Y_ ff y# ~+ E$ Y_ (# d% Y_ T+ T+ qf t% Yt+Yt+{A+P^ 2v.7t+eF+fF+gF+Jr+d% d% r@ d% d% U) U) U) U) x# U) d% B$ x# x# B$ (# e% E$ T+ s. E$ Y_ e% <$ *@ ~> D# c. ;- n& '$ ~> ~> <$ }> }> }> }> 8v.hF+iF+M9 jF+3u+Uq+kF+LE+4r+Rr+lF+Rr+.F+6t+^s+mF+h/ {+ n )$ )$ e& += += &, E$ Y_ `= E$ y# E$ e% e% e% (# d% r@ x# U) U) U) U) U) d% d% O^ d% e% d% D# i> i> }> }> }> }> D# &, E$ D$ C$ E$ T+ ~+ E$ y# e% e% e% e% e% e% y# E$ E$ E$ e% B$ (# e% B$ y# s. E$ d% E$ C$ Y_ ff I S+ T+ C$ ff Y_ C$ C$ !+ m% Y_ S+ S+ S+ m% ~= S+ :& +& :& :& :& +& k& }> ~> c> c> U+ D# ~> i> += e% L- S+ (= m% +& ~= I !+ I L- ff L- I +& ff L- S+ D$ Y_ ff Y_ E$ Y_ C$ D$ L- m% C$ Y_ Y_ E$ e% Y_ E$ E$ '! *N.lp+nF+oF+gy.U+ U) d% e% B$ (# d% d% X_ U) X_ d% d% d% d% "}; diff --git a/setup/linux/setup_image/setup.sh.in b/setup/linux/setup_image/setup.sh.in new file mode 100644 index 00000000..a6ec1187 --- /dev/null +++ b/setup/linux/setup_image/setup.sh.in @@ -0,0 +1,276 @@ +dnl `uname -m` quoting is a nightmare +dnl TTimo: You could do: `uname -a` +changequote([[, ]]) + +#! /bin/sh +# +# Product setup script +# +# TTimo - run in fink environement +# I am not entirely sure this is needed +# but in any case, it doesn't hurt +ifdef([[M4_OSX]], [[source /sw/bin/init.sh]]) +# Go to the proper setup directory (if not already there) +cd `dirname $0` + +# defaults +FATAL_ERROR="Fatal error, no tech support email configured in this setup" +# try to get root prior to running setup? +# 0: no +# 1: prompt, but run anyway if fails +# 2: require, abort if root fails +GET_ROOT=0 +XSU_ICON="" +# You may want to set USE_XHOST to 1 if you want an X11 application to +# be launched with root privileges right after installation +USE_XHOST=0 +# this is the message for su call, printf +SU_MESSAGE="You need to run this installation as the super user.\nPlease enter the root password." + +NULL=/dev/null +# See if we have the XPG4 utilities (Solaris) +if test -d /usr/xpg4/bin; then + PATH=/usr/xpg4/bin:$PATH +fi + +# Return the appropriate architecture string +DetectARCH() +{ + status=1 + case `uname -m` in + i?86) + echo "x86" + status=0;; + 90*/*) + echo "hppa" + status=0;; + *) + case `uname -s` in + IRIX*) + echo "mips" + status=0;; + *) + arch=`uname -p 2> /dev/null || uname -m` + if test "$arch" = powerpc; then + echo "ppc" + else + echo $arch + fi + status=0;; + esac + esac + return $status +} + +# Return the appropriate version string +DetectLIBC() +{ + status=1 + if [ `uname -s` != Linux ]; then + echo "glibc-2.1" + return $status + fi + if [ -f `echo /lib/libc.so.6* | tail -1` ]; then + if fgrep GLIBC_2.1 /lib/libc.so.6* 2> $NULL >> $NULL; then + echo "glibc-2.1" + status=0 + else + echo "glibc-2.0" + status=0 + fi + elif [ -f /lib/libc.so.5 ]; then + echo "libc5" + status=0 + else + echo "unknown" + fi + return $status +} + +DetectOS() +{ + os=`uname -s` + if test "$os" = "OpenUNIX"; then + echo SCO_SV + else + echo $os + fi + return 0 +} + +# Detect the environment +arch=`DetectARCH` +libc=`DetectLIBC` +os=`DetectOS` + +# Import preferences from a secondary script +if [ -f setup.data/config.sh ]; then + . setup.data/config.sh +elif [ -f SETUP.DAT/CONFIG.SH\;1 ]; then + # HP-UX and other systems unable to get LFN correctly + . SETUP.DAT/CONFIG.SH\;1 +fi + +# Add some standard paths for compatibility +PATH=$PATH:/usr/ucb + +# call setup with -auth when ran through su/xsu +auth=0 +if [ "$1" = "-auth" ] +then + auth=1 + [[shift]] +fi + +if [ "$auth" -eq 1 ] +then + # if root is absolutely required + # this happens if xsu/su execs setup.sh but it still doesn't have root rights + if [ "$GET_ROOT" -eq 2 ] + then + # NOTE TTimo: this causes the following error message in some cases: + # return: can only `return' from a function or sourced script + # BUT: in other cases, the return is legit, if you replace by an exit call, it's broken + return 1 + fi +fi + +# Feel free to add some additional command-line arguments for setup here. +args="" + +# Find the installation program +# try_run [-absolute] [-fatal] INSTALLER_NAME [PARAMETERS_PASSED] +# -absolute option: if what you are trying to execute has an absolute path +# NOTE: maybe try_run_absolute would be easier +# -fatal option: if you want verbose messages in case +# - the script could not be found +# - it's execution would fail +# INSTALLER_NAME: setup.gtk or setup +# PARAMETERS_PASSED: additional arguments passed to the setup script +try_run() +{ + absolute=0 + if [ "$1" = "-absolute" ]; then + absolute=1 + [[shift]] + fi + + fatal=0 + # older bash < 2.* don't like == operator, using = + if [ "$1" = "-fatal" ]; then + # got fatal + fatal=1 + [[shift]] + fi + + setup=$1 + [[shift]] + + # First find the binary we want to run + failed=0 + if [ "$absolute" -eq 0 ] + then + setup_bin="setup.data/bin/$os/$arch/$libc/$setup" + # trying $setup_bin + if [ ! -f "$setup_bin" ]; then + setup_bin="setup.data/bin/$os/$arch/$setup" + # libc dependant version failed, trying again + if [ ! -f "$setup_bin" ]; then + failed=1 + fi + fi + if [ "$failed" -eq 1 ]; then + if [ "$fatal" -eq 1 ]; then + cat <<__EOF__ +This installation doesn't support $libc on $os / $arch +(tried to run $setup) +$FATAL_ERROR +__EOF__ + fi + return $failed + fi + + # Try to run the binary ($setup_bin) + # The executable is here but we can't execute it from CD + # NOTE TTimo: this is dangerous, we also use $setup to store the name of the try_run + setup="$HOME/.setup$$" + rm -f "$setup" + cp "$setup_bin" "$setup" + chmod 700 "$setup" + fi + # echo Running "$setup" "$@" + if [ "$fatal" -eq 0 ]; then + "$setup" "$@" + failed="$?" + else + "$setup" "$@" 2>> $NULL + failed="$?" + fi + if [ "$absolute" -eq 0 ] + then + # don't attempt removal when we are passed an absolute path + # no, I don't want to imagine a faulty try_run as root on /bin/su + rm -f "$setup" + fi + return "$failed" +} + +# if we have not been through the auth yet, and if we need to get root, then prompt +if [ "$auth" -eq 0 ] && [ "$GET_ROOT" -ne 0 ] +then + GOT_ROOT=`id -u` + if [ "$GOT_ROOT" != "0" ] + then + + if [ "$USE_XHOST" -eq 1 ]; then + xhost +127.0.0.1 2> $NULL > $NULL + fi + try_run xsu -e -a -u root -c "sh `pwd`/setup.sh -auth" $XSU_ICON + status="$?" + # echo "got $status" + # if try_run successfully executed xsu, it will return xsu's exit code + # xsu returns 2 if ran and cancelled (i.e. the user 'doesn't want' to auth) + # it will return 0 if the command was executed correctly + # summing up, if we get 1, something failed + if [ "$status" -eq 0 ] + then + # the auth command was properly executed + exit 0 + elif [ "$status" -eq 1 ] + then + ifdef([[M4_OSX]], [[echo "You need to run the installer as root" ; exit 1]]) + # xsu wasn't found, or failed to run + # if xsu actually ran and the auth was cancelled, $status is 2 + # try with su + printf "$SU_MESSAGE\n" + try_run -absolute /bin/su root -c "export DISPLAY=$DISPLAY;sh `pwd`/setup.sh -auth" + status="$?" + elif [ "$status" -eq 3 ] + then + # the auth failed or was canceled + # we don't want to even start the setup if not root + echo "Please run this installation as the super user" + exit 1 + fi + # continue running as is + fi +fi + +# Try to run the setup program +# TTimo - setup.gtk only +# try_run setup.gtk $args $* +# status=$? +# if [ $status -eq 2 ]; then # setup.gtk couldn't connect to X11 server - ignore + try_run -fatal setup.gtk $args $* || { + # TTimo + echo "setup.gtk failed to start?" + echo "DISPLAY: $DISPLAY" + echo "Do you need to issue an xhost + command to let root access X11?" + ifdef([[M4_OSX]], [[echo "See http://www.qeradiant.com/wikifaq/index.php?OSX%20fink%20installer%20known%20issues for details"]]) + echo "The setup program seems to have failed on $arch/$libc" + echo + echo $FATAL_ERROR + status=1 + } +#fi +exit $status diff --git a/setup/linux/source_archive.sh b/setup/linux/source_archive.sh new file mode 100644 index 00000000..21ef8f68 --- /dev/null +++ b/setup/linux/source_archive.sh @@ -0,0 +1,22 @@ +mkdir gtkradiant-1.5.0 +cp GtkRadiant/* gtkradiant-1.5.0 +mkdir gtkradiant-1.5.0/radiant +cp -R GtkRadiant/radiant/* gtkradiant-1.5.0/radiant +mkdir gtkradiant-1.5.0/plugins +cp -R GtkRadiant/plugins/* gtkradiant-1.5.0/plugins +mkdir gtkradiant-1.5.0/libs +cp -R GtkRadiant/libs/* gtkradiant-1.5.0/libs +mkdir gtkradiant-1.5.0/include +cp -R GtkRadiant/include/* gtkradiant-1.5.0/include +mkdir gtkradiant-1.5.0/contrib +cp -R GtkRadiant/contrib/* gtkradiant-1.5.0/contrib +mkdir gtkradiant-1.5.0/tools +cp -R GtkRadiant/tools/* gtkradiant-1.5.0/tools +mkdir gtkradiant-1.5.0/setup +cp -R GtkRadiant/setup/* gtkradiant-1.5.0/setup +mkdir gtkradiant-1.5.0/games +cp -R GtkRadiant/games/* gtkradiant-1.5.0/games +mkdir gtkradiant-1.5.0/docs +cp -R GtkRadiant/docs/* gtkradiant-1.5.0/docs +tar -czf gtkradiant-1.5.0.tar.gz gtkradiant-1.5.0/ +rm -rf gtkradiant-1.5.0 diff --git a/setup/linux/wolf.cf b/setup/linux/wolf.cf new file mode 100644 index 00000000..4124be03 --- /dev/null +++ b/setup/linux/wolf.cf @@ -0,0 +1,8 @@ +# Linux build config +# wolf.cf +# +# includes core + wolfpack + +$DO_CORE=1; +$DO_GAME_Q3=0; +$DO_GAME_WOLF=1; diff --git a/setup/openurl.sh b/setup/openurl.sh new file mode 100644 index 00000000..d374a76a --- /dev/null +++ b/setup/openurl.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# use this script to customize the way the engine should open URLs + +for test_browser in mozilla netscape +do + browser=`which $test_browser` + if [ "x$browser" != "x" ] + then + $browser -remote "openURL($1,new-window)" || $browser "$1" + exit + fi +done + +xterm -e lynx "$1" diff --git a/setup/osx/build.sh b/setup/osx/build.sh new file mode 100644 index 00000000..25324c13 --- /dev/null +++ b/setup/osx/build.sh @@ -0,0 +1,13 @@ +#!/bin/sh +(cd loki_setup && patch -N -p0 < ../setup.patch) +(cd loki_setupdb ; make distclean ; ./autogen.sh && ./configure && make) +( +cd loki_setup +make distclean +./autogen.sh && ./configure && make +mkdir -p image/setup.data/bin/Darwin/ppc/glibc-2.1 +make install +find image -name setup -exec rm {} \; +) + +(cd GtkRadiant ; scons BUILD=release SETUP=1) diff --git a/setup/osx/radiant.info.m4 b/setup/osx/radiant.info.m4 new file mode 100644 index 00000000..64b8e0bd --- /dev/null +++ b/setup/osx/radiant.info.m4 @@ -0,0 +1,32 @@ +Package: radiant +Version: 1.M4_VER_MAJOR.M4_VER_MINOR +Revision: M4_REV +Description: GtkRadiant - level editor for Id technology games +DescDetail: << +You need to be running fink unstable to install GtkRadiant +Installation instructions: http://www.qeradiant.com/wikifaq/index.php?Installation%20on%20OSX +<< +DescPort: << +Add comments about dependencies here.. +<< +Maintainer: Timothee Besset +License: Restrictive +Homepage: http://www.qeradiant.com/ + +# Dependencies: +BuildDepends: gtk+2-dev, gtkglext1, libiconv-dev, scons, libxml, gtkglext1, libglade, glib, gtk+ +Depends: libglade, libxml-shlibs, gettext, libglade-shlibs, gtk+-shlibs, glib-shlibs, gtk+2-shlibs, dlcompat-shlibs, libxml2-shlibs, libiconv, glib2-shlibs, libpng-shlibs, x11, libpng3-shlibs, gtkglext1-shlibs, libglade-shlibs, glib-shlibs, gtk+-shlibs + +Source: http://zerowing.idsoftware.com/osx/GtkRadiant-osx-1.M4_VER_MAJOR.M4_VER_MINOR-M4_REV.tgz +Source-MD5: M4_MD5SUM + +PatchScript: echo "Dummy PatchScript" +CompileScript: sh ./build.sh + +# Install Phase: +InstallScript: << + mkdir -p %i/games/radiant-setup + mkdir -p %i/bin + cp GtkRadiant/M4_SETUP_TARGET %i/games/radiant-setup/M4_SETUP_TARGET + ln -s /sw/games/radiant-setup/M4_SETUP_TARGET %i/bin/radiant-setup +<< diff --git a/setup/quickstart.txt b/setup/quickstart.txt new file mode 100644 index 00000000..b087f080 --- /dev/null +++ b/setup/quickstart.txt @@ -0,0 +1,23 @@ +Some info for a good transition from Q3Radiant 202 to GtkRadiant: + +- shortcut keys are now in shortcuts.ini + the syntax has changed a bit, no spaces between the '=' + see shortcuts.sample +- no longer using the registry, settings are stored in radiant.ini and savedinfo.bin + in Radiant directory +- project settings templates: + the project settings file format has changed a bit. + it has version information now, and the setups come with a new quakev2.qe4 file + the project files are true templates, Radiant will read them and then store actual + project settings it uses in baseq3/scripts/user0.qe4 user1.qe4 etc. +- new compilation monitoring + GtkRadiant comes with a modified version of q3map that reports to GtkRadiant through + the network. You get the output of q3map in the console window. And the compiling + errors can be processed (currently it detects leaks) +- sleep mode: + before running Quake 3 Arena to playtest your maps, you can put Radiant to sleep: + manually with File > Sleep command + automatic if you compile and set "Activate sleep mode when running the engine" + +known issues: +- View n2&3 crashes on sleep mode diff --git a/setup/radiantgtkrc b/setup/radiantgtkrc new file mode 100644 index 00000000..c8b6e25b --- /dev/null +++ b/setup/radiantgtkrc @@ -0,0 +1,63 @@ +# ---------------------------------------------------------------------------------------- +# Basic style settings for GtkRadiant +# ---------------------------------------------------------------------------------------- + + + +style "gtk_radiant_menus" +{ + font_name = "Tahoma, Helvetica, Arial 8" +} + + + + +style "gtk_radiant_text" +{ + font_name = "Courier New, Courier, Monospaced, Fixed 9" +} + + + + +style "gtk_radiant_statusbar" +{ + font_name = "Tahoma, Helvetica, Arial 8" +} + + + + +style "gtk_radiant_tooltips" { + bg[NORMAL] = "#ffffe0" + font_name = "Tahoma, Helvetica, Arial 8" +} + + + +# -- TOOLTIPS +widget "gtk-tooltips" style "gtk_radiant_tooltips" + + + +# -- ALL MENUS +widget_class "*GtkMenu*" style "gtk_radiant_menus" + + + +# -- ALL TEXT (console) +widget_class "*GtkText*" style "gtk_radiant_text" + + + +# -- ALL LABELS (Statusbar) +widget_class "*GtkLabel*" style "gtk_radiant_statusbar" + + +# -- lists, trees, dropdowns, entries, spinners, sliders +widget_class "*GtkCList*" style "gtk_radiant_menus" +widget_class "*GtkCTree*" style "gtk_radiant_menus" +widget_class "*GtkCombo*" style "gtk_radiant_menus" +widget_class "*GtkEntry*" style "gtk_radiant_menus" +widget_class "*GtkSpinButton*" style "gtk_radiant_menus" +widget_class "*GtkAdjustment*" style "gtk_radiant_menus" diff --git a/setup/setup.bmp b/setup/setup.bmp new file mode 100644 index 0000000000000000000000000000000000000000..f9e7cc1dcdc0bda5f06e3fa07a5bda781ab8fb9c GIT binary patch literal 299934 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 z z3y3?(Edu!RCUdWN$&CX3Tn43szCtn<@q2Kbf_)Sn5#+eUfCaczh@b^w4H%#;JGPPoFe4)CKiAV`H86+Le~mCuyif&ffJC#|>@j za(`iXaHNngI?Yy1V6zbhK-G`_vj;pVimrA{8eHHmyF9%tMIZ>cMRH_FBC(;pw${bapotJtP+5|F4q8 zEy99Uj~`-fb_Go?0d}G7djfWUP`C{}FoCeu8==XM*%P+-VKNbmKW+~uEWVftp5vu% zH`-$jllMZfpuNx2=Zu?)1_n~DSl*W`gtDb*|3IodoE;oXmWRXrl@Rc+jO9nCi{m%? z$8Ui8%=k=xYCbVME#aRVUmjn+GqZMoU}h^hxEL)jq(;{Ir*CDZwiCk}g_)iF#CmCZ zeP!?Yz4yPo`S`_)Rh_cR~929tGU_R{Yy`V_TKKj{W;==Jt|dI^XFO1r~DQ6 zmC&93W0^o7WzGF>;`w0!%Ab>I2(uE)QdPBF&42=GC= zRQTmqtQxjJn724`1}uAR=CIoZoS}frW$x4A<%{_C-FjRGqhMFF)!_qNjL?Tm02!xA zw221q5TB9@V8ueOB|=DXVM`VMU?22JF%3LfgMEDdsQQR|un!&pEOH-rA1ZNIy9VIn z;YaRwtGj>>l9P|izd-y7eOcsBC~7}{@N4jwe;?rU_X*??VSpEzVNgbc%^+A7-xIRK z1nAplGD-ORY&Niu=w}92A`nQ0f}U`;H!~X+oY0e20wvruzz*brKm3ygngDBVDZuAm zakr8C5$YusxsUr4pA6p>>ixH63=X zw!5)Gb>ZB#6DO_!dFpUa9KCqr==tMEKt0%3=*YQaN6rEK6M|~qIeNb4WMkbmGRf7g z?Y&039;MwIv=c1w1)cbfe14n9XL5S*_8Y;w%WHOf%^ntRi_-&{9d5IowcF})+uUBr z>JmJ`==7Of0i#=BAGvSyhfn}4zMwr2L;p8Afq#^u7J>ah!sbs{{RxX71t8w%jOlD) zy(6r(1aJVFeIgp@j^_Ny{$QpU$(2&2O0F^jrOTtS(r~yq6dxGrADbSWStw7==SQY1 zGfVxGbJ2mZ`0yOq?;l&9SiU{G@nB?rH#NB6&rc;sSJRU>lcSrZ*}d}Y?d<5v^u~kL zz32B|f4TMaqq&{;@{=2$(zJVEHax!3KYuqpdw=rLx1YTI{>s=?7wxr0**R(02Xm3c zFb`Z^G_B(7s=-X@{nu!lVk1OC3-aOuw1nPrMyVq() zLxA8C68l2cHyYpord}qV>KHf$>AJf*F{)Ka{RXh#ObfiG2C47^Rn&gr;*<8c#J(c; zLA^pSfx0%8TGQ5ns*ld!)85tDO22k^r_dQ70TomqG$r9L7u-Nn)tZ10RM`vs1isSU z(QedH`5vd`n`a%H>@N7U;{2He~S*dR9+;m%P) zo<;QxH<^=g0B$cAqf*Wy)C2rOB)Nfj%jq&YaRPYsB+z;Sl>Zn# zL4zBI031L#{fw@t!O1a##JBq6R$m5!*#?pi1(c*A&V3cJ3!q_az|G?DZ=)!7Y;znd}COte~oLU>0 z-k4k8Ti$*=zOu=BBqYQ(LL=t>oBNxV$jFc7I{};qB+2?7jcx!;iiL2S`sW z+cKlh(o}eIH8OKEJ^yg_-j`2be7Q0?(^lVd?#z#1Up~kEM=sg>*Ps8vYk_^SRX|Z8 znVcP?LTQxa)X>lr&4>m@3`<|s@BL2a%1@=znPkMDi3z03Eeh}vPi#vGf3_vKW)Dk+ zU)iO!LlVGe=Z8JCMsx%ePd>nx>H$}}LA|0Ia~T+6jl^`AgyAd&C}`66YCGCmRkXXr z1=-wK#XdqG0~|a8Pb9Lm#F5CzWt4nj?}JZ_0@ihPBrSBbX*6spbyrWj5c(4PKwj$n zN;q6#UpPn<*(sHOI6xKt{9nSRxON4eqoM7ywg--6=+#OUg|7;s{JU*dG=G78i+Nx3 zQPh5(;xGS)KNdZ3CZ@6^HYBct= zwOu`R^6IH~FTZ<20{<}fPaQu}^X~D>=g!n#z0}^N?Q?h?{s6#t`vVTI58zAeTO4i+ z@|~lU^$xem?)oOd3Y3#hu&=YZv^E#T{#H*=?+Ri{^tpmW`ugmlK1H$<3go4LzfXaG zFlh@UO%(E2IginiFu9_r2ej{Sg_8bwE|}~OrAy)50PxS0hSH^xL}4gh8A~DJOTd4O z?EU=6OksS!GDC=etuVEa8(%0*uMW;_Os#HDt?iDl-X2=oDbL?3%-t%^-6_oOB}Q&0 zMm8(+dvjY)x1M}_^U24Xk3PEp`djq-_4H4MMi;yzbHT~A!02Xt;_m$2Pai#bJCH7l zUeX8dor8z?ji-J%hhItg;tG-dK^f-x^G>scfxMH0bmkctE-*%u){zAUxu(ifJ%fAN z@h8OXiAPIColVI0W0$f;r5lQu5E38louk`g6drqv8GE12@3PqfUN55_CF;474UApD zWr%wz8NoilSFkTE{e8LLN2R-_wY3GmenUf@AQp}Fk^=xhATQN&q3SD@J|JCUU#R^O z{`dwP8q}?Aoo$+~woVA(a|HXyd=MhsA_~|A9}xdUy7OTt0N9uC2m2@(9m?nq)qOR>&m9Fut)%tEd86CKx%;CU3hnC zg{X8xtnL}Hi?|!wJ~qCtQ{(rz>u^cFL&LpyIV0u%S@6Cu_m6^lCNuVQcC@#t z+Pk%VHkX6Rz({<6@AlYSLYcRs(t~=N(_(j_;hStO$Y65{BFXmZy*6d>GZqY5~n9B~qcY*o1*P_XZg=?VW?{TxvS?Ebhjl=Q_i0AEn5 z5KNWAnNln_KwFO_62CN(txSOZ7=FK@DN_5&lS`$^C9q$aSu9O04bN|kEZ!Vl+#FrH zS)N@RUD=tu`JjJxD|ch3y!aqHbtgHon;O40w0M8<&g=CDAC0ZtTiSho`^D#jOSi)# z%bv=jcX-u3y6meg_uqK1@%YOJPhNMoYov$gFc1Icb5}O@{z2;hyg<&8Ygb662eR3r z{(c}oTr5`djBd;i_UFsFep|fMQ(Q*bM@SK?ViSUWHY6lPmE4?cTHuUtpPlS9>A`h? z=)A!IoJrC2cs)YyV+A;@T*e;eDE7QfsKi_YG?4gqgD{rC4md)cW*3NPo`QFzJOuEO z`zZV@P`p7R*ykv+_!vi{nE530?2CIv`qO`aQNx-xbyr&l@CWi$!uhXg`HCPs$kdgn zm)roMFI9a|5AhO4L0&LZtEkL{db>K|0Kgxs1mH6U5d!`w0B|rk07uA)GN53elX3WL{xeR(Z}@N z-IoP~_Z__Zcvm!m>RQ-0qG7u-vgg1jEU_F%|DfZRD8TPHe>P^f z#XTNFb0dSRAO=@aq$BEdjvu`yBtGK)7zFSYw;so1t+iLXR81F73UMz@evShE?;N{y z=H$gQC!1=o>9pNO1A3mGer|1=4x`QCaC;qydjms>(P@{zWd=4>)S_^zCxnMMxjKU69X77LXEgW-UGA)3zF zEA#0%RaI!7B8!jRD8&#*ND6&+uaxF20JY0(z;#dYwUgjHroYqSvyrPK{Qqoqx!bu+ z82jk?MiCK^vL4_7pKeV@a}$`a1L=~a#FxNF?#n)1;1O&h{lPwJ0K|D*e026W(DrNb z40g1tHT%>K@HMIqN$?d#^ebkMf_>yWsFxU*;$BL8ℑrExG12tvjfEti{pS)=J(H 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%0AKg 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 literal 0 HcmV?d00001 diff --git a/setup/setup.patch b/setup/setup.patch new file mode 100644 index 00000000..ca85c13b --- /dev/null +++ b/setup/setup.patch @@ -0,0 +1,665 @@ +Index: CHANGES +=================================================================== +RCS file: /cvs/cvsroot/loki_setup/CHANGES,v +retrieving revision 1.86 +diff -u -r1.86 CHANGES +--- CHANGES 2003/09/24 04:02:47 1.86 ++++ CHANGES 2003/10/24 18:12:36 +@@ -25,6 +25,10 @@ + Stephane Peter (Codehost) - Tue Feb 11 20:33:32 PST 2003 + * Added the 'environment' tag to store the values of + environment variables. ++TTimo (qeradiant.com) - Mon Feb 10 12:17:08 CET 2003 ++ * Added SETUP_COMPONENT_PATH env to run scripts ++TTimo (qeradiant.com) - Sun Jan 26 23:36:06 CET 2003 ++ * Ported over our subcomponent code + Ryan C. Gordon (icculus.org) - Tue Jan 14 23:46:31 EST 2003 + * Committed initial MacOS X patches to CVS on behalf of Jon C. + Stephane Peter (Codehost) - Fri Dec 6 16:31:27 PST 2002 +Index: Makefile.in +=================================================================== +RCS file: /cvs/cvsroot/loki_setup/Makefile.in,v +retrieving revision 1.25 +diff -u -r1.25 Makefile.in +--- Makefile.in 2003/05/02 22:54:49 1.25 ++++ Makefile.in 2003/10/24 18:12:36 +@@ -134,19 +134,19 @@ + endif + @if [ -d image/setup.data/bin/$(os)/$(arch)/$(libc) ]; then \ + cp setup image/setup.data/bin/$(os)/$(arch); \ +- strip image/setup.data/bin/$(os)/$(arch)/setup; \ ++ strip -r image/setup.data/bin/$(os)/$(arch)/setup; \ + $(BRANDELF) -t $(os) image/setup.data/bin/$(os)/$(arch)/setup; \ + cp uninstall image/setup.data/bin/$(os)/$(arch); \ +- strip image/setup.data/bin/$(os)/$(arch)/uninstall; \ ++ strip -r image/setup.data/bin/$(os)/$(arch)/uninstall; \ + $(BRANDELF) -t $(os) image/setup.data/bin/$(os)/$(arch)/uninstall; \ + cp xsu image/setup.data/bin/$(os)/$(arch)/$(libc); \ +- strip image/setup.data/bin/$(os)/$(arch)/$(libc)/xsu; \ ++ strip -r image/setup.data/bin/$(os)/$(arch)/$(libc)/xsu; \ + $(BRANDELF) -t $(os) image/setup.data/bin/$(os)/$(arch)/$(libc)/xsu; \ + cp setup.gtk image/setup.data/bin/$(os)/$(arch)/$(libc); \ +- strip image/setup.data/bin/$(os)/$(arch)/$(libc)/setup.gtk; \ ++ strip -r image/setup.data/bin/$(os)/$(arch)/$(libc)/setup.gtk; \ + $(BRANDELF) -t $(os) image/setup.data/bin/$(os)/$(arch)/$(libc)/setup.gtk; \ + else \ +- echo No directory to copy the binary files to.; \ ++ echo image/setup.data/bin/$(os)/$(arch)/$(libc): No directory to copy the binary files to.; \ + fi + + install-image: all +@@ -171,7 +171,7 @@ + cp setup.gtk $(IMAGE)/setup.data/bin/$(os)/$(arch)/$(libc); \ + strip $(IMAGE)/setup.data/bin/$(os)/$(arch)/$(libc)/setup.gtk; \ + else \ +- echo No directory to copy the binary files to.; \ ++ echo $(IMAGE)/setup.data/bin/$(os)/$(arch)/$(libc): No directory to copy the binary files to.; \ + fi + + # Pretty LPP-specific +@@ -181,7 +181,7 @@ + strip $(IMAGE)/bin/$(os)/$(arch)/check; \ + cp check.glade $(IMAGE)/misc/; \ + else \ +- echo No directory to copy the binary files to.; \ ++ echo $(IMAGE)/bin/$(os)/$(arch): No directory to copy the binary files to.; \ + fi + + # Copy loki_uninstall and the required files +@@ -196,7 +196,7 @@ + cp $$file $$path; \ + done; \ + else \ +- echo No directory to copy the binary files to.; \ ++ echo $(IMAGE)/bin/$(os)/$(arch): No directory to copy the binary files to.; \ + fi + + install-loki_uninstall: loki_uninstall +@@ -211,7 +211,7 @@ + cp $$file $$path; \ + done; \ + else \ +- echo No directory to copy the binary files to.; \ ++ echo $(IMAGE)/loki_uninstall/bin/$(arch)/$(libc): No directory to copy the binary files to.; \ + fi + @if [ -d $(UPDATES) ]; then \ + rm -rf $(UPDATES)/bin-$(arch)-$(UNINSTALL_VERSION)/; \ +Index: configure.in +=================================================================== +RCS file: /cvs/cvsroot/loki_setup/configure.in,v +retrieving revision 1.41 +diff -u -r1.41 configure.in +--- configure.in 2003/08/13 21:48:29 1.41 ++++ configure.in 2003/10/24 18:12:36 +@@ -161,7 +161,7 @@ + LIBDL="" + ARCH=`uname -p` ;; + powerpc-apple-darwin*) +- STATIC="-static" ++ STATIC="" + RDYNAMIC="-Wl,-Sn" + BSTATIC="" + BDYNAMIC="" +@@ -242,16 +242,6 @@ + CARBONLIBS="$CARBON_LIBS $LIBINTL" + fi + ) +- +-case "$target" in +- powerpc-apple-darwin*) +- if test -f /sw/lib/libintl.a; then +- LIBINTL="/sw/lib/libintl.a /sw/lib/libiconv.a" +- LIBS="$LIBS $LIBINTL" +- GUILIBS="$GUI_LIBS $LIBINTL" +- CARBONLIBS="$CARBON_LIBS $LIBINTL" +- fi +-esac + + AC_CHECK_LIB(gpm, Gpm_Open, USE_GPM=yes) + +Index: copy.c +=================================================================== +RCS file: /cvs/cvsroot/loki_setup/copy.c,v +retrieving revision 1.69 +diff -u -r1.69 copy.c +--- copy.c 2003/09/04 02:29:03 1.69 ++++ copy.c 2003/10/24 18:12:37 +@@ -347,7 +347,7 @@ + *slash = '\0'; + } + push_curdir(dir); +- if ( run_script(info, buf, 0, 1) == 0 ) { ++ if ( run_script(info, buf, NULL, 0, 1) == 0 ) { + const char *target = xmlGetProp(node, "target"); + if ( target ) { + char targetpath[PATH_MAX]; +@@ -719,7 +719,7 @@ + if ( ! update(info, _("Running script"), 0, 0, current_option_txt) ) + return 0; + } +- return(run_script(info, script, -1, 1)); ++ return(run_script(info, script, dest, -1, 1)); + } + + ssize_t copy_node(install_info *info, xmlNodePtr node, const char *dest, +@@ -808,6 +808,7 @@ + { + ssize_t size, copied; + char tmppath[PATH_MAX]; ++ const char *component_dest; + + size = 0; + while ( node ) { +@@ -894,6 +895,58 @@ + } + current_component = NULL; /* Out of the component */ + } ++ } ++ /* Parse subcomponents */ ++ else if (!strcmp(node->name, "subcomponent")) { ++ const char *name, *version; ++ xmlNodePtr child; ++ name = xmlGetProp(node, "name"); ++ if (!name) ++ log_fatal(_("SubComponent element must have a name")); ++ version = xmlGetProp(node, "version"); ++ if (!version) ++ { ++ log_warning(_("SubComponent doesn't have a version")); ++ version = strdup("noversion"); ++ } ++ child = node->childs; ++ while(child) ++ { ++ if(!strcmp(child->name, "option")) ++ { ++ /* only run if it has been actually selected for install */ ++ const char *install; ++ install = xmlGetProp(child, "install"); ++ if (install && !strcmp(install,"true")) ++ { ++ /* add this subcomponent as a standard component */ ++ current_component = add_component_entry(info, name, version, ++ (strcmp(xmlGetProp(child, "install"), "true") != 0) ? 0 : 1, NULL, NULL ); ++ if(xmlGetProp(child, "path")) ++ { ++ /* if the path's been changed, use the path it was changed to */ ++ component_dest = xmlGetProp(child, "path"); ++ } ++ else ++ { ++ /* if the path hasn't been changed, install to the default path */ ++ component_dest = xmlGetProp(child, "default_path"); ++ } ++ // TODO: verify the install location ? ++ copied = copy_node(info, child, component_dest, update); ++ if ( copied > 0 ) ++ { ++ size += copied; ++ } ++ copied = copy_tree(info, child->childs, component_dest, update); ++ if(copied > 0) ++ { ++ size += copied; ++ } ++ } ++ } ++ child = child->next; ++ } + } else if ( ! strcmp(node->name, "environment") ) { + const char *prop = xmlGetProp(node, "var"); + if ( prop ) { +Index: gtk_ui.c +=================================================================== +RCS file: /cvs/cvsroot/loki_setup/gtk_ui.c,v +retrieving revision 1.87 +diff -u -r1.87 gtk_ui.c +--- gtk_ui.c 2003/10/15 00:08:11 1.87 ++++ gtk_ui.c 2003/10/24 18:12:37 +@@ -143,6 +143,7 @@ + static GladeXML *setup_glade = NULL; + static GladeXML *setup_glade_readme = NULL; + static GladeXML *setup_glade_license = NULL; ++static GladeXML *setup_glade_subcomponent = NULL; + static int cur_state; + static install_info *cur_info; + static int diskspace; +@@ -159,6 +160,7 @@ + void setup_destroy_view_readme_slot(GtkWidget*, gpointer); + static yesno_answer gtkui_prompt(const char*, yesno_answer); + static void gtkui_abort(install_info *info); ++static int gtkui_dir_prompt (install_info *info, const char *message, char *path); + + static int iterate_for_state(void) + { +@@ -1216,6 +1218,189 @@ + } + } + ++/*************** subcomponent *********************/ ++ ++void setup_button_subcomponent_browse(GtkWidget *widget, gpointer func_data) ++{ ++ GtkWidget *entry = (GtkWidget *)func_data; ++ xmlNodePtr node = gtk_object_get_data(GTK_OBJECT(widget), "node"); ++ char *widget_data = gtk_object_get_data(GTK_OBJECT(widget), "data"); ++ char path[PATH_MAX]; ++ strncpy(path, gtk_entry_get_text(GTK_ENTRY(entry)), PATH_MAX); ++ ++ gtkui_dir_prompt(cur_info, widget_data, path); ++ ++ if(path[strlen(path)-1] != '/') ++ strcat(path, "/"); ++ ++ gtk_entry_set_text(GTK_ENTRY(entry), path); ++ ++ xmlSetProp(node, "path", path); ++} ++ ++void setup_subcomponent_toggle(GtkWidget *widget, gpointer func_data) ++{ ++ int state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); ++ GtkWidget *entry = gtk_object_get_data(GTK_OBJECT(widget), "entry"); ++ GtkWidget *button = gtk_object_get_data(GTK_OBJECT(widget), "button"); ++ xmlNodePtr node = gtk_object_get_data(GTK_OBJECT(widget), "node"); ++ ++ if(state) ++ { ++ gtk_widget_set_sensitive(GTK_WIDGET(entry), TRUE); ++ gtk_widget_set_sensitive(GTK_WIDGET(button), TRUE); ++ xmlSetProp(node, "install", "true"); ++ xmlSetProp(node, "path", gtk_entry_get_text(GTK_ENTRY(entry))); ++ } ++ else ++ { ++ gtk_widget_set_sensitive(GTK_WIDGET(entry), FALSE); ++ gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); ++ xmlSetProp(node, "install", "false"); ++ xmlSetProp(node, "path", ""); ++ } ++} ++ ++void subcomponent_update_entry(GtkWidget *widget, gpointer func_data) ++{ ++ xmlNodePtr node = gtk_object_get_data(GTK_OBJECT(widget), "node"); ++ char *path = malloc(strlen(gtk_entry_get_text(GTK_ENTRY(widget)))+2); ++ strcpy(path, gtk_entry_get_text(GTK_ENTRY(widget))); ++ ++ if (path[strlen(path)-1] != '/') ++ strcat(path, "/"); ++ ++ if(path) ++ xmlSetProp(node, "path", path); ++} ++ ++void setup_button_subcomponent(GtkWidget *widget, gpointer func_data) ++{ ++ xmlNodePtr node; ++ xmlNodePtr child; ++ GtkWidget *window, *frame, *w, *vbox, *hbox, ++ *check, *entry, *button, *sep; ++ const char *text; ++ char name[256]; ++ int count=0; ++ int install=0; ++ ++ install_info *info = (install_info *)func_data; ++ ++ setup_glade_subcomponent = glade_xml_new(SETUP_GLADE, "subcomponent_dialog"); ++ glade_xml_signal_autoconnect(setup_glade_subcomponent); ++ window = glade_xml_get_widget(setup_glade_subcomponent, "subcomponent_dialog"); ++ frame = glade_xml_get_widget(setup_glade_subcomponent, "subcomponent_frame"); ++ ++ w = glade_xml_get_widget(setup_glade_subcomponent, "subcomponent_button_cancel"); ++ gtk_widget_hide(w); ++ ++ gtk_widget_realize(window); ++ gtk_widget_realize(frame); ++ gtk_container_foreach(GTK_CONTAINER(frame), empty_container, frame); ++ ++ w = gtk_vbox_new(TRUE, 2); ++ gtk_container_add(GTK_CONTAINER(frame), w); ++ gtk_widget_show(w); ++ ++ gtk_object_set_data(GTK_OBJECT(window), "data", w); ++ ++ node = info->config->root->childs; ++ while(node != NULL && strcmp(node->name, "subcomponent")) ++ node = node->next; ++ if (!node) ++ log_fatal(_("subcomponent element not found")); ++ child = node->childs; ++ // subcomponent options ++ while(child != NULL) ++ { ++ if(!strcmp(child->name, "option")) ++ { ++ if(!strcmp(xmlGetProp(child, "install"), "true")) ++ install=1; ++ else ++ install=0; ++ ++ vbox = gtk_vbox_new(FALSE, 2); ++ gtk_box_pack_start(GTK_BOX(w), vbox, TRUE, FALSE, 0); ++ gtk_container_set_border_width(GTK_CONTAINER(vbox), 5); ++ gtk_widget_show(vbox); ++ ++ if(count) ++ { ++ sep = gtk_hseparator_new(); ++ gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0); ++ gtk_widget_show(sep); ++ } ++ ++ text = xmlNodeListGetString(info->config, child->childs, 1); ++ parse_line(&text, name, sizeof(name)); ++ ++ check = gtk_check_button_new_with_label(name); ++ gtk_box_pack_start(GTK_BOX(vbox), check, FALSE, FALSE, 0); ++ if(install) ++ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), TRUE); ++ gtk_signal_connect(GTK_OBJECT(check), "toggled", GTK_SIGNAL_FUNC(setup_subcomponent_toggle), NULL); ++ gtk_object_set_data(GTK_OBJECT(check), "name", name); ++ gtk_object_set_data(GTK_OBJECT(check), "node", child); ++ gtk_widget_show(check); ++ ++ hbox = gtk_hbox_new(FALSE, 2); ++ gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, FALSE, 0); ++ gtk_widget_show(hbox); ++ ++ entry = gtk_entry_new(); ++ //gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, FALSE, 0); ++ gtk_container_add(GTK_CONTAINER(hbox), entry); ++ if(!install) ++ gtk_widget_set_sensitive(GTK_WIDGET(entry), FALSE); ++ gtk_signal_connect(GTK_OBJECT(entry), "focus_out_event", GTK_SIGNAL_FUNC(subcomponent_update_entry), NULL); ++ gtk_object_set_data(GTK_OBJECT(entry), "name", name); ++ gtk_object_set_data(GTK_OBJECT(entry), "node", child); ++ gtk_widget_show(entry); ++ ++ if(xmlGetProp(child, "path")) ++ { ++ gtk_entry_set_text(GTK_ENTRY(entry), xmlGetProp(child, "path")); ++ } ++ else if (xmlGetProp(child, "default_path")) ++ { ++ gtk_entry_set_text(GTK_ENTRY(entry), xmlGetProp(child, "default_path")); ++ } ++ ++ ++ button = gtk_button_new_with_label("..."); ++ gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0); ++ gtk_object_set_data(GTK_OBJECT(button), "data", gtk_entry_get_text(GTK_ENTRY(entry))); ++ gtk_object_set_data(GTK_OBJECT(button), "node", child); ++ gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(setup_button_subcomponent_browse), entry); ++ if(!install) ++ gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); ++ gtk_widget_show(button); ++ ++ gtk_object_set_data(GTK_OBJECT(check), "entry", entry); ++ gtk_object_set_data(GTK_OBJECT(check), "button", button); ++ ++ count++; ++ } ++ child = child->next; ++ } ++ gtk_widget_show(window); ++ //gtk_window_set_modal(GTK_WINDOW(window), TRUE); ++} ++ ++void setup_button_subcomponent_cancel(GtkWidget *widget, gpointer func_data) ++{ ++ GtkWidget *window = glade_xml_get_widget(setup_glade_subcomponent, "subcomponent_dialog"); ++ gtk_widget_hide(window); ++} ++ ++void setup_button_subcomponent_ok(GtkWidget *widget, gpointer func_data) ++{ ++ GtkWidget *window = glade_xml_get_widget(setup_glade_subcomponent, "subcomponent_dialog"); ++ gtk_widget_hide(window); ++} ++ + /********** UI functions *************/ + + static install_state gtkui_init(install_info *info, int argc, char **argv, int noninteractive) +@@ -1533,6 +1718,12 @@ + } + } + } ++ } else if ( ! strcmp(node->name, "subcomponent") ) { ++ GtkWidget *widget = gtk_button_new_with_label(xmlGetProp(node, "name")); ++ gtk_box_pack_start(GTK_BOX(options), GTK_WIDGET(widget), FALSE, FALSE, 5); ++ gtk_object_set_data(GTK_OBJECT(widget), "data", (gpointer)xmlGetProp(node, "name")); ++ gtk_signal_connect(GTK_OBJECT(widget), "clicked", GTK_SIGNAL_FUNC(setup_button_subcomponent), (gpointer)info); ++ gtk_widget_show(widget); + } + node = node->next; + } +@@ -1719,6 +1910,63 @@ + gtkui_idle(info); + } + ++static int dirname_loop; ++static int prompt_ret; ++ ++void store_dirname_slot(GtkFileSelection *selector, gpointer user_data) { ++ char *aux; ++ char *selected_dirname = (char *)user_data; ++ GtkWidget *parent; ++ ++ parent = gtk_widget_get_toplevel (GTK_WIDGET(selector)); ++ aux = gtk_file_selection_get_filename (GTK_FILE_SELECTION (parent)); ++ if (strlen(aux)) ++ strcpy(selected_dirname, aux); ++ ++ dirname_loop = 0; ++} ++ ++void abort_slot(GtkFileSelection *selector, gpointer user_data) { ++ dirname_loop = 0; ++ prompt_ret = 0; ++} ++ ++static int gtkui_dir_prompt (install_info *info, const char *message, char *path) ++{ ++ GtkWidget *selector; ++ char *aux; ++ ++ /* Create the selector */ ++ selector = gtk_file_selection_new(message); ++ ++ gtk_file_selection_set_filename(GTK_FILE_SELECTION(selector), path); ++ gtk_file_selection_hide_fileop_buttons (GTK_FILE_SELECTION(selector)); ++ ++ gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION(selector)->ok_button), ++ "clicked", GTK_SIGNAL_FUNC (store_dirname_slot), path); ++ ++ gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION(selector)->cancel_button), ++ "clicked", GTK_SIGNAL_FUNC (abort_slot), path); ++ ++ /* Display that dialog */ ++ ++ gtk_grab_add(selector); ++ gtk_widget_show (selector); ++ gtk_grab_remove(selector); ++ ++ dirname_loop = 1; ++ prompt_ret = 1; ++ ++ while (dirname_loop==1) ++ gtk_main_iteration(); ++ ++ aux = gtk_file_selection_get_filename (GTK_FILE_SELECTION(selector)); ++ ++ gtk_widget_destroy(selector); ++ ++ return prompt_ret; ++} ++ + int gtkui_okay(Install_UI *UI, int *argc, char ***argv) + { + extern int force_console; +@@ -1769,7 +2017,3 @@ + #endif + + #endif +- +- +- +- +Index: install.c +=================================================================== +RCS file: /cvs/cvsroot/loki_setup/install.c,v +retrieving revision 1.124 +diff -u -r1.124 install.c +--- install.c 2003/09/27 01:52:38 1.124 ++++ install.c 2003/10/24 18:12:38 +@@ -429,7 +429,7 @@ + } + prop = xmlGetProp(node, "command"); + if ( prop ) { /* Run the command */ +- if ( run_script(info, prop, 0, 1) != 0 ) /* Failed, skip */ ++ if ( run_script(info, prop, NULL, 0, 1) != 0 ) /* Failed, skip */ + continue; + } + prop = xmlGetProp(node, "lang"); +@@ -1084,7 +1084,7 @@ + log_fatal(_("XML: 'require' tag doesn't have a mandatory 'command' attribute")); + } else { + /* Launch the command */ +- if ( run_script(info, prop, 0, 0) != 0 ) { ++ if ( run_script(info, prop, NULL, 0, 0) != 0 ) { + /* We failed: print out error message */ + text = xmlNodeListGetString(info->config, node->childs, 1); + if(text) { +@@ -1224,7 +1224,7 @@ + return 0; + + /* Launch the command */ +- return run_script(info, txt, 0, 0) == 0; ++ return run_script(info, txt, NULL, 0, 0) == 0; + } + } + return 1; +@@ -1431,7 +1431,7 @@ + + if (GetPreUnInstall(info) && info->installed_bytes>0) { + snprintf(path, sizeof(path), "sh %s", GetPreUnInstall(info)); +- run_script(info, path, 0, 1); ++ run_script(info, path, NULL, 0, 1); + } + + if ( file_exists(info->install_path) ) { +@@ -1448,7 +1448,7 @@ + /* Do not run scripts if nothing was installed */ + if ( info->installed_bytes>0 ) { + for ( selem = opt->pre_script_list; selem; selem = selem->next ) { /* RPM pre-uninstall */ +- run_script(info, selem->script, 0, 1); ++ run_script(info, selem->script, NULL, 0, 1); + } + } + +@@ -1465,7 +1465,7 @@ + } + if ( info->installed_bytes>0 ) { + for ( selem = opt->post_script_list; selem; selem = selem->next ) { /* RPM post-uninstall */ +- run_script(info, selem->script, 0, 1); ++ run_script(info, selem->script, NULL, 0, 1); + } + } + +@@ -1483,7 +1483,7 @@ + } + if (GetPostUnInstall(info) && info->installed_bytes>0) { + snprintf(path, sizeof(path), "sh %s", GetPostUnInstall(info)); +- run_script(info, path, 0, 1); ++ run_script(info, path, NULL, 0, 1); + } + + if ( uninstall_generated ) { +@@ -1896,7 +1896,7 @@ + if ( ! restoring_corrupt() ) { + script = GetPreInstall(info); + if ( script ) { +- exitval = run_script(info, script, -1, 1); ++ exitval = run_script(info, script, NULL, -1, 1); + } + } + return exitval; +@@ -1909,7 +1909,7 @@ + if ( ! restoring_corrupt() ) { + script = GetPostInstall(info); + if ( script ) { +- exitval = run_script(info, script, -1, 1); ++ exitval = run_script(info, script, NULL, -1, 1); + } + } + return exitval; +@@ -1930,7 +1930,7 @@ + /* Run the command and set it to "true" if the return value is ok */ + str = xmlGetProp(child, "command"); + if ( str ) { +- cmd = run_script(info, str, 0, 0); ++ cmd = run_script(info, str, NULL, 0, 0); + xmlSetProp(child, "install", cmd ? "false" : "true"); + log_debug("Script run: '%s' returned %d\n", str, cmd); + } else { +@@ -2370,13 +2370,16 @@ + } + + /* Run some shell script commands */ +-int run_script(install_info *info, const char *script, int arg, int include_tags) ++int run_script(install_info *info, const char *script, const char *dest, int arg, int include_tags) + { + char script_file[PATH_MAX]; + int fd; + int exitval; + char working_dir[PATH_MAX]; + ++ if (!dest) ++ dest = ""; ++ + /* We need to append the working directory onto the script name so + it can always be found. Do this only if the script file exists + (to avoid problems with 'sh script.sh') +@@ -2412,13 +2415,16 @@ + "SETUP_CDROMPATH=\"%s\"\n" + "SETUP_DISTRO=\"%s\"\n" + "SETUP_REINSTALL=\"%s\"\n" +- "export SETUP_PRODUCTNAME SETUP_PRODUCTVER SETUP_INSTALLPATH SETUP_SYMLINKSPATH SETUP_CDROMPATH SETUP_DISTRO SETUP_REINSTALL\n", ++ "SETUP_COMPONENT_PATH=\"%s\"\n" ++ "export SETUP_PRODUCTNAME SETUP_PRODUCTVER SETUP_INSTALLPATH SETUP_SYMLINKSPATH SETUP_CDROMPATH SETUP_DISTRO SETUP_REINSTALL SETUP_OPTIONTAGS SETUP_COMPONENT_PATH\n", + info->name, info->version, + info->install_path, + info->symlinks_path, + info->cdroms_list ? info->cdroms_list->mounted : "", + info->distro ? distribution_symbol[info->distro] : "", +- info->options.reinstalling ? "1" : "0"); ++ info->options.reinstalling ? "1" : "0", ++ dest ++ ); + + if ( include_tags ) + fprintf(fp, + +Index: install.h +=================================================================== +RCS file: /cvs/cvsroot/loki_setup/install.h,v +retrieving revision 1.73 +diff -u -r1.73 install.h +--- install.h 2003/09/24 04:02:48 1.73 ++++ install.h 2003/10/24 18:12:38 +@@ -369,7 +369,7 @@ + otherwise the install path is passed as a command line argument. + 'include_tags' indicates if the SETUP_OPTIONTAGS should be set in the script header. + */ +-extern int run_script(install_info *info, const char *script, int arg, int include_tags); ++extern int run_script(install_info *info, const char *script, const char *dest, int arg, int include_tags); + + /* returns true if any deviant paths are not writable */ + char check_deviant_paths(xmlNodePtr node, install_info *info); +@@ -399,4 +399,3 @@ + + + #endif /* _install_h */ +- diff --git a/setup/shortcuts.ini.sample b/setup/shortcuts.ini.sample new file mode 100644 index 00000000..48bc47f6 --- /dev/null +++ b/setup/shortcuts.ini.sample @@ -0,0 +1,95 @@ +; TTimo: a sample file used as an example + +; command mapping for QERadiant +; +; syntax is as follows +; Command name = keystroke +; +; use +alt, shift or ctrl to modify the keys (you can combine them) +; i.e. +; EntityColor = k+ctrl +; +; the special keys are as follows +; +; UP = Cursor up +; DOWN = Cursor down +; LEFT = Cursor left +; RIGHT = Cursor right +; SPACE = space +; BACKSPACE = back space +; ESCAPE = escape +; END = end +; INSERT = insert +; DELETE = delete +; PAGEUP = page up +; PAGEDOWN = page down +; TAB = tab +; RETURN = return (enter) +; F1..F2 = f1 .. f12 +; COMMAN = , +; PERIOD = . +; PLUS = + +; MULTIPLY = * +; SUBTRACT = - + +[Commands] +EntityColor=K +CameraForward=UP +CameraBack=DOWN +CameraLeft=LEFT +CameraRight=RIGHT +CameraUp=A +CameraDown=Z +CameraAngleUp=A+shift +CameraAngleDown=Z+shift +CameraStrafeRight=right+shift +CameraStrafeLeft=left+shift +ToggleGrid=0 +SetGrid1=1 +SetGrid2=2 +SetGrid4=3 +SetGrid8=4 +SetGrid16=5 +SetGrid32=6 +SetGrid64=7 +DragEdges=E +DragVertices=V +ViewEntityInfo=N +ViewConsole=`+ctrl +ViewTextures=T +SurfaceInspector=S +CloneSelection=D+ctrl +DeleteSelection=DELETE +UnSelectSelection=ESCAPE +CenterView=SPACE +ZoomOut=SUBTRACT +ZoomIn=PLUS +ZZoomOut=SUBTRACT+ctrl +ZZoomIn=PLUS+ctrl +UpFloor=PAGEUP +DownFloor=PAGEDOWN +ToggleClipper=X +ToggleRealtime=R +EntityList=L +MapInfo=M +Preferences=P +ToggleCamera=C +ToggleConsole=~ +;ToggleView=V+ctrl +;ToggleZ=Z+ctrl +ConnectSelection=K+ctrl +Brush3Sided=3+ctrl +Brush4Sided=4+ctrl +Brush5Sided=5+ctrl +Brush6Sided=6+ctrl +Brush7Sided=7+ctrl +Brush8Sided=8+ctrl +Brush9Sided=9+ctrl +ShowDetail=D+alt +MakeDetail=M+alt +NextLeakSpot=PAGEUP+ctrl +PrevLeakSpot=PAGEDOWN+ctrl +FileOpen=O+ctrl +FileSave=S+ctrl +Exit=X+ctrl +NextView=TAB+ctrl diff --git a/setup/win32/HOWTO b/setup/win32/HOWTO new file mode 100644 index 00000000..af0ddb64 --- /dev/null +++ b/setup/win32/HOWTO @@ -0,0 +1,32 @@ +msi installer HOWTO for Radiant 1.5 +----------------------------------- + +Requirements: + +- all game packs from https://zerowing.idsoftware.com/svn/radiant.gamepacks/*Pack/trunk/ must be present in the ./games/ folder +- the Radiant manual from https://zerowing.idsoftware.com/svn/radiant.gamepacks/Q3Rad_Manual/trunk/ in ./docs/manual/ +- msitools from http://zerowing.idsoftware.com/files/radiant/developer/1.5/msitools.zip. + + +Building a new installer .msi file: + +- Unzip msitools.zip to ./setup/win32/ and run MsiVal2.Msi (it automatically installes to C:\Program files\MsiVal2). +- Copy all files from C:\Program files\MsiVal2 to the ./setup/win32/ folder. +- You might have to edit the .xml files in ./setup/win32/components/ +- Create a file "aboutmsg.default" in ./include containing a single line: + Official qeradiant.com build by +- Open a command-prompt in ./ and run "makeversion.py" +- Build the GtkRadiant solution in 'Release' configuration. +- If you want to create a .msi installer for GtkRadiant, go on with the next step. To create a game pack + .msi installer you have to modify the file "build.py". +- Open a command-prompt in ./setup/win32/ and run "build.py". This will create the installer + file GtkRadiant-1.5.0-[YYYY]-[MM]-[DD].msi + + +Additional information: + +If you want to build the msi-Installer wiht Python 2.4 you have to modify msiquery.vcproj ('Release' configuration) +located in ./setup/win32/msi/ (include paths and linker libraries) and rebuild it + + +Written by Shaderman and Topsun in Sept 2006 \ No newline at end of file diff --git a/setup/win32/HOWTO_outdated b/setup/win32/HOWTO_outdated new file mode 100644 index 00000000..775887a8 --- /dev/null +++ b/setup/win32/HOWTO_outdated @@ -0,0 +1,195 @@ +Howto add new game packs to the InstallShield setup +--------------------------------------------------- + +Background Info +--------------- +The files that IS uses are pretty much all text files, in the template/ directory +we have a special copy of these files that make up the IS project. + +The copy in the template/ directory has files which contain string like +<> which are replaced with actual values whenever the setup.pl script +is used. + + +TTimo +8/5/2002 + +Disclaimer: I'm writing that as I am building the RTCW game pack. It is possible +that it is fairly outdated when you read it, but my guess is it can be a useful +reference document. +Update: using this for JKII support, made sure everything is still valid + +Hydra +5/6/2002 + - Updating for Halflife build, added a bit more info in places. + +NOTE: you need cygwin installed (http://www.cygwin.com) to run setup.pl tools +(Base installation + perl) + +You also need UUIDGEN.exe in your path. It's normally in the +"Visual Studio .NET\Common7\Tools" directory + +Some experience with IS and our particular way of handling it is expected. +The following information is DENSE, read everything + +- select a base name for the pack (which we will use in various variables): +WOLF +- make a RELEASE build of GtkRadiant. +- run setup.pl to generate the IS directories + e.g. ./setup.pl 'c:\\my documents\\Source\\GtkRadiant' q3.cf + (the directory contains GtkRadiant, Src, etc..) +- start WorkDir/GtkRadiant.ipr, this is an half-templated setup we can +easily work on to add new stuff + +- go to file groups and start adding new groups: +Wolf Executable Files + will hold the editor modules and binaries (map compiler, bspc) +Wolf Media Files + will hold sample files and editor files: + maps, models, additional textures, shader scripts, entities.def, project template +set the destination directory for those files: +Wolf Executable Files + goes in the game pack directory: DIR_GAMETOOLS_WOLF +Wolf Media Files + goes straight into the Wolf path: DIR_GAME_WOLF + +- start feeding stuff in those file groups + make sure all those files start from the prefix we are working with + (C:\home\Id in my case) + +- add a component: + Wolf (Wolf Executable Files) + -- Wolf editing media (Wolf Media Files) + NOTE: make sure that you put the file groups in those components you created! + +- go to add the pack to setup.rul: +add new globals +// Wolf +NUMBER DO_GAME_WOLF_BOOL; +STRING szDIR_GAME_WOLF, szDIR_GAMETOOLS_WOLF; + +- in OnFirstUIBefore +define any strings you use, e.g. szJKII and DEFAULTJKIIDIR; +add template for wolf pack inclusion +DO_GAME_WOLF_BOOL = <>; + +- copy 'game pack #1' code and paste is as a 'game pack #2' +start renaming the code and updating it +(use the registry key for default path lookup if possible) +Wolf setup doesn't leave an install path, we will hardcode to +C:\\Program Files\\Return To Castle Wolfenstein +and look for the binary +(note, this is by far the part with the most things to do, +read carefully the game pack code, replace everywhere it's needed, +put the right 'BACK' code etc.) + +NOTE: the 'if (nResult = BACK) then' code gets more complicated as new packs are added +sadly, it's not possible to store labels into variables for jumps +the next 'nResult = BACK' in non-gamepack code needs to be updated too + +- in Dlg_SdStartCopy, add summary for Wolf operations + if (DO_GAME_WOLF_BOOL == 1) then + ListAddString(listStartCopy,"Return To Castle Wolfenstein folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAME_WOLF,AFTER); + ListAddString(listStartCopy,"Return To Castle Wolfenstein mapping package folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAMETOOLS_WOLF,AFTER); + endif; + +- in OnMoved, add generation of the game file for Wolf + if (DO_GAME_WOLF_BOOL == 1) then + if (CreateDir(TARGETDIR ^ "games")< 0) then + // Report the error; then abort. + MessageBox ("Unable to create directory " + TARGETDIR ^ "games", SEVERE); + abort; + endif; + if (CreateFile(nvFileHandle, TARGETDIR ^ "games", "wolf.game")< 0) then + // Report the error. + MessageBox ("CreateFile " + TARGETDIR ^ "games" + "\\wolf.game failed.", SEVERE); + abort; + endif; + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, " gamename=\"wolf\""); + WriteLine(nvFileHandle, " enginename=\"quake3\""); + CloseFile(nvFileHandle); + endif; + +- configure the setup so that the new components are installed by default: + in 'Setup Types' tab, check the new components + NOTE: do that in BOTH types, specially Custom + +- once all those changes are done, we are gonna validate the update.. +save and exit IS +make a backup copy of setup/win32/WorkDir ($ cp -R WorkDir/ WorkDir-backup) + +- templatize WorkDir/ with the setup.pl +$ ./setup.pl 'c:\\home\\Id' -template template-gen +Configured for base GtkRadiant directory: 'C:\\home\\Id' +Building a template version of WorkDir into template-gen/ +Copy files... +Templating UUID... +Processing 'C:\\home\\Id' into '<>' + +- check with a recursive diff that it's all good (Araxis Merge!) +Files template/Component Definitions/Default.cdf and template-gen/Component Definitions/Default.cdf differ +Files template/Component Definitions/Default.fgl and template-gen/Component Definitions/Default.fgl differ +Files template/File Groups/Default.fdf and template-gen/File Groups/Default.fdf differ +Files template/Script Files/Setup.rul and template-gen/Script Files/Setup.rul differ +Files template/Text Substitutions/Setup.tsb and template-gen/Text Substitutions/Setup.tsb differ + +newly added, the file groups files + +- copy over template-gen/ into template/ +$ cp -R template-gen/* template/ + +- cvs update in the template dir, add new files etc. + + + +- edit template/Component Definitions/Default.cdf in a text editor to configure the 'include in build' templates + search for [Wolf] and change the line: + INCLUDEINBUILD=NO + to: + INCLUDEINBUILD=<> + + search for [Wolf\Wolf Editing Media] and change the line: + INCLUDEINBUILD=NO + to: + INCLUDEINBUILD=<> + + that is, main is always installed, and the editing media only in full + NOTE: IS 6.0 has the nasty habit of changing order in Default.cdf on each save .. makes things harder + +- edit 'sub configure_tree' in setup/win32/setup.pl: + copy from an existing game pack code and adapt + there's a general boolean, and a full setup boolean + (search and replace affects Setup.rul and Default.cdf) + + add the corresponding items to + # set default config + + add a corresponding output string under + print " DO_CORE : $DO_CORE\n"; + +- search for '# set default config' and add the new default entry (default to 0) +as well as the verbosity below + +- create a new .cf file + +# ET setup + +# output dir name +$SETUP_DIR = 'Setup-ET'; + +$DO_CORE = 1; +$DO_GAME_ET = 1; + + +- build a new setup using a .cf file. + +e.g. + +./setup.pl 'C:\\home\\Id' wolf.cf + +- load up Setup-Wolf/GtkRadiant.ipl into IS and build it! diff --git a/setup/win32/TODO b/setup/win32/TODO new file mode 100644 index 00000000..78e9b423 --- /dev/null +++ b/setup/win32/TODO @@ -0,0 +1,74 @@ +TODO list for 1.2 setup: + +- do we need some DO_* variables for the plugins too.. +- does the nightly do some safe checks while installing? + (i.e. query the GUID of the existing full installation we expect to find) + +short explanation / design doc: + +------------ + +Any setup is made from a core and some game packs +In the template -> buildable setup process, we give a few parameters to +customize things: + +- Put only the binaries or put everything +- put / don't put the editor core +- put / don't put any game pack + +To do this, we mostly rely on a search replace in the template code: + +DO_* are variables telling wether are not a given component is INCLUDED into +the setup. We have currently: + +DO_CORE: editor core content + (ex: the central editor binary) +DO_CORE_FULL: include the full setup content related to the code + (ex: the GtkRadiant manual) +DO_GAME_Q3: include binaries for Q3 +DO_GAME_FULL_Q3: media for the Q3 game pack + +we search and replace for '<>' '<>' etc. +more such variables will be .. DO_GAME_WOLF_BOOL, DO_GAME_Q1_BOOL etc. +the *_BOOL are meant to be replaced by '1' or '0' +we use equivalents *_BOOL_YESNO that are replaced by 'Yes' and 'No' +(those are used for 'include in build setting') + +------------- + +Installation paths: +the variables DIR_* are holding the install paths for core or game packs +DIR_CORE is the install path for the core +DIR_GAME_Q3 is the Q3 directory +DIR_GAMETOOLS_Q3 is the subdirectory path choosen below Q3 dir to install + game specific stuff + +When the setup executes, it must rely on the stuff included in the setup to +prompt the user with the right questions (where do I install the editor, +where do I install the Q3 game pack?). This should be tempered by prior +selection of what and whatnot to install if necessary. There should always +be a 'Default' path that allows installing quickly without answering any +difficult questions. + +------------- + +Nightly builds: + +this was added afterwards. nightlies are intended to be an upgrade +over an existing installation it only holds binaries + +also adds some specific component and file groups for the media stuff +that may have changed since the full setup + +how does it work? +a matter of 'include in build' configuration again +if nightly is on, the *_FULL_* variables will be disabled +and the *_NIGHTLY_* ones will be enabled +Component Definitions/Default.cdf is the critical file in this operation +added the following: +DO_CORE_BOOL_FULL_YESNO +DO_GAME_Q3_BOOL_FULL_YESNO +DO_GAME_WOLF_BOOL_FULL_YESNO +those three are to disable the media we only put in full setup +DO_NIGHLY_BOOL_YESNO +this is for the specific additions of the nightly setup diff --git a/setup/win32/all.cf b/setup/win32/all.cf new file mode 100644 index 00000000..98518195 --- /dev/null +++ b/setup/win32/all.cf @@ -0,0 +1,16 @@ +# Core + all game packs + +# output dir name +$SETUP_DIR = 'Setup-All'; + +$DO_CORE = 1; +$DO_GAME_Q3 = 1; +$DO_GAME_WOLF = 1; +$DO_GAME_JKII = 1; +$DO_GAME_STVEF = 1; +$DO_GAME_HALFLIFE = 1; +$DO_GAME_SOF2 = 1; +$DO_GAME_ET = 1; +$DO_GAME_JA = 1; +$DO_GAME_Q2 = 1; +$DO_GAME_HER2 = 1; diff --git a/setup/win32/bin/bspc.exe b/setup/win32/bin/bspc.exe new file mode 100644 index 0000000000000000000000000000000000000000..7385e1eb0613afdff900d935574ad91c4d338786 GIT binary patch literal 331776 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

Fqt@@j)w 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 Cc#s+Z literal 0 HcmV?d00001 diff --git a/setup/win32/bin/msvcr70.dll b/setup/win32/bin/msvcr70.dll new file mode 100644 index 0000000000000000000000000000000000000000..2ef28fdd09615ae550152eb6c7f6ad9af4fd3e8a GIT binary patch literal 344064 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Ξ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

2Sctz2q?Grln$)6%6TYu=IMJtWhULo#WuASj{a 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 TB(%D;m@uY-#in=T5C}7M_S3 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

Qkb z_d$o6O$`L6NdL9So#~4PpKQNy@h{T8S9tpLMNYaJZ?mqE<8Qr}tS_4I)HQ6}2HYNm zkB6%1qR+GUw{6VB^%tHVX0D6w-rnfAe=A96je>Mh`H@leq4&xDeL6f<7Zp9$-Kp}H zw5KxT99?wc>|ymYD~W$kP0Dpqa{nur-)a#1#|YhiNb+5{BtswmpU((8n!^3yDsOew zPknIv5*m#X_CvGRSI2y?!}m*t{YPi^Lm}(*`n=|i0-Kp=R@e^>J$AT{WHULwW6V|h zqcgSrmgwodhy5=+Fvh$;s_q+`yW}js-zUr)8`K~9*=Q8)k&*VBKQ6gHis60Gc|QfG zGabgu`lAu+p1)eW9QOx8Tg&qPi2pz^uwgV=KLP3Lq3Qu?JKkoJ?Ij5u^pNRDtwgpH zwuO(ZV)anWweYI}0e zy+|Q$A41s#V|_GoyvYyo4AMWVJNW3Mpt$uaH^-CxHP10wAHDtZ>gePu91n(_DAPwP zO#{+9TqnnSiBp+AYI(mb(c&<^e=Qt8sfPiInV#Xg_#Qq#3inQSFhExmG=>d+iTfWR z-!;|%iI+(GPN+1(_As|}12k()$D8B24F#LoF(uakjkrE$Mx#GzKgu4AAzCrrDXwp) zb~t~Or?DaOx_R~5=YgcYnNz13qN*v^zpZs7{`Q%+2GVt8T~swl``t19k|9z`oR>2| z9s9><%q%lRoiuGL`_CcmU+rm^?(C0UBL*G1r+*F7A3ZJBV$ELptqi@4@7FPt@>nhQ z61!*jTaAI(R?Xj}#op59xgW~L|L?Bay`Gc&anFWpu~#xX)<5!}gwsu* zw{&CYT|cW)&=cQpV!k|o+>IS^MrUDBnhCb|7VPWBF7)kn$MvWUw%rQDyRny-NvL-iUw5;!IVtddlZ%uZ?)l(g&H`GbMe)4L#CR??2e_-~Lo*hkcp4BFh~%$X*%)e)jMx>Ro5|ZtsmmMvTmMgbL7!vd-@+;y0Tq0 zF1EdqSc&6t?qlDs><#Jn;@}5leZi-nYOK>S^B-~JM&R-vd}7sDLH58VWh)ZTsb!ng z*lmZOkM7l-w1=~0;cD!|wqH)9dyxG#<}*@b4P!q3I(ya#ug`w&qQ)w%n9vwCmgHZR z&vs$&byoe}=Ey{xp7hMS3%l`+bEk)nB)-YdTROAjuL;D)TBQ9Y7wqfI9@bxF)MVd) z{Y@%Fo!RB9Mozi6iQL~@^`f{F+b_<@RP%8?P9I#fuM=BxA#u5A4N33w5_Mwj`_B(; zZwb#Yz&{LL6?bG;4KB_;wZ#S7jj!T6vc{cs4~*SM?6TK=JF@$BpB&+|mgLWU!**cZ zd~QB1Y(xCdD)#Qcb~~b*-(iL=UVnQ@OMAA<(UQ@;6w-bNzh&FAS67{`JbY#XPH+3p zyFIJoG4kGlYA0-$ylYWqw@xxT`}_depBLWmQ)LBp7rmQqll`@>6sfXWCTX*RJU`(2 zO#4u*!ru6Fpg5wD>>tCA@ha@fI}c=9@|y@_AkCY+27l%S{U}dUY}t0fgUbj zw|XDLj$8L;PT@szd}UT6h8;c7XJUN{X|J=sur1OH4%XNDbQ^{9`+RL_mioN;dd0|u z^l!7ip=POD<~fzlEb-6ddwi4hwZ&B1k_HkF+aGMBbkgH<3&whqM;>7}4;1rI$*`^hY4e@Q)e9=M&8N{)}jik5n5bn!hnn*3NxiMautH#nwuf6hG-SJe9=vM0JZ?dZ%4e}ig}T>81E zRiFEVNPJ3a*c$254`=2+&YX<>Ns+fyOP8&xe==+}{(lQ&QH!dj_N(-Ss4MZWq&B`v zdU*M;#VRDNyEV72!6{{xKa zulRClX6fhks=tO}JEA83r*y?DspoWE60aWe_#e{z8&B@k>1eqN_xMctKImx_P9J@W6KL^OtHqKHNtk-7Bsay zm+m|@ru3kUwEyy^mOLr%(XXpxvPYBr%`Lgo1>G&$e~cyjgK2KLFRc{YEcBQ{`Xga; z%N^;}x_2wz`jdFT#0%Nd!|L{hvU#L_f|izxQtzPLP6lm=Kk$C!5oyQH8{OT{ll}!< z_}5SC(jR{L@i);AFaPa_Z!EE#o3(zhz3X|lomKDz8>8nem-AoG7mOO&aCE#@IlB1p z(yl?iyDfalyfx)$w#rO*aq>dR%J{IdZZ_p8?C#>8+6R**+&nhrC?G55(7UJ&7H{OQ z2Q7#wM}`(>-xY68l5q3dl%tl!b57D#z82!D+xIN@mZMR=COu*uHq&`-%29jYR}+m6 zr&`q7M-JKYpd2l9d8hTNPcohNrW{RKc56WQu>yj#0a{UGl&rM0Ra(1-g?F@`vVmH%SSZ7pDU49bK`D9lllKwlO0obw&kJJ^thJ ze1XVf8JQ=i0>!K=xNMc2Ao;Q($-I7D1zON$#}hrZ%@S_joC;JL-P$@{^BxzYVl?XH?K}5N=clU5OZgP z#r9XrH#_#NMBg(~QdS%l(Rp?%QDmDt7gn6vX@L%(b9v-ei4Ij)OFgsK(Rp_&(TmT5 zog=S_EE49{ye&?wM9Vv&q_NJsB-}hamB>^5H5hMH2aN9u&@>l)#A6m6qstUn#^-li-zx1{;{}Q zq6FUSt5VhSBJ))kE^<# zvXjn}REG?mR!yz=yxk(n_Po}J$93q~`_CihJd2QU^Cs1ykR9(2XgpYHal2)6hnbr7 zNY7xJ(URwTBp=8;O7-aKit`C~yyr=}Ov$-#JEIoWLMBbMo$vliwpdz~$1b?>3+0A~E!tv$MGHJ~gi}Rn_ri5;4M7Fbp4w4fK z={!)4=%wT5C-;?NEPmaX?b)HY5k>A1xsNN{Ac-UMLN%eZrAutgBx#b$wVhN}4{t(? zSMFK3%wdm&nKx;0xh0=CH(j~G1_=b)N+k|gno|U=BtA3keMBT@&7AW z9@%htJh%NnxrMVCA;*gO0qYbx;~f?d8|@oNzC_?15c(8PER?Ul)bh6as?Dobu7|tuXj59LzZn z-=kYdKIzH8jQn9Rt1@A{t}o7j8E1kYBnM#znJ~zN!A%om(1DqV@Wl-F0LM?)M=5Rq z0559-_Xa>3-UQC0ScljkUGi+&hxRPCHaP3_eLT(s|GGi&Cu?TSX*BTu=*d6l)l>Zp z*7bY*xQM21^@(27otx|$iokm6m1itU%^4B4%(o4lr%{IWvLDZHpjA4Z)=zp+N6Wd1 zb%$k_@1PFLOuH6F_M!Q6u{3f_g8DPRg>!Ez1O;!nUB$?mxG^o%HdNz7w1o} zS^ost-!sl$?>&&}bd<2f5?0CvLF6MXu0);4}E=&XgRO2KRH>QULpUa z$8~$ZDe}L;y7IgeuhHG&b@Hs}Xxj zz}{3R?=7azHh7C{RnOaRdxNM>-j~emQ1TXuXYJlsQ;WYEf`8;a&1oCAy+i9vzIA%9 zqDOV|{-<`i-FuX#k`PpTyC>Dj`=ysq#e4Mh-5;ypJk_8&d2cm+(~eS<*;8M&)^rTj z$$PP}HRd1Cg3UQK#ts%#C-2uLu6_0aMQRFVg<0lQC-3Q+2Znw`O6U5As#x}?I(gss z<`L!-nz{Xx#oWXJR44BZOBQVUg!*UlK6J}7pgMWqI4iej8G5}|5IE(8qI~k+a%0WD zGL)6%acvVjmgXnVyTiP>pV3Ie!(AtI;8C4ipJX!WpOJ;=+`Gb=G(JM2mmBo@f<8f!~>JR6pr9@A<@lUl)j=Jk=;xC4N#@|D{=m;&5>V`Jn6gn zGAC`CpXlEr-h4%4wp|Hn9-~k5SK<7U_V(YBTB z++l#@@m=Td=+W|X{`1!;{3rSYudwf^V2#G4sj-SU)ZqL>>+XF=CL4O1hxZyv{UN&T zYyBTcb?&v{SK2GugB<4<=*Im(DK)WXahWDGKhfh}S9Xs*WvOb&cgfY0seuk;az$h35h;b1D>yER|j4_ zbJ&XJugCd=*umv!QAN8expGDPiLO6yGF;Eu&AQY%*NNu;h4bIkSXhn*%z3Nw#L*P{~M>b&_}#d8;0J`b0#?>4*=b+(y!bY%n81w^mQ zva3W%hgK!7*-ZTr68+njnU!ecNKLz^%V>RNMECd^0oS|kfsO~<744ai%b)gWWhJs6 zJ?NLo4tiV&h+a2xS0%D6N;o9FW=8!dx{Cg(N)$D)%^~fbv|ciz3l3ygqN(@WkA4+M z+byFcVBr7S=khDjmcnJ7Pd(vrr*8vzB5cTah@(MpXh5+>MN0?xxa6bD{Y4| zqQ{1Is6s>C*RCu&r07qSarup<+Eqw2`r+pwKiVG(h|V+*t3r z>x@uW92Z2tLj+P=%0Ip2`>8%$eVzOz|c6*^EGd%D(~ z9*+W|Ctf~Qh34MZo4U1&HH{C^jjJwIA+w@Pqc78YaJr1>t{-oM|2i|jszexaI@1o9 zUzn0#g@R0zCjF?R?Lk2F6U@6RWcNJcRtTf;pXg~repaDF;oWxW|D?K%=t12ZtI#az z*OtNgivB?bmw)q?N;Mj`6KP$&Mg0^I-POEXHA)#6B8z)L<0d3}dPKi!6m#dp6Ty3W z+{uV8h!|FlE_R)&{;i(&lkh|dlz+&4Of}m7AwIt%YZO;60nsh)+g7963mUhD1&a1b z^q@hm)ktNEkL^7ddR)keF7%jLjm8)}W;X0n_}?CvpF1hA8uk3+eai3>Mf)duiq^tv zB)PFg=goITzeIH9Gs~)x>BsfYN7&Q+GNL>8UR8~5h>kYDjT=tm-vO7OG(EW*6*tJD zD*kLgL@!yes~Wv1zp>@1uOdD~pXPoD?yC$O*|a-G(SC^DsCEkOpJclX4fa!qsy9+%FGQj|}0|EBNNsB5>>^d)L^ zd?+J&*#>zvvfO8Q)p!i`lj)4hPibtfM#}9|8Z2%p+9%NmPHb0$)EqZyO;x4&g+%vR z+@%IxO`M}F$WZh@M6XNiUV~QaU4CeAjK-7cg3C{f>RW@9nC+7q`q2F@AUb0^pawlU zb=mDhKK_y%{DefmDL1M?V>h><`PReSUUqE!{!_GB`zdEeUA)fm0Lv)8n?ltJBmqzM06*`^~ z65agOj2hHFM|5Q2@o}7==|n$~>sx~+>&S<%oJrfijOYVTz<(CB){Z}<9ZKU?LUiNs z@EWw)LCtJb-$`6KOjqo`L$5_O$o1lFBh3SPoUTjs(vxv;zs&wqmyDORd;!sggT*!I zyPj#{)e(+dejlQzB(JSO;k`FKD-EZ|wUEpc4x8=$&A-b#Uff}?*>oya$PO*R0vHzydnc)8>?|$v23jc|& zW_k+l-<68T>TRU^Q9yKOm8=@HaNfqThAPxgAEG()n z=y_H^bd{QRa@6L2)4f^aX&iirUVgHp9BH?kKg+Ox7p@#3(JxKwDo4_e^(Iek>2+B; z(bH?X$x)4-i=NMI+74wzFH7ttM?04%i3@a$Y5O7i&9;5z=(OnKgjtGlEYl79AH7UZ zj#@GbPAd;p#GmNK?+3`yTDxt!vlHm?C?NV!%b{|#<=dN8s2e>W`4Bxl$ykoAo(^_g z*hO(ZCHl=rrgHQ#$hYe1NyYI?^c>|ea#X*;Dmm#PZRawgml}XZogDc_Y<4t0MaM0=L{FXPC`X5Oj@?sugZ6I%qJNt^ zNsclq4Z69hcjn^bLv(?sn;bbW8CA3^T;V^_bH;hf(W0Vx>BgJrc`cpjncb($(fmbk zI(jOOFB#F*KF^Y)+)q#6w=ALkZ3)qbp7D_*D}zZ(5{k6B?K0i5|JtGca#Y^%BV%u~ z!hfQx=?2MBcErQJdDXPP5)j?^?gBYl@T0T#=H>Lb^C5a#P`DiJIiGSV(2TZQA<>1+ zk#dx}yN&1iIxm%@uYMKQ1IE+#Rzh^2HOu8_hRWU2 zhoy@Cvj_HnO}$W#Oiq=rP2Ea$U82A8O_ZZ4?^bl%P*BS4F9Fe`vscTJ+hh;bW8Y|d z^C5a{kM(l&+-s!r+|P>hDbYL5+bBmLh78=h?tq*tC!OfFCpXK{!VNjsCrW6XWkh%W znj%LzJ#?6HLkH9LPjvHssd7|Ymy{y-D$%Ot>X&i(^7hXOhM=O`_9%;OM z80Swq(FNJZ2V<=`kCyLaulN4XG&lGKaWcYo=b1qqpPS7o8E%dI^Yb5qBBd!KN@a&+8(bzaI;I*9)f-CL?S{}Nr$lrKkK=if+oJJWG&I?;`H7s%0u zqWaH!M$_v-8PP397Rgci8n-PMCA58(5Iy$RD>*U?Kk;GsaT+&98~fkrRxC$HoaPqI z$fWhvC3=40TRCc2I&6m(Pcd#My5@{hIqGlSrowivV*J46fA&$13i`b9aolBO3FX3g zjOe<~pP^ssTg~_XY{2R1MDKX*E0j|Z(tojTHFsRdh@Ls{haB}fg9@}O`*Hb8h_1Y; zT#h!aoIj_f7mW|2BY>@#msi65n8$k^eyvpaPjqMJ8adh}I%2Roo}M=aME5vYD@XhL z-kA7!h+_OkbpM)qIU1)OBDZX!Z?*nh^pO)c6UK6%&pG)4O-dQe=u zTGZ_PbXnh%wBHsG-QuKbEwa{FAJ!*E(SC@Y^rAy8YC4zivufxNE^b1i`|~>2qFq@Z zI<&b>^QRL%Utg^jh49Wk@2RWH<(Cn?)J7ftFBm^$&W<&-osWQn9%*~eJ z@-uz0|3hQ6YSEl!WnJ{5>2aY;^tjbMYSH^SrcO`p)A9vGPv6$77OnhYdcdVU?Vo*! zp1DuE7EQA&+M1!OX#Ye%l+mXa4ZJfi|MC-h-JMSK)FZmJ@OgWG*Y|EWvABUX>v&C-Nhqnjw4FL`z-$^K2+A5ae7=I>=RfQHMuM9WmzOgy)Z%zsmkA1!Ana!@4rrSQ+Xd(Yi~$n?(*S{hXh0%h3*Z1C z3veBf4|oIk0+0jRt^^+dMgRwZ4`3-E8E_168}JTL3E;z5k2C>>0879$KoDRBAQf;9 z@Br`*Pz`9mTAAqu7z!{4Oa#mVgaOt7QUHem7XY^bMSyRBMnLB^%8V{xAix;l0Pq7W z0;~h<0-OQl07?LIK>M|@J%ACw8sG{D1grpT2b=)N0QrD-fFA(Di z6B!v0_Gf8}l)NH?qlpj9UYzb8?avhgpS}GpzeiAHfUm!!Kf{bs@(77?4~U-X8xj-1 zG%>hL?vv8a!C|oJ9K6=lfXFELJi1-16?{h4HXsB(0&fG`37+d4;tn4-ha_eg-#Nf{ z(LZEnJ!2OJ|ET?YcIE_$0_O*G%ZyD(cvJv&)g1eTH%ok)fjw~cjf%F5jKu$*J;9$6 z5ET;|pjbD6v4P4&|7n}ywI@Ud1Tf8fsMv37n*1XI3^N+%wh9Rep9>FpGZTWt{3pXd zm+?-tVr~(8LU3dh6_{+I5MPYro%ZD0d$h zKU@zSPsnlLuL|VJ!TZ1_JS-|aB)}#*GKAY5k<1FdTTDRY64!vp!0<@;miycQvWo&D z87VHqBY5rty3dTU{nsNN4&f7IOG~1n142E5q2Gxjdlih@nWvy z(mbOAB5eZ#ePMq(+c?_7{I6Jb4u|7CfNLJiemJgULfy$`xdhmOj1IIrIBJ9aIf?15 zux-Nq16+OQ1u$p%Q=&sW!xqDV#?1L^sSqO;E4UUDa44do#F1FBg~R(#C0mhaMtB(U z2Is_G5Ys@80e9H93^P-4d_aF04Sf_ml8o2m9475|wju`!Kf}CaCWOR91>xvf#{>pK zmj~?~#|bO0;la2YgRzqmD8Kir40M+fzL5ccwvCa0RiIXbl-xrC0wS1Rj5Q|a67&>s z;6XJ#!a0ZVGQ8CGn@$3#$i|EzQ3#G&fDaw_2;aHT#C);l96k^C!nTpYivqykuVlaC zeiBYbP@)6QgG;fBhJ(y6COQD`rD0g5?Z`Gb5~34Mj^I+f_P^18Dx&b0Qrt<|F(DzY zzR^MSj5i)@#2M}|u%>+n@i+e=x8+~z?Zr&|Grtuh=1=hrjtcm* zPl1<5x!!pSoPu%Z%;?}Y^&dN*&RlMC1mWDacw2Y~K@>WSU;>o9e1oImAaRF2C?vqe zZ$57HA4tzh`)FnsttTBqKuhrcs{%D?A;%CohT))tD01f@+{W=i#K&heFW-;_Foze! z!PlR5Xi!!G*$-U*#GE4He2&@pd%56I87|i@ z%-$}86N5sGhieX~@lFN%(!`Q||gut;9tr%rEt^olHaI^Mz4Tdx1%HO?m41=v$G4n|t>Il`A;!-Eag!%fFigqN3p~hDgCSW@d*D@W@h0!kd?T8iHF}Xi2>j2DfVh?+&u`-U zE5i2w_i^As^877IagixXad=q>;{nY0S6;#0pK-PI;DrB&f1Qh!$o&^1WHmKFF_aYu znBR3jUw?QUHYytAf9TOLgbnt`DSu7#_6`dV^Nx&;4)zWw=a;{giP!TEhTfE0l}X>J z6t@Nt0+Kr|o%5CRAS_ycAETmcRM0RZPW2N(ke z0<-~|02M$Z{EwjyPz@*p`XVY!JC#Y{q5M36q7S9epZgK1z zdp%DOe!C>;v+%C{m93dpl9dZQ2aMTJ8&MKxEqT51P5qvRuPctZ+3f$g=>3DrLBkUh z3~dLhbw5^jO6~ovo4;(o9?7q&{rYy|(ru6J0v_Di-1+dS^q%hzPUsZSy<<$ms+dK0 z9VSh)E|&fS{UYhclQq6b^IwlHw|K-zJES{{_mFifH)2?0{{<&FgpO}0r`L` zKvy_0i~*AYp#a=R;&sLXrUMoOpdB(ud;iM#5? z#CZZLVcE=)OFIPZ-AA-U)Q<+_a51;AKZE0heI<=}Nw7jlI1}<`A1pek%O(HgZ$dLC z2`Hcc-^_1C^Iw4cj}Md{=*4_EP?n-g-fIEOL9!hbKyp-ZJrYXhE1pIeT=R=(*0=58(08>4c;@&|04WaH~fdB64 zI#A~Tz)3(2VB1usxO%9+71Vt#;J&e9ZPsab@?yq%otr7T31k68h&t>9ca40NC zTu~;+qb^;(4!R~?zTnTqsnF$ZfiO;`%RbYYIK_E2#gHo=KgT32gZnbT9>A)=1A+0Q zsL7w;c}rj);I6>N!0Nyqfi-|jJ}Sla0L}#N2`mKe1?&La8(0lk8@LR%kFU!LftLX1 z051i;1dJ~dvVieL_90+AM$rObzc65UPZ-}Xz}tf1JJSi!7Jz+i2f*(nWB)N@4Kwy1 zr{U{#MH(&_uZy`00GE&Jhc#S3TsI7?Df(2((6^H3F>>XpKN?1X?4| z8iCdbv__yc0<95fjllokBY>X`DB;0-4}4|vYKB!5%jh}bG;GSgQmIVop2|)=d3CRgt6SgG;CIlzU zPuQNYEWsurF2OcoPJ)ZbRpcg`B61gbh&)A8MP8z5q6rE13F3r=gv11ggdGV!B43f8 zXs*a#6d(!|%@YNQf<^O13q&EJP*IpDTofT%D2fzCiK0a@qD7*`qF8uJJ~UxdLRf-J zf?GmBLV`#n5{nW=D@ChBt3_)>Yenls>qSYT4Wf;rO`>GcX3-YWR#A#*n`paehbUFF zQ?yGIp5T`dk>Cmsg&g4t#AkU{;!K{k_#Dqhe3WM^{)1;HKE|6M&f?jNkMkVF=Xs9e z3;c=Vi+pGCCH`dbWxlKU3V(|DD&Ipa;ZGG`<4+Sy`7^{Uf0kIrpCiuZ`-o5T{KVJ! zbHz9K0pgqddE#5VVDW9j&KFM1te#ToRKE+!te$HDXF5s;d7xLDLU+~t8 zi+D-mm%I()SNx4ok7V$Dv-mJ?i}*EfE7T`Ne1W%3e3`dhe1x}8be5Mc%H-`Ao#P!4 z9pxPq{lPmVI>yToW$_M+j`NO)&hs)w7x+g-7x~9Um-r_|m-(kfSNLZ|SNVU4B>eND zYy68MDgUyFf8y&o1E%Z)U<9-o%7ro)heO?s(zaGRzSOb^;jqStxu! z>H^{c4<9^y!2KG~!tw134o2WVb!dkFxMG(LJHpiATWB}(h6eO}H?8}{Clx5Si(aCA zZ~GIsj{HD^!xw@>PPe&cw(2VyIbLGD^ux0h_W37ty{YMuZ(`TQ5000j;m$MPPP!>g z+4k`++H%u3w zYCqL`IpT+7@{BQuS4(AHMH!;Q2hNXp7j&-cktzFZr(aYXYT#HpY_zG1`>96@3^q+y zJ|uf{eR)IK?LK=yt(oDq*@ATy_wO;;DZsBuyy%)b(%lh-D z_-404P<*@lPaA*u`?nwc#P^?yvf{(ySmA3iFGNgSVHy*+5P;d7vce7r8)GMBoagua z_?zIr|G*~S{pPe1gufU5`;UHV{7*^oZE}V1H}^^7gJupn0)&h7U5mG`04AJIB$SIU_Kxm5Cd2SNC2z`90X(nq<|ZMV!%&; zQW6uV0T>A|2TTEY0Tu(+0}cW%0tx`H0CGSBp!WtQZaBamFcYv0AO!3J>;tfXJiu2# z3!v{tCQct<39tdo1uO<^0vrU~0OSDP0X_lRZGvq8Mgr^s^8g`$Re<$?;{X;=0Knnw zB~${mV`hi|>-ezD&t-)0jSHbNFpp7#bTvqKg>+X)R}*3=Q>f%x;?pP``zve!PF#%t z^;bp6r7M(n@XZp%GG-p51?6i&`BS0%sZhR_kbeemUrVSA{Ci%-ay!O)i(*;jjAHxE z)+$W%M69cn|Ec}MAH{kK<}uo^eQntOEZF`m*uJ)qUxK%%=jh&jfbbM_hbnQtO720;*N2oe&4P-9I&s96w034$0}N{SM# zA!4qvrG%y$q^43)Q>fwnoEx;?@AvKVzVGk%KF{x;+&nk?4(FV`_u6ZH)@QA?@4Yk8 z|4jRM%)31Lx7T1M`p0jBc+K=kd)Gg%zps@CV^NPRb2R!2!~Gw}^S1xO4b9>^)X;-B zjZKZnn_e~hVYBsR*u$5CZiek%y>LL%fl*y@b6cu4f|NsRmOL5tRaU*Jy`Ky^BRQhi zkIAljd;9)fy|+!9GNGPz@W!W?cXnH_@AJarE8d^;z-atu+>)7(t-Wurzj8+C8I^U~S)b z*DrC;e`R9Z&RJ2+R~O@r{#macjt_`={884e)#v~CIZVo0Y8(`DZa&<<`eLb9pS(AD zf6?(f9T(aTxRBiCsN>3gzedfY_Bdku4)`QLc!^`%lfVwl`p)mJ|6P~GPj26?73{n6 z&I8%y;ep`GdFAT7Tpu*=!Hf8U7Of84{j*72+nafg3DKR6(H93~^*Pq<#r`jTvmyf` zf0&ZnXMe52K^?7qXU)9_J63M@klQ!1-v?c{R-bGXU6%xJ6-O;{RN5PKy8UWj?rT2i zv$Rz!7R~wWUH_r|3Y%3VR~?Z@ntJ|}{<~wv)8%W&ue{=zI5>Ly*fHB3OS)X&ksm$4 zaVut{SCro)>+SKkC$*|_BK3F2BeTA1mC08ePhxu~#sqJ73_S9{V{D(ech_Gx;nPK@ z{utr76`N~>PoL&!G`H=G6RSRPbWUIBsCP2L;hDAK*y+&njz7i2amkg|Sf6(6b#zbD zy^cQ?mf2pZ>S@PgK4zm|-T}w+XM>vT4@-5Z3oFj*_o(Z;>vza?eV%*y>A@it`c6rn z?-}yjrJ9Q)gdYxek13IpG;2cBl1m1kT{5Wh!RxPQ))>BFO>joaiO?~OfA-jrATd*z z9h9)*L=D@bW z^$I=mJBA(zdhOn&#i{vCg0x+W*PeMl<=uE%wG8dSc7Jy8hX2QD<*r?BdbM2LS0$<} zJlMtOx2>n$#HM*uE=RBPJCglLizOo#oN|vn9=rz^`e`LH&bilUwv%CV}|j@dqO|=arjI>!=knK|2INg6>V*_)zSK*wLdT5 zjkX3_Pqbyxx}&uz#fupCarS=vM`-PR>37lE`~36KRzsVIwmjNgwEk#w(As<0&!R1j z_88iVXtU7Td+RgN+QDT8T6+)tMzl6xPD5+&rC*A+GTLOcHPOySTMO-Uw6)PDp{;{< z9NM~Q6VRf;?d^9v7rvq6_uoma=zS*9E2N08<9Q;(zIhc5DQI}7bwS|2aKpde{pUvw z!yDddnh-cR5N&g`UGrR+j|u_@^vHXk>6KT8X^huH@w$w*9a;`;OSB@|HfY~onH1DP z|MlKIfBX9Xscjfu2mRMW|8>xRJ@j7!{ntbP<(r=&@i@3L13(fwi8-CykYr> zguwC|+Ky=de(e8`$40@2|JJ`B`mcxn{m_3s^k20>L;`7|q3wwFztU$^-oK=V{wtUG zZ=DmBXQSEwpIg_0hJ(8mG;Gqfzz=QJCOrz|J3R{;KIw(K-9wK85zp)3c^&8TsDi+e zy>X8!sE4-RNZ}z67hq}uuq+!)ZCok3y=lEDo+iA0eZTqJ*WY3J-_RWICA#SN{|35V zy@}H7*I$ReIn#cuI3FkCA+Rm+bqCEg+dENfb+!lB*iYb9# z`Tp0l>}Qc~0{(Z#_mlL(|4@8iNk^t7(~fD2=PmKS86MlWz3%t%{fKri@8SNu_S@F7 z-^9%wf38Ij-~G?T3of|caD1Cd95a~dkE_Mu8&6_z<-trpW(bZN8!6X|KplloLBXSR7T)R_OW*V{!ADi^>Owx4CAtI z6yKWiS09~m)G+k&b_5I~Gfglr7XKB@QNo}7{Ox(RJcc{Z8H|y~|81siAivI-SqEIf zK05?`hvB@*Od$FT!#Vw(XV{WTz*uZa_h({pHHP^dBmK`g{ga%U;Anq*k4-%O{Qq{H zkDbqKnb~t1iT}|UhkbK6=iKhivGxkvV}C1W`&rRy{nH$}Gu1GMaGXB`*BpU08UDAK z|I>PO#tc8k(RN?{PVE|jvu#QHV_a>a!x0#p?i|~{Ui;ry<)79h1n0Xz>b)_tx3l?6 zV^Oz&uH)N~@UE?wA7dPU*EW1Fk9&rFK=G|u0>u(2mO!xtiX~7ifno_1OQ2W+#S$o% z!2g>B5bz(u8UE~Sb}_q~J7I%RAfxE`t;;Qiyuk#K*gzwL<<1_dj zd?vq#&*Bg9$M} z`Qjb%zW7Y6CUuc|NW-M1Qkt}0IxRhxD#}&mV0nhTTwW*dkt@)lbQJYaYAdW_D$SKH zN}`gc996n$$F=L)7=4L;KtHNaupV0o%n&0nOe?k%JBS_1j$p^I*(+xQ!N8KRN4q%-MD29Tj-44Fjck)@dXRMixDT2-r|1!#eqq_x(1YyGtm+E{J6HdkAyt=2Yb+qEOw3GI9BiuSAa zRC`Y^tNZJXbx9A_XX)D@rSJ4M#x?VOE6fVFBCJTOyA@?cTd`KWwb)9v(yVlAqjlZ7 zW!<$3tVdR%^}?Q~0}-f;JzsaWF3Yk4OIe*g%zn@Q$`-Is*s5Gpt{vBzo5;=JKI67> zUqe?eaUOgHUgl%?WPS_3o4>-lkVLWoGD#z!lkFs%oFG4ti{v`FMO=ikg11mtm>_&D z91z^Z_aT{JF-#mJrii&%D;KGP)I`!Gd(HYtpGp^`nzAm>ku&5=a=!dfE|g!%uGE8i z((<$t^`-u_4sA#|YSK`eLvPUEXceUfWN9i*l{QLOB}y5kOj15o<|?VmRwYY0qP$Q% zRDV@gL)2dCRCSfQLw%sWR6A-PY2&piknM79x0a<{(4K4VdOe-!ir!3bqj%7w^g;SC zeX+hq-=Lq=J&n?4b+f72-i$E^ntRPd=0)>`>1|c90xS*^o^GwRzO;5)*|to>v4;>1 z!+Nk@Y#la;?ZFO*rcPn!!S;@_Zk#XIi0jRb=caJ`xl`O_?lxDzJ>yFAwP1T;d<5T} zkL5@5(;?p#{APYTe}F&2dy!}|lBAG5!cpOr@Plw!U`0u6Db5p@h^gXgai@4({879q zhD)8Lcv#y;=}T!lbpHdnrmV=#<#uwEJXB7UeP}f*&{=ddbg_@JNLj9Ysq9cH7)y+F zW2bR5oIc`;|PUq*_V!RU4_I+D9Fw zj#tm9IqJ{quj)g!h9+pCT3>CHHcp$YeX6BsJ2h4h)x-6k(1Wphl0HM9uiwz`=^9X? z6r{7-x@OBGfpO-@K7uUT0W}Bn%lT~nI^T@+Br#+r`I_7y;X;HEDMSg;LaZ=Qnk}7| zeg+D@ko@Ejd4#-Qwzb=fdIKM9{jN;|Xd}u}N_Fa>A@qt_V9Hi7EP8{r+xo@sGqLD5 zgk0G&Y<0E~I}q0T6VUtv&Y$BslWPHMox`o-zThqb)gN&TUyAqUtMLIm&l~(`ej70T z2!EFUnSaJp;Mg8=oIE3bLVzF&&4p<~flx}UEH)PhiX+7-;v(_9ILoO|UrTB7ud*9$ zNxRTEI-IVC4(+4wD^-<`fMuhVuapDIIpvB{M)gq}s6EuR>L&FYHCt`0b<&vRqVJaO z#;+v1Nhx6y@Zh3QUhFIl#Y*>+6XXSQHY8DluA|@3gY+c*nbuNtSny@#y7C*WI9QES zud27zIPI)9K)<5TG)9@9noG?zGs8S!J}{q|Wvt4UXa!kqtghBn*yC2~TkEKG4l?`A zdTPrp4SRjjG-O5AV0*H|pt~#C4eWlW{$2;xHGupm@U9cr57wE)UEn^17Jtw8gXK+z z?WL0pk}kN2WyQ_n4snloNOYIHBu?ri^_EUbtK@C+6-cNoZAO#mD!PkiQ>r+W7D~7> zSQ)A0DEUgE;;wqB{nh#EXX;M%i24KOl&)>oc4&LFL)ta%juxgz>hbz$Xk5Dfn_kiI zGgPCM(bFhc7AsfY&fd;h2Y`*6ALO5CD!AmP#tKpPkbq^0&<*?&Pk6X zSsp}}(G7HnHWFTFnRZZnt!>hG>KF7}{dc{kG0~W6BpWM?%4QR@sg+_eS=c8H8E?+RpFNKNGK=P6&s1N z*jnr)Mu~CaaPbRx(1YSJXrY@_RtkW(7z;l!ALDyZt|0r!mfTERhIhTL_S6Py`!ox9zFp7OD;u?qLB^-Xmqrs)2b!NZub8*Z$EJss z44&cii)Y_rcxkp0TN@e^%(h_LupNQni=0y3#_nSevEQ+$+4HOqSCb3m1nv&^JM8T_ zSCX&Fn|v^^yd&R}?+Xk}1`2M5r+mOCkcnh9*+xDT6rnSC(;2~CEDy$@h!??`Zi`RB z7xIAHRpi=om^=)2u}S_~J|v%(Z^#eir*dUrU0*t$PN55FI;{)e*IfGyY@iH~DjIt8 zOz&sJ8yhgU?+nJwGB25T%|A_ltFhJ1ItlNO#mQrk|A*gaKf~CsvwyGwToB(LdY()A z3TuTQrC+6~So6xt0>x9Urz&cs`dF)^2kN38rq4CXnSD*B0Q>R58#ulfP<|{wnP0#! z1+E<6@9+iu6TU3*CRIp%5=cbMcABtCxFXyY9t+O}SMejUxA=$jQgV|$WG{Ijyi%ro zM!qKBlNnkcxHpE*rC-wh^aTBhKA`SOX{D|rf*JOL70tw$mMSZibma>rL)oe9QL>d| z%4y|1a3W8+q1;vefQ`L^m6d{>*=S!=4NzHCQoE{A>KJvl>Z&!=R4rI*qjdz{$7xHn z>N=+fVP+%rN&2VyJbe*#WS@RaKc)Yy{{eZr8>NhLMpdJh(ZCR3jSkpjI4trc*yFFp zWb?TBgPCK#G)r0zt2xkTkTu3y12(V=KJz5J<}K@?^~{z#ssWGehAqXGXFp_XvGv(N zR$(nR6s+J3o6j!da=Bl*-?$RI!1sV$2ktvnJ zz_CgTRe;`2fZxGFOQD_6Md$%vlOSxB_DTogZLUcVr01}g^3W+6+`EO`M(zkcFkYSl z@4667d^NP}EBT0gN^b8H?g0G^&uVN_b}YF19CjhQoc)5$0PY@R z+j0?HFD{0g#C^)m=az6^al62YPQ!vK@gMVZz`ZMwS3+sAG8p|(X%0~K2dPkcB~_OF zJSLvT)J&)>@&2&>h4B^5AKyS;! zqJ!8jY#gG9+3Xf}FZ%-gMda21^ULxaJoyCJ>p{K>(Md%JL9wG;Zro)r=gKg)EPsA7EW@)Fi2T{;n z=>zcGUWkCQoiKGsvKs8t05tkt$SBZCK8p;kfn)nhRG)zIR5`a|gOa&w)z%gi><0qI_wr7T~o zo~2kJR%ffX^|3VpxVPNe42?Z%U9j%k>xLgPw11n>1M$^1_DA+Tt~}?*)#EsDyio3Y z?jGmHm*J}*UNpe;T7c)x0mIwLU*li#-lQ5~Nd)-nV8lub$#SxmY$D%~lZXl~k$iHG zJSOi8UP2{-6&ykbU|_s3To@;ODx?U@A;06oSs_RG8Awr06hsrgpt~3=CWsTomEt;a zlb9i%7O#u9#NWk7@Kq(?f&HXFNtP_BxzrJQ5-kmoh9LHvC9Oonxef9@C>@v1NIAeY zH@Sf<$kC8`2#ulBfHQvrVf+z~5v3hs&kafm^{!f|meqXqSfFXL?hOpRY%vZOJ5*vi zvV+-)Y#}=x`j8Apu#}`C>PaUx;l)nDe{$jlF$|tFTGr`vEe@LZt?qA5HJR`tJJ`t9 zhi<>*)5tII03C$|!bzc|7%C>g$F-GiN_AvOZY7U{HD&Ku%c(ZaOO+F)(5mac8ndguf6Ir=*EctD?JEH%D1&KXyYheitYey@4m^s@Yc zPkpR;hy-&jCd!3b>a@!+wi`Q(%>#dI#6@yZ$OPnaBBGCJ{9OJsCvV@$KgXJi@ZEzj z>b}Bw;h69q7-B7WEy1bZ;n46TaXz?1ko39qz0^=vfhvdP^YSQos##zVhv^xbNAJ*& zm2VXWtmi{*fObmr(YwMzf6}WP{f&i)JusjIyBf_f%h=M8>Q}-<@jLNX@i(!(G#IP* zQko?1l@qDE+D>f`$v)Ss8bQVo;|8L$t>!);@hi-;y%lGTv~@Vq#rb@FgWi*#N;~9}h(Qk^%DhI8D_51@ zl~ZcI`n!5eJExV`Yv?kff*JZ2{kYB;lF`hVZ)6zX7`|pla|tZ1rX^V`t$d3~cCjfL z(}0y&2OGr>0LnjROLCRDOzse8@g2a9+`x;5kg4QnK8&8=)kAq3h^gVltK}`4V4ODVa>o-PRKQ!oNOId zT~%5s1Hi|YDWAhbH$o&H3G^DLCL<$rK)vLQ$2sWnS}jkj2$^~#KQq_FATnG~f46OJ z5}V2P;&0R6X?OVj9q{xnY6o?h{)=ALm|~!xCd-AHj&phL z2zL&g%mql*88eszo!-X(z?VcWs}^ZY!r)ay#QwmRQPM(biL?&-`=#`i^tH4bzVm=| zSo%)7B-N1j(tGp^#Rs@>RT~IgeXRRH>&luH%(`YnlbDJbWVSNfL9#uO9~fuuG(WOt z+d6pG#hG`DWq)7;xw(8gzm?}m3w*Zu2{14QvF~rN|I%V(*wJD!OFSW-Kz~=j|9C`) zTjd|+%kT}i#$Q9lKusfcJ7=_~a0(A!M-?wfj{Uc#tvGzX8L1q*C%b~k%t^l8X$JTog= zwZIxNhddW&KBF?SL!oR3_{?!YjOB>=U$Uh*iEG1k;D&I+kxAUZ9p`@G9yq1llpg^* z+lgr72!ERYjep4h32zz)O&)e=gXS$-y$XgE1ztc6>S$lHSjq)n)2R#QIlNFD*iw0UnaA z3gUCb&3pr2Rg>uL|Z4xdHXiI&t#>O;pO<`I!EN|2oJp!R&^Db`4SS5njzGQ>*0kB%8D{`a+;arhZ>vX8eSxr?S}?*_X%WLTkO1X){KLD{~S$Iv+Tf%a-M8 zb3=iqPk~iDB7Xu81>~P1G1@W6 z1Uwfm!}H%oJX}?(A=N|NuV9tJrEXGxX{0m(vA{|x9l4{|(BsCiL_-cmygUuOBaBAS zo?z3vz&g&+KI#x{9(>>jusMG{0_)Vr*kwF2UKs*3XTG@stoS;3Lw9S2wFCSY8Mbg& zJFUk!*qLk!8^QJDt^?CvbIYJVuXsx8YH!qx=Ov^HQLB)kQIBMmIXcsz>1s9Y zyjE8~fjs3mSg9J;Hj7Cpn$J`)kM>+u{wV(kzm|jvxyZTPfd46Q#^tYtR>-$}A|``l zYrut6MC9$2k+AOd>X&M!dJ?(PSHRVpnvCeVqqa#~4q4jKUTdSTamu)01eznvR44Cp z#m+&7sf_rx7wR-A>`wkJ#$A=vBq}m!S>z14EL?L&NEJn2F-Dv&t_7F(m8cW~mZzfk zUcmM#%6zqyRtMFPty*J~19m8;0Y4iGTS_oLF_~mn=ibNmuyBE2Dr^`0kuhLV3FxYf z2kMv8_vjL;J$;QM#;^9c(u(kAFfxB5*-zLB>|}Nt);OR2o&AG6C7r|=?PK8AJ#7kd zy%mi*#&lyjSY36q4J>zrIRm+Q zM_hlzvN_xp#PenOmdLtg^R38cpij8)i_l9Pig@r#WIgI5zWx$*jNj=~yrIdzB zv^ES?p8{Zwd_{QM1bEy1zyb~#fq}wIk#EI|)N2HG6l$C+>d9;Bt3G9(*p8648 zHqIDc<_IuC=B%qTelN}18AXbic?$AhtJvG@pX~d{G`B;{Fq2C~4I`hc!v`X6_C&<) z1N+V(KatzS0~N8QV6$5VODqr-#AM51FAL=Yxec<&o9RwkTB(T4&QN8nvK`*#Ch|A_ z$k%j3hCUGtaSd4QJ+%)q4{_EsTi$v2CLc5d*>Cwve2gT?4!ISgfh-L#7Pvb3VkBP& z3_27TS5Z18{fKD9Ky;l7=|(6+lqK*|-B4K@fs936qpMlj;_d4)ZqC}Gfi+yp#&fQG zJhI?^h;Q18Jzy2n;Qv>O>Ee5^H%@8|%a4&JOLM_Y)=OKY9nwrxD14|y%fMGIS9U8^ z)V-)S@>(A~PH%4X2EW>E>@%(z%j~|q+?;;z8rV}bcb=2^)xgLz{3_y$jJO~g;`@kb z>q;YV?F&+CaJ!etXto2p8K_QDSF6EVcWsb1QxlC3&9A_9seMgWL#>=9)20)bff zx%PZD(i_~MF4%1o`6D3WB$}?&*VFXcCd0Zp`Ed|eg`bTq#7=pid=cJ1UoK56A`2%% zFQ?J9bO+r-uR8U!BC3`|v6N8dd!-rde~6ZdI?1=F(w+vte5H9H=MVsYKTMwpj5`T- zc||X2l!jMSoqjRe7yz#Gtr2P_n}>(itI$N*i38%E$M(r=%LsNn$ZK*fEX!G z8iaUggfvDPFHJ%OHv=APJ}fQ}Y-@qMSY9Ttgx^>%e*w+e2Kj%3705;g?mPKgnniQz zugXI(kP6^gv(zgp6Xj-?eVDH7dQ|TZBX66>-baqT9&B(ZxZnxYdhQ_eT#9dmtaB_s zluzVmASb&Yxx-H62&(j}g(IlYPZf8Gqa`oY%7!9ao<)D4->H|?JBS%eX;ri~MmN+M zhM^*U8y?cd{Ml@bx`OkYX%gI=U*%E~RfUO&3m1^df3gkD@A4lsvPLI-&O zw6?4opf%E1RP?B(YYtdTloqYUYVlfvHe4Hx3SXi&8*x!8*f^8y=ERK6$R_Vl4k4GE zqvS$^ULZ^1u6m=A6@Ypnt5P*wjX>NQtHvX8O;nSRNoLNvVGllF2R2xU+JPssYX11< z!2pZ3TA&JV*WVMY;fSr%kXy^N4mt75%~>B|pnE=GNdc%gg>Yf0l|*7*v52DbPz!#+ zx$_>p4__PXjzumwj1T9d_;}Rrl8|N30?K;>W9^+7ED?~QbRdomCy`<_@?^us(W1BF zqwG<$;IVIkeWb$Tcfc3s>9GR`)#kAH#Woxd>bi zp9kJoz!&li@j%7N2m43tF;k4Vg*{>th4?#xjK=14W)3}2B3Z;^pmFbi2GI}ezL zx=?{oC@`Xj=q37y{$c?35Kz$}wh+U`NK}dJ+~8=i+v(sgOR@K4BQk`Uh^3DqtCJ(< zAyZf&7K)7IflQ$t3k66ln1%y;4#K5KDN2e33mz>cO4Fr7!0aNK`8zdby0#SiE;6-a z;8Z!tPUOQYzt9-nL-z#Q`RlCiK!tcZVgN70$M8i}DFBR!0`FQN5{Pn2Il&lbBpS0J z?b>F5DVP)$yht+&aZ4(&3j=omb+!^pO@ur`MlG@M$kjSqLz-lUx-s9|9xMGt(~Kx zpNUoyb}-CF7C6OP`fjhtW-9}XI1^QjEa>hrthzgsiKU;7bxOfXrDCnpv09t4UOTX2 z6m^A1*sqDbE^(jnZ65O4bkcO2jTDOxJ z<1MU_y+U4C9e*c2ggAHUL}J7^IoaL0hk$YJze$D_Wxrn+7vV&VC@_dvE*>Ktj_l?*j5mp!?i_Opx70cAbZ(<_>^l%)+v7jP+4jPuxjSP@ zhPu-z8ck!7yGQ^-7){3^r<6pe)7dncrqHF}O=-wAZp4aafDL4V4`g9wkD<3HP*Mxzp&s3c*{XCsH5f|a-NI8jRi%T3W{|4rkI z_9(s8I|d(r>5k8J*%|hY;LaIj2g$^4g)DLiyRJ^69&?`LkX(|7n!+tql?xC>7Lpf) z5!?k2!4nk*Z&aCmQLm|u>R2OG8w6xXbkrO|uwyMuDB73N{mssdc+|^=3!{;JO%#$) z$@wQO-veYi1Z+A9bUF`w$^}B@1EcN&r5*vNUI3}wfmNPBD{tVHFA%FXFsl(zO8{=^ zK&}vAR~XPM0{GP(2xjAUJXXcV?r}h}Bw*QWpjis=EER~B4ouq&RNDbu+XG}f1Z+DA zbUP1x%LT&a1LN)jM^3j_K^0ROrJ z0i%I|@j$`hz`=1q!X#kfY$;hv!M=}F?8QmPZlcXn26%2Jswr90A?X-$NN2H=B}d9d zwK`wAg^E@I_6rnZUkf9wY*eD01a3DQ>@G!Kia0b)PM7T(t^hBrs}AIkTc}}g{Bd_wb)%hL z+4jgOYO0#9ZpI$3J;>OcRL`rq$l2djAE_@?cg<5v#IB0z`fNL?eAXSGwL?g#=U^v8 z9#F{B@Pco&QQY5&P27JN^dG1VY+wbx2@X(&T7k1Y8@V>mi(^ofg zdTWY3l)od!eu9k0E8dlqB&A4MQXaXFDyWRrBy~vx(uD9tLNsims?vf2{+(Lz*0GY_bH>}1(^~C<1CMu7rcq_FnGU=Vw0qS7v zlNhOPQMaL%YUjKDQ+nR0uhs^$62PiLP&V literal 0 HcmV?d00001 diff --git a/setup/win32/build.py b/setup/win32/build.py new file mode 100644 index 00000000..86adc57d --- /dev/null +++ b/setup/win32/build.py @@ -0,0 +1,59 @@ +# Copyright (C) 2001-2006 William Joseph. +# +# This file is part of GtkRadiant. +# +# GtkRadiant is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GtkRadiant is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GtkRadiant; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import datetime +from installer import MSIPackage + +def format_date_today(): + return str(datetime.date.today()) + +package = MSIPackage("gtkradiant-1.5.0.xml") +package.writeMSI("template.msi", "GtkRadiant-1.5.0-" + format_date_today() + ".msi") + +#package = MSIPackage("q3a_example_maps.xml") +#package.writeMSI("template.msi", "Q3A-Example-Maps-" + format_date_today() + ".msi") + +#package = MSIPackage("wolf_example_maps.xml") +#package.writeMSI("template.msi", "Wolf-Example-Maps-" + format_date_today() + ".msi") + +#package = MSIPackage("ja_example_maps.xml") +#package.writeMSI("template.msi", "JA-Example-Maps-" + format_date_today() + ".msi") + +#package = MSIPackage("et_example_maps.xml") +#package.writeMSI("template.msi", "ET-Example-Maps-" + format_date_today() + ".msi") + +#package = MSIPackage("jk2_example_maps.xml") +#package.writeMSI("template.msi", "JK2-Example-Maps-" + format_date_today() + ".msi") + +#package = MSIPackage("sof2_example_maps.xml") +#package.writeMSI("template.msi", "SoF2-Example-Maps-" + format_date_today() + ".msi") + +#package = MSIPackage("q2_example_maps.xml") +#package.writeMSI("template.msi", "Q2-Example-Maps-" + format_date_today() + ".msi") + +#package = MSIPackage("her2_example_maps.xml") +#package.writeMSI("template.msi", "Heretic2-Example-Maps-" + format_date_today() + ".msi") + +#package = MSIPackage("hl_example_maps.xml") +#package.writeMSI("template.msi", "HalfLife-Example-Maps-" + format_date_today() + ".msi") + +#package = MSIPackage("ef_example_maps.xml") +#package.writeMSI("template.msi", "STVEF-Example-Maps-" + format_date_today() + ".msi") + +#package = MSIPackage("warsow_mapping_files.xml") +#package.writeMSI("template.msi", "Warsow-mapping-files-" + format_date_today() + ".msi") diff --git a/setup/win32/classic.cf b/setup/win32/classic.cf new file mode 100644 index 00000000..e2302161 --- /dev/null +++ b/setup/win32/classic.cf @@ -0,0 +1,9 @@ +# classic gaming special + +# output dir name +$SETUP_DIR = 'Setup-Classic'; + +$DO_CORE = 1; +$DO_GAME_HALFLIFE = 1; +$DO_GAME_Q2 = 1; +$DO_GAME_HER2 = 1; diff --git a/setup/win32/components/archivepak.xml b/setup/win32/components/archivepak.xml new file mode 100644 index 00000000..edaa0399 --- /dev/null +++ b/setup/win32/components/archivepak.xml @@ -0,0 +1,6 @@ + + +

+ + + diff --git a/setup/win32/components/archivewad.xml b/setup/win32/components/archivewad.xml new file mode 100644 index 00000000..b9bef749 --- /dev/null +++ b/setup/win32/components/archivewad.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/archivezip.xml b/setup/win32/components/archivezip.xml new file mode 100644 index 00000000..40eb6ee7 --- /dev/null +++ b/setup/win32/components/archivezip.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/atk.xml b/setup/win32/components/atk.xml new file mode 100644 index 00000000..d8d0a38f --- /dev/null +++ b/setup/win32/components/atk.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/setup/win32/components/bobtoolz.xml b/setup/win32/components/bobtoolz.xml new file mode 100644 index 00000000..f48ba12b --- /dev/null +++ b/setup/win32/components/bobtoolz.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/brushexport.xml b/setup/win32/components/brushexport.xml new file mode 100644 index 00000000..5c44e8c2 --- /dev/null +++ b/setup/win32/components/brushexport.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/setup/win32/components/bspc.xml b/setup/win32/components/bspc.xml new file mode 100644 index 00000000..dcb34b67 --- /dev/null +++ b/setup/win32/components/bspc.xml @@ -0,0 +1,4 @@ + + + + diff --git a/setup/win32/components/cairo.xml b/setup/win32/components/cairo.xml new file mode 100644 index 00000000..49b12e52 --- /dev/null +++ b/setup/win32/components/cairo.xml @@ -0,0 +1,4 @@ + + + + diff --git a/setup/win32/components/darkplaces.xml b/setup/win32/components/darkplaces.xml new file mode 100644 index 00000000..8ef35dad --- /dev/null +++ b/setup/win32/components/darkplaces.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/setup/win32/components/dbghelp.xml b/setup/win32/components/dbghelp.xml new file mode 100644 index 00000000..92823106 --- /dev/null +++ b/setup/win32/components/dbghelp.xml @@ -0,0 +1,4 @@ + + + + diff --git a/setup/win32/components/doom3.xml b/setup/win32/components/doom3.xml new file mode 100644 index 00000000..724011c3 --- /dev/null +++ b/setup/win32/components/doom3.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/setup/win32/components/ef.xml b/setup/win32/components/ef.xml new file mode 100644 index 00000000..722ab2ed --- /dev/null +++ b/setup/win32/components/ef.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/ef_data.xml b/setup/win32/components/ef_data.xml new file mode 100644 index 00000000..44322076 --- /dev/null +++ b/setup/win32/components/ef_data.xml @@ -0,0 +1,414 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/ef_docs.xml b/setup/win32/components/ef_docs.xml new file mode 100644 index 00000000..e6020d39 --- /dev/null +++ b/setup/win32/components/ef_docs.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/setup/win32/components/ef_tools.xml b/setup/win32/components/ef_tools.xml new file mode 100644 index 00000000..007d2463 --- /dev/null +++ b/setup/win32/components/ef_tools.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/setup/win32/components/entityq3.xml b/setup/win32/components/entityq3.xml new file mode 100644 index 00000000..903aaca4 --- /dev/null +++ b/setup/win32/components/entityq3.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/et.xml b/setup/win32/components/et.xml new file mode 100644 index 00000000..8d03843b --- /dev/null +++ b/setup/win32/components/et.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/et_data.xml b/setup/win32/components/et_data.xml new file mode 100644 index 00000000..5ace631c --- /dev/null +++ b/setup/win32/components/et_data.xml @@ -0,0 +1,387 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/et_docs.xml b/setup/win32/components/et_docs.xml new file mode 100644 index 00000000..9f310596 --- /dev/null +++ b/setup/win32/components/et_docs.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/glib2.xml b/setup/win32/components/glib2.xml new file mode 100644 index 00000000..a49f02e3 --- /dev/null +++ b/setup/win32/components/glib2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/gtk2.xml b/setup/win32/components/gtk2.xml new file mode 100644 index 00000000..563fa8d3 --- /dev/null +++ b/setup/win32/components/gtk2.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/gtkglext.xml b/setup/win32/components/gtkglext.xml new file mode 100644 index 00000000..51124498 --- /dev/null +++ b/setup/win32/components/gtkglext.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/setup/win32/components/gtkradiant.xml b/setup/win32/components/gtkradiant.xml new file mode 100644 index 00000000..60d815ec --- /dev/null +++ b/setup/win32/components/gtkradiant.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/gtkradiant_data.xml b/setup/win32/components/gtkradiant_data.xml new file mode 100644 index 00000000..0f1528c3 --- /dev/null +++ b/setup/win32/components/gtkradiant_data.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/gtkradiant_docs.xml b/setup/win32/components/gtkradiant_docs.xml new file mode 100644 index 00000000..8bfbdd7c --- /dev/null +++ b/setup/win32/components/gtkradiant_docs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/setup/win32/components/her2.xml b/setup/win32/components/her2.xml new file mode 100644 index 00000000..2aab401b --- /dev/null +++ b/setup/win32/components/her2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/setup/win32/components/her2_data.xml b/setup/win32/components/her2_data.xml new file mode 100644 index 00000000..63161f50 --- /dev/null +++ b/setup/win32/components/her2_data.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/hl.xml b/setup/win32/components/hl.xml new file mode 100644 index 00000000..b1d28121 --- /dev/null +++ b/setup/win32/components/hl.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/setup/win32/components/hl_data.xml b/setup/win32/components/hl_data.xml new file mode 100644 index 00000000..c9ff859a --- /dev/null +++ b/setup/win32/components/hl_data.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/setup/win32/components/hlcs_data.xml b/setup/win32/components/hlcs_data.xml new file mode 100644 index 00000000..b4873d64 --- /dev/null +++ b/setup/win32/components/hlcs_data.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/icarus_manual.xml b/setup/win32/components/icarus_manual.xml new file mode 100644 index 00000000..407cbd9e --- /dev/null +++ b/setup/win32/components/icarus_manual.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/setup/win32/components/iconv.xml b/setup/win32/components/iconv.xml new file mode 100644 index 00000000..cb661244 --- /dev/null +++ b/setup/win32/components/iconv.xml @@ -0,0 +1,4 @@ + + + + diff --git a/setup/win32/components/imagehl.xml b/setup/win32/components/imagehl.xml new file mode 100644 index 00000000..1f8a0ee0 --- /dev/null +++ b/setup/win32/components/imagehl.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/imagem8.xml b/setup/win32/components/imagem8.xml new file mode 100644 index 00000000..8c55315e --- /dev/null +++ b/setup/win32/components/imagem8.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/imagepng.xml b/setup/win32/components/imagepng.xml new file mode 100644 index 00000000..b2d318f3 --- /dev/null +++ b/setup/win32/components/imagepng.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/imageq2.xml b/setup/win32/components/imageq2.xml new file mode 100644 index 00000000..f2353b2f --- /dev/null +++ b/setup/win32/components/imageq2.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/imageq3.xml b/setup/win32/components/imageq3.xml new file mode 100644 index 00000000..9bc54cb8 --- /dev/null +++ b/setup/win32/components/imageq3.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/intl.xml b/setup/win32/components/intl.xml new file mode 100644 index 00000000..af47c1d1 --- /dev/null +++ b/setup/win32/components/intl.xml @@ -0,0 +1,4 @@ + + + + diff --git a/setup/win32/components/ja.xml b/setup/win32/components/ja.xml new file mode 100644 index 00000000..2f54fcc5 --- /dev/null +++ b/setup/win32/components/ja.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/ja_data.xml b/setup/win32/components/ja_data.xml new file mode 100644 index 00000000..486f13f6 --- /dev/null +++ b/setup/win32/components/ja_data.xml @@ -0,0 +1,2183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/ja_tools.xml b/setup/win32/components/ja_tools.xml new file mode 100644 index 00000000..31ec1d5b --- /dev/null +++ b/setup/win32/components/ja_tools.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/jk2.xml b/setup/win32/components/jk2.xml new file mode 100644 index 00000000..7244c195 --- /dev/null +++ b/setup/win32/components/jk2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/jk2_data.xml b/setup/win32/components/jk2_data.xml new file mode 100644 index 00000000..999ad8f4 --- /dev/null +++ b/setup/win32/components/jk2_data.xml @@ -0,0 +1,417 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/jk2_docs.xml b/setup/win32/components/jk2_docs.xml new file mode 100644 index 00000000..251ccc09 --- /dev/null +++ b/setup/win32/components/jk2_docs.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/setup/win32/components/jk2_raven_docs.xml b/setup/win32/components/jk2_raven_docs.xml new file mode 100644 index 00000000..a7c31b0f --- /dev/null +++ b/setup/win32/components/jk2_raven_docs.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/setup/win32/components/jk2_tools.xml b/setup/win32/components/jk2_tools.xml new file mode 100644 index 00000000..3627b5b5 --- /dev/null +++ b/setup/win32/components/jk2_tools.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/libmhash.xml b/setup/win32/components/libmhash.xml new file mode 100644 index 00000000..f307af5f --- /dev/null +++ b/setup/win32/components/libmhash.xml @@ -0,0 +1,4 @@ + + + + diff --git a/setup/win32/components/libpng13.xml b/setup/win32/components/libpng13.xml new file mode 100644 index 00000000..ab7ab6da --- /dev/null +++ b/setup/win32/components/libpng13.xml @@ -0,0 +1,4 @@ + + + + diff --git a/setup/win32/components/libxml2.xml b/setup/win32/components/libxml2.xml new file mode 100644 index 00000000..a95a2736 --- /dev/null +++ b/setup/win32/components/libxml2.xml @@ -0,0 +1,4 @@ + + + + diff --git a/setup/win32/components/mapq3.xml b/setup/win32/components/mapq3.xml new file mode 100644 index 00000000..7a2b4214 --- /dev/null +++ b/setup/win32/components/mapq3.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/setup/win32/components/mapxml.xml b/setup/win32/components/mapxml.xml new file mode 100644 index 00000000..5f01e251 --- /dev/null +++ b/setup/win32/components/mapxml.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/modelmd3.xml b/setup/win32/components/modelmd3.xml new file mode 100644 index 00000000..dad1e37e --- /dev/null +++ b/setup/win32/components/modelmd3.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/modelpico.xml b/setup/win32/components/modelpico.xml new file mode 100644 index 00000000..67e62c32 --- /dev/null +++ b/setup/win32/components/modelpico.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/msvcr80.xml b/setup/win32/components/msvcr80.xml new file mode 100644 index 00000000..696121a8 --- /dev/null +++ b/setup/win32/components/msvcr80.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/setup/win32/components/nexuiz.xml b/setup/win32/components/nexuiz.xml new file mode 100644 index 00000000..6dbad9da --- /dev/null +++ b/setup/win32/components/nexuiz.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/setup/win32/components/pango.xml b/setup/win32/components/pango.xml new file mode 100644 index 00000000..28847287 --- /dev/null +++ b/setup/win32/components/pango.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + diff --git a/setup/win32/components/prey.xml b/setup/win32/components/prey.xml new file mode 100644 index 00000000..ffb43216 --- /dev/null +++ b/setup/win32/components/prey.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/setup/win32/components/prtview.xml b/setup/win32/components/prtview.xml new file mode 100644 index 00000000..40fd2c93 --- /dev/null +++ b/setup/win32/components/prtview.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/setup/win32/components/q1.xml b/setup/win32/components/q1.xml new file mode 100644 index 00000000..e2f5e2bf --- /dev/null +++ b/setup/win32/components/q1.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/setup/win32/components/q2.xml b/setup/win32/components/q2.xml new file mode 100644 index 00000000..3ecde5b5 --- /dev/null +++ b/setup/win32/components/q2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/setup/win32/components/q2_data.xml b/setup/win32/components/q2_data.xml new file mode 100644 index 00000000..c2e33794 --- /dev/null +++ b/setup/win32/components/q2_data.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/setup/win32/components/q2map.xml b/setup/win32/components/q2map.xml new file mode 100644 index 00000000..a8a03e8f --- /dev/null +++ b/setup/win32/components/q2map.xml @@ -0,0 +1,4 @@ + + + + diff --git a/setup/win32/components/q3.xml b/setup/win32/components/q3.xml new file mode 100644 index 00000000..153e3746 --- /dev/null +++ b/setup/win32/components/q3.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/q3a_compile_manual.xml b/setup/win32/components/q3a_compile_manual.xml new file mode 100644 index 00000000..f21b77ce --- /dev/null +++ b/setup/win32/components/q3a_compile_manual.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/setup/win32/components/q3a_data.xml b/setup/win32/components/q3a_data.xml new file mode 100644 index 00000000..2a0bc8cf --- /dev/null +++ b/setup/win32/components/q3a_data.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/q3a_model_manual.xml b/setup/win32/components/q3a_model_manual.xml new file mode 100644 index 00000000..361cdea2 --- /dev/null +++ b/setup/win32/components/q3a_model_manual.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/setup/win32/components/q3a_shader_manual.xml b/setup/win32/components/q3a_shader_manual.xml new file mode 100644 index 00000000..e4f7aa70 --- /dev/null +++ b/setup/win32/components/q3a_shader_manual.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/q3a_terrain_manual.xml b/setup/win32/components/q3a_terrain_manual.xml new file mode 100644 index 00000000..36723ffb --- /dev/null +++ b/setup/win32/components/q3a_terrain_manual.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/q3map2.xml b/setup/win32/components/q3map2.xml new file mode 100644 index 00000000..442d5627 --- /dev/null +++ b/setup/win32/components/q3map2.xml @@ -0,0 +1,4 @@ + + + + diff --git a/setup/win32/components/q3map2_docs.xml b/setup/win32/components/q3map2_docs.xml new file mode 100644 index 00000000..fcaae12c --- /dev/null +++ b/setup/win32/components/q3map2_docs.xml @@ -0,0 +1,4 @@ + + + + diff --git a/setup/win32/components/q3ta_data.xml b/setup/win32/components/q3ta_data.xml new file mode 100644 index 00000000..7c569a51 --- /dev/null +++ b/setup/win32/components/q3ta_data.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/win32/components/q3ta_editing_manual.xml b/setup/win32/components/q3ta_editing_manual.xml new file mode 100644 index 00000000..cac2fb65 --- /dev/null +++ b/setup/win32/components/q3ta_editing_manual.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/q3ta_teams_manual.xml b/setup/win32/components/q3ta_teams_manual.xml new file mode 100644 index 00000000..bbbff411 --- /dev/null +++ b/setup/win32/components/q3ta_teams_manual.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/setup/win32/components/q4.xml b/setup/win32/components/q4.xml new file mode 100644 index 00000000..83d07389 --- /dev/null +++ b/setup/win32/components/q4.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/setup/win32/components/qdata3.xml b/setup/win32/components/qdata3.xml new file mode 100644 index 00000000..e4c66326 --- /dev/null +++ b/setup/win32/components/qdata3.xml @@ -0,0 +1,4 @@ + + + + diff --git a/setup/win32/components/radiant_manual.xml b/setup/win32/components/radiant_manual.xml new file mode 100644 index 00000000..bc57b7a6 --- /dev/null +++ b/setup/win32/components/radiant_manual.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/raven.xml b/setup/win32/components/raven.xml new file mode 100644 index 00000000..1df785d3 --- /dev/null +++ b/setup/win32/components/raven.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/setup/win32/components/shaderplug.xml b/setup/win32/components/shaderplug.xml new file mode 100644 index 00000000..a32d816c --- /dev/null +++ b/setup/win32/components/shaderplug.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/setup/win32/components/shadersq3.xml b/setup/win32/components/shadersq3.xml new file mode 100644 index 00000000..114b21bb --- /dev/null +++ b/setup/win32/components/shadersq3.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/setup/win32/components/sof2.xml b/setup/win32/components/sof2.xml new file mode 100644 index 00000000..2a208472 --- /dev/null +++ b/setup/win32/components/sof2.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/sof2_data.xml b/setup/win32/components/sof2_data.xml new file mode 100644 index 00000000..f813e816 --- /dev/null +++ b/setup/win32/components/sof2_data.xml @@ -0,0 +1,848 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/sof2_docs.xml b/setup/win32/components/sof2_docs.xml new file mode 100644 index 00000000..168d8df5 --- /dev/null +++ b/setup/win32/components/sof2_docs.xml @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + diff --git a/setup/win32/components/sof2_raven_docs.xml b/setup/win32/components/sof2_raven_docs.xml new file mode 100644 index 00000000..05fd609a --- /dev/null +++ b/setup/win32/components/sof2_raven_docs.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/setup/win32/components/sof2_tools.xml b/setup/win32/components/sof2_tools.xml new file mode 100644 index 00000000..af8e0fb8 --- /dev/null +++ b/setup/win32/components/sof2_tools.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/setup/win32/components/sunplug.xml b/setup/win32/components/sunplug.xml new file mode 100644 index 00000000..9fd915b6 --- /dev/null +++ b/setup/win32/components/sunplug.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/setup/win32/components/trem.xml b/setup/win32/components/trem.xml new file mode 100644 index 00000000..a6440fbb --- /dev/null +++ b/setup/win32/components/trem.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/trem_compile_manual.xml b/setup/win32/components/trem_compile_manual.xml new file mode 100644 index 00000000..8f735227 --- /dev/null +++ b/setup/win32/components/trem_compile_manual.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/setup/win32/components/trem_model_manual.xml b/setup/win32/components/trem_model_manual.xml new file mode 100644 index 00000000..5bd065bc --- /dev/null +++ b/setup/win32/components/trem_model_manual.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/setup/win32/components/trem_shader_manual.xml b/setup/win32/components/trem_shader_manual.xml new file mode 100644 index 00000000..dfeaffdd --- /dev/null +++ b/setup/win32/components/trem_shader_manual.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/trem_terrain_manual.xml b/setup/win32/components/trem_terrain_manual.xml new file mode 100644 index 00000000..a629c854 --- /dev/null +++ b/setup/win32/components/trem_terrain_manual.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/ufoai.xml b/setup/win32/components/ufoai.xml new file mode 100644 index 00000000..7acb9fcd --- /dev/null +++ b/setup/win32/components/ufoai.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/setup/win32/components/ufoaiplug.xml b/setup/win32/components/ufoaiplug.xml new file mode 100644 index 00000000..aad3a79c --- /dev/null +++ b/setup/win32/components/ufoaiplug.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/vfsq3.xml b/setup/win32/components/vfsq3.xml new file mode 100644 index 00000000..aff6d851 --- /dev/null +++ b/setup/win32/components/vfsq3.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/setup/win32/components/warsow.xml b/setup/win32/components/warsow.xml new file mode 100644 index 00000000..23fb320b --- /dev/null +++ b/setup/win32/components/warsow.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/setup/win32/components/warsow_data.xml b/setup/win32/components/warsow_data.xml new file mode 100644 index 00000000..b768a8a4 --- /dev/null +++ b/setup/win32/components/warsow_data.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/setup/win32/components/warsow_docs.xml b/setup/win32/components/warsow_docs.xml new file mode 100644 index 00000000..6a84f7e2 --- /dev/null +++ b/setup/win32/components/warsow_docs.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/wolf.xml b/setup/win32/components/wolf.xml new file mode 100644 index 00000000..7df72809 --- /dev/null +++ b/setup/win32/components/wolf.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/setup/win32/components/wolf_data.xml b/setup/win32/components/wolf_data.xml new file mode 100644 index 00000000..7e23ae5c --- /dev/null +++ b/setup/win32/components/wolf_data.xml @@ -0,0 +1,887 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/wolf_docs.xml b/setup/win32/components/wolf_docs.xml new file mode 100644 index 00000000..b82b1655 --- /dev/null +++ b/setup/win32/components/wolf_docs.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/setup/win32/components/wolf_tools.xml b/setup/win32/components/wolf_tools.xml new file mode 100644 index 00000000..cef04048 --- /dev/null +++ b/setup/win32/components/wolf_tools.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/setup/win32/components/zhlt.xml b/setup/win32/components/zhlt.xml new file mode 100644 index 00000000..e3aa57b7 --- /dev/null +++ b/setup/win32/components/zhlt.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/components/zlib.xml b/setup/win32/components/zlib.xml new file mode 100644 index 00000000..f7153234 --- /dev/null +++ b/setup/win32/components/zlib.xml @@ -0,0 +1,4 @@ + + + + diff --git a/setup/win32/ef_example_maps.xml b/setup/win32/ef_example_maps.xml new file mode 100644 index 00000000..56c9c277 --- /dev/null +++ b/setup/win32/ef_example_maps.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/setup/win32/et.cf b/setup/win32/et.cf new file mode 100644 index 00000000..0dfec74e --- /dev/null +++ b/setup/win32/et.cf @@ -0,0 +1,7 @@ +# ET setup + +# output dir name +$SETUP_DIR = 'Setup-ET'; + +$DO_CORE = 1; +$DO_GAME_ET = 1; diff --git a/setup/win32/et_example_maps.xml b/setup/win32/et_example_maps.xml new file mode 100644 index 00000000..f58b2814 --- /dev/null +++ b/setup/win32/et_example_maps.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/setup/win32/gtkradiant-1.5.0.xml b/setup/win32/gtkradiant-1.5.0.xml new file mode 100644 index 00000000..37bfb5e6 --- /dev/null +++ b/setup/win32/gtkradiant-1.5.0.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/halflife.cf b/setup/win32/halflife.cf new file mode 100644 index 00000000..710ab364 --- /dev/null +++ b/setup/win32/halflife.cf @@ -0,0 +1,7 @@ +# Core + Halflife game pack + +# output dir name +$SETUP_DIR = 'Setup-Halflife'; + +$DO_CORE = 1; +$DO_GAME_HALFLIFE = 1; diff --git a/setup/win32/her2_example_maps.xml b/setup/win32/her2_example_maps.xml new file mode 100644 index 00000000..d52ea000 --- /dev/null +++ b/setup/win32/her2_example_maps.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/setup/win32/heretic2.cf b/setup/win32/heretic2.cf new file mode 100644 index 00000000..34050c3a --- /dev/null +++ b/setup/win32/heretic2.cf @@ -0,0 +1,4 @@ +$SETUP_DIR = 'Setup-HER2'; + +$DO_CORE = 1; +$DO_GAME_HER2 = 1; diff --git a/setup/win32/hl_example_maps.xml b/setup/win32/hl_example_maps.xml new file mode 100644 index 00000000..a9d9ad5b --- /dev/null +++ b/setup/win32/hl_example_maps.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/setup/win32/id-hl.cf b/setup/win32/id-hl.cf new file mode 100644 index 00000000..21dab8a7 --- /dev/null +++ b/setup/win32/id-hl.cf @@ -0,0 +1,11 @@ +# Core + all game packs + +# output dir name +$SETUP_DIR = 'Setup-IdHL'; + +$DO_CORE = 1; +$DO_GAME_Q3 = 1; +$DO_GAME_WOLF = 1; +$DO_GAME_HALFLIFE = 1; +$DO_GAME_ET = 1; +$DO_GAME_Q2 = 1; diff --git a/setup/win32/installer.py b/setup/win32/installer.py new file mode 100644 index 00000000..05e95821 --- /dev/null +++ b/setup/win32/installer.py @@ -0,0 +1,432 @@ +# Copyright (C) 2001-2006 William Joseph. +# +# This file is part of GtkRadiant. +# +# GtkRadiant is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GtkRadiant is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GtkRadiant; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +import os.path +import xml.dom +import os +import stat +import string + +from xml.dom.minidom import parse + +import msi + +cwd = os.getcwd() +print("cwd=" + cwd) + + +def format_guid(guid): + return "{" + guid.upper() + "}" + +def generate_guid(): + os.system("uuidgen > tmp_uuid.txt") + uuidFile = file("tmp_uuid.txt", "rt") + guid = format_guid(uuidFile.read(36)) + uuidFile.close() + os.system("del tmp_uuid.txt") + return guid + +def path_components(path): + directories = [] + remaining = path + while(remaining != ""): + splitPath = os.path.split(remaining) + remaining = splitPath[0] + directories.append(splitPath[1]) + directories.reverse() + return directories + + + +class Feature: + def __init__(self, feature, parent, title, desc, display, level, directory, attributes): + self.feature = feature + self.parent = parent + self.title = title + self.desc = desc + self.display = display + self.level = level + self.directory = directory + self.attributes = attributes + +class FeatureComponent: + def __init__(self, feature, component): + self.feature = feature + self.component = component + +class Directory: + def __init__(self, directory, parent, default): + self.directory = directory + self.parent = parent + self.default = default + +class Component: + def __init__(self, name, keypath, directory, attributes): + self.name = name + self.keypath = keypath + self.directory = directory + self.attributes = attributes + +class File: + def __init__(self, file, component, filename, filesize, sequence): + self.file = file + self.component = component + self.filename = filename + self.filesize = filesize + self.sequence = sequence + +class Shortcut: + def __init__(self, name, directory, component, feature, icon): + self.name = name + self.directory = directory + self.component = component + self.feature = feature + self.icon = icon + +class ComponentFiles: + def __init__(self, name, files, directory): + self.name = name + self.files = files + self.directory = directory + +class MSIPackage: + def __init__(self, packageFile): + self.code = "" + self.name = "" + self.version = "" + self.target = "" + self.license = "" + self.cabList = [] + self.featureCount = 0 + self.featureTable = [] + self.featurecomponentsTable = [] + self.componentCache = {} + self.componentCount = 0 + self.componentTable = {} + self.directoryTree = {} + self.directoryCount = 0 + self.directoryTable = [] + self.fileCount = 0 + self.fileTable = [] + self.shortcutCount = 0 + self.shortcutTable = [] + self.createPackage(packageFile) + + def addDirectory(self, directoryName, parentKey, directory): + if(not directory.has_key(directoryName)): + directoryKey = "d" + str(self.directoryCount) + self.directoryCount = self.directoryCount + 1 + print("adding msi directory " + directoryKey + " parent=" + parentKey + " name=" + directoryName) + self.directoryTable.append(Directory(directoryKey, parentKey, directoryKey + "|" + directoryName)) + directory[directoryName] = (directoryKey, {}) + else: + print("ignored duplicate directory " + directoryName) + return directory[directoryName] + + def parseComponentTree(self, treeElement, parent, directory, directoryPath, component): + files = [] + for childElement in treeElement.childNodes: + if (childElement.nodeName == "file"): + fileName = childElement.getAttribute("name") + filePath = os.path.join(directoryPath, fileName) + if(fileName != "" and os.path.exists(filePath)): + print("found file " + filePath) + file = (fileName, os.path.getsize(filePath), filePath) + files.append(file) + else: + raise Exception("file not found " + filePath) + + if (childElement.nodeName == "dir"): + directoryName = childElement.getAttribute("name") + print("found directory " + directoryName) + directoryPair = self.addDirectory(directoryName, parent, directory) + self.parseComponentTree(childElement, directoryPair[0], directoryPair[1], os.path.join(directoryPath, directoryName), component) + + count = len(files) + if(count != 0): + componentKey = "c" + str(self.componentCount) + self.componentCount = self.componentCount + 1 + msiComponent = ComponentFiles(componentKey, files, parent); + print("adding msi component " + msiComponent.name + " with " + str(count) + " file(s)") + component.append(msiComponent) + + def parseComponent(self, componentElement, rootPath): + shortcut = componentElement.getAttribute("shortcut") + icon = componentElement.getAttribute("icon") + component = [] + subDirectory = componentElement.getAttribute("subdirectory") + directoryPair = ("TARGETDIR", self.directoryTree) + for directoryName in path_components(subDirectory): + directoryPair = self.addDirectory(directoryName, directoryPair[0], directoryPair[1]) + self.parseComponentTree(componentElement, directoryPair[0], directoryPair[1], rootPath, component) + component.reverse() + print("component requires " + str(len(component)) + " msi component(s)") + return (component, shortcut, icon) + + def parseComponentXML(self, filename, rootPath): + componentDocument = parse(filename) + print("parsing component file " + filename) + componentElement = componentDocument.documentElement + return self.parseComponent(componentElement, rootPath) + + def componentForName(self, name, rootPath): + if(self.componentCache.has_key(name)): + return self.componentCache[name] + else: + component = self.parseComponentXML(name, rootPath) + self.componentCache[name] = component + return component + + def parseFeature(self, featureElement, parent, index): + featureName = "ft" + str(self.featureCount) + self.featureCount = self.featureCount + 1 + title = featureElement.getAttribute("name") + desc = featureElement.getAttribute("desc") + print("adding msi feature " + featureName + " title=" + title) + feature = Feature(featureName, parent, title, desc, index, 1, "TARGETDIR", 8) + self.featureTable.append(feature) + featureComponents = {} + indexChild = 2 + for childElement in featureElement.childNodes: + if (childElement.nodeName == "feature"): + self.parseFeature(childElement, featureName, indexChild) + indexChild = indexChild + 2 + elif (childElement.nodeName == "component"): + componentName = os.path.normpath(os.path.join(cwd, childElement.getAttribute("name"))) + if(featureComponents.has_key(componentName)): + raise Exception("feature \"" + title + "\" contains more than one reference to \"" + componentName + "\"") + featureComponents[componentName] = "" + componentSource = os.path.normpath(childElement.getAttribute("root")) + print("found component reference " + componentName) + componentPair = self.componentForName(componentName, componentSource) + component = componentPair[0] + for msiComponent in component: + print("adding msi featurecomponent " + featureName + " name=" + msiComponent.name) + self.featurecomponentsTable.append(FeatureComponent(featureName, msiComponent.name)) + + if(not self.componentTable.has_key(msiComponent.name)): + keyPath = "" + for fileTuple in msiComponent.files: + fileKey = "f" + str(self.fileCount) + self.fileCount = self.fileCount + 1 + if(keyPath == ""): + keyPath = fileKey + print("component " + msiComponent.name + " keypath=" + keyPath) + print("adding msi file " + fileKey + " name=" + fileTuple[0] + " size=" + str(fileTuple[1])) + self.fileTable.append(File(fileKey, msiComponent.name, fileKey + "|" + fileTuple[0], fileTuple[1], self.fileCount)) + self.cabList.append("\"" + fileTuple[2] + "\" " + fileKey + "\n") + self.componentTable[msiComponent.name] = Component(msiComponent.name, keyPath, msiComponent.directory, 0) + + shortcut = componentPair[1] + if(shortcut != ""): + shortcutName = "sc" + str(self.shortcutCount) + self.shortcutCount = self.shortcutCount + 1 + self.shortcutTable.append(Shortcut(shortcutName + "|" + shortcut, "ProductShortcutFolder", component[0].name, featureName, componentPair[2])) + print("adding msi shortcut " + shortcut) + + def parsePackage(self, packageElement): + index = 2 + self.code = packageElement.getAttribute("code") + if(self.code == ""): + raise Exception("invalid package code") + self.version = packageElement.getAttribute("version") + if(self.version == ""): + raise Exception("invalid package version") + self.name = packageElement.getAttribute("name") + if(self.name == ""): + raise Exception("invalid package name") + self.target = packageElement.getAttribute("target") + if(self.target == ""): + raise Exception("invalid target directory") + self.license = packageElement.getAttribute("license") + if(self.license == ""): + raise Exception("invalid package license agreement") + for childElement in packageElement.childNodes: + if (childElement.nodeName == "feature"): + self.parseFeature(childElement, "", index) + index = index + 2 + + def parsePackageXML(self, filename): + document = parse(filename) + print("parsing package file " + filename) + self.parsePackage(document.documentElement) + + def createPackage(self, packageFile): + self.directoryTable.append(Directory("TARGETDIR", "", "SourceDir")) + self.directoryTable.append(Directory("ProgramMenuFolder", "TARGETDIR", ".")) + self.directoryTable.append(Directory("SystemFolder", "TARGETDIR", ".")) + self.parsePackageXML(packageFile) + if(self.shortcutCount != 0): + self.directoryTable.append(Directory("ProductShortcutFolder", "ProgramMenuFolder", "s0|" + self.name)) + + def writeFileTable(self, name): + tableFile = file(name, "wt") + tableFile.write("File\tComponent_\tFileName\tFileSize\tVersion\tLanguage\tAttributes\tSequence\ns72\ts72\tl255\ti4\tS72\tS20\tI2\ti2\nFile\tFile\n") + for row in self.fileTable: + tableFile.write(row.file + "\t" + row.component + "\t" + row.filename + "\t" + str(row.filesize) + "\t" + "" + "\t" + "" + "\t" + "0" + "\t" + str(row.sequence) + "\n") + + def writeComponentTable(self, name): + tableFile = file(name, "wt") + tableFile.write("Component\tComponentId\tDirectory_\tAttributes\tCondition\tKeyPath\ns72\tS38\ts72\ti2\tS255\tS72\nComponent\tComponent\n") + for k, row in self.componentTable.iteritems(): + tableFile.write(row.name + "\t" + generate_guid() + "\t" + row.directory + "\t" + str(row.attributes) + "\t" + "" + "\t" + row.keypath + "\n") + + def writeFeatureComponentsTable(self, name): + tableFile = file(name, "wt") + tableFile.write("Feature_\tComponent_\ns38\ts72\nFeatureComponents\tFeature_\tComponent_\n") + for row in self.featurecomponentsTable: + tableFile.write(row.feature + "\t" + row.component + "\n") + + def writeDirectoryTable(self, name): + tableFile = file(name, "wt") + tableFile.write("Directory\tDirectory_Parent\tDefaultDir\ns72\tS72\tl255\nDirectory\tDirectory\n") + for row in self.directoryTable: + tableFile.write(row.directory + "\t" + row.parent + "\t" + row.default + "\n") + + def writeFeatureTable(self, name): + tableFile = file(name, "wt") + tableFile.write("Feature\tFeature_Parent\tTitle\tDescription\tDisplay\tLevel\tDirectory_\tAttributes\ns38\tS38\tL64\tL255\tI2\ti2\tS72\ti2\nFeature\tFeature\n") + for row in self.featureTable: + tableFile.write(row.feature + "\t" + row.parent + "\t" + row.title + "\t" + row.desc + "\t" + str(row.display) + "\t" + str(row.level) + "\t" + row.directory + "\t" + str(row.attributes) + "\n") + + def writeMediaTable(self, name): + tableFile = file(name, "wt") + tableFile.write("DiskId\tLastSequence\tDiskPrompt\tCabinet\tVolumeLabel\tSource\ni2\ti2\tL64\tS255\tS32\tS72\nMedia\tDiskId\n") + tableFile.write("1" + "\t" + str(self.fileCount) + "\t" + "" + "\t" + "#archive.cab" + "\t" + "" + "\t" + "" + "\n") + + def writeShortcutTable(self, name): + tableFile = file(name, "wt") + tableFile.write("Shortcut\tDirectory_\tName\tComponent_\tTarget\tArguments\tDescription\tHotkey\tIcon_\tIconIndex\tShowCmd\tWkDir\ns72\ts72\tl128\ts72\ts72\tS255\tL255\tI2\tS72\tI2\tI2\tS72\nShortcut\tShortcut\n") + for row in self.shortcutTable: + tableFile.write(row.component + "\t" + row.directory + "\t" + row.name + "\t" + row.component + "\t" + row.feature + "\t" + "" + "\t" + "" + "\t" + "" + "\t" + row.icon + "\t" + "" + "\t" + "" + "\t" + "" + "\n") + + def writeRemoveFileTable(self, name): + tableFile = file(name, "wt") + tableFile.write("FileKey\tComponent_\tFileName\tDirProperty\tInstallMode\ns72\ts72\tL255\ts72\ti2\nRemoveFile\tFileKey\n") + count = 0 + for row in self.shortcutTable: + tableFile.write("rf" + str(count) + "\t" + row.component + "\t" + "" + "\t" + row.directory + "\t" + "2" + "\n") + count = count + 1 + + def writeCustomActionTable(self, name): + tableFile = file(name, "wt") + tableFile.write("Action\tType\tSource\tTarget\ns72\ti2\tS72\tS255\nCustomAction\tAction\n") + tableFile.write("caSetTargetDir\t51\tTARGETDIR\t" + self.target) + + def writeUpgradeTable(self, name): + tableFile = file(name, "wt") + tableFile.write("UpgradeCode\tVersionMin\tVersionMax\tLanguage\tAttributes\tRemove\tActionProperty\ns38\tS20\tS20\tS255\ti4\tS255\ts72\nUpgrade\tUpgradeCode\tVersionMin\tVersionMax\tLanguage\tAttributes\n") + tableFile.write(format_guid(self.code) + "\t\t" + self.version + "\t1033\t1\t\tRELATEDPRODUCTS") + + def writeMSILicense(self, msiName, licenseName): + if(not os.path.exists(licenseName)): + raise Exception("file not found: " + licenseName) + print("license=\"" + licenseName + "\"") + licenseFile = file(licenseName, "rt") + text = licenseFile.read(1024) + rtfString = "" + while(text != ""): + rtfString += text + text = licenseFile.read(1024) + msiDB = msi.Database(msiName) + msiDB.setlicense(rtfString[:-1]) + msiDB.commit() + + def writeMSIProperties(self, msiName): + msiDB = msi.Database(msiName) + print("ProductCode=" + format_guid(self.code)) + msiDB.setproperty("ProductCode", format_guid(self.code)) + print("UpgradeCode=" + format_guid(self.code)) + msiDB.setproperty("UpgradeCode", format_guid(self.code)) + print("ProductName=" + self.name) + msiDB.setproperty("ProductName", self.name) + print("ProductVersion=" + self.version) + msiDB.setproperty("ProductVersion", self.version) + msiDB.setproperty("RELATEDPRODUCTS", "") + msiDB.setproperty("SecureCustomProperties", "RELATEDPRODUCTS") + msiDB.commit() + + def writeMSI(self, msiTemplate, msiName): + msiWorkName = "working.msi" + if(os.system("copy " + msiTemplate + " " + msiWorkName) != 0): + raise Exception("copy failed") + os.system("msiinfo " + msiWorkName + " /w 2 /v " + generate_guid() + " /a \"Radiant Community\" /j \"" + self.name + "\" /o \"This installation database contains the logic and data needed to install " + self.name + "\"") + + self.writeMSIProperties(msiWorkName) + self.writeMSILicense(msiWorkName, self.license) + + self.writeFileTable("File.idt") + os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" File.idt") + os.system("del File.idt") + self.writeComponentTable("Component.idt") + os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Component.idt") + os.system("del Component.idt") + self.writeFeatureComponentsTable("FeatureComponents.idt") + os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" FeatureComponents.idt") + os.system("del FeatureComponents.idt") + self.writeDirectoryTable("Directory.idt") + os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Directory.idt") + os.system("del Directory.idt") + self.writeFeatureTable("Feature.idt") + os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Feature.idt") + os.system("del Feature.idt") + self.writeMediaTable("Media.idt") + os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Media.idt") + os.system("del Media.idt") + self.writeShortcutTable("Shortcut.idt") + os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Shortcut.idt") + os.system("del Shortcut.idt") + self.writeRemoveFileTable("RemoveFile.idt") + os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" RemoveFile.idt") + os.system("del RemoveFile.idt") + self.writeCustomActionTable("CustomAction.idt") + os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" CustomAction.idt") + os.system("del CustomAction.idt") + self.writeUpgradeTable("Upgrade.idt") + os.system("msidb -d " + msiWorkName + " -i -f \"" + cwd + "\" Upgrade.idt") + os.system("del Upgrade.idt") + + cabText = file("archive_files.txt", "wt") + for cabDirective in self.cabList: + cabText.write(cabDirective) + cabText.close() + if(os.system("cabarc -m LZX:21 n archive.cab @archive_files.txt") != 0): + raise Exception("cabarc returned error") + os.system("del archive_files.txt") + os.system("msidb -d " + msiWorkName + " -a archive.cab") + os.system("del archive.cab") + + + #print("running standard MSI validators ...") + #if(os.system("msival2 " + msiWorkName + " darice.cub > darice.txt") != 0): + # raise Exception("MSI VALIDATION ERROR: see darice.txt") + + #print("running Logo Program validators ...") + #if(os.system("msival2 " + msiWorkName + " logo.cub > logo.txt") != 0): + # raise Exception("MSI VALIDATION ERROR: see logo.txt") + #print("running XP Logo Program validators ...") + #if(os.system("msival2 " + msiWorkName + " XPlogo.cub > XPlogo.txt") != 0): + # raise Exception("MSI VALIDATION ERROR: see XPlogo.txt") + + msiNameQuoted = "\"" + msiName + "\"" + if(os.path.exists(os.path.join(".\\", msiName)) and os.system("del " + msiNameQuoted) != 0): + raise Exception("failed to delete old target") + if(os.system("rename " + msiWorkName + " " + msiNameQuoted) != 0): + raise Exception("failed to rename new target") + diff --git a/setup/win32/ja.cf b/setup/win32/ja.cf new file mode 100644 index 00000000..9a3988e8 --- /dev/null +++ b/setup/win32/ja.cf @@ -0,0 +1,4 @@ +$SETUP_DIR = 'Setup-JA'; + +$DO_CORE = 1; +$DO_GAME_JA = 1; diff --git a/setup/win32/ja_example_maps.xml b/setup/win32/ja_example_maps.xml new file mode 100644 index 00000000..7a521f61 --- /dev/null +++ b/setup/win32/ja_example_maps.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/setup/win32/jk2.cf b/setup/win32/jk2.cf new file mode 100644 index 00000000..09eb03a2 --- /dev/null +++ b/setup/win32/jk2.cf @@ -0,0 +1,4 @@ +$SETUP_DIR = 'Setup-JK2'; + +$DO_CORE = 1; +$DO_GAME_JKII = 1; diff --git a/setup/win32/msi.py b/setup/win32/msi.py new file mode 100644 index 00000000..a01f15f7 --- /dev/null +++ b/setup/win32/msi.py @@ -0,0 +1,66 @@ +# Copyright (C) 2001-2006 William Joseph. +# +# This file is part of GtkRadiant. +# +# GtkRadiant is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GtkRadiant is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GtkRadiant; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +import msiquery + +class Record: + def __init__(self, record): + self.record = record + + def setstring(self, index, string): + self.record.setstring(index, string) + +class View: + def __init__(self, view): + self.view = view + + def fetch(self): + record = self.view.fetch() + if(record == None): + raise Exception("no records available") + return Record(record) + + def update(self, record): + self.view.update(record.record) + +class Database: + def __init__(self, name): + self.msiDB = msiquery.MsiDB(name) + + def commit(self): + result = self.msiDB.commit() + if(result != 0): + raise Exception("msi commit failed: error " + str(result)) + + def openview(self, query): + view = self.msiDB.openview(query) + if(view == None): + raise Exception("msi openview failed") + return View(view); + + def setproperty(self, propertyName, propertyValue): + query = "UPDATE `Property` SET `Property`.`Value`='" + propertyValue + "' WHERE `Property`.`Property`='" + propertyName + "'" + self.openview(query) + + def setlicense(self, rtfString): + view = self.openview("SELECT `Control`.`Text` FROM `Control` WHERE `Control`.`Control`='AgreementText'") + record = view.fetch(); + record.setstring(1, rtfString) + view.update(record) + diff --git a/setup/win32/msi/msiquery.c b/setup/win32/msi/msiquery.c new file mode 100644 index 00000000..ca6f4fde --- /dev/null +++ b/setup/win32/msi/msiquery.c @@ -0,0 +1,474 @@ +/* +Copyright (C) 2001-2006 William Joseph. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "Python.h" +#include "structmember.h" + +#include +#include +#include + +typedef struct { + PyObject_HEAD + MSIHANDLE handle; +} MsiRecord; + +static void +MsiRecord_dealloc(MsiRecord* self) +{ + if(self->handle != 0) + { + unsigned int result = MsiCloseHandle(self->handle); + } + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject * +MsiRecord_new(PyTypeObject *type, PyObject *args) +{ + MsiRecord *self; + + self = (MsiRecord *)type->tp_alloc(type, 0); + if (self != NULL) + { + self->handle = 0; + } + + return (PyObject *)self; +} + +static int +MsiRecord_init(MsiRecord *self, PyObject *args) +{ + return 0; +} + +static PyMemberDef MsiRecord_members[] = { + {NULL} /* Sentinel */ +}; + +static PyObject * +MsiRecord_getstring(MsiRecord* self, PyObject *args) +{ + char string[16384]; + DWORD size = 16384; + int index; + unsigned int result = 1; + if (!PyArg_ParseTuple(args, "i", &index)) + { + return NULL; + } + if(self->handle != 0) + { + if(MsiRecordIsNull(self->handle, index)) + { + puts("null record index"); + } + result = MsiRecordGetString(self->handle, index, string, &size); + printf("string: %s, index: %i\n", string, index); + if(result != 0) + { + strcpy(string, "bleh"); + } + } + return PyString_FromString(string); +} + +static PyObject * +MsiRecord_setstring(MsiRecord* self, PyObject *args) +{ + char* string; + int index; + unsigned int result = 1; + if (!PyArg_ParseTuple(args, "is", &index, &string)) + { + return NULL; + } + if(self->handle != 0) + { + result = MsiRecordSetString(self->handle, index, string); + } + return Py_BuildValue("i", result); +} + +static PyMethodDef MsiRecord_methods[] = { + {"getstring", (PyCFunction)MsiRecord_getstring, METH_VARARGS, + "MsiRecordGetString" + }, + {"setstring", (PyCFunction)MsiRecord_setstring, METH_VARARGS, + "MsiRecordSetString" + }, + {NULL} /* Sentinel */ +}; + +static PyTypeObject MsiRecordType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "msiquery.MsiRecord", /*tp_name*/ + sizeof(MsiRecord), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)MsiRecord_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "MsiRecord objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + MsiRecord_methods, /* tp_methods */ + MsiRecord_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)MsiRecord_init, /* tp_init */ + 0, /* tp_alloc */ + (newfunc)MsiRecord_new, /* tp_new */ +}; + +PyObject* +MsiRecord_FromHandle(MSIHANDLE handle) +{ + MsiRecord *record = (MsiRecord *)MsiRecordType.tp_new(&MsiRecordType, NULL, NULL); + record->handle = handle; + return (PyObject*)record; +} + + +typedef struct { + PyObject_HEAD + MSIHANDLE handle; +} MsiView; + +static void +MsiView_dealloc(MsiView* self) +{ + if(self->handle != 0) + { + unsigned int result; + result = MsiViewClose(self->handle); + result = MsiCloseHandle(self->handle); + } + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject * +MsiView_new(PyTypeObject *type, PyObject *args) +{ + MsiView *self; + + self = (MsiView *)type->tp_alloc(type, 0); + if (self != NULL) + { + self->handle = 0; + } + + return (PyObject *)self; +} + +static int +MsiView_init(MsiView *self) +{ + return 0; +} + +static PyMemberDef MsiView_members[] = { + {NULL} /* Sentinel */ +}; + +static PyObject * +MsiView_fetch(MsiView* self) +{ + unsigned int result = 1; + MSIHANDLE record; + result = MsiViewFetch(self->handle, &record); + if(result == 0) + { + return MsiRecord_FromHandle(record); + } + else + { + Py_INCREF(Py_None); + return Py_None; + } +} + +static PyObject * +MsiView_update(MsiView* self, PyObject *args) +{ + unsigned int result = 1; + MsiRecord *record; + if(!PyArg_ParseTuple(args, "O!", &MsiRecordType, &record)) + { + return NULL; + } + result = MsiViewModify(self->handle, MSIMODIFY_UPDATE, record->handle); + return Py_BuildValue("i", result); +} + +static PyMethodDef MsiView_methods[] = { + {"fetch", (PyCFunction)MsiView_fetch, METH_NOARGS, + "MsiViewFetch" + }, + {"update", (PyCFunction)MsiView_update, METH_VARARGS, + "MsiViewModify(UPDATE)" + }, + {NULL} /* Sentinel */ +}; + +static PyTypeObject MsiViewType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "msiquery.MsiView", /*tp_name*/ + sizeof(MsiView), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)MsiView_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "MsiView objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + MsiView_methods, /* tp_methods */ + MsiView_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)MsiView_init, /* tp_init */ + 0, /* tp_alloc */ + (newfunc)MsiView_new, /* tp_new */ +}; + +PyObject* +MsiView_FromHandle(MSIHANDLE handle) +{ + MsiView *view = (MsiView *)MsiViewType.tp_new(&MsiViewType, NULL, NULL); + view->handle = handle; + MsiViewExecute(handle, 0); + return (PyObject*)view; +} + + +typedef struct { + PyObject_HEAD + MSIHANDLE handle; +} MsiDB; + +static void +MsiDB_dealloc(MsiDB* self) +{ + if(self->handle != 0) + { + unsigned int result = MsiCloseHandle(self->handle); + } + self->ob_type->tp_free((PyObject*)self); +} + +static PyObject * +MsiDB_new(PyTypeObject *type, PyObject *args) +{ + MsiDB *self; + + self = (MsiDB *)type->tp_alloc(type, 0); + if (self != NULL) + { + self->handle = 0; + } + + return (PyObject *)self; +} + +static int +MsiDB_init(MsiDB *self, PyObject *args) +{ + char* filename; + if(!PyArg_ParseTuple(args, "s", &filename)) + { + return -1; + } + + MsiOpenDatabase(filename, MSIDBOPEN_TRANSACT, &self->handle); + + return 0; +} + +static PyMemberDef MsiDB_members[] = { + {NULL} /* Sentinel */ +}; + +static PyObject * +MsiDB_openview(MsiDB* self, PyObject *args) +{ + char* query; + unsigned int result = 1; + if (!PyArg_ParseTuple(args, "s", &query)) + { + return NULL; + } + if(self->handle != 0) + { + MSIHANDLE view; + result = MsiDatabaseOpenView(self->handle, query, &view); + if(result == ERROR_SUCCESS) + { + return MsiView_FromHandle(view); + } + } + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject * +MsiDB_commit(MsiDB* self) +{ + unsigned int result = 1; + if(self->handle != 0) + { + result = MsiDatabaseCommit(self->handle); + if(result == ERROR_INVALID_HANDLE) + { + printf("invalid??\n"); + } + } + return Py_BuildValue("i", result); +} + +static PyMethodDef MsiDB_methods[] = { + {"openview", (PyCFunction)MsiDB_openview, METH_VARARGS, + "MsiViewExecute SQL query" + }, + {"commit", (PyCFunction)MsiDB_commit, METH_NOARGS, + "MsiDatabaseCommit" + }, + {NULL} /* Sentinel */ +}; + +static PyTypeObject MsiDBType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "msiquery.MsiDB", /*tp_name*/ + sizeof(MsiDB), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)MsiDB_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "MsiDB objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + MsiDB_methods, /* tp_methods */ + MsiDB_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)MsiDB_init, /* tp_init */ + 0, /* tp_alloc */ + (newfunc)MsiDB_new, /* tp_new */ +}; + +static PyMethodDef msiquery_methods[] = { + {NULL} /* Sentinel */ +}; + +PyMODINIT_FUNC +initmsiquery(void) +{ + PyObject* m; + + if (PyType_Ready(&MsiRecordType) < 0) + return; + + if (PyType_Ready(&MsiViewType) < 0) + return; + + if (PyType_Ready(&MsiDBType) < 0) + return; + + m = Py_InitModule3("msiquery", msiquery_methods, "Msi API module."); + + if (m == NULL) + return; + + Py_INCREF(&MsiRecordType); + PyModule_AddObject(m, "MsiRecord", (PyObject *)&MsiRecordType); + + Py_INCREF(&MsiViewType); + PyModule_AddObject(m, "MsiView", (PyObject *)&MsiViewType); + + Py_INCREF(&MsiDBType); + PyModule_AddObject(m, "MsiDB", (PyObject *)&MsiDBType); +} + diff --git a/setup/win32/msi/msiquery.def b/setup/win32/msi/msiquery.def new file mode 100644 index 00000000..7c80b764 --- /dev/null +++ b/setup/win32/msi/msiquery.def @@ -0,0 +1,2 @@ +EXPORTS + initmsiquery diff --git a/setup/win32/msi/msiquery.sln b/setup/win32/msi/msiquery.sln new file mode 100644 index 00000000..27170171 --- /dev/null +++ b/setup/win32/msi/msiquery.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "msiquery", "msiquery.vcproj", "{7E8B9772-912C-4E32-88E2-62F9DE03C33C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7E8B9772-912C-4E32-88E2-62F9DE03C33C}.Debug|Win32.ActiveCfg = Debug|Win32 + {7E8B9772-912C-4E32-88E2-62F9DE03C33C}.Debug|Win32.Build.0 = Debug|Win32 + {7E8B9772-912C-4E32-88E2-62F9DE03C33C}.Release|Win32.ActiveCfg = Release|Win32 + {7E8B9772-912C-4E32-88E2-62F9DE03C33C}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/setup/win32/msi/msiquery.vcproj b/setup/win32/msi/msiquery.vcproj new file mode 100644 index 00000000..504ef77c --- /dev/null +++ b/setup/win32/msi/msiquery.vcproj @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup/win32/msiquery.dll b/setup/win32/msiquery.dll new file mode 100644 index 0000000000000000000000000000000000000000..fae0a5ebd35c11ce5c942191ed4d5d81dd520105 GIT binary patch literal 6144 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 literal 0 HcmV?d00001 diff --git a/setup/win32/msiquery.pyd b/setup/win32/msiquery.pyd new file mode 100644 index 0000000000000000000000000000000000000000..63a40307f563237670042f2d9b56f90c25401713 GIT binary patch literal 9728 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 literal 0 HcmV?d00001 diff --git a/setup/win32/nightly.cf b/setup/win32/nightly.cf new file mode 100644 index 00000000..71884edc --- /dev/null +++ b/setup/win32/nightly.cf @@ -0,0 +1,14 @@ +# Core + all game packs + +# output dir name +$SETUP_DIR = 'Setup-update'; + +$DO_CORE = 1; +$DO_GAME_Q3 = 1; +$DO_GAME_WOLF = 1; +$DO_GAME_JKII = 1; +$DO_GAME_STVEF = 1; +$DO_GAME_HALFLIFE = 1; +$DO_GAME_SOF2 = 1; + +$DO_NIGHTLY = 1; diff --git a/setup/win32/q1.cf b/setup/win32/q1.cf new file mode 100644 index 00000000..b71789c2 --- /dev/null +++ b/setup/win32/q1.cf @@ -0,0 +1,4 @@ +$SETUP_DIR = 'Setup-Q1'; + +$DO_CORE = 1; +$DO_GAME_Q1 = 1; diff --git a/setup/win32/q2_example_maps.xml b/setup/win32/q2_example_maps.xml new file mode 100644 index 00000000..e2eb741a --- /dev/null +++ b/setup/win32/q2_example_maps.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/setup/win32/q3.cf b/setup/win32/q3.cf new file mode 100644 index 00000000..2772c689 --- /dev/null +++ b/setup/win32/q3.cf @@ -0,0 +1,4 @@ +$SETUP_DIR = 'Setup-Q3'; + +$DO_CORE = 1; +$DO_GAME_Q3 = 1; diff --git a/setup/win32/q3a_example_maps.xml b/setup/win32/q3a_example_maps.xml new file mode 100644 index 00000000..7895babe --- /dev/null +++ b/setup/win32/q3a_example_maps.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/setup/win32/q3q1.cf b/setup/win32/q3q1.cf new file mode 100644 index 00000000..7c4d2f13 --- /dev/null +++ b/setup/win32/q3q1.cf @@ -0,0 +1,5 @@ +$SETUP_DIR = 'Setup-Q3Q1'; + +$DO_CORE = 1; +$DO_GAME_Q3 = 1; +$DO_GAME_Q1 = 1; diff --git a/setup/win32/q3wolf.cf b/setup/win32/q3wolf.cf new file mode 100644 index 00000000..8a360537 --- /dev/null +++ b/setup/win32/q3wolf.cf @@ -0,0 +1,5 @@ +$SETUP_DIR = 'Setup-Q3Wolf'; + +$DO_CORE = 1; +$DO_GAME_Q3 = 1; +$DO_GAME_WOLF = 1; diff --git a/setup/win32/q3wolfet.cf b/setup/win32/q3wolfet.cf new file mode 100644 index 00000000..b0da33ac --- /dev/null +++ b/setup/win32/q3wolfet.cf @@ -0,0 +1,6 @@ +$SETUP_DIR = 'Setup-Q3WolfET'; + +$DO_CORE = 1; +$DO_GAME_Q3 = 1; +$DO_GAME_WOLF = 1; +$DO_GAME_ET = 1; diff --git a/setup/win32/raven-hl.cf b/setup/win32/raven-hl.cf new file mode 100644 index 00000000..e1595fb4 --- /dev/null +++ b/setup/win32/raven-hl.cf @@ -0,0 +1,11 @@ +# Core + all game packs + +# output dir name +$SETUP_DIR = 'Setup-RavenAndMisc'; + +$DO_CORE = 1; +$DO_GAME_JKII = 1; +$DO_GAME_STVEF = 1; +$DO_GAME_HALFLIFE = 1; +$DO_GAME_SOF2 = 1; +$DO_GAME_JA = 1; \ No newline at end of file diff --git a/setup/win32/raven.cf b/setup/win32/raven.cf new file mode 100644 index 00000000..4c9e04c9 --- /dev/null +++ b/setup/win32/raven.cf @@ -0,0 +1,10 @@ +# Core + all game packs + +# output dir name +$SETUP_DIR = 'Setup-Raven'; + +$DO_CORE = 1; +$DO_GAME_JKII = 1; +$DO_GAME_STVEF = 1; +$DO_GAME_SOF2 = 1; +$DO_GAME_JA = 1; diff --git a/setup/win32/setup.pl b/setup/win32/setup.pl new file mode 100644 index 00000000..2d370127 --- /dev/null +++ b/setup/win32/setup.pl @@ -0,0 +1,497 @@ +#!/usr/bin/env perl +# see ./setup.pl for usage info + +# the templated source +$TEMPLATE_DIR='template'; +# the 'meta' directory (no longer a template, not a final setup dir yet) +$WORK_DIR='WorkDir'; + +# Source common perl functions +BEGIN { + push @INC, "../common"; +} +use setup; +$common = new setup(); + +# replace in file +sub replace { + # only wrap setup::replace() for now + $common->replace(@_); +} + +sub svn_cleanup { + $CLEAN = shift(@_); + # take out .svn/ directories + print "SVN file cleanup in $CLEAN...\n"; + my $findscan; + open($findscan, "find $CLEAN -name .svn | "); + my $line = <$findscan>; + do + { + chop($line); + system("rm -rf '$line'"); + } until (!($line = <$findscan>)); + close($findscan); +} + +# make a work version from the template +# for IS developer use, and for custom setups generation +sub build_work_tree() +{ + print "Building a work tree in $WORK_DIR/ from $TEMPLATE_DIR/\n"; + + # make a clean copy + print "File copy...\n"; + system("rm -rf $WORK_DIR"); + system("cp -R $TEMPLATE_DIR $WORK_DIR"); + + svn_cleanup( "$WORK_DIR" ); + + my $ESCAPED_GTKRAD_DIR = $GTKRAD_DIR; + $ESCAPED_GTKRAD_DIR =~ s/\\/\\\\/g; + + # proceed to replace <> in .fgl files + print "search/replace for base dir...\n"; + open($findscan, "find $WORK_DIR -name '*.fgl' | "); + my $line = <$findscan>; + do + { + chop($line); + replace($line, '<>', $ESCAPED_GTKRAD_DIR); + } until (!($line = <$findscan>)); + close($findscan); +} + +# finalize from a work tree version +# this sets some common settings, but doesn't set customizable stuff (like game packs selection) +# as we customize more things, this should shrink +sub finalize_tree +{ + my $DIR = shift(@_); + # what is the version? + # NOTE: I've had countless shit getting this extracted correctly + my $GTKRAD_VERSION=`cat ../../include/version.default | tr -d \\\\n\\\\r\\\\f`; + + # what is the name of the binary? + my $GTKRAD_BIN="GtkRadiant-$GTKRAD_VERSION.exe"; + + print " Finalizing $DIR/...\n"; + + # copy the Radiant binary to a generated file with the right name + #print "the thing: cp ../../radiant/Release/GtkRadiant.exe ./bin/$GTKRAD_BIN\n"; + system("cp ../../radiant/Release/GtkRadiant.exe ./bin/$GTKRAD_BIN"); + + # copy the splash screen - ET version + system("cp ../setup.bmp '$DIR/Setup Files/Uncompressed Files/Language Independent/OS Independent'"); + + # PRODUCT_VERSION + print " Product version $GTKRAD_VERSION...\n"; + replace("$DIR/String Tables/0009-English/value.shl", '<>', "$GTKRAD_VERSION "); + + # set the binary name + print " Binary name $GTKRAD_BIN...\n"; + replace("$DIR/File Groups/Program Executable Files.fgl", '<>', "$GTKRAD_BIN"); + replace("$DIR/String Tables/0009-English/value.shl", '<>', "$GTKRAD_BIN"); + replace("$DIR/Shell Objects/Default.shl", '<>', "$GTKRAD_BIN"); + + # install path prompt + replace("$SETUP_DIR/String Tables/0009-English/value.shl", '<>', "GtkRadiant $GTKRAD_VERSION"); + + # uuid + my $MAIN_GUID=`uuidgen`; + chop($MAIN_GUID); + chop($MAIN_GUID); + print " $SETUP_DIR/ has uuid: $MAIN_GUID...\n"; + replace("$SETUP_DIR/GtkRadiant.ipr", '<>', "$MAIN_GUID"); + replace("$SETUP_DIR/String Tables/0009-English/value.shl", '<>', "$MAIN_GUID"); + +} + +# configure a tree: +# - what is included in the media (core, game packs) +# - full/nightly +# TODO: this could be cleaner +# the variable names could be generated from DO_CORE DO_GAME_Q3 DO_GAME_WOLF +sub configure_tree +{ + my $DIR = $SETUP_DIR; + print " Configuring $DIR/...\n"; + # get the major and minor + my $RADIANT_MAJOR=`cat ../../include/RADIANT_MAJOR`; + chomp($RADIANT_MAJOR); + chomp($RADIANT_MAJOR); + my $RADIANT_MINOR=`cat ../../include/RADIANT_MINOR`; + chomp($RADIANT_MINOR); + chomp($RADIANT_MINOR); + print " version 1.$RADIANT_MAJOR.$RADIANT_MINOR\n"; + replace("$DIR/Script Files/Setup.rul", '<>', "$RADIANT_MAJOR"); + replace("$DIR/Script Files/Setup.rul", '<>', "$RADIANT_MINOR"); + if ($DO_CORE == 1) + { + replace("$DIR/Script Files/Setup.rul", '<>', '1'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + if ($DO_NIGHTLY == 1) + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + replace("$DIR/Script Files/Setup.rul", '<>', '1'); + } + else + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + replace("$DIR/Script Files/Setup.rul", '<>', '0'); + } + } + else + { + replace("$DIR/Script Files/Setup.rul", '<>', '0'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + if ($DO_GAME_Q3 == 1) + { + replace("$DIR/Script Files/Setup.rul", '<>', '1'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + if ($DO_NIGHTLY == 1) + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + else + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + } + } + else + { + replace("$DIR/Script Files/Setup.rul", '<>', '0'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + if ($DO_GAME_WOLF == 1) + { + replace("$DIR/Script Files/Setup.rul", '<>', '1'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + if ($DO_NIGHTLY == 1) + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + else + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + } + } + else + { + replace("$DIR/Script Files/Setup.rul", '<>', '0'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + if ($DO_GAME_JKII == 1) + { + replace("$DIR/Script Files/Setup.rul", '<>', '1'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + if ($DO_NIGHTLY == 1) + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + else + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + } + } + else + { + replace("$DIR/Script Files/Setup.rul", '<>', '0'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + if ($DO_GAME_STVEF == 1) + { + replace("$DIR/Script Files/Setup.rul", '<>', '1'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + if ($DO_NIGHTLY == 1) + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + else + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + } + } + else + { + replace("$DIR/Script Files/Setup.rul", '<>', '0'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + if ($DO_GAME_HALFLIFE == 1) + { + replace("$DIR/Script Files/Setup.rul", '<>', '1'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + if ($DO_NIGHTLY == 1) + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + else + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + } + } + else + { + replace("$DIR/Script Files/Setup.rul", '<>', '0'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + if ($DO_GAME_SOF2 == 1) + { + replace("$DIR/Script Files/Setup.rul", '<>', '1'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + if ($DO_NIGHTLY == 1) + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + else + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + } + } + else + { + replace("$DIR/Script Files/Setup.rul", '<>', '0'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + if ($DO_GAME_ET == 1) + { + replace("$DIR/Script Files/Setup.rul", '<>', '1'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + if ($DO_NIGHTLY == 1) + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + else + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + } + } + else + { + replace("$DIR/Script Files/Setup.rul", '<>', '0'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + if ($DO_GAME_Q1 == 1) + { + replace("$DIR/Script Files/Setup.rul", '<>', '1'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + if ($DO_NIGHTLY == 1) + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + else + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + } + } + else + { + replace("$DIR/Script Files/Setup.rul", '<>', '0'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + if ($DO_GAME_JA == 1) + { + # prepare the dynamic JA media file group + print " Prepare JA media file group..\n"; + # would go faster by copying only the right files in the first place + # rsync might even go faster - if we use it on a toplevel JAPack copy + system( "cp -R ../../../JAPack/base $DIR/JAPack" ); + svn_cleanup( "$DIR/JAPack" ); + replace( "$DIR/File Groups/JA Media Files.fgl", '<>', "$GTKRAD_DIR/GtkRadiant/setup/win32/$DIR/JAPack" ); + + replace("$DIR/Script Files/Setup.rul", '<>', '1'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + if ($DO_NIGHTLY == 1) + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + else + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + } + } + else + { + replace("$DIR/Script Files/Setup.rul", '<>', '0'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + if ($DO_GAME_Q2 == 1) + { + replace("$DIR/Script Files/Setup.rul", '<>', '1'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + if ($DO_NIGHTLY == 1) + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + else + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + } + } + else + { + replace("$DIR/Script Files/Setup.rul", '<>', '0'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + if ($DO_GAME_HER2 == 1) + { + replace("$DIR/Script Files/Setup.rul", '<>', '1'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + if ($DO_NIGHTLY == 1) + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + else + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + } + } + else + { + replace("$DIR/Script Files/Setup.rul", '<>', '0'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } + # and now the nightly only stuff (i.e. media upgrade) + if ($DO_NIGHTLY == 1) + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'Yes'); + } + else + { + replace("$DIR/Component Definitions/Default.cdf", '<>', 'No'); + } +} + +# command line options, default to generating the setups +# optional, build a template from a work version + +# usage instructions +sub usage { + print < + ex: setup.pl 'C:\\\\home\\\\Id' q3.cf + where q3.cf holds Q3 only game pack config +or +Takes a work tree and turns it back into a template tree + setup.pl -template + ex: setup.pl 'C:\\\\home\\\\Id' -template template-gen +eof +}; + +# main + +# configure some shit +$UUIDGEN=`which uuidgen.exe 2>/dev/null`; +chop($UUIDGEN); +if (!-e $UUIDGEN) +{ + print "uuidgen not found\n"; + exit; +} + +if (scalar(@ARGV)<2) +{ + usage(); + exit; +}; + +$GTKRAD_DIR=$ARGV[0]; +print "Configured for base GtkRadiant directory: '$GTKRAD_DIR'\n"; + +if (@ARGV>2 && $ARGV[1] eq '-template') +{ + # building a template from a work dir + $TEMPLATE_GEN_DIR=$ARGV[2]; + print "Building a template version of $WORK_DIR/ into $TEMPLATE_GEN_DIR/\n"; + print "Copy files...\n"; + system("rm -rf $TEMPLATE_GEN_DIR"); + system("cp -R $WORK_DIR $TEMPLATE_GEN_DIR\n"); + # replace UUID + print "Templating UUID...\n"; + system("cat $TEMPLATE_GEN_DIR/GtkRadiant.ipr | sed -e 's/InstallationGUID.*/InstallationGUID=<>/' > $TEMPLATE_GEN_DIR/GtkRadiant.ipr.tmp"); + system("cp $TEMPLATE_GEN_DIR/GtkRadiant.ipr.tmp $TEMPLATE_GEN_DIR/GtkRadiant.ipr; rm $TEMPLATE_GEN_DIR/GtkRadiant.ipr.tmp"); + # base dir + print "Processing '$GTKRAD_DIR' into '<>'\n"; + open($findscan, "find $TEMPLATE_GEN_DIR -name '*.fgl' | "); + my $line = <$findscan>; + do + { + chop($line); + replace($line, $GTKRAD_DIR, '<>'); + } until (!($line = <$findscan>)); + close($findscan); + exit; +} + +# set default config +$SETUP_DIR = 'SetupOutput'; +$DO_CORE = 1; +$DO_GAME_Q3 = 0; +$DO_GAME_WOLF = 0; +$DO_GAME_JKII = 0; +$DO_GAME_STVEF = 0; +$DO_GAME_HALFLIFE = 0; +$DO_GAME_SOF2 = 0; +$DO_GAME_ET = 0; +$DO_GAME_Q1 = 0; +$DO_GAME_JA = 0; +$DO_GAME_Q2 = 0; +$DO_GAME_HER2 = 0; +$DO_NIGHTLY = 0; + +# load config + +my $config_file = $ARGV[1]; + +print "Reading config from $config_file\n"; +eval "require \"$config_file\""; +if ($SETUP_DIR eq $WORK_DIR) +{ + die "Error: $WORK_DIR is reserved\n"; +} + +print "Config:\n"; +print " Output directory : $SETUP_DIR\n"; +print " DO_CORE : $DO_CORE\n"; +print " DO_GAME_Q3 : $DO_GAME_Q3\n"; +print " DO_GAME_WOLF : $DO_GAME_WOLF\n"; +print " DO_GAME_JKII : $DO_GAME_JKII\n"; +print " DO_GAME_STVEF : $DO_GAME_STVEF\n"; +print " DO_GAME_HALFLIFE : $DO_GAME_HALFLIFE\n"; +print " DO_GAME_SOF2 : $DO_GAME_SOF2\n"; +print " DO_GAME_ET : $DO_GAME_ET\n"; +print " DO_GAME_Q1 : $DO_GAME_Q1\n"; +print " DO_GAME_JA : $DO_GAME_JA\n"; +print " DO_GAME_Q2 : $DO_GAME_Q2\n"; +print " DO_GAME_HER2 : $DO_GAME_HER2\n"; +if ($DO_NIGHTLY == 1) +{ + print " Configured for nightly setup\n"; +} +else +{ + print " Configured for full setup\n"; +} + +build_work_tree(); + +print "Preparing $SETUP_DIR/...\n"; +system("rm -rf $SETUP_DIR"); +system("cp -R $WORK_DIR $SETUP_DIR"); +finalize_tree("$SETUP_DIR"); +configure_tree(); diff --git a/setup/win32/sof2.cf b/setup/win32/sof2.cf new file mode 100644 index 00000000..cf4e8898 --- /dev/null +++ b/setup/win32/sof2.cf @@ -0,0 +1,4 @@ +$SETUP_DIR = 'Setup-SOF2'; + +$DO_CORE = 1; +$DO_GAME_SOF2 = 1; diff --git a/setup/win32/stvef.cf b/setup/win32/stvef.cf new file mode 100644 index 00000000..15cd584e --- /dev/null +++ b/setup/win32/stvef.cf @@ -0,0 +1,4 @@ +$SETUP_DIR = 'Setup-STVEF'; + +$DO_CORE = 1; +$DO_GAME_STVEF = 1; diff --git a/setup/win32/template.msi b/setup/win32/template.msi new file mode 100644 index 0000000000000000000000000000000000000000..48e56e3d5a74f5f80e65351b41db7f587031dc53 GIT binary patch literal 299520 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 literal 0 HcmV?d00001 diff --git a/setup/win32/template/Component Definitions/Default.cdf b/setup/win32/template/Component Definitions/Default.cdf new file mode 100644 index 00000000..6f29fcf7 --- /dev/null +++ b/setup/win32/template/Component Definitions/Default.cdf @@ -0,0 +1,1477 @@ +[Info] +Name= +Type=CompDef +Version=2.10.000 + +[GtkRadiant core\Plugins\Curry\Curry Q3 pk3] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=aa6e48c3-31b7-400c-9e50-982af5930483 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Plugins - Curry pk3 +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Components] +component0=GtkRadiant core\Plugins\Curry\Curry Q3 pk3 +component1=JKII +component2=GtkRadiant core\Plugins\PrtView +component3=Wolf +component4=STVEF +component5=Halflife +component6=GtkRadiant core\Plugins\BobToolz +component7=Q2 +component8=ET\ET editing media +component9=GtkRadiant core\Plugins\GenSurf +component10=Q3 +component11=SOF2\SOF2 editing media +component12=Wolf\Wolf editing media +component13=Q3\Quake III Arena editing media\Q3TA editing media +component14=Q3\Quake III Arena editing media +component15=GtkRadiant core\Misc Manuals +component16=GtkRadiant core\Plugins\Curry\Curry Wolf pk3 +component17=GtkRadiant core\Plugins\bkgrnd2d +component18=GtkRadiant core\Plugins +component19=GtkRadiant core\Plugins\Curry +component20=JA\JA editing media +component21=JKII\JKII editing media +component22=GtkRadiant core\Plugins\TexTool +component23=JA +component24=SOF2 +component25=Q3\TA Manuals +component26=Q2\Q2 editing media +component27=Q3\Q3 media update +component28=STVEF\STVEF editing media +component29=HER2\HER2 editing media +component30=GtkRadiant core\Manual +component31=GtkRadiant core +component32=Wolf\Wolf media update +component33=ET +component34=Halflife\Halflife editing media +component35=HER2 +component36=Quake +component37=Quake\Quake editing media + + +[JKII] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT=Jedi Knight II: Jedi Outcast +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=bbfcdf2a-9651-47e1-8890-56c8e2cccb77 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=JKII\JKII editing media +filegroup0=JKII Executable Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[GtkRadiant core\Plugins\PrtView] +OBJECT=No +DESCRIPTION=Displays the portal file generated by q3map +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=fd7c1ea2-c127-4924-a581-5ff750f41efa +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Plugins - PrtView +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Wolf] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT=Return To Castle Wolfenstein +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=b9350989-33c9-4542-8765-9f7ec3c186cb +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=Wolf\Wolf editing media +member1=Wolf\Wolf media update +filegroup0=Wolf Exectuable Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[STVEF] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT=Star Trek Voyager : Elite Force +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=dcc01d72-e04a-4731-aab9-f0983399996d +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=STVEF\STVEF editing media +filegroup0=STVEF Executable Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Halflife] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT=Halflife +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=3b965464-c0dc-4ac8-bb2c-d3df4184eae3 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=Halflife\Halflife editing media +filegroup0=Halflife Executable Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[GtkRadiant core\Plugins\BobToolz] +OBJECT=No +DESCRIPTION=Adds a variety of features, including jumppad path-plotting, duplicate plane removal, and auto-caulk +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=828c7c80-5593-4ae1-bb42-8d7d0d23f6de +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Plugins - BobToolz +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Q2] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT=Quake II +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=59c8a4d7-a765-4705-a9a1-de25ed3c9134 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=Q2\Q2 editing media +filegroup0=Q2 Executable Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[ET\ET editing media] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=daf4e270-8ac5-47c2-8f23-dfd1e22299a4 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=ET Media Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[GtkRadiant core\Plugins\GenSurf] +OBJECT=No +DESCRIPTION=Surface generation tools essential for terrain +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=69f13834-cdbb-4bb3-a597-c84228392e2a +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Plugins - GTK GenSurf +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Q3] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT=Quake III Arena and Quake III : Team Arena +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=74fd6635-0222-42f5-91ef-06f0951e3bfd +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=Q3\Quake III Arena editing media +member1=Q3\TA Manuals +member2=Q3\Q3 media update +filegroup0=Q3 Executable Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[SOF2\SOF2 editing media] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=48a9daea-0264-49ee-a80a-3e64c032e9df +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=SOF2 Media Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Wolf\Wolf editing media] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=aa5d193e-32a4-4947-9c42-22de9fd23130 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Wolf Media Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Q3\Quake III Arena editing media\Q3TA editing media] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT=Quake III: Team Arena editing media +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=c63fb899-152e-4764-bd47-ffb4106cd5e6 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=shaderlist-ta +filegroup1=TA Sample Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Q3\Quake III Arena editing media] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=4e964981-39bc-4fad-bed7-eee0123bc52d +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=Q3\Quake III Arena editing media\Q3TA editing media +filegroup0=Q3 Default Project +filegroup1=Q3 Editor Images - SPoG pk3 +filegroup2=Q3 Misc Files +filegroup3=Q3 Sample Files +filegroup4=shaderlist +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[GtkRadiant core\Misc Manuals] +OBJECT=No +DESCRIPTION=Additional Manuals for Map Compiling, Models, Shaders and Terrain +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=c5cef5c7-edc2-47e1-b364-65307c32c2ab +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Compile Manual +filegroup1=Model Manual Files +filegroup2=Shader Manual Files +filegroup3=Terrain Manual Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[GtkRadiant core\Plugins\Curry\Curry Wolf pk3] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=66be5566-5455-4330-a15f-a1f6abbd6a88 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Plugins - Curry pk3 Wolf +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[GtkRadiant core\Plugins\bkgrnd2d] +OBJECT=No +DESCRIPTION=Draws a 2D image in the background +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=Yes +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=44efec54-497e-4e2a-aed2-97d4fcf9607a +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Plugins - bkgrnd2d +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[GtkRadiant core\Plugins] +OBJECT=No +DESCRIPTION=Plugins to provide additional editing features +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=16b5392d-1494-4493-bdb7-16b23536b778 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=GtkRadiant core\Plugins\TexTool +member1=GtkRadiant core\Plugins\PrtView +member2=GtkRadiant core\Plugins\GenSurf +member3=GtkRadiant core\Plugins\Curry +member4=GtkRadiant core\Plugins\BobToolz +member5=GtkRadiant core\Plugins\bkgrnd2d +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[GtkRadiant core\Plugins\Curry] +OBJECT=No +DESCRIPTION=Displays a shader in Radiant, to show how it will appear in Quake III Arena +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=No +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=65c401da-80a8-4187-8396-c4bbfdb50067 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=GtkRadiant core\Plugins\Curry\Curry Q3 pk3 +member1=GtkRadiant core\Plugins\Curry\Curry Wolf pk3 +filegroup0=Plugins - Curry +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[JA\JA editing media] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=cac92f56-1176-4149-9351-3c38919c7b6a +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=JA Media Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[JKII\JKII editing media] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=1508df12-4479-43fc-90de-eafde8d26d28 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=JKII Media Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[GtkRadiant core\Plugins\TexTool] +OBJECT=No +DESCRIPTION=Texture Tools BETA +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=No +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=d4e4aba4-92df-4ece-8bbc-8dd440806f5b +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Plugins - TexTool +filegroup1=TexTool Help +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[JA] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT=Star Wars Jedi Knight Jedi Academy +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=450216da-07b4-408b-98c6-100148f3a712 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=JA\JA editing media +filegroup0=JA Executable Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[SOF2] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT=Soldier Of Fortune 2 : Double Helix +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=50ad6ca3-f627-4789-adff-19f82cb68519 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=SOF2\SOF2 editing media +filegroup0=SOF2 Executable Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Q3\TA Manuals] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=02a28ce0-5537-4791-8c5a-ce2ee1741252 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=TA Manual Files +filegroup1=TA Teams Manual +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Q2\Q2 editing media] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=2d0b3180-ab5a-4228-ac8c-2bc41a182bbb +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Q2 Media Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Q3\Q3 media update] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=9053ca53-e588-424b-af96-c88e89aa55fd +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[STVEF\STVEF editing media] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=09ce2fbf-a2ed-4cdf-8cf6-27dba629e587 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=STVEF Media Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[HER2\HER2 editing media] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=72e2ce70-3766-4153-908f-f769afd69569 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Heretic2 Media Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[GtkRadiant core\Manual] +OBJECT=No +DESCRIPTION=GtkRadiant Manual +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=6c5a077f-6762-4392-82fc-83491144f07a +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Radiant Manual Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[GtkRadiant core] +OBJECT=No +DESCRIPTION=GtkRadiant core functionality +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=65e59be2-adce-4802-844d-74cbf2eeeb1a +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=GtkRadiant core\Manual +member1=GtkRadiant core\Plugins +member2=GtkRadiant core\Misc Manuals +filegroup0=Program DLL Files +filegroup1=Program Executable Files +filegroup2=Program Misc Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Wolf\Wolf media update] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=6cfb989b-5702-44f9-b24f-4f26b244ad02 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[ET] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT=Wolfenstein: Enemy Territory +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=22bc33ba-4ff9-41c0-bc9f-f611f6b560e7 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=ET\ET editing media +filegroup0=ET Executable Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Halflife\Halflife editing media] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=fd46d247-a2cb-4ce8-add7-7a3664fe32be +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Halflife Media Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[HER2] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT=Heretic II +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=6989f013-951e-4fbd-98a0-1de7928aef50 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=HER2\HER2 editing media +filegroup0=Heretic2 Executable Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Quake] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=22bc33ba-4ff9-41c0-bd9f-f611f6c560e7 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +member0=Quake\Quake editing media +filegroup0=Quake Executable Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[Quake\Quake editing media] +OBJECT=No +DESCRIPTION= +STATUS= +VISIBLE=Yes +DISK=ANYDISK +FILENEED=STANDARD +INCLUDEINBUILD=<> +PASSWORD= +ENCRYPT=No +COMPRESSIFSEPARATE=No +UNINSTALLABLE=Yes +COMMENT= +DEFSELECTION=Yes +SELECTED=Yes +IMAGE= +TARGETDIRCDROM= +DISPLAYTEXT= +HTTPLOCATION= +FTPLOCATION= +MISC= +GUID=daf4f270-8ad5-47c2-8f23-dfd1e22299a4 +_SPLIT_BEFORE= +_SPLIT_AFTER= +_DATAASFILES= +_NO_SPLIT= +_NO_SPLIT_BEFORE= +VOLATILE= +filegroup0=Quake Media Files +HANDLERONInstalling= +HANDLERONInstalled= +HANDLERONUnInstalling= +HANDLERONUnInstalled= + +[TopComponents] +component0=GtkRadiant core +component1=Q3 +component2=Wolf +component3=JKII +component4=STVEF +component5=Halflife +component6=SOF2 +component7=ET +component8=JA +component9=Q2 +component10=HER2 +component11=Quake + +[SetupType] +setuptype0=Full Install +setuptype1=Custom + +[SetupTypeItem-Full Install] +Comment= +Descrip=Choose this setup type to install all components of the program, including the sample files. This will overwrite any existing sample files installed in your Quake III Arena folder +DisplayText= +item0=GtkRadiant core\Plugins\Curry\Curry Q3 pk3 +item1=Wolf +item2=GtkRadiant core\Plugins\PrtView +item3=JKII +item4=STVEF +item5=Halflife +item6=GtkRadiant core\Plugins\BobToolz +item7=Q2 +item8=ET\ET editing media +item9=SOF2\SOF2 editing media +item10=Q3 +item11=GtkRadiant core\Plugins\GenSurf +item12=Q3\Quake III Arena editing media +item13=Q3\Quake III Arena editing media\Q3TA editing media +item14=Wolf\Wolf editing media +item15=GtkRadiant core\Misc Manuals +item16=GtkRadiant core\Plugins\Curry\Curry Wolf pk3 +item17=GtkRadiant core\Plugins\bkgrnd2d +item18=JA\JA editing media +item19=GtkRadiant core\Plugins +item20=GtkRadiant core\Plugins\Curry +item21=JA +item22=GtkRadiant core\Plugins\TexTool +item23=JKII\JKII editing media +item24=SOF2 +item25=Q3\TA Manuals +item26=Q2\Q2 editing media +item27=Q3\Q3 media update +item28=STVEF\STVEF editing media +item29=HER2\HER2 editing media +item30=GtkRadiant core +item31=GtkRadiant core\Manual +item32=Wolf\Wolf media update +item33=ET +item34=Halflife\Halflife editing media +item35=HER2 +item36=Quake +item37=Quake\Quake editing media + +[SetupTypeItem-Custom] +Comment= +Descrip=Choose this setup type if you want to choose which components to install +DisplayText= +item0=GtkRadiant core\Plugins\Curry\Curry Q3 pk3 +item1=Wolf +item2=GtkRadiant core\Plugins\PrtView +item3=JKII +item4=STVEF +item5=Halflife +item6=GtkRadiant core\Plugins\BobToolz +item7=Q2 +item8=ET\ET editing media +item9=Q3 +item10=GtkRadiant core\Plugins\GenSurf +item11=SOF2\SOF2 editing media +item12=Q3\Quake III Arena editing media +item13=Q3\Quake III Arena editing media\Q3TA editing media +item14=Wolf\Wolf editing media +item15=GtkRadiant core\Misc Manuals +item16=GtkRadiant core\Plugins\Curry\Curry Wolf pk3 +item17=GtkRadiant core\Plugins\bkgrnd2d +item18=JA\JA editing media +item19=GtkRadiant core\Plugins +item20=GtkRadiant core\Plugins\Curry +item21=JA +item22=GtkRadiant core\Plugins\TexTool +item23=JKII\JKII editing media +item24=SOF2 +item25=Q3\TA Manuals +item26=Q2\Q2 editing media +item27=Q3\Q3 media update +item28=STVEF\STVEF editing media +item29=HER2\HER2 editing media +item30=GtkRadiant core +item31=GtkRadiant core\Manual +item32=Wolf\Wolf media update +item33=ET +item34=Halflife\Halflife editing media +item35=HER2 +item36=Quake +item37=Quake\Quake editing media + diff --git a/setup/win32/template/Component Definitions/Default.fgl b/setup/win32/template/Component Definitions/Default.fgl new file mode 100644 index 00000000..cd5f9b2f --- /dev/null +++ b/setup/win32/template/Component Definitions/Default.fgl @@ -0,0 +1,336 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0= +SubDir1= +SubDir2= +SubDir3=USERDEFINED +SubDir4= + +[] +fulldirectory= +SubDir0=\ +DISPLAYTEXT=Windows Operating System +TYPE=TEXTSUBFIXED + +[\] +fulldirectory= +DISPLAYTEXT=Windows System Folder +TYPE=TEXTSUBFIXED + +[] +fulldirectory= +SubDir0=\ +DISPLAYTEXT=Program Files Folder +TYPE=TEXTSUBFIXED + +[\] +fulldirectory= +DISPLAYTEXT=Common Files Folder +TYPE=TEXTSUBFIXED + +[] +fulldirectory= +SubDir0=\plugins +DISPLAYTEXT=General Application Destination +TYPE=TEXTSUBFIXED + +[\plugins] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED] +fulldirectory= +SubDir0=USERDEFINED\ +SubDir1=USERDEFINED\ +SubDir2=USERDEFINED\ +SubDir3=USERDEFINED\ +SubDir4=USERDEFINED\ +SubDir5=USERDEFINED\ +SubDir6=USERDEFINED\ +SubDir7=USERDEFINED\ +SubDir8=USERDEFINED\ +SubDir9=USERDEFINED\ +SubDir10=USERDEFINED\ +SubDir11=USERDEFINED\ +SubDir12=USERDEFINED\ +SubDir13=USERDEFINED\ +SubDir14=USERDEFINED\ +SubDir15=USERDEFINED\ +SubDir16=USERDEFINED\ +SubDir17=USERDEFINED\ +SubDir18=USERDEFINED\ +SubDir19=USERDEFINED\ +SubDir20=USERDEFINED\ +SubDir21=USERDEFINED\ +SubDir22=USERDEFINED\ +SubDir23=USERDEFINED\ +SubDir24=USERDEFINED\ +DISPLAYTEXT=Script-defined Folders +TYPE=USERSTART + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\baseq3 +SubDir1=USERDEFINED\\missionpack +SubDir2=USERDEFINED\\GtkRadiant +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\baseq3] +fulldirectory= +SubDir0=USERDEFINED\\baseq3\scripts +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\\baseq3\scripts] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\\missionpack] +fulldirectory= +SubDir0=USERDEFINED\\missionpack\scripts +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\\missionpack\scripts] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\\GtkRadiant] +fulldirectory= +SubDir0=USERDEFINED\\GtkRadiant\Plugins +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\\GtkRadiant\Plugins] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\Plugins +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\Plugins] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\baseq3 +SubDir1=USERDEFINED\\missionpack +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\baseq3] +fulldirectory= +SubDir0=USERDEFINED\\baseq3\scripts +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\\baseq3\scripts] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\\missionpack] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\plugins +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\plugins] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\plugins +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\plugins] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\] +fulldirectory= +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\main +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\main] +fulldirectory= +SubDir0=USERDEFINED\\main\scripts +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\\main\scripts] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\] +fulldirectory= +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\base +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\base] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\] +fulldirectory= +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\baseEf +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\baseEf] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\] +fulldirectory= +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\] +fulldirectory= +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\] +fulldirectory= +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\base +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\base] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\] +fulldirectory= +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\etmain +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\etmain] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\] +fulldirectory= +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\id1 +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\id1] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\] +fulldirectory= +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\Base +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\Base] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\] +fulldirectory= +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\baseq2 +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\baseq2] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[USERDEFINED\] +fulldirectory= +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\] +fulldirectory= +SubDir0=USERDEFINED\\base +DISPLAYTEXT= +TYPE=TEXTSUB + +[USERDEFINED\\base] +fulldirectory= +DISPLAYTEXT= +TYPE=FOLDER + +[] +fulldirectory= +DISPLAYTEXT=Support Folder +TYPE=TEXTSUBFIXED + diff --git a/setup/win32/template/File Groups/1_2_1 Wolf Media Upgrade.fgl b/setup/win32/template/File Groups/1_2_1 Wolf Media Upgrade.fgl new file mode 100644 index 00000000..3fab8b89 --- /dev/null +++ b/setup/win32/template/File Groups/1_2_1 Wolf Media Upgrade.fgl @@ -0,0 +1,35 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=main + +[main] +fulldirectory= +SubDir0=main\scripts +SubDir1=main\models + +[main\scripts] +fulldirectory= +file0=<>\WolfPack\scripts\default_project.proj +file1=<>\WolfPack\scripts\skies.shader +file2=<>\WolfPack\scripts\common.shader +file3=<>\WolfPack\scripts\walls.shader +file4=<>\WolfPack\scripts\props.shader +file5=<>\WolfPack\scripts\models.shader +file6=<>\WolfPack\scripts\shaderlist.txt +file7=<>\WolfPack\scripts\wolf_entities.def + +[main\models] +fulldirectory= +SubDir0=main\models\mapobjects + +[main\models\mapobjects] +fulldirectory= +SubDir0=main\models\mapobjects\wine + +[main\models\mapobjects\wine] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\wine\wine_barrel.md3 + diff --git a/setup/win32/template/File Groups/Compile Manual.fgl b/setup/win32/template/File Groups/Compile Manual.fgl new file mode 100644 index 00000000..39772fac --- /dev/null +++ b/setup/win32/template/File Groups/Compile Manual.fgl @@ -0,0 +1,16 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=Compile_Manual + +[Compile_Manual] +fulldirectory= +file0=<>\GtkRadiant\docs\manual\quake3\Compile_Manual\q3map.html +file1=<>\GtkRadiant\docs\manual\quake3\Compile_Manual\cfgq3.c +file2=<>\GtkRadiant\docs\manual\quake3\Compile_Manual\headskins.txt +file3=<>\GtkRadiant\docs\manual\quake3\Compile_Manual\index.html +file4=<>\GtkRadiant\docs\manual\quake3\Compile_Manual\modelskins.txt +file5=<>\GtkRadiant\docs\manual\quake3\Compile_Manual\bspc.txt + diff --git a/setup/win32/template/File Groups/Default.fdf b/setup/win32/template/File Groups/Default.fdf new file mode 100644 index 00000000..32ac1689 --- /dev/null +++ b/setup/win32/template/File Groups/Default.fdf @@ -0,0 +1,947 @@ +[Info] +Name= +Type=FileGrp +Version=2.10.000 + +[Q2 Executable Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[FileGroups] +group0=Q2 Executable Files +group1=shaderlist +group2=Heretic2 Executable Files +group3=Q3 Sample Files +group4=TA Manual Files +group5=JKII Executable Files +group6=Halflife Executable Files +group7=Compile Manual +group8=SOF2 Media Files +group9=Q3 Executable Files +group10=Program Executable Files +group11=TA Sample Files +group12=Model Manual Files +group13=JA Media Files +group14=Q3 Default Project +group15=Plugins - PrtView +group16=STVEF Media Files +group17=Program Misc Files +group18=ET Media Files +group19=SOF2 Executable Files +group20=Plugins - BobToolz +group21=Plugins - GTK GenSurf +group22=Radiant Manual Files +group23=Terrain Manual Files +group24=Plugins - Curry pk3 +group25=Q2 Media Files +group26=Shader Manual Files +group27=ET Executable Files +group28=Plugins - TexTool +group29=shaderlist-ta +group30=Wolf Exectuable Files +group31=Q3 Editor Images - SPoG pk3 +group32=Plugins - bkgrnd2d +group33=TexTool Help +group34=Heretic2 Media Files +group35=Plugins - Curry pk3 Wolf +group36=Q3 Misc Files +group37=Program DLL Files +group38=Halflife Media Files +group39=TA Teams Manual +group40=Plugins - Curry +group41=JA Executable Files +group42=STVEF Executable Files +group43=Wolf Media Files +group44=JKII Media Files +group45=Quake Executable Files +group46=Quake Media Files + +[shaderlist] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=Yes +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEVEROVERWRITE +TARGET=\baseq3 +TARGETHIDDEN=Script-defined Folders\\baseq3 + +[Heretic2 Executable Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Q3 Sample Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\baseq3 +TARGETHIDDEN=Script-defined Folders\\baseq3 + +[TA Manual Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[JKII Executable Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Halflife Executable Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Compile Manual] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[SOF2 Media Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\base +TARGETHIDDEN=Script-defined Folders\\base + +[Q3 Executable Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Program Executable Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[TA Sample Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\missionpack +TARGETHIDDEN=Script-defined Folders\\missionpack + +[Model Manual Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[JA Media Files] +LINKTYPE=Dynamic Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\Base +TARGETHIDDEN=Script-defined Folders\\Base + +[Q3 Default Project] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\baseq3\scripts +TARGETHIDDEN=Script-defined Folders\\baseq3\scripts + +[Plugins - PrtView] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\plugins +TARGETHIDDEN=Script-defined Folders\\plugins + +[STVEF Media Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\baseEf +TARGETHIDDEN=Script-defined Folders\\baseEf + +[Program Misc Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[ET Media Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\etmain +TARGETHIDDEN=Script-defined Folders\\etmain + +[SOF2 Executable Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Plugins - BobToolz] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\plugins +TARGETHIDDEN=Script-defined Folders\\plugins + +[Plugins - GTK GenSurf] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\plugins +TARGETHIDDEN=Script-defined Folders\\plugins + +[Radiant Manual Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Terrain Manual Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Plugins - Curry pk3] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\baseq3 +TARGETHIDDEN=Script-defined Folders\\baseq3 + +[Q2 Media Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\baseq2 +TARGETHIDDEN=Script-defined Folders\\baseq2 + +[Shader Manual Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[ET Executable Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Plugins - TexTool] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\plugins +TARGETHIDDEN=Script-defined Folders\\plugins + +[shaderlist-ta] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=Yes +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEVEROVERWRITE +TARGET=\missionpack +TARGETHIDDEN=Script-defined Folders\\missionpack + +[Wolf Exectuable Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Q3 Editor Images - SPoG pk3] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\baseq3 +TARGETHIDDEN=Script-defined Folders\\baseq3 + +[Plugins - bkgrnd2d] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\plugins +TARGETHIDDEN=Script-defined Folders\\plugins + +[TexTool Help] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Heretic2 Media Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\base +TARGETHIDDEN=Script-defined Folders\\base + +[Plugins - Curry pk3 Wolf] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\main +TARGETHIDDEN=Script-defined Folders\\main + +[Q3 Misc Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Program DLL Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Halflife Media Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[TA Teams Manual] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Plugins - Curry] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\plugins +TARGETHIDDEN=Script-defined Folders\\plugins + +[JA Executable Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[STVEF Executable Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Wolf Media Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\main +TARGETHIDDEN=Script-defined Folders\\main + +[JKII Media Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\base +TARGETHIDDEN=Script-defined Folders\\base + +[Quake Executable Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET= +TARGETHIDDEN=Script-defined Folders\ + +[Quake Media Files] +LINKTYPE=Static Link +INFOTYPE=Standard +UNINSTALLABLE=Yes +FILETYPE=No +SELFREGISTERING=No +POTENTIALLY=No +COMPRESS=Yes +OPERATINGSYSTEM= +LANGUAGE= +COMMENT= +COMPRESSDLL= +HTTPLOCATION= +FTPLOCATION= +MISC= +INSTALLATION=NEWERVERSION\NEWERDATE +TARGET=\id1 +TARGETHIDDEN=Script-defined Folders\\id1 + diff --git a/setup/win32/template/File Groups/ET Executable Files.fgl b/setup/win32/template/File Groups/ET Executable Files.fgl new file mode 100644 index 00000000..95abd754 --- /dev/null +++ b/setup/win32/template/File Groups/ET Executable Files.fgl @@ -0,0 +1,117 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\ETPack\game.xlink +file1=<>\ETPack\synapse.config +SubDir0=docs +SubDir1=bitmaps + +[docs] +fulldirectory= +SubDir0=docs\Level Designers Reference + +[docs\Level Designers Reference] +fulldirectory= +file0=<>\ETPack\docs\Level Designers Reference\tracemaps.html +file1=<>\ETPack\docs\Level Designers Reference\appendix_a.html +file2=<>\ETPack\docs\Level Designers Reference\appendix_b.html +file3=<>\ETPack\docs\Level Designers Reference\appendix_c.html +file4=<>\ETPack\docs\Level Designers Reference\arenafile.html +file5=<>\ETPack\docs\Level Designers Reference\cabinets.html +file6=<>\ETPack\docs\Level Designers Reference\campaignfile.html +file7=<>\ETPack\docs\Level Designers Reference\commandmaps.html +file8=<>\ETPack\docs\Level Designers Reference\commandposts.html +file9=<>\ETPack\docs\Level Designers Reference\compiling.html +file10=<>\ETPack\docs\Level Designers Reference\consolecommands.html +file11=<>\ETPack\docs\Level Designers Reference\construction.html +file12=<>\ETPack\docs\Level Designers Reference\contents.html +file13=<>\ETPack\docs\Level Designers Reference\dualobj.html +file14=<>\ETPack\docs\Level Designers Reference\enveffects.html +file15=<>\ETPack\docs\Level Designers Reference\foliage.html +file16=<>\ETPack\docs\Level Designers Reference\glossary.html +file17=<>\ETPack\docs\Level Designers Reference\implicitshaders.html +file18=<>\ETPack\docs\Level Designers Reference\index.html +file19=<>\ETPack\docs\Level Designers Reference\introduction.html +file20=<>\ETPack\docs\Level Designers Reference\movingbobj.html +file21=<>\ETPack\docs\Level Designers Reference\objcameras.html +file22=<>\ETPack\docs\Level Designers Reference\spawnpoints.html +file23=<>\ETPack\docs\Level Designers Reference\speakereditor.html +file24=<>\ETPack\docs\Level Designers Reference\splash.html +file25=<>\ETPack\docs\Level Designers Reference\splinepaths.html +file26=<>\ETPack\docs\Level Designers Reference\surfaceparms.html +SubDir0=docs\Level Designers Reference\images +SubDir1=docs\Level Designers Reference\styles + +[docs\Level Designers Reference\images] +fulldirectory= +file0=<>\ETPack\docs\Level Designers Reference\images\tracemap_red.jpg +file1=<>\ETPack\docs\Level Designers Reference\images\3logos.jpg +file2=<>\ETPack\docs\Level Designers Reference\images\arena.jpg +file3=<>\ETPack\docs\Level Designers Reference\images\bspmenu.jpg +file4=<>\ETPack\docs\Level Designers Reference\images\cabinet_1.jpg +file5=<>\ETPack\docs\Level Designers Reference\images\cabinet_2.jpg +file6=<>\ETPack\docs\Level Designers Reference\images\campaign.jpg +file7=<>\ETPack\docs\Level Designers Reference\images\cmicon_cab.jpg +file8=<>\ETPack\docs\Level Designers Reference\images\cmicon_con.jpg +file9=<>\ETPack\docs\Level Designers Reference\images\cmicon_cp.jpg +file10=<>\ETPack\docs\Level Designers Reference\images\cmicon_custom_1.jpg +file11=<>\ETPack\docs\Level Designers Reference\images\cmicon_custom_2.jpg +file12=<>\ETPack\docs\Level Designers Reference\images\cmicon_des.jpg +file13=<>\ETPack\docs\Level Designers Reference\images\cmicon_spawns.jpg +file14=<>\ETPack\docs\Level Designers Reference\images\commandmap_1.jpg +file15=<>\ETPack\docs\Level Designers Reference\images\commandmap_2.jpg +file16=<>\ETPack\docs\Level Designers Reference\images\commandmap_3.jpg +file17=<>\ETPack\docs\Level Designers Reference\images\commandmap_4.jpg +file18=<>\ETPack\docs\Level Designers Reference\images\commandpost_1.jpg +file19=<>\ETPack\docs\Level Designers Reference\images\commandpost_2.jpg +file20=<>\ETPack\docs\Level Designers Reference\images\commandpost_3.jpg +file21=<>\ETPack\docs\Level Designers Reference\images\construct.jpg +file22=<>\ETPack\docs\Level Designers Reference\images\dualobj_1.jpg +file23=<>\ETPack\docs\Level Designers Reference\images\dualobj_2.jpg +file24=<>\ETPack\docs\Level Designers Reference\images\objcameras_1.jpg +file25=<>\ETPack\docs\Level Designers Reference\images\objcameras_2.jpg +file26=<>\ETPack\docs\Level Designers Reference\images\singleteam_1.jpg +file27=<>\ETPack\docs\Level Designers Reference\images\singleteam_2.jpg +file28=<>\ETPack\docs\Level Designers Reference\images\speaker_1.jpg +file29=<>\ETPack\docs\Level Designers Reference\images\speaker_2.jpg +file30=<>\ETPack\docs\Level Designers Reference\images\speaker_3.jpg +file31=<>\ETPack\docs\Level Designers Reference\images\speaker_4.jpg +file32=<>\ETPack\docs\Level Designers Reference\images\speaker_5.jpg +file33=<>\ETPack\docs\Level Designers Reference\images\splash.jpg +file34=<>\ETPack\docs\Level Designers Reference\images\splinepaths_1.jpg +file35=<>\ETPack\docs\Level Designers Reference\images\splinepaths_2.jpg +file36=<>\ETPack\docs\Level Designers Reference\images\splinepaths_3.jpg +file37=<>\ETPack\docs\Level Designers Reference\images\tankstuff.jpg +file38=<>\ETPack\docs\Level Designers Reference\images\tracemap_alpha.jpg +file39=<>\ETPack\docs\Level Designers Reference\images\tracemap_blue.jpg +file40=<>\ETPack\docs\Level Designers Reference\images\tracemap_green.jpg +SubDir0=docs\Level Designers Reference\images\common + +[docs\Level Designers Reference\images\common] +fulldirectory= +file0=<>\ETPack\docs\Level Designers Reference\images\common\trigger.jpg +file1=<>\ETPack\docs\Level Designers Reference\images\common\caulk.jpg +file2=<>\ETPack\docs\Level Designers Reference\images\common\clip.jpg +file3=<>\ETPack\docs\Level Designers Reference\images\common\clipmissile.jpg +file4=<>\ETPack\docs\Level Designers Reference\images\common\clipweapmetal.jpg +file5=<>\ETPack\docs\Level Designers Reference\images\common\clipweapwood.jpg +file6=<>\ETPack\docs\Level Designers Reference\images\common\hint.jpg +file7=<>\ETPack\docs\Level Designers Reference\images\common\ladder.jpg +file8=<>\ETPack\docs\Level Designers Reference\images\common\lightgrid.jpg +file9=<>\ETPack\docs\Level Designers Reference\images\common\nodraw.jpg +file10=<>\ETPack\docs\Level Designers Reference\images\common\origin.jpg +file11=<>\ETPack\docs\Level Designers Reference\images\common\skip.jpg +file12=<>\ETPack\docs\Level Designers Reference\images\common\terrain.jpg +file13=<>\ETPack\docs\Level Designers Reference\images\common\terrain_sand.jpg +file14=<>\ETPack\docs\Level Designers Reference\images\common\terrain_snow.jpg + +[docs\Level Designers Reference\styles] +fulldirectory= +file0=<>\ETPack\docs\Level Designers Reference\styles\style.css + +[bitmaps] +fulldirectory= +file0=<>\ETPack\bitmaps\splash.bmp + diff --git a/setup/win32/template/File Groups/ET Media Files.fgl b/setup/win32/template/File Groups/ET Media Files.fgl new file mode 100644 index 00000000..b34dd2c4 --- /dev/null +++ b/setup/win32/template/File Groups/ET Media Files.fgl @@ -0,0 +1,463 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\ETPack\common.pk3 +file1=<>\ETPack\lights.pk3 +file2=<>\ETPack\astro-skies.pk3 +file3=<>\ETPack\mapmedia.pk3 +file4=<>\ETPack\goldrush.pcx +SubDir0=scripts +SubDir1=models +SubDir2=maps + +[scripts] +fulldirectory= +file0=<>\ETPack\scripts\xlab_wall.shader +file1=<>\ETPack\scripts\_unsorted.shader +file2=<>\ETPack\scripts\alpha.shader +file3=<>\ETPack\scripts\alpha_sd.shader +file4=<>\ETPack\scripts\assault.shader +file5=<>\ETPack\scripts\assault_rock.shader +file6=<>\ETPack\scripts\awf_props.shader +file7=<>\ETPack\scripts\battery.shader +file8=<>\ETPack\scripts\battery_wall.shader +file9=<>\ETPack\scripts\bbmodels_mapobjects.shader +file10=<>\ETPack\scripts\blimp.shader +file11=<>\ETPack\scripts\bunker_sd.shader +file12=<>\ETPack\scripts\castle_door.shader +file13=<>\ETPack\scripts\castle_floor.shader +file14=<>\ETPack\scripts\castle_window.shader +file15=<>\ETPack\scripts\castle_wood.shader +file16=<>\ETPack\scripts\chat.shader +file17=<>\ETPack\scripts\chat_window.shader +file18=<>\ETPack\scripts\chat_wood.shader +file19=<>\ETPack\scripts\chateau.shader +file20=<>\ETPack\scripts\common.shader +file21=<>\ETPack\scripts\decals.shader +file22=<>\ETPack\scripts\default_project.proj +file23=<>\ETPack\scripts\doors.shader +file24=<>\ETPack\scripts\eerie.shader +file25=<>\ETPack\scripts\egypt_door_sd.shader +file26=<>\ETPack\scripts\egypt_floor_sd.shader +file27=<>\ETPack\scripts\egypt_props_sd.shader +file28=<>\ETPack\scripts\egypt_rock_sd.shader +file29=<>\ETPack\scripts\egypt_trim_sd.shader +file30=<>\ETPack\scripts\egypt_walls_sd.shader +file31=<>\ETPack\scripts\egypt_windows_sd.shader +file32=<>\ETPack\scripts\egypt_wood_sd.shader +file33=<>\ETPack\scripts\et_entities.def +file34=<>\ETPack\scripts\factory_sd.shader +file35=<>\ETPack\scripts\fueldump.shader +file36=<>\ETPack\scripts\gfx_2d.shader +file37=<>\ETPack\scripts\gfx_clipboard.shader +file38=<>\ETPack\scripts\gfx_damage.shader +file39=<>\ETPack\scripts\gfx_hud.shader +file40=<>\ETPack\scripts\gfx_limbo.shader +file41=<>\ETPack\scripts\gfx_misc.shader +file42=<>\ETPack\scripts\goldrush.shader +file43=<>\ETPack\scripts\icons.shader +file44=<>\ETPack\scripts\levelshots.shader +file45=<>\ETPack\scripts\lights.shader +file46=<>\ETPack\scripts\liquids.shader +file47=<>\ETPack\scripts\liquids_sd.shader +file48=<>\ETPack\scripts\mapfx.shader +file49=<>\ETPack\scripts\metal_misc.shader +file50=<>\ETPack\scripts\metals_sd.shader +file51=<>\ETPack\scripts\miltary_door.shader +file52=<>\ETPack\scripts\miltary_trim.shader +file53=<>\ETPack\scripts\miltary_wall.shader +file54=<>\ETPack\scripts\models_ammo.shader +file55=<>\ETPack\scripts\models_foliage.shader +file56=<>\ETPack\scripts\models_furniture.shader +file57=<>\ETPack\scripts\models_mapobjects.shader +file58=<>\ETPack\scripts\models_multiplayer.shader +file59=<>\ETPack\scripts\models_players.shader +file60=<>\ETPack\scripts\models_shards.shader +file61=<>\ETPack\scripts\models_weapons2.shader +file62=<>\ETPack\scripts\mp_goldrush.shader +file63=<>\ETPack\scripts\mp_guns.shader +file64=<>\ETPack\scripts\mp_railgun.shader +file65=<>\ETPack\scripts\mp_rocket.shader +file66=<>\ETPack\scripts\mp_seawall.shader +file67=<>\ETPack\scripts\mp_siwa.shader +file68=<>\ETPack\scripts\mp_wurzburg.shader +file69=<>\ETPack\scripts\props.shader +file70=<>\ETPack\scripts\props_sd.shader +file71=<>\ETPack\scripts\radar.shader +file72=<>\ETPack\scripts\railgun_props.shader +file73=<>\ETPack\scripts\railway_sd.shader +file74=<>\ETPack\scripts\rock.shader +file75=<>\ETPack\scripts\rubble.shader +file76=<>\ETPack\scripts\seawall_wall.shader +file77=<>\ETPack\scripts\sfx.shader +file78=<>\ETPack\scripts\shaderlist.txt +file79=<>\ETPack\scripts\shadows.shader +file80=<>\ETPack\scripts\siwa_fx_sd.shader +file81=<>\ETPack\scripts\siwa_props_sd.shader +file82=<>\ETPack\scripts\siwa_skyboxes_sd.shader +file83=<>\ETPack\scripts\skies.shader +file84=<>\ETPack\scripts\skies_sd.shader +file85=<>\ETPack\scripts\snow.shader +file86=<>\ETPack\scripts\snow_sd.shader +file87=<>\ETPack\scripts\sprites.shader +file88=<>\ETPack\scripts\stone.shader +file89=<>\ETPack\scripts\swf.shader +file90=<>\ETPack\scripts\temperate_sd.shader +file91=<>\ETPack\scripts\terrain.shader +file92=<>\ETPack\scripts\textures.shader +file93=<>\ETPack\scripts\tobruk_wall_sd.shader +file94=<>\ETPack\scripts\tobruk_windows_sd.shader +file95=<>\ETPack\scripts\town_props.shader +file96=<>\ETPack\scripts\town_roof.shader +file97=<>\ETPack\scripts\town_wall.shader +file98=<>\ETPack\scripts\town_window.shader +file99=<>\ETPack\scripts\town_wood.shader +file100=<>\ETPack\scripts\tree.shader +file101=<>\ETPack\scripts\ui_assets2.shader +file102=<>\ETPack\scripts\ui_assets.shader +file103=<>\ETPack\scripts\villa_sd.shader +file104=<>\ETPack\scripts\village.shader +file105=<>\ETPack\scripts\wood.shader +file106=<>\ETPack\scripts\xlab_door.shader +file107=<>\ETPack\scripts\xlab_props.shader + +[models] +fulldirectory= +SubDir0=models\mapobjects + +[models\mapobjects] +fulldirectory= +SubDir0=models\mapobjects\archeology +SubDir1=models\mapobjects\battery_rocks +SubDir2=models\mapobjects\blitz_sd +SubDir3=models\mapobjects\book +SubDir4=models\mapobjects\chandelier +SubDir5=models\mapobjects\coat +SubDir6=models\mapobjects\dinghy_sd +SubDir7=models\mapobjects\flag +SubDir8=models\mapobjects\furniture +SubDir9=models\mapobjects\light +SubDir10=models\mapobjects\logs_sd +SubDir11=models\mapobjects\military_trim +SubDir12=models\mapobjects\xlab +SubDir13=models\mapobjects\trees_sd +SubDir14=models\mapobjects\tree_temperate_sd +SubDir15=models\mapobjects\tree_desert_sd +SubDir16=models\mapobjects\tanks_sd +SubDir17=models\mapobjects\spool_sd +SubDir18=models\mapobjects\shell_sd +SubDir19=models\mapobjects\seawall_rocks +SubDir20=models\mapobjects\rocks_sd +SubDir21=models\mapobjects\raster +SubDir22=models\mapobjects\props_sd +SubDir23=models\mapobjects\plants_sd +SubDir24=models\mapobjects\plants +SubDir25=models\mapobjects\pak75_sd +SubDir26=models\mapobjects\electronics +SubDir27=models\mapobjects\siwa_props_sd +SubDir28=models\mapobjects\prefabs_sd +SubDir29=models\mapobjects\vehicles +SubDir30=models\mapobjects\barrel_sd + +[models\mapobjects\archeology] +fulldirectory= +file0=<>\ETPack\models\mapobjects\archeology\vessel.md3 +file1=<>\ETPack\models\mapobjects\archeology\obelisk.md3 +file2=<>\ETPack\models\mapobjects\archeology\pitcher.md3 +file3=<>\ETPack\models\mapobjects\archeology\vase1.md3 +file4=<>\ETPack\models\mapobjects\archeology\vase2.md3 +file5=<>\ETPack\models\mapobjects\archeology\vase3.md3 +file6=<>\ETPack\models\mapobjects\archeology\vase4.md3 +file7=<>\ETPack\models\mapobjects\archeology\vase5.md3 + +[models\mapobjects\battery_rocks] +fulldirectory= +file0=<>\ETPack\models\mapobjects\battery_rocks\rocks3.ase +file1=<>\ETPack\models\mapobjects\battery_rocks\rocks1.ase +file2=<>\ETPack\models\mapobjects\battery_rocks\rocks2.ase + +[models\mapobjects\blitz_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\blitz_sd\blitzwheelsf_s_mm.md3 +file1=<>\ETPack\models\mapobjects\blitz_sd\blitz_sd_interior.tga +file2=<>\ETPack\models\mapobjects\blitz_sd\blitz_sd_s.tga +file3=<>\ETPack\models\mapobjects\blitz_sd\blitzbody2_s.md3 +file4=<>\ETPack\models\mapobjects\blitz_sd\blitzbody2_s.tag +file5=<>\ETPack\models\mapobjects\blitz_sd\blitzbody3_mm.md3 +file6=<>\ETPack\models\mapobjects\blitz_sd\blitzbody3_s.md3 +file7=<>\ETPack\models\mapobjects\blitz_sd\blitzbody3_s_mm.md3 +file8=<>\ETPack\models\mapobjects\blitz_sd\blitzwheelsb_mm.md3 +file9=<>\ETPack\models\mapobjects\blitz_sd\blitzwheelsb_s.md3 +file10=<>\ETPack\models\mapobjects\blitz_sd\blitzwheelsb_s_mm.md3 +file11=<>\ETPack\models\mapobjects\blitz_sd\blitzwheelsf_mm.md3 +file12=<>\ETPack\models\mapobjects\blitz_sd\blitzwheelsf_s.md3 + +[models\mapobjects\book] +fulldirectory= +file0=<>\ETPack\models\mapobjects\book\book.md3 + +[models\mapobjects\chandelier] +fulldirectory= +file0=<>\ETPack\models\mapobjects\chandelier\chandlier_new.md3 +file1=<>\ETPack\models\mapobjects\chandelier\chandelier.md3 +file2=<>\ETPack\models\mapobjects\chandelier\chandelier_lt.md3 +file3=<>\ETPack\models\mapobjects\chandelier\chandelier_lt_sm.md3 + +[models\mapobjects\coat] +fulldirectory= +file0=<>\ETPack\models\mapobjects\coat\coat.md3 + +[models\mapobjects\dinghy_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\dinghy_sd\dinghy.md3 + +[models\mapobjects\flag] +fulldirectory= +file0=<>\ETPack\models\mapobjects\flag\flag_axis.md3 +file1=<>\ETPack\models\mapobjects\flag\flag_allied.md3 + +[models\mapobjects\furniture] +fulldirectory= +file0=<>\ETPack\models\mapobjects\furniture\type.mdc +file1=<>\ETPack\models\mapobjects\furniture\bunks.md3 +file2=<>\ETPack\models\mapobjects\furniture\chat_armchair.md3 +file3=<>\ETPack\models\mapobjects\furniture\clubchair.md3 +file4=<>\ETPack\models\mapobjects\furniture\faucet.md3 +file5=<>\ETPack\models\mapobjects\furniture\furnace1.md3 +file6=<>\ETPack\models\mapobjects\furniture\type.md3 + +[models\mapobjects\light] +fulldirectory= +file0=<>\ETPack\models\mapobjects\light\sd_sconce3.md3 +file1=<>\ETPack\models\mapobjects\light\cagelight.md3 +file2=<>\ETPack\models\mapobjects\light\cagelighta5k.md3 +file3=<>\ETPack\models\mapobjects\light\cagelighta9k.md3 +file4=<>\ETPack\models\mapobjects\light\cagelighta.md3 +file5=<>\ETPack\models\mapobjects\light\cagelightr.md3 +file6=<>\ETPack\models\mapobjects\light\lantern.md3 +file7=<>\ETPack\models\mapobjects\light\p_nolight.md3 +file8=<>\ETPack\models\mapobjects\light\pendant10k.md3 +file9=<>\ETPack\models\mapobjects\light\sd_chad2.md3 +file10=<>\ETPack\models\mapobjects\light\bel_lamp_top40.md3 + +[models\mapobjects\logs_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\logs_sd\stump05s.md3 +file1=<>\ETPack\models\mapobjects\logs_sd\log1.md3 +file2=<>\ETPack\models\mapobjects\logs_sd\log2.2.md3 +file3=<>\ETPack\models\mapobjects\logs_sd\log2.md3 +file4=<>\ETPack\models\mapobjects\logs_sd\log2.tga +file5=<>\ETPack\models\mapobjects\logs_sd\log3.md3 +file6=<>\ETPack\models\mapobjects\logs_sd\stump02.md3 +file7=<>\ETPack\models\mapobjects\logs_sd\stump02s.md3 +file8=<>\ETPack\models\mapobjects\logs_sd\stump03.md3 +file9=<>\ETPack\models\mapobjects\logs_sd\stump03s.md3 +file10=<>\ETPack\models\mapobjects\logs_sd\stump04.md3 +file11=<>\ETPack\models\mapobjects\logs_sd\stump04s.md3 + +[models\mapobjects\military_trim] +fulldirectory= +file0=<>\ETPack\models\mapobjects\miltary_trim\sandbag1s.md3 +file1=<>\ETPack\models\mapobjects\miltary_trim\dragon_teeth.md3 + +[models\mapobjects\xlab] +fulldirectory= +file0=<>\ETPack\models\mapobjects\xlab\cart.md3 + +[models\mapobjects\trees_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\trees_sd\tree_e.md3 +file1=<>\ETPack\models\mapobjects\trees_sd\brokentree_b.md3 +file2=<>\ETPack\models\mapobjects\trees_sd\brokentree_t.md3 +file3=<>\ETPack\models\mapobjects\trees_sd\damptrunk01.tga +file4=<>\ETPack\models\mapobjects\trees_sd\tree_a.md3 +file5=<>\ETPack\models\mapobjects\trees_sd\tree_b.md3 +file6=<>\ETPack\models\mapobjects\trees_sd\tree_c.md3 +file7=<>\ETPack\models\mapobjects\trees_sd\tree_d.md3 + +[models\mapobjects\tree_temperate_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\tree_temperate_sd\trunk_cut.tga +file1=<>\ETPack\models\mapobjects\tree_temperate_sd\bush_temperate1.md3 +file2=<>\ETPack\models\mapobjects\tree_temperate_sd\bush_temperate2.md3 +file3=<>\ETPack\models\mapobjects\tree_temperate_sd\bush_temperate3.md3 +file4=<>\ETPack\models\mapobjects\tree_temperate_sd\bush_temperate4.md3 +file5=<>\ETPack\models\mapobjects\tree_temperate_sd\cuttrunk.md3 +file6=<>\ETPack\models\mapobjects\tree_temperate_sd\floor_leaf1.md3 +file7=<>\ETPack\models\mapobjects\tree_temperate_sd\floor_leaf1.tga +file8=<>\ETPack\models\mapobjects\tree_temperate_sd\highfoliage1.md3 +file9=<>\ETPack\models\mapobjects\tree_temperate_sd\highfoliage2.md3 +file10=<>\ETPack\models\mapobjects\tree_temperate_sd\hightree1.md3 +file11=<>\ETPack\models\mapobjects\tree_temperate_sd\hightree2.md3 +file12=<>\ETPack\models\mapobjects\tree_temperate_sd\hightrunk.md3 +file13=<>\ETPack\models\mapobjects\tree_temperate_sd\mediumfoliage1.md3 +file14=<>\ETPack\models\mapobjects\tree_temperate_sd\mediumfoliage2.md3 +file15=<>\ETPack\models\mapobjects\tree_temperate_sd\mediumtrunk.md3 +file16=<>\ETPack\models\mapobjects\tree_temperate_sd\smallfoliage1.md3 +file17=<>\ETPack\models\mapobjects\tree_temperate_sd\smallfoliage2.md3 +file18=<>\ETPack\models\mapobjects\tree_temperate_sd\smalltrunk.md3 +file19=<>\ETPack\models\mapobjects\tree_temperate_sd\tree_temperate_high.md3 + +[models\mapobjects\tree_desert_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\tree_desert_sd\tall.md3 +file1=<>\ETPack\models\mapobjects\tree_desert_sd\newpalm.md3 +file2=<>\ETPack\models\mapobjects\tree_desert_sd\newpalmsmall.md3 +file3=<>\ETPack\models\mapobjects\tree_desert_sd\newpalmtall.md3 +file4=<>\ETPack\models\mapobjects\tree_desert_sd\palm_bark2.tga +file5=<>\ETPack\models\mapobjects\tree_desert_sd\palm_bark.tga +file6=<>\ETPack\models\mapobjects\tree_desert_sd\palm_leaves1.md3 +file7=<>\ETPack\models\mapobjects\tree_desert_sd\palm_leaves2.md3 +file8=<>\ETPack\models\mapobjects\tree_desert_sd\palm_leaves2.tga +file9=<>\ETPack\models\mapobjects\tree_desert_sd\palm_leaves3.md3 +file10=<>\ETPack\models\mapobjects\tree_desert_sd\palm_leaves.tga +file11=<>\ETPack\models\mapobjects\tree_desert_sd\palm_trunk1.md3 +file12=<>\ETPack\models\mapobjects\tree_desert_sd\palm_trunk2.md3 +file13=<>\ETPack\models\mapobjects\tree_desert_sd\palm_trunk3.md3 +file14=<>\ETPack\models\mapobjects\tree_desert_sd\small.md3 + +[models\mapobjects\tanks_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\tanks_sd\wheel_b.tga +file1=<>\ETPack\models\mapobjects\tanks_sd\jagdpanther_additions_snow.tga +file2=<>\ETPack\models\mapobjects\tanks_sd\jagdpanther_snow.tga +file3=<>\ETPack\models\mapobjects\tanks_sd\jagdpanther_snow_body.md3 +file4=<>\ETPack\models\mapobjects\tanks_sd\jagdpanther_snow_body.tag +file5=<>\ETPack\models\mapobjects\tanks_sd\jagdpanther_snow_damaged_body.md3 +file6=<>\ETPack\models\mapobjects\tanks_sd\jagdpanther_snow_damaged_body.tag +file7=<>\ETPack\models\mapobjects\tanks_sd\jagdpanther_snow_ext_hp.md3 +file8=<>\ETPack\models\mapobjects\tanks_sd\jagdpanther_snow_ext_lp.md3 +file9=<>\ETPack\models\mapobjects\tanks_sd\jagdpanther_snow_turret.md3 +file10=<>\ETPack\models\mapobjects\tanks_sd\wheel2_a_s.tga +file11=<>\ETPack\models\mapobjects\tanks_sd\wheel_a_s.tga + +[models\mapobjects\spool_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\spool_sd\spool_s.md3 + +[models\mapobjects\shell_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\shell_sd\big_shell.tga +file1=<>\ETPack\models\mapobjects\shell_sd\big_shell.md3 + +[models\mapobjects\seawall_rocks] +fulldirectory= +file0=<>\ETPack\models\mapobjects\seawall_rocks\rocks3.ase +file1=<>\ETPack\models\mapobjects\seawall_rocks\rocks1.ase +file2=<>\ETPack\models\mapobjects\seawall_rocks\rocks2.ase + +[models\mapobjects\rocks_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\rocks_sd\rock_tunnelsiwa.jpg +file1=<>\ETPack\models\mapobjects\rocks_sd\rock_big1.md3 +file2=<>\ETPack\models\mapobjects\rocks_sd\rock_big2.md3 +file3=<>\ETPack\models\mapobjects\rocks_sd\rock_big3.md3 +file4=<>\ETPack\models\mapobjects\rocks_sd\rock_big4.md3 +file5=<>\ETPack\models\mapobjects\rocks_sd\rock_big5.md3 +file6=<>\ETPack\models\mapobjects\rocks_sd\rock_desert.jpg +file7=<>\ETPack\models\mapobjects\rocks_sd\rock_desert_big.jpg +file8=<>\ETPack\models\mapobjects\rocks_sd\rock_desert_small.jpg +file9=<>\ETPack\models\mapobjects\rocks_sd\rock_medium1.md3 +file10=<>\ETPack\models\mapobjects\rocks_sd\rock_medium2.md3 +file11=<>\ETPack\models\mapobjects\rocks_sd\rock_medium3.md3 +file12=<>\ETPack\models\mapobjects\rocks_sd\rock_small1.md3 +file13=<>\ETPack\models\mapobjects\rocks_sd\rock_small2.md3 +file14=<>\ETPack\models\mapobjects\rocks_sd\rock_small3.md3 +file15=<>\ETPack\models\mapobjects\rocks_sd\rock_snow_small.jpg +file16=<>\ETPack\models\mapobjects\rocks_sd\rock_temperate.jpg +file17=<>\ETPack\models\mapobjects\rocks_sd\rock_temperate_big.jpg + +[models\mapobjects\raster] +fulldirectory= +file0=<>\ETPack\models\mapobjects\raster\moto_wheel.md3 +file1=<>\ETPack\models\mapobjects\raster\moto.md3 +file2=<>\ETPack\models\mapobjects\raster\moto_body2.md3 +file3=<>\ETPack\models\mapobjects\raster\moto_body.md3 +file4=<>\ETPack\models\mapobjects\raster\moto_gastank.md3 +file5=<>\ETPack\models\mapobjects\raster\moto_handelbar.md3 + +[models\mapobjects\props_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\props_sd\xlight_fg2_oasis.md3 +file1=<>\ETPack\models\mapobjects\props_sd\basket.md3 +file2=<>\ETPack\models\mapobjects\props_sd\basketlevel_high.md3 +file3=<>\ETPack\models\mapobjects\props_sd\basketlevel_low.md3 +file4=<>\ETPack\models\mapobjects\props_sd\fuel_can.md3 +file5=<>\ETPack\models\mapobjects\props_sd\snowrock1_a.md3 +file6=<>\ETPack\models\mapobjects\props_sd\snowrock2_a.md3 +file7=<>\ETPack\models\mapobjects\props_sd\snowrock3_a.md3 +file8=<>\ETPack\models\mapobjects\props_sd\snowrock.tga +file9=<>\ETPack\models\mapobjects\props_sd\vase.md3 +file10=<>\ETPack\models\mapobjects\props_sd\vase_broken_1.md3 +file11=<>\ETPack\models\mapobjects\props_sd\vase_broken_2.md3 +file12=<>\ETPack\models\mapobjects\props_sd\vase_broken_bits.md3 +file13=<>\ETPack\models\mapobjects\props_sd\xlight_fg2.md3 + +[models\mapobjects\plants_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\plants_sd\shrub_green2.tga +file1=<>\ETPack\models\mapobjects\plants_sd\bush_high.md3 +file2=<>\ETPack\models\mapobjects\plants_sd\bush_low.md3 +file3=<>\ETPack\models\mapobjects\plants_sd\bush_snow2.tga +file4=<>\ETPack\models\mapobjects\plants_sd\catail3.tga +file5=<>\ETPack\models\mapobjects\plants_sd\catail.md3 +file6=<>\ETPack\models\mapobjects\plants_sd\deadbranch2_damp.tga +file7=<>\ETPack\models\mapobjects\plants_sd\deadbranch3_damp.tga +file8=<>\ETPack\models\mapobjects\plants_sd\grass_dry1.tga +file9=<>\ETPack\models\mapobjects\plants_sd\grass_dry2.tga +file10=<>\ETPack\models\mapobjects\plants_sd\grass_dry3.tga +file11=<>\ETPack\models\mapobjects\plants_sd\grass_green2.tga +file12=<>\ETPack\models\mapobjects\plants_sd\grass_green3.tga +file13=<>\ETPack\models\mapobjects\plants_sd\grass_low.md3 +file14=<>\ETPack\models\mapobjects\plants_sd\grassdryfoliage.md3 +file15=<>\ETPack\models\mapobjects\plants_sd\grassdryfoliage_oasis.md3 +file16=<>\ETPack\models\mapobjects\plants_sd\grassfoliage.md3 +file17=<>\ETPack\models\mapobjects\plants_sd\leaf1.tga +file18=<>\ETPack\models\mapobjects\plants_sd\leaf2.tga +file19=<>\ETPack\models\mapobjects\plants_sd\leaf3.tga +file20=<>\ETPack\models\mapobjects\plants_sd\leaf_high.md3 +file21=<>\ETPack\models\mapobjects\plants_sd\leaf_low.md3 +file22=<>\ETPack\models\mapobjects\plants_sd\mil1.tga +file23=<>\ETPack\models\mapobjects\plants_sd\mil2.tga +file24=<>\ETPack\models\mapobjects\plants_sd\mil.md3 +file25=<>\ETPack\models\mapobjects\plants_sd\shrub_green1.tga + +[models\mapobjects\plants] +fulldirectory= +file0=<>\ETPack\models\mapobjects\plants\bush.md3 +file1=<>\ETPack\models\mapobjects\plants\bush2.md3 + +[models\mapobjects\pak75_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\pak75_sd\pak75-a.tga +file1=<>\ETPack\models\mapobjects\pak75_sd\pak75_broken_s.md3 +file2=<>\ETPack\models\mapobjects\pak75_sd\pak75_s.md3 +file3=<>\ETPack\models\mapobjects\pak75_sd\pak75_s.tga + +[models\mapobjects\electronics] +fulldirectory= +file0=<>\ETPack\models\mapobjects\electronics\phone.md3 + +[models\mapobjects\siwa_props_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\siwa_props_sd\siwa_cushiona1.md3 + +[models\mapobjects\prefabs_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\prefabs_sd\goldrush_laundry.ase +file1=<>\ETPack\models\mapobjects\prefabs_sd\goldrush_bank_roof.ase + +[models\mapobjects\vehicles] +fulldirectory= +file0=<>\ETPack\models\mapobjects\vehicles\wagon_tilt.md3 + +[models\mapobjects\barrel_sd] +fulldirectory= +file0=<>\ETPack\models\mapobjects\barrel_sd\barrel.md3 + +[maps] +fulldirectory= +file0=<>\ETPack\maps\goldrush.map + diff --git a/setup/win32/template/File Groups/Example Files.fgl b/setup/win32/template/File Groups/Example Files.fgl new file mode 100644 index 00000000..04090b95 --- /dev/null +++ b/setup/win32/template/File Groups/Example Files.fgl @@ -0,0 +1,4 @@ +[General] +Type=FILELIST +Version=1.10.000 + diff --git a/setup/win32/template/File Groups/Halflife Executable Files.fgl b/setup/win32/template/File Groups/Halflife Executable Files.fgl new file mode 100644 index 00000000..a3412cf8 --- /dev/null +++ b/setup/win32/template/File Groups/Halflife Executable Files.fgl @@ -0,0 +1,76 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\HalflifePack\gametools\common-hydra.wad +file1=<>\HalflifePack\gametools\radiant.wad +file2=<>\HalflifePack\gametools\synapse.config +SubDir0=modules +SubDir1=plugins +SubDir2=scripts +SubDir3=sprites +SubDir4=ZHLT + +[modules] +fulldirectory=modules + +[plugins] +fulldirectory=plugins +file0=<>\GtkRadiant\contrib\hydratoolz\Release\hydratoolz.dll + +[scripts] +fulldirectory= +file0=<>\HalflifePack\gametools\scripts\common-hydra.shader +file1=<>\HalflifePack\gametools\scripts\shaderlist.txt + +[sprites] +fulldirectory= +file0=<>\HalflifePack\gametools\sprites\speaker.spr +file1=<>\HalflifePack\gametools\sprites\lightbulb.spr +SubDir0=sprites\cs + +[sprites\cs] +fulldirectory= +file0=<>\HalflifePack\gametools\sprites\CS\VIP.spr +file1=<>\HalflifePack\gametools\sprites\CS\Armoury.spr +file2=<>\HalflifePack\gametools\sprites\CS\BombTarget.spr +file3=<>\HalflifePack\gametools\sprites\CS\CT.spr +file4=<>\HalflifePack\gametools\sprites\CS\Hostage.spr +file5=<>\HalflifePack\gametools\sprites\CS\HostageRescue.spr +file6=<>\HalflifePack\gametools\sprites\CS\MapParams.spr +file7=<>\HalflifePack\gametools\sprites\CS\Terrorist.spr + +[ZHLT] +fulldirectory= +file0=<>\HalflifePack\gametools\ZHLT\ZonersHalflifeTools.html +file1=<>\HalflifePack\gametools\ZHLT\CCXX32.dll +file2=<>\HalflifePack\gametools\ZHLT\clipnode.map +file3=<>\HalflifePack\gametools\ZHLT\default.hull +file4=<>\HalflifePack\gametools\ZHLT\dumpbrush.pl +file5=<>\HalflifePack\gametools\ZHLT\dumpent.pl +file6=<>\HalflifePack\gametools\ZHLT\dumpline.pl +file7=<>\HalflifePack\gametools\ZHLT\hlbsp.exe +file8=<>\HalflifePack\gametools\ZHLT\hlcsg.exe +file9=<>\HalflifePack\gametools\ZHLT\hlrad.exe +file10=<>\HalflifePack\gametools\ZHLT\hlvis.exe +file11=<>\HalflifePack\gametools\ZHLT\netvis.exe +file12=<>\HalflifePack\gametools\ZHLT\netvis.html +file13=<>\HalflifePack\gametools\ZHLT\numberbrush.pl +file14=<>\HalflifePack\gametools\ZHLT\ripent.exe +file15=<>\HalflifePack\gametools\ZHLT\stripnulents.pl +file16=<>\HalflifePack\gametools\ZHLT\tfcabbrev.pl +file17=<>\HalflifePack\gametools\ZHLT\zeditor.wad +file18=<>\HalflifePack\gametools\ZHLT\zhlt.css +file19=<>\HalflifePack\gametools\ZHLT\zhlt.wad +file20=<>\HalflifePack\gametools\ZHLT\ZHLTIntro.html +file21=<>\HalflifePack\gametools\ZHLT\ZHLTProblems.html +file22=<>\HalflifePack\gametools\ZHLT\ZHLTReference.html +SubDir0=ZHLT\pics + +[ZHLT\pics] +fulldirectory= +file0=<>\HalflifePack\gametools\ZHLT\pics\coplanar-1.gif +file1=<>\HalflifePack\gametools\ZHLT\pics\coplanar-2.gif +file2=<>\HalflifePack\gametools\ZHLT\pics\max_portals_on_leaf.gif + diff --git a/setup/win32/template/File Groups/Halflife Media Files.fgl b/setup/win32/template/File Groups/Halflife Media Files.fgl new file mode 100644 index 00000000..b57e3e68 --- /dev/null +++ b/setup/win32/template/File Groups/Halflife Media Files.fgl @@ -0,0 +1,30 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=valve +SubDir1=cstrike + +[valve] +fulldirectory= +SubDir0=valve\scripts + +[valve\scripts] +fulldirectory= +file0=<>\HalflifePack\valve\scripts\halflife.fgd +file1=<>\HalflifePack\valve\scripts\default_project.proj + +[cstrike] +fulldirectory= +SubDir0=cstrike\scripts +SubDir1=cstrike\maps + +[cstrike\scripts] +fulldirectory= +file0=<>\HalflifePack\cstrike\scripts\halflife-cs.fgd + +[cstrike\maps] +fulldirectory= +file0=<>\HalflifePack\cstrike\maps\de_mexico-sample.map + diff --git a/setup/win32/template/File Groups/Heretic2 Executable Files.fgl b/setup/win32/template/File Groups/Heretic2 Executable Files.fgl new file mode 100644 index 00000000..2d850b81 --- /dev/null +++ b/setup/win32/template/File Groups/Heretic2 Executable Files.fgl @@ -0,0 +1,18 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\tools\quake2\qdata_heretic2\Release\qdata3.exe +file1=<>\GtkRadiant\tools\quake2\q2map\Release\q2map.exe +file2=<>\Her2Pack\synapse.config +file3=<>\Her2Pack\game.xlink +file4=<>\libxml2\win32\binaries-release\libxml2.dll +SubDir0=modules + +[modules] +fulldirectory= +file0=<>\GtkRadiant\plugins\vfspak\Release\vfspak.dll +file1=<>\GtkRadiant\plugins\imagem8\Release\imagem8.dll +file2=<>\GtkRadiant\plugins\surface_heretic2\Release\surface_heretic2.dll + diff --git a/setup/win32/template/File Groups/Heretic2 Media Files.fgl b/setup/win32/template/File Groups/Heretic2 Media Files.fgl new file mode 100644 index 00000000..4e7124c0 --- /dev/null +++ b/setup/win32/template/File Groups/Heretic2 Media Files.fgl @@ -0,0 +1,66 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=models +SubDir1=scripts +SubDir2=textures + +[models] +fulldirectory= +SubDir0=models\radiant + +[models\radiant] +fulldirectory= +SubDir0=models\radiant\items + +[models\radiant\items] +fulldirectory= +SubDir0=models\radiant\items\mana +SubDir1=models\radiant\items\weapons + +[models\radiant\items\mana] +fulldirectory= +SubDir0=models\radiant\items\mana\defensive + +[models\radiant\items\mana\defensive] +fulldirectory= +SubDir0=models\radiant\items\mana\defensive\full +SubDir1=models\radiant\items\mana\defensive\half + +[models\radiant\items\mana\defensive\full] +fulldirectory= +file0=<>\Her2Pack\base\models\radiant\items\mana\defensive\full\tris.fm +file1=<>\Her2Pack\base\models\radiant\items\mana\defensive\full\!skin_def2.m8 +file2=<>\Her2Pack\base\models\radiant\items\mana\defensive\full\!skin_off2.m8 + +[models\radiant\items\mana\defensive\half] +fulldirectory= +file0=<>\Her2Pack\base\models\radiant\items\mana\defensive\half\tris.fm +file1=<>\Her2Pack\base\models\radiant\items\mana\defensive\half\!skin_def1.m8 +file2=<>\Her2Pack\base\models\radiant\items\mana\defensive\half\!skin_off1.m8 + +[models\radiant\items\weapons] +fulldirectory= +SubDir0=models\radiant\items\weapons\pbow + +[models\radiant\items\weapons\pbow] +fulldirectory= +file0=<>\Her2Pack\base\models\radiant\items\weapons\pbow\tris.fm +file1=<>\Her2Pack\base\models\radiant\items\weapons\pbow\!skin2.pcx.m8 +file2=<>\Her2Pack\base\models\radiant\items\weapons\pbow\!skin.pcx.m8 + +[scripts] +fulldirectory= +file0=<>\Her2Pack\base\scripts\entities.def +file1=<>\Her2Pack\base\scripts\default_project.proj + +[textures] +fulldirectory= +SubDir0=textures\radiant + +[textures\radiant] +fulldirectory= +file0=<>\Her2Pack\base\textures\radiant\notex.m8 + diff --git a/setup/win32/template/File Groups/JA Executable Files.fgl b/setup/win32/template/File Groups/JA Executable Files.fgl new file mode 100644 index 00000000..1376ab8c --- /dev/null +++ b/setup/win32/template/File Groups/JA Executable Files.fgl @@ -0,0 +1,58 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\JAPack\game.xlink +file1=<>\JAPack\synapse.config +SubDir0=docs +SubDir1=bitmaps +SubDir2=Tools + +[docs] +fulldirectory= +file0=<>\JAPack\docs\Siege.doc +file1=<>\JAPack\docs\JASDK.message.txt + +[bitmaps] +fulldirectory= +file0=<>\JAPack\bitmaps\splash.bmp + +[Tools] +fulldirectory= +file0=<>\JAPack\Tools\ShaderEd2.exe +file1=<>\JAPack\Tools\Assimilate.exe +file2=<>\JAPack\Tools\behaved.bhc +file3=<>\JAPack\Tools\BehavEd.exe +file4=<>\JAPack\Tools\Bot waypoint tutorial.doc +file5=<>\JAPack\Tools\carcass.exe +file6=<>\JAPack\Tools\Disclaimer-JediAcademy-Tools.htm +file7=<>\JAPack\Tools\IBIze.exe +file8=<>\JAPack\Tools\ICARUS Manual.doc +file9=<>\JAPack\Tools\JAscripts.zip +file10=<>\JAPack\Tools\JKA Game Source License.htm +file11=<>\JAPack\Tools\MD3View.exe +file12=<>\JAPack\Tools\ModView.exe +file13=<>\JAPack\Tools\Readme.txt +file14=<>\JAPack\Tools\roq.exe +SubDir0=Tools\docs +SubDir1=Tools\EffectsEd + +[Tools\docs] +fulldirectory= +file0=<>\JAPack\Tools\docs\template.vwp +file1=<>\JAPack\Tools\docs\NPC_read_me.txt +file2=<>\JAPack\Tools\docs\sab_read_me.txt +file3=<>\JAPack\Tools\docs\template.veh + +[Tools\EffectsEd] +fulldirectory= +file0=<>\JAPack\Tools\EffectsEd\Using_EffectsEd.doc +file1=<>\JAPack\Tools\EffectsEd\brick.jpg +file2=<>\JAPack\Tools\EffectsEd\clamp.jpg +file3=<>\JAPack\Tools\EffectsEd\dirt.jpg +file4=<>\JAPack\Tools\EffectsEd\EffectsEd.exe +file5=<>\JAPack\Tools\EffectsEd\none.jpg +file6=<>\JAPack\Tools\EffectsEd\stucco.jpg +file7=<>\JAPack\Tools\EffectsEd\transitions.exe + diff --git a/setup/win32/template/File Groups/JA Media Files.fgl b/setup/win32/template/File Groups/JA Media Files.fgl new file mode 100644 index 00000000..b0417d89 --- /dev/null +++ b/setup/win32/template/File Groups/JA Media Files.fgl @@ -0,0 +1,9 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[DYNAMIC] +FOLDER=<> +INCLUDESUBDIR=YES +WILDCARD0=*.* + diff --git a/setup/win32/template/File Groups/JKII Executable Files.fgl b/setup/win32/template/File Groups/JKII Executable Files.fgl new file mode 100644 index 00000000..92847eb0 --- /dev/null +++ b/setup/win32/template/File Groups/JKII Executable Files.fgl @@ -0,0 +1,55 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\JK2Pack\game.xlink +file1=<>\JK2Pack\bin\BehavEd.bhc +file2=<>\RavenPack\bin\BehavEd.exe +file3=<>\RavenPack\bin\EffectsEd.exe +file4=<>\RavenPack\bin\IBIze.exe +file5=<>\RavenPack\bin\roq.exe +file6=<>\JK2Pack\synapse.config +SubDir0=docs + +[docs] +fulldirectory= +file0=<>\JK2Pack\docs\Bot waypoint tutorial.htm +file1=<>\RavenPack\docs\Using_EffectsEd.htm +file2=<>\RavenPack\docs\Guide to SOF2JK2 Vertigons.htm +file3=<>\RavenPack\docs\Guide to SOF2JK2 Weather.htm +file4=<>\RavenPack\docs\ICARUS Manual.htm +file5=<>\RavenPack\docs\Q3_Enhancements.htm +file6=<>\RavenPack\docs\ROQ_Files.htm +SubDir0=docs\GtkRadiant_JK2_HOWTO +SubDir1=docs\Using_EffectsEd_files + +[docs\GtkRadiant_JK2_HOWTO] +fulldirectory= +file0=<>\JK2Pack\docs\GtkRadiant_JK2_HOWTO\index.html +file1=<>\JK2Pack\docs\GtkRadiant_JK2_HOWTO\bsp_menu.png +file2=<>\JK2Pack\docs\GtkRadiant_JK2_HOWTO\game1.png +file3=<>\JK2Pack\docs\GtkRadiant_JK2_HOWTO\game2.png +file4=<>\JK2Pack\docs\GtkRadiant_JK2_HOWTO\bsp_console.png +file5=<>\JK2Pack\docs\GtkRadiant_JK2_HOWTO\project.png + +[docs\Using_EffectsEd_files] +fulldirectory= +file0=<>\RavenPack\docs\Using_EffectsEd_files\filelist.xml +file1=<>\RavenPack\docs\Using_EffectsEd_files\image001.jpg +file2=<>\RavenPack\docs\Using_EffectsEd_files\image002.jpg +file3=<>\RavenPack\docs\Using_EffectsEd_files\image003.jpg +file4=<>\RavenPack\docs\Using_EffectsEd_files\image004.jpg +file5=<>\RavenPack\docs\Using_EffectsEd_files\image005.jpg +file6=<>\RavenPack\docs\Using_EffectsEd_files\image006.jpg +file7=<>\RavenPack\docs\Using_EffectsEd_files\image007.jpg +file8=<>\RavenPack\docs\Using_EffectsEd_files\image008.jpg +file9=<>\RavenPack\docs\Using_EffectsEd_files\image009.jpg +file10=<>\RavenPack\docs\Using_EffectsEd_files\image010.jpg +file11=<>\RavenPack\docs\Using_EffectsEd_files\image011.jpg +file12=<>\RavenPack\docs\Using_EffectsEd_files\image012.jpg +file13=<>\RavenPack\docs\Using_EffectsEd_files\image013.jpg +file14=<>\RavenPack\docs\Using_EffectsEd_files\image014.jpg +file15=<>\RavenPack\docs\Using_EffectsEd_files\image015.jpg +file16=<>\RavenPack\docs\Using_EffectsEd_files\image016.jpg + diff --git a/setup/win32/template/File Groups/JKII Media Files.fgl b/setup/win32/template/File Groups/JKII Media Files.fgl new file mode 100644 index 00000000..43606f0b --- /dev/null +++ b/setup/win32/template/File Groups/JKII Media Files.fgl @@ -0,0 +1,518 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\JK2Pack\mapextras.pk3 +file1=<>\JK2Pack\mapextras2.pk3 +SubDir0=maps +SubDir1=models +SubDir2=scripts +SubDir3=shaders + +[maps] +fulldirectory= +file0=<>\JK2Pack\maps\kejim_post.map +file1=<>\JK2Pack\maps\ctf_bespin.map +file2=<>\JK2Pack\maps\duel_valley.map +file3=<>\JK2Pack\maps\cairn_dock1.map +file4=<>\JK2Pack\maps\ctf_ns_streets.map +SubDir0=maps\prefabs + +[maps\prefabs] +fulldirectory= +file0=<>\JK2Pack\maps\prefabs\artus_control.map +file1=<>\JK2Pack\maps\prefabs\atst_physics.map +file2=<>\JK2Pack\maps\prefabs\center.map +file3=<>\JK2Pack\maps\prefabs\goodie_crate.map +file4=<>\JK2Pack\maps\prefabs\imp_controls.map +file5=<>\JK2Pack\maps\prefabs\lights_leavemealonedudethatmeansyou.map +file6=<>\JK2Pack\maps\prefabs\new_imp_controls.map +file7=<>\JK2Pack\maps\prefabs\open_srate.map +file8=<>\JK2Pack\maps\prefabs\ravensclaw.map +file9=<>\JK2Pack\maps\prefabs\scow_physics.map +file10=<>\JK2Pack\maps\prefabs\shuttle_physics.map +file11=<>\JK2Pack\maps\prefabs\standing_lamp_physics.map +file12=<>\JK2Pack\maps\prefabs\tie_physics.map +file13=<>\JK2Pack\maps\prefabs\xwing_physics.map +SubDir0=maps\prefabs\Bespin + +[maps\prefabs\Bespin] +fulldirectory= +file0=<>\JK2Pack\maps\prefabs\Bespin\cortosis.map +file1=<>\JK2Pack\maps\prefabs\Bespin\doubledoor.map + +[models] +fulldirectory= +SubDir0=models\chunks +SubDir1=models\flags +SubDir2=models\items +SubDir3=models\map_objects +SubDir4=models\player +SubDir5=models\weapons2 + +[models\chunks] +fulldirectory= +SubDir0=models\chunks\crate +SubDir1=models\chunks\generic +SubDir2=models\chunks\metal +SubDir3=models\chunks\rock + +[models\chunks\crate] +fulldirectory= +file0=<>\JK2Pack\models\chunks\crate\crate2_4.md3 +file1=<>\JK2Pack\models\chunks\crate\crate1_2.md3 +file2=<>\JK2Pack\models\chunks\crate\crate1_3.md3 +file3=<>\JK2Pack\models\chunks\crate\crate1_4.md3 +file4=<>\JK2Pack\models\chunks\crate\crate2_1.md3 +file5=<>\JK2Pack\models\chunks\crate\crate2_2.md3 +file6=<>\JK2Pack\models\chunks\crate\crate2_3.md3 +file7=<>\JK2Pack\models\chunks\crate\crate1_1.md3 + +[models\chunks\generic] +fulldirectory= +file0=<>\JK2Pack\models\chunks\generic\chunks_2.md3 +file1=<>\JK2Pack\models\chunks\generic\chunks_1.md3 + +[models\chunks\metal] +fulldirectory= +file0=<>\JK2Pack\models\chunks\metal\wmetal1_4.md3 +file1=<>\JK2Pack\models\chunks\metal\metal1_2.md3 +file2=<>\JK2Pack\models\chunks\metal\metal1_3.md3 +file3=<>\JK2Pack\models\chunks\metal\metal1_4.md3 +file4=<>\JK2Pack\models\chunks\metal\metal2_1.md3 +file5=<>\JK2Pack\models\chunks\metal\metal2_2.md3 +file6=<>\JK2Pack\models\chunks\metal\metal2_3.md3 +file7=<>\JK2Pack\models\chunks\metal\metal2_4.md3 +file8=<>\JK2Pack\models\chunks\metal\wmetal1_1.md3 +file9=<>\JK2Pack\models\chunks\metal\wmetal1_2.md3 +file10=<>\JK2Pack\models\chunks\metal\wmetal1_3.md3 +file11=<>\JK2Pack\models\chunks\metal\metal1_1.md3 + +[models\chunks\rock] +fulldirectory= +file0=<>\JK2Pack\models\chunks\rock\rock3_4.md3 +file1=<>\JK2Pack\models\chunks\rock\rock1_2.md3 +file2=<>\JK2Pack\models\chunks\rock\rock1_3.md3 +file3=<>\JK2Pack\models\chunks\rock\rock1_4.md3 +file4=<>\JK2Pack\models\chunks\rock\rock2_1.md3 +file5=<>\JK2Pack\models\chunks\rock\rock2_2.md3 +file6=<>\JK2Pack\models\chunks\rock\rock2_3.md3 +file7=<>\JK2Pack\models\chunks\rock\rock2_4.md3 +file8=<>\JK2Pack\models\chunks\rock\rock3_1.md3 +file9=<>\JK2Pack\models\chunks\rock\rock3_2.md3 +file10=<>\JK2Pack\models\chunks\rock\rock3_3.md3 +file11=<>\JK2Pack\models\chunks\rock\rock1_1.md3 + +[models\flags] +fulldirectory= +file0=<>\JK2Pack\models\flags\r_flag_ysal.md3 +file1=<>\JK2Pack\models\flags\b_flag_ysal.md3 +file2=<>\JK2Pack\models\flags\r_flag.md3 +file3=<>\JK2Pack\models\flags\b_flag.md3 + +[models\items] +fulldirectory= +file0=<>\JK2Pack\models\items\sphere.md3 +file1=<>\JK2Pack\models\items\a_shield_converter.md3 +file2=<>\JK2Pack\models\items\bacta.md3 +file3=<>\JK2Pack\models\items\battery.md3 +file4=<>\JK2Pack\models\items\binoculars.md3 +file5=<>\JK2Pack\models\items\datapad.md3 +file6=<>\JK2Pack\models\items\energy_cell.md3 +file7=<>\JK2Pack\models\items\forcegem.md3 +file8=<>\JK2Pack\models\items\key.md3 +file9=<>\JK2Pack\models\items\medpac.md3 +file10=<>\JK2Pack\models\items\metallic_bolts.md3 +file11=<>\JK2Pack\models\items\power_cell.md3 +file12=<>\JK2Pack\models\items\psd.md3 +file13=<>\JK2Pack\models\items\psd_sm.md3 +file14=<>\JK2Pack\models\items\remote.md3 +file15=<>\JK2Pack\models\items\rockets.md3 +file16=<>\JK2Pack\models\items\a_pwr_converter.md3 + +[models\map_objects] +fulldirectory= +SubDir0=models\map_objects\bespin +SubDir1=models\map_objects\cairn +SubDir2=models\map_objects\cinematics +SubDir3=models\map_objects\doom +SubDir4=models\map_objects\force_holocrons +SubDir5=models\map_objects\imp_detention +SubDir6=models\map_objects\imp_mine +SubDir7=models\map_objects\kejim +SubDir8=models\map_objects\mp +SubDir9=models\map_objects\nar_shaddar +SubDir10=models\map_objects\ships +SubDir11=models\map_objects\yavin + +[models\map_objects\bespin] +fulldirectory= +file0=<>\JK2Pack\models\map_objects\bespin\twinpodcc.md3 +file1=<>\JK2Pack\models\map_objects\bespin\chair.md3 +file2=<>\JK2Pack\models\map_objects\bespin\panels.md3 +file3=<>\JK2Pack\models\map_objects\bespin\sconce.md3 +file4=<>\JK2Pack\models\map_objects\bespin\scow.md3 +file5=<>\JK2Pack\models\map_objects\bespin\statue.md3 +file6=<>\JK2Pack\models\map_objects\bespin\streetlight.md3 +file7=<>\JK2Pack\models\map_objects\bespin\bench.md3 +file8=<>\JK2Pack\models\map_objects\bespin\landostatue.md3 + +[models\map_objects\cairn] +fulldirectory= +file0=<>\JK2Pack\models\map_objects\cairn\welder.md3 +file1=<>\JK2Pack\models\map_objects\cairn\absorber_d1.md3 +file2=<>\JK2Pack\models\map_objects\cairn\barrels.md3 +file3=<>\JK2Pack\models\map_objects\cairn\cargo_big.md3 +file4=<>\JK2Pack\models\map_objects\cairn\cargo_sm.md3 +file5=<>\JK2Pack\models\map_objects\cairn\container.md3 +file6=<>\JK2Pack\models\map_objects\cairn\controlpanel.md3 +file7=<>\JK2Pack\models\map_objects\cairn\emitter.md3 +file8=<>\JK2Pack\models\map_objects\cairn\floorlight.md3 +file9=<>\JK2Pack\models\map_objects\cairn\light.md3 +file10=<>\JK2Pack\models\map_objects\cairn\receptor.md3 +file11=<>\JK2Pack\models\map_objects\cairn\utilitylight.md3 +file12=<>\JK2Pack\models\map_objects\cairn\absorber.md3 + +[models\map_objects\cinematics] +fulldirectory= +file0=<>\JK2Pack\models\map_objects\cinematics\table.md3 +file1=<>\JK2Pack\models\map_objects\cinematics\crystal2.md3 +file2=<>\JK2Pack\models\map_objects\cinematics\crystal3.md3 +file3=<>\JK2Pack\models\map_objects\cinematics\desk.md3 +file4=<>\JK2Pack\models\map_objects\cinematics\imp_shuttle.md3 +file5=<>\JK2Pack\models\map_objects\cinematics\kylesleg.md3 +file6=<>\JK2Pack\models\map_objects\cinematics\pod.md3 +file7=<>\JK2Pack\models\map_objects\cinematics\pod_hatch.md3 +file8=<>\JK2Pack\models\map_objects\cinematics\raven_cockpit.md3 +file9=<>\JK2Pack\models\map_objects\cinematics\ravensclaw.md3 +file10=<>\JK2Pack\models\map_objects\cinematics\shuttle.md3 +file11=<>\JK2Pack\models\map_objects\cinematics\chair.md3 +file12=<>\JK2Pack\models\map_objects\cinematics\substatue.md3 +file13=<>\JK2Pack\models\map_objects\cinematics\asteroid.md3 +file14=<>\JK2Pack\models\map_objects\cinematics\crystal.md3 +file15=<>\JK2Pack\models\map_objects\cinematics\crystals.md3 +file16=<>\JK2Pack\models\map_objects\cinematics\ladyluck_chairs.md3 +file17=<>\JK2Pack\models\map_objects\cinematics\statue.md3 + +[models\map_objects\doom] +fulldirectory= +file0=<>\JK2Pack\models\map_objects\doom\antenna.md3 +file1=<>\JK2Pack\models\map_objects\doom\heater.md3 +file2=<>\JK2Pack\models\map_objects\doom\airpurifier.md3 + +[models\map_objects\force_holocrons] +fulldirectory= +file0=<>\JK2Pack\models\map_objects\force_holocrons\speed.md3 +file1=<>\JK2Pack\models\map_objects\force_holocrons\pull.md3 +file2=<>\JK2Pack\models\map_objects\force_holocrons\push.md3 +file3=<>\JK2Pack\models\map_objects\force_holocrons\jump.md3 +file4=<>\JK2Pack\models\map_objects\force_holocrons\telepathy.md3 +file5=<>\JK2Pack\models\map_objects\force_holocrons\grip.md3 +file6=<>\JK2Pack\models\map_objects\force_holocrons\heal.md3 +file7=<>\JK2Pack\models\map_objects\force_holocrons\L1.md3 + +[models\map_objects\imp_detention] +fulldirectory= +file0=<>\JK2Pack\models\map_objects\imp_detention\transport.md3 +file1=<>\JK2Pack\models\map_objects\imp_detention\door_lock.md3 +file2=<>\JK2Pack\models\map_objects\imp_detention\tie_fighter.md3 +file3=<>\JK2Pack\models\map_objects\imp_detention\tie_fighter_1.md3 +file4=<>\JK2Pack\models\map_objects\imp_detention\atst.md3 +file5=<>\JK2Pack\models\map_objects\imp_detention\tie_fighter_damage.md3 + +[models\map_objects\imp_mine] +fulldirectory= +file0=<>\JK2Pack\models\map_objects\imp_mine\x_wing_1.md3 +file1=<>\JK2Pack\models\map_objects\imp_mine\con2.md3 +file2=<>\JK2Pack\models\map_objects\imp_mine\control_panel.md3 +file3=<>\JK2Pack\models\map_objects\imp_mine\crate.md3 +file4=<>\JK2Pack\models\map_objects\imp_mine\crate_open.md3 +file5=<>\JK2Pack\models\map_objects\imp_mine\crystal_raw.md3 +file6=<>\JK2Pack\models\map_objects\imp_mine\flightsuit.md3 +file7=<>\JK2Pack\models\map_objects\imp_mine\generator.md3 +file8=<>\JK2Pack\models\map_objects\imp_mine\generator_u1.md3 +file9=<>\JK2Pack\models\map_objects\imp_mine\ion_cannon_damage.md3 +file10=<>\JK2Pack\models\map_objects\imp_mine\ladyluck_gun.md3 +file11=<>\JK2Pack\models\map_objects\imp_mine\lamp.md3 +file12=<>\JK2Pack\models\map_objects\imp_mine\mbay.md3 +file13=<>\JK2Pack\models\map_objects\imp_mine\mine_drill.md3 +file14=<>\JK2Pack\models\map_objects\imp_mine\shuttle.md3 +file15=<>\JK2Pack\models\map_objects\imp_mine\spotlight.md3 +file16=<>\JK2Pack\models\map_objects\imp_mine\tank.md3 +file17=<>\JK2Pack\models\map_objects\imp_mine\turret_damage.md3 +file18=<>\JK2Pack\models\map_objects\imp_mine\x_wing.md3 +file19=<>\JK2Pack\models\map_objects\imp_mine\con1.md3 +file20=<>\JK2Pack\models\map_objects\imp_mine\turret_cannon2.md3 +file21=<>\JK2Pack\models\map_objects\imp_mine\turret_cannon2_damage.md3 +file22=<>\JK2Pack\models\map_objects\imp_mine\turret_cannon.md3 +file23=<>\JK2Pack\models\map_objects\imp_mine\wall_tank2.md3 +file24=<>\JK2Pack\models\map_objects\imp_mine\chair.md3 +file25=<>\JK2Pack\models\map_objects\imp_mine\desk.md3 +file26=<>\JK2Pack\models\map_objects\imp_mine\gonkdestroyed.md3 +file27=<>\JK2Pack\models\map_objects\imp_mine\ioncannon.md3 +file28=<>\JK2Pack\models\map_objects\imp_mine\ore_cart.md3 +file29=<>\JK2Pack\models\map_objects\imp_mine\ore_cart_rocks.md3 +file30=<>\JK2Pack\models\map_objects\imp_mine\probedestroyed.md3 +file31=<>\JK2Pack\models\map_objects\imp_mine\probedestroyed_1.md3 +file32=<>\JK2Pack\models\map_objects\imp_mine\psgun.md3 +file33=<>\JK2Pack\models\map_objects\imp_mine\r5destroyed.md3 + +[models\map_objects\kejim] +fulldirectory= +file0=<>\JK2Pack\models\map_objects\kejim\weaponsrung.md3 +file1=<>\JK2Pack\models\map_objects\kejim\bombard_upper.md3 +file2=<>\JK2Pack\models\map_objects\kejim\cargo_small.md3 +file3=<>\JK2Pack\models\map_objects\kejim\crate_01.md3 +file4=<>\JK2Pack\models\map_objects\kejim\crate_02.md3 +file5=<>\JK2Pack\models\map_objects\kejim\crate_04.md3 +file6=<>\JK2Pack\models\map_objects\kejim\crate_xplode.md3 +file7=<>\JK2Pack\models\map_objects\kejim\crystal_machine.md3 +file8=<>\JK2Pack\models\map_objects\kejim\crystal_machine_d1.md3 +file9=<>\JK2Pack\models\map_objects\kejim\cutter.md3 +file10=<>\JK2Pack\models\map_objects\kejim\cutter_u1.md3 +file11=<>\JK2Pack\models\map_objects\kejim\dish.md3 +file12=<>\JK2Pack\models\map_objects\kejim\examiner.md3 +file13=<>\JK2Pack\models\map_objects\kejim\generator.md3 +file14=<>\JK2Pack\models\map_objects\kejim\generator_big.md3 +file15=<>\JK2Pack\models\map_objects\kejim\generator_big_d1.md3 +file16=<>\JK2Pack\models\map_objects\kejim\impcam.md3 +file17=<>\JK2Pack\models\map_objects\kejim\impcam_base.md3 +file18=<>\JK2Pack\models\map_objects\kejim\inc_mod.md3 +file19=<>\JK2Pack\models\map_objects\kejim\junction.md3 +file20=<>\JK2Pack\models\map_objects\kejim\ravensclaw.md3 +file21=<>\JK2Pack\models\map_objects\kejim\sec_panel.md3 +file22=<>\JK2Pack\models\map_objects\kejim\tram.md3 +file23=<>\JK2Pack\models\map_objects\kejim\tube.md3 +file24=<>\JK2Pack\models\map_objects\kejim\weaponsrack.md3 +file25=<>\JK2Pack\models\map_objects\kejim\bombard_base.md3 +file26=<>\JK2Pack\models\map_objects\kejim\stasistube.md3 +file27=<>\JK2Pack\models\map_objects\kejim\bombard.md3 +file28=<>\JK2Pack\models\map_objects\kejim\camera.md3 + +[models\map_objects\mp] +fulldirectory= +file0=<>\JK2Pack\models\map_objects\mp\ysalimari.md3 +file1=<>\JK2Pack\models\map_objects\mp\dk_drain.md3 +file2=<>\JK2Pack\models\map_objects\mp\dk_enlightenment.md3 +file3=<>\JK2Pack\models\map_objects\mp\dk_grip.md3 +file4=<>\JK2Pack\models\map_objects\mp\dk_lightning.md3 +file5=<>\JK2Pack\models\map_objects\mp\dk_powerother.md3 +file6=<>\JK2Pack\models\map_objects\mp\dk_rage.md3 +file7=<>\JK2Pack\models\map_objects\mp\force_boon.md3 +file8=<>\JK2Pack\models\map_objects\mp\force_jump.md3 +file9=<>\JK2Pack\models\map_objects\mp\force_pull.md3 +file10=<>\JK2Pack\models\map_objects\mp\force_push.md3 +file11=<>\JK2Pack\models\map_objects\mp\force_sight.md3 +file12=<>\JK2Pack\models\map_objects\mp\force_speed.md3 +file13=<>\JK2Pack\models\map_objects\mp\holo.md3 +file14=<>\JK2Pack\models\map_objects\mp\jedi_enlightenment.md3 +file15=<>\JK2Pack\models\map_objects\mp\lt_absorb.md3 +file16=<>\JK2Pack\models\map_objects\mp\lt_heal.md3 +file17=<>\JK2Pack\models\map_objects\mp\lt_healother.md3 +file18=<>\JK2Pack\models\map_objects\mp\lt_protect.md3 +file19=<>\JK2Pack\models\map_objects\mp\lt_telepathy.md3 +file20=<>\JK2Pack\models\map_objects\mp\medpac.md3 +file21=<>\JK2Pack\models\map_objects\mp\psd.md3 +file22=<>\JK2Pack\models\map_objects\mp\psd_sm.md3 +file23=<>\JK2Pack\models\map_objects\mp\saber_attack.md3 +file24=<>\JK2Pack\models\map_objects\mp\saber_defend.md3 +file25=<>\JK2Pack\models\map_objects\mp\saber_throw.md3 +file26=<>\JK2Pack\models\map_objects\mp\shield.md3 +file27=<>\JK2Pack\models\map_objects\mp\sphere.md3 +file28=<>\JK2Pack\models\map_objects\mp\sphere_1.md3 +file29=<>\JK2Pack\models\map_objects\mp\bacta.md3 + +[models\map_objects\nar_shaddar] +fulldirectory= +file0=<>\JK2Pack\models\map_objects\nar_shaddar\wine.md3 +file1=<>\JK2Pack\models\map_objects\nar_shaddar\book.md3 +file2=<>\JK2Pack\models\map_objects\nar_shaddar\coffee_pot.md3 +file3=<>\JK2Pack\models\map_objects\nar_shaddar\crate_xplode.md3 +file4=<>\JK2Pack\models\map_objects\nar_shaddar\cup.md3 +file5=<>\JK2Pack\models\map_objects\nar_shaddar\dualpod.md3 +file6=<>\JK2Pack\models\map_objects\nar_shaddar\fishtank.md3 +file7=<>\JK2Pack\models\map_objects\nar_shaddar\jabacam.md3 +file8=<>\JK2Pack\models\map_objects\nar_shaddar\plant.md3 +file9=<>\JK2Pack\models\map_objects\nar_shaddar\podbod.md3 +file10=<>\JK2Pack\models\map_objects\nar_shaddar\skiff.md3 +file11=<>\JK2Pack\models\map_objects\nar_shaddar\turret_cannon_damage.md3 +file12=<>\JK2Pack\models\map_objects\nar_shaddar\beerglass.md3 +file13=<>\JK2Pack\models\map_objects\nar_shaddar\bench01.md3 +file14=<>\JK2Pack\models\map_objects\nar_shaddar\crate.md3 +file15=<>\JK2Pack\models\map_objects\nar_shaddar\crate_banded.md3 +file16=<>\JK2Pack\models\map_objects\nar_shaddar\light_wall.md3 +file17=<>\JK2Pack\models\map_objects\nar_shaddar\reelochair.md3 +file18=<>\JK2Pack\models\map_objects\nar_shaddar\reelosdesk.md3 +file19=<>\JK2Pack\models\map_objects\nar_shaddar\table01.md3 + +[models\map_objects\ships] +fulldirectory= +file0=<>\JK2Pack\models\map_objects\ships\x_wing.md3 +file1=<>\JK2Pack\models\map_objects\ships\tie_fighter.md3 + +[models\map_objects\yavin] +fulldirectory= +file0=<>\JK2Pack\models\map_objects\yavin\tree09_b.md3 +file1=<>\JK2Pack\models\map_objects\yavin\glowlight.md3 +file2=<>\JK2Pack\models\map_objects\yavin\grass_b.md3 +file3=<>\JK2Pack\models\map_objects\yavin\grass_tall_b.md3 +file4=<>\JK2Pack\models\map_objects\yavin\head.md3 +file5=<>\JK2Pack\models\map_objects\yavin\plant.md3 +file6=<>\JK2Pack\models\map_objects\yavin\tree_sidehill_b.md3 +file7=<>\JK2Pack\models\map_objects\yavin\tree02.md3 +file8=<>\JK2Pack\models\map_objects\yavin\tree02_b.md3 +file9=<>\JK2Pack\models\map_objects\yavin\tree05.md3 +file10=<>\JK2Pack\models\map_objects\yavin\tree05_b.md3 +file11=<>\JK2Pack\models\map_objects\yavin\tree06_b.md3 +file12=<>\JK2Pack\models\map_objects\yavin\tree08_b.md3 +file13=<>\JK2Pack\models\map_objects\yavin\tree09.md3 +file14=<>\JK2Pack\models\map_objects\yavin\fern3_b.md3 +file15=<>\JK2Pack\models\map_objects\yavin\tree_sidehill.md3 +file16=<>\JK2Pack\models\map_objects\yavin\fern2.md3 +file17=<>\JK2Pack\models\map_objects\yavin\fern2_b.md3 +file18=<>\JK2Pack\models\map_objects\yavin\fern3.md3 +file19=<>\JK2Pack\models\map_objects\yavin\fern.md3 +file20=<>\JK2Pack\models\map_objects\yavin\ferntwo.md3 +file21=<>\JK2Pack\models\map_objects\yavin\grass.md3 +file22=<>\JK2Pack\models\map_objects\yavin\grass_tall.md3 + +[models\player] +fulldirectory= +SubDir0=models\player\droids +SubDir1=models\player\mouse +SubDir2=models\player\shadowtrooper +SubDir3=models\player\remote + +[models\player\droids] +fulldirectory= +file0=<>\JK2Pack\models\players\droids\r5d2_head.md3 +file1=<>\JK2Pack\models\players\droids\probe_droid_head.md3 +file2=<>\JK2Pack\models\players\droids\probe_droid_smleg.md3 +file3=<>\JK2Pack\models\players\droids\r5d2.md3 +file4=<>\JK2Pack\models\players\droids\probe_droid_bigleg.md3 + +[models\player\mouse] +fulldirectory= +file0=<>\JK2Pack\models\players\mouse\lower.md3 + +[models\player\shadowtrooper] +fulldirectory= +file0=<>\JK2Pack\models\players\shadowtrooper\test.md3 + +[models\player\remote] +fulldirectory= +file0=<>\JK2Pack\models\players\remote\lower.md3 + +[models\weapons2] +fulldirectory= +SubDir0=models\weapons2\blaster_r +SubDir1=models\weapons2\bowcaster +SubDir2=models\weapons2\briar_pistol +SubDir3=models\weapons2\demp2 +SubDir4=models\weapons2\detpack +SubDir5=models\weapons2\disruptor +SubDir6=models\weapons2\golan_arms +SubDir7=models\weapons2\heavy_repeater +SubDir8=models\weapons2\imp_pistol +SubDir9=models\weapons2\laser_trap +SubDir10=models\weapons2\merr_sonn +SubDir11=models\weapons2\stun_baton +SubDir12=models\weapons2\thermal +SubDir13=models\weapons2\saber + +[models\weapons2\blaster_r] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\blaster_r\blaster.md3 + +[models\weapons2\bowcaster] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\bowcaster\bowcaster.md3 + +[models\weapons2\briar_pistol] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\briar_pistol\briar_pistol.md3 + +[models\weapons2\demp2] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\demp2\demp2.md3 + +[models\weapons2\detpack] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\detpack\det_pack.md3 + +[models\weapons2\disruptor] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\disruptor\disruptor.md3 + +[models\weapons2\golan_arms] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\golan_arms\golan_arms.md3 + +[models\weapons2\heavy_repeater] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\heavy_repeater\heavy_repeater.md3 + +[models\weapons2\imp_pistol] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\imp_pistol\pistol.md3 + +[models\weapons2\laser_trap] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\laser_trap\laser_trap.md3 + +[models\weapons2\merr_sonn] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\merr_sonn\merr_sonn.md3 + +[models\weapons2\stun_baton] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\stun_baton\baton.md3 + +[models\weapons2\thermal] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\thermal\thermal.md3 + +[models\weapons2\saber] +fulldirectory= +file0=<>\JK2Pack\models\weapons2\saber\saber_w.md3 + +[scripts] +fulldirectory= +file0=<>\JK2Pack\scripts\sp_entities.def +file1=<>\JK2Pack\scripts\mp_entities.def +file2=<>\JK2Pack\scripts\default_project.proj + +[shaders] +fulldirectory= +file0=<>\JK2Pack\shaders\zoom.shader +file1=<>\JK2Pack\shaders\cinematics.shader +file2=<>\JK2Pack\shaders\common.shader +file3=<>\JK2Pack\shaders\decals.shader +file4=<>\JK2Pack\shaders\doomgiver.shader +file5=<>\JK2Pack\shaders\effects.shader +file6=<>\JK2Pack\shaders\explosions.shader +file7=<>\JK2Pack\shaders\flares.shader +file8=<>\JK2Pack\shaders\fogs.shader +file9=<>\JK2Pack\shaders\gfx.shader +file10=<>\JK2Pack\shaders\gfx2.shader +file11=<>\JK2Pack\shaders\imperial.shader +file12=<>\JK2Pack\shaders\items.shader +file13=<>\JK2Pack\shaders\marks.shader +file14=<>\JK2Pack\shaders\metashader.shader +file15=<>\JK2Pack\shaders\models.shader +file16=<>\JK2Pack\shaders\mp.shader +file17=<>\JK2Pack\shaders\nar_shaddaa.shader +file18=<>\JK2Pack\shaders\players.shader +file19=<>\JK2Pack\shaders\sabers.shader +file20=<>\JK2Pack\shaders\scavenger.shader +file21=<>\JK2Pack\shaders\shaderlist.txt +file22=<>\JK2Pack\shaders\skies.shader +file23=<>\JK2Pack\shaders\sprites.shader +file24=<>\JK2Pack\shaders\system.shader +file25=<>\JK2Pack\shaders\test.shader +file26=<>\JK2Pack\shaders\text_crawl.shader +file27=<>\JK2Pack\shaders\ui.shader +file28=<>\JK2Pack\shaders\yavin.shader +file29=<>\JK2Pack\shaders\bespin.shader + diff --git a/setup/win32/template/File Groups/Model Manual Files.fgl b/setup/win32/template/File Groups/Model Manual Files.fgl new file mode 100644 index 00000000..d4be5c23 --- /dev/null +++ b/setup/win32/template/File Groups/Model Manual Files.fgl @@ -0,0 +1,16 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=Model_Manual + +[Model_Manual] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Model_Manual +file0=<>\GtkRadiant\docs\manual\quake3\Model_Manual\model_manual.htm +SubDir0=Model_Manual\styles + +[Model_Manual\styles] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Model_Manual\styles +file0=<>\GtkRadiant\docs\manual\quake3\Model_Manual\styles\q3rad.css + diff --git a/setup/win32/template/File Groups/Plugins - BobToolz.fgl b/setup/win32/template/File Groups/Plugins - BobToolz.fgl new file mode 100644 index 00000000..fc38ddc7 --- /dev/null +++ b/setup/win32/template/File Groups/Plugins - BobToolz.fgl @@ -0,0 +1,30 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\contrib\bobtoolz\Release\bobToolz.dll +SubDir0=bt +SubDir1=bitmaps + +[bt] +fulldirectory= +file0=<>\GtkRadiant\contrib\bobtoolz\bt\door-tex.txt +file1=<>\GtkRadiant\contrib\bobtoolz\bt\bt-el2.txt +file2=<>\GtkRadiant\contrib\bobtoolz\bt\bt-el1.txt +file3=<>\GtkRadiant\contrib\bobtoolz\bt\door-tex-trim.txt +file4=<>\GtkRadiant\contrib\bobtoolz\bt\tp_ent.txt + +[bitmaps] +fulldirectory= +file0=<>\GtkRadiant\contrib\bobtoolz\bitmaps\bobtoolz_turnedge.bmp +file1=<>\GtkRadiant\contrib\bobtoolz\bitmaps\bobtoolz_caulk.bmp +file2=<>\GtkRadiant\contrib\bobtoolz\bitmaps\bobtoolz_cleanup.bmp +file3=<>\GtkRadiant\contrib\bobtoolz\bitmaps\bobtoolz_dropent.bmp +file4=<>\GtkRadiant\contrib\bobtoolz\bitmaps\bobtoolz_merge.bmp +file5=<>\GtkRadiant\contrib\bobtoolz\bitmaps\bobtoolz_poly.bmp +file6=<>\GtkRadiant\contrib\bobtoolz\bitmaps\bobtoolz_split.bmp +file7=<>\GtkRadiant\contrib\bobtoolz\bitmaps\bobtoolz_trainpathplot.bmp +file8=<>\GtkRadiant\contrib\bobtoolz\bitmaps\bobtoolz_treeplanter.bmp + + diff --git a/setup/win32/template/File Groups/Plugins - Curry pk3 Wolf.fgl b/setup/win32/template/File Groups/Plugins - Curry pk3 Wolf.fgl new file mode 100644 index 00000000..971a3066 --- /dev/null +++ b/setup/win32/template/File Groups/Plugins - Curry pk3 Wolf.fgl @@ -0,0 +1,7 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\setup\data\baseq3\curry.pk3 + diff --git a/setup/win32/template/File Groups/Plugins - Curry pk3.fgl b/setup/win32/template/File Groups/Plugins - Curry pk3.fgl new file mode 100644 index 00000000..971a3066 --- /dev/null +++ b/setup/win32/template/File Groups/Plugins - Curry pk3.fgl @@ -0,0 +1,7 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\setup\data\baseq3\curry.pk3 + diff --git a/setup/win32/template/File Groups/Plugins - Curry.fgl b/setup/win32/template/File Groups/Plugins - Curry.fgl new file mode 100644 index 00000000..45d45423 --- /dev/null +++ b/setup/win32/template/File Groups/Plugins - Curry.fgl @@ -0,0 +1,7 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\curry_gtk\Release\curry.dll + diff --git a/setup/win32/template/File Groups/Plugins - GTK GenSurf.fgl b/setup/win32/template/File Groups/Plugins - GTK GenSurf.fgl new file mode 100644 index 00000000..8073dccf --- /dev/null +++ b/setup/win32/template/File Groups/Plugins - GTK GenSurf.fgl @@ -0,0 +1,7 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\contrib\gtkgensurf\Release\gensurf.dll + diff --git a/setup/win32/template/File Groups/Plugins - Pk3Man.fgl b/setup/win32/template/File Groups/Plugins - Pk3Man.fgl new file mode 100644 index 00000000..4fbc9e1f --- /dev/null +++ b/setup/win32/template/File Groups/Plugins - Pk3Man.fgl @@ -0,0 +1,20 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\pk3man_gtk_2\Release\pk3man.dll +SubDir0=bitmaps + +[bitmaps] +fulldirectory=\quake3\GtkRadiant\plugins\bitmaps +file0=<>\pk3man_gtk_2\bitmaps\pk3man_tbnew.bmp +file1=<>\pk3man_gtk_2\bitmaps\pk3man_tbdelete.bmp +file2=<>\pk3man_gtk_2\bitmaps\pk3man_tbextract.bmp +file3=<>\pk3man_gtk_2\bitmaps\pk3man_tbadd.bmp +file4=<>\pk3man_gtk_2\bitmaps\pk3man_tbopen.bmp +file5=<>\pk3man_gtk_2\bitmaps\pk3man_tbrename.bmp +file6=<>\pk3man_gtk_2\bitmaps\pk3man_tbsave.bmp +file7=<>\pk3man_gtk_2\bitmaps\pk3man_tbview.bmp +file8=<>\pk3man_gtk_2\bitmaps\pk3man_tbwizard.bmp + diff --git a/setup/win32/template/File Groups/Plugins - PrtView.fgl b/setup/win32/template/File Groups/Plugins - PrtView.fgl new file mode 100644 index 00000000..fe746a48 --- /dev/null +++ b/setup/win32/template/File Groups/Plugins - PrtView.fgl @@ -0,0 +1,7 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\contrib\prtview\Release\PrtView.dll + diff --git a/setup/win32/template/File Groups/Plugins - TexTool.fgl b/setup/win32/template/File Groups/Plugins - TexTool.fgl new file mode 100644 index 00000000..531f04d5 --- /dev/null +++ b/setup/win32/template/File Groups/Plugins - TexTool.fgl @@ -0,0 +1,7 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\plugins\textool\Release\TexTool.dll + diff --git a/setup/win32/template/File Groups/Plugins - bkgrnd2d.fgl b/setup/win32/template/File Groups/Plugins - bkgrnd2d.fgl new file mode 100644 index 00000000..1041d7df --- /dev/null +++ b/setup/win32/template/File Groups/Plugins - bkgrnd2d.fgl @@ -0,0 +1,15 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\contrib\bkgrnd2d\Release\bkgrnd2d.dll +SubDir0=bitmaps + +[bitmaps] +fulldirectory= +file0=<>\GtkRadiant\contrib\bkgrnd2d\bitmaps\bkgrnd2d_yz_toggle.bmp +file1=<>\GtkRadiant\contrib\bkgrnd2d\bitmaps\bkgrnd2d_conf.bmp +file2=<>\GtkRadiant\contrib\bkgrnd2d\bitmaps\bkgrnd2d_xy_toggle.bmp +file3=<>\GtkRadiant\contrib\bkgrnd2d\bitmaps\bkgrnd2d_xz_toggle.bmp + diff --git a/setup/win32/template/File Groups/Program DLL Files.fgl b/setup/win32/template/File Groups/Program DLL Files.fgl new file mode 100644 index 00000000..f0de24cb --- /dev/null +++ b/setup/win32/template/File Groups/Program DLL Files.fgl @@ -0,0 +1,119 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\gtk2-2.2\install\iconv.dll +file1=<>\gtk2-2.2\install\libatk-1.0-0.dll +file2=<>\gtk2-2.2\install\libgtkglext-win32-1.0-0.dll +file3=<>\gtk2-2.2\install\libgtk-win32-2.0-0.dll +file4=<>\gtk2-2.2\install\libgdk_pixbuf-2.0-0.dll +file5=<>\gtk2-2.2\install\libgdkglext-win32-1.0-0.dll +file6=<>\gtk2-2.2\install\libgdk-win32-2.0-0.dll +file7=<>\gtk2-2.2\install\libglib-2.0-0.dll +file8=<>\gtk2-2.2\install\libgmodule-2.0-0.dll +file9=<>\gtk2-2.2\install\libgobject-2.0-0.dll +file10=<>\gtk2-2.2\install\libgthread-2.0-0.dll +file11=<>\gtk2-2.2\install\intl.dll +file12=<>\gtk2-2.2\install\libpangowin32-1.0-0.dll +file13=<>\gtk2-2.2\install\libpango-1.0-0.dll +file14=<>\libpng-1.2\lib\libpng13.dll +file15=<>\zlib1-1.2\zlib1.dll +file16=<>\libxml2-2.6\win32\install\libxml2.dll +SubDir0=lib +SubDir1=etc +SubDir2=share + +[lib] +fulldirectory= +SubDir0=lib\gtk-2.0 +SubDir1=lib\locale +SubDir2=lib\pango + +[lib\gtk-2.0] +fulldirectory= +SubDir0=lib\gtk-2.0\2.2.0 + +[lib\gtk-2.0\2.2.0] +fulldirectory= +SubDir0=lib\gtk-2.0\2.2.0\engines +SubDir1=lib\gtk-2.0\2.2.0\immodules +SubDir2=lib\gtk-2.0\2.2.0\loaders + +[lib\gtk-2.0\2.2.0\engines] +fulldirectory= +file0=<>\gtk2-2.2\install\lib\gtk-2.0\2.2.0\engines\libwimp.dll + +[lib\gtk-2.0\2.2.0\immodules] +fulldirectory= +file0=<>\gtk2-2.2\install\lib\gtk-2.0\2.2.0\immodules\im-ipa.dll + +[lib\gtk-2.0\2.2.0\loaders] +fulldirectory= +file0=<>\gtk2-2.2\install\lib\gtk-2.0\2.2.0\loaders\libpixbufloader-xpm.dll +file1=<>\gtk2-2.2\install\lib\gtk-2.0\2.2.0\loaders\libpixbufloader-bmp.dll + +[lib\locale] +fulldirectory= +SubDir0=lib\locale\en@IPA +SubDir1=lib\locale\en_GB + +[lib\locale\en@IPA] +fulldirectory= +SubDir0=lib\locale\en@IPA\LC_MESSAGES + +[lib\locale\en@IPA\LC_MESSAGES] +fulldirectory= +file0=<>\gtk2-2.2\install\lib\locale\en@IPA\LC_MESSAGES\gtk20.mo + +[lib\locale\en_GB] +fulldirectory= +SubDir0=lib\locale\en_GB\LC_MESSAGES + +[lib\locale\en_GB\LC_MESSAGES] +fulldirectory= +file0=<>\gtk2-2.2\install\lib\locale\en_GB\LC_MESSAGES\atk10.mo +file1=<>\gtk2-2.2\install\lib\locale\en_GB\LC_MESSAGES\gtk20.mo + +[lib\pango] +fulldirectory= +SubDir0=lib\pango\1.2.0 + +[lib\pango\1.2.0] +fulldirectory= +SubDir0=lib\pango\1.2.0\modules + +[lib\pango\1.2.0\modules] +fulldirectory= +file0=<>\gtk2-2.2\install\lib\pango\1.2.0\modules\pango-basic-win32.dll + +[etc] +fulldirectory= +SubDir0=etc\gtk-2.0 +SubDir1=etc\pango + +[etc\gtk-2.0] +fulldirectory= +file0=<>\gtk2-2.2\install\etc\gtk-2.0\gdk-pixbuf.loaders +file1=<>\gtk2-2.2\install\etc\gtk-2.0\gtk.immodules + +[etc\pango] +fulldirectory= +file0=<>\gtk2-2.2\install\etc\pango\pango.aliases +file1=<>\gtk2-2.2\install\etc\pango\pango.modules + +[share] +fulldirectory= +SubDir0=share\themes + +[share\themes] +fulldirectory= +SubDir0=share\themes\Default + +[share\themes\Default] +fulldirectory= +SubDir0=share\themes\Default\gtk-2.0 + +[share\themes\Default\gtk-2.0] +fulldirectory= +file0=<>\gtk2-2.2\install\share\themes\Default\gtk-2.0\gtkrc diff --git a/setup/win32/template/File Groups/Program DLLs.fgl b/setup/win32/template/File Groups/Program DLLs.fgl new file mode 100644 index 00000000..04090b95 --- /dev/null +++ b/setup/win32/template/File Groups/Program DLLs.fgl @@ -0,0 +1,4 @@ +[General] +Type=FILELIST +Version=1.10.000 + diff --git a/setup/win32/template/File Groups/Program Executable Files.fgl b/setup/win32/template/File Groups/Program Executable Files.fgl new file mode 100644 index 00000000..d4c2460c --- /dev/null +++ b/setup/win32/template/File Groups/Program Executable Files.fgl @@ -0,0 +1,43 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\setup\win32\bin\<> +file1=<>\GtkRadiant\tools\quake3\q3map2\Release\q3map2.exe +SubDir0=modules +SubDir1=plugins + +[modules] +fulldirectory= +file0=<>\GtkRadiant\plugins\image\Release\imageq3.dll +file1=<>\GtkRadiant\plugins\mapq3\Release\mapq3.dll +file2=<>\GtkRadiant\plugins\shaders\Release\shadersq3.dll +file3=<>\GtkRadiant\plugins\vfspk3\Release\vfsq3.dll +file4=<>\GtkRadiant\plugins\eclassfgd\Release\fgd.dll +file5=<>\GtkRadiant\plugins\entity\Release\entityq3.dll +file6=<>\GtkRadiant\plugins\imagehl\Release\imagehl.dll +file7=<>\GtkRadiant\plugins\mapxml\Release\mapxml.dll +file8=<>\GtkRadiant\plugins\spritemodel\Release\spritemodel.dll +file9=<>\GtkRadiant\plugins\archivewad\Release\archivewad.dll +file10=<>\GtkRadiant\plugins\archivepak\Release\archivepak.dll +file11=<>\GtkRadiant\plugins\archivezip\Release\archivezip.dll +file12=<>\GtkRadiant\plugins\model\Release\modelpico.dll +file13=<>\GtkRadiant\plugins\surface\Release\surface.dll +file14=<>\GtkRadiant\plugins\imagepng\Release\imagepng.dll +SubDir0=modules\bitmaps + +[modules\bitmaps] +fulldirectory= +file0=<>\GtkRadiant\plugins\model\bitmaps\picomodel.bmp +file1=<>\GtkRadiant\plugins\model\bitmaps\model_reload_entity.bmp + +[plugins] +fulldirectory= +file0=<>\GtkRadiant\contrib\camera\Release\camera.dll +SubDir0=plugins\bitmaps + +[plugins\bitmaps] +fulldirectory= +file0=<>\GtkRadiant\contrib\camera\bitmaps\camera_insp.bmp + diff --git a/setup/win32/template/File Groups/Program Misc Files.fgl b/setup/win32/template/File Groups/Program Misc Files.fgl new file mode 100644 index 00000000..3ff3145a --- /dev/null +++ b/setup/win32/template/File Groups/Program Misc Files.fgl @@ -0,0 +1,76 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\setup\data\tools\links.htm +file1=<>\GtkRadiant\setup\data\tools\shortcuts.ini.sample +file2=<>\GtkRadiant\setup\changelog.txt +file3=<>\GtkRadiant\setup\license.txt +file4=<>\GtkRadiant\setup\setup.bmp +file5=<>\GtkRadiant\setup\data\tools\Credits.html +file6=<>\GtkRadiant\setup\radiantgtkrc +file7=<>\GtkRadiant\setup\data\tools\global.xlink +file8=<>\GtkRadiant\tools\quake3\q3map2\changelog.q3map2.txt +SubDir0=bitmaps +SubDir1=dtds + +[bitmaps] +fulldirectory=<>\GtkRadiant\radiant\bitmaps +file0=<>\GtkRadiant\radiant\bitmaps\brush_flipx.bmp +file1=<>\GtkRadiant\radiant\bitmaps\brush_flipy.bmp +file2=<>\GtkRadiant\radiant\bitmaps\brush_flipz.bmp +file3=<>\GtkRadiant\radiant\bitmaps\brush_rotatex.bmp +file4=<>\GtkRadiant\radiant\bitmaps\brush_rotatey.bmp +file5=<>\GtkRadiant\radiant\bitmaps\brush_rotatez.bmp +file6=<>\GtkRadiant\radiant\bitmaps\cap_bevel.bmp +file7=<>\GtkRadiant\radiant\bitmaps\cap_endcap.bmp +file8=<>\GtkRadiant\radiant\bitmaps\cap_ibevel.bmp +file9=<>\GtkRadiant\radiant\bitmaps\cap_iendcap.bmp +file10=<>\GtkRadiant\radiant\bitmaps\curve_cap.bmp +file11=<>\GtkRadiant\radiant\bitmaps\dontselectcurve.bmp +file12=<>\GtkRadiant\radiant\bitmaps\dontselectmodel.bmp +file13=<>\GtkRadiant\radiant\bitmaps\file_open.bmp +file14=<>\GtkRadiant\radiant\bitmaps\file_save.bmp +file15=<>\GtkRadiant\radiant\bitmaps\icon.bmp +file16=<>\GtkRadiant\radiant\bitmaps\logo.bmp +file17=<>\GtkRadiant\radiant\bitmaps\patch_bend.bmp +file18=<>\GtkRadiant\radiant\bitmaps\patch_drilldown.bmp +file19=<>\GtkRadiant\radiant\bitmaps\patch_insdel.bmp +file20=<>\GtkRadiant\radiant\bitmaps\patch_showboundingbox.bmp +file21=<>\GtkRadiant\radiant\bitmaps\patch_weld.bmp +file22=<>\GtkRadiant\radiant\bitmaps\patch_wireframe.bmp +file23=<>\GtkRadiant\radiant\bitmaps\popup_selection.bmp +file24=<>\GtkRadiant\radiant\bitmaps\scalelockx.bmp +file25=<>\GtkRadiant\radiant\bitmaps\scalelocky.bmp +file26=<>\GtkRadiant\radiant\bitmaps\scalelockz.bmp +file27=<>\GtkRadiant\radiant\bitmaps\selection_csgmerge.bmp +file28=<>\GtkRadiant\radiant\bitmaps\selection_csgsubtract.bmp +file29=<>\GtkRadiant\radiant\bitmaps\selection_makehollow.bmp +file30=<>\GtkRadiant\radiant\bitmaps\selection_selectcompletetall.bmp +file31=<>\GtkRadiant\radiant\bitmaps\selection_selectinside.bmp +file32=<>\GtkRadiant\radiant\bitmaps\selection_selectpartialtall.bmp +file33=<>\GtkRadiant\radiant\bitmaps\selection_selecttouching.bmp +file34=<>\GtkRadiant\radiant\bitmaps\select_mouserotate.bmp +file35=<>\GtkRadiant\radiant\bitmaps\select_mousescale.bmp +file36=<>\GtkRadiant\radiant\bitmaps\select_mousetranslate.bmp +file37=<>\GtkRadiant\radiant\bitmaps\show_entities.bmp +file38=<>\GtkRadiant\radiant\bitmaps\splash.bmp +file39=<>\GtkRadiant\radiant\bitmaps\textures_popup.bmp +file40=<>\GtkRadiant\radiant\bitmaps\view_cameratoggle.bmp +file41=<>\GtkRadiant\radiant\bitmaps\view_cameraupdate.bmp +file42=<>\GtkRadiant\radiant\bitmaps\view_change.bmp +file43=<>\GtkRadiant\radiant\bitmaps\view_clipper.bmp +file44=<>\GtkRadiant\radiant\bitmaps\view_cubicclipping.bmp +file45=<>\GtkRadiant\radiant\bitmaps\view_entity.bmp +file46=<>\GtkRadiant\radiant\bitmaps\window1.bmp +file47=<>\GtkRadiant\radiant\bitmaps\window2.bmp +file48=<>\GtkRadiant\radiant\bitmaps\window3.bmp +file49=<>\GtkRadiant\radiant\bitmaps\window4.bmp + +[dtds] +fulldirectory=<>\GtkRadiant\setup\data\tools\dtds +file0=<>\GtkRadiant\setup\data\tools\dtds\project.dtd +file1=<>\GtkRadiant\setup\data\tools\dtds\mapq3.dtd + + diff --git a/setup/win32/template/File Groups/Q2 Executable Files.fgl b/setup/win32/template/File Groups/Q2 Executable Files.fgl new file mode 100644 index 00000000..e1c1b154 --- /dev/null +++ b/setup/win32/template/File Groups/Q2 Executable Files.fgl @@ -0,0 +1,18 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\Q2Pack\synapse.config +file1=<>\Q2Pack\game.xlink +file2=<>\GtkRadiant\tools\quake2\qdata\Release\qdata3.exe +file3=<>\GtkRadiant\tools\quake2\q2map\Release\q2map.exe +file4=<>\libxml2\win32\binaries-release\libxml2.dll +SubDir0=modules + +[modules] +fulldirectory= +file0=<>\GtkRadiant\plugins\vfspak\Release\vfspak.dll +file1=<>\GtkRadiant\plugins\imagewal\Release\imagewal.dll +file2=<>\GtkRadiant\plugins\surface_quake2\Release\surface_quake2.dll + diff --git a/setup/win32/template/File Groups/Q2 Media Files.fgl b/setup/win32/template/File Groups/Q2 Media Files.fgl new file mode 100644 index 00000000..bf0c617d --- /dev/null +++ b/setup/win32/template/File Groups/Q2 Media Files.fgl @@ -0,0 +1,31 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=maps +SubDir1=scripts +SubDir2=pics +SubDir3=textures + +[maps] +fulldirectory= +file0=<>\Q2Pack\baseq2\maps\base1.map + +[scripts] +fulldirectory= +file0=<>\Q2Pack\baseq2\scripts\entities.def +file1=<>\Q2Pack\baseq2\scripts\default_project.proj + +[pics] +fulldirectory= +file0=<>\Q2Pack\baseq2\pics\colormap.pcx + +[textures] +fulldirectory= +SubDir0=textures\radiant + +[textures\radiant] +fulldirectory= +file0=<>\Q2Pack\baseq2\textures\radiant\notex.pcx + diff --git a/setup/win32/template/File Groups/Q3 Default Project.fgl b/setup/win32/template/File Groups/Q3 Default Project.fgl new file mode 100644 index 00000000..6764f7a2 --- /dev/null +++ b/setup/win32/template/File Groups/Q3 Default Project.fgl @@ -0,0 +1,7 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\setup\data\baseq3\scripts\default_project.proj + diff --git a/setup/win32/template/File Groups/Q3 Editor Images - SPoG pk3.fgl b/setup/win32/template/File Groups/Q3 Editor Images - SPoG pk3.fgl new file mode 100644 index 00000000..3d633631 --- /dev/null +++ b/setup/win32/template/File Groups/Q3 Editor Images - SPoG pk3.fgl @@ -0,0 +1,7 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\setup\data\baseq3\common-spog.pk3 + diff --git a/setup/win32/template/File Groups/Q3 Executable Files.fgl b/setup/win32/template/File Groups/Q3 Executable Files.fgl new file mode 100644 index 00000000..e17393aa --- /dev/null +++ b/setup/win32/template/File Groups/Q3 Executable Files.fgl @@ -0,0 +1,17 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\setup\win32\bin\bspc.exe +file1=<>\GtkRadiant\tools\quake3\q3data\Release\q3data.exe +file2=<>\GtkRadiant\setup\data\tools\synapse.config +file3=<>\gtk2-2.2\install\libglib-2.0-0.dll +file4=<>\gtk2-2.2\install\iconv.dll +file5=<>\gtk2-2.2\install\intl.dll +file6=<>\libxml2-2.6\win32\install\libxml2.dll +SubDir0=modules + +[modules] +fulldirectory= + diff --git a/setup/win32/template/File Groups/Q3 Misc Files.fgl b/setup/win32/template/File Groups/Q3 Misc Files.fgl new file mode 100644 index 00000000..8050ce8e --- /dev/null +++ b/setup/win32/template/File Groups/Q3 Misc Files.fgl @@ -0,0 +1,8 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\setup\data\tools\q3data.qdt +file1=<>\GtkRadiant\setup\data\tools\game.xlink + diff --git a/setup/win32/template/File Groups/Q3 Sample Files.fgl b/setup/win32/template/File Groups/Q3 Sample Files.fgl new file mode 100644 index 00000000..25aafaa1 --- /dev/null +++ b/setup/win32/template/File Groups/Q3 Sample Files.fgl @@ -0,0 +1,184 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\setup\data\baseq3\mapmedia.pk3 +SubDir0=models +SubDir1=maps +SubDir2=scripts + +[models] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models +SubDir0=models\mapobjects + +[models\mapobjects] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\gargoyle1.md3 +file1=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\kmlamp1.md3 +file2=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\statue_major.md3 +file3=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\visor_posed.md3 +file4=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\walllamp3.md3 +SubDir0=models\mapobjects\banner +SubDir1=models\mapobjects\baph +SubDir2=models\mapobjects\bitch +SubDir3=models\mapobjects\corpse +SubDir4=models\mapobjects\gratelamp +SubDir5=models\mapobjects\jesus +SubDir6=models\mapobjects\jets +SubDir7=models\mapobjects\lamps +SubDir8=models\mapobjects\pipe +SubDir9=models\mapobjects\podium +SubDir10=models\mapobjects\portal_2 +SubDir11=models\mapobjects\skel +SubDir12=models\mapobjects\skull +SubDir13=models\mapobjects\spotlamp +SubDir14=models\mapobjects\storch +SubDir15=models\mapobjects\teleporter +SubDir16=models\mapobjects\timlamp +SubDir17=models\mapobjects\tree2 +SubDir18=models\mapobjects\wallhead + +[models\mapobjects\banner] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\banner +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\banner\banner5.md3 + +[models\mapobjects\baph] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\baph +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\baph\baphomet_gold.md3 +file1=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\baph\lil_baphomet.md3 + +[models\mapobjects\bitch] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\bitch +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\bitch\fembot.md3 +file1=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\bitch\fembotbig.md3 + +[models\mapobjects\corpse] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\corpse +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\corpse\corpse.md3 +file1=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\corpse\torso.md3 + +[models\mapobjects\gratelamp] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\gratelamp +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\gratelamp\gratelamp.md3 +file1=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\gratelamp\gratetorch.md3 +file2=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\gratelamp\gratetorchbig.md3 + +[models\mapobjects\jesus] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\jesus +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\jesus\jesus.md3 + +[models\mapobjects\jets] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\jets +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\jets\jets01.md3 + +[models\mapobjects\lamps] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\lamps +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\lamps\bot_lamp2.md3 + +[models\mapobjects\pipe] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\pipe +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\pipe\pipe02.md3 +file1=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\pipe\pipe02b.md3 + +[models\mapobjects\podium] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\podium +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\podium\podium4.md3 + +[models\mapobjects\portal_2] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\portal_2 +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\portal_2\portal_2.md3 + +[models\mapobjects\skel] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\skel +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\skel\skel01.md3 +file1=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\skel\skel02mid.md3 +file2=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\skel\skel_ribs.md3 +file3=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\skel\xray.md3 +file4=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\skel\xraybig.md3 + +[models\mapobjects\skull] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\skull +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\skull\monkeyface.md3 +file1=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\skull\skull.md3 +file2=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\skull\skull_tilt1.md3 + +[models\mapobjects\spotlamp] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\spotlamp +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\spotlamp\spotlamp.md3 + +[models\mapobjects\storch] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\storch +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\storch\storch.md3 +file1=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\storch\storchx.md3 +file2=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\storch\tall_torch.md3 + +[models\mapobjects\teleporter] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\teleporter +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\teleporter\teleporter.md3 + +[models\mapobjects\timlamp] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\timlamp +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\timlamp\timlamp.md3 + +[models\mapobjects\tree2] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\tree2 +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\tree2\branch2.tga +file1=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\tree2\tree1.md3 +file2=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\tree2\tree10.md3 +file3=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\tree2\tree2.md3 +file4=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\tree2\tree3.md3 +file5=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\tree2\tree4.md3 +file6=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\tree2\tree5.md3 +file7=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\tree2\tree6.md3 +file8=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\tree2\tree7.md3 +file9=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\tree2\tree8.md3 +file10=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\tree2\tree9.md3 +file11=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\tree2\trunk2.tga + +[models\mapobjects\wallhead] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\wallhead +file0=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\wallhead\femhead.md3 +file1=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\wallhead\lion.md3 +file2=<>\GtkRadiant\setup\data\baseq3\models\mapobjects\wallhead\wallhead02.md3 + +[maps] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\maps +file0=<>\GtkRadiant\setup\data\baseq3\maps\q3dm17sample.map +file1=<>\GtkRadiant\setup\data\baseq3\maps\q3dm1sample.map +file2=<>\GtkRadiant\setup\data\baseq3\maps\q3dm7sample.map +file3=<>\GtkRadiant\setup\data\baseq3\maps\terrademoQ3.map +file4=<>\GtkRadiant\setup\data\baseq3\maps\museum.map + +[scripts] +fulldirectory= +file0=<>\GtkRadiant\setup\data\baseq3\scripts\base_button.shader +file1=<>\GtkRadiant\setup\data\baseq3\scripts\base_door.shader +file2=<>\GtkRadiant\setup\data\baseq3\scripts\base_floor.shader +file3=<>\GtkRadiant\setup\data\baseq3\scripts\base_light.shader +file4=<>\GtkRadiant\setup\data\baseq3\scripts\base_object.shader +file5=<>\GtkRadiant\setup\data\baseq3\scripts\base_support.shader +file6=<>\GtkRadiant\setup\data\baseq3\scripts\base_trim.shader +file7=<>\GtkRadiant\setup\data\baseq3\scripts\base_wall.shader +file8=<>\GtkRadiant\setup\data\baseq3\scripts\common.shader +file9=<>\GtkRadiant\setup\data\baseq3\scripts\ctf.shader +file10=<>\GtkRadiant\setup\data\baseq3\scripts\gfx.shader +file11=<>\GtkRadiant\setup\data\baseq3\scripts\gothic_block.shader +file12=<>\GtkRadiant\setup\data\baseq3\scripts\gothic_button.shader +file13=<>\GtkRadiant\setup\data\baseq3\scripts\gothic_door.shader +file14=<>\GtkRadiant\setup\data\baseq3\scripts\gothic_floor.shader +file15=<>\GtkRadiant\setup\data\baseq3\scripts\gothic_light.shader +file16=<>\GtkRadiant\setup\data\baseq3\scripts\gothic_trim.shader +file17=<>\GtkRadiant\setup\data\baseq3\scripts\gothic_wall.shader +file18=<>\GtkRadiant\setup\data\baseq3\scripts\hell.shader +file19=<>\GtkRadiant\setup\data\baseq3\scripts\liquids.shader +file20=<>\GtkRadiant\setup\data\baseq3\scripts\models.shader +file21=<>\GtkRadiant\setup\data\baseq3\scripts\museum.shader +file22=<>\GtkRadiant\setup\data\baseq3\scripts\organics.shader +file23=<>\GtkRadiant\setup\data\baseq3\scripts\sfx.shader +file24=<>\GtkRadiant\setup\data\baseq3\scripts\skies.shader +file25=<>\GtkRadiant\setup\data\baseq3\scripts\skin.shader +file26=<>\GtkRadiant\setup\data\baseq3\scripts\terrademoQ3.shader +file27=<>\GtkRadiant\setup\data\baseq3\scripts\test.shader +file28=<>\GtkRadiant\setup\data\baseq3\scripts\entities.def + diff --git a/setup/win32/template/File Groups/Radiant Manual Files.fgl b/setup/win32/template/File Groups/Radiant Manual Files.fgl new file mode 100644 index 00000000..331898a1 --- /dev/null +++ b/setup/win32/template/File Groups/Radiant Manual Files.fgl @@ -0,0 +1,126 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=Q3Rad_Manual + +[Q3Rad_Manual] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\index.htm +SubDir0=Q3Rad_Manual\appndx +SubDir1=Q3Rad_Manual\ch01 +SubDir2=Q3Rad_Manual\ch02 +SubDir3=Q3Rad_Manual\ch03 +SubDir4=Q3Rad_Manual\ch04 +SubDir5=Q3Rad_Manual\ch05 +SubDir6=Q3Rad_Manual\ch06 +SubDir7=Q3Rad_Manual\ch07 +SubDir8=Q3Rad_Manual\ch08 +SubDir9=Q3Rad_Manual\ch09 +SubDir10=Q3Rad_Manual\ch10 +SubDir11=Q3Rad_Manual\ch11 +SubDir12=Q3Rad_Manual\ch12 +SubDir13=Q3Rad_Manual\Q3Rad_Manual_files +SubDir14=Q3Rad_Manual\styles +SubDir15=Q3Rad_Manual\gtkrad + +[Q3Rad_Manual\appndx] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_a.htm +file1=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_b_1.htm +file2=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_b_2.htm +file3=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_b_3.htm +file4=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_b_4.htm +file5=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_b_5.htm +file6=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_b_6.htm +file7=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_b_7.htm +file8=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_b_8.htm +file9=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_b_9.htm +file10=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_c.htm +file11=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_d.htm +file12=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_e.htm +file13=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\appn_f.htm +file14=<>\GtkRadiant\docs\manual\Q3Rad_Manual\appndx\sskey_dl.htm + +[Q3Rad_Manual\ch01] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch01 +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch01\pg1_1.htm +file1=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch01\pg1_2.htm + +[Q3Rad_Manual\ch02] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch02 +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch02\pg2_1.htm + +[Q3Rad_Manual\ch03] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch03 +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch03\pg3_1.htm + +[Q3Rad_Manual\ch04] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch04 +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch04\pg4_1.htm + +[Q3Rad_Manual\ch05] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch05 +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch05\pg5_1.htm + +[Q3Rad_Manual\ch06] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch06 +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch06\pg6_1.htm + +[Q3Rad_Manual\ch07] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch07 +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch07\pg7_1.htm + +[Q3Rad_Manual\ch08] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch08 +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch08\pg8_1.htm + +[Q3Rad_Manual\ch09] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch09 +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch09\pg9_1.htm + +[Q3Rad_Manual\ch10] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch10 +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch10\pg10_1.htm + +[Q3Rad_Manual\ch11] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch11 +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch11\pg11_1.htm + +[Q3Rad_Manual\ch12] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch12 +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\ch12\pg12_1.htm + +[Q3Rad_Manual\Q3Rad_Manual_files] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image002.png +file1=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image003.png +file2=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image004.png +file3=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image006.png +file4=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image008.png +file5=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image010.png +file6=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image012.png +file7=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image014.png +file8=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image016.png +file9=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image018.png +file10=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image020.png +file11=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image022.png +file12=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image024.png +file13=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image026.png +file14=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image028.png +file15=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image030.png +file16=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image032.png +file17=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image034.png +file18=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image035.png +file19=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image038.png +file20=<>\GtkRadiant\docs\manual\Q3Rad_Manual\Q3Rad_Manual_files\image040.png + +[Q3Rad_Manual\styles] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\styles +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\styles\q3rad.css + +[Q3Rad_Manual\gtkrad] +fulldirectory=<>\GtkRadiant\docs\manual\Q3Rad_Manual\gtkrad +file0=<>\GtkRadiant\docs\manual\Q3Rad_Manual\gtkrad\pg1_1.htm + diff --git a/setup/win32/template/File Groups/SOF2 Executable Files.fgl b/setup/win32/template/File Groups/SOF2 Executable Files.fgl new file mode 100644 index 00000000..2b71bdf3 --- /dev/null +++ b/setup/win32/template/File Groups/SOF2 Executable Files.fgl @@ -0,0 +1,276 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\SOF2Pack\game.xlink +file1=<>\SOF2Pack\ChangeLog.txt +file2=<>\Sof2Pack\bin\BehavEd.bhc +file3=<>\RavenPack\bin\BehavEd.exe +file4=<>\RavenPack\bin\ConfusEditor.exe +file5=<>\RavenPack\bin\EffectsEd.exe +file6=<>\RavenPack\bin\IBIze.exe +file7=<>\RavenPack\bin\roq.exe +file8=<>\Sof2Pack\synapse.config +SubDir0=docs +SubDir1=modules + +[docs] +fulldirectory= +file0=<>\SOF2Pack\docs\SoF2_Terrain.html +file1=<>\SOF2Pack\docs\SoF2_MP_Maps.html +file2=<>\SOF2Pack\docs\SoF2_Shaders.html +file3=<>\RavenPack\docs\Guide to SOF2JK2 Vertigons.htm +file4=<>\RavenPack\docs\Guide to SOF2JK2 Weather.htm +file5=<>\RavenPack\docs\Using_EffectsEd.htm +file6=<>\RavenPack\docs\Q3_Enhancements.htm +file7=<>\RavenPack\docs\ROQ_Files.htm +file8=<>\Sof2Pack\docs\SoF2_Weapons_WpnFile.htm +file9=<>\Sof2Pack\docs\Nav Point System.htm +file10=<>\Sof2Pack\docs\RMG tutorial.htm +file11=<>\Sof2Pack\docs\SoF2_Animating Objects.htm +file12=<>\Sof2Pack\docs\SoF2_Character_NPCFile.htm +file13=<>\Sof2Pack\docs\SoF2_Character_Skins.htm +file14=<>\Sof2Pack\docs\SoF2_ConfusEd.htm +file15=<>\Sof2Pack\docs\SoF2_Model_Formats.htm +file16=<>\Sof2Pack\docs\SoF2_Raven Menu Format.htm +file17=<>\Sof2Pack\docs\SoF2_Scripting.htm +file18=<>\Sof2Pack\docs\SoF2_StripEd.htm +file19=<>\Sof2Pack\docs\SoF2_Weapons_AnimatorPOV.htm +file20=<>\Sof2Pack\docs\SoF2_Weapons_FramesFile.htm +file21=<>\Sof2Pack\docs\SoF2_Weapons_InviewFile.htm +file22=<>\Sof2Pack\docs\SoF2_Weapons_Overview.htm +SubDir0=docs\GtkRadiant_SoF2_HOWTO +SubDir1=docs\SoF2_MP_Maps_files +SubDir2=docs\SoF2_Shaders_files +SubDir3=docs\SoF2_Terrain_files +SubDir4=docs\Using_EffectsEd_files +SubDir5=docs\SoF2_Weapons_WpnFile_files +SubDir6=docs\SoF2_Weapons_Overview_files +SubDir7=docs\SoF2_Weapons_InviewFile_files +SubDir8=docs\SoF2_Weapons_FramesFile_files +SubDir9=docs\SoF2_Weapons_AnimatorPOV_files +SubDir10=docs\SoF2_StripEd_files +SubDir11=docs\SoF2_Scripting_files +SubDir12=docs\SoF2_Raven Menu Format_files +SubDir13=docs\SoF2_Model_Formats_files +SubDir14=docs\SoF2_ConfusEd_files +SubDir15=docs\SoF2_Character_Skins_files +SubDir16=docs\SoF2_Character_NPCFile_files +SubDir17=docs\SoF2_Animating Objects_files +SubDir18=docs\Nav Point System_files + +[docs\GtkRadiant_SoF2_HOWTO] +fulldirectory= +file0=<>\SOF2Pack\docs\GtkRadiant_SoF2_HOWTO\project.png +file1=<>\SOF2Pack\docs\GtkRadiant_SoF2_HOWTO\bsp_console.png +file2=<>\SOF2Pack\docs\GtkRadiant_SoF2_HOWTO\bsp_menu.png +file3=<>\SOF2Pack\docs\GtkRadiant_SoF2_HOWTO\game1.png +file4=<>\SOF2Pack\docs\GtkRadiant_SoF2_HOWTO\game2.png +file5=<>\SOF2Pack\docs\GtkRadiant_SoF2_HOWTO\index.html + +[docs\SoF2_MP_Maps_files] +fulldirectory= +file0=<>\SOF2Pack\docs\SoF2_MP_Maps_files\image002.jpg +file1=<>\SOF2Pack\docs\SoF2_MP_Maps_files\filelist.xml +file2=<>\SOF2Pack\docs\SoF2_MP_Maps_files\header.html +file3=<>\SOF2Pack\docs\SoF2_MP_Maps_files\image001.jpg + +[docs\SoF2_Shaders_files] +fulldirectory= +file0=<>\SOF2Pack\docs\SoF2_Shaders_files\image002.jpg +file1=<>\SOF2Pack\docs\SoF2_Shaders_files\filelist.xml +file2=<>\SOF2Pack\docs\SoF2_Shaders_files\header.html +file3=<>\SOF2Pack\docs\SoF2_Shaders_files\image001.jpg + +[docs\SoF2_Terrain_files] +fulldirectory= +file0=<>\SOF2Pack\docs\SoF2_Terrain_files\image002.jpg +file1=<>\SOF2Pack\docs\SoF2_Terrain_files\filelist.xml +file2=<>\SOF2Pack\docs\SoF2_Terrain_files\header.html +file3=<>\SOF2Pack\docs\SoF2_Terrain_files\image001.jpg + +[docs\Using_EffectsEd_files] +fulldirectory= +file0=<>\RavenPack\docs\Using_EffectsEd_files\filelist.xml +file1=<>\RavenPack\docs\Using_EffectsEd_files\image001.jpg +file2=<>\RavenPack\docs\Using_EffectsEd_files\image002.jpg +file3=<>\RavenPack\docs\Using_EffectsEd_files\image003.jpg +file4=<>\RavenPack\docs\Using_EffectsEd_files\image004.jpg +file5=<>\RavenPack\docs\Using_EffectsEd_files\image005.jpg +file6=<>\RavenPack\docs\Using_EffectsEd_files\image006.jpg +file7=<>\RavenPack\docs\Using_EffectsEd_files\image007.jpg +file8=<>\RavenPack\docs\Using_EffectsEd_files\image008.jpg +file9=<>\RavenPack\docs\Using_EffectsEd_files\image009.jpg +file10=<>\RavenPack\docs\Using_EffectsEd_files\image010.jpg +file11=<>\RavenPack\docs\Using_EffectsEd_files\image011.jpg +file12=<>\RavenPack\docs\Using_EffectsEd_files\image012.jpg +file13=<>\RavenPack\docs\Using_EffectsEd_files\image013.jpg +file14=<>\RavenPack\docs\Using_EffectsEd_files\image014.jpg +file15=<>\RavenPack\docs\Using_EffectsEd_files\image015.jpg +file16=<>\RavenPack\docs\Using_EffectsEd_files\image016.jpg + +[docs\SoF2_Weapons_WpnFile_files] +fulldirectory= +file0=<>\Sof2Pack\docs\SoF2_Weapons_WpnFile_files\filelist.xml +file1=<>\Sof2Pack\docs\SoF2_Weapons_WpnFile_files\header.htm +file2=<>\Sof2Pack\docs\SoF2_Weapons_WpnFile_files\image001.jpg +file3=<>\Sof2Pack\docs\SoF2_Weapons_WpnFile_files\image002.jpg + +[docs\SoF2_Weapons_Overview_files] +fulldirectory= +file0=<>\Sof2Pack\docs\SoF2_Weapons_Overview_files\filelist.xml +file1=<>\Sof2Pack\docs\SoF2_Weapons_Overview_files\header.htm +file2=<>\Sof2Pack\docs\SoF2_Weapons_Overview_files\image001.jpg +file3=<>\Sof2Pack\docs\SoF2_Weapons_Overview_files\image002.jpg + +[docs\SoF2_Weapons_InviewFile_files] +fulldirectory= +file0=<>\Sof2Pack\docs\SoF2_Weapons_InviewFile_files\filelist.xml +file1=<>\Sof2Pack\docs\SoF2_Weapons_InviewFile_files\header.htm +file2=<>\Sof2Pack\docs\SoF2_Weapons_InviewFile_files\image001.jpg +file3=<>\Sof2Pack\docs\SoF2_Weapons_InviewFile_files\image002.jpg + +[docs\SoF2_Weapons_FramesFile_files] +fulldirectory= +file0=<>\Sof2Pack\docs\SoF2_Weapons_FramesFile_files\filelist.xml +file1=<>\Sof2Pack\docs\SoF2_Weapons_FramesFile_files\header.htm +file2=<>\Sof2Pack\docs\SoF2_Weapons_FramesFile_files\image001.jpg +file3=<>\Sof2Pack\docs\SoF2_Weapons_FramesFile_files\image002.jpg + +[docs\SoF2_Weapons_AnimatorPOV_files] +fulldirectory= +file0=<>\Sof2Pack\docs\SoF2_Weapons_AnimatorPOV_files\filelist.xml +file1=<>\Sof2Pack\docs\SoF2_Weapons_AnimatorPOV_files\header.htm +file2=<>\Sof2Pack\docs\SoF2_Weapons_AnimatorPOV_files\image001.jpg +file3=<>\Sof2Pack\docs\SoF2_Weapons_AnimatorPOV_files\image002.jpg + +[docs\SoF2_StripEd_files] +fulldirectory= +file0=<>\Sof2Pack\docs\SoF2_StripEd_files\filelist.xml +file1=<>\Sof2Pack\docs\SoF2_StripEd_files\header.htm +file2=<>\Sof2Pack\docs\SoF2_StripEd_files\image001.jpg +file3=<>\Sof2Pack\docs\SoF2_StripEd_files\image002.jpg + +[docs\SoF2_Scripting_files] +fulldirectory= +file0=<>\Sof2Pack\docs\SoF2_Scripting_files\filelist.xml +file1=<>\Sof2Pack\docs\SoF2_Scripting_files\header.htm + +[docs\SoF2_Raven Menu Format_files] +fulldirectory= +file0=<>\Sof2Pack\docs\SoF2_Raven Menu Format_files\filelist.xml +file1=<>\Sof2Pack\docs\SoF2_Raven Menu Format_files\header.htm +file2=<>\Sof2Pack\docs\SoF2_Raven Menu Format_files\image001.jpg +file3=<>\Sof2Pack\docs\SoF2_Raven Menu Format_files\image002.jpg + +[docs\SoF2_Model_Formats_files] +fulldirectory= +file0=<>\Sof2Pack\docs\SoF2_Model_Formats_files\filelist.xml +file1=<>\Sof2Pack\docs\SoF2_Model_Formats_files\header.htm +file2=<>\Sof2Pack\docs\SoF2_Model_Formats_files\image001.jpg +file3=<>\Sof2Pack\docs\SoF2_Model_Formats_files\image002.jpg +file4=<>\Sof2Pack\docs\SoF2_Model_Formats_files\image003.jpg +file5=<>\Sof2Pack\docs\SoF2_Model_Formats_files\image004.jpg +file6=<>\Sof2Pack\docs\SoF2_Model_Formats_files\image005.jpg +file7=<>\Sof2Pack\docs\SoF2_Model_Formats_files\image006.jpg +file8=<>\Sof2Pack\docs\SoF2_Model_Formats_files\image007.jpg +file9=<>\Sof2Pack\docs\SoF2_Model_Formats_files\image008.jpg +file10=<>\Sof2Pack\docs\SoF2_Model_Formats_files\image009.jpg +file11=<>\Sof2Pack\docs\SoF2_Model_Formats_files\image010.jpg +file12=<>\Sof2Pack\docs\SoF2_Model_Formats_files\image011.jpg + +[docs\SoF2_ConfusEd_files] +fulldirectory= +file0=<>\Sof2Pack\docs\SoF2_ConfusEd_files\filelist.xml +file1=<>\Sof2Pack\docs\SoF2_ConfusEd_files\header.htm +file2=<>\Sof2Pack\docs\SoF2_ConfusEd_files\image001.jpg +file3=<>\Sof2Pack\docs\SoF2_ConfusEd_files\image002.jpg + +[docs\SoF2_Character_Skins_files] +fulldirectory= +file0=<>\Sof2Pack\docs\SoF2_Character_Skins_files\filelist.xml +file1=<>\Sof2Pack\docs\SoF2_Character_Skins_files\header.htm +file2=<>\Sof2Pack\docs\SoF2_Character_Skins_files\image001.jpg +file3=<>\Sof2Pack\docs\SoF2_Character_Skins_files\image002.jpg +file4=<>\Sof2Pack\docs\SoF2_Character_Skins_files\image003.jpg +file5=<>\Sof2Pack\docs\SoF2_Character_Skins_files\image004.jpg +file6=<>\Sof2Pack\docs\SoF2_Character_Skins_files\image005.jpg +file7=<>\Sof2Pack\docs\SoF2_Character_Skins_files\image006.jpg + +[docs\SoF2_Character_NPCFile_files] +fulldirectory= +file0=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\filelist.xml +file1=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\header.htm +file2=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image001.jpg +file3=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image002.gif +file4=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image002.jpg +file5=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image003.gif +file6=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image004.gif +file7=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image005.gif +file8=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image006.gif +file9=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image007.gif +file10=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image008.gif +file11=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image009.gif +file12=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image010.gif +file13=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image011.gif +file14=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image012.gif +file15=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image013.gif +file16=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image014.gif +file17=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image015.gif +file18=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image016.gif +file19=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image017.gif +file20=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image018.gif +file21=<>\Sof2Pack\docs\SoF2_Character_NPCFile_files\image019.gif + +[docs\SoF2_Animating Objects_files] +fulldirectory= +file0=<>\Sof2Pack\docs\SoF2_Animating Objects_files\filelist.xml +file1=<>\Sof2Pack\docs\SoF2_Animating Objects_files\header.htm +file2=<>\Sof2Pack\docs\SoF2_Animating Objects_files\image001.jpg +file3=<>\Sof2Pack\docs\SoF2_Animating Objects_files\image002.jpg + +[docs\Nav Point System_files] +fulldirectory= +file0=<>\Sof2Pack\docs\Nav Point System_files\filelist.xml +file1=<>\Sof2Pack\docs\Nav Point System_files\header.htm +file2=<>\Sof2Pack\docs\Nav Point System_files\image001.png +file3=<>\Sof2Pack\docs\Nav Point System_files\image002.jpg +file4=<>\Sof2Pack\docs\Nav Point System_files\image003.png +file5=<>\Sof2Pack\docs\Nav Point System_files\image004.jpg +file6=<>\Sof2Pack\docs\Nav Point System_files\image005.png +file7=<>\Sof2Pack\docs\Nav Point System_files\image006.jpg +file8=<>\Sof2Pack\docs\Nav Point System_files\image007.png +file9=<>\Sof2Pack\docs\Nav Point System_files\image008.jpg +file10=<>\Sof2Pack\docs\Nav Point System_files\image009.png +file11=<>\Sof2Pack\docs\Nav Point System_files\image010.jpg +file12=<>\Sof2Pack\docs\Nav Point System_files\image011.png +file13=<>\Sof2Pack\docs\Nav Point System_files\image012.jpg +file14=<>\Sof2Pack\docs\Nav Point System_files\image013.png +file15=<>\Sof2Pack\docs\Nav Point System_files\image014.jpg +file16=<>\Sof2Pack\docs\Nav Point System_files\image015.png +file17=<>\Sof2Pack\docs\Nav Point System_files\image016.jpg +file18=<>\Sof2Pack\docs\Nav Point System_files\image017.png +file19=<>\Sof2Pack\docs\Nav Point System_files\image018.jpg +file20=<>\Sof2Pack\docs\Nav Point System_files\image019.png +file21=<>\Sof2Pack\docs\Nav Point System_files\image020.jpg +file22=<>\Sof2Pack\docs\Nav Point System_files\image021.png +file23=<>\Sof2Pack\docs\Nav Point System_files\image022.jpg +file24=<>\Sof2Pack\docs\Nav Point System_files\image023.png +file25=<>\Sof2Pack\docs\Nav Point System_files\image024.jpg +file26=<>\Sof2Pack\docs\Nav Point System_files\image025.png +file27=<>\Sof2Pack\docs\Nav Point System_files\image026.jpg +file28=<>\Sof2Pack\docs\Nav Point System_files\image027.png +file29=<>\Sof2Pack\docs\Nav Point System_files\image028.jpg +file30=<>\Sof2Pack\docs\Nav Point System_files\image029.png +file31=<>\Sof2Pack\docs\Nav Point System_files\image030.jpg +file32=<>\Sof2Pack\docs\Nav Point System_files\image031.png +file33=<>\Sof2Pack\docs\Nav Point System_files\image032.jpg +file34=<>\Sof2Pack\docs\Nav Point System_files\image033.png +file35=<>\Sof2Pack\docs\Nav Point System_files\image034.jpg +file36=<>\Sof2Pack\docs\Nav Point System_files\image035.png +file37=<>\Sof2Pack\docs\Nav Point System_files\image036.jpg + +[modules] +fulldirectory= + diff --git a/setup/win32/template/File Groups/SOF2 Media Files.fgl b/setup/win32/template/File Groups/SOF2 Media Files.fgl new file mode 100644 index 00000000..eafb9079 --- /dev/null +++ b/setup/win32/template/File Groups/SOF2 Media Files.fgl @@ -0,0 +1,1071 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\SOF2Pack\radiant.pk3 +SubDir0=maps +SubDir1=models +SubDir2=scripts +SubDir3=shaders + +[maps] +fulldirectory= +file0=<>\SOF2Pack\maps\mp_examples.map +file1=<>\Sof2Pack\maps\heli.map +file2=<>\Sof2Pack\maps\kam6.map + +[models] +fulldirectory= +SubDir0=models\chunks +SubDir1=models\flags +SubDir2=models\objects +SubDir3=models\pick_ups +SubDir4=models\weapons + +[models\chunks] +fulldirectory= +SubDir0=models\chunks\aqualid +SubDir1=models\chunks\bar +SubDir2=models\chunks\basket_sq +SubDir3=models\chunks\body_parts +SubDir4=models\chunks\canbase +SubDir5=models\chunks\candle +SubDir6=models\chunks\canlid +SubDir7=models\chunks\cart_bev +SubDir8=models\chunks\cart_food +SubDir9=models\chunks\clipboard +SubDir10=models\chunks\copier_chunks +SubDir11=models\chunks\cpu_chunk +SubDir12=models\chunks\dish +SubDir13=models\chunks\dishbase +SubDir14=models\chunks\fabric +SubDir15=models\chunks\fern +SubDir16=models\chunks\fish +SubDir17=models\chunks\flag +SubDir18=models\chunks\flag_blue +SubDir19=models\chunks\flag_red +SubDir20=models\chunks\glass +SubDir21=models\chunks\ice +SubDir22=models\chunks\katana +SubDir23=models\chunks\keyboard_chunks +SubDir24=models\chunks\lantern_long +SubDir25=models\chunks\leaf +SubDir26=models\chunks\lid +SubDir27=models\chunks\metal +SubDir28=models\chunks\micro1 +SubDir29=models\chunks\micro2 +SubDir30=models\chunks\micro3 +SubDir31=models\chunks\micro4 +SubDir32=models\chunks\micro5 +SubDir33=models\chunks\monitor_cart_chunks +SubDir34=models\chunks\monitor_chunks +SubDir35=models\chunks\monitor_front +SubDir36=models\chunks\paper +SubDir37=models\chunks\phone_chunks +SubDir38=models\chunks\poolchair +SubDir39=models\chunks\rebreather +SubDir40=models\chunks\rock +SubDir41=models\chunks\rubber +SubDir42=models\chunks\sam_missle +SubDir43=models\chunks\smalllid +SubDir44=models\chunks\spatula +SubDir45=models\chunks\spoon +SubDir46=models\chunks\sunglasses +SubDir47=models\chunks\tray +SubDir48=models\chunks\truck_chunks +SubDir49=models\chunks\tv +SubDir50=models\chunks\wood + +[models\chunks\aqualid] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\aqualid\aqualid.md3 + +[models\chunks\bar] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\bar\bar.md3 + +[models\chunks\basket_sq] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\basket_sq\basket_sq.md3 + +[models\chunks\body_parts] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\body_parts\head.md3 +file1=<>\SOF2Pack\models\chunks\body_parts\chunk_torso.md3 + +[models\chunks\canbase] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\canbase\canbase.md3 + +[models\chunks\candle] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\candle\candle.md3 + +[models\chunks\canlid] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\canlid\canlid.md3 + +[models\chunks\cart_bev] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\cart_bev\cart_bev.md3 + +[models\chunks\cart_food] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\cart_food\tray.md3 +file1=<>\SOF2Pack\models\chunks\cart_food\cart.md3 + +[models\chunks\clipboard] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\clipboard\clipboard.md3 + +[models\chunks\copier_chunks] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\copier_chunks\copier_chunk.md3 + +[models\chunks\cpu_chunk] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\cpu_chunk\cpu_chunk.md3 + +[models\chunks\dish] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\dish\dish.md3 + +[models\chunks\dishbase] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\dishbase\dishbase.md3 + +[models\chunks\fabric] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\fabric\fabric_small_wht.md3 +file1=<>\SOF2Pack\models\chunks\fabric\fabric_big.md3 +file2=<>\SOF2Pack\models\chunks\fabric\fabric_med.md3 +file3=<>\SOF2Pack\models\chunks\fabric\fabric_med_purp.md3 +file4=<>\SOF2Pack\models\chunks\fabric\fabric_med_wht.md3 +file5=<>\SOF2Pack\models\chunks\fabric\fabric_small.md3 + +[models\chunks\fern] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\fern\fern_ledge.md3 + +[models\chunks\fish] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\fish\fish.md3 + +[models\chunks\flag] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\flag\flag.md3 + +[models\chunks\flag_blue] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\flag_blue\flag_blue.md3 + +[models\chunks\flag_red] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\flag_red\flag_red.md3 + +[models\chunks\glass] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\glass\glass_big.md3 +file1=<>\SOF2Pack\models\chunks\glass\glass_med.md3 +file2=<>\SOF2Pack\models\chunks\glass\glass_small.md3 +file3=<>\SOF2Pack\models\chunks\glass\glchunks_1.md3 +file4=<>\SOF2Pack\models\chunks\glass\glchunks_2.md3 +file5=<>\SOF2Pack\models\chunks\glass\glchunks_3.md3 +file6=<>\SOF2Pack\models\chunks\glass\glchunks_4.md3 +file7=<>\SOF2Pack\models\chunks\glass\glchunks_5.md3 +file8=<>\SOF2Pack\models\chunks\glass\glchunks_6.md3 + +[models\chunks\ice] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\ice\ice_big.md3 +file1=<>\SOF2Pack\models\chunks\ice\ice_med.md3 +file2=<>\SOF2Pack\models\chunks\ice\ice_small.md3 + +[models\chunks\katana] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\katana\katana.md3 + +[models\chunks\keyboard_chunks] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\keyboard_chunks\keyboard_chunk1.md3 +file1=<>\SOF2Pack\models\chunks\keyboard_chunks\keyboard_chunk2.md3 +file2=<>\SOF2Pack\models\chunks\keyboard_chunks\keyboard_chunk_small.md3 + +[models\chunks\lantern_long] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\lantern_long\lantern_long.md3 + +[models\chunks\leaf] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\leaf\leaf.md3 + +[models\chunks\lid] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\lid\lid.md3 + +[models\chunks\metal] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\metal\metal_big.md3 +file1=<>\SOF2Pack\models\chunks\metal\metal_med.md3 +file2=<>\SOF2Pack\models\chunks\metal\metal_small.md3 + +[models\chunks\micro1] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\micro1\micro1.md3 + +[models\chunks\micro2] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\micro2\micro2.md3 + +[models\chunks\micro3] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\micro3\micro3.md3 + +[models\chunks\micro4] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\micro4\micro4.md3 + +[models\chunks\micro5] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\micro5\micro5.md3 + +[models\chunks\monitor_cart_chunks] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\monitor_cart_chunks\cart_wheel.md3 +file1=<>\SOF2Pack\models\chunks\monitor_cart_chunks\monitor_cart_chunk1.md3 + +[models\chunks\monitor_chunks] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\monitor_chunks\monitor_chunk_lrg.md3 +file1=<>\SOF2Pack\models\chunks\monitor_chunks\monitor_chunk_med.md3 + +[models\chunks\monitor_front] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\monitor_front\monitor_front.md3 + +[models\chunks\paper] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\paper\paper.md3 +file1=<>\SOF2Pack\models\chunks\paper\paper_mag1.md3 +file2=<>\SOF2Pack\models\chunks\paper\paper_mag2.md3 +file3=<>\SOF2Pack\models\chunks\paper\paper_mag3.md3 + +[models\chunks\phone_chunks] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\phone_chunks\phone_chunk1.md3 +file1=<>\SOF2Pack\models\chunks\phone_chunks\phone_chunk2.md3 + +[models\chunks\poolchair] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\poolchair\poolchair.md3 + +[models\chunks\rebreather] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\rebreather\rebreather.md3 + +[models\chunks\rock] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\rock\rock_big.md3 +file1=<>\SOF2Pack\models\chunks\rock\rock_med.md3 +file2=<>\SOF2Pack\models\chunks\rock\rock_small.md3 + +[models\chunks\rubber] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\rubber\rubber_big.md3 +file1=<>\SOF2Pack\models\chunks\rubber\rubber_med.md3 +file2=<>\SOF2Pack\models\chunks\rubber\rubber_small.md3 + +[models\chunks\sam_missle] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\sam_missle\sam_missle.md3 + +[models\chunks\smalllid] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\smalllid\smalllid.md3 + +[models\chunks\spatula] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\spatula\spatula.md3 + +[models\chunks\spoon] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\spoon\spoon.md3 + +[models\chunks\sunglasses] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\sunglasses\sunglass1.md3 +file1=<>\SOF2Pack\models\chunks\sunglasses\sunglass2.md3 +file2=<>\SOF2Pack\models\chunks\sunglasses\sunglass3.md3 + +[models\chunks\tray] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\tray\tray.md3 + +[models\chunks\truck_chunks] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\truck_chunks\truck_axle_chunk.md3 +file1=<>\SOF2Pack\models\chunks\truck_chunks\truck_back_chunk.md3 +file2=<>\SOF2Pack\models\chunks\truck_chunks\truck_cab_chunk.md3 +file3=<>\SOF2Pack\models\chunks\truck_chunks\truck_fender_chunk.md3 +file4=<>\SOF2Pack\models\chunks\truck_chunks\truck_hood_chunk.md3 +file5=<>\SOF2Pack\models\chunks\truck_chunks\truck_wheel_chunk.md3 + +[models\chunks\tv] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\tv\tv.md3 + +[models\chunks\wood] +fulldirectory= +file0=<>\SOF2Pack\models\chunks\wood\wood_big.md3 +file1=<>\SOF2Pack\models\chunks\wood\wood_med.md3 +file2=<>\SOF2Pack\models\chunks\wood\wood_small.md3 +file3=<>\SOF2Pack\models\chunks\wood\wood_splinter3.md3 +file4=<>\SOF2Pack\models\chunks\wood\wood_splinter4.md3 +file5=<>\SOF2Pack\models\chunks\wood\wood_splinter5.md3 +file6=<>\SOF2Pack\models\chunks\wood\wood_splinter6.md3 +file7=<>\SOF2Pack\models\chunks\wood\wood_splinter.md3 + +[models\flags] +fulldirectory= +file0=<>\SOF2Pack\models\flags\flag_blue.md3 +file1=<>\SOF2Pack\models\flags\flag_red.md3 + +[models\objects] +fulldirectory= +SubDir0=models\objects\Airport +SubDir1=models\objects\Armory +SubDir2=models\objects\Colombia +SubDir3=models\objects\Common +SubDir4=models\objects\Finca +SubDir5=models\objects\Hongkong +SubDir6=models\objects\Hospital +SubDir7=models\objects\Jordan +SubDir8=models\objects\Kamchatka +SubDir9=models\objects\Liner +SubDir10=models\objects\Prague +SubDir11=models\objects\Shop + +[models\objects\Airport] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Airport\box_cart.md3 +file1=<>\SOF2Pack\models\objects\Airport\box_cart_buggy.md3 +file2=<>\SOF2Pack\models\objects\Airport\box_cart_open.md3 +file3=<>\SOF2Pack\models\objects\Airport\gift_stand.md3 +file4=<>\SOF2Pack\models\objects\Airport\giftstore_candy.md3 +file5=<>\SOF2Pack\models\objects\Airport\luggage_cart.md3 +file6=<>\SOF2Pack\models\objects\Airport\magazine_stand.md3 +file7=<>\SOF2Pack\models\objects\Airport\parachute.md3 +file8=<>\SOF2Pack\models\objects\Airport\parachute_1.md3 +file9=<>\SOF2Pack\models\objects\Airport\parachute_2.md3 +file10=<>\SOF2Pack\models\objects\Airport\phone.md3 +file11=<>\SOF2Pack\models\objects\Airport\postcard_stand.md3 +file12=<>\SOF2Pack\models\objects\Airport\steps.md3 +file13=<>\SOF2Pack\models\objects\Airport\sunglass_stand.md3 +file14=<>\SOF2Pack\models\objects\Airport\sunglasses.md3 +file15=<>\SOF2Pack\models\objects\Airport\wand_detector.md3 + +[models\objects\Armory] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Armory\airtank.md3 +file1=<>\SOF2Pack\models\objects\Armory\airtank_1.md3 +file2=<>\SOF2Pack\models\objects\Armory\airtank_2.md3 +file3=<>\SOF2Pack\models\objects\Armory\ak74_armory.md3 +file4=<>\SOF2Pack\models\objects\Armory\ak74_nb.md3 +file5=<>\SOF2Pack\models\objects\Armory\dummy.md3 +file6=<>\SOF2Pack\models\objects\Armory\flippers.md3 +file7=<>\SOF2Pack\models\objects\Armory\knife_armory.md3 +file8=<>\SOF2Pack\models\objects\Armory\m4_armory.md3 +file9=<>\SOF2Pack\models\objects\Armory\m60_armory.md3 +file10=<>\SOF2Pack\models\objects\Armory\mask.md3 +file11=<>\SOF2Pack\models\objects\Armory\rebreather.md3 +file12=<>\SOF2Pack\models\objects\Armory\rebreather_1.md3 +file13=<>\SOF2Pack\models\objects\Armory\rebreather_2.md3 +file14=<>\SOF2Pack\models\objects\Armory\rope.md3 +file15=<>\SOF2Pack\models\objects\Armory\rope_1.md3 +file16=<>\SOF2Pack\models\objects\Armory\rope_2.md3 +file17=<>\SOF2Pack\models\objects\Armory\virus.md3 + +[models\objects\Colombia] +fulldirectory= +SubDir0=models\objects\Colombia\furniture +SubDir1=models\objects\Colombia\jungle +SubDir2=models\objects\Colombia\lights +SubDir3=models\objects\Colombia\market +SubDir4=models\objects\Colombia\misc +SubDir5=models\objects\Colombia\vehicles + +[models\objects\Colombia\furniture] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Colombia\furniture\chair_col.md3 +file1=<>\SOF2Pack\models\objects\Colombia\furniture\chair_col_1.md3 +file2=<>\SOF2Pack\models\objects\Colombia\furniture\chair_col_2.md3 +file3=<>\SOF2Pack\models\objects\Colombia\furniture\table_col.md3 +file4=<>\SOF2Pack\models\objects\Colombia\furniture\table_col_1.md3 + +[models\objects\Colombia\jungle] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Colombia\jungle\fern_ledge.md3 +file1=<>\SOF2Pack\models\objects\Colombia\jungle\fern_lrg01.md3 +file2=<>\SOF2Pack\models\objects\Colombia\jungle\fern_lrg01_1.md3 +file3=<>\SOF2Pack\models\objects\Colombia\jungle\fern_lrg01_2.md3 +file4=<>\SOF2Pack\models\objects\Colombia\jungle\fern_sm01.md3 +file5=<>\SOF2Pack\models\objects\Colombia\jungle\grass2.md3 +file6=<>\SOF2Pack\models\objects\Colombia\jungle\grass2sm.md3 +file7=<>\SOF2Pack\models\objects\Colombia\jungle\grass.md3 +file8=<>\SOF2Pack\models\objects\Colombia\jungle\grass_clump.md3 +file9=<>\SOF2Pack\models\objects\Colombia\jungle\grass_clumpsm.md3 +file10=<>\SOF2Pack\models\objects\Colombia\jungle\grass_tall.md3 +file11=<>\SOF2Pack\models\objects\Colombia\jungle\plant_4leafs.md3 +file12=<>\SOF2Pack\models\objects\Colombia\jungle\plant_lrg01.md3 +file13=<>\SOF2Pack\models\objects\Colombia\jungle\plant_lrg01_1.md3 +file14=<>\SOF2Pack\models\objects\Colombia\jungle\plant_lrg01_2.md3 +file15=<>\SOF2Pack\models\objects\Colombia\jungle\plant_stalk.md3 +file16=<>\SOF2Pack\models\objects\Colombia\jungle\rock01.md3 +file17=<>\SOF2Pack\models\objects\Colombia\jungle\rock02.md3 +file18=<>\SOF2Pack\models\objects\Colombia\jungle\tree01.md3 +file19=<>\SOF2Pack\models\objects\Colombia\jungle\tree02.md3 +file20=<>\SOF2Pack\models\objects\Colombia\jungle\tree02_1.md3 +file21=<>\SOF2Pack\models\objects\Colombia\jungle\tree02_2.md3 +file22=<>\SOF2Pack\models\objects\Colombia\jungle\tree02_rmg.md3 +file23=<>\SOF2Pack\models\objects\Colombia\jungle\tree02_rmg_1.md3 +file24=<>\SOF2Pack\models\objects\Colombia\jungle\tree02_rmg_2.md3 +file25=<>\SOF2Pack\models\objects\Colombia\jungle\tree04.md3 +file26=<>\SOF2Pack\models\objects\Colombia\jungle\tree05.md3 +file27=<>\SOF2Pack\models\objects\Colombia\jungle\tree06.md3 +file28=<>\SOF2Pack\models\objects\Colombia\jungle\tree06_1.md3 +file29=<>\SOF2Pack\models\objects\Colombia\jungle\tree06_2.md3 +file30=<>\SOF2Pack\models\objects\Colombia\jungle\tree06_rmg.md3 +file31=<>\SOF2Pack\models\objects\Colombia\jungle\tree06_rmg_1.md3 +file32=<>\SOF2Pack\models\objects\Colombia\jungle\tree06_rmg_2.md3 +file33=<>\SOF2Pack\models\objects\Colombia\jungle\tree08.md3 +file34=<>\SOF2Pack\models\objects\Colombia\jungle\tree08_1.md3 +file35=<>\SOF2Pack\models\objects\Colombia\jungle\tree08_2.md3 +file36=<>\SOF2Pack\models\objects\Colombia\jungle\tree08_3.md3 +file37=<>\SOF2Pack\models\objects\Colombia\jungle\tree08rmg.md3 +file38=<>\SOF2Pack\models\objects\Colombia\jungle\tree08rmg_1.md3 +file39=<>\SOF2Pack\models\objects\Colombia\jungle\tree08rmg_2.md3 +file40=<>\SOF2Pack\models\objects\Colombia\jungle\tree08rmg_3.md3 +file41=<>\SOF2Pack\models\objects\Colombia\jungle\tree09.md3 +file42=<>\SOF2Pack\models\objects\Colombia\jungle\tree09_1.md3 +file43=<>\SOF2Pack\models\objects\Colombia\jungle\tree09_2.md3 +file44=<>\SOF2Pack\models\objects\Colombia\jungle\tree09_3.md3 +file45=<>\SOF2Pack\models\objects\Colombia\jungle\tree09rmg.md3 +file46=<>\SOF2Pack\models\objects\Colombia\jungle\tree09rmg_1.md3 +file47=<>\SOF2Pack\models\objects\Colombia\jungle\tree09rmg_2.md3 +file48=<>\SOF2Pack\models\objects\Colombia\jungle\tree09rmg_3.md3 +file49=<>\SOF2Pack\models\objects\Colombia\jungle\tree10.md3 +file50=<>\SOF2Pack\models\objects\Colombia\jungle\tree_sidehill.md3 +file51=<>\SOF2Pack\models\objects\Colombia\jungle\tree_sidehill_1.md3 +file52=<>\SOF2Pack\models\objects\Colombia\jungle\tree_sidehill_2.md3 +file53=<>\SOF2Pack\models\objects\Colombia\jungle\treetest.md3 + +[models\objects\Colombia\lights] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Colombia\lights\hanging_light.md3 +file1=<>\SOF2Pack\models\objects\Colombia\lights\hanging_light_1.md3 +file2=<>\SOF2Pack\models\objects\Colombia\lights\hanging_light_2.md3 + +[models\objects\Colombia\market] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Colombia\market\basket.md3 +file1=<>\SOF2Pack\models\objects\Colombia\market\basket_1.md3 +file2=<>\SOF2Pack\models\objects\Colombia\market\basket_2.md3 +file3=<>\SOF2Pack\models\objects\Colombia\market\basket_cloth.md3 +file4=<>\SOF2Pack\models\objects\Colombia\market\basket_cloth_1.md3 +file5=<>\SOF2Pack\models\objects\Colombia\market\basket_cloth_2.md3 +file6=<>\SOF2Pack\models\objects\Colombia\market\basket_food.md3 +file7=<>\SOF2Pack\models\objects\Colombia\market\basket_food_1.md3 +file8=<>\SOF2Pack\models\objects\Colombia\market\basket_food_2.md3 +file9=<>\SOF2Pack\models\objects\Colombia\market\boxfruit.md3 +file10=<>\SOF2Pack\models\objects\Colombia\market\boxfruit_1.md3 +file11=<>\SOF2Pack\models\objects\Colombia\market\boxfruit_2.md3 +file12=<>\SOF2Pack\models\objects\Colombia\market\boxfruit_empty.md3 +file13=<>\SOF2Pack\models\objects\Colombia\market\boxfruit_empty_1.md3 +file14=<>\SOF2Pack\models\objects\Colombia\market\boxfruit_empty_2.md3 +file15=<>\SOF2Pack\models\objects\Colombia\market\chair.md3 +file16=<>\SOF2Pack\models\objects\Colombia\market\chair_1.md3 +file17=<>\SOF2Pack\models\objects\Colombia\market\chair_2.md3 +file18=<>\SOF2Pack\models\objects\Colombia\market\pots1.md3 +file19=<>\SOF2Pack\models\objects\Colombia\market\pots1_1.md3 +file20=<>\SOF2Pack\models\objects\Colombia\market\pots1_2.md3 +file21=<>\SOF2Pack\models\objects\Colombia\market\pots2.md3 +file22=<>\SOF2Pack\models\objects\Colombia\market\pots2_1.md3 +file23=<>\SOF2Pack\models\objects\Colombia\market\pots2_2.md3 +file24=<>\SOF2Pack\models\objects\Colombia\market\pots3.md3 +file25=<>\SOF2Pack\models\objects\Colombia\market\pots3_1.md3 +file26=<>\SOF2Pack\models\objects\Colombia\market\pots3_2.md3 +file27=<>\SOF2Pack\models\objects\Colombia\market\sawhorse.md3 +file28=<>\SOF2Pack\models\objects\Colombia\market\sawhorse_1.md3 +file29=<>\SOF2Pack\models\objects\Colombia\market\sawhorse_2.md3 + +[models\objects\Colombia\misc] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Colombia\misc\claymore.md3 +file1=<>\SOF2Pack\models\objects\Colombia\misc\com_equip_phone.md3 +file2=<>\SOF2Pack\models\objects\Colombia\misc\com_equip_popup.md3 +file3=<>\SOF2Pack\models\objects\Colombia\misc\com_equip_sdish.md3 +file4=<>\SOF2Pack\models\objects\Colombia\misc\flowerbox1.md3 +file5=<>\SOF2Pack\models\objects\Colombia\misc\flowerbox1_empty.md3 +file6=<>\SOF2Pack\models\objects\Colombia\misc\flowerbox2.md3 +file7=<>\SOF2Pack\models\objects\Colombia\misc\flowerbox2_empty.md3 +file8=<>\SOF2Pack\models\objects\Colombia\misc\flowerbox3.md3 +file9=<>\SOF2Pack\models\objects\Colombia\misc\flowerbox3_empty.md3 +file10=<>\SOF2Pack\models\objects\Colombia\misc\flowerpot_big2.md3 +file11=<>\SOF2Pack\models\objects\Colombia\misc\flowerpot_big2_1.md3 +file12=<>\SOF2Pack\models\objects\Colombia\misc\flowerpot_big2_2.md3 +file13=<>\SOF2Pack\models\objects\Colombia\misc\flowerpot_big.md3 +file14=<>\SOF2Pack\models\objects\Colombia\misc\flowerpot_big_1.md3 +file15=<>\SOF2Pack\models\objects\Colombia\misc\flowerpot_big_2.md3 +file16=<>\SOF2Pack\models\objects\Colombia\misc\flowerpot_med.md3 +file17=<>\SOF2Pack\models\objects\Colombia\misc\flowerpot_med_1.md3 +file18=<>\SOF2Pack\models\objects\Colombia\misc\flowerpot_med_2.md3 +file19=<>\SOF2Pack\models\objects\Colombia\misc\flowerpot_small.md3 +file20=<>\SOF2Pack\models\objects\Colombia\misc\flowerpot_small_1.md3 +file21=<>\SOF2Pack\models\objects\Colombia\misc\flowerpot_small_2.md3 +file22=<>\SOF2Pack\models\objects\Colombia\misc\statue.md3 +file23=<>\SOF2Pack\models\objects\Colombia\misc\tire.md3 +file24=<>\SOF2Pack\models\objects\Colombia\misc\tire_1.md3 +file25=<>\SOF2Pack\models\objects\Colombia\misc\tire_2.md3 + +[models\objects\Colombia\vehicles] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Colombia\vehicles\jeep.md3 +file1=<>\SOF2Pack\models\objects\Colombia\vehicles\jeep_bombed.md3 +file2=<>\SOF2Pack\models\objects\Colombia\vehicles\jeep_dm.md3 +file3=<>\SOF2Pack\models\objects\Colombia\vehicles\truck_black_temp.md3 +file4=<>\SOF2Pack\models\objects\Colombia\vehicles\truck_black_temp_1.md3 +file5=<>\SOF2Pack\models\objects\Colombia\vehicles\truck_black_temp_2.md3 +file6=<>\SOF2Pack\models\objects\Colombia\vehicles\truck_flatbed.md3 +file7=<>\SOF2Pack\models\objects\Colombia\vehicles\truck_personnel.md3 +file8=<>\SOF2Pack\models\objects\Colombia\vehicles\truck_personnel_1.md3 +file9=<>\SOF2Pack\models\objects\Colombia\vehicles\truck_personnel_2.md3 + +[models\objects\Common] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Common\ashtray.md3 +file1=<>\SOF2Pack\models\objects\Common\beam.md3 +file2=<>\SOF2Pack\models\objects\Common\body_bag.md3 +file3=<>\SOF2Pack\models\objects\Common\candleholder.md3 +file4=<>\SOF2Pack\models\objects\Common\desklamp1.md3 +file5=<>\SOF2Pack\models\objects\Common\desklamp1_1.md3 +file6=<>\SOF2Pack\models\objects\Common\desklamp1_2.md3 +file7=<>\SOF2Pack\models\objects\Common\desklamp2.md3 +file8=<>\SOF2Pack\models\objects\Common\desklamp2_1.md3 +file9=<>\SOF2Pack\models\objects\Common\desklamp2_2.md3 +file10=<>\SOF2Pack\models\objects\Common\faucet.md3 +file11=<>\SOF2Pack\models\objects\Common\glass.md3 +file12=<>\SOF2Pack\models\objects\Common\lantern.md3 +file13=<>\SOF2Pack\models\objects\Common\lantern_1.md3 +file14=<>\SOF2Pack\models\objects\Common\lantern_2.md3 +file15=<>\SOF2Pack\models\objects\Common\payphone.md3 +file16=<>\SOF2Pack\models\objects\Common\phone.md3 +file17=<>\SOF2Pack\models\objects\Common\poolball.md3 +file18=<>\SOF2Pack\models\objects\Common\sandbag_corner.md3 +file19=<>\SOF2Pack\models\objects\Common\sandbag_end.md3 +file20=<>\SOF2Pack\models\objects\Common\sandbag_section.md3 +file21=<>\SOF2Pack\models\objects\Common\search_light.md3 +file22=<>\SOF2Pack\models\objects\Common\shoes.md3 +file23=<>\SOF2Pack\models\objects\Common\shoes_1.md3 +file24=<>\SOF2Pack\models\objects\Common\shoes_2.md3 +file25=<>\SOF2Pack\models\objects\Common\sink_faucet.md3 +file26=<>\SOF2Pack\models\objects\Common\sprinkler.md3 +file27=<>\SOF2Pack\models\objects\Common\sprinkler_1.md3 +file28=<>\SOF2Pack\models\objects\Common\sprinkler_2.md3 +file29=<>\SOF2Pack\models\objects\Common\taxi_cab.md3 +file30=<>\SOF2Pack\models\objects\Common\test.md3 +file31=<>\SOF2Pack\models\objects\Common\toilet.md3 +file32=<>\SOF2Pack\models\objects\Common\toilet_1.md3 +file33=<>\SOF2Pack\models\objects\Common\toilet_2.md3 +file34=<>\SOF2Pack\models\objects\Common\toilet_damaged.md3 +file35=<>\SOF2Pack\models\objects\Common\toolkit.md3 +file36=<>\SOF2Pack\models\objects\Common\tracklight.md3 +file37=<>\SOF2Pack\models\objects\Common\tracklight_1.md3 +file38=<>\SOF2Pack\models\objects\Common\trash_can_empty.md3 +file39=<>\SOF2Pack\models\objects\Common\trash_can_lid.md3 +file40=<>\SOF2Pack\models\objects\Common\trash_can_lid_1.md3 +file41=<>\SOF2Pack\models\objects\Common\trash_can_lid_2.md3 +file42=<>\SOF2Pack\models\objects\Common\trash_can_nolid.md3 +file43=<>\SOF2Pack\models\objects\Common\trash_can_nolid_1.md3 +file44=<>\SOF2Pack\models\objects\Common\trash_can_nolid_2.md3 +file45=<>\SOF2Pack\models\objects\Common\trucksandbag.md3 +file46=<>\SOF2Pack\models\objects\Common\tv.md3 +file47=<>\SOF2Pack\models\objects\Common\tv_1.md3 +file48=<>\SOF2Pack\models\objects\Common\tv_2.md3 +file49=<>\SOF2Pack\models\objects\Common\urinal.md3 +file50=<>\SOF2Pack\models\objects\Common\urinal_1.md3 +file51=<>\SOF2Pack\models\objects\Common\urinal_2.md3 +file52=<>\SOF2Pack\models\objects\Common\yugo.md3 +SubDir0=models\objects\Common\barrels +SubDir1=models\objects\Common\doorhandle +SubDir2=models\objects\Common\forklift +SubDir3=models\objects\Common\handcart +SubDir4=models\objects\Common\rope +SubDir5=models\objects\Common\SAM + +[models\objects\Common\barrels] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Common\barrels\barrel_hk.md3 + +[models\objects\Common\doorhandle] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Common\doorhandle\handle.md3 + +[models\objects\Common\forklift] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Common\forklift\forklift.md3 + +[models\objects\Common\handcart] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Common\handcart\handcart.md3 +file1=<>\SOF2Pack\models\objects\Common\handcart\handcart_lrg.md3 + +[models\objects\Common\rope] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Common\rope\rope.md3 +file1=<>\SOF2Pack\models\objects\Common\rope\rope_1.md3 +file2=<>\SOF2Pack\models\objects\Common\rope\rope_2.md3 + +[models\objects\Common\SAM] +fulldirectory= +SubDir0=models\objects\Common\SAM\missile + +[models\objects\Common\SAM\missile] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Common\SAM\missile\missle.md3 + +[models\objects\Finca] +fulldirectory= +SubDir0=models\objects\Finca\cars +SubDir1=models\objects\Finca\furniture +SubDir2=models\objects\Finca\lamps +SubDir3=models\objects\Finca\misc + +[models\objects\Finca\cars] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Finca\cars\hummer.md3 +file1=<>\SOF2Pack\models\objects\Finca\cars\limo.md3 +file2=<>\SOF2Pack\models\objects\Finca\cars\luxurysedan.md3 + +[models\objects\Finca\furniture] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Finca\furniture\bar_stool.md3 +file1=<>\SOF2Pack\models\objects\Finca\furniture\chair_finca.md3 +file2=<>\SOF2Pack\models\objects\Finca\furniture\masterchair_finca.md3 +file3=<>\SOF2Pack\models\objects\Finca\furniture\oak_table.md3 +file4=<>\SOF2Pack\models\objects\Finca\furniture\poolside_chair.md3 +file5=<>\SOF2Pack\models\objects\Finca\furniture\poolside_foot_stool.md3 +file6=<>\SOF2Pack\models\objects\Finca\furniture\poolside_table.md3 +file7=<>\SOF2Pack\models\objects\Finca\furniture\sundial.md3 + +[models\objects\Finca\lamps] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Finca\lamps\desklamp1.md3 +file1=<>\SOF2Pack\models\objects\Finca\lamps\desklamp2.md3 +file2=<>\SOF2Pack\models\objects\Finca\lamps\floorlamp1.md3 +file3=<>\SOF2Pack\models\objects\Finca\lamps\floorlamp2.md3 +file4=<>\SOF2Pack\models\objects\Finca\lamps\lightbulb1.md3 +file5=<>\SOF2Pack\models\objects\Finca\lamps\lightbulb2.md3 +file6=<>\SOF2Pack\models\objects\Finca\lamps\wall_light1.md3 +file7=<>\SOF2Pack\models\objects\Finca\lamps\wall_light2.md3 +file8=<>\SOF2Pack\models\objects\Finca\lamps\wall_light3.md3 + +[models\objects\Finca\misc] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Finca\misc\bear_rug.md3 +file1=<>\SOF2Pack\models\objects\Finca\misc\bowling_ball.md3 +file2=<>\SOF2Pack\models\objects\Finca\misc\bowling_pin.md3 +file3=<>\SOF2Pack\models\objects\Finca\misc\bust.md3 +file4=<>\SOF2Pack\models\objects\Finca\misc\closed_book.md3 +file5=<>\SOF2Pack\models\objects\Finca\misc\coffee_cup.md3 +file6=<>\SOF2Pack\models\objects\Finca\misc\coffee_cup_1.md3 +file7=<>\SOF2Pack\models\objects\Finca\misc\coffee_cup_2.md3 +file8=<>\SOF2Pack\models\objects\Finca\misc\katana.md3 +file9=<>\SOF2Pack\models\objects\Finca\misc\moose.md3 +file10=<>\SOF2Pack\models\objects\Finca\misc\open_book.md3 +file11=<>\SOF2Pack\models\objects\Finca\misc\steer_horns.md3 +file12=<>\SOF2Pack\models\objects\Finca\misc\wine_bottle.md3 + +[models\objects\Hongkong] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Hongkong\electric_chair.md3 +SubDir0=models\objects\Hongkong\baskets_pots +SubDir1=models\objects\Hongkong\lights +SubDir2=models\objects\Hongkong\misc +SubDir3=models\objects\Hongkong\paper_lanterns +SubDir4=models\objects\Hongkong\prison +SubDir5=models\objects\Hongkong\rice_bags +SubDir6=models\objects\Hongkong\vehicles + +[models\objects\Hongkong\baskets_pots] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Hongkong\baskets_pots\basket_efx.md3 +file1=<>\SOF2Pack\models\objects\Hongkong\baskets_pots\basket_single_cover.md3 +file2=<>\SOF2Pack\models\objects\Hongkong\baskets_pots\basket_single_food.md3 +file3=<>\SOF2Pack\models\objects\Hongkong\baskets_pots\basket_single_open.md3 +file4=<>\SOF2Pack\models\objects\Hongkong\baskets_pots\basket_single_oval.md3 +file5=<>\SOF2Pack\models\objects\Hongkong\baskets_pots\basket_single_over.md3 +file6=<>\SOF2Pack\models\objects\Hongkong\baskets_pots\baskets_stack.md3 +file7=<>\SOF2Pack\models\objects\Hongkong\baskets_pots\baskets_stack_lrg.md3 +file8=<>\SOF2Pack\models\objects\Hongkong\baskets_pots\baskets_stack_single.md3 +file9=<>\SOF2Pack\models\objects\Hongkong\baskets_pots\baskets_stack_square.md3 +file10=<>\SOF2Pack\models\objects\Hongkong\baskets_pots\bucket_handles.md3 +file11=<>\SOF2Pack\models\objects\Hongkong\baskets_pots\pot_lrg.md3 + +[models\objects\Hongkong\lights] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Hongkong\lights\cell.md3 + +[models\objects\Hongkong\misc] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Hongkong\misc\mahi.md3 +file1=<>\SOF2Pack\models\objects\Hongkong\misc\stoplight.md3 +file2=<>\SOF2Pack\models\objects\Hongkong\misc\tire_dock.md3 +file3=<>\SOF2Pack\models\objects\Hongkong\misc\tuna.md3 +file4=<>\SOF2Pack\models\objects\Hongkong\misc\vase.md3 + +[models\objects\Hongkong\paper_lanterns] +fulldirectory= + +[models\objects\Hongkong\prison] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Hongkong\prison\bed.md3 +file1=<>\SOF2Pack\models\objects\Hongkong\prison\clipboard.md3 + +[models\objects\Hongkong\rice_bags] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Hongkong\rice_bags\rice_bag_bentdown.md3 +file1=<>\SOF2Pack\models\objects\Hongkong\rice_bags\rice_bag_bentup.md3 +file2=<>\SOF2Pack\models\objects\Hongkong\rice_bags\rice_bag_single.md3 +file3=<>\SOF2Pack\models\objects\Hongkong\rice_bags\rice_bags_stack.md3 +file4=<>\SOF2Pack\models\objects\Hongkong\rice_bags\rice_bags_stack_lrg.md3 +file5=<>\SOF2Pack\models\objects\Hongkong\rice_bags\rice_bags_stacklow.md3 +file6=<>\SOF2Pack\models\objects\Hongkong\rice_bags\rice_bags_stacklow_lrg.md3 + +[models\objects\Hongkong\vehicles] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Hongkong\vehicles\car_hk.md3 +file1=<>\SOF2Pack\models\objects\Hongkong\vehicles\truck_flatbed.md3 +file2=<>\SOF2Pack\models\objects\Hongkong\vehicles\truck_hk.md3 + +[models\objects\Hospital] +fulldirectory= +SubDir0=models\objects\Hospital\carts +SubDir1=models\objects\Hospital\misc + +[models\objects\Hospital\carts] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Hospital\carts\cart_2tray.md3 +file1=<>\SOF2Pack\models\objects\Hospital\carts\cart_2tray_items2.md3 +file2=<>\SOF2Pack\models\objects\Hospital\carts\cart_2tray_items.md3 +file3=<>\SOF2Pack\models\objects\Hospital\carts\cart_beverage.md3 +file4=<>\SOF2Pack\models\objects\Hospital\carts\cart_food.md3 +file5=<>\SOF2Pack\models\objects\Hospital\carts\cart_medical.md3 +file6=<>\SOF2Pack\models\objects\Hospital\carts\monitor_cart.md3 +file7=<>\SOF2Pack\models\objects\Hospital\carts\monitor_cart_1.md3 +file8=<>\SOF2Pack\models\objects\Hospital\carts\monitor_cart_2.md3 + +[models\objects\Hospital\misc] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Hospital\misc\ambulance.md3 +file1=<>\SOF2Pack\models\objects\Hospital\misc\bed.md3 +file2=<>\SOF2Pack\models\objects\Hospital\misc\drip_stand.md3 +file3=<>\SOF2Pack\models\objects\Hospital\misc\firetruck.md3 +file4=<>\SOF2Pack\models\objects\Hospital\misc\gurney.md3 +file5=<>\SOF2Pack\models\objects\Hospital\misc\microscope.md3 +file6=<>\SOF2Pack\models\objects\Hospital\misc\office_chair.md3 +file7=<>\SOF2Pack\models\objects\Hospital\misc\office_chair_1.md3 +file8=<>\SOF2Pack\models\objects\Hospital\misc\office_chair_2.md3 +file9=<>\SOF2Pack\models\objects\Hospital\misc\spray_bottle.md3 +file10=<>\SOF2Pack\models\objects\Hospital\misc\test_tube_1.md3 +file11=<>\SOF2Pack\models\objects\Hospital\misc\test_tube_2.md3 +file12=<>\SOF2Pack\models\objects\Hospital\misc\test_tube_3.md3 +file13=<>\SOF2Pack\models\objects\Hospital\misc\test_tube_4.md3 +file14=<>\SOF2Pack\models\objects\Hospital\misc\test_tube_tray.md3 +file15=<>\SOF2Pack\models\objects\Hospital\misc\wall_machine.md3 +file16=<>\SOF2Pack\models\objects\Hospital\misc\wheelchair.md3 + +[models\objects\Jordan] +fulldirectory= +SubDir0=models\objects\Jordan\misc + +[models\objects\Jordan\misc] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Jordan\misc\cpu.md3 +file1=<>\SOF2Pack\models\objects\Jordan\misc\keyboard.md3 +file2=<>\SOF2Pack\models\objects\Jordan\misc\keyboard_1.md3 +file3=<>\SOF2Pack\models\objects\Jordan\misc\keyboard_2.md3 +file4=<>\SOF2Pack\models\objects\Jordan\misc\monitor.md3 +file5=<>\SOF2Pack\models\objects\Jordan\misc\monitor_1.md3 +file6=<>\SOF2Pack\models\objects\Jordan\misc\monitor_2.md3 + +[models\objects\Kamchatka] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Kamchatka\deadtree_med.md3 +file1=<>\SOF2Pack\models\objects\Kamchatka\limb_1.md3 +file2=<>\SOF2Pack\models\objects\Kamchatka\limb_2.md3 +file3=<>\SOF2Pack\models\objects\Kamchatka\tree2.md3 +file4=<>\SOF2Pack\models\objects\Kamchatka\tree2_1.md3 +file5=<>\SOF2Pack\models\objects\Kamchatka\tree2_2.md3 +file6=<>\SOF2Pack\models\objects\Kamchatka\tree2_rmg.md3 +file7=<>\SOF2Pack\models\objects\Kamchatka\tree2_rmg_1.md3 +file8=<>\SOF2Pack\models\objects\Kamchatka\tree2_rmg_2.md3 +file9=<>\SOF2Pack\models\objects\Kamchatka\tree_lrg.md3 +file10=<>\SOF2Pack\models\objects\Kamchatka\tree_lrg_1.md3 +file11=<>\SOF2Pack\models\objects\Kamchatka\tree_lrg_2.md3 +file12=<>\SOF2Pack\models\objects\Kamchatka\tree_lrg_rmg.md3 +file13=<>\SOF2Pack\models\objects\Kamchatka\tree_lrg_rmg_1.md3 +file14=<>\SOF2Pack\models\objects\Kamchatka\tree_lrg_rmg_2.md3 +file15=<>\SOF2Pack\models\objects\Kamchatka\tree_med.md3 +file16=<>\SOF2Pack\models\objects\Kamchatka\tree_med_1.md3 +file17=<>\SOF2Pack\models\objects\Kamchatka\tree_med_2.md3 +file18=<>\SOF2Pack\models\objects\Kamchatka\tree_med_rmg.md3 +file19=<>\SOF2Pack\models\objects\Kamchatka\tree_med_rmg_1.md3 +file20=<>\SOF2Pack\models\objects\Kamchatka\tree_med_rmg_2.md3 +SubDir0=models\objects\Kamchatka\misc + +[models\objects\Kamchatka\misc] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Kamchatka\misc\chem_suit.md3 + +[models\objects\Liner] +fulldirectory= +SubDir0=models\objects\Liner\furniture +SubDir1=models\objects\Liner\lights +SubDir2=models\objects\Liner\misc +SubDir3=models\objects\Liner\toilet + +[models\objects\Liner\furniture] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Liner\furniture\galley_chair.md3 +file1=<>\SOF2Pack\models\objects\Liner\furniture\galley_chair_1.md3 +file2=<>\SOF2Pack\models\objects\Liner\furniture\galley_chair_2.md3 +file3=<>\SOF2Pack\models\objects\Liner\furniture\sink.md3 + +[models\objects\Liner\lights] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Liner\lights\wall_light2.md3 +file1=<>\SOF2Pack\models\objects\Liner\lights\wall_light.md3 + +[models\objects\Liner\misc] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Liner\misc\ax.md3 +file1=<>\SOF2Pack\models\objects\Liner\misc\ax_1.md3 +file2=<>\SOF2Pack\models\objects\Liner\misc\ax_2.md3 +file3=<>\SOF2Pack\models\objects\Liner\misc\big_pot.md3 +file4=<>\SOF2Pack\models\objects\Liner\misc\big_pot_1.md3 +file5=<>\SOF2Pack\models\objects\Liner\misc\big_pot_2.md3 +file6=<>\SOF2Pack\models\objects\Liner\misc\big_pot_nolid.md3 +file7=<>\SOF2Pack\models\objects\Liner\misc\big_pot_nolid_1.md3 +file8=<>\SOF2Pack\models\objects\Liner\misc\big_pot_nolid_2.md3 +file9=<>\SOF2Pack\models\objects\Liner\misc\cart_tray.md3 +file10=<>\SOF2Pack\models\objects\Liner\misc\cart_tray_1.md3 +file11=<>\SOF2Pack\models\objects\Liner\misc\cart_tray_2.md3 +file12=<>\SOF2Pack\models\objects\Liner\misc\coffee_maker.md3 +file13=<>\SOF2Pack\models\objects\Liner\misc\coffee_maker_1.md3 +file14=<>\SOF2Pack\models\objects\Liner\misc\coffee_maker_2.md3 +file15=<>\SOF2Pack\models\objects\Liner\misc\coffee_pot.md3 +file16=<>\SOF2Pack\models\objects\Liner\misc\control_1.md3 +file17=<>\SOF2Pack\models\objects\Liner\misc\extinguisher.md3 +file18=<>\SOF2Pack\models\objects\Liner\misc\extinguisher_1.md3 +file19=<>\SOF2Pack\models\objects\Liner\misc\extinguisher_2.md3 +file20=<>\SOF2Pack\models\objects\Liner\misc\foodtray.md3 +file21=<>\SOF2Pack\models\objects\Liner\misc\frying_pan.md3 +file22=<>\SOF2Pack\models\objects\Liner\misc\headphones.md3 +file23=<>\SOF2Pack\models\objects\Liner\misc\life_preserver.md3 +file24=<>\SOF2Pack\models\objects\Liner\misc\monitor.md3 +file25=<>\SOF2Pack\models\objects\Liner\misc\mop.md3 +file26=<>\SOF2Pack\models\objects\Liner\misc\mop_bucket.md3 +file27=<>\SOF2Pack\models\objects\Liner\misc\phone.md3 +file28=<>\SOF2Pack\models\objects\Liner\misc\pot.md3 +file29=<>\SOF2Pack\models\objects\Liner\misc\pot_nolid.md3 +file30=<>\SOF2Pack\models\objects\Liner\misc\raft.md3 +file31=<>\SOF2Pack\models\objects\Liner\misc\spatula.md3 +file32=<>\SOF2Pack\models\objects\Liner\misc\throttle.md3 +file33=<>\SOF2Pack\models\objects\Liner\misc\valve2.md3 +file34=<>\SOF2Pack\models\objects\Liner\misc\wood_spoon.md3 + +[models\objects\Liner\toilet] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Liner\toilet\toilet.md3 +file1=<>\SOF2Pack\models\objects\Liner\toilet\toilet_open.md3 + +[models\objects\Prague] +fulldirectory= +SubDir0=models\objects\Prague\furniture +SubDir1=models\objects\Prague\misc + +[models\objects\Prague\furniture] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Prague\furniture\table_long.md3 +file1=<>\SOF2Pack\models\objects\Prague\furniture\table_round.md3 +file2=<>\SOF2Pack\models\objects\Prague\furniture\umbrella.md3 + +[models\objects\Prague\misc] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Prague\misc\big_chandelier.md3 +file1=<>\SOF2Pack\models\objects\Prague\misc\knife_prague.md3 +file2=<>\SOF2Pack\models\objects\Prague\misc\m1911a1_prague.md3 +file3=<>\SOF2Pack\models\objects\Prague\misc\med_chandelier.md3 +file4=<>\SOF2Pack\models\objects\Prague\misc\statue.md3 + +[models\objects\Shop] +fulldirectory= +SubDir0=models\objects\Shop\misc + +[models\objects\Shop\misc] +fulldirectory= +file0=<>\SOF2Pack\models\objects\Shop\misc\copy_machine.md3 +file1=<>\SOF2Pack\models\objects\Shop\misc\helipad_lights.md3 +file2=<>\SOF2Pack\models\objects\Shop\misc\helipad_lights_1.md3 +file3=<>\SOF2Pack\models\objects\Shop\misc\helipad_lights_2.md3 + +[models\pick_ups] +fulldirectory= +file0=<>\SOF2Pack\models\pick_ups\ammo_9mm_lrg.md3 +file1=<>\SOF2Pack\models\pick_ups\ammo_9mm_smll.md3 +file2=<>\SOF2Pack\models\pick_ups\ammo_20_lrg.md3 +file3=<>\SOF2Pack\models\pick_ups\ammo_20_smll.md3 +file4=<>\SOF2Pack\models\pick_ups\ammo_40_lrg.md3 +file5=<>\SOF2Pack\models\pick_ups\ammo_40_smll.md3 +file6=<>\SOF2Pack\models\pick_ups\ammo_45_lrg.md3 +file7=<>\SOF2Pack\models\pick_ups\ammo_45_smll.md3 +file8=<>\SOF2Pack\models\pick_ups\ammo_50_lrg.md3 +file9=<>\SOF2Pack\models\pick_ups\ammo_50_smll.md3 +file10=<>\SOF2Pack\models\pick_ups\ammo_556_lrg.md3 +file11=<>\SOF2Pack\models\pick_ups\ammo_556_smll.md3 +file12=<>\SOF2Pack\models\pick_ups\ammo_762_lrg.md3 +file13=<>\SOF2Pack\models\pick_ups\ammo_762_smll.md3 +file14=<>\SOF2Pack\models\pick_ups\ammo_rpg7_lrg.md3 +file15=<>\SOF2Pack\models\pick_ups\ammo_rpg7_smll.md3 +file16=<>\SOF2Pack\models\pick_ups\ammo_shotgun_lrg.md3 +file17=<>\SOF2Pack\models\pick_ups\ammo_shotgun_smll.md3 +file18=<>\SOF2Pack\models\pick_ups\anm14_lrg.md3 +file19=<>\SOF2Pack\models\pick_ups\anm14_smll.md3 +file20=<>\SOF2Pack\models\pick_ups\armor_large.md3 +file21=<>\SOF2Pack\models\pick_ups\armor_medium.md3 +file22=<>\SOF2Pack\models\pick_ups\armor_small.md3 +file23=<>\SOF2Pack\models\pick_ups\briefcase_silver.md3 +file24=<>\SOF2Pack\models\pick_ups\f1_lrg.md3 +file25=<>\SOF2Pack\models\pick_ups\f1_smll.md3 +file26=<>\SOF2Pack\models\pick_ups\health_lrg.md3 +file27=<>\SOF2Pack\models\pick_ups\health_smll.md3 +file28=<>\SOF2Pack\models\pick_ups\l2a2_lrg.md3 +file29=<>\SOF2Pack\models\pick_ups\l2a2_smll.md3 +file30=<>\SOF2Pack\models\pick_ups\m15_lrg.md3 +file31=<>\SOF2Pack\models\pick_ups\m15_smll.md3 +file32=<>\SOF2Pack\models\pick_ups\m67_lrg.md3 +file33=<>\SOF2Pack\models\pick_ups\m67_smll.md3 +file34=<>\SOF2Pack\models\pick_ups\m84_lrg.md3 +file35=<>\SOF2Pack\models\pick_ups\m84_smll.md3 +file36=<>\SOF2Pack\models\pick_ups\mdn11_lrg.md3 +file37=<>\SOF2Pack\models\pick_ups\mdn11_smll.md3 +file38=<>\SOF2Pack\models\pick_ups\rmg_book.md3 +file39=<>\SOF2Pack\models\pick_ups\rmg_briefcase.md3 +file40=<>\SOF2Pack\models\pick_ups\rmg_discs.md3 +file41=<>\SOF2Pack\models\pick_ups\rmg_documents.md3 +file42=<>\SOF2Pack\models\pick_ups\rmg_tnt.md3 +file43=<>\SOF2Pack\models\pick_ups\smohg92_lrg.md3 +file44=<>\SOF2Pack\models\pick_ups\smohg92_smll.md3 + +[models\weapons] +fulldirectory= +file0=<>\SOF2Pack\models\weapons\grenade_handle.md3 +SubDir0=models\weapons\ak74 +SubDir1=models\weapons\shells + +[models\weapons\ak74] +fulldirectory= +file0=<>\SOF2Pack\models\weapons\ak74\ak74_muzzle_model.md3 + +[models\weapons\shells] +fulldirectory= +file0=<>\SOF2Pack\models\weapons\shells\shell_40mm.md3 +file1=<>\SOF2Pack\models\weapons\shells\shell_40mm_empty.md3 +file2=<>\SOF2Pack\models\weapons\shells\shell_brass.md3 +file3=<>\SOF2Pack\models\weapons\shells\shell_brass_large.md3 +file4=<>\SOF2Pack\models\weapons\shells\shell_brass_small.md3 +file5=<>\SOF2Pack\models\weapons\shells\shell_shotgun.md3 + +[scripts] +fulldirectory= +file0=<>\SOF2Pack\scripts\mp_entities.def +file1=<>\Sof2Pack\scripts\sp_entities.def +file2=<>\Sof2Pack\scripts\default_project.proj +SubDir0=scripts\kam6 + +[scripts\kam6] +fulldirectory=kam6 +file0=<>\Sof2Pack\scripts\kam6\elev_see_player.txt +file1=<>\Sof2Pack\scripts\kam6\ambush_one.txt +file2=<>\Sof2Pack\scripts\kam6\ceiling_cart.txt +file3=<>\Sof2Pack\scripts\kam6\door_ambush.txt +file4=<>\Sof2Pack\scripts\kam6\el_down.txt +file5=<>\Sof2Pack\scripts\kam6\el_downb.txt +file6=<>\Sof2Pack\scripts\kam6\el_up.txt + +[shaders] +fulldirectory= +file0=<>\SOF2Pack\shaders\airport.shader +file1=<>\SOF2Pack\shaders\airport_models.shader +file2=<>\SOF2Pack\shaders\arioche.shader +file3=<>\SOF2Pack\shaders\armory.shader +file4=<>\SOF2Pack\shaders\armory_models.shader +file5=<>\SOF2Pack\shaders\average_armor.shader +file6=<>\SOF2Pack\shaders\average_sleeves.shader +file7=<>\SOF2Pack\shaders\bolt_ons.shader +file8=<>\SOF2Pack\shaders\cemetery.shader +file9=<>\SOF2Pack\shaders\chem_suit.shader +file10=<>\SOF2Pack\shaders\chunks.shader +file11=<>\SOF2Pack\shaders\colombia.shader +file12=<>\SOF2Pack\shaders\colombia_models.shader +file13=<>\SOF2Pack\shaders\colors.shader +file14=<>\SOF2Pack\shaders\common.shader +file15=<>\SOF2Pack\shaders\common_models.shader +file16=<>\SOF2Pack\shaders\decals.shader +file17=<>\SOF2Pack\shaders\dog.shader +file18=<>\SOF2Pack\shaders\effects.shader +file19=<>\SOF2Pack\shaders\fat.shader +file20=<>\SOF2Pack\shaders\female_armor.shader +file21=<>\SOF2Pack\shaders\female_pants.shader +file22=<>\SOF2Pack\shaders\female_skirt.shader +file23=<>\SOF2Pack\shaders\finca.shader +file24=<>\SOF2Pack\shaders\finca_models.shader +file25=<>\SOF2Pack\shaders\fogs.shader +file26=<>\SOF2Pack\shaders\gfx.shader +file27=<>\SOF2Pack\shaders\glass.shader +file28=<>\SOF2Pack\shaders\gore.shader +file29=<>\SOF2Pack\shaders\hongkong.shader +file30=<>\SOF2Pack\shaders\hongkong_models.shader +file31=<>\SOF2Pack\shaders\hospital.shader +file32=<>\SOF2Pack\shaders\hospital_models.shader +file33=<>\SOF2Pack\shaders\hud.shader +file34=<>\SOF2Pack\shaders\instances.shader +file35=<>\SOF2Pack\shaders\jordan.shader +file36=<>\SOF2Pack\shaders\jordan_models.shader +file37=<>\SOF2Pack\shaders\kamchatka.shader +file38=<>\SOF2Pack\shaders\kamchatka_models.shader +file39=<>\SOF2Pack\shaders\liner.shader +file40=<>\SOF2Pack\shaders\liner_models.shader +file41=<>\SOF2Pack\shaders\menu.shader +file42=<>\SOF2Pack\shaders\meta.txt +file43=<>\SOF2Pack\shaders\metashader.shader +file44=<>\SOF2Pack\shaders\mp.shader +file45=<>\SOF2Pack\shaders\osprey.shader +file46=<>\SOF2Pack\shaders\pick_ups.shader +file47=<>\SOF2Pack\shaders\prague.shader +file48=<>\SOF2Pack\shaders\prague_models.shader +file49=<>\SOF2Pack\shaders\roam.shader +file50=<>\SOF2Pack\shaders\shaderlist.txt +file51=<>\SOF2Pack\shaders\shop.shader +file52=<>\SOF2Pack\shaders\shop_models.shader +file53=<>\SOF2Pack\shaders\skies.shader +file54=<>\SOF2Pack\shaders\skingore.shader +file55=<>\SOF2Pack\shaders\snow.shader +file56=<>\SOF2Pack\shaders\sprites.shader +file57=<>\SOF2Pack\shaders\suit_long_coat.shader +file58=<>\SOF2Pack\shaders\suit_sleeves.shader +file59=<>\SOF2Pack\shaders\test.shader +file60=<>\SOF2Pack\shaders\tools.shader +file61=<>\SOF2Pack\shaders\weapons.shader + diff --git a/setup/win32/template/File Groups/STVEF Executable Files.fgl b/setup/win32/template/File Groups/STVEF Executable Files.fgl new file mode 100644 index 00000000..c2f371b2 --- /dev/null +++ b/setup/win32/template/File Groups/STVEF Executable Files.fgl @@ -0,0 +1,41 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\STVEFPack\game.xlink +file1=<>\STVEFPack\bin\bspc.exe +file2=<>\GtkRadiant\tools\quake3\q3data\Release\q3data.exe +file3=<>\RavenPack\bin\BehavEd.exe +file4=<>\STVEFPack\synapse.config +file5=<>\gtk2-win32\bin\libglib-2.0-0.dll +file6=<>\gtk2-win32\bin\intl.dll +file7=<>\libxml2\win32\binaries-release\libxml2.dll +SubDir0=docs + +[docs] +fulldirectory= +file0=<>\STVEFPack\docs\EF-GDK-FAQ.htm +file1=<>\RavenPack\docs\ICARUS Manual.htm +SubDir0=docs\GtkRadiant_STVEF_HOWTO +SubDir1=docs\EF-GDK-FAQ_files +SubDir2=docs\ICARUS Manual_files + +[docs\GtkRadiant_STVEF_HOWTO] +fulldirectory= +file0=<>\STVEFPack\docs\GtkRadiant_STVEF_HOWTO\project.png +file1=<>\STVEFPack\docs\GtkRadiant_STVEF_HOWTO\game1.png +file2=<>\STVEFPack\docs\GtkRadiant_STVEF_HOWTO\game2.png +file3=<>\STVEFPack\docs\GtkRadiant_STVEF_HOWTO\index.html +file4=<>\STVEFPack\docs\GtkRadiant_STVEF_HOWTO\monitor.png + +[docs\EF-GDK-FAQ_files] +fulldirectory= +file0=<>\STVEFPack\docs\EF-GDK-FAQ_files\image001.gif +file1=<>\STVEFPack\docs\EF-GDK-FAQ_files\filelist.xml + +[docs\ICARUS Manual_files] +fulldirectory= +file0=<>\STVEFPack\docs\ICARUS Manual_files\header.htm +file1=<>\STVEFPack\docs\ICARUS Manual_files\filelist.xml + diff --git a/setup/win32/template/File Groups/STVEF Media Files.fgl b/setup/win32/template/File Groups/STVEF Media Files.fgl new file mode 100644 index 00000000..ad0608f0 --- /dev/null +++ b/setup/win32/template/File Groups/STVEF Media Files.fgl @@ -0,0 +1,471 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\STVEFPack\mapextras.pk3 +SubDir0=maps +SubDir1=models +SubDir2=scripts + +[maps] +fulldirectory= +file0=<>\STVEFPack\maps\voy3.map +file1=<>\STVEFPack\maps\_brig.map +file2=<>\STVEFPack\maps\borg1.map +file3=<>\STVEFPack\maps\ctf_kln1.map +file4=<>\STVEFPack\maps\hm_blastradius.map +file5=<>\STVEFPack\maps\tutorial.map +file6=<>\STVEFPack\maps\voy1.map + +[models] +fulldirectory= +SubDir0=models\boltOns +SubDir1=models\chunks +SubDir2=models\flags +SubDir3=models\misc +SubDir4=models\weaphits +SubDir5=models\powerups +SubDir6=models\mapobjects + +[models\boltOns] +fulldirectory= +file0=<>\STVEFPack\models\boltOns\ted.md3 +file1=<>\STVEFPack\models\boltOns\detpack.md3 +file2=<>\STVEFPack\models\boltOns\emitter.md3 +file3=<>\STVEFPack\models\boltOns\glass.md3 +file4=<>\STVEFPack\models\boltOns\helmet.md3 +file5=<>\STVEFPack\models\boltOns\jetpack.md3 +file6=<>\STVEFPack\models\boltOns\pat_enh.md3 +file7=<>\STVEFPack\models\boltOns\phaser.md3 + +[models\chunks] +fulldirectory= +SubDir0=models\chunks\borg +SubDir1=models\chunks\generic +SubDir2=models\chunks\glass +SubDir3=models\chunks\warriorbot +SubDir4=models\chunks\stasis + +[models\chunks\borg] +fulldirectory= +file0=<>\STVEFPack\models\chunks\borg\borg_3.md3 +file1=<>\STVEFPack\models\chunks\borg\borg_1.md3 +file2=<>\STVEFPack\models\chunks\borg\borg_2.md3 + +[models\chunks\generic] +fulldirectory= +file0=<>\STVEFPack\models\chunks\generic\chunks_6.md3 +file1=<>\STVEFPack\models\chunks\generic\chunks_1.md3 +file2=<>\STVEFPack\models\chunks\generic\chunks_2.md3 +file3=<>\STVEFPack\models\chunks\generic\chunks_3.md3 +file4=<>\STVEFPack\models\chunks\generic\chunks_4.md3 +file5=<>\STVEFPack\models\chunks\generic\chunks_5.md3 + +[models\chunks\glass] +fulldirectory= +file0=<>\STVEFPack\models\chunks\glass\glchunks_6.md3 +file1=<>\STVEFPack\models\chunks\glass\glchunks_1.md3 +file2=<>\STVEFPack\models\chunks\glass\glchunks_2.md3 +file3=<>\STVEFPack\models\chunks\glass\glchunks_3.md3 +file4=<>\STVEFPack\models\chunks\glass\glchunks_4.md3 +file5=<>\STVEFPack\models\chunks\glass\glchunks_5.md3 + +[models\chunks\warriorbot] +fulldirectory= +file0=<>\STVEFPack\models\chunks\warriorbot\turret.md3 +file1=<>\STVEFPack\models\chunks\warriorbot\arm.md3 +file2=<>\STVEFPack\models\chunks\warriorbot\foot.md3 +file3=<>\STVEFPack\models\chunks\warriorbot\head.md3 +file4=<>\STVEFPack\models\chunks\warriorbot\leg.md3 + +[models\chunks\stasis] +fulldirectory= +file0=<>\STVEFPack\models\chunks\stasis\stasis_4.md3 +file1=<>\STVEFPack\models\chunks\stasis\stasis_1.md3 +file2=<>\STVEFPack\models\chunks\stasis\stasis_2.md3 +file3=<>\STVEFPack\models\chunks\stasis\stasis_3.md3 + +[models\flags] +fulldirectory= +file0=<>\STVEFPack\models\flags\flag_red.md3 +file1=<>\STVEFPack\models\flags\flag_blue.md3 + +[models\misc] +fulldirectory= +file0=<>\STVEFPack\models\misc\telep.md3 + +[models\weaphits] +fulldirectory= +file0=<>\STVEFPack\models\weaphits\ring02.md3 +file1=<>\STVEFPack\models\weaphits\explosion.md3 +file2=<>\STVEFPack\models\weaphits\nuke.md3 + +[models\powerups] +fulldirectory= +SubDir0=models\powerups\trek + +[models\powerups\trek] +fulldirectory= +file0=<>\STVEFPack\models\powerups\trek\transporter_1.md3 +file1=<>\STVEFPack\models\powerups\trek\arc_ammo.md3 +file2=<>\STVEFPack\models\powerups\trek\arc_ammo_1.md3 +file3=<>\STVEFPack\models\powerups\trek\armor2.md3 +file4=<>\STVEFPack\models\powerups\trek\armor2_1.md3 +file5=<>\STVEFPack\models\powerups\trek\armor3.md3 +file6=<>\STVEFPack\models\powerups\trek\armor3_1.md3 +file7=<>\STVEFPack\models\powerups\trek\armor.md3 +file8=<>\STVEFPack\models\powerups\trek\armor_1.md3 +file9=<>\STVEFPack\models\powerups\trek\armor_shard.md3 +file10=<>\STVEFPack\models\powerups\trek\decoy.md3 +file11=<>\STVEFPack\models\powerups\trek\detpak.md3 +file12=<>\STVEFPack\models\powerups\trek\flag_base.md3 +file13=<>\STVEFPack\models\powerups\trek\flight.md3 +file14=<>\STVEFPack\models\powerups\trek\flight_1.md3 +file15=<>\STVEFPack\models\powerups\trek\flight_2.md3 +file16=<>\STVEFPack\models\powerups\trek\flyer.md3 +file17=<>\STVEFPack\models\powerups\trek\glauncher_ammo.md3 +file18=<>\STVEFPack\models\powerups\trek\haste.md3 +file19=<>\STVEFPack\models\powerups\trek\haste_1.md3 +file20=<>\STVEFPack\models\powerups\trek\hypo_double.md3 +file21=<>\STVEFPack\models\powerups\trek\hypo_single.md3 +file22=<>\STVEFPack\models\powerups\trek\imod_ammo.md3 +file23=<>\STVEFPack\models\powerups\trek\invisible.md3 +file24=<>\STVEFPack\models\powerups\trek\invisible_1.md3 +file25=<>\STVEFPack\models\powerups\trek\med_kit.md3 +file26=<>\STVEFPack\models\powerups\trek\prifle_ammo.md3 +file27=<>\STVEFPack\models\powerups\trek\quad_damage.md3 +file28=<>\STVEFPack\models\powerups\trek\quad_damage_1.md3 +file29=<>\STVEFPack\models\powerups\trek\regen.md3 +file30=<>\STVEFPack\models\powerups\trek\scavenger_ammo.md3 +file31=<>\STVEFPack\models\powerups\trek\shield_gen.md3 +file32=<>\STVEFPack\models\powerups\trek\stasis_ammo.md3 +file33=<>\STVEFPack\models\powerups\trek\tetrion_ammo.md3 +file34=<>\STVEFPack\models\powerups\trek\torpedo.md3 +file35=<>\STVEFPack\models\powerups\trek\torpedo_1.md3 +file36=<>\STVEFPack\models\powerups\trek\transporter.md3 + +[models\mapobjects] +fulldirectory= +SubDir0=models\mapobjects\borg +SubDir1=models\mapobjects\bridge +SubDir2=models\mapobjects\cargo +SubDir3=models\mapobjects\confroom +SubDir4=models\mapobjects\dn +SubDir5=models\mapobjects\forge +SubDir6=models\mapobjects\stasis +SubDir7=models\mapobjects\scavenger +SubDir8=models\mapobjects\podium + +[models\mapobjects\borg] +fulldirectory= +file0=<>\STVEFPack\models\mapobjects\borg\vynclumn_d1.md3 +file1=<>\STVEFPack\models\mapobjects\borg\alien_ammo2.md3 +file2=<>\STVEFPack\models\mapobjects\borg\alien_ammo2_b.md3 +file3=<>\STVEFPack\models\mapobjects\borg\alien_ammo.md3 +file4=<>\STVEFPack\models\mapobjects\borg\blite.md3 +file5=<>\STVEFPack\models\mapobjects\borg\blite_d1.md3 +file6=<>\STVEFPack\models\mapobjects\borg\borg_munro.md3 +file7=<>\STVEFPack\models\mapobjects\borg\borg_munrof.md3 +file8=<>\STVEFPack\models\mapobjects\borg\BorgCrew1.md3 +file9=<>\STVEFPack\models\mapobjects\borg\borgmachine1_d1.md3 +file10=<>\STVEFPack\models\mapobjects\borg\circuit_1.md3 +file11=<>\STVEFPack\models\mapobjects\borg\circuit_1_d1.md3 +file12=<>\STVEFPack\models\mapobjects\borg\circuit_2.md3 +file13=<>\STVEFPack\models\mapobjects\borg\circuit_2_d1.md3 +file14=<>\STVEFPack\models\mapobjects\borg\circuit_3.md3 +file15=<>\STVEFPack\models\mapobjects\borg\circuit_3_d1.md3 +file16=<>\STVEFPack\models\mapobjects\borg\dead_borg.md3 +file17=<>\STVEFPack\models\mapobjects\borg\dead_borg_arm.md3 +file18=<>\STVEFPack\models\mapobjects\borg\dead_borg_bashed.md3 +file19=<>\STVEFPack\models\mapobjects\borg\dead_borg_legs.md3 +file20=<>\STVEFPack\models\mapobjects\borg\dead_leg.md3 +file21=<>\STVEFPack\models\mapobjects\borg\disnode.md3 +file22=<>\STVEFPack\models\mapobjects\borg\disnode_d1.md3 +file23=<>\STVEFPack\models\mapobjects\borg\foster_borg.md3 +file24=<>\STVEFPack\models\mapobjects\borg\plugin2.md3 +file25=<>\STVEFPack\models\mapobjects\borg\plugin2_d1.md3 +file26=<>\STVEFPack\models\mapobjects\borg\plugin2_in.md3 +file27=<>\STVEFPack\models\mapobjects\borg\plugin.md3 +file28=<>\STVEFPack\models\mapobjects\borg\plugin_in.md3 +file29=<>\STVEFPack\models\mapobjects\borg\tank.md3 +file30=<>\STVEFPack\models\mapobjects\borg\vynclumn.md3 + +[models\mapobjects\bridge] +fulldirectory= +file0=<>\STVEFPack\models\mapobjects\bridge\stationsleft.md3 +file1=<>\STVEFPack\models\mapobjects\bridge\cchair.md3 +file2=<>\STVEFPack\models\mapobjects\bridge\ceiling.md3 +file3=<>\STVEFPack\models\mapobjects\bridge\chair.md3 +file4=<>\STVEFPack\models\mapobjects\bridge\confchair.md3 +file5=<>\STVEFPack\models\mapobjects\bridge\confLight.md3 +file6=<>\STVEFPack\models\mapobjects\bridge\hazardchair.md3 +file7=<>\STVEFPack\models\mapobjects\bridge\helm.md3 +file8=<>\STVEFPack\models\mapobjects\bridge\paris_chair.md3 +file9=<>\STVEFPack\models\mapobjects\bridge\railing.md3 +file10=<>\STVEFPack\models\mapobjects\bridge\station.md3 + +[models\mapobjects\cargo] +fulldirectory= +file0=<>\STVEFPack\models\mapobjects\cargo\turbo_lite.md3 +file1=<>\STVEFPack\models\mapobjects\cargo\barrel0.md3 +file2=<>\STVEFPack\models\mapobjects\cargo\barrel1.md3 +file3=<>\STVEFPack\models\mapobjects\cargo\barrel2.md3 +file4=<>\STVEFPack\models\mapobjects\cargo\barrel_wood2.md3 +file5=<>\STVEFPack\models\mapobjects\cargo\barrel_wood.md3 +file6=<>\STVEFPack\models\mapobjects\cargo\bullseye.md3 +file7=<>\STVEFPack\models\mapobjects\cargo\control_stand.md3 +file8=<>\STVEFPack\models\mapobjects\cargo\crate.md3 +file9=<>\STVEFPack\models\mapobjects\cargo\cube.md3 +file10=<>\STVEFPack\models\mapobjects\cargo\delta_flyer.md3 +file11=<>\STVEFPack\models\mapobjects\cargo\exam_table2.md3 +file12=<>\STVEFPack\models\mapobjects\cargo\exam_table.md3 +file13=<>\STVEFPack\models\mapobjects\cargo\hypo.md3 +file14=<>\STVEFPack\models\mapobjects\cargo\jetpack.md3 +file15=<>\STVEFPack\models\mapobjects\cargo\laptop.md3 +file16=<>\STVEFPack\models\mapobjects\cargo\med_padd.md3 +file17=<>\STVEFPack\models\mapobjects\cargo\medcart.md3 +file18=<>\STVEFPack\models\mapobjects\cargo\padd.md3 +file19=<>\STVEFPack\models\mapobjects\cargo\rack.md3 +file20=<>\STVEFPack\models\mapobjects\cargo\rugby_football.md3 +file21=<>\STVEFPack\models\mapobjects\cargo\s_chair.md3 +file22=<>\STVEFPack\models\mapobjects\cargo\scanner.md3 +file23=<>\STVEFPack\models\mapobjects\cargo\sm_plant.md3 +file24=<>\STVEFPack\models\mapobjects\cargo\square_cup.md3 +file25=<>\STVEFPack\models\mapobjects\cargo\stool.md3 +file26=<>\STVEFPack\models\mapobjects\cargo\suit.md3 +file27=<>\STVEFPack\models\mapobjects\cargo\suitb.md3 +file28=<>\STVEFPack\models\mapobjects\cargo\suith.md3 +file29=<>\STVEFPack\models\mapobjects\cargo\suitr.md3 +file30=<>\STVEFPack\models\mapobjects\cargo\toolkit.md3 +file31=<>\STVEFPack\models\mapobjects\cargo\tray1.md3 +file32=<>\STVEFPack\models\mapobjects\cargo\tray2.md3 + +[models\mapobjects\confroom] +fulldirectory= +file0=<>\STVEFPack\models\mapobjects\confroom\sm_chair.md3 +file1=<>\STVEFPack\models\mapobjects\confroom\confplant.md3 +file2=<>\STVEFPack\models\mapobjects\confroom\conftable.md3 +file3=<>\STVEFPack\models\mapobjects\confroom\egg.md3 +file4=<>\STVEFPack\models\mapobjects\confroom\head_sculp.md3 + +[models\mapobjects\dn] +fulldirectory= +file0=<>\STVEFPack\models\mapobjects\dn\toparm_full.md3 +file1=<>\STVEFPack\models\mapobjects\dn\alien_ammo2.md3 +file2=<>\STVEFPack\models\mapobjects\dn\alien_ammo2_b.md3 +file3=<>\STVEFPack\models\mapobjects\dn\chair.md3 +file4=<>\STVEFPack\models\mapobjects\dn\chair_d1.md3 +file5=<>\STVEFPack\models\mapobjects\dn\energy_container.md3 +file6=<>\STVEFPack\models\mapobjects\dn\fighter.md3 +file7=<>\STVEFPack\models\mapobjects\dn\gunturret_arm.md3 +file8=<>\STVEFPack\models\mapobjects\dn\gunturret_base.md3 +file9=<>\STVEFPack\models\mapobjects\dn\gunturret_head.md3 +file10=<>\STVEFPack\models\mapobjects\dn\headbot_plug.md3 +file11=<>\STVEFPack\models\mapobjects\dn\laser.md3 +file12=<>\STVEFPack\models\mapobjects\dn\laser_arm.md3 +file13=<>\STVEFPack\models\mapobjects\dn\laser_base.md3 +file14=<>\STVEFPack\models\mapobjects\dn\laser_head.md3 +file15=<>\STVEFPack\models\mapobjects\dn\light.md3 +file16=<>\STVEFPack\models\mapobjects\dn\loader_arm_full.md3 +file17=<>\STVEFPack\models\mapobjects\dn\machine2.md3 +file18=<>\STVEFPack\models\mapobjects\dn\machine2_d1.md3 +file19=<>\STVEFPack\models\mapobjects\dn\machine.md3 +file20=<>\STVEFPack\models\mapobjects\dn\powercell2.md3 +file21=<>\STVEFPack\models\mapobjects\dn\powercell.md3 +file22=<>\STVEFPack\models\mapobjects\dn\sleep_chamber.md3 +file23=<>\STVEFPack\models\mapobjects\dn\sleep_chamber_c1.md3 +file24=<>\STVEFPack\models\mapobjects\dn\sleep_chamber_d1.md3 + +[models\mapobjects\forge] +fulldirectory= +file0=<>\STVEFPack\models\mapobjects\forge\turret_neck.md3 +file1=<>\STVEFPack\models\mapobjects\forge\alien_ammo.md3 +file2=<>\STVEFPack\models\mapobjects\forge\arm.md3 +file3=<>\STVEFPack\models\mapobjects\forge\boss_shell.md3 +file4=<>\STVEFPack\models\mapobjects\forge\boss_shell_1.md3 +file5=<>\STVEFPack\models\mapobjects\forge\boss_shell_2.md3 +file6=<>\STVEFPack\models\mapobjects\forge\capsule2.md3 +file7=<>\STVEFPack\models\mapobjects\forge\capsule2_d1.md3 +file8=<>\STVEFPack\models\mapobjects\forge\capsule3.md3 +file9=<>\STVEFPack\models\mapobjects\forge\capsule3_d1.md3 +file10=<>\STVEFPack\models\mapobjects\forge\d_grid2.md3 +file11=<>\STVEFPack\models\mapobjects\forge\d_grid.md3 +file12=<>\STVEFPack\models\mapobjects\forge\exam_table.md3 +file13=<>\STVEFPack\models\mapobjects\forge\exam_table_d1.md3 +file14=<>\STVEFPack\models\mapobjects\forge\flesh_vat.md3 +file15=<>\STVEFPack\models\mapobjects\forge\flesh_vat_d1.md3 +file16=<>\STVEFPack\models\mapobjects\forge\monitor.md3 +file17=<>\STVEFPack\models\mapobjects\forge\monitor_d1.md3 +file18=<>\STVEFPack\models\mapobjects\forge\panels2.md3 +file19=<>\STVEFPack\models\mapobjects\forge\panels3.md3 +file20=<>\STVEFPack\models\mapobjects\forge\panels.md3 +file21=<>\STVEFPack\models\mapobjects\forge\panels_d1.md3 +file22=<>\STVEFPack\models\mapobjects\forge\power_up2.md3 +file23=<>\STVEFPack\models\mapobjects\forge\power_up.md3 +file24=<>\STVEFPack\models\mapobjects\forge\power_up_boss.md3 +file25=<>\STVEFPack\models\mapobjects\forge\power_up_bossoff.md3 +file26=<>\STVEFPack\models\mapobjects\forge\probe.md3 +file27=<>\STVEFPack\models\mapobjects\forge\probe_d1.md3 +file28=<>\STVEFPack\models\mapobjects\forge\seed.md3 +file29=<>\STVEFPack\models\mapobjects\forge\trophy.md3 +file30=<>\STVEFPack\models\mapobjects\forge\turret.md3 +file31=<>\STVEFPack\models\mapobjects\forge\turret_d1.md3 +file32=<>\STVEFPack\models\mapobjects\forge\turret_head.md3 + +[models\mapobjects\stasis] +fulldirectory= +file0=<>\STVEFPack\models\mapobjects\stasis\thing_r7.md3 +file1=<>\STVEFPack\models\mapobjects\stasis\alien_ammo2.md3 +file2=<>\STVEFPack\models\mapobjects\stasis\alien_ammo2_b.md3 +file3=<>\STVEFPack\models\mapobjects\stasis\control1.md3 +file4=<>\STVEFPack\models\mapobjects\stasis\control1_d1.md3 +file5=<>\STVEFPack\models\mapobjects\stasis\control1left.md3 +file6=<>\STVEFPack\models\mapobjects\stasis\control1left_d1.md3 +file7=<>\STVEFPack\models\mapobjects\stasis\control2.md3 +file8=<>\STVEFPack\models\mapobjects\stasis\control2_d1.md3 +file9=<>\STVEFPack\models\mapobjects\stasis\control_switch.md3 +file10=<>\STVEFPack\models\mapobjects\stasis\door2.md3 +file11=<>\STVEFPack\models\mapobjects\stasis\door.md3 +file12=<>\STVEFPack\models\mapobjects\stasis\generator.md3 +file13=<>\STVEFPack\models\mapobjects\stasis\generator_d1.md3 +file14=<>\STVEFPack\models\mapobjects\stasis\guntree.md3 +file15=<>\STVEFPack\models\mapobjects\stasis\holo1.md3 +file16=<>\STVEFPack\models\mapobjects\stasis\holo2.md3 +file17=<>\STVEFPack\models\mapobjects\stasis\holo3.md3 +file18=<>\STVEFPack\models\mapobjects\stasis\holo4.md3 +file19=<>\STVEFPack\models\mapobjects\stasis\jumppad.md3 +file20=<>\STVEFPack\models\mapobjects\stasis\jumppad_d1.md3 +file21=<>\STVEFPack\models\mapobjects\stasis\light2.md3 +file22=<>\STVEFPack\models\mapobjects\stasis\light2_d1.md3 +file23=<>\STVEFPack\models\mapobjects\stasis\mine.md3 +file24=<>\STVEFPack\models\mapobjects\stasis\monitor.md3 +file25=<>\STVEFPack\models\mapobjects\stasis\plugin2.md3 +file26=<>\STVEFPack\models\mapobjects\stasis\plugin2_floor.md3 +file27=<>\STVEFPack\models\mapobjects\stasis\plugin2_floor_in.md3 +file28=<>\STVEFPack\models\mapobjects\stasis\plugin2_in.md3 +file29=<>\STVEFPack\models\mapobjects\stasis\plugin.md3 +file30=<>\STVEFPack\models\mapobjects\stasis\plugin_floor.md3 +file31=<>\STVEFPack\models\mapobjects\stasis\plugin_floor_in.md3 +file32=<>\STVEFPack\models\mapobjects\stasis\plugin_in.md3 +file33=<>\STVEFPack\models\mapobjects\stasis\pod1.md3 +file34=<>\STVEFPack\models\mapobjects\stasis\pod2.md3 +file35=<>\STVEFPack\models\mapobjects\stasis\pod.md3 +file36=<>\STVEFPack\models\mapobjects\stasis\projector.md3 +file37=<>\STVEFPack\models\mapobjects\stasis\teleporter_base.md3 +file38=<>\STVEFPack\models\mapobjects\stasis\teleporter_base_d1.md3 +file39=<>\STVEFPack\models\mapobjects\stasis\teleporter_top.md3 +file40=<>\STVEFPack\models\mapobjects\stasis\teleporter_top_d1.md3 +file41=<>\STVEFPack\models\mapobjects\stasis\thing.md3 +file42=<>\STVEFPack\models\mapobjects\stasis\thing_l1.md3 +file43=<>\STVEFPack\models\mapobjects\stasis\thing_l2.md3 +file44=<>\STVEFPack\models\mapobjects\stasis\thing_l3.md3 +file45=<>\STVEFPack\models\mapobjects\stasis\thing_l4.md3 +file46=<>\STVEFPack\models\mapobjects\stasis\thing_l5.md3 +file47=<>\STVEFPack\models\mapobjects\stasis\thing_l6.md3 +file48=<>\STVEFPack\models\mapobjects\stasis\thing_l7.md3 +file49=<>\STVEFPack\models\mapobjects\stasis\thing_r1.md3 +file50=<>\STVEFPack\models\mapobjects\stasis\thing_r2.md3 +file51=<>\STVEFPack\models\mapobjects\stasis\thing_r3.md3 +file52=<>\STVEFPack\models\mapobjects\stasis\thing_r4.md3 +file53=<>\STVEFPack\models\mapobjects\stasis\thing_r5.md3 +file54=<>\STVEFPack\models\mapobjects\stasis\thing_r6.md3 + +[models\mapobjects\scavenger] +fulldirectory= +file0=<>\STVEFPack\models\mapobjects\scavenger\weapons.md3 +file1=<>\STVEFPack\models\mapobjects\scavenger\alien_ammo2.md3 +file2=<>\STVEFPack\models\mapobjects\scavenger\alien_ammo2_b.md3 +file3=<>\STVEFPack\models\mapobjects\scavenger\alienlight.md3 +file4=<>\STVEFPack\models\mapobjects\scavenger\alienlight_d1.md3 +file5=<>\STVEFPack\models\mapobjects\scavenger\camera.md3 +file6=<>\STVEFPack\models\mapobjects\scavenger\card2.md3 +file7=<>\STVEFPack\models\mapobjects\scavenger\card3.md3 +file8=<>\STVEFPack\models\mapobjects\scavenger\card4.md3 +file9=<>\STVEFPack\models\mapobjects\scavenger\card.md3 +file10=<>\STVEFPack\models\mapobjects\scavenger\cards.md3 +file11=<>\STVEFPack\models\mapobjects\scavenger\cchair.md3 +file12=<>\STVEFPack\models\mapobjects\scavenger\chessboard.md3 +file13=<>\STVEFPack\models\mapobjects\scavenger\chessboard_d1.md3 +file14=<>\STVEFPack\models\mapobjects\scavenger\dcrystal.md3 +file15=<>\STVEFPack\models\mapobjects\scavenger\deadplant2.md3 +file16=<>\STVEFPack\models\mapobjects\scavenger\deadplant2_d1.md3 +file17=<>\STVEFPack\models\mapobjects\scavenger\deadplant.md3 +file18=<>\STVEFPack\models\mapobjects\scavenger\deadplant_d1.md3 +file19=<>\STVEFPack\models\mapobjects\scavenger\galileo.md3 +file20=<>\STVEFPack\models\mapobjects\scavenger\h_control.md3 +file21=<>\STVEFPack\models\mapobjects\scavenger\hypospray.md3 +file22=<>\STVEFPack\models\mapobjects\scavenger\hypospray_r.md3 +file23=<>\STVEFPack\models\mapobjects\scavenger\hypostack_b.md3 +file24=<>\STVEFPack\models\mapobjects\scavenger\hypostack_r.md3 +file25=<>\STVEFPack\models\mapobjects\scavenger\iso.md3 +file26=<>\STVEFPack\models\mapobjects\scavenger\k_barrel.md3 +file27=<>\STVEFPack\models\mapobjects\scavenger\k_chair1.md3 +file28=<>\STVEFPack\models\mapobjects\scavenger\k_chair2.md3 +file29=<>\STVEFPack\models\mapobjects\scavenger\k_crate.md3 +file30=<>\STVEFPack\models\mapobjects\scavenger\k_cup.md3 +file31=<>\STVEFPack\models\mapobjects\scavenger\k_plate.md3 +file32=<>\STVEFPack\models\mapobjects\scavenger\laundry.md3 +file33=<>\STVEFPack\models\mapobjects\scavenger\laundry_boots.md3 +file34=<>\STVEFPack\models\mapobjects\scavenger\laundry_f.md3 +file35=<>\STVEFPack\models\mapobjects\scavenger\m_tank.md3 +file36=<>\STVEFPack\models\mapobjects\scavenger\m_tank_d1.md3 +file37=<>\STVEFPack\models\mapobjects\scavenger\malon_hanging.md3 +file38=<>\STVEFPack\models\mapobjects\scavenger\matt.md3 +file39=<>\STVEFPack\models\mapobjects\scavenger\power_up2.md3 +file40=<>\STVEFPack\models\mapobjects\scavenger\power_up.md3 +file41=<>\STVEFPack\models\mapobjects\scavenger\sc_chair1.md3 +file42=<>\STVEFPack\models\mapobjects\scavenger\sc_chair1_d1.md3 +file43=<>\STVEFPack\models\mapobjects\scavenger\sc_handle.md3 +file44=<>\STVEFPack\models\mapobjects\scavenger\sc_monitor.md3 +file45=<>\STVEFPack\models\mapobjects\scavenger\sc_monitor_d1.md3 +file46=<>\STVEFPack\models\mapobjects\scavenger\sc_scanner.md3 +file47=<>\STVEFPack\models\mapobjects\scavenger\sc_wallthing1.md3 +file48=<>\STVEFPack\models\mapobjects\scavenger\sc_wallthing1_d1.md3 +file49=<>\STVEFPack\models\mapobjects\scavenger\sc_wallthing2.md3 +file50=<>\STVEFPack\models\mapobjects\scavenger\sc_wallthing2_d1.md3 +file51=<>\STVEFPack\models\mapobjects\scavenger\sc_wallthing3.md3 +file52=<>\STVEFPack\models\mapobjects\scavenger\sc_wallthing3_d1.md3 +file53=<>\STVEFPack\models\mapobjects\scavenger\tank.md3 +file54=<>\STVEFPack\models\mapobjects\scavenger\testtube.md3 +file55=<>\STVEFPack\models\mapobjects\scavenger\toolbox.md3 +file56=<>\STVEFPack\models\mapobjects\scavenger\toolbox_d1.md3 +file57=<>\STVEFPack\models\mapobjects\scavenger\vermin.md3 +file58=<>\STVEFPack\models\mapobjects\scavenger\wallpipe.md3 +file59=<>\STVEFPack\models\mapobjects\scavenger\wallpipe_d1.md3 + +[models\mapobjects\podium] +fulldirectory= +file0=<>\STVEFPack\models\mapobjects\podium\podium_single.md3 +file1=<>\STVEFPack\models\mapobjects\podium\hm_room.md3 +file2=<>\STVEFPack\models\mapobjects\podium\podium4.md3 + +[scripts] +fulldirectory= +file0=<>\STVEFPack\Scripts\weapons.shader +file1=<>\STVEFPack\Scripts\borg.shader +file2=<>\STVEFPack\Scripts\common.shader +file3=<>\STVEFPack\Scripts\ctf2.shader +file4=<>\STVEFPack\Scripts\ctf.shader +file5=<>\STVEFPack\Scripts\damagedvoyager.shader +file6=<>\STVEFPack\Scripts\decals.shader +file7=<>\STVEFPack\Scripts\dreadnought3.shader +file8=<>\STVEFPack\Scripts\effects.shader +file9=<>\STVEFPack\Scripts\engineering.shader +file10=<>\STVEFPack\Scripts\explosions.shader +file11=<>\STVEFPack\Scripts\forge.shader +file12=<>\STVEFPack\Scripts\gfx.shader +file13=<>\STVEFPack\Scripts\holodeck_temple.shader +file14=<>\STVEFPack\Scripts\holodeck_training.shader +file15=<>\STVEFPack\Scripts\menu.shader +file16=<>\STVEFPack\Scripts\mg.shader +file17=<>\STVEFPack\Scripts\scavenger.shader +file18=<>\STVEFPack\Scripts\shaderlist.txt +file19=<>\STVEFPack\Scripts\Stasis.shader +file20=<>\STVEFPack\Scripts\STModels.shader +file21=<>\STVEFPack\Scripts\transporter.shader +file22=<>\STVEFPack\Scripts\voyager.shader +file23=<>\STVEFPack\scripts\sp_entities.def +file24=<>\STVEFPack\scripts\hm_entities.def +file25=<>\STVEFPack\scripts\default_project.proj + diff --git a/setup/win32/template/File Groups/Shader Manual Files.fgl b/setup/win32/template/File Groups/Shader Manual Files.fgl new file mode 100644 index 00000000..54298602 --- /dev/null +++ b/setup/win32/template/File Groups/Shader Manual Files.fgl @@ -0,0 +1,56 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=Q3AShader_Manual + +[Q3AShader_Manual] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual +file0=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\index.htm +SubDir0=Q3AShader_Manual\ch01 +SubDir1=Q3AShader_Manual\ch02 +SubDir2=Q3AShader_Manual\ch03 +SubDir3=Q3AShader_Manual\ch04 +SubDir4=Q3AShader_Manual\ch05 +SubDir5=Q3AShader_Manual\ch06 +SubDir6=Q3AShader_Manual\q3ashader_manual_files +SubDir7=Q3AShader_Manual\styles +SubDir8=Q3AShader_Manual\appendix + +[Q3AShader_Manual\ch01] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\ch01 +file0=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\ch01\pg1_1.htm + +[Q3AShader_Manual\ch02] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\ch02 +file0=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\ch02\pg2_1.htm + +[Q3AShader_Manual\ch03] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\ch03 +file0=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\ch03\pg3_1.htm + +[Q3AShader_Manual\ch04] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\ch04 +file0=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\ch04\pg4_1.htm + +[Q3AShader_Manual\ch05] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\ch05 +file0=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\ch05\pg5_1.htm + +[Q3AShader_Manual\ch06] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\ch06 +file0=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\ch06\pg6_1.htm + +[Q3AShader_Manual\q3ashader_manual_files] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\q3ashader_manual_files +file0=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\q3ashader_manual_files\image002.jpg + +[Q3AShader_Manual\styles] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\styles +file0=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\styles\q3rad.css + +[Q3AShader_Manual\appendix] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\appendix +file0=<>\GtkRadiant\docs\manual\quake3\Q3AShader_Manual\appendix\appA.html + diff --git a/setup/win32/template/File Groups/TA Manual Files.fgl b/setup/win32/template/File Groups/TA Manual Files.fgl new file mode 100644 index 00000000..07c17051 --- /dev/null +++ b/setup/win32/template/File Groups/TA Manual Files.fgl @@ -0,0 +1,37 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=Team_Arena_Mapping_Help + +[Team_Arena_Mapping_Help] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help +file0=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\start.html +SubDir0=Team_Arena_Mapping_Help\pages +SubDir1=Team_Arena_Mapping_Help\pics + +[Team_Arena_Mapping_Help\pages] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pages +file0=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pages\design_tips.html +file1=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pages\map_converters_checklist.html +file2=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pages\preface.html +file3=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pages\related_links.html +file4=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pages\table_of_contents.htm +file5=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pages\ta_game_types.html +file6=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pages\team_arena_entity_definitions.html +file7=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pages\team_arena_prefabs.html +file8=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pages\team_powerup_bases.html +file9=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pages\using_new_game_entities.html + +[Team_Arena_Mapping_Help\pics] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pics +file0=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pics\CRUSADER.gif +file1=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pics\INTRUDER.gif +file2=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pics\logo.gif +file3=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pics\MAINPOP.gif +file4=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pics\MENUBACKgif.gif +file5=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pics\PAGANs.gif +file6=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pics\STROGGS.gif +file7=<>\GtkRadiant\docs\manual\quake3\Team_Arena_Mapping_Help\pics\THEFALLEN.gif + diff --git a/setup/win32/template/File Groups/TA Sample Files.fgl b/setup/win32/template/File Groups/TA Sample Files.fgl new file mode 100644 index 00000000..6dfa9470 --- /dev/null +++ b/setup/win32/template/File Groups/TA Sample Files.fgl @@ -0,0 +1,87 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\GtkRadiant\setup\data\missionpack\mapmedia-TA.pk3 +file1=<>\GtkRadiant\setup\data\missionpack\terrademo-README.txt +SubDir0=models +SubDir1=scripts +SubDir2=maps +SubDir3=textures + +[models] +fulldirectory=<>\GtkRadiant\setup\data\missionpack\models +SubDir0=models\mapobjects + +[models\mapobjects] +fulldirectory=<>\GtkRadiant\setup\data\missionpack\models\mapobjects +SubDir0=models\mapobjects\spawn + +[models\mapobjects\spawn] +fulldirectory=<>\GtkRadiant\setup\data\missionpack\models\mapobjects\spawn +file0=<>\GtkRadiant\setup\data\missionpack\models\mapobjects\spawn\spawn.md3 +file1=<>\GtkRadiant\setup\data\missionpack\models\mapobjects\spawn\spawn_r.md3 + +[scripts] +fulldirectory=<>\GtkRadiant\setup\data\missionpack\scripts +file0=<>\GtkRadiant\setup\data\missionpack\scripts\base_floor.shader +file1=<>\GtkRadiant\setup\data\missionpack\scripts\base_floor2.shader +file2=<>\GtkRadiant\setup\data\missionpack\scripts\base_light.shader +file3=<>\GtkRadiant\setup\data\missionpack\scripts\base_wall.shader +file4=<>\GtkRadiant\setup\data\missionpack\scripts\base_wall2.shader +file5=<>\GtkRadiant\setup\data\missionpack\scripts\common.shader +file6=<>\GtkRadiant\setup\data\missionpack\scripts\ctf.shader +file7=<>\GtkRadiant\setup\data\missionpack\scripts\ctf2.shader +file8=<>\GtkRadiant\setup\data\missionpack\scripts\gothic_trim.shader +file9=<>\GtkRadiant\setup\data\missionpack\scripts\gothic_wall.shader +file10=<>\GtkRadiant\setup\data\missionpack\scripts\liquids.shader +file11=<>\GtkRadiant\setup\data\missionpack\scripts\liquids2.shader +file12=<>\GtkRadiant\setup\data\missionpack\scripts\museum.shader +file13=<>\GtkRadiant\setup\data\missionpack\scripts\proto2.shader +file14=<>\GtkRadiant\setup\data\missionpack\scripts\sfx.shader +file15=<>\GtkRadiant\setup\data\missionpack\scripts\sfx2.shader +file16=<>\GtkRadiant\setup\data\missionpack\scripts\skies.shader +file17=<>\GtkRadiant\setup\data\missionpack\scripts\skies2.shader +file18=<>\GtkRadiant\setup\data\missionpack\scripts\stone2.shader +file19=<>\GtkRadiant\setup\data\missionpack\scripts\team.shader +file20=<>\GtkRadiant\setup\data\missionpack\scripts\terrain.shader +file21=<>\GtkRadiant\setup\data\missionpack\scripts\tim.shader +file22=<>\GtkRadiant\setup\data\missionpack\scripts\entities-ta.def + +[maps] +fulldirectory=<>\GtkRadiant\setup\data\missionpack\maps +file0=<>\GtkRadiant\setup\data\missionpack\maps\terrademo.map +SubDir0=maps\Team Arena Prefabs + +[maps\Team Arena Prefabs] +fulldirectory=<>\GtkRadiant\setup\data\missionpack\maps\Team Arena Prefabs +file0=<>\GtkRadiant\setup\data\missionpack\maps\Team Arena Prefabs\armorpad_blue.pfb +file1=<>\GtkRadiant\setup\data\missionpack\maps\Team Arena Prefabs\armorpad_neutral.pfb +file2=<>\GtkRadiant\setup\data\missionpack\maps\Team Arena Prefabs\armorpad_red.pfb +file3=<>\GtkRadiant\setup\data\missionpack\maps\Team Arena Prefabs\TA_banner_blue.pfb +file4=<>\GtkRadiant\setup\data\missionpack\maps\Team Arena Prefabs\TA_banner_red.pfb +file5=<>\GtkRadiant\setup\data\missionpack\maps\Team Arena Prefabs\weaponpad_blue.pfb +file6=<>\GtkRadiant\setup\data\missionpack\maps\Team Arena Prefabs\weaponpad_neutral.pfb +file7=<>\GtkRadiant\setup\data\missionpack\maps\Team Arena Prefabs\weaponpad_red.pfb + +[textures] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\textures +SubDir0=textures\proto2 +SubDir1=textures\skies2 +SubDir2=textures\stone + +[textures\proto2] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\textures\proto2 +file0=<>\GtkRadiant\setup\data\baseq3\textures\proto2\pj_terralpha01.pcx + +[textures\skies2] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\textures\skies2 +file0=<>\GtkRadiant\setup\data\baseq3\textures\skies2\clouds.tga + +[textures\stone] +fulldirectory=<>\GtkRadiant\setup\data\baseq3\textures\stone +file0=<>\GtkRadiant\setup\data\baseq3\textures\stone\pjrock10b_2.tga +file1=<>\GtkRadiant\setup\data\baseq3\textures\stone\pjrock12b_2.tga +file2=<>\GtkRadiant\setup\data\baseq3\textures\stone\pjrock9b_2.tga + diff --git a/setup/win32/template/File Groups/TA Teams Manual.fgl b/setup/win32/template/File Groups/TA Teams Manual.fgl new file mode 100644 index 00000000..f095fd45 --- /dev/null +++ b/setup/win32/template/File Groups/TA Teams Manual.fgl @@ -0,0 +1,11 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=New_Teams_For_Q3TA + +[New_Teams_For_Q3TA] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\New_Teams_For_Q3TA +file0=<>\GtkRadiant\docs\manual\quake3\New_Teams_For_Q3TA\index.html + diff --git a/setup/win32/template/File Groups/Terrain Manual Files.fgl b/setup/win32/template/File Groups/Terrain Manual Files.fgl new file mode 100644 index 00000000..fe97ef3c --- /dev/null +++ b/setup/win32/template/File Groups/Terrain Manual Files.fgl @@ -0,0 +1,53 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=Terrain_Manual + +[Terrain_Manual] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual +file0=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\start.html +SubDir0=Terrain_Manual\pages +SubDir1=Terrain_Manual\pics + +[Terrain_Manual\pages] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages +file0=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\adding_bots.html +file1=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\adding_buildings_to_terrain.html +file2=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\art_tools.html +file3=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\blocking_vis.html +file4=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\boxing_in_the_world.html +file5=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\clipping_the_terrain.html +file6=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\creating_the_alphamap.html +file7=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\creating_the_terrain.html +file8=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\entity_keys_and_values.html +file9=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\glossary.html +file10=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\height_maps.html +file11=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\height_map_into_terrain_mesh.html +file12=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\Image3.gif +file13=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\Image4.gif +file14=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\Image5.gif +file15=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\Image6.gif +file16=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\introduction.html +file17=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\key_changes.html +file18=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\lighting_the_terrain.html +file19=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\manipulating_the_terrain_mesh.html +file20=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\mapping_the_textures.html +file21=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\new_or_revised_q3map_shader_comm.html +file22=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\other_possible_height_map_tools.html +file23=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\related_links.html +file24=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\suggested_gensurf_settings.html +file25=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\table_of_contents.html +file26=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\terrain_entity.html +file27=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\terrain_mesh_into_terrain_entity.html +file28=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\terrain_related_worldspawn_features.html +file29=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\terrain_texture.html +file30=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pages\the_meta_shader.html + +[Terrain_Manual\pics] +fulldirectory=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pics +file0=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pics\background.jpg +file1=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pics\start.gif +file2=<>\GtkRadiant\docs\manual\quake3\Terrain_Manual\pics\terrain.jpg + diff --git a/setup/win32/template/File Groups/TexTool Help.fgl b/setup/win32/template/File Groups/TexTool Help.fgl new file mode 100644 index 00000000..2672d46c --- /dev/null +++ b/setup/win32/template/File Groups/TexTool Help.fgl @@ -0,0 +1,12 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=TexToolHelp + +[TexToolHelp] +fulldirectory= +file0=<>\GtkRadiant\plugins\textool\Doc\Image2.jpg +file1=<>\GtkRadiant\plugins\textool\Doc\TexTool.html + diff --git a/setup/win32/template/File Groups/Wolf Exectuable Files.fgl b/setup/win32/template/File Groups/Wolf Exectuable Files.fgl new file mode 100644 index 00000000..265cafe3 --- /dev/null +++ b/setup/win32/template/File Groups/Wolf Exectuable Files.fgl @@ -0,0 +1,31 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\WolfPack\bin\aascfg_lg.c +file1=<>\WolfPack\bin\aascfg_sm.c +file2=<>\WolfPack\bin\bspc.ai +file3=<>\WolfPack\bin\bspc.exe +file4=<>\WolfPack\bin\roq.exe +file5=<>\WolfPack\game.xlink +file6=<>\WolfPack\synapse.config +SubDir0=modules +SubDir1=docs + +[modules] +fulldirectory= + +[docs] +fulldirectory= +file0=<>\WolfPack\docs\scripting_definitions.htm +SubDir0=docs\GtkRadiant_WOLF_HOWTO + +[docs\GtkRadiant_WOLF_HOWTO] +fulldirectory= +file0=<>\WolfPack\docs\GtkRadiant_WOLF_HOWTO\game2.png +file1=<>\WolfPack\docs\GtkRadiant_WOLF_HOWTO\game1.png +file2=<>\WolfPack\docs\GtkRadiant_WOLF_HOWTO\index.html +file3=<>\WolfPack\docs\GtkRadiant_WOLF_HOWTO\monitor.png +file4=<>\WolfPack\docs\GtkRadiant_WOLF_HOWTO\project.png + diff --git a/setup/win32/template/File Groups/Wolf Media Files.fgl b/setup/win32/template/File Groups/Wolf Media Files.fgl new file mode 100644 index 00000000..383de651 --- /dev/null +++ b/setup/win32/template/File Groups/Wolf Media Files.fgl @@ -0,0 +1,1046 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +file0=<>\WolfPack\common-astro-spog.pk3 +file1=<>\WolfPack\astro-skies.pk3 +file2=<>\WolfPack\lights.pk3 +SubDir0=scripts +SubDir1=maps +SubDir2=models + +[scripts] +fulldirectory= +file0=<>\WolfPack\scripts\shaderlist.txt +file1=<>\WolfPack\scripts\wolf_entities.def +file2=<>\WolfPack\scripts\alpha.shader +file3=<>\WolfPack\scripts\assault.shader +file4=<>\WolfPack\scripts\awf_props.shader +file5=<>\WolfPack\scripts\b-25.shader +file6=<>\WolfPack\scripts\blimp.shader +file7=<>\WolfPack\scripts\castle_door.shader +file8=<>\WolfPack\scripts\castle_floor.shader +file9=<>\WolfPack\scripts\castle_props.shader +file10=<>\WolfPack\scripts\castle_window.shader +file11=<>\WolfPack\scripts\castle_wood.shader +file12=<>\WolfPack\scripts\cathedrale_wall.shader +file13=<>\WolfPack\scripts\chat.shader +file14=<>\WolfPack\scripts\chateau.shader +file15=<>\WolfPack\scripts\common.shader +file16=<>\WolfPack\scripts\decals.shader +file17=<>\WolfPack\scripts\doors.shader +file18=<>\WolfPack\scripts\graveyard.shader +file19=<>\WolfPack\scripts\lights.shader +file20=<>\WolfPack\scripts\liquids.shader +file21=<>\WolfPack\scripts\metal_misc.shader +file22=<>\WolfPack\scripts\miltary_door.shader +file23=<>\WolfPack\scripts\miltary_floor.shader +file24=<>\WolfPack\scripts\miltary_wall.shader +file25=<>\WolfPack\scripts\models.shader +file26=<>\WolfPack\scripts\props.shader +file27=<>\WolfPack\scripts\rock.shader +file28=<>\WolfPack\scripts\rubble.shader +file29=<>\WolfPack\scripts\sfx.shader +file30=<>\WolfPack\scripts\skies.shader +file31=<>\WolfPack\scripts\snow.shader +file32=<>\WolfPack\scripts\stone.shader +file33=<>\WolfPack\scripts\swf.shader +file34=<>\WolfPack\scripts\terrain.shader +file35=<>\WolfPack\scripts\training.shader +file36=<>\WolfPack\scripts\tree.shader +file37=<>\WolfPack\scripts\village.shader +file38=<>\WolfPack\scripts\windows.shader +file39=<>\WolfPack\scripts\wood.shader +file40=<>\WolfPack\scripts\xlab_door.shader +file41=<>\WolfPack\scripts\xlab_props.shader +file42=<>\WolfPack\scripts\xlab_wall.shader +file43=<>\WolfPack\scripts\quakev3.qe4 +file44=<>\WolfPack\scripts\walls.shader +file45=<>\WolfPack\scripts\town_wood.shader +file46=<>\WolfPack\scripts\town_props.shader +file47=<>\WolfPack\scripts\town_roof.shader +file48=<>\WolfPack\scripts\town_wall.shader +file49=<>\WolfPack\scripts\town_window.shader +file50=<>\WolfPack\scripts\default_project.proj + +[maps] +fulldirectory= +file0=<>\WolfPack\maps\mp_beach_alpha.tga +file1=<>\WolfPack\maps\escape1.map +file2=<>\WolfPack\maps\escape1.script +file3=<>\WolfPack\maps\escape1_alpha.tga +file4=<>\WolfPack\maps\escape2.ai +file5=<>\WolfPack\maps\escape2.map +file6=<>\WolfPack\maps\escape2.script +file7=<>\WolfPack\maps\mp_beach.map +file8=<>\WolfPack\maps\mp_beach.script +file9=<>\WolfPack\maps\escape1.ai + +[models] +fulldirectory= +SubDir0=models\mapobjects + +[models\mapobjects] +fulldirectory= +SubDir0=models\mapobjects\apoth +SubDir1=models\mapobjects\archeology +SubDir2=models\mapobjects\b-25 +SubDir3=models\mapobjects\barbwire +SubDir4=models\mapobjects\bat +SubDir5=models\mapobjects\bjui +SubDir6=models\mapobjects\bodyparts +SubDir7=models\mapobjects\book +SubDir8=models\mapobjects\boxes +SubDir9=models\mapobjects\bush +SubDir10=models\mapobjects\chandelier +SubDir11=models\mapobjects\clipboard +SubDir12=models\mapobjects\clonetube +SubDir13=models\mapobjects\coat +SubDir14=models\mapobjects\coffin +SubDir15=models\mapobjects\corpse +SubDir16=models\mapobjects\curtain +SubDir17=models\mapobjects\deadosa +SubDir18=models\mapobjects\debris +SubDir19=models\mapobjects\door +SubDir20=models\mapobjects\electronics +SubDir21=models\mapobjects\end +SubDir22=models\mapobjects\eye +SubDir23=models\mapobjects\firehead +SubDir24=models\mapobjects\flag +SubDir25=models\mapobjects\furniture +SubDir26=models\mapobjects\gargoyle +SubDir27=models\mapobjects\gears +SubDir28=models\mapobjects\generator +SubDir29=models\mapobjects\goblet +SubDir30=models\mapobjects\ironmaiden +SubDir31=models\mapobjects\jetengine +SubDir32=models\mapobjects\knight +SubDir33=models\mapobjects\lamps +SubDir34=models\mapobjects\leaf +SubDir35=models\mapobjects\letters +SubDir36=models\mapobjects\light +SubDir37=models\mapobjects\lion +SubDir38=models\mapobjects\lop +SubDir39=models\mapobjects\miltary_trim +SubDir40=models\mapobjects\nazi +SubDir41=models\mapobjects\parachute +SubDir42=models\mapobjects\piano +SubDir43=models\mapobjects\plants +SubDir44=models\mapobjects\punchingbag +SubDir45=models\mapobjects\sawblade +SubDir46=models\mapobjects\sfx +SubDir47=models\mapobjects\shadtest +SubDir48=models\mapobjects\shield +SubDir49=models\mapobjects\size +SubDir50=models\mapobjects\skel +SubDir51=models\mapobjects\skull +SubDir52=models\mapobjects\sky +SubDir53=models\mapobjects\space +SubDir54=models\mapobjects\toolshed +SubDir55=models\mapobjects\torture +SubDir56=models\mapobjects\tree +SubDir57=models\mapobjects\tree_snow +SubDir58=models\mapobjects\ubergun +SubDir59=models\mapobjects\ui +SubDir60=models\mapobjects\undead +SubDir61=models\mapobjects\v2 +SubDir62=models\mapobjects\vacum +SubDir63=models\mapobjects\vehicles +SubDir64=models\mapobjects\watertower +SubDir65=models\mapobjects\weapons +SubDir66=models\mapobjects\wine +SubDir67=models\mapobjects\xlab +SubDir68=models\mapobjects\xlab_props +SubDir69=models\mapobjects\zemph +SubDir70=models\mapobjects\zombie + +[models\mapobjects\apoth] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\apoth\wired1.md3 + +[models\mapobjects\archeology] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\archeology\vessel.md3 +file1=<>\WolfPack\models\mapobjects\archeology\pitcher.md3 +file2=<>\WolfPack\models\mapobjects\archeology\vase1.md3 +file3=<>\WolfPack\models\mapobjects\archeology\vase2.md3 +file4=<>\WolfPack\models\mapobjects\archeology\vase3.md3 +file5=<>\WolfPack\models\mapobjects\archeology\vase4.md3 +file6=<>\WolfPack\models\mapobjects\archeology\vase5.md3 +file7=<>\WolfPack\models\mapobjects\archeology\obelisk.md3 + +[models\mapobjects\b-25] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\b-25\wires.md3 +file1=<>\WolfPack\models\mapobjects\b-25\door.md3 +file2=<>\WolfPack\models\mapobjects\b-25\ext_25.md3 +file3=<>\WolfPack\models\mapobjects\b-25\exterior.md3 +file4=<>\WolfPack\models\mapobjects\b-25\floor_cockpit.md3 +file5=<>\WolfPack\models\mapobjects\b-25\fuse1.md3 +file6=<>\WolfPack\models\mapobjects\b-25\fuse1a.md3 +file7=<>\WolfPack\models\mapobjects\b-25\fuse2.md3 +file8=<>\WolfPack\models\mapobjects\b-25\fuse2a.md3 +file9=<>\WolfPack\models\mapobjects\b-25\fuse3.md3 +file10=<>\WolfPack\models\mapobjects\b-25\fuse4.md3 +file11=<>\WolfPack\models\mapobjects\b-25\fuse5.md3 +file12=<>\WolfPack\models\mapobjects\b-25\fuse6.md3 +file13=<>\WolfPack\models\mapobjects\b-25\fuse7.md3 +file14=<>\WolfPack\models\mapobjects\b-25\glass.md3 +file15=<>\WolfPack\models\mapobjects\b-25\hatch.md3 +file16=<>\WolfPack\models\mapobjects\b-25\interior.md3 +file17=<>\WolfPack\models\mapobjects\b-25\main_hatch.md3 +file18=<>\WolfPack\models\mapobjects\b-25\nose1.md3 +file19=<>\WolfPack\models\mapobjects\b-25\nose1A.md3 +file20=<>\WolfPack\models\mapobjects\b-25\sawingspan.md3 +file21=<>\WolfPack\models\mapobjects\b-25\stringers.md3 +file22=<>\WolfPack\models\mapobjects\b-25\stringers1.md3 +file23=<>\WolfPack\models\mapobjects\b-25\stringers2.md3 +file24=<>\WolfPack\models\mapobjects\b-25\tail.md3 +file25=<>\WolfPack\models\mapobjects\b-25\tail_ext.md3 +file26=<>\WolfPack\models\mapobjects\b-25\wing_port.md3 +file27=<>\WolfPack\models\mapobjects\b-25\wing_starboard.md3 +file28=<>\WolfPack\models\mapobjects\b-25\wingspan.md3 +file29=<>\WolfPack\models\mapobjects\b-25\divider.md3 + +[models\mapobjects\barbwire] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\barbwire\danwire.md3 +file1=<>\WolfPack\models\mapobjects\barbwire\barbwire.md3 + +[models\mapobjects\bat] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\bat\bat.md3 + +[models\mapobjects\bjui] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\bjui\bjui_c.md3 +file1=<>\WolfPack\models\mapobjects\bjui\bjui_b.md3 +file2=<>\WolfPack\models\mapobjects\bjui\bjui.md3 + +[models\mapobjects\bodyparts] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\bodyparts\zomtorso.md3 +file1=<>\WolfPack\models\mapobjects\bodyparts\doa.md3 +file2=<>\WolfPack\models\mapobjects\bodyparts\helarm.md3 +file3=<>\WolfPack\models\mapobjects\bodyparts\helbody.md3 +file4=<>\WolfPack\models\mapobjects\bodyparts\helhand.md3 +file5=<>\WolfPack\models\mapobjects\bodyparts\helhead.md3 +file6=<>\WolfPack\models\mapobjects\bodyparts\l_arm.md3 +file7=<>\WolfPack\models\mapobjects\bodyparts\l_chest.md3 +file8=<>\WolfPack\models\mapobjects\bodyparts\l_engine.md3 +file9=<>\WolfPack\models\mapobjects\bodyparts\l_head.md3 +file10=<>\WolfPack\models\mapobjects\bodyparts\leg.md3 +file11=<>\WolfPack\models\mapobjects\bodyparts\legpelvis.md3 +file12=<>\WolfPack\models\mapobjects\bodyparts\legside.md3 +file13=<>\WolfPack\models\mapobjects\bodyparts\naziarm.md3 +file14=<>\WolfPack\models\mapobjects\bodyparts\nazitorso.md3 +file15=<>\WolfPack\models\mapobjects\bodyparts\torso.md3 +file16=<>\WolfPack\models\mapobjects\bodyparts\zom_loog.md3 +file17=<>\WolfPack\models\mapobjects\bodyparts\zomarm.md3 +file18=<>\WolfPack\models\mapobjects\bodyparts\deadnazi.md3 + +[models\mapobjects\book] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\book\book.md3 + +[models\mapobjects\boxes] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\boxes\box64.md3 +file1=<>\WolfPack\models\mapobjects\boxes\box48.md3 +file2=<>\WolfPack\models\mapobjects\boxes\box32.md3 + +[models\mapobjects\bush] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\bush\hedge.md3 +file1=<>\WolfPack\models\mapobjects\bush\bu.md3 + +[models\mapobjects\chandelier] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\chandelier\chandlier_new.md3 +file1=<>\WolfPack\models\mapobjects\chandelier\chandelier_lt.md3 +file2=<>\WolfPack\models\mapobjects\chandelier\chandelier_lt_sm.md3 +file3=<>\WolfPack\models\mapobjects\chandelier\chandelier.md3 + +[models\mapobjects\clipboard] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\clipboard\clipboard_v.md3 +file1=<>\WolfPack\models\mapobjects\clipboard\clipboard_h.md3 + +[models\mapobjects\clonetube] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\clonetube\clone.md3 + +[models\mapobjects\coat] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\coat\coat.md3 + +[models\mapobjects\coffin] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\coffin\coffin.md3 + +[models\mapobjects\corpse] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\corpse\legbroke.md3 +file1=<>\WolfPack\models\mapobjects\corpse\bone2.md3 +file2=<>\WolfPack\models\mapobjects\corpse\bone3.md3 +file3=<>\WolfPack\models\mapobjects\corpse\bone4.md3 +file4=<>\WolfPack\models\mapobjects\corpse\corpse.md3 +file5=<>\WolfPack\models\mapobjects\corpse\leg.md3 +file6=<>\WolfPack\models\mapobjects\corpse\bone1.md3 + +[models\mapobjects\curtain] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\curtain\curtain_direct.md3 +file1=<>\WolfPack\models\mapobjects\curtain\2curt_lghtwindB.md3 +file2=<>\WolfPack\models\mapobjects\curtain\2curtains_96.md3 +file3=<>\WolfPack\models\mapobjects\curtain\curtain.md3 +file4=<>\WolfPack\models\mapobjects\curtain\curtain_200.md3 +file5=<>\WolfPack\models\mapobjects\curtain\2curt_lghtwind.md3 + +[models\mapobjects\deadosa] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\deadosa\deadosa_new.md3 +file1=<>\WolfPack\models\mapobjects\deadosa\deadosa.md3 + +[models\mapobjects\debris] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\debris\woodxl.md3 +file1=<>\WolfPack\models\mapobjects\debris\boulder2.md3 +file2=<>\WolfPack\models\mapobjects\debris\boulder3.md3 +file3=<>\WolfPack\models\mapobjects\debris\brick1.md3 +file4=<>\WolfPack\models\mapobjects\debris\brick2.md3 +file5=<>\WolfPack\models\mapobjects\debris\brick3.md3 +file6=<>\WolfPack\models\mapobjects\debris\brick4.md3 +file7=<>\WolfPack\models\mapobjects\debris\brick5.md3 +file8=<>\WolfPack\models\mapobjects\debris\brick6.md3 +file9=<>\WolfPack\models\mapobjects\debris\bricksm.md3 +file10=<>\WolfPack\models\mapobjects\debris\brickxl.md3 +file11=<>\WolfPack\models\mapobjects\debris\fabric1.md3 +file12=<>\WolfPack\models\mapobjects\debris\fabric2.md3 +file13=<>\WolfPack\models\mapobjects\debris\fabric3.md3 +file14=<>\WolfPack\models\mapobjects\debris\personal1.md3 +file15=<>\WolfPack\models\mapobjects\debris\personal2.md3 +file16=<>\WolfPack\models\mapobjects\debris\personal3.md3 +file17=<>\WolfPack\models\mapobjects\debris\personal4.md3 +file18=<>\WolfPack\models\mapobjects\debris\personal5.md3 +file19=<>\WolfPack\models\mapobjects\debris\rubble1.md3 +file20=<>\WolfPack\models\mapobjects\debris\rubble2.md3 +file21=<>\WolfPack\models\mapobjects\debris\rubble3.md3 +file22=<>\WolfPack\models\mapobjects\debris\woodm.md3 +file23=<>\WolfPack\models\mapobjects\debris\woodsm.md3 +file24=<>\WolfPack\models\mapobjects\debris\boulder.md3 + +[models\mapobjects\door] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\door\slidingdoor.md3 +file1=<>\WolfPack\models\mapobjects\door\door.md3 + +[models\mapobjects\electronics] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\electronics\tuberadio_a.md3 +file1=<>\WolfPack\models\mapobjects\electronics\alarmbox.md3 +file2=<>\WolfPack\models\mapobjects\electronics\enigma.md3 +file3=<>\WolfPack\models\mapobjects\electronics\gramophone.md3 +file4=<>\WolfPack\models\mapobjects\electronics\gramophone_anim.md3 +file5=<>\WolfPack\models\mapobjects\electronics\loudspeaker.md3 +file6=<>\WolfPack\models\mapobjects\electronics\loudspeaker2.md3 +file7=<>\WolfPack\models\mapobjects\electronics\parloradio.md3 +file8=<>\WolfPack\models\mapobjects\electronics\phone.md3 +file9=<>\WolfPack\models\mapobjects\electronics\radar.md3 +file10=<>\WolfPack\models\mapobjects\electronics\radar_105.md3 +file11=<>\WolfPack\models\mapobjects\electronics\radar_90.md3 +file12=<>\WolfPack\models\mapobjects\electronics\radar_panel_blown.md3 +file13=<>\WolfPack\models\mapobjects\electronics\radar_panel_blown105.md3 +file14=<>\WolfPack\models\mapobjects\electronics\radar_panel_blown90.md3 +file15=<>\WolfPack\models\mapobjects\electronics\radio_port.md3 +file16=<>\WolfPack\models\mapobjects\electronics\radio1.md3 +file17=<>\WolfPack\models\mapobjects\electronics\radios.md3 +file18=<>\WolfPack\models\mapobjects\electronics\speaker.md3 +file19=<>\WolfPack\models\mapobjects\electronics\speaker_control.md3 +file20=<>\WolfPack\models\mapobjects\electronics\tesla_prod.md3 +file21=<>\WolfPack\models\mapobjects\electronics\3radios.md3 + +[models\mapobjects\end] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\end\castle.md3 + +[models\mapobjects\eye] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\eye\eye.md3 + +[models\mapobjects\firehead] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\firehead\kid.md3 + +[models\mapobjects\flag] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\flag\uibanner2.md3 +file1=<>\WolfPack\models\mapobjects\flag\banner1.md3 +file2=<>\WolfPack\models\mapobjects\flag\british.md3 +file3=<>\WolfPack\models\mapobjects\flag\flag.md3 +file4=<>\WolfPack\models\mapobjects\flag\flag_americanui.md3 +file5=<>\WolfPack\models\mapobjects\flag\flag_escape.md3 +file6=<>\WolfPack\models\mapobjects\flag\flag_larg.md3 +file7=<>\WolfPack\models\mapobjects\flag\flag_med.md3 +file8=<>\WolfPack\models\mapobjects\flag\flag_naziui.md3 +file9=<>\WolfPack\models\mapobjects\flag\flag01.md3 +file10=<>\WolfPack\models\mapobjects\flag\flag1a.md3 +file11=<>\WolfPack\models\mapobjects\flag\flag1b.md3 +file12=<>\WolfPack\models\mapobjects\flag\flag1c.md3 +file13=<>\WolfPack\models\mapobjects\flag\uibanner.md3 +file14=<>\WolfPack\models\mapobjects\flag\american.md3 + +[models\mapobjects\furniture] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\furniture\type.md3 +file1=<>\WolfPack\models\mapobjects\furniture\bench.md3 +file2=<>\WolfPack\models\mapobjects\furniture\bkshelfsm.md3 +file3=<>\WolfPack\models\mapobjects\furniture\bkshelfxl.md3 +file4=<>\WolfPack\models\mapobjects\furniture\bunks.md3 +file5=<>\WolfPack\models\mapobjects\furniture\candelabra_sm.md3 +file6=<>\WolfPack\models\mapobjects\furniture\candelabra_xl.md3 +file7=<>\WolfPack\models\mapobjects\furniture\castletable1.md3 +file8=<>\WolfPack\models\mapobjects\furniture\cbkshelfsm.md3 +file9=<>\WolfPack\models\mapobjects\furniture\cbkshelfxl.md3 +file10=<>\WolfPack\models\mapobjects\furniture\chandelier.md3 +file11=<>\WolfPack\models\mapobjects\furniture\chat_armchair.md3 +file12=<>\WolfPack\models\mapobjects\furniture\chat_chair.md3 +file13=<>\WolfPack\models\mapobjects\furniture\chat_couch.md3 +file14=<>\WolfPack\models\mapobjects\furniture\chat_endtable.md3 +file15=<>\WolfPack\models\mapobjects\furniture\chat_table_sm.md3 +file16=<>\WolfPack\models\mapobjects\furniture\chat_walltable.md3 +file17=<>\WolfPack\models\mapobjects\furniture\churchair.md3 +file18=<>\WolfPack\models\mapobjects\furniture\churchtable.md3 +file19=<>\WolfPack\models\mapobjects\furniture\churchutch.md3 +file20=<>\WolfPack\models\mapobjects\furniture\clubchair.md3 +file21=<>\WolfPack\models\mapobjects\furniture\cuckoo.md3 +file22=<>\WolfPack\models\mapobjects\furniture\cup.md3 +file23=<>\WolfPack\models\mapobjects\furniture\director_chair.md3 +file24=<>\WolfPack\models\mapobjects\furniture\eagleflag.md3 +file25=<>\WolfPack\models\mapobjects\furniture\eaglestand.md3 +file26=<>\WolfPack\models\mapobjects\furniture\faucet.md3 +file27=<>\WolfPack\models\mapobjects\furniture\floor_radio.md3 +file28=<>\WolfPack\models\mapobjects\furniture\footlocker.md3 +file29=<>\WolfPack\models\mapobjects\furniture\fptools.md3 +file30=<>\WolfPack\models\mapobjects\furniture\fptools_b.md3 +file31=<>\WolfPack\models\mapobjects\furniture\furnace.md3 +file32=<>\WolfPack\models\mapobjects\furniture\furnace1.md3 +file33=<>\WolfPack\models\mapobjects\furniture\globe.md3 +file34=<>\WolfPack\models\mapobjects\furniture\grandclock.md3 +file35=<>\WolfPack\models\mapobjects\furniture\heater.md3 +file36=<>\WolfPack\models\mapobjects\furniture\heinrichbust.md3 +file37=<>\WolfPack\models\mapobjects\furniture\heinrichsculptor.md3 +file38=<>\WolfPack\models\mapobjects\furniture\hitlerbust.md3 +file39=<>\WolfPack\models\mapobjects\furniture\locker_broken.md3 +file40=<>\WolfPack\models\mapobjects\furniture\loveseat.md3 +file41=<>\WolfPack\models\mapobjects\furniture\manacle.md3 +file42=<>\WolfPack\models\mapobjects\furniture\manacles.md3 +file43=<>\WolfPack\models\mapobjects\furniture\manacles_dark.md3 +file44=<>\WolfPack\models\mapobjects\furniture\metalchairgrid.md3 +file45=<>\WolfPack\models\mapobjects\furniture\metalshelf.md3 +file46=<>\WolfPack\models\mapobjects\furniture\plate1.md3 +file47=<>\WolfPack\models\mapobjects\furniture\plate2.md3 +file48=<>\WolfPack\models\mapobjects\furniture\plate3.md3 +file49=<>\WolfPack\models\mapobjects\furniture\sausage.md3 +file50=<>\WolfPack\models\mapobjects\furniture\sculptor.md3 +file51=<>\WolfPack\models\mapobjects\furniture\shelfxl.md3 +file52=<>\WolfPack\models\mapobjects\furniture\testchair.md3 +file53=<>\WolfPack\models\mapobjects\furniture\torture.md3 +file54=<>\WolfPack\models\mapobjects\furniture\beerstein.md3 + +[models\mapobjects\gargoyle] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\gargoyle\gargoyle.md3 + +[models\mapobjects\gears] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\gears\gear5.md3 +file1=<>\WolfPack\models\mapobjects\gears\gear2.md3 +file2=<>\WolfPack\models\mapobjects\gears\gear3.md3 +file3=<>\WolfPack\models\mapobjects\gears\gear4.md3 +file4=<>\WolfPack\models\mapobjects\gears\gear1.md3 + +[models\mapobjects\generator] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\generator\gen_sm.md3 +file1=<>\WolfPack\models\mapobjects\generator\gen.md3 +file2=<>\WolfPack\models\mapobjects\generator\gen_lrg.md3 +file3=<>\WolfPack\models\mapobjects\generator\gen_med.md3 +file4=<>\WolfPack\models\mapobjects\generator\expl_gen.md3 + +[models\mapobjects\goblet] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\goblet\chalice.md3 + +[models\mapobjects\ironmaiden] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\ironmaiden\maiden.md3 + +[models\mapobjects\jetengine] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\jetengine\jetengine.md3 + +[models\mapobjects\knight] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\knight\knight115.md3 +file1=<>\WolfPack\models\mapobjects\knight\knight_anim.md3 +file2=<>\WolfPack\models\mapobjects\knight\knight_anim2.md3 +file3=<>\WolfPack\models\mapobjects\knight\knight_anim3.md3 +file4=<>\WolfPack\models\mapobjects\knight\knight_stone.md3 +file5=<>\WolfPack\models\mapobjects\knight\knight.md3 + +[models\mapobjects\lamps] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\lamps\desklamp.md3 + +[models\mapobjects\leaf] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\leaf\leaf.md3 + +[models\mapobjects\letters] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\letters\type.md3 + +[models\mapobjects\light] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\light\worklamp_floor.md3 +file1=<>\WolfPack\models\mapobjects\light\bel_lamp_2k.md3 +file2=<>\WolfPack\models\mapobjects\light\bel_lamp_5k.md3 +file3=<>\WolfPack\models\mapobjects\light\bel_lamp_7k.md3 +file4=<>\WolfPack\models\mapobjects\light\bel_lamp_arm.md3 +file5=<>\WolfPack\models\mapobjects\light\bel_lamp_top.md3 +file6=<>\WolfPack\models\mapobjects\light\bel_lamp_top128.md3 +file7=<>\WolfPack\models\mapobjects\light\bel_lamp_top24.md3 +file8=<>\WolfPack\models\mapobjects\light\bel_lamp_top40.md3 +file9=<>\WolfPack\models\mapobjects\light\bel_lamp_top64.md3 +file10=<>\WolfPack\models\mapobjects\light\bel_lamp_top96.md3 +file11=<>\WolfPack\models\mapobjects\light\bel_lamp2.md3 +file12=<>\WolfPack\models\mapobjects\light\cagelight.md3 +file13=<>\WolfPack\models\mapobjects\light\cagelighta.md3 +file14=<>\WolfPack\models\mapobjects\light\cagelighta12k.md3 +file15=<>\WolfPack\models\mapobjects\light\cagelighta1k.md3 +file16=<>\WolfPack\models\mapobjects\light\cagelighta2k.md3 +file17=<>\WolfPack\models\mapobjects\light\cagelighta3k.md3 +file18=<>\WolfPack\models\mapobjects\light\cagelighta4k.md3 +file19=<>\WolfPack\models\mapobjects\light\cagelighta5k.md3 +file20=<>\WolfPack\models\mapobjects\light\cagelighta7k.md3 +file21=<>\WolfPack\models\mapobjects\light\cagelighta9k.md3 +file22=<>\WolfPack\models\mapobjects\light\cagelightr.md3 +file23=<>\WolfPack\models\mapobjects\light\camplt.md3 +file24=<>\WolfPack\models\mapobjects\light\camplt45.md3 +file25=<>\WolfPack\models\mapobjects\light\chad1.md3 +file26=<>\WolfPack\models\mapobjects\light\chad2.md3 +file27=<>\WolfPack\models\mapobjects\light\chandelier.md3 +file28=<>\WolfPack\models\mapobjects\light\floor_worklamp.md3 +file29=<>\WolfPack\models\mapobjects\light\gen_2lamps.md3 +file30=<>\WolfPack\models\mapobjects\light\lantern.md3 +file31=<>\WolfPack\models\mapobjects\light\lantern_r.md3 +file32=<>\WolfPack\models\mapobjects\light\p_nolight.md3 +file33=<>\WolfPack\models\mapobjects\light\pendant.md3 +file34=<>\WolfPack\models\mapobjects\light\pendant_1k.md3 +file35=<>\WolfPack\models\mapobjects\light\pendantb.md3 +file36=<>\WolfPack\models\mapobjects\light\sconce.md3 +file37=<>\WolfPack\models\mapobjects\light\sconce2.md3 +file38=<>\WolfPack\models\mapobjects\light\sconce3.md3 +file39=<>\WolfPack\models\mapobjects\light\searchlight.md3 +file40=<>\WolfPack\models\mapobjects\light\searchlight_l_broke.md3 +file41=<>\WolfPack\models\mapobjects\light\searchlight_pivot.md3 +file42=<>\WolfPack\models\mapobjects\light\searchlight1_b.md3 +file43=<>\WolfPack\models\mapobjects\light\searchlight1_l.md3 +file44=<>\WolfPack\models\mapobjects\light\test.md3 +file45=<>\WolfPack\models\mapobjects\light\worklamp.md3 +file46=<>\WolfPack\models\mapobjects\light\bel_lamp_10k.md3 + +[models\mapobjects\lion] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\lion\lion.md3 + +[models\mapobjects\lop] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\lop\lop3.md3 +file1=<>\WolfPack\models\mapobjects\lop\lop.md3 + +[models\mapobjects\miltary_trim] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\miltary_trim\sbag_st.md3 +file1=<>\WolfPack\models\mapobjects\miltary_trim\dish_only.md3 +file2=<>\WolfPack\models\mapobjects\miltary_trim\dragon_clip.md3 +file3=<>\WolfPack\models\mapobjects\miltary_trim\dragon_teeth.md3 +file4=<>\WolfPack\models\mapobjects\miltary_trim\dragon_teeth_e.md3 +file5=<>\WolfPack\models\mapobjects\miltary_trim\eagle150.md3 +file6=<>\WolfPack\models\mapobjects\miltary_trim\eagle250.md3 +file7=<>\WolfPack\models\mapobjects\miltary_trim\eagle90.md3 +file8=<>\WolfPack\models\mapobjects\miltary_trim\gangway.md3 +file9=<>\WolfPack\models\mapobjects\miltary_trim\gangway_up.md3 +file10=<>\WolfPack\models\mapobjects\miltary_trim\helmutbot.md3 +file11=<>\WolfPack\models\mapobjects\miltary_trim\helmuttop.md3 +file12=<>\WolfPack\models\mapobjects\miltary_trim\radar_twr.md3 +file13=<>\WolfPack\models\mapobjects\miltary_trim\rocket.md3 +file14=<>\WolfPack\models\mapobjects\miltary_trim\sandbag1.md3 +file15=<>\WolfPack\models\mapobjects\miltary_trim\sandbag1_45.md3 +file16=<>\WolfPack\models\mapobjects\miltary_trim\sbag_cr.md3 +file17=<>\WolfPack\models\mapobjects\miltary_trim\sbag_crs.md3 +file18=<>\WolfPack\models\mapobjects\miltary_trim\sbag_crs_90.md3 +file19=<>\WolfPack\models\mapobjects\miltary_trim\barbwire.md3 + +[models\mapobjects\nazi] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\nazi\soldierscale.md3 +file1=<>\WolfPack\models\mapobjects\nazi\soldier128.md3 +file2=<>\WolfPack\models\mapobjects\nazi\soldier72.md3 +file3=<>\WolfPack\models\mapobjects\nazi\soldier76.md3 +file4=<>\WolfPack\models\mapobjects\nazi\soldier80.md3 +file5=<>\WolfPack\models\mapobjects\nazi\soldier86.md3 +file6=<>\WolfPack\models\mapobjects\nazi\soldier96.md3 +file7=<>\WolfPack\models\mapobjects\nazi\soldier120.md3 + +[models\mapobjects\parachute] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\parachute\test2.md3 +file1=<>\WolfPack\models\mapobjects\parachute\asschute.md3 +file2=<>\WolfPack\models\mapobjects\parachute\assualtchute.md3 +file3=<>\WolfPack\models\mapobjects\parachute\assualtchute2.md3 +file4=<>\WolfPack\models\mapobjects\parachute\bjchute.md3 +file5=<>\WolfPack\models\mapobjects\parachute\test.md3 +file6=<>\WolfPack\models\mapobjects\parachute\assaultchute3.md3 + +[models\mapobjects\piano] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\piano\babyg.md3 + +[models\mapobjects\plants] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\plants\vine.md3 +file1=<>\WolfPack\models\mapobjects\plants\bush2.md3 +file2=<>\WolfPack\models\mapobjects\plants\flowers1.md3 +file3=<>\WolfPack\models\mapobjects\plants\flowers1_no_pot.md3 +file4=<>\WolfPack\models\mapobjects\plants\small_tree.md3 +file5=<>\WolfPack\models\mapobjects\plants\bush.md3 + +[models\mapobjects\punchingbag] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\punchingbag\pbag.md3 + +[models\mapobjects\sawblade] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\sawblade\saw_arm.md3 +file1=<>\WolfPack\models\mapobjects\sawblade\saw.md3 + +[models\mapobjects\sfx] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\sfx\hportal4.md3 +file1=<>\WolfPack\models\mapobjects\sfx\blob1.md3 +file2=<>\WolfPack\models\mapobjects\sfx\blob2.md3 +file3=<>\WolfPack\models\mapobjects\sfx\hportal.md3 +file4=<>\WolfPack\models\mapobjects\sfx\hportal1.md3 +file5=<>\WolfPack\models\mapobjects\sfx\hportal2.md3 +file6=<>\WolfPack\models\mapobjects\sfx\hportal3.md3 +file7=<>\WolfPack\models\mapobjects\sfx\blob.md3 +SubDir0=models\mapobjects\sfx\punchingbag + +[models\mapobjects\sfx\punchingbag] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\sfx\punchingbag\pbag4way.md3 +file1=<>\WolfPack\models\mapobjects\sfx\punchingbag\pbag.md3 + +[models\mapobjects\shadtest] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\shadtest\cin.md3 + +[models\mapobjects\shield] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\shield\shield_anim.md3 +file1=<>\WolfPack\models\mapobjects\shield\shield.md3 + +[models\mapobjects\size] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\size\bjsize.md3 + +[models\mapobjects\skel] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\skel\skeltest.md3 +file1=<>\WolfPack\models\mapobjects\skel\hand_bone.md3 +file2=<>\WolfPack\models\mapobjects\skel\hand_bone2.md3 +file3=<>\WolfPack\models\mapobjects\skel\leg.md3 +file4=<>\WolfPack\models\mapobjects\skel\leg_bone.md3 +file5=<>\WolfPack\models\mapobjects\skel\skel_ribs.md3 +file6=<>\WolfPack\models\mapobjects\skel\skel02mid.md3 +file7=<>\WolfPack\models\mapobjects\skel\hand.md3 + +[models\mapobjects\skull] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\skull\skull_wallb.md3 +file1=<>\WolfPack\models\mapobjects\skull\skul2t.md3 +file2=<>\WolfPack\models\mapobjects\skull\skull.md3 +file3=<>\WolfPack\models\mapobjects\skull\skull_scale.md3 +file4=<>\WolfPack\models\mapobjects\skull\skull_scale_t45.md3 +file5=<>\WolfPack\models\mapobjects\skull\skull_scale_t90.md3 +file6=<>\WolfPack\models\mapobjects\skull\skull_tilt1.md3 +file7=<>\WolfPack\models\mapobjects\skull\skull_wall.md3 +file8=<>\WolfPack\models\mapobjects\skull\skull_wall_f.md3 +file9=<>\WolfPack\models\mapobjects\skull\skul2.md3 + +[models\mapobjects\sky] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\sky\terrain9.md3 +file1=<>\WolfPack\models\mapobjects\sky\inner2.md3 +file2=<>\WolfPack\models\mapobjects\sky\mount2.md3 +file3=<>\WolfPack\models\mapobjects\sky\outer.md3 +file4=<>\WolfPack\models\mapobjects\sky\outer2.md3 +file5=<>\WolfPack\models\mapobjects\sky\plane.md3 +file6=<>\WolfPack\models\mapobjects\sky\plane2.md3 +file7=<>\WolfPack\models\mapobjects\sky\terrain.md3 +file8=<>\WolfPack\models\mapobjects\sky\terrain_ns.md3 +file9=<>\WolfPack\models\mapobjects\sky\terrain1.md3 +file10=<>\WolfPack\models\mapobjects\sky\terrain10.md3 +file11=<>\WolfPack\models\mapobjects\sky\terrain11.md3 +file12=<>\WolfPack\models\mapobjects\sky\terrain12.md3 +file13=<>\WolfPack\models\mapobjects\sky\terrain13.md3 +file14=<>\WolfPack\models\mapobjects\sky\terrain14.md3 +file15=<>\WolfPack\models\mapobjects\sky\terrain15.md3 +file16=<>\WolfPack\models\mapobjects\sky\terrain16.md3 +file17=<>\WolfPack\models\mapobjects\sky\terrain2.md3 +file18=<>\WolfPack\models\mapobjects\sky\terrain3.md3 +file19=<>\WolfPack\models\mapobjects\sky\terrain4.md3 +file20=<>\WolfPack\models\mapobjects\sky\terrain5.md3 +file21=<>\WolfPack\models\mapobjects\sky\terrain6.md3 +file22=<>\WolfPack\models\mapobjects\sky\terrain7.md3 +file23=<>\WolfPack\models\mapobjects\sky\terrain8.md3 +file24=<>\WolfPack\models\mapobjects\sky\inner.md3 + +[models\mapobjects\space] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\space\galaxystar.md3 +file1=<>\WolfPack\models\mapobjects\space\galaxy1.md3 +file2=<>\WolfPack\models\mapobjects\space\galaxyout.md3 +file3=<>\WolfPack\models\mapobjects\space\galaxy.md3 + +[models\mapobjects\toolshed] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\toolshed\weldtankxl.md3 +file1=<>\WolfPack\models\mapobjects\toolshed\pickax.md3 +file2=<>\WolfPack\models\mapobjects\toolshed\pickax_v.md3 +file3=<>\WolfPack\models\mapobjects\toolshed\shovel.md3 +file4=<>\WolfPack\models\mapobjects\toolshed\shovel_flat.md3 +file5=<>\WolfPack\models\mapobjects\toolshed\tools1.md3 +file6=<>\WolfPack\models\mapobjects\toolshed\weldtanksm.md3 +file7=<>\WolfPack\models\mapobjects\toolshed\generator.md3 + +[models\mapobjects\torture] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\torture\tools2.md3 +file1=<>\WolfPack\models\mapobjects\torture\brainjar.md3 +file2=<>\WolfPack\models\mapobjects\torture\cage1.md3 +file3=<>\WolfPack\models\mapobjects\torture\cage1BK.md3 +file4=<>\WolfPack\models\mapobjects\torture\cage2.md3 +file5=<>\WolfPack\models\mapobjects\torture\clamp.md3 +file6=<>\WolfPack\models\mapobjects\torture\fptools.md3 +file7=<>\WolfPack\models\mapobjects\torture\fptools_b.md3 +file8=<>\WolfPack\models\mapobjects\torture\headcrusher.md3 +file9=<>\WolfPack\models\mapobjects\torture\l_engine.md3 +file10=<>\WolfPack\models\mapobjects\torture\slash.md3 +file11=<>\WolfPack\models\mapobjects\torture\tools1.md3 +file12=<>\WolfPack\models\mapobjects\torture\beheader.md3 + +[models\mapobjects\tree] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\tree\weed_m01.md3 +file1=<>\WolfPack\models\mapobjects\tree\dead_tree.md3 +file2=<>\WolfPack\models\mapobjects\tree\dead_tree2.md3 +file3=<>\WolfPack\models\mapobjects\tree\dead_tree3.md3 +file4=<>\WolfPack\models\mapobjects\tree\dead_tree4.md3 +file5=<>\WolfPack\models\mapobjects\tree\dead_tree5.md3 +file6=<>\WolfPack\models\mapobjects\tree\dead_tree5a.md3 +file7=<>\WolfPack\models\mapobjects\tree\shrub.md3 +file8=<>\WolfPack\models\mapobjects\tree\shrubaa.md3 +file9=<>\WolfPack\models\mapobjects\tree\training_tree1.md3 +file10=<>\WolfPack\models\mapobjects\tree\training_tree2.md3 +file11=<>\WolfPack\models\mapobjects\tree\training_tree3.md3 +file12=<>\WolfPack\models\mapobjects\tree\tree_big.md3 +file13=<>\WolfPack\models\mapobjects\tree\tree_big1.md3 +file14=<>\WolfPack\models\mapobjects\tree\tree_big2.md3 +file15=<>\WolfPack\models\mapobjects\tree\tree_big3.md3 +file16=<>\WolfPack\models\mapobjects\tree\tree_big4.md3 +file17=<>\WolfPack\models\mapobjects\tree\tree_big5.md3 +file18=<>\WolfPack\models\mapobjects\tree\tree_d1.md3 +file19=<>\WolfPack\models\mapobjects\tree\tree_litl.md3 +file20=<>\WolfPack\models\mapobjects\tree\tree_low1.md3 +file21=<>\WolfPack\models\mapobjects\tree\tree_m01.md3 +file22=<>\WolfPack\models\mapobjects\tree\tree_m01s.md3 +file23=<>\WolfPack\models\mapobjects\tree\tree_m02.md3 +file24=<>\WolfPack\models\mapobjects\tree\tree_m03.md3 +file25=<>\WolfPack\models\mapobjects\tree\tree_m04.md3 +file26=<>\WolfPack\models\mapobjects\tree\tree_m05.md3 +file27=<>\WolfPack\models\mapobjects\tree\tree_m06.md3 +file28=<>\WolfPack\models\mapobjects\tree\tree_m07.md3 +file29=<>\WolfPack\models\mapobjects\tree\tree_m07_1.md3 +file30=<>\WolfPack\models\mapobjects\tree\tree_m07a.md3 +file31=<>\WolfPack\models\mapobjects\tree\tree_m08.md3 +file32=<>\WolfPack\models\mapobjects\tree\tree_m08_1.md3 +file33=<>\WolfPack\models\mapobjects\tree\tree_m08_snow.md3 +file34=<>\WolfPack\models\mapobjects\tree\tree_m08a.md3 +file35=<>\WolfPack\models\mapobjects\tree\tree_m09s.md3 +file36=<>\WolfPack\models\mapobjects\tree\tree_med.md3 +file37=<>\WolfPack\models\mapobjects\tree\tree_mp01.md3 +file38=<>\WolfPack\models\mapobjects\tree\tree_mp01_1.md3 +file39=<>\WolfPack\models\mapobjects\tree\tree_mp01_up.md3 +file40=<>\WolfPack\models\mapobjects\tree\tree_mp01a.md3 +file41=<>\WolfPack\models\mapobjects\tree\tree_mp01ab.md3 +file42=<>\WolfPack\models\mapobjects\tree\tree_mp02.md3 +file43=<>\WolfPack\models\mapobjects\tree\tree_new.md3 +file44=<>\WolfPack\models\mapobjects\tree\tree_pine.md3 +file45=<>\WolfPack\models\mapobjects\tree\tree_pine_2.md3 +file46=<>\WolfPack\models\mapobjects\tree\tree_pine_2_1.md3 +file47=<>\WolfPack\models\mapobjects\tree\tree_pine3.md3 +file48=<>\WolfPack\models\mapobjects\tree\tree_pine4.md3 +file49=<>\WolfPack\models\mapobjects\tree\tree_pine5.md3 +file50=<>\WolfPack\models\mapobjects\tree\tree_pine6.md3 +file51=<>\WolfPack\models\mapobjects\tree\tree_pine7.md3 +file52=<>\WolfPack\models\mapobjects\tree\tree_pine8.md3 +file53=<>\WolfPack\models\mapobjects\tree\tree_small.md3 +file54=<>\WolfPack\models\mapobjects\tree\tree_small3.md3 +file55=<>\WolfPack\models\mapobjects\tree\tree_tall.md3 +file56=<>\WolfPack\models\mapobjects\tree\tree_tall_256.md3 +file57=<>\WolfPack\models\mapobjects\tree\tree_vil1.md3 +file58=<>\WolfPack\models\mapobjects\tree\tree_vil2.md3 +file59=<>\WolfPack\models\mapobjects\tree\tree_village2.md3 +file60=<>\WolfPack\models\mapobjects\tree\bush1.md3 + +[models\mapobjects\tree_snow] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\tree_snow\snow_tree4.md3 +file1=<>\WolfPack\models\mapobjects\tree_snow\snow_rock.md3 +file2=<>\WolfPack\models\mapobjects\tree_snow\snow_tree1.md3 +file3=<>\WolfPack\models\mapobjects\tree_snow\snow_tree3.md3 +file4=<>\WolfPack\models\mapobjects\tree_snow\snow_bush1.md3 + +[models\mapobjects\ubergun] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\ubergun\ubergun2.md3 +file1=<>\WolfPack\models\mapobjects\ubergun\ubergun1.md3 + +[models\mapobjects\ui] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\ui\icon_burn.md3 + +[models\mapobjects\undead] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\undead\zom80.md3 +file1=<>\WolfPack\models\mapobjects\undead\undnazi76.md3 +file2=<>\WolfPack\models\mapobjects\undead\undnazi80.md3 +file3=<>\WolfPack\models\mapobjects\undead\zom2.md3 +file4=<>\WolfPack\models\mapobjects\undead\zom71.md3 +file5=<>\WolfPack\models\mapobjects\undead\zom76.md3 +file6=<>\WolfPack\models\mapobjects\undead\undnazi1.md3 + +[models\mapobjects\v2] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\v2\v4_tagsdestroyed.md3 +file1=<>\WolfPack\models\mapobjects\v2\v4_b.md3 +file2=<>\WolfPack\models\mapobjects\v2\v4_bw.md3 +file3=<>\WolfPack\models\mapobjects\v2\v4_c.md3 +file4=<>\WolfPack\models\mapobjects\v2\v4_cw.md3 +file5=<>\WolfPack\models\mapobjects\v2\v4_g.md3 +file6=<>\WolfPack\models\mapobjects\v2\v4_gw.md3 +file7=<>\WolfPack\models\mapobjects\v2\v4_side.md3 +file8=<>\WolfPack\models\mapobjects\v2\v4_side_lm.md3 +file9=<>\WolfPack\models\mapobjects\v2\v4_sideb.md3 +file10=<>\WolfPack\models\mapobjects\v2\v4_sideb_lm.md3 +file11=<>\WolfPack\models\mapobjects\v2\v4_tags.md3 +file12=<>\WolfPack\models\mapobjects\v2\v4.md3 + +[models\mapobjects\vacum] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\vacum\zom.md3 +file1=<>\WolfPack\models\mapobjects\vacum\box.md3 +file2=<>\WolfPack\models\mapobjects\vacum\vacum.md3 +file3=<>\WolfPack\models\mapobjects\vacum\bl.md3 + +[models\mapobjects\vehicles] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\vehicles\wheel.md3 +file1=<>\WolfPack\models\mapobjects\vehicles\boat_door.md3 +file2=<>\WolfPack\models\mapobjects\vehicles\flatbed.md3 +file3=<>\WolfPack\models\mapobjects\vehicles\gunflare.md3 +file4=<>\WolfPack\models\mapobjects\vehicles\halftrack.md3 +file5=<>\WolfPack\models\mapobjects\vehicles\halftrack2.md3 +file6=<>\WolfPack\models\mapobjects\vehicles\heinkel.md3 +file7=<>\WolfPack\models\mapobjects\vehicles\heinkel2.md3 +file8=<>\WolfPack\models\mapobjects\vehicles\heinkel3.md3 +file9=<>\WolfPack\models\mapobjects\vehicles\heinkelb1.md3 +file10=<>\WolfPack\models\mapobjects\vehicles\heinkelb2.md3 +file11=<>\WolfPack\models\mapobjects\vehicles\jeep.md3 +file12=<>\WolfPack\models\mapobjects\vehicles\jeepOLD.md3 +file13=<>\WolfPack\models\mapobjects\vehicles\jet_shadow.md3 +file14=<>\WolfPack\models\mapobjects\vehicles\lander.md3 +file15=<>\WolfPack\models\mapobjects\vehicles\lander_door.md3 +file16=<>\WolfPack\models\mapobjects\vehicles\lander_ndoor.md3 +file17=<>\WolfPack\models\mapobjects\vehicles\lander1w.md3 +file18=<>\WolfPack\models\mapobjects\vehicles\lander2.md3 +file19=<>\WolfPack\models\mapobjects\vehicles\lander2w.md3 +file20=<>\WolfPack\models\mapobjects\vehicles\m109.md3 +file21=<>\WolfPack\models\mapobjects\vehicles\m109_100 +file22=<>\WolfPack\models\mapobjects\vehicles\m109_prop.md3 +file23=<>\WolfPack\models\mapobjects\vehicles\M109_T.md3 +file24=<>\WolfPack\models\mapobjects\vehicles\M109_T1.md3 +file25=<>\WolfPack\models\mapobjects\vehicles\M109_T2.md3 +file26=<>\WolfPack\models\mapobjects\vehicles\m109crash.md3 +file27=<>\WolfPack\models\mapobjects\vehicles\m109debris_a.md3 +file28=<>\WolfPack\models\mapobjects\vehicles\m109debris_b.md3 +file29=<>\WolfPack\models\mapobjects\vehicles\m109debris_c.md3 +file30=<>\WolfPack\models\mapobjects\vehicles\m109debris_d.md3 +file31=<>\WolfPack\models\mapobjects\vehicles\natter.md3 +file32=<>\WolfPack\models\mapobjects\vehicles\natter_apart.md3 +file33=<>\WolfPack\models\mapobjects\vehicles\panzer.md3 +file34=<>\WolfPack\models\mapobjects\vehicles\panzer_a.md3 +file35=<>\WolfPack\models\mapobjects\vehicles\panzer_b.md3 +file36=<>\WolfPack\models\mapobjects\vehicles\panzer_b.md3BK +file37=<>\WolfPack\models\mapobjects\vehicles\panzer_c.md3 +file38=<>\WolfPack\models\mapobjects\vehicles\panzer_c.md3BK +file39=<>\WolfPack\models\mapobjects\vehicles\panzer_d.md3 +file40=<>\WolfPack\models\mapobjects\vehicles\panzer_dBK.md3 +file41=<>\WolfPack\models\mapobjects\vehicles\panzer_e.md3 +file42=<>\WolfPack\models\mapobjects\vehicles\raft.md3 +file43=<>\WolfPack\models\mapobjects\vehicles\raft1a.md3 +file44=<>\WolfPack\models\mapobjects\vehicles\raft2.md3 +file45=<>\WolfPack\models\mapobjects\vehicles\raft3.md3 +file46=<>\WolfPack\models\mapobjects\vehicles\rocket_notop.md3 +file47=<>\WolfPack\models\mapobjects\vehicles\rocket_top.md3 +file48=<>\WolfPack\models\mapobjects\vehicles\rocket_train.md3 +file49=<>\WolfPack\models\mapobjects\vehicles\rocket_trans.md3 +file50=<>\WolfPack\models\mapobjects\vehicles\rocketrain.md3 +file51=<>\WolfPack\models\mapobjects\vehicles\rocketraingear.md3 +file52=<>\WolfPack\models\mapobjects\vehicles\rocketrainWR.md3 +file53=<>\WolfPack\models\mapobjects\vehicles\shermanimtest.md3 +file54=<>\WolfPack\models\mapobjects\vehicles\shermanimtest_135.md3 +file55=<>\WolfPack\models\mapobjects\vehicles\shermanimtestbk.md3 +file56=<>\WolfPack\models\mapobjects\vehicles\sltrack.md3 +file57=<>\WolfPack\models\mapobjects\vehicles\srtrack.md3 +file58=<>\WolfPack\models\mapobjects\vehicles\staff_car.md3 +file59=<>\WolfPack\models\mapobjects\vehicles\staff_car_v.md3 +file60=<>\WolfPack\models\mapobjects\vehicles\staff4wheels.md3 +file61=<>\WolfPack\models\mapobjects\vehicles\staffcar.md3 +file62=<>\WolfPack\models\mapobjects\vehicles\staffdwheel.md3 +file63=<>\WolfPack\models\mapobjects\vehicles\staffpwheel.md3 +file64=<>\WolfPack\models\mapobjects\vehicles\sturret.md3 +file65=<>\WolfPack\models\mapobjects\vehicles\sub1.md3 +file66=<>\WolfPack\models\mapobjects\vehicles\sub1a.md3 +file67=<>\WolfPack\models\mapobjects\vehicles\sub2a.md3 +file68=<>\WolfPack\models\mapobjects\vehicles\sub3a.md3 +file69=<>\WolfPack\models\mapobjects\vehicles\tag_chassis.md3 +file70=<>\WolfPack\models\mapobjects\vehicles\tag_turret.md3 +file71=<>\WolfPack\models\mapobjects\vehicles\tankchassis.md3 +file72=<>\WolfPack\models\mapobjects\vehicles\tanktracks.md3 +file73=<>\WolfPack\models\mapobjects\vehicles\tankturret.md3 +file74=<>\WolfPack\models\mapobjects\vehicles\tankturret_b.md3 +file75=<>\WolfPack\models\mapobjects\vehicles\test.md3 +file76=<>\WolfPack\models\mapobjects\vehicles\torpedo.md3 +file77=<>\WolfPack\models\mapobjects\vehicles\train_trailer.md3 +file78=<>\WolfPack\models\mapobjects\vehicles\TRAIN_W.md3 +file79=<>\WolfPack\models\mapobjects\vehicles\TRAINCAR.md3 +file80=<>\WolfPack\models\mapobjects\vehicles\transport.md3 +file81=<>\WolfPack\models\mapobjects\vehicles\transport_w1.md3 +file82=<>\WolfPack\models\mapobjects\vehicles\transport_w1a.md3 +file83=<>\WolfPack\models\mapobjects\vehicles\transport_w2.md3 +file84=<>\WolfPack\models\mapobjects\vehicles\transport_w2a.md3 +file85=<>\WolfPack\models\mapobjects\vehicles\transport_w3.md3 +file86=<>\WolfPack\models\mapobjects\vehicles\transport_w3a.md3 +file87=<>\WolfPack\models\mapobjects\vehicles\truck.md3 +file88=<>\WolfPack\models\mapobjects\vehicles\truck_4wheels.md3 +file89=<>\WolfPack\models\mapobjects\vehicles\truck_4wheels.md3BK +file90=<>\WolfPack\models\mapobjects\vehicles\truck_b.md3 +file91=<>\WolfPack\models\mapobjects\vehicles\truck_base.md3 +file92=<>\WolfPack\models\mapobjects\vehicles\truck_exp.md3 +file93=<>\WolfPack\models\mapobjects\vehicles\truck_exp_ref.md3 +file94=<>\WolfPack\models\mapobjects\vehicles\truck_flatbed.md3 +file95=<>\WolfPack\models\mapobjects\vehicles\truck_flatbed_fire.md3 +file96=<>\WolfPack\models\mapobjects\vehicles\truck_flatbed_fire.md3BK +file97=<>\WolfPack\models\mapobjects\vehicles\truck_flatbed_grey.md3 +file98=<>\WolfPack\models\mapobjects\vehicles\truck_flatbed_grey.md3BK +file99=<>\WolfPack\models\mapobjects\vehicles\truck_lwheel.md3 +file100=<>\WolfPack\models\mapobjects\vehicles\truck_lwheel.MD3BK +file101=<>\WolfPack\models\mapobjects\vehicles\truck_rwheel.md3 +file102=<>\WolfPack\models\mapobjects\vehicles\truck_rwheel.MD3BK +file103=<>\WolfPack\models\mapobjects\vehicles\truck_shadow.md3 +file104=<>\WolfPack\models\mapobjects\vehicles\truck_support_grey.md3 +file105=<>\WolfPack\models\mapobjects\vehicles\truck_support_grey.md3BK +file106=<>\WolfPack\models\mapobjects\vehicles\truckn_90.md3 +file107=<>\WolfPack\models\mapobjects\vehicles\truckn_BK.md3 +file108=<>\WolfPack\models\mapobjects\vehicles\truckn_door.md3 +file109=<>\WolfPack\models\mapobjects\vehicles\truckn_door.md3BK +file110=<>\WolfPack\models\mapobjects\vehicles\truckn2.md3 +file111=<>\WolfPack\models\mapobjects\vehicles\truckn2.md3a +file112=<>\WolfPack\models\mapobjects\vehicles\truckn2aa.md3 +file113=<>\WolfPack\models\mapobjects\vehicles\truckn2bk.md3 +file114=<>\WolfPack\models\mapobjects\vehicles\trucknaa.md3 +file115=<>\WolfPack\models\mapobjects\vehicles\u_boat.md3 +file116=<>\WolfPack\models\mapobjects\vehicles\u_boat_ice.md3 +file117=<>\WolfPack\models\mapobjects\vehicles\vtol.md3 +file118=<>\WolfPack\models\mapobjects\vehicles\vtol_explode.md3 +file119=<>\WolfPack\models\mapobjects\vehicles\vtol_explode_ref.md3 +file120=<>\WolfPack\models\mapobjects\vehicles\vtol2.md3 +file121=<>\WolfPack\models\mapobjects\vehicles\vtol2_w1.md3 +file122=<>\WolfPack\models\mapobjects\vehicles\vtol2_w2.md3 +file123=<>\WolfPack\models\mapobjects\vehicles\vtol2_w3.md3 +file124=<>\WolfPack\models\mapobjects\vehicles\wagon_tilt.md3 +file125=<>\WolfPack\models\mapobjects\vehicles\blimp.md3 + +[models\mapobjects\watertower] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\watertower\watertower.md3 + +[models\mapobjects\weapons] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\weapons\turret_c.md3 +file1=<>\WolfPack\models\mapobjects\weapons\flak_b.md3 +file2=<>\WolfPack\models\mapobjects\weapons\flakgun.md3 +file3=<>\WolfPack\models\mapobjects\weapons\flakredux.md3 +file4=<>\WolfPack\models\mapobjects\weapons\mauser1.md3 +file5=<>\WolfPack\models\mapobjects\weapons\mauser2.md3 +file6=<>\WolfPack\models\mapobjects\weapons\mg42a.md3 +file7=<>\WolfPack\models\mapobjects\weapons\mg42a_BK.md3 +file8=<>\WolfPack\models\mapobjects\weapons\mg42b.md3 +file9=<>\WolfPack\models\mapobjects\weapons\monoflakgun.md3 +file10=<>\WolfPack\models\mapobjects\weapons\mortar.md3 +file11=<>\WolfPack\models\mapobjects\weapons\mp40_upright.md3 +file12=<>\WolfPack\models\mapobjects\weapons\mp40_upright_old.md3 +file13=<>\WolfPack\models\mapobjects\weapons\satchel_charge.md3 +file14=<>\WolfPack\models\mapobjects\weapons\sdagger.md3 +file15=<>\WolfPack\models\mapobjects\weapons\test.md3 +file16=<>\WolfPack\models\mapobjects\weapons\truckn_BK.md3 +file17=<>\WolfPack\models\mapobjects\weapons\turret_a.md3 +file18=<>\WolfPack\models\mapobjects\weapons\turret_b.md3 +file19=<>\WolfPack\models\mapobjects\weapons\flak_a.md3 + +[models\mapobjects\wine] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\wine\wine_barrel.md3 +file1=<>\WolfPack\models\mapobjects\wine\wine.md3 + +[models\mapobjects\xlab] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\xlab\tanks.md3 +file1=<>\WolfPack\models\mapobjects\xlab\shockchair.md3 +file2=<>\WolfPack\models\mapobjects\xlab\cart.md3 + +[models\mapobjects\xlab_props] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\xlab_props\xlight2.md3 +file1=<>\WolfPack\models\mapobjects\xlab_props\xlab_refer_insert.md3 +file2=<>\WolfPack\models\mapobjects\xlab_props\xlab_refer_insert2.md3 +file3=<>\WolfPack\models\mapobjects\xlab_props\xlight.md3 +file4=<>\WolfPack\models\mapobjects\xlab_props\xlight_dwn.md3 +file5=<>\WolfPack\models\mapobjects\xlab_props\xlight_dwn2.md3 +file6=<>\WolfPack\models\mapobjects\xlab_props\xlight_up.md3 +file7=<>\WolfPack\models\mapobjects\xlab_props\xlight_up2.md3 +file8=<>\WolfPack\models\mapobjects\xlab_props\xlab_refer.md3 + +[models\mapobjects\zemph] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\zemph\zemph.md3 + +[models\mapobjects\zombie] +fulldirectory= +file0=<>\WolfPack\models\mapobjects\zombie\zombie.md3 + diff --git a/setup/win32/template/File Groups/shaderlist-ta.fgl b/setup/win32/template/File Groups/shaderlist-ta.fgl new file mode 100644 index 00000000..2a484cb3 --- /dev/null +++ b/setup/win32/template/File Groups/shaderlist-ta.fgl @@ -0,0 +1,11 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=scripts + +[scripts] +fulldirectory= +file0=<>\GtkRadiant\setup\data\missionpack\scripts\shaderlist.txt + diff --git a/setup/win32/template/File Groups/shaderlist.fgl b/setup/win32/template/File Groups/shaderlist.fgl new file mode 100644 index 00000000..7c4dc32e --- /dev/null +++ b/setup/win32/template/File Groups/shaderlist.fgl @@ -0,0 +1,11 @@ +[General] +Type=FILELIST +Version=1.10.000 + +[TopDir] +SubDir0=scripts + +[scripts] +fulldirectory= +file0=<>\GtkRadiant\setup\data\baseq3\scripts\shaderlist.txt + diff --git a/setup/win32/template/GtkRadiant.ipr b/setup/win32/template/GtkRadiant.ipr new file mode 100644 index 00000000..b487b905 --- /dev/null +++ b/setup/win32/template/GtkRadiant.ipr @@ -0,0 +1,59 @@ +[Data] +CurrentMedia=GtkRadiant +CurrentComponentDef=Default.cdf +CurrentFileGroupDef=Default.fdf +CurrentLanguage=English +CurrentPlatform= +InstallRoot=C:\home\Id\GtkRadiant\setup\win32\WorkDir +CompanyName=QERadiant.com +HomeURL= +EmailAddresss= +Copyright= +SummaryText= +Author= +Department= +ProductName=GtkRadiant +Version=1.1-TA +InstallationGUID=<> +DevEnvironment= +Type= +Category= +AppExe=C:\quake3\GtkRadiant\q3radiant.exe +Description=Description.txt +Notes=Notes.txt +Instructions=Instructions.txt +set_level=Level 3 +set_warnaserr=No +set_maxwarn=50 +set_maxerr=50 +set_preproc= +set_compileb4build=Yes +set_args= +set_testmode=No +set_dlldebug=No +set_dllcmdline= +set_mif=No +set_miffile=Status.mif +set_mifserial= +set_libraries=isrt.obl ifx.obl +set_linkpath= +set_inline_debug_info=No + +[OperatingSystem] +OSSupport=00000000001100d0 + +[General] +Type=INSTALLMAIN +Version=2.20.000 + +[MediaInfo] +mediadata0=GtkRadiant/Media\GtkRadiant + +[DepManager] +Application=C:\quake3\GtkRadiant\q3radiant.exe +CommandLine= +StartInFolder=C:\quake3\GtkRadiant +Mode=0 +LogResults=Yes +Component=Default + diff --git a/setup/win32/template/Media/GtkRadiant/Default.mda b/setup/win32/template/Media/GtkRadiant/Default.mda new file mode 100644 index 00000000..c9b9c212 --- /dev/null +++ b/setup/win32/template/Media/GtkRadiant/Default.mda @@ -0,0 +1,66 @@ +[GeneralInfo] +Name=GtkRadiant +MediaType=Single +BuildType=Full +BuildSize= +Password= +GUID=f93c6af5-8487-46e7-90bb-e75f2d377eee +BuildFolder=\Media\GtkRadiant + +[SetupInfo] +ApplicationName= +EnableLangDlg=No + +[Platforms] +key0=0000000000000010 +key1=0000000000000040 +key2=0000000000000080 +key3=0000000000010000 +key4=0000000000100000 + +[Languages] +key0=0009 + +[Filter] +LanguageDefault=0009 + +[TagFileInfo] +Version=1.20.000 + +[Internet] +WebPage= +CreateDefaultWebPage=Yes + +[PostBuild] +FTPSite= +FTPFolder= +FTPUser= +FTPPassword= +Folder= +Batch File= + +[DataAsFilesComponents] +Program Files=No +Sample Files=No +Sample Files\Maps, Models and Textures=No +Sample Files\Scripts=No +Help Files=No +Help Files\Radiant Manual=No +Help Files\Shader Manual=No +Help Files\Terrain Manual=No +Help Files\Model Manual=No +Help Files\TA Help=No +Help Files\TexTool Help=No +Plugins=No +Plugins\TexTool=No +Plugins\PrtView=No +Plugins\GenSurf=No + +[Package] +Create=Yes +Welcome=No +File=GtkRadiant.exe +CommandLine= +ExtractFolder= +Password= + diff --git a/setup/win32/template/Registry Entries/Default.rge b/setup/win32/template/Registry Entries/Default.rge new file mode 100644 index 00000000..a0a672a2 --- /dev/null +++ b/setup/win32/template/Registry Entries/Default.rge @@ -0,0 +1,4 @@ +[General] +Type=REGISTRYDATA +Version=2.10.000 + diff --git a/setup/win32/template/Script Files/Setup.map b/setup/win32/template/Script Files/Setup.map new file mode 100644 index 00000000..1b92b267 --- /dev/null +++ b/setup/win32/template/Script Files/Setup.map @@ -0,0 +1,556 @@ +***** External Functions ***** +1 AppSearch isrt.obl(Driver.obs) +2 CCPSearch isrt.obl(Driver.obs) +3 CloseFile isrt.obl(Files.obs) +4 CmdGetHwndDlg isrt.obl(CustomDlg.obs) +5 ComponentCompareSizeRequired isrt.obl(component.obs) +6 ComponentErrorInfo isrt.obl(component.obs) +7 ComponentFilterLanguage isrt.obl(component.obs) +8 ComponentFilterOS isrt.obl(component.obs) +9 ComponentGetData isrt.obl(component.obs) +10 ComponentGetTotalCost isrt.obl(component.obs) +11 ComponentIsItemSelected isrt.obl(component.obs) +12 ComponentListItems isrt.obl(component.obs) +13 ComponentReinstall isrt.obl(component.obs) +14 ComponentRemoveAll isrt.obl(component.obs) +15 ComponentSaveTarget isrt.obl(component.obs) +16 ComponentSelectItem isrt.obl(component.obs) +17 ComponentSetTarget isrt.obl(component.obs) +18 ComponentSetupTypeEnum isrt.obl(component.obs) +19 ComponentSetupTypeGetData isrt.obl(component.obs) +20 ComponentSetupTypeSet isrt.obl(component.obs) +21 ComponentTransferData isrt.obl(component.obs) +22 ComponentTreeInit isrt.obl(Dialogs.obs) +23 CreateDir isrt.obl(Files.obs) +24 CreateFile isrt.obl(Files.obs) +25 CreateInstallationInfo isrt.obl(Registry.obs) +26 CtrlGetCurSel isrt.obl(CustomDlg.obs) +27 CtrlGetNotificationCode isrt.obl(CustomDlg.obs) +28 CtrlGetState isrt.obl(CustomDlg.obs) +29 CtrlGetSubCommand isrt.obl(CustomDlg.obs) +30 CtrlGetText isrt.obl(CustomDlg.obs) +31 CtrlPGroups isrt.obl(CustomDlg.obs) +32 CtrlSelectText isrt.obl(CustomDlg.obs) +33 CtrlSetCurSel isrt.obl(CustomDlg.obs) +34 CtrlSetList isrt.obl(CustomDlg.obs) +35 CtrlSetMLEText isrt.obl(CustomDlg.obs) +36 CtrlSetState isrt.obl(CustomDlg.obs) +37 CtrlSetText isrt.obl(CustomDlg.obs) +38 DefineDialog isrt.obl(CustomDlg.obs) +39 DeleteFile isrt.obl(Files.obs) +40 DialogSetDefaultFont isrt.obl(Dialogs.obs) +41 DialogSetFont isrt.obl(Dialogs.obs) +42 DialogSetInfo isrt.obl(Dialogs.obs) +43 Disable isrt.obl(Enable.obs) +44 DiskSizeToStr isrt.obl(SDINT.obs) +45 Divide isrt.obl(Misc.obs) +46 DllSizeOf isrt.obl(Str.obs) +47 Do isrt.obl(Misc.obs) +48 Enable isrt.obl(Enable.obs) +49 EndDialog isrt.obl(CustomDlg.obs) +50 EnterDisk isrt.obl(EnterDisk.obs) +51 ExistsDir isrt.obl(Files.obs) +52 ExistsDisk isrt.obl(Files.obs) +53 ExitInstall isrt.obl(Driver.obs) +54 EzDefineDialog isrt.obl(CustomDlg.obs) +55 FinalConstruct ifx.obl(Events.obs) +56 FinalRelease ifx.obl(Events.obs) +57 FindFile isrt.obl(Files.obs) +58 FormatMessage isrt.obl(Misc.obs) +59 GDI32.CreateRectRgn isrt.obl(AskDestPath.obs) +60 GDI32.GetDeviceCaps isrt.obl(AskDestPath.obs) +61 GetBillboard isrt.obl(SysVars.obs) +62 GetDialogTitle isrt.obl(Dialogs.obs) +63 GetDir isrt.obl(Str.obs) +64 GetDisk isrt.obl(Str.obs) +65 GetDiskSpace isrt.obl(GSI.obs) +66 GetDiskSpaceEx isrt.obl(GSI.obs) +67 GetErrorMsg isrt.obl(Dialogs.obs) +68 GetErrorTitle isrt.obl(Dialogs.obs) +69 GetExtents isrt.obl(GSI.obs) +70 GetFolderNameList isrt.obl(Shell.obs) +71 GetFolderPrograms isrt.obl(SysVars.obs) +72 GetISRes isrt.obl(SysVars.obs) +73 GetLine isrt.obl(Files.obs) +74 GetLog isrt.obl(SysVars.obs) +75 GetMaintenance isrt.obl(SysVars.obs) +76 GetOpenFileMode isrt.obl(Files.obs) +77 GetProductGUID isrt.obl(SysVars.obs) +78 GetProfString isrt.obl(Profile.obs) +79 GetProgramFiles isrt.obl(SysVars.obs) +80 GetSelectedLanguage isrt.obl(SysVars.obs) +81 GetSelectedTreeComponent isrt.obl(CmptView.obs) +82 GetSupportDir isrt.obl(SysVars.obs) +83 GetSystemInfo isrt.obl(GSI.obs) +84 GetWinDir isrt.obl(SysVars.obs) +85 GetWinSysDir isrt.obl(SysVars.obs) +86 GetWindowHandle isrt.obl(Misc.obs) +87 HIWORD isrt.obl(Misc.obs) +88 HexStrToNum isrt.obl(Str.obs) +89 ISDeterminePlatform isrt.obl(OsInfo.obs) +90 ISMIF32.InstallStatusMIF isrt.obl(MIF.obs) +91 ISRT.ComponentViewSetInfoEx isrt.obl(Dialogs.obs) +92 ISRT.EnableHourGlass isrt.obl(Enable.obs) +93 ISRT.EnumFoldersItems isrt.obl(Shell.obs) +94 ISRT.GetCPUType isrt.obl(GSI.obs) +95 ISRT.GetFontSub isrt.obl(Dialogs.obs) +96 ISRT.GetHandle Setup.Obs +97 ISRT.GetPorts isrt.obl(GSI.obs) +98 ISRT.IsEmpty Setup.Obs +99 ISRT.IsNTAdmin isrt.obl(Is.obs) +100 ISRT.IsObject Setup.Obs +101 ISRT.LangLoadString isrt.obl(SDINT.obs) +102 ISRT.MessageBeepP Setup.Obs +103 ISRT.PathCompactPathPixel isrt.obl(CustomDlg.obs) +104 ISRT.PathGetDir isrt.obl(Str.obs) +105 ISRT.PathGetDrive isrt.obl(Str.obs) +106 ISRT.PathGetFile isrt.obl(Str.obs) +107 ISRT.PathGetFileExt isrt.obl(Str.obs) +108 ISRT.PathGetFileName isrt.obl(Str.obs) +109 ISRT.PathGetPath isrt.obl(Str.obs) +110 ISRT.PathGetSpecialFolder isrt.obl(SysVars.obs) +111 ISRT.PathIsValidSyntax isrt.obl(Is.obs) +112 ISRT._AppSearch Setup.Obs +113 ISRT._BrowseForFolder isrt.obl(SelectDir.obs) +114 ISRT._CCPSearch Setup.Obs +115 ISRT._CleanupInet isrt.obl(MIO.obs) +116 ISRT._CloseFile isrt.obl(Files.obs) +117 ISRT._CmdGetHwndDlg isrt.obl(CustomDlg.obs) +118 ISRT._ComponentCompareSizeRequired isrt.obl(component.obs) +119 ISRT._ComponentErrorInfo isrt.obl(component.obs) +120 ISRT._ComponentFilterLanguage isrt.obl(component.obs) +121 ISRT._ComponentFilterOS isrt.obl(component.obs) +122 ISRT._ComponentGetData isrt.obl(component.obs) +123 ISRT._ComponentGetTotalCost isrt.obl(component.obs) +124 ISRT._ComponentIsItemSelected isrt.obl(component.obs) +125 ISRT._ComponentListItems isrt.obl(component.obs) +126 ISRT._ComponentReinstall isrt.obl(component.obs) +127 ISRT._ComponentRemoveAll isrt.obl(component.obs) +128 ISRT._ComponentSaveTarget isrt.obl(component.obs) +129 ISRT._ComponentSelectItem isrt.obl(component.obs) +130 ISRT._ComponentSetupTypeEnum isrt.obl(component.obs) +131 ISRT._ComponentSetupTypeGetData isrt.obl(component.obs) +132 ISRT._ComponentSetupTypeSet isrt.obl(component.obs) +133 ISRT._ComponentTransferData isrt.obl(component.obs) +134 ISRT._CreateDir isrt.obl(Files.obs) +135 ISRT._CtrlGetNotificationCode isrt.obl(CustomDlg.obs) +136 ISRT._CtrlGetSubCommand isrt.obl(CustomDlg.obs) +137 ISRT._DefineDialog isrt.obl(CustomDlg.obs) +138 ISRT._DialogSetFont isrt.obl(Dialogs.obs) +139 ISRT._DisableStatus isrt.obl(Enable.obs) +140 ISRT._Divide isrt.obl(Misc.obs) +141 ISRT._DoSprintf isrt.obl(MsgBox.obs) +142 ISRT._EnableDialogCache isrt.obl(Enable.obs) +143 ISRT._EnablePrevDialog isrt.obl(AskDestPath.obs) +144 ISRT._EnableStatus isrt.obl(Enable.obs) +145 ISRT._EndDialog isrt.obl(CustomDlg.obs) +146 ISRT._ExistsDir isrt.obl(Files.obs) +147 ISRT._ExistsDisk isrt.obl(Files.obs) +148 ISRT._ExistsFile isrt.obl(Files.obs) +149 ISRT._ExitInstall Setup.Obs +150 ISRT._GetDiskSpaceEx isrt.obl(GSI.obs) +151 ISRT._GetLine isrt.obl(Files.obs) +152 ISRT._GetSelectedTreeComponent isrt.obl(CmptView.obs) +153 ISRT._GetSupportDir isrt.obl(SysVars.obs) +154 ISRT._InetEndofTransfer isrt.obl(iftw.obs) +155 ISRT._InetGetLastError isrt.obl(iftw.obs) +156 ISRT._InetGetNextDisk isrt.obl(iftw.obs) +157 ISRT._InitInstall Setup.Obs +158 ISRT._ListAddItem isrt.obl(scrlist.obs) +159 ISRT._ListAddString isrt.obl(scrlist.obs) +160 ISRT._ListCount isrt.obl(scrlist.obs) +161 ISRT._ListCreate isrt.obl(scrlist.obs) +162 ISRT._ListCurrentString isrt.obl(scrlist.obs) +163 ISRT._ListDeleteString isrt.obl(scrlist.obs) +164 ISRT._ListDestroy isrt.obl(scrlist.obs) +165 ISRT._ListFindString isrt.obl(scrlist.obs) +166 ISRT._ListGetFirstItem isrt.obl(scrlist.obs) +167 ISRT._ListGetFirstString isrt.obl(scrlist.obs) +168 ISRT._ListGetNextItem isrt.obl(scrlist.obs) +169 ISRT._ListGetNextString isrt.obl(scrlist.obs) +170 ISRT._ListGetType isrt.obl(scrlist.obs) +171 ISRT._ListReadFromFile isrt.obl(scrlist.obs) +172 ISRT._ListSetIndex isrt.obl(scrlist.obs) +173 ISRT._OpenFile isrt.obl(Files.obs) +174 ISRT._Rebooted Setup.Obs +175 ISRT._RegCreateKey isrt.obl(Registry.obs) +176 ISRT._RegQueryKeyBinaryValue isrt.obl(Registry.obs) +177 ISRT._RegQueryKeyValue isrt.obl(Registry.obs) +178 ISRT._RegSetKeyBinaryValue isrt.obl(Registry.obs) +179 ISRT._RegSetKeyValue isrt.obl(Registry.obs) +180 ISRT._ReleaseDialog isrt.obl(CustomDlg.obs) +181 ISRT._SetAltMainImage isrt.obl(Dialogs.obs) +182 ISRT._SetDisplayEffect isrt.obl(ui.obs) +183 ISRT._SetPaletteFile isrt.obl(MIO.obs) +184 ISRT._SetTitle isrt.obl(ui.obs) +185 ISRT._SetupInet isrt.obl(MIO.obs) +186 ISRT._ShowObjWizardPages isrt.obl(Objects.obs) +187 ISRT._ShowWizardPages Setup.Obs +188 ISRT._StatusUpdate isrt.obl(ui.obs) +189 ISRT._TreeViewCreate isrt.obl(CmptView.obs) +190 ISRT._WaitOnDialog isrt.obl(CustomDlg.obs) +191 ISRT._WriteLine isrt.obl(Files.obs) +192 ISRT.__CreateObjectContext isrt.obl(ISRTInit.obs) +193 ISRT.__GetCmdLineOptions isrt.obl(ISRTInit.obs) +194 ISRT.__GetContextGUID isrt.obl(ISRTInit.obs) +195 ISRT.__GetFileRegistrar isrt.obl(ISRTInit.obs) +196 ISRT.__GetInfo isrt.obl(ISRTInit.obs) +197 ISRT.__GetLog isrt.obl(SysVars.obs) +198 ISRT.__GetLogDB isrt.obl(ISRTInit.obs) +199 ISRT.__GetMainWindow Setup.Obs +200 ISRT.__GetMaintenanceMode isrt.obl(SysVars.obs) +201 ISRT.__GetProductGuid isrt.obl(SysVars.obs) +202 ISRT.__GetProgress Setup.Obs +203 ISRT.__GetReboot isrt.obl(ISRTInit.obs) +204 ISRT.__GetTextSub isrt.obl(ISRTInit.obs) +205 ISRT.__GetUser isrt.obl(MIO.obs) +206 ISRT.__ISRTGetPropertyBag ifx.obl(PersistPropertyBag.obs) +207 ISRT.__ISRTReleasePropertyBag ifx.obl(PersistPropertyBag.obs) +208 ISRT.__LoadString isrt.obl(LoadStr.obs) +209 ISRT.__ReleaseObjectContext isrt.obl(ISRTInit.obs) +210 ISRT.__RestoreMainLog isrt.obl(ISRTInit.obs) +211 ISRT.__SetComponentLog isrt.obl(ISRTInit.obs) +212 IfxFilterComponents ifx.obl(MoveData.obs) +213 IfxFinalConstruct ifx.obl(EventsMIO.obs) +214 IfxFinalRelease ifx.obl(EventsMIO.obs) +215 IfxInitProperties ifx.obl(PersistPropertyBag.obs) +216 IfxMoveFileData ifx.obl(MoveData.obs) +217 IfxOnAbortInstall ifx.obl(Events.obs) +218 IfxOnAppSearch ifx.obl(Events.obs) +219 IfxOnCCPSearch ifx.obl(Events.obs) +220 IfxOnCanceling ifx.obl(Events.obs) +221 IfxOnDisk1Installed ifx.obl(EventsMIO.obs) +222 IfxOnDisk1Installing ifx.obl(EventsMIO.obs) +223 IfxOnExitInstall ifx.obl(EventsMIO.obs) +224 IfxOnFileError ifx.obl(Exceptions.obs) +225 IfxOnFileLocked ifx.obl(Exceptions.obs) +226 IfxOnFileReadOnly ifx.obl(Exceptions.obs) +227 IfxOnHelp ifx.obl(Events.obs) +228 IfxOnInitInstall ifx.obl(EventsMIO.obs) +229 IfxOnInternetError ifx.obl(Exceptions.obs) +230 IfxOnMD5Error ifx.obl(Exceptions.obs) +231 IfxOnNextDisk ifx.obl(Exceptions.obs) +232 IfxOnRebooted ifx.obl(Events.obs) +233 IfxOnRemovingSharedFile ifx.obl(Exceptions.obs) +234 IfxOnShowWizardPages ifx.obl(UserInterfaceMIO.obs) +235 IfxOnTransferred ifx.obl(Events.obs) +236 IfxOnTransferring ifx.obl(EventsMIO.obs) +237 IfxOnUnhandledException ifx.obl(Events.obs) +238 IfxReadProperties ifx.obl(PersistPropertyBag.obs) +239 IfxRunAfterReboot ifx.obl(Driver.obs) +240 IfxWriteProperties ifx.obl(PersistPropertyBag.obs) +241 InetEndofTransfer isrt.obl(iftw.obs) +242 InetErrorDisplayMode isrt.obl(iftw.obs) +243 InetGetLastError isrt.obl(iftw.obs) +244 InetNextDisk isrt.obl(iftw.obs) +245 InitInstall isrt.obl(Driver.obs) +246 InitProperties ifx.obl(PersistPropertyBag.obs) +247 Is isrt.obl(Is.obs) +248 IsInetInstall isrt.obl(iftw.obs) +249 KERNEL.GetModuleHandle Setup.Obs +250 KERNEL32.CloseHandle isrt.obl(AskDestPath.obs) +251 KERNEL32.CreateFileA isrt.obl(AskDestPath.obs) +252 KERNEL32.DeleteFileA isrt.obl(AskDestPath.obs) +253 KERNEL32.FindClose isrt.obl(AskDestPath.obs) +254 KERNEL32.FindFirstFileA isrt.obl(AskDestPath.obs) +255 KERNEL32.FindNextFileA isrt.obl(AskDestPath.obs) +256 KERNEL32.FormatMessageA isrt.obl(AskDestPath.obs) +257 KERNEL32.GetDriveType isrt.obl(AskDestPath.obs) +258 KERNEL32.GetFileAttributesA isrt.obl(AskDestPath.obs) +259 KERNEL32.GetLocalTime isrt.obl(AskDestPath.obs) +260 KERNEL32.GetLocaleInfo isrt.obl(AskDestPath.obs) +261 KERNEL32.GetPrivateProfileString isrt.obl(AskDestPath.obs) +262 KERNEL32.GetPrivateProfileStringA isrt.obl(AskDestPath.obs) +263 KERNEL32.GetProfileStringA isrt.obl(AskDestPath.obs) +264 KERNEL32.GetShortPathNameA isrt.obl(AskDestPath.obs) +265 KERNEL32.GetSystemDefaultLCID isrt.obl(AskDestPath.obs) +266 KERNEL32.GetUserDefaultLangID isrt.obl(AskDestPath.obs) +267 KERNEL32.GetVersion isrt.obl(AskDestPath.obs) +268 KERNEL32.GetVersionEx isrt.obl(AskDestPath.obs) +269 KERNEL32.GetVolumeInformation isrt.obl(AskDestPath.obs) +270 KERNEL32.GetWindowsDirectory isrt.obl(AskDestPath.obs) +271 KERNEL32.GlobalMemoryStatus isrt.obl(AskDestPath.obs) +272 KERNEL32.SetFileAttributesA isrt.obl(AskDestPath.obs) +273 KERNEL32.WritePrivateProfileString isrt.obl(AskDestPath.obs) +274 KERNEL32.WritePrivateProfileStringA isrt.obl(AskDestPath.obs) +275 KERNEL32.WriteProfileStringA isrt.obl(AskDestPath.obs) +276 KERNEL32.lstrlen isrt.obl(AskDestPath.obs) +277 LOWORD isrt.obl(Misc.obs) +278 ListAddItem isrt.obl(scrlist.obs) +279 ListAddString isrt.obl(scrlist.obs) +280 ListCount isrt.obl(scrlist.obs) +281 ListCreate isrt.obl(scrlist.obs) +282 ListCurrentString isrt.obl(scrlist.obs) +283 ListDeleteString isrt.obl(scrlist.obs) +284 ListDestroy isrt.obl(scrlist.obs) +285 ListFindString isrt.obl(scrlist.obs) +286 ListGetFirstItem isrt.obl(scrlist.obs) +287 ListGetFirstString isrt.obl(scrlist.obs) +288 ListGetNextItem isrt.obl(scrlist.obs) +289 ListGetNextString isrt.obl(scrlist.obs) +290 ListGetType isrt.obl(scrlist.obs) +291 ListReadFromFile isrt.obl(scrlist.obs) +292 ListSetIndex isrt.obl(scrlist.obs) +293 LongPathToQuote isrt.obl(Str.obs) +294 LongPathToShortPath isrt.obl(Str.obs) +295 MAKELONG isrt.obl(Misc.obs) +296 MIFCreateMIFFile isrt.obl(MIF.obs) +297 MIFDeleteMIFFile isrt.obl(MIF.obs) +298 MIFInitialize isrt.obl(MIF.obs) +299 MIFSetInformation isrt.obl(MIF.obs) +300 MIFUnInitialize isrt.obl(MIF.obs) +301 MIFWasSetInformationCalled isrt.obl(MIF.obs) +302 MIOShutdown isrt.obl(MIO.obs) +303 MIOStartup isrt.obl(MIO.obs) +304 MaintenanceStart isrt.obl(Registry.obs) +305 MessageBeep isrt.obl(Misc.obs) +306 MessageBox isrt.obl(MsgBox.obs) +307 OnAbort ifx.obl(Events.obs) +308 OnAppSearch ifx.obl(Events.obs) +309 OnBegin ifx.obl(Events.obs) +310 OnCCPSearch ifx.obl(Events.obs) +311 OnCanceling ifx.obl(EventsMIO.obs) +312 OnComponentError ifx.obl(Exceptions.obs) +313 OnEnd ifx.obl(Events.obs) +314 OnFileError ifx.obl(Exceptions.obs) +315 OnFileLocked ifx.obl(Exceptions.obs) +316 OnFileReadOnly ifx.obl(Exceptions.obs) +317 OnFirstUIAfter ifx.obl(UserInterfaceMIO.obs) +318 OnFirstUIBefore Setup.Obs +319 OnHelp ifx.obl(Events.obs) +320 OnInternetError ifx.obl(Exceptions.obs) +321 OnMD5Error ifx.obl(Exceptions.obs) +322 OnMaintUIAfter ifx.obl(UserInterfaceMIO.obs) +323 OnMaintUIBefore ifx.obl(UserInterfaceMIO.obs) +324 OnMoved Setup.Obs +325 OnMoving Setup.Obs +326 OnNextDisk ifx.obl(Exceptions.obs) +327 OnRebooted ifx.obl(Events.obs) +328 OnRemovingSharedFile ifx.obl(Exceptions.obs) +329 OnSelfRegistrationError ifx.obl(Events.obs) +330 OnUnhandledException ifx.obl(Events.obs) +331 OpenFile isrt.obl(Files.obs) +332 OpenFileMode isrt.obl(Files.obs) +333 ParsePath isrt.obl(Str.obs) +334 ProgDefGroupType isrt.obl(Shell.obs) +335 ProgGetGroupType isrt.obl(Shell.obs) +336 PthFixPath isrt.obl(Pth.obs) +337 PthIsAbsPath isrt.obl(Pth.obs) +338 ReadProperties ifx.obl(PersistPropertyBag.obs) +339 Rebooted isrt.obl(Driver.obs) +340 RegDBGetKeyValueEx isrt.obl(Registry.obs) +341 RegDBInit isrt.obl(Registry.obs) +342 RegDBQueryValue isrt.obl(Registry.obs) +343 RegDBSetDefaultRoot isrt.obl(Registry.obs) +344 RegDBSetItem isrt.obl(Registry.obs) +345 RegDBSetValue isrt.obl(Registry.obs) +346 ReleaseDialog isrt.obl(CustomDlg.obs) +347 SdAskDestPath isrt.obl(SDADPATH.obs) +348 SdCloseDlg isrt.obl(SDINT.obs) +349 SdComponentDlgCheckSpace isrt.obl(SDINT.obs) +350 SdComponentTree isrt.obl(SDCOMTREE.obs) +351 SdDlgToTop isrt.obl(SDINT.obs) +352 SdDoStdButton isrt.obl(SDINT.obs) +353 SdEnablement isrt.obl(SDINT.obs) +354 SdError isrt.obl(SDINT.obs) +355 SdExceptions isrt.obl(SdExceptions.obs) +356 SdFinish isrt.obl(SDFINISH.obs) +357 SdFinishEx isrt.obl(SdFinishEx.obs) +358 SdFinishReboot isrt.obl(SDFINBOT.obs) +359 SdGeneralInit isrt.obl(SDINT.obs) +360 SdInit isrt.obl(SDINT.obs) +361 SdIsShellExplorer isrt.obl(SDINT.obs) +362 SdIsStdButton isrt.obl(SDINT.obs) +363 SdLicense isrt.obl(SDLIC.obs) +364 SdLoadString isrt.obl(SDINT.obs) +365 SdMakeName isrt.obl(SDINT.obs) +366 SdPlugInProductName isrt.obl(SDINT.obs) +367 SdProductName isrt.obl(SDPRODCT.obs) +368 SdRemoveEndSpace isrt.obl(SDINT.obs) +369 SdSelectFolder isrt.obl(SDSFDR.obs) +370 SdSetDlgTitle isrt.obl(SDINT.obs) +371 SdSetStatic isrt.obl(SDINT.obs) +372 SdSetupTypeEx isrt.obl(Sdstypex.obs) +373 SdShowDlgEdit1 isrt.obl(SDSEDT1.obs) +374 SdShowInfoList isrt.obl(SDSINFOL.obs) +375 SdStartCopy isrt.obl(SDSCOPY.obs) +376 SdUnInit isrt.obl(SDINT.obs) +377 SdVerifyFolder isrt.obl(SDINT.obs) +378 SdWelcome isrt.obl(SDWEL.obs) +379 SdWelcomeMaint isrt.obl(SdWelcomeMaint.obs) +380 SelectDir isrt.obl(SelectDir.obs) +381 SelectDirNoLog isrt.obl(SelectDir.obs) +382 SendMessage isrt.obl(Misc.obs) +383 SetCmdLine isrt.obl(SysVars.obs) +384 SetDialogTitle isrt.obl(Dialogs.obs) +385 SetDisplayEffect isrt.obl(ui.obs) +386 SetFolderDesktop isrt.obl(SysVars.obs) +387 SetFolderPrograms isrt.obl(SysVars.obs) +388 SetFolderStartMenu isrt.obl(SysVars.obs) +389 SetFolderStartup isrt.obl(SysVars.obs) +390 SetInstallationInfo isrt.obl(Registry.obs) +391 SetStatusWindow isrt.obl(ui.obs) +392 SetTitle isrt.obl(ui.obs) +393 ShowObjWizardPages isrt.obl(Objects.obs) +394 ShowWizardPages isrt.obl(Driver.obs) +395 SilentCreateFile isrt.obl(Silent.obs) +396 SilentDoGeneralInfo isrt.obl(Silent.obs) +397 SilentFailed isrt.obl(Silent.obs) +398 SilentFinish isrt.obl(Silent.obs) +399 SilentGetMode isrt.obl(Silent.obs) +400 SilentInit isrt.obl(Silent.obs) +401 SilentLogWriteData isrt.obl(Silent.obs) +402 SilentReadData isrt.obl(Silent.obs) +403 SilentSetInfo isrt.obl(Silent.obs) +404 SilentSetMode isrt.obl(Silent.obs) +405 SilentWriteData isrt.obl(Silent.obs) +406 Sprintf isrt.obl(Str.obs) +407 SprintfBox isrt.obl(MsgBox.obs) +408 StatusUpdate isrt.obl(ui.obs) +409 StrGetTokens isrt.obl(Str.obs) +410 StrLength isrt.obl(Str.obs) +411 StrRemoveLastSlash isrt.obl(Str.obs) +412 StrRemoveSpaces isrt.obl(Str.obs) +413 StrToUpper isrt.obl(Str.obs) +414 SysVarsInit isrt.obl(SysVars.obs) +415 SysVarsUnInit isrt.obl(SysVars.obs) +416 System isrt.obl(Misc.obs) +417 TreeViewCreate isrt.obl(CmptView.obs) +418 USER.EnableWindow Setup.Obs +419 USER.GetClassName Setup.Obs +420 USER.GetClientRect Setup.Obs +421 USER.GetDC Setup.Obs +422 USER.GetDlgItem Setup.Obs +423 USER.GetFocus Setup.Obs +424 USER.GetWindowLong Setup.Obs +425 USER.GetWindowRect Setup.Obs +426 USER.IsIconic Setup.Obs +427 USER.IsWindow Setup.Obs +428 USER.IsWindowVisible isrt.obl(AskDestPath.obs) +429 USER.MoveWindow Setup.Obs +430 USER.ReleaseDC Setup.Obs +431 USER.SendMessageA isrt.obl(AskDestPath.obs) +432 USER.SetFocus Setup.Obs +433 USER.SetForegroundWindow isrt.obl(AskDestPath.obs) +434 USER.SetWindowPos Setup.Obs +435 USER.SetWindowText Setup.Obs +436 USER.ShowWindow Setup.Obs +437 USER32.CharUpperBuffA isrt.obl(AskDestPath.obs) +438 USER32.DrawText isrt.obl(AskDestPath.obs) +439 USER32.GetSystemMetrics isrt.obl(AskDestPath.obs) +440 USER32.GetWindowText isrt.obl(AskDestPath.obs) +441 USER32.GetWindowTextLength isrt.obl(AskDestPath.obs) +442 USER32.MessageBoxA isrt.obl(AskDestPath.obs) +443 USER32.ScreenToClient isrt.obl(AskDestPath.obs) +444 USER32.SendMessageA isrt.obl(AskDestPath.obs) +445 USER32.SetWindowLong isrt.obl(AskDestPath.obs) +446 USER32.SetWindowRgn isrt.obl(AskDestPath.obs) +447 VarRestore isrt.obl(SysVars.obs) +448 VarSave isrt.obl(SysVars.obs) +449 WaitOnDialog isrt.obl(CustomDlg.obs) +450 WriteLine isrt.obl(Files.obs) +451 WriteProfString isrt.obl(Profile.obs) +452 WriteProperties ifx.obl(PersistPropertyBag.obs) +453 _HandleException isrt.obl(exceptions.obs) +454 _WinSubEnableControl isrt.obl(WINSUB.obs) +455 _WinSubEnableWindow isrt.obl(WINSUB.obs) +456 _WinSubGetChildWindow isrt.obl(WINSUB.obs) +457 _WinSubIsWindow isrt.obl(WINSUB.obs) +458 __DefaultExitHandler isrt.obl(handlers.obs) +459 __ISRTDoExit isrt.obl(Misc.obs) +460 __ISRTInit isrt.obl(ISRTInit.obs) +461 __ISRTRestoreMainLog isrt.obl(ISRTInit.obs) +462 __ISRTSetComponentLog isrt.obl(ISRTInit.obs) +463 __ISRTUnInit isrt.obl(ISRTInit.obs) +464 __LoadIScriptString isrt.obl(LoadStr.obs) +465 program ifx.obl(Driver.obs) +466 sfc.SRSetRestorePoint Setup.Obs + + +***** External Variables ***** +1 BATCH_INSTALL Setup.Obs +2 CMDLINE isrt.obl(DebugSymbols.obs) +3 COMMONFILES isrt.obl(DebugSymbols.obs) +4 DISK1TARGET Setup.Obs +5 ERRORFILENAME Setup.Obs +6 Err Setup.Obs +7 FILETIME isrt.obl(AskDestPath.obs) +8 FIND_DATA isrt.obl(AskDestPath.obs) +9 FOLDER_DESKTOP isrt.obl(DebugSymbols.obs) +10 FOLDER_PROGRAMS isrt.obl(DebugSymbols.obs) +11 FOLDER_STARTMENU isrt.obl(DebugSymbols.obs) +12 FOLDER_STARTUP isrt.obl(DebugSymbols.obs) +13 FileRegistrar Setup.Obs +14 ISRES isrt.obl(DebugSymbols.obs) +15 ISUSER isrt.obl(DebugSymbols.obs) +16 ISVERSION isrt.obl(DebugSymbols.obs) +17 InetSRCDIR Setup.Obs +18 LAST_RESULT Setup.Obs +19 LogDB Setup.Obs +20 MAINTENANCE isrt.obl(DebugSymbols.obs) +21 MEDIA Setup.Obs +22 MEMORYSTATUS isrt.obl(AskDestPath.obs) +23 MODE isrt.obl(DebugSymbols.obs) +24 MainWindow Setup.Obs +25 NOTHING Setup.Obs +26 OSINFO_PLATFORM_INFO Setup.Obs +27 OSVERSIONINFO isrt.obl(AskDestPath.obs) +28 PRODUCT_GUID isrt.obl(DebugSymbols.obs) +29 PROGRAMFILES isrt.obl(DebugSymbols.obs) +30 RECT isrt.obl(AskDestPath.obs) +31 Reboot Setup.Obs +32 SELECTED_LANGUAGE isrt.obl(DebugSymbols.obs) +33 SHELL_OBJECT_FOLDER Setup.Obs +34 SRCDIR Setup.Obs +35 SRCDISK isrt.obl(DebugSymbols.obs) +36 SUPPORTDIR isrt.obl(DebugSymbols.obs) +37 SYSTEMTIME isrt.obl(AskDestPath.obs) +38 SetupInfo Setup.Obs +39 StatusDlg Setup.Obs +40 TARGETDIR Setup.Obs +41 TARGETDISK isrt.obl(DebugSymbols.obs) +42 TextSub Setup.Obs +43 UNINST Setup.Obs +44 UNINSTALL_STRING Setup.Obs +45 WINDIR isrt.obl(DebugSymbols.obs) +46 WINDISK isrt.obl(DebugSymbols.obs) +47 WINSYSDIR isrt.obl(DebugSymbols.obs) +48 WINSYSDISK isrt.obl(DebugSymbols.obs) +49 _INT64 Setup.Obs +50 _RESTOREINFO_ Setup.Obs +51 _RESTOREPTINFO Setup.Obs +52 _SMGRSTATUS Setup.Obs +53 _SMGSTATUS_ Setup.Obs +54 _WIN9X Setup.Obs +55 _WINNT Setup.Obs +56 __SYSTEMINFORMATION Setup.Obs +57 __hContext isrt.obl(AskYesNo.obs) +58 __objGlobalTextSub Setup.Obs +59 __szGUID isrt.obl(ISRTInit.obs) +60 bBackButton Setup.Obs +61 bCancelButton Setup.Obs +62 bEnterDiskBeep isrt.obl(AskDestPath.obs) +63 bFinishButton Setup.Obs +64 bIFXLFNoToAll Setup.Obs +65 bIFXLFOnRebootToAll Setup.Obs +66 bIFXLFYesToAll Setup.Obs +67 bIFXMD5IgnoreToAll Setup.Obs +68 bIFXRONoToAll Setup.Obs +69 bIFXROYesToAll Setup.Obs +70 bIFXSFNoToAll Setup.Obs +71 bIFXSFYesToAll Setup.Obs +72 bIfxPCHInitialized Setup.Obs +73 bIfxPCHOn Setup.Obs +74 bInstall16 Setup.Obs +75 bNextButton Setup.Obs +76 bSdInit Setup.Obs +77 bViewCmptKunits Setup.Obs +78 bViewCmptUseDecimal Setup.Obs +79 g_bSuppressMissingStringWarning isrt.obl(Enable.obs) +80 nIFXROKey ifx.obl(EventsMIO.obs) +81 nIFXSFKey ifx.obl(EventsMIO.obs) +82 nIfxPCHType Setup.Obs +83 szAppKey Setup.Obs +84 szInstallDeinstKey Setup.Obs +85 szSdProduct Setup.Obs +86 szSdStr_NotEnoughSpace Setup.Obs diff --git a/setup/win32/template/Script Files/Setup.rul b/setup/win32/template/Script Files/Setup.rul new file mode 100644 index 00000000..b3a62d70 --- /dev/null +++ b/setup/win32/template/Script Files/Setup.rul @@ -0,0 +1,1307 @@ + +//////////////////////////////////////////////////////////////////////////////// +// +// IIIIIII SSSSSS +// II SS InstallShield (R) +// II SSSSSS (c) 1996-2000, InstallShield Software Corporation +// II SS (c) 1990-1996, InstallShield Corporation +// IIIIIII SSSSSS All Rights Reserved. +// +// +// This code is generated as a starting setup template. You should +// modify it to provide all necessary steps for your setup. +// +// +// File Name: Setup.rul +// +// Description: InstallShield script +// +// Comments: This template script performs a basic setup. With minor +// modifications, this template can be adapted to create +// new, customized setups. +// +//////////////////////////////////////////////////////////////////////////////// + + +// Include header files + +#include "ifx.h" //DO NOT REMOVE + +////////////////////// string defines //////////////////////////// + +//////////////////// installation declarations /////////////////// + +// ---- globals ---- +// we don't need full/non-full information +// only the following are used for UI + +NUMBER DO_CORE_BOOL; +NUMBER DO_NIGHTLY_BOOL; + +// Q3 +NUMBER DO_GAME_Q3_BOOL; +// per-game package global config +STRING szDIR_GAME_Q3, szDIR_GAMETOOLS_Q3; + +// Wolf +NUMBER DO_GAME_WOLF_BOOL; +STRING szDIR_GAME_WOLF, szDIR_GAMETOOLS_WOLF; + +// JKII +NUMBER DO_GAME_JKII_BOOL; +STRING szDIR_GAME_JKII, szDIR_GAMETOOLS_JKII; + +// STVEF +NUMBER DO_GAME_STVEF_BOOL; +STRING szDIR_GAME_STVEF, szDIR_GAMETOOLS_STVEF; + +// SOF2 +NUMBER DO_GAME_SOF2_BOOL; +STRING szDIR_GAME_SOF2, szDIR_GAMETOOLS_SOF2; + +// Halflife +NUMBER DO_GAME_HALFLIFE_BOOL; +NUMBER DO_GAME_CSTRIKE_BOOL; // only used for .game generation +STRING szDIR_GAME_HALFLIFE, szDIR_GAMETOOLS_HALFLIFE; + +// ET +NUMBER DO_GAME_ET_BOOL; +STRING szDIR_GAME_ET, szDIR_GAMETOOLS_ET; + +// Quake +NUMBER DO_GAME_Q1_BOOL; +STRING szDIR_GAME_Q1, szDIR_GAMETOOLS_Q1; + +// JA +NUMBER DO_GAME_JA_BOOL; +STRING szDIR_GAME_JA, szDIR_GAMETOOLS_JA; + +// Q2 +NUMBER DO_GAME_Q2_BOOL; +STRING szDIR_GAME_Q2, szDIR_GAMETOOLS_Q2; + +// HER2 +NUMBER DO_GAME_HER2_BOOL; +STRING szDIR_GAME_HER2, szDIR_GAMETOOLS_HER2; + +// ---- script function prototypes ----- + + +// your script function prototypes +prototype ReadInfoList(LIST); + +function ReadInfoList(svInfoList) + STRING svLine; + NUMBER nFlag, nvFileHandle; + +begin + OpenFileMode(FILE_MODE_NORMAL); + OpenFile(nvFileHandle, SUPPORTDIR, "infolist.txt"); + while (nFlag = 0) + nFlag = GetLine(nvFileHandle, svLine); + ListAddString(svInfoList, svLine, AFTER); + endwhile; + CloseFile(nvFileHandle); +end; + + + +////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION: OnFirstUIBefore +// +// EVENT: FirstUIBefore event is sent when installation is run for the first +// time on given machine. In the handler installation usually displays +// UI allowing end user to specify installation parameters. After this +// function returns, ComponentTransferData is called to perform file +// transfer. +// +/////////////////////////////////////////////////////////////////////////////// +function OnFirstUIBefore() + NUMBER nRegKey,nRegKeySize,nKeyType; + STRING szRegKey, szQuake3, szJKII, szSTVEF, szSOF2, szRTCW, szHL, szCStrike, szET, szQ1, szJA, szQ2, szHER2; + NUMBER nResult,nSetupType; + STRING szTitle, szMsg; + STRING szLicenseFile, szQuestion; + STRING szTargetPath; + STRING szDir, szSubDir; + STRING szfolder; + STRING szComponents, szTargetdir; + STRING svSetupType; + NUMBER nLevel; + LIST listStartCopy, listInfoList; + NUMBER nvSize; + STRING DEFAULTJKIIDIR; + STRING DEFAULTSTVEFDIR; + STRING DEFAULTSOF2DIR; + STRING DEFAULTRTCWDIR; + STRING DEFAULTHALFLIFEDIR; + STRING DEFAULTETDIR; + STRING DEFAULTQ1DIR; + STRING DEFAULTJADIR; +begin + +// config +// some defaults +szSubDir = "Radiant-1.<>"; + +// template +DO_CORE_BOOL = <>; +DO_NIGHTLY_BOOL = <>; +DO_GAME_Q3_BOOL = <>; +DO_GAME_WOLF_BOOL = <>; +DO_GAME_JKII_BOOL = <>; +DO_GAME_STVEF_BOOL = <>; +DO_GAME_SOF2_BOOL = <>; +DO_GAME_HALFLIFE_BOOL = <>; +DO_GAME_ET_BOOL = <>; +DO_GAME_Q1_BOOL = <>; +DO_GAME_JA_BOOL = <>; +DO_GAME_Q2_BOOL = <>; +DO_GAME_HER2_BOOL = <>; +// common stuff + + // TO DO: if you want to enable background, window title, and caption bar title + // SetTitle( @TITLE_MAIN, 24, WHITE ); + // SetTitle( @TITLE_CAPTIONBAR, 0, BACKGROUNDCAPTION ); + // Enable( FULLWINDOWMODE ); + // Enable( BACKGROUND ); + // SetColor(BACKGROUND,RGB (0, 128, 128); + +Dlg_Start: + // beginning of dialogs label + +Dlg_SdWelcome: + szTitle = ""; + szMsg = ""; + nResult = SdWelcome( szTitle, szMsg ); + if (nResult = BACK) goto Dlg_Start; + +Dlg_SdLicense: + szLicenseFile = SUPPORTDIR ^ "license.txt"; + szTitle = ""; + szMsg = ""; + szQuestion = ""; + nResult = SdLicense( szTitle, szMsg, szQuestion, szLicenseFile ); + if (nResult = BACK) goto Dlg_SdWelcome; + +Dlg_SdShowInfoList: + szTitle = "Information\nLatest " + @PRODUCT_NAME + " project information."; + szMsg = ""; + listInfoList = ListCreate( STRINGLIST ); + ReadInfoList(listInfoList); + // sdShowInfoList dialog customised in _IsUser.dll + nResult = SdShowInfoList( szTitle, szMsg, listInfoList ); + if (nResult = BACK) goto Dlg_SdLicense; + + // TODO TTimo: prompt for what is in the setup and what the user wants to install? + // i.e. by component + + // core + if (DO_CORE_BOOL == 1) then +Dlg_SdAskCorePath: + szDir = "C:\\Program Files\\GtkRadiant-1.<>"; + szTitle = "Installation of " + @PRODUCT_NAME + " core files."; + szMsg = "Select the installation path for " + @PRODUCT_NAME + " core files."; + nResult = SdAskDestPath( szTitle, szMsg, szDir, 0 ); + // FIXME TTimo if a setup doesn't hold the core, it prolly needs to default that + // reading from registry for instance (if TARGETDIR init is required) + TARGETDIR = szDir; + ComponentSetTarget (MEDIA, "", TARGETDIR); + if (nResult = BACK) goto Dlg_SdShowInfoList; + + endif; + + if (DO_NIGHTLY_BOOL == 1) then + MessageBox ("IMPORTANT NOTE ABOUT UPDATE SETUPS:\n" + + "You may be prompted about games that are not installed. This setup is a single update to all the games we support.\n" + + "Let the setup select the default paths, and in 'Setup Type' dialog, select custom setup and disable the updates for the games you don't have.", WARNING); + endif; + + // --------------------------------------------------------------------------------- + // game pack #1 + if (DO_GAME_Q3_BOOL == 1) then +Dlg_SdAskGamePath_Q3: + // guess the default game path + nKeyType = REGDB_STRING; + RegDBSetDefaultRoot (HKEY_LOCAL_MACHINE); + nRegKey = RegDBGetKeyValueEx ("SOFTWARE\\Id\\Quake III Arena\\", "INSTALLPATH", nKeyType, szRegKey, nRegKeySize); + if (nRegKey = 0) then + FindFile (szRegKey, "quake3.exe", szQuake3); + if (szQuake3 = "quake3.exe") then + szDIR_GAME_Q3 = szRegKey; + else + szDIR_GAME_Q3 = @DEFAULTQUAKE3DIR; + endif; + else + szDIR_GAME_Q3 = @DEFAULTQUAKE3DIR; + endif; + + // prompt user for game path + szTitle = "Quake III Arena / Quake III: Team Arena and mods game package"; + szMsg = "Select the folder where Quake III Arena is installed.\nNOTE: For compatibility purposes, your Quake III Arena folder name must contain the word \"quake\" in the top level. eg. C:\\Program Files\\Quake III Arena\n\nTo continue, click Next."; + nResult = SdAskDestPath( szTitle, szMsg, szDIR_GAME_Q3, 0 ); + if (nResult = BACK) then + if (DO_CORE_BOOL == 1) then + goto Dlg_SdAskCorePath; + else + goto Dlg_SdShowInfoList; + endif; + endif; + + // prompt user for game subdir +Dlg_SdShowDlgEdit1_Q3: + szTitle = "Choose Folder\nEnter a folder name."; + szMsg = "Please enter the folder name for " + @PRODUCT_NAME + " Quake III Arena game pack in the edit field below.\nNOTE: This is for installation of the game specific files."; + nResult = SdShowDlgEdit1 (szTitle, szMsg, "Folder", szSubDir); + szDIR_GAMETOOLS_Q3 = szDIR_GAME_Q3 ^ szSubDir; + if (nResult = BACK) goto Dlg_SdAskGamePath_Q3; + + // set values in components + ComponentSetTarget (MEDIA, "", szDIR_GAME_Q3); + ComponentSetTarget (MEDIA, "", szDIR_GAMETOOLS_Q3); + + endif; + + // --------------------------------------------------------------------------------- + // game pack #2 + if (DO_GAME_WOLF_BOOL == 1) then +Dlg_SdAskGamePath_Wolf: + + nKeyType = REGDB_STRING; + RegDBSetDefaultRoot (HKEY_LOCAL_MACHINE); + nRegKey = RegDBGetKeyValueEx ("SOFTWARE\\Activision\\Return to Castle Wolfenstein - Game of The Year Edition\\", "INSTALLPATH", nKeyType, szRegKey, nRegKeySize); + if (nRegKey = 0) then + FindFile (szRegKey, "wolfmp.exe", szRTCW); + if (szRTCW = "wolfmp.exe") then + szDIR_GAME_WOLF = szRegKey; + else + szDIR_GAME_WOLF = @DEFAULTRTCWDIR; + endif; + else + szDIR_GAME_WOLF = @DEFAULTRTCWDIR; + endif; + + // prompt user for game path + // NOTE TTimo: only required in full setup actually? + szTitle = "Return To Castle Wolfenstein game package"; + szMsg = "Select the folder where Return To Castle Wolfenstein is installed."; + nResult = SdAskDestPath( szTitle, szMsg, szDIR_GAME_WOLF, 0 ); + if (nResult = BACK) then + if (DO_GAME_Q3_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q3; + elseif (DO_CORE_BOOL == 1) then + goto Dlg_SdAskCorePath; + else + goto Dlg_SdShowInfoList; + endif; + endif; + + // prompt user for game subdir +Dlg_SdShowDlgEdit1_Wolf: + szTitle = "Choose Folder\nEnter a folder name."; + szMsg = "Please enter the folder name for " + @PRODUCT_NAME + " Return to Castle Wolfenstein game pack in the edit field below.\nNOTE: This is for installation of the game specific files."; + nResult = SdShowDlgEdit1 (szTitle, szMsg, "Folder", szSubDir); + szDIR_GAMETOOLS_WOLF = szDIR_GAME_WOLF ^ szSubDir; + if (nResult = BACK) goto Dlg_SdAskGamePath_Wolf; + + // set values in components + ComponentSetTarget (MEDIA, "", szDIR_GAME_WOLF); + ComponentSetTarget (MEDIA, "", szDIR_GAMETOOLS_WOLF); + + endif; + + // --------------------------------------------------------------------------------- + // game pack #3 + if (DO_GAME_JKII_BOOL == 1) then +Dlg_SdAskGamePath_JKII: + + // guess the default game path from registry + DEFAULTJKIIDIR = "C:\\Program Files\\LucasArts\\Star Wars JK II Jedi Outcast\\GameData"; + nKeyType = REGDB_STRING; + RegDBSetDefaultRoot (HKEY_LOCAL_MACHINE); + nRegKey = RegDBGetKeyValueEx ("SOFTWARE\\LucasArts Entertainment Company LLC\\Star Wars JK II Jedi Outcast\\1.0\\", "Install Path", nKeyType, szRegKey, nRegKeySize); + if (nRegKey = 0) then + FindFile (szRegKey + "GameData", "jk2sp.exe", szJKII); + if (szJKII = "jk2sp.exe") then + szDIR_GAME_JKII = szRegKey; + else + szDIR_GAME_JKII = DEFAULTJKIIDIR; + endif; + else + szDIR_GAME_JKII = DEFAULTJKIIDIR; + endif; + + // prompt user for game path + szTitle = "Jedi Knight II: Jedi Outcast game package"; + szMsg = "Select the folder where Jedi Knight II: Jedi Outcast is installed."; + nResult = SdAskDestPath( szTitle, szMsg, szDIR_GAME_JKII, 0 ); + if (nResult = BACK) then + if (DO_GAME_WOLF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Wolf; + elseif (DO_GAME_Q3_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q3; + elseif (DO_CORE_BOOL == 1) then + goto Dlg_SdAskCorePath; + else + goto Dlg_SdShowInfoList; + endif; + endif; + + // prompt user for game subdir +Dlg_SdShowDlgEdit1_JKII: + szTitle = "Choose Folder\nEnter a folder name."; + szMsg = "Please enter the folder name for " + @PRODUCT_NAME + " Jedi Knight II: Jedi Outcast game pack in the edit field below.\nNOTE: This is for installation of the game specific files."; + nResult = SdShowDlgEdit1 (szTitle, szMsg, "Folder", szSubDir); + szDIR_GAMETOOLS_JKII = szDIR_GAME_JKII ^ szSubDir; + if (nResult = BACK) goto Dlg_SdAskGamePath_JKII; + + // set values in components + ComponentSetTarget (MEDIA, "", szDIR_GAME_JKII); + ComponentSetTarget (MEDIA, "", szDIR_GAMETOOLS_JKII); + + endif; + + // --------------------------------------------------------------------------------- + // game pack #4 + if (DO_GAME_STVEF_BOOL == 1) then +Dlg_SdAskGamePath_STVEF: + + // guess the default game path from registry + DEFAULTSTVEFDIR = "C:\\Program Files\\Raven\\Star Trek Voyager Elite Force\\"; + nKeyType = REGDB_STRING; + RegDBSetDefaultRoot (HKEY_LOCAL_MACHINE); + nRegKey = RegDBGetKeyValueEx ("SOFTWARE\\Activision\\Star Trek: Voyager - Elite Force\\1.0\\", "Install Path", nKeyType, szRegKey, nRegKeySize); + if (nRegKey = 0) then + FindFile (szRegKey + "GameData", "stvoy.exe", szSTVEF); + if (szSTVEF = "stvoy.exe") then + szDIR_GAME_STVEF = szRegKey; + else + szDIR_GAME_STVEF = DEFAULTSTVEFDIR; + endif; + else + szDIR_GAME_STVEF = DEFAULTSTVEFDIR; + endif; + + // prompt user for game path + szTitle = "Star Trek Voyage : Elite Force game package"; + szMsg = "Select the folder where Star Trek Voyage : Elite Force is installed."; + nResult = SdAskDestPath( szTitle, szMsg, szDIR_GAME_STVEF, 0 ); + if (nResult = BACK) then + if (DO_GAME_JKII_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_JKII; + elseif (DO_GAME_WOLF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Wolf; + elseif (DO_GAME_Q3_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q3; + elseif (DO_CORE_BOOL == 1) then + goto Dlg_SdAskCorePath; + else + goto Dlg_SdShowInfoList; + endif; + endif; + + // prompt user for game subdir +Dlg_SdShowDlgEdit1_STVEF: + szTitle = "Choose Folder\nEnter a folder name."; + szMsg = "Please enter the folder name for " + @PRODUCT_NAME + " Star Trek Voyage : Elite Force game pack in the edit field below.\nNOTE: This is for installation of the game specific files."; + nResult = SdShowDlgEdit1 (szTitle, szMsg, "Folder", szSubDir); + szDIR_GAMETOOLS_STVEF = szDIR_GAME_STVEF ^ szSubDir; + if (nResult = BACK) goto Dlg_SdAskGamePath_STVEF; + + // set values in components + ComponentSetTarget (MEDIA, "", szDIR_GAME_STVEF); + ComponentSetTarget (MEDIA, "", szDIR_GAMETOOLS_STVEF); + + endif; + + // --------------------------------------------------------------------------------- + // game pack #5, SofII + // --------------------------------------------------------------------------------- + if (DO_GAME_SOF2_BOOL == 1) then +Dlg_SdAskGamePath_SOF2: + + // guess the default game path from registry + DEFAULTSOF2DIR = "C:\\Program Files\\Soldier of Fortune II - Double Helix\\"; + nKeyType = REGDB_STRING; + RegDBSetDefaultRoot (HKEY_LOCAL_MACHINE); + nRegKey = RegDBGetKeyValueEx ("SOFTWARE\\Activision\\Soldier of Fortune II - Double Helix\\", "InstallPath", nKeyType, szRegKey, nRegKeySize); + if (nRegKey = 0) then + FindFile (szRegKey + "GameData", "sof2.exe", szSOF2); + if (szSOF2 = "sof2.exe") then + szDIR_GAME_SOF2 = szRegKey; + else + szDIR_GAME_SOF2 = DEFAULTSOF2DIR; + endif; + else + szDIR_GAME_SOF2 = DEFAULTSOF2DIR; + endif; + + // prompt user for game path + szTitle = "Soldier of Fortune II - Double Helix game package"; + szMsg = "Select the folder where Soldier of Fortune II - Double Helix is installed."; + nResult = SdAskDestPath( szTitle, szMsg, szDIR_GAME_SOF2, 0 ); + if (nResult = BACK) then + if (DO_GAME_STVEF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_STVEF; + elseif (DO_GAME_JKII_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_JKII; + elseif (DO_GAME_WOLF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Wolf; + elseif (DO_GAME_Q3_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q3; + elseif (DO_CORE_BOOL == 1) then + goto Dlg_SdAskCorePath; + else + goto Dlg_SdShowInfoList; + endif; + endif; + + // prompt user for game subdir +Dlg_SdShowDlgEdit1_SOF2: + szTitle = "Choose Folder\nEnter a folder name."; + szMsg = "Please enter the folder name for " + @PRODUCT_NAME + " Soldier of Fortune II - Double Helix game pack in the edit field below.\nNOTE: This is for installation of the game specific files."; + nResult = SdShowDlgEdit1 (szTitle, szMsg, "Folder", szSubDir); + szDIR_GAMETOOLS_SOF2 = szDIR_GAME_SOF2 ^ szSubDir; + if (nResult = BACK) goto Dlg_SdAskGamePath_SOF2; + + // set values in components + ComponentSetTarget (MEDIA, "", szDIR_GAME_SOF2); + ComponentSetTarget (MEDIA, "", szDIR_GAMETOOLS_SOF2); + + endif; + + // --------------------------------------------------------------------------------- + // game pack #6 + // Hydra: note, for addition game packs, do NOT copy this one. + if (DO_GAME_HALFLIFE_BOOL == 1) then +Dlg_SdAskGamePath_HALFLIFE: + + // for halflife/cs put the default in first, then override + DEFAULTHALFLIFEDIR = "C:\\Sierra\\Half-Life\\"; + szDIR_GAME_HALFLIFE = DEFAULTHALFLIFEDIR; + // for the .game file generation we need a flag. + DO_GAME_CSTRIKE_BOOL = 0; + + // guess the default game path (Counter Strike Retail) + nKeyType = REGDB_STRING; + RegDBSetDefaultRoot (HKEY_LOCAL_MACHINE); + nRegKey = RegDBGetKeyValueEx ("SOFTWARE\\Sierra OnLine\\Setup\\CSTRIKE\\", "Directory", nKeyType, szRegKey, nRegKeySize); + if (nRegKey = 0) then + FindFile (szRegKey, "cstrike.exe", szCStrike); + if (szCStrike = "cstrike.exe") then + szDIR_GAME_HALFLIFE = szRegKey; + DO_GAME_CSTRIKE_BOOL = 1; // set the flag + endif; + endif; + + if (szDIR_GAME_HALFLIFE = DEFAULTHALFLIFEDIR) then + // guess the default game path (HalfLife) + nKeyType = REGDB_STRING; + RegDBSetDefaultRoot (HKEY_LOCAL_MACHINE); + nRegKey = RegDBGetKeyValueEx ("SOFTWARE\\Sierra OnLine\\Setup\\HALFLIFE\\", "Directory", nKeyType, szRegKey, nRegKeySize); + if (nRegKey = 0) then + FindFile (szRegKey, "hl.exe", szHL); + if (szHL = "hl.exe") then + szDIR_GAME_HALFLIFE = szRegKey; + endif; + endif; + endif; + + // prompt user for game path + szTitle = "Halflife / Counterstrike-Retail and mods game package"; + szMsg = "Select the folder where Halflife or Counterstrike-Retail is installed.\nclick Next."; + nResult = SdAskDestPath( szTitle, szMsg, szDIR_GAME_HALFLIFE, 0 ); + if (nResult = BACK) then + if (DO_GAME_SOF2_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_SOF2; + elseif (DO_GAME_STVEF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_STVEF; + elseif (DO_GAME_JKII_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_JKII; + elseif (DO_GAME_WOLF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Wolf; + elseif (DO_GAME_Q3_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q3; + elseif (DO_CORE_BOOL == 1) then + goto Dlg_SdAskCorePath; + else + goto Dlg_SdShowInfoList; + endif; + endif; + + // prompt user for game subdir +Dlg_SdShowDlgEdit1_HALFLIFE: + szTitle = "Choose Folder\nEnter a folder name."; + szMsg = "Please enter the folder name for " + @PRODUCT_NAME + " Halflife game pack in the edit field below.\nNOTE: This is for installation of the game specific files."; + nResult = SdShowDlgEdit1 (szTitle, szMsg, "Folder", szSubDir); + szDIR_GAMETOOLS_HALFLIFE = szDIR_GAME_HALFLIFE ^ szSubDir; + if (nResult = BACK) goto Dlg_SdAskGamePath_HALFLIFE; + + // set values in components + ComponentSetTarget (MEDIA, "", szDIR_GAME_HALFLIFE); + ComponentSetTarget (MEDIA, "", szDIR_GAMETOOLS_HALFLIFE); + + endif; + + // --------------------------------------------------------------------------------- + // game pack #7, ET + // --------------------------------------------------------------------------------- + if (DO_GAME_ET_BOOL == 1) then +Dlg_SdAskGamePath_ET: + + // guess the default game path from registry + DEFAULTETDIR = "C:\\Program Files\\Wolfenstein - Enemy Territory\\"; + nKeyType = REGDB_STRING; + RegDBSetDefaultRoot (HKEY_LOCAL_MACHINE); + nRegKey = RegDBGetKeyValueEx ("SOFTWARE\\Activision\\Wolfenstein - Enemy Territory\\", "InstallPath", nKeyType, szRegKey, nRegKeySize); + if (nRegKey = 0) then + FindFile (szRegKey + "GameData", "ET.exe", szET); + if (szET = "ET.exe") then + szDIR_GAME_ET = szRegKey; + else + szDIR_GAME_ET = DEFAULTETDIR; + endif; + else + szDIR_GAME_ET = DEFAULTETDIR; + endif; + + // prompt user for game path + szTitle = "Wolfenstein: Enemy Territory game package"; + szMsg = "Select the folder where Wolfenstein: Enemy Territory is installed."; + nResult = SdAskDestPath( szTitle, szMsg, szDIR_GAME_ET, 0 ); + if (nResult = BACK) then + if (DO_GAME_HALFLIFE_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_HALFLIFE; + elseif (DO_GAME_STVEF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_STVEF; + elseif (DO_GAME_JKII_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_JKII; + elseif (DO_GAME_WOLF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Wolf; + elseif (DO_GAME_Q3_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q3; + elseif (DO_CORE_BOOL == 1) then + goto Dlg_SdAskCorePath; + else + goto Dlg_SdShowInfoList; + endif; + endif; + + // prompt user for game subdir +Dlg_SdShowDlgEdit1_ET: + szTitle = "Choose Folder\nEnter a folder name."; + szMsg = "Please enter the folder name for " + @PRODUCT_NAME + " Wolfenstein: Enemy Territory game pack in the edit field below.\nNOTE: This is for installation of the game specific files."; + nResult = SdShowDlgEdit1 (szTitle, szMsg, "Folder", szSubDir); + szDIR_GAMETOOLS_ET = szDIR_GAME_ET ^ szSubDir; + if (nResult = BACK) goto Dlg_SdAskGamePath_ET; + + // set values in components + ComponentSetTarget (MEDIA, "", szDIR_GAME_ET); + ComponentSetTarget (MEDIA, "", szDIR_GAMETOOLS_ET); + + endif; + + // --------------------------------------------------------------------------------- + // game pack #8, Quake + // --------------------------------------------------------------------------------- + if (DO_GAME_Q1_BOOL == 1) then +Dlg_SdAskGamePath_Q1: + + // guess the default game path from registry + DEFAULTQ1DIR = "C:\\quake\\"; + + FindFile (szRegKey + "GameData", "quake.exe", szQ1); + if (szQ1 = "quake.exe") then + szDIR_GAME_Q1 = szRegKey; + else + szDIR_GAME_Q1 = DEFAULTQ1DIR; + endif; + + // prompt user for game path + szTitle = "Quake game package"; + szMsg = "Select the folder where Quake is installed."; + nResult = SdAskDestPath( szTitle, szMsg, szDIR_GAME_Q1, 0 ); + if (nResult = BACK) then + if (DO_GAME_ET_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_ET; + elseif (DO_GAME_HALFLIFE_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_HALFLIFE; + elseif (DO_GAME_SOF2_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_SOF2; + elseif (DO_GAME_STVEF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_STVEF; + elseif (DO_GAME_JKII_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_JKII; + elseif (DO_GAME_WOLF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Wolf; + elseif (DO_GAME_Q3_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q3; + elseif (DO_CORE_BOOL == 1) then + goto Dlg_SdAskCorePath; + else + goto Dlg_SdShowInfoList; + endif; + endif; + + // prompt user for game subdir +Dlg_SdShowDlgEdit1_Q1: + szTitle = "Choose Folder\nEnter a folder name."; + szMsg = "Please enter the folder name for " + @PRODUCT_NAME + " Quake game pack in the edit field below.\nNOTE: This is for installation of the game specific files."; + nResult = SdShowDlgEdit1 (szTitle, szMsg, "Folder", szSubDir); + szDIR_GAMETOOLS_Q1 = szDIR_GAME_Q1 ^ szSubDir; + if (nResult = BACK) goto Dlg_SdAskGamePath_Q1; + + // set values in components + ComponentSetTarget (MEDIA, "", szDIR_GAME_Q1); + ComponentSetTarget (MEDIA, "", szDIR_GAMETOOLS_Q1); + + endif; + + // --------------------------------------------------------------------------------- + // game pack #8, JA + if (DO_GAME_JA_BOOL == 1) then +Dlg_SdAskGamePath_JA: + + // guess the default game path from registry + DEFAULTJADIR = "C:\\Program Files\\LucasArts\\Star Wars Jedi Knight Jedi Academy\\GameData"; + nKeyType = REGDB_STRING; + RegDBSetDefaultRoot (HKEY_LOCAL_MACHINE); + nRegKey = RegDBGetKeyValueEx ("SOFTWARE\\LucasArts\\Star Wars Jedi Knight Jedi Academy\\1.0\\", "Install Path", nKeyType, szRegKey, nRegKeySize); + if (nRegKey = 0) then + FindFile (szRegKey + "GameData", "jasp.exe", szJKII); + if (szJKII = "jasp.exe") then + szDIR_GAME_JA = szRegKey; + else + szDIR_GAME_JA = DEFAULTJADIR; + endif; + else + szDIR_GAME_JA = DEFAULTJADIR; + endif; + + // prompt user for game path + szTitle = "Star Wars Jedi Knight Jedi Academy"; + szMsg = "Select the folder where Star Wars Jedi Knight Jedi Academy is installed."; + nResult = SdAskDestPath( szTitle, szMsg, szDIR_GAME_JA, 0 ); + if (nResult = BACK) then + if (DO_GAME_Q1_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q1; + elseif (DO_GAME_ET_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_ET; + elseif (DO_GAME_HALFLIFE_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_HALFLIFE; + elseif (DO_GAME_SOF2_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_SOF2; + elseif (DO_GAME_STVEF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_STVEF; + elseif (DO_GAME_JKII_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_JKII; + elseif (DO_GAME_WOLF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Wolf; + elseif (DO_GAME_Q3_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q3; + elseif (DO_CORE_BOOL == 1) then + goto Dlg_SdAskCorePath; + else + goto Dlg_SdShowInfoList; + endif; + endif; + + // prompt user for game subdir +Dlg_SdShowDlgEdit1_JA: + szTitle = "Choose Folder\nEnter a folder name."; + szMsg = "Please enter the folder name for " + @PRODUCT_NAME + " Star Wars Jedi Knight Jedi Academy game pack in the edit field below.\nNOTE: This is for installation of the game specific files."; + nResult = SdShowDlgEdit1 (szTitle, szMsg, "Folder", szSubDir); + szDIR_GAMETOOLS_JA = szDIR_GAME_JA ^ szSubDir; + if (nResult = BACK) goto Dlg_SdAskGamePath_JA; + + // set values in components + ComponentSetTarget (MEDIA, "", szDIR_GAME_JA); + ComponentSetTarget (MEDIA, "", szDIR_GAMETOOLS_JA); + + endif; + + // --------------------------------------------------------------------------------- + // game pack #9, Q2 + // --------------------------------------------------------------------------------- + if (DO_GAME_Q2_BOOL == 1) then +Dlg_SdAskGamePath_Q2: + + // guess the default game path from registry + szDIR_GAME_Q2 = "C:\\Quake2\\"; + + // prompt user for game path + szTitle = "Quake II"; + szMsg = "Select the folder where Quake II is installed."; + nResult = SdAskDestPath( szTitle, szMsg, szDIR_GAME_Q2, 0 ); + if (nResult = BACK) then + if (DO_GAME_JA_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_JA; + elseif (DO_GAME_Q1_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q1; + elseif (DO_GAME_ET_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_ET; + elseif (DO_GAME_HALFLIFE_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_HALFLIFE; + elseif (DO_GAME_STVEF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_STVEF; + elseif (DO_GAME_JKII_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_JKII; + elseif (DO_GAME_WOLF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Wolf; + elseif (DO_GAME_Q3_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q3; + elseif (DO_CORE_BOOL == 1) then + goto Dlg_SdAskCorePath; + else + goto Dlg_SdShowInfoList; + endif; + endif; + + // prompt user for game subdir +Dlg_SdShowDlgEdit1_Q2: + szTitle = "Choose Folder\nEnter a folder name."; + szMsg = "Please enter the folder name for " + @PRODUCT_NAME + " Quake II game pack in the edit field below.\nNOTE: This is for installation of the game specific files."; + nResult = SdShowDlgEdit1 (szTitle, szMsg, "Folder", szSubDir); + szDIR_GAMETOOLS_Q2 = szDIR_GAME_Q2 ^ szSubDir; + if (nResult = BACK) goto Dlg_SdAskGamePath_Q2; + + // set values in components + ComponentSetTarget (MEDIA, "", szDIR_GAME_Q2); + ComponentSetTarget (MEDIA, "", szDIR_GAMETOOLS_Q2); + + endif; + + // --------------------------------------------------------------------------------- + // game pack #10, HER2 + // --------------------------------------------------------------------------------- + if (DO_GAME_HER2_BOOL == 1) then +Dlg_SdAskGamePath_HER2: + + // guess the default game path from registry + szDIR_GAME_HER2 = "C:\\Heretic2\\"; + + // prompt user for game path + szTitle = "Heretic II"; + szMsg = "Select the folder where Heretic II is installed."; + nResult = SdAskDestPath( szTitle, szMsg, szDIR_GAME_HER2, 0 ); + if (nResult = BACK) then + if (DO_GAME_Q2_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q2; + elseif (DO_GAME_JA_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_JA; + elseif (DO_GAME_Q1_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q1; + elseif (DO_GAME_ET_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_ET; + elseif (DO_GAME_HALFLIFE_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_HALFLIFE; + elseif (DO_GAME_STVEF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_STVEF; + elseif (DO_GAME_JKII_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_JKII; + elseif (DO_GAME_WOLF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Wolf; + elseif (DO_GAME_Q3_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q3; + elseif (DO_CORE_BOOL == 1) then + goto Dlg_SdAskCorePath; + else + goto Dlg_SdShowInfoList; + endif; + endif; + + // prompt user for game subdir +Dlg_SdShowDlgEdit1_HER2: + szTitle = "Choose Folder\nEnter a folder name."; + szMsg = "Please enter the folder name for " + @PRODUCT_NAME + " Heretic II game pack in the edit field below.\nNOTE: This is for installation of the game specific files."; + nResult = SdShowDlgEdit1 (szTitle, szMsg, "Folder", szSubDir); + szDIR_GAMETOOLS_HER2 = szDIR_GAME_HER2 ^ szSubDir; + if (nResult = BACK) goto Dlg_SdAskGamePath_HER2; + + // set values in components + ComponentSetTarget (MEDIA, "", szDIR_GAME_HER2); + ComponentSetTarget (MEDIA, "", szDIR_GAMETOOLS_HER2); + + endif; + + // --------------------------------------------------------------------------------- + // game pack #11, 'TODO: add your game pack here' + // --------------------------------------------------------------------------------- + + // prompt for typical, custom etc. install +Dlg_SetupType: + szTitle = ""; + szMsg = ""; + nResult = SdSetupTypeEx (szTitle, szMsg, "", svSetupType, 0); + if (nResult = BACK) then + // FIXME TTimo wish we could stick labels into variables instead? + if (DO_GAME_HER2_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_HER2; + elseif (DO_GAME_Q2_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q2; + elseif (DO_GAME_JA_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_JA; + elseif (DO_GAME_Q1_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q1; + elseif (DO_GAME_ET_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_ET; + elseif (DO_GAME_HALFLIFE_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_HALFLIFE; + elseif (DO_GAME_SOF2_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_SOF2; + elseif (DO_GAME_STVEF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_STVEF; + elseif (DO_GAME_JKII_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_JKII; + elseif (DO_GAME_WOLF_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Wolf; + elseif (DO_GAME_Q3_BOOL == 1) then + goto Dlg_SdShowDlgEdit1_Q3; + elseif (DO_CORE_BOOL == 1) then + goto Dlg_SdAskCorePath; + else + goto Dlg_SdShowInfoList; + endif; + endif; + if (svSetupType = "custom") then + nResult = CUSTOM; + endif; + nSetupType = nResult; + +Dlg_SdComponentTree: + if ((nResult = BACK) && (nSetupType != CUSTOM)) goto Dlg_SetupType; + szTitle = ""; + szMsg = ""; + szTargetdir = TARGETDIR; + szComponents = ""; + nLevel = 2; + if (nSetupType = CUSTOM) then + nResult = SdComponentTree(szTitle, szMsg, szTargetdir, szComponents, nLevel); + if (nResult = BACK) goto Dlg_SetupType; + endif; + +Dlg_ObjDialogs: + nResult = ShowObjWizardPages(nResult); + if (nResult = BACK) goto Dlg_SdComponentTree; + +Dlg_SdSelectFolder: + szfolder = @FOLDER_NAME; + szTitle = ""; + szMsg = ""; + nResult = SdSelectFolder( szTitle, szMsg, szfolder ); + SHELL_OBJECT_FOLDER = szfolder; + if (nResult = BACK) goto Dlg_ObjDialogs; + + // push the list of stuff +Dlg_SdStartCopy: + szTitle = ""; + szMsg = ""; + listStartCopy = ListCreate( STRINGLIST ); + if (DO_CORE_BOOL == 1) then + ListAddString(listStartCopy,@PRODUCT_NAME + " core installation directory:",AFTER); + ListAddString(listStartCopy," " + TARGETDIR,AFTER); + endif; + if (DO_GAME_Q3_BOOL == 1) then + ListAddString(listStartCopy,"Quake III Arena folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAME_Q3,AFTER); + ListAddString(listStartCopy,"Quake III Arena mapping package folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAMETOOLS_Q3,AFTER); + endif; + if (DO_GAME_WOLF_BOOL == 1) then + ListAddString(listStartCopy,"Return To Castle Wolfenstein folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAME_WOLF,AFTER); + ListAddString(listStartCopy,"Return To Castle Wolfenstein mapping package folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAMETOOLS_WOLF,AFTER); + endif; + if (DO_GAME_JKII_BOOL == 1) then + ListAddString(listStartCopy,"Jedi Knight II: Jedi Outcast folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAME_JKII,AFTER); + ListAddString(listStartCopy,"Jedi Knight II: Jedi Outcast mapping package folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAMETOOLS_JKII,AFTER); + endif; + if (DO_GAME_STVEF_BOOL == 1) then + ListAddString(listStartCopy,"Star Trek Voyager : Elite Force folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAME_STVEF,AFTER); + ListAddString(listStartCopy,"Star Trek Voyager : Elite Force mapping package folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAMETOOLS_STVEF,AFTER); + endif; + if (DO_GAME_SOF2_BOOL == 1) then + ListAddString(listStartCopy,"Soldier of Fortune II - Double Helix folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAME_SOF2,AFTER); + ListAddString(listStartCopy,"Soldier of Fortune II - Double Helix mapping package folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAMETOOLS_SOF2,AFTER); + endif; + if (DO_GAME_HALFLIFE_BOOL == 1) then + ListAddString(listStartCopy,"Halflife or CounterStrike-Retail folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAME_HALFLIFE,AFTER); + ListAddString(listStartCopy,"Halflife or CounterStrike-Retail mapping package folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAMETOOLS_HALFLIFE,AFTER); + endif; + if (DO_GAME_ET_BOOL == 1) then + ListAddString(listStartCopy,"Wolfenstein: Enemy Territory folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAME_ET,AFTER); + ListAddString(listStartCopy,"Wolfenstein: Enemy Territory mapping package folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAMETOOLS_ET,AFTER); + endif; + if (DO_GAME_Q1_BOOL == 1) then + ListAddString(listStartCopy,"Quake folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAME_Q1,AFTER); + ListAddString(listStartCopy,"Quake mapping package folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAMETOOLS_Q1,AFTER); + endif; + if (DO_GAME_JA_BOOL == 1) then + ListAddString(listStartCopy,"Star Wars Jedi Knight Jedi Academy folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAME_JA,AFTER); + ListAddString(listStartCopy,"Star Wars Jedi Knight Jedi Academy mapping package folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAMETOOLS_JA,AFTER); + endif; + if (DO_GAME_Q2_BOOL == 1) then + ListAddString(listStartCopy,"Quake II folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAME_Q2,AFTER); + ListAddString(listStartCopy,"Quake II mapping package folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAMETOOLS_Q2,AFTER); + endif; + if (DO_GAME_HER2_BOOL == 1) then + ListAddString(listStartCopy,"Heretic II folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAME_HER2,AFTER); + ListAddString(listStartCopy,"Heretic II mapping package folder:",AFTER); + ListAddString(listStartCopy," " + szDIR_GAMETOOLS_HER2,AFTER); + endif; + ListAddString(listStartCopy,"Setup type: ",AFTER); + ListAddString(listStartCopy," " + svSetupType,AFTER); + nResult = SdStartCopy( szTitle, szMsg, listStartCopy ); + ListDestroy(listStartCopy); + if (nResult = BACK) goto Dlg_SdSelectFolder; + + // setup default status + SetStatusWindow(0, ""); + Enable(STATUSEX); + StatusUpdate(ON, 100); + + + return 0; +end; + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION: OnMoving +// +// EVENT: Moving event is sent when file transfer is started as a result of +// ComponentTransferData call, before any file transfer operations +// are performed. +// +/////////////////////////////////////////////////////////////////////////////// +function OnMoving() + STRING szAppPath; +begin + // Set LOGO Compliance Application Path + // NOTE TTimo + // doc on RegDBSetItem says before calling you must call the InstallationInfo + // function, which provides information used to create the key + szAppPath = TARGETDIR; + RegDBSetItem(REGDB_APPPATH, szAppPath); + RegDBSetItem(REGDB_APPPATH_DEFAULT, szAppPath ^ @PRODUCT_KEY); +end; + +///////////////////////////////////////////////////////////////////////////////// +// The OnMoved event handler responds to the Moved event that is generated just +// after all selected components have been installed or uninstalled on the +// target computer. +///////////////////////////////////////////////////////////////////////////////// + +function OnMoved() + NUMBER nvFileHandle; +begin + if !MAINTENANCE then + // generate RADIANT_MAJOR and RADIANT_MINOR file items + if (CreateFile(nvFileHandle, TARGETDIR, "RADIANT_MAJOR")< 0) then + MessageBox ("CreateFile " + TARGETDIR + "\\RADIANT_MAJOR failed.", SEVERE); + abort; + endif; + // template + WriteLine(nvFileHandle, "<>"); + CloseFile(nvFileHandle); + if (CreateFile(nvFileHandle, TARGETDIR, "RADIANT_MINOR")< 0) then + MessageBox ("CreateFile " + TARGETDIR + "\\RADIANT_MINOR failed.", SEVERE); + abort; + endif; + // template + WriteLine(nvFileHandle, "<>"); + CloseFile(nvFileHandle); + // generate the config files for the game packages + // NOTE: we are directly creating them, not using XCopyFile + // that means those won't be uninstalled, doesn't look like a big problem to me + if (DO_GAME_Q3_BOOL == 1) then + if (CreateDir(TARGETDIR ^ "games")< 0) then + // Report the error; then abort. + MessageBox ("Unable to create directory " + TARGETDIR ^ "games", SEVERE); + abort; + endif; + if (CreateFile(nvFileHandle, TARGETDIR ^ "games", "q3.game")< 0) then + // Report the error. + MessageBox ("CreateFile " + TARGETDIR ^ "games" + "\\q3.game failed.", SEVERE); + abort; + endif; + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + CloseFile(nvFileHandle); + endif; + if (DO_GAME_WOLF_BOOL == 1) then + if (CreateDir(TARGETDIR ^ "games")< 0) then + // Report the error; then abort. + MessageBox ("Unable to create directory " + TARGETDIR ^ "games", SEVERE); + abort; + endif; + if (CreateFile(nvFileHandle, TARGETDIR ^ "games", "wolf.game")< 0) then + // Report the error. + MessageBox ("CreateFile " + TARGETDIR ^ "games" + "\\wolf.game failed.", SEVERE); + abort; + endif; + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + CloseFile(nvFileHandle); + endif; + if (DO_GAME_JKII_BOOL == 1) then + if (CreateDir(TARGETDIR ^ "games")< 0) then + // Report the error; then abort. + MessageBox ("Unable to create directory " + TARGETDIR ^ "games", SEVERE); + abort; + endif; + if (CreateFile(nvFileHandle, TARGETDIR ^ "games", "jk2.game")< 0) then + // Report the error. + MessageBox ("CreateFile " + TARGETDIR ^ "games" + "\\jk2.game failed.", SEVERE); + abort; + endif; + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + CloseFile(nvFileHandle); + endif; + if (DO_GAME_STVEF_BOOL == 1) then + if (CreateDir(TARGETDIR ^ "games")< 0) then + // Report the error; then abort. + MessageBox ("Unable to create directory " + TARGETDIR ^ "games", SEVERE); + abort; + endif; + if (CreateFile(nvFileHandle, TARGETDIR ^ "games", "stvef.game")< 0) then + // Report the error. + MessageBox ("CreateFile " + TARGETDIR ^ "games" + "\\stvef.game failed.", SEVERE); + abort; + endif; + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + CloseFile(nvFileHandle); + endif; + if (DO_GAME_SOF2_BOOL == 1) then + if (CreateDir(TARGETDIR ^ "games")< 0) then + // Report the error; then abort. + MessageBox ("Unable to create directory " + TARGETDIR ^ "games", SEVERE); + abort; + endif; + if (CreateFile(nvFileHandle, TARGETDIR ^ "games", "sof2.game")< 0) then + // Report the error. + MessageBox ("CreateFile " + TARGETDIR ^ "games" + "\\sof2.game failed.", SEVERE); + abort; + endif; + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + CloseFile(nvFileHandle); + endif; + if (DO_GAME_HALFLIFE_BOOL == 1) then + if (CreateDir(TARGETDIR ^ "games")< 0) then + // Report the error; then abort. + MessageBox ("Unable to create directory " + TARGETDIR ^ "games", SEVERE); + abort; + endif; + if (CreateFile(nvFileHandle, TARGETDIR ^ "games", "hl.game")< 0) then + // Report the error. + MessageBox ("CreateFile " + TARGETDIR ^ "games" + "\\hl.game failed.", SEVERE); + abort; + endif; + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + CloseFile(nvFileHandle); + endif; + if (DO_GAME_ET_BOOL == 1) then + if (CreateDir(TARGETDIR ^ "games")< 0) then + // Report the error; then abort. + MessageBox ("Unable to create directory " + TARGETDIR ^ "games", SEVERE); + abort; + endif; + if (CreateFile(nvFileHandle, TARGETDIR ^ "games", "et.game")< 0) then + // Report the error. + MessageBox ("CreateFile " + TARGETDIR ^ "games" + "\\et.game failed.", SEVERE); + abort; + endif; + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + CloseFile(nvFileHandle); + endif; + if (DO_GAME_Q1_BOOL == 1) then + if (CreateDir(TARGETDIR ^ "games")< 0) then + // Report the error; then abort. + MessageBox ("Unable to create directory " + TARGETDIR ^ "games", SEVERE); + abort; + endif; + if (CreateFile(nvFileHandle, TARGETDIR ^ "games", "q1.game")< 0) then + // Report the error. + MessageBox ("CreateFile " + TARGETDIR ^ "games" + "\\q1.game failed.", SEVERE); + abort; + endif; + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + CloseFile(nvFileHandle); + endif; + if (DO_GAME_JA_BOOL == 1) then + if (CreateDir(TARGETDIR ^ "games")< 0) then + // Report the error; then abort. + MessageBox ("Unable to create directory " + TARGETDIR ^ "games", SEVERE); + abort; + endif; + if (CreateFile(nvFileHandle, TARGETDIR ^ "games", "ja.game")< 0) then + // Report the error. + MessageBox ("CreateFile " + TARGETDIR ^ "games" + "\\ja.game failed.", SEVERE); + abort; + endif; + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + CloseFile(nvFileHandle); + endif; + if (DO_GAME_Q2_BOOL == 1) then + if (CreateDir(TARGETDIR ^ "games")< 0) then + // Report the error; then abort. + MessageBox ("Unable to create directory " + TARGETDIR ^ "games", SEVERE); + abort; + endif; + if (CreateFile(nvFileHandle, TARGETDIR ^ "games", "q2.game")< 0) then + // Report the error. + MessageBox ("CreateFile " + TARGETDIR ^ "games" + "\\q2.game failed.", SEVERE); + abort; + endif; + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + CloseFile(nvFileHandle); + endif; + if (DO_GAME_HER2_BOOL == 1) then + if (CreateDir(TARGETDIR ^ "games")< 0) then + // Report the error; then abort. + MessageBox ("Unable to create directory " + TARGETDIR ^ "games", SEVERE); + abort; + endif; + if (CreateFile(nvFileHandle, TARGETDIR ^ "games", "heretic2.game")< 0) then + // Report the error. + MessageBox ("CreateFile " + TARGETDIR ^ "games" + "\\heretic2.game failed.", SEVERE); + abort; + endif; + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + WriteLine(nvFileHandle, ""); + CloseFile(nvFileHandle); + endif; + + endif; +end; + +// --- include script file section --- diff --git a/setup/win32/template/Setup Files/Compressed Files/Language Independent/OS Independent/_IsUser.dll b/setup/win32/template/Setup Files/Compressed Files/Language Independent/OS Independent/_IsUser.dll new file mode 100644 index 0000000000000000000000000000000000000000..08c60142adb11412a16b9282831c67ab69897315 GIT binary patch literal 45056 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 z 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 +Folder1= +Folder2= +Folder3= +Group0=Main +Group1=Startup +Folder4=Folder2\GtkRadiant +Shortcut0=Folder4\GtkRadiant +Shortcut1=Folder4\Radiant Manual +Shortcut2=Folder4\Links +Shortcut3=Folder4\RTCW + GtkRadiant, Getting Started guide +Shortcut4=Folder4\JK2 + GtkRadiant, Getting Started guide +Shortcut5=Folder4\STVEF + GtkRadiant, Getting Started guide + +[Folder4] +Comments= +DisplayText= +Shared=Yes + +[Shortcut0] +DisplayText=PRODUCT_NAME +Target=\<> +Parameters= +StartIn= +ShortcutKey= +Run=Normal Window +IconFile= +IconIndex= +Replace=Yes +SeparateMemorySpace=No +InternetShortcut=No +Uninstall=Yes +Type=Automatic +Comments= +FileGroup0=Program Executable Files + +[Shortcut1] +DisplayText= +Target=\q3rad_manual\index.htm +Parameters= +StartIn= +ShortcutKey= +Run=Normal Window +IconFile= +IconIndex= +Replace=Yes +SeparateMemorySpace=No +InternetShortcut=No +Uninstall=Yes +Type=Automatic +Comments= +FileGroup0=Radiant Manual Files + +[Shortcut2] +DisplayText= +Target=\links.htm +Parameters= +StartIn= +ShortcutKey= +Run=Normal Window +IconFile= +IconIndex= +Replace=Yes +SeparateMemorySpace=No +InternetShortcut=No +Uninstall=Yes +Type=Automatic +Comments= +FileGroup0=Program Misc Files + +[Shortcut3] +DisplayText= +Target=\docs\GtkRadiant_WOLF_HOWTO\index.html +Parameters= +StartIn= +ShortcutKey= +Run=Normal Window +IconFile= +IconIndex= +Replace=Yes +SeparateMemorySpace=No +InternetShortcut=No +Uninstall=Yes +Type=Automatic +Comments= +FileGroup0=Wolf Exectuable Files + +[Shortcut4] +DisplayText= +Target=\docs\GtkRadiant_JK2_HOWTO\index.html +Parameters= +StartIn= +ShortcutKey= +Run=Normal Window +IconFile= +IconIndex= +Replace=Yes +SeparateMemorySpace=No +InternetShortcut=No +Uninstall=Yes +Type=Automatic +Comments= +FileGroup0=JKII Executable Files + +[Shortcut5] +DisplayText= +Target=\docs\GtkRadiant_STVEF_HOWTO\index.html +Parameters= +StartIn= +ShortcutKey= +Run=Normal Window +IconFile= +IconIndex= +Replace=Yes +SeparateMemorySpace=No +InternetShortcut=No +Uninstall=Yes +Type=Automatic +Comments= +FileGroup0=STVEF Executable Files + diff --git a/setup/win32/template/String Tables/0009-English/value.shl b/setup/win32/template/String Tables/0009-English/value.shl new file mode 100644 index 00000000..ee19017c --- /dev/null +++ b/setup/win32/template/String Tables/0009-English/value.shl @@ -0,0 +1,16 @@ +[General] +Type=STRINGTABLESPECIFIC +Version=1.00.000 +Language=0009 + +[Data] +TITLE_MAIN=GtkRadiant +TITLE_CAPTIONBAR=GtkRadiant Setup +COMPANY_NAME=QERadiant.com +PRODUCT_NAME=GtkRadiant-<> +PRODUCT_KEY=<> +PRODUCT_VERSION=<> +FOLDER_NAME=<> +DEFAULTQUAKE3DIR=C:\Program Files\Quake III Arena +DEFAULTRTCWDIR=C:\Program Files\Return To Castle Wolfenstein + diff --git a/setup/win32/template/String Tables/Default.shl b/setup/win32/template/String Tables/Default.shl new file mode 100644 index 00000000..f8fae252 --- /dev/null +++ b/setup/win32/template/String Tables/Default.shl @@ -0,0 +1,47 @@ +[GENERAL] +TYPE=STRINGTABLE +VERSION=1.00.000 + +[LANGUAGE] +CURRENTLANG=0 +LANG0=0009 +LANG1=0000 + +[TITLE_MAIN] +Comment=Do not delete. Setup requires this Identifier even if your script doesn't use it. + +[DATA] +ENTRY0=TITLE_MAIN +ENTRY1=TITLE_CAPTIONBAR +ENTRY2=COMPANY_NAME +ENTRY3=PRODUCT_NAME +ENTRY4=PRODUCT_KEY +ENTRY5=PRODUCT_VERSION +ENTRY6=FOLDER_NAME +ENTRY7=DEFAULTQUAKE3DIR +ENTRY8=DEFAULTRTCWDIR + +[TITLE_CAPTIONBAR] +Comment=Do not delete. Setup requires this Identifier even if your script doesn't use it. + +[COMPANY_NAME] +Comment=Do not delete. Setup requires this Identifier even if your script doesn't use it. + +[PRODUCT_NAME] +Comment=Do not delete. Setup requires this Identifier even if your script doesn't use it. + +[PRODUCT_KEY] +Comment=Do not delete. Setup requires this Identifier even if your script doesn't use it. + +[PRODUCT_VERSION] +Comment=Do not delete. Setup requires this Identifier even if your script doesn't use it. + +[FOLDER_NAME] +Comment=Do not delete. Setup requires this Identifier even if your script doesn't use it. + +[DEFAULTQUAKE3DIR] +Comment= + +[DEFAULTRTCWDIR] +Comment= + diff --git a/setup/win32/template/Text Substitutions/Build.tsb b/setup/win32/template/Text Substitutions/Build.tsb new file mode 100644 index 00000000..f18328d8 --- /dev/null +++ b/setup/win32/template/Text Substitutions/Build.tsb @@ -0,0 +1,41 @@ +[Data] +Key0= +Key1= +Key2= +Key3= +Key4= +Key5= +Key6= + +[General] +Type=TEXTSUB +Version=1.20.000 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value=c:\quake\gtkradiant_latest\ +KeyType=3 + +[] +Value= +KeyType=3 + diff --git a/setup/win32/template/Text Substitutions/Setup.tsb b/setup/win32/template/Text Substitutions/Setup.tsb new file mode 100644 index 00000000..93bd8ce8 --- /dev/null +++ b/setup/win32/template/Text Substitutions/Setup.tsb @@ -0,0 +1,238 @@ +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[Data] +Key0= +Key1= +Key2= +Key3= +Key4= +Key9= +Key5= +Key10= +Key6= +Key11= +Key7= +Key8= +Key12= +Key13= +Key14= +Key15= +Key16= +Key17= +Key18= +Key19= +Key20= +Key21= +Key22= +Key23= +Key24= +Key25= +Key26= +Key27= +Key28= +Key29= +Key30= +Key31= +Key32= +Key33= +Key34= +Key35= +Key36= +Key37= +Key38= +Key39= +Key40= +Key41= +Key42= +Key43= +Key44= +Key45= +Key46= +Key47= + +[] +Value= +KeyType=4 + +[General] +Type=TEXTSUB +Version=1.10.000 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=4 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + +[] +Value= +KeyType=3 + diff --git a/setup/win32/warsow_mapping_files.xml b/setup/win32/warsow_mapping_files.xml new file mode 100644 index 00000000..cd966d39 --- /dev/null +++ b/setup/win32/warsow_mapping_files.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/setup/win32/wolf.cf b/setup/win32/wolf.cf new file mode 100644 index 00000000..6f7d0f1f --- /dev/null +++ b/setup/win32/wolf.cf @@ -0,0 +1,7 @@ +# Core + Wolf game pack + +# output dir name +$SETUP_DIR = 'Setup-Wolf'; + +$DO_CORE = 1; +$DO_GAME_WOLF = 1; diff --git a/setup/win32/wolf_example_maps.xml b/setup/win32/wolf_example_maps.xml new file mode 100644 index 00000000..7ced580f --- /dev/null +++ b/setup/win32/wolf_example_maps.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/svn-commit.tmp b/svn-commit.tmp new file mode 100644 index 00000000..75302a54 --- /dev/null +++ b/svn-commit.tmp @@ -0,0 +1,4 @@ +initial commit of radiant15 +--This line, and those below, will be ignored-- + +A . diff --git a/svn.py b/svn.py new file mode 100644 index 00000000..68d60457 --- /dev/null +++ b/svn.py @@ -0,0 +1,16 @@ +import os +import sys + +def getRevision(path): + cmd = os.popen("svn info " + path) + + while True: + line = cmd.readline() + if line == "": + raise Exception("failed to obtain revision number") + for word in line.split(): + try: + return int(word) + except: + pass + diff --git a/tools/quake2/common/bspfile.c b/tools/quake2/common/bspfile.c new file mode 100644 index 00000000..05808c51 --- /dev/null +++ b/tools/quake2/common/bspfile.c @@ -0,0 +1,789 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" +#include "scriplib.h" +#include "inout.h" + +void GetLeafNums (void); + +//============================================================================= + +int nummodels; +dmodel_t dmodels[MAX_MAP_MODELS]; + +int visdatasize; +byte dvisdata[MAX_MAP_VISIBILITY]; +dvis_t *dvis = (dvis_t *)dvisdata; + +int lightdatasize; +byte dlightdata[MAX_MAP_LIGHTING]; + +int entdatasize; +char dentdata[MAX_MAP_ENTSTRING]; + +int numleafs; +dleaf_t dleafs[MAX_MAP_LEAFS]; + +int numplanes; +dplane_t dplanes[MAX_MAP_PLANES]; + +int numvertexes; +dvertex_t dvertexes[MAX_MAP_VERTS]; + +int numnodes; +dnode_t dnodes[MAX_MAP_NODES]; + +int numtexinfo; +texinfo_t texinfo[MAX_MAP_TEXINFO]; + +int numfaces; +dface_t dfaces[MAX_MAP_FACES]; + +int numedges; +dedge_t dedges[MAX_MAP_EDGES]; + +int numleaffaces; +unsigned short dleaffaces[MAX_MAP_LEAFFACES]; + +int numleafbrushes; +unsigned short dleafbrushes[MAX_MAP_LEAFBRUSHES]; + +int numsurfedges; +int dsurfedges[MAX_MAP_SURFEDGES]; + +int numbrushes; +dbrush_t dbrushes[MAX_MAP_BRUSHES]; + +int numbrushsides; +dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +int numareas; +darea_t dareas[MAX_MAP_AREAS]; + +int numareaportals; +dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; + +byte dpop[256]; + +/* +=============== +CompressVis + +=============== +*/ +int CompressVis (byte *vis, byte *dest) +{ + int j; + int rep; + int visrow; + byte *dest_p; + + dest_p = dest; +// visrow = (r_numvisleafs + 7)>>3; + visrow = (dvis->numclusters + 7)>>3; + + for (j=0 ; j>3; + row = (dvis->numclusters+7)>>3; + out = decompressed; + + do + { + if (*in) + { + *out++ = *in++; + continue; + } + + c = in[1]; + if (!c) + Error ("DecompressVis: 0 repeat"); + in += 2; + while (c) + { + *out++ = 0; + c--; + } + } while (out - decompressed < row); +} + +//============================================================================= + +/* +============= +SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void SwapBSPFile (qboolean todisk) +{ + int i, j; + dmodel_t *d; + + +// models + for (i=0 ; ifirstface = LittleLong (d->firstface); + d->numfaces = LittleLong (d->numfaces); + d->headnode = LittleLong (d->headnode); + + for (j=0 ; j<3 ; j++) + { + d->mins[j] = LittleFloat(d->mins[j]); + d->maxs[j] = LittleFloat(d->maxs[j]); + d->origin[j] = LittleFloat(d->origin[j]); + } + } + +// +// vertexes +// + for (i=0 ; inumclusters; + else + j = LittleLong(dvis->numclusters); + dvis->numclusters = LittleLong (dvis->numclusters); + for (i=0 ; ibitofs[i][0] = LittleLong (dvis->bitofs[i][0]); + dvis->bitofs[i][1] = LittleLong (dvis->bitofs[i][1]); + } +} + + +dheader_t *header; + +int CopyLump (int lump, void *dest, int size) +{ + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if (length % size) + Error ("LoadBSPFile: odd lump size"); + + memcpy (dest, (byte *)header + ofs, length); + + return length / size; +} + +/* +============= +LoadBSPFile +============= +*/ +void LoadBSPFile (char *filename) +{ + int i; + +// +// load the file header +// + LoadFile (filename, (void **)&header); + +// swap the header + for (i=0 ; i< sizeof(dheader_t)/4 ; i++) + ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + + if (header->ident != IDBSPHEADER) + Error ("%s is not a IBSP file", filename); + if (header->version != BSPVERSION) + Error ("%s is version %i, not %i", filename, header->version, BSPVERSION); + + nummodels = CopyLump (LUMP_MODELS, dmodels, sizeof(dmodel_t)); + numvertexes = CopyLump (LUMP_VERTEXES, dvertexes, sizeof(dvertex_t)); + numplanes = CopyLump (LUMP_PLANES, dplanes, sizeof(dplane_t)); + numleafs = CopyLump (LUMP_LEAFS, dleafs, sizeof(dleaf_t)); + numnodes = CopyLump (LUMP_NODES, dnodes, sizeof(dnode_t)); + numtexinfo = CopyLump (LUMP_TEXINFO, texinfo, sizeof(texinfo_t)); + numfaces = CopyLump (LUMP_FACES, dfaces, sizeof(dface_t)); + numleaffaces = CopyLump (LUMP_LEAFFACES, dleaffaces, sizeof(dleaffaces[0])); + numleafbrushes = CopyLump (LUMP_LEAFBRUSHES, dleafbrushes, sizeof(dleafbrushes[0])); + numsurfedges = CopyLump (LUMP_SURFEDGES, dsurfedges, sizeof(dsurfedges[0])); + numedges = CopyLump (LUMP_EDGES, dedges, sizeof(dedge_t)); + numbrushes = CopyLump (LUMP_BRUSHES, dbrushes, sizeof(dbrush_t)); + numbrushsides = CopyLump (LUMP_BRUSHSIDES, dbrushsides, sizeof(dbrushside_t)); + numareas = CopyLump (LUMP_AREAS, dareas, sizeof(darea_t)); + numareaportals = CopyLump (LUMP_AREAPORTALS, dareaportals, sizeof(dareaportal_t)); + + visdatasize = CopyLump (LUMP_VISIBILITY, dvisdata, 1); + lightdatasize = CopyLump (LUMP_LIGHTING, dlightdata, 1); + entdatasize = CopyLump (LUMP_ENTITIES, dentdata, 1); + + CopyLump (LUMP_POP, dpop, 1); + + free (header); // everything has been copied out + +// +// swap everything +// + SwapBSPFile (false); +} + + +/* +============= +LoadBSPFileTexinfo + +Only loads the texinfo lump, so qdata can scan for textures +============= +*/ +void LoadBSPFileTexinfo (char *filename) +{ + int i; + FILE *f; + int length, ofs; + + header = malloc(sizeof(dheader_t)); + + f = fopen (filename, "rb"); + fread (header, sizeof(dheader_t), 1, f); + +// swap the header + for (i=0 ; i< sizeof(dheader_t)/4 ; i++) + ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + + if (header->ident != IDBSPHEADER) + Error ("%s is not a IBSP file", filename); + if (header->version != BSPVERSION) + Error ("%s is version %i, not %i", filename, header->version, BSPVERSION); + + + length = header->lumps[LUMP_TEXINFO].filelen; + ofs = header->lumps[LUMP_TEXINFO].fileofs; + + fseek (f, ofs, SEEK_SET); + fread (texinfo, length, 1, f); + fclose (f); + + numtexinfo = length / sizeof(texinfo_t); + + free (header); // everything has been copied out + + SwapBSPFile (false); +} + + +//============================================================================ + +FILE *wadfile; +dheader_t outheader; + +void AddLump (int lumpnum, void *data, int len) +{ + lump_t *lump; + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell(wadfile) ); + lump->filelen = LittleLong(len); + SafeWrite (wadfile, data, (len+3)&~3); +} + +/* +============= +WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void WriteBSPFile (char *filename) +{ + header = &outheader; + memset (header, 0, sizeof(dheader_t)); + + SwapBSPFile (true); + + header->ident = LittleLong (IDBSPHEADER); + header->version = LittleLong (BSPVERSION); + + wadfile = SafeOpenWrite (filename); + SafeWrite (wadfile, header, sizeof(dheader_t)); // overwritten later + + AddLump (LUMP_PLANES, dplanes, numplanes*sizeof(dplane_t)); + AddLump (LUMP_LEAFS, dleafs, numleafs*sizeof(dleaf_t)); + AddLump (LUMP_VERTEXES, dvertexes, numvertexes*sizeof(dvertex_t)); + AddLump (LUMP_NODES, dnodes, numnodes*sizeof(dnode_t)); + AddLump (LUMP_TEXINFO, texinfo, numtexinfo*sizeof(texinfo_t)); + AddLump (LUMP_FACES, dfaces, numfaces*sizeof(dface_t)); + AddLump (LUMP_BRUSHES, dbrushes, numbrushes*sizeof(dbrush_t)); + AddLump (LUMP_BRUSHSIDES, dbrushsides, numbrushsides*sizeof(dbrushside_t)); + AddLump (LUMP_LEAFFACES, dleaffaces, numleaffaces*sizeof(dleaffaces[0])); + AddLump (LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes*sizeof(dleafbrushes[0])); + AddLump (LUMP_SURFEDGES, dsurfedges, numsurfedges*sizeof(dsurfedges[0])); + AddLump (LUMP_EDGES, dedges, numedges*sizeof(dedge_t)); + AddLump (LUMP_MODELS, dmodels, nummodels*sizeof(dmodel_t)); + AddLump (LUMP_AREAS, dareas, numareas*sizeof(darea_t)); + AddLump (LUMP_AREAPORTALS, dareaportals, numareaportals*sizeof(dareaportal_t)); + + AddLump (LUMP_LIGHTING, dlightdata, lightdatasize); + AddLump (LUMP_VISIBILITY, dvisdata, visdatasize); + AddLump (LUMP_ENTITIES, dentdata, entdatasize); + AddLump (LUMP_POP, dpop, sizeof(dpop)); + + fseek (wadfile, 0, SEEK_SET); + SafeWrite (wadfile, header, sizeof(dheader_t)); + fclose (wadfile); +} + +//============================================================================ + +/* +============= +PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void PrintBSPFileSizes (void) +{ + if (!num_entities) + ParseEntities (); + + printf ("%5i models %7i\n" + ,nummodels, (int)(nummodels*sizeof(dmodel_t))); + printf ("%5i brushes %7i\n" + ,numbrushes, (int)(numbrushes*sizeof(dbrush_t))); + printf ("%5i brushsides %7i\n" + ,numbrushsides, (int)(numbrushsides*sizeof(dbrushside_t))); + printf ("%5i planes %7i\n" + ,numplanes, (int)(numplanes*sizeof(dplane_t))); + printf ("%5i texinfo %7i\n" + ,numtexinfo, (int)(numtexinfo*sizeof(texinfo_t))); + printf ("%5i entdata %7i\n", num_entities, entdatasize); + + printf ("\n"); + + printf ("%5i vertexes %7i\n" + ,numvertexes, (int)(numvertexes*sizeof(dvertex_t))); + printf ("%5i nodes %7i\n" + ,numnodes, (int)(numnodes*sizeof(dnode_t))); + printf ("%5i faces %7i\n" + ,numfaces, (int)(numfaces*sizeof(dface_t))); + printf ("%5i leafs %7i\n" + ,numleafs, (int)(numleafs*sizeof(dleaf_t))); + printf ("%5i leaffaces %7i\n" + ,numleaffaces, (int)(numleaffaces*sizeof(dleaffaces[0]))); + printf ("%5i leafbrushes %7i\n" + ,numleafbrushes, (int)(numleafbrushes*sizeof(dleafbrushes[0]))); + printf ("%5i surfedges %7i\n" + ,numsurfedges, (int)(numsurfedges*sizeof(dsurfedges[0]))); + printf ("%5i edges %7i\n" + ,numedges, (int)(numedges*sizeof(dedge_t))); + printf (" lightdata %7i\n", lightdatasize); + printf (" visdata %7i\n", visdatasize); +} + + +//============================================ + +int num_entities; +entity_t entities[MAX_MAP_ENTITIES]; + +void StripTrailing (char *e) +{ + char *s; + + s = e + strlen(e)-1; + while (s >= e && *s <= 32) + { + *s = 0; + s--; + } +} + +/* +================= +ParseEpair +================= +*/ +epair_t *ParseEpair (void) +{ + epair_t *e; + + e = malloc (sizeof(epair_t)); + memset (e, 0, sizeof(epair_t)); + + if (strlen(token) >= MAX_KEY-1) + Error ("ParseEpar: token too long"); + e->key = copystring(token); + GetToken (false); + if (strlen(token) >= MAX_VALUE-1) + Error ("ParseEpar: token too long"); + e->value = copystring(token); + + // strip trailing spaces + StripTrailing (e->key); + StripTrailing (e->value); + + return e; +} + + +/* +================ +ParseEntity +================ +*/ +qboolean ParseEntity (void) +{ + epair_t *e; + entity_t *mapent; + + if (!GetToken (true)) + return false; + + if (strcmp (token, "{") ) + Error ("ParseEntity: { not found"); + + if (num_entities == MAX_MAP_ENTITIES) + Error ("num_entities == MAX_MAP_ENTITIES"); + + mapent = &entities[num_entities]; + num_entities++; + + do + { + if (!GetToken (true)) + Error ("ParseEntity: EOF without closing brace"); + if (!strcmp (token, "}") ) + break; + e = ParseEpair (); + e->next = mapent->epairs; + mapent->epairs = e; + } while (1); + + return true; +} + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void ParseEntities (void) +{ + num_entities = 0; + ParseFromMemory (dentdata, entdatasize); + + while (ParseEntity ()) + { + } +} + + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void UnparseEntities (void) +{ + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + char key[1024], value[1024]; + + buf = dentdata; + end = buf; + *end = 0; + + for (i=0 ; inext) + { + strcpy (key, ep->key); + StripTrailing (key); + strcpy (value, ep->value); + StripTrailing (value); + + sprintf (line, "\"%s\" \"%s\"\n", key, value); + strcat (end, line); + end += strlen(line); + } + strcat (end,"}\n"); + end += 2; + + if (end > buf + MAX_MAP_ENTSTRING) + Error ("Entity text too long"); + } + entdatasize = end - buf + 1; +} + +void PrintEntity (entity_t *ent) +{ + epair_t *ep; + + printf ("------- entity %p -------\n", ent); + for (ep=ent->epairs ; ep ; ep=ep->next) + { + printf ("%s = %s\n", ep->key, ep->value); + } + +} + +void SetKeyValue (entity_t *ent, char *key, char *value) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + { + free (ep->value); + ep->value = copystring(value); + return; + } + ep = malloc (sizeof(*ep)); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring(key); + ep->value = copystring(value); +} + +char *ValueForKey (entity_t *ent, char *key) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + return ep->value; + return ""; +} + +vec_t FloatForKey (entity_t *ent, char *key) +{ + char *k; + + k = ValueForKey (ent, key); + return atof(k); +} + +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec) +{ + char *k; + double v1, v2, v3; + + k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} + + diff --git a/tools/quake2/common/bspfile.h b/tools/quake2/common/bspfile.h new file mode 100644 index 00000000..e11ec208 --- /dev/null +++ b/tools/quake2/common/bspfile.h @@ -0,0 +1,128 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qfiles.h" + + +extern int nummodels; +extern dmodel_t dmodels[MAX_MAP_MODELS]; + +extern int visdatasize; +extern byte dvisdata[MAX_MAP_VISIBILITY]; +extern dvis_t *dvis; + +extern int lightdatasize; +extern byte dlightdata[MAX_MAP_LIGHTING]; + +extern int entdatasize; +extern char dentdata[MAX_MAP_ENTSTRING]; + +extern int numleafs; +extern dleaf_t dleafs[MAX_MAP_LEAFS]; + +extern int numplanes; +extern dplane_t dplanes[MAX_MAP_PLANES]; + +extern int numvertexes; +extern dvertex_t dvertexes[MAX_MAP_VERTS]; + +extern int numnodes; +extern dnode_t dnodes[MAX_MAP_NODES]; + +extern int numtexinfo; +extern texinfo_t texinfo[MAX_MAP_TEXINFO]; + +extern int numfaces; +extern dface_t dfaces[MAX_MAP_FACES]; + +extern int numedges; +extern dedge_t dedges[MAX_MAP_EDGES]; + +extern int numleaffaces; +extern unsigned short dleaffaces[MAX_MAP_LEAFFACES]; + +extern int numleafbrushes; +extern unsigned short dleafbrushes[MAX_MAP_LEAFBRUSHES]; + +extern int numsurfedges; +extern int dsurfedges[MAX_MAP_SURFEDGES]; + +extern int numareas; +extern darea_t dareas[MAX_MAP_AREAS]; + +extern int numareaportals; +extern dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; + +extern int numbrushes; +extern dbrush_t dbrushes[MAX_MAP_BRUSHES]; + +extern int numbrushsides; +extern dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +extern byte dpop[256]; + +void DecompressVis (byte *in, byte *decompressed); +int CompressVis (byte *vis, byte *dest); + +void LoadBSPFile (char *filename); +void LoadBSPFileTexinfo (char *filename); // just for qdata +void WriteBSPFile (char *filename); +void PrintBSPFileSizes (void); + +//=============== + + +typedef struct epair_s +{ + struct epair_s *next; + char *key; + char *value; +} epair_t; + +typedef struct +{ + vec3_t origin; + int firstbrush; + int numbrushes; + epair_t *epairs; + +// only valid for func_areaportals + int areaportalnum; + int portalareas[2]; +} entity_t; + +extern int num_entities; +extern entity_t entities[MAX_MAP_ENTITIES]; + +void ParseEntities (void); +void UnparseEntities (void); + +void SetKeyValue (entity_t *ent, char *key, char *value); +char *ValueForKey (entity_t *ent, char *key); +// will return "" if not present + +vec_t FloatForKey (entity_t *ent, char *key); +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec); + +epair_t *ParseEpair (void); + +void PrintEntity (entity_t *ent); + diff --git a/tools/quake2/common/cmdlib.c b/tools/quake2/common/cmdlib.c new file mode 100644 index 00000000..e59bf215 --- /dev/null +++ b/tools/quake2/common/cmdlib.c @@ -0,0 +1,1221 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// Nurail: Swiped from quake3/common + +#include "cmdlib.h" +#include "mathlib.h" +#include "inout.h" +#include +#include + +#ifdef WIN32 +#include +#include +#endif + +#if defined (__linux__) || defined (__APPLE__) +#include +#endif + +#ifdef NeXT +#include +#endif + +#define BASEDIRNAME "quake" // assumed to have a 2 or 3 following +#define HERETIC2_BASEDIRNAME "h" +#define PATHSEPERATOR '/' + +// qboolean verbose = false; + +#ifdef SAFE_MALLOC +void *safe_malloc( size_t size ) +{ + void *p; + + p = malloc(size); + if(!p) + Error ("safe_malloc failed on allocation of %i bytes", size); + + memset(p, 0, size); + return p; +} + +void *safe_malloc_info( size_t size, char* info ) +{ + void *p; + + p = malloc(size); + if(!p) + Error ("%s: safe_malloc failed on allocation of %i bytes", info, size); + + memset(p, 0, size); + return p; +} +#endif + +// set these before calling CheckParm +int myargc; +char **myargv; + +char com_token[1024]; +qboolean com_eof; + +qboolean archive; +char archivedir[1024]; + + +/* +=================== +ExpandWildcards + +Mimic unix command line expansion +=================== +*/ +#define MAX_EX_ARGC 1024 +int ex_argc; +char *ex_argv[MAX_EX_ARGC]; +#ifdef WIN32 +#include "io.h" +void ExpandWildcards( int *argc, char ***argv ) +{ + struct _finddata_t fileinfo; + int handle; + int i; + char filename[1024]; + char filebase[1024]; + char *path; + + ex_argc = 0; + for (i=0 ; i<*argc ; i++) + { + path = (*argv)[i]; + if ( path[0] == '-' + || ( !strstr(path, "*") && !strstr(path, "?") ) ) + { + ex_argv[ex_argc++] = path; + continue; + } + + handle = _findfirst (path, &fileinfo); + if (handle == -1) + return; + + ExtractFilePath (path, filebase); + + do + { + sprintf (filename, "%s%s", filebase, fileinfo.name); + ex_argv[ex_argc++] = copystring (filename); + } while (_findnext( handle, &fileinfo ) != -1); + + _findclose (handle); + } + + *argc = ex_argc; + *argv = ex_argv; +} +#else +void ExpandWildcards (int *argc, char ***argv) +{ +} +#endif + +/* + +qdir will hold the path up to the quake directory, including the slash + + f:\quake\ + /raid/quake/ + +gamedir will hold qdir + the game directory (id1, id2, etc) + +*/ + +char qdir[1024]; +char gamedir[1024]; +char writedir[1024]; + +void SetQdirFromPath( const char *path ) +{ + char temp[1024]; + const char *c; + const char *sep; + int len, count; + char basedirname[256]; + + if (!(path[0] == '/' || path[0] == '\\' || path[1] == ':')) + { // path is partial + Q_getwd (temp); + strcat (temp, path); + path = temp; + } + + // search for "quake2" in path + + if ( !strcmp( game, "heretic2" ) ) + strncpy(basedirname, HERETIC2_BASEDIRNAME, 256); + else + strncpy(basedirname, BASEDIRNAME, 256); + + len = strlen(basedirname); + for (c=path+strlen(path)-1 ; c != path ; c--) + { + int i; + + if (!Q_strncasecmp (c, basedirname, len)) + { + // + //strncpy (qdir, path, c+len+2-path); + // the +2 assumes a 2 or 3 following quake which is not the + // case with a retail install + // so we need to add up how much to the next separator + sep = c + len; + count = 1; + while (*sep && *sep != '/' && *sep != '\\') + { + sep++; + count++; + } + strncpy (qdir, path, c+len+count-path); + Sys_FPrintf( SYS_VRB, "qdir: %s\n", qdir); + for ( i = 0; i < strlen( qdir ); i++ ) + { + if ( qdir[i] == '\\' ) + qdir[i] = '/'; + } + + c += len+count; + while (*c) + { + if (*c == '/' || *c == '\\') + { + strncpy (gamedir, path, c+1-path); + + for ( i = 0; i < strlen( gamedir ); i++ ) + { + if ( gamedir[i] == '\\' ) + gamedir[i] = '/'; + } + + Sys_FPrintf( SYS_VRB, "gamedir: %s\n", gamedir); + + if ( !writedir[0] ) + strcpy( writedir, gamedir ); + else if ( writedir[strlen( writedir )-1] != '/' ) + { + writedir[strlen( writedir )] = '/'; + writedir[strlen( writedir )+1] = 0; + } + + return; + } + c++; + } + Error ("No gamedir in %s", path); + return; + } + } + Error ("SetQdirFromPath: no '%s' in %s", basedirname, path); +} + +char *ExpandArg (const char *path) +{ + static char full[1024]; + + if (path[0] != '/' && path[0] != '\\' && path[1] != ':') + { + Q_getwd (full); + strcat (full, path); + } + else + strcpy (full, path); + return full; +} + +char *ExpandPath (const char *path) +{ + static char full[1024]; + if (!qdir) + Error ("ExpandPath called without qdir set"); + if (path[0] == '/' || path[0] == '\\' || path[1] == ':') { + strcpy( full, path ); + return full; + } + sprintf (full, "%s%s", qdir, path); + return full; +} + +char *ExpandGamePath (const char *path) +{ + static char full[1024]; + if (!qdir) + Error ("ExpandGamePath called without qdir set"); + if (path[0] == '/' || path[0] == '\\' || path[1] == ':') { + strcpy( full, path ); + return full; + } + sprintf (full, "%s%s", gamedir, path); + return full; +} + +char *ExpandPathAndArchive (const char *path) +{ + char *expanded; + char archivename[1024]; + + expanded = ExpandPath (path); + + if (archive) + { + sprintf (archivename, "%s/%s", archivedir, path); + QCopyFile (expanded, archivename); + } + return expanded; +} + + +char *copystring(const char *s) +{ + char *b; + b = safe_malloc(strlen(s)+1); + strcpy (b, s); + return b; +} + + + +/* +================ +I_FloatTime +================ +*/ +double I_FloatTime (void) +{ + time_t t; + + time (&t); + + return t; +#if 0 +// more precise, less portable + struct timeval tp; + struct timezone tzp; + static int secbase; + + gettimeofday(&tp, &tzp); + + if (!secbase) + { + secbase = tp.tv_sec; + return tp.tv_usec/1000000.0; + } + + return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0; +#endif +} + +void Q_getwd (char *out) +{ + int i = 0; + +#ifdef WIN32 + _getcwd (out, 256); + strcat (out, "\\"); +#else + // Gef: Changed from getwd() to getcwd() to avoid potential buffer overflow + getcwd (out, 256); + strcat (out, "/"); +#endif + while ( out[i] != 0 ) + { + if ( out[i] == '\\' ) + out[i] = '/'; + i++; + } +} + + +void Q_mkdir (const char *path) +{ +#ifdef WIN32 + if (_mkdir (path) != -1) + return; +#else + if (mkdir (path, 0777) != -1) + return; +#endif + if (errno != EEXIST) + Error ("mkdir %s: %s",path, strerror(errno)); +} + +/* +============ +FileTime + +returns -1 if not present +============ +*/ +int FileTime (const char *path) +{ + struct stat buf; + + if (stat (path,&buf) == -1) + return -1; + + return buf.st_mtime; +} + + + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char *data) +{ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + { + com_eof = true; + return NULL; // end of file; + } + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + do + { + c = *data++; + if (c=='\"') + { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } while (1); + } + +// parse single characters + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') + { + com_token[len] = c; + len++; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + com_token[len] = c; + data++; + len++; + c = *data; + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') + break; + } while (c>32); + + com_token[len] = 0; + return data; +} + +int Q_strncasecmp (const char *s1, const char *s2, int n) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (!n--) + return 0; // strings are equal until end point + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return -1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + +int Q_stricmp (const char *s1, const char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + +int Q_strcasecmp (const char *s1, const char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + + +// NOTE TTimo when switching to Multithread DLL (Release/Debug) in the config +// started getting warnings about that function, prolly a duplicate with the runtime function +// maybe we still need to have it in linux builds +/* +char *strupr (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = toupper(*in); + in++; + } + return start; +} +*/ + +char *strlower (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = tolower(*in); + in++; + } + return start; +} + + +/* +============================================================================= + + MISC FUNCTIONS + +============================================================================= +*/ + + +/* +================= +CheckParm + +Checks for the given parameter in the program's command line arguments +Returns the argument number (1 to argc-1) or 0 if not present +================= +*/ +int CheckParm (const char *check) +{ + int i; + + for (i = 1;i 0) { + nAllocSize += MEM_BLOCKSIZE - nBlock; + } + buffer = safe_malloc (nAllocSize+1); + memset(buffer, 0, nAllocSize+1); + SafeRead (f, buffer, length); + fclose (f); + + *bufferptr = buffer; + return length; +} + + +/* +============== +TryLoadFile + +Allows failure +============== +*/ +int TryLoadFile (const char *filename, void **bufferptr) +{ + FILE *f; + int length; + void *buffer; + + *bufferptr = NULL; + + f = fopen (filename, "rb"); + if (!f) + return -1; + length = Q_filelength (f); + buffer = safe_malloc (length+1); + ((char *)buffer)[length] = 0; + SafeRead (f, buffer, length); + fclose (f); + + *bufferptr = buffer; + return length; +} + + +/* +============== +SaveFile +============== +*/ +void SaveFile (const char *filename, const void *buffer, int count) +{ + FILE *f; + + f = SafeOpenWrite (filename); + SafeWrite (f, buffer, count); + fclose (f); +} + + + +void DefaultExtension (char *path, const char *extension) +{ + char *src; +// +// if path doesnt have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen(path) - 1; + + while (*src != '/' && *src != '\\' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + strcat (path, extension); +} + + +void DefaultPath (char *path, const char *basepath) +{ + char temp[128]; + + if( path[ 0 ] == '/' || path[ 0 ] == '\\' ) + return; // absolute path location + strcpy (temp,path); + strcpy (path,basepath); + strcat (path,temp); +} + + +void StripFilename (char *path) +{ + int length; + + length = strlen(path)-1; + while (length > 0 && path[length] != '/' && path[ length ] != '\\' ) + length--; + path[length] = 0; +} + +void StripExtension (char *path) +{ + int length; + + length = strlen(path)-1; + while (length > 0 && path[length] != '.') + { + length--; + if (path[length] == '/' || path[ length ] == '\\' ) + return; // no extension + } + if (length) + path[length] = 0; +} + + +/* +==================== +Extract file parts +==================== +*/ +// FIXME: should include the slash, otherwise +// backing to an empty path will be wrong when appending a slash +void ExtractFilePath (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && *(src-1) != '\\' && *(src-1) != '/') + src--; + + memcpy (dest, path, src-path); + dest[src-path] = 0; +} + +void ExtractFileBase (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && *(src-1) != '/' && *(src-1) != '\\' ) + src--; + + while (*src && *src != '.') + { + *dest++ = *src++; + } + *dest = 0; +} + +void ExtractFileExtension (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a . or the start +// + while (src != path && *(src-1) != '.') + src--; + if (src == path) + { + *dest = 0; // no extension + return; + } + + strcpy (dest,src); +} + + +/* +============== +ParseNum / ParseHex +============== +*/ +int ParseHex (const char *hex) +{ + const char *str; + int num; + + num = 0; + str = hex; + + while (*str) + { + num <<= 4; + if (*str >= '0' && *str <= '9') + num += *str-'0'; + else if (*str >= 'a' && *str <= 'f') + num += 10 + *str-'a'; + else if (*str >= 'A' && *str <= 'F') + num += 10 + *str-'A'; + else + Error ("Bad hex number: %s",hex); + str++; + } + + return num; +} + + +int ParseNum (const char *str) +{ + if (str[0] == '$') + return ParseHex (str+1); + if (str[0] == '0' && str[1] == 'x') + return ParseHex (str+2); + return atol (str); +} +/* +// all output ends up through here +void FPrintf (int flag, char *buf) +{ + printf(buf); + +} + +void Sys_FPrintf (int flag, const char *format, ...) +{ + char out_buffer[4096]; + va_list argptr; + + if ((flag == SYS_VRB) && (verbose == false)) + return; + + va_start (argptr, format); + vsprintf (out_buffer, format, argptr); + va_end (argptr); + + FPrintf (flag, out_buffer); +} + +void Sys_Printf (const char *format, ...) +{ + char out_buffer[4096]; + va_list argptr; + + va_start (argptr, format); + vsprintf (out_buffer, format, argptr); + va_end (argptr); + + FPrintf (SYS_STD, out_buffer); +} + + +//================= +//Error +// +//For abnormal program terminations +//================= + +void Error( const char *error, ...) +{ + char out_buffer[4096]; + char tmp[4096]; + va_list argptr; + + va_start (argptr,error); + vsprintf (tmp, error, argptr); + va_end (argptr); + + sprintf( out_buffer, "************ ERROR ************\n%s\n", tmp ); + + FPrintf( SYS_ERR, out_buffer ); + + exit (1); +} +*/ + + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +#ifdef _SGI_SOURCE +#define __BIG_ENDIAN__ +#endif + +#ifdef __BIG_ENDIAN__ + +short LittleShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short BigShort (short l) +{ + return l; +} + + +int LittleLong (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int BigLong (int l) +{ + return l; +} + + +float LittleFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float BigFloat (float l) +{ + return l; +} + + +#else + + +short BigShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short LittleShort (short l) +{ + return l; +} + + +int BigLong (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int LittleLong (int l) +{ + return l; +} + +float BigFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float LittleFloat (float l) +{ + return l; +} + + +#endif + + +//======================================================= + + +// FIXME: byte swap? + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +static unsigned short crctable[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +void CRC_Init(unsigned short *crcvalue) +{ + *crcvalue = CRC_INIT_VALUE; +} + +void CRC_ProcessByte(unsigned short *crcvalue, byte data) +{ + *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; +} + +unsigned short CRC_Value(unsigned short crcvalue) +{ + return crcvalue ^ CRC_XOR_VALUE; +} +//============================================================================= + +/* +============ +CreatePath +============ +*/ +void CreatePath (const char *path) +{ + const char *ofs; + char c; + char dir[1024]; + +#ifdef WIN32 + int olddrive = -1; + + if ( path[1] == ':' ) + { + olddrive = _getdrive(); + _chdrive( toupper( path[0] ) - 'A' + 1 ); + } +#endif + + if (path[1] == ':') + path += 2; + + for (ofs = path+1 ; *ofs ; ofs++) + { + c = *ofs; + if (c == '/' || c == '\\') + { // create the directory + memcpy( dir, path, ofs - path ); + dir[ ofs - path ] = 0; + Q_mkdir( dir ); + } + } + +#ifdef WIN32 + if ( olddrive != -1 ) + { + _chdrive( olddrive ); + } +#endif +} + + +/* +============ +QCopyFile + + Used to archive source files +============ +*/ +void QCopyFile (const char *from, const char *to) +{ + void *buffer; + int length; + + length = LoadFile (from, &buffer); + CreatePath (to); + SaveFile (to, buffer, length); + free (buffer); +} + +void Sys_Sleep(int n) +{ +#ifdef WIN32 + Sleep (n); +#endif +#if defined (__linux__) || defined (__APPLE__) + usleep (n * 1000); +#endif +} diff --git a/tools/quake2/common/cmdlib.h b/tools/quake2/common/cmdlib.h new file mode 100644 index 00000000..42bd1dee --- /dev/null +++ b/tools/quake2/common/cmdlib.h @@ -0,0 +1,170 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// cmdlib.h + +#ifndef __CMDLIB__ +#define __CMDLIB__ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 + #ifdef NDEBUG // Don't show in a Release build + #pragma warning(disable : 4305) // truncate from double to float + #pragma warning(disable : 4244) // conversion from double to float + #pragma warning(disable : 4018) // signed/unsigned mismatch + #endif +#endif + +#ifdef WIN32 + #pragma intrinsic( memset, memcpy ) +#endif + +#ifndef __BYTEBOOL__ + #define __BYTEBOOL__ + typedef enum {false, true} qboolean; + typedef unsigned char byte; +#endif + +#define MAX_OS_PATH 1024 +#define MEM_BLOCKSIZE 4096 + +/* +extern qboolean verbose; +#define SYS_VRB 0 // verbose support (on/off) +#define SYS_STD 1 // standard print level +#define SYS_WRN 2 // warnings +#define SYS_ERR 3 // error +*/ + +// the dec offsetof macro doesnt work very well... +#define myoffsetof(type,identifier) ((size_t)&((type *)0)->identifier) + +#define SAFE_MALLOC +#ifdef SAFE_MALLOC +void *safe_malloc( size_t size ); +void *safe_malloc_info( size_t size, char* info ); +#else +#define safe_malloc(a) malloc(a) +#endif /* SAFE_MALLOC */ + +// set these before calling CheckParm +extern int myargc; +extern char **myargv; + +char *strlower (char *in); +int Q_strncasecmp( const char *s1, const char *s2, int n ); +int Q_strcasecmp( const char *s1, const char *s2 ); +int Q_stricmp( const char *s1, const char *s2 ); +void Q_getwd( char *out ); + +int Q_filelength (FILE *f); +int FileTime( const char *path ); + +void Q_mkdir( const char *path ); + +extern char qdir[1024]; +extern char gamedir[1024]; +extern char writedir[1024]; +extern char *moddirparam; +void SetQdirFromPath( const char *path); +char *ExpandArg( const char *path ); // from cmd line +char *ExpandPath( const char *path ); // from scripts +char *ExpandGamePath (const char *path); +char *ExpandPathAndArchive( const char *path ); +void ExpandWildcards( int *argc, char ***argv ); + + +double I_FloatTime( void ); + +int CheckParm( const char *check ); + +FILE *SafeOpenWrite( const char *filename ); +FILE *SafeOpenRead( const char *filename ); +void SafeRead (FILE *f, void *buffer, int count); +void SafeWrite (FILE *f, const void *buffer, int count); + +int LoadFile( const char *filename, void **bufferptr ); +int LoadFileBlock( const char *filename, void **bufferptr ); +int TryLoadFile( const char *filename, void **bufferptr ); +void SaveFile( const char *filename, const void *buffer, int count ); +qboolean FileExists( const char *filename ); + +void DefaultExtension( char *path, const char *extension ); +void DefaultPath( char *path, const char *basepath ); +void StripFilename( char *path ); +void StripExtension( char *path ); + +void ExtractFilePath( const char *path, char *dest ); +void ExtractFileBase( const char *path, char *dest ); +void ExtractFileExtension( const char *path, char *dest ); + +int ParseNum (const char *str); + +//void Sys_Printf (const char *text, ...); +//void Sys_FPrintf (int flag, const char *text, ...); +//void Error( const char *error, ... ); + +short BigShort (short l); +short LittleShort (short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); + + +char *COM_Parse (char *data); + +extern char com_token[1024]; +extern qboolean com_eof; + +char *copystring(const char *s); + + +void CRC_Init(unsigned short *crcvalue); +void CRC_ProcessByte(unsigned short *crcvalue, byte data); +unsigned short CRC_Value(unsigned short crcvalue); + +void CreatePath( const char *path ); +void QCopyFile( const char *from, const char *to ); + +extern qboolean archive; +extern char archivedir[1024]; + +// sleep for the given amount of milliseconds +void Sys_Sleep(int n); + +// for compression routines +typedef struct +{ + byte *data; + int count; +} cblock_t; + +extern char game[64]; + +#endif diff --git a/tools/quake2/common/inout.c b/tools/quake2/common/inout.c new file mode 100644 index 00000000..3cf66e7f --- /dev/null +++ b/tools/quake2/common/inout.c @@ -0,0 +1,367 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//----------------------------------------------------------------------------- +// +// +// DESCRIPTION: +// deal with in/out tasks, for either stdin/stdout or network/XML stream +// + +#include "cmdlib.h" +#include "mathlib.h" +#include "polylib.h" +#include "inout.h" +#include +#include + +#ifdef WIN32 +#include +#include +#endif + +// network broadcasting +#include "l_net/l_net.h" +#include "libxml/tree.h" + +#ifdef WIN32 +HWND hwndOut = NULL; +qboolean lookedForServer = false; +UINT wm_BroadcastCommand = -1; +#endif + +socket_t *brdcst_socket; +netmessage_t msg; + +qboolean verbose = false; + +// our main document +// is streamed through the network to Radiant +// possibly written to disk at the end of the run +//++timo FIXME: need to be global, required when creating nodes? +xmlDocPtr doc; +xmlNodePtr tree; + +// some useful stuff +xmlNodePtr xml_NodeForVec( vec3_t v ) +{ + xmlNodePtr ret; + char buf[1024]; + + sprintf (buf, "%f %f %f", v[0], v[1], v[2]); + ret = xmlNewNode (NULL, "point"); + xmlNodeSetContent (ret, buf); + return ret; +} + +// send a node down the stream, add it to the document +void xml_SendNode (xmlNodePtr node) +{ + xmlBufferPtr xml_buf; + char xmlbuf[MAX_NETMESSAGE]; // we have to copy content from the xmlBufferPtr into an aux buffer .. that sucks .. + // this index loops through the node buffer + int pos = 0; + int size; + + xmlAddChild( doc->children, node ); + + if (brdcst_socket) + { + xml_buf = xmlBufferCreate(); + xmlNodeDump( xml_buf, doc, node, 0, 0 ); + + // the XML node might be too big to fit in a single network message + // l_net library defines an upper limit of MAX_NETMESSAGE + // there are some size check errors, so we use MAX_NETMESSAGE-10 to be safe + // if the size of the buffer exceeds MAX_NETMESSAGE-10 we'll send in several network messages + while (pos < xml_buf->use) + { + // what size are we gonna send now? + (xml_buf->use - pos < MAX_NETMESSAGE - 10) ? (size = xml_buf->use - pos) : (size = MAX_NETMESSAGE - 10); + //++timo just a debug thing + if (size == MAX_NETMESSAGE - 10) + Sys_FPrintf (SYS_NOXML, "Got to split the buffer\n"); + memcpy( xmlbuf, xml_buf->content+pos, size); + xmlbuf[size] = '\0'; + NMSG_Clear( &msg ); + NMSG_WriteString (&msg, xmlbuf ); + Net_Send(brdcst_socket, &msg ); + // now that the thing is sent prepare to loop again + pos += size; + } + +#if 0 + // NOTE: the NMSG_WriteString is limited to MAX_NETMESSAGE + // we will need to split into chunks + // (we could also go lower level, in the end it's using send and receiv which are not size limited) + //++timo FIXME: MAX_NETMESSAGE is not exactly the max size we can stick in the message + // there's some tweaking to do in l_net for that .. so let's give us a margin for now + + //++timo we need to handle the case of a buffer too big to fit in a single message + // try without checks for now + if (xml_buf->use > MAX_NETMESSAGE-10 ) + { + // if we send that we are probably gonna break the stream at the other end.. + // and Error will call right there + //Error( "MAX_NETMESSAGE exceeded for XML feedback stream in FPrintf (%d)\n", xml_buf->use); + Sys_FPrintf (SYS_NOXML, "MAX_NETMESSAGE exceeded for XML feedback stream in FPrintf (%d)\n", xml_buf->use); + xml_buf->content[xml_buf->use]='\0'; //++timo this corrupts the buffer but we don't care it's for printing + Sys_FPrintf (SYS_NOXML, xml_buf->content); + + } + + size = xml_buf->use; + memcpy( xmlbuf, xml_buf->content, size ); + xmlbuf[size] = '\0'; + NMSG_Clear( &msg ); + NMSG_WriteString (&msg, xmlbuf ); + Net_Send(brdcst_socket, &msg ); +#endif + + xmlBufferFree( xml_buf ); + } +} + +void xml_Select (char *msg, int entitynum, int brushnum, qboolean bError) +{ + xmlNodePtr node, select; + char buf[1024]; + char level[2]; + + // now build a proper "select" XML node + sprintf (buf, "Entity %i, Brush %i: %s", entitynum, brushnum, msg); + node = xmlNewNode (NULL, "select"); + xmlNodeSetContent (node, buf); + level[0] = (int)'0' + (bError ? SYS_ERR : SYS_WRN) ; + level[1] = 0; + xmlSetProp (node, "level", (char *)&level); + // a 'select' information + sprintf (buf, "%i %i", entitynum, brushnum); + select = xmlNewNode (NULL, "brush"); + xmlNodeSetContent (select, buf); + xmlAddChild (node, select); + xml_SendNode (node); + + sprintf (buf, "Entity %i, Brush %i: %s", entitynum, brushnum, msg); + if (bError) + Error(buf); + else + Sys_FPrintf (SYS_NOXML, "%s\n", buf); + +} + +void xml_Point (char *msg, vec3_t pt) +{ + xmlNodePtr node, point; + char buf[1024]; + char level[2]; + + node = xmlNewNode (NULL, "pointmsg"); + xmlNodeSetContent (node, msg); + level[0] = (int)'0' + SYS_ERR; + level[1] = 0; + xmlSetProp (node, "level", (char *)&level); + // a 'point' node + sprintf (buf, "%g %g %g", pt[0], pt[1], pt[2]); + point = xmlNewNode (NULL, "point"); + xmlNodeSetContent (point, buf); + xmlAddChild (node, point); + xml_SendNode (node); + + sprintf (buf, "%s (%g %g %g)", msg, pt[0], pt[1], pt[2]); + Error (buf); +} + +#define WINDING_BUFSIZE 2048 +void xml_Winding (char *msg, vec3_t p[], int numpoints, qboolean die) +{ + xmlNodePtr node, winding; + char buf[WINDING_BUFSIZE]; + char smlbuf[128]; + char level[2]; + int i; + + node = xmlNewNode (NULL, "windingmsg"); + xmlNodeSetContent (node, msg); + level[0] = (int)'0' + SYS_ERR; + level[1] = 0; + xmlSetProp (node, "level", (char *)&level); + // a 'winding' node + sprintf( buf, "%i ", numpoints); + for(i = 0; i < numpoints; i++) + { + sprintf (smlbuf, "(%g %g %g)", p[i][0], p[i][1], p[i][2]); + // don't overflow + if (strlen(buf)+strlen(smlbuf)>WINDING_BUFSIZE) + break; + strcat( buf, smlbuf); + } + + winding = xmlNewNode (NULL, "winding"); + xmlNodeSetContent (winding, buf); + xmlAddChild (node, winding); + xml_SendNode (node); + + if(die) + Error (msg); + else + { + Sys_Printf(msg); + Sys_Printf("\n"); + } +} + +// in include +#include "stream_version.h" + +void Broadcast_Setup( const char *dest ) +{ + address_t address; + char sMsg[1024]; + + Net_Setup(); + Net_StringToAddress((char *)dest, &address); + brdcst_socket = Net_Connect(&address, 0); + if (brdcst_socket) + { + // send in a header + sprintf (sMsg, ""); + NMSG_Clear( &msg ); + NMSG_WriteString(&msg, sMsg ); + Net_Send(brdcst_socket, &msg ); + } +} + +void Broadcast_Shutdown() +{ + if (brdcst_socket) + { + Sys_Printf("Disconnecting\n"); + Net_Disconnect(brdcst_socket); + brdcst_socket = NULL; + } +} + +// all output ends up through here +void FPrintf (int flag, char *buf) +{ + xmlNodePtr node; + static qboolean bGotXML = false; + char level[2]; + + printf(buf); + + // the following part is XML stuff only.. but maybe we don't want that message to go down the XML pipe? + if (flag == SYS_NOXML) + return; + + // ouput an XML file of the run + // use the DOM interface to build a tree + /* + + message string + .. various nodes to describe corresponding geometry .. + + */ + if (!bGotXML) + { + // initialize + doc = xmlNewDoc("1.0"); + doc->children = xmlNewDocRawNode(doc, NULL, "q3map_feedback", NULL); + bGotXML = true; + } + node = xmlNewNode (NULL, "message"); + xmlNodeSetContent (node, buf); + level[0] = (int)'0' + flag; + level[1] = 0; + xmlSetProp (node, "level", (char *)&level ); + + xml_SendNode (node); +} + +#ifdef DBG_XML +void DumpXML() +{ + xmlSaveFile( "XMLDump.xml", doc ); +} +#endif + +void Sys_FPrintf (int flag, const char *format, ...) +{ + char out_buffer[4096]; + va_list argptr; + + if ((flag == SYS_VRB) && (verbose == false)) + return; + + va_start (argptr, format); + vsprintf (out_buffer, format, argptr); + va_end (argptr); + + FPrintf (flag, out_buffer); +} + +void Sys_Printf (const char *format, ...) +{ + char out_buffer[4096]; + va_list argptr; + + va_start (argptr, format); + vsprintf (out_buffer, format, argptr); + va_end (argptr); + + FPrintf (SYS_STD, out_buffer); +} + +/* +================= +Error + +For abnormal program terminations +================= +*/ +void Error( const char *error, ...) +{ + char out_buffer[4096]; + char tmp[4096]; + va_list argptr; + + va_start (argptr,error); + vsprintf (tmp, error, argptr); + va_end (argptr); + + sprintf( out_buffer, "************ ERROR ************\n%s\n", tmp ); + + FPrintf( SYS_ERR, out_buffer ); + +#ifdef DBG_XML + DumpXML(); +#endif + + //++timo HACK ALERT .. if we shut down too fast the xml stream won't reach the listener. + // a clean solution is to send a sync request node in the stream and wait for an answer before exiting + Sys_Sleep( 1000 ); + + Broadcast_Shutdown(); + + exit (1); +} + diff --git a/tools/quake2/common/inout.h b/tools/quake2/common/inout.h new file mode 100644 index 00000000..75ef55ee --- /dev/null +++ b/tools/quake2/common/inout.h @@ -0,0 +1,63 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __INOUT__ +#define __INOUT__ + +// inout is the only stuff relying on xml, include the headers there +#include "libxml/tree.h" +#include "mathlib.h" + +// some useful xml routines +xmlNodePtr xml_NodeForVec( vec3_t v ); +void xml_SendNode (xmlNodePtr node); +// print a message in q3map output and send the corresponding select information down the xml stream +// bError: do we end with an error on this one or do we go ahead? +void xml_Select (char *msg, int entitynum, int brushnum, qboolean bError); +// end q3map with an error message and send a point information in the xml stream +// note: we might want to add a boolean to use this as a warning or an error thing.. +void xml_Winding (char *msg, vec3_t p[], int numpoints, qboolean die); +void xml_Point (char *msg, vec3_t pt); + +extern qboolean bNetworkBroadcast; +void Broadcast_Setup( const char *dest ); +void Broadcast_Shutdown(); + +#define SYS_VRB 0 // verbose support (on/off) +#define SYS_STD 1 // standard print level +#define SYS_WRN 2 // warnings +#define SYS_ERR 3 // error +#define SYS_NOXML 4 // don't send that down the XML stream + +extern qboolean verbose; +void Sys_Printf (const char *text, ...); +void Sys_FPrintf (int flag, const char *text, ...); +void Error( const char *error, ...); + +#ifdef _DEBUG +#define DBG_XML 1 +#endif + +#ifdef DBG_XML +void DumpXML(); +#endif + +#endif diff --git a/tools/quake2/common/l3dslib.c b/tools/quake2/common/l3dslib.c new file mode 100644 index 00000000..f120bec2 --- /dev/null +++ b/tools/quake2/common/l3dslib.c @@ -0,0 +1,300 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// +// l3dslib.c: library for loading triangles from an Alias triangle file +// + +#include +#include "cmdlib.h" +#include "inout.h" +#include "mathlib.h" +#include "trilib.h" +#include "l3dslib.h" + +#define MAIN3DS 0x4D4D +#define EDIT3DS 0x3D3D // this is the start of the editor config +#define EDIT_OBJECT 0x4000 +#define OBJ_TRIMESH 0x4100 +#define TRI_VERTEXL 0x4110 +#define TRI_FACEL1 0x4120 + +#define MAXVERTS 2000 + +typedef struct { + int v[4]; +} tri; + +float fverts[MAXVERTS][3]; +tri tris[MAXTRIANGLES]; + +int bytesread, level, numtris, totaltris; +int vertsfound, trisfound; + +triangle_t *ptri; + + +// Alias stores triangles as 3 explicit vertices in .tri files, so even though we +// start out with a vertex pool and vertex indices for triangles, we have to convert +// to raw, explicit triangles +void StoreAliasTriangles (void) +{ + int i, j, k; + + if ((totaltris + numtris) > MAXTRIANGLES) + Error ("Error: Too many triangles"); + + for (i=0; i MAXVERTS) + Error ("Error: Too many vertices"); + + for (i=0 ; i MAXTRIANGLES) + Error ("Error: Too many triangles"); + + for (i=0 ; i 0) + { + w -= ParseChunk (input); + } + + retval = length; + goto Done; + + default: + // skip other chunks + while (w > 0) + { + t = w; + + if (t > BLOCK_SIZE) + t = BLOCK_SIZE; + + if (feof(input)) + Error ("Error: unexpected end of file"); + + fread (&temp, t, 1, input); + bytesread += t; + + w -= t; + } + + retval = length; + goto Done; + } + +Done: + level--; + return retval; +} + + +void Load3DSTriangleList (char *filename, triangle_t **pptri, int *numtriangles) +{ + FILE *input; + short int tshort; + + bytesread = 0; + level = 0; + numtris = 0; + totaltris = 0; + vertsfound = 0; + trisfound = 0; + + if ((input = fopen(filename, "rb")) == 0) { + fprintf(stderr,"reader: could not open file '%s'\n", filename); + exit(0); + } + + fread(&tshort, sizeof(tshort), 1, input); + +// should only be MAIN3DS, but some files seem to start with EDIT3DS, with +// no MAIN3DS + if ((tshort != MAIN3DS) && (tshort != EDIT3DS)) { + fprintf(stderr,"File is not a 3DS file.\n"); + exit(0); + } + +// back to top of file so we can parse the first chunk descriptor + fseek(input, 0, SEEK_SET); + + ptri = malloc (MAXTRIANGLES * sizeof(triangle_t)); + + *pptri = ptri; + +// parse through looking for the relevant chunk tree (MAIN3DS | EDIT3DS | EDIT_OBJECT | +// OBJ_TRIMESH | {TRI_VERTEXL, TRI_FACEL1}) and skipping other chunks + ParseChunk (input); + + if (vertsfound || trisfound) + Error ("Incomplete triangle set"); + + *numtriangles = totaltris; + + fclose (input); +} + diff --git a/tools/quake2/common/l3dslib.h b/tools/quake2/common/l3dslib.h new file mode 100644 index 00000000..1d362ce9 --- /dev/null +++ b/tools/quake2/common/l3dslib.h @@ -0,0 +1,25 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// +// l3dslib.h: header file for loading triangles from a 3DS triangle file +// +void Load3DSTriangleList (char *filename, triangle_t **pptri, int *numtriangles); + diff --git a/tools/quake2/common/lbmlib.c b/tools/quake2/common/lbmlib.c new file mode 100644 index 00000000..edc2c3e4 --- /dev/null +++ b/tools/quake2/common/lbmlib.c @@ -0,0 +1,837 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// lbmlib.c + +#include "cmdlib.h" +#include "inout.h" +#include "lbmlib.h" + + + +/* +============================================================================ + + LBM STUFF + +============================================================================ +*/ + + +typedef unsigned char UBYTE; +//conflicts with windows typedef short WORD; +typedef unsigned short UWORD; +typedef long LONG; + +typedef enum +{ + ms_none, + ms_mask, + ms_transcolor, + ms_lasso +} mask_t; + +typedef enum +{ + cm_none, + cm_rle1 +} compress_t; + +typedef struct +{ + UWORD w,h; + short x,y; + UBYTE nPlanes; + UBYTE masking; + UBYTE compression; + UBYTE pad1; + UWORD transparentColor; + UBYTE xAspect,yAspect; + short pageWidth,pageHeight; +} bmhd_t; + +extern bmhd_t bmhd; // will be in native byte order + + + +#define FORMID ('F'+('O'<<8)+((int)'R'<<16)+((int)'M'<<24)) +#define ILBMID ('I'+('L'<<8)+((int)'B'<<16)+((int)'M'<<24)) +#define PBMID ('P'+('B'<<8)+((int)'M'<<16)+((int)' '<<24)) +#define BMHDID ('B'+('M'<<8)+((int)'H'<<16)+((int)'D'<<24)) +#define BODYID ('B'+('O'<<8)+((int)'D'<<16)+((int)'Y'<<24)) +#define CMAPID ('C'+('M'<<8)+((int)'A'<<16)+((int)'P'<<24)) + + +bmhd_t bmhd; + +int Align (int l) +{ + if (l&1) + return l+1; + return l; +} + + + +/* +================ +LBMRLEdecompress + +Source must be evenly aligned! +================ +*/ +byte *LBMRLEDecompress (byte *source,byte *unpacked, int bpwidth) +{ + int count; + byte b,rept; + + count = 0; + + do + { + rept = *source++; + + if (rept > 0x80) + { + rept = (rept^0xff)+2; + b = *source++; + memset(unpacked,b,rept); + unpacked += rept; + } + else if (rept < 0x80) + { + rept++; + memcpy(unpacked,source,rept); + unpacked += rept; + source += rept; + } + else + rept = 0; // rept of 0x80 is NOP + + count += rept; + + } while (countbpwidth) + Error ("Decompression exceeded width!\n"); + + + return source; +} + + +/* +================= +LoadLBM +================= +*/ +void LoadLBM (char *filename, byte **picture, byte **palette) +{ + byte *LBMbuffer, *picbuffer, *cmapbuffer; + int y; + byte *LBM_P, *LBMEND_P; + byte *pic_p; + byte *body_p; + + int formtype,formlength; + int chunktype,chunklength; + +// qiet compiler warnings + picbuffer = NULL; + cmapbuffer = NULL; + +// +// load the LBM +// + LoadFile (filename, (void **)&LBMbuffer); + +// +// parse the LBM header +// + LBM_P = LBMbuffer; + if ( *(int *)LBMbuffer != LittleLong(FORMID) ) + Error ("No FORM ID at start of file!\n"); + + LBM_P += 4; + formlength = BigLong( *(int *)LBM_P ); + LBM_P += 4; + LBMEND_P = LBM_P + Align(formlength); + + formtype = LittleLong(*(int *)LBM_P); + + if (formtype != ILBMID && formtype != PBMID) + Error ("Unrecognized form type: %c%c%c%c\n", formtype&0xff + ,(formtype>>8)&0xff,(formtype>>16)&0xff,(formtype>>24)&0xff); + + LBM_P += 4; + +// +// parse chunks +// + + while (LBM_P < LBMEND_P) + { + chunktype = LBM_P[0] + (LBM_P[1]<<8) + (LBM_P[2]<<16) + (LBM_P[3]<<24); + LBM_P += 4; + chunklength = LBM_P[3] + (LBM_P[2]<<8) + (LBM_P[1]<<16) + (LBM_P[0]<<24); + LBM_P += 4; + + switch ( chunktype ) + { + case BMHDID: + memcpy (&bmhd,LBM_P,sizeof(bmhd)); + bmhd.w = BigShort(bmhd.w); + bmhd.h = BigShort(bmhd.h); + bmhd.x = BigShort(bmhd.x); + bmhd.y = BigShort(bmhd.y); + bmhd.pageWidth = BigShort(bmhd.pageWidth); + bmhd.pageHeight = BigShort(bmhd.pageHeight); + break; + + case CMAPID: + cmapbuffer = malloc (768); + memset (cmapbuffer, 0, 768); + memcpy (cmapbuffer, LBM_P, chunklength); + break; + + case BODYID: + body_p = LBM_P; + + pic_p = picbuffer = malloc (bmhd.w*bmhd.h); + if (formtype == PBMID) + { + // + // unpack PBM + // + for (y=0 ; ydata; + + pcx->xmin = LittleShort(pcx->xmin); + pcx->ymin = LittleShort(pcx->ymin); + pcx->xmax = LittleShort(pcx->xmax); + pcx->ymax = LittleShort(pcx->ymax); + pcx->hres = LittleShort(pcx->hres); + pcx->vres = LittleShort(pcx->vres); + pcx->bytes_per_line = LittleShort(pcx->bytes_per_line); + pcx->palette_type = LittleShort(pcx->palette_type); + + if (pcx->manufacturer != 0x0a + || pcx->version != 5 + || pcx->encoding != 1 + || pcx->bits_per_pixel != 8 + || pcx->xmax >= 640 + || pcx->ymax >= 480) + Error ("Bad pcx file %s", filename); + + if (palette) + { + *palette = malloc(768); + memcpy (*palette, (byte *)pcx + len - 768, 768); + } + + if (width) + *width = pcx->xmax+1; + if (height) + *height = pcx->ymax+1; + + if (!pic) + return; + + out = malloc ( (pcx->ymax+1) * (pcx->xmax+1) ); + if (!out) + Error ("Skin_Cache: couldn't allocate"); + + *pic = out; + + pix = out; + + for (y=0 ; y<=pcx->ymax ; y++, pix += pcx->xmax+1) + { + for (x=0 ; x<=pcx->xmax ; ) + { + dataByte = *raw++; + + if((dataByte & 0xC0) == 0xC0) + { + runLength = dataByte & 0x3F; + dataByte = *raw++; + } + else + runLength = 1; + + while(runLength-- > 0) + pix[x++] = dataByte; + } + + } + + if ( raw - (byte *)pcx > len) + Error ("PCX file %s was malformed", filename); + + free (pcx); +} + +/* +============== +WritePCXfile +============== +*/ +void WritePCXfile (char *filename, byte *data, + int width, int height, byte *palette) +{ + int i, j, length; + pcx_t *pcx; + byte *pack; + + pcx = malloc (width*height*2+1000); + memset (pcx, 0, sizeof(*pcx)); + + pcx->manufacturer = 0x0a; // PCX id + pcx->version = 5; // 256 color + pcx->encoding = 1; // uncompressed + pcx->bits_per_pixel = 8; // 256 color + pcx->xmin = 0; + pcx->ymin = 0; + pcx->xmax = LittleShort((short)(width-1)); + pcx->ymax = LittleShort((short)(height-1)); + pcx->hres = LittleShort((short)width); + pcx->vres = LittleShort((short)height); + pcx->color_planes = 1; // chunky image + pcx->bytes_per_line = LittleShort((short)width); + pcx->palette_type = LittleShort(2); // not a grey scale + + // pack the image + pack = &pcx->data; + + for (i=0 ; i=0; row--) { + pixbuf = targa_rgba + row*columns*4; + for(column=0; column=0; row--) { + pixbuf = targa_rgba + row*columns*4; + for(column=0; column0) + row--; + else + goto breakOut; + pixbuf = targa_rgba + row*columns*4; + } + } + } + else { // non run-length packet + for(j=0;j0) + row--; + else + goto breakOut; + pixbuf = targa_rgba + row*columns*4; + } + } + } + } + breakOut:; + } + } + + // vertically flipped + if ( (targa_header.attributes & (1<<5)) ) { + int flip; + for (row = 0; row < .5f * rows; row++) + { + for (column = 0; column < columns; column++) + { + flip = *( (int*)targa_rgba + row * columns + column); + *( (int*)targa_rgba + row * columns + column) = *( (int*)targa_rgba + ( ( rows - 1 ) - row ) * columns + column ); + *( (int*)targa_rgba + ( ( rows - 1 ) - row ) * columns + column ) = flip; + } + } + } + + fclose(fin); +} diff --git a/tools/quake2/common/lbmlib.h b/tools/quake2/common/lbmlib.h new file mode 100644 index 00000000..40e6dce6 --- /dev/null +++ b/tools/quake2/common/lbmlib.h @@ -0,0 +1,38 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// piclib.h + + +void LoadLBM (char *filename, byte **picture, byte **palette); +void WriteLBMfile (char *filename, byte *data, int width, int height + , byte *palette); +void LoadPCX (char *filename, byte **picture, byte **palette, int *width, int *height); +void WritePCXfile (char *filename, byte *data, int width, int height + , byte *palette); + +// loads / saves either lbm or pcx, depending on extension +void Load256Image (char *name, byte **pixels, byte **palette, + int *width, int *height); +void Save256Image (char *name, byte *pixels, byte *palette, + int width, int height); + + +void LoadTGA (char *filename, byte **pixels, int *width, int *height); diff --git a/tools/quake2/common/mathlib.c b/tools/quake2/common/mathlib.c new file mode 100644 index 00000000..b912b450 --- /dev/null +++ b/tools/quake2/common/mathlib.c @@ -0,0 +1,172 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// mathlib.c -- math primitives + +#include "cmdlib.h" +#include "mathlib.h" + +vec3_t vec3_origin = {0,0,0}; + + +double VectorLength(vec3_t v) +{ + int i; + double length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +qboolean VectorCompare (vec3_t v1, vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (fabs(v1[i]-v2[i]) > EQUAL_EPSILON) + return false; + + return true; +} + +vec_t Q_rint (vec_t in) +{ + return floor (in + 0.5); +} + +void VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc) +{ + vc[0] = va[0] + scale*vb[0]; + vc[1] = va[1] + scale*vb[1]; + vc[2] = va[2] + scale*vb[2]; +} + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]-vb[0]; + out[1] = va[1]-vb[1]; + out[2] = va[2]-vb[2]; +} + +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]+vb[0]; + out[1] = va[1]+vb[1]; + out[2] = va[2]+vb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void _VectorScale (vec3_t v, vec_t scale, vec3_t out) +{ + out[0] = v[0] * scale; + out[1] = v[1] * scale; + out[2] = v[2] * scale; +} + +vec_t VectorNormalize (vec3_t in, vec3_t out) +{ + vec_t length, ilength; + + length = sqrt (in[0]*in[0] + in[1]*in[1] + in[2]*in[2]); + if (length == 0) + { + VectorClear (out); + return 0; + } + + ilength = 1.0/length; + out[0] = in[0]*ilength; + out[1] = in[1]*ilength; + out[2] = in[2]*ilength; + + return length; +} + +vec_t ColorNormalize (vec3_t in, vec3_t out) +{ + float max, scale; + + max = in[0]; + if (in[1] > max) + max = in[1]; + if (in[2] > max) + max = in[2]; + + if (max == 0) + return 0; + + scale = 1.0 / max; + + VectorScale (in, scale, out); + + return max; +} + + + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void ClearBounds (vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} diff --git a/tools/quake2/common/mathlib.h b/tools/quake2/common/mathlib.h new file mode 100644 index 00000000..eb10f0fc --- /dev/null +++ b/tools/quake2/common/mathlib.h @@ -0,0 +1,75 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef __MATHLIB__ +#define __MATHLIB__ + +// mathlib.h + +#include + +#ifdef DOUBLEVEC_T +typedef double vec_t; +#else +typedef float vec_t; +#endif +typedef vec_t vec3_t[3]; + +#define SIDE_FRONT 0 +#define SIDE_ON 2 +#define SIDE_BACK 1 +#define SIDE_CROSS -2 + +#define Q_PI 3.14159265358979323846 + +extern vec3_t vec3_origin; + +#define EQUAL_EPSILON 0.001 + +qboolean VectorCompare (vec3_t v1, vec3_t v2); + +#define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) +#define VectorSubtract(a,b,c) {c[0]=a[0]-b[0];c[1]=a[1]-b[1];c[2]=a[2]-b[2];} +#define VectorAdd(a,b,c) {c[0]=a[0]+b[0];c[1]=a[1]+b[1];c[2]=a[2]+b[2];} +#define VectorCopy(a,b) {b[0]=a[0];b[1]=a[1];b[2]=a[2];} +#define VectorScale(a,b,c) {c[0]=b*a[0];c[1]=b*a[1];c[2]=b*a[2];} +#define VectorClear(x) {x[0] = x[1] = x[2] = 0;} +#define VectorNegate(x) {x[0]=-x[0];x[1]=-x[1];x[2]=-x[2];} + +vec_t Q_rint (vec_t in); +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out); +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); +void _VectorScale (vec3_t v, vec_t scale, vec3_t out); + +double VectorLength(vec3_t v); + +void VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc); + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +vec_t VectorNormalize (vec3_t in, vec3_t out); +vec_t ColorNormalize (vec3_t in, vec3_t out); +void VectorInverse (vec3_t v); + +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); + +#endif diff --git a/tools/quake2/common/md4.c b/tools/quake2/common/md4.c new file mode 100644 index 00000000..95fa4a1a --- /dev/null +++ b/tools/quake2/common/md4.c @@ -0,0 +1,297 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/* GLOBAL.H - RSAREF types and constants */ + +#include + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + + +/* MD4.H - header file for MD4C.C */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. + +All rights reserved. + +License to copy and use this software is granted provided that it is identified as the RSA Data Security, Inc. MD4 Message-Digest Algorithm in all material mentioning or referencing this software or this function. +License is also granted to make and use derivative works provided that such works are identified as derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm in all material mentioning or referencing the derived work. +RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided as is without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this documentation and/or software. */ + +/* MD4 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD4_CTX; + +void MD4Init (MD4_CTX *); +void MD4Update (MD4_CTX *, unsigned char *, unsigned int); +void MD4Final (unsigned char [16], MD4_CTX *); + + + +/* MD4C.C - RSA Data Security, Inc., MD4 message-digest algorithm */ +/* Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + +License to copy and use this software is granted provided that it is identified as the +RSA Data Security, Inc. MD4 Message-Digest Algorithm + in all material mentioning or referencing this software or this function. +License is also granted to make and use derivative works provided that such works are identified as +derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm +in all material mentioning or referencing the derived work. +RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided +as is without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this documentation and/or software. */ + +/* Constants for MD4Transform routine. */ +#define S11 3 +#define S12 7 +#define S13 11 +#define S14 19 +#define S21 3 +#define S22 5 +#define S23 9 +#define S24 13 +#define S31 3 +#define S32 9 +#define S33 11 +#define S34 15 + +static void MD4Transform (UINT4 [4], unsigned char [64]); +static void Encode (unsigned char *, UINT4 *, unsigned int); +static void Decode (UINT4 *, unsigned char *, unsigned int); +static void MD4_memcpy (POINTER, POINTER, unsigned int); +static void MD4_memset (POINTER, int, unsigned int); + +static unsigned char PADDING[64] = { +0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G and H are basic MD4 functions. */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits. */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG and HH are transformations for rounds 1, 2 and 3 */ +/* Rotation is separate from addition to prevent recomputation */ +#define FF(a, b, c, d, x, s) {(a) += F ((b), (c), (d)) + (x); (a) = ROTATE_LEFT ((a), (s));} + +#define GG(a, b, c, d, x, s) {(a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; (a) = ROTATE_LEFT ((a), (s));} + +#define HH(a, b, c, d, x, s) {(a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; (a) = \ +ROTATE_LEFT ((a), (s)); } + + +/* MD4 initialization. Begins an MD4 operation, writing a new context. */ +void MD4Init (MD4_CTX *context) +{ + context->count[0] = context->count[1] = 0; + +/* Load magic initialization constants.*/ +context->state[0] = 0x67452301; +context->state[1] = 0xefcdab89; +context->state[2] = 0x98badcfe; +context->state[3] = 0x10325476; +} + +/* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */ +void MD4Update (MD4_CTX *context, unsigned char *input, unsigned int inputLen) +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3)) + context->count[1]++; + + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible.*/ + if (inputLen >= partLen) + { + memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD4Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD4Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i); +} + + +/* MD4 finalization. Ends an MD4 message-digest operation, writing the the message digest and zeroizing the context. */ +void MD4Final (unsigned char digest[16], MD4_CTX *context) +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64.*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD4Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD4Update (context, bits, 8); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information.*/ + memset ((POINTER)context, 0, sizeof (*context)); +} + + +/* MD4 basic transformation. Transforms state based on block. */ +static void MD4Transform (UINT4 state[4], unsigned char block[64]) +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + +/* Round 1 */ +FF (a, b, c, d, x[ 0], S11); /* 1 */ +FF (d, a, b, c, x[ 1], S12); /* 2 */ +FF (c, d, a, b, x[ 2], S13); /* 3 */ +FF (b, c, d, a, x[ 3], S14); /* 4 */ +FF (a, b, c, d, x[ 4], S11); /* 5 */ +FF (d, a, b, c, x[ 5], S12); /* 6 */ +FF (c, d, a, b, x[ 6], S13); /* 7 */ +FF (b, c, d, a, x[ 7], S14); /* 8 */ +FF (a, b, c, d, x[ 8], S11); /* 9 */ +FF (d, a, b, c, x[ 9], S12); /* 10 */ +FF (c, d, a, b, x[10], S13); /* 11 */ +FF (b, c, d, a, x[11], S14); /* 12 */ +FF (a, b, c, d, x[12], S11); /* 13 */ +FF (d, a, b, c, x[13], S12); /* 14 */ +FF (c, d, a, b, x[14], S13); /* 15 */ +FF (b, c, d, a, x[15], S14); /* 16 */ + +/* Round 2 */ +GG (a, b, c, d, x[ 0], S21); /* 17 */ +GG (d, a, b, c, x[ 4], S22); /* 18 */ +GG (c, d, a, b, x[ 8], S23); /* 19 */ +GG (b, c, d, a, x[12], S24); /* 20 */ +GG (a, b, c, d, x[ 1], S21); /* 21 */ +GG (d, a, b, c, x[ 5], S22); /* 22 */ +GG (c, d, a, b, x[ 9], S23); /* 23 */ +GG (b, c, d, a, x[13], S24); /* 24 */ +GG (a, b, c, d, x[ 2], S21); /* 25 */ +GG (d, a, b, c, x[ 6], S22); /* 26 */ +GG (c, d, a, b, x[10], S23); /* 27 */ +GG (b, c, d, a, x[14], S24); /* 28 */ +GG (a, b, c, d, x[ 3], S21); /* 29 */ +GG (d, a, b, c, x[ 7], S22); /* 30 */ +GG (c, d, a, b, x[11], S23); /* 31 */ +GG (b, c, d, a, x[15], S24); /* 32 */ + +/* Round 3 */ +HH (a, b, c, d, x[ 0], S31); /* 33 */ +HH (d, a, b, c, x[ 8], S32); /* 34 */ +HH (c, d, a, b, x[ 4], S33); /* 35 */ +HH (b, c, d, a, x[12], S34); /* 36 */ +HH (a, b, c, d, x[ 2], S31); /* 37 */ +HH (d, a, b, c, x[10], S32); /* 38 */ +HH (c, d, a, b, x[ 6], S33); /* 39 */ +HH (b, c, d, a, x[14], S34); /* 40 */ +HH (a, b, c, d, x[ 1], S31); /* 41 */ +HH (d, a, b, c, x[ 9], S32); /* 42 */ +HH (c, d, a, b, x[ 5], S33); /* 43 */ +HH (b, c, d, a, x[13], S34); /* 44 */ +HH (a, b, c, d, x[ 3], S31); /* 45 */ +HH (d, a, b, c, x[11], S32); /* 46 */ +HH (c, d, a, b, x[ 7], S33); /* 47 */ +HH (b, c, d, a, x[15], S34); /* 48 */ + +state[0] += a; +state[1] += b; +state[2] += c; +state[3] += d; + + /* Zeroize sensitive information.*/ + memset ((POINTER)x, 0, sizeof (x)); +} + + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */ +static void Encode (unsigned char *output, UINT4 *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */ +static void Decode (UINT4 *output, unsigned char *input, unsigned int len) +{ +unsigned int i, j; + +for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +//=================================================================== + +unsigned Com_BlockChecksum (void *buffer, int length) +{ + int digest[4]; + unsigned val; + MD4_CTX ctx; + + MD4Init (&ctx); + MD4Update (&ctx, (unsigned char *)buffer, length); + MD4Final ( (unsigned char *)digest, &ctx); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} diff --git a/tools/quake2/common/path_init.c b/tools/quake2/common/path_init.c new file mode 100644 index 00000000..d309e95c --- /dev/null +++ b/tools/quake2/common/path_init.c @@ -0,0 +1,404 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Nurail: Swiped from Q3Map2 + +*/ + + + +/* marker */ +#define PATH_INIT_C + +#if defined( __linux__ ) || defined( __APPLE__ ) + #define Q_UNIX +#endif + +#ifdef Q_UNIX + #include + #include + #include +#endif + + +/* dependencies */ +#include "cmdlib.h" +#include "inout.h" + + + +/* path support */ +#define MAX_BASE_PATHS 10 +#define MAX_GAME_PATHS 10 + +char *homePath; +char installPath[ MAX_OS_PATH ]; + +int numBasePaths; +char *basePaths[ MAX_BASE_PATHS ]; +int numGamePaths; +char *gamePaths[ MAX_GAME_PATHS ]; + +/* +some of this code is based off the original q3map port from loki +and finds various paths. moved here from bsp.c for clarity. +*/ + +/* +PathLokiGetHomeDir() +gets the user's home dir (for ~/.q3a) +*/ + +char *LokiGetHomeDir( void ) +{ + #ifndef Q_UNIX + return NULL; + #else + char *home; + uid_t id; + struct passwd *pwd; + + + /* get the home environment variable */ + home = getenv( "HOME" ); + if( home == NULL ) + { + /* do some more digging */ + id = getuid(); + setpwent(); + while( (pwd = getpwent()) != NULL ) + { + if( pwd->pw_uid == id ) + { + home = pwd->pw_dir; + break; + } + } + endpwent(); + } + + /* return it */ + return home; + #endif +} + + + +/* +PathLokiInitPaths() +initializes some paths on linux/os x +*/ + +void LokiInitPaths( char *argv0 ) +{ + #ifndef Q_UNIX + /* this is kinda crap, but hey */ + strcpy( installPath, "../" ); + #else + char temp[ MAX_OS_PATH ]; + char *home; + char *path; + char *last; + qboolean found; + + + /* get home dir */ + home = LokiGetHomeDir(); + if( home == NULL ) + home = "."; + + /* do some path divining */ + strcpy( temp, argv0 ); + if( strrchr( temp, '/' ) ) + argv0 = strrchr( argv0, '/' ) + 1; + else + { + /* get path environment variable */ + path = getenv( "PATH" ); + + /* minor setup */ + last[ 0 ] = path[ 0 ]; + last[ 1 ] = '\0'; + found = false; + + /* go through each : segment of path */ + while( last[ 0 ] != '\0' && found == false ) + { + /* null out temp */ + temp[ 0 ] = '\0'; + + /* find next chunk */ + last = strchr( path, ':' ); + if( last == NULL ) + last = path + strlen( path ); + + /* found home dir candidate */ + if( *path == '~' ) + { + strcpy( temp, home ); + path++; + } + + /* concatenate */ + if( last > (path + 1) ) + { + strncat( temp, path, (last - path) ); + strcat( temp, "/" ); + } + strcat( temp, "./" ); + strcat( temp, argv0 ); + + /* verify the path */ + if( access( temp, X_OK ) == 0 ) + found++; + path = last + 1; + } + } + + /* flake */ + if( realpath( temp, installPath ) ) + { + /* q3map is in "tools/" */ + *(strrchr( installPath, '/' )) = '\0'; + *(strrchr( installPath, '/' ) + 1) = '\0'; + } + + /* set home path */ + homePath = home; + #endif +} + + + +/* +CleanPath() - ydnar +cleans a dos path \ -> / +*/ + +void CleanPath( char *path ) +{ + while( *path ) + { + if( *path == '\\' ) + *path = '/'; + path++; + } +} + +/* +AddBasePath() - ydnar +adds a base path to the list +*/ + +void AddBasePath( char *path ) +{ + /* dummy check */ + if( path == NULL || path[ 0 ] == '\0' || numBasePaths >= MAX_BASE_PATHS ) + return; + + /* add it to the list */ + basePaths[ numBasePaths ] = safe_malloc( strlen( path ) + 1 ); + strcpy( basePaths[ numBasePaths ], path ); + CleanPath( basePaths[ numBasePaths ] ); + numBasePaths++; +} + + + +/* +AddHomeBasePath() - ydnar +adds a base path to the beginning of the list, prefixed by ~/ +*/ + +void AddHomeBasePath( char *path ) +{ + #ifdef Q_UNIX + int i; + char temp[ MAX_OS_PATH ]; + + + /* dummy check */ + if( path == NULL || path[ 0 ] == '\0' ) + return; + + /* make a hole */ + for( i = 0; i < (MAX_BASE_PATHS - 1); i++ ) + basePaths[ i + 1 ] = basePaths[ i ]; + + /* concatenate home dir and path */ + sprintf( temp, "%s/%s", homePath, path ); + + /* add it to the list */ + basePaths[ 0 ] = safe_malloc( strlen( temp ) + 1 ); + strcpy( basePaths[ 0 ], temp ); + CleanPath( basePaths[ 0 ] ); + numBasePaths++; + #endif +} + + + +/* +AddGamePath() - ydnar +adds a game path to the list +*/ + +void AddGamePath( char *path ) +{ + /* dummy check */ + if( path == NULL || path[ 0 ] == '\0' || numGamePaths >= MAX_GAME_PATHS ) + return; + + /* add it to the list */ + gamePaths[ numGamePaths ] = safe_malloc( strlen( path ) + 1 ); + strcpy( gamePaths[ numGamePaths ], path ); + CleanPath( gamePaths[ numGamePaths ] ); + numGamePaths++; +} + + + + +/* +InitPaths() - ydnar +cleaned up some of the path initialization code from bsp.c +will remove any arguments it uses +*/ + +void InitPaths( int *argc, char **argv ) +{ + int i, j, k, len, len2; + char temp[ MAX_OS_PATH ]; + char gamePath[MAX_OS_PATH], homeBasePath[MAX_OS_PATH], game_magic[10]; + + strcpy(gamePath, "baseq2"); + strcpy(game_magic, "quake"); + strcpy(homeBasePath, ".quake2"); + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- InitPaths ---\n" ); + + /* get the install path for backup */ + LokiInitPaths( argv[ 0 ] ); + + /* set game to default (q3a) */ + numBasePaths = 0; + numGamePaths = 0; + + /* parse through the arguments and extract those relevant to paths */ + for( i = 0; i < *argc; i++ ) + { + /* check for null */ + if( argv[ i ] == NULL ) + continue; + + /* -fs_basepath */ + if( strcmp( argv[ i ], "-fs_basepath" ) == 0 ) + { + if( ++i >= *argc ) + Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] ); + argv[ i - 1 ] = NULL; + AddBasePath( argv[ i ] ); + argv[ i ] = NULL; + } + + } + + /* remove processed arguments */ + for( i = 0, j = 0, k = 0; i < *argc && j < *argc; i++, j++ ) + { + for( j; j < *argc && argv[ j ] == NULL; j++ ); + argv[ i ] = argv[ j ]; + if( argv[ i ] != NULL ) + k++; + } + *argc = k; + + /* add standard game path */ + AddGamePath( gamePath ); + + /* if there is no base path set, figure it out */ + if( numBasePaths == 0 ) + { + /* this is another crappy replacement for SetQdirFromPath() */ + len2 = strlen( game_magic ); + for( i = 0; i < *argc && numBasePaths == 0; i++ ) + { + /* extract the arg */ + strcpy( temp, argv[ i ] ); + CleanPath( temp ); + len = strlen( temp ); + Sys_FPrintf( SYS_VRB, "Searching for \"%s\" in \"%s\" (%d)...\n", game_magic, temp, i ); + + /* this is slow, but only done once */ + for( j = 0; j < (len - len2); j++ ) + { + /* check for the game's magic word */ + if( Q_strncasecmp( &temp[ j ], game_magic, len2 ) == 0 ) + { + /* now find the next slash and nuke everything after it */ + while( temp[ ++j ] != '/' && temp[ j ] != '\0' ); + temp[ j ] = '\0'; + + /* add this as a base path */ + AddBasePath( temp ); + break; + } + } + } + + /* add install path */ + if( numBasePaths == 0 ) + AddBasePath( installPath ); + + /* check again */ + if( numBasePaths == 0 ) + Error( "Failed to find a valid base path." ); + } + + /* this only affects unix */ + AddHomeBasePath( homeBasePath ); + + /* initialize vfs paths */ + if( numBasePaths > MAX_BASE_PATHS ) + numBasePaths = MAX_BASE_PATHS; + if( numGamePaths > MAX_GAME_PATHS ) + numGamePaths = MAX_GAME_PATHS; + + /* walk the list of game paths */ + //for( j = 0; j < numGamePaths; j++ ) + //{ + /* walk the list of base paths */ + // for( i = 0; i < numBasePaths; i++ ) + // { + /* create a full path and initialize it */ + // sprintf( temp, "%s/%s/", basePaths[ i ], gamePaths[ j ] ); + // vfsInitDirectory( temp ); + // } + //} + + /* done */ + Sys_Printf( "\n" ); +} + + + + diff --git a/tools/quake2/common/polylib.c b/tools/quake2/common/polylib.c new file mode 100644 index 00000000..e177d728 --- /dev/null +++ b/tools/quake2/common/polylib.c @@ -0,0 +1,642 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "cmdlib.h" +#include "inout.h" +#include "mathlib.h" +#include "polylib.h" + + +extern int numthreads; + +// counters are only bumped when running single threaded, +// because they are an awefull coherence problem +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; + +#define BOGUS_RANGE 8192 + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding (int points) +{ + winding_t *w; + int s; + + if (numthreads == 1) + { + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if (c_active_windings > c_peak_windings) + c_peak_windings = c_active_windings; + } + s = sizeof(vec_t)*3*points + sizeof(int); + w = malloc (s); + memset (w, 0, s); + return w; +} + +void FreeWinding (winding_t *w) +{ + if (*(unsigned *)w == 0xdeaddead) + Error ("FreeWinding: freed a freed winding"); + *(unsigned *)w = 0xdeaddead; + + if (numthreads == 1) + c_active_windings--; + free (w); +} + +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints (winding_t *w) +{ + int i, j, k; + vec3_t v1, v2; + int nump; + vec3_t p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for (i=0 ; inumpoints ; i++) + { + j = (i+1)%w->numpoints; + k = (i+w->numpoints-1)%w->numpoints; + VectorSubtract (w->p[j], w->p[i], v1); + VectorSubtract (w->p[i], w->p[k], v2); + VectorNormalize(v1,v1); + VectorNormalize(v2,v2); + if (DotProduct(v1, v2) < 0.999) + { + VectorCopy (w->p[i], p[nump]); + nump++; + } + } + + if (nump == w->numpoints) + return; + + if (numthreads == 1) + c_removed += w->numpoints - nump; + w->numpoints = nump; + memcpy (w->p, p, nump*sizeof(p[0])); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist) +{ + vec3_t v1, v2; + + VectorSubtract (w->p[1], w->p[0], v1); + VectorSubtract (w->p[2], w->p[0], v2); + CrossProduct (v2, v1, normal); + VectorNormalize (normal, normal); + *dist = DotProduct (w->p[0], normal); + +} + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea (winding_t *w) +{ + int i; + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + total += 0.5 * VectorLength ( cross ); + } + return total; +} + +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs) +{ + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter (winding_t *w, vec3_t center) +{ + int i; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->p[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist) +{ + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + winding_t *w; + +// find the major axis + + max = -BOGUS_RANGE; + x = -1; + for (i=0 ; i<3; i++) + { + v = fabs(normal[i]); + if (v > max) + { + x = i; + max = v; + } + } + if (x==-1) + Error ("BaseWindingForPlane: no axis found"); + + VectorCopy (vec3_origin, vup); + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct (vup, normal); + VectorMA (vup, -v, normal, vup); + VectorNormalize (vup, vup); + + VectorScale (normal, dist, org); + + CrossProduct (vup, normal, vright); + + VectorScale (vup, 8192, vup); + VectorScale (vright, 8192, vright); + +// project a really big axis aligned box onto the plane + w = AllocWinding (4); + + VectorSubtract (org, vright, w->p[0]); + VectorAdd (w->p[0], vup, w->p[0]); + + VectorAdd (org, vright, w->p[1]); + VectorAdd (w->p[1], vup, w->p[1]); + + VectorAdd (org, vright, w->p[2]); + VectorSubtract (w->p[2], vup, w->p[2]); + + VectorSubtract (org, vright, w->p[3]); + VectorSubtract (w->p[3], vup, w->p[3]); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding (winding_t *w) +{ + int size; + winding_t *c; + + c = AllocWinding (w->numpoints); + size = (int)((winding_t *)0)->p[w->numpoints]; + memcpy (c, w, size); + return c; +} + +/* +================== +ReverseWinding +================== +*/ +winding_t *ReverseWinding (winding_t *w) +{ + int i; + winding_t *c; + + c = AllocWinding (w->numpoints); + for (i=0 ; inumpoints ; i++) + { + VectorCopy (w->p[w->numpoints-1-i], c->p[i]); + } + c->numpoints = w->numpoints; + return c; +} + + +/* +============= +ClipWindingEpsilon +============= +*/ +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = CopyWinding (in); + return; + } + if (!counts[1]) + { + *front = CopyWinding (in); + return; + } + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); +} + + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace (winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon) +{ + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + { + FreeWinding (in); + *inout = NULL; + return; + } + if (!counts[1]) + return; // inout stays the same + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + } + + if (f->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); + + FreeWinding (in); + *inout = f; +} + + +/* +================= +ChopWinding + +Returns the fragment of in that is on the front side +of the cliping plane. The original is freed. +================= +*/ +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist) +{ + winding_t *f, *b; + + ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b); + FreeWinding (in); + if (b) + FreeWinding (b); + return f; +} + + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding (winding_t *w) +{ + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if (w->numpoints < 3) + Error ("CheckWinding: %i points",w->numpoints); + + area = WindingArea(w); + if (area < 1) + Error ("CheckWinding: %f area", area); + + WindingPlane (w, facenormal, &facedist); + + for (i=0 ; inumpoints ; i++) + { + p1 = w->p[i]; + + for (j=0 ; j<3 ; j++) + if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) + Error ("CheckFace: BUGUS_RANGE: %f",p1[j]); + + j = i+1 == w->numpoints ? 0 : i+1; + + // check the point is on the face plane + d = DotProduct (p1, facenormal) - facedist; + if (d < -ON_EPSILON || d > ON_EPSILON) + Error ("CheckWinding: point off plane"); + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + Error ("CheckWinding: degenerate edge"); + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize (edgenormal, edgenormal); + edgedist = DotProduct (p1, edgenormal); + edgedist += ON_EPSILON; + + // all other points must be on front side + for (j=0 ; jnumpoints ; j++) + { + if (j == i) + continue; + d = DotProduct (w->p[j], edgenormal); + if (d > edgedist) + Error ("CheckWinding: non-convex"); + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist) +{ + qboolean front, back; + int i; + vec_t d; + + front = false; + back = false; + for (i=0 ; inumpoints ; i++) + { + d = DotProduct (w->p[i], normal) - dist; + if (d < -ON_EPSILON) + { + if (front) + return SIDE_CROSS; + back = true; + continue; + } + if (d > ON_EPSILON) + { + if (back) + return SIDE_CROSS; + front = true; + continue; + } + } + + if (back) + return SIDE_BACK; + if (front) + return SIDE_FRONT; + return SIDE_ON; +} + diff --git a/tools/quake2/common/polylib.h b/tools/quake2/common/polylib.h new file mode 100644 index 00000000..d89062d5 --- /dev/null +++ b/tools/quake2/common/polylib.h @@ -0,0 +1,54 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +typedef struct +{ + int numpoints; + vec3_t p[4]; // variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 64 + +// you can define on_epsilon in the makefile as tighter +#ifndef ON_EPSILON +#define ON_EPSILON 0.1 +#endif + +winding_t *AllocWinding (int points); +vec_t WindingArea (winding_t *w); +void WindingCenter (winding_t *w, vec3_t center); +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back); +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist); +winding_t *CopyWinding (winding_t *w); +winding_t *ReverseWinding (winding_t *w); +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist); +void CheckWinding (winding_t *w); +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist); +void RemoveColinearPoints (winding_t *w); +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist); +void FreeWinding (winding_t *w); +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs); + +void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon); +// frees the original if clipped + +void pw(winding_t *w); diff --git a/tools/quake2/common/q2_threads.h b/tools/quake2/common/q2_threads.h new file mode 100644 index 00000000..b8fc2f53 --- /dev/null +++ b/tools/quake2/common/q2_threads.h @@ -0,0 +1,34 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef _THREADS_H + +#define _THREADS_H + +extern int numthreads; + +void ThreadSetDefault (void); +int GetThreadWork (void); +void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)); +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)); +void ThreadLock (void); +void ThreadUnlock (void); + +#endif // _THREADS_H diff --git a/tools/quake2/common/qfiles.h b/tools/quake2/common/qfiles.h new file mode 100644 index 00000000..7c9cc864 --- /dev/null +++ b/tools/quake2/common/qfiles.h @@ -0,0 +1,563 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +/* +======================================================================== + +The .pak files are just a linear collapse of a directory tree + +======================================================================== +*/ + +#ifdef WIN32 + #ifdef NDEBUG // Don't show in a Release build + #pragma warning(disable : 4305) // truncate from double to float + #pragma warning(disable : 4244) // conversion from double to float + #pragma warning(disable : 4018) // signed/unsigned mismatch + #endif +#endif + +#define IDPAKHEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') + +typedef struct +{ + char name[56]; + int filepos, filelen; +} dpackfile_t; + +typedef struct +{ + int ident; // == IDPAKHEADER + int dirofs; + int dirlen; +} dpackheader_t; + +#define MAX_FILES_IN_PACK 4096 + + +/* +======================================================================== + +PCX files are used for as many images as possible + +======================================================================== +*/ + +typedef struct +{ + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +/* +======================================================================== + +.MD2 triangle model file format + +======================================================================== +*/ + +#define IDALIASHEADER (('2'<<24)+('P'<<16)+('D'<<8)+'I') +#define ALIAS_VERSION 8 + +#define MAX_TRIANGLES 4096 +#define MAX_VERTS 2048 +#define MAX_FRAMES 512 +#define MAX_MD2SKINS 32 +#define MAX_SKINNAME 64 + +typedef struct +{ + short s; + short t; +} dstvert_t; + +typedef struct +{ + short index_xyz[3]; + short index_st[3]; +} dtriangle_t; + +typedef struct +{ + byte v[3]; // scaled byte to fit in frame mins/maxs + byte lightnormalindex; +} dtrivertx_t; + +#define DTRIVERTX_V0 0 +#define DTRIVERTX_V1 1 +#define DTRIVERTX_V2 2 +#define DTRIVERTX_LNI 3 +#define DTRIVERTX_SIZE 4 + +typedef struct +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name from grabbing + dtrivertx_t verts[1]; // variable sized +} daliasframe_t; + + +// the glcmd format: +// a positive integer starts a tristrip command, followed by that many +// vertex structures. +// a negative integer starts a trifan command, followed by -x vertexes +// a zero indicates the end of the command list. +// a vertex consists of a floating point s, a floating point t, +// and an integer vertex index. + + +typedef struct +{ + int ident; + int version; + + int skinwidth; + int skinheight; + int framesize; // byte size of each frame + + int num_skins; + int num_xyz; + int num_st; // greater than num_xyz for seams + int num_tris; + int num_glcmds; // dwords in strip/fan command list + int num_frames; + + int ofs_skins; // each skin is a MAX_SKINNAME string + int ofs_st; // byte offset from start for stverts + int ofs_tris; // offset for dtriangles + int ofs_frames; // offset for first frame + int ofs_glcmds; + int ofs_end; // end of file + +} dmdl_t; + +/* +======================================================================== + +.SP2 sprite file format + +======================================================================== +*/ + +#define IDSPRITEHEADER (('2'<<24)+('S'<<16)+('D'<<8)+'I') + // little-endian "IDS2" +#define SPRITE_VERSION 2 + +typedef struct +{ + int width, height; + int origin_x, origin_y; // raster coordinates inside pic + char name[MAX_SKINNAME]; // name of pcx file +} dsprframe_t; + +typedef struct { + int ident; + int version; + int numframes; + dsprframe_t frames[1]; // variable sized +} dsprite_t; + +/* +============================================================================== + + .WAL texture file format + +============================================================================== +*/ + + +#define MIPLEVELS 4 +typedef struct miptex_s +{ + char name[32]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored + char animname[32]; // next frame in animation chain + int flags; + int contents; + int value; +} miptex_t; + + /* + ============================================================================== + +- .WAL texture file format ++ .M8 texture file format + + ============================================================================== + */ + +typedef struct palette_s +{ + union + { + struct + { + byte r,g,b; + }; + }; +} palette_t; + +#define MIP_VERSION 2 +#define PAL_SIZE 256 +#define H2_MIPLEVELS 16 + + typedef struct miptex_m8_s + { + int version; + char name[32]; + unsigned width[H2_MIPLEVELS], height[H2_MIPLEVELS]; + unsigned offsets[H2_MIPLEVELS]; // four mip maps stored + char animname[32]; // next frame in animation chain + palette_t palette[PAL_SIZE]; + int flags; + int contents; + int value; + } miptex_m8_t; + + +#define MIP32_VERSION 4 + +#define MIP32_NOMIP_FLAG2 0x00000001 +#define MIP32_DETAILER_FLAG2 0x00000002 + +typedef struct miptex_m32_s +{ + int version; + char name[128]; + char altname[128]; // texture substitution + char animname[128]; // next frame in animation chain + char damagename[128]; // image that should be shown when damaged + unsigned width[H2_MIPLEVELS], height[H2_MIPLEVELS]; + unsigned offsets[H2_MIPLEVELS]; + int flags; + int contents; + int value; + float scale_x, scale_y; + int mip_scale; + + // detail texturing info + char dt_name[128]; // detailed texture name + float dt_scale_x, dt_scale_y; + float dt_u, dt_v; + float dt_alpha; + int dt_src_blend_mode, dt_dst_blend_mode; + + int flags2; + int unused[19]; // future expansion to maintain compatibility with h2 +} miptex_m32_t; + + + + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + +#define IDBSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') + // little-endian "IBSP" + +#define BSPVERSION 38 + + +// upper design bounds +// leaffaces, leafbrushes, planes, and verts are still bounded by +// 16 bit short limits +#define MAX_MAP_MODELS 1024 +#define MAX_MAP_BRUSHES 8192 +#define MAX_MAP_ENTITIES 2048 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_TEXINFO 8192 + +#define MAX_MAP_AREAS 256 +#define MAX_MAP_AREAPORTALS 1024 +#define MAX_MAP_PLANES 65536 +#define MAX_MAP_NODES 65536 +#define MAX_MAP_BRUSHSIDES 65536 +#define MAX_MAP_LEAFS 65536 +#define MAX_MAP_VERTS 65536 +#define MAX_MAP_FACES 65536 +#define MAX_MAP_LEAFFACES 65536 +#define MAX_MAP_LEAFBRUSHES 65536 +#define MAX_MAP_PORTALS 65536 +#define MAX_MAP_EDGES 128000 +#define MAX_MAP_SURFEDGES 256000 +#define MAX_MAP_LIGHTING 0x200000 +#define MAX_MAP_VISIBILITY 0x100000 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + +typedef struct +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_VERTEXES 2 +#define LUMP_VISIBILITY 3 +#define LUMP_NODES 4 +#define LUMP_TEXINFO 5 +#define LUMP_FACES 6 +#define LUMP_LIGHTING 7 +#define LUMP_LEAFS 8 +#define LUMP_LEAFFACES 9 +#define LUMP_LEAFBRUSHES 10 +#define LUMP_EDGES 11 +#define LUMP_SURFEDGES 12 +#define LUMP_MODELS 13 +#define LUMP_BRUSHES 14 +#define LUMP_BRUSHSIDES 15 +#define LUMP_POP 16 +#define LUMP_AREAS 17 +#define LUMP_AREAPORTALS 18 +#define HEADER_LUMPS 19 + +typedef struct +{ + int ident; + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; // for sounds or lights + int headnode; + int firstface, numfaces; // submodels just draw faces + // without walking the bsp tree +} dmodel_t; + + +typedef struct +{ + float point[3]; +} dvertex_t; + + +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 + +// 3-5 are non-axial planes snapped to the nearest +#define PLANE_ANYX 3 +#define PLANE_ANYY 4 +#define PLANE_ANYZ 5 + +// planes (x&~1) and (x&~1)+1 are allways opposites + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} dplane_t; + + +// contents flags are seperate bits +// a given brush can contribute multiple content bits +// multiple brushes can be in a single leaf + +// these definitions also need to be in q_shared.h! + +// lower bits are stronger, and will eat weaker brushes completely +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_WINDOW 2 // translucent, but not watery +#define CONTENTS_AUX 4 +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_MIST 64 +#define LAST_VISIBLE_CONTENTS 64 + +// remaining contents are non-visible, and don't eat brushes + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 + +// currents can be added to any other contents, and may be mixed +#define CONTENTS_CURRENT_0 0x40000 +#define CONTENTS_CURRENT_90 0x80000 +#define CONTENTS_CURRENT_180 0x100000 +#define CONTENTS_CURRENT_270 0x200000 +#define CONTENTS_CURRENT_UP 0x400000 +#define CONTENTS_CURRENT_DOWN 0x800000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game +#define CONTENTS_DEADMONSTER 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs +#define CONTENTS_TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTS_LADDER 0x20000000 + + + +#define SURF_LIGHT 0x1 // value will hold the light strength + +#define SURF_SLICK 0x2 // effects game physics + +#define SURF_SKY 0x4 // don't draw, but add to skybox +#define SURF_WARP 0x8 // turbulent water warp +#define SURF_TRANS33 0x10 +#define SURF_TRANS66 0x20 +#define SURF_FLOWING 0x40 // scroll towards angle +#define SURF_NODRAW 0x80 // don't bother referencing the texture + +#define SURF_HINT 0x100 // make a primary bsp splitter +#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes + + + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} dnode_t; + + +typedef struct texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int flags; // miptex flags + overrides + int value; // light emission, etc + char texture[32]; // texture name (textures/*.wal) + int nexttexinfo; // for animations, -1 = end of chain +} texinfo_t; + + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + unsigned short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dface_t; + +typedef struct +{ + int contents; // OR of all brushes (not needed?) + + short cluster; + short area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstleafface; + unsigned short numleaffaces; + + unsigned short firstleafbrush; + unsigned short numleafbrushes; +} dleaf_t; + +typedef struct +{ + unsigned short planenum; // facing out of the leaf + short texinfo; +} dbrushside_t; + +typedef struct +{ + int firstside; + int numsides; + int contents; +} dbrush_t; + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the visibility lump consists of a header with a count, then +// byte offsets for the PVS and PHS of each cluster, then the raw +// compressed bit vectors +#define DVIS_PVS 0 +#define DVIS_PHS 1 +typedef struct +{ + int numclusters; + int bitofs[8][2]; // bitofs[numclusters][2] +} dvis_t; + +// each area has a list of portals that lead into other areas +// when portals are closed, other areas may not be visible or +// hearable even if the vis info says that it should be +typedef struct +{ + int portalnum; + int otherarea; +} dareaportal_t; + +typedef struct +{ + int numareaportals; + int firstareaportal; +} darea_t; diff --git a/tools/quake2/common/scriplib.c b/tools/quake2/common/scriplib.c new file mode 100644 index 00000000..9cab6391 --- /dev/null +++ b/tools/quake2/common/scriplib.c @@ -0,0 +1,296 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// scriplib.c + +#include "cmdlib.h" +#include "inout.h" +#include "scriplib.h" + +/* +============================================================================= + + PARSING STUFF + +============================================================================= +*/ + +typedef struct +{ + char filename[1024]; + char *buffer,*script_p,*end_p; + int line; +} script_t; + +#define MAX_INCLUDES 8 +script_t scriptstack[MAX_INCLUDES]; +script_t *script; +int scriptline; + +char token[MAXTOKEN]; +qboolean endofscript; +qboolean tokenready; // only true if UnGetToken was just called + +/* +============== +AddScriptToStack +============== +*/ +void AddScriptToStack (char *filename) +{ + int size; + + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + strcpy (script->filename, ExpandPath (filename) ); + + size = LoadFile (script->filename, (void **)&script->buffer); + + printf ("entering %s\n", script->filename); + + script->line = 1; + + script->script_p = script->buffer; + script->end_p = script->buffer + size; +} + + +/* +============== +LoadScriptFile +============== +*/ +void LoadScriptFile (char *filename) +{ + script = scriptstack; + AddScriptToStack (filename); + + endofscript = false; + tokenready = false; +} + + +/* +============== +ParseFromMemory +============== +*/ +void ParseFromMemory (char *buffer, int size) +{ + script = scriptstack; + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + strcpy (script->filename, "memory buffer" ); + + script->buffer = buffer; + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + + endofscript = false; + tokenready = false; +} + + +/* +============== +UnGetToken + +Signals that the current token was not used, and should be reported +for the next GetToken. Note that + +GetToken (true); +UnGetToken (); +GetToken (false); + +could cross a line boundary. +============== +*/ +void UnGetToken (void) +{ + tokenready = true; +} + + +qboolean EndOfScript (qboolean crossline) +{ + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + + if (!strcmp (script->filename, "memory buffer")) + { + endofscript = true; + return false; + } + + free (script->buffer); + if (script == scriptstack+1) + { + endofscript = true; + return false; + } + script--; + scriptline = script->line; + printf ("returning to %s\n", script->filename); + return GetToken (crossline); +} + +/* +============== +GetToken +============== +*/ +qboolean GetToken (qboolean crossline) +{ + char *token_p; + + if (tokenready) // is a token allready waiting? + { + tokenready = false; + return true; + } + + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + +// +// skip space +// +skipspace: + while (*script->script_p <= 32) + { + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + if (*script->script_p++ == '\n') + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + scriptline = script->line++; + } + } + + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + + // ; # // comments + if (*script->script_p == ';' || *script->script_p == '#' + || ( script->script_p[0] == '/' && script->script_p[1] == '/') ) + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + while (*script->script_p++ != '\n') + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + goto skipspace; + } + + // /* */ comments + if (script->script_p[0] == '/' && script->script_p[1] == '*') + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + script->script_p+=2; + while (script->script_p[0] != '*' && script->script_p[1] != '/') + { + script->script_p++; + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + } + script->script_p += 2; + goto skipspace; + } + +// +// copy token +// + token_p = token; + + if (*script->script_p == '"') + { + // quoted token + script->script_p++; + while (*script->script_p != '"') + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + script->script_p++; + } + else // regular token + while ( *script->script_p > 32 && *script->script_p != ';') + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + + *token_p = 0; + + if (!strcmp (token, "$include")) + { + GetToken (false); + AddScriptToStack (token); + return GetToken (crossline); + } + + return true; +} + + +/* +============== +TokenAvailable + +Returns true if there is another token on the line +============== +*/ +qboolean TokenAvailable (void) +{ + char *search_p; + + search_p = script->script_p; + + if (search_p >= script->end_p) + return false; + + while ( *search_p <= 32) + { + if (*search_p == '\n') + return false; + search_p++; + if (search_p == script->end_p) + return false; + + } + + if (*search_p == ';') + return false; + + return true; +} + + diff --git a/tools/quake2/common/scriplib.h b/tools/quake2/common/scriplib.h new file mode 100644 index 00000000..d07a9bb4 --- /dev/null +++ b/tools/quake2/common/scriplib.h @@ -0,0 +1,43 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// scriplib.h + +#ifndef __CMDLIB__ +#include "cmdlib.h" +#endif + +#define MAXTOKEN 1024 + +extern char token[MAXTOKEN]; +extern char *scriptbuffer,*script_p,*scriptend_p; +extern int grabbed; +extern int scriptline; +extern qboolean endofscript; + + +void LoadScriptFile (char *filename); +void ParseFromMemory (char *buffer, int size); + +qboolean GetToken (qboolean crossline); +void UnGetToken (void); +qboolean TokenAvailable (void); + + diff --git a/tools/quake2/common/threads.c b/tools/quake2/common/threads.c new file mode 100644 index 00000000..76e969da --- /dev/null +++ b/tools/quake2/common/threads.c @@ -0,0 +1,622 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef WIN32 +// The below define is necessary to use +// pthreads extensions like pthread_mutexattr_settype +#define _GNU_SOURCE +#include +#endif + +#include "cmdlib.h" +#include "mathlib.h" +#include "inout.h" +#include "q2_threads.h" + +#define MAX_THREADS 64 + +int dispatch; +int workcount; +int oldf; +qboolean pacifier; + +qboolean threaded; + +/* +============= +GetThreadWork + +============= +*/ +int GetThreadWork (void) +{ + int r; + int f; + + ThreadLock (); + + if (dispatch == workcount) + { + ThreadUnlock (); + return -1; + } + + f = 10*dispatch / workcount; + if (f != oldf) + { + oldf = f; + if (pacifier) + { + Sys_Printf ("%i...", f); + fflush( stdout ); /* ydnar */ + } + } + + r = dispatch; + dispatch++; + ThreadUnlock (); + + return r; +} + + +void (*workfunction) (int); + +void ThreadWorkerFunction (int threadnum) +{ + int work; + + while (1) + { + work = GetThreadWork (); + if (work == -1) + break; + //Sys_FPrintf( SYS_VRB,"thread %i, work %i\n", threadnum, work); + workfunction(work); + } +} + +void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + if (numthreads == -1) + ThreadSetDefault (); + workfunction = func; + RunThreadsOn (workcnt, showpacifier, ThreadWorkerFunction); +} + + +/* +=================================================================== + +WIN32 + +=================================================================== +*/ +#ifdef WIN32 + +#define USED + +#include + +// Setting default Threads to 1 +int numthreads = 1; +CRITICAL_SECTION crit; +static int enter; + +void ThreadSetDefault (void) +{ + SYSTEM_INFO info; + + if (numthreads == -1) // not set manually + { + GetSystemInfo (&info); + numthreads = info.dwNumberOfProcessors; + if (numthreads < 1 || numthreads > 32) + numthreads = 1; + } + + Sys_Printf ("%i threads\n", numthreads); +} + + +void ThreadLock (void) +{ + if (!threaded) + return; + EnterCriticalSection (&crit); + if (enter) + Error ("Recursive ThreadLock\n"); + enter = 1; +} + +void ThreadUnlock (void) +{ + if (!threaded) + return; + if (!enter) + Error ("ThreadUnlock without lock\n"); + enter = 0; + LeaveCriticalSection (&crit); +} + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int threadid[MAX_THREADS]; + HANDLE threadhandle[MAX_THREADS]; + int i; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + // + // run threads in parallel + // + InitializeCriticalSection (&crit); + + if (numthreads == 1) + { // use same thread + func (0); + } + else + { + for (i=0 ; i + +pthread_mutex_t *my_mutex; + +void ThreadLock (void) +{ + if (my_mutex) + pthread_mutex_lock (my_mutex); +} + +void ThreadUnlock (void) +{ + if (my_mutex) + pthread_mutex_unlock (my_mutex); +} + + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int i; + pthread_t work_threads[MAX_THREADS]; + pthread_addr_t status; + pthread_attr_t attrib; + pthread_mutexattr_t mattrib; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if (pacifier) + setbuf (stdout, NULL); + + if (!my_mutex) + { + my_mutex = safe_malloc (sizeof(*my_mutex)); + if (pthread_mutexattr_create (&mattrib) == -1) + Error ("pthread_mutex_attr_create failed"); + if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1) + Error ("pthread_mutexattr_setkind_np failed"); + if (pthread_mutex_init (my_mutex, mattrib) == -1) + Error ("pthread_mutex_init failed"); + } + + if (pthread_attr_create (&attrib) == -1) + Error ("pthread_attr_create failed"); + if (pthread_attr_setstacksize (&attrib, 0x100000) == -1) + Error ("pthread_attr_setstacksize failed"); + + for (i=0 ; i +#include +#include +#include + + +int numthreads = -1; +abilock_t lck; + +void ThreadSetDefault (void) +{ + if (numthreads == -1) + numthreads = prctl(PR_MAXPPROCS); + Sys_Printf ("%i threads\n", numthreads); + usconfig (CONF_INITUSERS, numthreads); +} + + +void ThreadLock (void) +{ + spin_lock (&lck); +} + +void ThreadUnlock (void) +{ + release_lock (&lck); +} + + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int i; + int pid[MAX_THREADS]; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if (pacifier) + setbuf (stdout, NULL); + + init_lock (&lck); + + for (i=0 ; i 1) + Sys_Printf("threads: %d\n", numthreads); +} + +#include + +typedef struct pt_mutex_s +{ + pthread_t *owner; + pthread_mutex_t a_mutex; + pthread_cond_t cond; + unsigned int lock; +} pt_mutex_t; + +pt_mutex_t global_lock; + +void ThreadLock(void) +{ + pt_mutex_t *pt_mutex = &global_lock; + + if(!threaded) + return; + + pthread_mutex_lock(&pt_mutex->a_mutex); + if(pthread_equal(pthread_self(), (pthread_t)&pt_mutex->owner)) + pt_mutex->lock++; + else + { + if((!pt_mutex->owner) && (pt_mutex->lock == 0)) + { + pt_mutex->owner = (pthread_t *)pthread_self(); + pt_mutex->lock = 1; + } + else + { + while(1) + { + pthread_cond_wait(&pt_mutex->cond, &pt_mutex->a_mutex); + if((!pt_mutex->owner) && (pt_mutex->lock == 0)) + { + pt_mutex->owner = (pthread_t *)pthread_self(); + pt_mutex->lock = 1; + break; + } + } + } + } + pthread_mutex_unlock(&pt_mutex->a_mutex); +} + +void ThreadUnlock(void) +{ + pt_mutex_t *pt_mutex = &global_lock; + + if(!threaded) + return; + + pthread_mutex_lock(&pt_mutex->a_mutex); + pt_mutex->lock--; + + if(pt_mutex->lock == 0) + { + pt_mutex->owner = NULL; + pthread_cond_signal(&pt_mutex->cond); + } + + pthread_mutex_unlock(&pt_mutex->a_mutex); +} + +void recursive_mutex_init(pthread_mutexattr_t attribs) +{ + pt_mutex_t *pt_mutex = &global_lock; + + pt_mutex->owner = NULL; + if(pthread_mutex_init(&pt_mutex->a_mutex, &attribs) != 0) + Error("pthread_mutex_init failed\n"); + if(pthread_cond_init(&pt_mutex->cond, NULL) != 0) + Error("pthread_cond_init failed\n"); + + pt_mutex->lock = 0; +} + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + pthread_mutexattr_t mattrib; + pthread_t work_threads[MAX_THREADS]; + + int start, end; + int i=0, status=0; + + start = I_FloatTime (); + pacifier = showpacifier; + + dispatch = 0; + oldf = -1; + workcount = workcnt; + + if(numthreads == 1) + func(0); + else + { + threaded = true; + + if(pacifier) + setbuf(stdout, NULL); + + if(pthread_mutexattr_init(&mattrib) != 0) + Error("pthread_mutexattr_init failed"); +#if __GLIBC_MINOR__ == 1 + if (pthread_mutexattr_settype(&mattrib, PTHREAD_MUTEX_FAST_NP) != 0) +#else + if (pthread_mutexattr_settype(&mattrib, PTHREAD_MUTEX_ADAPTIVE_NP) != 0) +#endif + Error ("pthread_mutexattr_settype failed"); + recursive_mutex_init(mattrib); + + for (i=0 ; i +#include "cmdlib.h" +#include "inout.h" +#include "mathlib.h" +#include "trilib.h" + +// on disk representation of a face + + +#define FLOAT_START 99999.0 +#define FLOAT_END -FLOAT_START +#define MAGIC 123322 + +//#define NOISY 1 + +typedef struct { + float v[3]; +} vector; + +typedef struct +{ + vector n; /* normal */ + vector p; /* point */ + vector c; /* color */ + float u; /* u */ + float v; /* v */ +} aliaspoint_t; + +typedef struct { + aliaspoint_t pt[3]; +} tf_triangle; + + +void ByteSwapTri (tf_triangle *tri) +{ + int i; + + for (i=0 ; iverts[j][k] = tri.pt[j].p.v[k]; + } + } + + ptri++; + + if ((ptri - *pptri) >= MAXTRIANGLES) + Error ("Error: too many triangles; increase MAXTRIANGLES\n"); + } + } + + *numtriangles = ptri - *pptri; + + fclose (input); +} + diff --git a/tools/quake2/common/trilib.h b/tools/quake2/common/trilib.h new file mode 100644 index 00000000..5c32244a --- /dev/null +++ b/tools/quake2/common/trilib.h @@ -0,0 +1,31 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// +// trilib.h: header file for loading triangles from an Alias triangle file +// +#define MAXTRIANGLES 2048 + +typedef struct { + vec3_t verts[3]; +} triangle_t; + +void LoadTriangleList (char *filename, triangle_t **pptri, int *numtriangles); + diff --git a/tools/quake2/q2map/brushbsp.c b/tools/quake2/q2map/brushbsp.c new file mode 100644 index 00000000..53985b03 --- /dev/null +++ b/tools/quake2/q2map/brushbsp.c @@ -0,0 +1,1329 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qbsp.h" + + +int c_nodes; +int c_nonvis; +int c_active_brushes; + +// if a brush just barely pokes onto the other side, +// let it slide by without chopping +#define PLANESIDE_EPSILON 0.001 +//0.1 + +#define PSIDE_FRONT 1 +#define PSIDE_BACK 2 +#define PSIDE_BOTH (PSIDE_FRONT|PSIDE_BACK) +#define PSIDE_FACING 4 + + +void FindBrushInTree (node_t *node, int brushnum) +{ + bspbrush_t *b; + + if (node->planenum == PLANENUM_LEAF) + { + for (b=node->brushlist ; b ; b=b->next) + if (b->original->brushnum == brushnum) + Sys_Printf ("here\n"); + return; + } + FindBrushInTree (node->children[0], brushnum); + FindBrushInTree (node->children[1], brushnum); +} + +//================================================== + +/* +================ +DrawBrushList +================ +*/ +void DrawBrushList (bspbrush_t *brush, node_t *node) +{ + int i; + side_t *s; + + GLS_BeginScene (); + for ( ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + if (!s->winding) + continue; + if (s->texinfo == TEXINFO_NODE) + GLS_Winding (s->winding, 1); + else if (!s->visible) + GLS_Winding (s->winding, 2); + else + GLS_Winding (s->winding, 0); + } + } + GLS_EndScene (); +} + +/* +================ +WriteBrushList +================ +*/ +void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis) +{ + int i; + side_t *s; + FILE *f; + + Sys_FPrintf( SYS_VRB, "writing %s\n", name); + f = SafeOpenWrite (name); + + for ( ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + if (!s->winding) + continue; + if (onlyvis && !s->visible) + continue; + OutputWinding (brush->sides[i].winding, f); + } + } + + fclose (f); +} + +void PrintBrush (bspbrush_t *brush) +{ + int i; + + Sys_Printf ("brush: %p\n", brush); + for (i=0;inumsides ; i++) + { + pw(brush->sides[i].winding); + Sys_Printf ("\n"); + } +} + +/* +================== +BoundBrush + +Sets the mins/maxs based on the windings +================== +*/ +void BoundBrush (bspbrush_t *brush) +{ + int i, j; + winding_t *w; + + ClearBounds (brush->mins, brush->maxs); + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + AddPointToBounds (w->p[j], brush->mins, brush->maxs); + } +} + +/* +================== +CreateBrushWindings + +================== +*/ +void CreateBrushWindings (bspbrush_t *brush) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + for (i=0 ; inumsides ; i++) + { + side = &brush->sides[i]; + plane = &mapplanes[side->planenum]; + w = BaseWindingForPlane (plane->normal, plane->dist); + for (j=0 ; jnumsides && w; j++) + { + if (i == j) + continue; + if (brush->sides[j].bevel) + continue; + plane = &mapplanes[brush->sides[j].planenum^1]; + ChopWindingInPlace (&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); + } + + side->winding = w; + } + + BoundBrush (brush); +} + +/* +================== +BrushFromBounds + +Creates a new axial brush +================== +*/ +bspbrush_t *BrushFromBounds (vec3_t mins, vec3_t maxs) +{ + bspbrush_t *b; + int i; + vec3_t normal; + vec_t dist; + + b = AllocBrush (6); + b->numsides = 6; + for (i=0 ; i<3 ; i++) + { + VectorClear (normal); + normal[i] = 1; + dist = maxs[i]; + b->sides[i].planenum = FindFloatPlane (normal, dist); + + normal[i] = -1; + dist = -mins[i]; + b->sides[3+i].planenum = FindFloatPlane (normal, dist); + } + + CreateBrushWindings (b); + + return b; +} + +/* +================== +BrushVolume + +================== +*/ +vec_t BrushVolume (bspbrush_t *brush) +{ + int i; + winding_t *w; + vec3_t corner; + vec_t d, area, volume; + plane_t *plane; + + if (!brush) + return 0; + + // grab the first valid point as the corner + + w = NULL; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (w) + break; + } + if (!w) + return 0; + VectorCopy (w->p[0], corner); + + // make tetrahedrons to all other faces + + volume = 0; + for ( ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + plane = &mapplanes[brush->sides[i].planenum]; + d = -(DotProduct (corner, plane->normal) - plane->dist); + area = WindingArea (w); + volume += d*area; + } + + volume /= 3; + return volume; +} + +/* +================ +CountBrushList +================ +*/ +int CountBrushList (bspbrush_t *brushes) +{ + int c; + + c = 0; + for ( ; brushes ; brushes = brushes->next) + c++; + return c; +} + +/* +================ +AllocTree +================ +*/ +tree_t *AllocTree (void) +{ + tree_t *tree; + + tree = malloc(sizeof(*tree)); + memset (tree, 0, sizeof(*tree)); + ClearBounds (tree->mins, tree->maxs); + + return tree; +} + +/* +================ +AllocNode +================ +*/ +node_t *AllocNode (void) +{ + node_t *node; + + node = malloc(sizeof(*node)); + memset (node, 0, sizeof(*node)); + + return node; +} + + +/* +================ +AllocBrush +================ +*/ +bspbrush_t *AllocBrush (int numsides) +{ + bspbrush_t *bb; + int c; + + c = (int)&(((bspbrush_t *)0)->sides[numsides]); + bb = malloc(c); + memset (bb, 0, c); + if (numthreads == 1) + c_active_brushes++; + return bb; +} + +/* +================ +FreeBrush +================ +*/ +void FreeBrush (bspbrush_t *brushes) +{ + int i; + + for (i=0 ; inumsides ; i++) + if (brushes->sides[i].winding) + FreeWinding(brushes->sides[i].winding); + free (brushes); + if (numthreads == 1) + c_active_brushes--; +} + + +/* +================ +FreeBrushList +================ +*/ +void FreeBrushList (bspbrush_t *brushes) +{ + bspbrush_t *next; + + for ( ; brushes ; brushes = next) + { + next = brushes->next; + + FreeBrush (brushes); + } +} + +/* +================== +CopyBrush + +Duplicates the brush, the sides, and the windings +================== +*/ +bspbrush_t *CopyBrush (bspbrush_t *brush) +{ + bspbrush_t *newbrush; + int size; + int i; + + size = (int)&(((bspbrush_t *)0)->sides[brush->numsides]); + + newbrush = AllocBrush (brush->numsides); + memcpy (newbrush, brush, size); + + for (i=0 ; inumsides ; i++) + { + if (brush->sides[i].winding) + newbrush->sides[i].winding = CopyWinding (brush->sides[i].winding); + } + + return newbrush; +} + + +/* +================== +PointInLeaf + +================== +*/ +node_t *PointInLeaf (node_t *node, vec3_t point) +{ + vec_t d; + plane_t *plane; + + while (node->planenum != PLANENUM_LEAF) + { + plane = &mapplanes[node->planenum]; + d = DotProduct (point, plane->normal) - plane->dist; + if (d > 0) + node = node->children[0]; + else + node = node->children[1]; + } + + return node; +} + +//======================================================== + +/* +============== +BoxOnPlaneSide + +Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH +============== +*/ +int BoxOnPlaneSide (vec3_t mins, vec3_t maxs, plane_t *plane) +{ + int side; + int i; + vec3_t corners[2]; + vec_t dist1, dist2; + + // axial planes are easy + if (plane->type < 3) + { + side = 0; + if (maxs[plane->type] > plane->dist+PLANESIDE_EPSILON) + side |= PSIDE_FRONT; + if (mins[plane->type] < plane->dist-PLANESIDE_EPSILON) + side |= PSIDE_BACK; + return side; + } + + // create the proper leading and trailing verts for the box + + for (i=0 ; i<3 ; i++) + { + if (plane->normal[i] < 0) + { + corners[0][i] = mins[i]; + corners[1][i] = maxs[i]; + } + else + { + corners[1][i] = mins[i]; + corners[0][i] = maxs[i]; + } + } + + dist1 = DotProduct (plane->normal, corners[0]) - plane->dist; + dist2 = DotProduct (plane->normal, corners[1]) - plane->dist; + side = 0; + if (dist1 >= PLANESIDE_EPSILON) + side = PSIDE_FRONT; + if (dist2 < PLANESIDE_EPSILON) + side |= PSIDE_BACK; + + return side; +} + +/* +============ +QuickTestBrushToPlanenum + +============ +*/ +int QuickTestBrushToPlanenum (bspbrush_t *brush, int planenum, int *numsplits) +{ + int i, num; + plane_t *plane; + int s; + + *numsplits = 0; + + // if the brush actually uses the planenum, + // we can tell the side for sure + for (i=0 ; inumsides ; i++) + { + num = brush->sides[i].planenum; + if (num >= 0x10000) + Error ("bad planenum"); + if (num == planenum) + return PSIDE_BACK|PSIDE_FACING; + if (num == (planenum ^ 1) ) + return PSIDE_FRONT|PSIDE_FACING; + } + + // box on plane side + plane = &mapplanes[planenum]; + s = BoxOnPlaneSide (brush->mins, brush->maxs, plane); + + // if both sides, count the visible faces split + if (s == PSIDE_BOTH) + { + *numsplits += 3; + } + + return s; +} + +/* +============ +TestBrushToPlanenum + +============ +*/ +int TestBrushToPlanenum (bspbrush_t *brush, int planenum, + int *numsplits, qboolean *hintsplit, int *epsilonbrush) +{ + int i, j, num; + plane_t *plane; + int s; + winding_t *w; + vec_t d, d_front, d_back; + int front, back; + + *numsplits = 0; + *hintsplit = false; + + // if the brush actually uses the planenum, + // we can tell the side for sure + for (i=0 ; inumsides ; i++) + { + num = brush->sides[i].planenum; + if (num >= 0x10000) + Error ("bad planenum"); + if (num == planenum) + return PSIDE_BACK|PSIDE_FACING; + if (num == (planenum ^ 1) ) + return PSIDE_FRONT|PSIDE_FACING; + } + + // box on plane side + plane = &mapplanes[planenum]; + s = BoxOnPlaneSide (brush->mins, brush->maxs, plane); + + if (s != PSIDE_BOTH) + return s; + +// if both sides, count the visible faces split + d_front = d_back = 0; + + for (i=0 ; inumsides ; i++) + { + if (brush->sides[i].texinfo == TEXINFO_NODE) + continue; // on node, don't worry about splits + if (!brush->sides[i].visible) + continue; // we don't care about non-visible + w = brush->sides[i].winding; + if (!w) + continue; + front = back = 0; + for (j=0 ; jnumpoints; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > d_front) + d_front = d; + if (d < d_back) + d_back = d; + + if (d > 0.1) // PLANESIDE_EPSILON) + front = 1; + if (d < -0.1) // PLANESIDE_EPSILON) + back = 1; + } + if (front && back) + { + if ( !(brush->sides[i].surf & SURF_SKIP) ) + { + (*numsplits)++; + if (brush->sides[i].surf & SURF_HINT) + *hintsplit = true; + } + } + } + + if ( (d_front > 0.0 && d_front < 1.0) + || (d_back < 0.0 && d_back > -1.0) ) + (*epsilonbrush)++; + +#if 0 + if (*numsplits == 0) + { // didn't really need to be split + if (front) + s = PSIDE_FRONT; + else if (back) + s = PSIDE_BACK; + else + s = 0; + } +#endif + + return s; +} + +//======================================================== + +/* +================ +WindingIsTiny + +Returns true if the winding would be crunched out of +existance by the vertex snapping. +================ +*/ +#define EDGE_LENGTH 0.2 +qboolean WindingIsTiny (winding_t *w) +{ +#if 0 + if (WindingArea (w) < 1) + return true; + return false; +#else + int i, j; + vec_t len; + vec3_t delta; + int edges; + + edges = 0; + for (i=0 ; inumpoints ; i++) + { + j = i == w->numpoints - 1 ? 0 : i+1; + VectorSubtract (w->p[j], w->p[i], delta); + len = (float) VectorLength (delta); + if (len > EDGE_LENGTH) + { + if (++edges == 3) + return false; + } + } + return true; +#endif +} + +/* +================ +WindingIsHuge + +Returns true if the winding still has one of the points +from basewinding for plane +================ +*/ +qboolean WindingIsHuge (winding_t *w) +{ + int i, j; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + if (w->p[i][j] < -8000 || w->p[i][j] > 8000) + return true; + } + return false; +} + +//============================================================ + +/* +================ +Leafnode +================ +*/ +void LeafNode (node_t *node, bspbrush_t *brushes) +{ + bspbrush_t *b; + int i; + + node->planenum = PLANENUM_LEAF; + node->contents = 0; + + for (b=brushes ; b ; b=b->next) + { + // if the brush is solid and all of its sides are on nodes, + // it eats everything + if (b->original->contents & CONTENTS_SOLID) + { + for (i=0 ; inumsides ; i++) + if (b->sides[i].texinfo != TEXINFO_NODE) + break; + if (i == b->numsides) + { + node->contents = CONTENTS_SOLID; + break; + } + } + node->contents |= b->original->contents; + } + + node->brushlist = brushes; +} + + +//============================================================ + +void CheckPlaneAgainstParents (int pnum, node_t *node) +{ + node_t *p; + + for (p=node->parent ; p ; p=p->parent) + { + if (p->planenum == pnum) + Error ("Tried parent"); + } +} + +qboolean CheckPlaneAgainstVolume (int pnum, node_t *node) +{ + bspbrush_t *front, *back; + qboolean good; + + SplitBrush (node->volume, pnum, &front, &back); + + good = (front && back); + + if (front) + FreeBrush (front); + if (back) + FreeBrush (back); + + return good; +} + +/* +================ +SelectSplitSide + +Using a hueristic, choses one of the sides out of the brushlist +to partition the brushes with. +Returns NULL if there are no valid planes to split with.. +================ +*/ +side_t *SelectSplitSide (bspbrush_t *brushes, node_t *node) +{ + int value, bestvalue; + bspbrush_t *brush, *test; + side_t *side, *bestside; + int i, j, pass, numpasses; + int pnum; + int s; + int front, back, both, facing, splits; + int bsplits; + int bestsplits; + int epsilonbrush; + qboolean hintsplit; + + bestside = NULL; + bestvalue = -99999; + bestsplits = 0; + + // the search order goes: visible-structural, visible-detail, + // nonvisible-structural, nonvisible-detail. + // If any valid plane is available in a pass, no further + // passes will be tried. + numpasses = 4; + for (pass = 0 ; pass < numpasses ; pass++) + { + for (brush = brushes ; brush ; brush=brush->next) + { + if ( (pass & 1) && !(brush->original->contents & CONTENTS_DETAIL) ) + continue; + if ( !(pass & 1) && (brush->original->contents & CONTENTS_DETAIL) ) + continue; + for (i=0 ; inumsides ; i++) + { + side = brush->sides + i; + if (side->bevel) + continue; // never use a bevel as a spliter + if (!side->winding) + continue; // nothing visible, so it can't split + if (side->texinfo == TEXINFO_NODE) + continue; // allready a node splitter + if (side->tested) + continue; // we allready have metrics for this plane + if (side->surf & SURF_SKIP) + continue; // skip surfaces are never chosen + if ( side->visible ^ (pass<2) ) + continue; // only check visible faces on first pass + + pnum = side->planenum; + pnum &= ~1; // allways use positive facing plane + + CheckPlaneAgainstParents (pnum, node); + + if (!CheckPlaneAgainstVolume (pnum, node)) + continue; // would produce a tiny volume + + front = 0; + back = 0; + both = 0; + facing = 0; + splits = 0; + epsilonbrush = 0; + + for (test = brushes ; test ; test=test->next) + { + s = TestBrushToPlanenum (test, pnum, &bsplits, &hintsplit, &epsilonbrush); + + splits += bsplits; + if (bsplits && (s&PSIDE_FACING) ) + Error ("PSIDE_FACING with splits"); + + test->testside = s; + // if the brush shares this face, don't bother + // testing that facenum as a splitter again + if (s & PSIDE_FACING) + { + facing++; + for (j=0 ; jnumsides ; j++) + { + if ( (test->sides[j].planenum&~1) == pnum) + test->sides[j].tested = true; + } + } + if (s & PSIDE_FRONT) + front++; + if (s & PSIDE_BACK) + back++; + if (s == PSIDE_BOTH) + both++; + } + + // give a value estimate for using this plane + + value = 5*facing - 5*splits - abs(front-back); +// value = -5*splits; +// value = 5*facing - 5*splits; + if (mapplanes[pnum].type < 3) + value+=5; // axial is better + value -= epsilonbrush*1000; // avoid! + + // never split a hint side except with another hint + if (hintsplit && !(side->surf & SURF_HINT) ) + value = -9999999; + + // save off the side test so we don't need + // to recalculate it when we actually seperate + // the brushes + if (value > bestvalue) + { + bestvalue = value; + bestside = side; + bestsplits = splits; + for (test = brushes ; test ; test=test->next) + test->side = test->testside; + } + } + } + + // if we found a good plane, don't bother trying any + // other passes + if (bestside) + { + if (pass > 1) + { + if (numthreads == 1) + c_nonvis++; + } + if (pass > 0) + node->detail_seperator = true; // not needed for vis + break; + } + } + + // + // clear all the tested flags we set + // + for (brush = brushes ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + brush->sides[i].tested = false; + } + + return bestside; +} + + +/* +================== +BrushMostlyOnSide + +================== +*/ +int BrushMostlyOnSide (bspbrush_t *brush, plane_t *plane) +{ + int i, j; + winding_t *w; + vec_t d, max; + int side; + + max = 0; + side = PSIDE_FRONT; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > max) + { + max = d; + side = PSIDE_FRONT; + } + if (-d > max) + { + max = -d; + side = PSIDE_BACK; + } + } + } + return side; +} + +/* +================ +SplitBrush + +Generates two new brushes, leaving the original +unchanged +================ +*/ +void SplitBrush (bspbrush_t *brush, int planenum, + bspbrush_t **front, bspbrush_t **back) +{ + bspbrush_t *b[2]; + int i, j; + winding_t *w, *cw[2], *midwinding; + plane_t *plane, *plane2; + side_t *s, *cs; + float d, d_front, d_back; + + *front = *back = NULL; + plane = &mapplanes[planenum]; + + // check all points + d_front = d_back = 0; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > 0 && d > d_front) + d_front = d; + if (d < 0 && d < d_back) + d_back = d; + } + } + if (d_front < 0.1) // PLANESIDE_EPSILON) + { // only on back + *back = CopyBrush (brush); + return; + } + if (d_back > -0.1) // PLANESIDE_EPSILON) + { // only on front + *front = CopyBrush (brush); + return; + } + + // create a new winding from the split plane + + w = BaseWindingForPlane (plane->normal, plane->dist); + for (i=0 ; inumsides && w ; i++) + { + plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; + ChopWindingInPlace (&w, plane2->normal, plane2->dist, 0); // PLANESIDE_EPSILON); + } + + if (!w || WindingIsTiny (w) ) + { // the brush isn't really split + int side; + + side = BrushMostlyOnSide (brush, plane); + if (side == PSIDE_FRONT) + *front = CopyBrush (brush); + if (side == PSIDE_BACK) + *back = CopyBrush (brush); + return; + } + + if (WindingIsHuge (w)) + { + Sys_FPrintf( SYS_VRB, "WARNING: huge winding\n"); + } + + midwinding = w; + + // split it for real + + for (i=0 ; i<2 ; i++) + { + b[i] = AllocBrush (brush->numsides+1); + b[i]->original = brush->original; + } + + // split all the current windings + + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + w = s->winding; + if (!w) + continue; + ClipWindingEpsilon (w, plane->normal, plane->dist, + 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]); + for (j=0 ; j<2 ; j++) + { + if (!cw[j]) + continue; +#if 0 + if (WindingIsTiny (cw[j])) + { + FreeWinding (cw[j]); + continue; + } +#endif + cs = &b[j]->sides[b[j]->numsides]; + b[j]->numsides++; + *cs = *s; +// cs->planenum = s->planenum; +// cs->texinfo = s->texinfo; +// cs->visible = s->visible; +// cs->original = s->original; + cs->winding = cw[j]; + cs->tested = false; + } + } + + + // see if we have valid polygons on both sides + + for (i=0 ; i<2 ; i++) + { + BoundBrush (b[i]); + for (j=0 ; j<3 ; j++) + { + if (b[i]->mins[j] < -4096 || b[i]->maxs[j] > 4096) + { + Sys_FPrintf( SYS_VRB, "bogus brush after clip\n"); + break; + } + } + + if (b[i]->numsides < 3 || j < 3) + { + FreeBrush (b[i]); + b[i] = NULL; + } + } + + if ( !(b[0] && b[1]) ) + { + if (!b[0] && !b[1]) + Sys_FPrintf( SYS_VRB, "split removed brush\n"); + else + Sys_FPrintf( SYS_VRB, "split not on both sides\n"); + if (b[0]) + { + FreeBrush (b[0]); + *front = CopyBrush (brush); + } + if (b[1]) + { + FreeBrush (b[1]); + *back = CopyBrush (brush); + } + return; + } + + // add the midwinding to both sides + for (i=0 ; i<2 ; i++) + { + cs = &b[i]->sides[b[i]->numsides]; + b[i]->numsides++; + + cs->planenum = planenum^i^1; + cs->texinfo = TEXINFO_NODE; + cs->visible = false; + cs->tested = false; + if (i==0) + cs->winding = CopyWinding (midwinding); + else + cs->winding = midwinding; + } + +{ + vec_t v1; + int i; + + for (i=0 ; i<2 ; i++) + { + v1 = BrushVolume (b[i]); + if (v1 < 1.0) + { + FreeBrush (b[i]); + b[i] = NULL; + Sys_FPrintf( SYS_VRB, "tiny volume after clip\n"); + } + } +} + + *front = b[0]; + *back = b[1]; +} + +/* +================ +SplitBrushList +================ +*/ +void SplitBrushList (bspbrush_t *brushes, + node_t *node, bspbrush_t **front, bspbrush_t **back) +{ + bspbrush_t *brush, *newbrush, *newbrush2; + side_t *side; + int sides; + int i; + + *front = *back = NULL; + + for (brush = brushes ; brush ; brush=brush->next) + { + sides = brush->side; + + if (sides == PSIDE_BOTH) + { // split into two brushes + SplitBrush (brush, node->planenum, &newbrush, &newbrush2); + if (newbrush) + { + newbrush->next = *front; + *front = newbrush; + } + if (newbrush2) + { + newbrush2->next = *back; + *back = newbrush2; + } + continue; + } + + newbrush = CopyBrush (brush); + + // if the planenum is actualy a part of the brush + // find the plane and flag it as used so it won't be tried + // as a splitter again + if (sides & PSIDE_FACING) + { + for (i=0 ; inumsides ; i++) + { + side = newbrush->sides + i; + if ( (side->planenum& ~1) == node->planenum) + side->texinfo = TEXINFO_NODE; + } + } + + + if (sides & PSIDE_FRONT) + { + newbrush->next = *front; + *front = newbrush; + continue; + } + if (sides & PSIDE_BACK) + { + newbrush->next = *back; + *back = newbrush; + continue; + } + } +} + + +/* +================ +BuildTree_r +================ +*/ +node_t *BuildTree_r (node_t *node, bspbrush_t *brushes) +{ + node_t *newnode; + side_t *bestside; + int i; + bspbrush_t *children[2]; + + if (numthreads == 1) + c_nodes++; + + if (drawflag) + DrawBrushList (brushes, node); + + // find the best plane to use as a splitter + bestside = SelectSplitSide (brushes, node); + if (!bestside) + { + // leaf node + node->side = NULL; + node->planenum = -1; + LeafNode (node, brushes); + return node; + } + + // this is a splitplane node + node->side = bestside; + node->planenum = bestside->planenum & ~1; // always use front facing + + SplitBrushList (brushes, node, &children[0], &children[1]); + FreeBrushList (brushes); + + // allocate children before recursing + for (i=0 ; i<2 ; i++) + { + newnode = AllocNode (); + newnode->parent = node; + node->children[i] = newnode; + } + + SplitBrush (node->volume, node->planenum, &node->children[0]->volume, + &node->children[1]->volume); + + // recursively process children + for (i=0 ; i<2 ; i++) + { + node->children[i] = BuildTree_r (node->children[i], children[i]); + } + + return node; +} + +//=========================================================== + +/* +================= +BrushBSP + +The incoming list will be freed before exiting +================= +*/ +tree_t *BrushBSP (bspbrush_t *brushlist, vec3_t mins, vec3_t maxs) +{ + node_t *node; + bspbrush_t *b; + int c_faces, c_nonvisfaces; + int c_brushes; + tree_t *tree; + int i; + vec_t volume; + + Sys_FPrintf( SYS_VRB, "--- BrushBSP ---\n"); + + tree = AllocTree (); + + c_faces = 0; + c_nonvisfaces = 0; + c_brushes = 0; + for (b=brushlist ; b ; b=b->next) + { + c_brushes++; + + volume = BrushVolume (b); + if (volume < microvolume) + { + Sys_Printf ("WARNING: entity %i, brush %i: microbrush\n", + b->original->entitynum, b->original->brushnum); + } + + for (i=0 ; inumsides ; i++) + { + if (b->sides[i].bevel) + continue; + if (!b->sides[i].winding) + continue; + if (b->sides[i].texinfo == TEXINFO_NODE) + continue; + if (b->sides[i].visible) + c_faces++; + else + c_nonvisfaces++; + } + + AddPointToBounds (b->mins, tree->mins, tree->maxs); + AddPointToBounds (b->maxs, tree->mins, tree->maxs); + } + + Sys_FPrintf( SYS_VRB, "%5i brushes\n", c_brushes); + Sys_FPrintf( SYS_VRB, "%5i visible faces\n", c_faces); + Sys_FPrintf( SYS_VRB, "%5i nonvisible faces\n", c_nonvisfaces); + + c_nodes = 0; + c_nonvis = 0; + node = AllocNode (); + + node->volume = BrushFromBounds (mins, maxs); + + tree->headnode = node; + + node = BuildTree_r (node, brushlist); + Sys_FPrintf( SYS_VRB, "%5i visible nodes\n", c_nodes/2 - c_nonvis); + Sys_FPrintf( SYS_VRB, "%5i nonvis nodes\n", c_nonvis); + Sys_FPrintf( SYS_VRB, "%5i leafs\n", (c_nodes+1)/2); +#if 0 +{ // debug code +static node_t *tnode; +vec3_t p; + +p[0] = -1469; +p[1] = -118; +p[2] = 119; +tnode = PointInLeaf (tree->headnode, p); +Sys_Printf ("contents: %i\n", tnode->contents); +p[0] = 0; +} +#endif + return tree; +} + diff --git a/tools/quake2/q2map/csg.c b/tools/quake2/q2map/csg.c new file mode 100644 index 00000000..2e058299 --- /dev/null +++ b/tools/quake2/q2map/csg.c @@ -0,0 +1,634 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qbsp.h" + +/* + +tag all brushes with original contents +brushes may contain multiple contents +there will be no brush overlap after csg phase + + + + +each side has a count of the other sides it splits + +the best split will be the one that minimizes the total split counts +of all remaining sides + +precalc side on plane table + +evaluate split side +{ +cost = 0 +for all sides + for all sides + get + if side splits side and splitside is on same child + cost++; +} + + + */ + +void SplitBrush2 (bspbrush_t *brush, int planenum, + bspbrush_t **front, bspbrush_t **back) +{ + SplitBrush (brush, planenum, front, back); +#if 0 + if (*front && (*front)->sides[(*front)->numsides-1].texinfo == -1) + (*front)->sides[(*front)->numsides-1].texinfo = (*front)->sides[0].texinfo; // not -1 + if (*back && (*back)->sides[(*back)->numsides-1].texinfo == -1) + (*back)->sides[(*back)->numsides-1].texinfo = (*back)->sides[0].texinfo; // not -1 +#endif +} + +/* +=============== +SubtractBrush + +Returns a list of brushes that remain after B is subtracted from A. +May by empty if A is contained inside B. + +The originals are undisturbed. +=============== +*/ +bspbrush_t *SubtractBrush (bspbrush_t *a, bspbrush_t *b) +{ // a - b = out (list) + int i; + bspbrush_t *front, *back; + bspbrush_t *out, *in; + + in = a; + out = NULL; + for (i=0 ; inumsides && in ; i++) + { + SplitBrush2 (in, b->sides[i].planenum, &front, &back); + if (in != a) + FreeBrush (in); + if (front) + { // add to list + front->next = out; + out = front; + } + in = back; + } + if (in) + FreeBrush (in); + else + { // didn't really intersect + FreeBrushList (out); + return a; + } + return out; +} + +/* +=============== +IntersectBrush + +Returns a single brush made up by the intersection of the +two provided brushes, or NULL if they are disjoint. + +The originals are undisturbed. +=============== +*/ +bspbrush_t *IntersectBrush (bspbrush_t *a, bspbrush_t *b) +{ + int i; + bspbrush_t *front, *back; + bspbrush_t *in; + + in = a; + for (i=0 ; inumsides && in ; i++) + { + SplitBrush2 (in, b->sides[i].planenum, &front, &back); + if (in != a) + FreeBrush (in); + if (front) + FreeBrush (front); + in = back; + } + + if (in == a) + return NULL; + + in->next = NULL; + return in; +} + + +/* +=============== +BrushesDisjoint + +Returns true if the two brushes definately do not intersect. +There will be false negatives for some non-axial combinations. +=============== +*/ +qboolean BrushesDisjoint (bspbrush_t *a, bspbrush_t *b) +{ + int i, j; + + // check bounding boxes + for (i=0 ; i<3 ; i++) + if (a->mins[i] >= b->maxs[i] + || a->maxs[i] <= b->mins[i]) + return true; // bounding boxes don't overlap + + // check for opposing planes + for (i=0 ; inumsides ; i++) + { + for (j=0 ; jnumsides ; j++) + { + if (a->sides[i].planenum == + (b->sides[j].planenum^1) ) + return true; // opposite planes, so not touching + } + } + + return false; // might intersect +} + +/* +=============== +IntersectionContents + +Returns a content word for the intersection of two brushes. +Some combinations will generate a combination (water + clip), +but most will be the stronger of the two contents. +=============== +*/ +int IntersectionContents (int c1, int c2) +{ + int out; + + out = c1 | c2; + + if (out & CONTENTS_SOLID) + out = CONTENTS_SOLID; + + return out; +} + + +int minplanenums[3]; +int maxplanenums[3]; + +/* +=============== +ClipBrushToBox + +Any planes shared with the box edge will be set to no texinfo +=============== +*/ +bspbrush_t *ClipBrushToBox (bspbrush_t *brush, vec3_t clipmins, vec3_t clipmaxs) +{ + int i, j; + bspbrush_t *front, *back; + int p; + + for (j=0 ; j<2 ; j++) + { + if (brush->maxs[j] > clipmaxs[j]) + { + SplitBrush (brush, maxplanenums[j], &front, &back); + if (front) + FreeBrush (front); + brush = back; + if (!brush) + return NULL; + } + if (brush->mins[j] < clipmins[j]) + { + SplitBrush (brush, minplanenums[j], &front, &back); + if (back) + FreeBrush (back); + brush = front; + if (!brush) + return NULL; + } + } + + // remove any colinear faces + + for (i=0 ; inumsides ; i++) + { + p = brush->sides[i].planenum & ~1; + if (p == maxplanenums[0] || p == maxplanenums[1] + || p == minplanenums[0] || p == minplanenums[1]) + { + brush->sides[i].texinfo = TEXINFO_NODE; + brush->sides[i].visible = false; + } + } + return brush; +} + +/* +=============== +MakeBspBrushList +=============== +*/ +bspbrush_t *MakeBspBrushList (int startbrush, int endbrush, + vec3_t clipmins, vec3_t clipmaxs) +{ + mapbrush_t *mb; + bspbrush_t *brushlist, *newbrush; + int i, j; + int c_faces; + int c_brushes; + int numsides; + int vis; + vec3_t normal; + float dist; + + for (i=0 ; i<2 ; i++) + { + VectorClear (normal); + normal[i] = 1; + dist = clipmaxs[i]; + maxplanenums[i] = FindFloatPlane (normal, dist); + dist = clipmins[i]; + minplanenums[i] = FindFloatPlane (normal, dist); + } + + brushlist = NULL; + c_faces = 0; + c_brushes = 0; + + for (i=startbrush ; inumsides; + if (!numsides) + continue; + // make sure the brush has at least one face showing + vis = 0; + for (j=0 ; joriginal_sides[j].visible && mb->original_sides[j].winding) + vis++; +#if 0 + if (!vis) + continue; // no faces at all +#endif + // if the brush is outside the clip area, skip it + for (j=0 ; j<3 ; j++) + if (mb->mins[j] >= clipmaxs[j] + || mb->maxs[j] <= clipmins[j]) + break; + if (j != 3) + continue; + + // + // make a copy of the brush + // + newbrush = AllocBrush (mb->numsides); + newbrush->original = mb; + newbrush->numsides = mb->numsides; + memcpy (newbrush->sides, mb->original_sides, numsides*sizeof(side_t)); + for (j=0 ; jsides[j].winding) + newbrush->sides[j].winding = CopyWinding (newbrush->sides[j].winding); + if (newbrush->sides[j].surf & SURF_HINT) + newbrush->sides[j].visible = true; // hints are always visible + } + VectorCopy (mb->mins, newbrush->mins); + VectorCopy (mb->maxs, newbrush->maxs); + + // + // carve off anything outside the clip box + // + newbrush = ClipBrushToBox (newbrush, clipmins, clipmaxs); + if (!newbrush) + continue; + + c_faces += vis; + c_brushes++; + + newbrush->next = brushlist; + brushlist = newbrush; + } + + return brushlist; +} + +/* +=============== +AddBspBrushListToTail +=============== +*/ +bspbrush_t *AddBrushListToTail (bspbrush_t *list, bspbrush_t *tail) +{ + bspbrush_t *walk, *next; + + for (walk=list ; walk ; walk=next) + { // add to end of list + next = walk->next; + walk->next = NULL; + tail->next = walk; + tail = walk; + } + + return tail; +} + +/* +=========== +CullList + +Builds a new list that doesn't hold the given brush +=========== +*/ +bspbrush_t *CullList (bspbrush_t *list, bspbrush_t *skip1) +{ + bspbrush_t *newlist; + bspbrush_t *next; + + newlist = NULL; + + for ( ; list ; list = next) + { + next = list->next; + if (list == skip1) + { + FreeBrush (list); + continue; + } + list->next = newlist; + newlist = list; + } + return newlist; +} + + +/* +================== +WriteBrushMap +================== +*/ +void WriteBrushMap (char *name, bspbrush_t *list) +{ + FILE *f; + side_t *s; + int i; + winding_t *w; + + Sys_Printf ("writing %s\n", name); + f = fopen (name, "wb"); + if (!f) + Error ("Can't write %s\b", name); + + fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); + + for ( ; list ; list=list->next ) + { + fprintf (f, "{\n"); + for (i=0,s=list->sides ; inumsides ; i++,s++) + { + w = BaseWindingForPlane (mapplanes[s->planenum].normal, mapplanes[s->planenum].dist); + + fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + + fprintf (f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture); + FreeWinding (w); + } + fprintf (f, "}\n"); + } + fprintf (f, "}\n"); + + fclose (f); + +} + +/* +================== +BrushGE + +Returns true if b1 is allowed to bite b2 +================== +*/ +qboolean BrushGE (bspbrush_t *b1, bspbrush_t *b2) +{ + // detail brushes never bite structural brushes + if ( (b1->original->contents & CONTENTS_DETAIL) + && !(b2->original->contents & CONTENTS_DETAIL) ) + return false; + if (b1->original->contents & CONTENTS_SOLID) + return true; + return false; +} + +/* +================= +ChopBrushes + +Carves any intersecting solid brushes into the minimum number +of non-intersecting brushes. +================= +*/ +bspbrush_t *ChopBrushes (bspbrush_t *head) +{ + bspbrush_t *b1, *b2, *next; + bspbrush_t *tail; + bspbrush_t *keep; + bspbrush_t *sub, *sub2; + int c1, c2; + + Sys_FPrintf( SYS_VRB, "---- ChopBrushes ----\n"); + Sys_FPrintf( SYS_VRB, "original brushes: %i\n", CountBrushList (head)); + +#if 0 + if (startbrush == 0) + WriteBrushList ("before.gl", head, false); +#endif + keep = NULL; + +newlist: + // find tail + if (!head) + return NULL; + for (tail=head ; tail->next ; tail=tail->next) + ; + + for (b1=head ; b1 ; b1=next) + { + next = b1->next; + for (b2=b1->next ; b2 ; b2 = b2->next) + { + if (BrushesDisjoint (b1, b2)) + continue; + + sub = NULL; + sub2 = NULL; + c1 = 999999; + c2 = 999999; + + if ( BrushGE (b2, b1) ) + { + sub = SubtractBrush (b1, b2); + if (sub == b1) + continue; // didn't really intersect + if (!sub) + { // b1 is swallowed by b2 + head = CullList (b1, b1); + goto newlist; + } + c1 = CountBrushList (sub); + } + + if ( BrushGE (b1, b2) ) + { + sub2 = SubtractBrush (b2, b1); + if (sub2 == b2) + continue; // didn't really intersect + if (!sub2) + { // b2 is swallowed by b1 + FreeBrushList (sub); + head = CullList (b1, b2); + goto newlist; + } + c2 = CountBrushList (sub2); + } + + if (!sub && !sub2) + continue; // neither one can bite + + // only accept if it didn't fragment + // (commening this out allows full fragmentation) + if (c1 > 1 && c2 > 1) + { + if (sub2) + FreeBrushList (sub2); + if (sub) + FreeBrushList (sub); + continue; + } + + if (c1 < c2) + { + if (sub2) + FreeBrushList (sub2); + tail = AddBrushListToTail (sub, tail); + head = CullList (b1, b1); + goto newlist; + } + else + { + if (sub) + FreeBrushList (sub); + tail = AddBrushListToTail (sub2, tail); + head = CullList (b1, b2); + goto newlist; + } + } + + if (!b2) + { // b1 is no longer intersecting anything, so keep it + b1->next = keep; + keep = b1; + } + } + + Sys_FPrintf( SYS_VRB, "output brushes: %i\n", CountBrushList (keep)); +#if 0 + { + WriteBrushList ("after.gl", keep, false); + WriteBrushMap ("after.map", keep); + } +#endif + return keep; +} + + +/* +================= +InitialBrushList +================= +*/ +bspbrush_t *InitialBrushList (bspbrush_t *list) +{ + bspbrush_t *b; + bspbrush_t *out, *newb; + int i; + + // only return brushes that have visible faces + out = NULL; + for (b=list ; b ; b=b->next) + { +#if 0 + for (i=0 ; inumsides ; i++) + if (b->sides[i].visible) + break; + if (i == b->numsides) + continue; +#endif + newb = CopyBrush (b); + newb->next = out; + out = newb; + + // clear visible, so it must be set by MarkVisibleFaces_r + // to be used in the optimized list + for (i=0 ; inumsides ; i++) + { + newb->sides[i].original = &b->sides[i]; +// newb->sides[i].visible = true; + b->sides[i].visible = false; + } + } + + return out; +} + +/* +================= +OptimizedBrushList +================= +*/ +bspbrush_t *OptimizedBrushList (bspbrush_t *list) +{ + bspbrush_t *b; + bspbrush_t *out, *newb; + int i; + + // only return brushes that have visible faces + out = NULL; + for (b=list ; b ; b=b->next) + { + for (i=0 ; inumsides ; i++) + if (b->sides[i].visible) + break; + if (i == b->numsides) + continue; + newb = CopyBrush (b); + newb->next = out; + out = newb; + } + +// WriteBrushList ("vis.gl", out, true); + + return out; +} diff --git a/tools/quake2/q2map/faces.c b/tools/quake2/q2map/faces.c new file mode 100644 index 00000000..03d5b955 --- /dev/null +++ b/tools/quake2/q2map/faces.c @@ -0,0 +1,1076 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// faces.c + +#include "qbsp.h" + +/* + + some faces will be removed before saving, but still form nodes: + + the insides of sky volumes + meeting planes of different water current volumes + +*/ + +// undefine for dumb linear searches +#define USE_HASHING + +#define INTEGRAL_EPSILON 0.01 +#define POINT_EPSILON 0.5 +#define OFF_EPSILON 0.5 + +int c_merge; +int c_subdivide; + +int c_totalverts; +int c_uniqueverts; +int c_degenerate; +int c_tjunctions; +int c_faceoverflows; +int c_facecollapse; +int c_badstartverts; + +#define MAX_SUPERVERTS 512 +int superverts[MAX_SUPERVERTS]; +int numsuperverts; + +face_t *edgefaces[MAX_MAP_EDGES][2]; +int firstmodeledge = 1; +int firstmodelface; + +int c_tryedges; + +vec3_t edge_dir; +vec3_t edge_start; +vec_t edge_len; + +int num_edge_verts; +int edge_verts[MAX_MAP_VERTS]; + + +float subdivide_size = 240; + + +face_t *NewFaceFromFace (face_t *f); + +//=========================================================================== + +typedef struct hashvert_s +{ + struct hashvert_s *next; + int num; +} hashvert_t; + + +#define HASH_SIZE 64 + + +int vertexchain[MAX_MAP_VERTS]; // the next vertex in a hash chain +int hashverts[HASH_SIZE*HASH_SIZE]; // a vertex number, or 0 for no verts + +face_t *edgefaces[MAX_MAP_EDGES][2]; + +//============================================================================ + + +unsigned HashVec (vec3_t vec) +{ + int x, y; + + x = (4096 + (int)(vec[0]+0.5)) >> 7; + y = (4096 + (int)(vec[1]+0.5)) >> 7; + + if ( x < 0 || x >= HASH_SIZE || y < 0 || y >= HASH_SIZE ) + Error ("HashVec: point outside valid range"); + + return y*HASH_SIZE + x; +} + +#ifdef USE_HASHING +/* +============= +GetVertex + +Uses hashing +============= +*/ +int GetVertexnum (vec3_t in) +{ + int h; + int i; + float *p; + vec3_t vert; + int vnum; + + c_totalverts++; + + for (i=0 ; i<3 ; i++) + { + if ( fabs(in[i] - Q_rint(in[i])) < INTEGRAL_EPSILON) + vert[i] = Q_rint(in[i]); + else + vert[i] = in[i]; + } + + h = HashVec (vert); + + for (vnum=hashverts[h] ; vnum ; vnum=vertexchain[vnum]) + { + p = dvertexes[vnum].point; + if ( fabs(p[0]-vert[0]) 4096) + Error ("GetVertexnum: outside +/- 4096"); + } + + // search for an existing vertex match + for (i=0, dv=dvertexes ; ipoint[j]; + if ( d > POINT_EPSILON || d < -POINT_EPSILON) + break; + } + if (j == 3) + return i; // a match + } + + // new point + if (numvertexes == MAX_MAP_VERTS) + Error ("MAX_MAP_VERTS"); + VectorCopy (v, dv->point); + numvertexes++; + c_uniqueverts++; + + return numvertexes-1; +} +#endif + + +/* +================== +FaceFromSuperverts + +The faces vertexes have beeb added to the superverts[] array, +and there may be more there than can be held in a face (MAXEDGES). + +If less, the faces vertexnums[] will be filled in, otherwise +face will reference a tree of split[] faces until all of the +vertexnums can be added. + +superverts[base] will become face->vertexnums[0], and the others +will be circularly filled in. +================== +*/ +void FaceFromSuperverts (node_t *node, face_t *f, int base) +{ + face_t *newf; + int remaining; + int i; + + remaining = numsuperverts; + while (remaining > MAXEDGES) + { // must split into two faces, because of vertex overload + c_faceoverflows++; + + newf = f->split[0] = NewFaceFromFace (f); + newf = f->split[0]; + newf->next = node->faces; + node->faces = newf; + + newf->numpoints = MAXEDGES; + for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; + + f->split[1] = NewFaceFromFace (f); + f = f->split[1]; + f->next = node->faces; + node->faces = f; + + remaining -= (MAXEDGES-2); + base = (base+MAXEDGES-1)%numsuperverts; + } + + // copy the vertexes back to the face + f->numpoints = remaining; + for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; +} + + +/* +================== +EmitFaceVertexes +================== +*/ +void EmitFaceVertexes (node_t *node, face_t *f) +{ + winding_t *w; + int i; + + if (f->merged || f->split[0] || f->split[1]) + return; + + w = f->w; + for (i=0 ; inumpoints ; i++) + { + if (noweld) + { // make every point unique + if (numvertexes == MAX_MAP_VERTS) + Error ("MAX_MAP_VERTS"); + superverts[i] = numvertexes; + VectorCopy (w->p[i], dvertexes[numvertexes].point); + numvertexes++; + c_uniqueverts++; + c_totalverts++; + } + else + superverts[i] = GetVertexnum (w->p[i]); + } + numsuperverts = w->numpoints; + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts (node, f, 0); +} + +/* +================== +EmitVertexes_r +================== +*/ +void EmitVertexes_r (node_t *node) +{ + int i; + face_t *f; + + if (node->planenum == PLANENUM_LEAF) + return; + + for (f=node->faces ; f ; f=f->next) + { + EmitFaceVertexes (node, f); + } + + for (i=0 ; i<2 ; i++) + EmitVertexes_r (node->children[i]); +} + + +#ifdef USE_HASHING +/* +========== +FindEdgeVerts + +Uses the hash tables to cut down to a small number +========== +*/ +void FindEdgeVerts (vec3_t v1, vec3_t v2) +{ + int x1, x2, y1, y2, t; + int x, y; + int vnum; + +#if 0 +{ + int i; + num_edge_verts = numvertexes-1; + for (i=0 ; i> 7; + y1 = (4096 + (int)(v1[1]+0.5)) >> 7; + x2 = (4096 + (int)(v2[0]+0.5)) >> 7; + y2 = (4096 + (int)(v2[1]+0.5)) >> 7; + + if (x1 > x2) + { + t = x1; + x1 = x2; + x2 = t; + } + if (y1 > y2) + { + t = y1; + y1 = y2; + y2 = t; + } +#if 0 + x1--; + x2++; + y1--; + y2++; + if (x1 < 0) + x1 = 0; + if (x2 >= HASH_SIZE) + x2 = HASH_SIZE; + if (y1 < 0) + y1 = 0; + if (y2 >= HASH_SIZE) + y2 = HASH_SIZE; +#endif + num_edge_verts = 0; + for (x=x1 ; x <= x2 ; x++) + { + for (y=y1 ; y <= y2 ; y++) + { + for (vnum=hashverts[y*HASH_SIZE+x] ; vnum ; vnum=vertexchain[vnum]) + { + edge_verts[num_edge_verts++] = vnum; + } + } + } +} + +#else +/* +========== +FindEdgeVerts + +Forced a dumb check of everything +========== +*/ +void FindEdgeVerts (vec3_t v1, vec3_t v2) +{ + int i; + + num_edge_verts = numvertexes-1; + for (i=0 ; i= end) + continue; // off an end + VectorMA (edge_start, dist, edge_dir, exact); + VectorSubtract (p, exact, off); + error = VectorLength (off); + + if (fabs(error) > OFF_EPSILON) + continue; // not on the edge + + // break the edge + c_tjunctions++; + TestEdge (start, dist, p1, j, k+1); + TestEdge (dist, end, j, p2, k+1); + return; + } + + // the edge p1 to p2 is now free of tjunctions + if (numsuperverts >= MAX_SUPERVERTS) + Error ("MAX_SUPERVERTS"); + superverts[numsuperverts] = p1; + numsuperverts++; +} + +/* +================== +FixFaceEdges + +================== +*/ +void FixFaceEdges (node_t *node, face_t *f) +{ + int p1, p2; + int i; + vec3_t e2; + vec_t len; + int count[MAX_SUPERVERTS], start[MAX_SUPERVERTS]; + int base; + + if (f->merged || f->split[0] || f->split[1]) + return; + + numsuperverts = 0; + + for (i=0 ; inumpoints ; i++) + { + p1 = f->vertexnums[i]; + p2 = f->vertexnums[(i+1)%f->numpoints]; + + VectorCopy (dvertexes[p1].point, edge_start); + VectorCopy (dvertexes[p2].point, e2); + + FindEdgeVerts (edge_start, e2); + + VectorSubtract (e2, edge_start, edge_dir); + len = VectorNormalize (edge_dir, edge_dir); + + start[i] = numsuperverts; + TestEdge (0, len, p1, p2, 0); + + count[i] = numsuperverts - start[i]; + } + + if (numsuperverts < 3) + { // entire face collapsed + f->numpoints = 0; + c_facecollapse++; + return; + } + + // we want to pick a vertex that doesn't have tjunctions + // on either side, which can cause artifacts on trifans, + // especially underwater + for (i=0 ; inumpoints ; i++) + { + if (count[i] == 1 && count[(i+f->numpoints-1)%f->numpoints] == 1) + break; + } + if (i == f->numpoints) + { + f->badstartvert = true; + c_badstartverts++; + base = 0; + } + else + { // rotate the vertex order + base = start[i]; + } + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts (node, f, base); +} + +/* +================== +FixEdges_r +================== +*/ +void FixEdges_r (node_t *node) +{ + int i; + face_t *f; + + if (node->planenum == PLANENUM_LEAF) + return; + + for (f=node->faces ; f ; f=f->next) + FixFaceEdges (node, f); + + for (i=0 ; i<2 ; i++) + FixEdges_r (node->children[i]); +} + +/* +=========== +FixTjuncs + +=========== +*/ +void FixTjuncs (node_t *headnode) +{ + // snap and merge all vertexes + Sys_FPrintf( SYS_VRB, "---- snap verts ----\n"); + memset (hashverts, 0, sizeof(hashverts)); + c_totalverts = 0; + c_uniqueverts = 0; + c_faceoverflows = 0; + EmitVertexes_r (headnode); + Sys_FPrintf( SYS_VRB, "%i unique from %i\n", c_uniqueverts, c_totalverts); + + // break edges on tjunctions + Sys_FPrintf( SYS_VRB, "---- tjunc ----\n"); + c_tryedges = 0; + c_degenerate = 0; + c_facecollapse = 0; + c_tjunctions = 0; + if (!notjunc) + FixEdges_r (headnode); + Sys_FPrintf( SYS_VRB, "%5i edges degenerated\n", c_degenerate); + Sys_FPrintf( SYS_VRB, "%5i faces degenerated\n", c_facecollapse); + Sys_FPrintf( SYS_VRB, "%5i edges added by tjunctions\n", c_tjunctions); + Sys_FPrintf( SYS_VRB, "%5i faces added by tjunctions\n", c_faceoverflows); + Sys_FPrintf( SYS_VRB, "%5i bad start verts\n", c_badstartverts); +} + + +//======================================================== + +int c_faces; + +face_t *AllocFace (void) +{ + face_t *f; + + f = malloc(sizeof(*f)); + memset (f, 0, sizeof(*f)); + c_faces++; + + return f; +} + +face_t *NewFaceFromFace (face_t *f) +{ + face_t *newf; + + newf = AllocFace (); + *newf = *f; + newf->merged = NULL; + newf->split[0] = newf->split[1] = NULL; + newf->w = NULL; + return newf; +} + +void FreeFace (face_t *f) +{ + if (f->w) + FreeWinding (f->w); + free (f); + c_faces--; +} + +//======================================================== + +/* +================== +GetEdge + +Called by writebsp. +Don't allow four way edges +================== +*/ +int GetEdge2 (int v1, int v2, face_t *f) +{ + dedge_t *edge; + int i; + + c_tryedges++; + + if (!noshare) + { + for (i=firstmodeledge ; i < numedges ; i++) + { + edge = &dedges[i]; + if (v1 == edge->v[1] && v2 == edge->v[0] + && edgefaces[i][0]->contents == f->contents) + { + if (edgefaces[i][1]) + // Sys_Printf ("WARNING: multiple backward edge\n"); + continue; + edgefaces[i][1] = f; + return -i; + } + #if 0 + if (v1 == edge->v[0] && v2 == edge->v[1]) + { + Sys_Printf ("WARNING: multiple forward edge\n"); + return i; + } + #endif + } + } + +// emit an edge + if (numedges >= MAX_MAP_EDGES) + Error ("numedges == MAX_MAP_EDGES"); + edge = &dedges[numedges]; + numedges++; + edge->v[0] = v1; + edge->v[1] = v2; + edgefaces[numedges-1][0] = f; + + return numedges-1; +} + +/* +=========================================================================== + +FACE MERGING + +=========================================================================== +*/ + +#define CONTINUOUS_EPSILON 0.001 + +/* +============= +TryMergeWinding + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ +winding_t *TryMergeWinding (winding_t *f1, winding_t *f2, vec3_t planenormal) +{ + vec_t *p1, *p2, *p3, *p4, *back; + winding_t *newf; + int i, j, k, l; + vec3_t normal, delta; + vec_t dot; + qboolean keep1, keep2; + + + // + // find a common edge + // + p1 = p2 = NULL; // stop compiler warning + j = 0; // + + for (i=0 ; inumpoints ; i++) + { + p1 = f1->p[i]; + p2 = f1->p[(i+1)%f1->numpoints]; + for (j=0 ; jnumpoints ; j++) + { + p3 = f2->p[j]; + p4 = f2->p[(j+1)%f2->numpoints]; + for (k=0 ; k<3 ; k++) + { + if (fabs(p1[k] - p4[k]) > EQUAL_EPSILON) + break; + if (fabs(p2[k] - p3[k]) > EQUAL_EPSILON) + break; + } + if (k==3) + break; + } + if (j < f2->numpoints) + break; + } + + if (i == f1->numpoints) + return NULL; // no matching edges + + // + // check slope of connected lines + // if the slopes are colinear, the point can be removed + // + back = f1->p[(i+f1->numpoints-1)%f1->numpoints]; + VectorSubtract (p1, back, delta); + CrossProduct (planenormal, delta, normal); + VectorNormalize (normal, normal); + + back = f2->p[(j+2)%f2->numpoints]; + VectorSubtract (back, p1, delta); + dot = DotProduct (delta, normal); + if (dot > CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep1 = (qboolean)(dot < -CONTINUOUS_EPSILON); + + back = f1->p[(i+2)%f1->numpoints]; + VectorSubtract (back, p2, delta); + CrossProduct (planenormal, delta, normal); + VectorNormalize (normal, normal); + + back = f2->p[(j+f2->numpoints-1)%f2->numpoints]; + VectorSubtract (back, p2, delta); + dot = DotProduct (delta, normal); + if (dot > CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep2 = (qboolean)(dot < -CONTINUOUS_EPSILON); + + // + // build the new polygon + // + newf = AllocWinding (f1->numpoints + f2->numpoints); + + // copy first polygon + for (k=(i+1)%f1->numpoints ; k != i ; k=(k+1)%f1->numpoints) + { + if (k==(i+1)%f1->numpoints && !keep2) + continue; + + VectorCopy (f1->p[k], newf->p[newf->numpoints]); + newf->numpoints++; + } + + // copy second polygon + for (l= (j+1)%f2->numpoints ; l != j ; l=(l+1)%f2->numpoints) + { + if (l==(j+1)%f2->numpoints && !keep1) + continue; + VectorCopy (f2->p[l], newf->p[newf->numpoints]); + newf->numpoints++; + } + + return newf; +} + +/* +============= +TryMerge + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ +face_t *TryMerge (face_t *f1, face_t *f2, vec3_t planenormal) +{ + face_t *newf; + winding_t *nw; + + if (!f1->w || !f2->w) + return NULL; + if (f1->texinfo != f2->texinfo) + return NULL; + if (f1->planenum != f2->planenum) // on front and back sides + return NULL; + if (f1->contents != f2->contents) + return NULL; + + + nw = TryMergeWinding (f1->w, f2->w, planenormal); + if (!nw) + return NULL; + + c_merge++; + newf = NewFaceFromFace (f1); + newf->w = nw; + + f1->merged = newf; + f2->merged = newf; + + return newf; +} + +/* +=============== +MergeNodeFaces +=============== +*/ +void MergeNodeFaces (node_t *node) +{ + face_t *f1, *f2, *end; + face_t *merged; + plane_t *plane; + + plane = &mapplanes[node->planenum]; + merged = NULL; + + for (f1 = node->faces ; f1 ; f1 = f1->next) + { + if (f1->merged || f1->split[0] || f1->split[1]) + continue; + for (f2 = node->faces ; f2 != f1 ; f2=f2->next) + { + if (f2->merged || f2->split[0] || f2->split[1]) + continue; + merged = TryMerge (f1, f2, plane->normal); + if (!merged) + continue; + + // add merged to the end of the node face list + // so it will be checked against all the faces again + for (end = node->faces ; end->next ; end = end->next) + ; + merged->next = NULL; + end->next = merged; + break; + } + } +} + +//===================================================================== + +/* +=============== +SubdivideFace + +Chop up faces that are larger than we want in the surface cache +=============== +*/ +void SubdivideFace (node_t *node, face_t *f) +{ + float mins, maxs; + vec_t v; + int axis, i; + texinfo_t *tex; + vec3_t temp; + vec_t dist; + winding_t *w, *frontw, *backw; + + if (f->merged) + return; + +// special (non-surface cached) faces don't need subdivision + tex = &texinfo[f->texinfo]; + + if ( tex->flags & (SURF_WARP|SURF_SKY) ) + { + return; + } + + for (axis = 0 ; axis < 2 ; axis++) + { + while (1) + { + mins = 999999; + maxs = -999999; + + VectorCopy (tex->vecs[axis], temp); + w = f->w; + for (i=0 ; inumpoints ; i++) + { + v = DotProduct (w->p[i], temp); + if (v < mins) + mins = v; + if (v > maxs) + maxs = v; + } +#if 0 + if (maxs - mins <= 0) + Error ("zero extents"); +#endif + if (axis == 2) + { // allow double high walls + if (maxs - mins <= subdivide_size/* *2 */) + break; + } + else if (maxs - mins <= subdivide_size) + break; + + // split it + c_subdivide++; + + v = VectorNormalize (temp, temp); + + dist = (mins + subdivide_size - 16)/v; + + ClipWindingEpsilon (w, temp, dist, ON_EPSILON, &frontw, &backw); + if (!frontw || !backw) + Error ("SubdivideFace: didn't split the polygon"); + + f->split[0] = NewFaceFromFace (f); + f->split[0]->w = frontw; + f->split[0]->next = node->faces; + node->faces = f->split[0]; + + f->split[1] = NewFaceFromFace (f); + f->split[1]->w = backw; + f->split[1]->next = node->faces; + node->faces = f->split[1]; + + SubdivideFace (node, f->split[0]); + SubdivideFace (node, f->split[1]); + return; + } + } +} + +void SubdivideNodeFaces (node_t *node) +{ + face_t *f; + + for (f = node->faces ; f ; f=f->next) + { + SubdivideFace (node, f); + } +} + +//=========================================================================== + +int c_nodefaces; + + +/* +============ +FaceFromPortal + +============ +*/ +face_t *FaceFromPortal (portal_t *p, int pside) +{ + face_t *f; + side_t *side; + + side = p->side; + if (!side) + return NULL; // portal does not bridge different visible contents + + f = AllocFace (); + + f->texinfo = side->texinfo; + f->planenum = (side->planenum & ~1) | pside; + f->portal = p; + + if ( (p->nodes[pside]->contents & CONTENTS_WINDOW) + && VisibleContents(p->nodes[!pside]->contents^p->nodes[pside]->contents) == CONTENTS_WINDOW ) + return NULL; // don't show insides of windows + + if (pside) + { + f->w = ReverseWinding(p->winding); + f->contents = p->nodes[1]->contents; + } + else + { + f->w = CopyWinding(p->winding); + f->contents = p->nodes[0]->contents; + } + return f; +} + + +/* +=============== +MakeFaces_r + +If a portal will make a visible face, +mark the side that originally created it + + solid / empty : solid + solid / water : solid + water / empty : water + water / water : none +=============== +*/ +void MakeFaces_r (node_t *node) +{ + portal_t *p; + int s; + + // recurse down to leafs + if (node->planenum != PLANENUM_LEAF) + { + MakeFaces_r (node->children[0]); + MakeFaces_r (node->children[1]); + + // merge together all visible faces on the node + if (!nomerge) + MergeNodeFaces (node); + if (!nosubdiv) + SubdivideNodeFaces (node); + + return; + } + + // solid leafs never have visible faces + if (node->contents & CONTENTS_SOLID) + return; + + // see which portals are valid + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + p->face[s] = FaceFromPortal (p, s); + if (p->face[s]) + { + c_nodefaces++; + p->face[s]->next = p->onnode->faces; + p->onnode->faces = p->face[s]; + } + } +} + +/* +============ +MakeFaces +============ +*/ +void MakeFaces (node_t *node) +{ + Sys_FPrintf( SYS_VRB, "--- MakeFaces ---\n"); + c_merge = 0; + c_subdivide = 0; + c_nodefaces = 0; + + MakeFaces_r (node); + + Sys_FPrintf( SYS_VRB, "%5i makefaces\n", c_nodefaces); + Sys_FPrintf( SYS_VRB, "%5i merged\n", c_merge); + Sys_FPrintf( SYS_VRB, "%5i subdivided\n", c_subdivide); +} diff --git a/tools/quake2/q2map/flow.c b/tools/quake2/q2map/flow.c new file mode 100644 index 00000000..8bd8022a --- /dev/null +++ b/tools/quake2/q2map/flow.c @@ -0,0 +1,787 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "qvis.h" + +/* + + each portal will have a list of all possible to see from first portal + + if (!thread->portalmightsee[portalnum]) + + portal mightsee + + for p2 = all other portals in leaf + get sperating planes + for all portals that might be seen by p2 + mark as unseen if not present in seperating plane + flood fill a new mightsee + save as passagemightsee + + + void CalcMightSee (leaf_t *leaf, +*/ + +int CountBits (byte *bits, int numbits) +{ + int i; + int c; + + c = 0; + for (i=0 ; i>3] & (1<<(i&7)) ) + c++; + + return c; +} + +int c_fullskip; +int c_portalskip, c_leafskip; +int c_vistest, c_mighttest; + +int c_chop, c_nochop; + +int active; + +void CheckStack (leaf_t *leaf, threaddata_t *thread) +{ + pstack_t *p, *p2; + + for (p=thread->pstack_head.next ; p ; p=p->next) + { +// printf ("="); + if (p->leaf == leaf) + Error ("CheckStack: leaf recursion"); + for (p2=thread->pstack_head.next ; p2 != p ; p2=p2->next) + if (p2->leaf == p->leaf) + Error ("CheckStack: late leaf recursion"); + } +// printf ("\n"); +} + + +winding_t *AllocStackWinding (pstack_t *stack) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (stack->freewindings[i]) + { + stack->freewindings[i] = 0; + return &stack->windings[i]; + } + } + + Error ("AllocStackWinding: failed"); + + return NULL; +} + +void FreeStackWinding (winding_t *w, pstack_t *stack) +{ + int i; + + i = w - stack->windings; + + if (i<0 || i>2) + return; // not from local + + if (stack->freewindings[i]) + Error ("FreeStackWinding: allready free"); + stack->freewindings[i] = 1; +} + +/* +============== +Vis_ChopWinding + +============== +*/ +winding_t *Vis_ChopWinding (winding_t *in, pstack_t *stack, plane_t *split) +{ + vec_t dists[128]; + int sides[128]; + int counts[3]; + vec_t dot; + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *neww; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->points[i], split->normal); + dot -= split->dist; + dists[i] = dot; + if (dot > ON_EPSILON) + sides[i] = SIDE_FRONT; + else if (dot < -ON_EPSILON) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + if (!counts[1]) + return in; // completely on front side + + if (!counts[0]) + { + FreeStackWinding (in, stack); + return NULL; + } + + sides[i] = sides[0]; + dists[i] = dists[0]; + + neww = AllocStackWinding (stack); + + neww->numpoints = 0; + + for (i=0 ; inumpoints ; i++) + { + p1 = in->points[i]; + + if (neww->numpoints == MAX_POINTS_ON_FIXED_WINDING) + { + FreeStackWinding (neww, stack); + return in; // can't chop -- fall back to original + } + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, neww->points[neww->numpoints]); + neww->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, neww->points[neww->numpoints]); + neww->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + if (neww->numpoints == MAX_POINTS_ON_FIXED_WINDING) + { + FreeStackWinding (neww, stack); + return in; // can't chop -- fall back to original + } + + // generate a split point + p2 = in->points[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (split->normal[j] == 1) + mid[j] = split->dist; + else if (split->normal[j] == -1) + mid[j] = -split->dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, neww->points[neww->numpoints]); + neww->numpoints++; + } + +// free the original winding + FreeStackWinding (in, stack); + + return neww; +} + + +/* +============== +ClipToSeperators + +Source, pass, and target are an ordering of portals. + +Generates seperating planes canidates by taking two points from source and one +point from pass, and clips target by them. + +If target is totally clipped away, that portal can not be seen through. + +Normal clip keeps target on the same side as pass, which is correct if the +order goes source, pass, target. If the order goes pass, source, target then +flipclip should be set. +============== +*/ +winding_t *ClipToSeperators (winding_t *source, winding_t *pass, winding_t *target, qboolean flipclip, pstack_t *stack) +{ + int i, j, k, l; + plane_t plane; + vec3_t v1, v2; + float d; + vec_t length; + int counts[3]; + qboolean fliptest; + +// check all combinations + for (i=0 ; inumpoints ; i++) + { + l = (i+1)%source->numpoints; + VectorSubtract (source->points[l] , source->points[i], v1); + + // fing a vertex of pass that makes a plane that puts all of the + // vertexes of pass on the front side and all of the vertexes of + // source on the back side + for (j=0 ; jnumpoints ; j++) + { + VectorSubtract (pass->points[j], source->points[i], v2); + + plane.normal[0] = v1[1]*v2[2] - v1[2]*v2[1]; + plane.normal[1] = v1[2]*v2[0] - v1[0]*v2[2]; + plane.normal[2] = v1[0]*v2[1] - v1[1]*v2[0]; + + // if points don't make a valid plane, skip it + + length = plane.normal[0] * plane.normal[0] + + plane.normal[1] * plane.normal[1] + + plane.normal[2] * plane.normal[2]; + + if (length < ON_EPSILON) + continue; + + length = 1/sqrt(length); + + plane.normal[0] *= length; + plane.normal[1] *= length; + plane.normal[2] *= length; + + plane.dist = DotProduct (pass->points[j], plane.normal); + + // + // find out which side of the generated seperating plane has the + // source portal + // +#if 1 + fliptest = false; + for (k=0 ; knumpoints ; k++) + { + if (k == i || k == l) + continue; + d = DotProduct (source->points[k], plane.normal) - plane.dist; + if (d < -ON_EPSILON) + { // source is on the negative side, so we want all + // pass and target on the positive side + fliptest = false; + break; + } + else if (d > ON_EPSILON) + { // source is on the positive side, so we want all + // pass and target on the negative side + fliptest = true; + break; + } + } + if (k == source->numpoints) + continue; // planar with source portal +#else + fliptest = flipclip; +#endif + // + // flip the normal if the source portal is backwards + // + if (fliptest) + { + VectorSubtract (vec3_origin, plane.normal, plane.normal); + plane.dist = -plane.dist; + } +#if 1 + // + // if all of the pass portal points are now on the positive side, + // this is the seperating plane + // + counts[0] = counts[1] = counts[2] = 0; + for (k=0 ; knumpoints ; k++) + { + if (k==j) + continue; + d = DotProduct (pass->points[k], plane.normal) - plane.dist; + if (d < -ON_EPSILON) + break; + else if (d > ON_EPSILON) + counts[0]++; + else + counts[2]++; + } + if (k != pass->numpoints) + continue; // points on negative side, not a seperating plane + + if (!counts[0]) + continue; // planar with seperating plane +#else + k = (j+1)%pass->numpoints; + d = DotProduct (pass->points[k], plane.normal) - plane.dist; + if (d < -ON_EPSILON) + continue; + k = (j+pass->numpoints-1)%pass->numpoints; + d = DotProduct (pass->points[k], plane.normal) - plane.dist; + if (d < -ON_EPSILON) + continue; +#endif + // + // flip the normal if we want the back side + // + if (flipclip) + { + VectorSubtract (vec3_origin, plane.normal, plane.normal); + plane.dist = -plane.dist; + } + + // + // clip target by the seperating plane + // + target = Vis_ChopWinding (target, stack, &plane); + if (!target) + return NULL; // target is not visible + } + } + + return target; +} + + + +/* +================== +RecursiveLeafFlow + +Flood fill through the leafs +If src_portal is NULL, this is the originating leaf +================== +*/ +void RecursiveLeafFlow (int leafnum, threaddata_t *thread, pstack_t *prevstack) +{ + pstack_t stack; + portal_t *p; + plane_t backplane; + leaf_t *leaf; + int i, j; + long *test, *might, *vis, more; + int pnum; + + thread->c_chains++; + + leaf = &leafs[leafnum]; +// CheckStack (leaf, thread); + + prevstack->next = &stack; + + stack.next = NULL; + stack.leaf = leaf; + stack.portal = NULL; + + might = (long *)stack.mightsee; + vis = (long *)thread->base->portalvis; + +// check all portals for flowing into other leafs + for (i=0 ; inumportals ; i++) + { + p = leaf->portals[i]; + pnum = p - portals; + + if ( ! (prevstack->mightsee[pnum >> 3] & (1<<(pnum&7)) ) ) + { + continue; // can't possibly see it + } + + // if the portal can't see anything we haven't allready seen, skip it + if (p->status == stat_done) + { + test = (long *)p->portalvis; + } + else + { + test = (long *)p->portalflood; + } + + more = 0; + for (j=0 ; jmightsee)[j] & test[j]; + more |= (might[j] & ~vis[j]); + } + + if (!more && + (thread->base->portalvis[pnum>>3] & (1<<(pnum&7))) ) + { // can't see anything new + continue; + } + + // get plane of portal, point normal into the neighbor leaf + stack.portalplane = p->plane; + VectorSubtract (vec3_origin, p->plane.normal, backplane.normal); + backplane.dist = -p->plane.dist; + +// c_portalcheck++; + + stack.portal = p; + stack.next = NULL; + stack.freewindings[0] = 1; + stack.freewindings[1] = 1; + stack.freewindings[2] = 1; + +#if 1 +{ +float d; + + d = DotProduct (p->origin, thread->pstack_head.portalplane.normal); + d -= thread->pstack_head.portalplane.dist; + if (d < -p->radius) + { + continue; + } + else if (d > p->radius) + { + stack.pass = p->winding; + } + else + { + stack.pass = Vis_ChopWinding (p->winding, &stack, &thread->pstack_head.portalplane); + if (!stack.pass) + continue; + } +} +#else + stack.pass = Vis_ChopWinding (p->winding, &stack, &thread->pstack_head.portalplane); + if (!stack.pass) + continue; +#endif + + +#if 1 +{ +float d; + + d = DotProduct (thread->base->origin, p->plane.normal); + d -= p->plane.dist; + if (d > p->radius) + { + continue; + } + else if (d < -p->radius) + { + stack.source = prevstack->source; + } + else + { + stack.source = Vis_ChopWinding (prevstack->source, &stack, &backplane); + if (!stack.source) + continue; + } +} +#else + stack.source = Vis_ChopWinding (prevstack->source, &stack, &backplane); + if (!stack.source) + continue; +#endif + + if (!prevstack->pass) + { // the second leaf can only be blocked if coplanar + + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + + RecursiveLeafFlow (p->leaf, thread, &stack); + continue; + } + + stack.pass = ClipToSeperators (stack.source, prevstack->pass, stack.pass, false, &stack); + if (!stack.pass) + continue; + + stack.pass = ClipToSeperators (prevstack->pass, stack.source, stack.pass, true, &stack); + if (!stack.pass) + continue; + + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + + // flow through it for real + RecursiveLeafFlow (p->leaf, thread, &stack); + } +} + + +/* +=============== +PortalFlow + +generates the portalvis bit vector +=============== +*/ +void PortalFlow (int portalnum) +{ + threaddata_t data; + int i; + portal_t *p; + int c_might, c_can; + + p = sorted_portals[portalnum]; + p->status = stat_working; + + c_might = CountBits (p->portalflood, numportals*2); + + memset (&data, 0, sizeof(data)); + data.base = p; + + data.pstack_head.portal = p; + data.pstack_head.source = p->winding; + data.pstack_head.portalplane = p->plane; + for (i=0 ; iportalflood)[i]; + RecursiveLeafFlow (p->leaf, &data, &data.pstack_head); + + p->status = stat_done; + + c_can = CountBits (p->portalvis, numportals*2); + + Sys_FPrintf ( SYS_VRB, "portal:%4i mightsee:%4i cansee:%4i (%i chains)\n", + (int)(p - portals), c_might, c_can, data.c_chains); +} + + +/* +=============================================================================== + +This is a rough first-order aproximation that is used to trivially reject some +of the final calculations. + + +Calculates portalfront and portalflood bit vectors + +thinking about: + +typedef struct passage_s +{ + struct passage_s *next; + struct portal_s *to; + stryct sep_s *seperators; + byte *mightsee; +} passage_t; + +typedef struct portal_s +{ + struct passage_s *passages; + int leaf; // leaf portal faces into +} portal_s; + +leaf = portal->leaf +clear +for all portals + + +calc portal visibility + clear bit vector + for all passages + passage visibility + + +for a portal to be visible to a passage, it must be on the front of +all seperating planes, and both portals must be behind the mew portal + +=============================================================================== +*/ + +int c_flood, c_vis; + + +/* +================== +SimpleFlood + +================== +*/ +void SimpleFlood (portal_t *srcportal, int leafnum) +{ + int i; + leaf_t *leaf; + portal_t *p; + int pnum; + + leaf = &leafs[leafnum]; + + for (i=0 ; inumportals ; i++) + { + p = leaf->portals[i]; + pnum = p - portals; + if ( ! (srcportal->portalfront[pnum>>3] & (1<<(pnum&7)) ) ) + continue; + + if (srcportal->portalflood[pnum>>3] & (1<<(pnum&7)) ) + continue; + + srcportal->portalflood[pnum>>3] |= (1<<(pnum&7)); + + SimpleFlood (srcportal, p->leaf); + } +} + +/* +============== +BasePortalVis +============== +*/ +void BasePortalVis (int portalnum) +{ + int j, k; + portal_t *tp, *p; + float d; + winding_t *w; + + p = portals+portalnum; + + p->portalfront = malloc (portalbytes); + memset (p->portalfront, 0, portalbytes); + + p->portalflood = malloc (portalbytes); + memset (p->portalflood, 0, portalbytes); + + p->portalvis = malloc (portalbytes); + memset (p->portalvis, 0, portalbytes); + + for (j=0, tp = portals ; jwinding; + for (k=0 ; knumpoints ; k++) + { + d = DotProduct (w->points[k], p->plane.normal) + - p->plane.dist; + if (d > ON_EPSILON) + break; + } + if (k == w->numpoints) + continue; // no points on front + + w = p->winding; + for (k=0 ; knumpoints ; k++) + { + d = DotProduct (w->points[k], tp->plane.normal) + - tp->plane.dist; + if (d < -ON_EPSILON) + break; + } + if (k == w->numpoints) + continue; // no points on front + + p->portalfront[j>>3] |= (1<<(j&7)); + } + + SimpleFlood (p, p->leaf); + + p->nummightsee = CountBits (p->portalflood, numportals*2); +// printf ("portal %i: %i mightsee\n", portalnum, p->nummightsee); + c_flood += p->nummightsee; +} + + + + + +/* +=============================================================================== + +This is a second order aproximation + +Calculates portalvis bit vector + +WAAAAAAY too slow. + +=============================================================================== +*/ + +/* +================== +RecursiveLeafBitFlow + +================== +*/ +void RecursiveLeafBitFlow (int leafnum, byte *mightsee, byte *cansee) +{ + portal_t *p; + leaf_t *leaf; + int i, j; + long more; + int pnum; + byte newmight[MAX_PORTALS/8]; + + leaf = &leafs[leafnum]; + +// check all portals for flowing into other leafs + for (i=0 ; inumportals ; i++) + { + p = leaf->portals[i]; + pnum = p - portals; + + // if some previous portal can't see it, skip + if (! (mightsee[pnum>>3] & (1<<(pnum&7)) ) ) + continue; + + // if this portal can see some portals we mightsee, recurse + more = 0; + for (j=0 ; jportalflood)[j]; + more |= ((long *)newmight)[j] & ~((long *)cansee)[j]; + } + + if (!more) + continue; // can't see anything new + + cansee[pnum>>3] |= (1<<(pnum&7)); + + RecursiveLeafBitFlow (p->leaf, newmight, cansee); + } +} + +/* +============== +BetterPortalVis +============== +*/ +void BetterPortalVis (int portalnum) +{ + portal_t *p; + + p = portals+portalnum; + + RecursiveLeafBitFlow (p->leaf, p->portalflood, p->portalvis); + + // build leaf vis information + p->nummightsee = CountBits (p->portalvis, numportals*2); + c_vis += p->nummightsee; +} + + diff --git a/tools/quake2/q2map/gldraw.c b/tools/quake2/q2map/gldraw.c new file mode 100644 index 00000000..6e249c31 --- /dev/null +++ b/tools/quake2/q2map/gldraw.c @@ -0,0 +1,231 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include +#include + +#include "qbsp.h" + +// can't use the glvertex3fv functions, because the vec3_t fields +// could be either floats or doubles, depending on DOUBLEVEC_T + +qboolean drawflag; +vec3_t draw_mins, draw_maxs; + + +#define WIN_SIZE 512 + +void InitWindow (void) +{ + auxInitDisplayMode (AUX_SINGLE | AUX_RGB); + auxInitPosition (0, 0, WIN_SIZE, WIN_SIZE); + auxInitWindow ("qcsg"); +} + +void Draw_ClearWindow (void) +{ + static int init; + int w, h, g; + vec_t mx, my; + + if (!drawflag) + return; + + if (!init) + { + init = true; + InitWindow (); + } + + glClearColor (1,0.8,0.8,0); + glClear (GL_COLOR_BUFFER_BIT); + + w = (draw_maxs[0] - draw_mins[0]); + h = (draw_maxs[1] - draw_mins[1]); + + mx = draw_mins[0] + w/2; + my = draw_mins[1] + h/2; + + g = w > h ? w : h; + + glLoadIdentity (); + gluPerspective (90, 1, 2, 16384); + gluLookAt (mx, my, draw_maxs[2] + g/2, mx , my, draw_maxs[2], 0, 1, 0); + + glColor3f (0,0,0); +// glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + glDisable (GL_DEPTH_TEST); + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + +#if 0 + glColor4f (1,0,0,0.5); + glBegin (GL_POLYGON); + + glVertex3f (0, 500, 0); + glVertex3f (0, 900, 0); + glVertex3f (0, 900, 100); + glVertex3f (0, 500, 100); + + glEnd (); +#endif + + glFlush (); + +} + +void Draw_SetRed (void) +{ + if (!drawflag) + return; + + glColor3f (1,0,0); +} + +void Draw_SetGrey (void) +{ + if (!drawflag) + return; + + glColor3f (0.5,0.5,0.5); +} + +void Draw_SetBlack (void) +{ + if (!drawflag) + return; + + glColor3f (0,0,0); +} + +void DrawWinding (winding_t *w) +{ + int i; + + if (!drawflag) + return; + + glColor4f (0,0,0,0.5); + glBegin (GL_LINE_LOOP); + for (i=0 ; inumpoints ; i++) + glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); + glEnd (); + + glColor4f (0,1,0,0.3); + glBegin (GL_POLYGON); + for (i=0 ; inumpoints ; i++) + glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); + glEnd (); + + glFlush (); +} + +void DrawAuxWinding (winding_t *w) +{ + int i; + + if (!drawflag) + return; + + glColor4f (0,0,0,0.5); + glBegin (GL_LINE_LOOP); + for (i=0 ; inumpoints ; i++) + glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); + glEnd (); + + glColor4f (1,0,0,0.3); + glBegin (GL_POLYGON); + for (i=0 ; inumpoints ; i++) + glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); + glEnd (); + + glFlush (); +} + +//============================================================ + +#define GLSERV_PORT 25001 + +qboolean wins_init; +int draw_socket; + +void GLS_BeginScene (void) +{ + WSADATA winsockdata; + WORD wVersionRequested; + struct sockaddr_in address; + int r; + + if (!wins_init) + { + wins_init = true; + + wVersionRequested = MAKEWORD(1, 1); + + r = WSAStartup (MAKEWORD(1, 1), &winsockdata); + + if (r) + Error ("Winsock initialization failed."); + + } + + // connect a socket to the server + + draw_socket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (draw_socket == -1) + Error ("draw_socket failed"); + + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address.sin_port = GLSERV_PORT; + r = connect (draw_socket, (struct sockaddr *)&address, sizeof(address)); + if (r == -1) + { + closesocket (draw_socket); + draw_socket = 0; + } +} + +void GLS_Winding (winding_t *w, int code) +{ + byte buf[1024]; + int i, j; + + if (!draw_socket) + return; + + ((int *)buf)[0] = w->numpoints; + ((int *)buf)[1] = code; + for (i=0 ; inumpoints ; i++) + for (j=0 ; j<3 ; j++) + ((float *)buf)[2+i*3+j] = w->p[i][j]; + + send (draw_socket, buf, w->numpoints*12+8, 0); +} + +void GLS_EndScene (void) +{ + closesocket (draw_socket); + draw_socket = 0; +} diff --git a/tools/quake2/q2map/glfile.c b/tools/quake2/q2map/glfile.c new file mode 100644 index 00000000..6e98bd0f --- /dev/null +++ b/tools/quake2/q2map/glfile.c @@ -0,0 +1,148 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qbsp.h" + +int c_glfaces; + +int PortalVisibleSides (portal_t *p) +{ + int fcon, bcon; + + if (!p->onnode) + return 0; // outside + + fcon = p->nodes[0]->contents; + bcon = p->nodes[1]->contents; + + // same contents never create a face + if (fcon == bcon) + return 0; + + // FIXME: is this correct now? + if (!fcon) + return 1; + if (!bcon) + return 2; + return 0; +} + +void OutputWinding (winding_t *w, FILE *glview) +{ + static int level = 128; + vec_t light; + int i; + + fprintf (glview, "%i\n", w->numpoints); + level+=28; + light = (level&255)/255.0; + for (i=0 ; inumpoints ; i++) + { + fprintf (glview, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + light, + light, + light); + } + fprintf (glview, "\n"); +} + +/* +============= +OutputPortal +============= +*/ +void OutputPortal (portal_t *p, FILE *glview) +{ + winding_t *w; + int sides; + + sides = PortalVisibleSides (p); + if (!sides) + return; + + c_glfaces++; + + w = p->winding; + + if (sides == 2) // back side + w = ReverseWinding (w); + + OutputWinding (w, glview); + + if (sides == 2) + FreeWinding(w); +} + +/* +============= +WriteGLView_r +============= +*/ +void WriteGLView_r (node_t *node, FILE *glview) +{ + portal_t *p, *nextp; + + if (node->planenum != PLANENUM_LEAF) + { + WriteGLView_r (node->children[0], glview); + WriteGLView_r (node->children[1], glview); + return; + } + + // write all the portals + for (p=node->portals ; p ; p=nextp) + { + if (p->nodes[0] == node) + { + OutputPortal (p, glview); + nextp = p->next[0]; + } + else + nextp = p->next[1]; + } +} + +/* +============= +WriteGLView +============= +*/ +void WriteGLView (tree_t *tree, char *source) +{ + char name[1024]; + FILE *glview; + + c_glfaces = 0; + sprintf (name, "%s%s.gl",outbase, source); + Sys_Printf ("Writing %s\n", name); + + glview = fopen (name, "w"); + if (!glview) + Error ("Couldn't open %s", name); + WriteGLView_r (tree->headnode, glview); + fclose (glview); + + Sys_Printf ("%5i c_glfaces\n", c_glfaces); +} + diff --git a/tools/quake2/q2map/leakfile.c b/tools/quake2/q2map/leakfile.c new file mode 100644 index 00000000..8753eec5 --- /dev/null +++ b/tools/quake2/q2map/leakfile.c @@ -0,0 +1,180 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qbsp.h" + +/* +============================================================================== + +LEAF FILE GENERATION + +Save out name.line for qe3 to read +============================================================================== +*/ + + +/* +============= +LeakFile + +Finds the shortest possible chain of portals +that leads from the outside leaf to a specifically +occupied leaf +============= +*/ + +/* +void LeakFile (tree_t *tree) +{ + vec3_t mid; + FILE *linefile; + char filename[1024]; + node_t *node; + int count; + + if (!tree->outside_node.occupied) + return; + + Sys_Printf ("--- LeakFile ---\n"); + + // + // write the points to the file + // + sprintf (filename, "%s.lin", source); + linefile = fopen (filename, "w"); + if (!linefile) + Error ("Couldn't open %s\n", filename); + + count = 0; + node = &tree->outside_node; + while (node->occupied > 1) + { + int next; + portal_t *p, *nextportal; + node_t *nextnode; + int s; + + // find the best portal exit + next = node->occupied; + for (p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (p->nodes[s]->occupied + && p->nodes[s]->occupied < next) + { + nextportal = p; + nextnode = p->nodes[s]; + next = nextnode->occupied; + } + } + node = nextnode; + WindingCenter (nextportal->winding, mid); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + count++; + } + // add the occupant center + GetVectorForKey (node->occupant, "origin", mid); + + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + Sys_Printf ("%5i point linefile\n", count+1); + + fclose (linefile); +} +*/ + +/* +============= +LeakFile + +Finds the shortest possible chain of portals +that leads from the outside leaf to a specifically +occupied leaf + +TTimo: builds a polyline xml node +============= +*/ +xmlNodePtr LeakFile (tree_t *tree) +{ + vec3_t mid; + FILE *linefile; + char filename[1024]; + node_t *node; + int count; + xmlNodePtr xml_node, point; + + if (!tree->outside_node.occupied) + return NULL; + + Sys_FPrintf (SYS_VRB,"--- LeakFile ---\n"); + + // + // write the points to the file + // + sprintf (filename, "%s.lin", source); + linefile = fopen (filename, "w"); + if (!linefile) + Error ("Couldn't open %s\n", filename); + + xml_node = xmlNewNode (NULL, "polyline"); + + count = 0; + node = &tree->outside_node; + while (node->occupied > 1) + { + int next; + portal_t *p, *nextportal; + node_t *nextnode; + int s; + + // find the best portal exit + next = node->occupied; + for (p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (p->nodes[s]->occupied + && p->nodes[s]->occupied < next) + { + nextportal = p; + nextnode = p->nodes[s]; + next = nextnode->occupied; + } + } + node = nextnode; + WindingCenter (nextportal->winding, mid); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + point = xml_NodeForVec(mid); + xmlAddChild(xml_node, point); + count++; + } + // add the occupant center + GetVectorForKey (node->occupant, "origin", mid); + + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + point = xml_NodeForVec(mid); + xmlAddChild(xml_node, point); + Sys_FPrintf( SYS_VRB, "%9d point linefile\n", count+1); + + fclose (linefile); + + return xml_node; +} + + diff --git a/tools/quake2/q2map/lightmap.c b/tools/quake2/q2map/lightmap.c new file mode 100644 index 00000000..8a9580b7 --- /dev/null +++ b/tools/quake2/q2map/lightmap.c @@ -0,0 +1,1315 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "qrad.h" + +#define MAX_LSTYLES 256 + +typedef struct +{ + dface_t *faces[2]; + qboolean coplanar; +} edgeshare_t; + +edgeshare_t edgeshare[MAX_MAP_EDGES]; + +int facelinks[MAX_MAP_FACES]; +int planelinks[2][MAX_MAP_PLANES]; + +/* +============ +LinkPlaneFaces +============ +*/ +void LinkPlaneFaces (void) +{ + int i; + dface_t *f; + + f = dfaces; + for (i=0 ; iside][f->planenum]; + planelinks[f->side][f->planenum] = i; + } +} + +/* +============ +PairEdges +============ +*/ +void PairEdges (void) +{ + int i, j, k; + dface_t *f; + edgeshare_t *e; + + f = dfaces; + for (i=0 ; inumedges ; j++) + { + k = dsurfedges[f->firstedge + j]; + if (k < 0) + { + e = &edgeshare[-k]; + e->faces[1] = f; + } + else + { + e = &edgeshare[k]; + e->faces[0] = f; + } + + if (e->faces[0] && e->faces[1]) + { + // determine if coplanar + if (e->faces[0]->planenum == e->faces[1]->planenum) + e->coplanar = true; + } + } + } +} + +/* +================================================================= + + POINT TRIANGULATION + +================================================================= +*/ + +typedef struct triedge_s +{ + int p0, p1; + vec3_t normal; + vec_t dist; + struct triangle_s *tri; +} triedge_t; + +typedef struct triangle_s +{ + triedge_t *edges[3]; +} triangle_t; + +#define MAX_TRI_POINTS 1024 +#define MAX_TRI_EDGES (MAX_TRI_POINTS*6) +#define MAX_TRI_TRIS (MAX_TRI_POINTS*2) + +typedef struct +{ + int numpoints; + int numedges; + int numtris; + dplane_t *plane; + triedge_t *edgematrix[MAX_TRI_POINTS][MAX_TRI_POINTS]; + patch_t *points[MAX_TRI_POINTS]; + triedge_t edges[MAX_TRI_EDGES]; + triangle_t tris[MAX_TRI_TRIS]; +} triangulation_t; + +/* +=============== +AllocTriangulation +=============== +*/ +triangulation_t *AllocTriangulation (dplane_t *plane) +{ + triangulation_t *t; + + t = malloc(sizeof(triangulation_t)); + t->numpoints = 0; + t->numedges = 0; + t->numtris = 0; + + t->plane = plane; + +// memset (t->edgematrix, 0, sizeof(t->edgematrix)); + + return t; +} + +/* +=============== +FreeTriangulation +=============== +*/ +void FreeTriangulation (triangulation_t *tr) +{ + free (tr); +} + + +triedge_t *FindEdge (triangulation_t *trian, int p0, int p1) +{ + triedge_t *e, *be; + vec3_t v1; + vec3_t normal; + vec_t dist; + + if (trian->edgematrix[p0][p1]) + return trian->edgematrix[p0][p1]; + + if (trian->numedges > MAX_TRI_EDGES-2) + Error ("trian->numedges > MAX_TRI_EDGES-2"); + + VectorSubtract (trian->points[p1]->origin, trian->points[p0]->origin, v1); + VectorNormalize (v1, v1); + CrossProduct (v1, trian->plane->normal, normal); + dist = DotProduct (trian->points[p0]->origin, normal); + + e = &trian->edges[trian->numedges]; + e->p0 = p0; + e->p1 = p1; + e->tri = NULL; + VectorCopy (normal, e->normal); + e->dist = dist; + trian->numedges++; + trian->edgematrix[p0][p1] = e; + + be = &trian->edges[trian->numedges]; + be->p0 = p1; + be->p1 = p0; + be->tri = NULL; + VectorSubtract (vec3_origin, normal, be->normal); + be->dist = -dist; + trian->numedges++; + trian->edgematrix[p1][p0] = be; + + return e; +} + +triangle_t *AllocTriangle (triangulation_t *trian) +{ + triangle_t *t; + + if (trian->numtris >= MAX_TRI_TRIS) + Error ("trian->numtris >= MAX_TRI_TRIS"); + + t = &trian->tris[trian->numtris]; + trian->numtris++; + + return t; +} + +/* +============ +TriEdge_r +============ +*/ +void TriEdge_r (triangulation_t *trian, triedge_t *e) +{ + int i, bestp; + vec3_t v1, v2; + vec_t *p0, *p1, *p; + vec_t best, ang; + triangle_t *nt; + + if (e->tri) + return; // allready connected by someone + + // find the point with the best angle + p0 = trian->points[e->p0]->origin; + p1 = trian->points[e->p1]->origin; + best = 1.1; + for (i=0 ; i< trian->numpoints ; i++) + { + p = trian->points[i]->origin; + // a 0 dist will form a degenerate triangle + if (DotProduct(p, e->normal) - e->dist < 0) + continue; // behind edge + VectorSubtract (p0, p, v1); + VectorSubtract (p1, p, v2); + if (!VectorNormalize (v1,v1)) + continue; + if (!VectorNormalize (v2,v2)) + continue; + ang = DotProduct (v1, v2); + if (ang < best) + { + best = ang; + bestp = i; + } + } + if (best >= 1) + return; // edge doesn't match anything + + // make a new triangle + nt = AllocTriangle (trian); + nt->edges[0] = e; + nt->edges[1] = FindEdge (trian, e->p1, bestp); + nt->edges[2] = FindEdge (trian, bestp, e->p0); + for (i=0 ; i<3 ; i++) + nt->edges[i]->tri = nt; + TriEdge_r (trian, FindEdge (trian, bestp, e->p1)); + TriEdge_r (trian, FindEdge (trian, e->p0, bestp)); +} + +/* +============ +TriangulatePoints +============ +*/ +void TriangulatePoints (triangulation_t *trian) +{ + vec_t d, bestd; + vec3_t v1; + int bp1, bp2, i, j; + vec_t *p1, *p2; + triedge_t *e, *e2; + + if (trian->numpoints < 2) + return; + + // find the two closest points + bestd = 9999; + for (i=0 ; inumpoints ; i++) + { + p1 = trian->points[i]->origin; + for (j=i+1 ; jnumpoints ; j++) + { + p2 = trian->points[j]->origin; + VectorSubtract (p2, p1, v1); + d = VectorLength (v1); + if (d < bestd) + { + bestd = d; + bp1 = i; + bp2 = j; + } + } + } + + e = FindEdge (trian, bp1, bp2); + e2 = FindEdge (trian, bp2, bp1); + TriEdge_r (trian, e); + TriEdge_r (trian, e2); +} + +/* +=============== +AddPointToTriangulation +=============== +*/ +void AddPointToTriangulation (patch_t *patch, triangulation_t *trian) +{ + int pnum; + + pnum = trian->numpoints; + if (pnum == MAX_TRI_POINTS) + Error ("trian->numpoints == MAX_TRI_POINTS"); + trian->points[pnum] = patch; + trian->numpoints++; +} + +/* +=============== +LerpTriangle +=============== +*/ +void LerpTriangle (triangulation_t *trian, triangle_t *t, vec3_t point, vec3_t color) +{ + patch_t *p1, *p2, *p3; + vec3_t base, d1, d2; + float x, y, x1, y1, x2, y2; + + p1 = trian->points[t->edges[0]->p0]; + p2 = trian->points[t->edges[1]->p0]; + p3 = trian->points[t->edges[2]->p0]; + + VectorCopy (p1->totallight, base); + VectorSubtract (p2->totallight, base, d1); + VectorSubtract (p3->totallight, base, d2); + + x = DotProduct (point, t->edges[0]->normal) - t->edges[0]->dist; + y = DotProduct (point, t->edges[2]->normal) - t->edges[2]->dist; + + x1 = 0; + y1 = DotProduct (p2->origin, t->edges[2]->normal) - t->edges[2]->dist; + + x2 = DotProduct (p3->origin, t->edges[0]->normal) - t->edges[0]->dist; + y2 = 0; + + if (fabs(y1)edges[i]; + d = DotProduct (e->normal, point) - e->dist; + if (d < 0) + return false; // not inside + } + + return true; +} + +/* +=============== +SampleTriangulation +=============== +*/ +void SampleTriangulation (vec3_t point, triangulation_t *trian, vec3_t color) +{ + triangle_t *t; + triedge_t *e; + vec_t d, best; + patch_t *p0, *p1; + vec3_t v1, v2; + int i, j; + + if (trian->numpoints == 0) + { + VectorClear (color); + return; + } + if (trian->numpoints == 1) + { + VectorCopy (trian->points[0]->totallight, color); + return; + } + + // search for triangles + for (t = trian->tris, j=0 ; j < trian->numtris ; t++, j++) + { + if (!PointInTriangle (point, t)) + continue; + + // this is it + LerpTriangle (trian, t, point, color); + return; + } + + // search for exterior edge + for (e=trian->edges, j=0 ; j< trian->numedges ; e++, j++) + { + if (e->tri) + continue; // not an exterior edge + + d = DotProduct (point, e->normal) - e->dist; + if (d < 0) + continue; // not in front of edge + + p0 = trian->points[e->p0]; + p1 = trian->points[e->p1]; + + VectorSubtract (p1->origin, p0->origin, v1); + VectorNormalize (v1, v1); + VectorSubtract (point, p0->origin, v2); + d = DotProduct (v2, v1); + if (d < 0) + continue; + if (d > 1) + continue; + for (i=0 ; i<3 ; i++) + color[i] = p0->totallight[i] + d * (p1->totallight[i] - p0->totallight[i]); + return; + } + + // search for nearest point + best = 99999; + p1 = NULL; + for (j=0 ; jnumpoints ; j++) + { + p0 = trian->points[j]; + VectorSubtract (point, p0->origin, v1); + d = VectorLength (v1); + if (d < best) + { + best = d; + p1 = p0; + } + } + + if (!p1) + Error ("SampleTriangulation: no points"); + + VectorCopy (p1->totallight, color); +} + +/* +================================================================= + + LIGHTMAP SAMPLE GENERATION + +================================================================= +*/ + + +#define SINGLEMAP (64*64*4) + +typedef struct +{ + vec_t facedist; + vec3_t facenormal; + + int numsurfpt; + vec3_t surfpt[SINGLEMAP]; + + vec3_t modelorg; // for origined bmodels + + vec3_t texorg; + vec3_t worldtotex[2]; // s = (world - texorg) . worldtotex[0] + vec3_t textoworld[2]; // world = texorg + s * textoworld[0] + + vec_t exactmins[2], exactmaxs[2]; + + int texmins[2], texsize[2]; + int surfnum; + dface_t *face; +} lightinfo_t; + + +/* +================ +CalcFaceExtents + +Fills in s->texmins[] and s->texsize[] +also sets exactmins[] and exactmaxs[] +================ +*/ +void CalcFaceExtents (lightinfo_t *l) +{ + dface_t *s; + vec_t mins[2], maxs[2], val; + int i,j, e; + dvertex_t *v; + texinfo_t *tex; + vec3_t vt; + + s = l->face; + + mins[0] = mins[1] = 999999; + maxs[0] = maxs[1] = -99999; + + tex = &texinfo[s->texinfo]; + + for (i=0 ; inumedges ; i++) + { + e = dsurfedges[s->firstedge+i]; + if (e >= 0) + v = dvertexes + dedges[e].v[0]; + else + v = dvertexes + dedges[-e].v[1]; + +// VectorAdd (v->point, l->modelorg, vt); + VectorCopy (v->point, vt); + + for (j=0 ; j<2 ; j++) + { + val = DotProduct (vt, tex->vecs[j]) + tex->vecs[j][3]; + if (val < mins[j]) + mins[j] = val; + if (val > maxs[j]) + maxs[j] = val; + } + } + + for (i=0 ; i<2 ; i++) + { + l->exactmins[i] = mins[i]; + l->exactmaxs[i] = maxs[i]; + + mins[i] = floor(mins[i]/16); + maxs[i] = ceil(maxs[i]/16); + + l->texmins[i] = mins[i]; + l->texsize[i] = maxs[i] - mins[i]; + if (l->texsize[0] * l->texsize[1] > SINGLEMAP/4) // div 4 for extrasamples + Error ("Surface to large to map"); + } +} + +/* +================ +CalcFaceVectors + +Fills in texorg, worldtotex. and textoworld +================ +*/ +void CalcFaceVectors (lightinfo_t *l) +{ + texinfo_t *tex; + int i, j; + vec3_t texnormal; + vec_t distscale; + vec_t dist, len; + int w, h; + + tex = &texinfo[l->face->texinfo]; + +// convert from float to double + for (i=0 ; i<2 ; i++) + for (j=0 ; j<3 ; j++) + l->worldtotex[i][j] = tex->vecs[i][j]; + +// calculate a normal to the texture axis. points can be moved along this +// without changing their S/T + texnormal[0] = tex->vecs[1][1]*tex->vecs[0][2] + - tex->vecs[1][2]*tex->vecs[0][1]; + texnormal[1] = tex->vecs[1][2]*tex->vecs[0][0] + - tex->vecs[1][0]*tex->vecs[0][2]; + texnormal[2] = tex->vecs[1][0]*tex->vecs[0][1] + - tex->vecs[1][1]*tex->vecs[0][0]; + VectorNormalize (texnormal, texnormal); + +// flip it towards plane normal + distscale = DotProduct (texnormal, l->facenormal); + if (!distscale) + { + Sys_FPrintf( SYS_VRB, "WARNING: Texture axis perpendicular to face\n"); + distscale = 1; + } + if (distscale < 0) + { + distscale = -distscale; + VectorSubtract (vec3_origin, texnormal, texnormal); + } + +// distscale is the ratio of the distance along the texture normal to +// the distance along the plane normal + distscale = 1/distscale; + + for (i=0 ; i<2 ; i++) + { + len = VectorLength (l->worldtotex[i]); + dist = DotProduct (l->worldtotex[i], l->facenormal); + dist *= distscale; + VectorMA (l->worldtotex[i], -dist, texnormal, l->textoworld[i]); + VectorScale (l->textoworld[i], (1/len)*(1/len), l->textoworld[i]); + } + + +// calculate texorg on the texture plane + for (i=0 ; i<3 ; i++) + l->texorg[i] = -tex->vecs[0][3]* l->textoworld[0][i] - tex->vecs[1][3] * l->textoworld[1][i]; + +// project back to the face plane + dist = DotProduct (l->texorg, l->facenormal) - l->facedist - 1; + dist *= distscale; + VectorMA (l->texorg, -dist, texnormal, l->texorg); + + // compensate for org'd bmodels + VectorAdd (l->texorg, l->modelorg, l->texorg); + + // total sample count + h = l->texsize[1]+1; + w = l->texsize[0]+1; + l->numsurfpt = w * h; +} + +/* +================= +CalcPoints + +For each texture aligned grid point, back project onto the plane +to get the world xyz value of the sample point +================= +*/ +void CalcPoints (lightinfo_t *l, float sofs, float tofs) +{ + int i; + int s, t, j; + int w, h, step; + vec_t starts, startt, us, ut; + vec_t *surf; + vec_t mids, midt; + vec3_t facemid; + dleaf_t *leaf; + + surf = l->surfpt[0]; + mids = (l->exactmaxs[0] + l->exactmins[0])/2; + midt = (l->exactmaxs[1] + l->exactmins[1])/2; + + for (j=0 ; j<3 ; j++) + facemid[j] = l->texorg[j] + l->textoworld[0][j]*mids + l->textoworld[1][j]*midt; + + h = l->texsize[1]+1; + w = l->texsize[0]+1; + l->numsurfpt = w * h; + + starts = l->texmins[0]*16; + startt = l->texmins[1]*16; + step = 16; + + + for (t=0 ; ttexorg[j] + l->textoworld[0][j]*us + + l->textoworld[1][j]*ut; + + leaf = Rad_PointInLeaf (surf); + if (leaf->contents != CONTENTS_SOLID) + { + if (!TestLine_r (0, facemid, surf)) + break; // got it + } + + // nudge it + if (i & 1) + { + if (us > mids) + { + us -= 8; + if (us < mids) + us = mids; + } + else + { + us += 8; + if (us > mids) + us = mids; + } + } + else + { + if (ut > midt) + { + ut -= 8; + if (ut < midt) + ut = midt; + } + else + { + ut += 8; + if (ut > midt) + ut = midt; + } + } + } + } + } + +} + + +//============================================================== + + + +#define MAX_STYLES 32 +typedef struct +{ + int numsamples; + float *origins; + int numstyles; + int stylenums[MAX_STYLES]; + float *samples[MAX_STYLES]; +} facelight_t; + +directlight_t *directlights[MAX_MAP_LEAFS]; +facelight_t facelight[MAX_MAP_FACES]; +int numdlights; + +/* +================== +FindTargetEntity +================== +*/ +entity_t *FindTargetEntity (char *target) +{ + int i; + char *n; + + for (i=0 ; itotallight[0] < DIRECT_LIGHT + && p->totallight[1] < DIRECT_LIGHT + && p->totallight[2] < DIRECT_LIGHT) + continue; + + numdlights++; + dl = malloc(sizeof(directlight_t)); + memset (dl, 0, sizeof(*dl)); + + VectorCopy (p->origin, dl->origin); + + leaf = Rad_PointInLeaf (dl->origin); + cluster = leaf->cluster; + dl->next = directlights[cluster]; + directlights[cluster] = dl; + + dl->type = emit_surface; + VectorCopy (p->plane->normal, dl->normal); + + dl->intensity = ColorNormalize (p->totallight, dl->color); + dl->intensity *= p->area * direct_scale; + VectorClear (p->totallight); // all sent now + } + + // + // entities + // + for (i=0 ; iorigin); + dl->style = FloatForKey (e, "_style"); + if (!dl->style) + dl->style = FloatForKey (e, "style"); + if (dl->style < 0 || dl->style >= MAX_LSTYLES) + dl->style = 0; + + leaf = Rad_PointInLeaf (dl->origin); + cluster = leaf->cluster; + + dl->next = directlights[cluster]; + directlights[cluster] = dl; + + intensity = FloatForKey (e, "light"); + if (!intensity) + intensity = FloatForKey (e, "_light"); + if (!intensity) + intensity = 300; + _color = ValueForKey (e, "_color"); + if (_color && _color[0]) + { + sscanf (_color, "%f %f %f", &dl->color[0],&dl->color[1],&dl->color[2]); + ColorNormalize (dl->color, dl->color); + } + else + dl->color[0] = dl->color[1] = dl->color[2] = 1.0; + dl->intensity = intensity*entity_scale; + dl->type = emit_point; + + target = ValueForKey (e, "target"); + + if (!strcmp (name, "light_spot") || target[0]) + { + dl->type = emit_spotlight; + dl->stopdot = FloatForKey (e, "_cone"); + if (!dl->stopdot) + dl->stopdot = 10; + dl->stopdot = cos(dl->stopdot/180*3.14159); + if (target[0]) + { // point towards target + e2 = FindTargetEntity (target); + if (!e2) + Sys_Printf ("WARNING: light at (%i %i %i) has missing target\n", + (int)dl->origin[0], (int)dl->origin[1], (int)dl->origin[2]); + else + { + GetVectorForKey (e2, "origin", dest); + VectorSubtract (dest, dl->origin, dl->normal); + VectorNormalize (dl->normal, dl->normal); + } + } + else + { // point down angle + angle = FloatForKey (e, "angle"); + if (angle == ANGLE_UP) + { + dl->normal[0] = dl->normal[1] = 0; + dl->normal[2] = 1; + } + else if (angle == ANGLE_DOWN) + { + dl->normal[0] = dl->normal[1] = 0; + dl->normal[2] = -1; + } + else + { + dl->normal[2] = 0; + dl->normal[0] = cos (angle/180*3.14159); + dl->normal[1] = sin (angle/180*3.14159); + } + } + } + } + + Sys_FPrintf( SYS_VRB, "%i direct lights\n", numdlights); +} + +/* +============= +GatherSampleLight + +Lightscale is the normalizer for multisampling +============= +*/ +void GatherSampleLight (vec3_t pos, vec3_t normal, + float **styletable, int offset, int mapsize, float lightscale) +{ + int i; + directlight_t *l; + byte pvs[(MAX_MAP_LEAFS+7)/8]; + vec3_t delta; + float dot, dot2; + float dist; + float scale; + float *dest; + + // get the PVS for the pos to limit the number of checks + if (!PvsForOrigin (pos, pvs)) + { + return; + } + + for (i = 0 ; inumclusters ; i++) + { + if ( ! (pvs[ i>>3] & (1<<(i&7))) ) + continue; + + for (l=directlights[i] ; l ; l=l->next) + { + VectorSubtract (l->origin, pos, delta); + dist = VectorNormalize (delta, delta); + dot = DotProduct (delta, normal); + if (dot <= 0.001) + continue; // behind sample surface + + switch (l->type) + { + case emit_point: + // linear falloff + scale = (l->intensity - dist) * dot; + break; + + case emit_surface: + dot2 = -DotProduct (delta, l->normal); + if (dot2 <= 0.001) + goto skipadd; // behind light surface + scale = (l->intensity / (dist*dist) ) * dot * dot2; + break; + + case emit_spotlight: + // linear falloff + dot2 = -DotProduct (delta, l->normal); + if (dot2 <= l->stopdot) + goto skipadd; // outside light cone + scale = (l->intensity - dist) * dot; + break; + default: + Error ("Bad l->type"); + } + + if (TestLine_r (0, pos, l->origin)) + continue; // occluded + + if (scale <= 0) + continue; + + // if this style doesn't have a table yet, allocate one + if (!styletable[l->style]) + { + styletable[l->style] = malloc (mapsize); + memset (styletable[l->style], 0, mapsize); + } + + dest = styletable[l->style] + offset; + // add some light to it + VectorMA (dest, scale*lightscale, l->color, dest); + +skipadd: ; + } + } + +} + +/* +============= +AddSampleToPatch + +Take the sample's collected light and +add it back into the apropriate patch +for the radiosity pass. + +The sample is added to all patches that might include +any part of it. They are counted and averaged, so it +doesn't generate extra light. +============= +*/ +void AddSampleToPatch (vec3_t pos, vec3_t color, int facenum) +{ + patch_t *patch; + vec3_t mins, maxs; + int i; + + if (numbounce == 0) + return; + if (color[0] + color[1] + color[2] < 3) + return; + + for (patch = face_patches[facenum] ; patch ; patch=patch->next) + { + // see if the point is in this patch (roughly) + WindingBounds (patch->winding, mins, maxs); + for (i=0 ; i<3 ; i++) + { + if (mins[i] > pos[i] + 16) + goto nextpatch; + if (maxs[i] < pos[i] - 16) + goto nextpatch; + } + + // add the sample to the patch + patch->samples++; + VectorAdd (patch->samplelight, color, patch->samplelight); +nextpatch:; + } + +} + + +/* +============= +BuildFacelights +============= +*/ +float sampleofs[5][2] = +{ {0,0}, {-0.25, -0.25}, {0.25, -0.25}, {0.25, 0.25}, {-0.25, 0.25} }; + + +void BuildFacelights (int facenum) +{ + dface_t *f; + lightinfo_t l[5]; + float *styletable[MAX_LSTYLES]; + int i, j; + float *spot; + patch_t *patch; + int numsamples; + int tablesize; + facelight_t *fl; + + f = &dfaces[facenum]; + + if ( texinfo[f->texinfo].flags & (SURF_WARP|SURF_SKY) ) + return; // non-lit texture + + memset (styletable,0, sizeof(styletable)); + + if (extrasamples) + numsamples = 5; + else + numsamples = 1; + for (i=0 ; iplanenum].normal, l[i].facenormal); + l[i].facedist = dplanes[f->planenum].dist; + if (f->side) + { + VectorSubtract (vec3_origin, l[i].facenormal, l[i].facenormal); + l[i].facedist = -l[i].facedist; + } + + // get the origin offset for rotating bmodels + VectorCopy (face_offset[facenum], l[i].modelorg); + + CalcFaceVectors (&l[i]); + CalcFaceExtents (&l[i]); + CalcPoints (&l[i], sampleofs[i][0], sampleofs[i][1]); + } + + tablesize = l[0].numsurfpt * sizeof(vec3_t); + styletable[0] = malloc(tablesize); + memset (styletable[0], 0, tablesize); + + fl = &facelight[facenum]; + fl->numsamples = l[0].numsurfpt; + fl->origins = malloc (tablesize); + memcpy (fl->origins, l[0].surfpt, tablesize); + + for (i=0 ; inext) + { + if (patch->samples) + { + VectorScale (patch->samplelight, 1.0/patch->samples, patch->samplelight); + } + else + { +// printf ("patch with no samples\n"); + } + } + + for (i=0 ; inumstyles == MAX_STYLES) + break; + fl->samples[fl->numstyles] = styletable[i]; + fl->stylenums[fl->numstyles] = i; + fl->numstyles++; + } + + // the light from DIRECT_LIGHTS is sent out, but the + // texture itself should still be full bright + + if (face_patches[facenum]->baselight[0] >= DIRECT_LIGHT || + face_patches[facenum]->baselight[1] >= DIRECT_LIGHT || + face_patches[facenum]->baselight[2] >= DIRECT_LIGHT + ) + { + spot = fl->samples[0]; + for (i=0 ; ibaselight, spot); + } + } +} + + +/* +============= +FinalLightFace + +Add the indirect lighting on top of the direct +lighting and save into final map format +============= +*/ +void FinalLightFace (int facenum) +{ + dface_t *f; + int i, j, k, st; + vec3_t lb; + patch_t *patch; + triangulation_t *trian; + facelight_t *fl; + float minlight; + float max, newmax; + byte *dest; + int pfacenum; + vec3_t facemins, facemaxs; + + f = &dfaces[facenum]; + fl = &facelight[facenum]; + + if ( texinfo[f->texinfo].flags & (SURF_WARP|SURF_SKY) ) + return; // non-lit texture + + ThreadLock (); + f->lightofs = lightdatasize; + lightdatasize += fl->numstyles*(fl->numsamples*3); + +// add green sentinals between lightmaps +#if 0 +lightdatasize += 64*3; +for (i=0 ; i<64 ; i++) +dlightdata[lightdatasize-(i+1)*3 + 1] = 255; +#endif + + if (lightdatasize > MAX_MAP_LIGHTING) + Error ("MAX_MAP_LIGHTING"); + ThreadUnlock (); + + f->styles[0] = 0; + f->styles[1] = f->styles[2] = f->styles[3] = 0xff; + + // + // set up the triangulation + // + if (numbounce > 0) + { + ClearBounds (facemins, facemaxs); + for (i=0 ; inumedges ; i++) + { + int ednum; + + ednum = dsurfedges[f->firstedge+i]; + if (ednum >= 0) + AddPointToBounds (dvertexes[dedges[ednum].v[0]].point, + facemins, facemaxs); + else + AddPointToBounds (dvertexes[dedges[-ednum].v[1]].point, + facemins, facemaxs); + } + + trian = AllocTriangulation (&dplanes[f->planenum]); + + // for all faces on the plane, add the nearby patches + // to the triangulation + for (pfacenum = planelinks[f->side][f->planenum] + ; pfacenum ; pfacenum = facelinks[pfacenum]) + { + for (patch = face_patches[pfacenum] ; patch ; patch=patch->next) + { + for (i=0 ; i < 3 ; i++) + { + if (facemins[i] - patch->origin[i] > subdiv*2) + break; + if (patch->origin[i] - facemaxs[i] > subdiv*2) + break; + } + if (i != 3) + continue; // not needed for this face + AddPointToTriangulation (patch, trian); + } + } + for (i=0 ; inumpoints ; i++) + memset (trian->edgematrix[i], 0, trian->numpoints*sizeof(trian->edgematrix[0][0]) ); + TriangulatePoints (trian); + } + + // + // sample the triangulation + // + + // _minlight allows models that have faces that would not be + // illuminated to receive a mottled light pattern instead of + // black + minlight = FloatForKey (face_entity[facenum], "_minlight") * 128; + + dest = &dlightdata[f->lightofs]; + + if (fl->numstyles > MAXLIGHTMAPS) + { + fl->numstyles = MAXLIGHTMAPS; + Sys_Printf ("face with too many lightstyles: (%f %f %f)\n", + face_patches[facenum]->origin[0], + face_patches[facenum]->origin[1], + face_patches[facenum]->origin[2] + ); + } + + for (st=0 ; stnumstyles ; st++) + { + f->styles[st] = fl->stylenums[st]; + for (j=0 ; jnumsamples ; j++) + { + VectorCopy ( (fl->samples[st]+j*3), lb); + if (numbounce > 0 && st == 0) + { + vec3_t add; + + SampleTriangulation (fl->origins + j*3, trian, add); + VectorAdd (lb, add, lb); + } + // add an ambient term if desired + lb[0] += ambient; + lb[1] += ambient; + lb[2] += ambient; + + VectorScale (lb, lightscale, lb); + + // we need to clamp without allowing hue to change + for (k=0 ; k<3 ; k++) + if (lb[k] < 1) + lb[k] = 1; + max = lb[0]; + if (lb[1] > max) + max = lb[1]; + if (lb[2] > max) + max = lb[2]; + newmax = max; + if (newmax < 0) + newmax = 0; // roundoff problems + if (newmax < minlight) + { + newmax = minlight + (rand()%48); + } + if (newmax > maxlight) + newmax = maxlight; + + for (k=0 ; k<3 ; k++) + { + *dest++ = lb[k]*newmax/max; + } + } + } + + if (numbounce > 0) + FreeTriangulation (trian); +} diff --git a/tools/quake2/q2map/main.c b/tools/quake2/q2map/main.c new file mode 100644 index 00000000..5ed8daca --- /dev/null +++ b/tools/quake2/q2map/main.c @@ -0,0 +1,721 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +/* marker */ +#define MAIN_C + + + +/* dependencies */ +#include "q2map.h" + +#define qtrue true +#define qfalse false + +char *mapname; +char game[64]; +extern qboolean verbose; + +/// BSP +extern qboolean drawflag; +extern qboolean noprune; +extern qboolean glview; +extern qboolean nodetail; +extern qboolean fulldetail; +extern qboolean onlyents; +extern qboolean nomerge; +extern qboolean nowater; +extern qboolean nofill; +extern qboolean nocsg; +extern qboolean noweld; +extern qboolean noshare; +extern qboolean nosubdiv; +extern qboolean notjunc; +extern qboolean noopt; +extern qboolean leaktest; +extern qboolean verboseentities; +extern char outbase[32]; +extern int block_xl, block_xh, block_yl, block_yh; +extern vec_t microvolume; +extern float subdivide_size; + +// VIS +extern char inbase[32]; +extern qboolean fastvis; +extern qboolean nosort; +extern int testlevel; + +// RAD +extern qboolean dumppatches; +extern int numbounce; +extern qboolean extrasamples; +extern float subdiv; +extern float lightscale; +extern float direct_scale; +extern float entity_scale; +extern qboolean nopvs; +extern float ambient; +extern float maxlight; + + +void InitPaths( int *argc, char **argv ); + +/* +Random() +returns a pseudorandom number between 0 and 1 +*/ +/* +vec_t Random( void ) +{ + return (vec_t) rand() / RAND_MAX; +} +*/ + + +/* +ExitQ2Map() +cleanup routine +*/ +/* +static void ExitQ2Map( void ) +{ + BSPFilesCleanup(); + if( mapDrawSurfs != NULL ) + free( mapDrawSurfs ); +} +*/ + + +/* +BSPInfo() +emits statistics about the bsp file +*/ + +int BSPInfo() +{ + char source[ 1024 ], ext[ 64 ]; + int size; + FILE *f; + + Sys_Printf ("\n----- INFO ----\n\n"); + + /* dummy check */ + if( mapname == NULL ) + { + Sys_Printf( "No files to dump info for.\n"); + return -1; + } + + /* enable info mode */ + //infoMode = qtrue; + + + /* mangle filename and get size */ + strcpy( source, mapname ); + ExtractFileExtension( source, ext ); + if( !Q_stricmp( ext, "map" ) ) + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + f = fopen( source, "rb" ); + if( f ) + { + size = Q_filelength (f); + fclose( f ); + } + else + size = 0; + + /* load the bsp file and print lump sizes */ + Sys_Printf( "Map: %s\n\n", source ); + + Sys_Printf( "-----------------------------------------------------\n" ); + + LoadBSPFile( source ); + PrintBSPFileSizes(); + + Sys_Printf( "-----------------------------------------------------\n" ); + + /* print sizes */ + Sys_Printf( "Total: %d B = %.3f kB = %.3f MB\n", size, size / 1024.0, size / (1024.0 * 1024.0) ); + + Sys_Printf( "-----------------------------------------------------\n" ); + + /* return count */ + return 0; +} + + + +/* +ScaleBSPMain() +amaze and confuse your enemies with wierd scaled maps! +*/ +/* +int ScaleBSPMain( int argc, char **argv ) +{ + int i; + float f, scale; + vec3_t vec; + char str[ 1024 ]; + + + // arg checking + if( argc < 2 ) + { + Sys_Printf( "Usage: q3map -scale [-v] \n" ); + return 0; + } + + // get scale + scale = atof( argv[ argc - 2 ] ); + if( scale == 0.0f ) + { + Sys_Printf( "Usage: q3map -scale [-v] \n" ); + Sys_Printf( "Non-zero scale value required.\n" ); + return 0; + } + + // do some path mangling + strcpy( source, ExpandArg( argv[ argc - 1 ] ) ); + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + + // load the bsp + Sys_Printf( "Loading %s\n", source ); + LoadBSPFile( source ); + ParseEntities(); + + // note it + Sys_Printf( "--- ScaleBSP ---\n" ); + Sys_FPrintf( SYS_VRB, "%9d entities\n", numEntities ); + + // scale entity keys + for( i = 0; i < numBSPEntities && i < numEntities; i++ ) + { + // scale origin + GetVectorForKey( &entities[ i ], "origin", vec ); + if( (vec[ 0 ] + vec[ 1 ] + vec[ 2 ]) ) + { + VectorScale( vec, scale, vec ); + sprintf( str, "%f %f %f", vec[ 0 ], vec[ 1 ], vec[ 2 ] ); + SetKeyValue( &entities[ i ], "origin", str ); + } + + // scale door lip + f = FloatForKey( &entities[ i ], "lip" ); + if( f ) + { + f *= scale; + sprintf( str, "%f", f ); + SetKeyValue( &entities[ i ], "lip", str ); + } + } + + // scale models + for( i = 0; i < numBSPModels; i++ ) + { + VectorScale( bspModels[ i ].mins, scale, bspModels[ i ].mins ); + VectorScale( bspModels[ i ].maxs, scale, bspModels[ i ].maxs ); + } + + // scale nodes + for( i = 0; i < numBSPNodes; i++ ) + { + VectorScale( bspNodes[ i ].mins, scale, bspNodes[ i ].mins ); + VectorScale( bspNodes[ i ].maxs, scale, bspNodes[ i ].maxs ); + } + + // scale leafs + for( i = 0; i < numBSPLeafs; i++ ) + { + VectorScale( bspLeafs[ i ].mins, scale, bspLeafs[ i ].mins ); + VectorScale( bspLeafs[ i ].maxs, scale, bspLeafs[ i ].maxs ); + } + + // scale drawverts + for( i = 0; i < numBSPDrawVerts; i++ ) + VectorScale( bspDrawVerts[ i ].xyz, scale, bspDrawVerts[ i ].xyz ); + + // scale planes + for( i = 0; i < numBSPPlanes; i++ ) + bspPlanes[ i ].dist *= scale; + + // scale gridsize + GetVectorForKey( &entities[ 0 ], "gridsize", vec ); + if( (vec[ 0 ] + vec[ 1 ] + vec[ 2 ]) == 0.0f ) + VectorCopy( gridSize, vec ); + VectorScale( vec, scale, vec ); + sprintf( str, "%f %f %f", vec[ 0 ], vec[ 1 ], vec[ 2 ] ); + SetKeyValue( &entities[ 0 ], "gridsize", str ); + + // write the bsp + UnparseEntities(); + StripExtension( source ); + DefaultExtension( source, "_s.bsp" ); + Sys_Printf( "Writing %s\n", source ); + WriteBSPFile( source ); + + // return to sender + return 0; +} +*/ + + +/* +ConvertBSPMain() +main argument processing function for bsp conversion +*/ +/* +int ConvertBSPMain( int argc, char **argv ) +{ + int i; + int (*convertFunc)( char * ); + + + // set default + convertFunc = ConvertBSPToASE; + + // arg checking + if( argc < 1 ) + { + Sys_Printf( "Usage: q3map -scale [-v] \n" ); + return 0; + } + + // process arguments + for( i = 1; i < (argc - 1); i++ ) + { + // -format map|ase|... + if( !strcmp( argv[ i ], "-format" ) ) + { + i++; + if( !Q_stricmp( argv[ i ], "ase" ) ) + convertFunc = ConvertBSPToASE; + else if( !Q_stricmp( argv[ i ], "map" ) ) + convertFunc = ConvertBSPToMap; + else + Sys_Printf( "Unknown conversion format \"%s\". Defaulting to ASE.\n", argv[ i ] ); + } + } + + // clean up map name + strcpy( source, ExpandArg( argv[ i ] ) ); + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + + LoadShaderInfo(); + + Sys_Printf( "Loading %s\n", source ); + + // ydnar: load surface file + //% LoadSurfaceExtraFile( source ); + + LoadBSPFile( source ); + + // parse bsp entities + ParseEntities(); + + // convert + return convertFunc( source ); +} +*/ + +int Check_BSP_Options( int argc, char **argv ) +{ + int i; + + for (i=1 ; i 59 ) + Sys_Printf("%d Minutes ", total_time/60 ); + Sys_Printf( "%d Seconds\n", total_time%60 ); + + /* shut down connection */ + Broadcast_Shutdown(); + + /* return any error code */ + return r; +} diff --git a/tools/quake2/q2map/map.c b/tools/quake2/q2map/map.c new file mode 100644 index 00000000..818f2a1d --- /dev/null +++ b/tools/quake2/q2map/map.c @@ -0,0 +1,1017 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// map.c + +#include "qbsp.h" + +extern qboolean onlyents; + +int nummapbrushes; +mapbrush_t mapbrushes[MAX_MAP_BRUSHES]; + +int nummapbrushsides; +side_t brushsides[MAX_MAP_SIDES]; +brush_texture_t side_brushtextures[MAX_MAP_SIDES]; + +int nummapplanes; +plane_t mapplanes[MAX_MAP_PLANES]; + +#define PLANE_HASHES 1024 +plane_t *planehash[PLANE_HASHES]; + +vec3_t map_mins, map_maxs; + +// undefine to make plane finding use linear sort +#define USE_HASHING + +void TestExpandBrushes (void); + +int c_boxbevels; +int c_edgebevels; + +int c_areaportals; + +int c_clipbrushes; + +/* +============================================================================= + +PLANE FINDING + +============================================================================= +*/ + + +/* +================= +PlaneTypeForNormal +================= +*/ +int PlaneTypeForNormal (vec3_t normal) +{ + vec_t ax, ay, az; + +// NOTE: should these have an epsilon around 1.0? + if (normal[0] == 1.0 || normal[0] == -1.0) + return PLANE_X; + if (normal[1] == 1.0 || normal[1] == -1.0) + return PLANE_Y; + if (normal[2] == 1.0 || normal[2] == -1.0) + return PLANE_Z; + + ax = fabs(normal[0]); + ay = fabs(normal[1]); + az = fabs(normal[2]); + + if (ax >= ay && ax >= az) + return PLANE_ANYX; + if (ay >= ax && ay >= az) + return PLANE_ANYY; + return PLANE_ANYZ; +} + +/* +================ +PlaneEqual +================ +*/ +#define NORMAL_EPSILON 0.00001 +#define DIST_EPSILON 0.01 +qboolean PlaneEqual (plane_t *p, vec3_t normal, vec_t dist) +{ +#if 1 + if ( + fabs(p->normal[0] - normal[0]) < NORMAL_EPSILON + && fabs(p->normal[1] - normal[1]) < NORMAL_EPSILON + && fabs(p->normal[2] - normal[2]) < NORMAL_EPSILON + && fabs(p->dist - dist) < DIST_EPSILON ) + return true; +#else + if (p->normal[0] == normal[0] + && p->normal[1] == normal[1] + && p->normal[2] == normal[2] + && p->dist == dist) + return true; +#endif + return false; +} + +/* +================ +AddPlaneToHash +================ +*/ +void AddPlaneToHash (plane_t *p) +{ + int hash; + + hash = (int)fabs(p->dist) / 8; + hash &= (PLANE_HASHES-1); + + p->hash_chain = planehash[hash]; + planehash[hash] = p; +} + +/* +================ +CreateNewFloatPlane +================ +*/ +int CreateNewFloatPlane (vec3_t normal, vec_t dist) +{ + plane_t *p, temp; + + if (VectorLength(normal) < 0.5) + Error ("FloatPlane: bad normal"); + // create a new plane + if (nummapplanes+2 > MAX_MAP_PLANES) + Error ("MAX_MAP_PLANES"); + + p = &mapplanes[nummapplanes]; + VectorCopy (normal, p->normal); + p->dist = dist; + p->type = (p+1)->type = PlaneTypeForNormal (p->normal); + + VectorSubtract (vec3_origin, normal, (p+1)->normal); + (p+1)->dist = -dist; + + nummapplanes += 2; + + // allways put axial planes facing positive first + if (p->type < 3) + { + if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0) + { + // flip order + temp = *p; + *p = *(p+1); + *(p+1) = temp; + + AddPlaneToHash (p); + AddPlaneToHash (p+1); + return nummapplanes - 1; + } + } + + AddPlaneToHash (p); + AddPlaneToHash (p+1); + return nummapplanes - 2; +} + +/* +============== +SnapVector +============== +*/ +void SnapVector (vec3_t normal) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if ( fabs(normal[i] - 1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = 1; + break; + } + if ( fabs(normal[i] - -1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = -1; + break; + } + } +} + +/* +============== +SnapPlane +============== +*/ +void SnapPlane (vec3_t normal, vec_t *dist) +{ + SnapVector (normal); + + if (fabs(*dist-Q_rint(*dist)) < DIST_EPSILON) + *dist = Q_rint(*dist); +} + +/* +============= +FindFloatPlane + +============= +*/ +#ifndef USE_HASHING +int FindFloatPlane (vec3_t normal, vec_t dist) +{ + int i; + plane_t *p; + + SnapPlane (normal, &dist); + for (i=0, p=mapplanes ; ihash_chain) + { + if (PlaneEqual (p, normal, dist)) + return p-mapplanes; + } + } + + return CreateNewFloatPlane (normal, dist); +} +#endif + +/* +================ +PlaneFromPoints +================ +*/ +int PlaneFromPoints (int *p0, int *p1, int *p2) +{ + vec3_t t1, t2, normal; + vec_t dist; + + VectorSubtract (p0, p1, t1); + VectorSubtract (p2, p1, t2); + CrossProduct (t1, t2, normal); + VectorNormalize (normal, normal); + + dist = DotProduct (p0, normal); + + return FindFloatPlane (normal, dist); +} + + +//==================================================================== + + +/* +=========== +BrushContents +=========== +*/ +int BrushContents (mapbrush_t *b) +{ + int contents; + side_t *s; + int i; + int trans; + + s = &b->original_sides[0]; + contents = s->contents; + trans = texinfo[s->texinfo].flags; + for (i=1 ; inumsides ; i++, s++) + { + s = &b->original_sides[i]; + trans |= texinfo[s->texinfo].flags; + if (s->contents != contents) + { + Sys_Printf ("Entity %i, Brush %i: mixed face contents\n" + , b->entitynum, b->brushnum); + break; + } + } + + // if any side is translucent, mark the contents + // and change solid to window + if ( trans & (SURF_TRANS33|SURF_TRANS66) ) + { + contents |= CONTENTS_TRANSLUCENT; + if (contents & CONTENTS_SOLID) + { + contents &= ~CONTENTS_SOLID; + contents |= CONTENTS_WINDOW; + } + } + + return contents; +} + + +//============================================================================ + +/* +================= +AddBrushBevels + +Adds any additional planes necessary to allow the brush to be expanded +against axial bounding boxes +================= +*/ +void AddBrushBevels (mapbrush_t *b) +{ + int axis, dir; + int i, j, k, l, order; + side_t sidetemp; + brush_texture_t tdtemp; + side_t *s, *s2; + vec3_t normal; + float dist; + winding_t *w, *w2; + vec3_t vec, vec2; + float d; + + // + // add the axial planes + // + order = 0; + for (axis=0 ; axis <3 ; axis++) + { + for (dir=-1 ; dir <= 1 ; dir+=2, order++) + { + // see if the plane is allready present + for (i=0, s=b->original_sides ; inumsides ; i++,s++) + { + if (mapplanes[s->planenum].normal[axis] == dir) + break; + } + + if (i == b->numsides) + { // add a new side + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + nummapbrushsides++; + b->numsides++; + VectorClear (normal); + normal[axis] = dir; + if (dir == 1) + dist = b->maxs[axis]; + else + dist = -b->mins[axis]; + s->planenum = FindFloatPlane (normal, dist); + s->texinfo = b->original_sides[0].texinfo; + s->contents = b->original_sides[0].contents; + s->bevel = true; + c_boxbevels++; + } + + // if the plane is not in it canonical order, swap it + if (i != order) + { + sidetemp = b->original_sides[order]; + b->original_sides[order] = b->original_sides[i]; + b->original_sides[i] = sidetemp; + + j = b->original_sides - brushsides; + tdtemp = side_brushtextures[j+order]; + side_brushtextures[j+order] = side_brushtextures[j+i]; + side_brushtextures[j+i] = tdtemp; + } + } + } + + // + // add the edge bevels + // + if (b->numsides == 6) + return; // pure axial + + // test the non-axial plane edges + for (i=6 ; inumsides ; i++) + { + s = b->original_sides + i; + w = s->winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + k = (j+1)%w->numpoints; + VectorSubtract (w->p[j], w->p[k], vec); + if (VectorNormalize (vec, vec) < 0.5) + continue; + SnapVector (vec); + for (k=0 ; k<3 ; k++) + if ( vec[k] == -1 || vec[k] == 1) + break; // axial + if (k != 3) + continue; // only test non-axial edges + + // try the six possible slanted axials from this edge + for (axis=0 ; axis <3 ; axis++) + { + for (dir=-1 ; dir <= 1 ; dir+=2) + { + // construct a plane + VectorClear (vec2); + vec2[axis] = dir; + CrossProduct (vec, vec2, normal); + if (VectorNormalize (normal, normal) < 0.5) + continue; + dist = DotProduct (w->p[j], normal); + + // if all the points on all the sides are + // behind this plane, it is a proper edge bevel + for (k=0 ; knumsides ; k++) + { + // if this plane has allready been used, skip it + if (PlaneEqual (&mapplanes[b->original_sides[k].planenum] + , normal, dist) ) + break; + + w2 = b->original_sides[k].winding; + if (!w2) + continue; + for (l=0 ; lnumpoints ; l++) + { + d = DotProduct (w2->p[l], normal) - dist; + if (d > 0.1) + break; // point in front + } + if (l != w2->numpoints) + break; + } + + if (k != b->numsides) + continue; // wasn't part of the outer hull + // add this plane + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + nummapbrushsides++; + s2 = &b->original_sides[b->numsides]; + s2->planenum = FindFloatPlane (normal, dist); + s2->texinfo = b->original_sides[0].texinfo; + s2->contents = b->original_sides[0].contents; + s2->bevel = true; + c_edgebevels++; + b->numsides++; + } + } + } + } +} + + +/* +================ +MakeBrushWindings + +makes basewindigs for sides and mins / maxs for the brush +================ +*/ +qboolean MakeBrushWindings (mapbrush_t *ob) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + ClearBounds (ob->mins, ob->maxs); + + for (i=0 ; inumsides ; i++) + { + plane = &mapplanes[ob->original_sides[i].planenum]; + w = BaseWindingForPlane (plane->normal, plane->dist); + for (j=0 ; jnumsides && w; j++) + { + if (i == j) + continue; + if (ob->original_sides[j].bevel) + continue; + plane = &mapplanes[ob->original_sides[j].planenum^1]; + ChopWindingInPlace (&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); + } + + side = &ob->original_sides[i]; + side->winding = w; + if (w) + { + side->visible = true; + for (j=0 ; jnumpoints ; j++) + AddPointToBounds (w->p[j], ob->mins, ob->maxs); + } + } + + for (i=0 ; i<3 ; i++) + { + if (ob->mins[0] < -4096 || ob->maxs[0] > 4096) + Sys_Printf ("entity %i, brush %i: bounds out of range\n", ob->entitynum, ob->brushnum); + if (ob->mins[0] > 4096 || ob->maxs[0] < -4096) + Sys_Printf ("entity %i, brush %i: no visible sides on brush\n", ob->entitynum, ob->brushnum); + } + + return true; +} + + +/* +================= +ParseBrush +================= +*/ +void ParseBrush (entity_t *mapent) +{ + mapbrush_t *b; + int i,j, k; + int mt; + side_t *side, *s2; + int planenum; + brush_texture_t td; + int planepts[3][3]; + + if (nummapbrushes == MAX_MAP_BRUSHES) + Error ("nummapbrushes == MAX_MAP_BRUSHES"); + + b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = num_entities-1; + b->brushnum = nummapbrushes - mapent->firstbrush; + + do + { + if (!GetToken (true)) + break; + if (!strcmp (token, "}") ) + break; + + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + side = &brushsides[nummapbrushsides]; + + // read the three point plane definition + for (i=0 ; i<3 ; i++) + { + if (i != 0) + GetToken (true); + if (strcmp (token, "(") ) + Error ("parsing brush"); + + for (j=0 ; j<3 ; j++) + { + GetToken (false); + planepts[i][j] = atoi(token); + } + + GetToken (false); + if (strcmp (token, ")") ) + Error ("parsing brush"); + + } + + + // + // read the texturedef + // + GetToken (false); + strcpy (td.name, token); + + GetToken (false); + td.shift[0] = atoi(token); + GetToken (false); + td.shift[1] = atoi(token); + GetToken (false); + td.rotate = atoi(token); + GetToken (false); + td.scale[0] = atof(token); + GetToken (false); + td.scale[1] = atof(token); + + // find default flags and values + mt = FindMiptex (td.name); + td.flags = textureref[mt].flags; + td.value = textureref[mt].value; + side->contents = textureref[mt].contents; + side->surf = td.flags = textureref[mt].flags; + + if (TokenAvailable()) + { + GetToken (false); + side->contents = atoi(token); + GetToken (false); + side->surf = td.flags = atoi(token); + GetToken (false); + td.value = atoi(token); + } + + // translucent objects are automatically classified as detail + if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) + side->contents |= CONTENTS_DETAIL; + if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + side->contents |= CONTENTS_DETAIL; + if (fulldetail) + side->contents &= ~CONTENTS_DETAIL; + if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) + | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) + side->contents |= CONTENTS_SOLID; + + // hints and skips are never detail, and have no content + if (side->surf & (SURF_HINT|SURF_SKIP) ) + { + side->contents = 0; + side->surf &= ~CONTENTS_DETAIL; + } + + + // + // find the plane number + // + planenum = PlaneFromPoints (planepts[0], planepts[1], planepts[2]); + if (planenum == -1) + { + Sys_Printf ("Entity %i, Brush %i: plane with no normal\n" + , b->entitynum, b->brushnum); + continue; + } + + // + // see if the plane has been used already + // + for (k=0 ; knumsides ; k++) + { + s2 = b->original_sides + k; + if (s2->planenum == planenum) + { + Sys_Printf ("Entity %i, Brush %i: duplicate plane\n" + , b->entitynum, b->brushnum); + break; + } + if ( s2->planenum == (planenum^1) ) + { + Sys_Printf ("Entity %i, Brush %i: mirrored plane\n" + , b->entitynum, b->brushnum); + break; + } + } + if (k != b->numsides) + continue; // duplicated + + // + // keep this side + // + + side = b->original_sides + b->numsides; + side->planenum = planenum; + side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], + &td, vec3_origin); + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + side_brushtextures[nummapbrushsides] = td; + + nummapbrushsides++; + b->numsides++; + } while (1); + + // get the content for the entire brush + b->contents = BrushContents (b); + + // allow detail brushes to be removed + if (nodetail && (b->contents & CONTENTS_DETAIL) ) + { + b->numsides = 0; + return; + } + + // allow water brushes to be removed + if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) + { + b->numsides = 0; + return; + } + + // create windings for sides and bounds for brush + MakeBrushWindings (b); + + // brushes that will not be visible at all will never be + // used as bsp splitters + if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + { + c_clipbrushes++; + for (i=0 ; inumsides ; i++) + b->original_sides[i].texinfo = TEXINFO_NODE; + } + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + if (b->contents & CONTENTS_ORIGIN) + { + char string[32]; + vec3_t origin; + + if (num_entities == 1) + { + Error ("Entity %i, Brush %i: origin brushes not allowed in world" + , b->entitynum, b->brushnum); + return; + } + + VectorAdd (b->mins, b->maxs, origin); + VectorScale (origin, 0.5, origin); + + sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); + SetKeyValue (&entities[b->entitynum], "origin", string); + + VectorCopy (origin, entities[b->entitynum].origin); + + // don't keep this brush + b->numsides = 0; + + return; + } + + AddBrushBevels (b); + + nummapbrushes++; + mapent->numbrushes++; +} + +/* +================ +MoveBrushesToWorld + +Takes all of the brushes from the current entity and +adds them to the world's brush list. + +Used by func_group and func_areaportal +================ +*/ +void MoveBrushesToWorld (entity_t *mapent) +{ + int newbrushes; + int worldbrushes; + mapbrush_t *temp; + int i; + + // this is pretty gross, because the brushes are expected to be + // in linear order for each entity + + newbrushes = mapent->numbrushes; + worldbrushes = entities[0].numbrushes; + + temp = malloc(newbrushes*sizeof(mapbrush_t)); + memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); + +#if 0 // let them keep their original brush numbers + for (i=0 ; inumbrushes = 0; +} + +/* +================ +ParseMapEntity +================ +*/ +qboolean ParseMapEntity (void) +{ + entity_t *mapent; + epair_t *e; + side_t *s; + int i, j; + int startbrush, startsides; + vec_t newdist; + mapbrush_t *b; + + if (!GetToken (true)) + return false; + + if (strcmp (token, "{") ) + Error ("ParseEntity: { not found"); + + if (num_entities == MAX_MAP_ENTITIES) + Error ("num_entities == MAX_MAP_ENTITIES"); + + startbrush = nummapbrushes; + startsides = nummapbrushsides; + + mapent = &entities[num_entities]; + num_entities++; + memset (mapent, 0, sizeof(*mapent)); + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; +// mapent->portalareas[0] = -1; +// mapent->portalareas[1] = -1; + + do + { + if (!GetToken (true)) + Error ("ParseEntity: EOF without closing brace"); + if (!strcmp (token, "}") ) + break; + if (!strcmp (token, "{") ) + ParseBrush (mapent); + else + { + e = ParseEpair (); + e->next = mapent->epairs; + mapent->epairs = e; + } + } while (1); + + GetVectorForKey (mapent, "origin", mapent->origin); + + // + // if there was an origin brush, offset all of the planes and texinfo + // + if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) + { + for (i=0 ; inumbrushes ; i++) + { + b = &mapbrushes[mapent->firstbrush + i]; + for (j=0 ; jnumsides ; j++) + { + s = &b->original_sides[j]; + newdist = mapplanes[s->planenum].dist - + DotProduct (mapplanes[s->planenum].normal, mapent->origin); + s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist); + s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], + &side_brushtextures[s-brushsides], mapent->origin); + } + MakeBrushWindings (b); + } + } + + // group entities are just for editor convenience + // toss all brushes into the world entity + if (!strcmp ("func_group", ValueForKey (mapent, "classname"))) + { + MoveBrushesToWorld (mapent); + mapent->numbrushes = 0; + return true; + } + + // areaportal entities move their brushes, but don't eliminate + // the entity + if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) + { + char str[128]; + + if (mapent->numbrushes != 1) + Error ("Entity %i: func_areaportal can only be a single brush", num_entities-1); + + b = &mapbrushes[nummapbrushes-1]; + b->contents = CONTENTS_AREAPORTAL; + c_areaportals++; + mapent->areaportalnum = c_areaportals; + // set the portal number as "style" + sprintf (str, "%i", c_areaportals); + SetKeyValue (mapent, "style", str); + MoveBrushesToWorld (mapent); + return true; + } + + return true; +} + +//=================================================================== + +/* +================ +LoadMapFile +================ +*/ +void LoadMapFile (char *filename) +{ + int i; + + Sys_FPrintf( SYS_VRB, "--- LoadMapFile ---\n"); + + LoadScriptFile (filename); + + nummapbrushsides = 0; + num_entities = 0; + + while (ParseMapEntity ()) + { + } + + ClearBounds (map_mins, map_maxs); + for (i=0 ; i 4096) + continue; // no valid points + AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); + AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); + } + + Sys_FPrintf( SYS_VRB, "%5i brushes\n", nummapbrushes); + Sys_FPrintf( SYS_VRB, "%5i clipbrushes\n", c_clipbrushes); + Sys_FPrintf( SYS_VRB, "%5i total sides\n", nummapbrushsides); + Sys_FPrintf( SYS_VRB, "%5i boxbevels\n", c_boxbevels); + Sys_FPrintf( SYS_VRB, "%5i edgebevels\n", c_edgebevels); + Sys_FPrintf( SYS_VRB, "%5i entities\n", num_entities); + Sys_FPrintf( SYS_VRB, "%5i planes\n", nummapplanes); + Sys_FPrintf( SYS_VRB, "%5i areaportals\n", c_areaportals); + Sys_FPrintf( SYS_VRB, "size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0],map_mins[1],map_mins[2], + map_maxs[0],map_maxs[1],map_maxs[2]); + +// TestExpandBrushes (); +} + + +//==================================================================== + + +/* +================ +TestExpandBrushes + +Expands all the brush planes and saves a new map out +================ +*/ +void TestExpandBrushes (void) +{ + FILE *f; + side_t *s; + int i, j, bn; + winding_t *w; + char *name = "expanded.map"; + mapbrush_t *brush; + vec_t dist; + + Sys_Printf ("writing %s\n", name); + f = fopen (name, "wb"); + if (!f) + Error ("Can't write %s\b", name); + + fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); + + for (bn=0 ; bnnumsides ; i++) + { + s = brush->original_sides + i; + dist = mapplanes[s->planenum].dist; + for (j=0 ; j<3 ; j++) + dist += fabs( 16 * mapplanes[s->planenum].normal[j] ); + + w = BaseWindingForPlane (mapplanes[s->planenum].normal, dist); + + fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + + fprintf (f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture); + FreeWinding (w); + } + fprintf (f, "}\n"); + } + fprintf (f, "}\n"); + + fclose (f); + + Error ("can't proceed after expanding brushes"); +} diff --git a/tools/quake2/q2map/nodraw.c b/tools/quake2/q2map/nodraw.c new file mode 100644 index 00000000..888b2570 --- /dev/null +++ b/tools/quake2/q2map/nodraw.c @@ -0,0 +1,46 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qbsp.h" + +vec3_t draw_mins, draw_maxs; +qboolean drawflag; + +void Draw_ClearWindow (void) +{ +} + +//============================================================ + +#define GLSERV_PORT 25001 + + +void GLS_BeginScene (void) +{ +} + +void GLS_Winding (winding_t *w, int code) +{ +} + +void GLS_EndScene (void) +{ +} diff --git a/tools/quake2/q2map/patches.c b/tools/quake2/q2map/patches.c new file mode 100644 index 00000000..2ff82315 --- /dev/null +++ b/tools/quake2/q2map/patches.c @@ -0,0 +1,603 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qrad.h" + +vec3_t texture_reflectivity[MAX_MAP_TEXINFO]; + +/* +=================================================================== + + TEXTURE LIGHT VALUES + +=================================================================== +*/ + +/* +====================== +CalcTextureReflectivity_Quake2 +====================== +*/ +void CalcTextureReflectivity_Quake2 (void) +{ + int i; + int j, k, texels; + int color[3]; + int texel; + byte *palette; + char path[1024]; + float r, scale; + miptex_t *mt; + + sprintf (path, "%spics/colormap.pcx", gamedir); + + // get the game palette + Load256Image (path, NULL, &palette, NULL, NULL); + + // allways set index 0 even if no textures + texture_reflectivity[0][0] = 0.5; + texture_reflectivity[0][1] = 0.5; + texture_reflectivity[0][2] = 0.5; + + for (i=0 ; iwidth)*LittleLong(mt->height); + color[0] = color[1] = color[2] = 0; + + for (j=0 ; joffsets[0]) + j]; + for (k=0 ; k<3 ; k++) + color[k] += palette[texel*3+k]; + } + + for (j=0 ; j<3 ; j++) + { + r = color[j]/texels/255.0; + texture_reflectivity[i][j] = r; + } + // scale the reflectivity up, because the textures are + // so dim + scale = ColorNormalize (texture_reflectivity[i], + texture_reflectivity[i]); + if (scale < 0.5) + { + scale *= 2; + VectorScale (texture_reflectivity[i], scale, texture_reflectivity[i]); + } +#if 0 +texture_reflectivity[i][0] = 0.5; +texture_reflectivity[i][1] = 0.5; +texture_reflectivity[i][2] = 0.5; +#endif + } +} + +/* +====================== +CalcTextureReflectivity_Heretic2 +====================== +*/ +void CalcTextureReflectivity_Heretic2 (void) +{ + int i; + int j, texels; + int color[3]; + int texel; + char path[1024]; + float r; + miptex_m8_t *mt; + miptex_m32_t *mt32; + byte *pos; + + + // allways set index 0 even if no textures + texture_reflectivity[0][0] = 0.5; + texture_reflectivity[0][1] = 0.5; + texture_reflectivity[0][2] = 0.5; + + for (i=0 ; iwidth[0])*LittleLong(mt->height[0]); + color[0] = color[1] = color[2] = 0; + + for (j=0 ; joffsets[0]) + j]; + color[0] += mt->palette[texel].r; + color[1] += mt->palette[texel].g; + color[2] += mt->palette[texel].b; + } + + free(mt); + } + else + { + texels = LittleLong(mt32->width[0])*LittleLong(mt32->height[0]); + color[0] = color[1] = color[2] = 0; + + for (j=0 ; joffsets[0] + (j<<2); + color[0] += *pos++; // r + color[1] += *pos++; // g + color[2] += *pos++; // b + } + + free(mt32); + } + + + for (j=0 ; j<3 ; j++) + { + r = color[j]/((float) texels*255.0); + texture_reflectivity[i][j] = r; + } + } +} + +/* +======================================================================= + +MAKE FACES + +======================================================================= +*/ + +/* +============= +WindingFromFace +============= +*/ +winding_t *WindingFromFace (dface_t *f) +{ + int i; + int se; + dvertex_t *dv; + int v; + winding_t *w; + + w = AllocWinding (f->numedges); + w->numpoints = f->numedges; + + for (i=0 ; inumedges ; i++) + { + se = dsurfedges[f->firstedge + i]; + if (se < 0) + v = dedges[-se].v[1]; + else + v = dedges[se].v[0]; + + dv = &dvertexes[v]; + VectorCopy (dv->point, w->p[i]); + } + + RemoveColinearPoints (w); + + return w; +} + +/* +============= +BaseLightForFace +============= +*/ +void BaseLightForFace (dface_t *f, vec3_t color) +{ + texinfo_t *tx; + + // + // check for light emited by texture + // + tx = &texinfo[f->texinfo]; + if (!(tx->flags & SURF_LIGHT) || tx->value == 0) + { + VectorClear (color); + return; + } + + VectorScale (texture_reflectivity[f->texinfo], tx->value, color); +} + +qboolean IsSky (dface_t *f) +{ + texinfo_t *tx; + + tx = &texinfo[f->texinfo]; + if (tx->flags & SURF_SKY) + return true; + return false; +} + +/* +============= +MakePatchForFace +============= +*/ +float totalarea; +void MakePatchForFace (int fn, winding_t *w) +{ + dface_t *f; + float area; + patch_t *patch; + dplane_t *pl; + int i; + vec3_t color; + dleaf_t *leaf; + + f = &dfaces[fn]; + + area = WindingArea (w); + totalarea += area; + + patch = &patches[num_patches]; + if (num_patches == MAX_PATCHES) + Error ("num_patches == MAX_PATCHES"); + patch->next = face_patches[fn]; + face_patches[fn] = patch; + + patch->winding = w; + + if (f->side) + patch->plane = &backplanes[f->planenum]; + else + patch->plane = &dplanes[f->planenum]; + if (face_offset[fn][0] || face_offset[fn][1] || face_offset[fn][2] ) + { // origin offset faces must create new planes + if (numplanes + fakeplanes >= MAX_MAP_PLANES) + Error ("numplanes + fakeplanes >= MAX_MAP_PLANES"); + pl = &dplanes[numplanes + fakeplanes]; + fakeplanes++; + + *pl = *(patch->plane); + pl->dist += DotProduct (face_offset[fn], pl->normal); + patch->plane = pl; + } + + WindingCenter (w, patch->origin); + VectorAdd (patch->origin, patch->plane->normal, patch->origin); + leaf = Rad_PointInLeaf(patch->origin); + patch->cluster = leaf->cluster; + if (patch->cluster == -1) + Sys_FPrintf( SYS_VRB, "patch->cluster == -1\n"); + + patch->area = area; + if (patch->area <= 1) + patch->area = 1; + patch->sky = IsSky (f); + + VectorCopy (texture_reflectivity[f->texinfo], patch->reflectivity); + + // non-bmodel patches can emit light + if (fn < dmodels[0].numfaces) + { + BaseLightForFace (f, patch->baselight); + + ColorNormalize (patch->reflectivity, color); + + for (i=0 ; i<3 ; i++) + patch->baselight[i] *= color[i]; + + VectorCopy (patch->baselight, patch->totallight); + } + num_patches++; +} + + +entity_t *EntityForModel (int modnum) +{ + int i; + char *s; + char name[16]; + + sprintf (name, "*%i", modnum); + // search the entities for one using modnum + for (i=0 ; inumfaces ; j++) + { + fn = mod->firstface + j; + face_entity[fn] = ent; + VectorCopy (origin, face_offset[fn]); + f = &dfaces[fn]; + w = WindingFromFace (f); + for (k=0 ; knumpoints ; k++) + { + VectorAdd (w->p[k], origin, w->p[k]); + } + MakePatchForFace (fn, w); + } + } + + Sys_FPrintf( SYS_VRB, "%i sqaure feet\n", (int)(totalarea/64)); +} + +/* +======================================================================= + +SUBDIVIDE + +======================================================================= +*/ + +void FinishSplit (patch_t *patch, patch_t *newp) +{ + dleaf_t *leaf; + + VectorCopy (patch->baselight, newp->baselight); + VectorCopy (patch->totallight, newp->totallight); + VectorCopy (patch->reflectivity, newp->reflectivity); + newp->plane = patch->plane; + newp->sky = patch->sky; + + patch->area = WindingArea (patch->winding); + newp->area = WindingArea (newp->winding); + + if (patch->area <= 1) + patch->area = 1; + if (newp->area <= 1) + newp->area = 1; + + WindingCenter (patch->winding, patch->origin); + VectorAdd (patch->origin, patch->plane->normal, patch->origin); + leaf = Rad_PointInLeaf(patch->origin); + patch->cluster = leaf->cluster; + if (patch->cluster == -1) + Sys_FPrintf( SYS_VRB, "patch->cluster == -1\n"); + + WindingCenter (newp->winding, newp->origin); + VectorAdd (newp->origin, newp->plane->normal, newp->origin); + leaf = Rad_PointInLeaf(newp->origin); + newp->cluster = leaf->cluster; + if (newp->cluster == -1) + Sys_FPrintf( SYS_VRB, "patch->cluster == -1\n"); +} + +/* +============= +SubdividePatch + +Chops the patch only if its local bounds exceed the max size +============= +*/ +void SubdividePatch (patch_t *patch) +{ + winding_t *w, *o1, *o2; + vec3_t mins, maxs, total; + vec3_t split; + vec_t dist; + int i, j; + vec_t v; + patch_t *newp; + + w = patch->winding; + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } + VectorSubtract (maxs, mins, total); + for (i=0 ; i<3 ; i++) + if (total[i] > (subdiv+1) ) + break; + if (i == 3) + { + // no splitting needed + return; + } + + // + // split the winding + // + VectorCopy (vec3_origin, split); + split[i] = 1; + dist = (mins[i] + maxs[i])*0.5; + ClipWindingEpsilon (w, split, dist, ON_EPSILON, &o1, &o2); + + // + // create a new patch + // + if (num_patches == MAX_PATCHES) + Error ("MAX_PATCHES"); + newp = &patches[num_patches]; + num_patches++; + + newp->next = patch->next; + patch->next = newp; + + patch->winding = o1; + newp->winding = o2; + + FinishSplit (patch, newp); + + SubdividePatch (patch); + SubdividePatch (newp); +} + + +/* +============= +DicePatch + +Chops the patch by a global grid +============= +*/ +void DicePatch (patch_t *patch) +{ + winding_t *w, *o1, *o2; + vec3_t mins, maxs; + vec3_t split; + vec_t dist; + int i; + patch_t *newp; + + w = patch->winding; + WindingBounds (w, mins, maxs); + for (i=0 ; i<3 ; i++) + if (floor((mins[i]+1)/subdiv) < floor((maxs[i]-1)/subdiv)) + break; + if (i == 3) + { + // no splitting needed + return; + } + + // + // split the winding + // + VectorCopy (vec3_origin, split); + split[i] = 1; + dist = subdiv*(1+floor((mins[i]+1)/subdiv)); + ClipWindingEpsilon (w, split, dist, ON_EPSILON, &o1, &o2); + + // + // create a new patch + // + if (num_patches == MAX_PATCHES) + Error ("MAX_PATCHES"); + newp = &patches[num_patches]; + num_patches++; + + newp->next = patch->next; + patch->next = newp; + + patch->winding = o1; + newp->winding = o2; + + FinishSplit (patch, newp); + + DicePatch (patch); + DicePatch (newp); +} + + +/* +============= +SubdividePatches +============= +*/ +void SubdividePatches (void) +{ + int i, num; + + if (subdiv < 1) + return; + + num = num_patches; // because the list will grow + for (i=0 ; i c_peak_portals) + c_peak_portals = c_active_portals; + + p = malloc (sizeof(portal_t)); + memset (p, 0, sizeof(portal_t)); + + return p; +} + +void FreePortal (portal_t *p) +{ + if (p->winding) + FreeWinding (p->winding); + if (numthreads == 1) + c_active_portals--; + free (p); +} + +//============================================================== + +/* +============== +VisibleContents + +Returns the single content bit of the +strongest visible content present +============== +*/ +int VisibleContents (int contents) +{ + int i; + + for (i=1 ; i<=LAST_VISIBLE_CONTENTS ; i<<=1) + if (contents & i ) + return i; + + return 0; +} + + +/* +=============== +ClusterContents +=============== +*/ +int ClusterContents (node_t *node) +{ + int c1, c2, c; + + if (node->planenum == PLANENUM_LEAF) + return node->contents; + + c1 = ClusterContents(node->children[0]); + c2 = ClusterContents(node->children[1]); + c = c1|c2; + + // a cluster may include some solid detail areas, but + // still be seen into + if ( ! (c1&CONTENTS_SOLID) || ! (c2&CONTENTS_SOLID) ) + c &= ~CONTENTS_SOLID; + return c; +} + +/* +============= +Portal_VisFlood + +Returns true if the portal is empty or translucent, allowing +the PVS calculation to see through it. +The nodes on either side of the portal may actually be clusters, +not leafs, so all contents should be ored together +============= +*/ +qboolean Portal_VisFlood (portal_t *p) +{ + int c1, c2; + + if (!p->onnode) + return false; // to global outsideleaf + + c1 = ClusterContents(p->nodes[0]); + c2 = ClusterContents(p->nodes[1]); + + if (!VisibleContents (c1^c2)) + return true; + + if (c1 & (CONTENTS_TRANSLUCENT|CONTENTS_DETAIL)) + c1 = 0; + if (c2 & (CONTENTS_TRANSLUCENT|CONTENTS_DETAIL)) + c2 = 0; + + if ( (c1|c2) & CONTENTS_SOLID ) + return false; // can't see through solid + + if (! (c1 ^ c2)) + return true; // identical on both sides + + if (!VisibleContents (c1^c2)) + return true; + return false; +} + + +/* +=============== +Portal_EntityFlood + +The entity flood determines which areas are +"outside" on the map, which are then filled in. +Flowing from side s to side !s +=============== +*/ +qboolean Portal_EntityFlood (portal_t *p, int s) +{ + if (p->nodes[0]->planenum != PLANENUM_LEAF + || p->nodes[1]->planenum != PLANENUM_LEAF) + Error ("Portal_EntityFlood: not a leaf"); + + // can never cross to a solid + if ( (p->nodes[0]->contents & CONTENTS_SOLID) + || (p->nodes[1]->contents & CONTENTS_SOLID) ) + return false; + + // can flood through everything else + return true; +} + + +//============================================================================= + +int c_tinyportals; + +/* +============= +AddPortalToNodes +============= +*/ +void AddPortalToNodes (portal_t *p, node_t *front, node_t *back) +{ + if (p->nodes[0] || p->nodes[1]) + Error ("AddPortalToNode: allready included"); + + p->nodes[0] = front; + p->next[0] = front->portals; + front->portals = p; + + p->nodes[1] = back; + p->next[1] = back->portals; + back->portals = p; +} + + +/* +============= +RemovePortalFromNode +============= +*/ +void RemovePortalFromNode (portal_t *portal, node_t *l) +{ + portal_t **pp, *t; + +// remove reference to the current portal + pp = &l->portals; + while (1) + { + t = *pp; + if (!t) + Error ("RemovePortalFromNode: portal not in leaf"); + + if ( t == portal ) + break; + + if (t->nodes[0] == l) + pp = &t->next[0]; + else if (t->nodes[1] == l) + pp = &t->next[1]; + else + Error ("RemovePortalFromNode: portal not bounding leaf"); + } + + if (portal->nodes[0] == l) + { + *pp = portal->next[0]; + portal->nodes[0] = NULL; + } + else if (portal->nodes[1] == l) + { + *pp = portal->next[1]; + portal->nodes[1] = NULL; + } +} + +//============================================================================ + +void PrintPortal (portal_t *p) +{ + int i; + winding_t *w; + + w = p->winding; + for (i=0 ; inumpoints ; i++) + Sys_Printf ("(%5.0f,%5.0f,%5.0f)\n",w->p[i][0] + , w->p[i][1], w->p[i][2]); +} + +/* +================ +MakeHeadnodePortals + +The created portals will face the global outside_node +================ +*/ +#define SIDESPACE 8 +void MakeHeadnodePortals (tree_t *tree) +{ + vec3_t bounds[2]; + int i, j, n; + portal_t *p, *portals[6]; + plane_t bplanes[6], *pl; + node_t *node; + + node = tree->headnode; + +// pad with some space so there will never be null volume leafs + for (i=0 ; i<3 ; i++) + { + bounds[0][i] = tree->mins[i] - SIDESPACE; + bounds[1][i] = tree->maxs[i] + SIDESPACE; + } + + tree->outside_node.planenum = PLANENUM_LEAF; + tree->outside_node.brushlist = NULL; + tree->outside_node.portals = NULL; + tree->outside_node.contents = 0; + + for (i=0 ; i<3 ; i++) + for (j=0 ; j<2 ; j++) + { + n = j*3 + i; + + p = AllocPortal (); + portals[n] = p; + + pl = &bplanes[n]; + memset (pl, 0, sizeof(*pl)); + if (j) + { + pl->normal[i] = -1; + pl->dist = -bounds[j][i]; + } + else + { + pl->normal[i] = 1; + pl->dist = bounds[j][i]; + } + p->plane = *pl; + p->winding = BaseWindingForPlane (pl->normal, pl->dist); + AddPortalToNodes (p, node, &tree->outside_node); + } + +// clip the basewindings by all the other planes + for (i=0 ; i<6 ; i++) + { + for (j=0 ; j<6 ; j++) + { + if (j == i) + continue; + ChopWindingInPlace (&portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON); + } + } +} + +//=================================================== + + +/* +================ +BaseWindingForNode +================ +*/ +#define BASE_WINDING_EPSILON 0.001 +#define SPLIT_WINDING_EPSILON 0.001 + +winding_t *BaseWindingForNode (node_t *node) +{ + winding_t *w; + node_t *n; + plane_t *plane; + vec3_t normal; + vec_t dist; + + w = BaseWindingForPlane (mapplanes[node->planenum].normal + , mapplanes[node->planenum].dist); + + // clip by all the parents + for (n=node->parent ; n && w ; ) + { + plane = &mapplanes[n->planenum]; + + if (n->children[0] == node) + { // take front + ChopWindingInPlace (&w, plane->normal, plane->dist, BASE_WINDING_EPSILON); + } + else + { // take back + VectorSubtract (vec3_origin, plane->normal, normal); + dist = -plane->dist; + ChopWindingInPlace (&w, normal, dist, BASE_WINDING_EPSILON); + } + node = n; + n = n->parent; + } + + return w; +} + +//============================================================ + +qboolean WindingIsTiny (winding_t *w); + +/* +================== +MakeNodePortal + +create the new portal by taking the full plane winding for the cutting plane +and clipping it by all of parents of this node +================== +*/ +void MakeNodePortal (node_t *node) +{ + portal_t *new_portal, *p; + winding_t *w; + vec3_t normal; + float dist; + int side; + + w = BaseWindingForNode (node); + + // clip the portal by all the other portals in the node + for (p = node->portals ; p && w; p = p->next[side]) + { + if (p->nodes[0] == node) + { + side = 0; + VectorCopy (p->plane.normal, normal); + dist = p->plane.dist; + } + else if (p->nodes[1] == node) + { + side = 1; + VectorSubtract (vec3_origin, p->plane.normal, normal); + dist = -p->plane.dist; + } + else + Error ("CutNodePortals_r: mislinked portal"); + + ChopWindingInPlace (&w, normal, dist, 0.1); + } + + if (!w) + { + return; + } + + if (WindingIsTiny (w)) + { + c_tinyportals++; + FreeWinding (w); + return; + } + + + new_portal = AllocPortal (); + new_portal->plane = mapplanes[node->planenum]; + new_portal->onnode = node; + new_portal->winding = w; + AddPortalToNodes (new_portal, node->children[0], node->children[1]); +} + + +/* +============== +SplitNodePortals + +Move or split the portals that bound node so that the node's +children have portals instead of node. +============== +*/ +void SplitNodePortals (node_t *node) +{ + portal_t *p, *next_portal, *new_portal; + node_t *f, *b, *other_node; + int side; + plane_t *plane; + winding_t *frontwinding, *backwinding; + + plane = &mapplanes[node->planenum]; + f = node->children[0]; + b = node->children[1]; + + for (p = node->portals ; p ; p = next_portal) + { + if (p->nodes[0] == node) + side = 0; + else if (p->nodes[1] == node) + side = 1; + else + Error ("CutNodePortals_r: mislinked portal"); + next_portal = p->next[side]; + + other_node = p->nodes[!side]; + RemovePortalFromNode (p, p->nodes[0]); + RemovePortalFromNode (p, p->nodes[1]); + +// +// cut the portal into two portals, one on each side of the cut plane +// + ClipWindingEpsilon (p->winding, plane->normal, plane->dist, + SPLIT_WINDING_EPSILON, &frontwinding, &backwinding); + + if (frontwinding && WindingIsTiny(frontwinding)) + { + FreeWinding (frontwinding); + frontwinding = NULL; + c_tinyportals++; + } + + if (backwinding && WindingIsTiny(backwinding)) + { + FreeWinding (backwinding); + backwinding = NULL; + c_tinyportals++; + } + + if (!frontwinding && !backwinding) + { // tiny windings on both sides + continue; + } + + if (!frontwinding) + { + FreeWinding (backwinding); + if (side == 0) + AddPortalToNodes (p, b, other_node); + else + AddPortalToNodes (p, other_node, b); + continue; + } + if (!backwinding) + { + FreeWinding (frontwinding); + if (side == 0) + AddPortalToNodes (p, f, other_node); + else + AddPortalToNodes (p, other_node, f); + continue; + } + + // the winding is split + new_portal = AllocPortal (); + *new_portal = *p; + new_portal->winding = backwinding; + FreeWinding (p->winding); + p->winding = frontwinding; + + if (side == 0) + { + AddPortalToNodes (p, f, other_node); + AddPortalToNodes (new_portal, b, other_node); + } + else + { + AddPortalToNodes (p, other_node, f); + AddPortalToNodes (new_portal, other_node, b); + } + } + + node->portals = NULL; +} + + +/* +================ +CalcNodeBounds +================ +*/ +void CalcNodeBounds (node_t *node) +{ + portal_t *p; + int s; + int i; + + // calc mins/maxs for both leafs and nodes + ClearBounds (node->mins, node->maxs); + for (p = node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + for (i=0 ; iwinding->numpoints ; i++) + AddPointToBounds (p->winding->p[i], node->mins, node->maxs); + } +} + + +/* +================== +MakeTreePortals_r +================== +*/ +void MakeTreePortals_r (node_t *node) +{ + int i; + + CalcNodeBounds (node); + if (node->mins[0] >= node->maxs[0]) + { + Sys_Printf ("WARNING: node without a volume\n"); + } + + for (i=0 ; i<3 ; i++) + { + if (node->mins[i] < -8000 || node->maxs[i] > 8000) + { + Sys_Printf ("WARNING: node with unbounded volume\n"); + break; + } + } + if (node->planenum == PLANENUM_LEAF) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + MakeTreePortals_r (node->children[0]); + MakeTreePortals_r (node->children[1]); +} + +/* +================== +MakeTreePortals +================== +*/ +void MakeTreePortals (tree_t *tree) +{ + MakeHeadnodePortals (tree); + MakeTreePortals_r (tree->headnode); +} + +/* +========================================================= + +FLOOD ENTITIES + +========================================================= +*/ + +/* +============= +FloodPortals_r +============= +*/ +void FloodPortals_r (node_t *node, int dist) +{ + portal_t *p; + int s; + + node->occupied = dist; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + if (p->nodes[!s]->occupied) + continue; + + if (!Portal_EntityFlood (p, s)) + continue; + + FloodPortals_r (p->nodes[!s], dist+1); + } +} + +/* +============= +PlaceOccupant +============= +*/ +qboolean PlaceOccupant (node_t *headnode, vec3_t origin, entity_t *occupant) +{ + node_t *node; + vec_t d; + plane_t *plane; + + // find the leaf to start in + node = headnode; + while (node->planenum != PLANENUM_LEAF) + { + plane = &mapplanes[node->planenum]; + d = DotProduct (origin, plane->normal) - plane->dist; + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + + if (node->contents == CONTENTS_SOLID) + return false; + node->occupant = occupant; + + FloodPortals_r (node, 1); + + return true; +} + +/* +============= +FloodEntities + +Marks all nodes that can be reached by entites +============= +*/ +qboolean FloodEntities (tree_t *tree) +{ + int i; + vec3_t origin; + char *cl; + qboolean inside; + node_t *headnode; + + headnode = tree->headnode; + Sys_FPrintf( SYS_VRB, "--- FloodEntities ---\n"); + inside = false; + tree->outside_node.occupied = 0; + + for (i=1 ; ioutside_node.occupied) + { + Sys_FPrintf( SYS_VRB, "entity reached from outside -- no filling\n"); + } + + return (qboolean)(inside && !tree->outside_node.occupied); +} + +/* +========================================================= + +FLOOD AREAS + +========================================================= +*/ + +int c_areas; + +/* +============= +FloodAreas_r +============= +*/ +void FloodAreas_r (node_t *node) +{ + portal_t *p; + int s; + bspbrush_t *b; + entity_t *e; + + if (node->contents == CONTENTS_AREAPORTAL) + { + // this node is part of an area portal + b = node->brushlist; + e = &entities[b->original->entitynum]; + + // if the current area has allready touched this + // portal, we are done + if (e->portalareas[0] == c_areas || e->portalareas[1] == c_areas) + return; + + // note the current area as bounding the portal + if (e->portalareas[1]) + { + Sys_Printf ("WARNING: areaportal entity %i touches > 2 areas\n", b->original->entitynum); + return; + } + if (e->portalareas[0]) + e->portalareas[1] = c_areas; + else + e->portalareas[0] = c_areas; + + return; + } + + if (node->area) + return; // allready got it + node->area = c_areas; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); +#if 0 + if (p->nodes[!s]->occupied) + continue; +#endif + if (!Portal_EntityFlood (p, s)) + continue; + + FloodAreas_r (p->nodes[!s]); + } +} + +/* +============= +FindAreas_r + +Just decend the tree, and for each node that hasn't had an +area set, flood fill out from there +============= +*/ +void FindAreas_r (node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + FindAreas_r (node->children[0]); + FindAreas_r (node->children[1]); + return; + } + + if (node->area) + return; // allready got it + + if (node->contents & CONTENTS_SOLID) + return; + + if (!node->occupied) + return; // not reachable by entities + + // area portals are allways only flooded into, never + // out of + if (node->contents == CONTENTS_AREAPORTAL) + return; + + c_areas++; + FloodAreas_r (node); +} + +/* +============= +SetAreaPortalAreas_r + +Just decend the tree, and for each node that hasn't had an +area set, flood fill out from there +============= +*/ +void SetAreaPortalAreas_r (node_t *node) +{ + bspbrush_t *b; + entity_t *e; + + if (node->planenum != PLANENUM_LEAF) + { + SetAreaPortalAreas_r (node->children[0]); + SetAreaPortalAreas_r (node->children[1]); + return; + } + + if (node->contents == CONTENTS_AREAPORTAL) + { + if (node->area) + return; // allready set + + b = node->brushlist; + e = &entities[b->original->entitynum]; + node->area = e->portalareas[0]; + if (!e->portalareas[1]) + { + Sys_Printf ("WARNING: areaportal entity %i doesn't touch two areas\n", b->original->entitynum); + return; + } + } +} + +/* +============= +EmitAreaPortals + +============= +*/ +void EmitAreaPortals (node_t *headnode) +{ + int i, j; + entity_t *e; + dareaportal_t *dp; + + if (c_areas > MAX_MAP_AREAS) + Error ("MAX_MAP_AREAS"); + numareas = c_areas+1; + numareaportals = 1; // leave 0 as an error + + for (i=1 ; i<=c_areas ; i++) + { + dareas[i].firstareaportal = numareaportals; + for (j=0 ; jareaportalnum) + continue; + dp = &dareaportals[numareaportals]; + if (e->portalareas[0] == i) + { + dp->portalnum = e->areaportalnum; + dp->otherarea = e->portalareas[1]; + numareaportals++; + } + else if (e->portalareas[1] == i) + { + dp->portalnum = e->areaportalnum; + dp->otherarea = e->portalareas[0]; + numareaportals++; + } + } + dareas[i].numareaportals = numareaportals - dareas[i].firstareaportal; + } + + Sys_FPrintf( SYS_VRB, "%5i numareas\n", numareas); + Sys_FPrintf( SYS_VRB, "%5i numareaportals\n", numareaportals); +} + +/* +============= +FloodAreas + +Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL +============= +*/ +void FloodAreas (tree_t *tree) +{ + Sys_FPrintf( SYS_VRB, "--- FloodAreas ---\n"); + FindAreas_r (tree->headnode); + SetAreaPortalAreas_r (tree->headnode); + Sys_FPrintf( SYS_VRB, "%5i areas\n", c_areas); +} + +//====================================================== + +int c_outside; +int c_inside; +int c_solid; + +void FillOutside_r (node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + FillOutside_r (node->children[0]); + FillOutside_r (node->children[1]); + return; + } + + // anything not reachable by an entity + // can be filled away + if (!node->occupied) + { + if (node->contents != CONTENTS_SOLID) + { + c_outside++; + node->contents = CONTENTS_SOLID; + } + else + c_solid++; + } + else + c_inside++; + +} + +/* +============= +FillOutside + +Fill all nodes that can't be reached by entities +============= +*/ +void FillOutside (node_t *headnode) +{ + c_outside = 0; + c_inside = 0; + c_solid = 0; + Sys_FPrintf( SYS_VRB, "--- FillOutside ---\n"); + FillOutside_r (headnode); + Sys_FPrintf( SYS_VRB, "%5i solid leafs\n", c_solid); + Sys_FPrintf( SYS_VRB, "%5i leafs filled\n", c_outside); + Sys_FPrintf( SYS_VRB, "%5i inside leafs\n", c_inside); +} + + +//============================================================== + +/* +============ +FindPortalSide + +Finds a brush side to use for texturing the given portal +============ +*/ +void FindPortalSide (portal_t *p) +{ + int viscontents; + bspbrush_t *bb; + mapbrush_t *brush; + node_t *n; + int i,j; + int planenum; + side_t *side, *bestside; + float dot, bestdot; + plane_t *p1, *p2; + + // decide which content change is strongest + // solid > lava > water, etc + viscontents = VisibleContents (p->nodes[0]->contents ^ p->nodes[1]->contents); + if (!viscontents) + return; + + planenum = p->onnode->planenum; + bestside = NULL; + bestdot = 0; + + for (j=0 ; j<2 ; j++) + { + n = p->nodes[j]; + p1 = &mapplanes[p->onnode->planenum]; + for (bb=n->brushlist ; bb ; bb=bb->next) + { + brush = bb->original; + if ( !(brush->contents & viscontents) ) + continue; + for (i=0 ; inumsides ; i++) + { + side = &brush->original_sides[i]; + if (side->bevel) + continue; + if (side->texinfo == TEXINFO_NODE) + continue; // non-visible + if ((side->planenum&~1) == planenum) + { // exact match + bestside = &brush->original_sides[i]; + goto gotit; + } + // see how close the match is + p2 = &mapplanes[side->planenum&~1]; + dot = DotProduct (p1->normal, p2->normal); + if (dot > bestdot) + { + bestdot = dot; + bestside = side; + } + } + } + } + +gotit: + if (!bestside) + Sys_FPrintf( SYS_VRB, "WARNING: side not found for portal\n"); + + p->sidefound = true; + p->side = bestside; +} + + +/* +=============== +MarkVisibleSides_r + +=============== +*/ +void MarkVisibleSides_r (node_t *node) +{ + portal_t *p; + int s; + + if (node->planenum != PLANENUM_LEAF) + { + MarkVisibleSides_r (node->children[0]); + MarkVisibleSides_r (node->children[1]); + return; + } + + // empty leafs are never boundary leafs + if (!node->contents) + return; + + // see if there is a visible face + for (p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (!p->onnode) + continue; // edge of world + if (!p->sidefound) + FindPortalSide (p); + if (p->side) + p->side->visible = true; + } + +} + +/* +============= +MarkVisibleSides + +============= +*/ +void MarkVisibleSides (tree_t *tree, int startbrush, int endbrush) +{ + int i, j; + mapbrush_t *mb; + int numsides; + + Sys_FPrintf( SYS_VRB, "--- MarkVisibleSides ---\n"); + + // clear all the visible flags + for (i=startbrush ; inumsides; + for (j=0 ; joriginal_sides[j].visible = false; + } + + // set visible flags on the sides that are used by portals + MarkVisibleSides_r (tree->headnode); +} + diff --git a/tools/quake2/q2map/prtfile.c b/tools/quake2/q2map/prtfile.c new file mode 100644 index 00000000..790db065 --- /dev/null +++ b/tools/quake2/q2map/prtfile.c @@ -0,0 +1,286 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qbsp.h" + +/* +============================================================================== + +PORTAL FILE GENERATION + +Save out name.prt for qvis to read +============================================================================== +*/ + + +#define PORTALFILE "PRT1" + +FILE *pf; +int num_visclusters; // clusters the player can be in +int num_visportals; + +void WriteFloat (FILE *f, vec_t v) +{ + if ( fabs(v - Q_rint(v)) < 0.001 ) + fprintf (f,"%i ",(int)Q_rint(v)); + else + fprintf (f,"%f ",v); +} + +/* +================= +WritePortalFile_r +================= +*/ +void WritePortalFile_r (node_t *node) +{ + int i, s; + portal_t *p; + winding_t *w; + vec3_t normal; + vec_t dist; + + // decision node + if (node->planenum != PLANENUM_LEAF && !node->detail_seperator) + { + WritePortalFile_r (node->children[0]); + WritePortalFile_r (node->children[1]); + return; + } + + if (node->contents & CONTENTS_SOLID) + return; + + for (p = node->portals ; p ; p=p->next[s]) + { + w = p->winding; + s = (p->nodes[1] == node); + if (w && p->nodes[0] == node) + { + if (!Portal_VisFlood (p)) + continue; + // write out to the file + + // sometimes planes get turned around when they are very near + // the changeover point between different axis. interpret the + // plane the same way vis will, and flip the side orders if needed + // FIXME: is this still relevent? + WindingPlane (w, normal, &dist); + if ( DotProduct (p->plane.normal, normal) < 0.99 ) + { // backwards... + fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster); + } + else + fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster); + for (i=0 ; inumpoints ; i++) + { + fprintf (pf,"("); + WriteFloat (pf, w->p[i][0]); + WriteFloat (pf, w->p[i][1]); + WriteFloat (pf, w->p[i][2]); + fprintf (pf,") "); + } + fprintf (pf,"\n"); + } + } + +} + +/* +================ +FillLeafNumbers_r + +All of the leafs under node will have the same cluster +================ +*/ +void FillLeafNumbers_r (node_t *node, int num) +{ + if (node->planenum == PLANENUM_LEAF) + { + if (node->contents & CONTENTS_SOLID) + node->cluster = -1; + else + node->cluster = num; + return; + } + node->cluster = num; + FillLeafNumbers_r (node->children[0], num); + FillLeafNumbers_r (node->children[1], num); +} + +/* +================ +NumberLeafs_r +================ +*/ +void NumberLeafs_r (node_t *node) +{ + portal_t *p; + + if (node->planenum != PLANENUM_LEAF && !node->detail_seperator) + { // decision node + node->cluster = -99; + NumberLeafs_r (node->children[0]); + NumberLeafs_r (node->children[1]); + return; + } + + // either a leaf or a detail cluster + + if ( node->contents & CONTENTS_SOLID ) + { // solid block, viewpoint never inside + node->cluster = -1; + return; + } + + FillLeafNumbers_r (node, num_visclusters); + num_visclusters++; + + // count the portals + for (p = node->portals ; p ; ) + { + if (p->nodes[0] == node) // only write out from first leaf + { + if (Portal_VisFlood (p)) + num_visportals++; + p = p->next[0]; + } + else + p = p->next[1]; + } + +} + + +/* +================ +CreateVisPortals_r +================ +*/ +void CreateVisPortals_r (node_t *node) +{ + // stop as soon as we get to a detail_seperator, which + // means that everything below is in a single cluster + if (node->planenum == PLANENUM_LEAF || node->detail_seperator ) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + CreateVisPortals_r (node->children[0]); + CreateVisPortals_r (node->children[1]); +} + +/* +================ +FinishVisPortals_r +================ +*/ +void FinishVisPortals2_r (node_t *node) +{ + if (node->planenum == PLANENUM_LEAF) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + FinishVisPortals2_r (node->children[0]); + FinishVisPortals2_r (node->children[1]); +} + +void FinishVisPortals_r (node_t *node) +{ + if (node->planenum == PLANENUM_LEAF) + return; + + if (node->detail_seperator) + { + FinishVisPortals2_r (node); + return; + } + + FinishVisPortals_r (node->children[0]); + FinishVisPortals_r (node->children[1]); +} + + +int clusterleaf; +void SaveClusters_r (node_t *node) +{ + if (node->planenum == PLANENUM_LEAF) + { + dleafs[clusterleaf++].cluster = node->cluster; + return; + } + SaveClusters_r (node->children[0]); + SaveClusters_r (node->children[1]); +} + +/* +================ +WritePortalFile +================ +*/ +void WritePortalFile (tree_t *tree) +{ + char filename[1024]; + node_t *headnode; + + Sys_FPrintf( SYS_VRB, "--- WritePortalFile ---\n"); + + headnode = tree->headnode; + num_visclusters = 0; + num_visportals = 0; + + FreeTreePortals_r (headnode); + + MakeHeadnodePortals (tree); + + CreateVisPortals_r (headnode); + +// set the cluster field in every leaf and count the total number of portals + + NumberLeafs_r (headnode); + +// write the file + sprintf (filename, "%s.prt", source); + Sys_Printf ("writing %s\n", filename); + pf = fopen (filename, "w"); + if (!pf) + Error ("Error opening %s", filename); + + fprintf (pf, "%s\n", PORTALFILE); + fprintf (pf, "%i\n", num_visclusters); + fprintf (pf, "%i\n", num_visportals); + + Sys_FPrintf( SYS_VRB, "%5i visclusters\n", num_visclusters); + Sys_FPrintf( SYS_VRB, "%5i visportals\n", num_visportals); + + WritePortalFile_r (headnode); + + fclose (pf); + + // we need to store the clusters out now because ordering + // issues made us do this after writebsp... + clusterleaf = 1; + SaveClusters_r (headnode); +} + diff --git a/tools/quake2/q2map/q2map.h b/tools/quake2/q2map/q2map.h new file mode 100644 index 00000000..331b35df --- /dev/null +++ b/tools/quake2/q2map/q2map.h @@ -0,0 +1,50 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// q2map.h + +/* platform-specific */ +#if defined( __linux__ ) || defined( __APPLE__ ) + #define Q_UNIX +#endif + +#ifdef Q_UNIX + #include + #include + #include +#endif + +#ifdef WIN32 + #include +#endif + +#include + +#include "cmdlib.h" +#include "mathlib.h" +#include "scriplib.h" +#include "polylib.h" +#include "q2_threads.h" +#include "bspfile.h" +#include "inout.h" + +int BSP_Main (); +int VIS_Main (); +int RAD_Main (); diff --git a/tools/quake2/q2map/q2map.vcproj b/tools/quake2/q2map/q2map.vcproj new file mode 100644 index 00000000..5f3652a2 --- /dev/null +++ b/tools/quake2/q2map/q2map.vcproj @@ -0,0 +1,443 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/quake2/q2map/qbsp.c b/tools/quake2/q2map/qbsp.c new file mode 100644 index 00000000..2809390c --- /dev/null +++ b/tools/quake2/q2map/qbsp.c @@ -0,0 +1,426 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// csg4.c + +#include "qbsp.h" + +extern float subdivide_size; + +char source[1024]; +char name[1024]; + +vec_t microvolume = 1.0; +qboolean noprune; +qboolean glview; +qboolean nodetail; +qboolean fulldetail; +qboolean onlyents; +qboolean nomerge; +qboolean nowater; +qboolean nofill; +qboolean nocsg; +qboolean noweld; +qboolean noshare; +qboolean nosubdiv; +qboolean notjunc; +qboolean noopt; +qboolean leaktest; +qboolean verboseentities; + +char outbase[32]; + +int block_xl = -8, block_xh = 7, block_yl = -8, block_yh = 7; + +int entity_num; + + +node_t *block_nodes[10][10]; + + +/* +============ +BlockTree + +============ +*/ +node_t *BlockTree (int xl, int yl, int xh, int yh) +{ + node_t *node; + vec3_t normal; + float dist; + int mid; + + if (xl == xh && yl == yh) + { + node = block_nodes[xl+5][yl+5]; + if (!node) + { // return an empty leaf + node = AllocNode (); + node->planenum = PLANENUM_LEAF; + node->contents = 0; //CONTENTS_SOLID; + return node; + } + return node; + } + + // create a seperator along the largest axis + node = AllocNode (); + + if (xh - xl > yh - yl) + { // split x axis + mid = xl + (xh-xl)/2 + 1; + normal[0] = 1; + normal[1] = 0; + normal[2] = 0; + dist = mid*1024; + node->planenum = FindFloatPlane (normal, dist); + node->children[0] = BlockTree ( mid, yl, xh, yh); + node->children[1] = BlockTree ( xl, yl, mid-1, yh); + } + else + { + mid = yl + (yh-yl)/2 + 1; + normal[0] = 0; + normal[1] = 1; + normal[2] = 0; + dist = mid*1024; + node->planenum = FindFloatPlane (normal, dist); + node->children[0] = BlockTree ( xl, mid, xh, yh); + node->children[1] = BlockTree ( xl, yl, xh, mid-1); + } + + return node; +} + +/* +============ +ProcessBlock_Thread + +============ +*/ +int brush_start, brush_end; +void ProcessBlock_Thread (int blocknum) +{ + int xblock, yblock; + vec3_t mins, maxs; + bspbrush_t *brushes; + tree_t *tree; + node_t *node; + + yblock = block_yl + blocknum / (block_xh-block_xl+1); + xblock = block_xl + blocknum % (block_xh-block_xl+1); + + Sys_FPrintf( SYS_VRB, "############### block %2i,%2i ###############\n", xblock, yblock); + + mins[0] = xblock*1024; + mins[1] = yblock*1024; + mins[2] = -4096; + maxs[0] = (xblock+1)*1024; + maxs[1] = (yblock+1)*1024; + maxs[2] = 4096; + + // the makelist and chopbrushes could be cached between the passes... + brushes = MakeBspBrushList (brush_start, brush_end, mins, maxs); + if (!brushes) + { + node = AllocNode (); + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + block_nodes[xblock+5][yblock+5] = node; + return; + } + + if (!nocsg) + brushes = ChopBrushes (brushes); + + tree = BrushBSP (brushes, mins, maxs); + + block_nodes[xblock+5][yblock+5] = tree->headnode; +} + +/* +============ +ProcessWorldModel + +============ +*/ +void ProcessWorldModel (void) +{ + entity_t *e; + tree_t *tree; + qboolean leaked; + qboolean optimize; + xmlNodePtr polyline, leaknode; + char level[ 2 ]; + + e = &entities[entity_num]; + + brush_start = e->firstbrush; + brush_end = brush_start + e->numbrushes; + leaked = false; + + // + // perform per-block operations + // + if (block_xh * 1024 > map_maxs[0]) + block_xh = floor(map_maxs[0]/1024.0); + if ( (block_xl+1) * 1024 < map_mins[0]) + block_xl = floor(map_mins[0]/1024.0); + if (block_yh * 1024 > map_maxs[1]) + block_yh = floor(map_maxs[1]/1024.0); + if ( (block_yl+1) * 1024 < map_mins[1]) + block_yl = floor(map_mins[1]/1024.0); + + if (block_xl <-4) + block_xl = -4; + if (block_yl <-4) + block_yl = -4; + if (block_xh > 3) + block_xh = 3; + if (block_yh > 3) + block_yh = 3; + + for (optimize = false ; optimize <= true ; optimize++) + { + Sys_FPrintf( SYS_VRB, "--------------------------------------------\n"); + + RunThreadsOnIndividual ((block_xh-block_xl+1)*(block_yh-block_yl+1), + !verbose, ProcessBlock_Thread); + + // + // build the division tree + // oversizing the blocks guarantees that all the boundaries + // will also get nodes. + // + + Sys_FPrintf( SYS_VRB, "--------------------------------------------\n"); + + tree = AllocTree (); + tree->headnode = BlockTree (block_xl-1, block_yl-1, block_xh+1, block_yh+1); + + tree->mins[0] = (block_xl)*1024; + tree->mins[1] = (block_yl)*1024; + tree->mins[2] = map_mins[2] - 8; + + tree->maxs[0] = (block_xh+1)*1024; + tree->maxs[1] = (block_yh+1)*1024; + tree->maxs[2] = map_maxs[2] + 8; + + // + // perform the global operations + // + MakeTreePortals (tree); + + if (FloodEntities (tree)) + FillOutside (tree->headnode); + else + { + + Sys_FPrintf( SYS_NOXML, "**********************\n" ); + Sys_FPrintf( SYS_NOXML, "******* leaked *******\n" ); + Sys_FPrintf( SYS_NOXML, "**********************\n" ); + polyline = LeakFile( tree ); + leaknode = xmlNewNode( NULL, "message" ); + xmlNodeSetContent( leaknode, "MAP LEAKED\n" ); + xmlAddChild( leaknode, polyline ); + level[0] = (int) '0' + SYS_ERR; + level[1] = 0; + xmlSetProp( leaknode, "level", (char*) &level ); + xml_SendNode( leaknode ); + if( leaktest ) + { + Sys_Printf ("--- MAP LEAKED, ABORTING LEAKTEST ---\n"); + exit( 0 ); + } + leaked = true; +/* + Sys_Printf ("**** leaked ****\n"); + leaked = true; + LeakFile (tree); + if (leaktest) + { + Sys_Printf ("--- MAP LEAKED ---\n"); + exit (0); + } */ + } + + MarkVisibleSides (tree, brush_start, brush_end); + if (noopt || leaked) + break; + if (!optimize) + { + FreeTree (tree); + } + } + + FloodAreas (tree); + if (glview) + WriteGLView (tree, source); + MakeFaces (tree->headnode); + FixTjuncs (tree->headnode); + + if (!noprune) + PruneNodes (tree->headnode); + + WriteBSP (tree->headnode); + + if (!leaked) + WritePortalFile (tree); + + FreeTree (tree); +} + +/* +============ +ProcessSubModel + +============ +*/ +void ProcessSubModel (void) +{ + entity_t *e; + int start, end; + tree_t *tree; + bspbrush_t *list; + vec3_t mins, maxs; + + e = &entities[entity_num]; + + start = e->firstbrush; + end = start + e->numbrushes; + + mins[0] = mins[1] = mins[2] = -4096; + maxs[0] = maxs[1] = maxs[2] = 4096; + list = MakeBspBrushList (start, end, mins, maxs); + if (!nocsg) + list = ChopBrushes (list); + tree = BrushBSP (list, mins, maxs); + MakeTreePortals (tree); + MarkVisibleSides (tree, start, end); + MakeFaces (tree->headnode); + FixTjuncs (tree->headnode); + WriteBSP (tree->headnode); + FreeTree (tree); +} + +/* +============ +ProcessModels +============ +*/ +void ProcessModels (void) +{ + BeginBSPFile (); + + for (entity_num=0 ; entity_num< num_entities ; entity_num++) + { + if (!entities[entity_num].numbrushes) + continue; + + Sys_FPrintf( SYS_VRB, "############### model %i ###############\n", nummodels); + BeginModel (); + if (entity_num == 0) + ProcessWorldModel (); + else + ProcessSubModel (); + EndModel (); + + //if (!verboseentities) + // verbose = false; // don't bother printing submodels + } + + EndBSPFile (); +} + + +/* +============ +main +============ +*/ +int BSP_Main () +{ + double start, end; + char path[1024]; + int total_bsp_time; + + Sys_Printf ("\n----- BSP ----\n\n"); + + + start = I_FloatTime (); + + ThreadSetDefault (); + SetQdirFromPath (mapname); + + strcpy (source, ExpandArg (mapname)); + StripExtension (source); + + // delete portal and line files + sprintf (path, "%s.prt", source); + remove (path); + sprintf (path, "%s.lin", source); + remove (path); + + strcpy (name, ExpandArg (mapname)); + DefaultExtension (name, ".map"); // might be .reg + + // + // if onlyents, just grab the entites and resave + // + if (onlyents) + { + char out[1024]; + + sprintf (out, "%s.bsp", source); + LoadBSPFile (out); + num_entities = 0; + + LoadMapFile (name); + SetModelNumbers (); + SetLightStyles (); + + UnparseEntities (); + + WriteBSPFile (out); + } + else + { + // + // start from scratch + // + LoadMapFile (name); + SetModelNumbers (); + SetLightStyles (); + + ProcessModels (); + } + + end = I_FloatTime (); + total_bsp_time = (int) (end-start); + Sys_Printf("\nBSP Time: "); + if ( total_bsp_time > 59 ) + Sys_Printf("%d Minutes ", total_bsp_time/60 ); + Sys_Printf( "%d Seconds\n", total_bsp_time%60 ); + + + return 0; +} + diff --git a/tools/quake2/q2map/qbsp.h b/tools/quake2/q2map/qbsp.h new file mode 100644 index 00000000..cc5c822a --- /dev/null +++ b/tools/quake2/q2map/qbsp.h @@ -0,0 +1,390 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* Files: + +brushbsp.c +csg.c +faces.c +gldraw.c +glfile.c +leakfile.c +map.c +nodraw.c +portals.c +prtfile.c +qbsp3.c +textures.c +tree.c +writebsp.c + +*/ + +#include "cmdlib.h" +#include "mathlib.h" +#include "scriplib.h" +#include "polylib.h" +#include "q2_threads.h" +#include "bspfile.h" +#include "inout.h" + +#ifdef WIN32 + #ifdef NDEBUG // Don't show in a Release build + #pragma warning(disable : 4305) // truncate from double to float + #pragma warning(disable : 4244) // conversion from double to float + #pragma warning(disable : 4018) // signed/unsigned mismatch + #endif +#endif + +#define MAX_BRUSH_SIDES 128 +#define CLIP_EPSILON 0.1 + +#define BOGUS_RANGE 8192 + +#define TEXINFO_NODE -1 // side is allready on a node + +typedef struct plane_s +{ + vec3_t normal; + vec_t dist; + int type; + struct plane_s *hash_chain; +} plane_t; + +typedef struct +{ + vec_t shift[2]; + vec_t rotate; + vec_t scale[2]; + char name[32]; + int flags; + int value; +} brush_texture_t; + +typedef struct side_s +{ + int planenum; + int texinfo; + winding_t *winding; + struct side_s *original; // bspbrush_t sides will reference the mapbrush_t sides + int contents; // from miptex + int surf; // from miptex + qboolean visible; // choose visble planes first + qboolean tested; // this plane allready checked as a split + qboolean bevel; // don't ever use for bsp splitting +} side_t; + +typedef struct brush_s +{ + int entitynum; + int brushnum; + + int contents; + + vec3_t mins, maxs; + + int numsides; + side_t *original_sides; +} mapbrush_t; + +#define PLANENUM_LEAF -1 + +#define MAXEDGES 20 + +typedef struct face_s +{ + struct face_s *next; // on node + + // the chain of faces off of a node can be merged or split, + // but each face_t along the way will remain in the chain + // until the entire tree is freed + struct face_s *merged; // if set, this face isn't valid anymore + struct face_s *split[2]; // if set, this face isn't valid anymore + + struct portal_s *portal; + int texinfo; + int planenum; + int contents; // faces in different contents can't merge + int outputnumber; + winding_t *w; + int numpoints; + qboolean badstartvert; // tjunctions cannot be fixed without a midpoint vertex + int vertexnums[MAXEDGES]; +} face_t; + + + +typedef struct bspbrush_s +{ + struct bspbrush_s *next; + vec3_t mins, maxs; + int side, testside; // side of node during construction + mapbrush_t *original; + int numsides; + side_t sides[6]; // variably sized +} bspbrush_t; + + + +#define MAX_NODE_BRUSHES 8 +typedef struct node_s +{ + // both leafs and nodes + int planenum; // -1 = leaf node + struct node_s *parent; + vec3_t mins, maxs; // valid after portalization + bspbrush_t *volume; // one for each leaf/node + + // nodes only + qboolean detail_seperator; // a detail brush caused the split + side_t *side; // the side that created the node + struct node_s *children[2]; + face_t *faces; + + // leafs only + bspbrush_t *brushlist; // fragments of all brushes in this leaf + int contents; // OR of all brush contents + int occupied; // 1 or greater can reach entity + entity_t *occupant; // for leak file testing + int cluster; // for portalfile writing + int area; // for areaportals + struct portal_s *portals; // also on nodes during construction +} node_t; + +typedef struct portal_s +{ + plane_t plane; + node_t *onnode; // NULL = outside box + node_t *nodes[2]; // [0] = front side of plane + struct portal_s *next[2]; + winding_t *winding; + + qboolean sidefound; // false if ->side hasn't been checked + side_t *side; // NULL = non-visible + face_t *face[2]; // output face in bsp file +} portal_t; + +typedef struct +{ + node_t *headnode; + node_t outside_node; + vec3_t mins, maxs; +} tree_t; + +extern int entity_num; + +extern plane_t mapplanes[MAX_MAP_PLANES]; +extern int nummapplanes; + +extern int nummapbrushes; +extern mapbrush_t mapbrushes[MAX_MAP_BRUSHES]; + +extern vec3_t map_mins, map_maxs; + +#define MAX_MAP_SIDES (MAX_MAP_BRUSHES*6) + +extern int nummapbrushsides; +extern side_t brushsides[MAX_MAP_SIDES]; + +extern qboolean noprune; +extern qboolean nodetail; +extern qboolean fulldetail; +extern qboolean nomerge; +extern qboolean nosubdiv; +extern qboolean nowater; +extern qboolean noweld; +extern qboolean noshare; +extern qboolean notjunc; + +extern vec_t microvolume; + +extern char outbase[32]; + +extern char source[1024]; + +void LoadMapFile (char *filename); +int FindFloatPlane (vec3_t normal, vec_t dist); + +//============================================================================= + +// textures.c + +typedef struct +{ + char name[64]; + int flags; + int value; + int contents; + char animname[64]; +} textureref_t; + +#define MAX_MAP_TEXTURES 1024 + +extern textureref_t textureref[MAX_MAP_TEXTURES]; + +int FindMiptex (char *name); + +int TexinfoForBrushTexture (plane_t *plane, brush_texture_t *bt, vec3_t origin); + +//============================================================================= + +void FindGCD (int *v); + +mapbrush_t *Brush_LoadEntity (entity_t *ent); +int PlaneTypeForNormal (vec3_t normal); +qboolean MakeBrushPlanes (mapbrush_t *b); +int FindIntPlane (int *inormal, int *iorigin); +void CreateBrush (int brushnum); + + +//============================================================================= + +// draw.c + +extern vec3_t draw_mins, draw_maxs; +extern qboolean drawflag; + +void Draw_ClearWindow (void); +void DrawWinding (winding_t *w); + +void GLS_BeginScene (void); +void GLS_Winding (winding_t *w, int code); +void GLS_EndScene (void); + +//============================================================================= + +// csg + +bspbrush_t *MakeBspBrushList (int startbrush, int endbrush, + vec3_t clipmins, vec3_t clipmaxs); +bspbrush_t *ChopBrushes (bspbrush_t *head); +bspbrush_t *InitialBrushList (bspbrush_t *list); +bspbrush_t *OptimizedBrushList (bspbrush_t *list); + +void WriteBrushMap (char *name, bspbrush_t *list); + +//============================================================================= + +// brushbsp + +void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis); + +bspbrush_t *CopyBrush (bspbrush_t *brush); + +void SplitBrush (bspbrush_t *brush, int planenum, + bspbrush_t **front, bspbrush_t **back); + +tree_t *AllocTree (void); +node_t *AllocNode (void); +bspbrush_t *AllocBrush (int numsides); +int CountBrushList (bspbrush_t *brushes); +void FreeBrush (bspbrush_t *brushes); +vec_t BrushVolume (bspbrush_t *brush); + +void BoundBrush (bspbrush_t *brush); +void FreeBrushList (bspbrush_t *brushes); + +tree_t *BrushBSP (bspbrush_t *brushlist, vec3_t mins, vec3_t maxs); + +//============================================================================= + +// portals.c + +int VisibleContents (int contents); + +void MakeHeadnodePortals (tree_t *tree); +void MakeNodePortal (node_t *node); +void SplitNodePortals (node_t *node); + +qboolean Portal_VisFlood (portal_t *p); + +qboolean FloodEntities (tree_t *tree); +void FillOutside (node_t *headnode); +void FloodAreas (tree_t *tree); +void MarkVisibleSides (tree_t *tree, int start, int end); +void FreePortal (portal_t *p); +void EmitAreaPortals (node_t *headnode); + +void MakeTreePortals (tree_t *tree); + +//============================================================================= + +// glfile.c + +void OutputWinding (winding_t *w, FILE *glview); +void WriteGLView (tree_t *tree, char *source); + +//============================================================================= + +// leakfile.c + +//void LeakFile (tree_t *tree); +xmlNodePtr LeakFile (tree_t *tree); + +//============================================================================= + +// prtfile.c + +void WritePortalFile (tree_t *tree); + +//============================================================================= + +// writebsp.c + +void SetModelNumbers (void); +void SetLightStyles (void); + +void BeginBSPFile (void); +void WriteBSP (node_t *headnode); +void EndBSPFile (void); +void BeginModel (void); +void EndModel (void); + +//============================================================================= + +// faces.c + +void MakeFaces (node_t *headnode); +void FixTjuncs (node_t *headnode); +int GetEdge2 (int v1, int v2, face_t *f); + +face_t *AllocFace (void); +void FreeFace (face_t *f); + +void MergeNodeFaces (node_t *node); + +//============================================================================= + +// tree.c + +void FreeTree (tree_t *tree); +void FreeTree_r (node_t *node); +void PrintTree_r (node_t *node, int depth); +void FreeTreePortals_r (node_t *node); +void PruneNodes_r (node_t *node); +void PruneNodes (node_t *node); + +//============================================================================= + +// externs + +extern char *mapname; +extern char game[64]; diff --git a/tools/quake2/q2map/qrad.c b/tools/quake2/q2map/qrad.c new file mode 100644 index 00000000..e1744cdd --- /dev/null +++ b/tools/quake2/q2map/qrad.c @@ -0,0 +1,647 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// qrad.c + +#include "qrad.h" + + + +/* + +NOTES +----- + +every surface must be divided into at least two patches each axis + +*/ + +patch_t *face_patches[MAX_MAP_FACES]; +entity_t *face_entity[MAX_MAP_FACES]; +patch_t patches[MAX_PATCHES]; +unsigned num_patches; + +vec3_t radiosity[MAX_PATCHES]; // light leaving a patch +vec3_t illumination[MAX_PATCHES]; // light arriving at a patch + +vec3_t face_offset[MAX_MAP_FACES]; // for rotating bmodels +dplane_t backplanes[MAX_MAP_PLANES]; + +char inbase[32], outbase[32]; + +int fakeplanes; // created planes for origin offset + +int numbounce = 8; +qboolean extrasamples; + +float subdiv = 64; +qboolean dumppatches; + +void BuildLightmaps (void); +int TestLine (vec3_t start, vec3_t stop); + +int junk; + +float ambient = 0; +float maxlight = 196; + +float lightscale = 1.0; + +qboolean glview; + +qboolean nopvs; + +char source[1024]; + +float direct_scale = 0.4; +float entity_scale = 1.0; + +/* +=================================================================== + +MISC + +=================================================================== +*/ + + +/* +============= +MakeBackplanes +============= +*/ +void MakeBackplanes (void) +{ + int i; + + for (i=0 ; ichildren[i]; + if (j < 0) + leafparents[-j - 1] = nodenum; + else + MakeParents (j, nodenum); + } +} + + +/* +=================================================================== + +TRANSFER SCALES + +=================================================================== +*/ + +int PointInLeafnum (vec3_t point) +{ + int nodenum; + vec_t dist; + dnode_t *node; + dplane_t *plane; + + nodenum = 0; + while (nodenum >= 0) + { + node = &dnodes[nodenum]; + plane = &dplanes[node->planenum]; + dist = DotProduct (point, plane->normal) - plane->dist; + if (dist > 0) + nodenum = node->children[0]; + else + nodenum = node->children[1]; + } + + return -nodenum - 1; +} + + +dleaf_t *Rad_PointInLeaf (vec3_t point) +{ + int num; + + num = PointInLeafnum (point); + return &dleafs[num]; +} + + +qboolean PvsForOrigin (vec3_t org, byte *pvs) +{ + dleaf_t *leaf; + + if (!visdatasize) + { + memset (pvs, 255, (numleafs+7)/8 ); + return true; + } + + leaf = Rad_PointInLeaf (org); + if (leaf->cluster == -1) + return false; // in solid leaf + + DecompressVis (dvisdata + dvis->bitofs[leaf->cluster][DVIS_PVS], pvs); + return true; +} + + +/* +============= +MakeTransfers + +============= +*/ +int total_transfer; + +void MakeTransfers (int i) +{ + int j; + vec3_t delta; + vec_t dist, scale; + float trans; + int itrans; + patch_t *patch, *patch2; + float total; + dplane_t plane; + vec3_t origin; + float transfers[MAX_PATCHES], *all_transfers; + int s; + int itotal; + byte pvs[(MAX_MAP_LEAFS+7)/8]; + int cluster; + + patch = patches + i; + total = 0; + + VectorCopy (patch->origin, origin); + plane = *patch->plane; + + if (!PvsForOrigin (patch->origin, pvs)) + return; + + // find out which patch2s will collect light + // from patch + + all_transfers = transfers; + patch->numtransfers = 0; + for (j=0, patch2 = patches ; jcluster; + if (cluster == -1) + continue; + if ( ! ( pvs[cluster>>3] & (1<<(cluster&7)) ) ) + continue; // not in pvs + } + + // calculate vector + VectorSubtract (patch2->origin, origin, delta); + dist = VectorNormalize (delta, delta); + if (!dist) + continue; // should never happen + + // reletive angles + scale = DotProduct (delta, plane.normal); + scale *= -DotProduct (delta, patch2->plane->normal); + if (scale <= 0) + continue; + + // check exact tramsfer + if (TestLine_r (0, patch->origin, patch2->origin) ) + continue; + + trans = scale * patch2->area / (dist*dist); + + if (trans < 0) + trans = 0; // rounding errors... + + transfers[j] = trans; + if (trans > 0) + { + total += trans; + patch->numtransfers++; + } + } + + // copy the transfers out and normalize + // total should be somewhere near PI if everything went right + // because partial occlusion isn't accounted for, and nearby + // patches have underestimated form factors, it will usually + // be higher than PI + if (patch->numtransfers) + { + transfer_t *t; + + if (patch->numtransfers < 0 || patch->numtransfers > MAX_PATCHES) + Error ("Weird numtransfers"); + s = patch->numtransfers * sizeof(transfer_t); + patch->transfers = malloc (s); + if (!patch->transfers) + Error ("Memory allocation failure"); + + // + // normalize all transfers so all of the light + // is transfered to the surroundings + // + t = patch->transfers; + itotal = 0; + for (j=0 ; jtransfer = itrans; + t->patch = j; + t++; + } + } + + // don't bother locking around this. not that important. + total_transfer += patch->numtransfers; +} + + +/* +============= +FreeTransfers +============= +*/ +void FreeTransfers (void) +{ + int i; + + for (i=0 ; iwinding; + fprintf (out, "%i\n", w->numpoints); + for (i=0 ; inumpoints ; i++) + { + fprintf (out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + patch->totallight[0], + patch->totallight[1], + patch->totallight[2]); + } + fprintf (out, "\n"); + } + + fclose (out); +} + +/* +============= +WriteGlView +============= +*/ +void WriteGlView (void) +{ + char name[1024]; + FILE *f; + int i, j; + patch_t *p; + winding_t *w; + + strcpy (name, source); + StripExtension (name); + strcat (name, ".glr"); + + f = fopen (name, "w"); + if (!f) + Error ("Couldn't open %s", f); + + for (j=0 ; jwinding; + fprintf (f, "%i\n", w->numpoints); + for (i=0 ; inumpoints ; i++) + { + fprintf (f, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + p->totallight[0]/128, + p->totallight[1]/128, + p->totallight[2]/128); + } + fprintf (f, "\n"); + } + + fclose (f); +} + + +//============================================================== + +/* +============= +CollectLight +============= +*/ +float CollectLight (void) +{ + int i, j; + patch_t *patch; + vec_t total; + + total = 0; + + for (i=0, patch=patches ; isky) + { + VectorClear (radiosity[i]); + VectorClear (illumination[i]); + continue; + } + + for (j=0 ; j<3 ; j++) + { + patch->totallight[j] += illumination[i][j] / patch->area; + radiosity[i][j] = illumination[i][j] * patch->reflectivity[j]; + } + + total += radiosity[i][0] + radiosity[i][1] + radiosity[i][2]; + VectorClear (illumination[i]); + } + + return total; +} + + +/* +============= +ShootLight + +Send light out to other patches + Run multi-threaded +============= +*/ +void ShootLight (int patchnum) +{ + int k, l; + transfer_t *trans; + int num; + patch_t *patch; + vec3_t send; + + // this is the amount of light we are distributing + // prescale it so that multiplying by the 16 bit + // transfer values gives a proper output value + for (k=0 ; k<3 ; k++) + send[k] = radiosity[patchnum][k] / 0x10000; + patch = &patches[patchnum]; + + trans = patch->transfers; + num = patch->numtransfers; + + for (k=0 ; kpatch][l] += send[l]*trans->transfer; + } +} + +/* +============= +BounceLight +============= +*/ +void BounceLight (void) +{ + int i, j; + float added; + char name[64]; + patch_t *p; + + for (i=0 ; itotallight[j] = p->samplelight[j]; + radiosity[i][j] = p->samplelight[j] * p->reflectivity[j] * p->area; + } + } + + for (i=0 ; itotallight[0] < 0 || patch->totallight[1] < 0 || patch->totallight[2] < 0) + Error ("negative patch totallight\n"); + } +} + +/* +============= +RadWorld +============= +*/ +void RadWorld (void) +{ + if (numnodes == 0 || numfaces == 0) + Error ("Empty map"); + MakeBackplanes (); + MakeParents (0, -1); + MakeTnodes (&dmodels[0]); + + // turn each face into a single patch + MakePatches (); + + // subdivide patches to a maximum dimension + SubdividePatches (); + + // create directlights out of patches and lights + CreateDirectLights (); + + // build initial facelights + RunThreadsOnIndividual (numfaces, true, BuildFacelights); + + if (numbounce > 0) + { + // build transfer lists + RunThreadsOnIndividual (num_patches, true, MakeTransfers); + Sys_FPrintf( SYS_VRB, "transfer lists: %5.1f megs\n" + , (float)total_transfer * sizeof(transfer_t) / (1024*1024)); + + // spread light around + BounceLight (); + + FreeTransfers (); + + CheckPatches (); + } + + if (glview) + WriteGlView (); + + // blend bounced light into direct light and save + PairEdges (); + LinkPlaneFaces (); + + lightdatasize = 0; + RunThreadsOnIndividual (numfaces, true, FinalLightFace); +} + + +/* +======== +main + +light modelfile +======== +*/ +int RAD_Main () +{ + double start, end; + char name[1024]; + int total_rad_time; + + Sys_Printf ("\n----- RAD ----\n\n"); + + if (maxlight > 255) + maxlight = 255; + + start = I_FloatTime (); + + if ( !strcmp( game, "heretic2" ) ) + CalcTextureReflectivity = &CalcTextureReflectivity_Heretic2; + else + CalcTextureReflectivity = &CalcTextureReflectivity_Quake2; + + SetQdirFromPath (mapname); + strcpy (source, ExpandArg(mapname)); + StripExtension (source); + DefaultExtension (source, ".bsp"); + +// ReadLightFile (); + + sprintf (name, "%s%s", inbase, source); + Sys_Printf ("reading %s\n", name); + LoadBSPFile (name); + ParseEntities (); + (*CalcTextureReflectivity) (); + + if (!visdatasize) + { + Sys_Printf ("No vis information, direct lighting only.\n"); + numbounce = 0; + ambient = 0.1; + } + + RadWorld (); + + sprintf (name, "%s%s", outbase, source); + Sys_Printf ("writing %s\n", name); + WriteBSPFile (name); + + end = I_FloatTime (); + total_rad_time = (int) (end-start); + Sys_Printf("\nRAD Time: "); + if ( total_rad_time > 59 ) + Sys_Printf("%d Minutes ", total_rad_time/60 ); + Sys_Printf( "%d Seconds\n", total_rad_time%60 ); + + + return 0; +} + diff --git a/tools/quake2/q2map/qrad.h b/tools/quake2/q2map/qrad.h new file mode 100644 index 00000000..eda18919 --- /dev/null +++ b/tools/quake2/q2map/qrad.h @@ -0,0 +1,184 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/* Files: + +lightmap.c +patches.c +qrad.c +trace.c + +*/ + + +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" +#include "polylib.h" +#include "q2_threads.h" +#include "lbmlib.h" +#include "inout.h" + +#ifdef WIN32 +#include +#endif + +#ifdef WIN32 + #ifdef NDEBUG // Don't show in a Release build + #pragma warning(disable : 4305) // truncate from double to float + #pragma warning(disable : 4244) // conversion from double to float + #pragma warning(disable : 4018) // signed/unsigned mismatch + #endif +#endif + +typedef enum +{ + emit_surface, + emit_point, + emit_spotlight +} emittype_t; + + + +typedef struct directlight_s +{ + struct directlight_s *next; + emittype_t type; + + float intensity; + int style; + vec3_t origin; + vec3_t color; + vec3_t normal; // for surfaces and spotlights + float stopdot; // for spotlights +} directlight_t; + + +// the sum of all tranfer->transfer values for a given patch +// should equal exactly 0x10000, showing that all radiance +// reaches other patches +typedef struct +{ + unsigned short patch; + unsigned short transfer; +} transfer_t; + + +#define MAX_PATCHES 65000 // larger will cause 32 bit overflows + +typedef struct patch_s +{ + winding_t *winding; + struct patch_s *next; // next in face + int numtransfers; + transfer_t *transfers; + + int cluster; // for pvs checking + vec3_t origin; + dplane_t *plane; + + qboolean sky; + + vec3_t totallight; // accumulated by radiosity + // does NOT include light + // accounted for by direct lighting + float area; + + // illuminance * reflectivity = radiosity + vec3_t reflectivity; + vec3_t baselight; // emissivity only + + // each style 0 lightmap sample in the patch will be + // added up to get the average illuminance of the entire patch + vec3_t samplelight; + int samples; // for averaging direct light +} patch_t; + +extern patch_t *face_patches[MAX_MAP_FACES]; +extern entity_t *face_entity[MAX_MAP_FACES]; +extern vec3_t face_offset[MAX_MAP_FACES]; // for rotating bmodels +extern patch_t patches[MAX_PATCHES]; +extern unsigned num_patches; + +extern int leafparents[MAX_MAP_LEAFS]; +extern int nodeparents[MAX_MAP_NODES]; + +extern float lightscale; + + +void MakeShadowSplits (void); + +//============================================== + + +void BuildVisMatrix (void); +qboolean CheckVisBit (unsigned p1, unsigned p2); + +//============================================== + +extern float ambient, maxlight; + +void LinkPlaneFaces (void); + +extern qboolean extrasamples; +extern int numbounce; + +extern directlight_t *directlights[MAX_MAP_LEAFS]; + +extern byte nodehit[MAX_MAP_NODES]; + +void BuildLightmaps (void); + +void BuildFacelights (int facenum); + +void FinalLightFace (int facenum); + +qboolean PvsForOrigin (vec3_t org, byte *pvs); + +int TestLine_r (int node, vec3_t start, vec3_t stop); + +void CreateDirectLights (void); + +dleaf_t *Rad_PointInLeaf (vec3_t point); + + +extern dplane_t backplanes[MAX_MAP_PLANES]; +extern int fakeplanes; // created planes for origin offset + +extern float subdiv; + +extern float direct_scale; +extern float entity_scale; + +int PointInLeafnum (vec3_t point); +void MakeTnodes (dmodel_t *bm); +void MakePatches (void); +void SubdividePatches (void); +void PairEdges (void); +void (*CalcTextureReflectivity) (void); +void CalcTextureReflectivity_Quake2(void); +void CalcTextureReflectivity_Heretic2(void); + +//============================================================================= + +// externs + +extern char *mapname; +extern char game[64]; diff --git a/tools/quake2/q2map/qvis.c b/tools/quake2/q2map/qvis.c new file mode 100644 index 00000000..7785ebe5 --- /dev/null +++ b/tools/quake2/q2map/qvis.c @@ -0,0 +1,581 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// qvis.c + +#include "qvis.h" +#include "q2_threads.h" +#include "stdlib.h" + +int numportals; +int portalclusters; + +char inbase[32]; +char outbase[32]; + +portal_t *portals; +leaf_t *leafs; + +int c_portaltest, c_portalpass, c_portalcheck; + +byte *uncompressedvis; + +byte *vismap, *vismap_p, *vismap_end; // past visfile +int originalvismapsize; + +int leafbytes; // (portalclusters+63)>>3 +int leaflongs; + +int portalbytes, portallongs; + +qboolean fastvis; +qboolean nosort; + +int testlevel = 2; + +int totalvis; + +portal_t *sorted_portals[MAX_MAP_PORTALS*2]; + + +//============================================================================= + +void PlaneFromWinding (winding_t *w, plane_t *plane) +{ + vec3_t v1, v2; + +// calc plane + VectorSubtract (w->points[2], w->points[1], v1); + VectorSubtract (w->points[0], w->points[1], v2); + CrossProduct (v2, v1, plane->normal); + VectorNormalize (plane->normal, plane->normal); + plane->dist = DotProduct (w->points[0], plane->normal); +} + + +/* +================== +NewWinding +================== +*/ +winding_t *NewWinding (int points) +{ + winding_t *w; + int size; + + if (points > MAX_POINTS_ON_WINDING) + Error ("NewWinding: %i points", points); + + size = (int)((winding_t *)0)->points[points]; + w = malloc (size); + memset (w, 0, size); + + return w; +} + + +/* +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + Sys_Printf ("(%5.1f, %5.1f, %5.1f)\n",w->points[i][0], w->points[i][1],w->points[i][2]); +} +*/ +void prl(leaf_t *l) +{ + int i; + portal_t *p; + plane_t pl; + + for (i=0 ; inumportals ; i++) + { + p = l->portals[i]; + pl = p->plane; + Sys_Printf ("portal %4i to leaf %4i : %7.1f : (%4.1f, %4.1f, %4.1f)\n",(int)(p-portals),p->leaf,pl.dist, pl.normal[0], pl.normal[1], pl.normal[2]); + } +} + + +//============================================================================= + +/* +============= +SortPortals + +Sorts the portals from the least complex, so the later ones can reuse +the earlier information. +============= +*/ +int PComp (const void *a, const void *b) +{ + if ( (*(portal_t **)a)->nummightsee == (*(portal_t **)b)->nummightsee) + return 0; + if ( (*(portal_t **)a)->nummightsee < (*(portal_t **)b)->nummightsee) + return -1; + return 1; +} +void SortPortals (void) +{ + int i; + + for (i=0 ; i>3] & (1<<(i&7)) ) + { + p = portals+i; + leafbits[p->leaf>>3] |= (1<<(p->leaf&7)); + } + } + + c_leafs = CountBits (leafbits, portalclusters); + + return c_leafs; +} + + +/* +=============== +ClusterMerge + +Merges the portal visibility for a leaf +=============== +*/ +void ClusterMerge (int leafnum) +{ + leaf_t *leaf; + byte portalvector[MAX_PORTALS/8]; + byte uncompressed[MAX_MAP_LEAFS/8]; + byte compressed[MAX_MAP_LEAFS/8]; + int i, j; + int numvis; + byte *dest; + portal_t *p; + int pnum; + + // OR together all the portalvis bits + + memset (portalvector, 0, portalbytes); + leaf = &leafs[leafnum]; + for (i=0 ; inumportals ; i++) + { + p = leaf->portals[i]; + if (p->status != stat_done) + Error ("portal not done"); + for (j=0 ; jportalvis)[j]; + pnum = p - portals; + portalvector[pnum>>3] |= 1<<(pnum&7); + } + + // convert portal bits to leaf bits + numvis = LeafVectorFromPortalVector (portalvector, uncompressed); + + if (uncompressed[leafnum>>3] & (1<<(leafnum&7))) + Sys_Printf ("WARNING: Leaf portals saw into leaf\n"); + + uncompressed[leafnum>>3] |= (1<<(leafnum&7)); + numvis++; // count the leaf itself + + // save uncompressed for PHS calculation + memcpy (uncompressedvis + leafnum*leafbytes, uncompressed, leafbytes); + +// +// compress the bit string +// + Sys_FPrintf( SYS_VRB, "cluster %4i : %4i visible\n", leafnum, numvis); + totalvis += numvis; + + i = CompressVis (uncompressed, compressed); + + dest = vismap_p; + vismap_p += i; + + if (vismap_p > vismap_end) + Error ("Vismap expansion overflow"); + + dvis->bitofs[leafnum][DVIS_PVS] = dest-vismap; + + memcpy (dest, compressed, i); +} + + +/* +================== +CalcPortalVis +================== +*/ +void CalcPortalVis (void) +{ + int i; + +// fastvis just uses mightsee for a very loose bound + if (fastvis) + { + for (i=0 ; iwinding; + VectorCopy (vec3_origin, total); + for (i=0 ; inumpoints ; i++) + { + VectorAdd (total, w->points[i], total); + } + + for (i=0 ; i<3 ; i++) + total[i] /= w->numpoints; + + bestr = 0; + for (i=0 ; inumpoints ; i++) + { + VectorSubtract (w->points[i], total, dist); + r = VectorLength (dist); + if (r > bestr) + bestr = r; + } + VectorCopy (total, p->origin); + p->radius = bestr; +} + +/* +============ +LoadPortals +============ +*/ +void LoadPortals (char *name) +{ + int i, j; + portal_t *p; + leaf_t *l; + char magic[80]; + FILE *f; + int numpoints; + winding_t *w; + int leafnums[2]; + plane_t plane; + + if (!strcmp(name,"-")) + f = stdin; + else + { + f = fopen(name, "r"); + if (!f) + Error ("LoadPortals: couldn't read %s\n",name); + } + + if (fscanf (f,"%79s\n%i\n%i\n",magic, &portalclusters, &numportals) != 3) + Error ("LoadPortals: failed to read header"); + if (strcmp(magic,PORTALFILE)) + Error ("LoadPortals: not a portal file"); + + Sys_Printf ("%4i portalclusters\n", portalclusters); + Sys_Printf ("%4i numportals\n", numportals); + + // these counts should take advantage of 64 bit systems automatically + leafbytes = ((portalclusters+63)&~63)>>3; + leaflongs = leafbytes/sizeof(long); + + portalbytes = ((numportals*2+63)&~63)>>3; + portallongs = portalbytes/sizeof(long); + +// each file portal is split into two memory portals + portals = malloc(2*numportals*sizeof(portal_t)); + memset (portals, 0, 2*numportals*sizeof(portal_t)); + + leafs = malloc(portalclusters*sizeof(leaf_t)); + memset (leafs, 0, portalclusters*sizeof(leaf_t)); + + originalvismapsize = portalclusters*leafbytes; + uncompressedvis = malloc(originalvismapsize); + + vismap = vismap_p = dvisdata; + dvis->numclusters = portalclusters; + vismap_p = (byte *)&dvis->bitofs[portalclusters]; + + vismap_end = vismap + MAX_MAP_VISIBILITY; + + for (i=0, p=portals ; i MAX_POINTS_ON_WINDING) + Error ("LoadPortals: portal %i has too many points", i); + if ( (unsigned)leafnums[0] > portalclusters + || (unsigned)leafnums[1] > portalclusters) + Error ("LoadPortals: reading portal %i", i); + + w = p->winding = NewWinding (numpoints); + w->original = true; + w->numpoints = numpoints; + + for (j=0 ; jpoints[j][k] = v[k]; + } + fscanf (f, "\n"); + + // calc plane + PlaneFromWinding (w, &plane); + + // create forward portal + l = &leafs[leafnums[0]]; + if (l->numportals == MAX_PORTALS_ON_LEAF) + Error ("Leaf with too many portals"); + l->portals[l->numportals] = p; + l->numportals++; + + p->winding = w; + VectorSubtract (vec3_origin, plane.normal, p->plane.normal); + p->plane.dist = -plane.dist; + p->leaf = leafnums[1]; + SetPortalSphere (p); + p++; + + // create backwards portal + l = &leafs[leafnums[1]]; + if (l->numportals == MAX_PORTALS_ON_LEAF) + Error ("Leaf with too many portals"); + l->portals[l->numportals] = p; + l->numportals++; + + p->winding = NewWinding(w->numpoints); + p->winding->numpoints = w->numpoints; + for (j=0 ; jnumpoints ; j++) + { + VectorCopy (w->points[w->numpoints-1-j], p->winding->points[j]); + } + + p->plane = plane; + p->leaf = leafnums[0]; + SetPortalSphere (p); + p++; + + } + + fclose (f); +} + + +/* +================ +CalcPHS + +Calculate the PHS (Potentially Hearable Set) +by ORing together all the PVS visible from a leaf +================ +*/ +void CalcPHS (void) +{ + int i, j, k, l, index; + int bitbyte; + long *dest, *src; + byte *scan; + int count; + byte uncompressed[MAX_MAP_LEAFS/8]; + byte compressed[MAX_MAP_LEAFS/8]; + + Sys_Printf ("Building PHS...\n"); + + count = 0; + for (i=0 ; i= portalclusters) + Error ("Bad bit in PVS"); // pad bits should be 0 + src = (long *)(uncompressedvis + index*leafbytes); + dest = (long *)uncompressed; + for (l=0 ; l>3] & (1<<(j&7)) ) + count++; + + // + // compress the bit string + // + j = CompressVis (uncompressed, compressed); + + dest = (long *)vismap_p; + vismap_p += j; + + if (vismap_p > vismap_end) + Error ("Vismap expansion overflow"); + + dvis->bitofs[i][DVIS_PHS] = (byte *)dest-vismap; + + memcpy (dest, compressed, j); + } + + Sys_Printf ("Average clusters hearable: %i\n", count/portalclusters); +} + +/* +=========== +main +=========== +*/ +int VIS_Main () +{ + char portalfile[1024]; + char source[1024]; + char name[1024]; + double start, end; + int total_vis_time; + + Sys_Printf ("\n----- VIS ----\n\n"); + + //if (i != argc - 1) + // Error ("usage: vis [-threads #] [-level 0-4] [-fast] [-v] bspfile"); + + start = I_FloatTime (); + + ThreadSetDefault (); + + SetQdirFromPath (mapname); + strcpy (source, ExpandArg(mapname)); + StripExtension (source); + DefaultExtension (source, ".bsp"); + + sprintf (name, "%s%s", inbase, source); + Sys_Printf ("reading %s\n", name); + LoadBSPFile (name); + if (numnodes == 0 || numfaces == 0) + Error ("Empty map"); + + sprintf (portalfile, "%s%s", inbase, ExpandArg(mapname)); + StripExtension (portalfile); + strcat (portalfile, ".prt"); + + Sys_Printf ("reading %s\n", portalfile); + LoadPortals (portalfile); + + CalcVis (); + + CalcPHS (); + + visdatasize = vismap_p - dvisdata; + Sys_Printf ("visdatasize:%i compressed from %i\n", visdatasize, originalvismapsize*2); + + sprintf (name, "%s%s", outbase, source); + Sys_Printf ("writing %s\n", name); + WriteBSPFile (name); + + end = I_FloatTime (); + total_vis_time = (int) (end-start); + Sys_Printf("\nVIS Time: "); + if ( total_vis_time > 59 ) + Sys_Printf("%d Minutes ", total_vis_time/60 ); + Sys_Printf( "%d Seconds\n", total_vis_time%60 ); + + + return 0; +} + diff --git a/tools/quake2/q2map/qvis.h b/tools/quake2/q2map/qvis.h new file mode 100644 index 00000000..3f2306fb --- /dev/null +++ b/tools/quake2/q2map/qvis.h @@ -0,0 +1,169 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/* Files: + +flow.c +qvis3.c + +*/ + +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" +#include "inout.h" + +#ifdef WIN32 + #ifdef NDEBUG // Don't show in a Release build + #pragma warning(disable : 4305) // truncate from double to float + #pragma warning(disable : 4244) // conversion from double to float + #pragma warning(disable : 4018) // signed/unsigned mismatch + #endif +#endif + +#define MAX_PORTALS 32768 + +#define PORTALFILE "PRT1" + +#define ON_EPSILON 0.1 + +typedef struct +{ + vec3_t normal; + float dist; +} plane_t; + +#define MAX_POINTS_ON_WINDING 64 +#define MAX_POINTS_ON_FIXED_WINDING 12 + +typedef struct +{ + qboolean original; // don't free, it's part of the portal + int numpoints; + vec3_t points[MAX_POINTS_ON_FIXED_WINDING]; // variable sized +} winding_t; + +winding_t *NewWinding (int points); +void FreeWinding (winding_t *w); +winding_t *CopyWinding (winding_t *w); + + +typedef enum {stat_none, stat_working, stat_done} vstatus_t; +typedef struct +{ + plane_t plane; // normal pointing into neighbor + int leaf; // neighbor + + vec3_t origin; // for fast clip testing + float radius; + + winding_t *winding; + vstatus_t status; + byte *portalfront; // [portals], preliminary + byte *portalflood; // [portals], intermediate + byte *portalvis; // [portals], final + + int nummightsee; // bit count on portalflood for sort +} portal_t; + +typedef struct seperating_plane_s +{ + struct seperating_plane_s *next; + plane_t plane; // from portal is on positive side +} sep_t; + + +typedef struct passage_s +{ + struct passage_s *next; + int from, to; // leaf numbers + sep_t *planes; +} passage_t; + +#define MAX_PORTALS_ON_LEAF 128 +typedef struct leaf_s +{ + int numportals; + passage_t *passages; + portal_t *portals[MAX_PORTALS_ON_LEAF]; +} leaf_t; + + +typedef struct pstack_s +{ + byte mightsee[MAX_PORTALS/8]; // bit string + struct pstack_s *next; + leaf_t *leaf; + portal_t *portal; // portal exiting + winding_t *source; + winding_t *pass; + + winding_t windings[3]; // source, pass, temp in any order + int freewindings[3]; + + plane_t portalplane; +} pstack_t; + +typedef struct +{ + portal_t *base; + int c_chains; + pstack_t pstack_head; +} threaddata_t; + + + +extern int numportals; +extern int portalclusters; + +extern portal_t *portals; +extern leaf_t *leafs; + +extern int c_portaltest, c_portalpass, c_portalcheck; +extern int c_portalskip, c_leafskip; +extern int c_vistest, c_mighttest; +extern int c_chains; + +extern byte *vismap, *vismap_p, *vismap_end; // past visfile + +extern int testlevel; + +extern byte *uncompressed; + +extern int leafbytes, leaflongs; +extern int portalbytes, portallongs; + + +void LeafFlow (int leafnum); + + +void BasePortalVis (int portalnum); +void BetterPortalVis (int portalnum); +void PortalFlow (int portalnum); + +extern portal_t *sorted_portals[MAX_MAP_PORTALS*2]; + +int CountBits (byte *bits, int numbits); + +//============================================================================= + +// externs + +extern char *mapname; diff --git a/tools/quake2/q2map/textures.c b/tools/quake2/q2map/textures.c new file mode 100644 index 00000000..503d7a4d --- /dev/null +++ b/tools/quake2/q2map/textures.c @@ -0,0 +1,249 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qbsp.h" + +int nummiptex; +textureref_t textureref[MAX_MAP_TEXTURES]; + +//========================================================================== + + +int FindMiptex (char *name) +{ + int i; + char path[1024]; + miptex_t *mt; + miptex_m8_t *mt_m8; + miptex_m32_t *mt_m32; + + for (i=0 ; ivalue); + textureref[i].flags = LittleLong (mt_m32->flags); + textureref[i].contents = LittleLong (mt_m32->contents); + strcpy (textureref[i].animname, mt_m32->animname); + free (mt_m32); + } + else + sprintf (path, "%stextures/%s.m8", gamedir, name); + + if (TryLoadFile (path, (void **)&mt_m8) != -1) + { + textureref[i].value = LittleLong (mt_m8->value); + textureref[i].flags = LittleLong (mt_m8->flags); + textureref[i].contents = LittleLong (mt_m8->contents); + strcpy (textureref[i].animname, mt_m8->animname); + free (mt_m8); + } + } + else + { + sprintf (path, "%stextures/%s.wal", gamedir, name); + if (TryLoadFile (path, (void **)&mt) != -1) + { + textureref[i].value = LittleLong (mt->value); + textureref[i].flags = LittleLong (mt->flags); + textureref[i].contents = LittleLong (mt->contents); + strcpy (textureref[i].animname, mt->animname); + free (mt); + } + } + + nummiptex++; + + if (textureref[i].animname[0]) + FindMiptex (textureref[i].animname); + + return i; +} + + +/* +================== +textureAxisFromPlane +================== +*/ +vec3_t baseaxis[18] = +{ +{0,0,1}, {1,0,0}, {0,-1,0}, // floor +{0,0,-1}, {1,0,0}, {0,-1,0}, // ceiling +{1,0,0}, {0,1,0}, {0,0,-1}, // west wall +{-1,0,0}, {0,1,0}, {0,0,-1}, // east wall +{0,1,0}, {1,0,0}, {0,0,-1}, // south wall +{0,-1,0}, {1,0,0}, {0,0,-1} // north wall +}; + +void TextureAxisFromPlane(plane_t *pln, vec3_t xv, vec3_t yv) +{ + int bestaxis; + vec_t dot,best; + int i; + + best = 0; + bestaxis = 0; + + for (i=0 ; i<6 ; i++) + { + dot = DotProduct (pln->normal, baseaxis[i*3]); + if (dot > best) + { + best = dot; + bestaxis = i; + } + } + + VectorCopy (baseaxis[bestaxis*3+1], xv); + VectorCopy (baseaxis[bestaxis*3+2], yv); +} + + + + +int TexinfoForBrushTexture (plane_t *plane, brush_texture_t *bt, vec3_t origin) +{ + vec3_t vecs[2]; + int sv, tv; + vec_t ang, sinv, cosv; + vec_t ns, nt; + texinfo_t tx, *tc; + int i, j, k; + float shift[2]; + brush_texture_t anim; + int mt; + + if (!bt->name[0]) + return 0; + + memset (&tx, 0, sizeof(tx)); + strcpy (tx.texture, bt->name); + + TextureAxisFromPlane(plane, vecs[0], vecs[1]); + + shift[0] = DotProduct (origin, vecs[0]); + shift[1] = DotProduct (origin, vecs[1]); + + if (!bt->scale[0]) + bt->scale[0] = 1; + if (!bt->scale[1]) + bt->scale[1] = 1; + + +// rotate axis + if (bt->rotate == 0) + { sinv = 0 ; cosv = 1; } + else if (bt->rotate == 90) + { sinv = 1 ; cosv = 0; } + else if (bt->rotate == 180) + { sinv = 0 ; cosv = -1; } + else if (bt->rotate == 270) + { sinv = -1 ; cosv = 0; } + else + { + ang = bt->rotate / 180 * Q_PI; + sinv = sin(ang); + cosv = cos(ang); + } + + if (vecs[0][0]) + sv = 0; + else if (vecs[0][1]) + sv = 1; + else + sv = 2; + + if (vecs[1][0]) + tv = 0; + else if (vecs[1][1]) + tv = 1; + else + tv = 2; + + for (i=0 ; i<2 ; i++) + { + ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; + nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; + vecs[i][sv] = ns; + vecs[i][tv] = nt; + } + + for (i=0 ; i<2 ; i++) + for (j=0 ; j<3 ; j++) + tx.vecs[i][j] = vecs[i][j] / bt->scale[i]; + + tx.vecs[0][3] = bt->shift[0] + shift[0]; + tx.vecs[1][3] = bt->shift[1] + shift[1]; + tx.flags = bt->flags; + tx.value = bt->value; + + // + // find the texinfo + // + tc = texinfo; + for (i=0 ; iflags != tx.flags) + continue; + if (tc->value != tx.value) + continue; + for (j=0 ; j<2 ; j++) + { + if (strcmp (tc->texture, tx.texture)) + goto skip; + for (k=0 ; k<4 ; k++) + { + if (tc->vecs[j][k] != tx.vecs[j][k]) + goto skip; + } + } + return i; +skip:; + } + *tc = tx; + numtexinfo++; + + // load the next animation + mt = FindMiptex (bt->name); + if (textureref[mt].animname[0]) + { + anim = *bt; + strcpy (anim.name, textureref[mt].animname); + tc->nexttexinfo = TexinfoForBrushTexture (plane, &anim, origin); + } + else + tc->nexttexinfo = -1; + + + return i; +} diff --git a/tools/quake2/q2map/trace.c b/tools/quake2/q2map/trace.c new file mode 100644 index 00000000..b9392b17 --- /dev/null +++ b/tools/quake2/q2map/trace.c @@ -0,0 +1,298 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +// trace.c +/* +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" +*/ + +#include "qrad.h" + +#define ON_EPSILON 0.1 + +typedef struct tnode_s +{ + int type; + vec3_t normal; + float dist; + int children[2]; + int pad; +} tnode_t; + +tnode_t *tnodes, *tnode_p; + +/* +============== +MakeTnode + +Converts the disk node structure into the efficient tracing structure +============== +*/ +void MakeTnode (int nodenum) +{ + tnode_t *t; + dplane_t *plane; + int i; + dnode_t *node; + + t = tnode_p++; + + node = dnodes + nodenum; + plane = dplanes + node->planenum; + + t->type = plane->type; + VectorCopy (plane->normal, t->normal); + t->dist = plane->dist; + + for (i=0 ; i<2 ; i++) + { + if (node->children[i] < 0) + t->children[i] = (dleafs[-node->children[i] - 1].contents & CONTENTS_SOLID) | (1<<31); + else + { + t->children[i] = tnode_p - tnodes; + MakeTnode (node->children[i]); + } + } + +} + + +/* +============= +MakeTnodes + +Loads the node structure out of a .bsp file to be used for light occlusion +============= +*/ +void MakeTnodes (dmodel_t *bm) +{ + // 32 byte align the structs + tnodes = malloc( (numnodes+1) * sizeof(tnode_t)); + tnodes = (tnode_t *)(((int)tnodes + 31)&~31); + tnode_p = tnodes; + + MakeTnode (0); +} + + +//========================================================== + + +int TestLine_r (int node, vec3_t start, vec3_t stop) +{ + tnode_t *tnode; + float front, back; + vec3_t mid; + float frac; + int side; + int r; + + if (node & (1<<31)) + return node & ~(1<<31); // leaf node + + tnode = &tnodes[node]; + switch (tnode->type) + { + case PLANE_X: + front = start[0] - tnode->dist; + back = stop[0] - tnode->dist; + break; + case PLANE_Y: + front = start[1] - tnode->dist; + back = stop[1] - tnode->dist; + break; + case PLANE_Z: + front = start[2] - tnode->dist; + back = stop[2] - tnode->dist; + break; + default: + front = (start[0]*tnode->normal[0] + start[1]*tnode->normal[1] + start[2]*tnode->normal[2]) - tnode->dist; + back = (stop[0]*tnode->normal[0] + stop[1]*tnode->normal[1] + stop[2]*tnode->normal[2]) - tnode->dist; + break; + } + + if (front >= -ON_EPSILON && back >= -ON_EPSILON) + return TestLine_r (tnode->children[0], start, stop); + + if (front < ON_EPSILON && back < ON_EPSILON) + return TestLine_r (tnode->children[1], start, stop); + + side = front < 0; + + frac = front / (front-back); + + mid[0] = start[0] + (stop[0] - start[0])*frac; + mid[1] = start[1] + (stop[1] - start[1])*frac; + mid[2] = start[2] + (stop[2] - start[2])*frac; + + r = TestLine_r (tnode->children[side], start, mid); + if (r) + return r; + return TestLine_r (tnode->children[!side], mid, stop); +} + +int TestLine (vec3_t start, vec3_t stop) +{ + return TestLine_r (0, start, stop); +} + +/* +============================================================================== + +LINE TRACING + +The major lighting operation is a point to point visibility test, performed +by recursive subdivision of the line by the BSP tree. + +============================================================================== +*/ + +typedef struct +{ + vec3_t backpt; + int side; + int node; +} tracestack_t; + + +/* +============== +TestLine +============== +*/ +qboolean _TestLine (vec3_t start, vec3_t stop) +{ + int node; + float front, back; + tracestack_t *tstack_p; + int side; + float frontx,fronty, frontz, backx, backy, backz; + tracestack_t tracestack[64]; + tnode_t *tnode; + + frontx = start[0]; + fronty = start[1]; + frontz = start[2]; + backx = stop[0]; + backy = stop[1]; + backz = stop[2]; + + tstack_p = tracestack; + node = 0; + + while (1) + { + if (node == CONTENTS_SOLID) + { +#if 0 + float d1, d2, d3; + + d1 = backx - frontx; + d2 = backy - fronty; + d3 = backz - frontz; + + if (d1*d1 + d2*d2 + d3*d3 > 1) +#endif + return false; // DONE! + } + + while (node < 0) + { + // pop up the stack for a back side + tstack_p--; + if (tstack_p < tracestack) + return true; + node = tstack_p->node; + + // set the hit point for this plane + + frontx = backx; + fronty = backy; + frontz = backz; + + // go down the back side + + backx = tstack_p->backpt[0]; + backy = tstack_p->backpt[1]; + backz = tstack_p->backpt[2]; + + node = tnodes[tstack_p->node].children[!tstack_p->side]; + } + + tnode = &tnodes[node]; + + switch (tnode->type) + { + case PLANE_X: + front = frontx - tnode->dist; + back = backx - tnode->dist; + break; + case PLANE_Y: + front = fronty - tnode->dist; + back = backy - tnode->dist; + break; + case PLANE_Z: + front = frontz - tnode->dist; + back = backz - tnode->dist; + break; + default: + front = (frontx*tnode->normal[0] + fronty*tnode->normal[1] + frontz*tnode->normal[2]) - tnode->dist; + back = (backx*tnode->normal[0] + backy*tnode->normal[1] + backz*tnode->normal[2]) - tnode->dist; + break; + } + + if (front > -ON_EPSILON && back > -ON_EPSILON) +// if (front > 0 && back > 0) + { + node = tnode->children[0]; + continue; + } + + if (front < ON_EPSILON && back < ON_EPSILON) +// if (front <= 0 && back <= 0) + { + node = tnode->children[1]; + continue; + } + + side = front < 0; + + front = front / (front-back); + + tstack_p->node = node; + tstack_p->side = side; + tstack_p->backpt[0] = backx; + tstack_p->backpt[1] = backy; + tstack_p->backpt[2] = backz; + + tstack_p++; + + backx = frontx + front*(backx-frontx); + backy = fronty + front*(backy-fronty); + backz = frontz + front*(backz-frontz); + + node = tnode->children[side]; + } +} + + diff --git a/tools/quake2/q2map/tree.c b/tools/quake2/q2map/tree.c new file mode 100644 index 00000000..52d78fa8 --- /dev/null +++ b/tools/quake2/q2map/tree.c @@ -0,0 +1,218 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "qbsp.h" + +extern int c_nodes; + +void RemovePortalFromNode (portal_t *portal, node_t *l); + +node_t *NodeForPoint (node_t *node, vec3_t origin) +{ + plane_t *plane; + vec_t d; + + while (node->planenum != PLANENUM_LEAF) + { + plane = &mapplanes[node->planenum]; + d = DotProduct (origin, plane->normal) - plane->dist; + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + + return node; +} + + + +/* +============= +FreeTreePortals_r +============= +*/ +void FreeTreePortals_r (node_t *node) +{ + portal_t *p, *nextp; + int s; + + // free children + if (node->planenum != PLANENUM_LEAF) + { + FreeTreePortals_r (node->children[0]); + FreeTreePortals_r (node->children[1]); + } + + // free portals + for (p=node->portals ; p ; p=nextp) + { + s = (p->nodes[1] == node); + nextp = p->next[s]; + + RemovePortalFromNode (p, p->nodes[!s]); + FreePortal (p); + } + node->portals = NULL; +} + +/* +============= +FreeTree_r +============= +*/ +void FreeTree_r (node_t *node) +{ + face_t *f, *nextf; + + // free children + if (node->planenum != PLANENUM_LEAF) + { + FreeTree_r (node->children[0]); + FreeTree_r (node->children[1]); + } + + // free bspbrushes + FreeBrushList (node->brushlist); + + // free faces + for (f=node->faces ; f ; f=nextf) + { + nextf = f->next; + FreeFace (f); + } + + // free the node + if (node->volume) + FreeBrush (node->volume); + + if (numthreads == 1) + c_nodes--; + free (node); +} + + +/* +============= +FreeTree +============= +*/ +void FreeTree (tree_t *tree) +{ + FreeTreePortals_r (tree->headnode); + FreeTree_r (tree->headnode); + free (tree); +} + +//=============================================================== + +void PrintTree_r (node_t *node, int depth) +{ + int i; + plane_t *plane; + bspbrush_t *bb; + + for (i=0 ; iplanenum == PLANENUM_LEAF) + { + if (!node->brushlist) + Sys_Printf ("NULL\n"); + else + { + for (bb=node->brushlist ; bb ; bb=bb->next) + Sys_Printf ("%i ", bb->original->brushnum); + Sys_Printf ("\n"); + } + return; + } + + plane = &mapplanes[node->planenum]; + Sys_Printf ("#%i (%5.2f %5.2f %5.2f):%5.2f\n", node->planenum, + plane->normal[0], plane->normal[1], plane->normal[2], + plane->dist); + PrintTree_r (node->children[0], depth+1); + PrintTree_r (node->children[1], depth+1); +} + +/* +========================================================= + +NODES THAT DON'T SEPERATE DIFFERENT CONTENTS CAN BE PRUNED + +========================================================= +*/ + +int c_pruned; + +/* +============ +PruneNodes_r +============ +*/ +void PruneNodes_r (node_t *node) +{ + bspbrush_t *b, *next; + + if (node->planenum == PLANENUM_LEAF) + return; + PruneNodes_r (node->children[0]); + PruneNodes_r (node->children[1]); + + if ( (node->children[0]->contents & CONTENTS_SOLID) + && (node->children[1]->contents & CONTENTS_SOLID) ) + { + if (node->faces) + Error ("node->faces seperating CONTENTS_SOLID"); + if (node->children[0]->faces || node->children[1]->faces) + Error ("!node->faces with children"); + + // FIXME: free stuff + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + node->detail_seperator = false; + + if (node->brushlist) + Error ("PruneNodes: node->brushlist"); + + // combine brush lists + node->brushlist = node->children[1]->brushlist; + + for (b=node->children[0]->brushlist ; b ; b=next) + { + next = b->next; + b->next = node->brushlist; + node->brushlist = b; + } + + c_pruned++; + } +} + + +void PruneNodes (node_t *node) +{ + Sys_FPrintf( SYS_VRB, "--- PruneNodes ---\n"); + c_pruned = 0; + PruneNodes_r (node); + Sys_FPrintf( SYS_VRB, "%5i pruned nodes\n", c_pruned); +} + +//=========================================================== diff --git a/tools/quake2/q2map/writebsp.c b/tools/quake2/q2map/writebsp.c new file mode 100644 index 00000000..722f6ab9 --- /dev/null +++ b/tools/quake2/q2map/writebsp.c @@ -0,0 +1,591 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "qbsp.h" + +int c_nofaces; +int c_facenodes; + + +/* +========================================================= + +ONLY SAVE OUT PLANES THAT ARE ACTUALLY USED AS NODES + +========================================================= +*/ + +int planeused[MAX_MAP_PLANES]; + +/* +============ +EmitPlanes + +There is no oportunity to discard planes, because all of the original +brushes will be saved in the map. +============ +*/ +void EmitPlanes (void) +{ + int i; + dplane_t *dp; + plane_t *mp; + int planetranslate[MAX_MAP_PLANES]; + + mp = mapplanes; + for (i=0 ; inormal, dp->normal); + dp->dist = mp->dist; + dp->type = mp->type; + numplanes++; + } +} + + +//======================================================== + +void EmitMarkFace (dleaf_t *leaf_p, face_t *f) +{ + int i; + int facenum; + + while (f->merged) + f = f->merged; + + if (f->split[0]) + { + EmitMarkFace (leaf_p, f->split[0]); + EmitMarkFace (leaf_p, f->split[1]); + return; + } + + facenum = f->outputnumber; + if (facenum == -1) + return; // degenerate face + + if (facenum < 0 || facenum >= numfaces) + Error ("Bad leafface"); + for (i=leaf_p->firstleafface ; i= MAX_MAP_LEAFFACES) + Error ("MAX_MAP_LEAFFACES"); + + dleaffaces[numleaffaces] = facenum; + numleaffaces++; + } + +} + + +/* +================== +EmitLeaf +================== +*/ +void EmitLeaf (node_t *node) +{ + dleaf_t *leaf_p; + portal_t *p; + int s; + face_t *f; + bspbrush_t *b; + int i; + int brushnum; + + // emit a leaf + if (numleafs >= MAX_MAP_LEAFS) + Error ("MAX_MAP_LEAFS"); + + leaf_p = &dleafs[numleafs]; + numleafs++; + + leaf_p->contents = node->contents; + leaf_p->cluster = node->cluster; + leaf_p->area = node->area; + + // + // write bounding box info + // + VectorCopy ((short) node->mins, leaf_p->mins); + VectorCopy ((short) node->maxs, leaf_p->maxs); + + // + // write the leafbrushes + // + leaf_p->firstleafbrush = numleafbrushes; + for (b=node->brushlist ; b ; b=b->next) + { + if (numleafbrushes >= MAX_MAP_LEAFBRUSHES) + Error ("MAX_MAP_LEAFBRUSHES"); + + brushnum = b->original - mapbrushes; + for (i=leaf_p->firstleafbrush ; inumleafbrushes = numleafbrushes - leaf_p->firstleafbrush; + + // + // write the leaffaces + // + if (leaf_p->contents & CONTENTS_SOLID) + return; // no leaffaces in solids + + leaf_p->firstleafface = numleaffaces; + + for (p = node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + f = p->face[s]; + if (!f) + continue; // not a visible portal + + EmitMarkFace (leaf_p, f); + } + + leaf_p->numleaffaces = numleaffaces - leaf_p->firstleafface; +} + + +/* +================== +EmitFace +================== +*/ +void EmitFace (face_t *f) +{ + dface_t *df; + int i; + int e; + + f->outputnumber = -1; + + if (f->numpoints < 3) + { + return; // degenerated + } + if (f->merged || f->split[0] || f->split[1]) + { + return; // not a final face + } + + // save output number so leaffaces can use + f->outputnumber = numfaces; + + if (numfaces >= MAX_MAP_FACES) + Error ("numfaces == MAX_MAP_FACES"); + df = &dfaces[numfaces]; + numfaces++; + + // planenum is used by qlight, but not quake + df->planenum = f->planenum & (~1); + df->side = f->planenum & 1; + + df->firstedge = numsurfedges; + df->numedges = f->numpoints; + df->texinfo = f->texinfo; + for (i=0 ; inumpoints ; i++) + { +// e = GetEdge (f->pts[i], f->pts[(i+1)%f->numpoints], f); + e = GetEdge2 (f->vertexnums[i], f->vertexnums[(i+1)%f->numpoints], f); + if (numsurfedges >= MAX_MAP_SURFEDGES) + Error ("numsurfedges == MAX_MAP_SURFEDGES"); + dsurfedges[numsurfedges] = e; + numsurfedges++; + } +} + +/* +============ +EmitDrawingNode_r +============ +*/ +int EmitDrawNode_r (node_t *node) +{ + dnode_t *n; + face_t *f; + int i; + + if (node->planenum == PLANENUM_LEAF) + { + EmitLeaf (node); + return -numleafs; + } + + // emit a node + if (numnodes == MAX_MAP_NODES) + Error ("MAX_MAP_NODES"); + n = &dnodes[numnodes]; + numnodes++; + + VectorCopy ((short) node->mins, n->mins); + VectorCopy ((short) node->maxs, n->maxs); + + planeused[node->planenum]++; + planeused[node->planenum^1]++; + + if (node->planenum & 1) + Error ("WriteDrawNodes_r: odd planenum"); + n->planenum = node->planenum; + n->firstface = numfaces; + + if (!node->faces) + c_nofaces++; + else + c_facenodes++; + + for (f=node->faces ; f ; f=f->next) + EmitFace (f); + + n->numfaces = numfaces - n->firstface; + + + // + // recursively output the other nodes + // + for (i=0 ; i<2 ; i++) + { + if (node->children[i]->planenum == PLANENUM_LEAF) + { + n->children[i] = -(numleafs + 1); + EmitLeaf (node->children[i]); + } + else + { + n->children[i] = numnodes; + EmitDrawNode_r (node->children[i]); + } + } + + return n - dnodes; +} + +//========================================================= + + +/* +============ +WriteBSP +============ +*/ +void WriteBSP (node_t *headnode) +{ + int oldfaces; + + c_nofaces = 0; + c_facenodes = 0; + + Sys_FPrintf( SYS_VRB, "--- WriteBSP ---\n"); + + oldfaces = numfaces; + dmodels[nummodels].headnode = EmitDrawNode_r (headnode); + EmitAreaPortals (headnode); + + Sys_FPrintf( SYS_VRB, "%5i nodes with faces\n", c_facenodes); + Sys_FPrintf( SYS_VRB, "%5i nodes without faces\n", c_nofaces); + Sys_FPrintf( SYS_VRB, "%5i faces\n", numfaces-oldfaces); +} + +//=========================================================== + +/* +============ +SetModelNumbers +============ +*/ +void SetModelNumbers (void) +{ + int i; + int models; + char value[10]; + + models = 1; + for (i=1 ; icontents = b->contents; + db->firstside = numbrushsides; + db->numsides = b->numsides; + for (j=0 ; jnumsides ; j++) + { + if (numbrushsides == MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + cp = &dbrushsides[numbrushsides]; + numbrushsides++; + cp->planenum = b->original_sides[j].planenum; + cp->texinfo = b->original_sides[j].texinfo; + } + + // add any axis planes not contained in the brush to bevel off corners + for (x=0 ; x<3 ; x++) + for (s=-1 ; s<=1 ; s+=2) + { + // add the plane + VectorCopy (vec3_origin, normal); + normal[x] = (float) s; + if (s == -1) + dist = -b->mins[x]; + else + dist = b->maxs[x]; + planenum = FindFloatPlane (normal, dist); + for (i=0 ; inumsides ; i++) + if (b->original_sides[i].planenum == planenum) + break; + if (i == b->numsides) + { + if (numbrushsides >= MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + + dbrushsides[numbrushsides].planenum = planenum; + dbrushsides[numbrushsides].texinfo = + dbrushsides[numbrushsides-1].texinfo; + numbrushsides++; + db->numsides++; + } + } + + } + +} + +//=========================================================== + +/* +================== +BeginBSPFile +================== +*/ +void BeginBSPFile (void) +{ + // these values may actually be initialized + // if the file existed when loaded, so clear them explicitly + nummodels = 0; + numfaces = 0; + numnodes = 0; + numbrushsides = 0; + numvertexes = 0; + numleaffaces = 0; + numleafbrushes = 0; + numsurfedges = 0; + + // edge 0 is not used, because 0 can't be negated + numedges = 1; + + // leave vertex 0 as an error + numvertexes = 1; + + // leave leaf 0 as an error + numleafs = 1; + dleafs[0].contents = CONTENTS_SOLID; +} + + +/* +============ +EndBSPFile +============ +*/ +void EndBSPFile (void) +{ + char path[1024]; + +#if 0 + int len; + byte *buf; +#endif + + EmitBrushes (); + EmitPlanes (); + UnparseEntities (); + + // load the pop +#if 0 + sprintf (path, "%s/pics/pop.lmp", gamedir); + len = LoadFile (path, &buf); + memcpy (dpop, buf, sizeof(dpop)); + free (buf); +#endif + + // write the map + sprintf (path, "%s.bsp", source); + Sys_Printf ("Writing %s\n", path); + WriteBSPFile (path); +} + + +/* +================== +BeginModel +================== +*/ +int firstmodleaf; +extern int firstmodeledge; +extern int firstmodelface; +void BeginModel (void) +{ + dmodel_t *mod; + int start, end; + mapbrush_t *b; + int j; + entity_t *e; + vec3_t mins, maxs; + + if (nummodels == MAX_MAP_MODELS) + Error ("MAX_MAP_MODELS"); + mod = &dmodels[nummodels]; + + mod->firstface = numfaces; + + firstmodleaf = numleafs; + firstmodeledge = numedges; + firstmodelface = numfaces; + + // + // bound the brushes + // + e = &entities[entity_num]; + + start = e->firstbrush; + end = start + e->numbrushes; + ClearBounds (mins, maxs); + + for (j=start ; jnumsides) + continue; // not a real brush (origin brush) + AddPointToBounds (b->mins, mins, maxs); + AddPointToBounds (b->maxs, mins, maxs); + } + + VectorCopy (mins, mod->mins); + VectorCopy (maxs, mod->maxs); +} + + +/* +================== +EndModel +================== +*/ +void EndModel (void) +{ + dmodel_t *mod; + + mod = &dmodels[nummodels]; + + mod->numfaces = numfaces - mod->firstface; + + nummodels++; +} + diff --git a/tools/quake2/qdata/anorms.h b/tools/quake2/qdata/anorms.h new file mode 100644 index 00000000..65170ad9 --- /dev/null +++ b/tools/quake2/qdata/anorms.h @@ -0,0 +1,162 @@ + { -0.525731f, 0.000000f, 0.850651f }, + { -0.442863f, 0.238856f, 0.864188f }, + { -0.295242f, 0.000000f, 0.955423f }, + { -0.309017f, 0.500000f, 0.809017f }, + { -0.162460f, 0.262866f, 0.951056f }, + { 0.000000f, 0.000000f, 1.000000f }, + { 0.000000f, 0.850651f, 0.525731f }, + { -0.147621f, 0.716567f, 0.681718f }, + { 0.147621f, 0.716567f, 0.681718f }, + { 0.000000f, 0.525731f, 0.850651f }, + { 0.309017f, 0.500000f, 0.809017f }, + { 0.525731f, 0.000000f, 0.850651f }, + { 0.295242f, 0.000000f, 0.955423f }, + { 0.442863f, 0.238856f, 0.864188f }, + { 0.162460f, 0.262866f, 0.951056f }, + { -0.681718f, 0.147621f, 0.716567f }, + { -0.809017f, 0.309017f, 0.500000f }, + { -0.587785f, 0.425325f, 0.688191f }, + { -0.850651f, 0.525731f, 0.000000f }, + { -0.864188f, 0.442863f, 0.238856f }, + { -0.716567f, 0.681718f, 0.147621f }, + { -0.688191f, 0.587785f, 0.425325f }, + { -0.500000f, 0.809017f, 0.309017f }, + { -0.238856f, 0.864188f, 0.442863f }, + { -0.425325f, 0.688191f, 0.587785f }, + { -0.716567f, 0.681718f, -0.147621f }, + { -0.500000f, 0.809017f, -0.309017f }, + { -0.525731f, 0.850651f, 0.000000f }, + { 0.000000f, 0.850651f, -0.525731f }, + { -0.238856f, 0.864188f, -0.442863f }, + { 0.000000f, 0.955423f, -0.295242f }, + { -0.262866f, 0.951056f, -0.162460f }, + { 0.000000f, 1.000000f, 0.000000f }, + { 0.000000f, 0.955423f, 0.295242f }, + { -0.262866f, 0.951056f, 0.162460f }, + { 0.238856f, 0.864188f, 0.442863f }, + { 0.262866f, 0.951056f, 0.162460f }, + { 0.500000f, 0.809017f, 0.309017f }, + { 0.238856f, 0.864188f, -0.442863f }, + { 0.262866f, 0.951056f, -0.162460f }, + { 0.500000f, 0.809017f, -0.309017f }, + { 0.850651f, 0.525731f, 0.000000f }, + { 0.716567f, 0.681718f, 0.147621f }, + { 0.716567f, 0.681718f, -0.147621f }, + { 0.525731f, 0.850651f, 0.000000f }, + { 0.425325f, 0.688191f, 0.587785f }, + { 0.864188f, 0.442863f, 0.238856f }, + { 0.688191f, 0.587785f, 0.425325f }, + { 0.809017f, 0.309017f, 0.500000f }, + { 0.681718f, 0.147621f, 0.716567f }, + { 0.587785f, 0.425325f, 0.688191f }, + { 0.955423f, 0.295242f, 0.000000f }, + { 1.000000f, 0.000000f, 0.000000f }, + { 0.951056f, 0.162460f, 0.262866f }, + { 0.850651f, -0.525731f, 0.000000f }, + { 0.955423f, -0.295242f, 0.000000f }, + { 0.864188f, -0.442863f, 0.238856f }, + { 0.951056f, -0.162460f, 0.262866f }, + { 0.809017f, -0.309017f, 0.500000f }, + { 0.681718f, -0.147621f, 0.716567f }, + { 0.850651f, 0.000000f, 0.525731f }, + { 0.864188f, 0.442863f, -0.238856f }, + { 0.809017f, 0.309017f, -0.500000f }, + { 0.951056f, 0.162460f, -0.262866f }, + { 0.525731f, 0.000000f, -0.850651f }, + { 0.681718f, 0.147621f, -0.716567f }, + { 0.681718f, -0.147621f, -0.716567f }, + { 0.850651f, 0.000000f, -0.525731f }, + { 0.809017f, -0.309017f, -0.500000f }, + { 0.864188f, -0.442863f, -0.238856f }, + { 0.951056f, -0.162460f, -0.262866f }, + { 0.147621f, 0.716567f, -0.681718f }, + { 0.309017f, 0.500000f, -0.809017f }, + { 0.425325f, 0.688191f, -0.587785f }, + { 0.442863f, 0.238856f, -0.864188f }, + { 0.587785f, 0.425325f, -0.688191f }, + { 0.688191f, 0.587785f, -0.425325f }, + { -0.147621f, 0.716567f, -0.681718f }, + { -0.309017f, 0.500000f, -0.809017f }, + { 0.000000f, 0.525731f, -0.850651f }, + { -0.525731f, 0.000000f, -0.850651f }, + { -0.442863f, 0.238856f, -0.864188f }, + { -0.295242f, 0.000000f, -0.955423f }, + { -0.162460f, 0.262866f, -0.951056f }, + { 0.000000f, 0.000000f, -1.000000f }, + { 0.295242f, 0.000000f, -0.955423f }, + { 0.162460f, 0.262866f, -0.951056f }, + { -0.442863f, -0.238856f, -0.864188f }, + { -0.309017f, -0.500000f, -0.809017f }, + { -0.162460f, -0.262866f, -0.951056f }, + { 0.000000f, -0.850651f, -0.525731f }, + { -0.147621f, -0.716567f, -0.681718f }, + { 0.147621f, -0.716567f, -0.681718f }, + { 0.000000f, -0.525731f, -0.850651f }, + { 0.309017f, -0.500000f, -0.809017f }, + { 0.442863f, -0.238856f, -0.864188f }, + { 0.162460f, -0.262866f, -0.951056f }, + { 0.238856f, -0.864188f, -0.442863f }, + { 0.500000f, -0.809017f, -0.309017f }, + { 0.425325f, -0.688191f, -0.587785f }, + { 0.716567f, -0.681718f, -0.147621f }, + { 0.688191f, -0.587785f, -0.425325f }, + { 0.587785f, -0.425325f, -0.688191f }, + { 0.000000f, -0.955423f, -0.295242f }, + { 0.000000f, -1.000000f, 0.000000f }, + { 0.262866f, -0.951056f, -0.162460f }, + { 0.000000f, -0.850651f, 0.525731f }, + { 0.000000f, -0.955423f, 0.295242f }, + { 0.238856f, -0.864188f, 0.442863f }, + { 0.262866f, -0.951056f, 0.162460f }, + { 0.500000f, -0.809017f, 0.309017f }, + { 0.716567f, -0.681718f, 0.147621f }, + { 0.525731f, -0.850651f, 0.000000f }, + { -0.238856f, -0.864188f, -0.442863f }, + { -0.500000f, -0.809017f, -0.309017f }, + { -0.262866f, -0.951056f, -0.162460f }, + { -0.850651f, -0.525731f, 0.000000f }, + { -0.716567f, -0.681718f, -0.147621f }, + { -0.716567f, -0.681718f, 0.147621f }, + { -0.525731f, -0.850651f, 0.000000f }, + { -0.500000f, -0.809017f, 0.309017f }, + { -0.238856f, -0.864188f, 0.442863f }, + { -0.262866f, -0.951056f, 0.162460f }, + { -0.864188f, -0.442863f, 0.238856f }, + { -0.809017f, -0.309017f, 0.500000f }, + { -0.688191f, -0.587785f, 0.425325f }, + { -0.681718f, -0.147621f, 0.716567f }, + { -0.442863f, -0.238856f, 0.864188f }, + { -0.587785f, -0.425325f, 0.688191f }, + { -0.309017f, -0.500000f, 0.809017f }, + { -0.147621f, -0.716567f, 0.681718f }, + { -0.425325f, -0.688191f, 0.587785f }, + { -0.162460f, -0.262866f, 0.951056f }, + { 0.442863f, -0.238856f, 0.864188f }, + { 0.162460f, -0.262866f, 0.951056f }, + { 0.309017f, -0.500000f, 0.809017f }, + { 0.147621f, -0.716567f, 0.681718f }, + { 0.000000f, -0.525731f, 0.850651f }, + { 0.425325f, -0.688191f, 0.587785f }, + { 0.587785f, -0.425325f, 0.688191f }, + { 0.688191f, -0.587785f, 0.425325f }, + { -0.955423f, 0.295242f, 0.000000f }, + { -0.951056f, 0.162460f, 0.262866f }, + { -1.000000f, 0.000000f, 0.000000f }, + { -0.850651f, 0.000000f, 0.525731f }, + { -0.955423f, -0.295242f, 0.000000f }, + { -0.951056f, -0.162460f, 0.262866f }, + { -0.864188f, 0.442863f, -0.238856f }, + { -0.951056f, 0.162460f, -0.262866f }, + { -0.809017f, 0.309017f, -0.500000f }, + { -0.864188f, -0.442863f, -0.238856f }, + { -0.951056f, -0.162460f, -0.262866f }, + { -0.809017f, -0.309017f, -0.500000f }, + { -0.681718f, 0.147621f, -0.716567f }, + { -0.681718f, -0.147621f, -0.716567f }, + { -0.850651f, 0.000000f, -0.525731f }, + { -0.688191f, 0.587785f, -0.425325f }, + { -0.587785f, 0.425325f, -0.688191f }, + { -0.425325f, 0.688191f, -0.587785f }, + { -0.425325f, -0.688191f, -0.587785f }, + { -0.587785f, -0.425325f, -0.688191f }, + { -0.688191f, -0.587785f, -0.425325f }, diff --git a/tools/quake2/qdata/images.c b/tools/quake2/qdata/images.c new file mode 100644 index 00000000..ec4e99f3 --- /dev/null +++ b/tools/quake2/qdata/images.c @@ -0,0 +1,763 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qdata.h" +#include "inout.h" + +char mip_prefix[1024]; // directory to dump the textures in + +qboolean colormap_issued; +byte colormap_palette[768]; + +/* +============== +RemapZero + +Replaces all 0 bytes in an image with the closest palette entry. +This is because NT won't let us change index 0, so any palette +animation leaves those pixels untouched. +============== +*/ +void RemapZero (byte *pixels, byte *palette, int width, int height) +{ + int i, c; + int alt_zero; + int value, best; + + alt_zero = 0; + best = 9999999; + for (i=1 ; i<255 ; i++) + { + value = palette[i*3+0]+palette[i*3+1]+palette[i*3+2]; + if (value < best) + { + best = value; + alt_zero = i; + } + } + + c = width*height; + for (i=0 ; ibyteimagewidth || yl+h>byteimageheight) + Error ("GrabPic: Bad size: %i, %i, %i, %i",xl,yl,w,h); + + // crop it to the proper size + cropped = malloc (w*h); + for (y=0 ; ybyteimagewidth || yl+h>byteimageheight) + Error ("GrabPic: Bad size: %i, %i, %i, %i",xl,yl,w,h); + + // crop it to the proper size + cropped = malloc (w*h); + for (y=0 ; y 255) + r = 255; + if (r < 0) + r = 0; + if (g > 255) + g = 255; + if (g < 0) + g = 0; + if (b > 255) + b = 255; + if (b < 0) + b = 0; +#ifndef TABLECOLORS + bestcolor = BestColor (r, g, b, 0, 254); +#else + bestcolor = palmap[r>>3][g>>3][b>>3]; +#endif + + return bestcolor; +} + + +void BuildPalmap (void) +{ +#ifdef TABLECOLORS + int r, g, b; + int bestcolor; + + if (palmap_built) + return; + palmap_built = true; + + for (r=4 ; r<256 ; r+=8) + { + for (g=4 ; g<256 ; g+=8) + { + for (b=4 ; b<256 ; b+=8) + { + bestcolor = BestColor (r, g, b, 1, 254); + palmap[r>>3][g>>3][b>>3] = bestcolor; + } + } + } +#endif + + if (!colormap_issued) + Error ("You must issue a $colormap command first"); + +} + +/* +============= +AveragePixels +============= +*/ +byte AveragePixels (int count) +{ + int r,g,b; + int i; + int vis; + int pix; + int bestcolor; + byte *pal; + int fullbright; + + vis = 0; + r = g = b = 0; + fullbright = 0; + for (i=0 ; i +must be multiples of sixteen +SURF_WINDOW +============== +*/ +void Cmd_Mip (void) +{ + int x,y,xl,yl,xh,yh,w,h; + byte *screen_p, *source; + int linedelta; + miptex_t *qtex; + int miplevel, mipstep; + int xx, yy, pix; + int count; + int flags, value, contents; + mipparm_t *mp; + char lumpname[64]; + byte *lump_p; + char filename[1024]; + char animname[64]; + + GetToken (false); + strcpy (lumpname, token); + + GetToken (false); + xl = atoi (token); + GetToken (false); + yl = atoi (token); + GetToken (false); + w = atoi (token); + GetToken (false); + h = atoi (token); + + if ( (w & 15) || (h & 15) ) + Error ("line %i: miptex sizes must be multiples of 16", scriptline); + + flags = 0; + contents = 0; + value = 0; + + animname[0] = 0; + + // get optional flags and values + while (TokenAvailable ()) + { + GetToken (false); + + for (mp=mipparms ; mp->name ; mp++) + { + if (!strcmp(mp->name, token)) + { + switch (mp->type) + { + case pt_animvalue: + GetToken (false); // specify the next animation frame + strcpy (animname, token); + break; + case pt_flags: + flags |= mp->flags; + break; + case pt_contents: + contents |= mp->flags; + break; + case pt_flagvalue: + flags |= mp->flags; + GetToken (false); // specify the light value + value = atoi(token); + break; + } + break; + } + } + if (!mp->name) + Error ("line %i: unknown parm %s", scriptline, token); + } + + sprintf (filename, "%stextures/%s/%s.wal", gamedir, mip_prefix, lumpname); + if (g_release) + return; // textures are only released by $maps + + xh = xl+w; + yh = yl+h; + + qtex = malloc (sizeof(miptex_t) + w*h*2); + memset (qtex, 0, sizeof(miptex_t)); + + qtex->width = LittleLong(w); + qtex->height = LittleLong(h); + qtex->flags = LittleLong(flags); + qtex->contents = LittleLong(contents); + qtex->value = LittleLong(value); + sprintf (qtex->name, "%s/%s", mip_prefix, lumpname); + if (animname[0]) + sprintf (qtex->animname, "%s/%s", mip_prefix, animname); + + lump_p = (byte *)(&qtex->value+1); + + screen_p = byteimage + yl*byteimagewidth + xl; + linedelta = byteimagewidth - w; + + source = lump_p; + qtex->offsets[0] = LittleLong(lump_p - (byte *)qtex); + + for (y=yl ; yoffsets[miplevel] = LittleLong(lump_p - (byte *)qtex); + + mipstep = 1< /tmp/temp.i + cc $(CFLAGS) -o $@ /tmp/temp.i +$(ODIR)/models.o : models.c + cc $(CFLAGS) -E $? | tr -d '\015' > /tmp/temp.i + cc $(CFLAGS) -o $@ /tmp/temp.i +$(ODIR)/sprites.o : sprites.c + cc $(CFLAGS) -E $? | tr -d '\015' > /tmp/temp.i + cc $(CFLAGS) -o $@ /tmp/temp.i +$(ODIR)/images.o : images.c + cc $(CFLAGS) -E $? | tr -d '\015' > /tmp/temp.i + cc $(CFLAGS) -o $@ /tmp/temp.i +$(ODIR)/tables.o : tables.c + cc $(CFLAGS) -E $? | tr -d '\015' > /tmp/temp.i + cc $(CFLAGS) -o $@ /tmp/temp.i + +$(ODIR)/cmdlib.o : ../common/cmdlib.c + cc $(CFLAGS) -E $? | tr -d '\015' > /tmp/temp.i + cc $(CFLAGS) -o $@ /tmp/temp.i +$(ODIR)/scriplib.o : ../common/scriplib.c + cc $(CFLAGS) -E $? | tr -d '\015' > /tmp/temp.i + cc $(CFLAGS) -o $@ /tmp/temp.i +$(ODIR)/lbmlib.o : ../common/lbmlib.c + cc $(CFLAGS) -E $? | tr -d '\015' > /tmp/temp.i + cc $(CFLAGS) -o $@ /tmp/temp.i +$(ODIR)/mathlib.o : ../common/mathlib.c + cc $(CFLAGS) -E $? | tr -d '\015' > /tmp/temp.i + cc $(CFLAGS) -o $@ /tmp/temp.i +$(ODIR)/trilib.o : ../common/trilib.c + cc $(CFLAGS) -E $? | tr -d '\015' > /tmp/temp.i + cc $(CFLAGS) -o $@ /tmp/temp.i +$(ODIR)/l3dslib.o : ../common/l3dslib.c + cc $(CFLAGS) -E $? | tr -d '\015' > /tmp/temp.i + cc $(CFLAGS) -o $@ /tmp/temp.i +$(ODIR)/threads.o : ../common/threads.c + cc $(CFLAGS) -E $? | tr -d '\015' > /tmp/temp.i + cc $(CFLAGS) -o $@ /tmp/temp.i diff --git a/tools/quake2/qdata/models.c b/tools/quake2/qdata/models.c new file mode 100644 index 00000000..2316b0f6 --- /dev/null +++ b/tools/quake2/qdata/models.c @@ -0,0 +1,1152 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qdata.h" +#include "inout.h" + +//================================================================= + +typedef struct +{ + int numnormals; + vec3_t normalsum; +} vertexnormals_t; + +typedef struct +{ + vec3_t v; + int lightnormalindex; +} trivert_t; + +typedef struct +{ + vec3_t mins, maxs; + char name[16]; + trivert_t v[MAX_VERTS]; +} frame_t; + +//================================================================ + +frame_t g_frames[MAX_FRAMES]; + +dmdl_t model; + + +float scale_up; // set by $scale +vec3_t adjust; // set by $origin +int g_fixedwidth, g_fixedheight; // set by $skinsize + + +// +// base frame info +// +vec3_t base_xyz[MAX_VERTS]; +dstvert_t base_st[MAX_VERTS]; +dtriangle_t triangles[MAX_TRIANGLES]; + +int triangle_st[MAX_TRIANGLES][3][2]; + +// the command list holds counts, s/t values, and xyz indexes +// that are valid for every frame +int commands[16384]; +int numcommands; +int numglverts; +int used[MAX_TRIANGLES]; + +char g_skins[MAX_MD2SKINS][64]; + +char cdarchive[1024]; +char cdpartial[1024]; +char cddir[1024]; + +char modelname[64]; // empty unless $modelname issued (players) + +#define NUMVERTEXNORMALS 162 + +float avertexnormals[NUMVERTEXNORMALS][3] = { +#include "anorms.h" +}; + +FILE *headerouthandle = NULL; + +//============================================================== + +/* +=============== +ClearModel +=============== +*/ +void ClearModel (void) +{ + memset (&model, 0, sizeof(model)); + + modelname[0] = 0; + scale_up = 1.0; + VectorCopy (vec3_origin, adjust); + g_fixedwidth = g_fixedheight = 0; + g_skipmodel = false; +} + + +void H_printf(char *fmt, ...) +{ + va_list argptr; + char name[1024]; + + if (!headerouthandle) + { + sprintf (name, "%s/tris.h", cddir); + headerouthandle = SafeOpenWrite (name); + fprintf(headerouthandle, "// %s\n\n", cddir); + fprintf(headerouthandle, "// This file generated by qdata - Do NOT Modify\n\n"); + } + + va_start (argptr, fmt); + vfprintf (headerouthandle, fmt, argptr); + va_end (argptr); +} + + +/* +============ +WriteModelFile +============ +*/ +void WriteModelFile (FILE *modelouthandle) +{ + int i; + dmdl_t modeltemp; + int j, k; + frame_t *in; + daliasframe_t *out; + byte buffer[MAX_VERTS*4+128]; + float v; + int c_on, c_off; + + model.ident = IDALIASHEADER; + model.version = ALIAS_VERSION; + model.framesize = (int)&((daliasframe_t *)0)->verts[model.num_xyz]; + model.num_glcmds = numcommands; + model.ofs_skins = sizeof(dmdl_t); + model.ofs_st = model.ofs_skins + model.num_skins * MAX_SKINNAME; + model.ofs_tris = model.ofs_st + model.num_st*sizeof(dstvert_t); + model.ofs_frames = model.ofs_tris + model.num_tris*sizeof(dtriangle_t); + model.ofs_glcmds = model.ofs_frames + model.num_frames*model.framesize; + model.ofs_end = model.ofs_glcmds + model.num_glcmds*4; + + // + // write out the model header + // + for (i=0 ; iname, in->name); + for (j=0 ; j<3 ; j++) + { + out->scale[j] = (in->maxs[j] - in->mins[j])/255; + out->translate[j] = in->mins[j]; + } + + for (j=0 ; jverts[j].lightnormalindex = in->v[j].lightnormalindex; + + for (k=0 ; k<3 ; k++) + { + // scale to byte values & min/max check + v = Q_rint ( (in->v[j].v[k] - out->translate[k]) / out->scale[k] ); + + // clamp, so rounding doesn't wrap from 255.6 to 0 + if (v > 255.0) + v = 255.0; + if (v < 0) + v = 0; + out->verts[j].v[k] = v; + } + } + + for (j=0 ; j<3 ; j++) + { + out->scale[j] = LittleFloat (out->scale[j]); + out->translate[j] = LittleFloat (out->translate[j]); + } + + SafeWrite (modelouthandle, out, model.framesize); + } + + // + // write out glcmds + // + SafeWrite (modelouthandle, commands, numcommands*4); +} + + +/* +=============== +FinishModel +=============== +*/ +void FinishModel (void) +{ + FILE *modelouthandle; + int i; + char name[1024]; + + if (!model.num_frames) + return; + +// +// copy to release directory tree if doing a release build +// + if (g_release) + { + if (modelname[0]) + sprintf (name, "%s", modelname); + else + sprintf (name, "%s/tris.md2", cdpartial); + ReleaseFile (name); + + for (i=0 ; iindex_xyz[(startv)%3]; + strip_xyz[1] = last->index_xyz[(startv+1)%3]; + strip_xyz[2] = last->index_xyz[(startv+2)%3]; + strip_st[0] = last->index_st[(startv)%3]; + strip_st[1] = last->index_st[(startv+1)%3]; + strip_st[2] = last->index_st[(startv+2)%3]; + + strip_tris[0] = starttri; + stripcount = 1; + + m1 = last->index_xyz[(startv+2)%3]; + st1 = last->index_st[(startv+2)%3]; + m2 = last->index_xyz[(startv+1)%3]; + st2 = last->index_st[(startv+1)%3]; + + // look for a matching triangle +nexttri: + for (j=starttri+1, check=&triangles[starttri+1] + ; jindex_xyz[k] != m1) + continue; + if (check->index_st[k] != st1) + continue; + if (check->index_xyz[ (k+1)%3 ] != m2) + continue; + if (check->index_st[ (k+1)%3 ] != st2) + continue; + + // this is the next part of the fan + + // if we can't use this triangle, this tristrip is done + if (used[j]) + goto done; + + // the new edge + if (stripcount & 1) + { + m2 = check->index_xyz[ (k+2)%3 ]; + st2 = check->index_st[ (k+2)%3 ]; + } + else + { + m1 = check->index_xyz[ (k+2)%3 ]; + st1 = check->index_st[ (k+2)%3 ]; + } + + strip_xyz[stripcount+2] = check->index_xyz[ (k+2)%3 ]; + strip_st[stripcount+2] = check->index_st[ (k+2)%3 ]; + strip_tris[stripcount] = j; + stripcount++; + + used[j] = 2; + goto nexttri; + } + } +done: + + // clear the temp used flags + for (j=starttri+1 ; jindex_xyz[(startv)%3]; + strip_xyz[1] = last->index_xyz[(startv+1)%3]; + strip_xyz[2] = last->index_xyz[(startv+2)%3]; + strip_st[0] = last->index_st[(startv)%3]; + strip_st[1] = last->index_st[(startv+1)%3]; + strip_st[2] = last->index_st[(startv+2)%3]; + + strip_tris[0] = starttri; + stripcount = 1; + + m1 = last->index_xyz[(startv+0)%3]; + st1 = last->index_st[(startv+0)%3]; + m2 = last->index_xyz[(startv+2)%3]; + st2 = last->index_st[(startv+2)%3]; + + + // look for a matching triangle +nexttri: + for (j=starttri+1, check=&triangles[starttri+1] + ; jindex_xyz[k] != m1) + continue; + if (check->index_st[k] != st1) + continue; + if (check->index_xyz[ (k+1)%3 ] != m2) + continue; + if (check->index_st[ (k+1)%3 ] != st2) + continue; + + // this is the next part of the fan + + // if we can't use this triangle, this tristrip is done + if (used[j]) + goto done; + + // the new edge + m2 = check->index_xyz[ (k+2)%3 ]; + st2 = check->index_st[ (k+2)%3 ]; + + strip_xyz[stripcount+2] = m2; + strip_st[stripcount+2] = st2; + strip_tris[stripcount] = j; + stripcount++; + + used[j] = 2; + goto nexttri; + } + } +done: + + // clear the temp used flags + for (j=starttri+1 ; j bestlen) + { + besttype = type; + bestlen = len; + for (j=0 ; j= 150) + scale = 150.0 / width; + if (height*scale >= 190) + scale = 190.0 / height; + + s_scale = t_scale = scale; + + iwidth = ceil(width*s_scale); + iheight = ceil(height*t_scale); + + iwidth += 4; + iheight += 4; + } + else + { // new style + iwidth = g_fixedwidth / 2; + iheight = g_fixedheight; + + s_scale = (float)(iwidth-4) / width; + t_scale = (float)(iheight-4) / height; + } + +// +// determine which side of each triangle to map the texture to +// + for (i=0 ; i 0) + { + basex = iwidth + 2; + } + else + { + basex = 2; + } + basey = 2; + + for (j=0 ; j<3 ; j++) + { + pbasevert = ptri[i].verts[j]; + + triangle_st[i][j][0] = Q_rint((pbasevert[0] - mins[0]) * s_scale + basex); + triangle_st[i][j][1] = Q_rint((maxs[2] - pbasevert[2]) * t_scale + basey); + } + } + +// make the width a multiple of 4; some hardware requires this, and it ensures +// dword alignment for each scan + swidth = iwidth*2; + model.skinwidth = (swidth + 3) & ~3; + model.skinheight = iheight; +} + + +/* +================= +Cmd_Base +================= +*/ +void Cmd_Base (void) +{ + triangle_t *ptri; + int i, j, k; + int time1; + char file1[1024]; + + GetToken (false); + + if (g_skipmodel || g_release || g_archive) + return; + + printf ("---------------------\n"); + sprintf (file1, "%s/%s.%s", cdarchive, token, trifileext); + printf ("%s\n", file1); + + ExpandPathAndArchive (file1); + + sprintf (file1, "%s/%s.%s", cddir, token, trifileext); + + time1 = FileTime (file1); + if (time1 == -1) + Error ("%s doesn't exist", file1); + +// +// load the base triangles +// + if (do3ds) + Load3DSTriangleList (file1, &ptri, &model.num_tris); + else + LoadTriangleList (file1, &ptri, &model.num_tris); + +// +// get the ST values +// + BuildST (ptri, model.num_tris); + +// +// run through all the base triangles, storing each unique vertex in the +// base vertex list and setting the indirect triangles to point to the base +// vertices +// + for (i=0 ; i= '0' && *s <= '9') + s--; + + strcpy (suffix, s+1); + strcpy (base, frame); + base[s-frame+1] = 0; + + // check for 'run1.tri' + sprintf (file1, "%s/%s%s.%s",cddir, base, suffix, trifileext); + time1 = FileTime (file1); + if (time1 != -1) + { + sprintf (retname, "%s%s.%s", base, suffix, trifileext); + return retname; + } + + // check for 'run.1' + sprintf (file1, "%s/%s.%s",cddir, base, suffix); + time1 = FileTime (file1); + if (time1 != -1) + { + sprintf (retname, "%s.%s", base, suffix); + return retname; + } + + Error ("frame %s could not be found",frame); + return NULL; +} + +/* +=============== +GrabFrame +=============== +*/ +void GrabFrame (char *frame) +{ + triangle_t *ptri; + int i, j; + trivert_t *ptrivert; + int num_tris; + char file1[1024]; + frame_t *fr; + vertexnormals_t vnorms[MAX_VERTS]; + int index_xyz; + char *framefile; + + // the frame 'run1' will be looked for as either + // run.1 or run1.tri, so the new alias sequence save + // feature an be used + framefile = FindFrameFile (frame); + + sprintf (file1, "%s/%s", cdarchive, framefile); + ExpandPathAndArchive (file1); + + sprintf (file1, "%s/%s",cddir, framefile); + + printf ("grabbing %s\n", file1); + + if (model.num_frames >= MAX_FRAMES) + Error ("model.num_frames >= MAX_FRAMES"); + fr = &g_frames[model.num_frames]; + model.num_frames++; + + strcpy (fr->name, frame); + +// +// load the frame +// + if (do3ds) + Load3DSTriangleList (file1, &ptri, &num_tris); + else + LoadTriangleList (file1, &ptri, &num_tris); + + if (num_tris != model.num_tris) + Error ("%s: number of triangles doesn't match base frame\n", file1); + +// +// allocate storage for the frame's vertices +// + ptrivert = fr->v; + + for (i=0 ; imins, fr->maxs); + +// +// store the frame's vertices in the same order as the base. This assumes the +// triangles and vertices in this frame are in exactly the same order as in the +// base +// + for (i=0 ; imins, fr->maxs); + + VectorAdd (vnorms[index_xyz].normalsum, normal, vnorms[index_xyz].normalsum); + vnorms[index_xyz].numnormals++; + } + } + +// +// calculate the vertex normals, match them to the template list, and store the +// index of the best match +// + for (i=0 ; i maxdot) + { + maxdot = dot; + maxdotindex = j; + } + } + + ptrivert[i].lightnormalindex = maxdotindex; + } + + free (ptri); +} + +/* +=============== +Cmd_Frame +=============== +*/ +void Cmd_Frame (void) +{ + while (TokenAvailable()) + { + GetToken (false); + if (g_skipmodel) + continue; + if (g_release || g_archive) + { + model.num_frames = 1; // don't skip the writeout + continue; + } + + H_printf("#define FRAME_%-16s\t%i\n", token, model.num_frames); + + GrabFrame (token); + } +} + + +/* +=============== +Cmd_Skin + +Skins aren't actually stored in the file, only a reference +is saved out to the header file. +=============== +*/ +void Cmd_Skin (void) +{ + byte *palette; + byte *pixels; + int width, height; + byte *cropped; + int y; + char name[1024], savename[1024]; + + GetToken (false); + + if (model.num_skins == MAX_MD2SKINS) + Error ("model.num_skins == MAX_MD2SKINS"); + + if (g_skipmodel) + return; + + sprintf (name, "%s/%s.lbm", cdarchive, token); + strcpy (name, ExpandPathAndArchive( name ) ); +// sprintf (name, "%s/%s.lbm", cddir, token); + + if (TokenAvailable()) + { + GetToken (false); + sprintf (g_skins[model.num_skins], "%s.pcx", token); + sprintf (savename, "%s%s.pcx", gamedir, g_skins[model.num_skins]); + } + else + { + sprintf (savename, "%s/%s.pcx", cddir, token); + sprintf (g_skins[model.num_skins], "%s/%s.pcx", cdpartial, token); + } + + model.num_skins++; + + if (g_skipmodel || g_release || g_archive) + return; + + // load the image + printf ("loading %s\n", name); + Load256Image (name, &pixels, &palette, &width, &height); + RemapZero (pixels, palette, width, height); + + // crop it to the proper size + cropped = malloc (model.skinwidth*model.skinheight); + for (y=0 ; y= sizeof(pf->name)) + Error ("Filename too long for pak: %s", filename); + + len = LoadFile (source, (void **)&buf); + + if (g_compress_pak && len < 4096*1024 ) + { + cblock_t in, out; + cblock_t Huffman (cblock_t in); + + in.count = len; + in.data = buf; + + out = Huffman (in); + + if (out.count < in.count) + { + printf (" compressed from %i to %i\n", in.count, out.count); + free (in.data); + buf = out.data; + len = out.count; + } + else + free (out.data); + } + + strcpy (pf->name, filename); + pf->filepos = LittleLong(ftell(pakfile)); + pf->filelen = LittleLong(len); + pf++; + + SafeWrite (pakfile, buf, len); + + free (buf); +} + + +/* +============== +FinishPak +============== +*/ +void FinishPak (void) +{ + int dirlen; + int d; + int i; + unsigned checksum; + + if (!g_pak) + return; + + pakheader.id[0] = 'P'; + pakheader.id[1] = 'A'; + pakheader.id[2] = 'C'; + pakheader.id[3] = 'K'; + dirlen = (byte *)pf - (byte *)pfiles; + pakheader.dirofs = LittleLong(ftell(pakfile)); + pakheader.dirlen = LittleLong(dirlen); + + checksum = Com_BlockChecksum ( (void *)pfiles, dirlen ); + + SafeWrite (pakfile, pfiles, dirlen); + + i = ftell (pakfile); + + fseek (pakfile, 0, SEEK_SET); + SafeWrite (pakfile, &pakheader, sizeof(pakheader)); + fclose (pakfile); + + d = pf - pfiles; + printf ("%i files packed in %i bytes\n",d, i); + printf ("checksum: 0x%x\n", checksum); +} + + +/* +=============== +Cmd_File + +This is only used to cause a file to be copied during a release +build (default.cfg, maps, etc) +=============== +*/ +void Cmd_File (void) +{ + GetToken (false); + ReleaseFile (token); +} + +/* +=============== +PackDirectory_r + +=============== +*/ +#ifdef _WIN32 +#include "io.h" +void PackDirectory_r (char *dir) +{ + struct _finddata_t fileinfo; + int handle; + char dirstring[1024]; + char filename[1024]; + + sprintf (dirstring, "%s%s/*.*", gamedir, dir); + + handle = _findfirst (dirstring, &fileinfo); + if (handle == -1) + return; + + do + { + sprintf (filename, "%s/%s", dir, fileinfo.name); + if (fileinfo.attrib & _A_SUBDIR) + { // directory + if (fileinfo.name[0] != '.') // don't pak . and .. + PackDirectory_r (filename); + continue; + } + // copy or pack the file + ReleaseFile (filename); + } while (_findnext( handle, &fileinfo ) != -1); + + _findclose (handle); +} +#else + +#include +#include + +void PackDirectory_r (char *dir) +{ +#ifdef NeXT + struct direct **namelist, *ent; +#else + struct dirent **namelist, *ent; +#endif + int count; + struct stat st; + int i; + int len; + char fullname[1024]; + char dirstring[1024]; + char *name; + + sprintf (dirstring, "%s%s", gamedir, dir); + count = scandir(dirstring, &namelist, NULL, NULL); + + for (i=0 ; id_name; + + if (name[0] == '.') + continue; + + sprintf (fullname, "%s/%s", dir, name); + sprintf (dirstring, "%s%s/%s", gamedir, dir, name); + + if (stat (dirstring, &st) == -1) + Error ("fstating %s", pf->name); + if (st.st_mode & S_IFDIR) + { // directory + PackDirectory_r (fullname); + continue; + } + + // copy or pack the file + ReleaseFile (fullname); + } +} +#endif + + +/* +=============== +Cmd_Dir + +This is only used to cause a directory to be copied during a +release build (sounds, etc) +=============== +*/ +void Cmd_Dir (void) +{ + GetToken (false); + PackDirectory_r (token); +} + +//======================================================================== + +#define MAX_RTEX 16384 +int numrtex; +char rtex[MAX_RTEX][64]; + +void ReleaseTexture (char *name) +{ + int i; + char path[1024]; + + for (i=0 ; i= argc) + Error ("usage: %s [-archive ] [-release ] [-only ] [-3ds] file.qgr", argv[ 0 ] ); + + if (do3ds) + trifileext = ext_3ds; + else + trifileext = ext_tri; + + for ( ; i +#include +#include +#include +#include + +#include "cmdlib.h" +#include "scriplib.h" +#include "mathlib.h" +#include "trilib.h" +#include "lbmlib.h" +#include "q2_threads.h" +#include "l3dslib.h" +#include "bspfile.h" + +#ifdef WIN32 + #ifdef NDEBUG // Don't show in a Release build + #pragma warning(disable : 4305) // truncate from double to float + #pragma warning(disable : 4244) // conversion from double to float + #pragma warning(disable : 4018) // signed/unsigned mismatch + #endif +#endif + +void Cmd_Modelname (void); +void Cmd_Base (void); +void Cmd_Cd (void); +void Cmd_Origin (void); +void Cmd_ScaleUp (void); +void Cmd_Frame (void); +void Cmd_Modelname (void); +void Cmd_Skin (void); +void Cmd_Skinsize (void); +void FinishModel (void); + +void Cmd_Inverse16Table( void ); + +void Cmd_SpriteName (void); +void Cmd_Load (void); +void Cmd_SpriteFrame (void); +void FinishSprite (void); + +void Cmd_Grab (void); +void Cmd_Raw (void); +void Cmd_Mip (void); +void Cmd_Environment (void); +void Cmd_Colormap (void); + +void Cmd_File (void); +void Cmd_Dir (void); +void Cmd_StartWad (void); +void Cmd_EndWad (void); +void Cmd_Mippal (void); +void Cmd_Mipdir (void); +void Cmd_Alphalight (void); + +void Cmd_Video (void); + +void RemapZero (byte *pixels, byte *palette, int width, int height); + +void ReleaseFile (char *filename); + +extern byte *byteimage, *lbmpalette; +extern int byteimagewidth, byteimageheight; + +extern qboolean g_release; // don't grab, copy output data to new tree +extern char g_releasedir[1024]; // c:\quake2\baseq2, etc +extern qboolean g_archive; // don't grab, copy source data to new tree +extern qboolean do3ds; +extern char g_only[256]; // if set, only grab this cd +extern qboolean g_skipmodel; // set true when a cd is not g_only + +extern char *trifileext; diff --git a/tools/quake2/qdata/qdata3.vcproj b/tools/quake2/qdata/qdata3.vcproj new file mode 100644 index 00000000..239e990a --- /dev/null +++ b/tools/quake2/qdata/qdata3.vcproj @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/quake2/qdata/sprites.c b/tools/quake2/qdata/sprites.c new file mode 100644 index 00000000..a70f0074 --- /dev/null +++ b/tools/quake2/qdata/sprites.c @@ -0,0 +1,228 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qdata.h" +#include "inout.h" + +#define MAX_SPRFRAMES MAX_MD2SKINS + +dsprite_t sprite; +dsprframe_t frames[MAX_SPRFRAMES]; + +byte *byteimage, *lbmpalette; +int byteimagewidth, byteimageheight; + +char spritename[1024]; + + +void FinishSprite (void); +void Cmd_Spritename (void); + + + +/* +============== +FinishSprite +============== +*/ +void FinishSprite (void) +{ + FILE *spriteouthandle; + int i, curframe; + dsprite_t spritetemp; + char savename[1024]; + + if (sprite.numframes == 0) + return; + + if (!strlen(spritename)) + Error ("Didn't name sprite file"); + + sprintf (savename, "%s%s.sp2", gamedir, spritename); + + if (g_release) + { + char name[1024]; + + sprintf (name, "%s.sp2", spritename); + ReleaseFile (name); + spritename[0] = 0; // clear for a new sprite + sprite.numframes = 0; + return; + } + + + printf ("saving in %s\n", savename); + CreatePath (savename); + spriteouthandle = SafeOpenWrite (savename); + + +// +// write out the sprite header +// + spritetemp.ident = LittleLong (IDSPRITEHEADER); + spritetemp.version = LittleLong (SPRITE_VERSION); + spritetemp.numframes = LittleLong (sprite.numframes); + + SafeWrite (spriteouthandle, &spritetemp, 12); + +// +// write out the frames +// + curframe = 0; + + for (i=0 ; i 256) || (h > 256)) + Error ("Sprite has a dimension longer than 256"); + + xh = xl+w; + yh = yl+h; + + if (sprite.numframes >= MAX_SPRFRAMES) + Error ("Too many frames; increase MAX_SPRFRAMES\n"); + + pframe = &frames[sprite.numframes]; + pframe->width = w; + pframe->height = h; + pframe->origin_x = ox; + pframe->origin_y = oy; + sprintf (pframe->name, "%s_%i.pcx", spritename, sprite.numframes); + sprintf (savename, "%s%s_%i.pcx", gamedir, spritename, sprite.numframes); + sprite.numframes++; + + if (g_release) + { + ReleaseFile (pframe->name); + return; + } + + // crop it to the proper size + cropped = malloc (w*h); + for (y=0 ; y> 5 ) & 63 ) << 2; + b[0] = ( ( color >> 11 ) & 31 ) << 3; + + for ( i = 0; i < 256; i++ ) + { + r[1] = ( d_8to24table[i] >> 0 ) & 0xFF; + g[1] = ( d_8to24table[i] >> 8 ) & 0xFF; + b[1] = ( d_8to24table[i] >> 16 ) & 0xFF; + + d = ( r[1] - r[0] ) * ( r[1] - r[0] ) + + ( g[1] - g[0] ) * ( g[1] - g[0] ) + + ( b[1] - b[0] ) * ( b[1] - b[0] ); + + if ( d < closest_distance_so_far ) + { + closest_distance_so_far = d; + closest_so_far = i; + } + } + + return closest_so_far; +} +*/ + +extern byte BestColor( int, int, int, int, int ); + +void Inverse16_BuildTable( void ) +{ + int i; + + /* + ** create the 16-to-8 table + */ + for ( i = 0; i < 65536; i++ ) + { + int r = i & 31; + int g = ( i >> 5 ) & 63; + int b = ( i >> 11 ) & 31; + + r <<= 3; + g <<= 2; + b <<= 3; + + inverse16to8table[i] = BestColor( r, g, b, 0, 255 ); + } +} + +void Alphalight_Thread (int i) +{ + int j; + float r, g, b; + float mr, mg, mb, ma; + float distortion, bestdistortion; + float v; + + r = (i>>10) * (1.0/16); + g = ((i>>5)&31) * (1.0/16); + b = (i&31) * (1.0/16); + + bestdistortion = 999999; + for (j=0 ; j<16*16*16*16 ; j++) + { + mr = (j>>12) * (1.0/16); + mg = ((j>>8)&15) * (1.0/16); + mb = ((j>>4)&15) * (1.0/16); + ma = (j&15) * (1.0/16); + + v = r * 0.5 - (mr*ma + 0.5*(1.0-ma)); + distortion = v*v; + v = g * 0.5 - (mg*ma + 0.5*(1.0-ma)); + distortion += v*v; + v = b * 0.5 - (mb*ma + 0.5*(1.0-ma)); + distortion += v*v; + + distortion *= 1.0 + ma*4; + + if (distortion < bestdistortion) + { + bestdistortion = distortion; + alphamap[i] = j; + } + } +} + +void Cmd_Alphalight (void) +{ + char savename[1024]; + + GetToken (false); + + if (g_release) + { + ReleaseFile (token); + return; + } + + sprintf (savename, "%s%s", gamedir, token); + printf ("Building alphalight table...\n"); + + RunThreadsOnIndividual (32*32*32, true, Alphalight_Thread); + + SaveFile (savename, (byte *)alphamap, sizeof(alphamap)); +} + + +void Cmd_Inverse16Table( void ) +{ + char savename[1024]; + + if ( g_release ) + { + sprintf (savename, "pics/16to8.dat"); + ReleaseFile( savename ); + return; + } + + sprintf (savename, "%spics/16to8.dat", gamedir); + printf ("Building inverse 16-to-8 table...\n"); + + Inverse16_BuildTable(); + + SaveFile( savename, (byte *) inverse16to8table, sizeof( inverse16to8table ) ); +} diff --git a/tools/quake2/qdata/video.c b/tools/quake2/qdata/video.c new file mode 100644 index 00000000..c4027171 --- /dev/null +++ b/tools/quake2/qdata/video.c @@ -0,0 +1,1259 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qdata.h" +#include "inout.h" + +byte *soundtrack; +char base[32]; + +/* +=============================================================================== + +WAV loading + +=============================================================================== +*/ + +typedef struct +{ + int rate; + int width; + int channels; + int loopstart; + int samples; + int dataofs; // chunk starts this many bytes from file start +} wavinfo_t; + + +byte *data_p; +byte *iff_end; +byte *last_chunk; +byte *iff_data; +int iff_chunk_len; + + +int samplecounts[0x10000]; + +wavinfo_t wavinfo; + +short GetLittleShort(void) +{ + short val = 0; + val = *data_p; + val = val + (*(data_p+1)<<8); + data_p += 2; + return val; +} + +int GetLittleLong(void) +{ + int val = 0; + val = *data_p; + val = val + (*(data_p+1)<<8); + val = val + (*(data_p+2)<<16); + val = val + (*(data_p+3)<<24); + data_p += 4; + return val; +} + +void FindNextChunk(char *name) +{ + while (1) + { + data_p=last_chunk; + + if (data_p >= iff_end) + { // didn't find the chunk + data_p = NULL; + return; + } + + data_p += 4; + iff_chunk_len = GetLittleLong(); + if (iff_chunk_len < 0) + { + data_p = NULL; + return; + } +// if (iff_chunk_len > 1024*1024) +// Sys_Error ("FindNextChunk: %i length is past the 1 meg sanity limit", iff_chunk_len); + data_p -= 8; + last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 ); + if (!strncmp(data_p, name, 4)) + return; + } +} + +void FindChunk(char *name) +{ + last_chunk = iff_data; + FindNextChunk (name); +} + + +void DumpChunks(void) +{ + char str[5]; + + str[4] = 0; + data_p=iff_data; + do + { + memcpy (str, data_p, 4); + data_p += 4; + iff_chunk_len = GetLittleLong(); + printf ("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len); + data_p += (iff_chunk_len + 1) & ~1; + } while (data_p < iff_end); +} + +/* +============ +GetWavinfo +============ +*/ +wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength) +{ + wavinfo_t info; + int i; + int format; + int samples; + + memset (&info, 0, sizeof(info)); + + if (!wav) + return info; + + iff_data = wav; + iff_end = wav + wavlength; + +// find "RIFF" chunk + FindChunk("RIFF"); + if (!(data_p && !strncmp(data_p+8, "WAVE", 4))) + { + printf("Missing RIFF/WAVE chunks\n"); + return info; + } + +// get "fmt " chunk + iff_data = data_p + 12; +// DumpChunks (); + + FindChunk("fmt "); + if (!data_p) + { + printf("Missing fmt chunk\n"); + return info; + } + data_p += 8; + format = GetLittleShort(); + if (format != 1) + { + printf("Microsoft PCM format only\n"); + return info; + } + + info.channels = GetLittleShort(); + info.rate = GetLittleLong(); + data_p += 4+2; + info.width = GetLittleShort() / 8; + +// get cue chunk + FindChunk("cue "); + if (data_p) + { + data_p += 32; + info.loopstart = GetLittleLong(); +// Com_Printf("loopstart=%d\n", sfx->loopstart); + + // if the next chunk is a LIST chunk, look for a cue length marker + FindNextChunk ("LIST"); + if (data_p) + { + if (!strncmp (data_p + 28, "mark", 4)) + { // this is not a proper parse, but it works with cooledit... + data_p += 24; + i = GetLittleLong (); // samples in loop + info.samples = info.loopstart + i; + } + } + } + else + info.loopstart = -1; + +// find data chunk + FindChunk("data"); + if (!data_p) + { + printf("Missing data chunk\n"); + return info; + } + + data_p += 4; + samples = GetLittleLong (); + + if (info.samples) + { + if (samples < info.samples) + Error ("Sound %s has a bad loop length", name); + } + else + info.samples = samples; + + info.dataofs = data_p - wav; + + return info; +} + +//===================================================================== + +/* +============== +LoadSoundtrack +============== +*/ +void LoadSoundtrack (void) +{ + char name[1024]; + FILE *f; + int len; + int i, val, j; + + soundtrack = NULL; + sprintf (name, "%svideo/%s/%s.wav", gamedir, base, base); + printf ("%s\n", name); + f = fopen (name, "rb"); + if (!f) + { + printf ("no soundtrack for %s\n", base); + return; + } + len = Q_filelength(f); + soundtrack = malloc(len); + fread (soundtrack, 1, len, f); + fclose (f); + + wavinfo = GetWavinfo (name, soundtrack, len); + + // count samples for compression + memset (samplecounts, 0, sizeof(samplecounts)); + + j = wavinfo.samples/2; + for (i=0 ; i wavinfo.samples || !soundtrack) + fwrite (&empty, 1, width, output); + else + fwrite (soundtrack + wavinfo.dataofs + sample*width, 1, width,output); + } +} + +//========================================================================== + +/* +================== +MTF +================== +*/ +cblock_t MTF (cblock_t in) +{ + int i, j, b, code; + byte *out_p; + int index[256]; + cblock_t out; + + out_p = out.data = malloc(in.count + 4); + + // write count + *out_p++ = in.count&255; + *out_p++ = (in.count>>8)&255; + *out_p++ = (in.count>>16)&255; + *out_p++ = (in.count>>24)&255; + + for (i=0 ; i<256 ; i++) + index[i] = i; + + for (i=0 ; i b2) + return 1; + if (++i1 == bwt_size) + i1 = 0; + if (++i2 == bwt_size) + i2 = 0; + } + + return 0; +} + +/* +================== +BWT +================== +*/ +cblock_t BWT (cblock_t in) +{ + int *sorted; + int i; + byte *out_p; + cblock_t out; + + bwt_size = in.count; + bwt_data = in.data; + + sorted = malloc(in.count*sizeof(*sorted)); + for (i=0 ; i>8)&255; + *out_p++ = (in.count>>16)&255; + *out_p++ = (in.count>>24)&255; + + // write head index + for (i=0 ; i>8)&255; + *out_p++ = (i>>16)&255; + *out_p++ = (i>>24)&255; + + // write the L column + for (i=0 ; i 32) + Error ("bitcount > 32"); + charbits[nodenum] = bits; + charbitscount[nodenum] = bitcount; + return; + } + + node = &hnodes[nodenum]; + bits <<= 1; + BuildChars (node->children[0], bits, bitcount+1); + bits |= 1; + BuildChars (node->children[1], bits, bitcount+1); +} + + +/* +================== +Huffman +================== +*/ +cblock_t Huffman (cblock_t in) +{ + int i; + hnode_t *node; + int outbits, c; + unsigned bits; + byte *out_p; + cblock_t out; + int max, maxchar; + + // count + memset (hnodes, 0, sizeof(hnodes)); + for (i=0 ; i max) + { + max = hnodes[i].count; + maxchar = i; + } + } + if (max == 0) + Error ("Huffman: max == 0"); + + for (i=0 ; i<256 ; i++) + { + hnodes[i].count = (hnodes[i].count*255+max-1) / max; + } + + // build the nodes + numhnodes = 256; + while (numhnodes != 511) + { + node = &hnodes[numhnodes]; + + // pick two lowest counts + node->children[0] = SmallestNode (); + if (node->children[0] == -1) + break; // no more + + node->children[1] = SmallestNode (); + if (node->children[1] == -1) + { + if (node->children[0] != numhnodes-1) + Error ("Bad smallestnode"); + break; + } + node->count = hnodes[node->children[0]].count + + hnodes[node->children[1]].count; + numhnodes++; + } + + BuildChars (numhnodes-1, 0, 0); + + out_p = out.data = malloc(in.count*2 + 1024); + memset (out_p, 0, in.count*2+1024); + + // write count + *out_p++ = in.count&255; + *out_p++ = (in.count>>8)&255; + *out_p++ = (in.count>>16)&255; + *out_p++ = (in.count>>24)&255; + + // save out the 256 normalized counts so the tree can be recreated + for (i=0 ; i<256 ; i++) + *out_p++ = hnodes[i].count; + + // write bits + outbits = 0; + for (i=0 ; i>3] |= 1<<(outbits&7); + outbits++; + } + } + + out_p += (outbits+7)>>3; + + out.count = out_p - out.data; + + return out; +} + +//========================================================================== + +/* +================== +RLE +================== +*/ +#define RLE_CODE 0xe8 +#define RLE_TRIPPLE 0xe9 + +int rle_counts[256]; +int rle_bytes[256]; + +cblock_t RLE (cblock_t in) +{ + int i; + byte *out_p; + int val; + int repeat; + cblock_t out; + + out_p = out.data = malloc (in.count*2); + + // write count + *out_p++ = in.count&255; + *out_p++ = (in.count>>8)&255; + *out_p++ = (in.count>>16)&255; + *out_p++ = (in.count>>24)&255; + + for (i=0 ; i 3 || val == RLE_CODE) + { + *out_p++ = RLE_CODE; + *out_p++ = val; + *out_p++ = repeat; + } + else + { + while (repeat--) + *out_p++ = val; + } + } + + out.count = out_p - out.data; + return out; +} + +//========================================================================== + +unsigned lzss_head[256]; +unsigned lzss_next[0x20000]; + +/* +================== +LZSS +================== +*/ +#define BACK_WINDOW 0x10000 +#define BACK_BITS 16 +#define FRONT_WINDOW 16 +#define FRONT_BITS 4 +cblock_t LZSS (cblock_t in) +{ + int i; + byte *out_p; + cblock_t out; + int val; + int j, start, max; + int bestlength, beststart; + int outbits; + +if (in.count >= sizeof(lzss_next)/4) +Error ("LZSS: too big"); + + memset (lzss_head, -1, sizeof(lzss_head)); + + out_p = out.data = malloc (in.count*2); + memset (out.data, 0, in.count*2); + + // write count + *out_p++ = in.count&255; + *out_p++ = (in.count>>8)&255; + *out_p++ = (in.count>>16)&255; + *out_p++ = (in.count>>24)&255; + + outbits = 0; + for (i=0 ; i in.count) + max = in.count - i; + + start = lzss_head[val]; + while (start != -1 && start >= i-BACK_WINDOW) + { + // count match length + for (j=0 ; j bestlength) + { + bestlength = j; + beststart = start; + } + start = lzss_next[start]; + } + +#else +// slow simple search + // search for a match + max = FRONT_WINDOW; + if (i + max > in.count) + max = in.count - i; + + start = i - BACK_WINDOW; + if (start < 0) + start = 0; + bestlength = 0; + beststart = 0; + for ( ; start < i ; start++) + { + if (in.data[start] != val) + continue; + // count match length + for (j=0 ; j bestlength) + { + bestlength = j; + beststart = start; + } + } +#endif + beststart = BACK_WINDOW - (i-beststart); + + if (bestlength < 3) + { // output a single char + bestlength = 1; + + out_p[outbits>>3] |= 1<<(outbits&7); // set bit to mark char + outbits++; + for (j=0 ; j<8 ; j++, outbits++) + if (val & (1<>3] |= 1<<(outbits&7); + } + else + { // output a phrase + outbits++; // leave a 0 bit to mark phrase + for (j=0 ; j>3] |= 1<<(outbits&7); + for (j=0 ; j>3] |= 1<<(outbits&7); + } + + while (bestlength--) + { + val = in.data[i]; + lzss_next[i] = lzss_head[val]; + lzss_head[val] = i; + i++; + } + } + + out_p += (outbits+7)>>3; + out.count = out_p - out.data; + return out; +} + +//========================================================================== + +#define MIN_REPT 15 +#define MAX_REPT 0 +#define HUF_TOKENS (256+MAX_REPT) + +unsigned charbits1[256][HUF_TOKENS]; +int charbitscount1[256][HUF_TOKENS]; + +hnode_t hnodes1[256][HUF_TOKENS*2]; +int numhnodes1[256]; + +int order0counts[256]; + +/* +================== +SmallestNode1 +================== +*/ +int SmallestNode1 (hnode_t *hnodes, int numhnodes) +{ + int i; + int best, bestnode; + + best = 99999999; + bestnode = -1; + for (i=0 ; i 32) + Error ("bitcount > 32"); + charbits1[prev][nodenum] = bits; + charbitscount1[prev][nodenum] = bitcount; + return; + } + + node = &hnodes1[prev][nodenum]; + bits <<= 1; + BuildChars1 (prev, node->children[0], bits, bitcount+1); + bits |= 1; + BuildChars1 (prev, node->children[1], bits, bitcount+1); +} + + +/* +================== +BuildTree1 +================== +*/ +void BuildTree1 (int prev) +{ + hnode_t *node, *nodebase; + int numhnodes; + + // build the nodes + numhnodes = HUF_TOKENS; + nodebase = hnodes1[prev]; + while (1) + { + node = &nodebase[numhnodes]; + + // pick two lowest counts + node->children[0] = SmallestNode1 (nodebase, numhnodes); + if (node->children[0] == -1) + break; // no more + + node->children[1] = SmallestNode1 (nodebase, numhnodes); + if (node->children[1] == -1) + break; + + node->count = nodebase[node->children[0]].count + + nodebase[node->children[1]].count; + numhnodes++; + } + numhnodes1[prev] = numhnodes-1; + BuildChars1 (prev, numhnodes-1, 0, 0); +} + + +/* +================== +Huffman1_Count +================== +*/ +void Huffman1_Count (cblock_t in) +{ + int i; + int prev; + int v; + int rept; + + prev = 0; + for (i=0 ; i MIN_REPT) + { + hnodes1[prev][255+rept].count++; + i += rept-1; + } +#endif + } +} + + +/* +================== +Huffman1_Build +================== +*/ +byte scaled[256][HUF_TOKENS]; +void Huffman1_Build (FILE *f) +{ + int i, j, v; + int max; + int total; + + for (i=0 ; i<256 ; i++) + { + // normalize and save the counts + max = 0; + for (j=0 ; j max) + max = hnodes1[i][j].count; + } + if (max == 0) + max = 1; + total = 0; + for (j=0 ; j 255) + Error ("v > 255"); + scaled[i][j] = hnodes1[i][j].count = v; + if (v) + total++; + } + if (total == 1) + { // must have two tokens + if (!scaled[i][0]) + scaled[i][0] = hnodes1[i][0].count = 1; + else + scaled[i][1] = hnodes1[i][1].count = 1; + } + + BuildTree1 (i); + } + +#if 0 + // count up the total bits + total = 0; + for (i=0 ; i<256 ; i++) + for (j=0 ; j<256 ; j++) + total += charbitscount1[i][j] * hnodes1[i][j].count; + + total = (total+7)/8; + printf ("%i bytes huffman1 compressed\n", total); +#endif + + fwrite (scaled, 1, sizeof(scaled), f); +} + +/* +================== +Huffman1 + +Order 1 compression with pre-built table +================== +*/ +cblock_t Huffman1 (cblock_t in) +{ + int i; + int outbits, c; + unsigned bits; + byte *out_p; + cblock_t out; + int prev; + int v; + int rept; + + out_p = out.data = malloc(in.count*2 + 1024); + memset (out_p, 0, in.count*2+1024); + + // write count + *out_p++ = in.count&255; + *out_p++ = (in.count>>8)&255; + *out_p++ = (in.count>>16)&255; + *out_p++ = (in.count>>24)&255; + + // write bits + outbits = 0; + prev = 0; + for (i=0 ; i>3] |= 1<<(outbits&7); + outbits++; + } + + prev = v; +#if 1 + // check for repeat encodes + for (rept=1 ; i+rept < in.count && rept < MAX_REPT ; rept++) + if (in.data[i+rept] != v) + break; + if (rept > MIN_REPT) + { + c = charbitscount1[prev][255+rept]; + bits = charbits1[prev][255+rept]; + if (!c) + Error ("!bits"); + while (c) + { + c--; + if (bits & (1<>3] |= 1<<(outbits&7); + outbits++; + } + i += rept-1; + } +#endif + } + + out_p += (outbits+7)>>3; + + out.count = out_p - out.data; + + return out; +} + +//========================================================================== + + +/* +=================== +LoadFrame +=================== +*/ +cblock_t LoadFrame (char *base, int frame, int digits, byte **palette) +{ + int ten3, ten2, ten1, ten0; + cblock_t in; + int width, height; + char name[1024]; + FILE *f; + + in.data = NULL; + in.count = -1; + + ten3 = frame/1000; + ten2 = (frame-ten3*1000)/100; + ten1 = (frame-ten3*1000-ten2*100)/10; + ten0 = frame%10; + + if (digits == 4) + sprintf (name, "%svideo/%s/%s%i%i%i%i.pcx", gamedir, base, base, ten3, ten2, ten1, ten0); + else + sprintf (name, "%svideo/%s/%s%i%i%i.pcx", gamedir, base, base, ten2, ten1, ten0); + + f = fopen(name, "rb"); + if (!f) + { + in.data = NULL; + return in; + } + fclose (f); + + printf ("%s\n", name); + Load256Image (name, &in.data, palette, &width, &height); + in.count = width*height; +// FIXME: map 0 and 255! + +#if 0 + // rle compress + rle = RLE(in); + free (in.data); + + return rle; +#endif + + return in; +} + +/* +=============== +Cmd_Video + +video +=============== +*/ +void Cmd_Video (void) +{ + char savename[1024]; + char name[1024]; + FILE *output; + int startframe, frame; + byte *palette; + int width, height; + byte current_palette[768]; + int command; + int i; + int digits; + cblock_t in, huffman; + int swap; + + + GetToken (false); + strcpy (base, token); + if (g_release) + { +// sprintf (savename, "video/%s.cin", token); +// ReleaseFile (savename); + return; + } + + GetToken (false); + digits = atoi(token); + + // optionally skip frames + if (TokenAvailable ()) + { + GetToken (false); + startframe = atoi(token); + } + else + startframe=0; + + sprintf (savename, "%svideo/%s.cin", gamedir, base); + + + // clear stuff + memset (charbits1, 0, sizeof(charbits1)); + memset (charbitscount1, 0, sizeof(charbitscount1)); + memset (hnodes1, 0, sizeof(hnodes1)); + memset (numhnodes1, 0, sizeof(numhnodes1)); + memset (order0counts, 0, sizeof(order0counts)); + + + // load the entire sound wav file if present + LoadSoundtrack (); + + if (digits == 4) + sprintf (name, "%svideo/%s/%s0000.pcx", gamedir, base, base); + else + sprintf (name, "%svideo/%s/%s000.pcx", gamedir, base, base); + + printf ("%s\n", name); + Load256Image (name, NULL, &palette, &width, &height); + + output = fopen (savename, "wb"); + if (!output) + Error ("Can't open %s", savename); + + // write header info + i = LittleLong (width); + fwrite (&i, 4, 1, output); + i = LittleLong (height); + fwrite (&i, 4, 1, output); + i = LittleLong (wavinfo.rate); + fwrite (&i, 4, 1, output); + i = LittleLong (wavinfo.width); + fwrite (&i, 4, 1, output); + i = LittleLong (wavinfo.channels); + fwrite (&i, 4, 1, output); + + // build the dictionary + for ( frame=startframe ; ; frame++) + { + printf ("counting ", frame); + in = LoadFrame (base, frame, digits, &palette); + if (!in.data) + break; + Huffman1_Count (in); + free (in.data); + } + printf ("\n"); + + // build nodes and write counts + Huffman1_Build (output); + + + memset (current_palette, 0, sizeof(current_palette)); + + // compress it with the dictionary + for (frame=startframe ; ; frame++) + { + printf ("packing ", frame); + in = LoadFrame (base, frame, digits, &palette); + if (!in.data) + break; + + // see if the palette has changed + for (i=0 ; i<768 ; i++) + if (palette[i] != current_palette[i]) + { + // write a palette change + memcpy (current_palette, palette, sizeof(current_palette)); + command = LittleLong(1); + fwrite (&command, 1, 4, output); + fwrite (current_palette, 1, sizeof(current_palette), output); + break; + } + if (i == 768) + { + command = 0; // no palette change + fwrite (&command, 1, 4, output); + } + + // save the image + huffman = Huffman1 (in); + printf ("%5i bytes after huffman1\n", huffman.count); + + swap = LittleLong (huffman.count); + fwrite (&swap, 1, sizeof(swap), output); + + fwrite (huffman.data, 1, huffman.count, output); + + // save some sound samples + WriteSound (output, frame); + + free (palette); + free (in.data); + free (huffman.data); + } + printf ("\n"); + + // write end-of-file command + command = 2; + fwrite (&command, 1, 4, output); + + printf ("Total size: %i\n", ftell (output)); + + fclose (output); + + if (soundtrack) + free (soundtrack); +} diff --git a/tools/quake2/qdata_heretic2/adpcm.h b/tools/quake2/qdata_heretic2/adpcm.h new file mode 100644 index 00000000..e70f9664 --- /dev/null +++ b/tools/quake2/qdata_heretic2/adpcm.h @@ -0,0 +1,49 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +** adpcm.h - include file for adpcm coder. +** +** Version 1.0, 7-Jul-92. +** +** Modded 10/3/98 +** John Scott +*/ + +typedef struct adpcm_state_s +{ + short in_valprev; // Previous output value + short in_index; // Index into stepsize table + short out_valprev; // Previous output value + short out_index; // Index into stepsize table + int count; // Number of sample counts +} adpcm_state_t; + +typedef struct adpcm_s +{ + adpcm_state_t state; + char adpcm[0x10000]; +} adpcm_t; + +void adpcm_coder(short [], adpcm_t *); +void adpcm_decoder(adpcm_t *, short []); + +// end diff --git a/tools/quake2/qdata_heretic2/animcomp.c b/tools/quake2/qdata_heretic2/animcomp.c new file mode 100644 index 00000000..6af022f6 --- /dev/null +++ b/tools/quake2/qdata_heretic2/animcomp.c @@ -0,0 +1,351 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include +#include +#include +#include +#include "animcomp.h" + + +void *SafeMalloc(size_t n, char *desc); + + + +float *matrix; +float *delta; +float *best; +float *comp; +float *tcomp; +float *bestcomp; +float *frames; +float *base; + +int MatWidth; +int MatHeight; +int CFrameSize; +int nFrames; + + +void AnimCompressInit(int nframes,int nVerts,int CompressedFrameSize) +{ + nFrames=nframes; + MatWidth=nVerts*3; + MatHeight=CompressedFrameSize; + CFrameSize=CompressedFrameSize; + matrix=(float *)SafeMalloc(MatWidth*MatHeight*sizeof(float), "AnimCompressInit"); + best=(float *)SafeMalloc(MatWidth*MatHeight*sizeof(float), "AnimCompressInit"); + delta=(float *)SafeMalloc(MatWidth*MatHeight*sizeof(float), "AnimCompressInit"); + comp=(float *)SafeMalloc(CFrameSize*nFrames*sizeof(float), "AnimCompressInit"); + tcomp=(float *)SafeMalloc(CFrameSize*nFrames*sizeof(float), "AnimCompressInit"); + bestcomp=(float *)SafeMalloc(CFrameSize*nFrames*sizeof(float), "AnimCompressInit"); + base=(float *)SafeMalloc(MatWidth*sizeof(float), "AnimCompressInit"); + frames=(float *)SafeMalloc(MatWidth*nFrames*sizeof(float), "AnimCompressInit"); +} + +void AnimSetFrame(int frame,int index,float x,float y,float z) +{ + frames[frame*MatWidth+index*3]=x; + frames[frame*MatWidth+index*3+1]=y; + frames[frame*MatWidth+index*3+2]=z; +} + +typedef struct +{ + int index; + float val; +} SORTP; + + +#define F_RANDOM (((float)rand())/(float)RAND_MAX) + +extern void DOsvd(float *a,float *res,float *comp,float *values,int nframes,int framesize,int compressedsize); + +void AnimCompressDoit() +{ + float compression; + float *rescale; + float *ans; + float maxdev; + float avedev; + float tmp; + int j,k,l,numave; + + for (k=0;kmaxdev) + maxdev=tmp; + avedev+=tmp; + numave++; + } + } + avedev/=(float)numave; +printf("%f Max Deviation (inches) %f Ave Dev. (inches)\n",maxdev,avedev); +printf("%d bytes original size\n",MatWidth*nFrames); +printf("%d bytes of overhead\n",MatWidth*MatHeight); +printf("%d bytes/frame * %d frames = %d bytes\n",CFrameSize,nFrames,CFrameSize*nFrames); + compression=(float)(MatWidth*MatHeight+CFrameSize*nFrames+MatWidth); + compression/=(float)(MatWidth*nFrames); +printf("Overall compression = %f %%\n",100.0f-100.0f*compression); + compression=(float)(CFrameSize); + compression/=(float)(MatWidth); +printf("frame size compression = %f %%\n",100.0f-100.0f*compression); + free(rescale); + free(ans); +} + +void AnimCompressToBytes(float *trans,float *scale,char *mat,char *ccomp,unsigned char *cbase,float *cscale,float *coffset,float *bmin,float *bmax) +{ + int k,l,nv,j; + float maxdev; + float avedev; + float tmp; + int numave; + float t,mx; + float *ans; + + + nv=MatWidth/3; + + trans[0]=1E30f; + scale[0]=-1E30f; + trans[1]=1E30f; + scale[1]=-1E30f; + trans[2]=1E30f; + scale[2]=-1E30f; + for (k=0;kscale[0]) + scale[0]=base[k]; + if (base[k]scale[1]) + scale[1]=base[k+1]; + if (base[k+1]scale[2]) + scale[2]=base[k+2]; + if (base[k+2]255.0f) + t=255.0f; + cbase[k]=(unsigned char)t; + + t=(base[k+1]-trans[1])/scale[1]; + if (t<0.0f) + t=0.0f; + if (t>255.0f) + t=255.0f; + cbase[k+1]=(unsigned char)t; + + t=(base[k+2]-trans[2])/scale[2]; + if (t<0.0f) + t=0.0f; + if (t>255.0f) + t=255.0f; + cbase[k+2]=(unsigned char)t; + } + for (l=0;lmx) + mx=(float)fabs(best[l*MatWidth+k]); + } + if (mx>1E-8) + { + mx/=127.0f; + coffset[l]=1E30f; + cscale[l]=-1E30f; + for (j=0;jcscale[l]) + cscale[l]=bestcomp[j*MatHeight+l]; + if (bestcomp[j*MatHeight+l]1E-10) + { + for (j=0;j127.0f) + tmp=127.0f; + if (tmp<-127.0f) + tmp=-127.0f; + ccomp[j*MatHeight+l]=(char)floor(tmp+0.5); + } + coffset[l]+=cscale[l]*127.0f/254.0f; + cscale[l]/=254.0f; + } + else + { + cscale[l]=1.0f; + coffset[l]=0.0f; + for (j=0;j127.0f) + tmp=127.0f; + if (tmp<-127.0f) + tmp=-127.0f; + mat[k*MatHeight+l]=(char)floor(tmp+0.5); + } + } + else + { + cscale[l]=1.0f; + coffset[l]=0.0f; + for (j=0;jmaxdev) + maxdev=tmp; + avedev+=tmp; + numave++; + + if (bmin[k%3]>ans[k]) + bmin[k%3]=ans[k]; + if (bmax[k%3]version = MIP_VERSION; + + for(i=j=0;i<256;i++,j+=3) + { + mp->palette[i].r = palette[j]; + mp->palette[i].g = palette[j+1]; + mp->palette[i].b = palette[j+2]; + } + pos = (byte *)(mp + 1); + + mp->width[0] = w; + mp->height[0] = h; + mp->offsets[0] = sizeof(*mp); + memcpy(pos, buffer, w * h); + + *FinalSize = size; + return(mp); +} + +miptex32_t *CreateBook32(long *buffer, int w, int h, int *FinalSize) +{ + miptex32_t *mp; + byte *pos; + int size; + + size = sizeof(*mp) + (w * h * 4); + mp = (miptex32_t *)SafeMalloc(size, "CreateBook32"); + memset(mp, 0, size); + + mp->version = MIP32_VERSION; + + pos = (byte *)(mp + 1); + + mp->width[0] = w; + mp->height[0] = h; + mp->offsets[0] = sizeof(*mp); + memcpy(pos, buffer, w * h * 4); + + *FinalSize = size; + return(mp); +} + + +// Routines to chop a random sized image into gl texture friendly chunks + +typedef struct rect_s +{ + int x, y; + int w, h; + char name[4]; +} rect_t; + +int GetCoords(int x, int store[MAX_MD2SKINS]) +{ + int index, start, delta; + + index = 0; + start = 0; + delta = 256; + + store[index++] = start; + while(x) + { + if(x >= delta) + { + start += delta; + store[index++] = start; + x -= delta; + } + else + { + delta >>= 1; + } + } + return(index); +} + +int ChopImage(int w, int h, rect_t coords[MAX_MD2SKINS]) +{ + int xs[MAX_MD2SKINS], ys[MAX_MD2SKINS]; + int xcount, ycount, x, y, index; + + index = 0; + xcount = GetCoords(w, xs) - 1; + ycount = GetCoords(h, ys) - 1; + + for(y = 0; y < ycount; y++) + { + for(x = 0; x < xcount; x++, index++) + { + coords[index].x = xs[x]; + coords[index].y = ys[y]; + coords[index].w = xs[x + 1] - xs[x]; + coords[index].h = ys[y + 1] - ys[y]; + coords[index].name[0] = x + '0'; + coords[index].name[1] = y + '0'; + coords[index].name[2] = 0; + } + } + return(index); +} + +/* +=============== +Cmd_Pic +=============== +*/ + +void Cmd_Book() +{ + int xl,yl,xh,yh,w,h; + byte *dest, *source; + int flags, value, contents; + char lumpname[64]; + char filename[1024]; + unsigned long *destl, *sourcel; + int linedelta, x, y; + int size; + miptex_t *qtex; + miptex32_t *qtex32; + float scale_x, scale_y; + int numrects, i; + rect_t coords[MAX_MD2SKINS]; + bookframe_t bframes[MAX_MD2SKINS]; + bookframe_t *bf; + book_t book; + + GetScriptToken (false); + strcpy (lumpname, token); + + GetScriptToken (false); + xl = atoi (token); + GetScriptToken (false); + yl = atoi (token); + GetScriptToken (false); + w = atoi (token); + GetScriptToken (false); + h = atoi (token); + + total_x += w; + total_y += h; + total_textures++; + + if ( (w & 7) || (h & 7) ) + Error ("line %i: miptex sizes must be multiples of 8", scriptline); + + flags = 0; + contents = 0; + value = 0; + + scale_x = scale_y = 0.5; + + if (g_release) + return; + + if(TrueColorImage) + { + xh = xl + w; + yh = yl + h; + + if (xl >= longimagewidth || xh > longimagewidth || + yl >= longimageheight || yh > longimageheight) + { + Error ("line %i: bad clip dimmensions (%d,%d) (%d,%d) > image (%d,%d)", scriptline, xl,yl,w,h,longimagewidth,longimageheight); + } + + sourcel = (unsigned long *) longimage + (yl * longimagewidth) + xl; + destl = (unsigned long *) longimage; + linedelta = (longimagewidth - w); + + for(y = yl; y < yh; y++) + { + for(x = xl; x < xh; x++) + { + *destl++ = *sourcel++; // RGBA + } + sourcel += linedelta; + } + + // Get rectangles to chop into + numrects = ChopImage(w, h, coords); + + bf = bframes; + for(i = 0; i < numrects; i++, bf++) + { + // Copy section of image to buffer + sourcel = (unsigned long *) longimage + (coords[i].y * w) + coords[i].x; + destl = bufferl; + linedelta = w - coords[i].w; + + for(y = 0; y < coords[i].h; y++) + { + for(x = 0; x < coords[i].w; x++) + { + *destl++ = *sourcel++; + } + sourcel += linedelta; + } + + qtex32 = CreateBook32(bufferl, coords[i].w, coords[i].h, &size); + + qtex32->flags = flags; + qtex32->contents = contents; + qtex32->value = value; + qtex32->scale_x = scale_x; + qtex32->scale_y = scale_y; + + sprintf (filename, "%sbook/%s/%s_%s.m32", gamedir, book_prefix, lumpname, coords[i].name); + sprintf (qtex32->name, "%s/%s_%s.m32", book_prefix, lumpname, coords[i].name); + + strcpy(bf->name, qtex32->name); + bf->x = coords[i].x; + bf->y = coords[i].y; + bf->w = coords[i].w; + bf->h = coords[i].h; + // + // write it out + // + printf ("writing %s\n", filename); + SaveFile (filename, (byte *)qtex32, size); + + free (qtex32); + } + } + else + { + xh = xl + w; + yh = yl + h; + + if (xl >= byteimagewidth || xh > byteimagewidth || + yl >= byteimageheight || yh > byteimageheight) + { + Error ("line %i: bad clip dimmensions (%d,%d) (%d,%d) > image (%d,%d)", scriptline, xl,yl,w,h,byteimagewidth,byteimageheight); + } + + // Copy image to top left + source = byteimage + yl*byteimagewidth + xl; + dest = byteimage; + linedelta = byteimagewidth - w; + + for(y = yl; y < yh; y++) + { + for(x = xl; x < xh; x++) + { + *dest++ = *source++; + } + source += linedelta; + } + + // Get rectangles to chop into + numrects = ChopImage(w, h, coords); + + bf = bframes; + for(i = 0; i < numrects; i++, bf++) + { + // Copy section of image to buffer + source = byteimage + (coords[i].y * w) + coords[i].x; + dest = buffer; + linedelta = w - coords[i].w; + + for(y = 0; y < coords[i].h; y++) + { + for(x = 0; x < coords[i].w; x++) + { + *dest++ = *source++; + } + source += linedelta; + } + + qtex = CreateBook8(buffer, coords[i].w, coords[i].h, lbmpalette, &size); + + qtex->flags = flags; + qtex->contents = contents; + qtex->value = value; + + sprintf (filename, "%sbook/%s/%s_%s.m8", gamedir, book_prefix, lumpname, coords[i].name); + sprintf (qtex->name, "%s/%s_%s.m8", book_prefix, lumpname, coords[i].name); + + strcpy(bf->name, qtex->name); + bf->x = coords[i].x; + bf->y = coords[i].y; + bf->w = coords[i].w; + bf->h = coords[i].h; + // + // write it out + // + printf ("writing %s\n", filename); + SaveFile (filename, (byte *)qtex, size); + + free (qtex); + } + } + // Set up descriptor + size = sizeof(bookframe_t) * numrects; + + book.bheader.ident = IDBOOKHEADER; + book.bheader.version = BOOK_VERSION; + book.bheader.num_segments = numrects; + book.bheader.total_w = w; + book.bheader.total_h = h; + memcpy(book.bframes, bframes, size); + + // Save out segment descriptor + sprintf (filename, "%sBook/%s/%s.bk", gamedir, book_prefix, lumpname); + printf ("writing %s\n", filename); + SaveFile (filename, (byte *)&book, size + sizeof(bookheader_t)); +} + +/* +=============== +Cmd_picdir +=============== +*/ +void Cmd_Bookdir (void) +{ + char filename[1024]; + + GetScriptToken (false); + strcpy (book_prefix, token); + // create the directory if needed + sprintf (filename, "%sBook", gamedir); + Q_mkdir (filename); + sprintf (filename, "%sBook/%s", gamedir, book_prefix); + Q_mkdir (filename); +} + +// end diff --git a/tools/quake2/qdata_heretic2/common/bspfile.c b/tools/quake2/qdata_heretic2/common/bspfile.c new file mode 100644 index 00000000..146e03f3 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/bspfile.c @@ -0,0 +1,793 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "cmdlib.h" +#include "inout.h" +#include "mathlib.h" +#include "bspfile.h" +#include "scriplib.h" + +void GetLeafNums (void); + +//============================================================================= + +int nummodels; +dmodel_t dmodels[MAX_MAP_MODELS]; + +int visdatasize; +byte dvisdata[MAX_MAP_VISIBILITY]; +dvis_t *dvis = (dvis_t *)dvisdata; + +int lightdatasize; +byte dlightdata[MAX_MAP_LIGHTING]; + +int entdatasize; +char dentdata[MAX_MAP_ENTSTRING]; + +int numleafs; +dleaf_t dleafs[MAX_MAP_LEAFS]; + +int numplanes; +dplane_t dplanes[MAX_MAP_PLANES]; + +int numvertexes; +dvertex_t dvertexes[MAX_MAP_VERTS]; + +int numnodes; +dnode_t dnodes[MAX_MAP_NODES]; + +int numtexinfo; +texinfo_t texinfo[MAX_MAP_TEXINFO]; + +int numfaces; +dface_t dfaces[MAX_MAP_FACES]; + +int numedges; +dedge_t dedges[MAX_MAP_EDGES]; + +int numleaffaces; +unsigned short dleaffaces[MAX_MAP_LEAFFACES]; + +int numleafbrushes; +unsigned short dleafbrushes[MAX_MAP_LEAFBRUSHES]; + +int numsurfedges; +int dsurfedges[MAX_MAP_SURFEDGES]; + +int numbrushes; +dbrush_t dbrushes[MAX_MAP_BRUSHES]; + +int numbrushsides; +dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +int numareas; +darea_t dareas[MAX_MAP_AREAS]; + +int numareaportals; +dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; + +byte dpop[256]; + +/* +=============== +CompressVis + +=============== +*/ +int CompressVis (byte *vis, byte *dest) +{ + int j; + int rep; + int visrow; + byte *dest_p; + + dest_p = dest; +// visrow = (r_numvisleafs + 7)>>3; + visrow = (dvis->numclusters + 7)>>3; + + for (j=0 ; j>3; + row = (dvis->numclusters+7)>>3; + out = decompressed; + + do + { + if (*in) + { + *out++ = *in++; + continue; + } + + c = in[1]; + if (!c) + Error ("DecompressVis: 0 repeat"); + in += 2; + while (c) + { + *out++ = 0; + c--; + } + } while (out - decompressed < row); +} + +//============================================================================= + +/* +============= +SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void SwapBSPFile (qboolean todisk) +{ + int i, j; + dmodel_t *d; + + +// models + for (i=0 ; ifirstface = LittleLong (d->firstface); + d->numfaces = LittleLong (d->numfaces); + d->headnode = LittleLong (d->headnode); + + for (j=0 ; j<3 ; j++) + { + d->mins[j] = LittleFloat(d->mins[j]); + d->maxs[j] = LittleFloat(d->maxs[j]); + d->origin[j] = LittleFloat(d->origin[j]); + } + } + +// +// vertexes +// + for (i=0 ; inumclusters; + else + j = LittleLong(dvis->numclusters); + dvis->numclusters = LittleLong (dvis->numclusters); + for (i=0 ; ibitofs[i][0] = LittleLong (dvis->bitofs[i][0]); + dvis->bitofs[i][1] = LittleLong (dvis->bitofs[i][1]); + } +} + + +dheader_t *header; + +int CopyLump (int lump, void *dest, int size) +{ + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if (length % size) + Error ("LoadBSPFile: odd lump size"); + + memcpy (dest, (byte *)header + ofs, length); + + return length / size; +} + +/* +============= +LoadBSPFile +============= +*/ +void LoadBSPFile (char *filename) +{ + int i; + +// +// load the file header +// + LoadFile (filename, (void **)&header); + +// swap the header + for (i=0 ; i< sizeof(dheader_t)/4 ; i++) + ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + + if (header->ident != IDBSPHEADER) + Error ("%s is not a IBSP file", filename); + if (header->version != BSPVERSION) + Error ("%s is version %i, not %i", filename, header->version, BSPVERSION); + + nummodels = CopyLump (LUMP_MODELS, dmodels, sizeof(dmodel_t)); + numvertexes = CopyLump (LUMP_VERTEXES, dvertexes, sizeof(dvertex_t)); + numplanes = CopyLump (LUMP_PLANES, dplanes, sizeof(dplane_t)); + numleafs = CopyLump (LUMP_LEAFS, dleafs, sizeof(dleaf_t)); + numnodes = CopyLump (LUMP_NODES, dnodes, sizeof(dnode_t)); + numtexinfo = CopyLump (LUMP_TEXINFO, texinfo, sizeof(texinfo_t)); + numfaces = CopyLump (LUMP_FACES, dfaces, sizeof(dface_t)); + numleaffaces = CopyLump (LUMP_LEAFFACES, dleaffaces, sizeof(dleaffaces[0])); + numleafbrushes = CopyLump (LUMP_LEAFBRUSHES, dleafbrushes, sizeof(dleafbrushes[0])); + numsurfedges = CopyLump (LUMP_SURFEDGES, dsurfedges, sizeof(dsurfedges[0])); + numedges = CopyLump (LUMP_EDGES, dedges, sizeof(dedge_t)); + numbrushes = CopyLump (LUMP_BRUSHES, dbrushes, sizeof(dbrush_t)); + numbrushsides = CopyLump (LUMP_BRUSHSIDES, dbrushsides, sizeof(dbrushside_t)); + numareas = CopyLump (LUMP_AREAS, dareas, sizeof(darea_t)); + numareaportals = CopyLump (LUMP_AREAPORTALS, dareaportals, sizeof(dareaportal_t)); + + visdatasize = CopyLump (LUMP_VISIBILITY, dvisdata, 1); + lightdatasize = CopyLump (LUMP_LIGHTING, dlightdata, 1); + entdatasize = CopyLump (LUMP_ENTITIES, dentdata, 1); + + CopyLump (LUMP_POP, dpop, 1); + + free (header); // everything has been copied out + +// +// swap everything +// + SwapBSPFile (false); +} + + +/* +============= +LoadBSPFileTexinfo + +Only loads the texinfo lump, so qdata can scan for textures +============= +*/ +void LoadBSPFileTexinfo (char *filename) +{ + int i; + FILE *f; + int length, ofs; + + header = malloc(sizeof(dheader_t)); + + f = fopen (filename, "rb"); + fread (header, sizeof(dheader_t), 1, f); + +// swap the header + for (i=0 ; i< sizeof(dheader_t)/4 ; i++) + ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + + if (header->ident != IDBSPHEADER) + Error ("%s is not a IBSP file", filename); + if (header->version != BSPVERSION) + Error ("%s is version %i, not %i", filename, header->version, BSPVERSION); + + + length = header->lumps[LUMP_TEXINFO].filelen; + ofs = header->lumps[LUMP_TEXINFO].fileofs; + + fseek (f, ofs, SEEK_SET); + fread (texinfo, length, 1, f); + fclose (f); + + numtexinfo = length / sizeof(texinfo_t); + + free (header); // everything has been copied out + + SwapBSPFile (false); +} + + +//============================================================================ + +FILE *wadfile; +dheader_t outheader; + +void AddLump (int lumpnum, void *data, int len) +{ + lump_t *lump; + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell(wadfile) ); + lump->filelen = LittleLong(len); + SafeWrite (wadfile, data, (len+3)&~3); +} + +/* +============= +WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void WriteBSPFile (char *filename) +{ + header = &outheader; + memset (header, 0, sizeof(dheader_t)); + + SwapBSPFile (true); + + header->ident = LittleLong (IDBSPHEADER); + header->version = LittleLong (BSPVERSION); + + wadfile = SafeOpenWrite (filename); + SafeWrite (wadfile, header, sizeof(dheader_t)); // overwritten later + + AddLump (LUMP_PLANES, dplanes, numplanes*sizeof(dplane_t)); + AddLump (LUMP_LEAFS, dleafs, numleafs*sizeof(dleaf_t)); + AddLump (LUMP_VERTEXES, dvertexes, numvertexes*sizeof(dvertex_t)); + AddLump (LUMP_NODES, dnodes, numnodes*sizeof(dnode_t)); + AddLump (LUMP_TEXINFO, texinfo, numtexinfo*sizeof(texinfo_t)); + AddLump (LUMP_FACES, dfaces, numfaces*sizeof(dface_t)); + AddLump (LUMP_BRUSHES, dbrushes, numbrushes*sizeof(dbrush_t)); + AddLump (LUMP_BRUSHSIDES, dbrushsides, numbrushsides*sizeof(dbrushside_t)); + AddLump (LUMP_LEAFFACES, dleaffaces, numleaffaces*sizeof(dleaffaces[0])); + AddLump (LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes*sizeof(dleafbrushes[0])); + AddLump (LUMP_SURFEDGES, dsurfedges, numsurfedges*sizeof(dsurfedges[0])); + AddLump (LUMP_EDGES, dedges, numedges*sizeof(dedge_t)); + AddLump (LUMP_MODELS, dmodels, nummodels*sizeof(dmodel_t)); + AddLump (LUMP_AREAS, dareas, numareas*sizeof(darea_t)); + AddLump (LUMP_AREAPORTALS, dareaportals, numareaportals*sizeof(dareaportal_t)); + + AddLump (LUMP_LIGHTING, dlightdata, lightdatasize); + AddLump (LUMP_VISIBILITY, dvisdata, visdatasize); + AddLump (LUMP_ENTITIES, dentdata, entdatasize); + AddLump (LUMP_POP, dpop, sizeof(dpop)); + + fseek (wadfile, 0, SEEK_SET); + SafeWrite (wadfile, header, sizeof(dheader_t)); + fclose (wadfile); +} + +//============================================================================ + +/* +============= +PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void PrintBSPFileSizes (void) +{ + if (!num_entities) + ParseEntities (); + + printf ("%5i models %7i\n" + ,nummodels, (int)(nummodels*sizeof(dmodel_t))); + printf ("%5i brushes %7i\n" + ,numbrushes, (int)(numbrushes*sizeof(dbrush_t))); + printf ("%5i brushsides %7i\n" + ,numbrushsides, (int)(numbrushsides*sizeof(dbrushside_t))); + printf ("%5i planes %7i\n" + ,numplanes, (int)(numplanes*sizeof(dplane_t))); + printf ("%5i texinfo %7i\n" + ,numtexinfo, (int)(numtexinfo*sizeof(texinfo_t))); + printf ("%5i entdata %7i\n", num_entities, entdatasize); + + printf ("\n"); + + printf ("%5i vertexes %7i\n" + ,numvertexes, (int)(numvertexes*sizeof(dvertex_t))); + printf ("%5i nodes %7i\n" + ,numnodes, (int)(numnodes*sizeof(dnode_t))); + printf ("%5i faces %7i\n" + ,numfaces, (int)(numfaces*sizeof(dface_t))); + printf ("%5i leafs %7i\n" + ,numleafs, (int)(numleafs*sizeof(dleaf_t))); + printf ("%5i leaffaces %7i\n" + ,numleaffaces, (int)(numleaffaces*sizeof(dleaffaces[0]))); + printf ("%5i leafbrushes %7i\n" + ,numleafbrushes, (int)(numleafbrushes*sizeof(dleafbrushes[0]))); + printf ("%5i surfedges %7i\n" + ,numsurfedges, (int)(numsurfedges*sizeof(dsurfedges[0]))); + printf ("%5i edges %7i\n" + ,numedges, (int)(numedges*sizeof(dedge_t))); + printf (" lightdata %7i\n", lightdatasize); + printf (" visdata %7i\n", visdatasize); +} + + +//============================================ + +int num_entities; +entity_t entities[MAX_MAP_ENTITIES]; + +void StripTrailing (char *e) +{ + char *s; + + s = e + strlen(e)-1; + while (s >= e && *s <= 32) + { + *s = 0; + s--; + } +} + +/* +================= +ParseEpair +================= +*/ +epair_t *ParseEpair (void) +{ + epair_t *e; + + e = malloc (sizeof(epair_t)); + memset (e, 0, sizeof(epair_t)); + + if (strlen(token) >= MAX_KEY-1) + Error ("ParseEpar: token too long"); + e->key = copystring(token); + GetScriptToken (false); + if (strlen(token) >= MAX_VALUE-1) + Error ("ParseEpar: token too long"); + e->value = copystring(token); + + // strip trailing spaces + StripTrailing (e->key); + StripTrailing (e->value); + + return e; +} + + +/* +================ +ParseEntity +================ +*/ +qboolean ParseEntity (void) +{ + epair_t *e; + entity_t *mapent; + + if (!GetScriptToken (true)) + return false; + + if (strcmp (token, "{") ) + Error ("ParseEntity: { not found"); + + if (num_entities == MAX_MAP_ENTITIES) + Error ("num_entities == MAX_MAP_ENTITIES"); + + mapent = &entities[num_entities]; + num_entities++; + + do + { + if (!GetScriptToken (true)) + Error ("ParseEntity: EOF without closing brace"); + if (!strcmp (token, "}") ) + break; + e = ParseEpair (); + e->next = mapent->epairs; + mapent->epairs = e; + } while (1); + + return true; +} + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void ParseEntities (void) +{ + num_entities = 0; + ParseFromMemory (dentdata, entdatasize); + + while (ParseEntity ()) + { + } +} + + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void UnparseEntities (void) +{ + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + char key[1024], value[1024]; + + buf = dentdata; + end = buf; + *end = 0; + + for (i=0 ; inext) + { + strcpy (key, ep->key); + StripTrailing (key); + strcpy (value, ep->value); + StripTrailing (value); + + sprintf (line, "\"%s\" \"%s\"\n", key, value); + strcat (end, line); + end += strlen(line); + } + strcat (end,"}\n"); + end += 2; + + if (end > buf + MAX_MAP_ENTSTRING) + Error ("Entity text too long"); + } + entdatasize = end - buf + 1; +} + +void PrintEntity (entity_t *ent) +{ + epair_t *ep; + + printf ("------- entity %p -------\n", ent); + for (ep=ent->epairs ; ep ; ep=ep->next) + { + printf ("%s = %s\n", ep->key, ep->value); + } + +} + +void SetKeyValue (entity_t *ent, char *key, char *value) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + { + free (ep->value); + ep->value = copystring(value); + return; + } + ep = malloc (sizeof(*ep)); + if (!ep) + Error("SetKeyValue MALLOC failed! Could not allocate %s bytes.", sizeof(*ep)); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring(key); + ep->value = copystring(value); +} + +char *ValueForKey (entity_t *ent, char *key) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + return ep->value; + return ""; +} + +vec_t FloatForKey (entity_t *ent, char *key) +{ + char *k; + + k = ValueForKey (ent, key); + return atof(k); +} + +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec) +{ + char *k; + double v1, v2, v3; + + k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} + + diff --git a/tools/quake2/qdata_heretic2/common/bspfile.h b/tools/quake2/qdata_heretic2/common/bspfile.h new file mode 100644 index 00000000..1597a3df --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/bspfile.h @@ -0,0 +1,133 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _QBSP3_H +#define _QBSP3_H + + +#include "qfiles.h" + + +extern int nummodels; +extern dmodel_t dmodels[MAX_MAP_MODELS]; + +extern int visdatasize; +extern byte dvisdata[MAX_MAP_VISIBILITY]; +extern dvis_t *dvis; + +extern int lightdatasize; +extern byte dlightdata[MAX_MAP_LIGHTING]; + +extern int entdatasize; +extern char dentdata[MAX_MAP_ENTSTRING]; + +extern int numleafs; +extern dleaf_t dleafs[MAX_MAP_LEAFS]; + +extern int numplanes; +extern dplane_t dplanes[MAX_MAP_PLANES]; + +extern int numvertexes; +extern dvertex_t dvertexes[MAX_MAP_VERTS]; + +extern int numnodes; +extern dnode_t dnodes[MAX_MAP_NODES]; + +extern int numtexinfo; +extern texinfo_t texinfo[MAX_MAP_TEXINFO]; + +extern int numfaces; +extern dface_t dfaces[MAX_MAP_FACES]; + +extern int numedges; +extern dedge_t dedges[MAX_MAP_EDGES]; + +extern int numleaffaces; +extern unsigned short dleaffaces[MAX_MAP_LEAFFACES]; + +extern int numleafbrushes; +extern unsigned short dleafbrushes[MAX_MAP_LEAFBRUSHES]; + +extern int numsurfedges; +extern int dsurfedges[MAX_MAP_SURFEDGES]; + +extern int numareas; +extern darea_t dareas[MAX_MAP_AREAS]; + +extern int numareaportals; +extern dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; + +extern int numbrushes; +extern dbrush_t dbrushes[MAX_MAP_BRUSHES]; + +extern int numbrushsides; +extern dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +extern byte dpop[256]; + +void DecompressVis (byte *in, byte *decompressed); +int CompressVis (byte *vis, byte *dest); + +void LoadBSPFile (char *filename); +void LoadBSPFileTexinfo (char *filename); // just for qdata +void WriteBSPFile (char *filename); +void PrintBSPFileSizes (void); + +//=============== + + +typedef struct epair_s +{ + struct epair_s *next; + char *key; + char *value; +} epair_t; + +typedef struct +{ + vec3_t origin; + int firstbrush; + int numbrushes; + epair_t *epairs; + +// only valid for func_areaportals + int areaportalnum; + int portalareas[2]; +} entity_t; + +extern int num_entities; +extern entity_t entities[MAX_MAP_ENTITIES]; + +void ParseEntities (void); +void UnparseEntities (void); + +void SetKeyValue (entity_t *ent, char *key, char *value); +char *ValueForKey (entity_t *ent, char *key); +// will return "" if not present + +vec_t FloatForKey (entity_t *ent, char *key); +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec); + +epair_t *ParseEpair (void); + +void PrintEntity (entity_t *ent); + +#endif //_QBSP3_H diff --git a/tools/quake2/qdata_heretic2/common/cmdlib.c b/tools/quake2/qdata_heretic2/common/cmdlib.c new file mode 100644 index 00000000..f3115d29 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/cmdlib.c @@ -0,0 +1,1238 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// Nurail: Swiped from quake3/common + +#include "cmdlib.h" +#include "mathlib.h" +#include "inout.h" +#include +#include + +#ifdef WIN32 +#include +#include +#endif + +#if defined (__linux__) || defined (__APPLE__) +#include +#endif + +#ifdef NeXT +#include +#endif + +#define BASEDIRNAME "h" +#define PATHSEPERATOR '/' + +extern qboolean verbose; + +qboolean g_dokeypress = false; + +qboolean g_nomkdir = false; + + +#ifdef SAFE_MALLOC +void *safe_malloc( size_t size ) +{ + void *p; + + p = malloc(size); + if(!p) + Error ("safe_malloc failed on allocation of %i bytes", size); + + return p; +} + +void *safe_malloc_info( size_t size, char* info ) +{ + void *p; + + p = malloc(size); + if(!p) + Error ("%s: safe_malloc failed on allocation of %i bytes", info, size); + + return p; +} +#endif + +void *SafeMalloc(size_t n, char *desc) +{ + void *p; + + if((p = malloc(n)) == NULL) + { + Error("Failed to allocate %d bytes for '%s'.\n", n, desc); + } + memset(p, 0, n); + return p; +} + +#if defined (__linux__) || defined (__APPLE__) +void strlwr(char *conv_str) +{ + int i; + + for(i=0; i32); + + com_token[len] = 0; + return data; +} + +int Q_strncasecmp (const char *s1, const char *s2, int n) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (!n--) + return 0; // strings are equal until end point + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return -1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + +int Q_stricmp (const char *s1, const char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + +int Q_strcasecmp (const char *s1, const char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + +// NOTE TTimo when switching to Multithread DLL (Release/Debug) in the config +// started getting warnings about that function, prolly a duplicate with the runtime function +// maybe we still need to have it in linux builds +/* +char *strupr (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = toupper(*in); + in++; + } + return start; +} +*/ + +char *strlower (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = tolower(*in); + in++; + } + return start; +} + + +/* +============================================================================= + + MISC FUNCTIONS + +============================================================================= +*/ + + +/* +================= +CheckParm + +Checks for the given parameter in the program's command line arguments +Returns the argument number (1 to argc-1) or 0 if not present +================= +*/ +int CheckParm (const char *check) +{ + int i; + + for (i = 1;i 0) { + nAllocSize += MEM_BLOCKSIZE - nBlock; + } + buffer = safe_malloc (nAllocSize+1); + memset(buffer, 0, nAllocSize+1); + SafeRead (f, buffer, length); + fclose (f); + + *bufferptr = buffer; + return length; +} + + +/* +============== +TryLoadFile + +Allows failure +============== +*/ +int TryLoadFile (const char *filename, void **bufferptr) +{ + FILE *f; + int length; + void *buffer; + + *bufferptr = NULL; + + f = fopen (filename, "rb"); + if (!f) + return -1; + length = Q_filelength (f); + buffer = safe_malloc (length+1); + ((char *)buffer)[length] = 0; + SafeRead (f, buffer, length); + fclose (f); + + *bufferptr = buffer; + return length; +} + + +/* +============== +SaveFile +============== +*/ +void SaveFile (const char *filename, const void *buffer, int count) +{ + FILE *f; + + f = SafeOpenWrite (filename); + SafeWrite (f, buffer, count); + fclose (f); +} + + + +void DefaultExtension (char *path, const char *extension) +{ + char *src; +// +// if path doesnt have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen(path) - 1; + + while (*src != '/' && *src != '\\' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + strcat (path, extension); +} + + +void DefaultPath (char *path, const char *basepath) +{ + char temp[128]; + + if( path[ 0 ] == '/' || path[ 0 ] == '\\' ) + return; // absolute path location + strcpy (temp,path); + strcpy (path,basepath); + strcat (path,temp); +} + + +void StripFilename (char *path) +{ + int length; + + length = strlen(path)-1; + while (length > 0 && path[length] != '/' && path[ length ] != '\\' ) + length--; + path[length] = 0; +} + +void StripExtension (char *path) +{ + int length; + + length = strlen(path)-1; + while (length > 0 && path[length] != '.') + { + length--; + if (path[length] == '/' || path[ length ] == '\\' ) + return; // no extension + } + if (length) + path[length] = 0; +} + + +/* +==================== +Extract file parts +==================== +*/ +// FIXME: should include the slash, otherwise +// backing to an empty path will be wrong when appending a slash +void ExtractFilePath (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && *(src-1) != '\\' && *(src-1) != '/') + src--; + + memcpy (dest, path, src-path); + dest[src-path] = 0; +} + +void ExtractFileBase (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && *(src-1) != '/' && *(src-1) != '\\' ) + src--; + + while (*src && *src != '.') + { + *dest++ = *src++; + } + *dest = 0; +} + +void ExtractFileExtension (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a . or the start +// + while (src != path && *(src-1) != '.') + src--; + if (src == path) + { + *dest = 0; // no extension + return; + } + + strcpy (dest,src); +} + + +/* +============== +ParseNum / ParseHex +============== +*/ +int ParseHex (const char *hex) +{ + const char *str; + int num; + + num = 0; + str = hex; + + while (*str) + { + num <<= 4; + if (*str >= '0' && *str <= '9') + num += *str-'0'; + else if (*str >= 'a' && *str <= 'f') + num += 10 + *str-'a'; + else if (*str >= 'A' && *str <= 'F') + num += 10 + *str-'A'; + else + Error ("Bad hex number: %s",hex); + str++; + } + + return num; +} + + +int ParseNum (const char *str) +{ + if (str[0] == '$') + return ParseHex (str+1); + if (str[0] == '0' && str[1] == 'x') + return ParseHex (str+2); + return atol (str); +} +/* +// all output ends up through here +void FPrintf (int flag, char *buf) +{ + printf(buf); + +} + +void Sys_FPrintf (int flag, const char *format, ...) +{ + char out_buffer[4096]; + va_list argptr; + + if ((flag == SYS_VRB) && (verbose == false)) + return; + + va_start (argptr, format); + vsprintf (out_buffer, format, argptr); + va_end (argptr); + + FPrintf (flag, out_buffer); +} + +void Sys_Printf (const char *format, ...) +{ + char out_buffer[4096]; + va_list argptr; + + va_start (argptr, format); + vsprintf (out_buffer, format, argptr); + va_end (argptr); + + FPrintf (SYS_STD, out_buffer); +} + +//================= +//Error +// +//For abnormal program terminations +//================= + +void Error( const char *error, ...) +{ + char out_buffer[4096]; + char tmp[4096]; + va_list argptr; + + va_start (argptr,error); + vsprintf (tmp, error, argptr); + va_end (argptr); + + sprintf( out_buffer, "************ ERROR ************\n%s\n", tmp ); + + FPrintf( SYS_ERR, out_buffer ); + + exit (1); +} + +*/ + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +#ifdef _SGI_SOURCE +#define __BIG_ENDIAN__ +#endif + +#ifdef __BIG_ENDIAN__ + +short LittleShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short BigShort (short l) +{ + return l; +} + + +int LittleLong (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int BigLong (int l) +{ + return l; +} + + +float LittleFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float BigFloat (float l) +{ + return l; +} + + +#else + + +short BigShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short LittleShort (short l) +{ + return l; +} + + +int BigLong (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int LittleLong (int l) +{ + return l; +} + +float BigFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float LittleFloat (float l) +{ + return l; +} + + +#endif + + +//======================================================= + + +// FIXME: byte swap? + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +static unsigned short crctable[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +void CRC_Init(unsigned short *crcvalue) +{ + *crcvalue = CRC_INIT_VALUE; +} + +void CRC_ProcessByte(unsigned short *crcvalue, byte data) +{ + *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; +} + +unsigned short CRC_Value(unsigned short crcvalue) +{ + return crcvalue ^ CRC_XOR_VALUE; +} +//============================================================================= + +/* +============ +CreatePath +============ +*/ +void CreatePath (const char *path) +{ + const char *ofs; + char c; + char dir[1024]; + +#ifdef _WIN32 + int olddrive = -1; + + if ( path[1] == ':' ) + { + olddrive = _getdrive(); + _chdrive( toupper( path[0] ) - 'A' + 1 ); + } +#endif + + if (path[1] == ':') + path += 2; + + for (ofs = path+1 ; *ofs ; ofs++) + { + c = *ofs; + if (c == '/' || c == '\\') + { // create the directory + memcpy( dir, path, ofs - path ); + dir[ ofs - path ] = 0; + Q_mkdir( dir ); + } + } + +#ifdef _WIN32 + if ( olddrive != -1 ) + { + _chdrive( olddrive ); + } +#endif +} + + +/* +============ +QCopyFile + + Used to archive source files +============ +*/ +void QCopyFile (const char *from, const char *to) +{ + void *buffer; + int length; + + length = LoadFile (from, &buffer); + CreatePath (to); + SaveFile (to, buffer, length); + free (buffer); +} + +void Sys_Sleep(int n) +{ +#ifdef WIN32 + Sleep (n); +#endif +#if defined (__linux__) || defined (__APPLE__) + usleep (n * 1000); +#endif +} diff --git a/tools/quake2/qdata_heretic2/common/cmdlib.h b/tools/quake2/qdata_heretic2/common/cmdlib.h new file mode 100644 index 00000000..78d7a237 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/cmdlib.h @@ -0,0 +1,177 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// cmdlib.h + +#ifndef __CMDLIB__ +#define __CMDLIB__ + +#ifdef _WIN32 +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncate from double to float + +#pragma check_stack(off) + +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#pragma intrinsic( memset, memcpy ) + +#endif + +#ifndef __BYTEBOOL__ + #define __BYTEBOOL__ + //typedef enum {false, true} qboolean; + //typedef unsigned char byte; + #include "q_typedef.h" +#endif + +#define MAX_OS_PATH 1024 +#define MEM_BLOCKSIZE 4096 +/* +extern qboolean verbose; +#define SYS_VRB 0 // verbose support (on/off) +#define SYS_STD 1 // standard print level +#define SYS_WRN 2 // warnings +#define SYS_ERR 3 // error +*/ +// the dec offsetof macro doesnt work very well... +#define myoffsetof(type,identifier) ((size_t)&((type *)0)->identifier) + +#define SAFE_MALLOC +#ifdef SAFE_MALLOC +void *safe_malloc( size_t size ); +void *safe_malloc_info( size_t size, char* info ); +#else +#define safe_malloc(a) malloc(a) +#endif /* SAFE_MALLOC */ + +// set these before calling CheckParm +extern int myargc; +extern char **myargv; + +char *strlower (char *in); +int Q_strncasecmp( const char *s1, const char *s2, int n ); +int Q_stricmp( const char *s1, const char *s2 ); +int Q_strcasecmp( const char *s1, const char *s2 ); +void Q_getwd( char *out ); + +int Q_filelength (FILE *f); +int FileTime( const char *path ); + +void Q_mkdir( const char *path ); + +extern char qdir[1024]; +extern char gamedir[1024]; +extern char writedir[1024]; +extern char *moddirparam; +void SetQdirFromPath( const char *path); +char *ExpandArg( const char *path ); // from cmd line +char *ExpandPath( const char *path ); // from scripts +char *ExpandGamePath (const char *path); +char *ExpandPathAndArchive( const char *path ); +void ExpandWildcards( int *argc, char ***argv ); + + +double I_FloatTime( void ); + +int CheckParm( const char *check ); + +void *SafeMalloc(size_t n, char *desc); +FILE *SafeOpenWrite( const char *filename ); +FILE *SafeOpenRead( const char *filename ); +void SafeRead (FILE *f, void *buffer, int count); +void SafeWrite (FILE *f, const void *buffer, int count); + +int LoadFile( const char *filename, void **bufferptr ); +int LoadFileBlock( const char *filename, void **bufferptr ); +int TryLoadFile( const char *filename, void **bufferptr ); +void SaveFile( const char *filename, const void *buffer, int count ); +qboolean FileExists( const char *filename ); + +void DefaultExtension( char *path, const char *extension ); +void DefaultPath( char *path, const char *basepath ); +void StripFilename( char *path ); +void StripExtension( char *path ); + +void ExtractFilePath( const char *path, char *dest ); +void ExtractFileBase( const char *path, char *dest ); +void ExtractFileExtension( const char *path, char *dest ); + +int ParseNum (const char *str); +/* +void Sys_Printf (const char *text, ...); +void Sys_FPrintf (int flag, const char *text, ...); +void Error( const char *error, ... ); +*/ +short BigShort (short l); +short LittleShort (short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); + + +char *COM_Parse (char *data); + +extern char com_token[1024]; +extern qboolean com_eof; + +char *copystring(const char *s); + + +void CRC_Init(unsigned short *crcvalue); +void CRC_ProcessByte(unsigned short *crcvalue, byte data); +unsigned short CRC_Value(unsigned short crcvalue); + +void CreatePath( const char *path ); +void QCopyFile( const char *from, const char *to ); + +extern qboolean archive; +extern char archivedir[1024]; + +extern qboolean g_dokeypress; + +// sleep for the given amount of milliseconds +void Sys_Sleep(int n); + +// for compression routines +typedef struct +{ + byte *data; + int count; +} cblock_t; + + +#endif diff --git a/tools/quake2/qdata_heretic2/common/her2_threads.h b/tools/quake2/qdata_heretic2/common/her2_threads.h new file mode 100644 index 00000000..339b0c86 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/her2_threads.h @@ -0,0 +1,35 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef _THREADS_H + +#define _THREADS_H + + +extern int numthreads; + +void ThreadSetDefault (void); +int GetThreadWork (void); +void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)); +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)); +void ThreadLock (void); +void ThreadUnlock (void); + +#endif //_THREADS_H diff --git a/tools/quake2/qdata_heretic2/common/inout.c b/tools/quake2/qdata_heretic2/common/inout.c new file mode 100644 index 00000000..3cf66e7f --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/inout.c @@ -0,0 +1,367 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +//----------------------------------------------------------------------------- +// +// +// DESCRIPTION: +// deal with in/out tasks, for either stdin/stdout or network/XML stream +// + +#include "cmdlib.h" +#include "mathlib.h" +#include "polylib.h" +#include "inout.h" +#include +#include + +#ifdef WIN32 +#include +#include +#endif + +// network broadcasting +#include "l_net/l_net.h" +#include "libxml/tree.h" + +#ifdef WIN32 +HWND hwndOut = NULL; +qboolean lookedForServer = false; +UINT wm_BroadcastCommand = -1; +#endif + +socket_t *brdcst_socket; +netmessage_t msg; + +qboolean verbose = false; + +// our main document +// is streamed through the network to Radiant +// possibly written to disk at the end of the run +//++timo FIXME: need to be global, required when creating nodes? +xmlDocPtr doc; +xmlNodePtr tree; + +// some useful stuff +xmlNodePtr xml_NodeForVec( vec3_t v ) +{ + xmlNodePtr ret; + char buf[1024]; + + sprintf (buf, "%f %f %f", v[0], v[1], v[2]); + ret = xmlNewNode (NULL, "point"); + xmlNodeSetContent (ret, buf); + return ret; +} + +// send a node down the stream, add it to the document +void xml_SendNode (xmlNodePtr node) +{ + xmlBufferPtr xml_buf; + char xmlbuf[MAX_NETMESSAGE]; // we have to copy content from the xmlBufferPtr into an aux buffer .. that sucks .. + // this index loops through the node buffer + int pos = 0; + int size; + + xmlAddChild( doc->children, node ); + + if (brdcst_socket) + { + xml_buf = xmlBufferCreate(); + xmlNodeDump( xml_buf, doc, node, 0, 0 ); + + // the XML node might be too big to fit in a single network message + // l_net library defines an upper limit of MAX_NETMESSAGE + // there are some size check errors, so we use MAX_NETMESSAGE-10 to be safe + // if the size of the buffer exceeds MAX_NETMESSAGE-10 we'll send in several network messages + while (pos < xml_buf->use) + { + // what size are we gonna send now? + (xml_buf->use - pos < MAX_NETMESSAGE - 10) ? (size = xml_buf->use - pos) : (size = MAX_NETMESSAGE - 10); + //++timo just a debug thing + if (size == MAX_NETMESSAGE - 10) + Sys_FPrintf (SYS_NOXML, "Got to split the buffer\n"); + memcpy( xmlbuf, xml_buf->content+pos, size); + xmlbuf[size] = '\0'; + NMSG_Clear( &msg ); + NMSG_WriteString (&msg, xmlbuf ); + Net_Send(brdcst_socket, &msg ); + // now that the thing is sent prepare to loop again + pos += size; + } + +#if 0 + // NOTE: the NMSG_WriteString is limited to MAX_NETMESSAGE + // we will need to split into chunks + // (we could also go lower level, in the end it's using send and receiv which are not size limited) + //++timo FIXME: MAX_NETMESSAGE is not exactly the max size we can stick in the message + // there's some tweaking to do in l_net for that .. so let's give us a margin for now + + //++timo we need to handle the case of a buffer too big to fit in a single message + // try without checks for now + if (xml_buf->use > MAX_NETMESSAGE-10 ) + { + // if we send that we are probably gonna break the stream at the other end.. + // and Error will call right there + //Error( "MAX_NETMESSAGE exceeded for XML feedback stream in FPrintf (%d)\n", xml_buf->use); + Sys_FPrintf (SYS_NOXML, "MAX_NETMESSAGE exceeded for XML feedback stream in FPrintf (%d)\n", xml_buf->use); + xml_buf->content[xml_buf->use]='\0'; //++timo this corrupts the buffer but we don't care it's for printing + Sys_FPrintf (SYS_NOXML, xml_buf->content); + + } + + size = xml_buf->use; + memcpy( xmlbuf, xml_buf->content, size ); + xmlbuf[size] = '\0'; + NMSG_Clear( &msg ); + NMSG_WriteString (&msg, xmlbuf ); + Net_Send(brdcst_socket, &msg ); +#endif + + xmlBufferFree( xml_buf ); + } +} + +void xml_Select (char *msg, int entitynum, int brushnum, qboolean bError) +{ + xmlNodePtr node, select; + char buf[1024]; + char level[2]; + + // now build a proper "select" XML node + sprintf (buf, "Entity %i, Brush %i: %s", entitynum, brushnum, msg); + node = xmlNewNode (NULL, "select"); + xmlNodeSetContent (node, buf); + level[0] = (int)'0' + (bError ? SYS_ERR : SYS_WRN) ; + level[1] = 0; + xmlSetProp (node, "level", (char *)&level); + // a 'select' information + sprintf (buf, "%i %i", entitynum, brushnum); + select = xmlNewNode (NULL, "brush"); + xmlNodeSetContent (select, buf); + xmlAddChild (node, select); + xml_SendNode (node); + + sprintf (buf, "Entity %i, Brush %i: %s", entitynum, brushnum, msg); + if (bError) + Error(buf); + else + Sys_FPrintf (SYS_NOXML, "%s\n", buf); + +} + +void xml_Point (char *msg, vec3_t pt) +{ + xmlNodePtr node, point; + char buf[1024]; + char level[2]; + + node = xmlNewNode (NULL, "pointmsg"); + xmlNodeSetContent (node, msg); + level[0] = (int)'0' + SYS_ERR; + level[1] = 0; + xmlSetProp (node, "level", (char *)&level); + // a 'point' node + sprintf (buf, "%g %g %g", pt[0], pt[1], pt[2]); + point = xmlNewNode (NULL, "point"); + xmlNodeSetContent (point, buf); + xmlAddChild (node, point); + xml_SendNode (node); + + sprintf (buf, "%s (%g %g %g)", msg, pt[0], pt[1], pt[2]); + Error (buf); +} + +#define WINDING_BUFSIZE 2048 +void xml_Winding (char *msg, vec3_t p[], int numpoints, qboolean die) +{ + xmlNodePtr node, winding; + char buf[WINDING_BUFSIZE]; + char smlbuf[128]; + char level[2]; + int i; + + node = xmlNewNode (NULL, "windingmsg"); + xmlNodeSetContent (node, msg); + level[0] = (int)'0' + SYS_ERR; + level[1] = 0; + xmlSetProp (node, "level", (char *)&level); + // a 'winding' node + sprintf( buf, "%i ", numpoints); + for(i = 0; i < numpoints; i++) + { + sprintf (smlbuf, "(%g %g %g)", p[i][0], p[i][1], p[i][2]); + // don't overflow + if (strlen(buf)+strlen(smlbuf)>WINDING_BUFSIZE) + break; + strcat( buf, smlbuf); + } + + winding = xmlNewNode (NULL, "winding"); + xmlNodeSetContent (winding, buf); + xmlAddChild (node, winding); + xml_SendNode (node); + + if(die) + Error (msg); + else + { + Sys_Printf(msg); + Sys_Printf("\n"); + } +} + +// in include +#include "stream_version.h" + +void Broadcast_Setup( const char *dest ) +{ + address_t address; + char sMsg[1024]; + + Net_Setup(); + Net_StringToAddress((char *)dest, &address); + brdcst_socket = Net_Connect(&address, 0); + if (brdcst_socket) + { + // send in a header + sprintf (sMsg, ""); + NMSG_Clear( &msg ); + NMSG_WriteString(&msg, sMsg ); + Net_Send(brdcst_socket, &msg ); + } +} + +void Broadcast_Shutdown() +{ + if (brdcst_socket) + { + Sys_Printf("Disconnecting\n"); + Net_Disconnect(brdcst_socket); + brdcst_socket = NULL; + } +} + +// all output ends up through here +void FPrintf (int flag, char *buf) +{ + xmlNodePtr node; + static qboolean bGotXML = false; + char level[2]; + + printf(buf); + + // the following part is XML stuff only.. but maybe we don't want that message to go down the XML pipe? + if (flag == SYS_NOXML) + return; + + // ouput an XML file of the run + // use the DOM interface to build a tree + /* + + message string + .. various nodes to describe corresponding geometry .. + + */ + if (!bGotXML) + { + // initialize + doc = xmlNewDoc("1.0"); + doc->children = xmlNewDocRawNode(doc, NULL, "q3map_feedback", NULL); + bGotXML = true; + } + node = xmlNewNode (NULL, "message"); + xmlNodeSetContent (node, buf); + level[0] = (int)'0' + flag; + level[1] = 0; + xmlSetProp (node, "level", (char *)&level ); + + xml_SendNode (node); +} + +#ifdef DBG_XML +void DumpXML() +{ + xmlSaveFile( "XMLDump.xml", doc ); +} +#endif + +void Sys_FPrintf (int flag, const char *format, ...) +{ + char out_buffer[4096]; + va_list argptr; + + if ((flag == SYS_VRB) && (verbose == false)) + return; + + va_start (argptr, format); + vsprintf (out_buffer, format, argptr); + va_end (argptr); + + FPrintf (flag, out_buffer); +} + +void Sys_Printf (const char *format, ...) +{ + char out_buffer[4096]; + va_list argptr; + + va_start (argptr, format); + vsprintf (out_buffer, format, argptr); + va_end (argptr); + + FPrintf (SYS_STD, out_buffer); +} + +/* +================= +Error + +For abnormal program terminations +================= +*/ +void Error( const char *error, ...) +{ + char out_buffer[4096]; + char tmp[4096]; + va_list argptr; + + va_start (argptr,error); + vsprintf (tmp, error, argptr); + va_end (argptr); + + sprintf( out_buffer, "************ ERROR ************\n%s\n", tmp ); + + FPrintf( SYS_ERR, out_buffer ); + +#ifdef DBG_XML + DumpXML(); +#endif + + //++timo HACK ALERT .. if we shut down too fast the xml stream won't reach the listener. + // a clean solution is to send a sync request node in the stream and wait for an answer before exiting + Sys_Sleep( 1000 ); + + Broadcast_Shutdown(); + + exit (1); +} + diff --git a/tools/quake2/qdata_heretic2/common/inout.h b/tools/quake2/qdata_heretic2/common/inout.h new file mode 100644 index 00000000..75ef55ee --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/inout.h @@ -0,0 +1,63 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __INOUT__ +#define __INOUT__ + +// inout is the only stuff relying on xml, include the headers there +#include "libxml/tree.h" +#include "mathlib.h" + +// some useful xml routines +xmlNodePtr xml_NodeForVec( vec3_t v ); +void xml_SendNode (xmlNodePtr node); +// print a message in q3map output and send the corresponding select information down the xml stream +// bError: do we end with an error on this one or do we go ahead? +void xml_Select (char *msg, int entitynum, int brushnum, qboolean bError); +// end q3map with an error message and send a point information in the xml stream +// note: we might want to add a boolean to use this as a warning or an error thing.. +void xml_Winding (char *msg, vec3_t p[], int numpoints, qboolean die); +void xml_Point (char *msg, vec3_t pt); + +extern qboolean bNetworkBroadcast; +void Broadcast_Setup( const char *dest ); +void Broadcast_Shutdown(); + +#define SYS_VRB 0 // verbose support (on/off) +#define SYS_STD 1 // standard print level +#define SYS_WRN 2 // warnings +#define SYS_ERR 3 // error +#define SYS_NOXML 4 // don't send that down the XML stream + +extern qboolean verbose; +void Sys_Printf (const char *text, ...); +void Sys_FPrintf (int flag, const char *text, ...); +void Error( const char *error, ...); + +#ifdef _DEBUG +#define DBG_XML 1 +#endif + +#ifdef DBG_XML +void DumpXML(); +#endif + +#endif diff --git a/tools/quake2/qdata_heretic2/common/l3dslib.c b/tools/quake2/qdata_heretic2/common/l3dslib.c new file mode 100644 index 00000000..853ff1ca --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/l3dslib.c @@ -0,0 +1,476 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// l3dslib.c: library for loading triangles from an Alias triangle file +// + +#include +#include "cmdlib.h" +#include "inout.h" +#include "mathlib.h" +#include "trilib.h" +#include "l3dslib.h" +#include "token.h" +#include "fmodel.h" +#include "bspfile.h" + +#define MAIN3DS 0x4D4D +#define EDIT3DS 0x3D3D // this is the start of the editor config +#define EDIT_OBJECT 0x4000 +#define OBJ_TRIMESH 0x4100 +#define TRI_VERTEXL 0x4110 +#define TRI_FACEL1 0x4120 + +#define MAXVERTS 2000 + +typedef struct { + int v[4]; +} tri; + +float fverts[MAXVERTS][3]; +tri tris[MAXTRIANGLES]; + +int bytesread, level, numtris, totaltris; +int vertsfound, trisfound; + +triangle_t *ptri; + + + +void DefaultNodesList(mesh_node_t **nodesList, int *num_mesh_nodes, int *numtriangles) +{ + int pos, bit, i; + + if (nodesList) + { + *num_mesh_nodes = 1; + memset(&(*nodesList)[0], 0, sizeof(mesh_node_t)); + strcpy((*nodesList)[0].name, "default"); + + // set all of the tris to be used for the top node + for(i = 0; i < (*numtriangles); i++) + { + pos = (i) >> 3; + bit = 1 << ((i) & 7 ); + + (*nodesList)[0].tris[pos] |= bit; + } + } +} + + +// Alias stores triangles as 3 explicit vertices in .tri files, so even though we +// start out with a vertex pool and vertex indices for triangles, we have to convert +// to raw, explicit triangles +void StoreAliasTriangles (void) +{ + int i, j, k; + + if ((totaltris + numtris) > MAXTRIANGLES) + Error ("Error: Too many triangles"); + + for (i=0; i MAXVERTS) + Error ("Error: Too many vertices"); + + for (i=0 ; i MAXTRIANGLES) + Error ("Error: Too many triangles"); + + for (i=0 ; i 0) + { + w -= ParseChunk (input); + } + + retval = length; + goto Done; + + default: + // skip other chunks + while (w > 0) + { + t = w; + + if (t > BLOCK_SIZE) + t = BLOCK_SIZE; + + if (feof(input)) + Error ("Error: unexpected end of file"); + + fread (&temp, t, 1, input); + bytesread += t; + + w -= t; + } + + retval = length; + goto Done; + } + +Done: + level--; + return retval; +} + + +void Load3DSTriangleList (char *filename, triangle_t **pptri, int *numtriangles, mesh_node_t **nodesList, int *num_mesh_nodes) +{ + FILE *input; + short int tshort; + + if (nodesList) + { + *num_mesh_nodes = 0; + *nodesList = (mesh_node_t *) SafeMalloc(MAX_FM_MESH_NODES * sizeof(mesh_node_t), "Mesh Node List"); + } + + bytesread = 0; + level = 0; + numtris = 0; + totaltris = 0; + vertsfound = 0; + trisfound = 0; + + if ((input = fopen(filename, "rb")) == 0) { + fprintf(stderr,"reader: could not open file '%s'\n", filename); + exit(0); + } + + fread(&tshort, sizeof(tshort), 1, input); + +// should only be MAIN3DS, but some files seem to start with EDIT3DS, with +// no MAIN3DS + if ((tshort != MAIN3DS) && (tshort != EDIT3DS)) { + fprintf(stderr,"File is not a 3DS file.\n"); + exit(0); + } + +// back to top of file so we can parse the first chunk descriptor + fseek(input, 0, SEEK_SET); + + ptri = malloc (MAXTRIANGLES * sizeof(triangle_t)); + + *pptri = ptri; + +// parse through looking for the relevant chunk tree (MAIN3DS | EDIT3DS | EDIT_OBJECT | +// OBJ_TRIMESH | {TRI_VERTEXL, TRI_FACEL1}) and skipping other chunks + ParseChunk (input); + + if (vertsfound || trisfound) + Error ("Incomplete triangle set"); + + *numtriangles = totaltris; + + fclose (input); + + DefaultNodesList(nodesList,num_mesh_nodes,numtriangles); +} + +//========================================================================== +// +// LoadASC +// +//========================================================================== + +void LoadASC(char *fileName, triangle_t **triList, int *triangleCount, mesh_node_t **nodesList, int *num_mesh_nodes) +{ + int i, j; + int vertexCount; + struct + { + float v[3]; + } *vList; + int triCount; + triangle_t *tList; + float x, y, z; +// float x2, y2, z2; +// float rx, ry, rz; + qboolean goodObject; + + if (nodesList) + { + *num_mesh_nodes = 0; + *nodesList = (mesh_node_t *) SafeMalloc(MAX_FM_MESH_NODES * sizeof(mesh_node_t), "Mesh Node List"); + } + + TK_OpenSource(fileName); + + goodObject = false; + while(goodObject == false) + { + TK_Beyond(TK_C_NAMED); + TK_Beyond(TK_OBJECT); + TK_Beyond(TK_C_TRI); + TK_Beyond(TK_MESH); + TK_BeyondRequire(TK_C_VERTICES, TK_COLON); + TK_FetchRequire(TK_INTNUMBER); + vertexCount = tk_IntNumber; + if(vertexCount > 0) + { + goodObject = true; + } + } + TK_BeyondRequire(TK_C_FACES, TK_COLON); + TK_FetchRequire(TK_INTNUMBER); + triCount = tk_IntNumber; + if(triCount >= MAXTRIANGLES) + { + Error("Too many triangles in file %s\n", fileName); + } + *triangleCount = triCount; + tList = (triangle_t *) SafeMalloc(MAXTRIANGLES*sizeof(triangle_t), "Triangle list"); + *triList = tList; + + memset(*triList,0,MAXTRIANGLES*sizeof(triangle_t)); + TK_BeyondRequire(TK_C_VERTEX, TK_LIST); + +/* rx = ((rotation[0]+90.0)/360.0)*2.0*M_PI; + //rx = (rotation[0]/360.0)*2.0*M_PI; + ry = (rotation[1]/360.0)*2.0*M_PI; + rz = (rotation[2]/360.0)*2.0*M_PI; +*/ + vList = (void *) SafeMalloc(vertexCount*sizeof vList[0], "Vertex list"); + for(i = 0; i < vertexCount; i++) + { + TK_BeyondRequire(TK_C_VERTEX, TK_INTNUMBER); + if(tk_IntNumber != i) + { + Error("File '%s', line %d:\nVertex index mismatch.\n", + tk_SourceName, tk_Line); + } + TK_FetchRequireFetch(TK_COLON); + + TK_BeyondRequire(TK_COLON, TK_FLOATNUMBER); + x = tk_FloatNumber; + TK_BeyondRequire(TK_COLON, TK_FLOATNUMBER); + y = tk_FloatNumber; + TK_BeyondRequire(TK_COLON, TK_FLOATNUMBER); + z = tk_FloatNumber; + +/* x2 = x*cos(rz)+y*sin(rz); + y2 = -x*sin(rz)+y*cos(rz); + x = x2; + y = y2; + y2 = y*cos(rx)+z*sin(rx); + z2 = -y*sin(rx)+z*cos(rx); + y = y2; + z = z2; + x2 = x*cos(ry)-z*sin(ry); + z2 = x*sin(ry)+z*cos(ry); + x = x2; + z = z2; +*/ + vList[i].v[0] = x; + vList[i].v[1] = y; + vList[i].v[2] = z; + } + TK_BeyondRequire(TK_C_FACE, TK_LIST); + for(i = 0; i < triCount; i++) + { + TK_BeyondRequire(TK_C_FACE, TK_INTNUMBER); + if(tk_IntNumber != i) + { + Error("File '%s', line %d:\nTriangle index mismatch.\n", + tk_SourceName, tk_Line); + } + for(j = 0; j < 3; j++) + { + TK_BeyondRequire(TK_IDENTIFIER, TK_COLON); + TK_FetchRequire(TK_INTNUMBER); + if(tk_IntNumber >= vertexCount) + { + Error("File '%s', line %d:\nVertex number" + " > vertexCount: %d\n", tk_SourceName, tk_Line, + tk_IntNumber); + } + tList[i].verts[2-j][0] = vList[tk_IntNumber].v[0]; + tList[i].verts[2-j][1] = vList[tk_IntNumber].v[1]; + tList[i].verts[2-j][2] = vList[tk_IntNumber].v[2]; +#ifdef _QDATA + tList[i].indicies[2-j] = tk_IntNumber; +#endif + } + +/* printf("Face %i:\n v0: %f, %f, %f\n v1: %f, %f, %f\n" + " v2: %f, %f, %f\n", i, + tList[i].verts[0][0], + tList[i].verts[0][1], + tList[i].verts[0][2], + tList[i].verts[1][0], + tList[i].verts[1][1], + tList[i].verts[1][2], + tList[i].verts[2][0], + tList[i].verts[2][1], + tList[i].verts[2][2]); +*/ + } + + DefaultNodesList(nodesList,num_mesh_nodes,triangleCount); +} diff --git a/tools/quake2/qdata_heretic2/common/l3dslib.h b/tools/quake2/qdata_heretic2/common/l3dslib.h new file mode 100644 index 00000000..b20391c6 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/l3dslib.h @@ -0,0 +1,28 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// l3dslib.h: header file for loading triangles from a 3DS triangle file +// +void DefaultNodesList(mesh_node_t **nodesList, int *num_mesh_nodes, int *numtriangles); + +void Load3DSTriangleList (char *filename, triangle_t **pptri, int *numtriangles, mesh_node_t **ppmnodes, int *num_mesh_nodes); +void LoadASC(char *fileName, triangle_t **triList, int *triangleCount, mesh_node_t **ppmnodes, int *num_mesh_nodes); diff --git a/tools/quake2/qdata_heretic2/common/lbmlib.c b/tools/quake2/qdata_heretic2/common/lbmlib.c new file mode 100644 index 00000000..5ea39bd9 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/lbmlib.c @@ -0,0 +1,1052 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// lbmlib.c + +#include "cmdlib.h" +#include "inout.h" +#include "lbmlib.h" + +// Ups the palette values so no pixels except 0 appear transparent +// Need a value of 8 to cater for 16bit renderers + +typedef struct +{ + byte r; + byte g; + byte b; +} paletteRGB_t; + + +void CorrectPalette(byte *pal) +{ + paletteRGB_t *p; + + p = (paletteRGB_t *)pal; + // Color 0 always transparent + p->r = 0; + p->g = 0; + p->b = 0; +} + +/* +============================================================================ + + LBM STUFF + +============================================================================ +*/ + + +typedef unsigned char UBYTE; +//conflicts with windows typedef short WORD; +typedef unsigned short UWORD; +typedef long LONG; + +typedef enum +{ + ms_none, + ms_mask, + ms_transcolor, + ms_lasso +} mask_t; + +typedef enum +{ + cm_none, + cm_rle1 +} compress_t; + +typedef struct +{ + UWORD w,h; + short x,y; + UBYTE nPlanes; + UBYTE masking; + UBYTE compression; + UBYTE pad1; + UWORD transparentColor; + UBYTE xAspect,yAspect; + short pageWidth,pageHeight; +} bmhd_t; + +extern bmhd_t bmhd; // will be in native byte order + + + +#define FORMID ('F'+('O'<<8)+((int)'R'<<16)+((int)'M'<<24)) +#define ILBMID ('I'+('L'<<8)+((int)'B'<<16)+((int)'M'<<24)) +#define PBMID ('P'+('B'<<8)+((int)'M'<<16)+((int)' '<<24)) +#define BMHDID ('B'+('M'<<8)+((int)'H'<<16)+((int)'D'<<24)) +#define BODYID ('B'+('O'<<8)+((int)'D'<<16)+((int)'Y'<<24)) +#define CMAPID ('C'+('M'<<8)+((int)'A'<<16)+((int)'P'<<24)) + + +bmhd_t bmhd; + +int Align (int l) +{ + if (l&1) + return l+1; + return l; +} + + + +/* +================ +LBMRLEdecompress + +Source must be evenly aligned! +================ +*/ +byte *LBMRLEDecompress (byte *source,byte *unpacked, int bpwidth) +{ + int count; + byte b,rept; + + count = 0; + + do + { + rept = *source++; + + if (rept > 0x80) + { + rept = (rept^0xff)+2; + b = *source++; + memset(unpacked,b,rept); + unpacked += rept; + } + else if (rept < 0x80) + { + rept++; + memcpy(unpacked,source,rept); + unpacked += rept; + source += rept; + } + else + rept = 0; // rept of 0x80 is NOP + + count += rept; + + } while (countbpwidth) + Error ("Decompression exceeded width!\n"); + + + return source; +} + + +/* +================= +LoadLBM +================= +*/ +void LoadLBM (char *filename, byte **picture, byte **palette) +{ + byte *LBMbuffer, *picbuffer, *cmapbuffer; + int y; + byte *LBM_P, *LBMEND_P; + byte *pic_p; + byte *body_p; + + int formtype,formlength; + int chunktype,chunklength; + +// qiet compiler warnings + picbuffer = NULL; + cmapbuffer = NULL; + +// +// load the LBM +// + LoadFile (filename, (void **)&LBMbuffer); + +// +// parse the LBM header +// + LBM_P = LBMbuffer; + if ( *(int *)LBMbuffer != LittleLong(FORMID) ) + Error ("No FORM ID at start of file!\n"); + + LBM_P += 4; + formlength = BigLong( *(int *)LBM_P ); + LBM_P += 4; + LBMEND_P = LBM_P + Align(formlength); + + formtype = LittleLong(*(int *)LBM_P); + + if (formtype != ILBMID && formtype != PBMID) + Error ("Unrecognized form type: %c%c%c%c\n", formtype&0xff + ,(formtype>>8)&0xff,(formtype>>16)&0xff,(formtype>>24)&0xff); + + LBM_P += 4; + +// +// parse chunks +// + + while (LBM_P < LBMEND_P) + { + chunktype = LBM_P[0] + (LBM_P[1]<<8) + (LBM_P[2]<<16) + (LBM_P[3]<<24); + LBM_P += 4; + chunklength = LBM_P[3] + (LBM_P[2]<<8) + (LBM_P[1]<<16) + (LBM_P[0]<<24); + LBM_P += 4; + + switch ( chunktype ) + { + case BMHDID: + memcpy (&bmhd,LBM_P,sizeof(bmhd)); + bmhd.w = BigShort(bmhd.w); + bmhd.h = BigShort(bmhd.h); + bmhd.x = BigShort(bmhd.x); + bmhd.y = BigShort(bmhd.y); + bmhd.pageWidth = BigShort(bmhd.pageWidth); + bmhd.pageHeight = BigShort(bmhd.pageHeight); + break; + + case CMAPID: + cmapbuffer = malloc (768); + memset (cmapbuffer, 0, 768); + memcpy (cmapbuffer, LBM_P, chunklength); + CorrectPalette(cmapbuffer); + break; + + case BODYID: + body_p = LBM_P; + + pic_p = picbuffer = malloc (bmhd.w*bmhd.h); + if (formtype == PBMID) + { + // + // unpack PBM + // + for (y=0 ; ydata; + + pcx->xmin = LittleShort(pcx->xmin); + pcx->ymin = LittleShort(pcx->ymin); + pcx->xmax = LittleShort(pcx->xmax); + pcx->ymax = LittleShort(pcx->ymax); + pcx->hres = LittleShort(pcx->hres); + pcx->vres = LittleShort(pcx->vres); + pcx->bytes_per_line = LittleShort(pcx->bytes_per_line); + pcx->palette_type = LittleShort(pcx->palette_type); + + if (pcx->manufacturer != 0x0a + || pcx->version != 5 + || pcx->encoding != 1 + || pcx->bits_per_pixel != 8 + || pcx->xmax >= 640 + || pcx->ymax >= 480) + Error ("Bad pcx file %s", filename); + + if (palette) + { + *palette = malloc(768); + memcpy (*palette, (byte *)pcx + len - 768, 768); + CorrectPalette(*palette); + } + + if (width) + *width = pcx->xmax+1; + if (height) + *height = pcx->ymax+1; + + if (!pic) + return; + + out = malloc ( (pcx->ymax+1) * (pcx->xmax+1) ); + if (!out) + Error ("Skin_Cache: couldn't allocate"); + + *pic = out; + + pix = out; + + for (y=0 ; y<=pcx->ymax ; y++, pix += pcx->xmax+1) + { + for (x=0 ; x<=pcx->xmax ; ) + { + dataByte = *raw++; + + if((dataByte & 0xC0) == 0xC0) + { + runLength = dataByte & 0x3F; + dataByte = *raw++; + } + else + runLength = 1; + + while(runLength-- > 0) + pix[x++] = dataByte; + } + + } + + if ( raw - (byte *)pcx > len) + Error ("PCX file %s was malformed", filename); + + free (pcx); +} + +/* +============== +WritePCXfile +============== +*/ + +void StuffPackedByte(int curRepCount, byte curByte, byte** packPtr) +{ + byte* pack; + + pack = *packPtr; + + while(curRepCount > 0) + { + if (curRepCount == 1) + { + if ((curByte & 0xc0) != 0xc0) + { + *pack++ = curByte; + } + else + { + *pack++ = 0xc1; + *pack++ = curByte; + } + break; + } + if (curRepCount < 0x0040) + { + *pack++ = (0x00c0 | curRepCount); + curRepCount = 0; + } + else + { + *pack++ = 0xff; + curRepCount -= 0x003f; + } + *pack++ = curByte; + } + *packPtr = pack; +} + +void WritePCXfile (char *filename, byte *data, + int width, int height, byte *palette) +{ + int i, j, length; + pcx_t *pcx; + byte *pack; + byte curByte; + int curRepCount; + + pcx = malloc (width*height*2+1000); + memset (pcx, 0, sizeof(*pcx)); + + pcx->manufacturer = 0x0a; // PCX id + pcx->version = 5; // 256 color + pcx->encoding = 1; // RLE + pcx->bits_per_pixel = 8; // 256 color + pcx->xmin = 0; + pcx->ymin = 0; + pcx->xmax = LittleShort((short)(width-1)); + pcx->ymax = LittleShort((short)(height-1)); + pcx->hres = LittleShort((short)width); + pcx->vres = LittleShort((short)height); + pcx->color_planes = 1; // chunky image + pcx->bytes_per_line = LittleShort((short)width); + pcx->palette_type = LittleShort(1); // not a grey scale + + // pack the image + pack = &pcx->data; + +/* for (i=0 ; i= rows) + goto breakOut; + pixbuf += rowOffset; + rowBuf = pixbuf; + } + } + } + else + { // non run-length packet + for(j = 0; j < packetSize; j++) + { + switch (targa_header.pixel_size) + { + case 24: + blue = getc(fin); + green = getc(fin); + red = getc(fin); + rowBuf[0] = red; + rowBuf[1] = green; + rowBuf[2] = blue; + rowBuf[3] = 255; + rowBuf += pixDirection; + break; + case 32: + blue = getc(fin); + green = getc(fin); + red = getc(fin); + alphabyte = getc(fin); + rowBuf[0] = red; + rowBuf[1] = green; + rowBuf[2] = blue; + rowBuf[3] = alphabyte; + rowBuf += pixDirection; + break; + } + column++; + if (column == columns) + { // pixel packet run spans across rows + column = 0; + row++; + if (row >= rows) + goto breakOut; + pixbuf += rowOffset; + rowBuf = pixbuf; + } + } + } + } + breakOut:; + pixbuf += rowOffset; + } + } + fclose(fin); +} + +void MergeAlpha(byte *pix, byte *alpha, byte *pal, byte **out, int width, int height) +{ + int size, i; + byte *data, *src, *srca; + + size = width * height; + data = malloc(size * 4); + if(!data) + Error("Could not allocate memory for true color image"); + + *out = data; + src = pix; + srca = alpha; + + for(i = 0; i < size; i++, src++, srca++) + { + *data++ = pal[*src * 3 + 0]; // r + *data++ = pal[*src * 3 + 1]; // g + *data++ = pal[*src * 3 + 2]; // b + *data++ = *srca; // a + } + free(pix); + free(alpha); + free(pal); +} + +/* +============== +LoadAnyImage + +Return Value: + false: paletted texture + true: true color RGBA image (no palette) +============== +*/ +qboolean LoadAnyImage (char *name, byte **pixels, byte **palette, int *width, int *height) +{ + char ext[128]; + int len; + int alpha_width, alpha_height; + char alpha_name[128]; + byte *alpha_pixels; + + ExtractFileExtension (name, ext); + + if(palette) + { + *palette = NULL; + } + + if (!Q_strcasecmp (ext, "lbm")) + { + LoadLBM (name, pixels, palette); + if (width) + *width = bmhd.w; + if (height) + *height = bmhd.h; + return false; + } + else if (!Q_strcasecmp (ext, "pcx")) + { + len = strlen(name); + strcpy(alpha_name, name); + strcpy(&alpha_name[len - 4], "_a.pcx"); // Alpha map name (may not exist) + + if(FileExists(alpha_name)) + { + LoadPCX (name, pixels, palette, width, height); // Load in image + LoadPCX (alpha_name, &alpha_pixels, NULL, &alpha_width, &alpha_height); // Load in alpha map + if((*width != alpha_width) || (*height != alpha_height)) + { + Error("Alpha image dimensions not equal to graphic image dimensions"); + } + MergeAlpha(*pixels, alpha_pixels, *palette, pixels, *width, *height); + *palette = NULL;//Merge Frees pal + return true; + } + else + { + LoadPCX (name, pixels, palette, width, height); // Load in image + return false; + } + } + else if (!Q_strcasecmp (ext, "tga")) + { + LoadTGA(name, pixels, width, height); + if (palette) + { + *palette = NULL; + } + + return true; + } + else + Error ("%s doesn't have a known image extension", name); + + return false; +} + diff --git a/tools/quake2/qdata_heretic2/common/lbmlib.h b/tools/quake2/qdata_heretic2/common/lbmlib.h new file mode 100644 index 00000000..5a5f46f2 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/lbmlib.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// piclib.h + + +void LoadLBM (char *filename, byte **picture, byte **palette); +void WriteLBMfile (char *filename, byte *data, int width, int height + , byte *palette); +void LoadPCX (char *filename, byte **picture, byte **palette, int *width, int *height); +void WritePCXfile (char *filename, byte *data, int width, int height + , byte *palette); + +// loads / saves either lbm or pcx, depending on extension +void Load256Image (char *name, byte **pixels, byte **palette, + int *width, int *height); +void Save256Image (char *name, byte *pixels, byte *palette, + int width, int height); + + +void LoadTGA (char *filename, byte **pixels, int *width, int *height); + +qboolean LoadAnyImage (char *name, byte **pixels, byte **palette, int *width, int *height); diff --git a/tools/quake2/qdata_heretic2/common/mathlib.c b/tools/quake2/qdata_heretic2/common/mathlib.c new file mode 100644 index 00000000..aa53c44a --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/mathlib.c @@ -0,0 +1,176 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// mathlib.c -- math primitives + +#include "cmdlib.h" +#include "mathlib.h" + +vec3_t vec3_origin = {0,0,0}; + + +double VectorLength(vec3_t v) +{ + int i; + double length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +qboolean VectorCompare (vec3_t v1, vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (fabs(v1[i]-v2[i]) > EQUAL_EPSILON) + return false; + + return true; +} + +vec_t Q_rint (vec_t in) +{ + return floor (in + 0.5); +} + +void VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc) +{ + vc[0] = va[0] + scale*vb[0]; + vc[1] = va[1] + scale*vb[1]; + vc[2] = va[2] + scale*vb[2]; +} + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]-vb[0]; + out[1] = va[1]-vb[1]; + out[2] = va[2]-vb[2]; +} + +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out) +{ + out[0] = va[0]+vb[0]; + out[1] = va[1]+vb[1]; + out[2] = va[2]+vb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void _VectorScale (vec3_t v, vec_t scale, vec3_t out) +{ + out[0] = v[0] * scale; + out[1] = v[1] * scale; + out[2] = v[2] * scale; +} + +#pragma optimize("g", off) // went back to turning optimization off, + // the bug_fix thing stopped working + +vec_t VectorNormalize (vec3_t in, vec3_t out) +{ + vec_t length, ilength; + + length = sqrt (in[0]*in[0] + in[1]*in[1] + in[2]*in[2]); + if (length == 0) + { + VectorClear (out); + return 0; + } + + ilength = 1.0/length; + out[0] = in[0]*ilength; + out[1] = in[1]*ilength; + out[2] = in[2]*ilength; + + return length; +} + +vec_t ColorNormalize (vec3_t in, vec3_t out) +{ + float max, scale; + + max = in[0]; + if (in[1] > max) + max = in[1]; + if (in[2] > max) + max = in[2]; + + if (max == 0) + return 0; + + scale = 1.0 / max; + + VectorScale (in, scale, out); + + return max; +} + +#pragma optimize("", on) + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void ClearBounds (vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} diff --git a/tools/quake2/qdata_heretic2/common/mathlib.h b/tools/quake2/qdata_heretic2/common/mathlib.h new file mode 100644 index 00000000..76dfda32 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/mathlib.h @@ -0,0 +1,76 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __MATHLIB__ +#define __MATHLIB__ + +// mathlib.h + +#include +/* +#ifdef DOUBLEVEC_T +typedef double vec_t; +#else +typedef float vec_t; +#endif +typedef vec_t vec3_t[3]; +*/ +#define SIDE_FRONT 0 +#define SIDE_ON 2 +#define SIDE_BACK 1 +#define SIDE_CROSS -2 + +#define Q_PI 3.14159265358979323846 + +extern vec3_t vec3_origin; + +#define EQUAL_EPSILON 0.001 + +qboolean VectorCompare (vec3_t v1, vec3_t v2); + +#define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) +#define VectorSubtract(a,b,c) {c[0]=a[0]-b[0];c[1]=a[1]-b[1];c[2]=a[2]-b[2];} +#define VectorAdd(a,b,c) {c[0]=a[0]+b[0];c[1]=a[1]+b[1];c[2]=a[2]+b[2];} +#define VectorCopy(a,b) {b[0]=a[0];b[1]=a[1];b[2]=a[2];} +#define VectorScale(a,b,c) {c[0]=b*a[0];c[1]=b*a[1];c[2]=b*a[2];} +#define VectorClear(x) {x[0] = x[1] = x[2] = 0;} +#define VectorNegate(x) {x[0]=-x[0];x[1]=-x[1];x[2]=-x[2];} + +vec_t Q_rint (vec_t in); +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out); +void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); +void _VectorScale (vec3_t v, vec_t scale, vec3_t out); + +double VectorLength(vec3_t v); + +void VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc); + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +vec_t VectorNormalize (vec3_t in, vec3_t out); +vec_t ColorNormalize (vec3_t in, vec3_t out); +void VectorInverse (vec3_t v); + +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); + +#endif diff --git a/tools/quake2/qdata_heretic2/common/md4.c b/tools/quake2/qdata_heretic2/common/md4.c new file mode 100644 index 00000000..0c04b5a1 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/md4.c @@ -0,0 +1,298 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* GLOBAL.H - RSAREF types and constants */ + +#include + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + + +/* MD4.H - header file for MD4C.C */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. + +All rights reserved. + +License to copy and use this software is granted provided that it is identified as the “RSA Data Security, Inc. MD4 Message-Digest Algorithm” in all material mentioning or referencing this software or this function. +License is also granted to make and use derivative works provided that such works are identified as “derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm” in all material mentioning or referencing the derived work. +RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided “as is” without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this documentation and/or software. */ + +/* MD4 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD4_CTX; + +void MD4Init (MD4_CTX *); +void MD4Update (MD4_CTX *, unsigned char *, unsigned int); +void MD4Final (unsigned char [16], MD4_CTX *); + + + +/* MD4C.C - RSA Data Security, Inc., MD4 message-digest algorithm */ +/* Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + +License to copy and use this software is granted provided that it is identified as the +RSA Data Security, Inc. MD4 Message-Digest Algorithm + in all material mentioning or referencing this software or this function. +License is also granted to make and use derivative works provided that such works are identified as +derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm +in all material mentioning or referencing the derived work. +RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided +as is without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this documentation and/or software. */ + +/* Constants for MD4Transform routine. */ +#define S11 3 +#define S12 7 +#define S13 11 +#define S14 19 +#define S21 3 +#define S22 5 +#define S23 9 +#define S24 13 +#define S31 3 +#define S32 9 +#define S33 11 +#define S34 15 + +static void MD4Transform (UINT4 [4], unsigned char [64]); +static void Encode (unsigned char *, UINT4 *, unsigned int); +static void Decode (UINT4 *, unsigned char *, unsigned int); +static void MD4_memcpy (POINTER, POINTER, unsigned int); +static void MD4_memset (POINTER, int, unsigned int); + +static unsigned char PADDING[64] = { +0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G and H are basic MD4 functions. */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits. */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG and HH are transformations for rounds 1, 2 and 3 */ +/* Rotation is separate from addition to prevent recomputation */ +#define FF(a, b, c, d, x, s) {(a) += F ((b), (c), (d)) + (x); (a) = ROTATE_LEFT ((a), (s));} + +#define GG(a, b, c, d, x, s) {(a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; (a) = ROTATE_LEFT ((a), (s));} + +#define HH(a, b, c, d, x, s) {(a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; (a) = \ +ROTATE_LEFT ((a), (s)); } + + +/* MD4 initialization. Begins an MD4 operation, writing a new context. */ +void MD4Init (MD4_CTX *context) +{ + context->count[0] = context->count[1] = 0; + +/* Load magic initialization constants.*/ +context->state[0] = 0x67452301; +context->state[1] = 0xefcdab89; +context->state[2] = 0x98badcfe; +context->state[3] = 0x10325476; +} + +/* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */ +void MD4Update (MD4_CTX *context, unsigned char *input, unsigned int inputLen) +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3)) + context->count[1]++; + + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible.*/ + if (inputLen >= partLen) + { + memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD4Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD4Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i); +} + + +/* MD4 finalization. Ends an MD4 message-digest operation, writing the the message digest and zeroizing the context. */ +void MD4Final (unsigned char digest[16], MD4_CTX *context) +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64.*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD4Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD4Update (context, bits, 8); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information.*/ + memset ((POINTER)context, 0, sizeof (*context)); +} + + +/* MD4 basic transformation. Transforms state based on block. */ +static void MD4Transform (UINT4 state[4], unsigned char block[64]) +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + +/* Round 1 */ +FF (a, b, c, d, x[ 0], S11); /* 1 */ +FF (d, a, b, c, x[ 1], S12); /* 2 */ +FF (c, d, a, b, x[ 2], S13); /* 3 */ +FF (b, c, d, a, x[ 3], S14); /* 4 */ +FF (a, b, c, d, x[ 4], S11); /* 5 */ +FF (d, a, b, c, x[ 5], S12); /* 6 */ +FF (c, d, a, b, x[ 6], S13); /* 7 */ +FF (b, c, d, a, x[ 7], S14); /* 8 */ +FF (a, b, c, d, x[ 8], S11); /* 9 */ +FF (d, a, b, c, x[ 9], S12); /* 10 */ +FF (c, d, a, b, x[10], S13); /* 11 */ +FF (b, c, d, a, x[11], S14); /* 12 */ +FF (a, b, c, d, x[12], S11); /* 13 */ +FF (d, a, b, c, x[13], S12); /* 14 */ +FF (c, d, a, b, x[14], S13); /* 15 */ +FF (b, c, d, a, x[15], S14); /* 16 */ + +/* Round 2 */ +GG (a, b, c, d, x[ 0], S21); /* 17 */ +GG (d, a, b, c, x[ 4], S22); /* 18 */ +GG (c, d, a, b, x[ 8], S23); /* 19 */ +GG (b, c, d, a, x[12], S24); /* 20 */ +GG (a, b, c, d, x[ 1], S21); /* 21 */ +GG (d, a, b, c, x[ 5], S22); /* 22 */ +GG (c, d, a, b, x[ 9], S23); /* 23 */ +GG (b, c, d, a, x[13], S24); /* 24 */ +GG (a, b, c, d, x[ 2], S21); /* 25 */ +GG (d, a, b, c, x[ 6], S22); /* 26 */ +GG (c, d, a, b, x[10], S23); /* 27 */ +GG (b, c, d, a, x[14], S24); /* 28 */ +GG (a, b, c, d, x[ 3], S21); /* 29 */ +GG (d, a, b, c, x[ 7], S22); /* 30 */ +GG (c, d, a, b, x[11], S23); /* 31 */ +GG (b, c, d, a, x[15], S24); /* 32 */ + +/* Round 3 */ +HH (a, b, c, d, x[ 0], S31); /* 33 */ +HH (d, a, b, c, x[ 8], S32); /* 34 */ +HH (c, d, a, b, x[ 4], S33); /* 35 */ +HH (b, c, d, a, x[12], S34); /* 36 */ +HH (a, b, c, d, x[ 2], S31); /* 37 */ +HH (d, a, b, c, x[10], S32); /* 38 */ +HH (c, d, a, b, x[ 6], S33); /* 39 */ +HH (b, c, d, a, x[14], S34); /* 40 */ +HH (a, b, c, d, x[ 1], S31); /* 41 */ +HH (d, a, b, c, x[ 9], S32); /* 42 */ +HH (c, d, a, b, x[ 5], S33); /* 43 */ +HH (b, c, d, a, x[13], S34); /* 44 */ +HH (a, b, c, d, x[ 3], S31); /* 45 */ +HH (d, a, b, c, x[11], S32); /* 46 */ +HH (c, d, a, b, x[ 7], S33); /* 47 */ +HH (b, c, d, a, x[15], S34); /* 48 */ + +state[0] += a; +state[1] += b; +state[2] += c; +state[3] += d; + + /* Zeroize sensitive information.*/ + memset ((POINTER)x, 0, sizeof (x)); +} + + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */ +static void Encode (unsigned char *output, UINT4 *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */ +static void Decode (UINT4 *output, unsigned char *input, unsigned int len) +{ +unsigned int i, j; + +for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +//=================================================================== + +unsigned Com_BlockChecksum (void *buffer, int length) +{ + int digest[4]; + unsigned val; + MD4_CTX ctx; + + MD4Init (&ctx); + MD4Update (&ctx, (unsigned char *)buffer, length); + MD4Final ( (unsigned char *)digest, &ctx); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} diff --git a/tools/quake2/qdata_heretic2/common/path_init.c b/tools/quake2/qdata_heretic2/common/path_init.c new file mode 100644 index 00000000..8947de84 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/path_init.c @@ -0,0 +1,405 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* +Nurail: Swiped from Q3Map2 +*/ + + + +/* marker */ +#define PATH_INIT_C + +#if defined( __linux__ ) || defined( __APPLE__ ) + #define Q_UNIX +#endif + +#ifdef Q_UNIX + #include + #include + #include +#endif + + +/* dependencies */ +#include "cmdlib.h" +#include "inout.h" + + + +/* path support */ +#define MAX_BASE_PATHS 10 +#define MAX_GAME_PATHS 10 + +char *homePath; +char installPath[ MAX_OS_PATH ]; + +int numBasePaths; +char *basePaths[ MAX_BASE_PATHS ]; +int numGamePaths; +char *gamePaths[ MAX_GAME_PATHS ]; + +/* +some of this code is based off the original q3map port from loki +and finds various paths. moved here from bsp.c for clarity. +*/ + +/* +PathLokiGetHomeDir() +gets the user's home dir (for ~/.q3a) +*/ + +char *LokiGetHomeDir( void ) +{ + #ifndef Q_UNIX + return NULL; + #else + char *home; + uid_t id; + struct passwd *pwd; + + + /* get the home environment variable */ + home = getenv( "HOME" ); + if( home == NULL ) + { + /* do some more digging */ + id = getuid(); + setpwent(); + while( (pwd = getpwent()) != NULL ) + { + if( pwd->pw_uid == id ) + { + home = pwd->pw_dir; + break; + } + } + endpwent(); + } + + /* return it */ + return home; + #endif +} + + + +/* +PathLokiInitPaths() +initializes some paths on linux/os x +*/ + +void LokiInitPaths( char *argv0 ) +{ + #ifndef Q_UNIX + /* this is kinda crap, but hey */ + strcpy( installPath, "../" ); + #else + char temp[ MAX_OS_PATH ]; + char *home; + char *path; + char *last; + qboolean found; + + + /* get home dir */ + home = LokiGetHomeDir(); + if( home == NULL ) + home = "."; + + /* do some path divining */ + strcpy( temp, argv0 ); + if( strrchr( temp, '/' ) ) + argv0 = strrchr( argv0, '/' ) + 1; + else + { + /* get path environment variable */ + path = getenv( "PATH" ); + + /* minor setup */ + last[ 0 ] = path[ 0 ]; + last[ 1 ] = '\0'; + found = false; + + /* go through each : segment of path */ + while( last[ 0 ] != '\0' && found == false ) + { + /* null out temp */ + temp[ 0 ] = '\0'; + + /* find next chunk */ + last = strchr( path, ':' ); + if( last == NULL ) + last = path + strlen( path ); + + /* found home dir candidate */ + if( *path == '~' ) + { + strcpy( temp, home ); + path++; + } + + /* concatenate */ + if( last > (path + 1) ) + { + strncat( temp, path, (last - path) ); + strcat( temp, "/" ); + } + strcat( temp, "./" ); + strcat( temp, argv0 ); + + /* verify the path */ + if( access( temp, X_OK ) == 0 ) + found++; + path = last + 1; + } + } + + /* flake */ + if( realpath( temp, installPath ) ) + { + /* q3map is in "tools/" */ + *(strrchr( installPath, '/' )) = '\0'; + *(strrchr( installPath, '/' ) + 1) = '\0'; + } + + /* set home path */ + homePath = home; + #endif +} + + + +/* +CleanPath() - ydnar +cleans a dos path \ -> / +*/ + +void CleanPath( char *path ) +{ + while( *path ) + { + if( *path == '\\' ) + *path = '/'; + path++; + } +} + +/* +AddBasePath() - ydnar +adds a base path to the list +*/ + +void AddBasePath( char *path ) +{ + /* dummy check */ + if( path == NULL || path[ 0 ] == '\0' || numBasePaths >= MAX_BASE_PATHS ) + return; + + /* add it to the list */ + basePaths[ numBasePaths ] = safe_malloc( strlen( path ) + 1 ); + strcpy( basePaths[ numBasePaths ], path ); + CleanPath( basePaths[ numBasePaths ] ); + numBasePaths++; +} + + + +/* +AddHomeBasePath() - ydnar +adds a base path to the beginning of the list, prefixed by ~/ +*/ + +void AddHomeBasePath( char *path ) +{ + #ifdef Q_UNIX + int i; + char temp[ MAX_OS_PATH ]; + + + /* dummy check */ + if( path == NULL || path[ 0 ] == '\0' ) + return; + + /* make a hole */ + for( i = 0; i < (MAX_BASE_PATHS - 1); i++ ) + basePaths[ i + 1 ] = basePaths[ i ]; + + /* concatenate home dir and path */ + sprintf( temp, "%s/%s", homePath, path ); + + /* add it to the list */ + basePaths[ 0 ] = safe_malloc( strlen( temp ) + 1 ); + strcpy( basePaths[ 0 ], temp ); + CleanPath( basePaths[ 0 ] ); + numBasePaths++; + #endif +} + + + +/* +AddGamePath() - ydnar +adds a game path to the list +*/ + +void AddGamePath( char *path ) +{ + /* dummy check */ + if( path == NULL || path[ 0 ] == '\0' || numGamePaths >= MAX_GAME_PATHS ) + return; + + /* add it to the list */ + gamePaths[ numGamePaths ] = safe_malloc( strlen( path ) + 1 ); + strcpy( gamePaths[ numGamePaths ], path ); + CleanPath( gamePaths[ numGamePaths ] ); + numGamePaths++; +} + + + + +/* +InitPaths() - ydnar +cleaned up some of the path initialization code from bsp.c +will remove any arguments it uses +*/ + +void InitPaths( int *argc, char **argv ) +{ + int i, j, k, len, len2; + char temp[ MAX_OS_PATH ]; + char gamePath[MAX_OS_PATH], homeBasePath[MAX_OS_PATH], game_magic[10]; + + strcpy(gamePath, "base"); + strcpy(game_magic, "h"); + strcpy(homeBasePath, ".heretic2"); + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- InitPaths ---\n" ); + + /* get the install path for backup */ + LokiInitPaths( argv[ 0 ] ); + + /* set game to default (q3a) */ + numBasePaths = 0; + numGamePaths = 0; + + /* parse through the arguments and extract those relevant to paths */ + for( i = 0; i < *argc; i++ ) + { + /* check for null */ + if( argv[ i ] == NULL ) + continue; + + /* -fs_basepath */ + if( strcmp( argv[ i ], "-fs_basepath" ) == 0 ) + { + if( ++i >= *argc ) + Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] ); + argv[ i - 1 ] = NULL; + AddBasePath( argv[ i ] ); + argv[ i ] = NULL; + } + + } + + /* remove processed arguments */ + for( i = 0, j = 0, k = 0; i < *argc && j < *argc; i++, j++ ) + { + for( j; j < *argc && argv[ j ] == NULL; j++ ); + argv[ i ] = argv[ j ]; + if( argv[ i ] != NULL ) + k++; + } + *argc = k; + + /* add standard game path */ + AddGamePath( gamePath ); + + /* if there is no base path set, figure it out */ + if( numBasePaths == 0 ) + { + /* this is another crappy replacement for SetQdirFromPath() */ + len2 = strlen( game_magic ); + for( i = 0; i < *argc && numBasePaths == 0; i++ ) + { + /* extract the arg */ + strcpy( temp, argv[ i ] ); + CleanPath( temp ); + len = strlen( temp ); + Sys_FPrintf( SYS_VRB, "Searching for \"%s\" in \"%s\" (%d)...\n", game_magic, temp, i ); + + /* this is slow, but only done once */ + for( j = 0; j < (len - len2); j++ ) + { + /* check for the game's magic word */ + if( Q_strncasecmp( &temp[ j ], game_magic, len2 ) == 0 ) + { + /* now find the next slash and nuke everything after it */ + while( temp[ ++j ] != '/' && temp[ j ] != '\0' ); + temp[ j ] = '\0'; + + /* add this as a base path */ + AddBasePath( temp ); + break; + } + } + } + + /* add install path */ + if( numBasePaths == 0 ) + AddBasePath( installPath ); + + /* check again */ + if( numBasePaths == 0 ) + Error( "Failed to find a valid base path." ); + } + + /* this only affects unix */ + AddHomeBasePath( homeBasePath ); + + /* initialize vfs paths */ + if( numBasePaths > MAX_BASE_PATHS ) + numBasePaths = MAX_BASE_PATHS; + if( numGamePaths > MAX_GAME_PATHS ) + numGamePaths = MAX_GAME_PATHS; + + /* walk the list of game paths */ + //for( j = 0; j < numGamePaths; j++ ) + //{ + /* walk the list of base paths */ + // for( i = 0; i < numBasePaths; i++ ) + // { + /* create a full path and initialize it */ + // sprintf( temp, "%s/%s/", basePaths[ i ], gamePaths[ j ] ); + // vfsInitDirectory( temp ); + // } + //} + + /* done */ + Sys_Printf( "\n" ); +} + + + + diff --git a/tools/quake2/qdata_heretic2/common/polylib.c b/tools/quake2/qdata_heretic2/common/polylib.c new file mode 100644 index 00000000..ce1a909a --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/polylib.c @@ -0,0 +1,656 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "cmdlib.h" +#include "inout.h" +#include "mathlib.h" +#include "polylib.h" + + +extern int numthreads; + +// counters are only bumped when running single threaded, +// because they are an awefull coherence problem +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; + +#define BOGUS_RANGE 8192 + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding (int points) +{ + winding_t *w; + int s; + + if (numthreads == 1) + { + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if (c_active_windings > c_peak_windings) + c_peak_windings = c_active_windings; + } + s = sizeof(vec_t)*3*points + sizeof(int); + w = malloc (s); + if (!w) + Error("AllocWinding MALLOC failed! Could not allocate %s bytes.", s); + memset (w, 0, s); + return w; +} + +void FreeWinding (winding_t *w) +{ + if (*(unsigned *)w == 0xdeaddead) + Error ("FreeWinding: freed a freed winding"); + *(unsigned *)w = 0xdeaddead; + + if (numthreads == 1) + c_active_windings--; + free (w); +} + +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints (winding_t *w) +{ + int i, j, k; + vec3_t v1, v2; + int nump; + vec3_t p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for (i=0 ; inumpoints ; i++) + { + j = (i+1)%w->numpoints; + k = (i+w->numpoints-1)%w->numpoints; + VectorSubtract (w->p[j], w->p[i], v1); + VectorSubtract (w->p[i], w->p[k], v2); + VectorNormalize(v1,v1); + VectorNormalize(v2,v2); + if (DotProduct(v1, v2) < 0.999) + { + VectorCopy (w->p[i], p[nump]); + nump++; + } + } + + if (nump == w->numpoints) + return; + + if (numthreads == 1) + c_removed += w->numpoints - nump; + w->numpoints = nump; + memcpy (w->p, p, nump*sizeof(p[0])); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist) +{ + vec3_t v1, v2; + + VectorSubtract (w->p[1], w->p[0], v1); + VectorSubtract (w->p[2], w->p[0], v2); + CrossProduct (v2, v1, normal); + VectorNormalize (normal, normal); + *dist = DotProduct (w->p[0], normal); + +} + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea (winding_t *w) +{ + int i; + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + total += 0.5 * VectorLength ( cross ); + } + return total; +} + +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs) +{ + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter (winding_t *w, vec3_t center) +{ + int i; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->p[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist) +{ + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + winding_t *w; + +// find the major axis + + max = -BOGUS_RANGE; + x = -1; + for (i=0 ; i<3; i++) + { + v = fabs(normal[i]); + if (v > max) + { + x = i; + max = v; + } + } + if (x==-1) + Error ("BaseWindingForPlane: no axis found"); + + VectorCopy (vec3_origin, vup); + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct (vup, normal); + VectorMA (vup, -v, normal, vup); + VectorNormalize (vup, vup); + + VectorScale (normal, dist, org); + + CrossProduct (vup, normal, vright); + + VectorScale (vup, 8192, vup); + VectorScale (vright, 8192, vright); + +// project a really big axis aligned box onto the plane + w = AllocWinding (4); + + VectorSubtract (org, vright, w->p[0]); + VectorAdd (w->p[0], vup, w->p[0]); + + VectorAdd (org, vright, w->p[1]); + VectorAdd (w->p[1], vup, w->p[1]); + + VectorAdd (org, vright, w->p[2]); + VectorSubtract (w->p[2], vup, w->p[2]); + + VectorSubtract (org, vright, w->p[3]); + VectorSubtract (w->p[3], vup, w->p[3]); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding (winding_t *w) +{ + int size; + winding_t *c; + + c = AllocWinding (w->numpoints); + size = (int)((winding_t *)0)->p[w->numpoints]; + memcpy (c, w, size); + return c; +} + +/* +================== +ReverseWinding +================== +*/ +winding_t *ReverseWinding (winding_t *w) +{ + int i; + winding_t *c; + + c = AllocWinding (w->numpoints); + for (i=0 ; inumpoints ; i++) + { + VectorCopy (w->p[w->numpoints-1-i], c->p[i]); + } + c->numpoints = w->numpoints; + return c; +} + + +/* +============= +ClipWindingEpsilon +============= +*/ +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + if (in->numpoints >= MAX_POINTS_ON_WINDING-4) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = CopyWinding (in); + return; + } + if (!counts[1]) + { + *front = CopyWinding (in); + return; + } + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); +} + + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace (winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon) +{ + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + + if (!in) + { + printf ("Warning: NULL passed to ChopWindingInPlace\n"); + return; + } + if (in->numpoints >= MAX_POINTS_ON_WINDING-4) + Error ("ChopWinding: MAX_POINTS_ON_WINDING"); + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + { + FreeWinding (in); + *inout = NULL; + return; + } + if (!counts[1]) + return; // inout stays the same + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + } + + if (f->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); + + FreeWinding (in); + *inout = f; +} + + +/* +================= +ChopWinding + +Returns the fragment of in that is on the front side +of the cliping plane. The original is freed. +================= +*/ +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist) +{ + winding_t *f, *b; + + ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b); + FreeWinding (in); + if (b) + FreeWinding (b); + return f; +} + + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding (winding_t *w) +{ + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if (w->numpoints < 3) + Error ("CheckWinding: %i points",w->numpoints); + + area = WindingArea(w); + if (area < 1) + Error ("CheckWinding: %f area", area); + + WindingPlane (w, facenormal, &facedist); + + for (i=0 ; inumpoints ; i++) + { + p1 = w->p[i]; + + for (j=0 ; j<3 ; j++) + if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) + Error ("CheckFace: BUGUS_RANGE: %f",p1[j]); + + j = i+1 == w->numpoints ? 0 : i+1; + + // check the point is on the face plane + d = DotProduct (p1, facenormal) - facedist; + if (d < -ON_EPSILON || d > ON_EPSILON) + Error ("CheckWinding: point off plane"); + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + Error ("CheckWinding: degenerate edge"); + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize (edgenormal, edgenormal); + edgedist = DotProduct (p1, edgenormal); + edgedist += ON_EPSILON; + + // all other points must be on front side + for (j=0 ; jnumpoints ; j++) + { + if (j == i) + continue; + d = DotProduct (w->p[j], edgenormal); + if (d > edgedist) + Error ("CheckWinding: non-convex"); + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist) +{ + qboolean front, back; + int i; + vec_t d; + + front = false; + back = false; + for (i=0 ; inumpoints ; i++) + { + d = DotProduct (w->p[i], normal) - dist; + if (d < -ON_EPSILON) + { + if (front) + return SIDE_CROSS; + back = true; + continue; + } + if (d > ON_EPSILON) + { + if (back) + return SIDE_CROSS; + front = true; + continue; + } + } + + if (back) + return SIDE_BACK; + if (front) + return SIDE_FRONT; + return SIDE_ON; +} + diff --git a/tools/quake2/qdata_heretic2/common/polylib.h b/tools/quake2/qdata_heretic2/common/polylib.h new file mode 100644 index 00000000..fec80e06 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/polylib.h @@ -0,0 +1,55 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +typedef struct +{ + int numpoints; + vec3_t p[4]; // variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 64 + +// you can define on_epsilon in the makefile as tighter +#ifndef ON_EPSILON +#define ON_EPSILON 0.1 +#endif + +winding_t *AllocWinding (int points); +vec_t WindingArea (winding_t *w); +void WindingCenter (winding_t *w, vec3_t center); +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back); +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist); +winding_t *CopyWinding (winding_t *w); +winding_t *ReverseWinding (winding_t *w); +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist); +void CheckWinding (winding_t *w); +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist); +void RemoveColinearPoints (winding_t *w); +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist); +void FreeWinding (winding_t *w); +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs); + +void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon); +// frees the original if clipped + +void pw(winding_t *w); diff --git a/tools/quake2/qdata_heretic2/common/qfiles.c b/tools/quake2/qdata_heretic2/common/qfiles.c new file mode 100644 index 00000000..25600a25 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/qfiles.c @@ -0,0 +1,82 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qfiles.h" +#include "scriplib.h" +//#include + +materialtype_t defaultmaterialtypes[] = +{ + {"gravel", MATERIAL_GRAVEL}, + {"metal", MATERIAL_METAL}, + {"stone", MATERIAL_STONE}, + {"wood", MATERIAL_WOOD}, + {NULL, 0} +}; + +materialtype_t *materialtypes; + +void QFile_ReadMaterialTypes(char* filename) +{ + int i; + FILE *f; + + f = fopen (filename, "rb"); + if (!f) + { + materialtypes = defaultmaterialtypes; + return; + } + fclose (f); + + free(materialtypes); + materialtypes = (materialtype_t*)malloc(256 * sizeof(materialtype_t)); + + LoadScriptFile(filename); + i = 0; + + while (i < 255) + { + GetScriptToken (true); + if (endofscript) + { + break; + } + if (strcmp(token, "material") != 0) + { + while (ScriptTokenAvailable()) + { + GetScriptToken(false); + } + } + else + { + GetScriptToken(false); + materialtypes[i].name = (char*)malloc(strlen(token) + 1); + strcpy(materialtypes[i].name, token); + GetScriptToken (false); + materialtypes[i].value = atoi(token); + } + i++; + } + materialtypes[i].name = NULL; + materialtypes[i].value = 0; +} diff --git a/tools/quake2/qdata_heretic2/common/qfiles.h b/tools/quake2/qdata_heretic2/common/qfiles.h new file mode 100644 index 00000000..3492136e --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/qfiles.h @@ -0,0 +1,619 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _QFILES_H +#define _QFILES_H + +#include "q_typedef.h" + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +/* +======================================================================== + +The .pak files are just a linear collapse of a directory tree + +======================================================================== +*/ + +#define IDPAKHEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') + +typedef struct +{ + char name[56]; + int filepos, filelen; +} dpackfile_t; + +typedef struct +{ + int ident; // == IDPAKHEADER + int dirofs; + int dirlen; +} dpackheader_t; + +#define MAX_FILES_IN_PACK 4096 + + +/* +======================================================================== + +PCX files are used for as many images as possible + +======================================================================== +*/ + +typedef struct +{ + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +/* +======================================================================== + +.MD2 compressed triangle model file format + +======================================================================== +*/ +#define IDJOINTEDALIASHEADER (('2'<<24)+('J'<<16)+('D'<<8)+'I') + +/* +======================================================================== + +.MD2 triangle model file format + +======================================================================== +*/ + +#define IDALIASHEADER (('2'<<24)+('P'<<16)+('D'<<8)+'I') +#define ALIAS_VERSION 8 + +#define MAX_TRIANGLES 2048 +#define MAX_VERTS 2048 +#define MAX_FRAMES 512 +#define MAX_MD2SKINS 64 +#define MAX_SKINNAME 64 + +typedef struct +{ + short s; + short t; +} dstvert_t; + +typedef struct +{ + short index_xyz[3]; + short index_st[3]; +} dtriangle_t; + +typedef struct +{ + byte v[3]; // scaled byte to fit in frame mins/maxs + byte lightnormalindex; +} dtrivertx_t; + +#define DTRIVERTX_V0 0 +#define DTRIVERTX_V1 1 +#define DTRIVERTX_V2 2 +#define DTRIVERTX_LNI 3 +#define DTRIVERTX_SIZE 4 + +typedef struct +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name from grabbing + dtrivertx_t verts[1]; // variable sized +} daliasframe_t; + + +// the glcmd format: +// a positive integer starts a tristrip command, followed by that many +// vertex structures. +// a negative integer starts a trifan command, followed by -x vertexes +// a zero indicates the end of the command list. +// a vertex consists of a floating point s, a floating point t, +// and an integer vertex index. + + +typedef struct +{ + int ident; + int version; + + int skinwidth; + int skinheight; + int framesize; // byte size of each frame + + int num_skins; + int num_xyz; + int num_st; // greater than num_xyz for seams + int num_tris; + int num_glcmds; // dwords in strip/fan command list + int num_frames; + + int ofs_skins; // each skin is a MAX_SKINNAME string + int ofs_st; // byte offset from start for stverts + int ofs_tris; // offset for dtriangles + int ofs_frames; // offset for first frame + int ofs_glcmds; + int ofs_end; // end of file + +} dmdl_t; + +/* +======================================================================== + +.BK file format + +======================================================================== +*/ + +#define IDBOOKHEADER (('K'<<24)+('O'<<16)+('O'<<8)+'B') +#define BOOK_VERSION 2 + +typedef struct bookframe_s +{ + int x; + int y; + int w; + int h; + char name[MAX_SKINNAME]; // name of gfx file +} bookframe_t; + +typedef struct bookheader_s +{ + unsigned int ident; + unsigned int version; + int num_segments; + int total_w; + int total_h; +} bookheader_t; + +typedef struct book_s +{ + bookheader_t bheader; + bookframe_t bframes[MAX_MD2SKINS]; +} book_t; + +/* +======================================================================== + +.SP2 sprite file format + +======================================================================== +*/ + +#define IDSPRITEHEADER (('2'<<24)+('S'<<16)+('D'<<8)+'I') + // little-endian "IDS2" +#define SPRITE_VERSION 2 + +typedef struct +{ + int width, height; + int origin_x, origin_y; // raster coordinates inside pic + char name[MAX_SKINNAME]; // name of pcx file +} dsprframe_t; + +typedef struct { + int ident; + int version; + int numframes; + dsprframe_t frames[1]; // variable sized +} dsprite_t; + +/* +============================================================================== + + .M8 texture file format + +============================================================================== +*/ + +typedef struct palette_s +{ + union + { + struct + { + byte r,g,b; + }; + }; +} palette_t; + +#define MIP_VERSION 2 +#define PAL_SIZE 256 +#define MIPLEVELS 16 + +typedef struct miptex_s +{ + int version; + char name[32]; + unsigned width[MIPLEVELS], height[MIPLEVELS]; + unsigned offsets[MIPLEVELS]; // four mip maps stored + char animname[32]; // next frame in animation chain + palette_t palette[PAL_SIZE]; + int flags; + int contents; + int value; +} miptex_t; + + +#define MIP32_VERSION 4 + +#define MIP32_NOMIP_FLAG2 0x00000001 +#define MIP32_DETAILER_FLAG2 0x00000002 + +typedef struct miptex32_s +{ + int version; + char name[128]; + char altname[128]; // texture substitution + char animname[128]; // next frame in animation chain + char damagename[128]; // image that should be shown when damaged + unsigned width[MIPLEVELS], height[MIPLEVELS]; + unsigned offsets[MIPLEVELS]; + int flags; + int contents; + int value; + float scale_x, scale_y; + int mip_scale; + + // detail texturing info + char dt_name[128]; // detailed texture name + float dt_scale_x, dt_scale_y; + float dt_u, dt_v; + float dt_alpha; + int dt_src_blend_mode, dt_dst_blend_mode; + + int flags2; + int unused[19]; // future expansion to maintain compatibility with h2 +} miptex32_t; + + + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + +#define IDBSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') + // little-endian "IBSP" + +#define BSPVERSION 38 + + +// upper design bounds +// leaffaces, leafbrushes, planes, and verts are still bounded by +// 16 bit short limits +#define MAX_MAP_MODELS 1024 +#define MAX_MAP_BRUSHES 8192 +#define MAX_MAP_ENTITIES 2048 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_TEXINFO 8192 + +#define MAX_MAP_AREAS 256 +#define MAX_MAP_AREAPORTALS 1024 +#define MAX_MAP_PLANES 65536 +#define MAX_MAP_NODES 65536 +#define MAX_MAP_BRUSHSIDES 65536 +#define MAX_MAP_LEAFS 65536 +#define MAX_MAP_VERTS 65536 +#define MAX_MAP_FACES 65536 +#define MAX_MAP_LEAFFACES 65536 +#define MAX_MAP_LEAFBRUSHES 65536 +#define MAX_MAP_PORTALS 65536 +#define MAX_MAP_EDGES 128000 +#define MAX_MAP_SURFEDGES 256000 +#define MAX_MAP_LIGHTING 0x200000 +#define MAX_MAP_VISIBILITY 0x180000 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + +typedef struct +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_VERTEXES 2 +#define LUMP_VISIBILITY 3 +#define LUMP_NODES 4 +#define LUMP_TEXINFO 5 +#define LUMP_FACES 6 +#define LUMP_LIGHTING 7 +#define LUMP_LEAFS 8 +#define LUMP_LEAFFACES 9 +#define LUMP_LEAFBRUSHES 10 +#define LUMP_EDGES 11 +#define LUMP_SURFEDGES 12 +#define LUMP_MODELS 13 +#define LUMP_BRUSHES 14 +#define LUMP_BRUSHSIDES 15 +#define LUMP_POP 16 +#define LUMP_AREAS 17 +#define LUMP_AREAPORTALS 18 +#define HEADER_LUMPS 19 + +typedef struct +{ + int ident; + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; // for sounds or lights + int headnode; + int firstface, numfaces; // submodels just draw faces + // without walking the bsp tree +} dmodel_t; + + +typedef struct +{ + float point[3]; +} dvertex_t; + + +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 + +// 3-5 are non-axial planes snapped to the nearest +#define PLANE_ANYX 3 +#define PLANE_ANYY 4 +#define PLANE_ANYZ 5 + +// planes (x&~1) and (x&~1)+1 are allways opposites + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} dplane_t; + + +// contents flags are seperate bits +// a given brush can contribute multiple content bits +// multiple brushes can be in a single leaf + +// these definitions also need to be in q_shared.h! + +// lower bits are stronger, and will eat weaker brushes completely +#define CONTENTS_SOLID 0x00000001 // an eye is never valid in a solid +#define CONTENTS_WINDOW 0x00000002 // translucent, but not watery +#define CONTENTS_PUSHPULL 0x00000004 +#define CONTENTS_LAVA 0x00000008 +#define CONTENTS_SLIME 0x00000010 +#define CONTENTS_WATER 0x00000020 +#define CONTENTS_MIST 0x00000040 // 64 +#define LAST_VISIBLE_CONTENTS 64 // this one worries me a bit JKH + +// remaining contents are non-visible, and don't eat brushes + +#define CONTENTS_AREAPORTAL 0x00008000 + +#define CONTENTS_PLAYERCLIP 0x00010000 +#define CONTENTS_MONSTERCLIP 0x00020000 + +// currents can be added to any other contents, and may be mixed +#define CONTENTS_CURRENT_0 0x00040000 +#define CONTENTS_CURRENT_90 0x00080000 +#define CONTENTS_CURRENT_180 0x00100000 +#define CONTENTS_CURRENT_270 0x00200000 +#define CONTENTS_CURRENT_UP 0x00400000 +#define CONTENTS_CURRENT_DOWN 0x00800000 + +#define CONTENTS_ORIGIN 0x01000000 // removed before bsping an entity + +#define CONTENTS_MONSTER 0x02000000 // should never be on a brush, only in game +#define CONTENTS_DEADMONSTER 0x04000000 +#define CONTENTS_DETAIL 0x08000000 // brushes to be added after vis leafs +#define CONTENTS_TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTS_LADDER 0x20000000 + + + +#define SURF_LIGHT 0x00000001 // value will hold the light strength + +#define SURF_SLICK 0x00000002 // effects game physics + +#define SURF_SKY 0x00000004 // don't draw, but add to skybox +#define SURF_WARP 0x00000008 // turbulent water warp +#define SURF_TRANS33 0x00000010 +#define SURF_TRANS66 0x00000020 +#define SURF_FLOWING 0x00000040 // scroll towards angle +#define SURF_NODRAW 0x00000080 // don't bother referencing the texture + +#define SURF_HINT 0x00000100 // make a primary bsp splitter +#define SURF_SKIP 0x00000200 // completely ignore, allowing non-closed brushes +#define SURF_TALL_WALL 0x00000400 // face doesn't get broken up as normal + +#define SURF_ALPHA_TEXTURE 0x00000800 // texture has alpha in it, and should show through in bsp process +#define SURF_ANIMSPEED 0x00001000 // value will hold the anim speed in fps + +#define SURF_UNDULATE 0x00002000 // rock surface up and down... +#define SURF_SKYREFLECT 0x00004000 // liquid will somewhat reflect the sky - not quite finished.... + +#define SURF_TYPE_GRAVEL 0x00000000 +#define SURF_TYPE_METAL 0x01000000 +#define SURF_TYPE_STONE 0x02000000 +#define SURF_TYPE_WOOD 0x03000000 +#define SURF_MATERIAL 0xFF000000 + + + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} dnode_t; + + +typedef struct texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int flags; // miptex flags + overrides + int value; // light emission, etc + char texture[32]; // texture name (textures/*.wal) + int nexttexinfo; // for animations, -1 = end of chain +} texinfo_t; + + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + unsigned short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + union { + byte styles[MAXLIGHTMAPS]; + paletteRGBA_t lighting; + }; + int lightofs; // start of [numstyles*surfsize] samples +} dface_t; + +typedef struct +{ + int contents; // OR of all brushes (not needed?) + + short cluster; + short area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstleafface; + unsigned short numleaffaces; + + unsigned short firstleafbrush; + unsigned short numleafbrushes; +} dleaf_t; + +typedef struct +{ + unsigned short planenum; // facing out of the leaf + short texinfo; +} dbrushside_t; + +typedef struct +{ + int firstside; + int numsides; + int contents; +} dbrush_t; + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the visibility lump consists of a header with a count, then +// byte offsets for the PVS and PHS of each cluster, then the raw +// compressed bit vectors +#define DVIS_PVS 0 +#define DVIS_PHS 1 +typedef struct +{ + int numclusters; + int bitofs[8][2]; // bitofs[numclusters][2] +} dvis_t; + +// each area has a list of portals that lead into other areas +// when portals are closed, other areas may not be visible or +// hearable even if the vis info says that it should be +typedef struct +{ + int portalnum; + int otherarea; +} dareaportal_t; + + +typedef struct +{ + int numareaportals; + int firstareaportal; +} darea_t; + +typedef struct +{ + char *name; + int value; +} materialtype_t; + +enum +{ + MATERIAL_GRAVEL, + MATERIAL_METAL, + MATERIAL_STONE, + MATERIAL_WOOD, +}; + +materialtype_t *materialtypes; + +void QFile_ReadMaterialTypes(char* filename); + + +#endif //_QFILES_H diff --git a/tools/quake2/qdata_heretic2/common/scriplib.c b/tools/quake2/qdata_heretic2/common/scriplib.c new file mode 100644 index 00000000..c9001795 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/scriplib.c @@ -0,0 +1,297 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// scriplib.c + +#include "cmdlib.h" +#include "inout.h" +#include "scriplib.h" + +/* +============================================================================= + + PARSING STUFF + +============================================================================= +*/ + +typedef struct +{ + char filename[1024]; + char *buffer,*script_p,*end_p; + int line; +} script_t; + +#define MAX_INCLUDES 8 +script_t scriptstack[MAX_INCLUDES]; +script_t *script; +int scriptline; + +char token[MAXTOKEN]; +qboolean endofscript; +qboolean tokenready; // only true if UnGetScriptToken was just called + +/* +============== +AddScriptToStack +============== +*/ +void AddScriptToStack (char *filename) +{ + int size; + + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + strcpy (script->filename, ExpandPath (filename) ); + + size = LoadFile (script->filename, (void **)&script->buffer); + + printf ("entering %s\n", script->filename); + + script->line = 1; + + script->script_p = script->buffer; + script->end_p = script->buffer + size; +} + + +/* +============== +LoadScriptFile +============== +*/ +void LoadScriptFile (char *filename) +{ + script = scriptstack; + AddScriptToStack (filename); + + endofscript = false; + tokenready = false; +} + + +/* +============== +ParseFromMemory +============== +*/ +void ParseFromMemory (char *buffer, int size) +{ + script = scriptstack; + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + strcpy (script->filename, "memory buffer" ); + + script->buffer = buffer; + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + + endofscript = false; + tokenready = false; +} + + +/* +============== +UnGetScriptToken + +Signals that the current token was not used, and should be reported +for the next GetScriptToken. Note that + +GetScriptToken (true); +UnGetScriptToken (); +GetScriptToken (false); + +could cross a line boundary. +============== +*/ +void UnGetScriptToken (void) +{ + tokenready = true; +} + + +qboolean EndOfScript (qboolean crossline) +{ + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + + if (!strcmp (script->filename, "memory buffer")) + { + endofscript = true; + return false; + } + + free (script->buffer); + if (script == scriptstack+1) + { + endofscript = true; + return false; + } + script--; + scriptline = script->line; + printf ("returning to %s\n", script->filename); + return GetScriptToken (crossline); +} + +/* +============== +GetScriptToken +============== +*/ +qboolean GetScriptToken (qboolean crossline) +{ + char *token_p; + + if (tokenready) // is a token allready waiting? + { + tokenready = false; + return true; + } + + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + +// +// skip space +// +skipspace: + while (*script->script_p <= 32) + { + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + if (*script->script_p++ == '\n') + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + scriptline = script->line++; + } + } + + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + + // ; # // comments + if (*script->script_p == ';' || *script->script_p == '#' + || ( script->script_p[0] == '/' && script->script_p[1] == '/') ) + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + while (*script->script_p++ != '\n') + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + goto skipspace; + } + + // /* */ comments + if (script->script_p[0] == '/' && script->script_p[1] == '*') + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + script->script_p+=2; + while (script->script_p[0] != '*' && script->script_p[1] != '/') + { + script->script_p++; + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + } + script->script_p += 2; + goto skipspace; + } + +// +// copy token +// + token_p = token; + + if (*script->script_p == '"') + { + // quoted token + script->script_p++; + while (*script->script_p != '"') + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + script->script_p++; + } + else // regular token + while ( *script->script_p > 32 && *script->script_p != ';') + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + + *token_p = 0; + + if (!strcmp (token, "$include")) + { + GetScriptToken (false); + AddScriptToStack (token); + return GetScriptToken (crossline); + } + + return true; +} + + +/* +============== +ScriptTokenAvailable + +Returns true if there is another token on the line +============== +*/ +qboolean ScriptTokenAvailable (void) +{ + char *search_p; + + search_p = script->script_p; + + if (search_p >= script->end_p) + return false; + + while ( *search_p <= 32) + { + if (*search_p == '\n') + return false; + search_p++; + if (search_p == script->end_p) + return false; + + } + + if (*search_p == ';') + return false; + + return true; +} + + diff --git a/tools/quake2/qdata_heretic2/common/scriplib.h b/tools/quake2/qdata_heretic2/common/scriplib.h new file mode 100644 index 00000000..4a87d582 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/scriplib.h @@ -0,0 +1,44 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// scriplib.h + +#ifndef __CMDLIB__ +#include "cmdlib.h" +#endif + +#define MAXTOKEN 1024 + +extern char token[MAXTOKEN]; +extern char *scriptbuffer,*script_p,*scriptend_p; +extern int grabbed; +extern int scriptline; +extern qboolean endofscript; + + +void LoadScriptFile (char *filename); +void ParseFromMemory (char *buffer, int size); + +qboolean GetScriptToken (qboolean crossline); +void UnGetScriptToken (void); +qboolean ScriptTokenAvailable (void); + + diff --git a/tools/quake2/qdata_heretic2/common/threads.c b/tools/quake2/qdata_heretic2/common/threads.c new file mode 100644 index 00000000..bf37b9f7 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/threads.c @@ -0,0 +1,620 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef WIN32 +// The below define is necessary to use +// pthreads extensions like pthread_mutexattr_settype +#define _GNU_SOURCE +#include +#endif + +#include "cmdlib.h" +#include "mathlib.h" +#include "inout.h" +#include "her2_threads.h" + +#define MAX_THREADS 64 + +int dispatch; +int workcount; +int oldf; +qboolean pacifier; + +qboolean threaded; + +/* +============= +GetThreadWork + +============= +*/ +int GetThreadWork (void) +{ + int r; + int f; + + ThreadLock (); + + if (dispatch == workcount) + { + ThreadUnlock (); + return -1; + } + + f = 10*dispatch / workcount; + if (f != oldf) + { + oldf = f; + if (pacifier) + { + Sys_Printf ("%i...", f); + fflush( stdout ); /* ydnar */ + } + } + + r = dispatch; + dispatch++; + ThreadUnlock (); + + return r; +} + + +void (*workfunction) (int); + +void ThreadWorkerFunction (int threadnum) +{ + int work; + + while (1) + { + work = GetThreadWork (); + if (work == -1) + break; +//Sys_Printf ("thread %i, work %i\n", threadnum, work); + workfunction(work); + } +} + +void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + if (numthreads == -1) + ThreadSetDefault (); + workfunction = func; + RunThreadsOn (workcnt, showpacifier, ThreadWorkerFunction); +} + + +/* +=================================================================== + +WIN32 + +=================================================================== +*/ +#ifdef WIN32 + +#define USED + +#include + +int numthreads = -1; +CRITICAL_SECTION crit; +static int enter; + +void ThreadSetDefault (void) +{ + SYSTEM_INFO info; + + if (numthreads == -1) // not set manually + { + GetSystemInfo (&info); + numthreads = info.dwNumberOfProcessors; + if (numthreads < 1 || numthreads > 32) + numthreads = 1; + } + + Sys_Printf ("%i threads\n", numthreads); +} + + +void ThreadLock (void) +{ + if (!threaded) + return; + EnterCriticalSection (&crit); + if (enter) + Error ("Recursive ThreadLock\n"); + enter = 1; +} + +void ThreadUnlock (void) +{ + if (!threaded) + return; + if (!enter) + Error ("ThreadUnlock without lock\n"); + enter = 0; + LeaveCriticalSection (&crit); +} + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int threadid[MAX_THREADS]; + HANDLE threadhandle[MAX_THREADS]; + int i; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + // + // run threads in parallel + // + InitializeCriticalSection (&crit); + + if (numthreads == 1) + { // use same thread + func (0); + } + else + { + for (i=0 ; i + +pthread_mutex_t *my_mutex; + +void ThreadLock (void) +{ + if (my_mutex) + pthread_mutex_lock (my_mutex); +} + +void ThreadUnlock (void) +{ + if (my_mutex) + pthread_mutex_unlock (my_mutex); +} + + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int i; + pthread_t work_threads[MAX_THREADS]; + pthread_addr_t status; + pthread_attr_t attrib; + pthread_mutexattr_t mattrib; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if (pacifier) + setbuf (stdout, NULL); + + if (!my_mutex) + { + my_mutex = safe_malloc (sizeof(*my_mutex)); + if (pthread_mutexattr_create (&mattrib) == -1) + Error ("pthread_mutex_attr_create failed"); + if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1) + Error ("pthread_mutexattr_setkind_np failed"); + if (pthread_mutex_init (my_mutex, mattrib) == -1) + Error ("pthread_mutex_init failed"); + } + + if (pthread_attr_create (&attrib) == -1) + Error ("pthread_attr_create failed"); + if (pthread_attr_setstacksize (&attrib, 0x100000) == -1) + Error ("pthread_attr_setstacksize failed"); + + for (i=0 ; i +#include +#include +#include + + +int numthreads = -1; +abilock_t lck; + +void ThreadSetDefault (void) +{ + if (numthreads == -1) + numthreads = prctl(PR_MAXPPROCS); + Sys_Printf ("%i threads\n", numthreads); + usconfig (CONF_INITUSERS, numthreads); +} + + +void ThreadLock (void) +{ + spin_lock (&lck); +} + +void ThreadUnlock (void) +{ + release_lock (&lck); +} + + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int i; + int pid[MAX_THREADS]; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + + if (pacifier) + setbuf (stdout, NULL); + + init_lock (&lck); + + for (i=0 ; i 1) + Sys_Printf("threads: %d\n", numthreads); +} + +#include + +typedef struct pt_mutex_s +{ + pthread_t *owner; + pthread_mutex_t a_mutex; + pthread_cond_t cond; + unsigned int lock; +} pt_mutex_t; + +pt_mutex_t global_lock; + +void ThreadLock(void) +{ + pt_mutex_t *pt_mutex = &global_lock; + + if(!threaded) + return; + + pthread_mutex_lock(&pt_mutex->a_mutex); + if(pthread_equal(pthread_self(), (pthread_t)&pt_mutex->owner)) + pt_mutex->lock++; + else + { + if((!pt_mutex->owner) && (pt_mutex->lock == 0)) + { + pt_mutex->owner = (pthread_t *)pthread_self(); + pt_mutex->lock = 1; + } + else + { + while(1) + { + pthread_cond_wait(&pt_mutex->cond, &pt_mutex->a_mutex); + if((!pt_mutex->owner) && (pt_mutex->lock == 0)) + { + pt_mutex->owner = (pthread_t *)pthread_self(); + pt_mutex->lock = 1; + break; + } + } + } + } + pthread_mutex_unlock(&pt_mutex->a_mutex); +} + +void ThreadUnlock(void) +{ + pt_mutex_t *pt_mutex = &global_lock; + + if(!threaded) + return; + + pthread_mutex_lock(&pt_mutex->a_mutex); + pt_mutex->lock--; + + if(pt_mutex->lock == 0) + { + pt_mutex->owner = NULL; + pthread_cond_signal(&pt_mutex->cond); + } + + pthread_mutex_unlock(&pt_mutex->a_mutex); +} + +void recursive_mutex_init(pthread_mutexattr_t attribs) +{ + pt_mutex_t *pt_mutex = &global_lock; + + pt_mutex->owner = NULL; + if(pthread_mutex_init(&pt_mutex->a_mutex, &attribs) != 0) + Error("pthread_mutex_init failed\n"); + if(pthread_cond_init(&pt_mutex->cond, NULL) != 0) + Error("pthread_cond_init failed\n"); + + pt_mutex->lock = 0; +} + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + pthread_mutexattr_t mattrib; + pthread_t work_threads[MAX_THREADS]; + + int start, end; + int i=0, status=0; + + start = I_FloatTime (); + pacifier = showpacifier; + + dispatch = 0; + oldf = -1; + workcount = workcnt; + + if(numthreads == 1) + func(0); + else + { + threaded = true; + + if(pacifier) + setbuf(stdout, NULL); + + if(pthread_mutexattr_init(&mattrib) != 0) + Error("pthread_mutexattr_init failed"); +#if __GLIBC_MINOR__ == 1 + if (pthread_mutexattr_settype(&mattrib, PTHREAD_MUTEX_FAST_NP) != 0) +#else + if (pthread_mutexattr_settype(&mattrib, PTHREAD_MUTEX_ADAPTIVE_NP) != 0) +#endif + Error ("pthread_mutexattr_settype failed"); + recursive_mutex_init(mattrib); + + for (i=0 ; i", + "", + "", + "", + "", + "", + "", + "(", + ")", + "{", + "}", + "[", + "]", + ":", + "mesh", + "model", + "nodes", + "rotation", + "scaling", + "translation", + "polygons", + "position", + "vertex", + "vertices", + "HRCH", + "Softimage" +}; + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// TK_Init +// +//========================================================================== + +void TK_Init(void) +{ + int i; + + for(i = 0; i < 256; i++) + { + ASCIIToChrCode[i] = CHR_SPECIAL; + } + for(i = '0'; i <= '9'; i++) + { + ASCIIToChrCode[i] = CHR_NUMBER; + } + for(i = 'A'; i <= 'Z'; i++) + { + ASCIIToChrCode[i] = CHR_LETTER; + } + for(i = 'a'; i <= 'z'; i++) + { + ASCIIToChrCode[i] = CHR_LETTER; + } + ASCIIToChrCode[ASCII_QUOTE] = CHR_QUOTE; + ASCIIToChrCode[ASCII_UNDERSCORE] = CHR_LETTER; + ASCIIToChrCode[EOF_CHARACTER] = CHR_EOF; + tk_String = TokenStringBuffer; + IncLineNumber = FALSE; + SourceOpen = FALSE; +} + +//========================================================================== +// +// TK_OpenSource +// +//========================================================================== + +void TK_OpenSource(char *fileName) +{ + int size; + + TK_CloseSource(); + size = LoadFile(fileName, (void **)&FileStart); + strcpy(tk_SourceName, fileName); + SourceOpen = TRUE; + FileEnd = FileStart+size; + FilePtr = FileStart; + tk_Line = 1; + tk_Token = TK_NONE; + NextChr(); +} + +//========================================================================== +// +// TK_CloseSource +// +//========================================================================== + +void TK_CloseSource(void) +{ + if(SourceOpen) + { + free(FileStart); + SourceOpen = FALSE; + } +} + +//========================================================================== +// +// TK_Fetch +// +//========================================================================== + +tokenType_t TK_Fetch(void) +{ + while(Chr == ASCII_SPACE) + { + NextChr(); + } + if(Chr == '-') + { + ProcessNumberToken(); + } + else switch(ASCIIToChrCode[(byte)Chr]) + { + case CHR_EOF: + tk_Token = TK_EOF; + break; + case CHR_LETTER: + ProcessLetterToken(); + break; + case CHR_NUMBER: + ProcessNumberToken(); + break; + case CHR_QUOTE: + ProcessQuoteToken(); + break; + default: + ProcessSpecialToken(); + break; + } + return tk_Token; +} + +//========================================================================== +// +// TK_Require +// +//========================================================================== + +void TK_Require(tokenType_t tokType) +{ + if(tokType == TK_FLOATNUMBER && tk_Token == TK_INTNUMBER) + { + tk_FloatNumber = (float)tk_IntNumber; + tk_Token = TK_FLOATNUMBER; + return; + } + if(tk_Token != tokType) + { + Error("File '%s', line %d:\nExpected '%s', found '%s'.\n", + tk_SourceName, tk_Line, TokenNames[tokType], + TokenNames[tk_Token]); + } +} + +void TK_FetchRequire(tokenType_t tokType) +{ + TK_Fetch(); + TK_Require(tokType); +} + +tokenType_t TK_RequireFetch(tokenType_t tokType) +{ + TK_Require(tokType); + return TK_Fetch(); +} + +tokenType_t TK_FetchRequireFetch(tokenType_t tokType) +{ + TK_Fetch(); + TK_Require(tokType); + return TK_Fetch(); +} + +tokenType_t TK_Beyond(tokenType_t tokType) +{ + while(tk_Token != tokType) + { + if(TK_Fetch() == TK_EOF) + { + Error("File '%s':\nCould not find token '%s'.\n", // FIXME: TokenNames table not big enuff + tk_SourceName, TokenNames[tokType]); + } + } + return TK_Fetch(); +} + +void TK_BeyondRequire(tokenType_t bTok, tokenType_t rTok) +{ + TK_Beyond(bTok); + TK_Require(rTok); +} + +tokenType_t TK_Search(tokenType_t tokType) +{ + while(tk_Token != tokType) + { + if(TK_Fetch() == TK_EOF) + { + return TK_EOF; + } + } + return TK_Fetch(); +} + +tokenType_t TK_Get(tokenType_t tokType) +{ + while(tk_Token != tokType) + { + if(TK_Fetch() == TK_EOF) + { + Error("File '%s':\nCould not find token '%s'.\n", + tk_SourceName, TokenNames[tokType]); + } + } + return tk_Token; +} + +//========================================================================== +// +// ProcessLetterToken +// +//========================================================================== + +static void ProcessLetterToken(void) +{ + int i; + char *text; + + i = 0; + text = TokenStringBuffer; + while(ASCIIToChrCode[(byte)Chr] == CHR_LETTER + || ASCIIToChrCode[(byte)Chr] == CHR_NUMBER) + { + if(++i == MAX_IDENTIFIER_LENGTH) + { + Error("File '%s', line %d:\nIdentifier too long.\n", + tk_SourceName, tk_Line); + } + *text++ = Chr; + NextChr(); + } + *text = 0; + if(CheckForKeyword() == FALSE) + { + tk_Token = TK_IDENTIFIER; + } +} + +//========================================================================== +// +// CheckForKeyword +// +//========================================================================== + +static qboolean CheckForKeyword(void) +{ + int i; + + for(i = 0; Keywords[i].name != NULL; i++) + { + if(strcmp(tk_String, Keywords[i].name) == 0) + { + tk_Token = Keywords[i].token; + return TRUE; + } + } + return FALSE; +} + +//========================================================================== +// +// ProcessNumberToken +// +//========================================================================== + +static void ProcessNumberToken(void) +{ + char *buffer; + + buffer = TempBuffer; + *buffer++ = Chr; + NextChr(); + while(ASCIIToChrCode[(byte)Chr] == CHR_NUMBER) + { + *buffer++ = Chr; + NextChr(); + } + if(Chr == '.') + { // Float + *buffer++ = Chr; + NextChr(); // Skip period + while(ASCIIToChrCode[(byte)Chr] == CHR_NUMBER) + { + *buffer++ = Chr; + NextChr(); + } + *buffer = 0; + tk_FloatNumber = (float)atof(TempBuffer); + tk_Token = TK_FLOATNUMBER; + return; + } + + // Integer + *buffer = 0; + tk_IntNumber = atoi(TempBuffer); + tk_Token = TK_INTNUMBER; +} + +//========================================================================== +// +// ProcessQuoteToken +// +//========================================================================== + +static void ProcessQuoteToken(void) +{ + int i; + char *text; + + i = 0; + text = TokenStringBuffer; + NextChr(); + while(Chr != ASCII_QUOTE) + { + if(Chr == EOF_CHARACTER) + { + Error("File '%s', line %d:\n inside string.\n", + tk_SourceName, tk_Line); + } + if(++i > MAX_QUOTED_LENGTH-1) + { + Error("File '%s', line %d:\nString literal too long.\n", + tk_SourceName, tk_Line); + } + *text++ = Chr; + NextChr(); + } + *text = 0; + NextChr(); + tk_Token = TK_STRING; +} + +//========================================================================== +// +// ProcessSpecialToken +// +//========================================================================== + +static void ProcessSpecialToken(void) +{ + char c; + + c = Chr; + NextChr(); + switch(c) + { + case '(': + tk_Token = TK_LPAREN; + break; + case ')': + tk_Token = TK_RPAREN; + break; + case '{': + tk_Token = TK_LBRACE; + break; + case '}': + tk_Token = TK_RBRACE; + break; + case '[': + tk_Token = TK_LBRACKET; + break; + case ']': + tk_Token = TK_RBRACKET; + break; + case ':': + tk_Token = TK_COLON; + break; + default: + tk_Token = TK_UNKNOWNCHAR; + break; + } +} + +//========================================================================== +// +// NextChr +// +//========================================================================== + +static void NextChr(void) +{ + if(FilePtr >= FileEnd) + { + Chr = EOF_CHARACTER; + return; + } + if(IncLineNumber == TRUE) + { + tk_Line++; + IncLineNumber = FALSE; + } + Chr = *FilePtr++; + if(Chr < ASCII_SPACE) + { + if(Chr == '\n') + { + IncLineNumber = TRUE; + } + Chr = ASCII_SPACE; + } +} diff --git a/tools/quake2/qdata_heretic2/common/token.h b/tools/quake2/qdata_heretic2/common/token.h new file mode 100644 index 00000000..1e2fd27e --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/token.h @@ -0,0 +1,132 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +//************************************************************************** +//** +//** token.h +//** +//************************************************************************** + +#ifndef __TOKEN_H__ +#define __TOKEN_H__ + +#include "cmdlib.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef YES +#define YES 1 +#endif +#ifndef NO +#define NO 0 +#endif +#define ASCII_SPACE 32 +#define ASCII_QUOTE 34 +#define ASCII_UNDERSCORE 95 +#define EOF_CHARACTER 127 +#define MAX_IDENTIFIER_LENGTH 64 +#define MAX_QUOTED_LENGTH 1024 +#define MAX_FILE_NAME_LENGTH 1024 + +typedef enum +{ + TK_NONE, + TK_UNKNOWNCHAR, + TK_EOF, + TK_IDENTIFIER, // VALUE: (char *) tk_String + TK_STRING, // VALUE: (char *) tk_String + TK_INTNUMBER, // VALUE: (int) tk_IntNumber + TK_FLOATNUMBER, // VALUE: (float) tk_FloatNumber + TK_LPAREN, + TK_RPAREN, + TK_LBRACE, + TK_RBRACE, // 10 + TK_LBRACKET, + TK_RBRACKET, + TK_COLON, + TK_MESH, + TK_MODEL, // 15 + TK_NODES, + TK_ROTATION, + TK_SCALING, + TK_TRANSLATION, + TK_POLYGONS, // 20 + TK_POSITION, + TK_VERTEX, + TK_VERTICES, + TK_EDGES, + TK_HRCH, // 25 + TK_SOFTIMAGE, + TK_MATERIAL, + TK_SPLINE, // 28 + + TK_C_NAMED, + TK_OBJECT, // 30 + TK_C_TRI, + TK_C_VERTICES, + TK_C_FACES, + TK_C_VERTEX, + TK_LIST, // 35 + TK_C_FACE, + + TK_C_HEXEN, + TK_C_TRIANGLES, + TK_C_VERSION, + TK_FACES, // 40 + TK_FACE, + TK_ORIGIN, + + TK_CLUSTERS, + TK_NUM_CLUSTER_VERTICES, + TK_NAME, // 45 + TK_CLUSTER_NAME, + TK_CLUSTER_STATE, + + TK_ACTOR_DATA, + TK_UVTEXTURE, +} tokenType_t; + +void TK_Init(void); +void TK_OpenSource(char *fileName); +void TK_CloseSource(void); +tokenType_t TK_Fetch(void); +void TK_Require(tokenType_t tokType); +void TK_FetchRequire(tokenType_t tokType); +tokenType_t TK_RequireFetch(tokenType_t tokType); +tokenType_t TK_FetchRequireFetch(tokenType_t tokType); +tokenType_t TK_Beyond(tokenType_t tokType); +void TK_BeyondRequire(tokenType_t bTok, tokenType_t rTok); +tokenType_t TK_Search(tokenType_t tokType); +tokenType_t TK_Get(tokenType_t tokType); + +extern tokenType_t tk_Token; +extern int tk_Line; +extern int tk_IntNumber; +extern float tk_FloatNumber; +extern char *tk_String; +extern char tk_SourceName[MAX_FILE_NAME_LENGTH]; + +#endif diff --git a/tools/quake2/qdata_heretic2/common/trilib.c b/tools/quake2/qdata_heretic2/common/trilib.c new file mode 100644 index 00000000..e7583447 --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/trilib.c @@ -0,0 +1,1077 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// trilib.c: library for loading triangles from an Alias triangle file +// + +#include +#include "cmdlib.h" +#include "inout.h" +#include "mathlib.h" +#include "trilib.h" +#include "token.h" +#include "l3dslib.h" +#include "fmodel.h" +#if 1 +#include "qd_skeletons.h" +#endif + +// on disk representation of a face +#define FLOAT_START 99999.0 +#define FLOAT_END -FLOAT_START +#define MAGIC 123322 +#ifndef M_PI + #define M_PI 3.14159265 +#endif + +float FixHTRRotateX = 0.0; +float FixHTRRotateY = 0.0; +float FixHTRRotateZ = 0.0; +float FixHTRTranslateX = 0.0; +float FixHTRTranslateY = 0.0; +float FixHTRTranslateZ = 0.0; + +//#define NOISY 1 + +typedef struct { + float v[3]; +} vector; + +typedef struct +{ + vector n; /* normal */ + vector p; /* point */ + vector c; /* color */ + float u; /* u */ + float v; /* v */ +} aliaspoint_t; + +typedef struct { + aliaspoint_t pt[3]; +} tf_triangle; + + +void ByteSwapTri (tf_triangle *tri) +{ + int i; + + for (i=0 ; iverts[j][k] = tri.pt[j].p.v[k]; + } + } + + ptri++; + + if ((ptri - *pptri) >= MAXTRIANGLES) + Error ("Error: too many triangles; increase MAXTRIANGLES\n"); + } + } + + *numtriangles = ptri - *pptri; + + fclose (input); + + DefaultNodesList(nodesList,num_mesh_nodes,numtriangles); +} + + +//========================================================================== +// +// LoadHRC +// +//========================================================================== + +float scaling[3]; +float rotation[3]; +float translation[3]; +static char *hrc_name; + +struct +{ + float v[3]; +} vList[8192]; + +void HandleHRCModel(triangle_t **triList, int *triangleCount, mesh_node_t **nodesList, int *num_mesh_nodes, + int ActiveNode, int Depth, int numVerts) +{ + void ReadHRCClusterList(mesh_node_t *meshNode, int baseIndex); + + int i, j; + int vertexCount; + int triCount; + triangle_t *tList; + mesh_node_t *meshNode; + float x, y, z; + float x2, y2, z2; + float rx, ry, rz; + tokenType_t nextToken; + float orig_scaling[3]; + float orig_rotation[3]; + float orig_translation[3]; + int start_tri; + int pos,bit; + int vertIndexBase; + + // Update Node Info + if (nodesList) + { + TK_BeyondRequire(TK_NAME, TK_STRING); + + if (Depth == 0 || tk_String[0] == '_') + { // Root + ActiveNode = *num_mesh_nodes; + (*num_mesh_nodes)++; + if ((*num_mesh_nodes) > MAX_FM_MESH_NODES) + { + Error("Too many mesh nodes in file %s\n", hrc_name); + } + meshNode = &(*nodesList)[ActiveNode]; + +// memset(meshNode, 0, sizeof(mesh_node_t)); + strcpy(meshNode->name, tk_String); + + memset(meshNode->tris, 0, sizeof(meshNode->tris)); + memset(meshNode->verts, 0, sizeof(meshNode->verts)); + + meshNode->start_glcmds = 0; + meshNode->num_glcmds = 0; + vertIndexBase = 0; + } + else + { // Childs under the children + meshNode = &(*nodesList)[ActiveNode]; + vertIndexBase = numVerts; + } + } + else + { + meshNode = NULL; + } + + + // Get the scaling, rotation, and translation values + TK_Beyond(TK_SCALING); + for(i = 0; i < 3; i++) + { + orig_scaling[i] = scaling[i]; + + TK_Require(TK_FLOATNUMBER); + scaling[i] *= tk_FloatNumber; + + TK_Fetch(); + } + TK_Beyond(TK_ROTATION); + for(i = 0; i < 3; i++) + { + orig_rotation[i] = rotation[i]; + + TK_Require(TK_FLOATNUMBER); + rotation[i] = tk_FloatNumber; + + TK_Fetch(); + } + TK_Beyond(TK_TRANSLATION); + for(i = 0; i < 3; i++) + { + orig_translation[i] = translation[i]; + + TK_Require(TK_FLOATNUMBER); + translation[i] += tk_FloatNumber; + + TK_Fetch(); + } + + rx = ((rotation[0]-90.0)/360.0)*2.0*M_PI; + ry = (rotation[2]/360.0)*2.0*M_PI; + rz = (rotation[1]/360.0)*2.0*M_PI; + + // rjr - might not work if there an item doesn't have a mesh + nextToken = tk_Token; + if (nextToken == TK_ACTOR_DATA) + { + while (nextToken != TK_MODEL && nextToken != TK_RBRACE) + { + nextToken = TK_Fetch(); + } + } + + while (nextToken == TK_SPLINE) + { // spline node has two right braces + nextToken = TK_Beyond(TK_RBRACE); + nextToken = TK_Beyond(TK_RBRACE); + } + + while (nextToken == TK_MATERIAL) + { + nextToken = TK_Beyond(TK_RBRACE); + } + + while(nextToken == TK_MODEL) + { + HandleHRCModel(triList,triangleCount,nodesList,num_mesh_nodes,ActiveNode, Depth+1, 0); + + nextToken = TK_Fetch(); + } + + if (nextToken == TK_MESH) + { + // Get all the tri and vertex info + TK_BeyondRequire(TK_VERTICES, TK_INTNUMBER); + vertexCount = tk_IntNumber; + for(i = 0; i < vertexCount; i++) + { + TK_BeyondRequire(TK_LBRACKET, TK_INTNUMBER); + if(tk_IntNumber != i) + { + Error("File '%s', line %d:\nVertex index mismatch.\n", + tk_SourceName, tk_Line); + } + TK_Beyond(TK_POSITION); + // Apply the scaling, rotation, and translation in the order + // specified in the HRC file. This could be wrong. + TK_Require(TK_FLOATNUMBER); + x = tk_FloatNumber*scaling[0]; + TK_FetchRequire(TK_FLOATNUMBER); + y = tk_FloatNumber*scaling[1]; + TK_FetchRequire(TK_FLOATNUMBER); + z = tk_FloatNumber*scaling[2]; + + y2 = y*cos(rx)+z*sin(rx); + z2 = -y*sin(rx)+z*cos(rx); + y = y2; + z = z2; + + x2 = x*cos(ry)-z*sin(ry); + z2 = x*sin(ry)+z*cos(ry); + x = x2; + z = z2; + + x2 = x*cos(rz)+y*sin(rz); + y2 = -x*sin(rz)+y*cos(rz); + x = x2; + y = y2; + + vList[i].v[0] = x+translation[0]; + vList[i].v[1] = y-translation[2]; + vList[i].v[2] = z+translation[1]; + } + TK_BeyondRequire(TK_POLYGONS, TK_INTNUMBER); + triCount = tk_IntNumber; + if(triCount >= MAXTRIANGLES) + { + Error("Too many triangles in file %s\n", hrc_name); + } + + start_tri = *triangleCount; + *triangleCount += triCount; + + tList = *triList; + + for(i = 0; i < triCount; i++) + { + if (meshNode) + { // Update the node + pos = (i + start_tri) >> 3; + bit = 1 << ((i + start_tri) & 7 ); + meshNode->tris[pos] |= bit; + } + + TK_BeyondRequire(TK_LBRACKET, TK_INTNUMBER); + if(tk_IntNumber != i) + { + Error("File '%s', line %d:\nTriangle index mismatch.\n", + tk_SourceName, tk_Line); + } + TK_BeyondRequire(TK_NODES, TK_INTNUMBER); + if(tk_IntNumber != 3) + { + Error("File '%s', line %d:\nBad polygon vertex count: %d.", + tk_SourceName, tk_Line, tk_IntNumber); + } + tList[i+start_tri].HasUV = true; + for(j = 0; j < 3; j++) + { + TK_BeyondRequire(TK_LBRACKET, TK_INTNUMBER); + if(tk_IntNumber != j) + { + Error("File '%s', line %d:\nTriangle vertex index" + " mismatch. %d should be %d\n", tk_SourceName, tk_Line, + tk_IntNumber, j); + } + TK_BeyondRequire(TK_VERTEX, TK_INTNUMBER); + + tList[i+start_tri].verts[2-j][0] = vList[tk_IntNumber].v[0]; + tList[i+start_tri].verts[2-j][1] = vList[tk_IntNumber].v[1]; + tList[i+start_tri].verts[2-j][2] = vList[tk_IntNumber].v[2]; +#if 1 + tList[i+start_tri].indicies[2-j] = tk_IntNumber+vertIndexBase; +#endif + TK_BeyondRequire(TK_UVTEXTURE, TK_FLOATNUMBER); + tList[i+start_tri].uv[2-j][0] = tk_FloatNumber; + TK_Fetch(); + TK_Require(TK_FLOATNUMBER); + tList[i+start_tri].uv[2-j][1] = tk_FloatNumber; + } + + /* printf("Face %i:\n v0: %f, %f, %f\n v1: %f, %f, %f\n" + " v2: %f, %f, %f\n", i, + tList[i].verts[0][0], + tList[i].verts[0][1], + tList[i].verts[0][2], + tList[i].verts[1][0], + tList[i].verts[1][1], + tList[i].verts[1][2], + tList[i].verts[2][0], + tList[i].verts[2][1], + tList[i].verts[2][2]); + */ + } + + TK_Beyond(TK_RBRACE); + TK_Beyond(TK_RBRACE); + + if (tk_Token == TK_EDGES) + { + // TK_Beyond(TK_EDGES); + TK_Beyond(TK_RBRACE); + } + + scaling[0] = scaling[1] = scaling[2] = 1.0; + // rotation[0] = rotation[1] = rotation[2] = 0.0; + // translation[0] = translation[1] = translation[2] = 0.0; + + // See if there are any other models belonging to this node + +#if 1 + TK_Fetch(); + + nextToken = tk_Token; + if(nextToken == TK_CLUSTERS) + { + if(g_skelModel.clustered == -1) + { + ReadHRCClusterList(meshNode, vertIndexBase); + } + else + { + nextToken = TK_Get(TK_CLUSTER_NAME); + + while (nextToken == TK_CLUSTER_NAME) + { + TK_BeyondRequire(TK_CLUSTER_STATE, TK_INTNUMBER); + nextToken = TK_Fetch(); + } + } + + // one right brace follow the list of clusters + nextToken = TK_Beyond(TK_RBRACE); + } + else + { + if(g_skelModel.clustered == -1 && !vertIndexBase) + { + meshNode->clustered = false; + } + } +#endif + + nextToken = tk_Token; + if(nextToken == TK_SPLINE) + { + while (nextToken == TK_SPLINE) + { // spline node has two right braces + nextToken = TK_Beyond(TK_RBRACE); + nextToken = TK_Beyond(TK_RBRACE); + } + + nextToken = TK_Beyond(TK_RBRACE); + } + + while (nextToken == TK_MATERIAL) + { + nextToken = TK_Beyond(TK_RBRACE); + } + + while(nextToken == TK_MODEL) + { + HandleHRCModel(triList,triangleCount,nodesList, num_mesh_nodes, ActiveNode, Depth+1, vertexCount+vertIndexBase); + + nextToken = TK_Fetch(); + } + } + + for(i=0;i<3;i++) + { + scaling[i] = orig_scaling[i]; + rotation[i] = orig_rotation[i]; + translation[i] = orig_translation[i]; + } +} + +static void LoadHRC(char *fileName, triangle_t **triList, int *triangleCount, mesh_node_t **nodesList, int *num_mesh_nodes) +{ + if (nodesList) + { + *num_mesh_nodes = 0; + + if(!*nodesList) + { + *nodesList = (mesh_node_t *) SafeMalloc(MAX_FM_MESH_NODES * sizeof(mesh_node_t), "Mesh Node List"); + } + } + + hrc_name = fileName; + + scaling[0] = scaling[1] = scaling[2] = 1.0; + rotation[0] = rotation[1] = rotation[2] = 0.0; + translation[0] = translation[1] = translation[2] = 0.0; + + *triangleCount = 0; + *triList = (triangle_t *) SafeMalloc(MAXTRIANGLES*sizeof(triangle_t), "Triangle list"); + memset(*triList,0,MAXTRIANGLES*sizeof(triangle_t)); + + TK_OpenSource(fileName); + TK_FetchRequire(TK_HRCH); + TK_FetchRequire(TK_COLON); + TK_FetchRequire(TK_SOFTIMAGE); + + // prime it + TK_Beyond(TK_MODEL); + + HandleHRCModel(triList, triangleCount, nodesList, num_mesh_nodes, 0, 0, 0); + TK_CloseSource(); +} + +//========================================================================== +// +// LoadHTR +// +//========================================================================== +/* +static int Version2; + +void HandleHTRModel(triangle_t **triList, int *triangleCount, mesh_node_t **nodesList, int *num_mesh_nodes, + int ActiveNode, int Depth, int numVerts) +{ + int i, j; + int vertexCount; + int vertexNum; + int triCount; + float origin[3]; + triangle_t *tList; + float x, y, z; + float x2, y2, z2; + float rx, ry, rz; + mesh_node_t *meshNode; + int pos,bit; + int vertIndexBase; + int start_tri; + + if (nodesList) + { + TK_BeyondRequire(TK_NAME, TK_STRING); + + if (Depth == 0 || tk_String[0] == '_') + { // Root + ActiveNode = *num_mesh_nodes; + (*num_mesh_nodes)++; + if ((*num_mesh_nodes) > MAX_FM_MESH_NODES) + { + Error("Too many mesh nodes in file %s\n", hrc_name); + } + meshNode = &(*nodesList)[ActiveNode]; + +// memset(meshNode, 0, sizeof(mesh_node_t)); + strcpy(meshNode->name, tk_String); + + memset(meshNode->tris, 0, sizeof(meshNode->tris)); + memset(meshNode->verts, 0, sizeof(meshNode->verts)); + + meshNode->start_glcmds = 0; + meshNode->num_glcmds = 0; + vertIndexBase = 0; + } + else + { // Childs under the children + meshNode = &(*nodesList)[ActiveNode]; + vertIndexBase = numVerts; + } + } + else + { + meshNode = NULL; + } + + // Get vertex count + TK_BeyondRequire(TK_VERTICES, TK_INTNUMBER); + vertexCount = tk_IntNumber; + + // Get triangle count + TK_BeyondRequire(TK_FACES, TK_INTNUMBER); + triCount = tk_IntNumber; + if(triCount >= MAXTRIANGLES) + { + Error("Too many triangles in file %s\n", hrc_name); + } + + // Get origin + TK_Beyond(TK_ORIGIN); + TK_Require(TK_FLOATNUMBER); + origin[0] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + origin[1] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + origin[2] = tk_FloatNumber; + + //rx = 90.0/360.0*2.0*M_PI; + rx = FixHTRRotateX/360.0*2.0*M_PI; + ry = FixHTRRotateY/360.0*2.0*M_PI; + rz = FixHTRRotateZ/360.0*2.0*M_PI; + + // Get vertex list + for(i = 0; i < vertexCount; i++) + { + TK_FetchRequire(TK_VERTEX); + TK_FetchRequire(TK_FLOATNUMBER); + x = tk_FloatNumber-origin[0]; + TK_FetchRequire(TK_FLOATNUMBER); + y = tk_FloatNumber-origin[1]; + TK_FetchRequire(TK_FLOATNUMBER); + z = tk_FloatNumber-origin[2]; + + x += FixHTRTranslateX; + y += FixHTRTranslateY; + z += FixHTRTranslateZ; + + y2 = y*cos(rx)-z*sin(rx); + z2 = y*sin(rx)+z*cos(rx); + y = y2; + z = z2; + x2 = x*cos(ry)+z*sin(ry); + z2 = -x*sin(ry)+z*cos(ry); + x = x2; + z = z2; + x2 = x*cos(rz)-y*sin(rz); + y2 = x*sin(rz)+y*cos(rz); + x = x2; + y = y2; + + vList[i].v[0] = x; + vList[i].v[1] = y; + vList[i].v[2] = z; + } + + start_tri = *triangleCount; + *triangleCount += triCount; + + tList = *triList; + + // Get face list + for(i = 0; i < triCount; i++) + { + if (meshNode) + { // Update the node + pos = (i + start_tri) >> 3; + bit = 1 << ((i + start_tri) & 7 ); + meshNode->tris[pos] |= bit; + } + + TK_FetchRequire(TK_FACE); + TK_FetchRequire(TK_LPAREN); + for(j = 0; j < 3; j++) + { + TK_FetchRequire(TK_INTNUMBER); + vertexNum = tk_IntNumber-1; + if(vertexNum >= vertexCount) + { + Error("File '%s', line %d:\nVertex number" + " >= vertexCount: %d\n", tk_SourceName, tk_Line, + tk_IntNumber); + } + tList[i+start_tri].verts[2-j][0] = vList[vertexNum].v[0]; + tList[i+start_tri].verts[2-j][1] = vList[vertexNum].v[1]; + tList[i+start_tri].verts[2-j][2] = vList[vertexNum].v[2]; + } + TK_FetchRequire(TK_RPAREN); +#ifdef _QDATA + if (Version2) + { + TK_FetchRequire(TK_FLOATNUMBER); + tList[i+start_tri].uv[0][0]=tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + tList[i+start_tri].uv[0][1]=tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + tList[i+start_tri].uv[1][0]=tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + tList[i+start_tri].uv[1][1]=tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + tList[i+start_tri].uv[2][0]=tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + tList[i+start_tri].uv[2][1]=tk_FloatNumber; + tList[i+start_tri].HasUV=1; + } + else + tList[i+start_tri].HasUV=0; +#endif +// printf("Face %i:\n v0: %f, %f, %f\n v1: %f, %f, %f\n" +// " v2: %f, %f, %f\n", i, +// tList[i].verts[0][0], +// tList[i].verts[0][1], +// tList[i].verts[0][2], +// tList[i].verts[1][0], +// tList[i].verts[1][1], +// tList[i].verts[1][2], +// tList[i].verts[2][0], +// tList[i].verts[2][1], +// tList[i].verts[2][2]); + + } + + TK_Fetch(); + + if (tk_Token == TK_VERTICES) + { + HandleHTRModel(triList,triangleCount,nodesList, num_mesh_nodes, ActiveNode, Depth+1, vertexCount+vertIndexBase); + } +} + +static void LoadHTR(char *fileName, triangle_t **triList, int *triangleCount, mesh_node_t **nodesList, int *num_mesh_nodes) +{ + if (nodesList) + { + *num_mesh_nodes = 0; + + if(!*nodesList) + { + *nodesList = SafeMalloc(MAX_FM_MESH_NODES * sizeof(mesh_node_t), "Mesh Node List"); + } + } + + hrc_name = fileName; + + scaling[0] = scaling[1] = scaling[2] = 1.0; + rotation[0] = rotation[1] = rotation[2] = 0.0; + translation[0] = translation[1] = translation[2] = 0.0; + + *triangleCount = 0; + *triList = SafeMalloc(MAXTRIANGLES*sizeof(triangle_t), "Triangle list"); + memset(*triList,0,MAXTRIANGLES*sizeof(triangle_t)); + + TK_OpenSource(fileName); + + TK_Beyond(TK_C_HEXEN); + TK_Beyond(TK_C_TRIANGLES); + TK_BeyondRequire(TK_C_VERSION, TK_INTNUMBER); + if(tk_IntNumber != 1&&tk_IntNumber != 2) + { + Error("Unsupported version (%d) in file %s\n", tk_IntNumber, + fileName); + } + Version2=(tk_IntNumber==2); + + + HandleHTRModel(triList, triangleCount, nodesList, num_mesh_nodes, 0, 0, 0); +} + +*/ + +static void LoadHTR(char *fileName, triangle_t **triList, int *triangleCount, mesh_node_t **nodesList, int *num_mesh_nodes) +{ + int Version2=0; + int i, j; + int vertexCount; + int vertexNum; + struct + { + float v[3]; + } *vList; + int triCount; + float origin[3]; + triangle_t *tList; + float x, y, z; + float x2, y2, z2; + float rx, ry, rz; + + if (nodesList) + { + *num_mesh_nodes = 0; + *nodesList = (mesh_node_t *) SafeMalloc(MAX_FM_MESH_NODES * sizeof(mesh_node_t), "Mesh Node List"); + } + + TK_OpenSource(fileName); + + TK_Beyond(TK_C_HEXEN); + TK_Beyond(TK_C_TRIANGLES); + TK_BeyondRequire(TK_C_VERSION, TK_INTNUMBER); + if(tk_IntNumber != 1&&tk_IntNumber != 2) + { + Error("Unsupported version (%d) in file %s\n", tk_IntNumber, + fileName); + } + Version2=(tk_IntNumber==2); + + + // Get vertex count + TK_BeyondRequire(TK_VERTICES, TK_INTNUMBER); + vertexCount = tk_IntNumber; + vList = (void *) SafeMalloc(vertexCount*sizeof vList[0], "Vertex list"); + + // Get triangle count + TK_BeyondRequire(TK_FACES, TK_INTNUMBER); + triCount = tk_IntNumber; + if(triCount >= MAXTRIANGLES) + { + Error("Too many triangles in file %s\n", fileName); + } + *triangleCount = triCount; + tList = (triangle_t *) SafeMalloc(MAXTRIANGLES*sizeof(triangle_t), "Triangle list"); + *triList = tList; + memset(*triList,0,MAXTRIANGLES*sizeof(triangle_t)); + + // Get origin + TK_Beyond(TK_ORIGIN); + TK_Require(TK_FLOATNUMBER); + origin[0] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + origin[1] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + origin[2] = tk_FloatNumber; + + //rx = 90.0/360.0*2.0*M_PI; + rx = FixHTRRotateX/360.0*2.0*M_PI; + ry = FixHTRRotateY/360.0*2.0*M_PI; + rz = FixHTRRotateZ/360.0*2.0*M_PI; + + // Get vertex list + for(i = 0; i < vertexCount; i++) + { + TK_FetchRequire(TK_VERTEX); + TK_FetchRequire(TK_FLOATNUMBER); + x = tk_FloatNumber-origin[0]; + TK_FetchRequire(TK_FLOATNUMBER); + y = tk_FloatNumber-origin[1]; + TK_FetchRequire(TK_FLOATNUMBER); + z = tk_FloatNumber-origin[2]; + + x += FixHTRTranslateX; + y += FixHTRTranslateY; + z += FixHTRTranslateZ; + + y2 = y*cos(rx)-z*sin(rx); + z2 = y*sin(rx)+z*cos(rx); + y = y2; + z = z2; + x2 = x*cos(ry)+z*sin(ry); + z2 = -x*sin(ry)+z*cos(ry); + x = x2; + z = z2; + x2 = x*cos(rz)-y*sin(rz); + y2 = x*sin(rz)+y*cos(rz); + x = x2; + y = y2; + + vList[i].v[0] = x; + vList[i].v[1] = y; + vList[i].v[2] = z; + } + + // Get face list + for(i = 0; i < triCount; i++) + { + TK_FetchRequire(TK_FACE); + TK_FetchRequire(TK_LPAREN); + for(j = 0; j < 3; j++) + { + TK_FetchRequire(TK_INTNUMBER); + vertexNum = tk_IntNumber-1; + if(vertexNum >= vertexCount) + { + Error("File '%s', line %d:\nVertex number" + " >= vertexCount: %d\n", tk_SourceName, tk_Line, + tk_IntNumber); + } + tList[i].verts[2-j][0] = vList[vertexNum].v[0]; + tList[i].verts[2-j][1] = vList[vertexNum].v[1]; + tList[i].verts[2-j][2] = vList[vertexNum].v[2]; + } + TK_FetchRequire(TK_RPAREN); +#if 1 + if (Version2) + { + TK_FetchRequire(TK_FLOATNUMBER); + tList[i].uv[2][0]= fmod(1000+tk_FloatNumber,1); + TK_FetchRequire(TK_FLOATNUMBER); + tList[i].uv[2][1]=fmod(1000+tk_FloatNumber,1); + TK_FetchRequire(TK_FLOATNUMBER); + tList[i].uv[1][0]=fmod(1000+tk_FloatNumber,1); + TK_FetchRequire(TK_FLOATNUMBER); + tList[i].uv[1][1]=fmod(1000+tk_FloatNumber,1); + TK_FetchRequire(TK_FLOATNUMBER); + tList[i].uv[0][0]=fmod(1000+tk_FloatNumber,1); + TK_FetchRequire(TK_FLOATNUMBER); + tList[i].uv[0][1]=fmod(1000+tk_FloatNumber,1); + tList[i].HasUV=1; + } + else + tList[i].HasUV=0; +#endif +/* printf("Face %i:\n v0: %f, %f, %f\n v1: %f, %f, %f\n" + " v2: %f, %f, %f\n", i, + tList[i].verts[0][0], + tList[i].verts[0][1], + tList[i].verts[0][2], + tList[i].verts[1][0], + tList[i].verts[1][1], + tList[i].verts[1][2], + tList[i].verts[2][0], + tList[i].verts[2][1], + tList[i].verts[2][2]); +*/ + } + + free(vList); + TK_CloseSource(); + DefaultNodesList(nodesList,num_mesh_nodes,triangleCount); +} + +//========================================================================== +// +// LoadTriangleList +// +//========================================================================== + +void LoadTriangleList(char *fileName, triangle_t **triList, int *triangleCount, mesh_node_t **ppmnodes, int *num_mesh_nodes) +{ + FILE *file1; + int dot = '.'; + char *dotstart; + char InputFileName[256]; + + dotstart = strrchr(fileName,dot); // Does it already have an extension on the file name? + + if (!dotstart) + { + strcpy(InputFileName, fileName); + strcat(InputFileName, ".hrc"); + if((file1 = fopen(InputFileName, "rb")) != NULL) + { + fclose(file1); + LoadHRC(InputFileName, triList, triangleCount, ppmnodes, num_mesh_nodes); + printf(" - assuming .HRC\n"); + return; + } + + strcpy(InputFileName, fileName); + strcat(InputFileName, ".asc"); + if((file1 = fopen(InputFileName, "rb")) != NULL) + { + fclose(file1); + LoadASC(InputFileName, triList, triangleCount, ppmnodes, num_mesh_nodes); + printf(" - assuming .ASC\n"); + return; + } + + strcpy(InputFileName, fileName); + strcat(InputFileName, ".tri"); + if((file1 = fopen(InputFileName, "rb")) != NULL) + { + fclose(file1); + LoadTRI(InputFileName, triList, triangleCount, ppmnodes, num_mesh_nodes); + printf(" - assuming .TRI\n"); + return; + } + + strcpy(InputFileName, fileName); + strcat(InputFileName, ".3ds"); + if((file1 = fopen(InputFileName, "rb")) != NULL) + { + fclose(file1); + Load3DSTriangleList (InputFileName, triList, triangleCount, ppmnodes, num_mesh_nodes); + printf(" - assuming .3DS\n"); + return; + } + + strcpy(InputFileName, fileName); + strcat(InputFileName, ".htr"); + if((file1 = fopen(InputFileName, "rb")) != NULL) + { + fclose(file1); + LoadHTR (InputFileName, triList, triangleCount, ppmnodes, num_mesh_nodes); + printf(" - assuming .HTR\n"); + return; + } + Error("\n Could not open file '%s':\n" + "No HRC, ASC, 3DS, HTR, or TRI match.\n", fileName); + } + else + { + if((file1 = fopen(fileName, "rb")) != NULL) + { + printf("\n"); + fclose(file1); + if (strcmp(dotstart,".hrc") == 0 || strcmp(dotstart,".HRC") == 0) + { + LoadHRC(fileName, triList, triangleCount, ppmnodes, num_mesh_nodes); + } + else if (strcmp(dotstart,".asc") == 0 || strcmp(dotstart,".ASC") == 0) + { + LoadASC(fileName, triList, triangleCount, ppmnodes, num_mesh_nodes); + } + else if (strcmp(dotstart,".tri") == 0 || strcmp(dotstart,".TRI") == 0) + { + LoadTRI(fileName, triList, triangleCount, ppmnodes, num_mesh_nodes); + } + else if (strcmp(dotstart,".3ds") == 0 || strcmp(dotstart,".3DS") == 0) + { + Load3DSTriangleList (fileName, triList, triangleCount, ppmnodes, num_mesh_nodes); + } + else if (strcmp(dotstart,".htr") == 0 || strcmp(dotstart,".HTR") == 0) + { + LoadHTR (fileName, triList, triangleCount, ppmnodes, num_mesh_nodes); + } + else + { + Error("Could not open file '%s':\n",fileName); + return; + } + } + else //failed to load file + { + Error("Could not open file '%s':\n",fileName); + } + + } +} diff --git a/tools/quake2/qdata_heretic2/common/trilib.h b/tools/quake2/qdata_heretic2/common/trilib.h new file mode 100644 index 00000000..94263e0b --- /dev/null +++ b/tools/quake2/qdata_heretic2/common/trilib.h @@ -0,0 +1,56 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// trilib.h: header file for loading triangles from an Alias triangle file +// + +#include "fmodel.h" + +#define MAXTRIANGLES MAX_FM_TRIANGLES + +typedef struct +{ + vec3_t verts[3]; +#if 1 + int indicies[3]; + float uv[3][2]; + qboolean HasUV; +#endif +} triangle_t; + +#define NUM_CLUSTERS 8 + +typedef struct +{ + char name[64]; + byte tris[MAXTRIANGLES>>3]; + byte verts[MAX_FM_VERTS>>3]; + int start_glcmds, num_glcmds; + + int *clusters[NUM_CLUSTERS]; + struct IntListNode_s *vertLists[NUM_CLUSTERS]; + int num_verts[NUM_CLUSTERS + 1]; + int new_num_verts[NUM_CLUSTERS + 1]; + qboolean clustered; +} mesh_node_t; + +void LoadTriangleList (char *filename, triangle_t **pptri, int *numtriangles, mesh_node_t **ppmnodes, int *num_mesh_nodes); diff --git a/tools/quake2/qdata_heretic2/fmodels.c b/tools/quake2/qdata_heretic2/fmodels.c new file mode 100644 index 00000000..7570240a --- /dev/null +++ b/tools/quake2/qdata_heretic2/fmodels.c @@ -0,0 +1,3404 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qd_fmodel.h" +#include "animcomp.h" +#include "qd_skeletons.h" +#include "skeletons.h" +#include "qdata.h" +#include "flex.h" +#include "reference.h" + +#include + +/* +======================================================================== + +.FM triangle flexible model file format + +======================================================================== +*/ + +//================================================================= + +#define NUMVERTEXNORMALS 162 + +extern float avertexnormals[NUMVERTEXNORMALS][3]; + +#define MAX_GROUPS 128 + +typedef struct +{ + triangle_t triangle; + int group; +} trigroup_t; + +#define TRIVERT_DIST .1 + +typedef struct +{ + int start_frame; + int num_frames; + int degrees; + char *mat; + char *ccomp; + char *cbase; + float *cscale; + float *coffset; + float trans[3]; + float scale[3]; + float bmin[3]; + float bmax[3]; +} fmgroup_t; + +//================================================================ + +// Initial +fmheader_t fmheader; + +// Skin +extern char g_skins[MAX_FM_SKINS][64]; + +// ST Coord +extern fmstvert_t base_st[MAX_FM_VERTS]; + +// Triangles +extern fmtriangle_t triangles[MAX_FM_TRIANGLES]; + +// Frames +fmframe_t g_frames[MAX_FM_FRAMES]; +//fmframe_t *g_FMframes; + +// GL Commands +extern int commands[16384]; +extern int numcommands; + + +// +// varibles set by commands +// +extern float scale_up; // set by $scale +extern vec3_t adjust; // set by $origin +extern int g_fixedwidth, g_fixedheight; // set by $skinsize +extern char modelname[64]; // set by $modelname + + +extern char *g_outputDir; + + +// Mesh Nodes +mesh_node_t *pmnodes = NULL; +fmmeshnode_t mesh_nodes[MAX_FM_MESH_NODES]; + +fmgroup_t groups[MAX_GROUPS]; +int num_groups; +int frame_to_group[MAX_FM_FRAMES]; + +// +// variables set by command line arguments +// +qboolean g_no_opimizations = false; + + +// +// base frame info +// +static int triangle_st[MAX_FM_TRIANGLES][3][2]; + + +// number of gl vertices +extern int numglverts; +// indicates if a triangle has already been used in a glcmd +extern int used[MAX_FM_TRIANGLES]; +// indicates if a triangle has translucency in it or not +static qboolean translucent[MAX_FM_TRIANGLES]; + +// main output file handle +extern FILE *headerouthandle; +// output sizes of buildst() +static int skin_width, skin_height; + + +// statistics +static int total_skin_pixels; +static int skin_pixels_used; + +int ShareVertex( trigroup_t trione, trigroup_t tritwo); +float DistBetween(vec3_t point1, vec3_t point2); +int GetNumTris( trigroup_t *tris, int group); +void GetOneGroup(trigroup_t *tris, int grp, triangle_t* triangles); +void ScaleTris( vec3_t min, vec3_t max, int Width, int Height, float* u, float* v, int verts); +void NewDrawLine(int x1, int y1, int x2, int y2, unsigned char* picture, int width, int height); + +#ifndef _WIN32 + +void strupr(char *string) +{ + int i; + + for (i=0 ; iverts[fmheader.num_xyz]; + + WriteHeader(modelouthandle, FM_HEADER_NAME, FM_HEADER_VER, sizeof(fmheader), &fmheader); + + // + // write out the skin names + // + + WriteHeader(modelouthandle, FM_SKIN_NAME, FM_SKIN_VER, fmheader.num_skins * MAX_FM_SKINNAME, g_skins); + + // + // write out the texture coordinates + // + c_on = c_off = 0; + for (i=0 ; iname, in->name); + for (j=0 ; j<3 ; j++) + { + out->scale[j] = (in->maxs[j] - in->mins[j])/255; + out->translate[j] = in->mins[j]; + + outFrames[i].scale[j] = out->scale[j]; + outFrames[i].translate[j] = out->translate[j]; + } + + for (j=0 ; jverts[j].lightnormalindex = in->v[j].lightnormalindex; + + for (k=0 ; k<3 ; k++) + { + // scale to byte values & min/max check + v = Q_rint ( (in->v[j].v[k] - out->translate[k]) / out->scale[k] ); + + // clamp, so rounding doesn't wrap from 255.6 to 0 + if (v > 255.0) + v = 255.0; + if (v < 0) + v = 0; + out->verts[j].v[k] = v; + } + } + + for (j=0 ; j<3 ; j++) + { + out->scale[j] = LittleFloat (out->scale[j]); + out->translate[j] = LittleFloat (out->translate[j]); + } + + SafeWrite (modelouthandle, out, fmheader.framesize); + } + + // Go back and finish the header + // WriteHeader(modelouthandle, FM_FRAME_NAME, FM_FRAME_VER, -1, NULL); + } + else + { + WriteHeader(modelouthandle, FM_SHORT_FRAME_NAME, FM_SHORT_FRAME_VER,FRAME_NAME_LEN*fmheader.num_frames, NULL); + for (i=0 ; iname,FRAME_NAME_LEN); + } + WriteHeader(modelouthandle, FM_NORMAL_NAME, FM_NORMAL_VER,fmheader.num_xyz, NULL); + in = &g_frames[0]; + for (j=0 ; jv[j].lightnormalindex,1); + } + + // + // write out glcmds + // + WriteHeader(modelouthandle, FM_GLCMDS_NAME, FM_GLCMDS_VER, numcommands*4, commands); + + // + // write out mesh nodes + // + for(i=0;idegrees*sizeof(char) + char *ccomp; g->num_frames*g->degrees*sizeof(char) + char *cbase; fmheader.num_xyz*3*sizeof(unsigned char) + float *cscale; g->degrees*sizeof(float) + float *coffset; g->degrees*sizeof(float) + float trans[3]; 3*sizeof(float) + float scale[3]; 3*sizeof(float) +} fmgroup_t; +*/ + int tmp,k; + fmgroup_t *g; + size=sizeof(int)+fmheader.num_frames*sizeof(int); + for (k=0;kdegrees*sizeof(char); + size+=g->num_frames*g->degrees*sizeof(char); + size+=fmheader.num_xyz*3*sizeof(unsigned char); + size+=g->degrees*sizeof(float); + size+=g->degrees*sizeof(float); + size+=12*sizeof(float); + } + WriteHeader(modelouthandle, FM_COMP_NAME, FM_COMP_VER,size, NULL); + SafeWrite (modelouthandle,&num_groups,sizeof(int)); + SafeWrite (modelouthandle,frame_to_group,sizeof(int)*fmheader.num_frames); + + for (k=0;kstart_frame); + SafeWrite (modelouthandle,&tmp,sizeof(int)); + tmp=LittleLong(g->num_frames); + SafeWrite (modelouthandle,&tmp,sizeof(int)); + tmp=LittleLong(g->degrees); + SafeWrite (modelouthandle,&tmp,sizeof(int)); + + SafeWrite (modelouthandle,g->mat,fmheader.num_xyz*3*g->degrees*sizeof(char)); + SafeWrite (modelouthandle,g->ccomp,g->num_frames*g->degrees*sizeof(char)); + SafeWrite (modelouthandle,g->cbase,fmheader.num_xyz*3*sizeof(unsigned char)); + SafeWrite (modelouthandle,g->cscale,g->degrees*sizeof(float)); + SafeWrite (modelouthandle,g->coffset,g->degrees*sizeof(float)); + SafeWrite (modelouthandle,g->trans,3*sizeof(float)); + SafeWrite (modelouthandle,g->scale,3*sizeof(float)); + SafeWrite (modelouthandle,g->bmin,3*sizeof(float)); + SafeWrite (modelouthandle,g->bmax,3*sizeof(float)); + free(g->mat); + free(g->ccomp); + free(g->cbase); + free(g->cscale); + free(g->coffset); + } + } + + // write the skeletal info + if(g_skelModel.type != SKEL_NULL) + { + size = 0; + + temp = sizeof(int); // change this to a byte + memcpy(data + size, &g_skelModel.type, temp); + size += temp; + + // number of joints + temp = sizeof(int); // change this to a byte + memcpy(data + size, &numJointsInSkeleton[g_skelModel.type], temp); + size += temp; + + // number of verts in each joint cluster + temp = sizeof(int)*numJointsInSkeleton[g_skelModel.type]; // change this to shorts + memcpy(data + size, &g_skelModel.new_num_verts[1], temp); + size += temp; + + // cluster verts + for(i = 0; i < numJointsInSkeleton[g_skelModel.type]; ++i) + { + current = g_skelModel.vertLists[i]; + while(current) + { + temp = sizeof(int); // change this to a short + memcpy(data + size, ¤t->data, temp); + size += temp; + toFree = current; + current = current->next; + free(toFree); // freeing of memory allocated in ReplaceClusterIndex called in Cmd_Base + } + } + + if(!num_groups) // joints are stored with regular verts for compressed models + { + framesWritten = true; + + temp = sizeof(int); // change this to a byte + memcpy(data + size, &framesWritten, temp); + size += temp; + + for (i = 0; i < fmheader.num_frames; ++i) + { + in = &g_frames[i]; + + for (j = 0 ; j < numJointsInSkeleton[g_skelModel.type]; ++j) + { + for (k=0 ; k<3 ; k++) + { + // scale to byte values & min/max check + v = Q_rint ( (in->joints[j].placement.origin[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); + + // write out origin as a float since they arn't clamped + temp = sizeof(float); // change this to a short + assert(size+temp < DATA_SIZE); + memcpy(data + size, &v, temp); + size += temp; + } + + for (k=0 ; k<3 ; k++) + { + v = Q_rint ( (in->joints[j].placement.direction[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); + + // write out origin as a float since they arn't clamped + temp = sizeof(float); // change this to a short + assert(size+temp < DATA_SIZE); + memcpy(data + size, &v, temp); + size += temp; + } + + for (k=0 ; k<3 ; k++) + { + v = Q_rint ( (in->joints[j].placement.up[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); + + // write out origin as a float since they arn't clamped + temp = sizeof(float); // change this to a short + assert(size+temp < DATA_SIZE); + memcpy(data + size, &v, temp); + size += temp; + } + } + } + + } + else + { + temp = sizeof(int); // change this to a byte + memcpy(data + size, &framesWritten, temp); + size += temp; + } + + WriteHeader(modelouthandle, FM_SKELETON_NAME, FM_SKELETON_VER, size, data); + } + + if(g_skelModel.references != REF_NULL) + { + int refnum; + + size = 0; + if (RefPointNum <= 0) + { // Hard-coded labels + refnum = numReferences[g_skelModel.references]; + } + else + { // Labels indicated in QDT + refnum = RefPointNum; + } + + temp = sizeof(int); // change this to a byte + memcpy(data2 + size, &g_skelModel.references, temp); + size += temp; + + if(!num_groups) + { + framesWritten = true; + + temp = sizeof(int); // change this to a byte + memcpy(data2 + size, &framesWritten, temp); + size += temp; + + for (i = 0; i < fmheader.num_frames; ++i) + { + in = &g_frames[i]; + + for (j = 0 ; j < refnum; ++j) + { + for (k=0 ; k<3 ; k++) + { + // scale to byte values & min/max check + v = Q_rint ( (in->references[j].placement.origin[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); + + // write out origin as a float since they arn't clamped + temp = sizeof(float); // change this to a short + assert(size+temp < DATA_SIZE); + memcpy(data2 + size, &v, temp); + size += temp; + } + + for (k=0 ; k<3 ; k++) + { + v = Q_rint ( (in->references[j].placement.direction[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); + + // write out origin as a float since they arn't clamped + temp = sizeof(float); // change this to a short + assert(size+temp < DATA_SIZE); + memcpy(data2 + size, &v, temp); + size += temp; + } + + for (k=0 ; k<3 ; k++) + { + v = Q_rint ( (in->references[j].placement.up[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); + + // write out origin as a float since they arn't clamped + temp = sizeof(float); // change this to a short + assert(size+temp < DATA_SIZE); + memcpy(data2 + size, &v, temp); + size += temp; + } + } + } + } + else // FINISH ME: references need to be stored with regular verts for compressed models + { + framesWritten = false; + + temp = sizeof(int); // change this to a byte + memcpy(data2 + size, &framesWritten, temp); + size += temp; + } + + WriteHeader(modelouthandle, FM_REFERENCES_NAME, FM_REFERENCES_VER, size, data2); + } +} + +static void CompressFrames() +{ + fmgroup_t *g; + int i,j,k; + fmframe_t *in; + + j=0; + for (i=0;i=groups[j].start_frame+groups[j].num_frames&&jnum_frames,fmheader.num_xyz,g->degrees); + for (i=0;inum_frames;i++) + { + in = &g_frames[i+g->start_frame]; + for (j=0;jv[j].v[0],in->v[j].v[1],in->v[j].v[2]); + } + AnimCompressDoit(); + g->mat= (char *) SafeMalloc(fmheader.num_xyz*3*g->degrees*sizeof(char), "CompressFrames"); + g->ccomp=(char *) SafeMalloc(g->num_frames*g->degrees*sizeof(char), "CompressFrames"); + g->cbase=(char *) SafeMalloc(fmheader.num_xyz*3*sizeof(unsigned char), "CompressFrames"); + g->cscale=(float *) SafeMalloc(g->degrees*sizeof(float), "CompressFrames"); + g->coffset=(float *) SafeMalloc(g->degrees*sizeof(float), "CompressFrames"); + AnimCompressToBytes(g->trans,g->scale,g->mat,g->ccomp,g->cbase,g->cscale,g->coffset,g->bmin,g->bmax); + AnimCompressEnd(); + } +} + +static void OptimizeVertices(void) +{ + qboolean vert_used[MAX_FM_VERTS]; + short vert_replacement[MAX_FM_VERTS]; + int i,j,k,l,pos,bit,set_pos,set_bit; + fmframe_t *in; + qboolean Found; + int num_unique; + static IntListNode_t *newVertLists[NUM_CLUSTERS]; + static int newNum_verts[NUM_CLUSTERS]; + IntListNode_t *current, *next; + + printf("Optimizing vertices..."); + + memset(vert_used, 0, sizeof(vert_used)); + + if(g_skelModel.clustered == true) + { + memset(newNum_verts, 0, sizeof(newNum_verts)); + memset(newVertLists, 0, sizeof(newVertLists)); + } + + num_unique = 0; + + // search for common points among all the frames + for (i=0 ; iv[j].v[0] == in->v[k].v[0] && + in->v[j].v[1] == in->v[k].v[1] && + in->v[j].v[2] == in->v[k].v[2]) + { + Found = true; + vert_replacement[j] = k; + break; + } + + } + + if (!Found) + { + if (!vert_used[j]) + { + num_unique++; + } + vert_used[j] = true; + } + } + } + + // recompute the light normals + for (i=0 ; iv[j].vnorm.normalsum, in->v[k].vnorm.normalsum, in->v[k].vnorm.normalsum); + in->v[k].vnorm.numnormals += in->v[j].vnorm.numnormals++; + } + } + + for (j=0 ; jv[j].vnorm.numnormals; + if (!c) + Error ("Vertex with no triangles attached"); + + VectorScale (in->v[j].vnorm.normalsum, 1.0/c, v); + VectorNormalize (v, v); + + maxdot = -999999.0; + maxdotindex = -1; + + for (k=0 ; k maxdot) + { + maxdot = dot; + maxdotindex = k; + } + } + + in->v[j].lightnormalindex = maxdotindex; + } + } + + // create substitution list + num_unique = 0; + for(i=0;inext) + { + if(current->data == i) + { + IntListNode_t *current2; + int m; + qboolean added = false; + + for(m = 0, current2 = newVertLists[k]; m < newNum_verts[k+1]; + ++m, current2 = current2->next) + { + if(current2->data == vert_replacement[i]) + { + added = true; + break; + } + } + + if(!added) + { + ++newNum_verts[k+1]; + + next = newVertLists[k]; + + newVertLists[k] = (IntListNode_t *) SafeMalloc(sizeof(IntListNode_t), "OptimizeVertices"); + // freed after model write out + + newVertLists[k]->data = vert_replacement[i]; + newVertLists[k]->next = next; + } + break; + } + } + } + } + } + + // substitute + for (i=0 ; iv[vert_replacement[j]] = in->v[j]; + } + + } + + for(i = 0; i < numJointsInSkeleton[g_skelModel.type]; ++i) + { + IntListNode_t *toFree; + current = g_skelModel.vertLists[i]; + + while(current) + { + toFree = current; + current = current->next; + free(toFree); // freeing of memory allocated in ReplaceClusterIndex called in Cmd_Base + } + + g_skelModel.vertLists[i] = newVertLists[i]; + g_skelModel.new_num_verts[i+1] = newNum_verts[i+1]; + } + +#ifndef NDEBUG + for(k = 0; k < numJointsInSkeleton[g_skelModel.type]; ++k) + { + for(l = 0, current = g_skelModel.vertLists[k]; + l < g_skelModel.new_num_verts[k+1]; ++l, current = current->next) + { + IntListNode_t *current2; + int m; + + for(m = l+1, current2 = current->next; m < newNum_verts[k+1]; + ++m, current2 = current2->next) + { + if(current->data == current2->data) + { + printf("Warning duplicate vertex: %d\n", current->data); + break; + } + } + } + } +#endif + + for(i=0;i> 3; + bit = 1 << (i & 7 ); + + for (j=0 ; j<3 ; j++) + { + set_bit = set_pos = triangles[i].index_xyz[j] = vert_replacement[triangles[i].index_xyz[j]]; + + set_pos >>= 3; + set_bit = 1 << (set_bit & 7); + + for(k=0;kreferences[j].placement.origin, in->v[index].v); + index++; + + VectorCopy(in->references[j].placement.direction, in->v[index].v); + index++; + + VectorCopy(in->references[j].placement.up, in->v[index].v); + index++; + } + } + + fmheader.num_xyz += refnum*3; + } + + // tack on the skeletal joint verts to the regular verts + if(g_skelModel.type != SKEL_NULL) + { + fmframe_t *in; + int index; + + for (i = 0; i < fmheader.num_frames; ++i) + { + in = &g_frames[i]; + index = fmheader.num_xyz; + + for (j = 0 ; j < numJointsInSkeleton[g_skelModel.type]; ++j) + { + VectorCopy(in->joints[j].placement.origin, in->v[index].v); + index++; + + VectorCopy(in->joints[j].placement.direction, in->v[index].v); + index++; + + VectorCopy(in->joints[j].placement.up, in->v[index].v); + index++; + } + } + + fmheader.num_xyz += numJointsInSkeleton[g_skelModel.type]*3; + } + + CompressFrames(); + } +} + + +/* +=============== +FinishModel +=============== +*/ +void FMFinishModel (void) +{ + FILE *modelouthandle; + int i,j,length,tris,verts,bit,pos,total_tris,total_verts; + char name[1024]; + int trans_count; + + if (!fmheader.num_frames) + return; + +// +// copy to release directory tree if doing a release build +// + if (g_release) + { + if (modelname[0]) + sprintf (name, "%s", modelname); + else + sprintf (name, "%s/tris.fm", cdpartial); + ReleaseFile (name); + + for (i=0 ; i> 3; + bit = 1 << ((j) & 7 ); + if (pmnodes[i].tris[pos] & bit) + { + tris++; + } + } + for(j=0;j> 3; + bit = 1 << ((j) & 7 ); + if (pmnodes[i].verts[pos] & bit) + { + verts++; + } + } + + printf("%-33s %4d %5d\n",pmnodes[i].name,tris,verts); + + total_tris += tris; + total_verts += verts; + } + printf("--------------------------------- ---- -----\n"); + printf("%-33s %4d %5d\n","TOTALS",total_tris,total_verts); + } + } + fclose (modelouthandle); + + // finish writing header file + H_printf("\n"); + + // scale_up is usefull to allow step distances to be adjusted + H_printf("#define MODEL_SCALE\t\t%f\n", scale_up); + + // mesh nodes + if (fmheader.num_mesh_nodes) + { + H_printf("\n"); + H_printf("#define NUM_MESH_NODES\t\t%d\n\n",fmheader.num_mesh_nodes); + for(i=0;iindex_xyz[(startv)%3]; + strip_xyz[1] = last->index_xyz[(startv+1)%3]; + strip_xyz[2] = last->index_xyz[(startv+2)%3]; + strip_st[0] = last->index_st[(startv)%3]; + strip_st[1] = last->index_st[(startv+1)%3]; + strip_st[2] = last->index_st[(startv+2)%3]; + + strip_tris[0] = starttri; + stripcount = 1; + + m1 = last->index_xyz[(startv+2)%3]; + st1 = last->index_st[(startv+2)%3]; + m2 = last->index_xyz[(startv+1)%3]; + st2 = last->index_st[(startv+1)%3]; + + // look for a matching triangle +nexttri: + for (j=starttri+1, check=&triangles[starttri+1] + ; j> 3; + bit = 1 << (j & 7 ); + if (!(pmnodes[node].tris[pos] & bit)) + { + continue; + } + for (k=0 ; k<3 ; k++) + { + if (check->index_xyz[k] != m1) + continue; + if (check->index_st[k] != st1) + continue; + if (check->index_xyz[ (k+1)%3 ] != m2) + continue; + if (check->index_st[ (k+1)%3 ] != st2) + continue; + + // this is the next part of the fan + + // if we can't use this triangle, this tristrip is done + if (used[j] || translucent[j] != translucent[starttri]) + goto done; + + // the new edge + if (stripcount & 1) + { + m2 = check->index_xyz[ (k+2)%3 ]; + st2 = check->index_st[ (k+2)%3 ]; + } + else + { + m1 = check->index_xyz[ (k+2)%3 ]; + st1 = check->index_st[ (k+2)%3 ]; + } + + strip_xyz[stripcount+2] = check->index_xyz[ (k+2)%3 ]; + strip_st[stripcount+2] = check->index_st[ (k+2)%3 ]; + strip_tris[stripcount] = j; + stripcount++; + + used[j] = 2; + goto nexttri; + } + } +done: + + // clear the temp used flags + for (j=starttri+1 ; jindex_xyz[(startv)%3]; + strip_xyz[1] = last->index_xyz[(startv+1)%3]; + strip_xyz[2] = last->index_xyz[(startv+2)%3]; + strip_st[0] = last->index_st[(startv)%3]; + strip_st[1] = last->index_st[(startv+1)%3]; + strip_st[2] = last->index_st[(startv+2)%3]; + + strip_tris[0] = starttri; + stripcount = 1; + + m1 = last->index_xyz[(startv+0)%3]; + st1 = last->index_st[(startv+0)%3]; + m2 = last->index_xyz[(startv+2)%3]; + st2 = last->index_st[(startv+2)%3]; + + + // look for a matching triangle +nexttri: + for (j=starttri+1, check=&triangles[starttri+1] + ; j> 3; + bit = 1 << (j & 7 ); + if (!(pmnodes[node].tris[pos] & bit)) + { + continue; + } + for (k=0 ; k<3 ; k++) + { + if (check->index_xyz[k] != m1) + continue; + if (check->index_st[k] != st1) + continue; + if (check->index_xyz[ (k+1)%3 ] != m2) + continue; + if (check->index_st[ (k+1)%3 ] != st2) + continue; + + // this is the next part of the fan + + // if we can't use this triangle, this tristrip is done + if (used[j] || translucent[j] != translucent[starttri]) + goto done; + + // the new edge + m2 = check->index_xyz[ (k+2)%3 ]; + st2 = check->index_st[ (k+2)%3 ]; + + strip_xyz[stripcount+2] = m2; + strip_st[stripcount+2] = st2; + strip_tris[stripcount] = j; + stripcount++; + + used[j] = 2; + goto nexttri; + } + } +done: + + // clear the temp used flags + for (j=starttri+1 ; j> 3; + bit = 1 << (i & 7 ); + if (!(pmnodes[l].tris[pos] & bit)) + { + continue; + } + + // pick an unused triangle and start the trifan + if (used[i] || trans_check != translucent[i]) + { + continue; + } + + bestlen = 0; + for (type = 0 ; type < 2 ; type++) + // type = 1; + { + for (startv =0 ; startv < 3 ; startv++) + { + if (type == 1) + len = StripLength (i, startv, fmheader.num_tris, l); + else + len = FanLength (i, startv, fmheader.num_tris, l); + if (len > bestlen) + { + besttype = type; + bestlen = len; + for (j=0 ; j + "-------------------------", // ? + "-------------------------", // @ + "-***-*---*******---**---*", // A + "****-*---*****-*---*****-", + "-*****----*----*-----****", + "****-*---**---**---*****-", + "******----****-*----*****", + "******----****-*----*----", + "-*****----*--***---*-****", + "*---**---*******---**---*", + "-***---*----*----*---***-", + "----*----*----**---*-***-", + "-*--*-*-*--**---*-*--*--*", + "-*----*----*----*----****", + "*---***-***-*-**---**---*", + "*---***--**-*-**--***---*", + "-***-*---**---**---*-***-", + "****-*---*****-*----*----", + "-***-*---**---*-***----**", + "****-*---*****-*-*--*--**", + "-*****-----***-----*****-", + "*****--*----*----*----*--", + "*---**---**---**---******", + "*---**---**---*-*-*---*--", + "*---**---**-*-***-***---*", + "*---*-*-*---*---*-*-*---*", + "*---**---*-*-*---*----*--", + "*****---*---*---*---*****" // Z +}; + +void DrawLine(int x1, int y1, int x2, int y2) +{ + int dx, dy; + int adx, ady; + int count; + float xfrac, yfrac, xstep, ystep; + unsigned sx, sy; + float u, v; + + dx = x2 - x1; + dy = y2 - y1; + adx = abs(dx); + ady = abs(dy); + + count = adx > ady ? adx : ady; + count++; + + if(count > 300) + { + printf("Bad count\n"); + return; // don't ever hang up on bad data + } + + xfrac = x1; + yfrac = y1; + + xstep = (float)dx/count; + ystep = (float)dy/count; + + switch(LineType) + { + case LINE_NORMAL: + do + { + if(xfrac < SKINPAGE_WIDTH && yfrac < SKINPAGE_HEIGHT) + { + pic[(int)yfrac*SKINPAGE_WIDTH+(int)xfrac] = LineColor; + } + xfrac += xstep; + yfrac += ystep; + count--; + } while (count > 0); + break; + case LINE_FAT: + do + { + for (u=-0.1 ; u<=0.9 ; u+=0.999) + { + for (v=-0.1 ; v<=0.9 ; v+=0.999) + { + sx = xfrac+u; + sy = yfrac+v; + if(sx < SKINPAGE_WIDTH && sy < SKINPAGE_HEIGHT) + { + pic[sy*SKINPAGE_WIDTH+sx] = LineColor; + } + } + } + xfrac += xstep; + yfrac += ystep; + count--; + } while (count > 0); + break; + case LINE_DOTTED: + do + { + if(count&1 && xfrac < SKINPAGE_WIDTH && + yfrac < SKINPAGE_HEIGHT) + { + pic[(int)yfrac*SKINPAGE_WIDTH+(int)xfrac] = LineColor; + } + xfrac += xstep; + yfrac += ystep; + count--; + } while (count > 0); + break; + default: + Error("Unknown %d.\n", LineType); + } +} + +//========================================================================== +// +// DrawCharacter +// +//========================================================================== + +static void DrawCharacter(int x, int y, int character) +{ + int r, c; + char *def; + + character = toupper(character); + if(character < ASCII_SPACE || character > 'Z') + { + character = ASCII_SPACE; + } + character -= ASCII_SPACE; + for(def = CharDefs[character], r = 0; r < 5; r++) + { + for(c = 0; c < 5; c++) + { + pic[(y+r)*SKINPAGE_WIDTH+x+c] = *def++ == '*' ? 255 : 0; + } + } +} + +//========================================================================== +// +// DrawTextChar +// +//========================================================================== + +void DrawTextChar(int x, int y, char *text) +{ + int c; + + while((c = *text++) != '\0') + { + DrawCharacter(x, y, c); + x += 6; + } +} + + +extern void DrawScreen(float s_scale, float t_scale, float iwidth, float iheight); + +//========================================================================== +// ExtractDigit + +static int ExtractDigit(byte *pic, int x, int y) +{ + int i; + int r, c; + char digString[32]; + char *buffer; + byte backColor; + char **DigitDefs; + + backColor = pic[(SKINPAGE_HEIGHT - 1) * SKINPAGE_WIDTH]; + DigitDefs = &CharDefs['0' - ASCII_SPACE]; + + buffer = digString; + for(r = 0; r < 5; r++) + { + for(c = 0; c < 5; c++) + { + *buffer++ = (pic[(y + r) * SKINPAGE_WIDTH + x + c] == backColor) ? ' ' : '*'; + } + } + *buffer = '\0'; + for(i = 0; i < 10; i++) + { + if(strcmp(DigitDefs[i], digString) == 0) + { + return i; + } + } + + Error("Unable to extract scaling info from skin PCX."); + return 0; +} + +//========================================================================== +// ExtractNumber + +int ExtractNumber(byte *pic, int x, int y) +{ + return ExtractDigit(pic, x, y) * 100 + ExtractDigit(pic, x + 6, y) * 10 + ExtractDigit(pic, x + 12, y); +} + + + + + +/* +============ +BuildST + +Builds the triangle_st array for the base frame and +fmheader.skinwidth / fmheader.skinheight + + FIXME: allow this to be loaded from a file for + arbitrary mappings +============ +*/ +static void BuildST (triangle_t *ptri, int numtri, qboolean DrawSkin) +{ + int backface_flag; + int i, j; + int width, height, iwidth, iheight, swidth; + float basex, basey; + float scale; + vec3_t mins, maxs; + float *pbasevert; + vec3_t vtemp1, vtemp2, normal; + float s_scale, t_scale; + float scWidth; + float scHeight; + int skinwidth; + int skinheight; + + // + // find bounds of all the verts on the base frame + // + ClearBounds (mins, maxs); + backface_flag = false; + + if (ptri[0].HasUV) // if we have the uv already, we don't want to double up or scale + { + iwidth = ScaleWidth; + iheight = ScaleHeight; + + t_scale = s_scale = 1.0; + } + else + { + for (i=0 ; i 0) + { + backface_flag = true; + break; + } + } + scWidth = ScaleWidth*SCALE_ADJUST_FACTOR; + if (backface_flag) //we are doubling + scWidth /= 2; + + scHeight = ScaleHeight*SCALE_ADJUST_FACTOR; + + scale = scWidth/width; + + if(height*scale >= scHeight) + { + scale = scHeight/height; + } + + iwidth = ceil(width*scale)+4; + iheight = ceil(height*scale)+4; + + s_scale = (float)(iwidth-4) / width; + t_scale = (float)(iheight-4) / height; + t_scale = s_scale; + } + if (DrawSkin) + { + if(backface_flag) + DrawScreen(s_scale, t_scale, iwidth*2, iheight); + else + DrawScreen(s_scale, t_scale, iwidth, iheight); + } + if (backface_flag) + skinwidth=iwidth*2; + else + skinwidth=iwidth; + skinheight=iheight; + + +/* if (!g_fixedwidth) + { // old style + scale = 8; + if (width*scale >= 150) + scale = 150.0 / width; + if (height*scale >= 190) + scale = 190.0 / height; + + s_scale = t_scale = scale; + + iwidth = ceil(width*s_scale); + iheight = ceil(height*t_scale); + + iwidth += 4; + iheight += 4; + } + else + { // new style + iwidth = g_fixedwidth / 2; + iheight = g_fixedheight; + + s_scale = (float)(iwidth-4) / width; + t_scale = (float)(iheight-4) / height; + }*/ + +// +// determine which side of each triangle to map the texture to +// + basey = 2; + for (i=0 ; i 0) + { + basex = iwidth + 2; + } + else + { + basex = 2; + } + + for (j=0 ; j<3 ; j++) + { + pbasevert = ptri[i].verts[j]; + + triangle_st[i][j][0] = Q_rint((pbasevert[0] - mins[0]) * s_scale + basex); + triangle_st[i][j][1] = Q_rint((maxs[2] - pbasevert[2]) * t_scale + basey); + } + } + + if (DrawSkin) + { + DrawLine(triangle_st[i][0][0], triangle_st[i][0][1], + triangle_st[i][1][0], triangle_st[i][1][1]); + DrawLine(triangle_st[i][1][0], triangle_st[i][1][1], + triangle_st[i][2][0], triangle_st[i][2][1]); + DrawLine(triangle_st[i][2][0], triangle_st[i][2][1], + triangle_st[i][0][0], triangle_st[i][0][1]); + } + } + +// make the width a multiple of 4; some hardware requires this, and it ensures +// dword alignment for each scan + + swidth = iwidth; + if(backface_flag) + swidth *= 2; + fmheader.skinwidth = (swidth + 3) & ~3; + fmheader.skinheight = iheight; + + skin_width = iwidth; + skin_height = iheight; +} + + +static void BuildNewST (triangle_t *ptri, int numtri, qboolean DrawSkin) +{ + int i, j; + + for (i=0 ; i 1) + goto split; + d = lp2[1] - lp1[1]; + if (d < -1 || d > 1) + goto split; + + d = lp3[0] - lp2[0]; + if (d < -1 || d > 1) + goto split2; + d = lp3[1] - lp2[1]; + if (d < -1 || d > 1) + goto split2; + + d = lp1[0] - lp3[0]; + if (d < -1 || d > 1) + goto split3; + d = lp1[1] - lp3[1]; + if (d < -1 || d > 1) + { +split3: + temp = lp1; + lp1 = lp3; + lp3 = lp2; + lp2 = temp; + + goto split; + } + + return 0; // entire tri is filled + +split2: + temp = lp1; + lp1 = lp2; + lp2 = lp3; + lp3 = temp; + +split: +// split this edge + new[0] = (lp1[0] + lp2[0]) >> 1; + new[1] = (lp1[1] + lp2[1]) >> 1; + +// draw the point if splitting a leading edge + if (lp2[1] > lp1[1]) + goto nodraw; + if ((lp2[1] == lp1[1]) && (lp2[0] < lp1[0])) + goto nodraw; + + if (SetPixel) + { + assert ((new[1]*BaseWidth) + new[0] < BaseWidth*BaseHeight); + + if (BaseTrueColor) + { + BasePixels[((new[1]*BaseWidth) + new[0]) * 4] = 1; + } + else + { + BasePixels[(new[1]*BaseWidth) + new[0]] = 1; + } + } + else + { + if (TransPixels) + { + if (TransPixels[(new[1]*TransWidth) + new[0]] != 255) + return 1; + } + else if (BaseTrueColor) + { + if (BasePixels[(((new[1]*BaseWidth) + new[0]) * 4) + 3] != 255) + return 1; + } + else + { +// pixel = BasePixels[(new[1]*BaseWidth) + new[0]]; + } + } + +nodraw: +// recursively continue + if (CheckTransRecursiveTri(lp3, lp1, new)) + return 1; + + return CheckTransRecursiveTri(lp3, new, lp2); +} + +static void ReplaceClusterIndex(int newIndex, int oldindex, int **clusters, + IntListNode_t **vertLists, int *num_verts, int *new_num_verts) +{ + int i, j; + IntListNode_t *next; + + for(j = 0; j < numJointsInSkeleton[g_skelModel.type]; ++j) + { + if(!clusters[j]) + { + continue; + } + + for(i = 0; i < num_verts[j+1]; ++i) + { + if(clusters[j][i] == oldindex) + { + ++new_num_verts[j+1]; + + next = vertLists[j]; + + vertLists[j] = (IntListNode_t *) SafeMalloc(sizeof(IntListNode_t), "ReplaceClusterIndex"); + // Currently freed in WriteJointedModelFile only + + vertLists[j]->data = newIndex; + vertLists[j]->next = next; + } + } + } +} + +#define FUDGE_EPSILON 0.002 + +qboolean VectorFudgeCompare (vec3_t v1, vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (fabs(v1[i]-v2[i]) > FUDGE_EPSILON) + return false; + + return true; +} + +/* +================= +Cmd_Base +================= +*/ +void Cmd_FMBase (qboolean GetST) +{ + triangle_t *ptri, *st_tri; + int num_st_tris; + int i, j, k, l; + int x,y,z; +// int time1; + char file1[1024],file2[1024],trans_file[1024], stfile[1024], extension[256]; + vec3_t base_xyz[MAX_FM_VERTS]; + FILE *FH; + int pos,bit; + qboolean NewSkin; + + GetScriptToken (false); + + if (g_skipmodel || g_release || g_archive) + return; + + printf ("---------------------\n"); + sprintf (file1, "%s/%s.%s", cdarchive, token, trifileext); + printf ("%s ", file1); + + ExpandPathAndArchive (file1); + + // Use the input filepath for this one. + sprintf (file1, "%s/%s", cddir, token); + +// time1 = FileTime (file1); +// if (time1 == -1) +// Error ("%s doesn't exist", file1); + +// +// load the base triangles +// + if (do3ds) + Load3DSTriangleList (file1, &ptri, &fmheader.num_tris, &pmnodes, &fmheader.num_mesh_nodes); + else + LoadTriangleList (file1, &ptri, &fmheader.num_tris, &pmnodes, &fmheader.num_mesh_nodes); + + if (g_ignoreTriUV) + { + for (i=0;i> 3; + bit = 1 << (i & 7 ); + if (!(pmnodes[l].tris[pos] & bit)) + { + continue; + } + + for (j=0 ; j<3 ; j++) + { + // get the xyz index + for (k=0 ; k> 3; + bit = 1 << (k & 7); + pmnodes[l].verts[pos] |= bit; + + triangles[i].index_xyz[j] = k; + + // get the st index + for (k=0 ; k= fmheader.num_mesh_nodes) + { + Error("Node '%s' not in base list!\n", token); + } + } + + free(pmnodes); + pmnodes = newnodes; +} + +//=============================================================== + +extern char *FindFrameFile (char *frame); + + +/* +=============== +GrabFrame +=============== +*/ +void GrabFrame (char *frame) +{ + triangle_t *ptri; + int i, j; + fmtrivert_t *ptrivert; + int num_tris; + char file1[1024]; + fmframe_t *fr; + int index_xyz; + char *framefile; + + // the frame 'run1' will be looked for as either + // run.1 or run1.tri, so the new alias sequence save + // feature an be used + framefile = FindFrameFile (frame); + + sprintf (file1, "%s/%s", cdarchive, framefile); + ExpandPathAndArchive (file1); + + sprintf (file1, "%s/%s",cddir, framefile); + + printf ("grabbing %s ", file1); + + if (fmheader.num_frames >= MAX_FM_FRAMES) + Error ("fmheader.num_frames >= MAX_FM_FRAMES"); + fr = &g_frames[fmheader.num_frames]; + fmheader.num_frames++; + + strcpy (fr->name, frame); + +// +// load the frame +// + if (do3ds) + Load3DSTriangleList (file1, &ptri, &num_tris, NULL, NULL); + else + LoadTriangleList (file1, &ptri, &num_tris, NULL, NULL); + + if (num_tris != fmheader.num_tris) + Error ("%s: number of triangles (%d) doesn't match base frame (%d)\n", file1, num_tris, fmheader.num_tris); + +// +// allocate storage for the frame's vertices +// + ptrivert = fr->v; + + for (i=0 ; imins, fr->maxs); + +// +// store the frame's vertices in the same order as the base. This assumes the +// triangles and vertices in this frame are in exactly the same order as in the +// base +// + for (i=0 ; imins, fr->maxs); + + VectorAdd (ptrivert[index_xyz].vnorm.normalsum, normal, ptrivert[index_xyz].vnorm.normalsum); + ptrivert[index_xyz].vnorm.numnormals++; + } + } + +// +// calculate the vertex normals, match them to the template list, and store the +// index of the best match +// + for (i=0 ; i maxdot) + { + maxdot = dot; + maxdotindex = j; + } + } + + ptrivert[i].lightnormalindex = maxdotindex; + } + + free (ptri); +} + +/* +=============== +Cmd_Frame +=============== +*/ +void Cmd_FMFrame (void) +{ + while (ScriptTokenAvailable()) + { + GetScriptToken (false); + if (g_skipmodel) + continue; + if (g_release || g_archive) + { + fmheader.num_frames = 1; // don't skip the writeout + continue; + } + + H_printf("#define FRAME_%-16s\t%i\n", token, fmheader.num_frames); + + if((g_skelModel.type != SKEL_NULL) || (g_skelModel.references != REF_NULL)) + { + GrabModelTransform(token); + } + + GrabFrame (token); + + if(g_skelModel.type != SKEL_NULL) + { + GrabSkeletalFrame(token); + } + + if(g_skelModel.references != REF_NULL) + { + GrabReferencedFrame(token); + } + + // need to add the up and dir points to the frame bounds here + // using AddPointToBounds (ptrivert[index_xyz].v, fr->mins, fr->maxs); + // then remove fudge in determining scale on frame write out + } +} + +/* +=============== +Cmd_Skin + +Skins aren't actually stored in the file, only a reference +is saved out to the header file. +=============== +*/ +void Cmd_FMSkin (void) +{ + byte *palette; + byte *pixels; + int width, height; + byte *cropped; + int y; + char name[1024], savename[1024], transname[1024], extension[256]; + miptex32_t *qtex32; + int size; + FILE *FH; + qboolean TrueColor; + + GetScriptToken (false); + + if (fmheader.num_skins == MAX_FM_SKINS) + Error ("fmheader.num_skins == MAX_FM_SKINS"); + + if (g_skipmodel) + return; + + sprintf (name, "%s/%s", cdarchive, token); + strcpy (name, ExpandPathAndArchive( name ) ); +// sprintf (name, "%s/%s.lbm", cddir, token); + + if (ScriptTokenAvailable()) + { + GetScriptToken (false); + sprintf (g_skins[fmheader.num_skins], "!%s", token); + sprintf (savename, "%s!%s", g_outputDir, token); + sprintf (transname, "%s!%s_a.pcx", gamedir, token); + } + else + { + sprintf (g_skins[fmheader.num_skins], "%s/!%s", cdpartial, token); + sprintf (savename, "%s/!%s", g_outputDir, token); + sprintf (transname, "%s/!%s_a.pcx", cddir, token); + } + + fmheader.num_skins++; + + if (g_skipmodel || g_release || g_archive) + return; + + // load the image + printf ("loading %s\n", name); + ExtractFileExtension (name, extension); + if (extension[0] == 0) + { + strcat(name, ".pcx"); + } + + + TrueColor = LoadAnyImage (name, &pixels, &palette, &width, &height); +// RemapZero (pixels, palette, width, height); + + // crop it to the proper size + + if (!TrueColor) + { + cropped = (byte *) SafeMalloc (fmheader.skinwidth*fmheader.skinheight, "Cmd_FMSkin"); + for (y=0 ; y= 3); +// figure out the best plane projections + DOsvdPlane ((float*)vertices, num, (float *)&n, (float *)&base); + + if (DotProduct(aveNorm,n) < 0.0f) + { + VectorScale(n, -1.0f, n); + } + VectorNormalize(n,n); + if (fabs(n[2]) < .57) + { + CrossProduct( zaxis, n, crossvect); + VectorCopy(crossvect, u); + } + else + { + CrossProduct( yaxis, n, crossvect); + VectorCopy(crossvect, u); + } + VectorNormalize(u,u); + CrossProduct( n, u, crossvect); + VectorCopy(crossvect, v); + VectorNormalize(v,v); + + num = 0; + + for ( j = 0; j < 3; j++) + { + groupMin[j] = 1e30f; + groupMax[j] = -1e30f; + } + + for ( j = 0; j < numtris; j++) + { + for ( k = 0; k < 3; k++) + { + VectorCopy(grouptris[j].verts[k],v0); + VectorSubtract(v0, base, v0); + uvw[0] = DotProduct(v0, u); + uvw[1] = DotProduct(v0, v); + uvw[2] = DotProduct(v0, n); + VectorCopy(uvw,uvs[num]); + num++; + for ( l = 0; l < 3; l++) + { + if (uvw[l] < groupMin[l]) + { + groupMin[l] = uvw[l]; + } + if (uvw[l] > groupMax[l]) + { + groupMax[l] = uvw[l]; + } + } + } + } + + xwidth = ceil(0 - groupMin[0]) + 2; // move right of origin and avoid overlap + ywidth = ceil(0 - groupMin[1]) + 2; // move "above" origin + + for ( j=0; j < numverts; j++) + { + uFinal[finalcount] = uvs[j][0] + xwidth + xbase; + vFinal[finalcount] = uvs[j][1] + ywidth; + if (uFinal[finalcount] < uvwMin[0]) + { + uvwMin[0] = uFinal[finalcount]; + } + if (uFinal[finalcount] > uvwMax[0]) + { + uvwMax[0] = uFinal[finalcount]; + } + if (vFinal[finalcount] < uvwMin[1]) + { + uvwMin[1] = vFinal[finalcount]; + } + if (vFinal[finalcount] > uvwMax[1]) + { + uvwMax[1] = vFinal[finalcount]; + } + finalcount++; + } + + fprintf(grpfile,"svdPlaned Group min: ( %f , %f )\n",groupMin[0] + xwidth + xbase, groupMin[1] + ywidth); + fprintf(grpfile,"svdPlaned Group max: ( %f , %f )\n",groupMax[0] + xwidth + xbase, groupMax[1] + ywidth); + + finalcount = finalstart; + + for ( count = 0; count < numverts; count++) + { + fprintf(grpfile,"Vertex %d: ( %f , %f , %f )\n",count,vertices[count][0],vertices[count][1],vertices[count][2]); + fprintf(grpfile,"svdPlaned: ( %f , %f )\n",uFinal[finalcount],vFinal[finalcount++]); + } + + finalstart = finalcount; + + fprintf(grpfile,"\n"); + + free(vertices); + free(uvs); + free(grouptris); + + xbase += ceil(groupMax[0] - groupMin[0]) + 2; + + } + + fprintf(grpfile,"Global Min ( %f , %f )\n",uvwMin[0],uvwMin[1]); + fprintf(grpfile,"Global Max ( %f , %f )\n",uvwMax[0],uvwMax[1]); + + + ScaleTris(uvwMin, uvwMax, width, height, uFinal, vFinal, finalcount); + + for (k = 0; k < finalcount; k++) + { + fprintf(grpfile, "scaled vertex %d: ( %f , %f )\n",k,uFinal[k],vFinal[k]); + } + + // i've got the array of vertices in uFinal and vFinal. Now I need to write them and draw lines + + datasize = width * height*sizeof(unsigned char); + newpic = (unsigned char*)SafeMalloc(datasize, "NewGen"); + memset(newpic,0,datasize); + memset(pic_palette,0,sizeof(pic_palette)); + pic_palette[767] = pic_palette[766] = pic_palette[765] = 255; + + k = 0; + while (k < finalcount) + { + NewDrawLine(uFinal[k], vFinal[k], uFinal[k+1], vFinal[k+1], newpic, width, height); + k++; + NewDrawLine(uFinal[k], vFinal[k], uFinal[k+1], vFinal[k+1], newpic, width, height); + k++; + NewDrawLine(uFinal[k], vFinal[k], uFinal[k-2], vFinal[k-2], newpic, width, height); + k++; + fprintf(grpfile, "output tri with verts %d, %d, %d", k-2, k-1, k); + } + + WritePCXfile (OutputName, newpic, width, height, pic_palette); + + fclose(grpfile); + + free(todo); + free(done); + free(triangles); + free(newpic); + return; +} +void NewDrawLine(int x1, int y1, int x2, int y2, unsigned char* picture, int width, int height) +{ + long dx, dy; + long adx, ady; + long count; + float xfrac, yfrac, xstep, ystep; + unsigned long sx, sy; + float u, v; + + dx = x2 - x1; + dy = y2 - y1; + adx = abs(dx); + ady = abs(dy); + + count = adx > ady ? adx : ady; + count++; + + if(count > 300) + { + printf("Bad count\n"); + return; // don't ever hang up on bad data + } + + xfrac = x1; + yfrac = y1; + + xstep = (float)dx/count; + ystep = (float)dy/count; + + switch(LineType) + { + case LINE_NORMAL: + do + { + if(xfrac < width && yfrac < height) + { + picture[(long)yfrac*width+(long)xfrac] = LineColor; + } + xfrac += xstep; + yfrac += ystep; + count--; + } while (count > 0); + break; + case LINE_FAT: + do + { + for (u=-0.1 ; u<=0.9 ; u+=0.999) + { + for (v=-0.1 ; v<=0.9 ; v+=0.999) + { + sx = xfrac+u; + sy = yfrac+v; + if(sx < width && sy < height) + { + picture[sy*width+sx] = LineColor; + } + } + } + xfrac += xstep; + yfrac += ystep; + count--; + } while (count > 0); + break; + case LINE_DOTTED: + do + { + if(count&1 && xfrac < width && + yfrac < height) + { + picture[(long)yfrac*width+(long)xfrac] = LineColor; + } + xfrac += xstep; + yfrac += ystep; + count--; + } while (count > 0); + break; + default: + Error("Unknown %d.\n", LineType); + } +} +*/ +void ScaleTris( vec3_t min, vec3_t max, int Width, int Height, float* u, float* v, int verts) +{ + + int i; + float hscale, vscale; + float scale; + + hscale = max[0]; + vscale = max[1]; + + hscale = (Width-2) / max[0]; + vscale = (Height-2) / max[1]; + + scale = hscale; + if (scale > vscale) + { + scale = vscale; + } + for ( i = 0; i 32) + { + Error ("Degrees of freedom out of range: %d",groups[num_groups].degrees); + } +} + +void Cmd_FMEndGroup (void) +{ + groups[num_groups].num_frames = fmheader.num_frames - groups[num_groups].start_frame; + + if(num_groups < MAX_GROUPS - 1) + { + num_groups++; + } + else + { + Error("Number of compression groups exceded: %i\n", MAX_GROUPS); + } +} + diff --git a/tools/quake2/qdata_heretic2/icon1.ico b/tools/quake2/qdata_heretic2/icon1.ico new file mode 100644 index 0000000000000000000000000000000000000000..8f44194128ade27ac79a97e16a0100032e36fc90 GIT binary patch literal 766 zcmeHFI|{-;6nqgu(pd?@f+;P%fgWvd5Zi#5t3)isDpyd_cmxB1aoz^9j}XKIIGcHI zCi6BwB9d@r*-{j*naGNLOJ?MOOk~a~;+ajuGi2g@-&iT7sw#F35Do!Ukz9I8%OBsr zKw1#TI73}TCn-&8V%1}BH-yYwVC=)J$<^yU2GH2Y(bUGn+>e6)z$7~7E-l|Y@KoH} k^ZKc}bGs|b7CqzE#{#(RfxKj-tRrjcItQ8!@aBxg2Lb$&{Qv*} literal 0 HcmV?d00001 diff --git a/tools/quake2/qdata_heretic2/images.c b/tools/quake2/qdata_heretic2/images.c new file mode 100644 index 00000000..49f5cd27 --- /dev/null +++ b/tools/quake2/qdata_heretic2/images.c @@ -0,0 +1,1397 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qdata.h" + +#ifdef _WIN32 + #include +#endif + +#include + +#if 1 + extern char *g_outputDir; +#endif // _QDATA + +char mip_prefix[1024]; // directory to dump the textures in + +qboolean colormap_issued; +byte colormap_palette[768]; + +unsigned total_x = 0; +unsigned total_y = 0; +unsigned total_textures = 0; + +#define MAX_IMAGE_SIZE 512 + +#if 0 +/* +============== +RemapZero + +Replaces all 0 bytes in an image with the closest palette entry. +This is because NT won't let us change index 0, so any palette +animation leaves those pixels untouched. +============== +*/ +void RemapZero (byte *pixels, byte *palette, int width, int height) +{ + int i, c; + int alt_zero; + int value, best; + + alt_zero = 0; + best = 9999999; + for (i=1 ; i<255 ; i++) + { + value = palette[i*3+0]+palette[i*3+1]+palette[i*3+2]; + if (value < best) + { + best = value; + alt_zero = i; + } + } + + c = width*height; + for (i=0 ; i> 16; + palette_g[i] = (palette[i] & 0x0000ff00) >> 8; + palette_b[i] = (palette[i] & 0x000000ff); + } + + for(i=0;i biggest_delta) + continue; + dg = abs(palette_g[i] - g); + if (dg > biggest_delta) + continue; + db = abs(palette_b[i] - b); + if (db > biggest_delta) + continue; + + dist = dr * dr + dg * dg + db * db; + if (dist < min_dist) + { + min_dist = dist; + min_index = i; + if (min_dist == 0) break; + + dist = dr; + if (dg > dist) dist = dg; + if (db > dist) dist = db; + if (dist < biggest_delta) + biggest_delta = dist; + } + } + + last_place++; + if (last_place >= MAX_LAST) + last_place = 0; + + last_r[last_place] = r; + last_g[last_place] = g; + last_b[last_place] = b; + last_i[last_place] = min_index; + + return min_index; +} + + +void GL_ResampleTexture8P (byte *in, int inwidth, int inheight, byte *out, + int outwidth, int outheight, palette_t *palette) +{ + int i, j; + byte *inrow, *inrow2; + unsigned frac, fracstep; + unsigned p1[1024], p2[1024], *p1p, *p2p; + palette_t *c1,*c2,*c3,*c4; + unsigned r,g,b; + + fracstep = inwidth*0x10000/outwidth; + + frac = fracstep>>2; + for (i=0 ; i>16; + frac += fracstep; + } + frac = 3*(fracstep>>2); + for (i=0 ; i>16; + frac += fracstep; + } + + cached = 0; + + for (i=0 ; ir + (unsigned)c2->r + (unsigned)c3->r + (unsigned)c4->r)>>2; + g = ((unsigned)c1->g + (unsigned)c2->g + (unsigned)c3->g + (unsigned)c4->g)>>2; + b = ((unsigned)c1->b + (unsigned)c2->b + (unsigned)c3->b + (unsigned)c4->b)>>2; + + *out++ = ConvertTrueColorToPal(r,g,b); + } + } +} + +void GL_MipMap8P(byte *out, byte *in, int width, int height, palette_t *palette) +{ + int i, j; + palette_t *c1,*c2,*c3,*c4; + unsigned r,g,b; + + cached = 0; + memset(out, 0, 256 * 256); + width <<= 1; + height <<= 1; + + for (i = 0; i < height; i += 2, in += width) + { + for (j = 0; j < width; j += 2) + { + c1 = &palette[in[0]]; + c3 = &palette[in[width]]; + in++; + c2 = &palette[in[0]]; + c4 = &palette[in[width]]; + in++; + + r = ((unsigned)c1->r + (unsigned)c2->r + (unsigned)c3->r + (unsigned)c4->r) >> 2; + g = ((unsigned)c1->g + (unsigned)c2->g + (unsigned)c3->g + (unsigned)c4->g) >> 2; + b = ((unsigned)c1->b + (unsigned)c2->b + (unsigned)c3->b + (unsigned)c4->b) >> 2; + + *out++ = ConvertTrueColorToPal(r, g, b); + } + } +} + + +miptex_t *CreateMip(byte *data, unsigned width, unsigned height, byte *palette, int *FinalSize, qboolean mip) +{ + int scaled_width, scaled_height; + int i,j,r,g,b; + byte intensitytable[256]; + byte scaled[256*256]; + byte out[256*256]; + int miplevel; + miptex_t *mp; + byte *pos; + int size; + + for (i=0 ; i<256 ; i++) + { + j = i * intensity_value; + if (j > 255) + j = 255; + intensitytable[i] = j; + } + + for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1) + ; + if (1 && scaled_width > width && 1) + scaled_width >>= 1; + for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1) + ; + if (1 && scaled_height > height && 1) + scaled_height >>= 1; + + // don't ever bother with >256 textures + if (scaled_width > 256) + scaled_width = 256; + if (scaled_height > 256) + scaled_height = 256; + + if (scaled_width < 1) + scaled_width = 1; + if (scaled_height < 1) + scaled_height = 1; + + size = sizeof(*mp) + (scaled_width*scaled_height*3); + mp = (miptex_t *)SafeMalloc(size, "CreateMip"); + memset(mp,0,size); + + mp->version = MIP_VERSION; + + for(i=j=0;i<256;i++,j+=3) + { + mp->palette[i].r = r = intensitytable[palette[j]]; + mp->palette[i].g = g = intensitytable[palette[j+1]]; + mp->palette[i].b = b = intensitytable[palette[j+2]]; + image_pal[i] = 0xff000000 | (r<<16) | (g<<8) | (b); + } + + PrepareConvert(image_pal); + + if (scaled_width == width && scaled_height == height) + { + memcpy (scaled, data, width*height); + } + else + GL_ResampleTexture8P (data, width, height, scaled, scaled_width, scaled_height, mp->palette); + + pos = (byte *)(mp + 1); + miplevel = 0; + + while ((scaled_width >= 1 || scaled_height >= 1) && (miplevel <= MIPLEVELS-1) && (!miplevel || mip)) + { + if (scaled_width < 1) + scaled_width = 1; + if (scaled_height < 1) + scaled_height = 1; + + if(miplevel > 0) + GL_MipMap8P(out, (byte *)scaled, scaled_width, scaled_height, mp->palette); + else + memcpy(out, scaled, 256 * 256); + + mp->width[miplevel] = scaled_width; + mp->height[miplevel] = scaled_height; + mp->offsets[miplevel] = pos - ((byte *)(mp)); + memcpy(pos, out, scaled_width * scaled_height); + memcpy(scaled, out, 256 * 256); + pos += scaled_width * scaled_height; + + scaled_width >>= 1; + scaled_height >>= 1; + + miplevel++; + } + + *FinalSize = pos - ((byte *)(mp)); + + return mp; +} + + +void GL_ResampleTexture (unsigned *in, int inwidth, int inheight, unsigned *out, int outwidth, int outheight) +{ + int i, j; + unsigned *inrow, *inrow2; + unsigned frac, fracstep; + unsigned p1[1024], p2[1024]; + byte *pix1, *pix2, *pix3, *pix4; + + fracstep = inwidth*0x10000/outwidth; + + frac = fracstep>>2; + for (i=0 ; i>16); + frac += fracstep; + } + frac = 3*(fracstep>>2); + for (i=0 ; i>16); + frac += fracstep; + } + + for (i=0 ; i> 1; + for (j=0 ; j>2; + ((byte *)(out+j))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2; + ((byte *)(out+j))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2; + ((byte *)(out+j))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2; + } + } +} + +void GL_MipMap (byte *out, byte *in, int width, int height) +{ + int i, j; + + width <<=3; + height <<= 1; + for (i=0 ; i>2; + out[1] = (in[1] + in[5] + in[width+1] + in[width+5])>>2; + out[2] = (in[2] + in[6] + in[width+2] + in[width+6])>>2; + out[3] = (in[3] + in[7] + in[width+3] + in[width+7])>>2; + } + } +} + +miptex32_t *CreateMip32(unsigned *data, unsigned width, unsigned height, int *FinalSize, qboolean mip) +{ + int scaled_width, scaled_height; + unsigned scaled[MAX_IMAGE_SIZE*MAX_IMAGE_SIZE]; + unsigned out[MAX_IMAGE_SIZE*MAX_IMAGE_SIZE]; + int miplevel; + miptex32_t *mp; + byte *pos; + int size; + paletteRGBA_t *test; + + for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1) + ; + if (1 && scaled_width > width && 1) + scaled_width >>= 1; + for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1) + ; + if (1 && scaled_height > height && 1) + scaled_height >>= 1; + + // don't ever bother with >256 textures + if (scaled_width > MAX_IMAGE_SIZE) + scaled_width = MAX_IMAGE_SIZE; + if (scaled_height > MAX_IMAGE_SIZE) + scaled_height = MAX_IMAGE_SIZE; + + if (scaled_width < 1) + scaled_width = 1; + if (scaled_height < 1) + scaled_height = 1; + + size = sizeof(*mp) + (scaled_width*scaled_height*3*4); + mp = (miptex32_t *)SafeMalloc(size, "CreateMip"); + memset(mp,0,size); + + mp->version = MIP32_VERSION; + + size = width*height; + test = (paletteRGBA_t *)data; + while(size) + { + if (test->a != 255) + { + mp->flags |= LittleLong(SURF_ALPHA_TEXTURE); + break; + } + + size--; + test++; + } + + if (scaled_width == width && scaled_height == height) + { + memcpy (scaled, data, width*height*4); + } + else + GL_ResampleTexture (data, width, height, scaled, scaled_width, scaled_height); + + pos = (byte *)(mp + 1); + miplevel = 0; + + while ((scaled_width >= 1 || scaled_height >= 1) && (miplevel <= MIPLEVELS-1) && (!miplevel || mip)) + { + if (scaled_width < 1) + scaled_width = 1; + if (scaled_height < 1) + scaled_height = 1; + + if (miplevel > 0) + { + GL_MipMap((byte *)out, (byte *)scaled, scaled_width, scaled_height); + } + else + { + memcpy(out, scaled, MAX_IMAGE_SIZE * MAX_IMAGE_SIZE * 4); + } + + mp->width[miplevel] = scaled_width; + mp->height[miplevel] = scaled_height; + mp->offsets[miplevel] = pos - ((byte *)(mp)); + memcpy(pos, out, scaled_width * scaled_height * 4); + memcpy(scaled, out, MAX_IMAGE_SIZE * MAX_IMAGE_SIZE * 4); + pos += scaled_width * scaled_height * 4; + + scaled_width >>= 1; + scaled_height >>= 1; + + miplevel++; + } + + *FinalSize = pos - ((byte *)(mp)); + + return mp; +} + +/* +============== +Cmd_Grab + +$grab filename x y width height +============== +*/ +void Cmd_Grab (void) +{ + int xl,yl,w,h,y; + byte *cropped; + char savename[1024]; + char dest[1024]; + + GetScriptToken (false); + + if (token[0] == '/' || token[0] == '\\') + sprintf (savename, "%s%s.pcx", gamedir, token+1); + else + sprintf (savename, "%spics/%s.pcx", gamedir, token); + + if (g_release) + { + if (token[0] == '/' || token[0] == '\\') + sprintf (dest, "%s.pcx", token+1); + else + sprintf (dest, "pics/%s.pcx", token); + + ReleaseFile (dest); + return; + } + + GetScriptToken (false); + xl = atoi (token); + GetScriptToken (false); + yl = atoi (token); + GetScriptToken (false); + w = atoi (token); + GetScriptToken (false); + h = atoi (token); + + if (xl<0 || yl<0 || w<0 || h<0 || xl+w>byteimagewidth || yl+h>byteimageheight) + Error ("GrabPic: Bad size: %i, %i, %i, %i",xl,yl,w,h); + + // crop it to the proper size + cropped = (byte *) SafeMalloc (w*h, "Cmd_Grab"); + for (y=0 ; ybyteimagewidth || yl+h>byteimageheight) + Error ("GrabPic: Bad size: %i, %i, %i, %i",xl,yl,w,h); + + // crop it to the proper size + cropped = (byte *) SafeMalloc (w*h, "Cmd_Raw"); + for (y=0 ; y 255) + r = 255; + if (r < 0) + r = 0; + if (g > 255) + g = 255; + if (g < 0) + g = 0; + if (b > 255) + b = 255; + if (b < 0) + b = 0; +#ifndef TABLECOLORS + bestcolor = BestColor (r, g, b, 0, 254); +#else + bestcolor = palmap[r>>3][g>>3][b>>3]; +#endif + + return bestcolor; +} + + +void BuildPalmap (void) +{ +#ifdef TABLECOLORS + int r, g, b; + int bestcolor; + + if (palmap_built) + return; + palmap_built = true; + + for (r=4 ; r<256 ; r+=8) + { + for (g=4 ; g<256 ; g+=8) + { + for (b=4 ; b<256 ; b+=8) + { + bestcolor = BestColor (r, g, b, 1, 254); + palmap[r>>3][g>>3][b>>3] = bestcolor; + } + } + } +#endif + + if (!colormap_issued) + Error ("You must issue a $colormap command first"); + +} + +/* +============= +AveragePixels +============= +*/ +byte AveragePixels (int count) +{ + int r,g,b; + int i; + int vis; + int pix; + int bestcolor; + byte *pal; + int fullbright; + + vis = 0; + r = g = b = 0; + fullbright = 0; + for (i=0 ; i +must be multiples of sixteen +SURF_WINDOW +============== +*/ + +void Cmd_Mip (void) +{ + int xl,yl,xh,yh,w,h; + byte *dest, *source; + int flags, value, contents; + mipparm_t *mp; + char lumpname[128]; + char altname[128]; + char animname[128]; + char damagename[128]; + byte buffer[MAX_IMAGE_SIZE*MAX_IMAGE_SIZE]; + unsigned bufferl[MAX_IMAGE_SIZE*MAX_IMAGE_SIZE]; + materialtype_t *mat; + char filename[1024]; + unsigned *destl, *sourcel; + int linedelta, x, y; + int size; + miptex_t *qtex; + miptex32_t *qtex32; + float scale_x, scale_y; + int mip_scale; + // detail texturing + char dt_name[128]; + float dt_scale_x, dt_scale_y; + float dt_u, dt_v; + float dt_alpha; + int dt_src_blend_mode, dt_dst_blend_mode; + int flags2; + + + GetScriptToken (false); + strcpy (lumpname, token); + + GetScriptToken (false); + xl = atoi (token); + GetScriptToken (false); + yl = atoi (token); + GetScriptToken (false); + w = atoi (token); + GetScriptToken (false); + h = atoi (token); + + total_x += w; + total_y += h; + total_textures++; + + if ( (w & 15) || (h & 15) ) + Error ("line %i: miptex sizes must be multiples of 16", scriptline); + + flags = 0; + flags2 = 0; + contents = 0; + value = 0; + mip_scale = 0; + + altname[0] = animname[0] = damagename[0] = 0; + + scale_x = scale_y = 0.5; + + // detail texturing + dt_name[0] = 0; + dt_scale_x = dt_scale_y = 0.0; + dt_u = dt_v = 0.0; + dt_alpha = 0.0; + dt_src_blend_mode = dt_dst_blend_mode = 0; + + // get optional flags and values + while (ScriptTokenAvailable ()) + { + GetScriptToken (false); + + for (mp=mipparms ; mp->name ; mp++) + { + if (!strcmp(mp->name, token)) + { + switch (mp->type) + { + case pt_animvalue: + GetScriptToken (false); // specify the next animation frame + strcpy (animname, token); + break; + case pt_altnamevalue: + GetScriptToken (false); // specify the alternate texture + strcpy (altname, token); + break; + case pt_damagenamevalue: + GetScriptToken (false); // specify the damage texture + strcpy (damagename, token); + break; + case pt_flags: + flags |= mp->flags; + break; + case pt_contents: + contents |= mp->flags; + break; + case pt_flagvalue: + flags |= mp->flags; + GetScriptToken (false); // specify the light value + value = atoi(token); + break; + case pt_materialvalue: + GetScriptToken(false); + for (mat=materialtypes ; mat->name ; mat++) + { + if (!strcmp(mat->name, token)) + { + // assumes SURF_MATERIAL is in top 8 bits + flags = (flags & 0x0FFFFFF) | (mat->value << 24); + break; + } + } + break; + case pt_scale: + GetScriptToken (false); // specify the x scale + scale_x = atof(token); + GetScriptToken (false); // specify the y scale + scale_y = atof(token); + break; + + case pt_mip: + mip_scale = 1; + break; + + case pt_detailer: + flags2 |= MIP32_DETAILER_FLAG2; + break; + + case pt_nomip: + flags2 |= MIP32_NOMIP_FLAG2; + break; + + case pt_detail: + GetScriptToken(false); + strcpy(dt_name, token); + GetScriptToken(false); + dt_scale_x = atof(token); + GetScriptToken(false); + dt_scale_y = atof(token); + GetScriptToken(false); + dt_u = atof(token); + GetScriptToken(false); + dt_v = atof(token); + GetScriptToken(false); + dt_alpha = atof(token); + GetScriptToken(false); + for (mp=mipparms ; mp->name ; mp++) + { + if (!strcmp(mp->name, token)) + { + if (mp->type == pt_gl) + { + dt_src_blend_mode = mp->flags; + break; + } + } + } + if (!mp->name) + { + Error ("line %i: invalid gl blend mode %s", scriptline, token); + } + GetScriptToken (false); + for (mp=mipparms ; mp->name ; mp++) + { + if (!strcmp(mp->name, token)) + { + if (mp->type == pt_gl) + { + dt_dst_blend_mode = mp->flags; + break; + } + } + } + if (!mp->name) + { + Error ("line %i: invalid gl blend mode %s", scriptline, token); + } + break; + } + break; + } + } + if (!mp->name) + Error ("line %i: unknown parm %s", scriptline, token); + } + + if (g_release) + return; // textures are only released by $maps + + xh = xl+w; + yh = yl+h; + if (xh*yh > MAX_IMAGE_SIZE*MAX_IMAGE_SIZE) + { + Error("line %i image %s: image is too big!", scriptline, lumpname); + } + + if (TrueColorImage) + { + if (xl >= longimagewidth || xh > longimagewidth || + yl >= longimageheight || yh > longimageheight) + { + Error ("line %i image %s: bad clip dimmensions (%d,%d) (%d,%d) > image (%d,%d)", scriptline, lumpname, xl,yl,w,h,longimagewidth,longimageheight); + } + + sourcel = longimage + (yl*longimagewidth) + xl; + destl = bufferl; + linedelta = (longimagewidth - w); + + for (y=yl ; yflags |= LittleLong(flags); + qtex32->flags2 |= LittleLong(flags2); + qtex32->contents = LittleLong(contents); + qtex32->value = LittleLong(value); + qtex32->scale_x = scale_x; + qtex32->scale_y = scale_y; + qtex32->mip_scale = mip_scale; + sprintf (qtex32->name, "%s/%s", mip_prefix, lumpname); + if (animname[0]) + { + sprintf (qtex32->animname, "%s/%s", mip_prefix, animname); + } + if (altname[0]) + { + sprintf (qtex32->altname, "%s/%s", mip_prefix, altname); + } + if (damagename[0]) + { + sprintf (qtex32->damagename, "%s/%s", mip_prefix, damagename); + } + if (dt_name[0] & ((flags2 & MIP32_DETAILER_FLAG2) == 0)) + { + sprintf (qtex32->dt_name, "%s/%s", mip_prefix, dt_name); + qtex32->dt_scale_x = dt_scale_x; + qtex32->dt_scale_y = dt_scale_y; + qtex32->dt_u = dt_u; + qtex32->dt_v = dt_v; + qtex32->dt_alpha = dt_alpha; + qtex32->dt_src_blend_mode = dt_src_blend_mode; + qtex32->dt_dst_blend_mode = dt_dst_blend_mode; + } + + // + // write it out + // + sprintf (filename, "%stextures/%s/%s.m32", g_outputDir, mip_prefix, lumpname); + if(qtex32->flags & (SURF_ALPHA_TEXTURE)) + printf ("writing %s with ALPHA\n", filename); + else + printf ("writing %s\n", filename); + SaveFile (filename, (byte *)qtex32, size); + + free (qtex32); + } + else + { + if (xl >= byteimagewidth || xh > byteimagewidth || + yl >= byteimageheight || yh > byteimageheight) + { + Error ("line %i: bad clip dimmensions (%d,%d) (%d,%d) > image (%d,%d)", scriptline, xl,yl,w,h,byteimagewidth,byteimageheight); + } + + source = byteimage + yl*byteimagewidth + xl; + dest = buffer; + linedelta = byteimagewidth - w; + + for (y=yl ; yflags = LittleLong(flags); + qtex->contents = LittleLong(contents); + qtex->value = LittleLong(value); + sprintf (qtex->name, "%s/%s", mip_prefix, lumpname); + if (animname[0]) + sprintf (qtex->animname, "%s/%s", mip_prefix, animname); + + // + // write it out + // + sprintf (filename, "%stextures/%s/%s.m8", g_outputDir, mip_prefix, lumpname); + printf ("writing %s\n", filename); + SaveFile (filename, (byte *)qtex, size); + + free (qtex); + } +} + +/* +=============== +Cmd_Mippal +=============== +*/ +void Cmd_Mippal (void) +{ + colormap_issued = true; + if (g_release) + return; + + memcpy (colormap_palette, lbmpalette, 768); + + BuildPalmap(); +} + + +/* +=============== +Cmd_Mipdir +=============== +*/ +void Cmd_Mipdir (void) +{ + char filename[1024]; + + GetScriptToken (false); + strcpy (mip_prefix, token); + // create the directory if needed + sprintf (filename, "%stextures", g_outputDir); + Q_mkdir (filename); + sprintf (filename, "%stextures/%s", g_outputDir, mip_prefix); + Q_mkdir (filename); +} + + +/* +============================================================================= + +ENVIRONMENT MAP GRABBING + +Creates six pcx files from tga files without any palette edge seams +also copies the tga files for GL rendering. +============================================================================= +*/ + +// 3dstudio environment map suffixes +char *suf[6] = {"rt", "ft", "lf", "bk", "up", "dn"}; + +/* +================= +Cmd_Environment +================= +*/ +void Cmd_Environment (void) +{ + char name[1024]; + int i, x, y; + byte image[256*256]; + byte *tga; + + GetScriptToken (false); + + if (g_release) + { + for (i=0 ; i<6 ; i++) + { + sprintf (name, "env/%s%s.pcx", token, suf[i]); + ReleaseFile (name); + sprintf (name, "env/%s%s.tga", token, suf[i]); + ReleaseFile (name); + } + return; + } + // get the palette + BuildPalmap (); + + sprintf (name, "%senv/", gamedir); + CreatePath (name); + + // convert the images + for (i=0 ; i<6 ; i++) + { + sprintf (name, "%senv/%s%s.tga", gamedir, token, suf[i]); + printf ("loading %s...\n", name); + LoadTGA (name, &tga, NULL, NULL); + + for (y=0 ; y<256 ; y++) + { + for (x=0 ; x<256 ; x++) + { + image[y*256+x] = FindColor (tga[(y*256+x)*4+0],tga[(y*256+x)*4+1],tga[(y*256+x)*4+2]); + } + } + free (tga); + sprintf (name, "%senv/%s%s.pcx", gamedir, token, suf[i]); + if (FileTime (name) != -1) + printf ("%s already exists, not overwriting.\n", name); + else + WritePCXfile (name, image, 256, 256, colormap_palette); + } +} + diff --git a/tools/quake2/qdata_heretic2/jointed.c b/tools/quake2/qdata_heretic2/jointed.c new file mode 100644 index 00000000..4d78a792 --- /dev/null +++ b/tools/quake2/qdata_heretic2/jointed.c @@ -0,0 +1,572 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include "token.h" +#include "joints.h" +#include "angles.h" +#include "inout.h" + +char *SKEL_ROOT_NAMES[] = +{ + "RAVEN_SPINE" +}; + +char *SKEL_NAMES[] = +{ + "RAVEN_WAIST1", + "RAVEN_WAIST2", + "RAVEN_NECK" +}; + +int NAME_OFFSETS[] = +{ + 0 +}; + +int numJointsForSkeleton[] = +{ + NUM_JOINTS_RAVEN, + NUM_JOINTS_BOX +}; + +float g_scaling[3]; +float g_rotation[3]; +float g_translation[3]; + +//========================================================================== +// +// LoadHRCClustered +// +//========================================================================== + +static void LoadHRCClustered(char *fileName, int **clusterList, int *num_verts, int skelType) +{ + int i, j; + + TK_OpenSource(fileName); + TK_FetchRequire(TK_HRCH); + TK_FetchRequire(TK_COLON); + TK_FetchRequire(TK_SOFTIMAGE); + + TK_Beyond(TK_CLUSTERS); + + while(TK_Search(TK_CLUSTER_NAME) != TK_EOF) + { + TK_Require(TK_STRING); + + for( i = 0; i < numJointsForSkeleton[skelType]; ++i) + { + if(stricmp(tk_String, SKEL_NAMES[NAME_OFFSETS[skelType]+i]) == 0) + { + i = -i + numJointsForSkeleton[skelType] - 1; + + TK_BeyondRequire(TK_NUM_CLUSTER_VERTICES, TK_INTNUMBER); + + num_verts[i+1] = tk_IntNumber; + + clusterList[i] = (int *) SafeMalloc(num_verts[i+1]*sizeof(int), "LoadHRCClustered"); + assert(clusterList[i]); + // currently this function is only called by LoadTriangleListClustered, which in turn is only + // called by Cmd_Base in qdata. This is where the only call to free for this memory is being made. + + TK_Beyond(TK_LBRACE); + + for(j = 0; j < num_verts[i+1]; ++j) + { + TK_Require(TK_INTNUMBER); + clusterList[i][j] = tk_IntNumber; + TK_Fetch(); + } + + break; + } + } + } + + num_verts[0] = numJointsForSkeleton[skelType]; +} + +static void LoadHRCGlobals(char *fileName) +{ + int i; + + TK_OpenSource(fileName); + TK_FetchRequire(TK_HRCH); + TK_FetchRequire(TK_COLON); + TK_FetchRequire(TK_SOFTIMAGE); + TK_Beyond(TK_MODEL); + + TK_Beyond(TK_SCALING); + for(i = 0; i < 3; i++) + { + TK_Require(TK_FLOATNUMBER); + g_scaling[i] = tk_FloatNumber; + TK_Fetch(); + } + + TK_Beyond(TK_ROTATION); + for(i = 0; i < 3; i++) + { + TK_Require(TK_FLOATNUMBER); + g_rotation[i] = tk_FloatNumber; + TK_Fetch(); + } + + TK_Beyond(TK_TRANSLATION); + for(i = 0; i < 3; i++) + { + TK_Require(TK_FLOATNUMBER); + g_translation[i] = tk_FloatNumber; + TK_Fetch(); + } +} + +static void ParseVec3(vec3_t in) +{ + TK_Require(TK_FLOATNUMBER); + in[1] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[2] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[0] = tk_FloatNumber; +} + +static void ParseRotation3(vec3_t in) +{ + TK_Require(TK_FLOATNUMBER); + in[1] = -tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[2] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[0] = tk_FloatNumber; +} + +static void ParseTranslation3(vec3_t in) +{ + TK_Require(TK_FLOATNUMBER); + in[1] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[2] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[0] = tk_FloatNumber; +} + +static void LoadHRCJointList(char *fileName, QDataJoint_t *jointList, int skelType) +{ +#define MAX_STACK 64 + int i, j; + vec3_t curTranslation[MAX_STACK], curRotation[MAX_STACK], curScale[MAX_STACK]; + int curCorrespondingJoint[MAX_STACK]; + int currentStack = 0, stackSize; + int baseJoint; + float cx, sx, cy, sy, cz, sz; + float rx, ry, rz; + float x2, y2, z2; + + + TK_OpenSource(fileName); + TK_FetchRequire(TK_HRCH); + TK_FetchRequire(TK_COLON); + TK_FetchRequire(TK_SOFTIMAGE); + + TK_Beyond(TK_MODEL); + TK_Beyond(TK_MODEL); + +/* while(1) + { + TK_Beyond(TK_MODEL); + TK_BeyondRequire(TK_NAME, TK_STRING); + + if(_stricmp(tk_String, SKEL_ROOT_NAMES[skelType]) == 0) + break; + }*/ + + TK_Beyond(TK_SCALING); + + ParseVec3(curScale[currentStack]); + + TK_Beyond(TK_ROTATION); + + ParseRotation3(curRotation[currentStack]); + + TK_Beyond(TK_TRANSLATION); + + ParseVec3(curTranslation[currentStack]); + + // account for global model translation + curTranslation[currentStack][1] += g_translation[0]; + curTranslation[currentStack][2] += g_translation[1]; + curTranslation[currentStack][0] += g_translation[2]; + + ++currentStack; + + for(i = 0; i < NUM_JOINTS_RAVEN; ++i) + { + while(1) + { + TK_Beyond(TK_MODEL); + +// TK_BeyondRequire(TK_NAME, TK_STRING); + +// if(_stricmp(tk_String, SKEL_NAMES[NAME_OFFSETS[skelType]+i]) == 0) + break; + + TK_Beyond(TK_SCALING); + + ParseVec3(curScale[currentStack]); + + TK_Beyond(TK_ROTATION); + + ParseRotation3(curRotation[currentStack]); + + TK_Beyond(TK_TRANSLATION); + + ParseVec3(curTranslation[currentStack]); + + curCorrespondingJoint[currentStack] = -1; + + ++currentStack; + } + + TK_Beyond(TK_SCALING); + + ParseVec3(curScale[currentStack]); + + TK_Beyond(TK_ROTATION); + + ParseRotation3(curRotation[currentStack]); + + jointList[i].rotation[1] = curRotation[currentStack][1]; + jointList[i].rotation[2] = curRotation[currentStack][2]; + jointList[i].rotation[0] = curRotation[currentStack][0]; + + TK_Beyond(TK_TRANSLATION); + + ParseVec3(curTranslation[currentStack]); + + jointList[i].placement.origin[1] = curTranslation[currentStack][1]; + jointList[i].placement.origin[2] = curTranslation[currentStack][2]; + jointList[i].placement.origin[0] = curTranslation[currentStack][0]; + + jointList[i].placement.direction[1] = 7.5; + jointList[i].placement.direction[2] = 0.0; + jointList[i].placement.direction[0] = 0.0; + + jointList[i].placement.up[1] = 0.0; + jointList[i].placement.up[2] = 7.5; + jointList[i].placement.up[0] = 0.0; + + curCorrespondingJoint[currentStack] = i; + + ++currentStack; + } + + stackSize = currentStack; + + for(i = 0; i < NUM_JOINTS_RAVEN; ++i) + { + rx = jointList[i].rotation[0]*ANGLE_TO_RAD; + ry = jointList[i].rotation[1]*ANGLE_TO_RAD; + rz = jointList[i].rotation[2]*ANGLE_TO_RAD; + + cx = cos(rx); + sx = sin(rx); + + cy = cos(ry); + sy = sin(ry); + + cz = cos(rz); + sz = sin(rz); + + // y-axis rotation for direction + x2 = jointList[i].placement.direction[0]*cy+jointList[i].placement.direction[2]*sy; + z2 = -jointList[i].placement.direction[0]*sy+jointList[i].placement.direction[2]*cy; + jointList[i].placement.direction[0] = x2; + jointList[i].placement.direction[2] = z2; + + // y-axis rotation for up + x2 = jointList[i].placement.up[0]*cy+jointList[i].placement.up[2]*sy; + z2 = -jointList[i].placement.up[0]*sy+jointList[i].placement.up[2]*cy; + jointList[i].placement.up[0] = x2; + jointList[i].placement.up[2] = z2; + + // z-axis rotation for direction + x2 = jointList[i].placement.direction[0]*cz-jointList[i].placement.direction[1]*sz; + y2 = jointList[i].placement.direction[0]*sz+jointList[i].placement.direction[1]*cz; + jointList[i].placement.direction[0] = x2; + jointList[i].placement.direction[1] = y2; + + // z-axis rotation for up + x2 = jointList[i].placement.up[0]*cz-jointList[i].placement.up[1]*sz; + y2 = jointList[i].placement.up[0]*sz+jointList[i].placement.up[1]*cz; + jointList[i].placement.up[0] = x2; + jointList[i].placement.up[1] = y2; + + // x-axis rotation for direction vector + y2 = jointList[i].placement.direction[1]*cx-jointList[i].placement.direction[2]*sx; + z2 = jointList[i].placement.direction[1]*sx+jointList[i].placement.direction[2]*cx; + jointList[i].placement.direction[1] = y2; + jointList[i].placement.direction[2] = z2; + + // x-axis rotation for up vector + y2 = jointList[i].placement.up[1]*cx-jointList[i].placement.up[2]*sx; + z2 = jointList[i].placement.up[1]*sx+jointList[i].placement.up[2]*cx; + jointList[i].placement.up[1] = y2; + jointList[i].placement.up[2] = z2; + + // translate to position in model + jointList[i].placement.direction[0] += jointList[i].placement.origin[0]; + jointList[i].placement.direction[1] += jointList[i].placement.origin[1]; + jointList[i].placement.direction[2] += jointList[i].placement.origin[2]; + + // translate to position in model + jointList[i].placement.up[0] += jointList[i].placement.origin[0]; + jointList[i].placement.up[1] += jointList[i].placement.origin[1]; + jointList[i].placement.up[2] += jointList[i].placement.origin[2]; + } + + baseJoint = NUM_JOINTS_RAVEN; + + for(i = stackSize/*NUM_JOINTS_RAVEN*/ - 1; i > 0; --i) + { + + rx = curRotation[i-1][0]*ANGLE_TO_RAD; + ry = curRotation[i-1][1]*ANGLE_TO_RAD; + rz = curRotation[i-1][2]*ANGLE_TO_RAD; + + cx = cos(rx); + sx = sin(rx); + + cy = cos(ry); + sy = sin(ry); + + cz = cos(rz); + sz = sin(rz); + + for(j = i-1; j < stackSize-1; ++j) + { + // y-axis rotation for origin + x2 = jointList[j].placement.origin[0]*cy+jointList[j].placement.origin[2]*sy; + z2 = -jointList[j].placement.origin[0]*sy+jointList[j].placement.origin[2]*cy; + jointList[j].placement.origin[0] = x2; + jointList[j].placement.origin[2] = z2; + + // y-axis rotation for direction + x2 = jointList[j].placement.direction[0]*cy+jointList[j].placement.direction[2]*sy; + z2 = -jointList[j].placement.direction[0]*sy+jointList[j].placement.direction[2]*cy; + jointList[j].placement.direction[0] = x2; + jointList[j].placement.direction[2] = z2; + + // y-axis rotation for up + x2 = jointList[j].placement.up[0]*cy+jointList[j].placement.up[2]*sy; + z2 = -jointList[j].placement.up[0]*sy+jointList[j].placement.up[2]*cy; + jointList[j].placement.up[0] = x2; + jointList[j].placement.up[2] = z2; + + // z-axis rotation for origin + x2 = jointList[j].placement.origin[0]*cz-jointList[j].placement.origin[1]*sz; + y2 = jointList[j].placement.origin[0]*sz+jointList[j].placement.origin[1]*cz; + jointList[j].placement.origin[0] = x2; + jointList[j].placement.origin[1] = y2; + + // z-axis rotation for direction + x2 = jointList[j].placement.direction[0]*cz-jointList[j].placement.direction[1]*sz; + y2 = jointList[j].placement.direction[0]*sz+jointList[j].placement.direction[1]*cz; + jointList[j].placement.direction[0] = x2; + jointList[j].placement.direction[1] = y2; + + // z-axis rotation for up + x2 = jointList[j].placement.up[0]*cz-jointList[j].placement.up[1]*sz; + y2 = jointList[j].placement.up[0]*sz+jointList[j].placement.up[1]*cz; + jointList[j].placement.up[0] = x2; + jointList[j].placement.up[1] = y2; + + // x-axis rotation for origin + y2 = jointList[j].placement.origin[1]*cx-jointList[j].placement.origin[2]*sx; + z2 = jointList[j].placement.origin[1]*sx+jointList[j].placement.origin[2]*cx; + jointList[j].placement.origin[1] = y2; + jointList[j].placement.origin[2] = z2; + + // x-axis rotation for direction vector + y2 = jointList[j].placement.direction[1]*cx-jointList[j].placement.direction[2]*sx; + z2 = jointList[j].placement.direction[1]*sx+jointList[j].placement.direction[2]*cx; + jointList[j].placement.direction[1] = y2; + jointList[j].placement.direction[2] = z2; + + // x-axis rotation for up vector + y2 = jointList[j].placement.up[1]*cx-jointList[j].placement.up[2]*sx; + z2 = jointList[j].placement.up[1]*sx+jointList[j].placement.up[2]*cx; + jointList[j].placement.up[1] = y2; + jointList[j].placement.up[2] = z2; + + if(curCorrespondingJoint[j+1] != -1) + { + // translate origin + jointList[j].placement.origin[0] += curTranslation[i-1][0]; + jointList[j].placement.origin[1] += curTranslation[i-1][1]; + jointList[j].placement.origin[2] += curTranslation[i-1][2]; + + // translate back to local coord + jointList[j].placement.direction[0] += curTranslation[i-1][0]; + jointList[j].placement.direction[1] += curTranslation[i-1][1]; + jointList[j].placement.direction[2] += curTranslation[i-1][2]; + + // translate back to local coord + jointList[j].placement.up[0] += curTranslation[i-1][0]; + jointList[j].placement.up[1] += curTranslation[i-1][1]; + jointList[j].placement.up[2] += curTranslation[i-1][2]; + } + } + } +} + +void LoadGlobals(char *fileName) +{ + FILE *file1; + int dot = '.'; + char *dotstart; + char InputFileName[256]; + + dotstart = strrchr(fileName,dot); // Does it already have an extension on the file name? + + if (!dotstart) + { + strcpy(InputFileName, fileName); + strcat(InputFileName, ".hrc"); + if((file1 = fopen(InputFileName, "rb")) != NULL) + { + fclose(file1); + + LoadHRCGlobals(InputFileName); + + printf(" - assuming .HRC\n"); + return; + } + + Error("\n Could not open file '%s':\n" + "No HRC match.\n", fileName); + } + else + { + if((file1 = fopen(fileName, "rb")) != NULL) + { + printf("\n"); + fclose(file1); + if (strcmp(dotstart,".hrc") == 0 || strcmp(dotstart,".HRC") == 0) + { + LoadHRCGlobals(fileName); + return; + } + } + + Error("Could not open file '%s':\n",fileName); + } +} + +void LoadClusters(char *fileName, int **clusterList, int *num_verts, int skelType) +{ + FILE *file1; + int dot = '.'; + char *dotstart; + char InputFileName[256]; + + dotstart = strrchr(fileName,dot); // Does it already have an extension on the file name? + + if (!dotstart) + { + strcpy(InputFileName, fileName); + strcat(InputFileName, ".hrc"); + if((file1 = fopen(InputFileName, "rb")) != NULL) + { + fclose(file1); + + LoadHRCClustered(InputFileName, clusterList, num_verts, skelType); + + printf(" - assuming .HRC\n"); + return; + } + + Error("\n Could not open file '%s':\n" + "No HRC match.\n", fileName); + } + else + { + if((file1 = fopen(fileName, "rb")) != NULL) + { + printf("\n"); + fclose(file1); + if (strcmp(dotstart,".hrc") == 0 || strcmp(dotstart,".HRC") == 0) + { + LoadHRCClustered(fileName, clusterList, num_verts, skelType); + return; + } + } + + Error("Could not open file '%s':\n",fileName); + } +} + +void LoadJointList(char *fileName, QDataJoint_t *jointList, int skelType) +{ + FILE *file1; + int dot = '.'; + char *dotstart; + char InputFileName[256]; + + dotstart = strrchr(fileName,dot); // Does it already have an extension on the file name? + + if (!dotstart) + { + strcpy(InputFileName, fileName); + strcat(InputFileName, ".hrc"); + if((file1 = fopen(InputFileName, "rb")) != NULL) + { + fclose(file1); + + LoadHRCJointList(InputFileName, jointList, skelType); + + printf(" - assuming .HRC\n"); + return; + } + + Error("\n Could not open file '%s':\n" + "No HRC.\n", fileName); + } + else + { + if((file1 = fopen(fileName, "rb")) != NULL) + { + printf("\n"); + fclose(file1); + if (strcmp(dotstart,".hrc") == 0 || strcmp(dotstart,".HRC") == 0) + { + LoadHRCJointList(fileName, jointList, skelType); + + return; + } + } + + Error("Could not open file '%s':\n",fileName); + } +} + diff --git a/tools/quake2/qdata_heretic2/jointed.h b/tools/quake2/qdata_heretic2/jointed.h new file mode 100644 index 00000000..d0736c1f --- /dev/null +++ b/tools/quake2/qdata_heretic2/jointed.h @@ -0,0 +1,35 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _JOINTED_H +#define _JOINTED_H + +#include "joints.h" + +void LoadGlobals(char *fileName); +void LoadClusters(char *fileName, int **clusterList, int *num_verts, int skel_type); +void LoadJointList(char *fileName, struct QDataJoint_s *jointList, int num_verts); + +#define NUM_CLUSTERS 8 + +#define NOT_JOINTED -1 + +#endif //_JOINTED_H diff --git a/tools/quake2/qdata_heretic2/joints.h b/tools/quake2/qdata_heretic2/joints.h new file mode 100644 index 00000000..c08dee10 --- /dev/null +++ b/tools/quake2/qdata_heretic2/joints.h @@ -0,0 +1,144 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef JOINTS_H +#define JOINTS_H + +#ifdef _HERETIC2_ +#include "angles.h" +#endif + +//typedef float vec3_t[3]; +//typedef unsigned char byte; + +#ifndef _WIN32 +#define stricmp strcasecmp +#define strcmpi strcasecmp +#endif + +typedef struct Placement_s +{ + vec3_t origin; + vec3_t direction; + vec3_t up; +} Placement_t; + +#if 1 +typedef struct QDataJoint_s +{ + Placement_t placement; + vec3_t rotation; +} QDataJoint_t; +#endif + +typedef struct ArrayedListNode_s +{ + int data; + int next; + int inUse; +} ArrayedListNode_t; + +#define ARRAYEDLISTNODE_NULL -1 + +typedef struct JointAngles_s +{ + float angles[3]; + int children; + int created; +} JointAngles_t; + +typedef struct JointAngles2_s +{ + float angles[3]; + int children; + int changed[3]; + int inUse; +} JointAngles2_t; + +#define MAX_MODELJOINTS 256 +#define MAX_MODELJOINTNODES 255 + +extern JointAngles_t jointAngles[MAX_MODELJOINTS]; +extern JointAngles2_t jointAngles2[MAX_MODELJOINTS]; + +extern ArrayedListNode_t jointAngleNodes[MAX_MODELJOINTNODES]; + +// Skeletal structures enums +enum { + SKEL_RAVEN = 0, + SKEL_BOX, + NUM_SKELETONS +}; + +// Raven Skeletal structures enums +enum { + RAVEN_WAIST1 = 0, + RAVEN_WAIST2 = 1, + RAVEN_HEAD = 2, + NUM_JOINTS_RAVEN +}; + +// Box Skeletal structures enums +enum { + BOX_CENTER = 0, + NUM_JOINTS_BOX +}; + +extern int numJointsForSkeleton[]; +extern char *RAVEN_SKEL_NAMES[]; + +#define J_NEW_SKELETON 0x00001000 +#define J_YAW_CHANGED 0x00002000 +#define J_PITCH_CHANGED 0x00004000 +#define J_ROLL_CHANGED 0x00008000 +#define MAX_JOINTS 0x00000fff +/* +inline int GetFreeNode(ArrayedListNode_t *nodeArray, int max) +{ // yeah, I know this is a sucky, inefficient way to do this, but I didn't feel like taking the time to write a real resource manager in C + int i; + + for(i = 0; i < max; ++i) + { + if(!nodeArray[i].inUse) + { + nodeArray[i].inUse = 1; + return i; + } + } + + assert(0); + return -1; +} + +inline void FreeNode(ArrayedListNode_t *nodeArray, int index) +{ + nodeArray[index].inUse = 0; +} +*/ +int CreateSkeleton(int structure); +void CreateSkeletonAtIndex(int structure, int index); +void FreeSkeleton(int structure, int index); +void SetJointAngle(int jointIndex, int angleIndex, float angle); +float ModifyJointAngle(int jointIndex, int angleIndex, float deltaAngle); +int ZeroJointAngle(int jointIndex, int angleIndex, float angVel); +int ApplyAngVelToJoint(int jointIndex, int angleIndex, float angVel, float destAng); + +#endif diff --git a/tools/quake2/qdata_heretic2/models.c b/tools/quake2/qdata_heretic2/models.c new file mode 100644 index 00000000..27145b70 --- /dev/null +++ b/tools/quake2/qdata_heretic2/models.c @@ -0,0 +1,2050 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "qdata.h" +#include +#include "jointed.h" +#include "fmodel.h" + +//================================================================= + +typedef struct +{ + int numnormals; + vec3_t normalsum; +} vertexnormals_t; + +typedef struct +{ + vec3_t v; + int lightnormalindex; +} trivert_t; + +typedef struct +{ + vec3_t mins, maxs; + char name[16]; + trivert_t v[MAX_VERTS]; + QDataJoint_t joints[NUM_CLUSTERS]; // ,this +} frame_t; + +// ,and all of this should get out of here, need to use new stuff in fmodels instead + +typedef struct IntListNode_s +{ + int data; + struct IntListNode_s *next; +} IntListNode_t; // gaak + +typedef struct +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this +} PartialAliasFrame_t; + +int jointed; +int clustered; + +int *clusters[NUM_CLUSTERS]; +IntListNode_t *vertLists[NUM_CLUSTERS]; +int num_verts[NUM_CLUSTERS + 1]; +int new_num_verts[NUM_CLUSTERS + 1]; + +// end that + +//================================================================ + +frame_t g_frames[MAX_FRAMES]; +//frame_t *g_frames; + +static dmdl_t model; + + +float scale_up; // set by $scale +vec3_t adjust; // set by $origin +int g_fixedwidth, g_fixedheight; // set by $skinsize + + +// +// base frame info +// +dstvert_t base_st[MAX_VERTS]; +dtriangle_t triangles[MAX_TRIANGLES]; + +static int triangle_st[MAX_TRIANGLES][3][2]; + +// the command list holds counts, s/t values, and xyz indexes +// that are valid for every frame +int commands[16384]; +int numcommands; +int numglverts; +int used[MAX_TRIANGLES]; + +char g_skins[MAX_MD2SKINS][64]; + +char cdarchive[1024]; +char cdpartial[1024]; +char cddir[1024]; + +char modelname[64]; // empty unless $modelname issued (players) + +extern char *g_outputDir; + +#define NUMVERTEXNORMALS 162 + +float avertexnormals[NUMVERTEXNORMALS][3] = +{ + #include "anorms.h" +}; + +unsigned char pic[SKINPAGE_HEIGHT*SKINPAGE_WIDTH], pic_palette[768]; + +FILE *headerouthandle = NULL; + +//============================================================== + +/* +=============== +ClearModel +=============== +*/ +static void ClearModel (void) +{ + memset (&model, 0, sizeof(model)); + + modelname[0] = 0; + jointed = NOT_JOINTED; + clustered = 0; + scale_up = 1.0; + VectorCopy (vec3_origin, adjust); + g_fixedwidth = g_fixedheight = 0; + g_skipmodel = false; +} + + +void H_printf(char *fmt, ...) +{ + va_list argptr; + char name[1024]; + + if (!headerouthandle) + { + sprintf (name, "%s/tris.h", cddir); + headerouthandle = SafeOpenWrite (name); + fprintf(headerouthandle, "// %s\n\n", cddir); + fprintf(headerouthandle, "// This file generated by qdata - Do NOT Modify\n\n"); + } + + va_start (argptr, fmt); + vfprintf (headerouthandle, fmt, argptr); + va_end (argptr); +} + +#if 1 +/* +============ +WriteModelFile +============ +*/ +void WriteCommonModelFile (FILE *modelouthandle, PartialAliasFrame_t *outFrames) +{ + int i; + dmdl_t modeltemp; + int j, k; + frame_t *in; + daliasframe_t *out; + byte buffer[MAX_VERTS*4+128]; + float v; + int c_on, c_off; + + model.version = ALIAS_VERSION; + model.framesize = (int)&((daliasframe_t *)0)->verts[model.num_xyz]; + model.num_glcmds = numcommands; + model.ofs_skins = sizeof(dmdl_t); + model.ofs_st = model.ofs_skins + model.num_skins * MAX_SKINNAME; + model.ofs_tris = model.ofs_st + model.num_st*sizeof(dstvert_t); + model.ofs_frames = model.ofs_tris + model.num_tris*sizeof(dtriangle_t); + model.ofs_glcmds = model.ofs_frames + model.num_frames*model.framesize; + model.ofs_end = model.ofs_glcmds + model.num_glcmds*sizeof(int); + // + // write out the model header + // + for (i=0 ; iname, in->name); + for (j=0 ; j<3 ; j++) + { + out->scale[j] = (in->maxs[j] - in->mins[j])/255; + out->translate[j] = in->mins[j]; + + if(outFrames) + { + outFrames[i].scale[j] = out->scale[j]; + outFrames[i].translate[j] = out->translate[j]; + } + } + + for (j=0 ; jverts[j].lightnormalindex = in->v[j].lightnormalindex; + + for (k=0 ; k<3 ; k++) + { + // scale to byte values & min/max check + v = Q_rint ( (in->v[j].v[k] - out->translate[k]) / out->scale[k] ); + + // clamp, so rounding doesn't wrap from 255.6 to 0 + if (v > 255.0) + v = 255.0; + if (v < 0) + v = 0; + out->verts[j].v[k] = v; + } + } + + for (j=0 ; j<3 ; j++) + { + out->scale[j] = LittleFloat (out->scale[j]); + out->translate[j] = LittleFloat (out->translate[j]); + } + + SafeWrite (modelouthandle, out, model.framesize); + } + + // + // write out glcmds + // + SafeWrite (modelouthandle, commands, numcommands*4); +} + +/* +============ +WriteModelFile +============ +*/ +void WriteModelFile (FILE *modelouthandle) +{ + model.ident = IDALIASHEADER; + + WriteCommonModelFile(modelouthandle, NULL); +} + +/* +============ +WriteJointedModelFile +============ +*/ +void WriteJointedModelFile (FILE *modelouthandle) +{ + int i; + int j, k; + frame_t *in; + float v; + IntListNode_t *current, *toFree; + PartialAliasFrame_t outFrames[MAX_FRAMES]; + + model.ident = IDJOINTEDALIASHEADER; + + WriteCommonModelFile(modelouthandle, outFrames); + + // Skeletal Type + SafeWrite(modelouthandle, &jointed, sizeof(int)); + + // number of joints + SafeWrite(modelouthandle, &numJointsForSkeleton[jointed], sizeof(int)); + + // number of verts in each cluster + SafeWrite(modelouthandle, &new_num_verts[1], sizeof(int)*numJointsForSkeleton[jointed]); + + // cluster verts + for(i = 0; i < new_num_verts[0]; ++i) + { + current = vertLists[i]; + while(current) + { + SafeWrite (modelouthandle, ¤t->data, sizeof(int)); + toFree = current; + current = current->next; + free(toFree); // freeing of memory allocated in ReplaceClusterIndex called in Cmd_Base + } + } + + for (i=0 ; ijoints[j].placement.origin[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); + + // clamp, so rounding doesn't wrap from 255.6 to 0 + if (v > 255.0) + { + v = 255.0; + } + + if (v < 0) + { + v = 0; + } + + // write out origin as a float (there's only a few per model, so it's not really + // a size issue) + SafeWrite (modelouthandle, &v, sizeof(float)); + } + + for (k=0 ; k<3 ; k++) + { + v = Q_rint ( (in->joints[j].placement.direction[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); + + // clamp, so rounding doesn't wrap from 255.6 to 0 + if (v > 255.0) + { + v = 255.0; + } + + if (v < 0) + { + v = 0; + } + + // write out origin as a float (there's only a few per model, so it's not really + // a size issue) + SafeWrite (modelouthandle, &v, sizeof(float)); + } + + for (k=0 ; k<3 ; k++) + { + v = Q_rint ( (in->joints[j].placement.up[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); + + // clamp, so rounding doesn't wrap from 255.6 to 0 + if (v > 255.0) + { + v = 255.0; + } + + if (v < 0) + { + v = 0; + } + + // write out origin as a float (there's only a few per model, so it's not really + // a size issue) + SafeWrite (modelouthandle, &v, sizeof(float)); + } + } + } +} +#else +/* +============ +WriteModelFile +============ +*/ +static void WriteModelFile (FILE *modelouthandle) +{ + int i; + dmdl_t modeltemp; + int j, k; + frame_t *in; + daliasframe_t *out; + byte buffer[MAX_VERTS*4+128]; + float v; + int c_on, c_off; + + model.ident = IDALIASHEADER; + model.version = ALIAS_VERSION; + model.framesize = (int)&((daliasframe_t *)0)->verts[model.num_xyz]; + model.num_glcmds = numcommands; + model.ofs_skins = sizeof(dmdl_t); + model.ofs_st = model.ofs_skins + model.num_skins * MAX_SKINNAME; + model.ofs_tris = model.ofs_st + model.num_st*sizeof(dstvert_t); + model.ofs_frames = model.ofs_tris + model.num_tris*sizeof(dtriangle_t); + model.ofs_glcmds = model.ofs_frames + model.num_frames*model.framesize; + model.ofs_end = model.ofs_glcmds + model.num_glcmds*4; + + // + // write out the model header + // + for (i=0 ; iname, in->name); + for (j=0 ; j<3 ; j++) + { + out->scale[j] = (in->maxs[j] - in->mins[j])/255; + out->translate[j] = in->mins[j]; + } + + for (j=0 ; jverts[j].lightnormalindex = in->v[j].lightnormalindex; + + for (k=0 ; k<3 ; k++) + { + // scale to byte values & min/max check + v = Q_rint ( (in->v[j].v[k] - out->translate[k]) / out->scale[k] ); + + // clamp, so rounding doesn't wrap from 255.6 to 0 + if (v > 255.0) + v = 255.0; + if (v < 0) + v = 0; + out->verts[j].v[k] = v; + } + } + + for (j=0 ; j<3 ; j++) + { + out->scale[j] = LittleFloat (out->scale[j]); + out->translate[j] = LittleFloat (out->translate[j]); + } + + SafeWrite (modelouthandle, out, model.framesize); + } + + // + // write out glcmds + // + SafeWrite (modelouthandle, commands, numcommands*4); +} +#endif + +/* +=============== +FinishModel +=============== +*/ +void FinishModel (void) +{ + FILE *modelouthandle; + int i; + char name[1024]; + + if (!model.num_frames) + return; + +// +// copy to release directory tree if doing a release build +// + if (g_release) + { + if (modelname[0]) + sprintf (name, "%s", modelname); + else + sprintf (name, "%s/tris.md2", cdpartial); + ReleaseFile (name); + + for (i=0 ; iindex_xyz[(startv)%3]; + strip_xyz[1] = last->index_xyz[(startv+1)%3]; + strip_xyz[2] = last->index_xyz[(startv+2)%3]; + strip_st[0] = last->index_st[(startv)%3]; + strip_st[1] = last->index_st[(startv+1)%3]; + strip_st[2] = last->index_st[(startv+2)%3]; + + strip_tris[0] = starttri; + stripcount = 1; + + m1 = last->index_xyz[(startv+2)%3]; + st1 = last->index_st[(startv+2)%3]; + m2 = last->index_xyz[(startv+1)%3]; + st2 = last->index_st[(startv+1)%3]; + + // look for a matching triangle +nexttri: + for (j=starttri+1, check=&triangles[starttri+1] + ; jindex_xyz[k] != m1) + continue; + if (check->index_st[k] != st1) + continue; + if (check->index_xyz[ (k+1)%3 ] != m2) + continue; + if (check->index_st[ (k+1)%3 ] != st2) + continue; + + // this is the next part of the fan + + // if we can't use this triangle, this tristrip is done + if (used[j]) + goto done; + + // the new edge + if (stripcount & 1) + { + m2 = check->index_xyz[ (k+2)%3 ]; + st2 = check->index_st[ (k+2)%3 ]; + } + else + { + m1 = check->index_xyz[ (k+2)%3 ]; + st1 = check->index_st[ (k+2)%3 ]; + } + + strip_xyz[stripcount+2] = check->index_xyz[ (k+2)%3 ]; + strip_st[stripcount+2] = check->index_st[ (k+2)%3 ]; + strip_tris[stripcount] = j; + stripcount++; + + used[j] = 2; + goto nexttri; + } + } +done: + + // clear the temp used flags + for (j=starttri+1 ; jindex_xyz[(startv)%3]; + strip_xyz[1] = last->index_xyz[(startv+1)%3]; + strip_xyz[2] = last->index_xyz[(startv+2)%3]; + strip_st[0] = last->index_st[(startv)%3]; + strip_st[1] = last->index_st[(startv+1)%3]; + strip_st[2] = last->index_st[(startv+2)%3]; + + strip_tris[0] = starttri; + stripcount = 1; + + m1 = last->index_xyz[(startv+0)%3]; + st1 = last->index_st[(startv+0)%3]; + m2 = last->index_xyz[(startv+2)%3]; + st2 = last->index_st[(startv+2)%3]; + + + // look for a matching triangle +nexttri: + for (j=starttri+1, check=&triangles[starttri+1] + ; jindex_xyz[k] != m1) + continue; + if (check->index_st[k] != st1) + continue; + if (check->index_xyz[ (k+1)%3 ] != m2) + continue; + if (check->index_st[ (k+1)%3 ] != st2) + continue; + + // this is the next part of the fan + + // if we can't use this triangle, this tristrip is done + if (used[j]) + goto done; + + // the new edge + m2 = check->index_xyz[ (k+2)%3 ]; + st2 = check->index_st[ (k+2)%3 ]; + + strip_xyz[stripcount+2] = m2; + strip_st[stripcount+2] = st2; + strip_tris[stripcount] = j; + stripcount++; + + used[j] = 2; + goto nexttri; + } + } +done: + + // clear the temp used flags + for (j=starttri+1 ; j bestlen) + { + besttype = type; + bestlen = len; + for (j=0 ; j= 150) + scale = 150.0 / width; + if (height*scale >= 190) + scale = 190.0 / height; + + s_scale = t_scale = scale; + + iwidth = ceil(width*s_scale); + iheight = ceil(height*t_scale); + + iwidth += 4; + iheight += 4; + } + else + { // new style + iwidth = g_fixedwidth / 2; + iheight = g_fixedheight; + + s_scale = (float)(iwidth-4) / width; + t_scale = (float)(iheight-4) / height; + } + +// +// determine which side of each triangle to map the texture to +// + for (i=0 ; i 0) + { + basex = iwidth + 2; + } + else + { + basex = 2; + } + basey = 2; + + for (j=0 ; j<3 ; j++) + { + pbasevert = ptri[i].verts[j]; + + triangle_st[i][j][0] = Q_rint((pbasevert[0] - mins[0]) * s_scale + basex); + triangle_st[i][j][1] = Q_rint((maxs[2] - pbasevert[2]) * t_scale + basey); + } + } + +// make the width a multiple of 4; some hardware requires this, and it ensures +// dword alignment for each scan + swidth = iwidth*2; + model.skinwidth = (swidth + 3) & ~3; + model.skinheight = iheight; +} +#endif + +//========================================================================== +// +// DrawScreen +// +//========================================================================== + +void DrawScreen(float s_scale, float t_scale, float iwidth, float iheight) +{ + int i; + byte *scrpos; + char buffer[256]; + + // Divider + scrpos = &pic[(INFO_Y-2)*SKINPAGE_WIDTH]; + for(i = 0; i < SKINPAGE_WIDTH; i++) + { + *scrpos++ = 255; + } + + sprintf(buffer, "GENSKIN: "); + DrawTextChar(16, INFO_Y, buffer); + + sprintf(buffer, "( %03d * %03d ) SCALE %f %f, SKINWIDTH %d," + " SKINHEIGHT %d", (int)ScaleWidth, (int)ScaleHeight, s_scale, t_scale, (int)iwidth*2, (int)iheight); + DrawTextChar(80, INFO_Y, buffer); +} + +/* +============ +BuildST + +Builds the triangle_st array for the base frame and +model.skinwidth / model.skinheight + + FIXME: allow this to be loaded from a file for + arbitrary mappings +============ +*/ +void BuildST (triangle_t *ptri, int numtri, qboolean DrawSkin) +{ + int i, j; + int width, height, iwidth, iheight, swidth; + float basex, basey; + float scale; + vec3_t mins, maxs; + float *pbasevert; + vec3_t vtemp1, vtemp2, normal; + float s_scale, t_scale; + float scWidth; + float scHeight; + + // + // find bounds of all the verts on the base frame + // + ClearBounds (mins, maxs); + + for (i=0 ; i= scHeight) + { + scale = scHeight/height; + } + + iwidth = ceil(width*scale)+4; + iheight = ceil(height*scale)+4; + + s_scale = (float)(iwidth-4) / width; + t_scale = (float)(iheight-4) / height; + t_scale = s_scale; + + if (DrawSkin) + DrawScreen(s_scale, t_scale, iwidth, iheight); + + +/* if (!g_fixedwidth) + { // old style + scale = 8; + if (width*scale >= 150) + scale = 150.0 / width; + if (height*scale >= 190) + scale = 190.0 / height; + + s_scale = t_scale = scale; + + iwidth = ceil(width*s_scale); + iheight = ceil(height*t_scale); + + iwidth += 4; + iheight += 4; + } + else + { // new style + iwidth = g_fixedwidth / 2; + iheight = g_fixedheight; + + s_scale = (float)(iwidth-4) / width; + t_scale = (float)(iheight-4) / height; + }*/ + +// +// determine which side of each triangle to map the texture to +// + for (i=0 ; i 0) + { + basex = iwidth + 2; + } + else + { + basex = 2; + } + basey = 2; + + for (j=0 ; j<3 ; j++) + { + pbasevert = ptri[i].verts[j]; + + triangle_st[i][j][0] = Q_rint((pbasevert[0] - mins[0]) * s_scale + basex); + triangle_st[i][j][1] = Q_rint((maxs[2] - pbasevert[2]) * t_scale + basey); + } + } + + DrawLine(triangle_st[i][0][0], triangle_st[i][0][1], + triangle_st[i][1][0], triangle_st[i][1][1]); + DrawLine(triangle_st[i][1][0], triangle_st[i][1][1], + triangle_st[i][2][0], triangle_st[i][2][1]); + DrawLine(triangle_st[i][2][0], triangle_st[i][2][1], + triangle_st[i][0][0], triangle_st[i][0][1]); + } + +// make the width a multiple of 4; some hardware requires this, and it ensures +// dword alignment for each scan + + swidth = iwidth*2; + model.skinwidth = (swidth + 3) & ~3; + model.skinheight = iheight; +} + + +static void ReplaceClusterIndex(int newIndex, int oldindex, int **clusters, + IntListNode_t **vertLists, int *num_verts, int *new_num_verts) +{ + int i, j; + IntListNode_t *next; + + for(j = 0; j < num_verts[0]; ++j) + { + for(i = 0; i < num_verts[j+1]; ++i) + { + if(clusters[j][i] == oldindex) + { + ++new_num_verts[j+1]; + + next = vertLists[j]; + + vertLists[j] = (IntListNode_t *) SafeMalloc(sizeof(IntListNode_t), "ReplaceClusterIndex"); + // Currently freed in WriteJointedModelFile only + + vertLists[j]->data = newIndex; + vertLists[j]->next = next; + } + } + } +} + +/* +================= +Cmd_Base +================= +*/ +void Cmd_Base (void) +{ + vec3_t base_xyz[MAX_VERTS]; + triangle_t *ptri; + int i, j, k; +#if 1 +#else + int time1; +#endif + char file1[1024]; + char file2[1024]; + + GetScriptToken (false); + + if (g_skipmodel || g_release || g_archive) + return; + + printf ("---------------------\n"); +#if 1 + sprintf (file1, "%s/%s", cdpartial, token); + printf ("%s ", file1); + + ExpandPathAndArchive (file1); + + sprintf (file1, "%s/%s", cddir, token); +#else + sprintf (file1, "%s/%s.%s", cdarchive, token, trifileext); + printf ("%s\n", file1); + + ExpandPathAndArchive (file1); + + sprintf (file1, "%s/%s.%s", cddir, token, trifileext); + + time1 = FileTime (file1); + if (time1 == -1) + Error ("%s doesn't exist", file1); +#endif +// +// load the base triangles +// + if (do3ds) + Load3DSTriangleList (file1, &ptri, &model.num_tris, NULL, NULL); + else + LoadTriangleList (file1, &ptri, &model.num_tris, NULL, NULL); + + + GetScriptToken (false); + sprintf (file2, "%s/%s.pcx", cddir, token); +// sprintf (trans_file, "%s/!%s_a.pcx", cddir, token); + + printf ("skin: %s\n", file2); + Load256Image (file2, &BasePixels, &BasePalette, &BaseWidth, &BaseHeight); + + if (BaseWidth != SKINPAGE_WIDTH || BaseHeight != SKINPAGE_HEIGHT) + { + if (g_allow_newskin) + { + ScaleWidth = BaseWidth; + ScaleHeight = BaseHeight; + } + else + { + Error("Invalid skin page size: (%d,%d) should be (%d,%d)", + BaseWidth,BaseHeight,SKINPAGE_WIDTH,SKINPAGE_HEIGHT); + } + } + else + { + ScaleWidth = (float)ExtractNumber(BasePixels, ENCODED_WIDTH_X, + ENCODED_WIDTH_Y); + ScaleHeight = (float)ExtractNumber(BasePixels, ENCODED_HEIGHT_X, + ENCODED_HEIGHT_Y); + } + +// +// get the ST values +// + BuildST (ptri, model.num_tris,false); + +// +// run through all the base triangles, storing each unique vertex in the +// base vertex list and setting the indirect triangles to point to the base +// vertices +// + for (i=0 ; i= '0' && *s <= '9') + s--; + + strcpy (suffix, s+1); + strcpy (base, frame); + base[s-frame+1] = 0; + + sprintf (file1, "%s/%s%s.%s",cddir, base, suffix, "hrc"); + time1 = FileTime (file1); + if (time1 != -1) + { + sprintf (retname, "%s%s.%s", base, suffix, "hrc"); + return retname; + } + + sprintf (file1, "%s/%s%s.%s",cddir, base, suffix, "asc"); + time1 = FileTime (file1); + if (time1 != -1) + { + sprintf (retname, "%s%s.%s", base, suffix, "asc"); + return retname; + } + + sprintf (file1, "%s/%s%s.%s",cddir, base, suffix, "tri"); + time1 = FileTime (file1); + if (time1 != -1) + { + sprintf (retname, "%s%s.%s", base, suffix, "tri"); + return retname; + } + + sprintf (file1, "%s/%s%s.%s",cddir, base, suffix, "3ds"); + time1 = FileTime (file1); + if (time1 != -1) + { + sprintf (retname, "%s%s.%s", base, suffix, "3ds"); + return retname; + } + + sprintf (file1, "%s/%s%s.%s",cddir, base, suffix, "htr"); + time1 = FileTime (file1); + if (time1 != -1) + { + sprintf (retname, "%s%s.%s", base, suffix, "htr"); + return retname; + } + + // check for 'run.1' + sprintf (file1, "%s/%s.%s",cddir, base, suffix); + time1 = FileTime (file1); + if (time1 != -1) + { + sprintf (retname, "%s.%s", base, suffix); + return retname; + } + + Error ("frame %s could not be found",frame); + return NULL; +} + +/* +=============== +GrabFrame +=============== +*/ +static void GrabFrame (char *frame) +{ + triangle_t *ptri; + int i, j; + trivert_t *ptrivert; + int num_tris; + char file1[1024]; + frame_t *fr; + vertexnormals_t vnorms[MAX_VERTS]; + int index_xyz; + char *framefile; + + // the frame 'run1' will be looked for as either + // run.1 or run1.tri, so the new alias sequence save + // feature an be used + framefile = FindFrameFile (frame); + + sprintf (file1, "%s/%s", cdarchive, framefile); + ExpandPathAndArchive (file1); + + sprintf (file1, "%s/%s",cddir, framefile); + + printf ("grabbing %s ", file1); + + if (model.num_frames >= MAX_FRAMES) + Error ("model.num_frames >= MAX_FRAMES"); + fr = &g_frames[model.num_frames]; + model.num_frames++; + + strcpy (fr->name, frame); + +// +// load the frame +// + if (do3ds) + Load3DSTriangleList (file1, &ptri, &num_tris, NULL, NULL); + else + LoadTriangleList (file1, &ptri, &num_tris, NULL, NULL); + + if (num_tris != model.num_tris) + Error ("%s: number of triangles doesn't match base frame\n", file1); + +// +// allocate storage for the frame's vertices +// + ptrivert = fr->v; + + for (i=0 ; imins, fr->maxs); + +// +// store the frame's vertices in the same order as the base. This assumes the +// triangles and vertices in this frame are in exactly the same order as in the +// base +// + for (i=0 ; imins, fr->maxs); + + VectorAdd (vnorms[index_xyz].normalsum, normal, vnorms[index_xyz].normalsum); + vnorms[index_xyz].numnormals++; + } + } + +// +// calculate the vertex normals, match them to the template list, and store the +// index of the best match +// + for (i=0 ; i maxdot) + { + maxdot = dot; + maxdotindex = j; + } + } + + ptrivert[i].lightnormalindex = maxdotindex; + } + + free (ptri); +} + +/* +=============== +GrabJointedFrame +=============== +*/ +void GrabJointedFrame(char *frame) +{ + char file1[1024]; + char *framefile; + frame_t *fr; + + framefile = FindFrameFile (frame); + + sprintf (file1, "%s/%s", cdarchive, framefile); + ExpandPathAndArchive (file1); + + sprintf (file1, "%s/%s",cddir, framefile); + + printf ("grabbing %s\n", file1); + + fr = &g_frames[model.num_frames - 1]; // last frame read in + + LoadJointList(file1, fr->joints, jointed); +} + +/* +=============== +GrabGlobals +=============== +*/ +void GrabGlobals(char *frame) +{ + char file1[1024]; + char *framefile; + frame_t *fr; + + framefile = FindFrameFile (frame); + + sprintf (file1, "%s/%s", cdarchive, framefile); + ExpandPathAndArchive (file1); + + sprintf (file1, "%s/%s",cddir, framefile); + + printf ("grabbing %s\n", file1); + + fr = &g_frames[model.num_frames - 1]; // last frame read in + + LoadGlobals(file1); +} + +/* +=============== +Cmd_Frame +=============== +*/ +void Cmd_Frame (void) +{ + while (ScriptTokenAvailable()) + { + GetScriptToken (false); + if (g_skipmodel) + continue; + if (g_release || g_archive) + { + model.num_frames = 1; // don't skip the writeout + continue; + } + + H_printf("#define FRAME_%-16s\t%i\n", token, model.num_frames); + + GrabFrame (token); + } +} + +/* +=============== +Cmd_Skin + +Skins aren't actually stored in the file, only a reference +is saved out to the header file. +=============== +*/ +void Cmd_Skin (void) +{ + byte *palette; + byte *pixels; + int width, height; + byte *cropped; + int y; + char name[1024], savename[1024]; + + GetScriptToken (false); + + if (model.num_skins == MAX_MD2SKINS) + Error ("model.num_skins == MAX_MD2SKINS"); + + if (g_skipmodel) + return; + +#if 1 + sprintf (name, "%s/%s.pcx", cddir, token); + sprintf (savename, "%s/!%s.pcx", g_outputDir, token); + sprintf (g_skins[model.num_skins], "%s/!%s.pcx", cdpartial, token); +#else + sprintf (name, "%s/%s.lbm", cdarchive, token); + strcpy (name, ExpandPathAndArchive( name ) ); +// sprintf (name, "%s/%s.lbm", cddir, token); + + if (ScriptTokenAvailable()) + { + GetScriptToken (false); + sprintf (g_skins[model.num_skins], "%s.pcx", token); + sprintf (savename, "%s%s.pcx", g_outputDir, g_skins[model.num_skins]); + } + else + { + sprintf (savename, "%s/%s.pcx", g_outputDir, token); + sprintf (g_skins[model.num_skins], "%s/%s.pcx", cdpartial, token); + } +#endif + + model.num_skins++; + + if (g_skipmodel || g_release || g_archive) + return; + + // load the image + printf ("loading %s\n", name); + Load256Image (name, &pixels, &palette, &width, &height); +// RemapZero (pixels, palette, width, height); + + // crop it to the proper size + cropped = (byte *) SafeMalloc (model.skinwidth*model.skinheight, "Cmd_Skin"); + for (y=0 ; y= longimagewidth || xh > longimagewidth || + yl >= longimageheight || yh > longimageheight) + { + Error ("line %i: bad clip dimmensions (%d,%d) (%d,%d) > image (%d,%d)", scriptline, xl,yl,w,h,longimagewidth,longimageheight); + } + + sourcel = longimage + (yl*longimagewidth) + xl; + destl = bufferl; + linedelta = (longimagewidth - w); + + for (y=yl ; yflags |= LittleLong(flags); + qtex32->contents = contents; + qtex32->value = value; + qtex32->scale_x = scale_x; + qtex32->scale_y = scale_y; + sprintf (qtex32->name, "%s/%s", pic_prefix, lumpname); + if (animname[0]) + sprintf (qtex32->animname, "%s/%s", pic_prefix, animname); + + // + // write it out + // + printf ("writing %s\n", filename); + SaveFile (filename, (byte *)qtex32, size); + + free (qtex32); + } + else + { + sprintf (filename, "%spics/%s/%s.m8", g_outputDir, pic_prefix, lumpname); + if (g_release) + return; // textures are only released by $maps + + xh = xl+w; + yh = yl+h; + + if (xl >= byteimagewidth || xh > byteimagewidth || + yl >= byteimageheight || yh > byteimageheight) + { + Error ("line %i: bad clip dimmensions (%d,%d) (%d,%d) > image (%d,%d)", scriptline, xl,yl,w,h,byteimagewidth,byteimageheight); + } + + source = byteimage + yl*byteimagewidth + xl; + dest = buffer; + linedelta = byteimagewidth - w; + + for (y=yl ; yflags = flags; + qtex->contents = contents; + qtex->value = value; + sprintf (qtex->name, "%s/%s", pic_prefix, lumpname); + if (animname[0]) + sprintf (qtex->animname, "%s/%s", pic_prefix, animname); + + // + // write it out + // + printf ("writing %s\n", filename); + SaveFile (filename, (byte *)qtex, size); + + free (qtex); + } +} + + +/* +=============== +Cmd_picdir +=============== +*/ +void Cmd_Picdir (void) +{ + char filename[1024]; + + GetScriptToken (false); + strcpy (pic_prefix, token); + // create the directory if needed + sprintf (filename, "%sPics", g_outputDir); + Q_mkdir (filename); + sprintf (filename, "%sPics/%s", g_outputDir, pic_prefix); + Q_mkdir (filename); +} + + diff --git a/tools/quake2/qdata_heretic2/qcommon/angles.h b/tools/quake2/qdata_heretic2/qcommon/angles.h new file mode 100644 index 00000000..1a722dab --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/angles.h @@ -0,0 +1,76 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// Angles in radians + +#define ANGLE_0 0.0F +#define ANGLE_1 0.017453292F +#define ANGLE_5 0.087266462F +#define ANGLE_10 0.174532925F +#define ANGLE_15 0.261799387F +#define ANGLE_20 0.392699081F +#define ANGLE_30 0.523598775F +#define ANGLE_45 0.785398163F +#define ANGLE_60 1.047197551F +#define ANGLE_72 1.256637061F +#define ANGLE_90 1.570796327F +#define ANGLE_120 2.094395102F +#define ANGLE_135 2.35619449F +#define ANGLE_144 2.513274123F +#define ANGLE_180 3.141592653F +#define ANGLE_225 3.926990817F +#define ANGLE_270 4.71238898F +#define ANGLE_315 5.497787144F +#define ANGLE_360 6.283185307F + +// Angles in degrees + +#define DEGREE_0 0.0F +#define DEGREE_180 180.0F +#define DEGREE_45 (DEGREE_180 / 4.0F) +#define DEGREE_90 (DEGREE_180 / 2.0F) +#define DEGREE_135 (DEGREE_90 + DEGREE_45) +#define DEGREE_270 (DEGREE_180 + DEGREE_90) +#define DEGREE_360 (DEGREE_180 * 2.0F) + +#define DEGREE_225 (DEGREE_180 + DEGREE_45) +#define DEGREE_315 (DEGREE_270 + DEGREE_45) + +#define DEGREE_30 (DEGREE_180 / 6.0F) +#define DEGREE_60 (DEGREE_180 / 3.0F) +#define DEGREE_120 (DEGREE_360 / 3.0F) + +#define DEGREE_1 (DEGREE_180 / 180.0F) +#define DEGREE_5 (DEGREE_180 / 36.0F) +#define DEGREE_10 (DEGREE_180 / 18.0F) +#define DEGREE_15 (DEGREE_180 / 12.0F) +#define DEGREE_20 (DEGREE_180 / 8.0F) + +// Conversion routines + +#define ANGLE_TO_RAD ANGLE_1 +#define RAD_TO_ANGLE (180.0F / ANGLE_180) + +#define SHORT_TO_ANGLE (360.0/65536) + + +#pragma warning(disable : 4305) // 'initializing' : truncation from 'const double ' to 'float ' + diff --git a/tools/quake2/qdata_heretic2/qcommon/arrayedlist.h b/tools/quake2/qdata_heretic2/qcommon/arrayedlist.h new file mode 100644 index 00000000..c04d669f --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/arrayedlist.h @@ -0,0 +1,71 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _ARRAYEDLIST_H +#define _ARRAYEDLIST_H + +#include + +typedef struct ArrayedListNode_s +{ + int data; + int next; + int inUse; +} ArrayedListNode_t; + +#define ARRAYEDLISTNODE_NULL -1 + +static +#ifdef _WIN32 + _inline +#else + inline +#endif + int GetFreeNode(ArrayedListNode_t *nodeArray, int max) +{ + int i; + + for(i = 0; i < max; ++i) + { + if(!nodeArray[i].inUse) + { + nodeArray[i].inUse = 1; + return i; + } + } + + assert(0); + return -1; +} + +static +#ifdef _WIN32 + _inline +#else + inline +#endif +void FreeNode(ArrayedListNode_t *nodeArray, int index) +{ + nodeArray[index].inUse = 0; +} + +#endif //_ARRAYEDLIST_H + diff --git a/tools/quake2/qdata_heretic2/qcommon/flex.h b/tools/quake2/qdata_heretic2/qcommon/flex.h new file mode 100644 index 00000000..0b231cbf --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/flex.h @@ -0,0 +1,33 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// Generic flexible format + +typedef struct +{ + char ident[32]; + int version; + int size; +} header_t; + +void WriteHeader(FILE *, char *, int, int, void *); + +// end diff --git a/tools/quake2/qdata_heretic2/qcommon/fmodel.h b/tools/quake2/qdata_heretic2/qcommon/fmodel.h new file mode 100644 index 00000000..ee198fff --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/fmodel.h @@ -0,0 +1,202 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +/* +======================================================================== + +.FM triangle flexible model file format + +======================================================================== +*/ + +#ifndef __FMODEL_HEADER +#define __FMODEL_HEADER + +#include "bspfile.h" + +//typedef unsigned char byte; +//typedef int qboolean; +//typedef float vec3_t[3]; + +#define MAX_FM_TRIANGLES 2048 +#define MAX_FM_VERTS 2048 +#define MAX_FM_FRAMES 2048 +#define MAX_FM_SKINS 64 +#define MAX_FM_SKINNAME 64 +#define MAX_FM_MESH_NODES 16 // also defined in game/qshared.h + + +#define DTRIVERTX_V0 0 +#define DTRIVERTX_V1 1 +#define DTRIVERTX_V2 2 +#define DTRIVERTX_LNI 3 +#define DTRIVERTX_SIZE 4 + +#define SKINPAGE_WIDTH 640 +#define SKINPAGE_HEIGHT 480 + +#define ENCODED_WIDTH_X 92 +#define ENCODED_WIDTH_Y 475 +#define ENCODED_HEIGHT_X 128 +#define ENCODED_HEIGHT_Y 475 + +#define SCALE_ADJUST_FACTOR 0.96 + +#define INFO_HEIGHT 5 +#define INFO_Y (SKINPAGE_HEIGHT-INFO_HEIGHT) + +extern byte *BasePalette; +extern byte *BasePixels,*TransPixels; +extern int BaseWidth, BaseHeight, TransWidth, TransHeight; +extern int ScaleWidth, ScaleHeight; + +int ExtractNumber(byte *pic, int x, int y); +void DrawTextChar(int x, int y, char *text); +void DrawLine(int x1, int y1, int x2, int y2); + +// the glcmd format: +// a positive integer starts a tristrip command, followed by that many +// vertex structures. +// a negative integer starts a trifan command, followed by -x vertexes +// a zero indicates the end of the command list. +// a vertex consists of a floating point s, a floating point t, +// and an integer vertex index. + + +// Initial Header +#define FM_HEADER_NAME "header" +#define FM_HEADER_VER 2 + +typedef struct +{ + int skinwidth; + int skinheight; + int framesize; // byte size of each frame + + int num_skins; + int num_xyz; + int num_st; // greater than num_xyz for seams + int num_tris; + int num_glcmds; // dwords in strip/fan command list + int num_frames; + int num_mesh_nodes; +} fmheader_t; + + +// Skin Header +#define FM_SKIN_NAME "skin" +#define FM_SKIN_VER 1 + + +// ST Coord Header +#define FM_ST_NAME "st coord" +#define FM_ST_VER 1 + +typedef struct +{ + short s; + short t; +} fmstvert_t; + + +// Tri Header +#define FM_TRI_NAME "tris" +#define FM_TRI_VER 1 + +typedef struct +{ + short index_xyz[3]; + short index_st[3]; +} fmtriangle_t; + + +// Frame Header +#define FM_FRAME_NAME "frames" +#define FM_FRAME_VER 1 + +// Frame for compression, just the names +#define FM_SHORT_FRAME_NAME "short frames" +#define FM_SHORT_FRAME_VER 1 + +// Normals for compressed frames +#define FM_NORMAL_NAME "normals" +#define FM_NORMAL_VER 1 + +// Compressed Frame Data +#define FM_COMP_NAME "comp data" +#define FM_COMP_VER 1 + +// GL Cmds Header +#define FM_GLCMDS_NAME "glcmds" +#define FM_GLCMDS_VER 1 + + +// Mesh Nodes Header +#define FM_MESH_NAME "mesh nodes" +#define FM_MESH_VER 3 + +// Skeleton Header +#define FM_SKELETON_NAME "skeleton" +#define FM_SKELETON_VER 1 + +// References Header +#define FM_REFERENCES_NAME "references" +#define FM_REFERENCES_VER 1 + +typedef struct +{ + + union + { + + byte tris[MAX_FM_TRIANGLES>>3]; + + struct { + short *triIndicies; + int num_tris; + }; + + }; + + byte verts[MAX_FM_VERTS>>3]; + short start_glcmds, num_glcmds; +} fmmeshnode_t; + +//================================================================= + +// Frame info +typedef struct +{ + byte v[3]; // scaled byte to fit in frame mins/maxs + byte lightnormalindex; +} fmtrivertx_t; + +typedef struct +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name from grabbing + fmtrivertx_t verts[1]; // variable sized +} fmaliasframe_t; + + +#endif // #define __FMODEL_HEADER diff --git a/tools/quake2/qdata_heretic2/qcommon/h2common.h b/tools/quake2/qdata_heretic2/qcommon/h2common.h new file mode 100644 index 00000000..a7f6587d --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/h2common.h @@ -0,0 +1,26 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef H2COMMON_H +#define H2COMMON_H + #define H2COMMON_API +#endif + diff --git a/tools/quake2/qdata_heretic2/qcommon/placement.h b/tools/quake2/qdata_heretic2/qcommon/placement.h new file mode 100644 index 00000000..4db2c55b --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/placement.h @@ -0,0 +1,38 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef PLACEMENT_H +#define PLACEMENT_H + +#include "q_typedef.h" + +//typedef float vec3_t[3]; + +typedef struct Placement_s +{ + vec3_t origin; + vec3_t direction; + vec3_t up; +} Placement_t; + +#endif + + diff --git a/tools/quake2/qdata_heretic2/qcommon/q_typedef.h b/tools/quake2/qdata_heretic2/qcommon/q_typedef.h new file mode 100644 index 00000000..02f9a459 --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/q_typedef.h @@ -0,0 +1,63 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef Q_TYPEDEF_H +#define Q_TYPEDEF_H + +typedef float vec_t; +typedef vec_t vec2_t[2]; +typedef vec_t vec3_t[3]; +typedef double vec3d_t[3]; +typedef vec_t vec5_t[5]; + +typedef float matrix3_t[3][3]; +typedef float matrix3d_t[3][3]; + +typedef int fixed4_t; +typedef int fixed8_t; +typedef int fixed16_t; + +typedef unsigned char byte; + +#ifndef __cplusplus +typedef enum {false, true} qboolean; +#else +typedef int qboolean; +#endif + +typedef struct edict_s edict_t; + +typedef struct paletteRGBA_s +{ + union + { + struct + { + byte r,g,b,a; + }; + unsigned c; + byte c_array[4]; + }; +} paletteRGBA_t; + +#endif + + diff --git a/tools/quake2/qdata_heretic2/qcommon/qfiles.h b/tools/quake2/qdata_heretic2/qcommon/qfiles.h new file mode 100644 index 00000000..d71eaf2b --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/qfiles.h @@ -0,0 +1,604 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +/* +======================================================================== + +The .pak files are just a linear collapse of a directory tree + +======================================================================== +*/ + +#define IDPAKHEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') + +typedef struct +{ + char name[56]; + int filepos, filelen; +} dpackfile_t; + +typedef struct +{ + int ident; // == IDPAKHEADER + int dirofs; + int dirlen; +} dpackheader_t; + +#define MAX_FILES_IN_PACK 6144 + + +/* +======================================================================== + +PCX files are used for as many images as possible + +======================================================================== +*/ + +typedef struct +{ + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + +/* +======================================================================== + +.MD2 compressed triangle model file format + +======================================================================== +*/ +#define IDCOMPRESSEDALIASHEADER (('2'<<24)+('C'<<16)+('D'<<8)+'I') + +/* +======================================================================== + +.MD2 compressed triangle model file format + +======================================================================== +*/ +#define IDJOINTEDALIASHEADER (('2'<<24)+('J'<<16)+('D'<<8)+'I') + +/* +======================================================================== + +.MD2 triangle model file format + +======================================================================== +*/ + +#define IDALIASHEADER (('2'<<24)+('P'<<16)+('D'<<8)+'I') +#define ALIAS_VERSION 8 + +#define MAX_TRIANGLES 4096 +#define MAX_VERTS 2048 +#define MAX_FRAMES 512 +#define MAX_MD2SKINS 64 +#define MAX_SKINNAME 64 + +typedef struct +{ + short s; + short t; +} dstvert_t; + +typedef struct +{ + short index_xyz[3]; + short index_st[3]; +} dtriangle_t; + +typedef struct +{ + union + { + struct + { + byte v[3]; // scaled byte to fit in frame mins/maxs + byte lightnormalindex; + }; + + int vert; + }; +} dtrivertx_t; + +#define DTRIVERTX_V0 0 +#define DTRIVERTX_V1 1 +#define DTRIVERTX_V2 2 +#define DTRIVERTX_LNI 3 +#define DTRIVERTX_SIZE 4 + +typedef struct +{ + float scale[3]; // multiply byte verts by this + float translate[3]; // then add this + char name[16]; // frame name from grabbing + dtrivertx_t verts[1]; // variable sized +} daliasframe_t; + + +// the glcmd format: +// a positive integer starts a tristrip command, followed by that many +// vertex structures. +// a negative integer starts a trifan command, followed by -x vertexes +// a zero indicates the end of the command list. +// a vertex consists of a floating point s, a floating point t, +// and an integer vertex index. + + +typedef struct +{ + int ident; + int version; + + int skinwidth; + int skinheight; + int framesize; // byte size of each frame + + int num_skins; + int num_xyz; + int num_st; // greater than num_xyz for seams + int num_tris; + int num_glcmds; // dwords in strip/fan command list + int num_frames; + + int ofs_skins; // each skin is a MAX_SKINNAME string + int ofs_st; // byte offset from start for stverts + int ofs_tris; // offset for dtriangles + int ofs_frames; // offset for first frame + int ofs_glcmds; + int ofs_end; // end of file + +} dmdl_t; + +// compressed model +typedef struct dcompmdl_s +{ + dmdl_t header; + short CompressedFrameSize; + short UniqueVerts; + short *remap; + float *translate; // then add this + float *scale; // multiply byte verts by this + char *mat; + char *frames; + char *base; + float *ctranslate; + float *cscale; + char data[1]; +} dcompmdl_t; + +typedef struct +{ + dcompmdl_t compModInfo; + int rootCluster; + int skeletalType; + struct ModelSkeleton_s *skeletons; +} JointedModel_t; + +/* +======================================================================== + +.BK file format + +======================================================================== +*/ + +#define IDBOOKHEADER (('K'<<24)+('O'<<16)+('O'<<8)+'B') +#define BOOK_VERSION 2 + +typedef struct bookframe_s +{ + int x; + int y; + int w; + int h; + char name[MAX_SKINNAME]; // name of gfx file +} bookframe_t; + +typedef struct bookheader_s +{ + unsigned int ident; + unsigned int version; + int num_segments; + int total_w; + int total_h; +} bookheader_t; + +typedef struct book_s +{ + bookheader_t bheader; + bookframe_t bframes[MAX_MD2SKINS]; +} book_t; + +/* +======================================================================== + +.SP2 sprite file format + +======================================================================== +*/ + +#define IDSPRITEHEADER (('2'<<24)+('S'<<16)+('D'<<8)+'I') + // little-endian "IDS2" +#define SPRITE_VERSION 2 + +typedef struct +{ + int width, height; + int origin_x, origin_y; // raster coordinates inside pic + char name[MAX_SKINNAME]; // name of pcx file +} dsprframe_t; + +typedef struct { + int ident; + int version; + int numframes; + dsprframe_t frames[1]; // variable sized +} dsprite_t; + +/* +============================================================================== + + .M8 texture file format + +============================================================================== +*/ + +typedef struct palette_s +{ + union + { + struct + { + byte r,g,b; + }; + }; +} palette_t; + +#define MIP_VERSION 2 +#define PAL_SIZE 256 +#define MIPLEVELS 16 + +typedef struct miptex_s +{ + int version; + char name[32]; + unsigned width[MIPLEVELS], height[MIPLEVELS]; + unsigned offsets[MIPLEVELS]; // four mip maps stored + char animname[32]; // next frame in animation chain + palette_t palette[PAL_SIZE]; + int flags; + int contents; + int value; +} miptex_t; + + + +#define MIP32_VERSION 4 + +typedef struct miptex32_s +{ + int version; + char name[128]; + char altname[128]; // texture substitution + char animname[128]; // next frame in animation chain + char damagename[128]; // image that should be shown when damaged + unsigned width[MIPLEVELS], height[MIPLEVELS]; + unsigned offsets[MIPLEVELS]; + int flags; + int contents; + int value; + float scale_x, scale_y; + int mip_scale; + + // detail texturing info + char dt_name[128]; // detailed texture name + float dt_scale_x, dt_scale_y; + float dt_u, dt_v; + float dt_alpha; + int dt_src_blend_mode, dt_dst_blend_mode; + + int unused[20]; // future expansion to maintain compatibility with h2 +} miptex32_t; + + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + +#define IDBSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') + // little-endian "IBSP" + +#define BSPVERSION 38 + + +// upper design bounds +// leaffaces, leafbrushes, planes, and verts are still bounded by +// 16 bit short limits +#define MAX_MAP_MODELS 1024 +//#define MAX_MAP_BRUSHES 8192 // Quake 2 original +#define MAX_MAP_BRUSHES 10240 +#define MAX_MAP_ENTITIES 2048 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_TEXINFO 8192 + +#define MAX_MAP_AREAS 256 +#define MAX_MAP_AREAPORTALS 1024 +#define MAX_MAP_PLANES 65536 +#define MAX_MAP_NODES 65536 +#define MAX_MAP_BRUSHSIDES 65536 +#define MAX_MAP_LEAFS 65536 +#define MAX_MAP_VERTS 65536 +#define MAX_MAP_FACES 65536 +#define MAX_MAP_LEAFFACES 65536 +#define MAX_MAP_LEAFBRUSHES 65536 +#define MAX_MAP_PORTALS 65536 +#define MAX_MAP_EDGES 128000 +#define MAX_MAP_SURFEDGES 256000 +#define MAX_MAP_LIGHTING 0x200000 +#define MAX_MAP_VISIBILITY 0x180000 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + +typedef struct +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_VERTEXES 2 +#define LUMP_VISIBILITY 3 +#define LUMP_NODES 4 +#define LUMP_TEXINFO 5 +#define LUMP_FACES 6 +#define LUMP_LIGHTING 7 +#define LUMP_LEAFS 8 +#define LUMP_LEAFFACES 9 +#define LUMP_LEAFBRUSHES 10 +#define LUMP_EDGES 11 +#define LUMP_SURFEDGES 12 +#define LUMP_MODELS 13 +#define LUMP_BRUSHES 14 +#define LUMP_BRUSHSIDES 15 +#define LUMP_POP 16 +#define LUMP_AREAS 17 +#define LUMP_AREAPORTALS 18 +#define HEADER_LUMPS 19 + +typedef struct +{ + int ident; + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; // for sounds or lights + int headnode; + int firstface, numfaces; // submodels just draw faces + // without walking the bsp tree +} dmodel_t; + + +typedef struct +{ + float point[3]; +} dvertex_t; + + +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 + +// 3-5 are non-axial planes snapped to the nearest +#define PLANE_ANYX 3 +#define PLANE_ANYY 4 +#define PLANE_ANYZ 5 + +// planes (x&~1) and (x&~1)+1 are allways opposites + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} dplane_t; + + +// contents flags are seperate bits +// a given brush can contribute multiple content bits +// multiple brushes can be in a single leaf + +// These definitions also need to be in q_shared.h! + +// ************************************************************************************************ +// CONTENTS_XXX +// ------------ +// Contents flags. +// ************************************************************************************************ + +// Lower bits are stronger, and will eat weaker brushes completely. + +#define CONTENTS_SOLID 0x00000001 // An eye is never valid in a solid. +#define CONTENTS_WINDOW 0x00000002 // Translucent, but not watery. +#define CONTENTS_AUX 0x00000004 +#define CONTENTS_LAVA 0x00000008 +#define CONTENTS_SLIME 0x00000010 +#define CONTENTS_WATER 0x00000020 +#define CONTENTS_MIST 0x00000040 +#define LAST_VISIBLE_CONTENTS CONTENTS_MIST + +// Remaining contents are non-visible, and don't eat brushes. + +#define CONTENTS_AREAPORTAL 0x00008000 +#define CONTENTS_PLAYERCLIP 0x00010000 +#define CONTENTS_MONSTERCLIP 0x00020000 + +// Currents can be added to any other contents, and may be mixed. + +#define CONTENTS_CURRENT_0 0x00040000 +#define CONTENTS_CURRENT_90 0x00080000 +#define CONTENTS_CURRENT_180 0x00100000 +#define CONTENTS_CURRENT_270 0x00200000 +#define CONTENTS_CURRENT_UP 0x00400000 +#define CONTENTS_CURRENT_DOWN 0x00800000 +#define CONTENTS_ORIGIN 0x01000000 // Removed before bsping an entity. +#define CONTENTS_MONSTER 0x02000000 // Should never be on a brush, only in game. +#define CONTENTS_DEADMONSTER 0x04000000 +#define CONTENTS_DETAIL 0x08000000 // Brushes to be added after vis leaves. +#define CONTENTS_TRANSLUCENT 0x10000000 // Auto set if any surface has transparency. +#define CONTENTS_LADDER 0x20000000 +#define CONTENTS_CAMERANOBLOCK 0x40000000 // Camera LOS ignores any brushes with this flag. + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} dnode_t; + + +typedef struct texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int flags; // miptex flags + overrides + int value; // light emission, etc + char texture[32]; // texture name (textures/*.wal) + int nexttexinfo; // for animations, -1 = end of chain +} texinfo_t; + + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + unsigned short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dface_t; + +typedef struct +{ + int contents; // OR of all brushes (not needed?) + + short cluster; + short area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstleafface; + unsigned short numleaffaces; + + unsigned short firstleafbrush; + unsigned short numleafbrushes; +} dleaf_t; + +typedef struct +{ + unsigned short planenum; // facing out of the leaf + short texinfo; +} dbrushside_t; + +typedef struct +{ + int firstside; + int numsides; + int contents; +} dbrush_t; + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the visibility lump consists of a header with a count, then +// byte offsets for the PVS and PHS of each cluster, then the raw +// compressed bit vectors +#define DVIS_PVS 0 +#define DVIS_PHS 1 +typedef struct +{ + int numclusters; + int bitofs[8][2]; // bitofs[numclusters][2] +} dvis_t; + +// each area has a list of portals that lead into other areas +// when portals are closed, other areas may not be visible or +// hearable even if the vis info says that it should be +typedef struct +{ + int portalnum; + int otherarea; +} dareaportal_t; + +typedef struct +{ + int numareaportals; + int firstareaportal; +} darea_t; + diff --git a/tools/quake2/qdata_heretic2/qcommon/reference.c b/tools/quake2/qdata_heretic2/qcommon/reference.c new file mode 100644 index 00000000..ddad0fe4 --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/reference.c @@ -0,0 +1,124 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include "reference.h" +#include "arrayedlist.h" +#include "resourcemanager.h" +#include "skeletons.h" + +char *referenceRootNames[] = +{ + "elf_Lhandroot",//0 + "elf_Rhandroot", + "elf_Rfootroot", + "elf_Lfootroot", + "elf_Bstaffroot", + "elf_bladeroot", + "elf_hellroot", + "StaffBone",//7 + "SwordBone", + "SpearBone", + "RFootBone", + "LFootBone", + "hp_backroot",//12 + "hp_staffroot", + "hp_lhandroot", + "hp_rhandroot", + "hp_rfootroot", + "hp_lfootroot", + "staffroot",//18 + "rfootroot", + "lfootroot", + "rhandroot", + "lhandroot", + "leyeroot", + "reyeroot" +}; + +int referenceRootNameOffsets[NUM_REFERENCED] = +{ + 0, // CORVUS + 7, // INSECT + 12, // HIGH PRIESTESS + 18, // MORCALAVIN +}; + +int numReferences[NUM_REFERENCED] = +{ + NUM_REFERENCES_CORVUS, + NUM_REFERENCES_INSECT, + NUM_REFERENCES_PRIESTESS, + NUM_REFERENCES_MORK, +}; + +int corvusJointIDs[NUM_REFERENCES_CORVUS] = +{ + CORVUS_UPPERBACK, + CORVUS_UPPERBACK, + -1, + -1, + CORVUS_UPPERBACK, + CORVUS_UPPERBACK, + CORVUS_UPPERBACK, +}; + +int *jointIDs[NUM_REFERENCED] = +{ + corvusJointIDs, +}; + +static ResourceManager_t ReferenceMngr; + +void InitReferenceMngr() +{ +#define REFERENCE_BLOCK_SIZE 8 + char *dummystr = NULL; + + ResMngr_Con(&ReferenceMngr, sizeof(LERPedReferences_t), REFERENCE_BLOCK_SIZE, dummystr); +} + +void ReleaseReferenceMngr() +{ + ResMngr_Des(&ReferenceMngr); +} + +LERPedReferences_t *LERPedReferences_new(int init_refType) +{ + LERPedReferences_t *newRefs; + + newRefs = ResMngr_AllocateResource(&ReferenceMngr, sizeof(*newRefs)); + newRefs->refType = init_refType; + newRefs->jointIDs = jointIDs[init_refType]; + newRefs->lastUpdate = -(REF_MINCULLTIME*2.0); + + memset(newRefs->references, 0, MAX_REFPOINTS*sizeof(Reference_t)); + memset(newRefs->oldReferences, 0, MAX_REFPOINTS*sizeof(Reference_t)); + + return newRefs; +} + +void LERPedReferences_delete(LERPedReferences_t *toDelete) +{ + ResMngr_DeallocateResource(&ReferenceMngr, toDelete, sizeof(*toDelete)); +} + +// end diff --git a/tools/quake2/qdata_heretic2/qcommon/reference.h b/tools/quake2/qdata_heretic2/qcommon/reference.h new file mode 100644 index 00000000..ace3678b --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/reference.h @@ -0,0 +1,126 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef REFERENCE_H +#define REFERENCE_H + +#include "placement.h" + +#define MAX_REFPOINTS 16 +#define REF_MINCULLTIME 1.0 + +typedef struct Reference_s +{ + int activecount; + Placement_t placement; +} Reference_t; + +typedef struct LERPedReferences_s +{ + int refType; + int *jointIDs; + float lastUpdate; + Reference_t references[MAX_REFPOINTS]; + Reference_t oldReferences[MAX_REFPOINTS]; +} LERPedReferences_t; + +// Reference Types +enum { + REF_NULL = -1, + REF_CORVUS,//0 + REF_INSECT,//1 + REF_PRIESTESS,//2 + REF_MORK,//3 + NUM_REFERENCED//4 +}; + +// Corvus Reference Points +enum { + CORVUS_LEFTHAND,//0 + CORVUS_RIGHTHAND, + CORVUS_LEFTFOOT, + CORVUS_RIGHTFOOT, + CORVUS_STAFF, + CORVUS_BLADE, + CORVUS_HELL_HEAD, + NUM_REFERENCES_CORVUS//7 +}; + +// Tchekrik Reference Points +enum { + INSECT_STAFF,//0 + INSECT_SWORD, + INSECT_SPEAR, + INSECT_RIGHTFOOT, + INSECT_LEFTFOOT, + NUM_REFERENCES_INSECT//5 +}; + +// High Priestess Reference Points +enum { + PRIESTESS_BACK,//0 + PRIESTESS_STAFF, + PRIESTESS_LHAND, + PRIESTESS_RHAND, + PRIESTESS_RFOOT, + PRIESTESS_LFOOT, + NUM_REFERENCES_PRIESTESS//6 +}; + +// Morcalavin Reference Points +enum +{ + MORK_STAFFREF,//0 + MORK_RFOOTREF,//1 + MORK_LFOOTREF,//2 + MORK_RHANDREF,//3 + MORK_LHANDREF,//4 + MORK_LEYEREF,//5 + MORK_REYEREF,//6 + NUM_REFERENCES_MORK//7 +}; + +#define CORVUS_LIMBS_MASK ((1 << CORVUS_LEFTHAND) | (1 << CORVUS_RIGHTHAND) | (1 << CORVUS_LEFTFOOT) | (1 << CORVUS_RIGHTFOOT)) +#define CORVUS_WEAPON_MASK ((1 << CORVUS_STAFF) | (1 << CORVUS_BLADE) | (1 << CORVUS_HELL_HEAD)) +#define CORVUS_MASK (CORVUS_LIMBS_MASK | CORVUS_WEAPON_MASK) + +#define INSECT_MASK ((1 << INSECT_STAFF) | (1 << INSECT_SWORD) | (1 << INSECT_SPEAR) | (1 << INSECT_RIGHTFOOT) | (1 << INSECT_LEFTFOOT)) + +#define PRIESTESS_MASK ((1 << PRIESTESS_BACK) | (1 << PRIESTESS_STAFF) | (1 << PRIESTESS_LHAND) | (1 << PRIESTESS_RHAND) | (1 << PRIESTESS_RFOOT) | (1 << PRIESTESS_LFOOT)) + +#define MORK_MASK ((1 << MORK_STAFFREF) | (1 << MORK_RFOOTREF) | (1 << MORK_LFOOTREF) | (1 << MORK_RHANDREF) | (1 << MORK_LHANDREF) | (1 << MORK_LEYEREF) | (1 << MORK_REYEREF)) + +extern char *referenceRootNames[]; +extern int referenceRootNameOffsets[]; +extern int numReferences[]; + +void EnableRefPoints(LERPedReferences_t *refInfo, int mask); +void DisableRefPoints(LERPedReferences_t *refInfo, int mask); + +void InitReferenceMngr(); +void ReleaseReferenceMngr(); + +LERPedReferences_t *LERPedReferences_new(int init_refType); +void LERPedReferences_delete(LERPedReferences_t *toDelete); + +#endif + + diff --git a/tools/quake2/qdata_heretic2/qcommon/resourcemanager.c b/tools/quake2/qdata_heretic2/qcommon/resourcemanager.c new file mode 100644 index 00000000..ef5613df --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/resourcemanager.c @@ -0,0 +1,159 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +// +// ResourceManager.c +// + +#include +#include "resourcemanager.h" +#include + +typedef struct ResMngr_Block_s +{ + char *start; + unsigned int size; + struct ResMngr_Block_s *next; +} ResMngr_Block_t; + +static void ResMngr_CreateBlock(ResourceManager_t *resource) +{ + unsigned int _blockSize; + char *block; + char **current; + ResMngr_Block_t *temp; + unsigned int i; + + _blockSize = resource->nodeSize * resource->resPerBlock; + + block = malloc(_blockSize); + + assert(block); + + temp = malloc(sizeof(*temp)); + + temp->start = block; + temp->size = _blockSize; + temp->next = resource->blockList; + + resource->blockList = temp; + + resource->free = (char **)(block); + + current = resource->free; + + for(i = 1; i < resource->resPerBlock; ++i) + { + // set current->next to point to next node + *current = (char *)(current) + resource->nodeSize; + + // set current node to current->next + current = (char **)(*current); + } + + *current = NULL; +} + +H2COMMON_API void ResMngr_Con(ResourceManager_t *resource, size_t init_resSize, unsigned int init_resPerBlock, char *resman_name) +{ + resource->resSize = init_resSize; + + resource->resPerBlock = init_resPerBlock; + + resource->nodeSize = resource->resSize + sizeof(*resource->free); + + resource->blockList = NULL; + + resource->numResourcesAllocated = 0; + + ResMngr_CreateBlock(resource); +} + +H2COMMON_API void ResMngr_Des(ResourceManager_t *resource) +{ + ResMngr_Block_t *toDelete; + +#if 0 + if (resource->numResourcesAllocated) + { + char mess[100]; + sprintf(mess,"Potential memory leak %d bytes unfreed\n",resource->resSize*resource->numResourcesAllocated); + OutputDebugString(mess); + } +#endif + + while(resource->blockList) + { + toDelete = resource->blockList; + resource->blockList = resource->blockList->next; + free(toDelete->start); + free(toDelete); + } +} + +H2COMMON_API void *ResMngr_AllocateResource(ResourceManager_t *resource, size_t size) +{ + char **toPop; + + assert(size == resource->resSize); + + ++resource->numResourcesAllocated; + + assert(resource->free); // constructor not called; possibly due to a static object + // containing a static ResourceManagerFastLarge member being + // constructed before its own static members + + toPop = resource->free; + + // set unallocated to the next node and check for NULL (end of list) + if(!(resource->free = (char **)(*resource->free))) + { // if at end create new block + ResMngr_CreateBlock(resource); + } + + // set next to NULL + *toPop = NULL; + + // return the resource for the node + return (void *)(toPop + 1); +} + +H2COMMON_API void ResMngr_DeallocateResource(ResourceManager_t *resource, void *toDeallocate, size_t size) +{ + char **toPush; + + assert(size == resource->resSize); + + --resource->numResourcesAllocated; + + toPush = (char **)(toDeallocate) - 1; + + assert(resource->free); // see same assert at top of AllocateResource + + // set toPop->next to current unallocated front + *toPush = (char *)(resource->free); + + // set unallocated to the node removed from allocated + resource->free = toPush; +} + +// end diff --git a/tools/quake2/qdata_heretic2/qcommon/resourcemanager.h b/tools/quake2/qdata_heretic2/qcommon/resourcemanager.h new file mode 100644 index 00000000..e267ecd5 --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/resourcemanager.h @@ -0,0 +1,47 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// ResourceManager.h +// + +#include "h2common.h" +#include // needed here for size_t + +typedef struct ResourceManager_s +{ + size_t resSize; + unsigned int resPerBlock; + unsigned int nodeSize; + struct ResMngr_Block_s *blockList; + char **free; + char *ResMan_Name; + + unsigned numResourcesAllocated; + +} ResourceManager_t; + +extern H2COMMON_API void ResMngr_Con(ResourceManager_t *resource, size_t init_resSize, unsigned int init_resPerBlock, char *resman_name); +extern H2COMMON_API void ResMngr_Des(ResourceManager_t *resource); +extern H2COMMON_API void *ResMngr_AllocateResource(ResourceManager_t *resource, size_t size); +extern H2COMMON_API void ResMngr_DeallocateResource(ResourceManager_t *resource, void *toDeallocate, size_t size); + + diff --git a/tools/quake2/qdata_heretic2/qcommon/skeletons.c b/tools/quake2/qdata_heretic2/qcommon/skeletons.c new file mode 100644 index 00000000..1da0b4f1 --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/skeletons.c @@ -0,0 +1,232 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// Skeletons.c +// + +#include "skeletons.h" + +char *skeletonRootNames[] = +{ + "RAVEN_ROOT", + "BOX_ROOT", + "BEETLE_ROOT", + "ELFLORD_ROOT", + "PLAGUELF_ROOT", + "ELF_BACKROOT", +}; + +int skeletonRNameOffsets[] = +{ + 0, // RAVEN + 1, // BOX + 2, // BEETLE + 3, // ELFLORD + 4, // PLAGUE ELF + 5, // CORVUS +}; + +char *skeletonJointNames[] = +{ + "RAVEN_LOWERBACK", // 0 + "RAVEN_UPPERBACK", + "RAVEN_NECK", + "BOX_CENTER", // 3 + "BEETLE_NECK", // 4 + "BEETLE_HEAD", + "PLAGUELF_BACKB", // 6 + "PLAGUELF_BACKC", + "PLAGUELF_NECK", + "ELF_BACKB", // 9 + "ELF_BACKC", + "ELF_NECKB", +}; + +int skeletonNameOffsets[] = +{ + 0, // RAVEN + 3, // BOX + 4, // BEETLE + -1, // ELFLORD + 6, // PLAGUE ELF + 9, // CORVUS +}; + +char *skeletonEffectorNames[] = +{ + "BEETLE_EYES", // 0 + "CORVUS_EYES", // 1 +}; + +int skeletonENameOffsets[] = +{ + -1, // RAVEN + -1, // BOX + 0, // BEETLE + -1, // ELFLORD + 1, // PLAGUE ELF +}; + +int numJointsInSkeleton[] = +{ + NUM_JOINTS_RAVEN, + NUM_JOINTS_BOX, + NUM_JOINTS_BEETLE, + NUM_JOINTS_ELFLORD, + NUM_JOINTS_PLAGUE_ELF, + NUM_JOINTS_CORVUS, +}; + +int numNodesInSkeleton[] = +{ + 2, // RAVEN + 0, // BOX + 1, // BEETLE + -1, // ELFLORD + 2, // PLAGUE ELF + 2, // CORVUS +}; + +void CreateRavenSkel(void *g_skeletalJoints, size_t jointSize, struct ArrayedListNode_s *g_jointNodes, int root); +void CreateBoxSkel(void *g_skeletalJoints, size_t jointSize, struct ArrayedListNode_s *g_jointNodes, int root); +void CreateBeetleSkel(void *g_skeletalJoints, size_t jointSize, ArrayedListNode_t *g_jointNodes, int rootIndex); +void CreateElfLordSkel(void *g_skeletalJoints, size_t jointSize, ArrayedListNode_t *g_jointNodes, int rootIndex); +void CreatePlagueElfSkel(void *g_skeletalJoints, size_t jointSize, ArrayedListNode_t *g_jointNodes, int rootIndex); + +CreateSkeleton_t SkeletonCreators[NUM_SKELETONS] = +{ + CreateRavenSkel, + CreateBoxSkel, + CreateBeetleSkel, + CreateElfLordSkel, + CreatePlagueElfSkel, + CreatePlagueElfSkel, // Corvus has the same structure as the Plague Elf +}; + +void CreateRavenSkel(void *g_skeletalJoints, size_t jointSize, ArrayedListNode_t *g_jointNodes, int rootIndex) +{ + char *root; + int *children; + int nodeIndex; + + root = (char *)g_skeletalJoints + rootIndex * jointSize; + + children = (int *)(root + RAVEN_HEAD * jointSize); + *children = ARRAYEDLISTNODE_NULL; + + nodeIndex = GetFreeNode(g_jointNodes, MAX_ARRAYED_JOINT_NODES); + + children = (int *)(root + RAVEN_UPPERBACK * jointSize); + *children = nodeIndex; + + g_jointNodes[nodeIndex].data = rootIndex + RAVEN_HEAD; + g_jointNodes[nodeIndex].next = ARRAYEDLISTNODE_NULL; + + nodeIndex = GetFreeNode(g_jointNodes, MAX_ARRAYED_JOINT_NODES); + + children = (int *)(root + RAVEN_LOWERBACK * jointSize); + *children = nodeIndex; + + g_jointNodes[nodeIndex].data = rootIndex + RAVEN_UPPERBACK; + g_jointNodes[nodeIndex].next = ARRAYEDLISTNODE_NULL; +} + +void CreateBoxSkel(void *g_skeletalJoints, size_t jointSize, ArrayedListNode_t *g_jointNodes, int rootIndex) +{ + char *root; + int *children; + + root = (char *)g_skeletalJoints + rootIndex * jointSize; + + children = (int *)(root + RAVEN_HEAD * jointSize); + *children = ARRAYEDLISTNODE_NULL; +} + +void CreateBeetleSkel(void *g_skeletalJoints, size_t jointSize, ArrayedListNode_t *g_jointNodes, int rootIndex) +{ + char *root; + int *children; + int nodeIndex; + + root = (char *)g_skeletalJoints + rootIndex * jointSize; + + children = (int *)(root + BEETLE_HEAD * jointSize); + *children = ARRAYEDLISTNODE_NULL; + + nodeIndex = GetFreeNode(g_jointNodes, MAX_ARRAYED_JOINT_NODES); + + children = (int *)(root + BEETLE_NECK * jointSize); + *children = nodeIndex; + + g_jointNodes[nodeIndex].data = rootIndex + BEETLE_HEAD; + g_jointNodes[nodeIndex].next = ARRAYEDLISTNODE_NULL; +} + +void CreateElfLordSkel(void *g_skeletalJoints, size_t jointSize, ArrayedListNode_t *g_jointNodes, int rootIndex) +{ + char *root; + int *children; + int nodeIndex; + + root = (char *)g_skeletalJoints + rootIndex * jointSize; + + children = (int *)(root + BEETLE_HEAD * jointSize); + *children = ARRAYEDLISTNODE_NULL; + + nodeIndex = GetFreeNode(g_jointNodes, MAX_ARRAYED_JOINT_NODES); + + children = (int *)(root + BEETLE_NECK * jointSize); + *children = nodeIndex; + + g_jointNodes[nodeIndex].data = rootIndex + BEETLE_HEAD; + g_jointNodes[nodeIndex].next = ARRAYEDLISTNODE_NULL; +} + +void CreatePlagueElfSkel(void *g_skeletalJoints, size_t jointSize, ArrayedListNode_t *g_jointNodes, int rootIndex) +{ + char *root; + int *children; + int nodeIndex; + + root = (char *)g_skeletalJoints + rootIndex * jointSize; + + children = (int *)(root + PLAGUE_ELF_HEAD * jointSize); + *children = ARRAYEDLISTNODE_NULL; + + nodeIndex = GetFreeNode(g_jointNodes, MAX_ARRAYED_JOINT_NODES); + + children = (int *)(root + PLAGUE_ELF_UPPERBACK * jointSize); + *children = nodeIndex; + + g_jointNodes[nodeIndex].data = rootIndex + PLAGUE_ELF_HEAD; + g_jointNodes[nodeIndex].next = ARRAYEDLISTNODE_NULL; + + nodeIndex = GetFreeNode(g_jointNodes, MAX_ARRAYED_JOINT_NODES); + + children = (int *)(root + PLAGUE_ELF_LOWERBACK * jointSize); + *children = nodeIndex; + + g_jointNodes[nodeIndex].data = rootIndex + PLAGUE_ELF_UPPERBACK; + g_jointNodes[nodeIndex].next = ARRAYEDLISTNODE_NULL; +} + + diff --git a/tools/quake2/qdata_heretic2/qcommon/skeletons.h b/tools/quake2/qdata_heretic2/qcommon/skeletons.h new file mode 100644 index 00000000..b36c12ed --- /dev/null +++ b/tools/quake2/qdata_heretic2/qcommon/skeletons.h @@ -0,0 +1,107 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include // for size_t +#include "arrayedlist.h" + +#define JN_YAW_CHANGED 0x00000001 +#define JN_PITCH_CHANGED 0x00000002 +#define JN_ROLL_CHANGED 0x00000004 + +// Skeleton types +enum { + SKEL_NULL = -1, + SKEL_RAVEN = 0, + SKEL_BOX, + SKEL_BEETLE, + SKEL_ELFLORD, + SKEL_PLAGUE_ELF, + SKEL_CORVUS, + NUM_SKELETONS +}; + +// Raven Skeletal joints +enum { + RAVEN_LOWERBACK = 0, + RAVEN_UPPERBACK, + RAVEN_HEAD, + NUM_JOINTS_RAVEN +}; + +// Box Skeletal joints +enum { + BOX_CENTER = 0, + NUM_JOINTS_BOX +}; + +// Beetle Skeletal joints +enum { + BEETLE_NECK = 0, + BEETLE_HEAD, + NUM_JOINTS_BEETLE +}; + +// Elflord Skeletal joints +enum { + ELFLORD_, + ELFLORD__, + NUM_JOINTS_ELFLORD +}; + +// Plague Elf Skeletal joints +enum { + PLAGUE_ELF_LOWERBACK, + PLAGUE_ELF_UPPERBACK, + PLAGUE_ELF_HEAD, + NUM_JOINTS_PLAGUE_ELF +}; + +// Corvus Skeletal joints +enum { + CORVUS_LOWERBACK, + CORVUS_UPPERBACK, + CORVUS_HEAD, + NUM_JOINTS_CORVUS +}; + +#define NO_SWAP_FRAME -1 +#define NULL_ROOT_JOINT -1 + +#define MAX_ARRAYED_SKELETAL_JOINTS 255 // has max of 65,535 (if this remains at 255, net code can be changed to reflect) +#define MAX_ARRAYED_JOINT_NODES (MAX_ARRAYED_SKELETAL_JOINTS - 1) + +#define MAX_JOINTS_PER_SKELETON 8 // arbitrary small number +#define MAX_JOINT_NODES_PER_SKELETON (MAX_JOINTS_PER_SKELETON - 1) + +extern char *skeletonRootNames[]; +extern int skeletonRNameOffsets[]; +extern char *skeletonJointNames[]; +extern int skeletonNameOffsets[]; +extern int numJointsInSkeleton[]; +extern char *skeletonEffectorNames[]; +extern int skeletonENameOffsets[]; +extern int numNodesInSkeleton[]; + +typedef void (*CreateSkeleton_t)(void *skeletalJoints, size_t jointSize, struct ArrayedListNode_s *jointNodes, int rootIndex); + +extern CreateSkeleton_t SkeletonCreators[NUM_SKELETONS]; + + diff --git a/tools/quake2/qdata_heretic2/qd_fmodel.h b/tools/quake2/qdata_heretic2/qd_fmodel.h new file mode 100644 index 00000000..7ec6570c --- /dev/null +++ b/tools/quake2/qdata_heretic2/qd_fmodel.h @@ -0,0 +1,61 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef FMODEL_H +#define FMODEL_H +#include "fmodel.h" +#endif +#include "qd_skeletons.h" + +typedef struct +{ + int numnormals; + vec3_t normalsum; +} fmvertexnormals_t; + +typedef struct +{ + vec3_t v; + int lightnormalindex; + fmvertexnormals_t vnorm; +} fmtrivert_t; + +#define FRAME_NAME_LEN (16) + +typedef struct +{ + vec3_t mins, maxs; + char name[FRAME_NAME_LEN]; + fmtrivert_t v[MAX_FM_VERTS]; + struct QD_SkeletalJoint_s joints[NUM_CLUSTERS]; + struct QD_SkeletalJoint_s references[NUM_REFERENCES]; +} fmframe_t; + +extern fmframe_t g_frames[MAX_FM_FRAMES]; + +extern fmheader_t fmheader; +extern char cdarchive[1024]; // set by $fmcd +extern char cdpartial[1024]; // set by $fmcd +extern char cddir[1024]; // set by $fmcd + +void GrabFrame (char *frame); +void H_printf(char *fmt, ...); +char *FindFrameFile (char *frame); diff --git a/tools/quake2/qdata_heretic2/qd_skeletons.c b/tools/quake2/qdata_heretic2/qd_skeletons.c new file mode 100644 index 00000000..43395c8c --- /dev/null +++ b/tools/quake2/qdata_heretic2/qd_skeletons.c @@ -0,0 +1,1291 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qd_skeletons.h" +#include "skeletons.h" +#include "qd_fmodel.h" +#include "angles.h" +#include "token.h" +#include "qdata.h" +#include "reference.h" + +#include +#include +#include + + +// We're assuming no more than 16 reference points, with no more than 32 characters in the name +char RefPointNameList[REF_MAX_POINTS][REF_MAX_STRLEN]; +int RefPointNum = 0; + +Skeletalfmheader_t g_skelModel; + +void ClearSkeletalModel() +{ + g_skelModel.type = SKEL_NULL; + g_skelModel.clustered = false; + g_skelModel.references = REF_NULL; +} + +//========================================================================== +// +// LoadHRCClustered +// +//========================================================================== + +// Places the null terminated src string into the dest string less any trailing digits or underscores +void StripTrailingDigits(char *src, char *dest) +{ +#ifndef NDEBUG + int max = SKELETAL_NAME_MAX; // should be sufficient for inteded use on names from hrc files +#endif + int i = 0; + + while(src[i] != '\0') + { + ++i; +#ifndef NDEBUG + assert(i < max); +#endif + } + + while((src[--i] >= '0' && src[i] <= '9') || src[i] == '_') + { + + } + + memcpy(dest, src, ++i); + + dest[i] = '\0'; +} + +static void LoadHRCClustered(char *fileName, int **clusterList, int *num_verts, int skelType) +{ + extern void HandleHRCModel(triangle_t **triList, int *triangleCount, + mesh_node_t **nodesList, int *num_mesh_nodes, int ActiveNode, int Depth); + + extern mesh_node_t *pmnodes; + + triangle_t *triList; +// mesh_node_t *nodesList; + int num_mesh_nodes = 0, triangleCount = 0; + +#if 0 + int i; + int j, numVerts; + char stripped[SKELETAL_NAME_MAX]; + + for( i = 1; i < numJointsInSkeleton[skelType] + 1; ++i) + { + num_verts[i] = 0; + } + + TK_OpenSource(fileName); + TK_FetchRequire(TK_HRCH); + TK_FetchRequire(TK_COLON); + TK_FetchRequire(TK_SOFTIMAGE); + + TK_Beyond(TK_CLUSTERS); + + while(TK_Search(TK_CLUSTER_NAME) != TK_EOF) + { + TK_Require(TK_STRING); + + StripTrailingDigits(tk_String, stripped); + + for( i = 0; i < numJointsInSkeleton[skelType]; ++i) + { + if(stricmp(stripped, skeletonJointNames[skeletonNameOffsets[skelType]+i]) == 0) + { + i = -i + numJointsInSkeleton[skelType] - 1; + + TK_BeyondRequire(TK_NUM_CLUSTER_VERTICES, TK_INTNUMBER); + + numVerts = tk_IntNumber; + + if(!num_verts[i+1]) // first set of verts for cluster + { + clusterList[i] = SafeMalloc(numVerts*sizeof(int), "LoadHRCClustered"); + assert(clusterList[i]); + } + else // any later sets of verts need to copy current + { + int *temp; + + temp = SafeMalloc((num_verts[i+1]+numVerts)*sizeof(int), "LoadHRCClustered"); + assert(temp); + + memcpy(temp + numVerts, clusterList[i], num_verts[i+1]*sizeof(int)); + + free(clusterList[i]); + + clusterList[i] = temp; + } + + // currently this function is only called by LoadModelClusters. + // Apparently the matching free has disappeared, + // should probably be free at the end of FMCmd_Base + + TK_Beyond(TK_LBRACE); + + for(j = 0; j < numVerts; ++j) + { + TK_Require(TK_INTNUMBER); + clusterList[i][j] = tk_IntNumber; + TK_Fetch(); + } + + num_verts[i+1] += numVerts; + + break; + } + } + } + + num_verts[0] = numJointsInSkeleton[skelType]; +#endif + +#if 1 // get the index number localized to the root +// for( i = 1; i < numJointsInSkeleton[skelType] + 1; ++i) +// { +// g_skelModel.num_verts[i] = 0; +// } + + TK_OpenSource(fileName); + TK_FetchRequire(TK_HRCH); + TK_FetchRequire(TK_COLON); + TK_FetchRequire(TK_SOFTIMAGE); + + // prime it + TK_Beyond(TK_MODEL); + + triList = (triangle_t *) SafeMalloc(MAXTRIANGLES*sizeof(triangle_t), "Triangle list"); + memset(triList,0,MAXTRIANGLES*sizeof(triangle_t)); +// nodesList = SafeMalloc(MAX_FM_MESH_NODES * sizeof(mesh_node_t), "Mesh Node List"); + pmnodes = (mesh_node_t *) SafeMalloc(MAX_FM_MESH_NODES * sizeof(mesh_node_t), "Mesh Node List"); + + memset(pmnodes, 0, MAX_FM_MESH_NODES * sizeof(mesh_node_t)); + + // this should eventually use a stripped down version of this + HandleHRCModel(&triList, &triangleCount, &pmnodes, &num_mesh_nodes, 0, 0); + +// free(nodesList); + free(triList); + + num_verts[0] = numJointsInSkeleton[skelType]; +#endif +} + +void ReadHRCClusterList(mesh_node_t *meshNode, int baseIndex) +{ + int i, j, numVerts; + tokenType_t nextToken; + char stripped[SKELETAL_NAME_MAX]; + + meshNode->clustered = true; + + nextToken = TK_Get(TK_CLUSTER_NAME); + + while (nextToken == TK_CLUSTER_NAME) + { + TK_FetchRequire(TK_STRING); + + StripTrailingDigits(tk_String, stripped); + + for( i = 0; i < numJointsInSkeleton[g_skelModel.type]; ++i) + { + if(stricmp(stripped, skeletonJointNames[skeletonNameOffsets[g_skelModel.type]+i]) == 0) + { + i = -i + numJointsInSkeleton[g_skelModel.type] - 1; + + TK_BeyondRequire(TK_NUM_CLUSTER_VERTICES, TK_INTNUMBER); + + numVerts = tk_IntNumber; + + if(!baseIndex) + { + meshNode->clusters[i] = (int *) SafeMalloc(numVerts*sizeof(int), "ReadHRCClusterList"); + assert(meshNode->clusters[i]); + } + else + { + int *temp; + + temp = meshNode->clusters[i]; + meshNode->clusters[i] = (int *) SafeMalloc((meshNode->num_verts[i+1]+numVerts)*sizeof(int), "ReadHRCClusterList"); + assert(meshNode->clusters[i]); + + memcpy(meshNode->clusters[i], temp, meshNode->num_verts[i+1]*sizeof(int)); + free(temp); + } + + // currently this function is only called by LoadModelClusters. + // Apparently the matching free has disappeared, + // should probably be free at the end of FMCmd_Base + + TK_Beyond(TK_LBRACE); + + for(j = 0; j < numVerts; ++j) + { + TK_Require(TK_INTNUMBER); + meshNode->clusters[i][baseIndex+j] = tk_IntNumber+baseIndex; + TK_Fetch(); + } + + if(baseIndex) + { + meshNode->num_verts[i+1] += numVerts; + } + else + { + meshNode->num_verts[i+1] = numVerts; + } + + break; + } + } + + TK_BeyondRequire(TK_CLUSTER_STATE, TK_INTNUMBER); + nextToken = TK_Fetch(); + } +} + +static void LoadHRCGlobals(char *fileName) +{ + int i; + + TK_OpenSource(fileName); + TK_FetchRequire(TK_HRCH); + TK_FetchRequire(TK_COLON); + TK_FetchRequire(TK_SOFTIMAGE); + TK_Beyond(TK_MODEL); + + TK_Beyond(TK_SCALING); + for(i = 0; i < 3; i++) + { + TK_Require(TK_FLOATNUMBER); + g_skelModel.scaling[i] = tk_FloatNumber; + TK_Fetch(); + } + + TK_Beyond(TK_ROTATION); + for(i = 0; i < 3; i++) + { + TK_Require(TK_FLOATNUMBER); + g_skelModel.rotation[i] = tk_FloatNumber; + TK_Fetch(); + } + + TK_Beyond(TK_TRANSLATION); + for(i = 0; i < 3; i++) + { + TK_Require(TK_FLOATNUMBER); + g_skelModel.translation[i] = tk_FloatNumber; + TK_Fetch(); + } +} + +static void ParseVec3(vec3_t in) +{ + TK_Require(TK_FLOATNUMBER); + in[1] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[2] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[0] = tk_FloatNumber; +} + +static void ParseVec3d(vec3d_t in) +{ + TK_Require(TK_FLOATNUMBER); + in[1] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[2] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[0] = tk_FloatNumber; +} + +static void ParseRotation3(vec3_t in) +{ + TK_Require(TK_FLOATNUMBER); + in[1] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[2] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[0] = tk_FloatNumber; +} + +static void ParseRotation3d(vec3d_t in) +{ + TK_Require(TK_FLOATNUMBER); + in[1] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[2] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[0] = tk_FloatNumber; +} + +static void ParseTranslation3(vec3_t in) +{ + TK_Require(TK_FLOATNUMBER); + in[1] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[2] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[0] = tk_FloatNumber; +} + +static void ParseTranslation3d(vec3d_t in) +{ + TK_Require(TK_FLOATNUMBER); + in[1] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[2] = tk_FloatNumber; + TK_FetchRequire(TK_FLOATNUMBER); + in[0] = tk_FloatNumber; +} + +static void LoadHRCJointList(char *fileName, QD_SkeletalJoint_t *jointList, int skelType) +{ +#define MAX_STACK 64 + int i, j; + vec3d_t curTranslation[MAX_STACK], curRotation[MAX_STACK], curScale[MAX_STACK]; + int curCorrespondingJoint[MAX_STACK]; + int currentStack = 0, stackSize; + double cx, sx, cy, sy, cz, sz; + double rx, ry, rz; + double x2, y2, z2; + char stripped[SKELETAL_NAME_MAX]; + Placement_d_t *placement; + + TK_OpenSource(fileName); + TK_FetchRequire(TK_HRCH); + TK_FetchRequire(TK_COLON); + TK_FetchRequire(TK_SOFTIMAGE); + + TK_Beyond(TK_MODEL); + + while(TK_Search(TK_NAME) != TK_EOF) + { + TK_Require(TK_STRING); + + StripTrailingDigits(tk_String, stripped); + + if(stricmp(stripped, skeletonRootNames[skeletonRNameOffsets[skelType]]) == 0) + { + break; + } + } + + if(tk_Token == TK_EOF) + { + Error("Bone Chain Root: %s not found\n", skeletonRootNames[skeletonRNameOffsets[skelType]]); + return; + } + + TK_Beyond(TK_SCALING); + + ParseVec3d(curScale[currentStack]); + + TK_Beyond(TK_ROTATION); + + ParseRotation3d(curRotation[currentStack]); + + TK_Beyond(TK_TRANSLATION); + + ParseVec3d(curTranslation[currentStack]); + + // account for global model translation + curTranslation[currentStack][1] += g_skelModel.translation[0]; + curTranslation[currentStack][2] += g_skelModel.translation[1]; + curTranslation[currentStack][0] += g_skelModel.translation[2]; + + curCorrespondingJoint[currentStack] = -1; + + ++currentStack; + + for(i = 0; i < numJointsInSkeleton[skelType]; ++i) + { + while(1) + { + TK_Beyond(TK_MODEL); + + TK_BeyondRequire(TK_NAME, TK_STRING); + + StripTrailingDigits(tk_String, stripped); + + if(stricmp(stripped, skeletonJointNames[skeletonNameOffsets[skelType]+i]) == 0) + break; + + TK_Beyond(TK_SCALING); + + ParseVec3d(curScale[currentStack]); + + TK_Beyond(TK_ROTATION); + + ParseRotation3d(curRotation[currentStack]); + + TK_Beyond(TK_TRANSLATION); + + ParseVec3d(curTranslation[currentStack]); + + curCorrespondingJoint[currentStack] = -1; + + ++currentStack; + } + + TK_Beyond(TK_SCALING); + + ParseVec3d(curScale[currentStack]); + + TK_Beyond(TK_ROTATION); + + ParseRotation3d(curRotation[currentStack]); + + jointList[i].rotation[1] = curRotation[currentStack][1]; + jointList[i].rotation[2] = curRotation[currentStack][2]; + jointList[i].rotation[0] = curRotation[currentStack][0]; + + TK_Beyond(TK_TRANSLATION); + + ParseVec3d(curTranslation[currentStack]); + +// jointList[i].placement.origin[1] = curTranslation[currentStack][1]; +// jointList[i].placement.origin[2] = curTranslation[currentStack][2]; +// jointList[i].placement.origin[0] = curTranslation[currentStack][0]; + + jointList[i].placement.origin[1] = 0.0; + jointList[i].placement.origin[2] = 0.0; + jointList[i].placement.origin[0] = 0.0; + + jointList[i].placement.direction[1] = 20.0; + jointList[i].placement.direction[2] = 0.0; + jointList[i].placement.direction[0] = 0.0; + + jointList[i].placement.up[1] = 0.0; + jointList[i].placement.up[2] = 20.0; + jointList[i].placement.up[0] = 0.0; + + curCorrespondingJoint[currentStack] = i; + + ++currentStack; + } + + stackSize = currentStack; + +#if 0 + // rotate the direction and up vectors to correspond to the rotation + for(i = 0; i < numJointsInSkeleton[skelType]; ++i) + { + rx = jointList[i].rotation[0]*ANGLE_TO_RAD; + ry = jointList[i].rotation[1]*ANGLE_TO_RAD; + rz = jointList[i].rotation[2]*ANGLE_TO_RAD; + + cx = cos(rx); + sx = sin(rx); + + cy = cos(ry); + sy = sin(ry); + + cz = cos(rz); + sz = sin(rz); + + // y-axis rotation for direction + x2 = jointList[i].placement.direction[0]*cy+jointList[i].placement.direction[2]*sy; + z2 = -jointList[i].placement.direction[0]*sy+jointList[i].placement.direction[2]*cy; + jointList[i].placement.direction[0] = x2; + jointList[i].placement.direction[2] = z2; + + // y-axis rotation for up + x2 = jointList[i].placement.up[0]*cy+jointList[i].placement.up[2]*sy; + z2 = -jointList[i].placement.up[0]*sy+jointList[i].placement.up[2]*cy; + jointList[i].placement.up[0] = x2; + jointList[i].placement.up[2] = z2; + + // z-axis rotation for direction + x2 = jointList[i].placement.direction[0]*cz-jointList[i].placement.direction[1]*sz; + y2 = jointList[i].placement.direction[0]*sz+jointList[i].placement.direction[1]*cz; + jointList[i].placement.direction[0] = x2; + jointList[i].placement.direction[1] = y2; + + // z-axis rotation for up + x2 = jointList[i].placement.up[0]*cz-jointList[i].placement.up[1]*sz; + y2 = jointList[i].placement.up[0]*sz+jointList[i].placement.up[1]*cz; + jointList[i].placement.up[0] = x2; + jointList[i].placement.up[1] = y2; + + // x-axis rotation for direction vector + y2 = jointList[i].placement.direction[1]*cx-jointList[i].placement.direction[2]*sx; + z2 = jointList[i].placement.direction[1]*sx+jointList[i].placement.direction[2]*cx; + jointList[i].placement.direction[1] = y2; + jointList[i].placement.direction[2] = z2; + + // x-axis rotation for up vector + y2 = jointList[i].placement.up[1]*cx-jointList[i].placement.up[2]*sx; + z2 = jointList[i].placement.up[1]*sx+jointList[i].placement.up[2]*cx; + jointList[i].placement.up[1] = y2; + jointList[i].placement.up[2] = z2; + + // translate direction to a point in the model + jointList[i].placement.direction[0] += jointList[i].placement.origin[0]; + jointList[i].placement.direction[1] += jointList[i].placement.origin[1]; + jointList[i].placement.direction[2] += jointList[i].placement.origin[2]; + + // translate up to a point in the model + jointList[i].placement.up[0] += jointList[i].placement.origin[0]; + jointList[i].placement.up[1] += jointList[i].placement.origin[1]; + jointList[i].placement.up[2] += jointList[i].placement.origin[2]; + } +#endif + + for(i = stackSize - 1; i >= 0; --i) + { + rx = curRotation[i][0]*ANGLE_TO_RAD; + ry = curRotation[i][1]*ANGLE_TO_RAD; + rz = curRotation[i][2]*ANGLE_TO_RAD; + + cx = cos(rx); + sx = sin(rx); + + cy = cos(ry); + sy = sin(ry); + + cz = cos(rz); + sz = sin(rz); + +#if 1 + for(j = i; j < stackSize; ++j) + { + if(curCorrespondingJoint[j] != -1) + { + placement = &jointList[curCorrespondingJoint[j]].placement; + + // y-axis rotation for origin + x2 = placement->origin[0]*cy+placement->origin[2]*sy; + z2 = -placement->origin[0]*sy+placement->origin[2]*cy; + placement->origin[0] = x2; + placement->origin[2] = z2; + + // y-axis rotation for direction + x2 = placement->direction[0]*cy+placement->direction[2]*sy; + z2 = -placement->direction[0]*sy+placement->direction[2]*cy; + placement->direction[0] = x2; + placement->direction[2] = z2; + + // y-axis rotation for up + x2 = placement->up[0]*cy+placement->up[2]*sy; + z2 = -placement->up[0]*sy+placement->up[2]*cy; + placement->up[0] = x2; + placement->up[2] = z2; + + // z-axis rotation for origin + x2 = placement->origin[0]*cz-placement->origin[1]*sz; + y2 = placement->origin[0]*sz+placement->origin[1]*cz; + placement->origin[0] = x2; + placement->origin[1] = y2; + + // z-axis rotation for direction + x2 = placement->direction[0]*cz-placement->direction[1]*sz; + y2 = placement->direction[0]*sz+placement->direction[1]*cz; + placement->direction[0] = x2; + placement->direction[1] = y2; + + // z-axis rotation for up + x2 = placement->up[0]*cz-placement->up[1]*sz; + y2 = placement->up[0]*sz+placement->up[1]*cz; + placement->up[0] = x2; + placement->up[1] = y2; + + // x-axis rotation for origin + y2 = placement->origin[1]*cx-placement->origin[2]*sx; + z2 = placement->origin[1]*sx+placement->origin[2]*cx; + placement->origin[1] = y2; + placement->origin[2] = z2; + + // x-axis rotation for direction vector + y2 = placement->direction[1]*cx-placement->direction[2]*sx; + z2 = placement->direction[1]*sx+placement->direction[2]*cx; + placement->direction[1] = y2; + placement->direction[2] = z2; + + // x-axis rotation for up vector + y2 = placement->up[1]*cx-placement->up[2]*sx; + z2 = placement->up[1]*sx+placement->up[2]*cx; + placement->up[1] = y2; + placement->up[2] = z2; + + // translate origin + placement->origin[0] += curTranslation[i][0]; + placement->origin[1] += curTranslation[i][1]; + placement->origin[2] += curTranslation[i][2]; + + // translate back to local coord + placement->direction[0] += curTranslation[i][0]; + placement->direction[1] += curTranslation[i][1]; + placement->direction[2] += curTranslation[i][2]; + + // translate back to local coord + placement->up[0] += curTranslation[i][0]; + placement->up[1] += curTranslation[i][1]; + placement->up[2] += curTranslation[i][2]; + } + } +#else + // This screwed up and needs to be sorted out!!! + // The stack info needs to be written too instead of the jointList for j > numJoints for Skeleton + for(j = i-1; j < stackSize-1; ++j) + { + // y-axis rotation for origin + x2 = jointList[j].placement.origin[0]*cy+jointList[j].placement.origin[2]*sy; + z2 = -jointList[j].placement.origin[0]*sy+jointList[j].placement.origin[2]*cy; + jointList[j].placement.origin[0] = x2; + jointList[j].placement.origin[2] = z2; + + // y-axis rotation for direction + x2 = jointList[j].placement.direction[0]*cy+jointList[j].placement.direction[2]*sy; + z2 = -jointList[j].placement.direction[0]*sy+jointList[j].placement.direction[2]*cy; + jointList[j].placement.direction[0] = x2; + jointList[j].placement.direction[2] = z2; + + // y-axis rotation for up + x2 = jointList[j].placement.up[0]*cy+jointList[j].placement.up[2]*sy; + z2 = -jointList[j].placement.up[0]*sy+jointList[j].placement.up[2]*cy; + jointList[j].placement.up[0] = x2; + jointList[j].placement.up[2] = z2; + + // z-axis rotation for origin + x2 = jointList[j].placement.origin[0]*cz-jointList[j].placement.origin[1]*sz; + y2 = jointList[j].placement.origin[0]*sz+jointList[j].placement.origin[1]*cz; + jointList[j].placement.origin[0] = x2; + jointList[j].placement.origin[1] = y2; + + // z-axis rotation for direction + x2 = jointList[j].placement.direction[0]*cz-jointList[j].placement.direction[1]*sz; + y2 = jointList[j].placement.direction[0]*sz+jointList[j].placement.direction[1]*cz; + jointList[j].placement.direction[0] = x2; + jointList[j].placement.direction[1] = y2; + + // z-axis rotation for up + x2 = jointList[j].placement.up[0]*cz-jointList[j].placement.up[1]*sz; + y2 = jointList[j].placement.up[0]*sz+jointList[j].placement.up[1]*cz; + jointList[j].placement.up[0] = x2; + jointList[j].placement.up[1] = y2; + + // x-axis rotation for origin + y2 = jointList[j].placement.origin[1]*cx-jointList[j].placement.origin[2]*sx; + z2 = jointList[j].placement.origin[1]*sx+jointList[j].placement.origin[2]*cx; + jointList[j].placement.origin[1] = y2; + jointList[j].placement.origin[2] = z2; + + // x-axis rotation for direction vector + y2 = jointList[j].placement.direction[1]*cx-jointList[j].placement.direction[2]*sx; + z2 = jointList[j].placement.direction[1]*sx+jointList[j].placement.direction[2]*cx; + jointList[j].placement.direction[1] = y2; + jointList[j].placement.direction[2] = z2; + + // x-axis rotation for up vector + y2 = jointList[j].placement.up[1]*cx-jointList[j].placement.up[2]*sx; + z2 = jointList[j].placement.up[1]*sx+jointList[j].placement.up[2]*cx; + jointList[j].placement.up[1] = y2; + jointList[j].placement.up[2] = z2; + + if(curCorrespondingJoint[j+1] != -1) + { + // translate origin + jointList[j].placement.origin[0] += curTranslation[i-1][0]; + jointList[j].placement.origin[1] += curTranslation[i-1][1]; + jointList[j].placement.origin[2] += curTranslation[i-1][2]; + + // translate back to local coord + jointList[j].placement.direction[0] += curTranslation[i-1][0]; + jointList[j].placement.direction[1] += curTranslation[i-1][1]; + jointList[j].placement.direction[2] += curTranslation[i-1][2]; + + // translate back to local coord + jointList[j].placement.up[0] += curTranslation[i-1][0]; + jointList[j].placement.up[1] += curTranslation[i-1][1]; + jointList[j].placement.up[2] += curTranslation[i-1][2]; + } + } +#endif + } +} + +void LoadModelTransform(char *fileName) +{ + FILE *file1; + int dot = '.'; + char *dotstart; + char InputFileName[256]; + + dotstart = strrchr(fileName,dot); // Does it already have an extension on the file name? + + if (!dotstart) + { + strcpy(InputFileName, fileName); + strcat(InputFileName, ".hrc"); + if((file1 = fopen(InputFileName, "rb")) != NULL) + { + fclose(file1); + + LoadHRCGlobals(InputFileName); + + printf(" - assuming .HRC\n"); + return; + } + + Error("\n Could not open file '%s':\n" + "No HRC match.\n", fileName); + } + else + { + if((file1 = fopen(fileName, "rb")) != NULL) + { +// printf("\n"); + fclose(file1); + if (strcmp(dotstart,".hrc") == 0 || strcmp(dotstart,".HRC") == 0) + { + LoadHRCGlobals(fileName); + return; + } + } + + Error("Could not open file '%s':\n",fileName); + } +} + +void LoadModelClusters(char *fileName, int **clusterList, int *num_verts, int skelType) +{ + FILE *file1; + int dot = '.'; + char *dotstart; + char InputFileName[256]; + + dotstart = strrchr(fileName,dot); // Does it already have an extension on the file name? + + if (!dotstart) + { + strcpy(InputFileName, fileName); + strcat(InputFileName, ".hrc"); + if((file1 = fopen(InputFileName, "rb")) != NULL) + { + fclose(file1); + + LoadHRCClustered(InputFileName, clusterList, num_verts, skelType); + + printf(" - assuming .HRC\n"); + return; + } + + Error("\n Could not open file '%s':\n" + "No HRC match.\n", fileName); + } + else + { + if((file1 = fopen(fileName, "rb")) != NULL) + { +// printf("\n"); + fclose(file1); + if (strcmp(dotstart,".hrc") == 0 || strcmp(dotstart,".HRC") == 0) + { + LoadHRCClustered(fileName, clusterList, num_verts, skelType); + return; + } + } + + Error("Could not open file '%s':\n",fileName); + } +} + +void LoadSkeleton(char *fileName, QD_SkeletalJoint_t *jointList, int skelType) +{ + FILE *file1; + int dot = '.'; + char *dotstart; + char InputFileName[256]; + + dotstart = strrchr(fileName,dot); // Does it already have an extension on the file name? + + if (!dotstart) + { + strcpy(InputFileName, fileName); + strcat(InputFileName, ".hrc"); + if((file1 = fopen(InputFileName, "rb")) != NULL) + { + fclose(file1); + + LoadHRCJointList(InputFileName, jointList, skelType); + + printf(" - assuming .HRC\n"); + return; + } + + Error("\n Could not open file '%s':\n" + "No HRC.\n", fileName); + } + else + { + if((file1 = fopen(fileName, "rb")) != NULL) + { +// printf("\n"); + fclose(file1); + if (strcmp(dotstart,".hrc") == 0 || strcmp(dotstart,".HRC") == 0) + { + LoadHRCJointList(fileName, jointList, skelType); + + return; + } + } + + Error("Could not open file '%s':\n",fileName); + } +} + +/* +=============== +GrabSkeletalFrame +=============== +*/ +void GrabSkeletalFrame(char *frame) +{ + char file1[1024]; + char *framefile; + fmframe_t *fr; + + framefile = FindFrameFile (frame); + + sprintf (file1, "%s/%s", cdarchive, framefile); + ExpandPathAndArchive (file1); + + sprintf (file1, "%s/%s",cddir, framefile); + + printf ("Grabbing Skeletal Frame %s\n", file1); + + fr = &g_frames[fmheader.num_frames - 1]; // last frame read in + + LoadSkeleton(file1, fr->joints, g_skelModel.type); +} + +/* +=============== +GrabModelTransform +=============== +*/ +void GrabModelTransform(char *frame) +{ + char file1[1024]; + char *framefile; + fmframe_t *fr; + + framefile = FindFrameFile (frame); + + sprintf (file1, "%s/%s", cdarchive, framefile); + ExpandPathAndArchive (file1); + + sprintf (file1, "%s/%s",cddir, framefile); + +// printf ("grabbing %s\n", file1); + + fr = &g_frames[fmheader.num_frames - 1]; // last frame read in + + LoadModelTransform(file1); +} + +void Cmd_FMCluster() +{ + char file1[1024]; + + GetScriptToken (false); + + printf ("---------------------\n"); + sprintf (file1, "%s/%s", cdpartial, token); + printf ("%s\n", file1); + + ExpandPathAndArchive (file1); + + sprintf (file1, "%s/%s", cddir, token); + + g_skelModel.clustered = -1; + + LoadModelClusters(file1, (int **)&g_skelModel.clusters, (int *)&g_skelModel.num_verts, g_skelModel.type); + + g_skelModel.new_num_verts[0] = g_skelModel.num_verts[0]; + + g_skelModel.clustered = true; +} + +void Cmd_FMSkeleton() +{ + GetScriptToken (false); + g_skelModel.type = atoi(token); +} + +void Cmd_FMSkeletalFrame() +{ + while (ScriptTokenAvailable()) + { + GetScriptToken (false); + if (g_skipmodel) + { + GetScriptToken (false); + continue; + } + if (g_release || g_archive) + { + fmheader.num_frames = 1; // don't skip the writeout + GetScriptToken (false); + continue; + } + + H_printf("#define FRAME_%-16s\t%i\n", token, fmheader.num_frames); + + GrabModelTransform (token); + GrabFrame (token); + GrabSkeletalFrame (token); + + // need to add the up and dir points to the frame bounds here + // using AddPointToBounds (ptrivert[index_xyz].v, fr->mins, fr->maxs); + // then remove fudge in determining scale on frame write out + } +} + +static void LoadHRCReferences(char *fileName, fmframe_t *fr) +{ +#define MAX_STACK 64 + int i, j, k; + vec3d_t curTranslation[MAX_STACK], curRotation[MAX_STACK]; + int curCorrespondingJoint[MAX_STACK]; + int currentStack, stackSize; + double cx, sx, cy, sy, cz, sz; + double rx, ry, rz; + double x2, y2, z2; + char stripped[SKELETAL_NAME_MAX]; + Placement_d_t *placement; + int refnum; + + TK_OpenSource(fileName); + TK_FetchRequire(TK_HRCH); + TK_FetchRequire(TK_COLON); + TK_FetchRequire(TK_SOFTIMAGE); + + if (RefPointNum <= 0) + { // There were no labels indicated in the QDT, so use the hard-coded stuff. + refnum = numReferences[g_skelModel.references]; + } + else + { + refnum = RefPointNum; + } + + for(k = 0; k < refnum; ++k) + { + currentStack = 0; + + // Load the root to get translation and initial rotation +// TK_Beyond(TK_MODEL); + + while(TK_Search(TK_NAME) != TK_EOF) + { + TK_Require(TK_STRING); + + StripTrailingDigits(tk_String, stripped); + + if (RefPointNum == 0) + { // Hard coded refpoint labels + if(stricmp(stripped, + referenceRootNames[referenceRootNameOffsets[g_skelModel.references]+k]) == 0) + { + break; + } + } + else + { // labels indicated by the QDT + if(stricmp(stripped, RefPointNameList[k]) == 0) + { + break; + } + } + } + + if(tk_Token == TK_EOF) + { + if (RefPointNum == 0) + { // Hard coded refpoint labels + Error("Bone Chain Root: %s not found\n", referenceRootNames[referenceRootNameOffsets[g_skelModel.references]]); + } + else + { // labels indicated by the QDT + Error("Bone Chain Root: %s not found\n", RefPointNameList[k]); + } + return; + } + +// TK_Beyond(TK_SCALING); + +// ParseVec3d(curScale[currentStack]); + + TK_Beyond(TK_ROTATION); + + ParseRotation3d(curRotation[currentStack]); + + TK_Beyond(TK_TRANSLATION); + + ParseVec3d(curTranslation[currentStack]); + + // account for global model translation + curTranslation[currentStack][1] += g_skelModel.translation[0]; + curTranslation[currentStack][2] += g_skelModel.translation[1]; + curTranslation[currentStack][0] += g_skelModel.translation[2]; + + curCorrespondingJoint[currentStack] = -1; + +// rjr - this one not needed, as there is also a stack increment 20 lines below??? +// ++currentStack; + + // Load the joint to get orientation + TK_Beyond(TK_MODEL); + +// TK_Beyond(TK_SCALING); + +// ParseVec3d(curScale[currentStack]); + + TK_Beyond(TK_ROTATION); + + ParseRotation3d(curRotation[currentStack]); + +// TK_Beyond(TK_TRANSLATION); + +// ParseVec3d(curTranslation[currentStack]); + + fr->references[k].placement.origin[1] = 0.0; + fr->references[k].placement.origin[2] = 0.0; + fr->references[k].placement.origin[0] = 0.0; + + fr->references[k].placement.direction[1] = 20.0; + fr->references[k].placement.direction[2] = 0.0; + fr->references[k].placement.direction[0] = 0.0; + + fr->references[k].placement.up[1] = 0.0; + fr->references[k].placement.up[2] = 20.0; + fr->references[k].placement.up[0] = 0.0; + + curCorrespondingJoint[currentStack] = k; + + ++currentStack; + + stackSize = currentStack; + + for(i = stackSize - 1; i >= 0; --i) + { + rx = curRotation[i][0]*ANGLE_TO_RAD; + ry = curRotation[i][1]*ANGLE_TO_RAD; + rz = curRotation[i][2]*ANGLE_TO_RAD; + + cx = cos(rx); + sx = sin(rx); + + cy = cos(ry); + sy = sin(ry); + + cz = cos(rz); + sz = sin(rz); + + for(j = i; j < stackSize; ++j) + { + if(curCorrespondingJoint[j] != -1) + { + placement = &fr->references[curCorrespondingJoint[j]].placement; + + // y-axis rotation for origin + x2 = placement->origin[0]*cy+placement->origin[2]*sy; + z2 = -placement->origin[0]*sy+placement->origin[2]*cy; + placement->origin[0] = x2; + placement->origin[2] = z2; + + // y-axis rotation for direction + x2 = placement->direction[0]*cy+placement->direction[2]*sy; + z2 = -placement->direction[0]*sy+placement->direction[2]*cy; + placement->direction[0] = x2; + placement->direction[2] = z2; + + // y-axis rotation for up + x2 = placement->up[0]*cy+placement->up[2]*sy; + z2 = -placement->up[0]*sy+placement->up[2]*cy; + placement->up[0] = x2; + placement->up[2] = z2; + + // z-axis rotation for origin + x2 = placement->origin[0]*cz-placement->origin[1]*sz; + y2 = placement->origin[0]*sz+placement->origin[1]*cz; + placement->origin[0] = x2; + placement->origin[1] = y2; + + // z-axis rotation for direction + x2 = placement->direction[0]*cz-placement->direction[1]*sz; + y2 = placement->direction[0]*sz+placement->direction[1]*cz; + placement->direction[0] = x2; + placement->direction[1] = y2; + + // z-axis rotation for up + x2 = placement->up[0]*cz-placement->up[1]*sz; + y2 = placement->up[0]*sz+placement->up[1]*cz; + placement->up[0] = x2; + placement->up[1] = y2; + + // x-axis rotation for origin + y2 = placement->origin[1]*cx-placement->origin[2]*sx; + z2 = placement->origin[1]*sx+placement->origin[2]*cx; + placement->origin[1] = y2; + placement->origin[2] = z2; + + // x-axis rotation for direction vector + y2 = placement->direction[1]*cx-placement->direction[2]*sx; + z2 = placement->direction[1]*sx+placement->direction[2]*cx; + placement->direction[1] = y2; + placement->direction[2] = z2; + + // x-axis rotation for up vector + y2 = placement->up[1]*cx-placement->up[2]*sx; + z2 = placement->up[1]*sx+placement->up[2]*cx; + placement->up[1] = y2; + placement->up[2] = z2; + + // translate origin + placement->origin[0] += curTranslation[i][0]; + placement->origin[1] += curTranslation[i][1]; + placement->origin[2] += curTranslation[i][2]; + + // translate back to local coord + placement->direction[0] += curTranslation[i][0]; + placement->direction[1] += curTranslation[i][1]; + placement->direction[2] += curTranslation[i][2]; + + // translate back to local coord + placement->up[0] += curTranslation[i][0]; + placement->up[1] += curTranslation[i][1]; + placement->up[2] += curTranslation[i][2]; + + } + } + } + printf("%f, %f, %f\n", placement->origin[0], placement->origin[1], placement->origin[2]); + } + printf("\n"); +} + +void Cmd_FMReferenced() +{ + int i; + + GetScriptToken (false); + g_skelModel.references = atoi(token); + + // Guess what? Now, we now want a list of strings to look for here instead of a hard-coded list + for (i=0; i 0) + { + printf("Searching for %d different reference points.\n", RefPointNum); + } + else + { + printf("Using built-in reference points.\n"); + } + +} + +void LoadReferences(char *fileName, fmframe_t *fr) +{ + FILE *file1; + int dot = '.'; + char *dotstart; + char InputFileName[256]; + + dotstart = strrchr(fileName,dot); // Does it already have an extension on the file name? + + if (!dotstart) + { + strcpy(InputFileName, fileName); + strcat(InputFileName, ".hrc"); + if((file1 = fopen(InputFileName, "rb")) != NULL) + { + fclose(file1); + + LoadHRCReferences(InputFileName, fr); + + printf(" - assuming .HRC\n"); + return; + } + + Error("\n Could not open file '%s':\n" + "No HRC.\n", fileName); + } + else + { + if((file1 = fopen(fileName, "rb")) != NULL) + { + printf("\n"); + fclose(file1); + if (strcmp(dotstart,".hrc") == 0 || strcmp(dotstart,".HRC") == 0) + { + LoadHRCReferences(fileName, fr); + + return; + } + } + + Error("Could not open file '%s':\n",fileName); + } +} + +void GrabReferencedFrame(char *frame) +{ + char file1[1024]; + char *framefile; + fmframe_t *fr; + + framefile = FindFrameFile (frame); + + sprintf (file1, "%s/%s", cdarchive, framefile); + ExpandPathAndArchive (file1); + + sprintf (file1, "%s/%s",cddir, framefile); + + printf ("Grabbing Referenced %s\n", file1); + + fr = &g_frames[fmheader.num_frames - 1]; // last frame read in + + LoadReferences(file1, fr); +} + diff --git a/tools/quake2/qdata_heretic2/qd_skeletons.h b/tools/quake2/qdata_heretic2/qd_skeletons.h new file mode 100644 index 00000000..b6ff7bcd --- /dev/null +++ b/tools/quake2/qdata_heretic2/qd_skeletons.h @@ -0,0 +1,84 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef QD_SKELETONS_H +#define QD_SKELETONS_H + +#include "placement.h" + + +typedef struct Placement_d_s +{ + vec3d_t origin; + vec3d_t direction; + vec3d_t up; +} Placement_d_t; + +typedef struct QD_SkeletalJoint_s +{ + Placement_d_t placement; + vec3d_t rotation; +} QD_SkeletalJoint_t; + +#define NUM_CLUSTERS 8 + +typedef struct IntListNode_s +{ + int data; + struct IntListNode_s *next; +} IntListNode_t; // gaak + +typedef struct Skeletalfmheader_s +{ + int type; + int clustered; + int references; + + int *clusters[NUM_CLUSTERS]; + IntListNode_t *vertLists[NUM_CLUSTERS]; + int num_verts[NUM_CLUSTERS + 1]; + int new_num_verts[NUM_CLUSTERS + 1]; + + float scaling[3]; + float rotation[3]; + float translation[3]; +} Skeletalfmheader_t; + +#define SKELETAL_NAME_MAX 32 + +extern Skeletalfmheader_t g_skelModel; + +void ClearSkeletalModel(); +void GrabModelTransform(char *frame); +void GrabSkeletalFrame(char *frame); +void GrabReferencedFrame(char *frame); + +// Reference Stuff +#define NUM_REFERENCES 8 + +#define REF_MAX_POINTS 16 +#define REF_MAX_STRLEN 32 + +// We're assuming no more than 16 reference points, with no more than 32 characters in the name +extern char RefPointNameList[REF_MAX_POINTS][REF_MAX_STRLEN]; +extern int RefPointNum; + +#endif diff --git a/tools/quake2/qdata_heretic2/qdata.c b/tools/quake2/qdata_heretic2/qdata.c new file mode 100644 index 00000000..d8c1612a --- /dev/null +++ b/tools/quake2/qdata_heretic2/qdata.c @@ -0,0 +1,736 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qdata.h" + +void TK_Init(); + +qboolean g_compress_pak; +qboolean g_release; // don't grab, copy output data to new tree +qboolean g_pak; // if true, copy to pak instead of release +char g_releasedir[1024]; // c:\quake2\baseq2, etc +qboolean g_archive; // don't grab, copy source data to new tree +qboolean do3ds; +char g_only[256]; // if set, only grab this cd +qboolean g_skipmodel; // set true when a cd is not g_only +int g_forcemodel = MODEL_AUTO; +qboolean g_verbose = false; +qboolean g_allow_newskin = true; +qboolean g_ignoreTriUV = false; +qboolean g_publishOutput = false; + +char *ext_3ds = "3ds"; +char *ext_tri= "tri"; +char *trifileext; + +char g_materialFile[256] = "none"; // default for Heretic2 +char *g_outputDir; +extern char *g_publishDir; + +extern qboolean g_nomkdir; + +/* +======================================================= + + PAK FILES + +======================================================= +*/ + +unsigned Com_BlockChecksum (void *buffer, int length); + +typedef struct +{ + char name[56]; + int filepos, filelen; +} packfile_t; + +typedef struct +{ + char id[4]; + int dirofs; + int dirlen; +} packheader_t; + +packfile_t pfiles[16384]; +FILE *pakfile; +packfile_t *pf; +packheader_t pakheader; + + + +/* +============== +BeginPak +============== +*/ +void BeginPak (char *outname) +{ + if (!g_pak) + return; + + pakfile = SafeOpenWrite (outname); + + // leave space for header + SafeWrite (pakfile, &pakheader, sizeof(pakheader)); + + pf = pfiles; +} + + +/* +============== +ReleaseFile + +Filename should be gamedir reletive. +Either copies the file to the release dir, or adds it to +the pak file. +============== +*/ +void ReleaseFile (char *filename) +{ + int len; + byte *buf; + char source[1024]; + char dest[1024]; + + if (!g_release) + return; + + sprintf (source, "%s%s", gamedir, filename); + + if (!g_pak) + { // copy it + sprintf (dest, "%s/%s", g_releasedir, filename); + printf ("copying to %s\n", dest); + QCopyFile (source, dest); + return; + } + + // pak it + printf ("paking %s\n", filename); + if (strlen(filename) >= sizeof(pf->name)) + Error ("Filename too long for pak: %s", filename); + + len = LoadFile (source, (void **)&buf); + + // segment moved to old.c + + strcpy (pf->name, filename); + pf->filepos = LittleLong(ftell(pakfile)); + pf->filelen = LittleLong(len); + pf++; + + SafeWrite (pakfile, buf, len); + + free (buf); +} + + +/* +============== +FinishPak +============== +*/ +void FinishPak (void) +{ + int dirlen; + int d; + int i; + unsigned checksum; + + if (!g_pak) + return; + + pakheader.id[0] = 'P'; + pakheader.id[1] = 'A'; + pakheader.id[2] = 'C'; + pakheader.id[3] = 'K'; + dirlen = (byte *)pf - (byte *)pfiles; + pakheader.dirofs = LittleLong(ftell(pakfile)); + pakheader.dirlen = LittleLong(dirlen); + + checksum = Com_BlockChecksum ( (void *)pfiles, dirlen ); + + SafeWrite (pakfile, pfiles, dirlen); + + i = ftell (pakfile); + + fseek (pakfile, 0, SEEK_SET); + SafeWrite (pakfile, &pakheader, sizeof(pakheader)); + fclose (pakfile); + + d = pf - pfiles; + printf ("%i files packed in %i bytes\n",d, i); + printf ("checksum: 0x%x\n", checksum); +} + + +/* +=============== +Cmd_File + +This is only used to cause a file to be copied during a release +build (default.cfg, maps, etc) +=============== +*/ +void Cmd_File (void) +{ + GetScriptToken (false); + ReleaseFile (token); +} + +/* +=============== +PackDirectory_r + +=============== +*/ +#ifdef _WIN32 +#include "io.h" +void PackDirectory_r (char *dir) +{ + struct _finddata_t fileinfo; + int handle; + char dirstring[1024]; + char filename[1024]; + + sprintf (dirstring, "%s%s/*.*", gamedir, dir); + + handle = _findfirst (dirstring, &fileinfo); + if (handle == -1) + return; + + do + { + sprintf (filename, "%s/%s", dir, fileinfo.name); + if (fileinfo.attrib & _A_SUBDIR) + { // directory + if (fileinfo.name[0] != '.') // don't pak . and .. + PackDirectory_r (filename); + continue; + } + // copy or pack the file + ReleaseFile (filename); + } while (_findnext( handle, &fileinfo ) != -1); + + _findclose (handle); +} +#else + +#include +#ifdef NeXT +#include +#else +#include +#endif + +void PackDirectory_r (char *dir) +{ +#ifdef NeXT + struct direct **namelist, *ent; +#else + struct dirent **namelist, *ent; +#endif + int count; + struct stat st; + int i; + int len; + char fullname[1024]; + char dirstring[1024]; + char *name; + + sprintf (dirstring, "%s%s", gamedir, dir); + count = scandir(dirstring, &namelist, NULL, NULL); + + for (i=0 ; id_name; + + if (name[0] == '.') + continue; + + sprintf (fullname, "%s/%s", dir, name); + sprintf (dirstring, "%s%s/%s", gamedir, dir, name); + + if (stat (dirstring, &st) == -1) + Error ("fstating %s", pf->name); + if (st.st_mode & S_IFDIR) + { // directory + PackDirectory_r (fullname); + continue; + } + + // copy or pack the file + ReleaseFile (fullname); + } +} +#endif + + +/* +=============== +Cmd_Dir + +This is only used to cause a directory to be copied during a +release build (sounds, etc) +=============== +*/ +void Cmd_Dir (void) +{ + GetScriptToken (false); + PackDirectory_r (token); +} + +//======================================================================== + +#define MAX_RTEX 16384 +int numrtex; +char rtex[MAX_RTEX][64]; + +void ReleaseTexture (char *name) +{ + int i; + char path[1024]; + + for (i=0 ; i width height\n"); + } + return 0; + } +*/ else if (!strcmpi(argv[i], "-genskin")) + { + i++; + if (i < argc-3) + { + GenSkin(argv[i],argv[i+1],atol(argv[i+2]),atol(argv[i+3])); + } + else + { + printf("qdata -genskin \n"); + } + return 0; + + } + else if (!strcmpi(argv[i], "-noopts")) + { + g_no_opimizations = true; + printf("not performing optimizations\n"); + } + else if (!strcmpi(argv[i], "-md2")) + { + g_forcemodel = MODEL_MD2; + } + else if (!strcmpi(argv[i], "-fm")) + { + g_forcemodel = MODEL_FM; + } + else if (!strcmpi(argv[i], "-verbose")) + { + g_verbose = true; + } + else if (!strcmpi(argv[i], "-oldskin")) + { + g_allow_newskin = false; + } + else if (!strcmpi(argv[i], "-ignoreUV")) + { + g_ignoreTriUV = true; + } + else if (!strcmpi(argv[i], "-publish")) + { + g_publishOutput = true; + } + else if (!strcmpi(argv[i], "-nomkdir")) + { + g_nomkdir = true; + } + else if (argv[i][0] == '-') + Error ("Unknown option \"%s\"", argv[i]); + else + break; + } + + if (i >= argc) + { + Error ("usage: qdata [-archive ]\n" + " [-release ]\n" + " [-base ]\n" + " [-compress]\n" + " [-pak ]\n" + " [-only ]\n" + " [-keypress]\n" + " [-3ds]\n" + " [-materialfile ]\n" + " [-noopts]\n" + " [-md2]\n" + " [-fm]\n" + " [-verbose]\n" + " [-ignoreUV]\n" + " [-oldskin]\n" + " [-publish]\n" + " [-nomkdir]\n" + " file.qdt\n" + "or\n" + " qdata -genskin "); + } + + if (do3ds) + trifileext = ext_3ds; + else + trifileext = ext_tri; + + for ( ; i +#include +#include +#include +#include + +#include "cmdlib.h" +#include "inout.h" +#include "scriplib.h" +#include "mathlib.h" +#include "trilib.h" +#include "lbmlib.h" +#include "her2_threads.h" +#include "l3dslib.h" +#include "bspfile.h" + +#ifndef _WIN32 +#define stricmp strcasecmp +#define strcmpi strcasecmp +#endif + + +#define MODEL_AUTO 0 +#define MODEL_MD2 1 +#define MODEL_FM 2 + +// Model cover functions (to allow the forcing of a model type) +void MODELCMD_Modelname (int modeltype); +void MODELCMD_Cd (int modeltype); +void MODELCMD_Origin (int modeltype); +void MODELCMD_Jointed (int modeltype); +void MODELCMD_Cluster (int modeltype); +void MODELCMD_Base (int modeltype); +void MODELCMD_BaseST (int modeltype); +void MODELCMD_ScaleUp (int modeltype); +void MODELCMD_Frame (int modeltype); +void MODELCMD_Skin (int modeltype); +void MODELCMD_Skinsize (int modeltype); +void MODELCMD_Skeleton (int modeltype); +void MODELCMD_SkeletalFrame (int modeltype); +void MODELCMD_BeginGroup(int modeltype); +void MODELCMD_EndGroup(int modeltype); +void MODELCMD_Referenced(int modeltype); +void MODELCMD_NodeOrder(int modeltype); + +void Cmd_Modelname (void); +void Cmd_Base (void); +void Cmd_Cd (void); +void Cmd_Origin (void); +void Cmd_ScaleUp (void); +void Cmd_Frame (void); +void Cmd_Skin (void); +void Cmd_Skinsize (void); +void FinishModel (void); +void Cmd_Cluster (void); + +// Flexible Models +//void Cmd_FMModelname (void); +void Cmd_FMBase (qboolean GetST); +void Cmd_FMCd (void); +//void Cmd_FMOrigin (void); +void Cmd_FMCluster(); +void Cmd_FMSkeleton(); +//void Cmd_FMScaleUp (void); +void Cmd_FMFrame (void); +void Cmd_FMSkeletalFrame(); +void Cmd_FMSkin (void); +//void Cmd_FMSkinsize (void); +void Cmd_FMBeginGroup(void); +void Cmd_FMEndGroup(void); +void Cmd_FMReferenced(); +void Cmd_FMNodeOrder(void); +void FMFinishModel (void); +void GenSkin(char *ModelFile, char *OutputName, int Width, int Height); +void NewGen (char *ModelFile, char *OutputName, int width, int height); + + +void Cmd_Inverse16Table( void ); + +void Cmd_SpriteName (void); +void Cmd_Load (void); +void Cmd_SpriteFrame (void); +void Cmd_Sprdir (void); +void FinishSprite (void); + +void Cmd_Grab (void); +void Cmd_Raw (void); +void Cmd_Mip (void); +void Cmd_Environment (void); +void Cmd_Colormap (void); + +void Cmd_File (void); +void Cmd_Dir (void); +void Cmd_StartWad (void); +void Cmd_EndWad (void); +void Cmd_Mippal (void); +void Cmd_Mipdir (void); +void Cmd_Alphalight (void); + +void Cmd_Picdir (void); +void Cmd_Pic (void); + +void Cmd_Bookdir (void); +void Cmd_Book (void); + +void Cmd_TextureMix (void); + +void Cmd_Video (void); + +//void RemapZero (byte *pixels, byte *palette, int width, int height); + +void ReleaseFile (char *filename); + +extern byte *byteimage, *lbmpalette; +extern int byteimagewidth, byteimageheight; +extern qboolean TrueColorImage; +extern unsigned *longimage; +extern int longimagewidth, longimageheight; + +extern qboolean g_release; // don't grab, copy output data to new tree +extern char g_releasedir[1024]; // c:\quake2\baseq2, etc +extern qboolean g_archive; // don't grab, copy source data to new tree +extern qboolean do3ds; +extern char g_only[256]; // if set, only grab this cd +extern qboolean g_skipmodel; // set true when a cd is not g_only +extern qboolean g_no_opimizations; +extern int g_forcemodel; +extern qboolean g_verbose; +extern qboolean g_allow_newskin; +extern qboolean g_ignoreTriUV; //from qdata.c +extern qboolean g_dokeypress; + +extern char *trifileext; + +extern char g_materialFile[256]; + +extern unsigned total_x; +extern unsigned total_y; +extern unsigned total_textures; + +miptex_t *CreateMip(byte *data, unsigned width, unsigned height, byte *palette, int *FinalSize, qboolean mip); +miptex32_t *CreateMip32(unsigned *data, unsigned width, unsigned height, int *FinalSize, qboolean mip); diff --git a/tools/quake2/qdata_heretic2/qdata3_heretic2.vcproj b/tools/quake2/qdata_heretic2/qdata3_heretic2.vcproj new file mode 100644 index 00000000..2eea9286 --- /dev/null +++ b/tools/quake2/qdata_heretic2/qdata3_heretic2.vcproj @@ -0,0 +1,329 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/quake2/qdata_heretic2/resource.h b/tools/quake2/qdata_heretic2/resource.h new file mode 100644 index 00000000..14e2fb5b --- /dev/null +++ b/tools/quake2/qdata_heretic2/resource.h @@ -0,0 +1,18 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by Script1.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/tools/quake2/qdata_heretic2/script1.aps b/tools/quake2/qdata_heretic2/script1.aps new file mode 100644 index 0000000000000000000000000000000000000000..6650b67c6de853163570a230cecb91f4c3d4d645 GIT binary patch literal 18732 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 literal 0 HcmV?d00001 diff --git a/tools/quake2/qdata_heretic2/script1.rc b/tools/quake2/qdata_heretic2/script1.rc new file mode 100644 index 00000000..70e36910 --- /dev/null +++ b/tools/quake2/qdata_heretic2/script1.rc @@ -0,0 +1,115 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON DISCARDABLE "icon1.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "Heavily modified from original ID tool\0" + VALUE "CompanyName", "Raven Software\0" + VALUE "FileDescription", "qdata\0" + VALUE "FileVersion", "2.0\0" + VALUE "InternalName", "qdata\0" + VALUE "LegalCopyright", "Copyright © 1998\0" + VALUE "OriginalFilename", "qdata.exe\0" + VALUE "ProductName", "Raven Software qdata\0" + VALUE "ProductVersion", "2.0\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/tools/quake2/qdata_heretic2/sprites.c b/tools/quake2/qdata_heretic2/sprites.c new file mode 100644 index 00000000..bd9d8bf2 --- /dev/null +++ b/tools/quake2/qdata_heretic2/sprites.c @@ -0,0 +1,349 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "qdata.h" + +#define MAX_SPRFRAMES MAX_MD2SKINS + +dsprite_t sprite; +dsprframe_t frames[MAX_SPRFRAMES]; + +byte *byteimage, *lbmpalette; +int byteimagewidth, byteimageheight; + +qboolean TrueColorImage; +unsigned *longimage; +int longimagewidth, longimageheight; + +char spritename[1024]; + + +void FinishSprite (void); +void Cmd_Spritename (void); + +char spr_prefix[1024]; +char pic_prefix[1024]; + +extern char *g_outputDir; + + +/* +============== +FinishSprite +============== +*/ +void FinishSprite (void) +{ + FILE *spriteouthandle; + int i, curframe; + dsprite_t spritetemp; + char savename[1024]; + + if (sprite.numframes == 0) + return; + + if (!strlen(spritename)) + Error ("Didn't name sprite file"); + + sprintf (savename, "%sSprites/%s/%s.sp2", g_outputDir, spr_prefix, spritename); + + if (g_release) + { + char name[1024]; + + sprintf (name, "%s.sp2", spritename); + ReleaseFile (name); + spritename[0] = 0; // clear for a new sprite + sprite.numframes = 0; + return; + } + + + printf ("saving in %s\n", savename); + CreatePath (savename); + spriteouthandle = SafeOpenWrite (savename); + + +// +// write out the sprite header +// + spritetemp.ident = LittleLong (IDSPRITEHEADER); + spritetemp.version = LittleLong (SPRITE_VERSION); + spritetemp.numframes = LittleLong (sprite.numframes); + + SafeWrite (spriteouthandle, &spritetemp, 12); + +// +// write out the frames +// + curframe = 0; + + for (i=0 ; i 256) || (h > 256)) + Error ("Sprite has a dimension longer than 256"); + + xh = xl+w; + yh = yl+h; + + if (sprite.numframes >= MAX_SPRFRAMES) + Error ("Too many frames; increase MAX_SPRFRAMES\n"); + + pframe = &frames[sprite.numframes]; + pframe->width = w; + pframe->height = h; + pframe->origin_x = ox; + pframe->origin_y = oy; + + if (g_release) + { + ReleaseFile (pframe->name); + return; + } + + if (TrueColorImage) + { + sprintf (filename, "%ssprites/%s/%s_%i.m32", g_outputDir, spr_prefix, spritename, sprite.numframes); + sprintf (pframe->name, "%s/%s_%i.m32", spr_prefix, spritename, sprite.numframes); + + if (g_release) + return; // textures are only released by $maps + + xh = xl+w; + yh = yl+h; + + if (xl >= longimagewidth || xh > longimagewidth || + yl >= longimageheight || yh > longimageheight) + { + Error ("line %i: bad clip dimmensions (%d,%d) (%d,%d) > image (%d,%d)", scriptline, xl,yl,w,h,longimagewidth,longimageheight); + } + + sourcel = longimage + (yl*longimagewidth) + xl; + destl = bufferl; + linedelta = (longimagewidth - w); + + for (y=yl ; ycontents = 0; + qtex32->value = 0; + strcpy(qtex32->name, pframe->name); + // + // write it out + // + printf ("writing %s\n", filename); + SaveFile (filename, (byte *)qtex32, size); + + free (qtex32); + } + else + { + sprintf (filename, "%ssprites/%s/%s_%i.m8", g_outputDir, spr_prefix, spritename, sprite.numframes); + sprintf (pframe->name, "%s/%s_%i.m8", spr_prefix, spritename, sprite.numframes); + + if (g_release) + return; // textures are only released by $maps + + xh = xl+w; + yh = yl+h; + + if (xl >= byteimagewidth || xh > byteimagewidth || + yl >= byteimageheight || yh > byteimageheight) + { + Error ("line %i: bad clip dimmensions (%d,%d) (%d,%d) > image (%d,%d)", scriptline, xl,yl,w,h,byteimagewidth,byteimageheight); + } + + source = byteimage + yl*byteimagewidth + xl; + dest = buffer; + linedelta = byteimagewidth - w; + + for (y=yl ; yflags = 0; + qtex->contents = 0; + qtex->value = 0; + strcpy(qtex->name, pframe->name); + // + // write it out + // + printf ("writing %s\n", filename); + SaveFile (filename, (byte *)qtex, size); + + free (qtex); + } + + sprite.numframes++; +} + + +/* +============== +Cmd_SpriteName +============== +*/ +void Cmd_SpriteName (void) +{ + if (sprite.numframes) + FinishSprite (); + + GetScriptToken (false); + strcpy (spritename, token); + memset (&sprite, 0, sizeof(sprite)); + memset (&frames, 0, sizeof(frames)); +} + + +/* +=============== +Cmd_Sprdir +=============== +*/ +void Cmd_Sprdir (void) +{ + char filename[1024]; + + GetScriptToken (false); + strcpy (spr_prefix, token); + // create the directory if needed + sprintf (filename, "%sSprites", g_outputDir); + Q_mkdir (filename); + sprintf (filename, "%sSprites/%s", g_outputDir, spr_prefix); + Q_mkdir (filename); +} + + diff --git a/tools/quake2/qdata_heretic2/svdcmp.c b/tools/quake2/qdata_heretic2/svdcmp.c new file mode 100644 index 00000000..04ac7230 --- /dev/null +++ b/tools/quake2/qdata_heretic2/svdcmp.c @@ -0,0 +1,490 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include +#include +#include + +static double at,bt,ct; +#define PYTHAG(a,b) ((at=fabs(a)) > (bt=fabs(b)) ? \ + (ct=bt/at,at*sqrt(1.0+ct*ct)) : (bt ? (ct=at/bt,bt*sqrt(1.0+ct*ct)): 0.0)) + + static double maxarg1,maxarg2; +#define MAX(a,b) (maxarg1=(a),maxarg2=(b),(maxarg1) > (maxarg2) ?\ + (maxarg1) : (maxarg2)) +#define SIGN(a,b) ((b) >= 0.0 ? fabs(a) : -fabs(a)) + +void ntrerror(char *s) +{ + printf("%s\n",s); + exit(1); +} + +double *allocVect(int sz) +{ + double *ret; + + ret = calloc(sizeof(double), (size_t)sz); + return ret; +} + +void freeVect(double *ret) +{ + free(ret); +} + +double **allocMatrix(int r,int c) +{ + double **ret; + + ret = calloc(sizeof(double), (size_t)(r*c)); + return ret; +} + +void freeMatrix(double **ret,int r) +{ + free(ret); +} + +void svdcmp(double** a, int m, int n, double* w, double** v) +{ + int flag,i,its,j,jj,k,l,nm; + double c,f,h,s,x,y,z; + double anorm=0.0,g=0.0,scale=0.0; + double *rv1; + void nrerror(); + + if (m < n) ntrerror("SVDCMP: You must augment A with extra zero rows"); + rv1=allocVect(n); + for (i=1;i<=n;i++) { + l=i+1; + rv1[i]=scale*g; + g=s=scale=0.0; + if (i <= m) { + for (k=i;k<=m;k++) scale += fabs(a[k][i]); + if (scale) { + for (k=i;k<=m;k++) { + a[k][i] /= scale; + s += a[k][i]*a[k][i]; + } + f=a[i][i]; + g = -SIGN(sqrt(s),f); + h=f*g-s; + a[i][i]=f-g; + if (i != n) { + for (j=l;j<=n;j++) { + for (s=0.0,k=i;k<=m;k++) s += a[k][i]*a[k][j]; + f=s/h; + for (k=i;k<=m;k++) a[k][j] += f*a[k][i]; + } + } + for (k=i;k<=m;k++) a[k][i] *= scale; + } + } + w[i]=scale*g; + g=s=scale=0.0; + if (i <= m && i != n) { + for (k=l;k<=n;k++) scale += fabs(a[i][k]); + if (scale) { + for (k=l;k<=n;k++) { + a[i][k] /= scale; + s += a[i][k]*a[i][k]; + } + f=a[i][l]; + g = -SIGN(sqrt(s),f); + h=f*g-s; + a[i][l]=f-g; + for (k=l;k<=n;k++) rv1[k]=a[i][k]/h; + if (i != m) { + for (j=l;j<=m;j++) { + for (s=0.0,k=l;k<=n;k++) s += a[j][k]*a[i][k]; + for (k=l;k<=n;k++) a[j][k] += s*rv1[k]; + } + } + for (k=l;k<=n;k++) a[i][k] *= scale; + } + } + anorm=MAX(anorm,(fabs(w[i])+fabs(rv1[i]))); + } + for (i=n;i>=1;i--) { + if (i < n) { + if (g) { + for (j=l;j<=n;j++) + v[j][i]=(a[i][j]/a[i][l])/g; + for (j=l;j<=n;j++) { + for (s=0.0,k=l;k<=n;k++) s += a[i][k]*v[k][j]; + for (k=l;k<=n;k++) v[k][j] += s*v[k][i]; + } + } + for (j=l;j<=n;j++) v[i][j]=v[j][i]=0.0; + } + v[i][i]=1.0; + g=rv1[i]; + l=i; + } + for (i=n;i>=1;i--) { + l=i+1; + g=w[i]; + if (i < n) + for (j=l;j<=n;j++) a[i][j]=0.0; + if (g) { + g=1.0/g; + if (i != n) { + for (j=l;j<=n;j++) { + for (s=0.0,k=l;k<=m;k++) s += a[k][i]*a[k][j]; + f=(s/a[i][i])*g; + for (k=i;k<=m;k++) a[k][j] += f*a[k][i]; + } + } + for (j=i;j<=m;j++) a[j][i] *= g; + } else { + for (j=i;j<=m;j++) a[j][i]=0.0; + } + ++a[i][i]; + } + for (k=n;k>=1;k--) { + for (its=1;its<=30;its++) { + flag=1; + for (l=k;l>=1;l--) { + nm=l-1; + if (fabs(rv1[l])+anorm == anorm) { + flag=0; + break; + } + if (fabs(w[nm])+anorm == anorm) break; + } + if (flag) { + c=0.0; + s=1.0; + for (i=l;i<=k;i++) { + f=s*rv1[i]; + if (fabs(f)+anorm != anorm) { + g=w[i]; + h=PYTHAG(f,g); + w[i]=h; + h=1.0/h; + c=g*h; + s=(-f*h); + for (j=1;j<=m;j++) { + y=a[j][nm]; + z=a[j][i]; + a[j][nm]=y*c+z*s; + a[j][i]=z*c-y*s; + } + } + } + } + z=w[k]; + if (l == k) { + if (z < 0.0) { + w[k] = -z; + for (j=1;j<=n;j++) v[j][k]=(-v[j][k]); + } + break; + } + if (its == 30) ntrerror("No convergence in 30 SVDCMP iterations"); + x=w[l]; + nm=k-1; + y=w[nm]; + g=rv1[nm]; + h=rv1[k]; + f=((y-z)*(y+z)+(g-h)*(g+h))/(2.0*h*y); + g=PYTHAG(f,1.0); + f=((x-z)*(x+z)+h*((y/(f+SIGN(g,f)))-h))/x; + c=s=1.0; + for (j=l;j<=nm;j++) { + i=j+1; + g=rv1[i]; + y=w[i]; + h=s*g; + g=c*g; + z=PYTHAG(f,h); + rv1[j]=z; + c=f/z; + s=h/z; + f=x*c+g*s; + g=g*c-x*s; + h=y*s; + y=y*c; + for (jj=1;jj<=n;jj++) { + x=v[jj][j]; + z=v[jj][i]; + v[jj][j]=x*c+z*s; + v[jj][i]=z*c-x*s; + } + z=PYTHAG(f,h); + w[j]=z; + if (z) { + z=1.0/z; + c=f*z; + s=h*z; + } + f=(c*g)+(s*y); + x=(c*y)-(s*g); + for (jj=1;jj<=m;jj++) { + y=a[jj][j]; + z=a[jj][i]; + a[jj][j]=y*c+z*s; + a[jj][i]=z*c-y*s; + } + } + rv1[l]=0.0; + rv1[k]=f; + w[k]=x; + } + } + freeVect(rv1); +} + + + +void svbksb(double** u, double* w, double** v,int m, int n, double* b, double* x) +{ + int jj,j,i; + double s,*tmp; + tmp=allocVect(n); + for (j=1;j<=n;j++) + { + s=0.0; + if (w[j]) + { + for (i=1;i<=m;i++) + s += u[i][j]*b[i]; + s /= w[j]; + } + tmp[j]=s; + } + for (j=1;j<=n;j++) + { + s=0.0; + for (jj=1;jj<=n;jj++) + s += v[j][jj]*tmp[jj]; + x[j]=s; + } + freeVect(tmp); +} + +#undef SIGN +#undef MAX +#undef PYTHAG + + +#if 1 +void DOsvd(float *a,float *res,float *comp,float *values,int nframes,int framesize,int compressedsize) +{ + int usedfs; + int *remap; + int i,j; + double **da; + double **v; + double *w; + int DOFerr; + float mx; + int bestat; + + if (nframes>framesize) + usedfs=nframes; + else + usedfs=framesize; + + da=allocMatrix(usedfs,nframes); + v=allocMatrix(nframes,nframes); + w=allocVect(nframes); + + DOFerr = 0; //false + for (i=0;imx) + { + mx=(float) fabs(w[i+1]); + bestat=i; + } + } + + if(mx>0) + { + remap[bestat]=j; + } + else + { + DOFerr = 1; //true + } + } + + if(DOFerr) + { + printf("Warning: To many degrees of freedom! File size may increase\n"); + + for (i=0;imx) + { + mx=fabs(w[i+1]); + bestat=i; + } + } + assert(mx>-.5f); + remap[bestat]=j; + } + // josh **DO NOT** put your dof>nframes mod here + for (i=0;i=3); + base[0]=pnts[0]; + base[1]=pnts[1]; + base[2]=pnts[2]; + for (i=1;i> 5 ) & 63 ) << 2; + b[0] = ( ( color >> 11 ) & 31 ) << 3; + + for ( i = 0; i < 256; i++ ) + { + r[1] = ( d_8to24table[i] >> 0 ) & 0xFF; + g[1] = ( d_8to24table[i] >> 8 ) & 0xFF; + b[1] = ( d_8to24table[i] >> 16 ) & 0xFF; + + d = ( r[1] - r[0] ) * ( r[1] - r[0] ) + + ( g[1] - g[0] ) * ( g[1] - g[0] ) + + ( b[1] - b[0] ) * ( b[1] - b[0] ); + + if ( d < closest_distance_so_far ) + { + closest_distance_so_far = d; + closest_so_far = i; + } + } + + return closest_so_far; +} +*/ + +extern byte BestColor( int, int, int, int, int ); + +void Inverse16_BuildTable( void ) +{ + int i; + + /* + ** create the 16-to-8 table + */ + for ( i = 0; i < 65536; i++ ) + { + int r = i & 31; + int g = ( i >> 5 ) & 63; + int b = ( i >> 11 ) & 31; + + r <<= 3; + g <<= 2; + b <<= 3; + + inverse16to8table[i] = BestColor( r, g, b, 0, 255 ); + } +} + +void Alphalight_Thread (int i) +{ + int j; + float r, g, b; + float mr, mg, mb, ma; + float distortion, bestdistortion; + float v; + + r = (i>>10) * (1.0/16); + g = ((i>>5)&31) * (1.0/16); + b = (i&31) * (1.0/16); + + bestdistortion = 999999; + for (j=0 ; j<16*16*16*16 ; j++) + { + mr = (j>>12) * (1.0/16); + mg = ((j>>8)&15) * (1.0/16); + mb = ((j>>4)&15) * (1.0/16); + ma = (j&15) * (1.0/16); + + v = r * 0.5 - (mr*ma + 0.5*(1.0-ma)); + distortion = v*v; + v = g * 0.5 - (mg*ma + 0.5*(1.0-ma)); + distortion += v*v; + v = b * 0.5 - (mb*ma + 0.5*(1.0-ma)); + distortion += v*v; + + distortion *= 1.0 + ma*4; + + if (distortion < bestdistortion) + { + bestdistortion = distortion; + alphamap[i] = j; + } + } +} + +void Cmd_Alphalight (void) +{ + char savename[1024]; + + GetScriptToken (false); + + if (g_release) + { + ReleaseFile (token); + return; + } + + sprintf (savename, "%s%s", gamedir, token); + printf ("Building alphalight table...\n"); + + RunThreadsOnIndividual (32*32*32, true, Alphalight_Thread); + + SaveFile (savename, (byte *)alphamap, sizeof(alphamap)); +} + + +void Cmd_Inverse16Table( void ) +{ + char savename[1024]; + + if ( g_release ) + { + sprintf (savename, "pics/16to8.dat"); + ReleaseFile( savename ); + return; + } + + sprintf (savename, "%spics/16to8.dat", gamedir); + printf ("Building inverse 16-to-8 table...\n"); + + Inverse16_BuildTable(); + + SaveFile( savename, (byte *) inverse16to8table, sizeof( inverse16to8table ) ); +} diff --git a/tools/quake2/qdata_heretic2/tmix.c b/tools/quake2/qdata_heretic2/tmix.c new file mode 100644 index 00000000..ac1b2f30 --- /dev/null +++ b/tools/quake2/qdata_heretic2/tmix.c @@ -0,0 +1,698 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qdata.h" +#include "flex.h" + +#define MAXFILES 2048 + +typedef struct +{ + int x; + int y; + int w; + int h; + int cw; + int ch; + int rw; + int index; + int depth; + int col; + int baseline; + char name[128]; +} Coords; + +int filenum; +int valid; +Coords in[MAXFILES]; +Coords out; +char outscript[256]; +char sourcedir[256]; +char outusage[256]; +char root[32]; + +int destsize = 0; +byte *pixels = NULL; // Buffer to load image +long *outpixels = NULL; // Buffer to store combined textures +long *usagemap = NULL; // Buffer of usage map +void *bmptemp = NULL; // Buffer of usage map +byte *map = NULL; + +int xcharsize; +int ycharsize; +int dosort = 0; +int missed = 0; +int overlap = 0; +int nobaseline = 0; +int percent; + +////////////////////////////////////////////////// +// Setting the char based usage map // +////////////////////////////////////////////////// + +byte TryPlace(Coords *coord) +{ + int x, y; + byte entry = 0; + byte *mapitem; + + mapitem = map + (coord->x / xcharsize) + ((coord->y / ycharsize) * out.cw); + + for (y = 0; y < coord->ch; y++, mapitem += out.cw - coord->cw) + { + for (x = 0; x < coord->cw; x++) + { + if (entry |= *mapitem++ & 8) + { + return(entry); + } + } + } + return(entry); +} + +void SetMap(Coords *coord) +{ + int x, y; + byte *mapitem; + + mapitem = map + (coord->x / xcharsize) + ((coord->y / ycharsize) * out.cw); + + for (y = 0; y < coord->ch; y++, mapitem += out.cw - coord->cw) + for (x = 0; x < coord->cw; x++) + *mapitem++ |= 8; +} + +////////////////////////////////////////////////// +// Setting the pixel based usage map // +////////////////////////////////////////////////// + +void CheckOverlap(Coords *coord) +{ + int x; + int y; + long *dest; + + x = coord->x; + y = coord->y; + + dest = (long *)(usagemap + x + (y * out.w)); + + for (y = 0; y < coord->h; y++, dest += out.w - coord->w) + { + for (x = 0; x < coord->w; x++) + { + if (*dest++) + { + overlap++; + return; + } + } + } +} + +void SetUsageMap(Coords *coord) +{ + int x; + int y; + long *dest; + + x = coord->x; + y = coord->y; + + dest = (long *)(usagemap + x + (y * out.w)); + + for (y = 0; y < coord->h; y++, dest += out.w - coord->w) + { + for (x = 0; x < coord->w; x++) + { + *dest++ = coord->col; + } + } +} + +////////////////////////////////////////////////// +// Flips the BMP image to the correct way up // +////////////////////////////////////////////////// + +void CopyLine(byte *dest, byte *src, int size) +{ + int x; + + for (x = 0; x < size; x++) + *dest++ = *src++; +} + +/****************************************************/ +/* Printing headers etc */ +/****************************************************/ + +void RemoveLeading(char *name) +{ + int i; + char temp[128]; + + for(i = strlen(name) - 1; i > 0; i--) + { + if((name[i] == '\\') || (name[i] == '/')) + { + strcpy(temp, name + i + 1); + strcpy(name, temp); + return; + } + } +} + +void RemoveExt(char *name) +{ + while ((*name != '.') && *name) + name++; + *name = 0; +} + +/****************************************************/ +/* Misc calcualtions */ +/****************************************************/ + +int TotalArea() +{ + int i; + int total = 0; + + for (i = 0; i < (filenum + 2); i++) + total += in[i].w * in[i].h; + + return(total); +} + +/****************************************************/ +/* Setup and checking of all info */ +/****************************************************/ + +void InitVars() +{ + filenum = 0; + valid = 0; + dosort = 0; + missed = 0; + overlap = 0; + nobaseline = 0; + + memset(outscript, 0, sizeof(outscript)); + memset(outscript, 0, sizeof(sourcedir)); + memset(outscript, 0, sizeof(outusage)); + memset(outscript, 0, sizeof(root)); + + memset(in, 0, sizeof(in)); + memset(&out, 0, sizeof(out)); +} +void Cleanup() +{ + if (pixels) + free(pixels); + if (usagemap) + free(usagemap); + if (outpixels) + free(outpixels); + if (bmptemp) + free(bmptemp); + if (map) + free(map); +} + +typedef struct glxy_s +{ + float xl, yt, xr, yb; + int w, h, baseline; +} glxy_t; + +int SaveScript(char *name) +{ + FILE *fp; + int i, j; + glxy_t buff; + + if(fp = fopen(name, "wb")) + { + for (j = 0; j < filenum; j++) + { + for (i = 0; i < filenum; i++) + { + if (in[i].index == j) + { + if (in[i].depth) + { + buff.xl = (float)in[i].x / (float)out.w; + buff.yt = (float)in[i].y / (float)out.h; + buff.xr = ((float)in[i].w + (float)in[i].x) / (float)out.w; + buff.yb = ((float)in[i].h + (float)in[i].y) / (float)out.h; + buff.w = in[i].w; + buff.h = in[i].h; + buff.baseline = in[i].baseline; + } + else + { + memset(&buff, 0, sizeof(glxy_t)); + } + fwrite(&buff, 1, sizeof(glxy_t), fp); + i = filenum; + } + } + } + fclose(fp); + return(true); + } + else + return(false); +} + +int GetScriptInfo(char *name) +{ + FILE *fp; + char buffer[256]; + char tempbuff[256]; + char delims[] = {" \t,\n"}; + + printf("Opening script file %s.\n", name); + + if (fp = fopen(name, "r")) + { + while(fgets(buffer, 256, fp)) + { + if (strncmp(buffer, "//", 2) && strncmp(buffer, "\n", 1)) + { + strupr(buffer); + strcpy(tempbuff, buffer); + if (strcmp(strtok(tempbuff, delims), "OUTPUT") == 0) + { + strcpy(out.name, strtok(NULL, delims)); + strlwr(out.name); + } + + strcpy(tempbuff, buffer); + if (strcmp(strtok(tempbuff, delims), "SOURCEDIR") == 0) + { + strcpy(tempbuff, strtok(NULL, delims)); + strcpy(sourcedir, ExpandPathAndArchive(tempbuff)); + } + + strcpy(tempbuff, buffer); + if (strcmp(strtok(tempbuff, delims), "DOSORT") == 0) + dosort = 1; + + strcpy(tempbuff, buffer); + if (strcmp(strtok(tempbuff, delims), "XCHARSIZE") == 0) + xcharsize = strtol(strtok(NULL, delims), NULL, 0); + + strcpy(tempbuff, buffer); + if (strcmp(strtok(tempbuff, delims), "YCHARSIZE") == 0) + ycharsize = strtol(strtok(NULL, delims), NULL, 0); + + strcpy(tempbuff, buffer); + if (strcmp(strtok(tempbuff, delims), "OUTSCRIPT") == 0) + { + strcpy(outscript, strtok(NULL, delims)); + strlwr(outscript); + } + + strcpy(tempbuff, buffer); + if (strcmp(strtok(tempbuff, delims), "OUTUSAGE") == 0) + strcpy(outusage, strtok(NULL, delims)); + + strcpy(tempbuff, buffer); + if (strcmp(strtok(tempbuff, delims), "POS") == 0) + { + out.w = strtol(strtok(NULL, delims), NULL, 0); + out.h = strtol(strtok(NULL, delims), NULL, 0); + } + + strcpy(tempbuff, buffer); + if (strcmp(strtok(tempbuff, delims), "FILE") == 0) + { + strcpy(in[filenum].name, strtok(NULL, delims)); + in[filenum].x = strtol(strtok(NULL, delims), NULL, 0); + in[filenum].y = strtol(strtok(NULL, delims), NULL, 0); + in[filenum].col = strtol(strtok(NULL, delims), NULL, 0); + filenum++; + } + } + } + fclose(fp); + return(true); + } + else + { + printf("ERROR : Could not open script file.\n"); + return(false); + } +} + +int CheckVars() +{ + int i; + + if (out.name[0] == 0) + { + printf("ERROR : No output name specified.\n"); + return(false); + } + if ((out.w <= 0) || (out.h <= 0)) + { + printf("ERROR : Invalid VRAM coordinates.\n"); + return(false); + } + if (filenum == 0) + { + printf("ERROR : No input files specified.\n"); + return(false); + } + for (i = 0; i < filenum; i++) + if (in[i].name[0] == 0) + { + printf("ERROR : Input filename invalid.\n"); + return(false); + } + return(true); +} + +// Makes sure texture is totally within the output area + +int CheckCoords(Coords *coord) +{ + if ((coord->x + coord->w) > out.w) + return(false); + if ((coord->y + coord->h) > out.h) + return(false); + + return(true); +} +// Gets the width, height, palette width and palette height of each BMP file + +int GetFileDimensions() +{ + int i; + int width, height; + char name[128]; + + for (i = 0; i < filenum; i++) + { + in[i].index = i; + + strcpy(name, sourcedir); + strcat(name, in[i].name); + printf("Getting file dimensions, file : %s \r", in[i].name); + if(FileExists(name)) + { + LoadAnyImage(name, NULL, NULL, &width, &height); + in[i].depth = 32; + in[i].rw = width; + in[i].w = width; // makes it width in + in[i].h = height; + in[i].cw = (in[i].w + (xcharsize - 1)) / xcharsize; + in[i].ch = (in[i].h + (ycharsize - 1)) / ycharsize; + + if (!CheckCoords(&in[i]) && (in[i].x >= 0)) + { + printf("Error : texture %s out of bounds.\n", in[i].name); + return(false); + } + valid++; + } + else + { + in[i].depth = 0; + in[i].x = -1; + in[i].y = -1; + in[i].w = 0; + in[i].h = 0; + } + } + printf("\n\n"); + return(true); +} + +// Sorts files into order for optimal space finding +// Fixed position ones first, followed by the others in descending size +// The theory being that it is easier to find space for smaller textures. +// size = (width + height) +// For space finding it is easier to place a 32x32 than a 128x2 + +#define WEIGHT 0x8000 + +void Swap(Coords *a, Coords *b) +{ + Coords c; + + c = *a; + *a = *b; + *b = c; +} + +void SortInNames() +{ + int i, j; + int largest, largcount; + int size; + + printf("Sorting filenames by size.\n\n"); + + for (j = 0; j < filenum; j++) + { + largest = -1; + largcount = -1; + + for (i = j; i < filenum; i++) + { + if (in[i].depth) + { + size = in[i].w + in[i].h; + + if ((in[i].x < 0) && (size > largest)) + { + largcount = i; + largest = size; + } + } + } + if ((largcount >= 0) && (largcount != j)) + Swap(&in[j], &in[largcount]); + } +} + +int SetVars(char *name) +{ + if (!GetScriptInfo(name)) + return(false); + + if (!CheckVars()) + return(false); + + destsize = out.w * out.h; + + out.cw = out.w / xcharsize; + out.ch = out.h / ycharsize; + + if ((usagemap = (long *)SafeMalloc(destsize * 4, "")) == NULL) + return(false); + if ((outpixels = (long *)SafeMalloc(destsize * 4, "")) == NULL) + return(false); + if ((bmptemp = (void *)SafeMalloc(destsize * 4, "")) == NULL) + return(false); + if ((map = (byte *)SafeMalloc(destsize / (xcharsize * ycharsize), "")) == NULL) + return(false); + + if (GetFileDimensions() == false) + return(false); + + if (dosort) + SortInNames(); + + return(true); +} +/****************************************************/ +/* Actual copying routines */ +/****************************************************/ + +int FindCoords(Coords *coord) +{ + int tx, ty; + + if (coord->x >= 0) + { + SetMap(coord); + return(true); + } + else + { + for (ty = 0; ty < out.ch; ty++) + { + for (tx = 0; tx < out.cw; tx++) + { + coord->x = (tx * xcharsize); + coord->y = (ty * ycharsize); + + if (CheckCoords(coord) && !TryPlace(coord)) + { + SetMap(coord); + return(true); + } + } + } + } + coord->x = -1; + coord->y = -1; + + return(false); +} + +void CheckBaseline(int i) +{ + int y; + long *pix; + + in[i].baseline = -1; + pix = (long *)pixels; + + for(y = 0; y < in[i].h; y++, pix += in[i].w) + { + if((*pix & 0x00ffffff) == 0x00ff00ff) + { + in[i].baseline = y; + break; + } + } + pix = (long *)pixels; + for(y = 0; y < in[i].w * in[i].h; y++, pix++) + { + if((*pix & 0x00ffffff) == 0x00ff00ff) + { + *pix = 0; + } + } + + if(in[i].baseline == -1) + { + printf("\nERROR : %s has no baseline\n", in[i].name); + nobaseline++; + } +} + +void CopyToMain32(Coords *coord) +{ + int x; + int y; + long *source; + long *dest; + + x = coord->x; + y = coord->y; + + source = (long *)pixels; + dest = (long *)(outpixels + x + (y * out.w)); + + for (y = 0; y < coord->h; y++, dest += out.w - coord->w) + { + for (x = 0; x < coord->w; x++) + { + *dest++ = *source++; + } + } +} + +void CreateMain() +{ + int i, count; + int width, height; + char name[128]; + + for (i = 0, count = 0; i < filenum; i++) + { + if (in[i].depth) + { + printf("\rProcessing %d of %d (%d missed, %d overlapping, %d nobase)\r", count + 1, valid, missed, overlap, nobaseline); + count++; + if (!FindCoords(&in[i])) + missed++; + else + { + strcpy(name, sourcedir); + strcat(name, in[i].name); + LoadAnyImage(name, &pixels, NULL, &width, &height); + CheckBaseline(i); + CheckOverlap(&in[i]); + CopyToMain32(&in[i]); + SetUsageMap(&in[i]); + } + } + } +} + +void Cmd_TextureMix() +{ + miptex32_t *qtex32; + char filename[1024]; + int size; + + InitVars(); + + GetScriptToken (false); + + strcpy(root, token); + RemoveExt(root); + RemoveLeading(root); + + strcpy(filename, ExpandPathAndArchive(token)); + if (SetVars(filename)) + { + // Create combined texture + percent = ((TotalArea() * 100) / (out.w * out.h)); + printf("Total area consumed : %d%%\n", percent); + printf("Texture resolution : %dx%d pixels.\n", xcharsize, ycharsize); + CreateMain(); + + // Save image as m32 + sprintf (filename, "%spics/misc/%s.m32", gamedir, out.name); + qtex32 = CreateMip32((unsigned *)outpixels, out.w, out.h, &size, false); + + qtex32->contents = 0; + qtex32->value = 0; + qtex32->scale_x = 1.0; + qtex32->scale_y = 1.0; + sprintf (qtex32->name, "misc/%s", out.name); + + printf ("\n\nwriting %s\n", filename); + SaveFile (filename, (byte *)qtex32, size); + free (qtex32); + + // Save out script file + sprintf (filename, "%spics/misc/%s.fnt", gamedir, outscript); + printf("Writing %s as script file\n", filename); + if (!SaveScript(filename)) + { + printf("Unable to save output script.\n"); + } + } + printf("Everythings groovy.\n"); + Cleanup(); +} + +// end + diff --git a/tools/quake2/qdata_heretic2/video.c b/tools/quake2/qdata_heretic2/video.c new file mode 100644 index 00000000..181a9735 --- /dev/null +++ b/tools/quake2/qdata_heretic2/video.c @@ -0,0 +1,1149 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// To do + +// Sound error handling (when sound too short) +// rle b4 huffing +// adpcm encoding of sound + +#if 0 +#include "qdata.h" +#include "flex.h" +#include "fc.h" +#include "adpcm.h" + +#define MIN_REPT 15 +#define MAX_REPT 0 +#define HUF_TOKENS (256 + MAX_REPT) + +#define BLOCKSIZE 8 + +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#define SQRT2 1.414213562 + +typedef struct hnode_s +{ + int count; + qboolean used; + int children[2]; +} hnode_t; + +typedef struct +{ + int rate; + int width; + int channels; + int loopstart; + int samples; + int dataofs; // chunk starts this many bytes from file start +} wavinfo_t; + +// These weren`t picked out my ass.... +// They were defined at http://www.rahul.net/jfm/dct.html +// However, I think he plucked them out of his ass..... + +float Quantise[BLOCKSIZE * BLOCKSIZE]; + +float LUT_Quantise[BLOCKSIZE * BLOCKSIZE] = +{ + 16.0F/16.0F, 11.0F/16.0F, 10.0F/16.0F, 16.0F/16.0F, 24.0F/16.0F, 40.0F/16.0F, 51.0F/16.0F, 61.0F/16.0F, + 12.0F/16.0F, 13.0F/16.0F, 14.0F/16.0F, 19.0F/16.0F, 26.0F/16.0F, 58.0F/16.0F, 60.0F/16.0F, 55.0F/16.0F, + 14.0F/16.0F, 13.0F/16.0F, 16.0F/16.0F, 24.0F/16.0F, 40.0F/16.0F, 57.0F/16.0F, 69.0F/16.0F, 56.0F/16.0F, + 14.0F/16.0F, 17.0F/16.0F, 22.0F/16.0F, 29.0F/16.0F, 51.0F/16.0F, 87.0F/16.0F, 80.0F/16.0F, 62.0F/16.0F, + 18.0F/16.0F, 22.0F/16.0F, 37.0F/16.0F, 56.0F/16.0F, 68.0F/16.0F,109.0F/16.0F,103.0F/16.0F, 77.0F/16.0F, + 24.0F/16.0F, 35.0F/16.0F, 55.0F/16.0F, 64.0F/16.0F, 81.0F/16.0F,104.0F/16.0F,113.0F/16.0F, 92.0F/16.0F, + 49.0F/16.0F, 64.0F/16.0F, 78.0F/16.0F, 87.0F/16.0F,103.0F/16.0F,121.0F/16.0F,120.0F/16.0F,101.0F/16.0F, + 72.0F/16.0F, 92.0F/16.0F, 95.0F/16.0F, 98.0F/16.0F,112.0F/16.0F,100.0F/16.0F,103.0F/16.0F, 99.0F/16.0F +}; + +int LUT_ZZ[BLOCKSIZE * BLOCKSIZE] = +{ + 0, + 1, 8, + 16, 9, 2, + 3, 10, 17, 24, + 32, 25, 18, 11, 4, + 5, 12, 19, 26, 33, 40, + 48, 41, 34, 27, 20, 13, 6, + 7, 14, 21, 28, 35, 42, 49, 56, + 57, 50, 43, 36, 29, 22, 15, + 23, 30, 37, 44, 51, 58, + 59, 52, 45, 38, 31, + 39, 46, 53, 60, + 61, 54, 47, + 55, 62, + 63 +}; + +char base[32]; + +byte *soundtrack; + +byte scaled[256][HUF_TOKENS]; +unsigned int charbits1[256][HUF_TOKENS]; +int charbitscount1[256][HUF_TOKENS]; +hnode_t hnodes1[256][HUF_TOKENS * 2]; +int numhnodes1[256]; +int order0counts[256]; +int numhnodes; +hnode_t hnodes[512]; +unsigned charbits[256]; +int charbitscount[256]; + +CineHead_t cinehead; + +byte *data_p; +byte *iff_end; +byte *last_chunk; +byte *iff_data; +int iff_chunk_len; + +float dctbase[BLOCKSIZE][BLOCKSIZE]; +float red[BLOCKSIZE * BLOCKSIZE]; +float green[BLOCKSIZE * BLOCKSIZE]; +float blue[BLOCKSIZE * BLOCKSIZE]; +float temp[BLOCKSIZE * BLOCKSIZE]; + +wavinfo_t wavinfo; +adpcm_t adpcm; + +/* +=============================================================================== + +WAV loading + +=============================================================================== +*/ + +/* Intel ADPCM step variation table */ +static int indexTable[16] = +{ + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8, +}; + +static int stepsizeTable[89] = +{ + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 +}; + +#if 0 +static void adpcm_decoder(char *indata, short *outdata, int len, adpcm_state_t *state) +{ + signed char *inp; /* Input buffer pointer */ + short *outp; /* output buffer pointer */ + int sign; /* Current adpcm sign bit */ + int delta; /* Current adpcm output value */ + int step; /* Stepsize */ + int valpred; /* Predicted value */ + int vpdiff; /* Current change to valpred */ + int index; /* Current step change index */ + int inputbuffer; /* place to keep next 4-bit value */ + int bufferstep; /* toggle between inputbuffer/input */ + + outp = outdata; + inp = (signed char *)indata; + + valpred = state->valprev; + index = state->index; + step = stepsizeTable[index]; + + bufferstep = 0; + + for(; len > 0; len--) + { + /* Step 1 - get the delta value */ + if (bufferstep) + delta = inputbuffer & 0xf; + else + { + inputbuffer = *inp++; + delta = (inputbuffer >> 4) & 0xf; + } + bufferstep = !bufferstep; + + /* Step 2 - Find new index value (for later) */ + index += indexTable[delta]; + if(index < 0) + index = 0; + if(index > 88) + index = 88; + + /* Step 3 - Separate sign and magnitude */ + sign = delta & 8; + delta = delta & 7; + + /* Step 4 - Compute difference and new predicted value */ + /* + ** Computes 'vpdiff = (delta+0.5)*step/4', but see comment + ** in adpcm_coder. + */ + vpdiff = step >> 3; + if(delta & 4) + vpdiff += step; + if(delta & 2) + vpdiff += step>>1; + if(delta & 1) + vpdiff += step>>2; + + if (sign) + valpred -= vpdiff; + else + valpred += vpdiff; + + /* Step 5 - clamp output value */ + if (valpred > 32767) + valpred = 32767; + else if (valpred < -32768) + valpred = -32768; + + /* Step 6 - Update step value */ + step = stepsizeTable[index]; + + /* Step 7 - Output value */ + *outp++ = valpred; + } + + state->valprev = valpred; + state->index = index; +} +#endif + +void adpcm_coder(short *inp, adpcm_t *adpcm) +{ + int val; /* Current input sample value */ + int sign; /* Current adpcm sign bit */ + int delta; /* Current adpcm output value */ + int diff; /* Difference between val and valprev */ + int step; /* Stepsize */ + int valpred; /* Predicted output value */ + int vpdiff; /* Current change to valpred */ + int index; /* Current step change index */ + int outputbuffer; /* place to keep previous 4-bit value */ + int bufferstep; /* toggle between outputbuffer/output */ + adpcm_state_t *state; + char *outp; + int len; + + state = &adpcm->state; + len = state->count; + outp = adpcm->adpcm; + + valpred = state->in_valprev; + index = state->in_index; + step = stepsizeTable[index]; + + bufferstep = 1; + while(len--) + { + val = *inp++; + + /* Step 1 - compute difference with previous value */ + diff = val - valpred; + sign = (diff < 0) ? 8 : 0; + if (sign) + diff = -diff; + + /* Step 2 - Divide and clamp */ + /* Note: + ** This code *approximately* computes: + ** delta = diff*4/step; + ** vpdiff = (delta+0.5)*step/4; + ** but in shift step bits are dropped. The net result of this is + ** that even if you have fast mul/div hardware you cannot put it to + ** good use since the fixup would be too expensive. + */ + delta = 0; + vpdiff = (step >> 3); + + if (diff >= step) + { + delta = 4; + diff -= step; + vpdiff += step; + } + step >>= 1; + if (diff >= step) + { + delta |= 2; + diff -= step; + vpdiff += step; + } + step >>= 1; + if (diff >= step) + { + delta |= 1; + vpdiff += step; + } + + /* Step 3 - Update previous value */ + if (sign) + valpred -= vpdiff; + else + valpred += vpdiff; + + /* Step 4 - Clamp previous value to 16 bits */ + if (valpred > 32767) + valpred = 32767; + else if (valpred < -32768) + valpred = -32768; + + /* Step 5 - Assemble value, update index and step values */ + delta |= sign; + + index += indexTable[delta]; + if (index < 0) + index = 0; + if (index > 88) + index = 88; + step = stepsizeTable[index]; + + /* Step 6 - Output value */ + if (bufferstep) + outputbuffer = (delta << 4) & 0xf0; + else + *outp++ = (delta & 0x0f) | outputbuffer; + + bufferstep = !bufferstep; + } + + /* Output last step, if needed */ + if(!bufferstep) + *outp++ = outputbuffer; + + state->out_valprev = valpred; + state->out_index = index; +} + +void FindNextChunk(char *name) +{ + while(1) + { + data_p = last_chunk; + + if(data_p >= iff_end) + { // didn't find the chunk + data_p = NULL; + return; + } + + data_p += 4; + iff_chunk_len = *(long *)data_p; + data_p += 4; + if(iff_chunk_len < 0) + { + data_p = NULL; + return; + } + + data_p -= 8; + last_chunk = data_p + 8 + ((iff_chunk_len + 1) & ~1); + if (!strncmp(data_p, name, 4)) + return; + } +} + +void FindChunk(char *name) +{ + last_chunk = iff_data; + FindNextChunk (name); +} + +void DumpChunks(void) +{ + char str[5]; + + str[4] = 0; + data_p = iff_data; + do + { + memcpy (str, data_p, 4); + data_p += 4; + iff_chunk_len = *(long *)data_p; + data_p += 4; + printf ("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len); + data_p += (iff_chunk_len + 1) & ~1; + } + while(data_p < iff_end); +} + +/* +============ +GetWavinfo +============ +*/ +wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength) +{ + wavinfo_t info; + int i; + int format; + int samples; + + memset(&info, 0, sizeof(info)); + + if (!wav) + return(info); + + iff_data = wav; + iff_end = wav + wavlength; + +// find "RIFF" chunk + FindChunk("RIFF"); + if (!(data_p && !strncmp(data_p + 8, "WAVE", 4))) + { + printf("Missing RIFF/WAVE chunks\n"); + return(info); + } + +// get "fmt " chunk + iff_data = data_p + 12; + + FindChunk("fmt "); + if(!data_p) + { + printf("Missing fmt chunk\n"); + return(info); + } + data_p += 8; + format = *(short *)data_p; + data_p += 2; + if (format != 1) + { + printf("Microsoft PCM format only\n"); + return(info); + } + + info.channels = *(short *)data_p; + data_p += 2; + info.rate = *(long *)data_p; + data_p += 4; + data_p += 6; + info.width = *(short *)data_p / 8; + data_p += 2; + +// get cue chunk + FindChunk("cue "); + if(data_p) + { + data_p += 32; + info.loopstart = *(long *)data_p; + data_p += 4; + +// if the next chunk is a LIST chunk, look for a cue length marker + FindNextChunk ("LIST"); + if(data_p) + { +// this is not a proper parse, but it works with cooledit... + if (!strncmp (data_p + 28, "mark", 4)) + { + data_p += 24; + i = *(long *)data_p; // samples in loop + data_p += 4; + info.samples = info.loopstart + i; + } + } + } + else + info.loopstart = -1; + +// find data chunk + FindChunk("data"); + if (!data_p) + { + printf("Missing data chunk\n"); + return(info); + } + + data_p += 4; + samples = *(long *)data_p; + data_p += 4; + + if (info.samples) + { + if(samples < info.samples) + Error ("Sound %s has a bad loop length", name); + } + else + info.samples = samples; + + info.dataofs = data_p - wav; + return(info); +} + +// ============== +// LoadSoundtrack +// ============== + +void LoadSoundtrack() +{ + char name[1024]; + FILE *f; + int len; + + soundtrack = NULL; + sprintf (name, "%svideo/%s/%s.wav", gamedir, base, base); + printf ("\nLoading sound : %s\n", name); + f = fopen (name, "rb"); + if (!f) + { + printf ("\nNo soundtrack for %s\n", base); + return; + } + len = Q_filelength(f); + soundtrack = SafeMalloc(len, "LoadSoundtrack"); + fread(soundtrack, 1, len, f); + fclose(f); + + wavinfo = GetWavinfo(name, soundtrack, len); + adpcm.state.out_valprev = 0; + adpcm.state.out_index = 0; +} + +// ================== +// WriteSound +// ================== + +int WriteSound(FILE *output, int frame, int numframes) +{ + int start, end; + int count; + int empty = 0; + int width; + char *work; + + width = wavinfo.width * wavinfo.channels; + start = ((frame * wavinfo.rate / 14) + 31) & 0xffffffe0; // start sample + end = (((frame + numframes) * wavinfo.rate / 14) + 31) & 0xffffffe0; // end sample + count = end - start; + + work = soundtrack + wavinfo.dataofs + (start * width); + adpcm.state.count = count * wavinfo.channels; // Number of samples + adpcm.state.in_valprev = adpcm.state.out_valprev; + adpcm.state.in_index = adpcm.state.out_index; + adpcm_coder((short *)work, &adpcm); + WriteHeader(output, FC_SOUND_22KMADPCM, FC_ADPCM_VERSION, (adpcm.state.count / 2) + sizeof(adpcm_state_t), (char *)&adpcm); + return(count / 2); +} +// ============================== +// Basic run length encoder +// ============================== + +char *RLEZZ(char *in, char *out) +{ + int srun; + char count; + int idx = 0; + + while(idx < 64) + { + srun = idx; // Start of run + + while(idx < 63) + { + if(in[LUT_ZZ[idx]] != in[LUT_ZZ[idx + 1]]) + break; + idx++; + } + count = (char)(idx - srun); // count of repeated bytes + + if(!count) + { + while(idx < 63) + { + if(in[LUT_ZZ[idx]] == in[LUT_ZZ[idx + 1]]) + break; + idx++; + } + if(idx == 63) + idx++; + + count = (char)(idx - srun); // count of unique bytes + *out++ = count; + while(count--) + *out++ = in[LUT_ZZ[srun++]]; + } + else + { + *out++ = -(count + 1); + *out++ = in[LUT_ZZ[idx]]; + idx++; + } + } + return(out); +} + +// ============================== +// Discrete Cosine Transformation +// ============================== + +void init_base(float quant) +{ + int y, x; + + for(y = 0; y < BLOCKSIZE; y++) + for(x = 0; x < BLOCKSIZE; x++) + { + if(y == 0) + dctbase[y][x] = 1; + else + dctbase[y][x] = SQRT2 * cos(((x * 2 + 1) * y * M_PI) / (BLOCKSIZE * 2)); + } + + for(y = 0; y < BLOCKSIZE * BLOCKSIZE; y++) + Quantise[y] = LUT_Quantise[y] / quant; +} + +void SplitComponents(byte *src, int width, int height) +{ + int i, j; + float *tr = red; + float *tg = green; + float *tb = blue; + + for(i = 0; i < BLOCKSIZE; i++, src += (width - BLOCKSIZE) * 4) + for(j = 0; j < BLOCKSIZE; j++) + { + *tr++ = ((float)*src++) - 128.0F; + *tg++ = ((float)*src++) - 128.0F; + *tb++ = ((float)*src++) - 128.0F; + src++; + } +} + +void transferH(float *src, float *dst) +{ + int y, dx, dy; + float sum; + float *work; + + for(y = 0; y < BLOCKSIZE; y++, src += BLOCKSIZE) + { + for(dy = 0; dy < BLOCKSIZE; dy++) + { + sum = 0; + work = src; + for(dx = 0; dx < BLOCKSIZE; dx++, work++) + sum += dctbase[dy][dx] * *work; + + *dst++ = sum / BLOCKSIZE; + } + } +} + +void transferV(float *src, float *dst) +{ + int x, dy, fy; + float sum; + float *work; + + for(x = 0; x < BLOCKSIZE; x++, src++, dst++) + { + for(fy = 0; fy < BLOCKSIZE; fy++) + { + sum = 0; + work = src; + for(dy = 0; dy < BLOCKSIZE; dy++, work += BLOCKSIZE) + sum += dctbase[fy][dy] * *work; + + dst[fy * BLOCKSIZE] = sum / BLOCKSIZE; + } + } +} + +char *Combine(byte *dst, float *p, float *q) +{ + int i, j; + byte rlesrc[BLOCKSIZE * BLOCKSIZE]; + int c; + byte *work; + + work = rlesrc; + for(j = 0; j < BLOCKSIZE; j++) + for(i = 0; i < BLOCKSIZE; i++) + { + c = (int)((*p++ / *q++) + 128.5F); + c -= 128; + + if(c < -128) + c = -128; + if(c > 127) + c = 127; + + *work++ = (char)c; + } + + dst = RLEZZ(rlesrc, dst); + return(dst); +} + +char *CombineComponents(char *dst, int width, int height) +{ + dst = Combine(dst, red, Quantise); + dst = Combine(dst, green, Quantise); + dst = Combine(dst, blue, Quantise); + return(dst); +} + +void DCT(cblock_t *out, cblock_t in, int width, int height) +{ + int x, y; + char *cursrc; + char *curdst; + + curdst = out->data; + for(y = 0; y < height; y += BLOCKSIZE) + for(x = 0; x < width; x += BLOCKSIZE) + { + cursrc = in.data + ((y * width) + x) * 4; + SplitComponents(cursrc, width, height); + transferH(red, temp); + transferV(temp, red); + transferH(green, temp); + transferV(temp, green); + transferH(blue, temp); + transferV(temp, blue); + curdst = CombineComponents(curdst, width, height); + } + out->count = curdst - out->data; +} + +// ================== +// BuildChars1 +// ================== + +void BuildChars1(int prev, int nodenum, unsigned bits, int bitcount) +{ + hnode_t *node; + + if(nodenum < HUF_TOKENS) + { + if (bitcount > 32) + Error("bitcount > 32"); + charbits1[prev][nodenum] = bits; + charbitscount1[prev][nodenum] = bitcount; + return; + } + + node = &hnodes1[prev][nodenum]; + bits <<= 1; + BuildChars1(prev, node->children[0], bits, bitcount+1); + bits |= 1; + BuildChars1(prev, node->children[1], bits, bitcount+1); +} + +// ================== +// SmallestNode1 +// ================== + +int SmallestNode1(hnode_t *hnodes, int numhnodes) +{ + int i; + int best, bestnode; + + best = 99999999; + bestnode = -1; + for(i = 0; i < numhnodes; i++) + { + if(hnodes[i].used) + continue; + if(!hnodes[i].count) + continue; + if(hnodes[i].count < best) + { + best = hnodes[i].count; + bestnode = i; + } + } + + if (bestnode == -1) + return(-1); + + hnodes[bestnode].used = true; + return(bestnode); +} + +// ================== +// BuildTree1 +// ================== + +void BuildTree1(int prev) +{ + hnode_t *node, *nodebase; + int numhnodes; + + // build the nodes + numhnodes = HUF_TOKENS; + nodebase = hnodes1[prev]; + while(1) + { + node = &nodebase[numhnodes]; + + // pick two lowest counts + node->children[0] = SmallestNode1 (nodebase, numhnodes); + if (node->children[0] == -1) + break; // no more + + node->children[1] = SmallestNode1 (nodebase, numhnodes); + if (node->children[1] == -1) + break; + + node->count = nodebase[node->children[0]].count + + nodebase[node->children[1]].count; + numhnodes++; + } + numhnodes1[prev] = numhnodes-1; + BuildChars1 (prev, numhnodes-1, 0, 0); +} + +// ================== +// Huffman1_Count +// ================== + +void Huffman1_Count(cblock_t in) +{ + int i; + int prev; + int v; + int rept; + + prev = 0; + for(i = 0; i < in.count; i++) + { + v = in.data[i]; + order0counts[v]++; + hnodes1[prev][v].count++; + prev = v; + + for(rept = 1; (i + rept < in.count) && (rept < MAX_REPT); rept++) + if(in.data[i+rept] != v) + break; + if(rept > MIN_REPT) + { + hnodes1[prev][255 + rept].count++; + i += rept - 1; + } + } +} + +// ================== +// Huffman1_Build +// ================== + +void Huffman1_Build() +{ + int i, j, v; + int max; + int total; + + for(i = 0; i < 256; i++) + { +// normalize and save the counts + max = 0; + for (j = 0; j < HUF_TOKENS; j++) + { + if (hnodes1[i][j].count > max) + max = hnodes1[i][j].count; + } + if (max == 0) + max = 1; + total = 0; +// easy to overflow 32 bits here! + for(j = 0; j < HUF_TOKENS; j++) + { + v = (hnodes1[i][j].count * (double) 255 + max - 1) / max; + if (v > 255) + Error ("v > 255"); + scaled[i][j] = hnodes1[i][j].count = v; + if (v) + total++; + } + if (total == 1) + { // must have two tokens + if (!scaled[i][0]) + scaled[i][0] = hnodes1[i][0].count = 1; + else + scaled[i][1] = hnodes1[i][1].count = 1; + } + BuildTree1 (i); + } +} + +// ================== +// Huffman1 +// Order 1 compression with pre-built table +// ================== + +cblock_t Huffman1(cblock_t in) +{ + int i; + int outbits, c; + unsigned bits; + byte *out_p; + cblock_t out; + int prev; + int v; + int rept; + + out_p = out.data = SafeMalloc((in.count * 2) + 1024 + 4, "Huffman"); + memset(out_p, 0, (in.count * 2) + 1024 + 4); + + // leave space for compressed count + out_p += 4; + // write count + *(long *)out_p = in.count; + out_p += 4; + + // write bits + outbits = 0; + prev = 0; + for(i = 0; i < in.count; i++) + { + v = in.data[i]; + + c = charbitscount1[prev][v]; + bits = charbits1[prev][v]; + if (!c) + Error ("!bits"); + while (c) + { + c--; + if (bits & (1 << c)) + out_p[outbits>>3] |= 1 << (outbits & 7); + outbits++; + } + + prev = v; + // check for repeat encodes + for(rept = 1; (i + rept < in.count) && (rept < MAX_REPT); rept++) + if(in.data[i + rept] != v) + break; + if (rept > MIN_REPT) + { + c = charbitscount1[prev][255 + rept]; + bits = charbits1[prev][255 + rept]; + if (!c) + Error ("!bits"); + while (c) + { + c--; + if(bits & (1 << c)) + out_p[outbits >> 3] |= 1 << (outbits & 7); + outbits++; + } + i += rept - 1; + } + } + out_p += (outbits + 7) >> 3; + out.count = out_p - out.data; + + out_p = out.data; + *(long *)out_p = out.count; + return(out); +} +// =================== +// LoadFrame +// =================== + +void LoadFrame(cblock_t *out, char *base, int frame) +{ + cblock_t in; + int width, height; + char name[1024]; + FILE *f; + + in.data = NULL; + in.count = -1; + sprintf (name, "%svideo/%s/%s%04i.tga", gamedir, base, base, frame); + + f = fopen(name, "rb"); + if (!f) + { + out->data = NULL; + return; + } + fclose (f); + + LoadTGA(name, &in.data, &width, &height); + if((width != cinehead.Width) || (height != cinehead.Height)) + { + free(in.data); + printf("Invalid picture size\n"); + out->data = NULL; + return; + } + out->data = SafeMalloc(width * height * 3, "LoadFrame"); // rle could possibly expand file so this not 100% safe (however DCT should force a lot of compression) + DCT(out, in, width, height); + free(in.data); +} + +// ================================== +// Cmd_Video +// +// video +// ================================== + +void Cmd_Video() +{ + char savename[256]; + char name[256]; + FILE *output; + int frame; + int width, height; + cblock_t in, huffman; + int size; + float dctconst; + int maxsize, ssize; + int min_rle_size, warnings; + int ave_image, ave_sound; + + GetScriptToken(false); + strcpy(base, token); + if (g_release) + return; + + GetScriptToken(false); + dctconst = atof(token); + GetScriptToken(false); + maxsize = atoi(token); + + sprintf (savename, "%svideo/%s.cin", gamedir, base); + + // clear stuff + memset(charbits1, 0, sizeof(charbits1)); + memset(charbitscount1, 0, sizeof(charbitscount1)); + memset(hnodes1, 0, sizeof(hnodes1)); + memset(numhnodes1, 0, sizeof(numhnodes1)); + memset(order0counts, 0, sizeof(order0counts)); + + // load the entire sound wav file if present + LoadSoundtrack(); + + cinehead.SndRate = wavinfo.rate; + cinehead.SndWidth = wavinfo.width; + cinehead.SndChannels = wavinfo.channels; + + sprintf(name, "%svideo/%s/%s0000.tga", gamedir, base, base); + printf("Loading sequence : %s\n", name); + printf("DCT constant : %f\n", dctconst); + + LoadTGA (name, NULL, &width, &height); + + output = fopen (savename, "wb"); + if (!output) + Error ("Can't open %s", savename); + + if((width % BLOCKSIZE) || (height % BLOCKSIZE)) + Error("Width and height must be a multiple of %d", BLOCKSIZE); + + cinehead.Width = width; + cinehead.Height = height; + init_base(dctconst); + + // build the dictionary + printf("Counting : "); + min_rle_size = 0; + for (frame = 0; ; frame++) + { + printf("."); + LoadFrame(&in, base, frame); + if(!in.data) + break; + Huffman1_Count(in); + if(in.count > min_rle_size) + min_rle_size = in.count; + free(in.data); + } + printf ("\n"); + cinehead.NumFrames = frame; + printf("Num Frames : %d\n", frame); + cinehead.MaxRleSize = (min_rle_size + 0x1f) & 0xfffffe0; + cinehead.MaxSndSize = ((4 * wavinfo.rate * wavinfo.channels / 14) + 0x1f) & 0xffffffe0; + + WriteHeader(output, FC_HEADER_NAME, FC_HEADER_VERSION, sizeof(CineHead_t), &cinehead); + + // build nodes and write counts + Huffman1_Build(); + WriteHeader(output, FC_HUFFBITS_NAME, FC_HUFFBITS_VERSION, sizeof(scaled), scaled); + WriteHeader(output, FC_QUANT_NAME, FC_QUANT_VERSION, sizeof(Quantise), Quantise); + + ave_image = 0; + ave_sound = 0; + warnings = 0; + // compress it with the dictionary + if(soundtrack) + { + ssize = WriteSound(output, frame, 4); + ave_sound += ssize; + } + + for (frame = 0; frame < cinehead.NumFrames; frame++) + { + // save some sound samples + printf ("Packing : ", frame); + LoadFrame(&in, base, frame); + + // save the image + huffman = Huffman1(in); + printf ("%d bytes rle, %d bytes huffman", in.count, huffman.count); + size = (huffman.count + 3) & 0xfffffffc; // round up to longwords + if(size > maxsize) + { + printf(" ** WARNING **"); + warnings++; + } + printf("\n"); + ave_image += huffman.count; + + WriteHeader(output, FC_IMAGE_NAME, FC_IMAGE_VERSION, size, huffman.data); + if(soundtrack) + { + ssize = WriteSound(output, frame + 4, 1); + ave_sound += ssize; + } + + free (in.data); + free (huffman.data); + } + printf("\nTotal size: %d (headers + %d image + %d sound)\n", ftell(output), ave_image, ave_sound); + printf("Data rate : %d bytes per sec (image and sound)\n", (ave_image + ave_sound) / cinehead.NumFrames); + printf("Cin created ok with %d warnings.\n", warnings); + fclose (output); + + if (soundtrack) + free (soundtrack); +} +#endif + +void Cmd_Video() +{ +} + +// end + diff --git a/tools/quake3/common/aselib.c b/tools/quake3/common/aselib.c new file mode 100644 index 00000000..980e7266 --- /dev/null +++ b/tools/quake3/common/aselib.c @@ -0,0 +1,965 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "aselib.h" +#include "inout.h" + +#include +#include +#include + +#define MAX_ASE_MATERIALS 32 +#define MAX_ASE_OBJECTS 64 +#define MAX_ASE_ANIMATIONS 32 +#define MAX_ASE_ANIMATION_FRAMES 512 + +#define VERBOSE( x ) { if ( ase.verbose ) { Sys_Printf x ; } } + +typedef struct +{ + float x, y, z; + float nx, ny, nz; + float s, t; +} aseVertex_t; + +typedef struct +{ + float s, t; +} aseTVertex_t; + +typedef int aseFace_t[3]; + +typedef struct +{ + int numFaces; + int numVertexes; + int numTVertexes; + + int timeValue; + + aseVertex_t *vertexes; + aseTVertex_t *tvertexes; + aseFace_t *faces, *tfaces; + + int currentFace, currentVertex; +} aseMesh_t; + +typedef struct +{ + int numFrames; + aseMesh_t frames[MAX_ASE_ANIMATION_FRAMES]; + + int currentFrame; +} aseMeshAnimation_t; + +typedef struct +{ + char name[128]; +} aseMaterial_t; + +/* +** contains the animate sequence of a single surface +** using a single material +*/ +typedef struct +{ + char name[128]; + + int materialRef; + int numAnimations; + + aseMeshAnimation_t anim; + +} aseGeomObject_t; + +typedef struct +{ + int numMaterials; + aseMaterial_t materials[MAX_ASE_MATERIALS]; + aseGeomObject_t objects[MAX_ASE_OBJECTS]; + + char *buffer; + char *curpos; + int len; + + int currentObject; + qboolean verbose; + qboolean grabAnims; + +} ase_t; + +static char s_token[1024]; +static ase_t ase; +static char gl_filename[1024]; + +static void ASE_Process( void ); +static void ASE_FreeGeomObject( int ndx ); + +#if defined (__linux__) || defined (__APPLE__) + +static char* strlwr (char* string) +{ + char *cp; + for (cp = string; *cp; ++cp) + { + if ('A' <= *cp && *cp <= 'Z') + *cp += 'a' - 'A'; + } + + return string; +} + +#endif + +/* +** ASE_Load +*/ +void ASE_Load( const char *filename, qboolean verbose, qboolean grabAnims ) +{ + FILE *fp = fopen( filename, "rb" ); + + if ( !fp ) + Error( "File not found '%s'", filename ); + + memset( &ase, 0, sizeof( ase ) ); + + ase.verbose = verbose; + ase.grabAnims = grabAnims; + ase.len = Q_filelength( fp ); + + ase.curpos = ase.buffer = safe_malloc( ase.len ); + + Sys_Printf( "Processing '%s'\n", filename ); + + if ( fread( ase.buffer, ase.len, 1, fp ) != 1 ) + { + fclose( fp ); + Error( "fread() != -1 for '%s'", filename ); + } + + fclose( fp ); + + strcpy(gl_filename, filename); + + ASE_Process(); +} + +/* +** ASE_Free +*/ +void ASE_Free( void ) +{ + int i; + + for ( i = 0; i < ase.currentObject; i++ ) + { + ASE_FreeGeomObject( i ); + } +} + +/* +** ASE_GetNumSurfaces +*/ +int ASE_GetNumSurfaces( void ) +{ + return ase.currentObject; +} + +/* +** ASE_GetSurfaceName +*/ +const char *ASE_GetSurfaceName( int which ) +{ + aseGeomObject_t *pObject = &ase.objects[which]; + + if ( !pObject->anim.numFrames ) + return 0; + + return pObject->name; +} + +/* +** ASE_GetSurfaceAnimation +** +** Returns an animation (sequence of polysets) +*/ +polyset_t *ASE_GetSurfaceAnimation( int which, int *pNumFrames, int skipFrameStart, int skipFrameEnd, int maxFrames ) +{ + aseGeomObject_t *pObject = &ase.objects[which]; + polyset_t *psets; + int numFramesInAnimation; + int numFramesToKeep; + int i, f; + + if ( !pObject->anim.numFrames ) + return 0; + + if ( pObject->anim.numFrames > maxFrames && maxFrames != -1 ) + { + numFramesInAnimation = maxFrames; + } + else + { + numFramesInAnimation = pObject->anim.numFrames; + if ( maxFrames != -1 ) + Sys_Printf( "WARNING: ASE_GetSurfaceAnimation maxFrames > numFramesInAnimation\n" ); + } + + if ( skipFrameEnd != -1 ) + numFramesToKeep = numFramesInAnimation - ( skipFrameEnd - skipFrameStart + 1 ); + else + numFramesToKeep = numFramesInAnimation; + + *pNumFrames = numFramesToKeep; + + psets = calloc( sizeof( polyset_t ) * numFramesToKeep, 1 ); + + for ( f = 0, i = 0; i < numFramesInAnimation; i++ ) + { + int t; + aseMesh_t *pMesh = &pObject->anim.frames[i]; + + if ( skipFrameStart != -1 ) + { + if ( i >= skipFrameStart && i <= skipFrameEnd ) + continue; + } + + strcpy( psets[f].name, pObject->name ); + strcpy( psets[f].materialname, ase.materials[pObject->materialRef].name ); + + psets[f].triangles = calloc( sizeof( triangle_t ) * pObject->anim.frames[i].numFaces, 1 ); + psets[f].numtriangles = pObject->anim.frames[i].numFaces; + + for ( t = 0; t < pObject->anim.frames[i].numFaces; t++ ) + { + int k; + + for ( k = 0; k < 3; k++ ) + { + psets[f].triangles[t].verts[k][0] = pMesh->vertexes[pMesh->faces[t][k]].x; + psets[f].triangles[t].verts[k][1] = pMesh->vertexes[pMesh->faces[t][k]].y; + psets[f].triangles[t].verts[k][2] = pMesh->vertexes[pMesh->faces[t][k]].z; + + if ( pMesh->tvertexes && pMesh->tfaces ) + { + psets[f].triangles[t].texcoords[k][0] = pMesh->tvertexes[pMesh->tfaces[t][k]].s; + psets[f].triangles[t].texcoords[k][1] = pMesh->tvertexes[pMesh->tfaces[t][k]].t; + } + + } + } + + f++; + } + + return psets; +} + +static void ASE_FreeGeomObject( int ndx ) +{ + aseGeomObject_t *pObject; + int i; + + pObject = &ase.objects[ndx]; + + for ( i = 0; i < pObject->anim.numFrames; i++ ) + { + if ( pObject->anim.frames[i].vertexes ) + { + free( pObject->anim.frames[i].vertexes ); + } + if ( pObject->anim.frames[i].tvertexes ) + { + free( pObject->anim.frames[i].tvertexes ); + } + if ( pObject->anim.frames[i].faces ) + { + free( pObject->anim.frames[i].faces ); + } + if ( pObject->anim.frames[i].tfaces ) + { + free( pObject->anim.frames[i].tfaces ); + } + } + + memset( pObject, 0, sizeof( *pObject ) ); +} + +static aseMesh_t *ASE_GetCurrentMesh( void ) +{ + aseGeomObject_t *pObject; + + if ( ase.currentObject >= MAX_ASE_OBJECTS ) + { + Error( "Too many GEOMOBJECTs" ); + return 0; // never called + } + + pObject = &ase.objects[ase.currentObject]; + + if ( pObject->anim.currentFrame >= MAX_ASE_ANIMATION_FRAMES ) + { + Error( "Too many MESHes" ); + return 0; + } + + return &pObject->anim.frames[pObject->anim.currentFrame]; +} + +static int CharIsTokenDelimiter( int ch ) +{ + if ( ch <= 32 ) + return 1; + return 0; +} + +static int ASE_GetToken( qboolean restOfLine ) +{ + int i = 0; + + if ( ase.buffer == 0 ) + return 0; + + if ( ( ase.curpos - ase.buffer ) == ase.len ) + return 0; + + // skip over crap + while ( ( ( ase.curpos - ase.buffer ) < ase.len ) && + ( *ase.curpos <= 32 ) ) + { + ase.curpos++; + } + + while ( ( ase.curpos - ase.buffer ) < ase.len ) + { + s_token[i] = *ase.curpos; + + ase.curpos++; + i++; + + if ( ( CharIsTokenDelimiter( s_token[i-1] ) && !restOfLine ) || + ( ( s_token[i-1] == '\n' ) || ( s_token[i-1] == '\r' ) ) ) + { + s_token[i-1] = 0; + break; + } + } + + s_token[i] = 0; + + return 1; +} + +static void ASE_ParseBracedBlock( void (*parser)( const char *token ) ) +{ + int indent = 0; + + while ( ASE_GetToken( qfalse ) ) + { + if ( !strcmp( s_token, "{" ) ) + { + indent++; + } + else if ( !strcmp( s_token, "}" ) ) + { + --indent; + if ( indent == 0 ) + break; + else if ( indent < 0 ) + Error( "Unexpected '}'" ); + } + else + { + if ( parser ) + parser( s_token ); + } + } +} + +static void ASE_SkipEnclosingBraces( void ) +{ + int indent = 0; + + while ( ASE_GetToken( qfalse ) ) + { + if ( !strcmp( s_token, "{" ) ) + { + indent++; + } + else if ( !strcmp( s_token, "}" ) ) + { + indent--; + if ( indent == 0 ) + break; + else if ( indent < 0 ) + Error( "Unexpected '}'" ); + } + } +} + +static void ASE_SkipRestOfLine( void ) +{ + ASE_GetToken( qtrue ); +} + +static void ASE_KeyMAP_DIFFUSE( const char *token ) +{ + char fullpath[1024], bitmap[1024], modeldir[1024]; + char filename[1024]; + int i = 0, count; + + strcpy(filename, gl_filename); + + if ( !strcmp( token, "*BITMAP" ) ) + { + ASE_GetToken( qfalse ); + + // the purpose of this whole chunk of code below is to extract the relative path + // from a full path in the ASE + + strcpy( bitmap, s_token + 1 ); + if ( strchr( bitmap, '"' ) ) + *strchr( bitmap, '"' ) = 0; + + /* convert backslash to slash */ + while ( bitmap[i] ) + { + if ( bitmap[i] == '\\' ) + bitmap[i] = '/'; + i++; + } + + /* remove filename from path */ + for( i=strlen(filename); i>0; i--) + { + if(filename[i] == '/') + { + filename[i] = '\0'; + break; + } + } + + /* replaces a relative path with a full path */ + if(bitmap[0] == '.' && bitmap[1] == '.' && bitmap[2] == '/') + { + while(bitmap[0] == '.' && bitmap[1] == '.' && bitmap[2] == '/') + { + /* remove last item from path */ + for( i=strlen(filename); i>0; i--) + { + if(filename[i] == '/') + { + filename[i] = '\0'; + break; + } + } + strcpy(bitmap, &bitmap[3]); + } + strcat(filename, "/"); + strcat(filename, bitmap); + strcpy(bitmap, filename); + } + + if ( strstr( bitmap, gamedir ) ) + { + strcpy( ase.materials[ase.numMaterials].name, strstr( bitmap, gamedir ) + strlen( gamedir ) ); + Sys_Printf("material name: \'%s\'\n", strstr( bitmap, gamedir ) + strlen( gamedir ) ); + } + else + { + sprintf( ase.materials[ase.numMaterials].name, "(not converted: '%s')", bitmap ); + Sys_Printf( "WARNING: illegal material name '%s'\n", bitmap ); + } + } + else + { + } +} + +static void ASE_KeyMATERIAL( const char *token ) +{ + if ( !strcmp( token, "*MAP_DIFFUSE" ) ) + { + ASE_ParseBracedBlock( ASE_KeyMAP_DIFFUSE ); + } + else + { + } +} + +static void ASE_KeyMATERIAL_LIST( const char *token ) +{ + if ( !strcmp( token, "*MATERIAL_COUNT" ) ) + { + ASE_GetToken( qfalse ); + VERBOSE( ( "..num materials: %s\n", s_token ) ); + if ( atoi( s_token ) > MAX_ASE_MATERIALS ) + { + Error( "Too many materials!" ); + } + ase.numMaterials = 0; + } + else if ( !strcmp( token, "*MATERIAL" ) ) + { + VERBOSE( ( "..material %d ", ase.numMaterials ) ); + ASE_ParseBracedBlock( ASE_KeyMATERIAL ); + ase.numMaterials++; + } +} + +static void ASE_KeyMESH_VERTEX_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_VERTEX" ) ) + { + ASE_GetToken( qfalse ); // skip number + + ASE_GetToken( qfalse ); + pMesh->vertexes[pMesh->currentVertex].y = atof( s_token ); + + ASE_GetToken( qfalse ); + pMesh->vertexes[pMesh->currentVertex].x = -atof( s_token ); + + ASE_GetToken( qfalse ); + pMesh->vertexes[pMesh->currentVertex].z = atof( s_token ); + + pMesh->currentVertex++; + + if ( pMesh->currentVertex > pMesh->numVertexes ) + { + Error( "pMesh->currentVertex >= pMesh->numVertexes" ); + } + } + else + { + Error( "Unknown token '%s' while parsing MESH_VERTEX_LIST", token ); + } +} + +static void ASE_KeyMESH_FACE_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_FACE" ) ) + { + ASE_GetToken( qfalse ); // skip face number + + ASE_GetToken( qfalse ); // skip label + ASE_GetToken( qfalse ); // first vertex + pMesh->faces[pMesh->currentFace][0] = atoi( s_token ); + + ASE_GetToken( qfalse ); // skip label + ASE_GetToken( qfalse ); // second vertex + pMesh->faces[pMesh->currentFace][2] = atoi( s_token ); + + ASE_GetToken( qfalse ); // skip label + ASE_GetToken( qfalse ); // third vertex + pMesh->faces[pMesh->currentFace][1] = atoi( s_token ); + + ASE_GetToken( qtrue ); + +/* + if ( ( p = strstr( s_token, "*MESH_MTLID" ) ) != 0 ) + { + p += strlen( "*MESH_MTLID" ) + 1; + mtlID = atoi( p ); + } + else + { + Error( "No *MESH_MTLID found for face!" ); + } +*/ + + pMesh->currentFace++; + } + else + { + Error( "Unknown token '%s' while parsing MESH_FACE_LIST", token ); + } +} + +static void ASE_KeyTFACE_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_TFACE" ) ) + { + int a, b, c; + + ASE_GetToken( qfalse ); + + ASE_GetToken( qfalse ); + a = atoi( s_token ); + ASE_GetToken( qfalse ); + c = atoi( s_token ); + ASE_GetToken( qfalse ); + b = atoi( s_token ); + + pMesh->tfaces[pMesh->currentFace][0] = a; + pMesh->tfaces[pMesh->currentFace][1] = b; + pMesh->tfaces[pMesh->currentFace][2] = c; + + pMesh->currentFace++; + } + else + { + Error( "Unknown token '%s' in MESH_TFACE", token ); + } +} + +static void ASE_KeyMESH_TVERTLIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_TVERT" ) ) + { + char u[80], v[80], w[80]; + + ASE_GetToken( qfalse ); + + ASE_GetToken( qfalse ); + strcpy( u, s_token ); + + ASE_GetToken( qfalse ); + strcpy( v, s_token ); + + ASE_GetToken( qfalse ); + strcpy( w, s_token ); + + pMesh->tvertexes[pMesh->currentVertex].s = atof( u ); + pMesh->tvertexes[pMesh->currentVertex].t = 1.0f - atof( v ); + + pMesh->currentVertex++; + + if ( pMesh->currentVertex > pMesh->numTVertexes ) + { + Error( "pMesh->currentVertex > pMesh->numTVertexes" ); + } + } + else + { + Error( "Unknown token '%s' while parsing MESH_TVERTLIST", token ); + } +} + +static void ASE_KeyMESH( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*TIMEVALUE" ) ) + { + ASE_GetToken( qfalse ); + + pMesh->timeValue = atoi( s_token ); + VERBOSE( ( ".....timevalue: %d\n", pMesh->timeValue ) ); + } + else if ( !strcmp( token, "*MESH_NUMVERTEX" ) ) + { + ASE_GetToken( qfalse ); + + pMesh->numVertexes = atoi( s_token ); + VERBOSE( ( ".....TIMEVALUE: %d\n", pMesh->timeValue ) ); + VERBOSE( ( ".....num vertexes: %d\n", pMesh->numVertexes ) ); + } + else if ( !strcmp( token, "*MESH_NUMFACES" ) ) + { + ASE_GetToken( qfalse ); + + pMesh->numFaces = atoi( s_token ); + VERBOSE( ( ".....num faces: %d\n", pMesh->numFaces ) ); + } + else if ( !strcmp( token, "*MESH_NUMTVFACES" ) ) + { + ASE_GetToken( qfalse ); + + if ( atoi( s_token ) != pMesh->numFaces ) + { + Error( "MESH_NUMTVFACES != MESH_NUMFACES" ); + } + } + else if ( !strcmp( token, "*MESH_NUMTVERTEX" ) ) + { + ASE_GetToken( qfalse ); + + pMesh->numTVertexes = atoi( s_token ); + VERBOSE( ( ".....num tvertexes: %d\n", pMesh->numTVertexes ) ); + } + else if ( !strcmp( token, "*MESH_VERTEX_LIST" ) ) + { + pMesh->vertexes = calloc( sizeof( aseVertex_t ) * pMesh->numVertexes, 1 ); + pMesh->currentVertex = 0; + VERBOSE( ( ".....parsing MESH_VERTEX_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_VERTEX_LIST ); + } + else if ( !strcmp( token, "*MESH_TVERTLIST" ) ) + { + pMesh->currentVertex = 0; + pMesh->tvertexes = calloc( sizeof( aseTVertex_t ) * pMesh->numTVertexes, 1 ); + VERBOSE( ( ".....parsing MESH_TVERTLIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_TVERTLIST ); + } + else if ( !strcmp( token, "*MESH_FACE_LIST" ) ) + { + pMesh->faces = calloc( sizeof( aseFace_t ) * pMesh->numFaces, 1 ); + pMesh->currentFace = 0; + VERBOSE( ( ".....parsing MESH_FACE_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_FACE_LIST ); + } + else if ( !strcmp( token, "*MESH_TFACELIST" ) ) + { + pMesh->tfaces = calloc( sizeof( aseFace_t ) * pMesh->numFaces, 1 ); + pMesh->currentFace = 0; + VERBOSE( ( ".....parsing MESH_TFACE_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyTFACE_LIST ); + } + else if ( !strcmp( token, "*MESH_NORMALS" ) ) + { + ASE_ParseBracedBlock( 0 ); + } +} + +static void ASE_KeyMESH_ANIMATION( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + // loads a single animation frame + if ( !strcmp( token, "*MESH" ) ) + { + VERBOSE( ( "...found MESH\n" ) ); + assert( pMesh->faces == 0 ); + assert( pMesh->vertexes == 0 ); + assert( pMesh->tvertexes == 0 ); + memset( pMesh, 0, sizeof( *pMesh ) ); + + ASE_ParseBracedBlock( ASE_KeyMESH ); + + if ( ++ase.objects[ase.currentObject].anim.currentFrame == MAX_ASE_ANIMATION_FRAMES ) + { + Error( "Too many animation frames" ); + } + } + else + { + Error( "Unknown token '%s' while parsing MESH_ANIMATION", token ); + } +} + +static void ASE_KeyGEOMOBJECT( const char *token ) +{ + if ( !strcmp( token, "*NODE_NAME" ) ) + { + char *name = ase.objects[ase.currentObject].name; + + ASE_GetToken( qtrue ); + VERBOSE( ( " %s\n", s_token ) ); + strcpy( ase.objects[ase.currentObject].name, s_token + 1 ); + if ( strchr( ase.objects[ase.currentObject].name, '"' ) ) + *strchr( ase.objects[ase.currentObject].name, '"' ) = 0; + + if ( strstr( name, "tag" ) == name ) + { + while ( strchr( name, '_' ) != strrchr( name, '_' ) ) + { + *strrchr( name, '_' ) = 0; + } + while ( strrchr( name, ' ' ) ) + { + *strrchr( name, ' ' ) = 0; + } + } + } + else if ( !strcmp( token, "*NODE_PARENT" ) ) + { + ASE_SkipRestOfLine(); + } + // ignore unused data blocks + else if ( !strcmp( token, "*NODE_TM" ) || + !strcmp( token, "*TM_ANIMATION" ) ) + { + ASE_ParseBracedBlock( 0 ); + } + // ignore regular meshes that aren't part of animation + else if ( !strcmp( token, "*MESH" ) && !ase.grabAnims ) + { +/* + if ( strstr( ase.objects[ase.currentObject].name, "tag_" ) == ase.objects[ase.currentObject].name ) + { + s_forceStaticMesh = true; + ASE_ParseBracedBlock( ASE_KeyMESH ); + s_forceStaticMesh = false; + } +*/ + ASE_ParseBracedBlock( ASE_KeyMESH ); + if ( ++ase.objects[ase.currentObject].anim.currentFrame == MAX_ASE_ANIMATION_FRAMES ) + { + Error( "Too many animation frames" ); + } + ase.objects[ase.currentObject].anim.numFrames = ase.objects[ase.currentObject].anim.currentFrame; + ase.objects[ase.currentObject].numAnimations++; +/* + // ignore meshes that aren't part of animations if this object isn't a + // a tag + else + { + ASE_ParseBracedBlock( 0 ); + } +*/ + } + // according to spec these are obsolete + else if ( !strcmp( token, "*MATERIAL_REF" ) ) + { + ASE_GetToken( qfalse ); + + ase.objects[ase.currentObject].materialRef = atoi( s_token ); + } + // loads a sequence of animation frames + else if ( !strcmp( token, "*MESH_ANIMATION" ) ) + { + if ( ase.grabAnims ) + { + VERBOSE( ( "..found MESH_ANIMATION\n" ) ); + + if ( ase.objects[ase.currentObject].numAnimations ) + { + Error( "Multiple MESH_ANIMATIONS within a single GEOM_OBJECT" ); + } + ASE_ParseBracedBlock( ASE_KeyMESH_ANIMATION ); + ase.objects[ase.currentObject].anim.numFrames = ase.objects[ase.currentObject].anim.currentFrame; + ase.objects[ase.currentObject].numAnimations++; + } + else + { + ASE_SkipEnclosingBraces(); + } + } + // skip unused info + else if ( !strcmp( token, "*PROP_MOTIONBLUR" ) || + !strcmp( token, "*PROP_CASTSHADOW" ) || + !strcmp( token, "*PROP_RECVSHADOW" ) ) + { + ASE_SkipRestOfLine(); + } +} + +static void ConcatenateObjects( aseGeomObject_t *pObjA, aseGeomObject_t *pObjB ) +{ +} + +static void CollapseObjects( void ) +{ + int i; + int numObjects = ase.currentObject; + + for ( i = 0; i < numObjects; i++ ) + { + int j; + + // skip tags + if ( strstr( ase.objects[i].name, "tag" ) == ase.objects[i].name ) + { + continue; + } + + if ( !ase.objects[i].numAnimations ) + { + continue; + } + + for ( j = i + 1; j < numObjects; j++ ) + { + if ( strstr( ase.objects[j].name, "tag" ) == ase.objects[j].name ) + { + continue; + } + if ( ase.objects[i].materialRef == ase.objects[j].materialRef ) + { + if ( ase.objects[j].numAnimations ) + { + ConcatenateObjects( &ase.objects[i], &ase.objects[j] ); + } + } + } + } +} + +/* +** ASE_Process +*/ +static void ASE_Process( void ) +{ + while ( ASE_GetToken( qfalse ) ) + { + if ( !strcmp( s_token, "*3DSMAX_ASCIIEXPORT" ) || + !strcmp( s_token, "*COMMENT" ) ) + { + ASE_SkipRestOfLine(); + } + else if ( !strcmp( s_token, "*SCENE" ) ) + ASE_SkipEnclosingBraces(); + else if ( !strcmp( s_token, "*MATERIAL_LIST" ) ) + { + VERBOSE( ("MATERIAL_LIST\n") ); + + ASE_ParseBracedBlock( ASE_KeyMATERIAL_LIST ); + } + else if ( !strcmp( s_token, "*GEOMOBJECT" ) ) + { + VERBOSE( ("GEOMOBJECT" ) ); + + ASE_ParseBracedBlock( ASE_KeyGEOMOBJECT ); + + if ( strstr( ase.objects[ase.currentObject].name, "Bip" ) || + strstr( ase.objects[ase.currentObject].name, "ignore_" ) ) + { + ASE_FreeGeomObject( ase.currentObject ); + VERBOSE( ( "(discarding BIP/ignore object)\n" ) ); + } + else if ( ( strstr( ase.objects[ase.currentObject].name, "h_" ) != ase.objects[ase.currentObject].name ) && + ( strstr( ase.objects[ase.currentObject].name, "l_" ) != ase.objects[ase.currentObject].name ) && + ( strstr( ase.objects[ase.currentObject].name, "u_" ) != ase.objects[ase.currentObject].name ) && + ( strstr( ase.objects[ase.currentObject].name, "tag" ) != ase.objects[ase.currentObject].name ) && + ase.grabAnims ) + { + VERBOSE( ( "(ignoring improperly labeled object '%s')\n", ase.objects[ase.currentObject].name ) ); + ASE_FreeGeomObject( ase.currentObject ); + } + else + { + if ( ++ase.currentObject == MAX_ASE_OBJECTS ) + { + Error( "Too many GEOMOBJECTs" ); + } + } + } + else if ( s_token[0] ) + { + Sys_Printf( "Unknown token '%s'\n", s_token ); + } + } + + if ( !ase.currentObject ) + Error( "No animation data!" ); + + CollapseObjects(); +} diff --git a/tools/quake3/common/aselib.h b/tools/quake3/common/aselib.h new file mode 100644 index 00000000..299e6053 --- /dev/null +++ b/tools/quake3/common/aselib.h @@ -0,0 +1,31 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "../common/cmdlib.h" +#include "mathlib.h" +#include "polyset.h" + +void ASE_Load( const char *filename, qboolean verbose, qboolean meshanims ); +int ASE_GetNumSurfaces( void ); +polyset_t *ASE_GetSurfaceAnimation( int ndx, int *numFrames, int skipFrameStart, int skipFrameEnd, int maxFrames ); +const char *ASE_GetSurfaceName( int ndx ); +void ASE_Free( void ); diff --git a/tools/quake3/common/bspfile.c b/tools/quake3/common/bspfile.c new file mode 100644 index 00000000..828d0938 --- /dev/null +++ b/tools/quake3/common/bspfile.c @@ -0,0 +1,706 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "cmdlib.h" +#include "mathlib.h" +#include "inout.h" +#include "bspfile.h" +#include "scriplib.h" + +void GetLeafNums (void); + +//============================================================================= + +int bsp_version = Q3_BSP_VERSION; + +int nummodels; +dmodel_t dmodels[MAX_MAP_MODELS]; + +int numShaders; +dshader_t dshaders[MAX_MAP_SHADERS]; + +int entdatasize; +char dentdata[MAX_MAP_ENTSTRING]; + +int numleafs; +dleaf_t dleafs[MAX_MAP_LEAFS]; + +int numplanes; +dplane_t dplanes[MAX_MAP_PLANES]; + +int numnodes; +dnode_t dnodes[MAX_MAP_NODES]; + +int numleafsurfaces; +int dleafsurfaces[MAX_MAP_LEAFFACES]; + +int numleafbrushes; +int dleafbrushes[MAX_MAP_LEAFBRUSHES]; + +int numbrushes; +dbrush_t dbrushes[MAX_MAP_BRUSHES]; + +int numbrushsides; +dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +int numLightBytes; +byte *lightBytes; + +int numGridPoints; +byte *gridData; + +int numVisBytes; +byte visBytes[MAX_MAP_VISIBILITY]; + +int numDrawVerts = 0; +int numDrawVertsBuffer = 0; +drawVert_t *drawVerts = NULL; + +int numDrawIndexes; +int drawIndexes[MAX_MAP_DRAW_INDEXES]; + +int numDrawSurfaces; +int numDrawSurfacesBuffer = 0; +dsurface_t *drawSurfaces = NULL; + +int numFogs; +dfog_t dfogs[MAX_MAP_FOGS]; + +void SetLightBytes(int n) +{ + if(lightBytes != 0) + free(lightBytes); + + numLightBytes = n; + + if(n == 0) + return; + + lightBytes = safe_malloc_info(numLightBytes, "SetLightBytes"); + + memset(lightBytes, 0, numLightBytes); +} + +void SetGridPoints(int n) +{ + if(gridData != 0) + free(gridData); + + numGridPoints = n; + + if(n == 0) + return; + + gridData = safe_malloc_info(numGridPoints * 8, "SetGridPoints"); + + memset(gridData, 0, numGridPoints * 8); +} + +void IncDrawVerts() +{ + numDrawVerts++; + + if(drawVerts == 0) + { + numDrawVertsBuffer = MAX_MAP_DRAW_VERTS / 37; + + drawVerts = safe_malloc_info(sizeof(drawVert_t) * numDrawVertsBuffer, "IncDrawVerts"); + + } + else if(numDrawVerts > numDrawVertsBuffer) + { + numDrawVertsBuffer *= 3; // multiply by 1.5 + numDrawVertsBuffer /= 2; + + if(numDrawVertsBuffer > MAX_MAP_DRAW_VERTS) + numDrawVertsBuffer = MAX_MAP_DRAW_VERTS; + + drawVerts = realloc(drawVerts, sizeof(drawVert_t) * numDrawVertsBuffer); + + if(!drawVerts) + Error( "realloc() failed (IncDrawVerts)"); + } + + memset(drawVerts + (numDrawVerts - 1), 0, sizeof(drawVert_t)); +} + +void SetDrawVerts(int n) +{ + if(drawVerts != 0) + free(drawVerts); + + numDrawVerts = n; + numDrawVertsBuffer = numDrawVerts; + + drawVerts = safe_malloc_info(sizeof(drawVert_t) * numDrawVertsBuffer, "IncDrawVerts"); + + memset(drawVerts, 0, n * sizeof(drawVert_t)); +} + +void SetDrawSurfacesBuffer() +{ + if(drawSurfaces != 0) + free(drawSurfaces); + + numDrawSurfacesBuffer = MAX_MAP_DRAW_SURFS; + + drawSurfaces = safe_malloc_info(sizeof(dsurface_t) * numDrawSurfacesBuffer, "IncDrawSurfaces"); + + memset(drawSurfaces, 0, MAX_MAP_DRAW_SURFS * sizeof(drawVert_t)); +} + +void SetDrawSurfaces(int n) +{ + if(drawSurfaces != 0) + free(drawSurfaces); + + numDrawSurfaces = n; + numDrawSurfacesBuffer = numDrawSurfaces; + + drawSurfaces = safe_malloc_info(sizeof(dsurface_t) * numDrawSurfacesBuffer, "IncDrawSurfaces"); + + memset(drawSurfaces, 0, n * sizeof(drawVert_t)); +} + +void BspFilesCleanup() +{ + if(drawVerts != 0) + free(drawVerts); + if(drawSurfaces != 0) + free(drawSurfaces); + if(lightBytes != 0) + free(lightBytes); + if(gridData != 0) + free(gridData); +} + +//============================================================================= + +/* +============= +SwapBlock + +If all values are 32 bits, this can be used to swap everything +============= +*/ +void SwapBlock( int *block, int sizeOfBlock ) { + int i; + + sizeOfBlock >>= 2; + for ( i = 0 ; i < sizeOfBlock ; i++ ) { + block[i] = LittleLong( block[i] ); + } +} + +/* +============= +SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void SwapBSPFile( void ) { + int i; + + // models + SwapBlock( (int *)dmodels, nummodels * sizeof( dmodels[0] ) ); + + // shaders (don't swap the name) + for ( i = 0 ; i < numShaders ; i++ ) { + dshaders[i].contentFlags = LittleLong( dshaders[i].contentFlags ); + dshaders[i].surfaceFlags = LittleLong( dshaders[i].surfaceFlags ); + } + + // planes + SwapBlock( (int *)dplanes, numplanes * sizeof( dplanes[0] ) ); + + // nodes + SwapBlock( (int *)dnodes, numnodes * sizeof( dnodes[0] ) ); + + // leafs + SwapBlock( (int *)dleafs, numleafs * sizeof( dleafs[0] ) ); + + // leaffaces + SwapBlock( (int *)dleafsurfaces, numleafsurfaces * sizeof( dleafsurfaces[0] ) ); + + // leafbrushes + SwapBlock( (int *)dleafbrushes, numleafbrushes * sizeof( dleafbrushes[0] ) ); + + // brushes + SwapBlock( (int *)dbrushes, numbrushes * sizeof( dbrushes[0] ) ); + + // brushsides + SwapBlock( (int *)dbrushsides, numbrushsides * sizeof( dbrushsides[0] ) ); + + // vis + ((int *)&visBytes)[0] = LittleLong( ((int *)&visBytes)[0] ); + ((int *)&visBytes)[1] = LittleLong( ((int *)&visBytes)[1] ); + + // drawverts (don't swap colors ) + for ( i = 0 ; i < numDrawVerts ; i++ ) { + drawVerts[i].lightmap[0] = LittleFloat( drawVerts[i].lightmap[0] ); + drawVerts[i].lightmap[1] = LittleFloat( drawVerts[i].lightmap[1] ); + drawVerts[i].st[0] = LittleFloat( drawVerts[i].st[0] ); + drawVerts[i].st[1] = LittleFloat( drawVerts[i].st[1] ); + drawVerts[i].xyz[0] = LittleFloat( drawVerts[i].xyz[0] ); + drawVerts[i].xyz[1] = LittleFloat( drawVerts[i].xyz[1] ); + drawVerts[i].xyz[2] = LittleFloat( drawVerts[i].xyz[2] ); + drawVerts[i].normal[0] = LittleFloat( drawVerts[i].normal[0] ); + drawVerts[i].normal[1] = LittleFloat( drawVerts[i].normal[1] ); + drawVerts[i].normal[2] = LittleFloat( drawVerts[i].normal[2] ); + } + + // drawindexes + SwapBlock( (int *)drawIndexes, numDrawIndexes * sizeof( drawIndexes[0] ) ); + + // drawsurfs + SwapBlock( (int *)drawSurfaces, numDrawSurfaces * sizeof( drawSurfaces[0] ) ); + + // fogs + for ( i = 0 ; i < numFogs ; i++ ) { + dfogs[i].brushNum = LittleLong( dfogs[i].brushNum ); + dfogs[i].visibleSide = LittleLong( dfogs[i].visibleSide ); + } +} + + + +/* +============= +GetLumpElements +============= +*/ +int GetLumpElements( dheader_t *header, int lump, int size ) { + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if ( length % size ) { + Error ("LoadBSPFile: odd lump size"); + } + + return length / size; +} + +/* +============= +CopyLump +============= +*/ +int CopyLump( dheader_t *header, int lump, void *dest, int size ) { + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if(length == 0) + return 0; + + if ( length % size ) { + Error ("LoadBSPFile: odd lump size"); + } + + memcpy( dest, (byte *)header + ofs, length ); + + return length / size; +} + +/* +============= +LoadBSPFile +============= +*/ +void LoadBSPFile( const char *filename ) { + dheader_t *header; + + // load the file header + LoadFile (filename, (void **)&header); + + // swap the header + SwapBlock( (int *)header, sizeof(*header) ); + + if ( header->ident != BSP_IDENT ) { + Error( "%s is not a IBSP file", filename ); + } + if ( header->version != bsp_version ) { + Error( "%s is version %i, not %i", filename, header->version, bsp_version ); + } + + numShaders = CopyLump( header, LUMP_SHADERS, dshaders, sizeof(dshader_t) ); + nummodels = CopyLump( header, LUMP_MODELS, dmodels, sizeof(dmodel_t) ); + numplanes = CopyLump( header, LUMP_PLANES, dplanes, sizeof(dplane_t) ); + numleafs = CopyLump( header, LUMP_LEAFS, dleafs, sizeof(dleaf_t) ); + numnodes = CopyLump( header, LUMP_NODES, dnodes, sizeof(dnode_t) ); + numleafsurfaces = CopyLump( header, LUMP_LEAFSURFACES, dleafsurfaces, sizeof(dleafsurfaces[0]) ); + numleafbrushes = CopyLump( header, LUMP_LEAFBRUSHES, dleafbrushes, sizeof(dleafbrushes[0]) ); + numbrushes = CopyLump( header, LUMP_BRUSHES, dbrushes, sizeof(dbrush_t) ); + numbrushsides = CopyLump( header, LUMP_BRUSHSIDES, dbrushsides, sizeof(dbrushside_t) ); + numDrawVerts = GetLumpElements( header, LUMP_DRAWVERTS, sizeof(drawVert_t) ); + SetDrawVerts(numDrawVerts); + CopyLump( header, LUMP_DRAWVERTS, drawVerts, sizeof(drawVert_t) ); + numDrawSurfaces = GetLumpElements( header, LUMP_SURFACES, sizeof(dsurface_t) ); + SetDrawSurfaces(numDrawSurfaces); + numDrawSurfaces = CopyLump( header, LUMP_SURFACES, drawSurfaces, sizeof(dsurface_t) ); + numFogs = CopyLump( header, LUMP_FOGS, dfogs, sizeof(dfog_t) ); + numDrawIndexes = CopyLump( header, LUMP_DRAWINDEXES, drawIndexes, sizeof(drawIndexes[0]) ); + + numVisBytes = CopyLump( header, LUMP_VISIBILITY, visBytes, 1 ); + numLightBytes = GetLumpElements( header, LUMP_LIGHTMAPS, 1 ); + SetLightBytes(numLightBytes); + CopyLump( header, LUMP_LIGHTMAPS, lightBytes, 1 ); + entdatasize = CopyLump( header, LUMP_ENTITIES, dentdata, 1); + + numGridPoints = GetLumpElements( header, LUMP_LIGHTGRID, 8 ); + SetGridPoints(numGridPoints); + CopyLump( header, LUMP_LIGHTGRID, gridData, 8 ); + + + free( header ); // everything has been copied out + + // swap everything + SwapBSPFile(); +} + + +//============================================================================ + +/* +============= +AddLump +============= +*/ +void AddLump( FILE *bspfile, dheader_t *header, int lumpnum, const void *data, int len ) { + lump_t *lump; + + lump = &header->lumps[lumpnum]; + + lump->fileofs = LittleLong( ftell(bspfile) ); + lump->filelen = LittleLong( len ); + SafeWrite( bspfile, data, (len+3)&~3 ); +} + +/* +============= +WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void WriteBSPFile( const char *filename ) { + dheader_t outheader, *header; + FILE *bspfile; + + header = &outheader; + memset( header, 0, sizeof(dheader_t) ); + + SwapBSPFile(); + + header->ident = LittleLong( BSP_IDENT ); + header->version = LittleLong( bsp_version ); + + bspfile = SafeOpenWrite( filename ); + SafeWrite( bspfile, header, sizeof(dheader_t) ); // overwritten later + + AddLump( bspfile, header, LUMP_SHADERS, dshaders, numShaders*sizeof(dshader_t) ); + AddLump( bspfile, header, LUMP_PLANES, dplanes, numplanes*sizeof(dplane_t) ); + AddLump( bspfile, header, LUMP_LEAFS, dleafs, numleafs*sizeof(dleaf_t) ); + AddLump( bspfile, header, LUMP_NODES, dnodes, numnodes*sizeof(dnode_t) ); + AddLump( bspfile, header, LUMP_BRUSHES, dbrushes, numbrushes*sizeof(dbrush_t) ); + AddLump( bspfile, header, LUMP_BRUSHSIDES, dbrushsides, numbrushsides*sizeof(dbrushside_t) ); + AddLump( bspfile, header, LUMP_LEAFSURFACES, dleafsurfaces, numleafsurfaces*sizeof(dleafsurfaces[0]) ); + AddLump( bspfile, header, LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes*sizeof(dleafbrushes[0]) ); + AddLump( bspfile, header, LUMP_MODELS, dmodels, nummodels*sizeof(dmodel_t) ); + AddLump( bspfile, header, LUMP_DRAWVERTS, drawVerts, numDrawVerts*sizeof(drawVert_t) ); + AddLump( bspfile, header, LUMP_SURFACES, drawSurfaces, numDrawSurfaces*sizeof(dsurface_t) ); + AddLump( bspfile, header, LUMP_VISIBILITY, visBytes, numVisBytes ); + AddLump( bspfile, header, LUMP_LIGHTMAPS, lightBytes, numLightBytes ); + AddLump( bspfile, header, LUMP_LIGHTGRID, gridData, 8 * numGridPoints ); + AddLump( bspfile, header, LUMP_ENTITIES, dentdata, entdatasize ); + AddLump( bspfile, header, LUMP_FOGS, dfogs, numFogs * sizeof(dfog_t) ); + AddLump( bspfile, header, LUMP_DRAWINDEXES, drawIndexes, numDrawIndexes * sizeof(drawIndexes[0]) ); + + fseek (bspfile, 0, SEEK_SET); + SafeWrite (bspfile, header, sizeof(dheader_t)); + fclose (bspfile); +} + +//============================================================================ + +/* +============= +PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void PrintBSPFileSizes( void ) { + if ( !num_entities ) { + ParseEntities(); + } + + Sys_Printf ("%6i models %7i\n" + ,nummodels, (int)(nummodels*sizeof(dmodel_t))); + Sys_Printf ("%6i shaders %7i\n" + ,numShaders, (int)(numShaders*sizeof(dshader_t))); + Sys_Printf ("%6i brushes %7i\n" + ,numbrushes, (int)(numbrushes*sizeof(dbrush_t))); + Sys_Printf ("%6i brushsides %7i\n" + ,numbrushsides, (int)(numbrushsides*sizeof(dbrushside_t))); + Sys_Printf ("%6i fogs %7i\n" + ,numFogs, (int)(numFogs*sizeof(dfog_t))); + Sys_Printf ("%6i planes %7i\n" + ,numplanes, (int)(numplanes*sizeof(dplane_t))); + Sys_Printf ("%6i entdata %7i\n", num_entities, entdatasize); + + Sys_Printf ("\n"); + + Sys_Printf ("%6i nodes %7i\n" + ,numnodes, (int)(numnodes*sizeof(dnode_t))); + Sys_Printf ("%6i leafs %7i\n" + ,numleafs, (int)(numleafs*sizeof(dleaf_t))); + Sys_Printf ("%6i leafsurfaces %7i\n" + ,numleafsurfaces, (int)(numleafsurfaces*sizeof(dleafsurfaces[0]))); + Sys_Printf ("%6i leafbrushes %7i\n" + ,numleafbrushes, (int)(numleafbrushes*sizeof(dleafbrushes[0]))); + Sys_Printf ("%6i drawverts %7i\n" + ,numDrawVerts, (int)(numDrawVerts*sizeof(drawVerts[0]))); + Sys_Printf ("%6i drawindexes %7i\n" + ,numDrawIndexes, (int)(numDrawIndexes*sizeof(drawIndexes[0]))); + Sys_Printf ("%6i drawsurfaces %7i\n" + ,numDrawSurfaces, (int)(numDrawSurfaces*sizeof(drawSurfaces[0]))); + + Sys_Printf ("%6i lightmaps %7i\n" + ,numLightBytes / (LIGHTMAP_WIDTH*LIGHTMAP_HEIGHT*3), numLightBytes ); + Sys_Printf (" visibility %7i\n" + , numVisBytes ); +} + + +//============================================ + +int num_entities; +entity_t entities[MAX_MAP_ENTITIES]; + +void StripTrailing( char *e ) { + char *s; + + s = e + strlen(e)-1; + while (s >= e && *s <= 32) + { + *s = 0; + s--; + } +} + +/* +================= +ParseEpair +================= +*/ +epair_t *ParseEpair( void ) { + epair_t *e; + + e = safe_malloc( sizeof(epair_t) ); + memset( e, 0, sizeof(epair_t) ); + + if ( strlen(token) >= MAX_KEY-1 ) { + Error ("ParseEpar: token too long"); + } + e->key = copystring( token ); + GetToken( qfalse ); + if ( strlen(token) >= MAX_VALUE-1 ) { + Error ("ParseEpar: token too long"); + } + e->value = copystring( token ); + + // strip trailing spaces that sometimes get accidentally + // added in the editor + StripTrailing( e->key ); + StripTrailing( e->value ); + + return e; +} + + +/* +================ +ParseEntity +================ +*/ +qboolean ParseEntity( void ) { + epair_t *e; + entity_t *mapent; + + if ( !GetToken (qtrue) ) { + return qfalse; + } + + if ( strcmp (token, "{") ) { + Error ("ParseEntity: { not found"); + } + if ( num_entities == MAX_MAP_ENTITIES ) { + Error ("num_entities == MAX_MAP_ENTITIES"); + } + mapent = &entities[num_entities]; + num_entities++; + + do { + if ( !GetToken (qtrue) ) { + Error ("ParseEntity: EOF without closing brace"); + } + if ( !strcmp (token, "}") ) { + break; + } + e = ParseEpair (); + e->next = mapent->epairs; + mapent->epairs = e; + } while (1); + + return qtrue; +} + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void ParseEntities( void ) { + num_entities = 0; + ParseFromMemory( dentdata, entdatasize ); + + while ( ParseEntity () ) { + } +} + + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +This allows the utilities to add or remove key/value pairs +to the data created by the map editor. +================ +*/ +void UnparseEntities( void ) { + char *buf, *end; + epair_t *ep; + char line[2048]; + int i; + char key[1024], value[1024]; + + buf = dentdata; + end = buf; + *end = 0; + + for (i=0 ; inext ) { + strcpy (key, ep->key); + StripTrailing (key); + strcpy (value, ep->value); + StripTrailing (value); + + sprintf (line, "\"%s\" \"%s\"\n", key, value); + strcat (end, line); + end += strlen(line); + } + strcat (end,"}\n"); + end += 2; + + if (end > buf + MAX_MAP_ENTSTRING) { + Error ("Entity text too long"); + } + } + entdatasize = end - buf + 1; +} + +void PrintEntity( const entity_t *ent ) { + epair_t *ep; + + Sys_Printf ("------- entity %p -------\n", ent); + for (ep=ent->epairs ; ep ; ep=ep->next) { + Sys_Printf( "%s = %s\n", ep->key, ep->value ); + } + +} + +void SetKeyValue( entity_t *ent, const char *key, const char *value ) { + epair_t *ep; + + for ( ep=ent->epairs ; ep ; ep=ep->next ) { + if ( !strcmp (ep->key, key) ) { + free (ep->value); + ep->value = copystring(value); + return; + } + } + ep = safe_malloc (sizeof(*ep)); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring(key); + ep->value = copystring(value); +} + +const char *ValueForKey( const entity_t *ent, const char *key ) { + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) { + if (!strcmp (ep->key, key) ) { + return ep->value; + } + } + return ""; +} + +vec_t FloatForKey( const entity_t *ent, const char *key ) { + const char *k; + + k = ValueForKey( ent, key ); + return atof(k); +} + +void GetVectorForKey( const entity_t *ent, const char *key, vec3_t vec ) { + const char *k; + double v1, v2, v3; + + k = ValueForKey (ent, key); + + // scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} + + diff --git a/tools/quake3/common/bspfile.h b/tools/quake3/common/bspfile.h new file mode 100644 index 00000000..ad70bae5 --- /dev/null +++ b/tools/quake3/common/bspfile.h @@ -0,0 +1,121 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qfiles.h" +#include "surfaceflags.h" + +extern int bsp_version; + +extern int nummodels; +extern dmodel_t dmodels[MAX_MAP_MODELS]; + +extern int numShaders; +extern dshader_t dshaders[MAX_MAP_MODELS]; + +extern int entdatasize; +extern char dentdata[MAX_MAP_ENTSTRING]; + +extern int numleafs; +extern dleaf_t dleafs[MAX_MAP_LEAFS]; + +extern int numplanes; +extern dplane_t dplanes[MAX_MAP_PLANES]; + +extern int numnodes; +extern dnode_t dnodes[MAX_MAP_NODES]; + +extern int numleafsurfaces; +extern int dleafsurfaces[MAX_MAP_LEAFFACES]; + +extern int numleafbrushes; +extern int dleafbrushes[MAX_MAP_LEAFBRUSHES]; + +extern int numbrushes; +extern dbrush_t dbrushes[MAX_MAP_BRUSHES]; + +extern int numbrushsides; +extern dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +void SetLightBytes(int n); +extern int numLightBytes; +extern byte *lightBytes; + +void SetGridPoints(int n); +extern int numGridPoints; +extern byte *gridData; + +extern int numVisBytes; +extern byte visBytes[MAX_MAP_VISIBILITY]; + +void SetDrawVerts(int n); +void IncDrawVerts(); +extern int numDrawVerts; +extern drawVert_t *drawVerts; + +extern int numDrawIndexes; +extern int drawIndexes[MAX_MAP_DRAW_INDEXES]; + +void SetDrawSurfaces(int n); +void SetDrawSurfacesBuffer(); +extern int numDrawSurfaces; +extern dsurface_t *drawSurfaces; + +extern int numFogs; +extern dfog_t dfogs[MAX_MAP_FOGS]; + +void LoadBSPFile( const char *filename ); +void WriteBSPFile( const char *filename ); +void PrintBSPFileSizes( void ); + +//=============== + + +typedef struct epair_s { + struct epair_s *next; + char *key; + char *value; +} epair_t; + +typedef struct { + vec3_t origin; + struct bspbrush_s *brushes; + struct parseMesh_s *patches; + int firstDrawSurf; + epair_t *epairs; +} entity_t; + +extern int num_entities; +extern entity_t entities[MAX_MAP_ENTITIES]; + +void ParseEntities( void ); +void UnparseEntities( void ); + +void SetKeyValue( entity_t *ent, const char *key, const char *value ); +const char *ValueForKey( const entity_t *ent, const char *key ); +// will return "" if not present + +vec_t FloatForKey( const entity_t *ent, const char *key ); +void GetVectorForKey( const entity_t *ent, const char *key, vec3_t vec ); + +epair_t *ParseEpair( void ); + +void PrintEntity( const entity_t *ent ); + diff --git a/tools/quake3/common/cmdlib.c b/tools/quake3/common/cmdlib.c new file mode 100644 index 00000000..cb3e9d0c --- /dev/null +++ b/tools/quake3/common/cmdlib.c @@ -0,0 +1,1153 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// cmdlib.c +// TTimo 09/30/2000 +// from an intial copy of common/cmdlib.c +// stripped out the Sys_Printf Sys_Printf stuff + +// SPoG 05/27/2001 +// merging alpha branch into trunk +// replaced qprintf with Sys_Printf + +#include "cmdlib.h" +#include "mathlib.h" +#include "inout.h" +#include +#include + +#ifdef WIN32 +#include +#include +#endif + +#if defined (__linux__) || defined (__APPLE__) +#include +#endif + +#ifdef NeXT +#include +#endif + +#define BASEDIRNAME "quake" // assumed to have a 2 or 3 following +#define PATHSEPERATOR '/' + +#ifdef SAFE_MALLOC +void *safe_malloc( size_t size ) +{ + void *p; + + p = malloc(size); + if(!p) + Error ("safe_malloc failed on allocation of %i bytes", size); + + return p; +} + +void *safe_malloc_info( size_t size, char* info ) +{ + void *p; + + p = malloc(size); + if(!p) + Error ("%s: safe_malloc failed on allocation of %i bytes", info, size); + + return p; +} +#endif + +// set these before calling CheckParm +int myargc; +char **myargv; + +char com_token[1024]; +qboolean com_eof; + +qboolean archive; +char archivedir[1024]; + + +/* +=================== +ExpandWildcards + +Mimic unix command line expansion +=================== +*/ +#define MAX_EX_ARGC 1024 +int ex_argc; +char *ex_argv[MAX_EX_ARGC]; +#ifdef _WIN32 +#include "io.h" +void ExpandWildcards( int *argc, char ***argv ) +{ + struct _finddata_t fileinfo; + int handle; + int i; + char filename[1024]; + char filebase[1024]; + char *path; + + ex_argc = 0; + for (i=0 ; i<*argc ; i++) + { + path = (*argv)[i]; + if ( path[0] == '-' + || ( !strstr(path, "*") && !strstr(path, "?") ) ) + { + ex_argv[ex_argc++] = path; + continue; + } + + handle = _findfirst (path, &fileinfo); + if (handle == -1) + return; + + ExtractFilePath (path, filebase); + + do + { + sprintf (filename, "%s%s", filebase, fileinfo.name); + ex_argv[ex_argc++] = copystring (filename); + } while (_findnext( handle, &fileinfo ) != -1); + + _findclose (handle); + } + + *argc = ex_argc; + *argv = ex_argv; +} +#else +void ExpandWildcards (int *argc, char ***argv) +{ +} +#endif + +/* + +qdir will hold the path up to the quake directory, including the slash + + f:\quake\ + /raid/quake/ + +gamedir will hold qdir + the game directory (id1, id2, etc) + +*/ + +char qdir[1024]; +char gamedir[1024]; +char writedir[1024]; + +void SetQdirFromPath( const char *path ) +{ + char temp[1024]; + const char *c; + const char *sep; + int len, count; + + if (!(path[0] == '/' || path[0] == '\\' || path[1] == ':')) + { // path is partial + Q_getwd (temp); + strcat (temp, path); + path = temp; + } + + // search for "quake2" in path + + len = strlen(BASEDIRNAME); + for (c=path+strlen(path)-1 ; c != path ; c--) + { + int i; + + if (!Q_strncasecmp (c, BASEDIRNAME, len)) + { + // + //strncpy (qdir, path, c+len+2-path); + // the +2 assumes a 2 or 3 following quake which is not the + // case with a retail install + // so we need to add up how much to the next separator + sep = c + len; + count = 1; + while (*sep && *sep != '/' && *sep != '\\') + { + sep++; + count++; + } + strncpy (qdir, path, c+len+count-path); + Sys_Printf ("qdir: %s\n", qdir); + for ( i = 0; i < strlen( qdir ); i++ ) + { + if ( qdir[i] == '\\' ) + qdir[i] = '/'; + } + + c += len+count; + while (*c) + { + if (*c == '/' || *c == '\\') + { + strncpy (gamedir, path, c+1-path); + + for ( i = 0; i < strlen( gamedir ); i++ ) + { + if ( gamedir[i] == '\\' ) + gamedir[i] = '/'; + } + + Sys_Printf ("gamedir: %s\n", gamedir); + + if ( !writedir[0] ) + strcpy( writedir, gamedir ); + else if ( writedir[strlen( writedir )-1] != '/' ) + { + writedir[strlen( writedir )] = '/'; + writedir[strlen( writedir )+1] = 0; + } + + return; + } + c++; + } + Error ("No gamedir in %s", path); + return; + } + } + Error ("SetQdirFromPath: no '%s' in %s", BASEDIRNAME, path); +} + +char *ExpandArg (const char *path) +{ + static char full[1024]; + + if (path[0] != '/' && path[0] != '\\' && path[1] != ':') + { + Q_getwd (full); + strcat (full, path); + } + else + strcpy (full, path); + return full; +} + +char *ExpandPath (const char *path) +{ + static char full[1024]; + if (!qdir) + Error ("ExpandPath called without qdir set"); + if (path[0] == '/' || path[0] == '\\' || path[1] == ':') { + strcpy( full, path ); + return full; + } + sprintf (full, "%s%s", qdir, path); + return full; +} + +char *ExpandGamePath (const char *path) +{ + static char full[1024]; + if (!qdir) + Error ("ExpandGamePath called without qdir set"); + if (path[0] == '/' || path[0] == '\\' || path[1] == ':') { + strcpy( full, path ); + return full; + } + sprintf (full, "%s%s", gamedir, path); + return full; +} + +char *ExpandPathAndArchive (const char *path) +{ + char *expanded; + char archivename[1024]; + + expanded = ExpandPath (path); + + if (archive) + { + sprintf (archivename, "%s/%s", archivedir, path); + QCopyFile (expanded, archivename); + } + return expanded; +} + + +char *copystring(const char *s) +{ + char *b; + b = safe_malloc(strlen(s)+1); + strcpy (b, s); + return b; +} + + + +/* +================ +I_FloatTime +================ +*/ +double I_FloatTime (void) +{ + time_t t; + + time (&t); + + return t; +#if 0 +// more precise, less portable + struct timeval tp; + struct timezone tzp; + static int secbase; + + gettimeofday(&tp, &tzp); + + if (!secbase) + { + secbase = tp.tv_sec; + return tp.tv_usec/1000000.0; + } + + return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0; +#endif +} + +void Q_getwd (char *out) +{ + int i = 0; + +#ifdef WIN32 + _getcwd (out, 256); + strcat (out, "\\"); +#else + // Gef: Changed from getwd() to getcwd() to avoid potential buffer overflow + getcwd (out, 256); + strcat (out, "/"); +#endif + while ( out[i] != 0 ) + { + if ( out[i] == '\\' ) + out[i] = '/'; + i++; + } +} + + +void Q_mkdir (const char *path) +{ +#ifdef WIN32 + if (_mkdir (path) != -1) + return; +#else + if (mkdir (path, 0777) != -1) + return; +#endif + if (errno != EEXIST) + Error ("mkdir %s: %s",path, strerror(errno)); +} + +/* +============ +FileTime + +returns -1 if not present +============ +*/ +int FileTime (const char *path) +{ + struct stat buf; + + if (stat (path,&buf) == -1) + return -1; + + return buf.st_mtime; +} + + + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char *data) +{ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + { + com_eof = qtrue; + return NULL; // end of file; + } + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + do + { + c = *data++; + if (c=='\"') + { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } while (1); + } + +// parse single characters + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') + { + com_token[len] = c; + len++; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + com_token[len] = c; + data++; + len++; + c = *data; + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') + break; + } while (c>32); + + com_token[len] = 0; + return data; +} + +int Q_strncasecmp (const char *s1, const char *s2, int n) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (!n--) + return 0; // strings are equal until end point + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return -1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + +int Q_stricmp (const char *s1, const char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + +// NOTE TTimo when switching to Multithread DLL (Release/Debug) in the config +// started getting warnings about that function, prolly a duplicate with the runtime function +// maybe we still need to have it in linux builds +/* +char *strupr (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = toupper(*in); + in++; + } + return start; +} +*/ + +char *strlower (char *start) +{ + char *in; + in = start; + while (*in) + { + *in = tolower(*in); + in++; + } + return start; +} + + +/* +============================================================================= + + MISC FUNCTIONS + +============================================================================= +*/ + + +/* +================= +CheckParm + +Checks for the given parameter in the program's command line arguments +Returns the argument number (1 to argc-1) or 0 if not present +================= +*/ +int CheckParm (const char *check) +{ + int i; + + for (i = 1;i 0) { + nAllocSize += MEM_BLOCKSIZE - nBlock; + } + buffer = safe_malloc (nAllocSize+1); + memset(buffer, 0, nAllocSize+1); + SafeRead (f, buffer, length); + fclose (f); + + *bufferptr = buffer; + return length; +} + + +/* +============== +TryLoadFile + +Allows failure +============== +*/ +int TryLoadFile (const char *filename, void **bufferptr) +{ + FILE *f; + int length; + void *buffer; + + *bufferptr = NULL; + + f = fopen (filename, "rb"); + if (!f) + return -1; + length = Q_filelength (f); + buffer = safe_malloc (length+1); + ((char *)buffer)[length] = 0; + SafeRead (f, buffer, length); + fclose (f); + + *bufferptr = buffer; + return length; +} + + +/* +============== +SaveFile +============== +*/ +void SaveFile (const char *filename, const void *buffer, int count) +{ + FILE *f; + + f = SafeOpenWrite (filename); + SafeWrite (f, buffer, count); + fclose (f); +} + + + +void DefaultExtension (char *path, const char *extension) +{ + char *src; +// +// if path doesnt have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen(path) - 1; + + while (*src != '/' && *src != '\\' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + strcat (path, extension); +} + + +void DefaultPath (char *path, const char *basepath) +{ + char temp[128]; + + if( path[ 0 ] == '/' || path[ 0 ] == '\\' ) + return; // absolute path location + strcpy (temp,path); + strcpy (path,basepath); + strcat (path,temp); +} + + +void StripFilename (char *path) +{ + int length; + + length = strlen(path)-1; + while (length > 0 && path[length] != '/' && path[ length ] != '\\' ) + length--; + path[length] = 0; +} + +void StripExtension (char *path) +{ + int length; + + length = strlen(path)-1; + while (length > 0 && path[length] != '.') + { + length--; + if (path[length] == '/' || path[ length ] == '\\' ) + return; // no extension + } + if (length) + path[length] = 0; +} + + +/* +==================== +Extract file parts +==================== +*/ +// FIXME: should include the slash, otherwise +// backing to an empty path will be wrong when appending a slash +void ExtractFilePath (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && *(src-1) != '\\' && *(src-1) != '/') + src--; + + memcpy (dest, path, src-path); + dest[src-path] = 0; +} + +void ExtractFileBase (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a \ or the start +// + while (src != path && *(src-1) != '/' && *(src-1) != '\\' ) + src--; + + while (*src && *src != '.') + { + *dest++ = *src++; + } + *dest = 0; +} + +void ExtractFileExtension (const char *path, char *dest) +{ + const char *src; + + src = path + strlen(path) - 1; + +// +// back up until a . or the start +// + while (src != path && *(src-1) != '.') + src--; + if (src == path) + { + *dest = 0; // no extension + return; + } + + strcpy (dest,src); +} + + +/* +============== +ParseNum / ParseHex +============== +*/ +int ParseHex (const char *hex) +{ + const char *str; + int num; + + num = 0; + str = hex; + + while (*str) + { + num <<= 4; + if (*str >= '0' && *str <= '9') + num += *str-'0'; + else if (*str >= 'a' && *str <= 'f') + num += 10 + *str-'a'; + else if (*str >= 'A' && *str <= 'F') + num += 10 + *str-'A'; + else + Error ("Bad hex number: %s",hex); + str++; + } + + return num; +} + + +int ParseNum (const char *str) +{ + if (str[0] == '$') + return ParseHex (str+1); + if (str[0] == '0' && str[1] == 'x') + return ParseHex (str+2); + return atol (str); +} + + + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +#ifdef _SGI_SOURCE +#define __BIG_ENDIAN__ +#endif + +#ifdef __BIG_ENDIAN__ + +short LittleShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short BigShort (short l) +{ + return l; +} + + +int LittleLong (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int BigLong (int l) +{ + return l; +} + + +float LittleFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float BigFloat (float l) +{ + return l; +} + + +#else + + +short BigShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short LittleShort (short l) +{ + return l; +} + + +int BigLong (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int LittleLong (int l) +{ + return l; +} + +float BigFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float LittleFloat (float l) +{ + return l; +} + + +#endif + + +//======================================================= + + +// FIXME: byte swap? + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +static unsigned short crctable[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +void CRC_Init(unsigned short *crcvalue) +{ + *crcvalue = CRC_INIT_VALUE; +} + +void CRC_ProcessByte(unsigned short *crcvalue, byte data) +{ + *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; +} + +unsigned short CRC_Value(unsigned short crcvalue) +{ + return crcvalue ^ CRC_XOR_VALUE; +} +//============================================================================= + +/* +============ +CreatePath +============ +*/ +void CreatePath (const char *path) +{ + const char *ofs; + char c; + char dir[1024]; + +#ifdef _WIN32 + int olddrive = -1; + + if ( path[1] == ':' ) + { + olddrive = _getdrive(); + _chdrive( toupper( path[0] ) - 'A' + 1 ); + } +#endif + + if (path[1] == ':') + path += 2; + + for (ofs = path+1 ; *ofs ; ofs++) + { + c = *ofs; + if (c == '/' || c == '\\') + { // create the directory + memcpy( dir, path, ofs - path ); + dir[ ofs - path ] = 0; + Q_mkdir( dir ); + } + } + +#ifdef _WIN32 + if ( olddrive != -1 ) + { + _chdrive( olddrive ); + } +#endif +} + + +/* +============ +QCopyFile + + Used to archive source files +============ +*/ +void QCopyFile (const char *from, const char *to) +{ + void *buffer; + int length; + + length = LoadFile (from, &buffer); + CreatePath (to); + SaveFile (to, buffer, length); + free (buffer); +} + +void Sys_Sleep(int n) +{ +#ifdef WIN32 + Sleep (n); +#endif +#if defined (__linux__) || defined (__APPLE__) + usleep (n * 1000); +#endif +} diff --git a/tools/quake3/common/cmdlib.h b/tools/quake3/common/cmdlib.h new file mode 100644 index 00000000..245f7ed0 --- /dev/null +++ b/tools/quake3/common/cmdlib.h @@ -0,0 +1,160 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// cmdlib.h + +#ifndef __CMDLIB__ +#define __CMDLIB__ + +#include "bytebool.h" + +#ifdef _WIN32 +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncate from double to float + +#pragma check_stack(off) + +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#pragma intrinsic( memset, memcpy ) + +#endif + + +#define MAX_OS_PATH 1024 +#define MEM_BLOCKSIZE 4096 + +// the dec offsetof macro doesnt work very well... +#define myoffsetof(type,identifier) ((size_t)&((type *)0)->identifier) + +#define SAFE_MALLOC +#ifdef SAFE_MALLOC +void *safe_malloc( size_t size ); +void *safe_malloc_info( size_t size, char* info ); +#else +#define safe_malloc(a) malloc(a) +#endif /* SAFE_MALLOC */ + +// set these before calling CheckParm +extern int myargc; +extern char **myargv; + +char *strlower (char *in); +int Q_strncasecmp( const char *s1, const char *s2, int n ); +int Q_stricmp( const char *s1, const char *s2 ); +void Q_getwd( char *out ); + +int Q_filelength (FILE *f); +int FileTime( const char *path ); + +void Q_mkdir( const char *path ); + +extern char qdir[1024]; +extern char gamedir[1024]; +extern char writedir[1024]; +extern char *moddirparam; +void SetQdirFromPath( const char *path); +char *ExpandArg( const char *path ); // from cmd line +char *ExpandPath( const char *path ); // from scripts +char *ExpandGamePath (const char *path); +char *ExpandPathAndArchive( const char *path ); +void ExpandWildcards( int *argc, char ***argv ); + + +double I_FloatTime( void ); + +void Error( const char *error, ... ); +int CheckParm( const char *check ); + +FILE *SafeOpenWrite( const char *filename ); +FILE *SafeOpenRead( const char *filename ); +void SafeRead (FILE *f, void *buffer, int count); +void SafeWrite (FILE *f, const void *buffer, int count); + +int LoadFile( const char *filename, void **bufferptr ); +int LoadFileBlock( const char *filename, void **bufferptr ); +int TryLoadFile( const char *filename, void **bufferptr ); +void SaveFile( const char *filename, const void *buffer, int count ); +qboolean FileExists( const char *filename ); + +void DefaultExtension( char *path, const char *extension ); +void DefaultPath( char *path, const char *basepath ); +void StripFilename( char *path ); +void StripExtension( char *path ); + +void ExtractFilePath( const char *path, char *dest ); +void ExtractFileBase( const char *path, char *dest ); +void ExtractFileExtension( const char *path, char *dest ); + +int ParseNum (const char *str); + +short BigShort (short l); +short LittleShort (short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); + + +char *COM_Parse (char *data); + +extern char com_token[1024]; +extern qboolean com_eof; + +char *copystring(const char *s); + + +void CRC_Init(unsigned short *crcvalue); +void CRC_ProcessByte(unsigned short *crcvalue, byte data); +unsigned short CRC_Value(unsigned short crcvalue); + +void CreatePath( const char *path ); +void QCopyFile( const char *from, const char *to ); + +extern qboolean archive; +extern char archivedir[1024]; + +// sleep for the given amount of milliseconds +void Sys_Sleep(int n); + +// for compression routines +typedef struct +{ + void *data; + int count, width, height; +} cblock_t; + + +#endif diff --git a/tools/quake3/common/imagelib.c b/tools/quake3/common/imagelib.c new file mode 100644 index 00000000..5971d810 --- /dev/null +++ b/tools/quake3/common/imagelib.c @@ -0,0 +1,1230 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// imagelib.c + +#include "inout.h" +#include "cmdlib.h" +#include "imagelib.h" +#include "vfs.h" + +int fgetLittleShort (FILE *f) +{ + byte b1, b2; + + b1 = fgetc(f); + b2 = fgetc(f); + + return (short)(b1 + b2*256); +} + +int fgetLittleLong (FILE *f) +{ + byte b1, b2, b3, b4; + + b1 = fgetc(f); + b2 = fgetc(f); + b3 = fgetc(f); + b4 = fgetc(f); + + return b1 + (b2<<8) + (b3<<16) + (b4<<24); +} + +int bufLittleShort (byte *buf, int len, int *pos) +{ + byte b1, b2; + + if ((len - *pos) < 2) + Error ("Unexpected buffer end"); + + b1 = buf[*pos]; *pos += 1; + b2 = buf[*pos]; *pos += 1; + + return (short)(b1 + b2*256); +} + +int bufLittleLong (byte *buf, int len, int *pos) +{ + byte b1, b2, b3, b4; + + if ((len - *pos) < 4) + Error ("Unexpected buffer end"); + + b1 = buf[*pos]; *pos += 1; + b2 = buf[*pos]; *pos += 1; + b3 = buf[*pos]; *pos += 1; + b4 = buf[*pos]; *pos += 1; + + return b1 + (b2<<8) + (b3<<16) + (b4<<24); +} + + +/* +============================================================================ + + LBM STUFF + +============================================================================ +*/ + + +typedef unsigned char UBYTE; +//conflicts with windows typedef short WORD; +typedef unsigned short UWORD; +typedef long LONG; + +typedef enum +{ + ms_none, + ms_mask, + ms_transcolor, + ms_lasso +} mask_t; + +typedef enum +{ + cm_none, + cm_rle1 +} compress_t; + +typedef struct +{ + UWORD w,h; + short x,y; + UBYTE nPlanes; + UBYTE masking; + UBYTE compression; + UBYTE pad1; + UWORD transparentColor; + UBYTE xAspect,yAspect; + short pageWidth,pageHeight; +} bmhd_t; + +extern bmhd_t bmhd; // will be in native byte order + + + +#define FORMID ('F'+('O'<<8)+((int)'R'<<16)+((int)'M'<<24)) +#define ILBMID ('I'+('L'<<8)+((int)'B'<<16)+((int)'M'<<24)) +#define PBMID ('P'+('B'<<8)+((int)'M'<<16)+((int)' '<<24)) +#define BMHDID ('B'+('M'<<8)+((int)'H'<<16)+((int)'D'<<24)) +#define BODYID ('B'+('O'<<8)+((int)'D'<<16)+((int)'Y'<<24)) +#define CMAPID ('C'+('M'<<8)+((int)'A'<<16)+((int)'P'<<24)) + + +bmhd_t bmhd; + +int Align (int l) +{ + if (l&1) + return l+1; + return l; +} + + + +/* +================ +LBMRLEdecompress + +Source must be evenly aligned! +================ +*/ +byte *LBMRLEDecompress (byte *source,byte *unpacked, int bpwidth) +{ + int count; + byte b,rept; + + count = 0; + + do + { + rept = *source++; + + if (rept > 0x80) + { + rept = (rept^0xff)+2; + b = *source++; + memset(unpacked,b,rept); + unpacked += rept; + } + else if (rept < 0x80) + { + rept++; + memcpy(unpacked,source,rept); + unpacked += rept; + source += rept; + } + else + rept = 0; // rept of 0x80 is NOP + + count += rept; + + } while (countbpwidth) + Error ("Decompression exceeded width!\n"); + + + return source; +} + + +/* +================= +LoadLBM +================= +*/ +void LoadLBM (const char *filename, byte **picture, byte **palette) +{ + byte *LBMbuffer, *picbuffer, *cmapbuffer; + int y; + byte *LBM_P, *LBMEND_P; + byte *pic_p; + byte *body_p; + + int formtype,formlength; + int chunktype,chunklength; + +// qiet compiler warnings + picbuffer = NULL; + cmapbuffer = NULL; + +// +// load the LBM +// + LoadFile (filename, (void **)&LBMbuffer); + +// +// parse the LBM header +// + LBM_P = LBMbuffer; + if ( *(int *)LBMbuffer != LittleLong(FORMID) ) + Error ("No FORM ID at start of file!\n"); + + LBM_P += 4; + formlength = BigLong( *(int *)LBM_P ); + LBM_P += 4; + LBMEND_P = LBM_P + Align(formlength); + + formtype = LittleLong(*(int *)LBM_P); + + if (formtype != ILBMID && formtype != PBMID) + Error ("Unrecognized form type: %c%c%c%c\n", formtype&0xff + ,(formtype>>8)&0xff,(formtype>>16)&0xff,(formtype>>24)&0xff); + + LBM_P += 4; + +// +// parse chunks +// + + while (LBM_P < LBMEND_P) + { + chunktype = LBM_P[0] + (LBM_P[1]<<8) + (LBM_P[2]<<16) + (LBM_P[3]<<24); + LBM_P += 4; + chunklength = LBM_P[3] + (LBM_P[2]<<8) + (LBM_P[1]<<16) + (LBM_P[0]<<24); + LBM_P += 4; + + switch ( chunktype ) + { + case BMHDID: + memcpy (&bmhd,LBM_P,sizeof(bmhd)); + bmhd.w = BigShort(bmhd.w); + bmhd.h = BigShort(bmhd.h); + bmhd.x = BigShort(bmhd.x); + bmhd.y = BigShort(bmhd.y); + bmhd.pageWidth = BigShort(bmhd.pageWidth); + bmhd.pageHeight = BigShort(bmhd.pageHeight); + break; + + case CMAPID: + cmapbuffer = safe_malloc (768); + memset (cmapbuffer, 0, 768); + memcpy (cmapbuffer, LBM_P, chunklength); + break; + + case BODYID: + body_p = LBM_P; + + pic_p = picbuffer = safe_malloc (bmhd.w*bmhd.h); + if (formtype == PBMID) + { + // + // unpack PBM + // + for (y=0 ; ydata; + + pcx->xmin = LittleShort(pcx->xmin); + pcx->ymin = LittleShort(pcx->ymin); + pcx->xmax = LittleShort(pcx->xmax); + pcx->ymax = LittleShort(pcx->ymax); + pcx->hres = LittleShort(pcx->hres); + pcx->vres = LittleShort(pcx->vres); + pcx->bytes_per_line = LittleShort(pcx->bytes_per_line); + pcx->palette_type = LittleShort(pcx->palette_type); + + if (pcx->manufacturer != 0x0a + || pcx->version != 5 + || pcx->encoding != 1 + || pcx->bits_per_pixel != 8 + || pcx->xmax >= 640 + || pcx->ymax >= 480) + Error ("Bad pcx file %s", filename); + + if (palette) + { + *palette = safe_malloc(768); + memcpy (*palette, (byte *)pcx + len - 768, 768); + } + + if (width) + *width = pcx->xmax+1; + if (height) + *height = pcx->ymax+1; + + if (!pic) + return; + + out = safe_malloc ( (pcx->ymax+1) * (pcx->xmax+1) ); + if (!out) + Error( "LoadPCX: couldn't allocate"); + + *pic = out; + pix = out; + + /* RR2DO2: pcx fix */ + lsize = pcx->color_planes * pcx->bytes_per_line; + + /* go scanline by scanline */ + for( y = 0; y <= pcx->ymax; y++, pix += pcx->xmax + 1 ) + { + /* do a scanline */ + for( x=0; x <= pcx->xmax; ) + { + /* RR2DO2 */ + DECODEPCX( raw, dataByte, runLength ); + while( runLength-- > 0 ) + pix[ x++ ] = dataByte; + } + + /* RR2DO2: discard any other data */ + while( x < lsize ) + { + DECODEPCX( raw, dataByte, runLength ); + x++; + } + while( runLength-- > 0 ) + x++; + } + + /* validity check */ + if( raw - (byte *) pcx > len) + Error( "PCX file %s was malformed", filename ); + free( pcx ); +} + + + +/* +============== +WritePCXfile +============== +*/ +void WritePCXfile (const char *filename, byte *data, + int width, int height, byte *palette) +{ + int i, j, length; + pcx_t *pcx; + byte *pack; + + pcx = safe_malloc (width*height*2+1000); + memset (pcx, 0, sizeof(*pcx)); + + pcx->manufacturer = 0x0a; // PCX id + pcx->version = 5; // 256 color + pcx->encoding = 1; // uncompressed + pcx->bits_per_pixel = 8; // 256 color + pcx->xmin = 0; + pcx->ymin = 0; + pcx->xmax = LittleShort((short)(width-1)); + pcx->ymax = LittleShort((short)(height-1)); + pcx->hres = LittleShort((short)width); + pcx->vres = LittleShort((short)height); + pcx->color_planes = 1; // chunky image + pcx->bytes_per_line = LittleShort((short)width); + pcx->palette_type = LittleShort(1); // not a grey scale + + // pack the image + pack = &pcx->data; + + for (i=0 ; iid_length, t->colormap_type, t->image_type, t->colormap_index, t->colormap_length, t->colormap_size, t->x_origin, t->y_origin, t->width, t->height, t->pixel_size, t->attributes); +} + +/* +============= +LoadTGABuffer +============= +*/ +void LoadTGABuffer (const byte *f, const byte *enddata, byte **pic, int *width, int *height) +{ + int x, y, row_inc, compressed, readpixelcount, red, green, blue, alpha, runlen, pindex, alphabits, image_width, image_height; + byte *pixbuf, *image_rgba; + const byte *fin; + unsigned char *p; + TargaHeader targa_header; + unsigned char palette[256*4]; + + *pic = NULL; + + // abort if it is too small to parse + if (enddata - f < 19) + return; + + targa_header.id_length = f[0]; + targa_header.colormap_type = f[1]; + targa_header.image_type = f[2]; + + targa_header.colormap_index = f[3] + f[4] * 256; + targa_header.colormap_length = f[5] + f[6] * 256; + targa_header.colormap_size = f[7]; + targa_header.x_origin = f[8] + f[9] * 256; + targa_header.y_origin = f[10] + f[11] * 256; + targa_header.width = image_width = f[12] + f[13] * 256; + targa_header.height = image_height = f[14] + f[15] * 256; + + targa_header.pixel_size = f[16]; + targa_header.attributes = f[17]; + + // advance to end of header + fin = f + 18; + + // skip TARGA image comment (usually 0 bytes) + fin += targa_header.id_length; + + // read/skip the colormap if present (note: according to the TARGA spec it + // can be present even on truecolor or greyscale images, just not used by + // the image data) + if (targa_header.colormap_type) + { + if (targa_header.colormap_length > 256) + { + TargaError(&targa_header, "LoadTGA: only up to 256 colormap_length supported\n"); + return; + } + if (targa_header.colormap_index) + { + TargaError(&targa_header, "LoadTGA: colormap_index not supported\n"); + return; + } + if (targa_header.colormap_size == 24) + { + for (x = 0;x < targa_header.colormap_length;x++) + { + palette[x*4+2] = *fin++; + palette[x*4+1] = *fin++; + palette[x*4+0] = *fin++; + palette[x*4+3] = 255; + } + } + else if (targa_header.colormap_size == 32) + { + for (x = 0;x < targa_header.colormap_length;x++) + { + palette[x*4+2] = *fin++; + palette[x*4+1] = *fin++; + palette[x*4+0] = *fin++; + palette[x*4+3] = *fin++; + } + } + else + { + TargaError(&targa_header, "LoadTGA: Only 32 and 24 bit colormap_size supported\n"); + return; + } + } + + // check our pixel_size restrictions according to image_type + if (targa_header.image_type == 2 || targa_header.image_type == 10) + { + if (targa_header.pixel_size != 24 && targa_header.pixel_size != 32) + { + TargaError(&targa_header, "LoadTGA: only 24bit and 32bit pixel sizes supported for type 2 and type 10 images\n"); + return; + } + } + else if (targa_header.image_type == 1 || targa_header.image_type == 9) + { + if (targa_header.pixel_size != 8) + { + TargaError(&targa_header, "LoadTGA: only 8bit pixel size for type 1, 3, 9, and 11 images supported\n"); + return; + } + } + else if (targa_header.image_type == 3 || targa_header.image_type == 11) + { + if (targa_header.pixel_size != 8) + { + TargaError(&targa_header, "LoadTGA: only 8bit pixel size for type 1, 3, 9, and 11 images supported\n"); + return; + } + } + else + { + TargaError(&targa_header, "LoadTGA: Only type 1, 2, 3, 9, 10, and 11 targa RGB images supported"); + return; + } + + if (targa_header.attributes & 0x10) + { + TargaError(&targa_header, "LoadTGA: origin must be in top left or bottom left, top right and bottom right are not supported\n"); + return; + } + + // number of attribute bits per pixel, we only support 0 or 8 + alphabits = targa_header.attributes & 0x0F; + if (alphabits != 8 && alphabits != 0) + { + TargaError(&targa_header, "LoadTGA: only 0 or 8 attribute (alpha) bits supported\n"); + return; + } + + image_rgba = safe_malloc(image_width * image_height * 4); + if (!image_rgba) + { + Sys_Printf("LoadTGA: not enough memory for %i by %i image\n", image_width, image_height); + return; + } + + // If bit 5 of attributes isn't set, the image has been stored from bottom to top + if ((targa_header.attributes & 0x20) == 0) + { + pixbuf = image_rgba + (image_height - 1)*image_width*4; + row_inc = -image_width*4*2; + } + else + { + pixbuf = image_rgba; + row_inc = 0; + } + + compressed = targa_header.image_type == 9 || targa_header.image_type == 10 || targa_header.image_type == 11; + x = 0; + y = 0; + red = green = blue = alpha = 255; + while (y < image_height) + { + // decoder is mostly the same whether it's compressed or not + readpixelcount = 1000000; + runlen = 1000000; + if (compressed && fin < enddata) + { + runlen = *fin++; + // high bit indicates this is an RLE compressed run + if (runlen & 0x80) + readpixelcount = 1; + runlen = 1 + (runlen & 0x7f); + } + + while((runlen--) && y < image_height) + { + if (readpixelcount > 0) + { + readpixelcount--; + red = green = blue = alpha = 255; + if (fin < enddata) + { + switch(targa_header.image_type) + { + case 1: + case 9: + // colormapped + pindex = *fin++; + if (pindex >= targa_header.colormap_length) + pindex = 0; // error + p = palette + pindex * 4; + red = p[0]; + green = p[1]; + blue = p[2]; + alpha = p[3]; + break; + case 2: + case 10: + // BGR or BGRA + blue = *fin++; + if (fin < enddata) + green = *fin++; + if (fin < enddata) + red = *fin++; + if (targa_header.pixel_size == 32 && fin < enddata) + alpha = *fin++; + break; + case 3: + case 11: + // greyscale + red = green = blue = *fin++; + break; + } + if (!alphabits) + alpha = 255; + } + } + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + x++; + if (x == image_width) + { + // end of line, advance to next + x = 0; + y++; + pixbuf += row_inc; + } + } + } + + *pic = image_rgba; + if (width) + *width = image_width; + if (height) + *height = image_height; +} + + +/* +============= +LoadTGA +============= +*/ +void LoadTGA (const char *name, byte **pixels, int *width, int *height) +{ + byte *buffer; + int nLen; + // + // load the file + // + nLen = vfsLoadFile ( ( char * ) name, (void **)&buffer, 0); + if (nLen == -1) + { + Error ("Couldn't read %s", name); + } + + LoadTGABuffer(buffer, buffer + nLen, pixels, width, height); + +} + + +/* +================ +WriteTGA +================ +*/ +void WriteTGA (const char *filename, byte *data, int width, int height) { + byte *buffer; + int i; + int c; + FILE *f; + + buffer = safe_malloc(width*height*4 + 18); + memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = width&255; + buffer[13] = width>>8; + buffer[14] = height&255; + buffer[15] = height>>8; + buffer[16] = 32; // pixel size + + // swap rgb to bgr + c = 18 + width * height * 4; + for (i=18 ; i +#include + +#ifdef WIN32 +#include +#include +#endif + +// network broadcasting +#include "l_net/l_net.h" +#include "libxml/tree.h" + +// utf8 conversion +#include +#include + +#ifdef WIN32 +HWND hwndOut = NULL; +qboolean lookedForServer = qfalse; +UINT wm_BroadcastCommand = -1; +#endif + +socket_t *brdcst_socket; +netmessage_t msg; + +qboolean verbose = qfalse; + +// our main document +// is streamed through the network to Radiant +// possibly written to disk at the end of the run +//++timo FIXME: need to be global, required when creating nodes? +xmlDocPtr doc; +xmlNodePtr tree; + +// some useful stuff +xmlNodePtr xml_NodeForVec( vec3_t v ) +{ + xmlNodePtr ret; + char buf[1024]; + + sprintf (buf, "%f %f %f", v[0], v[1], v[2]); + ret = xmlNewNode (NULL, "point"); + xmlNodeSetContent (ret, buf); + return ret; +} + +// send a node down the stream, add it to the document +void xml_SendNode (xmlNodePtr node) +{ + xmlBufferPtr xml_buf; + char xmlbuf[MAX_NETMESSAGE]; // we have to copy content from the xmlBufferPtr into an aux buffer .. that sucks .. + // this index loops through the node buffer + int pos = 0; + int size; + + xmlAddChild( doc->children, node ); + + if (brdcst_socket) + { + xml_buf = xmlBufferCreate(); + xmlNodeDump( xml_buf, doc, node, 0, 0 ); + + // the XML node might be too big to fit in a single network message + // l_net library defines an upper limit of MAX_NETMESSAGE + // there are some size check errors, so we use MAX_NETMESSAGE-10 to be safe + // if the size of the buffer exceeds MAX_NETMESSAGE-10 we'll send in several network messages + while (pos < xml_buf->use) + { + // what size are we gonna send now? + (xml_buf->use - pos < MAX_NETMESSAGE - 10) ? (size = xml_buf->use - pos) : (size = MAX_NETMESSAGE - 10); + //++timo just a debug thing + if (size == MAX_NETMESSAGE - 10) + Sys_FPrintf (SYS_NOXML, "Got to split the buffer\n"); + memcpy( xmlbuf, xml_buf->content+pos, size); + xmlbuf[size] = '\0'; + NMSG_Clear( &msg ); + NMSG_WriteString (&msg, xmlbuf ); + Net_Send(brdcst_socket, &msg ); + // now that the thing is sent prepare to loop again + pos += size; + } + +#if 0 + // NOTE: the NMSG_WriteString is limited to MAX_NETMESSAGE + // we will need to split into chunks + // (we could also go lower level, in the end it's using send and receiv which are not size limited) + //++timo FIXME: MAX_NETMESSAGE is not exactly the max size we can stick in the message + // there's some tweaking to do in l_net for that .. so let's give us a margin for now + + //++timo we need to handle the case of a buffer too big to fit in a single message + // try without checks for now + if (xml_buf->use > MAX_NETMESSAGE-10 ) + { + // if we send that we are probably gonna break the stream at the other end.. + // and Error will call right there + //Error( "MAX_NETMESSAGE exceeded for XML feedback stream in FPrintf (%d)\n", xml_buf->use); + Sys_FPrintf (SYS_NOXML, "MAX_NETMESSAGE exceeded for XML feedback stream in FPrintf (%d)\n", xml_buf->use); + xml_buf->content[xml_buf->use]='\0'; //++timo this corrupts the buffer but we don't care it's for printing + Sys_FPrintf (SYS_NOXML, xml_buf->content); + + } + + size = xml_buf->use; + memcpy( xmlbuf, xml_buf->content, size ); + xmlbuf[size] = '\0'; + NMSG_Clear( &msg ); + NMSG_WriteString (&msg, xmlbuf ); + Net_Send(brdcst_socket, &msg ); +#endif + + xmlBufferFree( xml_buf ); + } +} + +void xml_Select (char *msg, int entitynum, int brushnum, qboolean bError) +{ + xmlNodePtr node, select; + char buf[1024]; + char level[2]; + + // now build a proper "select" XML node + sprintf (buf, "Entity %i, Brush %i: %s", entitynum, brushnum, msg); + node = xmlNewNode (NULL, "select"); + xmlNodeSetContent (node, buf); + level[0] = (int)'0' + (bError ? SYS_ERR : SYS_WRN) ; + level[1] = 0; + xmlSetProp (node, "level", (char *)&level); + // a 'select' information + sprintf (buf, "%i %i", entitynum, brushnum); + select = xmlNewNode (NULL, "brush"); + xmlNodeSetContent (select, buf); + xmlAddChild (node, select); + xml_SendNode (node); + + sprintf (buf, "Entity %i, Brush %i: %s", entitynum, brushnum, msg); + if (bError) + Error(buf); + else + Sys_FPrintf (SYS_NOXML, "%s\n", buf); + +} + +void xml_Point (char *msg, vec3_t pt) +{ + xmlNodePtr node, point; + char buf[1024]; + char level[2]; + + node = xmlNewNode (NULL, "pointmsg"); + xmlNodeSetContent (node, msg); + level[0] = (int)'0' + SYS_ERR; + level[1] = 0; + xmlSetProp (node, "level", (char *)&level); + // a 'point' node + sprintf (buf, "%g %g %g", pt[0], pt[1], pt[2]); + point = xmlNewNode (NULL, "point"); + xmlNodeSetContent (point, buf); + xmlAddChild (node, point); + xml_SendNode (node); + + sprintf (buf, "%s (%g %g %g)", msg, pt[0], pt[1], pt[2]); + Error (buf); +} + +#define WINDING_BUFSIZE 2048 +void xml_Winding (char *msg, vec3_t p[], int numpoints, qboolean die) +{ + xmlNodePtr node, winding; + char buf[WINDING_BUFSIZE]; + char smlbuf[128]; + char level[2]; + int i; + + node = xmlNewNode (NULL, "windingmsg"); + xmlNodeSetContent (node, msg); + level[0] = (int)'0' + SYS_ERR; + level[1] = 0; + xmlSetProp (node, "level", (char *)&level); + // a 'winding' node + sprintf( buf, "%i ", numpoints); + for(i = 0; i < numpoints; i++) + { + sprintf (smlbuf, "(%g %g %g)", p[i][0], p[i][1], p[i][2]); + // don't overflow + if (strlen(buf)+strlen(smlbuf)>WINDING_BUFSIZE) + break; + strcat( buf, smlbuf); + } + + winding = xmlNewNode (NULL, "winding"); + xmlNodeSetContent (winding, buf); + xmlAddChild (node, winding); + xml_SendNode (node); + + if(die) + Error (msg); + else + { + Sys_Printf(msg); + Sys_Printf("\n"); + } +} + +// in include +#include "stream_version.h" + +void Broadcast_Setup( const char *dest ) +{ + address_t address; + char sMsg[1024]; + + Net_Setup(); + Net_StringToAddress((char *)dest, &address); + brdcst_socket = Net_Connect(&address, 0); + if (brdcst_socket) + { + // send in a header + sprintf (sMsg, ""); + NMSG_Clear( &msg ); + NMSG_WriteString(&msg, sMsg ); + Net_Send(brdcst_socket, &msg ); + } +} + +void Broadcast_Shutdown() +{ + if (brdcst_socket) + { + Sys_Printf("Disconnecting\n"); + Net_Disconnect(brdcst_socket); + brdcst_socket = NULL; + } +} + +// all output ends up through here +void FPrintf (int flag, char *buf) +{ + xmlNodePtr node; + static qboolean bGotXML = qfalse; + char level[2]; + + printf(buf); + + // the following part is XML stuff only.. but maybe we don't want that message to go down the XML pipe? + if (flag == SYS_NOXML) + return; + + // ouput an XML file of the run + // use the DOM interface to build a tree + /* + + message string + .. various nodes to describe corresponding geometry .. + + */ + if (!bGotXML) + { + // initialize + doc = xmlNewDoc("1.0"); + doc->children = xmlNewDocRawNode(doc, NULL, "q3map_feedback", NULL); + bGotXML = qtrue; + } + node = xmlNewNode (NULL, "message"); + { + gchar* utf8 = g_locale_to_utf8(buf, -1, NULL, NULL, NULL); + xmlNodeSetContent(node, utf8); + g_free(utf8); + } + level[0] = (int)'0' + flag; + level[1] = 0; + xmlSetProp (node, "level", (char *)&level ); + + xml_SendNode (node); +} + +#ifdef DBG_XML +void DumpXML() +{ + xmlSaveFile( "XMLDump.xml", doc ); +} +#endif + +void Sys_FPrintf (int flag, const char *format, ...) +{ + char out_buffer[4096]; + va_list argptr; + + if ((flag == SYS_VRB) && (verbose == qfalse)) + return; + + va_start (argptr, format); + vsprintf (out_buffer, format, argptr); + va_end (argptr); + + FPrintf (flag, out_buffer); +} + +void Sys_Printf (const char *format, ...) +{ + char out_buffer[4096]; + va_list argptr; + + va_start (argptr, format); + vsprintf (out_buffer, format, argptr); + va_end (argptr); + + FPrintf (SYS_STD, out_buffer); +} + +/* +================= +Error + +For abnormal program terminations +================= +*/ +void Error( const char *error, ...) +{ + char out_buffer[4096]; + char tmp[4096]; + va_list argptr; + + va_start (argptr,error); + vsprintf (tmp, error, argptr); + va_end (argptr); + + sprintf( out_buffer, "************ ERROR ************\n%s\n", tmp ); + + FPrintf( SYS_ERR, out_buffer ); + +#ifdef DBG_XML + DumpXML(); +#endif + + //++timo HACK ALERT .. if we shut down too fast the xml stream won't reach the listener. + // a clean solution is to send a sync request node in the stream and wait for an answer before exiting + Sys_Sleep( 1000 ); + + Broadcast_Shutdown(); + + exit (1); +} + diff --git a/tools/quake3/common/inout.h b/tools/quake3/common/inout.h new file mode 100644 index 00000000..c60b0b85 --- /dev/null +++ b/tools/quake3/common/inout.h @@ -0,0 +1,62 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __INOUT__ +#define __INOUT__ + +// inout is the only stuff relying on xml, include the headers there +#include "libxml/tree.h" +#include "mathlib.h" + +// some useful xml routines +xmlNodePtr xml_NodeForVec( vec3_t v ); +void xml_SendNode (xmlNodePtr node); +// print a message in q3map output and send the corresponding select information down the xml stream +// bError: do we end with an error on this one or do we go ahead? +void xml_Select (char *msg, int entitynum, int brushnum, qboolean bError); +// end q3map with an error message and send a point information in the xml stream +// note: we might want to add a boolean to use this as a warning or an error thing.. +void xml_Winding (char *msg, vec3_t p[], int numpoints, qboolean die); +void xml_Point (char *msg, vec3_t pt); + +extern qboolean bNetworkBroadcast; +void Broadcast_Setup( const char *dest ); +void Broadcast_Shutdown(); + +#define SYS_VRB 0 // verbose support (on/off) +#define SYS_STD 1 // standard print level +#define SYS_WRN 2 // warnings +#define SYS_ERR 3 // error +#define SYS_NOXML 4 // don't send that down the XML stream + +extern qboolean verbose; +void Sys_Printf (const char *text, ...); +void Sys_FPrintf (int flag, const char *text, ...); + +#ifdef _DEBUG +#define DBG_XML 1 +#endif + +#ifdef DBG_XML +void DumpXML(); +#endif + +#endif diff --git a/tools/quake3/common/l3dslib.c b/tools/quake3/common/l3dslib.c new file mode 100644 index 00000000..3030b0bf --- /dev/null +++ b/tools/quake3/common/l3dslib.c @@ -0,0 +1,301 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// l3dslib.c: library for loading triangles from an Alias triangle file +// + +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "trilib.h" +#include "l3dslib.h" + +#define MAIN3DS 0x4D4D +#define EDIT3DS 0x3D3D // this is the start of the editor config +#define EDIT_OBJECT 0x4000 +#define OBJ_TRIMESH 0x4100 +#define TRI_VERTEXL 0x4110 +#define TRI_FACEL1 0x4120 + +#define MAXVERTS 2000 +#define MAXTRIANGLES 750 + +typedef struct { + int v[4]; +} tri; + +float fverts[MAXVERTS][3]; +tri tris[MAXTRIANGLES]; + +int bytesread, level, numtris, totaltris; +int vertsfound, trisfound; + +triangle_t *ptri; + + +// Alias stores triangles as 3 explicit vertices in .tri files, so even though we +// start out with a vertex pool and vertex indices for triangles, we have to convert +// to raw, explicit triangles +void StoreAliasTriangles (void) +{ + int i, j, k; + + if ((totaltris + numtris) > MAXTRIANGLES) + Error ("Error: Too many triangles"); + + for (i=0; i MAXVERTS) + Error ("Error: Too many vertices"); + + for (i=0 ; i MAXTRIANGLES) + Error ("Error: Too many triangles"); + + for (i=0 ; i 0) + { + w -= ParseChunk (input); + } + + retval = length; + goto Done; + + default: + // skip other chunks + while (w > 0) + { + t = w; + + if (t > BLOCK_SIZE) + t = BLOCK_SIZE; + + if (feof(input)) + Error ("Error: unexpected end of file"); + + fread (&temp, t, 1, input); + bytesread += t; + + w -= t; + } + + retval = length; + goto Done; + } + +Done: + level--; + return retval; +} + + +void Load3DSTriangleList (char *filename, triangle_t **pptri, int *numtriangles) +{ + FILE *input; + short int tshort; + + bytesread = 0; + level = 0; + numtris = 0; + totaltris = 0; + vertsfound = 0; + trisfound = 0; + + if ((input = fopen(filename, "rb")) == 0) { + fprintf(stderr,"reader: could not open file '%s'\n", filename); + exit(0); + } + + fread(&tshort, sizeof(tshort), 1, input); + +// should only be MAIN3DS, but some files seem to start with EDIT3DS, with +// no MAIN3DS + if ((tshort != MAIN3DS) && (tshort != EDIT3DS)) { + fprintf(stderr,"File is not a 3DS file.\n"); + exit(0); + } + +// back to top of file so we can parse the first chunk descriptor + fseek(input, 0, SEEK_SET); + + ptri = safe_malloc (MAXTRIANGLES * sizeof(triangle_t)); + + *pptri = ptri; + +// parse through looking for the relevant chunk tree (MAIN3DS | EDIT3DS | EDIT_OBJECT | +// OBJ_TRIMESH | {TRI_VERTEXL, TRI_FACEL1}) and skipping other chunks + ParseChunk (input); + + if (vertsfound || trisfound) + Error ("Incomplete triangle set"); + + *numtriangles = totaltris; + + fclose (input); +} + diff --git a/tools/quake3/common/l3dslib.h b/tools/quake3/common/l3dslib.h new file mode 100644 index 00000000..bac2433b --- /dev/null +++ b/tools/quake3/common/l3dslib.h @@ -0,0 +1,26 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// l3dslib.h: header file for loading triangles from a 3DS triangle file +// +void Load3DSTriangleList (char *filename, triangle_t **pptri, int *numtriangles); + diff --git a/tools/quake3/common/md4.c b/tools/quake3/common/md4.c new file mode 100644 index 00000000..0c04b5a1 --- /dev/null +++ b/tools/quake3/common/md4.c @@ -0,0 +1,298 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* GLOBAL.H - RSAREF types and constants */ + +#include + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + + +/* MD4.H - header file for MD4C.C */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. + +All rights reserved. + +License to copy and use this software is granted provided that it is identified as the “RSA Data Security, Inc. MD4 Message-Digest Algorithm” in all material mentioning or referencing this software or this function. +License is also granted to make and use derivative works provided that such works are identified as “derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm” in all material mentioning or referencing the derived work. +RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided “as is” without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this documentation and/or software. */ + +/* MD4 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD4_CTX; + +void MD4Init (MD4_CTX *); +void MD4Update (MD4_CTX *, unsigned char *, unsigned int); +void MD4Final (unsigned char [16], MD4_CTX *); + + + +/* MD4C.C - RSA Data Security, Inc., MD4 message-digest algorithm */ +/* Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + +License to copy and use this software is granted provided that it is identified as the +RSA Data Security, Inc. MD4 Message-Digest Algorithm + in all material mentioning or referencing this software or this function. +License is also granted to make and use derivative works provided that such works are identified as +derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm +in all material mentioning or referencing the derived work. +RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided +as is without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this documentation and/or software. */ + +/* Constants for MD4Transform routine. */ +#define S11 3 +#define S12 7 +#define S13 11 +#define S14 19 +#define S21 3 +#define S22 5 +#define S23 9 +#define S24 13 +#define S31 3 +#define S32 9 +#define S33 11 +#define S34 15 + +static void MD4Transform (UINT4 [4], unsigned char [64]); +static void Encode (unsigned char *, UINT4 *, unsigned int); +static void Decode (UINT4 *, unsigned char *, unsigned int); +static void MD4_memcpy (POINTER, POINTER, unsigned int); +static void MD4_memset (POINTER, int, unsigned int); + +static unsigned char PADDING[64] = { +0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G and H are basic MD4 functions. */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits. */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG and HH are transformations for rounds 1, 2 and 3 */ +/* Rotation is separate from addition to prevent recomputation */ +#define FF(a, b, c, d, x, s) {(a) += F ((b), (c), (d)) + (x); (a) = ROTATE_LEFT ((a), (s));} + +#define GG(a, b, c, d, x, s) {(a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; (a) = ROTATE_LEFT ((a), (s));} + +#define HH(a, b, c, d, x, s) {(a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; (a) = \ +ROTATE_LEFT ((a), (s)); } + + +/* MD4 initialization. Begins an MD4 operation, writing a new context. */ +void MD4Init (MD4_CTX *context) +{ + context->count[0] = context->count[1] = 0; + +/* Load magic initialization constants.*/ +context->state[0] = 0x67452301; +context->state[1] = 0xefcdab89; +context->state[2] = 0x98badcfe; +context->state[3] = 0x10325476; +} + +/* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */ +void MD4Update (MD4_CTX *context, unsigned char *input, unsigned int inputLen) +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3)) + context->count[1]++; + + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible.*/ + if (inputLen >= partLen) + { + memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD4Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD4Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i); +} + + +/* MD4 finalization. Ends an MD4 message-digest operation, writing the the message digest and zeroizing the context. */ +void MD4Final (unsigned char digest[16], MD4_CTX *context) +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64.*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD4Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD4Update (context, bits, 8); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information.*/ + memset ((POINTER)context, 0, sizeof (*context)); +} + + +/* MD4 basic transformation. Transforms state based on block. */ +static void MD4Transform (UINT4 state[4], unsigned char block[64]) +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + +/* Round 1 */ +FF (a, b, c, d, x[ 0], S11); /* 1 */ +FF (d, a, b, c, x[ 1], S12); /* 2 */ +FF (c, d, a, b, x[ 2], S13); /* 3 */ +FF (b, c, d, a, x[ 3], S14); /* 4 */ +FF (a, b, c, d, x[ 4], S11); /* 5 */ +FF (d, a, b, c, x[ 5], S12); /* 6 */ +FF (c, d, a, b, x[ 6], S13); /* 7 */ +FF (b, c, d, a, x[ 7], S14); /* 8 */ +FF (a, b, c, d, x[ 8], S11); /* 9 */ +FF (d, a, b, c, x[ 9], S12); /* 10 */ +FF (c, d, a, b, x[10], S13); /* 11 */ +FF (b, c, d, a, x[11], S14); /* 12 */ +FF (a, b, c, d, x[12], S11); /* 13 */ +FF (d, a, b, c, x[13], S12); /* 14 */ +FF (c, d, a, b, x[14], S13); /* 15 */ +FF (b, c, d, a, x[15], S14); /* 16 */ + +/* Round 2 */ +GG (a, b, c, d, x[ 0], S21); /* 17 */ +GG (d, a, b, c, x[ 4], S22); /* 18 */ +GG (c, d, a, b, x[ 8], S23); /* 19 */ +GG (b, c, d, a, x[12], S24); /* 20 */ +GG (a, b, c, d, x[ 1], S21); /* 21 */ +GG (d, a, b, c, x[ 5], S22); /* 22 */ +GG (c, d, a, b, x[ 9], S23); /* 23 */ +GG (b, c, d, a, x[13], S24); /* 24 */ +GG (a, b, c, d, x[ 2], S21); /* 25 */ +GG (d, a, b, c, x[ 6], S22); /* 26 */ +GG (c, d, a, b, x[10], S23); /* 27 */ +GG (b, c, d, a, x[14], S24); /* 28 */ +GG (a, b, c, d, x[ 3], S21); /* 29 */ +GG (d, a, b, c, x[ 7], S22); /* 30 */ +GG (c, d, a, b, x[11], S23); /* 31 */ +GG (b, c, d, a, x[15], S24); /* 32 */ + +/* Round 3 */ +HH (a, b, c, d, x[ 0], S31); /* 33 */ +HH (d, a, b, c, x[ 8], S32); /* 34 */ +HH (c, d, a, b, x[ 4], S33); /* 35 */ +HH (b, c, d, a, x[12], S34); /* 36 */ +HH (a, b, c, d, x[ 2], S31); /* 37 */ +HH (d, a, b, c, x[10], S32); /* 38 */ +HH (c, d, a, b, x[ 6], S33); /* 39 */ +HH (b, c, d, a, x[14], S34); /* 40 */ +HH (a, b, c, d, x[ 1], S31); /* 41 */ +HH (d, a, b, c, x[ 9], S32); /* 42 */ +HH (c, d, a, b, x[ 5], S33); /* 43 */ +HH (b, c, d, a, x[13], S34); /* 44 */ +HH (a, b, c, d, x[ 3], S31); /* 45 */ +HH (d, a, b, c, x[11], S32); /* 46 */ +HH (c, d, a, b, x[ 7], S33); /* 47 */ +HH (b, c, d, a, x[15], S34); /* 48 */ + +state[0] += a; +state[1] += b; +state[2] += c; +state[3] += d; + + /* Zeroize sensitive information.*/ + memset ((POINTER)x, 0, sizeof (x)); +} + + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */ +static void Encode (unsigned char *output, UINT4 *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */ +static void Decode (UINT4 *output, unsigned char *input, unsigned int len) +{ +unsigned int i, j; + +for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +//=================================================================== + +unsigned Com_BlockChecksum (void *buffer, int length) +{ + int digest[4]; + unsigned val; + MD4_CTX ctx; + + MD4Init (&ctx); + MD4Update (&ctx, (unsigned char *)buffer, length); + MD4Final ( (unsigned char *)digest, &ctx); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} diff --git a/tools/quake3/common/mutex.c b/tools/quake3/common/mutex.c new file mode 100644 index 00000000..38a383d6 --- /dev/null +++ b/tools/quake3/common/mutex.c @@ -0,0 +1,197 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "cmdlib.h" +#include "qthreads.h" +#include "mutex.h" + +/* +=================================================================== + +WIN32 + +=================================================================== +*/ +#ifdef WIN32 + +#define USED + +#include + +void MutexLock (mutex_t *m) +{ + CRITICAL_SECTION *crit; + + if (!m) + return; + crit = (CRITICAL_SECTION *) m; + EnterCriticalSection (crit); +} + +void MutexUnlock (mutex_t *m) +{ + CRITICAL_SECTION *crit; + + if (!m) + return; + crit = (CRITICAL_SECTION *) m; + LeaveCriticalSection (crit); +} + +mutex_t *MutexAlloc(void) +{ + CRITICAL_SECTION *crit; + + if (numthreads == 1) + return NULL; + crit = (CRITICAL_SECTION *) safe_malloc(sizeof(CRITICAL_SECTION)); + InitializeCriticalSection (crit); + return (void *) crit; +} + +#endif + +/* +=================================================================== + +OSF1 + +=================================================================== +*/ + +#ifdef __osf__ +#define USED + +#include + +void MutexLock (mutex_t *m) +{ + pthread_mutex_t *my_mutex; + + if (!m) + return; + my_mutex = (pthread_mutex_t *) m; + pthread_mutex_lock (my_mutex); +} + +void MutexUnlock (mutex_t *m) +{ + pthread_mutex_t *my_mutex; + + if (!m) + return; + my_mutex = (pthread_mutex_t *) m; + pthread_mutex_unlock (my_mutex); +} + +mutex_t *MutexAlloc(void) +{ + pthread_mutex_t *my_mutex; + pthread_mutexattr_t mattrib; + + if (numthreads == 1) + return NULL; + my_mutex = safe_malloc (sizeof(*my_mutex)); + if (pthread_mutexattr_create (&mattrib) == -1) + Error ("pthread_mutex_attr_create failed"); + if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1) + Error ("pthread_mutexattr_setkind_np failed"); + if (pthread_mutex_init (my_mutex, mattrib) == -1) + Error ("pthread_mutex_init failed"); + return (void *) my_mutex; +} + +#endif + +/* +=================================================================== + +IRIX + +=================================================================== +*/ + +#ifdef _MIPS_ISA +#define USED + +#include +#include +#include +#include + +void MutexLock (mutex_t *m) +{ + abilock_t *lck; + + if (!m) + return; + lck = (abilock_t *) m; + spin_lock (lck); +} + +void MutexUnlock (mutex_t *m) +{ + abilock_t *lck; + + if (!m) + return; + lck = (abilock_t *) m; + release_lock (lck); +} + +mutex_t *MutexAlloc(void) +{ + abilock_t *lck; + + if (numthreads == 1) + return NULL; + lck = (abilock_t *) safe_malloc(sizeof(abilock_t)); + init_lock (lck); + return (void *) lck; +} + +#endif + +/* +======================================================================= + + SINGLE THREAD + +======================================================================= +*/ + +#ifndef USED + +void MutexLock (mutex_t *m) +{ +} + +void MutexUnlock (mutex_t *m) +{ +} + +mutex_t *MutexAlloc(void) +{ + return NULL; +} + +#endif diff --git a/tools/quake3/common/mutex.h b/tools/quake3/common/mutex.h new file mode 100644 index 00000000..aa929923 --- /dev/null +++ b/tools/quake3/common/mutex.h @@ -0,0 +1,28 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + + +typedef void *mutex_t; + +void MutexLock (mutex_t *m); +void MutexUnlock (mutex_t *m); +mutex_t *MutexAlloc(void); diff --git a/tools/quake3/common/polylib.c b/tools/quake3/common/polylib.c new file mode 100644 index 00000000..21067ca2 --- /dev/null +++ b/tools/quake3/common/polylib.c @@ -0,0 +1,748 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#include "cmdlib.h" +#include "mathlib.h" +#include "inout.h" +#include "polylib.h" +#include "qfiles.h" + + +extern int numthreads; + +// counters are only bumped when running single threaded, +// because they are an awefull coherence problem +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; + +#define BOGUS_RANGE WORLD_SIZE + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + Sys_Printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding (int points) +{ + winding_t *w; + int s; + + if (points >= MAX_POINTS_ON_WINDING) + Error ("AllocWinding failed: MAX_POINTS_ON_WINDING exceeded"); + + if (numthreads == 1) + { + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if (c_active_windings > c_peak_windings) + c_peak_windings = c_active_windings; + } + s = sizeof(vec_t)*3*points + sizeof(int); + w = safe_malloc (s); + memset (w, 0, s); + return w; +} + +void FreeWinding (winding_t *w) +{ + if (*(unsigned *)w == 0xdeaddead) + Error ("FreeWinding: freed a freed winding"); + *(unsigned *)w = 0xdeaddead; + + if (numthreads == 1) + c_active_windings--; + free (w); +} + +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints (winding_t *w) +{ + int i, j, k; + vec3_t v1, v2; + int nump; + vec3_t p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for (i=0 ; inumpoints ; i++) + { + j = (i+1)%w->numpoints; + k = (i+w->numpoints-1)%w->numpoints; + VectorSubtract (w->p[j], w->p[i], v1); + VectorSubtract (w->p[i], w->p[k], v2); + VectorNormalize(v1,v1); + VectorNormalize(v2,v2); + if (DotProduct(v1, v2) < 0.999) + { + VectorCopy (w->p[i], p[nump]); + nump++; + } + } + + if (nump == w->numpoints) + return; + + if (numthreads == 1) + c_removed += w->numpoints - nump; + w->numpoints = nump; + memcpy (w->p, p, nump*sizeof(p[0])); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist) +{ + vec3_t v1, v2; + + VectorSubtract (w->p[1], w->p[0], v1); + VectorSubtract (w->p[2], w->p[0], v2); + CrossProduct (v2, v1, normal); + VectorNormalize (normal, normal); + *dist = DotProduct (w->p[0], normal); + +} + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea (winding_t *w) +{ + int i; + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + total += 0.5 * VectorLength ( cross ); + } + return total; +} + +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs) +{ + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter (winding_t *w, vec3_t center) +{ + int i; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->p[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist) +{ + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + winding_t *w; + +// find the major axis + + max = -BOGUS_RANGE; + x = -1; + for (i=0 ; i<3; i++) + { + v = fabs(normal[i]); + if (v > max) + { + x = i; + max = v; + } + } + if (x==-1) + Error ("BaseWindingForPlane: no axis found"); + + VectorCopy (vec3_origin, vup); + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct (vup, normal); + VectorMA (vup, -v, normal, vup); + VectorNormalize (vup, vup); + + VectorScale (normal, dist, org); + + CrossProduct (vup, normal, vright); + + // LordHavoc: this has to use *2 because otherwise some created points may + // be inside the world (think of a diagonal case), and any brush with such + // points should be removed, failure to detect such cases is disasterous + VectorScale (vup, MAX_WORLD_COORD*2, vup); + VectorScale (vright, MAX_WORLD_COORD*2, vright); + + // project a really big axis aligned box onto the plane + w = AllocWinding (4); + + VectorSubtract (org, vright, w->p[0]); + VectorAdd (w->p[0], vup, w->p[0]); + + VectorAdd (org, vright, w->p[1]); + VectorAdd (w->p[1], vup, w->p[1]); + + VectorAdd (org, vright, w->p[2]); + VectorSubtract (w->p[2], vup, w->p[2]); + + VectorSubtract (org, vright, w->p[3]); + VectorSubtract (w->p[3], vup, w->p[3]); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding (winding_t *w) +{ + int size; + winding_t *c; + + c = AllocWinding (w->numpoints); + size = (int)((winding_t *)0)->p[w->numpoints]; + memcpy (c, w, size); + return c; +} + +/* +================== +ReverseWinding +================== +*/ +winding_t *ReverseWinding (winding_t *w) +{ + int i; + winding_t *c; + + c = AllocWinding (w->numpoints); + for (i=0 ; inumpoints ; i++) + { + VectorCopy (w->p[w->numpoints-1-i], c->p[i]); + } + c->numpoints = w->numpoints; + return c; +} + + +/* +============= +ClipWindingEpsilon +============= +*/ +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = CopyWinding (in); + return; + } + if (!counts[1]) + { + *front = CopyWinding (in); + return; + } + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); +} + + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace (winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon) +{ + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + { + FreeWinding (in); + *inout = NULL; + return; + } + if (!counts[1]) + return; // inout stays the same + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + } + + if (f->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); + + FreeWinding (in); + *inout = f; +} + + +/* +================= +ChopWinding + +Returns the fragment of in that is on the front side +of the cliping plane. The original is freed. +================= +*/ +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist) +{ + winding_t *f, *b; + + ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b); + FreeWinding (in); + if (b) + FreeWinding (b); + return f; +} + + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding (winding_t *w) +{ + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if (w->numpoints < 3) + Error ("CheckWinding: %i points",w->numpoints); + + area = WindingArea(w); + if (area < 1) + Error ("CheckWinding: %f area", area); + + WindingPlane (w, facenormal, &facedist); + + for (i=0 ; inumpoints ; i++) + { + p1 = w->p[i]; + + for (j=0 ; j<3 ; j++) + if (p1[j] > MAX_WORLD_COORD || p1[j] < MIN_WORLD_COORD) + Error ("CheckFace: MAX_WORLD_COORD exceeded: %f",p1[j]); + + j = i+1 == w->numpoints ? 0 : i+1; + + // check the point is on the face plane + d = DotProduct (p1, facenormal) - facedist; + if (d < -ON_EPSILON || d > ON_EPSILON) + Error ("CheckWinding: point off plane"); + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + Error ("CheckWinding: degenerate edge"); + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize (edgenormal, edgenormal); + edgedist = DotProduct (p1, edgenormal); + edgedist += ON_EPSILON; + + // all other points must be on front side + for (j=0 ; jnumpoints ; j++) + { + if (j == i) + continue; + d = DotProduct (w->p[j], edgenormal); + if (d > edgedist) + Error ("CheckWinding: non-convex"); + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist) +{ + qboolean front, back; + int i; + vec_t d; + + front = qfalse; + back = qfalse; + for (i=0 ; inumpoints ; i++) + { + d = DotProduct (w->p[i], normal) - dist; + if (d < -ON_EPSILON) + { + if (front) + return SIDE_CROSS; + back = qtrue; + continue; + } + if (d > ON_EPSILON) + { + if (back) + return SIDE_CROSS; + front = qtrue; + continue; + } + } + + if (back) + return SIDE_BACK; + if (front) + return SIDE_FRONT; + return SIDE_ON; +} + + +/* +================= +AddWindingToConvexHull + +Both w and *hull are on the same plane +================= +*/ +#define MAX_HULL_POINTS 128 +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ) { + int i, j, k; + float *p, *copy; + vec3_t dir; + float d; + int numHullPoints, numNew; + vec3_t hullPoints[MAX_HULL_POINTS]; + vec3_t newHullPoints[MAX_HULL_POINTS]; + vec3_t hullDirs[MAX_HULL_POINTS]; + qboolean hullSide[MAX_HULL_POINTS]; + qboolean outside; + + if ( !*hull ) { + *hull = CopyWinding( w ); + return; + } + + numHullPoints = (*hull)->numpoints; + memcpy( hullPoints, (*hull)->p, numHullPoints * sizeof(vec3_t) ); + + for ( i = 0 ; i < w->numpoints ; i++ ) { + p = w->p[i]; + + // calculate hull side vectors + for ( j = 0 ; j < numHullPoints ; j++ ) { + k = ( j + 1 ) % numHullPoints; + + VectorSubtract( hullPoints[k], hullPoints[j], dir ); + VectorNormalize( dir, dir ); + CrossProduct( normal, dir, hullDirs[j] ); + } + + outside = qfalse; + for ( j = 0 ; j < numHullPoints ; j++ ) { + VectorSubtract( p, hullPoints[j], dir ); + d = DotProduct( dir, hullDirs[j] ); + if ( d >= ON_EPSILON ) { + outside = qtrue; + } + if ( d >= -ON_EPSILON ) { + hullSide[j] = qtrue; + } else { + hullSide[j] = qfalse; + } + } + + // if the point is effectively inside, do nothing + if ( !outside ) { + continue; + } + + // find the back side to front side transition + for ( j = 0 ; j < numHullPoints ; j++ ) { + if ( !hullSide[ j % numHullPoints ] && hullSide[ (j + 1) % numHullPoints ] ) { + break; + } + } + if ( j == numHullPoints ) { + continue; + } + + // insert the point here + VectorCopy( p, newHullPoints[0] ); + numNew = 1; + + // copy over all points that aren't double fronts + j = (j+1)%numHullPoints; + for ( k = 0 ; k < numHullPoints ; k++ ) { + if ( hullSide[ (j+k) % numHullPoints ] && hullSide[ (j+k+1) % numHullPoints ] ) { + continue; + } + copy = hullPoints[ (j+k+1) % numHullPoints ]; + VectorCopy( copy, newHullPoints[numNew] ); + numNew++; + } + + numHullPoints = numNew; + memcpy( hullPoints, newHullPoints, numHullPoints * sizeof(vec3_t) ); + } + + FreeWinding( *hull ); + w = AllocWinding( numHullPoints ); + w->numpoints = numHullPoints; + *hull = w; + memcpy( w->p, hullPoints, numHullPoints * sizeof(vec3_t) ); +} + + diff --git a/tools/quake3/common/polylib.h b/tools/quake3/common/polylib.h new file mode 100644 index 00000000..e58875a2 --- /dev/null +++ b/tools/quake3/common/polylib.h @@ -0,0 +1,57 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +typedef struct +{ + int numpoints; + vec3_t p[4]; // variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 64 + +// you can define on_epsilon in the makefile as tighter +#ifndef ON_EPSILON +#define ON_EPSILON 0.1 +#endif + +winding_t *AllocWinding (int points); +vec_t WindingArea (winding_t *w); +void WindingCenter (winding_t *w, vec3_t center); +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back); +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist); +winding_t *CopyWinding (winding_t *w); +winding_t *ReverseWinding (winding_t *w); +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist); +void CheckWinding (winding_t *w); +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist); +void RemoveColinearPoints (winding_t *w); +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist); +void FreeWinding (winding_t *w); +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs); + +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ); + +void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon); +// frees the original if clipped + +void pw(winding_t *w); diff --git a/tools/quake3/common/polyset.h b/tools/quake3/common/polyset.h new file mode 100644 index 00000000..12993999 --- /dev/null +++ b/tools/quake3/common/polyset.h @@ -0,0 +1,51 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __POLYSET_H__ +#define __POLYSET_H__ + +#define POLYSET_MAXTRIANGLES 4096 +#define POLYSET_MAXPOLYSETS 64 + +typedef float st_t[2]; +typedef float rgb_t[3]; + +typedef struct { + vec3_t verts[3]; + vec3_t normals[3]; + st_t texcoords[3]; +} triangle_t; + +typedef struct +{ + char name[100]; + char materialname[100]; + triangle_t *triangles; + int numtriangles; +} polyset_t; + +polyset_t *Polyset_LoadSets( const char *file, int *numpolysets, int maxTrisPerSet ); +polyset_t *Polyset_CollapseSets( polyset_t *psets, int numpolysets ); +polyset_t *Polyset_SplitSets( polyset_t *psets, int numpolysets, int *pNumNewPolysets, int maxTris ); +void Polyset_SnapSets( polyset_t *psets, int numpolysets ); +void Polyset_ComputeNormals( polyset_t *psets, int numpolysets ); + +#endif diff --git a/tools/quake3/common/qfiles.h b/tools/quake3/common/qfiles.h new file mode 100644 index 00000000..29e94978 --- /dev/null +++ b/tools/quake3/common/qfiles.h @@ -0,0 +1,489 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef __QFILES_H__ +#define __QFILES_H__ + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +// surface geometry should not exceed these limits +#define SHADER_MAX_VERTEXES 1000 +#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES) + + +// the maximum size of game reletive pathnames +#define MAX_QPATH 64 + +/* +======================================================================== + +QVM files + +======================================================================== +*/ + +#define VM_MAGIC 0x12721444 +typedef struct { + int vmMagic; + + int instructionCount; + + int codeOffset; + int codeLength; + + int dataOffset; + int dataLength; + int litLength; // ( dataLength - litLength ) should be byteswapped on load + int bssLength; // zero filled memory appended to datalength +} vmHeader_t; + + +/* +======================================================================== + +PCX files are used for 8 bit images + +======================================================================== +*/ + +typedef struct { + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +/* +======================================================================== + +TGA files are used for 24/32 bit images + +======================================================================== +*/ + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + + + +/* +======================================================================== + +.MD3 triangle model file format + +======================================================================== +*/ + +#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD3_VERSION 15 + +// limits +#define MD3_MAX_LODS 4 +#define MD3_MAX_TRIANGLES 8192 // per surface +#define MD3_MAX_VERTS 4096 // per surface +#define MD3_MAX_SHADERS 256 // per surface +#define MD3_MAX_FRAMES 1024 // per model +#define MD3_MAX_SURFACES 32 // per model +#define MD3_MAX_TAGS 16 // per frame + +// vertex scales +#define MD3_XYZ_SCALE (1.0/64) + +typedef struct md3Frame_s { + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { + char name[MAX_QPATH]; // tag name + vec3_t origin; + vec3_t axis[3]; +} md3Tag_t; + +/* +** md3Surface_t +** +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** shaders sizeof( md3Shader_t ) * numShaders +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames +*/ +typedef struct { + int ident; // + + char name[MAX_QPATH]; // polyset name + + int flags; + int numFrames; // all surfaces in a model should have the same + + int numShaders; // all surfaces in a model should have the same + int numVerts; + + int numTriangles; + int ofsTriangles; + + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames + + int ofsEnd; // next surface follows +} md3Surface_t; + +typedef struct { + char name[MAX_QPATH]; + int shaderIndex; // for in-game use +} md3Shader_t; + +typedef struct { + int indexes[3]; +} md3Triangle_t; + +typedef struct { + float st[2]; +} md3St_t; + +typedef struct { + short xyz[3]; + short normal; +} md3XyzNormal_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + int flags; + + int numFrames; + int numTags; + int numSurfaces; + + int numSkins; + + int ofsFrames; // offset for first frame + int ofsTags; // numFrames * numTags + int ofsSurfaces; // first surface, others follow + + int ofsEnd; // end of file +} md3Header_t; + +/* +============================================================================== + +MD4 file format + +============================================================================== +*/ + +#define MD4_IDENT (('4'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD4_VERSION 1 +#define MD4_MAX_BONES 128 + +typedef struct { + int boneIndex; // these are indexes into the boneReferences, + float boneWeight; // not the global per-frame bone list +} md4Weight_t; + +typedef struct { + vec3_t vertex; + vec3_t normal; + float texCoords[2]; + int numWeights; + md4Weight_t weights[1]; // variable sized +} md4Vertex_t; + +typedef struct { + int indexes[3]; +} md4Triangle_t; + +typedef struct { + int ident; + + char name[MAX_QPATH]; // polyset name + char shader[MAX_QPATH]; + int shaderIndex; // for in-game use + + int ofsHeader; // this will be a negative number + + int numVerts; + int ofsVerts; + + int numTriangles; + int ofsTriangles; + + // Bone references are a set of ints representing all the bones + // present in any vertex weights for this surface. This is + // needed because a model may have surfaces that need to be + // drawn at different sort times, and we don't want to have + // to re-interpolate all the bones for each surface. + int numBoneReferences; + int ofsBoneReferences; + + int ofsEnd; // next surface follows +} md4Surface_t; + +typedef struct { + float matrix[3][4]; +} md4Bone_t; + +typedef struct { + vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame + vec3_t localOrigin; // midpoint of bounds, used for sphere cull + float radius; // dist from localOrigin to corner + char name[16]; + md4Bone_t bones[1]; // [numBones] +} md4Frame_t; + +typedef struct { + int numSurfaces; + int ofsSurfaces; // first surface, others follow + int ofsEnd; // next lod follows +} md4LOD_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + // frames and bones are shared by all levels of detail + int numFrames; + int numBones; + int ofsFrames; // md4Frame_t[numFrames] + + // each level of detail has completely separate sets of surfaces + int numLODs; + int ofsLODs; + + int ofsEnd; // end of file +} md4Header_t; + + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + + +#define BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'I') + // little-endian "IBSP" + +//#define BSP_VERSION 46 +#define Q3_BSP_VERSION 46 +#define WOLF_BSP_VERSION 47 + +// there shouldn't be any problem with increasing these values at the +// expense of more memory allocation in the utilities +#define MAX_MAP_MODELS 0x400 +#define MAX_MAP_BRUSHES 0x8000 +#define MAX_MAP_ENTITIES 0x800 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_SHADERS 0x400 + +#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! +#define MAX_MAP_FOGS 0x100 +#define MAX_MAP_PLANES 0x20000 +#define MAX_MAP_NODES 0x20000 +#define MAX_MAP_BRUSHSIDES 0x40000 //% 0x20000 /* ydnar */ +#define MAX_MAP_LEAFS 0x20000 +#define MAX_MAP_LEAFFACES 0x20000 +#define MAX_MAP_LEAFBRUSHES 0x40000 +#define MAX_MAP_PORTALS 0x20000 +#define MAX_MAP_LIGHTING 0x800000 +#define MAX_MAP_LIGHTGRID 0x800000 +#define MAX_MAP_VISIBILITY 0x200000 + +#define MAX_MAP_DRAW_SURFS 0x20000 +#define MAX_MAP_DRAW_VERTS 0x80000 +#define MAX_MAP_DRAW_INDEXES 0x80000 + + +// key / value pair sizes in the entities lump +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +// the editor uses these predefined yaw angles to orient entities up or down +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +#define LIGHTMAP_WIDTH 128 +#define LIGHTMAP_HEIGHT 128 + +#define MIN_WORLD_COORD (-65536) +#define MAX_WORLD_COORD (65536) +#define WORLD_SIZE (MAX_WORLD_COORD - MIN_WORLD_COORD) + +//============================================================================= + + +typedef struct { + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define HEADER_LUMPS 17 + +typedef struct { + int ident; + int version; + + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct { + float mins[3], maxs[3]; + int firstSurface, numSurfaces; + int firstBrush, numBrushes; +} dmodel_t; + +typedef struct { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; +} dshader_t; + +// planes x^1 is allways the opposite of plane x + +typedef struct { + float normal[3]; + float dist; +} dplane_t; + +typedef struct { + int planeNum; + int children[2]; // negative numbers are -(leafs+1), not nodes + int mins[3]; // for frustom culling + int maxs[3]; +} dnode_t; + +typedef struct { + int cluster; // -1 = opaque cluster (do I still store these?) + int area; + + int mins[3]; // for frustum culling + int maxs[3]; + + int firstLeafSurface; + int numLeafSurfaces; + + int firstLeafBrush; + int numLeafBrushes; +} dleaf_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + int shaderNum; +} dbrushside_t; + +typedef struct { + int firstSide; + int numSides; + int shaderNum; // the shader that determines the contents flags +} dbrush_t; + +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} dfog_t; + +typedef struct { + vec3_t xyz; + float st[2]; + float lightmap[2]; + vec3_t normal; + byte color[4]; +} drawVert_t; + +typedef enum { + MST_BAD, + MST_PLANAR, + MST_PATCH, + MST_TRIANGLE_SOUP, + MST_FLARE +} mapSurfaceType_t; + +typedef struct { + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + int lightmapNum; + int lightmapX, lightmapY; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds + + int patchWidth; + int patchHeight; +} dsurface_t; + + +#endif diff --git a/tools/quake3/common/qthreads.h b/tools/quake3/common/qthreads.h new file mode 100644 index 00000000..ab806030 --- /dev/null +++ b/tools/quake3/common/qthreads.h @@ -0,0 +1,31 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +extern int numthreads; + +void ThreadSetDefault (void); +int GetThreadWork (void); +void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)); +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)); +void ThreadLock (void); +void ThreadUnlock (void); + diff --git a/tools/quake3/common/scriplib.c b/tools/quake3/common/scriplib.c new file mode 100644 index 00000000..47196e53 --- /dev/null +++ b/tools/quake3/common/scriplib.c @@ -0,0 +1,409 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// scriplib.c + +#include "cmdlib.h" +#include "mathlib.h" +#include "inout.h" +#include "scriplib.h" +#include "vfs.h" + +/* +============================================================================= + + PARSING STUFF + +============================================================================= +*/ + +typedef struct +{ + char filename[1024]; + char *buffer,*script_p,*end_p; + int line; +} script_t; + +#define MAX_INCLUDES 8 +script_t scriptstack[MAX_INCLUDES]; +script_t *script; +int scriptline; + +char token[MAXTOKEN]; +qboolean endofscript; +qboolean tokenready; // only qtrue if UnGetToken was just called + +/* +============== +AddScriptToStack +============== +*/ +void AddScriptToStack (const char *filename, int index) +{ + int size; + + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + strcpy (script->filename, ExpandPath (filename)); + + size = vfsLoadFile (script->filename, (void **)&script->buffer, index); + + if (size == -1) + Sys_Printf ("Script file %s was not found\n", script->filename); + else + { + if (index > 0) + Sys_Printf ("entering %s (%d)\n", script->filename, index+1); + else + Sys_Printf ("entering %s\n", script->filename); + } + + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + size; +} + + +/* +============== +LoadScriptFile +============== +*/ +void LoadScriptFile (const char *filename, int index) +{ + script = scriptstack; + AddScriptToStack (filename, index); + + endofscript = qfalse; + tokenready = qfalse; +} + + +/* +============== +ParseFromMemory +============== +*/ +void ParseFromMemory (char *buffer, int size) +{ + script = scriptstack; + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + strcpy (script->filename, "memory buffer" ); + + script->buffer = buffer; + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + + endofscript = qfalse; + tokenready = qfalse; +} + + +/* +============== +UnGetToken + +Signals that the current token was not used, and should be reported +for the next GetToken. Note that + +GetToken (qtrue); +UnGetToken (); +GetToken (qfalse); + +could cross a line boundary. +============== +*/ +void UnGetToken (void) +{ + tokenready = qtrue; +} + + +qboolean EndOfScript (qboolean crossline) +{ + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + + if (!strcmp (script->filename, "memory buffer")) + { + endofscript = qtrue; + return qfalse; + } + + if( script->buffer == NULL ) + Sys_Printf( "WARNING: Attempt to free already freed script buffer\n" ); + else + free( script->buffer ); + script->buffer = NULL; + if (script == scriptstack+1) + { + endofscript = qtrue; + return qfalse; + } + script--; + scriptline = script->line; + Sys_Printf ("returning to %s\n", script->filename); + return GetToken (crossline); +} + +/* +============== +GetToken +============== +*/ +qboolean GetToken (qboolean crossline) +{ + char *token_p; + + + /* ydnar: dummy testing */ + if( script == NULL || script->buffer == NULL ) + return qfalse; + + if (tokenready) // is a token already waiting? + { + tokenready = qfalse; + return qtrue; + } + + if ((script->script_p >= script->end_p) || (script->script_p == NULL)) + return EndOfScript (crossline); + +// +// skip space +// +skipspace: + while (*script->script_p <= 32) + { + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + if (*script->script_p++ == '\n') + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + script->line++; + scriptline = script->line; + } + } + + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + + // ; # // comments + if (*script->script_p == ';' || *script->script_p == '#' + || ( script->script_p[0] == '/' && script->script_p[1] == '/') ) + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + while (*script->script_p++ != '\n') + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + script->line++; + scriptline = script->line; + goto skipspace; + } + + // /* */ comments + if (script->script_p[0] == '/' && script->script_p[1] == '*') + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + script->script_p+=2; + while (script->script_p[0] != '*' && script->script_p[1] != '/') + { + if ( *script->script_p == '\n' ) + { + script->line++; + scriptline = script->line; + } + script->script_p++; + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + } + script->script_p += 2; + goto skipspace; + } + +// +// copy token +// + token_p = token; + + if (*script->script_p == '"') + { + // quoted token + script->script_p++; + while (*script->script_p != '"') + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + script->script_p++; + } + else // regular token + while ( *script->script_p > 32 && *script->script_p != ';') + { + *token_p++ = *script->script_p++; + if (script->script_p == script->end_p) + break; + if (token_p == &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + } + + *token_p = 0; + + if (!strcmp (token, "$include")) + { + GetToken (qfalse); + AddScriptToStack (token, 0); + return GetToken (crossline); + } + + return qtrue; +} + + +/* +============== +TokenAvailable + +Returns qtrue if there is another token on the line +============== +*/ +qboolean TokenAvailable (void) { + int oldLine, oldScriptLine; + qboolean r; + + /* save */ + oldLine = scriptline; + oldScriptLine = script->line; + + /* test */ + r = GetToken( qtrue ); + if ( !r ) { + return qfalse; + } + UnGetToken(); + if ( oldLine == scriptline ) { + return qtrue; + } + + /* restore */ + //% scriptline = oldLine; + //% script->line = oldScriptLine; + + return qfalse; +} + + +//===================================================================== + + +void MatchToken( char *match ) { + GetToken( qtrue ); + + if ( strcmp( token, match ) ) { + Error( "MatchToken( \"%s\" ) failed at line %i in file %s", match, scriptline, script->filename); + } +} + + +void Parse1DMatrix (int x, vec_t *m) { + int i; + + MatchToken( "(" ); + + for (i = 0 ; i < x ; i++) { + GetToken( qfalse ); + m[i] = atof(token); + } + + MatchToken( ")" ); +} + +void Parse2DMatrix (int y, int x, vec_t *m) { + int i; + + MatchToken( "(" ); + + for (i = 0 ; i < y ; i++) { + Parse1DMatrix (x, m + i * x); + } + + MatchToken( ")" ); +} + +void Parse3DMatrix (int z, int y, int x, vec_t *m) { + int i; + + MatchToken( "(" ); + + for (i = 0 ; i < z ; i++) { + Parse2DMatrix (y, x, m + i * x*y); + } + + MatchToken( ")" ); +} + + +void Write1DMatrix (FILE *f, int x, vec_t *m) { + int i; + + fprintf (f, "( "); + for (i = 0 ; i < x ; i++) { + if (m[i] == (int)m[i] ) { + fprintf (f, "%i ", (int)m[i]); + } else { + fprintf (f, "%f ", m[i]); + } + } + fprintf (f, ")"); +} + +void Write2DMatrix (FILE *f, int y, int x, vec_t *m) { + int i; + + fprintf (f, "( "); + for (i = 0 ; i < y ; i++) { + Write1DMatrix (f, x, m + i*x); + fprintf (f, " "); + } + fprintf (f, ")\n"); +} + + +void Write3DMatrix (FILE *f, int z, int y, int x, vec_t *m) { + int i; + + fprintf (f, "(\n"); + for (i = 0 ; i < z ; i++) { + Write2DMatrix (f, y, x, m + i*(x*y) ); + } + fprintf (f, ")\n"); +} + diff --git a/tools/quake3/common/scriplib.h b/tools/quake3/common/scriplib.h new file mode 100644 index 00000000..1e3f2c31 --- /dev/null +++ b/tools/quake3/common/scriplib.h @@ -0,0 +1,55 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// scriplib.h + +#ifndef __CMDLIB__ +#include "../common/cmdlib.h" +#endif +#ifndef __MATHLIB__ +#include "mathlib.h" +#endif + +#define MAXTOKEN 1024 + +extern char token[MAXTOKEN]; +extern char *scriptbuffer,*script_p,*scriptend_p; +extern int grabbed; +extern int scriptline; +extern qboolean endofscript; + + +void LoadScriptFile (const char *filename, int index); +void ParseFromMemory (char *buffer, int size); + +qboolean GetToken (qboolean crossline); +void UnGetToken (void); +qboolean TokenAvailable (void); + +void MatchToken( char *match ); + +void Parse1DMatrix (int x, vec_t *m); +void Parse2DMatrix (int y, int x, vec_t *m); +void Parse3DMatrix (int z, int y, int x, vec_t *m); + +void Write1DMatrix (FILE *f, int x, vec_t *m); +void Write2DMatrix (FILE *f, int y, int x, vec_t *m); +void Write3DMatrix (FILE *f, int z, int y, int x, vec_t *m); diff --git a/tools/quake3/common/surfaceflags.h b/tools/quake3/common/surfaceflags.h new file mode 100644 index 00000000..d7ab2ba7 --- /dev/null +++ b/tools/quake3/common/surfaceflags.h @@ -0,0 +1,114 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// Copyright (C) 1999-2000 Id Software, Inc. +// +// This file must be identical in the quake and utils directories + +// contents flags are seperate bits +// a given brush can contribute multiple content bits + +// these definitions also need to be in q_shared.h! + +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_FOG 64 + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 +//bot specific contents types +#define CONTENTS_TELEPORTER 0x40000 +#define CONTENTS_JUMPPAD 0x80000 +#define CONTENTS_CLUSTERPORTAL 0x100000 +#define CONTENTS_DONOTENTER 0x200000 +#define CONTENTS_BOTCLIP 0x400000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_BODY 0x2000000 // should never be on a brush, only in game +#define CONTENTS_CORPSE 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes not used for the bsp +#define CONTENTS_STRUCTURAL 0x10000000 // brushes used for the bsp +#define CONTENTS_TRANSLUCENT 0x20000000 // don't consume surface fragments inside +#define CONTENTS_TRIGGER 0x40000000 +#define CONTENTS_NODROP 0x80000000 // don't leave bodies or items (death fog, lava) + +#define SURF_NODAMAGE 0x1 // never give falling damage +#define SURF_SLICK 0x2 // effects game physics +#define SURF_SKY 0x4 // lighting from environment map +#define SURF_LADDER 0x8 +#define SURF_NOIMPACT 0x10 // don't make missile explosions +#define SURF_NOMARKS 0x20 // don't leave missile marks +#define SURF_FLESH 0x40 // make flesh sounds and effects +#define SURF_NODRAW 0x80 // don't generate a drawsurface at all +#define SURF_HINT 0x100 // make a primary bsp splitter +#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes +#define SURF_NOLIGHTMAP 0x400 // surface doesn't need a lightmap +#define SURF_POINTLIGHT 0x800 // generate lighting info at vertexes +#define SURF_METALSTEPS 0x1000 // clanking footsteps +#define SURF_NOSTEPS 0x2000 // no footstep sounds +#define SURF_NONSOLID 0x4000 // don't collide against curves with this set +#define SURF_LIGHTFILTER 0x8000 // act as a light filter during q3map -light +#define SURF_ALPHASHADOW 0x10000 // do per-pixel light shadow casting in q3map +#define SURF_NODLIGHT 0x20000 // don't dlight even if solid (solid lava, skies) +#define SURF_DUST 0x40000 // leave a dust trail when walking on this surface + + + + +/* ydnar flags */ + +#define CONTENTS_OPAQUE 0x02 +#define CONTENTS_LIGHTGRID 0x04 + +#define SURF_VERTEXLIT (SURF_POINTLIGHT | SURF_NOLIGHTMAP) + + + +/* wolfenstein flags (collisions with valid q3a flags are noted) */ + +#define CONTENTS_MISSILECLIP 0x80 +#define CONTENTS_ITEM 0x100 +#define CONTENTS_AI_NOSIGHT 0x1000 +#define CONTENTS_CLIPSHOT 0x2000 +#define CONTENTS_DONOTENTER_LARGE 0x400000 /* CONTENTS_BOTCLIP */ + +#define SURF_CERAMIC 0x40 /* SURF_FLESH */ +#define SURF_METAL 0x1000 /* SURF_METALSTEPS */ +#define SURF_WOOD 0x40000 /* SURF_DUST */ +#define SURF_GRASS 0x80000 +#define SURF_GRAVEL 0x100000 +#define SURF_GLASS 0x200000 +#define SURF_SNOW 0x400000 +#define SURF_ROOF 0x800000 +#define SURF_RUBBLE 0x1000000 +#define SURF_CARPET 0x2000000 +#define SURF_MONSTERSLICK 0x4000000 +#define SURF_MONSLICK_W 0x8000000 +#define SURF_MONSLICK_N 0x10000000 +#define SURF_MONSLICK_E 0x20000000 +#define SURF_MONSLICK_S 0x40000000 + + diff --git a/tools/quake3/common/threads.c b/tools/quake3/common/threads.c new file mode 100644 index 00000000..137597c7 --- /dev/null +++ b/tools/quake3/common/threads.c @@ -0,0 +1,620 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef WIN32 +// The below define is necessary to use +// pthreads extensions like pthread_mutexattr_settype +#define _GNU_SOURCE +#include +#endif + +#include "cmdlib.h" +#include "mathlib.h" +#include "inout.h" +#include "qthreads.h" + +#define MAX_THREADS 64 + +int dispatch; +int workcount; +int oldf; +qboolean pacifier; + +qboolean threaded; + +/* +============= +GetThreadWork + +============= +*/ +int GetThreadWork (void) +{ + int r; + int f; + + ThreadLock (); + + if (dispatch == workcount) + { + ThreadUnlock (); + return -1; + } + + f = 10*dispatch / workcount; + if (f != oldf) + { + oldf = f; + if (pacifier) + { + Sys_Printf ("%i...", f); + fflush( stdout ); /* ydnar */ + } + } + + r = dispatch; + dispatch++; + ThreadUnlock (); + + return r; +} + + +void (*workfunction) (int); + +void ThreadWorkerFunction (int threadnum) +{ + int work; + + while (1) + { + work = GetThreadWork (); + if (work == -1) + break; +//Sys_Printf ("thread %i, work %i\n", threadnum, work); + workfunction(work); + } +} + +void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + if (numthreads == -1) + ThreadSetDefault (); + workfunction = func; + RunThreadsOn (workcnt, showpacifier, ThreadWorkerFunction); +} + + +/* +=================================================================== + +WIN32 + +=================================================================== +*/ +#ifdef WIN32 + +#define USED + +#include + +int numthreads = -1; +CRITICAL_SECTION crit; +static int enter; + +void ThreadSetDefault (void) +{ + SYSTEM_INFO info; + + if (numthreads == -1) // not set manually + { + GetSystemInfo (&info); + numthreads = info.dwNumberOfProcessors; + if (numthreads < 1 || numthreads > 32) + numthreads = 1; + } + + Sys_Printf ("%i threads\n", numthreads); +} + + +void ThreadLock (void) +{ + if (!threaded) + return; + EnterCriticalSection (&crit); + if (enter) + Error ("Recursive ThreadLock\n"); + enter = 1; +} + +void ThreadUnlock (void) +{ + if (!threaded) + return; + if (!enter) + Error ("ThreadUnlock without lock\n"); + enter = 0; + LeaveCriticalSection (&crit); +} + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int threadid[MAX_THREADS]; + HANDLE threadhandle[MAX_THREADS]; + int i; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = qtrue; + + // + // run threads in parallel + // + InitializeCriticalSection (&crit); + + if (numthreads == 1) + { // use same thread + func (0); + } + else + { + for (i=0 ; i + +pthread_mutex_t *my_mutex; + +void ThreadLock (void) +{ + if (my_mutex) + pthread_mutex_lock (my_mutex); +} + +void ThreadUnlock (void) +{ + if (my_mutex) + pthread_mutex_unlock (my_mutex); +} + + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int i; + pthread_t work_threads[MAX_THREADS]; + pthread_addr_t status; + pthread_attr_t attrib; + pthread_mutexattr_t mattrib; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = qtrue; + + if (pacifier) + setbuf (stdout, NULL); + + if (!my_mutex) + { + my_mutex = safe_malloc (sizeof(*my_mutex)); + if (pthread_mutexattr_create (&mattrib) == -1) + Error ("pthread_mutex_attr_create failed"); + if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1) + Error ("pthread_mutexattr_setkind_np failed"); + if (pthread_mutex_init (my_mutex, mattrib) == -1) + Error ("pthread_mutex_init failed"); + } + + if (pthread_attr_create (&attrib) == -1) + Error ("pthread_attr_create failed"); + if (pthread_attr_setstacksize (&attrib, 0x100000) == -1) + Error ("pthread_attr_setstacksize failed"); + + for (i=0 ; i +#include +#include +#include + + +int numthreads = -1; +abilock_t lck; + +void ThreadSetDefault (void) +{ + if (numthreads == -1) + numthreads = prctl(PR_MAXPPROCS); + Sys_Printf ("%i threads\n", numthreads); + usconfig (CONF_INITUSERS, numthreads); +} + + +void ThreadLock (void) +{ + spin_lock (&lck); +} + +void ThreadUnlock (void) +{ + release_lock (&lck); +} + + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + int i; + int pid[MAX_THREADS]; + int start, end; + + start = I_FloatTime (); + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = qtrue; + + if (pacifier) + setbuf (stdout, NULL); + + init_lock (&lck); + + for (i=0 ; i 1) + Sys_Printf("threads: %d\n", numthreads); +} + +#include + +typedef struct pt_mutex_s +{ + pthread_t *owner; + pthread_mutex_t a_mutex; + pthread_cond_t cond; + unsigned int lock; +} pt_mutex_t; + +pt_mutex_t global_lock; + +void ThreadLock(void) +{ + pt_mutex_t *pt_mutex = &global_lock; + + if(!threaded) + return; + + pthread_mutex_lock(&pt_mutex->a_mutex); + if(pthread_equal(pthread_self(), (pthread_t)&pt_mutex->owner)) + pt_mutex->lock++; + else + { + if((!pt_mutex->owner) && (pt_mutex->lock == 0)) + { + pt_mutex->owner = (pthread_t *)pthread_self(); + pt_mutex->lock = 1; + } + else + { + while(1) + { + pthread_cond_wait(&pt_mutex->cond, &pt_mutex->a_mutex); + if((!pt_mutex->owner) && (pt_mutex->lock == 0)) + { + pt_mutex->owner = (pthread_t *)pthread_self(); + pt_mutex->lock = 1; + break; + } + } + } + } + pthread_mutex_unlock(&pt_mutex->a_mutex); +} + +void ThreadUnlock(void) +{ + pt_mutex_t *pt_mutex = &global_lock; + + if(!threaded) + return; + + pthread_mutex_lock(&pt_mutex->a_mutex); + pt_mutex->lock--; + + if(pt_mutex->lock == 0) + { + pt_mutex->owner = NULL; + pthread_cond_signal(&pt_mutex->cond); + } + + pthread_mutex_unlock(&pt_mutex->a_mutex); +} + +void recursive_mutex_init(pthread_mutexattr_t attribs) +{ + pt_mutex_t *pt_mutex = &global_lock; + + pt_mutex->owner = NULL; + if(pthread_mutex_init(&pt_mutex->a_mutex, &attribs) != 0) + Error("pthread_mutex_init failed\n"); + if(pthread_cond_init(&pt_mutex->cond, NULL) != 0) + Error("pthread_cond_init failed\n"); + + pt_mutex->lock = 0; +} + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) +{ + pthread_mutexattr_t mattrib; + pthread_t work_threads[MAX_THREADS]; + + int start, end; + int i=0, status=0; + + start = I_FloatTime (); + pacifier = showpacifier; + + dispatch = 0; + oldf = -1; + workcount = workcnt; + + if(numthreads == 1) + func(0); + else + { + threaded = qtrue; + + if(pacifier) + setbuf(stdout, NULL); + + if(pthread_mutexattr_init(&mattrib) != 0) + Error("pthread_mutexattr_init failed"); +#if __GLIBC_MINOR__ == 1 + if (pthread_mutexattr_settype(&mattrib, PTHREAD_MUTEX_FAST_NP) != 0) +#else + if (pthread_mutexattr_settype(&mattrib, PTHREAD_MUTEX_ADAPTIVE_NP) != 0) +#endif + Error ("pthread_mutexattr_settype failed"); + recursive_mutex_init(mattrib); + + for (i=0 ; i +#include "cmdlib.h" +#include "mathlib.h" +#include "polyset.h" +#include "trilib.h" + +// on disk representation of a face + + +#define FLOAT_START 99999.0 +#define FLOAT_END -FLOAT_START +#define MAGIC 123322 + +//#define NOISY 1 + +#if defined (__linux__) || defined (__APPLE__) +#define strlwr strlower +#endif + +typedef struct { + float v[3]; +} vector; + +typedef struct +{ + vector n; /* normal */ + vector p; /* point */ + vector c; /* color */ + float u; /* u */ + float v; /* v */ +} aliaspoint_t; + +typedef struct { + aliaspoint_t pt[3]; +} tf_triangle; + + +static void ByteSwapTri (tf_triangle *tri) +{ + int i; + + for (i=0 ; iverts[j][k] = tri.pt[j].p.v[k]; + ptri->normals[j][k] = tri.pt[j].n.v[k]; +// ptri->colors[j][k] = tri.pt[j].c.v[k]; + } + + ptri->texcoords[j][0] = tri.pt[j].u; + ptri->texcoords[j][1] = tri.pt[j].v; + } + + ptri++; + if ((ptri - tripool ) >= POLYSET_MAXTRIANGLES) + Error ("Error: too many triangles; increase POLYSET_MAXTRIANGLES\n"); + } +} + +void TRI_LoadPolysets( const char *filename, polyset_t **ppPSET, int *numpsets ) +{ + FILE *input; + float start; + char name[256], tex[256]; + int i, count, magic, pset = 0; + triangle_t *ptri; + polyset_t *pPSET; + int iLevel; + int exitpattern; + float t; + + t = -FLOAT_START; + *((unsigned char *)&exitpattern + 0) = *((unsigned char *)&t + 3); + *((unsigned char *)&exitpattern + 1) = *((unsigned char *)&t + 2); + *((unsigned char *)&exitpattern + 2) = *((unsigned char *)&t + 1); + *((unsigned char *)&exitpattern + 3) = *((unsigned char *)&t + 0); + + if ((input = fopen(filename, "rb")) == 0) + Error ("reader: could not open file '%s'", filename); + + iLevel = 0; + + fread(&magic, sizeof(int), 1, input); + if (BigLong(magic) != MAGIC) + Error ("%s is not a Alias object separated triangle file, magic number is wrong.", filename); + + pPSET = calloc( 1, POLYSET_MAXPOLYSETS * sizeof( polyset_t ) ); + ptri = calloc( 1, POLYSET_MAXTRIANGLES * sizeof( triangle_t ) ); + + *ppPSET = pPSET; + + while (feof(input) == 0) { + if (fread(&start, sizeof(float), 1, input) < 1) + break; + *(int *)&start = BigLong(*(int *)&start); + if (*(int *)&start != exitpattern) + { + if (start == FLOAT_START) { + /* Start of an object or group of objects. */ + i = -1; + do { + /* There are probably better ways to read a string from */ + /* a file, but this does allow you to do error checking */ + /* (which I'm not doing) on a per character basis. */ + ++i; + fread( &(name[i]), sizeof( char ), 1, input); + } while( name[i] != '\0' ); + + if ( i != 0 ) + strncpy( pPSET[pset].name, name, sizeof( pPSET[pset].name ) - 1 ); + else + strcpy( pPSET[pset].name , "(unnamed)" ); + strlwr( pPSET[pset].name ); + +// indent(); +// fprintf(stdout,"OBJECT START: %s\n",name); + fread( &count, sizeof(int), 1, input); + count = BigLong(count); + ++iLevel; + if (count != 0) { +// indent(); +// fprintf(stdout,"NUMBER OF TRIANGLES: %d\n",count); + + i = -1; + do { + ++i; + fread( &(tex[i]), sizeof( char ), 1, input); + } while( tex[i] != '\0' ); + +/* + if ( i != 0 ) + strncpy( pPSET[pset].texname, tex, sizeof( pPSET[pset].texname ) - 1 ); + else + strcpy( pPSET[pset].texname, "(unnamed)" ); + strlwr( pPSET[pset].texname ); +*/ + +// indent(); +// fprintf(stdout," Object texture name: '%s'\n",tex); + } + + /* Else (count == 0) this is the start of a group, and */ + /* no texture name is present. */ + } + else if (start == FLOAT_END) { + /* End of an object or group. Yes, the name should be */ + /* obvious from context, but it is in here just to be */ + /* safe and to provide a little extra information for */ + /* those who do not wish to write a recursive reader. */ + /* Mea culpa. */ + --iLevel; + i = -1; + do { + ++i; + fread( &(name[i]), sizeof( char ), 1, input); + } while( name[i] != '\0' ); + + if ( i != 0 ) + strncpy( pPSET[pset].name, name, sizeof( pPSET[pset].name ) - 1 ); + else + strcpy( pPSET[pset].name , "(unnamed)" ); + + strlwr( pPSET[pset].name ); + +// indent(); +// fprintf(stdout,"OBJECT END: %s\n",name); + continue; + } + } + +// +// read the triangles +// + if ( count > 0 ) + { + pPSET[pset].triangles = ptri; + ReadPolysetGeometry( pPSET[0].triangles, input, count, ptri ); + ptri += count; + pPSET[pset].numtriangles = count; + if ( ++pset >= POLYSET_MAXPOLYSETS ) + { + Error ("Error: too many polysets; increase POLYSET_MAXPOLYSETS\n"); + } + } + } + + *numpsets = pset; + + fclose (input); +} + diff --git a/tools/quake3/common/trilib.h b/tools/quake3/common/trilib.h new file mode 100644 index 00000000..89dcf764 --- /dev/null +++ b/tools/quake3/common/trilib.h @@ -0,0 +1,26 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// trilib.h: header file for loading triangles from an Alias triangle file +// +void TRI_LoadPolysets( const char *filename, polyset_t **ppPSET, int *numpsets ); + diff --git a/tools/quake3/common/unzip.c b/tools/quake3/common/unzip.c new file mode 100644 index 00000000..63878e6a --- /dev/null +++ b/tools/quake3/common/unzip.c @@ -0,0 +1,4596 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/***************************************************************************** + * name: unzip.c + * + * desc: IO on .zip files using portions of zlib + * + * + *****************************************************************************/ + +#include +#include +#include +#include "unzip.h" + +// TTimo added for safe_malloc wrapping +#include "cmdlib.h" + +/* unzip.h -- IO for uncompress .zip files using zlib + Version 0.15 beta, Mar 19th, 1998, + + Copyright (C) 1998 Gilles Vollant + + This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + Encryption and multi volume ZipFile (span) are not supported. + Old compressions used by old PKZip 1.x are not supported + + THIS IS AN ALPHA VERSION. AT THIS STAGE OF DEVELOPPEMENT, SOMES API OR STRUCTURE + CAN CHANGE IN FUTURE VERSION !! + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + +*/ +/* for more info about .ZIP format, see + ftp://ftp.cdrom.com/pub/infozip/doc/appnote-970311-iz.zip + PkWare has also a specification at : + ftp://ftp.pkware.com/probdesc.zip */ + +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.1.3, July 9th, 1998 + + Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files ftp://ds.internic.net/rfc/rfc1950.txt + (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-1998 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + + +#ifndef _ZCONF_H +#define _ZCONF_H + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +#define OF(args) args +#endif + +typedef unsigned char Byte; /* 8 bits */ +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ +typedef Byte *voidp; + +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif + +#endif /* _ZCONF_H */ + +#define ZLIB_VERSION "1.1.3" + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed + data. This version of the library supports only one compression method + (deflation) but other algorithms will be added later and will have the same + stream interface. + + Compression can be done in a single step if the buffers are large + enough (for example if an input file is mmap'ed), or can be done by + repeated calls of the compression function. In the latter case, the + application must provide more input and/or consume the output + (providing more output space) before each call. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never + crash even in case of corrupted input. +*/ + +/* + The application must update next_in and avail_in when avail_in has + dropped to zero. It must update next_out and avail_out when avail_out + has dropped to zero. The application must initialize zalloc, zfree and + opaque before calling the init function. All other fields are set by the + compression library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this + if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, + pointers returned by zalloc for objects of exactly 65536 bytes *must* + have their offset normalized to zero. The default allocation function + provided by this library ensures this (see zutil.c). To reduce memory + requirements and avoid any allocation of 64K objects, at the expense of + compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or + progress reports. After compression, total_in holds the total size of + the uncompressed data and may be saved for use in the decompressor + (particularly if the decompressor wants to decompress everything in + a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +/* Allowed flush values; see deflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_ASCII 1 +#define Z_UNKNOWN 2 +/* Possible values of the data_type field */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + /* basic functions */ + +const char * zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is + not compatible with the zlib.h header file used by the application. + This check is automatically made by deflateInit and inflateInit. + */ + +/* +int deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. + If zalloc and zfree are set to Z_NULL, deflateInit updates them to + use default allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at + all (the input data is simply copied a block at a time). + Z_DEFAULT_COMPRESSION requests a default compromise between speed and + compression (currently equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if level is not a valid compression level, + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). + msg is set to null if there is no error message. deflateInit does not + perform any compression: this will be done by deflate(). +*/ + + +int deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce some + output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). + Some output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating avail_in or avail_out accordingly; avail_out + should never be zero before the call. The application can consume the + compressed output when it wants, for example when the output buffer is full + (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + and with zero avail_out, it must be called again after making room in the + output buffer because there might be more output pending. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In particular + avail_in is zero after the call if enough output space has been provided + before the call.) Flushing may degrade compression for some compression + algorithms and so it should be used only when necessary. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + the compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there + was enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the + stream are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least + 0.1% larger than avail_in plus 12 bytes. If deflate does not return + Z_STREAM_END, then it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so (that is, total_in bytes). + + deflate() may update data_type if it can make a good guess about + the input data type (Z_ASCII or Z_BINARY). In doubt, the data is considered + binary. This field is only for information purposes and does not affect + the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). +*/ + + +int deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, + msg may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +int inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the exact + value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller. msg is set to null if there is no error + message. inflateInit does not perform any decompression apart from reading + the zlib header if present: this will be done by inflate(). (So next_in and + avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +int inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may some + introduce some output latency (reading input without producing any output) + except when forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing + will resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there + is no more input data or no more space in the output buffer (see below + about the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating the next_* and avail_* values accordingly. + The application can consume the uncompressed output when it wants, for + example when the output buffer is full (avail_out == 0), or after each + call of inflate(). If inflate returns Z_OK and with zero avail_out, it + must be called again after making room in the output buffer because there + might be more output pending. + + If the parameter flush is set to Z_SYNC_FLUSH, inflate flushes as much + output as possible to the output buffer. The flushing behavior of inflate is + not specified for values of the flush parameter other than Z_SYNC_FLUSH + and Z_FINISH, but the current implementation actually flushes as much output + as possible anyway. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step + (a single call of inflate), the parameter flush should be set to + Z_FINISH. In this case all pending input is processed and all pending + output is flushed; avail_out must be large enough to hold all the + uncompressed data. (The size of the uncompressed data may have been saved + by the compressor for this purpose.) The next operation on this stream must + be inflateEnd to deallocate the decompression state. The use of Z_FINISH + is never required, but can be used to inform inflate that a faster routine + may be used for the single inflate() call. + + If a preset dictionary is needed at this point (see inflateSetDictionary + below), inflate sets strm-adler to the adler32 checksum of the + dictionary chosen by the compressor and returns Z_NEED_DICT; otherwise + it sets strm->adler to the adler32 checksum of all output produced + so (that is, total_out bytes) and returns Z_OK, Z_STREAM_END or + an error code as described below. At the end of the stream, inflate() + checks that its computed adler32 checksum is equal to that saved by the + compressor and returns Z_STREAM_END only if the checksum is correct. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect + adler32 checksum), Z_STREAM_ERROR if the stream structure was inconsistent + (for example if next_in or next_out was NULL), Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if no progress is possible or if there was not + enough room in the output buffer when Z_FINISH is used. In the Z_DATA_ERROR + case, the application may then call inflateSync to look for a good + compression block. +*/ + + +int inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +int deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by + the caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but + is slow and reduces compression ratio; memLevel=9 uses maximum memory + for optimal speed. The default value is 8. See zconf.h for total memory + usage as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), or Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match). Filtered data consists mostly of small values with a + somewhat random distribution. In this case, the compression algorithm is + tuned to compress them better. The effect of Z_FILTERED is to force more + Huffman coding and less string matching; it is somewhat intermediate + between Z_DEFAULT and Z_HUFFMAN_ONLY. The strategy parameter only affects + the compression ratio but not the correctness of the compressed output even + if it is not set appropriately. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid + method). msg is set to null if there is no error message. deflateInit2 does + not perform any compression: this will be done by deflate(). +*/ + +int deflateSetDictionary OF((z_streamp strm, + const Byte *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. This function must be called + immediately after deflateInit, deflateInit2 or deflateReset, before any + call of deflate. The compressor and decompressor must use exactly the same + dictionary (see inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size in + deflate or deflate2. Thus the strings most likely to be useful should be + put at the end of the dictionary, not at the front. + + Upon return of this function, strm->adler is set to the Adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The Adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if the compression method is bsort). deflateSetDictionary does not + perform any compression: this will be done by deflate(). +*/ + +int deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and + can consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +int deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. + The stream will keep the same compression level and any other attributes + that may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +int deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different + strategy. If the compression level is changed, the input available so far + is compressed with the old level (and may be flushed); the new level will + take effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to + be compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR + if strm->avail_out was zero. +*/ + +/* +int inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. If a compressed stream with a larger window size is given as + input, inflate() will return with the error code Z_DATA_ERROR instead of + trying to allocate a larger window. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as a negative + memLevel). msg is set to null if there is no error message. inflateInit2 + does not perform any decompression apart from reading the zlib header if + present: this will be done by inflate(). (So next_in and avail_in may be + modified, but next_out and avail_out are unchanged.) +*/ + +int inflateSetDictionary OF((z_streamp strm, + const Byte *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate + if this call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the Adler32 value returned by this call of + inflate. The compressor and decompressor must use exactly the same + dictionary (see deflateSetDictionary). + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect Adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +int inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a full flush point (see above the + description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR + if no more input was provided, Z_DATA_ERROR if no flush point has been found, + or Z_STREAM_ERROR if the stream structure was inconsistent. In the success + case, the application may save the current current value of total_in which + indicates where valid compressed data was found. In the error case, the + application may repeatedly call inflateSync, providing more input each time, + until success or end of the input data. +*/ + +int inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. + The stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + + + /* utility functions */ + +/* + The following utility functions are implemented on top of the + basic stream-oriented functions. To simplify the interface, some + default options are assumed (compression level and memory usage, + standard memory allocation functions). The source code of these + utility functions can easily be modified if you need special options. +*/ + +int compress OF((Byte *dest, uLong *destLen, + const Byte *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least 0.1% larger than + sourceLen plus 12 bytes. Upon exit, destLen is the actual size of the + compressed buffer. + This function can be used to compress a whole file at once if the + input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +int compress2 OF((Byte *dest, uLong *destLen, + const Byte *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least 0.1% larger than sourceLen plus + 12 bytes. Upon exit, destLen is the actual size of the compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +int uncompress OF((Byte *dest, uLong *destLen, + const Byte *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted. +*/ + + +typedef voidp gzFile; + +gzFile gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h". (See the description + of deflateInit2 for more information about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +gzFile gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +int gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +int gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +int gzwrite OF((gzFile file, + const voidp buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +int gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). +*/ + +int gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +char * gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +int gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +int gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +int gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +long gzseek OF((gzFile file, + long offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +int gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +long gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +int gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +int gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +const char * gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the + compression library. +*/ + +uLong adler32 OF((uLong adler, const Byte *buf, uInt len)); + +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is NULL, this function returns + the required initial value for the checksum. + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +uLong crc32 OF((uLong crc, const Byte *buf, uInt len)); +/* + Update a running crc with the bytes buf[0..len-1] and return the updated + crc. If buf is NULL, this function returns the required initial value + for the crc. Pre- and post-conditioning (one's complement) is performed + within this function so it shouldn't be done by the application. + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + +// private stuff to not include cmdlib.h +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +#ifdef _SGI_SOURCE +#define __BIG_ENDIAN__ +#endif + +#ifdef __BIG_ENDIAN__ + +short __LittleShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short __BigShort (short l) +{ + return l; +} + + +int __LittleLong (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int __BigLong (int l) +{ + return l; +} + + +float __LittleFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float __BigFloat (float l) +{ + return l; +} + + +#else + + +short __BigShort (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short __LittleShort (short l) +{ + return l; +} + + +int __BigLong (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int __LittleLong (int l) +{ + return l; +} + +float __BigFloat (float l) +{ + union {byte b[4]; float f;} in, out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float __LittleFloat (float l) +{ + return l; +} + + + +#endif + + + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +int deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +int inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +int deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +int inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) + + +const char * zError OF((int err)); +int inflateSyncPoint OF((z_streamp z)); +const uLong * get_crc_table OF((void)); + +typedef unsigned char uch; +typedef unsigned short ush; +typedef unsigned long ulg; + +extern const char *z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = (char*)ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + + /* common constants */ + +#ifndef DEF_WBITS +# define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + + /* target dependencies */ + + /* Common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef F_OPEN +# define F_OPEN(name, mode) fopen((name), (mode)) +#endif + + /* functions */ + +#ifdef HAVE_STRERROR + extern char *strerror OF((int)); +# define zstrerror(errnum) strerror(errnum) +#else +# define zstrerror(errnum) "" +#endif + +#define zmemcpy memcpy +#define zmemcmp memcmp +#define zmemzero(dest, len) memset(dest, 0, len) + +/* Diagnostic functions */ +#ifdef _ZIP_DEBUG_ + int z_verbose = 0; +# define Assert(cond,msg) assert(cond); + //{if(!(cond)) Sys_Error(msg);} +# define Trace(x) {if (z_verbose>=0) Sys_Error x ;} +# define Tracev(x) {if (z_verbose>0) Sys_Error x ;} +# define Tracevv(x) {if (z_verbose>1) Sys_Error x ;} +# define Tracec(c,x) {if (z_verbose>0 && (c)) Sys_Error x ;} +# define Tracecv(c,x) {if (z_verbose>1 && (c)) Sys_Error x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +#endif + + +typedef uLong (*check_func) OF((uLong check, const Byte *buf, uInt len)); +voidp zcalloc OF((voidp opaque, unsigned items, unsigned size)); +void zcfree OF((voidp opaque, voidp ptr)); + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidp)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + + +#if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) && \ + !defined(CASESENSITIVITYDEFAULT_NO) +#define CASESENSITIVITYDEFAULT_NO +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (65536) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (safe_malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) free(p);} +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + + + +/* =========================================================================== + Read a byte from a gz_stream; update next_in and avail_in. Return EOF + for end of file. + IN assertion: the stream s has been sucessfully opened for reading. +*/ + +/* +static int unzlocal_getByte(FILE *fin,int *pi) +{ + unsigned char c; + int err = fread(&c, 1, 1, fin); + if (err==1) + { + *pi = (int)c; + return UNZ_OK; + } + else + { + if (ferror(fin)) + return UNZ_ERRNO; + else + return UNZ_EOF; + } +} +*/ + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +static int unzlocal_getShort (FILE* fin, uLong *pX) +{ + short v; + + fread( &v, sizeof(v), 1, fin ); + + *pX = __LittleShort( v); + return UNZ_OK; + +/* + uLong x ; + int i; + int err; + + err = unzlocal_getByte(fin,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(fin,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +*/ +} + +static int unzlocal_getLong (FILE *fin, uLong *pX) +{ + int v; + + fread( &v, sizeof(v), 1, fin ); + + *pX = __LittleLong( v); + return UNZ_OK; + +/* + uLong x ; + int i; + int err; + + err = unzlocal_getByte(fin,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(fin,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + err = unzlocal_getByte(fin,&i); + x += ((uLong)i)<<16; + + if (err==UNZ_OK) + err = unzlocal_getByte(fin,&i); + x += ((uLong)i)<<24; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +*/ +} + + +/* My own strcmpi / strcasecmp */ +static int strcmpcasenosensitive_internal (const char* fileName1,const char* fileName2) +{ + for (;;) + { + char c1=*(fileName1++); + char c2=*(fileName2++); + if ((c1>='a') && (c1<='z')) + c1 -= 0x20; + if ((c2>='a') && (c2<='z')) + c2 -= 0x20; + if (c1=='\0') + return ((c2=='\0') ? 0 : -1); + if (c2=='\0') + return 1; + if (c1c2) + return 1; + } +} + + +#ifdef CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) + +*/ +extern int unzStringFileNameCompare (const char* fileName1,const char* fileName2,int iCaseSensitivity) +{ + if (iCaseSensitivity==0) + iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + + if (iCaseSensitivity==1) + return strcmp(fileName1,fileName2); + + return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#define BUFREADCOMMENT (0x400) + +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +static uLong unzlocal_SearchCentralDir(FILE *fin) +{ + unsigned char* buf; + uLong uSizeFile; + uLong uBackRead; + uLong uMaxBack=0xffff; /* maximum size of global comment */ + uLong uPosFound=0; + + if (fseek(fin,0,SEEK_END) != 0) + return 0; + + + uSizeFile = ftell( fin ); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)safe_malloc(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); + if (fseek(fin,uReadPos,SEEK_SET)!=0) + break; + + if (fread(buf,(uInt)uReadSize,1,fin)!=1) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + free(buf); + return uPosFound; +} + +extern unzFile unzReOpen (const char* path, unzFile file) +{ + unz_s *s; + FILE * fin; + + fin=fopen(path,"rb"); + if (fin==NULL) + return NULL; + + s=(unz_s*)safe_malloc(sizeof(unz_s)); + memcpy(s, (unz_s*)file, sizeof(unz_s)); + + s->file = fin; + return (unzFile)s; +} + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\test\\zlib109.zip" or on an Unix computer + "zlib/zlib109.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ +extern unzFile unzOpen (const char* path) +{ + unz_s us; + unz_s *s; + uLong central_pos,uL; + FILE * fin ; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + uLong number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + + int err=UNZ_OK; + + fin=fopen(path,"rb"); + if (fin==NULL) + return NULL; + + central_pos = unzlocal_SearchCentralDir(fin); + if (central_pos==0) + err=UNZ_ERRNO; + + if (fseek(fin,central_pos,SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unzlocal_getLong(fin,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unzlocal_getShort(fin,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unzlocal_getShort(fin,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (unzlocal_getShort(fin,&us.gi.number_entry)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir */ + if (unzlocal_getShort(fin,&number_entry_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unzlocal_getLong(fin,&us.size_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unzlocal_getLong(fin,&us.offset_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* zipfile comment length */ + if (unzlocal_getShort(fin,&us.gi.size_comment)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((central_pospfile_in_zip_read!=NULL) + unzCloseCurrentFile(file); + + fclose(s->file); + free(s); + return UNZ_OK; +} + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ +extern int unzGetGlobalInfo (unzFile file,unz_global_info *pglobal_info) +{ + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + *pglobal_info=s->gi; + return UNZ_OK; +} + + +/* + Translate date/time from Dos format to tm_unz (readable more easilty) +*/ +static void unzlocal_DosDateToTmuDate (uLong ulDosDate, tm_unz* ptm) +{ + uLong uDate; + uDate = (uLong)(ulDosDate>>16); + ptm->tm_mday = (uInt)(uDate&0x1f) ; + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + + ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; + ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; +} + +/* + Get Info about the current file in the zipfile, with internal only info +*/ +static int unzlocal_GetCurrentFileInfoInternal (unzFile file, + unz_file_info *pfile_info, + unz_file_info_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize) +{ + unz_s* s; + unz_file_info file_info; + unz_file_info_internal file_info_internal; + int err=UNZ_OK; + uLong uMagic; + long lSeek=0; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (fseek(s->file,s->pos_in_central_dir+s->byte_before_the_zipfile,SEEK_SET)!=0) + err=UNZ_ERRNO; + + + /* we check the magic */ + if (err==UNZ_OK) + if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x02014b50) + err=UNZ_BADZIPFILE; + + if (unzlocal_getShort(s->file,&file_info.version) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.version_needed) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.flag) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.compression_method) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.dosDate) != UNZ_OK) + err=UNZ_ERRNO; + + unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + + if (unzlocal_getLong(s->file,&file_info.crc) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.compressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.uncompressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_filename) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_file_extra) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_file_comment) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.disk_num_start) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.internal_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.external_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info_internal.offset_curfile) != UNZ_OK) + err=UNZ_ERRNO; + + lSeek+=file_info.size_filename; + if ((err==UNZ_OK) && (szFileName!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_filename0) && (fileNameBufferSize>0)) + if (fread(szFileName,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + lSeek -= uSizeRead; + } + + + if ((err==UNZ_OK) && (extraField!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_extrafile,lSeek,SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) + if (fread(extraField,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + lSeek += file_info.size_file_extra - uSizeRead; + } + else + lSeek+=file_info.size_file_extra; + + + if ((err==UNZ_OK) && (szComment!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_commentfile,lSeek,SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + if ((file_info.size_file_comment>0) && (commentBufferSize>0)) + if (fread(szComment,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + lSeek+=file_info.size_file_comment - uSizeRead; + } + else + lSeek+=file_info.size_file_comment; + + if ((err==UNZ_OK) && (pfile_info!=NULL)) + *pfile_info=file_info; + + if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) + *pfile_info_internal=file_info_internal; + + return err; +} + + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. +*/ +extern int unzGetCurrentFileInfo ( unzFile file, unz_file_info *pfile_info, + char *szFileName, uLong fileNameBufferSize, + void *extraField, uLong extraFieldBufferSize, + char *szComment, uLong commentBufferSize) +{ + return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); +} + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ +extern int unzGoToFirstFile (unzFile file) +{ + int err=UNZ_OK; + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + s->pos_in_central_dir=s->offset_central_dir; + s->num_file=0; + err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int unzGoToNextFile (unzFile file) +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->num_file+1==s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; + s->num_file++; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzipStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) +{ + unz_s* s; + int err; + + + uLong num_fileSaved; + uLong pos_in_central_dirSaved; + + + if (file==NULL) + return UNZ_PARAMERROR; + + if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + + err = unzGoToFirstFile(file); + + while (err == UNZ_OK) + { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; + unzGetCurrentFileInfo(file,NULL, + szCurrentFileName,sizeof(szCurrentFileName)-1, + NULL,0,NULL,0); + if (unzStringFileNameCompare(szCurrentFileName, + szFileName,iCaseSensitivity)==0) + return UNZ_OK; + err = unzGoToNextFile(file); + } + + s->num_file = num_fileSaved ; + s->pos_in_central_dir = pos_in_central_dirSaved ; + return err; +} + + +/* + Read the static header of the current zipfile + Check the coherency of the static header and info in the end of central + directory about this file + store in *piSizeVar the size of extra info in static header + (filename and size of extra field data) +*/ +static int unzlocal_CheckCurrentFileCoherencyHeader (unz_s* s, uInt* piSizeVar, + uLong *poffset_local_extrafield, + uInt *psize_local_extrafield) +{ + uLong uMagic,uData,uFlags; + uLong size_filename; + uLong size_extra_field; + int err=UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if (fseek(s->file,s->cur_file_info_internal.offset_curfile + + s->byte_before_the_zipfile,SEEK_SET)!=0) + return UNZ_ERRNO; + + + if (err==UNZ_OK) + if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x04034b50) + err=UNZ_BADZIPFILE; + + if (unzlocal_getShort(s->file,&uData) != UNZ_OK) + err=UNZ_ERRNO; +/* + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + err=UNZ_BADZIPFILE; +*/ + if (unzlocal_getShort(s->file,&uFlags) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&uData) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) + err=UNZ_BADZIPFILE; + + if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* date/time */ + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* crc */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size compr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size uncompr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + + if (unzlocal_getShort(s->file,&size_filename) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) + err=UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unzlocal_getShort(s->file,&size_extra_field) != UNZ_OK) + err=UNZ_ERRNO; + *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int unzOpenCurrentFile (unzFile file) +{ + int err=UNZ_OK; + int Store; + uInt iSizeVar; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uLong offset_local_extrafield; /* offset of the static extra field */ + uInt size_local_extrafield; /* size of the static extra field */ + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar, + &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip_read_info_s*) + safe_malloc(sizeof(file_in_zip_read_info_s)); + if (pfile_in_zip_read_info==NULL) + return UNZ_INTERNALERROR; + + pfile_in_zip_read_info->read_buffer=(char*)safe_malloc(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield=0; + + if (pfile_in_zip_read_info->read_buffer==NULL) + { + free(pfile_in_zip_read_info); + return UNZ_INTERNALERROR; + } + + pfile_in_zip_read_info->stream_initialised=0; + + if ((s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + Store = s->cur_file_info.compression_method==0; + + pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; + pfile_in_zip_read_info->crc32=0; + pfile_in_zip_read_info->compression_method = + s->cur_file_info.compression_method; + pfile_in_zip_read_info->file=s->file; + pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + + pfile_in_zip_read_info->stream.total_out = 0; + + if (!Store) + { + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidp)0; + + err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=1; + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + pfile_in_zip_read_info->rest_read_compressed = + s->cur_file_info.compressed_size ; + pfile_in_zip_read_info->rest_read_uncompressed = + s->cur_file_info.uncompressed_size ; + + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + + s->pfile_in_zip_read = pfile_in_zip_read_info; + return UNZ_OK; +} + + +/* + Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int unzReadCurrentFile (unzFile file, void *buf, unsigned len) +{ + int err=UNZ_OK; + uInt iRead = 0; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->read_buffer == NULL)) + return UNZ_END_OF_LIST_OF_FILE; + if (len==0) + return 0; + + pfile_in_zip_read_info->stream.next_out = (Byte*)buf; + + pfile_in_zip_read_info->stream.avail_out = (uInt)len; + + if (len>pfile_in_zip_read_info->rest_read_uncompressed) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + + while (pfile_in_zip_read_info->stream.avail_out>0) + { + if ((pfile_in_zip_read_info->stream.avail_in==0) && + (pfile_in_zip_read_info->rest_read_compressed>0)) + { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; + if (uReadThis == 0) + return UNZ_EOF; + if (s->cur_file_info.compressed_size == pfile_in_zip_read_info->rest_read_compressed) + if (fseek(pfile_in_zip_read_info->file, + pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile,SEEK_SET)!=0) + return UNZ_ERRNO; + if (fread(pfile_in_zip_read_info->read_buffer,uReadThis,1, + pfile_in_zip_read_info->file)!=1) + return UNZ_ERRNO; + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + + pfile_in_zip_read_info->rest_read_compressed-=uReadThis; + + pfile_in_zip_read_info->stream.next_in = + (Byte*)pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; + } + + if (pfile_in_zip_read_info->compression_method==0) + { + uInt uDoCopy,i ; + if (pfile_in_zip_read_info->stream.avail_out < + pfile_in_zip_read_info->stream.avail_in) + uDoCopy = pfile_in_zip_read_info->stream.avail_out ; + else + uDoCopy = pfile_in_zip_read_info->stream.avail_in ; + + for (i=0;istream.next_out+i) = + *(pfile_in_zip_read_info->stream.next_in+i); + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, + pfile_in_zip_read_info->stream.next_out, + uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + } + else + { + uLong uTotalOutBefore,uTotalOutAfter; + const Byte *bufBefore; + uLong uOutThis; + int flush=Z_SYNC_FLUSH; + + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err=inflate(&pfile_in_zip_read_info->stream,flush); + + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->crc32 = + crc32(pfile_in_zip_read_info->crc32,bufBefore, + (uInt)(uOutThis)); + + pfile_in_zip_read_info->rest_read_uncompressed -= + uOutThis; + + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + if (err==Z_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=Z_OK) + break; + } + } + + if (err==Z_OK) + return iRead; + return err; +} + + +/* + Give the current position in uncompressed data +*/ +extern long unztell (unzFile file) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + return (long)pfile_in_zip_read_info->stream.total_out; +} + + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ +extern int unzeof (unzFile file) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the static-header version of the extra field (sometimes, there is + more info in the static-header version than in the central-header) + + if buf==NULL, it return the size of the static extra field that can be read + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ +extern int unzGetLocalExtrafield (unzFile file,void *buf,unsigned len) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uInt read_now; + uLong size_to_read; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf==NULL) + return (int)size_to_read; + + if (len>size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now==0) + return 0; + + if (fseek(pfile_in_zip_read_info->file, + pfile_in_zip_read_info->offset_local_extrafield + + pfile_in_zip_read_info->pos_local_extrafield,SEEK_SET)!=0) + return UNZ_ERRNO; + + if (fread(buf,(uInt)size_to_read,1,pfile_in_zip_read_info->file)!=1) + return UNZ_ERRNO; + + return (int)read_now; +} + +/* + Close the file in zip opened with unzipOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int unzCloseCurrentFile (unzFile file) +{ + int err=UNZ_OK; + + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err=UNZ_CRCERROR; + } + + + free(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised) + inflateEnd(&pfile_in_zip_read_info->stream); + + pfile_in_zip_read_info->stream_initialised = 0; + free(pfile_in_zip_read_info); + + s->pfile_in_zip_read=NULL; + + return err; +} + + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ +extern int unzGetGlobalComment (unzFile file, char *szComment, uLong uSizeBuf) +{ + unz_s* s; + uLong uReadThis ; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + uReadThis = uSizeBuf; + if (uReadThis>s->gi.size_comment) + uReadThis = s->gi.size_comment; + + if (fseek(s->file,s->central_pos+22,SEEK_SET)!=0) + return UNZ_ERRNO; + + if (uReadThis>0) + { + *szComment='\0'; + if (fread(szComment,(uInt)uReadThis,1,s->file)!=1) + return UNZ_ERRNO; + } + + if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) + *(szComment+s->gi.size_comment)='\0'; + return (int)uReadThis; +} + +/* crc32.c -- compute the CRC-32 of a data stream + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + + +#ifdef DYNAMIC_CRC_TABLE + +static int crc_table_empty = 1; +static uLong crc_table[256]; +static void make_crc_table OF((void)); + +/* + Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The table is simply the CRC of all possible eight bit values. This is all + the information needed to generate CRC's on data a byte at a time for all + combinations of CRC register values and incoming bytes. +*/ +static void make_crc_table() +{ + uLong c; + int n, k; + uLong poly; /* polynomial exclusive-or pattern */ + /* terms of polynomial defining this crc (except x^32): */ + static const Byte p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* make exclusive-or pattern from polynomial (0xedb88320L) */ + poly = 0L; + for (n = 0; n < sizeof(p)/sizeof(Byte); n++) + poly |= 1L << (31 - p[n]); + + for (n = 0; n < 256; n++) + { + c = (uLong)n; + for (k = 0; k < 8; k++) + c = c & 1 ? poly ^ (c >> 1) : c >> 1; + crc_table[n] = c; + } + crc_table_empty = 0; +} +#else +/* ======================================================================== + * Table of CRC-32's of all single-byte values (made by make_crc_table) + */ +static const uLong crc_table[256] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL +}; +#endif + +/* ========================================================================= + * This function can be used by asm versions of crc32() + */ +#ifndef __APPLE__ +const uLong * get_crc_table() +{ +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) make_crc_table(); +#endif + return (const uLong *)crc_table; +} +#endif + +/* ========================================================================= */ +#define DO1(buf) crc = crc_table[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8); +#define DO2(buf) DO1(buf); DO1(buf); +#define DO4(buf) DO2(buf); DO2(buf); +#define DO8(buf) DO4(buf); DO4(buf); + +/* ========================================================================= */ +#ifndef __APPLE__ +uLong crc32(uLong crc, const Byte *buf, uInt len) +{ + if (buf == Z_NULL) return 0L; +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif + crc = crc ^ 0xffffffffL; + while (len >= 8) + { + DO8(buf); + len -= 8; + } + if (len) do { + DO1(buf); + } while (--len); + return crc ^ 0xffffffffL; +} +#endif + +/* infblock.h -- header to use infblock.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +struct inflate_blocks_state; +typedef struct inflate_blocks_state inflate_blocks_statef; + +extern inflate_blocks_statef * inflate_blocks_new OF(( + z_streamp z, + check_func c, /* check function */ + uInt w)); /* window size */ + +extern int inflate_blocks OF(( + inflate_blocks_statef *, + z_streamp , + int)); /* initial return code */ + +extern void inflate_blocks_reset OF(( + inflate_blocks_statef *, + z_streamp , + uLong *)); /* check value on output */ + +extern int inflate_blocks_free OF(( + inflate_blocks_statef *, + z_streamp)); + +extern void inflate_set_dictionary OF(( + inflate_blocks_statef *s, + const Byte *d, /* dictionary */ + uInt n)); /* dictionary length */ + +extern int inflate_blocks_sync_point OF(( + inflate_blocks_statef *s)); + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +/* Table for deflate from PKZIP's appnote.txt. */ +static const uInt border[] = { /* Order of the bit length code lengths */ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Huffman code lookup table entry--this entry is four bytes for machines + that have 16-bit pointers (e.g. PC's in the small or medium model). */ + +typedef struct inflate_huft_s inflate_huft; + +struct inflate_huft_s { + union { + struct { + Byte Exop; /* number of extra bits or operation */ + Byte Bits; /* number of bits in this code or subcode */ + } what; + uInt pad; /* pad structure to a power of 2 (4 bytes for */ + } word; /* 16-bit, 8 bytes for 32-bit int's) */ + uInt base; /* literal, length base, distance base, + or table offset */ +}; + +/* Maximum size of dynamic tree. The maximum found in a long but non- + exhaustive search was 1004 huft structures (850 for length/literals + and 154 for distances, the latter actually the result of an + exhaustive search). The actual maximum is not known, but the + value below is more than safe. */ +#define MANY 1440 + +extern int inflate_trees_bits OF(( + uInt *, /* 19 code lengths */ + uInt *, /* bits tree desired/actual depth */ + inflate_huft * *, /* bits tree result */ + inflate_huft *, /* space for trees */ + z_streamp)); /* for messages */ + +extern int inflate_trees_dynamic OF(( + uInt, /* number of literal/length codes */ + uInt, /* number of distance codes */ + uInt *, /* that many (total) code lengths */ + uInt *, /* literal desired/actual bit depth */ + uInt *, /* distance desired/actual bit depth */ + inflate_huft * *, /* literal/length tree result */ + inflate_huft * *, /* distance tree result */ + inflate_huft *, /* space for trees */ + z_streamp)); /* for messages */ + +extern int inflate_trees_fixed OF(( + uInt *, /* literal desired/actual bit depth */ + uInt *, /* distance desired/actual bit depth */ + inflate_huft * *, /* literal/length tree result */ + inflate_huft * *, /* distance tree result */ + z_streamp)); /* for memory allocation */ + + +/* infcodes.h -- header to use infcodes.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +struct inflate_codes_state; +typedef struct inflate_codes_state inflate_codes_statef; + +extern inflate_codes_statef *inflate_codes_new OF(( + uInt, uInt, + inflate_huft *, inflate_huft *, + z_streamp )); + +extern int inflate_codes OF(( + inflate_blocks_statef *, + z_streamp , + int)); + +extern void inflate_codes_free OF(( + inflate_codes_statef *, + z_streamp )); + +/* infutil.h -- types and macros common to blocks and codes + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +#ifndef _INFUTIL_H +#define _INFUTIL_H + +typedef enum { + TYPE, /* get type bits (3, including end bit) */ + LENS, /* get lengths for stored */ + STORED, /* processing stored block */ + TABLE, /* get table lengths */ + BTREE, /* get bit lengths tree for a dynamic block */ + DTREE, /* get length, distance trees for a dynamic block */ + CODES, /* processing fixed or dynamic block */ + DRY, /* output remaining window bytes */ + DONE, /* finished last block, done */ + BAD} /* got a data error--stuck here */ +inflate_block_mode; + +/* inflate blocks semi-private state */ +struct inflate_blocks_state { + + /* mode */ + inflate_block_mode mode; /* current inflate_block mode */ + + /* mode dependent information */ + union { + uInt left; /* if STORED, bytes left to copy */ + struct { + uInt table; /* table lengths (14 bits) */ + uInt index; /* index into blens (or border) */ + uInt *blens; /* bit lengths of codes */ + uInt bb; /* bit length tree depth */ + inflate_huft *tb; /* bit length decoding tree */ + } trees; /* if DTREE, decoding info for trees */ + struct { + inflate_codes_statef + *codes; + } decode; /* if CODES, current state */ + } sub; /* submode */ + uInt last; /* true if this block is the last block */ + + /* mode independent information */ + uInt bitk; /* bits in bit buffer */ + uLong bitb; /* bit buffer */ + inflate_huft *hufts; /* single safe_malloc for tree space */ + Byte *window; /* sliding window */ + Byte *end; /* one byte after sliding window */ + Byte *read; /* window read pointer */ + Byte *write; /* window write pointer */ + check_func checkfn; /* check function */ + uLong check; /* check on output */ + +}; + + +/* defines for inflate input/output */ +/* update pointers and return */ +#define UPDBITS {s->bitb=b;s->bitk=k;} +#define UPDIN {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} +#define UPDOUT {s->write=q;} +#define UPDATE {UPDBITS UPDIN UPDOUT} +#define LEAVE {UPDATE return inflate_flush(s,z,r);} +/* get bytes and bits */ +#define LOADIN {p=z->next_in;n=z->avail_in;b=s->bitb;k=s->bitk;} +#define NEEDBYTE {if(n)r=Z_OK;else LEAVE} +#define NEXTBYTE (n--,*p++) +#define NEEDBITS(j) {while(k<(j)){NEEDBYTE;b|=((uLong)NEXTBYTE)<>=(j);k-=(j);} +/* output bytes */ +#define WAVAIL (uInt)(qread?s->read-q-1:s->end-q) +#define LOADOUT {q=s->write;m=(uInt)WAVAIL;} +#define WRAP {if(q==s->end&&s->read!=s->window){q=s->window;m=(uInt)WAVAIL;}} +#define FLUSH {UPDOUT r=inflate_flush(s,z,r); LOADOUT} +#define NEEDOUT {if(m==0){WRAP if(m==0){FLUSH WRAP if(m==0) LEAVE}}r=Z_OK;} +#define OUTBYTE(a) {*q++=(Byte)(a);m--;} +/* load static pointers */ +#define LOAD {LOADIN LOADOUT} + +/* masks for lower bits (size given to avoid silly warnings with Visual C++) */ +extern uInt inflate_mask[17]; + +/* copy as much as possible from the sliding window to the output area */ +extern int inflate_flush OF(( + inflate_blocks_statef *, + z_streamp , + int)); + +#endif + + +/* + Notes beyond the 1.93a appnote.txt: + + 1. Distance pointers never point before the beginning of the output + stream. + 2. Distance pointers can point back across blocks, up to 32k away. + 3. There is an implied maximum of 7 bits for the bit length table and + 15 bits for the actual data. + 4. If only one code exists, then it is encoded using one bit. (Zero + would be more efficient, but perhaps a little confusing.) If two + codes exist, they are coded using one bit each (0 and 1). + 5. There is no way of sending zero distance codes--a dummy must be + sent if there are none. (History: a pre 2.0 version of PKZIP would + store blocks with no distance codes, but this was discovered to be + too harsh a criterion.) Valid only for 1.93a. 2.04c does allow + zero distance codes, which is sent as one code of zero bits in + length. + 6. There are up to 286 literal/length codes. Code 256 represents the + end-of-block. Note however that the static length tree defines + 288 codes just to fill out the Huffman codes. Codes 286 and 287 + cannot be used though, since there is no length base or extra bits + defined for them. Similarily, there are up to 30 distance codes. + However, static trees define 32 codes (all 5 bits) to fill out the + Huffman codes, but the last two had better not show up in the data. + 7. Unzip can check dynamic Huffman blocks for complete code sets. + The exception is that a single code would not be complete (see #4). + 8. The five bits following the block type is really the number of + literal codes sent minus 257. + 9. Length codes 8,16,16 are interpreted as 13 length codes of 8 bits + (1+6+6). Therefore, to output three times the length, you output + three codes (1+1+1), whereas to output four times the same length, + you only need two codes (1+3). Hmm. + 10. In the tree reconstruction algorithm, Code = Code + Increment + only if BitLength(i) is not zero. (Pretty obvious.) + 11. Correction: 4 Bits: # of Bit Length codes - 4 (4 - 19) + 12. Note: length code 284 can represent 227-258, but length code 285 + really is 258. The last length deserves its own, short code + since it gets used a lot in very redundant files. The length + 258 is special since 258 - 3 (the min match length) is 255. + 13. The literal/length and distance code bit lengths are read as a + single stream of lengths. It is possible (and advantageous) for + a repeat code (16, 17, or 18) to go across the boundary between + the two sets of lengths. + */ + + +#ifndef __APPLE__ +void inflate_blocks_reset(inflate_blocks_statef *s, z_streamp z, uLong *c) +{ + if (c != Z_NULL) + *c = s->check; + if (s->mode == BTREE || s->mode == DTREE) + ZFREE(z, s->sub.trees.blens); + if (s->mode == CODES) + inflate_codes_free(s->sub.decode.codes, z); + s->mode = TYPE; + s->bitk = 0; + s->bitb = 0; + s->read = s->write = s->window; + if (s->checkfn != Z_NULL) + z->adler = s->check = (*s->checkfn)(0L, (const Byte *)Z_NULL, 0); + Tracev(("inflate: blocks reset\n")); +} +#endif + +#ifndef __APPLE__ +inflate_blocks_statef *inflate_blocks_new(z_streamp z, check_func c, uInt w) +{ + inflate_blocks_statef *s; + + if ((s = (inflate_blocks_statef *)ZALLOC + (z,1,sizeof(struct inflate_blocks_state))) == Z_NULL) + return s; + if ((s->hufts = + (inflate_huft *)ZALLOC(z, sizeof(inflate_huft), MANY)) == Z_NULL) + { + ZFREE(z, s); + return Z_NULL; + } + if ((s->window = (Byte *)ZALLOC(z, 1, w)) == Z_NULL) + { + ZFREE(z, s->hufts); + ZFREE(z, s); + return Z_NULL; + } + s->end = s->window + w; + s->checkfn = c; + s->mode = TYPE; + Tracev(("inflate: blocks allocated\n")); + inflate_blocks_reset(s, z, Z_NULL); + return s; +} +#endif + +#ifndef __APPLE__ +int inflate_blocks(inflate_blocks_statef *s, z_streamp z, int r) +{ + uInt t; /* temporary storage */ + uLong b; /* bit buffer */ + uInt k; /* bits in bit buffer */ + Byte *p; /* input data pointer */ + uInt n; /* bytes available there */ + Byte *q; /* output window write pointer */ + uInt m; /* bytes to end of window or read pointer */ + + /* copy input/output information to locals (UPDATE macro restores) */ + LOAD + + /* process input based on current state */ + while (1) switch (s->mode) + { + case TYPE: + NEEDBITS(3) + t = (uInt)b & 7; + s->last = t & 1; + switch (t >> 1) + { + case 0: /* stored */ + Tracev(("inflate: stored block%s\n", + s->last ? " (last)" : "")); + DUMPBITS(3) + t = k & 7; /* go to byte boundary */ + DUMPBITS(t) + s->mode = LENS; /* get length of stored block */ + break; + case 1: /* fixed */ + Tracev(("inflate: fixed codes block%s\n", + s->last ? " (last)" : "")); + { + uInt bl, bd; + inflate_huft *tl, *td; + + inflate_trees_fixed(&bl, &bd, &tl, &td, z); + s->sub.decode.codes = inflate_codes_new(bl, bd, tl, td, z); + if (s->sub.decode.codes == Z_NULL) + { + r = Z_MEM_ERROR; + LEAVE + } + } + DUMPBITS(3) + s->mode = CODES; + break; + case 2: /* dynamic */ + Tracev(("inflate: dynamic codes block%s\n", + s->last ? " (last)" : "")); + DUMPBITS(3) + s->mode = TABLE; + break; + case 3: /* illegal */ + DUMPBITS(3) + s->mode = BAD; + z->msg = (char*)"invalid block type"; + r = Z_DATA_ERROR; + LEAVE + } + break; + case LENS: + NEEDBITS(32) + if ((((~b) >> 16) & 0xffff) != (b & 0xffff)) + { + s->mode = BAD; + z->msg = (char*)"invalid stored block lengths"; + r = Z_DATA_ERROR; + LEAVE + } + s->sub.left = (uInt)b & 0xffff; + b = k = 0; /* dump bits */ + Tracev(("inflate: stored length %u\n", s->sub.left)); + s->mode = s->sub.left ? STORED : (s->last ? DRY : TYPE); + break; + case STORED: + if (n == 0) + LEAVE + NEEDOUT + t = s->sub.left; + if (t > n) t = n; + if (t > m) t = m; + zmemcpy(q, p, t); + p += t; n -= t; + q += t; m -= t; + if ((s->sub.left -= t) != 0) + break; + Tracev(("inflate: stored end, %lu total out\n", + z->total_out + (q >= s->read ? q - s->read : + (s->end - s->read) + (q - s->window)))); + s->mode = s->last ? DRY : TYPE; + break; + case TABLE: + NEEDBITS(14) + s->sub.trees.table = t = (uInt)b & 0x3fff; +#ifndef PKZIP_BUG_WORKAROUND + if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) + { + s->mode = BAD; + z->msg = (char*)"too many length or distance symbols"; + r = Z_DATA_ERROR; + LEAVE + } +#endif + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + if ((s->sub.trees.blens = (uInt*)ZALLOC(z, t, sizeof(uInt))) == Z_NULL) + { + r = Z_MEM_ERROR; + LEAVE + } + DUMPBITS(14) + s->sub.trees.index = 0; + Tracev(("inflate: table sizes ok\n")); + s->mode = BTREE; + case BTREE: + while (s->sub.trees.index < 4 + (s->sub.trees.table >> 10)) + { + NEEDBITS(3) + s->sub.trees.blens[border[s->sub.trees.index++]] = (uInt)b & 7; + DUMPBITS(3) + } + while (s->sub.trees.index < 19) + s->sub.trees.blens[border[s->sub.trees.index++]] = 0; + s->sub.trees.bb = 7; + t = inflate_trees_bits(s->sub.trees.blens, &s->sub.trees.bb, + &s->sub.trees.tb, s->hufts, z); + if (t != Z_OK) + { + ZFREE(z, s->sub.trees.blens); + r = t; + if (r == Z_DATA_ERROR) + s->mode = BAD; + LEAVE + } + s->sub.trees.index = 0; + Tracev(("inflate: bits tree ok\n")); + s->mode = DTREE; + case DTREE: + while (t = s->sub.trees.table, + s->sub.trees.index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f)) + { + inflate_huft *h; + uInt i, j, c; + + t = s->sub.trees.bb; + NEEDBITS(t) + h = s->sub.trees.tb + ((uInt)b & inflate_mask[t]); + t = h->bits; + c = h->base; + if (c < 16) + { + DUMPBITS(t) + s->sub.trees.blens[s->sub.trees.index++] = c; + } + else /* c == 16..18 */ + { + i = c == 18 ? 7 : c - 14; + j = c == 18 ? 11 : 3; + NEEDBITS(t + i) + DUMPBITS(t) + j += (uInt)b & inflate_mask[i]; + DUMPBITS(i) + i = s->sub.trees.index; + t = s->sub.trees.table; + if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || + (c == 16 && i < 1)) + { + ZFREE(z, s->sub.trees.blens); + s->mode = BAD; + z->msg = (char*)"invalid bit length repeat"; + r = Z_DATA_ERROR; + LEAVE + } + c = c == 16 ? s->sub.trees.blens[i - 1] : 0; + do { + s->sub.trees.blens[i++] = c; + } while (--j); + s->sub.trees.index = i; + } + } + s->sub.trees.tb = Z_NULL; + { + uInt bl, bd; + inflate_huft *tl, *td; + inflate_codes_statef *c; + + bl = 9; /* must be <= 9 for lookahead assumptions */ + bd = 6; /* must be <= 9 for lookahead assumptions */ + t = s->sub.trees.table; + t = inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), + s->sub.trees.blens, &bl, &bd, &tl, &td, + s->hufts, z); + ZFREE(z, s->sub.trees.blens); + if (t != Z_OK) + { + if (t == (uInt)Z_DATA_ERROR) + s->mode = BAD; + r = t; + LEAVE + } + Tracev(("inflate: trees ok\n")); + if ((c = inflate_codes_new(bl, bd, tl, td, z)) == Z_NULL) + { + r = Z_MEM_ERROR; + LEAVE + } + s->sub.decode.codes = c; + } + s->mode = CODES; + case CODES: + UPDATE + if ((r = inflate_codes(s, z, r)) != Z_STREAM_END) + return inflate_flush(s, z, r); + r = Z_OK; + inflate_codes_free(s->sub.decode.codes, z); + LOAD + Tracev(("inflate: codes end, %lu total out\n", + z->total_out + (q >= s->read ? q - s->read : + (s->end - s->read) + (q - s->window)))); + if (!s->last) + { + s->mode = TYPE; + break; + } + s->mode = DRY; + case DRY: + FLUSH + if (s->read != s->write) + LEAVE + s->mode = DONE; + case DONE: + r = Z_STREAM_END; + LEAVE + case BAD: + r = Z_DATA_ERROR; + LEAVE + default: + r = Z_STREAM_ERROR; + LEAVE + } +} +#endif + +#ifndef __APPLE__ +int inflate_blocks_free(inflate_blocks_statef *s, z_streamp z) +{ + inflate_blocks_reset(s, z, Z_NULL); + ZFREE(z, s->window); + ZFREE(z, s->hufts); + ZFREE(z, s); + Tracev(("inflate: blocks freed\n")); + return Z_OK; +} +#endif + +#ifndef __APPLE__ +void inflate_set_dictionary(inflate_blocks_statef *s, const Byte *d, uInt n) +{ + zmemcpy(s->window, d, n); + s->read = s->write = s->window + n; +} +#endif + +/* Returns true if inflate is currently at the end of a block generated + * by Z_SYNC_FLUSH or Z_FULL_FLUSH. + * IN assertion: s != Z_NULL + */ +#ifndef __APPLE__ +int inflate_blocks_sync_point(inflate_blocks_statef *s) +{ + return s->mode == LENS; +} +#endif + +/* And'ing with mask[n] masks the lower n bits */ +uInt inflate_mask[17] = { + 0x0000, + 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff +}; + +/* copy as much as possible from the sliding window to the output area */ +#ifndef __APPLE__ +int inflate_flush(inflate_blocks_statef *s, z_streamp z, int r) +{ + uInt n; + Byte *p; + Byte *q; + + /* static copies of source and destination pointers */ + p = z->next_out; + q = s->read; + + /* compute number of bytes to copy as as end of window */ + n = (uInt)((q <= s->write ? s->write : s->end) - q); + if (n > z->avail_out) n = z->avail_out; + if (n && r == Z_BUF_ERROR) r = Z_OK; + + /* update counters */ + z->avail_out -= n; + z->total_out += n; + + /* update check information */ + if (s->checkfn != Z_NULL) + z->adler = s->check = (*s->checkfn)(s->check, q, n); + + /* copy as as end of window */ + zmemcpy(p, q, n); + p += n; + q += n; + + /* see if more to copy at beginning of window */ + if (q == s->end) + { + /* wrap pointers */ + q = s->window; + if (s->write == s->end) + s->write = s->window; + + /* compute bytes to copy */ + n = (uInt)(s->write - q); + if (n > z->avail_out) n = z->avail_out; + if (n && r == Z_BUF_ERROR) r = Z_OK; + + /* update counters */ + z->avail_out -= n; + z->total_out += n; + + /* update check information */ + if (s->checkfn != Z_NULL) + z->adler = s->check = (*s->checkfn)(s->check, q, n); + + /* copy */ + zmemcpy(p, q, n); + p += n; + q += n; + } + + /* update pointers */ + z->next_out = p; + s->read = q; + + /* done */ + return r; +} +#endif + +/* inftrees.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#ifndef __APPLE__ +const char inflate_copyright[] = + " inflate 1.1.3 Copyright 1995-1998 Mark Adler "; +#endif + +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + + +static int huft_build OF(( + uInt *, /* code lengths in bits */ + uInt, /* number of codes */ + uInt, /* number of "simple" codes */ + const uInt *, /* list of base values for non-simple codes */ + const uInt *, /* list of extra bits for non-simple codes */ + inflate_huft **, /* result: starting table */ + uInt *, /* maximum lookup bits (returns actual) */ + inflate_huft *, /* space for trees */ + uInt *, /* hufts used in space */ + uInt * )); /* space for values */ + +/* Tables for deflate from PKZIP's appnote.txt. */ +static const uInt cplens[31] = { /* Copy lengths for literal codes 257..285 */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; + /* see note #13 above about 258 */ +static const uInt cplext[31] = { /* Extra bits for literal codes 257..285 */ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112}; /* 112==invalid */ +static const uInt cpdist[30] = { /* Copy offsets for distance codes 0..29 */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577}; +static const uInt cpdext[30] = { /* Extra bits for distance codes */ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + +/* + Huffman code decoding is performed using a multi-level table lookup. + The fastest way to decode is to simply build a lookup table whose + size is determined by the longest code. However, the time it takes + to build this table can also be a factor if the data being decoded + is not very long. The most common codes are necessarily the + shortest codes, so those codes dominate the decoding time, and hence + the speed. The idea is you can have a shorter table that decodes the + shorter, more probable codes, and then point to subsidiary tables for + the longer codes. The time it costs to decode the longer codes is + then traded against the time it takes to make longer tables. + + This results of this trade are in the variables lbits and dbits + below. lbits is the number of bits the first level table for literal/ + length codes can decode in one step, and dbits is the same thing for + the distance codes. Subsequent tables are also less than or equal to + those sizes. These values may be adjusted either when all of the + codes are shorter than that, in which case the longest code length in + bits is used, or when the shortest code is *longer* than the requested + table size, in which case the length of the shortest code in bits is + used. + + There are two different values for the two tables, since they code a + different number of possibilities each. The literal/length table + codes 286 possible values, or in a flat code, a little over eight + bits. The distance table codes 30 possible values, or a little less + than five bits, flat. The optimum values for speed end up being + about one bit more than those, so lbits is 8+1 and dbits is 5+1. + The optimum values may differ though from machine to machine, and + possibly even between compilers. Your mileage may vary. + */ + + +/* If BMAX needs to be larger than 16, then h and x[] should be uLong. */ +#define BMAX 15 /* maximum bit length of any code */ + +static int huft_build(uInt *b, uInt n, uInt s, const uInt *d, const uInt *e, inflate_huft ** t, uInt *m, inflate_huft *hp, uInt *hn, uInt *v) +//uInt *b; /* code lengths in bits (all assumed <= BMAX) */ +//uInt n; /* number of codes (assumed <= 288) */ +//uInt s; /* number of simple-valued codes (0..s-1) */ +//const uInt *d; /* list of base values for non-simple codes */ +//const uInt *e; /* list of extra bits for non-simple codes */ +//inflate_huft ** t; /* result: starting table */ +//uInt *m; /* maximum lookup bits, returns actual */ +//inflate_huft *hp; /* space for trees */ +//uInt *hn; /* hufts used in space */ +//uInt *v; /* working area: values in order of bit length */ +/* Given a list of code lengths and a maximum table size, make a set of + tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR + if the given code set is incomplete (the tables are still built in this + case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of + lengths), or Z_MEM_ERROR if not enough memory. */ +{ + + uInt a; /* counter for codes of length k */ + uInt c[BMAX+1]; /* bit length count table */ + uInt f; /* i repeats in table every f entries */ + int g; /* maximum code length */ + int h; /* table level */ + register uInt i; /* counter, current code */ + register uInt j; /* counter */ + register int k; /* number of bits in current code */ + int l; /* bits per table (returned in m) */ + uInt mask; /* (1 << w) - 1, to avoid cc -O bug on HP */ + register uInt *p; /* pointer into c[], b[], or v[] */ + inflate_huft *q; /* points to current table */ + struct inflate_huft_s r; /* table entry for structure assignment */ + inflate_huft *u[BMAX]; /* table stack */ + register int w; /* bits before this table == (l * h) */ + uInt x[BMAX+1]; /* bit offsets, then code stack */ + uInt *xp; /* pointer into x */ + int y; /* number of dummy codes added */ + uInt z; /* number of entries in current table */ + + + /* Generate counts for each bit length */ + p = c; +#define C0 *p++ = 0; +#define C2 C0 C0 C0 C0 +#define C4 C2 C2 C2 C2 + C4 /* clear c[]--assume BMAX+1 is 16 */ + p = b; i = n; + do { + c[*p++]++; /* assume all entries <= BMAX */ + } while (--i); + if (c[0] == n) /* null input--all zero length codes */ + { + *t = (inflate_huft *)Z_NULL; + *m = 0; + return Z_OK; + } + + + /* Find minimum and maximum length, bound *m by those */ + l = *m; + for (j = 1; j <= BMAX; j++) + if (c[j]) + break; + k = j; /* minimum code length */ + if ((uInt)l < j) + l = j; + for (i = BMAX; i; i--) + if (c[i]) + break; + g = i; /* maximum code length */ + if ((uInt)l > i) + l = i; + *m = l; + + + /* Adjust last length count to fill out codes, if needed */ + for (y = 1 << j; j < i; j++, y <<= 1) + if ((y -= c[j]) < 0) + return Z_DATA_ERROR; + if ((y -= c[i]) < 0) + return Z_DATA_ERROR; + c[i] += y; + + + /* Generate starting offsets into the value table for each length */ + x[1] = j = 0; + p = c + 1; xp = x + 2; + while (--i) { /* note that i == g from above */ + *xp++ = (j += *p++); + } + + + /* Make a table of values in order of bit lengths */ + p = b; i = 0; + do { + if ((j = *p++) != 0) + v[x[j]++] = i; + } while (++i < n); + n = x[g]; /* set n to length of v */ + + + /* Generate the Huffman codes and for each, make the table entries */ + x[0] = i = 0; /* first Huffman code is zero */ + p = v; /* grab values in bit order */ + h = -1; /* no tables yet--level -1 */ + w = -l; /* bits decoded == (l * h) */ + u[0] = (inflate_huft *)Z_NULL; /* just to keep compilers happy */ + q = (inflate_huft *)Z_NULL; /* ditto */ + z = 0; /* ditto */ + + /* go through the bit lengths (k already is bits in shortest code) */ + for (; k <= g; k++) + { + a = c[k]; + while (a--) + { + /* here i is the Huffman code of length k bits for value *p */ + /* make tables up to required level */ + while (k > w + l) + { + h++; + w += l; /* previous table always l bits */ + + /* compute minimum size table less than or equal to l bits */ + z = g - w; + z = z > (uInt)l ? l : z; /* table size upper limit */ + if ((f = 1 << (j = k - w)) > a + 1) /* try a k-w bit table */ + { /* too few codes for k-w bit table */ + f -= a + 1; /* deduct codes from patterns left */ + xp = c + k; + if (j < z) + while (++j < z) /* try smaller tables up to z bits */ + { + if ((f <<= 1) <= *++xp) + break; /* enough codes to use up j bits */ + f -= *xp; /* else deduct codes from patterns */ + } + } + z = 1 << j; /* table entries for j-bit table */ + + /* allocate new table */ + if (*hn + z > MANY) /* (note: doesn't matter for fixed) */ + return Z_MEM_ERROR; /* not enough memory */ + u[h] = q = hp + *hn; + *hn += z; + + /* connect to last table, if there is one */ + if (h) + { + x[h] = i; /* save pattern for backing up */ + r.bits = (Byte)l; /* bits to dump before this table */ + r.exop = (Byte)j; /* bits in this table */ + j = i >> (w - l); + r.base = (uInt)(q - u[h-1] - j); /* offset to this table */ + u[h-1][j] = r; /* connect to last table */ + } + else + *t = q; /* first table is returned result */ + } + + /* set up table entry in r */ + r.bits = (Byte)(k - w); + if (p >= v + n) + r.exop = 128 + 64; /* out of values--invalid code */ + else if (*p < s) + { + r.exop = (Byte)(*p < 256 ? 0 : 32 + 64); /* 256 is end-of-block */ + r.base = *p++; /* simple code is just the value */ + } + else + { + r.exop = (Byte)(e[*p - s] + 16 + 64);/* non-simple--look up in lists */ + r.base = d[*p++ - s]; + } + + /* fill code-like entries with r */ + f = 1 << (k - w); + for (j = i >> w; j < z; j += f) + q[j] = r; + + /* backwards increment the k-bit code i */ + for (j = 1 << (k - 1); i & j; j >>= 1) + i ^= j; + i ^= j; + + /* backup over finished tables */ + mask = (1 << w) - 1; /* needed on HP, cc -O bug */ + while ((i & mask) != x[h]) + { + h--; /* don't need to update q */ + w -= l; + mask = (1 << w) - 1; + } + } + } + + + /* Return Z_BUF_ERROR if we were given an incomplete table */ + return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; +} + + +#ifndef __APPLE__ +int inflate_trees_bits(uInt *c, uInt *bb, inflate_huft * *tb, inflate_huft *hp, z_streamp z) +//uInt *c; /* 19 code lengths */ +//uInt *bb; /* bits tree desired/actual depth */ +//inflate_huft * *tb; /* bits tree result */ +//inflate_huft *hp; /* space for trees */ +//z_streamp z; /* for messages */ +{ + int r; + uInt hn = 0; /* hufts used in space */ + uInt *v; /* work area for huft_build */ + + if ((v = (uInt*)ZALLOC(z, 19, sizeof(uInt))) == Z_NULL) + return Z_MEM_ERROR; + r = huft_build(c, 19, 19, (uInt*)Z_NULL, (uInt*)Z_NULL, + tb, bb, hp, &hn, v); + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed dynamic bit lengths tree"; + else if (r == Z_BUF_ERROR || *bb == 0) + { + z->msg = (char*)"incomplete dynamic bit lengths tree"; + r = Z_DATA_ERROR; + } + ZFREE(z, v); + return r; +} +#endif + +#ifndef __APPLE__ +int inflate_trees_dynamic(uInt nl, uInt nd, uInt *c, uInt *bl, uInt *bd, inflate_huft * *tl, inflate_huft * *td, inflate_huft *hp, z_streamp z) +//uInt nl; /* number of literal/length codes */ +//uInt nd; /* number of distance codes */ +//uInt *c; /* that many (total) code lengths */ +//uInt *bl; /* literal desired/actual bit depth */ +//uInt *bd; /* distance desired/actual bit depth */ +//inflate_huft * *tl; /* literal/length tree result */ +//inflate_huft * *td; /* distance tree result */ +//inflate_huft *hp; /* space for trees */ +//z_streamp z; /* for messages */ +{ + int r; + uInt hn = 0; /* hufts used in space */ + uInt *v; /* work area for huft_build */ + + /* allocate work area */ + if ((v = (uInt*)ZALLOC(z, 288, sizeof(uInt))) == Z_NULL) + return Z_MEM_ERROR; + + /* build literal/length tree */ + r = huft_build(c, nl, 257, cplens, cplext, tl, bl, hp, &hn, v); + if (r != Z_OK || *bl == 0) + { + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed literal/length tree"; + else if (r != Z_MEM_ERROR) + { + z->msg = (char*)"incomplete literal/length tree"; + r = Z_DATA_ERROR; + } + ZFREE(z, v); + return r; + } + + /* build distance tree */ + r = huft_build(c + nl, nd, 0, cpdist, cpdext, td, bd, hp, &hn, v); + if (r != Z_OK || (*bd == 0 && nl > 257)) + { + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed distance tree"; + else if (r == Z_BUF_ERROR) { +#ifdef PKZIP_BUG_WORKAROUND + r = Z_OK; + } +#else + z->msg = (char*)"incomplete distance tree"; + r = Z_DATA_ERROR; + } + else if (r != Z_MEM_ERROR) + { + z->msg = (char*)"empty distance tree with lengths"; + r = Z_DATA_ERROR; + } + ZFREE(z, v); + return r; +#endif + } + + /* done */ + ZFREE(z, v); + return Z_OK; +} +#endif + +/* inffixed.h -- table for decoding fixed codes + * Generated automatically by the maketree.c program + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +static uInt fixed_bl = 9; +static uInt fixed_bd = 5; +static inflate_huft fixed_tl[] = { + {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115}, + {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},192}, + {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},160}, + {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},224}, + {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},144}, + {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},208}, + {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},176}, + {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},240}, + {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227}, + {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},200}, + {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},168}, + {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},232}, + {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},152}, + {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},216}, + {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},184}, + {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},248}, + {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163}, + {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},196}, + {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},164}, + {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},228}, + {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},148}, + {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},212}, + {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},180}, + {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},244}, + {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},204}, + {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},172}, + {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},236}, + {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},156}, + {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},220}, + {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},188}, + {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},252}, + {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131}, + {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},194}, + {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},162}, + {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},226}, + {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},146}, + {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},210}, + {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},178}, + {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},242}, + {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258}, + {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},202}, + {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},170}, + {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},234}, + {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},154}, + {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},218}, + {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},186}, + {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},250}, + {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195}, + {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},198}, + {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},166}, + {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},230}, + {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},150}, + {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},214}, + {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},182}, + {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},246}, + {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},206}, + {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},174}, + {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},238}, + {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},158}, + {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},222}, + {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},190}, + {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},254}, + {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115}, + {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},193}, + {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},161}, + {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},225}, + {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},145}, + {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},209}, + {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},177}, + {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},241}, + {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227}, + {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},201}, + {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},169}, + {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},233}, + {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},153}, + {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},217}, + {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},185}, + {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},249}, + {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163}, + {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},197}, + {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},165}, + {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},229}, + {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},149}, + {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},213}, + {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},181}, + {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},245}, + {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},205}, + {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},173}, + {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},237}, + {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},157}, + {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},221}, + {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},189}, + {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},253}, + {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131}, + {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},195}, + {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},163}, + {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},227}, + {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},147}, + {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},211}, + {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},179}, + {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},243}, + {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258}, + {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},203}, + {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},171}, + {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},235}, + {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},155}, + {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},219}, + {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},187}, + {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},251}, + {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195}, + {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},199}, + {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},167}, + {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},231}, + {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},151}, + {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},215}, + {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},183}, + {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},247}, + {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},207}, + {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},175}, + {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},239}, + {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},159}, + {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},223}, + {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},191}, + {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},255} + }; +static inflate_huft fixed_td[] = { + {{{80,5}},1}, {{{87,5}},257}, {{{83,5}},17}, {{{91,5}},4097}, + {{{81,5}},5}, {{{89,5}},1025}, {{{85,5}},65}, {{{93,5}},16385}, + {{{80,5}},3}, {{{88,5}},513}, {{{84,5}},33}, {{{92,5}},8193}, + {{{82,5}},9}, {{{90,5}},2049}, {{{86,5}},129}, {{{192,5}},24577}, + {{{80,5}},2}, {{{87,5}},385}, {{{83,5}},25}, {{{91,5}},6145}, + {{{81,5}},7}, {{{89,5}},1537}, {{{85,5}},97}, {{{93,5}},24577}, + {{{80,5}},4}, {{{88,5}},769}, {{{84,5}},49}, {{{92,5}},12289}, + {{{82,5}},13}, {{{90,5}},3073}, {{{86,5}},193}, {{{192,5}},24577} + }; + +#ifndef __APPLE__ +int inflate_trees_fixed(uInt *bl, uInt *bd, inflate_huft * *tl, inflate_huft * *td, z_streamp z) +//uInt *bl; /* literal desired/actual bit depth */ +//uInt *bd; /* distance desired/actual bit depth */ +//inflate_huft * *tl; /* literal/length tree result */ +//inflate_huft * *td; /* distance tree result */ +//z_streamp z; /* for memory allocation */ +{ + *bl = fixed_bl; + *bd = fixed_bd; + *tl = fixed_tl; + *td = fixed_td; + return Z_OK; +} +#endif + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +/* macros for bit input with no checking and for returning unused bytes */ +#define GRABBITS(j) {while(k<(j)){b|=((uLong)NEXTBYTE)<avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3;} + +/* Called with number of bytes left to write in window at least 258 + (the maximum string length) and number of input bytes available + at least ten. The ten bytes are six bytes for the longest length/ + distance pair plus four bytes for overloading the bit buffer. */ + +#ifndef __APPLE__ +int inflate_fast(uInt bl, uInt bd, inflate_huft *tl, inflate_huft *td, inflate_blocks_statef *s, z_streamp z) +{ + inflate_huft *t; /* temporary pointer */ + uInt e; /* extra bits or operation */ + uLong b; /* bit buffer */ + uInt k; /* bits in bit buffer */ + Byte *p; /* input data pointer */ + uInt n; /* bytes available there */ + Byte *q; /* output window write pointer */ + uInt m; /* bytes to end of window or read pointer */ + uInt ml; /* mask for literal/length tree */ + uInt md; /* mask for distance tree */ + uInt c; /* bytes to copy */ + uInt d; /* distance back to copy from */ + Byte *r; /* copy source pointer */ + + /* load input, output, bit values */ + LOAD + + /* initialize masks */ + ml = inflate_mask[bl]; + md = inflate_mask[bd]; + + /* do until not enough input or output space for fast loop */ + do { /* assume called with m >= 258 && n >= 10 */ + /* get literal/length code */ + GRABBITS(20) /* max bits for literal/length code */ + if ((e = (t = tl + ((uInt)b & ml))->exop) == 0) + { + DUMPBITS(t->bits) + Tracevv((t->base >= 0x20 && t->base < 0x7f ? + "inflate: * literal '%c'\n" : + "inflate: * literal 0x%02x\n", t->base)); + *q++ = (Byte)t->base; + m--; + continue; + } + do { + DUMPBITS(t->bits) + if (e & 16) + { + /* get extra bits for length */ + e &= 15; + c = t->base + ((uInt)b & inflate_mask[e]); + DUMPBITS(e) + Tracevv(("inflate: * length %u\n", c)); + + /* decode distance base of block to copy */ + GRABBITS(15); /* max bits for distance code */ + e = (t = td + ((uInt)b & md))->exop; + do { + DUMPBITS(t->bits) + if (e & 16) + { + /* get extra bits to add to distance base */ + e &= 15; + GRABBITS(e) /* get extra bits (up to 13) */ + d = t->base + ((uInt)b & inflate_mask[e]); + DUMPBITS(e) + Tracevv(("inflate: * distance %u\n", d)); + + /* do the copy */ + m -= c; + if ((uInt)(q - s->window) >= d) /* offset before dest */ + { /* just copy */ + r = q - d; + *q++ = *r++; c--; /* minimum count is three, */ + *q++ = *r++; c--; /* so unroll loop a little */ + } + else /* else offset after destination */ + { + e = d - (uInt)(q - s->window); /* bytes from offset to end */ + r = s->end - e; /* pointer to offset */ + if (c > e) /* if source crosses, */ + { + c -= e; /* copy to end of window */ + do { + *q++ = *r++; + } while (--e); + r = s->window; /* copy rest from start of window */ + } + } + do { /* copy all or what's left */ + *q++ = *r++; + } while (--c); + break; + } + else if ((e & 64) == 0) + { + t += t->base; + e = (t += ((uInt)b & inflate_mask[e]))->exop; + } + else + { + z->msg = (char*)"invalid distance code"; + UNGRAB + UPDATE + return Z_DATA_ERROR; + } + } while (1); + break; + } + if ((e & 64) == 0) + { + t += t->base; + if ((e = (t += ((uInt)b & inflate_mask[e]))->exop) == 0) + { + DUMPBITS(t->bits) + Tracevv((t->base >= 0x20 && t->base < 0x7f ? + "inflate: * literal '%c'\n" : + "inflate: * literal 0x%02x\n", t->base)); + *q++ = (Byte)t->base; + m--; + break; + } + } + else if (e & 32) + { + Tracevv(("inflate: * end of block\n")); + UNGRAB + UPDATE + return Z_STREAM_END; + } + else + { + z->msg = (char*)"invalid literal/length code"; + UNGRAB + UPDATE + return Z_DATA_ERROR; + } + } while (1); + } while (m >= 258 && n >= 10); + + /* not enough input or output--restore pointers and return */ + UNGRAB + UPDATE + return Z_OK; +} +#endif + +/* infcodes.c -- process literals and length/distance pairs + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +typedef enum { /* waiting for "i:"=input, "o:"=output, "x:"=nothing */ + START, /* x: set up for LEN */ + LEN, /* i: get length/literal/eob next */ + LENEXT, /* i: getting length extra (have base) */ + DIST, /* i: get distance next */ + DISTEXT, /* i: getting distance extra */ + COPY, /* o: copying bytes in window, waiting for space */ + LIT, /* o: got literal, waiting for output space */ + WASH, /* o: got eob, possibly still output waiting */ + END, /* x: got eob and all data flushed */ + BADCODE} /* x: got error */ +inflate_codes_mode; + +/* inflate codes private state */ +struct inflate_codes_state { + + /* mode */ + inflate_codes_mode mode; /* current inflate_codes mode */ + + /* mode dependent information */ + uInt len; + union { + struct { + inflate_huft *tree; /* pointer into tree */ + uInt need; /* bits needed */ + } code; /* if LEN or DIST, where in tree */ + uInt lit; /* if LIT, literal */ + struct { + uInt get; /* bits to get for extra */ + uInt dist; /* distance back to copy from */ + } copy; /* if EXT or COPY, where and how much */ + } sub; /* submode */ + + /* mode independent information */ + Byte lbits; /* ltree bits decoded per branch */ + Byte dbits; /* dtree bits decoder per branch */ + inflate_huft *ltree; /* literal/length/eob tree */ + inflate_huft *dtree; /* distance tree */ + +}; + +#ifndef __APPLE__ +inflate_codes_statef *inflate_codes_new(uInt bl, uInt bd, inflate_huft *tl, inflate_huft *td, z_streamp z) +{ + inflate_codes_statef *c; + + if ((c = (inflate_codes_statef *) + ZALLOC(z,1,sizeof(struct inflate_codes_state))) != Z_NULL) + { + c->mode = START; + c->lbits = (Byte)bl; + c->dbits = (Byte)bd; + c->ltree = tl; + c->dtree = td; + Tracev(("inflate: codes new\n")); + } + return c; +} +#endif + +#ifndef __APPLE__ +int inflate_codes(inflate_blocks_statef *s, z_streamp z, int r) +{ + uInt j; /* temporary storage */ + inflate_huft *t; /* temporary pointer */ + uInt e; /* extra bits or operation */ + uLong b; /* bit buffer */ + uInt k; /* bits in bit buffer */ + Byte *p; /* input data pointer */ + uInt n; /* bytes available there */ + Byte *q; /* output window write pointer */ + uInt m; /* bytes to end of window or read pointer */ + Byte *f; /* pointer to copy strings from */ + inflate_codes_statef *c = s->sub.decode.codes; /* codes state */ + + /* copy input/output information to locals (UPDATE macro restores) */ + LOAD + + /* process input and output based on current state */ + while (1) switch (c->mode) + { /* waiting for "i:"=input, "o:"=output, "x:"=nothing */ + case START: /* x: set up for LEN */ +#ifndef SLOW + if (m >= 258 && n >= 10) + { + UPDATE + r = inflate_fast(c->lbits, c->dbits, c->ltree, c->dtree, s, z); + LOAD + if (r != Z_OK) + { + c->mode = r == Z_STREAM_END ? WASH : BADCODE; + break; + } + } +#endif /* !SLOW */ + c->sub.code.need = c->lbits; + c->sub.code.tree = c->ltree; + c->mode = LEN; + case LEN: /* i: get length/literal/eob next */ + j = c->sub.code.need; + NEEDBITS(j) + t = c->sub.code.tree + ((uInt)b & inflate_mask[j]); + DUMPBITS(t->bits) + e = (uInt)(t->exop); + if (e == 0) /* literal */ + { + c->sub.lit = t->base; + Tracevv((t->base >= 0x20 && t->base < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", t->base)); + c->mode = LIT; + break; + } + if (e & 16) /* length */ + { + c->sub.copy.get = e & 15; + c->len = t->base; + c->mode = LENEXT; + break; + } + if ((e & 64) == 0) /* next table */ + { + c->sub.code.need = e; + c->sub.code.tree = t + t->base; + break; + } + if (e & 32) /* end of block */ + { + Tracevv(("inflate: end of block\n")); + c->mode = WASH; + break; + } + c->mode = BADCODE; /* invalid code */ + z->msg = (char*)"invalid literal/length code"; + r = Z_DATA_ERROR; + LEAVE + case LENEXT: /* i: getting length extra (have base) */ + j = c->sub.copy.get; + NEEDBITS(j) + c->len += (uInt)b & inflate_mask[j]; + DUMPBITS(j) + c->sub.code.need = c->dbits; + c->sub.code.tree = c->dtree; + Tracevv(("inflate: length %u\n", c->len)); + c->mode = DIST; + case DIST: /* i: get distance next */ + j = c->sub.code.need; + NEEDBITS(j) + t = c->sub.code.tree + ((uInt)b & inflate_mask[j]); + DUMPBITS(t->bits) + e = (uInt)(t->exop); + if (e & 16) /* distance */ + { + c->sub.copy.get = e & 15; + c->sub.copy.dist = t->base; + c->mode = DISTEXT; + break; + } + if ((e & 64) == 0) /* next table */ + { + c->sub.code.need = e; + c->sub.code.tree = t + t->base; + break; + } + c->mode = BADCODE; /* invalid code */ + z->msg = (char*)"invalid distance code"; + r = Z_DATA_ERROR; + LEAVE + case DISTEXT: /* i: getting distance extra */ + j = c->sub.copy.get; + NEEDBITS(j) + c->sub.copy.dist += (uInt)b & inflate_mask[j]; + DUMPBITS(j) + Tracevv(("inflate: distance %u\n", c->sub.copy.dist)); + c->mode = COPY; + case COPY: /* o: copying bytes in window, waiting for space */ +#ifndef __TURBOC__ /* Turbo C bug for following expression */ + f = (uInt)(q - s->window) < c->sub.copy.dist ? + s->end - (c->sub.copy.dist - (q - s->window)) : + q - c->sub.copy.dist; +#else + f = q - c->sub.copy.dist; + if ((uInt)(q - s->window) < c->sub.copy.dist) + f = s->end - (c->sub.copy.dist - (uInt)(q - s->window)); +#endif + while (c->len) + { + NEEDOUT + OUTBYTE(*f++) + if (f == s->end) + f = s->window; + c->len--; + } + c->mode = START; + break; + case LIT: /* o: got literal, waiting for output space */ + NEEDOUT + OUTBYTE(c->sub.lit) + c->mode = START; + break; + case WASH: /* o: got eob, possibly more output */ + if (k > 7) /* return unused byte, if any */ + { + Assert(k < 16, "inflate_codes grabbed too many bytes") + k -= 8; + n++; + p--; /* can always return one */ + } + FLUSH + if (s->read != s->write) + LEAVE + c->mode = END; + case END: + r = Z_STREAM_END; + LEAVE + case BADCODE: /* x: got error */ + r = Z_DATA_ERROR; + LEAVE + default: + r = Z_STREAM_ERROR; + LEAVE + } +#ifdef NEED_DUMMY_RETURN + return Z_STREAM_ERROR; /* Some dumb compilers complain without this */ +#endif +} +#endif + +#ifndef __APPLE__ +void inflate_codes_free(inflate_codes_statef *c, z_streamp z) +{ + ZFREE(z, c); + Tracev(("inflate: codes free\n")); +} +#endif + +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#define BASE 65521L /* largest prime smaller than 65536 */ +#define NMAX 5552 +/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ + +#undef DO1 +#undef DO2 +#undef DO4 +#undef DO8 + +#define DO1(buf,i) {s1 += buf[i]; s2 += s1;} +#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); +#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); +#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); +#define DO16(buf) DO8(buf,0); DO8(buf,8); + +/* ========================================================================= */ +#ifndef __APPLE__ +uLong adler32(uLong adler, const Byte *buf, uInt len) +{ + unsigned long s1 = adler & 0xffff; + unsigned long s2 = (adler >> 16) & 0xffff; + int k; + + if (buf == Z_NULL) return 1L; + + while (len > 0) { + k = len < NMAX ? len : NMAX; + len -= k; + while (k >= 16) { + DO16(buf); + buf += 16; + k -= 16; + } + if (k != 0) do { + s1 += *buf++; + s2 += s1; + } while (--k); + s1 %= BASE; + s2 %= BASE; + } + return (s2 << 16) | s1; +} +#endif + + +/* infblock.h -- header to use infblock.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +extern inflate_blocks_statef * inflate_blocks_new OF(( + z_streamp z, + check_func c, /* check function */ + uInt w)); /* window size */ + +extern int inflate_blocks OF(( + inflate_blocks_statef *, + z_streamp , + int)); /* initial return code */ + +extern void inflate_blocks_reset OF(( + inflate_blocks_statef *, + z_streamp , + uLong *)); /* check value on output */ + +extern int inflate_blocks_free OF(( + inflate_blocks_statef *, + z_streamp)); + +extern void inflate_set_dictionary OF(( + inflate_blocks_statef *s, + const Byte *d, /* dictionary */ + uInt n)); /* dictionary length */ + +extern int inflate_blocks_sync_point OF(( + inflate_blocks_statef *s)); + +typedef enum { + imMETHOD, /* waiting for method byte */ + imFLAG, /* waiting for flag byte */ + imDICT4, /* four dictionary check bytes to go */ + imDICT3, /* three dictionary check bytes to go */ + imDICT2, /* two dictionary check bytes to go */ + imDICT1, /* one dictionary check byte to go */ + imDICT0, /* waiting for inflateSetDictionary */ + imBLOCKS, /* decompressing blocks */ + imCHECK4, /* four check bytes to go */ + imCHECK3, /* three check bytes to go */ + imCHECK2, /* two check bytes to go */ + imCHECK1, /* one check byte to go */ + imDONE, /* finished check, done */ + imBAD} /* got an error--stay here */ +inflate_mode; + +/* inflate private state */ +struct internal_state { + + /* mode */ + inflate_mode mode; /* current inflate mode */ + + /* mode dependent information */ + union { + uInt method; /* if FLAGS, method byte */ + struct { + uLong was; /* computed check value */ + uLong need; /* stream check value */ + } check; /* if CHECK, check values to compare */ + uInt marker; /* if BAD, inflateSync's marker bytes count */ + } sub; /* submode */ + + /* mode independent information */ + int nowrap; /* flag for no wrapper */ + uInt wbits; /* log2(window size) (8..15, defaults to 15) */ + inflate_blocks_statef + *blocks; /* current inflate_blocks state */ + +}; + + +#ifndef __APPLE__ +int inflateReset(z_streamp z) +{ + if (z == Z_NULL || z->state == Z_NULL) + return Z_STREAM_ERROR; + z->total_in = z->total_out = 0; + z->msg = Z_NULL; + z->state->mode = z->state->nowrap ? imBLOCKS : imMETHOD; + inflate_blocks_reset(z->state->blocks, z, Z_NULL); + Tracev(("inflate: reset\n")); + return Z_OK; +} +#endif + +#ifndef __APPLE__ +int inflateEnd(z_streamp z) +{ + if (z == Z_NULL || z->state == Z_NULL || z->zfree == Z_NULL) + return Z_STREAM_ERROR; + if (z->state->blocks != Z_NULL) + inflate_blocks_free(z->state->blocks, z); + ZFREE(z, z->state); + z->state = Z_NULL; + Tracev(("inflate: end\n")); + return Z_OK; +} +#endif + +#ifndef __APPLE__ +int inflateInit2_(z_streamp z, int w, const char *version, int stream_size) +{ + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != sizeof(z_stream)) + return Z_VERSION_ERROR; + + /* initialize state */ + if (z == Z_NULL) + return Z_STREAM_ERROR; + z->msg = Z_NULL; + if (z->zalloc == Z_NULL) + { + z->zalloc = (void *(*)(void *, unsigned, unsigned))zcalloc; + z->opaque = (voidp)0; + } + if (z->zfree == Z_NULL) z->zfree = (void (*)(void *, void *))zcfree; + if ((z->state = (struct internal_state *) + ZALLOC(z,1,sizeof(struct internal_state))) == Z_NULL) + return Z_MEM_ERROR; + z->state->blocks = Z_NULL; + + /* handle undocumented nowrap option (no zlib header or check) */ + z->state->nowrap = 0; + if (w < 0) + { + w = - w; + z->state->nowrap = 1; + } + + /* set window size */ + if (w < 8 || w > 15) + { + inflateEnd(z); + return Z_STREAM_ERROR; + } + z->state->wbits = (uInt)w; + + /* create inflate_blocks state */ + if ((z->state->blocks = + inflate_blocks_new(z, z->state->nowrap ? Z_NULL : adler32, (uInt)1 << w)) + == Z_NULL) + { + inflateEnd(z); + return Z_MEM_ERROR; + } + Tracev(("inflate: allocated\n")); + + /* reset state */ + inflateReset(z); + return Z_OK; +} +#endif + +#ifndef __APPLE__ +int inflateInit_(z_streamp z, const char *version, int stream_size) +{ + return inflateInit2_(z, DEF_WBITS, version, stream_size); +} +#endif + +#define iNEEDBYTE {if(z->avail_in==0)return r;r=f;} +#define iNEXTBYTE (z->avail_in--,z->total_in++,*z->next_in++) + +#ifndef __APPLE__ +int inflate(z_streamp z, int f) +{ + int r; + uInt b; + + if (z == Z_NULL || z->state == Z_NULL || z->next_in == Z_NULL) + return Z_STREAM_ERROR; + f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK; + r = Z_BUF_ERROR; + while (1) switch (z->state->mode) + { + case imMETHOD: + iNEEDBYTE + if (((z->state->sub.method = iNEXTBYTE) & 0xf) != Z_DEFLATED) + { + z->state->mode = imBAD; + z->msg = (char*)"unknown compression method"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + if ((z->state->sub.method >> 4) + 8 > z->state->wbits) + { + z->state->mode = imBAD; + z->msg = (char*)"invalid window size"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + z->state->mode = imFLAG; + case imFLAG: + iNEEDBYTE + b = iNEXTBYTE; + if (((z->state->sub.method << 8) + b) % 31) + { + z->state->mode = imBAD; + z->msg = (char*)"incorrect header check"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + Tracev(("inflate: zlib header ok\n")); + if (!(b & PRESET_DICT)) + { + z->state->mode = imBLOCKS; + break; + } + z->state->mode = imDICT4; + case imDICT4: + iNEEDBYTE + z->state->sub.check.need = (uLong)iNEXTBYTE << 24; + z->state->mode = imDICT3; + case imDICT3: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE << 16; + z->state->mode = imDICT2; + case imDICT2: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE << 8; + z->state->mode = imDICT1; + case imDICT1: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE; + z->adler = z->state->sub.check.need; + z->state->mode = imDICT0; + return Z_NEED_DICT; + case imDICT0: + z->state->mode = imBAD; + z->msg = (char*)"need dictionary"; + z->state->sub.marker = 0; /* can try inflateSync */ + return Z_STREAM_ERROR; + case imBLOCKS: + r = inflate_blocks(z->state->blocks, z, r); + if (r == Z_DATA_ERROR) + { + z->state->mode = imBAD; + z->state->sub.marker = 0; /* can try inflateSync */ + break; + } + if (r == Z_OK) + r = f; + if (r != Z_STREAM_END) + return r; + r = f; + inflate_blocks_reset(z->state->blocks, z, &z->state->sub.check.was); + if (z->state->nowrap) + { + z->state->mode = imDONE; + break; + } + z->state->mode = imCHECK4; + case imCHECK4: + iNEEDBYTE + z->state->sub.check.need = (uLong)iNEXTBYTE << 24; + z->state->mode = imCHECK3; + case imCHECK3: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE << 16; + z->state->mode = imCHECK2; + case imCHECK2: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE << 8; + z->state->mode = imCHECK1; + case imCHECK1: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE; + + if (z->state->sub.check.was != z->state->sub.check.need) + { + z->state->mode = imBAD; + z->msg = (char*)"incorrect data check"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + Tracev(("inflate: zlib check ok\n")); + z->state->mode = imDONE; + case imDONE: + return Z_STREAM_END; + case imBAD: + return Z_DATA_ERROR; + default: + return Z_STREAM_ERROR; + } +#ifdef NEED_DUMMY_RETURN + return Z_STREAM_ERROR; /* Some dumb compilers complain without this */ +#endif +} +#endif + +#ifndef __APPLE__ +int inflateSetDictionary(z_streamp z, const Byte *dictionary, uInt dictLength) +{ + uInt length = dictLength; + + if (z == Z_NULL || z->state == Z_NULL || z->state->mode != imDICT0) + return Z_STREAM_ERROR; + + if (adler32(1L, dictionary, dictLength) != z->adler) return Z_DATA_ERROR; + z->adler = 1L; + + if (length >= ((uInt)1<state->wbits)) + { + length = (1<state->wbits)-1; + dictionary += dictLength - length; + } + inflate_set_dictionary(z->state->blocks, dictionary, length); + z->state->mode = imBLOCKS; + return Z_OK; +} +#endif + +#ifndef __APPLE__ +int inflateSync(z_streamp z) +{ + uInt n; /* number of bytes to look at */ + Byte *p; /* pointer to bytes */ + uInt m; /* number of marker bytes found in a row */ + uLong r, w; /* temporaries to save total_in and total_out */ + + /* set up */ + if (z == Z_NULL || z->state == Z_NULL) + return Z_STREAM_ERROR; + if (z->state->mode != imBAD) + { + z->state->mode = imBAD; + z->state->sub.marker = 0; + } + if ((n = z->avail_in) == 0) + return Z_BUF_ERROR; + p = z->next_in; + m = z->state->sub.marker; + + /* search */ + while (n && m < 4) + { + static const Byte mark[4] = {0, 0, 0xff, 0xff}; + if (*p == mark[m]) + m++; + else if (*p) + m = 0; + else + m = 4 - m; + p++, n--; + } + + /* restore */ + z->total_in += p - z->next_in; + z->next_in = p; + z->avail_in = n; + z->state->sub.marker = m; + + /* return no joy or set up to restart on a new block */ + if (m != 4) + return Z_DATA_ERROR; + r = z->total_in; w = z->total_out; + inflateReset(z); + z->total_in = r; z->total_out = w; + z->state->mode = imBLOCKS; + return Z_OK; +} +#endif + +/* Returns true if inflate is currently at the end of a block generated + * by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + * implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH + * but removes the length bytes of the resulting empty stored block. When + * decompressing, PPP checks that at the end of input packet, inflate is + * waiting for these length bytes. + */ +#ifndef __APPLE__ +int inflateSyncPoint(z_streamp z) +{ + if (z == Z_NULL || z->state == Z_NULL || z->state->blocks == Z_NULL) + return Z_STREAM_ERROR; + return inflate_blocks_sync_point(z->state->blocks); +} +#endif + +#ifndef __APPLE__ +voidp zcalloc (voidp opaque, unsigned items, unsigned size) +{ + if (opaque) items += size - size; /* make compiler happy */ + return (voidp)safe_malloc(items*size); +} + +void zcfree (voidp opaque, voidp ptr) +{ + free(ptr); + if (opaque) return; /* make compiler happy */ +} +#endif diff --git a/tools/quake3/common/unzip.h b/tools/quake3/common/unzip.h new file mode 100644 index 00000000..5cbfce93 --- /dev/null +++ b/tools/quake3/common/unzip.h @@ -0,0 +1,321 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef void* unzFile; +#endif + + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + unsigned int tm_sec; /* seconds after the minute - [0,59] */ + unsigned int tm_min; /* minutes after the hour - [0,59] */ + unsigned int tm_hour; /* hours since midnight - [0,23] */ + unsigned int tm_mday; /* day of the month - [1,31] */ + unsigned int tm_mon; /* months since January - [0,11] */ + unsigned int tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info_s +{ + unsigned long number_entry; /* total number of entries in the central dir on this disk */ + unsigned long size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info_s +{ + unsigned long version; /* version made by 2 unsigned chars */ + unsigned long version_needed; /* version needed to extract 2 unsigned chars */ + unsigned long flag; /* general purpose bit flag 2 unsigned chars */ + unsigned long compression_method; /* compression method 2 unsigned chars */ + unsigned long dosDate; /* last mod file date in Dos fmt 4 unsigned chars */ + unsigned long crc; /* crc-32 4 unsigned chars */ + unsigned long compressed_size; /* compressed size 4 unsigned chars */ + unsigned long uncompressed_size; /* uncompressed size 4 unsigned chars */ + unsigned long size_filename; /* filename length 2 unsigned chars */ + unsigned long size_file_extra; /* extra field length 2 unsigned chars */ + unsigned long size_file_comment; /* file comment length 2 unsigned chars */ + + unsigned long disk_num_start; /* disk number start 2 unsigned chars */ + unsigned long internal_fa; /* internal file attributes 2 unsigned chars */ + unsigned long external_fa; /* external file attributes 4 unsigned chars */ + + tm_unz tmu_date; +} unz_file_info; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info_internal_s +{ + unsigned long offset_curfile;/* relative offset of static header 4 unsigned chars */ +} unz_file_info_internal; + +typedef void* (*alloc_func) (void* opaque, unsigned int items, unsigned int size); +typedef void (*free_func) (void* opaque, void* address); + +struct internal_state; + +typedef struct z_stream_s { + unsigned char *next_in; /* next input unsigned char */ + unsigned int avail_in; /* number of unsigned chars available at next_in */ + unsigned long total_in; /* total nb of input unsigned chars read so */ + + unsigned char *next_out; /* next output unsigned char should be put there */ + unsigned int avail_out; /* remaining free space at next_out */ + unsigned long total_out; /* total nb of unsigned chars output so */ + + char *msg; /* last error message, NULL if no error */ + struct internal_state *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + unsigned char* opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: ascii or binary */ + unsigned long adler; /* adler32 value of the uncompressed data */ + unsigned long reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream *z_streamp; + + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, + when reading and decompress it */ +typedef struct +{ + char *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + + unsigned long pos_in_zipfile; /* position in unsigned char on the zipfile, for fseek*/ + unsigned long stream_initialised; /* flag set if stream structure is initialised*/ + + unsigned long offset_local_extrafield;/* offset of the static extra field */ + unsigned int size_local_extrafield;/* size of the static extra field */ + unsigned long pos_local_extrafield; /* position in the static extra field in read*/ + + unsigned long crc32; /* crc32 of all data uncompressed */ + unsigned long crc32_wait; /* crc32 we must obtain after decompress all */ + unsigned long rest_read_compressed; /* number of unsigned char to be decompressed */ + unsigned long rest_read_uncompressed;/*number of unsigned char to be obtained after decomp*/ + FILE* file; /* io structore of the zipfile */ + unsigned long compression_method; /* compression method (0==store) */ + unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/ +} file_in_zip_read_info_s; + + +/* unz_s contain internal information about the zipfile +*/ +typedef struct +{ + FILE* file; /* io structore of the zipfile */ + unz_global_info gi; /* public global information */ + unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/ + unsigned long num_file; /* number of the current file in the zipfile*/ + unsigned long pos_in_central_dir; /* pos of the current file in the central dir*/ + unsigned long current_file_ok; /* flag about the usability of the current file*/ + unsigned long central_pos; /* position of the beginning of the central dir*/ + + unsigned long size_central_dir; /* size of the central directory */ + unsigned long offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info cur_file_info; /* public info about the current file in zip*/ + unz_file_info_internal cur_file_info_internal; /* private info about it*/ + file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current + file if we are decompressing it */ +} unz_s; + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) + +#define UNZ_CASESENSITIVE 1 +#define UNZ_NOTCASESENSITIVE 2 +#define UNZ_OSDEFAULTCASE 0 + +extern int unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity); + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + +extern unzFile unzOpen (const char *path); +extern unzFile unzReOpen (const char* path, unzFile file); + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\zlib\\zlib111.zip" or on an Unix computer + "zlib/zlib111.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ + +extern int unzClose (unzFile file); + +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ + +extern int unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info); + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int unzGetGlobalComment (unzFile file, char *szComment, unsigned long uSizeBuf); + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of unsigned char copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int unzGoToFirstFile (unzFile file); + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int unzGoToNextFile (unzFile file); + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity); + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +extern int unzGetCurrentFileInfo (unzFile file, unz_file_info *pfile_info, char *szFileName, unsigned long fileNameBufferSize, void *extraField, unsigned long extraFieldBufferSize, char *szComment, unsigned long commentBufferSize); + +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int unzOpenCurrentFile (unzFile file); + +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int unzCloseCurrentFile (unzFile file); + +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + + +extern int unzReadCurrentFile (unzFile file, void* buf, unsigned len); + +/* + Read unsigned chars from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of unsigned char copied if somes unsigned chars are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern long unztell(unzFile file); + +/* + Give the current position in uncompressed data +*/ + +extern int unzeof (unzFile file); + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int unzGetLocalExtrafield (unzFile file, void* buf, unsigned len); + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of unsigned chars copied in buf, or (if <0) + the error code +*/ diff --git a/tools/quake3/common/vfs.c b/tools/quake3/common/vfs.c new file mode 100644 index 00000000..25865661 --- /dev/null +++ b/tools/quake3/common/vfs.c @@ -0,0 +1,365 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// +// Rules: +// +// - Directories should be searched in the following order: ~/.q3a/baseq3, +// install dir (/usr/local/games/quake3/baseq3) and cd_path (/mnt/cdrom/baseq3). +// +// - Pak files are searched first inside the directories. +// - Case insensitive. +// - Unix-style slashes (/) (windows is backwards .. everyone knows that) +// +// Leonardo Zide (leo@lokigames.com) +// + +#include + +#if defined (__linux__) || defined (__APPLE__) +#include +#include +#else +#include +#include +#define R_OK 04 +#define S_ISDIR(mode) (mode & _S_IFDIR) +#define PATH_MAX 260 +#endif + +#include +#include +#include + +#include "cmdlib.h" +#include "mathlib.h" +#include +#include "inout.h" +#include "vfs.h" +#include "unzip.h" + +typedef struct +{ + char* name; + unz_s zipinfo; + unzFile zipfile; + guint32 size; +} VFS_PAKFILE; + +// ============================================================================= +// Global variables + +static GSList* g_unzFiles; +static GSList* g_pakFiles; +static char g_strDirs[VFS_MAXDIRS][PATH_MAX]; +static int g_numDirs; +static gboolean g_bUsePak = TRUE; + +// ============================================================================= +// Static functions + +static void vfsAddSlash (char *str) +{ + int n = strlen (str); + if (n > 0) + { + if (str[n-1] != '\\' && str[n-1] != '/') + strcat (str, "/"); + } +} + +static void vfsFixDOSName (char *src) +{ + if (src == NULL) + return; + + while (*src) + { + if (*src == '\\') + *src = '/'; + src++; + } +} + +//!\todo Define globally or use heap-allocated string. +#define NAME_MAX 255 + +static void vfsInitPakFile (const char *filename) +{ + unz_global_info gi; + unzFile uf; + guint32 i; + int err; + + uf = unzOpen (filename); + if (uf == NULL) + return; + + g_unzFiles = g_slist_append (g_unzFiles, uf); + + err = unzGetGlobalInfo (uf,&gi); + if (err != UNZ_OK) + return; + unzGoToFirstFile(uf); + + for (i = 0; i < gi.number_entry; i++) + { + char filename_inzip[NAME_MAX]; + unz_file_info file_info; + VFS_PAKFILE* file; + + err = unzGetCurrentFileInfo (uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); + if (err != UNZ_OK) + break; + + file = (VFS_PAKFILE*)safe_malloc (sizeof (VFS_PAKFILE)); + g_pakFiles = g_slist_append (g_pakFiles, file); + + vfsFixDOSName (filename_inzip); + g_strdown (filename_inzip); + + file->name = strdup (filename_inzip); + file->size = file_info.uncompressed_size; + file->zipfile = uf; + memcpy (&file->zipinfo, uf, sizeof (unz_s)); + + if ((i+1) < gi.number_entry) + { + err = unzGoToNextFile(uf); + if (err!=UNZ_OK) + break; + } + } +} + +// ============================================================================= +// Global functions + +// reads all pak files from a dir +void vfsInitDirectory (const char *path) +{ + char filename[PATH_MAX]; + char *dirlist; + GDir *dir; + + if (g_numDirs == (VFS_MAXDIRS-1)) + return; + + Sys_Printf ("VFS Init: %s\n", path); + + strcpy (g_strDirs[g_numDirs], path); + vfsFixDOSName (g_strDirs[g_numDirs]); + vfsAddSlash (g_strDirs[g_numDirs]); + g_numDirs++; + + if (g_bUsePak) + { + dir = g_dir_open (path, 0, NULL); + + if (dir != NULL) + { + while (1) + { + const char* name = g_dir_read_name(dir); + if(name == NULL) + break; + + dirlist = g_strdup(name); + + { + char *ext = strrchr (dirlist, '.'); + if ((ext == NULL) || (Q_stricmp (ext, ".pk3") != 0)) + continue; + } + + sprintf (filename, "%s/%s", path, dirlist); + vfsInitPakFile (filename); + + g_free(dirlist); + } + g_dir_close (dir); + } + } +} + +// frees all memory that we allocated +void vfsShutdown () +{ + while (g_unzFiles) + { + unzClose ((unzFile)g_unzFiles->data); + g_unzFiles = g_slist_remove (g_unzFiles, g_unzFiles->data); + } + + while (g_pakFiles) + { + VFS_PAKFILE* file = (VFS_PAKFILE*)g_pakFiles->data; + free (file->name); + free (file); + g_pakFiles = g_slist_remove (g_pakFiles, file); + } +} + +// return the number of files that match +int vfsGetFileCount (const char *filename) +{ + int i, count = 0; + char fixed[NAME_MAX], tmp[NAME_MAX]; + GSList *lst; + + strcpy (fixed, filename); + vfsFixDOSName (fixed); + g_strdown (fixed); + + for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst)) + { + VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data; + + if (strcmp (file->name, fixed) == 0) + count++; + } + + for (i = 0; i < g_numDirs; i++) + { + strcpy (tmp, g_strDirs[i]); + strcat (tmp, fixed); + if (access (tmp, R_OK) == 0) + count++; + } + + return count; +} + +// NOTE: when loading a file, you have to allocate one extra byte and set it to \0 +int vfsLoadFile (const char *filename, void **bufferptr, int index) +{ + int i, count = 0; + char tmp[NAME_MAX], fixed[NAME_MAX]; + GSList *lst; + + // filename is a full path + if (index == -1) + { + long len; + FILE *f; + + f = fopen (filename, "rb"); + if (f == NULL) + return -1; + + fseek (f, 0, SEEK_END); + len = ftell (f); + rewind (f); + + *bufferptr = safe_malloc (len+1); + if (*bufferptr == NULL) + return -1; + + fread (*bufferptr, 1, len, f); + fclose (f); + + // we need to end the buffer with a 0 + ((char*) (*bufferptr))[len] = 0; + + return len; + } + + *bufferptr = NULL; + strcpy (fixed, filename); + vfsFixDOSName (fixed); + g_strdown (fixed); + + for (i = 0; i < g_numDirs; i++) + { + strcpy (tmp, g_strDirs[i]); + strcat (tmp, filename); + if (access (tmp, R_OK) == 0) + { + if (count == index) + { + long len; + FILE *f; + + f = fopen (tmp, "rb"); + if (f == NULL) + return -1; + + fseek (f, 0, SEEK_END); + len = ftell (f); + rewind (f); + + *bufferptr = safe_malloc (len+1); + if (*bufferptr == NULL) + return -1; + + fread (*bufferptr, 1, len, f); + fclose (f); + + // we need to end the buffer with a 0 + ((char*) (*bufferptr))[len] = 0; + + return len; + } + + count++; + } + } + + for (lst = g_pakFiles; lst != NULL; lst = g_slist_next (lst)) + { + VFS_PAKFILE* file = (VFS_PAKFILE*)lst->data; + + if (strcmp (file->name, fixed) != 0) + continue; + + if (count == index) + { + memcpy (file->zipfile, &file->zipinfo, sizeof (unz_s)); + + if (unzOpenCurrentFile (file->zipfile) != UNZ_OK) + return -1; + + *bufferptr = safe_malloc (file->size+1); + // we need to end the buffer with a 0 + ((char*) (*bufferptr))[file->size] = 0; + + i = unzReadCurrentFile (file->zipfile , *bufferptr, file->size); + unzCloseCurrentFile (file->zipfile); + if (i < 0) + return -1; + else + return file->size; + } + + count++; + } + + return -1; +} diff --git a/tools/quake3/common/vfs.h b/tools/quake3/common/vfs.h new file mode 100644 index 00000000..44b3ffad --- /dev/null +++ b/tools/quake3/common/vfs.h @@ -0,0 +1,41 @@ +/* +Copyright (c) 2001, Loki software, inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of Loki software nor the names of its contributors may be used +to endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _VFS_H_ +#define _VFS_H_ + +#define VFS_MAXDIRS 8 + +void vfsInitDirectory (const char *path); +void vfsShutdown (); +int vfsGetFileCount (const char *filename); +int vfsLoadFile (const char *filename, void **buffer, int index); + +#endif // _VFS_H_ diff --git a/tools/quake3/q3data/.cvsignore b/tools/quake3/q3data/.cvsignore new file mode 100644 index 00000000..e70c49e1 --- /dev/null +++ b/tools/quake3/q3data/.cvsignore @@ -0,0 +1,12 @@ +q3map +*.d +*.o +*.bak +*.BAK +*~ +*.ncb +*.plg +*.opt +*.log +Debug +Release diff --git a/tools/quake3/q3data/.cvswrappers b/tools/quake3/q3data/.cvswrappers new file mode 100644 index 00000000..2ea7d171 --- /dev/null +++ b/tools/quake3/q3data/.cvswrappers @@ -0,0 +1,2 @@ +*.dsp -m 'COPY' -k 'b' +*.dsw -m 'COPY' -k 'b' diff --git a/tools/quake3/q3data/3dslib.c b/tools/quake3/q3data/3dslib.c new file mode 100644 index 00000000..e2012c70 --- /dev/null +++ b/tools/quake3/q3data/3dslib.c @@ -0,0 +1,651 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include "q3data.h" + +static void Load3DS( const char *filename, _3DS_t *p3DS, qboolean verbose ); + +static qboolean s_verbose; + +#define MAX_MATERIALS 100 +#define MAX_NAMED_OBJECTS 100 +#define MAX_MESH_MATERIAL_GROUPS 100 +#define MAX_TRI_OBJECTS 512 + +static char s_buffer[1000000]; + +static int ReadString( FILE *fp, char *buffer ) +{ + int i = 0; + int bytesRead = 0; + + do + { + fread( &buffer[i], 1, sizeof( char ), fp ); + bytesRead++; + } while ( buffer[i++] != 0 ); + buffer[i] = 0; + + return bytesRead; +} + +static int ReadChunkAndLength( FILE *fp, short *chunk, long *len ) +{ + if ( fread( chunk, sizeof( short ), 1, fp ) != 1 ) + return 0; + if ( fread( len, sizeof( long ), 1, fp ) != 1 ) + Error( "Unexpected EOF found" ); + return 1; +} + +static void LoadMapName( FILE *fp, char *buffer, int thisChunkLen ) +{ + unsigned short chunkID; + long chunkLen; + long bytesRead = 0; + + while ( ReadChunkAndLength( fp, &chunkID, &chunkLen ) ) + { + switch ( chunkID ) + { + case _3DS_CHUNK_MAT_MAPNAME: + fread( buffer, chunkLen - 6, 1, fp ); + break; + default: + fread( s_buffer, chunkLen - 6, 1, fp ); + break; + } + bytesRead += chunkLen; + if ( bytesRead >= thisChunkLen ) + return; + } +} + +static void LoadMaterialList( FILE *fp, long thisChunkLen, _3DSMaterial_t *pMat ) +{ + long chunkLen; + unsigned short chunkID; + long bytesRead = 0; + _3DSMaterial_t mat; + char curdir[1024]; + char buffer[2048]; + + memset( &mat, 0, sizeof( mat ) ); + + if ( s_verbose ) + printf( " >>> MATERIAL LIST\n" ); + + while ( ReadChunkAndLength( fp, &chunkID, &chunkLen ) ) + { + switch ( chunkID ) + { + case _3DS_CHUNK_MAT_NAME: + fread( mat.name, chunkLen - 6, 1, fp ); + if ( s_verbose ) + printf( " found mat name '%s'\n", mat.name ); + break; + case _3DS_CHUNK_TEXMAP: + LoadMapName( fp, mat.texture, chunkLen - 6 ); + if ( s_verbose ) + printf( " found texture '%s'\n", mat.texture ); + break; + case _3DS_CHUNK_SPECMAP: + LoadMapName( fp, mat.specular, chunkLen - 6 ); + if ( s_verbose ) + printf( " found specular map '%s'\n", mat.specular ); + break; + case _3DS_CHUNK_OPACMAP: + LoadMapName( fp, mat.opacity, chunkLen - 6 ); + if ( s_verbose ) + printf( " found opacity map '%s'\n", mat.opacity ); + break; + case _3DS_CHUNK_REFLMAP: + LoadMapName( fp, mat.reflection, chunkLen - 6 ); + if ( s_verbose ) + printf( " found reflection map '%s'\n", mat.reflection ); + break; + case _3DS_CHUNK_BUMPMAP: + LoadMapName( fp, mat.bump, chunkLen - 6 ); + if ( s_verbose ) + printf( " found bump map '%s'\n", mat.bump ); + break; + default: + fread( s_buffer, chunkLen - 6, 1, fp ); + break; + } + + bytesRead += chunkLen; + + if ( bytesRead >= thisChunkLen ) + break; + } + + Q_getwd( curdir ); + + if ( mat.texture[0] ) + { + sprintf( buffer, "%s%s", curdir, mat.texture ); + if ( strstr( buffer, gamedir + 1 ) ) + strcpy( mat.texture, strstr( buffer, gamedir + 1 ) + strlen( gamedir ) - 1 ); + else + strcpy( mat.texture, buffer ); + } + + if ( mat.specular[0] ) + { + sprintf( buffer, "%s%s", curdir, mat.specular ); + if ( strstr( buffer, gamedir + 1 ) ) + strcpy( mat.specular, strstr( buffer, gamedir + 1 ) + strlen( gamedir ) - 1 ); + else + strcpy( mat.specular, buffer ); + } + + if ( mat.bump[0] ) + { + sprintf( buffer, "%s%s", curdir, mat.bump ); + if ( strstr( buffer, gamedir + 1 ) ) + strcpy( mat.bump, strstr( buffer, gamedir + 1 ) + strlen( gamedir ) - 1 ); + else + strcpy( mat.bump, buffer ); + } + + if ( mat.reflection[0] ) + { + sprintf( buffer, "%s%s", curdir, mat.reflection ); + if ( strstr( buffer, gamedir + 1 ) ) + strcpy( mat.reflection, strstr( buffer, gamedir + 1 ) + strlen( gamedir ) - 1 ); + else + strcpy( mat.reflection, buffer ); + } + + if ( mat.opacity[0] ) + { + sprintf( buffer, "%s%s", curdir, mat.opacity ); + if ( strstr( buffer, gamedir + 1 ) ) + strcpy( mat.opacity, strstr( buffer, gamedir + 1 ) + strlen( gamedir ) - 1 ); + else + strcpy( mat.opacity, buffer ); + } + + *pMat = mat; +} + +static void LoadMeshMaterialGroup( FILE *fp, long thisChunkLen, _3DSMeshMaterialGroup_t *pMMG ) +{ + _3DSMeshMaterialGroup_t mmg; + + memset( &mmg, 0, sizeof( mmg ) ); + + ReadString( fp, mmg.name ); + + fread( &mmg.numFaces, sizeof( mmg.numFaces ), 1, fp ); + mmg.pFaces = malloc( sizeof( mmg.pFaces[0] ) * mmg.numFaces ); + fread( mmg.pFaces, sizeof( mmg.pFaces[0] ), mmg.numFaces, fp ); + + if ( s_verbose ) + { + printf( " >>> MESH MATERIAL GROUP '%s' (%d faces)\n", mmg.name, mmg.numFaces ); + + { + int i; + + for ( i = 0; i < mmg.numFaces; i++ ) + { + printf( " %d\n", mmg.pFaces[i] ); + } + } + } + + *pMMG = mmg; +} + +static void LoadNamedTriObject( FILE *fp, long thisChunkLen, _3DSTriObject_t *pTO ) +{ + long chunkLen; + unsigned short chunkID; + int i = 0; + long bytesRead = 0; + _3DSTriObject_t triObj; + _3DSMeshMaterialGroup_t meshMaterialGroups[MAX_MESH_MATERIAL_GROUPS]; + int numMeshMaterialGroups = 0; + + memset( &triObj, 0, sizeof( triObj ) ); + + if ( s_verbose ) + printf( " >>> NAMED TRI OBJECT\n" ); + + while ( ReadChunkAndLength( fp, &chunkID, &chunkLen ) ) + { + switch ( chunkID ) + { + case _3DS_CHUNK_MSH_MAT_GROUP: + LoadMeshMaterialGroup( fp, chunkLen - 6, &meshMaterialGroups[numMeshMaterialGroups] ); + bytesRead += chunkLen; + numMeshMaterialGroups++; + break; + case _3DS_CHUNK_FACE_ARRAY: + fread( &triObj.numFaces, sizeof( triObj.numFaces ), 1, fp ); + assert( triObj.pFaces == 0 ); + + triObj.pFaces = malloc( sizeof( triObj.pFaces[0] ) * triObj.numFaces ); + fread( triObj.pFaces, sizeof( triObj.pFaces[0] ), triObj.numFaces, fp ); + bytesRead += sizeof( triObj.numFaces ) + triObj.numFaces * sizeof( triObj.pFaces[0] ) + 6; + + if ( s_verbose ) + { + printf( " found face array with %d faces\n", triObj.numFaces ); + for ( i = 0; i < triObj.numFaces; i++ ) + { + printf( " %d: %d,%d,%d\n", i, triObj.pFaces[i].a, triObj.pFaces[i].b, triObj.pFaces[i].c ); + } + } + + break; + case _3DS_CHUNK_POINT_ARRAY: + fread( &triObj.numPoints, sizeof( triObj.numPoints ), 1, fp ); + triObj.pPoints = malloc( sizeof( triObj.pPoints[0] ) * triObj.numPoints ); + fread( triObj.pPoints, sizeof( triObj.pPoints[0] ), triObj.numPoints, fp ); + bytesRead += sizeof( triObj.numPoints ) + triObj.numPoints * sizeof( triObj.pPoints[0] ) + 6; + + // flip points around into our coordinate system + for ( i = 0; i < triObj.numPoints; i++ ) + { + float x, y, z; + + x = triObj.pPoints[i].x; + y = triObj.pPoints[i].y; + z = triObj.pPoints[i].z; + + triObj.pPoints[i].x = -y; + triObj.pPoints[i].y = x; + triObj.pPoints[i].z = z; + } + + if ( s_verbose ) + { + printf( " found point array with %d points\n", triObj.numPoints ); + for ( i = 0; i < triObj.numPoints; i++ ) + { + printf( " %d: %f,%f,%f\n", i, triObj.pPoints[i].x, triObj.pPoints[i].y, triObj.pPoints[i].z ); + } + } + break; + case _3DS_CHUNK_TEX_VERTS: + fread( &triObj.numTexVerts, sizeof( triObj.numTexVerts ), 1, fp ); + triObj.pTexVerts = malloc( sizeof( triObj.pTexVerts[0] ) * triObj.numTexVerts ); + fread( triObj.pTexVerts, sizeof( triObj.pTexVerts[0] ), triObj.numTexVerts, fp ); + bytesRead += sizeof( triObj.numTexVerts ) + sizeof( triObj.pTexVerts[0] ) * triObj.numTexVerts + 6; + + if ( s_verbose ) + { + printf( " found tex vert array with %d tex verts\n", triObj.numTexVerts ); + for ( i = 0; i < triObj.numTexVerts; i++ ) + { + printf( " %d: %f,%f\n", i, triObj.pTexVerts[i].s, triObj.pTexVerts[i].t ); + } + } + break; + default: + fread( s_buffer, chunkLen - 6, 1, fp ); + bytesRead += chunkLen; + break; + } + + if ( bytesRead >= thisChunkLen ) + break; + } + *pTO = triObj; + + if ( numMeshMaterialGroups == 0 ) + { + numMeshMaterialGroups = 1; + strcpy( meshMaterialGroups[0].name, "(null)" ); + if ( pTO->numTexVerts ) { + printf( "Warning: assigning (null) skin to tri object\n" ); + } + } + else + { + assert( pTO->numFaces == meshMaterialGroups[0].numFaces ); + } + + pTO->pMeshMaterialGroups = malloc( sizeof( _3DSMeshMaterialGroup_t ) * numMeshMaterialGroups ); + memcpy( pTO->pMeshMaterialGroups, meshMaterialGroups, numMeshMaterialGroups * sizeof( meshMaterialGroups[0] ) ); + pTO->numMeshMaterialGroups = numMeshMaterialGroups; + + // + // sanity checks + // + assert( numMeshMaterialGroups <= 1 ); +} + +static void LoadNamedObject( FILE *fp, long thisChunkLen, _3DSNamedObject_t *pNO ) +{ + long chunkLen; + unsigned short chunkID; + int i = 0; + long bytesRead = 0; + char name[100]; + _3DSTriObject_t triObj[MAX_TRI_OBJECTS]; + int numTriObjects = 0; + + memset( triObj, 0, sizeof( triObj ) ); + + bytesRead += ReadString( fp, name ); + + if ( s_verbose ) + printf( " >>> NAMED OBJECT '%s'\n", name ); + + while ( ReadChunkAndLength( fp, &chunkID, &chunkLen ) ) + { + switch ( chunkID ) + { + case _3DS_CHUNK_NAMED_TRI_OBJECT: + LoadNamedTriObject( fp, chunkLen - 6, &triObj[numTriObjects] ); + numTriObjects++; + break; + default: + fread( s_buffer, chunkLen - 6, 1, fp ); + break; + } + + bytesRead += chunkLen; + + if ( bytesRead >= thisChunkLen ) + break; + } + + strcpy( pNO->name, name ); + pNO->pTriObjects = malloc( sizeof( _3DSTriObject_t ) * numTriObjects ); + memcpy( pNO->pTriObjects, triObj, sizeof( triObj[0] ) * numTriObjects ); + pNO->numTriObjects = numTriObjects; + + assert( numTriObjects <= 1 ); +} + +static void LoadEditChunk( FILE *fp, long thisChunkLen, _3DSEditChunk_t *pEC ) +{ + unsigned short chunkID; + long chunkLen; + long bytesRead = 0; + _3DSEditChunk_t editChunk; + + _3DSMaterial_t mat[MAX_MATERIALS]; + _3DSNamedObject_t namedObjects[MAX_NAMED_OBJECTS]; + + int numMaterials = 0, numNamedObjects = 0; + + memset( &editChunk, 0, sizeof( editChunk ) ); + + if ( s_verbose ) + printf( ">>> EDIT CHUNK\n" ); + + while ( ReadChunkAndLength( fp, &chunkID, &chunkLen ) ) + { + switch ( chunkID ) + { + case _3DS_CHUNK_MAT_LIST: + LoadMaterialList( fp, chunkLen - 6, &mat[numMaterials] ); + numMaterials++; + break; + case _3DS_CHUNK_NAMED_OBJECT: + LoadNamedObject( fp, chunkLen - 6, &namedObjects[numNamedObjects] ); + if ( namedObjects[numNamedObjects].numTriObjects != 0 ) + ++numNamedObjects; + break; + case _3DS_CHUNK_MESH_VERSION: + default: + fread( s_buffer, chunkLen - 6, 1, fp ); + break; + } + + bytesRead += chunkLen; + + if ( bytesRead >= thisChunkLen ) + break; + } + + if ( numMaterials == 0 ) + { + numMaterials = 1; + strcpy( mat[0].name, "(null)" ); + printf( "Warning: no material definitions found\n" ); + } + + pEC->numNamedObjects = numNamedObjects; + + pEC->pMaterials = malloc( sizeof( _3DSMaterial_t ) * numMaterials ); + pEC->pNamedObjects = malloc( sizeof( _3DSNamedObject_t ) * numNamedObjects ); + + memcpy( pEC->pMaterials, mat, numMaterials * sizeof( mat[0] ) ); + memcpy( pEC->pNamedObjects, namedObjects, numNamedObjects * sizeof( namedObjects[0] ) ); +} + +static void Load3DS( const char *filename, _3DS_t *p3DS, qboolean verbose ) +{ + FILE *fp; + unsigned short chunkID; + long chunkLen; + _3DSEditChunk_t editChunk; + + s_verbose = verbose; + + if ( ( fp = fopen( filename, "rb" ) ) == 0 ) + Error( "Unable to open '%s'", filename ); + + // read magic number + if ( ( fread( &chunkID, sizeof( short ), 1, fp ) != 1 ) || + ( LittleShort( chunkID ) != _3DS_CHUNK_MAGIC ) ) + { + Error( "Missing or incorrect magic number in '%s'", filename ); + } + if ( fread( &chunkLen, sizeof( chunkLen ), 1, fp ) != 1 ) + Error( "Unexpected EOF encountered in '%s'", filename ); + // version number + if ( !ReadChunkAndLength( fp, &chunkID, &chunkLen ) ) + Error( "Missing version number in '%s'", filename ); + if ( fread( s_buffer, chunkLen - 6, 1, fp ) != 1 ) + Error( "Unexpected EOF encountered in '%s'", filename ); + + while ( ReadChunkAndLength( fp, &chunkID, &chunkLen ) ) + { + switch ( chunkID ) + { + case _3DS_CHUNK_EDIT: + LoadEditChunk( fp, chunkLen - 6, &editChunk ); + break; + case _3DS_CHUNK_KEYFRAME_DATA: + fread( s_buffer, chunkLen - 6, 1, fp ); + break; + default: + fread( s_buffer, chunkLen - 6, 1, fp ); + break; + } + } + + fclose( fp ); + + p3DS->editChunk = editChunk; +} + +static void ComputeNormals( _3DSTriObject_t *pTO, triangle_t *pTris ) +{ + vec3_t faceNormals[POLYSET_MAXTRIANGLES]; + vec3_t vertexNormals[POLYSET_MAXTRIANGLES*3]; + vec3_t side0, side1, facenormal; + int f, v; + + memset( faceNormals, 0, sizeof( faceNormals ) ); + memset( vertexNormals, 0, sizeof( vertexNormals ) ); + + // + // compute face normals + // + for ( f = 0; f < pTO->numFaces; f++ ) + { + VectorSubtract( pTris[f].verts[0], pTris[f].verts[1], side0 ); + VectorSubtract( pTris[f].verts[2], pTris[f].verts[1], side1 ); + + CrossProduct( side0, side1, facenormal ); + VectorNormalize( facenormal, faceNormals[f] ); + } + + // + // sum vertex normals + // + for ( v = 0; v < pTO->numPoints; v++ ) + { + for ( f = 0; f < pTO->numFaces; f++ ) + { + if ( ( pTO->pFaces[f].a == v ) || + ( pTO->pFaces[f].b == v ) || + ( pTO->pFaces[f].c == v ) ) + { + vertexNormals[v][0] += faceNormals[f][0]; + vertexNormals[v][1] += faceNormals[f][1]; + vertexNormals[v][2] += faceNormals[f][2]; + } + } + + VectorNormalize( vertexNormals[v], vertexNormals[v] ); + } + + // + // copy vertex normals into triangles + // + for ( f = 0; f < pTO->numFaces; f++ ) + { + int i0 = pTO->pFaces[f].c; + int i1 = pTO->pFaces[f].b; + int i2 = pTO->pFaces[f].a; + + VectorCopy( vertexNormals[i0], pTris[f].normals[0] ); + VectorCopy( vertexNormals[i1], pTris[f].normals[1] ); + VectorCopy( vertexNormals[i2], pTris[f].normals[2] ); + } +} + +/* +** void _3DS_LoadPolysets +*/ +void _3DS_LoadPolysets( const char *filename, polyset_t **ppPSET, int *numpsets, qboolean verbose ) +{ + _3DS_t _3ds; + int numPolysets; + polyset_t *pPSET; + triangle_t *ptri, *triangles; + int i; + + // load the 3DS + memset( &_3ds, 0, sizeof( _3ds ) ); + Load3DS( filename, &_3ds, verbose ); + + // compute information + numPolysets = _3ds.editChunk.numNamedObjects; + + // allocate memory + pPSET = calloc( 1, numPolysets * sizeof( polyset_t ) ); + triangles = ptri = calloc( 1, POLYSET_MAXTRIANGLES * sizeof( triangle_t ) ); + + // copy the data over + for ( i = 0; i < numPolysets; i++ ) + { + char matnamebuf[1024]; + int j; + triangle_t *tri; + _3DSTriObject_t *pTO = &_3ds.editChunk.pNamedObjects[i].pTriObjects[0]; + + pPSET[i].triangles = ptri; + pPSET[i].numtriangles = pTO->numFaces; + strcpy( pPSET[i].name, _3ds.editChunk.pNamedObjects[i].name ); + + strcpy( matnamebuf, filename ); + if ( strrchr( matnamebuf, '/' ) ) + *( strrchr( matnamebuf, '/' ) + 1 )= 0; + strcat( matnamebuf, pTO->pMeshMaterialGroups[0].name ); + + if ( strstr( matnamebuf, gamedir ) ) + strcpy( pPSET[i].materialname, strstr( matnamebuf, gamedir ) + strlen( gamedir ) ); + else + strcpy( pPSET[i].materialname, pTO->pMeshMaterialGroups[0].name ); + + assert( pPSET[i].numtriangles < POLYSET_MAXTRIANGLES ); + + for ( tri = ptri, j = 0; j < pPSET[i].numtriangles; j++ ) + { + int i0 = pTO->pFaces[j].c; + int i1 = pTO->pFaces[j].b; + int i2 = pTO->pFaces[j].a; + + tri->verts[0][0] = pTO->pPoints[i0].x; + tri->verts[0][1] = pTO->pPoints[i0].y; + tri->verts[0][2] = pTO->pPoints[i0].z; + + tri->verts[1][0] = pTO->pPoints[i1].x; + tri->verts[1][1] = pTO->pPoints[i1].y; + tri->verts[1][2] = pTO->pPoints[i1].z; + + tri->verts[2][0] = pTO->pPoints[i2].x; + tri->verts[2][1] = pTO->pPoints[i2].y; + tri->verts[2][2] = pTO->pPoints[i2].z; +/* + for ( k = 0; k < 3; k++ ) + { + tri->colors[0][k] = 1; + tri->colors[1][k] = 1; + tri->colors[2][k] = 1; + } +*/ + + if ( pTO->pTexVerts ) + { + tri->texcoords[0][0] = pTO->pTexVerts[i0].s; + tri->texcoords[0][1] = 1.0f - pTO->pTexVerts[i0].t; + tri->texcoords[1][0] = pTO->pTexVerts[i1].s; + tri->texcoords[1][1] = 1.0f - pTO->pTexVerts[i1].t; + tri->texcoords[2][0] = pTO->pTexVerts[i2].s; + tri->texcoords[2][1] = 1.0f - pTO->pTexVerts[i2].t; + } + + tri++; + } + + ptri += pPSET[i].numtriangles; + assert( ptri - triangles < POLYSET_MAXTRIANGLES ); + } + + // compute normal data +#if 0 + for ( i = 0; i < numPolysets; i++ ) + { + // unique vertices based solely on vertex position + ComputeNormals( &_3ds.editChunk.pNamedObjects[i].pTriObjects[0], + pPSET[i].triangles ); + } +#endif + + free( _3ds.editChunk.pMaterials ); + free( _3ds.editChunk.pNamedObjects ); + + *ppPSET = pPSET; + *numpsets = numPolysets; +} diff --git a/tools/quake3/q3data/3dslib.h b/tools/quake3/q3data/3dslib.h new file mode 100644 index 00000000..406fe2d7 --- /dev/null +++ b/tools/quake3/q3data/3dslib.h @@ -0,0 +1,139 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +typedef struct +{ + float x, y, z; +} _3DSPoint_t; + +typedef struct +{ + short a, b, c; + short flags; +} _3DSFace_t; + +typedef struct +{ + float s, t; +} _3DSTexVert_t; + +typedef struct +{ + char name[100]; + short numFaces; + short *pFaces; +} _3DSMeshMaterialGroup_t; + +typedef struct +{ + char name[80]; + + char texture[100]; + char specular[100]; + char reflection[100]; + char bump[100]; + char opacity[100]; +} _3DSMaterial_t; + +typedef struct +{ + short numFaces, numPoints, numTexVerts; + int numMeshMaterialGroups; + + _3DSPoint_t *pPoints; + _3DSFace_t *pFaces; + _3DSTexVert_t *pTexVerts; + + _3DSMeshMaterialGroup_t *pMeshMaterialGroups; +} _3DSTriObject_t; + +typedef struct +{ + char name[100]; + + int numTriObjects; + _3DSTriObject_t *pTriObjects; +} _3DSNamedObject_t; + +typedef struct +{ + int numNamedObjects; + int numMaterials; + + _3DSNamedObject_t *pNamedObjects; + _3DSMaterial_t *pMaterials; + +} _3DSEditChunk_t; + +typedef struct +{ + _3DSEditChunk_t editChunk; +} _3DS_t; + +#define _3DS_CHUNK_NULL 0x0000 +#define _3DS_CHUNK_UNKNOWN0 0x0001 +#define _3DS_CHUNK_M3D_VERSION 0x0002 +#define _3DS_CHUNK_M3D_KFVERSION 0x0005 +#define _3DS_CHUNK_COLOR_F 0x0010 +#define _3DS_CHUNK_COLOR_24 0x0011 +#define _3DS_CHUNK_LIN_COLOR_24 0x0012 +#define _3DS_CHUNK_LIN_COLOR_F 0x0013 +#define _3DS_CHUNK_INT_PERCENTAGE 0x0030 +#define _3DS_CHUNK_FLOAT_PERCENT 0x0031 +#define _3DS_CHUNK_MASTER_SCALE 0x0100 +#define _3DS_CHUNK_CHUNK_TYPE 0x0995 +#define _3DS_CHUNK_CHUNK_UNIQUE 0x0996 +#define _3DS_CHUNK_NOT_CHUNK 0x0997 +#define _3DS_CHUNK_CONTAINER 0x0998 +#define _3DS_CHUNK_IS_CHUNK 0x0999 +#define _3DS_CHUNK_C_SXP_SELFI_MASKDATA 0x0c3c + +#define _3DS_CHUNK_BITMAP 0x1100 +#define _3DS_CHUNK_USE_BITMAP 0x1101 +#define _3DS_CHUNK_SOLID_BGND 0x1200 +#define _3DS_CHUNK_USE_SOLID_BGND 0x1201 + +#define _3DS_CHUNK_EDIT 0x3d3d +#define _3DS_CHUNK_MESH_VERSION 0x3d3e + +#define _3DS_CHUNK_NAMED_OBJECT 0x4000 +#define _3DS_CHUNK_NAMED_TRI_OBJECT 0x4100 +#define _3DS_CHUNK_POINT_ARRAY 0x4110 +#define _3DS_CHUNK_POINT_FLAG_ARRAY 0x4111 +#define _3DS_CHUNK_FACE_ARRAY 0x4120 +#define _3DS_CHUNK_MSH_MAT_GROUP 0x4130 +#define _3DS_CHUNK_TEX_VERTS 0x4140 +#define _3DS_CHUNK_SMOOTH_GROUP 0x4150 +#define _3DS_CHUNK_MESH_MATRIX 0x4160 +#define _3DS_CHUNK_MAGIC 0x4d4d + +#define _3DS_CHUNK_MAT_NAME 0xa000 +#define _3DS_CHUNK_TEXMAP 0xa200 +#define _3DS_CHUNK_SPECMAP 0xa204 +#define _3DS_CHUNK_OPACMAP 0xa210 +#define _3DS_CHUNK_REFLMAP 0xa220 +#define _3DS_CHUNK_BUMPMAP 0xa230 +#define _3DS_CHUNK_MAT_MAPNAME 0xa300 +#define _3DS_CHUNK_MAT_LIST 0xafff + +#define _3DS_CHUNK_KEYFRAME_DATA 0xb000 + +void _3DS_LoadPolysets( const char *filename, polyset_t **ppPSET, int *numpsets, qboolean verbose ); diff --git a/tools/quake3/q3data/compress.c b/tools/quake3/q3data/compress.c new file mode 100644 index 00000000..fdf9043a --- /dev/null +++ b/tools/quake3/q3data/compress.c @@ -0,0 +1,771 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "q3data.h" + +#if 0 +/* +================== +MTF +================== +*/ +cblock_t MTF (cblock_t in) +{ + int i, j, b, code; + byte *out_p; + int index[256]; + cblock_t out; + + out_p = out.data = malloc(in.count + 4); + + // write count + *out_p++ = in.count&255; + *out_p++ = (in.count>>8)&255; + *out_p++ = (in.count>>16)&255; + *out_p++ = (in.count>>24)&255; + + for (i=0 ; i<256 ; i++) + index[i] = i; + + for (i=0 ; i b2) + return 1; + if (++i1 == bwt_size) + i1 = 0; + if (++i2 == bwt_size) + i2 = 0; + } + + return 0; +} + +/* +================== +BWT +================== +*/ +cblock_t BWT (cblock_t in) +{ + int *sorted; + int i; + byte *out_p; + cblock_t out; + + bwt_size = in.count; + bwt_data = in.data; + + sorted = malloc(in.count*sizeof(*sorted)); + for (i=0 ; i>8)&255; + *out_p++ = (in.count>>16)&255; + *out_p++ = (in.count>>24)&255; + + // write head index + for (i=0 ; i>8)&255; + *out_p++ = (i>>16)&255; + *out_p++ = (i>>24)&255; + + // write the L column + for (i=0 ; i 32) + Error ("bitcount > 32"); + charbits[nodenum] = bits; + charbitscount[nodenum] = bitcount; + return; + } + + node = &hnodes[nodenum]; + bits <<= 1; + BuildChars (node->children[0], bits, bitcount+1); + bits |= 1; + BuildChars (node->children[1], bits, bitcount+1); +} + +/* +================== +Huffman +================== +*/ +cblock_t Huffman (cblock_t in) +{ + int i; + hnode_t *node; + int outbits, c; + unsigned bits; + byte *out_p; + cblock_t out; + int max, maxchar; + + // count + memset (hnodes, 0, sizeof(hnodes)); + for (i=0 ; i max) + { + max = hnodes[i].count; + maxchar = i; + } + } + if (max == 0) + Error ("Huffman: max == 0"); + + for (i=0 ; i<256 ; i++) + { + hnodes[i].count = (hnodes[i].count*255+max-1) / max; + } + + // build the nodes + numhnodes = 256; + while (numhnodes != 511) + { + node = &hnodes[numhnodes]; + + // pick two lowest counts + node->children[0] = SmallestNode (); + if (node->children[0] == -1) + break; // no more + + node->children[1] = SmallestNode (); + if (node->children[1] == -1) + { + if (node->children[0] != numhnodes-1) + Error ("Bad smallestnode"); + break; + } + node->count = hnodes[node->children[0]].count + + hnodes[node->children[1]].count; + numhnodes++; + } + + BuildChars (numhnodes-1, 0, 0); + + out_p = out.data = malloc(in.count*2 + 1024); + memset (out_p, 0, in.count*2+1024); + + // write count + *out_p++ = in.count&255; + *out_p++ = (in.count>>8)&255; + *out_p++ = (in.count>>16)&255; + *out_p++ = (in.count>>24)&255; + + // save out the 256 normalized counts so the tree can be recreated + for (i=0 ; i<256 ; i++) + *out_p++ = hnodes[i].count; + + // write bits + outbits = 0; + for (i=0 ; i>3] |= 1<<(outbits&7); + outbits++; + } + } + + out_p += (outbits+7)>>3; + + out.count = out_p - out.data; + + return out; +} + +//========================================================================== + +/* +================== +RLE +================== +*/ +#define RLE_CODE 0xe8 +#define RLE_TRIPPLE 0xe9 + +int rle_counts[256]; +int rle_bytes[256]; + +cblock_t RLE (cblock_t in) +{ + int i; + byte *out_p; + int val; + int repeat; + cblock_t out; + + out_p = out.data = malloc (in.count*2); + + // write count + *out_p++ = in.count&255; + *out_p++ = (in.count>>8)&255; + *out_p++ = (in.count>>16)&255; + *out_p++ = (in.count>>24)&255; + + for (i=0 ; i 3 || val == RLE_CODE) + { + *out_p++ = RLE_CODE; + *out_p++ = val; + *out_p++ = repeat; + } + else + { + while (repeat--) + *out_p++ = val; + } + } + + out.count = out_p - out.data; + return out; +} + +//========================================================================== + +unsigned lzss_head[256]; +unsigned lzss_next[0x20000]; + +/* +================== +LZSS +================== +*/ +#define BACK_WINDOW 0x10000 +#define BACK_BITS 16 +#define FRONT_WINDOW 16 +#define FRONT_BITS 4 +cblock_t LZSS (cblock_t in) +{ + int i; + byte *out_p; + cblock_t out; + int val; + int j, start, max; + int bestlength, beststart; + int outbits; + +if (in.count >= sizeof(lzss_next)/4) +Error ("LZSS: too big"); + + memset (lzss_head, -1, sizeof(lzss_head)); + + out_p = out.data = malloc (in.count*2); + memset (out.data, 0, in.count*2); + + // write count + *out_p++ = in.count&255; + *out_p++ = (in.count>>8)&255; + *out_p++ = (in.count>>16)&255; + *out_p++ = (in.count>>24)&255; + + outbits = 0; + for (i=0 ; i in.count) + max = in.count - i; + + start = lzss_head[val]; + while (start != -1 && start >= i-BACK_WINDOW) + { + // count match length + for (j=0 ; j bestlength) + { + bestlength = j; + beststart = start; + } + start = lzss_next[start]; + } + +#else +// slow simple search + // search for a match + max = FRONT_WINDOW; + if (i + max > in.count) + max = in.count - i; + + start = i - BACK_WINDOW; + if (start < 0) + start = 0; + bestlength = 0; + beststart = 0; + for ( ; start < i ; start++) + { + if (in.data[start] != val) + continue; + // count match length + for (j=0 ; j bestlength) + { + bestlength = j; + beststart = start; + } + } +#endif + beststart = BACK_WINDOW - (i-beststart); + + if (bestlength < 3) + { // output a single char + bestlength = 1; + + out_p[outbits>>3] |= 1<<(outbits&7); // set bit to mark char + outbits++; + for (j=0 ; j<8 ; j++, outbits++) + if (val & (1<>3] |= 1<<(outbits&7); + } + else + { // output a phrase + outbits++; // leave a 0 bit to mark phrase + for (j=0 ; j>3] |= 1<<(outbits&7); + for (j=0 ; j>3] |= 1<<(outbits&7); + } + + while (bestlength--) + { + val = in.data[i]; + lzss_next[i] = lzss_head[val]; + lzss_head[val] = i; + i++; + } + } + + out_p += (outbits+7)>>3; + out.count = out_p - out.data; + return out; +} + +//========================================================================== + +#define MIN_REPT 15 +#define MAX_REPT 0 +#define HUF_TOKENS (256+MAX_REPT) + +unsigned charbits1[256][HUF_TOKENS]; +int charbitscount1[256][HUF_TOKENS]; + +hnode_t hnodes1[256][HUF_TOKENS*2]; +int numhnodes1[256]; + +int order0counts[256]; + +/* +================== +SmallestNode1 +================== +*/ +int SmallestNode1 (hnode_t *hnodes, int numhnodes) +{ + int i; + int best, bestnode; + + best = 99999999; + bestnode = -1; + for (i=0 ; i 32) + Error ("bitcount > 32"); + charbits1[prev][nodenum] = bits; + charbitscount1[prev][nodenum] = bitcount; + return; + } + + node = &hnodes1[prev][nodenum]; + bits <<= 1; + BuildChars1 (prev, node->children[0], bits, bitcount+1); + bits |= 1; + BuildChars1 (prev, node->children[1], bits, bitcount+1); +} + + +/* +================== +BuildTree1 +================== +*/ +void BuildTree1 (int prev) +{ + hnode_t *node, *nodebase; + int numhnodes; + + // build the nodes + numhnodes = HUF_TOKENS; + nodebase = hnodes1[prev]; + while (1) + { + node = &nodebase[numhnodes]; + + // pick two lowest counts + node->children[0] = SmallestNode1 (nodebase, numhnodes); + if (node->children[0] == -1) + break; // no more + + node->children[1] = SmallestNode1 (nodebase, numhnodes); + if (node->children[1] == -1) + break; + + node->count = nodebase[node->children[0]].count + + nodebase[node->children[1]].count; + numhnodes++; + } + numhnodes1[prev] = numhnodes-1; + BuildChars1 (prev, numhnodes-1, 0, 0); +} + + +/* +================== +Huffman1_Count +================== +*/ +void Huffman1_Count (cblock_t in) +{ + int i; + int prev; + int v; + int rept; + + prev = 0; + for (i=0 ; i MIN_REPT) + { + hnodes1[prev][255+rept].count++; + i += rept-1; + } +#endif + } +} + + +/* +================== +Huffman1_Build +================== +*/ +byte scaled[256][HUF_TOKENS]; +void Huffman1_Build (FILE *f) +{ + int i, j, v; + int max; + int total; + + for (i=0 ; i<256 ; i++) + { + // normalize and save the counts + max = 0; + for (j=0 ; j max) + max = hnodes1[i][j].count; + } + if (max == 0) + max = 1; + total = 0; + for (j=0 ; j 255) + Error ("v > 255"); + scaled[i][j] = hnodes1[i][j].count = v; + if (v) + total++; + } + if (total == 1) + { // must have two tokens + if (!scaled[i][0]) + scaled[i][0] = hnodes1[i][0].count = 1; + else + scaled[i][1] = hnodes1[i][1].count = 1; + } + + BuildTree1 (i); + } + +#if 0 + // count up the total bits + total = 0; + for (i=0 ; i<256 ; i++) + for (j=0 ; j<256 ; j++) + total += charbitscount1[i][j] * hnodes1[i][j].count; + + total = (total+7)/8; + printf ("%i bytes huffman1 compressed\n", total); +#endif + + fwrite (scaled, 1, sizeof(scaled), f); +} + +/* +================== +Huffman1 + +Order 1 compression with pre-built table +================== +*/ +cblock_t Huffman1 (cblock_t in) +{ + int i; + int outbits, c; + unsigned bits; + byte *out_p; + cblock_t out; + int prev; + int v; + int rept; + + out_p = out.data = malloc(in.count*2 + 1024); + memset (out_p, 0, in.count*2+1024); + + // write count + *out_p++ = in.count&255; + *out_p++ = (in.count>>8)&255; + *out_p++ = (in.count>>16)&255; + *out_p++ = (in.count>>24)&255; + + // write bits + outbits = 0; + prev = 0; + for (i=0 ; i>3] |= 1<<(outbits&7); + outbits++; + } + + prev = v; +#if 1 + // check for repeat encodes + for (rept=1 ; i+rept < in.count && rept < MAX_REPT ; rept++) + if (in.data[i+rept] != v) + break; + if (rept > MIN_REPT) + { + c = charbitscount1[prev][255+rept]; + bits = charbits1[prev][255+rept]; + if (!c) + Error ("!bits"); + while (c) + { + c--; + if (bits & (1<>3] |= 1<<(outbits&7); + outbits++; + } + i += rept-1; + } +#endif + } + + out_p += (outbits+7)>>3; + + out.count = out_p - out.data; + + return out; +} + +#endif diff --git a/tools/quake3/q3data/images.c b/tools/quake3/q3data/images.c new file mode 100644 index 00000000..5009ae38 --- /dev/null +++ b/tools/quake3/q3data/images.c @@ -0,0 +1,486 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "q3data.h" + +byte *byteimage, *lbmpalette; +int byteimagewidth, byteimageheight; + + +char mip_prefix[1024]; // directory to dump the textures in + +qboolean colormap_issued; +byte colormap_palette[768]; + +/* +============== +Cmd_Grab + +$grab filename x y width height +============== +*/ +void Cmd_Grab (void) +{ + int xl,yl,w,h,y; + byte *cropped; + char savename[1024]; + char dest[1024]; + + GetToken (qfalse); + + if (token[0] == '/' || token[0] == '\\') + sprintf (savename, "%s%s.pcx", writedir, token+1); + else + sprintf (savename, "%spics/%s.pcx", writedir, token); + + if (g_release) + { + if (token[0] == '/' || token[0] == '\\') + sprintf (dest, "%s.pcx", token+1); + else + sprintf (dest, "pics/%s.pcx", token); + + ReleaseFile (dest); + return; + } + + GetToken (qfalse); + xl = atoi (token); + GetToken (qfalse); + yl = atoi (token); + GetToken (qfalse); + w = atoi (token); + GetToken (qfalse); + h = atoi (token); + + if (xl<0 || yl<0 || w<0 || h<0 || xl+w>byteimagewidth || yl+h>byteimageheight) + Error ("GrabPic: Bad size: %i, %i, %i, %i",xl,yl,w,h); + + // crop it to the proper size + cropped = malloc (w*h); + for (y=0 ; ybyteimagewidth || yl+h>byteimageheight) + Error ("GrabPic: Bad size: %i, %i, %i, %i",xl,yl,w,h); + + // crop it to the proper size + cropped = malloc (w*h); + for (y=0 ; y 255) + r = 255; + if (r < 0) + r = 0; + if (g > 255) + g = 255; + if (g < 0) + g = 0; + if (b > 255) + b = 255; + if (b < 0) + b = 0; +#ifndef TABLECOLORS + bestcolor = BestColor (r, g, b, 0, 254); +#else + bestcolor = palmap[r>>3][g>>3][b>>3]; +#endif + + return bestcolor; +} + + +void BuildPalmap (void) +{ +#ifdef TABLECOLORS + int r, g, b; + int bestcolor; + + if (palmap_built) + return; + palmap_built = qtrue; + + for (r=4 ; r<256 ; r+=8) + { + for (g=4 ; g<256 ; g+=8) + { + for (b=4 ; b<256 ; b+=8) + { + bestcolor = BestColor (r, g, b, 1, 254); + palmap[r>>3][g>>3][b>>3] = bestcolor; + } + } + } +#endif + + if (!colormap_issued) + Error ("You must issue a $colormap command first"); + +} + +/* +============= +AveragePixels +============= +*/ +byte AveragePixels (int count) +{ + int r,g,b; + int i; + int vis; + int pix; + int bestcolor; + byte *pal; + int fullbright; + + vis = 0; + r = g = b = 0; + fullbright = 0; + for (i=0 ; i +#ifdef WIN32 +#include +#endif +#include "md3lib.h" + +#if defined (__linux__) || defined (__APPLE__) +#define filelength Q_filelength +#endif + +/* +** MD3_ComputeTagFromTri +*/ +void MD3_ComputeTagFromTri( md3Tag_t *pTag, const float pTri[3][3] ) +{ + float len[3]; + vec3_t axes[3], sides[3]; + int longestSide, shortestSide, hypotSide; + int origin; + int j; + float d; + + memset( axes, 0, sizeof( axes ) ); + memset( sides, 0, sizeof( sides ) ); + + // + // compute sides + // + for ( j = 0; j < 3; j++ ) + { + sides[j][0] = pTri[(j+1)%3][0] - pTri[j][0]; + sides[j][1] = pTri[(j+1)%3][1] - pTri[j][1]; + sides[j][2] = pTri[(j+1)%3][2] - pTri[j][2]; + + len[j] = ( float ) sqrt( DotProduct( sides[j], sides[j] ) ); + } + +#if 0 + if ( len[0] > len[1] && len[0] > len[2] ) + { + longestSide = 0; shortestSide = 1; origin = 2; + } + else if ( len[1] > len[0] && len[1] > len[2] ) + { + longestSide = 1; shortestSide = 2; origin = 0; + } + else if ( len[2] > len[0] && len[2] > len[1] ) + { + longestSide = 2; shortestSide = 0; origin = 1; + } + else + { + Error( "invalid tag triangle, must be a right triangle with unequal length sides" ); + } +#endif + if ( len[0] > len[1] && len[0] > len[2] ) { + hypotSide = 0; + origin = 2; + } else if ( len[1] > len[0] && len[1] > len[2] ) { + hypotSide = 1; + origin = 0; + } else if ( len[2] > len[0] && len[2] > len[1] ) { + hypotSide = 2; + origin = 1; + } + len[hypotSide] = -1; + + if ( len[0] > len[1] && len[0] > len[2] ) { + longestSide = 0; + } else if ( len[1] > len[0] && len[1] > len[2] ) { + longestSide = 1; + } else if ( len[2] > len[0] && len[2] > len[1] ) { + longestSide = 2; + } + len[longestSide] = -1; + + if ( len[0] > len[1] && len[0] > len[2] ) { + shortestSide = 0; + } else if ( len[1] > len[0] && len[1] > len[2] ) { + shortestSide = 1; + } else if ( len[2] > len[0] && len[2] > len[1] ) { + shortestSide = 2; + } + len[shortestSide] = -1; + + + +// VectorNormalize( sides[shortestSide], axes[0] ); +// VectorNormalize( sides[longestSide], axes[1] ); + VectorNormalize( sides[longestSide], axes[0] ); + VectorNormalize( sides[shortestSide], axes[1] ); + + // project shortest side so that it is exactly 90 degrees to the longer side + d = DotProduct( axes[0], axes[1] ); + VectorMA( axes[0], -d, axes[1], axes[0] ); + VectorNormalize( axes[0], axes[0] ); + + CrossProduct( sides[longestSide], sides[shortestSide], axes[2] ); + VectorNormalize( axes[2], axes[2] ); + + pTag->origin[0] = pTri[origin][0]; + pTag->origin[1] = pTri[origin][1]; + pTag->origin[2] = pTri[origin][2]; + + VectorCopy( axes[0], pTag->axis[0] ); + VectorCopy( axes[1], pTag->axis[1] ); + VectorCopy( axes[2], pTag->axis[2] ); +} + +/* +============== +MD3_Dump +============== +*/ +void MD3_Dump( const char *filename ) +{ + md3Header_t header; + md3Tag_t *pTag; + md3Surface_t *pSurface; + FILE *fp; + void *_buffer; + void *buffer; + long fileSize; + int i; + + if ( ( fp = fopen( filename, "rb" ) ) == 0 ) + { + Error( "Unable to open '%s'\n", filename ); + } + + fileSize = filelength( fileno( fp ) ); + _buffer = malloc( filelength( fileno( fp ) ) ); + fread( _buffer, fileSize, 1, fp ); + fclose( fp ); + + buffer = ( char * ) _buffer; + header = *( md3Header_t * ) _buffer; + + if ( header.ident != MD3_IDENT ) + { + Error( "Incorrect ident for '%s'\n", filename ); + } + + printf( "Contents of '%s'\n", filename ); + printf( " version: %d\n", header.version ); + printf( " name: %s\n", header.name ); + printf( " num frames: %d\n", header.numFrames ); + printf( " num tags: %d\n", header.numTags ); + printf( " num surfaces: %d\n", header.numSurfaces ); + printf( " num skins: %d\n", header.numSkins ); + printf( " file size: %d\n", fileSize ); + + printf( "--- TAGS ---\n" ); + pTag = ( md3Tag_t * ) ( ( ( char * ) buffer ) + header.ofsTags ); + for ( i = 0; i < header.numTags; i++, pTag++ ) + { + printf( " tag %d ('%s')\n", i, pTag->name ); + printf( " origin: %f,%f,%f\n", pTag->origin[0], pTag->origin[1], pTag->origin[2] ); + printf( " vf: %f,%f,%f\n", pTag->axis[0][0], pTag->axis[0][1], pTag->axis[0][2] ); + printf( " vr: %f,%f,%f\n", pTag->axis[1][0], pTag->axis[1][1], pTag->axis[1][2] ); + printf( " vu: %f,%f,%f\n", pTag->axis[2][0], pTag->axis[2][1], pTag->axis[2][2] ); + } + + printf( "--- SURFACES ---\n" ); + pSurface = ( md3Surface_t * ) ( ( ( char * ) buffer ) + header.ofsSurfaces ); + + for ( i = 0; i < header.numSurfaces; i++ ) + { + int j; + + md3Shader_t *pShader = ( md3Shader_t * ) ( ( ( char * ) pSurface ) + pSurface->ofsShaders ); + + printf( "\n surface %d ('%s')\n", i, pSurface->name ); + printf( " num frames: %d\n", pSurface->numFrames ); + printf( " num shaders: %d\n", pSurface->numShaders ); + printf( " num tris: %d\n", pSurface->numTriangles ); + printf( " num verts: %d\n", pSurface->numVerts ); + + if ( pSurface->numShaders > 0 ) + { + printf( " --- SHADERS ---\n" ); + + for ( j = 0; j < pSurface->numShaders; j++, pShader++ ) + { + printf( " shader %d ('%s')\n", j, pShader->name ); + } + } + pSurface = ( md3Surface_t * ) ( ( ( char * ) pSurface ) + pSurface->ofsEnd ); + } + + free( _buffer ); +} + diff --git a/tools/quake3/q3data/md3lib.h b/tools/quake3/q3data/md3lib.h new file mode 100644 index 00000000..fbf48b57 --- /dev/null +++ b/tools/quake3/q3data/md3lib.h @@ -0,0 +1,28 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include "../common/cmdlib.h" +#include "mathlib.h" +#include "../common/qfiles.h" + +void MD3_Dump( const char *filename ); +void MD3_ComputeTagFromTri( md3Tag_t *pTag, const float tri[3][3] ); diff --git a/tools/quake3/q3data/models.c b/tools/quake3/q3data/models.c new file mode 100644 index 00000000..b87dc625 --- /dev/null +++ b/tools/quake3/q3data/models.c @@ -0,0 +1,2155 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include "q3data.h" + +//================================================================= + +static void OrderSurfaces( void ); +static void LoadBase( const char *filename ); +static int LoadModelFile( const char *filename, polyset_t **ppsets, int *pnumpolysets ); + +#define MAX_SURFACE_TRIS (SHADER_MAX_INDEXES / 3) +#define MAX_SURFACE_VERTS SHADER_MAX_VERTEXES + +#define MD3_TYPE_UNKNOWN 0 +#define MD3_TYPE_BASE3DS 1 +#define MD3_TYPE_SPRITE 2 +#define MD3_TYPE_ASE 3 + +#define MAX_ANIM_FRAMES 512 +#define MAX_ANIM_SURFACES 32 + +typedef struct +{ + polyset_t *frames; + int numFrames; +} SurfaceAnimation_t; + +typedef struct +{ + polyset_t *surfaces[MAX_ANIM_SURFACES]; + int numSurfaces; +} ObjectAnimationFrame_t; + +typedef struct { + vec3_t xyz; + vec3_t normal; + vec3_t color; + float st[2]; + int index; +} baseVertex_t; + +typedef struct { + baseVertex_t v[3]; +} baseTriangle_t; + +//================================================================ + +typedef struct +{ + md3Surface_t header; + md3Shader_t shaders[MD3_MAX_SHADERS]; + // all verts (xyz_normal) + float *verts[MD3_MAX_FRAMES]; + + baseTriangle_t baseTriangles[MD3_MAX_TRIANGLES]; + + // the triangles will be sorted so that they form long generalized tristrips + int orderedTriangles[MD3_MAX_TRIANGLES][3]; + int lodTriangles[MD3_MAX_TRIANGLES][3]; + baseVertex_t baseVertexes[MD3_MAX_VERTS]; + +} md3SurfaceData_t; + +typedef struct +{ + int skinwidth, skinheight; + + md3SurfaceData_t surfData[MD3_MAX_SURFACES]; + + md3Tag_t tags[MD3_MAX_FRAMES][MD3_MAX_TAGS]; + md3Frame_t frames[MD3_MAX_FRAMES]; + + md3Header_t model; + float scale_up; // set by $scale + vec3_t adjust; // set by $origin + vec3_t aseAdjust; + int fixedwidth, fixedheight; // set by $skinsize + + int maxSurfaceTris; + + int lowerSkipFrameStart, lowerSkipFrameEnd; + int maxUpperFrames; + int maxHeadFrames; + int currentLod; + float lodBias; + + int type; // MD3_TYPE_BASE, MD3_TYPE_OLDBASE, MD3_TYPE_ASE, or MD3_TYPE_SPRITE + +} q3data; + +q3data g_data; + +// the command list holds counts, the count * 3 xyz, st, normal indexes +// that are valid for every frame +char g_cddir[1024]; +char g_modelname[1024]; + +//============================================================== + +/* +=============== +ClearModel +=============== +*/ +void ClearModel (void) +{ + int i; + + g_data.type = MD3_TYPE_UNKNOWN; + + for ( i = 0; i < MD3_MAX_SURFACES; i++ ) + { + memset( &g_data.surfData[i].header, 0, sizeof( g_data.surfData[i].header ) ); + memset( &g_data.surfData[i].shaders, 0, sizeof( g_data.surfData[i].shaders ) ); + memset( &g_data.surfData[i].verts, 0, sizeof( g_data.surfData[i].verts ) ); + } + + memset( g_data.tags, 0, sizeof( g_data.tags ) ); + + for ( i = 0; i < g_data.model.numSurfaces; i++ ) + { + int j; + + for ( j = 0; j < g_data.surfData[i].header.numShaders; j++ ) + { + memset( &g_data.surfData[i].shaders[j], 0, sizeof( g_data.surfData[i].shaders[j] ) ); + } + } + memset (&g_data.model, 0, sizeof(g_data.model)); + memset (g_cddir, 0, sizeof(g_cddir)); + + g_modelname[0] = 0; + g_data.scale_up = 1.0; + memset( &g_data.model, 0, sizeof( g_data.model ) ); + VectorCopy (vec3_origin, g_data.adjust); + g_data.fixedwidth = g_data.fixedheight = 0; + g_skipmodel = qfalse; +} + +/* +** void WriteModelSurface( FILE *modelouthandle, md3SurfaceData_t *pSurfData ) +** +** This routine assumes that the file position has been adjusted +** properly prior to entry to point at the beginning of the surface. +** +** Since surface header information is completely relative, we can't +** just randomly seek to an arbitrary surface location right now. Is +** this something we should add? +*/ +void WriteModelSurface( FILE *modelouthandle, md3SurfaceData_t *pSurfData ) +{ + md3Surface_t *pSurf = &pSurfData->header; + md3Shader_t *pShader = pSurfData->shaders; + baseVertex_t *pBaseVertex = pSurfData->baseVertexes; + float **verts = pSurfData->verts; + + short xyznormals[MD3_MAX_VERTS][4]; + + float base_st[MD3_MAX_VERTS][2]; + md3Surface_t surftemp; + + int f, i, j, k; + + if ( strstr( pSurf->name, "tag_" ) == pSurf->name ) + return; + + // + // write out the header + // + surftemp = *pSurf; + surftemp.ident = LittleLong( MD3_IDENT ); + surftemp.flags = LittleLong( pSurf->flags ); + surftemp.numFrames = LittleLong( pSurf->numFrames ); + surftemp.numShaders = LittleLong( pSurf->numShaders ); + + surftemp.ofsShaders = LittleLong( pSurf->ofsShaders ); + + surftemp.ofsTriangles = LittleLong( pSurf->ofsTriangles ); + surftemp.numTriangles = LittleLong( pSurf->numTriangles ); + + surftemp.ofsSt = LittleLong( pSurf->ofsSt ); + surftemp.ofsXyzNormals = LittleLong( pSurf->ofsXyzNormals ); + surftemp.ofsEnd = LittleLong( pSurf->ofsEnd ); + + SafeWrite( modelouthandle, &surftemp, sizeof( surftemp ) ); + + if ( g_verbose ) + { + printf( "surface '%s'\n", pSurf->name ); + printf( "...num shaders: %d\n", pSurf->numShaders ); + } + + // + // write out shaders + // + for ( i = 0; i < pSurf->numShaders; i++ ) + { + md3Shader_t shadertemp; + + if ( g_verbose ) + printf( "......'%s'\n", pShader[i].name ); + + shadertemp = pShader[i]; + shadertemp.shaderIndex = LittleLong( shadertemp.shaderIndex ); + SafeWrite( modelouthandle, &shadertemp, sizeof( shadertemp ) ); + } + + // + // write out the triangles + // + for ( i = 0 ; i < pSurf->numTriangles ; i++ ) + { + for (j = 0 ; j < 3 ; j++) + { + int ivalue = LittleLong( pSurfData->orderedTriangles[i][j] ); + pSurfData->orderedTriangles[i][j] = ivalue; + } + } + + SafeWrite( modelouthandle, pSurfData->orderedTriangles, pSurf->numTriangles * sizeof( g_data.surfData[0].orderedTriangles[0] ) ); + + if ( g_verbose ) + { + printf( "\n...num verts: %d\n", pSurf->numVerts ); + printf( "...TEX COORDINATES\n" ); + } + + // + // write out the texture coordinates + // + for ( i = 0; i < pSurf->numVerts ; i++) { + base_st[i][0] = LittleFloat( pBaseVertex[i].st[0] ); + base_st[i][1] = LittleFloat( pBaseVertex[i].st[1] ); + if ( g_verbose ) + printf( "......%d: %f,%f\n", i, base_st[i][0], base_st[i][1] ); + } + SafeWrite( modelouthandle, base_st, pSurf->numVerts * sizeof(base_st[0])); + + // + // write the xyz_normal + // + if ( g_verbose ) + printf( "...XYZNORMALS\n" ); + for ( f = 0; f < g_data.model.numFrames; f++ ) + { + for (j=0 ; j< pSurf->numVerts; j++) + { + short value; + + for (k=0 ; k < 3 ; k++) + { + value = ( short ) ( verts[f][j*6+k] / MD3_XYZ_SCALE ); + xyznormals[j][k] = LittleShort( value ); + } + NormalToLatLong( &verts[f][j*6+3], (byte *)&xyznormals[j][3] ); + } + SafeWrite( modelouthandle, xyznormals, pSurf->numVerts * sizeof( short ) * 4 ); + } +} + +/* +** void WriteModelFile( FILE *modelouthandle ) +** +** CHUNK SIZE +** header sizeof( md3Header_t ) +** frames sizeof( md3Frame_t ) * numFrames +** tags sizeof( md3Tag_t ) * numFrames * numTags +** surfaces surfaceSum +*/ +void WriteModelFile( FILE *modelouthandle ) +{ + int f; + int i, j; + md3Header_t modeltemp; + long surfaceSum = 0; + int numRealSurfaces = 0; + int numFrames = g_data.model.numFrames; + + // compute offsets for all surfaces, sum their total size + for ( i = 0; i < g_data.model.numSurfaces; i++ ) + { + if ( strstr( g_data.surfData[i].header.name, "tag_" ) != g_data.surfData[i].header.name ) + { + md3Surface_t *psurf = &g_data.surfData[i].header; + + if ( psurf->numTriangles == 0 || psurf->numVerts == 0 ) + continue; + + // + // the triangle and vertex split threshold is controlled by a parameter + // to $base, a la $base blah.3ds 1900, where "1900" determines the number + // of triangles to split on + // + else if ( psurf->numVerts > MAX_SURFACE_VERTS ) + { + Error( "too many vertices\n" ); + } + + psurf->numFrames = numFrames; + + psurf->ofsShaders = sizeof( md3Surface_t ); + + if ( psurf->numTriangles > MAX_SURFACE_TRIS ) + { + Error( "too many faces\n" ); + } + + psurf->ofsTriangles = psurf->ofsShaders + psurf->numShaders * sizeof( md3Shader_t ); + + psurf->ofsSt = psurf->ofsTriangles + psurf->numTriangles * sizeof( md3Triangle_t ); + psurf->ofsXyzNormals = psurf->ofsSt + psurf->numVerts * sizeof( md3St_t ); + psurf->ofsEnd = psurf->ofsXyzNormals + psurf->numFrames * psurf->numVerts * ( sizeof( short ) * 4 ); + + surfaceSum += psurf->ofsEnd; + + numRealSurfaces++; + } + } + + g_data.model.ident = MD3_IDENT; + g_data.model.version = MD3_VERSION; + + g_data.model.ofsFrames = sizeof(md3Header_t); + g_data.model.ofsTags = g_data.model.ofsFrames + numFrames*sizeof(md3Frame_t); + g_data.model.ofsSurfaces = g_data.model.ofsTags + numFrames*g_data.model.numTags*sizeof(md3Tag_t); + g_data.model.ofsEnd = g_data.model.ofsSurfaces + surfaceSum; + + // + // write out the model header + // + modeltemp = g_data.model; + modeltemp.ident = LittleLong( modeltemp.ident ); + modeltemp.version = LittleLong( modeltemp.version ); + modeltemp.numFrames = LittleLong( modeltemp.numFrames ); + modeltemp.numTags = LittleLong( modeltemp.numTags ); + modeltemp.numSurfaces = LittleLong( numRealSurfaces ); + modeltemp.ofsFrames = LittleLong( modeltemp.ofsFrames ); + modeltemp.ofsTags = LittleLong( modeltemp.ofsTags ); + modeltemp.ofsSurfaces = LittleLong( modeltemp.ofsSurfaces ); + modeltemp.ofsEnd = LittleLong( modeltemp.ofsEnd ); + + SafeWrite (modelouthandle, &modeltemp, sizeof(modeltemp)); + + // + // write out the frames + // + for (i=0 ; i < numFrames ; i++) + { + vec3_t tmpVec; + float maxRadius = 0; + + // + // compute localOrigin and radius + // + g_data.frames[i].localOrigin[0] = + g_data.frames[i].localOrigin[1] = + g_data.frames[i].localOrigin[2] = 0; + + for ( j = 0; j < 8; j++ ) + { + tmpVec[0] = g_data.frames[i].bounds[(j&1)!=0][0]; + tmpVec[1] = g_data.frames[i].bounds[(j&2)!=0][1]; + tmpVec[2] = g_data.frames[i].bounds[(j&4)!=0][2]; + + if ( VectorLength( tmpVec ) > maxRadius ) + maxRadius = VectorLength( tmpVec ); + } + + g_data.frames[i].radius = LittleFloat( maxRadius ); + + // swap + for (j=0 ; j<3 ; j++) { + g_data.frames[i].bounds[0][j] = LittleFloat( g_data.frames[i].bounds[0][j] ); + g_data.frames[i].bounds[1][j] = LittleFloat( g_data.frames[i].bounds[1][j] ); + g_data.frames[i].localOrigin[j] = LittleFloat( g_data.frames[i].localOrigin[j] ); + } + } + fseek (modelouthandle, g_data.model.ofsFrames, SEEK_SET); + SafeWrite( modelouthandle, g_data.frames, numFrames * sizeof(g_data.frames[0]) ); + + // + // write out the tags + // + fseek( modelouthandle, g_data.model.ofsTags, SEEK_SET ); + for (f=0 ; fshaders[j].name ); + } + } + return; + } + + // + // write the model output file + // + printf ("saving to %s\n", name); + CreatePath (name); + modelouthandle = SafeOpenWrite (name); + + WriteModelFile (modelouthandle); + + printf ("%4d surfaces\n", g_data.model.numSurfaces); + printf ("%4d frames\n", g_data.model.numFrames); + printf ("%4d tags\n", g_data.model.numTags); + printf ("file size: %d\n", (int)ftell (modelouthandle) ); + printf ("---------------------\n"); + + fclose (modelouthandle); +} + +/* +** OrderSurfaces +** +** Reorders triangles in all the surfaces. +*/ +static void OrderSurfaces( void ) +{ + int s; + extern qboolean g_stripify; + + // go through each surface and find best strip/fans possible + for ( s = 0; s < g_data.model.numSurfaces; s++ ) + { + int mesh[MD3_MAX_TRIANGLES][3]; + int i; + + printf( "stripifying surface %d/%d with %d tris\n", s, g_data.model.numSurfaces, g_data.surfData[s].header.numTriangles ); + + for ( i = 0; i < g_data.surfData[s].header.numTriangles; i++ ) + { + mesh[i][0] = g_data.surfData[s].lodTriangles[i][0]; + mesh[i][1] = g_data.surfData[s].lodTriangles[i][1]; + mesh[i][2] = g_data.surfData[s].lodTriangles[i][2]; + } + + if ( g_stripify ) + { + OrderMesh( mesh, // input + g_data.surfData[s].orderedTriangles, // output + g_data.surfData[s].header.numTriangles ); + } + else + { + memcpy( g_data.surfData[s].orderedTriangles, mesh, sizeof( int ) * 3 * g_data.surfData[s].header.numTriangles ); + } + } +} + + +/* +=============================================================== + +BASE FRAME SETUP + +=============================================================== +*/ +/* +============ +CopyTrianglesToBaseTriangles + +============ +*/ +static void CopyTrianglesToBaseTriangles(triangle_t *ptri, int numtri, baseTriangle_t *bTri ) +{ + int i; +// int width, height, iwidth, iheight, swidth; +// float s_scale, t_scale; +// float scale; +// vec3_t mins, maxs; + float *pbasevert; + +/* + // + // find bounds of all the verts on the base frame + // + ClearBounds (mins, maxs); + + for (i=0 ; i= 150) + scale = 150.0 / width; + if (height*scale >= 190) + scale = 190.0 / height; + + s_scale = t_scale = scale; + + iwidth = ceil(width*s_scale); + iheight = ceil(height*t_scale); + + iwidth += 4; + iheight += 4; + } + else + { // new style + iwidth = g_data.fixedwidth / 2; + iheight = g_data.fixedheight; + + s_scale = (float)(iwidth-4) / width; + t_scale = (float)(iheight-4) / height; + } + + // make the width a multiple of 4; some hardware requires this, and it ensures + // dword alignment for each scan + swidth = iwidth*2; + g_data.skinwidth = (swidth + 3) & ~3; + g_data.skinheight = iheight; +*/ + + for (i=0; iverts[j]; + + VectorCopy( ptri->verts[j], bTri->v[j].xyz); + VectorCopy( ptri->normals[j], bTri->v[j].normal ); + + bTri->v[j].st[0] = ptri->texcoords[j][0]; + bTri->v[j].st[1] = ptri->texcoords[j][1]; + } + } +} + +static void BuildBaseFrame( const char *filename, ObjectAnimationFrame_t *pOAF ) +{ + baseTriangle_t *bTri; + baseVertex_t *bVert; + int i, j; + + // calculate the base triangles + for ( i = 0; i < g_data.model.numSurfaces; i++ ) + { + CopyTrianglesToBaseTriangles( pOAF->surfaces[i]->triangles, + pOAF->surfaces[i]->numtriangles, + g_data.surfData[i].baseTriangles ); + + strcpy( g_data.surfData[i].header.name, pOAF->surfaces[i]->name ); + + g_data.surfData[i].header.numTriangles = pOAF->surfaces[i]->numtriangles; + g_data.surfData[i].header.numVerts = 0; + +/* + if ( strstr( filename, gamedir + 1 ) ) + { + strcpy( shaderName, strstr( filename, gamedir + 1 ) + strlen( gamedir ) - 1 ); + } + else + { + strcpy( shaderName, filename ); + } + + if ( strrchr( shaderName, '/' ) ) + *( strrchr( shaderName, '/' ) + 1 ) = 0; + + + strcpy( shaderName, pOAF->surfaces[i]->materialname ); +*/ + strcpy( g_data.surfData[i].shaders[g_data.surfData[i].header.numShaders].name, pOAF->surfaces[i]->materialname ); + + g_data.surfData[i].header.numShaders++; + } + + // + // compute unique vertices for each polyset + // + for ( i = 0; i < g_data.model.numSurfaces; i++ ) + { + int t; + + for ( t = 0; t < pOAF->surfaces[i]->numtriangles; t++ ) + { + bTri = &g_data.surfData[i].baseTriangles[t]; + + for (j=0 ; j<3 ; j++) + { + int k; + + bVert = &bTri->v[j]; + + // get the xyz index + for ( k = 0; k < g_data.surfData[i].header.numVerts; k++ ) + { + if ( ( g_data.surfData[i].baseVertexes[k].st[0] == bVert->st[0] ) && + ( g_data.surfData[i].baseVertexes[k].st[1] == bVert->st[1] ) && + ( VectorCompare (bVert->xyz, g_data.surfData[i].baseVertexes[k].xyz) ) && + ( VectorCompare (bVert->normal, g_data.surfData[i].baseVertexes[k].normal) ) ) + { + break; // this vertex is already in the base vertex list + } + } + + if (k == g_data.surfData[i].header.numVerts) { // new index + g_data.surfData[i].baseVertexes[g_data.surfData[i].header.numVerts] = *bVert; + g_data.surfData[i].header.numVerts++; + } + + bVert->index = k; + + g_data.surfData[i].lodTriangles[t][j] = k; + } + } + } + + // + // find tags + // + for ( i = 0; i < g_data.model.numSurfaces; i++ ) + { + if ( strstr( pOAF->surfaces[i]->name, "tag_" ) == pOAF->surfaces[i]->name ) + { + if ( pOAF->surfaces[i]->numtriangles != 1 ) + { + Error( "tag polysets must consist of only one triangle" ); + } + if ( strstr( filename, "_flash.md3" ) && !strcmp( pOAF->surfaces[i]->name, "tag_parent" ) ) + continue; + printf( "found tag '%s'\n", pOAF->surfaces[i]->name ); + g_data.model.numTags++; + } + } + +} + +static int LoadModelFile( const char *filename, polyset_t **psets, int *numpolysets ) +{ + int time1; + char file1[1024]; + const char *frameFile; + + printf ("---------------------\n"); + if ( filename[1] != ':' ) + { + frameFile = filename; + sprintf( file1, "%s/%s", g_cddir, frameFile ); + } + else + { + strcpy( file1, filename ); + } + + time1 = FileTime (file1); + if (time1 == -1) + Error ("%s doesn't exist", file1); + + // + // load the base triangles + // + *psets = Polyset_LoadSets( file1, numpolysets, g_data.maxSurfaceTris ); + + // + // snap polysets + // + Polyset_SnapSets( *psets, *numpolysets ); + + if ( strstr( file1, ".3ds" ) || strstr( file1, ".3DS" ) ) + return MD3_TYPE_BASE3DS; + + Error( "Unknown model file type" ); + + return MD3_TYPE_UNKNOWN; +} + +/* +================= +Cmd_Base +================= +*/ +void Cmd_Base( void ) +{ + char filename[1024]; + + GetToken( qfalse ); + sprintf( filename, "%s/%s", g_cddir, token ); + LoadBase( filename ); +} + +static void LoadBase( const char *filename ) +{ + int numpolysets; + polyset_t *psets; + int i; + ObjectAnimationFrame_t oaf; + + // determine polyset splitting threshold + if ( TokenAvailable() ) + { + GetToken( qfalse ); + g_data.maxSurfaceTris = atoi( token ); + } + else + { + g_data.maxSurfaceTris = MAX_SURFACE_TRIS - 1; + } + + g_data.type = LoadModelFile( filename, &psets, &numpolysets ); + + Polyset_ComputeNormals( psets, numpolysets ); + + g_data.model.numSurfaces = numpolysets; + + memset( &oaf, 0, sizeof( oaf ) ); + + for ( i = 0; i < numpolysets; i++ ) + { + oaf.surfaces[i] = &psets[i]; + oaf.numSurfaces = numpolysets; + } + + BuildBaseFrame( filename, &oaf ); + + free( psets[0].triangles ); + free( psets ); +} + +/* +================= +Cmd_SpriteBase + +$spritebase xorg yorg width height + +Generate a single square for the model +================= +*/ +void Cmd_SpriteBase (void) +{ + float xl, yl, width, height; + + g_data.type = MD3_TYPE_SPRITE; + + GetToken (qfalse); + xl = atof(token); + GetToken (qfalse); + yl = atof(token); + GetToken (qfalse); + width = atof(token); + GetToken (qfalse); + height = atof(token); + +// if (g_skipmodel || g_release || g_archive) +// return; + + printf ("---------------------\n"); + + g_data.surfData[0].verts[0] = ( float * ) calloc( 1, sizeof( float ) * 6 * 4 ); + + g_data.surfData[0].header.numVerts = 4; + + g_data.surfData[0].verts[0][0+0] = 0; + g_data.surfData[0].verts[0][0+1] = -xl; + g_data.surfData[0].verts[0][0+2] = yl + height; + + g_data.surfData[0].verts[0][0+3] = -1; + g_data.surfData[0].verts[0][0+4] = 0; + g_data.surfData[0].verts[0][0+5] = 0; + g_data.surfData[0].baseVertexes[0].st[0] = 0; + g_data.surfData[0].baseVertexes[0].st[1] = 0; + + + g_data.surfData[0].verts[0][6+0] = 0; + g_data.surfData[0].verts[0][6+1] = -xl - width; + g_data.surfData[0].verts[0][6+2] = yl + height; + + g_data.surfData[0].verts[0][6+3] = -1; + g_data.surfData[0].verts[0][6+4] = 0; + g_data.surfData[0].verts[0][6+5] = 0; + g_data.surfData[0].baseVertexes[1].st[0] = 1; + g_data.surfData[0].baseVertexes[1].st[1] = 0; + + + g_data.surfData[0].verts[0][12+0] = 0; + g_data.surfData[0].verts[0][12+1] = -xl - width; + g_data.surfData[0].verts[0][12+2] = yl; + + g_data.surfData[0].verts[0][12+3] = -1; + g_data.surfData[0].verts[0][12+4] = 0; + g_data.surfData[0].verts[0][12+5] = 0; + g_data.surfData[0].baseVertexes[2].st[0] = 1; + g_data.surfData[0].baseVertexes[2].st[1] = 1; + + + g_data.surfData[0].verts[0][18+0] = 0; + g_data.surfData[0].verts[0][18+1] = -xl; + g_data.surfData[0].verts[0][18+2] = yl; + + g_data.surfData[0].verts[0][18+3] = -1; + g_data.surfData[0].verts[0][18+4] = 0; + g_data.surfData[0].verts[0][18+5] = 0; + g_data.surfData[0].baseVertexes[3].st[0] = 0; + g_data.surfData[0].baseVertexes[3].st[1] = 1; + + g_data.surfData[0].lodTriangles[0][0] = 0; + g_data.surfData[0].lodTriangles[0][1] = 1; + g_data.surfData[0].lodTriangles[0][2] = 2; + + g_data.surfData[0].lodTriangles[1][0] = 2; + g_data.surfData[0].lodTriangles[1][1] = 3; + g_data.surfData[0].lodTriangles[1][2] = 0; + + g_data.model.numSurfaces = 1; + + g_data.surfData[0].header.numTriangles = 2; + g_data.surfData[0].header.numVerts = 4; + + g_data.model.numFrames = 1; +} + +/* +=========================================================================== + + FRAME GRABBING + +=========================================================================== +*/ + +/* +=============== +GrabFrame +=============== +*/ +void GrabFrame (const char *frame) +{ + int i, j, k; + char file1[1024]; + md3Frame_t *fr; + md3Tag_t tagParent; + float *frameXyz; + float *frameNormals; + const char *framefile; + polyset_t *psets; + qboolean parentTagExists = qfalse; + int numpolysets; + int numtags = 0; + int tagcount; + + // the frame 'run1' will be looked for as either + // run.1 or run1.tri, so the new alias sequence save + // feature an be used + if ( frame[1] != ':' ) + { +// framefile = FindFrameFile (frame); + framefile = frame; + sprintf (file1, "%s/%s",g_cddir, framefile); + } + else + { + strcpy( file1, frame ); + } + printf ("grabbing %s\n", file1); + + if (g_data.model.numFrames >= MD3_MAX_FRAMES) + Error ("model.numFrames >= MD3_MAX_FRAMES"); + fr = &g_data.frames[g_data.model.numFrames]; + + strcpy (fr->name, frame); + + psets = Polyset_LoadSets( file1, &numpolysets, g_data.maxSurfaceTris ); + + // + // snap polysets + // + Polyset_SnapSets( psets, numpolysets ); + + // + // compute vertex normals + // + Polyset_ComputeNormals( psets, numpolysets ); + + // + // flip everything to compensate for the alias coordinate system + // and perform global scale and adjust + // + for ( i = 0; i < g_data.model.numSurfaces; i++ ) + { + triangle_t *ptri = psets[i].triangles; + int t; + + for ( t = 0; t < psets[i].numtriangles; t++ ) + { + + for ( j = 0; j < 3; j++ ) + { + + // scale and adjust + for ( k = 0 ; k < 3 ; k++ ) { + ptri[t].verts[j][k] = ptri[t].verts[j][k] * g_data.scale_up + + g_data.adjust[k]; + + if ( ptri[t].verts[j][k] > 1023 || + ptri[t].verts[j][k] < -1023 ) + { + Error( "Model extents too large" ); + } + } + } + } + } + + // + // find and count tags, locate parent tag + // + for ( i = 0; i < numpolysets; i++ ) + { + if ( strstr( psets[i].name, "tag_" ) == psets[i].name ) + { + if ( strstr( psets[i].name, "tag_parent" ) == psets[i].name ) + { + if ( strstr( psets[i].name, "tag_parent" ) ) + { + float tri[3][3]; + + if ( parentTagExists ) + Error( "Multiple parent tags not allowed" ); + + memcpy( tri[0], psets[i].triangles[0].verts[0], sizeof( float ) * 3 ); + memcpy( tri[1], psets[i].triangles[0].verts[1], sizeof( float ) * 3 ); + memcpy( tri[2], psets[i].triangles[0].verts[2], sizeof( float ) * 3 ); + + MD3_ComputeTagFromTri( &tagParent, tri ); + strcpy( tagParent.name, psets[i].name ); + g_data.tags[g_data.model.numFrames][numtags] = tagParent; + parentTagExists = qtrue; + + } + } + numtags++; + } + + if ( strcmp( psets[i].name, g_data.surfData[i].header.name ) ) + { + Error( "Mismatched surfaces from base('%s') to frame('%s') in model '%s'\n", g_data.surfData[i].header.name, psets[i].name, g_modelname ); + } + } + + if ( numtags != g_data.model.numTags ) + { + Error( "mismatched number of tags in frame(%d) vs. base(%d)", numtags, g_data.model.numTags ); + } + + if ( numpolysets != g_data.model.numSurfaces ) + { + Error( "mismatched number of surfaces in frame(%d) vs. base(%d)", numpolysets-numtags, g_data.model.numSurfaces ); + } + + // + // prepare to accumulate bounds and normals + // + ClearBounds( fr->bounds[0], fr->bounds[1] ); + + // + // store the frame's vertices in the same order as the base. This assumes the + // triangles and vertices in this frame are in exactly the same order as in the + // base + // + for ( i = 0, tagcount = 0; i < numpolysets; i++ ) + { + int t; + triangle_t *pTris = psets[i].triangles; + + strcpy( g_data.surfData[i].header.name, psets[i].name ); + + // + // parent tag adjust + // + if ( parentTagExists ) { + for ( t = 0; t < psets[i].numtriangles; t++ ) + { + for ( j = 0; j < 3 ; j++ ) + { + vec3_t tmp; + + VectorSubtract( pTris[t].verts[j], tagParent.origin, tmp ); + + pTris[t].verts[j][0] = DotProduct( tmp, tagParent.axis[0] ); + pTris[t].verts[j][1] = DotProduct( tmp, tagParent.axis[1] ); + pTris[t].verts[j][2] = DotProduct( tmp, tagParent.axis[2] ); + + VectorCopy( pTris[t].normals[j], tmp ); + pTris[t].normals[j][0] = DotProduct( tmp, tagParent.axis[0] ); + pTris[t].normals[j][1] = DotProduct( tmp, tagParent.axis[1] ); + pTris[t].normals[j][2] = DotProduct( tmp, tagParent.axis[2] ); + } + } + } + + // + // compute tag data + // + if ( strstr( psets[i].name, "tag_" ) == psets[i].name ) + { + md3Tag_t *pTag = &g_data.tags[g_data.model.numFrames][tagcount]; + float tri[3][3]; + + strcpy( pTag->name, psets[i].name ); + + memcpy( tri[0], pTris[0].verts[0], sizeof( float ) * 3 ); + memcpy( tri[1], pTris[0].verts[1], sizeof( float ) * 3 ); + memcpy( tri[2], pTris[0].verts[2], sizeof( float ) * 3 ); + + MD3_ComputeTagFromTri( pTag, tri ); + tagcount++; + } + else + { + if ( g_data.surfData[i].verts[g_data.model.numFrames] ) + free( g_data.surfData[i].verts[g_data.model.numFrames] ); + frameXyz = g_data.surfData[i].verts[g_data.model.numFrames] = calloc( 1, sizeof( float ) * 6 * g_data.surfData[i].header.numVerts ); + frameNormals = frameXyz + 3; + + for ( t = 0; t < psets[i].numtriangles; t++ ) + { + for ( j = 0; j < 3 ; j++ ) + { + int index; + + index = g_data.surfData[i].baseTriangles[t].v[j].index; + frameXyz[index*6+0] = pTris[t].verts[j][0]; + frameXyz[index*6+1] = pTris[t].verts[j][1]; + frameXyz[index*6+2] = pTris[t].verts[j][2]; + frameNormals[index*6+0] = pTris[t].normals[j][0]; + frameNormals[index*6+1] = pTris[t].normals[j][1]; + frameNormals[index*6+2] = pTris[t].normals[j][2]; + AddPointToBounds (&frameXyz[index*6], fr->bounds[0], fr->bounds[1] ); + } + } + } + } + + g_data.model.numFrames++; + + // only free the first triangle array, all of the psets in this array share the + // same triangle pool!!! +// free( psets[0].triangles ); +// free( psets ); +} + +//=========================================================================== + + + +/* +=============== +Cmd_Frame +=============== +*/ +void Cmd_Frame (void) +{ + while (TokenAvailable()) + { + GetToken (qfalse); + if (g_skipmodel) + continue; + if (g_release || g_archive) + { + g_data.model.numFrames = 1; // don't skip the writeout + continue; + } + + GrabFrame( token ); + } +} + + +/* +=============== +Cmd_Skin + +=============== +*/ +void SkinFrom3DS( const char *filename ) +{ + polyset_t *psets; + char name[1024]; + int numPolysets; + int i; + + _3DS_LoadPolysets( filename, &psets, &numPolysets, g_verbose ); + + for ( i = 0; i < numPolysets; i++ ) + { +/* + if ( strstr( filename, gamedir + 1 ) ) + { + strcpy( name, strstr( filename, gamedir + 1 ) + strlen( gamedir ) - 1 ); + } + else + { + strcpy( name, filename ); + } + + if ( strrchr( name, '/' ) ) + *( strrchr( name, '/' ) + 1 ) = 0; +*/ + strcpy( name, psets[i].materialname ); + strcpy( g_data.surfData[i].shaders[g_data.surfData[i].header.numShaders].name, name ); + + g_data.surfData[i].header.numShaders++; + } + + free( psets[0].triangles ); + free( psets ); +} + +void Cmd_Skin (void) +{ + char skinfile[1024]; + + if ( g_data.type == MD3_TYPE_BASE3DS ) + { + GetToken( qfalse ); + + sprintf( skinfile, "%s/%s", g_cddir, token ); + + if ( strstr( token, ".3ds" ) || strstr( token, ".3DS" ) ) + { + SkinFrom3DS( skinfile ); + } + else + { + Error( "Unknown file format for $skin '%s'\n", skinfile ); + } + } + else + { + Error( "invalid model type while processing $skin" ); + } + + g_data.model.numSkins++; +} + +/* +================= +Cmd_SpriteShader +================= + +This routine is also called for $oldskin + +*/ +void Cmd_SpriteShader() +{ + GetToken( qfalse ); + strcpy( g_data.surfData[0].shaders[g_data.surfData[0].header.numShaders].name, token ); + g_data.surfData[0].header.numShaders++; + g_data.model.numSkins++; +} + +/* +================= +Cmd_Origin +================= +*/ +void Cmd_Origin (void) +{ + // rotate points into frame of reference so model points down the + // positive x axis + // FIXME: use alias native coordinate system + GetToken (qfalse); + g_data.adjust[1] = -atof (token); + + GetToken (qfalse); + g_data.adjust[0] = atof (token); + + GetToken (qfalse); + g_data.adjust[2] = -atof (token); +} + + +/* +================= +Cmd_ScaleUp +================= +*/ +void Cmd_ScaleUp (void) +{ + GetToken (qfalse); + g_data.scale_up = atof (token); + if (g_skipmodel || g_release || g_archive) + return; + + printf ("Scale up: %f\n", g_data.scale_up); +} + + +/* +================= +Cmd_Skinsize + +Set a skin size other than the default +QUAKE3: not needed +================= +*/ +void Cmd_Skinsize (void) +{ + GetToken (qfalse); + g_data.fixedwidth = atoi(token); + GetToken (qfalse); + g_data.fixedheight = atoi(token); +} + +/* +================= +Cmd_Modelname + +Begin creating a model of the given name +================= +*/ +void Cmd_Modelname (void) +{ + FinishModel ( TYPE_UNKNOWN ); + ClearModel (); + + GetToken (qfalse); + strcpy (g_modelname, token); + StripExtension (g_modelname); + strcat (g_modelname, ".md3"); + strcpy (g_data.model.name, g_modelname); +} + +/* +=============== +fCmd_Cd +=============== +*/ +void Cmd_Cd (void) +{ + if ( g_cddir[0]) { + Error ("$cd command without a $modelname"); + } + + GetToken (qfalse); + + sprintf ( g_cddir, "%s%s", gamedir, token); + + // if -only was specified and this cd doesn't match, + // skip the model (you only need to match leading chars, + // so you could regrab all monsters with -only models/monsters) + if (!g_only[0]) + return; + if (strncmp(token, g_only, strlen(g_only))) + { + g_skipmodel = qtrue; + printf ("skipping %s\n", token); + } +} + +void Convert3DStoMD3( const char *file ) +{ + LoadBase( file ); + GrabFrame( file ); + SkinFrom3DS( file ); + + strcpy( g_data.model.name, g_modelname ); + + FinishModel( TYPE_UNKNOWN ); + ClearModel(); +} + +/* +** Cmd_3DSConvert +*/ +void Cmd_3DSConvert() +{ + char file[1024]; + + FinishModel( TYPE_UNKNOWN ); + ClearModel(); + + GetToken( qfalse ); + + sprintf( file, "%s%s", gamedir, token ); + strcpy( g_modelname, token ); + if ( strrchr( g_modelname, '.' ) ) + *strrchr( g_modelname, '.' ) = 0; + strcat( g_modelname, ".md3" ); + + if ( FileTime( file ) == -1 ) + Error( "%s doesn't exist", file ); + + if ( TokenAvailable() ) + { + GetToken( qfalse ); + g_data.scale_up = atof( token ); + } + + Convert3DStoMD3( file ); +} + +static void ConvertASE( const char *filename, int type, qboolean grabAnims ); + +/* +** Cmd_ASEConvert +*/ +void Cmd_ASEConvert( qboolean grabAnims ) +{ + char filename[1024]; + int type = TYPE_ITEM; + + FinishModel( TYPE_UNKNOWN ); + ClearModel(); + + GetToken( qfalse ); + sprintf( filename, "%s%s", gamedir, token ); + + strcpy (g_modelname, token); + StripExtension (g_modelname); + strcat (g_modelname, ".md3"); + strcpy (g_data.model.name, g_modelname); + + if ( !strstr( filename, ".ase" ) && !strstr( filename, ".ASE" ) ) + strcat( filename, ".ASE" ); + + g_data.maxSurfaceTris = MAX_SURFACE_TRIS - 1; + + while ( TokenAvailable() ) + { + GetToken( qfalse ); + if ( !strcmp( token, "-origin" ) ) + { + if ( !TokenAvailable() ) + Error( "missing parameter for -origin" ); + GetToken( qfalse ); + g_data.aseAdjust[1] = -atof( token ); + + if ( !TokenAvailable() ) + Error( "missing parameter for -origin" ); + GetToken( qfalse ); + g_data.aseAdjust[0] = atof (token); + + if ( !TokenAvailable() ) + Error( "missing parameter for -origin" ); + GetToken( qfalse ); + g_data.aseAdjust[2] = -atof (token); + } + else if ( !strcmp( token, "-lod" ) ) + { + if ( !TokenAvailable() ) + Error( "No parameter for -lod" ); + GetToken( qfalse ); + g_data.currentLod = atoi( token ); + if ( g_data.currentLod > MD3_MAX_LODS - 1 ) + { + Error( "-lod parameter too large! (%d)\n", g_data.currentLod ); + } + + if ( !TokenAvailable() ) + Error( "No second parameter for -lod" ); + GetToken( qfalse ); + g_data.lodBias = atof( token ); + } + else if ( !strcmp( token, "-maxtris" ) ) + { + if ( !TokenAvailable() ) + Error( "No parameter for -maxtris" ); + GetToken( qfalse ); + g_data.maxSurfaceTris = atoi( token ); + } + else if ( !strcmp( token, "-playerparms" ) ) + { + if ( !TokenAvailable() ) + Error( "missing skip start parameter for -playerparms" ); + GetToken( qfalse ); + g_data.lowerSkipFrameStart = atoi( token ); + +#if 0 + if ( !TokenAvailable() ) + Error( "missing skip end parameter for -playerparms" ); + GetToken( qfalse ); + g_data.lowerSkipFrameEnd = atoi( token ); +#endif + + if ( !TokenAvailable() ) + Error( "missing upper parameter for -playerparms" ); + GetToken( qfalse ); + g_data.maxUpperFrames = atoi( token ); + + g_data.lowerSkipFrameEnd = g_data.maxUpperFrames - 1; + +#if 0 + if ( !TokenAvailable() ) + Error( "missing head parameter for -playerparms" ); + GetToken( qfalse ); + g_data.maxHeadFrames = atoi( token ); +#endif + g_data.maxHeadFrames = 1; + + if ( type != TYPE_ITEM ) + Error( "invalid argument" ); + + type = TYPE_PLAYER; + } + else if ( !strcmp( token, "-weapon" ) ) + { + if ( type != TYPE_ITEM ) + Error( "invalid argument" ); + + type = TYPE_WEAPON; + } + } + + g_data.type = MD3_TYPE_ASE; + + if ( type == TYPE_WEAPON && grabAnims ) + { + Error( "can't grab anims with weapon models" ); + } + if ( type == TYPE_PLAYER && !grabAnims ) + { + Error( "player models must be converted with $aseanimconvert" ); + } + + if ( type == TYPE_WEAPON ) + { + ConvertASE( filename, type, qfalse ); + ConvertASE( filename, TYPE_HAND, qtrue ); + } + else + { + ConvertASE( filename, type, grabAnims ); + } +} + +static int GetSurfaceAnimations( SurfaceAnimation_t sanims[MAX_ANIM_SURFACES], + const char *part, + int skipFrameStart, + int skipFrameEnd, + int maxFrames ) + +{ + int numSurfaces; + int numValidSurfaces; + int i; + int numFrames = -1; + + if ( ( numSurfaces = ASE_GetNumSurfaces() ) > MAX_ANIM_SURFACES ) + { + Error( "Too many surfaces in ASE" ); + } + + for ( numValidSurfaces = 0, i = 0; i < numSurfaces; i++ ) + { + polyset_t *splitSets; + int numNewFrames; + const char *surfaceName = ASE_GetSurfaceName( i ); + + if ( !surfaceName ) + { + continue; +// Error( "Missing animation frames in model" ); + } + + if ( strstr( surfaceName, "tag_" ) || + !strcmp( part, "any" ) || + ( strstr( surfaceName, part ) == surfaceName ) ) + { + + // skip this if it's an inappropriate tag + if ( strcmp( part, "any" ) ) + { + // ignore non-"tag_head" tags if this is the head + if ( !strcmp( part, "h_" ) && strstr( surfaceName, "tag_" ) && strcmp( surfaceName, "tag_head" ) ) + continue; + // ignore "tag_head" if this is the legs + if ( !strcmp( part, "l_" ) && !strcmp( surfaceName, "tag_head" ) ) + continue; + // ignore "tag_weapon" if this is the legs + if ( !strcmp( part, "l_" ) && !strcmp( surfaceName, "tag_weapon" ) ) + continue; + } + + if ( ( sanims[numValidSurfaces].frames = ASE_GetSurfaceAnimation( i, &sanims[numValidSurfaces].numFrames, skipFrameStart, skipFrameEnd, maxFrames ) ) != 0 ) + { + splitSets = Polyset_SplitSets( sanims[numValidSurfaces].frames, sanims[numValidSurfaces].numFrames, &numNewFrames, g_data.maxSurfaceTris ); + + if ( numFrames == -1 ) + numFrames = sanims[numValidSurfaces].numFrames; + else if ( numFrames != sanims[numValidSurfaces].numFrames ) + Error( "Different number of animation frames on surfaces" ); + + if ( sanims[numValidSurfaces].frames != splitSets ) + { + int j; + + // free old data if we split the surfaces + for ( j = 0; j < sanims[numValidSurfaces].numFrames; j++ ) + { + free( sanims[numValidSurfaces].frames[j].triangles ); + free( sanims[numValidSurfaces].frames ); + } + + sanims[numValidSurfaces].frames = splitSets; + sanims[numValidSurfaces].numFrames = numNewFrames; + } + Polyset_SnapSets( sanims[numValidSurfaces].frames, sanims[numValidSurfaces].numFrames ); + Polyset_ComputeNormals( sanims[numValidSurfaces].frames, sanims[numValidSurfaces].numFrames ); + + numValidSurfaces++; + } + } + } + + return numValidSurfaces; +} + +static int SurfaceOrderToFrameOrder( SurfaceAnimation_t sanims[], ObjectAnimationFrame_t oanims[], int numSurfaces ) +{ + int i, s; + int numFrames = -1; + + /* + ** we have the data here arranged in surface order, now we need to convert it to + ** frame order + */ + for ( i = 0, s = 0; i < numSurfaces; i++ ) + { + int j; + + if ( sanims[i].frames ) + { + if ( numFrames == -1 ) + numFrames = sanims[i].numFrames; + else if ( numFrames != sanims[i].numFrames ) + Error( "numFrames != sanims[i].numFrames (%d != %d)\n", numFrames, sanims[i].numFrames ); + + for ( j = 0; j < sanims[i].numFrames; j++ ) + { + oanims[j].surfaces[s] = &sanims[i].frames[j]; + oanims[j].numSurfaces = numSurfaces; + } + s++; + } + } + + return numFrames; +} + +static void WriteMD3( const char *_filename, ObjectAnimationFrame_t oanims[], int numFrames ) +{ + char filename[1024]; + + strcpy( filename, _filename ); + if ( strchr( filename, '.' ) ) + *strchr( filename, '.' ) = 0; + strcat( filename, ".md3" ); +} + +static void BuildAnimationFromOAFs( const char *filename, ObjectAnimationFrame_t oanims[], int numFrames, int type ) +{ + int f, i, j, tagcount; + float *frameXyz; + float *frameNormals; + + g_data.model.numSurfaces = oanims[0].numSurfaces; + g_data.model.numFrames = numFrames; + if ( g_data.model.numFrames < 0) + Error ("model.numFrames < 0"); + if ( g_data.model.numFrames >= MD3_MAX_FRAMES) + Error ("model.numFrames >= MD3_MAX_FRAMES"); + + // build base frame + BuildBaseFrame( filename, &oanims[0] ); + + // build animation frames + for ( f = 0; f < numFrames; f++ ) + { + ObjectAnimationFrame_t *pOAF = &oanims[f]; + qboolean parentTagExists = qfalse; + md3Tag_t tagParent; + int numtags = 0; + md3Frame_t *fr; + + fr = &g_data.frames[f]; + + strcpy( fr->name, "(from ASE)" ); + + // scale and adjust frame + for ( i = 0; i < pOAF->numSurfaces; i++ ) + { + triangle_t *pTris = pOAF->surfaces[i]->triangles; + int t; + + for ( t = 0; t < pOAF->surfaces[i]->numtriangles; t++ ) + { + for ( j = 0; j < 3; j++ ) + { + int k; + + // scale and adjust + for ( k = 0 ; k < 3 ; k++ ) { + pTris[t].verts[j][k] = pTris[t].verts[j][k] * g_data.scale_up + + g_data.aseAdjust[k]; + + if ( pTris[t].verts[j][k] > 1023 || + pTris[t].verts[j][k] < -1023 ) + { + Error( "Model extents too large" ); + } + } + } + } + } + + // + // find and count tags, locate parent tag + // + for ( i = 0; i < pOAF->numSurfaces; i++ ) + { + if ( strstr( pOAF->surfaces[i]->name, "tag_" ) == pOAF->surfaces[i]->name ) + { + // ignore parent tags when grabbing a weapon model and this is the flash portion + if ( !strcmp( pOAF->surfaces[i]->name, "tag_parent" ) && strstr( filename, "_flash.md3" ) ) + { + continue; + } + else if ( !strstr( filename, "_hand.md3" ) && ( + ( !strcmp( pOAF->surfaces[i]->name, "tag_parent" ) && !strstr( filename, "_flash.md3" ) ) || + ( !strcmp( pOAF->surfaces[i]->name, "tag_torso" ) && ( strstr( filename, "upper_" ) || strstr( filename, "upper.md3" ) ) ) || + ( !strcmp( pOAF->surfaces[i]->name, "tag_head" ) && ( strstr( filename, "head.md3" ) || strstr( filename, "head_" ) ) ) || + ( !strcmp( pOAF->surfaces[i]->name, "tag_flash" ) && strstr( filename, "_flash.md3" ) )|| + ( !strcmp( pOAF->surfaces[i]->name, "tag_weapon" ) && type == TYPE_WEAPON ) ) ) + { + float tri[3][3]; + + if ( parentTagExists ) + Error( "Multiple parent tags not allowed" ); + + memcpy( tri[0], pOAF->surfaces[i]->triangles[0].verts[0], sizeof( float ) * 3 ); + memcpy( tri[1], pOAF->surfaces[i]->triangles[0].verts[1], sizeof( float ) * 3 ); + memcpy( tri[2], pOAF->surfaces[i]->triangles[0].verts[2], sizeof( float ) * 3 ); + + MD3_ComputeTagFromTri( &tagParent, tri ); + strcpy( tagParent.name, "tag_parent" ); + g_data.tags[f][numtags] = tagParent; + parentTagExists = qtrue; + } + else + { + float tri[3][3]; + + memcpy( tri[0], pOAF->surfaces[i]->triangles[0].verts[0], sizeof( float ) * 3 ); + memcpy( tri[1], pOAF->surfaces[i]->triangles[0].verts[1], sizeof( float ) * 3 ); + memcpy( tri[2], pOAF->surfaces[i]->triangles[0].verts[2], sizeof( float ) * 3 ); + + MD3_ComputeTagFromTri( &g_data.tags[f][numtags], tri ); + strcpy( g_data.tags[f][numtags].name, pOAF->surfaces[i]->name ); + if ( strstr( g_data.tags[f][numtags].name, "tag_flash" ) ) + * ( strstr( g_data.tags[f][numtags].name, "tag_flash" ) + strlen( "tag_flash" ) ) = 0; + } + + numtags++; + } + + if ( strcmp( pOAF->surfaces[i]->name, g_data.surfData[i].header.name ) ) + { + Error( "Mismatched surfaces from base('%s') to frame('%s') in model '%s'\n", g_data.surfData[i].header.name, pOAF->surfaces[i]->name, filename ); + } + } + + if ( numtags != g_data.model.numTags ) + { + Error( "mismatched number of tags in frame(%d) vs. base(%d)", numtags, g_data.model.numTags ); + } + + // + // prepare to accumulate bounds and normals + // + ClearBounds( fr->bounds[0], fr->bounds[1] ); + + // + // store the frame's vertices in the same order as the base. This assumes the + // triangles and vertices in this frame are in exactly the same order as in the + // base + // + for ( i = 0, tagcount = 0; i < pOAF->numSurfaces; i++ ) + { + int t; + triangle_t *pTris = pOAF->surfaces[i]->triangles; + + // + // parent tag adjust + // + if ( parentTagExists ) + { + for ( t = 0; t < pOAF->surfaces[i]->numtriangles; t++ ) + { + for ( j = 0; j < 3 ; j++ ) + { + vec3_t tmp; + + VectorSubtract( pTris[t].verts[j], tagParent.origin, tmp ); + + pTris[t].verts[j][0] = DotProduct( tmp, tagParent.axis[0] ); + pTris[t].verts[j][1] = DotProduct( tmp, tagParent.axis[1] ); + pTris[t].verts[j][2] = DotProduct( tmp, tagParent.axis[2] ); + + VectorCopy( pTris[t].normals[j], tmp ); + pTris[t].normals[j][0] = DotProduct( tmp, tagParent.axis[0] ); + pTris[t].normals[j][1] = DotProduct( tmp, tagParent.axis[1] ); + pTris[t].normals[j][2] = DotProduct( tmp, tagParent.axis[2] ); + } + } + } + + // + // compute tag data + // + if ( strstr( pOAF->surfaces[i]->name, "tag_" ) == pOAF->surfaces[i]->name ) + { + md3Tag_t *pTag = &g_data.tags[f][tagcount]; + float tri[3][3]; + + strcpy( pTag->name, pOAF->surfaces[i]->name ); + + memcpy( tri[0], pTris[0].verts[0], sizeof( float ) * 3 ); + memcpy( tri[1], pTris[0].verts[1], sizeof( float ) * 3 ); + memcpy( tri[2], pTris[0].verts[2], sizeof( float ) * 3 ); + + MD3_ComputeTagFromTri( pTag, tri ); + tagcount++; + } + else + { + if ( g_data.surfData[i].verts[f] ) + free( g_data.surfData[i].verts[f] ); + frameXyz = g_data.surfData[i].verts[f] = calloc( 1, sizeof( float ) * 6 * g_data.surfData[i].header.numVerts ); + frameNormals = frameXyz + 3; + + for ( t = 0; t < pOAF->surfaces[i]->numtriangles; t++ ) + { + for ( j = 0; j < 3 ; j++ ) + { + int index; + + index = g_data.surfData[i].baseTriangles[t].v[j].index; + frameXyz[index*6+0] = pTris[t].verts[j][0]; + frameXyz[index*6+1] = pTris[t].verts[j][1]; + frameXyz[index*6+2] = pTris[t].verts[j][2]; + frameNormals[index*6+0] = pTris[t].normals[j][0]; + frameNormals[index*6+1] = pTris[t].normals[j][1]; + frameNormals[index*6+2] = pTris[t].normals[j][2]; + AddPointToBounds (&frameXyz[index*6], fr->bounds[0], fr->bounds[1] ); + } + } + } + } + } + + if ( strstr( filename, gamedir + 1 ) ) + { + strcpy( g_modelname, strstr( filename, gamedir + 1 ) + strlen( gamedir ) - 1 ); + } + else + { + strcpy( g_modelname, filename ); + } + + FinishModel( type ); + ClearModel(); +} + +static void ConvertASE( const char *filename, int type, qboolean grabAnims ) +{ + int i, j; + int numSurfaces; + int numFrames = -1; + SurfaceAnimation_t surfaceAnimations[MAX_ANIM_SURFACES]; + ObjectAnimationFrame_t objectAnimationFrames[MAX_ANIM_FRAMES]; + char outfilename[1024]; + + /* + ** load ASE into memory + */ + ASE_Load( filename, g_verbose, grabAnims ); + + /* + ** process parts + */ + if ( type == TYPE_ITEM ) + { + numSurfaces = GetSurfaceAnimations( surfaceAnimations, "any", -1, -1, -1 ); + + if ( numSurfaces <= 0 ) + Error( "numSurfaces <= 0" ); + + numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces ); + + if ( numFrames <= 0 ) + Error( "numFrames <= 0" ); + + strcpy( outfilename, filename ); + if ( strrchr( outfilename, '.' ) ) + *( strrchr( outfilename, '.' ) + 1 ) = 0; + strcat( outfilename, "md3" ); + BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type ); + + // free memory + for ( i = 0; i < numSurfaces; i++ ) + { + if ( surfaceAnimations[i].frames ) + { + for ( j = 0; j < surfaceAnimations[i].numFrames; j++ ) + { + free( surfaceAnimations[i].frames[j].triangles ); + } + free( surfaceAnimations[i].frames ); + surfaceAnimations[i].frames = 0; + } + } + } + else if ( type == TYPE_PLAYER ) + { + qboolean tagTorso = qfalse; + qboolean tagHead = qfalse; + qboolean tagWeapon = qfalse; + + // + // verify that all necessary tags exist + // + numSurfaces = ASE_GetNumSurfaces(); + for ( i = 0; i < numSurfaces; i++ ) + { + if ( !strcmp( ASE_GetSurfaceName( i ), "tag_head" ) ) + { + tagHead = qtrue; + } + if ( !strcmp( ASE_GetSurfaceName( i ), "tag_torso" ) ) + { + tagTorso = qtrue; + } + if ( !strcmp( ASE_GetSurfaceName( i ), "tag_weapon" ) ) + { + tagWeapon = qtrue; + } + } + + if ( !tagWeapon ) + { + Error( "Missing tag_weapon!" ); + } + if ( !tagTorso ) + { + Error( "Missing tag_torso!" ); + } + if ( !tagWeapon ) + { + Error( "Missing tag_weapon!" ); + } + + // get all upper body surfaces + numSurfaces = GetSurfaceAnimations( surfaceAnimations, "u_", -1, -1, g_data.maxUpperFrames ); + numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces ); + strcpy( outfilename, filename ); + if ( strrchr( outfilename, '/' ) ) + *( strrchr( outfilename, '/' ) + 1 ) = 0; + + if ( g_data.currentLod == 0 ) + { + strcat( outfilename, "upper.md3" ); + } + else + { + char temp[128]; + + sprintf( temp, "upper_%d.md3", g_data.currentLod ); + strcat( outfilename, temp ); + } + + BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type ); + + // free memory + for ( i = 0; i < numSurfaces; i++ ) + { + if ( surfaceAnimations[i].frames ) + { + for ( j = 0; j < surfaceAnimations[i].numFrames; j++ ) + { + free( surfaceAnimations[i].frames[j].triangles ); + } + free( surfaceAnimations[i].frames ); + surfaceAnimations[i].frames = 0; + } + } + + // get lower body surfaces + numSurfaces = GetSurfaceAnimations( surfaceAnimations, "l_", g_data.lowerSkipFrameStart, g_data.lowerSkipFrameEnd, -1 ); + numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces ); + strcpy( outfilename, filename ); + if ( strrchr( outfilename, '/' ) ) + *( strrchr( outfilename, '/' ) + 1 ) = 0; + + if ( g_data.currentLod == 0 ) + { + strcat( outfilename, "lower.md3" ); + } + else + { + char temp[128]; + + sprintf( temp, "lower_%d.md3", g_data.currentLod ); + strcat( outfilename, temp ); + } + BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type ); + + // free memory + for ( i = 0; i < numSurfaces; i++ ) + { + if ( surfaceAnimations[i].frames ) + { + for ( j = 0; j < surfaceAnimations[i].numFrames; j++ ) + { + free( surfaceAnimations[i].frames[j].triangles ); + } + free( surfaceAnimations[i].frames ); + surfaceAnimations[i].frames = 0; + } + } + + // get head surfaces + numSurfaces = GetSurfaceAnimations( surfaceAnimations, "h_", -1, -1, g_data.maxHeadFrames ); + numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces ); + strcpy( outfilename, filename ); + if ( strrchr( outfilename, '/' ) ) + *( strrchr( outfilename, '/' ) + 1 ) = 0; + + if ( g_data.currentLod == 0 ) + { + strcat( outfilename, "head.md3" ); + } + else + { + char temp[128]; + + sprintf( temp, "head_%d.md3", g_data.currentLod ); + strcat( outfilename, temp ); + } + BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type ); + + // free memory + for ( i = 0; i < numSurfaces; i++ ) + { + if ( surfaceAnimations[i].frames ) + { + for ( j = 0; j < surfaceAnimations[i].numFrames; j++ ) + { + free( surfaceAnimations[i].frames[j].triangles ); + } + free( surfaceAnimations[i].frames ); + surfaceAnimations[i].frames = 0; + } + } + } + else if ( type == TYPE_WEAPON ) + { + // get the weapon surfaces + numSurfaces = GetSurfaceAnimations( surfaceAnimations, "w_", -1, -1, -1 ); + numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces ); + + strcpy( outfilename, filename ); + if ( strrchr( outfilename, '.' ) ) + *( strrchr( outfilename, '.' ) + 1 ) = 0; + strcat( outfilename, "md3" ); + BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, type ); + + // free memory + for ( i = 0; i < numSurfaces; i++ ) + { + if ( surfaceAnimations[i].frames ) + { + for ( j = 0; j < surfaceAnimations[i].numFrames; j++ ) + { + free( surfaceAnimations[i].frames[j].triangles ); + } + free( surfaceAnimations[i].frames ); + surfaceAnimations[i].frames = 0; + } + } + + // get the flash surfaces + numSurfaces = GetSurfaceAnimations( surfaceAnimations, "f_", -1, -1, -1 ); + numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces ); + + strcpy( outfilename, filename ); + if ( strrchr( outfilename, '.' ) ) + *strrchr( outfilename, '.' ) = 0; + strcat( outfilename, "_flash.md3" ); + BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, TYPE_ITEM ); + + // free memory + for ( i = 0; i < numSurfaces; i++ ) + { + if ( surfaceAnimations[i].frames ) + { + for ( j = 0; j < surfaceAnimations[i].numFrames; j++ ) + { + free( surfaceAnimations[i].frames[j].triangles ); + } + free( surfaceAnimations[i].frames ); + surfaceAnimations[i].frames = 0; + } + } + } + else if ( type == TYPE_HAND ) + { + // get the hand tags + numSurfaces = GetSurfaceAnimations( surfaceAnimations, "tag_", -1, -1, -1 ); + numFrames = SurfaceOrderToFrameOrder( surfaceAnimations, objectAnimationFrames, numSurfaces ); + + strcpy( outfilename, filename ); + if ( strrchr( outfilename, '.' ) ) + *strrchr( outfilename, '.' ) = 0; + strcat( outfilename, "_hand.md3" ); + BuildAnimationFromOAFs( outfilename, objectAnimationFrames, numFrames, TYPE_HAND ); + + // free memory + for ( i = 0; i < numSurfaces; i++ ) + { + if ( surfaceAnimations[i].frames ) + { + for ( j = 0; j < surfaceAnimations[i].numFrames; j++ ) + { + free( surfaceAnimations[i].frames[j].triangles ); + } + free( surfaceAnimations[i].frames ); + surfaceAnimations[i].frames = 0; + } + } + } + else + { + Error( "Unknown type passed to ConvertASE()" ); + } + + g_data.currentLod = 0; + g_data.lodBias = 0; + g_data.maxHeadFrames = 0; + g_data.maxUpperFrames = 0; + g_data.lowerSkipFrameStart = 0; + g_data.lowerSkipFrameEnd = 0; + VectorCopy( vec3_origin, g_data.aseAdjust ); + + // unload ASE from memory + ASE_Free(); +} diff --git a/tools/quake3/q3data/oldstuff.c b/tools/quake3/q3data/oldstuff.c new file mode 100644 index 00000000..045c67e7 --- /dev/null +++ b/tools/quake3/q3data/oldstuff.c @@ -0,0 +1,151 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if 0 + +/* +** ReindexTriangle +** +** Given a triangle_t, find which indices match into the associated +** surface's base triangles. +*/ +static void ReindexTriangle( int surfno, triangle_t *pTri, int indices[3] ) +{ + int t, i; + md3SurfaceData_t *pSurfData = &g_data.surfData[surfno]; + int matches[3][3]; + int numMatches = 0; + + + indices[0] = -1; + indices[1] = -1; + indices[2] = -1; + + for ( i = 0; i < 3; i++ ) + { + numMatches = 0; + + matches[i][0] = -1; + matches[i][1] = -1; + matches[i][2] = -1; + + for ( t = 0; t < pSurfData->header.numVerts; t++ ) + { + if ( !VectorCompare( pTri->verts[i], pSurfData->baseVertexes[t].xyz ) ) + continue; + +/* + if ( !VectorCompare( pTri->normals[i], pSurfData->baseVertexes[t].normal ) ) + continue; + if ( pTri->texcoords[i][0] != pSurfData->baseVertexes[t].st[0] ) + continue; + if ( pTri->texcoords[i][1] != pSurfData->baseVertexes[t].st[1] ) + continue; +*/ + + matches[i][numMatches++] = t; + } + + if ( indices[i] == -1 ) + { +// Error( "Could not ReindexTriangle, vertex not found" ); + } + } + +#if 0 + for ( t = 0; t < psets[i].numtriangles; t++ ) + { + int b; + + bTri = &g_data.surfData[i].baseTriangles[t]; + + for (j=0 ; j<3 ; j++) + { + bVert = &bTri->v[j]; + + // get the xyz index + for ( k = 0; k < g_data.surfData[i].header.numVerts; k++ ) + { + if ( ( g_data.surfData[i].baseVertexes[k].st[0] == bVert->st[0] ) && + ( g_data.surfData[i].baseVertexes[k].st[1] == bVert->st[1] ) && + ( VectorCompare (bVert->xyz, g_data.surfData[i].baseVertexes[k].xyz) ) && + ( VectorCompare (bVert->normal, g_data.surfData[i].baseVertexes[k].normal) ) ) + { + break; // this vertex is already in the base vertex list + } + } + + if (k == g_data.surfData[i].header.numVerts) { // new index + g_data.surfData[i].baseVertexes[g_data.surfData[i].header.numVerts] = *bVert; + g_data.surfData[i].header.numVerts++; + } + + bVert->index = k; + } + } +#endif +} + +const char *FindFrameFile (const char *frame) +{ + int time1; + char file1[1024]; + static char retname[1024]; + char base[32]; + char suffix[32]; + const char *s; + + if (strstr (frame, ".")) + return frame; // allready in dot format + + // split 'run1' into 'run' and '1' + s = frame + strlen(frame)-1; + + while (s != frame && *s >= '0' && *s <= '9') + s--; + + strcpy (suffix, s+1); + strcpy (base, frame); + base[s-frame+1] = 0; + + // check for 'run1.tri' + sprintf (file1, "%s/%s%s.tri", g_cddir, base, suffix); + time1 = FileTime (file1); + if (time1 != -1) + { + sprintf (retname, "%s%s.tri", base, suffix); + return retname; + } + + // check for 'run.1' + sprintf (file1, "%s/%s.%s",g_cddir, base, suffix); + time1 = FileTime (file1); + if (time1 != -1) + { + sprintf (retname, "%s.%s", base, suffix); + return retname; + } + + Error ("frame %s could not be found",frame); + return NULL; +} + +#endif diff --git a/tools/quake3/q3data/p3dlib.c b/tools/quake3/q3data/p3dlib.c new file mode 100644 index 00000000..3ccdfbe1 --- /dev/null +++ b/tools/quake3/q3data/p3dlib.c @@ -0,0 +1,345 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "p3dlib.h" + +#ifdef WIN32 +#include +#endif +#include +#include +#include + +#define MAX_POLYSETS 64 + +#if defined (__linux__) || defined (__APPLE__) +#define _strcmpi Q_stricmp +#define filelength Q_filelength +#define strlwr strlower +#endif +typedef struct +{ + long len; + + int numPairs; + char polysetNames[MAX_POLYSETS][256]; + char shaders[MAX_POLYSETS][256]; + + char *buffer, *curpos; +} p3d_t; + +static p3d_t p3d; + +static int P3DProcess(); +static int P3DGetToken( int restOfLine ); + +static char s_token[1024]; +static int s_curpair; + +/* +** P3DLoad +** +*/ +int P3DLoad( const char *filename ) +{ + FILE *fp = fopen( filename, "rb" ); + + if ( !fp ) + return 0; + + memset( &p3d, 0, sizeof( p3d ) ); + + p3d.len = filelength( fileno( fp ) ); + + p3d.curpos = p3d.buffer = malloc( p3d.len ); + + if ( fread( p3d.buffer, p3d.len, 1, fp ) != 1 ) + { + fclose( fp ); + return 0; + } + + fclose( fp ); + + return P3DProcess(); +} + +/* +** P3DClose +** +*/ +void P3DClose() +{ + if ( p3d.buffer ) + { + free( p3d.buffer ); + p3d.buffer = 0; + } +} + +int CharIsTokenDelimiter( int ch ) +{ + if ( ch <= 32 ) + return 1; + return 0; +} + +int P3DSkipToToken( const char *name ) +{ + while ( P3DGetToken( 0 ) ) + { + if ( !_strcmpi( s_token, name ) ) + return 1; + } + + return 0; +} + +/* +** P3DGetToken +** +*/ +int P3DGetToken( int restOfLine ) +{ + int i = 0; + + if ( p3d.buffer == 0 ) + return 0; + + if ( ( p3d.curpos - p3d.buffer ) == p3d.len ) + return 0; + + // skip over crap + while ( ( ( p3d.curpos - p3d.buffer ) < p3d.len ) && + ( *p3d.curpos <= 32 ) ) + { + p3d.curpos++; + } + + while ( ( p3d.curpos - p3d.buffer ) < p3d.len ) + { + s_token[i] = *p3d.curpos; + + p3d.curpos++; + i++; + + if ( ( CharIsTokenDelimiter( s_token[i-1] ) && !restOfLine ) || + ( ( s_token[i-1] == '\n' ) ) ) + { + s_token[i-1] = 0; + break; + } + } + + s_token[i] = 0; + + return 1; +} + +int P3DGetNextPair( char **psetName, char **associatedShader ) +{ + if ( s_curpair < p3d.numPairs ) + { + *psetName = p3d.polysetNames[s_curpair]; + *associatedShader = p3d.shaders[s_curpair]; + s_curpair++; + return 1; + } + + return 0; +} + +int P3DSkipToTokenInBlock( const char *name ) +{ + int iLevel = 0; + + while ( P3DGetToken( 0 ) ) + { + if ( !_strcmpi( s_token, "}" ) ) + iLevel--; + else if ( !_strcmpi( s_token, "{" ) ) + iLevel++; + + if ( !_strcmpi( s_token, name ) ) + return 1; + + if ( iLevel == 0 ) + { + return 0; + } + } + + return 0; +} + +/* +** P3DProcess +** +** Nothing fancy here. +*/ +int P3DProcess() +{ + + s_curpair = 0; + + // first token should be a string + P3DGetToken( 1 ); // Voodoo Ascii File + + // skip to the first Obj declaration + while ( P3DGetToken( 0 ) ) + { + if ( !_strcmpi( s_token, "Obj" ) ) + { + int j = 0, k = 0; + + if ( P3DSkipToToken( "Text" ) ) + { + if ( P3DSkipToTokenInBlock( "TMap" ) ) + { + char *p; + + if ( !P3DSkipToToken( "Path" ) ) + return 0; + + if ( !P3DGetToken( 1 ) ) + return 0; + + while ( s_token[j] != 0 ) + { + if ( s_token[j] == '\\' ) + { + j++; + p3d.shaders[p3d.numPairs][k] = '/'; + } + else + { + p3d.shaders[p3d.numPairs][k] = s_token[j]; + } + j++; + k++; + } + p3d.shaders[p3d.numPairs][k] = 0; + + // + // strip off any explicit extensions + // + if ( ( p = strrchr( p3d.shaders[p3d.numPairs], '/' ) ) != 0 ) + { + while ( *p ) + { + if ( *p == '.' ) + { + *p = 0; + break; + } + p++; + } + } + + // + // skip to the end of the Object and grab its name + // + if ( !P3DSkipToToken( "Name" ) ) + return 0; + + if ( P3DGetToken( 0 ) ) + { + // strip off leading 'Obj_' if it exists + if ( strstr( s_token, "Obj_" ) == s_token ) + strcpy( p3d.polysetNames[p3d.numPairs], s_token + strlen( "Obj_" ) ); + else + strcpy( p3d.polysetNames[p3d.numPairs], s_token ); + + // strip off trailing unused color information +// if ( strrchr( p3d.polysetNames[p3d.numPairs], '_' ) != 0 ) +// *strrchr( p3d.polysetNames[p3d.numPairs], '_' ) = 0; + + p3d.numPairs++; + } + else + { + return 0; + } + } + } + } + } + + s_curpair = 0; + + return 1; +} + +#if 0 +void SkinFromP3D( const char *file ) +{ + char filename[1024]; + char *psetName, *associatedShader; + + /* + ** a P3D file contains a list of polysets, each with a list of associated + ** texture names that constitute it's + ** + ** Thus: + ** + ** P3D file -> skin + ** polyset -> polyset + ** texture -> texture.SHADER becomes polyset's shader + */ + sprintf( filename, "%s/%s", g_cddir, file ); + + if ( !P3DLoad( filename ) ) + Error( "unable to load '%s'", filename ); + + while ( P3DGetNextPair( &psetName, &associatedShader ) ) + { + int i; + + // find the polyset in the object that this particular pset/shader pair + // corresponds to and append the shader to it + for ( i = 0; i < g_data.model.numSurfaces; i++ ) + { + if ( !_strcmpi( g_data.surfData[i].header.name, psetName) ) + { + char *p; + + if ( strstr( associatedShader, gamedir + 1 ) ) + { + p = strstr( associatedShader, gamedir + 1 ) + strlen( gamedir ) - 1; + } + else + { + p = associatedShader; + } + + strcpy( g_data.surfData[i].shaders[g_data.surfData[i].header.numShaders].name, p ); + + g_data.surfData[i].header.numShaders++; + } + } + + } + + P3DClose(); +} +#endif + + diff --git a/tools/quake3/q3data/p3dlib.h b/tools/quake3/q3data/p3dlib.h new file mode 100644 index 00000000..1bd63078 --- /dev/null +++ b/tools/quake3/q3data/p3dlib.h @@ -0,0 +1,29 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +#define P3D_GET_CROSSLINE 1 +#define P3D_GET_RESTOFLINE 2 + +int P3DLoad( const char *filename ); +void P3DClose(); + +int P3DGetNextPair( char **name, char **associatedShader ); diff --git a/tools/quake3/q3data/polyset.c b/tools/quake3/q3data/polyset.c new file mode 100644 index 00000000..cdb0cda7 --- /dev/null +++ b/tools/quake3/q3data/polyset.c @@ -0,0 +1,273 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include "q3data.h" + +polyset_t *Polyset_SplitSets( polyset_t *psets, int numpolysets, int *pNumNewPolysets, int maxTris ) +{ + int p, np, op; + int numNewPolysets = 0; + int numSplitPolysets = 0; + polyset_t *newpsets; + int sumTriangles = 0; + + for ( p = 0; p < numpolysets; p++ ) + { + numNewPolysets += psets[p].numtriangles / maxTris + 1; + } + + if ( numNewPolysets == numpolysets ) + return psets; + + printf( "Warning: creating %d polysets from input of %d polysets\n", numNewPolysets, numpolysets ); + + newpsets = calloc( sizeof( polyset_t ) * numNewPolysets, 1 ); + + for ( np = 0, op = 0; op < numpolysets; op++ ) + { + numSplitPolysets = ( psets[op].numtriangles / ( maxTris + 1 ) ) + 1; + if ( numSplitPolysets == 1 ) + { + memcpy( &newpsets[np], &psets[op], sizeof( polyset_t ) ); + np++; + } + else + { + sumTriangles = 0; + + // split this pset into multiple smaller psets + for ( p = 0; p < numSplitPolysets; p++, np++ ) + { + memcpy( &newpsets[np], &psets[op], sizeof( polyset_t ) ); + + newpsets[np].triangles = psets[op].triangles + sumTriangles; + + if ( sumTriangles + maxTris > psets[op].numtriangles ) + newpsets[np].numtriangles = psets[op].numtriangles - sumTriangles; + else + newpsets[np].numtriangles = maxTris; + + sumTriangles += newpsets[np].numtriangles; + } + } + } + + *pNumNewPolysets = numNewPolysets; + + return newpsets; +} + +polyset_t *Polyset_LoadSets( const char *file, int *numpolysets, int maxTrisPerSet ) +{ + polyset_t *psets; + polyset_t *finalpsets; + + // + // load the frame + // + if ( strstr( file, ".3DS" ) || strstr( file, ".3ds" ) ) + _3DS_LoadPolysets( file, &psets, numpolysets, g_verbose ); + else + Error( "TRI files no longer supported" ); +// TRI_LoadPolysets( file, &psets, numpolysets ); + +/* + // + // scale polysets + // + for ( i = 0; i < psets; i++ ) + { + int j; + + for ( j = 0; j < psets[i].numtriangles; j++ ) + { + } + } +*/ + + // + // split polysets if necessary + // + finalpsets = Polyset_SplitSets( psets, *numpolysets, numpolysets, maxTrisPerSet ); + + return finalpsets; +} + +polyset_t *Polyset_CollapseSets( polyset_t *psets, int numpolysets ) +{ + int p; + int sumtriangles = 0; + + polyset_t *oldpsets = psets; + + // + // no tag checking because this is an $oldbase and thus shouldn't have any + // tags + // + for ( p = 0; p < numpolysets; p++ ) + { + sumtriangles += oldpsets[p].numtriangles; + } + + psets = calloc( 1, sizeof( polyset_t ) ); + psets[0].numtriangles = sumtriangles; + psets[0].triangles = malloc( MD3_MAX_TRIANGLES * sizeof( triangle_t ) ); + + // each call to "LoadPolysets" only allocates a single large chunk of + // triangle memory that is utilized by all the polysets loaded by + // that one call + memcpy( psets[0].triangles, oldpsets[0].triangles, sizeof( triangle_t ) * sumtriangles ); + + free( oldpsets[0].triangles ); + free( oldpsets ); + + return psets; +} + +static float SnapFloat( float x ) +{ + int ix; + + x *= 1.0f / MD3_XYZ_SCALE; + ix = ( int ) x; + x = ( float ) ix; + x *= MD3_XYZ_SCALE; + + return x; +} + +void Polyset_SnapSets( polyset_t *psets, int numpolysets ) +{ + int p; + + for ( p = 0; p < numpolysets; p++ ) + { + int t; + + for ( t = 0; t < psets[p].numtriangles; t++ ) + { + int v; + + for ( v = 0; v < 3; v++ ) + { + psets[p].triangles[t].verts[v][0] = SnapFloat( psets[p].triangles[t].verts[v][0] ); + psets[p].triangles[t].verts[v][1] = SnapFloat( psets[p].triangles[t].verts[v][1] ); + psets[p].triangles[t].verts[v][2] = SnapFloat( psets[p].triangles[t].verts[v][2] ); + } + } + } +} + +void Polyset_ComputeNormals( polyset_t *psets, int numpolysets ) +{ + int p; + int i, t; + int vertexIndex[MD3_MAX_TRIANGLES][3]; + vec3_t verts[MD3_MAX_VERTS]; + vec3_t normals[MD3_MAX_VERTS]; + vec3_t faceNormals[MD3_MAX_TRIANGLES]; + + // + // iterate through polysets + // + for ( p = 0; p < numpolysets; p++ ) + { + int numUniqueVertices = 0; + + assert( psets[p].numtriangles < MD3_MAX_TRIANGLES ); + + memset( vertexIndex, 0xff, sizeof( vertexIndex ) ); + memset( verts, 0, sizeof( verts ) ); + memset( normals, 0, sizeof( normals ) ); + + // + // unique vertices + // + for ( t = 0; t < psets[p].numtriangles; t++ ) + { + int j; + + for ( j = 0; j < 3; j++ ) + { + for ( i = 0; i < numUniqueVertices; i++ ) + { + if ( VectorCompare( psets[p].triangles[t].verts[j], verts[i] ) ) + { + break; + } + } + if ( i == numUniqueVertices ) + { + vertexIndex[t][j] = numUniqueVertices; + VectorCopy( (psets[p].triangles[t].verts[j]), (verts[numUniqueVertices]) ); + numUniqueVertices++; + } + else + { + vertexIndex[t][j] = i; + } + } + } + + // + // compute face normals + // + for ( t = 0; t < psets[p].numtriangles; t++ ) + { + vec3_t side0, side1, facenormal; + + VectorSubtract( psets[p].triangles[t].verts[0], psets[p].triangles[t].verts[1], side0 ); + VectorSubtract( psets[p].triangles[t].verts[2], psets[p].triangles[t].verts[1], side1); + + CrossProduct( side0, side1, facenormal ); + VectorNormalize( facenormal, faceNormals[t] ); + } + + // + // sum normals and copy them back + // + for ( i = 0; i < numUniqueVertices; i++ ) + { + for ( t = 0; t < psets[p].numtriangles; t++ ) + { + if ( vertexIndex[t][0] == i || + vertexIndex[t][1] == i || + vertexIndex[t][2] == i ) + { + normals[i][0] += faceNormals[t][0]; + normals[i][1] += faceNormals[t][1]; + normals[i][2] += faceNormals[t][2]; + } + } + VectorNormalize( normals[i], normals[i] ); + } + + + for ( t = 0; t < psets[p].numtriangles; t++ ) + { + VectorCopy( normals[vertexIndex[t][0]], psets[p].triangles[t].normals[0] ); + VectorCopy( normals[vertexIndex[t][1]], psets[p].triangles[t].normals[1] ); + VectorCopy( normals[vertexIndex[t][2]], psets[p].triangles[t].normals[2] ); + } + } +} + diff --git a/tools/quake3/q3data/q3data.c b/tools/quake3/q3data/q3data.c new file mode 100644 index 00000000..40887fcc --- /dev/null +++ b/tools/quake3/q3data/q3data.c @@ -0,0 +1,666 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifdef WIN32 +#include +#endif +#include "q3data.h" +#include "md3lib.h" + +#include "vfs.h" + +qboolean g_verbose; +qboolean g_stripify = qtrue; +qboolean g_release; // don't grab, copy output data to new tree +char g_releasedir[1024]; // c:\quake2\baseq2, etc +qboolean g_archive; // don't grab, copy source data to new tree +char g_only[256]; // if set, only grab this cd +qboolean g_skipmodel; // set true when a cd is not g_only + +// bogus externs for some TA hacks (common/ using them against q3map) +char *moddir = NULL; +// some old defined that was in cmdlib lost during merge +char writedir[1024]; + +#if defined (__linux__) || defined (__APPLE__) +#define strlwr strlower +#endif + +/* +======================================================= + + PAK FILES + +======================================================= +*/ + +unsigned Com_BlockChecksum (void *buffer, int length); + +typedef struct +{ + char name[56]; + int filepos, filelen; +} packfile_t; + +typedef struct +{ + char id[4]; + int dirofs; + int dirlen; +} packheader_t; + +packfile_t pfiles[16384]; +FILE *pakfile; +packfile_t *pf; +packheader_t pakheader; + +/* +============== +ReleaseFile + +Filename should be gamedir reletive. +Either copies the file to the release dir, or adds it to +the pak file. +============== +*/ +void ReleaseFile (char *filename) +{ + char source[1024]; + char dest[1024]; + + if (!g_release) + return; + + sprintf (source, "%s%s", gamedir, filename); + sprintf (dest, "%s/%s", g_releasedir, filename); + printf ("copying to %s\n", dest); + QCopyFile (source, dest); + return; +} + +typedef struct +{ + // shader + // opaque + // opaque 2 + // blend + // blend 2 + char names[5][1024]; + int num; +} ShaderFiles_t; + +ShaderFiles_t s_shaderFiles; + +void FindShaderFiles( char *filename ) +{ + char buffer[1024]; + char stripped[1024]; + char linebuffer[1024]; + int len, i; + char *buf; + char *diffuseExtensions[] = + { + ".TGA", + ".WAL", + ".PCX", + 0 + }; + char *otherExtensions[] = + { + ".specular.TGA", + ".blend.TGA", + ".alpha.TGA", + 0 + }; + + s_shaderFiles.num = 0; + + strcpy( stripped, filename ); + if ( strrchr( stripped, '.' ) ) + *strrchr( stripped, '.' ) = 0; + strcat( stripped, ".shader" ); + + if ( FileExists( stripped ) ) + { + char *p; + char mapa[512], mapb[512]; + + strcpy( s_shaderFiles.names[s_shaderFiles.num], stripped ); + s_shaderFiles.num++; + + // load and parse + len = LoadFile( stripped, (void **)&buf); + + p = buf; + + while ( p - buf < len ) + { + i = 0; + + // skip spaces + while ( *p == ' ' || *p == '\n' || *p == '\t' ) + p++; + + // grab rest of the line + while ( *p != 0 && *p != '\n' ) + { + linebuffer[i] = *p; + i++; + p++; + } + if ( *p == '\n' ) + p++; + linebuffer[i] = 0; + + strlwr( linebuffer ); + + // see if the line specifies an opaque map or blendmap + if ( strstr( linebuffer, "opaquemap" ) == linebuffer || + strstr( linebuffer, "blendmap" ) == linebuffer ) + { + int j; + + i = 0; + + mapa[0] = mapb[0] = 0; + + // skip past the keyword + while ( linebuffer[i] != ' ' && linebuffer[i] != '\t' && linebuffer[i] ) + i++; + // skip past spaces + while ( ( linebuffer[i] == ' ' || linebuffer[i] == '\t' ) && linebuffer[i] ) + i++; + + // grab first map name + j = 0; + while ( linebuffer[i] != ' ' && linebuffer[i] != '\t' && linebuffer[i] ) + { + mapa[j] = linebuffer[i]; + j++; + i++; + } + mapa[j] = 0; + + // skip past spaces + while ( ( linebuffer[i] == ' ' || linebuffer[i] == '\t' ) && linebuffer[i] ) + i++; + + // grab second map name + j = 0; + while ( linebuffer[i] != ' ' && linebuffer[i] != '\t' && linebuffer[i] ) + { + mapb[j] = linebuffer[i]; + j++; + i++; + } + mapb[j] = 0; + + // store map names + if ( mapa[0] != 0 && mapa[0] != '-' ) + { + sprintf( s_shaderFiles.names[s_shaderFiles.num], "%s%s", gamedir, mapa ); + s_shaderFiles.num++; + } + if ( mapb[0] != 0 && mapb[0] != '-' && mapb[0] != '^' && mapb[0] != '*' ) + { + sprintf( s_shaderFiles.names[s_shaderFiles.num], "%s%s", gamedir, mapb ); + s_shaderFiles.num++; + } + } + } + } + else + { + if ( strrchr( stripped, '.' ) ) + *strrchr( stripped, '.' ) = 0; + + // look for diffuse maps + for ( i = 0; i < 3; i++ ) + { + strcpy( buffer, stripped ); + strcat( buffer, diffuseExtensions[i] ); + if ( FileExists( buffer ) ) + { + strcpy( s_shaderFiles.names[s_shaderFiles.num], buffer ); + s_shaderFiles.num++; + break; + } + } + for ( i = 0; i < 3; i++ ) + { + strcpy( buffer, stripped ); + strcat( buffer, otherExtensions[i] ); + if ( FileExists( buffer ) ) + { + strcpy( s_shaderFiles.names[s_shaderFiles.num], buffer ); + s_shaderFiles.num++; + } + } + } +} + +/* +============== +ReleaseShader + +Copies all needed files for a shader to the release directory +============== +*/ +void ReleaseShader( char *filename ) +{ + char fullpath[1024]; + char dest[1024]; + char stripped[1024]; + int i; + + sprintf( fullpath, "%s%s", gamedir, filename ); + + FindShaderFiles( fullpath ); + + for ( i = 0; i < s_shaderFiles.num; i++ ) + { + strcpy( stripped, s_shaderFiles.names[i] ); + if ( strstr( stripped, gamedir ) ) + { + memmove( stripped, stripped+ strlen( gamedir ), strlen( stripped ) ); + } + sprintf( dest, "%s/%s", g_releasedir, stripped ); + printf ("copying to %s\n", dest ); + QCopyFile( s_shaderFiles.names[i], dest ); + } +} + +/* +=============== +Cmd_File + +This is only used to cause a file to be copied during a release +build (default.cfg, maps, etc) +=============== +*/ +void Cmd_File (void) +{ + GetToken (qfalse); + ReleaseFile (token); +} + +/* +=============== +PackDirectory_r + +=============== +*/ +#ifdef _WIN32 +#include "io.h" +void PackDirectory_r (char *dir) +{ + struct _finddata_t fileinfo; + int handle; + char dirstring[1024]; + char filename[1024]; + + sprintf (dirstring, "%s%s/*.*", gamedir, dir); + + handle = _findfirst (dirstring, &fileinfo); + if (handle == -1) + return; + + do + { + sprintf (filename, "%s/%s", dir, fileinfo.name); + if (fileinfo.attrib & _A_SUBDIR) + { // directory + if (fileinfo.name[0] != '.') // don't pak . and .. + PackDirectory_r (filename); + continue; + } + // copy or pack the file + ReleaseFile (filename); + } while (_findnext( handle, &fileinfo ) != -1); + + _findclose (handle); +} +#else + +#include +#ifndef WIN32 +#include +#else +#include +#endif + +void PackDirectory_r (char *dir) +{ +#ifdef NeXT + struct direct **namelist, *ent; +#else + struct dirent **namelist, *ent; +#endif + int count; + struct stat st; + int i; + int len; + char fullname[1024]; + char dirstring[1024]; + char *name; + + sprintf (dirstring, "%s%s", gamedir, dir); + count = scandir(dirstring, &namelist, NULL, NULL); + + for (i=0 ; id_name; + + if (name[0] == '.') + continue; + + sprintf (fullname, "%s/%s", dir, name); + sprintf (dirstring, "%s%s/%s", gamedir, dir, name); + + if (stat (dirstring, &st) == -1) + Error ("fstating %s", pf->name); + if (st.st_mode & S_IFDIR) + { // directory + PackDirectory_r (fullname); + continue; + } + + // copy or pack the file + ReleaseFile (fullname); + } +} +#endif + + +/* +=============== +Cmd_Dir + +This is only used to cause a directory to be copied during a +release build (sounds, etc) +=============== +*/ +void Cmd_Dir (void) +{ + GetToken (qfalse); + PackDirectory_r (token); +} + +//======================================================================== + +#define MAX_RTEX 16384 +int numrtex; +char rtex[MAX_RTEX][64]; + +void ReleaseTexture (char *name) +{ + int i; + char path[1024]; + + for (i=0 ; i] [-dump ] [-release ] [-only ] [-3dsconvert ] [-verbose] [file.qdt]"); + + for ( ; i +#include +#include +#include +#include + +#include "../common/cmdlib.h" +#include "scriplib.h" +#include "mathlib.h" +#include "polyset.h" +#include "trilib.h" +#include "imagelib.h" +#include "qthreads.h" +#include "l3dslib.h" +#include "bspfile.h" +#include "p3dlib.h" +#include "3dslib.h" +#include "aselib.h" +#include "md3lib.h" + +void Cmd_ASEConvert( qboolean grabAnims ); +void Cmd_3DSConvert( void ); +void Cmd_Modelname (void); +void Cmd_SpriteBase (void); +void Cmd_Base (void); +void Cmd_Cd (void); +void Cmd_Origin (void); +void Cmd_ScaleUp (void); +void Cmd_Frame (void); +void Cmd_Modelname (void); +void Cmd_SpriteShader(void); +void Cmd_Skin(void); +void Cmd_Skinsize (void); +void FinishModel (int type); + +void Cmd_Grab (void); +void Cmd_Raw (void); +void Cmd_Mip (void); +void Cmd_Environment (void); +void Cmd_Colormap (void); + +void Cmd_File (void); +void Cmd_Dir (void); +void Cmd_StartWad (void); +void Cmd_EndWad (void); +void Cmd_Mippal (void); +void Cmd_Mipdir (void); + +void Cmd_Video (void); + +void ReleaseFile (char *filename); +void ReleaseShader( char *filename ); + +void Convert3DStoMD3( const char *filename ); + +void OrderMesh( int input[][3], int output[][3], int numTris ); + +extern byte *byteimage, *lbmpalette; +extern int byteimagewidth, byteimageheight; + +extern qboolean g_release; // don't grab, copy output data to new tree +extern char g_releasedir[1024]; // c:\quake2\baseq2, etc +extern qboolean g_archive; // don't grab, copy source data to new tree +extern qboolean do3ds; +extern char g_only[256]; // if set, only grab this cd +extern qboolean g_skipmodel; // set true when a cd is not g_only +extern qboolean g_verbose; + +extern char *trifileext; + +#define TYPE_ITEM 0 +#define TYPE_PLAYER 1 +#define TYPE_WEAPON 2 +#define TYPE_HAND 3 +#define TYPE_UNKNOWN 4 diff --git a/tools/quake3/q3data/q3data.vcproj b/tools/quake3/q3data/q3data.vcproj new file mode 100644 index 00000000..f5a0e163 --- /dev/null +++ b/tools/quake3/q3data/q3data.vcproj @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/quake3/q3data/stripper.c b/tools/quake3/q3data/stripper.c new file mode 100644 index 00000000..76fc96b3 --- /dev/null +++ b/tools/quake3/q3data/stripper.c @@ -0,0 +1,303 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include + +static int s_used[8192]; // same as MD3_MAX_TRIANGLES + +/* +** FindNextTriangleInStrip +** +** Given a surface and triangle this tries to find the next triangle +** in the strip that would continue the strip. The next triangle in +** the strip should have the same winding as this triangle. +*/ +static int FindNextTriangleInStripOrFan( int mesh[][3], int tri, int orientation, int numTris, int odd ) +{ + int t; + int sum = 0; + int currentTri[3]; + int side; + int a, b, c; + int refa, refb; + + currentTri[0] = mesh[tri][(0+orientation)%3]; + currentTri[1] = mesh[tri][(1+orientation)%3]; + currentTri[2] = mesh[tri][(2+orientation)%3]; + + if ( odd ) + { + refa = currentTri[1]; + refb = currentTri[2]; + } + else + { + refa = currentTri[2]; + refb = currentTri[0]; + } + + // go through all triangles and look for sides that match + // this triangle's + for ( t = 0; t < numTris; t++ ) + { + // don't check against self or against previously used triangles + if ( t == tri ) + continue; + if ( s_used[t] ) + continue; + + // check all three sides of the candidate triangle + for ( side = 0; side < 3; side++ ) + { + // check only the second (abutting) side + if ( ( refa == mesh[t][(side+1)%3] ) && + ( refb == mesh[t][side] ) ) + { + + a = mesh[t][0]; + b = mesh[t][1]; + c = mesh[t][2]; + + // rotate the candidate triangle to align it properly in the strip + if ( side == 1 ) + { + mesh[t][0] = b; + mesh[t][1] = c; + mesh[t][2] = a; + } + else if ( side == 2 ) + { + mesh[t][0] = c; + mesh[t][1] = a; + mesh[t][2] = b; + } + + return t; + } +/* + else + { + Error( "fans not implemented yet" ); + + // check only the third (abutting) side + if ( ( currentTri[2] == pSurf->baseTriangles[t].v[side].index ) && + ( currentTri[0] == pSurf->baseTriangles[t].v[(side+1)%3].index ) ) + { + return t; + } + } +*/ + } + } + + return -1; +} + +/* +** StripLength +*/ +static int StripLength( int mesh[][3], int strip[][3], int tri, int orientation, int numInputTris, int fillNo ) +{ + int stripIndex = 0; + int next; + + int odd = 1; + + strip[stripIndex][0] = mesh[tri][(0+orientation)%3]; + strip[stripIndex][1] = mesh[tri][(1+orientation)%3]; + strip[stripIndex][2] = mesh[tri][(2+orientation)%3]; + s_used[tri] = fillNo; + stripIndex++; + + next = tri; + + while ( ( next = FindNextTriangleInStripOrFan( mesh, next, orientation, numInputTris, odd ) ) != -1 ) + { + s_used[next] = fillNo; + odd = !odd; + strip[stripIndex][0] = mesh[next][0]; + strip[stripIndex][1] = mesh[next][1]; + strip[stripIndex][2] = mesh[next][2]; + stripIndex++; + + // all iterations after first need to be with an unrotated reference triangle + orientation = 0; + } + + return stripIndex; +} + +/* +** BuildOptimizedList +** +** Attempts to build the longest strip/fan possible. Does not adhere +** to pure strip or fan, will intermix between the two so long as some +** type of connectivity can be maintained. +*/ +#define MAX_ORIENTATIONS 3 +#define MAX_MATCHED_SIDES 4 +#define MAX_SEED_TRIANGLES 16 + +static int BuildOptimizedList( int mesh[][3], int strip[][3], int numInputTris ) +{ + int t; + int stripLen = 0; + int startTri = -1; + int bestTri = -1, bestLength = 0, bestOrientation = -1; + int matchedSides = 0; + int orientation = 0; + int seedTriangles[MAX_MATCHED_SIDES][MAX_SEED_TRIANGLES]; + int seedLengths[MAX_ORIENTATIONS][MAX_MATCHED_SIDES][MAX_SEED_TRIANGLES]; + int numSeeds[MAX_MATCHED_SIDES] = { 0, 0, 0 }; + int i; + + // build a ranked list of candidate seed triangles based on + // number of offshoot strips. Precedence goes to orphans, + // then corners, then edges, and interiors. + memset( seedTriangles, 0xff, sizeof( seedTriangles ) ); + memset( seedLengths, 0xff, sizeof( seedLengths ) ); + + for ( i = 0; i < MAX_MATCHED_SIDES; i++ ) + { + // find the triangle with lowest number of child strips + for ( t = 0; t < numInputTris; t++ ) + { + int orientation; + int n; + + if ( s_used[t] ) + continue; + + // try the candidate triangle in three different orientations + matchedSides = 0; + for ( orientation = 0; orientation < 3; orientation++ ) + { + if ( ( n = FindNextTriangleInStripOrFan( mesh, t, orientation, numInputTris, 1 ) ) != -1 ) + { + matchedSides++; + } + } + + if ( matchedSides == i ) + { + seedTriangles[i][numSeeds[i]] = t; + numSeeds[i]++; + if ( numSeeds[i] == MAX_SEED_TRIANGLES ) + break; + } + } + } + + // we have a list of potential seed triangles, so we now go through each + // potential candidate and look to see which produces the longest strip + // and select our startTri based on this + for ( i = 0; i < MAX_MATCHED_SIDES; i++ ) + { + int j; + + for ( j = 0; j < numSeeds[i]; j++ ) + { + for ( orientation = 0; orientation < 3; orientation++ ) + { + int k; + + seedLengths[orientation][i][j] = StripLength( mesh, strip, seedTriangles[i][j], orientation, numInputTris, 2 ); + + if ( seedLengths[orientation][i][j] > bestLength ) + { + bestTri = seedTriangles[i][j]; + bestLength = seedLengths[orientation][i][j]; + bestOrientation = orientation; + } + + for ( k = 0; k < numInputTris; k++ ) + { + if ( s_used[k] == 2 ) + s_used[k] = 0; + } + } + } + + if ( bestTri != -1 ) + { + break; + } + } + + // build the strip for real + if ( bestTri != -1 ) + { + stripLen = StripLength( mesh, strip, bestTri, bestOrientation, numInputTris, 1 ); + } + + return stripLen; +} + +/* +** OrderMesh +** +** Given an input mesh and an output mesh, this routine will reorder +** the triangles within the mesh into strips/fans. +*/ +void OrderMesh( int input[][3], int output[][3], int numTris ) +{ + int i; + int sumStrippedTriangles = 0; + int strippedTriangles; + int totalStrips = 0; + int strip[8192][3]; // could dump directly into 'output', but + // this helps with debugging + + memset( s_used, 0, sizeof( s_used ) ); + +#if 0 + FILE *fp = fopen( "strip.txt", "wt" ); + + for ( i = 0; i < numTris; i++ ) + { + fprintf( fp, "%4d: %3d %3d %3d\n", i, input[i][0], input[i][1], input[i][2] ); + } + fclose( fp ); +#endif + + // while there are still triangles that are not part of a strip + while ( sumStrippedTriangles < numTris ) + { + // build a strip + strippedTriangles = BuildOptimizedList( input, strip, numTris ); + + for ( i = 0; i < strippedTriangles; i++ ) + { + output[sumStrippedTriangles+i][0] = strip[i][0]; + output[sumStrippedTriangles+i][1] = strip[i][1]; + output[sumStrippedTriangles+i][2] = strip[i][2]; + } + + sumStrippedTriangles += strippedTriangles; + totalStrips++; + } + + printf( "Triangles on surface: %d\n", sumStrippedTriangles ); + printf( "Total strips from surface: %d\n", totalStrips ); + printf( "Average strip length: %f\n", ( float ) sumStrippedTriangles / totalStrips ); +} diff --git a/tools/quake3/q3data/video.c b/tools/quake3/q3data/video.c new file mode 100644 index 00000000..afabdeb9 --- /dev/null +++ b/tools/quake3/q3data/video.c @@ -0,0 +1,1153 @@ +/* +Copyright (C) 1999-2006 Id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include "q3data.h" + +static int s_resample_width = 256; +static int s_resample_height = 256; + +#define OUTPUT_TGAS 1 + +#define UNCOMPRESSED 0 +#define BTC_COMPRESSION 1 + +static int s_compression_method = BTC_COMPRESSION; + +static const char *CIN_EXTENSION = "cn2"; +static const int CIN_SIGNATURE = ( 'C' << 24 ) | ( 'I' << 16 ) | ( 'N' << 8 ) | ( '2' ); + +static byte *s_soundtrack; +static char s_base[32]; +static char s_output_base[32]; + +/* +=============================================================================== + +WAV loading + +=============================================================================== +*/ + +typedef struct +{ + int rate; + int width; + int channels; + int loopstart; + int samples; + int dataofs; // chunk starts this many bytes from file start +} wavinfo_t; + + +byte *data_p; +byte *iff_end; +byte *last_chunk; +byte *iff_data; +int iff_chunk_len; + + +static int s_samplecounts[0x10000]; +static wavinfo_t s_wavinfo; + +short GetLittleShort(void) +{ + short val = 0; + val = *data_p; + val = val + (*(data_p+1)<<8); + data_p += 2; + return val; +} + +int GetLittleLong(void) +{ + int val = 0; + val = *data_p; + val = val + (*(data_p+1)<<8); + val = val + (*(data_p+2)<<16); + val = val + (*(data_p+3)<<24); + data_p += 4; + return val; +} + +void FindNextChunk(char *name) +{ + while (1) + { + data_p=last_chunk; + + if (data_p >= iff_end) + { // didn't find the chunk + data_p = NULL; + return; + } + + data_p += 4; + iff_chunk_len = GetLittleLong(); + if (iff_chunk_len < 0) + { + data_p = NULL; + return; + } +// if (iff_chunk_len > 1024*1024) +// Sys_Error ("FindNextChunk: %i length is past the 1 meg sanity limit", iff_chunk_len); + data_p -= 8; + last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 ); + if (!strncmp(data_p, name, 4)) + return; + } +} + +void FindChunk(char *name) +{ + last_chunk = iff_data; + FindNextChunk (name); +} + + +void DumpChunks(void) +{ + char str[5]; + + str[4] = 0; + data_p=iff_data; + do + { + memcpy (str, data_p, 4); + data_p += 4; + iff_chunk_len = GetLittleLong(); + printf ("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len); + data_p += (iff_chunk_len + 1) & ~1; + } while (data_p < iff_end); +} + +/* +============ +GetWavinfo +============ +*/ +wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength) +{ + wavinfo_t info; + int i; + int format; + int samples; + + memset (&info, 0, sizeof(info)); + + if (!wav) + return info; + + iff_data = wav; + iff_end = wav + wavlength; + +// find "RIFF" chunk + FindChunk("RIFF"); + if (!(data_p && !strncmp(data_p+8, "WAVE", 4))) + { + printf("Missing RIFF/WAVE chunks\n"); + return info; + } + +// get "fmt " chunk + iff_data = data_p + 12; +// DumpChunks (); + + FindChunk("fmt "); + if (!data_p) + { + printf("Missing fmt chunk\n"); + return info; + } + data_p += 8; + format = GetLittleShort(); + if (format != 1) + { + printf("Microsoft PCM format only\n"); + return info; + } + + info.channels = GetLittleShort(); + info.rate = GetLittleLong(); + data_p += 4+2; + info.width = GetLittleShort() / 8; + +// get cue chunk + FindChunk("cue "); + if (data_p) + { + data_p += 32; + info.loopstart = GetLittleLong(); +// Com_Printf("loopstart=%d\n", sfx->loopstart); + + // if the next chunk is a LIST chunk, look for a cue length marker + FindNextChunk ("LIST"); + if (data_p) + { + if (!strncmp (data_p + 28, "mark", 4)) + { // this is not a proper parse, but it works with cooledit... + data_p += 24; + i = GetLittleLong (); // samples in loop + info.samples = info.loopstart + i; + } + } + } + else + info.loopstart = -1; + +// find data chunk + FindChunk("data"); + if (!data_p) + { + printf("Missing data chunk\n"); + return info; + } + + data_p += 4; + samples = GetLittleLong (); + + if (info.samples) + { + if (samples < info.samples) + Error ("Sound %s has a bad loop length", name); + } + else + info.samples = samples; + + info.dataofs = data_p - wav; + + return info; +} + +//===================================================================== + +/* +============== +LoadSoundtrack +============== +*/ +void LoadSoundtrack (void) +{ + char name[1024]; + FILE *f; + int len; + int i, val, j; + + s_soundtrack = NULL; + sprintf (name, "%svideo/%s/%s.wav", gamedir, s_base, s_base); + printf ("WAV: %s\n", name); + f = fopen (name, "rb"); + if (!f) + { + printf ("no soundtrack for %s\n", s_base); + return; + } + len = Q_filelength(f); + s_soundtrack = malloc(len); + fread (s_soundtrack, 1, len, f); + fclose (f); + + s_wavinfo = GetWavinfo (name, s_soundtrack, len); + + // count samples for compression + memset (s_samplecounts, 0, sizeof(s_samplecounts)); + + j = s_wavinfo.samples/2; + for (i=0 ; i s_wavinfo.samples || !s_soundtrack) + fwrite (&empty, 1, width, output); + else + fwrite (s_soundtrack + s_wavinfo.dataofs + sample*width, 1, width,output); + } +} + +//========================================================================== + +static float s_resampleXRatio; +static float s_resampleYRatio; + +static void BoxFilterHorizontalElements( unsigned char *dst, unsigned char *src, float s0, float s1 ) +{ + float w; + float rSum = 0, gSum = 0, bSum = 0; + float x = s0; + float sumWeight = 0; + + for ( x = s0; x < s1; x++, src += 4 ) + { + if ( x == s0 ) + { + w = ( int ) ( s0 + 1 ) - x; + } + else if ( x + 1 >= s1 ) + { + w = s1 - ( int ) x; + } + else + { + w = 1.0f; + } + + rSum += src[0] * w; + gSum += src[1] * w; + bSum += src[2] * w; + sumWeight += w; + } + + rSum /= sumWeight; + gSum /= sumWeight; + bSum /= sumWeight; + + dst[0] = ( unsigned char ) ( rSum + 0.5 ); + dst[1] = ( unsigned char ) ( gSum + 0.5 ); + dst[2] = ( unsigned char ) ( bSum + 0.5 ); +} + +static void BoxFilterVerticalElements( unsigned char *dst, // destination of the filter process + unsigned char *src, // source pixels + int srcStep, // stride of the source pixels + float s0, float s1 ) +{ + float w; + float rSum = 0, gSum = 0, bSum = 0; + float y = s0; + float sumWeight = 0; + + for ( y = s0; y < ( int ) ( s1 + 1 ) ; y++, src += srcStep ) + { + if ( y == s0 ) + { + w = ( int ) ( s0 + 1 ) - y; + } + else if ( y + 1 >= s1 ) + { + w = s1 - ( int ) y; + } + else + { + w = 1.0f; + } + + rSum += src[0] * w; + gSum += src[1] * w; + bSum += src[2] * w; + sumWeight += w; + } + + rSum /= sumWeight; + gSum /= sumWeight; + bSum /= sumWeight; + + dst[0] = ( unsigned char ) ( rSum + 0.5 ); + dst[1] = ( unsigned char ) ( gSum + 0.5 ); + dst[2] = ( unsigned char ) ( bSum + 0.5 ); + dst[3] = 0xff; + +} + +static void BoxFilterRow( unsigned char *dstStart, cblock_t *in, int dstRow, int rowWidth ) +{ + int i; + unsigned char *indata = ( unsigned char * ) in->data; + + indata += 4 * dstRow * in->width; + + for ( i = 0; i < rowWidth; i++ ) + { + float c0 = i * s_resampleXRatio; + float c1 = ( i + 1 ) * s_resampleXRatio; + + BoxFilterHorizontalElements( &dstStart[i*4], &indata[( ( int ) c0 ) * 4], c0, c1 ); + } +} + +static void BoxFilterColumn( unsigned char *dstStart, unsigned char *srcStart, int dstCol, int dstRowWidth, int dstColHeight, int srcRowWidthInPels ) +{ + float c0, c1; + int i; + + for ( i = 0; i < dstColHeight; i++ ) + { + c0 = i * s_resampleYRatio; + c1 = ( i + 1 ) * s_resampleYRatio; + + BoxFilterVerticalElements( &dstStart[i*4*dstRowWidth], &srcStart[(int)c0*srcRowWidthInPels*4], srcRowWidthInPels*4, c0, c1 ); + } +} + +#define DROP_SAMPLE 0 +#define BOX_FILTER 1 + +static void ResampleFrame( cblock_t *in, unsigned char *out, int method, int outWidth, int outHeight ) +{ + int row, column; + unsigned char *indata = ( unsigned char * ) in->data; + + s_resampleXRatio = in->width / ( float ) outWidth; + s_resampleYRatio = in->height / ( float ) outHeight; + + if ( method == DROP_SAMPLE ) + { + for ( row = 0; row < outHeight; row++ ) + { + int r = ( int ) ( row * s_resampleYRatio ); + + for ( column = 0; column < outWidth; column++ ) + { + int c = ( int ) ( column * s_resampleXRatio ); + + out[(row*outWidth+column)*4+0] = indata[(r*in->width+c)*4+0]; + out[(row*outWidth+column)*4+1] = indata[(r*in->width+c)*4+1]; + out[(row*outWidth+column)*4+2] = indata[(r*in->width+c)*4+2]; + out[(row*outWidth+column)*4+3] = 0xff; + } + } + } + else if ( method == BOX_FILTER ) + { + unsigned char intermediate[1024*1024*4]; + + assert( in->height <= 1024 ); + assert( in->width <= 1024 ); + + // + // filter our M x N source image into a RESAMPLE_WIDTH x N horizontally filtered image + // + for ( row = 0; row < in->height; row++ ) + { + BoxFilterRow( &intermediate[row*4*outWidth], in, row, outWidth ); + } + + // + // filter our RESAMPLE_WIDTH x N horizontally filtered image into a RESAMPLE_WIDTH x RESAMPLE_HEIGHT filtered image + // + for ( column = 0; column < outWidth; column++ ) + { + BoxFilterColumn( &out[column*4], &intermediate[column*4], column, outWidth, outHeight, s_resample_width ); + } + } +} + +static float BTCDistanceSquared( float a[3], float b[3] ) +{ + return ( b[0] - a[0] ) * ( b[0] - a[0] ) + + ( b[1] - a[1] ) * ( b[1] - a[1] ) + + ( b[2] - a[2] ) * ( b[2] - a[2] ); +} + +static void BTCFindEndpoints( float inBlock[4][4][3], unsigned int endPoints[2][2] ) +{ + float longestDistance = -1; + + int bX, bY; + + // + // find the two points farthest from each other + // + for ( bY = 0; bY < 4; bY++ ) + { + for ( bX = 0; bX < 4; bX++ ) + { + int cX, cY; + float d; + + // + // check the rest of the current row + // + for ( cX = bX + 1; cX < 4; cX++ ) + { + if ( ( d = BTCDistanceSquared( inBlock[bY][bX], inBlock[bY][cX] ) ) > longestDistance ) + { + longestDistance = d; + endPoints[0][0] = bX; + endPoints[0][1] = bY; + endPoints[1][0] = cX; + endPoints[1][1] = bY; + } + } + + // + // check remaining rows and columns + // + for ( cY = bY+1; cY < 4; cY++ ) + { + for ( cX = 0; cX < 4; cX++ ) + { + if ( ( d = BTCDistanceSquared( inBlock[bY][bX], inBlock[cY][cX] ) ) > longestDistance ) + { + longestDistance = d; + endPoints[0][0] = bX; + endPoints[0][1] = bY; + endPoints[1][0] = cX; + endPoints[1][1] = cY; + } + } + } + } + } +} + +static float BTCQuantizeBlock( float inBlock[4][4][3], unsigned long endPoints[2][2], int btcQuantizedBlock[4][4], float bestError ) +{ + int i; + int blockY, blockX; + float dR, dG, dB; + float R, G, B; + float error = 0; + float colorLine[4][3]; + + // + // build the color line + // + dR = inBlock[endPoints[1][1]][endPoints[1][0]][0] - + inBlock[endPoints[0][1]][endPoints[0][0]][0]; + dG = inBlock[endPoints[1][1]][endPoints[1][0]][1] - + inBlock[endPoints[0][1]][endPoints[0][0]][1]; + dB = inBlock[endPoints[1][1]][endPoints[1][0]][2] - + inBlock[endPoints[0][1]][endPoints[0][0]][2]; + + dR *= 0.33f; + dG *= 0.33f; + dB *= 0.33f; + + R = inBlock[endPoints[0][1]][endPoints[0][0]][0]; + G = inBlock[endPoints[0][1]][endPoints[0][0]][1]; + B = inBlock[endPoints[0][1]][endPoints[0][0]][2]; + + for ( i = 0; i < 4; i++ ) + { + colorLine[i][0] = R; + colorLine[i][1] = G; + colorLine[i][2] = B; + + R += dR; + G += dG; + B += dB; + } + + // + // quantize each pixel into the appropriate range + // + for ( blockY = 0; blockY < 4; blockY++ ) + { + for ( blockX = 0; blockX < 4; blockX++ ) + { + float distance = 10000000000; + int shortest = -1; + + for ( i = 0; i < 4; i++ ) + { + float d; + + if ( ( d = BTCDistanceSquared( inBlock[blockY][blockX], colorLine[i] ) ) < distance ) + { + distance = d; + shortest = i; + } + } + + error += distance; + + // + // if bestError is not -1 then that means this is a speculative quantization + // + if ( bestError != -1 ) + { + if ( error > bestError ) + return error; + } + + btcQuantizedBlock[blockY][blockX] = shortest; + } + } + + return error; +} + +/* +** float BTCCompressBlock +*/ +static float BTCCompressBlock( float inBlock[4][4][3], unsigned long out[2] ) +{ + int i; + int btcQuantizedBlock[4][4]; // values should be [0..3] + unsigned long encodedEndPoints, encodedBitmap; + unsigned int endPoints[2][2]; // endPoints[0] = color start, endPoints[1] = color end + int blockY, blockX; + float error = 0; + float bestError = 10000000000; + unsigned int bestEndPoints[2][2]; + +#if 0 + // + // find the "ideal" end points for the color vector + // + BTCFindEndpoints( inBlock, endPoints ); + error = BTCQuantizeBlock( inBlock, endPoints, btcQuantizedBlock ); + memcpy( bestEndPoints, endPoints, sizeof( bestEndPoints ) ); +#else + for ( blockY = 0; blockY < 4; blockY++ ) + { + for ( blockX = 0; blockX < 4; blockX++ ) + { + int x2, y2; + + for ( y2 = 0; y2 < 4; y2++ ) + { + for ( x2 = 0; x2 < 4; x2++ ) + { + if ( ( x2 == blockX ) && ( y2 == blockY ) ) + continue; + + endPoints[0][0] = blockX; + endPoints[0][1] = blockY; + endPoints[1][0] = x2; + endPoints[1][1] = y2; + + error = BTCQuantizeBlock( inBlock, endPoints, btcQuantizedBlock, -1 ); //bestError ); + + if ( error < bestError ) + { + bestError = error; + memcpy( bestEndPoints, endPoints, sizeof( bestEndPoints ) ); + } + } + } + } + } + + error = BTCQuantizeBlock( inBlock, bestEndPoints, btcQuantizedBlock, -1.0f ); +#endif + + // + // encode the results + // + encodedBitmap = 0; + for ( blockY = 0; blockY < 4; blockY++ ) + { + for ( blockX = 0; blockX < 4; blockX++ ) + { + int shift = ( blockX + blockY * 4 ) * 2; + encodedBitmap |= btcQuantizedBlock[blockY][blockX] << shift; + } + } + + // + // encode endpoints + // + encodedEndPoints = 0; + for ( i = 0; i < 2; i++ ) + { + int iR, iG, iB; + + iR = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][0] ); + if ( iR > 255 ) + iR = 255; + else if ( iR < 0 ) + iR = 0; + iR >>= 3; + + iG = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][1] ); + if ( iG > 255 ) + iG = 255; + else if ( iG < 0 ) + iG = 0; + iG >>= 2; + + iB = ( ( int ) inBlock[bestEndPoints[i][1]][bestEndPoints[i][0]][2] ); + if ( iB > 255 ) + iB = 255; + else if ( iB < 0 ) + iB = 0; + iB >>= 3; + + + encodedEndPoints |= ( ( ( iR << 11 ) | ( iG << 5 ) | ( iB ) ) << ( i * 16 ) ); + } + + // + // store + // + out[0] = encodedBitmap; + out[1] = encodedEndPoints; + + return error; +} + +/* +** void BTCDecompressFrame +*/ +static void BTCDecompressFrame( unsigned long *src, unsigned char *dst ) +{ + int x, y; + int iR, iG, iB; + int dstX, dstY; + float colorStart[3], colorEnd[3]; + unsigned char colorRampABGR[4][4]; + unsigned encoded; + + memset( colorRampABGR, 0xff, sizeof( colorRampABGR ) ); + + for ( y = 0; y < s_resample_height / 4; y++ ) + { + for ( x = 0; x < s_resample_width / 4; x++ ) + { + unsigned colorStartPacked = src[(y*s_resample_width/4 + x)*2 + 1] & 0xffff; + unsigned colorEndPacked = src[(y*s_resample_width/4 + x)*2 + 1] >> 16; + + // + // grab the end points + // 0 = color start + // 1 = color end + // + iR = ( ( colorStartPacked >> 11 ) & ( ( 1 << 5 ) - 1 ) ); + iR = ( iR << 3 ) | ( iR >> 2 ); + iG = ( ( colorStartPacked >> 5 ) & ( ( 1 << 6 ) - 1 ) ); + iG = ( iG << 2 ) | ( iG >> 4 ); + iB = ( ( colorStartPacked ) & ( ( 1 << 5 ) - 1 ) ); + iB = ( iB << 3 ) | ( iB >> 2 ); + + colorStart[0] = iR; + colorStart[1] = iG; + colorStart[2] = iB; + colorRampABGR[0][0] = iR; + colorRampABGR[0][1] = iG; + colorRampABGR[0][2] = iB; + + iR = ( ( colorEndPacked >> 11 ) & ( ( 1 << 5 ) - 1 ) ); + iR = ( iR << 3 ) | ( iR >> 2 ); + iG = ( ( colorEndPacked >> 5 ) & ( ( 1 << 6 ) - 1 ) ); + iG = ( iG << 2 ) | ( iG >> 4 ); + iB = ( colorEndPacked & ( ( 1 << 5 ) - 1 ) ); + iB = ( iB << 3 ) | ( iB >> 2 ); + + colorEnd[0] = iR; + colorEnd[1] = iG; + colorEnd[2] = iB; + colorRampABGR[3][0] = iR; + colorRampABGR[3][1] = iG; + colorRampABGR[3][2] = iB; + + // + // compute this block's color ramp + // FIXME: This needs to be reversed on big-endian machines + // + + colorRampABGR[1][0] = colorStart[0] * 0.66f + colorEnd[0] * 0.33f; + colorRampABGR[1][1] = colorStart[1] * 0.66f + colorEnd[1] * 0.33f; + colorRampABGR[1][2] = colorStart[2] * 0.66f + colorEnd[2] * 0.33f; + + colorRampABGR[2][0] = colorStart[0] * 0.33f + colorEnd[0] * 0.66f; + colorRampABGR[2][1] = colorStart[1] * 0.33f + colorEnd[1] * 0.66f; + colorRampABGR[2][2] = colorStart[2] * 0.33f + colorEnd[2] * 0.66f; + + // + // decode the color data + // information is encoded in 2-bit pixels, with low order bits corresponding + // to upper left pixels. These 2-bit values are indexed into the block's + // computer color ramp. + // + encoded = src[(y*s_resample_width/4 + x)*2 + 0]; + + for ( dstY = 0; dstY < 4; dstY++ ) + { + for ( dstX = 0; dstX < 4; dstX++ ) + { + memcpy( &dst[(y*4+dstY)*s_resample_width*4+x*4*4+dstX*4], colorRampABGR[encoded&3], sizeof( colorRampABGR[0] ) ); + encoded >>= 2; + } + } + } + } +} + +/* +** BTCCompressFrame +** +** Perform a BTC compression using a 2-bit encoding at each pixel. This +** compression method is performed by decomposing the incoming image into +** a sequence of 4x4 blocks. At each block two color values are computed +** that define the endpoints of a vector in color space that represent +** the two colors "farthest apart". +*/ +static float BTCCompressFrame( unsigned char *src, unsigned long *dst ) +{ + int x, y; + int bX, bY; + float btcBlock[4][4][3]; + + float error = 0; + + for ( y = 0; y < s_resample_height / 4; y++ ) + { + for ( x = 0; x < s_resample_width / 4; x++ ) + { + // + // fill in the BTC block with raw values + // + for ( bY = 0; bY < 4; bY++ ) + { + for ( bX = 0; bX < 4; bX++ ) + { + btcBlock[bY][bX][0] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 0]; + btcBlock[bY][bX][1] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 1]; + btcBlock[bY][bX][2] = src[(y*4+bY)*s_resample_width*4 + (x*4+bX)*4 + 2]; + } + } + + error += BTCCompressBlock( btcBlock, &dst[(y*s_resample_width/4+x)*2] ); + } + } + + return error / ( ( s_resample_width / 4 ) * ( s_resample_height / 4 ) ); +} + +/* +=================== +LoadFrame +=================== +*/ +cblock_t LoadFrame (char *base, int frame, int digits, byte **palette) +{ + int ten3, ten2, ten1, ten0; + cblock_t in; + int width, height; + char name[1024]; + FILE *f; + + in.data = NULL; + in.count = -1; + + ten3 = frame/1000; + ten2 = (frame-ten3*1000)/100; + ten1 = (frame-ten3*1000-ten2*100)/10; + ten0 = frame%10; + + if (digits == 4) + sprintf (name, "%svideo/%s/%s%i%i%i%i.tga", gamedir, base, base, ten3, ten2, ten1, ten0); + else + sprintf (name, "%svideo/%s/%s%i%i%i.tga", gamedir, base, base, ten2, ten1, ten0); + + f = fopen(name, "rb"); + if (!f) + { + in.data = NULL; + return in; + } + fclose (f); + + printf ("%s", name); + LoadTGA( name, ( unsigned char ** ) &in.data, &width, &height ); + if ( palette ) + *palette = 0; +// Load256Image (name, &in.data, palette, &width, &height); + in.count = width*height; + in.width = width; + in.height = height; +// FIXME: map 0 and 255! + +#if 0 + // rle compress + rle = RLE(in); + free (in.data); + + return rle; +#endif + + return in; +} + +/* +=============== +Cmd_Video + +video +=============== +*/ +void Cmd_Video (void) +{ + float sumError = 0, error = 0, maxError = 0; + char savename[1024]; + char name[1024]; + FILE *output; + int startframe, frame; + int width, height; + int i; + int digits; + int minutes; + float fseconds; + int remSeconds; + cblock_t in; + unsigned char *resampled; + unsigned long *compressed; + clock_t start, stop; + + GetToken (qfalse); + strcpy (s_base, token); + if (g_release) + { +// sprintf (savename, "video/%s.cin", token); +// ReleaseFile (savename); + return; + } + + GetToken( qfalse ); + strcpy( s_output_base, token ); + + GetToken (qfalse); + digits = atoi(token); + + GetToken( qfalse ); + + if ( !strcmp( token, "btc" ) ) + { + s_compression_method = BTC_COMPRESSION; + printf( "Compression: BTC\n" ); + } + else if ( !strcmp( token, "uc" ) ) + { + s_compression_method = UNCOMPRESSED; + printf( "Compression: none\n" ); + } + else + { + Error( "Uknown compression method '%s'\n", token ); + } + + GetToken( qfalse ); + s_resample_width = atoi( token ); + + GetToken( qfalse ); + s_resample_height = atoi( token ); + + resampled = malloc( sizeof( unsigned char ) * 4 * s_resample_width * s_resample_height ); + compressed = malloc( sizeof( long ) * 2 * ( s_resample_width / 4 ) * ( s_resample_height / 4 ) ); + + printf( "Resample width: %d\n", s_resample_width ); + printf( "Resample height: %d\n", s_resample_height ); + + // optionally skip frames + if (TokenAvailable ()) + { + GetToken (qfalse); + startframe = atoi(token); + } + else + startframe=0; + + sprintf (savename, "%svideo/%s.%s", writedir, s_output_base, CIN_EXTENSION ); + + // load the entire sound wav file if present + LoadSoundtrack (); + + if (digits == 4) + sprintf (name, "%svideo/%s/%s0000.tga", gamedir, s_base, s_base); + else + sprintf (name, "%svideo/%s/%s000.tga", gamedir, s_base, s_base); + + printf ("%s\n", name); + LoadTGA( name, NULL, &width, &height); + + output = fopen (savename, "wb"); + if (!output) + Error ("Can't open %s", savename); + + // write header info + i = LittleLong( CIN_SIGNATURE ); + fwrite (&i, 4, 1, output ); + i = LittleLong (s_resample_width); + fwrite (&i, 4, 1, output); + i = LittleLong (s_resample_height); + fwrite (&i, 4, 1, output); + i = LittleLong (s_wavinfo.rate); + fwrite (&i, 4, 1, output); + i = LittleLong (s_wavinfo.width); + fwrite (&i, 4, 1, output); + i = LittleLong (s_wavinfo.channels); + fwrite (&i, 4, 1, output); + i = LittleLong ( s_compression_method ); + fwrite (&i, 4, 1, output ); + + start = clock(); + + // perform compression on a per frame basis + for ( frame=startframe ; ; frame++) + { + printf ("%02d: ", frame); + in = LoadFrame (s_base, frame, digits, 0 ); + if (!in.data) + break; + + ResampleFrame( &in, ( unsigned char * ) resampled, BOX_FILTER, s_resample_width, s_resample_height ); + + if ( s_compression_method == UNCOMPRESSED ) + { + printf( "\n" ); + fwrite( resampled, 1, sizeof( unsigned char ) * s_resample_width * s_resample_height * 4, output ); + +#if OUTPUT_TGAS + { + int x, y; + char buffer[1000]; + + for ( y = 0; y < s_resample_height/2; y++ ) + { + for ( x = 0; x < s_resample_width; x++ ) + { + unsigned char tmp[4]; + + tmp[0] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0]; + tmp[1] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1]; + tmp[2] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2]; + tmp[3] = resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3]; + + resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0] = resampled[y*s_resample_width*4 + x*4 + 0]; + resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1] = resampled[y*s_resample_width*4 + x*4 + 1]; + resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2] = resampled[y*s_resample_width*4 + x*4 + 2]; + resampled[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3] = resampled[y*s_resample_width*4 + x*4 + 3]; + + resampled[y*s_resample_width*4 + x*4 + 0] = tmp[0]; + resampled[y*s_resample_width*4 + x*4 + 1] = tmp[1]; + resampled[y*s_resample_width*4 + x*4 + 2] = tmp[2]; + resampled[y*s_resample_width*4 + x*4 + 3] = tmp[3]; + } + } + + sprintf( buffer, "%svideo/%s/uc%04d.tga", gamedir, s_base, frame ); + WriteTGA( buffer, resampled, s_resample_width, s_resample_height ); + } +#endif + } + else if ( s_compression_method == BTC_COMPRESSION ) + { + error = BTCCompressFrame( resampled, compressed ); + + sumError += error; + + if ( error > maxError ) + maxError = error; + + printf( " (error = %f)\n", error ); + fwrite( compressed, 1, 2 * sizeof( long ) * ( s_resample_width / 4 ) * ( s_resample_height / 4 ), output ); + +#if OUTPUT_TGAS + { + int x, y; + unsigned char *uncompressed; + char buffer[1000]; + + uncompressed = malloc( sizeof( unsigned char ) * 4 * s_resample_width * s_resample_height ); + BTCDecompressFrame( compressed, uncompressed ); + + for ( y = 0; y < s_resample_height/2; y++ ) + { + for ( x = 0; x < s_resample_width; x++ ) + { + unsigned char tmp[4]; + + tmp[0] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0]; + tmp[1] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1]; + tmp[2] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2]; + tmp[3] = uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3]; + + uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 0] = uncompressed[y*s_resample_width*4 + x*4 + 0]; + uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 1] = uncompressed[y*s_resample_width*4 + x*4 + 1]; + uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 2] = uncompressed[y*s_resample_width*4 + x*4 + 2]; + uncompressed[(s_resample_height-1-y)*s_resample_width*4 + x*4 + 3] = uncompressed[y*s_resample_width*4 + x*4 + 3]; + + uncompressed[y*s_resample_width*4 + x*4 + 0] = tmp[0]; + uncompressed[y*s_resample_width*4 + x*4 + 1] = tmp[1]; + uncompressed[y*s_resample_width*4 + x*4 + 2] = tmp[2]; + uncompressed[y*s_resample_width*4 + x*4 + 3] = tmp[3]; + } + } + + + sprintf( buffer, "%svideo/%s/btc%04d.tga", gamedir, s_base, frame ); + WriteTGA( buffer, uncompressed, s_resample_width, s_resample_height ); + + free( uncompressed ); + } +#endif + } + + WriteSound( output, frame ); + + free (in.data); + } + stop = clock(); + + printf ("\n"); + + printf ("Total size: %i\n", ftell( output ) ); + printf ("Average error: %f\n", sumError / ( frame - startframe ) ); + printf ("Max error: %f\n", maxError ); + + fseconds = ( stop - start ) / 1000.0f; + minutes = fseconds / 60; + remSeconds = fseconds - minutes * 60; + + printf ("Total time: %d s (%d m %d s)\n", ( int ) fseconds, minutes, remSeconds ); + printf ("Time/frame: %.2f seconds\n", fseconds / ( frame - startframe ) ); + + fclose (output); + + if ( s_soundtrack ) + { + free( s_soundtrack ); + s_soundtrack = 0; + } +} diff --git a/tools/quake3/q3map2/.cvsignore b/tools/quake3/q3map2/.cvsignore new file mode 100644 index 00000000..e70c49e1 --- /dev/null +++ b/tools/quake3/q3map2/.cvsignore @@ -0,0 +1,12 @@ +q3map +*.d +*.o +*.bak +*.BAK +*~ +*.ncb +*.plg +*.opt +*.log +Debug +Release diff --git a/tools/quake3/q3map2/brush.c b/tools/quake3/q3map2/brush.c new file mode 100644 index 00000000..2517ca63 --- /dev/null +++ b/tools/quake3/q3map2/brush.c @@ -0,0 +1,970 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define BRUSH_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* ------------------------------------------------------------------------------- + +functions + +------------------------------------------------------------------------------- */ + +/* +AllocSideRef() - ydnar +allocates and assigns a brush side reference +*/ + +sideRef_t *AllocSideRef( side_t *side, sideRef_t *next ) +{ + sideRef_t *sideRef; + + + /* dummy check */ + if( side == NULL ) + return next; + + /* allocate and return */ + sideRef = safe_malloc( sizeof( *sideRef ) ); + sideRef->side = side; + sideRef->next = next; + return sideRef; +} + + + +/* +CountBrushList() +counts the number of brushes in a brush linked list +*/ + +int CountBrushList( brush_t *brushes ) +{ + int c = 0; + + + /* count brushes */ + for( ; brushes != NULL; brushes = brushes->next ) + c++; + return c; +} + + + +/* +AllocBrush() +allocates a new brush +*/ + +brush_t *AllocBrush( int numSides ) +{ + brush_t *bb; + size_t c; + + + /* allocate and clear */ + if( numSides <= 0 ) + Error( "AllocBrush called with numsides = %d", numSides ); + c = (size_t)&(((brush_t*) 0)->sides[ numSides ]); + bb = safe_malloc( c ); + memset( bb, 0, c ); + if( numthreads == 1 ) + numActiveBrushes++; + + /* return it */ + return bb; +} + + + +/* +FreeBrush() +frees a single brush and all sides/windings +*/ + +void FreeBrush( brush_t *b ) +{ + int i; + + + /* error check */ + if( *((unsigned int*) b) == 0xFEFEFEFE ) + { + Sys_FPrintf( SYS_VRB, "WARNING: Attempt to free an already freed brush!\n" ); + return; + } + + /* free brush sides */ + for( i = 0; i < b->numsides; i++ ) + if( b->sides[i].winding != NULL ) + FreeWinding( b->sides[ i ].winding ); + + /* ydnar: overwrite it */ + memset( b, 0xFE, (size_t)&(((brush_t*) 0)->sides[ b->numsides ]) ); + *((unsigned int*) b) = 0xFEFEFEFE; + + /* free it */ + free( b ); + if( numthreads == 1 ) + numActiveBrushes--; +} + + + +/* +FreeBrushList() +frees a linked list of brushes +*/ + +void FreeBrushList( brush_t *brushes ) +{ + brush_t *next; + + + /* walk brush list */ + for( ; brushes != NULL; brushes = next ) + { + next = brushes->next; + FreeBrush( brushes ); + } +} + + + +/* +CopyBrush() +duplicates the brush, sides, and windings +*/ + +brush_t *CopyBrush( brush_t *brush ) +{ + brush_t *newBrush; + size_t size; + int i; + + + /* copy brush */ + size = (size_t)&(((brush_t*) 0)->sides[ brush->numsides ]); + newBrush = AllocBrush( brush->numsides ); + memcpy( newBrush, brush, size ); + + /* ydnar: nuke linked list */ + newBrush->next = NULL; + + /* copy sides */ + for( i = 0; i < brush->numsides; i++ ) + { + if( brush->sides[ i ].winding != NULL ) + newBrush->sides[ i ].winding = CopyWinding( brush->sides[ i ].winding ); + } + + /* return it */ + return newBrush; +} + + + + +/* +BoundBrush() +sets the mins/maxs based on the windings +returns false if the brush doesn't enclose a valid volume +*/ + +qboolean BoundBrush( brush_t *brush ) +{ + int i, j; + winding_t *w; + + + ClearBounds( brush->mins, brush->maxs ); + for( i = 0; i < brush->numsides; i++ ) + { + w = brush->sides[ i ].winding; + if( w == NULL ) + continue; + for( j = 0; j < w->numpoints; j++ ) + AddPointToBounds( w->p[ j ], brush->mins, brush->maxs ); + } + + for( i = 0; i < 3; i++ ) + { + if( brush->mins[ i ] < MIN_WORLD_COORD || brush->maxs[ i ] > MAX_WORLD_COORD || brush->mins[i] >= brush->maxs[ i ] ) + return qfalse; + } + + return qtrue; +} + + + + +/* +SnapWeldVector() - ydnar +welds two vec3_t's into a third, taking into account nearest-to-integer +instead of averaging +*/ + +#define SNAP_EPSILON 0.01 + +void SnapWeldVector( vec3_t a, vec3_t b, vec3_t out ) +{ + int i; + vec_t ai, bi, outi; + + + /* dummy check */ + if( a == NULL || b == NULL || out == NULL ) + return; + + /* do each element */ + for( i = 0; i < 3; i++ ) + { + /* round to integer */ + ai = Q_rint( a[ i ] ); + bi = Q_rint( a[ i ] ); + + /* prefer exact integer */ + if( ai == a[ i ] ) + out[ i ] = a[ i ]; + else if( bi == b[ i ] ) + out[ i ] = b[ i ]; + + /* use nearest */ + else if( fabs( ai - a[ i ] ) < fabs( bi < b[ i ] ) ) + out[ i ] = a[ i ]; + else + out[ i ] = b[ i ]; + + /* snap */ + outi = Q_rint( out[ i ] ); + if( fabs( outi - out[ i ] ) <= SNAP_EPSILON ) + out[ i ] = outi; + } +} + + + +/* +FixWinding() - ydnar +removes degenerate edges from a winding +returns qtrue if the winding is valid +*/ + +#define DEGENERATE_EPSILON 0.1 + +qboolean FixWinding( winding_t *w ) +{ + qboolean valid = qtrue; + int i, j, k; + vec3_t vec; + float dist; + + + /* dummy check */ + if( !w ) + return qfalse; + + /* check all verts */ + for( i = 0; i < w->numpoints; i++ ) + { + /* don't remove points if winding is a triangle */ + if( w->numpoints == 3 ) + return valid; + + /* get second point index */ + j = (i + 1) % w->numpoints; + + /* degenerate edge? */ + VectorSubtract( w->p[ i ], w->p[ j ], vec ); + dist = VectorLength( vec ); + if( dist < DEGENERATE_EPSILON ) + { + valid = qfalse; + //Sys_FPrintf( SYS_VRB, "WARNING: Degenerate winding edge found, fixing...\n" ); + + /* create an average point (ydnar 2002-01-26: using nearest-integer weld preference) */ + SnapWeldVector( w->p[ i ], w->p[ j ], vec ); + VectorCopy( vec, w->p[ i ] ); + //VectorAdd( w->p[ i ], w->p[ j ], vec ); + //VectorScale( vec, 0.5, w->p[ i ] ); + + /* move the remaining verts */ + for( k = i + 2; k < w->numpoints; k++ ) + { + VectorCopy( w->p[ k ], w->p[ k - 1 ] ); + } + w->numpoints--; + } + } + + /* one last check and return */ + if( w->numpoints < 3 ) + valid = qfalse; + return valid; +} + + + + + + + +/* +CreateBrushWindings() +makes basewindigs for sides and mins/maxs for the brush +returns false if the brush doesn't enclose a valid volume +*/ + +qboolean CreateBrushWindings( brush_t *brush ) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + + /* walk the list of brush sides */ + for( i = 0; i < brush->numsides; i++ ) + { + /* get side and plane */ + side = &brush->sides[ i ]; + plane = &mapplanes[ side->planenum ]; + + /* make huge winding */ + w = BaseWindingForPlane( plane->normal, plane->dist ); + + /* walk the list of brush sides */ + for( j = 0; j < brush->numsides && w != NULL; j++ ) + { + if( i == j ) + continue; + if( brush->sides[ j ].planenum == (brush->sides[ i ].planenum ^ 1) ) + continue; /* back side clipaway */ + if( brush->sides[ j ].bevel ) + continue; + plane = &mapplanes[ brush->sides[ j ].planenum ^ 1 ]; + ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); // CLIP_EPSILON ); + + /* ydnar: fix broken windings that would generate trifans */ + FixWinding( w ); + } + + /* set side winding */ + side->winding = w; + } + + /* find brush bounds */ + return BoundBrush( brush ); +} + + + + +/* +================== +BrushFromBounds + +Creates a new axial brush +================== +*/ +brush_t *BrushFromBounds (vec3_t mins, vec3_t maxs) +{ + brush_t *b; + int i; + vec3_t normal; + vec_t dist; + + b = AllocBrush (6); + b->numsides = 6; + for (i=0 ; i<3 ; i++) + { + VectorClear (normal); + normal[i] = 1; + dist = maxs[i]; + b->sides[i].planenum = FindFloatPlane (normal, dist, 1, (vec3_t*) &maxs ); + + normal[i] = -1; + dist = -mins[i]; + b->sides[3+i].planenum = FindFloatPlane (normal, dist, 1, (vec3_t*) &mins ); + } + + CreateBrushWindings (b); + + return b; +} + +/* +================== +BrushVolume + +================== +*/ +vec_t BrushVolume (brush_t *brush) +{ + int i; + winding_t *w; + vec3_t corner; + vec_t d, area, volume; + plane_t *plane; + + if (!brush) + return 0; + + // grab the first valid point as the corner + + w = NULL; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (w) + break; + } + if (!w) + return 0; + VectorCopy (w->p[0], corner); + + // make tetrahedrons to all other faces + + volume = 0; + for ( ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + plane = &mapplanes[brush->sides[i].planenum]; + d = -(DotProduct (corner, plane->normal) - plane->dist); + area = WindingArea (w); + volume += d*area; + } + + volume /= 3; + return volume; +} + + + +/* +WriteBSPBrushMap() +writes a map with the split bsp brushes +*/ + +void WriteBSPBrushMap( char *name, brush_t *list ) +{ + FILE *f; + side_t *s; + int i; + winding_t *w; + + + /* note it */ + Sys_Printf( "Writing %s\n", name ); + + /* open the map file */ + f = fopen( name, "wb" ); + if( f == NULL ) + Error( "Can't write %s\b", name ); + + fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); + + for ( ; list ; list=list->next ) + { + fprintf (f, "{\n"); + for (i=0,s=list->sides ; inumsides ; i++,s++) + { + w = BaseWindingForPlane (mapplanes[s->planenum].normal, mapplanes[s->planenum].dist); + + fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + + fprintf (f, "notexture 0 0 0 1 1\n" ); + FreeWinding (w); + } + fprintf (f, "}\n"); + } + fprintf (f, "}\n"); + + fclose (f); + +} + + + +/* +FilterBrushIntoTree_r() +adds brush reference to any intersecting bsp leafnode +*/ + +int FilterBrushIntoTree_r( brush_t *b, node_t *node ) +{ + brush_t *front, *back; + int c; + + + /* dummy check */ + if( b == NULL ) + return 0; + + /* add it to the leaf list */ + if( node->planenum == PLANENUM_LEAF ) + { + /* something somewhere is hammering brushlist */ + b->next = node->brushlist; + node->brushlist = b; + + /* classify the leaf by the structural brush */ + if( !b->detail ) + { + if( b->opaque ) + { + node->opaque = qtrue; + node->areaportal = qfalse; + } + else if( b->compileFlags & C_AREAPORTAL ) + { + if( !node->opaque ) + node->areaportal = qtrue; + } + } + + return 1; + } + + /* split it by the node plane */ + c = b->numsides; + SplitBrush( b, node->planenum, &front, &back ); + FreeBrush( b ); + + c = 0; + c += FilterBrushIntoTree_r( front, node->children[ 0 ] ); + c += FilterBrushIntoTree_r( back, node->children[ 1 ] ); + + return c; +} + + + +/* +FilterDetailBrushesIntoTree +fragment all the detail brushes into the structural leafs +*/ + +void FilterDetailBrushesIntoTree( entity_t *e, tree_t *tree ) +{ + brush_t *b, *newb; + int r; + int c_unique, c_clusters; + int i; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- FilterDetailBrushesIntoTree ---\n" ); + + /* walk the list of brushes */ + c_unique = 0; + c_clusters = 0; + for( b = e->brushes; b; b = b->next ) + { + if( !b->detail ) + continue; + c_unique++; + newb = CopyBrush( b ); + r = FilterBrushIntoTree_r( newb, tree->headnode ); + c_clusters += r; + + /* mark all sides as visible so drawsurfs are created */ + if( r ) + { + for( i = 0; i < b->numsides; i++ ) + { + if( b->sides[ i ].winding ) + b->sides[ i ].visible = qtrue; + } + } + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d detail brushes\n", c_unique ); + Sys_FPrintf( SYS_VRB, "%9d cluster references\n", c_clusters ); +} + +/* +===================== +FilterStructuralBrushesIntoTree + +Mark the leafs as opaque and areaportals +===================== +*/ +void FilterStructuralBrushesIntoTree( entity_t *e, tree_t *tree ) { + brush_t *b, *newb; + int r; + int c_unique, c_clusters; + int i; + + Sys_FPrintf (SYS_VRB, "--- FilterStructuralBrushesIntoTree ---\n"); + + c_unique = 0; + c_clusters = 0; + for ( b = e->brushes ; b ; b = b->next ) { + if ( b->detail ) { + continue; + } + c_unique++; + newb = CopyBrush( b ); + r = FilterBrushIntoTree_r( newb, tree->headnode ); + c_clusters += r; + + // mark all sides as visible so drawsurfs are created + if ( r ) { + for ( i = 0 ; i < b->numsides ; i++ ) { + if ( b->sides[i].winding ) { + b->sides[i].visible = qtrue; + } + } + } + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d structural brushes\n", c_unique ); + Sys_FPrintf( SYS_VRB, "%9d cluster references\n", c_clusters ); +} + + + +/* +================ +AllocTree +================ +*/ +tree_t *AllocTree (void) +{ + tree_t *tree; + + tree = safe_malloc(sizeof(*tree)); + memset (tree, 0, sizeof(*tree)); + ClearBounds (tree->mins, tree->maxs); + + return tree; +} + +/* +================ +AllocNode +================ +*/ +node_t *AllocNode (void) +{ + node_t *node; + + node = safe_malloc(sizeof(*node)); + memset (node, 0, sizeof(*node)); + + return node; +} + + +/* +================ +WindingIsTiny + +Returns true if the winding would be crunched out of +existance by the vertex snapping. +================ +*/ +#define EDGE_LENGTH 0.2 +qboolean WindingIsTiny (winding_t *w) +{ +/* + if (WindingArea (w) < 1) + return qtrue; + return qfalse; +*/ + int i, j; + vec_t len; + vec3_t delta; + int edges; + + edges = 0; + for (i=0 ; inumpoints ; i++) + { + j = i == w->numpoints - 1 ? 0 : i+1; + VectorSubtract (w->p[j], w->p[i], delta); + len = VectorLength (delta); + if (len > EDGE_LENGTH) + { + if (++edges == 3) + return qfalse; + } + } + return qtrue; +} + +/* +================ +WindingIsHuge + +Returns true if the winding still has one of the points +from basewinding for plane +================ +*/ +qboolean WindingIsHuge (winding_t *w) +{ + int i, j; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + if (w->p[i][j] <= MIN_WORLD_COORD || w->p[i][j] >= MAX_WORLD_COORD) + return qtrue; + } + return qfalse; +} + +//============================================================ + +/* +================== +BrushMostlyOnSide + +================== +*/ +int BrushMostlyOnSide (brush_t *brush, plane_t *plane) +{ + int i, j; + winding_t *w; + vec_t d, max; + int side; + + max = 0; + side = PSIDE_FRONT; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > max) + { + max = d; + side = PSIDE_FRONT; + } + if (-d > max) + { + max = -d; + side = PSIDE_BACK; + } + } + } + return side; +} + + + +/* +SplitBrush() +generates two new brushes, leaving the original unchanged +*/ + +void SplitBrush( brush_t *brush, int planenum, brush_t **front, brush_t **back ) +{ + brush_t *b[2]; + int i, j; + winding_t *w, *cw[2], *midwinding; + plane_t *plane, *plane2; + side_t *s, *cs; + float d, d_front, d_back; + + + *front = NULL; + *back = NULL; + plane = &mapplanes[planenum]; + + // check all points + d_front = d_back = 0; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > 0 && d > d_front) + d_front = d; + if (d < 0 && d < d_back) + d_back = d; + } + } + + if (d_front < 0.1) // PLANESIDE_EPSILON) + { // only on back + *back = CopyBrush( brush ); + return; + } + + if (d_back > -0.1) // PLANESIDE_EPSILON) + { // only on front + *front = CopyBrush( brush ); + return; + } + + // create a new winding from the split plane + w = BaseWindingForPlane (plane->normal, plane->dist); + for (i=0 ; inumsides && w ; i++) + { + plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; + ChopWindingInPlace (&w, plane2->normal, plane2->dist, 0); // PLANESIDE_EPSILON); + } + + if (!w || WindingIsTiny (w) ) + { // the brush isn't really split + int side; + + side = BrushMostlyOnSide (brush, plane); + if (side == PSIDE_FRONT) + *front = CopyBrush (brush); + if (side == PSIDE_BACK) + *back = CopyBrush (brush); + return; + } + + if( WindingIsHuge( w ) ) + Sys_FPrintf( SYS_VRB,"WARNING: huge winding\n" ); + + midwinding = w; + + // split it for real + + for (i=0 ; i<2 ; i++) + { + b[i] = AllocBrush (brush->numsides+1); + memcpy( b[i], brush, sizeof( brush_t ) - sizeof( brush->sides ) ); + b[i]->numsides = 0; + b[i]->next = NULL; + b[i]->original = brush->original; + } + + // split all the current windings + + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + w = s->winding; + if (!w) + continue; + ClipWindingEpsilon (w, plane->normal, plane->dist, + 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]); + for (j=0 ; j<2 ; j++) + { + if (!cw[j]) + continue; + cs = &b[j]->sides[b[j]->numsides]; + b[j]->numsides++; + *cs = *s; + cs->winding = cw[j]; + } + } + + + // see if we have valid polygons on both sides + for (i=0 ; i<2 ; i++) + { + if (b[i]->numsides < 3 || !BoundBrush (b[i])) + { + if (b[i]->numsides >= 3) + Sys_FPrintf (SYS_VRB,"bogus brush after clip\n"); + FreeBrush (b[i]); + b[i] = NULL; + } + } + + if ( !(b[0] && b[1]) ) + { + if (!b[0] && !b[1]) + Sys_FPrintf (SYS_VRB,"split removed brush\n"); + else + Sys_FPrintf (SYS_VRB,"split not on both sides\n"); + if (b[0]) + { + FreeBrush (b[0]); + *front = CopyBrush (brush); + } + if (b[1]) + { + FreeBrush (b[1]); + *back = CopyBrush (brush); + } + return; + } + + // add the midwinding to both sides + for (i=0 ; i<2 ; i++) + { + cs = &b[i]->sides[b[i]->numsides]; + b[i]->numsides++; + + cs->planenum = planenum^i^1; + cs->shaderInfo = NULL; + if (i==0) + cs->winding = CopyWinding (midwinding); + else + cs->winding = midwinding; + } + + { + vec_t v1; + int i; + + + for (i=0 ; i<2 ; i++) + { + v1 = BrushVolume (b[i]); + if (v1 < 1.0) + { + FreeBrush (b[i]); + b[i] = NULL; + // Sys_FPrintf (SYS_VRB,"tiny volume after clip\n"); + } + } + } + + *front = b[0]; + *back = b[1]; +} diff --git a/tools/quake3/q3map2/brush_primit.c b/tools/quake3/q3map2/brush_primit.c new file mode 100644 index 00000000..4a0d2520 --- /dev/null +++ b/tools/quake3/q3map2/brush_primit.c @@ -0,0 +1,81 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define BRUSH_PRIMIT_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* ------------------------------------------------------------------------------- + +functions + +------------------------------------------------------------------------------- */ + +/* +ComputeAxisBase() +computes the base texture axis for brush primitive texturing +note: ComputeAxisBase here and in editor code must always BE THE SAME! +warning: special case behaviour of atan2( y, x ) <-> atan( y / x ) might not be the same everywhere when x == 0 +rotation by (0,RotY,RotZ) assigns X to normal +*/ + +void ComputeAxisBase( vec3_t normal, vec3_t texX, vec3_t texY ) +{ + vec_t RotY, RotZ; + + + /* do some cleaning */ + if( fabs( normal[ 0 ] ) < 1e-6 ) + normal[ 0 ]= 0.0f; + if( fabs( normal[ 1 ] ) < 1e-6 ) + normal[ 1 ]=0.0f; + if( fabs( normal[ 2 ] ) < 1e-6 ) + normal[ 2 ] = 0.0f; + + /* compute the two rotations around y and z to rotate x to normal */ + RotY = -atan2( normal[ 2 ], sqrt( normal[ 1 ] * normal[ 1 ] + normal[ 0 ] * normal[ 0 ]) ); + RotZ = atan2( normal[ 1 ], normal[ 0 ] ); + + /* rotate (0,1,0) and (0,0,1) to compute texX and texY */ + texX[ 0 ] = -sin( RotZ ); + texX[ 1 ] = cos( RotZ ); + texX[ 2 ] = 0; + + /* the texY vector is along -z (t texture coorinates axis) */ + texY[ 0 ] = -sin( RotY ) * cos( RotZ ); + texY[ 1 ] = -sin( RotY ) * sin( RotZ ); + texY[ 2 ] = -cos( RotY ); +} diff --git a/tools/quake3/q3map2/bsp.c b/tools/quake3/q3map2/bsp.c new file mode 100644 index 00000000..aeaf7363 --- /dev/null +++ b/tools/quake3/q3map2/bsp.c @@ -0,0 +1,931 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define BSP_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* ------------------------------------------------------------------------------- + +functions + +------------------------------------------------------------------------------- */ + + +/* +ProcessAdvertisements() +copies advertisement info into the BSP structures +*/ + +static void ProcessAdvertisements( void ) { + int i; + const char* className; + const char* modelKey; + int modelNum; + bspModel_t* adModel; + bspDrawSurface_t* adSurface; + + Sys_FPrintf( SYS_VRB, "--- ProcessAdvertisements ---\n" ); + + for( i = 0; i < numEntities; i++ ) { + + /* is an advertisement? */ + className = ValueForKey( &entities[ i ], "classname" ); + + if( !Q_stricmp( "advertisement", className ) ) { + + modelKey = ValueForKey( &entities[ i ], "model" ); + + if( strlen( modelKey ) > MAX_QPATH - 1 ) { + Error( "Model Key for entity exceeds ad struct string length." ); + } else { + if( numBSPAds < MAX_MAP_ADVERTISEMENTS ) { + bspAds[numBSPAds].cellId = IntForKey( &entities[ i ], "cellId" ); + strncpy( bspAds[numBSPAds].model, modelKey, sizeof( bspAds[numBSPAds].model ) ); + + modelKey++; + modelNum = atoi( modelKey ); + adModel = &bspModels[modelNum]; + + if( adModel->numBSPSurfaces != 1 ) { + Error( "Ad cell id %d has more than one surface.", bspAds[numBSPAds].cellId ); + } + + adSurface = &bspDrawSurfaces[adModel->firstBSPSurface]; + + // store the normal for use at run time.. all ad verts are assumed to + // have identical normals (because they should be a simple rectangle) + // so just use the first vert's normal + VectorCopy( bspDrawVerts[adSurface->firstVert].normal, bspAds[numBSPAds].normal ); + + // store the ad quad for quick use at run time + if( adSurface->surfaceType == MST_PATCH ) { + int v0 = adSurface->firstVert + adSurface->patchHeight - 1; + int v1 = adSurface->firstVert + adSurface->numVerts - 1; + int v2 = adSurface->firstVert + adSurface->numVerts - adSurface->patchWidth; + int v3 = adSurface->firstVert; + VectorCopy( bspDrawVerts[v0].xyz, bspAds[numBSPAds].rect[0] ); + VectorCopy( bspDrawVerts[v1].xyz, bspAds[numBSPAds].rect[1] ); + VectorCopy( bspDrawVerts[v2].xyz, bspAds[numBSPAds].rect[2] ); + VectorCopy( bspDrawVerts[v3].xyz, bspAds[numBSPAds].rect[3] ); + } else { + Error( "Ad cell %d has an unsupported Ad Surface type.", bspAds[numBSPAds].cellId ); + } + + numBSPAds++; + } else { + Error( "Maximum number of map advertisements exceeded." ); + } + } + } + } + + Sys_FPrintf( SYS_VRB, "%9d in-game advertisements\n", numBSPAds ); +} + +/* +SetCloneModelNumbers() - ydnar +sets the model numbers for brush entities +*/ + +static void SetCloneModelNumbers( void ) +{ + int i, j; + int models; + char modelValue[ 10 ]; + const char *value, *value2, *value3; + + + /* start with 1 (worldspawn is model 0) */ + models = 1; + for( i = 1; i < numEntities; i++ ) + { + /* only entities with brushes or patches get a model number */ + if( entities[ i ].brushes == NULL && entities[ i ].patches == NULL ) + continue; + + /* is this a clone? */ + value = ValueForKey( &entities[ i ], "_clone" ); + if( value[ 0 ] != '\0' ) + continue; + + /* add the model key */ + sprintf( modelValue, "*%d", models ); + SetKeyValue( &entities[ i ], "model", modelValue ); + + /* increment model count */ + models++; + } + + /* fix up clones */ + for( i = 1; i < numEntities; i++ ) + { + /* only entities with brushes or patches get a model number */ + if( entities[ i ].brushes == NULL && entities[ i ].patches == NULL ) + continue; + + /* is this a clone? */ + value = ValueForKey( &entities[ i ], "_ins" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( &entities[ i ], "_instance" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( &entities[ i ], "_clone" ); + if( value[ 0 ] == '\0' ) + continue; + + /* find an entity with matching clone name */ + for( j = 0; j < numEntities; j++ ) + { + /* is this a clone parent? */ + value2 = ValueForKey( &entities[ j ], "_clonename" ); + if( value2[ 0 ] == '\0' ) + continue; + + /* do they match? */ + if( strcmp( value, value2 ) == 0 ) + { + /* get the model num */ + value3 = ValueForKey( &entities[ j ], "model" ); + if( value3[ 0 ] == '\0' ) + { + Sys_Printf( "WARNING: Cloned entity %s referenced entity without model\n", value2 ); + continue; + } + models = atoi( &value2[ 1 ] ); + + /* add the model key */ + sprintf( modelValue, "*%d", models ); + SetKeyValue( &entities[ i ], "model", modelValue ); + + /* nuke the brushes/patches for this entity (fixme: leak!) */ + entities[ i ].brushes = NULL; + entities[ i ].patches = NULL; + } + } + } +} + + + +/* +FixBrushSides() - ydnar +matches brushsides back to their appropriate drawsurface and shader +*/ + +static void FixBrushSides( entity_t *e ) +{ + int i; + mapDrawSurface_t *ds; + sideRef_t *sideRef; + bspBrushSide_t *side; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- FixBrushSides ---\n" ); + + /* walk list of drawsurfaces */ + for( i = e->firstDrawSurf; i < numMapDrawSurfs; i++ ) + { + /* get surface and try to early out */ + ds = &mapDrawSurfs[ i ]; + if( ds->outputNum < 0 ) + continue; + + /* walk sideref list */ + for( sideRef = ds->sideRef; sideRef != NULL; sideRef = sideRef->next ) + { + /* get bsp brush side */ + if( sideRef->side == NULL || sideRef->side->outputNum < 0 ) + continue; + side = &bspBrushSides[ sideRef->side->outputNum ]; + + /* set drawsurface */ + side->surfaceNum = ds->outputNum; + //% Sys_FPrintf( SYS_VRB, "DS: %7d Side: %7d ", ds->outputNum, sideRef->side->outputNum ); + + /* set shader */ + if( strcmp( bspShaders[ side->shaderNum ].shader, ds->shaderInfo->shader ) ) + { + //% Sys_FPrintf( SYS_VRB, "Remapping %s to %s\n", bspShaders[ side->shaderNum ].shader, ds->shaderInfo->shader ); + side->shaderNum = EmitShader( ds->shaderInfo->shader, &ds->shaderInfo->contentFlags, &ds->shaderInfo->surfaceFlags ); + } + } + } +} + + + +/* +ProcessWorldModel() +creates a full bsp + surfaces for the worldspawn entity +*/ + +void ProcessWorldModel( void ) +{ + int i, s; + entity_t *e; + tree_t *tree; + face_t *faces; + qboolean ignoreLeaks, leaked; + xmlNodePtr polyline, leaknode; + char level[ 2 ], shader[ 1024 ]; + const char *value; + + + /* sets integer blockSize from worldspawn "_blocksize" key if it exists */ + value = ValueForKey( &entities[ 0 ], "_blocksize" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( &entities[ 0 ], "blocksize" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( &entities[ 0 ], "chopsize" ); /* sof2 */ + if( value[ 0 ] != '\0' ) + { + /* scan 3 numbers */ + s = sscanf( value, "%d %d %d", &blockSize[ 0 ], &blockSize[ 1 ], &blockSize[ 2 ] ); + + /* handle legacy case */ + if( s == 1 ) + { + blockSize[ 1 ] = blockSize[ 0 ]; + blockSize[ 2 ] = blockSize[ 0 ]; + } + } + Sys_Printf( "block size = { %d %d %d }\n", blockSize[ 0 ], blockSize[ 1 ], blockSize[ 2 ] ); + + /* sof2: ignore leaks? */ + value = ValueForKey( &entities[ 0 ], "_ignoreleaks" ); /* ydnar */ + if( value[ 0 ] == '\0' ) + value = ValueForKey( &entities[ 0 ], "ignoreleaks" ); + if( value[ 0 ] == '1' ) + ignoreLeaks = qtrue; + else + ignoreLeaks = qfalse; + + /* begin worldspawn model */ + BeginModel(); + e = &entities[ 0 ]; + e->firstDrawSurf = 0; + + /* ydnar: gs mods */ + ClearMetaTriangles(); + + /* check for patches with adjacent edges that need to lod together */ + PatchMapDrawSurfs( e ); + + /* build an initial bsp tree using all of the sides of all of the structural brushes */ + faces = MakeStructuralBSPFaceList( entities[ 0 ].brushes ); + tree = FaceBSP( faces ); + MakeTreePortals( tree ); + FilterStructuralBrushesIntoTree( e, tree ); + + /* see if the bsp is completely enclosed */ + if( FloodEntities( tree ) || ignoreLeaks ) + { + /* rebuild a better bsp tree using only the sides that are visible from the inside */ + FillOutside( tree->headnode ); + + /* chop the sides to the convex hull of their visible fragments, giving us the smallest polygons */ + ClipSidesIntoTree( e, tree ); + + /* build a visible face tree */ + faces = MakeVisibleBSPFaceList( entities[ 0 ].brushes ); + FreeTree( tree ); + tree = FaceBSP( faces ); + MakeTreePortals( tree ); + FilterStructuralBrushesIntoTree( e, tree ); + leaked = qfalse; + + /* ydnar: flood again for skybox */ + if( skyboxPresent ) + FloodEntities( tree ); + } + else + { + Sys_FPrintf( SYS_NOXML, "**********************\n" ); + Sys_FPrintf( SYS_NOXML, "******* leaked *******\n" ); + Sys_FPrintf( SYS_NOXML, "**********************\n" ); + polyline = LeakFile( tree ); + leaknode = xmlNewNode( NULL, "message" ); + xmlNodeSetContent( leaknode, "MAP LEAKED\n" ); + xmlAddChild( leaknode, polyline ); + level[0] = (int) '0' + SYS_ERR; + level[1] = 0; + xmlSetProp( leaknode, "level", (char*) &level ); + xml_SendNode( leaknode ); + if( leaktest ) + { + Sys_Printf ("--- MAP LEAKED, ABORTING LEAKTEST ---\n"); + exit( 0 ); + } + leaked = qtrue; + + /* chop the sides to the convex hull of their visible fragments, giving us the smallest polygons */ + ClipSidesIntoTree( e, tree ); + } + + /* save out information for visibility processing */ + NumberClusters( tree ); + if( !leaked ) + WritePortalFile( tree ); + + /* flood from entities */ + FloodAreas( tree ); + + /* create drawsurfs for triangle models */ + AddTriangleModels( e ); + + /* create drawsurfs for surface models */ + AddEntitySurfaceModels( e ); + + /* generate bsp brushes from map brushes */ + EmitBrushes( e->brushes, &e->firstBrush, &e->numBrushes ); + + /* add references to the detail brushes */ + FilterDetailBrushesIntoTree( e, tree ); + + /* drawsurfs that cross fog boundaries will need to be split along the fog boundary */ + if( !nofog ) + FogDrawSurfaces( e ); + + /* subdivide each drawsurf as required by shader tesselation */ + if( !nosubdivide ) + SubdivideFaceSurfaces( e, tree ); + + /* add in any vertexes required to fix t-junctions */ + if( !notjunc ) + FixTJunctions( e ); + + /* ydnar: classify the surfaces */ + ClassifyEntitySurfaces( e ); + + /* ydnar: project decals */ + MakeEntityDecals( e ); + + /* ydnar: meta surfaces */ + MakeEntityMetaTriangles( e ); + SmoothMetaTriangles(); + FixMetaTJunctions(); + MergeMetaTriangles(); + + /* ydnar: debug portals */ + if( debugPortals ) + MakeDebugPortalSurfs( tree ); + + /* ydnar: fog hull */ + value = ValueForKey( &entities[ 0 ], "_foghull" ); + if( value[ 0 ] != '\0' ) + { + sprintf( shader, "textures/%s", value ); + MakeFogHullSurfs( e, tree, shader ); + } + + /* ydnar: bug 645: do flares for lights */ + for( i = 0; i < numEntities && emitFlares; i++ ) + { + entity_t *light, *target; + const char *value, *flareShader; + vec3_t origin, targetOrigin, normal, color; + int lightStyle; + + + /* get light */ + light = &entities[ i ]; + value = ValueForKey( light, "classname" ); + if( !strcmp( value, "light" ) ) + { + /* get flare shader */ + flareShader = ValueForKey( light, "_flareshader" ); + value = ValueForKey( light, "_flare" ); + if( flareShader[ 0 ] != '\0' || value[ 0 ] != '\0' ) + { + /* get specifics */ + GetVectorForKey( light, "origin", origin ); + GetVectorForKey( light, "_color", color ); + lightStyle = IntForKey( light, "_style" ); + if( lightStyle == 0 ) + lightStyle = IntForKey( light, "style" ); + + /* handle directional spotlights */ + value = ValueForKey( light, "target" ); + if( value[ 0 ] != '\0' ) + { + /* get target light */ + target = FindTargetEntity( value ); + if( target != NULL ) + { + GetVectorForKey( target, "origin", targetOrigin ); + VectorSubtract( targetOrigin, origin, normal ); + VectorNormalize( normal, normal ); + } + } + else + //% VectorClear( normal ); + VectorSet( normal, 0, 0, -1 ); + + /* create the flare surface (note shader defaults automatically) */ + DrawSurfaceForFlare( mapEntityNum, origin, normal, color, (char*) flareShader, lightStyle ); + } + } + } + + /* add references to the final drawsurfs in the apropriate clusters */ + FilterDrawsurfsIntoTree( e, tree ); + + /* match drawsurfaces back to original brushsides (sof2) */ + FixBrushSides( e ); + + /* finish */ + EndModel( e, tree->headnode ); + FreeTree( tree ); +} + + + +/* +ProcessSubModel() +creates bsp + surfaces for other brush models +*/ + +void ProcessSubModel( void ) +{ + entity_t *e; + tree_t *tree; + brush_t *b, *bc; + node_t *node; + + + /* start a brush model */ + BeginModel(); + e = &entities[ mapEntityNum ]; + e->firstDrawSurf = numMapDrawSurfs; + + /* ydnar: gs mods */ + ClearMetaTriangles(); + + /* check for patches with adjacent edges that need to lod together */ + PatchMapDrawSurfs( e ); + + /* allocate a tree */ + node = AllocNode(); + node->planenum = PLANENUM_LEAF; + tree = AllocTree(); + tree->headnode = node; + + /* add the sides to the tree */ + ClipSidesIntoTree( e, tree ); + + /* ydnar: create drawsurfs for triangle models */ + AddTriangleModels( e ); + + /* create drawsurfs for surface models */ + AddEntitySurfaceModels( e ); + + /* generate bsp brushes from map brushes */ + EmitBrushes( e->brushes, &e->firstBrush, &e->numBrushes ); + + /* just put all the brushes in headnode */ + for( b = e->brushes; b; b = b->next ) + { + bc = CopyBrush( b ); + bc->next = node->brushlist; + node->brushlist = bc; + } + + /* subdivide each drawsurf as required by shader tesselation */ + if( !nosubdivide ) + SubdivideFaceSurfaces( e, tree ); + + /* add in any vertexes required to fix t-junctions */ + if( !notjunc ) + FixTJunctions( e ); + + /* ydnar: classify the surfaces and project lightmaps */ + ClassifyEntitySurfaces( e ); + + /* ydnar: project decals */ + MakeEntityDecals( e ); + + /* ydnar: meta surfaces */ + MakeEntityMetaTriangles( e ); + SmoothMetaTriangles(); + FixMetaTJunctions(); + MergeMetaTriangles(); + + /* add references to the final drawsurfs in the apropriate clusters */ + FilterDrawsurfsIntoTree( e, tree ); + + /* match drawsurfaces back to original brushsides (sof2) */ + FixBrushSides( e ); + + /* finish */ + EndModel( e, node ); + FreeTree( tree ); +} + + + +/* +ProcessModels() +process world + other models into the bsp +*/ + +void ProcessModels( void ) +{ + qboolean oldVerbose; + entity_t *entity; + + + /* preserve -v setting */ + oldVerbose = verbose; + + /* start a new bsp */ + BeginBSPFile(); + + /* create map fogs */ + CreateMapFogs(); + + /* walk entity list */ + for( mapEntityNum = 0; mapEntityNum < numEntities; mapEntityNum++ ) + { + /* get entity */ + entity = &entities[ mapEntityNum ]; + if( entity->brushes == NULL && entity->patches == NULL ) + continue; + + /* process the model */ + Sys_FPrintf( SYS_VRB, "############### model %i ###############\n", numBSPModels ); + if( mapEntityNum == 0 ) + ProcessWorldModel(); + else + ProcessSubModel(); + + /* potentially turn off the deluge of text */ + verbose = verboseEntities; + } + + /* restore -v setting */ + verbose = oldVerbose; + + /* write fogs */ + EmitFogs(); +} + + + +/* +OnlyEnts() +this is probably broken unless teamed with a radiant version that preserves entity order +*/ + +void OnlyEnts( void ) +{ + char out[ 1024 ]; + + + /* note it */ + Sys_Printf( "--- OnlyEnts ---\n" ); + + sprintf( out, "%s.bsp", source ); + LoadBSPFile( out ); + numEntities = 0; + + LoadShaderInfo(); + LoadMapFile( name, qfalse ); + SetModelNumbers(); + SetLightStyles(); + + numBSPEntities = numEntities; + UnparseEntities(); + + WriteBSPFile( out ); +} + + + +/* +BSPMain() - ydnar +handles creation of a bsp from a map file +*/ + +int BSPMain( int argc, char **argv ) +{ + int i; + char path[ 1024 ], tempSource[ 1024 ]; + qboolean onlyents = qfalse; + + + /* note it */ + Sys_Printf( "--- BSP ---\n" ); + + SetDrawSurfacesBuffer(); + mapDrawSurfs = safe_malloc( sizeof( mapDrawSurface_t ) * MAX_MAP_DRAW_SURFS ); + memset( mapDrawSurfs, 0, sizeof( mapDrawSurface_t ) * MAX_MAP_DRAW_SURFS ); + numMapDrawSurfs = 0; + + tempSource[ 0 ] = '\0'; + + /* set standard game flags */ + maxSurfaceVerts = game->maxSurfaceVerts; + maxSurfaceIndexes = game->maxSurfaceIndexes; + emitFlares = game->emitFlares; + + /* process arguments */ + for( i = 1; i < (argc - 1); i++ ) + { + if( !strcmp( argv[ i ], "-onlyents" ) ) + { + Sys_Printf( "Running entity-only compile\n" ); + onlyents = qtrue; + } + else if( !strcmp( argv[ i ], "-tempname" ) ) + strcpy( tempSource, argv[ ++i ] ); + else if( !strcmp( argv[ i ], "-tmpout" ) ) + strcpy( outbase, "/tmp" ); + else if( !strcmp( argv[ i ], "-nowater" ) ) + { + Sys_Printf( "Disabling water\n" ); + nowater = qtrue; + } + else if( !strcmp( argv[ i ], "-nodetail" ) ) + { + Sys_Printf( "Ignoring detail brushes\n") ; + nodetail = qtrue; + } + else if( !strcmp( argv[ i ], "-fulldetail" ) ) + { + Sys_Printf( "Turning detail brushes into structural brushes\n" ); + fulldetail = qtrue; + } + else if( !strcmp( argv[ i ], "-nofog" ) ) + { + Sys_Printf( "Fog volumes disabled\n" ); + nofog = qtrue; + } + else if( !strcmp( argv[ i ], "-nosubdivide" ) ) + { + Sys_Printf( "Disabling brush face subdivision\n" ); + nosubdivide = qtrue; + } + else if( !strcmp( argv[ i ], "-leaktest" ) ) + { + Sys_Printf( "Leaktest enabled\n" ); + leaktest = qtrue; + } + else if( !strcmp( argv[ i ], "-verboseentities" ) ) + { + Sys_Printf( "Verbose entities enabled\n" ); + verboseEntities = qtrue; + } + else if( !strcmp( argv[ i ], "-nocurves" ) ) + { + Sys_Printf( "Ignoring curved surfaces (patches)\n" ); + noCurveBrushes = qtrue; + } + else if( !strcmp( argv[ i ], "-notjunc" ) ) + { + Sys_Printf( "T-junction fixing disabled\n" ); + notjunc = qtrue; + } + else if( !strcmp( argv[ i ], "-fakemap" ) ) + { + Sys_Printf( "Generating fakemap.map\n" ); + fakemap = qtrue; + } + else if( !strcmp( argv[ i ], "-samplesize" ) ) + { + sampleSize = atoi( argv[ i + 1 ] ); + if( sampleSize < 1 ) + sampleSize = 1; + i++; + Sys_Printf( "Lightmap sample size set to %dx%d units\n", sampleSize, sampleSize ); + } + else if( !strcmp( argv[ i ], "-custinfoparms") ) + { + Sys_Printf( "Custom info parms enabled\n" ); + useCustomInfoParms = qtrue; + } + + /* sof2 args */ + else if( !strcmp( argv[ i ], "-rename" ) ) + { + Sys_Printf( "Appending _bsp suffix to misc_model shaders (SOF2)\n" ); + renameModelShaders = qtrue; + } + + /* ydnar args */ + else if( !strcmp( argv[ i ], "-ne" ) ) + { + normalEpsilon = atof( argv[ i + 1 ] ); + i++; + Sys_Printf( "Normal epsilon set to %f\n", normalEpsilon ); + } + else if( !strcmp( argv[ i ], "-de" ) ) + { + distanceEpsilon = atof( argv[ i + 1 ] ); + i++; + Sys_Printf( "Distance epsilon set to %f\n", distanceEpsilon ); + } + else if( !strcmp( argv[ i ], "-mv" ) ) + { + maxLMSurfaceVerts = atoi( argv[ i + 1 ] ); + if( maxLMSurfaceVerts < 3 ) + maxLMSurfaceVerts = 3; + if( maxLMSurfaceVerts > maxSurfaceVerts ) + maxSurfaceVerts = maxLMSurfaceVerts; + i++; + Sys_Printf( "Maximum lightmapped surface vertex count set to %d\n", maxLMSurfaceVerts ); + } + else if( !strcmp( argv[ i ], "-mi" ) ) + { + maxSurfaceIndexes = atoi( argv[ i + 1 ] ); + if( maxSurfaceIndexes < 3 ) + maxSurfaceIndexes = 3; + i++; + Sys_Printf( "Maximum per-surface index count set to %d\n", maxSurfaceIndexes ); + } + else if( !strcmp( argv[ i ], "-np" ) ) + { + npDegrees = atof( argv[ i + 1 ] ); + if( npDegrees < 0.0f ) + shadeAngleDegrees = 0.0f; + else if( npDegrees > 0.0f ) + Sys_Printf( "Forcing nonplanar surfaces with a breaking angle of %f degrees\n", npDegrees ); + i++; + } + else if( !strcmp( argv[ i ], "-snap" ) ) + { + bevelSnap = atoi( argv[ i + 1 ]); + if( bevelSnap < 0 ) + bevelSnap = 0; + i++; + if( bevelSnap > 0 ) + Sys_Printf( "Snapping brush bevel planes to %d units\n", bevelSnap ); + } + else if( !strcmp( argv[ i ], "-texrange" ) ) + { + texRange = atoi( argv[ i + 1 ]); + if( texRange < 0 ) + texRange = 0; + i++; + Sys_Printf( "Limiting per-surface texture range to %d texels\n", texRange ); + } + else if( !strcmp( argv[ i ], "-nohint" ) ) + { + Sys_Printf( "Hint brushes disabled\n" ); + noHint = qtrue; + } + else if( !strcmp( argv[ i ], "-flat" ) ) + { + Sys_Printf( "Flatshading enabled\n" ); + flat = qtrue; + } + else if( !strcmp( argv[ i ], "-meta" ) ) + { + Sys_Printf( "Creating meta surfaces from brush faces\n" ); + meta = qtrue; + } + else if( !strcmp( argv[ i ], "-patchmeta" ) ) + { + Sys_Printf( "Creating meta surfaces from patches\n" ); + patchMeta = qtrue; + } + else if( !strcmp( argv[ i ], "-flares" ) ) + { + Sys_Printf( "Flare surfaces enabled\n" ); + emitFlares = qtrue; + } + else if( !strcmp( argv[ i ], "-noflares" ) ) + { + Sys_Printf( "Flare surfaces disabled\n" ); + emitFlares = qfalse; + } + else if( !strcmp( argv[ i ], "-skyfix" ) ) + { + Sys_Printf( "GL_CLAMP sky fix/hack/workaround enabled\n" ); + skyFixHack = qtrue; + } + else if( !strcmp( argv[ i ], "-debugsurfaces" ) ) + { + Sys_Printf( "emitting debug surfaces\n" ); + debugSurfaces = qtrue; + } + else if( !strcmp( argv[ i ], "-debuginset" ) ) + { + Sys_Printf( "Debug surface triangle insetting enabled\n" ); + debugInset = qtrue; + } + else if( !strcmp( argv[ i ], "-debugportals" ) ) + { + Sys_Printf( "Debug portal surfaces enabled\n" ); + debugPortals = qtrue; + } + else if( !strcmp( argv[ i ], "-bsp" ) ) + Sys_Printf( "-bsp argument unnecessary\n" ); + else + Sys_Printf( "WARNING: Unknown option \"%s\"\n", argv[ i ] ); + } + + /* fixme: print more useful usage here */ + if( i != (argc - 1) ) + Error( "usage: q3map [options] mapfile" ); + + /* copy source name */ + strcpy( source, ExpandArg( argv[ i ] ) ); + StripExtension( source ); + + /* ydnar: set default sample size */ + SetDefaultSampleSize( sampleSize ); + + /* delete portal, line and surface files */ + sprintf( path, "%s.prt", source ); + remove( path ); + sprintf( path, "%s.lin", source ); + remove( path ); + //% sprintf( path, "%s.srf", source ); /* ydnar */ + //% remove( path ); + + /* expand mapname */ + strcpy( name, ExpandArg( argv[ i ] ) ); + if( strcmp( name + strlen( name ) - 4, ".reg" ) ) + { + /* if we are doing a full map, delete the last saved region map */ + sprintf( path, "%s.reg", source ); + remove( path ); + DefaultExtension( name, ".map" ); /* might be .reg */ + } + + /* if onlyents, just grab the entites and resave */ + if( onlyents ) + { + OnlyEnts(); + return 0; + } + + /* load shaders */ + LoadShaderInfo(); + + /* load original file from temp spot in case it was renamed by the editor on the way in */ + if( strlen( tempSource ) > 0 ) + LoadMapFile( tempSource, qfalse ); + else + LoadMapFile( name, qfalse ); + + /* ydnar: decal setup */ + ProcessDecals(); + + /* ydnar: cloned brush model entities */ + SetCloneModelNumbers(); + + /* process world and submodels */ + ProcessModels(); + + /* set light styles from targetted light entities */ + SetLightStyles(); + + /* process in game advertisements */ + ProcessAdvertisements(); + + /* finish and write bsp */ + EndBSPFile(); + + /* remove temp map source file if appropriate */ + if( strlen( tempSource ) > 0) + remove( tempSource ); + + /* return to sender */ + return 0; +} + diff --git a/tools/quake3/q3map2/bspfile_abstract.c b/tools/quake3/q3map2/bspfile_abstract.c new file mode 100644 index 00000000..31b6f171 --- /dev/null +++ b/tools/quake3/q3map2/bspfile_abstract.c @@ -0,0 +1,853 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define BSPFILE_ABSTRACT_C + + + +/* dependencies */ +#include "q3map2.h" + + + + +/* ------------------------------------------------------------------------------- + +this file was copied out of the common directory in order to not break +compatibility with the q3map 1.x tree. it was moved out in order to support +the raven bsp format (RBSP) used in soldier of fortune 2 and jedi knight 2. + +since each game has its own set of particular features, the data structures +below no longer directly correspond to the binary format of a particular game. + +the translation will be done at bsp load/save time to keep any sort of +special-case code messiness out of the rest of the program. + +------------------------------------------------------------------------------- */ + + + +/* FIXME: remove the functions below that handle memory management of bsp file chunks */ + +int numBSPDrawVertsBuffer = 0; +void IncDrawVerts() +{ + numBSPDrawVerts++; + + if(bspDrawVerts == 0) + { + numBSPDrawVertsBuffer = MAX_MAP_DRAW_VERTS / 37; + + bspDrawVerts = safe_malloc_info(sizeof(bspDrawVert_t) * numBSPDrawVertsBuffer, "IncDrawVerts"); + + } + else if(numBSPDrawVerts > numBSPDrawVertsBuffer) + { + numBSPDrawVertsBuffer *= 3; // multiply by 1.5 + numBSPDrawVertsBuffer /= 2; + + if(numBSPDrawVertsBuffer > MAX_MAP_DRAW_VERTS) + numBSPDrawVertsBuffer = MAX_MAP_DRAW_VERTS; + + bspDrawVerts = realloc(bspDrawVerts, sizeof(bspDrawVert_t) * numBSPDrawVertsBuffer); + + if(!bspDrawVerts) + Error( "realloc() failed (IncDrawVerts)"); + } + + memset(bspDrawVerts + (numBSPDrawVerts - 1), 0, sizeof(bspDrawVert_t)); +} + +void SetDrawVerts(int n) +{ + if(bspDrawVerts != 0) + free(bspDrawVerts); + + numBSPDrawVerts = n; + numBSPDrawVertsBuffer = numBSPDrawVerts; + + bspDrawVerts = safe_malloc_info(sizeof(bspDrawVert_t) * numBSPDrawVertsBuffer, "IncDrawVerts"); + + memset(bspDrawVerts, 0, n * sizeof(bspDrawVert_t)); +} + +int numBSPDrawSurfacesBuffer = 0; +void SetDrawSurfacesBuffer() +{ + if(bspDrawSurfaces != 0) + free(bspDrawSurfaces); + + numBSPDrawSurfacesBuffer = MAX_MAP_DRAW_SURFS; + + bspDrawSurfaces = safe_malloc_info(sizeof(bspDrawSurface_t) * numBSPDrawSurfacesBuffer, "IncDrawSurfaces"); + + memset(bspDrawSurfaces, 0, MAX_MAP_DRAW_SURFS * sizeof(bspDrawVert_t)); +} + +void SetDrawSurfaces(int n) +{ + if(bspDrawSurfaces != 0) + free(bspDrawSurfaces); + + numBSPDrawSurfaces = n; + numBSPDrawSurfacesBuffer = numBSPDrawSurfaces; + + bspDrawSurfaces = safe_malloc_info(sizeof(bspDrawSurface_t) * numBSPDrawSurfacesBuffer, "IncDrawSurfaces"); + + memset(bspDrawSurfaces, 0, n * sizeof(bspDrawVert_t)); +} + +void BSPFilesCleanup() +{ + if(bspDrawVerts != 0) + free(bspDrawVerts); + if(bspDrawSurfaces != 0) + free(bspDrawSurfaces); + if(bspLightBytes != 0) + free(bspLightBytes); + if(bspGridPoints != 0) + free(bspGridPoints); +} + + + + + + +/* +SwapBlock() +if all values are 32 bits, this can be used to swap everything +*/ + +void SwapBlock( int *block, int size ) +{ + int i; + + + /* dummy check */ + if( block == NULL ) + return; + + /* swap */ + size >>= 2; + for( i = 0; i < size; i++ ) + block[ i ] = LittleLong( block[ i ] ); +} + + + +/* +SwapBSPFile() +byte swaps all data in the abstract bsp +*/ + +void SwapBSPFile( void ) +{ + int i, j; + + + /* models */ + SwapBlock( (int*) bspModels, numBSPModels * sizeof( bspModels[ 0 ] ) ); + + /* shaders (don't swap the name) */ + for( i = 0; i < numBSPShaders ; i++ ) + { + bspShaders[ i ].contentFlags = LittleLong( bspShaders[ i ].contentFlags ); + bspShaders[ i ].surfaceFlags = LittleLong( bspShaders[ i ].surfaceFlags ); + } + + /* planes */ + SwapBlock( (int*) bspPlanes, numBSPPlanes * sizeof( bspPlanes[ 0 ] ) ); + + /* nodes */ + SwapBlock( (int*) bspNodes, numBSPNodes * sizeof( bspNodes[ 0 ] ) ); + + /* leafs */ + SwapBlock( (int*) bspLeafs, numBSPLeafs * sizeof( bspLeafs[ 0 ] ) ); + + /* leaffaces */ + SwapBlock( (int*) bspLeafSurfaces, numBSPLeafSurfaces * sizeof( bspLeafSurfaces[ 0 ] ) ); + + /* leafbrushes */ + SwapBlock( (int*) bspLeafBrushes, numBSPLeafBrushes * sizeof( bspLeafBrushes[ 0 ] ) ); + + // brushes + SwapBlock( (int*) bspBrushes, numBSPBrushes * sizeof( bspBrushes[ 0 ] ) ); + + // brushsides + SwapBlock( (int*) bspBrushSides, numBSPBrushSides * sizeof( bspBrushSides[ 0 ] ) ); + + // vis + ((int*) &bspVisBytes)[ 0 ] = LittleLong( ((int*) &bspVisBytes)[ 0 ] ); + ((int*) &bspVisBytes)[ 1 ] = LittleLong( ((int*) &bspVisBytes)[ 1 ] ); + + /* drawverts (don't swap colors) */ + for( i = 0; i < numBSPDrawVerts; i++ ) + { + bspDrawVerts[ i ].xyz[ 0 ] = LittleFloat( bspDrawVerts[ i ].xyz[ 0 ] ); + bspDrawVerts[ i ].xyz[ 1 ] = LittleFloat( bspDrawVerts[ i ].xyz[ 1 ] ); + bspDrawVerts[ i ].xyz[ 2 ] = LittleFloat( bspDrawVerts[ i ].xyz[ 2 ] ); + bspDrawVerts[ i ].normal[ 0 ] = LittleFloat( bspDrawVerts[ i ].normal[ 0 ] ); + bspDrawVerts[ i ].normal[ 1 ] = LittleFloat( bspDrawVerts[ i ].normal[ 1 ] ); + bspDrawVerts[ i ].normal[ 2 ] = LittleFloat( bspDrawVerts[ i ].normal[ 2 ] ); + bspDrawVerts[ i ].st[ 0 ] = LittleFloat( bspDrawVerts[ i ].st[ 0 ] ); + bspDrawVerts[ i ].st[ 1 ] = LittleFloat( bspDrawVerts[ i ].st[ 1 ] ); + for( j = 0; j < MAX_LIGHTMAPS; j++ ) + { + bspDrawVerts[ i ].lightmap[ j ][ 0 ] = LittleFloat( bspDrawVerts[ i ].lightmap[ j ][ 0 ] ); + bspDrawVerts[ i ].lightmap[ j ][ 1 ] = LittleFloat( bspDrawVerts[ i ].lightmap[ j ][ 1 ] ); + } + } + + /* drawindexes */ + SwapBlock( (int*) bspDrawIndexes, numBSPDrawIndexes * sizeof( bspDrawIndexes[0] ) ); + + /* drawsurfs */ + /* note: rbsp files (and hence q3map2 abstract bsp) have byte lightstyles index arrays, this follows sof2map convention */ + SwapBlock( (int*) bspDrawSurfaces, numBSPDrawSurfaces * sizeof( bspDrawSurfaces[ 0 ] ) ); + + /* fogs */ + for( i = 0; i < numBSPFogs; i++ ) + { + bspFogs[ i ].brushNum = LittleLong( bspFogs[ i ].brushNum ); + bspFogs[ i ].visibleSide = LittleLong( bspFogs[ i ].visibleSide ); + } + + /* advertisements */ + for( i = 0; i < numBSPAds; i++ ) + { + bspAds[ i ].cellId = LittleLong( bspAds[ i ].cellId ); + bspAds[ i ].normal[ 0 ] = LittleFloat( bspAds[ i ].normal[ 0 ] ); + bspAds[ i ].normal[ 1 ] = LittleFloat( bspAds[ i ].normal[ 1 ] ); + bspAds[ i ].normal[ 2 ] = LittleFloat( bspAds[ i ].normal[ 2 ] ); + + for( j = 0; j < 4; j++ ) + { + bspAds[ i ].rect[j][ 0 ] = LittleFloat( bspAds[ i ].rect[j][ 0 ] ); + bspAds[ i ].rect[j][ 1 ] = LittleFloat( bspAds[ i ].rect[j][ 1 ] ); + bspAds[ i ].rect[j][ 2 ] = LittleFloat( bspAds[ i ].rect[j][ 2 ] ); + } + + //bspAds[ i ].model[ MAX_QPATH ]; + } +} + + + +/* +GetLumpElements() +gets the number of elements in a bsp lump +*/ + +int GetLumpElements( bspHeader_t *header, int lump, int size ) +{ + /* check for odd size */ + if( header->lumps[ lump ].length % size ) + { + if( force ) + { + Sys_Printf( "WARNING: GetLumpElements: odd lump size (%d) in lump %d\n", header->lumps[ lump ].length, lump ); + return 0; + } + else + Error( "GetLumpElements: odd lump size (%d) in lump %d", header->lumps[ lump ].length, lump ); + } + + /* return element count */ + return header->lumps[ lump ].length / size; +} + + + +/* +GetLump() +returns a pointer to the specified lump +*/ + +void *GetLump( bspHeader_t *header, int lump ) +{ + return (void*)( (byte*) header + header->lumps[ lump ].offset); +} + + + +/* +CopyLump() +copies a bsp file lump into a destination buffer +*/ + +int CopyLump( bspHeader_t *header, int lump, void *dest, int size ) +{ + int length, offset; + + + /* get lump length and offset */ + length = header->lumps[ lump ].length; + offset = header->lumps[ lump ].offset; + + /* handle erroneous cases */ + if( length == 0 ) + return 0; + if( length % size ) + { + if( force ) + { + Sys_Printf( "WARNING: CopyLump: odd lump size (%d) in lump %d\n", length, lump ); + return 0; + } + else + Error( "CopyLump: odd lump size (%d) in lump %d", length, lump ); + } + + /* copy block of memory and return */ + memcpy( dest, (byte*) header + offset, length ); + return length / size; +} + + + +/* +AddLump() +adds a lump to an outgoing bsp file +*/ + +void AddLump( FILE *file, bspHeader_t *header, int lumpNum, const void *data, int length ) +{ + bspLump_t *lump; + + + /* add lump to bsp file header */ + lump = &header->lumps[ lumpNum ]; + lump->offset = LittleLong( ftell( file ) ); + lump->length = LittleLong( length ); + + /* write lump to file */ + SafeWrite( file, data, (length + 3) & ~3 ); +} + + + +/* +LoadBSPFile() +loads a bsp file into memory +*/ + +void LoadBSPFile( const char *filename ) +{ + /* dummy check */ + if( game == NULL || game->load == NULL ) + Error( "LoadBSPFile: unsupported BSP file format" ); + + /* load it, then byte swap the in-memory version */ + game->load( filename ); + SwapBSPFile(); +} + + + +/* +WriteBSPFile() +writes a bsp file +*/ + +void WriteBSPFile( const char *filename ) +{ + char tempname[ 1024 ]; + time_t tm; + + + /* dummy check */ + if( game == NULL || game->write == NULL ) + Error( "WriteBSPFile: unsupported BSP file format" ); + + /* make fake temp name so existing bsp file isn't damaged in case write process fails */ + time( &tm ); + sprintf( tempname, "%s.%08X", filename, (int) tm ); + + /* byteswap, write the bsp, then swap back so it can be manipulated further */ + SwapBSPFile(); + game->write( tempname ); + SwapBSPFile(); + + /* replace existing bsp file */ + remove( filename ); + rename( tempname, filename ); +} + + + +/* +PrintBSPFileSizes() +dumps info about current file +*/ + +void PrintBSPFileSizes( void ) +{ + /* parse entities first */ + if( numEntities <= 0 ) + ParseEntities(); + + /* note that this is abstracted */ + Sys_Printf( "Abstracted BSP file components (*actual sizes may differ)\n" ); + + /* print various and sundry bits */ + Sys_Printf( "%9d models %9d\n", + numBSPModels, (int) (numBSPModels * sizeof( bspModel_t )) ); + Sys_Printf( "%9d shaders %9d\n", + numBSPShaders, (int) (numBSPShaders * sizeof( bspShader_t )) ); + Sys_Printf( "%9d brushes %9d\n", + numBSPBrushes, (int) (numBSPBrushes * sizeof( bspBrush_t )) ); + Sys_Printf( "%9d brushsides %9d *\n", + numBSPBrushSides, (int) (numBSPBrushSides * sizeof( bspBrushSide_t )) ); + Sys_Printf( "%9d fogs %9d\n", + numBSPFogs, (int) (numBSPFogs * sizeof( bspFog_t ) ) ); + Sys_Printf( "%9d planes %9d\n", + numBSPPlanes, (int) (numBSPPlanes * sizeof( bspPlane_t )) ); + Sys_Printf( "%9d entdata %9d\n", + numEntities, bspEntDataSize ); + Sys_Printf( "\n"); + + Sys_Printf( "%9d nodes %9d\n", + numBSPNodes, (int) (numBSPNodes * sizeof( bspNode_t)) ); + Sys_Printf( "%9d leafs %9d\n", + numBSPLeafs, (int) (numBSPLeafs * sizeof( bspLeaf_t )) ); + Sys_Printf( "%9d leafsurfaces %9d\n", + numBSPLeafSurfaces, (int) (numBSPLeafSurfaces * sizeof( *bspLeafSurfaces )) ); + Sys_Printf( "%9d leafbrushes %9d\n", + numBSPLeafBrushes, (int) (numBSPLeafBrushes * sizeof( *bspLeafBrushes )) ); + Sys_Printf( "\n"); + + Sys_Printf( "%9d drawsurfaces %9d *\n", + numBSPDrawSurfaces, (int) (numBSPDrawSurfaces * sizeof( *bspDrawSurfaces )) ); + Sys_Printf( "%9d drawverts %9d *\n", + numBSPDrawVerts, (int) (numBSPDrawVerts * sizeof( *bspDrawVerts )) ); + Sys_Printf( "%9d drawindexes %9d\n", + numBSPDrawIndexes, (int) (numBSPDrawIndexes * sizeof( *bspDrawIndexes )) ); + Sys_Printf( "\n"); + + Sys_Printf( "%9d lightmaps %9d\n", + numBSPLightBytes / (game->lightmapSize * game->lightmapSize * 3), numBSPLightBytes ); + Sys_Printf( "%9d lightgrid %9d *\n", + numBSPGridPoints, (int) (numBSPGridPoints * sizeof( *bspGridPoints )) ); + Sys_Printf( " visibility %9d\n", + numBSPVisBytes ); +} + + + +/* ------------------------------------------------------------------------------- + +entity data handling + +------------------------------------------------------------------------------- */ + + +/* +StripTrailing() +strips low byte chars off the end of a string +*/ + +void StripTrailing( char *e ) +{ + char *s; + + + s = e + strlen( e ) - 1; + while( s >= e && *s <= 32 ) + { + *s = 0; + s--; + } +} + + + +/* +ParseEpair() +parses a single quoted "key" "value" pair into an epair struct +*/ + +epair_t *ParseEPair( void ) +{ + epair_t *e; + + + /* allocate and clear new epair */ + e = safe_malloc( sizeof( epair_t ) ); + memset( e, 0, sizeof( epair_t ) ); + + /* handle key */ + if( strlen( token ) >= (MAX_KEY - 1) ) + Error( "ParseEPair: token too long" ); + + e->key = copystring( token ); + GetToken( qfalse ); + + /* handle value */ + if( strlen( token ) >= MAX_VALUE - 1 ) + Error( "ParseEpar: token too long" ); + e->value = copystring( token ); + + /* strip trailing spaces that sometimes get accidentally added in the editor */ + StripTrailing( e->key ); + StripTrailing( e->value ); + + /* return it */ + return e; +} + + + +/* +ParseEntity() +parses an entity's epairs +*/ + +qboolean ParseEntity( void ) +{ + epair_t *e; + + + /* dummy check */ + if( !GetToken( qtrue ) ) + return qfalse; + if( strcmp( token, "{" ) ) + Error( "ParseEntity: { not found" ); + if( numEntities == MAX_MAP_ENTITIES ) + Error( "numEntities == MAX_MAP_ENTITIES" ); + + /* create new entity */ + mapEnt = &entities[ numEntities ]; + numEntities++; + + /* parse */ + while( 1 ) + { + if( !GetToken( qtrue ) ) + Error( "ParseEntity: EOF without closing brace" ); + if( !EPAIR_STRCMP( token, "}" ) ) + break; + e = ParseEPair(); + e->next = mapEnt->epairs; + mapEnt->epairs = e; + } + + /* return to sender */ + return qtrue; +} + + + +/* +ParseEntities() +parses the bsp entity data string into entities +*/ + +void ParseEntities( void ) +{ + numEntities = 0; + ParseFromMemory( bspEntData, bspEntDataSize ); + while( ParseEntity() ); + + /* ydnar: set number of bsp entities in case a map is loaded on top */ + numBSPEntities = numEntities; +} + + + +/* +UnparseEntities() +generates the dentdata string from all the entities. +this allows the utilities to add or remove key/value +pairs to the data created by the map editor +*/ + +void UnparseEntities( void ) +{ + int i; + char *buf, *end; + epair_t *ep; + char line[ 2048 ]; + char key[ 1024 ], value[ 1024 ]; + const char *value2; + + + /* setup */ + buf = bspEntData; + end = buf; + *end = 0; + + /* run through entity list */ + for( i = 0; i < numBSPEntities && i < numEntities; i++ ) + { + /* get epair */ + ep = entities[ i ].epairs; + if( ep == NULL ) + continue; /* ent got removed */ + + /* ydnar: certain entities get stripped from bsp file */ + value2 = ValueForKey( &entities[ i ], "classname" ); + if( !Q_stricmp( value2, "misc_model" ) || + !Q_stricmp( value2, "_decal" ) || + !Q_stricmp( value2, "_skybox" ) ) + continue; + + /* add beginning brace */ + strcat( end, "{\n" ); + end += 2; + + /* walk epair list */ + for( ep = entities[ i ].epairs; ep != NULL; ep = ep->next ) + { + /* copy and clean */ + strcpy( key, ep->key ); + StripTrailing( key ); + strcpy( value, ep->value ); + StripTrailing( value ); + + /* add to buffer */ + sprintf( line, "\"%s\" \"%s\"\n", key, value ); + strcat( end, line ); + end += strlen( line ); + } + + /* add trailing brace */ + strcat( end,"}\n" ); + end += 2; + + /* check for overflow */ + if( end > buf + MAX_MAP_ENTSTRING ) + Error( "Entity text too long" ); + } + + /* set size */ + bspEntDataSize = end - buf + 1; +} + + + +/* +PrintEntity() +prints an entity's epairs to the console +*/ + +void PrintEntity( const entity_t *ent ) +{ + epair_t *ep; + + + Sys_Printf( "------- entity %p -------\n", ent ); + for( ep = ent->epairs; ep != NULL; ep = ep->next ) + Sys_Printf( "%s = %s\n", ep->key, ep->value ); + +} + + + +/* +SetKeyValue() +sets an epair in an entity +*/ + +void SetKeyValue( entity_t *ent, const char *key, const char *value ) +{ + epair_t *ep; + + + /* check for existing epair */ + for( ep = ent->epairs; ep != NULL; ep = ep->next ) + { + if( !EPAIR_STRCMP( ep->key, key ) ) + { + free( ep->value ); + ep->value = copystring( value ); + return; + } + } + + /* create new epair */ + ep = safe_malloc( sizeof( *ep ) ); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring( key ); + ep->value = copystring( value ); +} + + + +/* +ValueForKey() +gets the value for an entity key +*/ + +const char *ValueForKey( const entity_t *ent, const char *key ) +{ + epair_t *ep; + + + /* dummy check */ + if( ent == NULL ) + return ""; + + /* walk epair list */ + for( ep = ent->epairs; ep != NULL; ep = ep->next ) + { + if( !EPAIR_STRCMP( ep->key, key ) ) + return ep->value; + } + + /* if no match, return empty string */ + return ""; +} + + + +/* +IntForKey() +gets the integer point value for an entity key +*/ + +int IntForKey( const entity_t *ent, const char *key ) +{ + const char *k; + + + k = ValueForKey( ent, key ); + return atoi( k ); +} + + + +/* +FloatForKey() +gets the floating point value for an entity key +*/ + +vec_t FloatForKey( const entity_t *ent, const char *key ) +{ + const char *k; + + + k = ValueForKey( ent, key ); + return atof( k ); +} + + + +/* +GetVectorForKey() +gets a 3-element vector value for an entity key +*/ + +void GetVectorForKey( const entity_t *ent, const char *key, vec3_t vec ) +{ + const char *k; + double v1, v2, v3; + + + /* get value */ + k = ValueForKey( ent, key ); + + /* scanf into doubles, then assign, so it is vec_t size independent */ + v1 = v2 = v3 = 0.0; + sscanf( k, "%lf %lf %lf", &v1, &v2, &v3 ); + vec[ 0 ] = v1; + vec[ 1 ] = v2; + vec[ 2 ] = v3; +} + + + +/* +FindTargetEntity() +finds an entity target +*/ + +entity_t *FindTargetEntity( const char *target ) +{ + int i; + const char *n; + + + /* walk entity list */ + for( i = 0; i < numEntities; i++ ) + { + n = ValueForKey( &entities[ i ], "targetname" ); + if ( !strcmp( n, target ) ) + return &entities[ i ]; + } + + /* nada */ + return NULL; +} + + + +/* +GetEntityShadowFlags() - ydnar +gets an entity's shadow flags +note: does not set them to defaults if the keys are not found! +*/ + +void GetEntityShadowFlags( const entity_t *ent, const entity_t *ent2, int *castShadows, int *recvShadows ) +{ + const char *value; + + + /* get cast shadows */ + if( castShadows != NULL ) + { + value = ValueForKey( ent, "_castShadows" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( ent, "_cs" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( ent2, "_castShadows" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( ent2, "_cs" ); + if( value[ 0 ] != '\0' ) + *castShadows = atoi( value ); + } + + /* receive */ + if( recvShadows != NULL ) + { + value = ValueForKey( ent, "_receiveShadows" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( ent, "_rs" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( ent2, "_receiveShadows" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( ent2, "_rs" ); + if( value[ 0 ] != '\0' ) + *recvShadows = atoi( value ); + } +} + diff --git a/tools/quake3/q3map2/bspfile_ibsp.c b/tools/quake3/q3map2/bspfile_ibsp.c new file mode 100644 index 00000000..26fc56dc --- /dev/null +++ b/tools/quake3/q3map2/bspfile_ibsp.c @@ -0,0 +1,593 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define BSPFILE_IBSP_C + + + +/* dependencies */ +#include "q3map2.h" + + + + +/* ------------------------------------------------------------------------------- + +this file handles translating the bsp file format used by quake 3, rtcw, and ef +into the abstracted bsp file used by q3map2. + +------------------------------------------------------------------------------- */ + +/* constants */ +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define LUMP_ADVERTISEMENTS 17 +#define HEADER_LUMPS 18 + + +/* types */ +typedef struct +{ + char ident[ 4 ]; + int version; + + bspLump_t lumps[ HEADER_LUMPS ]; +} +ibspHeader_t; + + + +/* brush sides */ +typedef struct +{ + int planeNum; + int shaderNum; +} +ibspBrushSide_t; + + +static void CopyBrushSidesLump( ibspHeader_t *header ) +{ + int i; + ibspBrushSide_t *in; + bspBrushSide_t *out; + + + /* get count */ + numBSPBrushSides = GetLumpElements( (bspHeader_t*) header, LUMP_BRUSHSIDES, sizeof( *in ) ); + + /* copy */ + in = GetLump( (bspHeader_t*) header, LUMP_BRUSHSIDES ); + out = bspBrushSides; + for( i = 0; i < numBSPBrushSides; i++ ) + { + out->planeNum = in->planeNum; + out->shaderNum = in->shaderNum; + out->surfaceNum = -1; + in++; + out++; + } +} + + +static void AddBrushSidesLump( FILE *file, ibspHeader_t *header ) +{ + int i, size; + bspBrushSide_t *in; + ibspBrushSide_t *buffer, *out; + + + /* allocate output buffer */ + size = numBSPBrushSides * sizeof( *buffer ); + buffer = safe_malloc( size ); + memset( buffer, 0, size ); + + /* convert */ + in = bspBrushSides; + out = buffer; + for( i = 0; i < numBSPBrushSides; i++ ) + { + out->planeNum = in->planeNum; + out->shaderNum = in->shaderNum; + in++; + out++; + } + + /* write lump */ + AddLump( file, (bspHeader_t*) header, LUMP_BRUSHSIDES, buffer, size ); + + /* free buffer */ + free( buffer ); +} + + + +/* drawsurfaces */ +typedef struct ibspDrawSurface_s +{ + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + int lightmapNum; + int lightmapX, lightmapY; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[ 3 ]; + + int patchWidth; + int patchHeight; +} +ibspDrawSurface_t; + + +static void CopyDrawSurfacesLump( ibspHeader_t *header ) +{ + int i, j; + ibspDrawSurface_t *in; + bspDrawSurface_t *out; + + + /* get count */ + numBSPDrawSurfaces = GetLumpElements( (bspHeader_t*) header, LUMP_SURFACES, sizeof( *in ) ); + SetDrawSurfaces( numBSPDrawSurfaces ); + + /* copy */ + in = GetLump( (bspHeader_t*) header, LUMP_SURFACES ); + out = bspDrawSurfaces; + for( i = 0; i < numBSPDrawSurfaces; i++ ) + { + out->shaderNum = in->shaderNum; + out->fogNum = in->fogNum; + out->surfaceType = in->surfaceType; + out->firstVert = in->firstVert; + out->numVerts = in->numVerts; + out->firstIndex = in->firstIndex; + out->numIndexes = in->numIndexes; + + out->lightmapStyles[ 0 ] = LS_NORMAL; + out->vertexStyles[ 0 ] = LS_NORMAL; + out->lightmapNum[ 0 ] = in->lightmapNum; + out->lightmapX[ 0 ] = in->lightmapX; + out->lightmapY[ 0 ] = in->lightmapY; + + for( j = 1; j < MAX_LIGHTMAPS; j++ ) + { + out->lightmapStyles[ j ] = LS_NONE; + out->vertexStyles[ j ] = LS_NONE; + out->lightmapNum[ j ] = -3; + out->lightmapX[ j ] = 0; + out->lightmapY[ j ] = 0; + } + + out->lightmapWidth = in->lightmapWidth; + out->lightmapHeight = in->lightmapHeight; + + VectorCopy( in->lightmapOrigin, out->lightmapOrigin ); + VectorCopy( in->lightmapVecs[ 0 ], out->lightmapVecs[ 0 ] ); + VectorCopy( in->lightmapVecs[ 1 ], out->lightmapVecs[ 1 ] ); + VectorCopy( in->lightmapVecs[ 2 ], out->lightmapVecs[ 2 ] ); + + out->patchWidth = in->patchWidth; + out->patchHeight = in->patchHeight; + + in++; + out++; + } +} + + +static void AddDrawSurfacesLump( FILE *file, ibspHeader_t *header ) +{ + int i, size; + bspDrawSurface_t *in; + ibspDrawSurface_t *buffer, *out; + + + /* allocate output buffer */ + size = numBSPDrawSurfaces * sizeof( *buffer ); + buffer = safe_malloc( size ); + memset( buffer, 0, size ); + + /* convert */ + in = bspDrawSurfaces; + out = buffer; + for( i = 0; i < numBSPDrawSurfaces; i++ ) + { + out->shaderNum = in->shaderNum; + out->fogNum = in->fogNum; + out->surfaceType = in->surfaceType; + out->firstVert = in->firstVert; + out->numVerts = in->numVerts; + out->firstIndex = in->firstIndex; + out->numIndexes = in->numIndexes; + + out->lightmapNum = in->lightmapNum[ 0 ]; + out->lightmapX = in->lightmapX[ 0 ]; + out->lightmapY = in->lightmapY[ 0 ]; + out->lightmapWidth = in->lightmapWidth; + out->lightmapHeight = in->lightmapHeight; + + VectorCopy( in->lightmapOrigin, out->lightmapOrigin ); + VectorCopy( in->lightmapVecs[ 0 ], out->lightmapVecs[ 0 ] ); + VectorCopy( in->lightmapVecs[ 1 ], out->lightmapVecs[ 1 ] ); + VectorCopy( in->lightmapVecs[ 2 ], out->lightmapVecs[ 2 ] ); + + out->patchWidth = in->patchWidth; + out->patchHeight = in->patchHeight; + + in++; + out++; + } + + /* write lump */ + AddLump( file, (bspHeader_t*) header, LUMP_SURFACES, buffer, size ); + + /* free buffer */ + free( buffer ); +} + + + +/* drawverts */ +typedef struct +{ + vec3_t xyz; + float st[ 2 ]; + float lightmap[ 2 ]; + vec3_t normal; + byte color[ 4 ]; +} +ibspDrawVert_t; + + +static void CopyDrawVertsLump( ibspHeader_t *header ) +{ + int i; + ibspDrawVert_t *in; + bspDrawVert_t *out; + + + /* get count */ + numBSPDrawVerts = GetLumpElements( (bspHeader_t*) header, LUMP_DRAWVERTS, sizeof( *in ) ); + SetDrawVerts( numBSPDrawVerts ); + + /* copy */ + in = GetLump( (bspHeader_t*) header, LUMP_DRAWVERTS ); + out = bspDrawVerts; + for( i = 0; i < numBSPDrawVerts; i++ ) + { + VectorCopy( in->xyz, out->xyz ); + out->st[ 0 ] = in->st[ 0 ]; + out->st[ 1 ] = in->st[ 1 ]; + + out->lightmap[ 0 ][ 0 ] = in->lightmap[ 0 ]; + out->lightmap[ 0 ][ 1 ] = in->lightmap[ 1 ]; + + VectorCopy( in->normal, out->normal ); + + out->color[ 0 ][ 0 ] = in->color[ 0 ]; + out->color[ 0 ][ 1 ] = in->color[ 1 ]; + out->color[ 0 ][ 2 ] = in->color[ 2 ]; + out->color[ 0 ][ 3 ] = in->color[ 3 ]; + + in++; + out++; + } +} + + +static void AddDrawVertsLump( FILE *file, ibspHeader_t *header ) +{ + int i, size; + bspDrawVert_t *in; + ibspDrawVert_t *buffer, *out; + + + /* allocate output buffer */ + size = numBSPDrawVerts * sizeof( *buffer ); + buffer = safe_malloc( size ); + memset( buffer, 0, size ); + + /* convert */ + in = bspDrawVerts; + out = buffer; + for( i = 0; i < numBSPDrawVerts; i++ ) + { + VectorCopy( in->xyz, out->xyz ); + out->st[ 0 ] = in->st[ 0 ]; + out->st[ 1 ] = in->st[ 1 ]; + + out->lightmap[ 0 ] = in->lightmap[ 0 ][ 0 ]; + out->lightmap[ 1 ] = in->lightmap[ 0 ][ 1 ]; + + VectorCopy( in->normal, out->normal ); + + out->color[ 0 ] = in->color[ 0 ][ 0 ]; + out->color[ 1 ] = in->color[ 0 ][ 1 ]; + out->color[ 2 ] = in->color[ 0 ][ 2 ]; + out->color[ 3 ] = in->color[ 0 ][ 3 ]; + + in++; + out++; + } + + /* write lump */ + AddLump( file, (bspHeader_t*) header, LUMP_DRAWVERTS, buffer, size ); + + /* free buffer */ + free( buffer ); +} + + + +/* light grid */ +typedef struct +{ + byte ambient[ 3 ]; + byte directed[ 3 ]; + byte latLong[ 2 ]; +} +ibspGridPoint_t; + + +static void CopyLightGridLumps( ibspHeader_t *header ) +{ + int i, j; + ibspGridPoint_t *in; + bspGridPoint_t *out; + + + /* get count */ + numBSPGridPoints = GetLumpElements( (bspHeader_t*) header, LUMP_LIGHTGRID, sizeof( *in ) ); + + /* allocate buffer */ + bspGridPoints = safe_malloc( numBSPGridPoints * sizeof( *bspGridPoints ) ); + memset( bspGridPoints, 0, numBSPGridPoints * sizeof( *bspGridPoints ) ); + + /* copy */ + in = GetLump( (bspHeader_t*) header, LUMP_LIGHTGRID ); + out = bspGridPoints; + for( i = 0; i < numBSPGridPoints; i++ ) + { + for( j = 0; j < MAX_LIGHTMAPS; j++ ) + { + VectorCopy( in->ambient, out->ambient[ j ] ); + VectorCopy( in->directed, out->directed[ j ] ); + out->styles[ j ] = LS_NONE; + } + + out->styles[ 0 ] = LS_NORMAL; + + out->latLong[ 0 ] = in->latLong[ 0 ]; + out->latLong[ 1 ] = in->latLong[ 1 ]; + + in++; + out++; + } +} + + +static void AddLightGridLumps( FILE *file, ibspHeader_t *header ) +{ + int i; + bspGridPoint_t *in; + ibspGridPoint_t *buffer, *out; + + + /* dummy check */ + if( bspGridPoints == NULL ) + return; + + /* allocate temporary buffer */ + buffer = safe_malloc( numBSPGridPoints * sizeof( *out ) ); + + /* convert */ + in = bspGridPoints; + out = buffer; + for( i = 0; i < numBSPGridPoints; i++ ) + { + VectorCopy( in->ambient[ 0 ], out->ambient ); + VectorCopy( in->directed[ 0 ], out->directed ); + + out->latLong[ 0 ] = in->latLong[ 0 ]; + out->latLong[ 1 ] = in->latLong[ 1 ]; + + in++; + out++; + } + + /* write lumps */ + AddLump( file, (bspHeader_t*) header, LUMP_LIGHTGRID, buffer, (numBSPGridPoints * sizeof( *out )) ); + + /* free buffer (ydnar 2002-10-22: [bug 641] thanks Rap70r! */ + free( buffer ); +} + +/* +LoadIBSPFile() +loads a quake 3 bsp file into memory +*/ + +void LoadIBSPFile( const char *filename ) +{ + ibspHeader_t *header; + + + /* load the file header */ + LoadFile( filename, (void**) &header ); + + /* swap the header (except the first 4 bytes) */ + SwapBlock( (int*) ((byte*) header + sizeof( int )), sizeof( *header ) - sizeof( int ) ); + + /* make sure it matches the format we're trying to load */ + if( force == qfalse && *((int*) header->ident) != *((int*) game->bspIdent) ) + Error( "%s is not a %s file", filename, game->bspIdent ); + if( force == qfalse && header->version != game->bspVersion ) + Error( "%s is version %d, not %d", filename, header->version, game->bspVersion ); + + /* load/convert lumps */ + numBSPShaders = CopyLump( (bspHeader_t*) header, LUMP_SHADERS, bspShaders, sizeof( bspShader_t ) ); + + numBSPModels = CopyLump( (bspHeader_t*) header, LUMP_MODELS, bspModels, sizeof( bspModel_t ) ); + + numBSPPlanes = CopyLump( (bspHeader_t*) header, LUMP_PLANES, bspPlanes, sizeof( bspPlane_t ) ); + + numBSPLeafs = CopyLump( (bspHeader_t*) header, LUMP_LEAFS, bspLeafs, sizeof( bspLeaf_t ) ); + + numBSPNodes = CopyLump( (bspHeader_t*) header, LUMP_NODES, bspNodes, sizeof( bspNode_t ) ); + + numBSPLeafSurfaces = CopyLump( (bspHeader_t*) header, LUMP_LEAFSURFACES, bspLeafSurfaces, sizeof( bspLeafSurfaces[ 0 ] ) ); + + numBSPLeafBrushes = CopyLump( (bspHeader_t*) header, LUMP_LEAFBRUSHES, bspLeafBrushes, sizeof( bspLeafBrushes[ 0 ] ) ); + + numBSPBrushes = CopyLump( (bspHeader_t*) header, LUMP_BRUSHES, bspBrushes, sizeof( bspBrush_t ) ); + + CopyBrushSidesLump( header ); + + CopyDrawVertsLump( header ); + + CopyDrawSurfacesLump( header ); + + numBSPFogs = CopyLump( (bspHeader_t*) header, LUMP_FOGS, bspFogs, sizeof( bspFog_t ) ); + + numBSPDrawIndexes = CopyLump( (bspHeader_t*) header, LUMP_DRAWINDEXES, bspDrawIndexes, sizeof( bspDrawIndexes[ 0 ] ) ); + + numBSPVisBytes = CopyLump( (bspHeader_t*) header, LUMP_VISIBILITY, bspVisBytes, 1 ); + + numBSPLightBytes = GetLumpElements( (bspHeader_t*) header, LUMP_LIGHTMAPS, 1 ); + bspLightBytes = safe_malloc( numBSPLightBytes ); + CopyLump( (bspHeader_t*) header, LUMP_LIGHTMAPS, bspLightBytes, 1 ); + + bspEntDataSize = CopyLump( (bspHeader_t*) header, LUMP_ENTITIES, bspEntData, 1); + + CopyLightGridLumps( header ); + + /* advertisements */ + if(header->version == 47) // quake live's bsp version + numBSPAds = CopyLump( (bspHeader_t*) header, LUMP_ADVERTISEMENTS, bspAds, sizeof( bspAdvertisement_t ) ); + else + numBSPAds = 0; + + /* free the file buffer */ + free( header ); +} + + + +/* +WriteIBSPFile() +writes an id bsp file +*/ + +void WriteIBSPFile( const char *filename ) +{ + ibspHeader_t outheader, *header; + FILE *file; + time_t t; + char marker[ 1024 ]; + int size; + + + /* set header */ + header = &outheader; + memset( header, 0, sizeof( *header ) ); + + //% Swapfile(); + + /* set up header */ + *((int*) (bspHeader_t*) header->ident) = *((int*) game->bspIdent); + header->version = LittleLong( game->bspVersion ); + + /* write initial header */ + file = SafeOpenWrite( filename ); + SafeWrite( file, (bspHeader_t*) header, sizeof( *header ) ); /* overwritten later */ + + /* add marker lump */ + time( &t ); + sprintf( marker, "I LOVE MY Q3MAP2 %s on %s)", Q3MAP_VERSION, asctime( localtime( &t ) ) ); + AddLump( file, (bspHeader_t*) header, 0, marker, strlen( marker ) + 1 ); + + /* add lumps */ + AddLump( file, (bspHeader_t*) header, LUMP_SHADERS, bspShaders, numBSPShaders * sizeof( bspShader_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_PLANES, bspPlanes, numBSPPlanes * sizeof( bspPlane_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_LEAFS, bspLeafs, numBSPLeafs * sizeof( bspLeaf_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_NODES, bspNodes, numBSPNodes * sizeof( bspNode_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_BRUSHES, bspBrushes, numBSPBrushes*sizeof( bspBrush_t ) ); + AddBrushSidesLump( file, header ); + AddLump( file, (bspHeader_t*) header, LUMP_LEAFSURFACES, bspLeafSurfaces, numBSPLeafSurfaces * sizeof( bspLeafSurfaces[ 0 ] ) ); + AddLump( file, (bspHeader_t*) header, LUMP_LEAFBRUSHES, bspLeafBrushes, numBSPLeafBrushes * sizeof( bspLeafBrushes[ 0 ] ) ); + AddLump( file, (bspHeader_t*) header, LUMP_MODELS, bspModels, numBSPModels * sizeof( bspModel_t ) ); + AddDrawVertsLump( file, header ); + AddDrawSurfacesLump( file, header ); + AddLump( file, (bspHeader_t*) header, LUMP_VISIBILITY, bspVisBytes, numBSPVisBytes ); + AddLump( file, (bspHeader_t*) header, LUMP_LIGHTMAPS, bspLightBytes, numBSPLightBytes ); + AddLightGridLumps( file, header ); + AddLump( file, (bspHeader_t*) header, LUMP_ENTITIES, bspEntData, bspEntDataSize ); + AddLump( file, (bspHeader_t*) header, LUMP_FOGS, bspFogs, numBSPFogs * sizeof( bspFog_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_DRAWINDEXES, bspDrawIndexes, numBSPDrawIndexes * sizeof( bspDrawIndexes[ 0 ] ) ); + + /* advertisements */ + AddLump( file, (bspHeader_t*) header, LUMP_ADVERTISEMENTS, bspAds, numBSPAds * sizeof( bspAdvertisement_t ) ); + + /* emit bsp size */ + size = ftell( file ); + Sys_Printf( "Wrote %.1f MB (%d bytes)\n", (float) size / (1024 * 1024), size ); + + /* write the completed header */ + fseek( file, 0, SEEK_SET ); + SafeWrite( file, header, sizeof( *header ) ); + + /* close the file */ + fclose( file ); +} diff --git a/tools/quake3/q3map2/bspfile_rbsp.c b/tools/quake3/q3map2/bspfile_rbsp.c new file mode 100644 index 00000000..74d77ae9 --- /dev/null +++ b/tools/quake3/q3map2/bspfile_rbsp.c @@ -0,0 +1,340 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define BSPFILE_RBSP_C + + + +/* dependencies */ +#include "q3map2.h" + + + + +/* ------------------------------------------------------------------------------- + +this file handles translating the bsp file format used by quake 3, rtcw, and ef +into the abstracted bsp file used by q3map2. + +------------------------------------------------------------------------------- */ + +/* constants */ +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define LUMP_LIGHTARRAY 17 +#define HEADER_LUMPS 18 + + +/* types */ +typedef struct +{ + char ident[ 4 ]; + int version; + + bspLump_t lumps[ HEADER_LUMPS ]; +} +rbspHeader_t; + + + +/* light grid */ +#define MAX_MAP_GRID 0xffff +#define MAX_MAP_GRIDARRAY 0x100000 +#define LG_EPSILON 4 + + +static void CopyLightGridLumps( rbspHeader_t *header ) +{ + int i; + unsigned short *inArray; + bspGridPoint_t *in, *out; + + + /* get count */ + numBSPGridPoints = GetLumpElements( (bspHeader_t*) header, LUMP_LIGHTARRAY, sizeof( *inArray ) ); + + /* allocate buffer */ + bspGridPoints = safe_malloc( numBSPGridPoints * sizeof( *bspGridPoints ) ); + memset( bspGridPoints, 0, numBSPGridPoints * sizeof( *bspGridPoints ) ); + + /* copy */ + inArray = GetLump( (bspHeader_t*) header, LUMP_LIGHTARRAY ); + in = GetLump( (bspHeader_t*) header, LUMP_LIGHTGRID ); + out = bspGridPoints; + for( i = 0; i < numBSPGridPoints; i++ ) + { + memcpy( out, &in[ *inArray ], sizeof( *in ) ); + inArray++; + out++; + } +} + + +static void AddLightGridLumps( FILE *file, rbspHeader_t *header ) +{ + int i, j, k, c, d; + int numGridPoints, maxGridPoints; + bspGridPoint_t *gridPoints, *in, *out; + int numGridArray; + unsigned short *gridArray; + qboolean bad; + + + /* allocate temporary buffers */ + maxGridPoints = (numBSPGridPoints < MAX_MAP_GRID) ? numBSPGridPoints : MAX_MAP_GRID; + gridPoints = safe_malloc( maxGridPoints * sizeof( *gridPoints ) ); + gridArray = safe_malloc( numBSPGridPoints * sizeof( *gridArray ) ); + + /* zero out */ + numGridPoints = 0; + numGridArray = numBSPGridPoints; + + /* for each bsp grid point, find an approximate twin */ + Sys_Printf( "Storing lightgrid: %d points\n", numBSPGridPoints ); + for( i = 0; i < numGridArray; i++ ) + { + /* get points */ + in = &bspGridPoints[ i ]; + + /* walk existing list */ + for( j = 0; j < numGridPoints; j++ ) + { + /* get point */ + out = &gridPoints[ j ]; + + /* compare styles */ + if( *((unsigned int*) in->styles) != *((unsigned int*) out->styles) ) + continue; + + /* compare direction */ + d = abs( in->latLong[ 0 ] - out->latLong[ 0 ] ); + if( d < (255 - LG_EPSILON) && d > LG_EPSILON ) + continue; + d = abs( in->latLong[ 1 ] - out->latLong[ 1 ] ); + if( d < 255 - LG_EPSILON && d > LG_EPSILON ) + continue; + + /* compare light */ + bad = qfalse; + for( k = 0; (k < MAX_LIGHTMAPS && bad == qfalse); k++ ) + { + for( c = 0; c < 3; c++ ) + { + if( abs( (int) in->ambient[ k ][ c ] - (int) out->ambient[ k ][ c ]) > LG_EPSILON || + abs( (int) in->directed[ k ][ c ] - (int) out->directed[ k ][ c ]) > LG_EPSILON ) + { + bad = qtrue; + break; + } + } + } + + /* failure */ + if( bad ) + continue; + + /* this sample is ok */ + break; + } + + /* set sample index */ + gridArray[ i ] = (unsigned short) j; + + /* if no sample found, add a new one */ + if( j >= numGridPoints && numGridPoints < maxGridPoints ) + { + out = &gridPoints[ numGridPoints++ ]; + memcpy( out, in, sizeof( *in ) ); + } + } + + /* swap array */ + for( i = 0; i < numGridArray; i++ ) + gridArray[ i ] = LittleShort( gridArray[ i ] ); + + /* write lumps */ + AddLump( file, (bspHeader_t*) header, LUMP_LIGHTGRID, gridPoints, (numGridPoints * sizeof( *gridPoints )) ); + AddLump( file, (bspHeader_t*) header, LUMP_LIGHTARRAY, gridArray, (numGridArray * sizeof( *gridArray )) ); + + /* free buffers */ + free( gridPoints ); + free( gridArray ); +} + + + +/* +LoadRBSPFile() +loads a raven bsp file into memory +*/ + +void LoadRBSPFile( const char *filename ) +{ + rbspHeader_t *header; + + + /* load the file header */ + LoadFile( filename, (void**) &header ); + + /* swap the header (except the first 4 bytes) */ + SwapBlock( (int*) ((byte*) header + sizeof( int )), sizeof( *header ) - sizeof( int ) ); + + /* make sure it matches the format we're trying to load */ + if( force == qfalse && *((int*) header->ident) != *((int*) game->bspIdent) ) + Error( "%s is not a %s file", filename, game->bspIdent ); + if( force == qfalse && header->version != game->bspVersion ) + Error( "%s is version %d, not %d", filename, header->version, game->bspVersion ); + + /* load/convert lumps */ + numBSPShaders = CopyLump( (bspHeader_t*) header, LUMP_SHADERS, bspShaders, sizeof( bspShader_t ) ); + + numBSPModels = CopyLump( (bspHeader_t*) header, LUMP_MODELS, bspModels, sizeof( bspModel_t ) ); + + numBSPPlanes = CopyLump( (bspHeader_t*) header, LUMP_PLANES, bspPlanes, sizeof( bspPlane_t ) ); + + numBSPLeafs = CopyLump( (bspHeader_t*) header, LUMP_LEAFS, bspLeafs, sizeof( bspLeaf_t ) ); + + numBSPNodes = CopyLump( (bspHeader_t*) header, LUMP_NODES, bspNodes, sizeof( bspNode_t ) ); + + numBSPLeafSurfaces = CopyLump( (bspHeader_t*) header, LUMP_LEAFSURFACES, bspLeafSurfaces, sizeof( bspLeafSurfaces[ 0 ] ) ); + + numBSPLeafBrushes = CopyLump( (bspHeader_t*) header, LUMP_LEAFBRUSHES, bspLeafBrushes, sizeof( bspLeafBrushes[ 0 ] ) ); + + numBSPBrushes = CopyLump( (bspHeader_t*) header, LUMP_BRUSHES, bspBrushes, sizeof( bspBrush_t ) ); + + numBSPBrushSides = CopyLump( (bspHeader_t*) header, LUMP_BRUSHSIDES, bspBrushSides, sizeof( bspBrushSide_t ) ); + + numBSPDrawVerts = GetLumpElements( (bspHeader_t*) header, LUMP_DRAWVERTS, sizeof( bspDrawVerts[ 0 ] ) ); + SetDrawVerts( numBSPDrawVerts ); + CopyLump( (bspHeader_t*) header, LUMP_DRAWVERTS, bspDrawVerts, sizeof( bspDrawVerts[ 0 ] ) ); + + numBSPDrawSurfaces = GetLumpElements( (bspHeader_t*) header, LUMP_SURFACES, sizeof( bspDrawSurfaces[ 0 ] ) ); + SetDrawSurfaces( numBSPDrawSurfaces ); + CopyLump( (bspHeader_t*) header, LUMP_SURFACES, bspDrawSurfaces, sizeof( bspDrawSurfaces[ 0 ] ) ); + + numBSPFogs = CopyLump( (bspHeader_t*) header, LUMP_FOGS, bspFogs, sizeof( bspFogs[ 0 ] ) ); + + numBSPDrawIndexes = CopyLump( (bspHeader_t*) header, LUMP_DRAWINDEXES, bspDrawIndexes, sizeof( bspDrawIndexes[ 0 ] ) ); + + numBSPVisBytes = CopyLump( (bspHeader_t*) header, LUMP_VISIBILITY, bspVisBytes, 1 ); + + numBSPLightBytes = GetLumpElements( (bspHeader_t*) header, LUMP_LIGHTMAPS, 1 ); + bspLightBytes = safe_malloc( numBSPLightBytes ); + CopyLump( (bspHeader_t*) header, LUMP_LIGHTMAPS, bspLightBytes, 1 ); + + bspEntDataSize = CopyLump( (bspHeader_t*) header, LUMP_ENTITIES, bspEntData, 1); + + CopyLightGridLumps( header ); + + /* free the file buffer */ + free( header ); +} + + + +/* +WriteRBSPFile() +writes a raven bsp file +*/ + +void WriteRBSPFile( const char *filename ) +{ + rbspHeader_t outheader, *header; + FILE *file; + time_t t; + char marker[ 1024 ]; + int size; + + + /* set header */ + header = &outheader; + memset( header, 0, sizeof( *header ) ); + + //% Swapfile(); + + /* set up header */ + *((int*) (bspHeader_t*) header->ident) = *((int*) game->bspIdent); + header->version = LittleLong( game->bspVersion ); + + /* write initial header */ + file = SafeOpenWrite( filename ); + SafeWrite( file, (bspHeader_t*) header, sizeof( *header ) ); /* overwritten later */ + + /* add marker lump */ + time( &t ); + sprintf( marker, "I LOVE MY Q3MAP2 %s on %s)", Q3MAP_VERSION, asctime( localtime( &t ) ) ); + AddLump( file, (bspHeader_t*) header, 0, marker, strlen( marker ) + 1 ); + + /* add lumps */ + AddLump( file, (bspHeader_t*) header, LUMP_SHADERS, bspShaders, numBSPShaders * sizeof( bspShader_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_PLANES, bspPlanes, numBSPPlanes * sizeof( bspPlane_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_LEAFS, bspLeafs, numBSPLeafs * sizeof( bspLeaf_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_NODES, bspNodes, numBSPNodes * sizeof( bspNode_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_BRUSHES, bspBrushes, numBSPBrushes*sizeof( bspBrush_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_BRUSHSIDES, bspBrushSides, numBSPBrushSides * sizeof( bspBrushSides[ 0 ] ) ); + AddLump( file, (bspHeader_t*) header, LUMP_LEAFSURFACES, bspLeafSurfaces, numBSPLeafSurfaces * sizeof( bspLeafSurfaces[ 0 ] ) ); + AddLump( file, (bspHeader_t*) header, LUMP_LEAFBRUSHES, bspLeafBrushes, numBSPLeafBrushes * sizeof( bspLeafBrushes[ 0 ] ) ); + AddLump( file, (bspHeader_t*) header, LUMP_MODELS, bspModels, numBSPModels * sizeof( bspModel_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_DRAWVERTS, bspDrawVerts, numBSPDrawVerts * sizeof( bspDrawVerts[ 0 ] ) ); + AddLump( file, (bspHeader_t*) header, LUMP_SURFACES, bspDrawSurfaces, numBSPDrawSurfaces * sizeof( bspDrawSurfaces[ 0 ] ) ); + AddLump( file, (bspHeader_t*) header, LUMP_VISIBILITY, bspVisBytes, numBSPVisBytes ); + AddLump( file, (bspHeader_t*) header, LUMP_LIGHTMAPS, bspLightBytes, numBSPLightBytes ); + AddLightGridLumps( file, header ); + AddLump( file, (bspHeader_t*) header, LUMP_ENTITIES, bspEntData, bspEntDataSize ); + AddLump( file, (bspHeader_t*) header, LUMP_FOGS, bspFogs, numBSPFogs * sizeof( bspFog_t ) ); + AddLump( file, (bspHeader_t*) header, LUMP_DRAWINDEXES, bspDrawIndexes, numBSPDrawIndexes * sizeof( bspDrawIndexes[ 0 ] ) ); + + /* emit bsp size */ + size = ftell( file ); + Sys_Printf( "Wrote %.1f MB (%d bytes)\n", (float) size / (1024 * 1024), size ); + + /* write the completed header */ + fseek( file, 0, SEEK_SET ); + SafeWrite( file, header, sizeof( *header ) ); + + /* close the file */ + fclose( file ); +} diff --git a/tools/quake3/q3map2/changelog.q3map1 b/tools/quake3/q3map2/changelog.q3map1 new file mode 100644 index 00000000..21b0cb99 --- /dev/null +++ b/tools/quake3/q3map2/changelog.q3map1 @@ -0,0 +1,371 @@ +Old Q3Map 1.x and Early Q3Map2 Changelog (Chronological Order) + +- FILE IS STATIC. IF YOU MAKE CHANGES, UPDATE CHANGELOG.Q3MAP2 - + + +Date Version Notes +---------------------------------------------------------------- +2001-12-03 1.2 (ydnar) Alpha Initial version (win32) + +2001-12-03 1.2 (ydnar 2) Alpha Tolerance expanded + (more brush faces caught) + +2001-12-04 1.2 (ydnar 3) Alpha Detail faces inside other + detail brushes now culled, + Small against large detail + faces also culled. + +2001-12-04 1.2 (ydnar 4) Alpha djbob found a bug where + coincident caulk faces + were causing textured + faces to be caulked. Fixed. + +2001-12-04 1.2 (ydnar 7) Alpha 5 and 6 were internal test + versions. This version + takes into account extra + surface info, so coplanar + clip brushes no longer + cull away textured sides. + +2001-12-22 1.2 (ydnar 8) Alpha Optimized light. Lighting + for most maps should now + be measurable in minutes + as opposed to hours. + +2001-12-24 1.2 (ydnar 9) Alpha Fixed light. It is still + faster, but to enable + "blinding fast" mode, + you must supply the -fast + switch on the commandline + after -light. Fast mode + should be approximately + 2x as fast as build 8. + +2001-12-24 1.2 (ydnar 10) Alpha Grid lighting is now + optimized. Not as much + as I would like, but + a distance^2 cull before + traces on EVERY SINGLE + SURFACE LIGHT IN THE MAP + certainly speeds things + the fuck up. -fast not + necessary to see this + optimization. Also added + the -cheap switch to + limit light contributions + to a point when it exceeds + 255 in R, G, and B. This + *may* cause artifacts. + Test away... + +2001-12-24 1.2 (ydnar 11) Alpha Now using PVS data (vis). + Well constructed and + hinted maps should now + see a bit of a speedup. + Lights in the void are + also now removed as a + byproduct. + +2001-12-24 1.2 (ydnar 12) Alpha Fixed bug that caused + vlight to crash. + +2001-12-27 1.2 (ydnar 13) Alpha - Fixed broken PVS check. + - Cheap now supresses sun + Sun trace skipped if + sample is "cheapened." + - Experimental -smooth + option for subsampling + shadow edges. + - Experimental radiosity + code. Will probably crash. + - Other minor optimizations. + +2001-12-27 1.2 (ydnar 14) Alpha Build 13 always subsampled, + making it slower. Fixed. + +2001-12-28 1.2 (ydnar 15) Alpha Bad windings from edge- or + vertex- manipulated brushes + no longer created. Vertex + lighting on func_* with + an origin now works. + Radiosity should be more + stable (but not fully + correct yet). Light + envelopes now properly + calculated for entities + with origins. + +2001-12-28 1.2 (ydnar 16) Alpha Un-vised maps will now light. + +2001-12-30 1.2 (ydnar 17) Alpha Radiosity. Use q3map_bounce + in shaders to specify + amount of light to reflect. + Use -bounce N after -light + to enable radiosity. Use + -dump to emit radiosity + lights as a prefab. + +2001-12-31 1.2 (ydnar 18) Alpha Normalization release. New + features include -fastgrid, + -cheapgrid, and -fastbounce. + Running with -fastgrid and + -cheapgrid will produce + results identical to normal + q3map (with the lightgrid + being a little darker). + Also added q3map_nofast to + shaders to override -fast + switch for a surface light. + +2002-01-01 1.2 (ydnar 19) Alpha Fixed an odd vertex lighting + bug (thanks Quakin) that was + causing sun to leak to brush + faces when using r_vertexlight + ingame. Changed a little bit + of the default behavior, so + test with vertex lighting + and with terrain. Minor + shader changes might be + necessary to get some maps to + look as before. + +2002-01-01 1.2 (ydnar 20) Alpha Colored alpha shadows. Some + minor optimizations in + shadow tracing. Should be + slightly faster than 19. + +2002-01-02 1.2 (ydnar 21) Alpha Set up colored shadows + properly to use + surfaceparm lightfilter. + Shaders must use this parm + to have colored shadows. + Can be used with alphashadow + as well. + +2002-01-04 1.2.1-y1 (nightly) This version is all new, + based off the official + GtkRadiant tree, which has + all the previous enhancements. + New features include colored + lightgrid tracing through + lightfilter shaders, and + surfaceparm lightgrid, for + large/space maps with large + volumes. Also fixed are + potential broken brush + winding radiosity crashes. + Maybe. + +2002-01-05 1.2.1-y2 (nightly) Merged latest CVS. Removed + bug where ambient was getting + calculated into the radiosity + solution for every pass, + leading to overbright maps + in a hurry. Also removed + the bad PTPFF reporting, + as it only caused problems + with radiosity in a big way. + Sue me. + +2002-01-05 1.2.1-y3 (nightly) I really suck. Sample color + now properly cleared to 0 + when bouncing. + +2002-01-07 1.2.1-y4 (nightly) Particle Studio generated + brush faces should no longer + be culled. I no longer cull + faces that are autosprite. + Added -bouncegrid to have + radiosity add to lightgrid. + +2002-01-08 1.2.1-y5 (nightly) Same as y4, but compiled with + full optimizations. Should + be 10-25% faster in all, + including BSP and vis + stages. + +2002-01-09 1.2.1-y6 (nightly) Brushfaces with polygonoffset + in their shader will no longer + be faceculled. + +2002-01-11 1.2.1-y7 (nightly) Increased stack size for threads + to 4MB on Win32 to (hopefully) + elminate stack overflow + crash with radiosity. Also + made subdivision use the heap + to lessen the stack load. Fixed + bug where q3map_bounce was not + being used in shader parsing. + Redid some of the divide math + to work in 0-255 instead of + 0-256. + +2002-01-11 1.2.1-y8 (nightly) More Win32 threading + crutches. Eat me, Bill. + +2002-01-15 1.2.1-y9 RR2DO2 noticed a stupid bug + in my PVS code. Fixed it, + so the PVS light opts work as + they should. Lighting is + faster. Also got rid of some + redundant square roots from + the raytracing, speeding up + lighting another ~25%. + +2002-01-20 1.2.1-y10 Fixed a potential crash bug + with maps with 0 lights. Also + changed how lightmaps are + projected onto patches that + lie in a single plane (bevel + endcaps, floors, etc). Shadows + now work properly on them. + +2002-01-22 1.2.1-y11 Fixed a divide-by-zero crash + with maps with no lights or + no tracing. Also added + code to make brush/patch + vertex lighting more closely + resemble lightmap, even on + less-than-perfect maps. And + -light is faster, too...about + 25% on q3dm17. 34->25 seconds. + +2002-01-24 1.2.1-y12 Completely rewrote the path + argument handling. Should find + the Quake 3 dir and other + dirs properly now. Needs to + be tested on Linux though. + Also made lights linear by + default when run with -game + wolf. This is to match the + Gray Matter q3map and + entity definition. + +2002-01-28 1.2.4-y1 Merged from 1.2.4-nightly CVS + sources. Fog sparklies gone. + -nopatchfix so vlight works + properly again. Cleaned up + paths processing some more, + including Linux stuff. Added + _lightmapscale entity key. + Brought -game wolf lighting + to parity with GM tools. + RR2DO2's PCX loading fix. A + bunch of other useful fun shit. + +2002-01-29 1.2.4-y2 Fixed a bug in RR2DO2's PCX fix. + Fixed a stupid bug in lightmap + dimension bounds checking (thanks + Laerth). + +2002-01-29 1.2.4-y3 Now will detect (and report to + GtkRadiant) all degenerate patches + like those created by capping a + cone. + +2002-02-23 2.0.0-a1 thru a3 Rewrote about 30% of the code. + Lots of cool new shit. + +2002-02-24 2.0.0-a4 thru a8 Terrain fix (thx Pointy), patches + are no longer circus colored, more + terrain texturing fixes. + +2002-02-26 2.0.0-a10 thru a11 Adjacent coplanar surfaces now + will share lightmaps. This prevents + most wierd edge cases with filter + and speeds things up a bit. Patches + too. + +2002-02-27 2.0.0-a12 More lightmap fixes for non-planar + surfaces. Bugfixes in allocation/ + compression of lightmaps as well. + +2002-03-02 2.0.0-a13 Fixed some surface light bugs, + adjusted the occluded-luxel finding + code, and amped the radiosity. Other + fixes to RTCW lighting code (better + angle attenuation on linear lights). + +2002-03-04 2.0.0-a14 Vertex light should now be near- + perfect on clean (and mostly on not- + so-clean) maps. Unlit maps will no + longer have tri-fanned brush faces + with random vertex colors. VLight is + now totally gone (reverts to -light). + +2002-03-06 2.0.0-a15 Relaxed the planar check, should now + classify all slightly-off plane brush + face metasurfaces as planar. Triangle + checking much more stringent as well. + +2002-03-11 2.0.0-a16 Vis crash gone. Lightmap allocation + now sorted by shader to minimize + shader count (and lessen chance for + RTCW shader substitution bug). Hit + big quarter-century also. + +2002-03-12 2.0.0-a17 Dammit. + +2002-03-12 2.0.0-a18 Hunting phantom lights... + +2002-03-16 2.0.0-a19 Fogclip and _celshader. Check the + extras folder... + +2002-03-18 2.0.0-b1-rc1 Beta release candidate. Fixed the + stupid phantom light bug finally. + Tricked out the sun tracing a wee + bit as well, should be a little + faster + more accurate. Other little + bits fixed up as well. Thanks to K, + {wf}ShadowSpawn and RR2DO2 for their + help tracking these last bugs down. + +2002-03-19 2.0.0-b1-rc2 Increased some maximums, and got + rid of some cruft. + +2002-03-22 2.0.0-b1-rc3 Some minor optimizations. + +2002-03-30 2.0.0-b1-rc5 Now with fur (see extras/fur.shader). + +2002-04-01 2.0.0-b1-rc6 Enhanced with baby seal technology. + +2002-05-01 2.0.1 OK, better late than never. Fixed the + alphashadow = 255 = transparent bug. + +2002-06-24 2.1.0-b1 Added _foghull functionality. Works + like terrain "_shader" where + you don't need "textures/" prefix. + Also added q3map_normalmap. See + NVIDIA's website for Photoshop filter + to generate normalmaps from grayscale + heightmaps. This makes lightmaps + look bumpmapped. Currently 50% broken. + +2002-07-06 2.2.0-b1 Empty epairs now stripped from map, + fixing Wolfenstein crash bug. Func_* + entities are now fogged properly. + Sort of. This will be enhanced later. + Added the .srf file to store all the + extra crap I was hiding in the BSP. + It's an editable text file that + -light uses, so you can change the + samplesize w/o recompiling the map + (just -light'ing it). Changed color + normalization to clamping, because + it looks better. Other stuff got + fixed as well. + +2002-07-08 2.2.0-b2 thru b11 Test versions. Thanks jer and jet! + +2002-07-09 2.2.0-b12 Larger-than-life lightmaps are now + supported, up to 1024x1024. Add + q3map_lightmapSize H W to a shader + to use. Lightmaps are stored in + maps/{mapname}/_lm_NNN.tga and a + shader script q3map_{mapname}.shader + is generated. Also added + q3map_lightmapGamma N.N. Use a + value of 2.0 to simulate + r_overBrightBits 1 and + r_mapOverBrightBits 2 on external + lightmap images. diff --git a/tools/quake3/q3map2/changelog.q3map2.txt b/tools/quake3/q3map2/changelog.q3map2.txt new file mode 100644 index 00000000..d58c0242 --- /dev/null +++ b/tools/quake3/q3map2/changelog.q3map2.txt @@ -0,0 +1,709 @@ +Q3Map2 Version History + Changelog (Reverse Chronological Order) + +2.5.16 (2004-10-18) + +- New: -fixaas mode to reassociate an AAS file with a changed BSP +- New: -nostyles switch on light phase disabling lightstyles +- Using libmhash cryptographic hashing library instead of md5lib + + +2.5.15 (2004-07-08) + +- New: q3map_rgbGen, q3map_rgbMod or q3map_colorGen, q3map_colorMod +- New: q3map_alphaGen const (like q3map_alphaMod set) +- New: q3map_noVertexLight to suppress overwriting of vertex rgb +- q3map_backShader now works again, can be used instead of + q3map_cloneShader + q3map_invert on the target shader +- q3map_alphaMod volume brushes in func_group entities will now only + affect that entity's surfaces +- q3map_clipModel now works on trans/nonsolid shaders, allowing + simplified clipping surfaces in models +- Fixed bug in alphaMod code where only the first vertex was being + modded (thanks Ratty) +- Vis exits instead of crashing with divide-by-zero when there are + no portals on simple box maps +- All of a surface's lightmaps must be solid or not solid, fixing + ST coord problem with non-Raven games using lightstyles +- Reverted a change in the meta code which was causing lightmap coord + overflows and ugly self-shadowing errors on surface boundaries +- Any use of lightstyles automatically sets -nocollapse to reduce + total shader counts +- Tenebrae lightmap size changed to 512x512 + + +2.5.14 (2004-04-09) + +- New: -dirty mode courtesy of RaP7oR (dirtmapping/ambient occlusion) +- New: q3map_skyLight code using more uniform angular distribution about + the sky hemisphere +- New: q3map_alphaMod set N.N +- New: q3map_alphaMod scale N.N +- New: q3map_alphaMod volume - applies all other q3map_alphaMod directives + to each vertex inside a brush textured with this shader, allowing large + faded scrolling fire shaders, waterfalls, marquees, explicit dotProduct + terrain blending control, etc. +- Fixed a bug in alphaMod code where the type of the first alphaMod was + being used for all subsequent alphaMods +- Lowered vertex-lit surface limits to 999 verts, 1000 was breaking Quake 3 +- Tightened up solid lightmap tolerances +- Fixed bug in solid lightmap code where lightmap SD coords were being + incorrectly set + + +2.5.13 (2004-03-03) + +- New: -convert -format to convert between different BSP formats. + Example, to convert a Jedi Academy map to Enemy Territory: + -game ja -convert -format et +- New: -game etut support for Urban Terror on Enemy Territory +- New: -analyze mode for reverse engineering a BSP format +- New: -compensate N.N (default 1.0) for descaling lightmap/vertex/grid + data to compensate for ingame overbrighting. 4.0 is a good value for + Quake 3 if used in combination with -gamma 2.2 +- New: compensate/gamma per-game setting +- New: -light -cpma argument for "classic" (sic) vertex lighting +- Replaced malloc() with stack allocation in IlluminateRawLightmap for + most small/medium lightmap calculations +- Misc cleanups in q3map2.h +- Support for non-128x128 lightmaps +- The -meta process will now generate surfaces with more than 64 + verts that have non-lightmapped shaders +- Extended lightmap size tolerance to 2x for merging meta triangles in + maps with aggressive lightmapscale. Sorry kids! +- Moved surface finish pass (celshading, cloning) to final surface pass. + This should fix a bug involving fog/tesselation/celshading/cloning +- Solid-color lightmaps (within 1/255 in RGB) are detected and replaced + with a single pixel lightmap, saving space + + +2.5.12 (2004-01-18) + +- New: -dark option for dark lightmap/brush seams in -light (Unreal 1-ish) +- New: spawnflags 4 on misc_model entities makes them forcemeta/lightmapped + unless explicitly set as vertex lit in their shader(s). This can be + combined with spawnflags 2 (solid) as spawnflags 6 +- New: -gamma N.N switch sets lightmap/vertex gamma value for more + realistic lighting, instead of using high ambient/minlight. Default + is 1.0 (linear ramp), good values are between 1.5 and 2.2 +- Changed q3map_lightmapGamma N.N to q3map_lightmapBrightness N.N, to + better match its intended function +- Ported to VS.NET 2003 and GtkRadiant 1.5 (spog_branch) +- Fixed bug in _skybox maps where sunlight was leaking through brushes. + Thanks pjw! +- Fixed bug in ASE loader where models without submodels/submaterials + would not load properly. +- Fixed bug where q3map_tcGen was not being applied to models +- Optimized MergeMetaTriangles a bit +- Added KOR's fix to raytracer +- -bouncegrid no longer affects the lightgrid dir +- Added feature to PicoModel where spaces in material names in 3DS, ASE + and LWO models will mark the end of the shader name, so you can have + "textures/foo/bar 1" and "textures/foo/bar 2" in Lightwave, and both + surfaces will map to "textures/foo/bar" + + +2.5.11 (2003-12-01) + +- New: added support for _skybox entities to generate "portal sky" + surfaces in games w/o native support (Quake 3). _skybox entities have + 3 keys: _scale (default 64), and angle/angles (for rotation of the + skybox relative to the map) +- New: added -skyfix switch to BSP phase as a workaround hack for + the black GL_CLAMP border on skybox edges on ATI (and newer nvidia) + video cards. Note: unnecessary in ET or JA +- New: Added _anglescale to light entities for scaling angle attenuation. + Use a small value (< 1.0) to lessen the angle attenuation, and a high + value (> 1.0) for sharper, more faceted lighting +- New: Added _lightmapscale support to misc_model entities +- Custom shaders (external lightmaps, styles) will not be generated + if the find/replace text cannot be found +- Tightened up light culling epsilon from 1.0 to 0.125 to stop certain + surface lights from being incorrectly culled (thanks RasputiN!) +- Fixed bug where small 3 and 4 sided brush faces were getting fanned, + adding triangle/vertex counts +- Moved to Visual Studio .NET, with aggressive optimizations enabled +- Cleaned up missing image warnings +- Parsing images out of shader stages if not found explicit/implicitly +- Loads Enemy Territory implicitMap images if editor/light image not found + + +2.5.10 (2003-10-22) + +- New: Lightwave model support (beta) courtesy of RR2DO2 +- New: Heretic 2 FM model support courtesy of Nurail +- Re-enabled vertex cache friendly triangle reordering with fix +- Disabled triangle reordering on certain surfaces, including autosprite + shaders due to visual errors +- Fixed bug in radiosity where sorting of lights by style took forever. + Thanks ReBoOT! +- Fixed bug in sun lighting code where maps too far off the origin would + not be properly it by sun or sky light. Thanks MindLink! +- Entity causing a leak will be printed and selected in Radiant if BSP + monitoring is enabled. Requested by heeen +- Fixed odd bug causing 10x slowdown in lighting in some maps. Should + be back to 2.5.7 performance. Also fixed a couple old bugs related to + autosprite shader (point) lights and backsplash lights not being styled + or setup correctly + + +2.5.9 (2003-10-12) + +- Disabled triangle reordering for now (crashing on some maps) + + +2.5.8 (2003-10-02) + +- New: Added two new sun parameters: angular deviation (width of the sun in + degrees) and sampling count (jitters). This allows for decent approximation + of penumbra "half-shadow" effects from sunlight with 16+ samples. These + parameters are accessible for entity lights (including spots and suns) via + these entity keys: _deviance and _samples. To use in shaders, use the new + q3map_sunExt +- New: q3map_lightmapFilterRadius for light-emitting shaders. + Put *after* any q3map_sun directives or else your sun will be filtered. This + is good for eliminating the "stadium lighting" effect from q3map_skyLight. + Also usable as an entity key: _filterradius or _filter +- New: Quake 2 MD2 model support in PicoModel for misc_model entities + (thanks to Nurail!) +- Re-enabled vertex-cache-aware triangle reordering. Will probably have a + negligible effect on rendering performance, but can't hurt +- Added short-circuit to raytracer: any empty nodes (including children) are + ignored on sun traces +- Added BSP file size printout +- Filtering of any kind now disables adaptive supersampling on a per-light, + per-ightmap basis +- Fixed another _minlight <-> styled light interaction bug (thanks pjw!) + + +2.5.7 (2003-08-31) + +- New: Jedi Academy support via -game ja +- New: DDS (DXT1/3/5) texture support for future games +- Re-enabled q3map_surfaceModel support, and the 'oriented' flag works as well +- Re-enabled (fixed, really) large external lightmap support +- Fixed a bug in the model code that would cause a crash if an uninvertable + matrix was created +- Fixed a bug in Mathlib m4x4_t code where the tolerance for a singular matrix + was too low and crapping out on small (scaled down) matrices +- Fixed bug in divide-by-zero on lightmap efficiency calculation +- Added -force switch, allows unsupported BSP formats to (try) to be loaded + + +2.5.6 (2003-08-15) + +- New: can convert BSP files to MAP files via -convert -format map. Note: not + perfect by any means, as certain pieces of data are irretrievably lost, such + as func_group entities (including terrain specifics), brush face texturing + info, and light/misc_model entities with Q3Map2-generated BSPs + + +2.5.5 + +- New: -scale N.N mode to scale the BSP +- New: -light -lomem switch to supress trace BSP optimization. This + feature trades lighting performance for decreased memory usage +- New: Added negative light support (note: will not darken below _minlight value) + might screw up radiosity, haven't tested much. Should work with entity + lights, shader lights, sun/sky lights and radiosity. Lightfilter shadows + tint negative lights too, so the end effect is subtraction of the color +- New: Lightstyle support for non-Raven (JK2/SOF2) games, including Quake 3, + RTCW, ET, EF. Only works with lightmapped surfaces, use with care +- Fixed long standing terrain texturing bug, should produce exact desired + results all of the time now. May require fixing alphamaps that were + kludged together to accomodate this bug on existing maps +- Fixed bug (huh, wtf) causing misc_model surfaces to not be fogged +- Fixed bug where fog brushes wouldn't fog surfaces if a global map fog + shader was present +- Fixed bug where -patchmeta surfaces were being ignored for raytracing +- Fixed long-standing bug where twosided surfaces were not correctly + bouncing light. You can now have a foggy glass window that re-emits + bright light with q3map_bounce 3.0 or so +- Fixed really stupid bug in radiosity with bouncing styled lights +- Stripping .map and appending .bsp in -info mode +- Fixed bug where tesselated brush face surfaces were not being fogged +- Twosided surfaces (using cull disable/twosided/none) are now lit twosided +- Added tighter tolerance for alphashadow/lightfilter shadowing to raytracer + which fixed problem with double shadows from tracing near triangle seams +- Brush entities should now be properly fogged. Really. +- Styled lightmaps are no longer affected by _minlight (doh) + + +2.5.4 (2003-04-01) + +- New: q3map_tessSize support for JK2/SOF2 +- New: -lightmapsize N argument +- Fixed bug where switched styled lights weren't working correctly in SOF2/JK2 +- Fixed bug where external lightmaps with generated shaders were referencing + the wrong lightmap +- Fixed bug causing lightgrid brushes to be ignored +- Added variable sphere around trace sample points to inhibit occluder geometry + + +2.5.3 (2003-03-06) + +- New: SOF2/JK2 light styles now supported +- New: q3map_lightStyle N to set shader lightstyles +- New: Tenebrae 2 support via -game tenebrae +- New: -light -deluxe and -debugdeluxe for Tenebrae "deluxemap" static + lighting algorithm +- Light envelopes now properly clipped to the PVS +- q3map_vertexScale re-enabled (scales vertex lighting per-shader) +- Minor bug in brush bevel code corrected +- Brushes from func_group entities are correctly sorted on insertion +- Fixed a couple misc warnings to print "percent" instead of "%" +- Added -custinfoparms support to -light mode to suppress warnings +- _minlight, _minvertexlight and _mingridlight order independent, and now + allow for values of 0 + + +2.5.2 (2003-02-17) + +- Fixed crash bugs with global map fog +- Model loading really only warns once now + + +2.5.1 (2003-02-17) (Splash Damage internal release) + +- Added more Hella-Fast juice to light code. Overall should be 35% faster +- Refactored surface portion of raytracer code for less memory usage +- Changed UVW epsilon in raytracer to catch more edge cases +- Removed bounds check on occluded luxel finding, was causing more problems + than it was solving +- Adaptive antialiasing code now ignores unmapped luxels for better shadow + edges and higher performance +- Brushes in the BSP are now sorted opaque first +- Fixed Really Stupid bug causing MapRawLightmap to take about 4x as long +- Added optimization to make MapRawLightmap 2x as fast +- New non-sucky quadrilateral subdivision of patches for MapRawLightmap +- Patches with < 90 degrees of curvature are now box-lightmapped +- Patch vertex normals now correctly stored, fixing bug in 2.5.0 +- Prints warning if map with < 10% detail brushes is detected + + +2.5.0 (2003-02-14) (Splash Damage internal release) + +RAYTRACING AND SHADOW CALCULATION +- New raytracing code. Rewrote the raytracer to maximize efficiency on modern + "caulk-hull" maps. Uses triangle intercept code written by SPoG, based on code + by Tomas Moller and Ben Trumbore (Journal of Graphics Tools, 2(1):21-28, 1997) + and a biased octree leaf subdivision scheme by Y.T. +- Shadows (casting and receiving) are now controllable per-entity + New entity keys: "_castShadows" or "_cs" and "_receiveShadows" or "_rc" + Values: 0 = no shadows, 1 = worldspawn shadows, > 1 explicit shadow group, + negative values imply no worldspawn shadow interation. + *Entities, including model2 and RTCW misc_gamemodels can now cast shadows* + +RADIOSITY +- Bumped up default and smallest radiosity patch size. Performance should be + approximately 4x with a small quality tradeoff +- Radiosity patches now trace to centroid of triangle, and not bounds center +- Radiosity and surface lights are now nudged around a bit if in solid +- Radiosity light generation code is now thread-safe +- Radiosity -dump files now .map instead of .pfb +- Poorly worded "q3map_bounce" renamed to "q3map_bounceScale" (old still works) +- New -bounceonly switch to store only bounced light in the BSP (for Tenebrae) + +MISC LIGHTING +- Optimized case where light and sample are coplanar +- Forcing nudged luxels to stay within lightmap surfaces' bounds + +CURVED SURFACES +- New -subdivisions N argument, works with -patchmeta and -light for setting + patch level-of-detail. Default value is 8, use 4 to simulate default Q3 +- All patch tesselation code changed to create x-patterned tesselation for + better lighting +- Storing patch LOD info in the .srf file for better patch light/shadows + +FOG +- Reworked fog code to fix bad interation with fog and clipped models + +MODELS +- Entities with attached MD3/ASE misc_models now have their bounds correctly set +- Attached misc_models now support q3map_clipModel for solidity +- Missing models will only warn once, rather than spew errors + +MISC +- Metasurface merging no longer folds nonplanar triangles into planar surfaces + and vice-versa * +- Fixed Really Stupid Bug where entity numbering was not loaded by lighting code + resulting in lightmaps merging across entity boundaries * + +* Might result in slightly larger BSP. For maximum efficiency, ungroup + func_group entities before doing a final compile + +TODO ++ Document new shadow stuff ++ Merge adjacent light-casting triangles into convex windings + + +2.3.38 (2003-02-07) + +- New lighting code, return of Smoove-B. Intelligently antialises shadow edges + when you use the new -samples N switch. Get -extra quality in 1/3 the time +- New lightmap filtering code. Now using a proper 0.25/0.5/1.0 filter kernel. + Also operates on individual lightsources, so per-lightsource filter/kernel + settings are now possible +- New -patchmeta fixes, now does stitching and adaptive subdivision. + Thanks Sock! +- Nonsolid patches will no longer be in the BSP when run with -patchmeta +- Misc fog fixes, including q3map_noFog support in maps with global _fog + support (SOF2/JK2) +- Now stripping misc_model entities from the BSP +- Fixed disappearing face bug that's been present since 2.3.36. + Thanks Shadowspawn! + + +2.3.37 (2003-01-24) + +- Building from GtkRadiant CVS trunk +- Added new brush bevel code by MrElusive to fix lingering aas problems (sweet!) +- Added -snap N arg to BSP phase for axial bevel plane snapping to reduce + clipped model plane count (note: will muck with above, use with care) +- Patches in terrain entities should now have proper vertex alpha set +- Fixed bug in fur code where fur was being limited to 2 layers (thanks pazur) +- Reduced vertexlight search area to a reasonable size to keep vertex lighting + times down + + +2.3.36 (2003-01-15) + +- Plane hashing re-enabled (I suck) +- Plane hashing optimized (faster parsing of larger maps) +- Plane finding accuracy code enabled +- New ASE clipping code + + With above should be 10-50% faster + + Should generate 33% fewer planes + + Generates mostly-axial 5-sided polyhedra instead of pyramids, + for tighter 2-sided clipping +- New -light args: + + -scale N -- scales all lightsources (area, radiosity, point, sky) + + -sky[scale] N -- scales sky lights (q3map_skylight, q3map_sunlight) +- Changed fur code to scale fur offset based on original vertex alpha + + +2.3.35 (2003-01-14) + +- PicoModel now inverts ASE T coordinate +- BSP to ASE converter now inverts T coordinate +- Disabling 2.3.34 triangle optimization code until I find out why it crashes +- Fixed Conscript-q3map2 to use stack_size ld flags directly on Darwin/OS X +- Added Conscript-q3map2 to q3map2.dsp for easier Win32 edit, *nix target + + +2.3.34 (2003-01-08) + +- Building from merged GtkRadiant 1.2 -> 1.3 merged codebase +- IMPORTANT NEW CHANGE: Light entities are now STRIPPED from the BSP file. + They are re-read in by -light from the MAP file. This has two consequences: + + It is no longer necessary to re-BSP & re-vis a map in order to change + lighting. You can just change lights in the map file and run -light. + + Slightly smaller BSP file, due to fewer entities + + Faster loading time, as the game code doesn't have to deal with them +- Added new -ne (normal epsilon) and -de (distance epsilon) for tuning precision + of plane snapping to correct potential AAS/BSP issues +- Using latest PicoModel, with support for RTCW MDC models +- Surfaces per raw lightmap are now sorted by shader name, which should give + slightly better lightmap efficiency and lower in-game shader counts +- Adjusted model code to use correct m4x4_t code & angles key +- Minor bugfix in patch color gradient calculation code +- Silenced erroneous areaportal warning spew +- q3map_tcGen now works on model surfaces +- Using default radiosity subdivide of 256 again (should make radiosity faster) +- Enabled byte-swapping code so Q3Map2 can be compiled/run on little-endian + architectures (Mac OS X) + + +2.3.33 (2002-12-08) + +- Added new -bouncescale argument for radiosity scaling +- Added -pointscale and -areascale for consistent naming +- Radiosity patch subdivision code enhanced +- Hint portals split the BSP first (higher priority) +- Antiportal and areaportal faces split the BSP last, to minimize errors +- Areaportals work internally like hint and antiportals, so they no longer need + to be full brushes (the other sides can be skip) +- External lightmaps are now named "lm_NNNN.tga" in the maps/mapname dir +- Cleaned up some of -light argument processing +- Planar surfaces w/o lightmaps will no longer be tagged as MST_TRIANGLE_SOUP + (this fixes problems with Particle Studio particles dropping out of view) + + +2.3.32 (2002-11-30) + +- GtkRadiant (1.2.11) integration +- Added epsilon to texture plane choose code to eliminate numerical + inconsistencies on brush faces slanted at 45 degree angles (bug 637) +- Fixed bug in lightmap export after lighting when map contains 0 BSP lightmaps +- Adjusted some light tracing constants to fix certain brush/patch seam shadows +- Tinkered with skylight code again +- Fixed bug where lightgrid would be black if level was compiled with -nogrid +- Fixed -approx code to work in floating-point space, using _minlight +- Fixed bug where vertex light code was using invalid pvs data to create + light list for surface, leading to incorrect vertex lighting +- Fixed related bug in anti-light-leak code that was causing brush faces to go + black (bug 694) +- New: _minlight sets _minvertexlight and (new) _mingridlight automatically +- New: _mingridlight key to set minimum grid lighting + + +2.3.31 (2002-11-21) + +- Stitching the edges of lightmaps on patches that wrap around (cyls and cones) + so the seam is no longer visible +- The -patchmeta switch works better now, the patches are still stored in the + BSP for collision, but are pre-tesselated into nonplanar meta surfaces for + more efficient rendering +- Better, more uniform lightmap sample position finding on patch meshes +- Moved q3map_tcMod and q3map_alphaMod processing to the final phase +- New: q3map_skylight AMOUNT ITERATIONS to replace surfacelight on sky surfaces + for much faster and more uniform sky illumination + + +2.3.30 (Splash Damage internal release) + +- Fixed bug in PicoModel ASE material parsing code +- Fixed a few seam/lightmap precision/projection errors +- Increased MAX_SHADER FILES to 1024 and fixed overrun error when more than that + number of shaders was listed in shaderlist.txt +- Increased a few compiler maximums for larger maps +- New: -np N switch on BSP phase, works like -shadeangle, in that it forces all + planar shaders to be nonplanar with the shading angle specified +- New: -nohint switch on BSP phase, omits hint brushes from compile for testing +- New: -debugaxis switch on light mode. Colors lightmaps based on their lightmap + axis (which direction the lightmap was projected on) +- New: -debugorigin switch on light mode. Colors lightmaps based on the luxel + origin relative to the raw lightmap's bounding box +- New: -debugcluster switch on light mode. Colors lightmaps based on the pvs + cluster the luxel falls into +- New: -convert switch to convert BSP to ASE file (experimental) +- New: q3map_lightmapmergable directive to allow terrain to be mapped onto a + single lightmap page for seamless terrain shadows + + +2.3.29 (2002-11-03) + +- Merged with latest CVS, fixed minor issues with matrix order +- Fixed minor Sys_FPrintf/Sys_Printf substitution typo in Q3Map2 +- Expanded debug colors to 12 for debugging surface meshes +- PicoModel: fixed ASE loader to support > 1 texture coordinate per-vertex, + so more models supported correctly, also loading vertex normals +- PicoModel: md3 shader names are now cleaned. Suffixes (such as .tga or .jpg) + are stripped, and \ path separators are changed to / +- New: Add :q3map to the end of any shader name, and it will be interpreted as + the named shader minus :q3map. Example: + textures/shaderlab/concrete:q3map -> textures/shaderlab/concrete + One potential use is the -approx feature to collapse lightmapped surfaces + into vertexlit surfaces, saving lightmap space/memory +- New: q3map_clipModel -- does what you think it does, sort of. This code ix + really experimental, and should *only* be used on large models such as terrain + (not small decorative models). This code will be evolving. Note: the shader's + surfaceparms are inherited by the magic clip brush, so if you have nonsolid + in your model's shader that uses q3map_clipModel, then the brush will also + be nonsolid + + +2.3.28 (2002-11-01) + +- Bug 654 (http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=654): + Fixed problem where brush faces, drawsurfaces, and surfaceparms weren't living + together in perfect harmony (terrain surfaceparms now inherited by brushes) +- Nodraw fog works now, albeit when you're underneath, surfaces above don't get + fogged properly. Could be good for foggy water where you want the above-water + portions to only be occluded by the water surface +- Bug 656 (http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=656): + Number of lightgrid points displayed (byte size is currently out of proportion + due to internal storage format) when Q3Map is called with the -info switch +- Fixed wack surface merging bug where code would attempt to merge triangles not + adjacent to the current set, causing bad lightmap projections on nonplanar + surfaces +- Fixed tiny 1-character bug in 2d lightmap texture allocator where adjacent + luxels were being checked for occlusion rather than the actual source luxel + + +2.3.27 (2002-10-31) Happy Halloween! + +- Fixed minor bug in scriplib bugfix where the last character in a file wasn't + being read. +- Fixed bug where 0-area or bogus triangles were causing crash in MapRawLightmap + if they used a shader with a normalmap (thanks ShadowSpawn) +- Fixed bug where lightmaps were getting hosed levelwide on a prerelease version + of 2.3.27 +- Fixed bug where lightmaps were getting knackered on models and certain patches +- Merged latest PicoModel version from seaw0lf, adding support for ASE and WF OBJ + models (preliminary) +- Increased MAX_MAP_PLANES to 0x40000 (~256k) + +Known issues: +- Lightmap projection and surface merging on large ASE models sometimes flakes +- Surface to brush surfaceparm propogation doesn't work properly with large + metasurfaces: http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=654 + + +2.3.26 (2002-10-27) + +- Now using GtkRadiant's libpng and zlib config (linked as DLLs) +- Fixed bug in script parser where repeat calls to GetToken() were causing + memory corruption +- Fixed SOF2 -rename bug +- When using -game sof2 or -game jk2, the -flares argument is implied +- Added -noflares argument to disable the above behavior +- Added support for flares on entities. Use one of the following keys: + "_flare" "1" -- use default flare (different for each game) + "_flareshader" "path/to/flareshader" -- use a specific flare shader + Note: This only matters in SOF2/JK2 now. Make a light targetted (a spotlight) + to get it to aim the correct direction, otherwise it defaults to pointing + downward. You cannot have omnidirectional flares +- Lightgrid size is automatically increased to accomodate large maps. The + MAX_MAP_LIGHTGRID error will never happen again + + +2.3.25 (2002-10-22) + +- Go Giants! +- Fixed bug where Q3Map would crash on writing the BSP after the light stage. + Thanks to Rap7or (#q3map) and loon8 (Q3W forums) [bug 641] +- Fixed bug where surface lights were not affecting the light grid properly. + Thanks to Shadowspawn and djbob [bug 642] +- NEW: Added -faster support to lightgrid calculations while fixing previous bug +- NEW: Changed it so the BSP file is written to a temp file first, then renamed. + This should prevent BSP file corruption on crashes during writes + + +2.3.24 (2002-10-20) + +- Fixed numerous outstanding bugs and issues. +- Normal interpolation is now improved. It's slightly slower, but more 'correct' + in cases where you have 10 triangles in one plane and 1 triangle in another + meeting up and the 10 triangles were over-affecting the average. Only new + identical normals are averaged now. This change affects phong shading, meta + surfaces, and PicoModel +- PicoModel up to version 0.7.6, BSD license, better 3DS model support +- PicoModel library now fixes broken normals on MD3 and 3DS models +- Bumpmapping code is improved. The correct tangent vectors per-triangle are + now calculated so the bumpmaps are consistent with regards to light direction +- Metasurface merging code optimized. Should be about 100x as fast on complex + maps or maps using models with high triangle counts +- Vertexlight code tweaked a bit +- Triangle/winding orders now more consistent. Tesselated surfaces will have + a uniform triangle ordering (thanks RR2DO2) +- NEW: "vertexDeform move" now parsed and surfaces are merged into the + appropriate BSP leaves they may enter into (thanks to Bart Vrijkorte) +- NEW: shader command: q3map_alphaMod. Currently takes a single form: + q3map_alphaMod dotproduct ( NX NY NZ ) + where NX NY NZ are a unit normal (length of 1.0) specifying direction. + An example use would be snow in a shader's 2nd pass, using alphaFunc or + blendFunc: + q3map_alphaMod dotproduct ( 0 0 1 ) // surfaces facing upwards have snow + (idea contributed by RR2DO2) + + +2.3.23 (2002-10-18) + +- In my haste to release the previous version, I neglected to give credit where + it was due. Seaw0lf had as much (probably more) to do with the new model + loading library (PicoModel). Because of his efforts, you can load 3DS models + and use them in misc_model entities. +- PicoModel model library up to version 0.7. Improved 3DS support, more stable. +- Surface models still not reenabled. Soon. :) +- You can now remap a misc_model's shaders like this: + Key "_remapNN" "the/model/shader;the/real/shader" + This works just like TA terrain vertexRemapShader key. You can also supply a + * glob for the source shader if you want all your model's shaders to use the + specified shader: + "_remap" "*;models/mapobjects/tree/bark" + + +2.3.22 (2002-10-16) + +- Moving to sensible Linux-style versioning. +- The misc_model code has been completely rewritten, breaking surface models. + Surface models will reappear in the next release, once the new model API has + stablized. +- New: MD3 and 3D Studio 3DS models now natively supported. +- The misc_model "angles" key now supported. Values: "pitch yaw roll" in keeping + with standard Quake 3 angles order. +- Models scaled with "modelscale_vec" now have proper normal scaling/rotation + (thanks SPOG). +- Models can now be lightmapped. +- Models can now have > 1000 vertexes per surface. +- For best results for above, add the following to models' shaders: + q3map_splotchfix + q3map_nonplanar +- 3DS models' MATERIAL NAMES ARE THE FINAL Q3 SHADER NAMES. YOU HAVE BEEN WARNED. +- Models are generally 13373R. :) + + +2.3.0-a21 (2002-10-02) + +- Fixed a stack of really stupid bugs in the lightgrid code. Should be faster + and more predictable now. +- SOF2/JK2 lightgrid now compiled. This is the first version of Q3Map2 that can + compile full, release-worthy SOF2 and JK2 maps. +- SOF2/JK2 damageshader and damagable brush faces should work correctly now. + + +2.3.0-a20 (2002-09-26) + +- SOF2/JK2 worldspawn "fog" (and "_fog") shader key support for levelwide fog +- SOF2/JK2 light "scale" key to scale light brightness +- SOF2/JK2 -rename function for _bsp and _rmg_bsp shader renaming + + +2.3.0-a19 (2002-09-24) + +- Shaders can now be subclassed (Q3Map relavant portions only, such as + surfaceparms, lighting, texture projection, etc). To subclass an existing + shader, add "q3map_baseshader X" where X is the name of the base shader. +- Preliminary auto-model distribution over surfaces. You can now have things + like grass and tree models automatically distributed across your terrain + or other surfaces. To wit: + + q3map_surfacemodel models/mapobjects/tree2/tree2.md3 64 0.001 0.5 4.0 0 360 1 + + q3map_surfacemodel + + + The last flag is 1 or 0, and sets whether the model gets fitted to the + orientation of the surface. Not functional yet. See screenshots page for + shots of this in action. + + +2.3.0-a18 (2002-09-21) + +- misc_models can now be attached to any brush model entity. Just target the + brush entity with the misc_model (select model, then entity, hit Ctrl+K) +- q3map_tcMod translate (or shift or offset) +- q3map_tcMod rotate (rotates around origin, not center) +- q3map_tcMod scale +- Metasurface merging now much, much better. Merges into roughly rectangular or + square areas wherever possible +- q3map_terrain no longer sets Z-axis lightmap projection. It must be set in + the terrain layer shaders if you want previous behavior +- Worlspawn _blocksize key now supports 3 elements for setting X Y and Z splits + independently of each other (use a value of 0 for no splits on that axis) +- Misc bugfixes + + +2.3.0-a1 through 2.3.0-a17 (2002-07 through 2002-09-20) + +- Elite Force support (via -game ef) +- SOF2 and JK2 support (via -game sof2 or -game jk2) +- All new image handling with PNG support +- q3map_lightimage specifies image for radiosity and lighting +- External lightmaps, set using q3map_lightmapsize . Up to + 1024 x 1024 supported. +- q3map_lightmapGamma sets the brightness scale of a lightmap +- q3map_lightmapsampleoffset to fix glitches in lightmapped terrain +- Tons more features and bugfixes. See the forum threads for details/screenshots +- Two new surfaceparms, "antiportal" and "skip," and associated shaders, for + allowing the mapper to more cleanly set up visibility data +- Lightmaps will always have square texels now (no more stretching) +- Vertex light improvements +- Light grid improvements +- q3map_lightrgb support for RTCW + + + + + + diff --git a/tools/quake3/q3map2/convert_ase.c b/tools/quake3/q3map2/convert_ase.c new file mode 100644 index 00000000..2be25a43 --- /dev/null +++ b/tools/quake3/q3map2/convert_ase.c @@ -0,0 +1,375 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define CONVERT_ASE_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* +ConvertSurface() +converts a bsp drawsurface to an ase chunk +*/ + +static void ConvertSurface( FILE *f, bspModel_t *model, int modelNum, bspDrawSurface_t *ds, int surfaceNum, vec3_t origin ) +{ + int i, v, face, a, b, c; + bspDrawVert_t *dv; + vec3_t normal; + char name[ 1024 ]; + + + /* ignore patches for now */ + if( ds->surfaceType != MST_PLANAR && ds->surfaceType != MST_TRIANGLE_SOUP ) + return; + + /* print object header for each dsurf */ + sprintf( name, "mat%dmodel%dsurf%d", ds->shaderNum, modelNum, surfaceNum ); + fprintf( f, "*GEOMOBJECT\t{\r\n" ); + fprintf( f, "\t*NODE_NAME\t\"%s\"\r\n", name ); + fprintf( f, "\t*NODE_TM\t{\r\n" ); + fprintf( f, "\t\t*NODE_NAME\t\"%s\"\r\n", name ); + fprintf( f, "\t\t*INHERIT_POS\t0\t0\t0\r\n" ); + fprintf( f, "\t\t*INHERIT_ROT\t0\t0\t0\r\n" ); + fprintf( f, "\t\t*INHERIT_SCL\t0\t0\t0\r\n" ); + fprintf( f, "\t\t*TM_ROW0\t1.0\t0\t0\r\n" ); + fprintf( f, "\t\t*TM_ROW1\t0\t1.0\t0\r\n" ); + fprintf( f, "\t\t*TM_ROW2\t0\t0\t1.0\r\n" ); + fprintf( f, "\t\t*TM_ROW3\t0\t0\t0\r\n" ); + fprintf( f, "\t\t*TM_POS\t%f\t%f\t%f\r\n", origin[ 0 ], origin[ 1 ], origin[ 2 ] ); + fprintf( f, "\t}\r\n" ); + + /* print mesh header */ + fprintf( f, "\t*MESH\t{\r\n" ); + fprintf( f, "\t\t*TIMEVALUE\t0\r\n" ); + fprintf( f, "\t\t*MESH_NUMVERTEX\t%d\r\n", ds->numVerts ); + fprintf( f, "\t\t*MESH_NUMFACES\t%d\r\n", ds->numIndexes / 3 ); + switch( ds->surfaceType ) + { + case MST_PLANAR: + fprintf( f, "\t\t*COMMENT\t\"SURFACETYPE\tMST_PLANAR\"\r\n" ); + break; + case MST_TRIANGLE_SOUP: + fprintf( f, "\t\t*COMMENT\t\"SURFACETYPE\tMST_TRIANGLE_SOUP\"\r\n" ); + break; + } + + /* export vertex xyz */ + fprintf( f, "\t\t*MESH_VERTEX_LIST\t{\r\n" ); + for( i = 0; i < ds->numVerts; i++ ) + { + v = i + ds->firstVert; + dv = &bspDrawVerts[ v ]; + fprintf( f, "\t\t\t*MESH_VERTEX\t%d\t%f\t%f\t%f\r\n", i, dv->xyz[ 0 ], dv->xyz[ 1 ], dv->xyz[ 2 ] ); + } + fprintf( f, "\t\t}\r\n" ); + + /* export vertex normals */ + fprintf( f, "\t\t*MESH_NORMALS\t{\r\n" ); + for( i = 0; i < ds->numIndexes; i += 3 ) + { + face = (i / 3); + a = bspDrawIndexes[ i + ds->firstIndex ]; + b = bspDrawIndexes[ i + ds->firstIndex + 1 ]; + c = bspDrawIndexes[ i + ds->firstIndex + 2 ]; + VectorCopy( bspDrawVerts[ a ].normal, normal ); + VectorAdd( normal, bspDrawVerts[ b ].normal, normal ); + VectorAdd( normal, bspDrawVerts[ c ].normal, normal ); + if( VectorNormalize( normal, normal ) ) + fprintf( f, "\t\t\t*MESH_FACENORMAL\t%d\t%f\t%f\t%f\r\n", face, normal[ 0 ], normal[ 1 ], normal[ 2 ] ); + } + for( i = 0; i < ds->numVerts; i++ ) + { + v = i + ds->firstVert; + dv = &bspDrawVerts[ v ]; + fprintf( f, "\t\t\t*MESH_VERTEXNORMAL\t%d\t%f\t%f\t%f\r\n", i, dv->normal[ 0 ], dv->normal[ 1 ], dv->normal[ 2 ] ); + } + fprintf( f, "\t\t}\r\n" ); + + /* export faces */ + fprintf( f, "\t\t*MESH_FACE_LIST\t{\r\n" ); + for( i = 0; i < ds->numIndexes; i += 3 ) + { + face = (i / 3); + a = bspDrawIndexes[ i + ds->firstIndex ]; + c = bspDrawIndexes[ i + ds->firstIndex + 1 ]; + b = bspDrawIndexes[ i + ds->firstIndex + 2 ]; + fprintf( f, "\t\t\t*MESH_FACE\t%d\tA:\t%d\tB:\t%d\tC:\t%d\tAB:\t1\tBC:\t1\tCA:\t1\t*MESH_SMOOTHING\t0\t*MESH_MTLID\t0\r\n", + face, a, b, c ); + } + fprintf( f, "\t\t}\r\n" ); + + /* export vertex st */ + fprintf( f, "\t\t*MESH_NUMTVERTEX\t%d\r\n", ds->numVerts ); + fprintf( f, "\t\t*MESH_TVERTLIST\t{\r\n" ); + for( i = 0; i < ds->numVerts; i++ ) + { + v = i + ds->firstVert; + dv = &bspDrawVerts[ v ]; + fprintf( f, "\t\t\t*MESH_TVERT\t%d\t%f\t%f\t%f\r\n", i, dv->st[ 0 ], (1.0 - dv->st[ 1 ]), 1.0f ); + } + fprintf( f, "\t\t}\r\n" ); + + /* export texture faces */ + fprintf( f, "\t\t*MESH_NUMTVFACES\t%d\r\n", ds->numIndexes / 3 ); + fprintf( f, "\t\t*MESH_TFACELIST\t{\r\n" ); + for( i = 0; i < ds->numIndexes; i += 3 ) + { + face = (i / 3); + a = bspDrawIndexes[ i + ds->firstIndex ]; + c = bspDrawIndexes[ i + ds->firstIndex + 1 ]; + b = bspDrawIndexes[ i + ds->firstIndex + 2 ]; + fprintf( f, "\t\t\t*MESH_TFACE\t%d\t%d\t%d\t%d\r\n", face, a, b, c ); + } + fprintf( f, "\t\t}\r\n" ); + + /* print mesh footer */ + fprintf( f, "\t}\r\n" ); + + /* print object footer */ + fprintf( f, "\t*PROP_MOTIONBLUR\t0\r\n" ); + fprintf( f, "\t*PROP_CASTSHADOW\t1\r\n" ); + fprintf( f, "\t*PROP_RECVSHADOW\t1\r\n" ); + fprintf( f, "\t*MATERIAL_REF\t%d\r\n", ds->shaderNum ); + fprintf( f, "}\r\n" ); +} + + + +/* +ConvertModel() +exports a bsp model to an ase chunk +*/ + +static void ConvertModel( FILE *f, bspModel_t *model, int modelNum, vec3_t origin ) +{ + int i, s; + bspDrawSurface_t *ds; + + + /* go through each drawsurf in the model */ + for( i = 0; i < model->numBSPSurfaces; i++ ) + { + s = i + model->firstBSPSurface; + ds = &bspDrawSurfaces[ s ]; + ConvertSurface( f, model, modelNum, ds, s, origin ); + } +} + + + +/* +ConvertShader() +exports a bsp shader to an ase chunk +*/ + +/* + *MATERIAL 0 { + *MATERIAL_NAME "models/test/rock16l" + *MATERIAL_CLASS "Standard" + *MATERIAL_AMBIENT 0.5882 0.5882 0.5882 + *MATERIAL_DIFFUSE 0.5882 0.5882 0.5882 + *MATERIAL_SPECULAR 0.5882 0.5882 0.5882 + *MATERIAL_SHINE 0.0000 + *MATERIAL_SHINESTRENGTH 0.0000 + *MATERIAL_TRANSPARENCY 0.0000 + *MATERIAL_WIRESIZE 1.0000 + *MATERIAL_SHADING Phong + *MATERIAL_XP_FALLOFF 0.0000 + *MATERIAL_SELFILLUM 0.0000 + *MATERIAL_FALLOFF In + *MATERIAL_XP_TYPE Filter + *MAP_DIFFUSE { + *MAP_NAME "Map #2" + *MAP_CLASS "Bitmap" + *MAP_SUBNO 1 + *MAP_AMOUNT 1.0000 + *BITMAP "models/test/rock16l" + *MAP_TYPE Screen + *UVW_U_OFFSET 0.0000 + *UVW_V_OFFSET 0.0000 + *UVW_U_TILING 1.0000 + *UVW_V_TILING 1.0000 + *UVW_ANGLE 0.0000 + *UVW_BLUR 1.0000 + *UVW_BLUR_OFFSET 0.0000 + *UVW_NOUSE_AMT 1.0000 + *UVW_NOISE_SIZE 1.0000 + *UVW_NOISE_LEVEL 1 + *UVW_NOISE_PHASE 0.0000 + *BITMAP_FILTER Pyramidal + } + } +*/ + +static void ConvertShader( FILE *f, bspShader_t *shader, int shaderNum ) +{ + shaderInfo_t *si; + char *c, filename[ 1024 ]; + + + /* get shader */ + si = ShaderInfoForShader( shader->shader ); + if( si == NULL ) + { + Sys_Printf( "WARNING: NULL shader in BSP\n" ); + return; + } + + /* set bitmap filename */ + if( si->shaderImage->filename[ 0 ] != '*' ) + strcpy( filename, si->shaderImage->filename ); + else + sprintf( filename, "%s.tga", si->shader ); + for( c = filename; *c != '\0'; c++ ) + if( *c == '/' ) + *c = '\\'; + + /* print shader info */ + fprintf( f, "\t*MATERIAL\t%d\t{\r\n", shaderNum ); + fprintf( f, "\t\t*MATERIAL_NAME\t\"%s\"\r\n", shader->shader ); + fprintf( f, "\t\t*MATERIAL_CLASS\t\"Standard\"\r\n" ); + fprintf( f, "\t\t*MATERIAL_DIFFUSE\t%f\t%f\t%f\r\n", si->color[ 0 ], si->color[ 1 ], si->color[ 2 ] ); + fprintf( f, "\t\t*MATERIAL_SHADING Phong\r\n" ); + + /* print map info */ + fprintf( f, "\t\t*MAP_DIFFUSE\t{\r\n" ); + fprintf( f, "\t\t\t*MAP_NAME\t\"%s\"\r\n", shader->shader ); + fprintf( f, "\t\t\t*MAP_CLASS\t\"Bitmap\"\r\n"); + fprintf( f, "\t\t\t*MAP_SUBNO\t1\r\n" ); + fprintf( f, "\t\t\t*MAP_AMOUNT\t1.0\r\n" ); + fprintf( f, "\t\t\t*MAP_TYPE\tScreen\r\n" ); + fprintf( f, "\t\t\t*BITMAP\t\"..\\%s\"\r\n", filename ); + fprintf( f, "\t\t\t*BITMAP_FILTER\tPyramidal\r\n" ); + fprintf( f, "\t\t}\r\n" ); + + fprintf( f, "\t}\r\n" ); +} + + + +/* +ConvertBSPToASE() +exports an 3d studio ase file from the bsp +*/ + +int ConvertBSPToASE( char *bspName ) +{ + int i, modelNum; + FILE *f; + bspShader_t *shader; + bspModel_t *model; + entity_t *e; + vec3_t origin; + const char *key; + char name[ 1024 ], base[ 1024 ]; + + + /* note it */ + Sys_Printf( "--- Convert BSP to ASE ---\n" ); + + /* create the ase filename from the bsp name */ + strcpy( name, bspName ); + StripExtension( name ); + strcat( name, ".ase" ); + Sys_Printf( "writing %s\n", name ); + + ExtractFileBase( bspName, base ); + strcat( base, ".bsp" ); + + /* open it */ + f = fopen( name, "wb" ); + if( f == NULL ) + Error( "Open failed on %s\n", name ); + + /* print header */ + fprintf( f, "*3DSMAX_ASCIIEXPORT\t200\r\n" ); + fprintf( f, "*COMMENT\t\"Generated by Q3Map2 (ydnar) -convert -format ase\"\r\n" ); + fprintf( f, "*SCENE\t{\r\n" ); + fprintf( f, "\t*SCENE_FILENAME\t\"%s\"\r\n", base ); + fprintf( f, "\t*SCENE_FIRSTFRAME\t0\r\n" ); + fprintf( f, "\t*SCENE_LASTFRAME\t100\r\n" ); + fprintf( f, "\t*SCENE_FRAMESPEED\t30\r\n" ); + fprintf( f, "\t*SCENE_TICKSPERFRAME\t160\r\n" ); + fprintf( f, "\t*SCENE_BACKGROUND_STATIC\t0.0000\t0.0000\t0.0000\r\n" ); + fprintf( f, "\t*SCENE_AMBIENT_STATIC\t0.0000\t0.0000\t0.0000\r\n" ); + fprintf( f, "}\r\n" ); + + /* print materials */ + fprintf( f, "*MATERIAL_LIST\t{\r\n" ); + fprintf( f, "\t*MATERIAL_COUNT\t%d\r\n", numBSPShaders ); + for( i = 0; i < numBSPShaders; i++ ) + { + shader = &bspShaders[ i ]; + ConvertShader( f, shader, i ); + } + fprintf( f, "}\r\n" ); + + /* walk entity list */ + for( i = 0; i < numEntities; i++ ) + { + /* get entity and model */ + e = &entities[ i ]; + if( i == 0 ) + modelNum = 0; + else + { + key = ValueForKey( e, "model" ); + if( key[ 0 ] != '*' ) + continue; + modelNum = atoi( key + 1 ); + } + model = &bspModels[ modelNum ]; + + /* get entity origin */ + key = ValueForKey( e, "origin" ); + if( key[ 0 ] == '\0' ) + VectorClear( origin ); + else + GetVectorForKey( e, "origin", origin ); + + /* convert model */ + ConvertModel( f, model, modelNum, origin ); + } + + /* close the file and return */ + fclose( f ); + + /* return to sender */ + return 0; +} + + + diff --git a/tools/quake3/q3map2/convert_map.c b/tools/quake3/q3map2/convert_map.c new file mode 100644 index 00000000..38fc738f --- /dev/null +++ b/tools/quake3/q3map2/convert_map.c @@ -0,0 +1,634 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define CONVERT_MAP_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* +ConvertBrush() +exports a map brush +*/ + +#define SNAP_FLOAT_TO_INT 4 +#define SNAP_INT_TO_FLOAT (1.0 / SNAP_FLOAT_TO_INT) + +typedef vec_t vec2_t[2]; + +static vec_t Det3x3(vec_t a00, vec_t a01, vec_t a02, + vec_t a10, vec_t a11, vec_t a12, + vec_t a20, vec_t a21, vec_t a22) +{ + return + a00 * (a11 * a22 - a12 * a21) + - a01 * (a10 * a22 - a12 * a20) + + a02 * (a10 * a21 - a11 * a20); +} + +void GetBestSurfaceTriangleMatchForBrushside(side_t *buildSide, bspDrawVert_t *bestVert[3]) +{ + bspDrawSurface_t *s; + int i; + int t; + vec_t best = 0; + vec_t thisarea; + vec3_t normdiff; + vec3_t v1v0, v2v0, norm; + bspDrawVert_t *vert[3]; + winding_t *polygon; + plane_t *buildPlane = &mapplanes[buildSide->planenum]; + int matches = 0; + + // first, start out with NULLs + bestVert[0] = bestVert[1] = bestVert[2] = NULL; + + // brute force through all surfaces + for(s = bspDrawSurfaces; s != bspDrawSurfaces + numBSPDrawSurfaces; ++s) + { + if(s->surfaceType != MST_PLANAR && s->surfaceType != MST_TRIANGLE_SOUP) + continue; + if(strcmp(buildSide->shaderInfo->shader, bspShaders[s->shaderNum].shader)) + continue; + for(t = 0; t + 3 <= s->numIndexes; t += 3) + { + vert[0] = &bspDrawVerts[s->firstVert + bspDrawIndexes[s->firstIndex + t + 0]]; + vert[1] = &bspDrawVerts[s->firstVert + bspDrawIndexes[s->firstIndex + t + 1]]; + vert[2] = &bspDrawVerts[s->firstVert + bspDrawIndexes[s->firstIndex + t + 2]]; + if(s->surfaceType == MST_PLANAR) + { + VectorSubtract(vert[0]->normal, buildPlane->normal, normdiff); if(VectorLength(normdiff) >= normalEpsilon) continue; + VectorSubtract(vert[1]->normal, buildPlane->normal, normdiff); if(VectorLength(normdiff) >= normalEpsilon) continue; + VectorSubtract(vert[2]->normal, buildPlane->normal, normdiff); if(VectorLength(normdiff) >= normalEpsilon) continue; + } + else + { + // this is more prone to roundoff errors, but with embedded + // models, there is no better way + VectorSubtract(vert[1]->xyz, vert[0]->xyz, v1v0); + VectorSubtract(vert[2]->xyz, vert[0]->xyz, v2v0); + CrossProduct(v2v0, v1v0, norm); + VectorNormalize(norm, norm); + VectorSubtract(norm, buildPlane->normal, normdiff); if(VectorLength(normdiff) >= normalEpsilon) continue; + } + if(abs(DotProduct(vert[0]->xyz, buildPlane->normal) - buildPlane->dist) >= distanceEpsilon) continue; + if(abs(DotProduct(vert[1]->xyz, buildPlane->normal) - buildPlane->dist) >= distanceEpsilon) continue; + if(abs(DotProduct(vert[2]->xyz, buildPlane->normal) - buildPlane->dist) >= distanceEpsilon) continue; + // Okay. Correct surface type, correct shader, correct plane. Let's start with the business... + polygon = CopyWinding(buildSide->winding); + for(i = 0; i < 3; ++i) + { + // 0: 1, 2 + // 1: 2, 0 + // 2; 0, 1 + vec3_t *v1 = &vert[(i+1)%3]->xyz; + vec3_t *v2 = &vert[(i+2)%3]->xyz; + vec3_t triNormal; + vec_t triDist; + vec3_t sideDirection; + // we now need to generate triNormal and triDist so that they represent the plane spanned by normal and (v2 - v1). + VectorSubtract(*v2, *v1, sideDirection); + CrossProduct(sideDirection, buildPlane->normal, triNormal); + triDist = DotProduct(*v1, triNormal); + ChopWindingInPlace(&polygon, triNormal, triDist, distanceEpsilon); + if(!polygon) + goto exwinding; + } + thisarea = WindingArea(polygon); + if(thisarea > 0) + ++matches; + if(thisarea > best) + { + best = thisarea; + bestVert[0] = vert[0]; + bestVert[1] = vert[1]; + bestVert[2] = vert[2]; + } + FreeWinding(polygon); +exwinding: + ; + } + } + //if(strncmp(buildSide->shaderInfo->shader, "textures/common/", 16)) + // fprintf(stderr, "brushside with %s: %d matches (%f area)\n", buildSide->shaderInfo->shader, matches, best); +} + +static void ConvertBrush( FILE *f, int num, bspBrush_t *brush, vec3_t origin ) +{ + int i, j; + bspBrushSide_t *side; + side_t *buildSide; + bspShader_t *shader; + char *texture; + bspPlane_t *plane; + plane_t *buildPlane; + vec3_t pts[ 3 ]; + bspDrawVert_t *vert[3]; + int valid; + + + /* start brush */ + fprintf( f, "\t// brush %d\n", num ); + fprintf( f, "\t{\n" ); + fprintf( f, "\tbrushDef\n" ); + fprintf( f, "\t{\n" ); + + /* clear out build brush */ + for( i = 0; i < buildBrush->numsides; i++ ) + { + buildSide = &buildBrush->sides[ i ]; + if( buildSide->winding != NULL ) + { + FreeWinding( buildSide->winding ); + buildSide->winding = NULL; + } + } + buildBrush->numsides = 0; + + /* iterate through bsp brush sides */ + for( i = 0; i < brush->numSides; i++ ) + { + /* get side */ + side = &bspBrushSides[ brush->firstSide + i ]; + + /* get shader */ + if( side->shaderNum < 0 || side->shaderNum >= numBSPShaders ) + continue; + shader = &bspShaders[ side->shaderNum ]; + if( !Q_stricmp( shader->shader, "default" ) || !Q_stricmp( shader->shader, "noshader" ) ) + continue; + + /* get plane */ + plane = &bspPlanes[ side->planeNum ]; + + /* add build side */ + buildSide = &buildBrush->sides[ buildBrush->numsides ]; + buildBrush->numsides++; + + /* tag it */ + buildSide->shaderInfo = ShaderInfoForShader( shader->shader ); + buildSide->planenum = side->planeNum; + buildSide->winding = NULL; + } + + /* make brush windings */ + if( !CreateBrushWindings( buildBrush ) ) + return; + + /* iterate through build brush sides */ + for( i = 0; i < buildBrush->numsides; i++ ) + { + /* get build side */ + buildSide = &buildBrush->sides[ i ]; + + /* get plane */ + buildPlane = &mapplanes[ buildSide->planenum ]; + + /* dummy check */ + if( buildSide->shaderInfo == NULL || buildSide->winding == NULL ) + continue; + + // st-texcoords -> texMat block + // start out with dummy + VectorSet(buildSide->texMat[0], 1/32.0, 0, 0); + VectorSet(buildSide->texMat[1], 0, 1/32.0, 0); + + // find surface for this side (by brute force) + // surface format: + // - meshverts point in pairs of three into verts + // - (triangles) + // - find the triangle that has most in common with our side + GetBestSurfaceTriangleMatchForBrushside(buildSide, vert); + valid = 0; + + if(vert[0] && vert[1] && vert[2]) + { + int i; + vec3_t texX, texY; + vec3_t xy1I, xy1J, xy1K; + vec2_t stI, stJ, stK; + vec_t D, D0, D1, D2; + + ComputeAxisBase(buildPlane->normal, texX, texY); + + VectorSet(xy1I, DotProduct(vert[0]->xyz, texX), DotProduct(vert[0]->xyz, texY), 1); + VectorSet(xy1J, DotProduct(vert[1]->xyz, texX), DotProduct(vert[1]->xyz, texY), 1); + VectorSet(xy1K, DotProduct(vert[2]->xyz, texX), DotProduct(vert[2]->xyz, texY), 1); + stI[0] = vert[0]->st[0]; stI[1] = vert[0]->st[1]; + stJ[0] = vert[1]->st[0]; stJ[1] = vert[1]->st[1]; + stK[0] = vert[2]->st[0]; stK[1] = vert[2]->st[1]; + + // - solve linear equations: + // - (x, y) := xyz . (texX, texY) + // - st[i] = texMat[i][0]*x + texMat[i][1]*y + texMat[i][2] + // (for three vertices) + D = Det3x3( + xy1I[0], xy1I[1], 1, + xy1J[0], xy1J[1], 1, + xy1K[0], xy1K[1], 1 + ); + if(D != 0) + { + for(i = 0; i < 2; ++i) + { + D0 = Det3x3( + stI[i], xy1I[1], 1, + stJ[i], xy1J[1], 1, + stK[i], xy1K[1], 1 + ); + D1 = Det3x3( + xy1I[0], stI[i], 1, + xy1J[0], stJ[i], 1, + xy1K[0], stK[i], 1 + ); + D2 = Det3x3( + xy1I[0], xy1I[1], stI[i], + xy1J[0], xy1J[1], stJ[i], + xy1K[0], xy1K[1], stK[i] + ); + VectorSet(buildSide->texMat[i], D0 / D, D1 / D, D2 / D); + valid = 1; + } + } + else + fprintf(stderr, "degenerate triangle found when solving texMat equations for\n(%f %f %f) (%f %f %f) (%f %f %f)\n( %f %f %f )\n( %f %f %f ) -> ( %f %f )\n( %f %f %f ) -> ( %f %f )\n( %f %f %f ) -> ( %f %f )\n", + buildPlane->normal[0], buildPlane->normal[1], buildPlane->normal[2], + vert[0]->normal[0], vert[0]->normal[1], vert[0]->normal[2], + texX[0], texX[1], texX[2], texY[0], texY[1], texY[2], + vert[0]->xyz[0], vert[0]->xyz[1], vert[0]->xyz[2], xy1I[0], xy1I[1], + vert[1]->xyz[0], vert[1]->xyz[1], vert[1]->xyz[2], xy1J[0], xy1J[1], + vert[2]->xyz[0], vert[2]->xyz[1], vert[2]->xyz[2], xy1K[0], xy1K[1] + ); + } + else + if(strncmp(buildSide->shaderInfo->shader, "textures/common/", 16)) + fprintf(stderr, "no matching triangle for brushside using %s (hopefully nobody can see this side anyway)\n", buildSide->shaderInfo->shader); + + /* get texture name */ + if( !Q_strncasecmp( buildSide->shaderInfo->shader, "textures/", 9 ) ) + texture = buildSide->shaderInfo->shader + 9; + else + texture = buildSide->shaderInfo->shader; + + /* get plane points and offset by origin */ + for( j = 0; j < 3; j++ ) + { + VectorAdd( buildSide->winding->p[ j ], origin, pts[ j ] ); + //% pts[ j ][ 0 ] = SNAP_INT_TO_FLOAT * floor( pts[ j ][ 0 ] * SNAP_FLOAT_TO_INT + 0.5f ); + //% pts[ j ][ 1 ] = SNAP_INT_TO_FLOAT * floor( pts[ j ][ 1 ] * SNAP_FLOAT_TO_INT + 0.5f ); + //% pts[ j ][ 2 ] = SNAP_INT_TO_FLOAT * floor( pts[ j ][ 2 ] * SNAP_FLOAT_TO_INT + 0.5f ); + } + + /* print brush side */ + /* ( 640 24 -224 ) ( 448 24 -224 ) ( 448 -232 -224 ) common/caulk 0 48 0 0.500000 0.500000 0 0 0 */ + fprintf( f, "\t\t( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( ( %.8f %.8f %.8f ) ( %.8f %.8f %.8f ) ) %s %d 0 0\n", + pts[ 0 ][ 0 ], pts[ 0 ][ 1 ], pts[ 0 ][ 2 ], + pts[ 1 ][ 0 ], pts[ 1 ][ 1 ], pts[ 1 ][ 2 ], + pts[ 2 ][ 0 ], pts[ 2 ][ 1 ], pts[ 2 ][ 2 ], + buildSide->texMat[0][0], buildSide->texMat[0][1], buildSide->texMat[0][2], + buildSide->texMat[1][0], buildSide->texMat[1][1], buildSide->texMat[1][2], + texture, + // DEBUG: valid ? 0 : C_DETAIL + 0 + ); + // TODO write brush primitives format here + } + + /* end brush */ + fprintf( f, "\t}\n" ); + fprintf( f, "\t}\n\n" ); +} + +#if 0 + /* iterate through the brush sides (ignore the first 6 bevel planes) */ + for( i = 0; i < brush->numSides; i++ ) + { + /* get side */ + side = &bspBrushSides[ brush->firstSide + i ]; + + /* get shader */ + if( side->shaderNum < 0 || side->shaderNum >= numBSPShaders ) + continue; + shader = &bspShaders[ side->shaderNum ]; + if( !Q_stricmp( shader->shader, "default" ) || !Q_stricmp( shader->shader, "noshader" ) ) + continue; + + /* get texture name */ + if( !Q_strncasecmp( shader->shader, "textures/", 9 ) ) + texture = shader->shader + 9; + else + texture = shader->shader; + + /* get plane */ + plane = &bspPlanes[ side->planeNum ]; + + /* make plane points */ + { + vec3_t vecs[ 2 ]; + + + MakeNormalVectors( plane->normal, vecs[ 0 ], vecs[ 1 ] ); + VectorMA( vec3_origin, plane->dist, plane->normal, pts[ 0 ] ); + VectorMA( pts[ 0 ], 256.0f, vecs[ 0 ], pts[ 1 ] ); + VectorMA( pts[ 0 ], 256.0f, vecs[ 1 ], pts[ 2 ] ); + } + + /* offset by origin */ + for( j = 0; j < 3; j++ ) + VectorAdd( pts[ j ], origin, pts[ j ] ); + + /* print brush side */ + /* ( 640 24 -224 ) ( 448 24 -224 ) ( 448 -232 -224 ) common/caulk 0 48 0 0.500000 0.500000 0 0 0 */ + fprintf( f, "\t\t( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) ( %.3f %.3f %.3f ) %s 0 0 0 0.5 0.5 0 0 0\n", + pts[ 0 ][ 0 ], pts[ 0 ][ 1 ], pts[ 0 ][ 2 ], + pts[ 1 ][ 0 ], pts[ 1 ][ 1 ], pts[ 1 ][ 2 ], + pts[ 2 ][ 0 ], pts[ 2 ][ 1 ], pts[ 2 ][ 2 ], + texture ); + } +#endif + + + +/* +ConvertPatch() +converts a bsp patch to a map patch + + { + patchDef2 + { + base_wall/concrete + ( 9 3 0 0 0 ) + ( + ( ( 168 168 -192 0 2 ) ( 168 168 -64 0 1 ) ( 168 168 64 0 0 ) ... ) + ... + ) + } + } + +*/ + +static void ConvertPatch( FILE *f, int num, bspDrawSurface_t *ds, vec3_t origin ) +{ + int x, y; + bspShader_t *shader; + char *texture; + bspDrawVert_t *dv; + vec3_t xyz; + + + /* only patches */ + if( ds->surfaceType != MST_PATCH ) + return; + + /* get shader */ + if( ds->shaderNum < 0 || ds->shaderNum >= numBSPShaders ) + return; + shader = &bspShaders[ ds->shaderNum ]; + + /* get texture name */ + if( !Q_strncasecmp( shader->shader, "textures/", 9 ) ) + texture = shader->shader + 9; + else + texture = shader->shader; + + /* start patch */ + fprintf( f, "\t// patch %d\n", num ); + fprintf( f, "\t{\n" ); + fprintf( f, "\t\tpatchDef2\n" ); + fprintf( f, "\t\t{\n" ); + fprintf( f, "\t\t\t%s\n", texture ); + fprintf( f, "\t\t\t( %d %d 0 0 0 )\n", ds->patchWidth, ds->patchHeight ); + fprintf( f, "\t\t\t(\n" ); + + /* iterate through the verts */ + for( x = 0; x < ds->patchWidth; x++ ) + { + /* start row */ + fprintf( f, "\t\t\t\t(" ); + + /* iterate through the row */ + for( y = 0; y < ds->patchHeight; y++ ) + { + /* get vert */ + dv = &bspDrawVerts[ ds->firstVert + (y * ds->patchWidth) + x ]; + + /* offset it */ + VectorAdd( origin, dv->xyz, xyz ); + + /* print vertex */ + fprintf( f, " ( %f %f %f %f %f )", xyz[ 0 ], xyz[ 1 ], xyz[ 2 ], dv->st[ 0 ], dv->st[ 1 ] ); + } + + /* end row */ + fprintf( f, " )\n" ); + } + + /* end patch */ + fprintf( f, "\t\t\t)\n" ); + fprintf( f, "\t\t}\n" ); + fprintf( f, "\t}\n\n" ); +} + + + +/* +ConvertModel() +exports a bsp model to a map file +*/ + +static void ConvertModel( FILE *f, bspModel_t *model, int modelNum, vec3_t origin ) +{ + int i, num; + bspBrush_t *brush; + bspDrawSurface_t *ds; + + + /* convert bsp planes to map planes */ + nummapplanes = numBSPPlanes; + for( i = 0; i < numBSPPlanes; i++ ) + { + VectorCopy( bspPlanes[ i ].normal, mapplanes[ i ].normal ); + mapplanes[ i ].dist = bspPlanes[ i ].dist; + mapplanes[ i ].type = PlaneTypeForNormal( mapplanes[ i ].normal ); + mapplanes[ i ].hash_chain = NULL; + } + + /* allocate a build brush */ + buildBrush = AllocBrush( 512 ); + buildBrush->entityNum = 0; + buildBrush->original = buildBrush; + + /* go through each brush in the model */ + for( i = 0; i < model->numBSPBrushes; i++ ) + { + num = i + model->firstBSPBrush; + brush = &bspBrushes[ num ]; + ConvertBrush( f, num, brush, origin ); + } + + /* free the build brush */ + free( buildBrush ); + + /* go through each drawsurf in the model */ + for( i = 0; i < model->numBSPSurfaces; i++ ) + { + num = i + model->firstBSPSurface; + ds = &bspDrawSurfaces[ num ]; + + /* we only love patches */ + if( ds->surfaceType == MST_PATCH ) + ConvertPatch( f, num, ds, origin ); + } +} + + + +/* +ConvertEPairs() +exports entity key/value pairs to a map file +*/ + +static void ConvertEPairs( FILE *f, entity_t *e ) +{ + epair_t *ep; + + + /* walk epairs */ + for( ep = e->epairs; ep != NULL; ep = ep->next ) + { + /* ignore empty keys/values */ + if( ep->key[ 0 ] == '\0' || ep->value[ 0 ] == '\0' ) + continue; + + /* ignore model keys with * prefixed values */ + if( !Q_stricmp( ep->key, "model" ) && ep->value[ 0 ] == '*' ) + continue; + + /* emit the epair */ + fprintf( f, "\t\"%s\" \"%s\"\n", ep->key, ep->value ); + } +} + + + +/* +ConvertBSPToMap() +exports an quake map file from the bsp +*/ + +int ConvertBSPToMap( char *bspName ) +{ + int i, modelNum; + FILE *f; + bspModel_t *model; + entity_t *e; + vec3_t origin; + const char *value; + char name[ 1024 ], base[ 1024 ]; + + + /* note it */ + Sys_Printf( "--- Convert BSP to MAP ---\n" ); + + /* create the bsp filename from the bsp name */ + strcpy( name, bspName ); + StripExtension( name ); + strcat( name, "_converted.map" ); + Sys_Printf( "writing %s\n", name ); + + ExtractFileBase( bspName, base ); + strcat( base, ".bsp" ); + + /* open it */ + f = fopen( name, "wb" ); + if( f == NULL ) + Error( "Open failed on %s\n", name ); + + /* print header */ + fprintf( f, "// Generated by Q3Map2 (ydnar) -convert -format map\n" ); + + /* walk entity list */ + for( i = 0; i < numEntities; i++ ) + { + /* get entity */ + e = &entities[ i ]; + + /* start entity */ + fprintf( f, "// entity %d\n", i ); + fprintf( f, "{\n" ); + + /* export keys */ + ConvertEPairs( f, e ); + fprintf( f, "\n" ); + + /* get model num */ + if( i == 0 ) + modelNum = 0; + else + { + value = ValueForKey( e, "model" ); + if( value[ 0 ] == '*' ) + modelNum = atoi( value + 1 ); + else + modelNum = -1; + } + + /* only handle bsp models */ + if( modelNum >= 0 ) + { + /* get model */ + model = &bspModels[ modelNum ]; + + /* get entity origin */ + value = ValueForKey( e, "origin" ); + if( value[ 0 ] == '\0' ) + VectorClear( origin ); + else + GetVectorForKey( e, "origin", origin ); + + /* convert model */ + ConvertModel( f, model, modelNum, origin ); + } + + /* end entity */ + fprintf( f, "}\n\n" ); + } + + /* close the file and return */ + fclose( f ); + + /* return to sender */ + return 0; +} diff --git a/tools/quake3/q3map2/decals.c b/tools/quake3/q3map2/decals.c new file mode 100644 index 00000000..f4de7cea --- /dev/null +++ b/tools/quake3/q3map2/decals.c @@ -0,0 +1,905 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define DECALS_C + + + +/* dependencies */ +#include "q3map2.h" + + + +#define MAX_PROJECTORS 1024 + +typedef struct decalProjector_s +{ + shaderInfo_t *si; + vec3_t mins, maxs; + vec3_t center; + float radius, radius2; + int numPlanes; /* either 5 or 6, for quad or triangle projectors */ + vec4_t planes[ 6 ]; + vec4_t texMat[ 2 ]; +} +decalProjector_t; + +static int numProjectors = 0; +static decalProjector_t projectors[ MAX_PROJECTORS ]; + +static int numDecalSurfaces = 0; + +static vec3_t entityOrigin; + + + +/* +DVectorNormalize() +normalizes a vector, returns the length, operates using doubles +*/ + +typedef double dvec_t; +typedef dvec_t dvec3_t[ 3 ]; + +dvec_t DVectorNormalize( dvec3_t in, dvec3_t out ) +{ + dvec_t len, ilen; + + + len = (dvec_t) sqrt( in[ 0 ] * in[ 0 ] + in[ 1 ] * in[ 1 ] + in[ 2 ] * in[ 2 ] ); + if( len == 0.0 ) + { + VectorClear( out ); + return 0.0; + } + + ilen = 1.0 / len; + out[ 0 ] = in[ 0 ] * ilen; + out[ 1 ] = in[ 1 ] * ilen; + out[ 2 ] = in[ 2 ] * ilen; + + return len; +} + + + +/* +MakeTextureMatrix() +generates a texture projection matrix for a triangle +returns qfalse if a texture matrix cannot be created +*/ + +#define Vector2Subtract(a,b,c) ((c)[ 0 ] = (a)[ 0 ] - (b)[ 0 ], (c)[ 1 ] = (a)[ 1 ] - (b)[ 1 ]) + +static qboolean MakeTextureMatrix( decalProjector_t *dp, vec4_t projection, bspDrawVert_t *a, bspDrawVert_t *b, bspDrawVert_t *c ) +{ + int i, j; + double bb, s, t, d; + dvec3_t pa, pb, pc; + dvec3_t bary, xyz; + dvec3_t vecs[ 3 ], axis[ 3 ], lengths; + + + /* project triangle onto plane of projection */ + d = DotProduct( a->xyz, projection ) - projection[ 3 ]; + VectorMA( a->xyz, -d, projection, pa ); + d = DotProduct( b->xyz, projection ) - projection[ 3 ]; + VectorMA( b->xyz, -d, projection, pb ); + d = DotProduct( c->xyz, projection ) - projection[ 3 ]; + VectorMA( c->xyz, -d, projection, pc ); + + /* two methods */ + #if 1 + { + /* old code */ + + /* calculate barycentric basis for the triangle */ + bb = (b->st[ 0 ] - a->st[ 0 ]) * (c->st[ 1 ] - a->st[ 1 ]) - (c->st[ 0 ] - a->st[ 0 ]) * (b->st[ 1 ] - a->st[ 1 ]); + if( fabs( bb ) < 0.00000001 ) + return qfalse; + + /* calculate texture origin */ + #if 0 + s = 0.0; + t = 0.0; + bary[ 0 ] = ((b->st[ 0 ] - s) * (c->st[ 1 ] - t) - (c->st[ 0 ] - s) * (b->st[ 1 ] - t)) / bb; + bary[ 1 ] = ((c->st[ 0 ] - s) * (a->st[ 1 ] - t) - (a->st[ 0 ] - s) * (c->st[ 1 ] - t)) / bb; + bary[ 2 ] = ((a->st[ 0 ] - s) * (b->st[ 1 ] - t) - (b->st[ 0 ] - s) * (a->st[ 1 ] - t)) / bb; + + origin[ 0 ] = bary[ 0 ] * pa[ 0 ] + bary[ 1 ] * pb[ 0 ] + bary[ 2 ] * pc[ 0 ]; + origin[ 1 ] = bary[ 0 ] * pa[ 1 ] + bary[ 1 ] * pb[ 1 ] + bary[ 2 ] * pc[ 1 ]; + origin[ 2 ] = bary[ 0 ] * pa[ 2 ] + bary[ 1 ] * pb[ 2 ] + bary[ 2 ] * pc[ 2 ]; + #endif + + /* calculate s vector */ + s = a->st[ 0 ] + 1.0; + t = a->st[ 1 ] + 0.0; + bary[ 0 ] = ((b->st[ 0 ] - s) * (c->st[ 1 ] - t) - (c->st[ 0 ] - s) * (b->st[ 1 ] - t)) / bb; + bary[ 1 ] = ((c->st[ 0 ] - s) * (a->st[ 1 ] - t) - (a->st[ 0 ] - s) * (c->st[ 1 ] - t)) / bb; + bary[ 2 ] = ((a->st[ 0 ] - s) * (b->st[ 1 ] - t) - (b->st[ 0 ] - s) * (a->st[ 1 ] - t)) / bb; + + xyz[ 0 ] = bary[ 0 ] * pa[ 0 ] + bary[ 1 ] * pb[ 0 ] + bary[ 2 ] * pc[ 0 ]; + xyz[ 1 ] = bary[ 0 ] * pa[ 1 ] + bary[ 1 ] * pb[ 1 ] + bary[ 2 ] * pc[ 1 ]; + xyz[ 2 ] = bary[ 0 ] * pa[ 2 ] + bary[ 1 ] * pb[ 2 ] + bary[ 2 ] * pc[ 2 ]; + + //% VectorSubtract( xyz, origin, vecs[ 0 ] ); + VectorSubtract( xyz, pa, vecs[ 0 ] ); + + /* calculate t vector */ + s = a->st[ 0 ] + 0.0; + t = a->st[ 1 ] + 1.0; + bary[ 0 ] = ((b->st[ 0 ] - s) * (c->st[ 1 ] - t) - (c->st[ 0 ] - s) * (b->st[ 1 ] - t)) / bb; + bary[ 1 ] = ((c->st[ 0 ] - s) * (a->st[ 1 ] - t) - (a->st[ 0 ] - s) * (c->st[ 1 ] - t)) / bb; + bary[ 2 ] = ((a->st[ 0 ] - s) * (b->st[ 1 ] - t) - (b->st[ 0 ] - s) * (a->st[ 1 ] - t)) / bb; + + xyz[ 0 ] = bary[ 0 ] * pa[ 0 ] + bary[ 1 ] * pb[ 0 ] + bary[ 2 ] * pc[ 0 ]; + xyz[ 1 ] = bary[ 0 ] * pa[ 1 ] + bary[ 1 ] * pb[ 1 ] + bary[ 2 ] * pc[ 1 ]; + xyz[ 2 ] = bary[ 0 ] * pa[ 2 ] + bary[ 1 ] * pb[ 2 ] + bary[ 2 ] * pc[ 2 ]; + + //% VectorSubtract( xyz, origin, vecs[ 1 ] ); + VectorSubtract( xyz, pa, vecs[ 1 ] ); + + /* calcuate r vector */ + VectorScale( projection, -1.0, vecs[ 2 ] ); + + /* calculate transform axis */ + for( i = 0; i < 3; i++ ) + lengths[ i ] = DVectorNormalize( vecs[ i ], axis[ i ] ); + for( i = 0; i < 2; i++ ) + for( j = 0; j < 3; j++ ) + dp->texMat[ i ][ j ] = lengths[ i ] > 0.0 ? (axis[ i ][ j ] / lengths[ i ]) : 0.0; + //% dp->texMat[ i ][ j ] = fabs( vecs[ i ][ j ] ) > 0.0 ? (1.0 / vecs[ i ][ j ]) : 0.0; + //% dp->texMat[ i ][ j ] = axis[ i ][ j ] > 0.0 ? (1.0 / axis[ i ][ j ]) : 0.0; + + /* calculalate translation component */ + dp->texMat[ 0 ][ 3 ] = a->st[ 0 ] - DotProduct( a->xyz, dp->texMat[ 0 ] ); + dp->texMat[ 1 ][ 3 ] = a->st[ 1 ] - DotProduct( a->xyz, dp->texMat[ 1 ] ); + } + #else + { + int k; + dvec3_t origin, deltas[ 3 ]; + double texDeltas[ 3 ][ 2 ]; + double delta, texDelta; + + + /* new code */ + + /* calculate deltas */ + VectorSubtract( pa, pb, deltas[ 0 ] ); + VectorSubtract( pa, pc, deltas[ 1 ] ); + VectorSubtract( pb, pc, deltas[ 2 ] ); + Vector2Subtract( a->st, b->st, texDeltas[ 0 ] ); + Vector2Subtract( a->st, c->st, texDeltas[ 1 ] ); + Vector2Subtract( b->st, c->st, texDeltas[ 2 ] ); + + /* walk st */ + for( i = 0; i < 2; i++ ) + { + /* walk xyz */ + for( j = 0; j < 3; j++ ) + { + /* clear deltas */ + delta = 0.0; + texDelta = 0.0; + + /* walk deltas */ + for( k = 0; k < 3; k++ ) + { + if( fabs( deltas[ k ][ j ] ) > delta && + fabs( texDeltas[ k ][ i ] ) > texDelta ) + { + delta = deltas[ k ][ j ]; + texDelta = texDeltas[ k ][ i ]; + } + } + + /* set texture matrix component */ + if( fabs( delta ) > 0.0 ) + dp->texMat[ i ][ j ] = texDelta / delta; + else + dp->texMat[ i ][ j ] = 0.0; + } + + /* set translation component */ + dp->texMat[ i ][ 3 ] = a->st[ i ] - DotProduct( pa, dp->texMat[ i ] ); + } + } + #endif + + /* debug code */ + #if 1 + Sys_Printf( "Mat: [ %f %f %f %f ] [ %f %f %f %f ] Theta: %f (%f)\n", + dp->texMat[ 0 ][ 0 ], dp->texMat[ 0 ][ 1 ], dp->texMat[ 0 ][ 2 ], dp->texMat[ 0 ][ 3 ], + dp->texMat[ 1 ][ 0 ], dp->texMat[ 1 ][ 1 ], dp->texMat[ 1 ][ 2 ], dp->texMat[ 1 ][ 3 ], + RAD2DEG( acos( DotProduct( dp->texMat[ 0 ], dp->texMat[ 1 ] ) ) ), + RAD2DEG( acos( DotProduct( axis[ 0 ], axis[ 1 ] ) ) ) ); + + Sys_Printf( "XYZ: %f %f %f ST: %f %f ST(t): %f %f\n", + a->xyz[ 0 ], a->xyz[ 1 ], a->xyz[ 2 ], + a->st[ 0 ], a->st[ 1 ], + DotProduct( a->xyz, dp->texMat[ 0 ] ) + dp->texMat[ 0 ][ 3 ], DotProduct( a->xyz, dp->texMat[ 1 ] ) + dp->texMat[ 1 ][ 3 ] ); + #endif + + /* test texture matrix */ + s = DotProduct( a->xyz, dp->texMat[ 0 ] ) + dp->texMat[ 0 ][ 3 ]; + t = DotProduct( a->xyz, dp->texMat[ 1 ] ) + dp->texMat[ 1 ][ 3 ]; + if( fabs( s - a->st[ 0 ] ) > 0.01 || fabs( t - a->st[ 1 ] ) > 0.01 ) + { + Sys_Printf( "Bad texture matrix! (A) (%f, %f) != (%f, %f)\n", + s, t, a->st[ 0 ], a->st[ 1 ] ); + //% return qfalse; + } + s = DotProduct( b->xyz, dp->texMat[ 0 ] ) + dp->texMat[ 0 ][ 3 ]; + t = DotProduct( b->xyz, dp->texMat[ 1 ] ) + dp->texMat[ 1 ][ 3 ]; + if( fabs( s - b->st[ 0 ] ) > 0.01 || fabs( t - b->st[ 1 ] ) > 0.01 ) + { + Sys_Printf( "Bad texture matrix! (B) (%f, %f) != (%f, %f)\n", + s, t, b->st[ 0 ], b->st[ 1 ] ); + //% return qfalse; + } + s = DotProduct( c->xyz, dp->texMat[ 0 ] ) + dp->texMat[ 0 ][ 3 ]; + t = DotProduct( c->xyz, dp->texMat[ 1 ] ) + dp->texMat[ 1 ][ 3 ]; + if( fabs( s - c->st[ 0 ] ) > 0.01 || fabs( t - c->st[ 1 ] ) > 0.01 ) + { + Sys_Printf( "Bad texture matrix! (C) (%f, %f) != (%f, %f)\n", + s, t, c->st[ 0 ], c->st[ 1 ] ); + //% return qfalse; + } + + /* disco */ + return qtrue; +} + + + +/* +TransformDecalProjector() +transforms a decal projector +note: non-normalized axes will screw up the plane transform +*/ + +static void TransformDecalProjector( decalProjector_t *in, vec3_t axis[ 3 ], vec3_t origin, decalProjector_t *out ) +{ + int i; + + + /* copy misc stuff */ + out->si = in->si; + out->numPlanes = in->numPlanes; + + /* translate bounding box and sphere (note: rotated projector bounding box will be invalid!) */ + VectorSubtract( in->mins, origin, out->mins ); + VectorSubtract( in->maxs, origin, out->maxs ); + VectorSubtract( in->center, origin, out->center ); + out->radius = in->radius; + out->radius2 = in->radius2; + + /* translate planes */ + for( i = 0; i < in->numPlanes; i++ ) + { + out->planes[ i ][ 0 ] = DotProduct( in->planes[ i ], axis[ 0 ] ); + out->planes[ i ][ 1 ] = DotProduct( in->planes[ i ], axis[ 1 ] ); + out->planes[ i ][ 2 ] = DotProduct( in->planes[ i ], axis[ 2 ] ); + out->planes[ i ][ 3 ] = in->planes[ i ][ 3 ] - DotProduct( out->planes[ i ], origin ); + } + + /* translate texture matrix */ + for( i = 0; i < 2; i++ ) + { + out->texMat[ i ][ 0 ] = DotProduct( in->texMat[ i ], axis[ 0 ] ); + out->texMat[ i ][ 1 ] = DotProduct( in->texMat[ i ], axis[ 1 ] ); + out->texMat[ i ][ 2 ] = DotProduct( in->texMat[ i ], axis[ 2 ] ); + out->texMat[ i ][ 3 ] = in->texMat[ i ][ 3 ] + DotProduct( out->texMat[ i ], origin ); + } +} + + + +/* +MakeDecalProjector() +creates a new decal projector from a triangle +*/ + +static int MakeDecalProjector( shaderInfo_t *si, vec4_t projection, float distance, int numVerts, bspDrawVert_t **dv ) +{ + int i, j; + decalProjector_t *dp; + vec3_t xyz; + + + /* dummy check */ + if( numVerts != 3 && numVerts != 4 ) + return -1; + + /* limit check */ + if( numProjectors >= MAX_PROJECTORS ) + { + Sys_Printf( "WARNING: MAX_PROJECTORS (%d) exceeded, no more decal projectors available.\n", MAX_PROJECTORS ); + return -2; + } + + /* create a new projector */ + dp = &projectors[ numProjectors ]; + memset( dp, 0, sizeof( *dp ) ); + + /* basic setup */ + dp->si = si; + dp->numPlanes = numVerts + 2; + + /* make texture matrix */ + if( !MakeTextureMatrix( dp, projection, dv[ 0 ], dv[ 1 ], dv[ 2 ] ) ) + return -1; + + /* bound the projector */ + ClearBounds( dp->mins, dp->maxs ); + for( i = 0; i < numVerts; i++ ) + { + AddPointToBounds( dv[ i ]->xyz, dp->mins, dp->maxs ); + VectorMA( dv[ i ]->xyz, distance, projection, xyz ); + AddPointToBounds( xyz, dp->mins, dp->maxs ); + } + + /* make bouding sphere */ + VectorAdd( dp->mins, dp->maxs, dp->center ); + VectorScale( dp->center, 0.5f, dp->center ); + VectorSubtract( dp->maxs, dp->center, xyz ); + dp->radius = VectorLength( xyz ); + dp->radius2 = dp->radius * dp->radius; + + /* make the front plane */ + if( !PlaneFromPoints( dp->planes[ 0 ], dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) ) + return -1; + + /* make the back plane */ + VectorSubtract( vec3_origin, dp->planes[ 0 ], dp->planes[ 1 ] ); + VectorMA( dv[ 0 ]->xyz, distance, projection, xyz ); + dp->planes[ 1 ][ 3 ] = DotProduct( xyz, dp->planes[ 1 ] ); + + /* make the side planes */ + for( i = 0; i < numVerts; i++ ) + { + j = (i + 1) % numVerts; + VectorMA( dv[ i ]->xyz, distance, projection, xyz ); + if( !PlaneFromPoints( dp->planes[ i + 2 ], dv[ j ]->xyz, dv[ i ]->xyz, xyz ) ) + return -1; + } + + /* return ok */ + numProjectors++; + return numProjectors - 1; +} + + + +/* +ProcessDecals() +finds all decal entities and creates decal projectors +*/ + +#define PLANAR_EPSILON 0.5f + +void ProcessDecals( void ) +{ + int i, j, x, y, pw[ 5 ], r, iterations; + float distance; + vec4_t projection, plane; + vec3_t origin, target, delta; + entity_t *e, *e2; + parseMesh_t *p; + mesh_t *mesh, *subdivided; + bspDrawVert_t *dv[ 4 ]; + const char *value; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- ProcessDecals ---\n" ); + + /* walk entity list */ + for( i = 0; i < numEntities; i++ ) + { + /* get entity */ + e = &entities[ i ]; + value = ValueForKey( e, "classname" ); + if( Q_stricmp( value, "_decal" ) ) + continue; + + /* any patches? */ + if( e->patches == NULL ) + { + Sys_Printf( "WARNING: Decal entity without any patch meshes, ignoring.\n" ); + e->epairs = NULL; /* fixme: leak! */ + continue; + } + + /* find target */ + value = ValueForKey( e, "target" ); + e2 = FindTargetEntity( value ); + + /* no target? */ + if( e2 == NULL ) + { + Sys_Printf( "WARNING: Decal entity without a valid target, ignoring.\n" ); + continue; + } + + /* walk entity patches */ + for( p = e->patches; p != NULL; p = e->patches ) + { + /* setup projector */ + if( VectorCompare( e->origin, vec3_origin ) ) + { + VectorAdd( p->eMins, p->eMaxs, origin ); + VectorScale( origin, 0.5f, origin ); + } + else + VectorCopy( e->origin, origin ); + + VectorCopy( e2->origin, target ); + VectorSubtract( target, origin, delta ); + + /* setup projection plane */ + distance = VectorNormalize( delta, projection ); + projection[ 3 ] = DotProduct( origin, projection ); + + /* create projectors */ + if( distance > 0.125f ) + { + /* tesselate the patch */ + iterations = IterationsForCurve( p->longestCurve, patchSubdivisions ); + subdivided = SubdivideMesh2( p->mesh, iterations ); + + /* fit it to the curve and remove colinear verts on rows/columns */ + PutMeshOnCurve( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + + /* offset by projector origin */ + for( j = 0; j < (mesh->width * mesh->height); j++ ) + VectorAdd( mesh->verts[ j ].xyz, e->origin, mesh->verts[ j ].xyz ); + + /* iterate through the mesh quads */ + for( y = 0; y < (mesh->height - 1); y++ ) + { + for( x = 0; x < (mesh->width - 1); x++ ) + { + /* set indexes */ + pw[ 0 ] = x + (y * mesh->width); + pw[ 1 ] = x + ((y + 1) * mesh->width); + pw[ 2 ] = x + 1 + ((y + 1) * mesh->width); + pw[ 3 ] = x + 1 + (y * mesh->width); + pw[ 4 ] = x + (y * mesh->width); /* same as pw[ 0 ] */ + + /* set radix */ + r = (x + y) & 1; + + /* get drawverts */ + dv[ 0 ] = &mesh->verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &mesh->verts[ pw[ r + 1 ] ]; + dv[ 2 ] = &mesh->verts[ pw[ r + 2 ] ]; + dv[ 3 ] = &mesh->verts[ pw[ r + 3 ] ]; + + /* planar? (nuking this optimization as it doesn't work on non-rectangular quads) */ + plane[ 0 ] = 0.0f; /* stupid msvc */ + if( 0 && PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) && + fabs( DotProduct( dv[ 1 ]->xyz, plane ) - plane[ 3 ] ) <= PLANAR_EPSILON ) + { + /* make a quad projector */ + MakeDecalProjector( p->shaderInfo, projection, distance, 4, dv ); + } + else + { + /* make first triangle */ + MakeDecalProjector( p->shaderInfo, projection, distance, 3, dv ); + + /* make second triangle */ + dv[ 1 ] = dv[ 2 ]; + dv[ 2 ] = dv[ 3 ]; + MakeDecalProjector( p->shaderInfo, projection, distance, 3, dv ); + } + } + } + + /* clean up */ + free( mesh ); + } + + /* remove patch from entity (fixme: leak!) */ + e->patches = p->next; + + /* push patch to worldspawn (enable this to debug projectors) */ + #if 0 + p->next = entities[ 0 ].patches; + entities[ 0 ].patches = p; + #endif + } + } + + /* emit some stats */ + Sys_FPrintf( SYS_VRB, "%9d decal projectors\n", numProjectors ); +} + + + +/* +ProjectDecalOntoWinding() +projects a decal onto a winding +*/ + +static void ProjectDecalOntoWinding( decalProjector_t *dp, mapDrawSurface_t *ds, winding_t *w ) +{ + int i, j; + float d, d2, alpha; + winding_t *front, *back; + mapDrawSurface_t *ds2; + bspDrawVert_t *dv; + vec4_t plane; + + + /* dummy check */ + if( w->numpoints < 3 ) + { + FreeWinding( w ); + return; + } + + /* offset by entity origin */ + for( i = 0; i < w->numpoints; i++ ) + VectorAdd( w->p[ i ], entityOrigin, w->p[ i ] ); + + /* make a plane from the winding */ + if( !PlaneFromPoints( plane, w->p[ 0 ], w->p[ 1 ], w->p[ 2 ] ) ) + { + FreeWinding( w ); + return; + } + + /* backface check */ + d = DotProduct( dp->planes[ 0 ], plane ); + if( d < -0.0001f ) + { + FreeWinding( w ); + return; + } + + /* walk list of planes */ + for( i = 0; i < dp->numPlanes; i++ ) + { + /* chop winding by the plane */ + ClipWindingEpsilon( w, dp->planes[ i ], dp->planes[ i ][ 3 ], 0.0625f, &front, &back ); + FreeWinding( w ); + + /* lose the front fragment */ + if( front != NULL ) + FreeWinding( front ); + + /* if nothing left in back, then bail */ + if( back == NULL ) + return; + + /* reset winding */ + w = back; + } + + /* nothing left? */ + if( w == NULL || w->numpoints < 3 ) + return; + + /* add to counts */ + numDecalSurfaces++; + + /* make a new surface */ + ds2 = AllocDrawSurface( SURFACE_DECAL ); + + /* set it up */ + ds2->entityNum = ds->entityNum; + ds2->castShadows = ds->castShadows; + ds2->recvShadows = ds->recvShadows; + ds2->shaderInfo = dp->si; + ds2->fogNum = ds->fogNum; /* why was this -1? */ + ds2->lightmapScale = ds->lightmapScale; + ds2->numVerts = w->numpoints; + ds2->verts = safe_malloc( ds2->numVerts * sizeof( *ds2->verts ) ); + memset( ds2->verts, 0, ds2->numVerts * sizeof( *ds2->verts ) ); + + /* set vertexes */ + for( i = 0; i < ds2->numVerts; i++ ) + { + /* get vertex */ + dv = &ds2->verts[ i ]; + + /* set alpha */ + d = DotProduct( w->p[ i ], dp->planes[ 0 ] ) - dp->planes[ 0 ][ 3 ]; + d2 = DotProduct( w->p[ i ], dp->planes[ 1 ] ) - dp->planes[ 1 ][ 3 ]; + alpha = 255.0f * d2 / (d + d2); + if( alpha > 255 ) + alpha = 255; + else if( alpha < 0 ) + alpha = 0; + + /* set misc */ + VectorSubtract( w->p[ i ], entityOrigin, dv->xyz ); + VectorCopy( plane, dv->normal ); + dv->st[ 0 ] = DotProduct( dv->xyz, dp->texMat[ 0 ] ) + dp->texMat[ 0 ][ 3 ]; + dv->st[ 1 ] = DotProduct( dv->xyz, dp->texMat[ 1 ] ) + dp->texMat[ 1 ][ 3 ]; + + /* set color */ + for( j = 0; j < MAX_LIGHTMAPS; j++ ) + { + dv->color[ j ][ 0 ] = 255; + dv->color[ j ][ 1 ] = 255; + dv->color[ j ][ 2 ] = 255; + dv->color[ j ][ 3 ] = alpha; + } + } +} + + + +/* +ProjectDecalOntoFace() +projects a decal onto a brushface surface +*/ + +static void ProjectDecalOntoFace( decalProjector_t *dp, mapDrawSurface_t *ds ) +{ + vec4_t plane; + float d; + winding_t *w; + + + /* dummy check */ + if( ds->sideRef == NULL || ds->sideRef->side == NULL ) + return; + + /* backface check */ + if( ds->planar ) + { + VectorCopy( mapplanes[ ds->planeNum ].normal, plane ); + plane[ 3 ] = mapplanes[ ds->planeNum ].dist + DotProduct( plane, entityOrigin ); + d = DotProduct( dp->planes[ 0 ], plane ); + if( d < -0.0001f ) + return; + } + + /* generate decal */ + w = WindingFromDrawSurf( ds ); + ProjectDecalOntoWinding( dp, ds, w ); +} + + + +/* +ProjectDecalOntoPatch() +projects a decal onto a patch surface +*/ + +static void ProjectDecalOntoPatch( decalProjector_t *dp, mapDrawSurface_t *ds ) +{ + int x, y, pw[ 5 ], r, iterations; + vec4_t plane; + float d; + mesh_t src, *mesh, *subdivided; + winding_t *w; + + + /* backface check */ + if( ds->planar ) + { + VectorCopy( mapplanes[ ds->planeNum ].normal, plane ); + plane[ 3 ] = mapplanes[ ds->planeNum ].dist + DotProduct( plane, entityOrigin ); + d = DotProduct( dp->planes[ 0 ], plane ); + if( d < -0.0001f ) + return; + } + + /* tesselate the patch */ + src.width = ds->patchWidth; + src.height = ds->patchHeight; + src.verts = ds->verts; + iterations = IterationsForCurve( ds->longestCurve, patchSubdivisions ); + subdivided = SubdivideMesh2( src, iterations ); + + /* fit it to the curve and remove colinear verts on rows/columns */ + PutMeshOnCurve( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + + /* iterate through the mesh quads */ + for( y = 0; y < (mesh->height - 1); y++ ) + { + for( x = 0; x < (mesh->width - 1); x++ ) + { + /* set indexes */ + pw[ 0 ] = x + (y * mesh->width); + pw[ 1 ] = x + ((y + 1) * mesh->width); + pw[ 2 ] = x + 1 + ((y + 1) * mesh->width); + pw[ 3 ] = x + 1 + (y * mesh->width); + pw[ 4 ] = x + (y * mesh->width); /* same as pw[ 0 ] */ + + /* set radix */ + r = (x + y) & 1; + + /* generate decal for first triangle */ + w = AllocWinding( 3 ); + w->numpoints = 3; + VectorCopy( mesh->verts[ pw[ r + 0 ] ].xyz, w->p[ 0 ] ); + VectorCopy( mesh->verts[ pw[ r + 1 ] ].xyz, w->p[ 1 ] ); + VectorCopy( mesh->verts[ pw[ r + 2 ] ].xyz, w->p[ 2 ] ); + ProjectDecalOntoWinding( dp, ds, w ); + + /* generate decal for second triangle */ + w = AllocWinding( 3 ); + w->numpoints = 3; + VectorCopy( mesh->verts[ pw[ r + 0 ] ].xyz, w->p[ 0 ] ); + VectorCopy( mesh->verts[ pw[ r + 2 ] ].xyz, w->p[ 1 ] ); + VectorCopy( mesh->verts[ pw[ r + 3 ] ].xyz, w->p[ 2 ] ); + ProjectDecalOntoWinding( dp, ds, w ); + } + } + + /* clean up */ + free( mesh ); +} + + + +/* +ProjectDecalOntoTriangles() +projects a decal onto a triangle surface +*/ + +static void ProjectDecalOntoTriangles( decalProjector_t *dp, mapDrawSurface_t *ds ) +{ + int i; + vec4_t plane; + float d; + winding_t *w; + + + /* triangle surfaces without shaders don't get marks by default */ + if( ds->type == SURFACE_TRIANGLES && ds->shaderInfo->shaderText == NULL ) + return; + + /* backface check */ + if( ds->planar ) + { + VectorCopy( mapplanes[ ds->planeNum ].normal, plane ); + plane[ 3 ] = mapplanes[ ds->planeNum ].dist + DotProduct( plane, entityOrigin ); + d = DotProduct( dp->planes[ 0 ], plane ); + if( d < -0.0001f ) + return; + } + + /* iterate through triangles */ + for( i = 0; i < ds->numIndexes; i += 3 ) + { + /* generate decal */ + w = AllocWinding( 3 ); + w->numpoints = 3; + VectorCopy( ds->verts[ ds->indexes[ i ] ].xyz, w->p[ 0 ] ); + VectorCopy( ds->verts[ ds->indexes[ i + 1 ] ].xyz, w->p[ 1 ] ); + VectorCopy( ds->verts[ ds->indexes[ i + 2 ] ].xyz, w->p[ 2 ] ); + ProjectDecalOntoWinding( dp, ds, w ); + } +} + + + +/* +MakeEntityDecals() +projects decals onto world surfaces +*/ + +void MakeEntityDecals( entity_t *e ) +{ + int i, j, k, f, fOld, start; + decalProjector_t dp; + mapDrawSurface_t *ds; + vec3_t identityAxis[ 3 ] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- MakeEntityDecals ---\n" ); + + /* set entity origin */ + VectorCopy( e->origin, entityOrigin ); + + /* transform projector instead of geometry */ + VectorClear( entityOrigin ); + + /* init pacifier */ + fOld = -1; + start = I_FloatTime(); + + /* walk the list of decal projectors */ + for( i = 0; i < numProjectors; i++ ) + { + /* print pacifier */ + f = 10 * i / numProjectors; + if( f != fOld ) + { + fOld = f; + Sys_FPrintf( SYS_VRB, "%d...", f ); + } + + /* get projector */ + TransformDecalProjector( &projectors[ i ], identityAxis, e->origin, &dp ); + + /* walk the list of surfaces in the entity */ + for( j = e->firstDrawSurf; j < numMapDrawSurfs; j++ ) + { + /* get surface */ + ds = &mapDrawSurfs[ j ]; + if( ds->numVerts <= 0 ) + continue; + + /* ignore autosprite or nomarks */ + if( ds->shaderInfo->autosprite || (ds->shaderInfo->compileFlags & C_NOMARKS) ) + continue; + + /* bounds check */ + for( k = 0; k < 3; k++ ) + if( ds->mins[ k ] >= (dp.center[ k ] + dp.radius) || + ds->maxs[ k ] <= (dp.center[ k ] - dp.radius) ) + break; + if( k < 3 ) + continue; + + /* switch on type */ + switch( ds->type ) + { + case SURFACE_FACE: + ProjectDecalOntoFace( &dp, ds ); + break; + + case SURFACE_PATCH: + ProjectDecalOntoPatch( &dp, ds ); + break; + + case SURFACE_TRIANGLES: + case SURFACE_FORCED_META: + case SURFACE_META: + ProjectDecalOntoTriangles( &dp, ds ); + break; + + default: + break; + } + } + } + + /* print time */ + Sys_FPrintf( SYS_VRB, " (%d)\n", (int) (I_FloatTime() - start) ); + + /* emit some stats */ + Sys_FPrintf( SYS_VRB, "%9d decal surfaces\n", numDecalSurfaces ); +} diff --git a/tools/quake3/q3map2/facebsp.c b/tools/quake3/q3map2/facebsp.c new file mode 100644 index 00000000..270eefe2 --- /dev/null +++ b/tools/quake3/q3map2/facebsp.c @@ -0,0 +1,454 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define FACEBSP_C + + + +/* dependencies */ +#include "q3map2.h" + + + +int c_faceLeafs; + + +/* +================ +AllocBspFace +================ +*/ +face_t *AllocBspFace( void ) { + face_t *f; + + f = safe_malloc(sizeof(*f)); + memset( f, 0, sizeof(*f) ); + + return f; +} + + + +/* +================ +FreeBspFace +================ +*/ +void FreeBspFace( face_t *f ) { + if ( f->w ) { + FreeWinding( f->w ); + } + free( f ); +} + + + +/* +SelectSplitPlaneNum() +finds the best split plane for this node +*/ + +static void SelectSplitPlaneNum( node_t *node, face_t *list, int *splitPlaneNum, int *compileFlags ) +{ + face_t *split; + face_t *check; + face_t *bestSplit; + int splits, facing, front, back; + int side; + plane_t *plane; + int value, bestValue; + int i; + vec3_t normal; + float dist; + int planenum; + + + /* ydnar: set some defaults */ + *splitPlaneNum = -1; /* leaf */ + *compileFlags = 0; + + /* ydnar 2002-06-24: changed this to split on z-axis as well */ + /* ydnar 2002-09-21: changed blocksize to be a vector, so mappers can specify a 3 element value */ + + /* if it is crossing a block boundary, force a split */ + for( i = 0; i < 3; i++ ) + { + if( blockSize[ i ] <= 0 ) + continue; + dist = blockSize[ i ] * (floor( node->mins[ i ] / blockSize[ i ] ) + 1); + if( node->maxs[ i ] > dist ) + { + VectorClear( normal ); + normal[ i ] = 1; + planenum = FindFloatPlane( normal, dist, 0, NULL ); + *splitPlaneNum = planenum; + return; + } + } + + /* pick one of the face planes */ + bestValue = -99999; + bestSplit = list; + + for( split = list; split; split = split->next ) + split->checked = qfalse; + + for( split = list; split; split = split->next ) + { + if ( split->checked ) + continue; + + plane = &mapplanes[ split->planenum ]; + splits = 0; + facing = 0; + front = 0; + back = 0; + for ( check = list ; check ; check = check->next ) { + if ( check->planenum == split->planenum ) { + facing++; + check->checked = qtrue; // won't need to test this plane again + continue; + } + side = WindingOnPlaneSide( check->w, plane->normal, plane->dist ); + if ( side == SIDE_CROSS ) { + splits++; + } else if ( side == SIDE_FRONT ) { + front++; + } else if ( side == SIDE_BACK ) { + back++; + } + } + value = 5*facing - 5*splits; // - abs(front-back); + if ( plane->type < 3 ) { + value+=5; // axial is better + } + value += split->priority; // prioritize hints higher + + if ( value > bestValue ) { + bestValue = value; + bestSplit = split; + } + } + + /* nothing, we have a leaf */ + if( bestValue == -99999 ) + return; + + /* set best split data */ + *splitPlaneNum = bestSplit->planenum; + *compileFlags = bestSplit->compileFlags; +} + + + +/* +CountFaceList() +counts bsp faces in the linked list +*/ + +int CountFaceList( face_t *list ) +{ + int c; + + + c = 0; + for( ; list != NULL; list = list->next ) + c++; + return c; +} + + + +/* +BuildFaceTree_r() +recursively builds the bsp, splitting on face planes +*/ + +void BuildFaceTree_r( node_t *node, face_t *list ) +{ + face_t *split; + face_t *next; + int side; + plane_t *plane; + face_t *newFace; + face_t *childLists[2]; + winding_t *frontWinding, *backWinding; + int i; + int splitPlaneNum, compileFlags; + + + /* count faces left */ + i = CountFaceList( list ); + + /* select the best split plane */ + SelectSplitPlaneNum( node, list, &splitPlaneNum, &compileFlags ); + + /* if we don't have any more faces, this is a node */ + if ( splitPlaneNum == -1 ) + { + node->planenum = PLANENUM_LEAF; + c_faceLeafs++; + return; + } + + /* partition the list */ + node->planenum = splitPlaneNum; + node->compileFlags = compileFlags; + plane = &mapplanes[ splitPlaneNum ]; + childLists[0] = NULL; + childLists[1] = NULL; + for( split = list; split; split = next ) + { + /* set next */ + next = split->next; + + /* don't split by identical plane */ + if( split->planenum == node->planenum ) + { + FreeBspFace( split ); + continue; + } + + /* determine which side the face falls on */ + side = WindingOnPlaneSide( split->w, plane->normal, plane->dist ); + + /* switch on side */ + if( side == SIDE_CROSS ) + { + ClipWindingEpsilon( split->w, plane->normal, plane->dist, CLIP_EPSILON * 2, + &frontWinding, &backWinding ); + if( frontWinding ) { + newFace = AllocBspFace(); + newFace->w = frontWinding; + newFace->next = childLists[0]; + newFace->planenum = split->planenum; + newFace->priority = split->priority; + newFace->compileFlags = split->compileFlags; + childLists[0] = newFace; + } + if( backWinding ) { + newFace = AllocBspFace(); + newFace->w = backWinding; + newFace->next = childLists[1]; + newFace->planenum = split->planenum; + newFace->priority = split->priority; + newFace->compileFlags = split->compileFlags; + childLists[1] = newFace; + } + FreeBspFace( split ); + } else if ( side == SIDE_FRONT ) { + split->next = childLists[0]; + childLists[0] = split; + } else if ( side == SIDE_BACK ) { + split->next = childLists[1]; + childLists[1] = split; + } + } + + + // recursively process children + for ( i = 0 ; i < 2 ; i++ ) { + node->children[i] = AllocNode(); + node->children[i]->parent = node; + VectorCopy( node->mins, node->children[i]->mins ); + VectorCopy( node->maxs, node->children[i]->maxs ); + } + + for ( i = 0 ; i < 3 ; i++ ) { + if ( plane->normal[i] == 1 ) { + node->children[0]->mins[i] = plane->dist; + node->children[1]->maxs[i] = plane->dist; + break; + } + } + + for ( i = 0 ; i < 2 ; i++ ) { + BuildFaceTree_r ( node->children[i], childLists[i]); + } +} + + +/* +================ +FaceBSP + +List will be freed before returning +================ +*/ +tree_t *FaceBSP( face_t *list ) { + tree_t *tree; + face_t *face; + int i; + int count; + + Sys_FPrintf (SYS_VRB, "--- FaceBSP ---\n" ); + + tree = AllocTree (); + + count = 0; + for( face = list; face != NULL; face = face->next ) + { + count++; + for( i = 0; i < face->w->numpoints; i++ ) + { + AddPointToBounds( face->w->p[ i ], tree->mins, tree->maxs ); + } + } + Sys_FPrintf( SYS_VRB, "%9d faces\n", count ); + + tree->headnode = AllocNode(); + VectorCopy( tree->mins, tree->headnode->mins ); + VectorCopy( tree->maxs, tree->headnode->maxs ); + c_faceLeafs = 0; + + BuildFaceTree_r ( tree->headnode, list ); + + Sys_FPrintf( SYS_VRB, "%9d leafs\n", c_faceLeafs ); + + return tree; +} + + + +/* +MakeStructuralBSPFaceList() +get structural brush faces +*/ + +face_t *MakeStructuralBSPFaceList( brush_t *list ) +{ + brush_t *b; + int i; + side_t *s; + winding_t *w; + face_t *f, *flist; + + + flist = NULL; + for( b = list; b != NULL; b = b->next ) + { + if( b->detail ) + continue; + + for( i = 0; i < b->numsides; i++ ) + { + /* get side and winding */ + s = &b->sides[ i ]; + w = s->winding; + if( w == NULL ) + continue; + + /* ydnar: skip certain faces */ + if( s->compileFlags & C_SKIP ) + continue; + + /* allocate a face */ + f = AllocBspFace(); + f->w = CopyWinding( w ); + f->planenum = s->planenum & ~1; + f->compileFlags = s->compileFlags; /* ydnar */ + + /* ydnar: set priority */ + f->priority = 0; + if( f->compileFlags & C_HINT ) + f->priority += HINT_PRIORITY; + if( f->compileFlags & C_ANTIPORTAL ) + f->priority += ANTIPORTAL_PRIORITY; + if( f->compileFlags & C_AREAPORTAL ) + f->priority += AREAPORTAL_PRIORITY; + + /* get next face */ + f->next = flist; + flist = f; + } + } + + return flist; +} + + + +/* +MakeVisibleBSPFaceList() +get visible brush faces +*/ + +face_t *MakeVisibleBSPFaceList( brush_t *list ) +{ + brush_t *b; + int i; + side_t *s; + winding_t *w; + face_t *f, *flist; + + + flist = NULL; + for( b = list; b != NULL; b = b->next ) + { + if( b->detail ) + continue; + + for( i = 0; i < b->numsides; i++ ) + { + /* get side and winding */ + s = &b->sides[ i ]; + w = s->visibleHull; + if( w == NULL ) + continue; + + /* ydnar: skip certain faces */ + if( s->compileFlags & C_SKIP ) + continue; + + /* allocate a face */ + f = AllocBspFace(); + f->w = CopyWinding( w ); + f->planenum = s->planenum & ~1; + f->compileFlags = s->compileFlags; /* ydnar */ + + /* ydnar: set priority */ + f->priority = 0; + if( f->compileFlags & C_HINT ) + f->priority += HINT_PRIORITY; + if( f->compileFlags & C_ANTIPORTAL ) + f->priority += ANTIPORTAL_PRIORITY; + if( f->compileFlags & C_AREAPORTAL ) + f->priority += AREAPORTAL_PRIORITY; + + /* get next face */ + f->next = flist; + flist = f; + } + } + + return flist; +} + diff --git a/tools/quake3/q3map2/fog.c b/tools/quake3/q3map2/fog.c new file mode 100644 index 00000000..c9ee6e25 --- /dev/null +++ b/tools/quake3/q3map2/fog.c @@ -0,0 +1,803 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define FOG_C + + + +/* dependencies */ +#include "q3map2.h" + + + +int numFogFragments; +int numFogPatchFragments; + + + +/* +DrawSurfToMesh() +converts a patch drawsurface to a mesh_t +*/ + +mesh_t *DrawSurfToMesh( mapDrawSurface_t *ds ) +{ + mesh_t *m; + + + m = safe_malloc( sizeof( *m ) ); + m->width = ds->patchWidth; + m->height = ds->patchHeight; + m->verts = safe_malloc( sizeof(m->verts[ 0 ]) * m->width * m->height ); + memcpy( m->verts, ds->verts, sizeof(m->verts[ 0 ]) * m->width * m->height ); + + return m; +} + + + +/* +SplitMeshByPlane() +chops a mesh by a plane +*/ + +void SplitMeshByPlane( mesh_t *in, vec3_t normal, float dist, mesh_t **front, mesh_t **back ) +{ + int w, h, split; + float d[MAX_PATCH_SIZE][MAX_PATCH_SIZE]; + bspDrawVert_t *dv, *v1, *v2; + int c_front, c_back, c_on; + mesh_t *f, *b; + int i; + float frac; + int frontAprox, backAprox; + + for ( i = 0 ; i < 2 ; i++ ) { + dv = in->verts; + c_front = 0; + c_back = 0; + c_on = 0; + for ( h = 0 ; h < in->height ; h++ ) { + for ( w = 0 ; w < in->width ; w++, dv++ ) { + d[h][w] = DotProduct( dv->xyz, normal ) - dist; + if ( d[h][w] > ON_EPSILON ) { + c_front++; + } else if ( d[h][w] < -ON_EPSILON ) { + c_back++; + } else { + c_on++; + } + } + } + + *front = NULL; + *back = NULL; + + if ( !c_front ) { + *back = in; + return; + } + if ( !c_back ) { + *front = in; + return; + } + + // find a split point + split = -1; + for ( w = 0 ; w < in->width -1 ; w++ ) { + if ( ( d[0][w] < 0 ) != ( d[0][w+1] < 0 ) ) { + if ( split == -1 ) { + split = w; + break; + } + } + } + + if ( split == -1 ) { + if ( i == 1 ) { + Sys_FPrintf (SYS_VRB, "No crossing points in patch\n"); + *front = in; + return; + } + + in = TransposeMesh( in ); + InvertMesh( in ); + continue; + } + + // make sure the split point stays the same for all other rows + for ( h = 1 ; h < in->height ; h++ ) { + for ( w = 0 ; w < in->width -1 ; w++ ) { + if ( ( d[h][w] < 0 ) != ( d[h][w+1] < 0 ) ) { + if ( w != split ) { + Sys_Printf( "multiple crossing points for patch -- can't clip\n"); + *front = in; + return; + } + } + } + if ( ( d[h][split] < 0 ) == ( d[h][split+1] < 0 ) ) { + Sys_Printf( "differing crossing points for patch -- can't clip\n"); + *front = in; + return; + } + } + + break; + } + + + // create two new meshes + f = safe_malloc( sizeof( *f ) ); + f->width = split + 2; + if ( ! (f->width & 1) ) { + f->width++; + frontAprox = 1; + } else { + frontAprox = 0; + } + if ( f->width > MAX_PATCH_SIZE ) { + Error( "MAX_PATCH_SIZE after split"); + } + f->height = in->height; + f->verts = safe_malloc( sizeof(f->verts[0]) * f->width * f->height ); + + b = safe_malloc( sizeof( *b ) ); + b->width = in->width - split; + if ( ! (b->width & 1) ) { + b->width++; + backAprox = 1; + } else { + backAprox = 0; + } + if ( b->width > MAX_PATCH_SIZE ) { + Error( "MAX_PATCH_SIZE after split"); + } + b->height = in->height; + b->verts = safe_malloc( sizeof(b->verts[0]) * b->width * b->height ); + + if ( d[0][0] > 0 ) { + *front = f; + *back = b; + } else { + *front = b; + *back = f; + } + + // distribute the points + for ( w = 0 ; w < in->width ; w++ ) { + for ( h = 0 ; h < in->height ; h++ ) { + if ( w <= split ) { + f->verts[ h * f->width + w ] = in->verts[ h * in->width + w ]; + } else { + b->verts[ h * b->width + w - split + backAprox ] = in->verts[ h * in->width + w ]; + } + } + } + + // clip the crossing line + for ( h = 0; h < in->height; h++ ) + { + dv = &f->verts[ h * f->width + split + 1 ]; + v1 = &in->verts[ h * in->width + split ]; + v2 = &in->verts[ h * in->width + split + 1 ]; + + frac = d[h][split] / ( d[h][split] - d[h][split+1] ); + + /* interpolate */ + //% for( i = 0; i < 10; i++ ) + //% dv->xyz[ i ] = v1->xyz[ i ] + frac * (v2->xyz[ i ] - v1->xyz[ i ]); + //% dv->xyz[10] = 0; // set all 4 colors to 0 + LerpDrawVertAmount( v1, v2, frac, dv ); + + if ( frontAprox ) { + f->verts[ h * f->width + split + 2 ] = *dv; + } + b->verts[ h * b->width ] = *dv; + if ( backAprox ) { + b->verts[ h * b->width + 1 ] = *dv; + } + } + + /* +PrintMesh( in ); +Sys_Printf("\n"); +PrintMesh( f ); +Sys_Printf("\n"); +PrintMesh( b ); +Sys_Printf("\n"); + */ + + FreeMesh( in ); +} + + +/* +ChopPatchSurfaceByBrush() +chops a patch up by a fog brush +*/ + +qboolean ChopPatchSurfaceByBrush( entity_t *e, mapDrawSurface_t *ds, brush_t *b ) +{ + int i, j; + side_t *s; + plane_t *plane; + mesh_t *outside[MAX_BRUSH_SIDES]; + int numOutside; + mesh_t *m, *front, *back; + mapDrawSurface_t *newds; + + m = DrawSurfToMesh( ds ); + numOutside = 0; + + // only split by the top and bottom planes to avoid + // some messy patch clipping issues + + for ( i = 4 ; i <= 5 ; i++ ) { + s = &b->sides[ i ]; + plane = &mapplanes[ s->planenum ]; + + SplitMeshByPlane( m, plane->normal, plane->dist, &front, &back ); + + if ( !back ) { + // nothing actually contained inside + for ( j = 0 ; j < numOutside ; j++ ) { + FreeMesh( outside[j] ); + } + return qfalse; + } + m = back; + + if ( front ) { + if ( numOutside == MAX_BRUSH_SIDES ) { + Error( "MAX_BRUSH_SIDES" ); + } + outside[ numOutside ] = front; + numOutside++; + } + } + + /* all of outside fragments become seperate drawsurfs */ + numFogPatchFragments += numOutside; + for( i = 0; i < numOutside; i++ ) + { + /* transpose and invert the chopped patch (fixes potential crash. fixme: why?) */ + outside[ i ] = TransposeMesh( outside[ i ] ); + InvertMesh( outside[ i ] ); + + /* ydnar: do this the hacky right way */ + newds = AllocDrawSurface( SURFACE_PATCH ); + memcpy( newds, ds, sizeof( *ds ) ); + newds->patchWidth = outside[ i ]->width; + newds->patchHeight = outside[ i ]->height; + newds->numVerts = outside[ i ]->width * outside[ i ]->height; + newds->verts = safe_malloc( newds->numVerts * sizeof( *newds->verts ) ); + memcpy( newds->verts, outside[ i ]->verts, newds->numVerts * sizeof( *newds->verts ) ); + + /* free the source mesh */ + FreeMesh( outside[ i ] ); + } + + /* only rejigger this patch if it was chopped */ + //% Sys_Printf( "Inside: %d x %d\n", m->width, m->height ); + if( numOutside > 0 ) + { + /* transpose and invert the chopped patch (fixes potential crash. fixme: why?) */ + m = TransposeMesh( m ); + InvertMesh( m ); + + /* replace ds with m */ + ds->patchWidth = m->width; + ds->patchHeight = m->height; + ds->numVerts = m->width * m->height; + free( ds->verts ); + ds->verts = safe_malloc( ds->numVerts * sizeof( *ds->verts ) ); + memcpy( ds->verts, m->verts, ds->numVerts * sizeof( *ds->verts ) ); + } + + /* free the source mesh and return */ + FreeMesh( m ); + return qtrue; +} + + + +/* +WindingFromDrawSurf() +creates a winding from a surface's verts +*/ + +winding_t *WindingFromDrawSurf( mapDrawSurface_t *ds ) +{ + winding_t *w; + int i; + + // we use the first point of the surface, maybe something more clever would be useful + // (actually send the whole draw surface would be cool?) + if( ds->numVerts >= MAX_POINTS_ON_WINDING ) + { + int max = ds->numVerts; + vec3_t p[256]; + + if(max > 256) + max = 256; + + for ( i = 0 ; i < max ; i++ ) { + VectorCopy( ds->verts[i].xyz, p[i] ); + } + + xml_Winding( "WindingFromDrawSurf failed: MAX_POINTS_ON_WINDING exceeded", p, max, qtrue ); + } + + w = AllocWinding( ds->numVerts ); + w->numpoints = ds->numVerts; + for ( i = 0 ; i < ds->numVerts ; i++ ) { + VectorCopy( ds->verts[i].xyz, w->p[i] ); + } + return w; +} + + + +/* +ChopFaceSurfaceByBrush() +chops up a face drawsurface by a fog brush, with a potential fragment left inside +*/ + +qboolean ChopFaceSurfaceByBrush( entity_t *e, mapDrawSurface_t *ds, brush_t *b ) +{ + int i, j; + side_t *s; + plane_t *plane; + winding_t *w; + winding_t *front, *back; + winding_t *outside[ MAX_BRUSH_SIDES ]; + int numOutside; + mapDrawSurface_t *newds; + + + /* dummy check */ + if( ds->sideRef == NULL || ds->sideRef->side == NULL ) + return qfalse; + + /* initial setup */ + w = WindingFromDrawSurf( ds ); + numOutside = 0; + + /* chop by each brush side */ + for( i = 0; i < b->numsides; i++ ) + { + /* get brush side and plane */ + s = &b->sides[ i ]; + plane = &mapplanes[ s->planenum ]; + + /* handle coplanar outfacing (don't fog) */ + if( ds->sideRef->side->planenum == s->planenum ) + return qfalse; + + /* handle coplanar infacing (keep inside) */ + if( (ds->sideRef->side->planenum ^ 1) == s->planenum ) + continue; + + /* general case */ + ClipWindingEpsilon( w, plane->normal, plane->dist, ON_EPSILON, &front, &back ); + FreeWinding( w ); + + if( back == NULL ) + { + /* nothing actually contained inside */ + for( j = 0; j < numOutside; j++ ) + FreeWinding( outside[ j ] ); + return qfalse; + } + + if( front != NULL ) + { + if( numOutside == MAX_BRUSH_SIDES ) + Error( "MAX_BRUSH_SIDES" ); + outside[ numOutside ] = front; + numOutside++; + } + + w = back; + } + + /* fixme: celshaded surface fragment errata */ + + /* all of outside fragments become seperate drawsurfs */ + numFogFragments += numOutside; + s = ds->sideRef->side; + for( i = 0; i < numOutside; i++ ) + { + newds = DrawSurfaceForSide( e, ds->mapBrush, s, outside[ i ] ); + newds->fogNum = ds->fogNum; + FreeWinding( outside[ i ] ); + } + + /* ydnar: the old code neglected to snap to 0.125 for the fragment + inside the fog brush, leading to sparklies. this new code does + the right thing and uses the original surface's brush side */ + + /* build a drawsurf for it */ + newds = DrawSurfaceForSide( e, ds->mapBrush, s, w ); + if( newds == NULL ) + return qfalse; + + /* copy new to original */ + ClearSurface( ds ); + memcpy( ds, newds, sizeof( mapDrawSurface_t ) ); + + /* didn't really add a new drawsurface... :) */ + numMapDrawSurfs--; + + /* return ok */ + return qtrue; +} + + + +/* +FogDrawSurfaces() +call after the surface list has been pruned, before tjunction fixing +*/ + +void FogDrawSurfaces( entity_t *e ) +{ + int i, j, k, fogNum; + fog_t *fog; + mapDrawSurface_t *ds; + vec3_t mins, maxs; + int fogged, numFogged; + int numBaseDrawSurfs; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "----- FogDrawSurfs -----\n" ); + + /* reset counters */ + numFogged = 0; + numFogFragments = 0; + + /* walk fog list */ + for( fogNum = 0; fogNum < numMapFogs; fogNum++ ) + { + /* get fog */ + fog = &mapFogs[ fogNum ]; + + /* clip each surface into this, but don't clip any of the resulting fragments to the same brush */ + numBaseDrawSurfs = numMapDrawSurfs; + for( i = 0; i < numBaseDrawSurfs; i++ ) + { + /* get the drawsurface */ + ds = &mapDrawSurfs[ i ]; + + /* no fog? */ + if( ds->shaderInfo->noFog ) + continue; + + /* global fog doesn't have a brush */ + if( fog->brush == NULL ) + { + /* don't re-fog already fogged surfaces */ + if( ds->fogNum >= 0 ) + continue; + fogged = 1; + } + else + { + /* find drawsurface bounds */ + ClearBounds( mins, maxs ); + for( j = 0; j < ds->numVerts; j++ ) + AddPointToBounds( ds->verts[ j ].xyz, mins, maxs ); + + /* check against the fog brush */ + for( k = 0; k < 3; k++ ) + { + if( mins[ k ] > fog->brush->maxs[ k ] ) + break; + if( maxs[ k ] < fog->brush->mins[ k ] ) + break; + } + + /* no intersection? */ + if( k < 3 ) + continue; + + /* ydnar: gs mods: handle the various types of surfaces */ + switch( ds->type ) + { + /* handle brush faces */ + case SURFACE_FACE: + fogged = ChopFaceSurfaceByBrush( e, ds, fog->brush ); + break; + + /* handle patches */ + case SURFACE_PATCH: + fogged = ChopPatchSurfaceByBrush( e, ds, fog->brush ); + break; + + /* handle triangle surfaces (fixme: split triangle surfaces) */ + case SURFACE_TRIANGLES: + case SURFACE_FORCED_META: + case SURFACE_META: + fogged = 1; + break; + + /* no fogging */ + default: + fogged = 0; + break; + } + } + + /* is this surface fogged? */ + if( fogged ) + { + numFogged += fogged; + ds->fogNum = fogNum; + } + } + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d fog polygon fragments\n", numFogFragments ); + Sys_FPrintf( SYS_VRB, "%9d fog patch fragments\n", numFogPatchFragments ); + Sys_FPrintf( SYS_VRB, "%9d fogged drawsurfs\n", numFogged ); +} + + + +/* +FogForPoint() - ydnar +gets the fog number for a point in space +*/ + +int FogForPoint( vec3_t point, float epsilon ) +{ + int fogNum, i, j; + float dot; + qboolean inside; + brush_t *brush; + plane_t *plane; + + + /* start with bogus fog num */ + fogNum = defaultFogNum; + + /* walk the list of fog volumes */ + for( i = 0; i < numMapFogs; i++ ) + { + /* sof2: global fog doesn't reference a brush */ + if( mapFogs[ i ].brush == NULL ) + { + fogNum = i; + continue; + } + + /* get fog brush */ + brush = mapFogs[ i ].brush; + + /* check point against all planes */ + inside = qtrue; + for( j = 0; j < brush->numsides && inside; j++ ) + { + plane = &mapplanes[ brush->sides[ j ].planenum ]; /* note usage of map planes here */ + dot = DotProduct( point, plane->normal ); + dot -= plane->dist; + if( dot > epsilon ) + inside = qfalse; + } + + /* if inside, return the fog num */ + if( inside ) + { + //% Sys_Printf( "FogForPoint: %f, %f, %f in fog %d\n", point[ 0 ], point[ 1 ], point[ 2 ], i ); + return i; + } + } + + /* if the point made it this far, it's not inside any fog volumes (or inside global fog) */ + return fogNum; +} + + + +/* +FogForBounds() - ydnar +gets the fog number for a bounding box +*/ + +int FogForBounds( vec3_t mins, vec3_t maxs, float epsilon ) +{ + int fogNum, i, j; + float highMin, lowMax, volume, bestVolume; + vec3_t fogMins, fogMaxs, overlap; + brush_t *brush; + + + /* start with bogus fog num */ + fogNum = defaultFogNum; + + /* init */ + bestVolume = 0.0f; + + /* walk the list of fog volumes */ + for( i = 0; i < numMapFogs; i++ ) + { + /* sof2: global fog doesn't reference a brush */ + if( mapFogs[ i ].brush == NULL ) + { + fogNum = i; + continue; + } + + /* get fog brush */ + brush = mapFogs[ i ].brush; + + /* get bounds */ + fogMins[ 0 ] = brush->mins[ 0 ] - epsilon; + fogMins[ 1 ] = brush->mins[ 1 ] - epsilon; + fogMins[ 2 ] = brush->mins[ 2 ] - epsilon; + fogMaxs[ 0 ] = brush->maxs[ 0 ] + epsilon; + fogMaxs[ 1 ] = brush->maxs[ 1 ] + epsilon; + fogMaxs[ 2 ] = brush->maxs[ 2 ] + epsilon; + + /* check against bounds */ + for( j = 0; j < 3; j++ ) + { + if( mins[ j ] > fogMaxs[ j ] || maxs[ j ] < fogMins[ j ] ) + break; + highMin = mins[ j ] > fogMins[ j ] ? mins[ j ] : fogMins[ j ]; + lowMax = maxs[ j ] < fogMaxs[ j ] ? maxs[ j ] : fogMaxs[ j ]; + overlap[ j ] = lowMax - highMin; + if( overlap[ j ] < 1.0f ) + overlap[ j ] = 1.0f; + } + + /* no overlap */ + if( j < 3 ) + continue; + + /* get volume */ + volume = overlap[ 0 ] * overlap[ 1 ] * overlap[ 2 ]; + + /* test against best volume */ + if( volume > bestVolume ) + { + bestVolume = volume; + fogNum = i; + } + } + + /* if the point made it this far, it's not inside any fog volumes (or inside global fog) */ + return fogNum; +} + + + +/* +CreateMapFogs() - ydnar +generates a list of map fogs +*/ + +void CreateMapFogs( void ) +{ + int i; + entity_t *entity; + brush_t *brush; + fog_t *fog; + vec3_t invFogDir; + const char *globalFog; + + + /* skip? */ + if( nofog ) + return; + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- CreateMapFogs ---\n" ); + + /* walk entities */ + for( i = 0; i < numEntities; i++ ) + { + /* get entity */ + entity = &entities[ i ]; + + /* walk entity brushes */ + for( brush = entity->brushes; brush != NULL; brush = brush->next ) + { + /* ignore non-fog brushes */ + if( brush->contentShader->fogParms == qfalse ) + continue; + + /* test limit */ + if( numMapFogs >= MAX_MAP_FOGS ) + Error( "Exceeded MAX_MAP_FOGS (%d)", MAX_MAP_FOGS ); + + /* set up fog */ + fog = &mapFogs[ numMapFogs++ ]; + fog->si = brush->contentShader; + fog->brush = brush; + fog->visibleSide = -1; + + /* if shader specifies an explicit direction, then find a matching brush side with an opposed normal */ + if( VectorLength( fog->si->fogDir ) ) + { + /* flip it */ + VectorScale( fog->si->fogDir, -1.0f, invFogDir ); + + /* find the brush side */ + for( i = 0; i < brush->numsides; i++ ) + { + if( VectorCompare( invFogDir, mapplanes[ brush->sides[ i ].planenum ].normal ) ) + { + fog->visibleSide = i; + //% Sys_Printf( "Brush num: %d Side num: %d\n", fog->brushNum, fog->visibleSide ); + break; + } + } + } + } + } + + /* ydnar: global fog */ + globalFog = ValueForKey( &entities[ 0 ], "_fog" ); + if( globalFog[ 0 ] == '\0' ) + globalFog = ValueForKey( &entities[ 0 ], "fog" ); + if( globalFog[ 0 ] != '\0' ) + { + /* test limit */ + if( numMapFogs >= MAX_MAP_FOGS ) + Error( "Exceeded MAX_MAP_FOGS (%d) trying to add global fog", MAX_MAP_FOGS ); + + /* note it */ + Sys_FPrintf( SYS_VRB, "Map has global fog shader %s\n", globalFog ); + + /* set up fog */ + fog = &mapFogs[ numMapFogs++ ]; + fog->si = ShaderInfoForShader( globalFog ); + if( fog->si == NULL ) + Error( "Invalid shader \"%s\" referenced trying to add global fog", globalFog ); + fog->brush = NULL; + fog->visibleSide = -1; + + /* set as default fog */ + defaultFogNum = numMapFogs - 1; + + /* mark all worldspawn brushes as fogged */ + for( brush = entities[ 0 ].brushes; brush != NULL; brush = brush->next ) + ApplySurfaceParm( "fog", &brush->contentFlags, NULL, &brush->compileFlags ); + } + + /* emit some stats */ + Sys_FPrintf( SYS_VRB, "%9d fogs\n", numMapFogs ); +} + diff --git a/tools/quake3/q3map2/game_ef.h b/tools/quake3/q3map2/game_ef.h new file mode 100644 index 00000000..7cf5a157 --- /dev/null +++ b/tools/quake3/q3map2/game_ef.h @@ -0,0 +1,195 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME_EF_H +#define GAME_EF_H + + + +/* ------------------------------------------------------------------------------- + +content and surface flags + +------------------------------------------------------------------------------- */ + +/* game flags */ +#define E_CONT_SOLID 1 /* an eye is never valid in a solid */ +#define E_CONT_LAVA 8 +#define E_CONT_SLIME 16 +#define E_CONT_WATER 32 +#define E_CONT_FOG 64 +#define E_CONT_LADDER 128 /* elite force ladder contents */ + +#define E_CONT_AREAPORTAL 0x8000 + +#define E_CONT_PLAYERCLIP 0x10000 +#define E_CONT_MONSTERCLIP 0x20000 +#define E_CONT_SHOTCLIP 0x40000 /* elite force shot clip */ +#define E_CONT_ITEM 0x80000 +#define E_CONT_CLUSTERPORTAL 0x100000 +#define E_CONT_DONOTENTER 0x200000 +#define E_CONT_BOTCLIP 0x400000 + +#define E_CONT_ORIGIN 0x1000000 /* removed before bsping an entity */ + +#define E_CONT_BODY 0x2000000 /* should never be on a brush, only in game */ +#define E_CONT_CORPSE 0x4000000 +#define E_CONT_DETAIL 0x8000000 /* brushes not used for the bsp */ +#define E_CONT_STRUCTURAL 0x10000000 /* brushes used for the bsp */ +#define E_CONT_TRANSLUCENT 0x20000000 /* don't consume surface fragments inside */ +#define E_CONT_TRIGGER 0x40000000 +#define E_CONT_NODROP 0x80000000 /* don't leave bodies or items (death fog, lava) */ + +#define E_SURF_NODAMAGE 0x1 /* never give falling damage */ +#define E_SURF_SLICK 0x2 /* effects game physics */ +#define E_SURF_SKY 0x4 /* lighting from environment map */ +#define E_SURF_LADDER 0x8 +#define E_SURF_NOIMPACT 0x10 /* don't make missile explosions */ +#define E_SURF_NOMARKS 0x20 /* don't leave missile marks */ +#define E_SURF_FLESH 0x40 /* make flesh sounds and effects */ +#define E_SURF_NODRAW 0x80 /* don't generate a drawsurface at all */ +#define E_SURF_HINT 0x100 /* make a primary bsp splitter */ +#define E_SURF_SKIP 0x200 /* completely ignore, allowing non-closed brushes */ +#define E_SURF_NOLIGHTMAP 0x400 /* surface doesn't need a lightmap */ +#define E_SURF_POINTLIGHT 0x800 /* generate lighting info at vertexes */ +#define E_SURF_METALSTEPS 0x1000 /* clanking footsteps */ +#define E_SURF_NOSTEPS 0x2000 /* no footstep sounds */ +#define E_SURF_NONSOLID 0x4000 /* don't collide against curves with this set */ +#define E_SURF_LIGHTFILTER 0x8000 /* act as a light filter during q3map -light */ +#define E_SURF_ALPHASHADOW 0x10000 /* do per-pixel light shadow casting in q3map */ +#define E_SURF_NODLIGHT 0x20000 /* don't dlight even if solid (solid lava, skies) */ +#define E_SURF_FORCEFIELD 0x40000 /* elite force forcefield brushes */ + +/* ydnar flags */ +#define E_SURF_VERTEXLIT (E_SURF_POINTLIGHT | E_SURF_NOLIGHTMAP) + + + +/* ------------------------------------------------------------------------------- + +game_t struct + +------------------------------------------------------------------------------- */ + +{ + "ef", /* -game x */ + "baseef", /* default base game data dir */ + ".ef", /* unix home sub-dir */ + "elite", /* magic path word */ + "scripts", /* shader directory */ + 64, /* max lightmapped surface verts */ + 999, /* max surface verts */ + 6000, /* max surface indexes */ + qfalse, /* flares */ + "flareshader", /* default flare shader */ + qfalse, /* wolf lighting model? */ + 128, /* lightmap width/height */ + 1.0f, /* lightmap gamma */ + 1.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + "IBSP", /* bsp file prefix */ + 46, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadIBSPFile, /* bsp load function */ + WriteIBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", E_CONT_SOLID, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", E_CONT_ORIGIN, E_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", E_CONT_AREAPORTAL, E_CONT_SOLID, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", E_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", E_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", E_CONT_STRUCTURAL, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, E_SURF_HINT, 0, C_HINT, 0 }, + { "nodraw", 0, 0, E_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, E_SURF_ALPHASHADOW, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, E_SURF_LIGHTFILTER, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, E_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, E_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, E_CONT_SOLID, E_SURF_NONSOLID, 0, 0, C_SOLID }, + + { "trigger", E_CONT_TRIGGER, E_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", E_CONT_WATER, E_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slime", E_CONT_SLIME, E_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", E_CONT_LAVA, E_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "playerclip", E_CONT_PLAYERCLIP, E_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", E_CONT_MONSTERCLIP, E_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "shotclip", E_CONT_SHOTCLIP, E_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", E_CONT_NODROP, E_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "clusterportal", E_CONT_CLUSTERPORTAL, E_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "donotenter", E_CONT_DONOTENTER, E_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "botclip", E_CONT_BOTCLIP, E_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "fog", E_CONT_FOG, E_CONT_SOLID, 0, 0, C_FOG, C_SOLID }, + { "sky", 0, 0, E_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, E_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, E_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, E_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "ladder", E_CONT_LADDER, E_CONT_SOLID, E_SURF_LADDER, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodamage", 0, 0, E_SURF_NODAMAGE, 0, 0, 0 }, + { "metalsteps", 0, 0, E_SURF_METALSTEPS, 0, 0, 0 }, + { "flesh", 0, 0, E_SURF_FLESH, 0, 0, 0 }, + { "nosteps", 0, 0, E_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, E_SURF_NODLIGHT, 0, 0, 0 }, + { "forcefield", 0, 0, E_SURF_FORCEFIELD, 0, 0, 0 }, + + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif + diff --git a/tools/quake3/q3map2/game_etut.h b/tools/quake3/q3map2/game_etut.h new file mode 100644 index 00000000..db7ec467 --- /dev/null +++ b/tools/quake3/q3map2/game_etut.h @@ -0,0 +1,265 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME_ETUT_H +#define GAME_ETUT_H + + + +/* ------------------------------------------------------------------------------- + +content and surface flags + +------------------------------------------------------------------------------- */ + +/* game flags */ +#define U_CONT_SOLID 1 /* an eye is never valid in a solid */ +#define U_CONT_LAVA 8 +#define U_CONT_SLIME 16 +#define U_CONT_WATER 32 +#define U_CONT_FOG 64 + +#define U_CONT_AREAPORTAL 0x8000 + +#define U_CONT_PLAYERCLIP 0x10000 +#define U_CONT_MONSTERCLIP 0x20000 +#define U_CONT_TELEPORTER 0x40000 +#define U_CONT_JUMPPAD 0x80000 +#define U_CONT_CLUSTERPORTAL 0x100000 +#define U_CONT_DONOTENTER 0x200000 +#define U_CONT_BOTCLIP 0x400000 + +#define U_CONT_ORIGIN 0x1000000 /* removed before bsping an entity */ + +#define U_CONT_BODY 0x2000000 /* should never be on a brush, only in game */ +#define U_CONT_CORPSE 0x4000000 +#define U_CONT_DETAIL 0x8000000 /* brushes not used for the bsp */ +#define U_CONT_STRUCTURAL 0x10000000 /* brushes used for the bsp */ +#define U_CONT_TRANSLUCENT 0x20000000 /* don't consume surface fragments inside */ +#define U_CONT_TRIGGER 0x40000000 +#define U_CONT_NODROP 0x80000000 /* don't leave bodies or items (death fog, lava) */ + +#define U_SURF_NODAMAGE 0x1 /* never give falling damage */ +#define U_SURF_SLICK 0x2 /* effects game physics */ +#define U_SURF_SKY 0x4 /* lighting from environment map */ +#define U_SURF_LADDER 0x8 +#define U_SURF_NOIMPACT 0x10 /* don't make missile explosions */ +#define U_SURF_NOMARKS 0x20 /* don't leave missile marks */ +#define U_SURF_FLESH 0x40 /* make flesh sounds and effects */ +#define U_SURF_NODRAW 0x80 /* don't generate a drawsurface at all */ +#define U_SURF_HINT 0x100 /* make a primary bsp splitter */ +#define U_SURF_SKIP 0x200 /* completely ignore, allowing non-closed brushes */ +#define U_SURF_NOLIGHTMAP 0x400 /* surface doesn't need a lightmap */ +#define U_SURF_POINTLIGHT 0x800 /* generate lighting info at vertexes */ +#define U_SURF_METALSTEPS 0x1000 /* clanking footsteps */ +#define U_SURF_NOSTEPS 0x2000 /* no footstep sounds */ +#define U_SURF_NONSOLID 0x4000 /* don't collide against curves with this set */ +#define U_SURF_LIGHTFILTER 0x8000 /* act as a light filter during q3map -light */ +#define U_SURF_ALPHASHADOW 0x10000 /* do per-pixel light shadow casting in q3map */ +#define U_SURF_NODLIGHT 0x20000 /* don't dlight even if solid (solid lava, skies) */ +#define U_SURF_DUST 0x40000 /* leave a dust trail when walking on this surface */ + +/* ydnar flags */ +#define U_SURF_VERTEXLIT (U_SURF_POINTLIGHT | U_SURF_NOLIGHTMAP) + +/* materials */ +#define U_MAT_MASK 0xFFF00000 /* mask to get the material type */ + +#define U_MAT_NONE 0x00000000 +#define U_MAT_TIN 0x00100000 +#define U_MAT_ALUMINUM 0x00200000 +#define U_MAT_IRON 0x00300000 +#define U_MAT_TITANIUM 0x00400000 +#define U_MAT_STEEL 0x00500000 +#define U_MAT_BRASS 0x00600000 +#define U_MAT_COPPER 0x00700000 +#define U_MAT_CEMENT 0x00800000 +#define U_MAT_ROCK 0x00900000 +#define U_MAT_GRAVEL 0x00A00000 +#define U_MAT_PAVEMENT 0x00B00000 +#define U_MAT_BRICK 0x00C00000 +#define U_MAT_CLAY 0x00D00000 +#define U_MAT_GRASS 0x00E00000 +#define U_MAT_DIRT 0x00F00000 +#define U_MAT_MUD 0x01000000 +#define U_MAT_SNOW 0x01100000 +#define U_MAT_ICE 0x01200000 +#define U_MAT_SAND 0x01300000 +#define U_MAT_CERAMICTILE 0x01400000 +#define U_MAT_LINOLEUM 0x01500000 +#define U_MAT_RUG 0x01600000 +#define U_MAT_PLASTER 0x01700000 +#define U_MAT_PLASTIC 0x01800000 +#define U_MAT_CARDBOARD 0x01900000 +#define U_MAT_HARDWOOD 0x01A00000 +#define U_MAT_SOFTWOOD 0x01B00000 +#define U_MAT_PLANK 0x01C00000 +#define U_MAT_GLASS 0x01D00000 +#define U_MAT_WATER 0x01E00000 +#define U_MAT_STUCCO 0x01F00000 + + + +/* ------------------------------------------------------------------------------- + +game_t struct + +------------------------------------------------------------------------------- */ + +{ + "etut", /* -game x */ + "etut", /* default base game data dir */ + ".etwolf", /* unix home sub-dir */ + "et", /* magic path word */ + "scripts", /* shader directory */ + 1024, /* max lightmapped surface verts */ + 1024, /* max surface verts */ + 6144, /* max surface indexes */ + qfalse, /* flares */ + "flareshader", /* default flare shader */ + qfalse, /* wolf lighting model? */ + 128, /* lightmap width/height */ + 2.2f, /* lightmap gamma */ + 1.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + "IBSP", /* bsp file prefix */ + 46, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadIBSPFile, /* bsp load function */ + WriteIBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", U_CONT_SOLID, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", U_CONT_ORIGIN, U_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", U_CONT_AREAPORTAL, U_CONT_SOLID, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", U_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", U_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", U_CONT_STRUCTURAL, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, U_SURF_HINT, 0, C_HINT, 0 }, + { "nodraw", 0, 0, U_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, U_SURF_ALPHASHADOW, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, U_SURF_LIGHTFILTER, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, U_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, U_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, U_CONT_SOLID, U_SURF_NONSOLID, 0, 0, C_SOLID }, + + { "trigger", U_CONT_TRIGGER, U_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", U_CONT_WATER, U_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slime", U_CONT_SLIME, U_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", U_CONT_LAVA, U_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "playerclip", U_CONT_PLAYERCLIP, U_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", U_CONT_MONSTERCLIP, U_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", U_CONT_NODROP, U_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "clusterportal", U_CONT_CLUSTERPORTAL, U_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "donotenter", U_CONT_DONOTENTER, U_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "botclip", U_CONT_BOTCLIP, U_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "fog", U_CONT_FOG, U_CONT_SOLID, 0, 0, C_FOG, C_SOLID }, + { "sky", 0, 0, U_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, U_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, U_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, U_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "ladder", 0, 0, U_SURF_LADDER, 0, 0, 0 }, + { "nodamage", 0, 0, U_SURF_NODAMAGE, 0, 0, 0 }, + { "metalsteps", 0, 0, U_SURF_METALSTEPS, 0, 0, 0 }, + { "flesh", 0, 0, U_SURF_FLESH, 0, 0, 0 }, + { "nosteps", 0, 0, U_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, U_SURF_NODLIGHT, 0, 0, 0 }, + { "dust", 0, 0, U_SURF_DUST, 0, 0, 0 }, + + + /* materials */ + { "*mat_none", 0, 0, U_MAT_NONE, U_MAT_MASK, 0, 0 }, + { "*mat_tin", 0, 0, U_MAT_TIN, U_MAT_MASK, 0, 0 }, + { "*mat_aluminum", 0, 0, U_MAT_ALUMINUM, U_MAT_MASK, 0, 0 }, + { "*mat_iron", 0, 0, U_MAT_IRON, U_MAT_MASK, 0, 0 }, + { "*mat_titanium", 0, 0, U_MAT_TITANIUM, U_MAT_MASK, 0, 0 }, + { "*mat_steel", 0, 0, U_MAT_STEEL, U_MAT_MASK, 0, 0 }, + { "*mat_brass", 0, 0, U_MAT_BRASS, U_MAT_MASK, 0, 0 }, + { "*mat_copper", 0, 0, U_MAT_COPPER, U_MAT_MASK, 0, 0 }, + { "*mat_cement", 0, 0, U_MAT_CEMENT, U_MAT_MASK, 0, 0 }, + { "*mat_rock", 0, 0, U_MAT_ROCK, U_MAT_MASK, 0, 0 }, + { "*mat_gravel", 0, 0, U_MAT_GRAVEL, U_MAT_MASK, 0, 0 }, + { "*mat_pavement", 0, 0, U_MAT_PAVEMENT, U_MAT_MASK, 0, 0 }, + { "*mat_brick", 0, 0, U_MAT_BRICK, U_MAT_MASK, 0, 0 }, + { "*mat_clay", 0, 0, U_MAT_CLAY, U_MAT_MASK, 0, 0 }, + { "*mat_grass", 0, 0, U_MAT_GRASS, U_MAT_MASK, 0, 0 }, + { "*mat_dirt", 0, 0, U_MAT_DIRT, U_MAT_MASK, 0, 0 }, + { "*mat_mud", 0, 0, U_MAT_MUD, U_MAT_MASK, 0, 0 }, + { "*mat_snow", 0, 0, U_MAT_SNOW, U_MAT_MASK, 0, 0 }, + { "*mat_ice", 0, 0, U_MAT_ICE, U_MAT_MASK, 0, 0 }, + { "*mat_sand", 0, 0, U_MAT_SAND, U_MAT_MASK, 0, 0 }, + { "*mat_ceramic", 0, 0, U_MAT_CERAMICTILE, U_MAT_MASK, 0, 0 }, + { "*mat_ceramictile", 0, 0, U_MAT_CERAMICTILE, U_MAT_MASK, 0, 0 }, + { "*mat_linoleum", 0, 0, U_MAT_LINOLEUM, U_MAT_MASK, 0, 0 }, + { "*mat_rug", 0, 0, U_MAT_RUG, U_MAT_MASK, 0, 0 }, + { "*mat_plaster", 0, 0, U_MAT_PLASTER, U_MAT_MASK, 0, 0 }, + { "*mat_plastic", 0, 0, U_MAT_PLASTIC, U_MAT_MASK, 0, 0 }, + { "*mat_cardboard", 0, 0, U_MAT_CARDBOARD, U_MAT_MASK, 0, 0 }, + { "*mat_hardwood", 0, 0, U_MAT_HARDWOOD, U_MAT_MASK, 0, 0 }, + { "*mat_softwood", 0, 0, U_MAT_SOFTWOOD, U_MAT_MASK, 0, 0 }, + { "*mat_plank", 0, 0, U_MAT_PLANK, U_MAT_MASK, 0, 0 }, + { "*mat_glass", 0, 0, U_MAT_GLASS, U_MAT_MASK, 0, 0 }, + { "*mat_water", 0, 0, U_MAT_WATER, U_MAT_MASK, 0, 0 }, + { "*mat_stucco", 0, 0, U_MAT_STUCCO, U_MAT_MASK, 0, 0 }, + + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif + diff --git a/tools/quake3/q3map2/game_ja.h b/tools/quake3/q3map2/game_ja.h new file mode 100644 index 00000000..f1b9c3da --- /dev/null +++ b/tools/quake3/q3map2/game_ja.h @@ -0,0 +1,189 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME_JA_H +#define GAME_JA_H + + + +/* ------------------------------------------------------------------------------- + +content and surface flags + +------------------------------------------------------------------------------- */ + +/* this file must be included *after* game_sof2.h and game_jk2.h because they share defines! */ + +#define JA_CONT_INSIDE 0x10000000 /* jedi academy 'inside' */ +#define JA_SURF_FORCESIGHT 0x02000000 /* jedi academy 'forcesight' */ + + + +/* ------------------------------------------------------------------------------- + +game_t struct + +------------------------------------------------------------------------------- */ + +{ + "ja", /* -game x */ + "base", /* default base game data dir */ + ".ja", /* unix home sub-dir */ + "GameData", /* magic path word */ + "shaders", /* shader directory */ + 64, /* max lightmapped surface verts */ + 999, /* max surface verts */ + 6000, /* max surface indexes */ + qtrue, /* flares */ + "gfx/misc/flare", /* default flare shader */ + qfalse, /* wolf lighting model? */ + 128, /* lightmap width/height */ + 1.0f, /* lightmap gamma */ + 1.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + "RBSP", /* bsp file prefix */ + 1, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadRBSPFile, /* bsp load function */ + WriteRBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", S_CONT_SOLID | S_CONT_OPAQUE, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", 0, S_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", S_CONT_TRANSLUCENT, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", S_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", S_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", 0, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, 0, 0, C_HINT, 0 }, + { "nodraw", 0, 0, S_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, 0, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, 0, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, 0, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, 0, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, S_CONT_SOLID, 0, 0, 0, C_SOLID }, + { "nonopaque", 0, S_CONT_OPAQUE, 0, 0, C_TRANSLUCENT, 0 }, /* setting trans ok? */ + + { "trigger", S_CONT_TRIGGER, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", S_CONT_WATER, S_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slime", S_CONT_SLIME, S_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", S_CONT_LAVA, S_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "shotclip", S_CONT_SHOTCLIP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, /* setting trans/detail ok? */ + { "playerclip", S_CONT_PLAYERCLIP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", S_CONT_MONSTERCLIP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", S_CONT_NODROP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + + { "terrain", S_CONT_TERRAIN, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "ladder", S_CONT_LADDER, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "abseil", S_CONT_ABSEIL, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "outside", S_CONT_OUTSIDE, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + + { "botclip", S_CONT_BOTCLIP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + + { "fog", S_CONT_FOG, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_FOG | C_DETAIL | C_TRANSLUCENT, C_SOLID }, /* nonopaque? */ + { "sky", 0, 0, S_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, S_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, S_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, S_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "nodamage", 0, 0, S_SURF_NODAMAGE, 0, 0, 0 }, + { "metalsteps", 0, 0, S_SURF_METALSTEPS, 0, 0, 0 }, + { "nosteps", 0, 0, S_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, S_SURF_NODLIGHT, 0, 0, 0 }, + { "nomiscents", 0, 0, S_SURF_NOMISCENTS, 0, 0, 0 }, + { "forcefield", 0, 0, S_SURF_FORCEFIELD, 0, 0, 0 }, + + /* jedi academy */ + { "inside", JA_CONT_INSIDE, 0, 0, 0, 0, 0 }, + { "forcesight", 0, 0, JA_SURF_FORCESIGHT, 0, 0, 0 }, + + /* materials */ + { "*mat_none", 0, 0, S_MAT_NONE, S_MAT_MASK, 0, 0 }, + { "*mat_solidwood", 0, 0, S_MAT_SOLIDWOOD, S_MAT_MASK, 0, 0 }, + { "*mat_hollowwood", 0, 0, S_MAT_HOLLOWWOOD, S_MAT_MASK, 0, 0 }, + { "*mat_solidmetal", 0, 0, S_MAT_SOLIDMETAL, S_MAT_MASK, 0, 0 }, + { "*mat_hollowmetal", 0, 0, S_MAT_HOLLOWMETAL, S_MAT_MASK, 0, 0 }, + { "*mat_shortgrass", 0, 0, S_MAT_SHORTGRASS, S_MAT_MASK, 0, 0 }, + { "*mat_longgrass", 0, 0, S_MAT_LONGGRASS, S_MAT_MASK, 0, 0 }, + { "*mat_dirt", 0, 0, S_MAT_DIRT, S_MAT_MASK, 0, 0 }, + { "*mat_sand", 0, 0, S_MAT_SAND, S_MAT_MASK, 0, 0 }, + { "*mat_gravel", 0, 0, S_MAT_GRAVEL, S_MAT_MASK, 0, 0 }, + { "*mat_glass", 0, 0, S_MAT_GLASS, S_MAT_MASK, 0, 0 }, + { "*mat_concrete", 0, 0, S_MAT_CONCRETE, S_MAT_MASK, 0, 0 }, + { "*mat_marble", 0, 0, S_MAT_MARBLE, S_MAT_MASK, 0, 0 }, + { "*mat_water", 0, 0, S_MAT_WATER, S_MAT_MASK, 0, 0 }, + { "*mat_snow", 0, 0, S_MAT_SNOW, S_MAT_MASK, 0, 0 }, + { "*mat_ice", 0, 0, S_MAT_ICE, S_MAT_MASK, 0, 0 }, + { "*mat_flesh", 0, 0, S_MAT_FLESH, S_MAT_MASK, 0, 0 }, + { "*mat_mud", 0, 0, S_MAT_MUD, S_MAT_MASK, 0, 0 }, + { "*mat_bpglass", 0, 0, S_MAT_BPGLASS, S_MAT_MASK, 0, 0 }, + { "*mat_dryleaves", 0, 0, S_MAT_DRYLEAVES, S_MAT_MASK, 0, 0 }, + { "*mat_greenleaves", 0, 0, S_MAT_GREENLEAVES, S_MAT_MASK, 0, 0 }, + { "*mat_fabric", 0, 0, S_MAT_FABRIC, S_MAT_MASK, 0, 0 }, + { "*mat_canvas", 0, 0, S_MAT_CANVAS, S_MAT_MASK, 0, 0 }, + { "*mat_rock", 0, 0, S_MAT_ROCK, S_MAT_MASK, 0, 0 }, + { "*mat_rubber", 0, 0, S_MAT_RUBBER, S_MAT_MASK, 0, 0 }, + { "*mat_plastic", 0, 0, S_MAT_PLASTIC, S_MAT_MASK, 0, 0 }, + { "*mat_tiles", 0, 0, S_MAT_TILES, S_MAT_MASK, 0, 0 }, + { "*mat_carpet", 0, 0, S_MAT_CARPET, S_MAT_MASK, 0, 0 }, + { "*mat_plaster", 0, 0, S_MAT_PLASTER, S_MAT_MASK, 0, 0 }, + { "*mat_shatterglass", 0, 0, S_MAT_SHATTERGLASS, S_MAT_MASK, 0, 0 }, + { "*mat_armor", 0, 0, S_MAT_ARMOR, S_MAT_MASK, 0, 0 }, + { "*mat_computer", 0, 0, S_MAT_COMPUTER, S_MAT_MASK, 0, 0 }, + + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif diff --git a/tools/quake3/q3map2/game_jk2.h b/tools/quake3/q3map2/game_jk2.h new file mode 100644 index 00000000..3a28c7bb --- /dev/null +++ b/tools/quake3/q3map2/game_jk2.h @@ -0,0 +1,183 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME_JK2_H +#define GAME_JK2_H + + + +/* ------------------------------------------------------------------------------- + +content and surface flags + +------------------------------------------------------------------------------- */ + +/* this file must be included *after* game_sof2.h because it shares defines! */ + + + +/* ------------------------------------------------------------------------------- + +game_t struct + +------------------------------------------------------------------------------- */ + +{ + "jk2", /* -game x */ + "base", /* default base game data dir */ + ".jk2", /* unix home sub-dir */ + "GameData", /* magic path word */ + "shaders", /* shader directory */ + 64, /* max lightmapped surface verts */ + 999, /* max surface verts */ + 6000, /* max surface indexes */ + qtrue, /* flares */ + "gfx/misc/flare", /* default flare shader */ + qfalse, /* wolf lighting model? */ + 128, /* lightmap width/height */ + 1.0f, /* lightmap gamma */ + 1.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + "RBSP", /* bsp file prefix */ + 1, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadRBSPFile, /* bsp load function */ + WriteRBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", S_CONT_SOLID | S_CONT_OPAQUE, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", 0, S_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", S_CONT_TRANSLUCENT, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", S_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", S_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", 0, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, 0, 0, C_HINT, 0 }, + { "nodraw", 0, 0, S_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, 0, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, 0, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, 0, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, 0, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, S_CONT_SOLID, 0, 0, 0, C_SOLID }, + { "nonopaque", 0, S_CONT_OPAQUE, 0, 0, C_TRANSLUCENT, 0 }, /* setting trans ok? */ + + { "trigger", S_CONT_TRIGGER, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", S_CONT_WATER, S_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slime", S_CONT_SLIME, S_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", S_CONT_LAVA, S_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "shotclip", S_CONT_SHOTCLIP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, /* setting trans/detail ok? */ + { "playerclip", S_CONT_PLAYERCLIP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", S_CONT_MONSTERCLIP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", S_CONT_NODROP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + + { "terrain", S_CONT_TERRAIN, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "ladder", S_CONT_LADDER, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "abseil", S_CONT_ABSEIL, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "outside", S_CONT_OUTSIDE, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + + { "botclip", S_CONT_BOTCLIP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + + { "fog", S_CONT_FOG, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_FOG | C_DETAIL | C_TRANSLUCENT, C_SOLID }, /* nonopaque? */ + { "sky", 0, 0, S_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, S_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, S_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, S_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "nodamage", 0, 0, S_SURF_NODAMAGE, 0, 0, 0 }, + { "metalsteps", 0, 0, S_SURF_METALSTEPS, 0, 0, 0 }, + { "nosteps", 0, 0, S_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, S_SURF_NODLIGHT, 0, 0, 0 }, + { "nomiscents", 0, 0, S_SURF_NOMISCENTS, 0, 0, 0 }, + { "forcefield", 0, 0, S_SURF_FORCEFIELD, 0, 0, 0 }, + + + /* materials */ + { "*mat_none", 0, 0, S_MAT_NONE, S_MAT_MASK, 0, 0 }, + { "*mat_solidwood", 0, 0, S_MAT_SOLIDWOOD, S_MAT_MASK, 0, 0 }, + { "*mat_hollowwood", 0, 0, S_MAT_HOLLOWWOOD, S_MAT_MASK, 0, 0 }, + { "*mat_solidmetal", 0, 0, S_MAT_SOLIDMETAL, S_MAT_MASK, 0, 0 }, + { "*mat_hollowmetal", 0, 0, S_MAT_HOLLOWMETAL, S_MAT_MASK, 0, 0 }, + { "*mat_shortgrass", 0, 0, S_MAT_SHORTGRASS, S_MAT_MASK, 0, 0 }, + { "*mat_longgrass", 0, 0, S_MAT_LONGGRASS, S_MAT_MASK, 0, 0 }, + { "*mat_dirt", 0, 0, S_MAT_DIRT, S_MAT_MASK, 0, 0 }, + { "*mat_sand", 0, 0, S_MAT_SAND, S_MAT_MASK, 0, 0 }, + { "*mat_gravel", 0, 0, S_MAT_GRAVEL, S_MAT_MASK, 0, 0 }, + { "*mat_glass", 0, 0, S_MAT_GLASS, S_MAT_MASK, 0, 0 }, + { "*mat_concrete", 0, 0, S_MAT_CONCRETE, S_MAT_MASK, 0, 0 }, + { "*mat_marble", 0, 0, S_MAT_MARBLE, S_MAT_MASK, 0, 0 }, + { "*mat_water", 0, 0, S_MAT_WATER, S_MAT_MASK, 0, 0 }, + { "*mat_snow", 0, 0, S_MAT_SNOW, S_MAT_MASK, 0, 0 }, + { "*mat_ice", 0, 0, S_MAT_ICE, S_MAT_MASK, 0, 0 }, + { "*mat_flesh", 0, 0, S_MAT_FLESH, S_MAT_MASK, 0, 0 }, + { "*mat_mud", 0, 0, S_MAT_MUD, S_MAT_MASK, 0, 0 }, + { "*mat_bpglass", 0, 0, S_MAT_BPGLASS, S_MAT_MASK, 0, 0 }, + { "*mat_dryleaves", 0, 0, S_MAT_DRYLEAVES, S_MAT_MASK, 0, 0 }, + { "*mat_greenleaves", 0, 0, S_MAT_GREENLEAVES, S_MAT_MASK, 0, 0 }, + { "*mat_fabric", 0, 0, S_MAT_FABRIC, S_MAT_MASK, 0, 0 }, + { "*mat_canvas", 0, 0, S_MAT_CANVAS, S_MAT_MASK, 0, 0 }, + { "*mat_rock", 0, 0, S_MAT_ROCK, S_MAT_MASK, 0, 0 }, + { "*mat_rubber", 0, 0, S_MAT_RUBBER, S_MAT_MASK, 0, 0 }, + { "*mat_plastic", 0, 0, S_MAT_PLASTIC, S_MAT_MASK, 0, 0 }, + { "*mat_tiles", 0, 0, S_MAT_TILES, S_MAT_MASK, 0, 0 }, + { "*mat_carpet", 0, 0, S_MAT_CARPET, S_MAT_MASK, 0, 0 }, + { "*mat_plaster", 0, 0, S_MAT_PLASTER, S_MAT_MASK, 0, 0 }, + { "*mat_shatterglass", 0, 0, S_MAT_SHATTERGLASS, S_MAT_MASK, 0, 0 }, + { "*mat_armor", 0, 0, S_MAT_ARMOR, S_MAT_MASK, 0, 0 }, + { "*mat_computer", 0, 0, S_MAT_COMPUTER, S_MAT_MASK, 0, 0 }, + + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif diff --git a/tools/quake3/q3map2/game_nexuiz.h b/tools/quake3/q3map2/game_nexuiz.h new file mode 100644 index 00000000..8ceab611 --- /dev/null +++ b/tools/quake3/q3map2/game_nexuiz.h @@ -0,0 +1,143 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME_NEXUIZ_H +#define GAME_NEXUIZ_H + + + +/* ------------------------------------------------------------------------------- + +content and surface flags +are in game_quake3.h + +------------------------------------------------------------------------------- */ + + + +/* ------------------------------------------------------------------------------- + +game_t struct + +------------------------------------------------------------------------------- */ + +{ + "nexuiz", /* -game x */ + "data", /* default base game data dir */ + ".nexuiz", /* unix home sub-dir */ + "nexuiz", /* magic path word */ + "scripts", /* shader directory */ + 999, /* max lightmapped surface verts */ + 999, /* max surface verts */ + 6000, /* max surface indexes */ + qfalse, /* flares */ + "flareshader", /* default flare shader */ + qfalse, /* wolf lighting model? */ + 128, /* lightmap width/height */ + 1.0f, /* lightmap gamma */ + 1.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + "IBSP", /* bsp file prefix */ + 46, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadIBSPFile, /* bsp load function */ + WriteIBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", Q_CONT_SOLID, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", Q_CONT_ORIGIN, Q_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", Q_CONT_AREAPORTAL, Q_CONT_SOLID, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", Q_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", Q_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", Q_CONT_STRUCTURAL, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, Q_SURF_HINT, 0, C_HINT, 0 }, + { "nodraw", 0, 0, Q_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, Q_SURF_ALPHASHADOW, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, Q_SURF_LIGHTFILTER, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, Q_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, Q_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, Q_CONT_SOLID, Q_SURF_NONSOLID, 0, 0, C_SOLID }, + + { "trigger", Q_CONT_TRIGGER, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", Q_CONT_WATER, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slime", Q_CONT_SLIME, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", Q_CONT_LAVA, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "playerclip", Q_CONT_PLAYERCLIP, Q_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", Q_CONT_MONSTERCLIP, Q_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", Q_CONT_NODROP, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "clusterportal", Q_CONT_CLUSTERPORTAL, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "donotenter", Q_CONT_DONOTENTER, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "botclip", Q_CONT_BOTCLIP, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "fog", Q_CONT_FOG, Q_CONT_SOLID, 0, 0, C_FOG, C_SOLID }, + { "sky", 0, 0, Q_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, Q_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, Q_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, Q_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "ladder", 0, 0, Q_SURF_LADDER, 0, 0, 0 }, + { "nodamage", 0, 0, Q_SURF_NODAMAGE, 0, 0, 0 }, + { "metalsteps", 0, 0, Q_SURF_METALSTEPS, 0, 0, 0 }, + { "flesh", 0, 0, Q_SURF_FLESH, 0, 0, 0 }, + { "nosteps", 0, 0, Q_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, Q_SURF_NODLIGHT, 0, 0, 0 }, + { "dust", 0, 0, Q_SURF_DUST, 0, 0, 0 }, + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif + diff --git a/tools/quake3/q3map2/game_qfusion.h b/tools/quake3/q3map2/game_qfusion.h new file mode 100644 index 00000000..25118ef1 --- /dev/null +++ b/tools/quake3/q3map2/game_qfusion.h @@ -0,0 +1,196 @@ +/* ------------------------------------------------------------------------------- + +This code is based on source provided under the terms of the Id Software +LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the +GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of +LICENSE_ID, please contact Id Software immediately at info@idsoftware.com. + +All changes and additions to the original source which have been developed by +other contributors (see CONTRIBUTORS) are provided under the terms of the +license the contributors choose (see LICENSE), to the extent permitted by the +LICENSE_ID. If you did not receive a copy of the contributor license, +please contact the GtkRadiant maintainers at info@gtkradiant.com immediately. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME_QFUSION_H +#define GAME_QFUSION_H + + + +/* ------------------------------------------------------------------------------- + +content and surface flags + +------------------------------------------------------------------------------- */ + +/* game flags */ +#define F_CONT_SOLID 1 /* an eye is never valid in a solid */ +#define F_CONT_LAVA 8 +#define F_CONT_SLIME 16 +#define F_CONT_WATER 32 +#define F_CONT_FOG 64 + +#define F_CONT_AREAPORTAL 0x8000 + +#define F_CONT_PLAYERCLIP 0x10000 +#define F_CONT_MONSTERCLIP 0x20000 +#define F_CONT_TELEPORTER 0x40000 +#define F_CONT_JUMPPAD 0x80000 +#define F_CONT_CLUSTERPORTAL 0x100000 +#define F_CONT_DONOTENTER 0x200000 +#define F_CONT_BOTCLIP 0x400000 + +#define F_CONT_ORIGIN 0x1000000 /* removed before bsping an entity */ + +#define F_CONT_BODY 0x2000000 /* should never be on a brush, only in game */ +#define F_CONT_CORPSE 0x4000000 +#define F_CONT_DETAIL 0x8000000 /* brushes not used for the bsp */ +#define F_CONT_STRUCTURAL 0x10000000 /* brushes used for the bsp */ +#define F_CONT_TRANSLUCENT 0x20000000 /* don't consume surface fragments inside */ +#define F_CONT_TRIGGER 0x40000000 +#define F_CONT_NODROP 0x80000000 /* don't leave bodies or items (death fog, lava) */ + +#define F_SURF_NODAMAGE 0x1 /* never give falling damage */ +#define F_SURF_SLICK 0x2 /* effects game physics */ +#define F_SURF_SKY 0x4 /* lighting from environment map */ +#define F_SURF_LADDER 0x8 +#define F_SURF_NOIMPACT 0x10 /* don't make missile explosions */ +#define F_SURF_NOMARKS 0x20 /* don't leave missile marks */ +#define F_SURF_FLESH 0x40 /* make flesh sounds and effects */ +#define F_SURF_NODRAW 0x80 /* don't generate a drawsurface at all */ +#define F_SURF_HINT 0x100 /* make a primary bsp splitter */ +#define F_SURF_SKIP 0x200 /* completely ignore, allowing non-closed brushes */ +#define F_SURF_NOLIGHTMAP 0x400 /* surface doesn't need a lightmap */ +#define F_SURF_POINTLIGHT 0x800 /* generate lighting info at vertexes */ +#define F_SURF_METALSTEPS 0x1000 /* clanking footsteps */ +#define F_SURF_NOSTEPS 0x2000 /* no footstep sounds */ +#define F_SURF_NONSOLID 0x4000 /* don't collide against curves with this set */ +#define F_SURF_LIGHTFILTER 0x8000 /* act as a light filter during q3map -light */ +#define F_SURF_ALPHASHADOW 0x10000 /* do per-pixel light shadow casting in q3map */ +#define F_SURF_NODLIGHT 0x20000 /* don't dlight even if solid (solid lava, skies) */ +#define F_SURF_DUST 0x40000 /* leave a dust trail when walking on this surface */ + +/* ydnar flags */ +#define F_SURF_VERTEXLIT (F_SURF_POINTLIGHT | F_SURF_NOLIGHTMAP) + + + +/* ------------------------------------------------------------------------------- + +game_t struct + +------------------------------------------------------------------------------- */ + +{ + "qfusion", /* -game x */ + "baseq3", /* default base game data dir */ + ".q3a", /* unix home sub-dir */ + "quake", /* magic path word */ + "scripts", /* shader directory */ + 2048, /* max lightmapped surface verts */ + 2048, /* max surface verts */ + 12288, /* max surface indexes */ + qtrue, /* flares */ + "flareshader", /* default flare shader */ + qfalse, /* wolf lighting model? */ + 512, /* lightmap width/height */ + 1.0f, /* lightmap gamma */ + 1.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + "FBSP", /* bsp file prefix */ + 1, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadRBSPFile, /* bsp load function */ + WriteRBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", F_CONT_SOLID, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", F_CONT_ORIGIN, F_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", F_CONT_AREAPORTAL, F_CONT_SOLID, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", F_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", F_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", F_CONT_STRUCTURAL, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, F_SURF_HINT, 0, C_HINT, 0 }, + { "nodraw", 0, 0, F_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, F_SURF_ALPHASHADOW, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, F_SURF_LIGHTFILTER, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, F_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, F_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, F_CONT_SOLID, F_SURF_NONSOLID, 0, 0, C_SOLID }, + + { "trigger", F_CONT_TRIGGER, F_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", F_CONT_WATER, F_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slime", F_CONT_SLIME, F_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", F_CONT_LAVA, F_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "playerclip", F_CONT_PLAYERCLIP, F_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", F_CONT_MONSTERCLIP, F_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", F_CONT_NODROP, F_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "clusterportal", F_CONT_CLUSTERPORTAL, F_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "donotenter", F_CONT_DONOTENTER, F_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "botclip", F_CONT_BOTCLIP, F_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "fog", F_CONT_FOG, F_CONT_SOLID, 0, 0, C_FOG, C_SOLID }, + { "sky", 0, 0, F_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, F_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, F_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, F_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "ladder", 0, 0, F_SURF_LADDER, 0, 0, 0 }, + { "nodamage", 0, 0, F_SURF_NODAMAGE, 0, 0, 0 }, + { "metalsteps", 0, 0, F_SURF_METALSTEPS, 0, 0, 0 }, + { "flesh", 0, 0, F_SURF_FLESH, 0, 0, 0 }, + { "nosteps", 0, 0, F_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, F_SURF_NODLIGHT, 0, 0, 0 }, + { "dust", 0, 0, F_SURF_DUST, 0, 0, 0 }, + + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif + diff --git a/tools/quake3/q3map2/game_quake3.h b/tools/quake3/q3map2/game_quake3.h new file mode 100644 index 00000000..b8d48fce --- /dev/null +++ b/tools/quake3/q3map2/game_quake3.h @@ -0,0 +1,192 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME_QUAKE3_H +#define GAME_QUAKE3_H + + + +/* ------------------------------------------------------------------------------- + +content and surface flags + +------------------------------------------------------------------------------- */ + +/* game flags */ +#define Q_CONT_SOLID 1 /* an eye is never valid in a solid */ +#define Q_CONT_LAVA 8 +#define Q_CONT_SLIME 16 +#define Q_CONT_WATER 32 +#define Q_CONT_FOG 64 + +#define Q_CONT_AREAPORTAL 0x8000 + +#define Q_CONT_PLAYERCLIP 0x10000 +#define Q_CONT_MONSTERCLIP 0x20000 +#define Q_CONT_TELEPORTER 0x40000 +#define Q_CONT_JUMPPAD 0x80000 +#define Q_CONT_CLUSTERPORTAL 0x100000 +#define Q_CONT_DONOTENTER 0x200000 +#define Q_CONT_BOTCLIP 0x400000 + +#define Q_CONT_ORIGIN 0x1000000 /* removed before bsping an entity */ + +#define Q_CONT_BODY 0x2000000 /* should never be on a brush, only in game */ +#define Q_CONT_CORPSE 0x4000000 +#define Q_CONT_DETAIL 0x8000000 /* brushes not used for the bsp */ +#define Q_CONT_STRUCTURAL 0x10000000 /* brushes used for the bsp */ +#define Q_CONT_TRANSLUCENT 0x20000000 /* don't consume surface fragments inside */ +#define Q_CONT_TRIGGER 0x40000000 +#define Q_CONT_NODROP 0x80000000 /* don't leave bodies or items (death fog, lava) */ + +#define Q_SURF_NODAMAGE 0x1 /* never give falling damage */ +#define Q_SURF_SLICK 0x2 /* effects game physics */ +#define Q_SURF_SKY 0x4 /* lighting from environment map */ +#define Q_SURF_LADDER 0x8 +#define Q_SURF_NOIMPACT 0x10 /* don't make missile explosions */ +#define Q_SURF_NOMARKS 0x20 /* don't leave missile marks */ +#define Q_SURF_FLESH 0x40 /* make flesh sounds and effects */ +#define Q_SURF_NODRAW 0x80 /* don't generate a drawsurface at all */ +#define Q_SURF_HINT 0x100 /* make a primary bsp splitter */ +#define Q_SURF_SKIP 0x200 /* completely ignore, allowing non-closed brushes */ +#define Q_SURF_NOLIGHTMAP 0x400 /* surface doesn't need a lightmap */ +#define Q_SURF_POINTLIGHT 0x800 /* generate lighting info at vertexes */ +#define Q_SURF_METALSTEPS 0x1000 /* clanking footsteps */ +#define Q_SURF_NOSTEPS 0x2000 /* no footstep sounds */ +#define Q_SURF_NONSOLID 0x4000 /* don't collide against curves with this set */ +#define Q_SURF_LIGHTFILTER 0x8000 /* act as a light filter during q3map -light */ +#define Q_SURF_ALPHASHADOW 0x10000 /* do per-pixel light shadow casting in q3map */ +#define Q_SURF_NODLIGHT 0x20000 /* don't dlight even if solid (solid lava, skies) */ +#define Q_SURF_DUST 0x40000 /* leave a dust trail when walking on this surface */ + +/* ydnar flags */ +#define Q_SURF_VERTEXLIT (Q_SURF_POINTLIGHT | Q_SURF_NOLIGHTMAP) + + + +/* ------------------------------------------------------------------------------- + +game_t struct + +------------------------------------------------------------------------------- */ + +{ + "quake3", /* -game x */ + "baseq3", /* default base game data dir */ + ".q3a", /* unix home sub-dir */ + "quake", /* magic path word */ + "scripts", /* shader directory */ + 64, /* max lightmapped surface verts */ + 999, /* max surface verts */ + 6000, /* max surface indexes */ + qfalse, /* flares */ + "flareshader", /* default flare shader */ + qfalse, /* wolf lighting model? */ + 128, /* lightmap width/height */ + 1.0f, /* lightmap gamma */ + 1.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + "IBSP", /* bsp file prefix */ + 46, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadIBSPFile, /* bsp load function */ + WriteIBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", Q_CONT_SOLID, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", Q_CONT_ORIGIN, Q_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", Q_CONT_AREAPORTAL, Q_CONT_SOLID, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", Q_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", Q_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", Q_CONT_STRUCTURAL, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, Q_SURF_HINT, 0, C_HINT, 0 }, + { "nodraw", 0, 0, Q_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, Q_SURF_ALPHASHADOW, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, Q_SURF_LIGHTFILTER, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, Q_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, Q_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, Q_CONT_SOLID, Q_SURF_NONSOLID, 0, 0, C_SOLID }, + + { "trigger", Q_CONT_TRIGGER, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", Q_CONT_WATER, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slime", Q_CONT_SLIME, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", Q_CONT_LAVA, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "playerclip", Q_CONT_PLAYERCLIP, Q_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", Q_CONT_MONSTERCLIP, Q_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", Q_CONT_NODROP, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "clusterportal", Q_CONT_CLUSTERPORTAL, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "donotenter", Q_CONT_DONOTENTER, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "botclip", Q_CONT_BOTCLIP, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "fog", Q_CONT_FOG, Q_CONT_SOLID, 0, 0, C_FOG, C_SOLID }, + { "sky", 0, 0, Q_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, Q_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, Q_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, Q_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "ladder", 0, 0, Q_SURF_LADDER, 0, 0, 0 }, + { "nodamage", 0, 0, Q_SURF_NODAMAGE, 0, 0, 0 }, + { "metalsteps", 0, 0, Q_SURF_METALSTEPS, 0, 0, 0 }, + { "flesh", 0, 0, Q_SURF_FLESH, 0, 0, 0 }, + { "nosteps", 0, 0, Q_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, Q_SURF_NODLIGHT, 0, 0, 0 }, + { "dust", 0, 0, Q_SURF_DUST, 0, 0, 0 }, + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif + diff --git a/tools/quake3/q3map2/game_quakelive.h b/tools/quake3/q3map2/game_quakelive.h new file mode 100644 index 00000000..26e1cb80 --- /dev/null +++ b/tools/quake3/q3map2/game_quakelive.h @@ -0,0 +1,144 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME_QUAKELIVE_H +#define GAME_QUAKELIVE_H + + + +/* ------------------------------------------------------------------------------- + +no content and surface flags here +they are the same as Quake 3's +(this file must be included AFTER game_quake3.h) + +------------------------------------------------------------------------------- */ + + + +/* ------------------------------------------------------------------------------- + +game_t struct + +------------------------------------------------------------------------------- */ + +{ + "quakelive", /* -game x */ + "baseq3", /* default base game data dir (FIXME what does quake live really use?) */ + ".q3a", /* unix home sub-dir (FIXME what does quake live really use?) */ + "quake", /* magic path word (FIXME where does quake live install to?) */ + "scripts", /* shader directory */ + 64, /* max lightmapped surface verts */ + 999, /* max surface verts */ + 6000, /* max surface indexes */ + qfalse, /* flares */ + "flareshader", /* default flare shader */ + qfalse, /* wolf lighting model? */ + 128, /* lightmap width/height */ + 1.0f, /* lightmap gamma */ + 1.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + "IBSP", /* bsp file prefix */ + 47, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadIBSPFile, /* bsp load function */ + WriteIBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", Q_CONT_SOLID, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", Q_CONT_ORIGIN, Q_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", Q_CONT_AREAPORTAL, Q_CONT_SOLID, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", Q_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", Q_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", Q_CONT_STRUCTURAL, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, Q_SURF_HINT, 0, C_HINT, 0 }, + { "nodraw", 0, 0, Q_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, Q_SURF_ALPHASHADOW, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, Q_SURF_LIGHTFILTER, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, Q_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, Q_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, Q_CONT_SOLID, Q_SURF_NONSOLID, 0, 0, C_SOLID }, + + { "trigger", Q_CONT_TRIGGER, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", Q_CONT_WATER, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slime", Q_CONT_SLIME, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", Q_CONT_LAVA, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "playerclip", Q_CONT_PLAYERCLIP, Q_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", Q_CONT_MONSTERCLIP, Q_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", Q_CONT_NODROP, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "clusterportal", Q_CONT_CLUSTERPORTAL, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "donotenter", Q_CONT_DONOTENTER, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "botclip", Q_CONT_BOTCLIP, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "fog", Q_CONT_FOG, Q_CONT_SOLID, 0, 0, C_FOG, C_SOLID }, + { "sky", 0, 0, Q_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, Q_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, Q_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, Q_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "ladder", 0, 0, Q_SURF_LADDER, 0, 0, 0 }, + { "nodamage", 0, 0, Q_SURF_NODAMAGE, 0, 0, 0 }, + { "metalsteps", 0, 0, Q_SURF_METALSTEPS, 0, 0, 0 }, + { "flesh", 0, 0, Q_SURF_FLESH, 0, 0, 0 }, + { "nosteps", 0, 0, Q_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, Q_SURF_NODLIGHT, 0, 0, 0 }, + { "dust", 0, 0, Q_SURF_DUST, 0, 0, 0 }, + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif + diff --git a/tools/quake3/q3map2/game_sof2.h b/tools/quake3/q3map2/game_sof2.h new file mode 100644 index 00000000..6836e286 --- /dev/null +++ b/tools/quake3/q3map2/game_sof2.h @@ -0,0 +1,258 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME_SOF2_H +#define GAME_SOF2_H + + + +/* ------------------------------------------------------------------------------- + +content and surface flags + +------------------------------------------------------------------------------- */ + +/* thanks to the gracious fellows at raven */ +#define S_CONT_SOLID 0x00000001 /* Default setting. An eye is never valid in a solid */ +#define S_CONT_LAVA 0x00000002 +#define S_CONT_WATER 0x00000004 +#define S_CONT_FOG 0x00000008 +#define S_CONT_PLAYERCLIP 0x00000010 +#define S_CONT_MONSTERCLIP 0x00000020 +#define S_CONT_BOTCLIP 0x00000040 +#define S_CONT_SHOTCLIP 0x00000080 +#define S_CONT_BODY 0x00000100 /* should never be on a brush, only in game */ +#define S_CONT_CORPSE 0x00000200 /* should never be on a brush, only in game */ +#define S_CONT_TRIGGER 0x00000400 +#define S_CONT_NODROP 0x00000800 /* don't leave bodies or items (death fog, lava) */ +#define S_CONT_TERRAIN 0x00001000 /* volume contains terrain data */ +#define S_CONT_LADDER 0x00002000 +#define S_CONT_ABSEIL 0x00004000 /* used like ladder to define where an NPC can abseil */ +#define S_CONT_OPAQUE 0x00008000 /* defaults to on, when off, solid can be seen through */ +#define S_CONT_OUTSIDE 0x00010000 /* volume is considered to be in the outside (i.e. not indoors) */ +#define S_CONT_SLIME 0x00020000 /* don't be fooled. it may SAY "slime" but it really means "projectileclip" */ +#define S_CONT_LIGHTSABER 0x00040000 +#define S_CONT_TELEPORTER 0x00080000 +#define S_CONT_ITEM 0x00100000 +#define S_CONT_DETAIL 0x08000000 /* brushes not used for the bsp */ +#define S_CONT_TRANSLUCENT 0x80000000 /* don't consume surface fragments inside */ + +#define S_SURF_SKY 0x00002000 /* lighting from environment map */ +#define S_SURF_SLICK 0x00004000 /* affects game physics */ +#define S_SURF_METALSTEPS 0x00008000 /* chc needs this since we use same tools */ +#define S_SURF_FORCEFIELD 0x00010000 /* chc */ +#define S_SURF_NODAMAGE 0x00040000 /* never give falling damage */ +#define S_SURF_NOIMPACT 0x00080000 /* don't make missile explosions */ +#define S_SURF_NOMARKS 0x00100000 /* don't leave missile marks */ +#define S_SURF_NODRAW 0x00200000 /* don't generate a drawsurface at all */ +#define S_SURF_NOSTEPS 0x00400000 /* no footstep sounds */ +#define S_SURF_NODLIGHT 0x00800000 /* don't dlight even if solid (solid lava, skies) */ +#define S_SURF_NOMISCENTS 0x01000000 /* no client models allowed on this surface */ + +#define S_SURF_PATCH 0x80000000 /* mark this face as a patch(editor only) */ + +/* materials */ +#define S_MAT_BITS 5 +#define S_MAT_MASK 0x1f /* mask to get the material type */ + +#define S_MAT_NONE 0 /* for when the artist hasn't set anything up =) */ +#define S_MAT_SOLIDWOOD 1 /* freshly cut timber */ +#define S_MAT_HOLLOWWOOD 2 /* termite infested creaky wood */ +#define S_MAT_SOLIDMETAL 3 /* solid girders */ +#define S_MAT_HOLLOWMETAL 4 /* hollow metal machines */ +#define S_MAT_SHORTGRASS 5 /* manicured lawn */ +#define S_MAT_LONGGRASS 6 /* long jungle grass */ +#define S_MAT_DIRT 7 /* hard mud */ +#define S_MAT_SAND 8 /* sandy beach */ +#define S_MAT_GRAVEL 9 /* lots of small stones */ +#define S_MAT_GLASS 10 +#define S_MAT_CONCRETE 11 /* hardened concrete pavement */ +#define S_MAT_MARBLE 12 /* marble floors */ +#define S_MAT_WATER 13 /* light covering of water on a surface */ +#define S_MAT_SNOW 14 /* freshly laid snow */ +#define S_MAT_ICE 15 /* packed snow/solid ice */ +#define S_MAT_FLESH 16 /* hung meat, corpses in the world */ +#define S_MAT_MUD 17 /* wet soil */ +#define S_MAT_BPGLASS 18 /* bulletproof glass */ +#define S_MAT_DRYLEAVES 19 /* dried up leaves on the floor */ +#define S_MAT_GREENLEAVES 20 /* fresh leaves still on a tree */ +#define S_MAT_FABRIC 21 /* Cotton sheets */ +#define S_MAT_CANVAS 22 /* tent material */ +#define S_MAT_ROCK 23 +#define S_MAT_RUBBER 24 /* hard tire like rubber */ +#define S_MAT_PLASTIC 25 +#define S_MAT_TILES 26 /* tiled floor */ +#define S_MAT_CARPET 27 /* lush carpet */ +#define S_MAT_PLASTER 28 /* drywall style plaster */ +#define S_MAT_SHATTERGLASS 29 /* glass with the Crisis Zone style shattering */ +#define S_MAT_ARMOR 30 /* body armor */ +#define S_MAT_COMPUTER 31 /* computers/electronic equipment */ +#define S_MAT_LAST 32 /* number of materials */ + + + +/* ------------------------------------------------------------------------------- + +game_t struct + +------------------------------------------------------------------------------- */ + +{ + "sof2", /* -game x */ + "base", /* default base game data dir */ + ".sof2", /* unix home sub-dir */ + "soldier", /* magic path word */ + "shaders", /* shader directory */ + 64, /* max lightmapped surface verts */ + 999, /* max surface verts */ + 6000, /* max surface indexes */ + qtrue, /* flares */ + "gfx/misc/lens_flare", /* default flare shader */ + qfalse, /* wolf lighting model? */ + 128, /* lightmap width/height */ + 1.0f, /* lightmap gamma */ + 1.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + "RBSP", /* bsp file prefix */ + 1, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadRBSPFile, /* bsp load function */ + WriteRBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", S_CONT_SOLID | S_CONT_OPAQUE, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", 0, S_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", S_CONT_TRANSLUCENT, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", S_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", S_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", 0, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, 0, 0, C_HINT, 0 }, + { "nodraw", 0, 0, S_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, 0, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, 0, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, 0, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, 0, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, S_CONT_SOLID, 0, 0, 0, C_SOLID }, + { "nonopaque", 0, S_CONT_OPAQUE, 0, 0, C_TRANSLUCENT, 0 }, /* setting trans ok? */ + + { "trigger", S_CONT_TRIGGER, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", S_CONT_WATER, S_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slime", S_CONT_SLIME, S_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", S_CONT_LAVA, S_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "shotclip", S_CONT_SHOTCLIP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, /* setting trans/detail ok? */ + { "playerclip", S_CONT_PLAYERCLIP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", S_CONT_MONSTERCLIP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", S_CONT_NODROP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + + { "terrain", S_CONT_TERRAIN, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "ladder", S_CONT_LADDER, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "abseil", S_CONT_ABSEIL, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "outside", S_CONT_OUTSIDE, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + + { "botclip", S_CONT_BOTCLIP, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + + { "fog", S_CONT_FOG, S_CONT_SOLID | S_CONT_OPAQUE, 0, 0, C_FOG | C_DETAIL | C_TRANSLUCENT, C_SOLID }, /* nonopaque? */ + { "sky", 0, 0, S_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, S_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, S_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, S_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "nodamage", 0, 0, S_SURF_NODAMAGE, 0, 0, 0 }, + { "metalsteps", 0, 0, S_SURF_METALSTEPS, 0, 0, 0 }, + { "nosteps", 0, 0, S_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, S_SURF_NODLIGHT, 0, 0, 0 }, + { "nomiscents", 0, 0, S_SURF_NOMISCENTS, 0, 0, 0 }, + { "forcefield", 0, 0, S_SURF_FORCEFIELD, 0, 0, 0 }, + + + /* materials */ + { "*mat_none", 0, 0, S_MAT_NONE, S_MAT_MASK, 0, 0 }, + { "*mat_solidwood", 0, 0, S_MAT_SOLIDWOOD, S_MAT_MASK, 0, 0 }, + { "*mat_hollowwood", 0, 0, S_MAT_HOLLOWWOOD, S_MAT_MASK, 0, 0 }, + { "*mat_solidmetal", 0, 0, S_MAT_SOLIDMETAL, S_MAT_MASK, 0, 0 }, + { "*mat_hollowmetal", 0, 0, S_MAT_HOLLOWMETAL, S_MAT_MASK, 0, 0 }, + { "*mat_shortgrass", 0, 0, S_MAT_SHORTGRASS, S_MAT_MASK, 0, 0 }, + { "*mat_longgrass", 0, 0, S_MAT_LONGGRASS, S_MAT_MASK, 0, 0 }, + { "*mat_dirt", 0, 0, S_MAT_DIRT, S_MAT_MASK, 0, 0 }, + { "*mat_sand", 0, 0, S_MAT_SAND, S_MAT_MASK, 0, 0 }, + { "*mat_gravel", 0, 0, S_MAT_GRAVEL, S_MAT_MASK, 0, 0 }, + { "*mat_glass", 0, 0, S_MAT_GLASS, S_MAT_MASK, 0, 0 }, + { "*mat_concrete", 0, 0, S_MAT_CONCRETE, S_MAT_MASK, 0, 0 }, + { "*mat_marble", 0, 0, S_MAT_MARBLE, S_MAT_MASK, 0, 0 }, + { "*mat_water", 0, 0, S_MAT_WATER, S_MAT_MASK, 0, 0 }, + { "*mat_snow", 0, 0, S_MAT_SNOW, S_MAT_MASK, 0, 0 }, + { "*mat_ice", 0, 0, S_MAT_ICE, S_MAT_MASK, 0, 0 }, + { "*mat_flesh", 0, 0, S_MAT_FLESH, S_MAT_MASK, 0, 0 }, + { "*mat_mud", 0, 0, S_MAT_MUD, S_MAT_MASK, 0, 0 }, + { "*mat_bpglass", 0, 0, S_MAT_BPGLASS, S_MAT_MASK, 0, 0 }, + { "*mat_dryleaves", 0, 0, S_MAT_DRYLEAVES, S_MAT_MASK, 0, 0 }, + { "*mat_greenleaves", 0, 0, S_MAT_GREENLEAVES, S_MAT_MASK, 0, 0 }, + { "*mat_fabric", 0, 0, S_MAT_FABRIC, S_MAT_MASK, 0, 0 }, + { "*mat_canvas", 0, 0, S_MAT_CANVAS, S_MAT_MASK, 0, 0 }, + { "*mat_rock", 0, 0, S_MAT_ROCK, S_MAT_MASK, 0, 0 }, + { "*mat_rubber", 0, 0, S_MAT_RUBBER, S_MAT_MASK, 0, 0 }, + { "*mat_plastic", 0, 0, S_MAT_PLASTIC, S_MAT_MASK, 0, 0 }, + { "*mat_tiles", 0, 0, S_MAT_TILES, S_MAT_MASK, 0, 0 }, + { "*mat_carpet", 0, 0, S_MAT_CARPET, S_MAT_MASK, 0, 0 }, + { "*mat_plaster", 0, 0, S_MAT_PLASTER, S_MAT_MASK, 0, 0 }, + { "*mat_shatterglass", 0, 0, S_MAT_SHATTERGLASS, S_MAT_MASK, 0, 0 }, + { "*mat_armor", 0, 0, S_MAT_ARMOR, S_MAT_MASK, 0, 0 }, + { "*mat_computer", 0, 0, S_MAT_COMPUTER, S_MAT_MASK, 0, 0 }, + + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif diff --git a/tools/quake3/q3map2/game_t.h b/tools/quake3/q3map2/game_t.h new file mode 100644 index 00000000..5eec9063 --- /dev/null +++ b/tools/quake3/q3map2/game_t.h @@ -0,0 +1,34 @@ + +/* +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* ydnar: for -game support */ +typedef struct game_s +{ + char *arg; /* -game matches this */ + char *gamePath; /* main game data dir */ + char *homeBasePath; /* home sub-dir on unix */ + char *magic; /* magic word for figuring out base path */ + qboolean wolfLight; /* when true, lights work like wolf q3map */ + int bspVersion; /* BSP version to use */ +} +game_t; + diff --git a/tools/quake3/q3map2/game_tenebrae.h b/tools/quake3/q3map2/game_tenebrae.h new file mode 100644 index 00000000..83991350 --- /dev/null +++ b/tools/quake3/q3map2/game_tenebrae.h @@ -0,0 +1,193 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME_TENEBRAE_H +#define GAME_TENEBRAE_H + + + +/* ------------------------------------------------------------------------------- + +content and surface flags + +------------------------------------------------------------------------------- */ + +/* game flags */ +#define T_CONT_SOLID 1 /* an eye is never valid in a solid */ +#define T_CONT_LAVA 8 +#define T_CONT_SLIME 16 +#define T_CONT_WATER 32 +#define T_CONT_FOG 64 + +#define T_CONT_AREAPORTAL 0x8000 + +#define T_CONT_PLAYERCLIP 0x10000 +#define T_CONT_MONSTERCLIP 0x20000 +#define T_CONT_TELEPORTER 0x40000 +#define T_CONT_JUMPPAD 0x80000 +#define T_CONT_CLUSTERPORTAL 0x100000 +#define T_CONT_DONOTENTER 0x200000 +#define T_CONT_BOTCLIP 0x400000 + +#define T_CONT_ORIGIN 0x1000000 /* removed before bsping an entity */ + +#define T_CONT_BODY 0x2000000 /* should never be on a brush, only in game */ +#define T_CONT_CORPSE 0x4000000 +#define T_CONT_DETAIL 0x8000000 /* brushes not used for the bsp */ +#define T_CONT_STRUCTURAL 0x10000000 /* brushes used for the bsp */ +#define T_CONT_TRANSLUCENT 0x20000000 /* don't consume surface fragments inside */ +#define T_CONT_TRIGGER 0x40000000 +#define T_CONT_NODROP 0x80000000 /* don't leave bodies or items (death fog, lava) */ + +#define T_SURF_NODAMAGE 0x1 /* never give falling damage */ +#define T_SURF_SLICK 0x2 /* effects game physics */ +#define T_SURF_SKY 0x4 /* lighting from environment map */ +#define T_SURF_LADDER 0x8 +#define T_SURF_NOIMPACT 0x10 /* don't make missile explosions */ +#define T_SURF_NOMARKS 0x20 /* don't leave missile marks */ +#define T_SURF_FLESH 0x40 /* make flesh sounds and effects */ +#define T_SURF_NODRAW 0x80 /* don't generate a drawsurface at all */ +#define T_SURF_HINT 0x100 /* make a primary bsp splitter */ +#define T_SURF_SKIP 0x200 /* completely ignore, allowing non-closed brushes */ +#define T_SURF_NOLIGHTMAP 0x400 /* surface doesn't need a lightmap */ +#define T_SURF_POINTLIGHT 0x800 /* generate lighting info at vertexes */ +#define T_SURF_METALSTEPS 0x1000 /* clanking footsteps */ +#define T_SURF_NOSTEPS 0x2000 /* no footstep sounds */ +#define T_SURF_NONSOLID 0x4000 /* don't collide against curves with this set */ +#define T_SURF_LIGHTFILTER 0x8000 /* act as a light filter during q3map -light */ +#define T_SURF_ALPHASHADOW 0x10000 /* do per-pixel light shadow casting in q3map */ +#define T_SURF_NODLIGHT 0x20000 /* don't dlight even if solid (solid lava, skies) */ +#define T_SURF_DUST 0x40000 /* leave a dust trail when walking on this surface */ + +/* ydnar flags */ +#define T_SURF_VERTEXLIT (T_SURF_POINTLIGHT | T_SURF_NOLIGHTMAP) + + + +/* ------------------------------------------------------------------------------- + +game_t struct + +------------------------------------------------------------------------------- */ + +{ + "tenebrae", /* -game x */ + "base", /* default base game data dir */ + ".tenebrae", /* unix home sub-dir */ + "tenebrae", /* magic path word */ + "scripts", /* shader directory */ + 1024, /* max lightmapped surface verts */ + 1024, /* max surface verts */ + 6144, /* max surface indexes */ + qfalse, /* flares */ + "flareshader", /* default flare shader */ + qfalse, /* wolf lighting model? */ + 512, /* lightmap width/height */ + 2.0f, /* lightmap gamma */ + 1.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + "IBSP", /* bsp file prefix */ + 46, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadIBSPFile, /* bsp load function */ + WriteIBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", T_CONT_SOLID, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", T_CONT_ORIGIN, T_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", T_CONT_AREAPORTAL, T_CONT_SOLID, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", T_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", T_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", T_CONT_STRUCTURAL, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, T_SURF_HINT, 0, C_HINT, 0 }, + { "nodraw", 0, 0, T_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, T_SURF_ALPHASHADOW, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, T_SURF_LIGHTFILTER, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, T_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, T_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, T_CONT_SOLID, T_SURF_NONSOLID, 0, 0, C_SOLID }, + + { "trigger", T_CONT_TRIGGER, T_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", T_CONT_WATER, T_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slime", T_CONT_SLIME, T_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", T_CONT_LAVA, T_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "playerclip", T_CONT_PLAYERCLIP, T_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", T_CONT_MONSTERCLIP, T_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", T_CONT_NODROP, T_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "clusterportal", T_CONT_CLUSTERPORTAL, T_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "donotenter", T_CONT_DONOTENTER, T_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "botclip", T_CONT_BOTCLIP, T_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "fog", T_CONT_FOG, T_CONT_SOLID, 0, 0, C_FOG, C_SOLID }, + { "sky", 0, 0, T_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, T_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, T_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, T_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "ladder", 0, 0, T_SURF_LADDER, 0, 0, 0 }, + { "nodamage", 0, 0, T_SURF_NODAMAGE, 0, 0, 0 }, + { "metalsteps", 0, 0, T_SURF_METALSTEPS, 0, 0, 0 }, + { "flesh", 0, 0, T_SURF_FLESH, 0, 0, 0 }, + { "nosteps", 0, 0, T_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, T_SURF_NODLIGHT, 0, 0, 0 }, + { "dust", 0, 0, T_SURF_DUST, 0, 0, 0 }, + + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif + diff --git a/tools/quake3/q3map2/game_tremulous.h b/tools/quake3/q3map2/game_tremulous.h new file mode 100644 index 00000000..958dd81d --- /dev/null +++ b/tools/quake3/q3map2/game_tremulous.h @@ -0,0 +1,161 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + +/* Tremulous support, by LinuxManMikeC */ + + +/* marker */ +#ifndef GAME_TREMULOUS_H +#define GAME_TREMULOUS_H + + + +/* ------------------------------------------------------------------------------- + +content and surface flags - also uses defines from game_quake3.h + +------------------------------------------------------------------------------- */ +#define TREM_CONT_NOALIENBUILD 0x1000 +#define TREM_CONT_NOHUMANBUILD 0x2000 +#define TREM_CONT_NOBUILD 0x4000 + +#define TREM_SURF_NOALIENBUILDSURFACE 0x80000 +#define TREM_SURF_NOHUMANBUILDSURFACE 0x100000 +#define TREM_SURF_NOBUILDSURFACE 0x200000 + + + +/* ------------------------------------------------------------------------------- + +game_t struct + +------------------------------------------------------------------------------- */ + +{ + "tremulous", /* -game x */ + "base", /* default base game data dir */ + ".tremulous", /* unix home sub-dir */ + "tremulous", /* magic path word - think this is right for trem*/ + "scripts", /* shader directory */ + 64, /* max lightmapped surface verts */ + 999, /* max surface verts */ + 6000, /* max surface indexes */ + qfalse, /* flares */ + "flareshader", /* default flare shader */ + qfalse, /* wolf lighting model? */ + 128, /* lightmap width/height */ + 1.0f, /* lightmap gamma */ + 1.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + "IBSP", /* bsp file prefix */ + 46, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadIBSPFile, /* bsp load function */ + WriteIBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", Q_CONT_SOLID, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", Q_CONT_ORIGIN, Q_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", Q_CONT_AREAPORTAL, Q_CONT_SOLID, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", Q_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", Q_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", Q_CONT_STRUCTURAL, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, Q_SURF_HINT, 0, C_HINT, 0 }, + { "nodraw", 0, 0, Q_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, Q_SURF_ALPHASHADOW, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, Q_SURF_LIGHTFILTER, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, Q_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, Q_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, Q_CONT_SOLID, Q_SURF_NONSOLID, 0, 0, C_SOLID }, + + { "trigger", Q_CONT_TRIGGER, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", Q_CONT_WATER, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slime", Q_CONT_SLIME, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", Q_CONT_LAVA, Q_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "playerclip", Q_CONT_PLAYERCLIP, Q_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", Q_CONT_MONSTERCLIP, Q_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", Q_CONT_NODROP, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "clusterportal", Q_CONT_CLUSTERPORTAL, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "donotenter", Q_CONT_DONOTENTER, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "botclip", Q_CONT_BOTCLIP, Q_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "fog", Q_CONT_FOG, Q_CONT_SOLID, 0, 0, C_FOG, C_SOLID }, + { "sky", 0, 0, Q_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, Q_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, Q_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, Q_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "ladder", 0, 0, Q_SURF_LADDER, 0, 0, 0 }, + { "nodamage", 0, 0, Q_SURF_NODAMAGE, 0, 0, 0 }, + { "metalsteps", 0, 0, Q_SURF_METALSTEPS, 0, 0, 0 }, + { "flesh", 0, 0, Q_SURF_FLESH, 0, 0, 0 }, + { "nosteps", 0, 0, Q_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, Q_SURF_NODLIGHT, 0, 0, 0 }, + { "dust", 0, 0, Q_SURF_DUST, 0, 0, 0 }, + + + /* tremulous */ + {"noalienbuild", TREM_CONT_NOALIENBUILD,0,0,0,0,0}, + {"nohumanbuild", TREM_CONT_NOHUMANBUILD,0,0,0,0,0}, + {"nobuild", TREM_CONT_NOBUILD,0,0,0,0,0}, + + {"noalienbuildsurface", 0,0,TREM_SURF_NOALIENBUILDSURFACE,0,0,0}, + {"nohumanbuildsurface", 0,0,TREM_SURF_NOHUMANBUILDSURFACE,0,0,0}, + {"nobuildsurface", 0,0,TREM_SURF_NOBUILDSURFACE,0,0,0}, + + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif + diff --git a/tools/quake3/q3map2/game_wolf.h b/tools/quake3/q3map2/game_wolf.h new file mode 100644 index 00000000..e029255d --- /dev/null +++ b/tools/quake3/q3map2/game_wolf.h @@ -0,0 +1,239 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME_WOLF_H +#define GAME_WOLF_H + + + +/* ------------------------------------------------------------------------------- + +content and surface flags + +------------------------------------------------------------------------------- */ + +/* game flags */ +#define W_CONT_SOLID 1 /* an eye is never valid in a solid */ +#define W_CONT_LAVA 8 +#define W_CONT_SLIME 16 +#define W_CONT_WATER 32 +#define W_CONT_FOG 64 + +#define W_CONT_MISSILECLIP 0x80 /* wolf ranged missile blocking */ +#define W_CONT_ITEM 0x100 /* wolf item contents */ +#define W_CONT_AI_NOSIGHT 0x1000 /* wolf ai sight blocking */ +#define W_CONT_CLIPSHOT 0x2000 /* wolf shot clip */ +#define W_CONT_AREAPORTAL 0x8000 + +#define W_CONT_PLAYERCLIP 0x10000 +#define W_CONT_MONSTERCLIP 0x20000 +#define W_CONT_TELEPORTER 0x40000 +#define W_CONT_JUMPPAD 0x80000 +#define W_CONT_CLUSTERPORTAL 0x100000 +#define W_CONT_DONOTENTER 0x200000 +#define W_CONT_DONOTENTER_LARGE 0x400000 /* wolf dne */ + +#define W_CONT_ORIGIN 0x1000000 /* removed before bsping an entity */ + +#define W_CONT_BODY 0x2000000 /* should never be on a brush, only in game */ +#define W_CONT_CORPSE 0x4000000 +#define W_CONT_DETAIL 0x8000000 /* brushes not used for the bsp */ +#define W_CONT_STRUCTURAL 0x10000000 /* brushes used for the bsp */ +#define W_CONT_TRANSLUCENT 0x20000000 /* don't consume surface fragments inside */ +#define W_CONT_TRIGGER 0x40000000 +#define W_CONT_NODROP 0x80000000 /* don't leave bodies or items (death fog, lava) */ + +#define W_SURF_NODAMAGE 0x1 /* never give falling damage */ +#define W_SURF_SLICK 0x2 /* effects game physics */ +#define W_SURF_SKY 0x4 /* lighting from environment map */ +#define W_SURF_LADDER 0x8 +#define W_SURF_NOIMPACT 0x10 /* don't make missile explosions */ +#define W_SURF_NOMARKS 0x20 /* don't leave missile marks */ +#define W_SURF_CERAMIC 0x40 /* wolf ceramic material */ +#define W_SURF_NODRAW 0x80 /* don't generate a drawsurface at all */ +#define W_SURF_HINT 0x100 /* make a primary bsp splitter */ +#define W_SURF_SKIP 0x200 /* completely ignore, allowing non-closed brushes */ +#define W_SURF_NOLIGHTMAP 0x400 /* surface doesn't need a lightmap */ +#define W_SURF_POINTLIGHT 0x800 /* generate lighting info at vertexes */ +#define W_SURF_METAL 0x1000 /* wolf metal material */ +#define W_SURF_NOSTEPS 0x2000 /* no footstep sounds */ +#define W_SURF_NONSOLID 0x4000 /* don't collide against curves with this set */ +#define W_SURF_LIGHTFILTER 0x8000 /* act as a light filter during q3map -light */ +#define W_SURF_ALPHASHADOW 0x10000 /* do per-pixel light shadow casting in q3map */ +#define W_SURF_NODLIGHT 0x20000 /* don't dlight even if solid (solid lava, skies) */ +#define W_SURF_WOOD 0x40000 /* wolf wood material */ +#define W_SURF_GRASS 0x80000 /* wolf grass material */ +#define W_SURF_GRAVEL 0x100000 /* wolf gravel material */ +#define W_SURF_GLASS 0x200000 /* wolf glass material */ +#define W_SURF_SNOW 0x400000 /* wolf snow material */ +#define W_SURF_ROOF 0x800000 /* wolf roof material */ +#define W_SURF_RUBBLE 0x1000000 /* wolf rubble material */ +#define W_SURF_CARPET 0x2000000 /* wolf carpet material */ + +#define W_SURF_MONSTERSLICK 0x4000000 /* wolf npc slick surface */ +#define W_SURF_MONSLICK_W 0x8000000 /* wolf slide bodies west */ +#define W_SURF_MONSLICK_N 0x10000000 /* wolf slide bodies north */ +#define W_SURF_MONSLICK_E 0x20000000 /* wolf slide bodies east */ +#define W_SURF_MONSLICK_S 0x40000000 /* wolf slide bodies south */ + +/* ydnar flags */ +#define W_SURF_VERTEXLIT (W_SURF_POINTLIGHT | W_SURF_NOLIGHTMAP) + + + +/* ------------------------------------------------------------------------------- + +game_t struct + +------------------------------------------------------------------------------- */ + +{ + "wolf", /* -game x */ + "main", /* default base game data dir */ + ".wolf", /* unix home sub-dir */ + "wolf", /* magic path word */ + "scripts", /* shader directory */ + 64, /* max lightmapped surface verts */ + 999, /* max surface verts */ + 6000, /* max surface indexes */ + qfalse, /* flares */ + "flareshader", /* default flare shader */ + qtrue, /* wolf lighting model? */ + 128, /* lightmap width/height */ + 1.0f, /* lightmap gamma */ + 1.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + "IBSP", /* bsp file prefix */ + 46, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadIBSPFile, /* bsp load function */ + WriteIBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", W_CONT_SOLID, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", W_CONT_ORIGIN, W_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", W_CONT_AREAPORTAL, W_CONT_SOLID, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", W_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", W_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", W_CONT_STRUCTURAL, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, W_SURF_HINT, 0, C_HINT, 0 }, + { "nodraw", 0, 0, W_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, W_SURF_ALPHASHADOW, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, W_SURF_LIGHTFILTER, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, W_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, W_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, W_CONT_SOLID, W_SURF_NONSOLID, 0, 0, C_SOLID }, + + { "trigger", W_CONT_TRIGGER, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", W_CONT_WATER, W_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slag", W_CONT_SLIME, W_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", W_CONT_LAVA, W_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "playerclip", W_CONT_PLAYERCLIP, W_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", W_CONT_MONSTERCLIP, W_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "clipmissile", W_CONT_MISSILECLIP, W_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "clipshot", W_CONT_CLIPSHOT, W_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", W_CONT_NODROP, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "clusterportal", W_CONT_CLUSTERPORTAL, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "donotenter", W_CONT_DONOTENTER, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "nonotenterlarge",W_CONT_DONOTENTER_LARGE, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "fog", W_CONT_FOG, W_CONT_SOLID, 0, 0, C_FOG, C_SOLID }, + { "sky", 0, 0, W_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, W_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, W_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, W_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "ladder", 0, 0, W_SURF_LADDER, 0, 0, 0 }, + { "nodamage", 0, 0, W_SURF_NODAMAGE, 0, 0, 0 }, + { "nosteps", 0, 0, W_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, W_SURF_NODLIGHT, 0, 0, 0 }, + + + /* materials */ + { "metal", 0, 0, W_SURF_METAL, 0, 0, 0 }, + { "metalsteps", 0, 0, W_SURF_METAL, 0, 0, 0 }, + { "glass", 0, 0, W_SURF_GLASS, 0, 0, 0 }, + { "ceramic", 0, 0, W_SURF_CERAMIC, 0, 0, 0 }, + { "woodsteps", 0, 0, W_SURF_WOOD, 0, 0, 0 }, + { "grasssteps", 0, 0, W_SURF_GRASS, 0, 0, 0 }, + { "gravelsteps", 0, 0, W_SURF_GRAVEL, 0, 0, 0 }, + { "rubble", 0, 0, W_SURF_RUBBLE, 0, 0, 0 }, + { "carpetsteps", 0, 0, W_SURF_CARPET, 0, 0, 0 }, + { "snowsteps", 0, 0, W_SURF_SNOW, 0, 0, 0 }, + { "roofsteps", 0, 0, W_SURF_ROOF, 0, 0, 0 }, + + + /* ai */ + { "ai_nosight", W_CONT_AI_NOSIGHT, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + /* ydnar: experimental until bits are confirmed! */ + { "ai_nopass", W_CONT_DONOTENTER, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "ai_nopasslarge", W_CONT_DONOTENTER_LARGE, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + + /* sliding bodies */ + { "monsterslick", 0, 0, W_SURF_MONSTERSLICK, 0, C_TRANSLUCENT, 0 }, + { "monsterslicknorth", 0, 0, W_SURF_MONSLICK_N, 0, C_TRANSLUCENT, 0 }, + { "monsterslickeast", 0, 0, W_SURF_MONSLICK_E, 0, C_TRANSLUCENT, 0 }, + { "monsterslicksouth", 0, 0, W_SURF_MONSLICK_S, 0, C_TRANSLUCENT, 0 }, + { "monsterslickwest", 0, 0, W_SURF_MONSLICK_W, 0, C_TRANSLUCENT, 0 }, + + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif + diff --git a/tools/quake3/q3map2/game_wolfet.h b/tools/quake3/q3map2/game_wolfet.h new file mode 100644 index 00000000..077be85c --- /dev/null +++ b/tools/quake3/q3map2/game_wolfet.h @@ -0,0 +1,178 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +Support for Wolfenstein: Enemy Territory by ydnar@splashdamage.com + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef GAME_WOLFET_H +#define GAME_WOLFET_H + + + +/* ------------------------------------------------------------------------------- + +content and surface flags + +------------------------------------------------------------------------------- */ + +/* this file must be included *after* game_wolf.h because it shares defines! */ + +#define W_SURF_SPLASH 0x00000040 /* enemy territory water splash surface */ +#define W_SURF_LANDMINE 0x80000000 /* enemy territory 'landminable' surface */ + + + +/* ------------------------------------------------------------------------------- + +game_t struct + +------------------------------------------------------------------------------- */ + +{ + "et", /* -game x */ + "etmain", /* default base game data dir */ + ".etwolf", /* unix home sub-dir */ + "et", /* magic path word */ + "scripts", /* shader directory */ + 1024, /* max lightmapped surface verts */ + 1024, /* max surface verts */ + 6144, /* max surface indexes */ + qfalse, /* flares */ + "flareshader", /* default flare shader */ + qtrue, /* wolf lighting model? */ + 128, /* lightmap width/height */ + 1.0f, /* lightmap gamma */ + 1.0f, /* lightmap exposure */ + 1.0f, /* lightmap compensate */ + "IBSP", /* bsp file prefix */ + 46, /* bsp file version */ + qfalse, /* cod-style lump len/ofs order */ + LoadIBSPFile, /* bsp load function */ + WriteIBSPFile, /* bsp write function */ + + { + /* name contentFlags contentFlagsClear surfaceFlags surfaceFlagsClear compileFlags compileFlagsClear */ + + /* default */ + { "default", W_CONT_SOLID, -1, 0, -1, C_SOLID, -1 }, + + + /* ydnar */ + { "lightgrid", 0, 0, 0, 0, C_LIGHTGRID, 0 }, + { "antiportal", 0, 0, 0, 0, C_ANTIPORTAL, 0 }, + { "skip", 0, 0, 0, 0, C_SKIP, 0 }, + + + /* compiler */ + { "origin", W_CONT_ORIGIN, W_CONT_SOLID, 0, 0, C_ORIGIN | C_TRANSLUCENT, C_SOLID }, + { "areaportal", W_CONT_AREAPORTAL, W_CONT_SOLID, 0, 0, C_AREAPORTAL | C_TRANSLUCENT, C_SOLID }, + { "trans", W_CONT_TRANSLUCENT, 0, 0, 0, C_TRANSLUCENT, 0 }, + { "detail", W_CONT_DETAIL, 0, 0, 0, C_DETAIL, 0 }, + { "structural", W_CONT_STRUCTURAL, 0, 0, 0, C_STRUCTURAL, 0 }, + { "hint", 0, 0, W_SURF_HINT, 0, C_HINT, 0 }, + { "nodraw", 0, 0, W_SURF_NODRAW, 0, C_NODRAW, 0 }, + + { "alphashadow", 0, 0, W_SURF_ALPHASHADOW, 0, C_ALPHASHADOW | C_TRANSLUCENT, 0 }, + { "lightfilter", 0, 0, W_SURF_LIGHTFILTER, 0, C_LIGHTFILTER | C_TRANSLUCENT, 0 }, + { "nolightmap", 0, 0, W_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + { "pointlight", 0, 0, W_SURF_VERTEXLIT, 0, C_VERTEXLIT, 0 }, + + + /* game */ + { "nonsolid", 0, W_CONT_SOLID, W_SURF_NONSOLID, 0, 0, C_SOLID }, + + { "trigger", W_CONT_TRIGGER, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "water", W_CONT_WATER, W_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "slag", W_CONT_SLIME, W_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + { "lava", W_CONT_LAVA, W_CONT_SOLID, 0, 0, C_LIQUID | C_TRANSLUCENT, C_SOLID }, + + { "playerclip", W_CONT_PLAYERCLIP, W_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "monsterclip", W_CONT_MONSTERCLIP, W_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "clipmissile", W_CONT_MISSILECLIP, W_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "clipshot", W_CONT_CLIPSHOT, W_CONT_SOLID, 0, 0, C_DETAIL | C_TRANSLUCENT, C_SOLID }, + { "nodrop", W_CONT_NODROP, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "clusterportal", W_CONT_CLUSTERPORTAL, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "donotenter", W_CONT_DONOTENTER, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "nonotenterlarge",W_CONT_DONOTENTER_LARGE, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + { "fog", W_CONT_FOG, W_CONT_SOLID, 0, 0, C_FOG, C_SOLID }, + { "sky", 0, 0, W_SURF_SKY, 0, C_SKY, 0 }, + + { "slick", 0, 0, W_SURF_SLICK, 0, 0, 0 }, + + { "noimpact", 0, 0, W_SURF_NOIMPACT, 0, 0, 0 }, + { "nomarks", 0, 0, W_SURF_NOMARKS, 0, C_NOMARKS, 0 }, + { "ladder", 0, 0, W_SURF_LADDER, 0, 0, 0 }, + { "nodamage", 0, 0, W_SURF_NODAMAGE, 0, 0, 0 }, + { "nosteps", 0, 0, W_SURF_NOSTEPS, 0, 0, 0 }, + { "nodlight", 0, 0, W_SURF_NODLIGHT, 0, 0, 0 }, + + /* wolf et landmine-able surface */ + { "landmine", 0, 0, W_SURF_LANDMINE, 0, 0, 0 }, + + /* materials */ + { "metal", 0, 0, W_SURF_METAL, 0, 0, 0 }, + { "metalsteps", 0, 0, W_SURF_METAL, 0, 0, 0 }, + { "glass", 0, 0, W_SURF_GLASS, 0, 0, 0 }, + { "splash", 0, 0, W_SURF_SPLASH, 0, 0, 0 }, + { "woodsteps", 0, 0, W_SURF_WOOD, 0, 0, 0 }, + { "grasssteps", 0, 0, W_SURF_GRASS, 0, 0, 0 }, + { "gravelsteps", 0, 0, W_SURF_GRAVEL, 0, 0, 0 }, + { "rubble", 0, 0, W_SURF_RUBBLE, 0, 0, 0 }, + { "carpetsteps", 0, 0, W_SURF_CARPET, 0, 0, 0 }, + { "snowsteps", 0, 0, W_SURF_SNOW, 0, 0, 0 }, + { "roofsteps", 0, 0, W_SURF_ROOF, 0, 0, 0 }, + + + /* ai */ + { "ai_nosight", W_CONT_AI_NOSIGHT, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + /* ydnar: experimental until bits are confirmed! */ + { "ai_nopass", W_CONT_DONOTENTER, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + { "ai_nopasslarge", W_CONT_DONOTENTER_LARGE, W_CONT_SOLID, 0, 0, C_TRANSLUCENT, C_SOLID }, + + + /* sliding bodies */ + { "monsterslick", 0, 0, W_SURF_MONSTERSLICK, 0, C_TRANSLUCENT, 0 }, + { "monsterslicknorth", 0, 0, W_SURF_MONSLICK_N, 0, C_TRANSLUCENT, 0 }, + { "monsterslickeast", 0, 0, W_SURF_MONSLICK_E, 0, C_TRANSLUCENT, 0 }, + { "monsterslicksouth", 0, 0, W_SURF_MONSLICK_S, 0, C_TRANSLUCENT, 0 }, + { "monsterslickwest", 0, 0, W_SURF_MONSLICK_W, 0, C_TRANSLUCENT, 0 }, + + + /* null */ + { NULL, 0, 0, 0, 0, 0, 0 } + } +} + + + +/* end marker */ +#endif + diff --git a/tools/quake3/q3map2/image.c b/tools/quake3/q3map2/image.c new file mode 100644 index 00000000..0ddcaf86 --- /dev/null +++ b/tools/quake3/q3map2/image.c @@ -0,0 +1,468 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define IMAGE_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* ------------------------------------------------------------------------------- + +this file contains image pool management with reference counting. note: it isn't +reentrant, so only call it from init/shutdown code or wrap calls in a mutex + +------------------------------------------------------------------------------- */ + +/* +LoadDDSBuffer() +loads a dxtc (1, 3, 5) dds buffer into a valid rgba image +*/ + +static void LoadDDSBuffer( byte *buffer, int size, byte **pixels, int *width, int *height ) +{ + int w, h; + ddsPF_t pf; + + + /* dummy check */ + if( buffer == NULL || size <= 0 || pixels == NULL || width == NULL || height == NULL ) + return; + + /* null out */ + *pixels = 0; + *width = 0; + *height = 0; + + /* get dds info */ + if( DDSGetInfo( (ddsBuffer_t*) buffer, &w, &h, &pf ) ) + { + Sys_Printf( "WARNING: Invalid DDS texture\n" ); + return; + } + + /* only certain types of dds textures are supported */ + if( pf != DDS_PF_ARGB8888 && pf != DDS_PF_DXT1 && pf != DDS_PF_DXT3 && pf != DDS_PF_DXT5 ) + { + Sys_Printf( "WARNING: Only DDS texture formats ARGB8888, DXT1, DXT3, and DXT5 are supported (%d)\n", pf ); + return; + } + + /* create image pixel buffer */ + *width = w; + *height = h; + *pixels = safe_malloc( w * h * 4 ); + + /* decompress the dds texture */ + DDSDecompress( (ddsBuffer_t*) buffer, *pixels ); +} + + + +/* +PNGReadData() +callback function for libpng to read from a memory buffer +note: this function is a total hack, as it reads/writes the png struct directly! +*/ + +typedef struct pngBuffer_s +{ + byte *buffer; + int size, offset; +} +pngBuffer_t; + +void PNGReadData( png_struct *png, png_byte *buffer, png_size_t size ) +{ + pngBuffer_t *pb = (pngBuffer_t*) png_get_io_ptr( png ); + + + if( (pb->offset + size) > pb->size ) + size = (pb->size - pb->offset); + memcpy( buffer, &pb->buffer[ pb->offset ], size ); + pb->offset += size; + //% Sys_Printf( "Copying %d bytes from 0x%08X to 0x%08X (offset: %d of %d)\n", size, &pb->buffer[ pb->offset ], buffer, pb->offset, pb->size ); +} + + + +/* +LoadPNGBuffer() +loads a png file buffer into a valid rgba image +*/ + +static void LoadPNGBuffer( byte *buffer, int size, byte **pixels, int *width, int *height ) +{ + png_struct *png; + png_info *info, *end; + pngBuffer_t pb; + int i, bitDepth, colorType, channels; + png_uint_32 w, h; + byte **rowPointers; + + + /* dummy check */ + if( buffer == NULL || size <= 0 || pixels == NULL || width == NULL || height == NULL ) + return; + + /* null out */ + *pixels = 0; + *width = 0; + *height = 0; + + /* determine if this is a png file */ + if( png_sig_cmp( buffer, 0, 8 ) != 0 ) + { + Sys_Printf( "WARNING: Invalid PNG file\n" ); + return; + } + + /* create png structs */ + png = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL ); + if( png == NULL ) + { + Sys_Printf( "WARNING: Unable to create PNG read struct\n" ); + return; + } + + info = png_create_info_struct( png ); + if( info == NULL ) + { + Sys_Printf( "WARNING: Unable to create PNG info struct\n" ); + png_destroy_read_struct( &png, NULL, NULL ); + return; + } + + end = png_create_info_struct( png ); + if( end == NULL ) + { + Sys_Printf( "WARNING: Unable to create PNG end info struct\n" ); + png_destroy_read_struct( &png, &info, NULL ); + return; + } + + /* set read callback */ + pb.buffer = buffer; + pb.size = size; + pb.offset = 0; + png_set_read_fn( png, &pb, PNGReadData ); + png->io_ptr = &pb; /* hack! */ + + /* set error longjmp */ + if( setjmp( png->jmpbuf ) ) + { + Sys_Printf( "WARNING: An error occurred reading PNG image\n" ); + png_destroy_read_struct( &png, &info, &end ); + return; + } + + /* fixme: add proper i/o stuff here */ + + /* read png info */ + png_read_info( png, info ); + + /* read image header chunk */ + png_get_IHDR( png, info, + &w, &h, &bitDepth, &colorType, NULL, NULL, NULL ); + + /* read number of channels */ + channels = png_get_channels( png, info ); + + /* the following will probably bork on certain types of png images, but hey... */ + + /* force indexed/gray/trans chunk to rgb */ + if( (colorType == PNG_COLOR_TYPE_PALETTE && bitDepth <= 8) || + (colorType == PNG_COLOR_TYPE_GRAY && bitDepth <= 8) || + png_get_valid( png, info, PNG_INFO_tRNS ) ) + png_set_expand( png ); + + /* strip 16bpc -> 8bpc */ + if( bitDepth == 16 ) + png_set_strip_16( png ); + + /* pad rgb to rgba */ + if( bitDepth == 8 && colorType == PNG_COLOR_TYPE_RGB) + png_set_filler( png, 255, PNG_FILLER_AFTER ); + + /* create image pixel buffer */ + *width = w; + *height = h; + *pixels = safe_malloc( w * h * 4 ); + + /* create row pointers */ + rowPointers = safe_malloc( h * sizeof( byte* ) ); + for( i = 0; i < h; i++ ) + rowPointers[ i ] = *pixels + (i * w * 4); + + /* read the png */ + png_read_image( png, rowPointers ); + + /* clean up */ + free( rowPointers ); + png_destroy_read_struct( &png, &info, &end ); + +} + + + +/* +ImageInit() +implicitly called by every function to set up image list +*/ + +static void ImageInit( void ) +{ + int i; + + + if( numImages <= 0 ) + { + /* clear images (fixme: this could theoretically leak) */ + memset( images, 0, sizeof( images ) ); + + /* generate *bogus image */ + images[ 0 ].name = safe_malloc( strlen( DEFAULT_IMAGE ) + 1 ); + strcpy( images[ 0 ].name, DEFAULT_IMAGE ); + images[ 0 ].filename = safe_malloc( strlen( DEFAULT_IMAGE ) + 1 ); + strcpy( images[ 0 ].filename, DEFAULT_IMAGE ); + images[ 0 ].width = 64; + images[ 0 ].height = 64; + images[ 0 ].refCount = 1; + images[ 0 ].pixels = safe_malloc( 64 * 64 * 4 ); + for( i = 0; i < (64 * 64 * 4); i++ ) + images[ 0 ].pixels[ i ] = 255; + } +} + + + +/* +ImageFree() +frees an rgba image +*/ + +void ImageFree( image_t *image ) +{ + /* dummy check */ + if( image == NULL ) + return; + + /* decrement refcount */ + image->refCount--; + + /* free? */ + if( image->refCount <= 0 ) + { + if( image->name != NULL ) + free( image->name ); + image->name = NULL; + if( image->filename != NULL ) + free( image->filename ); + image->filename = NULL; + free( image->pixels ); + image->width = 0; + image->height = 0; + numImages--; + } +} + + + +/* +ImageFind() +finds an existing rgba image and returns a pointer to the image_t struct or NULL if not found +*/ + +image_t *ImageFind( const char *filename ) +{ + int i; + char name[ 1024 ]; + + + /* init */ + ImageInit(); + + /* dummy check */ + if( filename == NULL || filename[ 0 ] == '\0' ) + return NULL; + + /* strip file extension off name */ + strcpy( name, filename ); + StripExtension( name ); + + /* search list */ + for( i = 0; i < MAX_IMAGES; i++ ) + { + if( images[ i ].name != NULL && !strcmp( name, images[ i ].name ) ) + return &images[ i ]; + } + + /* no matching image found */ + return NULL; +} + + + +/* +ImageLoad() +loads an rgba image and returns a pointer to the image_t struct or NULL if not found +*/ + +image_t *ImageLoad( const char *filename ) +{ + int i; + image_t *image; + char name[ 1024 ]; + int size; + byte *buffer = NULL; + + + /* init */ + ImageInit(); + + /* dummy check */ + if( filename == NULL || filename[ 0 ] == '\0' ) + return NULL; + + /* strip file extension off name */ + strcpy( name, filename ); + StripExtension( name ); + + /* try to find existing image */ + image = ImageFind( name ); + if( image != NULL ) + { + image->refCount++; + return image; + } + + /* none found, so find first non-null image */ + image = NULL; + for( i = 0; i < MAX_IMAGES; i++ ) + { + if( images[ i ].name == NULL ) + { + image = &images[ i ]; + break; + } + } + + /* too many images? */ + if( image == NULL ) + Error( "MAX_IMAGES (%d) exceeded, there are too many image files referenced by the map.", MAX_IMAGES ); + + /* set it up */ + image->name = safe_malloc( strlen( name ) + 1 ); + strcpy( image->name, name ); + + /* attempt to load tga */ + StripExtension( name ); + strcat( name, ".tga" ); + size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); + if( size > 0 ) + LoadTGABuffer( buffer, buffer + size, &image->pixels, &image->width, &image->height ); + else + { + /* attempt to load png */ + StripExtension( name ); + strcat( name, ".png" ); + size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); + if( size > 0 ) + LoadPNGBuffer( buffer, size, &image->pixels, &image->width, &image->height ); + else + { + /* attempt to load jpg */ + StripExtension( name ); + strcat( name, ".jpg" ); + size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); + if( size > 0 ) + { + if( LoadJPGBuff( buffer, size, &image->pixels, &image->width, &image->height ) == -1 && image->pixels != NULL ) + Sys_Printf( "WARNING: LoadJPGBuff: %s\n", (unsigned char*) image->pixels ); + } + else + { + /* attempt to load dds */ + StripExtension( name ); + strcat( name, ".dds" ); + size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 ); + if( size > 0 ) + { + LoadDDSBuffer( buffer, size, &image->pixels, &image->width, &image->height ); + + /* debug code */ + #if 1 + { + ddsPF_t pf; + DDSGetInfo( (ddsBuffer_t*) buffer, NULL, NULL, &pf ); + Sys_Printf( "pf = %d\n", pf ); + if( image->width > 0 ) + { + StripExtension( name ); + strcat( name, "_converted.tga" ); + WriteTGA( "C:\\games\\quake3\\baseq3\\textures\\rad\\dds_converted.tga", image->pixels, image->width, image->height ); + } + } + #endif + } + } + } + } + + /* free file buffer */ + free( buffer ); + + /* make sure everything's kosher */ + if( size <= 0 || image->width <= 0 || image->height <= 0 || image->pixels == NULL ) + { + //% Sys_Printf( "size = %d width = %d height = %d pixels = 0x%08x (%s)\n", + //% size, image->width, image->height, image->pixels, name ); + free( image->name ); + image->name = NULL; + return NULL; + } + + /* set filename */ + image->filename = safe_malloc( strlen( name ) + 1 ); + strcpy( image->filename, name ); + + /* set count */ + image->refCount = 1; + numImages++; + + /* return the image */ + return image; +} + + diff --git a/tools/quake3/q3map2/leakfile.c b/tools/quake3/q3map2/leakfile.c new file mode 100644 index 00000000..51fda300 --- /dev/null +++ b/tools/quake3/q3map2/leakfile.c @@ -0,0 +1,127 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define LEAKFILE_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* +============================================================================== + +LEAK FILE GENERATION + +Save out name.line for qe3 to read +============================================================================== +*/ + + +/* +============= +LeakFile + +Finds the shortest possible chain of portals +that leads from the outside leaf to a specifically +occupied leaf + +TTimo: builds a polyline xml node +============= +*/ +xmlNodePtr LeakFile (tree_t *tree) +{ + vec3_t mid; + FILE *linefile; + char filename[1024]; + node_t *node; + int count; + xmlNodePtr xml_node, point; + + if (!tree->outside_node.occupied) + return NULL; + + Sys_FPrintf (SYS_VRB,"--- LeakFile ---\n"); + + // + // write the points to the file + // + sprintf (filename, "%s.lin", source); + linefile = fopen (filename, "w"); + if (!linefile) + Error ("Couldn't open %s\n", filename); + + xml_node = xmlNewNode (NULL, "polyline"); + + count = 0; + node = &tree->outside_node; + while (node->occupied > 1) + { + int next; + portal_t *p, *nextportal; + node_t *nextnode; + int s; + + // find the best portal exit + next = node->occupied; + for (p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (p->nodes[s]->occupied + && p->nodes[s]->occupied < next) + { + nextportal = p; + nextnode = p->nodes[s]; + next = nextnode->occupied; + } + } + node = nextnode; + WindingCenter (nextportal->winding, mid); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + point = xml_NodeForVec(mid); + xmlAddChild(xml_node, point); + count++; + } + // add the occupant center + GetVectorForKey (node->occupant, "origin", mid); + + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + point = xml_NodeForVec(mid); + xmlAddChild(xml_node, point); + Sys_FPrintf( SYS_VRB, "%9d point linefile\n", count+1); + + fclose (linefile); + + return xml_node; +} + diff --git a/tools/quake3/q3map2/light.c b/tools/quake3/q3map2/light.c new file mode 100644 index 00000000..f2f28e93 --- /dev/null +++ b/tools/quake3/q3map2/light.c @@ -0,0 +1,2386 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define LIGHT_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* +CreateSunLight() - ydnar +this creates a sun light +*/ + +static void CreateSunLight( sun_t *sun ) +{ + int i; + float photons, d, angle, elevation, da, de; + vec3_t direction; + light_t *light; + + + /* dummy check */ + if( sun == NULL ) + return; + + /* fixup */ + if( sun->numSamples < 1 ) + sun->numSamples = 1; + + /* set photons */ + photons = sun->photons / sun->numSamples; + + /* create the right number of suns */ + for( i = 0; i < sun->numSamples; i++ ) + { + /* calculate sun direction */ + if( i == 0 ) + VectorCopy( sun->direction, direction ); + else + { + /* + sun->direction[ 0 ] = cos( angle ) * cos( elevation ); + sun->direction[ 1 ] = sin( angle ) * cos( elevation ); + sun->direction[ 2 ] = sin( elevation ); + + xz_dist = sqrt( x*x + z*z ) + latitude = atan2( xz_dist, y ) * RADIANS + longitude = atan2( x, z ) * RADIANS + */ + + d = sqrt( sun->direction[ 0 ] * sun->direction[ 0 ] + sun->direction[ 1 ] * sun->direction[ 1 ] ); + angle = atan2( sun->direction[ 1 ], sun->direction[ 0 ] ); + elevation = atan2( sun->direction[ 2 ], d ); + + /* jitter the angles (loop to keep random sample within sun->deviance steridians) */ + do + { + da = (Random() * 2.0f - 1.0f) * sun->deviance; + de = (Random() * 2.0f - 1.0f) * sun->deviance; + } + while( (da * da + de * de) > (sun->deviance * sun->deviance) ); + angle += da; + elevation += de; + + /* debug code */ + //% Sys_Printf( "%d: Angle: %3.4f Elevation: %3.3f\n", sun->numSamples, (angle / Q_PI * 180.0f), (elevation / Q_PI * 180.0f) ); + + /* create new vector */ + direction[ 0 ] = cos( angle ) * cos( elevation ); + direction[ 1 ] = sin( angle ) * cos( elevation ); + direction[ 2 ] = sin( elevation ); + } + + /* create a light */ + numSunLights++; + light = safe_malloc( sizeof( *light ) ); + memset( light, 0, sizeof( *light ) ); + light->next = lights; + lights = light; + + /* initialize the light */ + light->flags = LIGHT_SUN_DEFAULT; + light->type = EMIT_SUN; + light->fade = 1.0f; + light->falloffTolerance = falloffTolerance; + light->filterRadius = sun->filterRadius / sun->numSamples; + light->style = noStyles ? LS_NORMAL : sun->style; + + /* set the light's position out to infinity */ + VectorMA( vec3_origin, (MAX_WORLD_COORD * 8.0f), direction, light->origin ); /* MAX_WORLD_COORD * 2.0f */ + + /* set the facing to be the inverse of the sun direction */ + VectorScale( direction, -1.0, light->normal ); + light->dist = DotProduct( light->origin, light->normal ); + + /* set color and photons */ + VectorCopy( sun->color, light->color ); + light->photons = photons * skyScale; + } + + /* another sun? */ + if( sun->next != NULL ) + CreateSunLight( sun->next ); +} + + + +/* +CreateSkyLights() - ydnar +simulates sky light with multiple suns +*/ + +static void CreateSkyLights( vec3_t color, float value, int iterations, float filterRadius, int style ) +{ + int i, j, numSuns; + int angleSteps, elevationSteps; + float angle, elevation; + float angleStep, elevationStep; + float step, start; + sun_t sun; + + + /* dummy check */ + if( value <= 0.0f || iterations < 2 ) + return; + + /* calculate some stuff */ + step = 2.0f / (iterations - 1); + start = -1.0f; + + /* basic sun setup */ + VectorCopy( color, sun.color ); + sun.deviance = 0.0f; + sun.filterRadius = filterRadius; + sun.numSamples = 1; + sun.style = noStyles ? LS_NORMAL : style; + sun.next = NULL; + + /* setup */ + elevationSteps = iterations - 1; + angleSteps = elevationSteps * 4; + angle = 0.0f; + elevationStep = DEG2RAD( 90.0f / iterations ); /* skip elevation 0 */ + angleStep = DEG2RAD( 360.0f / angleSteps ); + + /* calc individual sun brightness */ + numSuns = angleSteps * elevationSteps + 1; + sun.photons = value / numSuns; + + /* iterate elevation */ + elevation = elevationStep * 0.5f; + angle = 0.0f; + for( i = 0, elevation = elevationStep * 0.5f; i < elevationSteps; i++ ) + { + /* iterate angle */ + for( j = 0; j < angleSteps; j++ ) + { + /* create sun */ + sun.direction[ 0 ] = cos( angle ) * cos( elevation ); + sun.direction[ 1 ] = sin( angle ) * cos( elevation ); + sun.direction[ 2 ] = sin( elevation ); + CreateSunLight( &sun ); + + /* move */ + angle += angleStep; + } + + /* move */ + elevation += elevationStep; + angle += angleStep / elevationSteps; + } + + /* create vertical sun */ + VectorSet( sun.direction, 0.0f, 0.0f, 1.0f ); + CreateSunLight( &sun ); + + /* short circuit */ + return; +} + + + +/* +CreateEntityLights() +creates lights from light entities +*/ + +void CreateEntityLights( void ) +{ + int i, j; + light_t *light, *light2; + entity_t *e, *e2; + const char *name; + const char *target; + vec3_t dest; + const char *_color; + float intensity, scale, deviance, filterRadius; + int spawnflags, flags, numSamples; + qboolean junior; + + + /* go throught entity list and find lights */ + for( i = 0; i < numEntities; i++ ) + { + /* get entity */ + e = &entities[ i ]; + name = ValueForKey( e, "classname" ); + + /* ydnar: check for lightJunior */ + if( Q_strncasecmp( name, "lightJunior", 11 ) == 0 ) + junior = qtrue; + else if( Q_strncasecmp( name, "light", 5 ) == 0 ) + junior = qfalse; + else + continue; + + /* lights with target names (and therefore styles) are only parsed from BSP */ + target = ValueForKey( e, "targetname" ); + if( target[ 0 ] != '\0' && i >= numBSPEntities ) + continue; + + /* create a light */ + numPointLights++; + light = safe_malloc( sizeof( *light ) ); + memset( light, 0, sizeof( *light ) ); + light->next = lights; + lights = light; + + /* handle spawnflags */ + spawnflags = IntForKey( e, "spawnflags" ); + + /* ydnar: quake 3+ light behavior */ + if( wolfLight == qfalse ) + { + /* set default flags */ + flags = LIGHT_Q3A_DEFAULT; + + /* linear attenuation? */ + if( spawnflags & 1 ) + { + flags |= LIGHT_ATTEN_LINEAR; + flags &= ~LIGHT_ATTEN_ANGLE; + } + + /* no angle attenuate? */ + if( spawnflags & 2 ) + flags &= ~LIGHT_ATTEN_ANGLE; + } + + /* ydnar: wolf light behavior */ + else + { + /* set default flags */ + flags = LIGHT_WOLF_DEFAULT; + + /* inverse distance squared attenuation? */ + if( spawnflags & 1 ) + { + flags &= ~LIGHT_ATTEN_LINEAR; + flags |= LIGHT_ATTEN_ANGLE; + } + + /* angle attenuate? */ + if( spawnflags & 2 ) + flags |= LIGHT_ATTEN_ANGLE; + } + + /* other flags (borrowed from wolf) */ + + /* wolf dark light? */ + if( (spawnflags & 4) || (spawnflags & 8) ) + flags |= LIGHT_DARK; + + /* nogrid? */ + if( spawnflags & 16 ) + flags &= ~LIGHT_GRID; + + /* junior? */ + if( junior ) + { + flags |= LIGHT_GRID; + flags &= ~LIGHT_SURFACES; + } + + /* store the flags */ + light->flags = flags; + + /* ydnar: set fade key (from wolf) */ + light->fade = 1.0f; + if( light->flags & LIGHT_ATTEN_LINEAR ) + { + light->fade = FloatForKey( e, "fade" ); + if( light->fade == 0.0f ) + light->fade = 1.0f; + } + + /* ydnar: set angle scaling (from vlight) */ + light->angleScale = FloatForKey( e, "_anglescale" ); + if( light->angleScale != 0.0f ) + light->flags |= LIGHT_ATTEN_ANGLE; + + /* set origin */ + GetVectorForKey( e, "origin", light->origin); + light->style = IntForKey( e, "_style" ); + if( light->style == LS_NORMAL ) + light->style = IntForKey( e, "style" ); + if( light->style < LS_NORMAL || light->style >= LS_NONE ) + Error( "Invalid lightstyle (%d) on entity %d", light->style, i ); + + if( light->style != LS_NORMAL ) { + Sys_FPrintf (SYS_WRN, "WARNING: Styled light found targeting %s\n **", target ); + } + + /* set light intensity */ + intensity = FloatForKey( e, "_light" ); + if( intensity == 0.0f ) + intensity = FloatForKey( e, "light" ); + if( intensity == 0.0f) + intensity = 300.0f; + + /* ydnar: set light scale (sof2) */ + scale = FloatForKey( e, "scale" ); + if( scale == 0.0f ) + scale = 1.0f; + intensity *= scale; + + /* ydnar: get deviance and samples */ + deviance = FloatForKey( e, "_deviance" ); + if( deviance == 0.0f ) + deviance = FloatForKey( e, "_deviation" ); + if( deviance == 0.0f ) + deviance = FloatForKey( e, "_jitter" ); + numSamples = IntForKey( e, "_samples" ); + if( deviance < 0.0f || numSamples < 1 ) + { + deviance = 0.0f; + numSamples = 1; + } + intensity /= numSamples; + + /* ydnar: get filter radius */ + filterRadius = FloatForKey( e, "_filterradius" ); + if( filterRadius == 0.0f ) + filterRadius = FloatForKey( e, "_filteradius" ); + if( filterRadius == 0.0f ) + filterRadius = FloatForKey( e, "_filter" ); + if( filterRadius < 0.0f ) + filterRadius = 0.0f; + light->filterRadius = filterRadius; + + /* set light color */ + _color = ValueForKey( e, "_color" ); + if( _color && _color[ 0 ] ) + { + sscanf( _color, "%f %f %f", &light->color[ 0 ], &light->color[ 1 ], &light->color[ 2 ] ); + ColorNormalize( light->color, light->color ); + } + else + light->color[ 0 ] = light->color[ 1 ] = light->color[ 2 ] = 1.0f; + + intensity = intensity * pointScale; + light->photons = intensity; + + light->type = EMIT_POINT; + + /* set falloff threshold */ + light->falloffTolerance = falloffTolerance / numSamples; + + /* lights with a target will be spotlights */ + target = ValueForKey( e, "target" ); + if( target[ 0 ] ) + { + float radius; + float dist; + sun_t sun; + const char *_sun; + + + /* get target */ + e2 = FindTargetEntity( target ); + if( e2 == NULL ) + { + Sys_Printf( "WARNING: light at (%i %i %i) has missing target\n", + (int) light->origin[ 0 ], (int) light->origin[ 1 ], (int) light->origin[ 2 ] ); + } + else + { + /* not a point light */ + numPointLights--; + numSpotLights++; + + /* make a spotlight */ + GetVectorForKey( e2, "origin", dest ); + VectorSubtract( dest, light->origin, light->normal ); + dist = VectorNormalize( light->normal, light->normal ); + radius = FloatForKey( e, "radius" ); + if( !radius ) + radius = 64; + if( !dist ) + dist = 64; + light->radiusByDist = (radius + 16) / dist; + light->type = EMIT_SPOT; + + /* ydnar: wolf mods: spotlights always use nonlinear + angle attenuation */ + light->flags &= ~LIGHT_ATTEN_LINEAR; + light->flags |= LIGHT_ATTEN_ANGLE; + light->fade = 1.0f; + + /* ydnar: is this a sun? */ + _sun = ValueForKey( e, "_sun" ); + if( _sun[ 0 ] == '1' ) + { + /* not a spot light */ + numSpotLights--; + + /* unlink this light */ + lights = light->next; + + /* make a sun */ + VectorScale( light->normal, -1.0f, sun.direction ); + VectorCopy( light->color, sun.color ); + sun.photons = (intensity / pointScale); + sun.deviance = deviance / 180.0f * Q_PI; + sun.numSamples = numSamples; + sun.style = noStyles ? LS_NORMAL : light->style; + sun.next = NULL; + + /* make a sun light */ + CreateSunLight( &sun ); + + /* free original light */ + free( light ); + light = NULL; + + /* skip the rest of this love story */ + continue; + } + } + } + + /* jitter the light */ + for( j = 1; j < numSamples; j++ ) + { + /* create a light */ + light2 = safe_malloc( sizeof( *light ) ); + memcpy( light2, light, sizeof( *light ) ); + light2->next = lights; + lights = light2; + + /* add to counts */ + if( light->type == EMIT_SPOT ) + numSpotLights++; + else + numPointLights++; + + /* jitter it */ + light2->origin[ 0 ] = light->origin[ 0 ] + (Random() * 2.0f - 1.0f) * deviance; + light2->origin[ 1 ] = light->origin[ 1 ] + (Random() * 2.0f - 1.0f) * deviance; + light2->origin[ 2 ] = light->origin[ 2 ] + (Random() * 2.0f - 1.0f) * deviance; + } + } +} + + + +/* +CreateSurfaceLights() - ydnar +this hijacks the radiosity code to generate surface lights for first pass +*/ + +#define APPROX_BOUNCE 1.0f + +void CreateSurfaceLights( void ) +{ + int i; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + shaderInfo_t *si; + light_t *light; + float subdivide; + vec3_t origin; + clipWork_t cw; + const char *nss; + + + /* get sun shader supressor */ + nss = ValueForKey( &entities[ 0 ], "_noshadersun" ); + + /* walk the list of surfaces */ + for( i = 0; i < numBSPDrawSurfaces; i++ ) + { + /* get surface and other bits */ + ds = &bspDrawSurfaces[ i ]; + info = &surfaceInfos[ i ]; + si = info->si; + + /* sunlight? */ + if( si->sun != NULL && nss[ 0 ] != '1' ) + { + Sys_FPrintf( SYS_VRB, "Sun: %s\n", si->shader ); + CreateSunLight( si->sun ); + si->sun = NULL; /* FIXME: leak! */ + } + + /* sky light? */ + if( si->skyLightValue > 0.0f ) + { + Sys_FPrintf( SYS_VRB, "Sky: %s\n", si->shader ); + CreateSkyLights( si->color, si->skyLightValue, si->skyLightIterations, si->lightFilterRadius, si->lightStyle ); + si->skyLightValue = 0.0f; /* FIXME: hack! */ + } + + /* try to early out */ + if( si->value <= 0 ) + continue; + + /* autosprite shaders become point lights */ + if( si->autosprite ) + { + /* create an average xyz */ + VectorAdd( info->mins, info->maxs, origin ); + VectorScale( origin, 0.5f, origin ); + + /* create a light */ + light = safe_malloc( sizeof( *light ) ); + memset( light, 0, sizeof( *light ) ); + light->next = lights; + lights = light; + + /* set it up */ + light->flags = LIGHT_Q3A_DEFAULT; + light->type = EMIT_POINT; + light->photons = si->value * pointScale; + light->fade = 1.0f; + light->si = si; + VectorCopy( origin, light->origin ); + VectorCopy( si->color, light->color ); + light->falloffTolerance = falloffTolerance; + light->style = si->lightStyle; + + /* add to point light count and continue */ + numPointLights++; + continue; + } + + /* get subdivision amount */ + if( si->lightSubdivide > 0 ) + subdivide = si->lightSubdivide; + else + subdivide = defaultLightSubdivide; + + /* switch on type */ + switch( ds->surfaceType ) + { + case MST_PLANAR: + case MST_TRIANGLE_SOUP: + RadLightForTriangles( i, 0, info->lm, si, APPROX_BOUNCE, subdivide, &cw ); + break; + + case MST_PATCH: + RadLightForPatch( i, 0, info->lm, si, APPROX_BOUNCE, subdivide, &cw ); + break; + + default: + break; + } + } +} + + + +/* +SetEntityOrigins() +find the offset values for inline models +*/ + +void SetEntityOrigins( void ) +{ + int i, j, k, f; + entity_t *e; + vec3_t origin; + const char *key; + int modelnum; + bspModel_t *dm; + bspDrawSurface_t *ds; + + + /* ydnar: copy drawverts into private storage for nefarious purposes */ + yDrawVerts = safe_malloc( numBSPDrawVerts * sizeof( bspDrawVert_t ) ); + memcpy( yDrawVerts, bspDrawVerts, numBSPDrawVerts * sizeof( bspDrawVert_t ) ); + + /* set the entity origins */ + for( i = 0; i < numEntities; i++ ) + { + /* get entity and model */ + e = &entities[ i ]; + key = ValueForKey( e, "model" ); + if( key[ 0 ] != '*' ) + continue; + modelnum = atoi( key + 1 ); + dm = &bspModels[ modelnum ]; + + /* get entity origin */ + key = ValueForKey( e, "origin" ); + if( key[ 0 ] == '\0' ) + continue; + GetVectorForKey( e, "origin", origin ); + + /* set origin for all surfaces for this model */ + for( j = 0; j < dm->numBSPSurfaces; j++ ) + { + /* get drawsurf */ + ds = &bspDrawSurfaces[ dm->firstBSPSurface + j ]; + + /* set its verts */ + for( k = 0; k < ds->numVerts; k++ ) + { + f = ds->firstVert + k; + VectorAdd( origin, bspDrawVerts[ f ].xyz, yDrawVerts[ f ].xyz ); + } + } + } +} + + + +/* +PointToPolygonFormFactor() +calculates the area over a point/normal hemisphere a winding covers +ydnar: fixme: there has to be a faster way to calculate this +without the expensive per-vert sqrts and transcendental functions +ydnar 2002-09-30: added -faster switch because only 19% deviance > 10% +between this and the approximation +*/ + +#define ONE_OVER_2PI 0.159154942f //% (1.0f / (2.0f * 3.141592657f)) + +float PointToPolygonFormFactor( const vec3_t point, const vec3_t normal, const winding_t *w ) +{ + vec3_t triVector, triNormal; + int i, j; + vec3_t dirs[ MAX_POINTS_ON_WINDING ]; + float total; + float dot, angle, facing; + + + /* this is expensive */ + for( i = 0; i < w->numpoints; i++ ) + { + VectorSubtract( w->p[ i ], point, dirs[ i ] ); + VectorNormalize( dirs[ i ], dirs[ i ] ); + } + + /* duplicate first vertex to avoid mod operation */ + VectorCopy( dirs[ 0 ], dirs[ i ] ); + + /* calculcate relative area */ + total = 0.0f; + for( i = 0; i < w->numpoints; i++ ) + { + /* get a triangle */ + j = i + 1; + dot = DotProduct( dirs[ i ], dirs[ j ] ); + + /* roundoff can cause slight creep, which gives an IND from acos */ + if( dot > 1.0f ) + dot = 1.0f; + else if( dot < -1.0f ) + dot = -1.0f; + + /* get the angle */ + angle = acos( dot ); + + CrossProduct( dirs[ i ], dirs[ j ], triVector ); + if( VectorNormalize( triVector, triNormal ) < 0.0001f ) + continue; + + facing = DotProduct( normal, triNormal ); + total += facing * angle; + + /* ydnar: this was throwing too many errors with radiosity + crappy maps. ignoring it. */ + if( total > 6.3f || total < -6.3f ) + return 0.0f; + } + + /* now in the range of 0 to 1 over the entire incoming hemisphere */ + //% total /= (2.0f * 3.141592657f); + total *= ONE_OVER_2PI; + return total; +} + + + +/* +LightContributionTosample() +determines the amount of light reaching a sample (luxel or vertex) from a given light +*/ + +int LightContributionToSample( trace_t *trace ) +{ + light_t *light; + float angle; + float add; + float dist; + + + /* get light */ + light = trace->light; + + /* clear color */ + VectorClear( trace->color ); + + /* ydnar: early out */ + if( !(light->flags & LIGHT_SURFACES) || light->envelope <= 0.0f ) + return 0; + + /* do some culling checks */ + if( light->type != EMIT_SUN ) + { + /* MrE: if the light is behind the surface */ + if( trace->twoSided == qfalse ) + if( DotProduct( light->origin, trace->normal ) - DotProduct( trace->origin, trace->normal ) < 0.0f ) + return 0; + + /* ydnar: test pvs */ + if( !ClusterVisible( trace->cluster, light->cluster ) ) + return 0; + } + + /* exact point to polygon form factor */ + if( light->type == EMIT_AREA ) + { + float factor; + float d; + vec3_t pushedOrigin; + + + /* project sample point into light plane */ + d = DotProduct( trace->origin, light->normal ) - light->dist; + if( d < 3.0f ) + { + /* sample point behind plane? */ + if( !(light->flags & LIGHT_TWOSIDED) && d < -1.0f ) + return 0; + + /* sample plane coincident? */ + if( d > -3.0f && DotProduct( trace->normal, light->normal ) > 0.9f ) + return 0; + } + + /* nudge the point so that it is clearly forward of the light */ + /* so that surfaces meeting a light emiter don't get black edges */ + if( d > -8.0f && d < 8.0f ) + VectorMA( trace->origin, (8.0f - d), light->normal, pushedOrigin ); + else + VectorCopy( trace->origin, pushedOrigin ); + + /* get direction and distance */ + VectorCopy( light->origin, trace->end ); + dist = SetupTrace( trace ); + if( dist >= light->envelope ) + return 0; + + /* ptpff approximation */ + if( faster ) + { + /* angle attenuation */ + angle = DotProduct( trace->normal, trace->direction ); + + /* twosided lighting */ + if( trace->twoSided ) + angle = fabs( angle ); + + /* attenuate */ + angle *= -DotProduct( light->normal, trace->direction ); + if( angle == 0.0f ) + return 0; + else if( angle < 0.0f && + (trace->twoSided || (light->flags & LIGHT_TWOSIDED)) ) + angle = -angle; + add = light->photons / (dist * dist) * angle; + } + else + { + /* calculate the contribution */ + factor = PointToPolygonFormFactor( pushedOrigin, trace->normal, light->w ); + if( factor == 0.0f ) + return 0; + else if( factor < 0.0f ) + { + /* twosided lighting */ + if( trace->twoSided || (light->flags & LIGHT_TWOSIDED) ) + { + factor = -factor; + + /* push light origin to other side of the plane */ + VectorMA( light->origin, -2.0f, light->normal, trace->end ); + dist = SetupTrace( trace ); + if( dist >= light->envelope ) + return 0; + } + else + return 0; + } + + /* ydnar: moved to here */ + add = factor * light->add; + } + } + + /* point/spot lights */ + else if( light->type == EMIT_POINT || light->type == EMIT_SPOT ) + { + /* get direction and distance */ + VectorCopy( light->origin, trace->end ); + dist = SetupTrace( trace ); + if( dist >= light->envelope ) + return 0; + + /* clamp the distance to prevent super hot spots */ + if( dist < 16.0f ) + dist = 16.0f; + + /* angle attenuation */ + angle = (light->flags & LIGHT_ATTEN_ANGLE) ? DotProduct( trace->normal, trace->direction ) : 1.0f; + if( light->angleScale != 0.0f ) + { + angle /= light->angleScale; + if( angle > 1.0f ) + angle = 1.0f; + } + + /* twosided lighting */ + if( trace->twoSided ) + angle = fabs( angle ); + + /* attenuate */ + if( light->flags & LIGHT_ATTEN_LINEAR ) + { + add = angle * light->photons * linearScale - (dist * light->fade); + if( add < 0.0f ) + add = 0.0f; + } + else + add = light->photons / (dist * dist) * angle; + + /* handle spotlights */ + if( light->type == EMIT_SPOT ) + { + float distByNormal, radiusAtDist, sampleRadius; + vec3_t pointAtDist, distToSample; + + + /* do cone calculation */ + distByNormal = -DotProduct( trace->displacement, light->normal ); + if( distByNormal < 0.0f ) + return 0; + VectorMA( light->origin, distByNormal, light->normal, pointAtDist ); + radiusAtDist = light->radiusByDist * distByNormal; + VectorSubtract( trace->origin, pointAtDist, distToSample ); + sampleRadius = VectorLength( distToSample ); + + /* outside the cone */ + if( sampleRadius >= radiusAtDist ) + return 0; + + /* attenuate */ + if( sampleRadius > (radiusAtDist - 32.0f) ) + add *= ((radiusAtDist - sampleRadius) / 32.0f); + } + } + + /* ydnar: sunlight */ + else if( light->type == EMIT_SUN ) + { + /* get origin and direction */ + VectorAdd( trace->origin, light->origin, trace->end ); + dist = SetupTrace( trace ); + + /* angle attenuation */ + angle = (light->flags & LIGHT_ATTEN_ANGLE) + ? DotProduct( trace->normal, trace->direction ) + : 1.0f; + + /* twosided lighting */ + if( trace->twoSided ) + angle = fabs( angle ); + + /* attenuate */ + add = light->photons * angle; + if( add <= 0.0f ) + return 0; + + /* setup trace */ + trace->testAll = qtrue; + VectorScale( light->color, add, trace->color ); + + /* trace to point */ + if( trace->testOcclusion && !trace->forceSunlight ) + { + /* trace */ + TraceLine( trace ); + if( !(trace->compileFlags & C_SKY) || trace->opaque ) + { + VectorClear( trace->color ); + return -1; + } + } + + /* return to sender */ + return 1; + } + + /* ydnar: changed to a variable number */ + if( add <= 0.0f || (add <= light->falloffTolerance && (light->flags & LIGHT_FAST_ACTUAL)) ) + return 0; + + /* setup trace */ + trace->testAll = qfalse; + VectorScale( light->color, add, trace->color ); + + /* raytrace */ + TraceLine( trace ); + if( trace->passSolid || trace->opaque ) + { + VectorClear( trace->color ); + return -1; + } + + /* return to sender */ + return 1; +} + + + +/* +LightingAtSample() +determines the amount of light reaching a sample (luxel or vertex) +*/ + +void LightingAtSample( trace_t *trace, byte styles[ MAX_LIGHTMAPS ], vec3_t colors[ MAX_LIGHTMAPS ] ) +{ + int i, lightmapNum; + + + /* clear colors */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + VectorClear( colors[ lightmapNum ] ); + + /* ydnar: normalmap */ + if( normalmap ) + { + colors[ 0 ][ 0 ] = (trace->normal[ 0 ] + 1.0f) * 127.5f; + colors[ 0 ][ 1 ] = (trace->normal[ 1 ] + 1.0f) * 127.5f; + colors[ 0 ][ 2 ] = (trace->normal[ 2 ] + 1.0f) * 127.5f; + return; + } + + /* ydnar: don't bounce ambient all the time */ + if( !bouncing ) + VectorCopy( ambientColor, colors[ 0 ] ); + + /* ydnar: trace to all the list of lights pre-stored in tw */ + for( i = 0; i < trace->numLights && trace->lights[ i ] != NULL; i++ ) + { + /* set light */ + trace->light = trace->lights[ i ]; + + /* style check */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + if( styles[ lightmapNum ] == trace->light->style || + styles[ lightmapNum ] == LS_NONE ) + break; + } + + /* max of MAX_LIGHTMAPS (4) styles allowed to hit a sample */ + if( lightmapNum >= MAX_LIGHTMAPS ) + continue; + + /* sample light */ + LightContributionToSample( trace ); + if( trace->color[ 0 ] == 0.0f && trace->color[ 1 ] == 0.0f && trace->color[ 2 ] == 0.0f ) + continue; + + /* handle negative light */ + if( trace->light->flags & LIGHT_NEGATIVE ) + VectorScale( trace->color, -1.0f, trace->color ); + + /* set style */ + styles[ lightmapNum ] = trace->light->style; + + /* add it */ + VectorAdd( colors[ lightmapNum ], trace->color, colors[ lightmapNum ] ); + + /* cheap mode */ + if( cheap && + colors[ 0 ][ 0 ] >= 255.0f && + colors[ 0 ][ 1 ] >= 255.0f && + colors[ 0 ][ 2 ] >= 255.0f ) + break; + } +} + + + +/* +LightContributionToPoint() +for a given light, how much light/color reaches a given point in space (with no facing) +note: this is similar to LightContributionToSample() but optimized for omnidirectional sampling +*/ + +int LightContributionToPoint( trace_t *trace ) +{ + light_t *light; + float add, dist; + + + /* get light */ + light = trace->light; + + /* clear color */ + VectorClear( trace->color ); + + /* ydnar: early out */ + if( !(light->flags & LIGHT_GRID) || light->envelope <= 0.0f ) + return qfalse; + + /* is this a sun? */ + if( light->type != EMIT_SUN ) + { + /* sun only? */ + if( sunOnly ) + return qfalse; + + /* test pvs */ + if( !ClusterVisible( trace->cluster, light->cluster ) ) + return qfalse; + } + + /* ydnar: check origin against light's pvs envelope */ + if( trace->origin[ 0 ] > light->maxs[ 0 ] || trace->origin[ 0 ] < light->mins[ 0 ] || + trace->origin[ 1 ] > light->maxs[ 1 ] || trace->origin[ 1 ] < light->mins[ 1 ] || + trace->origin[ 2 ] > light->maxs[ 2 ] || trace->origin[ 2 ] < light->mins[ 2 ] ) + { + gridBoundsCulled++; + return qfalse; + } + + /* set light origin */ + if( light->type == EMIT_SUN ) + VectorAdd( trace->origin, light->origin, trace->end ); + else + VectorCopy( light->origin, trace->end ); + + /* set direction */ + dist = SetupTrace( trace ); + + /* test envelope */ + if( dist > light->envelope ) + { + gridEnvelopeCulled++; + return qfalse; + } + + /* ptpff approximation */ + if( light->type == EMIT_AREA && faster ) + { + /* clamp the distance to prevent super hot spots */ + if( dist < 16.0f ) + dist = 16.0f; + + /* attenuate */ + add = light->photons / (dist * dist); + } + + /* exact point to polygon form factor */ + else if( light->type == EMIT_AREA ) + { + float factor, d; + vec3_t pushedOrigin; + + + /* see if the point is behind the light */ + d = DotProduct( trace->origin, light->normal ) - light->dist; + if( !(light->flags & LIGHT_TWOSIDED) && d < -1.0f ) + return qfalse; + + /* nudge the point so that it is clearly forward of the light */ + /* so that surfaces meeting a light emiter don't get black edges */ + if( d > -8.0f && d < 8.0f ) + VectorMA( trace->origin, (8.0f - d), light->normal, pushedOrigin ); + else + VectorCopy( trace->origin, pushedOrigin ); + + /* calculate the contribution (ydnar 2002-10-21: [bug 642] bad normal calc) */ + factor = PointToPolygonFormFactor( pushedOrigin, trace->direction, light->w ); + if( factor == 0.0f ) + return qfalse; + else if( factor < 0.0f ) + { + if( light->flags & LIGHT_TWOSIDED ) + factor = -factor; + else + return qfalse; + } + + /* ydnar: moved to here */ + add = factor * light->add; + } + + /* point/spot lights */ + else if( light->type == EMIT_POINT || light->type == EMIT_SPOT ) + { + /* clamp the distance to prevent super hot spots */ + if( dist < 16.0f ) + dist = 16.0f; + + /* attenuate */ + if( light->flags & LIGHT_ATTEN_LINEAR ) + { + add = light->photons * linearScale - (dist * light->fade); + if( add < 0.0f ) + add = 0.0f; + } + else + add = light->photons / (dist * dist); + + /* handle spotlights */ + if( light->type == EMIT_SPOT ) + { + float distByNormal, radiusAtDist, sampleRadius; + vec3_t pointAtDist, distToSample; + + + /* do cone calculation */ + distByNormal = -DotProduct( trace->displacement, light->normal ); + if( distByNormal < 0.0f ) + return qfalse; + VectorMA( light->origin, distByNormal, light->normal, pointAtDist ); + radiusAtDist = light->radiusByDist * distByNormal; + VectorSubtract( trace->origin, pointAtDist, distToSample ); + sampleRadius = VectorLength( distToSample ); + + /* outside the cone */ + if( sampleRadius >= radiusAtDist ) + return qfalse; + + /* attenuate */ + if( sampleRadius > (radiusAtDist - 32.0f) ) + add *= ((radiusAtDist - sampleRadius) / 32.0f); + } + } + + /* ydnar: sunlight */ + else if( light->type == EMIT_SUN ) + { + /* attenuate */ + add = light->photons; + if( add <= 0.0f ) + return qfalse; + + /* setup trace */ + trace->testAll = qtrue; + VectorScale( light->color, add, trace->color ); + + /* trace to point */ + if( trace->testOcclusion && !trace->forceSunlight ) + { + /* trace */ + TraceLine( trace ); + if( !(trace->compileFlags & C_SKY) || trace->opaque ) + { + VectorClear( trace->color ); + return -1; + } + } + + /* return to sender */ + return qtrue; + } + + /* unknown light type */ + else + return qfalse; + + /* ydnar: changed to a variable number */ + if( add <= 0.0f || (add <= light->falloffTolerance && (light->flags & LIGHT_FAST_ACTUAL)) ) + return qfalse; + + /* setup trace */ + trace->testAll = qfalse; + VectorScale( light->color, add, trace->color ); + + /* trace */ + TraceLine( trace ); + if( trace->passSolid ) + { + VectorClear( trace->color ); + return qfalse; + } + + /* we have a valid sample */ + return qtrue; +} + + + +/* +TraceGrid() +grid samples are for quickly determining the lighting +of dynamically placed entities in the world +*/ + +#define MAX_CONTRIBUTIONS 1024 + +typedef struct +{ + vec3_t dir; + vec3_t color; + int style; +} +contribution_t; + +void TraceGrid( int num ) +{ + int i, j, x, y, z, mod, step, numCon, numStyles; + float d; + vec3_t baseOrigin, cheapColor, color; + rawGridPoint_t *gp; + bspGridPoint_t *bgp; + contribution_t contributions[ MAX_CONTRIBUTIONS ]; + trace_t trace; + + + /* get grid points */ + gp = &rawGridPoints[ num ]; + bgp = &bspGridPoints[ num ]; + + /* get grid origin */ + mod = num; + z = mod / (gridBounds[ 0 ] * gridBounds[ 1 ]); + mod -= z * (gridBounds[ 0 ] * gridBounds[ 1 ]); + y = mod / gridBounds[ 0 ]; + mod -= y * gridBounds[ 0 ]; + x = mod; + + trace.origin[ 0 ] = gridMins[ 0 ] + x * gridSize[ 0 ]; + trace.origin[ 1 ] = gridMins[ 1 ] + y * gridSize[ 1 ]; + trace.origin[ 2 ] = gridMins[ 2 ] + z * gridSize[ 2 ]; + + /* set inhibit sphere */ + if( gridSize[ 0 ] > gridSize[ 1 ] && gridSize[ 0 ] > gridSize[ 2 ] ) + trace.inhibitRadius = gridSize[ 0 ] * 0.5f; + else if( gridSize[ 1 ] > gridSize[ 0 ] && gridSize[ 1 ] > gridSize[ 2 ] ) + trace.inhibitRadius = gridSize[ 1 ] * 0.5f; + else + trace.inhibitRadius = gridSize[ 2 ] * 0.5f; + + /* find point cluster */ + trace.cluster = ClusterForPointExt( trace.origin, GRID_EPSILON ); + if( trace.cluster < 0 ) + { + /* try to nudge the origin around to find a valid point */ + VectorCopy( trace.origin, baseOrigin ); + for( step = 9; step <= 18; step += 9 ) + { + for( i = 0; i < 8; i++ ) + { + VectorCopy( baseOrigin, trace.origin ); + if( i & 1 ) + trace.origin[ 0 ] += step; + else + trace.origin[ 0 ] -= step; + + if( i & 2 ) + trace.origin[ 1 ] += step; + else + trace.origin[ 1 ] -= step; + + if( i & 4 ) + trace.origin[ 2 ] += step; + else + trace.origin[ 2 ] -= step; + + /* ydnar: changed to find cluster num */ + trace.cluster = ClusterForPointExt( trace.origin, VERTEX_EPSILON ); + if( trace.cluster >= 0 ) + break; + } + + if( i != 8 ) + break; + } + + /* can't find a valid point at all */ + if( step > 18 ) + return; + } + + /* setup trace */ + trace.testOcclusion = !noTrace; + trace.forceSunlight = qfalse; + trace.recvShadows = WORLDSPAWN_RECV_SHADOWS; + trace.numSurfaces = 0; + trace.surfaces = NULL; + trace.numLights = 0; + trace.lights = NULL; + + /* clear */ + numCon = 0; + VectorClear( cheapColor ); + + /* trace to all the lights, find the major light direction, and divide the + total light between that along the direction and the remaining in the ambient */ + for( trace.light = lights; trace.light != NULL; trace.light = trace.light->next ) + { + float addSize; + + + /* sample light */ + if( !LightContributionToPoint( &trace ) ) + continue; + + /* handle negative light */ + if( trace.light->flags & LIGHT_NEGATIVE ) + VectorScale( trace.color, -1.0f, trace.color ); + + /* add a contribution */ + VectorCopy( trace.color, contributions[ numCon ].color ); + VectorCopy( trace.direction, contributions[ numCon ].dir ); + contributions[ numCon ].style = trace.light->style; + numCon++; + + /* push average direction around */ + addSize = VectorLength( trace.color ); + VectorMA( gp->dir, addSize, trace.direction, gp->dir ); + + /* stop after a while */ + if( numCon >= (MAX_CONTRIBUTIONS - 1) ) + break; + + /* ydnar: cheap mode */ + VectorAdd( cheapColor, trace.color, cheapColor ); + if( cheapgrid && cheapColor[ 0 ] >= 255.0f && cheapColor[ 1 ] >= 255.0f && cheapColor[ 2 ] >= 255.0f ) + break; + } + + /////// Floodlighting for point ////////////////// + //do our floodlight ambient occlusion loop, and add a single contribution based on the brightest dir + if (floodlighty) + { + int q; + float addSize,f; + vec3_t col,dir; + col[0]=col[1]=col[2]=floodlightIntensity; + dir[0]=dir[1]=0; + dir[2]=1; + + trace.testOcclusion = qtrue; + trace.forceSunlight = qfalse; + trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS; + trace.testAll = qtrue; + + for (q=0;q<2;q++) + { + if (q==0) //upper hemisphere + { + trace.normal[0]=0; + trace.normal[1]=0; + trace.normal[2]=1; + } + else //lower hemisphere + { + trace.normal[0]=0; + trace.normal[1]=0; + trace.normal[2]=-1; + } + + f = FloodLightForSample(&trace); + + contributions[ numCon ].color[0]=col[0]*f; + contributions[ numCon ].color[1]=col[1]*f; + contributions[ numCon ].color[2]=col[2]*f; + + contributions[ numCon ].dir[0]=dir[0]; + contributions[ numCon ].dir[1]=dir[1]; + contributions[ numCon ].dir[2]=dir[2]; + + contributions[ numCon ].style = 0; + numCon++; + /* push average direction around */ + addSize = VectorLength( col ); + VectorMA( gp->dir, addSize, dir, gp->dir ); + } + } + ///////////////////// + + /* normalize to get primary light direction */ + VectorNormalize( gp->dir, gp->dir ); + + /* now that we have identified the primary light direction, + go back and separate all the light into directed and ambient */ + numStyles = 1; + for( i = 0; i < numCon; i++ ) + { + /* get relative directed strength */ + d = DotProduct( contributions[ i ].dir, gp->dir ); + if( d < 0.0f ) + d = 0.0f; + + /* find appropriate style */ + for( j = 0; j < numStyles; j++ ) + { + if( gp->styles[ j ] == contributions[ i ].style ) + break; + } + + /* style not found? */ + if( j >= numStyles ) + { + /* add a new style */ + if( numStyles < MAX_LIGHTMAPS ) + { + gp->styles[ numStyles ] = contributions[ i ].style; + bgp->styles[ numStyles ] = contributions[ i ].style; + numStyles++; + //% Sys_Printf( "(%d, %d) ", num, contributions[ i ].style ); + } + + /* fallback */ + else + j = 0; + } + + /* add the directed color */ + VectorMA( gp->directed[ j ], d, contributions[ i ].color, gp->directed[ j ] ); + + /* ambient light will be at 1/4 the value of directed light */ + /* (ydnar: nuke this in favor of more dramatic lighting?) */ + d = 0.25f * (1.0f - d); + VectorMA( gp->ambient[ j ], d, contributions[ i ].color, gp->ambient[ j ] ); + } + + + /* store off sample */ + for( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + /* do some fudging to keep the ambient from being too low (2003-07-05: 0.25 -> 0.125) */ + if( !bouncing ) + VectorMA( gp->ambient[ i ], 0.125f, gp->directed[ i ], gp->ambient[ i ] ); + + /* set minimum light and copy off to bytes */ + VectorCopy( gp->ambient[ i ], color ); + for( j = 0; j < 3; j++ ) + if( color[ j ] < minGridLight[ j ] ) + color[ j ] = minGridLight[ j ]; + ColorToBytes( color, bgp->ambient[ i ], 1.0f ); + ColorToBytes( gp->directed[ i ], bgp->directed[ i ], 1.0f ); + } + + /* debug code */ + #if 0 + //% Sys_FPrintf( SYS_VRB, "%10d %10d %10d ", &gp->ambient[ 0 ][ 0 ], &gp->ambient[ 0 ][ 1 ], &gp->ambient[ 0 ][ 2 ] ); + Sys_FPrintf( SYS_VRB, "%9d Amb: (%03.1f %03.1f %03.1f) Dir: (%03.1f %03.1f %03.1f)\n", + num, + gp->ambient[ 0 ][ 0 ], gp->ambient[ 0 ][ 1 ], gp->ambient[ 0 ][ 2 ], + gp->directed[ 0 ][ 0 ], gp->directed[ 0 ][ 1 ], gp->directed[ 0 ][ 2 ] ); + #endif + + /* store direction */ + if( !bouncing ) + NormalToLatLong( gp->dir, bgp->latLong ); +} + + + +/* +SetupGrid() +calculates the size of the lightgrid and allocates memory +*/ + +void SetupGrid( void ) +{ + int i, j; + vec3_t maxs, oldGridSize; + const char *value; + char temp[ 64 ]; + + + /* don't do this if not grid lighting */ + if( noGridLighting ) + return; + + /* ydnar: set grid size */ + value = ValueForKey( &entities[ 0 ], "gridsize" ); + if( value[ 0 ] != '\0' ) + sscanf( value, "%f %f %f", &gridSize[ 0 ], &gridSize[ 1 ], &gridSize[ 2 ] ); + + /* quantize it */ + VectorCopy( gridSize, oldGridSize ); + for( i = 0; i < 3; i++ ) + gridSize[ i ] = gridSize[ i ] >= 8.0f ? floor( gridSize[ i ] ) : 8.0f; + + /* ydnar: increase gridSize until grid count is smaller than max allowed */ + numRawGridPoints = MAX_MAP_LIGHTGRID + 1; + j = 0; + while( numRawGridPoints > MAX_MAP_LIGHTGRID ) + { + /* get world bounds */ + for( i = 0; i < 3; i++ ) + { + gridMins[ i ] = gridSize[ i ] * ceil( bspModels[ 0 ].mins[ i ] / gridSize[ i ] ); + maxs[ i ] = gridSize[ i ] * floor( bspModels[ 0 ].maxs[ i ] / gridSize[ i ] ); + gridBounds[ i ] = (maxs[ i ] - gridMins[ i ]) / gridSize[ i ] + 1; + } + + /* set grid size */ + numRawGridPoints = gridBounds[ 0 ] * gridBounds[ 1 ] * gridBounds[ 2 ]; + + /* increase grid size a bit */ + if( numRawGridPoints > MAX_MAP_LIGHTGRID ) + gridSize[ j++ % 3 ] += 16.0f; + } + + /* print it */ + Sys_Printf( "Grid size = { %1.0f, %1.0f, %1.0f }\n", gridSize[ 0 ], gridSize[ 1 ], gridSize[ 2 ] ); + + /* different? */ + if( !VectorCompare( gridSize, oldGridSize ) ) + { + sprintf( temp, "%.0f %.0f %.0f", gridSize[ 0 ], gridSize[ 1 ], gridSize[ 2 ] ); + SetKeyValue( &entities[ 0 ], "gridsize", (const char*) temp ); + Sys_FPrintf( SYS_VRB, "Storing adjusted grid size\n" ); + } + + /* 2nd variable. fixme: is this silly? */ + numBSPGridPoints = numRawGridPoints; + + /* allocate lightgrid */ + rawGridPoints = safe_malloc( numRawGridPoints * sizeof( *rawGridPoints ) ); + memset( rawGridPoints, 0, numRawGridPoints * sizeof( *rawGridPoints ) ); + + if( bspGridPoints != NULL ) + free( bspGridPoints ); + bspGridPoints = safe_malloc( numBSPGridPoints * sizeof( *bspGridPoints ) ); + memset( bspGridPoints, 0, numBSPGridPoints * sizeof( *bspGridPoints ) ); + + /* clear lightgrid */ + for( i = 0; i < numRawGridPoints; i++ ) + { + VectorCopy( ambientColor, rawGridPoints[ i ].ambient[ j ] ); + rawGridPoints[ i ].styles[ 0 ] = LS_NORMAL; + bspGridPoints[ i ].styles[ 0 ] = LS_NORMAL; + for( j = 1; j < MAX_LIGHTMAPS; j++ ) + { + rawGridPoints[ i ].styles[ j ] = LS_NONE; + bspGridPoints[ i ].styles[ j ] = LS_NONE; + } + } + + /* note it */ + Sys_Printf( "%9d grid points\n", numRawGridPoints ); +} + + + +/* +LightWorld() +does what it says... +*/ + +void LightWorld( void ) +{ + vec3_t color; + float f; + int b, bt; + qboolean minVertex, minGrid; + const char *value; + + + /* ydnar: smooth normals */ + if( shade ) + { + Sys_Printf( "--- SmoothNormals ---\n" ); + SmoothNormals(); + } + + /* determine the number of grid points */ + Sys_Printf( "--- SetupGrid ---\n" ); + SetupGrid(); + + /* find the optional minimum lighting values */ + GetVectorForKey( &entities[ 0 ], "_color", color ); + if( VectorLength( color ) == 0.0f ) + VectorSet( color, 1.0, 1.0, 1.0 ); + + /* ambient */ + f = FloatForKey( &entities[ 0 ], "_ambient" ); + if( f == 0.0f ) + f = FloatForKey( &entities[ 0 ], "ambient" ); + VectorScale( color, f, ambientColor ); + + /* minvertexlight */ + minVertex = qfalse; + value = ValueForKey( &entities[ 0 ], "_minvertexlight" ); + if( value[ 0 ] != '\0' ) + { + minVertex = qtrue; + f = atof( value ); + VectorScale( color, f, minVertexLight ); + } + + /* mingridlight */ + minGrid = qfalse; + value = ValueForKey( &entities[ 0 ], "_mingridlight" ); + if( value[ 0 ] != '\0' ) + { + minGrid = qtrue; + f = atof( value ); + VectorScale( color, f, minGridLight ); + } + + /* minlight */ + value = ValueForKey( &entities[ 0 ], "_minlight" ); + if( value[ 0 ] != '\0' ) + { + f = atof( value ); + VectorScale( color, f, minLight ); + if( minVertex == qfalse ) + VectorScale( color, f, minVertexLight ); + if( minGrid == qfalse ) + VectorScale( color, f, minGridLight ); + } + + /* create world lights */ + Sys_FPrintf( SYS_VRB, "--- CreateLights ---\n" ); + CreateEntityLights(); + CreateSurfaceLights(); + Sys_Printf( "%9d point lights\n", numPointLights ); + Sys_Printf( "%9d spotlights\n", numSpotLights ); + Sys_Printf( "%9d diffuse (area) lights\n", numDiffuseLights ); + Sys_Printf( "%9d sun/sky lights\n", numSunLights ); + + /* calculate lightgrid */ + if( !noGridLighting ) + { + /* ydnar: set up light envelopes */ + SetupEnvelopes( qtrue, fastgrid ); + + Sys_Printf( "--- TraceGrid ---\n" ); + RunThreadsOnIndividual( numRawGridPoints, qtrue, TraceGrid ); + Sys_Printf( "%d x %d x %d = %d grid\n", + gridBounds[ 0 ], gridBounds[ 1 ], gridBounds[ 2 ], numBSPGridPoints ); + + /* ydnar: emit statistics on light culling */ + Sys_FPrintf( SYS_VRB, "%9d grid points envelope culled\n", gridEnvelopeCulled ); + Sys_FPrintf( SYS_VRB, "%9d grid points bounds culled\n", gridBoundsCulled ); + } + + /* slight optimization to remove a sqrt */ + subdivideThreshold *= subdivideThreshold; + + /* map the world luxels */ + Sys_Printf( "--- MapRawLightmap ---\n" ); + RunThreadsOnIndividual( numRawLightmaps, qtrue, MapRawLightmap ); + Sys_Printf( "%9d luxels\n", numLuxels ); + Sys_Printf( "%9d luxels mapped\n", numLuxelsMapped ); + Sys_Printf( "%9d luxels occluded\n", numLuxelsOccluded ); + + /* dirty them up */ + if( dirty ) + { + Sys_Printf( "--- DirtyRawLightmap ---\n" ); + + + + + RunThreadsOnIndividual( numRawLightmaps, qtrue, DirtyRawLightmap ); + } + + /* floodlight them up */ + if( floodlighty ) + { + Sys_Printf( "--- FloodlightRawLightmap ---\n" ); + RunThreadsOnIndividual( numRawLightmaps, qtrue, FloodLightRawLightmap ); + } + + /* ydnar: set up light envelopes */ + SetupEnvelopes( qfalse, fast ); + + /* light up my world */ + lightsPlaneCulled = 0; + lightsEnvelopeCulled = 0; + lightsBoundsCulled = 0; + lightsClusterCulled = 0; + + Sys_Printf( "--- IlluminateRawLightmap ---\n" ); + RunThreadsOnIndividual( numRawLightmaps, qtrue, IlluminateRawLightmap ); + Sys_Printf( "%9d luxels illuminated\n", numLuxelsIlluminated ); + + StitchSurfaceLightmaps(); + + Sys_Printf( "--- IlluminateVertexes ---\n" ); + RunThreadsOnIndividual( numBSPDrawSurfaces, qtrue, IlluminateVertexes ); + Sys_Printf( "%9d vertexes illuminated\n", numVertsIlluminated ); + + /* ydnar: emit statistics on light culling */ + Sys_FPrintf( SYS_VRB, "%9d lights plane culled\n", lightsPlaneCulled ); + Sys_FPrintf( SYS_VRB, "%9d lights envelope culled\n", lightsEnvelopeCulled ); + Sys_FPrintf( SYS_VRB, "%9d lights bounds culled\n", lightsBoundsCulled ); + Sys_FPrintf( SYS_VRB, "%9d lights cluster culled\n", lightsClusterCulled ); + + /* radiosity */ + b = 1; + bt = bounce; + while( bounce > 0 ) + { + /* store off the bsp between bounces */ + StoreSurfaceLightmaps(); + Sys_Printf( "Writing %s\n", source ); + WriteBSPFile( source ); + + /* note it */ + Sys_Printf( "\n--- Radiosity (bounce %d of %d) ---\n", b, bt ); + + /* flag bouncing */ + bouncing = qtrue; + VectorClear( ambientColor ); + floodlighty = false; + + /* generate diffuse lights */ + RadFreeLights(); + RadCreateDiffuseLights(); + + /* setup light envelopes */ + SetupEnvelopes( qfalse, fastbounce ); + if( numLights == 0 ) + { + Sys_Printf( "No diffuse light to calculate, ending radiosity.\n" ); + break; + } + + /* add to lightgrid */ + if( bouncegrid ) + { + gridEnvelopeCulled = 0; + gridBoundsCulled = 0; + + Sys_Printf( "--- BounceGrid ---\n" ); + RunThreadsOnIndividual( numRawGridPoints, qtrue, TraceGrid ); + Sys_FPrintf( SYS_VRB, "%9d grid points envelope culled\n", gridEnvelopeCulled ); + Sys_FPrintf( SYS_VRB, "%9d grid points bounds culled\n", gridBoundsCulled ); + } + + /* light up my world */ + lightsPlaneCulled = 0; + lightsEnvelopeCulled = 0; + lightsBoundsCulled = 0; + lightsClusterCulled = 0; + + Sys_Printf( "--- IlluminateRawLightmap ---\n" ); + RunThreadsOnIndividual( numRawLightmaps, qtrue, IlluminateRawLightmap ); + Sys_Printf( "%9d luxels illuminated\n", numLuxelsIlluminated ); + Sys_Printf( "%9d vertexes illuminated\n", numVertsIlluminated ); + + StitchSurfaceLightmaps(); + + Sys_Printf( "--- IlluminateVertexes ---\n" ); + RunThreadsOnIndividual( numBSPDrawSurfaces, qtrue, IlluminateVertexes ); + Sys_Printf( "%9d vertexes illuminated\n", numVertsIlluminated ); + + /* ydnar: emit statistics on light culling */ + Sys_FPrintf( SYS_VRB, "%9d lights plane culled\n", lightsPlaneCulled ); + Sys_FPrintf( SYS_VRB, "%9d lights envelope culled\n", lightsEnvelopeCulled ); + Sys_FPrintf( SYS_VRB, "%9d lights bounds culled\n", lightsBoundsCulled ); + Sys_FPrintf( SYS_VRB, "%9d lights cluster culled\n", lightsClusterCulled ); + + /* interate */ + bounce--; + b++; + } +} + + + +/* +LightMain() +main routine for light processing +*/ + +int LightMain( int argc, char **argv ) +{ + int i; + float f; + char mapSource[ 1024 ]; + const char *value; + + + /* note it */ + Sys_Printf( "--- Light ---\n" ); + + /* set standard game flags */ + wolfLight = game->wolfLight; + lmCustomSize = game->lightmapSize; + lightmapGamma = game->lightmapGamma; + lightmapCompensate = game->lightmapCompensate; + + /* process commandline arguments */ + for( i = 1; i < (argc - 1); i++ ) + { + /* lightsource scaling */ + if( !strcmp( argv[ i ], "-point" ) || !strcmp( argv[ i ], "-pointscale" ) ) + { + f = atof( argv[ i + 1 ] ); + pointScale *= f; + Sys_Printf( "Point (entity) light scaled by %f to %f\n", f, pointScale ); + i++; + } + + else if( !strcmp( argv[ i ], "-area" ) || !strcmp( argv[ i ], "-areascale" ) ) + { + f = atof( argv[ i + 1 ] ); + areaScale *= f; + Sys_Printf( "Area (shader) light scaled by %f to %f\n", f, areaScale ); + i++; + } + + else if( !strcmp( argv[ i ], "-sky" ) || !strcmp( argv[ i ], "-skyscale" ) ) + { + f = atof( argv[ i + 1 ] ); + skyScale *= f; + Sys_Printf( "Sky/sun light scaled by %f to %f\n", f, skyScale ); + i++; + } + + else if( !strcmp( argv[ i ], "-bouncescale" ) ) + { + f = atof( argv[ i + 1 ] ); + bounceScale *= f; + Sys_Printf( "Bounce (radiosity) light scaled by %f to %f\n", f, bounceScale ); + i++; + } + + else if( !strcmp( argv[ i ], "-scale" ) ) + { + f = atof( argv[ i + 1 ] ); + pointScale *= f; + areaScale *= f; + skyScale *= f; + bounceScale *= f; + Sys_Printf( "All light scaled by %f\n", f ); + i++; + } + + else if( !strcmp( argv[ i ], "-gamma" ) ) + { + f = atof( argv[ i + 1 ] ); + lightmapGamma = f; + Sys_Printf( "Lighting gamma set to %f\n", lightmapGamma ); + i++; + } + + else if( !strcmp( argv[ i ], "-exposure" ) ) + { + f = atof( argv[ i + 1 ] ); + lightmapExposure = f; + Sys_Printf( "Lighting exposure set to %f\n", lightmapExposure ); + i++; + } + + else if( !strcmp( argv[ i ], "-compensate" ) ) + { + f = atof( argv[ i + 1 ] ); + if( f <= 0.0f ) + f = 1.0f; + lightmapCompensate = f; + Sys_Printf( "Lighting compensation set to 1/%f\n", lightmapCompensate ); + i++; + } + + /* ydnar switches */ + else if( !strcmp( argv[ i ], "-bounce" ) ) + { + bounce = atoi( argv[ i + 1 ] ); + if( bounce < 0 ) + bounce = 0; + else if( bounce > 0 ) + Sys_Printf( "Radiosity enabled with %d bounce(s)\n", bounce ); + i++; + } + + else if( !strcmp( argv[ i ], "-supersample" ) || !strcmp( argv[ i ], "-super" ) ) + { + superSample = atoi( argv[ i + 1 ] ); + if( superSample < 1 ) + superSample = 1; + else if( superSample > 1 ) + Sys_Printf( "Ordered-grid supersampling enabled with %d sample(s) per lightmap texel\n", (superSample * superSample) ); + i++; + } + + else if( !strcmp( argv[ i ], "-samples" ) ) + { + lightSamples = atoi( argv[ i + 1 ] ); + if( lightSamples < 1 ) + lightSamples = 1; + else if( lightSamples > 1 ) + Sys_Printf( "Adaptive supersampling enabled with %d sample(s) per lightmap texel\n", lightSamples ); + i++; + } + + else if( !strcmp( argv[ i ], "-filter" ) ) + { + filter = qtrue; + Sys_Printf( "Lightmap filtering enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-dark" ) ) + { + dark = qtrue; + Sys_Printf( "Dark lightmap seams enabled\n" ); + } + + + + + + + + else if( !strcmp( argv[ i ], "-shadeangle" ) ) + { + shadeAngleDegrees = atof( argv[ i + 1 ] ); + if( shadeAngleDegrees < 0.0f ) + shadeAngleDegrees = 0.0f; + else if( shadeAngleDegrees > 0.0f ) + { + shade = qtrue; + Sys_Printf( "Phong shading enabled with a breaking angle of %f degrees\n", shadeAngleDegrees ); + } + i++; + } + + else if( !strcmp( argv[ i ], "-thresh" ) ) + { + subdivideThreshold = atof( argv[ i + 1 ] ); + if( subdivideThreshold < 0 ) + subdivideThreshold = DEFAULT_SUBDIVIDE_THRESHOLD; + else + Sys_Printf( "Subdivision threshold set at %.3f\n", subdivideThreshold ); + i++; + } + + else if( !strcmp( argv[ i ], "-approx" ) ) + { + approximateTolerance = atoi( argv[ i + 1 ] ); + if( approximateTolerance < 0 ) + approximateTolerance = 0; + else if( approximateTolerance > 0 ) + Sys_Printf( "Approximating lightmaps within a byte tolerance of %d\n", approximateTolerance ); + i++; + } + + else if( !strcmp( argv[ i ], "-deluxe" ) || !strcmp( argv[ i ], "-deluxemap" ) ) + { + deluxemap = qtrue; + Sys_Printf( "Generating deluxemaps for average light direction\n" ); + } + + else if( !strcmp( argv[ i ], "-external" ) ) + { + externalLightmaps = qtrue; + Sys_Printf( "Storing all lightmaps externally\n" ); + } + + else if( !strcmp( argv[ i ], "-lightmapsize" ) ) + { + lmCustomSize = atoi( argv[ i + 1 ] ); + + /* must be a power of 2 and greater than 2 */ + if( ((lmCustomSize - 1) & lmCustomSize) || lmCustomSize < 2 ) + { + Sys_Printf( "WARNING: Lightmap size must be a power of 2, greater or equal to 2 pixels.\n" ); + lmCustomSize = game->lightmapSize; + } + i++; + Sys_Printf( "Default lightmap size set to %d x %d pixels\n", lmCustomSize, lmCustomSize ); + + /* enable external lightmaps */ + if( lmCustomSize != game->lightmapSize ) + { + externalLightmaps = qtrue; + Sys_Printf( "Storing all lightmaps externally\n" ); + } + } + + /* ydnar: add this to suppress warnings */ + else if( !strcmp( argv[ i ], "-custinfoparms") ) + { + Sys_Printf( "Custom info parms enabled\n" ); + useCustomInfoParms = qtrue; + } + + else if( !strcmp( argv[ i ], "-wolf" ) ) + { + /* -game should already be set */ + wolfLight = qtrue; + Sys_Printf( "Enabling Wolf lighting model (linear default)\n" ); + } + + else if( !strcmp( argv[ i ], "-q3" ) ) + { + /* -game should already be set */ + wolfLight = qfalse; + Sys_Printf( "Enabling Quake 3 lighting model (nonlinear default)\n" ); + } + + else if( !strcmp( argv[ i ], "-sunonly" ) ) + { + sunOnly = qtrue; + Sys_Printf( "Only computing sunlight\n" ); + } + + else if( !strcmp( argv[ i ], "-bounceonly" ) ) + { + bounceOnly = qtrue; + Sys_Printf( "Storing bounced light (radiosity) only\n" ); + } + + else if( !strcmp( argv[ i ], "-nocollapse" ) ) + { + noCollapse = qtrue; + Sys_Printf( "Identical lightmap collapsing disabled\n" ); + } + + else if( !strcmp( argv[ i ], "-shade" ) ) + { + shade = qtrue; + Sys_Printf( "Phong shading enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-bouncegrid") ) + { + bouncegrid = qtrue; + if( bounce > 0 ) + Sys_Printf( "Grid lighting with radiosity enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-smooth" ) ) + { + lightSamples = EXTRA_SCALE; + Sys_Printf( "The -smooth argument is deprecated, use \"-samples 2\" instead\n" ); + } + + else if( !strcmp( argv[ i ], "-fast" ) ) + { + fast = qtrue; + fastgrid = qtrue; + fastbounce = qtrue; + Sys_Printf( "Fast mode enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-faster" ) ) + { + faster = qtrue; + fast = qtrue; + fastgrid = qtrue; + fastbounce = qtrue; + Sys_Printf( "Faster mode enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-fastgrid" ) ) + { + fastgrid = qtrue; + Sys_Printf( "Fast grid lighting enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-fastbounce" ) ) + { + fastbounce = qtrue; + Sys_Printf( "Fast bounce mode enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-cheap" ) ) + { + cheap = qtrue; + cheapgrid = qtrue; + Sys_Printf( "Cheap mode enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-cheapgrid" ) ) + { + cheapgrid = qtrue; + Sys_Printf( "Cheap grid mode enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-normalmap" ) ) + { + normalmap = qtrue; + Sys_Printf( "Storing normal map instead of lightmap\n" ); + } + + else if( !strcmp( argv[ i ], "-trisoup" ) ) + { + trisoup = qtrue; + Sys_Printf( "Converting brush faces to triangle soup\n" ); + } + + else if( !strcmp( argv[ i ], "-debug" ) ) + { + debug = qtrue; + Sys_Printf( "Lightmap debugging enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-debugsurfaces" ) || !strcmp( argv[ i ], "-debugsurface" ) ) + { + debugSurfaces = qtrue; + Sys_Printf( "Lightmap surface debugging enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-debugunused" ) ) + { + debugUnused = qtrue; + Sys_Printf( "Unused luxel debugging enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-debugaxis" ) ) + { + debugAxis = qtrue; + Sys_Printf( "Lightmap axis debugging enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-debugcluster" ) ) + { + debugCluster = qtrue; + Sys_Printf( "Luxel cluster debugging enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-debugorigin" ) ) + { + debugOrigin = qtrue; + Sys_Printf( "Luxel origin debugging enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-debugdeluxe" ) ) + { + deluxemap = qtrue; + debugDeluxemap = qtrue; + Sys_Printf( "Deluxemap debugging enabled\n" ); + } + + else if( !strcmp( argv[ i ], "-export" ) ) + { + exportLightmaps = qtrue; + Sys_Printf( "Exporting lightmaps\n" ); + } + + else if( !strcmp(argv[ i ], "-notrace" )) + { + noTrace = qtrue; + Sys_Printf( "Shadow occlusion disabled\n" ); + } + else if( !strcmp(argv[ i ], "-patchshadows" ) ) + { + patchShadows = qtrue; + Sys_Printf( "Patch shadow casting enabled\n" ); + } + else if( !strcmp( argv[ i ], "-extra" ) ) + { + superSample = EXTRA_SCALE; /* ydnar */ + Sys_Printf( "The -extra argument is deprecated, use \"-super 2\" instead\n" ); + } + else if( !strcmp( argv[ i ], "-extrawide" ) ) + { + superSample = EXTRAWIDE_SCALE; /* ydnar */ + filter = qtrue; /* ydnar */ + Sys_Printf( "The -extrawide argument is deprecated, use \"-filter [-super 2]\" instead\n"); + } + else if( !strcmp( argv[ i ], "-samplesize" ) ) + { + sampleSize = atoi( argv[ i + 1 ] ); + if( sampleSize < 1 ) + sampleSize = 1; + i++; + Sys_Printf( "Default lightmap sample size set to %dx%d units\n", sampleSize, sampleSize ); + } + else if( !strcmp( argv[ i ], "-novertex" ) ) + { + noVertexLighting = qtrue; + Sys_Printf( "Disabling vertex lighting\n" ); + } + else if( !strcmp( argv[ i ], "-nogrid" ) ) + { + noGridLighting = qtrue; + Sys_Printf( "Disabling grid lighting\n" ); + } + else if( !strcmp( argv[ i ], "-border" ) ) + { + lightmapBorder = qtrue; + Sys_Printf( "Adding debug border to lightmaps\n" ); + } + else if( !strcmp( argv[ i ], "-nosurf" ) ) + { + noSurfaces = qtrue; + Sys_Printf( "Not tracing against surfaces\n" ); + } + else if( !strcmp( argv[ i ], "-dump" ) ) + { + dump = qtrue; + Sys_Printf( "Dumping radiosity lights into numbered prefabs\n" ); + } + else if( !strcmp( argv[ i ], "-lomem" ) ) + { + loMem = qtrue; + Sys_Printf( "Enabling low-memory (potentially slower) lighting mode\n" ); + } + else if( !strcmp( argv[ i ], "-nostyle" ) || !strcmp( argv[ i ], "-nostyles" ) ) + { + noStyles = qtrue; + Sys_Printf( "Disabling lightstyles\n" ); + } + else if( !strcmp( argv[ i ], "-cpma" ) ) + { + cpmaHack = qtrue; + Sys_Printf( "Enabling Challenge Pro Mode Asstacular Vertex Lighting Mode (tm)\n" ); + } + else if( !strcmp( argv[ i ], "-floodlight" ) ) + { + floodlighty = qtrue; + Sys_Printf( "FloodLighting enabled\n" ); + } + else if( !strcmp( argv[ i ], "-debugnormals" ) ) + { + debugnormals = qtrue; + Sys_Printf( "DebugNormals enabled\n" ); + } + else if( !strcmp( argv[ i ], "-lowquality" ) ) + { + floodlight_lowquality = qtrue; + Sys_Printf( "Low Quality FloodLighting enabled\n" ); + } + + /* r7: dirtmapping */ + else if( !strcmp( argv[ i ], "-dirty" ) ) + { + dirty = qtrue; + Sys_Printf( "Dirtmapping enabled\n" ); + } + else if( !strcmp( argv[ i ], "-dirtdebug" ) || !strcmp( argv[ i ], "-debugdirt" ) ) + { + dirtDebug = qtrue; + Sys_Printf( "Dirtmap debugging enabled\n" ); + } + else if( !strcmp( argv[ i ], "-dirtmode" ) ) + { + dirtMode = atoi( argv[ i + 1 ] ); + if( dirtMode != 0 && dirtMode != 1 ) + dirtMode = 0; + if( dirtMode == 1 ) + Sys_Printf( "Enabling randomized dirtmapping\n" ); + else + Sys_Printf( "Enabling ordered dir mapping\n" ); + } + else if( !strcmp( argv[ i ], "-dirtdepth" ) ) + { + dirtDepth = atof( argv[ i + 1 ] ); + if( dirtDepth <= 0.0f ) + dirtDepth = 128.0f; + Sys_Printf( "Dirtmapping depth set to %.1f\n", dirtDepth ); + } + else if( !strcmp( argv[ i ], "-dirtscale" ) ) + { + dirtScale = atof( argv[ i + 1 ] ); + if( dirtScale <= 0.0f ) + dirtScale = 1.0f; + Sys_Printf( "Dirtmapping scale set to %.1f\n", dirtScale ); + } + else if( !strcmp( argv[ i ], "-dirtgain" ) ) + { + dirtGain = atof( argv[ i + 1 ] ); + if( dirtGain <= 0.0f ) + dirtGain = 1.0f; + Sys_Printf( "Dirtmapping gain set to %.1f\n", dirtGain ); + } + + /* unhandled args */ + else + Sys_Printf( "WARNING: Unknown argument \"%s\"\n", argv[ i ] ); + + } + + /* clean up map name */ + strcpy( source, ExpandArg( argv[ i ] ) ); + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + strcpy( mapSource, ExpandArg( argv[ i ] ) ); + StripExtension( mapSource ); + DefaultExtension( mapSource, ".map" ); + + /* ydnar: set default sample size */ + SetDefaultSampleSize( sampleSize ); + + /* ydnar: handle shaders */ + BeginMapShaderFile( source ); + LoadShaderInfo(); + + /* note loading */ + Sys_Printf( "Loading %s\n", source ); + + /* ydnar: load surface file */ + LoadSurfaceExtraFile( source ); + + /* load bsp file */ + LoadBSPFile( source ); + + /* parse bsp entities */ + ParseEntities(); + + /* load map file */ + value = ValueForKey( &entities[ 0 ], "_keepLights" ); + if( value[ 0 ] != '1' ) + LoadMapFile( mapSource, qtrue ); + + /* set the entity/model origins and init yDrawVerts */ + SetEntityOrigins(); + + /* ydnar: set up optimization */ + SetupBrushes(); + SetupDirt(); + SetupFloodLight(); + SetupSurfaceLightmaps(); + + /* initialize the surface facet tracing */ + SetupTraceNodes(); + + /* light the world */ + LightWorld(); + + /* ydnar: store off lightmaps */ + StoreSurfaceLightmaps(); + + /* write out the bsp */ + UnparseEntities(); + Sys_Printf( "Writing %s\n", source ); + WriteBSPFile( source ); + + /* ydnar: export lightmaps */ + if( exportLightmaps && !externalLightmaps ) + ExportLightmaps(); + + /* return to sender */ + return 0; +} + diff --git a/tools/quake3/q3map2/light_bounce.c b/tools/quake3/q3map2/light_bounce.c new file mode 100644 index 00000000..dba5b131 --- /dev/null +++ b/tools/quake3/q3map2/light_bounce.c @@ -0,0 +1,956 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define LIGHT_BOUNCE_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* functions */ + +/* +RadFreeLights() +deletes any existing lights, freeing up memory for the next bounce +*/ + +void RadFreeLights( void ) +{ + light_t *light, *next; + + + /* delete lights */ + for( light = lights; light; light = next ) + { + next = light->next; + if( light->w != NULL ) + FreeWinding( light->w ); + free( light ); + } + numLights = 0; + lights = NULL; +} + + + +/* +RadClipWindingEpsilon() +clips a rad winding by a plane +based off the regular clip winding code +*/ + +static void RadClipWindingEpsilon( radWinding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, radWinding_t *front, radWinding_t *back, clipWork_t *cw ) +{ + vec_t *dists; + int *sides; + int counts[ 3 ]; + vec_t dot; /* ydnar: changed from static b/c of threading */ /* VC 4.2 optimizer bug if not static? */ + int i, j, k; + radVert_t *v1, *v2, mid; + int maxPoints; + + + /* crutch */ + dists = cw->dists; + sides = cw->sides; + + /* clear counts */ + counts[ 0 ] = counts[ 1 ] = counts[ 2 ] = 0; + + /* determine sides for each point */ + for( i = 0; i < in->numVerts; i++ ) + { + dot = DotProduct( in->verts[ i ].xyz, normal ); + dot -= dist; + dists[ i ] = dot; + if( dot > epsilon ) + sides[ i ] = SIDE_FRONT; + else if( dot < -epsilon ) + sides[ i ] = SIDE_BACK; + else + sides[ i ] = SIDE_ON; + counts[ sides[ i ] ]++; + } + sides[ i ] = sides[ 0 ]; + dists[ i ] = dists[ 0 ]; + + /* clear front and back */ + front->numVerts = back->numVerts = 0; + + /* handle all on one side cases */ + if( counts[ 0 ] == 0 ) + { + memcpy( back, in, sizeof( radWinding_t ) ); + return; + } + if( counts[ 1 ] == 0 ) + { + memcpy( front, in, sizeof( radWinding_t ) ); + return; + } + + /* setup windings */ + maxPoints = in->numVerts + 4; + + /* do individual verts */ + for( i = 0; i < in->numVerts; i++ ) + { + /* do simple vertex copies first */ + v1 = &in->verts[ i ]; + + if( sides[ i ] == SIDE_ON ) + { + memcpy( &front->verts[ front->numVerts++ ], v1, sizeof( radVert_t ) ); + memcpy( &back->verts[ back->numVerts++ ], v1, sizeof( radVert_t ) ); + continue; + } + + if( sides[ i ] == SIDE_FRONT ) + memcpy( &front->verts[ front->numVerts++ ], v1, sizeof( radVert_t ) ); + + if( sides[ i ] == SIDE_BACK ) + memcpy( &back->verts[ back->numVerts++ ], v1, sizeof( radVert_t ) ); + + if( sides[ i + 1 ] == SIDE_ON || sides[ i + 1 ] == sides[ i ] ) + continue; + + /* generate a split vertex */ + v2 = &in->verts[ (i + 1) % in->numVerts ]; + + dot = dists[ i ] / (dists[ i ] - dists[ i + 1 ]); + + /* average vertex values */ + for( j = 0; j < 4; j++ ) + { + /* color */ + if( j < 4 ) + { + for( k = 0; k < MAX_LIGHTMAPS; k++ ) + mid.color[ k ][ j ] = v1->color[ k ][ j ] + dot * (v2->color[ k ][ j ] - v1->color[ k ][ j ]); + } + + /* xyz, normal */ + if( j < 3 ) + { + mid.xyz[ j ] = v1->xyz[ j ] + dot * (v2->xyz[ j ] - v1->xyz[ j ]); + mid.normal[ j ] = v1->normal[ j ] + dot * (v2->normal[ j ] - v1->normal[ j ]); + } + + /* st, lightmap */ + if( j < 2 ) + { + mid.st[ j ] = v1->st[ j ] + dot * (v2->st[ j ] - v1->st[ j ]); + for( k = 0; k < MAX_LIGHTMAPS; k++ ) + mid.lightmap[ k ][ j ] = v1->lightmap[ k ][ j ] + dot * (v2->lightmap[ k ][ j ] - v1->lightmap[ k ][ j ]); + } + } + + /* normalize the averaged normal */ + VectorNormalize( mid.normal, mid.normal ); + + /* copy the midpoint to both windings */ + memcpy( &front->verts[ front->numVerts++ ], &mid, sizeof( radVert_t ) ); + memcpy( &back->verts[ back->numVerts++ ], &mid, sizeof( radVert_t ) ); + } + + /* error check */ + if( front->numVerts > maxPoints || front->numVerts > maxPoints ) + Error( "RadClipWindingEpsilon: points exceeded estimate" ); + if( front->numVerts > MAX_POINTS_ON_WINDING || front->numVerts > MAX_POINTS_ON_WINDING ) + Error( "RadClipWindingEpsilon: MAX_POINTS_ON_WINDING" ); +} + + + + + +/* +RadSampleImage() +samples a texture image for a given color +returns qfalse if pixels are bad +*/ + +qboolean RadSampleImage( byte *pixels, int width, int height, float st[ 2 ], float color[ 4 ] ) +{ + float sto[ 2 ]; + int x, y; + + + /* clear color first */ + color[ 0 ] = color[ 1 ] = color[ 2 ] = color[ 3 ] = 255; + + /* dummy check */ + if( pixels == NULL || width < 1 || height < 1 ) + return qfalse; + + /* bias st */ + sto[ 0 ] = st[ 0 ]; + while( sto[ 0 ] < 0.0f ) + sto[ 0 ] += 1.0f; + sto[ 1 ] = st[ 1 ]; + while( sto[ 1 ] < 0.0f ) + sto[ 1 ] += 1.0f; + + /* get offsets */ + x = ((float) width * sto[ 0 ]) + 0.5f; + x %= width; + y = ((float) height * sto[ 1 ]) + 0.5f; + y %= height; + + /* get pixel */ + pixels += (y * width * 4) + (x * 4); + VectorCopy( pixels, color ); + color[ 3 ] = pixels[ 3 ]; + return qtrue; +} + + + +/* +RadSample() +samples a fragment's lightmap or vertex color and returns an +average color and a color gradient for the sample +*/ + +#define MAX_SAMPLES 150 +#define SAMPLE_GRANULARITY 6 + +static void RadSample( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm, shaderInfo_t *si, radWinding_t *rw, vec3_t average, vec3_t gradient, int *style ) +{ + int i, j, k, l, v, x, y, samples; + vec3_t color, mins, maxs; + vec4_t textureColor; + float alpha, alphaI, bf; + vec3_t blend; + float st[ 2 ], lightmap[ 2 ], *radLuxel; + radVert_t *rv[ 3 ]; + + + /* initial setup */ + ClearBounds( mins, maxs ); + VectorClear( average ); + VectorClear( gradient ); + alpha = 0; + + /* dummy check */ + if( rw == NULL || rw->numVerts < 3 ) + return; + + /* start sampling */ + samples = 0; + + /* sample vertex colors if no lightmap or this is the initial pass */ + if( lm == NULL || lm->radLuxels[ lightmapNum ] == NULL || bouncing == qfalse ) + { + for( samples = 0; samples < rw->numVerts; samples++ ) + { + /* multiply by texture color */ + if( !RadSampleImage( si->lightImage->pixels, si->lightImage->width, si->lightImage->height, rw->verts[ samples ].st, textureColor ) ) + { + VectorCopy( si->averageColor, textureColor ); + textureColor[ 4 ] = 255.0f; + } + for( i = 0; i < 3; i++ ) + color[ i ] = (textureColor[ i ] / 255) * (rw->verts[ samples ].color[ lightmapNum ][ i ] / 255.0f); + + AddPointToBounds( color, mins, maxs ); + VectorAdd( average, color, average ); + + /* get alpha */ + alpha += (textureColor[ 3 ] / 255.0f) * (rw->verts[ samples ].color[ lightmapNum ][ 3 ] / 255.0f); + } + + /* set style */ + *style = ds->vertexStyles[ lightmapNum ]; + } + + /* sample lightmap */ + else + { + /* fracture the winding into a fan (including degenerate tris) */ + for( v = 1; v < (rw->numVerts - 1) && samples < MAX_SAMPLES; v++ ) + { + /* get a triangle */ + rv[ 0 ] = &rw->verts[ 0 ]; + rv[ 1 ] = &rw->verts[ v ]; + rv[ 2 ] = &rw->verts[ v + 1 ]; + + /* this code is embarassing (really should just rasterize the triangle) */ + for( i = 1; i < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; i++ ) + { + for( j = 1; j < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; j++ ) + { + for( k = 1; k < SAMPLE_GRANULARITY && samples < MAX_SAMPLES; k++ ) + { + /* create a blend vector (barycentric coordinates) */ + blend[ 0 ] = i; + blend[ 1 ] = j; + blend[ 2 ] = k; + bf = (1.0 / (blend[ 0 ] + blend[ 1 ] + blend[ 2 ])); + VectorScale( blend, bf, blend ); + + /* create a blended sample */ + st[ 0 ] = st[ 1 ] = 0.0f; + lightmap[ 0 ] = lightmap[ 1 ] = 0.0f; + alphaI = 0.0f; + for( l = 0; l < 3; l++ ) + { + st[ 0 ] += (rv[ l ]->st[ 0 ] * blend[ l ]); + st[ 1 ] += (rv[ l ]->st[ 1 ] * blend[ l ]); + lightmap[ 0 ] += (rv[ l ]->lightmap[ lightmapNum ][ 0 ] * blend[ l ]); + lightmap[ 1 ] += (rv[ l ]->lightmap[ lightmapNum ][ 1 ] * blend[ l ]); + alphaI += (rv[ l ]->color[ lightmapNum ][ 3 ] * blend[ l ]); + } + + /* get lightmap xy coords */ + x = lightmap[ 0 ] / (float) superSample; + y = lightmap[ 1 ] / (float) superSample; + if( x < 0 ) + x = 0; + else if ( x >= lm->w ) + x = lm->w - 1; + if( y < 0 ) + y = 0; + else if ( y >= lm->h ) + y = lm->h - 1; + + /* get radiosity luxel */ + radLuxel = RAD_LUXEL( lightmapNum, x, y ); + + /* ignore unlit/unused luxels */ + if( radLuxel[ 0 ] < 0.0f ) + continue; + + /* inc samples */ + samples++; + + /* multiply by texture color */ + if( !RadSampleImage( si->lightImage->pixels, si->lightImage->width, si->lightImage->height, st, textureColor ) ) + { + VectorCopy( si->averageColor, textureColor ); + textureColor[ 4 ] = 255; + } + for( i = 0; i < 3; i++ ) + color[ i ] = (textureColor[ i ] / 255) * (radLuxel[ i ] / 255); + + AddPointToBounds( color, mins, maxs ); + VectorAdd( average, color, average ); + + /* get alpha */ + alpha += (textureColor[ 3 ] / 255) * (alphaI / 255); + } + } + } + } + + /* set style */ + *style = ds->lightmapStyles[ lightmapNum ]; + } + + /* any samples? */ + if( samples <= 0 ) + return; + + /* average the color */ + VectorScale( average, (1.0 / samples), average ); + + /* create the color gradient */ + //% VectorSubtract( maxs, mins, delta ); + + /* new: color gradient will always be 0-1.0, expressed as the range of light relative to overall light */ + //% gradient[ 0 ] = maxs[ 0 ] > 0.0f ? (maxs[ 0 ] - mins[ 0 ]) / maxs[ 0 ] : 0.0f; + //% gradient[ 1 ] = maxs[ 1 ] > 0.0f ? (maxs[ 1 ] - mins[ 1 ]) / maxs[ 1 ] : 0.0f; + //% gradient[ 2 ] = maxs[ 2 ] > 0.0f ? (maxs[ 2 ] - mins[ 2 ]) / maxs[ 2 ] : 0.0f; + + /* newer: another contrast function */ + for( i = 0; i < 3; i++ ) + gradient[ i ] = (maxs[ i ] - mins[ i ]) * maxs[ i ]; +} + + + +/* +RadSubdivideDiffuseLight() +subdivides a radiosity winding until it is smaller than subdivide, then generates an area light +*/ + +#define RADIOSITY_MAX_GRADIENT 0.75f //% 0.25f +#define RADIOSITY_VALUE 500.0f +#define RADIOSITY_MIN 0.0001f +#define RADIOSITY_CLIP_EPSILON 0.125f + +static void RadSubdivideDiffuseLight( int lightmapNum, bspDrawSurface_t *ds, rawLightmap_t *lm, shaderInfo_t *si, + float scale, float subdivide, qboolean original, radWinding_t *rw, clipWork_t *cw ) +{ + int i, style; + float dist, area, value; + vec3_t mins, maxs, normal, d1, d2, cross, color, gradient; + light_t *light, *splash; + winding_t *w; + + + /* dummy check */ + if( rw == NULL || rw->numVerts < 3 ) + return; + + /* get bounds for winding */ + ClearBounds( mins, maxs ); + for( i = 0; i < rw->numVerts; i++ ) + AddPointToBounds( rw->verts[ i ].xyz, mins, maxs ); + + /* subdivide if necessary */ + for( i = 0; i < 3; i++ ) + { + if( maxs[ i ] - mins[ i ] > subdivide ) + { + radWinding_t front, back; + + + /* make axial plane */ + VectorClear( normal ); + normal[ i ] = 1; + dist = (maxs[ i ] + mins[ i ]) * 0.5f; + + /* clip the winding */ + RadClipWindingEpsilon( rw, normal, dist, RADIOSITY_CLIP_EPSILON, &front, &back, cw ); + + /* recurse */ + RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qfalse, &front, cw ); + RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qfalse, &back, cw ); + return; + } + } + + /* check area */ + area = 0.0f; + for( i = 2; i < rw->numVerts; i++ ) + { + VectorSubtract( rw->verts[ i - 1 ].xyz, rw->verts[ 0 ].xyz, d1 ); + VectorSubtract( rw->verts[ i ].xyz, rw->verts[ 0 ].xyz, d2 ); + CrossProduct( d1, d2, cross ); + area += 0.5f * VectorLength( cross ); + } + if( area < 1.0f || area > 20000000.0f ) + return; + + /* more subdivision may be necessary */ + if( bouncing ) + { + /* get color sample for the surface fragment */ + RadSample( lightmapNum, ds, lm, si, rw, color, gradient, &style ); + + /* if color gradient is too high, subdivide again */ + if( subdivide > minDiffuseSubdivide && + (gradient[ 0 ] > RADIOSITY_MAX_GRADIENT || gradient[ 1 ] > RADIOSITY_MAX_GRADIENT || gradient[ 2 ] > RADIOSITY_MAX_GRADIENT) ) + { + RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, (subdivide / 2.0f), qfalse, rw, cw ); + return; + } + } + + /* create a regular winding and an average normal */ + w = AllocWinding( rw->numVerts ); + w->numpoints = rw->numVerts; + VectorClear( normal ); + for( i = 0; i < rw->numVerts; i++ ) + { + VectorCopy( rw->verts[ i ].xyz, w->p[ i ] ); + VectorAdd( normal, rw->verts[ i ].normal, normal ); + } + VectorScale( normal, (1.0f / rw->numVerts), normal ); + if( VectorNormalize( normal, normal ) == 0.0f ) + return; + + /* early out? */ + if( bouncing && VectorLength( color ) < RADIOSITY_MIN ) + return; + + /* debug code */ + //% Sys_Printf( "Size: %d %d %d\n", (int) (maxs[ 0 ] - mins[ 0 ]), (int) (maxs[ 1 ] - mins[ 1 ]), (int) (maxs[ 2 ] - mins[ 2 ]) ); + //% Sys_Printf( "Grad: %f %f %f\n", gradient[ 0 ], gradient[ 1 ], gradient[ 2 ] ); + + /* increment counts */ + numDiffuseLights++; + switch( ds->surfaceType ) + { + case MST_PLANAR: + numBrushDiffuseLights++; + break; + + case MST_TRIANGLE_SOUP: + numTriangleDiffuseLights++; + break; + + case MST_PATCH: + numPatchDiffuseLights++; + break; + } + + /* create a light */ + light = safe_malloc( sizeof( *light ) ); + memset( light, 0, sizeof( *light ) ); + + /* attach it */ + ThreadLock(); + light->next = lights; + lights = light; + ThreadUnlock(); + + /* initialize the light */ + light->flags = LIGHT_AREA_DEFAULT; + light->type = EMIT_AREA; + light->si = si; + light->fade = 1.0f; + light->w = w; + + /* set falloff threshold */ + light->falloffTolerance = falloffTolerance; + + /* bouncing light? */ + if( bouncing == qfalse ) + { + /* handle first-pass lights in normal q3a style */ + value = si->value; + light->photons = value * area * areaScale; + light->add = value * formFactorValueScale * areaScale; + VectorCopy( si->color, light->color ); + VectorScale( light->color, light->add, light->emitColor ); + light->style = noStyles ? LS_NORMAL : si->lightStyle; + if( light->style < LS_NORMAL || light->style >= LS_NONE ) + light->style = LS_NORMAL; + + /* set origin */ + VectorAdd( mins, maxs, light->origin ); + VectorScale( light->origin, 0.5f, light->origin ); + + /* nudge it off the plane a bit */ + VectorCopy( normal, light->normal ); + VectorMA( light->origin, 1.0f, light->normal, light->origin ); + light->dist = DotProduct( light->origin, normal ); + + /* optionally create a point splashsplash light for first pass */ + if( original && si->backsplashFraction > 0 ) + { + /* allocate a new point light */ + splash = safe_malloc( sizeof( *splash ) ); + memset( splash, 0, sizeof( *splash ) ); + splash->next = lights; + lights = splash; + + /* set it up */ + splash->flags = LIGHT_Q3A_DEFAULT; + splash->type = EMIT_POINT; + splash->photons = light->photons * si->backsplashFraction; + splash->fade = 1.0f; + splash->si = si; + VectorMA( light->origin, si->backsplashDistance, normal, splash->origin ); + VectorCopy( si->color, splash->color ); + splash->falloffTolerance = falloffTolerance; + splash->style = noStyles ? LS_NORMAL : light->style; + + /* add to counts */ + numPointLights++; + } + } + else + { + /* handle bounced light (radiosity) a little differently */ + value = RADIOSITY_VALUE * si->bounceScale * 0.375f; + light->photons = value * area * bounceScale; + light->add = value * formFactorValueScale * bounceScale; + VectorCopy( color, light->color ); + VectorScale( light->color, light->add, light->emitColor ); + light->style = noStyles ? LS_NORMAL : style; + if( light->style < LS_NORMAL || light->style >= LS_NONE ) + light->style = LS_NORMAL; + + /* set origin */ + WindingCenter( w, light->origin ); + + /* nudge it off the plane a bit */ + VectorCopy( normal, light->normal ); + VectorMA( light->origin, 1.0f, light->normal, light->origin ); + light->dist = DotProduct( light->origin, normal ); + } + + /* emit light from both sides? */ + if( si->compileFlags & C_FOG || si->twoSided ) + light->flags |= LIGHT_TWOSIDED; + + //% Sys_Printf( "\nAL: C: (%6f, %6f, %6f) [%6f] N: (%6f, %6f, %6f) %s\n", + //% light->color[ 0 ], light->color[ 1 ], light->color[ 2 ], light->add, + //% light->normal[ 0 ], light->normal[ 1 ], light->normal[ 2 ], + //% light->si->shader ); +} + + + +/* +RadLightForTriangles() +creates unbounced diffuse lights for triangle soup (misc_models, etc) +*/ + +void RadLightForTriangles( int num, int lightmapNum, rawLightmap_t *lm, shaderInfo_t *si, float scale, float subdivide, clipWork_t *cw ) +{ + int i, j, k, v; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + float *radVertexLuxel; + radWinding_t rw; + + + /* get surface */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* each triangle is a potential emitter */ + rw.numVerts = 3; + for( i = 0; i < ds->numIndexes; i += 3 ) + { + /* copy each vert */ + for( j = 0; j < 3; j++ ) + { + /* get vertex index and rad vertex luxel */ + v = ds->firstVert + bspDrawIndexes[ ds->firstIndex + i + j ]; + + /* get most everything */ + memcpy( &rw.verts[ j ], &yDrawVerts[ v ], sizeof( bspDrawVert_t ) ); + + /* fix colors */ + for( k = 0; k < MAX_LIGHTMAPS; k++ ) + { + radVertexLuxel = RAD_VERTEX_LUXEL( k, ds->firstVert + bspDrawIndexes[ ds->firstIndex + i + j ] ); + VectorCopy( radVertexLuxel, rw.verts[ j ].color[ k ] ); + rw.verts[ j ].color[ k ][ 3 ] = yDrawVerts[ v ].color[ k ][ 3 ]; + } + } + + /* subdivide into area lights */ + RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw ); + } +} + + + +/* +RadLightForPatch() +creates unbounced diffuse lights for patches +*/ + +#define PLANAR_EPSILON 0.1f + +void RadLightForPatch( int num, int lightmapNum, rawLightmap_t *lm, shaderInfo_t *si, float scale, float subdivide, clipWork_t *cw ) +{ + int i, x, y, v, t, pw[ 5 ], r; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + bspDrawVert_t *bogus; + bspDrawVert_t *dv[ 4 ]; + mesh_t src, *subdivided, *mesh; + float *radVertexLuxel; + float dist; + vec4_t plane; + qboolean planar; + radWinding_t rw; + + + /* get surface */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* construct a bogus vert list with color index stuffed into color[ 0 ] */ + bogus = safe_malloc( ds->numVerts * sizeof( bspDrawVert_t ) ); + memcpy( bogus, &yDrawVerts[ ds->firstVert ], ds->numVerts * sizeof( bspDrawVert_t ) ); + for( i = 0; i < ds->numVerts; i++ ) + bogus[ i ].color[ 0 ][ 0 ] = i; + + /* build a subdivided mesh identical to shadow facets for this patch */ + /* this MUST MATCH FacetsForPatch() identically! */ + src.width = ds->patchWidth; + src.height = ds->patchHeight; + src.verts = bogus; + //% subdivided = SubdivideMesh( src, 8, 512 ); + subdivided = SubdivideMesh2( src, info->patchIterations ); + PutMeshOnCurve( *subdivided ); + //% MakeMeshNormals( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + free( bogus ); + + /* FIXME: build interpolation table into color[ 1 ] */ + + /* fix up color indexes */ + for( i = 0; i < (mesh->width * mesh->height); i++ ) + { + dv[ 0 ] = &mesh->verts[ i ]; + if( dv[ 0 ]->color[ 0 ][ 0 ] >= ds->numVerts ) + dv[ 0 ]->color[ 0 ][ 0 ] = ds->numVerts - 1; + } + + /* iterate through the mesh quads */ + for( y = 0; y < (mesh->height - 1); y++ ) + { + for( x = 0; x < (mesh->width - 1); x++ ) + { + /* set indexes */ + pw[ 0 ] = x + (y * mesh->width); + pw[ 1 ] = x + ((y + 1) * mesh->width); + pw[ 2 ] = x + 1 + ((y + 1) * mesh->width); + pw[ 3 ] = x + 1 + (y * mesh->width); + pw[ 4 ] = x + (y * mesh->width); /* same as pw[ 0 ] */ + + /* set radix */ + r = (x + y) & 1; + + /* get drawverts */ + dv[ 0 ] = &mesh->verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &mesh->verts[ pw[ r + 1 ] ]; + dv[ 2 ] = &mesh->verts[ pw[ r + 2 ] ]; + dv[ 3 ] = &mesh->verts[ pw[ r + 3 ] ]; + + /* planar? */ + planar = PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ); + if( planar ) + { + dist = DotProduct( dv[ 1 ]->xyz, plane ) - plane[ 3 ]; + if( fabs( dist ) > PLANAR_EPSILON ) + planar = qfalse; + } + + /* generate a quad */ + if( planar ) + { + rw.numVerts = 4; + for( v = 0; v < 4; v++ ) + { + /* get most everything */ + memcpy( &rw.verts[ v ], dv[ v ], sizeof( bspDrawVert_t ) ); + + /* fix colors */ + for( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + radVertexLuxel = RAD_VERTEX_LUXEL( i, ds->firstVert + dv[ v ]->color[ 0 ][ 0 ] ); + VectorCopy( radVertexLuxel, rw.verts[ v ].color[ i ] ); + rw.verts[ v ].color[ i ][ 3 ] = dv[ v ]->color[ i ][ 3 ]; + } + } + + /* subdivide into area lights */ + RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw ); + } + + /* generate 2 tris */ + else + { + rw.numVerts = 3; + for( t = 0; t < 2; t++ ) + { + for( v = 0; v < 3 + t; v++ ) + { + /* get "other" triangle (stupid hacky logic, but whatevah) */ + if( v == 1 && t == 1 ) + v++; + + /* get most everything */ + memcpy( &rw.verts[ v ], dv[ v ], sizeof( bspDrawVert_t ) ); + + /* fix colors */ + for( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + radVertexLuxel = RAD_VERTEX_LUXEL( i, ds->firstVert + dv[ v ]->color[ 0 ][ 0 ] ); + VectorCopy( radVertexLuxel, rw.verts[ v ].color[ i ] ); + rw.verts[ v ].color[ i ][ 3 ] = dv[ v ]->color[ i ][ 3 ]; + } + } + + /* subdivide into area lights */ + RadSubdivideDiffuseLight( lightmapNum, ds, lm, si, scale, subdivide, qtrue, &rw, cw ); + } + } + } + } + + /* free the mesh */ + FreeMesh( mesh ); +} + + + + +/* +RadLight() +creates unbounced diffuse lights for a given surface +*/ + +void RadLight( int num ) +{ + int lightmapNum; + float scale, subdivide; + int contentFlags, surfaceFlags, compileFlags; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + rawLightmap_t *lm; + shaderInfo_t *si; + clipWork_t cw; + + + /* get drawsurface, lightmap, and shader info */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + lm = info->lm; + si = info->si; + scale = si->bounceScale; + + /* find nodraw bit */ + contentFlags = surfaceFlags = compileFlags = 0; + ApplySurfaceParm( "nodraw", &contentFlags, &surfaceFlags, &compileFlags ); + + /* early outs? */ + if( scale <= 0.0f || (si->compileFlags & C_SKY) || si->autosprite || + (bspShaders[ ds->shaderNum ].contentFlags & contentFlags) || (bspShaders[ ds->shaderNum ].surfaceFlags & surfaceFlags) || + (si->compileFlags & compileFlags) ) + return; + + /* determine how much we need to chop up the surface */ + if( si->lightSubdivide ) + subdivide = si->lightSubdivide; + else + subdivide = diffuseSubdivide; + + /* inc counts */ + numDiffuseSurfaces++; + + /* iterate through styles (this could be more efficient, yes) */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* switch on type */ + if( ds->lightmapStyles[ lightmapNum ] != LS_NONE && ds->lightmapStyles[ lightmapNum ] != LS_UNUSED ) + { + switch( ds->surfaceType ) + { + case MST_PLANAR: + case MST_TRIANGLE_SOUP: + RadLightForTriangles( num, lightmapNum, lm, si, scale, subdivide, &cw ); + break; + + case MST_PATCH: + RadLightForPatch( num, lightmapNum, lm, si, scale, subdivide, &cw ); + break; + + default: + break; + } + } + } +} + + + +/* +RadCreateDiffuseLights() +creates lights for unbounced light on surfaces in the bsp +*/ + +int iterations = 0; + +void RadCreateDiffuseLights( void ) +{ + /* startup */ + Sys_FPrintf( SYS_VRB, "--- RadCreateDiffuseLights ---\n" ); + numDiffuseSurfaces = 0; + numDiffuseLights = 0; + numBrushDiffuseLights = 0; + numTriangleDiffuseLights = 0; + numPatchDiffuseLights = 0; + numAreaLights = 0; + + /* hit every surface (threaded) */ + RunThreadsOnIndividual( numBSPDrawSurfaces, qtrue, RadLight ); + + /* dump the lights generated to a file */ + if( dump ) + { + char dumpName[ 1024 ], ext[ 64 ]; + FILE *file; + light_t *light; + + strcpy( dumpName, source ); + StripExtension( dumpName ); + sprintf( ext, "_bounce_%03d.map", iterations ); + strcat( dumpName, ext ); + file = fopen( dumpName, "wb" ); + Sys_Printf( "Writing %s...\n", dumpName ); + if( file ) + { + for( light = lights; light; light = light->next ) + { + fprintf( file, + "{\n" + "\"classname\" \"light\"\n" + "\"light\" \"%d\"\n" + "\"origin\" \"%.0f %.0f %.0f\"\n" + "\"_color\" \"%.3f %.3f %.3f\"\n" + "}\n", + + (int) light->add, + + light->origin[ 0 ], + light->origin[ 1 ], + light->origin[ 2 ], + + light->color[ 0 ], + light->color[ 1 ], + light->color[ 2 ] ); + } + fclose( file ); + } + } + + /* increment */ + iterations++; + + /* print counts */ + Sys_Printf( "%8d diffuse surfaces\n", numDiffuseSurfaces ); + Sys_FPrintf( SYS_VRB, "%8d total diffuse lights\n", numDiffuseLights ); + Sys_FPrintf( SYS_VRB, "%8d brush diffuse lights\n", numBrushDiffuseLights ); + Sys_FPrintf( SYS_VRB, "%8d patch diffuse lights\n", numPatchDiffuseLights ); + Sys_FPrintf( SYS_VRB, "%8d triangle diffuse lights\n", numTriangleDiffuseLights ); +} + + + + + + diff --git a/tools/quake3/q3map2/light_shadows.c b/tools/quake3/q3map2/light_shadows.c new file mode 100644 index 00000000..d482bbca --- /dev/null +++ b/tools/quake3/q3map2/light_shadows.c @@ -0,0 +1,124 @@ +/* +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#define LIGHT_SHADOWS_C + +#include "light.h" +#include "inout.h" + + + +/* ------------------------------------------------------------------------------- + +ydnar: this code deals with shadow volume bsps + +------------------------------------------------------------------------------- */ + +typedef struct shadowNode_s +{ + vec4_t plane; + int children[ 2 ]; +} +shadowNode_t; + +int numShadowNodes; +shadowNode_t *shadowNodes; + + + +/* +AddShadow() +adds a shadow, returning the index into the shadow list +*/ + + + +/* +MakeShadowFromPoints() +creates a shadow volume from 4 points (the first being the light origin) +*/ + + + +/* +SetupShadows() +sets up the shadow volumes for all lights in the world +*/ + +void SetupShadows( void ) +{ + int i, j, s; + light_t *light; + dleaf_t *leaf; + dsurface_t *ds; + surfaceInfo_t *info; + shaderInfo_t *si; + byte *tested; + + + /* early out for weird cases where there are no lights */ + if( lights == NULL ) + return; + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupShadows ---\n" ); + + /* allocate a surface test list */ + tested = safe_malloc( numDrawSurfaces / 8 + 1 ); + + /* walk the list of lights */ + for( light = lights; light != NULL; light = light->next ) + { + /* do some early out testing */ + if( light->cluster < 0 ) + continue; + + /* clear surfacetest list */ + memset( tested, 0, numDrawSurfaces / 8 + 1 ); + + /* walk the bsp leaves */ + for( i = 0, leaf = dleafs; i < numleafs; i++, leaf++ ) + { + /* in pvs? */ + if( ClusterVisible( light->cluster, leaf->cluster ) == qfalse ) + continue; + + /* walk the surface list for this leaf */ + for( j = 0; j < leaf->numLeafSurfaces; j++ ) + { + /* don't filter a surface more than once */ + s = dleafsurfaces[ leaf->firstLeafSurface + j ]; + if( tested[ s >> 3 ] & (1 << (s & 7)) ) + continue; + tested[ s >> 3 ] |= (1 << (s & 7)); + + /* get surface and info */ + ds = &drawSurfaces[ s ]; + info = &surfaceInfos[ s ]; + si = info->si; + + /* don't create shadow volumes from translucent surfaces */ + if( si->contents & CONTENTS_TRANSLUCENT ) + continue; + } + } + } +} diff --git a/tools/quake3/q3map2/light_trace.c b/tools/quake3/q3map2/light_trace.c new file mode 100644 index 00000000..2a4e7e46 --- /dev/null +++ b/tools/quake3/q3map2/light_trace.c @@ -0,0 +1,1760 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define LIGHT_TRACE_C + + + +/* dependencies */ +#include "q3map2.h" + + +/* dependencies */ +#include "q3map2.h" + + + +#define Vector2Copy( a, b ) ((b)[ 0 ] = (a)[ 0 ], (b)[ 1 ] = (a)[ 1 ]) +#define Vector4Copy( a, b ) ((b)[ 0 ] = (a)[ 0 ], (b)[ 1 ] = (a)[ 1 ], (b)[ 2 ] = (a)[ 2 ], (b)[ 3 ] = (a)[ 3 ]) + +#define MAX_NODE_ITEMS 5 +#define MAX_NODE_TRIANGLES 5 +#define MAX_TRACE_DEPTH 32 +#define MIN_NODE_SIZE 32.0f + +#define GROW_TRACE_INFOS 32768 //% 4096 +#define GROW_TRACE_WINDINGS 65536 //% 32768 +#define GROW_TRACE_TRIANGLES 131072 //% 32768 +#define GROW_TRACE_NODES 16384 //% 16384 +#define GROW_NODE_ITEMS 16 //% 256 + +#define MAX_TW_VERTS 12 + +#define TRACE_ON_EPSILON 0.1f + +#define TRACE_LEAF -1 +#define TRACE_LEAF_SOLID -2 + +typedef struct traceVert_s +{ + vec3_t xyz; + float st[ 2 ]; +} +traceVert_t; + +typedef struct traceInfo_s +{ + shaderInfo_t *si; + int surfaceNum, castShadows; +} +traceInfo_t; + +typedef struct traceWinding_s +{ + vec4_t plane; + int infoNum, numVerts; + traceVert_t v[ MAX_TW_VERTS ]; +} +traceWinding_t; + +typedef struct traceTriangle_s +{ + vec3_t edge1, edge2; + int infoNum; + traceVert_t v[ 3 ]; +} +traceTriangle_t; + +typedef struct traceNode_s +{ + int type; + vec4_t plane; + vec3_t mins, maxs; + int children[ 2 ]; + int numItems, maxItems; + int *items; +} +traceNode_t; + + +int noDrawContentFlags, noDrawSurfaceFlags, noDrawCompileFlags; + +int numTraceInfos = 0, maxTraceInfos = 0, firstTraceInfo = 0; +traceInfo_t *traceInfos = NULL; + +int numTraceWindings = 0, maxTraceWindings = 0, deadWinding = -1; +traceWinding_t *traceWindings = NULL; + +int numTraceTriangles = 0, maxTraceTriangles = 0, deadTriangle = -1; +traceTriangle_t *traceTriangles = NULL; + +int headNodeNum = 0, skyboxNodeNum = 0, maxTraceDepth = 0, numTraceLeafNodes = 0; +int numTraceNodes = 0, maxTraceNodes = 0; +traceNode_t *traceNodes = NULL; + + + +/* ------------------------------------------------------------------------------- + +allocation and list management + +------------------------------------------------------------------------------- */ + +/* +AddTraceInfo() - ydnar +adds a trace info structure to the pool +*/ + +static int AddTraceInfo( traceInfo_t *ti ) +{ + int num; + void *temp; + + + /* find an existing info */ + for( num = firstTraceInfo; num < numTraceInfos; num++ ) + { + if( traceInfos[ num ].si == ti->si && + traceInfos[ num ].surfaceNum == ti->surfaceNum && + traceInfos[ num ].castShadows == ti->castShadows ) + return num; + } + + /* enough space? */ + if( numTraceInfos >= maxTraceInfos ) + { + /* allocate more room */ + maxTraceInfos += GROW_TRACE_INFOS; + temp = safe_malloc( maxTraceInfos * sizeof( *traceInfos ) ); + if( traceInfos != NULL ) + { + memcpy( temp, traceInfos, numTraceInfos * sizeof( *traceInfos ) ); + free( traceInfos ); + } + traceInfos = (traceInfo_t*) temp; + } + + /* add the info */ + memcpy( &traceInfos[ num ], ti, sizeof( *traceInfos ) ); + if( num == numTraceInfos ) + numTraceInfos++; + + /* return the ti number */ + return num; +} + + + +/* +AllocTraceNode() - ydnar +allocates a new trace node +*/ + +static int AllocTraceNode( void ) +{ + traceNode_t *temp; + + + /* enough space? */ + if( numTraceNodes >= maxTraceNodes ) + { + /* reallocate more room */ + maxTraceNodes += GROW_TRACE_NODES; + temp = safe_malloc( maxTraceNodes * sizeof( traceNode_t ) ); + if( traceNodes != NULL ) + { + memcpy( temp, traceNodes, numTraceNodes * sizeof( traceNode_t ) ); + free( traceNodes ); + } + traceNodes = temp; + } + + /* add the node */ + memset( &traceNodes[ numTraceNodes ], 0, sizeof( traceNode_t ) ); + traceNodes[ numTraceNodes ].type = TRACE_LEAF; + ClearBounds( traceNodes[ numTraceNodes ].mins, traceNodes[ numTraceNodes ].maxs ); + numTraceNodes++; + + /* return the count */ + return (numTraceNodes - 1); +} + + + +/* +AddTraceWinding() - ydnar +adds a winding to the raytracing pool +*/ + +static int AddTraceWinding( traceWinding_t *tw ) +{ + int num; + void *temp; + + + /* check for a dead winding */ + if( deadWinding >= 0 && deadWinding < numTraceWindings ) + num = deadWinding; + else + { + /* put winding at the end of the list */ + num = numTraceWindings; + + /* enough space? */ + if( numTraceWindings >= maxTraceWindings ) + { + /* allocate more room */ + maxTraceWindings += GROW_TRACE_WINDINGS; + temp = safe_malloc( maxTraceWindings * sizeof( *traceWindings ) ); + if( traceWindings != NULL ) + { + memcpy( temp, traceWindings, numTraceWindings * sizeof( *traceWindings ) ); + free( traceWindings ); + } + traceWindings = (traceWinding_t*) temp; + } + } + + /* add the winding */ + memcpy( &traceWindings[ num ], tw, sizeof( *traceWindings ) ); + if( num == numTraceWindings ) + numTraceWindings++; + deadWinding = -1; + + /* return the winding number */ + return num; +} + + + +/* +AddTraceTriangle() - ydnar +adds a triangle to the raytracing pool +*/ + +static int AddTraceTriangle( traceTriangle_t *tt ) +{ + int num; + void *temp; + + + /* check for a dead triangle */ + if( deadTriangle >= 0 && deadTriangle < numTraceTriangles ) + num = deadTriangle; + else + { + /* put triangle at the end of the list */ + num = numTraceTriangles; + + /* enough space? */ + if( numTraceTriangles >= maxTraceTriangles ) + { + /* allocate more room */ + maxTraceTriangles += GROW_TRACE_TRIANGLES; + temp = safe_malloc( maxTraceTriangles * sizeof( *traceTriangles ) ); + if( traceTriangles != NULL ) + { + memcpy( temp, traceTriangles, numTraceTriangles * sizeof( *traceTriangles ) ); + free( traceTriangles ); + } + traceTriangles = (traceTriangle_t*) temp; + } + } + + /* find vectors for two edges sharing the first vert */ + VectorSubtract( tt->v[ 1 ].xyz, tt->v[ 0 ].xyz, tt->edge1 ); + VectorSubtract( tt->v[ 2 ].xyz, tt->v[ 0 ].xyz, tt->edge2 ); + + /* add the triangle */ + memcpy( &traceTriangles[ num ], tt, sizeof( *traceTriangles ) ); + if( num == numTraceTriangles ) + numTraceTriangles++; + deadTriangle = -1; + + /* return the triangle number */ + return num; +} + + + +/* +AddItemToTraceNode() - ydnar +adds an item reference (winding or triangle) to a trace node +*/ + +static int AddItemToTraceNode( traceNode_t *node, int num ) +{ + void *temp; + + + /* dummy check */ + if( num < 0 ) + return -1; + + /* enough space? */ + if( node->numItems >= node->maxItems ) + { + /* allocate more room */ + if( node == traceNodes ) + node->maxItems *= 2; + else + node->maxItems += GROW_NODE_ITEMS; + if( node->maxItems <= 0 ) + node->maxItems = GROW_NODE_ITEMS; + temp = safe_malloc( node->maxItems * sizeof( *node->items ) ); + if( node->items != NULL ) + { + memcpy( temp, node->items, node->numItems * sizeof( *node->items ) ); + free( node->items ); + } + node->items = (int*) temp; + } + + /* add the poly */ + node->items[ node->numItems ] = num; + node->numItems++; + + /* return the count */ + return (node->numItems - 1); +} + + + + +/* ------------------------------------------------------------------------------- + +trace node setup + +------------------------------------------------------------------------------- */ + +/* +SetupTraceNodes_r() - ydnar +recursively create the initial trace node structure from the bsp tree +*/ + +static int SetupTraceNodes_r( int bspNodeNum ) +{ + int i, nodeNum, bspLeafNum; + bspPlane_t *plane; + bspNode_t *bspNode; + + + /* get bsp node and plane */ + bspNode = &bspNodes[ bspNodeNum ]; + plane = &bspPlanes[ bspNode->planeNum ]; + + /* allocate a new trace node */ + nodeNum = AllocTraceNode(); + + /* setup trace node */ + traceNodes[ nodeNum ].type = PlaneTypeForNormal( plane->normal ); + VectorCopy( plane->normal, traceNodes[ nodeNum ].plane ); + traceNodes[ nodeNum ].plane[ 3 ] = plane->dist; + + /* setup children */ + for( i = 0; i < 2; i++ ) + { + /* leafnode */ + if( bspNode->children[ i ] < 0 ) + { + bspLeafNum = -bspNode->children[ i ] - 1; + + /* new code */ + traceNodes[ nodeNum ].children[ i ] = AllocTraceNode(); + if( bspLeafs[ bspLeafNum ].cluster == -1 ) + traceNodes[ traceNodes[ nodeNum ].children[ i ] ].type = TRACE_LEAF_SOLID; + } + + /* normal node */ + else + traceNodes[ nodeNum ].children[ i ] = SetupTraceNodes_r( bspNode->children[ i ] ); + } + + /* return node number */ + return nodeNum; +} + + + +/* +ClipTraceWinding() - ydnar +clips a trace winding against a plane into one or two parts +*/ + +#define TW_ON_EPSILON 0.25f + +void ClipTraceWinding( traceWinding_t *tw, vec4_t plane, traceWinding_t *front, traceWinding_t *back ) +{ + int i, j, k; + int sides[ MAX_TW_VERTS ], counts[ 3 ] = { 0, 0, 0 }; + float dists[ MAX_TW_VERTS ]; + float frac; + traceVert_t *a, *b, mid; + + + /* clear front and back */ + front->numVerts = 0; + back->numVerts = 0; + + /* classify points */ + for( i = 0; i < tw->numVerts; i++ ) + { + dists[ i ] = DotProduct( tw->v[ i ].xyz, plane ) - plane[ 3 ]; + if( dists[ i ] < -TW_ON_EPSILON ) + sides[ i ] = SIDE_BACK; + else if( dists[ i ] > TW_ON_EPSILON ) + sides[ i ] = SIDE_FRONT; + else + sides[ i ] = SIDE_ON; + counts[ sides[ i ] ]++; + } + + /* entirely on front? */ + if( counts[ SIDE_BACK ] == 0 ) + memcpy( front, tw, sizeof( *front ) ); + + /* entirely on back? */ + else if( counts[ SIDE_FRONT ] == 0 ) + memcpy( back, tw, sizeof( *back ) ); + + /* straddles the plane */ + else + { + /* setup front and back */ + memcpy( front, tw, sizeof( *front ) ); + front->numVerts = 0; + memcpy( back, tw, sizeof( *back ) ); + back->numVerts = 0; + + /* split the winding */ + for( i = 0; i < tw->numVerts; i++ ) + { + /* radix */ + j = (i + 1) % tw->numVerts; + + /* get verts */ + a = &tw->v[ i ]; + b = &tw->v[ j ]; + + /* handle points on the splitting plane */ + switch( sides[ i ] ) + { + case SIDE_FRONT: + if( front->numVerts >= MAX_TW_VERTS ) + Error( "MAX_TW_VERTS (%d) exceeded", MAX_TW_VERTS ); + front->v[ front->numVerts++ ] = *a; + break; + + case SIDE_BACK: + if( back->numVerts >= MAX_TW_VERTS ) + Error( "MAX_TW_VERTS (%d) exceeded", MAX_TW_VERTS ); + back->v[ back->numVerts++ ] = *a; + break; + + case SIDE_ON: + if( front->numVerts >= MAX_TW_VERTS || back->numVerts >= MAX_TW_VERTS ) + Error( "MAX_TW_VERTS (%d) exceeded", MAX_TW_VERTS ); + front->v[ front->numVerts++ ] = *a; + back->v[ back->numVerts++ ] = *a; + continue; + } + + /* check next point to see if we need to split the edge */ + if( sides[ j ] == SIDE_ON || sides[ j ] == sides[ i ] ) + continue; + + /* check limit */ + if( front->numVerts >= MAX_TW_VERTS || back->numVerts >= MAX_TW_VERTS ) + Error( "MAX_TW_VERTS (%d) exceeded", MAX_TW_VERTS ); + + /* generate a split point */ + frac = dists[ i ] / (dists[ i ] - dists[ j ]); + for( k = 0; k < 3; k++ ) + { + /* minimize fp precision errors */ + if( plane[ k ] == 1.0f ) + mid.xyz[ k ] = plane[ 3 ]; + else if( plane[ k ] == -1.0f ) + mid.xyz[ k ] = -plane[ 3 ]; + else + mid.xyz[ k ] = a->xyz[ k ] + frac * (b->xyz[ k ] - a->xyz[ k ]); + + /* set texture coordinates */ + if( k > 1 ) + continue; + mid.st[ 0 ] = a->st[ 0 ] + frac * (b->st[ 0 ] - a->st[ 0 ]); + mid.st[ 1 ] = a->st[ 1 ] + frac * (b->st[ 1 ] - a->st[ 1 ]); + } + + /* copy midpoint to front and back polygons */ + front->v[ front->numVerts++ ] = mid; + back->v[ back->numVerts++ ] = mid; + } + } +} + + + +/* +FilterPointToTraceNodes_r() - ydnar +debugging tool +*/ + +static int FilterPointToTraceNodes_r( vec3_t pt, int nodeNum ) +{ + float dot; + traceNode_t *node; + + + if( nodeNum < 0 || nodeNum >= numTraceNodes ) + return -1; + + node = &traceNodes[ nodeNum ]; + + if( node->type >= 0 ) + { + dot = DotProduct( pt, node->plane ) - node->plane[ 3 ]; + if( dot > -0.001f ) + FilterPointToTraceNodes_r( pt, node->children[ 0 ] ); + if( dot < 0.001f ) + FilterPointToTraceNodes_r( pt, node->children[ 1 ] ); + return -1; + } + + Sys_Printf( "%d ", nodeNum ); + + return nodeNum; +} + + + +/* +FilterTraceWindingIntoNodes_r() - ydnar +filters a trace winding into the raytracing tree +*/ + +static void FilterTraceWindingIntoNodes_r( traceWinding_t *tw, int nodeNum ) +{ + int num; + vec4_t plane1, plane2, reverse; + traceNode_t *node; + traceWinding_t front, back; + + + /* don't filter if passed a bogus node (solid, etc) */ + if( nodeNum < 0 || nodeNum >= numTraceNodes ) + return; + + /* get node */ + node = &traceNodes[ nodeNum ]; + + /* is this a decision node? */ + if( node->type >= 0 ) + { + /* create winding plane if necessary, filtering out bogus windings as well */ + if( nodeNum == headNodeNum ) + { + if( !PlaneFromPoints( tw->plane, tw->v[ 0 ].xyz, tw->v[ 1 ].xyz, tw->v[ 2 ].xyz ) ) + return; + } + + /* validate the node */ + if( node->children[ 0 ] == 0 || node->children[ 1 ] == 0 ) + Error( "Invalid tracenode: %d", nodeNum ); + + /* get node plane */ + Vector4Copy( node->plane, plane1 ); + + /* get winding plane */ + Vector4Copy( tw->plane, plane2 ); + + /* invert surface plane */ + VectorSubtract( vec3_origin, plane2, reverse ); + reverse[ 3 ] = -plane2[ 3 ]; + + /* front only */ + if( DotProduct( plane1, plane2 ) > 0.999f && fabs( plane1[ 3 ] - plane2[ 3 ] ) < 0.001f ) + { + FilterTraceWindingIntoNodes_r( tw, node->children[ 0 ] ); + return; + } + + /* back only */ + if( DotProduct( plane1, reverse ) > 0.999f && fabs( plane1[ 3 ] - reverse[ 3 ] ) < 0.001f ) + { + FilterTraceWindingIntoNodes_r( tw, node->children[ 1 ] ); + return; + } + + /* clip the winding by node plane */ + ClipTraceWinding( tw, plane1, &front, &back ); + + /* filter by node plane */ + if( front.numVerts >= 3 ) + FilterTraceWindingIntoNodes_r( &front, node->children[ 0 ] ); + if( back.numVerts >= 3 ) + FilterTraceWindingIntoNodes_r( &back, node->children[ 1 ] ); + + /* return to caller */ + return; + } + + /* add winding to leaf node */ + num = AddTraceWinding( tw ); + AddItemToTraceNode( node, num ); +} + + + +/* +SubdivideTraceNode_r() - ydnar +recursively subdivides a tracing node until it meets certain size and complexity criteria +*/ + +static void SubdivideTraceNode_r( int nodeNum, int depth ) +{ + int i, j, count, num, frontNum, backNum, type; + vec3_t size; + float dist; + double average[ 3 ]; + traceNode_t *node, *frontNode, *backNode; + traceWinding_t *tw, front, back; + + + /* dummy check */ + if( nodeNum < 0 || nodeNum >= numTraceNodes ) + return; + + /* get node */ + node = &traceNodes[ nodeNum ]; + + /* runaway recursion check */ + if( depth >= MAX_TRACE_DEPTH ) + { + //% Sys_Printf( "Depth: (%d items)\n", node->numItems ); + numTraceLeafNodes++; + return; + } + depth++; + + /* is this a decision node? */ + if( node->type >= 0 ) + { + /* subdivide children */ + frontNum = node->children[ 0 ]; + backNum = node->children[ 1 ]; + SubdivideTraceNode_r( frontNum, depth ); + SubdivideTraceNode_r( backNum, depth ); + return; + } + + /* bound the node */ + ClearBounds( node->mins, node->maxs ); + VectorClear( average ); + count = 0; + for( i = 0; i < node->numItems; i++ ) + { + /* get winding */ + tw = &traceWindings[ node->items[ i ] ]; + + /* walk its verts */ + for( j = 0; j < tw->numVerts; j++ ) + { + AddPointToBounds( tw->v[ j ].xyz, node->mins, node->maxs ); + average[ 0 ] += tw->v[ j ].xyz[ 0 ]; + average[ 1 ] += tw->v[ j ].xyz[ 1 ]; + average[ 2 ] += tw->v[ j ].xyz[ 2 ]; + count++; + } + } + + /* check triangle limit */ + //% if( node->numItems <= MAX_NODE_ITEMS ) + if( (count - (node->numItems * 2)) < MAX_NODE_TRIANGLES ) + { + //% Sys_Printf( "Limit: (%d triangles)\n", (count - (node->numItems * 2)) ); + numTraceLeafNodes++; + return; + } + + /* the largest dimension of the bounding box will be the split axis */ + VectorSubtract( node->maxs, node->mins, size ); + if( size[ 0 ] >= size[ 1 ] && size[ 0 ] >= size[ 2 ] ) + type = PLANE_X; + else if( size[ 1 ] >= size[ 0 ] && size[ 1 ] >= size[ 2 ] ) + type = PLANE_Y; + else + type = PLANE_Z; + + /* don't split small nodes */ + if( size[ type ] <= MIN_NODE_SIZE ) + { + //% Sys_Printf( "Limit: %f %f %f (%d items)\n", size[ 0 ], size[ 1 ], size[ 2 ], node->numItems ); + numTraceLeafNodes++; + return; + } + + /* set max trace depth */ + if( depth > maxTraceDepth ) + maxTraceDepth = depth; + + /* snap the average */ + dist = floor( average[ type ] / count ); + + /* dummy check it */ + if( dist <= node->mins[ type ] || dist >= node->maxs[ type ] ) + dist = floor( 0.5f * (node->mins[ type ] + node->maxs[ type ]) ); + + /* allocate child nodes */ + frontNum = AllocTraceNode(); + backNum = AllocTraceNode(); + + /* reset pointers */ + node = &traceNodes[ nodeNum ]; + frontNode = &traceNodes[ frontNum ]; + backNode = &traceNodes[ backNum ]; + + /* attach children */ + node->type = type; + node->plane[ type ] = 1.0f; + node->plane[ 3 ] = dist; + node->children[ 0 ] = frontNum; + node->children[ 1 ] = backNum; + + /* setup front node */ + frontNode->maxItems = (node->maxItems >> 1); + frontNode->items = safe_malloc( frontNode->maxItems * sizeof( *frontNode->items ) ); + + /* setup back node */ + backNode->maxItems = (node->maxItems >> 1); + backNode->items = safe_malloc( backNode->maxItems * sizeof( *backNode->items ) ); + + /* filter windings into child nodes */ + for( i = 0; i < node->numItems; i++ ) + { + /* get winding */ + tw = &traceWindings[ node->items[ i ] ]; + + /* clip the winding by the new split plane */ + ClipTraceWinding( tw, node->plane, &front, &back ); + + /* kill the existing winding */ + if( front.numVerts >= 3 || back.numVerts >= 3 ) + deadWinding = node->items[ i ]; + + /* add front winding */ + if( front.numVerts >= 3 ) + { + num = AddTraceWinding( &front ); + AddItemToTraceNode( frontNode, num ); + } + + /* add back winding */ + if( back.numVerts >= 3 ) + { + num = AddTraceWinding( &back ); + AddItemToTraceNode( backNode, num ); + } + } + + /* free original node winding list */ + node->numItems = 0; + node->maxItems = 0; + free( node->items ); + node->items = NULL; + + /* check children */ + if( frontNode->numItems <= 0 ) + { + frontNode->maxItems = 0; + free( frontNode->items ); + frontNode->items = NULL; + } + + if( backNode->numItems <= 0 ) + { + backNode->maxItems = 0; + free( backNode->items ); + backNode->items = NULL; + } + + /* subdivide children */ + SubdivideTraceNode_r( frontNum, depth ); + SubdivideTraceNode_r( backNum, depth ); +} + + + +/* +TriangulateTraceNode_r() +optimizes the tracing data by changing trace windings into triangles +*/ + +static int TriangulateTraceNode_r( int nodeNum ) +{ + int i, j, num, frontNum, backNum, numWindings, *windings; + traceNode_t *node; + traceWinding_t *tw; + traceTriangle_t tt; + + + /* dummy check */ + if( nodeNum < 0 || nodeNum >= numTraceNodes ) + return 0; + + /* get node */ + node = &traceNodes[ nodeNum ]; + + /* is this a decision node? */ + if( node->type >= 0 ) + { + /* triangulate children */ + frontNum = node->children[ 0 ]; + backNum = node->children[ 1 ]; + node->numItems = TriangulateTraceNode_r( frontNum ); + node->numItems += TriangulateTraceNode_r( backNum ); + return node->numItems; + } + + /* empty node? */ + if( node->numItems == 0 ) + { + node->maxItems = 0; + if( node->items != NULL ) + free( node->items ); + return node->numItems; + } + + /* store off winding data */ + numWindings = node->numItems; + windings = node->items; + + /* clear it */ + node->numItems = 0; + node->maxItems = numWindings * 2; + node->items = safe_malloc( node->maxItems * sizeof( tt ) ); + + /* walk winding list */ + for( i = 0; i < numWindings; i++ ) + { + /* get winding */ + tw = &traceWindings[ windings[ i ] ]; + + /* initial setup */ + tt.infoNum = tw->infoNum; + tt.v[ 0 ] = tw->v[ 0 ]; + + /* walk vertex list */ + for( j = 1; j + 1 < tw->numVerts; j++ ) + { + /* set verts */ + tt.v[ 1 ] = tw->v[ j ]; + tt.v[ 2 ] = tw->v[ j + 1 ]; + + /* find vectors for two edges sharing the first vert */ + VectorSubtract( tt.v[ 1 ].xyz, tt.v[ 0 ].xyz, tt.edge1 ); + VectorSubtract( tt.v[ 2 ].xyz, tt.v[ 0 ].xyz, tt.edge2 ); + + /* add it to the node */ + num = AddTraceTriangle( &tt ); + AddItemToTraceNode( node, num ); + } + } + + /* free windings */ + if( windings != NULL ) + free( windings ); + + /* return item count */ + return node->numItems; +} + + + +/* ------------------------------------------------------------------------------- + +shadow casting item setup (triangles, patches, entities) + +------------------------------------------------------------------------------- */ + +/* +PopulateWithBSPModel() - ydnar +filters a bsp model's surfaces into the raytracing tree +*/ + +static void PopulateWithBSPModel( bspModel_t *model, m4x4_t transform ) +{ + int i, j, x, y, pw[ 5 ], r, nodeNum; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + bspDrawVert_t *verts; + int *indexes; + mesh_t srcMesh, *mesh, *subdivided; + traceInfo_t ti; + traceWinding_t tw; + + + /* dummy check */ + if( model == NULL || transform == NULL ) + return; + + /* walk the list of surfaces in this model and fill out the info structs */ + for( i = 0; i < model->numBSPSurfaces; i++ ) + { + /* get surface and info */ + ds = &bspDrawSurfaces[ model->firstBSPSurface + i ]; + info = &surfaceInfos[ model->firstBSPSurface + i ]; + if( info->si == NULL ) + continue; + + /* no shadows */ + if( !info->castShadows ) + continue; + + /* patchshadows? */ + if( ds->surfaceType == MST_PATCH && patchShadows == qfalse ) + continue; + + /* some surfaces in the bsp might have been tagged as nodraw, with a bogus shader */ + if( (bspShaders[ ds->shaderNum ].contentFlags & noDrawContentFlags) || + (bspShaders[ ds->shaderNum ].surfaceFlags & noDrawSurfaceFlags) ) + continue; + + /* translucent surfaces that are neither alphashadow or lightfilter don't cast shadows */ + if( (info->si->compileFlags & C_NODRAW) ) + continue; + if( (info->si->compileFlags & C_TRANSLUCENT) && + !(info->si->compileFlags & C_ALPHASHADOW) && + !(info->si->compileFlags & C_LIGHTFILTER) ) + continue; + + /* setup trace info */ + ti.si = info->si; + ti.castShadows = info->castShadows; + ti.surfaceNum = model->firstBSPBrush + i; + + /* choose which node (normal or skybox) */ + if( info->parentSurfaceNum >= 0 ) + { + nodeNum = skyboxNodeNum; + + /* sky surfaces in portal skies are ignored */ + if( info->si->compileFlags & C_SKY ) + continue; + } + else + nodeNum = headNodeNum; + + /* setup trace winding */ + memset( &tw, 0, sizeof( tw ) ); + tw.infoNum = AddTraceInfo( &ti ); + tw.numVerts = 3; + + /* switch on type */ + switch( ds->surfaceType ) + { + /* handle patches */ + case MST_PATCH: + /* subdivide the surface */ + srcMesh.width = ds->patchWidth; + srcMesh.height = ds->patchHeight; + srcMesh.verts = &bspDrawVerts[ ds->firstVert ]; + //% subdivided = SubdivideMesh( srcMesh, 8, 512 ); + subdivided = SubdivideMesh2( srcMesh, info->patchIterations ); + + /* fit it to the curve and remove colinear verts on rows/columns */ + PutMeshOnCurve( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + + /* set verts */ + verts = mesh->verts; + + /* subdivide each quad to place the models */ + for( y = 0; y < (mesh->height - 1); y++ ) + { + for( x = 0; x < (mesh->width - 1); x++ ) + { + /* set indexes */ + pw[ 0 ] = x + (y * mesh->width); + pw[ 1 ] = x + ((y + 1) * mesh->width); + pw[ 2 ] = x + 1 + ((y + 1) * mesh->width); + pw[ 3 ] = x + 1 + (y * mesh->width); + pw[ 4 ] = x + (y * mesh->width); /* same as pw[ 0 ] */ + + /* set radix */ + r = (x + y) & 1; + + /* make first triangle */ + VectorCopy( verts[ pw[ r + 0 ] ].xyz, tw.v[ 0 ].xyz ); + Vector2Copy( verts[ pw[ r + 0 ] ].st, tw.v[ 0 ].st ); + VectorCopy( verts[ pw[ r + 1 ] ].xyz, tw.v[ 1 ].xyz ); + Vector2Copy( verts[ pw[ r + 1 ] ].st, tw.v[ 1 ].st ); + VectorCopy( verts[ pw[ r + 2 ] ].xyz, tw.v[ 2 ].xyz ); + Vector2Copy( verts[ pw[ r + 2 ] ].st, tw.v[ 2 ].st ); + m4x4_transform_point( transform, tw.v[ 0 ].xyz ); + m4x4_transform_point( transform, tw.v[ 1 ].xyz ); + m4x4_transform_point( transform, tw.v[ 2 ].xyz ); + FilterTraceWindingIntoNodes_r( &tw, nodeNum ); + + /* make second triangle */ + VectorCopy( verts[ pw[ r + 0 ] ].xyz, tw.v[ 0 ].xyz ); + Vector2Copy( verts[ pw[ r + 0 ] ].st, tw.v[ 0 ].st ); + VectorCopy( verts[ pw[ r + 2 ] ].xyz, tw.v[ 1 ].xyz ); + Vector2Copy( verts[ pw[ r + 2 ] ].st, tw.v[ 1 ].st ); + VectorCopy( verts[ pw[ r + 3 ] ].xyz, tw.v[ 2 ].xyz ); + Vector2Copy( verts[ pw[ r + 3 ] ].st, tw.v[ 2 ].st ); + m4x4_transform_point( transform, tw.v[ 0 ].xyz ); + m4x4_transform_point( transform, tw.v[ 1 ].xyz ); + m4x4_transform_point( transform, tw.v[ 2 ].xyz ); + FilterTraceWindingIntoNodes_r( &tw, nodeNum ); + } + } + + /* free the subdivided mesh */ + FreeMesh( mesh ); + break; + + /* handle triangle surfaces */ + case MST_TRIANGLE_SOUP: + case MST_PLANAR: + /* set verts and indexes */ + verts = &bspDrawVerts[ ds->firstVert ]; + indexes = &bspDrawIndexes[ ds->firstIndex ]; + + /* walk the triangle list */ + for( j = 0; j < ds->numIndexes; j += 3 ) + { + VectorCopy( verts[ indexes[ j ] ].xyz, tw.v[ 0 ].xyz ); + Vector2Copy( verts[ indexes[ j ] ].st, tw.v[ 0 ].st ); + VectorCopy( verts[ indexes[ j + 1 ] ].xyz, tw.v[ 1 ].xyz ); + Vector2Copy( verts[ indexes[ j + 1 ] ].st, tw.v[ 1 ].st ); + VectorCopy( verts[ indexes[ j + 2 ] ].xyz, tw.v[ 2 ].xyz ); + Vector2Copy( verts[ indexes[ j + 2 ] ].st, tw.v[ 2 ].st ); + m4x4_transform_point( transform, tw.v[ 0 ].xyz ); + m4x4_transform_point( transform, tw.v[ 1 ].xyz ); + m4x4_transform_point( transform, tw.v[ 2 ].xyz ); + FilterTraceWindingIntoNodes_r( &tw, nodeNum ); + } + break; + + /* other surface types do not cast shadows */ + default: + break; + } + } +} + + + +/* +PopulateWithPicoModel() - ydnar +filters a picomodel's surfaces into the raytracing tree +*/ + +static void PopulateWithPicoModel( int castShadows, picoModel_t *model, m4x4_t transform ) +{ + int i, j, k, numSurfaces, numIndexes; + picoSurface_t *surface; + picoShader_t *shader; + picoVec_t *xyz, *st; + picoIndex_t *indexes; + traceInfo_t ti; + traceWinding_t tw; + + + /* dummy check */ + if( model == NULL || transform == NULL ) + return; + + /* get info */ + numSurfaces = PicoGetModelNumSurfaces( model ); + + /* walk the list of surfaces in this model and fill out the info structs */ + for( i = 0; i < numSurfaces; i++ ) + { + /* get surface */ + surface = PicoGetModelSurface( model, i ); + if( surface == NULL ) + continue; + + /* only handle triangle surfaces initially (fixme: support patches) */ + if( PicoGetSurfaceType( surface ) != PICO_TRIANGLES ) + continue; + + /* get shader (fixme: support shader remapping) */ + shader = PicoGetSurfaceShader( surface ); + if( shader == NULL ) + continue; + ti.si = ShaderInfoForShader( PicoGetShaderName( shader ) ); + if( ti.si == NULL ) + continue; + + /* translucent surfaces that are neither alphashadow or lightfilter don't cast shadows */ + if( (ti.si->compileFlags & C_NODRAW) ) + continue; + if( (ti.si->compileFlags & C_TRANSLUCENT) && + !(ti.si->compileFlags & C_ALPHASHADOW) && + !(ti.si->compileFlags & C_LIGHTFILTER) ) + continue; + + /* setup trace info */ + ti.castShadows = castShadows; + ti.surfaceNum = -1; + + /* setup trace winding */ + memset( &tw, 0, sizeof( tw ) ); + tw.infoNum = AddTraceInfo( &ti ); + tw.numVerts = 3; + + /* get info */ + numIndexes = PicoGetSurfaceNumIndexes( surface ); + indexes = PicoGetSurfaceIndexes( surface, 0 ); + + /* walk the triangle list */ + for( j = 0; j < numIndexes; j += 3, indexes += 3 ) + { + for( k = 0; k < 3; k++ ) + { + xyz = PicoGetSurfaceXYZ( surface, indexes[ k ] ); + st = PicoGetSurfaceST( surface, 0, indexes[ k ] ); + VectorCopy( xyz, tw.v[ k ].xyz ); + Vector2Copy( st, tw.v[ k ].st ); + m4x4_transform_point( transform, tw.v[ k ].xyz ); + } + FilterTraceWindingIntoNodes_r( &tw, headNodeNum ); + } + } +} + + + +/* +PopulateTraceNodes() - ydnar +fills the raytracing tree with world and entity occluders +*/ + +static void PopulateTraceNodes( void ) +{ + int i, m, frame, castShadows; + float temp; + entity_t *e; + const char *value; + picoModel_t *model; + vec3_t origin, scale, angles; + m4x4_t transform; + + + /* add worldspawn triangles */ + m4x4_identity( transform ); + PopulateWithBSPModel( &bspModels[ 0 ], transform ); + + /* walk each entity list */ + for( i = 1; i < numEntities; i++ ) + { + /* get entity */ + e = &entities[ i ]; + + /* get shadow flags */ + castShadows = ENTITY_CAST_SHADOWS; + GetEntityShadowFlags( e, NULL, &castShadows, NULL ); + + /* early out? */ + if( !castShadows ) + continue; + + /* get entity origin */ + GetVectorForKey( e, "origin", origin ); + + /* get scale */ + scale[ 0 ] = scale[ 1 ] = scale[ 2 ] = 1.0f; + temp = FloatForKey( e, "modelscale" ); + if( temp != 0.0f ) + scale[ 0 ] = scale[ 1 ] = scale[ 2 ] = temp; + value = ValueForKey( e, "modelscale_vec" ); + if( value[ 0 ] != '\0' ) + sscanf( value, "%f %f %f", &scale[ 0 ], &scale[ 1 ], &scale[ 2 ] ); + + /* get "angle" (yaw) or "angles" (pitch yaw roll) */ + angles[ 0 ] = angles[ 1 ] = angles[ 2 ] = 0.0f; + angles[ 2 ] = FloatForKey( e, "angle" ); + value = ValueForKey( e, "angles" ); + if( value[ 0 ] != '\0' ) + sscanf( value, "%f %f %f", &angles[ 1 ], &angles[ 2 ], &angles[ 0 ] ); + + /* set transform matrix (thanks spog) */ + m4x4_identity( transform ); + m4x4_pivoted_transform_by_vec3( transform, origin, angles, eXYZ, scale, vec3_origin ); + + /* hack: Stable-1_2 and trunk have differing row/column major matrix order + this transpose is necessary with Stable-1_2 + uncomment the following line with old m4x4_t (non 1.3/spog_branch) code */ + //% m4x4_transpose( transform ); + + /* get model */ + value = ValueForKey( e, "model" ); + + /* switch on model type */ + switch( value[ 0 ] ) + { + /* no model */ + case '\0': + break; + + /* bsp model */ + case '*': + m = atoi( &value[ 1 ] ); + if( m <= 0 || m >= numBSPModels ) + continue; + PopulateWithBSPModel( &bspModels[ m ], transform ); + break; + + /* external model */ + default: + frame = IntForKey( e, "_frame" ); + model = LoadModel( (char*) value, frame ); + if( model == NULL ) + continue; + PopulateWithPicoModel( castShadows, model, transform ); + continue; + } + + /* get model2 */ + value = ValueForKey( e, "model2" ); + + /* switch on model type */ + switch( value[ 0 ] ) + { + /* no model */ + case '\0': + break; + + /* bsp model */ + case '*': + m = atoi( &value[ 1 ] ); + if( m <= 0 || m >= numBSPModels ) + continue; + PopulateWithBSPModel( &bspModels[ m ], transform ); + break; + + /* external model */ + default: + frame = IntForKey( e, "_frame2" ); + model = LoadModel( (char*) value, frame ); + if( model == NULL ) + continue; + PopulateWithPicoModel( castShadows, model, transform ); + continue; + } + } +} + + + + +/* ------------------------------------------------------------------------------- + +trace initialization + +------------------------------------------------------------------------------- */ + +/* +SetupTraceNodes() - ydnar +creates a balanced bsp with axis-aligned splits for efficient raytracing +*/ + +void SetupTraceNodes( void ) +{ + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupTraceNodes ---\n" ); + + /* find nodraw bit */ + noDrawContentFlags = noDrawSurfaceFlags = noDrawCompileFlags = 0; + ApplySurfaceParm( "nodraw", &noDrawContentFlags, &noDrawSurfaceFlags, &noDrawCompileFlags ); + + /* create the baseline raytracing tree from the bsp tree */ + headNodeNum = SetupTraceNodes_r( 0 ); + + /* create outside node for skybox surfaces */ + skyboxNodeNum = AllocTraceNode(); + + /* populate the tree with triangles from the world and shadow casting entities */ + PopulateTraceNodes(); + + /* create the raytracing bsp */ + if( loMem == qfalse ) + { + SubdivideTraceNode_r( headNodeNum, 0 ); + SubdivideTraceNode_r( skyboxNodeNum, 0 ); + } + + /* create triangles from the trace windings */ + TriangulateTraceNode_r( headNodeNum ); + TriangulateTraceNode_r( skyboxNodeNum ); + + /* emit some stats */ + //% Sys_FPrintf( SYS_VRB, "%9d original triangles\n", numOriginalTriangles ); + Sys_FPrintf( SYS_VRB, "%9d trace windings (%.2fMB)\n", numTraceWindings, (float) (numTraceWindings * sizeof( *traceWindings )) / (1024.0f * 1024.0f) ); + Sys_FPrintf( SYS_VRB, "%9d trace triangles (%.2fMB)\n", numTraceTriangles, (float) (numTraceTriangles * sizeof( *traceTriangles )) / (1024.0f * 1024.0f) ); + Sys_FPrintf( SYS_VRB, "%9d trace nodes (%.2fMB)\n", numTraceNodes, (float) (numTraceNodes * sizeof( *traceNodes )) / (1024.0f * 1024.0f) ); + Sys_FPrintf( SYS_VRB, "%9d leaf nodes (%.2fMB)\n", numTraceLeafNodes, (float) (numTraceLeafNodes * sizeof( *traceNodes )) / (1024.0f * 1024.0f) ); + //% Sys_FPrintf( SYS_VRB, "%9d average triangles per leaf node\n", numTraceTriangles / numTraceLeafNodes ); + Sys_FPrintf( SYS_VRB, "%9d average windings per leaf node\n", numTraceWindings / (numTraceLeafNodes + 1) ); + Sys_FPrintf( SYS_VRB, "%9d max trace depth\n", maxTraceDepth ); + + /* free trace windings */ + free( traceWindings ); + numTraceWindings = 0; + maxTraceWindings = 0; + deadWinding = -1; + + /* debug code: write out trace triangles to an alias obj file */ + #if 0 + { + int i, j; + FILE *file; + char filename[ 1024 ]; + traceWinding_t *tw; + + + /* open the file */ + strcpy( filename, source ); + StripExtension( filename ); + strcat( filename, ".lin" ); + Sys_Printf( "Opening light trace file %s...\n", filename ); + file = fopen( filename, "w" ); + if( file == NULL ) + Error( "Error opening %s for writing", filename ); + + /* walk node list */ + for( i = 0; i < numTraceWindings; i++ ) + { + tw = &traceWindings[ i ]; + for( j = 0; j < tw->numVerts + 1; j++ ) + fprintf( file, "%f %f %f\n", + tw->v[ j % tw->numVerts ].xyz[ 0 ], tw->v[ j % tw->numVerts ].xyz[ 1 ], tw->v[ j % tw->numVerts ].xyz[ 2 ] ); + } + + /* close it */ + fclose( file ); + } + #endif +} + + + +/* ------------------------------------------------------------------------------- + +raytracer + +------------------------------------------------------------------------------- */ + +/* +TraceTriangle() +based on code written by william 'spog' joseph +based on code originally written by tomas moller and ben trumbore, journal of graphics tools, 2(1):21-28, 1997 +*/ + +#define BARY_EPSILON 0.01f +#define ASLF_EPSILON 0.0001f /* so to not get double shadows */ +#define COPLANAR_EPSILON 0.25f //% 0.000001f +#define NEAR_SHADOW_EPSILON 1.5f //% 1.25f +#define SELF_SHADOW_EPSILON 0.5f + +qboolean TraceTriangle( traceInfo_t *ti, traceTriangle_t *tt, trace_t *trace ) +{ + int i; + float tvec[ 3 ], pvec[ 3 ], qvec[ 3 ]; + float det, invDet, depth; + float u, v, w, s, t; + int is, it; + byte *pixel; + float shadow; + shaderInfo_t *si; + + + /* don't double-trace against sky */ + si = ti->si; + if( trace->compileFlags & si->compileFlags & C_SKY ) + return qfalse; + + /* receive shadows from worldspawn group only */ + if( trace->recvShadows == 1 ) + { + if( ti->castShadows != 1 ) + return qfalse; + } + + /* receive shadows from same group and worldspawn group */ + else if( trace->recvShadows > 1 ) + { + if( ti->castShadows != 1 && abs( ti->castShadows ) != abs( trace->recvShadows ) ) + return qfalse; + //% Sys_Printf( "%d:%d ", tt->castShadows, trace->recvShadows ); + } + + /* receive shadows from the same group only (< 0) */ + else + { + if( abs( ti->castShadows ) != abs( trace->recvShadows ) ) + return qfalse; + } + + /* begin calculating determinant - also used to calculate u parameter */ + CrossProduct( trace->direction, tt->edge2, pvec ); + + /* if determinant is near zero, trace lies in plane of triangle */ + det = DotProduct( tt->edge1, pvec ); + + /* the non-culling branch */ + if( fabs( det ) < COPLANAR_EPSILON ) + return qfalse; + invDet = 1.0f / det; + + /* calculate distance from first vertex to ray origin */ + VectorSubtract( trace->origin, tt->v[ 0 ].xyz, tvec ); + + /* calculate u parameter and test bounds */ + u = DotProduct( tvec, pvec ) * invDet; + if( u < -BARY_EPSILON || u > (1.0f + BARY_EPSILON) ) + return qfalse; + + /* prepare to test v parameter */ + CrossProduct( tvec, tt->edge1, qvec ); + + /* calculate v parameter and test bounds */ + v = DotProduct( trace->direction, qvec ) * invDet; + if( v < -BARY_EPSILON || (u + v) > (1.0f + BARY_EPSILON) ) + return qfalse; + + /* calculate t (depth) */ + depth = DotProduct( tt->edge2, qvec ) * invDet; + if( depth <= trace->inhibitRadius || depth >= trace->distance ) + return qfalse; + + /* if hitpoint is really close to trace origin (sample point), then check for self-shadowing */ + if( depth <= SELF_SHADOW_EPSILON ) + { + /* don't self-shadow */ + for( i = 0; i < trace->numSurfaces; i++ ) + { + if( ti->surfaceNum == trace->surfaces[ i ] ) + return qfalse; + } + } + + /* stack compile flags */ + trace->compileFlags |= si->compileFlags; + + /* don't trace against sky */ + if( si->compileFlags & C_SKY ) + return qfalse; + + /* most surfaces are completely opaque */ + if( !(si->compileFlags & (C_ALPHASHADOW | C_LIGHTFILTER)) || + si->lightImage == NULL || si->lightImage->pixels == NULL ) + { + VectorMA( trace->origin, depth, trace->direction, trace->hit ); + VectorClear( trace->color ); + trace->opaque = qtrue; + return qtrue; + } + + /* try to avoid double shadows near triangle seams */ + if( u < -ASLF_EPSILON || u > (1.0f + ASLF_EPSILON) || + v < -ASLF_EPSILON || (u + v) > (1.0f + ASLF_EPSILON) ) + return qfalse; + + /* calculate w parameter */ + w = 1.0f - (u + v); + + /* calculate st from uvw (barycentric) coordinates */ + s = w * tt->v[ 0 ].st[ 0 ] + u * tt->v[ 1 ].st[ 0 ] + v * tt->v[ 2 ].st[ 0 ]; + t = w * tt->v[ 0 ].st[ 1 ] + u * tt->v[ 1 ].st[ 1 ] + v * tt->v[ 2 ].st[ 1 ]; + s = s - floor( s ); + t = t - floor( t ); + is = s * si->lightImage->width; + it = t * si->lightImage->height; + + /* get pixel */ + pixel = si->lightImage->pixels + 4 * (it * si->lightImage->width + is); + + /* ydnar: color filter */ + if( si->compileFlags & C_LIGHTFILTER ) + { + /* filter by texture color */ + trace->color[ 0 ] *= ((1.0f / 255.0f) * pixel[ 0 ]); + trace->color[ 1 ] *= ((1.0f / 255.0f) * pixel[ 1 ]); + trace->color[ 2 ] *= ((1.0f / 255.0f) * pixel[ 2 ]); + } + + /* ydnar: alpha filter */ + if( si->compileFlags & C_ALPHASHADOW ) + { + /* filter by inverse texture alpha */ + shadow = (1.0f / 255.0f) * (255 - pixel[ 3 ]); + trace->color[ 0 ] *= shadow; + trace->color[ 1 ] *= shadow; + trace->color[ 2 ] *= shadow; + } + + /* check filter for opaque */ + if( trace->color[ 0 ] <= 0.001f && trace->color[ 1 ] <= 0.001f && trace->color[ 2 ] <= 0.001f ) + { + VectorMA( trace->origin, depth, trace->direction, trace->hit ); + trace->opaque = qtrue; + return qtrue; + } + + /* continue tracing */ + return qfalse; +} + + + +/* +TraceWinding() - ydnar +temporary hack +*/ + +qboolean TraceWinding( traceWinding_t *tw, trace_t *trace ) +{ + int i; + traceTriangle_t tt; + + + /* initial setup */ + tt.infoNum = tw->infoNum; + tt.v[ 0 ] = tw->v[ 0 ]; + + /* walk vertex list */ + for( i = 1; i + 1 < tw->numVerts; i++ ) + { + /* set verts */ + tt.v[ 1 ] = tw->v[ i ]; + tt.v[ 2 ] = tw->v[ i + 1 ]; + + /* find vectors for two edges sharing the first vert */ + VectorSubtract( tt.v[ 1 ].xyz, tt.v[ 0 ].xyz, tt.edge1 ); + VectorSubtract( tt.v[ 2 ].xyz, tt.v[ 0 ].xyz, tt.edge2 ); + + /* trace it */ + if( TraceTriangle( &traceInfos[ tt.infoNum ], &tt, trace ) ) + return qtrue; + } + + /* done */ + return qfalse; +} + + + + +/* +TraceLine_r() +returns qtrue if something is hit and tracing can stop +*/ + +static qboolean TraceLine_r( int nodeNum, vec3_t origin, vec3_t end, trace_t *trace ) +{ + traceNode_t *node; + int side; + float front, back, frac; + vec3_t mid; + qboolean r; + + + /* bogus node number means solid, end tracing unless testing all */ + if( nodeNum < 0 ) + { + VectorCopy( origin, trace->hit ); + trace->passSolid = qtrue; + return qtrue; + } + + /* get node */ + node = &traceNodes[ nodeNum ]; + + /* solid? */ + if( node->type == TRACE_LEAF_SOLID ) + { + VectorCopy( origin, trace->hit ); + trace->passSolid = qtrue; + return qtrue; + } + + /* leafnode? */ + if( node->type < 0 ) + { + /* note leaf and return */ + if( node->numItems > 0 && trace->numTestNodes < MAX_TRACE_TEST_NODES ) + trace->testNodes[ trace->numTestNodes++ ] = nodeNum; + return qfalse; + } + + /* ydnar 2003-09-07: don't test branches of the bsp with nothing in them when testall is enabled */ + if( trace->testAll && node->numItems == 0 ) + return qfalse; + + /* classify beginning and end points */ + switch( node->type ) + { + case PLANE_X: + front = origin[ 0 ] - node->plane[ 3 ]; + back = end[ 0 ] - node->plane[ 3 ]; + break; + + case PLANE_Y: + front = origin[ 1 ] - node->plane[ 3 ]; + back = end[ 1 ] - node->plane[ 3 ]; + break; + + case PLANE_Z: + front = origin[ 2 ] - node->plane[ 3 ]; + back = end[ 2 ] - node->plane[ 3 ]; + break; + + default: + front = DotProduct( origin, node->plane ) - node->plane[ 3 ]; + back = DotProduct( end, node->plane ) - node->plane[ 3 ]; + break; + } + + /* entirely in front side? */ + if( front >= -TRACE_ON_EPSILON && back >= -TRACE_ON_EPSILON ) + return TraceLine_r( node->children[ 0 ], origin, end, trace ); + + /* entirely on back side? */ + if( front < TRACE_ON_EPSILON && back < TRACE_ON_EPSILON ) + return TraceLine_r( node->children[ 1 ], origin, end, trace ); + + /* select side */ + side = front < 0; + + /* calculate intercept point */ + frac = front / (front - back); + mid[ 0 ] = origin[ 0 ] + (end[ 0 ] - origin[ 0 ]) * frac; + mid[ 1 ] = origin[ 1 ] + (end[ 1 ] - origin[ 1 ]) * frac; + mid[ 2 ] = origin[ 2 ] + (end[ 2 ] - origin[ 2 ]) * frac; + + /* fixme: check inhibit radius, then solid nodes and ignore */ + + /* set trace hit here */ + //% VectorCopy( mid, trace->hit ); + + /* trace first side */ + r = TraceLine_r( node->children[ side ], origin, mid, trace ); + if( r ) + return r; + + /* trace other side */ + return TraceLine_r( node->children[ !side ], mid, end, trace ); +} + + + +/* +TraceLine() - ydnar +rewrote this function a bit :) +*/ + +void TraceLine( trace_t *trace ) +{ + int i, j; + traceNode_t *node; + traceTriangle_t *tt; + traceInfo_t *ti; + + + /* setup output (note: this code assumes the input data is completely filled out) */ + trace->passSolid = qfalse; + trace->opaque = qfalse; + trace->compileFlags = 0; + trace->numTestNodes = 0; + + /* early outs */ + if( !trace->recvShadows || !trace->testOcclusion || trace->distance <= 0.00001f ) + return; + + /* trace through nodes */ + TraceLine_r( headNodeNum, trace->origin, trace->end, trace ); + if( trace->passSolid && !trace->testAll ) + { + trace->opaque = qtrue; + return; + } + + /* skip surfaces? */ + if( noSurfaces ) + return; + + /* testall means trace through sky */ + if( trace->testAll && trace->numTestNodes < MAX_TRACE_TEST_NODES && + trace->compileFlags & C_SKY && + (trace->numSurfaces == 0 || surfaceInfos[ trace->surfaces[ 0 ] ].childSurfaceNum < 0) ) + { + //% trace->testNodes[ trace->numTestNodes++ ] = skyboxNodeNum; + TraceLine_r( skyboxNodeNum, trace->origin, trace->end, trace ); + } + + /* walk node list */ + for( i = 0; i < trace->numTestNodes; i++ ) + { + /* get node */ + node = &traceNodes[ trace->testNodes[ i ] ]; + + /* walk node item list */ + for( j = 0; j < node->numItems; j++ ) + { + tt = &traceTriangles[ node->items[ j ] ]; + ti = &traceInfos[ tt->infoNum ]; + if( TraceTriangle( ti, tt, trace ) ) + return; + //% if( TraceWinding( &traceWindings[ node->items[ j ] ], trace ) ) + //% return; + } + } +} + + + +/* +SetupTrace() - ydnar +sets up certain trace values +*/ + +float SetupTrace( trace_t *trace ) +{ + VectorSubtract( trace->end, trace->origin, trace->displacement ); + trace->distance = VectorNormalize( trace->displacement, trace->direction ); + VectorCopy( trace->origin, trace->hit ); + return trace->distance; +} diff --git a/tools/quake3/q3map2/light_ydnar.c b/tools/quake3/q3map2/light_ydnar.c new file mode 100644 index 00000000..4f629492 --- /dev/null +++ b/tools/quake3/q3map2/light_ydnar.c @@ -0,0 +1,4087 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define LIGHT_YDNAR_C + + + +/* dependencies */ +#include "q3map2.h" + + + + +/* +ColorToBytes() +ydnar: moved to here 2001-02-04 +*/ + +void ColorToBytes( const float *color, byte *colorBytes, float scale ) +{ + int i; + float max, gamma; + vec3_t sample; + float inv, dif; + + + /* ydnar: scaling necessary for simulating r_overbrightBits on external lightmaps */ + if( scale <= 0.0f ) + scale = 1.0f; + + /* make a local copy */ + VectorScale( color, scale, sample ); + + /* muck with it */ + gamma = 1.0f / lightmapGamma; + for( i = 0; i < 3; i++ ) + { + /* handle negative light */ + if( sample[ i ] < 0.0f ) + { + sample[ i ] = 0.0f; + continue; + } + + /* gamma */ + sample[ i ] = pow( sample[ i ] / 255.0f, gamma ) * 255.0f; + } + + if (lightmapExposure == 1) + { + /* clamp with color normalization */ + max = sample[ 0 ]; + if( sample[ 1 ] > max ) + max = sample[ 1 ]; + if( sample[ 2 ] > max ) + max = sample[ 2 ]; + if( max > 255.0f ) + VectorScale( sample, (255.0f / max), sample ); + } + else + { + if (lightmapExposure==0) + { + lightmapExposure=1.0f; + } + inv=1.f/lightmapExposure; + //Exposure + + max = sample[ 0 ]; + if( sample[ 1 ] > max ) + max = sample[ 1 ]; + if( sample[ 2 ] > max ) + max = sample[ 2 ]; + + dif = (1- exp(-max * inv) ) * 255; + + if (max >0) + { + dif = dif / max; + } + else + { + dif = 0; + } + + for (i=0;i<3;i++) + { + sample[i]*=dif; + } + } + + + /* compensate for ingame overbrighting/bitshifting */ + VectorScale( sample, (1.0f / lightmapCompensate), sample ); + + /* store it off */ + colorBytes[ 0 ] = sample[ 0 ]; + colorBytes[ 1 ] = sample[ 1 ]; + colorBytes[ 2 ] = sample[ 2 ]; +} + + + +/* ------------------------------------------------------------------------------- + +this section deals with phong shading (normal interpolation across brush faces) + +------------------------------------------------------------------------------- */ + +/* +SmoothNormals() +smooths together coincident vertex normals across the bsp +*/ + +#define MAX_SAMPLES 256 +#define THETA_EPSILON 0.000001 +#define EQUAL_NORMAL_EPSILON 0.01 + +void SmoothNormals( void ) +{ + int i, j, k, f, cs, numVerts, numVotes, fOld, start; + float shadeAngle, defaultShadeAngle, maxShadeAngle, dot, testAngle; + bspDrawSurface_t *ds; + shaderInfo_t *si; + float *shadeAngles; + byte *smoothed; + vec3_t average, diff; + int indexes[ MAX_SAMPLES ]; + vec3_t votes[ MAX_SAMPLES ]; + + + /* allocate shade angle table */ + shadeAngles = safe_malloc( numBSPDrawVerts * sizeof( float ) ); + memset( shadeAngles, 0, numBSPDrawVerts * sizeof( float ) ); + + /* allocate smoothed table */ + cs = (numBSPDrawVerts / 8) + 1; + smoothed = safe_malloc( cs ); + memset( smoothed, 0, cs ); + + /* set default shade angle */ + defaultShadeAngle = DEG2RAD( shadeAngleDegrees ); + maxShadeAngle = 0; + + /* run through every surface and flag verts belonging to non-lightmapped surfaces + and set per-vertex smoothing angle */ + for( i = 0; i < numBSPDrawSurfaces; i++ ) + { + /* get drawsurf */ + ds = &bspDrawSurfaces[ i ]; + + /* get shader for shade angle */ + si = surfaceInfos[ i ].si; + if( si->shadeAngleDegrees ) + shadeAngle = DEG2RAD( si->shadeAngleDegrees ); + else + shadeAngle = defaultShadeAngle; + if( shadeAngle > maxShadeAngle ) + maxShadeAngle = shadeAngle; + + /* flag its verts */ + for( j = 0; j < ds->numVerts; j++ ) + { + f = ds->firstVert + j; + shadeAngles[ f ] = shadeAngle; + if( ds->surfaceType == MST_TRIANGLE_SOUP ) + smoothed[ f >> 3 ] |= (1 << (f & 7)); + } + + /* ydnar: optional force-to-trisoup */ + if( trisoup && ds->surfaceType == MST_PLANAR ) + { + ds->surfaceType = MST_TRIANGLE_SOUP; + ds->lightmapNum[ 0 ] = -3; + } + } + + /* bail if no surfaces have a shade angle */ + if( maxShadeAngle == 0 ) + { + free( shadeAngles ); + free( smoothed ); + return; + } + + /* init pacifier */ + fOld = -1; + start = I_FloatTime(); + + /* go through the list of vertexes */ + for( i = 0; i < numBSPDrawVerts; i++ ) + { + /* print pacifier */ + f = 10 * i / numBSPDrawVerts; + if( f != fOld ) + { + fOld = f; + Sys_Printf( "%i...", f ); + } + + /* already smoothed? */ + if( smoothed[ i >> 3 ] & (1 << (i & 7)) ) + continue; + + /* clear */ + VectorClear( average ); + numVerts = 0; + numVotes = 0; + + /* build a table of coincident vertexes */ + for( j = i; j < numBSPDrawVerts && numVerts < MAX_SAMPLES; j++ ) + { + /* already smoothed? */ + if( smoothed[ j >> 3 ] & (1 << (j & 7)) ) + continue; + + /* test vertexes */ + if( VectorCompare( yDrawVerts[ i ].xyz, yDrawVerts[ j ].xyz ) == qfalse ) + continue; + + /* use smallest shade angle */ + shadeAngle = (shadeAngles[ i ] < shadeAngles[ j ] ? shadeAngles[ i ] : shadeAngles[ j ]); + + /* check shade angle */ + dot = DotProduct( bspDrawVerts[ i ].normal, bspDrawVerts[ j ].normal ); + if( dot > 1.0 ) + dot = 1.0; + else if( dot < -1.0 ) + dot = -1.0; + testAngle = acos( dot ) + THETA_EPSILON; + if( testAngle >= shadeAngle ) + { + //Sys_Printf( "F(%3.3f >= %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) ); + continue; + } + //Sys_Printf( "P(%3.3f < %3.3f) ", RAD2DEG( testAngle ), RAD2DEG( shadeAngle ) ); + + /* add to the list */ + indexes[ numVerts++ ] = j; + + /* flag vertex */ + smoothed[ j >> 3 ] |= (1 << (j & 7)); + + /* see if this normal has already been voted */ + for( k = 0; k < numVotes; k++ ) + { + VectorSubtract( bspDrawVerts[ j ].normal, votes[ k ], diff ); + if( fabs( diff[ 0 ] ) < EQUAL_NORMAL_EPSILON && + fabs( diff[ 1 ] ) < EQUAL_NORMAL_EPSILON && + fabs( diff[ 2 ] ) < EQUAL_NORMAL_EPSILON ) + break; + } + + /* add a new vote? */ + if( k == numVotes && numVotes < MAX_SAMPLES ) + { + VectorAdd( average, bspDrawVerts[ j ].normal, average ); + VectorCopy( bspDrawVerts[ j ].normal, votes[ numVotes ] ); + numVotes++; + } + } + + /* don't average for less than 2 verts */ + if( numVerts < 2 ) + continue; + + /* average normal */ + if( VectorNormalize( average, average ) > 0 ) + { + /* smooth */ + for( j = 0; j < numVerts; j++ ) + VectorCopy( average, yDrawVerts[ indexes[ j ] ].normal ); + } + } + + /* free the tables */ + free( shadeAngles ); + free( smoothed ); + + /* print time */ + Sys_Printf( " (%i)\n", (int) (I_FloatTime() - start) ); +} + + + +/* ------------------------------------------------------------------------------- + +this section deals with phong shaded lightmap tracing + +------------------------------------------------------------------------------- */ + +/* 9th rewrite (recursive subdivision of a lightmap triangle) */ + +/* +CalcTangentVectors() +calculates the st tangent vectors for normalmapping +*/ + +static qboolean CalcTangentVectors( int numVerts, bspDrawVert_t **dv, vec3_t *stv, vec3_t *ttv ) +{ + int i; + float bb, s, t; + vec3_t bary; + + + /* calculate barycentric basis for the triangle */ + bb = (dv[ 1 ]->st[ 0 ] - dv[ 0 ]->st[ 0 ]) * (dv[ 2 ]->st[ 1 ] - dv[ 0 ]->st[ 1 ]) - (dv[ 2 ]->st[ 0 ] - dv[ 0 ]->st[ 0 ]) * (dv[ 1 ]->st[ 1 ] - dv[ 0 ]->st[ 1 ]); + if( fabs( bb ) < 0.00000001f ) + return qfalse; + + /* do each vertex */ + for( i = 0; i < numVerts; i++ ) + { + /* calculate s tangent vector */ + s = dv[ i ]->st[ 0 ] + 10.0f; + t = dv[ i ]->st[ 1 ]; + bary[ 0 ] = ((dv[ 1 ]->st[ 0 ] - s) * (dv[ 2 ]->st[ 1 ] - t) - (dv[ 2 ]->st[ 0 ] - s) * (dv[ 1 ]->st[ 1 ] - t)) / bb; + bary[ 1 ] = ((dv[ 2 ]->st[ 0 ] - s) * (dv[ 0 ]->st[ 1 ] - t) - (dv[ 0 ]->st[ 0 ] - s) * (dv[ 2 ]->st[ 1 ] - t)) / bb; + bary[ 2 ] = ((dv[ 0 ]->st[ 0 ] - s) * (dv[ 1 ]->st[ 1 ] - t) - (dv[ 1 ]->st[ 0 ] - s) * (dv[ 0 ]->st[ 1 ] - t)) / bb; + + stv[ i ][ 0 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 0 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 0 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 0 ]; + stv[ i ][ 1 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 1 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 1 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 1 ]; + stv[ i ][ 2 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 2 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 2 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 2 ]; + + VectorSubtract( stv[ i ], dv[ i ]->xyz, stv[ i ] ); + VectorNormalize( stv[ i ], stv[ i ] ); + + /* calculate t tangent vector */ + s = dv[ i ]->st[ 0 ]; + t = dv[ i ]->st[ 1 ] + 10.0f; + bary[ 0 ] = ((dv[ 1 ]->st[ 0 ] - s) * (dv[ 2 ]->st[ 1 ] - t) - (dv[ 2 ]->st[ 0 ] - s) * (dv[ 1 ]->st[ 1 ] - t)) / bb; + bary[ 1 ] = ((dv[ 2 ]->st[ 0 ] - s) * (dv[ 0 ]->st[ 1 ] - t) - (dv[ 0 ]->st[ 0 ] - s) * (dv[ 2 ]->st[ 1 ] - t)) / bb; + bary[ 2 ] = ((dv[ 0 ]->st[ 0 ] - s) * (dv[ 1 ]->st[ 1 ] - t) - (dv[ 1 ]->st[ 0 ] - s) * (dv[ 0 ]->st[ 1 ] - t)) / bb; + + ttv[ i ][ 0 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 0 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 0 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 0 ]; + ttv[ i ][ 1 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 1 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 1 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 1 ]; + ttv[ i ][ 2 ] = bary[ 0 ] * dv[ 0 ]->xyz[ 2 ] + bary[ 1 ] * dv[ 1 ]->xyz[ 2 ] + bary[ 2 ] * dv[ 2 ]->xyz[ 2 ]; + + VectorSubtract( ttv[ i ], dv[ i ]->xyz, ttv[ i ] ); + VectorNormalize( ttv[ i ], ttv[ i ] ); + + /* debug code */ + //% Sys_FPrintf( SYS_VRB, "%d S: (%f %f %f) T: (%f %f %f)\n", i, + //% stv[ i ][ 0 ], stv[ i ][ 1 ], stv[ i ][ 2 ], ttv[ i ][ 0 ], ttv[ i ][ 1 ], ttv[ i ][ 2 ] ); + } + + /* return to caller */ + return qtrue; +} + + + + +/* +PerturbNormal() +perterbs the normal by the shader's normalmap in tangent space +*/ + +static void PerturbNormal( bspDrawVert_t *dv, shaderInfo_t *si, vec3_t pNormal, vec3_t stv[ 3 ], vec3_t ttv[ 3 ] ) +{ + int i; + vec4_t bump; + + + /* passthrough */ + VectorCopy( dv->normal, pNormal ); + + /* sample normalmap */ + if( RadSampleImage( si->normalImage->pixels, si->normalImage->width, si->normalImage->height, dv->st, bump ) == qfalse ) + return; + + /* remap sampled normal from [0,255] to [-1,-1] */ + for( i = 0; i < 3; i++ ) + bump[ i ] = (bump[ i ] - 127.0f) * (1.0f / 127.5f); + + /* scale tangent vectors and add to original normal */ + VectorMA( dv->normal, bump[ 0 ], stv[ 0 ], pNormal ); + VectorMA( pNormal, bump[ 1 ], ttv[ 0 ], pNormal ); + VectorMA( pNormal, bump[ 2 ], dv->normal, pNormal ); + + /* renormalize and return */ + VectorNormalize( pNormal, pNormal ); +} + + + +/* +MapSingleLuxel() +maps a luxel for triangle bv at +*/ + +#define NUDGE 0.5f +#define BOGUS_NUDGE -99999.0f + +static int MapSingleLuxel( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv, vec4_t plane, float pass, vec3_t stv[ 3 ], vec3_t ttv[ 3 ], vec3_t worldverts[ 3 ] ) +{ + int i, x, y, numClusters, *clusters, pointCluster, *cluster; + float *luxel, *origin, *normal, d, lightmapSampleOffset; + shaderInfo_t *si; + vec3_t pNormal; + vec3_t vecs[ 3 ]; + vec3_t nudged; + vec3_t cverts[ 3 ]; + vec3_t temp; + vec4_t sideplane, hostplane; + vec3_t origintwo; + int j, next; + float e; + float *nudge; + static float nudges[][ 2 ] = + { + //%{ 0, 0 }, /* try center first */ + { -NUDGE, 0 }, /* left */ + { NUDGE, 0 }, /* right */ + { 0, NUDGE }, /* up */ + { 0, -NUDGE }, /* down */ + { -NUDGE, NUDGE }, /* left/up */ + { NUDGE, -NUDGE }, /* right/down */ + { NUDGE, NUDGE }, /* right/up */ + { -NUDGE, -NUDGE }, /* left/down */ + { BOGUS_NUDGE, BOGUS_NUDGE } + }; + + + /* find luxel xy coords (fixme: subtract 0.5?) */ + x = dv->lightmap[ 0 ][ 0 ]; + y = dv->lightmap[ 0 ][ 1 ]; + if( x < 0 ) + x = 0; + else if( x >= lm->sw ) + x = lm->sw - 1; + if( y < 0 ) + y = 0; + else if( y >= lm->sh ) + y = lm->sh - 1; + + /* set shader and cluster list */ + if( info != NULL ) + { + si = info->si; + numClusters = info->numSurfaceClusters; + clusters = &surfaceClusters[ info->firstSurfaceCluster ]; + } + else + { + si = NULL; + numClusters = 0; + clusters = NULL; + } + + /* get luxel, origin, cluster, and normal */ + luxel = SUPER_LUXEL( 0, x, y ); + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + cluster = SUPER_CLUSTER( x, y ); + + /* don't attempt to remap occluded luxels for planar surfaces */ + if( (*cluster) == CLUSTER_OCCLUDED && lm->plane != NULL ) + return (*cluster); + + /* only average the normal for premapped luxels */ + else if( (*cluster) >= 0 ) + { + /* do bumpmap calculations */ + if( stv != NULL ) + PerturbNormal( dv, si, pNormal, stv, ttv ); + else + VectorCopy( dv->normal, pNormal ); + + /* add the additional normal data */ + VectorAdd( normal, pNormal, normal ); + luxel[ 3 ] += 1.0f; + return (*cluster); + } + + /* otherwise, unmapped luxels (*cluster == CLUSTER_UNMAPPED) will have their full attributes calculated */ + + /* get origin */ + + /* axial lightmap projection */ + if( lm->vecs != NULL ) + { + /* calculate an origin for the sample from the lightmap vectors */ + VectorCopy( lm->origin, origin ); + for( i = 0; i < 3; i++ ) + { + /* add unless it's the axis, which is taken care of later */ + if( i == lm->axisNum ) + continue; + origin[ i ] += (x * lm->vecs[ 0 ][ i ]) + (y * lm->vecs[ 1 ][ i ]); + } + + /* project the origin onto the plane */ + d = DotProduct( origin, plane ) - plane[ 3 ]; + d /= plane[ lm->axisNum ]; + origin[ lm->axisNum ] -= d; + } + + /* non axial lightmap projection (explicit xyz) */ + else + VectorCopy( dv->xyz, origin ); + + ////////////////////// + //27's test to make sure samples stay within the triangle boundaries + //1) Test the sample origin to see if it lays on the wrong side of any edge (x/y) + //2) if it does, nudge it onto the correct side. + + if (worldverts!=NULL) + { + for (j=0;j<3;j++) + { + VectorCopy(worldverts[j],cverts[j]); + } + PlaneFromPoints(hostplane,cverts[0],cverts[1],cverts[2]); + + for (j=0;j<3;j++) + { + for (i=0;i<3;i++) + { + //build plane using 2 edges and a normal + next=(i+1)%3; + + VectorCopy(cverts[next],temp); + VectorAdd(temp,hostplane,temp); + PlaneFromPoints(sideplane,cverts[i],cverts[ next ], temp); + + //planetest sample point + e=DotProduct(origin,sideplane); + e=e-sideplane[3]; + if (e>0) + { + //we're bad. + //VectorClear(origin); + //Move the sample point back inside triangle bounds + origin[0]-=sideplane[0]*(e+1); + origin[1]-=sideplane[1]*(e+1); + origin[2]-=sideplane[2]*(e+1); +#ifdef DEBUG_27_1 + VectorClear(origin); +#endif + } + } + } + } + + //////////////////////// + + /* planar surfaces have precalculated lightmap vectors for nudging */ + if( lm->plane != NULL ) + { + VectorCopy( lm->vecs[ 0 ], vecs[ 0 ] ); + VectorCopy( lm->vecs[ 1 ], vecs[ 1 ] ); + VectorCopy( lm->plane, vecs[ 2 ] ); + } + + /* non-planar surfaces must calculate them */ + else + { + if( plane != NULL ) + VectorCopy( plane, vecs[ 2 ] ); + else + VectorCopy( dv->normal, vecs[ 2 ] ); + MakeNormalVectors( vecs[ 2 ], vecs[ 0 ], vecs[ 1 ] ); + } + + /* push the origin off the surface a bit */ + if( si != NULL ) + lightmapSampleOffset = si->lightmapSampleOffset; + else + lightmapSampleOffset = DEFAULT_LIGHTMAP_SAMPLE_OFFSET; + if( lm->axisNum < 0 ) + VectorMA( origin, lightmapSampleOffset, vecs[ 2 ], origin ); + else if( vecs[ 2 ][ lm->axisNum ] < 0.0f ) + origin[ lm->axisNum ] -= lightmapSampleOffset; + else + origin[ lm->axisNum ] += lightmapSampleOffset; + + VectorCopy(origin,origintwo); + origintwo[0]+=vecs[2][0]; + origintwo[1]+=vecs[2][1]; + origintwo[2]+=vecs[2][2]; + + /* get cluster */ + pointCluster = ClusterForPointExtFilter( origintwo, LUXEL_EPSILON, numClusters, clusters ); + + /* another retarded hack, storing nudge count in luxel[ 1 ] */ + luxel[ 1 ] = 0.0f; + + /* point in solid? (except in dark mode) */ + if( pointCluster < 0 && dark == qfalse ) + { + /* nudge the the location around */ + nudge = nudges[ 0 ]; + while( nudge[ 0 ] > BOGUS_NUDGE && pointCluster < 0 ) + { + /* nudge the vector around a bit */ + for( i = 0; i < 3; i++ ) + { + /* set nudged point*/ + nudged[ i ] = origintwo[ i ] + (nudge[ 0 ] * vecs[ 0 ][ i ]) + (nudge[ 1 ] * vecs[ 1 ][ i ]); + } + nudge += 2; + + /* get pvs cluster */ + pointCluster = ClusterForPointExtFilter( nudged, LUXEL_EPSILON, numClusters, clusters ); //% + 0.625 ); + //if( pointCluster >= 0 ) + // VectorCopy( nudged, origin ); + luxel[ 1 ] += 1.0f; + } + } + + /* as a last resort, if still in solid, try drawvert origin offset by normal (except in dark mode) */ + if( pointCluster < 0 && si != NULL && dark == qfalse ) + { + VectorMA( dv->xyz, lightmapSampleOffset, dv->normal, nudged ); + pointCluster = ClusterForPointExtFilter( nudged, LUXEL_EPSILON, numClusters, clusters ); + //if( pointCluster >= 0 ) + // VectorCopy( nudged, origin ); + luxel[ 1 ] += 1.0f; + } + + /* valid? */ + if( pointCluster < 0 ) + { + (*cluster) = CLUSTER_OCCLUDED; + VectorClear( origin ); + VectorClear( normal ); + numLuxelsOccluded++; + return (*cluster); + } + + /* debug code */ + //% Sys_Printf( "%f %f %f\n", origin[ 0 ], origin[ 1 ], origin[ 2 ] ); + + /* do bumpmap calculations */ + if( stv ) + PerturbNormal( dv, si, pNormal, stv, ttv ); + else + VectorCopy( dv->normal, pNormal ); + + /* store the cluster and normal */ + (*cluster) = pointCluster; + VectorCopy( pNormal, normal ); + + /* store explicit mapping pass and implicit mapping pass */ + luxel[ 0 ] = pass; + luxel[ 3 ] = 1.0f; + + /* add to count */ + numLuxelsMapped++; + + /* return ok */ + return (*cluster); +} + + + +/* +MapTriangle_r() +recursively subdivides a triangle until its edges are shorter +than the distance between two luxels (thanks jc :) +*/ + +static void MapTriangle_r( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 3 ], vec4_t plane, vec3_t stv[ 3 ], vec3_t ttv[ 3 ], vec3_t worldverts[ 3 ] ) +{ + bspDrawVert_t mid, *dv2[ 3 ]; + int max; + + + /* map the vertexes */ + #if 0 + MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv ); + MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv ); + MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv ); + #endif + + /* subdivide calc */ + { + int i; + float *a, *b, dx, dy, dist, maxDist; + + + /* find the longest edge and split it */ + max = -1; + maxDist = 0; + for( i = 0; i < 3; i++ ) + { + /* get verts */ + a = dv[ i ]->lightmap[ 0 ]; + b = dv[ (i + 1) % 3 ]->lightmap[ 0 ]; + + /* get dists */ + dx = a[ 0 ] - b[ 0 ]; + dy = a[ 1 ] - b[ 1 ]; + dist = (dx * dx) + (dy * dy); //% sqrt( (dx * dx) + (dy * dy) ); + + /* longer? */ + if( dist > maxDist ) + { + maxDist = dist; + max = i; + } + } + + /* try to early out */ + if( max < 0 || maxDist <= subdivideThreshold ) /* ydnar: was i < 0 instead of max < 0 (?) */ + return; + } + + /* split the longest edge and map it */ + LerpDrawVert( dv[ max ], dv[ (max + 1) % 3 ], &mid ); + MapSingleLuxel( lm, info, &mid, plane, 1, stv, ttv, worldverts ); + + /* push the point up a little bit to account for fp creep (fixme: revisit this) */ + //% VectorMA( mid.xyz, 2.0f, mid.normal, mid.xyz ); + + /* recurse to first triangle */ + VectorCopy( dv, dv2 ); + dv2[ max ] = ∣ + MapTriangle_r( lm, info, dv2, plane, stv, ttv, worldverts ); + + /* recurse to second triangle */ + VectorCopy( dv, dv2 ); + dv2[ (max + 1) % 3 ] = ∣ + MapTriangle_r( lm, info, dv2, plane, stv, ttv, worldverts ); +} + + + +/* +MapTriangle() +seed function for MapTriangle_r() +requires a cw ordered triangle +*/ + +static qboolean MapTriangle( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 3 ], qboolean mapNonAxial ) +{ + int i; + vec4_t plane; + vec3_t *stv, *ttv, stvStatic[ 3 ], ttvStatic[ 3 ]; + vec3_t worldverts[ 3 ]; + + + /* get plane if possible */ + if( lm->plane != NULL ) + { + VectorCopy( lm->plane, plane ); + plane[ 3 ] = lm->plane[ 3 ]; + } + + /* otherwise make one from the points */ + else if( PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) == qfalse ) + return qfalse; + + /* check to see if we need to calculate texture->world tangent vectors */ + if( info->si->normalImage != NULL && CalcTangentVectors( 3, dv, stvStatic, ttvStatic ) ) + { + stv = stvStatic; + ttv = ttvStatic; + } + else + { + stv = NULL; + ttv = NULL; + } + + VectorCopy( dv[ 0 ]->xyz, worldverts[ 0 ] ); + VectorCopy( dv[ 1 ]->xyz, worldverts[ 1 ] ); + VectorCopy( dv[ 2 ]->xyz, worldverts[ 2 ] ); + + /* map the vertexes */ + MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv, worldverts ); + MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv, worldverts ); + MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv, worldverts ); + + /* 2002-11-20: prefer axial triangle edges */ + if( mapNonAxial ) + { + /* subdivide the triangle */ + MapTriangle_r( lm, info, dv, plane, stv, ttv, worldverts ); + return qtrue; + } + + for( i = 0; i < 3; i++ ) + { + float *a, *b; + bspDrawVert_t *dv2[ 3 ]; + + + /* get verts */ + a = dv[ i ]->lightmap[ 0 ]; + b = dv[ (i + 1) % 3 ]->lightmap[ 0 ]; + + /* make degenerate triangles for mapping edges */ + if( fabs( a[ 0 ] - b[ 0 ] ) < 0.01f || fabs( a[ 1 ] - b[ 1 ] ) < 0.01f ) + { + dv2[ 0 ] = dv[ i ]; + dv2[ 1 ] = dv[ (i + 1) % 3 ]; + dv2[ 2 ] = dv[ (i + 1) % 3 ]; + + /* map the degenerate triangle */ + MapTriangle_r( lm, info, dv2, plane, stv, ttv, worldverts ); + } + } + + return qtrue; +} + + + +/* +MapQuad_r() +recursively subdivides a quad until its edges are shorter +than the distance between two luxels +*/ + +static void MapQuad_r( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 4 ], vec4_t plane, vec3_t stv[ 4 ], vec3_t ttv[ 4 ] ) +{ + bspDrawVert_t mid[ 2 ], *dv2[ 4 ]; + int max; + + + /* subdivide calc */ + { + int i; + float *a, *b, dx, dy, dist, maxDist; + + + /* find the longest edge and split it */ + max = -1; + maxDist = 0; + for( i = 0; i < 4; i++ ) + { + /* get verts */ + a = dv[ i ]->lightmap[ 0 ]; + b = dv[ (i + 1) % 4 ]->lightmap[ 0 ]; + + /* get dists */ + dx = a[ 0 ] - b[ 0 ]; + dy = a[ 1 ] - b[ 1 ]; + dist = (dx * dx) + (dy * dy); //% sqrt( (dx * dx) + (dy * dy) ); + + /* longer? */ + if( dist > maxDist ) + { + maxDist = dist; + max = i; + } + } + + /* try to early out */ + if( max < 0 || maxDist <= subdivideThreshold ) + return; + } + + /* we only care about even/odd edges */ + max &= 1; + + /* split the longest edges */ + LerpDrawVert( dv[ max ], dv[ (max + 1) % 4 ], &mid[ 0 ] ); + LerpDrawVert( dv[ max + 2 ], dv[ (max + 3) % 4 ], &mid[ 1 ] ); + + /* map the vertexes */ + MapSingleLuxel( lm, info, &mid[ 0 ], plane, 1, stv, ttv, NULL ); + MapSingleLuxel( lm, info, &mid[ 1 ], plane, 1, stv, ttv, NULL ); + + /* 0 and 2 */ + if( max == 0 ) + { + /* recurse to first quad */ + dv2[ 0 ] = dv[ 0 ]; + dv2[ 1 ] = &mid[ 0 ]; + dv2[ 2 ] = &mid[ 1 ]; + dv2[ 3 ] = dv[ 3 ]; + MapQuad_r( lm, info, dv2, plane, stv, ttv ); + + /* recurse to second quad */ + dv2[ 0 ] = &mid[ 0 ]; + dv2[ 1 ] = dv[ 1 ]; + dv2[ 2 ] = dv[ 2 ]; + dv2[ 3 ] = &mid[ 1 ]; + MapQuad_r( lm, info, dv2, plane, stv, ttv ); + } + + /* 1 and 3 */ + else + { + /* recurse to first quad */ + dv2[ 0 ] = dv[ 0 ]; + dv2[ 1 ] = dv[ 1 ]; + dv2[ 2 ] = &mid[ 0 ]; + dv2[ 3 ] = &mid[ 1 ]; + MapQuad_r( lm, info, dv2, plane, stv, ttv ); + + /* recurse to second quad */ + dv2[ 0 ] = &mid[ 1 ]; + dv2[ 1 ] = &mid[ 0 ]; + dv2[ 2 ] = dv[ 2 ]; + dv2[ 3 ] = dv[ 3 ]; + MapQuad_r( lm, info, dv2, plane, stv, ttv ); + } +} + + + +/* +MapQuad() +seed function for MapQuad_r() +requires a cw ordered triangle quad +*/ + +#define QUAD_PLANAR_EPSILON 0.5f + +static qboolean MapQuad( rawLightmap_t *lm, surfaceInfo_t *info, bspDrawVert_t *dv[ 4 ] ) +{ + float dist; + vec4_t plane; + vec3_t *stv, *ttv, stvStatic[ 4 ], ttvStatic[ 4 ]; + + + /* get plane if possible */ + if( lm->plane != NULL ) + { + VectorCopy( lm->plane, plane ); + plane[ 3 ] = lm->plane[ 3 ]; + } + + /* otherwise make one from the points */ + else if( PlaneFromPoints( plane, dv[ 0 ]->xyz, dv[ 1 ]->xyz, dv[ 2 ]->xyz ) == qfalse ) + return qfalse; + + /* 4th point must fall on the plane */ + dist = DotProduct( plane, dv[ 3 ]->xyz ) - plane[ 3 ]; + if( fabs( dist ) > QUAD_PLANAR_EPSILON ) + return qfalse; + + /* check to see if we need to calculate texture->world tangent vectors */ + if( info->si->normalImage != NULL && CalcTangentVectors( 4, dv, stvStatic, ttvStatic ) ) + { + stv = stvStatic; + ttv = ttvStatic; + } + else + { + stv = NULL; + ttv = NULL; + } + + /* map the vertexes */ + MapSingleLuxel( lm, info, dv[ 0 ], plane, 1, stv, ttv, NULL ); + MapSingleLuxel( lm, info, dv[ 1 ], plane, 1, stv, ttv, NULL ); + MapSingleLuxel( lm, info, dv[ 2 ], plane, 1, stv, ttv, NULL ); + MapSingleLuxel( lm, info, dv[ 3 ], plane, 1, stv, ttv, NULL ); + + /* subdivide the quad */ + MapQuad_r( lm, info, dv, plane, stv, ttv ); + return qtrue; +} + + + +/* +MapRawLightmap() +maps the locations, normals, and pvs clusters for a raw lightmap +*/ + +#define VectorDivide( in, d, out ) VectorScale( in, (1.0f / (d)), out ) //% (out)[ 0 ] = (in)[ 0 ] / (d), (out)[ 1 ] = (in)[ 1 ] / (d), (out)[ 2 ] = (in)[ 2 ] / (d) + +void MapRawLightmap( int rawLightmapNum ) +{ + int n, num, i, x, y, sx, sy, pw[ 5 ], r, *cluster, mapNonAxial; + float *luxel, *origin, *normal, samples, radius, pass; + rawLightmap_t *lm; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + mesh_t src, *subdivided, *mesh; + bspDrawVert_t *verts, *dv[ 4 ], fake; + + + /* bail if this number exceeds the number of raw lightmaps */ + if( rawLightmapNum >= numRawLightmaps ) + return; + + /* get lightmap */ + lm = &rawLightmaps[ rawLightmapNum ]; + + /* ----------------------------------------------------------------- + map referenced surfaces onto the raw lightmap + ----------------------------------------------------------------- */ + + /* walk the list of surfaces on this raw lightmap */ + for( n = 0; n < lm->numLightSurfaces; n++ ) + { + /* with > 1 surface per raw lightmap, clear occluded */ + if( n > 0 ) + { + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + if( *cluster < 0 ) + *cluster = CLUSTER_UNMAPPED; + } + } + } + + /* get surface */ + num = lightSurfaces[ lm->firstLightSurface + n ]; + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* bail if no lightmap to calculate */ + if( info->lm != lm ) + { + Sys_Printf( "!" ); + continue; + } + + /* map the surface onto the lightmap origin/cluster/normal buffers */ + switch( ds->surfaceType ) + { + case MST_PLANAR: + /* get verts */ + verts = yDrawVerts + ds->firstVert; + + /* map the triangles */ + for( mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++ ) + { + for( i = 0; i < ds->numIndexes; i += 3 ) + { + dv[ 0 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i ] ]; + dv[ 1 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 1 ] ]; + dv[ 2 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 2 ] ]; + MapTriangle( lm, info, dv, mapNonAxial ); + } + } + break; + + case MST_PATCH: + /* make a mesh from the drawsurf */ + src.width = ds->patchWidth; + src.height = ds->patchHeight; + src.verts = &yDrawVerts[ ds->firstVert ]; + //% subdivided = SubdivideMesh( src, 8, 512 ); + subdivided = SubdivideMesh2( src, info->patchIterations ); + + /* fit it to the curve and remove colinear verts on rows/columns */ + PutMeshOnCurve( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + + /* get verts */ + verts = mesh->verts; + + /* debug code */ + #if 0 + if( lm->plane ) + { + Sys_Printf( "Planar patch: [%1.3f %1.3f %1.3f] [%1.3f %1.3f %1.3f] [%1.3f %1.3f %1.3f]\n", + lm->plane[ 0 ], lm->plane[ 1 ], lm->plane[ 2 ], + lm->vecs[ 0 ][ 0 ], lm->vecs[ 0 ][ 1 ], lm->vecs[ 0 ][ 2 ], + lm->vecs[ 1 ][ 0 ], lm->vecs[ 1 ][ 1 ], lm->vecs[ 1 ][ 2 ] ); + } + #endif + + /* map the mesh quads */ + #if 0 + + for( mapNonAxial = 0; mapNonAxial < 2; mapNonAxial++ ) + { + for( y = 0; y < (mesh->height - 1); y++ ) + { + for( x = 0; x < (mesh->width - 1); x++ ) + { + /* set indexes */ + pw[ 0 ] = x + (y * mesh->width); + pw[ 1 ] = x + ((y + 1) * mesh->width); + pw[ 2 ] = x + 1 + ((y + 1) * mesh->width); + pw[ 3 ] = x + 1 + (y * mesh->width); + pw[ 4 ] = x + (y * mesh->width); /* same as pw[ 0 ] */ + + /* set radix */ + r = (x + y) & 1; + + /* get drawverts and map first triangle */ + dv[ 0 ] = &verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &verts[ pw[ r + 1 ] ]; + dv[ 2 ] = &verts[ pw[ r + 2 ] ]; + MapTriangle( lm, info, dv, mapNonAxial ); + + /* get drawverts and map second triangle */ + dv[ 0 ] = &verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &verts[ pw[ r + 2 ] ]; + dv[ 2 ] = &verts[ pw[ r + 3 ] ]; + MapTriangle( lm, info, dv, mapNonAxial ); + } + } + } + + #else + + for( y = 0; y < (mesh->height - 1); y++ ) + { + for( x = 0; x < (mesh->width - 1); x++ ) + { + /* set indexes */ + pw[ 0 ] = x + (y * mesh->width); + pw[ 1 ] = x + ((y + 1) * mesh->width); + pw[ 2 ] = x + 1 + ((y + 1) * mesh->width); + pw[ 3 ] = x + 1 + (y * mesh->width); + pw[ 4 ] = pw[ 0 ]; + + /* set radix */ + r = (x + y) & 1; + + /* attempt to map quad first */ + dv[ 0 ] = &verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &verts[ pw[ r + 1 ] ]; + dv[ 2 ] = &verts[ pw[ r + 2 ] ]; + dv[ 3 ] = &verts[ pw[ r + 3 ] ]; + if( MapQuad( lm, info, dv ) ) + continue; + + /* get drawverts and map first triangle */ + MapTriangle( lm, info, dv, mapNonAxial ); + + /* get drawverts and map second triangle */ + dv[ 1 ] = &verts[ pw[ r + 2 ] ]; + dv[ 2 ] = &verts[ pw[ r + 3 ] ]; + MapTriangle( lm, info, dv, mapNonAxial ); + } + } + + #endif + + /* free the mesh */ + FreeMesh( mesh ); + break; + + default: + break; + } + } + + /* ----------------------------------------------------------------- + average and clean up luxel normals + ----------------------------------------------------------------- */ + + /* walk the luxels */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + luxel = SUPER_LUXEL( 0, x, y ); + normal = SUPER_NORMAL( x, y ); + cluster = SUPER_CLUSTER( x, y ); + + /* only look at mapped luxels */ + if( *cluster < 0 ) + continue; + + /* the normal data could be the sum of multiple samples */ + if( luxel[ 3 ] > 1.0f ) + VectorNormalize( normal, normal ); + + /* mark this luxel as having only one normal */ + luxel[ 3 ] = 1.0f; + } + } + + /* non-planar surfaces stop here */ + if( lm->plane == NULL ) + return; + + /* ----------------------------------------------------------------- + map occluded or unuxed luxels + ----------------------------------------------------------------- */ + + /* walk the luxels */ + radius = floor( superSample / 2 ); + radius = radius > 0 ? radius : 1.0f; + radius += 1.0f; + for( pass = 2.0f; pass <= radius; pass += 1.0f ) + { + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + luxel = SUPER_LUXEL( 0, x, y ); + normal = SUPER_NORMAL( x, y ); + cluster = SUPER_CLUSTER( x, y ); + + /* only look at unmapped luxels */ + if( *cluster != CLUSTER_UNMAPPED ) + continue; + + /* divine a normal and origin from neighboring luxels */ + VectorClear( fake.xyz ); + VectorClear( fake.normal ); + fake.lightmap[ 0 ][ 0 ] = x; //% 0.0001 + x; + fake.lightmap[ 0 ][ 1 ] = y; //% 0.0001 + y; + samples = 0.0f; + for( sy = (y - 1); sy <= (y + 1); sy++ ) + { + if( sy < 0 || sy >= lm->sh ) + continue; + + for( sx = (x - 1); sx <= (x + 1); sx++ ) + { + if( sx < 0 || sx >= lm->sw || (sx == x && sy == y) ) + continue; + + /* get neighboring luxel */ + luxel = SUPER_LUXEL( 0, sx, sy ); + origin = SUPER_ORIGIN( sx, sy ); + normal = SUPER_NORMAL( sx, sy ); + cluster = SUPER_CLUSTER( sx, sy ); + + /* only consider luxels mapped in previous passes */ + if( *cluster < 0 || luxel[ 0 ] >= pass ) + continue; + + /* add its distinctiveness to our own */ + VectorAdd( fake.xyz, origin, fake.xyz ); + VectorAdd( fake.normal, normal, fake.normal ); + samples += luxel[ 3 ]; + } + } + + /* any samples? */ + if( samples == 0.0f ) + continue; + + /* average */ + VectorDivide( fake.xyz, samples, fake.xyz ); + //% VectorDivide( fake.normal, samples, fake.normal ); + if( VectorNormalize( fake.normal, fake.normal ) == 0.0f ) + continue; + + /* map the fake vert */ + MapSingleLuxel( lm, NULL, &fake, lm->plane, pass, NULL, NULL, NULL ); + } + } + } + + /* ----------------------------------------------------------------- + average and clean up luxel normals + ----------------------------------------------------------------- */ + + /* walk the luxels */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + luxel = SUPER_LUXEL( 0, x, y ); + normal = SUPER_NORMAL( x, y ); + cluster = SUPER_CLUSTER( x, y ); + + /* only look at mapped luxels */ + if( *cluster < 0 ) + continue; + + /* the normal data could be the sum of multiple samples */ + if( luxel[ 3 ] > 1.0f ) + VectorNormalize( normal, normal ); + + /* mark this luxel as having only one normal */ + luxel[ 3 ] = 1.0f; + } + } + + /* debug code */ + #if 0 + Sys_Printf( "\n" ); + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + vec3_t mins, maxs; + + + cluster = SUPER_CLUSTER( x, y ); + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + luxel = SUPER_LUXEL( x, y ); + + if( *cluster < 0 ) + continue; + + /* check if within the bounding boxes of all surfaces referenced */ + ClearBounds( mins, maxs ); + for( n = 0; n < lm->numLightSurfaces; n++ ) + { + int TOL; + info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + n ] ]; + TOL = info->sampleSize + 2; + AddPointToBounds( info->mins, mins, maxs ); + AddPointToBounds( info->maxs, mins, maxs ); + if( origin[ 0 ] > (info->mins[ 0 ] - TOL) && origin[ 0 ] < (info->maxs[ 0 ] + TOL) && + origin[ 1 ] > (info->mins[ 1 ] - TOL) && origin[ 1 ] < (info->maxs[ 1 ] + TOL) && + origin[ 2 ] > (info->mins[ 2 ] - TOL) && origin[ 2 ] < (info->maxs[ 2 ] + TOL) ) + break; + } + + /* inside? */ + if( n < lm->numLightSurfaces ) + continue; + + /* report bogus origin */ + Sys_Printf( "%6d [%2d,%2d] (%4d): XYZ(%+4.1f %+4.1f %+4.1f) LO(%+4.1f %+4.1f %+4.1f) HI(%+4.1f %+4.1f %+4.1f) <%3.0f>\n", + rawLightmapNum, x, y, *cluster, + origin[ 0 ], origin[ 1 ], origin[ 2 ], + mins[ 0 ], mins[ 1 ], mins[ 2 ], + maxs[ 0 ], maxs[ 1 ], maxs[ 2 ], + luxel[ 3 ] ); + } + } + #endif +} + + + +/* +SetupDirt() +sets up dirtmap (ambient occlusion) +*/ + +#define DIRT_CONE_ANGLE 88 /* degrees */ +#define DIRT_NUM_ANGLE_STEPS 16 +#define DIRT_NUM_ELEVATION_STEPS 3 +#define DIRT_NUM_VECTORS (DIRT_NUM_ANGLE_STEPS * DIRT_NUM_ELEVATION_STEPS) + +static vec3_t dirtVectors[ DIRT_NUM_VECTORS ]; +static int numDirtVectors = 0; + +void SetupDirt( void ) +{ + int i, j; + float angle, elevation, angleStep, elevationStep; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupDirt ---\n" ); + + /* calculate angular steps */ + angleStep = DEG2RAD( 360.0f / DIRT_NUM_ANGLE_STEPS ); + elevationStep = DEG2RAD( DIRT_CONE_ANGLE / DIRT_NUM_ELEVATION_STEPS ); + + /* iterate angle */ + angle = 0.0f; + for( i = 0, angle = 0.0f; i < DIRT_NUM_ANGLE_STEPS; i++, angle += angleStep ) + { + /* iterate elevation */ + for( j = 0, elevation = elevationStep * 0.5f; j < DIRT_NUM_ELEVATION_STEPS; j++, elevation += elevationStep ) + { + dirtVectors[ numDirtVectors ][ 0 ] = sin( elevation ) * cos( angle ); + dirtVectors[ numDirtVectors ][ 1 ] = sin( elevation ) * sin( angle ); + dirtVectors[ numDirtVectors ][ 2 ] = cos( elevation ); + numDirtVectors++; + } + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d dirtmap vectors\n", numDirtVectors ); +} + + +/* +DirtForSample() +calculates dirt value for a given sample +*/ + +float DirtForSample( trace_t *trace ) +{ + int i; + float gatherDirt, outDirt, angle, elevation, ooDepth; + vec3_t normal, worldUp, myUp, myRt, temp, direction, displacement; + + + /* dummy check */ + if( !dirty ) + return 1.0f; + if( trace == NULL || trace->cluster < 0 ) + return 0.0f; + + /* setup */ + gatherDirt = 0.0f; + ooDepth = 1.0f / dirtDepth; + VectorCopy( trace->normal, normal ); + + /* check if the normal is aligned to the world-up */ + if( normal[ 0 ] == 0.0f && normal[ 1 ] == 0.0f ) + { + if( normal[ 2 ] == 1.0f ) + { + VectorSet( myRt, 1.0f, 0.0f, 0.0f ); + VectorSet( myUp, 0.0f, 1.0f, 0.0f ); + } + else if( normal[ 2 ] == -1.0f ) + { + VectorSet( myRt, -1.0f, 0.0f, 0.0f ); + VectorSet( myUp, 0.0f, 1.0f, 0.0f ); + } + } + else + { + VectorSet( worldUp, 0.0f, 0.0f, 1.0f ); + CrossProduct( normal, worldUp, myRt ); + VectorNormalize( myRt, myRt ); + CrossProduct( myRt, normal, myUp ); + VectorNormalize( myUp, myUp ); + } + + /* 1 = random mode, 0 (well everything else) = non-random mode */ + if( dirtMode == 1 ) + { + /* iterate */ + for( i = 0; i < numDirtVectors; i++ ) + { + /* get random vector */ + angle = Random() * DEG2RAD( 360.0f ); + elevation = Random() * DEG2RAD( DIRT_CONE_ANGLE ); + temp[ 0 ] = cos( angle ) * sin( elevation ); + temp[ 1 ] = sin( angle ) * sin( elevation ); + temp[ 2 ] = cos( elevation ); + + /* transform into tangent space */ + direction[ 0 ] = myRt[ 0 ] * temp[ 0 ] + myUp[ 0 ] * temp[ 1 ] + normal[ 0 ] * temp[ 2 ]; + direction[ 1 ] = myRt[ 1 ] * temp[ 0 ] + myUp[ 1 ] * temp[ 1 ] + normal[ 1 ] * temp[ 2 ]; + direction[ 2 ] = myRt[ 2 ] * temp[ 0 ] + myUp[ 2 ] * temp[ 1 ] + normal[ 2 ] * temp[ 2 ]; + + /* set endpoint */ + VectorMA( trace->origin, dirtDepth, direction, trace->end ); + SetupTrace( trace ); + + /* trace */ + TraceLine( trace ); + if( trace->opaque ) + { + VectorSubtract( trace->hit, trace->origin, displacement ); + gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); + } + } + } + else + { + /* iterate through ordered vectors */ + for( i = 0; i < numDirtVectors; i++ ) + { + /* transform vector into tangent space */ + direction[ 0 ] = myRt[ 0 ] * dirtVectors[ i ][ 0 ] + myUp[ 0 ] * dirtVectors[ i ][ 1 ] + normal[ 0 ] * dirtVectors[ i ][ 2 ]; + direction[ 1 ] = myRt[ 1 ] * dirtVectors[ i ][ 0 ] + myUp[ 1 ] * dirtVectors[ i ][ 1 ] + normal[ 1 ] * dirtVectors[ i ][ 2 ]; + direction[ 2 ] = myRt[ 2 ] * dirtVectors[ i ][ 0 ] + myUp[ 2 ] * dirtVectors[ i ][ 1 ] + normal[ 2 ] * dirtVectors[ i ][ 2 ]; + + /* set endpoint */ + VectorMA( trace->origin, dirtDepth, direction, trace->end ); + SetupTrace( trace ); + + /* trace */ + TraceLine( trace ); + if( trace->opaque ) + { + VectorSubtract( trace->hit, trace->origin, displacement ); + gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); + } + } + } + + /* direct ray */ + VectorMA( trace->origin, dirtDepth, normal, trace->end ); + SetupTrace( trace ); + + /* trace */ + TraceLine( trace ); + if( trace->opaque ) + { + VectorSubtract( trace->hit, trace->origin, displacement ); + gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); + } + + /* early out */ + if( gatherDirt <= 0.0f ) + return 1.0f; + + /* apply gain (does this even do much? heh) */ + outDirt = pow( gatherDirt / (numDirtVectors + 1), dirtGain ); + if( outDirt > 1.0f ) + outDirt = 1.0f; + + /* apply scale */ + outDirt *= dirtScale; + if( outDirt > 1.0f ) + outDirt = 1.0f; + + /* return to sender */ + return 1.0f - outDirt; +} + + + +/* +DirtyRawLightmap() +calculates dirty fraction for each luxel +*/ + +void DirtyRawLightmap( int rawLightmapNum ) +{ + int i, x, y, sx, sy, *cluster; + float *origin, *normal, *dirt, *dirt2, average, samples; + rawLightmap_t *lm; + surfaceInfo_t *info; + trace_t trace; + + + /* bail if this number exceeds the number of raw lightmaps */ + if( rawLightmapNum >= numRawLightmaps ) + return; + + /* get lightmap */ + lm = &rawLightmaps[ rawLightmapNum ]; + + /* setup trace */ + trace.testOcclusion = qtrue; + trace.forceSunlight = qfalse; + trace.recvShadows = lm->recvShadows; + trace.numSurfaces = lm->numLightSurfaces; + trace.surfaces = &lightSurfaces[ lm->firstLightSurface ]; + trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS; + trace.testAll = qfalse; + + /* twosided lighting (may or may not be a good idea for lightmapped stuff) */ + trace.twoSided = qfalse; + for( i = 0; i < trace.numSurfaces; i++ ) + { + /* get surface */ + info = &surfaceInfos[ trace.surfaces[ i ] ]; + + /* check twosidedness */ + if( info->si->twoSided ) + { + trace.twoSided = qtrue; + break; + } + } + + /* gather dirt */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + cluster = SUPER_CLUSTER( x, y ); + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + dirt = SUPER_DIRT( x, y ); + + /* set default dirt */ + *dirt = 0.0f; + + /* only look at mapped luxels */ + if( *cluster < 0 ) + continue; + + /* copy to trace */ + trace.cluster = *cluster; + VectorCopy( origin, trace.origin ); + VectorCopy( normal, trace.normal ); + + /* get dirt */ + *dirt = DirtForSample( &trace ); + } + } + + /* testing no filtering */ + //% return; + + /* filter dirt */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + cluster = SUPER_CLUSTER( x, y ); + dirt = SUPER_DIRT( x, y ); + + /* filter dirt by adjacency to unmapped luxels */ + average = *dirt; + samples = 1.0f; + for( sy = (y - 1); sy <= (y + 1); sy++ ) + { + if( sy < 0 || sy >= lm->sh ) + continue; + + for( sx = (x - 1); sx <= (x + 1); sx++ ) + { + if( sx < 0 || sx >= lm->sw || (sx == x && sy == y) ) + continue; + + /* get neighboring luxel */ + cluster = SUPER_CLUSTER( sx, sy ); + dirt2 = SUPER_DIRT( sx, sy ); + if( *cluster < 0 || *dirt2 <= 0.0f ) + continue; + + /* add it */ + average += *dirt2; + samples += 1.0f; + } + + /* bail */ + if( samples <= 0.0f ) + break; + } + + /* bail */ + if( samples <= 0.0f ) + continue; + + /* scale dirt */ + *dirt = average / samples; + } + } +} + + + +/* +SubmapRawLuxel() +calculates the pvs cluster, origin, normal of a sub-luxel +*/ + +static qboolean SubmapRawLuxel( rawLightmap_t *lm, int x, int y, float bx, float by, int *sampleCluster, vec3_t sampleOrigin, vec3_t sampleNormal ) +{ + int i, *cluster, *cluster2; + float *origin, *origin2, *normal; //% , *normal2; + vec3_t originVecs[ 2 ]; //% , normalVecs[ 2 ]; + + + /* calulate x vector */ + if( (x < (lm->sw - 1) && bx >= 0.0f) || (x == 0 && bx <= 0.0f) ) + { + cluster = SUPER_CLUSTER( x, y ); + origin = SUPER_ORIGIN( x, y ); + //% normal = SUPER_NORMAL( x, y ); + cluster2 = SUPER_CLUSTER( x + 1, y ); + origin2 = *cluster2 < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x + 1, y ); + //% normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x + 1, y ); + } + else if( (x > 0 && bx <= 0.0f) || (x == (lm->sw - 1) && bx >= 0.0f) ) + { + cluster = SUPER_CLUSTER( x - 1, y ); + origin = *cluster < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x - 1, y ); + //% normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x - 1, y ); + cluster2 = SUPER_CLUSTER( x, y ); + origin2 = SUPER_ORIGIN( x, y ); + //% normal2 = SUPER_NORMAL( x, y ); + } + else + Sys_Printf( "WARNING: Spurious lightmap S vector\n" ); + + VectorSubtract( origin2, origin, originVecs[ 0 ] ); + //% VectorSubtract( normal2, normal, normalVecs[ 0 ] ); + + /* calulate y vector */ + if( (y < (lm->sh - 1) && bx >= 0.0f) || (y == 0 && bx <= 0.0f) ) + { + cluster = SUPER_CLUSTER( x, y ); + origin = SUPER_ORIGIN( x, y ); + //% normal = SUPER_NORMAL( x, y ); + cluster2 = SUPER_CLUSTER( x, y + 1 ); + origin2 = *cluster2 < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x, y + 1 ); + //% normal2 = *cluster2 < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y + 1 ); + } + else if( (y > 0 && bx <= 0.0f) || (y == (lm->sh - 1) && bx >= 0.0f) ) + { + cluster = SUPER_CLUSTER( x, y - 1 ); + origin = *cluster < 0 ? SUPER_ORIGIN( x, y ) : SUPER_ORIGIN( x, y - 1 ); + //% normal = *cluster < 0 ? SUPER_NORMAL( x, y ) : SUPER_NORMAL( x, y - 1 ); + cluster2 = SUPER_CLUSTER( x, y ); + origin2 = SUPER_ORIGIN( x, y ); + //% normal2 = SUPER_NORMAL( x, y ); + } + else + Sys_Printf( "WARNING: Spurious lightmap T vector\n" ); + + VectorSubtract( origin2, origin, originVecs[ 1 ] ); + //% VectorSubtract( normal2, normal, normalVecs[ 1 ] ); + + /* calculate new origin */ + //% VectorMA( origin, bx, originVecs[ 0 ], sampleOrigin ); + //% VectorMA( sampleOrigin, by, originVecs[ 1 ], sampleOrigin ); + for( i = 0; i < 3; i++ ) + sampleOrigin[ i ] = sampleOrigin[ i ] + (bx * originVecs[ 0 ][ i ]) + (by * originVecs[ 1 ][ i ]); + + /* get cluster */ + *sampleCluster = ClusterForPointExtFilter( sampleOrigin, (LUXEL_EPSILON * 2), lm->numLightClusters, lm->lightClusters ); + if( *sampleCluster < 0 ) + return qfalse; + + /* calculate new normal */ + //% VectorMA( normal, bx, normalVecs[ 0 ], sampleNormal ); + //% VectorMA( sampleNormal, by, normalVecs[ 1 ], sampleNormal ); + //% if( VectorNormalize( sampleNormal, sampleNormal ) <= 0.0f ) + //% return qfalse; + normal = SUPER_NORMAL( x, y ); + VectorCopy( normal, sampleNormal ); + + /* return ok */ + return qtrue; +} + + +/* +SubsampleRawLuxel_r() +recursively subsamples a luxel until its color gradient is low enough or subsampling limit is reached +*/ + +static void SubsampleRawLuxel_r( rawLightmap_t *lm, trace_t *trace, vec3_t sampleOrigin, int x, int y, float bias, float *lightLuxel ) +{ + int b, samples, mapped, lighted; + int cluster[ 4 ]; + vec4_t luxel[ 4 ]; + vec3_t origin[ 4 ], normal[ 4 ]; + float biasDirs[ 4 ][ 2 ] = { { -1.0f, -1.0f }, { 1.0f, -1.0f }, { -1.0f, 1.0f }, { 1.0f, 1.0f } }; + vec3_t color, total; + + + /* limit check */ + if( lightLuxel[ 3 ] >= lightSamples ) + return; + + /* setup */ + VectorClear( total ); + mapped = 0; + lighted = 0; + + /* make 2x2 subsample stamp */ + for( b = 0; b < 4; b++ ) + { + /* set origin */ + VectorCopy( sampleOrigin, origin[ b ] ); + + /* calculate position */ + if( !SubmapRawLuxel( lm, x, y, (bias * biasDirs[ b ][ 0 ]), (bias * biasDirs[ b ][ 1 ]), &cluster[ b ], origin[ b ], normal[ b ] ) ) + { + cluster[ b ] = -1; + continue; + } + mapped++; + + /* increment sample count */ + luxel[ b ][ 3 ] = lightLuxel[ 3 ] + 1.0f; + + /* setup trace */ + trace->cluster = *cluster; + VectorCopy( origin[ b ], trace->origin ); + VectorCopy( normal[ b ], trace->normal ); + + /* sample light */ + + LightContributionToSample( trace ); + + /* add to totals (fixme: make contrast function) */ + VectorCopy( trace->color, luxel[ b ] ); + VectorAdd( total, trace->color, total ); + if( (luxel[ b ][ 0 ] + luxel[ b ][ 1 ] + luxel[ b ][ 2 ]) > 0.0f ) + lighted++; + } + + /* subsample further? */ + if( (lightLuxel[ 3 ] + 1.0f) < lightSamples && + (total[ 0 ] > 4.0f || total[ 1 ] > 4.0f || total[ 2 ] > 4.0f) && + lighted != 0 && lighted != mapped ) + { + for( b = 0; b < 4; b++ ) + { + if( cluster[ b ] < 0 ) + continue; + SubsampleRawLuxel_r( lm, trace, origin[ b ], x, y, (bias * 0.25f), luxel[ b ] ); + } + } + + /* average */ + //% VectorClear( color ); + //% samples = 0; + VectorCopy( lightLuxel, color ); + samples = 1; + for( b = 0; b < 4; b++ ) + { + if( cluster[ b ] < 0 ) + continue; + VectorAdd( color, luxel[ b ], color ); + samples++; + } + + /* add to luxel */ + if( samples > 0 ) + { + /* average */ + color[ 0 ] /= samples; + color[ 1 ] /= samples; + color[ 2 ] /= samples; + + /* add to color */ + VectorCopy( color, lightLuxel ); + lightLuxel[ 3 ] += 1.0f; + } +} + + + +/* +IlluminateRawLightmap() +illuminates the luxels +*/ + +#define STACK_LL_SIZE (SUPER_LUXEL_SIZE * 64 * 64) +#define LIGHT_LUXEL( x, y ) (lightLuxels + ((((y) * lm->sw) + (x)) * SUPER_LUXEL_SIZE)) + +void IlluminateRawLightmap( int rawLightmapNum ) +{ + int i, t, x, y, sx, sy, size, llSize, luxelFilterRadius, lightmapNum; + int *cluster, *cluster2, mapped, lighted, totalLighted; + rawLightmap_t *lm; + surfaceInfo_t *info; + qboolean filterColor, filterDir; + float brightness; + float *origin, *normal, *dirt, *luxel, *luxel2, *deluxel, *deluxel2; + float *lightLuxels, *lightLuxel, samples, filterRadius, weight; + vec3_t color, averageColor, averageDir, total, temp, temp2; + float tests[ 4 ][ 2 ] = { { 0.0f, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }; + trace_t trace; + float stackLightLuxels[ STACK_LL_SIZE ]; + vec3_t flood; + float *floodlight; + + + /* bail if this number exceeds the number of raw lightmaps */ + if( rawLightmapNum >= numRawLightmaps ) + return; + + /* get lightmap */ + lm = &rawLightmaps[ rawLightmapNum ]; + + /* setup trace */ + trace.testOcclusion = !noTrace; + trace.forceSunlight = qfalse; + trace.recvShadows = lm->recvShadows; + trace.numSurfaces = lm->numLightSurfaces; + trace.surfaces = &lightSurfaces[ lm->firstLightSurface ]; + trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS; + + /* twosided lighting (may or may not be a good idea for lightmapped stuff) */ + trace.twoSided = qfalse; + for( i = 0; i < trace.numSurfaces; i++ ) + { + /* get surface */ + info = &surfaceInfos[ trace.surfaces[ i ] ]; + + /* check twosidedness */ + if( info->si->twoSided ) + { + trace.twoSided = qtrue; + break; + } + } + + /* create a culled light list for this raw lightmap */ + CreateTraceLightsForBounds( lm->mins, lm->maxs, lm->plane, lm->numLightClusters, lm->lightClusters, LIGHT_SURFACES, &trace ); + + /* ----------------------------------------------------------------- + fill pass + ----------------------------------------------------------------- */ + + /* set counts */ + numLuxelsIlluminated += (lm->sw * lm->sh); + + /* test debugging state */ + if( debugSurfaces || debugAxis || debugCluster || debugOrigin || dirtDebug || normalmap ) + { + /* debug fill the luxels */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + + /* only fill mapped luxels */ + if( *cluster < 0 ) + continue; + + /* get particulars */ + luxel = SUPER_LUXEL( 0, x, y ); + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + + /* color the luxel with raw lightmap num? */ + if( debugSurfaces ) + VectorCopy( debugColors[ rawLightmapNum % 12 ], luxel ); + + /* color the luxel with lightmap axis? */ + else if( debugAxis ) + { + luxel[ 0 ] = (lm->axis[ 0 ] + 1.0f) * 127.5f; + luxel[ 1 ] = (lm->axis[ 1 ] + 1.0f) * 127.5f; + luxel[ 2 ] = (lm->axis[ 2 ] + 1.0f) * 127.5f; + } + + /* color the luxel with luxel cluster? */ + else if( debugCluster ) + VectorCopy( debugColors[ *cluster % 12 ], luxel ); + + /* color the luxel with luxel origin? */ + else if( debugOrigin ) + { + VectorSubtract( lm->maxs, lm->mins, temp ); + VectorScale( temp, (1.0f / 255.0f), temp ); + VectorSubtract( origin, lm->mins, temp2 ); + luxel[ 0 ] = lm->mins[ 0 ] + (temp[ 0 ] * temp2[ 0 ]); + luxel[ 1 ] = lm->mins[ 1 ] + (temp[ 1 ] * temp2[ 1 ]); + luxel[ 2 ] = lm->mins[ 2 ] + (temp[ 2 ] * temp2[ 2 ]); + } + + /* color the luxel with the normal */ + else if( normalmap ) + { + luxel[ 0 ] = (normal[ 0 ] + 1.0f) * 127.5f; + luxel[ 1 ] = (normal[ 1 ] + 1.0f) * 127.5f; + luxel[ 2 ] = (normal[ 2 ] + 1.0f) * 127.5f; + } + + /* otherwise clear it */ + else + VectorClear( luxel ); + + /* add to counts */ + luxel[ 3 ] = 1.0f; + } + } + } + else + { + /* allocate temporary per-light luxel storage */ + llSize = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float ); + if( llSize <= (STACK_LL_SIZE * sizeof( float )) ) + lightLuxels = stackLightLuxels; + else + lightLuxels = safe_malloc( llSize ); + + /* clear luxels */ + //% memset( lm->superLuxels[ 0 ], 0, llSize ); + + /* set ambient color */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + luxel = SUPER_LUXEL( 0, x, y ); + normal = SUPER_NORMAL( x, y ); + deluxel = SUPER_DELUXEL( x, y ); + + /* blacken unmapped clusters */ + if( *cluster < 0 ) + VectorClear( luxel ); + + /* set ambient */ + else + { + VectorCopy( ambientColor, luxel ); + if( deluxemap ) + VectorScale( normal, 0.00390625f, deluxel ); + luxel[ 3 ] = 1.0f; + } + } + } + + /* clear styled lightmaps */ + size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float ); + for( lightmapNum = 1; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + if( lm->superLuxels[ lightmapNum ] != NULL ) + memset( lm->superLuxels[ lightmapNum ], 0, size ); + } + + /* debugging code */ + //% if( trace.numLights <= 0 ) + //% Sys_Printf( "Lightmap %9d: 0 lights, axis: %.2f, %.2f, %.2f\n", rawLightmapNum, lm->axis[ 0 ], lm->axis[ 1 ], lm->axis[ 2 ] ); + + /* walk light list */ + for( i = 0; i < trace.numLights; i++ ) + { + /* setup trace */ + trace.light = trace.lights[ i ]; + + /* style check */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + if( lm->styles[ lightmapNum ] == trace.light->style || + lm->styles[ lightmapNum ] == LS_NONE ) + break; + } + + /* max of MAX_LIGHTMAPS (4) styles allowed to hit a surface/lightmap */ + if( lightmapNum >= MAX_LIGHTMAPS ) + { + Sys_Printf( "WARNING: Hit per-surface style limit (%d)\n", MAX_LIGHTMAPS ); + continue; + } + + /* setup */ + memset( lightLuxels, 0, llSize ); + totalLighted = 0; + + /* initial pass, one sample per luxel */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + if( *cluster < 0 ) + continue; + + /* get particulars */ + lightLuxel = LIGHT_LUXEL( x, y ); + deluxel = SUPER_DELUXEL( x, y ); + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + + ////////// 27's temp hack for testing edge clipping //// + if( origin[0]==0 && origin[1]==0 && origin[2]==0 ) + { + lightLuxel[ 1 ] = 255; + lightLuxel[ 3 ] = 1.0f; + totalLighted++; + } + else + { + /* set contribution count */ + lightLuxel[ 3 ] = 1.0f; + + /* setup trace */ + trace.cluster = *cluster; + VectorCopy( origin, trace.origin ); + VectorCopy( normal, trace.normal ); + + /* get light for this sample */ + LightContributionToSample( &trace ); + VectorCopy( trace.color, lightLuxel ); + + /* add to count */ + if( trace.color[ 0 ] || trace.color[ 1 ] || trace.color[ 2 ] ) + totalLighted++; + } + + /* add to light direction map (fixme: use luxel normal as starting point for deluxel?) */ + if( deluxemap ) + { + /* color to grayscale (photoshop rgb weighting) */ + brightness = trace.color[ 0 ] * 0.3f + trace.color[ 1 ] * 0.59f + trace.color[ 2 ] * 0.11f; + brightness *= (1.0 / 255.0); + VectorScale( trace.direction, brightness, trace.direction ); + VectorAdd( deluxel, trace.direction, deluxel ); + } + } + } + + /* don't even bother with everything else if nothing was lit */ + if( totalLighted == 0 ) + continue; + + /* determine filter radius */ + filterRadius = lm->filterRadius > trace.light->filterRadius + ? lm->filterRadius + : trace.light->filterRadius; + if( filterRadius < 0.0f ) + filterRadius = 0.0f; + + /* set luxel filter radius */ + luxelFilterRadius = superSample * filterRadius / lm->sampleSize; + if( luxelFilterRadius == 0 && (filterRadius > 0.0f || filter) ) + luxelFilterRadius = 1; + + /* secondary pass, adaptive supersampling (fixme: use a contrast function to determine if subsampling is necessary) */ + /* 2003-09-27: changed it so filtering disamples supersampling, as it would waste time */ + if( lightSamples > 1 && luxelFilterRadius == 0 ) + { + /* walk luxels */ + for( y = 0; y < (lm->sh - 1); y++ ) + { + for( x = 0; x < (lm->sw - 1); x++ ) + { + /* setup */ + mapped = 0; + lighted = 0; + VectorClear( total ); + + /* test 2x2 stamp */ + for( t = 0; t < 4; t++ ) + { + /* set sample coords */ + sx = x + tests[ t ][ 0 ]; + sy = y + tests[ t ][ 1 ]; + + /* get cluster */ + cluster = SUPER_CLUSTER( sx, sy ); + if( *cluster < 0 ) + continue; + mapped++; + + /* get luxel */ + lightLuxel = LIGHT_LUXEL( sx, sy ); + VectorAdd( total, lightLuxel, total ); + if( (lightLuxel[ 0 ] + lightLuxel[ 1 ] + lightLuxel[ 2 ]) > 0.0f ) + lighted++; + } + + /* if total color is under a certain amount, then don't bother subsampling */ + if( total[ 0 ] <= 4.0f && total[ 1 ] <= 4.0f && total[ 2 ] <= 4.0f ) + continue; + + /* if all 4 pixels are either in shadow or light, then don't subsample */ + if( lighted != 0 && lighted != mapped ) + { + for( t = 0; t < 4; t++ ) + { + /* set sample coords */ + sx = x + tests[ t ][ 0 ]; + sy = y + tests[ t ][ 1 ]; + + /* get luxel */ + cluster = SUPER_CLUSTER( sx, sy ); + if( *cluster < 0 ) + continue; + lightLuxel = LIGHT_LUXEL( sx, sy ); + origin = SUPER_ORIGIN( sx, sy ); + + /* only subsample shadowed luxels */ + //% if( (lightLuxel[ 0 ] + lightLuxel[ 1 ] + lightLuxel[ 2 ]) <= 0.0f ) + //% continue; + + /* subsample it */ + SubsampleRawLuxel_r( lm, &trace, origin, sx, sy, 0.25f, lightLuxel ); + + /* debug code to colorize subsampled areas to yellow */ + //% luxel = SUPER_LUXEL( lightmapNum, sx, sy ); + //% VectorSet( luxel, 255, 204, 0 ); + } + } + } + } + } + + /* tertiary pass, apply dirt map (ambient occlusion) */ + if( 0 && dirty ) + { + /* walk luxels */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + if( *cluster < 0 ) + continue; + + /* get particulars */ + lightLuxel = LIGHT_LUXEL( x, y ); + dirt = SUPER_DIRT( x, y ); + + /* scale light value */ + VectorScale( lightLuxel, *dirt, lightLuxel ); + } + } + } + + /* allocate sampling lightmap storage */ + if( lm->superLuxels[ lightmapNum ] == NULL ) + { + /* allocate sampling lightmap storage */ + size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float ); + lm->superLuxels[ lightmapNum ] = safe_malloc( size ); + memset( lm->superLuxels[ lightmapNum ], 0, size ); + } + + /* set style */ + if( lightmapNum > 0 ) + { + lm->styles[ lightmapNum ] = trace.light->style; + //% Sys_Printf( "Surface %6d has lightstyle %d\n", rawLightmapNum, trace.light->style ); + } + + /* copy to permanent luxels */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get cluster and origin */ + cluster = SUPER_CLUSTER( x, y ); + if( *cluster < 0 ) + continue; + origin = SUPER_ORIGIN( x, y ); + + /* filter? */ + if( luxelFilterRadius ) + { + /* setup */ + VectorClear( averageColor ); + samples = 0.0f; + + /* cheaper distance-based filtering */ + for( sy = (y - luxelFilterRadius); sy <= (y + luxelFilterRadius); sy++ ) + { + if( sy < 0 || sy >= lm->sh ) + continue; + + for( sx = (x - luxelFilterRadius); sx <= (x + luxelFilterRadius); sx++ ) + { + if( sx < 0 || sx >= lm->sw ) + continue; + + /* get particulars */ + cluster = SUPER_CLUSTER( sx, sy ); + if( *cluster < 0 ) + continue; + lightLuxel = LIGHT_LUXEL( sx, sy ); + + /* create weight */ + weight = (abs( sx - x ) == luxelFilterRadius ? 0.5f : 1.0f); + weight *= (abs( sy - y ) == luxelFilterRadius ? 0.5f : 1.0f); + + /* scale luxel by filter weight */ + VectorScale( lightLuxel, weight, color ); + VectorAdd( averageColor, color, averageColor ); + samples += weight; + } + } + + /* any samples? */ + if( samples <= 0.0f ) + continue; + + /* scale into luxel */ + luxel = SUPER_LUXEL( lightmapNum, x, y ); + luxel[ 3 ] = 1.0f; + + /* handle negative light */ + if( trace.light->flags & LIGHT_NEGATIVE ) + { + luxel[ 0 ] -= averageColor[ 0 ] / samples; + luxel[ 1 ] -= averageColor[ 1 ] / samples; + luxel[ 2 ] -= averageColor[ 2 ] / samples; + } + + /* handle normal light */ + else + { + luxel[ 0 ] += averageColor[ 0 ] / samples; + luxel[ 1 ] += averageColor[ 1 ] / samples; + luxel[ 2 ] += averageColor[ 2 ] / samples; + } + } + + /* single sample */ + else + { + /* get particulars */ + lightLuxel = LIGHT_LUXEL( x, y ); + luxel = SUPER_LUXEL( lightmapNum, x, y ); + + /* handle negative light */ + if( trace.light->flags & LIGHT_NEGATIVE ) + VectorScale( averageColor, -1.0f, averageColor ); + + /* add color */ + luxel[ 3 ] = 1.0f; + + /* handle negative light */ + if( trace.light->flags & LIGHT_NEGATIVE ) + VectorSubtract( luxel, lightLuxel, luxel ); + + /* handle normal light */ + else + VectorAdd( luxel, lightLuxel, luxel ); + } + } + } + } + + /* free temporary luxels */ + if( lightLuxels != stackLightLuxels ) + free( lightLuxels ); + } + + /* free light list */ + FreeTraceLights( &trace ); + + /* ----------------------------------------------------------------- + floodlight pass + ----------------------------------------------------------------- */ + + if( floodlighty ) + { + /* walk lightmaps */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if( lm->superLuxels[ lightmapNum ] == NULL ) + continue; + + /* apply floodlight to each luxel */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + //% if( *cluster < 0 ) + //% continue; + + /* get particulars */ + luxel = SUPER_LUXEL( lightmapNum, x, y ); + floodlight = SUPER_FLOODLIGHT( x, y ); + + flood[0]=floodlightRGB[0]*floodlightIntensity; + flood[1]=floodlightRGB[1]*floodlightIntensity; + flood[2]=floodlightRGB[2]*floodlightIntensity; + + /* scale light value */ + VectorScale( flood, *floodlight, flood ); + luxel[0]+=flood[0]; + luxel[1]+=flood[1]; + luxel[2]+=flood[2]; + + if (luxel[3]==0) luxel[3]=1; + } + } + } + } + + if (debugnormals) + { + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if( lm->superLuxels[ lightmapNum ] == NULL ) + continue; + + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + //% if( *cluster < 0 ) + //% continue; + + /* get particulars */ + luxel = SUPER_LUXEL( lightmapNum, x, y ); + normal = SUPER_NORMAL ( x, y ); + + luxel[0]=(normal[0]*127)+127; + luxel[1]=(normal[1]*127)+127; + luxel[2]=(normal[2]*127)+127; + } + } + } + } + + /* ----------------------------------------------------------------- + dirt pass + ----------------------------------------------------------------- */ + + if( dirty ) + { + /* walk lightmaps */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if( lm->superLuxels[ lightmapNum ] == NULL ) + continue; + + /* apply dirt to each luxel */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get cluster */ + cluster = SUPER_CLUSTER( x, y ); + //% if( *cluster < 0 ) + //% continue; + + /* get particulars */ + luxel = SUPER_LUXEL( lightmapNum, x, y ); + dirt = SUPER_DIRT( x, y ); + + /* apply dirt */ + VectorScale( luxel, *dirt, luxel ); + + /* debugging */ + if( dirtDebug ) + VectorSet( luxel, *dirt * 255.0f, *dirt * 255.0f, *dirt * 255.0f ); + } + } + } + } + + /* ----------------------------------------------------------------- + filter pass + ----------------------------------------------------------------- */ + + /* walk lightmaps */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if( lm->superLuxels[ lightmapNum ] == NULL ) + continue; + + /* average occluded luxels from neighbors */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get particulars */ + cluster = SUPER_CLUSTER( x, y ); + luxel = SUPER_LUXEL( lightmapNum, x, y ); + deluxel = SUPER_DELUXEL( x, y ); + normal = SUPER_NORMAL( x, y ); + + /* determine if filtering is necessary */ + filterColor = qfalse; + filterDir = qfalse; + if( *cluster < 0 || + (lm->splotchFix && (luxel[ 0 ] <= ambientColor[ 0 ] || luxel[ 1 ] <= ambientColor[ 1 ] || luxel[ 2 ] <= ambientColor[ 2 ])) ) + filterColor = qtrue; + if( deluxemap && lightmapNum == 0 && (*cluster < 0 || filter) ) + filterDir = qtrue; + + if( !filterColor && !filterDir ) + continue; + + /* choose seed amount */ + VectorClear( averageColor ); + VectorClear( averageDir ); + samples = 0.0f; + + /* walk 3x3 matrix */ + for( sy = (y - 1); sy <= (y + 1); sy++ ) + { + if( sy < 0 || sy >= lm->sh ) + continue; + + for( sx = (x - 1); sx <= (x + 1); sx++ ) + { + if( sx < 0 || sx >= lm->sw || (sx == x && sy == y) ) + continue; + + /* get neighbor's particulars */ + cluster2 = SUPER_CLUSTER( sx, sy ); + luxel2 = SUPER_LUXEL( lightmapNum, sx, sy ); + deluxel2 = SUPER_DELUXEL( sx, sy ); + + /* ignore unmapped/unlit luxels */ + if( *cluster2 < 0 || luxel2[ 3 ] == 0.0f || + (lm->splotchFix && VectorCompare( luxel2, ambientColor )) ) + continue; + + /* add its distinctiveness to our own */ + VectorAdd( averageColor, luxel2, averageColor ); + samples += luxel2[ 3 ]; + if( filterDir ) + VectorAdd( averageDir, deluxel2, averageDir ); + } + } + + /* fall through */ + if( samples <= 0.0f ) + continue; + + /* dark lightmap seams */ + if( dark ) + { + if( lightmapNum == 0 ) + VectorMA( averageColor, 2.0f, ambientColor, averageColor ); + samples += 2.0f; + } + + /* average it */ + if( filterColor ) + { + VectorDivide( averageColor, samples, luxel ); + luxel[ 3 ] = 1.0f; + } + if( filterDir ) + VectorDivide( averageDir, samples, deluxel ); + + /* set cluster to -3 */ + if( *cluster < 0 ) + *cluster = CLUSTER_FLOODED; + } + } + } +} + + + +/* +IlluminateVertexes() +light the surface vertexes +*/ + +#define VERTEX_NUDGE 4.0f + +void IlluminateVertexes( int num ) +{ + int i, x, y, z, x1, y1, z1, sx, sy, radius, maxRadius, *cluster; + int lightmapNum, numAvg; + float samples, *vertLuxel, *radVertLuxel, *luxel, dirt; + vec3_t origin, temp, temp2, colors[ MAX_LIGHTMAPS ], avgColors[ MAX_LIGHTMAPS ]; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + rawLightmap_t *lm; + bspDrawVert_t *verts; + trace_t trace; + + + /* get surface, info, and raw lightmap */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + lm = info->lm; + + /* ----------------------------------------------------------------- + illuminate the vertexes + ----------------------------------------------------------------- */ + + /* calculate vertex lighting for surfaces without lightmaps */ + if( lm == NULL || cpmaHack ) + { + /* setup trace */ + trace.testOcclusion = (cpmaHack && lm != NULL) ? qfalse : !noTrace; + trace.forceSunlight = info->si->forceSunlight; + trace.recvShadows = info->recvShadows; + trace.numSurfaces = 1; + trace.surfaces = # + trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS; + + /* twosided lighting */ + trace.twoSided = info->si->twoSided; + + /* make light list for this surface */ + CreateTraceLightsForSurface( num, &trace ); + + /* setup */ + verts = yDrawVerts + ds->firstVert; + numAvg = 0; + memset( avgColors, 0, sizeof( avgColors ) ); + + /* walk the surface verts */ + for( i = 0; i < ds->numVerts; i++ ) + { + /* get vertex luxel */ + radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); + + /* color the luxel with raw lightmap num? */ + if( debugSurfaces ) + VectorCopy( debugColors[ num % 12 ], radVertLuxel ); + + /* color the luxel with luxel origin? */ + else if( debugOrigin ) + { + VectorSubtract( info->maxs, info->mins, temp ); + VectorScale( temp, (1.0f / 255.0f), temp ); + VectorSubtract( origin, lm->mins, temp2 ); + radVertLuxel[ 0 ] = info->mins[ 0 ] + (temp[ 0 ] * temp2[ 0 ]); + radVertLuxel[ 1 ] = info->mins[ 1 ] + (temp[ 1 ] * temp2[ 1 ]); + radVertLuxel[ 2 ] = info->mins[ 2 ] + (temp[ 2 ] * temp2[ 2 ]); + } + + /* color the luxel with the normal */ + else if( normalmap ) + { + radVertLuxel[ 0 ] = (verts[ i ].normal[ 0 ] + 1.0f) * 127.5f; + radVertLuxel[ 1 ] = (verts[ i ].normal[ 1 ] + 1.0f) * 127.5f; + radVertLuxel[ 2 ] = (verts[ i ].normal[ 2 ] + 1.0f) * 127.5f; + } + + /* illuminate the vertex */ + else + { + /* clear vertex luxel */ + VectorSet( radVertLuxel, -1.0f, -1.0f, -1.0f ); + + /* try at initial origin */ + trace.cluster = ClusterForPointExtFilter( verts[ i ].xyz, VERTEX_EPSILON, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ] ); + if( trace.cluster >= 0 ) + { + /* setup trace */ + VectorCopy( verts[ i ].xyz, trace.origin ); + VectorCopy( verts[ i ].normal, trace.normal ); + + /* r7 dirt */ + if( dirty ) + dirt = DirtForSample( &trace ); + else + dirt = 1.0f; + + /* trace */ + LightingAtSample( &trace, ds->vertexStyles, colors ); + + /* store */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* r7 dirt */ + VectorScale( colors[ lightmapNum ], dirt, colors[ lightmapNum ] ); + + /* store */ + radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + VectorCopy( colors[ lightmapNum ], radVertLuxel ); + VectorAdd( avgColors[ lightmapNum ], colors[ lightmapNum ], colors[ lightmapNum ] ); + } + } + + /* is this sample bright enough? */ + radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); + if( radVertLuxel[ 0 ] <= ambientColor[ 0 ] && + radVertLuxel[ 1 ] <= ambientColor[ 1 ] && + radVertLuxel[ 2 ] <= ambientColor[ 2 ] ) + { + /* nudge the sample point around a bit */ + for( x = 0; x < 4; x++ ) + { + /* two's complement 0, 1, -1, 2, -2, etc */ + x1 = ((x >> 1) ^ (x & 1 ? -1 : 0)) + (x & 1); + + for( y = 0; y < 4; y++ ) + { + y1 = ((y >> 1) ^ (y & 1 ? -1 : 0)) + (y & 1); + + for( z = 0; z < 4; z++ ) + { + z1 = ((z >> 1) ^ (z & 1 ? -1 : 0)) + (z & 1); + + /* nudge origin */ + trace.origin[ 0 ] = verts[ i ].xyz[ 0 ] + (VERTEX_NUDGE * x1); + trace.origin[ 1 ] = verts[ i ].xyz[ 1 ] + (VERTEX_NUDGE * y1); + trace.origin[ 2 ] = verts[ i ].xyz[ 2 ] + (VERTEX_NUDGE * z1); + + /* try at nudged origin */ + trace.cluster = ClusterForPointExtFilter( origin, VERTEX_EPSILON, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ] ); + if( trace.cluster < 0 ) + continue; + + /* trace */ + LightingAtSample( &trace, ds->vertexStyles, colors ); + + /* store */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* r7 dirt */ + VectorScale( colors[ lightmapNum ], dirt, colors[ lightmapNum ] ); + + /* store */ + radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + VectorCopy( colors[ lightmapNum ], radVertLuxel ); + } + + /* bright enough? */ + radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); + if( radVertLuxel[ 0 ] > ambientColor[ 0 ] || + radVertLuxel[ 1 ] > ambientColor[ 1 ] || + radVertLuxel[ 2 ] > ambientColor[ 2 ] ) + x = y = z = 1000; + } + } + } + } + + /* add to average? */ + radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); + if( radVertLuxel[ 0 ] > ambientColor[ 0 ] || + radVertLuxel[ 1 ] > ambientColor[ 1 ] || + radVertLuxel[ 2 ] > ambientColor[ 2 ] ) + { + numAvg++; + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + VectorAdd( avgColors[ lightmapNum ], radVertLuxel, avgColors[ lightmapNum ] ); + } + } + } + + /* another happy customer */ + numVertsIlluminated++; + } + + /* set average color */ + if( numAvg > 0 ) + { + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + VectorScale( avgColors[ lightmapNum ], (1.0f / numAvg), avgColors[ lightmapNum ] ); + } + else + { + VectorCopy( ambientColor, avgColors[ 0 ] ); + } + + /* clean up and store vertex color */ + for( i = 0; i < ds->numVerts; i++ ) + { + /* get vertex luxel */ + radVertLuxel = RAD_VERTEX_LUXEL( 0, ds->firstVert + i ); + + /* store average in occluded vertexes */ + if( radVertLuxel[ 0 ] < 0.0f ) + { + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + VectorCopy( avgColors[ lightmapNum ], radVertLuxel ); + + /* debug code */ + //% VectorSet( radVertLuxel, 255.0f, 0.0f, 0.0f ); + } + } + + /* store it */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* get luxels */ + vertLuxel = VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + + /* store */ + if( bouncing || bounce == 0 || !bounceOnly ) + VectorAdd( vertLuxel, radVertLuxel, vertLuxel ); + if( !info->si->noVertexLight ) + ColorToBytes( vertLuxel, verts[ i ].color[ lightmapNum ], info->si->vertexScale ); + } + } + + /* free light list */ + FreeTraceLights( &trace ); + + /* return to sender */ + return; + } + + /* ----------------------------------------------------------------- + reconstitute vertex lighting from the luxels + ----------------------------------------------------------------- */ + + /* set styles from lightmap */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + ds->vertexStyles[ lightmapNum ] = lm->styles[ lightmapNum ]; + + /* get max search radius */ + maxRadius = lm->sw; + maxRadius = maxRadius > lm->sh ? maxRadius : lm->sh; + + /* walk the surface verts */ + verts = yDrawVerts + ds->firstVert; + for( i = 0; i < ds->numVerts; i++ ) + { + /* do each lightmap */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if( lm->superLuxels[ lightmapNum ] == NULL ) + continue; + + /* get luxel coords */ + x = verts[ i ].lightmap[ lightmapNum ][ 0 ]; + y = verts[ i ].lightmap[ lightmapNum ][ 1 ]; + if( x < 0 ) + x = 0; + else if( x >= lm->sw ) + x = lm->sw - 1; + if( y < 0 ) + y = 0; + else if( y >= lm->sh ) + y = lm->sh - 1; + + /* get vertex luxels */ + vertLuxel = VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + radVertLuxel = RAD_VERTEX_LUXEL( lightmapNum, ds->firstVert + i ); + + /* color the luxel with the normal? */ + if( normalmap ) + { + radVertLuxel[ 0 ] = (verts[ i ].normal[ 0 ] + 1.0f) * 127.5f; + radVertLuxel[ 1 ] = (verts[ i ].normal[ 1 ] + 1.0f) * 127.5f; + radVertLuxel[ 2 ] = (verts[ i ].normal[ 2 ] + 1.0f) * 127.5f; + } + + /* color the luxel with surface num? */ + else if( debugSurfaces ) + VectorCopy( debugColors[ num % 12 ], radVertLuxel ); + + /* divine color from the superluxels */ + else + { + /* increasing radius */ + VectorClear( radVertLuxel ); + samples = 0.0f; + for( radius = 0; radius < maxRadius && samples <= 0.0f; radius++ ) + { + /* sample within radius */ + for( sy = (y - radius); sy <= (y + radius); sy++ ) + { + if( sy < 0 || sy >= lm->sh ) + continue; + + for( sx = (x - radius); sx <= (x + radius); sx++ ) + { + if( sx < 0 || sx >= lm->sw ) + continue; + + /* get luxel particulars */ + luxel = SUPER_LUXEL( lightmapNum, sx, sy ); + cluster = SUPER_CLUSTER( sx, sy ); + if( *cluster < 0 ) + continue; + + /* testing: must be brigher than ambient color */ + //% if( luxel[ 0 ] <= ambientColor[ 0 ] || luxel[ 1 ] <= ambientColor[ 1 ] || luxel[ 2 ] <= ambientColor[ 2 ] ) + //% continue; + + /* add its distinctiveness to our own */ + VectorAdd( radVertLuxel, luxel, radVertLuxel ); + samples += luxel[ 3 ]; + } + } + } + + /* any color? */ + if( samples > 0.0f ) + VectorDivide( radVertLuxel, samples, radVertLuxel ); + else + VectorCopy( ambientColor, radVertLuxel ); + } + + /* store into floating point storage */ + VectorAdd( vertLuxel, radVertLuxel, vertLuxel ); + numVertsIlluminated++; + + /* store into bytes (for vertex approximation) */ + if( !info->si->noVertexLight ) + ColorToBytes( vertLuxel, verts[ i ].color[ lightmapNum ], 1.0f ); + } + } +} + + + +/* ------------------------------------------------------------------------------- + +light optimization (-fast) + +creates a list of lights that will affect a surface and stores it in tw +this is to optimize surface lighting by culling out as many of the +lights in the world as possible from further calculation + +------------------------------------------------------------------------------- */ + +/* +SetupBrushes() +determines opaque brushes in the world and find sky shaders for sunlight calculations +*/ + +void SetupBrushes( void ) +{ + int i, j, b, compileFlags; + qboolean inside; + bspBrush_t *brush; + bspBrushSide_t *side; + bspShader_t *shader; + shaderInfo_t *si; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupBrushes ---\n" ); + + /* allocate */ + if( opaqueBrushes == NULL ) + opaqueBrushes = safe_malloc( numBSPBrushes / 8 + 1 ); + + /* clear */ + memset( opaqueBrushes, 0, numBSPBrushes / 8 + 1 ); + numOpaqueBrushes = 0; + + /* walk the list of worldspawn brushes */ + for( i = 0; i < bspModels[ 0 ].numBSPBrushes; i++ ) + { + /* get brush */ + b = bspModels[ 0 ].firstBSPBrush + i; + brush = &bspBrushes[ b ]; + + /* check all sides */ + inside = qtrue; + compileFlags = 0; + for( j = 0; j < brush->numSides && inside; j++ ) + { + /* do bsp shader calculations */ + side = &bspBrushSides[ brush->firstSide + j ]; + shader = &bspShaders[ side->shaderNum ]; + + /* get shader info */ + si = ShaderInfoForShader( shader->shader ); + if( si == NULL ) + continue; + + /* or together compile flags */ + compileFlags |= si->compileFlags; + } + + /* determine if this brush is opaque to light */ + if( !(compileFlags & C_TRANSLUCENT) ) + { + opaqueBrushes[ b >> 3 ] |= (1 << (b & 7)); + numOpaqueBrushes++; + maxOpaqueBrush = i; + } + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d opaque brushes\n", numOpaqueBrushes ); +} + + + +/* +ClusterVisible() +determines if two clusters are visible to each other using the PVS +*/ + +qboolean ClusterVisible( int a, int b ) +{ + int portalClusters, leafBytes; + byte *pvs; + + + /* dummy check */ + if( a < 0 || b < 0 ) + return qfalse; + + /* early out */ + if( a == b ) + return qtrue; + + /* not vised? */ + if( numBSPVisBytes <=8 ) + return qtrue; + + /* get pvs data */ + portalClusters = ((int *) bspVisBytes)[ 0 ]; + leafBytes = ((int*) bspVisBytes)[ 1 ]; + pvs = bspVisBytes + VIS_HEADER_SIZE + (a * leafBytes); + + /* check */ + if( (pvs[ b >> 3 ] & (1 << (b & 7))) ) + return qtrue; + return qfalse; +} + + + +/* +PointInLeafNum_r() +borrowed from vlight.c +*/ + +int PointInLeafNum_r( vec3_t point, int nodenum ) +{ + int leafnum; + vec_t dist; + bspNode_t *node; + bspPlane_t *plane; + + + while( nodenum >= 0 ) + { + node = &bspNodes[ nodenum ]; + plane = &bspPlanes[ node->planeNum ]; + dist = DotProduct( point, plane->normal ) - plane->dist; + if( dist > 0.1 ) + nodenum = node->children[ 0 ]; + else if( dist < -0.1 ) + nodenum = node->children[ 1 ]; + else + { + leafnum = PointInLeafNum_r( point, node->children[ 0 ] ); + if( bspLeafs[ leafnum ].cluster != -1 ) + return leafnum; + nodenum = node->children[ 1 ]; + } + } + + leafnum = -nodenum - 1; + return leafnum; +} + + + +/* +PointInLeafnum() +borrowed from vlight.c +*/ + +int PointInLeafNum( vec3_t point ) +{ + return PointInLeafNum_r( point, 0 ); +} + + + +/* +ClusterVisibleToPoint() - ydnar +returns qtrue if point can "see" cluster +*/ + +qboolean ClusterVisibleToPoint( vec3_t point, int cluster ) +{ + int pointCluster; + + + /* get leafNum for point */ + pointCluster = ClusterForPoint( point ); + if( pointCluster < 0 ) + return qfalse; + + /* check pvs */ + return ClusterVisible( pointCluster, cluster ); +} + + + +/* +ClusterForPoint() - ydnar +returns the pvs cluster for point +*/ + +int ClusterForPoint( vec3_t point ) +{ + int leafNum; + + + /* get leafNum for point */ + leafNum = PointInLeafNum( point ); + if( leafNum < 0 ) + return -1; + + /* return the cluster */ + return bspLeafs[ leafNum ].cluster; +} + + + +/* +ClusterForPointExt() - ydnar +also takes brushes into account for occlusion testing +*/ + +int ClusterForPointExt( vec3_t point, float epsilon ) +{ + int i, j, b, leafNum, cluster; + float dot; + qboolean inside; + int *brushes, numBSPBrushes; + bspLeaf_t *leaf; + bspBrush_t *brush; + bspPlane_t *plane; + + + /* get leaf for point */ + leafNum = PointInLeafNum( point ); + if( leafNum < 0 ) + return -1; + leaf = &bspLeafs[ leafNum ]; + + /* get the cluster */ + cluster = leaf->cluster; + if( cluster < 0 ) + return -1; + + /* transparent leaf, so check point against all brushes in the leaf */ + brushes = &bspLeafBrushes[ leaf->firstBSPLeafBrush ]; + numBSPBrushes = leaf->numBSPLeafBrushes; + for( i = 0; i < numBSPBrushes; i++ ) + { + /* get parts */ + b = brushes[ i ]; + if( b > maxOpaqueBrush ) + continue; + brush = &bspBrushes[ b ]; + if( !(opaqueBrushes[ b >> 3 ] & (1 << (b & 7))) ) + continue; + + /* check point against all planes */ + inside = qtrue; + for( j = 0; j < brush->numSides && inside; j++ ) + { + plane = &bspPlanes[ bspBrushSides[ brush->firstSide + j ].planeNum ]; + dot = DotProduct( point, plane->normal ); + dot -= plane->dist; + if( dot > epsilon ) + inside = qfalse; + } + + /* if inside, return bogus cluster */ + if( inside ) + return -1 - b; + } + + /* if the point made it this far, it's not inside any opaque brushes */ + return cluster; +} + + + +/* +ClusterForPointExtFilter() - ydnar +adds cluster checking against a list of known valid clusters +*/ + +int ClusterForPointExtFilter( vec3_t point, float epsilon, int numClusters, int *clusters ) +{ + int i, cluster; + + + /* get cluster for point */ + cluster = ClusterForPointExt( point, epsilon ); + + /* check if filtering is necessary */ + if( cluster < 0 || numClusters <= 0 || clusters == NULL ) + return cluster; + + /* filter */ + for( i = 0; i < numClusters; i++ ) + { + if( cluster == clusters[ i ] || ClusterVisible( cluster, clusters[ i ] ) ) + return cluster; + } + + /* failed */ + return -1; +} + + + +/* +ShaderForPointInLeaf() - ydnar +checks a point against all brushes in a leaf, returning the shader of the brush +also sets the cumulative surface and content flags for the brush hit +*/ + +int ShaderForPointInLeaf( vec3_t point, int leafNum, float epsilon, int wantContentFlags, int wantSurfaceFlags, int *contentFlags, int *surfaceFlags ) +{ + int i, j; + float dot; + qboolean inside; + int *brushes, numBSPBrushes; + bspLeaf_t *leaf; + bspBrush_t *brush; + bspBrushSide_t *side; + bspPlane_t *plane; + bspShader_t *shader; + int allSurfaceFlags, allContentFlags; + + + /* clear things out first */ + *surfaceFlags = 0; + *contentFlags = 0; + + /* get leaf */ + if( leafNum < 0 ) + return -1; + leaf = &bspLeafs[ leafNum ]; + + /* transparent leaf, so check point against all brushes in the leaf */ + brushes = &bspLeafBrushes[ leaf->firstBSPLeafBrush ]; + numBSPBrushes = leaf->numBSPLeafBrushes; + for( i = 0; i < numBSPBrushes; i++ ) + { + /* get parts */ + brush = &bspBrushes[ brushes[ i ] ]; + + /* check point against all planes */ + inside = qtrue; + allSurfaceFlags = 0; + allContentFlags = 0; + for( j = 0; j < brush->numSides && inside; j++ ) + { + side = &bspBrushSides[ brush->firstSide + j ]; + plane = &bspPlanes[ side->planeNum ]; + dot = DotProduct( point, plane->normal ); + dot -= plane->dist; + if( dot > epsilon ) + inside = qfalse; + else + { + shader = &bspShaders[ side->shaderNum ]; + allSurfaceFlags |= shader->surfaceFlags; + allContentFlags |= shader->contentFlags; + } + } + + /* handle if inside */ + if( inside ) + { + /* if there are desired flags, check for same and continue if they aren't matched */ + if( wantContentFlags && !(wantContentFlags & allContentFlags) ) + continue; + if( wantSurfaceFlags && !(wantSurfaceFlags & allSurfaceFlags) ) + continue; + + /* store the cumulative flags and return the brush shader (which is mostly useless) */ + *surfaceFlags = allSurfaceFlags; + *contentFlags = allContentFlags; + return brush->shaderNum; + } + } + + /* if the point made it this far, it's not inside any brushes */ + return -1; +} + + + +/* +ChopBounds() +chops a bounding box by the plane defined by origin and normal +returns qfalse if the bounds is entirely clipped away + +this is not exactly the fastest way to do this... +*/ + +qboolean ChopBounds( vec3_t mins, vec3_t maxs, vec3_t origin, vec3_t normal ) +{ + /* FIXME: rewrite this so it doesn't use bloody brushes */ + return qtrue; +} + + + +/* +SetupEnvelopes() +calculates each light's effective envelope, +taking into account brightness, type, and pvs. +*/ + +#define LIGHT_EPSILON 0.125f +#define LIGHT_NUDGE 2.0f + +void SetupEnvelopes( qboolean forGrid, qboolean fastFlag ) +{ + int i, x, y, z, x1, y1, z1; + light_t *light, *light2, **owner; + bspLeaf_t *leaf; + vec3_t origin, dir, mins, maxs, nullVector = { 0, 0, 0 }; + float radius, intensity; + light_t *buckets[ 256 ]; + + + /* early out for weird cases where there are no lights */ + if( lights == NULL ) + return; + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupEnvelopes%s ---\n", fastFlag ? " (fast)" : "" ); + + /* count lights */ + numLights = 0; + numCulledLights = 0; + owner = &lights; + while( *owner != NULL ) + { + /* get light */ + light = *owner; + + /* handle negative lights */ + if( light->photons < 0.0f || light->add < 0.0f ) + { + light->photons *= -1.0f; + light->add *= -1.0f; + light->flags |= LIGHT_NEGATIVE; + } + + /* sunlight? */ + if( light->type == EMIT_SUN ) + { + /* special cased */ + light->cluster = 0; + light->envelope = MAX_WORLD_COORD * 8.0f; + VectorSet( light->mins, MIN_WORLD_COORD * 8.0f, MIN_WORLD_COORD * 8.0f, MIN_WORLD_COORD * 8.0f ); + VectorSet( light->maxs, MAX_WORLD_COORD * 8.0f, MAX_WORLD_COORD * 8.0f, MAX_WORLD_COORD * 8.0f ); + } + + /* everything else */ + else + { + /* get pvs cluster for light */ + light->cluster = ClusterForPointExt( light->origin, LIGHT_EPSILON ); + + /* invalid cluster? */ + if( light->cluster < 0 ) + { + /* nudge the sample point around a bit */ + for( x = 0; x < 4; x++ ) + { + /* two's complement 0, 1, -1, 2, -2, etc */ + x1 = ((x >> 1) ^ (x & 1 ? -1 : 0)) + (x & 1); + + for( y = 0; y < 4; y++ ) + { + y1 = ((y >> 1) ^ (y & 1 ? -1 : 0)) + (y & 1); + + for( z = 0; z < 4; z++ ) + { + z1 = ((z >> 1) ^ (z & 1 ? -1 : 0)) + (z & 1); + + /* nudge origin */ + origin[ 0 ] = light->origin[ 0 ] + (LIGHT_NUDGE * x1); + origin[ 1 ] = light->origin[ 1 ] + (LIGHT_NUDGE * y1); + origin[ 2 ] = light->origin[ 2 ] + (LIGHT_NUDGE * z1); + + /* try at nudged origin */ + light->cluster = ClusterForPointExt( origin, LIGHT_EPSILON ); + if( light->cluster < 0 ) + continue; + + /* set origin */ + VectorCopy( origin, light->origin ); + } + } + } + } + + /* only calculate for lights in pvs and outside of opaque brushes */ + if( light->cluster >= 0 ) + { + /* set light fast flag */ + if( fastFlag ) + light->flags |= LIGHT_FAST_TEMP; + else + light->flags &= ~LIGHT_FAST_TEMP; + if( light->si && light->si->noFast ) + light->flags &= ~(LIGHT_FAST | LIGHT_FAST_TEMP); + + /* clear light envelope */ + light->envelope = 0; + + /* handle area lights */ + if( exactPointToPolygon && light->type == EMIT_AREA && light->w != NULL ) + { + /* ugly hack to calculate extent for area lights, but only done once */ + VectorScale( light->normal, -1.0f, dir ); + for( radius = 100.0f; radius < 130000.0f && light->envelope == 0; radius += 10.0f ) + { + float factor; + + VectorMA( light->origin, radius, light->normal, origin ); + factor = PointToPolygonFormFactor( origin, dir, light->w ); + if( factor < 0.0f ) + factor *= -1.0f; + if( (factor * light->add) <= light->falloffTolerance ) + light->envelope = radius; + } + + /* check for fast mode */ + if( !(light->flags & LIGHT_FAST) && !(light->flags & LIGHT_FAST_TEMP) ) + light->envelope = MAX_WORLD_COORD * 8.0f; + } + else + { + radius = 0.0f; + intensity = light->photons; + } + + /* other calcs */ + if( light->envelope <= 0.0f ) + { + /* solve distance for non-distance lights */ + if( !(light->flags & LIGHT_ATTEN_DISTANCE) ) + light->envelope = MAX_WORLD_COORD * 8.0f; + + /* solve distance for linear lights */ + else if( (light->flags & LIGHT_ATTEN_LINEAR ) ) + //% light->envelope = ((intensity / light->falloffTolerance) * linearScale - 1 + radius) / light->fade; + light->envelope = ((intensity * linearScale) - light->falloffTolerance) / light->fade; + + /* + add = angle * light->photons * linearScale - (dist * light->fade); + T = (light->photons * linearScale) - (dist * light->fade); + T + (dist * light->fade) = (light->photons * linearScale); + dist * light->fade = (light->photons * linearScale) - T; + dist = ((light->photons * linearScale) - T) / light->fade; + */ + + /* solve for inverse square falloff */ + else + light->envelope = sqrt( intensity / light->falloffTolerance ) + radius; + + /* + add = light->photons / (dist * dist); + T = light->photons / (dist * dist); + T * (dist * dist) = light->photons; + dist = sqrt( light->photons / T ); + */ + } + + /* chop radius against pvs */ + { + /* clear bounds */ + ClearBounds( mins, maxs ); + + /* check all leaves */ + for( i = 0; i < numBSPLeafs; i++ ) + { + /* get test leaf */ + leaf = &bspLeafs[ i ]; + + /* in pvs? */ + if( leaf->cluster < 0 ) + continue; + if( ClusterVisible( light->cluster, leaf->cluster ) == qfalse ) /* ydnar: thanks Arnout for exposing my stupid error (this never failed before) */ + continue; + + /* add this leafs bbox to the bounds */ + VectorCopy( leaf->mins, origin ); + AddPointToBounds( origin, mins, maxs ); + VectorCopy( leaf->maxs, origin ); + AddPointToBounds( origin, mins, maxs ); + } + + /* test to see if bounds encompass light */ + for( i = 0; i < 3; i++ ) + { + if( mins[ i ] > light->origin[ i ] || maxs[ i ] < light->origin[ i ] ) + { + //% Sys_Printf( "WARNING: Light PVS bounds (%.0f, %.0f, %.0f) -> (%.0f, %.0f, %.0f)\ndo not encompass light %d (%f, %f, %f)\n", + //% mins[ 0 ], mins[ 1 ], mins[ 2 ], + //% maxs[ 0 ], maxs[ 1 ], maxs[ 2 ], + //% numLights, light->origin[ 0 ], light->origin[ 1 ], light->origin[ 2 ] ); + AddPointToBounds( light->origin, mins, maxs ); + } + } + + /* chop the bounds by a plane for area lights and spotlights */ + if( light->type == EMIT_AREA || light->type == EMIT_SPOT ) + ChopBounds( mins, maxs, light->origin, light->normal ); + + /* copy bounds */ + VectorCopy( mins, light->mins ); + VectorCopy( maxs, light->maxs ); + + /* reflect bounds around light origin */ + //% VectorMA( light->origin, -1.0f, origin, origin ); + VectorScale( light->origin, 2, origin ); + VectorSubtract( origin, maxs, origin ); + AddPointToBounds( origin, mins, maxs ); + //% VectorMA( light->origin, -1.0f, mins, origin ); + VectorScale( light->origin, 2, origin ); + VectorSubtract( origin, mins, origin ); + AddPointToBounds( origin, mins, maxs ); + + /* calculate spherical bounds */ + VectorSubtract( maxs, light->origin, dir ); + radius = (float) VectorLength( dir ); + + /* if this radius is smaller than the envelope, then set the envelope to it */ + if( radius < light->envelope ) + { + light->envelope = radius; + //% Sys_FPrintf( SYS_VRB, "PVS Cull (%d): culled\n", numLights ); + } + //% else + //% Sys_FPrintf( SYS_VRB, "PVS Cull (%d): failed (%8.0f > %8.0f)\n", numLights, radius, light->envelope ); + } + + /* add grid/surface only check */ + if( forGrid ) + { + if( !(light->flags & LIGHT_GRID) ) + light->envelope = 0.0f; + } + else + { + if( !(light->flags & LIGHT_SURFACES) ) + light->envelope = 0.0f; + } + } + + /* culled? */ + if( light->cluster < 0 || light->envelope <= 0.0f ) + { + /* debug code */ + //% Sys_Printf( "Culling light: Cluster: %d Envelope: %f\n", light->cluster, light->envelope ); + + /* delete the light */ + numCulledLights++; + *owner = light->next; + if( light->w != NULL ) + free( light->w ); + free( light ); + continue; + } + } + + /* square envelope */ + light->envelope2 = (light->envelope * light->envelope); + + /* increment light count */ + numLights++; + + /* set next light */ + owner = &((**owner).next); + } + + /* bucket sort lights by style */ + memset( buckets, 0, sizeof( buckets ) ); + light2 = NULL; + for( light = lights; light != NULL; light = light2 ) + { + /* get next light */ + light2 = light->next; + + /* filter into correct bucket */ + light->next = buckets[ light->style ]; + buckets[ light->style ] = light; + + /* if any styled light is present, automatically set nocollapse */ + if( light->style != LS_NORMAL ) + noCollapse = qtrue; + } + + /* filter back into light list */ + lights = NULL; + for( i = 255; i >= 0; i-- ) + { + light2 = NULL; + for( light = buckets[ i ]; light != NULL; light = light2 ) + { + light2 = light->next; + light->next = lights; + lights = light; + } + } + + /* emit some statistics */ + Sys_Printf( "%9d total lights\n", numLights ); + Sys_Printf( "%9d culled lights\n", numCulledLights ); +} + + + +/* +CreateTraceLightsForBounds() +creates a list of lights that affect the given bounding box and pvs clusters (bsp leaves) +*/ + +void CreateTraceLightsForBounds( vec3_t mins, vec3_t maxs, vec3_t normal, int numClusters, int *clusters, int flags, trace_t *trace ) +{ + int i; + light_t *light; + vec3_t origin, dir, nullVector = { 0.0f, 0.0f, 0.0f }; + float radius, dist, length; + + + /* potential pre-setup */ + if( numLights == 0 ) + SetupEnvelopes( qfalse, fast ); + + /* debug code */ + //% Sys_Printf( "CTWLFB: (%4.1f %4.1f %4.1f) (%4.1f %4.1f %4.1f)\n", mins[ 0 ], mins[ 1 ], mins[ 2 ], maxs[ 0 ], maxs[ 1 ], maxs[ 2 ] ); + + /* allocate the light list */ + trace->lights = safe_malloc( sizeof( light_t* ) * (numLights + 1) ); + trace->numLights = 0; + + /* calculate spherical bounds */ + VectorAdd( mins, maxs, origin ); + VectorScale( origin, 0.5f, origin ); + VectorSubtract( maxs, origin, dir ); + radius = (float) VectorLength( dir ); + + /* get length of normal vector */ + if( normal != NULL ) + length = VectorLength( normal ); + else + { + normal = nullVector; + length = 0; + } + + /* test each light and see if it reaches the sphere */ + /* note: the attenuation code MUST match LightingAtSample() */ + for( light = lights; light; light = light->next ) + { + /* check zero sized envelope */ + if( light->envelope <= 0 ) + { + lightsEnvelopeCulled++; + continue; + } + + /* check flags */ + if( !(light->flags & flags) ) + continue; + + /* sunlight skips all this nonsense */ + if( light->type != EMIT_SUN ) + { + /* sun only? */ + if( sunOnly ) + continue; + + /* check against pvs cluster */ + if( numClusters > 0 && clusters != NULL ) + { + for( i = 0; i < numClusters; i++ ) + { + if( ClusterVisible( light->cluster, clusters[ i ] ) ) + break; + } + + /* fixme! */ + if( i == numClusters ) + { + lightsClusterCulled++; + continue; + } + } + + /* if the light's bounding sphere intersects with the bounding sphere then this light needs to be tested */ + VectorSubtract( light->origin, origin, dir ); + dist = VectorLength( dir ); + dist -= light->envelope; + dist -= radius; + if( dist > 0 ) + { + lightsEnvelopeCulled++; + continue; + } + + /* check bounding box against light's pvs envelope (note: this code never eliminated any lights, so disabling it) */ + #if 0 + skip = qfalse; + for( i = 0; i < 3; i++ ) + { + if( mins[ i ] > light->maxs[ i ] || maxs[ i ] < light->mins[ i ] ) + skip = qtrue; + } + if( skip ) + { + lightsBoundsCulled++; + continue; + } + #endif + } + + /* planar surfaces (except twosided surfaces) have a couple more checks */ + if( length > 0.0f && trace->twoSided == qfalse ) + { + /* lights coplanar with a surface won't light it */ + if( !(light->flags & LIGHT_TWOSIDED) && DotProduct( light->normal, normal ) > 0.999f ) + { + lightsPlaneCulled++; + continue; + } + + /* check to see if light is behind the plane */ + if( DotProduct( light->origin, normal ) - DotProduct( origin, normal ) < -1.0f ) + { + lightsPlaneCulled++; + continue; + } + } + + /* add this light */ + trace->lights[ trace->numLights++ ] = light; + } + + /* make last night null */ + trace->lights[ trace->numLights ] = NULL; +} + + + +void FreeTraceLights( trace_t *trace ) +{ + if( trace->lights != NULL ) + free( trace->lights ); +} + + + +/* +CreateTraceLightsForSurface() +creates a list of lights that can potentially affect a drawsurface +*/ + +void CreateTraceLightsForSurface( int num, trace_t *trace ) +{ + int i; + vec3_t mins, maxs, normal; + bspDrawVert_t *dv; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + + + /* dummy check */ + if( num < 0 ) + return; + + /* get drawsurface and info */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* get the mins/maxs for the dsurf */ + ClearBounds( mins, maxs ); + VectorCopy( bspDrawVerts[ ds->firstVert ].normal, normal ); + for( i = 0; i < ds->numVerts; i++ ) + { + dv = &yDrawVerts[ ds->firstVert + i ]; + AddPointToBounds( dv->xyz, mins, maxs ); + if( !VectorCompare( dv->normal, normal ) ) + VectorClear( normal ); + } + + /* create the lights for the bounding box */ + CreateTraceLightsForBounds( mins, maxs, normal, info->numSurfaceClusters, &surfaceClusters[ info->firstSurfaceCluster ], LIGHT_SURFACES, trace ); +} + +///////////////////////////////////////////////////////////// + +#define FLOODLIGHT_CONE_ANGLE 88 /* degrees */ +#define FLOODLIGHT_NUM_ANGLE_STEPS 16 +#define FLOODLIGHT_NUM_ELEVATION_STEPS 4 +#define FLOODLIGHT_NUM_VECTORS (FLOODLIGHT_NUM_ANGLE_STEPS * FLOODLIGHT_NUM_ELEVATION_STEPS) + +static vec3_t floodVectors[ FLOODLIGHT_NUM_VECTORS ]; +static int numFloodVectors = 0; + +void SetupFloodLight( void ) +{ + int i, j; + float angle, elevation, angleStep, elevationStep; + const char *value; + double v1,v2,v3,v4,v5; + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupFloodLight ---\n" ); + + /* calculate angular steps */ + angleStep = DEG2RAD( 360.0f / FLOODLIGHT_NUM_ANGLE_STEPS ); + elevationStep = DEG2RAD( FLOODLIGHT_CONE_ANGLE / FLOODLIGHT_NUM_ELEVATION_STEPS ); + + /* iterate angle */ + angle = 0.0f; + for( i = 0, angle = 0.0f; i < FLOODLIGHT_NUM_ANGLE_STEPS; i++, angle += angleStep ) + { + /* iterate elevation */ + for( j = 0, elevation = elevationStep * 0.5f; j < FLOODLIGHT_NUM_ELEVATION_STEPS; j++, elevation += elevationStep ) + { + floodVectors[ numFloodVectors ][ 0 ] = sin( elevation ) * cos( angle ); + floodVectors[ numFloodVectors ][ 1 ] = sin( elevation ) * sin( angle ); + floodVectors[ numFloodVectors ][ 2 ] = cos( elevation ); + numFloodVectors++; + } + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d numFloodVectors\n", numFloodVectors ); + + /* floodlight */ + value = ValueForKey( &entities[ 0 ], "_floodlight" ); + + if( value[ 0 ] != '\0' ) + { + v1=v2=v3=0; + v4=floodlightDistance; + v5=floodlightIntensity; + + sscanf( value, "%lf %lf %lf %lf %lf", &v1, &v2, &v3, &v4, &v5); + + floodlightRGB[0]=v1; + floodlightRGB[1]=v2; + floodlightRGB[2]=v3; + + if (VectorLength(floodlightRGB)==0) + { + VectorSet(floodlightRGB,240,240,255); + } + + if (v4<1) v4=1024; + if (v5<1) v5=128; + + floodlightDistance=v4; + floodlightIntensity=v5; + + floodlighty = qtrue; + Sys_Printf( "FloodLighting enabled via worldspawn _floodlight key.\n" ); + } + else + { + VectorSet(floodlightRGB,240,240,255); + //floodlighty = qtrue; + //Sys_Printf( "FloodLighting enabled via worldspawn _floodlight key.\n" ); + } + VectorNormalize(floodlightRGB,floodlightRGB); +} + +//27 - lighttracer style ambient occlusion light hack. +//Kudos to the dirtmapping author for most of this source. +void FloodLightRawLightmap( int rawLightmapNum ) +{ + int i, x, y, sx, sy, *cluster; + float *origin, *normal, *floodlight, *floodlight2, average, samples; + rawLightmap_t *lm; + surfaceInfo_t *info; + trace_t trace; + + /* bail if this number exceeds the number of raw lightmaps */ + if( rawLightmapNum >= numRawLightmaps ) + return; + + /* get lightmap */ + lm = &rawLightmaps[ rawLightmapNum ]; + + memset(&trace,0,sizeof(trace_t)); + /* setup trace */ + trace.testOcclusion = qtrue; + trace.forceSunlight = qfalse; + trace.twoSided = qtrue; + trace.recvShadows = lm->recvShadows; + trace.numSurfaces = lm->numLightSurfaces; + trace.surfaces = &lightSurfaces[ lm->firstLightSurface ]; + trace.inhibitRadius = DEFAULT_INHIBIT_RADIUS; + trace.testAll = qfalse; + trace.distance = 1024; + + /* twosided lighting (may or may not be a good idea for lightmapped stuff) */ + //trace.twoSided = qfalse; + for( i = 0; i < trace.numSurfaces; i++ ) + { + /* get surface */ + info = &surfaceInfos[ trace.surfaces[ i ] ]; + + /* check twosidedness */ + if( info->si->twoSided ) + { + trace.twoSided = qtrue; + break; + } + } + + /* gather dirt */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + cluster = SUPER_CLUSTER( x, y ); + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + floodlight = SUPER_FLOODLIGHT( x, y ); + + /* set default dirt */ + *floodlight = 0.0f; + + /* only look at mapped luxels */ + if( *cluster < 0 ) + continue; + + /* copy to trace */ + trace.cluster = *cluster; + VectorCopy( origin, trace.origin ); + VectorCopy( normal, trace.normal ); + + + + /* get dirt */ + *floodlight = FloodLightForSample( &trace ); + } + } + + /* testing no filtering */ + return; + + /* filter "dirt" */ + for( y = 0; y < lm->sh; y++ ) + { + for( x = 0; x < lm->sw; x++ ) + { + /* get luxel */ + cluster = SUPER_CLUSTER( x, y ); + floodlight = SUPER_FLOODLIGHT( x, y ); + + /* filter dirt by adjacency to unmapped luxels */ + average = *floodlight; + samples = 1.0f; + for( sy = (y - 1); sy <= (y + 1); sy++ ) + { + if( sy < 0 || sy >= lm->sh ) + continue; + + for( sx = (x - 1); sx <= (x + 1); sx++ ) + { + if( sx < 0 || sx >= lm->sw || (sx == x && sy == y) ) + continue; + + /* get neighboring luxel */ + cluster = SUPER_CLUSTER( sx, sy ); + floodlight2 = SUPER_FLOODLIGHT( sx, sy ); + if( *cluster < 0 || *floodlight2 <= 0.0f ) + continue; + + /* add it */ + average += *floodlight2; + samples += 1.0f; + } + + /* bail */ + if( samples <= 0.0f ) + break; + } + + /* bail */ + if( samples <= 0.0f ) + continue; + + /* scale dirt */ + *floodlight = average / samples; + } + } +} + +/* +FloodLightForSample() +calculates floodlight value for a given sample +once again, kudos to the dirtmapping coder +*/ +float FloodLightForSample( trace_t *trace ) +{ + int i; + float d; + float contribution; + int sub = 0; + float gatherLight, outLight; + vec3_t normal, worldUp, myUp, myRt, direction, displacement; + float dd; + int vecs = 0; + + gatherLight=0; + /* dummy check */ + //if( !dirty ) + // return 1.0f; + if( trace == NULL || trace->cluster < 0 ) + return 0.0f; + + + /* setup */ + dd = floodlightDistance; + VectorCopy( trace->normal, normal ); + + /* check if the normal is aligned to the world-up */ + if( normal[ 0 ] == 0.0f && normal[ 1 ] == 0.0f ) + { + if( normal[ 2 ] == 1.0f ) + { + VectorSet( myRt, 1.0f, 0.0f, 0.0f ); + VectorSet( myUp, 0.0f, 1.0f, 0.0f ); + } + else if( normal[ 2 ] == -1.0f ) + { + VectorSet( myRt, -1.0f, 0.0f, 0.0f ); + VectorSet( myUp, 0.0f, 1.0f, 0.0f ); + } + } + else + { + VectorSet( worldUp, 0.0f, 0.0f, 1.0f ); + CrossProduct( normal, worldUp, myRt ); + VectorNormalize( myRt, myRt ); + CrossProduct( myRt, normal, myUp ); + VectorNormalize( myUp, myUp ); + } + + /* iterate through ordered vectors */ + for( i = 0; i < numFloodVectors; i++ ) + { + if (floodlight_lowquality==qtrue) + { + if (rand()%10 != 0 ) continue; + } + + vecs++; + + /* transform vector into tangent space */ + direction[ 0 ] = myRt[ 0 ] * floodVectors[ i ][ 0 ] + myUp[ 0 ] * floodVectors[ i ][ 1 ] + normal[ 0 ] * floodVectors[ i ][ 2 ]; + direction[ 1 ] = myRt[ 1 ] * floodVectors[ i ][ 0 ] + myUp[ 1 ] * floodVectors[ i ][ 1 ] + normal[ 1 ] * floodVectors[ i ][ 2 ]; + direction[ 2 ] = myRt[ 2 ] * floodVectors[ i ][ 0 ] + myUp[ 2 ] * floodVectors[ i ][ 1 ] + normal[ 2 ] * floodVectors[ i ][ 2 ]; + + /* set endpoint */ + VectorMA( trace->origin, dd, direction, trace->end ); + + //VectorMA( trace->origin, 1, direction, trace->origin ); + + SetupTrace( trace ); + /* trace */ + TraceLine( trace ); + contribution=1; + + if (trace->compileFlags & C_SKY ) + { + contribution=1.0f; + } + else if ( trace->opaque ) + { + VectorSubtract( trace->hit, trace->origin, displacement ); + d=VectorLength( displacement ); + + // d=trace->distance; + //if (d>256) gatherDirt+=1; + contribution=d/dd; + if (contribution>1) contribution=1.0f; + + //gatherDirt += 1.0f - ooDepth * VectorLength( displacement ); + } + + gatherLight+=contribution; + } + + /* early out */ + if( gatherLight <= 0.0f ) + return 0.0f; + + sub=vecs; + + if (sub<1) sub=1; + gatherLight/=(sub); + + outLight=gatherLight; + if( outLight > 1.0f ) + outLight = 1.0f; + + /* return to sender */ + return outLight; +} + diff --git a/tools/quake3/q3map2/lightmaps.c b/tools/quake3/q3map2/lightmaps.c new file mode 100644 index 00000000..62977ddf --- /dev/null +++ b/tools/quake3/q3map2/lightmaps.c @@ -0,0 +1,496 @@ +/* +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "qbsp.h" + + +/* + + Lightmap allocation has to be done after all flood filling and + visible surface determination. + +*/ + +int numSortShaders; +mapDrawSurface_t *surfsOnShader[ MAX_MAP_SHADERS ]; + + +int allocated[ LIGHTMAP_WIDTH ]; + +int numLightmaps = 1; +int c_exactLightmap = 0; +int c_planarPatch = 0; +int c_nonplanarLightmap = 0; + + +void PrepareNewLightmap( void ) { + memset( allocated, 0, sizeof( allocated ) ); + numLightmaps++; +} + +/* +=============== +AllocLMBlock + +returns a texture number and the position inside it +=============== +*/ +qboolean AllocLMBlock (int w, int h, int *x, int *y) +{ + int i, j; + int best, best2; + + best = LIGHTMAP_HEIGHT; + + for ( i=0 ; i <= LIGHTMAP_WIDTH-w ; i++ ) { + best2 = 0; + + for (j=0 ; j= best) { + break; + } + if (allocated[i+j] > best2) { + best2 = allocated[i+j]; + } + } + if (j == w) { // this is a valid spot + *x = i; + *y = best = best2; + } + } + + if (best + h > LIGHTMAP_HEIGHT) { + return qfalse; + } + + for (i=0 ; iverts; + + mesh.width = ds->patchWidth; + mesh.height = ds->patchHeight; + mesh.verts = verts; + newmesh = SubdivideMesh( mesh, 8, 999 ); + + PutMeshOnCurve( *newmesh ); + tempMesh = RemoveLinearMeshColumnsRows( newmesh ); + FreeMesh(newmesh); + + /* get sample size */ + ssize = ds->sampleSize; + + +#ifdef LIGHTMAP_PATCHSHIFT + subdividedMesh = SubdivideMeshQuads( tempMesh, ssize, LIGHTMAP_WIDTH-1, widthtable, heighttable ); +#else + subdividedMesh = SubdivideMeshQuads( tempMesh, ssize, LIGHTMAP_WIDTH, widthtable, heighttable ); +#endif + + w = subdividedMesh->width; + h = subdividedMesh->height; + +#ifdef LIGHTMAP_PATCHSHIFT + w++; + h++; +#endif + + FreeMesh(subdividedMesh); + + // allocate the lightmap + c_exactLightmap += w * h; + + if ( !AllocLMBlock( w, h, &x, &y ) ) { + PrepareNewLightmap(); + if ( !AllocLMBlock( w, h, &x, &y ) ) + { + Error("Entity %i, brush %i: Lightmap allocation failed", + ds->mapBrush->entitynum, ds->mapBrush->brushnum ); + } + } + +#ifdef LIGHTMAP_PATCHSHIFT + w--; + h--; +#endif + + // set the lightmap texture coordinates in the drawVerts + ds->lightmapNum = numLightmaps - 1; + ds->lightmapWidth = w; + ds->lightmapHeight = h; + ds->lightmapX = x; + ds->lightmapY = y; + + for ( i = 0 ; i < ds->patchWidth ; i++ ) { + for ( k = 0 ; k < w ; k++ ) { + if ( originalWidths[k] >= i ) { + break; + } + } + if (k >= w) + k = w-1; + s = x + k; + for ( j = 0 ; j < ds->patchHeight ; j++ ) { + for ( k = 0 ; k < h ; k++ ) { + if ( originalHeights[k] >= j ) { + break; + } + } + if (k >= h) + k = h-1; + t = y + k; + verts[i + j * ds->patchWidth].lightmap[0] = ( s + 0.5 ) / LIGHTMAP_WIDTH; + verts[i + j * ds->patchWidth].lightmap[1] = ( t + 0.5 ) / LIGHTMAP_HEIGHT; + } + } +} + + +/* +=================== +AllocateLightmapForSurface +=================== +*/ + +//#define LIGHTMAP_BLOCK 16 + +void AllocateLightmapForSurface( mapDrawSurface_t *ds ) +{ + vec3_t mins, maxs, size, exactSize, delta; + int i; + drawVert_t *verts; + int w, h; + int x, y, ssize; + int axis; + vec3_t vecs[ 2 ]; + float s, t; + vec3_t origin; + vec4_t plane; + float d; + + + /* debug code */ + #if 0 + if( ds->type == SURF_META && ds->planar == qfalse ) + Sys_Printf( "NPMS: %3d vertexes, %s\n", ds->numVerts, ds->shaderInfo->shader ); + else if( ds->type == SURF_META && ds->planar == qtrue ) + Sys_Printf( "PMS: %3d vertexes, %s\n", ds->numVerts, ds->shaderInfo->shader ); + #endif + + /* ydnar: handle planar patches */ + if( noPatchFix == qtrue || (ds->type == SURF_PATCH && ds->planeNum < 0) ) + { + AllocateLightmapForPatch( ds ); + return; + } + + /* get sample size */ + ssize = ds->sampleSize; + + /* bound the surface */ + ClearBounds( mins, maxs ); + verts = ds->verts; + for ( i = 0 ; i < ds->numVerts ; i++ ) + AddPointToBounds( verts[i].xyz, mins, maxs ); + + /* round to the lightmap resolution */ + for( i = 0; i < 3; i++ ) + { + exactSize[i] = maxs[i] - mins[i]; + mins[i] = ssize * floor( mins[i] / ssize ); + maxs[i] = ssize * ceil( maxs[i] / ssize ); + size[i] = (maxs[i] - mins[i]) / ssize + 1; + } + + /* ydnar: lightmap projection axis is already stored */ + memset( vecs, 0, sizeof( vecs ) ); + + /* classify the plane (x y or z major) (ydnar: biased to z axis projection) */ + if( ds->lightmapAxis[ 2 ] >= ds->lightmapAxis[ 0 ] && ds->lightmapAxis[ 2 ] >= ds->lightmapAxis[ 1 ] ) + { + w = size[ 0 ]; + h = size[ 1 ]; + axis = 2; + vecs[ 0 ][ 0 ] = 1.0 / ssize; + vecs[ 1 ][ 1 ] = 1.0 / ssize; + } + else if( ds->lightmapAxis[ 0 ] >= ds->lightmapAxis[ 1 ] && ds->lightmapAxis[ 0 ] >= ds->lightmapAxis[ 2 ] ) + { + w = size[ 1 ]; + h = size[ 2 ]; + axis = 0; + vecs[ 0 ][ 1 ] = 1.0 / ssize; + vecs[ 1 ][ 2 ] = 1.0 / ssize; + } + else + { + w = size[ 0 ]; + h = size[ 2 ]; + axis = 1; + vecs[ 0 ][ 0 ] = 1.0 / ssize; + vecs[ 1 ][ 2 ] = 1.0 / ssize; + } + + /* odd check, given projection is now precalculated */ + if( ds->lightmapAxis[ axis ] == 0 ) + Error( "Chose a 0 valued axis" ); + + /* clamp to lightmap texture resolution */ + if( w > LIGHTMAP_WIDTH ) + { + VectorScale ( vecs[0], (float) LIGHTMAP_WIDTH / w, vecs[0] ); + w = LIGHTMAP_WIDTH; + } + if( h > LIGHTMAP_HEIGHT ) + { + VectorScale ( vecs[1], (float) LIGHTMAP_HEIGHT / h, vecs[1] ); + h = LIGHTMAP_HEIGHT; + } + + + /* ydnar */ + if( ds->planar == qfalse ) + c_nonplanarLightmap += w * h; + c_exactLightmap += w * h; + + + if( !AllocLMBlock( w, h, &x, &y ) ) + { + PrepareNewLightmap(); + if ( !AllocLMBlock( w, h, &x, &y ) ) + { + Error( "Entity %i, brush %i: Lightmap allocation failed", + ds->mapBrush->entitynum, ds->mapBrush->brushnum ); + } + } + + /* set the lightmap texture coordinates in the drawVerts */ + ds->lightmapNum = numLightmaps - 1; + ds->lightmapWidth = w; + ds->lightmapHeight = h; + ds->lightmapX = x; + ds->lightmapY = y; + for ( i = 0 ; i < ds->numVerts ; i++ ) + { + VectorSubtract( verts[i].xyz, mins, delta ); + s = DotProduct( delta, vecs[0] ) + x + 0.5; + t = DotProduct( delta, vecs[1] ) + y + 0.5; + verts[i].lightmap[0] = s / LIGHTMAP_WIDTH; + verts[i].lightmap[1] = t / LIGHTMAP_HEIGHT; + } + + /* calculate the world coordinates of the lightmap samples */ + + /* construct a plane from the first vert and clear bounding box */ + + /* project mins onto plane to get origin */ + VectorCopy( ds->lightmapVecs[ 2 ], plane ); + plane[ 3 ] = DotProduct( ds->verts[ 0 ].xyz, plane ); + d = DotProduct( mins, plane ) - plane[ 3 ]; + d /= plane[ axis ]; + + //% d = DotProduct( mins, plane->normal ) - plane->dist; + //% d /= plane->normal[ axis ]; + VectorCopy( mins, origin ); + origin[ axis ] -= d; + + /* project stepped lightmap blocks and subtract to get planevecs */ + for( i = 0; i < 2; i++ ) + { + vec3_t normalized; + float len; + + len = VectorNormalize( vecs[i], normalized ); + VectorScale( normalized, (1.0/len), vecs[i] ); + d = DotProduct( vecs[i], plane ); + d /= plane[ axis ]; + //%d = DotProduct( vecs[i], plane->normal ); + //%d /= plane->normal[ axis ]; + vecs[i][axis] -= d; + } + + /* store lightmap origin and vectors (fixme: make this work right) */ + VectorCopy( origin, ds->lightmapOrigin ); + //% VectorCopy( plane->normal, ds->lightmapVecs[ 2 ] ); + + /* ydnar: lightmap vectors 0 and 1 are used for lod bounds, so don't overwrite */ + if( ds->type == SURF_PATCH ) + c_planarPatch++; + + /* store lightmap vectors */ + VectorCopy( vecs[ 0 ], ds->lightmapVecs[ 0 ] ); + VectorCopy( vecs[ 1 ], ds->lightmapVecs[ 1 ] ); + + /* ydnar: print some stats */ + //Sys_FPrintf( SYS_VRB, "Lightmap block %3d (%3d, %3d) (%3d x %3d) emitted\n", (numLightmaps - 1), x, y, w, h ); +} + + +/* +=================== +AllocateLightmaps +=================== +*/ +void AllocateLightmaps( entity_t *e ) +{ + int i, j; + mapDrawSurface_t *ds; + shaderInfo_t *si; + + + /* note it */ + Sys_FPrintf( SYS_VRB,"--- AllocateLightmaps ---\n" ); + + + /* sort all surfaces by shader so common shaders will usually be in the same lightmap */ + /* ydnar: this is done in two passes, because of an odd bug with lightmapped terrain */ + numSortShaders = 0; + for( i = e->firstDrawSurf; i < numMapDrawSurfs; i++ ) + { + /* get surface and early out if possible */ + ds = &mapDrawSurfs[ i ]; + si = ds->shaderInfo; + if( si->surfaceFlags & SURF_VERTEXLIT ) + continue; + if( ds->numVerts <= 0 ) + continue; + + /* ydnar: handle brush faces and patches first */ + if( ds->type != SURF_FACE && ds->type != SURF_PATCH ) + continue; + + /* ydnar: this is unecessary because it should already be set */ + //% VectorCopy( ds->plane.normal, ds->lightmapVecs[ 2 ] ); + + /* search for this shader */ + for( j = 0 ; j < numSortShaders; j++ ) + { + if( ds->shaderInfo == surfsOnShader[ j ]->shaderInfo ) + { + ds->nextOnShader = surfsOnShader[ j ]; + surfsOnShader[ j ] = ds; + break; + } + } + + /* new shader */ + if( j == numSortShaders ) + { + if( numSortShaders >= MAX_MAP_SHADERS ) + Error( "MAX_MAP_SHADERS" ); + surfsOnShader[ j ] = ds; + ds->nextOnShader = NULL; + numSortShaders++; + } + } + + /* second pass, to allocate lightmapped terrain last */ + for( i = e->firstDrawSurf; i < numMapDrawSurfs; i++ ) + { + /* get surface and early out if possible */ + ds = &mapDrawSurfs[ i ]; + si = ds->shaderInfo; + if( si->surfaceFlags & SURF_VERTEXLIT ) + continue; + if( ds->numVerts <= 0 ) + continue; + + /* ydnar: this only handles metasurfaces and terrain */ + if( ds->type != SURF_TERRAIN && ds->type != SURF_META ) + continue; + + /* ydnar: a lightmap projection should be pre-stored for anything but excessively curved patches */ + if( VectorLength( ds->lightmapAxis ) <= 0 ) + continue; + + /* search for this shader */ + for( j = 0; j < numSortShaders; j++ ) + { + if( ds->shaderInfo == surfsOnShader[ j ]->shaderInfo ) + { + ds->nextOnShader = surfsOnShader[ j ]; + surfsOnShader[ j ] = ds; + break; + } + } + + /* new shader */ + if( j == numSortShaders ) + { + if( numSortShaders >= MAX_MAP_SHADERS ) + Error( "MAX_MAP_SHADERS" ); + surfsOnShader[ j ] = ds; + ds->nextOnShader = NULL; + numSortShaders++; + } + } + + /* tot up shader count */ + Sys_FPrintf( SYS_VRB, "%9d unique shaders\n", numSortShaders ); + + /* for each shader, allocate lightmaps for each surface */ + for( i = 0; i < numSortShaders; i++ ) + { + si = surfsOnShader[ i ]->shaderInfo; + for( ds = surfsOnShader[ i ]; ds; ds = ds->nextOnShader ) + { + /* ydnar: promoting pointlight above nolightmap */ + if( si->surfaceFlags & SURF_POINTLIGHT ) + ds->lightmapNum = -3; + else if( si->surfaceFlags & SURF_NOLIGHTMAP ) + ds->lightmapNum = -1; + else + AllocateLightmapForSurface( ds ); + } + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d exact lightmap texels\n", c_exactLightmap ); + Sys_FPrintf( SYS_VRB, "%9d block lightmap texels\n", numLightmaps * LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT ); + Sys_FPrintf( SYS_VRB, "%9d non-planar or terrain lightmap texels\n", c_nonplanarLightmap ); + Sys_FPrintf( SYS_VRB, "%9d planar patch lightmaps\n", c_planarPatch ); + Sys_FPrintf( SYS_VRB, "%9d lightmap textures, size: %d Kbytes\n", numLightmaps, (numLightmaps * LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * 3) / 1024 ); +} + + + diff --git a/tools/quake3/q3map2/lightmaps_ydnar.c b/tools/quake3/q3map2/lightmaps_ydnar.c new file mode 100644 index 00000000..e0671147 --- /dev/null +++ b/tools/quake3/q3map2/lightmaps_ydnar.c @@ -0,0 +1,3142 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define LIGHTMAPS_YDNAR_C + + + +/* dependencies */ +#include "q3map2.h" + + + + +/* ------------------------------------------------------------------------------- + +this file contains code that doe lightmap allocation and projection that +runs in the -light phase. + +this is handled here rather than in the bsp phase for a few reasons-- +surfaces are no longer necessarily convex polygons, patches may or may not be +planar or have lightmaps projected directly onto control points. + +also, this allows lightmaps to be calculated before being allocated and stored +in the bsp. lightmaps that have little high-frequency information are candidates +for having their resolutions scaled down. + +------------------------------------------------------------------------------- */ + +/* +WriteTGA24() +based on WriteTGA() from imagelib.c +*/ + +void WriteTGA24( char *filename, byte *data, int width, int height, qboolean flip ) +{ + int i, c; + byte *buffer, *in; + FILE *file; + + + /* allocate a buffer and set it up */ + buffer = safe_malloc( width * height * 3 + 18 ); + memset( buffer, 0, 18 ); + buffer[ 2 ] = 2; + buffer[ 12 ] = width & 255; + buffer[ 13 ] = width >> 8; + buffer[ 14 ] = height & 255; + buffer[ 15 ] = height >> 8; + buffer[ 16 ] = 24; + + /* swap rgb to bgr */ + c = (width * height * 3) + 18; + for( i = 18; i < c; i += 3 ) + { + buffer[ i ] = data[ i - 18 + 2 ]; /* blue */ + buffer[ i + 1 ] = data[ i - 18 + 1 ]; /* green */ + buffer[ i + 2 ] = data[ i - 18 + 0 ]; /* red */ + } + + /* write it and free the buffer */ + file = fopen( filename, "wb" ); + if( file == NULL ) + Error( "Unable to open %s for writing", filename ); + + /* flip vertically? */ + if( flip ) + { + fwrite( buffer, 1, 18, file ); + for( in = buffer + ((height - 1) * width * 3) + 18; in >= buffer; in -= (width * 3) ) + fwrite( in, 1, (width * 3), file ); + } + else + fwrite( buffer, 1, c, file ); + + /* close the file */ + fclose( file ); + free( buffer ); +} + + + +/* +ExportLightmaps() +exports the lightmaps as a list of numbered tga images +*/ + +void ExportLightmaps( void ) +{ + int i; + char dirname[ 1024 ], filename[ 1024 ]; + byte *lightmap; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- ExportLightmaps ---\n"); + + /* do some path mangling */ + strcpy( dirname, source ); + StripExtension( dirname ); + + /* sanity check */ + if( bspLightBytes == NULL ) + { + Sys_Printf( "WARNING: No BSP lightmap data\n" ); + return; + } + + /* make a directory for the lightmaps */ + Q_mkdir( dirname ); + + /* iterate through the lightmaps */ + for( i = 0, lightmap = bspLightBytes; lightmap < (bspLightBytes + numBSPLightBytes); i++, lightmap += (game->lightmapSize * game->lightmapSize * 3) ) + { + /* write a tga image out */ + sprintf( filename, "%s/lightmap_%04d.tga", dirname, i ); + Sys_Printf( "Writing %s\n", filename ); + WriteTGA24( filename, lightmap, game->lightmapSize, game->lightmapSize, qfalse ); + } +} + + + +/* +ExportLightmapsMain() +exports the lightmaps as a list of numbered tga images +*/ + +int ExportLightmapsMain( int argc, char **argv ) +{ + /* arg checking */ + if( argc < 1 ) + { + Sys_Printf( "Usage: q3map -export [-v] \n" ); + return 0; + } + + /* do some path mangling */ + strcpy( source, ExpandArg( argv[ argc - 1 ] ) ); + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + + /* load the bsp */ + Sys_Printf( "Loading %s\n", source ); + LoadBSPFile( source ); + + /* export the lightmaps */ + ExportLightmaps(); + + /* return to sender */ + return 0; +} + + + +/* +ImportLightmapsMain() +imports the lightmaps from a list of numbered tga images +*/ + +int ImportLightmapsMain( int argc, char **argv ) +{ + int i, x, y, len, width, height; + char dirname[ 1024 ], filename[ 1024 ]; + byte *lightmap, *buffer, *pixels, *in, *out; + + + /* arg checking */ + if( argc < 1 ) + { + Sys_Printf( "Usage: q3map -import [-v] \n" ); + return 0; + } + + /* do some path mangling */ + strcpy( source, ExpandArg( argv[ argc - 1 ] ) ); + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + + /* load the bsp */ + Sys_Printf( "Loading %s\n", source ); + LoadBSPFile( source ); + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- ImportLightmaps ---\n"); + + /* do some path mangling */ + strcpy( dirname, source ); + StripExtension( dirname ); + + /* sanity check */ + if( bspLightBytes == NULL ) + Error( "No lightmap data" ); + + /* make a directory for the lightmaps */ + Q_mkdir( dirname ); + + /* iterate through the lightmaps */ + for( i = 0, lightmap = bspLightBytes; lightmap < (bspLightBytes + numBSPLightBytes); i++, lightmap += (game->lightmapSize * game->lightmapSize * 3) ) + { + /* read a tga image */ + sprintf( filename, "%s/lightmap_%04d.tga", dirname, i ); + Sys_Printf( "Loading %s\n", filename ); + buffer = NULL; + len = vfsLoadFile( filename, (void*) &buffer, -1 ); + if( len < 0 ) + { + Sys_Printf( "WARNING: Unable to load image %s\n", filename ); + continue; + } + + /* parse file into an image */ + pixels = NULL; + LoadTGABuffer( buffer, buffer + len, &pixels, &width, &height ); + free( buffer ); + + /* sanity check it */ + if( pixels == NULL ) + { + Sys_Printf( "WARNING: Unable to load image %s\n", filename ); + continue; + } + if( width != game->lightmapSize || height != game->lightmapSize ) + Sys_Printf( "WARNING: Image %s is not the right size (%d, %d) != (%d, %d)\n", + filename, width, height, game->lightmapSize, game->lightmapSize ); + + /* copy the pixels */ + in = pixels; + for( y = 1; y <= game->lightmapSize; y++ ) + { + out = lightmap + ((game->lightmapSize - y) * game->lightmapSize * 3); + for( x = 0; x < game->lightmapSize; x++, in += 4, out += 3 ) + VectorCopy( in, out ); + } + + /* free the image */ + free( pixels ); + } + + /* write the bsp */ + Sys_Printf( "writing %s\n", source ); + WriteBSPFile( source ); + + /* return to sender */ + return 0; +} + + + +/* ------------------------------------------------------------------------------- + +this section deals with projecting a lightmap onto a raw drawsurface + +------------------------------------------------------------------------------- */ + +/* +CompareLightSurface() +compare function for qsort() +*/ + +static int CompareLightSurface( const void *a, const void *b ) +{ + shaderInfo_t *asi, *bsi; + + + /* get shaders */ + asi = surfaceInfos[ *((int*) a) ].si; + bsi = surfaceInfos[ *((int*) b) ].si; + + /* dummy check */ + if( asi == NULL ) + return -1; + if( bsi == NULL ) + return 1; + + /* compare shader names */ + return strcmp( asi->shader, bsi->shader ); +} + + + +/* +FinishRawLightmap() +allocates a raw lightmap's necessary buffers +*/ + +void FinishRawLightmap( rawLightmap_t *lm ) +{ + int i, j, c, size, *sc; + float is; + surfaceInfo_t *info; + + + /* sort light surfaces by shader name */ + qsort( &lightSurfaces[ lm->firstLightSurface ], lm->numLightSurfaces, sizeof( int ), CompareLightSurface ); + + /* count clusters */ + lm->numLightClusters = 0; + for( i = 0; i < lm->numLightSurfaces; i++ ) + { + /* get surface info */ + info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + i ] ]; + + /* add surface clusters */ + lm->numLightClusters += info->numSurfaceClusters; + } + + /* allocate buffer for clusters and copy */ + lm->lightClusters = safe_malloc( lm->numLightClusters * sizeof( *lm->lightClusters ) ); + c = 0; + for( i = 0; i < lm->numLightSurfaces; i++ ) + { + /* get surface info */ + info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + i ] ]; + + /* add surface clusters */ + for( j = 0; j < info->numSurfaceClusters; j++ ) + lm->lightClusters[ c++ ] = surfaceClusters[ info->firstSurfaceCluster + j ]; + } + + /* set styles */ + lm->styles[ 0 ] = LS_NORMAL; + for( i = 1; i < MAX_LIGHTMAPS; i++ ) + lm->styles[ i ] = LS_NONE; + + /* set supersampling size */ + lm->sw = lm->w * superSample; + lm->sh = lm->h * superSample; + + /* add to super luxel count */ + numRawSuperLuxels += (lm->sw * lm->sh); + + /* manipulate origin/vecs for supersampling */ + if( superSample > 1 && lm->vecs != NULL ) + { + /* calc inverse supersample */ + is = 1.0f / superSample; + + /* scale the vectors and shift the origin */ + #if 1 + /* new code that works for arbitrary supersampling values */ + VectorMA( lm->origin, -0.5, lm->vecs[ 0 ], lm->origin ); + VectorMA( lm->origin, -0.5, lm->vecs[ 1 ], lm->origin ); + VectorScale( lm->vecs[ 0 ], is, lm->vecs[ 0 ] ); + VectorScale( lm->vecs[ 1 ], is, lm->vecs[ 1 ] ); + VectorMA( lm->origin, is, lm->vecs[ 0 ], lm->origin ); + VectorMA( lm->origin, is, lm->vecs[ 1 ], lm->origin ); + #else + /* old code that only worked with a value of 2 */ + VectorScale( lm->vecs[ 0 ], is, lm->vecs[ 0 ] ); + VectorScale( lm->vecs[ 1 ], is, lm->vecs[ 1 ] ); + VectorMA( lm->origin, -is, lm->vecs[ 0 ], lm->origin ); + VectorMA( lm->origin, -is, lm->vecs[ 1 ], lm->origin ); + #endif + } + + /* allocate bsp lightmap storage */ + size = lm->w * lm->h * BSP_LUXEL_SIZE * sizeof( float ); + if( lm->bspLuxels[ 0 ] == NULL ) + lm->bspLuxels[ 0 ] = safe_malloc( size ); + memset( lm->bspLuxels[ 0 ], 0, size ); + + /* allocate radiosity lightmap storage */ + if( bounce ) + { + size = lm->w * lm->h * RAD_LUXEL_SIZE * sizeof( float ); + if( lm->radLuxels[ 0 ] == NULL ) + lm->radLuxels[ 0 ] = safe_malloc( size ); + memset( lm->radLuxels[ 0 ], 0, size ); + } + + /* allocate sampling lightmap storage */ + size = lm->sw * lm->sh * SUPER_LUXEL_SIZE * sizeof( float ); + if( lm->superLuxels[ 0 ] == NULL ) + lm->superLuxels[ 0 ] = safe_malloc( size ); + memset( lm->superLuxels[ 0 ], 0, size ); + + /* allocate origin map storage */ + size = lm->sw * lm->sh * SUPER_ORIGIN_SIZE * sizeof( float ); + if( lm->superOrigins == NULL ) + lm->superOrigins = safe_malloc( size ); + memset( lm->superOrigins, 0, size ); + + /* allocate normal map storage */ + size = lm->sw * lm->sh * SUPER_NORMAL_SIZE * sizeof( float ); + if( lm->superNormals == NULL ) + lm->superNormals = safe_malloc( size ); + memset( lm->superNormals, 0, size ); + + /* allocate floodlight map storage */ + size = lm->sw * lm->sh * SUPER_FLOODLIGHT_SIZE * sizeof( float ); + if( lm->superFloodLight == NULL ) + lm->superFloodLight = safe_malloc( size ); + memset( lm->superFloodLight, 0, size ); + + /* allocate cluster map storage */ + size = lm->sw * lm->sh * sizeof( int ); + if( lm->superClusters == NULL ) + lm->superClusters = safe_malloc( size ); + size = lm->sw * lm->sh; + sc = lm->superClusters; + for( i = 0; i < size; i++ ) + (*sc++) = CLUSTER_UNMAPPED; + + /* deluxemap allocation */ + if( deluxemap ) + { + /* allocate sampling deluxel storage */ + size = lm->sw * lm->sh * SUPER_DELUXEL_SIZE * sizeof( float ); + if( lm->superDeluxels == NULL ) + lm->superDeluxels = safe_malloc( size ); + memset( lm->superDeluxels, 0, size ); + + /* allocate bsp deluxel storage */ + size = lm->w * lm->h * BSP_DELUXEL_SIZE * sizeof( float ); + if( lm->bspDeluxels == NULL ) + lm->bspDeluxels = safe_malloc( size ); + memset( lm->bspDeluxels, 0, size ); + } + + /* add to count */ + numLuxels += (lm->sw * lm->sh); +} + + + +/* +AddPatchToRawLightmap() +projects a lightmap for a patch surface +since lightmap calculation for surfaces is now handled in a general way (light_ydnar.c), +it is no longer necessary for patch verts to fall exactly on a lightmap sample +based on AllocateLightmapForPatch() +*/ + +qboolean AddPatchToRawLightmap( int num, rawLightmap_t *lm ) +{ + bspDrawSurface_t *ds; + surfaceInfo_t *info; + int x, y; + bspDrawVert_t *verts, *a, *b; + vec3_t delta; + mesh_t src, *subdivided, *mesh; + float sBasis, tBasis, s, t; + float length, widthTable[ MAX_EXPANDED_AXIS ], heightTable[ MAX_EXPANDED_AXIS ]; + + + /* patches finish a raw lightmap */ + lm->finished = qtrue; + + /* get surface and info */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* make a temporary mesh from the drawsurf */ + src.width = ds->patchWidth; + src.height = ds->patchHeight; + src.verts = &yDrawVerts[ ds->firstVert ]; + //% subdivided = SubdivideMesh( src, 8, 512 ); + subdivided = SubdivideMesh2( src, info->patchIterations ); + + /* fit it to the curve and remove colinear verts on rows/columns */ + PutMeshOnCurve( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + + /* find the longest distance on each row/column */ + verts = mesh->verts; + memset( widthTable, 0, sizeof( widthTable ) ); + memset( heightTable, 0, sizeof( heightTable ) ); + for( y = 0; y < mesh->height; y++ ) + { + for( x = 0; x < mesh->width; x++ ) + { + /* get width */ + if( x + 1 < mesh->width ) + { + a = &verts[ (y * mesh->width) + x ]; + b = &verts[ (y * mesh->width) + x + 1 ]; + VectorSubtract( a->xyz, b->xyz, delta ); + length = VectorLength( delta ); + if( length > widthTable[ x ] ) + widthTable[ x ] = length; + } + + /* get height */ + if( y + 1 < mesh->height ) + { + a = &verts[ (y * mesh->width) + x ]; + b = &verts[ ((y + 1) * mesh->width) + x ]; + VectorSubtract( a->xyz, b->xyz, delta ); + length = VectorLength( delta ); + if( length > heightTable[ y ] ) + heightTable[ y ] = length; + } + } + } + + /* determine lightmap width */ + length = 0; + for( x = 0; x < (mesh->width - 1); x++ ) + length += widthTable[ x ]; + lm->w = ceil( length / lm->sampleSize ) + 1; + if( lm->w < ds->patchWidth ) + lm->w = ds->patchWidth; + if( lm->w > lm->customWidth ) + lm->w = lm->customWidth; + sBasis = (float) (lm->w - 1) / (float) (ds->patchWidth - 1); + + /* determine lightmap height */ + length = 0; + for( y = 0; y < (mesh->height - 1); y++ ) + length += heightTable[ y ]; + lm->h = ceil( length / lm->sampleSize ) + 1; + if( lm->h < ds->patchHeight ) + lm->h = ds->patchHeight; + if( lm->h > lm->customHeight ) + lm->h = lm->customHeight; + tBasis = (float) (lm->h - 1) / (float) (ds->patchHeight - 1); + + /* free the temporary mesh */ + FreeMesh( mesh ); + + /* set the lightmap texture coordinates in yDrawVerts */ + lm->wrap[ 0 ] = qtrue; + lm->wrap[ 1 ] = qtrue; + verts = &yDrawVerts[ ds->firstVert ]; + for( y = 0; y < ds->patchHeight; y++ ) + { + t = (tBasis * y) + 0.5f; + for( x = 0; x < ds->patchWidth; x++ ) + { + s = (sBasis * x) + 0.5f; + verts[ (y * ds->patchWidth) + x ].lightmap[ 0 ][ 0 ] = s * superSample; + verts[ (y * ds->patchWidth) + x ].lightmap[ 0 ][ 1 ] = t * superSample; + + if( y == 0 && !VectorCompare( verts[ x ].xyz, verts[ ((ds->patchHeight - 1) * ds->patchWidth) + x ].xyz ) ) + lm->wrap[ 1 ] = qfalse; + } + + if( !VectorCompare( verts[ (y * ds->patchWidth) ].xyz, verts[ (y * ds->patchWidth) + (ds->patchWidth - 1) ].xyz ) ) + lm->wrap[ 0 ] = qfalse; + } + + /* debug code: */ + //% Sys_Printf( "wrap S: %d wrap T: %d\n", lm->wrap[ 0 ], lm->wrap[ 1 ] ); + //% if( lm->w > (ds->lightmapWidth & 0xFF) || lm->h > (ds->lightmapHeight & 0xFF) ) + //% Sys_Printf( "Patch lightmap: (%3d %3d) > (%3d, %3d)\n", lm->w, lm->h, ds->lightmapWidth & 0xFF, ds->lightmapHeight & 0xFF ); + //% ds->lightmapWidth = lm->w | (ds->lightmapWidth & 0xFFFF0000); + //% ds->lightmapHeight = lm->h | (ds->lightmapHeight & 0xFFFF0000); + + /* add to counts */ + numPatchesLightmapped++; + + /* return */ + return qtrue; +} + + + +/* +AddSurfaceToRawLightmap() +projects a lightmap for a surface +based on AllocateLightmapForSurface() +*/ + +qboolean AddSurfaceToRawLightmap( int num, rawLightmap_t *lm ) +{ + bspDrawSurface_t *ds, *ds2; + surfaceInfo_t *info, *info2; + int num2, n, i, axisNum; + float s, t, d, len, sampleSize; + vec3_t mins, maxs, origin, faxis, size, exactSize, delta, normalized, vecs[ 2 ]; + vec4_t plane; + bspDrawVert_t *verts; + + + /* get surface and info */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* add the surface to the raw lightmap */ + lightSurfaces[ numLightSurfaces++ ] = num; + lm->numLightSurfaces++; + + /* does this raw lightmap already have any surfaces? */ + if( lm->numLightSurfaces > 1 ) + { + /* surface and raw lightmap must have the same lightmap projection axis */ + if( VectorCompare( info->axis, lm->axis ) == qfalse ) + return qfalse; + + /* match identical attributes */ + if( info->sampleSize != lm->sampleSize || + info->entityNum != lm->entityNum || + info->recvShadows != lm->recvShadows || + info->si->lmCustomWidth != lm->customWidth || + info->si->lmCustomHeight != lm->customHeight || + info->si->lmBrightness != lm->brightness || + info->si->lmFilterRadius != lm->filterRadius || + info->si->splotchFix != lm->splotchFix ) + return qfalse; + + /* surface bounds must intersect with raw lightmap bounds */ + for( i = 0; i < 3; i++ ) + { + if( info->mins[ i ] > lm->maxs[ i ] ) + return qfalse; + if( info->maxs[ i ] < lm->mins[ i ] ) + return qfalse; + } + + /* plane check (fixme: allow merging of nonplanars) */ + if( info->si->lmMergable == qfalse ) + { + if( info->plane == NULL || lm->plane == NULL ) + return qfalse; + + /* compare planes */ + for( i = 0; i < 4; i++ ) + if( fabs( info->plane[ i ] - lm->plane[ i ] ) > EQUAL_EPSILON ) + return qfalse; + } + + /* debug code hacking */ + //% if( lm->numLightSurfaces > 1 ) + //% return qfalse; + } + + /* set plane */ + if( info->plane == NULL ) + lm->plane = NULL; + + /* add surface to lightmap bounds */ + AddPointToBounds( info->mins, lm->mins, lm->maxs ); + AddPointToBounds( info->maxs, lm->mins, lm->maxs ); + + /* check to see if this is a non-planar patch */ + if( ds->surfaceType == MST_PATCH && + lm->axis[ 0 ] == 0.0f && lm->axis[ 1 ] == 0.0f && lm->axis[ 2 ] == 0.0f ) + return AddPatchToRawLightmap( num, lm ); + + /* start with initially requested sample size */ + sampleSize = lm->sampleSize; + + /* round to the lightmap resolution */ + for( i = 0; i < 3; i++ ) + { + exactSize[ i ] = lm->maxs[ i ] - lm->mins[ i ]; + mins[ i ] = sampleSize * floor( lm->mins[ i ] / sampleSize ); + maxs[ i ] = sampleSize * ceil( lm->maxs[ i ] / sampleSize ); + size[ i ] = (maxs[ i ] - mins[ i ]) / sampleSize + 1.0f; + + /* hack (god this sucks) */ + if( size[ i ] > lm->customWidth || size[ i ] > lm->customHeight ) + { + i = -1; + sampleSize += 1.0f; + } + } + + /* set actual sample size */ + lm->actualSampleSize = sampleSize; + + /* fixme: copy rounded mins/maxes to lightmap record? */ + if( lm->plane == NULL ) + { + VectorCopy( mins, lm->mins ); + VectorCopy( maxs, lm->maxs ); + VectorCopy( mins, origin ); + } + + /* set lightmap origin */ + VectorCopy( lm->mins, origin ); + + /* make absolute axis */ + faxis[ 0 ] = fabs( lm->axis[ 0 ] ); + faxis[ 1 ] = fabs( lm->axis[ 1 ] ); + faxis[ 2 ] = fabs( lm->axis[ 2 ] ); + + /* clear out lightmap vectors */ + memset( vecs, 0, sizeof( vecs ) ); + + /* classify the plane (x y or z major) (ydnar: biased to z axis projection) */ + if( faxis[ 2 ] >= faxis[ 0 ] && faxis[ 2 ] >= faxis[ 1 ] ) + { + axisNum = 2; + lm->w = size[ 0 ]; + lm->h = size[ 1 ]; + vecs[ 0 ][ 0 ] = 1.0f / sampleSize; + vecs[ 1 ][ 1 ] = 1.0f / sampleSize; + } + else if( faxis[ 0 ] >= faxis[ 1 ] && faxis[ 0 ] >= faxis[ 2 ] ) + { + axisNum = 0; + lm->w = size[ 1 ]; + lm->h = size[ 2 ]; + vecs[ 0 ][ 1 ] = 1.0f / sampleSize; + vecs[ 1 ][ 2 ] = 1.0f / sampleSize; + } + else + { + axisNum = 1; + lm->w = size[ 0 ]; + lm->h = size[ 2 ]; + vecs[ 0 ][ 0 ] = 1.0f / sampleSize; + vecs[ 1 ][ 2 ] = 1.0f / sampleSize; + } + + /* check for bogus axis */ + if( faxis[ axisNum ] == 0.0f ) + { + Sys_Printf( "WARNING: ProjectSurfaceLightmap: Chose a 0 valued axis\n" ); + lm->w = lm->h = 0; + return qfalse; + } + + /* store the axis number in the lightmap */ + lm->axisNum = axisNum; + + /* walk the list of surfaces on this raw lightmap */ + for( n = 0; n < lm->numLightSurfaces; n++ ) + { + /* get surface */ + num2 = lightSurfaces[ lm->firstLightSurface + n ]; + ds2 = &bspDrawSurfaces[ num2 ]; + info2 = &surfaceInfos[ num2 ]; + verts = &yDrawVerts[ ds2->firstVert ]; + + /* set the lightmap texture coordinates in yDrawVerts in [0, superSample * lm->customWidth] space */ + for( i = 0; i < ds2->numVerts; i++ ) + { + VectorSubtract( verts[ i ].xyz, origin, delta ); + s = DotProduct( delta, vecs[ 0 ] ) + 0.5f; + t = DotProduct( delta, vecs[ 1 ] ) + 0.5f; + verts[ i ].lightmap[ 0 ][ 0 ] = s * superSample; + verts[ i ].lightmap[ 0 ][ 1 ] = t * superSample; + + if( s > (float) lm->w || t > (float) lm->h ) + { + Sys_FPrintf( SYS_VRB, "WARNING: Lightmap texture coords out of range: S %1.4f > %3d || T %1.4f > %3d\n", + s, lm->w, t, lm->h ); + } + } + } + + /* get first drawsurface */ + num2 = lightSurfaces[ lm->firstLightSurface ]; + ds2 = &bspDrawSurfaces[ num2 ]; + info2 = &surfaceInfos[ num2 ]; + verts = &yDrawVerts[ ds2->firstVert ]; + + /* calculate lightmap origin */ + if( VectorLength( ds2->lightmapVecs[ 2 ] ) ) + VectorCopy( ds2->lightmapVecs[ 2 ], plane ); + else + VectorCopy( lm->axis, plane ); + plane[ 3 ] = DotProduct( verts[ 0 ].xyz, plane ); + + VectorCopy( origin, lm->origin ); + d = DotProduct( lm->origin, plane ) - plane[ 3 ]; + d /= plane[ axisNum ]; + lm->origin[ axisNum ] -= d; + + /* legacy support */ + VectorCopy( lm->origin, ds->lightmapOrigin ); + + /* for planar surfaces, create lightmap vectors for st->xyz conversion */ + if( VectorLength( ds->lightmapVecs[ 2 ] ) || 1 ) /* ydnar: can't remember what exactly i was thinking here... */ + { + /* allocate space for the vectors */ + lm->vecs = safe_malloc( 3 * sizeof( vec3_t ) ); + memset( lm->vecs, 0, 3 * sizeof( vec3_t ) ); + VectorCopy( ds->lightmapVecs[ 2 ], lm->vecs[ 2 ] ); + + /* project stepped lightmap blocks and subtract to get planevecs */ + for( i = 0; i < 2; i++ ) + { + len = VectorNormalize( vecs[ i ], normalized ); + VectorScale( normalized, (1.0 / len), lm->vecs[ i ] ); + d = DotProduct( lm->vecs[ i ], plane ); + d /= plane[ axisNum ]; + lm->vecs[ i ][ axisNum ] -= d; + } + } + else + { + /* lightmap vectors are useless on a non-planar surface */ + lm->vecs = NULL; + } + + /* add to counts */ + if( ds->surfaceType == MST_PATCH ) + { + numPatchesLightmapped++; + if( lm->plane != NULL ) + numPlanarPatchesLightmapped++; + } + else + { + if( lm->plane != NULL ) + numPlanarsLightmapped++; + else + numNonPlanarsLightmapped++; + } + + /* return */ + return qtrue; +} + + + +/* +CompareSurfaceInfo() +compare function for qsort() +*/ + +static int CompareSurfaceInfo( const void *a, const void *b ) +{ + surfaceInfo_t *aInfo, *bInfo; + int i; + + + /* get surface info */ + aInfo = &surfaceInfos[ *((int*) a) ]; + bInfo = &surfaceInfos[ *((int*) b) ]; + + /* model first */ + if( aInfo->model < bInfo->model ) + return 1; + else if( aInfo->model > bInfo->model ) + return -1; + + /* then lightmap status */ + if( aInfo->hasLightmap < bInfo->hasLightmap ) + return 1; + else if( aInfo->hasLightmap > bInfo->hasLightmap ) + return -1; + + /* then lightmap sample size */ + if( aInfo->sampleSize < bInfo->sampleSize ) + return 1; + else if( aInfo->sampleSize > bInfo->sampleSize ) + return -1; + + /* then lightmap axis */ + for( i = 0; i < 3; i++ ) + { + if( aInfo->axis[ i ] < bInfo->axis[ i ] ) + return 1; + else if( aInfo->axis[ i ] > bInfo->axis[ i ] ) + return -1; + } + + /* then plane */ + if( aInfo->plane == NULL && bInfo->plane != NULL ) + return 1; + else if( aInfo->plane != NULL && bInfo->plane == NULL ) + return -1; + else if( aInfo->plane != NULL && bInfo->plane != NULL ) + { + for( i = 0; i < 4; i++ ) + { + if( aInfo->plane[ i ] < bInfo->plane[ i ] ) + return 1; + else if( aInfo->plane[ i ] > bInfo->plane[ i ] ) + return -1; + } + } + + /* then position in world */ + for( i = 0; i < 3; i++ ) + { + if( aInfo->mins[ i ] < bInfo->mins[ i ] ) + return 1; + else if( aInfo->mins[ i ] > bInfo->mins[ i ] ) + return -1; + } + + /* these are functionally identical (this should almost never happen) */ + return 0; +} + + + +/* +SetupSurfaceLightmaps() +allocates lightmaps for every surface in the bsp that needs one +this depends on yDrawVerts being allocated +*/ + +void SetupSurfaceLightmaps( void ) +{ + int i, j, k, s,num, num2; + bspModel_t *model; + bspLeaf_t *leaf; + bspDrawSurface_t *ds, *ds2; + surfaceInfo_t *info, *info2; + rawLightmap_t *lm; + qboolean added; + vec3_t mapSize, entityOrigin; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- SetupSurfaceLightmaps ---\n"); + + /* determine supersample amount */ + if( superSample < 1 ) + superSample = 1; + else if( superSample > 8 ) + { + Sys_Printf( "WARNING: Insane supersampling amount (%d) detected.\n", superSample ); + superSample = 8; + } + + /* clear map bounds */ + ClearBounds( mapMins, mapMaxs ); + + /* allocate a list of surface clusters */ + numSurfaceClusters = 0; + maxSurfaceClusters = numBSPLeafSurfaces; + surfaceClusters = safe_malloc( maxSurfaceClusters * sizeof( *surfaceClusters ) ); + memset( surfaceClusters, 0, maxSurfaceClusters * sizeof( *surfaceClusters ) ); + + /* allocate a list for per-surface info */ + surfaceInfos = safe_malloc( numBSPDrawSurfaces * sizeof( *surfaceInfos ) ); + memset( surfaceInfos, 0, numBSPDrawSurfaces * sizeof( *surfaceInfos ) ); + for( i = 0; i < numBSPDrawSurfaces; i++ ) + surfaceInfos[ i ].childSurfaceNum = -1; + + /* allocate a list of surface indexes to be sorted */ + sortSurfaces = safe_malloc( numBSPDrawSurfaces * sizeof( int ) ); + memset( sortSurfaces, 0, numBSPDrawSurfaces * sizeof( int ) ); + + /* walk each model in the bsp */ + for( i = 0; i < numBSPModels; i++ ) + { + /* get model */ + model = &bspModels[ i ]; + + /* walk the list of surfaces in this model and fill out the info structs */ + for( j = 0; j < model->numBSPSurfaces; j++ ) + { + /* make surface index */ + num = model->firstBSPSurface + j; + + /* copy index to sort list */ + sortSurfaces[ num ] = num; + + /* get surface and info */ + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* set entity origin */ + if( ds->numVerts > 0 ) + VectorSubtract( yDrawVerts[ ds->firstVert ].xyz, bspDrawVerts[ ds->firstVert ].xyz, entityOrigin ); + else + VectorClear( entityOrigin ); + + /* basic setup */ + info->model = model; + info->lm = NULL; + info->plane = NULL; + info->firstSurfaceCluster = numSurfaceClusters; + + /* get extra data */ + info->si = GetSurfaceExtraShaderInfo( num ); + if( info->si == NULL ) + info->si = ShaderInfoForShader( bspShaders[ ds->shaderNum ].shader ); + info->parentSurfaceNum = GetSurfaceExtraParentSurfaceNum( num ); + info->entityNum = GetSurfaceExtraEntityNum( num ); + info->castShadows = GetSurfaceExtraCastShadows( num ); + info->recvShadows = GetSurfaceExtraRecvShadows( num ); + info->sampleSize = GetSurfaceExtraSampleSize( num ); + info->longestCurve = GetSurfaceExtraLongestCurve( num ); + info->patchIterations = IterationsForCurve( info->longestCurve, patchSubdivisions ); + GetSurfaceExtraLightmapAxis( num, info->axis ); + + /* mark parent */ + if( info->parentSurfaceNum >= 0 ) + surfaceInfos[ info->parentSurfaceNum ].childSurfaceNum = j; + + /* determine surface bounds */ + ClearBounds( info->mins, info->maxs ); + for( k = 0; k < ds->numVerts; k++ ) + { + AddPointToBounds( yDrawVerts[ ds->firstVert + k ].xyz, mapMins, mapMaxs ); + AddPointToBounds( yDrawVerts[ ds->firstVert + k ].xyz, info->mins, info->maxs ); + } + + /* find all the bsp clusters the surface falls into */ + for( k = 0; k < numBSPLeafs; k++ ) + { + /* get leaf */ + leaf = &bspLeafs[ k ]; + + /* test bbox */ + if( leaf->mins[ 0 ] > info->maxs[ 0 ] || leaf->maxs[ 0 ] < info->mins[ 0 ] || + leaf->mins[ 1 ] > info->maxs[ 1 ] || leaf->maxs[ 1 ] < info->mins[ 1 ] || + leaf->mins[ 2 ] > info->maxs[ 2 ] || leaf->maxs[ 2 ] < info->mins[ 2 ] ) + continue; + + /* test leaf surfaces */ + for( s = 0; s < leaf->numBSPLeafSurfaces; s++ ) + { + if( bspLeafSurfaces[ leaf->firstBSPLeafSurface + s ] == num ) + { + if( numSurfaceClusters >= maxSurfaceClusters ) + Error( "maxSurfaceClusters exceeded" ); + surfaceClusters[ numSurfaceClusters ] = leaf->cluster; + numSurfaceClusters++; + info->numSurfaceClusters++; + } + } + } + + /* determine if surface is planar */ + if( VectorLength( ds->lightmapVecs[ 2 ] ) > 0.0f ) + { + /* make a plane */ + info->plane = safe_malloc( 4 * sizeof( float ) ); + VectorCopy( ds->lightmapVecs[ 2 ], info->plane ); + info->plane[ 3 ] = DotProduct( yDrawVerts[ ds->firstVert ].xyz, info->plane ); + } + + /* determine if surface requires a lightmap */ + if( ds->surfaceType == MST_TRIANGLE_SOUP || + ds->surfaceType == MST_FOLIAGE || + (info->si->compileFlags & C_VERTEXLIT) ) + numSurfsVertexLit++; + else + { + numSurfsLightmapped++; + info->hasLightmap = qtrue; + } + } + } + + /* find longest map distance */ + VectorSubtract( mapMaxs, mapMins, mapSize ); + maxMapDistance = VectorLength( mapSize ); + + /* sort the surfaces info list */ + qsort( sortSurfaces, numBSPDrawSurfaces, sizeof( int ), CompareSurfaceInfo ); + + /* allocate a list of surfaces that would go into raw lightmaps */ + numLightSurfaces = 0; + lightSurfaces = safe_malloc( numSurfsLightmapped * sizeof( int ) ); + memset( lightSurfaces, 0, numSurfsLightmapped * sizeof( int ) ); + + /* allocate a list of raw lightmaps */ + numRawSuperLuxels = 0; + numRawLightmaps = 0; + rawLightmaps = safe_malloc( numSurfsLightmapped * sizeof( *rawLightmaps ) ); + memset( rawLightmaps, 0, numSurfsLightmapped * sizeof( *rawLightmaps ) ); + + /* walk the list of sorted surfaces */ + for( i = 0; i < numBSPDrawSurfaces; i++ ) + { + /* get info and attempt early out */ + num = sortSurfaces[ i ]; + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + if( info->hasLightmap == qfalse || info->lm != NULL || info->parentSurfaceNum >= 0 ) + continue; + + /* allocate a new raw lightmap */ + lm = &rawLightmaps[ numRawLightmaps ]; + numRawLightmaps++; + + /* set it up */ + lm->splotchFix = info->si->splotchFix; + lm->firstLightSurface = numLightSurfaces; + lm->numLightSurfaces = 0; + lm->sampleSize = info->sampleSize; + lm->actualSampleSize = info->sampleSize; + lm->entityNum = info->entityNum; + lm->recvShadows = info->recvShadows; + lm->brightness = info->si->lmBrightness; + lm->filterRadius = info->si->lmFilterRadius; + VectorCopy( info->axis, lm->axis ); + lm->plane = info->plane; + VectorCopy( info->mins, lm->mins ); + VectorCopy( info->maxs, lm->maxs ); + + lm->customWidth = info->si->lmCustomWidth; + lm->customHeight = info->si->lmCustomHeight; + + /* add the surface to the raw lightmap */ + AddSurfaceToRawLightmap( num, lm ); + info->lm = lm; + + /* do an exhaustive merge */ + added = qtrue; + while( added ) + { + /* walk the list of surfaces again */ + added = qfalse; + for( j = i + 1; j < numBSPDrawSurfaces && lm->finished == qfalse; j++ ) + { + /* get info and attempt early out */ + num2 = sortSurfaces[ j ]; + ds2 = &bspDrawSurfaces[ num2 ]; + info2 = &surfaceInfos[ num2 ]; + if( info2->hasLightmap == qfalse || info2->lm != NULL ) + continue; + + /* add the surface to the raw lightmap */ + if( AddSurfaceToRawLightmap( num2, lm ) ) + { + info2->lm = lm; + added = qtrue; + } + else + { + /* back up one */ + lm->numLightSurfaces--; + numLightSurfaces--; + } + } + } + + /* finish the lightmap and allocate the various buffers */ + FinishRawLightmap( lm ); + } + + /* allocate vertex luxel storage */ + for( k = 0; k < MAX_LIGHTMAPS; k++ ) + { + vertexLuxels[ k ] = safe_malloc( numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) ); + memset( vertexLuxels[ k ], 0, numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) ); + radVertexLuxels[ k ] = safe_malloc( numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) ); + memset( radVertexLuxels[ k ], 0, numBSPDrawVerts * VERTEX_LUXEL_SIZE * sizeof( float ) ); + } + + /* emit some stats */ + Sys_FPrintf( SYS_VRB, "%9d surfaces\n", numBSPDrawSurfaces ); + Sys_FPrintf( SYS_VRB, "%9d raw lightmaps\n", numRawLightmaps ); + Sys_FPrintf( SYS_VRB, "%9d surfaces vertex lit\n", numSurfsVertexLit ); + Sys_FPrintf( SYS_VRB, "%9d surfaces lightmapped\n", numSurfsLightmapped ); + Sys_FPrintf( SYS_VRB, "%9d planar surfaces lightmapped\n", numPlanarsLightmapped ); + Sys_FPrintf( SYS_VRB, "%9d non-planar surfaces lightmapped\n", numNonPlanarsLightmapped ); + Sys_FPrintf( SYS_VRB, "%9d patches lightmapped\n", numPatchesLightmapped ); + Sys_FPrintf( SYS_VRB, "%9d planar patches lightmapped\n", numPlanarPatchesLightmapped ); +} + + + +/* +StitchSurfaceLightmaps() +stitches lightmap edges +2002-11-20 update: use this func only for stitching nonplanar patch lightmap seams +*/ + +#define MAX_STITCH_CANDIDATES 32 +#define MAX_STITCH_LUXELS 64 + +void StitchSurfaceLightmaps( void ) +{ + int i, j, x, y, x2, y2, *cluster, *cluster2, + numStitched, numCandidates, numLuxels, f, fOld, start; + rawLightmap_t *lm, *a, *b, *c[ MAX_STITCH_CANDIDATES ]; + float *luxel, *luxel2, *origin, *origin2, *normal, *normal2, + sampleSize, average[ 3 ], totalColor, ootc, *luxels[ MAX_STITCH_LUXELS ]; + + + /* disabled for now */ + return; + + /* note it */ + Sys_Printf( "--- StitchSurfaceLightmaps ---\n"); + + /* init pacifier */ + fOld = -1; + start = I_FloatTime(); + + /* walk the list of raw lightmaps */ + numStitched = 0; + for( i = 0; i < numRawLightmaps; i++ ) + { + /* print pacifier */ + f = 10 * i / numRawLightmaps; + if( f != fOld ) + { + fOld = f; + Sys_Printf( "%i...", f ); + } + + /* get lightmap a */ + a = &rawLightmaps[ i ]; + + /* walk rest of lightmaps */ + numCandidates = 0; + for( j = i + 1; j < numRawLightmaps && numCandidates < MAX_STITCH_CANDIDATES; j++ ) + { + /* get lightmap b */ + b = &rawLightmaps[ j ]; + + /* test bounding box */ + if( a->mins[ 0 ] > b->maxs[ 0 ] || a->maxs[ 0 ] < b->mins[ 0 ] || + a->mins[ 1 ] > b->maxs[ 1 ] || a->maxs[ 1 ] < b->mins[ 1 ] || + a->mins[ 2 ] > b->maxs[ 2 ] || a->maxs[ 2 ] < b->mins[ 2 ] ) + continue; + + /* add candidate */ + c[ numCandidates++ ] = b; + } + + /* walk luxels */ + for( y = 0; y < a->sh; y++ ) + { + for( x = 0; x < a->sw; x++ ) + { + /* ignore unmapped/unlit luxels */ + lm = a; + cluster = SUPER_CLUSTER( x, y ); + if( *cluster == CLUSTER_UNMAPPED ) + continue; + luxel = SUPER_LUXEL( 0, x, y ); + if( luxel[ 3 ] <= 0.0f ) + continue; + + /* get particulars */ + origin = SUPER_ORIGIN( x, y ); + normal = SUPER_NORMAL( x, y ); + + /* walk candidate list */ + for( j = 0; j < numCandidates; j++ ) + { + /* get candidate */ + b = c[ j ]; + lm = b; + + /* set samplesize to the smaller of the pair */ + sampleSize = 0.5f * (a->actualSampleSize < b->actualSampleSize ? a->actualSampleSize : b->actualSampleSize); + + /* test bounding box */ + if( origin[ 0 ] < (b->mins[ 0 ] - sampleSize) || (origin[ 0 ] > b->maxs[ 0 ] + sampleSize) || + origin[ 1 ] < (b->mins[ 1 ] - sampleSize) || (origin[ 1 ] > b->maxs[ 1 ] + sampleSize) || + origin[ 2 ] < (b->mins[ 2 ] - sampleSize) || (origin[ 2 ] > b->maxs[ 2 ] + sampleSize) ) + continue; + + /* walk candidate luxels */ + VectorClear( average ); + numLuxels = 0; + totalColor = 0.0f; + for( y2 = 0; y2 < b->sh && numLuxels < MAX_STITCH_LUXELS; y2++ ) + { + for( x2 = 0; x2 < b->sw && numLuxels < MAX_STITCH_LUXELS; x2++ ) + { + /* ignore same luxels */ + if( a == b && abs( x - x2 ) <= 1 && abs( y - y2 ) <= 1 ) + continue; + + /* ignore unmapped/unlit luxels */ + cluster2 = SUPER_CLUSTER( x2, y2 ); + if( *cluster2 == CLUSTER_UNMAPPED ) + continue; + luxel2 = SUPER_LUXEL( 0, x2, y2 ); + if( luxel2[ 3 ] <= 0.0f ) + continue; + + /* get particulars */ + origin2 = SUPER_ORIGIN( x2, y2 ); + normal2 = SUPER_NORMAL( x2, y2 ); + + /* test normal */ + if( DotProduct( normal, normal2 ) < 0.5f ) + continue; + + /* test bounds */ + if( fabs( origin[ 0 ] - origin2[ 0 ] ) > sampleSize || + fabs( origin[ 1 ] - origin2[ 1 ] ) > sampleSize || + fabs( origin[ 2 ] - origin2[ 2 ] ) > sampleSize ) + continue; + + /* add luxel */ + //% VectorSet( luxel2, 255, 0, 255 ); + luxels[ numLuxels++ ] = luxel2; + VectorAdd( average, luxel2, average ); + totalColor += luxel2[ 3 ]; + } + } + + /* early out */ + if( numLuxels == 0 ) + continue; + + /* scale average */ + ootc = 1.0f / totalColor; + VectorScale( average, ootc, luxel ); + luxel[ 3 ] = 1.0f; + numStitched++; + } + } + } + } + + /* emit statistics */ + Sys_Printf( " (%i)\n", (int) (I_FloatTime() - start) ); + Sys_FPrintf( SYS_VRB, "%9d luxels stitched\n", numStitched ); +} + + + +/* +CompareBSPLuxels() +compares two surface lightmaps' bsp luxels, ignoring occluded luxels +*/ + +#define SOLID_EPSILON 0.0625 +#define LUXEL_TOLERANCE 0.0025 +#define LUXEL_COLOR_FRAC 0.001302083 /* 1 / 3 / 256 */ + +static qboolean CompareBSPLuxels( rawLightmap_t *a, int aNum, rawLightmap_t *b, int bNum ) +{ + rawLightmap_t *lm; + int x, y; + double delta, total, rd, gd, bd; + float *aLuxel, *bLuxel; + + + /* styled lightmaps will never be collapsed to non-styled lightmaps when there is _minlight */ + if( (minLight[ 0 ] || minLight[ 1 ] || minLight[ 2 ]) && + ((aNum == 0 && bNum != 0) || (aNum != 0 && bNum == 0)) ) + return qfalse; + + /* basic tests */ + if( a->customWidth != b->customWidth || a->customHeight != b->customHeight || + a->brightness != b->brightness || + a->solid[ aNum ] != b->solid[ bNum ] || + a->bspLuxels[ aNum ] == NULL || b->bspLuxels[ bNum ] == NULL ) + return qfalse; + + /* compare solid color lightmaps */ + if( a->solid[ aNum ] && b->solid[ bNum ] ) + { + /* get deltas */ + rd = fabs( a->solidColor[ aNum ][ 0 ] - b->solidColor[ bNum ][ 0 ] ); + gd = fabs( a->solidColor[ aNum ][ 1 ] - b->solidColor[ bNum ][ 1 ] ); + bd = fabs( a->solidColor[ aNum ][ 2 ] - b->solidColor[ bNum ][ 2 ] ); + + /* compare color */ + if( rd > SOLID_EPSILON || gd > SOLID_EPSILON|| bd > SOLID_EPSILON ) + return qfalse; + + /* okay */ + return qtrue; + } + + /* compare nonsolid lightmaps */ + if( a->w != b->w || a->h != b->h ) + return qfalse; + + /* compare luxels */ + delta = 0.0; + total = 0.0; + for( y = 0; y < a->h; y++ ) + { + for( x = 0; x < a->w; x++ ) + { + /* increment total */ + total += 1.0; + + /* get luxels */ + lm = a; aLuxel = BSP_LUXEL( aNum, x, y ); + lm = b; bLuxel = BSP_LUXEL( bNum, x, y ); + + /* ignore unused luxels */ + if( aLuxel[ 0 ] < 0 || bLuxel[ 0 ] < 0 ) + continue; + + /* get deltas */ + rd = fabs( aLuxel[ 0 ] - bLuxel[ 0 ] ); + gd = fabs( aLuxel[ 1 ] - bLuxel[ 1 ] ); + bd = fabs( aLuxel[ 2 ] - bLuxel[ 2 ] ); + + /* 2003-09-27: compare individual luxels */ + if( rd > 3.0 || gd > 3.0 || bd > 3.0 ) + return qfalse; + + /* compare (fixme: take into account perceptual differences) */ + delta += rd * LUXEL_COLOR_FRAC; + delta += gd * LUXEL_COLOR_FRAC; + delta += bd * LUXEL_COLOR_FRAC; + + /* is the change too high? */ + if( total > 0.0 && ((delta / total) > LUXEL_TOLERANCE) ) + return qfalse; + } + } + + /* made it this far, they must be identical (or close enough) */ + return qtrue; +} + + + +/* +MergeBSPLuxels() +merges two surface lightmaps' bsp luxels, overwriting occluded luxels +*/ + +static qboolean MergeBSPLuxels( rawLightmap_t *a, int aNum, rawLightmap_t *b, int bNum ) +{ + rawLightmap_t *lm; + int x, y; + float luxel[ 3 ], *aLuxel, *bLuxel; + + + /* basic tests */ + if( a->customWidth != b->customWidth || a->customHeight != b->customHeight || + a->brightness != b->brightness || + a->solid[ aNum ] != b->solid[ bNum ] || + a->bspLuxels[ aNum ] == NULL || b->bspLuxels[ bNum ] == NULL ) + return qfalse; + + /* compare solid lightmaps */ + if( a->solid[ aNum ] && b->solid[ bNum ] ) + { + /* average */ + VectorAdd( a->solidColor[ aNum ], b->solidColor[ bNum ], luxel ); + VectorScale( luxel, 0.5f, luxel ); + + /* copy to both */ + VectorCopy( luxel, a->solidColor[ aNum ] ); + VectorCopy( luxel, b->solidColor[ bNum ] ); + + /* return to sender */ + return qtrue; + } + + /* compare nonsolid lightmaps */ + if( a->w != b->w || a->h != b->h ) + return qfalse; + + /* merge luxels */ + for( y = 0; y < a->h; y++ ) + { + for( x = 0; x < a->w; x++ ) + { + /* get luxels */ + lm = a; aLuxel = BSP_LUXEL( aNum, x, y ); + lm = b; bLuxel = BSP_LUXEL( bNum, x, y ); + + /* handle occlusion mismatch */ + if( aLuxel[ 0 ] < 0.0f ) + VectorCopy( bLuxel, aLuxel ); + else if( bLuxel[ 0 ] < 0.0f ) + VectorCopy( aLuxel, bLuxel ); + else + { + /* average */ + VectorAdd( aLuxel, bLuxel, luxel ); + VectorScale( luxel, 0.5f, luxel ); + + /* debugging code */ + //% luxel[ 2 ] += 64.0f; + + /* copy to both */ + VectorCopy( luxel, aLuxel ); + VectorCopy( luxel, bLuxel ); + } + } + } + + /* done */ + return qtrue; +} + + + +/* +ApproximateLuxel() +determines if a single luxel is can be approximated with the interpolated vertex rgba +*/ + +static qboolean ApproximateLuxel( rawLightmap_t *lm, bspDrawVert_t *dv ) +{ + int i, x, y, d, lightmapNum; + float *luxel; + vec3_t color, vertexColor; + byte cb[ 4 ], vcb[ 4 ]; + + + /* find luxel xy coords */ + x = dv->lightmap[ 0 ][ 0 ] / superSample; + y = dv->lightmap[ 0 ][ 1 ] / superSample; + if( x < 0 ) + x = 0; + else if( x >= lm->w ) + x = lm->w - 1; + if( y < 0 ) + y = 0; + else if( y >= lm->h ) + y = lm->h - 1; + + /* walk list */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if( lm->styles[ lightmapNum ] == LS_NONE ) + continue; + + /* get luxel */ + luxel = BSP_LUXEL( lightmapNum, x, y ); + + /* ignore occluded luxels */ + if( luxel[ 0 ] < 0.0f || luxel[ 1 ] < 0.0f || luxel[ 2 ] < 0.0f ) + return qtrue; + + /* copy, set min color and compare */ + VectorCopy( luxel, color ); + VectorCopy( dv->color[ 0 ], vertexColor ); + + /* styles are not affected by minlight */ + if( lightmapNum == 0 ) + { + for( i = 0; i < 3; i++ ) + { + /* set min color */ + if( color[ i ] < minLight[ i ] ) + color[ i ] = minLight[ i ]; + if( vertexColor[ i ] < minLight[ i ] ) /* note NOT minVertexLight */ + vertexColor[ i ] = minLight[ i ]; + } + } + + /* set to bytes */ + ColorToBytes( color, cb, 1.0f ); + ColorToBytes( vertexColor, vcb, 1.0f ); + + /* compare */ + for( i = 0; i < 3; i++ ) + { + d = cb[ i ] - vcb[ i ]; + if( d < 0 ) + d *= -1; + if( d > approximateTolerance ) + return qfalse; + } + } + + /* close enough for the girls i date */ + return qtrue; +} + + + +/* +ApproximateTriangle() +determines if a single triangle can be approximated with vertex rgba +*/ + +static qboolean ApproximateTriangle_r( rawLightmap_t *lm, bspDrawVert_t *dv[ 3 ] ) +{ + bspDrawVert_t mid, *dv2[ 3 ]; + int max; + + + /* approximate the vertexes */ + if( ApproximateLuxel( lm, dv[ 0 ] ) == qfalse ) + return qfalse; + if( ApproximateLuxel( lm, dv[ 1 ] ) == qfalse ) + return qfalse; + if( ApproximateLuxel( lm, dv[ 2 ] ) == qfalse ) + return qfalse; + + /* subdivide calc */ + { + int i; + float dx, dy, dist, maxDist; + + + /* find the longest edge and split it */ + max = -1; + maxDist = 0; + for( i = 0; i < 3; i++ ) + { + dx = dv[ i ]->lightmap[ 0 ][ 0 ] - dv[ (i + 1) % 3 ]->lightmap[ 0 ][ 0 ]; + dy = dv[ i ]->lightmap[ 0 ][ 1 ] - dv[ (i + 1) % 3 ]->lightmap[ 0 ][ 1 ]; + dist = sqrt( (dx * dx) + (dy * dy) ); + if( dist > maxDist ) + { + maxDist = dist; + max = i; + } + } + + /* try to early out */ + if( i < 0 || maxDist < subdivideThreshold ) + return qtrue; + } + + /* split the longest edge and map it */ + LerpDrawVert( dv[ max ], dv[ (max + 1) % 3 ], &mid ); + if( ApproximateLuxel( lm, &mid ) == qfalse ) + return qfalse; + + /* recurse to first triangle */ + VectorCopy( dv, dv2 ); + dv2[ max ] = ∣ + if( ApproximateTriangle_r( lm, dv2 ) == qfalse ) + return qfalse; + + /* recurse to second triangle */ + VectorCopy( dv, dv2 ); + dv2[ (max + 1) % 3 ] = ∣ + return ApproximateTriangle_r( lm, dv2 ); +} + + + +/* +ApproximateLightmap() +determines if a raw lightmap can be approximated sufficiently with vertex colors +*/ + +static qboolean ApproximateLightmap( rawLightmap_t *lm ) +{ + int n, num, i, x, y, pw[ 5 ], r; + bspDrawSurface_t *ds; + surfaceInfo_t *info; + mesh_t src, *subdivided, *mesh; + bspDrawVert_t *verts, *dv[ 3 ]; + qboolean approximated; + + + /* approximating? */ + if( approximateTolerance <= 0 ) + return qfalse; + + /* test for jmonroe */ + #if 0 + /* don't approx lightmaps with styled twins */ + if( lm->numStyledTwins > 0 ) + return qfalse; + + /* don't approx lightmaps with styles */ + for( i = 1; i < MAX_LIGHTMAPS; i++ ) + { + if( lm->styles[ i ] != LS_NONE ) + return qfalse; + } + #endif + + /* assume reduced until shadow detail is found */ + approximated = qtrue; + + /* walk the list of surfaces on this raw lightmap */ + for( n = 0; n < lm->numLightSurfaces; n++ ) + { + /* get surface */ + num = lightSurfaces[ lm->firstLightSurface + n ]; + ds = &bspDrawSurfaces[ num ]; + info = &surfaceInfos[ num ]; + + /* assume not-reduced initially */ + info->approximated = qfalse; + + /* bail if lightmap doesn't match up */ + if( info->lm != lm ) + continue; + + /* bail if not vertex lit */ + if( info->si->noVertexLight ) + continue; + + /* assume that surfaces whose bounding boxes is smaller than 2x samplesize will be forced to vertex */ + if( (info->maxs[ 0 ] - info->mins[ 0 ]) <= (2.0f * info->sampleSize) && + (info->maxs[ 1 ] - info->mins[ 1 ]) <= (2.0f * info->sampleSize) && + (info->maxs[ 2 ] - info->mins[ 2 ]) <= (2.0f * info->sampleSize) ) + { + info->approximated = qtrue; + numSurfsVertexForced++; + continue; + } + + /* handle the triangles */ + switch( ds->surfaceType ) + { + case MST_PLANAR: + /* get verts */ + verts = yDrawVerts + ds->firstVert; + + /* map the triangles */ + info->approximated = qtrue; + for( i = 0; i < ds->numIndexes && info->approximated; i += 3 ) + { + dv[ 0 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i ] ]; + dv[ 1 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 1 ] ]; + dv[ 2 ] = &verts[ bspDrawIndexes[ ds->firstIndex + i + 2 ] ]; + info->approximated = ApproximateTriangle_r( lm, dv ); + } + break; + + case MST_PATCH: + /* make a mesh from the drawsurf */ + src.width = ds->patchWidth; + src.height = ds->patchHeight; + src.verts = &yDrawVerts[ ds->firstVert ]; + //% subdivided = SubdivideMesh( src, 8, 512 ); + subdivided = SubdivideMesh2( src, info->patchIterations ); + + /* fit it to the curve and remove colinear verts on rows/columns */ + PutMeshOnCurve( *subdivided ); + mesh = RemoveLinearMeshColumnsRows( subdivided ); + FreeMesh( subdivided ); + + /* get verts */ + verts = mesh->verts; + + /* map the mesh quads */ + info->approximated = qtrue; + for( y = 0; y < (mesh->height - 1) && info->approximated; y++ ) + { + for( x = 0; x < (mesh->width - 1) && info->approximated; x++ ) + { + /* set indexes */ + pw[ 0 ] = x + (y * mesh->width); + pw[ 1 ] = x + ((y + 1) * mesh->width); + pw[ 2 ] = x + 1 + ((y + 1) * mesh->width); + pw[ 3 ] = x + 1 + (y * mesh->width); + pw[ 4 ] = x + (y * mesh->width); /* same as pw[ 0 ] */ + + /* set radix */ + r = (x + y) & 1; + + /* get drawverts and map first triangle */ + dv[ 0 ] = &verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &verts[ pw[ r + 1 ] ]; + dv[ 2 ] = &verts[ pw[ r + 2 ] ]; + info->approximated = ApproximateTriangle_r( lm, dv ); + + /* get drawverts and map second triangle */ + dv[ 0 ] = &verts[ pw[ r + 0 ] ]; + dv[ 1 ] = &verts[ pw[ r + 2 ] ]; + dv[ 2 ] = &verts[ pw[ r + 3 ] ]; + if( info->approximated ) + info->approximated = ApproximateTriangle_r( lm, dv ); + } + } + + /* free the mesh */ + FreeMesh( mesh ); + break; + + default: + break; + } + + /* reduced? */ + if( info->approximated == qfalse ) + approximated = qfalse; + else + numSurfsVertexApproximated++; + } + + /* return */ + return approximated; +} + + + +/* +TestOutLightmapStamp() +tests a stamp on a given lightmap for validity +*/ + +static qboolean TestOutLightmapStamp( rawLightmap_t *lm, int lightmapNum, outLightmap_t *olm, int x, int y ) +{ + int sx, sy, ox, oy, offset; + float *luxel; + + + /* bounds check */ + if( x < 0 || y < 0 || (x + lm->w) > olm->customWidth || (y + lm->h) > olm->customHeight ) + return qfalse; + + /* solid lightmaps test a 1x1 stamp */ + if( lm->solid[ lightmapNum ] ) + { + offset = (y * olm->customWidth) + x; + if( olm->lightBits[ offset >> 3 ] & (1 << (offset & 7)) ) + return qfalse; + return qtrue; + } + + /* test the stamp */ + for( sy = 0; sy < lm->h; sy++ ) + { + for( sx = 0; sx < lm->w; sx++ ) + { + /* get luxel */ + luxel = BSP_LUXEL( lightmapNum, sx, sy ); + if( luxel[ 0 ] < 0.0f ) + continue; + + /* get bsp lightmap coords and test */ + ox = x + sx; + oy = y + sy; + offset = (oy * olm->customWidth) + ox; + if( olm->lightBits[ offset >> 3 ] & (1 << (offset & 7)) ) + return qfalse; + } + } + + /* stamp is empty */ + return qtrue; +} + + + +/* +SetupOutLightmap() +sets up an output lightmap +*/ + +static void SetupOutLightmap( rawLightmap_t *lm, outLightmap_t *olm ) +{ + /* dummy check */ + if( lm == NULL || olm == NULL ) + return; + + /* is this a "normal" bsp-stored lightmap? */ + if( (lm->customWidth == game->lightmapSize && lm->customHeight == game->lightmapSize) || externalLightmaps ) + { + olm->lightmapNum = numBSPLightmaps; + numBSPLightmaps++; + + /* lightmaps are interleaved with light direction maps */ + if( deluxemap ) + numBSPLightmaps++; + } + else + olm->lightmapNum = -3; + + /* set external lightmap number */ + olm->extLightmapNum = -1; + + /* set it up */ + olm->numLightmaps = 0; + olm->customWidth = lm->customWidth; + olm->customHeight = lm->customHeight; + olm->freeLuxels = olm->customWidth * olm->customHeight; + olm->numShaders = 0; + + /* allocate buffers */ + olm->lightBits = safe_malloc( (olm->customWidth * olm->customHeight / 8) + 8 ); + memset( olm->lightBits, 0, (olm->customWidth * olm->customHeight / 8) + 8 ); + olm->bspLightBytes = safe_malloc( olm->customWidth * olm->customHeight * 3 ); + memset( olm->bspLightBytes, 0, olm->customWidth * olm->customHeight * 3 ); + if( deluxemap ) + { + olm->bspDirBytes = safe_malloc( olm->customWidth * olm->customHeight * 3 ); + memset( olm->bspDirBytes, 0, olm->customWidth * olm->customHeight * 3 ); + } +} + + + +/* +FindOutLightmaps() +for a given surface lightmap, find output lightmap pages and positions for it +*/ + +static void FindOutLightmaps( rawLightmap_t *lm ) +{ + int i, j, lightmapNum, xMax, yMax, x, y, sx, sy, ox, oy, offset, temp; + outLightmap_t *olm; + surfaceInfo_t *info; + float *luxel, *deluxel; + vec3_t color, direction; + byte *pixel; + qboolean ok; + + + /* set default lightmap number (-3 = LIGHTMAP_BY_VERTEX) */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + lm->outLightmapNums[ lightmapNum ] = -3; + + /* can this lightmap be approximated with vertex color? */ + if( ApproximateLightmap( lm ) ) + return; + + /* walk list */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + if( lm->styles[ lightmapNum ] == LS_NONE ) + continue; + + /* don't store twinned lightmaps */ + if( lm->twins[ lightmapNum ] != NULL ) + continue; + + /* if this is a styled lightmap, try some normalized locations first */ + ok = qfalse; + if( lightmapNum > 0 && outLightmaps != NULL ) + { + /* loop twice */ + for( j = 0; j < 2; j++ ) + { + /* try identical position */ + for( i = 0; i < numOutLightmaps; i++ ) + { + /* get the output lightmap */ + olm = &outLightmaps[ i ]; + + /* simple early out test */ + if( olm->freeLuxels < lm->used ) + continue; + + /* don't store non-custom raw lightmaps on custom bsp lightmaps */ + if( olm->customWidth != lm->customWidth || + olm->customHeight != lm->customHeight ) + continue; + + /* try identical */ + if( j == 0 ) + { + x = lm->lightmapX[ 0 ]; + y = lm->lightmapY[ 0 ]; + ok = TestOutLightmapStamp( lm, lightmapNum, olm, x, y ); + } + + /* try shifting */ + else + { + for( sy = -1; sy <= 1; sy++ ) + { + for( sx = -1; sx <= 1; sx++ ) + { + x = lm->lightmapX[ 0 ] + sx * (olm->customWidth >> 1); //% lm->w; + y = lm->lightmapY[ 0 ] + sy * (olm->customHeight >> 1); //% lm->h; + ok = TestOutLightmapStamp( lm, lightmapNum, olm, x, y ); + + if( ok ) + break; + } + + if( ok ) + break; + } + } + + if( ok ) + break; + } + + if( ok ) + break; + } + } + + /* try normal placement algorithm */ + if( ok == qfalse ) + { + /* reset origin */ + x = 0; + y = 0; + + /* walk the list of lightmap pages */ + for( i = 0; i < numOutLightmaps; i++ ) + { + /* get the output lightmap */ + olm = &outLightmaps[ i ]; + + /* simple early out test */ + if( olm->freeLuxels < lm->used ) + continue; + + /* don't store non-custom raw lightmaps on custom bsp lightmaps */ + if( olm->customWidth != lm->customWidth || + olm->customHeight != lm->customHeight ) + continue; + + /* set maxs */ + if( lm->solid[ lightmapNum ] ) + { + xMax = olm->customWidth; + yMax = olm->customHeight; + } + else + { + xMax = (olm->customWidth - lm->w) + 1; + yMax = (olm->customHeight - lm->h) + 1; + } + + /* walk the origin around the lightmap */ + for( y = 0; y < yMax; y++ ) + { + for( x = 0; x < xMax; x++ ) + { + /* find a fine tract of lauhnd */ + ok = TestOutLightmapStamp( lm, lightmapNum, olm, x, y ); + + if( ok ) + break; + } + + if( ok ) + break; + } + + if( ok ) + break; + + /* reset x and y */ + x = 0; + y = 0; + } + } + + /* no match? */ + if( ok == qfalse ) + { + /* allocate two new output lightmaps */ + numOutLightmaps += 2; + olm = safe_malloc( numOutLightmaps * sizeof( outLightmap_t ) ); + if( outLightmaps != NULL && numOutLightmaps > 2 ) + { + memcpy( olm, outLightmaps, (numOutLightmaps - 2) * sizeof( outLightmap_t ) ); + free( outLightmaps ); + } + outLightmaps = olm; + + /* initialize both out lightmaps */ + SetupOutLightmap( lm, &outLightmaps[ numOutLightmaps - 2 ] ); + SetupOutLightmap( lm, &outLightmaps[ numOutLightmaps - 1 ] ); + + /* set out lightmap */ + i = numOutLightmaps - 2; + olm = &outLightmaps[ i ]; + + /* set stamp xy origin to the first surface lightmap */ + if( lightmapNum > 0 ) + { + x = lm->lightmapX[ 0 ]; + y = lm->lightmapY[ 0 ]; + } + } + + /* if this is a style-using lightmap, it must be exported */ + if( lightmapNum > 0 && game->load != LoadRBSPFile ) + olm->extLightmapNum = 0; + + /* add the surface lightmap to the bsp lightmap */ + lm->outLightmapNums[ lightmapNum ] = i; + lm->lightmapX[ lightmapNum ] = x; + lm->lightmapY[ lightmapNum ] = y; + olm->numLightmaps++; + + /* add shaders */ + for( i = 0; i < lm->numLightSurfaces; i++ ) + { + /* get surface info */ + info = &surfaceInfos[ lightSurfaces[ lm->firstLightSurface + i ] ]; + + /* test for shader */ + for( j = 0; j < olm->numShaders; j++ ) + { + if( olm->shaders[ j ] == info->si ) + break; + } + + /* if it doesn't exist, add it */ + if( j >= olm->numShaders && olm->numShaders < MAX_LIGHTMAP_SHADERS ) + { + olm->shaders[ olm->numShaders ] = info->si; + olm->numShaders++; + numLightmapShaders++; + } + } + + /* set maxs */ + if( lm->solid[ lightmapNum ] ) + { + xMax = 1; + yMax = 1; + } + else + { + xMax = lm->w; + yMax = lm->h; + } + + /* mark the bits used */ + for( y = 0; y < yMax; y++ ) + { + for( x = 0; x < xMax; x++ ) + { + /* get luxel */ + luxel = BSP_LUXEL( lightmapNum, x, y ); + deluxel = BSP_DELUXEL( x, y ); + if( luxel[ 0 ] < 0.0f && !lm->solid[ lightmapNum ]) + continue; + + /* set minimum light */ + if( lm->solid[ lightmapNum ] ) + { + if( debug ) + VectorSet( color, 255.0f, 0.0f, 0.0f ); + else + VectorCopy( lm->solidColor[ lightmapNum ], color ); + } + else + VectorCopy( luxel, color ); + + /* styles are not affected by minlight */ + if( lightmapNum == 0 ) + { + for( i = 0; i < 3; i++ ) + { + if( color[ i ] < minLight[ i ] ) + color[ i ] = minLight[ i ]; + } + } + + /* get bsp lightmap coords */ + ox = x + lm->lightmapX[ lightmapNum ]; + oy = y + lm->lightmapY[ lightmapNum ]; + offset = (oy * olm->customWidth) + ox; + + /* flag pixel as used */ + olm->lightBits[ offset >> 3 ] |= (1 << (offset & 7)); + olm->freeLuxels--; + + /* store color */ + pixel = olm->bspLightBytes + (((oy * olm->customWidth) + ox) * 3); + ColorToBytes( color, pixel, lm->brightness ); + + /* store direction */ + if( deluxemap ) + { + /* normalize average light direction */ + if( VectorNormalize( deluxel, direction ) ) + { + /* encode [-1,1] in [0,255] */ + pixel = olm->bspDirBytes + (((oy * olm->customWidth) + ox) * 3); + for( i = 0; i < 3; i++ ) + { + temp = (direction[ i ] + 1.0f) * 127.5f; + if( temp < 0 ) + pixel[ i ] = 0; + else if( temp > 255 ) + pixel[ i ] = 255; + else + pixel[ i ] = temp; + } + } + } + } + } + } +} + + + +/* +CompareRawLightmap() +compare function for qsort() +*/ + +static int CompareRawLightmap( const void *a, const void *b ) +{ + rawLightmap_t *alm, *blm; + surfaceInfo_t *aInfo, *bInfo; + int i, min, diff; + + + /* get lightmaps */ + alm = &rawLightmaps[ *((int*) a) ]; + blm = &rawLightmaps[ *((int*) b) ]; + + /* get min number of surfaces */ + min = (alm->numLightSurfaces < blm->numLightSurfaces ? alm->numLightSurfaces : blm->numLightSurfaces); + + /* iterate */ + for( i = 0; i < min; i++ ) + { + /* get surface info */ + aInfo = &surfaceInfos[ lightSurfaces[ alm->firstLightSurface + i ] ]; + bInfo = &surfaceInfos[ lightSurfaces[ blm->firstLightSurface + i ] ]; + + /* compare shader names */ + diff = strcmp( aInfo->si->shader, bInfo->si->shader ); + if( diff != 0 ) + return diff; + } + + /* test style count */ + diff = 0; + for( i = 0; i < MAX_LIGHTMAPS; i++ ) + diff += blm->styles[ i ] - alm->styles[ i ]; + if( diff ) + return diff; + + /* compare size */ + diff = (blm->w * blm->h) - (alm->w * alm->h); + if( diff != 0 ) + return diff; + + /* must be equivalent */ + return 0; +} + + + +/* +StoreSurfaceLightmaps() +stores the surface lightmaps into the bsp as byte rgb triplets +*/ + +void StoreSurfaceLightmaps( void ) +{ + int i, j, k, x, y, lx, ly, sx, sy, *cluster, mappedSamples; + int style, size, lightmapNum, lightmapNum2; + float *normal, *luxel, *bspLuxel, *bspLuxel2, *radLuxel, samples, occludedSamples; + vec3_t sample, occludedSample, dirSample, colorMins, colorMaxs; + float *deluxel, *bspDeluxel, *bspDeluxel2; + byte *lb; + int numUsed, numTwins, numTwinLuxels, numStored; + float lmx, lmy, efficiency; + vec3_t color; + bspDrawSurface_t *ds, *parent, dsTemp; + surfaceInfo_t *info; + rawLightmap_t *lm, *lm2; + outLightmap_t *olm; + bspDrawVert_t *dv, *ydv, *dvParent; + char dirname[ 1024 ], filename[ 1024 ]; + shaderInfo_t *csi; + char lightmapName[ 128 ]; + char *rgbGenValues[ 256 ]; + char *alphaGenValues[ 256 ]; + + + /* note it */ + Sys_Printf( "--- StoreSurfaceLightmaps ---\n"); + + /* setup */ + strcpy( dirname, source ); + StripExtension( dirname ); + memset( rgbGenValues, 0, sizeof( rgbGenValues ) ); + memset( alphaGenValues, 0, sizeof( alphaGenValues ) ); + + /* ----------------------------------------------------------------- + average the sampled luxels into the bsp luxels + ----------------------------------------------------------------- */ + + /* note it */ + Sys_FPrintf( SYS_VRB, "Subsampling..." ); + + /* walk the list of raw lightmaps */ + numUsed = 0; + numTwins = 0; + numTwinLuxels = 0; + numSolidLightmaps = 0; + for( i = 0; i < numRawLightmaps; i++ ) + { + /* get lightmap */ + lm = &rawLightmaps[ i ]; + + /* walk individual lightmaps */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early outs */ + if( lm->superLuxels[ lightmapNum ] == NULL ) + continue; + + /* allocate bsp luxel storage */ + if( lm->bspLuxels[ lightmapNum ] == NULL ) + { + size = lm->w * lm->h * BSP_LUXEL_SIZE * sizeof( float ); + lm->bspLuxels[ lightmapNum ] = safe_malloc( size ); + memset( lm->bspLuxels[ lightmapNum ], 0, size ); + } + + /* allocate radiosity lightmap storage */ + if( bounce ) + { + size = lm->w * lm->h * RAD_LUXEL_SIZE * sizeof( float ); + if( lm->radLuxels[ lightmapNum ] == NULL ) + lm->radLuxels[ lightmapNum ] = safe_malloc( size ); + memset( lm->radLuxels[ lightmapNum ], 0, size ); + } + + /* average supersampled luxels */ + for( y = 0; y < lm->h; y++ ) + { + for( x = 0; x < lm->w; x++ ) + { + /* subsample */ + samples = 0.0f; + occludedSamples = 0.0f; + mappedSamples = 0; + VectorClear( sample ); + VectorClear( occludedSample ); + VectorClear( dirSample ); + for( ly = 0; ly < superSample; ly++ ) + { + for( lx = 0; lx < superSample; lx++ ) + { + /* sample luxel */ + sx = x * superSample + lx; + sy = y * superSample + ly; + luxel = SUPER_LUXEL( lightmapNum, sx, sy ); + deluxel = SUPER_DELUXEL( sx, sy ); + normal = SUPER_NORMAL( sx, sy ); + cluster = SUPER_CLUSTER( sx, sy ); + + /* sample deluxemap */ + if( deluxemap && lightmapNum == 0 ) + VectorAdd( dirSample, deluxel, dirSample ); + + /* keep track of used/occluded samples */ + if( *cluster != CLUSTER_UNMAPPED ) + mappedSamples++; + + /* handle lightmap border? */ + if( lightmapBorder && (sx == 0 || sx == (lm->sw - 1) || sy == 0 || sy == (lm->sh - 1) ) && luxel[ 3 ] > 0.0f ) + { + VectorSet( sample, 255.0f, 0.0f, 0.0f ); + samples += 1.0f; + } + + /* handle debug */ + else if( debug && *cluster < 0 ) + { + if( *cluster == CLUSTER_UNMAPPED ) + VectorSet( luxel, 255, 204, 0 ); + else if( *cluster == CLUSTER_OCCLUDED ) + VectorSet( luxel, 255, 0, 255 ); + else if( *cluster == CLUSTER_FLOODED ) + VectorSet( luxel, 0, 32, 255 ); + VectorAdd( occludedSample, luxel, occludedSample ); + occludedSamples += 1.0f; + } + + /* normal luxel handling */ + else if( luxel[ 3 ] > 0.0f ) + { + /* handle lit or flooded luxels */ + if( *cluster > 0 || *cluster == CLUSTER_FLOODED ) + { + VectorAdd( sample, luxel, sample ); + samples += luxel[ 3 ]; + } + + /* handle occluded or unmapped luxels */ + else + { + VectorAdd( occludedSample, luxel, occludedSample ); + occludedSamples += luxel[ 3 ]; + } + + /* handle style debugging */ + if( debug && lightmapNum > 0 && x < 2 && y < 2 ) + { + VectorCopy( debugColors[ 0 ], sample ); + samples = 1; + } + } + } + } + + /* only use occluded samples if necessary */ + if( samples <= 0.0f ) + { + VectorCopy( occludedSample, sample ); + samples = occludedSamples; + } + + /* get luxels */ + luxel = SUPER_LUXEL( lightmapNum, x, y ); + deluxel = SUPER_DELUXEL( x, y ); + + /* store light direction */ + if( deluxemap && lightmapNum == 0 ) + VectorCopy( dirSample, deluxel ); + + /* store the sample back in super luxels */ + if( samples > 0.01f ) + { + VectorScale( sample, (1.0f / samples), luxel ); + luxel[ 3 ] = 1.0f; + } + + /* if any samples were mapped in any way, store ambient color */ + else if( mappedSamples > 0 ) + { + if( lightmapNum == 0 ) + VectorCopy( ambientColor, luxel ); + else + VectorClear( luxel ); + luxel[ 3 ] = 1.0f; + } + + /* store a bogus value to be fixed later */ + else + { + VectorClear( luxel ); + luxel[ 3 ] = -1.0f; + } + } + } + + /* setup */ + lm->used = 0; + ClearBounds( colorMins, colorMaxs ); + + /* clean up and store into bsp luxels */ + for( y = 0; y < lm->h; y++ ) + { + for( x = 0; x < lm->w; x++ ) + { + /* get luxels */ + luxel = SUPER_LUXEL( lightmapNum, x, y ); + deluxel = SUPER_DELUXEL( x, y ); + + /* copy light direction */ + if( deluxemap && lightmapNum == 0 ) + VectorCopy( deluxel, dirSample ); + + /* is this a valid sample? */ + if( luxel[ 3 ] > 0.0f ) + { + VectorCopy( luxel, sample ); + samples = luxel[ 3 ]; + numUsed++; + lm->used++; + + /* fix negative samples */ + for( j = 0; j < 3; j++ ) + { + if( sample[ j ] < 0.0f ) + sample[ j ] = 0.0f; + } + } + else + { + /* nick an average value from the neighbors */ + VectorClear( sample ); + VectorClear( dirSample ); + samples = 0.0f; + + /* fixme: why is this disabled?? */ + for( sy = (y - 1); sy <= (y + 1); sy++ ) + { + if( sy < 0 || sy >= lm->h ) + continue; + + for( sx = (x - 1); sx <= (x + 1); sx++ ) + { + if( sx < 0 || sx >= lm->w || (sx == x && sy == y) ) + continue; + + /* get neighbor's particulars */ + luxel = SUPER_LUXEL( lightmapNum, sx, sy ); + if( luxel[ 3 ] < 0.0f ) + continue; + VectorAdd( sample, luxel, sample ); + samples += luxel[ 3 ]; + } + } + + /* no samples? */ + if( samples == 0.0f ) + { + VectorSet( sample, -1.0f, -1.0f, -1.0f ); + samples = 1.0f; + } + else + { + numUsed++; + lm->used++; + + /* fix negative samples */ + for( j = 0; j < 3; j++ ) + { + if( sample[ j ] < 0.0f ) + sample[ j ] = 0.0f; + } + } + } + + /* scale the sample */ + VectorScale( sample, (1.0f / samples), sample ); + + /* store the sample in the radiosity luxels */ + if( bounce > 0 ) + { + radLuxel = RAD_LUXEL( lightmapNum, x, y ); + VectorCopy( sample, radLuxel ); + + /* if only storing bounced light, early out here */ + if( bounceOnly && !bouncing ) + continue; + } + + /* store the sample in the bsp luxels */ + bspLuxel = BSP_LUXEL( lightmapNum, x, y ); + bspDeluxel = BSP_DELUXEL( x, y ); + + VectorAdd( bspLuxel, sample, bspLuxel ); + if( deluxemap && lightmapNum == 0 ) + VectorAdd( bspDeluxel, dirSample, bspDeluxel ); + + /* add color to bounds for solid checking */ + if( samples > 0.0f ) + AddPointToBounds( bspLuxel, colorMins, colorMaxs ); + } + } + + /* set solid color */ + lm->solid[ lightmapNum ] = qfalse; + VectorAdd( colorMins, colorMaxs, lm->solidColor[ lightmapNum ] ); + VectorScale( lm->solidColor[ lightmapNum ], 0.5f, lm->solidColor[ lightmapNum ] ); + + /* nocollapse prevents solid lightmaps */ + if( noCollapse == qfalse ) + { + /* check solid color */ + VectorSubtract( colorMaxs, colorMins, sample ); + if( (sample[ 0 ] <= SOLID_EPSILON && sample[ 1 ] <= SOLID_EPSILON && sample[ 2 ] <= SOLID_EPSILON) || + (lm->w <= 2 && lm->h <= 2) ) /* small lightmaps get forced to solid color */ + { + /* set to solid */ + VectorCopy( colorMins, lm->solidColor[ lightmapNum ] ); + lm->solid[ lightmapNum ] = qtrue; + numSolidLightmaps++; + } + + /* if all lightmaps aren't solid, then none of them are solid */ + if( lm->solid[ lightmapNum ] != lm->solid[ 0 ] ) + { + for( y = 0; y < MAX_LIGHTMAPS; y++ ) + { + if( lm->solid[ y ] ) + numSolidLightmaps--; + lm->solid[ y ] = qfalse; + } + } + } + + /* wrap bsp luxels if necessary */ + if( lm->wrap[ 0 ] ) + { + for( y = 0; y < lm->h; y++ ) + { + bspLuxel = BSP_LUXEL( lightmapNum, 0, y ); + bspLuxel2 = BSP_LUXEL( lightmapNum, lm->w - 1, y ); + VectorAdd( bspLuxel, bspLuxel2, bspLuxel ); + VectorScale( bspLuxel, 0.5f, bspLuxel ); + VectorCopy( bspLuxel, bspLuxel2 ); + if( deluxemap && lightmapNum == 0 ) + { + bspDeluxel = BSP_DELUXEL( 0, y ); + bspDeluxel2 = BSP_DELUXEL( lm->w - 1, y ); + VectorAdd( bspDeluxel, bspDeluxel2, bspDeluxel ); + VectorScale( bspDeluxel, 0.5f, bspDeluxel ); + VectorCopy( bspDeluxel, bspDeluxel2 ); + } + } + } + if( lm->wrap[ 1 ] ) + { + for( x = 0; x < lm->w; x++ ) + { + bspLuxel = BSP_LUXEL( lightmapNum, x, 0 ); + bspLuxel2 = BSP_LUXEL( lightmapNum, x, lm->h - 1 ); + VectorAdd( bspLuxel, bspLuxel2, bspLuxel ); + VectorScale( bspLuxel, 0.5f, bspLuxel ); + VectorCopy( bspLuxel, bspLuxel2 ); + if( deluxemap && lightmapNum == 0 ) + { + bspDeluxel = BSP_DELUXEL( x, 0 ); + bspDeluxel2 = BSP_DELUXEL( x, lm->h - 1 ); + VectorAdd( bspDeluxel, bspDeluxel2, bspDeluxel ); + VectorScale( bspDeluxel, 0.5f, bspDeluxel ); + VectorCopy( bspDeluxel, bspDeluxel2 ); + } + } + } + } + } + + /* ----------------------------------------------------------------- + collapse non-unique lightmaps + ----------------------------------------------------------------- */ + + if( noCollapse == qfalse && deluxemap == qfalse ) + { + /* note it */ + Sys_FPrintf( SYS_VRB, "collapsing..." ); + + /* set all twin refs to null */ + for( i = 0; i < numRawLightmaps; i++ ) + { + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + rawLightmaps[ i ].twins[ lightmapNum ] = NULL; + rawLightmaps[ i ].twinNums[ lightmapNum ] = -1; + rawLightmaps[ i ].numStyledTwins = 0; + } + } + + /* walk the list of raw lightmaps */ + for( i = 0; i < numRawLightmaps; i++ ) + { + /* get lightmap */ + lm = &rawLightmaps[ i ]; + + /* walk lightmaps */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early outs */ + if( lm->bspLuxels[ lightmapNum ] == NULL || + lm->twins[ lightmapNum ] != NULL ) + continue; + + /* find all lightmaps that are virtually identical to this one */ + for( j = i + 1; j < numRawLightmaps; j++ ) + { + /* get lightmap */ + lm2 = &rawLightmaps[ j ]; + + /* walk lightmaps */ + for( lightmapNum2 = 0; lightmapNum2 < MAX_LIGHTMAPS; lightmapNum2++ ) + { + /* early outs */ + if( lm2->bspLuxels[ lightmapNum2 ] == NULL || + lm2->twins[ lightmapNum2 ] != NULL ) + continue; + + /* compare them */ + if( CompareBSPLuxels( lm, lightmapNum, lm2, lightmapNum2 ) ) + { + /* merge and set twin */ + if( MergeBSPLuxels( lm, lightmapNum, lm2, lightmapNum2 ) ) + { + lm2->twins[ lightmapNum2 ] = lm; + lm2->twinNums[ lightmapNum2 ] = lightmapNum; + numTwins++; + numTwinLuxels += (lm->w * lm->h); + + /* count styled twins */ + if( lightmapNum > 0 ) + lm->numStyledTwins++; + } + } + } + } + } + } + } + + /* ----------------------------------------------------------------- + sort raw lightmaps by shader + ----------------------------------------------------------------- */ + + /* note it */ + Sys_FPrintf( SYS_VRB, "sorting..." ); + + /* allocate a new sorted list */ + if( sortLightmaps == NULL ) + sortLightmaps = safe_malloc( numRawLightmaps * sizeof( int ) ); + + /* fill it out and sort it */ + for( i = 0; i < numRawLightmaps; i++ ) + sortLightmaps[ i ] = i; + qsort( sortLightmaps, numRawLightmaps, sizeof( int ), CompareRawLightmap ); + + /* ----------------------------------------------------------------- + allocate output lightmaps + ----------------------------------------------------------------- */ + + /* note it */ + Sys_FPrintf( SYS_VRB, "allocating..." ); + + /* kill all existing output lightmaps */ + if( outLightmaps != NULL ) + { + for( i = 0; i < numOutLightmaps; i++ ) + { + free( outLightmaps[ i ].lightBits ); + free( outLightmaps[ i ].bspLightBytes ); + } + free( outLightmaps ); + outLightmaps = NULL; + } + + numLightmapShaders = 0; + numOutLightmaps = 0; + numBSPLightmaps = 0; + numExtLightmaps = 0; + + /* find output lightmap */ + for( i = 0; i < numRawLightmaps; i++ ) + { + lm = &rawLightmaps[ sortLightmaps[ i ] ]; + FindOutLightmaps( lm ); + } + + /* set output numbers in twinned lightmaps */ + for( i = 0; i < numRawLightmaps; i++ ) + { + /* get lightmap */ + lm = &rawLightmaps[ sortLightmaps[ i ] ]; + + /* walk lightmaps */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* get twin */ + lm2 = lm->twins[ lightmapNum ]; + if( lm2 == NULL ) + continue; + lightmapNum2 = lm->twinNums[ lightmapNum ]; + + /* find output lightmap from twin */ + lm->outLightmapNums[ lightmapNum ] = lm2->outLightmapNums[ lightmapNum2 ]; + lm->lightmapX[ lightmapNum ] = lm2->lightmapX[ lightmapNum2 ]; + lm->lightmapY[ lightmapNum ] = lm2->lightmapY[ lightmapNum2 ]; + } + } + + /* ----------------------------------------------------------------- + store output lightmaps + ----------------------------------------------------------------- */ + + /* note it */ + Sys_FPrintf( SYS_VRB, "storing..." ); + + /* count the bsp lightmaps and allocate space */ + if( bspLightBytes != NULL ) + free( bspLightBytes ); + if( numBSPLightmaps == 0 || externalLightmaps ) + { + numBSPLightBytes = 0; + bspLightBytes = NULL; + } + else + { + numBSPLightBytes = (numBSPLightmaps * game->lightmapSize * game->lightmapSize * 3); + bspLightBytes = safe_malloc( numBSPLightBytes ); + memset( bspLightBytes, 0, numBSPLightBytes ); + } + + /* walk the list of output lightmaps */ + for( i = 0; i < numOutLightmaps; i++ ) + { + /* get output lightmap */ + olm = &outLightmaps[ i ]; + + /* is this a valid bsp lightmap? */ + if( olm->lightmapNum >= 0 && !externalLightmaps ) + { + /* copy lighting data */ + lb = bspLightBytes + (olm->lightmapNum * game->lightmapSize * game->lightmapSize * 3); + memcpy( lb, olm->bspLightBytes, game->lightmapSize * game->lightmapSize * 3 ); + + /* copy direction data */ + if( deluxemap ) + { + lb = bspLightBytes + ((olm->lightmapNum + 1) * game->lightmapSize * game->lightmapSize * 3); + memcpy( lb, olm->bspDirBytes, game->lightmapSize * game->lightmapSize * 3 ); + } + } + + /* external lightmap? */ + if( olm->lightmapNum < 0 || olm->extLightmapNum >= 0 || externalLightmaps ) + { + /* make a directory for the lightmaps */ + Q_mkdir( dirname ); + + /* set external lightmap number */ + olm->extLightmapNum = numExtLightmaps; + + /* write lightmap */ + sprintf( filename, "%s/" EXTERNAL_LIGHTMAP, dirname, numExtLightmaps ); + Sys_FPrintf( SYS_VRB, "\nwriting %s", filename ); + WriteTGA24( filename, olm->bspLightBytes, olm->customWidth, olm->customHeight, qtrue ); + numExtLightmaps++; + + /* write deluxemap */ + if( deluxemap ) + { + sprintf( filename, "%s/" EXTERNAL_LIGHTMAP, dirname, numExtLightmaps ); + Sys_FPrintf( SYS_VRB, "\nwriting %s", filename ); + WriteTGA24( filename, olm->bspDirBytes, olm->customWidth, olm->customHeight, qtrue ); + numExtLightmaps++; + + if( debugDeluxemap ) + olm->extLightmapNum++; + } + } + } + + if( numExtLightmaps > 0 ) + Sys_FPrintf( SYS_VRB, "\n" ); + + /* delete unused external lightmaps */ + for( i = numExtLightmaps; i; i++ ) + { + /* determine if file exists */ + sprintf( filename, "%s/" EXTERNAL_LIGHTMAP, dirname, i ); + if( !FileExists( filename ) ) + break; + + /* delete it */ + remove( filename ); + } + + /* ----------------------------------------------------------------- + project the lightmaps onto the bsp surfaces + ----------------------------------------------------------------- */ + + /* note it */ + Sys_FPrintf( SYS_VRB, "projecting..." ); + + /* walk the list of surfaces */ + for( i = 0; i < numBSPDrawSurfaces; i++ ) + { + /* get the surface and info */ + ds = &bspDrawSurfaces[ i ]; + info = &surfaceInfos[ i ]; + lm = info->lm; + olm = NULL; + + /* handle surfaces with identical parent */ + if( info->parentSurfaceNum >= 0 ) + { + /* preserve original data and get parent */ + parent = &bspDrawSurfaces[ info->parentSurfaceNum ]; + memcpy( &dsTemp, ds, sizeof( *ds ) ); + + /* overwrite child with parent data */ + memcpy( ds, parent, sizeof( *ds ) ); + + /* restore key parts */ + ds->fogNum = dsTemp.fogNum; + ds->firstVert = dsTemp.firstVert; + ds->firstIndex = dsTemp.firstIndex; + memcpy( ds->lightmapVecs, dsTemp.lightmapVecs, sizeof( dsTemp.lightmapVecs ) ); + + /* set vertex data */ + dv = &bspDrawVerts[ ds->firstVert ]; + dvParent = &bspDrawVerts[ parent->firstVert ]; + for( j = 0; j < ds->numVerts; j++ ) + { + memcpy( dv[ j ].lightmap, dvParent[ j ].lightmap, sizeof( dv[ j ].lightmap ) ); + memcpy( dv[ j ].color, dvParent[ j ].color, sizeof( dv[ j ].color ) ); + } + + /* skip the rest */ + continue; + } + + /* handle vertex lit or approximated surfaces */ + else if( lm == NULL || lm->outLightmapNums[ 0 ] < 0 ) + { + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + ds->lightmapNum[ lightmapNum ] = -3; + ds->lightmapStyles[ lightmapNum ] = ds->vertexStyles[ lightmapNum ]; + } + } + + /* handle lightmapped surfaces */ + else + { + /* walk lightmaps */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* set style */ + ds->lightmapStyles[ lightmapNum ] = lm->styles[ lightmapNum ]; + + /* handle unused style */ + if( lm->styles[ lightmapNum ] == LS_NONE || lm->outLightmapNums[ lightmapNum ] < 0 ) + { + ds->lightmapNum[ lightmapNum ] = -3; + continue; + } + + /* get output lightmap */ + olm = &outLightmaps[ lm->outLightmapNums[ lightmapNum ] ]; + + /* set bsp lightmap number */ + ds->lightmapNum[ lightmapNum ] = olm->lightmapNum; + + /* deluxemap debugging makes the deluxemap visible */ + if( deluxemap && debugDeluxemap && lightmapNum == 0 ) + ds->lightmapNum[ lightmapNum ]++; + + /* calc lightmap origin in texture space */ + lmx = (float) lm->lightmapX[ lightmapNum ] / (float) olm->customWidth; + lmy = (float) lm->lightmapY[ lightmapNum ] / (float) olm->customHeight; + + /* calc lightmap st coords */ + dv = &bspDrawVerts[ ds->firstVert ]; + ydv = &yDrawVerts[ ds->firstVert ]; + for( j = 0; j < ds->numVerts; j++ ) + { + if( lm->solid[ lightmapNum ] ) + { + dv[ j ].lightmap[ lightmapNum ][ 0 ] = lmx + (0.5f / (float) olm->customWidth); + dv[ j ].lightmap[ lightmapNum ][ 1 ] = lmy + (0.5f / (float) olm->customWidth); + } + else + { + dv[ j ].lightmap[ lightmapNum ][ 0 ] = lmx + (ydv[ j ].lightmap[ 0 ][ 0 ] / (superSample * olm->customWidth)); + dv[ j ].lightmap[ lightmapNum ][ 1 ] = lmy + (ydv[ j ].lightmap[ 0 ][ 1 ] / (superSample * olm->customHeight)); + } + } + } + } + + /* store vertex colors */ + dv = &bspDrawVerts[ ds->firstVert ]; + for( j = 0; j < ds->numVerts; j++ ) + { + /* walk lightmaps */ + for( lightmapNum = 0; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* handle unused style */ + if( ds->vertexStyles[ lightmapNum ] == LS_NONE ) + VectorClear( color ); + else + { + /* get vertex color */ + luxel = VERTEX_LUXEL( lightmapNum, ds->firstVert + j ); + VectorCopy( luxel, color ); + + /* set minimum light */ + if( lightmapNum == 0 ) + { + for( k = 0; k < 3; k++ ) + if( color[ k ] < minVertexLight[ k ] ) + color[ k ] = minVertexLight[ k ]; + } + } + + /* store to bytes */ + if( !info->si->noVertexLight ) + ColorToBytes( color, dv[ j ].color[ lightmapNum ], info->si->vertexScale ); + } + } + + /* surfaces with styled lightmaps and a style marker get a custom generated shader (fixme: make this work with external lightmaps) */ + if( olm != NULL && lm != NULL && lm->styles[ 1 ] != LS_NONE && game->load != LoadRBSPFile ) //% info->si->styleMarker > 0 ) + { + qboolean dfEqual; + char key[ 32 ], styleStage[ 512 ], styleStages[ 4096 ], rgbGen[ 128 ], alphaGen[ 128 ]; + + + /* setup */ + sprintf( styleStages, "\n\t// Q3Map2 custom lightstyle stage(s)\n" ); + dv = &bspDrawVerts[ ds->firstVert ]; + + /* depthFunc equal? */ + if( info->si->styleMarker == 2 || info->si->implicitMap == IM_MASKED ) + dfEqual = qtrue; + else + dfEqual = qfalse; + + /* generate stages for styled lightmaps */ + for( lightmapNum = 1; lightmapNum < MAX_LIGHTMAPS; lightmapNum++ ) + { + /* early out */ + style = lm->styles[ lightmapNum ]; + if( style == LS_NONE || lm->outLightmapNums[ lightmapNum ] < 0 ) + continue; + + /* get output lightmap */ + olm = &outLightmaps[ lm->outLightmapNums[ lightmapNum ] ]; + + /* lightmap name */ + if( lm->outLightmapNums[ lightmapNum ] == lm->outLightmapNums[ 0 ] ) + strcpy( lightmapName, "$lightmap" ); + else + sprintf( lightmapName, "maps/%s/" EXTERNAL_LIGHTMAP, mapName, olm->extLightmapNum ); + + /* get rgbgen string */ + if( rgbGenValues[ style ] == NULL ) + { + sprintf( key, "_style%drgbgen", style ); + rgbGenValues[ style ] = (char*) ValueForKey( &entities[ 0 ], key ); + if( rgbGenValues[ style ][ 0 ] == '\0' ) + rgbGenValues[ style ] = "wave noise 0.5 1 0 5.37"; + } + rgbGen[ 0 ] = '\0'; + if( rgbGenValues[ style ][ 0 ] != '\0' ) + sprintf( rgbGen, "\t\trgbGen %s // style %d\n", rgbGenValues[ style ], style ); + else + rgbGen[ 0 ] = '\0'; + + /* get alphagen string */ + if( alphaGenValues[ style ] == NULL ) + { + sprintf( key, "_style%dalphagen", style ); + alphaGenValues[ style ] = (char*) ValueForKey( &entities[ 0 ], key ); + } + if( alphaGenValues[ style ][ 0 ] != '\0' ) + sprintf( alphaGen, "\t\talphaGen %s // style %d\n", alphaGenValues[ style ], style ); + else + alphaGen[ 0 ] = '\0'; + + /* calculate st offset */ + lmx = dv[ 0 ].lightmap[ lightmapNum ][ 0 ] - dv[ 0 ].lightmap[ 0 ][ 0 ]; + lmy = dv[ 0 ].lightmap[ lightmapNum ][ 1 ] - dv[ 0 ].lightmap[ 0 ][ 1 ]; + + /* create additional stage */ + if( lmx == 0.0f && lmy == 0.0f ) + { + sprintf( styleStage, "\t{\n" + "\t\tmap %s\n" /* lightmap */ + "\t\tblendFunc GL_SRC_ALPHA GL_ONE\n" + "%s" /* depthFunc equal */ + "%s" /* rgbGen */ + "%s" /* alphaGen */ + "\t\ttcGen lightmap\n" + "\t}\n", + lightmapName, + (dfEqual ? "\t\tdepthFunc equal\n" : ""), + rgbGen, + alphaGen ); + } + else + { + sprintf( styleStage, "\t{\n" + "\t\tmap %s\n" /* lightmap */ + "\t\tblendFunc GL_SRC_ALPHA GL_ONE\n" + "%s" /* depthFunc equal */ + "%s" /* rgbGen */ + "%s" /* alphaGen */ + "\t\ttcGen lightmap\n" + "\t\ttcMod transform 1 0 0 1 %1.5f %1.5f\n" /* st offset */ + "\t}\n", + lightmapName, + (dfEqual ? "\t\tdepthFunc equal\n" : ""), + rgbGen, + alphaGen, + lmx, lmy ); + + } + + /* concatenate */ + strcat( styleStages, styleStage ); + } + + /* create custom shader */ + if( info->si->styleMarker == 2 ) + csi = CustomShader( info->si, "q3map_styleMarker2", styleStages ); + else + csi = CustomShader( info->si, "q3map_styleMarker", styleStages ); + + /* emit remap command */ + //% EmitVertexRemapShader( csi->shader, info->si->shader ); + + /* store it */ + //% Sys_Printf( "Emitting: %s (%d", csi->shader, strlen( csi->shader ) ); + ds->shaderNum = EmitShader( csi->shader, &bspShaders[ ds->shaderNum ].contentFlags, &bspShaders[ ds->shaderNum ].surfaceFlags ); + //% Sys_Printf( ")\n" ); + } + + /* devise a custom shader for this surface (fixme: make this work with light styles) */ + else if( olm != NULL && lm != NULL && !externalLightmaps && + (olm->customWidth != game->lightmapSize || olm->customHeight != game->lightmapSize) ) + { + /* get output lightmap */ + olm = &outLightmaps[ lm->outLightmapNums[ 0 ] ]; + + /* do some name mangling */ + sprintf( lightmapName, "maps/%s/" EXTERNAL_LIGHTMAP, mapName, olm->extLightmapNum ); + + /* create custom shader */ + csi = CustomShader( info->si, "$lightmap", lightmapName ); + + /* store it */ + //% Sys_Printf( "Emitting: %s (%d", csi->shader, strlen( csi->shader ) ); + ds->shaderNum = EmitShader( csi->shader, &bspShaders[ ds->shaderNum ].contentFlags, &bspShaders[ ds->shaderNum ].surfaceFlags ); + //% Sys_Printf( ")\n" ); + } + + /* use the normal plain-jane shader */ + else + ds->shaderNum = EmitShader( info->si->shader, &bspShaders[ ds->shaderNum ].contentFlags, &bspShaders[ ds->shaderNum ].surfaceFlags ); + } + + /* finish */ + Sys_FPrintf( SYS_VRB, "done.\n" ); + + /* calc num stored */ + numStored = numBSPLightBytes / 3; + efficiency = (numStored <= 0) + ? 0 + : (float) numUsed / (float) numStored; + + /* print stats */ + Sys_Printf( "%9d luxels used\n", numUsed ); + Sys_Printf( "%9d luxels stored (%3.2f percent efficiency)\n", numStored, efficiency * 100.0f ); + Sys_Printf( "%9d solid surface lightmaps\n", numSolidLightmaps ); + Sys_Printf( "%9d identical surface lightmaps, using %d luxels\n", numTwins, numTwinLuxels ); + Sys_Printf( "%9d vertex forced surfaces\n", numSurfsVertexForced ); + Sys_Printf( "%9d vertex approximated surfaces\n", numSurfsVertexApproximated ); + Sys_Printf( "%9d BSP lightmaps\n", numBSPLightmaps ); + Sys_Printf( "%9d total lightmaps\n", numOutLightmaps ); + Sys_Printf( "%9d unique lightmap/shader combinations\n", numLightmapShaders ); + + /* write map shader file */ + WriteMapShaderFile(); +} + + + + diff --git a/tools/quake3/q3map2/listen.pl b/tools/quake3/q3map2/listen.pl new file mode 100644 index 00000000..6101a767 --- /dev/null +++ b/tools/quake3/q3map2/listen.pl @@ -0,0 +1,46 @@ +#!/usr/bin/perl -w + +use IO::Socket; +use Net::hostent; + +my $port = shift || 13131; + +my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalPort => $port, + Listen => SOMAXCONN, + Reuse => 1 ) + || die "can't setup server"; +print "[Q3Map2 listener $0 is now active on port $port]\n"; + +while( $client = $server->accept() ) +{ + + $client->autoflush( 1 ); + + $hostinfo = gethostbyaddr( $client->peeraddr ); + printf "[Connect from %s]\n\n", $hostinfo ? $hostinfo->name : $client->peerhost; + + #ask the client for a command + print $client "[server]\$"; + my $text = ""; + while( <$client> ) + { + $text .= $_; + while( $text =~ s|]*>([^<]+)|| ) + { + my $msg = $1; + + # fix xml ents + $msg =~ s|<|<|g; + $msg =~ s|>|>|g; + $msg =~ s|"|"|g;#" + $msg =~ s|'|'|g;#' + + print $msg; + } + } + + printf "\n[Disconnected: %s]\n\n", $hostinfo ? $hostinfo->name : $client->peerhost; + close $client; +} diff --git a/tools/quake3/q3map2/main.c b/tools/quake3/q3map2/main.c new file mode 100644 index 00000000..fc4cf104 --- /dev/null +++ b/tools/quake3/q3map2/main.c @@ -0,0 +1,757 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define MAIN_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* +Random() +returns a pseudorandom number between 0 and 1 +*/ + +vec_t Random( void ) +{ + return (vec_t) rand() / RAND_MAX; +} + + + +/* +ExitQ3Map() +cleanup routine +*/ + +static void ExitQ3Map( void ) +{ + BSPFilesCleanup(); + if( mapDrawSurfs != NULL ) + free( mapDrawSurfs ); +} + + + +/* +MD4BlockChecksum() +calculates an md4 checksum for a block of data +*/ + +static int MD4BlockChecksum( void *buffer, int length ) +{ + MHASH mh; + int digest[ 4 ], checksum; + + + /* make md4 hash */ + mh = mhash_init( MHASH_MD4 ); + if( !mh ) + Error( "Unable to initialize MD4 hash context" ); + mhash( mh, buffer, length ); + mhash_deinit( mh, digest ); + + /* xor the bits and return */ + checksum = digest[ 0 ] ^ digest[ 1 ] ^ digest[ 2 ] ^ digest[ 3 ]; + return checksum; +} + + + +/* +FixAAS() +resets an aas checksum to match the given BSP +*/ + +int FixAAS( int argc, char **argv ) +{ + int length, checksum; + void *buffer; + FILE *file; + char aas[ 1024 ], **ext; + char *exts[] = + { + ".aas", + "_b0.aas", + "_b1.aas", + NULL + }; + + + /* arg checking */ + if( argc < 2 ) + { + Sys_Printf( "Usage: q3map -fixaas [-v] \n" ); + return 0; + } + + /* do some path mangling */ + strcpy( source, ExpandArg( argv[ argc - 1 ] ) ); + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + + /* note it */ + Sys_Printf( "--- FixAAS ---\n" ); + + /* load the bsp */ + Sys_Printf( "Loading %s\n", source ); + length = LoadFile( source, &buffer ); + + /* create bsp checksum */ + Sys_Printf( "Creating checksum...\n" ); + checksum = LittleLong( MD4BlockChecksum( buffer, length ) ); + + /* write checksum to aas */ + ext = exts; + while( *ext ) + { + /* mangle name */ + strcpy( aas, source ); + StripExtension( aas ); + strcat( aas, *ext ); + Sys_Printf( "Trying %s\n", aas ); + ext++; + + /* fix it */ + file = fopen( aas, "r+b" ); + if( !file ) + continue; + if( fwrite( &checksum, 4, 1, file ) != 1 ) + Error( "Error writing checksum to %s", aas ); + fclose( file ); + } + + /* return to sender */ + return 0; +} + + + +/* +AnalyzeBSP() - ydnar +analyzes a Quake engine BSP file +*/ + +typedef struct abspHeader_s +{ + char ident[ 4 ]; + int version; + + bspLump_t lumps[ 1 ]; /* unknown size */ +} +abspHeader_t; + +typedef struct abspLumpTest_s +{ + int radix, minCount; + char *name; +} +abspLumpTest_t; + +int AnalyzeBSP( int argc, char **argv ) +{ + abspHeader_t *header; + int size, i, version, offset, length, lumpInt, count; + char ident[ 5 ]; + void *lump; + float lumpFloat; + char lumpString[ 1024 ], source[ 1024 ]; + qboolean lumpSwap = qfalse; + abspLumpTest_t *lumpTest; + static abspLumpTest_t lumpTests[] = + { + { sizeof( bspPlane_t ), 6, "IBSP LUMP_PLANES" }, + { sizeof( bspBrush_t ), 1, "IBSP LUMP_BRUSHES" }, + { 8, 6, "IBSP LUMP_BRUSHSIDES" }, + { sizeof( bspBrushSide_t ), 6, "RBSP LUMP_BRUSHSIDES" }, + { sizeof( bspModel_t ), 1, "IBSP LUMP_MODELS" }, + { sizeof( bspNode_t ), 2, "IBSP LUMP_NODES" }, + { sizeof( bspLeaf_t ), 1, "IBSP LUMP_LEAFS" }, + { 104, 3, "IBSP LUMP_DRAWSURFS" }, + { 44, 3, "IBSP LUMP_DRAWVERTS" }, + { 4, 6, "IBSP LUMP_DRAWINDEXES" }, + { 128 * 128 * 3, 1, "IBSP LUMP_LIGHTMAPS" }, + { 256 * 256 * 3, 1, "IBSP LUMP_LIGHTMAPS (256 x 256)" }, + { 512 * 512 * 3, 1, "IBSP LUMP_LIGHTMAPS (512 x 512)" }, + { 0, 0, NULL } + }; + + + /* arg checking */ + if( argc < 1 ) + { + Sys_Printf( "Usage: q3map -analyze [-lumpswap] [-v] \n" ); + return 0; + } + + /* process arguments */ + for( i = 1; i < (argc - 1); i++ ) + { + /* -format map|ase|... */ + if( !strcmp( argv[ i ], "-lumpswap" ) ) + { + Sys_Printf( "Swapped lump structs enabled\n" ); + lumpSwap = qtrue; + } + } + + /* clean up map name */ + strcpy( source, ExpandArg( argv[ i ] ) ); + Sys_Printf( "Loading %s\n", source ); + + /* load the file */ + size = LoadFile( source, (void**) &header ); + if( size == 0 || header == NULL ) + { + Sys_Printf( "Unable to load %s.\n", source ); + return -1; + } + + /* analyze ident/version */ + memcpy( ident, header->ident, 4 ); + ident[ 4 ] = '\0'; + version = LittleLong( header->version ); + + Sys_Printf( "Identity: %s\n", ident ); + Sys_Printf( "Version: %d\n", version ); + Sys_Printf( "---------------------------------------\n" ); + + /* analyze each lump */ + for( i = 0; i < 100; i++ ) + { + /* call of duty swapped lump pairs */ + if( lumpSwap ) + { + offset = LittleLong( header->lumps[ i ].length ); + length = LittleLong( header->lumps[ i ].offset ); + } + + /* standard lump pairs */ + else + { + offset = LittleLong( header->lumps[ i ].offset ); + length = LittleLong( header->lumps[ i ].length ); + } + + /* extract data */ + lump = (byte*) header + offset; + lumpInt = LittleLong( (int) *((int*) lump) ); + lumpFloat = LittleFloat( (float) *((float*) lump) ); + memcpy( lumpString, (char*) lump, (length < 1024 ? length : 1024) ); + lumpString[ 1024 ] = '\0'; + + /* print basic lump info */ + Sys_Printf( "Lump: %d\n", i ); + Sys_Printf( "Offset: %d bytes\n", offset ); + Sys_Printf( "Length: %d bytes\n", length ); + + /* only operate on valid lumps */ + if( length > 0 ) + { + /* print data in 4 formats */ + Sys_Printf( "As hex: %08X\n", lumpInt ); + Sys_Printf( "As int: %d\n", lumpInt ); + Sys_Printf( "As float: %f\n", lumpFloat ); + Sys_Printf( "As string: %s\n", lumpString ); + + /* guess lump type */ + if( lumpString[ 0 ] == '{' && lumpString[ 2 ] == '"' ) + Sys_Printf( "Type guess: IBSP LUMP_ENTITIES\n" ); + else if( strstr( lumpString, "textures/" ) ) + Sys_Printf( "Type guess: IBSP LUMP_SHADERS\n" ); + else + { + /* guess based on size/count */ + for( lumpTest = lumpTests; lumpTest->radix > 0; lumpTest++ ) + { + if( (length % lumpTest->radix) != 0 ) + continue; + count = length / lumpTest->radix; + if( count < lumpTest->minCount ) + continue; + Sys_Printf( "Type guess: %s (%d x %d)\n", lumpTest->name, count, lumpTest->radix ); + } + } + } + + Sys_Printf( "---------------------------------------\n" ); + + /* end of file */ + if( offset + length >= size ) + break; + } + + /* last stats */ + Sys_Printf( "Lump count: %d\n", i + 1 ); + Sys_Printf( "File size: %d bytes\n", size ); + + /* return to caller */ + return 0; +} + + + +/* +BSPInfo() +emits statistics about the bsp file +*/ + +int BSPInfo( int count, char **fileNames ) +{ + int i; + char source[ 1024 ], ext[ 64 ]; + int size; + FILE *f; + + + /* dummy check */ + if( count < 1 ) + { + Sys_Printf( "No files to dump info for.\n"); + return -1; + } + + /* enable info mode */ + infoMode = qtrue; + + /* walk file list */ + for( i = 0; i < count; i++ ) + { + Sys_Printf( "---------------------------------\n" ); + + /* mangle filename and get size */ + strcpy( source, fileNames[ i ] ); + ExtractFileExtension( source, ext ); + if( !Q_stricmp( ext, "map" ) ) + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + f = fopen( source, "rb" ); + if( f ) + { + size = Q_filelength (f); + fclose( f ); + } + else + size = 0; + + /* load the bsp file and print lump sizes */ + Sys_Printf( "%s\n", source ); + LoadBSPFile( source ); + PrintBSPFileSizes(); + + /* print sizes */ + Sys_Printf( "\n" ); + Sys_Printf( " total %9d\n", size ); + Sys_Printf( " %9d KB\n", size / 1024 ); + Sys_Printf( " %9d MB\n", size / (1024 * 1024) ); + + Sys_Printf( "---------------------------------\n" ); + } + + /* return count */ + return i; +} + + + +/* +ScaleBSPMain() +amaze and confuse your enemies with wierd scaled maps! +*/ + +int ScaleBSPMain( int argc, char **argv ) +{ + int i; + float f, scale; + vec3_t vec; + char str[ 1024 ]; + + + /* arg checking */ + if( argc < 2 ) + { + Sys_Printf( "Usage: q3map -scale [-v] \n" ); + return 0; + } + + /* get scale */ + scale = atof( argv[ argc - 2 ] ); + if( scale == 0.0f ) + { + Sys_Printf( "Usage: q3map -scale [-v] \n" ); + Sys_Printf( "Non-zero scale value required.\n" ); + return 0; + } + + /* do some path mangling */ + strcpy( source, ExpandArg( argv[ argc - 1 ] ) ); + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + + /* load the bsp */ + Sys_Printf( "Loading %s\n", source ); + LoadBSPFile( source ); + ParseEntities(); + + /* note it */ + Sys_Printf( "--- ScaleBSP ---\n" ); + Sys_FPrintf( SYS_VRB, "%9d entities\n", numEntities ); + + /* scale entity keys */ + for( i = 0; i < numBSPEntities && i < numEntities; i++ ) + { + /* scale origin */ + GetVectorForKey( &entities[ i ], "origin", vec ); + if( (vec[ 0 ] + vec[ 1 ] + vec[ 2 ]) ) + { + VectorScale( vec, scale, vec ); + sprintf( str, "%f %f %f", vec[ 0 ], vec[ 1 ], vec[ 2 ] ); + SetKeyValue( &entities[ i ], "origin", str ); + } + + /* scale door lip */ + f = FloatForKey( &entities[ i ], "lip" ); + if( f ) + { + f *= scale; + sprintf( str, "%f", f ); + SetKeyValue( &entities[ i ], "lip", str ); + } + } + + /* scale models */ + for( i = 0; i < numBSPModels; i++ ) + { + VectorScale( bspModels[ i ].mins, scale, bspModels[ i ].mins ); + VectorScale( bspModels[ i ].maxs, scale, bspModels[ i ].maxs ); + } + + /* scale nodes */ + for( i = 0; i < numBSPNodes; i++ ) + { + VectorScale( bspNodes[ i ].mins, scale, bspNodes[ i ].mins ); + VectorScale( bspNodes[ i ].maxs, scale, bspNodes[ i ].maxs ); + } + + /* scale leafs */ + for( i = 0; i < numBSPLeafs; i++ ) + { + VectorScale( bspLeafs[ i ].mins, scale, bspLeafs[ i ].mins ); + VectorScale( bspLeafs[ i ].maxs, scale, bspLeafs[ i ].maxs ); + } + + /* scale drawverts */ + for( i = 0; i < numBSPDrawVerts; i++ ) + VectorScale( bspDrawVerts[ i ].xyz, scale, bspDrawVerts[ i ].xyz ); + + /* scale planes */ + for( i = 0; i < numBSPPlanes; i++ ) + bspPlanes[ i ].dist *= scale; + + /* scale gridsize */ + GetVectorForKey( &entities[ 0 ], "gridsize", vec ); + if( (vec[ 0 ] + vec[ 1 ] + vec[ 2 ]) == 0.0f ) + VectorCopy( gridSize, vec ); + VectorScale( vec, scale, vec ); + sprintf( str, "%f %f %f", vec[ 0 ], vec[ 1 ], vec[ 2 ] ); + SetKeyValue( &entities[ 0 ], "gridsize", str ); + + /* write the bsp */ + UnparseEntities(); + StripExtension( source ); + DefaultExtension( source, "_s.bsp" ); + Sys_Printf( "Writing %s\n", source ); + WriteBSPFile( source ); + + /* return to sender */ + return 0; +} + + + +/* +ConvertBSPMain() +main argument processing function for bsp conversion +*/ + +int ConvertBSPMain( int argc, char **argv ) +{ + int i; + int (*convertFunc)( char * ); + game_t *convertGame; + + + /* set default */ + convertFunc = ConvertBSPToASE; + convertGame = NULL; + + /* arg checking */ + if( argc < 1 ) + { + Sys_Printf( "Usage: q3map -scale [-v] \n" ); + return 0; + } + + /* process arguments */ + for( i = 1; i < (argc - 1); i++ ) + { + /* -format map|ase|... */ + if( !strcmp( argv[ i ], "-format" ) ) + { + i++; + if( !Q_stricmp( argv[ i ], "ase" ) ) + convertFunc = ConvertBSPToASE; + else if( !Q_stricmp( argv[ i ], "map" ) ) + convertFunc = ConvertBSPToMap; + else + { + convertGame = GetGame( argv[ i ] ); + if( convertGame == NULL ) + Sys_Printf( "Unknown conversion format \"%s\". Defaulting to ASE.\n", argv[ i ] ); + } + } + else if( !strcmp( argv[ i ], "-ne" ) ) + { + normalEpsilon = atof( argv[ i + 1 ] ); + i++; + Sys_Printf( "Normal epsilon set to %f\n", normalEpsilon ); + } + else if( !strcmp( argv[ i ], "-de" ) ) + { + distanceEpsilon = atof( argv[ i + 1 ] ); + i++; + Sys_Printf( "Distance epsilon set to %f\n", distanceEpsilon ); + } + } + + /* clean up map name */ + strcpy( source, ExpandArg( argv[ i ] ) ); + StripExtension( source ); + DefaultExtension( source, ".bsp" ); + + LoadShaderInfo(); + + Sys_Printf( "Loading %s\n", source ); + + /* ydnar: load surface file */ + //% LoadSurfaceExtraFile( source ); + + LoadBSPFile( source ); + + /* parse bsp entities */ + ParseEntities(); + + /* bsp format convert? */ + if( convertGame != NULL ) + { + /* set global game */ + game = convertGame; + + /* write bsp */ + StripExtension( source ); + DefaultExtension( source, "_c.bsp" ); + Sys_Printf( "Writing %s\n", source ); + WriteBSPFile( source ); + + /* return to sender */ + return 0; + } + + /* normal convert */ + return convertFunc( source ); +} + + + +/* +main() +q3map mojo... +*/ + +int main( int argc, char **argv ) +{ + int i, r; + double start, end; + + + /* we want consistent 'randomness' */ + srand( 0 ); + + /* start timer */ + start = I_FloatTime(); + + /* this was changed to emit version number over the network */ + printf( Q3MAP_VERSION "\n" ); + + /* set exit call */ + atexit( ExitQ3Map ); + + /* read general options first */ + for( i = 1; i < argc; i++ ) + { + /* -connect */ + if( !strcmp( argv[ i ], "-connect" ) ) + { + argv[ i ] = NULL; + i++; + Broadcast_Setup( argv[ i ] ); + argv[ i ] = NULL; + } + + /* verbose */ + else if( !strcmp( argv[ i ], "-v" ) ) + { + verbose = qtrue; + argv[ i ] = NULL; + } + + /* force */ + else if( !strcmp( argv[ i ], "-force" ) ) + { + force = qtrue; + argv[ i ] = NULL; + } + + /* patch subdivisions */ + else if( !strcmp( argv[ i ], "-subdivisions" ) ) + { + argv[ i ] = NULL; + i++; + patchSubdivisions = atoi( argv[ i ] ); + argv[ i ] = NULL; + if( patchSubdivisions <= 0 ) + patchSubdivisions = 1; + } + + /* threads */ + else if( !strcmp( argv[ i ], "-threads" ) ) + { + argv[ i ] = NULL; + i++; + numthreads = atoi( argv[ i ] ); + argv[ i ] = NULL; + } + } + + /* init model library */ + PicoInit(); + PicoSetMallocFunc( safe_malloc ); + PicoSetFreeFunc( free ); + PicoSetPrintFunc( PicoPrintFunc ); + PicoSetLoadFileFunc( PicoLoadFileFunc ); + PicoSetFreeFileFunc( free ); + + /* set number of threads */ + ThreadSetDefault(); + + /* generate sinusoid jitter table */ + for( i = 0; i < MAX_JITTERS; i++ ) + { + jitters[ i ] = sin( i * 139.54152147 ); + //% Sys_Printf( "Jitter %4d: %f\n", i, jitters[ i ] ); + } + + /* we print out two versions, q3map's main version (since it evolves a bit out of GtkRadiant) + and we put the GtkRadiant version to make it easy to track with what version of Radiant it was built with */ + + Sys_Printf( "Q3Map - v1.0r (c) 1999 Id Software Inc.\n" ); + Sys_Printf( "Q3Map (ydnar) - v" Q3MAP_VERSION "\n" ); + Sys_Printf( "GtkRadiant - v" RADIANT_VERSION " " __DATE__ " " __TIME__ "\n" ); + Sys_Printf( "%s\n", Q3MAP_MOTD ); + + /* ydnar: new path initialization */ + InitPaths( &argc, argv ); + + /* check if we have enough options left to attempt something */ + if( argc < 2 ) + Error( "Usage: %s [general options] [options] mapfile", argv[ 0 ] ); + + /* fixaas */ + if( !strcmp( argv[ 1 ], "-fixaas" ) ) + r = FixAAS( argc - 1, argv + 1 ); + + /* analyze */ + else if( !strcmp( argv[ 1 ], "-analyze" ) ) + r = AnalyzeBSP( argc - 1, argv + 1 ); + + /* info */ + else if( !strcmp( argv[ 1 ], "-info" ) ) + r = BSPInfo( argc - 2, argv + 2 ); + + /* vis */ + else if( !strcmp( argv[ 1 ], "-vis" ) ) + r = VisMain( argc - 1, argv + 1 ); + + /* light */ + else if( !strcmp( argv[ 1 ], "-light" ) ) + r = LightMain( argc - 1, argv + 1 ); + + /* vlight */ + else if( !strcmp( argv[ 1 ], "-vlight" ) ) + { + Sys_Printf( "WARNING: VLight is no longer supported, defaulting to -light -fast instead\n\n" ); + argv[ 1 ] = "-fast"; /* eek a hack */ + r = LightMain( argc, argv ); + } + + /* ydnar: lightmap export */ + else if( !strcmp( argv[ 1 ], "-export" ) ) + r = ExportLightmapsMain( argc - 1, argv + 1 ); + + /* ydnar: lightmap import */ + else if( !strcmp( argv[ 1 ], "-import" ) ) + r = ImportLightmapsMain( argc - 1, argv + 1 ); + + /* ydnar: bsp scaling */ + else if( !strcmp( argv[ 1 ], "-scale" ) ) + r = ScaleBSPMain( argc - 1, argv + 1 ); + + /* ydnar: bsp conversion */ + else if( !strcmp( argv[ 1 ], "-convert" ) ) + r = ConvertBSPMain( argc - 1, argv + 1 ); + + /* ydnar: otherwise create a bsp */ + else + r = BSPMain( argc, argv ); + + /* emit time */ + end = I_FloatTime(); + Sys_Printf( "%9.0f seconds elapsed\n", end - start ); + + /* shut down connection */ + Broadcast_Shutdown(); + + /* return any error code */ + return r; +} diff --git a/tools/quake3/q3map2/map.c b/tools/quake3/q3map2/map.c new file mode 100644 index 00000000..78c77293 --- /dev/null +++ b/tools/quake3/q3map2/map.c @@ -0,0 +1,1690 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define MAP_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* FIXME: remove these vars */ + +/* undefine to make plane finding use linear sort (note: really slow) */ +#define USE_HASHING +#define PLANE_HASHES 8192 + +plane_t *planehash[ PLANE_HASHES ]; + +int c_boxbevels; +int c_edgebevels; +int c_areaportals; +int c_detail; +int c_structural; + + + +/* +PlaneEqual() +ydnar: replaced with variable epsilon for djbob +*/ + +#define NORMAL_EPSILON 0.00001 +#define DIST_EPSILON 0.01 + +qboolean PlaneEqual( plane_t *p, vec3_t normal, vec_t dist ) +{ + float ne, de; + + + /* get local copies */ + ne = normalEpsilon; + de = distanceEpsilon; + + /* compare */ + if( fabs( p->dist - dist ) <= de && + fabs( p->normal[ 0 ] - normal[ 0 ] ) <= ne && + fabs( p->normal[ 1 ] - normal[ 1 ] ) <= ne && + fabs( p->normal[ 2 ] - normal[ 2 ] ) <= ne ) + return qtrue; + + /* different */ + return qfalse; +} + + + +/* +AddPlaneToHash() +*/ + +void AddPlaneToHash( plane_t *p ) +{ + int hash; + + + hash = (PLANE_HASHES - 1) & (int) fabs( p->dist ); + + p->hash_chain = planehash[hash]; + planehash[hash] = p; +} + +/* +================ +CreateNewFloatPlane +================ +*/ +int CreateNewFloatPlane (vec3_t normal, vec_t dist) +{ + plane_t *p, temp; + + if (VectorLength(normal) < 0.5) + { + Sys_Printf( "FloatPlane: bad normal\n"); + return -1; + } + + // create a new plane + if (nummapplanes+2 > MAX_MAP_PLANES) + Error ("MAX_MAP_PLANES"); + + p = &mapplanes[nummapplanes]; + VectorCopy (normal, p->normal); + p->dist = dist; + p->type = (p+1)->type = PlaneTypeForNormal (p->normal); + + VectorSubtract (vec3_origin, normal, (p+1)->normal); + (p+1)->dist = -dist; + + nummapplanes += 2; + + // allways put axial planes facing positive first + if (p->type < 3) + { + if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0) + { + // flip order + temp = *p; + *p = *(p+1); + *(p+1) = temp; + + AddPlaneToHash (p); + AddPlaneToHash (p+1); + return nummapplanes - 1; + } + } + + AddPlaneToHash (p); + AddPlaneToHash (p+1); + return nummapplanes - 2; +} + + + +/* +SnapNormal() +snaps a near-axial normal vector +*/ + +void SnapNormal( vec3_t normal ) +{ + int i; + + for( i = 0; i < 3; i++ ) + { + if( fabs( normal[ i ] - 1 ) < normalEpsilon ) + { + VectorClear( normal ); + normal[ i ] = 1; + break; + } + if( fabs( normal[ i ] - -1 ) < normalEpsilon ) + { + VectorClear( normal ); + normal[ i ] = -1; + break; + } + } +} + + + +/* +SnapPlane() +snaps a plane to normal/distance epsilons +*/ + +void SnapPlane( vec3_t normal, vec_t *dist, vec3_t center ) +{ +// SnapPlane disabled by LordHavoc because it often messes up collision +// brushes made from triangles of embedded models, and it has little effect +// on anything else (axial planes are usually derived from snapped points) +/* + SnapPlane reenabled by namespace because of multiple reports of + q3map2-crashes which were triggered by this patch. +*/ + // div0: ensure the point "center" stays on the plane (actually, this + // rotates the plane around the point center). + // if center lies on the plane, it is guaranteed to stay on the plane by + // this fix. + vec_t centerDist = DotProduct(normal, center); + SnapNormal( normal ); + *dist += (DotProduct(normal, center) - centerDist); + + if( fabs( *dist - Q_rint( *dist ) ) < distanceEpsilon ) + *dist = Q_rint( *dist ); +} + + + +/* +FindFloatPlane() +ydnar: changed to allow a number of test points to be supplied that +must be within an epsilon distance of the plane +*/ + +int FindFloatPlane( vec3_t normal, vec_t dist, int numPoints, vec3_t *points ) // NOTE: this has a side effect on the normal. Good or bad? + +#ifdef USE_HASHING + +{ + int i, j, hash, h; + plane_t *p; + vec_t d; + vec3_t centerofweight; + + VectorClear(centerofweight); + for(i = 0; i < numPoints; ++i) + VectorMA(centerofweight, 1.0 / numPoints, points[i], centerofweight); + + /* hash the plane */ + SnapPlane( normal, &dist, centerofweight ); + hash = (PLANE_HASHES - 1) & (int) fabs( dist ); + + /* search the border bins as well */ + for( i = -1; i <= 1; i++ ) + { + h = (hash + i) & (PLANE_HASHES - 1); + for( p = planehash[ h ]; p != NULL; p = p->hash_chain ) + { + /* do standard plane compare */ + if( !PlaneEqual( p, normal, dist ) ) + continue; + + /* ydnar: uncomment the following line for old-style plane finding */ + //% return p - mapplanes; + + /* ydnar: test supplied points against this plane */ + for( j = 0; j < numPoints; j++ ) + { + d = DotProduct( points[ j ], normal ) - dist; + if( fabs( d ) > distanceEpsilon ) + break; + } + + /* found a matching plane */ + if( j >= numPoints ) + return p - mapplanes; + } + } + + /* none found, so create a new one */ + return CreateNewFloatPlane( normal, dist ); +} + +#else + +{ + int i; + plane_t *p; + + + vec3_t centerofweight; + + VectorClear(centerofweight); + for(i = 0; i < numPoints; ++i) + VectorMA(centerofweight, 1.0 / numPoints, points[i], centerofweight); + + SnapPlane( normal, &dist, centerofweight ); + for( i = 0, p = mapplanes; i < nummapplanes; i++, p++ ) + { + if( PlaneEqual( p, normal, dist ) ) + return i; + } + + return CreateNewFloatPlane( normal, dist ); +} + +#endif + + + +/* +MapPlaneFromPoints() +takes 3 points and finds the plane they lie in +*/ + +int MapPlaneFromPoints( vec3_t *p ) +{ + vec3_t t1, t2, normal; + vec_t dist; + + + /* calc plane normal */ + VectorSubtract( p[ 0 ], p[ 1 ], t1 ); + VectorSubtract( p[ 2 ], p[ 1 ], t2 ); + CrossProduct( t1, t2, normal ); + VectorNormalize( normal, normal ); + + /* calc plane distance */ + dist = DotProduct( p[ 0 ], normal ); + + /* store the plane */ + return FindFloatPlane( normal, dist, 3, p ); +} + + + +/* +SetBrushContents() +the content flags and compile flags on all sides of a brush should be the same +*/ + +void SetBrushContents( brush_t *b ) +{ + int contentFlags, compileFlags; + side_t *s; + int i; + qboolean mixed; + + + /* get initial compile flags from first side */ + s = &b->sides[ 0 ]; + contentFlags = s->contentFlags; + compileFlags = s->compileFlags; + b->contentShader = s->shaderInfo; + mixed = qfalse; + + /* get the content/compile flags for every side in the brush */ + for( i = 1; i < b->numsides; i++, s++ ) + { + s = &b->sides[ i ]; + if( s->shaderInfo == NULL ) + continue; + if( s->contentFlags != contentFlags || s->compileFlags != compileFlags ) + mixed = qtrue; + } + + /* ydnar: getting rid of this stupid warning */ + //% if( mixed ) + //% Sys_FPrintf( SYS_VRB,"Entity %i, Brush %i: mixed face contentFlags\n", b->entitynum, b->brushnum ); + + /* check for detail & structural */ + if( (compileFlags & C_DETAIL) && (compileFlags & C_STRUCTURAL) ) + { + xml_Select( "Mixed detail and structural (defaulting to structural)", mapEnt->mapEntityNum, entitySourceBrushes, qfalse ); + compileFlags &= ~C_DETAIL; + } + + /* the fulldetail flag will cause detail brushes to be treated like normal brushes */ + if( fulldetail ) + compileFlags &= ~C_DETAIL; + + /* all translucent brushes that aren't specifically made structural will be detail */ + if( (compileFlags & C_TRANSLUCENT) && !(compileFlags & C_STRUCTURAL) ) + compileFlags |= C_DETAIL; + + /* detail? */ + if( compileFlags & C_DETAIL ) + { + c_detail++; + b->detail = qtrue; + } + else + { + c_structural++; + b->detail = qfalse; + } + + /* opaque? */ + if( compileFlags & C_TRANSLUCENT ) + b->opaque = qfalse; + else + b->opaque = qtrue; + + /* areaportal? */ + if( compileFlags & C_AREAPORTAL ) + c_areaportals++; + + /* set brush flags */ + b->contentFlags = contentFlags; + b->compileFlags = compileFlags; +} + + + +/* +AddBrushBevels() +adds any additional planes necessary to allow the brush being +built to be expanded against axial bounding boxes +ydnar 2003-01-20: added mrelusive fixes +*/ + +void AddBrushBevels( void ) +{ + int axis, dir; + int i, j, k, l, order; + side_t sidetemp; + side_t *s, *s2; + winding_t *w, *w2; + vec3_t normal; + float dist; + vec3_t vec, vec2; + float d, minBack; + + // + // add the axial planes + // + order = 0; + for ( axis = 0; axis < 3; axis++ ) { + for ( dir = -1; dir <= 1; dir += 2, order++ ) { + // see if the plane is allready present + for ( i = 0, s = buildBrush->sides; i < buildBrush->numsides; i++, s++ ) + { + /* ydnar: testing disabling of mre code */ + #if 0 + if ( dir > 0 ) { + if ( mapplanes[s->planenum].normal[axis] >= 0.9999f ) { + break; + } + } + else { + if ( mapplanes[s->planenum].normal[axis] <= -0.9999f ) { + break; + } + } + #else + if( (dir > 0 && mapplanes[ s->planenum ].normal[ axis ] == 1.0f ) || + (dir < 0 && mapplanes[ s->planenum ].normal[ axis ] == -1.0f) ) + break; + #endif + } + + if ( i == buildBrush->numsides ) { + // add a new side + if ( buildBrush->numsides == MAX_BUILD_SIDES ) { + xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue); + } + memset( s, 0, sizeof( *s ) ); + buildBrush->numsides++; + VectorClear (normal); + normal[axis] = dir; + + if( dir == 1 ) + { + /* ydnar: adding bevel plane snapping for fewer bsp planes */ + if( bevelSnap > 0 ) + dist = floor( buildBrush->maxs[ axis ] / bevelSnap ) * bevelSnap; + else + dist = buildBrush->maxs[ axis ]; + } + else + { + /* ydnar: adding bevel plane snapping for fewer bsp planes */ + if( bevelSnap > 0 ) + dist = -ceil( buildBrush->mins[ axis ] / bevelSnap ) * bevelSnap; + else + dist = -buildBrush->mins[ axis ]; + } + + s->planenum = FindFloatPlane( normal, dist, 0, NULL ); + s->contentFlags = buildBrush->sides[ 0 ].contentFlags; + s->bevel = qtrue; + c_boxbevels++; + } + + // if the plane is not in it canonical order, swap it + if ( i != order ) { + sidetemp = buildBrush->sides[order]; + buildBrush->sides[order] = buildBrush->sides[i]; + buildBrush->sides[i] = sidetemp; + } + } + } + + // + // add the edge bevels + // + if ( buildBrush->numsides == 6 ) { + return; // pure axial + } + + // test the non-axial plane edges + for ( i = 6; i < buildBrush->numsides; i++ ) { + s = buildBrush->sides + i; + w = s->winding; + if ( !w ) { + continue; + } + for ( j = 0; j < w->numpoints; j++) { + k = (j+1)%w->numpoints; + VectorSubtract( w->p[j], w->p[k], vec ); + if ( VectorNormalize( vec, vec ) < 0.5f ) { + continue; + } + SnapNormal( vec ); + for ( k = 0; k < 3; k++ ) { + if ( vec[k] == -1.0f || vec[k] == 1.0f || (vec[k] == 0.0f && vec[(k+1)%3] == 0.0f) ) { + break; // axial + } + } + if ( k != 3 ) { + continue; // only test non-axial edges + } + + /* debug code */ + //% Sys_Printf( "-------------\n" ); + + // try the six possible slanted axials from this edge + for ( axis = 0; axis < 3; axis++ ) { + for ( dir = -1; dir <= 1; dir += 2 ) { + // construct a plane + VectorClear( vec2 ); + vec2[axis] = dir; + CrossProduct( vec, vec2, normal ); + if ( VectorNormalize( normal, normal ) < 0.5f ) { + continue; + } + dist = DotProduct( w->p[j], normal ); + + // if all the points on all the sides are + // behind this plane, it is a proper edge bevel + for ( k = 0; k < buildBrush->numsides; k++ ) { + + // if this plane has allready been used, skip it + if ( PlaneEqual( &mapplanes[buildBrush->sides[k].planenum], normal, dist ) ) { + break; + } + + w2 = buildBrush->sides[k].winding; + if ( !w2 ) { + continue; + } + minBack = 0.0f; + for ( l = 0; l < w2->numpoints; l++ ) { + d = DotProduct( w2->p[l], normal ) - dist; + if ( d > 0.1f ) { + break; // point in front + } + if ( d < minBack ) { + minBack = d; + } + } + // if some point was at the front + if ( l != w2->numpoints ) { + break; + } + + // if no points at the back then the winding is on the bevel plane + if ( minBack > -0.1f ) { + //% Sys_Printf( "On bevel plane\n" ); + break; + } + } + + if ( k != buildBrush->numsides ) { + continue; // wasn't part of the outer hull + } + + /* debug code */ + //% Sys_Printf( "n = %f %f %f\n", normal[ 0 ], normal[ 1 ], normal[ 2 ] ); + + // add this plane + if( buildBrush->numsides == MAX_BUILD_SIDES ) { + xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue); + } + s2 = &buildBrush->sides[buildBrush->numsides]; + buildBrush->numsides++; + memset( s2, 0, sizeof( *s2 ) ); + + s2->planenum = FindFloatPlane( normal, dist, 1, &w->p[ j ] ); + s2->contentFlags = buildBrush->sides[0].contentFlags; + s2->bevel = qtrue; + c_edgebevels++; + } + } + } + } +} + + + +/* +FinishBrush() +produces a final brush based on the buildBrush->sides array +and links it to the current entity +*/ + +brush_t *FinishBrush( void ) +{ + brush_t *b; + + + /* create windings for sides and bounds for brush */ + if ( !CreateBrushWindings( buildBrush ) ) + return NULL; + + /* origin brushes are removed, but they set the rotation origin for the rest of the brushes in the entity. + after the entire entity is parsed, the planenums and texinfos will be adjusted for the origin brush */ + if( buildBrush->compileFlags & C_ORIGIN ) + { + char string[ 32 ]; + vec3_t origin; + + if( numEntities == 1 ) + { + Sys_Printf( "Entity %i, Brush %i: origin brushes not allowed in world\n", + mapEnt->mapEntityNum, entitySourceBrushes ); + return NULL; + } + + VectorAdd (buildBrush->mins, buildBrush->maxs, origin); + VectorScale (origin, 0.5, origin); + + sprintf( string, "%i %i %i", (int) origin[ 0 ], (int) origin[ 1 ], (int) origin[ 2 ] ); + SetKeyValue( &entities[ numEntities - 1 ], "origin", string); + + VectorCopy( origin, entities[ numEntities - 1 ].origin); + + /* don't keep this brush */ + return NULL; + } + + /* determine if the brush is an area portal */ + if( buildBrush->compileFlags & C_AREAPORTAL ) + { + if( numEntities != 1 ) + { + Sys_Printf ("Entity %i, Brush %i: areaportals only allowed in world\n", numEntities - 1, entitySourceBrushes ); + return NULL; + } + } + + /* add bevel planes */ + AddBrushBevels(); + + /* keep it */ + b = CopyBrush( buildBrush ); + + /* set map entity and brush numbering */ + b->entityNum = mapEnt->mapEntityNum; + b->brushNum = entitySourceBrushes; + + /* set original */ + b->original = b; + + /* link opaque brushes to head of list, translucent brushes to end */ + if( b->opaque || mapEnt->lastBrush == NULL ) + { + b->next = mapEnt->brushes; + mapEnt->brushes = b; + if( mapEnt->lastBrush == NULL ) + mapEnt->lastBrush = b; + } + else + { + b->next = NULL; + mapEnt->lastBrush->next = b; + mapEnt->lastBrush = b; + } + + /* link colorMod volume brushes to the entity directly */ + if( b->contentShader != NULL && + b->contentShader->colorMod != NULL && + b->contentShader->colorMod->type == CM_VOLUME ) + { + b->nextColorModBrush = mapEnt->colorModBrushes; + mapEnt->colorModBrushes = b; + } + + /* return to sender */ + return b; +} + + + +/* +TextureAxisFromPlane() +determines best orthagonal axis to project a texture onto a wall +(must be identical in radiant!) +*/ + +vec3_t baseaxis[18] = +{ + {0,0,1}, {1,0,0}, {0,-1,0}, // floor + {0,0,-1}, {1,0,0}, {0,-1,0}, // ceiling + {1,0,0}, {0,1,0}, {0,0,-1}, // west wall + {-1,0,0}, {0,1,0}, {0,0,-1}, // east wall + {0,1,0}, {1,0,0}, {0,0,-1}, // south wall + {0,-1,0}, {1,0,0}, {0,0,-1} // north wall +}; + +void TextureAxisFromPlane( plane_t *pln, vec3_t xv, vec3_t yv ) +{ + int bestaxis; + vec_t dot,best; + int i; + + best = 0; + bestaxis = 0; + + for (i=0 ; i<6 ; i++) + { + dot = DotProduct (pln->normal, baseaxis[i*3]); + if( dot > best + 0.0001f ) /* ydnar: bug 637 fix, suggested by jmonroe */ + { + best = dot; + bestaxis = i; + } + } + + VectorCopy (baseaxis[bestaxis*3+1], xv); + VectorCopy (baseaxis[bestaxis*3+2], yv); +} + + + +/* +QuakeTextureVecs() +creates world-to-texture mapping vecs for crappy quake plane arrangements +*/ + +void QuakeTextureVecs( plane_t *plane, vec_t shift[ 2 ], vec_t rotate, vec_t scale[ 2 ], vec_t mappingVecs[ 2 ][ 4 ] ) +{ + vec3_t vecs[2]; + int sv, tv; + vec_t ang, sinv, cosv; + vec_t ns, nt; + int i, j; + + + TextureAxisFromPlane(plane, vecs[0], vecs[1]); + + if (!scale[0]) + scale[0] = 1; + if (!scale[1]) + scale[1] = 1; + + // rotate axis + if (rotate == 0) + { sinv = 0 ; cosv = 1; } + else if (rotate == 90) + { sinv = 1 ; cosv = 0; } + else if (rotate == 180) + { sinv = 0 ; cosv = -1; } + else if (rotate == 270) + { sinv = -1 ; cosv = 0; } + else + { + ang = rotate / 180 * Q_PI; + sinv = sin(ang); + cosv = cos(ang); + } + + if (vecs[0][0]) + sv = 0; + else if (vecs[0][1]) + sv = 1; + else + sv = 2; + + if (vecs[1][0]) + tv = 0; + else if (vecs[1][1]) + tv = 1; + else + tv = 2; + + for (i=0 ; i<2 ; i++) { + ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; + nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; + vecs[i][sv] = ns; + vecs[i][tv] = nt; + } + + for (i=0 ; i<2 ; i++) + for (j=0 ; j<3 ; j++) + mappingVecs[i][j] = vecs[i][j] / scale[i]; + + mappingVecs[0][3] = shift[0]; + mappingVecs[1][3] = shift[1]; +} + + + +/* +ParseRawBrush() +parses the sides into buildBrush->sides[], nothing else. +no validation, back plane removal, etc. + +Timo - 08/26/99 +added brush epairs parsing ( ignoring actually ) +Timo - 08/04/99 +added exclusive brush primitive parsing +Timo - 08/08/99 +support for old brush format back in +NOTE: it would be "cleaner" to have seperate functions to parse between old and new brushes +*/ + +static void ParseRawBrush( qboolean onlyLights ) +{ + side_t *side; + vec3_t planePoints[ 3 ]; + int planenum; + shaderInfo_t *si; + vec_t shift[ 2 ]; + vec_t rotate; + vec_t scale[ 2 ]; + char name[ MAX_QPATH ]; + char shader[ MAX_QPATH ]; + int flags; + + + /* initial setup */ + buildBrush->numsides = 0; + buildBrush->detail = qfalse; + + /* bp */ + if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) + MatchToken( "{" ); + + /* parse sides */ + while( 1 ) + { + if( !GetToken( qtrue ) ) + break; + if( !strcmp( token, "}" ) ) + break; + + /* ttimo : bp: here we may have to jump over brush epairs (only used in editor) */ + if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) + { + while( 1 ) + { + if( strcmp( token, "(" ) ) + GetToken( qfalse ); + else + break; + GetToken( qtrue ); + } + } + UnGetToken(); + + /* test side count */ + if( buildBrush->numsides >= MAX_BUILD_SIDES ) + xml_Select( "MAX_BUILD_SIDES", buildBrush->entityNum, buildBrush->brushNum, qtrue ); + + /* add side */ + side = &buildBrush->sides[ buildBrush->numsides ]; + memset( side, 0, sizeof( *side ) ); + buildBrush->numsides++; + + /* read the three point plane definition */ + Parse1DMatrix( 3, planePoints[ 0 ] ); + Parse1DMatrix( 3, planePoints[ 1 ] ); + Parse1DMatrix( 3, planePoints[ 2 ] ); + + /* bp: read the texture matrix */ + if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) + Parse2DMatrix( 2, 3, (float*) side->texMat ); + + /* read shader name */ + GetToken( qfalse ); + strcpy( name, token ); + + /* bp */ + if( g_bBrushPrimit == BPRIMIT_OLDBRUSHES ) + { + GetToken( qfalse ); + shift[ 0 ] = atof( token ); + GetToken( qfalse ); + shift[ 1 ] = atof( token ); + GetToken( qfalse ); + rotate = atof( token ); + GetToken( qfalse ); + scale[ 0 ] = atof( token ); + GetToken( qfalse ); + scale[ 1 ] = atof( token ); + } + + /* set default flags and values */ + sprintf( shader, "textures/%s", name ); + if( onlyLights ) + si = &shaderInfo[ 0 ]; + else + si = ShaderInfoForShader( shader ); + side->shaderInfo = si; + side->surfaceFlags = si->surfaceFlags; + side->contentFlags = si->contentFlags; + side->compileFlags = si->compileFlags; + side->value = si->value; + + /* ydnar: gs mods: bias texture shift */ + if( si->globalTexture == qfalse ) + { + shift[ 0 ] -= (floor( shift[ 0 ] / si->shaderWidth ) * si->shaderWidth); + shift[ 1 ] -= (floor( shift[ 1 ] / si->shaderHeight ) * si->shaderHeight); + } + + /* + historically, there are 3 integer values at the end of a brushside line in a .map file. + in quake 3, the only thing that mattered was the first of these three values, which + was previously the content flags. and only then did a single bit matter, the detail + bit. because every game has its own special flags for specifying detail, the + traditionally game-specified CONTENTS_DETAIL flag was overridden for Q3Map 2.3.0 + by C_DETAIL, defined in q3map2.h. the value is exactly as it was before, but + is stored in compileFlags, as opposed to contentFlags, for multiple-game + portability. :sigh: + */ + + if( TokenAvailable() ) + { + /* get detail bit from map content flags */ + GetToken( qfalse ); + flags = atoi( token ); + if( flags & C_DETAIL ) + side->compileFlags |= C_DETAIL; + + /* historical */ + GetToken( qfalse ); + //% td.flags = atoi( token ); + GetToken( qfalse ); + //% td.value = atoi( token ); + } + + /* find the plane number */ + planenum = MapPlaneFromPoints( planePoints ); + side->planenum = planenum; + + /* bp: get the texture mapping for this texturedef / plane combination */ + if( g_bBrushPrimit == BPRIMIT_OLDBRUSHES ) + QuakeTextureVecs( &mapplanes[ planenum ], shift, rotate, scale, side->vecs ); + } + + /* bp */ + if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) + { + UnGetToken(); + MatchToken( "}" ); + MatchToken( "}" ); + } +} + + + +/* +RemoveDuplicateBrushPlanes +returns false if the brush has a mirrored set of planes, +meaning it encloses no volume. +also removes planes without any normal +*/ + +qboolean RemoveDuplicateBrushPlanes( brush_t *b ) +{ + int i, j, k; + side_t *sides; + + sides = b->sides; + + for ( i = 1 ; i < b->numsides ; i++ ) { + + // check for a degenerate plane + if ( sides[i].planenum == -1) { + xml_Select( "degenerate plane", b->entityNum, b->brushNum, qfalse ); + // remove it + for ( k = i + 1 ; k < b->numsides ; k++ ) { + sides[k-1] = sides[k]; + } + b->numsides--; + i--; + continue; + } + + // check for duplication and mirroring + for ( j = 0 ; j < i ; j++ ) { + if ( sides[i].planenum == sides[j].planenum ) { + xml_Select( "duplicate plane", b->entityNum, b->brushNum, qfalse ); + // remove the second duplicate + for ( k = i + 1 ; k < b->numsides ; k++ ) { + sides[k-1] = sides[k]; + } + b->numsides--; + i--; + break; + } + + if ( sides[i].planenum == (sides[j].planenum ^ 1) ) { + // mirror plane, brush is invalid + xml_Select( "mirrored plane", b->entityNum, b->brushNum, qfalse ); + return qfalse; + } + } + } + return qtrue; +} + + + +/* +ParseBrush() +parses a brush out of a map file and sets it up +*/ + +static void ParseBrush( qboolean onlyLights ) +{ + brush_t *b; + + + /* parse the brush out of the map */ + ParseRawBrush( onlyLights ); + + /* only go this far? */ + if( onlyLights ) + return; + + /* set some defaults */ + buildBrush->portalareas[ 0 ] = -1; + buildBrush->portalareas[ 1 ] = -1; + buildBrush->entityNum = numMapEntities - 1; + buildBrush->brushNum = entitySourceBrushes; + + /* if there are mirrored planes, the entire brush is invalid */ + if( !RemoveDuplicateBrushPlanes( buildBrush ) ) + return; + + /* get the content for the entire brush */ + SetBrushContents( buildBrush ); + + /* allow detail brushes to be removed */ + if( nodetail && (buildBrush->compileFlags & C_DETAIL) ) + { + //% FreeBrush( buildBrush ); + return; + } + + /* allow liquid brushes to be removed */ + if( nowater && (buildBrush->compileFlags & C_LIQUID ) ) + { + //% FreeBrush( buildBrush ); + return; + } + + /* ydnar: allow hint brushes to be removed */ + if( noHint && (buildBrush->compileFlags & C_HINT) ) + { + //% FreeBrush( buildBrush ); + return; + } + + /* finish the brush */ + b = FinishBrush(); +} + + + +/* +MoveBrushesToWorld() +takes all of the brushes from the current entity and +adds them to the world's brush list +(used by func_group) +*/ + +void MoveBrushesToWorld( entity_t *ent ) +{ + brush_t *b, *next; + parseMesh_t *pm; + + + /* move brushes */ + for( b = ent->brushes; b != NULL; b = next ) + { + /* get next brush */ + next = b->next; + + /* link opaque brushes to head of list, translucent brushes to end */ + if( b->opaque || entities[ 0 ].lastBrush == NULL ) + { + b->next = entities[ 0 ].brushes; + entities[ 0 ].brushes = b; + if( entities[ 0 ].lastBrush == NULL ) + entities[ 0 ].lastBrush = b; + } + else + { + b->next = NULL; + entities[ 0 ].lastBrush->next = b; + entities[ 0 ].lastBrush = b; + } + } + ent->brushes = NULL; + + /* ydnar: move colormod brushes */ + if( ent->colorModBrushes != NULL ) + { + for( b = ent->colorModBrushes; b->nextColorModBrush != NULL; b = b->nextColorModBrush ); + + b->nextColorModBrush = entities[ 0 ].colorModBrushes; + entities[ 0 ].colorModBrushes = ent->colorModBrushes; + + ent->colorModBrushes = NULL; + } + + /* move patches */ + if( ent->patches != NULL ) + { + for( pm = ent->patches; pm->next != NULL; pm = pm->next ); + + pm->next = entities[ 0 ].patches; + entities[ 0 ].patches = ent->patches; + + ent->patches = NULL; + } +} + + + +/* +AdjustBrushesForOrigin() +*/ + +void AdjustBrushesForOrigin( entity_t *ent ) +{ + + int i; + side_t *s; + vec_t newdist; + brush_t *b; + parseMesh_t *p; + + + /* walk brush list */ + for( b = ent->brushes; b != NULL; b = b->next ) + { + /* offset brush planes */ + for( i = 0; i < b->numsides; i++) + { + /* get brush side */ + s = &b->sides[ i ]; + + /* offset side plane */ + newdist = mapplanes[ s->planenum ].dist - DotProduct( mapplanes[ s->planenum ].normal, ent->origin ); + + /* find a new plane */ + s->planenum = FindFloatPlane( mapplanes[ s->planenum ].normal, newdist, 0, NULL ); + } + + /* rebuild brush windings (ydnar: just offsetting the winding above should be fine) */ + CreateBrushWindings( b ); + } + + /* walk patch list */ + for( p = ent->patches; p != NULL; p = p->next ) + { + for( i = 0; i < (p->mesh.width * p->mesh.height); i++ ) + VectorSubtract( p->mesh.verts[ i ].xyz, ent->origin, p->mesh.verts[ i ].xyz ); + } +} + + + +/* +SetEntityBounds() - ydnar +finds the bounds of an entity's brushes (necessary for terrain-style generic metashaders) +*/ + +void SetEntityBounds( entity_t *e ) +{ + int i; + brush_t *b; + parseMesh_t *p; + vec3_t mins, maxs; + const char *value; + + + + + /* walk the entity's brushes/patches and determine bounds */ + ClearBounds( mins, maxs ); + for( b = e->brushes; b; b = b->next ) + { + AddPointToBounds( b->mins, mins, maxs ); + AddPointToBounds( b->maxs, mins, maxs ); + } + for( p = e->patches; p; p = p->next ) + { + for( i = 0; i < (p->mesh.width * p->mesh.height); i++ ) + AddPointToBounds( p->mesh.verts[ i ].xyz, mins, maxs ); + } + + /* try to find explicit min/max key */ + value = ValueForKey( e, "min" ); + if( value[ 0 ] != '\0' ) + GetVectorForKey( e, "min", mins ); + value = ValueForKey( e, "max" ); + if( value[ 0 ] != '\0' ) + GetVectorForKey( e, "max", maxs ); + + /* store the bounds */ + for( b = e->brushes; b; b = b->next ) + { + VectorCopy( mins, b->eMins ); + VectorCopy( maxs, b->eMaxs ); + } + for( p = e->patches; p; p = p->next ) + { + VectorCopy( mins, p->eMins ); + VectorCopy( maxs, p->eMaxs ); + } +} + + + +/* +LoadEntityIndexMap() - ydnar +based on LoadAlphaMap() from terrain.c, a little more generic +*/ + +void LoadEntityIndexMap( entity_t *e ) +{ + int i, size, numLayers, w, h; + const char *value, *indexMapFilename, *shader; + char ext[ MAX_QPATH ], offset[ 4096 ], *search, *space; + byte *pixels; + unsigned int *pixels32; + indexMap_t *im; + brush_t *b; + parseMesh_t *p; + + + /* this only works with bmodel ents */ + if( e->brushes == NULL && e->patches == NULL ) + return; + + /* determine if there is an index map (support legacy "alphamap" key as well) */ + value = ValueForKey( e, "_indexmap" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( e, "alphamap" ); + if( value[ 0 ] == '\0' ) + return; + indexMapFilename = value; + + /* get number of layers (support legacy "layers" key as well) */ + value = ValueForKey( e, "_layers" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( e, "layers" ); + if( value[ 0 ] == '\0' ) + { + Sys_Printf( "WARNING: Entity with index/alpha map \"%s\" has missing \"_layers\" or \"layers\" key\n", indexMapFilename ); + Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" ); + return; + } + numLayers = atoi( value ); + if( numLayers < 1 ) + { + Sys_Printf( "WARNING: Entity with index/alpha map \"%s\" has < 1 layer (%d)\n", indexMapFilename, numLayers ); + Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" ); + return; + } + + /* get base shader name (support legacy "shader" key as well) */ + value = ValueForKey( mapEnt, "_shader" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( e, "shader" ); + if( value[ 0 ] == '\0' ) + { + Sys_Printf( "WARNING: Entity with index/alpha map \"%s\" has missing \"_shader\" or \"shader\" key\n", indexMapFilename ); + Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" ); + return; + } + shader = value; + + /* note it */ + Sys_FPrintf( SYS_VRB, "Entity %d (%s) has shader index map \"%s\"\n", mapEnt->mapEntityNum, ValueForKey( e, "classname" ), indexMapFilename ); + + /* get index map file extension */ + ExtractFileExtension( indexMapFilename, ext ); + + /* handle tga image */ + if( !Q_stricmp( ext, "tga" ) ) + { + /* load it */ + Load32BitImage( indexMapFilename, &pixels32, &w, &h ); + + /* convert to bytes */ + size = w * h; + pixels = safe_malloc( size ); + for( i = 0; i < size; i++ ) + { + pixels[ i ] = ((pixels32[ i ] & 0xFF) * numLayers) / 256; + if( pixels[ i ] >= numLayers ) + pixels[ i ] = numLayers - 1; + } + + /* free the 32 bit image */ + free( pixels32 ); + } + else + { + /* load it */ + Load256Image( indexMapFilename, &pixels, NULL, &w, &h ); + + /* debug code */ + //% Sys_Printf( "-------------------------------" ); + + /* fix up out-of-range values */ + size = w * h; + for( i = 0; i < size; i++ ) + { + if( pixels[ i ] >= numLayers ) + pixels[ i ] = numLayers - 1; + + /* debug code */ + //% if( (i % w) == 0 ) + //% Sys_Printf( "\n" ); + //% Sys_Printf( "%c", pixels[ i ] + '0' ); + } + + /* debug code */ + //% Sys_Printf( "\n-------------------------------\n" ); + } + + /* the index map must be at least 2x2 pixels */ + if( w < 2 || h < 2 ) + { + Sys_Printf( "WARNING: Entity with index/alpha map \"%s\" is smaller than 2x2 pixels\n", indexMapFilename ); + Sys_Printf( "Entity will not be textured properly. Check your keys/values.\n" ); + free( pixels ); + return; + } + + /* create a new index map */ + im = safe_malloc( sizeof( *im ) ); + memset( im, 0, sizeof( *im ) ); + + /* set it up */ + im->w = w; + im->h = h; + im->numLayers = numLayers; + strcpy( im->name, indexMapFilename ); + strcpy( im->shader, shader ); + im->pixels = pixels; + + /* get height offsets */ + value = ValueForKey( mapEnt, "_offsets" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( e, "offsets" ); + if( value[ 0 ] != '\0' ) + { + /* value is a space-seperated set of numbers */ + strcpy( offset, value ); + search = offset; + + /* get each value */ + for( i = 0; i < 256 && *search != '\0'; i++ ) + { + space = strstr( search, " " ); + if( space != NULL ) + *space = '\0'; + im->offsets[ i ] = atof( search ); + if( space == NULL ) + break; + search = space + 1; + } + } + + /* store the index map in every brush/patch in the entity */ + for( b = e->brushes; b != NULL; b = b->next ) + b->im = im; + for( p = e->patches; p != NULL; p = p->next ) + p->im = im; +} + + + + + + + +/* +ParseMapEntity() +parses a single entity out of a map file +*/ + +static qboolean ParseMapEntity( qboolean onlyLights ) +{ + epair_t *ep; + const char *classname, *value; + float lightmapScale; + char shader[ MAX_QPATH ]; + shaderInfo_t *celShader = NULL; + brush_t *brush; + parseMesh_t *patch; + qboolean funcGroup; + int castShadows, recvShadows; + + + /* eof check */ + if( !GetToken( qtrue ) ) + return qfalse; + + /* conformance check */ + if( strcmp( token, "{" ) ) + { + Sys_Printf( "WARNING: ParseEntity: { not found, found %s on line %d - last entity was at: <%4.2f, %4.2f, %4.2f>...\n" + "Continuing to process map, but resulting BSP may be invalid.\n", + token, scriptline, entities[ numEntities ].origin[ 0 ], entities[ numEntities ].origin[ 1 ], entities[ numEntities ].origin[ 2 ] ); + return qfalse; + } + + /* range check */ + if( numEntities >= MAX_MAP_ENTITIES ) + Error( "numEntities == MAX_MAP_ENTITIES" ); + + /* setup */ + entitySourceBrushes = 0; + mapEnt = &entities[ numEntities ]; + numEntities++; + memset( mapEnt, 0, sizeof( *mapEnt ) ); + + /* ydnar: true entity numbering */ + mapEnt->mapEntityNum = numMapEntities; + numMapEntities++; + + /* loop */ + while( 1 ) + { + /* get initial token */ + if( !GetToken( qtrue ) ) + { + Sys_Printf( "WARNING: ParseEntity: EOF without closing brace\n" + "Continuing to process map, but resulting BSP may be invalid.\n" ); + return qfalse; + } + + if( !strcmp( token, "}" ) ) + break; + + if( !strcmp( token, "{" ) ) + { + /* parse a brush or patch */ + if( !GetToken( qtrue ) ) + break; + + /* check */ + if( !strcmp( token, "patchDef2" ) ) + { + numMapPatches++; + ParsePatch( onlyLights ); + } + else if( !strcmp( token, "terrainDef" ) ) + { + //% ParseTerrain(); + Sys_Printf( "WARNING: Terrain entity parsing not supported in this build.\n" ); /* ydnar */ + } + else if( !strcmp( token, "brushDef" ) ) + { + if( g_bBrushPrimit == BPRIMIT_OLDBRUSHES ) + Error( "Old brush format not allowed in new brush format map" ); + g_bBrushPrimit = BPRIMIT_NEWBRUSHES; + + /* parse brush primitive */ + ParseBrush( onlyLights ); + } + else + { + if( g_bBrushPrimit == BPRIMIT_NEWBRUSHES ) + Error( "New brush format not allowed in old brush format map" ); + g_bBrushPrimit = BPRIMIT_OLDBRUSHES; + + /* parse old brush format */ + UnGetToken(); + ParseBrush( onlyLights ); + } + entitySourceBrushes++; + } + else + { + /* parse a key / value pair */ + ep = ParseEPair(); + + /* ydnar: 2002-07-06 fixed wolf bug with empty epairs */ + if( ep->key[ 0 ] != '\0' && ep->value[ 0 ] != '\0' ) + { + ep->next = mapEnt->epairs; + mapEnt->epairs = ep; + } + } + } + + /* ydnar: get classname */ + classname = ValueForKey( mapEnt, "classname" ); + + /* ydnar: only lights? */ + if( onlyLights && Q_strncasecmp( classname, "light", 5 ) ) + { + numEntities--; + return qtrue; + } + + /* ydnar: determine if this is a func_group */ + if( !Q_stricmp( "func_group", classname ) ) + funcGroup = qtrue; + else + funcGroup = qfalse; + + /* worldspawn (and func_groups) default to cast/recv shadows in worldspawn group */ + if( funcGroup || mapEnt->mapEntityNum == 0 ) + { + //% Sys_Printf( "World: %d\n", mapEnt->mapEntityNum ); + castShadows = WORLDSPAWN_CAST_SHADOWS; + recvShadows = WORLDSPAWN_RECV_SHADOWS; + } + + /* other entities don't cast any shadows, but recv worldspawn shadows */ + else + { + //% Sys_Printf( "Entity: %d\n", mapEnt->mapEntityNum ); + castShadows = ENTITY_CAST_SHADOWS; + recvShadows = ENTITY_RECV_SHADOWS; + } + + /* get explicit shadow flags */ + GetEntityShadowFlags( mapEnt, NULL, &castShadows, &recvShadows ); + + /* ydnar: get lightmap scaling value for this entity */ + if( strcmp( "", ValueForKey( mapEnt, "lightmapscale" ) ) || + strcmp( "", ValueForKey( mapEnt, "_lightmapscale" ) ) ) + { + /* get lightmap scale from entity */ + lightmapScale = FloatForKey( mapEnt, "lightmapscale" ); + if( lightmapScale <= 0.0f ) + lightmapScale = FloatForKey( mapEnt, "_lightmapscale" ); + if( lightmapScale > 0.0f ) + Sys_Printf( "Entity %d (%s) has lightmap scale of %.4f\n", mapEnt->mapEntityNum, classname, lightmapScale ); + } + else + lightmapScale = 0.0f; + + /* ydnar: get cel shader :) for this entity */ + value = ValueForKey( mapEnt, "_celshader" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( &entities[ 0 ], "_celshader" ); + if( value[ 0 ] != '\0' ) + { + sprintf( shader, "textures/%s", value ); + celShader = ShaderInfoForShader( shader ); + Sys_Printf( "Entity %d (%s) has cel shader %s\n", mapEnt->mapEntityNum, classname, celShader->shader ); + } + else + celShader = NULL; + + /* attach stuff to everything in the entity */ + for( brush = mapEnt->brushes; brush != NULL; brush = brush->next ) + { + brush->entityNum = mapEnt->mapEntityNum; + brush->castShadows = castShadows; + brush->recvShadows = recvShadows; + brush->lightmapScale = lightmapScale; + brush->celShader = celShader; + } + + for( patch = mapEnt->patches; patch != NULL; patch = patch->next ) + { + patch->entityNum = mapEnt->mapEntityNum; + patch->castShadows = castShadows; + patch->recvShadows = recvShadows; + patch->lightmapScale = lightmapScale; + patch->celShader = celShader; + } + + /* ydnar: gs mods: set entity bounds */ + SetEntityBounds( mapEnt ); + + /* ydnar: gs mods: load shader index map (equivalent to old terrain alphamap) */ + LoadEntityIndexMap( mapEnt ); + + /* get entity origin and adjust brushes */ + GetVectorForKey( mapEnt, "origin", mapEnt->origin ); + if( mapEnt->origin[ 0 ] || mapEnt->origin[ 1 ] || mapEnt->origin[ 2 ] ) + AdjustBrushesForOrigin( mapEnt ); + + /* group_info entities are just for editor grouping (fixme: leak!) */ + if( !Q_stricmp( "group_info", classname ) ) + { + numEntities--; + return qtrue; + } + + /* group entities are just for editor convenience, toss all brushes into worldspawn */ + if( funcGroup ) + { + MoveBrushesToWorld( mapEnt ); + numEntities--; + return qtrue; + } + + /* done */ + return qtrue; +} + + + +/* +LoadMapFile() +loads a map file into a list of entities +*/ + +void LoadMapFile( char *filename, qboolean onlyLights ) +{ + FILE *file; + brush_t *b; + int oldNumEntities, numMapBrushes; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- LoadMapFile ---\n" ); + Sys_Printf( "Loading %s\n", filename ); + + /* hack */ + file = SafeOpenRead( filename ); + fclose( file ); + + /* load the map file */ + LoadScriptFile( filename, -1 ); + + /* setup */ + if( onlyLights ) + oldNumEntities = numEntities; + else + numEntities = 0; + + /* initial setup */ + numMapDrawSurfs = 0; + c_detail = 0; + g_bBrushPrimit = BPRIMIT_UNDEFINED; + + /* allocate a very large temporary brush for building the brushes as they are loaded */ + buildBrush = AllocBrush( MAX_BUILD_SIDES ); + + /* parse the map file */ + while( ParseMapEntity( onlyLights ) ); + + /* light loading */ + if( onlyLights ) + { + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d light entities\n", numEntities - oldNumEntities ); + } + else + { + /* set map bounds */ + ClearBounds( mapMins, mapMaxs ); + for( b = entities[ 0 ].brushes; b; b = b->next ) + { + AddPointToBounds( b->mins, mapMins, mapMaxs ); + AddPointToBounds( b->maxs, mapMins, mapMaxs ); + } + + /* get brush counts */ + numMapBrushes = CountBrushList( entities[ 0 ].brushes ); + if( (float) c_detail / (float) numMapBrushes < 0.10f && numMapBrushes > 500 ) + Sys_Printf( "WARNING: Over 90 percent structural map detected. Compile time may be adversely affected.\n" ); + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d total world brushes\n", numMapBrushes ); + Sys_FPrintf( SYS_VRB, "%9d detail brushes\n", c_detail ); + Sys_FPrintf( SYS_VRB, "%9d patches\n", numMapPatches); + Sys_FPrintf( SYS_VRB, "%9d boxbevels\n", c_boxbevels); + Sys_FPrintf( SYS_VRB, "%9d edgebevels\n", c_edgebevels); + Sys_FPrintf( SYS_VRB, "%9d entities\n", numEntities ); + Sys_FPrintf( SYS_VRB, "%9d planes\n", nummapplanes); + Sys_Printf( "%9d areaportals\n", c_areaportals); + Sys_Printf( "Size: %5.0f, %5.0f, %5.0f to %5.0f, %5.0f, %5.0f\n", + mapMins[ 0 ], mapMins[ 1 ], mapMins[ 2 ], + mapMaxs[ 0 ], mapMaxs[ 1 ], mapMaxs[ 2 ]); + + /* write bogus map */ + if( fakemap ) + WriteBSPBrushMap( "fakemap.map", entities[ 0 ].brushes ); + } +} diff --git a/tools/quake3/q3map2/mesh.c b/tools/quake3/q3map2/mesh.c new file mode 100644 index 00000000..288525cf --- /dev/null +++ b/tools/quake3/q3map2/mesh.c @@ -0,0 +1,826 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define MESH_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* +LerpDrawVert() +returns an 50/50 interpolated vert +*/ + +void LerpDrawVert( bspDrawVert_t *a, bspDrawVert_t *b, bspDrawVert_t *out ) +{ + int k; + + + out->xyz[ 0 ] = 0.5 * (a->xyz[ 0 ] + b->xyz[ 0 ]); + out->xyz[ 1 ] = 0.5 * (a->xyz[ 1 ] + b->xyz[ 1 ]); + out->xyz[ 2 ] = 0.5 * (a->xyz[ 2 ] + b->xyz[ 2 ]); + + out->st[ 0 ] = 0.5 * (a->st[ 0 ] + b->st[ 0 ]); + out->st[ 1 ] = 0.5 * (a->st[ 1 ] + b->st[ 1 ]); + + for( k = 0; k < MAX_LIGHTMAPS; k++ ) + { + out->lightmap[ k ][ 0 ] = 0.5f * (a->lightmap[ k ][ 0 ] + b->lightmap[ k ][ 0 ]); + out->lightmap[ k ][ 1 ] = 0.5f * (a->lightmap[ k ][ 1 ] + b->lightmap[ k ][ 1 ]); + out->color[ k ][ 0 ] = (a->color[ k ][ 0 ] + b->color[ k ][ 0 ]) >> 1; + out->color[ k ][ 1 ] = (a->color[ k ][ 1 ] + b->color[ k ][ 1 ]) >> 1; + out->color[ k ][ 2 ] = (a->color[ k ][ 2 ] + b->color[ k ][ 2 ]) >> 1; + out->color[ k ][ 3 ] = (a->color[ k ][ 3 ] + b->color[ k ][ 3 ]) >> 1; + } + + /* ydnar: added normal interpolation */ + out->normal[ 0 ] = 0.5f * (a->normal[ 0 ] + b->normal[ 0 ]); + out->normal[ 1 ] = 0.5f * (a->normal[ 1 ] + b->normal[ 1 ]); + out->normal[ 2 ] = 0.5f * (a->normal[ 2 ] + b->normal[ 2 ]); + + /* if the interpolant created a bogus normal, just copy the normal from a */ + if( VectorNormalize( out->normal, out->normal ) == 0 ) + VectorCopy( a->normal, out->normal ); +} + + + +/* +LerpDrawVertAmount() +returns a biased interpolated vert +*/ + +void LerpDrawVertAmount( bspDrawVert_t *a, bspDrawVert_t *b, float amount, bspDrawVert_t *out ) +{ + int k; + + + out->xyz[ 0 ] = a->xyz[ 0 ] + amount * (b->xyz[ 0 ] - a->xyz[ 0 ]); + out->xyz[ 1 ] = a->xyz[ 1 ] + amount * (b->xyz[ 1 ] - a->xyz[ 1 ]); + out->xyz[ 2 ] = a->xyz[ 2 ] + amount * (b->xyz[ 2 ] - a->xyz[ 2 ]); + + out->st[ 0 ] = a->st[ 0 ] + amount * (b->st[ 0 ] - a->st[ 0 ]); + out->st[ 1 ] = a->st[ 1 ] + amount * (b->st[ 1 ] - a->st[ 1 ]); + + for( k = 0; k < MAX_LIGHTMAPS; k++ ) + { + out->lightmap[ k ][ 0 ] = a->lightmap[ k ][ 0 ] + amount * (b->lightmap[ k ][ 0 ] - a->lightmap[ k ][ 0 ]); + out->lightmap[ k ][ 1 ] = a->lightmap[ k ][ 1 ] + amount * (b->lightmap[ k ][ 1 ] - a->lightmap[ k ][ 1 ]); + out->color[ k ][ 0 ] = a->color[ k ][ 0 ] + amount * (b->color[ k ][ 0 ] - a->color[ k ][ 0 ]); + out->color[ k ][ 1 ] = a->color[ k ][ 1 ] + amount * (b->color[ k ][ 1 ] - a->color[ k ][ 1 ]); + out->color[ k ][ 2 ] = a->color[ k ][ 2 ] + amount * (b->color[ k ][ 2 ] - a->color[ k ][ 2 ]); + out->color[ k ][ 3 ] = a->color[ k ][ 3 ] + amount * (b->color[ k ][ 3 ] - a->color[ k ][ 3 ]); + } + + out->normal[ 0 ] = a->normal[ 0 ] + amount * (b->normal[ 0 ] - a->normal[ 0 ]); + out->normal[ 1 ] = a->normal[ 1 ] + amount * (b->normal[ 1 ] - a->normal[ 1 ]); + out->normal[ 2 ] = a->normal[ 2 ] + amount * (b->normal[ 2 ] - a->normal[ 2 ]); + + /* if the interpolant created a bogus normal, just copy the normal from a */ + if( VectorNormalize( out->normal, out->normal ) == 0 ) + VectorCopy( a->normal, out->normal ); +} + + +void FreeMesh( mesh_t *m ) { + free( m->verts ); + free( m ); +} + +void PrintMesh( mesh_t *m ) { + int i, j; + + for ( i = 0 ; i < m->height ; i++ ) { + for ( j = 0 ; j < m->width ; j++ ) { + Sys_Printf("(%5.2f %5.2f %5.2f) " + , m->verts[i*m->width+j].xyz[0] + , m->verts[i*m->width+j].xyz[1] + , m->verts[i*m->width+j].xyz[2] ); + } + Sys_Printf("\n"); + } +} + + +mesh_t *CopyMesh( mesh_t *mesh ) { + mesh_t *out; + int size; + + out = safe_malloc( sizeof( *out ) ); + out->width = mesh->width; + out->height = mesh->height; + + size = out->width * out->height * sizeof( *out->verts ); + out->verts = safe_malloc( size ); + memcpy( out->verts, mesh->verts, size ); + + return out; +} + + +/* +TransposeMesh() +returns a transposed copy of the mesh, freeing the original +*/ + +mesh_t *TransposeMesh( mesh_t *in ) { + int w, h; + mesh_t *out; + + out = safe_malloc( sizeof( *out ) ); + out->width = in->height; + out->height = in->width; + out->verts = safe_malloc( out->width * out->height * sizeof( bspDrawVert_t ) ); + + for ( h = 0 ; h < in->height ; h++ ) { + for ( w = 0 ; w < in->width ; w++ ) { + out->verts[ w * in->height + h ] = in->verts[ h * in->width + w ]; + } + } + + FreeMesh( in ); + + return out; +} + +void InvertMesh( mesh_t *in ) { + int w, h; + bspDrawVert_t temp; + + for ( h = 0 ; h < in->height ; h++ ) { + for ( w = 0 ; w < in->width / 2 ; w++ ) { + temp = in->verts[ h * in->width + w ]; + in->verts[ h * in->width + w ] = in->verts[ h * in->width + in->width - 1 - w ]; + in->verts[ h * in->width + in->width - 1 - w ] = temp; + } + } +} + +/* +================= +MakeMeshNormals + +================= +*/ +void MakeMeshNormals( mesh_t in ) +{ + int i, j, k, dist; + vec3_t normal; + vec3_t sum; + int count; + vec3_t base; + vec3_t delta; + int x, y; + bspDrawVert_t *dv; + vec3_t around[8], temp; + qboolean good[8]; + qboolean wrapWidth, wrapHeight; + float len; + int neighbors[8][2] = + { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + + wrapWidth = qfalse; + for ( i = 0 ; i < in.height ; i++ ) { + VectorSubtract( in.verts[i*in.width].xyz, + in.verts[i*in.width+in.width-1].xyz, delta ); + len = VectorLength( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == in.height ) { + wrapWidth = qtrue; + } + + wrapHeight = qfalse; + for ( i = 0 ; i < in.width ; i++ ) { + VectorSubtract( in.verts[i].xyz, + in.verts[i + (in.height-1)*in.width].xyz, delta ); + len = VectorLength( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == in.width) { + wrapHeight = qtrue; + } + + + for ( i = 0 ; i < in.width ; i++ ) { + for ( j = 0 ; j < in.height ; j++ ) { + count = 0; + dv = &in.verts[j*in.width+i]; + VectorCopy( dv->xyz, base ); + for ( k = 0 ; k < 8 ; k++ ) { + VectorClear( around[k] ); + good[k] = qfalse; + + for ( dist = 1 ; dist <= 3 ; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = in.width - 1 + x; + } else if ( x >= in.width ) { + x = 1 + x - in.width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = in.height - 1 + y; + } else if ( y >= in.height ) { + y = 1 + y - in.height; + } + } + + if ( x < 0 || x >= in.width || y < 0 || y >= in.height ) { + break; // edge of patch + } + VectorSubtract( in.verts[y*in.width+x].xyz, base, temp ); + if ( VectorNormalize( temp, temp ) == 0 ) { + continue; // degenerate edge, get more dist + } else { + good[k] = qtrue; + VectorCopy( temp, around[k] ); + break; // good edge + } + } + } + + VectorClear( sum ); + for ( k = 0 ; k < 8 ; k++ ) { + if ( !good[k] || !good[(k+1)&7] ) { + continue; // didn't get two points + } + CrossProduct( around[(k+1)&7], around[k], normal ); + if ( VectorNormalize( normal, normal ) == 0 ) { + continue; + } + VectorAdd( normal, sum, sum ); + count++; + } + if ( count == 0 ) { +//Sys_Printf("bad normal\n"); + count = 1; + } + VectorNormalize( sum, dv->normal ); + } + } +} + +/* +PutMeshOnCurve() +drops the aproximating points onto the curve +ydnar: fixme: make this use LerpDrawVert() rather than this complicated mess +*/ + +void PutMeshOnCurve( mesh_t in ) { + int i, j, l, m; + float prev, next; + + + // put all the aproximating points on the curve + for ( i = 0 ; i < in.width ; i++ ) { + for ( j = 1 ; j < in.height ; j += 2 ) { + for ( l = 0 ; l < 3 ; l++ ) { + prev = ( in.verts[j*in.width+i].xyz[l] + in.verts[(j+1)*in.width+i].xyz[l] ) * 0.5; + next = ( in.verts[j*in.width+i].xyz[l] + in.verts[(j-1)*in.width+i].xyz[l] ) * 0.5; + in.verts[j*in.width+i].xyz[l] = ( prev + next ) * 0.5; + + /* ydnar: interpolating st coords */ + if( l < 2 ) + { + prev = ( in.verts[j*in.width+i].st[l] + in.verts[(j+1)*in.width+i].st[l] ) * 0.5; + next = ( in.verts[j*in.width+i].st[l] + in.verts[(j-1)*in.width+i].st[l] ) * 0.5; + in.verts[j*in.width+i].st[l] = ( prev + next ) * 0.5; + + for( m = 0; m < MAX_LIGHTMAPS; m++ ) + { + prev = ( in.verts[j*in.width+i].lightmap[ m ][l] + in.verts[(j+1)*in.width+i].lightmap[ m ][l] ) * 0.5; + next = ( in.verts[j*in.width+i].lightmap[ m ][l] + in.verts[(j-1)*in.width+i].lightmap[ m ][l] ) * 0.5; + in.verts[j*in.width+i].lightmap[ m ][l] = ( prev + next ) * 0.5; + } + } + } + } + } + + for ( j = 0 ; j < in.height ; j++ ) { + for ( i = 1 ; i < in.width ; i += 2 ) { + for ( l = 0 ; l < 3 ; l++ ) { + prev = ( in.verts[j*in.width+i].xyz[l] + in.verts[j*in.width+i+1].xyz[l] ) * 0.5; + next = ( in.verts[j*in.width+i].xyz[l] + in.verts[j*in.width+i-1].xyz[l] ) * 0.5; + in.verts[j*in.width+i].xyz[l] = ( prev + next ) * 0.5; + + /* ydnar: interpolating st coords */ + if( l < 2 ) + { + prev = ( in.verts[j*in.width+i].st[l] + in.verts[j*in.width+i+1].st[l] ) * 0.5; + next = ( in.verts[j*in.width+i].st[l] + in.verts[j*in.width+i-1].st[l] ) * 0.5; + in.verts[j*in.width+i].st[l] = ( prev + next ) * 0.5; + + for( m = 0; m < MAX_LIGHTMAPS; m++ ) + { + prev = ( in.verts[j*in.width+i].lightmap[ m ][l] + in.verts[j*in.width+i+1].lightmap[ m ][l] ) * 0.5; + next = ( in.verts[j*in.width+i].lightmap[ m ][l] + in.verts[j*in.width+i-1].lightmap[ m ][l] ) * 0.5; + in.verts[j*in.width+i].lightmap[ m ][l] = ( prev + next ) * 0.5; + } + } + } + } + } +} + + +/* +================= +SubdivideMesh + +================= +*/ +mesh_t *SubdivideMesh( mesh_t in, float maxError, float minLength ) +{ + int i, j, k, l; + bspDrawVert_t prev, next, mid; + vec3_t prevxyz, nextxyz, midxyz; + vec3_t delta; + float len; + mesh_t out; + + /* ydnar: static for os x */ + MAC_STATIC bspDrawVert_t expand[MAX_EXPANDED_AXIS][MAX_EXPANDED_AXIS]; + + + out.width = in.width; + out.height = in.height; + + for ( i = 0 ; i < in.width ; i++ ) { + for ( j = 0 ; j < in.height ; j++ ) { + expand[j][i] = in.verts[j*in.width+i]; + } + } + + // horizontal subdivisions + for ( j = 0 ; j + 2 < out.width ; j += 2 ) { + // check subdivided midpoints against control points + for ( i = 0 ; i < out.height ; i++ ) { + for ( l = 0 ; l < 3 ; l++ ) { + prevxyz[l] = expand[i][j+1].xyz[l] - expand[i][j].xyz[l]; + nextxyz[l] = expand[i][j+2].xyz[l] - expand[i][j+1].xyz[l]; + midxyz[l] = (expand[i][j].xyz[l] + expand[i][j+1].xyz[l] * 2 + + expand[i][j+2].xyz[l] ) * 0.25; + } + + // if the span length is too long, force a subdivision + if ( VectorLength( prevxyz ) > minLength + || VectorLength( nextxyz ) > minLength ) { + break; + } + + // see if this midpoint is off far enough to subdivide + VectorSubtract( expand[i][j+1].xyz, midxyz, delta ); + len = VectorLength( delta ); + if ( len > maxError ) { + break; + } + } + + if ( out.width + 2 >= MAX_EXPANDED_AXIS ) { + break; // can't subdivide any more + } + + if ( i == out.height ) { + continue; // didn't need subdivision + } + + // insert two columns and replace the peak + out.width += 2; + + for ( i = 0 ; i < out.height ; i++ ) { + LerpDrawVert( &expand[i][j], &expand[i][j+1], &prev ); + LerpDrawVert( &expand[i][j+1], &expand[i][j+2], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = out.width - 1 ; k > j + 3 ; k-- ) { + expand[i][k] = expand[i][k-2]; + } + expand[i][j + 1] = prev; + expand[i][j + 2] = mid; + expand[i][j + 3] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + + } + + // vertical subdivisions + for ( j = 0 ; j + 2 < out.height ; j += 2 ) { + // check subdivided midpoints against control points + for ( i = 0 ; i < out.width ; i++ ) { + for ( l = 0 ; l < 3 ; l++ ) { + prevxyz[l] = expand[j+1][i].xyz[l] - expand[j][i].xyz[l]; + nextxyz[l] = expand[j+2][i].xyz[l] - expand[j+1][i].xyz[l]; + midxyz[l] = (expand[j][i].xyz[l] + expand[j+1][i].xyz[l] * 2 + + expand[j+2][i].xyz[l] ) * 0.25; + } + + // if the span length is too long, force a subdivision + if ( VectorLength( prevxyz ) > minLength + || VectorLength( nextxyz ) > minLength ) { + break; + } + // see if this midpoint is off far enough to subdivide + VectorSubtract( expand[j+1][i].xyz, midxyz, delta ); + len = VectorLength( delta ); + if ( len > maxError ) { + break; + } + } + + if ( out.height + 2 >= MAX_EXPANDED_AXIS ) { + break; // can't subdivide any more + } + + if ( i == out.width ) { + continue; // didn't need subdivision + } + + // insert two columns and replace the peak + out.height += 2; + + for ( i = 0 ; i < out.width ; i++ ) { + LerpDrawVert( &expand[j][i], &expand[j+1][i], &prev ); + LerpDrawVert( &expand[j+1][i], &expand[j+2][i], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = out.height - 1 ; k > j + 3 ; k-- ) { + expand[k][i] = expand[k-2][i]; + } + expand[j+1][i] = prev; + expand[j+2][i] = mid; + expand[j+3][i] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + + } + + // collapse the verts + + out.verts = &expand[0][0]; + for ( i = 1 ; i < out.height ; i++ ) { + memmove( &out.verts[i*out.width], expand[i], out.width * sizeof(bspDrawVert_t) ); + } + + return CopyMesh(&out); +} + + + +/* +IterationsForCurve() - ydnar +given a curve of a certain length, return the number of subdivision iterations +note: this is affected by subdivision amount +*/ + +int IterationsForCurve( float len, int subdivisions ) +{ + int iterations, facets; + + + /* calculate the number of subdivisions */ + for( iterations = 0; iterations < 3; iterations++ ) + { + facets = subdivisions * 16 * pow( 2, iterations ); + if( facets >= len ) + break; + } + + /* return to caller */ + return iterations; +} + + +/* +SubdivideMesh2() - ydnar +subdivides each mesh quad a specified number of times +*/ + +mesh_t *SubdivideMesh2( mesh_t in, int iterations ) +{ + int i, j, k; + bspDrawVert_t prev, next, mid; + mesh_t out; + + /* ydnar: static for os x */ + MAC_STATIC bspDrawVert_t expand[ MAX_EXPANDED_AXIS ][ MAX_EXPANDED_AXIS ]; + + + /* initial setup */ + out.width = in.width; + out.height = in.height; + for( i = 0; i < in.width; i++ ) + { + for( j = 0; j < in.height; j++ ) + expand[ j ][ i ] = in.verts[ j * in.width + i ]; + } + + /* keep chopping */ + for( ; iterations > 0; iterations-- ) + { + /* horizontal subdivisions */ + for( j = 0; j + 2 < out.width; j += 4 ) + { + /* check size limit */ + if( out.width + 2 >= MAX_EXPANDED_AXIS ) + break; + + /* insert two columns and replace the peak */ + out.width += 2; + for( i = 0; i < out.height; i++ ) + { + LerpDrawVert( &expand[ i ][ j ], &expand[ i ][ j + 1 ], &prev ); + LerpDrawVert( &expand[ i ][ j + 1 ], &expand[ i ][ j + 2 ], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = out.width - 1 ; k > j + 3; k-- ) + expand [ i ][ k ] = expand[ i ][ k - 2 ]; + expand[ i ][ j + 1 ] = prev; + expand[ i ][ j + 2 ] = mid; + expand[ i ][ j + 3 ] = next; + } + + } + + /* vertical subdivisions */ + for ( j = 0; j + 2 < out.height; j += 4 ) + { + /* check size limit */ + if( out.height + 2 >= MAX_EXPANDED_AXIS ) + break; + + /* insert two columns and replace the peak */ + out.height += 2; + for( i = 0; i < out.width; i++ ) + { + LerpDrawVert( &expand[ j ][ i ], &expand[ j + 1 ][ i ], &prev ); + LerpDrawVert( &expand[ j + 1 ][ i ], &expand[ j + 2 ][ i ], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for( k = out.height - 1; k > j + 3; k-- ) + expand[ k ][ i ] = expand[ k - 2 ][ i ]; + expand[ j + 1 ][ i ] = prev; + expand[ j + 2 ][ i ] = mid; + expand[ j + 3 ][ i ] = next; + } + } + } + + /* collapse the verts */ + out.verts = &expand[ 0 ][ 0 ]; + for( i = 1; i < out.height; i++ ) + memmove( &out.verts[ i * out.width ], expand[ i ], out.width * sizeof( bspDrawVert_t ) ); + + /* return to sender */ + return CopyMesh( &out ); +} + + + + + + + +/* +================ +ProjectPointOntoVector +================ +*/ +void ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ) +{ + vec3_t pVec, vec; + + VectorSubtract( point, vStart, pVec ); + VectorSubtract( vEnd, vStart, vec ); + VectorNormalize( vec, vec ); + // project onto the directional vector for this segment + VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj ); +} + +/* +================ +RemoveLinearMeshColumsRows +================ +*/ +mesh_t *RemoveLinearMeshColumnsRows( mesh_t *in ) { + int i, j, k; + float len, maxLength; + vec3_t proj, dir; + mesh_t out; + + /* ydnar: static for os x */ + MAC_STATIC bspDrawVert_t expand[MAX_EXPANDED_AXIS][MAX_EXPANDED_AXIS]; + + + out.width = in->width; + out.height = in->height; + + for ( i = 0 ; i < in->width ; i++ ) { + for ( j = 0 ; j < in->height ; j++ ) { + expand[j][i] = in->verts[j*in->width+i]; + } + } + + for ( j = 1 ; j < out.width - 1; j++ ) { + maxLength = 0; + for ( i = 0 ; i < out.height ; i++ ) { + ProjectPointOntoVector(expand[i][j].xyz, expand[i][j-1].xyz, expand[i][j+1].xyz, proj); + VectorSubtract(expand[i][j].xyz, proj, dir); + len = VectorLength(dir); + if (len > maxLength) { + maxLength = len; + } + } + if (maxLength < 0.1) + { + out.width--; + for ( i = 0 ; i < out.height ; i++ ) { + for (k = j; k < out.width; k++) { + expand[i][k] = expand[i][k+1]; + } + } + j--; + } + } + for ( j = 1 ; j < out.height - 1; j++ ) { + maxLength = 0; + for ( i = 0 ; i < out.width ; i++ ) { + ProjectPointOntoVector(expand[j][i].xyz, expand[j-1][i].xyz, expand[j+1][i].xyz, proj); + VectorSubtract(expand[j][i].xyz, proj, dir); + len = VectorLength(dir); + if (len > maxLength) { + maxLength = len; + } + } + if (maxLength < 0.1) + { + out.height--; + for ( i = 0 ; i < out.width ; i++ ) { + for (k = j; k < out.height; k++) { + expand[k][i] = expand[k+1][i]; + } + } + j--; + } + } + // collapse the verts + out.verts = &expand[0][0]; + for ( i = 1 ; i < out.height ; i++ ) { + memmove( &out.verts[i*out.width], expand[i], out.width * sizeof(bspDrawVert_t) ); + } + + return CopyMesh(&out); +} + + + +/* +================= +SubdivideMeshQuads +================= +*/ +mesh_t *SubdivideMeshQuads( mesh_t *in, float minLength, int maxsize, int *widthtable, int *heighttable ) +{ + int i, j, k, w, h, maxsubdivisions, subdivisions; + vec3_t dir; + float length, maxLength, amount; + mesh_t out; + bspDrawVert_t expand[MAX_EXPANDED_AXIS][MAX_EXPANDED_AXIS]; + + out.width = in->width; + out.height = in->height; + + for ( i = 0 ; i < in->width ; i++ ) { + for ( j = 0 ; j < in->height ; j++ ) { + expand[j][i] = in->verts[j*in->width+i]; + } + } + + if (maxsize > MAX_EXPANDED_AXIS) + Error("SubdivideMeshQuads: maxsize > MAX_EXPANDED_AXIS"); + + // horizontal subdivisions + + maxsubdivisions = (maxsize - in->width) / (in->width - 1); + + for ( w = 0, j = 0 ; w < in->width - 1; w++, j += subdivisions + 1) { + maxLength = 0; + for ( i = 0 ; i < out.height ; i++ ) { + VectorSubtract(expand[i][j+1].xyz, expand[i][j].xyz, dir); + length = VectorLength( dir ); + if (length > maxLength) { + maxLength = length; + } + } + + subdivisions = (int) (maxLength / minLength); + if (subdivisions > maxsubdivisions) + subdivisions = maxsubdivisions; + + widthtable[w] = subdivisions + 1; + if (subdivisions <= 0) + continue; + + out.width += subdivisions; + + for ( i = 0 ; i < out.height ; i++ ) { + for ( k = out.width - 1 ; k > j + subdivisions; k-- ) { + expand[i][k] = expand[i][k-subdivisions]; + } + for (k = 1; k <= subdivisions; k++) + { + amount = (float) k / (subdivisions + 1); + LerpDrawVertAmount(&expand[i][j], &expand[i][j+subdivisions+1], amount, &expand[i][j+k]); + } + } + } + + maxsubdivisions = (maxsize - in->height) / (in->height - 1); + + for ( h = 0, j = 0 ; h < in->height - 1; h++, j += subdivisions + 1) { + maxLength = 0; + for ( i = 0 ; i < out.width ; i++ ) { + VectorSubtract(expand[j+1][i].xyz, expand[j][i].xyz, dir); + length = VectorLength( dir ); + if (length > maxLength) { + maxLength = length; + } + } + + subdivisions = (int) (maxLength / minLength); + if (subdivisions > maxsubdivisions) + subdivisions = maxsubdivisions; + + heighttable[h] = subdivisions + 1; + if (subdivisions <= 0) + continue; + + out.height += subdivisions; + + for ( i = 0 ; i < out.width ; i++ ) { + for ( k = out.height - 1 ; k > j + subdivisions; k-- ) { + expand[k][i] = expand[k-subdivisions][i]; + } + for (k = 1; k <= subdivisions; k++) + { + amount = (float) k / (subdivisions + 1); + LerpDrawVertAmount(&expand[j][i], &expand[j+subdivisions+1][i], amount, &expand[j+k][i]); + } + } + } + + // collapse the verts + out.verts = &expand[0][0]; + for ( i = 1 ; i < out.height ; i++ ) { + memmove( &out.verts[i*out.width], expand[i], out.width * sizeof(bspDrawVert_t) ); + } + + return CopyMesh(&out); +} diff --git a/tools/quake3/q3map2/model.c b/tools/quake3/q3map2/model.c new file mode 100644 index 00000000..92000684 --- /dev/null +++ b/tools/quake3/q3map2/model.c @@ -0,0 +1,758 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define MODEL_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* +PicoPrintFunc() +callback for picomodel.lib +*/ + +void PicoPrintFunc( int level, const char *str ) +{ + if( str == NULL ) + return; + switch( level ) + { + case PICO_NORMAL: + Sys_Printf( "%s\n", str ); + break; + + case PICO_VERBOSE: + Sys_FPrintf( SYS_VRB, "%s\n", str ); + break; + + case PICO_WARNING: + Sys_Printf( "WARNING: %s\n", str ); + break; + + case PICO_ERROR: + Sys_Printf( "ERROR: %s\n", str ); + break; + + case PICO_FATAL: + Error( "ERROR: %s\n", str ); + break; + } +} + + + +/* +PicoLoadFileFunc() +callback for picomodel.lib +*/ + +void PicoLoadFileFunc( char *name, byte **buffer, int *bufSize ) +{ + *bufSize = vfsLoadFile( (const char*) name, (void**) buffer, 0 ); +} + + + +/* +FindModel() - ydnar +finds an existing picoModel and returns a pointer to the picoModel_t struct or NULL if not found +*/ + +picoModel_t *FindModel( char *name, int frame ) +{ + int i; + + + /* init */ + if( numPicoModels <= 0 ) + memset( picoModels, 0, sizeof( picoModels ) ); + + /* dummy check */ + if( name == NULL || name[ 0 ] == '\0' ) + return NULL; + + /* search list */ + for( i = 0; i < MAX_MODELS; i++ ) + { + if( picoModels[ i ] != NULL && + !strcmp( PicoGetModelName( picoModels[ i ] ), name ) && + PicoGetModelFrameNum( picoModels[ i ] ) == frame ) + return picoModels[ i ]; + } + + /* no matching picoModel found */ + return NULL; +} + + + +/* +LoadModel() - ydnar +loads a picoModel and returns a pointer to the picoModel_t struct or NULL if not found +*/ + +picoModel_t *LoadModel( char *name, int frame ) +{ + int i; + picoModel_t *model, **pm; + + + /* init */ + if( numPicoModels <= 0 ) + memset( picoModels, 0, sizeof( picoModels ) ); + + /* dummy check */ + if( name == NULL || name[ 0 ] == '\0' ) + return NULL; + + /* try to find existing picoModel */ + model = FindModel( name, frame ); + if( model != NULL ) + return model; + + /* none found, so find first non-null picoModel */ + pm = NULL; + for( i = 0; i < MAX_MODELS; i++ ) + { + if( picoModels[ i ] == NULL ) + { + pm = &picoModels[ i ]; + break; + } + } + + /* too many picoModels? */ + if( pm == NULL ) + Error( "MAX_MODELS (%d) exceeded, there are too many model files referenced by the map.", MAX_MODELS ); + + /* attempt to parse model */ + *pm = PicoLoadModel( (char*) name, frame ); + + /* if loading failed, make a bogus model to silence the rest of the warnings */ + if( *pm == NULL ) + { + /* allocate a new model */ + *pm = PicoNewModel(); + if( *pm == NULL ) + return NULL; + + /* set data */ + PicoSetModelName( *pm, name ); + PicoSetModelFrameNum( *pm, frame ); + } + + /* debug code */ + #if 0 + { + int numSurfaces, numVertexes; + picoSurface_t *ps; + + + Sys_Printf( "Model %s\n", name ); + numSurfaces = PicoGetModelNumSurfaces( *pm ); + for( i = 0; i < numSurfaces; i++ ) + { + ps = PicoGetModelSurface( *pm, i ); + numVertexes = PicoGetSurfaceNumVertexes( ps ); + Sys_Printf( "Surface %d has %d vertexes\n", i, numVertexes ); + } + } + #endif + + /* set count */ + if( *pm != NULL ) + numPicoModels++; + + /* return the picoModel */ + return *pm; +} + + + +/* +InsertModel() - ydnar +adds a picomodel into the bsp +*/ + +void InsertModel( char *name, int frame, m4x4_t transform, remap_t *remap, shaderInfo_t *celShader, int eNum, int castShadows, int recvShadows, int spawnFlags, float lightmapScale ) +{ + int i, j, k, s, numSurfaces; + m4x4_t identity, nTransform; + picoModel_t *model; + picoShader_t *shader; + picoSurface_t *surface; + shaderInfo_t *si; + mapDrawSurface_t *ds; + bspDrawVert_t *dv; + char *picoShaderName; + char shaderName[ MAX_QPATH ]; + picoVec_t *xyz, *normal, *st; + byte *color; + picoIndex_t *indexes; + remap_t *rm, *glob; + double normalEpsilon_save; + double distanceEpsilon_save; + + + /* get model */ + model = LoadModel( name, frame ); + if( model == NULL ) + return; + + /* handle null matrix */ + if( transform == NULL ) + { + m4x4_identity( identity ); + transform = identity; + } + + /* hack: Stable-1_2 and trunk have differing row/column major matrix order + this transpose is necessary with Stable-1_2 + uncomment the following line with old m4x4_t (non 1.3/spog_branch) code */ + //% m4x4_transpose( transform ); + + /* create transform matrix for normals */ + memcpy( nTransform, transform, sizeof( m4x4_t ) ); + if( m4x4_invert( nTransform ) ) + Sys_FPrintf( SYS_VRB, "WARNING: Can't invert model transform matrix, using transpose instead\n" ); + m4x4_transpose( nTransform ); + + /* fix bogus lightmap scale */ + if( lightmapScale <= 0.0f ) + lightmapScale = 1.0f; + + /* each surface on the model will become a new map drawsurface */ + numSurfaces = PicoGetModelNumSurfaces( model ); + //% Sys_FPrintf( SYS_VRB, "Model %s has %d surfaces\n", name, numSurfaces ); + for( s = 0; s < numSurfaces; s++ ) + { + /* get surface */ + surface = PicoGetModelSurface( model, s ); + if( surface == NULL ) + continue; + + /* only handle triangle surfaces initially (fixme: support patches) */ + if( PicoGetSurfaceType( surface ) != PICO_TRIANGLES ) + continue; + + /* fix the surface's normals */ + PicoFixSurfaceNormals( surface ); + + /* allocate a surface (ydnar: gs mods) */ + ds = AllocDrawSurface( SURFACE_TRIANGLES ); + ds->entityNum = eNum; + ds->castShadows = castShadows; + ds->recvShadows = recvShadows; + + /* get shader name */ + shader = PicoGetSurfaceShader( surface ); + if( shader == NULL ) + picoShaderName = ""; + else + picoShaderName = PicoGetShaderName( shader ); + + /* handle shader remapping */ + glob = NULL; + for( rm = remap; rm != NULL; rm = rm->next ) + { + if( rm->from[ 0 ] == '*' && rm->from[ 1 ] == '\0' ) + glob = rm; + else if( !Q_stricmp( picoShaderName, rm->from ) ) + { + Sys_FPrintf( SYS_VRB, "Remapping %s to %s\n", picoShaderName, rm->to ); + picoShaderName = rm->to; + glob = NULL; + break; + } + } + + if( glob != NULL ) + { + Sys_FPrintf( SYS_VRB, "Globbing %s to %s\n", picoShaderName, glob->to ); + picoShaderName = glob->to; + } + + /* shader renaming for sof2 */ + if( renameModelShaders ) + { + strcpy( shaderName, picoShaderName ); + StripExtension( shaderName ); + if( spawnFlags & 1 ) + strcat( shaderName, "_RMG_BSP" ); + else + strcat( shaderName, "_BSP" ); + si = ShaderInfoForShader( shaderName ); + } + else + si = ShaderInfoForShader( picoShaderName ); + + /* set shader */ + ds->shaderInfo = si; + + /* set lightmap scale */ + ds->lightmapScale = lightmapScale; + + /* force to meta? */ + if( (si != NULL && si->forceMeta) || (spawnFlags & 4) ) /* 3rd bit */ + ds->type = SURFACE_FORCED_META; + + /* set particulars */ + ds->numVerts = PicoGetSurfaceNumVertexes( surface ); + ds->verts = safe_malloc( ds->numVerts * sizeof( ds->verts[ 0 ] ) ); + memset( ds->verts, 0, ds->numVerts * sizeof( ds->verts[ 0 ] ) ); + + ds->numIndexes = PicoGetSurfaceNumIndexes( surface ); + ds->indexes = safe_malloc( ds->numIndexes * sizeof( ds->indexes[ 0 ] ) ); + memset( ds->indexes, 0, ds->numIndexes * sizeof( ds->indexes[ 0 ] ) ); + + /* copy vertexes */ + for( i = 0; i < ds->numVerts; i++ ) + { + /* get vertex */ + dv = &ds->verts[ i ]; + + /* xyz and normal */ + xyz = PicoGetSurfaceXYZ( surface, i ); + VectorCopy( xyz, dv->xyz ); + m4x4_transform_point( transform, dv->xyz ); + + normal = PicoGetSurfaceNormal( surface, i ); + VectorCopy( normal, dv->normal ); + m4x4_transform_normal( nTransform, dv->normal ); + VectorNormalize( dv->normal, dv->normal ); + + /* ydnar: tek-fu celshading support for flat shaded shit */ + if( flat ) + { + dv->st[ 0 ] = si->stFlat[ 0 ]; + dv->st[ 1 ] = si->stFlat[ 1 ]; + } + + /* ydnar: gs mods: added support for explicit shader texcoord generation */ + else if( si->tcGen ) + { + /* project the texture */ + dv->st[ 0 ] = DotProduct( si->vecs[ 0 ], dv->xyz ); + dv->st[ 1 ] = DotProduct( si->vecs[ 1 ], dv->xyz ); + } + + /* normal texture coordinates */ + else + { + st = PicoGetSurfaceST( surface, 0, i ); + dv->st[ 0 ] = st[ 0 ]; + dv->st[ 1 ] = st[ 1 ]; + } + + /* set lightmap/color bits */ + color = PicoGetSurfaceColor( surface, 0, i ); + for( j = 0; j < MAX_LIGHTMAPS; j++ ) + { + dv->lightmap[ j ][ 0 ] = 0.0f; + dv->lightmap[ j ][ 1 ] = 0.0f; + dv->color[ j ][ 0 ] = color[ 0 ]; + dv->color[ j ][ 1 ] = color[ 1 ]; + dv->color[ j ][ 2 ] = color[ 2 ]; + dv->color[ j ][ 3 ] = color[ 3 ]; + } + } + + /* copy indexes */ + indexes = PicoGetSurfaceIndexes( surface, 0 ); + for( i = 0; i < ds->numIndexes; i++ ) + ds->indexes[ i ] = indexes[ i ]; + + /* set cel shader */ + ds->celShader = celShader; + + /* ydnar: giant hack land: generate clipping brushes for model triangles */ + if( si->clipModel || (spawnFlags & 2) ) /* 2nd bit */ + { + vec3_t points[ 4 ], backs[ 3 ]; + vec4_t plane, reverse, pa, pb, pc; + + + /* temp hack */ + if( !si->clipModel && + ((si->compileFlags & C_TRANSLUCENT) || !(si->compileFlags & C_SOLID)) ) + continue; + + /* overflow check */ + if( (nummapplanes + 64) >= (MAX_MAP_PLANES >> 1) ) + continue; + + /* walk triangle list */ + for( i = 0; i < ds->numIndexes; i += 3 ) + { + /* overflow hack */ + if( (nummapplanes + 64) >= (MAX_MAP_PLANES >> 1) ) + { + Sys_Printf( "WARNING: MAX_MAP_PLANES (%d) hit generating clip brushes for model %s.\n", + MAX_MAP_PLANES, name ); + break; + } + + /* make points and back points */ + for( j = 0; j < 3; j++ ) + { + /* get vertex */ + dv = &ds->verts[ ds->indexes[ i + j ] ]; + + /* copy xyz */ + VectorCopy( dv->xyz, points[ j ] ); + VectorCopy( dv->xyz, backs[ j ] ); + + /* find nearest axial to normal and push back points opposite */ + /* note: this doesn't work as well as simply using the plane of the triangle, below */ + for( k = 0; k < 3; k++ ) + { + if( fabs( dv->normal[ k ] ) >= fabs( dv->normal[ (k + 1) % 3 ] ) && + fabs( dv->normal[ k ] ) >= fabs( dv->normal[ (k + 2) % 3 ] ) ) + { + backs[ j ][ k ] += dv->normal[ k ] < 0.0f ? 64.0f : -64.0f; + break; + } + } + } + + VectorCopy( points[0], points[3] ); // for cyclic usage + + /* make plane for triangle */ + // div0: add some extra spawnflags: + // 0: snap normals to axial planes for extrusion + // 8: extrude with the original normals + // 16: extrude only with up/down normals (ideal for terrain) + // 24: extrude by distance zero (may need engine changes) + if( PlaneFromPoints( plane, points[ 0 ], points[ 1 ], points[ 2 ] ) ) + { + vec3_t bestNormal; + float backPlaneDistance = 2; + + if(spawnFlags & 8) // use a DOWN normal + { + if(spawnFlags & 16) + { + // 24: normal as is, and zero width (broken) + VectorCopy(plane, bestNormal); + } + else + { + // 8: normal as is + VectorCopy(plane, bestNormal); + } + } + else + { + if(spawnFlags & 16) + { + // 16: UP/DOWN normal + VectorSet(bestNormal, 0, 0, (plane[2] >= 0 ? 1 : -1)); + } + else + { + // 0: axial normal + if(fabs(plane[0]) > fabs(plane[1])) // x>y + if(fabs(plane[1]) > fabs(plane[2])) // x>y, y>z + VectorSet(bestNormal, (plane[0] >= 0 ? 1 : -1), 0, 0); + else // x>y, z>=y + if(fabs(plane[0]) > fabs(plane[2])) // x>z, z>=y + VectorSet(bestNormal, (plane[0] >= 0 ? 1 : -1), 0, 0); + else // z>=x, x>y + VectorSet(bestNormal, 0, 0, (plane[2] >= 0 ? 1 : -1)); + else // y>=x + if(fabs(plane[1]) > fabs(plane[2])) // y>z, y>=x + VectorSet(bestNormal, 0, (plane[1] >= 0 ? 1 : -1), 0); + else // z>=y, y>=x + VectorSet(bestNormal, 0, 0, (plane[2] >= 0 ? 1 : -1)); + } + } + + /* build a brush */ + buildBrush = AllocBrush( 48 ); + buildBrush->entityNum = mapEntityNum; + buildBrush->original = buildBrush; + buildBrush->contentShader = si; + buildBrush->compileFlags = si->compileFlags; + buildBrush->contentFlags = si->contentFlags; + normalEpsilon_save = normalEpsilon; + distanceEpsilon_save = distanceEpsilon; + if(si->compileFlags & C_STRUCTURAL) // allow forced structural brushes here + { + buildBrush->detail = qfalse; + + // only allow EXACT matches when snapping for these (this is mostly for caulk brushes inside a model) + if(normalEpsilon > 0) + normalEpsilon = 0; + if(distanceEpsilon > 0) + distanceEpsilon = 0; + } + else + buildBrush->detail = qtrue; + + /* regenerate back points */ + for( j = 0; j < 3; j++ ) + { + /* get vertex */ + dv = &ds->verts[ ds->indexes[ i + j ] ]; + + // shift by some units + VectorMA(dv->xyz, -64.0f, bestNormal, backs[j]); // 64 prevents roundoff errors a bit + } + + /* make back plane */ + VectorScale( plane, -1.0f, reverse ); + reverse[ 3 ] = -plane[ 3 ]; + if((spawnFlags & 24) != 24) + reverse[3] += DotProduct(bestNormal, plane) * backPlaneDistance; + // that's at least sqrt(1/3) backPlaneDistance, unless in DOWN mode; in DOWN mode, we are screwed anyway if we encounter a plane that's perpendicular to the xy plane) + + if( PlaneFromPoints( pa, points[ 2 ], points[ 1 ], backs[ 1 ] ) && + PlaneFromPoints( pb, points[ 1 ], points[ 0 ], backs[ 0 ] ) && + PlaneFromPoints( pc, points[ 0 ], points[ 2 ], backs[ 2 ] ) ) + { + /* set up brush sides */ + buildBrush->numsides = 5; + for( j = 0; j < buildBrush->numsides; j++ ) + buildBrush->sides[ j ].shaderInfo = si; + + buildBrush->sides[ 0 ].planenum = FindFloatPlane( plane, plane[ 3 ], 3, points ); + buildBrush->sides[ 1 ].planenum = FindFloatPlane( pa, pa[ 3 ], 2, &points[ 1 ] ); // pa contains points[1] and points[2] + buildBrush->sides[ 2 ].planenum = FindFloatPlane( pb, pb[ 3 ], 2, &points[ 0 ] ); // pb contains points[0] and points[1] + buildBrush->sides[ 3 ].planenum = FindFloatPlane( pc, pc[ 3 ], 2, &points[ 2 ] ); // pc contains points[2] and points[0] (copied to points[3] + buildBrush->sides[ 4 ].planenum = FindFloatPlane( reverse, reverse[ 3 ], 3, backs ); + } + else + { + free(buildBrush); + continue; + } + + normalEpsilon = normalEpsilon_save; + distanceEpsilon = distanceEpsilon_save; + + /* add to entity */ + if( CreateBrushWindings( buildBrush ) ) + { + AddBrushBevels(); + //% EmitBrushes( buildBrush, NULL, NULL ); + buildBrush->next = entities[ mapEntityNum ].brushes; + entities[ mapEntityNum ].brushes = buildBrush; + entities[ mapEntityNum ].numBrushes++; + } + else + free( buildBrush ); + } + } + } + } +} + + + +/* +AddTriangleModels() +adds misc_model surfaces to the bsp +*/ + +void AddTriangleModels( entity_t *e ) +{ + int num, frame, castShadows, recvShadows, spawnFlags; + entity_t *e2; + const char *targetName; + const char *target, *model, *value; + char shader[ MAX_QPATH ]; + shaderInfo_t *celShader; + float temp, baseLightmapScale, lightmapScale; + vec3_t origin, scale, angles; + m4x4_t transform; + epair_t *ep; + remap_t *remap, *remap2; + char *split; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- AddTriangleModels ---\n" ); + + /* get current brush entity targetname */ + if( e == entities ) + targetName = ""; + else + { + targetName = ValueForKey( e, "targetname" ); + + /* misc_model entities target non-worldspawn brush model entities */ + if( targetName[ 0 ] == '\0' ) + return; + } + + /* get lightmap scale */ + baseLightmapScale = FloatForKey( e, "_lightmapscale" ); + if( baseLightmapScale <= 0.0f ) + baseLightmapScale = 0.0f; + + /* walk the entity list */ + for( num = 1; num < numEntities; num++ ) + { + /* get e2 */ + e2 = &entities[ num ]; + + /* convert misc_models into raw geometry */ + if( Q_stricmp( "misc_model", ValueForKey( e2, "classname" ) ) ) + continue; + + /* ydnar: added support for md3 models on non-worldspawn models */ + target = ValueForKey( e2, "target" ); + if( strcmp( target, targetName ) ) + continue; + + /* get model name */ + model = ValueForKey( e2, "model" ); + if( model[ 0 ] == '\0' ) + { + Sys_Printf( "WARNING: misc_model at %i %i %i without a model key\n", + (int) origin[ 0 ], (int) origin[ 1 ], (int) origin[ 2 ] ); + continue; + } + + /* get model frame */ + frame = IntForKey( e2, "_frame" ); + + /* worldspawn (and func_groups) default to cast/recv shadows in worldspawn group */ + if( e == entities ) + { + castShadows = WORLDSPAWN_CAST_SHADOWS; + recvShadows = WORLDSPAWN_RECV_SHADOWS; + } + + /* other entities don't cast any shadows, but recv worldspawn shadows */ + else + { + castShadows = ENTITY_CAST_SHADOWS; + recvShadows = ENTITY_RECV_SHADOWS; + } + + /* get explicit shadow flags */ + GetEntityShadowFlags( e2, e, &castShadows, &recvShadows ); + + /* get spawnflags */ + spawnFlags = IntForKey( e2, "spawnflags" ); + + /* get origin */ + GetVectorForKey( e2, "origin", origin ); + VectorSubtract( origin, e->origin, origin ); /* offset by parent */ + + /* get scale */ + scale[ 0 ] = scale[ 1 ] = scale[ 2 ] = 1.0f; + temp = FloatForKey( e2, "modelscale" ); + if( temp != 0.0f ) + scale[ 0 ] = scale[ 1 ] = scale[ 2 ] = temp; + value = ValueForKey( e2, "modelscale_vec" ); + if( value[ 0 ] != '\0' ) + sscanf( value, "%f %f %f", &scale[ 0 ], &scale[ 1 ], &scale[ 2 ] ); + + /* get "angle" (yaw) or "angles" (pitch yaw roll) */ + angles[ 0 ] = angles[ 1 ] = angles[ 2 ] = 0.0f; + angles[ 2 ] = FloatForKey( e2, "angle" ); + value = ValueForKey( e2, "angles" ); + if( value[ 0 ] != '\0' ) + sscanf( value, "%f %f %f", &angles[ 1 ], &angles[ 2 ], &angles[ 0 ] ); + + /* set transform matrix (thanks spog) */ + m4x4_identity( transform ); + m4x4_pivoted_transform_by_vec3( transform, origin, angles, eXYZ, scale, vec3_origin ); + + /* get shader remappings */ + remap = NULL; + for( ep = e2->epairs; ep != NULL; ep = ep->next ) + { + /* look for keys prefixed with "_remap" */ + if( ep->key != NULL && ep->value != NULL && + ep->key[ 0 ] != '\0' && ep->value[ 0 ] != '\0' && + !Q_strncasecmp( ep->key, "_remap", 6 ) ) + { + /* create new remapping */ + remap2 = remap; + remap = safe_malloc( sizeof( *remap ) ); + remap->next = remap2; + strcpy( remap->from, ep->value ); + + /* split the string */ + split = strchr( remap->from, ';' ); + if( split == NULL ) + { + Sys_Printf( "WARNING: Shader _remap key found in misc_model without a ; character\n" ); + free( remap ); + remap = remap2; + continue; + } + + /* store the split */ + *split = '\0'; + strcpy( remap->to, (split + 1) ); + + /* note it */ + //% Sys_FPrintf( SYS_VRB, "Remapping %s to %s\n", remap->from, remap->to ); + } + } + + /* ydnar: cel shader support */ + value = ValueForKey( e2, "_celshader" ); + if( value[ 0 ] == '\0' ) + value = ValueForKey( &entities[ 0 ], "_celshader" ); + if( value[ 0 ] != '\0' ) + { + sprintf( shader, "textures/%s", value ); + celShader = ShaderInfoForShader( shader ); + } + else + celShader = NULL; + + /* get lightmap scale */ + lightmapScale = FloatForKey( e2, "_lightmapscale" ); + if( lightmapScale <= 0.0f ) + lightmapScale = baseLightmapScale; + + /* insert the model */ + InsertModel( (char*) model, frame, transform, remap, celShader, mapEntityNum, castShadows, recvShadows, spawnFlags, lightmapScale ); + + /* free shader remappings */ + while( remap != NULL ) + { + remap2 = remap->next; + free( remap ); + remap = remap2; + } + } +} diff --git a/tools/quake3/q3map2/patch.c b/tools/quake3/q3map2/patch.c new file mode 100644 index 00000000..58b8ea20 --- /dev/null +++ b/tools/quake3/q3map2/patch.c @@ -0,0 +1,525 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define PATCH_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* +ExpandLongestCurve() - ydnar +finds length of quadratic curve specified and determines if length is longer than the supplied max +*/ + +#define APPROX_SUBDIVISION 8 + +static void ExpandLongestCurve( float *longestCurve, vec3_t a, vec3_t b, vec3_t c ) +{ + int i; + float t, len; + vec3_t ab, bc, ac, pt, last, delta; + + + /* calc vectors */ + VectorSubtract( b, a, ab ); + if( VectorNormalize( ab, ab ) < 0.125f ) + return; + VectorSubtract( c, b, bc ); + if( VectorNormalize( bc, bc ) < 0.125f ) + return; + VectorSubtract( c, a, ac ); + if( VectorNormalize( ac, ac ) < 0.125f ) + return; + + /* if all 3 vectors are the same direction, then this edge is linear, so we ignore it */ + if( DotProduct( ab, bc ) > 0.99f && DotProduct( ab, ac ) > 0.99f ) + return; + + /* recalculate vectors */ + VectorSubtract( b, a, ab ); + VectorSubtract( c, b, bc ); + + /* determine length */ + VectorCopy( a, last ); + for( i = 0, len = 0.0f, t = 0.0f; i < APPROX_SUBDIVISION; i++, t += (1.0f / APPROX_SUBDIVISION) ) + { + /* calculate delta */ + delta[ 0 ] = ((1.0f - t) * ab[ 0 ]) + (t * bc[ 0 ]); + delta[ 1 ] = ((1.0f - t) * ab[ 1 ]) + (t * bc[ 1 ]); + delta[ 2 ] = ((1.0f - t) * ab[ 2 ]) + (t * bc[ 2 ]); + + /* add to first point and calculate pt-pt delta */ + VectorAdd( a, delta, pt ); + VectorSubtract( pt, last, delta ); + + /* add it to length and store last point */ + len += VectorLength( delta ); + VectorCopy( pt, last ); + } + + /* longer? */ + if( len > *longestCurve ) + *longestCurve = len; +} + + + +/* +ExpandMaxIterations() - ydnar +determines how many iterations a quadratic curve needs to be subdivided with to fit the specified error +*/ + +static void ExpandMaxIterations( int *maxIterations, int maxError, vec3_t a, vec3_t b, vec3_t c ) +{ + int i, j; + vec3_t prev, next, mid, delta, delta2; + float len, len2; + int numPoints, iterations; + vec3_t points[ MAX_EXPANDED_AXIS ]; + + + /* initial setup */ + numPoints = 3; + VectorCopy( a, points[ 0 ] ); + VectorCopy( b, points[ 1 ] ); + VectorCopy( c, points[ 2 ] ); + + /* subdivide */ + for( i = 0; i + 2 < numPoints; i += 2 ) + { + /* check subdivision limit */ + if( numPoints + 2 >= MAX_EXPANDED_AXIS ) + break; + + /* calculate new curve deltas */ + for( j = 0; j < 3; j++ ) + { + prev[ j ] = points[ i + 1 ][ j ] - points[ i ][ j ]; + next[ j ] = points[ i + 2 ][ j ] - points[ i + 1 ][ j ]; + mid[ j ] = (points[ i ][ j ] + points[ i + 1 ][ j ] * 2.0f + points[ i + 2 ][ j ]) * 0.25f; + } + + /* see if this midpoint is off far enough to subdivide */ + VectorSubtract( points[ i + 1 ], mid, delta ); + len = VectorLength( delta ); + if( len < maxError ) + continue; + + /* subdivide */ + numPoints += 2; + + /* create new points */ + for( j = 0; j < 3; j++ ) + { + prev[ j ] = 0.5f * (points[ i ][ j ] + points[ i + 1 ][ j ]); + next[ j ] = 0.5f * (points[ i + 1 ][ j ] + points[ i + 2 ][ j ]); + mid[ j ] = 0.5f * (prev[ j ] + next[ j ]); + } + + /* push points out */ + for( j = numPoints - 1; j > i + 3; j-- ) + VectorCopy( points[ j - 2 ], points[ j ] ); + + /* insert new points */ + VectorCopy( prev, points[ i + 1 ] ); + VectorCopy( mid, points[ i + 2 ] ); + VectorCopy( next, points[ i + 3 ] ); + + /* back up and recheck this set again, it may need more subdivision */ + i -= 2; + } + + /* put the line on the curve */ + for( i = 1; i < numPoints; i += 2 ) + { + for( j = 0; j < 3; j++ ) + { + prev[ j ] = 0.5f * (points[ i ][ j ] + points[ i + 1 ][ j ] ); + next[ j ] = 0.5f * (points[ i ][ j ] + points[ i - 1 ][ j ] ); + points[ i ][ j ] = 0.5f * (prev[ j ] + next[ j ]); + } + } + + /* eliminate linear sections */ + for( i = 0; i + 2 < numPoints; i++ ) + { + /* create vectors */ + VectorSubtract( points[ i + 1 ], points[ i ], delta ); + len = VectorNormalize( delta, delta ); + VectorSubtract( points[ i + 2 ], points[ i + 1 ], delta2 ); + len2 = VectorNormalize( delta2, delta2 ); + + /* if either edge is degenerate, then eliminate it */ + if( len < 0.0625f || len2 < 0.0625f || DotProduct( delta, delta2 ) >= 1.0f ) + { + for( j = i + 1; j + 1 < numPoints; j++ ) + VectorCopy( points[ j + 1 ], points[ j ] ); + numPoints--; + continue; + } + } + + /* the number of iterations is 2^(points - 1) - 1 */ + numPoints >>= 1; + iterations = 0; + while( numPoints > 1 ) + { + numPoints >>= 1; + iterations++; + } + + /* more? */ + if( iterations > *maxIterations ) + *maxIterations = iterations; +} + + + +/* +ParsePatch() +creates a mapDrawSurface_t from the patch text +*/ + +void ParsePatch( qboolean onlyLights ) +{ + vec_t info[ 5 ]; + int i, j, k; + parseMesh_t *pm; + char texture[ MAX_QPATH ]; + char shader[ MAX_QPATH ]; + mesh_t m; + bspDrawVert_t *verts; + epair_t *ep; + vec4_t delta, delta2, delta3; + qboolean degenerate; + float longestCurve; + int maxIterations; + + + MatchToken( "{" ); + + /* get texture */ + GetToken( qtrue ); + strcpy( texture, token ); + + Parse1DMatrix( 5, info ); + m.width = info[0]; + m.height = info[1]; + m.verts = verts = safe_malloc( m.width * m.height * sizeof( m.verts[0] ) ); + + if( m.width < 0 || m.width > MAX_PATCH_SIZE || m.height < 0 || m.height > MAX_PATCH_SIZE ) + Error( "ParsePatch: bad size" ); + + MatchToken( "(" ); + for( j = 0; j < m.width ; j++ ) + { + MatchToken( "(" ); + for( i = 0; i < m.height ; i++ ) + { + Parse1DMatrix( 5, verts[ i * m.width + j ].xyz ); + + /* ydnar: fix colors */ + for( k = 0; k < MAX_LIGHTMAPS; k++ ) + { + verts[ i * m.width + j ].color[ k ][ 0 ] = 255; + verts[ i * m.width + j ].color[ k ][ 1 ] = 255; + verts[ i * m.width + j ].color[ k ][ 2 ] = 255; + verts[ i * m.width + j ].color[ k ][ 3 ] = 255; + } + } + MatchToken( ")" ); + } + MatchToken( ")" ); + + // if brush primitives format, we may have some epairs to ignore here + GetToken(qtrue); + if (g_bBrushPrimit!=BPRIMIT_OLDBRUSHES && strcmp(token,"}")) + { + // NOTE: we leak that! + ep = ParseEPair(); + } + else + UnGetToken(); + + MatchToken( "}" ); + MatchToken( "}" ); + + /* short circuit */ + if( noCurveBrushes || onlyLights ) + return; + + + /* ydnar: delete and warn about degenerate patches */ + j = (m.width * m.height); + VectorClear( delta ); + delta[ 3 ] = 0; + degenerate = qtrue; + + /* find first valid vector */ + for( i = 1; i < j && delta[ 3 ] == 0; i++ ) + { + VectorSubtract( m.verts[ 0 ].xyz, m.verts[ i ].xyz, delta ); + delta[ 3 ] = VectorNormalize( delta, delta ); + } + + /* secondary degenerate test */ + if( delta[ 3 ] == 0 ) + degenerate = qtrue; + else + { + /* if all vectors match this or are zero, then this is a degenerate patch */ + for( i = 1; i < j && degenerate == qtrue; i++ ) + { + VectorSubtract( m.verts[ 0 ].xyz, m.verts[ i ].xyz, delta2 ); + delta2[ 3 ] = VectorNormalize( delta2, delta2 ); + if( delta2[ 3 ] != 0 ) + { + /* create inverse vector */ + VectorCopy( delta2, delta3 ); + delta3[ 3 ] = delta2[ 3 ]; + VectorInverse( delta3 ); + + /* compare */ + if( VectorCompare( delta, delta2 ) == qfalse && VectorCompare( delta, delta3 ) == qfalse ) + degenerate = qfalse; + } + } + } + + /* warn and select degenerate patch */ + if( degenerate ) + { + xml_Select( "degenerate patch", mapEnt->mapEntityNum, entitySourceBrushes, qfalse ); + free( m.verts ); + return; + } + + /* find longest curve on the mesh */ + longestCurve = 0.0f; + maxIterations = 0; + for( j = 0; j + 2 < m.width; j += 2 ) + { + for( i = 0; i + 2 < m.height; i += 2 ) + { + ExpandLongestCurve( &longestCurve, verts[ i * m.width + j ].xyz, verts[ i * m.width + (j + 1) ].xyz, verts[ i * m.width + (j + 2) ].xyz ); /* row */ + ExpandLongestCurve( &longestCurve, verts[ i * m.width + j ].xyz, verts[ (i + 1) * m.width + j ].xyz, verts[ (i + 2) * m.width + j ].xyz ); /* col */ + ExpandMaxIterations( &maxIterations, patchSubdivisions, verts[ i * m.width + j ].xyz, verts[ i * m.width + (j + 1) ].xyz, verts[ i * m.width + (j + 2) ].xyz ); /* row */ + ExpandMaxIterations( &maxIterations, patchSubdivisions, verts[ i * m.width + j ].xyz, verts[ (i + 1) * m.width + j ].xyz, verts[ (i + 2) * m.width + j ].xyz ); /* col */ + } + } + + /* allocate patch mesh */ + pm = safe_malloc( sizeof( *pm ) ); + memset( pm, 0, sizeof( *pm ) ); + + /* ydnar: add entity/brush numbering */ + pm->entityNum = mapEnt->mapEntityNum; + pm->brushNum = entitySourceBrushes; + + /* set shader */ + sprintf( shader, "textures/%s", texture ); + pm->shaderInfo = ShaderInfoForShader( shader ); + + /* set mesh */ + pm->mesh = m; + + /* set longest curve */ + pm->longestCurve = longestCurve; + pm->maxIterations = maxIterations; + + /* link to the entity */ + pm->next = mapEnt->patches; + mapEnt->patches = pm; +} + + + +/* +GrowGroup_r() +recursively adds patches to a lod group +*/ + +static void GrowGroup_r( parseMesh_t *pm, int patchNum, int patchCount, parseMesh_t **meshes, byte *bordering, byte *group ) +{ + int i; + const byte *row; + + + /* early out check */ + if( group[ patchNum ] ) + return; + + + /* set it */ + group[ patchNum ] = 1; + row = bordering + patchNum * patchCount; + + /* check maximums */ + if( meshes[ patchNum ]->longestCurve > pm->longestCurve ) + pm->longestCurve = meshes[ patchNum ]->longestCurve; + if( meshes[ patchNum ]->maxIterations > pm->maxIterations ) + pm->maxIterations = meshes[ patchNum ]->maxIterations; + + /* walk other patches */ + for( i = 0; i < patchCount; i++ ) + { + if( row[ i ] ) + GrowGroup_r( pm, i, patchCount, meshes, bordering, group ); + } +} + + +/* +PatchMapDrawSurfs() +any patches that share an edge need to choose their +level of detail as a unit, otherwise the edges would +pull apart. +*/ + +void PatchMapDrawSurfs( entity_t *e ) +{ + int i, j, k, l, c1, c2; + parseMesh_t *pm; + parseMesh_t *check, *scan; + mapDrawSurface_t *ds; + int patchCount, groupCount; + bspDrawVert_t *v1, *v2; + vec3_t bounds[ 2 ]; + byte *bordering; + + /* ydnar: mac os x fails with these if not static */ + MAC_STATIC parseMesh_t *meshes[ MAX_MAP_DRAW_SURFS ]; + MAC_STATIC qb_t grouped[ MAX_MAP_DRAW_SURFS ]; + MAC_STATIC byte group[ MAX_MAP_DRAW_SURFS ]; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- PatchMapDrawSurfs ---\n" ); + + patchCount = 0; + for ( pm = e->patches ; pm ; pm = pm->next ) { + meshes[patchCount] = pm; + patchCount++; + } + + if ( !patchCount ) { + return; + } + bordering = safe_malloc( patchCount * patchCount ); + memset( bordering, 0, patchCount * patchCount ); + + // build the bordering matrix + for ( k = 0 ; k < patchCount ; k++ ) { + bordering[k*patchCount+k] = 1; + + for ( l = k+1 ; l < patchCount ; l++ ) { + check = meshes[k]; + scan = meshes[l]; + c1 = scan->mesh.width * scan->mesh.height; + v1 = scan->mesh.verts; + + for ( i = 0 ; i < c1 ; i++, v1++ ) { + c2 = check->mesh.width * check->mesh.height; + v2 = check->mesh.verts; + for ( j = 0 ; j < c2 ; j++, v2++ ) { + if ( fabs( v1->xyz[0] - v2->xyz[0] ) < 1.0 + && fabs( v1->xyz[1] - v2->xyz[1] ) < 1.0 + && fabs( v1->xyz[2] - v2->xyz[2] ) < 1.0 ) { + break; + } + } + if ( j != c2 ) { + break; + } + } + if ( i != c1 ) { + // we have a connection + bordering[k*patchCount+l] = + bordering[l*patchCount+k] = 1; + } else { + // no connection + bordering[k*patchCount+l] = + bordering[l*patchCount+k] = 0; + } + + } + } + + /* build groups */ + memset( grouped, 0, patchCount ); + groupCount = 0; + for ( i = 0; i < patchCount; i++ ) + { + /* get patch */ + scan = meshes[ i ]; + + /* start a new group */ + if( !grouped[ i ] ) + groupCount++; + + /* recursively find all patches that belong in the same group */ + memset( group, 0, patchCount ); + GrowGroup_r( scan, i, patchCount, meshes, bordering, group ); + + /* bound them */ + ClearBounds( bounds[ 0 ], bounds[ 1 ] ); + for( j = 0; j < patchCount; j++ ) + { + if ( group[ j ] ) + { + grouped[ j ] = qtrue; + check = meshes[ j ]; + c1 = check->mesh.width * check->mesh.height; + v1 = check->mesh.verts; + for( k = 0; k < c1; k++, v1++ ) + AddPointToBounds( v1->xyz, bounds[ 0 ], bounds[ 1 ] ); + } + } + + /* debug code */ + //% Sys_Printf( "Longest curve: %f Iterations: %d\n", scan->longestCurve, scan->maxIterations ); + + /* create drawsurf */ + scan->grouped = qtrue; + ds = DrawSurfaceForMesh( e, scan, NULL ); /* ydnar */ + VectorCopy( bounds[ 0 ], ds->bounds[ 0 ] ); + VectorCopy( bounds[ 1 ], ds->bounds[ 1 ] ); + } + + /* emit some statistics */ + Sys_FPrintf( SYS_VRB, "%9d patches\n", patchCount ); + Sys_FPrintf( SYS_VRB, "%9d patch LOD groups\n", groupCount ); +} + diff --git a/tools/quake3/q3map2/path_init.c b/tools/quake3/q3map2/path_init.c new file mode 100644 index 00000000..186102b0 --- /dev/null +++ b/tools/quake3/q3map2/path_init.c @@ -0,0 +1,462 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define PATH_INIT_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* path support */ +#define MAX_BASE_PATHS 10 +#define MAX_GAME_PATHS 10 + +char *homePath; +char installPath[ MAX_OS_PATH ]; + +int numBasePaths; +char *basePaths[ MAX_BASE_PATHS ]; +int numGamePaths; +char *gamePaths[ MAX_GAME_PATHS ]; + + + +/* +some of this code is based off the original q3map port from loki +and finds various paths. moved here from bsp.c for clarity. +*/ + +/* +PathLokiGetHomeDir() +gets the user's home dir (for ~/.q3a) +*/ + +char *LokiGetHomeDir( void ) +{ + #ifndef Q_UNIX + return NULL; + #else + char *home; + uid_t id; + struct passwd *pwd; + + + /* get the home environment variable */ + home = getenv( "HOME" ); + if( home == NULL ) + { + /* do some more digging */ + id = getuid(); + setpwent(); + while( (pwd = getpwent()) != NULL ) + { + if( pwd->pw_uid == id ) + { + home = pwd->pw_dir; + break; + } + } + endpwent(); + } + + /* return it */ + return home; + #endif +} + + + +/* +PathLokiInitPaths() +initializes some paths on linux/os x +*/ + +void LokiInitPaths( char *argv0 ) +{ + #ifndef Q_UNIX + /* this is kinda crap, but hey */ + strcpy( installPath, "../" ); + #else + char temp[ MAX_OS_PATH ]; + char *home; + char *path; + char *last; + qboolean found; + + + /* get home dir */ + home = LokiGetHomeDir(); + if( home == NULL ) + home = "."; + + /* do some path divining */ + strcpy( temp, argv0 ); + if( strrchr( temp, '/' ) ) + argv0 = strrchr( argv0, '/' ) + 1; + else + { + /* get path environment variable */ + path = getenv( "PATH" ); + + /* minor setup */ + last[ 0 ] = path[ 0 ]; + last[ 1 ] = '\0'; + found = qfalse; + + /* go through each : segment of path */ + while( last[ 0 ] != '\0' && found == qfalse ) + { + /* null out temp */ + temp[ 0 ] = '\0'; + + /* find next chunk */ + last = strchr( path, ':' ); + if( last == NULL ) + last = path + strlen( path ); + + /* found home dir candidate */ + if( *path == '~' ) + { + strcpy( temp, home ); + path++; + } + + /* concatenate */ + if( last > (path + 1) ) + { + strncat( temp, path, (last - path) ); + strcat( temp, "/" ); + } + strcat( temp, "./" ); + strcat( temp, argv0 ); + + /* verify the path */ + if( access( temp, X_OK ) == 0 ) + found++; + path = last + 1; + } + } + + /* flake */ + if( realpath( temp, installPath ) ) + { + /* q3map is in "tools/" */ + *(strrchr( installPath, '/' )) = '\0'; + *(strrchr( installPath, '/' ) + 1) = '\0'; + } + + /* set home path */ + homePath = home; + #endif +} + + + +/* +CleanPath() - ydnar +cleans a dos path \ -> / +*/ + +void CleanPath( char *path ) +{ + while( *path ) + { + if( *path == '\\' ) + *path = '/'; + path++; + } +} + + + +/* +GetGame() - ydnar +gets the game_t based on a -game argument +returns NULL if no match found +*/ + +game_t *GetGame( char *arg ) +{ + int i; + + + /* dummy check */ + if( arg == NULL || arg[ 0 ] == '\0' ) + return NULL; + + /* joke */ + if( !Q_stricmp( arg, "quake1" ) || + !Q_stricmp( arg, "quake2" ) || + !Q_stricmp( arg, "unreal" ) || + !Q_stricmp( arg, "ut2k3" ) || + !Q_stricmp( arg, "dn3d" ) || + !Q_stricmp( arg, "dnf" ) || + !Q_stricmp( arg, "hl" ) ) + { + Sys_Printf( "April fools, silly rabbit!\n" ); + exit( 0 ); + } + + /* test it */ + i = 0; + while( games[ i ].arg != NULL ) + { + if( Q_stricmp( arg, games[ i ].arg ) == 0 ) + return &games[ i ]; + i++; + } + + /* no matching game */ + return NULL; +} + + + +/* +AddBasePath() - ydnar +adds a base path to the list +*/ + +void AddBasePath( char *path ) +{ + /* dummy check */ + if( path == NULL || path[ 0 ] == '\0' || numBasePaths >= MAX_BASE_PATHS ) + return; + + /* add it to the list */ + basePaths[ numBasePaths ] = safe_malloc( strlen( path ) + 1 ); + strcpy( basePaths[ numBasePaths ], path ); + CleanPath( basePaths[ numBasePaths ] ); + numBasePaths++; +} + + + +/* +AddHomeBasePath() - ydnar +adds a base path to the beginning of the list, prefixed by ~/ +*/ + +void AddHomeBasePath( char *path ) +{ + #ifdef Q_UNIX + int i; + char temp[ MAX_OS_PATH ]; + + + /* dummy check */ + if( path == NULL || path[ 0 ] == '\0' ) + return; + + /* make a hole */ + for( i = 0; i < (MAX_BASE_PATHS - 1); i++ ) + basePaths[ i + 1 ] = basePaths[ i ]; + + /* concatenate home dir and path */ + sprintf( temp, "%s/%s", homePath, path ); + + /* add it to the list */ + basePaths[ 0 ] = safe_malloc( strlen( temp ) + 1 ); + strcpy( basePaths[ 0 ], temp ); + CleanPath( basePaths[ 0 ] ); + numBasePaths++; + #endif +} + + + +/* +AddGamePath() - ydnar +adds a game path to the list +*/ + +void AddGamePath( char *path ) +{ + /* dummy check */ + if( path == NULL || path[ 0 ] == '\0' || numGamePaths >= MAX_GAME_PATHS ) + return; + + /* add it to the list */ + gamePaths[ numGamePaths ] = safe_malloc( strlen( path ) + 1 ); + strcpy( gamePaths[ numGamePaths ], path ); + CleanPath( gamePaths[ numGamePaths ] ); + numGamePaths++; +} + + + + +/* +InitPaths() - ydnar +cleaned up some of the path initialization code from bsp.c +will remove any arguments it uses +*/ + +void InitPaths( int *argc, char **argv ) +{ + int i, j, k, len, len2; + char temp[ MAX_OS_PATH ]; + + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- InitPaths ---\n" ); + + /* get the install path for backup */ + LokiInitPaths( argv[ 0 ] ); + + /* set game to default (q3a) */ + game = &games[ 0 ]; + numBasePaths = 0; + numGamePaths = 0; + + /* parse through the arguments and extract those relevant to paths */ + for( i = 0; i < *argc; i++ ) + { + /* check for null */ + if( argv[ i ] == NULL ) + continue; + + /* -game */ + if( strcmp( argv[ i ], "-game" ) == 0 ) + { + if( ++i >= *argc ) + Error( "Out of arguments: No game specified after %s", argv[ i - 1 ] ); + argv[ i - 1 ] = NULL; + game = GetGame( argv[ i ] ); + if( game == NULL ) + game = &games[ 0 ]; + argv[ i ] = NULL; + } + + /* -fs_basepath */ + else if( strcmp( argv[ i ], "-fs_basepath" ) == 0 ) + { + if( ++i >= *argc ) + Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] ); + argv[ i - 1 ] = NULL; + AddBasePath( argv[ i ] ); + argv[ i ] = NULL; + } + + /* -fs_game */ + else if( strcmp( argv[ i ], "-fs_game" ) == 0 ) + { + if( ++i >= *argc ) + Error( "Out of arguments: No path specified after %s.", argv[ i - 1 ] ); + argv[ i - 1 ] = NULL; + AddGamePath( argv[ i ] ); + argv[ i ] = NULL; + } + } + + /* remove processed arguments */ + for( i = 0, j = 0, k = 0; i < *argc && j < *argc; i++, j++ ) + { + for( ; j < *argc && argv[ j ] == NULL; j++ ); + argv[ i ] = argv[ j ]; + if( argv[ i ] != NULL ) + k++; + } + *argc = k; + + /* add standard game path */ + AddGamePath( game->gamePath ); + + /* if there is no base path set, figure it out */ + if( numBasePaths == 0 ) + { + /* this is another crappy replacement for SetQdirFromPath() */ + len2 = strlen( game->magic ); + for( i = 0; i < *argc && numBasePaths == 0; i++ ) + { + /* extract the arg */ + strcpy( temp, argv[ i ] ); + CleanPath( temp ); + len = strlen( temp ); + Sys_FPrintf( SYS_VRB, "Searching for \"%s\" in \"%s\" (%d)...\n", game->magic, temp, i ); + + /* this is slow, but only done once */ + for( j = 0; j < (len - len2); j++ ) + { + /* check for the game's magic word */ + if( Q_strncasecmp( &temp[ j ], game->magic, len2 ) == 0 ) + { + /* now find the next slash and nuke everything after it */ + while( temp[ ++j ] != '/' && temp[ j ] != '\0' ); + temp[ j ] = '\0'; + + /* add this as a base path */ + AddBasePath( temp ); + break; + } + } + } + + /* add install path */ + if( numBasePaths == 0 ) + AddBasePath( installPath ); + + /* check again */ + if( numBasePaths == 0 ) + Error( "Failed to find a valid base path." ); + } + + /* this only affects unix */ + AddHomeBasePath( game->homeBasePath ); + + /* initialize vfs paths */ + if( numBasePaths > MAX_BASE_PATHS ) + numBasePaths = MAX_BASE_PATHS; + if( numGamePaths > MAX_GAME_PATHS ) + numGamePaths = MAX_GAME_PATHS; + + /* walk the list of game paths */ + for( j = 0; j < numGamePaths; j++ ) + { + /* walk the list of base paths */ + for( i = 0; i < numBasePaths; i++ ) + { + /* create a full path and initialize it */ + sprintf( temp, "%s/%s/", basePaths[ i ], gamePaths[ j ] ); + vfsInitDirectory( temp ); + } + } + + /* done */ + Sys_Printf( "\n" ); +} + + + + diff --git a/tools/quake3/q3map2/portals.c b/tools/quake3/q3map2/portals.c new file mode 100644 index 00000000..02209d54 --- /dev/null +++ b/tools/quake3/q3map2/portals.c @@ -0,0 +1,971 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define PORTALS_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* ydnar: to fix broken portal windings */ +extern qboolean FixWinding( winding_t *w ); + + +int c_active_portals; +int c_peak_portals; +int c_boundary; +int c_boundary_sides; + +/* +=========== +AllocPortal +=========== +*/ +portal_t *AllocPortal (void) +{ + portal_t *p; + + if (numthreads == 1) + c_active_portals++; + if (c_active_portals > c_peak_portals) + c_peak_portals = c_active_portals; + + p = safe_malloc (sizeof(portal_t)); + memset (p, 0, sizeof(portal_t)); + + return p; +} + +void FreePortal (portal_t *p) +{ + if (p->winding) + FreeWinding (p->winding); + if (numthreads == 1) + c_active_portals--; + free (p); +} + + + +/* +PortalPassable +returns true if the portal has non-opaque leafs on both sides +*/ + +qboolean PortalPassable( portal_t *p ) +{ + /* is this to global outside leaf? */ + if( !p->onnode ) + return qfalse; + + /* this should never happen */ + if( p->nodes[ 0 ]->planenum != PLANENUM_LEAF || + p->nodes[ 1 ]->planenum != PLANENUM_LEAF ) + Error( "Portal_EntityFlood: not a leaf" ); + + /* ydnar: added antiportal to supress portal generation for visibility blocking */ + if( p->compileFlags & C_ANTIPORTAL ) + return qfalse; + + /* both leaves on either side of the portal must be passable */ + if( p->nodes[ 0 ]->opaque == qfalse && p->nodes[ 1 ]->opaque == qfalse ) + return qtrue; + + /* otherwise this isn't a passable portal */ + return qfalse; +} + + + + +int c_tinyportals; +int c_badportals; /* ydnar */ + +/* +============= +AddPortalToNodes +============= +*/ +void AddPortalToNodes (portal_t *p, node_t *front, node_t *back) +{ + if (p->nodes[0] || p->nodes[1]) + Error ("AddPortalToNode: allready included"); + + p->nodes[0] = front; + p->next[0] = front->portals; + front->portals = p; + + p->nodes[1] = back; + p->next[1] = back->portals; + back->portals = p; +} + + +/* +============= +RemovePortalFromNode +============= +*/ +void RemovePortalFromNode (portal_t *portal, node_t *l) +{ + portal_t **pp, *t; + +// remove reference to the current portal + pp = &l->portals; + while (1) + { + t = *pp; + if (!t) + Error ("RemovePortalFromNode: portal not in leaf"); + + if ( t == portal ) + break; + + if (t->nodes[0] == l) + pp = &t->next[0]; + else if (t->nodes[1] == l) + pp = &t->next[1]; + else + Error ("RemovePortalFromNode: portal not bounding leaf"); + } + + if (portal->nodes[0] == l) + { + *pp = portal->next[0]; + portal->nodes[0] = NULL; + } + else if (portal->nodes[1] == l) + { + *pp = portal->next[1]; + portal->nodes[1] = NULL; + } +} + +//============================================================================ + +void PrintPortal (portal_t *p) +{ + int i; + winding_t *w; + + w = p->winding; + for (i=0 ; inumpoints ; i++) + Sys_Printf ("(%5.0f,%5.0f,%5.0f)\n",w->p[i][0] + , w->p[i][1], w->p[i][2]); +} + +/* +================ +MakeHeadnodePortals + +The created portals will face the global outside_node +================ +*/ +#define SIDESPACE 8 +void MakeHeadnodePortals (tree_t *tree) +{ + vec3_t bounds[2]; + int i, j, n; + portal_t *p, *portals[6]; + plane_t bplanes[6], *pl; + node_t *node; + + node = tree->headnode; + +// pad with some space so there will never be null volume leafs + for (i=0 ; i<3 ; i++) + { + bounds[0][i] = tree->mins[i] - SIDESPACE; + bounds[1][i] = tree->maxs[i] + SIDESPACE; + if ( bounds[0][i] >= bounds[1][i] ) { + Error( "Backwards tree volume" ); + } + } + + tree->outside_node.planenum = PLANENUM_LEAF; + tree->outside_node.brushlist = NULL; + tree->outside_node.portals = NULL; + tree->outside_node.opaque = qfalse; + + for (i=0 ; i<3 ; i++) + for (j=0 ; j<2 ; j++) + { + n = j*3 + i; + + p = AllocPortal (); + portals[n] = p; + + pl = &bplanes[n]; + memset (pl, 0, sizeof(*pl)); + if (j) + { + pl->normal[i] = -1; + pl->dist = -bounds[j][i]; + } + else + { + pl->normal[i] = 1; + pl->dist = bounds[j][i]; + } + p->plane = *pl; + p->winding = BaseWindingForPlane (pl->normal, pl->dist); + AddPortalToNodes (p, node, &tree->outside_node); + } + +// clip the basewindings by all the other planes + for (i=0 ; i<6 ; i++) + { + for (j=0 ; j<6 ; j++) + { + if (j == i) + continue; + ChopWindingInPlace (&portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON); + } + } +} + +//=================================================== + + +/* +================ +BaseWindingForNode +================ +*/ +#define BASE_WINDING_EPSILON 0.001 +#define SPLIT_WINDING_EPSILON 0.001 + +winding_t *BaseWindingForNode (node_t *node) +{ + winding_t *w; + node_t *n; + plane_t *plane; + vec3_t normal; + vec_t dist; + + w = BaseWindingForPlane (mapplanes[node->planenum].normal + , mapplanes[node->planenum].dist); + + // clip by all the parents + for (n=node->parent ; n && w ; ) + { + plane = &mapplanes[n->planenum]; + + if (n->children[0] == node) + { // take front + ChopWindingInPlace (&w, plane->normal, plane->dist, BASE_WINDING_EPSILON); + } + else + { // take back + VectorSubtract (vec3_origin, plane->normal, normal); + dist = -plane->dist; + ChopWindingInPlace (&w, normal, dist, BASE_WINDING_EPSILON); + } + node = n; + n = n->parent; + } + + return w; +} + +//============================================================ + +/* +================== +MakeNodePortal + +create the new portal by taking the full plane winding for the cutting plane +and clipping it by all of parents of this node +================== +*/ +void MakeNodePortal (node_t *node) +{ + portal_t *new_portal, *p; + winding_t *w; + vec3_t normal; + float dist; + int side; + + w = BaseWindingForNode (node); + + // clip the portal by all the other portals in the node + for (p = node->portals ; p && w; p = p->next[side]) + { + if (p->nodes[0] == node) + { + side = 0; + VectorCopy (p->plane.normal, normal); + dist = p->plane.dist; + } + else if (p->nodes[1] == node) + { + side = 1; + VectorSubtract (vec3_origin, p->plane.normal, normal); + dist = -p->plane.dist; + } + else + Error ("CutNodePortals_r: mislinked portal"); + + ChopWindingInPlace (&w, normal, dist, CLIP_EPSILON); + } + + if (!w) + { + return; + } + + + /* ydnar: adding this here to fix degenerate windings */ + #if 0 + if( FixWinding( w ) == qfalse ) + { + c_badportals++; + FreeWinding( w ); + return; + } + #endif + + if (WindingIsTiny (w)) + { + c_tinyportals++; + FreeWinding (w); + return; + } + + new_portal = AllocPortal (); + new_portal->plane = mapplanes[node->planenum]; + new_portal->onnode = node; + new_portal->winding = w; + new_portal->compileFlags = node->compileFlags; + AddPortalToNodes (new_portal, node->children[0], node->children[1]); +} + + +/* +============== +SplitNodePortals + +Move or split the portals that bound node so that the node's +children have portals instead of node. +============== +*/ +void SplitNodePortals (node_t *node) +{ + portal_t *p, *next_portal, *new_portal; + node_t *f, *b, *other_node; + int side; + plane_t *plane; + winding_t *frontwinding, *backwinding; + + plane = &mapplanes[node->planenum]; + f = node->children[0]; + b = node->children[1]; + + for (p = node->portals ; p ; p = next_portal) + { + if (p->nodes[0] == node) + side = 0; + else if (p->nodes[1] == node) + side = 1; + else + Error ("SplitNodePortals: mislinked portal"); + next_portal = p->next[side]; + + other_node = p->nodes[!side]; + RemovePortalFromNode (p, p->nodes[0]); + RemovePortalFromNode (p, p->nodes[1]); + +// +// cut the portal into two portals, one on each side of the cut plane +// + ClipWindingEpsilon (p->winding, plane->normal, plane->dist, + SPLIT_WINDING_EPSILON, &frontwinding, &backwinding); + + if (frontwinding && WindingIsTiny(frontwinding)) + { + if (!f->tinyportals) + VectorCopy(frontwinding->p[0], f->referencepoint); + f->tinyportals++; + if (!other_node->tinyportals) + VectorCopy(frontwinding->p[0], other_node->referencepoint); + other_node->tinyportals++; + + FreeWinding (frontwinding); + frontwinding = NULL; + c_tinyportals++; + } + + if (backwinding && WindingIsTiny(backwinding)) + { + if (!b->tinyportals) + VectorCopy(backwinding->p[0], b->referencepoint); + b->tinyportals++; + if (!other_node->tinyportals) + VectorCopy(backwinding->p[0], other_node->referencepoint); + other_node->tinyportals++; + + FreeWinding (backwinding); + backwinding = NULL; + c_tinyportals++; + } + + if (!frontwinding && !backwinding) + { // tiny windings on both sides + continue; + } + + if (!frontwinding) + { + FreeWinding (backwinding); + if (side == 0) + AddPortalToNodes (p, b, other_node); + else + AddPortalToNodes (p, other_node, b); + continue; + } + if (!backwinding) + { + FreeWinding (frontwinding); + if (side == 0) + AddPortalToNodes (p, f, other_node); + else + AddPortalToNodes (p, other_node, f); + continue; + } + + // the winding is split + new_portal = AllocPortal (); + *new_portal = *p; + new_portal->winding = backwinding; + FreeWinding (p->winding); + p->winding = frontwinding; + + if (side == 0) + { + AddPortalToNodes (p, f, other_node); + AddPortalToNodes (new_portal, b, other_node); + } + else + { + AddPortalToNodes (p, other_node, f); + AddPortalToNodes (new_portal, other_node, b); + } + } + + node->portals = NULL; +} + + +/* +================ +CalcNodeBounds +================ +*/ +void CalcNodeBounds (node_t *node) +{ + portal_t *p; + int s; + int i; + + // calc mins/maxs for both leafs and nodes + ClearBounds (node->mins, node->maxs); + for (p = node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + for (i=0 ; iwinding->numpoints ; i++) + AddPointToBounds (p->winding->p[i], node->mins, node->maxs); + } +} + +/* +================== +MakeTreePortals_r +================== +*/ +void MakeTreePortals_r (node_t *node) +{ + int i; + + CalcNodeBounds (node); + if (node->mins[0] >= node->maxs[0]) + { + Sys_Printf ("WARNING: node without a volume\n"); + Sys_Printf("node has %d tiny portals\n", node->tinyportals); + Sys_Printf("node reference point %1.2f %1.2f %1.2f\n", node->referencepoint[0], + node->referencepoint[1], + node->referencepoint[2]); + } + + for (i=0 ; i<3 ; i++) + { + if (node->mins[i] < MIN_WORLD_COORD || node->maxs[i] > MAX_WORLD_COORD) + { + if(node->portals && node->portals->winding) + xml_Winding("WARNING: Node With Unbounded Volume", node->portals->winding->p, node->portals->winding->numpoints, qfalse); + + break; + } + } + if (node->planenum == PLANENUM_LEAF) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + MakeTreePortals_r (node->children[0]); + MakeTreePortals_r (node->children[1]); +} + +/* +================== +MakeTreePortals +================== +*/ +void MakeTreePortals (tree_t *tree) +{ + Sys_FPrintf (SYS_VRB, "--- MakeTreePortals ---\n"); + MakeHeadnodePortals (tree); + MakeTreePortals_r (tree->headnode); + Sys_FPrintf( SYS_VRB, "%9d tiny portals\n", c_tinyportals ); + Sys_FPrintf( SYS_VRB, "%9d bad portals\n", c_badportals ); /* ydnar */ +} + +/* +========================================================= + +FLOOD ENTITIES + +========================================================= +*/ + +int c_floodedleafs; + +/* +============= +FloodPortals_r +============= +*/ + +void FloodPortals_r( node_t *node, int dist, qboolean skybox ) +{ + int s; + portal_t *p; + + + if( skybox ) + node->skybox = skybox; + + if( node->occupied || node->opaque ) + return; + + c_floodedleafs++; + node->occupied = dist; + + for( p = node->portals; p; p = p->next[ s ] ) + { + s = (p->nodes[ 1 ] == node); + FloodPortals_r( p->nodes[ !s ], dist + 1, skybox ); + } +} + + + +/* +============= +PlaceOccupant +============= +*/ + +qboolean PlaceOccupant( node_t *headnode, vec3_t origin, entity_t *occupant, qboolean skybox ) +{ + vec_t d; + node_t *node; + plane_t *plane; + + + // find the leaf to start in + node = headnode; + while( node->planenum != PLANENUM_LEAF ) + { + plane = &mapplanes[ node->planenum ]; + d = DotProduct( origin, plane->normal ) - plane->dist; + if( d >= 0 ) + node = node->children[ 0 ]; + else + node = node->children[ 1 ]; + } + + if( node->opaque ) + return qfalse; + node->occupant = occupant; + node->skybox = skybox; + + FloodPortals_r( node, 1, skybox ); + + return qtrue; +} + +/* +============= +FloodEntities + +Marks all nodes that can be reached by entites +============= +*/ + +qboolean FloodEntities( tree_t *tree ) +{ + int i, s; + vec3_t origin, offset, scale, angles; + qboolean r, inside, tripped, skybox; + node_t *headnode; + entity_t *e; + const char *value; + + + headnode = tree->headnode; + Sys_FPrintf( SYS_VRB,"--- FloodEntities ---\n" ); + inside = qfalse; + tree->outside_node.occupied = 0; + + tripped = qfalse; + c_floodedleafs = 0; + for( i = 1; i < numEntities; i++ ) + { + /* get entity */ + e = &entities[ i ]; + + /* get origin */ + GetVectorForKey( e, "origin", origin ); + if( VectorCompare( origin, vec3_origin ) ) + continue; + + /* handle skybox entities */ + value = ValueForKey( e, "classname" ); + if( !Q_stricmp( value, "_skybox" ) ) + { + skybox = qtrue; + skyboxPresent = qtrue; + + /* invert origin */ + VectorScale( origin, -1.0f, offset ); + + /* get scale */ + VectorSet( scale, 64.0f, 64.0f, 64.0f ); + value = ValueForKey( e, "_scale" ); + if( value[ 0 ] != '\0' ) + { + s = sscanf( value, "%f %f %f", &scale[ 0 ], &scale[ 1 ], &scale[ 2 ] ); + if( s == 1 ) + { + scale[ 1 ] = scale[ 0 ]; + scale[ 2 ] = scale[ 0 ]; + } + } + + /* get "angle" (yaw) or "angles" (pitch yaw roll) */ + VectorClear( angles ); + angles[ 2 ] = FloatForKey( e, "angle" ); + value = ValueForKey( e, "angles" ); + if( value[ 0 ] != '\0' ) + sscanf( value, "%f %f %f", &angles[ 1 ], &angles[ 2 ], &angles[ 0 ] ); + + /* set transform matrix (thanks spog) */ + m4x4_identity( skyboxTransform ); + m4x4_pivoted_transform_by_vec3( skyboxTransform, offset, angles, eXYZ, scale, origin ); + } + else + skybox = qfalse; + + /* nudge off floor */ + origin[ 2 ] += 1; + + /* debugging code */ + //% if( i == 1 ) + //% origin[ 2 ] += 4096; + + /* find leaf */ + r = PlaceOccupant( headnode, origin, e, skybox ); + if( r ) + inside = qtrue; + if( (!r || tree->outside_node.occupied) && !tripped ) + { + xml_Select( "Entity leaked", e->mapEntityNum, 0, qfalse ); + tripped = qtrue; + } + } + + Sys_FPrintf( SYS_VRB, "%9d flooded leafs\n", c_floodedleafs ); + + if( !inside ) + Sys_FPrintf( SYS_VRB, "no entities in open -- no filling\n" ); + else if( tree->outside_node.occupied ) + Sys_FPrintf( SYS_VRB, "entity reached from outside -- no filling\n" ); + + return (qboolean) (inside && !tree->outside_node.occupied); +} + +/* +========================================================= + +FLOOD AREAS + +========================================================= +*/ + +int c_areas; + + + +/* +FloodAreas_r() +floods through leaf portals to tag leafs with an area +*/ + +void FloodAreas_r( node_t *node ) +{ + int s; + portal_t *p; + brush_t *b; + + + if( node->areaportal ) + { + if( node->area == -1 ) + node->area = c_areas; + + /* this node is part of an area portal brush */ + b = node->brushlist->original; + + /* if the current area has already touched this portal, we are done */ + if( b->portalareas[ 0 ] == c_areas || b->portalareas[ 1 ] == c_areas ) + return; + + // note the current area as bounding the portal + if( b->portalareas[ 1 ] != -1 ) + { + Sys_Printf( "WARNING: areaportal brush %i touches > 2 areas\n", b->brushNum ); + return; + } + if( b->portalareas[ 0 ] != -1 ) + b->portalareas[ 1 ] = c_areas; + else + b->portalareas[ 0 ] = c_areas; + + return; + } + + if( node->area != -1 ) + return; + if( node->cluster == -1 ) + return; + + node->area = c_areas; + + /* ydnar: skybox nodes set the skybox area */ + if( node->skybox ) + skyboxArea = c_areas; + + for( p = node->portals; p; p = p->next[ s ] ) + { + s = (p->nodes[1] == node); + + /* ydnar: allow areaportal portals to block area flow */ + if( p->compileFlags & C_AREAPORTAL ) + continue; + + if( !PortalPassable( p ) ) + continue; + + FloodAreas_r( p->nodes[ !s ] ); + } +} + +/* +============= +FindAreas_r + +Just decend the tree, and for each node that hasn't had an +area set, flood fill out from there +============= +*/ +void FindAreas_r( node_t *node ) +{ + if( node->planenum != PLANENUM_LEAF ) + { + FindAreas_r( node->children[ 0 ] ); + FindAreas_r( node->children[ 1 ] ); + return; + } + + if( node->opaque || node->areaportal || node->area != -1 ) + return; + + FloodAreas_r( node ); + c_areas++; +} + +/* +============= +CheckAreas_r +============= +*/ +void CheckAreas_r (node_t *node) +{ + brush_t *b; + + if (node->planenum != PLANENUM_LEAF) + { + CheckAreas_r (node->children[0]); + CheckAreas_r (node->children[1]); + return; + } + + if (node->opaque) + return; + + if (node->cluster != -1) + if (node->area == -1) + Sys_Printf("WARNING: cluster %d has area set to -1\n", node->cluster); + if (node->areaportal) + { + b = node->brushlist->original; + + // check if the areaportal touches two areas + if (b->portalareas[0] == -1 || b->portalareas[1] == -1) + Sys_Printf ("WARNING: areaportal brush %i doesn't touch two areas\n", b->brushNum); + } +} + + + +/* +FloodSkyboxArea_r() - ydnar +sets all nodes with the skybox area to skybox +*/ + +void FloodSkyboxArea_r( node_t *node ) +{ + if( skyboxArea < 0 ) + return; + + if( node->planenum != PLANENUM_LEAF ) + { + FloodSkyboxArea_r( node->children[ 0 ] ); + FloodSkyboxArea_r( node->children[ 1 ] ); + return; + } + + if( node->opaque || node->area != skyboxArea ) + return; + + node->skybox = qtrue; +} + + + +/* +FloodAreas() +mark each leaf with an area, bounded by C_AREAPORTAL +*/ + +void FloodAreas( tree_t *tree ) +{ + Sys_FPrintf( SYS_VRB,"--- FloodAreas ---\n" ); + FindAreas_r( tree->headnode ); + + /* ydnar: flood all skybox nodes */ + FloodSkyboxArea_r( tree->headnode ); + + /* check for areaportal brushes that don't touch two areas */ + /* ydnar: fix this rather than just silence the warnings */ + //% CheckAreas_r( tree->headnode ); + + Sys_FPrintf( SYS_VRB, "%9d areas\n", c_areas ); +} + + + +//====================================================== + +int c_outside; +int c_inside; +int c_solid; + +void FillOutside_r (node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + FillOutside_r (node->children[0]); + FillOutside_r (node->children[1]); + return; + } + + // anything not reachable by an entity + // can be filled away + if (!node->occupied) { + if ( !node->opaque ) { + c_outside++; + node->opaque = qtrue; + } else { + c_solid++; + } + } else { + c_inside++; + } + +} + +/* +============= +FillOutside + +Fill all nodes that can't be reached by entities +============= +*/ +void FillOutside (node_t *headnode) +{ + c_outside = 0; + c_inside = 0; + c_solid = 0; + Sys_FPrintf( SYS_VRB,"--- FillOutside ---\n" ); + FillOutside_r( headnode ); + Sys_FPrintf( SYS_VRB,"%9d solid leafs\n", c_solid ); + Sys_Printf( "%9d leafs filled\n", c_outside ); + Sys_FPrintf( SYS_VRB, "%9d inside leafs\n", c_inside ); +} + + +//============================================================== + diff --git a/tools/quake3/q3map2/prtfile.c b/tools/quake3/q3map2/prtfile.c new file mode 100644 index 00000000..6f36f758 --- /dev/null +++ b/tools/quake3/q3map2/prtfile.c @@ -0,0 +1,292 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define PRTFILE_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* +============================================================================== + +PORTAL FILE GENERATION + +Save out name.prt for qvis to read +============================================================================== +*/ + + +#define PORTALFILE "PRT1" + +FILE *pf; +int num_visclusters; // clusters the player can be in +int num_visportals; +int num_solidfaces; + +void WriteFloat (FILE *f, vec_t v) +{ + if ( fabs(v - Q_rint(v)) < 0.001 ) + fprintf (f,"%i ",(int)Q_rint(v)); + else + fprintf (f,"%f ",v); +} + +/* +================= +WritePortalFile_r +================= +*/ +void WritePortalFile_r (node_t *node) +{ + int i, s; + portal_t *p; + winding_t *w; + vec3_t normal; + vec_t dist; + + // decision node + if (node->planenum != PLANENUM_LEAF) { + WritePortalFile_r (node->children[0]); + WritePortalFile_r (node->children[1]); + return; + } + + if (node->opaque) { + return; + } + + for (p = node->portals ; p ; p=p->next[s]) + { + w = p->winding; + s = (p->nodes[1] == node); + if (w && p->nodes[0] == node) + { + if (!PortalPassable(p)) + continue; + // write out to the file + + // sometimes planes get turned around when they are very near + // the changeover point between different axis. interpret the + // plane the same way vis will, and flip the side orders if needed + // FIXME: is this still relevent? + WindingPlane (w, normal, &dist); + if ( DotProduct (p->plane.normal, normal) < 0.99 ) + { // backwards... + fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster); + } + else + fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster); + + /* ydnar: added this change to make antiportals work */ + if( p->compileFlags & C_HINT ) + fprintf( pf, "1 " ); + else + fprintf( pf, "0 " ); + + /* write the winding */ + for (i=0 ; inumpoints ; i++) + { + fprintf (pf,"("); + WriteFloat (pf, w->p[i][0]); + WriteFloat (pf, w->p[i][1]); + WriteFloat (pf, w->p[i][2]); + fprintf (pf,") "); + } + fprintf (pf,"\n"); + } + } + +} + +/* +================= +WriteFaceFile_r +================= +*/ +void WriteFaceFile_r (node_t *node) +{ + int i, s; + portal_t *p; + winding_t *w; + + // decision node + if (node->planenum != PLANENUM_LEAF) { + WriteFaceFile_r (node->children[0]); + WriteFaceFile_r (node->children[1]); + return; + } + + if (node->opaque) { + return; + } + + for (p = node->portals ; p ; p=p->next[s]) + { + w = p->winding; + s = (p->nodes[1] == node); + if (w) + { + if (PortalPassable(p)) + continue; + // write out to the file + + if (p->nodes[0] == node) + { + fprintf (pf,"%i %i ",w->numpoints, p->nodes[0]->cluster); + for (i=0 ; inumpoints ; i++) + { + fprintf (pf,"("); + WriteFloat (pf, w->p[i][0]); + WriteFloat (pf, w->p[i][1]); + WriteFloat (pf, w->p[i][2]); + fprintf (pf,") "); + } + fprintf (pf,"\n"); + } + else + { + fprintf (pf,"%i %i ",w->numpoints, p->nodes[1]->cluster); + for (i = w->numpoints-1; i >= 0; i--) + { + fprintf (pf,"("); + WriteFloat (pf, w->p[i][0]); + WriteFloat (pf, w->p[i][1]); + WriteFloat (pf, w->p[i][2]); + fprintf (pf,") "); + } + fprintf (pf,"\n"); + } + } + } +} + +/* +================ +NumberLeafs_r +================ +*/ +void NumberLeafs_r (node_t *node) +{ + portal_t *p; + + if ( node->planenum != PLANENUM_LEAF ) { + // decision node + node->cluster = -99; + NumberLeafs_r (node->children[0]); + NumberLeafs_r (node->children[1]); + return; + } + + node->area = -1; + + if ( node->opaque ) { + // solid block, viewpoint never inside + node->cluster = -1; + return; + } + + node->cluster = num_visclusters; + num_visclusters++; + + // count the portals + for (p = node->portals ; p ; ) + { + if (p->nodes[0] == node) // only write out from first leaf + { + if (PortalPassable(p)) + num_visportals++; + else + num_solidfaces++; + p = p->next[0]; + } + else + { + if (!PortalPassable(p)) + num_solidfaces++; + p = p->next[1]; + } + } +} + + +/* +================ +NumberClusters +================ +*/ +void NumberClusters(tree_t *tree) { + num_visclusters = 0; + num_visportals = 0; + num_solidfaces = 0; + + Sys_FPrintf (SYS_VRB,"--- NumberClusters ---\n"); + + // set the cluster field in every leaf and count the total number of portals + NumberLeafs_r (tree->headnode); + + Sys_FPrintf( SYS_VRB, "%9d visclusters\n", num_visclusters ); + Sys_FPrintf( SYS_VRB, "%9d visportals\n", num_visportals ); + Sys_FPrintf( SYS_VRB, "%9d solidfaces\n", num_solidfaces ); +} + +/* +================ +WritePortalFile +================ +*/ +void WritePortalFile (tree_t *tree) +{ + char filename[1024]; + + Sys_FPrintf (SYS_VRB,"--- WritePortalFile ---\n"); + + // write the file + sprintf (filename, "%s.prt", source); + Sys_Printf ("writing %s\n", filename); + pf = fopen (filename, "w"); + if (!pf) + Error ("Error opening %s", filename); + + fprintf (pf, "%s\n", PORTALFILE); + fprintf (pf, "%i\n", num_visclusters); + fprintf (pf, "%i\n", num_visportals); + fprintf (pf, "%i\n", num_solidfaces); + + WritePortalFile_r(tree->headnode); + WriteFaceFile_r(tree->headnode); + + fclose (pf); +} + diff --git a/tools/quake3/q3map2/q3map2.h b/tools/quake3/q3map2/q3map2.h new file mode 100644 index 00000000..45bb9869 --- /dev/null +++ b/tools/quake3/q3map2/q3map2.h @@ -0,0 +1,2352 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#ifndef Q3MAP2_H +#define Q3MAP2_H + + + +/* version */ +#define Q3MAP_VERSION "2.5.17" +#define Q3MAP_MOTD "Last one turns the lights off" + + + +/* ------------------------------------------------------------------------------- + +dependencies + +------------------------------------------------------------------------------- */ + +/* platform-specific */ +#if defined( __linux__ ) || defined( __APPLE__ ) + #define Q_UNIX +#endif + +#ifdef Q_UNIX + #include + #include + #include +#endif + +#ifdef WIN32 + #include +#endif + + +/* general */ +#include "version.h" /* ttimo: might want to guard that if built outside of the GtkRadiant tree */ + +#include "cmdlib.h" +#include "mathlib.h" +#include "md5lib.h" +#include "ddslib.h" + +#include "picomodel.h" + +#include "scriplib.h" +#include "polylib.h" +#include "imagelib.h" +#include "qthreads.h" +#include "inout.h" +#include "vfs.h" +#include "png.h" +#include "mhash.h" + +#include + + + +/* ------------------------------------------------------------------------------- + +port-related hacks + +------------------------------------------------------------------------------- */ + +#define MAC_STATIC_HACK 0 +#if defined( __APPLE__ ) && MAC_STATIC_HACK + #define MAC_STATIC static +#else + #define MAC_STATIC +#endif + +#if 1 + #ifdef WIN32 + #define Q_stricmp stricmp + #define Q_strncasecmp strnicmp + #else + #define Q_stricmp strcasecmp + #define Q_strncasecmp strncasecmp + #endif +#endif + +/* macro version */ +#define VectorMA( a, s, b, c ) ((c)[ 0 ] = (a)[ 0 ] + (s) * (b)[ 0 ], (c)[ 1 ] = (a)[ 1 ] + (s) * (b)[ 1 ], (c)[ 2 ] = (a)[ 2 ] + (s) * (b)[ 2 ]) + + + +/* ------------------------------------------------------------------------------- + +constants + +------------------------------------------------------------------------------- */ + +/* general */ +#define MAX_QPATH 64 + +#define MAX_IMAGES 512 +#define DEFAULT_IMAGE "*default" + +#define MAX_MODELS 512 + +#define DEF_BACKSPLASH_FRACTION 0.05f /* 5% backsplash by default */ +#define DEF_BACKSPLASH_DISTANCE 23 + +#define DEF_RADIOSITY_BOUNCE 1.0f /* ydnar: default to 100% re-emitted light */ + +#define MAX_SHADER_INFO 8192 +#define MAX_CUST_SURFACEPARMS 64 + +#define SHADER_MAX_VERTEXES 1000 +#define SHADER_MAX_INDEXES (6 * SHADER_MAX_VERTEXES) + +#define MAX_JITTERS 256 + + +/* epair parsing (note case-sensitivity directive) */ +#define CASE_INSENSITIVE_EPAIRS 1 + +#if CASE_INSENSITIVE_EPAIRS + #define EPAIR_STRCMP Q_stricmp +#else + #define EPAIR_STRCMP strcmp +#endif + + +/* ydnar: compiler flags, because games have widely varying content/surface flags */ +#define C_SOLID 0x00000001 +#define C_TRANSLUCENT 0x00000002 +#define C_STRUCTURAL 0x00000004 +#define C_HINT 0x00000008 +#define C_NODRAW 0x00000010 +#define C_LIGHTGRID 0x00000020 +#define C_ALPHASHADOW 0x00000040 +#define C_LIGHTFILTER 0x00000080 +#define C_VERTEXLIT 0x00000100 +#define C_LIQUID 0x00000200 +#define C_FOG 0x00000400 +#define C_SKY 0x00000800 +#define C_ORIGIN 0x00001000 +#define C_AREAPORTAL 0x00002000 +#define C_ANTIPORTAL 0x00004000 /* like hint, but doesn't generate portals */ +#define C_SKIP 0x00008000 /* like hint, but skips this face (doesn't split bsp) */ +#define C_NOMARKS 0x00010000 /* no decals */ +#define C_DETAIL 0x08000000 /* THIS MUST BE THE SAME AS IN RADIANT! */ + + +/* shadow flags */ +#define WORLDSPAWN_CAST_SHADOWS 1 +#define WORLDSPAWN_RECV_SHADOWS 1 +#define ENTITY_CAST_SHADOWS 0 +#define ENTITY_RECV_SHADOWS 1 + + +/* bsp */ +#define MAX_PATCH_SIZE 32 +#define MAX_BRUSH_SIDES 1024 +#define MAX_BUILD_SIDES 300 + +#define MAX_EXPANDED_AXIS 128 + +#define CLIP_EPSILON 0.1f +#define PLANESIDE_EPSILON 0.001f +#define PLANENUM_LEAF -1 + +#define HINT_PRIORITY 1000 /* ydnar: force hint splits first and antiportal/areaportal splits last */ +#define ANTIPORTAL_PRIORITY -1000 +#define AREAPORTAL_PRIORITY -1000 + +#define PSIDE_FRONT 1 +#define PSIDE_BACK 2 +#define PSIDE_BOTH (PSIDE_FRONT | PSIDE_BACK) +#define PSIDE_FACING 4 + +#define BPRIMIT_UNDEFINED 0 +#define BPRIMIT_OLDBRUSHES 1 +#define BPRIMIT_NEWBRUSHES 2 + + +/* vis */ +#define VIS_HEADER_SIZE 8 + +#define SEPERATORCACHE /* seperator caching helps a bit */ + +#define PORTALFILE "PRT1" + +#define MAX_PORTALS 32768 +#define MAX_SEPERATORS MAX_POINTS_ON_WINDING +#define MAX_POINTS_ON_FIXED_WINDING 24 /* ydnar: increased this from 12 at the expense of more memory */ +#define MAX_PORTALS_ON_LEAF 128 + + +/* light */ +#define EMIT_POINT 0 +#define EMIT_AREA 1 +#define EMIT_SPOT 2 +#define EMIT_SUN 3 + +#define LIGHT_ATTEN_LINEAR 1 +#define LIGHT_ATTEN_ANGLE 2 +#define LIGHT_ATTEN_DISTANCE 4 +#define LIGHT_TWOSIDED 8 +#define LIGHT_GRID 16 +#define LIGHT_SURFACES 32 +#define LIGHT_DARK 64 /* probably never use this */ +#define LIGHT_FAST 256 +#define LIGHT_FAST_TEMP 512 +#define LIGHT_FAST_ACTUAL (LIGHT_FAST | LIGHT_FAST_TEMP) +#define LIGHT_NEGATIVE 1024 + +#define LIGHT_SUN_DEFAULT (LIGHT_ATTEN_ANGLE | LIGHT_GRID | LIGHT_SURFACES) +#define LIGHT_AREA_DEFAULT (LIGHT_ATTEN_ANGLE | LIGHT_ATTEN_DISTANCE | LIGHT_GRID | LIGHT_SURFACES) /* q3a and wolf are the same */ +#define LIGHT_Q3A_DEFAULT (LIGHT_ATTEN_ANGLE | LIGHT_ATTEN_DISTANCE | LIGHT_GRID | LIGHT_SURFACES | LIGHT_FAST) +#define LIGHT_WOLF_DEFAULT (LIGHT_ATTEN_LINEAR | LIGHT_ATTEN_DISTANCE | LIGHT_GRID | LIGHT_SURFACES | LIGHT_FAST) + +#define MAX_TRACE_TEST_NODES 256 +#define DEFAULT_INHIBIT_RADIUS 1.5f + +#define LUXEL_EPSILON 0.125f +#define VERTEX_EPSILON -0.125f +#define GRID_EPSILON 0.0f + +#define DEFAULT_LIGHTMAP_SAMPLE_SIZE 16 +#define DEFAULT_LIGHTMAP_SAMPLE_OFFSET 1.0f +#define DEFAULT_SUBDIVIDE_THRESHOLD 1.0f + +#define EXTRA_SCALE 2 /* -extrawide = -super 2 */ +#define EXTRAWIDE_SCALE 2 /* -extrawide = -super 2 -filter */ + +#define CLUSTER_UNMAPPED -1 +#define CLUSTER_OCCLUDED -2 +#define CLUSTER_FLOODED -3 + +#define VERTEX_LUXEL_SIZE 3 +#define BSP_LUXEL_SIZE 3 +#define RAD_LUXEL_SIZE 3 +#define SUPER_LUXEL_SIZE 4 +#define SUPER_ORIGIN_SIZE 3 +#define SUPER_NORMAL_SIZE 4 +#define SUPER_DELUXEL_SIZE 3 +#define BSP_DELUXEL_SIZE 3 +#define SUPER_FLOODLIGHT_SIZE 1 + +#define VERTEX_LUXEL( s, v ) (vertexLuxels[ s ] + ((v) * VERTEX_LUXEL_SIZE)) +#define RAD_VERTEX_LUXEL( s, v )(radVertexLuxels[ s ] + ((v) * VERTEX_LUXEL_SIZE)) +#define BSP_LUXEL( s, x, y ) (lm->bspLuxels[ s ] + ((((y) * lm->w) + (x)) * BSP_LUXEL_SIZE)) +#define RAD_LUXEL( s, x, y ) (lm->radLuxels[ s ] + ((((y) * lm->w) + (x)) * RAD_LUXEL_SIZE)) +#define SUPER_LUXEL( s, x, y ) (lm->superLuxels[ s ] + ((((y) * lm->sw) + (x)) * SUPER_LUXEL_SIZE)) +#define SUPER_DELUXEL( x, y ) (lm->superDeluxels + ((((y) * lm->sw) + (x)) * SUPER_DELUXEL_SIZE)) +#define BSP_DELUXEL( x, y ) (lm->bspDeluxels + ((((y) * lm->w) + (x)) * BSP_DELUXEL_SIZE)) +#define SUPER_CLUSTER( x, y ) (lm->superClusters + (((y) * lm->sw) + (x))) +#define SUPER_ORIGIN( x, y ) (lm->superOrigins + ((((y) * lm->sw) + (x)) * SUPER_ORIGIN_SIZE)) +#define SUPER_NORMAL( x, y ) (lm->superNormals + ((((y) * lm->sw) + (x)) * SUPER_NORMAL_SIZE)) +#define SUPER_DIRT( x, y ) (lm->superNormals + ((((y) * lm->sw) + (x)) * SUPER_NORMAL_SIZE) + 3) /* stash dirtyness in normal[ 3 ] */ +#define SUPER_FLOODLIGHT( x, y ) (lm->superFloodLight + ((((y) * lm->sw) + (x)) * SUPER_FLOODLIGHT_SIZE) ) + + + +/* ------------------------------------------------------------------------------- + +abstracted bsp file + +------------------------------------------------------------------------------- */ + +#define EXTERNAL_LIGHTMAP "lm_%04d.tga" + +#define MAX_LIGHTMAPS 4 /* RBSP */ +#define MAX_LIGHT_STYLES 64 +#define MAX_SWITCHED_LIGHTS 32 +#define LS_NORMAL 0x00 +#define LS_UNUSED 0xFE +#define LS_NONE 0xFF + +#define MAX_LIGHTMAP_SHADERS 256 + +/* ok to increase these at the expense of more memory */ +#define MAX_MAP_MODELS 0x400 +#define MAX_MAP_BRUSHES 0x8000 +#define MAX_MAP_ENTITIES 0x1000 //% 0x800 /* ydnar */ +#define MAX_MAP_ENTSTRING 0x80000 //% 0x40000 /* ydnar */ +#define MAX_MAP_SHADERS 0x800 //% 0x400 /* ydnar */ + +#define MAX_MAP_AREAS 0x100 /* MAX_MAP_AREA_BYTES in q_shared must match! */ +#define MAX_MAP_FOGS 30 //& 0x100 /* RBSP (32 - world fog - goggles) */ +#define MAX_MAP_PLANES 0x100000 //% 0x20000 /* ydnar for md */ +#define MAX_MAP_NODES 0x20000 +#define MAX_MAP_BRUSHSIDES 0x100000 //% 0x20000 /* ydnar */ +#define MAX_MAP_LEAFS 0x20000 +#define MAX_MAP_LEAFFACES 0x100000 //% 0x20000 /* ydnar */ +#define MAX_MAP_LEAFBRUSHES 0x40000 +#define MAX_MAP_PORTALS 0x20000 +#define MAX_MAP_LIGHTING 0x800000 +#define MAX_MAP_LIGHTGRID 0x100000 //% 0x800000 /* ydnar: set to points, not bytes */ +#define MAX_MAP_VISIBILITY 0x200000 + +#define MAX_MAP_DRAW_SURFS 0x20000 +#define MAX_MAP_DRAW_VERTS 0x80000 +#define MAX_MAP_DRAW_INDEXES 0x80000 + +#define MAX_MAP_ADVERTISEMENTS 30 + +/* key / value pair sizes in the entities lump */ +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +/* the editor uses these predefined yaw angles to orient entities up or down */ +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +#define LIGHTMAP_WIDTH 128 +#define LIGHTMAP_HEIGHT 128 + +#define MIN_WORLD_COORD (-65536) +#define MAX_WORLD_COORD (65536) +#define WORLD_SIZE (MAX_WORLD_COORD - MIN_WORLD_COORD) + + +typedef void (*bspFunc)( const char * ); + + +typedef struct +{ + int offset, length; +} +bspLump_t; + + +typedef struct +{ + char ident[ 4 ]; + int version; + + bspLump_t lumps[ 100 ]; /* theoretical maximum # of bsp lumps */ +} +bspHeader_t; + + +typedef struct +{ + float mins[ 3 ], maxs[ 3 ]; + int firstBSPSurface, numBSPSurfaces; + int firstBSPBrush, numBSPBrushes; +} +bspModel_t; + + +typedef struct +{ + char shader[ MAX_QPATH ]; + int surfaceFlags; + int contentFlags; +} +bspShader_t; + + +/* planes x^1 is allways the opposite of plane x */ + +typedef struct +{ + float normal[ 3 ]; + float dist; +} +bspPlane_t; + + +typedef struct +{ + int planeNum; + int children[ 2 ]; /* negative numbers are -(leafs+1), not nodes */ + int mins[ 3 ]; /* for frustom culling */ + int maxs[ 3 ]; +} +bspNode_t; + + +typedef struct +{ + int cluster; /* -1 = opaque cluster (do I still store these?) */ + int area; + + int mins[ 3 ]; /* for frustum culling */ + int maxs[ 3 ]; + + int firstBSPLeafSurface; + int numBSPLeafSurfaces; + + int firstBSPLeafBrush; + int numBSPLeafBrushes; +} +bspLeaf_t; + + +typedef struct +{ + int planeNum; /* positive plane side faces out of the leaf */ + int shaderNum; + int surfaceNum; /* RBSP */ +} +bspBrushSide_t; + + +typedef struct +{ + int firstSide; + int numSides; + int shaderNum; /* the shader that determines the content flags */ +} +bspBrush_t; + + +typedef struct +{ + char shader[ MAX_QPATH ]; + int brushNum; + int visibleSide; /* the brush side that ray tests need to clip against (-1 == none) */ +} +bspFog_t; + + +typedef struct +{ + vec3_t xyz; + float st[ 2 ]; + float lightmap[ MAX_LIGHTMAPS ][ 2 ]; /* RBSP */ + vec3_t normal; + byte color[ MAX_LIGHTMAPS ][ 4 ]; /* RBSP */ +} +bspDrawVert_t; + + +typedef enum +{ + MST_BAD, + MST_PLANAR, + MST_PATCH, + MST_TRIANGLE_SOUP, + MST_FLARE, + MST_FOLIAGE +} +bspSurfaceType_t; + + +typedef struct bspGridPoint_s +{ + byte ambient[ MAX_LIGHTMAPS ][ 3 ]; + byte directed[ MAX_LIGHTMAPS ][ 3 ]; + byte styles[ MAX_LIGHTMAPS ]; + byte latLong[ 2 ]; +} +bspGridPoint_t; + + +typedef struct +{ + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + byte lightmapStyles[ MAX_LIGHTMAPS ]; /* RBSP */ + byte vertexStyles[ MAX_LIGHTMAPS ]; /* RBSP */ + int lightmapNum[ MAX_LIGHTMAPS ]; /* RBSP */ + int lightmapX[ MAX_LIGHTMAPS ], lightmapY[ MAX_LIGHTMAPS ]; /* RBSP */ + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[ 3 ]; /* on patches, [ 0 ] and [ 1 ] are lodbounds */ + + int patchWidth; + int patchHeight; +} +bspDrawSurface_t; + + +/* advertisements */ +typedef struct { + int cellId; + vec3_t normal; + vec3_t rect[4]; + char model[ MAX_QPATH ]; +} bspAdvertisement_t; + + +/* ------------------------------------------------------------------------------- + +general types + +------------------------------------------------------------------------------- */ + +/* ydnar: for smaller structs */ +typedef char qb_t; + + +/* ydnar: for q3map_tcMod */ +typedef float tcMod_t[ 3 ][ 3 ]; + + +/* ydnar: for multiple game support */ +typedef struct surfaceParm_s +{ + char *name; + int contentFlags, contentFlagsClear; + int surfaceFlags, surfaceFlagsClear; + int compileFlags, compileFlagsClear; +} +surfaceParm_t; + + +typedef struct game_s +{ + char *arg; /* -game matches this */ + char *gamePath; /* main game data dir */ + char *homeBasePath; /* home sub-dir on unix */ + char *magic; /* magic word for figuring out base path */ + char *shaderPath; /* shader directory */ + int maxLMSurfaceVerts; /* default maximum meta surface verts */ + int maxSurfaceVerts; /* default maximum surface verts */ + int maxSurfaceIndexes; /* default maximum surface indexes (tris * 3) */ + qboolean emitFlares; /* when true, emit flare surfaces */ + char *flareShader; /* default flare shader (MUST BE SET) */ + qboolean wolfLight; /* when true, lights work like wolf q3map */ + int lightmapSize; /* bsp lightmap width/height */ + float lightmapGamma; /* default lightmap gamma */ + float lightmapExposure; /* default lightmap exposure */ + float lightmapCompensate; /* default lightmap compensate value */ + char *bspIdent; /* 4-letter bsp file prefix */ + int bspVersion; /* bsp version to use */ + qboolean lumpSwap; /* cod-style len/ofs order */ + bspFunc load, write; /* load/write function pointers */ + surfaceParm_t surfaceParms[ 128 ]; /* surfaceparm array */ +} +game_t; + + +typedef struct image_s +{ + char *name, *filename; + int refCount; + int width, height; + byte *pixels; +} +image_t; + + +typedef struct sun_s +{ + struct sun_s *next; + vec3_t direction, color; + float photons, deviance, filterRadius; + int numSamples, style; +} +sun_t; + + +typedef struct surfaceModel_s +{ + struct surfaceModel_s *next; + char model[ MAX_QPATH ]; + float density, odds; + float minScale, maxScale; + float minAngle, maxAngle; + qboolean oriented; +} +surfaceModel_t; + + +/* ydnar/sd: foliage stuff for wolf et (engine-supported optimization of the above) */ +typedef struct foliage_s +{ + struct foliage_s *next; + char model[ MAX_QPATH ]; + float scale, density, odds; + qboolean inverseAlpha; +} +foliage_t; + +typedef struct foliageInstance_s +{ + vec3_t xyz, normal; +} +foliageInstance_t; + + +typedef struct remap_s +{ + struct remap_s *next; + char from[ 1024 ]; + char to[ MAX_QPATH ]; +} +remap_t; + + +/* wingdi.h hack, it's the same: 0 */ +#undef CM_NONE + +typedef enum +{ + CM_NONE, + CM_VOLUME, + CM_COLOR_SET, + CM_ALPHA_SET, + CM_COLOR_SCALE, + CM_ALPHA_SCALE, + CM_COLOR_DOT_PRODUCT, + CM_ALPHA_DOT_PRODUCT, + CM_COLOR_DOT_PRODUCT_2, + CM_ALPHA_DOT_PRODUCT_2 +} +colorModType_t; + + +typedef struct colorMod_s +{ + struct colorMod_s *next; + colorModType_t type; + vec_t data[ 16 ]; +} +colorMod_t; + + +typedef enum +{ + IM_NONE, + IM_OPAQUE, + IM_MASKED, + IM_BLEND +} +implicitMap_t; + + +typedef struct shaderInfo_s +{ + char shader[ MAX_QPATH ]; + int surfaceFlags; + int contentFlags; + int compileFlags; + float value; /* light value */ + + char *flareShader; /* for light flares */ + char *damageShader; /* ydnar: sof2 damage shader name */ + char *backShader; /* for surfaces that generate different front and back passes */ + char *cloneShader; /* ydnar: for cloning of a surface */ + char *remapShader; /* ydnar: remap a shader in final stage */ + + surfaceModel_t *surfaceModel; /* ydnar: for distribution of models */ + foliage_t *foliage; /* ydnar/splash damage: wolf et foliage */ + + float subdivisions; /* from a "tesssize xxx" */ + float backsplashFraction; /* floating point value, usually 0.05 */ + float backsplashDistance; /* default 16 */ + float lightSubdivide; /* default 999 */ + float lightFilterRadius; /* ydnar: lightmap filtering/blurring radius for lights created by this shader (default: 0) */ + + int lightmapSampleSize; /* lightmap sample size */ + float lightmapSampleOffset; /* ydnar: lightmap sample offset (default: 1.0) */ + + float bounceScale; /* ydnar: radiosity re-emission [0,1.0+] */ + float offset; /* ydnar: offset in units */ + float shadeAngleDegrees; /* ydnar: breaking angle for smooth shading (degrees) */ + + vec3_t mins, maxs; /* ydnar: for particle studio vertexDeform move support */ + + qb_t legacyTerrain; /* ydnar: enable legacy terrain crutches */ + qb_t indexed; /* ydnar: attempt to use indexmap (terrain alphamap style) */ + qb_t forceMeta; /* ydnar: force metasurface path */ + qb_t noClip; /* ydnar: don't clip into bsp, preserve original face winding */ + qb_t noFast; /* ydnar: supress fast lighting for surfaces with this shader */ + qb_t invert; /* ydnar: reverse facing */ + qb_t nonplanar; /* ydnar: for nonplanar meta surface merging */ + qb_t tcGen; /* ydnar: has explicit texcoord generation */ + vec3_t vecs[ 2 ]; /* ydnar: explicit texture vectors for [0,1] texture space */ + tcMod_t mod; /* ydnar: q3map_tcMod matrix for djbob :) */ + vec3_t lightmapAxis; /* ydnar: explicit lightmap axis projection */ + colorMod_t *colorMod; /* ydnar: q3map_rgb/color/alpha/Set/Mod support */ + + int furNumLayers; /* ydnar: number of fur layers */ + float furOffset; /* ydnar: offset of each layer */ + float furFade; /* ydnar: alpha fade amount per layer */ + + qb_t splotchFix; /* ydnar: filter splotches on lightmaps */ + + qb_t hasPasses; /* false if the shader doesn't define any rendering passes */ + qb_t globalTexture; /* don't normalize texture repeats */ + qb_t twoSided; /* cull none */ + qb_t autosprite; /* autosprite shaders will become point lights instead of area lights */ + qb_t polygonOffset; /* ydnar: don't face cull this or against this */ + qb_t patchShadows; /* have patches casting shadows when using -light for this surface */ + qb_t vertexShadows; /* shadows will be casted at this surface even when vertex lit */ + qb_t forceSunlight; /* force sun light at this surface even tho we might not calculate shadows in vertex lighting */ + qb_t notjunc; /* don't use this surface for tjunction fixing */ + qb_t fogParms; /* ydnar: has fogparms */ + qb_t noFog; /* ydnar: supress fogging */ + qb_t clipModel; /* ydnar: solid model hack */ + qb_t noVertexLight; /* ydnar: leave vertex color alone */ + + byte styleMarker; /* ydnar: light styles hack */ + + float vertexScale; /* vertex light scale */ + + char skyParmsImageBase[ MAX_QPATH ]; /* ydnar: for skies */ + + char editorImagePath[ MAX_QPATH ]; /* use this image to generate texture coordinates */ + char lightImagePath[ MAX_QPATH ]; /* use this image to generate color / averageColor */ + char normalImagePath[ MAX_QPATH ]; /* ydnar: normalmap image for bumpmapping */ + + implicitMap_t implicitMap; /* ydnar: enemy territory implicit shaders */ + char implicitImagePath[ MAX_QPATH ]; + + image_t *shaderImage; + image_t *lightImage; + image_t *normalImage; + + float skyLightValue; /* ydnar */ + int skyLightIterations; /* ydnar */ + sun_t *sun; /* ydnar */ + + vec3_t color; /* normalized color */ + vec3_t averageColor; + byte lightStyle; + + qb_t lmMergable; /* ydnar */ + int lmCustomWidth, lmCustomHeight; /* ydnar */ + float lmBrightness; /* ydnar */ + float lmFilterRadius; /* ydnar: lightmap filtering/blurring radius for this shader (default: 0) */ + + int shaderWidth, shaderHeight; /* ydnar */ + float stFlat[ 2 ]; + + vec3_t fogDir; /* ydnar */ + + char *shaderText; /* ydnar */ + qb_t custom; + qb_t finished; +} +shaderInfo_t; + + + +/* ------------------------------------------------------------------------------- + +bsp structures + +------------------------------------------------------------------------------- */ + +typedef struct face_s +{ + struct face_s *next; + int planenum; + int priority; + qboolean checked; + int compileFlags; + winding_t *w; +} +face_t; + + +typedef struct plane_s +{ + vec3_t normal; + vec_t dist; + int type; + struct plane_s *hash_chain; +} +plane_t; + + +typedef struct side_s +{ + int planenum; + + int outputNum; /* set when the side is written to the file list */ + + float texMat[ 2 ][ 3 ]; /* brush primitive texture matrix */ + float vecs[ 2 ][ 4 ]; /* old-style texture coordinate mapping */ + + winding_t *winding; + winding_t *visibleHull; /* convex hull of all visible fragments */ + + shaderInfo_t *shaderInfo; + + int contentFlags; /* from shaderInfo */ + int surfaceFlags; /* from shaderInfo */ + int compileFlags; /* from shaderInfo */ + int value; /* from shaderInfo */ + + qboolean visible; /* choose visble planes first */ + qboolean bevel; /* don't ever use for bsp splitting, and don't bother making windings for it */ + qboolean culled; /* ydnar: face culling */ +} +side_t; + + +typedef struct sideRef_s +{ + struct sideRef_s *next; + side_t *side; +} +sideRef_t; + + +/* ydnar: generic index mapping for entities (natural extension of terrain texturing) */ +typedef struct indexMap_s +{ + int w, h, numLayers; + char name[ MAX_QPATH ], shader[ MAX_QPATH ]; + float offsets[ 256 ]; + byte *pixels; +} +indexMap_t; + + +typedef struct brush_s +{ + struct brush_s *next; + struct brush_s *nextColorModBrush; /* ydnar: colorMod volume brushes go here */ + struct brush_s *original; /* chopped up brushes will reference the originals */ + + int entityNum, brushNum;/* editor numbering */ + int outputNum; /* set when the brush is written to the file list */ + + /* ydnar: for shadowcasting entities */ + int castShadows; + int recvShadows; + + shaderInfo_t *contentShader; + shaderInfo_t *celShader; /* :) */ + + /* ydnar: gs mods */ + float lightmapScale; + vec3_t eMins, eMaxs; + indexMap_t *im; + + int contentFlags; + int compileFlags; /* ydnar */ + qboolean detail; + qboolean opaque; + + int portalareas[ 2 ]; + + vec3_t mins, maxs; + int numsides; + + side_t sides[ 6 ]; /* variably sized */ +} +brush_t; + + +typedef struct fog_s +{ + shaderInfo_t *si; + brush_t *brush; + int visibleSide; /* the brush side that ray tests need to clip against (-1 == none) */ +} +fog_t; + + +typedef struct +{ + int width, height; + bspDrawVert_t *verts; +} +mesh_t; + + +typedef struct parseMesh_s +{ + struct parseMesh_s *next; + + int entityNum, brushNum; /* ydnar: editor numbering */ + + /* ydnar: for shadowcasting entities */ + int castShadows; + int recvShadows; + + mesh_t mesh; + shaderInfo_t *shaderInfo; + shaderInfo_t *celShader; /* :) */ + + /* ydnar: gs mods */ + float lightmapScale; + vec3_t eMins, eMaxs; + indexMap_t *im; + + /* grouping */ + qboolean grouped; + float longestCurve; + int maxIterations; +} +parseMesh_t; + + +/* + ydnar: the drawsurf struct was extended to allow for: + - non-convex planar surfaces + - non-planar brushface surfaces + - lightmapped terrain + - planar patches +*/ + +typedef enum +{ + /* ydnar: these match up exactly with bspSurfaceType_t */ + SURFACE_BAD, + SURFACE_FACE, + SURFACE_PATCH, + SURFACE_TRIANGLES, + SURFACE_FLARE, + SURFACE_FOLIAGE, /* wolf et */ + + /* ydnar: compiler-relevant surface types */ + SURFACE_FORCED_META, + SURFACE_META, + SURFACE_FOGHULL, + SURFACE_DECAL, + SURFACE_SHADER, + + NUM_SURFACE_TYPES +} +surfaceType_t; + +char *surfaceTypes[ NUM_SURFACE_TYPES ] +#ifndef MAIN_C + ; +#else + = + { + "SURFACE_BAD", + "SURFACE_FACE", + "SURFACE_PATCH", + "SURFACE_TRIANGLES", + "SURFACE_FLARE", + "SURFACE_FOLIAGE", + "SURFACE_FORCED_META", + "SURFACE_META", + "SURFACE_FOGHULL", + "SURFACE_DECAL", + "SURFACE_SHADER" + }; +#endif + + +/* ydnar: this struct needs an overhaul (again, heh) */ +typedef struct mapDrawSurface_s +{ + surfaceType_t type; + qboolean planar; + int outputNum; /* ydnar: to match this sort of thing up */ + + qboolean fur; /* ydnar: this is kind of a hack, but hey... */ + qboolean skybox; /* ydnar: yet another fun hack */ + qboolean backSide; /* ydnar: q3map_backShader support */ + + struct mapDrawSurface_s *parent; /* ydnar: for cloned (skybox) surfaces to share lighting data */ + struct mapDrawSurface_s *clone; /* ydnar: for cloned surfaces */ + struct mapDrawSurface_s *cel; /* ydnar: for cloned cel surfaces */ + + shaderInfo_t *shaderInfo; + shaderInfo_t *celShader; + brush_t *mapBrush; + parseMesh_t *mapMesh; + sideRef_t *sideRef; + + int fogNum; + + int numVerts; /* vertexes and triangles */ + bspDrawVert_t *verts; + int numIndexes; + int *indexes; + + int planeNum; + vec3_t lightmapOrigin; /* also used for flares */ + vec3_t lightmapVecs[ 3 ]; /* also used for flares */ + int lightStyle; /* used for flares */ + + /* ydnar: per-surface (per-entity, actually) lightmap sample size scaling */ + float lightmapScale; + + /* ydnar: surface classification */ + vec3_t mins, maxs; + vec3_t lightmapAxis; + int sampleSize; + + /* ydnar: shadow group support */ + int castShadows, recvShadows; + + /* ydnar: texture coordinate range monitoring for hardware with limited texcoord precision (in texel space) */ + float bias[ 2 ]; + int texMins[ 2 ], texMaxs[ 2 ], texRange[ 2 ]; + + /* ydnar: for patches */ + float longestCurve; + int maxIterations; + int patchWidth, patchHeight; + vec3_t bounds[ 2 ]; + + /* ydnar/sd: for foliage */ + int numFoliageInstances; + + /* ydnar: editor/useful numbering */ + int entityNum; + int surfaceNum; +} +mapDrawSurface_t; + + +typedef struct drawSurfRef_s +{ + struct drawSurfRef_s *nextRef; + int outputNum; +} +drawSurfRef_t; + + +/* ydnar: metasurfaces are constructed from lists of metatriangles so they can be merged in the best way */ +typedef struct metaTriangle_s +{ + shaderInfo_t *si; + side_t *side; + int entityNum, surfaceNum, planeNum, fogNum, sampleSize, castShadows, recvShadows; + vec4_t plane; + vec3_t lightmapAxis; + int indexes[ 3 ]; +} +metaTriangle_t; + + +typedef struct epair_s +{ + struct epair_s *next; + char *key, *value; +} +epair_t; + + +typedef struct +{ + vec3_t origin; + brush_t *brushes, *lastBrush, *colorModBrushes; + parseMesh_t *patches; + int mapEntityNum, firstDrawSurf; + int firstBrush, numBrushes; /* only valid during BSP compile */ + epair_t *epairs; +} +entity_t; + + +typedef struct node_s +{ + /* both leafs and nodes */ + int planenum; /* -1 = leaf node */ + struct node_s *parent; + vec3_t mins, maxs; /* valid after portalization */ + brush_t *volume; /* one for each leaf/node */ + + /* nodes only */ + side_t *side; /* the side that created the node */ + struct node_s *children[ 2 ]; + int compileFlags; /* ydnar: hint, antiportal */ + int tinyportals; + vec3_t referencepoint; + + /* leafs only */ + qboolean opaque; /* view can never be inside */ + qboolean areaportal; + qboolean skybox; /* ydnar: a skybox leaf */ + qboolean sky; /* ydnar: a sky leaf */ + int cluster; /* for portalfile writing */ + int area; /* for areaportals */ + brush_t *brushlist; /* fragments of all brushes in this leaf */ + drawSurfRef_t *drawSurfReferences; + + int occupied; /* 1 or greater can reach entity */ + entity_t *occupant; /* for leak file testing */ + + struct portal_s *portals; /* also on nodes during construction */ +} +node_t; + + +typedef struct portal_s +{ + plane_t plane; + node_t *onnode; /* NULL = outside box */ + node_t *nodes[ 2 ]; /* [ 0 ] = front side of plane */ + struct portal_s *next[ 2 ]; + winding_t *winding; + + qboolean sidefound; /* false if ->side hasn't been checked */ + int compileFlags; /* from original face that caused the split */ + side_t *side; /* NULL = non-visible */ +} +portal_t; + + +typedef struct +{ + node_t *headnode; + node_t outside_node; + vec3_t mins, maxs; +} +tree_t; + + + +/* ------------------------------------------------------------------------------- + +vis structures + +------------------------------------------------------------------------------- */ + +typedef struct +{ + vec3_t normal; + float dist; +} +visPlane_t; + + +typedef struct +{ + int numpoints; + vec3_t points[ MAX_POINTS_ON_FIXED_WINDING ]; /* variable sized */ +} +fixedWinding_t; + + +typedef struct passage_s +{ + struct passage_s *next; + byte cansee[ 1 ]; /* all portals that can be seen through this passage */ +} passage_t; + + +typedef enum +{ + stat_none, + stat_working, + stat_done +} +vstatus_t; + + +typedef struct +{ + int num; + qboolean hint; /* true if this portal was created from a hint splitter */ + qboolean removed; + visPlane_t plane; /* normal pointing into neighbor */ + int leaf; /* neighbor */ + + vec3_t origin; /* for fast clip testing */ + float radius; + + fixedWinding_t *winding; + vstatus_t status; + byte *portalfront; /* [portals], preliminary */ + byte *portalflood; /* [portals], intermediate */ + byte *portalvis; /* [portals], final */ + + int nummightsee; /* bit count on portalflood for sort */ + passage_t *passages; /* there are just as many passages as there */ + /* are portals in the leaf this portal leads */ +} +vportal_t; + + +typedef struct leaf_s +{ + int numportals; + int merged; + vportal_t *portals[MAX_PORTALS_ON_LEAF]; +} +leaf_t; + + +typedef struct pstack_s +{ + byte mightsee[ MAX_PORTALS / 8 ]; + struct pstack_s *next; + leaf_t *leaf; + vportal_t *portal; /* portal exiting */ + fixedWinding_t *source; + fixedWinding_t *pass; + + fixedWinding_t windings[ 3 ]; /* source, pass, temp in any order */ + int freewindings[ 3 ]; + + visPlane_t portalplane; + int depth; +#ifdef SEPERATORCACHE + visPlane_t seperators[ 2 ][ MAX_SEPERATORS ]; + int numseperators[ 2 ]; +#endif +} +pstack_t; + + +typedef struct +{ + vportal_t *base; + int c_chains; + pstack_t pstack_head; +} +threaddata_t; + + + +/* ------------------------------------------------------------------------------- + +light structures + +------------------------------------------------------------------------------- */ + +/* ydnar: new light struct with flags */ +typedef struct light_s +{ + struct light_s *next; + + int type; + int flags; /* ydnar: condensed all the booleans into one flags int */ + shaderInfo_t *si; + + vec3_t origin; + vec3_t normal; /* for surfaces, spotlights, and suns */ + float dist; /* plane location along normal */ + + float photons; + int style; + vec3_t color; + float radiusByDist; /* for spotlights */ + float fade; /* ydnar: from wolf, for linear lights */ + float angleScale; /* ydnar: stolen from vlight for K */ + + float add; /* ydnar: used for area lights */ + float envelope; /* ydnar: units until falloff < tolerance */ + float envelope2; /* ydnar: envelope squared (tiny optimization) */ + vec3_t mins, maxs; /* ydnar: pvs envelope */ + int cluster; /* ydnar: cluster light falls into */ + + winding_t *w; + vec3_t emitColor; /* full out-of-gamut value */ + + float falloffTolerance; /* ydnar: minimum attenuation threshold */ + float filterRadius; /* ydnar: lightmap filter radius in world units, 0 == default */ +} +light_t; + + +typedef struct +{ + /* constant input */ + qboolean testOcclusion, forceSunlight, testAll; + int recvShadows; + + int numSurfaces; + int *surfaces; + + int numLights; + light_t **lights; + + qboolean twoSided; + + /* per-sample input */ + int cluster; + vec3_t origin, normal; + vec_t inhibitRadius; /* sphere in which occluding geometry is ignored */ + + /* per-light input */ + light_t *light; + vec3_t end; + + /* calculated input */ + vec3_t displacement, direction; + vec_t distance; + + /* input and output */ + vec3_t color; /* starts out at full color, may be reduced if transparent surfaces are crossed */ + + /* output */ + vec3_t hit; + int compileFlags; /* for determining surface compile flags traced through */ + qboolean passSolid; + qboolean opaque; + + /* working data */ + int numTestNodes; + int testNodes[ MAX_TRACE_TEST_NODES ]; +} +trace_t; + + + +/* must be identical to bspDrawVert_t except for float color! */ +typedef struct +{ + vec3_t xyz; + float st[ 2 ]; + float lightmap[ MAX_LIGHTMAPS ][ 2 ]; + vec3_t normal; + float color[ MAX_LIGHTMAPS ][ 4 ]; +} +radVert_t; + + +typedef struct +{ + int numVerts; + radVert_t verts[ MAX_POINTS_ON_WINDING ]; +} +radWinding_t; + + +/* crutch for poor local allocations in win32 smp */ +typedef struct +{ + vec_t dists[ MAX_POINTS_ON_WINDING + 4 ]; + int sides[ MAX_POINTS_ON_WINDING + 4 ]; +} +clipWork_t; + + +/* ydnar: new lightmap handling code */ +typedef struct outLightmap_s +{ + int lightmapNum, extLightmapNum; + int customWidth, customHeight; + int numLightmaps; + int freeLuxels; + int numShaders; + shaderInfo_t *shaders[ MAX_LIGHTMAP_SHADERS ]; + byte *lightBits; + byte *bspLightBytes; + byte *bspDirBytes; +} +outLightmap_t; + + +typedef struct rawLightmap_s +{ + qboolean finished, splotchFix, wrap[ 2 ]; + int customWidth, customHeight; + float brightness; + float filterRadius; + + int firstLightSurface, numLightSurfaces; /* index into lightSurfaces */ + int numLightClusters, *lightClusters; + + int sampleSize, actualSampleSize, axisNum; + int entityNum; + int recvShadows; + vec3_t mins, maxs, axis, origin, *vecs; + float *plane; + int w, h, sw, sh, used; + + qboolean solid[ MAX_LIGHTMAPS ]; + vec3_t solidColor[ MAX_LIGHTMAPS ]; + + int numStyledTwins; + struct rawLightmap_s *twins[ MAX_LIGHTMAPS ]; + + int outLightmapNums[ MAX_LIGHTMAPS ]; + int twinNums[ MAX_LIGHTMAPS ]; + int lightmapX[ MAX_LIGHTMAPS ], lightmapY[ MAX_LIGHTMAPS ]; + byte styles[ MAX_LIGHTMAPS ]; + float *bspLuxels[ MAX_LIGHTMAPS ]; + float *radLuxels[ MAX_LIGHTMAPS ]; + float *superLuxels[ MAX_LIGHTMAPS ]; + float *superOrigins; + float *superNormals; + int *superClusters; + + float *superDeluxels; /* average light direction */ + float *bspDeluxels; + float *superFloodLight; +} +rawLightmap_t; + + +typedef struct rawGridPoint_s +{ + vec3_t ambient[ MAX_LIGHTMAPS ]; + vec3_t directed[ MAX_LIGHTMAPS ]; + vec3_t dir; + byte styles[ MAX_LIGHTMAPS ]; +} +rawGridPoint_t; + + +typedef struct surfaceInfo_s +{ + bspModel_t *model; + shaderInfo_t *si; + rawLightmap_t *lm; + int parentSurfaceNum, childSurfaceNum; + int entityNum, castShadows, recvShadows, sampleSize, patchIterations; + float longestCurve; + float *plane; + vec3_t axis, mins, maxs; + qboolean hasLightmap, approximated; + int firstSurfaceCluster, numSurfaceClusters; +} +surfaceInfo_t; + + + +/* ------------------------------------------------------------------------------- + +prototypes + +------------------------------------------------------------------------------- */ + +/* main.c */ +vec_t Random( void ); +int BSPInfo( int count, char **fileNames ); +int ScaleBSPMain( int argc, char **argv ); +int ConvertMain( int argc, char **argv ); + + +/* path_init.c */ +game_t *GetGame( char *arg ); +void InitPaths( int *argc, char **argv ); + + +/* bsp.c */ +int BSPMain( int argc, char **argv ); + + +/* convert_map.c */ +int ConvertBSPToMap( char *bspName ); + + +/* convert_ase.c */ +int ConvertBSPToASE( char *bspName ); + + +/* brush.c */ +sideRef_t *AllocSideRef( side_t *side, sideRef_t *next ); +int CountBrushList( brush_t *brushes ); +brush_t *AllocBrush( int numsides ); +void FreeBrush( brush_t *brushes ); +void FreeBrushList( brush_t *brushes ); +brush_t *CopyBrush( brush_t *brush ); +qboolean BoundBrush( brush_t *brush ); +qboolean CreateBrushWindings( brush_t *brush ); +brush_t *BrushFromBounds( vec3_t mins, vec3_t maxs ); +vec_t BrushVolume( brush_t *brush ); +void WriteBSPBrushMap( char *name, brush_t *list ); + +void FilterDetailBrushesIntoTree( entity_t *e, tree_t *tree ); +void FilterStructuralBrushesIntoTree( entity_t *e, tree_t *tree ); + +int BoxOnPlaneSide( vec3_t mins, vec3_t maxs, plane_t *plane ); +qboolean WindingIsTiny( winding_t *w ); + +void SplitBrush( brush_t *brush, int planenum, brush_t **front, brush_t **back); + +tree_t *AllocTree( void ); +node_t *AllocNode( void ); + + +/* mesh.c */ +void LerpDrawVert( bspDrawVert_t *a, bspDrawVert_t *b, bspDrawVert_t *out ); +void LerpDrawVertAmount( bspDrawVert_t *a, bspDrawVert_t *b, float amount, bspDrawVert_t *out ); +void FreeMesh( mesh_t *m ); +mesh_t *CopyMesh( mesh_t *mesh ); +void PrintMesh( mesh_t *m ); +mesh_t *TransposeMesh( mesh_t *in ); +void InvertMesh( mesh_t *m ); +mesh_t *SubdivideMesh( mesh_t in, float maxError, float minLength ); +int IterationsForCurve( float len, int subdivisions ); +mesh_t *SubdivideMesh2( mesh_t in, int iterations ); +mesh_t *SubdivideMeshQuads( mesh_t *in, float minLength, int maxsize, int *widthtable, int *heighttable ); +mesh_t *RemoveLinearMeshColumnsRows( mesh_t *in ); +void MakeMeshNormals( mesh_t in ); +void PutMeshOnCurve( mesh_t in ); + +void MakeNormalVectors( vec3_t forward, vec3_t right, vec3_t up ); + + +/* map.c */ +void LoadMapFile( char *filename, qboolean onlyLights ); +int FindFloatPlane( vec3_t normal, vec_t dist, int numPoints, vec3_t *points ); +int PlaneTypeForNormal( vec3_t normal ); +void AddBrushBevels( void ); +brush_t *FinishBrush( void ); + + +/* portals.c */ +void MakeHeadnodePortals( tree_t *tree ); +void MakeNodePortal( node_t *node ); +void SplitNodePortals( node_t *node ); + +qboolean PortalPassable( portal_t *p ); + +qboolean FloodEntities( tree_t *tree ); +void FillOutside( node_t *headnode); +void FloodAreas( tree_t *tree); +face_t *VisibleFaces( entity_t *e, tree_t *tree ); +void FreePortal( portal_t *p ); + +void MakeTreePortals( tree_t *tree ); + + +/* leakfile.c */ +xmlNodePtr LeakFile( tree_t *tree ); + + +/* prtfile.c */ +void NumberClusters( tree_t *tree ); +void WritePortalFile( tree_t *tree ); + + +/* writebsp.c */ +void SetModelNumbers( void ); +void SetLightStyles( void ); + +int EmitShader( const char *shader, int *contentFlags, int *surfaceFlags ); + +void BeginBSPFile( void ); +void EndBSPFile( void ); +void EmitBrushes( brush_t *brushes, int *firstBrush, int *numBrushes ); +void EmitFogs( void ); + +void BeginModel( void ); +void EndModel( entity_t *e, node_t *headnode ); + + +/* tree.c */ +void FreeTree( tree_t *tree ); +void FreeTree_r( node_t *node ); +void PrintTree_r( node_t *node, int depth ); +void FreeTreePortals_r( node_t *node ); + + +/* patch.c */ +void ParsePatch( qboolean onlyLights ); +mesh_t *SubdivideMesh( mesh_t in, float maxError, float minLength ); +void PatchMapDrawSurfs( entity_t *e ); + + +/* tjunction.c */ +void FixTJunctions( entity_t *e ); + + +/* fog.c */ +winding_t *WindingFromDrawSurf( mapDrawSurface_t *ds ); +void FogDrawSurfaces( entity_t *e ); +int FogForPoint( vec3_t point, float epsilon ); +int FogForBounds( vec3_t mins, vec3_t maxs, float epsilon ); +void CreateMapFogs( void ); + + +/* facebsp.c */ +face_t *MakeStructuralBSPFaceList( brush_t *list ); +face_t *MakeVisibleBSPFaceList( brush_t *list ); +tree_t *FaceBSP( face_t *list ); + + +/* model.c */ +void PicoPrintFunc( int level, const char *str ); +void PicoLoadFileFunc( char *name, byte **buffer, int *bufSize ); +picoModel_t *FindModel( char *name, int frame ); +picoModel_t *LoadModel( char *name, int frame ); +void InsertModel( char *name, int frame, m4x4_t transform, remap_t *remap, shaderInfo_t *celShader, int eNum, int castShadows, int recvShadows, int spawnFlags, float lightmapScale ); +void AddTriangleModels( entity_t *e ); + + +/* surface.c */ +mapDrawSurface_t *AllocDrawSurface( surfaceType_t type ); +void FinishSurface( mapDrawSurface_t *ds ); +void StripFaceSurface( mapDrawSurface_t *ds ); +qboolean CalcSurfaceTextureRange( mapDrawSurface_t *ds ); +qboolean CalcLightmapAxis( vec3_t normal, vec3_t axis ); +void ClassifySurfaces( int numSurfs, mapDrawSurface_t *ds ); +void ClassifyEntitySurfaces( entity_t *e ); +void TidyEntitySurfaces( entity_t *e ); +mapDrawSurface_t *CloneSurface( mapDrawSurface_t *src, shaderInfo_t *si ); +mapDrawSurface_t *MakeCelSurface( mapDrawSurface_t *src, shaderInfo_t *si ); +qboolean IsTriangleDegenerate( bspDrawVert_t *points, int a, int b, int c ); +void ClearSurface( mapDrawSurface_t *ds ); +void AddEntitySurfaceModels( entity_t *e ); +mapDrawSurface_t *DrawSurfaceForSide( entity_t *e, brush_t *b, side_t *s, winding_t *w ); +mapDrawSurface_t *DrawSurfaceForMesh( entity_t *e, parseMesh_t *p, mesh_t *mesh ); +mapDrawSurface_t *DrawSurfaceForFlare( int entNum, vec3_t origin, vec3_t normal, vec3_t color, char *flareShader, int lightStyle ); +mapDrawSurface_t *DrawSurfaceForShader( char *shader ); +void ClipSidesIntoTree( entity_t *e, tree_t *tree ); +void MakeDebugPortalSurfs( tree_t *tree ); +void MakeFogHullSurfs( entity_t *e, tree_t *tree, char *shader ); +void SubdivideFaceSurfaces( entity_t *e, tree_t *tree ); +void AddEntitySurfaceModels( entity_t *e ); +int AddSurfaceModels( mapDrawSurface_t *ds ); +void FilterDrawsurfsIntoTree( entity_t *e, tree_t *tree ); + + +/* surface_fur.c */ +void Fur( mapDrawSurface_t *src ); + + +/* surface_foliage.c */ +void Foliage( mapDrawSurface_t *src ); + + +/* ydnar: surface_meta.c */ +void ClearMetaTriangles( void ); +int FindMetaTriangle( metaTriangle_t *src, bspDrawVert_t *a, bspDrawVert_t *b, bspDrawVert_t *c, int planeNum ); +void MakeEntityMetaTriangles( entity_t *e ); +void FixMetaTJunctions( void ); +void SmoothMetaTriangles( void ); +void MergeMetaTriangles( void ); + + +/* surface_extra.c */ +void SetDefaultSampleSize( int sampleSize ); + +void SetSurfaceExtra( mapDrawSurface_t *ds, int num ); + +shaderInfo_t *GetSurfaceExtraShaderInfo( int num ); +int GetSurfaceExtraParentSurfaceNum( int num ); +int GetSurfaceExtraEntityNum( int num ); +int GetSurfaceExtraCastShadows( int num ); +int GetSurfaceExtraRecvShadows( int num ); +int GetSurfaceExtraSampleSize( int num ); +float GetSurfaceExtraLongestCurve( int num ); +void GetSurfaceExtraLightmapAxis( int num, vec3_t lightmapAxis ); + +void WriteSurfaceExtraFile( const char *path ); +void LoadSurfaceExtraFile( const char *path ); + + +/* decals.c */ +void ProcessDecals( void ); +void MakeEntityDecals( entity_t *e ); + + +/* brush_primit.c */ +void ComputeAxisBase( vec3_t normal, vec3_t texX, vec3_t texY); + + +/* vis.c */ +fixedWinding_t *NewFixedWinding( int points ); +int VisMain( int argc, char **argv ); + +/* visflow.c */ +int CountBits( byte *bits, int numbits ); +void PassageFlow( int portalnum ); +void CreatePassages( int portalnum ); +void PassageMemory( void ); +void BasePortalVis( int portalnum ); +void BetterPortalVis( int portalnum ); +void PortalFlow( int portalnum ); +void PassagePortalFlow( int portalnum ); + + + +/* light.c */ +float PointToPolygonFormFactor( const vec3_t point, const vec3_t normal, const winding_t *w ); +int LightContributionToSample( trace_t *trace ); +void LightingAtSample( trace_t *trace, byte styles[ MAX_LIGHTMAPS ], vec3_t colors[ MAX_LIGHTMAPS ] ); +int LightContributionToPoint( trace_t *trace ); +int LightMain( int argc, char **argv ); + + +/* light_trace.c */ +void SetupTraceNodes( void ); +void TraceLine( trace_t *trace ); +float SetupTrace( trace_t *trace ); + + +/* light_bounce.c */ +qboolean RadSampleImage( byte *pixels, int width, int height, float st[ 2 ], float color[ 4 ] ); +void RadLightForTriangles( int num, int lightmapNum, rawLightmap_t *lm, shaderInfo_t *si, float scale, float subdivide, clipWork_t *cw ); +void RadLightForPatch( int num, int lightmapNum, rawLightmap_t *lm, shaderInfo_t *si, float scale, float subdivide, clipWork_t *cw ); +void RadCreateDiffuseLights( void ); +void RadFreeLights(); + + +/* light_ydnar.c */ +void ColorToBytes( const float *color, byte *colorBytes, float scale ); +void SmoothNormals( void ); + +void MapRawLightmap( int num ); + +void SetupDirt(); +float DirtForSample( trace_t *trace ); +void DirtyRawLightmap( int num ); + +void SetupFloodLight(); +float FloodLightForSample( trace_t *trace ); +void FloodLightRawLightmap( int num ); + +void IlluminateRawLightmap( int num ); +void IlluminateVertexes( int num ); + +void SetupBrushes( void ); +void SetupClusters( void ); +qboolean ClusterVisible( int a, int b ); +qboolean ClusterVisibleToPoint( vec3_t point, int cluster ); +int ClusterForPoint( vec3_t point ); +int ClusterForPointExt( vec3_t point, float epsilon ); +int ClusterForPointExtFilter( vec3_t point, float epsilon, int numClusters, int *clusters ); +int ShaderForPointInLeaf( vec3_t point, int leafNum, float epsilon, int wantContentFlags, int wantSurfaceFlags, int *contentFlags, int *surfaceFlags ); +void SetupEnvelopes( qboolean forGrid, qboolean fastFlag ); +void FreeTraceLights( trace_t *trace ); +void CreateTraceLightsForBounds( vec3_t mins, vec3_t maxs, vec3_t normal, int numClusters, int *clusters, int flags, trace_t *trace ); +void CreateTraceLightsForSurface( int num, trace_t *trace ); + + +/* lightmaps_ydnar.c */ +void ExportLightmaps( void ); + +int ExportLightmapsMain( int argc, char **argv ); +int ImportLightmapsMain( int argc, char **argv ); + +void SetupSurfaceLightmaps( void ); +void StitchSurfaceLightmaps( void ); +void StoreSurfaceLightmaps( void ); + + +/* image.c */ +void ImageFree( image_t *image ); +image_t *ImageFind( const char *filename ); +image_t *ImageLoad( const char *filename ); + + +/* shaders.c */ +void ColorMod( colorMod_t *am, int numVerts, bspDrawVert_t *drawVerts ); + +void TCMod( tcMod_t mod, float st[ 2 ] ); +void TCModIdentity( tcMod_t mod ); +void TCModMultiply( tcMod_t a, tcMod_t b, tcMod_t out ); +void TCModTranslate( tcMod_t mod, float s, float t ); +void TCModScale( tcMod_t mod, float s, float t ); +void TCModRotate( tcMod_t mod, float euler ); + +qboolean ApplySurfaceParm( char *name, int *contentFlags, int *surfaceFlags, int *compileFlags ); + +void BeginMapShaderFile( const char *mapFile ); +void WriteMapShaderFile( void ); +shaderInfo_t *CustomShader( shaderInfo_t *si, char *find, char *replace ); +void EmitVertexRemapShader( char *from, char *to ); + +void LoadShaderInfo( void ); +shaderInfo_t *ShaderInfoForShader( const char *shader ); + + +/* bspfile_abstract.c */ +void SetGridPoints( int n ); +void SetDrawVerts( int n ); +void IncDrawVerts(); +void SetDrawSurfaces(int n); +void SetDrawSurfacesBuffer(); +void BSPFilesCleanup(); + +void SwapBlock( int *block, int size ); + +int GetLumpElements( bspHeader_t *header, int lump, int size ); +void *GetLump( bspHeader_t *header, int lump ); +int CopyLump( bspHeader_t *header, int lump, void *dest, int size ); +void AddLump( FILE *file, bspHeader_t *header, int lumpNum, const void *data, int length ); + +void LoadBSPFile( const char *filename ); +void WriteBSPFile( const char *filename ); +void PrintBSPFileSizes( void ); + +epair_t *ParseEPair( void ); +void ParseEntities( void ); +void UnparseEntities( void ); +void PrintEntity( const entity_t *ent ); +void SetKeyValue( entity_t *ent, const char *key, const char *value ); +const char *ValueForKey( const entity_t *ent, const char *key ); +int IntForKey( const entity_t *ent, const char *key ); +vec_t FloatForKey( const entity_t *ent, const char *key ); +void GetVectorForKey( const entity_t *ent, const char *key, vec3_t vec ); +entity_t *FindTargetEntity( const char *target ); +void GetEntityShadowFlags( const entity_t *ent, const entity_t *ent2, int *castShadows, int *recvShadows ); + + +/* bspfile_ibsp.c */ +void LoadIBSPFile( const char *filename ); +void WriteIBSPFile( const char *filename ); + + +/* bspfile_rbsp.c */ +void LoadRBSPFile( const char *filename ); +void WriteRBSPFile( const char *filename ); + + + +/* ------------------------------------------------------------------------------- + +bsp/general global variables + +------------------------------------------------------------------------------- */ + +#ifdef MAIN_C + #define Q_EXTERN + #define Q_ASSIGN( a ) = a +#else + #define Q_EXTERN extern + #define Q_ASSIGN( a ) +#endif + +/* game support */ +Q_EXTERN game_t games[] +#ifndef MAIN_C + ; +#else + = + { + #include "game_quake3.h" + , + #include "game_quakelive.h"/* most be after game_quake3.h as they share defines! */ + , + #include "game_nexuiz.h"/* most be after game_quake3.h as they share defines! */ + , + #include "game_tremulous.h" /*LinuxManMikeC: must be after game_quake3.h, depends on #define's set in it */ + , + #include "game_tenebrae.h" + , + #include "game_wolf.h" + , + #include "game_wolfet.h"/* most be after game_wolf.h as they share defines! */ + , + #include "game_etut.h" + , + #include "game_ef.h" + , + #include "game_sof2.h" + , + #include "game_jk2.h" /* most be after game_sof2.h as they share defines! */ + , + #include "game_ja.h" /* most be after game_jk2.h as they share defines! */ + , + #include "game_qfusion.h" /* qfusion game */ + , + { NULL } /* null game */ + }; +#endif +Q_EXTERN game_t *game Q_ASSIGN( &games[ 0 ] ); + + +/* general */ +Q_EXTERN int numImages Q_ASSIGN( 0 ); +Q_EXTERN image_t images[ MAX_IMAGES ]; + +Q_EXTERN int numPicoModels Q_ASSIGN( 0 ); +Q_EXTERN picoModel_t *picoModels[ MAX_MODELS ]; + +Q_EXTERN shaderInfo_t *shaderInfo Q_ASSIGN( NULL ); +Q_EXTERN int numShaderInfo Q_ASSIGN( 0 ); +Q_EXTERN int numVertexRemaps Q_ASSIGN( 0 ); + +Q_EXTERN surfaceParm_t custSurfaceParms[ MAX_CUST_SURFACEPARMS ]; +Q_EXTERN int numCustSurfaceParms Q_ASSIGN( 0 ); + +Q_EXTERN char mapName[ MAX_QPATH ]; /* ydnar: per-map custom shaders for larger lightmaps */ +Q_EXTERN char mapShaderFile[ 1024 ]; +Q_EXTERN qboolean warnImage Q_ASSIGN( qtrue ); + +/* ydnar: sinusoid samples */ +Q_EXTERN float jitters[ MAX_JITTERS ]; + + +/* commandline arguments */ +Q_EXTERN qboolean verbose; +Q_EXTERN qboolean verboseEntities Q_ASSIGN( qfalse ); +Q_EXTERN qboolean force Q_ASSIGN( qfalse ); +Q_EXTERN qboolean infoMode Q_ASSIGN( qfalse ); +Q_EXTERN qboolean useCustomInfoParms Q_ASSIGN( qfalse ); +Q_EXTERN qboolean noprune Q_ASSIGN( qfalse ); +Q_EXTERN qboolean leaktest Q_ASSIGN( qfalse ); +Q_EXTERN qboolean nodetail Q_ASSIGN( qfalse ); +Q_EXTERN qboolean nosubdivide Q_ASSIGN( qfalse ); +Q_EXTERN qboolean notjunc Q_ASSIGN( qfalse ); +Q_EXTERN qboolean fulldetail Q_ASSIGN( qfalse ); +Q_EXTERN qboolean nowater Q_ASSIGN( qfalse ); +Q_EXTERN qboolean noCurveBrushes Q_ASSIGN( qfalse ); +Q_EXTERN qboolean fakemap Q_ASSIGN( qfalse ); +Q_EXTERN qboolean coplanar Q_ASSIGN( qfalse ); +Q_EXTERN qboolean nofog Q_ASSIGN( qfalse ); +Q_EXTERN qboolean noHint Q_ASSIGN( qfalse ); /* ydnar */ +Q_EXTERN qboolean renameModelShaders Q_ASSIGN( qfalse ); /* ydnar */ +Q_EXTERN qboolean skyFixHack Q_ASSIGN( qfalse ); /* ydnar */ + +Q_EXTERN int patchSubdivisions Q_ASSIGN( 8 ); /* ydnar: -patchmeta subdivisions */ + +Q_EXTERN int maxLMSurfaceVerts Q_ASSIGN( 64 ); /* ydnar */ +Q_EXTERN int maxSurfaceVerts Q_ASSIGN( 999 ); /* ydnar */ +Q_EXTERN int maxSurfaceIndexes Q_ASSIGN( 6000 ); /* ydnar */ +Q_EXTERN float npDegrees Q_ASSIGN( 0.0f ); /* ydnar: nonplanar degrees */ +Q_EXTERN int bevelSnap Q_ASSIGN( 0 ); /* ydnar: bevel plane snap */ +Q_EXTERN int texRange Q_ASSIGN( 0 ); +Q_EXTERN qboolean flat Q_ASSIGN( qfalse ); +Q_EXTERN qboolean meta Q_ASSIGN( qfalse ); +Q_EXTERN qboolean patchMeta Q_ASSIGN( qfalse ); +Q_EXTERN qboolean emitFlares Q_ASSIGN( qfalse ); +Q_EXTERN qboolean debugSurfaces Q_ASSIGN( qfalse ); +Q_EXTERN qboolean debugInset Q_ASSIGN( qfalse ); +Q_EXTERN qboolean debugPortals Q_ASSIGN( qfalse ); + +Q_EXTERN double normalEpsilon Q_ASSIGN( 0.00001 ); +Q_EXTERN double distanceEpsilon Q_ASSIGN( 0.01 ); + + +/* bsp */ +Q_EXTERN int numMapEntities Q_ASSIGN( 0 ); + +Q_EXTERN int blockSize[ 3 ] /* should be the same as in radiant */ +#ifndef MAIN_C + ; +#else + = { 1024, 1024, 1024 }; +#endif + +Q_EXTERN char name[ 1024 ]; +Q_EXTERN char source[ 1024 ]; +Q_EXTERN char outbase[ 32 ]; + +Q_EXTERN int sampleSize; /* lightmap sample size in units */ + +Q_EXTERN int mapEntityNum Q_ASSIGN( 0 ); + +Q_EXTERN int entitySourceBrushes; + +Q_EXTERN plane_t mapplanes[ MAX_MAP_PLANES ]; /* mapplanes[ num ^ 1 ] will always be the mirror or mapplanes[ num ] */ +Q_EXTERN int nummapplanes; /* nummapplanes will always be even */ +Q_EXTERN int numMapPatches; +Q_EXTERN vec3_t mapMins, mapMaxs; + +Q_EXTERN int defaultFogNum Q_ASSIGN( -1 ); /* ydnar: cleaner fog handling */ +Q_EXTERN int numMapFogs Q_ASSIGN( 0 ); +Q_EXTERN fog_t mapFogs[ MAX_MAP_FOGS ]; + +Q_EXTERN entity_t *mapEnt; +Q_EXTERN brush_t *buildBrush; +Q_EXTERN int numActiveBrushes; +Q_EXTERN int g_bBrushPrimit; + +Q_EXTERN int numStrippedLights Q_ASSIGN( 0 ); + + +/* surface stuff */ +Q_EXTERN mapDrawSurface_t *mapDrawSurfs Q_ASSIGN( NULL ); +Q_EXTERN int numMapDrawSurfs; + +Q_EXTERN int numSurfacesByType[ NUM_SURFACE_TYPES ]; +Q_EXTERN int numClearedSurfaces; +Q_EXTERN int numStripSurfaces; +Q_EXTERN int numFanSurfaces; +Q_EXTERN int numMergedSurfaces; +Q_EXTERN int numMergedVerts; + +Q_EXTERN int numRedundantIndexes; + +Q_EXTERN int numSurfaceModels Q_ASSIGN( 0 ); + +Q_EXTERN byte debugColors[ 12 ][ 3 ] +#ifndef MAIN_C + ; +#else + = + { + { 255, 0, 0 }, + { 192, 128, 128 }, + { 255, 255, 0 }, + { 192, 192, 128 }, + { 0, 255, 255 }, + { 128, 192, 192 }, + { 0, 0, 255 }, + { 128, 128, 192 }, + { 255, 0, 255 }, + { 192, 128, 192 }, + { 0, 255, 0 }, + { 128, 192, 128 } + }; +#endif + +Q_EXTERN qboolean skyboxPresent Q_ASSIGN( qfalse ); +Q_EXTERN int skyboxArea Q_ASSIGN( -1 ); +Q_EXTERN m4x4_t skyboxTransform; + + + +/* ------------------------------------------------------------------------------- + +vis global variables + +------------------------------------------------------------------------------- */ + +/* commandline arguments */ +Q_EXTERN qboolean fastvis; +Q_EXTERN qboolean noPassageVis; +Q_EXTERN qboolean passageVisOnly; +Q_EXTERN qboolean mergevis; +Q_EXTERN qboolean nosort; +Q_EXTERN qboolean saveprt; +Q_EXTERN qboolean hint; /* ydnar */ +Q_EXTERN char inbase[ MAX_QPATH ]; + +/* other bits */ +Q_EXTERN int totalvis; + +Q_EXTERN float farPlaneDist; /* rr2do2, rf, mre, ydnar all contributed to this one... */ + +Q_EXTERN int numportals; +Q_EXTERN int portalclusters; + +Q_EXTERN vportal_t *portals; +Q_EXTERN leaf_t *leafs; + +Q_EXTERN vportal_t *faces; +Q_EXTERN leaf_t *faceleafs; + +Q_EXTERN int numfaces; + +Q_EXTERN int c_portaltest, c_portalpass, c_portalcheck; +Q_EXTERN int c_portalskip, c_leafskip; +Q_EXTERN int c_vistest, c_mighttest; +Q_EXTERN int c_chains; + +Q_EXTERN byte *vismap, *vismap_p, *vismap_end; + +Q_EXTERN int testlevel; + +Q_EXTERN byte *uncompressed; + +Q_EXTERN int leafbytes, leaflongs; +Q_EXTERN int portalbytes, portallongs; + +Q_EXTERN vportal_t *sorted_portals[ MAX_MAP_PORTALS * 2 ]; + + + +/* ------------------------------------------------------------------------------- + +light global variables + +------------------------------------------------------------------------------- */ + +/* commandline arguments */ +Q_EXTERN qboolean wolfLight Q_ASSIGN( qfalse ); +Q_EXTERN qboolean loMem Q_ASSIGN( qfalse ); +Q_EXTERN qboolean noStyles Q_ASSIGN( qfalse ); + +Q_EXTERN int sampleSize Q_ASSIGN( DEFAULT_LIGHTMAP_SAMPLE_SIZE ); +Q_EXTERN qboolean noVertexLighting Q_ASSIGN( qfalse ); +Q_EXTERN qboolean noGridLighting Q_ASSIGN( qfalse ); + +Q_EXTERN qboolean noTrace Q_ASSIGN( qfalse ); +Q_EXTERN qboolean noSurfaces Q_ASSIGN( qfalse ); +Q_EXTERN qboolean patchShadows Q_ASSIGN( qfalse ); +Q_EXTERN qboolean cpmaHack Q_ASSIGN( qfalse ); + +Q_EXTERN qboolean deluxemap Q_ASSIGN( qfalse ); +Q_EXTERN qboolean debugDeluxemap Q_ASSIGN( qfalse ); + +Q_EXTERN qboolean fast Q_ASSIGN( qfalse ); +Q_EXTERN qboolean faster Q_ASSIGN( qfalse ); +Q_EXTERN qboolean fastgrid Q_ASSIGN( qfalse ); +Q_EXTERN qboolean fastbounce Q_ASSIGN( qfalse ); +Q_EXTERN qboolean cheap Q_ASSIGN( qfalse ); +Q_EXTERN qboolean cheapgrid Q_ASSIGN( qfalse ); +Q_EXTERN int bounce Q_ASSIGN( 0 ); +Q_EXTERN qboolean bounceOnly Q_ASSIGN( qfalse ); +Q_EXTERN qboolean bouncing Q_ASSIGN( qfalse ); +Q_EXTERN qboolean bouncegrid Q_ASSIGN( qfalse ); +Q_EXTERN qboolean normalmap Q_ASSIGN( qfalse ); +Q_EXTERN qboolean trisoup Q_ASSIGN( qfalse ); +Q_EXTERN qboolean shade Q_ASSIGN( qfalse ); +Q_EXTERN float shadeAngleDegrees Q_ASSIGN( 0.0f ); +Q_EXTERN int superSample Q_ASSIGN( 0 ); +Q_EXTERN int lightSamples Q_ASSIGN( 1 ); +Q_EXTERN qboolean filter Q_ASSIGN( qfalse ); +Q_EXTERN qboolean dark Q_ASSIGN( qfalse ); +Q_EXTERN qboolean sunOnly Q_ASSIGN( qfalse ); +Q_EXTERN int approximateTolerance Q_ASSIGN( 0 ); +Q_EXTERN qboolean noCollapse Q_ASSIGN( qfalse ); +Q_EXTERN qboolean exportLightmaps Q_ASSIGN( qfalse ); +Q_EXTERN qboolean externalLightmaps Q_ASSIGN( qfalse ); +Q_EXTERN int lmCustomSize Q_ASSIGN( LIGHTMAP_WIDTH ); + +Q_EXTERN qboolean dirty Q_ASSIGN( qfalse ); +Q_EXTERN qboolean dirtDebug Q_ASSIGN( qfalse ); +Q_EXTERN int dirtMode Q_ASSIGN( 0 ); +Q_EXTERN float dirtDepth Q_ASSIGN( 128.0f ); +Q_EXTERN float dirtScale Q_ASSIGN( 1.0f ); +Q_EXTERN float dirtGain Q_ASSIGN( 1.0f ); + +Q_EXTERN qboolean debugnormals Q_ASSIGN( qfalse ); +Q_EXTERN qboolean floodlighty Q_ASSIGN( qfalse ); +Q_EXTERN qboolean floodlight_lowquality Q_ASSIGN( qfalse ); +Q_EXTERN vec3_t floodlightRGB; +Q_EXTERN float floodlightIntensity Q_ASSIGN( 512 ); +Q_EXTERN float floodlightDistance Q_ASSIGN( 1024 ); + +Q_EXTERN qboolean dump Q_ASSIGN( qfalse ); +Q_EXTERN qboolean debug Q_ASSIGN( qfalse ); +Q_EXTERN qboolean debugUnused Q_ASSIGN( qfalse ); +Q_EXTERN qboolean debugAxis Q_ASSIGN( qfalse ); +Q_EXTERN qboolean debugCluster Q_ASSIGN( qfalse ); +Q_EXTERN qboolean debugOrigin Q_ASSIGN( qfalse ); +Q_EXTERN qboolean lightmapBorder Q_ASSIGN( qfalse ); + +/* longest distance across the map */ +Q_EXTERN float maxMapDistance Q_ASSIGN( 0 ); + +/* for run time tweaking of light sources */ +Q_EXTERN float pointScale Q_ASSIGN( 7500.0f ); +Q_EXTERN float areaScale Q_ASSIGN( 0.25f ); +Q_EXTERN float skyScale Q_ASSIGN( 1.0f ); +Q_EXTERN float bounceScale Q_ASSIGN( 0.25f ); + +/* ydnar: lightmap gamma/compensation */ +Q_EXTERN float lightmapGamma Q_ASSIGN( 1.0f ); +Q_EXTERN float lightmapExposure Q_ASSIGN( 1.0f ); +Q_EXTERN float lightmapCompensate Q_ASSIGN( 1.0f ); + +/* ydnar: for runtime tweaking of falloff tolerance */ +Q_EXTERN float falloffTolerance Q_ASSIGN( 1.0f ); +Q_EXTERN qboolean exactPointToPolygon Q_ASSIGN( qtrue ); +Q_EXTERN float formFactorValueScale Q_ASSIGN( 3.0f ); +Q_EXTERN float linearScale Q_ASSIGN( 1.0f / 8000.0f ); + +Q_EXTERN light_t *lights; +Q_EXTERN int numPointLights; +Q_EXTERN int numSpotLights; +Q_EXTERN int numSunLights; +Q_EXTERN int numAreaLights; + +/* ydnar: for luxel placement */ +Q_EXTERN int numSurfaceClusters, maxSurfaceClusters; +Q_EXTERN int *surfaceClusters; + +/* ydnar: for radiosity */ +Q_EXTERN int numDiffuseLights; +Q_EXTERN int numBrushDiffuseLights; +Q_EXTERN int numTriangleDiffuseLights; +Q_EXTERN int numPatchDiffuseLights; + +/* ydnar: general purpose extra copy of drawvert list */ +Q_EXTERN bspDrawVert_t *yDrawVerts; + +/* ydnar: for tracing statistics */ +Q_EXTERN int minSurfacesTested; +Q_EXTERN int maxSurfacesTested; +Q_EXTERN int totalSurfacesTested; +Q_EXTERN int totalTraces; + +Q_EXTERN FILE *dumpFile; + +Q_EXTERN int c_visible, c_occluded; +Q_EXTERN int c_subsampled; /* ydnar */ + +Q_EXTERN int defaultLightSubdivide Q_ASSIGN( 999 ); + +Q_EXTERN vec3_t ambientColor; +Q_EXTERN vec3_t minLight, minVertexLight, minGridLight; + +Q_EXTERN int *entitySurface; +Q_EXTERN vec3_t *surfaceOrigin; + +Q_EXTERN vec3_t sunDirection; +Q_EXTERN vec3_t sunLight; + +/* tracing */ +Q_EXTERN int c_totalTrace; +Q_EXTERN int c_cullTrace, c_testTrace; +Q_EXTERN int c_testFacets; + +/* ydnar: light optimization */ +Q_EXTERN float subdivideThreshold Q_ASSIGN( DEFAULT_SUBDIVIDE_THRESHOLD ); + +Q_EXTERN int numOpaqueBrushes, maxOpaqueBrush; +Q_EXTERN byte *opaqueBrushes; + +Q_EXTERN int numLights; +Q_EXTERN int numCulledLights; + +Q_EXTERN int gridBoundsCulled; +Q_EXTERN int gridEnvelopeCulled; + +Q_EXTERN int lightsBoundsCulled; +Q_EXTERN int lightsEnvelopeCulled; +Q_EXTERN int lightsPlaneCulled; +Q_EXTERN int lightsClusterCulled; + +/* ydnar: radiosity */ +Q_EXTERN float diffuseSubdivide Q_ASSIGN( 256.0f ); +Q_EXTERN float minDiffuseSubdivide Q_ASSIGN( 64.0f ); +Q_EXTERN int numDiffuseSurfaces Q_ASSIGN( 0 ); + +/* ydnar: list of surface information necessary for lightmap calculation */ +Q_EXTERN surfaceInfo_t *surfaceInfos Q_ASSIGN( NULL ); + +/* ydnar: sorted list of surfaces */ +Q_EXTERN int *sortSurfaces Q_ASSIGN( NULL ); + +/* clumps of surfaces that share a raw lightmap */ +Q_EXTERN int numLightSurfaces Q_ASSIGN( 0 ); +Q_EXTERN int *lightSurfaces Q_ASSIGN( NULL ); + +/* raw lightmaps */ +Q_EXTERN int numRawSuperLuxels Q_ASSIGN( 0 ); +Q_EXTERN int numRawLightmaps Q_ASSIGN( 0 ); +Q_EXTERN rawLightmap_t *rawLightmaps Q_ASSIGN( NULL ); +Q_EXTERN int *sortLightmaps Q_ASSIGN( NULL ); + +/* vertex luxels */ +Q_EXTERN float *vertexLuxels[ MAX_LIGHTMAPS ]; +Q_EXTERN float *radVertexLuxels[ MAX_LIGHTMAPS ]; + +/* bsp lightmaps */ +Q_EXTERN int numLightmapShaders Q_ASSIGN( 0 ); +Q_EXTERN int numSolidLightmaps Q_ASSIGN( 0 ); +Q_EXTERN int numOutLightmaps Q_ASSIGN( 0 ); +Q_EXTERN int numBSPLightmaps Q_ASSIGN( 0 ); +Q_EXTERN int numExtLightmaps Q_ASSIGN( 0 ); +Q_EXTERN outLightmap_t *outLightmaps Q_ASSIGN( NULL ); + +/* grid points */ +Q_EXTERN int numRawGridPoints Q_ASSIGN( 0 ); +Q_EXTERN rawGridPoint_t *rawGridPoints Q_ASSIGN( NULL ); + +Q_EXTERN int numSurfsVertexLit Q_ASSIGN( 0 ); +Q_EXTERN int numSurfsVertexForced Q_ASSIGN( 0 ); +Q_EXTERN int numSurfsVertexApproximated Q_ASSIGN( 0 ); +Q_EXTERN int numSurfsLightmapped Q_ASSIGN( 0 ); +Q_EXTERN int numPlanarsLightmapped Q_ASSIGN( 0 ); +Q_EXTERN int numNonPlanarsLightmapped Q_ASSIGN( 0 ); +Q_EXTERN int numPatchesLightmapped Q_ASSIGN( 0 ); +Q_EXTERN int numPlanarPatchesLightmapped Q_ASSIGN( 0 ); + +Q_EXTERN int numLuxels Q_ASSIGN( 0 ); +Q_EXTERN int numLuxelsMapped Q_ASSIGN( 0 ); +Q_EXTERN int numLuxelsOccluded Q_ASSIGN( 0 ); +Q_EXTERN int numLuxelsIlluminated Q_ASSIGN( 0 ); +Q_EXTERN int numVertsIlluminated Q_ASSIGN( 0 ); + +/* lightgrid */ +Q_EXTERN vec3_t gridMins; +Q_EXTERN int gridBounds[ 3 ]; +Q_EXTERN vec3_t gridSize +#ifndef MAIN_C + ; +#else + = { 64, 64, 128 }; +#endif + + + +/* ------------------------------------------------------------------------------- + +abstracted bsp globals + +------------------------------------------------------------------------------- */ + +Q_EXTERN int numEntities Q_ASSIGN( 0 ); +Q_EXTERN int numBSPEntities Q_ASSIGN( 0 ); +Q_EXTERN entity_t entities[ MAX_MAP_ENTITIES ]; + +Q_EXTERN int numBSPModels Q_ASSIGN( 0 ); +Q_EXTERN bspModel_t bspModels[ MAX_MAP_MODELS ]; + +Q_EXTERN int numBSPShaders Q_ASSIGN( 0 ); +Q_EXTERN bspShader_t bspShaders[ MAX_MAP_MODELS ]; + +Q_EXTERN int bspEntDataSize Q_ASSIGN( 0 ); +Q_EXTERN char bspEntData[ MAX_MAP_ENTSTRING ]; + +Q_EXTERN int numBSPLeafs Q_ASSIGN( 0 ); +Q_EXTERN bspLeaf_t bspLeafs[ MAX_MAP_LEAFS ]; + +Q_EXTERN int numBSPPlanes Q_ASSIGN( 0 ); +Q_EXTERN bspPlane_t bspPlanes[ MAX_MAP_PLANES ]; + +Q_EXTERN int numBSPNodes Q_ASSIGN( 0 ); +Q_EXTERN bspNode_t bspNodes[ MAX_MAP_NODES ]; + +Q_EXTERN int numBSPLeafSurfaces Q_ASSIGN( 0 ); +Q_EXTERN int bspLeafSurfaces[ MAX_MAP_LEAFFACES ]; + +Q_EXTERN int numBSPLeafBrushes Q_ASSIGN( 0 ); +Q_EXTERN int bspLeafBrushes[ MAX_MAP_LEAFBRUSHES ]; + +Q_EXTERN int numBSPBrushes Q_ASSIGN( 0 ); +Q_EXTERN bspBrush_t bspBrushes[ MAX_MAP_BRUSHES ]; + +Q_EXTERN int numBSPBrushSides Q_ASSIGN( 0 ); +Q_EXTERN bspBrushSide_t bspBrushSides[ MAX_MAP_BRUSHSIDES ]; + +Q_EXTERN int numBSPLightBytes Q_ASSIGN( 0 ); +Q_EXTERN byte *bspLightBytes Q_ASSIGN( NULL ); + +//% Q_EXTERN int numBSPGridPoints Q_ASSIGN( 0 ); +//% Q_EXTERN byte *bspGridPoints Q_ASSIGN( NULL ); + +Q_EXTERN int numBSPGridPoints Q_ASSIGN( 0 ); +Q_EXTERN bspGridPoint_t *bspGridPoints Q_ASSIGN( NULL ); + +Q_EXTERN int numBSPVisBytes Q_ASSIGN( 0 ); +Q_EXTERN byte bspVisBytes[ MAX_MAP_VISIBILITY ]; + +Q_EXTERN int numBSPDrawVerts Q_ASSIGN( 0 ); +Q_EXTERN bspDrawVert_t *bspDrawVerts Q_ASSIGN( NULL ); + +Q_EXTERN int numBSPDrawIndexes Q_ASSIGN( 0 ); +Q_EXTERN int bspDrawIndexes[ MAX_MAP_DRAW_INDEXES ]; + +Q_EXTERN int numBSPDrawSurfaces Q_ASSIGN( 0 ); +Q_EXTERN bspDrawSurface_t *bspDrawSurfaces Q_ASSIGN( NULL ); + +Q_EXTERN int numBSPFogs Q_ASSIGN( 0 ); +Q_EXTERN bspFog_t bspFogs[ MAX_MAP_FOGS ]; + +Q_EXTERN int numBSPAds Q_ASSIGN( 0 ); +Q_EXTERN bspAdvertisement_t bspAds[ MAX_MAP_ADVERTISEMENTS ]; + + +/* end marker */ +#endif diff --git a/tools/quake3/q3map2/q3map2.ico b/tools/quake3/q3map2/q3map2.ico new file mode 100644 index 0000000000000000000000000000000000000000..4fe56fa66b6c64f555216795121402f9f324afbc GIT binary patch literal 1078 zcmc(eJCcJi42DIr^_nA4?c^AHB)XLLHC%BOj+2sZ$CdL*1_Fdhm^3S_$8Sj^%N8P) zoTO=LQM#Q(e$aJY@TC&@K8sw~Yv5nFk}sr4Aa}NJ`7WneG!JdqG{G?` OHfpDO`oGcO$9xA8=ia6O literal 0 HcmV?d00001 diff --git a/tools/quake3/q3map2/q3map2.rc b/tools/quake3/q3map2/q3map2.rc new file mode 100644 index 00000000..78cf7b3a --- /dev/null +++ b/tools/quake3/q3map2/q3map2.rc @@ -0,0 +1 @@ +101 ICON DISCARDABLE "q3map2.ico" diff --git a/tools/quake3/q3map2/q3map2.vcproj b/tools/quake3/q3map2/q3map2.vcproj new file mode 100644 index 00000000..182f50c1 --- /dev/null +++ b/tools/quake3/q3map2/q3map2.vcproj @@ -0,0 +1,480 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/quake3/q3map2/q3map2_VC9.vcproj b/tools/quake3/q3map2/q3map2_VC9.vcproj new file mode 100644 index 00000000..06fdc3e3 --- /dev/null +++ b/tools/quake3/q3map2/q3map2_VC9.vcproj @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/quake3/q3map2/shaders.c b/tools/quake3/q3map2/shaders.c new file mode 100644 index 00000000..cd269585 --- /dev/null +++ b/tools/quake3/q3map2/shaders.c @@ -0,0 +1,2068 @@ +/* ------------------------------------------------------------------------------- + +Copyright (C) 1999-2007 id Software, Inc. and contributors. +For a list of contributors, see the accompanying CONTRIBUTORS file. + +This file is part of GtkRadiant. + +GtkRadiant is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +GtkRadiant is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GtkRadiant; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +---------------------------------------------------------------------------------- + +This code has been altered significantly from its original form, to support +several games based on the Quake III Arena engine, in the form of "Q3Map2." + +------------------------------------------------------------------------------- */ + + + +/* marker */ +#define SHADERS_C + + + +/* dependencies */ +#include "q3map2.h" + + + +/* +ColorMod() +routines for dealing with vertex color/alpha modification +*/ + +void ColorMod( colorMod_t *cm, int numVerts, bspDrawVert_t *drawVerts ) +{ + int i, j, k; + float c; + vec4_t mult, add; + bspDrawVert_t *dv; + colorMod_t *cm2; + + + /* dummy check */ + if( cm == NULL || numVerts < 1 || drawVerts == NULL ) + return; + + + /* walk vertex list */ + for( i = 0; i < numVerts; i++ ) + { + /* get vertex */ + dv = &drawVerts[ i ]; + + /* walk colorMod list */ + for( cm2 = cm; cm2 != NULL; cm2 = cm2->next ) + { + /* default */ + VectorSet( mult, 1.0f, 1.0f, 1.0f ); + mult[ 3 ] = 1.0f; + VectorSet( add, 0.0f, 0.0f, 0.0f ); + mult[ 3 ] = 0.0f; + + /* switch on type */ + switch( cm2->type ) + { + case CM_COLOR_SET: + VectorClear( mult ); + VectorScale( cm2->data, 255.0f, add ); + break; + + case CM_ALPHA_SET: + mult[ 3 ] = 0.0f; + add[ 3 ] = cm2->data[ 0 ] * 255.0f; + break; + + case CM_COLOR_SCALE: + VectorCopy( cm2->data, mult ); + break; + + case CM_ALPHA_SCALE: + mult[ 3 ] = cm2->data[ 0 ]; + break; + + case CM_COLOR_DOT_PRODUCT: + c = DotProduct( dv->normal, cm2->data ); + VectorSet( mult, c, c, c ); + break; + + case CM_ALPHA_DOT_PRODUCT: + mult[ 3 ] = DotProduct( dv->normal, cm2->data ); + break; + + case CM_COLOR_DOT_PRODUCT_2: + c = DotProduct( dv->normal, cm2->data ); + c *= c; + VectorSet( mult, c, c, c ); + break; + + case CM_ALPHA_DOT_PRODUCT_2: + mult[ 3 ] = DotProduct( dv->normal, cm2->data ); + mult[ 3 ] *= mult[ 3 ]; + break; + + default: + break; + } + + /* apply mod */ + for( j = 0; j < MAX_LIGHTMAPS; j++ ) + { + for( k = 0; k < 4; k++ ) + { + c = (mult[ k ] * dv->color[ j ][ k ]) + add[ k ]; + if( c < 0 ) + c = 0; + else if( c > 255 ) + c = 255; + dv->color[ j ][ k ] = c; + } + } + } + } +} + + + +/* +TCMod*() +routines for dealing with a 3x3 texture mod matrix +*/ + +void TCMod( tcMod_t mod, float st[ 2 ] ) +{ + float old[ 2 ]; + + + old[ 0 ] = st[ 0 ]; + old[ 1 ] = st[ 1 ]; + st[ 0 ] = (mod[ 0 ][ 0 ] * old[ 0 ]) + (mod[ 0 ][ 1 ] * old[ 1 ]) + mod[ 0 ][ 2 ]; + st[ 1 ] = (mod[ 1 ][ 0 ] * old[ 0 ]) + (mod[ 1 ][ 1 ] * old[ 1 ]) + mod[ 1 ][ 2 ]; +} + + +void TCModIdentity( tcMod_t mod ) +{ + mod[ 0 ][ 0 ] = 1.0f; mod[ 0 ][ 1 ] = 0.0f; mod[ 0 ][ 2 ] = 0.0f; + mod[ 1 ][ 0 ] = 0.0f; mod[ 1 ][ 1 ] = 1.0f; mod[ 1 ][ 2 ] = 0.0f; + mod[ 2 ][ 0 ] = 0.0f; mod[ 2 ][ 1 ] = 0.0f; mod[ 2 ][ 2 ] = 1.0f; /* this row is only used for multiples, not transformation */ +} + + +void TCModMultiply( tcMod_t a, tcMod_t b, tcMod_t out ) +{ + int i; + + + for( i = 0; i < 3; i++ ) + { + out[ i ][ 0 ] = (a[ i ][ 0 ] * b[ 0 ][ 0 ]) + (a[ i ][ 1 ] * b[ 1 ][ 0 ]) + (a[ i ][ 2 ] * b[ 2 ][ 0 ]); + out[ i ][ 1 ] = (a[ i ][ 0 ] * b[ 0 ][ 1 ]) + (a[ i ][ 1 ] * b[ 1 ][ 1 ]) + (a[ i ][ 2 ] * b[ 2 ][ 1 ]); + out[ i ][ 2 ] = (a[ i ][ 0 ] * b[ 0 ][ 2 ]) + (a[ i ][ 1 ] * b[ 1 ][ 2 ]) + (a[ i ][ 2 ] * b[ 2 ][ 2 ]); + } +} + + +void TCModTranslate( tcMod_t mod, float s, float t ) +{ + mod[ 0 ][ 2 ] += s; + mod[ 1 ][ 2 ] += t; +} + + +void TCModScale( tcMod_t mod, float s, float t ) +{ + mod[ 0 ][ 0 ] *= s; + mod[ 1 ][ 1 ] *= t; +} + + +void TCModRotate( tcMod_t mod, float euler ) +{ + tcMod_t old, temp; + float radians, sinv, cosv; + + + memcpy( old, mod, sizeof( tcMod_t ) ); + TCModIdentity( temp ); + + radians = euler / 180 * Q_PI; + sinv = sin( radians ); + cosv = cos( radians ); + + temp[ 0 ][ 0 ] = cosv; temp[ 0 ][ 1 ] = -sinv; + temp[ 1 ][ 0 ] = sinv; temp[ 1 ][ 1 ] = cosv; + + TCModMultiply( old, temp, mod ); +} + + + +/* +ApplySurfaceParm() - ydnar +applies a named surfaceparm to the supplied flags +*/ + +qboolean ApplySurfaceParm( char *name, int *contentFlags, int *surfaceFlags, int *compileFlags ) +{ + int i, fake; + surfaceParm_t *sp; + + + /* dummy check */ + if( name == NULL ) + name = ""; + if( contentFlags == NULL ) + contentFlags = &fake; + if( surfaceFlags == NULL ) + surfaceFlags = &fake; + if( compileFlags == NULL ) + compileFlags = &fake; + + /* walk the current game's surfaceparms */ + sp = game->surfaceParms; + while( sp->name != NULL ) + { + /* match? */ + if( !Q_stricmp( name, sp->name ) ) + { + /* clear and set flags */ + *contentFlags &= ~(sp->contentFlagsClear); + *contentFlags |= sp->contentFlags; + *surfaceFlags &= ~(sp->surfaceFlagsClear); + *surfaceFlags |= sp->surfaceFlags; + *compileFlags &= ~(sp->compileFlagsClear); + *compileFlags |= sp->compileFlags; + + /* return ok */ + return qtrue; + } + + /* next */ + sp++; + } + + /* check custom info parms */ + for( i = 0; i < numCustSurfaceParms; i++ ) + { + /* get surfaceparm */ + sp = &custSurfaceParms[ i ]; + + /* match? */ + if( !Q_stricmp( name, sp->name ) ) + { + /* clear and set flags */ + *contentFlags &= ~(sp->contentFlagsClear); + *contentFlags |= sp->contentFlags; + *surfaceFlags &= ~(sp->surfaceFlagsClear); + *surfaceFlags |= sp->surfaceFlags; + *compileFlags &= ~(sp->compileFlagsClear); + *compileFlags |= sp->compileFlags; + + /* return ok */ + return qtrue; + } + } + + /* no matching surfaceparm found */ + return qfalse; +} + + + +/* +BeginMapShaderFile() - ydnar +erases and starts a new map shader script +*/ + +void BeginMapShaderFile( const char *mapFile ) +{ + char base[ 1024 ]; + int len; + + + /* dummy check */ + mapName[ 0 ] = '\0'; + mapShaderFile[ 0 ] = '\0'; + if( mapFile == NULL || mapFile[ 0 ] == '\0' ) + return; + + /* copy map name */ + strcpy( base, mapFile ); + StripExtension( base ); + + /* extract map name */ + len = strlen( base ) - 1; + while( len > 0 && base[ len ] != '/' && base[ len ] != '\\' ) + len--; + strcpy( mapName, &base[ len + 1 ] ); + base[ len ] = '\0'; + if( len <= 0 ) + return; + + /* append ../scripts/q3map2_.shader */ + sprintf( mapShaderFile, "%s/../%s/q3map2_%s.shader", base, game->shaderPath, mapName ); + Sys_FPrintf( SYS_VRB, "Map has shader script %s\n", mapShaderFile ); + + /* remove it */ + remove( mapShaderFile ); + + /* stop making warnings about missing images */ + warnImage = qfalse; +} + + + +/* +WriteMapShaderFile() - ydnar +writes a shader to the map shader script +*/ + +void WriteMapShaderFile( void ) +{ + FILE *file; + shaderInfo_t *si; + int i, num; + + + /* dummy check */ + if( mapShaderFile[ 0 ] == '\0' ) + return; + + /* are there any custom shaders? */ + for( i = 0, num = 0; i < numShaderInfo; i++ ) + { + if( shaderInfo[ i ].custom ) + break; + } + if( i == numShaderInfo ) + return; + + /* note it */ + Sys_FPrintf( SYS_VRB, "--- WriteMapShaderFile ---\n"); + Sys_FPrintf( SYS_VRB, "Writing %s", mapShaderFile ); + + /* open shader file */ + file = fopen( mapShaderFile, "w" ); + if( file == NULL ) + { + Sys_Printf( "WARNING: Unable to open map shader file %s for writing\n", mapShaderFile ); + return; + } + + /* print header */ + fprintf( file, + "// Custom shader file for %s.bsp\n" + "// Generated by Q3Map2 (ydnar)\n" + "// Do not edit! This file is overwritten on recompiles.\n\n", + mapName ); + + /* walk the shader list */ + for( i = 0, num = 0; i < numShaderInfo; i++ ) + { + /* get the shader and print it */ + si = &shaderInfo[ i ]; + if( si->custom == qfalse || si->shaderText == NULL || si->shaderText[ 0 ] == '\0' ) + continue; + num++; + + /* print it to the file */ + fprintf( file, "%s%s\n", si->shader, si->shaderText ); + //Sys_Printf( "%s%s\n", si->shader, si->shaderText ); /* FIXME: remove debugging code */ + + Sys_FPrintf( SYS_VRB, "." ); + } + + /* close the shader */ + fflush( file ); + fclose( file ); + + Sys_FPrintf( SYS_VRB, "\n" ); + + /* print some stats */ + Sys_Printf( "%9d custom shaders emitted\n", num ); +} + + + +/* +CustomShader() - ydnar +sets up a custom map shader +*/ + +shaderInfo_t *CustomShader( shaderInfo_t *si, char *find, char *replace ) +{ + shaderInfo_t *csi; + char shader[ MAX_QPATH ]; + char *s; + int loc; + MHASH mh; + byte digest[ 16 ]; + char *srcShaderText, temp[ 8192 ], shaderText[ 8192 ]; /* ydnar: fixme (make this bigger?) */ + + + /* dummy check */ + if( si == NULL ) + return ShaderInfoForShader( "default" ); + + /* default shader text source */ + srcShaderText = si->shaderText; + + /* et: implicitMap */ + if( si->implicitMap == IM_OPAQUE ) + { + srcShaderText = temp; + sprintf( temp, "\n" + "{ // Q3Map2 defaulted (implicitMap)\n" + "\t{\n" + "\t\tmap $lightmap\n" + "\t\trgbGen identity\n" + "\t}\n" + "\tq3map_styleMarker\n" + "\t{\n" + "\t\tmap %s\n" + "\t\tblendFunc GL_DST_COLOR GL_ZERO\n" + "\t\trgbGen identity\n" + "\t}\n" + "}\n", + si->implicitImagePath ); + } + + /* et: implicitMask */ + else if( si->implicitMap == IM_MASKED ) + { + srcShaderText = temp; + sprintf( temp, "\n" + "{ // Q3Map2 defaulted (implicitMask)\n" + "\tcull none\n" + "\t{\n" + "\t\tmap %s\n" + "\t\talphaFunc GE128\n" + "\t\tdepthWrite\n" + "\t}\n" + "\t{\n" + "\t\tmap $lightmap\n" + "\t\trgbGen identity\n" + "\t\tdepthFunc equal\n" + "\t}\n" + "\tq3map_styleMarker\n" + "\t{\n" + "\t\tmap %s\n" + "\t\tblendFunc GL_DST_COLOR GL_ZERO\n" + "\t\tdepthFunc equal\n" + "\t\trgbGen identity\n" + "\t}\n" + "}\n", + si->implicitImagePath, + si->implicitImagePath ); + } + + /* et: implicitBlend */ + else if( si->implicitMap == IM_BLEND ) + { + srcShaderText = temp; + sprintf( temp, "\n" + "{ // Q3Map2 defaulted (implicitBlend)\n" + "\tcull none\n" + "\t{\n" + "\t\tmap %s\n" + "\t\tblendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA\n" + "\t}\n" + "\t{\n" + "\t\tmap $lightmap\n" + "\t\trgbGen identity\n" + "\t\tblendFunc GL_DST_COLOR GL_ZERO\n" + "\t}\n" + "\tq3map_styleMarker\n" + "}\n", + si->implicitImagePath ); + } + + /* default shader text */ + else if( srcShaderText == NULL ) + { + srcShaderText = temp; + sprintf( temp, "\n" + "{ // Q3Map2 defaulted\n" + "\t{\n" + "\t\tmap $lightmap\n" + "\t\trgbGen identity\n" + "\t}\n" + "\tq3map_styleMarker\n" + "\t{\n" + "\t\tmap %s.tga\n" + "\t\tblendFunc GL_DST_COLOR GL_ZERO\n" + "\t\trgbGen identity\n" + "\t}\n" + "}\n", + si->shader ); + } + + /* error check */ + if( (strlen( mapName ) + 1 + 32) > MAX_QPATH ) + Error( "Custom shader name length (%d) exceeded. Shorten your map name.\n", MAX_QPATH ); + + /* do some bad find-replace */ + s = strstr( srcShaderText, find ); + if( s == NULL ) + //% strcpy( shaderText, srcShaderText ); + return si; /* testing just using the existing shader if this fails */ + else + { + /* substitute 'find' with 'replace' */ + loc = s - srcShaderText; + strcpy( shaderText, srcShaderText ); + shaderText[ loc ] = '\0'; + strcat( shaderText, replace ); + strcat( shaderText, &srcShaderText[ loc + strlen( find ) ] ); + } + + /* make md5 hash of the shader text */ + mh = mhash_init( MHASH_MD5 ); + if( !mh ) + Error( "Unable to initialize MD5 hash context" ); + mhash( mh, shaderText, strlen( shaderText ) ); + mhash_deinit( mh, digest ); + + /* mangle hash into a shader name */ + sprintf( shader, "%s/%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", mapName, + digest[ 0 ], digest[ 1 ], digest[ 2 ], digest[ 3 ], digest[ 4 ], digest[ 5 ], digest[ 6 ], digest[ 7 ], + digest[ 8 ], digest[ 9 ], digest[ 10 ], digest[ 11 ], digest[ 12 ], digest[ 13 ], digest[ 14 ], digest[ 15 ] ); + + /* get shader */ + csi = ShaderInfoForShader( shader ); + + /* might be a preexisting shader */ + if( csi->custom ) + return csi; + + /* clone the existing shader and rename */ + memcpy( csi, si, sizeof( shaderInfo_t ) ); + strcpy( csi->shader, shader ); + csi->custom = qtrue; + + /* store new shader text */ + csi->shaderText = safe_malloc( strlen( shaderText ) + 1 ); + strcpy( csi->shaderText, shaderText ); /* LEAK! */ + + /* return it */ + return csi; +} + + + +/* +EmitVertexRemapShader() +adds a vertexremapshader key/value pair to worldspawn +*/ + +void EmitVertexRemapShader( char *from, char *to ) +{ + MHASH mh; + byte digest[ 16 ]; + char key[ 64 ], value[ 256 ]; + + + /* dummy check */ + if( from == NULL || from[ 0 ] == '\0' || + to == NULL || to[ 0 ] == '\0' ) + return; + + /* build value */ + sprintf( value, "%s;%s", from, to ); + + /* make md5 hash */ + mh = mhash_init( MHASH_MD5 ); + if( !mh ) + Error( "Unable to initialize MD5 hash context" ); + mhash( mh, value, strlen( value ) ); + mhash_deinit( mh, digest ); + + /* make key (this is annoying, as vertexremapshader is precisely 17 characters, + which is one too long, so we leave off the last byte of the md5 digest) */ + sprintf( key, "vertexremapshader%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + digest[ 0 ], digest[ 1 ], digest[ 2 ], digest[ 3 ], digest[ 4 ], digest[ 5 ], digest[ 6 ], digest[ 7 ], + digest[ 8 ], digest[ 9 ], digest[ 10 ], digest[ 11 ], digest[ 12 ], digest[ 13 ], digest[ 14 ] ); /* no: digest[ 15 ] */ + + /* add key/value pair to worldspawn */ + SetKeyValue( &entities[ 0 ], key, value ); +} + + + +/* +AllocShaderInfo() +allocates and initializes a new shader +*/ + +static shaderInfo_t *AllocShaderInfo( void ) +{ + shaderInfo_t *si; + + + /* allocate? */ + if( shaderInfo == NULL ) + { + shaderInfo = safe_malloc( sizeof( shaderInfo_t ) * MAX_SHADER_INFO ); + numShaderInfo = 0; + } + + /* bounds check */ + if( numShaderInfo == MAX_SHADER_INFO ) + Error( "MAX_SHADER_INFO exceeded. Remove some PK3 files or shader scripts from shaderlist.txt and try again." ); + si = &shaderInfo[ numShaderInfo ]; + numShaderInfo++; + + /* ydnar: clear to 0 first */ + memset( si, 0, sizeof( shaderInfo_t ) ); + + /* set defaults */ + ApplySurfaceParm( "default", &si->contentFlags, &si->surfaceFlags, &si->compileFlags ); + + si->backsplashFraction = DEF_BACKSPLASH_FRACTION; + si->backsplashDistance = DEF_BACKSPLASH_DISTANCE; + + si->bounceScale = DEF_RADIOSITY_BOUNCE; + + si->lightStyle = LS_NORMAL; + + si->polygonOffset = qfalse; + + si->shadeAngleDegrees = 0.0f; + si->lightmapSampleSize = 0; + si->lightmapSampleOffset = DEFAULT_LIGHTMAP_SAMPLE_OFFSET; + si->patchShadows = qfalse; + si->vertexShadows = qtrue; /* ydnar: changed default behavior */ + si->forceSunlight = qfalse; + si->vertexScale = 1.0; + si->notjunc = qfalse; + + /* ydnar: set texture coordinate transform matrix to identity */ + TCModIdentity( si->mod ); + + /* ydnar: lightmaps can now be > 128x128 in certain games or an externally generated tga */ + si->lmCustomWidth = lmCustomSize; + si->lmCustomHeight = lmCustomSize; + + /* return to sender */ + return si; +} + + + +/* +FinishShader() - ydnar +sets a shader's width and height among other things +*/ + +void FinishShader( shaderInfo_t *si ) +{ + int x, y; + float st[ 2 ], o[ 2 ], dist, bestDist; + vec4_t color, bestColor, delta; + + + /* don't double-dip */ + if( si->finished ) + return; + + /* if they're explicitly set, copy from image size */ + if( si->shaderWidth == 0 && si->shaderHeight == 0 ) + { + si->shaderWidth = si->shaderImage->width; + si->shaderHeight = si->shaderImage->height; + } + + /* legacy terrain has explicit image-sized texture projection */ + if( si->legacyTerrain && si->tcGen == qfalse ) + { + /* set xy texture projection */ + si->tcGen = qtrue; + VectorSet( si->vecs[ 0 ], (1.0f / (si->shaderWidth * 0.5f)), 0, 0 ); + VectorSet( si->vecs[ 1 ], 0, (1.0f / (si->shaderHeight * 0.5f)), 0 ); + } + + /* find pixel coordinates best matching the average color of the image */ + bestDist = 99999999; + o[ 0 ] = 1.0f / si->shaderImage->width; + o[ 1 ] = 1.0f / si->shaderImage->height; + for( y = 0, st[ 1 ] = 0.0f; y < si->shaderImage->height; y++, st[ 1 ] += o[ 1 ] ) + { + for( x = 0, st[ 0 ] = 0.0f; x < si->shaderImage->width; x++, st[ 0 ] += o[ 0 ] ) + { + /* sample the shader image */ + RadSampleImage( si->shaderImage->pixels, si->shaderImage->width, si->shaderImage->height, st, color ); + + /* determine error squared */ + VectorSubtract( color, si->averageColor, delta ); + delta[ 3 ] = color[ 3 ] - si->averageColor[ 3 ]; + dist = delta[ 0 ] * delta[ 0 ] + delta[ 1 ] * delta[ 1 ] + delta[ 2 ] * delta[ 2 ] + delta[ 3 ] * delta[ 3 ]; + if( dist < bestDist ) + { + VectorCopy( color, bestColor ); + bestColor[ 3 ] = color[ 3 ]; + si->stFlat[ 0 ] = st[ 0 ]; + si->stFlat[ 1 ] = st[ 1 ]; + } + } + } + + /* set to finished */ + si->finished = qtrue; +} + + + +/* +LoadShaderImages() +loads a shader's images +ydnar: image.c made this a bit simpler +*/ + +static void LoadShaderImages( shaderInfo_t *si ) +{ + int i, count; + float color[ 4 ]; + + + /* nodraw shaders don't need images */ + if( si->compileFlags & C_NODRAW ) + si->shaderImage = ImageLoad( DEFAULT_IMAGE ); + else + { + /* try to load editor image first */ + si->shaderImage = ImageLoad( si->editorImagePath ); + + /* then try shadername */ + if( si->shaderImage == NULL ) + si->shaderImage = ImageLoad( si->shader ); + + /* then try implicit image path (note: new behavior!) */ + if( si->shaderImage == NULL ) + si->shaderImage = ImageLoad( si->implicitImagePath ); + + /* then try lightimage (note: new behavior!) */ + if( si->shaderImage == NULL ) + si->shaderImage = ImageLoad( si->lightImagePath ); + + /* otherwise, use default image */ + if( si->shaderImage == NULL ) + { + si->shaderImage = ImageLoad( DEFAULT_IMAGE ); + if( warnImage && strcmp( si->shader, "noshader" ) ) + Sys_Printf( "WARNING: Couldn't find image for shader %s\n", si->shader ); + } + + /* load light image */ + si->lightImage = ImageLoad( si->lightImagePath ); + + /* load normalmap image (ok if this is NULL) */ + si->normalImage = ImageLoad( si->normalImagePath ); + if( si->normalImage != NULL ) + { + Sys_FPrintf( SYS_VRB, "Shader %s has\n" + " NM %s\n", si->shader, si->normalImagePath ); + } + } + + /* if no light image, use shader image */ + if( si->lightImage == NULL ) + si->lightImage = ImageLoad( si->shaderImage->name ); + + /* create default and average colors */ + count = si->lightImage->width * si->lightImage->height; + VectorClear( color ); + color[ 3 ] = 0.0f; + for( i = 0; i < count; i++ ) + { + color[ 0 ] += si->lightImage->pixels[ i * 4 + 0 ]; + color[ 1 ] += si->lightImage->pixels[ i * 4 + 1 ]; + color[ 2 ] += si->lightImage->pixels[ i * 4 + 2 ]; + color[ 3 ] += si->lightImage->pixels[ i * 4 + 3 ]; + } + + if( VectorLength( si->color ) <= 0.0f ) + { + ColorNormalize( color, si->color ); + VectorScale( color, (1.0f / count), si->averageColor ); + } + else + { + VectorCopy( si->color, si->averageColor ); + } +} + + + +/* +ShaderInfoForShader() +finds a shaderinfo for a named shader +*/ + +shaderInfo_t *ShaderInfoForShader( const char *shaderName ) +{ + int i; + shaderInfo_t *si; + char shader[ MAX_QPATH ]; + + + /* dummy check */ + if( shaderName == NULL || shaderName[ 0 ] == '\0' ) + { + Sys_Printf( "WARNING: Null or empty shader name\n" ); + shaderName = "missing"; + } + + /* strip off extension */ + strcpy( shader, shaderName ); + StripExtension( shader ); + + /* search for it */ + for( i = 0; i < numShaderInfo; i++ ) + { + si = &shaderInfo[ i ]; + if( !Q_stricmp( shader, si->shader ) ) + { + /* load image if necessary */ + if( si->finished == qfalse ) + { + LoadShaderImages( si ); + FinishShader( si ); + } + + /* return it */ + return si; + } + } + + /* allocate a default shader */ + si = AllocShaderInfo(); + strcpy( si->shader, shader ); + LoadShaderImages( si ); + FinishShader( si ); + + /* return it */ + return si; +} + + + +/* +GetTokenAppend() - ydnar +gets a token and appends its text to the specified buffer +*/ + +static int oldScriptLine = 0; +static int tabDepth = 0; + +qboolean GetTokenAppend( char *buffer, qboolean crossline ) +{ + qboolean r; + int i; + + + /* get the token */ + r = GetToken( crossline ); + if( r == qfalse || buffer == NULL || token[ 0 ] == '\0' ) + return r; + + /* pre-tabstops */ + if( token[ 0 ] == '}' ) + tabDepth--; + + /* append? */ + if( oldScriptLine != scriptline ) + { + strcat( buffer, "\n" ); + for( i = 0; i < tabDepth; i++ ) + strcat( buffer, "\t" ); + } + else + strcat( buffer, " " ); + oldScriptLine = scriptline; + strcat( buffer, token ); + + /* post-tabstops */ + if( token[ 0 ] == '{' ) + tabDepth++; + + /* return */ + return r; +} + + +void Parse1DMatrixAppend( char *buffer, int x, vec_t *m ) +{ + int i; + + + if( !GetTokenAppend( buffer, qtrue ) || strcmp( token, "(" ) ) + Error( "Parse1DMatrixAppend(): line %d: ( not found!", scriptline ); + for( i = 0; i < x; i++ ) + { + if( !GetTokenAppend( buffer, qfalse ) ) + Error( "Parse1DMatrixAppend(): line %d: Number not found!", scriptline ); + m[ i ] = atof( token ); + } + if( !GetTokenAppend( buffer, qtrue ) || strcmp( token, ")" ) ) + Error( "Parse1DMatrixAppend(): line %d: ) not found!", scriptline ); +} + + + + +/* +ParseShaderFile() +parses a shader file into discrete shaderInfo_t +*/ + +static void ParseShaderFile( const char *filename ) +{ + int i, val; + shaderInfo_t *si; + char *suffix, temp[ 1024 ]; + char shaderText[ 8192 ]; /* ydnar: fixme (make this bigger?) */ + + + /* init */ + si = NULL; + shaderText[ 0 ] = '\0'; + + /* load the shader */ + LoadScriptFile( filename, 0 ); + + /* tokenize it */ + while( 1 ) + { + /* copy shader text to the shaderinfo */ + if( si != NULL && shaderText[ 0 ] != '\0' ) + { + strcat( shaderText, "\n" ); + si->shaderText = safe_malloc( strlen( shaderText ) + 1 ); + strcpy( si->shaderText, shaderText ); + //% if( VectorLength( si->vecs[ 0 ] ) ) + //% Sys_Printf( "%s\n", shaderText ); + } + + /* ydnar: clear shader text buffer */ + shaderText[ 0 ] = '\0'; + + /* test for end of file */ + if( !GetToken( qtrue ) ) + break; + + /* shader name is initial token */ + si = AllocShaderInfo(); + strcpy( si->shader, token ); + + /* ignore ":q3map" suffix */ + suffix = strstr( si->shader, ":q3map" ); + if( suffix != NULL ) + *suffix = '\0'; + + /* handle { } section */ + if( !GetTokenAppend( shaderText, qtrue ) ) + break; + if( strcmp( token, "{" ) ) + { + if( si != NULL ) + Error( "ParseShaderFile(): %s, line %d: { not found!\nFound instead: %s\nLast known shader: %s", + filename, scriptline, token, si->shader ); + else + Error( "ParseShaderFile(): %s, line %d: { not found!\nFound instead: %s", + filename, scriptline, token ); + } + + while( 1 ) + { + /* get the next token */ + if( !GetTokenAppend( shaderText, qtrue ) ) + break; + if( !strcmp( token, "}" ) ) + break; + + + /* ----------------------------------------------------------------- + shader stages (passes) + ----------------------------------------------------------------- */ + + /* parse stage directives */ + if( !strcmp( token, "{" ) ) + { + si->hasPasses = qtrue; + while( 1 ) + { + if( !GetTokenAppend( shaderText, qtrue ) ) + break; + if( !strcmp( token, "}" ) ) + break; + + /* only care about images if we don't have a editor/light image */ + if( si->editorImagePath[ 0 ] == '\0' && si->lightImagePath[ 0 ] == '\0' && si->implicitImagePath[ 0 ] == '\0' ) + { + /* digest any images */ + if( !Q_stricmp( token, "map" ) || + !Q_stricmp( token, "clampMap" ) || + !Q_stricmp( token, "animMap" ) || + !Q_stricmp( token, "clampAnimMap" ) || + !Q_stricmp( token, "clampMap" ) || + !Q_stricmp( token, "mapComp" ) || + !Q_stricmp( token, "mapNoComp" ) ) + { + /* skip one token for animated stages */ + if( !Q_stricmp( token, "animMap" ) || !Q_stricmp( token, "clampAnimMap" ) ) + GetTokenAppend( shaderText, qfalse ); + + /* get an image */ + GetTokenAppend( shaderText, qfalse ); + if( token[ 0 ] != '*' && token[ 0 ] != '$' ) + { + strcpy( si->lightImagePath, token ); + DefaultExtension( si->lightImagePath, ".tga" ); + + /* debug code */ + //% Sys_FPrintf( SYS_VRB, "Deduced shader image: %s\n", si->lightImagePath ); + } + } + } + } + } + + + /* ----------------------------------------------------------------- + surfaceparm * directives + ----------------------------------------------------------------- */ + + /* match surfaceparm */ + else if( !Q_stricmp( token, "surfaceparm" ) ) + { + GetTokenAppend( shaderText, qfalse ); + if( ApplySurfaceParm( token, &si->contentFlags, &si->surfaceFlags, &si->compileFlags ) == qfalse ) + Sys_Printf( "WARNING: Unknown surfaceparm: \"%s\"\n", token ); + } + + + /* ----------------------------------------------------------------- + game-related shader directives + ----------------------------------------------------------------- */ + + /* ydnar: fogparms (for determining fog volumes) */ + else if( !Q_stricmp( token, "fogparms" ) ) + si->fogParms = qtrue; + + /* ydnar: polygonoffset (for no culling) */ + else if( !Q_stricmp( token, "polygonoffset" ) ) + si->polygonOffset = qtrue; + + /* tesssize is used to force liquid surfaces to subdivide */ + else if( !Q_stricmp( token, "tessSize" ) || !Q_stricmp( token, "q3map_tessSize" ) /* sof2 */ ) + { + GetTokenAppend( shaderText, qfalse ); + si->subdivisions = atof( token ); + } + + /* cull none will set twoSided (ydnar: added disable too) */ + else if ( !Q_stricmp( token, "cull" ) ) + { + GetTokenAppend( shaderText, qfalse ); + if( !Q_stricmp( token, "none" ) || !Q_stricmp( token, "disable" ) || !Q_stricmp( token, "twosided" ) ) + si->twoSided = qtrue; + } + + /* deformVertexes autosprite[ 2 ] + we catch this so autosprited surfaces become point + lights instead of area lights */ + else if( !Q_stricmp( token, "deformVertexes" ) ) + { + GetTokenAppend( shaderText, qfalse ); + + /* deformVertexes autosprite(2) */ + if( !Q_strncasecmp( token, "autosprite", 10 ) ) + { + /* set it as autosprite and detail */ + si->autosprite = qtrue; + ApplySurfaceParm( "detail", &si->contentFlags, &si->surfaceFlags, &si->compileFlags ); + + /* ydnar: gs mods: added these useful things */ + si->noClip = qtrue; + si->notjunc = qtrue; + } + + /* deformVertexes move (ydnar: for particle studio support) */ + if( !Q_stricmp( token, "move") ) + { + vec3_t amt, mins, maxs; + float base, amp; + + + /* get move amount */ + GetTokenAppend( shaderText, qfalse ); amt[ 0 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); amt[ 1 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); amt[ 2 ] = atof( token ); + + /* skip func */ + GetTokenAppend( shaderText, qfalse ); + + /* get base and amplitude */ + GetTokenAppend( shaderText, qfalse ); base = atof( token ); + GetTokenAppend( shaderText, qfalse ); amp = atof( token ); + + /* calculate */ + VectorScale( amt, base, mins ); + VectorMA( mins, amp, amt, maxs ); + VectorAdd( si->mins, mins, si->mins ); + VectorAdd( si->maxs, maxs, si->maxs ); + } + } + + /* light (old-style flare specification) */ + else if( !Q_stricmp( token, "light" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->flareShader = game->flareShader; + } + + /* ydnar: damageShader (sof2 mods) */ + else if( !Q_stricmp( token, "damageShader" ) ) + { + GetTokenAppend( shaderText, qfalse ); + if( token[ 0 ] != '\0' ) + { + si->damageShader = safe_malloc( strlen( token ) + 1 ); + strcpy( si->damageShader, token ); + } + GetTokenAppend( shaderText, qfalse ); /* don't do anything with health */ + } + + /* ydnar: enemy territory implicit shaders */ + else if( !Q_stricmp( token, "implicitMap" ) ) + { + si->implicitMap = IM_OPAQUE; + GetTokenAppend( shaderText, qfalse ); + if( token[ 0 ] == '-' && token[ 1 ] == '\0' ) + sprintf( si->implicitImagePath, "%s.tga", si->shader ); + else + strcpy( si->implicitImagePath, token ); + } + + else if( !Q_stricmp( token, "implicitMask" ) ) + { + si->implicitMap = IM_MASKED; + GetTokenAppend( shaderText, qfalse ); + if( token[ 0 ] == '-' && token[ 1 ] == '\0' ) + sprintf( si->implicitImagePath, "%s.tga", si->shader ); + else + strcpy( si->implicitImagePath, token ); + } + + else if( !Q_stricmp( token, "implicitBlend" ) ) + { + si->implicitMap = IM_MASKED; + GetTokenAppend( shaderText, qfalse ); + if( token[ 0 ] == '-' && token[ 1 ] == '\0' ) + sprintf( si->implicitImagePath, "%s.tga", si->shader ); + else + strcpy( si->implicitImagePath, token ); + } + + + /* ----------------------------------------------------------------- + image directives + ----------------------------------------------------------------- */ + + /* qer_editorimage */ + else if( !Q_stricmp( token, "qer_editorImage" ) ) + { + GetTokenAppend( shaderText, qfalse ); + strcpy( si->editorImagePath, token ); + DefaultExtension( si->editorImagePath, ".tga" ); + } + + /* ydnar: q3map_normalimage (bumpmapping normal map) */ + else if( !Q_stricmp( token, "q3map_normalImage" ) ) + { + GetTokenAppend( shaderText, qfalse ); + strcpy( si->normalImagePath, token ); + DefaultExtension( si->normalImagePath, ".tga" ); + } + + /* q3map_lightimage */ + else if( !Q_stricmp( token, "q3map_lightImage" ) ) + { + GetTokenAppend( shaderText, qfalse ); + strcpy( si->lightImagePath, token ); + DefaultExtension( si->lightImagePath, ".tga" ); + } + + /* ydnar: skyparms */ + else if( !Q_stricmp( token, "skyParms" ) ) + { + /* get image base */ + GetTokenAppend( shaderText, qfalse ); + + /* ignore bogus paths */ + if( Q_stricmp( token, "-" ) && Q_stricmp( token, "full" ) ) + { + strcpy( si->skyParmsImageBase, token ); + + /* use top image as sky light image */ + if( si->lightImagePath[ 0 ] == '\0' ) + sprintf( si->lightImagePath, "%s_up.tga", si->skyParmsImageBase ); + } + + /* skip rest of line */ + GetTokenAppend( shaderText, qfalse ); + GetTokenAppend( shaderText, qfalse ); + } + + /* ----------------------------------------------------------------- + q3map_* directives + ----------------------------------------------------------------- */ + + /* q3map_sun + color will be normalized, so it doesn't matter what range you use + intensity falls off with angle but not distance 100 is a fairly bright sun + degree of 0 = from the east, 90 = north, etc. altitude of 0 = sunrise/set, 90 = noon + ydnar: sof2map has bareword 'sun' token, so we support that as well */ + else if( !Q_stricmp( token, "sun" ) /* sof2 */ || !Q_stricmp( token, "q3map_sun" ) || !Q_stricmp( token, "q3map_sunExt" ) ) + { + float a, b; + sun_t *sun; + qboolean ext; + + + /* ydnar: extended sun directive? */ + if( !Q_stricmp( token, "q3map_sunext" ) ) + ext = qtrue; + + /* allocate sun */ + sun = safe_malloc( sizeof( *sun ) ); + memset( sun, 0, sizeof( *sun ) ); + + /* set style */ + sun->style = si->lightStyle; + + /* get color */ + GetTokenAppend( shaderText, qfalse ); + sun->color[ 0 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); + sun->color[ 1 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); + sun->color[ 2 ] = atof( token ); + + /* normalize it */ + VectorNormalize( sun->color, sun->color ); + + /* scale color by brightness */ + GetTokenAppend( shaderText, qfalse ); + sun->photons = atof( token ); + + /* get sun angle/elevation */ + GetTokenAppend( shaderText, qfalse ); + a = atof( token ); + a = a / 180.0f * Q_PI; + + GetTokenAppend( shaderText, qfalse ); + b = atof( token ); + b = b / 180.0f * Q_PI; + + sun->direction[ 0 ] = cos( a ) * cos( b ); + sun->direction[ 1 ] = sin( a ) * cos( b ); + sun->direction[ 2 ] = sin( b ); + + /* get filter radius from shader */ + sun->filterRadius = si->lightFilterRadius; + + /* ydnar: get sun angular deviance/samples */ + if( ext && TokenAvailable() ) + { + GetTokenAppend( shaderText, qfalse ); + sun->deviance = atof( token ); + sun->deviance = sun->deviance / 180.0f * Q_PI; + + GetTokenAppend( shaderText, qfalse ); + sun->numSamples = atoi( token ); + } + + /* store sun */ + sun->next = si->sun; + si->sun = sun; + + /* apply sky surfaceparm */ + ApplySurfaceParm( "sky", &si->contentFlags, &si->surfaceFlags, &si->compileFlags ); + + /* don't process any more tokens on this line */ + continue; + } + + /* match q3map_ */ + else if( !Q_strncasecmp( token, "q3map_", 6 ) ) + { + /* ydnar: q3map_baseShader (inherit this shader's parameters) */ + if( !Q_stricmp( token, "q3map_baseShader" ) ) + { + shaderInfo_t *si2; + qboolean oldWarnImage; + + + /* get shader */ + GetTokenAppend( shaderText, qfalse ); + //% Sys_FPrintf( SYS_VRB, "Shader %s has base shader %s\n", si->shader, token ); + oldWarnImage = warnImage; + warnImage = qfalse; + si2 = ShaderInfoForShader( token ); + warnImage = oldWarnImage; + + /* subclass it */ + if( si2 != NULL ) + { + /* preserve name */ + strcpy( temp, si->shader ); + + /* copy shader */ + memcpy( si, si2, sizeof( *si ) ); + + /* restore name and set to unfinished */ + strcpy( si->shader, temp ); + si->shaderWidth = 0; + si->shaderHeight = 0; + si->finished = qfalse; + } + } + + /* ydnar: q3map_surfacemodel */ + else if( !Q_stricmp( token, "q3map_surfacemodel" ) ) + { + surfaceModel_t *model; + + + /* allocate new model and attach it */ + model = safe_malloc( sizeof( *model ) ); + memset( model, 0, sizeof( *model ) ); + model->next = si->surfaceModel; + si->surfaceModel = model; + + /* get parameters */ + GetTokenAppend( shaderText, qfalse ); + strcpy( model->model, token ); + + GetTokenAppend( shaderText, qfalse ); + model->density = atof( token ); + GetTokenAppend( shaderText, qfalse ); + model->odds = atof( token ); + + GetTokenAppend( shaderText, qfalse ); + model->minScale = atof( token ); + GetTokenAppend( shaderText, qfalse ); + model->maxScale = atof( token ); + + GetTokenAppend( shaderText, qfalse ); + model->minAngle = atof( token ); + GetTokenAppend( shaderText, qfalse ); + model->maxAngle = atof( token ); + + GetTokenAppend( shaderText, qfalse ); + model->oriented = (token[ 0 ] == '1' ? qtrue : qfalse); + } + + /* ydnar/sd: q3map_foliage */ + else if( !Q_stricmp( token, "q3map_foliage" ) ) + { + foliage_t *foliage; + + + /* allocate new foliage struct and attach it */ + foliage = safe_malloc( sizeof( *foliage ) ); + memset( foliage, 0, sizeof( *foliage ) ); + foliage->next = si->foliage; + si->foliage = foliage; + + /* get parameters */ + GetTokenAppend( shaderText, qfalse ); + strcpy( foliage->model, token ); + + GetTokenAppend( shaderText, qfalse ); + foliage->scale = atof( token ); + GetTokenAppend( shaderText, qfalse ); + foliage->density = atof( token ); + GetTokenAppend( shaderText, qfalse ); + foliage->odds = atof( token ); + GetTokenAppend( shaderText, qfalse ); + foliage->inverseAlpha = atoi( token ); + } + + /* ydnar: q3map_bounce (fraction of light to re-emit during radiosity passes) */ + else if( !Q_stricmp( token, "q3map_bounce" ) || !Q_stricmp( token, "q3map_bounceScale" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->bounceScale = atof( token ); + } + + /* ydnar/splashdamage: q3map_skyLight */ + else if( !Q_stricmp( token, "q3map_skyLight" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->skyLightValue = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->skyLightIterations = atoi( token ); + + /* clamp */ + if( si->skyLightValue < 0.0f ) + si->skyLightValue = 0.0f; + if( si->skyLightIterations < 2 ) + si->skyLightIterations = 2; + } + + /* q3map_surfacelight */ + else if( !Q_stricmp( token, "q3map_surfacelight" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->value = atof( token ); + } + + /* q3map_lightStyle (sof2/jk2 lightstyle) */ + else if( !Q_stricmp( token, "q3map_lightStyle" ) ) + { + GetTokenAppend( shaderText, qfalse ); + val = atoi( token ); + if( val < 0 ) + val = 0; + else if( val > LS_NONE ) + val = LS_NONE; + si->lightStyle = val; + } + + /* wolf: q3map_lightRGB */ + else if( !Q_stricmp( token, "q3map_lightRGB" ) ) + { + VectorClear( si->color ); + GetTokenAppend( shaderText, qfalse ); + si->color[ 0 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->color[ 1 ] = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->color[ 2 ] = atof( token ); + ColorNormalize( si->color, si->color ); + } + + /* q3map_lightSubdivide */ + else if( !Q_stricmp( token, "q3map_lightSubdivide" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->lightSubdivide = atoi( token ); + } + + /* q3map_backsplash */ + else if( !Q_stricmp( token, "q3map_backsplash" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->backsplashFraction = atof( token ) * 0.01f; + GetTokenAppend( shaderText, qfalse ); + si->backsplashDistance = atof( token ); + } + + /* q3map_lightmapSampleSize */ + else if( !Q_stricmp( token, "q3map_lightmapSampleSize" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->lightmapSampleSize = atoi( token ); + } + + /* q3map_lightmapSampleSffset */ + else if( !Q_stricmp( token, "q3map_lightmapSampleOffset" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->lightmapSampleOffset = atof( token ); + } + + /* ydnar: q3map_lightmapFilterRadius */ + else if( !Q_stricmp( token, "q3map_lightmapFilterRadius" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->lmFilterRadius = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->lightFilterRadius = atof( token ); + } + + /* ydnar: q3map_lightmapAxis [xyz] */ + else if( !Q_stricmp( token, "q3map_lightmapAxis" ) ) + { + GetTokenAppend( shaderText, qfalse ); + if( !Q_stricmp( token, "x" ) ) + VectorSet( si->lightmapAxis, 1, 0, 0 ); + else if( !Q_stricmp( token, "y" ) ) + VectorSet( si->lightmapAxis, 0, 1, 0 ); + else if( !Q_stricmp( token, "z" ) ) + VectorSet( si->lightmapAxis, 0, 0, 1 ); + else + { + Sys_Printf( "WARNING: Unknown value for lightmap axis: %s\n", token ); + VectorClear( si->lightmapAxis ); + } + } + + /* ydnar: q3map_lightmapSize (for autogenerated shaders + external tga lightmaps) */ + else if( !Q_stricmp( token, "q3map_lightmapSize" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->lmCustomWidth = atoi( token ); + GetTokenAppend( shaderText, qfalse ); + si->lmCustomHeight = atoi( token ); + + /* must be a power of 2 */ + if( ((si->lmCustomWidth - 1) & si->lmCustomWidth) || + ((si->lmCustomHeight - 1) & si->lmCustomHeight) ) + { + Sys_Printf( "WARNING: Non power-of-two lightmap size specified (%d, %d)\n", + si->lmCustomWidth, si->lmCustomHeight ); + si->lmCustomWidth = lmCustomSize; + si->lmCustomHeight = lmCustomSize; + } + } + + /* ydnar: q3map_lightmapBrightness N (for autogenerated shaders + external tga lightmaps) */ + else if( !Q_stricmp( token, "q3map_lightmapBrightness" ) || !Q_stricmp( token, "q3map_lightmapGamma" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->lmBrightness = atof( token ); + if( si->lmBrightness < 0 ) + si->lmBrightness = 1.0; + } + + /* q3map_vertexScale (scale vertex lighting by this fraction) */ + else if( !Q_stricmp( token, "q3map_vertexScale" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->vertexScale = atof( token ); + } + + /* q3map_noVertexLight */ + else if( !Q_stricmp( token, "q3map_noVertexLight" ) ) + { + si->noVertexLight = qtrue; + } + + /* q3map_flare[Shader] */ + else if( !Q_stricmp( token, "q3map_flare" ) || !Q_stricmp( token, "q3map_flareShader" ) ) + { + GetTokenAppend( shaderText, qfalse ); + if( token[ 0 ] != '\0' ) + { + si->flareShader = safe_malloc( strlen( token ) + 1 ); + strcpy( si->flareShader, token ); + } + } + + /* q3map_backShader */ + else if( !Q_stricmp( token, "q3map_backShader" ) ) + { + GetTokenAppend( shaderText, qfalse ); + if( token[ 0 ] != '\0' ) + { + si->backShader = safe_malloc( strlen( token ) + 1 ); + strcpy( si->backShader, token ); + } + } + + /* ydnar: q3map_cloneShader */ + else if ( !Q_stricmp( token, "q3map_cloneShader" ) ) + { + GetTokenAppend( shaderText, qfalse ); + if( token[ 0 ] != '\0' ) + { + si->cloneShader = safe_malloc( strlen( token ) + 1 ); + strcpy( si->cloneShader, token ); + } + } + + /* q3map_remapShader */ + else if( !Q_stricmp( token, "q3map_remapShader" ) ) + { + GetTokenAppend( shaderText, qfalse ); + if( token[ 0 ] != '\0' ) + { + si->remapShader = safe_malloc( strlen( token ) + 1 ); + strcpy( si->remapShader, token ); + } + } + + /* ydnar: q3map_offset */ + else if( !Q_stricmp( token, "q3map_offset" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->offset = atof( token ); + } + + /* ydnar: q3map_textureSize (substitute for q3map_lightimage derivation for terrain) */ + else if( !Q_stricmp( token, "q3map_fur" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->furNumLayers = atoi( token ); + GetTokenAppend( shaderText, qfalse ); + si->furOffset = atof( token ); + GetTokenAppend( shaderText, qfalse ); + si->furFade = atof( token ); + } + + /* ydnar: gs mods: legacy support for terrain/terrain2 shaders */ + else if( !Q_stricmp( token, "q3map_terrain" ) ) + { + /* team arena terrain is assumed to be nonplanar, with full normal averaging, + passed through the metatriangle surface pipeline, with a lightmap axis on z */ + si->legacyTerrain = qtrue; + si->noClip = qtrue; + si->notjunc = qtrue; + si->indexed = qtrue; + si->nonplanar = qtrue; + si->forceMeta = qtrue; + si->shadeAngleDegrees = 179.0f; + //% VectorSet( si->lightmapAxis, 0, 0, 1 ); /* ydnar 2002-09-21: turning this off for better lightmapping of cliff faces */ + } + + /* ydnar: picomodel: q3map_forceMeta (forces brush faces and/or triangle models to go through the metasurface pipeline) */ + else if( !Q_stricmp( token, "q3map_forceMeta" ) ) + { + si->forceMeta = qtrue; + } + + /* ydnar: gs mods: q3map_shadeAngle */ + else if( !Q_stricmp( token, "q3map_shadeAngle" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->shadeAngleDegrees = atof( token ); + } + + /* ydnar: q3map_textureSize (substitute for q3map_lightimage derivation for terrain) */ + else if( !Q_stricmp( token, "q3map_textureSize" ) ) + { + GetTokenAppend( shaderText, qfalse ); + si->shaderWidth = atoi( token ); + GetTokenAppend( shaderText, qfalse ); + si->shaderHeight = atoi( token ); + } + + /* ydnar: gs mods: q3map_tcGen

pFW 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%0Dm- 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/dev/tty + + if [ $# -lt 2 ] ; then + echo "Incorrect number of params to GenGameFiles"; + return; + fi + + # NOTE: the naming of the vars here doesn't follow attributes names in XML, it can be confusing + game=$1; + INPUT_FILE="games/$game.game"; + game_engine=`grep "enginepath_linux=" $INPUT_FILE | awk -F"\"" '{print $2 }'` + game_engine_path=$SETUP_COMPONENT_PATH; + game_tools=$2; + OUT_FILE="$game_tools/games/$game.game"; + + echo -e "Generating game file '$OUT_FILE' from '$INPUT_FILE' with the following values..."; + echo -e "\tGame :\t $game"; + echo -e "\tGame Engine :\t $game_engine_path/$game_engine"; + echo -e "\tGame Tools :\t $game_tools"; + + if [ -f "$OUT_FILE" ] ; then + rm -f $OUT_FILE; + fi + + if [ ! -d "$game_tools/games" ] ; then + mkdir "$game_tools/games"; + fi + +sed -e 's!enginepath_linux=\".*.\"!enginepath_linux=\"'$game_engine_path'\"\n gametools=\"'$game_tools/$game'\"!g' <$INPUT_FILE >$OUT_FILE + +exit 0; diff --git a/setup/linux/setup_image/setup.data/config.sh.in b/setup/linux/setup_image/setup.data/config.sh.in new file mode 100644 index 00000000..4854795d --- /dev/null +++ b/setup/linux/setup_image/setup.data/config.sh.in @@ -0,0 +1,15 @@ +# +# Use this script to customize the installer bootstrap script +# + +# override some defaults + +# try to get root prior to running setup? +# 0: no +# 1: prompt, but run anyway if fails +# 2: require, abort if root fails +ifdef(`M4_OSX', `GET_ROOT=2') + +FATAL_ERROR="Please report to http://zerowing.idsoftware.com/bugzilla/" + +#XSU_ICON="-i icon.xpm" diff --git a/setup/linux/setup_image/setup.data/postinstall.sh.in b/setup/linux/setup_image/setup.data/postinstall.sh.in new file mode 100644 index 00000000..9a7f62fc --- /dev/null +++ b/setup/linux/setup_image/setup.data/postinstall.sh.in @@ -0,0 +1,64 @@ +dnl `uname -m` quoting is a nightmare +dnl TTimo: You could do: `uname -a` +changequote([, ]) + +#!/bin/sh +# post installation script, finalize everything + +# The install path is the first argument of the script +install_path="$1" + +# Return the appropriate architecture string +function DetectARCH { + status=1 + case `uname -m` in + i?86) echo "x86" + status=0;; + *) case `uname -p` in + powerpc) echo "ppc" + status=0;; + *) echo "unknown" + status=0;; + esac + esac + return $status +} +arch=`DetectARCH` + +# Create a wrapper script +cat <<__EOF__ >"$install_path/radiant" +#!/bin/sh +# Needed to make symlinks/shortcuts work. +# Run map editor with some default arguments + +cd "$install_path" +radiant="./radiant.$arch" +# gcc 3.x, trying to reduce ABI issues +export LD_LIBRARY_PATH=.:\$LD_LIBRARY_PATH +"\$radiant" \$* +exit \$? +__EOF__ + +chmod 755 "$install_path/radiant" + +# Create a q3map2 wrapper script +cat <<__EOF__ >"$install_path/q3map2" +#!/bin/sh +# Needed to make symlinks/shortcuts work. + +cd "$install_path" +q3map2="./q3map2.$arch" +# gcc 3.x, trying to reduce ABI issues +export LD_LIBRARY_PATH=.:\$LD_LIBRARY_PATH +"\$q3map2" \$* +exit \$? +__EOF__ + +chmod 755 "$install_path/q3map2" + +# setup the safe guard +echo "M4_VER_MAJOR" > $install_path/RADIANT_MAJOR +echo "M4_VER_MINOR" > $install_path/RADIANT_MINOR + +# why the fuck is openurl.sh not +x by default anyway +chmod 755 "$install_path/openurl.sh" diff --git a/setup/linux/setup_image/setup.data/setup.glade b/setup/linux/setup_image/setup.data/setup.glade new file mode 100644 index 00000000..3b956a25 --- /dev/null +++ b/setup/linux/setup_image/setup.data/setup.glade @@ -0,0 +1,2192 @@ + + + + + Setup + setup + + src + pixmaps + C + False + True + False + True + setup.txt + + + + GtkWindow + setup_window + False + + delete_event + setup_button_cancel_slot + Wed, 15 Sep 1999 01:24:58 GMT + + + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + False + True + False + + + GtkHBox + hbox1 + False + 0 + + + GtkFrame + image_frame + 5 + 0 + GTK_SHADOW_ETCHED_IN + + 0 + True + True + + + + GtkLabel + label30 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + + GtkNotebook + setup_notebook + 5 + False + True + GTK_POS_TOP + False + 2 + 2 + False + + 0 + True + True + + + + GtkVBox + class_continue + True + 0 + + + GtkLabel + label69 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkRadioButton + recommended_but + 9 + This will install the product with the default options and paths + True + + True + True + install_class + + 0 + False + False + + + + + GtkRadioButton + expert_but + 10 + This will allow you to pick the installation paths and options to be installed + True + + False + True + install_class + + 0 + False + False + + + + + GtkHBox + hbox4 + True + 0 + + 0 + True + False + GTK_PACK_END + + + + GtkButton + class_cancel + True + True + + 0 + GDK_Escape + clicked + + + clicked + setup_button_exit_slot + Thu, 13 Sep 2001 23:46:19 GMT + + + GTK_RELIEF_NORMAL + + 0 + False + False + + + + + GtkButton + class_readme + True + True + + 0 + GDK_F1 + clicked + + + clicked + setup_button_view_readme_slot + Thu, 13 Sep 2001 23:23:42 GMT + + + GTK_RELIEF_NORMAL + + 0 + False + False + + + + + GtkButton + class_continue + True + True + True + + 0 + GDK_Return + clicked + + + clicked + on_class_continue_clicked + Thu, 13 Sep 2001 23:39:48 GMT + + + GTK_RELIEF_NORMAL + + 0 + False + False + + + + + + + GtkLabel + Notebook:tab + setup_class + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + GtkTable + table11 + 4 + 1 + False + 0 + 0 + + + GtkFrame + global_frame + + 0 + GTK_SHADOW_ETCHED_IN + + 0 + 1 + 0 + 1 + 0 + 0 + True + True + False + False + True + True + + + + GtkTable + table12 + 5 + 3 + 2 + False + 5 + 5 + + + GtkLabel + label23 + + GTK_JUSTIFY_CENTER + False + 1 + 0.5 + 0 + 0 + + 0 + 1 + 0 + 1 + 0 + 0 + False + False + False + False + True + False + + + + + GtkCombo + install_path + False + True + False + True + False + + + 1 + 2 + 0 + 1 + 0 + 0 + False + False + False + False + True + False + + + + GtkEntry + GtkCombo:entry + install_entry + True + + focus_out_event + setup_entry_installpath_slot + Wed, 15 Sep 1999 01:04:39 GMT + + True + True + 0 + + + + + + GtkLabel + binary_label + + GTK_JUSTIFY_RIGHT + False + 1 + 0.5 + 0 + 0 + + 0 + 1 + 2 + 3 + 0 + 0 + False + False + False + False + True + False + + + + + GtkCombo + binary_path + False + True + False + True + False + + + 1 + 2 + 2 + 3 + 0 + 0 + True + False + False + False + True + False + + + + GtkEntry + GtkCombo:entry + binary_entry + Symbolic links to installed binaries are placed here + True + + focus_out_event + setup_entry_binarypath_slot + Wed, 15 Sep 1999 01:06:14 GMT + + True + True + 0 + + + + + + GtkCheckButton + symlink_checkbox + True + True + True + + toggled + on_use_binary_toggled + Thu, 03 Aug 2000 09:53:24 GMT + + + True + True + + 0 + 2 + 1 + 2 + 0 + 0 + False + False + False + False + True + False + + + + + + + GtkFrame + frame10 + + 0 + GTK_SHADOW_ETCHED_IN + + 0 + 1 + 1 + 2 + 0 + 0 + False + True + False + False + True + True + + + + GtkTable + table13 + 5 + 4 + 4 + False + 5 + 5 + + + GtkLabel + label_free_space + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 1 + 2 + 2 + 3 + 0 + 0 + False + False + False + False + False + False + + + + + GtkLabel + label_install_size + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 3 + 4 + 2 + 3 + 0 + 0 + False + False + False + False + False + False + + + + + GtkVBox + option_vbox + False + 0 + + 0 + 4 + 0 + 1 + 0 + 0 + True + True + False + False + True + True + + + + GtkLabel + label31 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + + GtkLabel + free_space_label + + GTK_JUSTIFY_CENTER + False + 1 + 0.5 + 0 + 0 + + 0 + 1 + 2 + 3 + 0 + 0 + False + False + False + False + True + False + + + + + GtkLabel + estim_size_label + + GTK_JUSTIFY_CENTER + False + 1 + 0.5 + 0 + 0 + + 2 + 3 + 2 + 3 + 0 + 0 + False + False + False + False + True + False + + + + + GtkCheckButton + setup_menuitems_checkbox + True + + toggled + setup_checkbox_menuitems_slot + Thu, 16 Sep 1999 01:16:33 GMT + + + True + True + + 0 + 4 + 3 + 4 + 0 + 0 + False + False + False + False + True + False + + + + + GtkHSeparator + install_separator + + 0 + 4 + 1 + 2 + 0 + 0 + False + False + False + False + True + True + + + + + + + GtkHButtonBox + hbuttonbox3 + GTK_BUTTONBOX_DEFAULT_STYLE + 5 + 60 + 27 + 7 + 0 + + 0 + 1 + 3 + 4 + 0 + 0 + False + False + False + False + True + True + + + + GtkButton + button_cancel + True + True + + 0 + GDK_Escape + clicked + + + clicked + setup_button_exit_slot + Tue, 14 Sep 1999 22:58:10 GMT + + + GTK_RELIEF_NORMAL + + + + GtkButton + button_readme + True + True + + 0 + GDK_F1 + clicked + + + clicked + setup_button_view_readme_slot + Wed, 15 Sep 1999 01:10:59 GMT + + + GTK_RELIEF_NORMAL + + + + GtkButton + button_install + True + True + + clicked + setup_button_install_slot + Tue, 14 Sep 1999 22:36:50 GMT + + + GTK_RELIEF_NORMAL + + + + + GtkLabel + options_status + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + 1 + 2 + 3 + 0 + 0 + False + False + False + False + True + False + + + + + + GtkLabel + Notebook:tab + setup_options + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + GtkTable + table14 + 2 + 1 + False + 5 + 5 + + + GtkFrame + frame13 + + 0 + GTK_SHADOW_ETCHED_IN + + 0 + 1 + 0 + 1 + 0 + 0 + False + True + False + False + True + True + + + + GtkVBox + vbox10 + 5 + False + 0 + + + GtkLabel + label39 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkVBox + vbox11 + False + 0 + + 0 + True + True + + + + GtkLabel + current_option_label + + GTK_JUSTIFY_CENTER + False + 0 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkLabel + label50 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkLabel + current_file_label + + GTK_JUSTIFY_CENTER + False + 0 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkProgressBar + current_file_progress + 0 + 0 + 100 + GTK_PROGRESS_CONTINUOUS + GTK_PROGRESS_LEFT_TO_RIGHT + False + False + %P %% + 0.5 + 0.5 + + 0 + False + False + + + + + + GtkVBox + vbox12 + False + 0 + + 0 + True + True + + + + GtkLabel + label41 + + GTK_JUSTIFY_CENTER + False + 0 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkProgressBar + total_file_progress + 0 + 0 + 100 + GTK_PROGRESS_CONTINUOUS + GTK_PROGRESS_LEFT_TO_RIGHT + False + False + %P %% + 0.5 + 0.5 + + 0 + False + False + + + + + + + + GtkHButtonBox + hbuttonbox4 + GTK_BUTTONBOX_START + 5 + 60 + 27 + 7 + 0 + + 0 + 1 + 1 + 2 + 0 + 0 + True + False + False + False + True + True + + + + GtkButton + button12 + True + True + + 0 + GDK_Escape + clicked + + + clicked + setup_button_cancel_slot + Tue, 14 Sep 1999 19:53:47 GMT + + + GTK_RELIEF_NORMAL + + + + GtkButton + view_readme_progress_button + True + True + + 0 + GDK_F1 + clicked + + + clicked + setup_button_view_readme_slot + Wed, 15 Sep 1999 01:11:10 GMT + + + GTK_RELIEF_NORMAL + + + + + + GtkLabel + Notebook:tab + setup_install + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + GtkTable + table15 + 2 + 1 + False + 5 + 5 + + + GtkHButtonBox + hbuttonbox5 + GTK_BUTTONBOX_DEFAULT_STYLE + 5 + 60 + 27 + 7 + 0 + + 0 + 1 + 1 + 2 + 0 + 0 + True + False + False + False + True + True + + + + GtkButton + button15 + True + True + + 0 + GDK_Escape + clicked + + + clicked + setup_button_exit_slot + Tue, 14 Sep 1999 22:58:23 GMT + + + GTK_RELIEF_NORMAL + + + + GtkButton + view_readme_end_button + True + True + + 0 + GDK_F1 + clicked + + + clicked + setup_button_view_readme_slot + Wed, 15 Sep 1999 01:11:23 GMT + + + GTK_RELIEF_NORMAL + + + + GtkButton + play_game_button + True + True + + 0 + GDK_Return + clicked + + + 0 + GDK_space + enter + + + clicked + setup_button_play_slot + Tue, 14 Sep 1999 22:35:17 GMT + + + GTK_RELIEF_NORMAL + + + + + GtkFrame + frame14 + + 0 + GTK_SHADOW_ETCHED_IN + + 0 + 1 + 0 + 1 + 0 + 0 + False + True + False + False + True + True + + + + GtkVBox + vbox15 + 5 + False + 0 + + + GtkLabel + label45 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkLabel + setup_complete_label + + GTK_JUSTIFY_CENTER + True + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkLabel + label54 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkLabel + label55 + + GTK_JUSTIFY_CENTER + False + 0 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkLabel + install_directory_label + + GTK_JUSTIFY_CENTER + False + 0 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkLabel + label57 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkLabel + play_game_label + + GTK_JUSTIFY_CENTER + False + 0 + 0.5 + 0 + 0 + + 0 + False + False + + + + + + + + GtkLabel + Notebook:tab + setup_complete + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + GtkTable + table16 + 2 + 1 + False + 5 + 5 + + + GtkHButtonBox + hbuttonbox6 + GTK_BUTTONBOX_DEFAULT_STYLE + 5 + 120 + 27 + 7 + 0 + + 0 + 1 + 1 + 2 + 0 + 0 + True + False + False + False + True + True + + + + GtkButton + button18 + True + True + + 0 + GDK_Return + clicked + + + 0 + GDK_Escape + clicked + + + clicked + setup_button_abort_slot + Wed, 06 Feb 2002 01:32:49 GMT + + + GTK_RELIEF_NORMAL + + + + + GtkFrame + frame15 + + 0 + GTK_SHADOW_ETCHED_IN + + 0 + 1 + 0 + 1 + 0 + 0 + False + True + False + False + True + True + + + + GtkVBox + vbox16 + 5 + False + 0 + + + GtkLabel + label48 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkLabel + label49 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + + + + GtkLabel + Notebook:tab + setup_abort + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + GtkTable + table17 + 2 + 1 + False + 5 + 5 + + + GtkHButtonBox + hbuttonbox7 + GTK_BUTTONBOX_DEFAULT_STYLE + 5 + 60 + 27 + 7 + 0 + + 0 + 1 + 1 + 2 + 0 + 0 + True + False + False + False + True + True + + + + GtkButton + button21 + True + True + + 0 + GDK_Escape + clicked + + + clicked + setup_button_warning_cancel_slot + Wed, 29 Sep 1999 21:51:04 GMT + + + GTK_RELIEF_NORMAL + + + + GtkButton + button22 + True + True + True + + 0 + GDK_Return + clicked + + + clicked + setup_button_warning_continue_slot + Wed, 29 Sep 1999 21:50:51 GMT + + + GTK_RELIEF_NORMAL + + + + + GtkFrame + frame16 + + 0 + GTK_SHADOW_ETCHED_IN + + 0 + 1 + 0 + 1 + 0 + 0 + False + True + False + False + True + True + + + + GtkVBox + vbox17 + 5 + False + 0 + + + GtkLabel + warning_label + + GTK_JUSTIFY_LEFT + False + 0 + 0.5 + 5 + 0 + + 0 + False + False + + + + + + + + GtkLabel + Notebook:tab + setup_warning + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + GtkTable + table18 + 2 + 1 + False + 5 + 5 + + + GtkHButtonBox + hbuttonbox10 + GTK_BUTTONBOX_DEFAULT_STYLE + 5 + 60 + 27 + 7 + 0 + + 0 + 1 + 1 + 2 + 0 + 0 + True + False + False + False + True + True + + + + GtkButton + button30 + True + True + + 0 + GDK_Escape + clicked + + + clicked + setup_button_exit_slot + Tue, 14 Sep 1999 22:58:23 GMT + + + GTK_RELIEF_NORMAL + + + + GtkButton + button31 + True + True + + 0 + GDK_F1 + clicked + + + clicked + setup_button_view_readme_slot + Wed, 15 Sep 1999 01:11:23 GMT + + + GTK_RELIEF_NORMAL + + + + GtkButton + button32 + True + True + True + + clicked + setup_button_complete_slot + Sat, 11 Dec 1999 04:16:26 GMT + + + GTK_RELIEF_NORMAL + + + + + GtkFrame + frame17 + + 0 + GTK_SHADOW_ETCHED_IN + + 0 + 1 + 0 + 1 + 0 + 0 + False + True + False + False + True + True + + + + GtkVBox + vbox20 + 5 + False + 0 + + + GtkLabel + label60 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkHBox + hbox3 + False + 0 + + 0 + False + False + + + + GtkLabel + label67 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkLabel + website_product_label + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + + GtkLabel + website_text_label + + GTK_JUSTIFY_CENTER + False + 0 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkLabel + label63 + + GTK_JUSTIFY_CENTER + False + 0 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkLabel + label65 + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkLabel + auto_url_yes + + GTK_JUSTIFY_CENTER + False + 0 + 0.5 + 0 + 0 + + 0 + False + False + + + + + GtkHButtonBox + auto_url_no + GTK_BUTTONBOX_DEFAULT_STYLE + 30 + 85 + 27 + 7 + 0 + + 0 + True + True + + + + GtkButton + button33 + True + True + + clicked + setup_button_browser_slot + Sat, 11 Dec 1999 04:35:32 GMT + + + GTK_RELIEF_NORMAL + + + + + + + + GtkLabel + Notebook:tab + setup_website + + GTK_JUSTIFY_CENTER + False + 0.5 + 0.5 + 0 + 0 + + + + + + + GtkDialog + readme_dialog + 480 + 360 + False + + destroy + setup_destroy_view_readme_slot + + Readme File + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + True + False + + + GtkVBox + Dialog:vbox + dialog-vbox1 + False + 0 + + + GtkHBox + Dialog:action_area + dialog-action_area1 + 10 + True + 0 + + 0 + False + True + GTK_PACK_END + + + + GtkButton + button20 + True + True + + 0 + GDK_Return + clicked + + + 0 + GDK_Escape + clicked + + + clicked + setup_close_view_readme_slot + Wed, 15 Sep 1999 01:10:31 GMT + + + GTK_RELIEF_NORMAL + + 0 + False + True + + + + + + GtkScrolledWindow + scrolledwindow1 + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_UPDATE_CONTINUOUS + GTK_UPDATE_CONTINUOUS + + 0 + True + True + + + + GtkText + readme_area + True + False + + + + + + + + GtkDialog + license_dialog + 480 + 360 + False + + destroy + setup_destroy_license_slot + + License Agreement + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_CENTER + True + True + True + False + + + GtkVBox + Dialog:vbox + vbox19 + False + 0 + + + GtkHBox + Dialog:action_area + hbox2 + 10 + True + 0 + + 0 + False + True + GTK_PACK_END + + + + GtkHButtonBox + hbuttonbox9 + GTK_BUTTONBOX_DEFAULT_STYLE + 5 + 60 + 27 + 7 + 0 + + 0 + False + True + + + + GtkButton + button28 + True + True + + 0 + GDK_Escape + clicked + + + clicked + setup_button_exit_slot + Tue, 14 Sep 1999 22:58:10 GMT + + + GTK_RELIEF_NORMAL + + + + GtkButton + button29 + True + True + + 0 + GDK_Y + clicked + + + 0 + GDK_y + clicked + + + 0 + GDK_Return + clicked + + + clicked + setup_button_license_agree_slot + Tue, 30 Nov 1999 01:28:20 GMT + + + GTK_RELIEF_NORMAL + + + + + + GtkScrolledWindow + scrolledwindow3 + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_UPDATE_CONTINUOUS + GTK_UPDATE_CONTINUOUS + + 0 + True + True + + + + GtkText + license_area + True + False + + + + + + + + GtkDialog + subcomponent_dialog + + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + True + False + + + GtkVBox + Dialog:vbox + vbox21 + False + 2 + + + GtkHBox + Dialog:action_area + hbox5 + 2 + True + 2 + + 2 + False + True + GTK_PACK_END + + + + GtkHButtonBox + hbuttonbox11 + GTK_BUTTONBOX_DEFAULT_STYLE + 30 + 85 + 27 + 7 + 0 + + 0 + True + True + + + + GtkButton + subcomponent_button_cancel + True + True + + clicked + setup_button_subcomponent_cancel + Wed, 09 Jan 2002 21:54:50 GMT + + + GTK_RELIEF_NORMAL + + + + GtkButton + subcomponent_button_ok + True + True + + clicked + setup_button_subcomponent_ok + Wed, 09 Jan 2002 21:54:13 GMT + + + GTK_RELIEF_NORMAL + + + + + + GtkFrame + subcomponent_frame + 2 + 0 + GTK_SHADOW_ETCHED_IN + + 0 + True + False + + + + Placeholder + + + + + + diff --git a/setup/linux/setup_image/setup.data/setup.xml.in b/setup/linux/setup_image/setup.data/setup.xml.in new file mode 100644 index 00000000..e8a346f1 --- /dev/null +++ b/setup/linux/setup_image/setup.data/setup.xml.in @@ -0,0 +1,139 @@ + + +ifdef(`M4_OSX', ` + /sw/games + /Applications + ') + + license.txt + + + README + + + + + + + + ifelse(M4_GAME_ET, `1', ` + + ') + ifelse(M4_GAME_DOOM3, `1', ` + + ') + ifelse(M4_GAME_Q2, `1', ` + + ') + ifdef(`M4_OSX', `',` ') + + diff --git a/setup/linux/setup_image/setup.data/splash.xpm b/setup/linux/setup_image/setup.data/splash.xpm new file mode 100644 index 00000000..448687ea --- /dev/null +++ b/setup/linux/setup_image/setup.data/splash.xpm @@ -0,0 +1,23719 @@ +/* XPM */ +static char * splash_xpm[] = { +"340 294 23422 3", +" c None", +". c #0B090B", +"+ c #09080A", +"@ c #080708", +"# c #050506", +"$ c #030304", +"% c #040404", +"& c #575757", +"* c #E6E6E6", +"= c #FFFFFF", +"- c #DEDEDE", +"; c #535353", +"> c #040304", +", c #060506", +"' c #0A090B", +") c #0C0A0C", +"! c #0C0B0D", +"~ c #0D0B0D", +"{ c #0D0B0E", +"] c #0E0C0E", +"^ c #0E0E0E", +"/ c #0D0D0E", +"( c #0E0C0F", +"_ c #0D0C0E", +": c #0B0A0C", +"< c #0C0A0D", +"[ c #0F0E10", +"} c #1A1F1F", +"| c #191F1F", +"1 c #171C1C", +"2 c #1D201F", +"3 c #171A19", +"4 c #111312", +"5 c #090B0B", +"6 c #C0C0C0", +"7 c #909090", +"8 c #0D1011", +"9 c #141919", +"0 c #161B1B", +"a c #151B1B", +"b c #171C1E", +"c c #1B2022", +"d c #222727", +"e c #1C2120", +"f c #171B1C", +"g c #1A1E1D", +"h c #272C28", +"i c #1D2120", +"j c #15191A", +"k c #131717", +"l c #101314", +"m c #0F1212", +"n c #0B0D0D", +"o c #A8A9A9", +"p c #F1F1F1", +"q c #BABABA", +"r c #6B6B6B", +"s c #181718", +"t c #020203", +"u c #080709", +"v c #0E0D0F", +"w c #0F0D10", +"x c #100E11", +"y c #110F12", +"z c #070608", +"A c #050405", +"B c #F9F9F9", +"C c #575658", +"D c #0B0B0D", +"E c #0A0A0C", +"F c #080809", +"G c #575758", +"H c #666767", +"I c #161819", +"J c #212325", +"K c #2C3032", +"L c #35393C", +"M c #383D41", +"N c #3D4246", +"O c #3F4447", +"P c #454B4B", +"Q c #515757", +"R c #464B4C", +"S c #404246", +"T c #3B3E41", +"U c #474A49", +"V c #4D4F4A", +"W c #393B3C", +"X c #37393D", +"Y c #383D40", +"Z c #303538", +"` c #363B3E", +" . c #3A3F42", +".. c #34393C", +"+. c #373C3F", +"@. c #3B4043", +"#. c #3B4041", +"$. c #3B4141", +"%. c #3F4545", +"&. c #4E5355", +"*. c #6D7373", +"=. c #707674", +"-. c #707877", +";. c #707775", +">. c #767B78", +",. c #7B7E7B", +"'. c #737875", +"). c #69706F", +"!. c #6B726F", +"~. c #6E7471", +"{. c #6F7574", +"]. c #696B6D", +"^. c #1D1E20", +"/. c #100E10", +"(. c #100F10", +"_. c #101010", +":. c #0F0F0F", +"<. c #0F0E0F", +"[. c #0A080A", +"}. c #070607", +"|. c #040405", +"1. c #868687", +"2. c #F8F8F8", +"3. c #F7F7F8", +"4. c #8C8C8C", +"5. c #0C0C0C", +"6. c #0D0D0D", +"7. c #0C0B0C", +"8. c #0A080B", +"9. c #0D0D0F", +"0. c #191E1E", +"a. c #161A19", +"b. c #141716", +"c. c #0E1010", +"d. c #8F9090", +"e. c #080B0B", +"f. c #111515", +"g. c #1D2222", +"h. c #191F20", +"i. c #191F21", +"j. c #222827", +"k. c #262B2A", +"l. c #1D2122", +"m. c #181C1D", +"n. c #1B1F1F", +"o. c #282D29", +"p. c #202424", +"q. c #181B1C", +"r. c #16191A", +"s. c #121515", +"t. c #0D1010", +"u. c #A9A9A9", +"v. c #FEFEFE", +"w. c #E0E0E0", +"x. c #5D5C5D", +"y. c #090809", +"z. c #0B0A0D", +"A. c #656767", +"B. c #151819", +"C. c #212426", +"D. c #2D3133", +"E. c #34383B", +"F. c #404549", +"G. c #44494B", +"H. c #4C5252", +"I. c #505656", +"J. c #43494A", +"K. c #414549", +"L. c #4C5150", +"M. c #37393B", +"N. c #36393C", +"O. c #313538", +"P. c #313639", +"Q. c #353A3D", +"R. c #454A4D", +"S. c #494E50", +"T. c #323739", +"U. c #2E3336", +"V. c #6E7472", +"W. c #727876", +"X. c #787C7A", +"Y. c #7B7E79", +"Z. c #7A7D79", +"`. c #707472", +" + c #6D7370", +".+ c #6D7372", +"++ c #656869", +"@+ c #1E1E20", +"#+ c #0B090C", +"$+ c #09090A", +"%+ c #908F90", +"&+ c #AAAAAA", +"*+ c #121212", +"=+ c #0D0C0D", +"-+ c #0B0B0C", +";+ c #0B0B0B", +">+ c #0A0A0A", +",+ c #0A090A", +"'+ c #141819", +")+ c #181D1D", +"!+ c #151818", +"~+ c #131616", +"{+ c #0F1110", +"]+ c #8F8F90", +"^+ c #090B0C", +"/+ c #141718", +"(+ c #171B1B", +"_+ c #1F2424", +":+ c #1A2020", +"<+ c #1B2122", +"[+ c #1A1F21", +"}+ c #191F1E", +"|+ c #232928", +"1+ c #1B1F20", +"2+ c #191D1E", +"3+ c #1E2221", +"4+ c #212623", +"5+ c #1C2020", +"6+ c #1A1D1E", +"7+ c #AAABAB", +"8+ c #FBFBFB", +"9+ c #8E8E8E", +"0+ c #100F12", +"a+ c #100F11", +"b+ c #0E0D10", +"c+ c #565657", +"d+ c #171A1B", +"e+ c #232628", +"f+ c #383C3F", +"g+ c #404448", +"h+ c #43484B", +"i+ c #43484A", +"j+ c #464C4C", +"k+ c #4D5353", +"l+ c #42494A", +"m+ c #41474A", +"n+ c #444B4C", +"o+ c #454B4C", +"p+ c #3C4145", +"q+ c #3C4143", +"r+ c #3B4042", +"s+ c #33383B", +"t+ c #32373A", +"u+ c #33383A", +"v+ c #32373B", +"w+ c #2E3337", +"x+ c #2C3235", +"y+ c #343A3B", +"z+ c #606664", +"A+ c #828480", +"B+ c #80827C", +"C+ c #7D807C", +"D+ c #6F7472", +"E+ c #697170", +"F+ c #707673", +"G+ c #686B6C", +"H+ c #202022", +"I+ c #060607", +"J+ c #FDFDFD", +"K+ c #B1B1B1", +"L+ c #0B0A0B", +"M+ c #070708", +"N+ c #070707", +"O+ c #060606", +"P+ c #060507", +"Q+ c #080608", +"R+ c #1C2021", +"S+ c #161919", +"T+ c #131516", +"U+ c #0F1111", +"V+ c #0A0B0B", +"W+ c #999999", +"X+ c #0C0E0F", +"Y+ c #272B29", +"Z+ c #212524", +"`+ c #1A1F20", +" @ c #191E1F", +".@ c #171D1E", +"+@ c #171D1D", +"@@ c #1A1E1E", +"#@ c #202423", +"$@ c #1F2323", +"%@ c #191C1D", +"&@ c #161A1A", +"*@ c #0A0C0C", +"=@ c #A9AAAA", +"-@ c #FCFCFC", +";@ c #868586", +">@ c #101012", +",@ c #0F0F11", +"'@ c #0F0E11", +")@ c #666768", +"!@ c #191B1C", +"~@ c #242729", +"{@ c #303336", +"]@ c #3B4044", +"^@ c #3E4347", +"/@ c #42474B", +"(@ c #464B4D", +"_@ c #4E5453", +":@ c #41474B", +"<@ c #42484B", +"[@ c #3B4144", +"}@ c #3A4041", +"|@ c #383E3E", +"1@ c #3C4243", +"2@ c #383E3F", +"3@ c #303539", +"4@ c #31363A", +"5@ c #292E31", +"6@ c #34393B", +"7@ c #696F6E", +"8@ c #787E7C", +"9@ c #777C7A", +"0@ c #787D7D", +"a@ c #6F7575", +"b@ c #6B7171", +"c@ c #707676", +"d@ c #696F70", +"e@ c #1F2022", +"f@ c #111012", +"g@ c #110F11", +"h@ c #100E0F", +"i@ c #080707", +"j@ c #0A0A0B", +"k@ c #5F5E5F", +"l@ c #FAFAFA", +"m@ c #8D8D8D", +"n@ c #363536", +"o@ c #6A6A6A", +"p@ c #6A6A6B", +"q@ c #5B5A5B", +"r@ c #101313", +"s@ c #0E1011", +"t@ c #0A0B0C", +"u@ c #C0C1C1", +"v@ c #9A9B9B", +"w@ c #151918", +"x@ c #232726", +"y@ c #232826", +"z@ c #1F2423", +"A@ c #161A1B", +"B@ c #161C1C", +"C@ c #1B1F21", +"D@ c #222626", +"E@ c #242828", +"F@ c #1E2122", +"G@ c #131617", +"H@ c #121516", +"I@ c #A8A8A8", +"J@ c #4F4F50", +"K@ c #09070A", +"L@ c #121013", +"M@ c #111013", +"N@ c #181A1B", +"O@ c #25282A", +"P@ c #34383A", +"Q@ c #363B3D", +"R@ c #3A3E42", +"S@ c #484D4F", +"T@ c #4F5454", +"U@ c #44494D", +"V@ c #3F4448", +"W@ c #3D4145", +"X@ c #404648", +"Y@ c #444A4A", +"Z@ c #393E40", +"`@ c #33383C", +" # c #373C40", +".# c #383D3F", +"+# c #393E41", +"@# c #2D3235", +"## c #2B3033", +"$# c #464C4D", +"%# c #747A7A", +"&# c #777D7D", +"*# c #757B7B", +"=# c #717777", +"-# c #1E1E21", +";# c #0C0C0F", +"># c #110F10", +",# c #606061", +"'# c #6B6A6B", +")# c #313031", +"!# c #030203", +"~# c #282728", +"{# c #ECECEC", +"]# c #545354", +"^# c #787878", +"/# c #D6D6D6", +"(# c #111314", +"_# c #1A1D1D", +":# c #0F1112", +"<# c #080A0B", +"[# c #0E1111", +"}# c #131718", +"|# c #181D1E", +"1# c #1C2022", +"2# c #232727", +"3# c #212525", +"4# c #1E2223", +"5# c #111415", +"6# c #DBDBDC", +"7# c #1A1C1D", +"8# c #2F3335", +"9# c #484D51", +"0# c #4B5153", +"a# c #3F4347", +"b# c #3C4144", +"c# c #3A3F43", +"d# c #393D41", +"e# c #3E4345", +"f# c #404647", +"g# c #3C4044", +"h# c #3E4346", +"i# c #353A3E", +"j# c #545A5C", +"k# c #72777A", +"l# c #727878", +"m# c #737979", +"n# c #787E7E", +"o# c #767C7C", +"p# c #686F6E", +"q# c #1F1F21", +"r# c #0E0E10", +"s# c #E3E3E3", +"t# c #6C6C6D", +"u# c #0C0C0D", +"v# c #D7D7D7", +"w# c #131314", +"x# c #101213", +"y# c #121415", +"z# c #0C0F10", +"A# c #0F1214", +"B# c #15181A", +"C# c #1F2322", +"D# c #0C0E0D", +"E# c #797879", +"F# c #0F0D0F", +"G# c #050505", +"H# c #585758", +"I# c #676868", +"J# c #1D1F21", +"K# c #272B2D", +"L# c #2D3134", +"M# c #43474B", +"N# c #494E52", +"O# c #494E51", +"P# c #4D5254", +"Q# c #414649", +"R# c #414648", +"S# c #3F4445", +"T# c #35393D", +"U# c #373B3E", +"V# c #373C3D", +"W# c #393F3F", +"X# c #3E4445", +"Y# c #2F3438", +"Z# c #5C6164", +"`# c #777B79", +" $ c #757A79", +".$ c #717779", +"+$ c #73797A", +"@$ c #676B6C", +"#$ c #0E0D0E", +"$$ c #0F0E0E", +"%$ c #0E0E0F", +"&$ c #0D0C0F", +"*$ c #E3E2E3", +"=$ c #6C6C6C", +"-$ c #373737", +";$ c #F6F6F6", +">$ c #6B6B6C", +",$ c #787778", +"'$ c #0D0F0F", +")$ c #0B0C0C", +"!$ c #08090A", +"~$ c #0C0D0F", +"{$ c #0E1012", +"]$ c #101416", +"^$ c #181E1E", +"/$ c #1E2323", +"($ c #202626", +"_$ c #242827", +":$ c #1D2121", +"<$ c #0C0D0D", +"[$ c #DFDFDF", +"}$ c #0F0F10", +"|$ c #585759", +"1$ c #686969", +"2$ c #1E2022", +"3$ c #282B2D", +"4$ c #2F3235", +"5$ c #42464A", +"6$ c #42474A", +"7$ c #4A4F52", +"8$ c #4D5255", +"9$ c #404548", +"0$ c #44494C", +"a$ c #45494C", +"b$ c #3C4142", +"c$ c #393D40", +"d$ c #3A3F40", +"e$ c #303437", +"f$ c #2F3337", +"g$ c #2E3235", +"h$ c #303537", +"i$ c #353B3B", +"j$ c #3A3F3F", +"k$ c #3A3F41", +"l$ c #2F3437", +"m$ c #2C3134", +"n$ c #272C2F", +"o$ c #686C6C", +"p$ c #737878", +"q$ c #757C7C", +"r$ c #6F7577", +"s$ c #6D7274", +"t$ c #656769", +"u$ c #0E0C0D", +"v$ c #080808", +"w$ c #E2E2E2", +"x$ c #6C6B6C", +"y$ c #D0D0D0", +"z$ c #090909", +"A$ c #777778", +"B$ c #111414", +"C$ c #141717", +"D$ c #141617", +"E$ c #131515", +"F$ c #9A9A9B", +"G$ c #0C0D0E", +"H$ c #0D1012", +"I$ c #101315", +"J$ c #191D1F", +"K$ c #212626", +"L$ c #161B1A", +"M$ c #1E2121", +"N$ c #191D1C", +"O$ c #1C201F", +"P$ c #141818", +"Q$ c #111516", +"R$ c #0B0D0E", +"S$ c #414141", +"T$ c #121012", +"U$ c #565656", +"V$ c #0C0B0E", +"W$ c #0A090C", +"X$ c #49484A", +"Y$ c #CECECE", +"Z$ c #CECDCE", +"`$ c #CDCDCD", +" % c #CDCECE", +".% c #CFCFCF", +"+% c #CFD0D0", +"@% c #58595A", +"#% c #191C1E", +"$% c #232629", +"%% c #2C2F33", +"&% c #363A3E", +"*% c #494D50", +"=% c #484C4F", +"-% c #3C4043", +";% c #424648", +">% c #3A3E41", +",% c #363A3B", +"'% c #2A2E31", +")% c #2A2D30", +"!% c #292D30", +"~% c #292C2F", +"{% c #282C2E", +"]% c #2F3434", +"^% c #333838", +"/% c #2B3032", +"(% c #2C3133", +"_% c #2C3034", +":% c #2B3034", +"<% c #2A2F32", +"[% c #262B2E", +"}% c #272C2E", +"|% c #414347", +"1% c #6B7071", +"2% c #737A77", +"3% c #707677", +"4% c #6D7275", +"5% c #686C6F", +"6% c #5F6163", +"7% c #1B1B1D", +"8% c #0C0B0B", +"9% c #0C0C0E", +"0% c #E8E8E8", +"a% c #2C2C2C", +"b% c #777777", +"c% c #050606", +"d% c #111313", +"e% c #121414", +"f% c #070909", +"g% c #0E1212", +"h% c #0F1313", +"i% c #1B1E1F", +"j% c #191D1D", +"k% c #151919", +"l% c #111616", +"m% c #151718", +"n% c #090A0B", +"o% c #848485", +"p% c #555555", +"q% c #020202", +"r% c #060708", +"s% c #0E0F10", +"t% c #141618", +"u% c #222528", +"v% c #3F4348", +"w% c #373B3D", +"x% c #373A3D", +"y% c #35383B", +"z% c #323537", +"A% c #26292B", +"B% c #222527", +"C% c #212527", +"D% c #24282A", +"E% c #25292B", +"F% c #272C2B", +"G% c #2D3131", +"H% c #252A2B", +"I% c #23282A", +"J% c #252A2D", +"K% c #272B2E", +"L% c #2E3335", +"M% c #282D30", +"N% c #2F3436", +"O% c #4E5454", +"P% c #6E7473", +"Q% c #6D7172", +"R% c #6B6E70", +"S% c #636668", +"T% c #494B4C", +"U% c #707070", +"V% c #5D5D5D", +"W% c #AFAFAF", +"X% c #6E6E6E", +"Y% c #030303", +"Z% c #0C0F0F", +"`% c #070808", +" & c #0B0E0E", +".& c #121514", +"+& c #171919", +"@& c #0F1213", +"#& c #0E1112", +"$& c #0B0C0D", +"%& c #080909", +"&& c #BEBEBE", +"*& c #545454", +"=& c #0B090D", +"-& c #060707", +";& c #1B1D1F", +">& c #24282B", +",& c #414647", +"'& c #414547", +")& c #393E42", +"!& c #3B3F42", +"~& c #3A3E40", +"{& c #343839", +"]& c #2C3031", +"^& c #242728", +"/& c #232627", +"(& c #181A1C", +"_& c #1A1C1E", +":& c #17191A", +"<& c #191B1D", +"[& c #181C1C", +"}& c #1C1F1F", +"|& c #1F2122", +"1& c #2A2E30", +"2& c #2B2F32", +"3& c #292E30", +"4& c #313638", +"5& c #555A5B", +"6& c #5F6365", +"7& c #5E6264", +"8& c #3B3D3E", +"9& c #090709", +"0& c #3F3F3F", +"a& c #010101", +"b& c #C2C2C2", +"c& c #A6A6A6", +"d& c #767676", +"e& c #0C0E0E", +"f& c #A5A5A5", +"g& c #060808", +"h& c #080A0A", +"i& c #090C0C", +"j& c #0D0F0E", +"k& c #0E100F", +"l& c #121413", +"m& c #121412", +"n& c #101211", +"o& c #0A0D0D", +"p& c #A7A7A8", +"q& c #E5E5E5", +"r& c #161616", +"s& c #111011", +"t& c #262627", +"u& c #494949", +"v& c #696969", +"w& c #7B7B7B", +"x& c #868686", +"y& c #858586", +"z& c #7C7C7C", +"A& c #525152", +"B& c #2F2E2F", +"C& c #1A1A1A", +"D& c #484848", +"E& c #737373", +"F& c #858485", +"G& c #7F7F7F", +"H& c #626263", +"I& c #323233", +"J& c #09090B", +"K& c #050406", +"L& c #040504", +"M& c #040505", +"N& c #1B1E20", +"O& c #3D4243", +"P& c #4B504F", +"Q& c #484D4D", +"R& c #363A3D", +"S& c #333639", +"T& c #303436", +"U& c #2D3130", +"V& c #2A2D2D", +"W& c #262828", +"X& c #1E2020", +"Y& c #202122", +"Z& c #303233", +"`& c #515253", +" * c #6F7071", +".* c #818282", +"+* c #8C8D8D", +"@* c #8B8C8C", +"#* c #848484", +"$* c #767777", +"%* c #5E5F5F", +"&* c #3B3D3D", +"** c #272828", +"=* c #19191A", +"-* c #181919", +";* c #1B1D1E", +">* c #222426", +",* c #52585A", +"'* c #505657", +")* c #494F50", +"!* c #323435", +"~* c #1D1D1E", +"{* c #4D4D4E", +"]* c #757576", +"^* c #7E7D7E", +"/* c #5F5F5F", +"(* c #2D2D2E", +"_* c #010102", +":* c #090A0A", +"<* c #A6A7A7", +"[* c #080A09", +"}* c #4B4C4B", +"|* c #656665", +"1* c #646665", +"2* c #686968", +"3* c #666766", +"4* c #646565", +"5* c #636564", +"6* c #636464", +"7* c #676869", +"8* c #646566", +"9* c #4C4D4D", +"0* c #0A0C0D", +"a* c #A7A7A7", +"b* c #353535", +"c* c #A1A1A1", +"d* c #252526", +"e* c #3B3B3C", +"f* c #939394", +"g* c #D5D5D5", +"h* c #F7F7F7", +"i* c #B5B5B5", +"j* c #666566", +"k* c #151415", +"l* c #1A1A1B", +"m* c #6D6C6D", +"n* c #616162", +"o* c #605F60", +"p* c #606060", +"q* c #606161", +"r* c #626262", +"s* c #636364", +"t* c #313334", +"u* c #323637", +"v* c #393D3F", +"w* c #434749", +"x* c #2C2F31", +"y* c #26292A", +"z* c #1F2121", +"A* c #1E1F1F", +"B* c #444545", +"C* c #969697", +"D* c #BCBCBC", +"E* c #717272", +"F* c #222324", +"G* c #252829", +"H* c #272A2C", +"I* c #2F3336", +"J* c #303638", +"K* c #2C3436", +"L* c #2D3335", +"M* c #2E3234", +"N* c #474D4D", +"O* c #424747", +"P* c #78797A", +"Q* c #616061", +"R* c #616161", +"S* c #3E3E3F", +"T* c #212121", +"U* c #99999A", +"V* c #EBEBEB", +"W* c #C5C5C5", +"X* c #4E4D4E", +"Y* c #BBBBBB", +"Z* c #020102", +"`* c #E4E4E4", +" = c #060706", +".= c #BFC0C0", +"+= c #0D0E0E", +"@= c #DBDBDB", +"#= c #A7A8A8", +"$= c #ADADAD", +"%= c #3E3E3E", +"&= c #EDEDED", +"*= c #838283", +"== c #464647", +"-= c #D2D2D2", +";= c #343434", +">= c #35393A", +",= c #363B3C", +"'= c #282C2D", +")= c #242628", +"!= c #1D2021", +"~= c #171A1A", +"{= c #454747", +"]= c #BDBEBE", +"^= c #F2F2F2", +"/= c #292B2B", +"(= c #191B1B", +"_= c #25292C", +":= c #272D30", +"<= c #2B3234", +"[= c #2C3234", +"}= c #2F3234", +"|= c #272A2B", +"1= c #323636", +"2= c #3C3E3F", +"3= c #939293", +"4= c #989898", +"5= c #414041", +"6= c #F5F5F5", +"7= c #272727", +"8= c #6F6F6F", +"9= c #D5D6D6", +"0= c #BFBFBF", +"a= c #303131", +"b= c #525353", +"c= c #878787", +"d= c #F3F3F3", +"e= c #CECECF", +"f= c #363636", +"g= c #4B4B4C", +"h= c #F0F0F0", +"i= c #EAEAEA", +"j= c #656667", +"k= c #16181A", +"l= c #313637", +"m= c #303435", +"n= c #323638", +"o= c #292C2E", +"p= c #1B1C1D", +"q= c #838484", +"r= c #D6D7D7", +"s= c #464747", +"t= c #1B1C1E", +"u= c #1E2224", +"v= c #212729", +"w= c #272E2F", +"x= c #2B2E30", +"y= c #232527", +"z= c #2E2F2F", +"A= c #818181", +"B= c #595959", +"C= c #9A9A9A", +"D= c #CACACA", +"E= c #858585", +"F= c #333333", +"G= c #393939", +"H= c #000001", +"I= c #121313", +"J= c #030404", +"K= c #666666", +"L= c #171918", +"M= c #202020", +"N= c #B0B0B0", +"O= c #313131", +"P= c #E9E9E9", +"Q= c #EEEEEE", +"R= c #646666", +"S= c #16191B", +"T= c #232728", +"U= c #2B2F30", +"V= c #2D3233", +"W= c #212425", +"X= c #232525", +"Y= c #ACADAD", +"Z= c #515252", +"`= c #141516", +" - c #1C1F21", +".- c #202526", +"+- c #242829", +"@- c #202323", +"#- c #1F1F1F", +"$- c #3A3A3A", +"%- c #9E9E9E", +"&- c #919091", +"*- c #2B2B2B", +"=- c #4F4F4F", +"-- c #000000", +";- c #101111", +">- c #010202", +",- c #020303", +"'- c #020302", +")- c #050605", +"!- c #151515", +"~- c #353435", +"{- c #626364", +"]- c #262A2B", +"^- c #1F2123", +"/- c #202123", +"(- c #B7B7B8", +"_- c #ECEDED", +":- c #464748", +"<- c #1E2023", +"[- c #1F2224", +"}- c #1F2222", +"|- c #181A1A", +"1- c #181819", +"2- c #656565", +"3- c #D1D1D1", +"4- c #1D1D1D", +"5- c #9D9D9D", +"6- c #898989", +"7- c #242324", +"8- c #A4A4A4", +"9- c #484748", +"0- c #555556", +"a- c #5D5E5E", +"b- c #040606", +"c- c #070809", +"d- c #A0A0A0", +"e- c #D8D9D9", +"f- c #212324", +"g- c #151719", +"h- c #1B1E21", +"i- c #18191A", +"j- c #B9B9B9", +"k- c #747474", +"l- c #1F1F20", +"m- c #717171", +"n- c #292929", +"o- c #4F504F", +"p- c #030403", +"q- c #8A8A8A", +"r- c #08080A", +"s- c #6D6D6D", +"t- c #A2A2A2", +"u- c #989899", +"v- c #181B1D", +"w- c #191A1B", +"x- c #010001", +"y- c #7D7D7D", +"z- c #0E0F0F", +"A- c #676767", +"B- c #222222", +"C- c #212021", +"D- c #E4E3E4", +"E- c #1B1B1C", +"F- c #D4D4D4", +"G- c #5C5C5C", +"H- c #171818", +"I- c #DCDCDC", +"J- c #F4F4F4", +"K- c #2D2E2F", +"L- c #161818", +"M- c #CCCCCC", +"N- c #7D7C7D", +"O- c #1A191A", +"P- c #828282", +"Q- c #040506", +"R- c #040607", +"S- c #040707", +"T- c #111111", +"U- c #89898A", +"V- c #201F20", +"W- c #8F8F8F", +"X- c #DADADA", +"Y- c #C4C4C4", +"Z- c #5C5D5D", +"`- c #5B5B5B", +" ; c #919191", +".; c #0D0E0D", +"+; c #2E2E2E", +"@; c #7B7B7C", +"#; c #B6B6B6", +"$; c #1E1D1E", +"%; c #454546", +"&; c #515151", +"*; c #DDDDDD", +"=; c #252525", +"-; c #3D3D3D", +";; c #ACADAC", +">; c #EFEFEF", +",; c #7A7A7A", +"'; c #757575", +"); c #626162", +"!; c #1C1C1C", +"~; c #A6A7A6", +"{; c #E7E7E7", +"]; c #595859", +"^; c #050504", +"/; c #060705", +"(; c #000100", +"_; c #000101", +":; c #424242", +"<; c #B8B8B8", +"[; c #8A898A", +"}; c #202021", +"|; c #767576", +"1; c #CBCBCB", +"2; c #030504", +"3; c #929293", +"4; c #040403", +"5; c #848584", +"6; c #838383", +"7; c #9A999A", +"8; c #69696A", +"9; c #212122", +"0; c #929292", +"a; c #030405", +"b; c #939393", +"c; c #464546", +"d; c #444344", +"e; c #B7B7B7", +"f; c #262626", +"g; c #D8D8D8", +"h; c #131313", +"i; c #5B5C5B", +"j; c #A9AAA9", +"k; c #010201", +"l; c #1D1C1D", +"m; c #CCCBCC", +"n; c #777677", +"o; c #B3B3B3", +"p; c #545555", +"q; c #040605", +"r; c #2A2A2A", +"s; c #181818", +"t; c #070706", +"u; c #252625", +"v; c #020201", +"w; c #5C5B5C", +"x; c #050706", +"y; c #191819", +"z; c #131213", +"A; c #121112", +"B; c #7C7C7D", +"C; c #A0A1A1", +"D; c #969596", +"E; c #070709", +"F; c #AEAEAE", +"G; c #C1C1C1", +"H; c #4D4D4D", +"I; c #BEBEBF", +"J; c #0A0709", +"K; c #C8C8C8", +"L; c #959595", +"M; c #060807", +"N; c #7F7F80", +"O; c #040303", +"P; c #B0AFB0", +"Q; c #1A1B1A", +"R; c #BDBDBD", +"S; c #444444", +"T; c #C2C1C2", +"U; c #070807", +"V; c #060806", +"W; c #282829", +"X; c #1F1E1F", +"Y; c #545455", +"Z; c #0A070A", +"`; c #242524", +" > c #090A09", +".> c #0A0C0A", +"+> c #080A08", +"@> c #09080B", +"#> c #646464", +"$> c #C3C3C3", +"%> c #030302", +"&> c #D0D1D1", +"*> c #0C0C0B", +"=> c #070806", +"-> c #0B0C0A", +";> c #0C0E0C", +">> c #C9C9C9", +",> c #626363", +"'> c #424142", +")> c #969696", +"!> c #080907", +"~> c #0B0D0C", +"{> c #656566", +"]> c #D3D3D3", +"^> c #555455", +"/> c #565556", +"(> c #191A19", +"_> c #797979", +":> c #E1E1E1", +"<> c #414142", +"[> c #888888", +"}> c #0A0C0B", +"|> c #C7C7C7", +"1> c #3D3D3E", +"2> c #08070A", +"3> c #0E0B0D", +"4> c #0E0B0E", +"5> c #0D0A0D", +"6> c #232223", +"7> c #111211", +"8> c #191919", +"9> c #050404", +"0> c #100E12", +"a> c #1E1E1E", +"b> c #080908", +"c> c #0C0D0C", +"d> c #0E0A0D", +"e> c #0F0B0E", +"f> c #2D2D2D", +"g> c #0F0D11", +"h> c #0E0F0E", +"i> c #0B0C0B", +"j> c #0A0B0A", +"k> c #222122", +"l> c #0F0C0F", +"m> c #646364", +"n> c #5E5E5E", +"o> c #E0E0E1", +"p> c #040503", +"q> c #606160", +"r> c #5F605F", +"s> c #5E5F5E", +"t> c #5D5E5D", +"u> c #070606", +"v> c #B4B4B4", +"w> c #0D0F0D", +"x> c #0C0E0B", +"y> c #0C0D0B", +"z> c #3C3C3D", +"A> c #060605", +"B> c #2F2F2F", +"C> c #0D0F0C", +"D> c #0A0B09", +"E> c #DFDFE0", +"F> c #232324", +"G> c #242424", +"H> c #9C9C9C", +"I> c #5C5D5C", +"J> c #1D1E1E", +"K> c #0D0E0C", +"L> c #A9A9AA", +"M> c #232323", +"N> c #050604", +"O> c #090A08", +"P> c #4F5050", +"Q> c #0F100F", +"R> c #39393A", +"S> c #110F13", +"T> c #060505", +"U> c #090B09", +"V> c #1B1B1B", +"W> c #0E0C10", +"X> c #141515", +"Y> c #7E7E7E", +"Z> c #6D6C6C", +"`> c #100D10", +" , c #131414", +"., c #131413", +"+, c #101110", +"@, c #1C1B1D", +"#, c #555655", +"$, c #131113", +"%, c #141414", +"&, c #111212", +"*, c #373738", +"=, c #ABABAB", +"-, c #151616", +";, c #121113", +">, c #575657", +",, c #707071", +"', c #1C1B1C", +"), c #DEDDDE", +"!, c #808080", +"~, c #D9D9D9", +"{, c #888889", +"], c #8C8D8C", +"^, c #0F100E", +"/, c #6F6E6F", +"(, c #505050", +"_, c #141014", +":, c #A8A7A8", +"<, c #2E2E2F", +"[, c #130F12", +"}, c #0D0E0F", +"|, c #727273", +"1, c #8B8B8B", +"2, c #0E0B0F", +"3, c #120E12", +"4, c #BBBABB", +"5, c #C0BFC0", +"6, c #363736", +"7, c #ACACAC", +"8, c #171717", +"9, c #080609", +"0, c #0C090C", +"a, c #100C10", +"b, c #888788", +"c, c #0C090D", +"d, c #D6D6D7", +"e, c #A8A8A9", +"f, c #8B8A8B", +"g, c #141415", +"h, c #636363", +"i, c #D7D6D7", +"j, c #090B0A", +"k, c #404040", +"l, c #4E4E4E", +"m, c #121114", +"n, c #373837", +"o, c #0D0C10", +"p, c #212221", +"q, c #5F6060", +"r, c #282828", +"s, c #525252", +"t, c #131115", +"u, c #0F1010", +"v, c #515251", +"w, c #8B8B8C", +"x, c #030204", +"y, c #222221", +"z, c #090908", +"A, c #7D7D7C", +"B, c #8A8A8B", +"C, c #D3D3D4", +"D, c #060608", +"E, c #8A8B8B", +"F, c #080807", +"G, c #6E6D6E", +"H, c #171817", +"I, c #545453", +"J, c #D9D8D9", +"K, c #101011", +"L, c #222323", +"M, c #737473", +"N, c #303030", +"O, c #676768", +"P, c #434343", +"Q, c #424243", +"R, c #9F9F9F", +"S, c #141314", +"T, c #3C3B3C", +"U, c #272627", +"V, c #585858", +"W, c #434243", +"X, c #151516", +"Y, c #535453", +"Z, c #5D5D5E", +"`, c #9B9B9B", +" ' c #DEDFDE", +".' c #959695", +"+' c #5A5A5A", +"@' c #747574", +"#' c #B2B2B2", +"$' c #2B2B2C", +"%' c #1E1E1F", +"&' c #585859", +"*' c #BFBEBF", +"=' c #A8A8A7", +"-' c #242425", +";' c #3C3C3C", +">' c #8E8F8F", +",' c #585658", +"'' c #5E5E5F", +")' c #A3A3A3", +"!' c #949393", +"~' c #0C0A0B", +"{' c #323132", +"]' c #989998", +"^' c #6D6D6E", +"/' c #474747", +"(' c #949394", +"_' c #979797", +":' c #3B3B3B", +"<' c #454545", +"[' c #111112", +"}' c #A9A8A9", +"|' c #323232", +"1' c #949494", +"2' c #59595A", +"3' c #C3C2C3", +"4' c #3A393A", +"5' c #949495", +"6' c #8C8C8D", +"7' c #121213", +"8' c #282929", +"9' c #343535", +"0' c #5A5A5B", +"a' c #202121", +"b' c #131214", +"c' c #454445", +"d' c #302F30", +"e' c #C6C6C6", +"f' c #252425", +"g' c #959596", +"h' c #141216", +"i' c #131215", +"j' c #D1D1D2", +"k' c #CFCFD0", +"l' c #8F908F", +"m' c #CCCDCD", +"n' c #0E0F0D", +"o' c #10110F", +"p' c #1D1E1D", +"q' c #B6B7B7", +"r' c #FBFCFB", +"s' c #5B5B5C", +"t' c #141315", +"u' c #5D5C5E", +"v' c #646365", +"w' c #1A1C1C", +"x' c #EEEEED", +"y' c #EDEDEE", +"z' c #4A4A4B", +"A' c #4A4A4A", +"B' c #4A494A", +"C' c #49494A", +"D' c #4A494B", +"E' c #4B4A4C", +"F' c #313032", +"G' c #151317", +"H' c #17161A", +"I' c #161418", +"J' c #151417", +"K' c #141316", +"L' c #1A191B", +"M' c #2C2B2D", +"N' c #383839", +"O' c #4B4A4B", +"P' c #403F40", +"Q' c #4C4B4C", +"R' c #484849", +"S' c #474847", +"T' c #494A49", +"U' c #4A4B4A", +"V' c #2D2E2E", +"W' c #383939", +"X' c #4C4C4C", +"Y' c #4C4D4C", +"Z' c #4A4B4B", +"`' c #484949", +" ) c #494A4A", +".) c #464646", +"+) c #4C4B4D", +"@) c #4D4C4E", +"#) c #4D4C4D", +"$) c #1D1C1E", +"%) c #373637", +"&) c #4D4B4D", +"*) c #4E4C4E", +"=) c #4F4E4F", +"-) c #090907", +";) c #454645", +">) c #4B4C4C", +",) c #4B4B4B", +"') c #2E2F2E", +")) c #111210", +"!) c #131513", +"~) c #141513", +"{) c #131512", +"]) c #434444", +"^) c #484948", +"/) c #474848", +"() c #494948", +"_) c #454544", +":) c #0B0E0D", +"<) c #0A0D0C", +"[) c #090C0B", +"}) c #7A7B7B", +"|) c #050607", +"1) c #464746", +"2) c #454646", +"3) c #171518", +"4) c #161518", +"5) c #151316", +"6) c #282628", +"7) c #514F51", +"8) c #4F4E50", +"9) c #4E4D4F", +"0) c #504E50", +"a) c #29272A", +"b) c #141215", +"c) c #3E3F40", +"d) c #0E0E0D", +"e) c #4B494B", +"f) c #1A1A1C", +"g) c #494849", +"h) c #3D3C3D", +"i) c #131114", +"j) c #4B4A4A", +"k) c #4B4C4A", +"l) c #4A4B49", +"m) c #4A4A49", +"n) c #353635", +"o) c #9B9A9B", +"p) c #171519", +"q) c #19171B", +"r) c #18161A", +"s) c #151418", +"t) c #111113", +"u) c #131013", +"v) c #141114", +"w) c #151215", +"x) c #0B0D0A", +"y) c #0B0C09", +"z) c #151714", +"A) c #181A18", +"B) c #191B18", +"C) c #181917", +"D) c #171816", +"E) c #0E100E", +"F) c #656666", +"G) c #313232", +"H) c #0A0A09", +"I) c #0B0F0E", +"J) c #6C6D6D", +"K) c #373939", +"L) c #070908", +"M) c #1A191C", +"N) c #19161A", +"O) c #191619", +"P) c #171417", +"Q) c #110E11", +"R) c #120F12", +"S) c #151216", +"T) c #18171A", +"U) c #101212", +"V) c #0B0E0F", +"W) c #424343", +"X) c #5B5C5C", +"Y) c #333434", +"Z) c #0B0B0A", +"`) c #0E0E0C", +" ! c #0D0E0B", +".! c #0B0B09", +"+! c #161419", +"@! c #19181C", +"#! c #1B191C", +"$! c #1C1A1E", +"%! c #1B1A1E", +"&! c #1A191D", +"*! c #1A181C", +"=! c #0B0D0B", +"-! c #101210", +";! c #111413", +">! c #1A1B19", +",! c #181918", +"'! c #151716", +")! c #181519", +"!! c #19171A", +"~! c #1A181B", +"{! c #19181B", +"]! c #1B181C", +"^! c #1C191D", +"/! c #1A171B", +"(! c #1A171A", +"_! c #1B181B", +":! c #1B191D", +"~ c #0E120F", +",~ c #0D100D", +"'~ c #0C100D", +")~ c #0D0F10", +"!~ c #0C0C0A", +"~~ c #211E22", +"{~ c #232125", +"]~ c #212024", +"^~ c #1F1E22", +"/~ c #1D1C20", +"(~ c #1D1C1F", +"_~ c #1F1B1F", +":~ c #1D1B1F", +"<~ c #1D1A1F", +"[~ c #1E1A1F", +"}~ c #1F1B20", +"|~ c #1F1B21", +"1~ c #1F1D22", +"2~ c #090A0C", +"3~ c #151817", +"4~ c #0B0D0F", +"5~ c #161417", +"6~ c #171619", +"7~ c #161517", +"8~ c #161519", +"9~ c #121115", +"0~ c #151416", +"a~ c #151517", +"b~ c #161617", +"c~ c #131412", +"d~ c #161614", +"e~ c #050507", +"f~ c #17151A", +"g~ c #18161B", +"h~ c #19171C", +"i~ c #1D1B1D", +"j~ c #1E1C1E", +"k~ c #1F1E21", +"l~ c #201E23", +"m~ c #1E1C21", +"n~ c #1D1B20", +"o~ c #231F24", +"p~ c #221E22", +"q~ c #1C1A1F", +"r~ c #1E1D21", +"s~ c #201E22", +"t~ c #222024", +"u~ c #211F24", +"v~ c #20221F", +"w~ c #20211F", +"x~ c #1A1D1C", +"y~ c #171B1A", +"z~ c #141815", +"A~ c #171B18", +"B~ c #161A18", +"C~ c #262126", +"D~ c #262227", +"E~ c #272328", +"F~ c #242227", +"G~ c #242327", +"H~ c #252327", +"I~ c #262127", +"J~ c #242325", +"K~ c #141613", +"L~ c #1D1F1C", +"M~ c #1B1D1B", +"N~ c #171917", +"O~ c #161717", +"P~ c #191B1A", +"Q~ c #151715", +"R~ c #181C19", +"S~ c #141817", +"T~ c #0E1211", +"U~ c #111512", +"V~ c #121613", +"W~ c #090C0D", +"X~ c #080C0C", +"Y~ c #0C1010", +"Z~ c #0A0A08", +"`~ c #111311", +" { c #10130F", +".{ c #242226", +"+{ c #232226", +"@{ c #232126", +"#{ c #222025", +"${ c #222125", +"%{ c #232025", +"&{ c #241F25", +"*{ c #211F23", +"={ c #221F23", +"-{ c #07080A", +";{ c #101415", +">{ c #111514", +",{ c #131618", +"'{ c #151314", +"){ c #1A181D", +"!{ c #1B191E", +"~{ c #191719", +"{{ c #19181A", +"]{ c #1A181A", +"^{ c #161615", +"/{ c #171716", +"({ c #181817", +"_{ c #1A1A19", +":{ c #1A1A18", +"<{ c #191B19", +"[{ c #181A17", +"}{ c #07090A", +"|{ c #18151A", +"1{ c #1E1D1F", +"2{ c #1F1D20", +"3{ c #201E21", +"4{ c #221F24", +"5{ c #252126", +"6{ c #242025", +"7{ c #232026", +"8{ c #242126", +"9{ c #221F25", +"0{ c #232024", +"a{ c #161516", +"b{ c #171916", +"c{ c #151A19", +"d{ c #151A18", +"e{ c #1A1E1C", +"f{ c #1B201E", +"g{ c #1C201E", +"h{ c #29242A", +"i{ c #2A252B", +"j{ c #2B262C", +"k{ c #2B282D", +"l{ c #2A272D", +"m{ c #2B272C", +"n{ c #2C272D", +"o{ c #2A272B", +"p{ c #1E1E1D", +"q{ c #161915", +"r{ c #191C18", +"s{ c #181916", +"t{ c #151915", +"u{ c #151814", +"v{ c #151615", +"w{ c #1C1E1B", +"x{ c #191A1A", +"y{ c #161816", +"z{ c #131615", +"A{ c #0F1312", +"B{ c #101413", +"C{ c #161917", +"D{ c #161A16", +"E{ c #141714", +"F{ c #131613", +"G{ c #121512", +"H{ c #0C0E10", +"I{ c #141813", +"J{ c #151713", +"K{ c #201F21", +"L{ c #27262A", +"M{ c #272529", +"N{ c #272429", +"O{ c #272329", +"P{ c #262328", +"Q{ c #27242A", +"R{ c #28252A", +"S{ c #282329", +"T{ c #231F25", +"U{ c #221E24", +"V{ c #242127", +"W{ c #181B1B", +"X{ c #121617", +"Y{ c #181B1A", +"Z{ c #1F1C22", +"`{ c #201D23", +" ] c #1F1C21", +".] c #1E1B20", +"+] c #1C1A1C", +"@] c #171617", +"#] c #1B1B19", +"$] c #1E1F1C", +"%] c #1C1D1A", +"&] c #1B1C19", +"*] c #1A1B18", +"=] c #1C1D1B", +"-] c #191A18", +";] c #10120F", +">] c #151513", +",] c #0F0F0D", +"'] c #060A09", +")] c #171517", +"!] c #1A161A", +"~] c #211F22", +"{] c #231E24", +"]] c #252328", +"^] c #272228", +"/] c #262429", +"(] c #28262A", +"_] c #262529", +":] c #262228", +"<] c #1F1C20", +"[] c #212320", +"}] c #1C1F1D", +"|] c #1B201D", +"1] c #1F2320", +"2] c #212622", +"3] c #1A1F1B", +"4] c #282328", +"5] c #2A252A", +"6] c #2A262B", +"7] c #2E292F", +"8] c #2F2A30", +"9] c #302B31", +"0] c #292429", +"a] c #2C282D", +"b] c #312A32", +"c] c #312D32", +"d] c #2B2A2C", +"e] c #1E201B", +"f] c #181A15", +"g] c #1C1E18", +"h] c #20231C", +"i] c #1B1D18", +"j] c #151A16", +"k] c #171C18", +"l] c #161A17", +"m] c #151816", +"n] c #161817", +"o] c #0D1110", +"p] c #151815", +"q] c #161814", +"r] c #171815", +"s] c #0E0F11", +"t] c #0D0E10", +"u] c #252723", +"v] c #262823", +"w] c #282A25", +"x] c #272927", +"y] c #2B292D", +"z] c #29252A", +"A] c #252128", +"B] c #1B1F1E", +"C] c #121517", +"D] c #121618", +"E] c #1C1F1E", +"F] c #131315", +"G] c #252026", +"H] c #221D23", +"I] c #1C1C1B", +"J] c #1D1D1B", +"K] c #1E1E1C", +"L] c #1C1C1A", +"M] c #121310", +"N] c #080B0A", +"O] c #27252A", +"P] c #252228", +"Q] c #252127", +"R] c #28262B", +"S] c #1F2220", +"T] c #1F2421", +"U] c #232824", +"V] c #222723", +"W] c #181D19", +"X] c #262329", +"Y] c #322D33", +"Z] c #2D282E", +"`] c #302D31", +" ^ c #2C2B2C", +".^ c #262725", +"+^ c #262824", +"@^ c #232520", +"#^ c #1E201A", +"$^ c #1C1E19", +"%^ c #191E1A", +"&^ c #1B201C", +"*^ c #1A1D1B", +"=^ c #1A1C18", +"-^ c #1B1D19", +";^ c #181A19", +">^ c #1B1D1C", +",^ c #202221", +"'^ c #242623", +")^ c #2B2D2A", +"!^ c #343631", +"~^ c #30322D", +"{^ c #2F312C", +"]^ c #2D2F2B", +"^^ c #2A2B29", +"/^ c #2D2B2C", +"(^ c #302C31", +"_^ c #2B262B", +":^ c #2E2A2F", +"<^ c #242026", +"[^ c #1F1F22", +"}^ c #14181A", +"|^ c #1C1C1E", +"1^ c #1D1B1E", +"2^ c #1B191B", +"3^ c #1C1D1C", +"4^ c #1F201F", +"5^ c #1F1F1D", +"6^ c #222421", +"7^ c #1F201D", +"8^ c #1D1D1C", +"9^ c #0A0C09", +"0^ c #101112", +"a^ c #0F1011", +"b^ c #1C1B1E", +"c^ c #1D1A20", +"d^ c #282429", +"e^ c #28252B", +"f^ c #252227", +"g^ c #191918", +"h^ c #1E201C", +"i^ c #21231F", +"j^ c #242722", +"k^ c #272B28", +"l^ c #292E2C", +"m^ c #202524", +"n^ c #222624", +"o^ c #262A27", +"p^ c #1D211E", +"q^ c #1A1E1B", +"r^ c #111412", +"s^ c #2A262C", +"t^ c #2B262D", +"u^ c #302B30", +"v^ c #2B292B", +"w^ c #282928", +"x^ c #272A27", +"y^ c #292B28", +"z^ c #2D2F2C", +"A^ c #292B27", +"B^ c #20221C", +"C^ c #1D1F1B", +"D^ c #1C201B", +"E^ c #1C201C", +"F^ c #1F231F", +"G^ c #1C201D", +"H^ c #191C1A", +"I^ c #181B19", +"J^ c #131411", +"K^ c #131416", +"L^ c #161718", +"M^ c #171819", +"N^ c #131415", +"O^ c #121314", +"P^ c #111214", +"Q^ c #0F1013", +"R^ c #0E0F12", +"S^ c #0D0E12", +"T^ c #11130F", +"U^ c #222422", +"V^ c #262827", +"W^ c #2C2E2C", +"X^ c #2D2E2B", +"Y^ c #2F302D", +"Z^ c #31332E", +"`^ c #32342F", +" / c #333530", +"./ c #2F312D", +"+/ c #2B2C29", +"@/ c #222121", +"#/ c #2B282C", +"$/ c #2D292E", +"%/ c #29252B", +"&/ c #28242A", +"*/ c #2C282E", +"=/ c #181C1B", +"-/ c #080706", +";/ c #222023", +">/ c #1D1C1C", +",/ c #222220", +"'/ c #1E1E1B", +")/ c #21211E", +"!/ c #232421", +"~/ c #21221F", +"{/ c #20211E", +"]/ c #1F201E", +"^/ c #20201D", +"// c #1E191F", +"(/ c #29262A", +"_/ c #262822", +":/ c #2C2F29", +"( c #262721", +",( c #2B2C26", +"'( c #252621", +")( c #20211D", +"!( c #242422", +"~( c #21211F", +"{( c #232321", +"]( c #21221D", +"^( c #23241F", +"/( c #222320", +"(( c #191D19", +"_( c #181C18", +":( c #2B272D", +"<( c #252724", +"[( c #353733", +"}( c #292B29", +"|( c #272928", +"1( c #202322", +"2( c #1C1C1D", +"3( c #1C1C1F", +"4( c #2C2A2E", +"5( c #191917", +"6( c #2A2E2A", +"7( c #343632", +"8( c #2C2E2B", +"9( c #292A24", +"0( c #232420", +"a( c #1E231E", +"b( c #1F2420", +"c( c #191D1A", +"d( c #0C0B0F", +"e( c #222127", +"f( c #404148", +"g( c #65666D", +"h( c #808288", +"i( c #8E9196", +"j( c #B3B7BB", +"k( c #BDC1C5", +"l( c #C4C7CB", +"m( c #CED2D5", +"n( c #D5D9DC", +"o( c #D4DBDE", +"p( c #D6DDE0", +"q( c #D6DEE1", +"r( c #D1DCE0", +"s( c #D2DEE2", +"t( c #D5E0E4", +"u( c #CEDCE3", +"v( c #C6DBE2", +"w( c #C9DBE4", +"x( c #D0DDE4", +"y( c #D0DDE3", +"z( c #D1DDE2", +"A( c #D2DEE4", +"B( c #D0DBE3", +"C( c #CAD8E0", +"D( c #C5D4DC", +"E( c #BCCDD6", +"F( c #AABBC5", +"G( c #99A8B4", +"H( c #8A96A4", +"I( c #6E7784", +"J( c #69707A", +"K( c #4C5059", +"L( c #383D42", +"M( c #1D2023", +"N( c #0B0A0A", +"O( c #272926", +"P( c #31322E", +"Q( c #363733", +"R( c #33332F", +"S( c #343633", +"T( c #333532", +"U( c #353734", +"V( c #282529", +"W( c #1C1E20", +"X( c #1C1E1F", +"Y( c #1D211F", +"Z( c #252925", +"`( c #1A1819", +" _ c #262427", +"._ c #1D181E", +"+_ c #1C181E", +"@_ c #20201F", +"#_ c #212120", +"$_ c #262722", +"%_ c #2D2E28", +"&_ c #292A25", +"*_ c #22231F", +"=_ c #242523", +"-_ c #21221E", +";_ c #20211C", +">_ c #29262C", +",_ c #2A272C", +"'_ c #29262B", +")_ c #222321", +"!_ c #181719", +"~_ c #2C282C", +"{_ c #2F2A2F", +"]_ c #2E292E", +"^_ c #28272B", +"/_ c #1D1D20", +"(_ c #222522", +"__ c #292D2A", +":_ c #393B35", +"<_ c #3C3E38", +"[_ c #353731", +"}_ c #242521", +"|_ c #202421", +"1_ c #38383B", +"2_ c #636368", +"3_ c #81838A", +"4_ c #A6AAB2", +"5_ c #B7BFC8", +"6_ c #C4CCD6", +"7_ c #CED6DF", +"8_ c #D4DCE2", +"9_ c #D6DEE4", +"0_ c #DAE0E5", +"a_ c #DCE0E5", +"b_ c #DCE1E5", +"c_ c #DADFE3", +"d_ c #D7E0E2", +"e_ c #D5DFE1", +"f_ c #D5DEE0", +"g_ c #D7DDE2", +"h_ c #D7DDE3", +"i_ c #D6DCE2", +"j_ c #CDDCE4", +"k_ c #C1D8E0", +"l_ c #BED8E4", +"m_ c #C5D9E4", +"n_ c #C8DBE4", +"o_ c #CADDE6", +"p_ c #CCDEE3", +"q_ c #CDDEE3", +"r_ c #CDDDE4", +"s_ c #C9DCE3", +"t_ c #C3DAE1", +"u_ c #BBD6DF", +"v_ c #B3D1DC", +"w_ c #ADCCDA", +"x_ c #A7C3D7", +"y_ c #9CB8CD", +"z_ c #90ADC2", +"A_ c #84A1B6", +"B_ c #809AAE", +"C_ c #7F93A5", +"D_ c #8994A1", +"E_ c #747981", +"F_ c #6C6C71", +"G_ c #3C3B3E", +"H_ c #080A07", +"I_ c #32332E", +"J_ c #363732", +"K_ c #373835", +"L_ c #3C3D3A", +"M_ c #393B38", +"N_ c #2D302D", +"O_ c #171A17", +"P_ c #222123", +"Q_ c #262428", +"R_ c #29272B", +"S_ c #27282C", +"T_ c #26252B", +"U_ c #272227", +"V_ c #181419", +"W_ c #0F0F12", +"X_ c #111213", +"Y_ c #141616", +"Z_ c #1D1F1F", +"`_ c #202422", +" : c #2A2E2B", +".: c #232022", +"+: c #272428", +"@: c #1E1A20", +"#: c #212220", +"$: c #242522", +"%: c #242621", +"&: c #2A2B26", +"*: c #1C1D19", +"=: c #1A1E1A", +"-: c #191C19", +";: c #171715", +">: c #201F23", +",: c #2A282D", +"': c #2D292F", +"): c #030202", +"!: c #010000", +"~: c #171316", +"{: c #1E1A1D", +"]: c #282427", +"^: c #1B1A1B", +"/: c #2A2E2D", +"(: c #2D302C", +"_: c #383A34", +":: c #43463E", +"<: c #42453D", +"[: c #393C35", +"}: c #2F322D", +"|: c #2A2C29", +"1: c #262825", +"2: c #242824", +"3: c #202521", +"4: c #1A1D1A", +"5: c #1F1F1E", +"6: c #57585A", +"7: c #828286", +"8: c #97989E", +"9: c #A8A9B2", +"0: c #AEB0BD", +"a: c #AFB7C2", +"b: c #B0BDC6", +"c: c #B6C2CC", +"d: c #B8C4CE", +"e: c #BAC5CF", +"f: c #C1CCD3", +"g: c #CED7DC", +"h: c #D7DEE2", +"i: c #DCE1E4", +"j: c #DCE1E3", +"k: c #DBE0E2", +"l: c #DADFE2", +"m: c #DADEE1", +"n: c #D8DEE2", +"o: c #D1DBE0", +"p: c #BFD7E3", +"q: c #BFD8E6", +"r: c #C1D8E6", +"s: c #C2D9E6", +"t: c #C4DAE4", +"u: c #C4DAE3", +"v: c #C6DBE4", +"w: c #C6D9E4", +"x: c #C3D8E4", +"y: c #BFD7E4", +"z: c #BED7E4", +"A: c #C1D8E3", +"B: c #C0D5E0", +"C: c #B6CDDC", +"D: c #97B7D0", +"E: c #88ADC7", +"F: c #7FA0B9", +"G: c #8097AD", +"H: c #8996A8", +"I: c #9BA0AC", +"J: c #A3A3AA", +"K: c #979498", +"L: c #706D6F", +"M: c #525154", +"N: c #060609", +"O: c #333531", +"P: c #3D3E39", +"Q: c #42433D", +"R: c #3B3C36", +"S: c #363A35", +"T: c #292D2C", +"U: c #252226", +"V: c #262325", +"W: c #1F1D1E", +"X: c #251F21", +"Y: c #272327", +"Z: c #282527", +"`: c #272427", +" < c #141517", +".< c #1C1D1F", +"+< c #1A1C1B", +"@< c #1E1F20", +"#< c #22241F", +"$< c #252722", +"%< c #272725", +"&< c #1F201C", +"*< c #1E1F1A", +"=< c #0F100D", +"-< c #1A1A1D", +";< c #020101", +">< c #070506", +",< c #272B2A", +"'< c #3A3D35", +")< c #3C3F38", +"!< c #373935", +"~< c #2E302E", +"{< c #2B2F2B", +"]< c #242925", +"^< c #1C1F1C", +"/< c #131316", +"(< c #454547", +"_< c #757778", +":< c #939798", +"<< c #9EA1A5", +"[< c #A5A8AF", +"}< c #A5A6B1", +"|< c #A3A4B1", +"1< c #9FA1AF", +"2< c #9C9FAE", +"3< c #9A9FAE", +"4< c #9CA0AF", +"5< c #A1A8B6", +"6< c #AFBAC6", +"7< c #BDCAD2", +"8< c #CBD6DD", +"9< c #D3DCE1", +"0< c #D9DEE2", +"a< c #DBE0E4", +"b< c #DFE0E4", +"c< c #E0E2E6", +"d< c #DFE1E5", +"e< c #DAE1E4", +"f< c #D9E0E3", +"g< c #D8DFE3", +"h< c #D7DFE3", +"i< c #D6DFE2", +"j< c #C6DAE2", +"k< c #BDD7E4", +"l< c #BCD7E2", +"m< c #BDD9E4", +"n< c #BBD9E6", +"o< c #B3D5E4", +"p< c #ADD3E3", +"q< c #ADD3E5", +"r< c #B4D4E6", +"s< c #BCD7E3", +"t< c #C6D8E2", +"u< c #C9D9E3", +"v< c #C5D5E0", +"w< c #B8CCDB", +"x< c #ADC2D2", +"y< c #A5B8C8", +"z< c #A1B2C1", +"A< c #A3AEBD", +"B< c #A9AEB8", +"C< c #AFB0B8", +"D< c #B1B0B7", +"E< c #B0ADB0", +"F< c #A6A1A3", +"G< c #7E7C7C", +"H< c #4D4C4C", +"I< c #08070C", +"J< c #3F413B", +"K< c #454640", +"L< c #3F403A", +"M< c #292D2B", +"N< c #0F1314", +"O< c #30271B", +"P< c #41362E", +"Q< c #454447", +"R< c #323333", +"S< c #2C2422", +"T< c #2C2626", +"U< c #121015", +"V< c #20221D", +"W< c #272823", +"X< c #252523", +"Y< c #322D32", +"Z< c #060604", +"`< c #2F322E", +" [ c #2F322C", +".[ c #31342D", +"+[ c #333631", +"@[ c #2C2F2D", +"#[ c #272D29", +"$[ c #2A302C", +"%[ c #1F221F", +"&[ c #252628", +"*[ c #555759", +"=[ c #808285", +"-[ c #9A9D9F", +";[ c #9A9EA3", +">[ c #9AA1AB", +",[ c #9BA1AE", +"'[ c #9A9EAD", +")[ c #979AA9", +"![ c #9497A8", +"~[ c #9396A8", +"{[ c #9396A7", +"][ c #9395A7", +"^[ c #9798AA", +"/[ c #9CA2B1", +"([ c #A4AFBD", +"_[ c #ACBCC8", +":[ c #B7C5D0", +"<[ c #C4CFD7", +"[[ c #CFD7DD", +"}[ c #D8DDE1", +"|[ c #D9DFE3", +"1[ c #DADFE4", +"2[ c #D9DFE2", +"3[ c #D8E0E1", +"4[ c #D6DFE3", +"5[ c #D5DEE3", +"6[ c #D7DFE2", +"7[ c #CFDEE3", +"8[ c #C3DAE4", +"9[ c #C1D9E4", +"0[ c #C0D9E3", +"a[ c #BED9E4", +"b[ c #BCD9E4", +"c[ c #BBD9E4", +"d[ c #BBD7E1", +"e[ c #B5D2DE", +"f[ c #B1D2DF", +"g[ c #B4D3E0", +"h[ c #B5D2E0", +"i[ c #AECCDC", +"j[ c #AAC7D5", +"k[ c #ABC6D2", +"l[ c #ABC4D0", +"m[ c #A6BCCA", +"n[ c #9EB3C2", +"o[ c #95ABBB", +"p[ c #8EA3B5", +"q[ c #91A0B1", +"r[ c #9DA7B3", +"s[ c #A8AEB8", +"t[ c #B0B1BA", +"u[ c #AEA9AD", +"v[ c #A69D9E", +"w[ c #A09597", +"x[ c #9D9393", +"y[ c #908A89", +"z[ c #828080", +"A[ c #4B494A", +"B[ c #31332F", +"C[ c #373933", +"D[ c #3D3E38", +"E[ c #3D3F39", +"F[ c #363A36", +"G[ c #282C2A", +"H[ c #1D1B1A", +"I[ c #29241C", +"J[ c #373633", +"K[ c #4E535B", +"L[ c #4E4A48", +"M[ c #35291F", +"N[ c #251F1E", +"O[ c #110F14", +"P[ c #141217", +"Q[ c #222420", +"R[ c #1D1D1A", +"S[ c #1E1F1B", +"T[ c #1C1E1A", +"U[ c #20221E", +"V[ c #1A151B", +"W[ c #2C2A2F", +"X[ c #312B30", +"Y[ c #2C292B", +"Z[ c #030604", +"`[ c #020304", +" } c #030306", +".} c #070805", +"+} c #232524", +"@} c #262B27", +"#} c #2F3430", +"$} c #303531", +"%} c #292E2A", +"&} c #1D211D", +"*} c #09070B", +"=} c #08060B", +"-} c #0F1014", +";} c #282A2B", +">} c #5D5F5F", +",} c #8A8F90", +"'} c #949A9E", +")} c #939AA0", +"!} c #969DA4", +"~} c #9CA3AA", +"{} c #9DA3AC", +"]} c #999FAA", +"^} c #9498A6", +"/} c #9092A3", +"(} c #8E91A1", +"_} c #9091A1", +":} c #8E91A2", +"<} c #9095A6", +"[} c #9098A7", +"}} c #939DAD", +"|} c #9AA6B6", +"1} c #AAB5C4", +"2} c #BCC5CF", +"3} c #C9CFD6", +"4} c #D1D7DC", +"5} c #CDD5DB", +"6} c #CAD3DA", +"7} c #CED6DC", +"8} c #D3DBDF", +"9} c #D6E0E5", +"0} c #D5DFE5", +"a} c #D5DFE4", +"b} c #D6E0E3", +"c} c #D7E1E4", +"d} c #D7E1E5", +"e} c #D1DEE4", +"f} c #D1DEE5", +"g} c #D1DFE5", +"h} c #CCDDE3", +"i} c #C8DCE3", +"j} c #C5DAE1", +"k} c #C0D7DE", +"l} c #BBD4DD", +"m} c #B7D1DB", +"n} c #B0CAD7", +"o} c #A4BDCF", +"p} c #99B3C5", +"q} c #93ADBF", +"r} c #8EA7BC", +"s} c #87A0B4", +"t} c #839BAE", +"u} c #8396A8", +"v} c #8191A5", +"w} c #808EA0", +"x} c #8992A2", +"y} c #969BA8", +"z} c #A4A4AD", +"A} c #AAA8AC", +"B} c #A6A0A0", +"C} c #998F90", +"D} c #8A7E7E", +"E} c #8A7F7D", +"F} c #908885", +"G} c #938E8A", +"H} c #7C7977", +"I} c #07060A", +"J} c #141412", +"K} c #3A3D37", +"L} c #3A3C37", +"M} c #373B37", +"N} c #212624", +"O} c #18181A", +"P} c #1D1D21", +"Q} c #181611", +"R} c #191511", +"S} c #1B171A", +"T} c #191618", +"U} c #141416", +"V} c #232124", +"W} c #171719", +"X} c #242622", +"Y} c #282924", +"Z} c #0C100E", +"`} c #292729", +" | c #2B292C", +".| c #080504", +"+| c #050704", +"@| c #050705", +"#| c #050304", +"$| c #050708", +"%| c #242124", +"&| c #202222", +"*| c #262A29", +"=| c #252928", +"-| c #252A26", +";| c #1F221E", +">| c #39393B", +",| c #6C6E6F", +"'| c #898D8E", +")| c #8C9393", +"!| c #889193", +"~| c #8B9298", +"{| c #9297A1", +"]| c #969BA5", +"^| c #999DA7", +"/| c #9A9DA7", +"(| c #9C9FAB", +"_| c #9D9FAB", +":| c #A1A1AC", +"<| c #A3A4AE", +"[| c #A5A6B0", +"}| c #A4A7AF", +"|| c #9FA3AD", +"1| c #9297A5", +"2| c #898FA1", +"3| c #868DA1", +"4| c #838CA0", +"5| c #9BA5B3", +"6| c #A9B4C0", +"7| c #AEBAC5", +"8| c #AEBAC6", +"9| c #A8B5C2", +"0| c #A6B5C3", +"a| c #AFBCC9", +"b| c #B9C6D1", +"c| c #C6D3DC", +"d| c #CFDCE3", +"e| c #D1DFE6", +"f| c #D3DEE4", +"g| c #D4DFE5", +"h| c #D4DEE5", +"i| c #D3DFE5", +"j| c #CEDDE3", +"k| c #CADDE4", +"l| c #C8DAE2", +"m| c #C6D9E2", +"n| c #C3D8E2", +"o| c #C0D6DF", +"p| c #B4C9D4", +"q| c #A2B6C6", +"r| c #95ABBD", +"s| c #8CA2B6", +"t| c #8399B0", +"u| c #7A92A8", +"v| c #768EA3", +"w| c #778A9F", +"x| c #78879C", +"y| c #798399", +"z| c #7E8497", +"A| c #8A8D9E", +"B| c #9797A4", +"C| c #A3A1A8", +"D| c #A9A4A7", +"E| c #A29B9E", +"F| c #93898A", +"G| c #837978", +"H| c #847B78", +"I| c #898480", +"J| c #918E8A", +"K| c #959490", +"L| c #8D8C88", +"M| c #5E5D59", +"N| c #30322E", +"O| c #373936", +"P| c #343834", +"Q| c #222625", +"R| c #181716", +"S| c #231E16", +"T| c #231F1A", +"U| c #1B191A", +"V| c #232521", +"W| c #242620", +"X| c #2B2D26", +"Y| c #1F201B", +"Z| c #131614", +"`| c #141313", +" 1 c #252324", +".1 c #292529", +"+1 c #2C292F", +"@1 c #222325", +"#1 c #23211C", +"$1 c #352918", +"%1 c #3C2F1A", +"&1 c #43413D", +"*1 c #454749", +"=1 c #302B24", +"-1 c #2B1E11", +";1 c #120D09", +">1 c #232023", +",1 c #1F1E20", +"'1 c #252929", +")1 c #202425", +"!1 c #1E2222", +"~1 c #1E2320", +"{1 c #1D221E", +"]1 c #1C211D", +"^1 c #242926", +"/1 c #202522", +"(1 c #1E211F", +"_1 c #3E4041", +":1 c #686E6C", +"<1 c #7E8884", +"[1 c #808B88", +"}1 c #818B8C", +"|1 c #838C90", +"11 c #879196", +"21 c #8E959D", +"31 c #989AA6", +"41 c #A1A4AF", +"51 c #ABADB8", +"61 c #B1B2BC", +"71 c #B2B3BE", +"81 c #B5B5BF", +"91 c #BEBFC4", +"01 c #CACACF", +"a1 c #CECED3", +"b1 c #CDCFD4", +"c1 c #C4C7CD", +"d1 c #AAB0B8", +"e1 c #9A9FAC", +"f1 c #939AA8", +"g1 c #9AA2B1", +"h1 c #ADBBC8", +"i1 c #A9B9C9", +"j1 c #A1B0C1", +"k1 c #94A5B7", +"l1 c #8D9FB3", +"m1 c #95A8BB", +"n1 c #A4B7C8", +"o1 c #B2C6D4", +"p1 c #BFD0DA", +"q1 c #C8D9E1", +"r1 c #CDDCE2", +"s1 c #D3DEE6", +"t1 c #D3DDE5", +"u1 c #D1DDE4", +"v1 c #CFDEE5", +"w1 c #CBDEE4", +"x1 c #C3D8DE", +"y1 c #BBD3DB", +"z1 c #B7D2DC", +"A1 c #B6D0DC", +"B1 c #ADC5D4", +"C1 c #9CB3C7", +"D1 c #8FA6BA", +"E1 c #8399AE", +"F1 c #778DA5", +"G1 c #6E859D", +"H1 c #6C829A", +"I1 c #6D7E97", +"J1 c #6C7A93", +"K1 c #68728A", +"L1 c #676B81", +"M1 c #6D6F83", +"N1 c #7A798C", +"O1 c #8C8A96", +"P1 c #9E9AA2", +"Q1 c #A9A3A8", +"R1 c #AAA2A4", +"S1 c #A69D9C", +"T1 c #9B9291", +"U1 c #999390", +"V1 c #9E9B96", +"W1 c #A09D97", +"X1 c #9E9B95", +"Y1 c #9C9B94", +"Z1 c #8F8E8A", +"`1 c #61615E", +" 2 c #282827", +".2 c #373B38", +"+2 c #161415", +"@2 c #1D1611", +"#2 c #1F1A14", +"$2 c #242125", +"%2 c #221F22", +"&2 c #1A161B", +"*2 c #222521", +"=2 c #252721", +"-2 c #272921", +";2 c #1F211C", +">2 c #181816", +",2 c #1A1918", +"'2 c #211F20", +")2 c #282629", +"!2 c #242525", +"~2 c #322E2C", +"{2 c #4B3D2C", +"]2 c #59513D", +"^2 c #6C7479", +"/2 c #8D9297", +"(2 c #694D2E", +"_2 c #1B1409", +":2 c #020402", +"<2 c #030502", +"[2 c #252025", +"}2 c #212123", +"|2 c #3D4344", +"12 c #636E6D", +"22 c #76827E", +"32 c #7C8882", +"42 c #7E8985", +"52 c #808B8D", +"62 c #838C93", +"72 c #8A919B", +"82 c #9397A2", +"92 c #9FA2AD", +"02 c #ACAFB8", +"a2 c #B0B3BC", +"b2 c #B3B5BE", +"c2 c #B7B8BF", +"d2 c #BFC0C6", +"e2 c #C9CACF", +"f2 c #D3D4D8", +"g2 c #DAD9DD", +"h2 c #DAD8DB", +"i2 c #D2D1D5", +"j2 c #C9CAD0", +"k2 c #BEC3CA", +"l2 c #B8BFC8", +"m2 c #B8C1CB", +"n2 c #BCC8D2", +"o2 c #B7C7D1", +"p2 c #ABBBCA", +"q2 c #9EAEC2", +"r2 c #98A8BB", +"s2 c #98A9BD", +"t2 c #96AAC0", +"u2 c #91A9C2", +"v2 c #93ACC2", +"w2 c #9EB8CA", +"x2 c #ADC4D3", +"y2 c #BCD0DB", +"z2 c #CADBE1", +"A2 c #CEDFE4", +"B2 c #C4D7DE", +"C2 c #C9DAE1", +"D2 c #C7DBE4", +"E2 c #CADCE4", +"F2 c #CADAE1", +"G2 c #B8CED7", +"H2 c #A8C5D1", +"I2 c #9DBAC8", +"J2 c #93ACBC", +"K2 c #8BA2B5", +"L2 c #849AB0", +"M2 c #7B93AA", +"N2 c #6F88A3", +"O2 c #677D99", +"P2 c #657894", +"Q2 c #647492", +"R2 c #64738F", +"S2 c #647089", +"T2 c #626780", +"U2 c #61637B", +"V2 c #63657C", +"W2 c #6C6D82", +"X2 c #797889", +"Y2 c #8C8693", +"Z2 c #989098", +"`2 c #A0989C", +" 3 c #A79DA1", +".3 c #A99FA2", +"+3 c #ADA5A6", +"@3 c #AFABA7", +"#3 c #ABA9A4", +"$3 c #A5A49F", +"%3 c #A19E98", +"&3 c #9D9A93", +"*3 c #8F8D84", +"=3 c #605F5D", +"-3 c #262525", +";3 c #353939", +">3 c #282D2D", +",3 c #121214", +"'3 c #17191B", +")3 c #151315", +"!3 c #2B262A", +"~3 c #2A2529", +"{3 c #252125", +"]3 c #252326", +"^3 c #0B0C0E", +"/3 c #232723", +"(3 c #262923", +"_3 c #282923", +":3 c #22231E", +"<3 c #161618", +"[3 c #2C292E", +"}3 c #201F24", +"|3 c #202125", +"13 c #232528", +"23 c #262629", +"33 c #2A2929", +"43 c #2D302F", +"53 c #22292F", +"63 c #201D1A", +"73 c #080906", +"83 c #040604", +"93 c #0A0B0D", +"03 c #0B0B0E", +"a3 c #030305", +"b3 c #000002", +"c3 c #5A6564", +"d3 c #6A7C78", +"e3 c #748580", +"f3 c #7F8D87", +"g3 c #889191", +"h3 c #8C9197", +"i3 c #92979F", +"j3 c #9BA1A8", +"k3 c #A4A9AE", +"l3 c #AAADB3", +"m3 c #ACAEB6", +"n3 c #ADB0B9", +"o3 c #AFB2BB", +"p3 c #B2B4BE", +"q3 c #B7B9C3", +"r3 c #C1C3CA", +"s3 c #C6C9CF", +"t3 c #C8CAD0", +"u3 c #D2D2D7", +"v3 c #D8D7DB", +"w3 c #D5D6D8", +"x3 c #CED1D5", +"y3 c #C8CED4", +"z3 c #C0C9D3", +"A3 c #B9C5CF", +"B3 c #B2BECB", +"C3 c #AAB7C5", +"D3 c #A5B2C2", +"E3 c #A8B4C5", +"F3 c #A6B7C7", +"G3 c #A4B7C9", +"H3 c #9FB5C7", +"I3 c #96AEC3", +"J3 c #97AFC3", +"K3 c #A1B8C8", +"L3 c #ADC5D3", +"M3 c #B7D0DC", +"N3 c #B0CCD9", +"O3 c #ADCBDA", +"P3 c #BAD1DC", +"Q3 c #BED2DB", +"R3 c #BFD5DE", +"S3 c #BFD8E2", +"T3 c #B8D3E2", +"U3 c #B6D1E0", +"V3 c #BAD3E0", +"W3 c #C2D8E2", +"X3 c #C0D7E1", +"Y3 c #AFC9D4", +"Z3 c #9FBAC7", +"`3 c #8CA5B5", +" 4 c #8299AC", +".4 c #798DA6", +"+4 c #7384A0", +"@4 c #6D7C98", +"#4 c #687793", +"$4 c #61728D", +"%4 c #5D6C87", +"&4 c #5E6884", +"*4 c #5D647F", +"=4 c #686B85", +"-4 c #676983", +";4 c #676980", +">4 c #6D6E84", +",4 c #727186", +"'4 c #757185", +")4 c #7C7888", +"!4 c #89828B", +"~4 c #93878E", +"{4 c #9A8D90", +"]4 c #A19898", +"^4 c #A7A09F", +"/4 c #AAA4A3", +"(4 c #A8A4A0", +"_4 c #A3A099", +":4 c #9B9890", +"<4 c #959389", +"[4 c #86837C", +"}4 c #5B5853", +"|4 c #312D2B", +"14 c #131112", +"24 c #0A0A0D", +"34 c #1C1E1C", +"44 c #2E3031", +"54 c #27292B", +"64 c #201D17", +"74 c #1E1B19", +"84 c #1A151A", +"94 c #242225", +"04 c #2D282C", +"a4 c #2C272B", +"b4 c #212022", +"c4 c #232623", +"d4 c #2A2D27", +"e4 c #2C2E28", +"f4 c #24251F", +"g4 c #23241E", +"h4 c #1D1E1C", +"i4 c #2F2A31", +"j4 c #1F2024", +"k4 c #1F2226", +"l4 c #1F2228", +"m4 c #222425", +"n4 c #252627", +"o4 c #26262C", +"p4 c #242426", +"q4 c #1A1515", +"r4 c #191816", +"s4 c #17181A", +"t4 c #151617", +"u4 c #241F26", +"v4 c #19181D", +"w4 c #0D0C11", +"x4 c #343438", +"y4 c #495352", +"z4 c #5C6C69", +"A4 c #667B77", +"B4 c #738681", +"C4 c #86948E", +"D4 c #989F9A", +"E4 c #9C9FA0", +"F4 c #9EA0A5", +"G4 c #A4A6AA", +"H4 c #A6A9AF", +"I4 c #A4A7B0", +"J4 c #A5A5AF", +"K4 c #AFB0B9", +"L4 c #B1B3BC", +"M4 c #AFB2BC", +"N4 c #AFB2BD", +"O4 c #AFB3BC", +"P4 c #ADB1B9", +"Q4 c #B4B8C0", +"R4 c #BFC2C8", +"S4 c #C2C4CA", +"T4 c #BBBEC7", +"U4 c #B6BBC4", +"V4 c #BBC1CB", +"W4 c #BAC4CD", +"X4 c #B8C4CF", +"Y4 c #BEC7CF", +"Z4 c #BFC9CF", +"`4 c #BFCAD1", +" 5 c #BAC8D1", +".5 c #B4C2CC", +"+5 c #ABBAC8", +"@5 c #A1AEC1", +"#5 c #9AA7B8", +"$5 c #9DADBF", +"%5 c #A0B4C8", +"&5 c #8FA8BF", +"*5 c #91B3C8", +"=5 c #9FC2CE", +"-5 c #A4C4D1", +";5 c #A0C0D0", +">5 c #96B7CA", +",5 c #90B2C9", +"'5 c #92B3C9", +")5 c #92B2C6", +"!5 c #98B7CA", +"~5 c #9CBACC", +"{5 c #91AFBF", +"]5 c #87A9BB", +"^5 c #7A9BB1", +"/5 c #6D8BA4", +"(5 c #607996", +"_5 c #5C6F8C", +":5 c #596681", +"<5 c #5B637F", +"[5 c #5A5F7D", +"}5 c #555977", +"|5 c #515472", +"15 c #505470", +"25 c #585E78", +"35 c #5E637D", +"45 c #61637E", +"55 c #676A82", +"65 c #72758B", +"75 c #777A8E", +"85 c #6C6D83", +"95 c #68677D", +"05 c #767181", +"a5 c #8A7F89", +"b5 c #928286", +"c5 c #908284", +"d5 c #928788", +"e5 c #999191", +"f5 c #A29E9C", +"g5 c #A7A49F", +"h5 c #A3A09A", +"i5 c #9E9B92", +"j5 c #96928B", +"k5 c #898680", +"l5 c #7C7771", +"m5 c #585453", +"n5 c #1E201E", +"o5 c #2D2F2F", +"p5 c #292A2C", +"q5 c #2C2319", +"r5 c #2E2516", +"s5 c #221E19", +"t5 c #1A1B1D", +"u5 c #16181B", +"v5 c #191A1D", +"w5 c #1B1814", +"x5 c #221A11", +"y5 c #21170C", +"z5 c #120F0E", +"A5 c #0C0D10", +"B5 c #100D11", +"C5 c #312C31", +"D5 c #2E292D", +"E5 c #242024", +"F5 c #1B1A1F", +"G5 c #111014", +"H5 c #212421", +"I5 c #202420", +"J5 c #212521", +"K5 c #232622", +"L5 c #242723", +"M5 c #2A2C27", +"N5 c #23251F", +"O5 c #141612", +"P5 c #151916", +"Q5 c #272729", +"R5 c #2C272E", +"S5 c #2D282F", +"T5 c #212125", +"U5 c #202226", +"V5 c #212225", +"W5 c #212226", +"X5 c #222227", +"Y5 c #32281E", +"Z5 c #2C271D", +"`5 c #1E1F21", +" 6 c #1C1D1E", +".6 c #1F1E1E", +"+6 c #262226", +"@6 c #1E201F", +"#6 c #1C1E1D", +"$6 c #292427", +"%6 c #312C30", +"&6 c #414646", +"*6 c #546562", +"=6 c #59716C", +"-6 c #637A77", +";6 c #6E817E", +">6 c #7B8A83", +",6 c #8B968E", +"'6 c #98A09C", +")6 c #A3A9A7", +"!6 c #A9AEAF", +"~6 c #A5A9AF", +"{6 c #A2A6AE", +"]6 c #A4A7AE", +"^6 c #A9ACB3", +"/6 c #B0B0B7", +"(6 c #B3B3BB", +"_6 c #ADB0BA", +":6 c #A6A9B5", +"<6 c #A0A4AF", +"[6 c #A3A6B1", +"}6 c #ACB0B9", +"|6 c #B7BBC4", +"16 c #BCC3CB", +"26 c #C5CDD4", +"36 c #CAD2D9", +"46 c #CFD5DA", +"56 c #D2D6DB", +"66 c #D2D7DC", +"76 c #D3D8DC", +"86 c #D6DBDE", +"96 c #D9DEE1", +"06 c #D7DCDF", +"a6 c #D2D8DB", +"b6 c #C4D1D7", +"c6 c #A5BCCB", +"d6 c #8BA1BA", +"e6 c #8D9EB3", +"f6 c #98A1B5", +"g6 c #96A2B3", +"h6 c #8694A9", +"i6 c #8093A9", +"j6 c #819AAF", +"k6 c #778EA4", +"l6 c #7289A0", +"m6 c #6B849C", +"n6 c #617994", +"o6 c #637993", +"p6 c #647891", +"q6 c #667C97", +"r6 c #748BA5", +"s6 c #738BA2", +"t6 c #6F8BA3", +"u6 c #67839D", +"v6 c #5C7691", +"w6 c #506682", +"x6 c #4A5A78", +"y6 c #475170", +"z6 c #494C6C", +"A6 c #4F516F", +"B6 c #515371", +"C6 c #4D506E", +"D6 c #484D6B", +"E6 c #515B76", +"F6 c #606A85", +"G6 c #5C617D", +"H6 c #585D76", +"I6 c #5E6279", +"J6 c #696B82", +"K6 c #686A81", +"L6 c #5D6078", +"M6 c #5C5E76", +"N6 c #6C6779", +"O6 c #817681", +"P6 c #877B7F", +"Q6 c #827678", +"R6 c #7E7375", +"S6 c #8A8384", +"T6 c #9D9794", +"U6 c #A39D99", +"V6 c #A19D96", +"W6 c #9F9E96", +"X6 c #9A9891", +"Y6 c #908B84", +"Z6 c #7C766F", +"`6 c #69645F", +" 7 c #3E3B3D", +".7 c #1A171C", +"+7 c #0F0C13", +"@7 c #272824", +"#7 c #292C2A", +"$7 c #1B150F", +"%7 c #1F1B13", +"&7 c #1F2021", +"*7 c #1E1E22", +"=7 c #19191B", +"-7 c #28221C", +";7 c #453317", +">7 c #4E4539", +",7 c #3D4247", +"'7 c #1F1D1D", +")7 c #14130A", +"!7 c #100B10", +"~7 c #2B292E", +"{7 c #222622", +"]7 c #141915", +"^7 c #171B17", +"/7 c #292C28", +"(7 c #2C2C2D", +"_7 c #2A272E", +":7 c #202024", +"<7 c #1E1F23", +"[7 c #1F2225", +"}7 c #1E1C1C", +"|7 c #161316", +"17 c #302B2F", +"27 c #312F32", +"37 c #404344", +"47 c #53615E", +"57 c #586D68", +"67 c #5C746E", +"77 c #627978", +"87 c #667977", +"97 c #73837B", +"07 c #86928A", +"a7 c #969F9A", +"b7 c #A2A8A7", +"c7 c #A5A8AB", +"d7 c #A3A5AD", +"e7 c #A4A6AF", +"f7 c #A7A9B2", +"g7 c #A8ABB2", +"h7 c #A7ABB6", +"i7 c #A0A6B6", +"j7 c #969CAB", +"k7 c #9699A8", +"l7 c #9E9EAB", +"m7 c #ACADB8", +"n7 c #BDBEC7", +"o7 c #C4C7CF", +"p7 c #C5CAD1", +"q7 c #C7CDD4", +"r7 c #DDE2E5", +"s7 c #E0E2E5", +"t7 c #E0E3E6", +"u7 c #E1E4E7", +"v7 c #E1E3E6", +"w7 c #E0E3E4", +"x7 c #E0E2E4", +"y7 c #DDE0E2", +"z7 c #CFDCE1", +"A7 c #B0C9DC", +"B7 c #87AECE", +"C7 c #81A2C4", +"D7 c #95AAC2", +"E7 c #9AA2B0", +"F7 c #9094A8", +"G7 c #7D849A", +"H7 c #747C94", +"I7 c #5A627C", +"J7 c #4D5672", +"K7 c #4D5875", +"L7 c #475070", +"M7 c #454D6B", +"N7 c #4C5370", +"O7 c #465271", +"P7 c #566481", +"Q7 c #596882", +"R7 c #5A6783", +"S7 c #57637F", +"T7 c #525C79", +"U7 c #4A5471", +"V7 c #424C69", +"W7 c #3F4664", +"X7 c #414665", +"Y7 c #444866", +"Z7 c #454967", +"`7 c #424665", +" 8 c #3C4161", +".8 c #3E4866", +"+8 c #505C79", +"@8 c #5A6380", +"#8 c #565B78", +"$8 c #555672", +"%8 c #555771", +"&8 c #575973", +"*8 c #575B75", +"=8 c #535773", +"-8 c #51526A", +";8 c #5F5C71", +">8 c #736A79", +",8 c #776A74", +"'8 c #6B5C61", +")8 c #68595A", +"!8 c #7F706F", +"~8 c #968A87", +"{8 c #978E89", +"]8 c #958F8A", +"^8 c #9B9790", +"/8 c #9D9990", +"(8 c #939087", +"_8 c #7D786F", +":8 c #71675F", +"<8 c #575250", +"[8 c #2E2E31", +"}8 c #272726", +"|8 c #232425", +"18 c #251A10", +"28 c #4B3A26", +"38 c #636361", +"48 c #4C555E", +"58 c #312D29", +"68 c #272520", +"78 c #1E2123", +"88 c #1D1D1F", +"98 c #19191C", +"08 c #221F1A", +"a8 c #2E2C23", +"b8 c #50565F", +"c8 c #646B75", +"d8 c #3E3522", +"e8 c #251A0D", +"f8 c #272528", +"g8 c #080806", +"h8 c #222520", +"i8 c #131510", +"j8 c #171915", +"k8 c #1B1F1C", +"l8 c #262B26", +"m8 c #2C312B", +"n8 c #2E332D", +"o8 c #303530", +"p8 c #2C2C2E", +"q8 c #28262C", +"r8 c #28272D", +"s8 c #26252A", +"t8 c #1D1E22", +"u8 c #1F1F24", +"v8 c #1F2025", +"w8 c #1C1C20", +"x8 c #232426", +"y8 c #202224", +"z8 c #322C32", +"A8 c #332E34", +"B8 c #3A383C", +"C8 c #4F5756", +"D8 c #576B66", +"E8 c #58746D", +"F8 c #5E7771", +"G8 c #607671", +"H8 c #657873", +"I8 c #71817D", +"J8 c #7F8B88", +"K8 c #8B9295", +"L8 c #969BA3", +"M8 c #989AA5", +"N8 c #9696A3", +"O8 c #9897A5", +"P8 c #9F9FAA", +"Q8 c #A2A4AC", +"R8 c #A6A9B0", +"S8 c #A7AAB1", +"T8 c #999DA9", +"U8 c #8F94A5", +"V8 c #9A9CAA", +"W8 c #A3A3B0", +"X8 c #A6A6B3", +"Y8 c #B1B1BD", +"Z8 c #BEBEC7", +"`8 c #C3C4CA", +" 9 c #BEC1C7", +".9 c #BFC4C9", +"+9 c #D3D8DB", +"@9 c #E2E3E4", +"#9 c #E2E3E5", +"$9 c #E3E4E5", +"%9 c #E1E3E2", +"&9 c #DEE1E0", +"*9 c #D9DFE0", +"=9 c #CAD6E0", +"-9 c #AAC8D8", +";9 c #98BFD9", +">9 c #87B2CB", +",9 c #86A5BD", +"'9 c #8E97A9", +")9 c #83889B", +"!9 c #7C7F95", +"~9 c #5F637E", +"{9 c #454B68", +"]9 c #414967", +"^9 c #3F4567", +"/9 c #404464", +"(9 c #494C6A", +"_9 c #464B6C", +":9 c #484E6E", +"<9 c #474D6A", +"[9 c #464B69", +"}9 c #444765", +"|9 c #424564", +"19 c #414664", +"29 c #3F4562", +"39 c #404564", +"49 c #3E4363", +"59 c #393E5E", +"69 c #363B5B", +"79 c #353A5C", +"89 c #3D4767", +"99 c #526482", +"09 c #5E7390", +"a9 c #586583", +"b9 c #505573", +"c9 c #4D4F6B", +"d9 c #4F506D", +"e9 c #565772", +"f9 c #5C5D75", +"g9 c #5D5C74", +"h9 c #5E5D72", +"i9 c #686474", +"j9 c #70656D", +"k9 c #6E5C5F", +"l9 c #644E50", +"m9 c #5D4747", +"n9 c #6B5755", +"o9 c #847672", +"p9 c #8D827D", +"q9 c #827B74", +"r9 c #858377", +"s9 c #8C8A7F", +"t9 c #87857A", +"u9 c #7E786D", +"v9 c #6F675E", +"w9 c #5B534D", +"x9 c #413B3D", +"y9 c #2F2A2E", +"z9 c #211D21", +"A9 c #131116", +"B9 c #0C0807", +"C9 c #20180C", +"D9 c #38342F", +"E9 c #626975", +"F9 c #685D4B", +"G9 c #503C1F", +"H9 c #2D2925", +"I9 c #1C1A20", +"J9 c #18181B", +"K9 c #30303D", +"L9 c #2D2A23", +"M9 c #201A0F", +"N9 c #131716", +"O9 c #121615", +"P9 c #0F1310", +"Q9 c #1A1E19", +"R9 c #191D1B", +"S9 c #313534", +"T9 c #313433", +"U9 c #323430", +"V9 c #2D2F2A", +"W9 c #2E2E2B", +"X9 c #2B2A2A", +"Y9 c #202023", +"Z9 c #1F1F23", +"`9 c #1F1E23", +" 0 c #1A211E", +".0 c #151618", +"+0 c #232325", +"@0 c #151917", +"#0 c #1E1C1D", +"$0 c #252123", +"%0 c #323035", +"&0 c #343137", +"*0 c #352F35", +"=0 c #3D4043", +"-0 c #576765", +";0 c #5E7369", +">0 c #607872", +",0 c #5B7071", +"'0 c #58696C", +")0 c #5D6A6F", +"!0 c #616E75", +"~0 c #67727E", +"{0 c #6D7584", +"]0 c #767A8C", +"^0 c #77798C", +"/0 c #727489", +"(0 c #787A90", +"_0 c #838597", +":0 c #92939F", +"<0 c #A0A2AB", +"[0 c #A6AAB5", +"}0 c #9C9DAA", +"|0 c #A4A3B2", +"10 c #AAA9B7", +"20 c #A2A4B0", +"30 c #A7A9B5", +"40 c #B4B4C0", +"50 c #BCBDC7", +"60 c #B9BEC6", +"70 c #C7CDD2", +"80 c #DEE0E0", +"90 c #E1E3E3", +"00 c #E2E3E3", +"a0 c #E2E4E3", +"b0 c #E3E4E4", +"c0 c #E1E4E3", +"d0 c #DFE2E1", +"e0 c #DBE1DF", +"f0 c #DADFDD", +"g0 c #D3DADF", +"h0 c #B0CDDA", +"i0 c #74A3BF", +"j0 c #6492B4", +"k0 c #7588A3", +"l0 c #8A8FA1", +"m0 c #8A8DA0", +"n0 c #85889C", +"o0 c #6E7088", +"p0 c #4B4F6B", +"q0 c #3D4362", +"r0 c #393F60", +"s0 c #454A68", +"t0 c #51556D", +"u0 c #545775", +"v0 c #545978", +"w0 c #4E5672", +"x0 c #424867", +"y0 c #3B4060", +"z0 c #393D5F", +"A0 c #3B3F61", +"B0 c #3C4163", +"C0 c #3B4062", +"D0 c #373B5E", +"E0 c #323659", +"F0 c #2C3053", +"G0 c #292E50", +"H0 c #2F3456", +"I0 c #404B6C", +"J0 c #576987", +"K0 c #5E6E8D", +"L0 c #535F7E", +"M0 c #4A4E6D", +"N0 c #464967", +"O0 c #464966", +"P0 c #5E5D73", +"Q0 c #706D7D", +"R0 c #74707D", +"S0 c #746C78", +"T0 c #776A76", +"U0 c #6C5D62", +"V0 c #574547", +"W0 c #4E3A39", +"X0 c #58433E", +"Y0 c #75615A", +"Z0 c #7B7064", +"`0 c #706B5E", +" a c #747265", +".a c #757467", +"+a c #727365", +"@a c #7A776A", +"#a c #6B6459", +"$a c #60554D", +"%a c #514948", +"&a c #383239", +"*a c #2E2C31", +"=a c #222022", +"-a c #040406", +";a c #1F1C14", +">a c #362612", +",a c #31271A", +"'a c #1D1E21", +")a c #1B1C1F", +"!a c #1B1B1E", +"~a c #121111", +"{a c #0E0D08", +"]a c #13100E", +"^a c #171914", +"/a c #1A1C17", +"(a c #191B17", +"_a c #21241F", +":a c #202320", +"b c #2A2E51", +",b c #3A4062", +"'b c #4C5A79", +")b c #586685", +"!b c #545B7B", +"~b c #4C506F", +"{b c #494D6B", +"]b c #464A67", +"^b c #484963", +"/b c #58576B", +"(b c #726976", +"_b c #786C76", +":b c #73656F", +"c c #72778D", +",c c #7C7E95", +"'c c #7F8096", +")c c #86889B", +"!c c #8B8EA1", +"~c c #7A7D92", +"{c c #65677F", +"]c c #5C607C", +"^c c #525774", +"/c c #6D7188", +"(c c #757586", +"_c c #76768A", +":c c #6B6D86", +"d c #404363", +",d c #404262", +"'d c #424864", +")d c #4F5470", +"!d c #6C6C83", +"~d c #938E9B", +"{d c #A8A4A8", +"]d c #B0B2B8", +"^d c #AFB3BE", +"/d c #9FA3B0", +"(d c #9295A6", +"_d c #8D919F", +":d c #8A8E9D", +"e c #282227", +",e c #272025", +"'e c #0D1111", +")e c #141611", +"!e c #131511", +"~e c #070906", +"{e c #161813", +"]e c #101410", +"^e c #0D100E", +"/e c #232624", +"(e c #191A15", +"_e c #161713", +":e c #1D1F1E", +"f c #373B5F", +",f c #303458", +"'f c #2E3557", +")f c #3A4162", +"!f c #4C4864", +"~f c #665A69", +"{f c #645760", +"]f c #605257", +"^f c #5F4E4F", +"/f c #583F3C", +"(f c #483337", +"_f c #41333F", +":f c #51393D", +"g c #DBDDDB", +",g c #DADCD9", +"'g c #CFD6DB", +")g c #9AABBB", +"!g c #6D758B", +"~g c #7D8397", +"{g c #86899D", +"]g c #60657F", +"^g c #4B4F6E", +"/g c #595E79", +"(g c #74758F", +"_g c #82869A", +":g c #666B83", +"h c #2D3336", +",h c #2E3D3F", +"'h c #334948", +")h c #506461", +"!h c #4D555F", +"~h c #40425A", +"{h c #454663", +"]h c #4F506C", +"^h c #51506A", +"/h c #64647B", +"(h c #646479", +"_h c #4B4D69", +":h c #4E526D", +"i c #4A4331", +",i c #665C52", +"'i c #5E5251", +")i c #2F272C", +"!i c #0B080A", +"~i c #010302", +"{i c #1C1919", +"]i c #070A0A", +"^i c #13110B", +"/i c #493115", +"(i c #5A4830", +"_i c #677381", +":i c #282B33", +"j c #6D6E88", +",j c #6D6F86", +"'j c #5B5D78", +")j c #535774", +"!j c #3B4061", +"~j c #363B5D", +"{j c #33385A", +"]j c #4E5271", +"^j c #5D627E", +"/j c #626881", +"(j c #505471", +"_j c #4D516F", +":j c #545873", +"k c #6B4C22", +",k c #473117", +"'k c #0B0807", +")k c #2B312F", +"!k c #5E6D69", +"~k c #697774", +"{k c #6A7575", +"]k c #5D646E", +"^k c #4D5068", +"/k c #4B4E6A", +"(k c #484864", +"_k c #44445F", +":k c #5D5A6F", +"l c #363F2C", +",l c #353A25", +"'l c #333521", +")l c #43422F", +"!l c #605D49", +"~l c #6E6956", +"{l c #5E5745", +"]l c #61564B", +"^l c #5A514B", +"/l c #1F1A18", +"(l c #020204", +"_l c #120F0F", +":l c #30353A", +"m c #5E627D", +",m c #585A77", +"'m c #525573", +")m c #4A4D6D", +"!m c #515271", +"~m c #60627F", +"{m c #6F708B", +"]m c #6E7089", +"^m c #868A9C", +"/m c #5C5F7C", +"(m c #414565", +"_m c #474C6A", +":m c #4D5270", +"n c #312D35", +",n c #211F25", +"'n c #222229", +")n c #1E1D22", +"!n c #080C0B", +"~n c #1F2625", +"{n c #3E4C49", +"]n c #6A7573", +"^n c #929899", +"/n c #98989F", +"(n c #717385", +"_n c #4E516B", +":n c #4F5069", +"o c #828675", +",o c #929284", +"'o c #9A998E", +")o c #A4A39A", +"!o c #B0ADA6", +"~o c #A4A398", +"{o c #82796C", +"]o c #755E4F", +"^o c #644937", +"/o c #61493D", +"(o c #594C47", +"_o c #1A1412", +":o c #010203", +"p c #4B4D6C", +",p c #555674", +"'p c #696A86", +")p c #777991", +"!p c #7E8196", +"~p c #8A8C9F", +"{p c #34385C", +"]p c #34395C", +"^p c #3E4364", +"/p c #5E617C", +"(p c #7F8198", +"_p c #84879B", +":p c #8B8E9E", +"